From 7da95bd28a30d2c4f6fb9086d9203962414e2903 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Tue, 6 Jan 2015 18:48:35 +0100 Subject: [PATCH 0001/6300] Introduce missing call to request verification, so unknown address types and commands are handled properly, allow for extra socket data after the request for fast request sending, it will just be forwarded on I2PConnect --- SOCKS.cpp | 19 ++++++++++--------- SOCKS.h | 4 +++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/SOCKS.cpp b/SOCKS.cpp index 29ef3955..2abe3830 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -149,12 +149,12 @@ namespace proxy m_state = nstate; } - void SOCKSHandler::ValidateSOCKSRequest() { + bool SOCKSHandler::ValidateSOCKSRequest() { if ( m_cmd != CMD_CONNECT ) { //TODO: we need to support binds and other shit! LogPrint(eLogError,"--- SOCKS unsupported command: ", m_cmd); SocksRequestFailed(SOCKS5_CMD_UNSUP); - return; + return false; } //TODO: we may want to support other address types! if ( m_addrtype != ADDR_DNS ) { @@ -167,14 +167,15 @@ namespace proxy break; } SocksRequestFailed(SOCKS5_ADDR_UNSUP); - return; + return false; } //TODO: we may want to support other domains if(m_addrtype == ADDR_DNS && m_address.dns.ToString().find(".i2p") == std::string::npos) { LogPrint(eLogError,"--- SOCKS invalid hostname: ", m_address.dns.ToString()); SocksRequestFailed(SOCKS5_ADDR_UNSUP); - return; + return false; } + return true; } bool SOCKSHandler::HandleData(uint8_t *sock_buff, std::size_t len) @@ -315,10 +316,10 @@ namespace proxy } sock_buff++; len--; - if (len && m_state == DONE) { - LogPrint(eLogError,"--- SOCKS rejected because we can't handle extra data"); - SocksRequestFailed(SOCKS5_GEN_FAIL); - return false; + if (m_state == DONE) { + m_remaining_data_len = len; + m_remaining_data = sock_buff; + return ValidateSOCKSRequest(); } } return true; @@ -363,7 +364,7 @@ namespace proxy LogPrint (eLogInfo,"--- SOCKS New I2PTunnel connection"); auto connection = std::make_shared((i2p::client::I2PTunnel *)m_parent, m_sock, m_stream); m_parent->AddConnection (connection); - connection->I2PConnect (); + connection->I2PConnect (m_remaining_data,m_remaining_data_len); Done(); } else diff --git a/SOCKS.h b/SOCKS.h index 3fd0f52f..599f18ad 100644 --- a/SOCKS.h +++ b/SOCKS.h @@ -94,7 +94,7 @@ namespace proxy void EnterState(state nstate, uint8_t parseleft = 1); bool HandleData(uint8_t *sock_buff, std::size_t len); - void ValidateSOCKSRequest(); + bool ValidateSOCKSRequest(); void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Done(); void Terminate(); @@ -114,8 +114,10 @@ namespace proxy SOCKSServer * m_parent; boost::asio::ip::tcp::socket * m_sock; std::shared_ptr m_stream; + uint8_t *m_remaining_data; //Data left to be sent uint8_t m_response[7+max_socks_hostname_size]; address m_address; //Address + std::size_t m_remaining_data_len; //Size of the data left to be sent uint32_t m_4aip; //Used in 4a requests uint16_t m_port; uint8_t m_command; From 63927fc1fa3658d15ee9ac8635fa405c29a40b39 Mon Sep 17 00:00:00 2001 From: iShift Date: Tue, 6 Jan 2015 23:52:13 +0300 Subject: [PATCH 0002/6300] fix deps for ubuntu/debian fix https://github.com/PrivacySolutions/i2pd/issues/137 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2f55ca5f..7e354a76 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ Testing First, build it. +On Ubuntu/Debian based +* sudo apt-get install libboost-dev libboost-filesystem-dev libboost-program-options-dev libboost-regex-dev libcrypto++-dev libboost-date-time-dev * $ cd i2pd * $ make From bc78460f636206cfffd7ec73b397a01a68aa3e80 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Tue, 6 Jan 2015 20:27:55 +0100 Subject: [PATCH 0003/6300] Enter state BEFORE reading data to avoid race conditions --- SOCKS.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SOCKS.h b/SOCKS.h index 599f18ad..f0d5ceec 100644 --- a/SOCKS.h +++ b/SOCKS.h @@ -133,7 +133,7 @@ namespace proxy SOCKSHandler(SOCKSServer * parent, boost::asio::ip::tcp::socket * sock) : m_parent(parent), m_sock(sock), m_stream(nullptr), m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4), dead(false) - { m_address.ip = 0; AsyncSockRead(); EnterState(GET_SOCKSV); } + { m_address.ip = 0; EnterState(GET_SOCKSV); AsyncSockRead(); } ~SOCKSHandler() { Terminate(); } }; From 5e8d28abbabb90ff25ae47302e26c72710d33e95 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Tue, 6 Jan 2015 22:40:45 +0100 Subject: [PATCH 0004/6300] Reorder SOCKS headers for cleanness --- SOCKS.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SOCKS.cpp b/SOCKS.cpp index 2abe3830..103d82b1 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -1,11 +1,11 @@ +#include +#include #include "SOCKS.h" #include "Identity.h" #include "NetDb.h" #include "Destination.h" #include "ClientContext.h" #include "I2PEndian.h" -#include -#include namespace i2p { From 2fca02816173dd2ec818521de4bd93814f93a4f4 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Tue, 6 Jan 2015 22:41:29 +0100 Subject: [PATCH 0005/6300] Remove unnecessary header --- SOCKS.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/SOCKS.cpp b/SOCKS.cpp index 103d82b1..6354dbfa 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -2,7 +2,6 @@ #include #include "SOCKS.h" #include "Identity.h" -#include "NetDb.h" #include "Destination.h" #include "ClientContext.h" #include "I2PEndian.h" From 634718d6b41be6577fc6948f35b0c108ee86f942 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Tue, 6 Jan 2015 23:51:10 +0100 Subject: [PATCH 0006/6300] Detect null stream on I2PConnect --- I2PTunnel.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index e003f7e9..b782a657 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -1,3 +1,4 @@ +#include #include "base64.h" #include "Log.h" #include "Destination.h" @@ -36,10 +37,13 @@ namespace client void I2PTunnelConnection::I2PConnect (const uint8_t * msg, size_t len) { - if (msg) - m_Stream->Send (msg, len); // connect and send - else - m_Stream->Send (m_Buffer, 0); // connect + if (m_Stream) + { + if (msg) + m_Stream->Send (msg, len); // connect and send + else + m_Stream->Send (m_Buffer, 0); // connect + } StreamReceive (); Receive (); } From 6aca908462d49e40c51313e908016d49b16d1994 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Wed, 7 Jan 2015 00:15:38 +0100 Subject: [PATCH 0007/6300] Initial HTTPProxy support by simply transferring control to a tunnel --- HTTPProxy.cpp | 270 ++++++++++++++++++++++++++++++++++++++++---------- HTTPProxy.h | 99 +++++++++++++----- 2 files changed, 291 insertions(+), 78 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index ae644b6a..4bc7aedc 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -1,87 +1,247 @@ +#include +#include #include #include - -#include "ClientContext.h" #include "HTTPProxy.h" +#include "Identity.h" +#include "Destination.h" +#include "ClientContext.h" +#include "I2PEndian.h" namespace i2p { namespace proxy { - void HTTPProxyConnection::parseHeaders(const std::string& h, std::vector
& hm) { - std::string str (h); - std::string::size_type idx; - std::string t; - int i = 0; - while( (idx=str.find ("\r\n")) != std::string::npos) { - t=str.substr (0,idx); - str.erase (0,idx+2); - if (t == "") - break; - idx=t.find(": "); - if (idx == std::string::npos) - { - std::cout << "Bad header line: " << t << std::endl; - break; - } - LogPrint ("Name: ", t.substr (0,idx), " Value: ", t.substr (idx+2)); - hm[i].name = t.substr (0,idx); - hm[i].value = t.substr (idx+2); - i++; + void HTTPProxyHandler::AsyncSockRead() + { + LogPrint(eLogDebug,"--- HTTP Proxy async sock read"); + if(m_sock) { + m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), + std::bind(&HTTPProxyHandler::HandleSockRecv, this, + std::placeholders::_1, std::placeholders::_2)); + } else { + LogPrint(eLogError,"--- HTTP Proxy no socket for read"); } } - void HTTPProxyConnection::ExtractRequest(request& r) + void HTTPProxyHandler::Done() { + if (m_parent) m_parent->RemoveHandler (shared_from_this ()); + } + + void HTTPProxyHandler::Terminate() { + if (dead.exchange(true)) return; + if (m_sock) { + LogPrint(eLogDebug,"--- HTTP Proxy close sock"); + m_sock->close(); + delete m_sock; + m_sock = nullptr; + } + Done(); + } + + /* All hope is lost beyond this point */ + //TODO: handle this apropriately + void HTTPProxyHandler::HTTPRequestFailed(/*HTTPProxyHandler::errTypes error*/) { - std::string requestString = m_Buffer; - int idx=requestString.find(" "); - std::string method = requestString.substr(0,idx); - requestString = requestString.substr(idx+1); - idx=requestString.find(" "); - std::string requestUrl = requestString.substr(0,idx); - LogPrint("method is: ", method, "\nRequest is: ", requestUrl); + std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n"; + boost::asio::async_write(*m_sock, boost::asio::buffer(response,response.size()), + std::bind(&HTTPProxyHandler::SentHTTPFailed, this, std::placeholders::_1)); + } + + void HTTPProxyHandler::EnterState(HTTPProxyHandler::state nstate) { + m_state = nstate; + } + + void HTTPProxyHandler::ExtractRequest() + { + LogPrint(eLogDebug,"--- HTTP Proxy method is: ", m_method, "\nRequest is: ", m_url); std::string server=""; std::string port="80"; boost::regex rHTTP("http://(.*?)(:(\\d+))?(/.*)"); boost::smatch m; std::string path; - if(boost::regex_search(requestUrl, m, rHTTP, boost::match_extra)) { + if(boost::regex_search(m_url, m, rHTTP, boost::match_extra)) { server=m[1].str(); if(m[2].str() != "") { port=m[3].str(); } path=m[4].str(); } - LogPrint("server is: ",server, " port is: ", port, "\n path is: ",path); - r.uri = path; - r.method = method; - r.host = server; - r.port = boost::lexical_cast(port); + LogPrint(eLogDebug,"--- HTTP Proxy server is: ",server, " port is: ", port, "\n path is: ",path); + m_address = server; + m_port = boost::lexical_cast(port); + m_path = path; } + bool HTTPProxyHandler::ValidateHTTPRequest() { + if ( m_version != "HTTP/1.0" ) { + //TODO: we want to support 1.1 in the future + LogPrint(eLogError,"--- HTTP Proxy unsupported version: ", m_version); + HTTPRequestFailed(); //TODO: send right stuff + return false; + } + return true; + } - void HTTPProxyConnection::RunRequest() + bool HTTPProxyHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { + ExtractRequest(); //TODO: parse earlier + if (!ValidateHTTPRequest()) return false; + m_request = m_method; + m_request.push_back(' '); + m_request += m_path; + m_request.push_back(' '); + m_request += m_version; + m_request.push_back('\r'); + m_request.push_back('\n'); + m_request.append(reinterpret_cast(http_buff),len); + return true; + } + + bool HTTPProxyHandler::HandleData(uint8_t *http_buff, std::size_t len) { - request r; - ExtractRequest(r); - parseHeaders(m_Buffer, r.headers); - size_t addressHelperPos = r.uri.find ("i2paddresshelper"); - if (addressHelperPos != std::string::npos) - { - // jump service - size_t addressPos = r.uri.find ("=", addressHelperPos); - if (addressPos != std::string::npos) - { - LogPrint ("Jump service for ", r.host, " found. Inserting to address book"); - auto base64 = r.uri.substr (addressPos + 1); - i2p::client::context.GetAddressBook ().InsertAddress (r.host, base64); + assert(len); // This should always be called with a least a byte left to parse + while (len > 0) { + //TODO: fallback to finding HOst: header if needed + switch (m_state) { + case GET_METHOD: + switch (*http_buff) { + case ' ': EnterState(GET_HOSTNAME); break; + default: m_method.push_back(*http_buff); break; + } + break; + case GET_HOSTNAME: + switch (*http_buff) { + case ' ': EnterState(GET_HTTPV); break; + default: m_url.push_back(*http_buff); break; + } + break; + case GET_HTTPV: + switch (*http_buff) { + case '\r': EnterState(GET_HTTPVNL); break; + default: m_version.push_back(*http_buff); break; + } + break; + case GET_HTTPVNL: + switch (*http_buff) { + case '\n': EnterState(DONE); break; + default: + LogPrint(eLogError,"--- HTTP Proxy rejected invalid request ending with: ", ((int)*http_buff)); + HTTPRequestFailed(); //TODO: add correct code + return false; + } + break; + default: + LogPrint(eLogError,"--- HTTP Proxy invalid state: ", m_state); + HTTPRequestFailed(); //TODO: add correct code 500 + return false; } - } - - LogPrint("Requesting ", r.host, ":", r.port, " with path ", r.uri, " and method ", r.method); - SendToAddress (r.host, r.port, m_Buffer, m_BufferLen); + http_buff++; + len--; + if (m_state == DONE) + return CreateHTTPRequest(http_buff,len); + } + return true; + } + + void HTTPProxyHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) + { + LogPrint(eLogDebug,"--- HTTP Proxy sock recv: ", len); + if(ecode) { + LogPrint(eLogWarning," --- HTTP Proxy sock recv got error: ", ecode); + Terminate(); + return; + } + + if (HandleData(m_http_buff, len)) { + if (m_state == DONE) { + LogPrint(eLogInfo,"--- HTTP Proxy requested: ", m_url); + m_parent->GetLocalDestination ()->CreateStream ( + std::bind (&HTTPProxyHandler::HandleStreamRequestComplete, + this, std::placeholders::_1), m_address, m_port); + } else { + AsyncSockRead(); + } + } + + } + + void HTTPProxyHandler::SentHTTPFailed(const boost::system::error_code & ecode) + { + if (!ecode) { + Terminate(); + } else { + LogPrint (eLogError,"--- HTTP Proxy Closing socket after sending failure because: ", ecode.message ()); + Terminate(); + } + } + + void HTTPProxyHandler::HandleStreamRequestComplete (std::shared_ptr stream) + { + if (stream) { + if (dead.exchange(true)) return; + LogPrint (eLogInfo,"--- HTTP Proxy New I2PTunnel connection"); + auto connection = std::make_shared((i2p::client::I2PTunnel *)m_parent, m_sock, stream); + m_parent->AddConnection (connection); + connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); + Done(); + } else { + LogPrint (eLogError,"--- HTTP Proxy Issue when creating the stream, check the previous warnings for more info."); + HTTPRequestFailed(); // TODO: Send correct error message host unreachable + } + } + + void HTTPProxyServer::Start () + { + m_Acceptor.listen (); + Accept (); + } + + void HTTPProxyServer::Stop () + { + m_Acceptor.close(); + m_Timer.cancel (); + ClearConnections (); + ClearHandlers(); + } + + void HTTPProxyServer::Accept () + { + auto newSocket = new boost::asio::ip::tcp::socket (GetService ()); + m_Acceptor.async_accept (*newSocket, std::bind (&HTTPProxyServer::HandleAccept, this, + std::placeholders::_1, newSocket)); + } + + void HTTPProxyServer::AddHandler (std::shared_ptr handler) { + std::unique_lock l(m_HandlersMutex); + m_Handlers.insert (handler); + } + + void HTTPProxyServer::RemoveHandler (std::shared_ptr handler) + { + std::unique_lock l(m_HandlersMutex); + m_Handlers.erase (handler); + } + + void HTTPProxyServer::ClearHandlers () + { + std::unique_lock l(m_HandlersMutex); + m_Handlers.clear (); + } + + void HTTPProxyServer::HandleAccept (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket) + { + if (!ecode) + { + LogPrint(eLogDebug,"--- HTTP Proxy accepted"); + AddHandler(std::make_shared (this, socket)); + Accept(); + } + else + { + LogPrint (eLogError,"--- HTTP Proxy Closing socket on accept because: ", ecode.message ()); + delete socket; + } } } } - diff --git a/HTTPProxy.h b/HTTPProxy.h index c9ccc31c..3fd1302e 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -1,42 +1,95 @@ #ifndef HTTP_PROXY_H__ #define HTTP_PROXY_H__ -#include -#include +#include +#include +#include #include -#include - -#include "HTTPServer.h" +#include +#include +#include "Identity.h" +#include "Streaming.h" +#include "I2PTunnel.h" namespace i2p { namespace proxy { - class HTTPProxyConnection : public i2p::util::HTTPConnection - { - public: - HTTPProxyConnection (boost::asio::ip::tcp::socket * socket): HTTPConnection(socket) { }; - protected: - void RunRequest(); - void parseHeaders(const std::string& h, std::vector
& hm); - void ExtractRequest(request& r); + const size_t http_buffer_size = 8192; + + class HTTPProxyServer; + class HTTPProxyHandler: public std::enable_shared_from_this { + private: + enum state { + GET_METHOD, + GET_HOSTNAME, + GET_HTTPV, + GET_HTTPVNL, //TODO: fallback to finding HOst: header if needed + DONE + }; + + void EnterState(state nstate); + bool HandleData(uint8_t *http_buff, std::size_t len); + void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); + void Done(); + void Terminate(); + void AsyncSockRead(); + void HTTPRequestFailed(/*std::string message*/); + void ExtractRequest(); + bool ValidateHTTPRequest(); + bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); + void SentHTTPFailed(const boost::system::error_code & ecode); + void HandleStreamRequestComplete (std::shared_ptr stream); + + uint8_t m_http_buff[http_buffer_size]; + HTTPProxyServer * m_parent; + 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 + std::atomic dead; //To avoid cleaning up multiple times + state m_state;//Parsing state + + public: + HTTPProxyHandler(HTTPProxyServer * parent, boost::asio::ip::tcp::socket * sock) : + m_parent(parent), m_sock(sock), dead(false) + { AsyncSockRead(); EnterState(GET_METHOD); } + ~HTTPProxyHandler() { Terminate(); } }; - class HTTPProxy : public i2p::util::HTTPServer + class HTTPProxyServer: public i2p::client::I2PTunnel { - public: - HTTPProxy (int port): HTTPServer(port) {}; + private: + std::set > m_Handlers; + boost::asio::ip::tcp::acceptor m_Acceptor; + boost::asio::deadline_timer m_Timer; + std::mutex m_HandlersMutex; private: - void CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket) - { - new HTTPProxyConnection(m_NewSocket); - } + + void Accept(); + void HandleAccept(const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket); + + public: + HTTPProxyServer(int port) : I2PTunnel(nullptr), + m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), + m_Timer (GetService ()) {}; + ~HTTPProxyServer() { Stop(); } + + void Start (); + void Stop (); + void AddHandler (std::shared_ptr handler); + void RemoveHandler (std::shared_ptr handler); + void ClearHandlers (); }; + + typedef HTTPProxyServer HTTPProxy; } } -#endif - - +#endif \ No newline at end of file From fe13a85c0f01f8be39a069a51488283bbc76010e Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 6 Jan 2015 19:05:48 -0500 Subject: [PATCH 0008/6300] read all available data from closed stream --- Streaming.h | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Streaming.h b/Streaming.h index 69ef124d..944e40de 100644 --- a/Streaming.h +++ b/Streaming.h @@ -224,9 +224,19 @@ namespace stream // no error handler (boost::system::error_code (), received); else - // socket closed - handler (m_IsReset ? boost::asio::error::make_error_code (boost::asio::error::connection_reset) : - boost::asio::error::make_error_code (boost::asio::error::operation_aborted), received); + { + // stream closed + if (m_IsReset) + { + // stream closed by peer + handler (received > 0 ? boost::system::error_code () : // we still have some data + boost::asio::error::make_error_code (boost::asio::error::connection_reset), // no more data + received); + + } + else // stream closed by us + handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), received); + } } else // timeout expired From cb8a4656056d5ea94ebaac18ad3d52d83af3b61a Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Wed, 7 Jan 2015 02:40:30 +0100 Subject: [PATCH 0009/6300] Force Connection: Close and allow http/1.1 --- HTTPProxy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 4bc7aedc..bb1dd214 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -74,8 +74,7 @@ namespace proxy } bool HTTPProxyHandler::ValidateHTTPRequest() { - if ( m_version != "HTTP/1.0" ) { - //TODO: we want to support 1.1 in the future + if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) { LogPrint(eLogError,"--- HTTP Proxy unsupported version: ", m_version); HTTPRequestFailed(); //TODO: send right stuff return false; @@ -93,6 +92,7 @@ namespace proxy m_request += m_version; m_request.push_back('\r'); m_request.push_back('\n'); + m_request.append("Connection: close\r\n"); m_request.append(reinterpret_cast(http_buff),len); return true; } From dd42819a2fff59fe8f8a107edcd7eb4e4a6da371 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Wed, 7 Jan 2015 19:09:59 +0100 Subject: [PATCH 0010/6300] Create I2Pservice as a way to integrate service management, hide unnecessary handlers --- BOB.cpp | 8 +- BOB.h | 5 +- HTTPProxy.cpp | 81 +++++++++++------- HTTPProxy.h | 61 +------------- I2PService.cpp | 19 +++++ I2PService.h | 73 ++++++++++++++++ I2PTunnel.cpp | 53 ++++-------- I2PTunnel.h | 43 +++------- SOCKS.cpp | 160 +++++++++++++++++++++++++++++------- SOCKS.h | 136 +----------------------------- build/CMakeLists.txt | 1 + build/autotools/Makefile.am | 4 +- filelist.mk | 4 +- 13 files changed, 321 insertions(+), 327 deletions(-) create mode 100644 I2PService.cpp create mode 100644 I2PService.h diff --git a/BOB.cpp b/BOB.cpp index afbb00b0..f925979f 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -28,7 +28,7 @@ namespace client void BOBI2PInboundTunnel::Stop () { m_Acceptor.close(); - ClearConnections (); + ClearHandlers (); } void BOBI2PInboundTunnel::Accept () @@ -136,7 +136,7 @@ namespace client { LogPrint ("New BOB inbound connection"); auto connection = std::make_shared(this, receiver->socket, leaseSet); - AddConnection (connection); + AddHandler (connection); connection->I2PConnect (receiver->data, receiver->dataLen); delete receiver; } @@ -154,7 +154,7 @@ namespace client void BOBI2POutboundTunnel::Stop () { - ClearConnections (); + ClearHandlers (); } void BOBI2POutboundTunnel::Accept () @@ -171,7 +171,7 @@ namespace client if (stream) { auto conn = std::make_shared (this, stream, new boost::asio::ip::tcp::socket (GetService ()), m_Endpoint, m_IsQuiet); - AddConnection (conn); + AddHandler (conn); conn->Connect (); } } diff --git a/BOB.h b/BOB.h index fc961137..bb75e40d 100644 --- a/BOB.h +++ b/BOB.h @@ -8,6 +8,7 @@ #include #include #include "I2PTunnel.h" +#include "I2PService.h" #include "Identity.h" #include "LeaseSet.h" @@ -41,12 +42,12 @@ namespace client const char BOB_REPLY_ERROR[] = "ERROR %s\n"; const char BOB_DATA[] = "NICKNAME %s\n"; - class BOBI2PTunnel: public I2PTunnel + class BOBI2PTunnel: public I2PService { public: BOBI2PTunnel (ClientDestination * localDestination): - I2PTunnel (localDestination) {}; + I2PService (localDestination) {}; virtual void Start () {}; virtual void Stop () {}; diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index bb1dd214..8f3bf835 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -2,16 +2,61 @@ #include #include #include +#include +#include #include "HTTPProxy.h" #include "Identity.h" +#include "Streaming.h" #include "Destination.h" #include "ClientContext.h" #include "I2PEndian.h" +#include "I2PTunnel.h" namespace i2p { namespace proxy { + static const size_t http_buffer_size = 8192; + class HTTPProxyHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { + private: + enum state { + GET_METHOD, + GET_HOSTNAME, + GET_HTTPV, + GET_HTTPVNL, //TODO: fallback to finding HOst: header if needed + DONE + }; + + void EnterState(state nstate); + bool HandleData(uint8_t *http_buff, std::size_t len); + void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); + void Terminate(); + void AsyncSockRead(); + void HTTPRequestFailed(/*std::string message*/); + void ExtractRequest(); + bool ValidateHTTPRequest(); + bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); + void SentHTTPFailed(const boost::system::error_code & ecode); + void HandleStreamRequestComplete (std::shared_ptr stream); + + uint8_t m_http_buff[http_buffer_size]; + 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: + HTTPProxyHandler(HTTPProxyServer * parent, boost::asio::ip::tcp::socket * sock) : + I2PServiceHandler(parent), m_sock(sock) + { AsyncSockRead(); EnterState(GET_METHOD); } + ~HTTPProxyHandler() { Terminate(); } + }; + void HTTPProxyHandler::AsyncSockRead() { LogPrint(eLogDebug,"--- HTTP Proxy async sock read"); @@ -24,19 +69,15 @@ namespace proxy } } - void HTTPProxyHandler::Done() { - if (m_parent) m_parent->RemoveHandler (shared_from_this ()); - } - void HTTPProxyHandler::Terminate() { - if (dead.exchange(true)) return; + if (Kill()) return; if (m_sock) { LogPrint(eLogDebug,"--- HTTP Proxy close sock"); m_sock->close(); delete m_sock; m_sock = nullptr; } - Done(); + Done(shared_from_this()); } /* All hope is lost beyond this point */ @@ -155,7 +196,7 @@ namespace proxy if (HandleData(m_http_buff, len)) { if (m_state == DONE) { LogPrint(eLogInfo,"--- HTTP Proxy requested: ", m_url); - m_parent->GetLocalDestination ()->CreateStream ( + GetOwner()->GetLocalDestination ()->CreateStream ( std::bind (&HTTPProxyHandler::HandleStreamRequestComplete, this, std::placeholders::_1), m_address, m_port); } else { @@ -178,12 +219,12 @@ namespace proxy void HTTPProxyHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (stream) { - if (dead.exchange(true)) return; + if (Kill()) return; LogPrint (eLogInfo,"--- HTTP Proxy New I2PTunnel connection"); - auto connection = std::make_shared((i2p::client::I2PTunnel *)m_parent, m_sock, stream); - m_parent->AddConnection (connection); + auto connection = std::make_shared(GetOwner(), m_sock, stream); + GetOwner()->AddHandler (connection); connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); - Done(); + Done(shared_from_this()); } else { LogPrint (eLogError,"--- HTTP Proxy Issue when creating the stream, check the previous warnings for more info."); HTTPRequestFailed(); // TODO: Send correct error message host unreachable @@ -200,7 +241,6 @@ namespace proxy { m_Acceptor.close(); m_Timer.cancel (); - ClearConnections (); ClearHandlers(); } @@ -211,23 +251,6 @@ namespace proxy std::placeholders::_1, newSocket)); } - void HTTPProxyServer::AddHandler (std::shared_ptr handler) { - std::unique_lock l(m_HandlersMutex); - m_Handlers.insert (handler); - } - - void HTTPProxyServer::RemoveHandler (std::shared_ptr handler) - { - std::unique_lock l(m_HandlersMutex); - m_Handlers.erase (handler); - } - - void HTTPProxyServer::ClearHandlers () - { - std::unique_lock l(m_HandlersMutex); - m_Handlers.clear (); - } - void HTTPProxyServer::HandleAccept (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket) { if (!ecode) diff --git a/HTTPProxy.h b/HTTPProxy.h index 3fd1302e..45bbff46 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -2,67 +2,17 @@ #define HTTP_PROXY_H__ #include -#include #include #include #include -#include -#include "Identity.h" -#include "Streaming.h" -#include "I2PTunnel.h" +#include "I2PService.h" namespace i2p { namespace proxy { - - const size_t http_buffer_size = 8192; - - class HTTPProxyServer; - class HTTPProxyHandler: public std::enable_shared_from_this { - private: - enum state { - GET_METHOD, - GET_HOSTNAME, - GET_HTTPV, - GET_HTTPVNL, //TODO: fallback to finding HOst: header if needed - DONE - }; - - void EnterState(state nstate); - bool HandleData(uint8_t *http_buff, std::size_t len); - void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); - void Done(); - void Terminate(); - void AsyncSockRead(); - void HTTPRequestFailed(/*std::string message*/); - void ExtractRequest(); - bool ValidateHTTPRequest(); - bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); - void SentHTTPFailed(const boost::system::error_code & ecode); - void HandleStreamRequestComplete (std::shared_ptr stream); - - uint8_t m_http_buff[http_buffer_size]; - HTTPProxyServer * m_parent; - 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 - std::atomic dead; //To avoid cleaning up multiple times - state m_state;//Parsing state - - public: - HTTPProxyHandler(HTTPProxyServer * parent, boost::asio::ip::tcp::socket * sock) : - m_parent(parent), m_sock(sock), dead(false) - { AsyncSockRead(); EnterState(GET_METHOD); } - ~HTTPProxyHandler() { Terminate(); } - }; - - class HTTPProxyServer: public i2p::client::I2PTunnel + class HTTPProxyHandler; + class HTTPProxyServer: public i2p::client::I2PService { private: std::set > m_Handlers; @@ -76,16 +26,13 @@ namespace proxy void HandleAccept(const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket); public: - HTTPProxyServer(int port) : I2PTunnel(nullptr), + HTTPProxyServer(int port) : I2PService(nullptr), m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), m_Timer (GetService ()) {}; ~HTTPProxyServer() { Stop(); } void Start (); void Stop (); - void AddHandler (std::shared_ptr handler); - void RemoveHandler (std::shared_ptr handler); - void ClearHandlers (); }; typedef HTTPProxyServer HTTPProxy; diff --git a/I2PService.cpp b/I2PService.cpp new file mode 100644 index 00000000..04eca700 --- /dev/null +++ b/I2PService.cpp @@ -0,0 +1,19 @@ +#include "Destination.h" +#include "Identity.h" +#include "ClientContext.h" +#include "I2PService.h" + + +namespace i2p +{ +namespace client +{ + static const i2p::data::SigningKeyType I2P_SERVICE_DEFAULT_KEY_TYPE = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; + + I2PService::I2PService (ClientDestination * localDestination): + m_LocalDestination (localDestination ? localDestination : + i2p::client::context.CreateNewLocalDestination (false, I2P_SERVICE_DEFAULT_KEY_TYPE)) + { + } +} +} diff --git a/I2PService.h b/I2PService.h new file mode 100644 index 00000000..cbdbc82d --- /dev/null +++ b/I2PService.h @@ -0,0 +1,73 @@ +#ifndef I2PSERVICE_H__ +#define I2PSERVICE_H__ + +#include +#include +#include +#include +#include +#include "Destination.h" +#include "Identity.h" + +namespace i2p +{ +namespace client +{ + class I2PServiceHandler; + class I2PService + { + public: + I2PService (ClientDestination * localDestination = nullptr); + virtual ~I2PService () { ClearHandlers (); } + + inline void AddHandler (std::shared_ptr conn) { + std::unique_lock l(m_HandlersMutex); + m_Handlers.insert(conn); + } + inline void RemoveHandler (std::shared_ptr conn) { + std::unique_lock l(m_HandlersMutex); + m_Handlers.erase(conn); + } + inline void ClearHandlers () { + std::unique_lock l(m_HandlersMutex); + m_Handlers.clear(); + } + + inline ClientDestination * GetLocalDestination () { return m_LocalDestination; }; + inline void SetLocalDestination (ClientDestination * dest) { m_LocalDestination = dest; }; + + inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); }; + + virtual void Start () = 0; + virtual void Stop () = 0; + + private: + + ClientDestination * m_LocalDestination; + std::unordered_set > m_Handlers; + std::mutex m_HandlersMutex; + }; + + /*Simple interface for I2PHandlers, allows detection of finalization amongst other things */ + class I2PServiceHandler + { + public: + I2PServiceHandler(I2PService * parent) : m_Dead(false), m_Service(parent) { } + virtual ~I2PServiceHandler() { } + protected: + // Call when terminating or handing over to avoid race conditions + inline bool Kill() { return m_Dead.exchange(true); } + // Call to know if the handler is dead + inline bool Dead() { return m_Dead; } + // Call when done to clean up (make sure Kill is called first) + inline void Done(std::shared_ptr me) { if(m_Service) m_Service->RemoveHandler(me); } + // Call to talk with the owner + inline I2PService * GetOwner() { return m_Service; } + private: + I2PService *m_Service; + std::atomic m_Dead; //To avoid cleaning up multiple times + }; +} +} + +#endif diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index b782a657..2f96b859 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -9,24 +9,25 @@ namespace i2p { namespace client { - I2PTunnelConnection::I2PTunnelConnection (I2PTunnel * owner, + I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, boost::asio::ip::tcp::socket * socket, const i2p::data::LeaseSet * leaseSet): - m_Socket (socket), m_Owner (owner), m_RemoteEndpoint (socket->remote_endpoint ()), + I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { - m_Stream = m_Owner->GetLocalDestination ()->CreateStream (*leaseSet); + m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (*leaseSet); } - I2PTunnelConnection::I2PTunnelConnection (I2PTunnel * owner, + I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, boost::asio::ip::tcp::socket * socket, std::shared_ptr stream): - m_Socket (socket), m_Stream (stream), m_Owner (owner), + I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { } - I2PTunnelConnection::I2PTunnelConnection (I2PTunnel * owner, std::shared_ptr stream, + I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, boost::asio::ip::tcp::socket * socket, const boost::asio::ip::tcp::endpoint& target, bool quiet): - m_Socket (socket), m_Stream (stream), m_Owner (owner), m_RemoteEndpoint (target), m_IsQuiet (quiet) + I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), + m_RemoteEndpoint (target), m_IsQuiet (quiet) { } @@ -56,15 +57,15 @@ namespace client } void I2PTunnelConnection::Terminate () - { + { + if (Kill()) return; if (m_Stream) { m_Stream->Close (); m_Stream.reset (); } m_Socket->close (); - if (m_Owner) - m_Owner->RemoveConnection (shared_from_this ()); + Done(shared_from_this ()); } void I2PTunnelConnection::Receive () @@ -150,28 +151,8 @@ namespace client } } - I2PTunnel::I2PTunnel (ClientDestination * localDestination) : - m_LocalDestination (localDestination ? localDestination : - i2p::client::context.CreateNewLocalDestination (false, I2P_TUNNEL_DEFAULT_KEY_TYPE)) - { - } - void I2PTunnel::AddConnection (std::shared_ptr conn) - { - m_Connections.insert (conn); - } - - void I2PTunnel::RemoveConnection (std::shared_ptr conn) - { - m_Connections.erase (conn); - } - - void I2PTunnel::ClearConnections () - { - m_Connections.clear (); - } - I2PClientTunnel::I2PClientTunnel (const std::string& destination, int port, ClientDestination * localDestination): - I2PTunnel (localDestination), + I2PService (localDestination), m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), m_Timer (GetService ()), m_Destination (destination), m_DestinationIdentHash (nullptr) { @@ -193,7 +174,7 @@ namespace client { m_Acceptor.close(); m_Timer.cancel (); - ClearConnections (); + ClearHandlers (); auto *originalIdentHash = m_DestinationIdentHash; m_DestinationIdentHash = nullptr; delete originalIdentHash; @@ -250,7 +231,7 @@ namespace client { LogPrint (eLogInfo,"New I2PTunnel connection"); auto connection = std::make_shared(this, socket, stream); - AddConnection (connection); + AddHandler (connection); connection->I2PConnect (); } else @@ -261,7 +242,7 @@ namespace client } I2PServerTunnel::I2PServerTunnel (const std::string& address, int port, ClientDestination * localDestination): - I2PTunnel (localDestination), m_Endpoint (boost::asio::ip::address::from_string (address), port) + I2PService (localDestination), m_Endpoint (boost::asio::ip::address::from_string (address), port) { } @@ -272,7 +253,7 @@ namespace client void I2PServerTunnel::Stop () { - ClearConnections (); + ClearHandlers (); } void I2PServerTunnel::Accept () @@ -289,7 +270,7 @@ namespace client if (stream) { auto conn = std::make_shared (this, stream, new boost::asio::ip::tcp::socket (GetService ()), m_Endpoint); - AddConnection (conn); + AddHandler (conn); conn->Connect (); } } diff --git a/I2PTunnel.h b/I2PTunnel.h index 2a941f35..f19f128c 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -9,6 +9,7 @@ #include "Identity.h" #include "Destination.h" #include "Streaming.h" +#include "I2PService.h" namespace i2p { @@ -17,21 +18,19 @@ namespace client const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 8192; const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds - const i2p::data::SigningKeyType I2P_TUNNEL_DEFAULT_KEY_TYPE = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; - class I2PTunnel; - class I2PTunnelConnection: public std::enable_shared_from_this + + class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this { public: - I2PTunnelConnection (I2PTunnel * owner, boost::asio::ip::tcp::socket * socket, + I2PTunnelConnection (I2PService * owner, boost::asio::ip::tcp::socket * socket, const i2p::data::LeaseSet * leaseSet); // to I2P - I2PTunnelConnection (I2PTunnel * owner, boost::asio::ip::tcp::socket * socket, + I2PTunnelConnection (I2PService * owner, boost::asio::ip::tcp::socket * socket, std::shared_ptr stream); // to I2P using simplified API :) - I2PTunnelConnection (I2PTunnel * owner, std::shared_ptr stream, boost::asio::ip::tcp::socket * socket, + I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, boost::asio::ip::tcp::socket * socket, const boost::asio::ip::tcp::endpoint& target, bool quiet = true); // from I2P ~I2PTunnelConnection (); - void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0); void Connect (); @@ -52,33 +51,11 @@ namespace client uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; boost::asio::ip::tcp::socket * m_Socket; std::shared_ptr m_Stream; - I2PTunnel * m_Owner; boost::asio::ip::tcp::endpoint m_RemoteEndpoint; bool m_IsQuiet; // don't send destination - }; + }; - class I2PTunnel - { - public: - - I2PTunnel (ClientDestination * localDestination = nullptr); - virtual ~I2PTunnel () { ClearConnections (); }; - - void AddConnection (std::shared_ptr conn); - void RemoveConnection (std::shared_ptr conn); - void ClearConnections (); - ClientDestination * GetLocalDestination () { return m_LocalDestination; }; - void SetLocalDestination (ClientDestination * dest) { m_LocalDestination = dest; }; - - boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); }; - - private: - - ClientDestination * m_LocalDestination; - std::set > m_Connections; - }; - - class I2PClientTunnel: public I2PTunnel + class I2PClientTunnel: public I2PService { public: @@ -103,7 +80,7 @@ namespace client const i2p::data::IdentHash * m_DestinationIdentHash; }; - class I2PServerTunnel: public I2PTunnel + class I2PServerTunnel: public I2PService { public: @@ -121,7 +98,7 @@ namespace client boost::asio::ip::tcp::endpoint m_Endpoint; }; -} +} } #endif diff --git a/SOCKS.cpp b/SOCKS.cpp index 6354dbfa..be6c4824 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -1,15 +1,137 @@ #include #include +#include +#include #include "SOCKS.h" #include "Identity.h" +#include "Streaming.h" #include "Destination.h" #include "ClientContext.h" #include "I2PEndian.h" +#include "I2PTunnel.h" namespace i2p { namespace proxy { + static const size_t socks_buffer_size = 8192; + static const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse + + struct SOCKSDnsAddress { + uint8_t size; + char value[max_socks_hostname_size]; + void FromString (std::string str) { + size = str.length(); + if (str.length() > max_socks_hostname_size) size = max_socks_hostname_size; + memcpy(value,str.c_str(),size); + } + std::string ToString() { return std::string(value, size); } + void push_back (char c) { value[size++] = c; } + }; + + class SOCKSServer; + class SOCKSHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { + private: + enum state { + GET_SOCKSV, + GET_COMMAND, + GET_PORT, + GET_IPV4, + GET4_IDENT, + GET4A_HOST, + GET5_AUTHNUM, + GET5_AUTH, + GET5_REQUESTV, + GET5_GETRSV, + GET5_GETADDRTYPE, + GET5_IPV6, + GET5_HOST_SIZE, + GET5_HOST, + DONE + }; + enum authMethods { + AUTH_NONE = 0, //No authentication, skip to next step + AUTH_GSSAPI = 1, //GSSAPI authentication + AUTH_USERPASSWD = 2, //Username and password + AUTH_UNACCEPTABLE = 0xff //No acceptable method found + }; + enum addrTypes { + ADDR_IPV4 = 1, //IPv4 address (4 octets) + ADDR_DNS = 3, // DNS name (up to 255 octets) + ADDR_IPV6 = 4 //IPV6 address (16 octets) + }; + enum errTypes { + SOCKS5_OK = 0, // No error for SOCKS5 + SOCKS5_GEN_FAIL = 1, // General server failure + SOCKS5_RULE_DENIED = 2, // Connection disallowed by ruleset + SOCKS5_NET_UNREACH = 3, // Network unreachable + SOCKS5_HOST_UNREACH = 4, // Host unreachable + SOCKS5_CONN_REFUSED = 5, // Connection refused by the peer + SOCKS5_TTL_EXPIRED = 6, // TTL Expired + SOCKS5_CMD_UNSUP = 7, // Command unsuported + SOCKS5_ADDR_UNSUP = 8, // Address type unsuported + SOCKS4_OK = 90, // No error for SOCKS4 + SOCKS4_FAIL = 91, // Failed establishing connecting or not allowed + SOCKS4_IDENTD_MISSING = 92, // Couldn't connect to the identd server + SOCKS4_IDENTD_DIFFER = 93 // The ID reported by the application and by identd differ + }; + enum cmdTypes { + CMD_CONNECT = 1, // TCP Connect + CMD_BIND = 2, // TCP Bind + CMD_UDP = 3 // UDP associate + }; + enum socksVersions { + SOCKS4 = 4, // SOCKS4 + SOCKS5 = 5 // SOCKS5 + }; + union address { + uint32_t ip; + SOCKSDnsAddress dns; + uint8_t ipv6[16]; + }; + + void EnterState(state nstate, uint8_t parseleft = 1); + bool HandleData(uint8_t *sock_buff, std::size_t len); + bool ValidateSOCKSRequest(); + void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); + void Terminate(); + void AsyncSockRead(); + boost::asio::const_buffers_1 GenerateSOCKS5SelectAuth(authMethods method); + boost::asio::const_buffers_1 GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port); + boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port); + bool Socks5ChooseAuth(); + void SocksRequestFailed(errTypes error); + void SocksRequestSuccess(); + void SentSocksFailed(const boost::system::error_code & ecode); + void SentSocksDone(const boost::system::error_code & ecode); + void SentSocksResponse(const boost::system::error_code & ecode); + void HandleStreamRequestComplete (std::shared_ptr stream); + + uint8_t m_sock_buff[socks_buffer_size]; + boost::asio::ip::tcp::socket * m_sock; + std::shared_ptr m_stream; + uint8_t *m_remaining_data; //Data left to be sent + uint8_t m_response[7+max_socks_hostname_size]; + address m_address; //Address + std::size_t m_remaining_data_len; //Size of the data left to be sent + uint32_t m_4aip; //Used in 4a requests + uint16_t m_port; + uint8_t m_command; + uint8_t m_parseleft; //Octets left to parse + authMethods m_authchosen; //Authentication chosen + addrTypes m_addrtype; //Address type chosen + socksVersions m_socksv; //Socks version + cmdTypes m_cmd; // Command requested + state m_state; + + public: + SOCKSHandler(SOCKSServer * parent, boost::asio::ip::tcp::socket * sock) : + I2PServiceHandler(parent), m_sock(sock), m_stream(nullptr), + m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4) + { m_address.ip = 0; EnterState(GET_SOCKSV); AsyncSockRead(); } + ~SOCKSHandler() { Terminate(); } + }; + void SOCKSHandler::AsyncSockRead() { LogPrint(eLogDebug,"--- SOCKS async sock read"); @@ -22,12 +144,8 @@ namespace proxy } } - void SOCKSHandler::Done() { - if (m_parent) m_parent->RemoveHandler (shared_from_this ()); - } - void SOCKSHandler::Terminate() { - if (dead.exchange(true)) return; + if (Kill()) return; if (m_sock) { LogPrint(eLogDebug,"--- SOCKS close sock"); m_sock->close(); @@ -38,7 +156,7 @@ namespace proxy LogPrint(eLogDebug,"--- SOCKS close stream"); m_stream.reset (); } - Done(); + Done(shared_from_this()); } boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port) @@ -125,7 +243,7 @@ namespace proxy break; case SOCKS5: LogPrint(eLogInfo,"--- SOCKS5 connection success"); - auto s = i2p::client::context.GetAddressBook().ToAddress(m_parent->GetLocalDestination()->GetIdentHash()); + auto s = i2p::client::context.GetAddressBook().ToAddress(GetOwner()->GetLocalDestination()->GetIdentHash()); address ad; ad.dns.FromString(s); //HACK only 16 bits passed in port as SOCKS5 doesn't allow for more response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, ad, m_stream->GetRecvStreamID()); @@ -336,7 +454,7 @@ namespace proxy if (HandleData(m_sock_buff, len)) { if (m_state == DONE) { LogPrint(eLogInfo,"--- SOCKS requested ", m_address.dns.ToString(), ":" , m_port); - m_parent->GetLocalDestination ()->CreateStream ( + GetOwner()->GetLocalDestination ()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete, this, std::placeholders::_1), m_address.dns.ToString(), m_port); } else { @@ -359,12 +477,12 @@ namespace proxy void SOCKSHandler::SentSocksDone(const boost::system::error_code & ecode) { if (!ecode) { - if (dead.exchange(true)) return; + if (Kill()) return; LogPrint (eLogInfo,"--- SOCKS New I2PTunnel connection"); - auto connection = std::make_shared((i2p::client::I2PTunnel *)m_parent, m_sock, m_stream); - m_parent->AddConnection (connection); + auto connection = std::make_shared(GetOwner(), m_sock, m_stream); + GetOwner()->AddHandler (connection); connection->I2PConnect (m_remaining_data,m_remaining_data_len); - Done(); + Done(shared_from_this()); } else { @@ -402,7 +520,6 @@ namespace proxy { m_Acceptor.close(); m_Timer.cancel (); - ClearConnections (); ClearHandlers(); } @@ -413,23 +530,6 @@ namespace proxy std::placeholders::_1, newSocket)); } - void SOCKSServer::AddHandler (std::shared_ptr handler) { - std::unique_lock l(m_HandlersMutex); - m_Handlers.insert (handler); - } - - void SOCKSServer::RemoveHandler (std::shared_ptr handler) - { - std::unique_lock l(m_HandlersMutex); - m_Handlers.erase (handler); - } - - void SOCKSServer::ClearHandlers () - { - std::unique_lock l(m_HandlersMutex); - m_Handlers.clear (); - } - void SOCKSServer::HandleAccept (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket) { if (!ecode) diff --git a/SOCKS.h b/SOCKS.h index f0d5ceec..dc97bc0e 100644 --- a/SOCKS.h +++ b/SOCKS.h @@ -2,142 +2,17 @@ #define SOCKS_H__ #include -#include #include #include #include -#include -#include "Identity.h" -#include "Streaming.h" -#include "I2PTunnel.h" +#include "I2PService.h" namespace i2p { namespace proxy { - - const size_t socks_buffer_size = 8192; - const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse - - struct SOCKSDnsAddress { - uint8_t size; - char value[max_socks_hostname_size]; - void FromString (std::string str) { - size = str.length(); - if (str.length() > max_socks_hostname_size) size = max_socks_hostname_size; - memcpy(value,str.c_str(),size); - } - std::string ToString() { return std::string(value, size); } - void push_back (char c) { value[size++] = c; } - }; - - class SOCKSServer; - class SOCKSHandler: public std::enable_shared_from_this { - private: - enum state { - GET_SOCKSV, - GET_COMMAND, - GET_PORT, - GET_IPV4, - GET4_IDENT, - GET4A_HOST, - GET5_AUTHNUM, - GET5_AUTH, - GET5_REQUESTV, - GET5_GETRSV, - GET5_GETADDRTYPE, - GET5_IPV6, - GET5_HOST_SIZE, - GET5_HOST, - DONE - }; - enum authMethods { - AUTH_NONE = 0, //No authentication, skip to next step - AUTH_GSSAPI = 1, //GSSAPI authentication - AUTH_USERPASSWD = 2, //Username and password - AUTH_UNACCEPTABLE = 0xff //No acceptable method found - }; - enum addrTypes { - ADDR_IPV4 = 1, //IPv4 address (4 octets) - ADDR_DNS = 3, // DNS name (up to 255 octets) - ADDR_IPV6 = 4 //IPV6 address (16 octets) - }; - enum errTypes { - SOCKS5_OK = 0, // No error for SOCKS5 - SOCKS5_GEN_FAIL = 1, // General server failure - SOCKS5_RULE_DENIED = 2, // Connection disallowed by ruleset - SOCKS5_NET_UNREACH = 3, // Network unreachable - SOCKS5_HOST_UNREACH = 4, // Host unreachable - SOCKS5_CONN_REFUSED = 5, // Connection refused by the peer - SOCKS5_TTL_EXPIRED = 6, // TTL Expired - SOCKS5_CMD_UNSUP = 7, // Command unsuported - SOCKS5_ADDR_UNSUP = 8, // Address type unsuported - SOCKS4_OK = 90, // No error for SOCKS4 - SOCKS4_FAIL = 91, // Failed establishing connecting or not allowed - SOCKS4_IDENTD_MISSING = 92, // Couldn't connect to the identd server - SOCKS4_IDENTD_DIFFER = 93 // The ID reported by the application and by identd differ - }; - enum cmdTypes { - CMD_CONNECT = 1, // TCP Connect - CMD_BIND = 2, // TCP Bind - CMD_UDP = 3 // UDP associate - }; - enum socksVersions { - SOCKS4 = 4, // SOCKS4 - SOCKS5 = 5 // SOCKS5 - }; - union address { - uint32_t ip; - SOCKSDnsAddress dns; - uint8_t ipv6[16]; - }; - - void EnterState(state nstate, uint8_t parseleft = 1); - bool HandleData(uint8_t *sock_buff, std::size_t len); - bool ValidateSOCKSRequest(); - void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); - void Done(); - void Terminate(); - void AsyncSockRead(); - boost::asio::const_buffers_1 GenerateSOCKS5SelectAuth(authMethods method); - boost::asio::const_buffers_1 GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port); - boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port); - bool Socks5ChooseAuth(); - void SocksRequestFailed(errTypes error); - void SocksRequestSuccess(); - void SentSocksFailed(const boost::system::error_code & ecode); - void SentSocksDone(const boost::system::error_code & ecode); - void SentSocksResponse(const boost::system::error_code & ecode); - void HandleStreamRequestComplete (std::shared_ptr stream); - - uint8_t m_sock_buff[socks_buffer_size]; - SOCKSServer * m_parent; - boost::asio::ip::tcp::socket * m_sock; - std::shared_ptr m_stream; - uint8_t *m_remaining_data; //Data left to be sent - uint8_t m_response[7+max_socks_hostname_size]; - address m_address; //Address - std::size_t m_remaining_data_len; //Size of the data left to be sent - uint32_t m_4aip; //Used in 4a requests - uint16_t m_port; - uint8_t m_command; - uint8_t m_parseleft; //Octets left to parse - authMethods m_authchosen; //Authentication chosen - addrTypes m_addrtype; //Address type chosen - socksVersions m_socksv; //Socks version - cmdTypes m_cmd; // Command requested - state m_state; - std::atomic dead; //To avoid cleaning up multiple times - - public: - SOCKSHandler(SOCKSServer * parent, boost::asio::ip::tcp::socket * sock) : - m_parent(parent), m_sock(sock), m_stream(nullptr), - m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4), dead(false) - { m_address.ip = 0; EnterState(GET_SOCKSV); AsyncSockRead(); } - ~SOCKSHandler() { Terminate(); } - }; - - class SOCKSServer: public i2p::client::I2PTunnel + class SOCKSHandler; + class SOCKSServer: public i2p::client::I2PService { private: std::set > m_Handlers; @@ -151,16 +26,13 @@ namespace proxy void HandleAccept(const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket); public: - SOCKSServer(int port) : I2PTunnel(nullptr), + SOCKSServer(int port) : I2PService(nullptr), m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), m_Timer (GetService ()) {}; ~SOCKSServer() { Stop(); } void Start (); void Stop (); - void AddHandler (std::shared_ptr handler); - void RemoveHandler (std::shared_ptr handler); - void ClearHandlers (); }; typedef SOCKSServer SOCKSProxy; diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index bad1daf9..4937c02e 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -48,6 +48,7 @@ set (DAEMON_SRC "${CMAKE_SOURCE_DIR}/Daemon.cpp" "${CMAKE_SOURCE_DIR}/HTTPProxy.cpp" "${CMAKE_SOURCE_DIR}/HTTPServer.cpp" + "${CMAKE_SOURCE_DIR}/I2PService.cpp" "${CMAKE_SOURCE_DIR}/I2PTunnel.cpp" "${CMAKE_SOURCE_DIR}/SAM.cpp" "${CMAKE_SOURCE_DIR}/SOCKS.cpp" diff --git a/build/autotools/Makefile.am b/build/autotools/Makefile.am index fe1af15a..06006bc0 100644 --- a/build/autotools/Makefile.am +++ b/build/autotools/Makefile.am @@ -8,7 +8,7 @@ i2p_SOURCES = AddressBook.cpp CryptoConst.cpp Daemon.cpp \ SSUData.cpp Streaming.cpp TransitTunnel.cpp \ Transports.cpp Tunnel.cpp TunnelEndpoint.cpp \ TunnelGateway.cpp TunnelPool.cpp UPnP.cpp aes.cpp \ - base64.cpp i2p.cpp util.cpp \ + base64.cpp i2p.cpp util.cpp I2PService.cpp \ \ AddressBook.h CryptoConst.h Daemon.h ElGamal.h \ Garlic.h HTTPProxy.h HTTPServer.h I2NPProtocol.h \ @@ -19,7 +19,7 @@ i2p_SOURCES = AddressBook.cpp CryptoConst.cpp Daemon.cpp \ TransitTunnel.h Transports.h Tunnel.h TunnelBase.h \ TunnelConfig.h TunnelEndpoint.h TunnelGateway.h \ TunnelPool.h UPnP.h aes.h base64.h config.h hmac.h \ - util.h version.h + util.h version.h I2PService.h AM_LDFLAGS = @BOOST_DATE_TIME_LIB@ @BOOST_FILESYSTEM_LIB@ \ @BOOST_PROGRAM_OPTIONS_LIB@ @BOOST_REGEX_LIB@ \ diff --git a/filelist.mk b/filelist.mk index 08ba2885..e6adebab 100644 --- a/filelist.mk +++ b/filelist.mk @@ -10,14 +10,14 @@ ifeq ($(UNAME),Darwin) # This is needed on OS X for some reason I don't understand (yet). # Else will get linker error about unknown symbols. - torkel COMMON_SRC += \ - BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp SAM.cpp SOCKS.cpp \ + BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp I2PService.cpp SAM.cpp SOCKS.cpp \ UPnP.cpp HTTPServer.cpp HTTPProxy.cpp i2p.cpp DaemonLinux.cpp endif # also: Daemon{Linux,Win32}.cpp will be added later DAEMON_SRC = $(COMMON_SRC) \ - BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp SAM.cpp SOCKS.cpp UPnP.cpp \ + BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp I2PService.cpp SAM.cpp SOCKS.cpp UPnP.cpp \ HTTPServer.cpp HTTPProxy.cpp i2p.cpp LIB_SRC := $(COMMON_SRC) \ From cdc0aa658a735479e246674e9c1d78bfea6dae93 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 7 Jan 2015 13:26:44 -0500 Subject: [PATCH 0011/6300] I2PControl added --- I2PControl.cpp | 15 +++++++++++++++ I2PControl.h | 29 +++++++++++++++++++++++++++++ Win32/i2pd.vcxproj | 2 ++ build/CMakeLists.txt | 1 + build/autotools/Makefile.in | 9 ++++++--- filelist.mk | 2 +- 6 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 I2PControl.cpp create mode 100644 I2PControl.h diff --git a/I2PControl.cpp b/I2PControl.cpp new file mode 100644 index 00000000..260e8b80 --- /dev/null +++ b/I2PControl.cpp @@ -0,0 +1,15 @@ +#include +#include +#include "I2PControl.h" + +namespace i2p +{ +namespace client +{ + I2PControlService::I2PControlService (int port): + m_IsRunning (false), m_Thread (nullptr), + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)) + { + } +} +} diff --git a/I2PControl.h b/I2PControl.h new file mode 100644 index 00000000..1c0e90da --- /dev/null +++ b/I2PControl.h @@ -0,0 +1,29 @@ +#ifndef I2P_CONTROL_H__ +#define I2P_CONTROL_H__ + +#include +#include + +namespace i2p +{ +namespace client +{ + class I2PControlService + { + public: + + I2PControlService (int port); + + private: + + bool m_IsRunning; + std::thread * m_Thread; + + boost::asio::io_service m_Service; + boost::asio::ip::tcp::acceptor m_Acceptor; + }; +} +} + +#endif + diff --git a/Win32/i2pd.vcxproj b/Win32/i2pd.vcxproj index bba9a6a7..745ebe10 100644 --- a/Win32/i2pd.vcxproj +++ b/Win32/i2pd.vcxproj @@ -57,6 +57,7 @@ + @@ -102,6 +103,7 @@ + diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index bad1daf9..26447d76 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -48,6 +48,7 @@ set (DAEMON_SRC "${CMAKE_SOURCE_DIR}/Daemon.cpp" "${CMAKE_SOURCE_DIR}/HTTPProxy.cpp" "${CMAKE_SOURCE_DIR}/HTTPServer.cpp" + "${CMAKE_SOURCE_DIR}/I2PControl.cpp" "${CMAKE_SOURCE_DIR}/I2PTunnel.cpp" "${CMAKE_SOURCE_DIR}/SAM.cpp" "${CMAKE_SOURCE_DIR}/SOCKS.cpp" diff --git a/build/autotools/Makefile.in b/build/autotools/Makefile.in index 6c861710..b28c620d 100644 --- a/build/autotools/Makefile.in +++ b/build/autotools/Makefile.in @@ -115,7 +115,8 @@ am_i2p_OBJECTS = AddressBook.$(OBJEXT) CryptoConst.$(OBJEXT) \ TunnelGateway.$(OBJEXT) TunnelPool.$(OBJEXT) UPnP.$(OBJEXT) \ aes.$(OBJEXT) base64.$(OBJEXT) i2p.$(OBJEXT) util.$(OBJEXT) \ SAM.$(OBJEXT) Destination.$(OBJEXT) ClientContext.$(OBJEXT) \ - Datagram.$(OBJEXT) SSUSession.$(OBJEXT) BOB.$(OBJEXT) + Datagram.$(OBJEXT) SSUSession.$(OBJEXT) BOB.$(OBJEXT) \ + I2PControl.$(OBJEXT) i2p_OBJECTS = $(am_i2p_OBJECTS) i2p_LDADD = $(LDADD) AM_V_P = $(am__v_P_@AM_V@) @@ -326,7 +327,8 @@ i2p_SOURCES = AddressBook.cpp CryptoConst.cpp Daemon.cpp \ Transports.cpp Tunnel.cpp TunnelEndpoint.cpp \ TunnelGateway.cpp TunnelPool.cpp UPnP.cpp aes.cpp \ base64.cpp i2p.cpp util.cpp SAM.cpp Destination.cpp \ - ClientContext.cpp DataFram.cpp SSUSession.cpp BOB.cpp \ + ClientContext.cpp DataFram.cpp SSUSession.cpp BOB.cpp \ + I2PControl.cpp \ \ AddressBook.h CryptoConst.h Daemon.h ElGamal.h \ Garlic.h HTTPProxy.h HTTPServer.h I2NPProtocol.h \ @@ -338,7 +340,8 @@ i2p_SOURCES = AddressBook.cpp CryptoConst.cpp Daemon.cpp \ TunnelConfig.h TunnelEndpoint.h TunnelGateway.h \ TunnelPool.h UPnP.h aes.h base64.h config.h hmac.h \ util.h version.h Destination.h ClientContext.h \ - TransportSession.h Datagram.h SSUSession.h BOB.h + TransportSession.h Datagram.h SSUSession.h BOB.h \ + I2PControl.h AM_LDFLAGS = @BOOST_DATE_TIME_LIB@ @BOOST_FILESYSTEM_LIB@ \ @BOOST_PROGRAM_OPTIONS_LIB@ @BOOST_REGEX_LIB@ \ diff --git a/filelist.mk b/filelist.mk index 08ba2885..56c18cec 100644 --- a/filelist.mk +++ b/filelist.mk @@ -18,7 +18,7 @@ endif # also: Daemon{Linux,Win32}.cpp will be added later DAEMON_SRC = $(COMMON_SRC) \ BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp SAM.cpp SOCKS.cpp UPnP.cpp \ - HTTPServer.cpp HTTPProxy.cpp i2p.cpp + HTTPServer.cpp HTTPProxy.cpp I2PControl.cpp i2p.cpp LIB_SRC := $(COMMON_SRC) \ api.cpp From 90005c8237c013ab268b20a33d926e3d781890cf Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Wed, 7 Jan 2015 20:44:24 +0100 Subject: [PATCH 0012/6300] Migrate to I2Pservice --- Destination.cpp | 12 ------------ Destination.h | 6 +++--- HTTPProxy.cpp | 3 +-- I2PService.cpp | 12 ++++++++++++ I2PService.h | 1 + SOCKS.cpp | 3 +-- 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 154cfee0..abaf0de0 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -387,18 +387,6 @@ namespace client } } - void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port) { - assert(streamRequestComplete); - i2p::data::IdentHash identHash; - if (i2p::client::context.GetAddressBook ().GetIdentHash (dest, identHash)) - CreateStream (streamRequestComplete, identHash, port); - else - { - LogPrint (eLogWarning, "Remote destination ", dest, " not found"); - streamRequestComplete (nullptr); - } - } - void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) { assert(streamRequestComplete); const i2p::data::LeaseSet * leaseSet = FindLeaseSet (dest); diff --git a/Destination.h b/Destination.h index b4d910ad..14580e6b 100644 --- a/Destination.h +++ b/Destination.h @@ -36,7 +36,9 @@ namespace client const char I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH[] = "outbound.length"; const int DEFAULT_OUTBOUND_TUNNEL_LENGTH = 3; const int STREAM_REQUEST_TIMEOUT = 60; //in seconds - + + typedef std::function stream)> StreamRequestComplete; + class ClientDestination: public i2p::garlic::GarlicDestination { typedef std::function RequestComplete; @@ -49,7 +51,6 @@ namespace client RequestComplete requestComplete; }; - typedef std::function stream)> StreamRequestComplete; public: @@ -67,7 +68,6 @@ namespace client // streaming i2p::stream::StreamingDestination * GetStreamingDestination () const { return m_StreamingDestination; }; - void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port = 0); void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); std::shared_ptr CreateStream (const i2p::data::LeaseSet& remote, int port = 0); void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 8f3bf835..22940592 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -196,8 +196,7 @@ namespace proxy if (HandleData(m_http_buff, len)) { if (m_state == DONE) { LogPrint(eLogInfo,"--- HTTP Proxy requested: ", m_url); - GetOwner()->GetLocalDestination ()->CreateStream ( - std::bind (&HTTPProxyHandler::HandleStreamRequestComplete, + GetOwner()->CreateStream (std::bind (&HTTPProxyHandler::HandleStreamRequestComplete, this, std::placeholders::_1), m_address, m_port); } else { AsyncSockRead(); diff --git a/I2PService.cpp b/I2PService.cpp index 04eca700..062713a0 100644 --- a/I2PService.cpp +++ b/I2PService.cpp @@ -15,5 +15,17 @@ namespace client i2p::client::context.CreateNewLocalDestination (false, I2P_SERVICE_DEFAULT_KEY_TYPE)) { } + + void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port) { + assert(streamRequestComplete); + i2p::data::IdentHash identHash; + if (i2p::client::context.GetAddressBook ().GetIdentHash (dest, identHash)) + localDestination->CreateStream (streamRequestComplete, identHash, port); + else + { + LogPrint (eLogWarning, "Remote destination ", dest, " not found"); + streamRequestComplete (nullptr); + } + } } } diff --git a/I2PService.h b/I2PService.h index cbdbc82d..91835565 100644 --- a/I2PService.h +++ b/I2PService.h @@ -35,6 +35,7 @@ namespace client inline ClientDestination * GetLocalDestination () { return m_LocalDestination; }; inline void SetLocalDestination (ClientDestination * dest) { m_LocalDestination = dest; }; + void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port = 0); inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); }; diff --git a/SOCKS.cpp b/SOCKS.cpp index be6c4824..0bc2e051 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -454,8 +454,7 @@ namespace proxy if (HandleData(m_sock_buff, len)) { if (m_state == DONE) { LogPrint(eLogInfo,"--- SOCKS requested ", m_address.dns.ToString(), ":" , m_port); - GetOwner()->GetLocalDestination ()->CreateStream ( - std::bind (&SOCKSHandler::HandleStreamRequestComplete, + GetOwner()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete, this, std::placeholders::_1), m_address.dns.ToString(), m_port); } else { AsyncSockRead(); From bcbe20751536bfe60c56a1f34b189d865061cc0c Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Wed, 7 Jan 2015 21:15:04 +0100 Subject: [PATCH 0013/6300] Make HTTPProxy use SIGNING_KEY_TYPE_DSA_SHA1 --- HTTPProxy.h | 2 +- I2PService.cpp | 9 +++++++-- I2PService.h | 3 ++- SOCKS.cpp | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/HTTPProxy.h b/HTTPProxy.h index 45bbff46..8e348478 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -26,7 +26,7 @@ namespace proxy void HandleAccept(const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket); public: - HTTPProxyServer(int port) : I2PService(nullptr), + HTTPProxyServer(int port) : I2PService(i2p::data::SIGNING_KEY_TYPE_DSA_SHA1), m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), m_Timer (GetService ()) {}; ~HTTPProxyServer() { Stop(); } diff --git a/I2PService.cpp b/I2PService.cpp index 062713a0..568d03eb 100644 --- a/I2PService.cpp +++ b/I2PService.cpp @@ -16,11 +16,16 @@ namespace client { } - void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port) { + I2PService::I2PService (i2p::data::SigningKeyType kt): + m_LocalDestination (i2p::client::context.CreateNewLocalDestination (false, kt)) + { + } + + void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port) { assert(streamRequestComplete); i2p::data::IdentHash identHash; if (i2p::client::context.GetAddressBook ().GetIdentHash (dest, identHash)) - localDestination->CreateStream (streamRequestComplete, identHash, port); + m_LocalDestination->CreateStream (streamRequestComplete, identHash, port); else { LogPrint (eLogWarning, "Remote destination ", dest, " not found"); diff --git a/I2PService.h b/I2PService.h index 91835565..5345ab6d 100644 --- a/I2PService.h +++ b/I2PService.h @@ -18,6 +18,7 @@ namespace client { public: I2PService (ClientDestination * localDestination = nullptr); + I2PService (i2p::data::SigningKeyType kt); virtual ~I2PService () { ClearHandlers (); } inline void AddHandler (std::shared_ptr conn) { @@ -53,7 +54,7 @@ namespace client class I2PServiceHandler { public: - I2PServiceHandler(I2PService * parent) : m_Dead(false), m_Service(parent) { } + I2PServiceHandler(I2PService * parent) : m_Service(parent), m_Dead(false) { } virtual ~I2PServiceHandler() { } protected: // Call when terminating or handing over to avoid race conditions diff --git a/SOCKS.cpp b/SOCKS.cpp index 0bc2e051..068b4499 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -172,7 +172,7 @@ namespace proxy boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port) { - size_t size; + size_t size = 6; assert(error <= SOCKS5_ADDR_UNSUP); m_response[0] = '\x05'; //Version m_response[1] = error; //Response code From c1a29b08acee79afd41104afe86189bffc4932a9 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Wed, 7 Jan 2015 21:50:12 +0100 Subject: [PATCH 0014/6300] Remove ClientContext.h dependency --- AddressBook.h | 4 +++- Destination.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/AddressBook.h b/AddressBook.h index 5b24c017..1d22018e 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -23,6 +23,8 @@ namespace client const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 240; // in minutes const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes const int SUBSCRIPTION_REQUEST_TIMEOUT = 60; //in second + + inline std::string GetB32Address(const i2p::data::IdentHash& ident) { return ident.ToBase32().append(".b32.i2p"); } class AddressBookStorage // interface for storage { @@ -55,7 +57,7 @@ namespace client void LoadHostsFromStream (std::istream& f); void DownloadComplete (bool success); //This method returns the ".b32.i2p" address - std::string ToAddress(const i2p::data::IdentHash& ident) { return ident.ToBase32().append(".b32.i2p"); } + std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); } std::string ToAddress(const i2p::data::IdentityEx& ident) { return ToAddress(ident.GetIdentHash ()); } private: diff --git a/Destination.cpp b/Destination.cpp index abaf0de0..77cae1d0 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -6,7 +6,7 @@ #include "ElGamal.h" #include "Timestamp.h" #include "NetDb.h" -#include "ClientContext.h" +#include "AddressBook.h" #include "Destination.h" namespace i2p @@ -47,7 +47,7 @@ namespace client } m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (this, inboundTunnelLen, outboundTunnelLen); if (m_IsPublic) - LogPrint (eLogInfo, "Local address ", i2p::client::context.GetAddressBook ().ToAddress(GetIdentHash()), " created"); + LogPrint (eLogInfo, "Local address ", i2p::client::GetB32Address(GetIdentHash()), " created"); m_StreamingDestination = new i2p::stream::StreamingDestination (*this); // TODO: } From b3232b42db7a39e22bf0cd96ccd33dde36a19393 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Wed, 7 Jan 2015 21:52:40 +0100 Subject: [PATCH 0015/6300] Use shared_from_this to avoid being killed easily on stop --- HTTPProxy.cpp | 6 +++--- I2PService.h | 15 +++++++++------ SOCKS.cpp | 16 ++++++++++------ 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 22940592..b99c63b2 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -62,7 +62,7 @@ namespace proxy LogPrint(eLogDebug,"--- HTTP Proxy async sock read"); if(m_sock) { m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), - std::bind(&HTTPProxyHandler::HandleSockRecv, this, + std::bind(&HTTPProxyHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { LogPrint(eLogError,"--- HTTP Proxy no socket for read"); @@ -86,7 +86,7 @@ namespace proxy { std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n"; boost::asio::async_write(*m_sock, boost::asio::buffer(response,response.size()), - std::bind(&HTTPProxyHandler::SentHTTPFailed, this, std::placeholders::_1)); + std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } void HTTPProxyHandler::EnterState(HTTPProxyHandler::state nstate) { @@ -197,7 +197,7 @@ namespace proxy if (m_state == DONE) { LogPrint(eLogInfo,"--- HTTP Proxy requested: ", m_url); GetOwner()->CreateStream (std::bind (&HTTPProxyHandler::HandleStreamRequestComplete, - this, std::placeholders::_1), m_address, m_port); + shared_from_this(), std::placeholders::_1), m_address, m_port); } else { AsyncSockRead(); } diff --git a/I2PService.h b/I2PService.h index 5345ab6d..6d9e878f 100644 --- a/I2PService.h +++ b/I2PService.h @@ -21,24 +21,27 @@ namespace client I2PService (i2p::data::SigningKeyType kt); virtual ~I2PService () { ClearHandlers (); } - inline void AddHandler (std::shared_ptr conn) { + inline void AddHandler (std::shared_ptr conn) + { std::unique_lock l(m_HandlersMutex); m_Handlers.insert(conn); } - inline void RemoveHandler (std::shared_ptr conn) { + inline void RemoveHandler (std::shared_ptr conn) + { std::unique_lock l(m_HandlersMutex); m_Handlers.erase(conn); } - inline void ClearHandlers () { + inline void ClearHandlers () + { std::unique_lock l(m_HandlersMutex); m_Handlers.clear(); } - inline ClientDestination * GetLocalDestination () { return m_LocalDestination; }; - inline void SetLocalDestination (ClientDestination * dest) { m_LocalDestination = dest; }; + inline ClientDestination * GetLocalDestination () { return m_LocalDestination; } + inline void SetLocalDestination (ClientDestination * dest) { m_LocalDestination = dest; } void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port = 0); - inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); }; + inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); } virtual void Start () = 0; virtual void Stop () = 0; diff --git a/SOCKS.cpp b/SOCKS.cpp index 068b4499..d4db0767 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -137,7 +137,7 @@ namespace proxy LogPrint(eLogDebug,"--- SOCKS async sock read"); if(m_sock) { m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size), - std::bind(&SOCKSHandler::HandleSockRecv, this, + std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { LogPrint(eLogError,"--- SOCKS no socket for read"); @@ -204,11 +204,13 @@ namespace proxy boost::asio::const_buffers_1 response(m_response,2); if (m_authchosen == AUTH_UNACCEPTABLE) { LogPrint(eLogWarning,"--- SOCKS5 authentication negotiation failed"); - boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, this, std::placeholders::_1)); + boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, + shared_from_this(), std::placeholders::_1)); return false; } else { LogPrint(eLogDebug,"--- SOCKS5 choosing authentication method: ", m_authchosen); - boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse, this, std::placeholders::_1)); + boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse, + shared_from_this(), std::placeholders::_1)); return true; } } @@ -229,7 +231,8 @@ namespace proxy response = GenerateSOCKS5Response(error, m_addrtype, m_address, m_port); break; } - boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, this, std::placeholders::_1)); + boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, + shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::SocksRequestSuccess() @@ -249,7 +252,8 @@ namespace proxy response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, ad, m_stream->GetRecvStreamID()); break; } - boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone, this, std::placeholders::_1)); + boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone, + shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::EnterState(SOCKSHandler::state nstate, uint8_t parseleft) { @@ -455,7 +459,7 @@ namespace proxy if (m_state == DONE) { LogPrint(eLogInfo,"--- SOCKS requested ", m_address.dns.ToString(), ":" , m_port); GetOwner()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete, - this, std::placeholders::_1), m_address.dns.ToString(), m_port); + shared_from_this(), std::placeholders::_1), m_address.dns.ToString(), m_port); } else { AsyncSockRead(); } From 0339a4f963068a29a09fd4c17260120f67779df6 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 7 Jan 2015 16:09:32 -0500 Subject: [PATCH 0016/6300] JSON parser --- I2PControl.cpp | 100 +++++++++++++++++++++++++++++++++++++++++++++++++ I2PControl.h | 31 ++++++++++++++- 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 260e8b80..efb425e2 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -1,5 +1,7 @@ +#include #include #include +#include "Log.h" #include "I2PControl.h" namespace i2p @@ -11,5 +13,103 @@ namespace client m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)) { } + + I2PControlService::~I2PControlService () + { + Stop (); + } + + void I2PControlService::Start () + { + if (!m_IsRunning) + { + Accept (); + m_IsRunning = true; + m_Thread = new std::thread (std::bind (&I2PControlService::Run, this)); + } + } + + void I2PControlService::Stop () + { + if (m_IsRunning) + { + m_IsRunning = false; + m_Acceptor.cancel (); + m_Service.stop (); + if (m_Thread) + { + m_Thread->join (); + delete m_Thread; + m_Thread = nullptr; + } + } + } + + void I2PControlService::Run () + { + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "I2PControl: ", ex.what ()); + } + } + } + + void I2PControlService::Accept () + { + auto newSocket = std::make_shared (m_Service); + m_Acceptor.async_accept (*newSocket, std::bind (&I2PControlService::HandleAccept, this, + std::placeholders::_1, newSocket)); + } + + void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) + { + if (ecode != boost::asio::error::operation_aborted) + Accept (); + + if (!ecode) + { + LogPrint (eLogInfo, "New I2PControl request from ", socket->remote_endpoint ()); + ReadRequest (socket); + } + else + LogPrint (eLogError, "I2PControl accept error: ", ecode.message ()); + } + + void I2PControlService::ReadRequest (std::shared_ptr socket) + { + auto request = std::make_shared(); + socket->async_read_some (boost::asio::buffer (*request), + std::bind(&I2PControlService::HandleRequestReceived, this, + std::placeholders::_1, std::placeholders::_2, socket, request)); + } + + void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode, + size_t bytes_transferred, std::shared_ptr socket, + std::shared_ptr buf) + { + if (ecode) + { + LogPrint (eLogError, "I2PControl read error: ", ecode.message ()); + } + else + { + std::stringstream ss; + ss.write (buf->data (), bytes_transferred); + boost::property_tree::ptree pt; + boost::property_tree::read_json (ss, pt); + std::string method = pt.get(I2P_CONTROL_PROPERTY_METHOD); + auto it = m_MethodHanders.find (method); + if (it != m_MethodHanders.end ()) + (this->*(it->second))(); + else + LogPrint (eLogWarning, "Unknown I2PControl method ", method); + } + } } } diff --git a/I2PControl.h b/I2PControl.h index 1c0e90da..38dca6b1 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -1,18 +1,44 @@ #ifndef I2P_CONTROL_H__ #define I2P_CONTROL_H__ +#include #include +#include +#include +#include +#include #include namespace i2p { namespace client { + const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024; + typedef std::array I2PControlBuffer; + + const char I2P_CONTROL_PROPERTY_ID[] = "id"; + const char I2P_CONTROL_PROPERTY_METHOD[] = "method"; + const char I2P_CONTROL_PROPERTY_TOKEN[] = "Token"; + const char I2P_CONTROL_PROPERTY_PARAMS[] = "params"; + class I2PControlService { public: I2PControlService (int port); + ~I2PControlService (); + + void Start (); + void Stop (); + + private: + + void Run (); + void Accept (); + void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); + void ReadRequest (std::shared_ptr socket); + void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, + std::shared_ptr socket, std::shared_ptr buf); private: @@ -20,7 +46,10 @@ namespace client std::thread * m_Thread; boost::asio::io_service m_Service; - boost::asio::ip::tcp::acceptor m_Acceptor; + boost::asio::ip::tcp::acceptor m_Acceptor; + + typedef void (I2PControlService::*MethodHandler)(); + std::map m_MethodHanders; }; } } From bc11689f358054a3b004075c559e3fe9e65b2321 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 7 Jan 2015 16:41:11 -0500 Subject: [PATCH 0017/6300] extract params --- I2PControl.cpp | 10 +++++++++- I2PControl.h | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index efb425e2..203dce8e 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -106,7 +106,15 @@ namespace client std::string method = pt.get(I2P_CONTROL_PROPERTY_METHOD); auto it = m_MethodHanders.find (method); if (it != m_MethodHanders.end ()) - (this->*(it->second))(); + { + std::map params; + for (auto& v: pt.get_child (I2P_CONTROL_PROPERTY_PARAMS)) + { + if (!v.first.empty()) + params[v.first] = v.second.data (); + } + (this->*(it->second))(params); + } else LogPrint (eLogWarning, "Unknown I2PControl method ", method); } diff --git a/I2PControl.h b/I2PControl.h index 38dca6b1..f46afcb7 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -48,7 +48,7 @@ namespace client boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; - typedef void (I2PControlService::*MethodHandler)(); + typedef void (I2PControlService::*MethodHandler)(const std::map& params); std::map m_MethodHanders; }; } From 6643b4188a75464e20144ccfdbd9a689157bb0f9 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Wed, 7 Jan 2015 22:49:28 +0100 Subject: [PATCH 0018/6300] Solve weak_ptr issue --- HTTPProxy.cpp | 7 +++++-- SOCKS.cpp | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index b99c63b2..77c2be40 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -53,8 +53,9 @@ namespace proxy public: HTTPProxyHandler(HTTPProxyServer * parent, boost::asio::ip::tcp::socket * sock) : I2PServiceHandler(parent), m_sock(sock) - { AsyncSockRead(); EnterState(GET_METHOD); } + { EnterState(GET_METHOD); } ~HTTPProxyHandler() { Terminate(); } + void Handle () { AsyncSockRead(); } }; void HTTPProxyHandler::AsyncSockRead() @@ -255,7 +256,9 @@ namespace proxy if (!ecode) { LogPrint(eLogDebug,"--- HTTP Proxy accepted"); - AddHandler(std::make_shared (this, socket)); + auto handler = std::make_shared (this, socket); + AddHandler(handler); + handler->Handle(); Accept(); } else diff --git a/SOCKS.cpp b/SOCKS.cpp index d4db0767..1cae4c83 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -128,8 +128,9 @@ namespace proxy SOCKSHandler(SOCKSServer * parent, boost::asio::ip::tcp::socket * sock) : I2PServiceHandler(parent), m_sock(sock), m_stream(nullptr), m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4) - { m_address.ip = 0; EnterState(GET_SOCKSV); AsyncSockRead(); } + { m_address.ip = 0; EnterState(GET_SOCKSV); } ~SOCKSHandler() { Terminate(); } + void Handle() { AsyncSockRead(); } }; void SOCKSHandler::AsyncSockRead() @@ -538,7 +539,9 @@ namespace proxy if (!ecode) { LogPrint(eLogDebug,"--- SOCKS accepted"); - AddHandler(std::make_shared (this, socket)); + auto handle = std::make_shared (this, socket); + AddHandler(handle); + handle->Handle(); Accept(); } else From 114022d18a1947217b02351a3be1686bb89d79c6 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Thu, 8 Jan 2015 01:31:31 +0100 Subject: [PATCH 0019/6300] Add the TCPIPAcceptor class for handling TCP/IP services on clearnet --- I2PService.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ I2PService.h | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/I2PService.cpp b/I2PService.cpp index 568d03eb..69cee2bc 100644 --- a/I2PService.cpp +++ b/I2PService.cpp @@ -32,5 +32,49 @@ namespace client streamRequestComplete (nullptr); } } + + void TCPIPAcceptor::Start () + { + m_Acceptor.listen (); + Accept (); + } + + void TCPIPAcceptor::Stop () + { + m_Acceptor.close(); + m_Timer.cancel (); + ClearHandlers(); + } + + void TCPIPAcceptor::Accept () + { + auto newSocket = new boost::asio::ip::tcp::socket (GetService ()); + m_Acceptor.async_accept (*newSocket, std::bind (&TCPIPAcceptor::HandleAccept, this, + std::placeholders::_1, newSocket)); + } + + void TCPIPAcceptor::HandleAccept (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket) + { + if (!ecode) + { + LogPrint(eLogDebug,"--- ",GetName()," accepted"); + auto handler = CreateHandler(socket); + if (handler) { + AddHandler(handler); + handler->Handle(); + } else { + socket->close(); + delete socket; + } + Accept(); + } + else + { + if (ecode != boost::asio::error::operation_aborted) + LogPrint (eLogError,"--- ",GetName()," Closing socket on accept because: ", ecode.message ()); + delete socket; + } + } + } } diff --git a/I2PService.h b/I2PService.h index 6d9e878f..636a9780 100644 --- a/I2PService.h +++ b/I2PService.h @@ -46,6 +46,7 @@ namespace client virtual void Start () = 0; virtual void Stop () = 0; + virtual const char* GetName() { return "Generic I2P Service"; } private: ClientDestination * m_LocalDestination; @@ -59,19 +60,48 @@ namespace client public: I2PServiceHandler(I2PService * parent) : m_Service(parent), m_Dead(false) { } virtual ~I2PServiceHandler() { } + //If you override this make sure you call it from the children + virtual void Handle() {}; //Start handling the socket protected: // Call when terminating or handing over to avoid race conditions - inline bool Kill() { return m_Dead.exchange(true); } + inline bool Kill () { return m_Dead.exchange(true); } // Call to know if the handler is dead - inline bool Dead() { return m_Dead; } + inline bool Dead () { return m_Dead; } // Call when done to clean up (make sure Kill is called first) - inline void Done(std::shared_ptr me) { if(m_Service) m_Service->RemoveHandler(me); } + inline void Done (std::shared_ptr me) { if(m_Service) m_Service->RemoveHandler(me); } // Call to talk with the owner inline I2PService * GetOwner() { return m_Service; } private: I2PService *m_Service; std::atomic m_Dead; //To avoid cleaning up multiple times }; + + //This is a service that listens for connections on the IP network and interacts with I2P + class TCPIPAcceptor: public I2PService + { + public: + TCPIPAcceptor (int port, ClientDestination * localDestination = nullptr) : + I2PService(localDestination), + m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), + m_Timer (GetService ()) {} + TCPIPAcceptor (int port, i2p::data::SigningKeyType kt) : + I2PService(kt), + m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), + m_Timer (GetService ()) {} + virtual ~TCPIPAcceptor () { TCPIPAcceptor::Stop(); } + //If you override this make sure you call it from the children + void Start (); + //If you override this make sure you call it from the children + void Stop (); + protected: + virtual std::shared_ptr CreateHandler(boost::asio::ip::tcp::socket * socket) = 0; + virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; } + private: + void Accept(); + void HandleAccept(const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket); + boost::asio::ip::tcp::acceptor m_Acceptor; + boost::asio::deadline_timer m_Timer; + }; } } From 8a6bea64bce85795998b2ea4ee18960feb53440b Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Thu, 8 Jan 2015 01:35:42 +0100 Subject: [PATCH 0020/6300] Make the HTTP Proxy use TCPIPAcceptor --- HTTPProxy.cpp | 36 ++---------------------------------- HTTPProxy.h | 25 +++++++------------------ 2 files changed, 9 insertions(+), 52 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 77c2be40..5659a80b 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -231,41 +231,9 @@ namespace proxy } } - void HTTPProxyServer::Start () + std::shared_ptr HTTPProxyServer::CreateHandler(boost::asio::ip::tcp::socket * socket) { - m_Acceptor.listen (); - Accept (); - } - - void HTTPProxyServer::Stop () - { - m_Acceptor.close(); - m_Timer.cancel (); - ClearHandlers(); - } - - void HTTPProxyServer::Accept () - { - auto newSocket = new boost::asio::ip::tcp::socket (GetService ()); - m_Acceptor.async_accept (*newSocket, std::bind (&HTTPProxyServer::HandleAccept, this, - std::placeholders::_1, newSocket)); - } - - void HTTPProxyServer::HandleAccept (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket) - { - if (!ecode) - { - LogPrint(eLogDebug,"--- HTTP Proxy accepted"); - auto handler = std::make_shared (this, socket); - AddHandler(handler); - handler->Handle(); - Accept(); - } - else - { - LogPrint (eLogError,"--- HTTP Proxy Closing socket on accept because: ", ecode.message ()); - delete socket; - } + return std::make_shared (this, socket); } } diff --git a/HTTPProxy.h b/HTTPProxy.h index 8e348478..4558c88f 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -12,27 +12,16 @@ namespace i2p namespace proxy { class HTTPProxyHandler; - class HTTPProxyServer: public i2p::client::I2PService + class HTTPProxyServer: public i2p::client::TCPIPAcceptor { - private: - std::set > m_Handlers; - boost::asio::ip::tcp::acceptor m_Acceptor; - boost::asio::deadline_timer m_Timer; - std::mutex m_HandlersMutex; - - private: - - void Accept(); - void HandleAccept(const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket); + protected: + // Implements TCPIPAcceptor + std::shared_ptr CreateHandler(boost::asio::ip::tcp::socket * socket); + const char* GetName() { return "HTTP Proxy"; } public: - HTTPProxyServer(int port) : I2PService(i2p::data::SIGNING_KEY_TYPE_DSA_SHA1), - m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), - m_Timer (GetService ()) {}; - ~HTTPProxyServer() { Stop(); } - - void Start (); - void Stop (); + HTTPProxyServer(int port) : TCPIPAcceptor(port, i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) {} + ~HTTPProxyServer() {} }; typedef HTTPProxyServer HTTPProxy; From 7d9c0b76fcbadf2f86f5e30f58aa044902a1867c Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Thu, 8 Jan 2015 01:45:49 +0100 Subject: [PATCH 0021/6300] Make SOCKS use TCPIPAcceptor --- SOCKS.cpp | 36 ++---------------------------------- SOCKS.h | 26 +++++++------------------- 2 files changed, 9 insertions(+), 53 deletions(-) diff --git a/SOCKS.cpp b/SOCKS.cpp index 1cae4c83..ac30b6bd 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -514,41 +514,9 @@ namespace proxy } } - void SOCKSServer::Start () + std::shared_ptr SOCKSServer::CreateHandler(boost::asio::ip::tcp::socket * socket) { - m_Acceptor.listen (); - Accept (); - } - - void SOCKSServer::Stop () - { - m_Acceptor.close(); - m_Timer.cancel (); - ClearHandlers(); - } - - void SOCKSServer::Accept () - { - auto newSocket = new boost::asio::ip::tcp::socket (GetService ()); - m_Acceptor.async_accept (*newSocket, std::bind (&SOCKSServer::HandleAccept, this, - std::placeholders::_1, newSocket)); - } - - void SOCKSServer::HandleAccept (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket) - { - if (!ecode) - { - LogPrint(eLogDebug,"--- SOCKS accepted"); - auto handle = std::make_shared (this, socket); - AddHandler(handle); - handle->Handle(); - Accept(); - } - else - { - LogPrint (eLogError,"--- SOCKS Closing socket on accept because: ", ecode.message ()); - delete socket; - } + return std::make_shared (this, socket); } } diff --git a/SOCKS.h b/SOCKS.h index dc97bc0e..3d107f1c 100644 --- a/SOCKS.h +++ b/SOCKS.h @@ -11,28 +11,16 @@ namespace i2p { namespace proxy { - class SOCKSHandler; - class SOCKSServer: public i2p::client::I2PService + class SOCKSServer: public i2p::client::TCPIPAcceptor { - private: - std::set > m_Handlers; - boost::asio::ip::tcp::acceptor m_Acceptor; - boost::asio::deadline_timer m_Timer; - std::mutex m_HandlersMutex; - - private: - - void Accept(); - void HandleAccept(const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket); + protected: + // Implements TCPIPAcceptor + std::shared_ptr CreateHandler(boost::asio::ip::tcp::socket * socket); + const char* GetName() { return "SOCKS"; } public: - SOCKSServer(int port) : I2PService(nullptr), - m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), - m_Timer (GetService ()) {}; - ~SOCKSServer() { Stop(); } - - void Start (); - void Stop (); + SOCKSServer(int port) : TCPIPAcceptor(port) {} + ~SOCKSServer() {} }; typedef SOCKSServer SOCKSProxy; From df3e8ce93708a26a5772014cf50450b899e3a76c Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Thu, 8 Jan 2015 03:28:54 +0100 Subject: [PATCH 0022/6300] Move Stream creation to its own handler for cleanliness, it will hand over to a tunnel connection when done --- I2PTunnel.cpp | 76 ++++++++++++++++++++++++++++++++++++++------------- I2PTunnel.h | 1 - 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 2f96b859..6771abd5 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -151,6 +151,57 @@ namespace client } } + /* This handler tries to stablish a connection with the desired server and dies if it fails to do so */ + class I2PClientTunnelHandler: public I2PServiceHandler, public std::enable_shared_from_this + { + public: + I2PClientTunnelHandler (I2PClientTunnel * parent, i2p::data::IdentHash destination, + boost::asio::ip::tcp::socket * socket): + I2PServiceHandler(parent), m_DestinationIdentHash(destination), m_Socket(socket) {} + void Handle(); + void Terminate(); + private: + void HandleStreamRequestComplete (std::shared_ptr stream); + i2p::data::IdentHash m_DestinationIdentHash; + boost::asio::ip::tcp::socket * m_Socket; + }; + + void I2PClientTunnelHandler::Handle() + { + GetOwner()->GetLocalDestination ()->CreateStream (std::bind (&I2PClientTunnelHandler::HandleStreamRequestComplete, + shared_from_this(), std::placeholders::_1), m_DestinationIdentHash); + } + + void I2PClientTunnelHandler::HandleStreamRequestComplete (std::shared_ptr stream) + { + if (stream) + { + if (Kill()) return; + LogPrint (eLogInfo,"New I2PTunnel connection"); + auto connection = std::make_shared(GetOwner(), m_Socket, stream); + GetOwner()->AddHandler (connection); + connection->I2PConnect (); + Done(shared_from_this()); + } + else + { + LogPrint (eLogError,"I2P Client Tunnel Issue when creating the stream, check the previous warnings for more info."); + Terminate(); + } + } + + void I2PClientTunnelHandler::Terminate() + { + if (Kill()) return; + if (m_Socket) + { + m_Socket->close(); + delete m_Socket; + m_Socket = nullptr; + } + Done(shared_from_this()); + } + I2PClientTunnel::I2PClientTunnel (const std::string& destination, int port, ClientDestination * localDestination): I2PService (localDestination), m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), @@ -208,12 +259,15 @@ namespace client { const i2p::data::IdentHash *identHash = GetIdentHash(); if (identHash) - GetLocalDestination ()->CreateStream ( - std::bind (&I2PClientTunnel::HandleStreamRequestComplete, - this, std::placeholders::_1, socket), *identHash); + { + auto connection = std::make_shared(this, *identHash, socket); + AddHandler (connection); + connection->Handle (); + } else { LogPrint (eLogError,"Closing socket"); + socket->close(); delete socket; } Accept (); @@ -225,22 +279,6 @@ namespace client } } - void I2PClientTunnel::HandleStreamRequestComplete (std::shared_ptr stream, boost::asio::ip::tcp::socket * socket) - { - if (stream) - { - LogPrint (eLogInfo,"New I2PTunnel connection"); - auto connection = std::make_shared(this, socket, stream); - AddHandler (connection); - connection->I2PConnect (); - } - else - { - LogPrint (eLogError,"Issue when creating the stream, check the previous warnings for more info."); - delete socket; - } - } - I2PServerTunnel::I2PServerTunnel (const std::string& address, int port, ClientDestination * localDestination): I2PService (localDestination), m_Endpoint (boost::asio::ip::address::from_string (address), port) { diff --git a/I2PTunnel.h b/I2PTunnel.h index f19f128c..374fa107 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -70,7 +70,6 @@ namespace client const i2p::data::IdentHash * GetIdentHash (); void Accept (); void HandleAccept (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket); - void HandleStreamRequestComplete (std::shared_ptr stream, boost::asio::ip::tcp::socket * socket); private: From 56014962d4cba1b585b2bd18b1890284f3908c79 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Thu, 8 Jan 2015 03:49:35 +0100 Subject: [PATCH 0023/6300] Make I2PClientTunnel use TCPIPAcceptor --- HTTPProxy.h | 1 - I2PService.h | 1 + I2PTunnel.cpp | 53 ++++++++------------------------------------------- I2PTunnel.h | 16 ++++++++-------- 4 files changed, 17 insertions(+), 54 deletions(-) diff --git a/HTTPProxy.h b/HTTPProxy.h index 4558c88f..a7b81553 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -11,7 +11,6 @@ namespace i2p { namespace proxy { - class HTTPProxyHandler; class HTTPProxyServer: public i2p::client::TCPIPAcceptor { protected: diff --git a/I2PService.h b/I2PService.h index 636a9780..a03f16ed 100644 --- a/I2PService.h +++ b/I2PService.h @@ -76,6 +76,7 @@ namespace client std::atomic m_Dead; //To avoid cleaning up multiple times }; + /* TODO: support IPv6 too */ //This is a service that listens for connections on the IP network and interacts with I2P class TCPIPAcceptor: public I2PService { diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 6771abd5..fe59b7d7 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -203,29 +203,17 @@ namespace client } I2PClientTunnel::I2PClientTunnel (const std::string& destination, int port, ClientDestination * localDestination): - I2PService (localDestination), - m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), - m_Timer (GetService ()), m_Destination (destination), m_DestinationIdentHash (nullptr) - { - } + TCPIPAcceptor (port,localDestination), m_Destination (destination), m_DestinationIdentHash (nullptr) + {} - I2PClientTunnel::~I2PClientTunnel () - { - Stop (); - } - void I2PClientTunnel::Start () { GetIdentHash(); - m_Acceptor.listen (); - Accept (); } void I2PClientTunnel::Stop () { - m_Acceptor.close(); - m_Timer.cancel (); - ClearHandlers (); + TCPIPAcceptor::Stop(); auto *originalIdentHash = m_DestinationIdentHash; m_DestinationIdentHash = nullptr; delete originalIdentHash; @@ -245,38 +233,13 @@ namespace client return m_DestinationIdentHash; } - - void I2PClientTunnel::Accept () + std::shared_ptr I2PClientTunnel::CreateHandler(boost::asio::ip::tcp::socket * socket) { - auto newSocket = new boost::asio::ip::tcp::socket (GetService ()); - m_Acceptor.async_accept (*newSocket, std::bind (&I2PClientTunnel::HandleAccept, this, - std::placeholders::_1, newSocket)); - } - - void I2PClientTunnel::HandleAccept (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket) - { - if (!ecode) - { - const i2p::data::IdentHash *identHash = GetIdentHash(); - if (identHash) - { - auto connection = std::make_shared(this, *identHash, socket); - AddHandler (connection); - connection->Handle (); - } - else - { - LogPrint (eLogError,"Closing socket"); - socket->close(); - delete socket; - } - Accept (); - } + const i2p::data::IdentHash *identHash = GetIdentHash(); + if (identHash) + return std::make_shared(this, *identHash, socket); else - { - LogPrint (eLogError,"Closing socket on accept because: ", ecode.message ()); - delete socket; - } + return nullptr; } I2PServerTunnel::I2PServerTunnel (const std::string& address, int port, ClientDestination * localDestination): diff --git a/I2PTunnel.h b/I2PTunnel.h index 374fa107..010f4843 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -55,12 +55,18 @@ namespace client bool m_IsQuiet; // don't send destination }; - class I2PClientTunnel: public I2PService + class I2PClientTunnel: public TCPIPAcceptor { + protected: + + // Implements TCPIPAcceptor + std::shared_ptr CreateHandler(boost::asio::ip::tcp::socket * socket); + const char* GetName() { return "I2P Client Tunnel"; } + public: I2PClientTunnel (const std::string& destination, int port, ClientDestination * localDestination = nullptr); - ~I2PClientTunnel (); + ~I2PClientTunnel () {} void Start (); void Stop (); @@ -68,13 +74,7 @@ namespace client private: const i2p::data::IdentHash * GetIdentHash (); - void Accept (); - void HandleAccept (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket); - private: - - boost::asio::ip::tcp::acceptor m_Acceptor; - boost::asio::deadline_timer m_Timer; std::string m_Destination; const i2p::data::IdentHash * m_DestinationIdentHash; }; From e82507ca4ea646ac4cfc6733269787b9c93fab34 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Jan 2015 07:39:35 -0500 Subject: [PATCH 0024/6300] call TCPIPAccetor::Start from I2PClientTunnel::Start --- I2PTunnel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index fe59b7d7..bfd7cfce 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -208,6 +208,7 @@ namespace client void I2PClientTunnel::Start () { + TCPIPAcceptor::Start (); GetIdentHash(); } From efdadfd7c599bc64da4da071d1792c718fe3be7c Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Jan 2015 13:28:51 -0500 Subject: [PATCH 0025/6300] added I2PControl to ClientContext --- ClientContext.cpp | 18 +++++++++++++++++- ClientContext.h | 2 ++ I2PControl.cpp | 40 +++++++++++++++++++++++++++++++++++++++- I2PControl.h | 15 ++++++++++++++- README.md | 1 + 5 files changed, 73 insertions(+), 3 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index b6126fcb..4a34cbdc 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -12,7 +12,8 @@ namespace client ClientContext::ClientContext (): m_SharedLocalDestination (nullptr), m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_IrcTunnel (nullptr), - m_ServerTunnel (nullptr), m_SamBridge (nullptr), m_BOBCommandChannel (nullptr) + m_ServerTunnel (nullptr), m_SamBridge (nullptr), m_BOBCommandChannel (nullptr), + m_I2PControlService (nullptr) { } @@ -24,6 +25,7 @@ namespace client delete m_ServerTunnel; delete m_SamBridge; delete m_BOBCommandChannel; + delete m_I2PControlService; } void ClientContext::Start () @@ -75,6 +77,13 @@ namespace client m_BOBCommandChannel->Start (); LogPrint("BOB command channel started"); } + int i2pcontrolPort = i2p::util::config::GetArg("-i2pcontrolport", 0); + if (i2pcontrolPort) + { + m_I2PControlService = new I2PControlService (i2pcontrolPort); + m_I2PControlService->Start (); + LogPrint("I2PControl started"); + } m_AddressBook.StartSubscriptions (); } @@ -117,6 +126,13 @@ namespace client m_BOBCommandChannel = nullptr; LogPrint("BOB command channel stoped"); } + if (m_I2PControlService) + { + m_I2PControlService->Stop (); + delete m_I2PControlService; + m_I2PControlService = nullptr; + LogPrint("I2PControl stoped"); + } for (auto it: m_Destinations) { diff --git a/ClientContext.h b/ClientContext.h index 4ee81549..f472dc32 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -9,6 +9,7 @@ #include "SAM.h" #include "BOB.h" #include "AddressBook.h" +#include "I2PControl.h" namespace i2p { @@ -49,6 +50,7 @@ namespace client I2PServerTunnel * m_ServerTunnel; SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; + I2PControlService * m_I2PControlService; public: // for HTTP diff --git a/I2PControl.cpp b/I2PControl.cpp index 203dce8e..e95a6bbd 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -12,6 +12,7 @@ namespace client m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)) { + m_MethodHanders[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlService::RouterInfoHandler; } I2PControlService::~I2PControlService () @@ -113,11 +114,48 @@ namespace client if (!v.first.empty()) params[v.first] = v.second.data (); } - (this->*(it->second))(params); + std::map results; + (this->*(it->second))(params, results); + SendResponse (socket, buf, pt.get(I2P_CONTROL_PROPERTY_ID), results); } else LogPrint (eLogWarning, "Unknown I2PControl method ", method); } } + + void I2PControlService::SendResponse (std::shared_ptr socket, + std::shared_ptr buf, const std::string& id, + const std::map& results) + { + boost::property_tree::ptree ptr; + for (auto& result: results) + ptr.put (result.first, result.second); + + boost::property_tree::ptree pt; + pt.put (I2P_CONTROL_PROPERTY_ID, id); + pt.put_child (I2P_CONTROL_PROPERTY_RESULT, ptr); + pt.put ("jsonrpc", "2.0"); + + std::ostringstream ss; + boost::property_tree::write_json (ss, pt, false); + size_t len = ss.str ().length (); + memcpy (buf->data (), ss.str ().c_str (), len); + boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), len), + boost::asio::transfer_all (), + std::bind(&I2PControlService::HandleResponseSent, this, + std::placeholders::_1, std::placeholders::_2, socket, buf)); + } + + void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, + std::shared_ptr socket, std::shared_ptr buf) + { + if (ecode) + LogPrint (eLogError, "I2PControl write error: ", ecode.message ()); + socket->close (); + } + + void I2PControlService::RouterInfoHandler (const std::map& params, std::map results) + { + } } } diff --git a/I2PControl.h b/I2PControl.h index f46afcb7..600e44d9 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -20,6 +20,10 @@ namespace client const char I2P_CONTROL_PROPERTY_METHOD[] = "method"; const char I2P_CONTROL_PROPERTY_TOKEN[] = "Token"; const char I2P_CONTROL_PROPERTY_PARAMS[] = "params"; + const char I2P_CONTROL_PROPERTY_RESULT[] = "result"; + + // methods + const char I2P_CONTROL_METHOD_ROUTER_INFO[] = "RouterInfo"; class I2PControlService { @@ -39,6 +43,15 @@ namespace client void ReadRequest (std::shared_ptr socket); void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); + void SendResponse (std::shared_ptr socket, + std::shared_ptr buf, const std::string& id, + const std::map& results); + void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, + std::shared_ptr socket, std::shared_ptr buf); + + private: + + void RouterInfoHandler (const std::map& params, std::map results); private: @@ -48,7 +61,7 @@ namespace client boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; - typedef void (I2PControlService::*MethodHandler)(const std::map& params); + typedef void (I2PControlService::*MethodHandler)(const std::map& params, std::map results); std::map m_MethodHanders; }; } diff --git a/README.md b/README.md index 7e354a76..f5bcab00 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Cmdline options * --eepport= - Port incoming trafic forward to. 80 by default * --samport= - Port of SAM bridge. Usually 7656. SAM is off if not specified * --bobport= - Port of BOB command channel. Usually 2827. BOB is off if not specified +* --i2pcontrolport= - Port of I2P control service. Usually 7650. I2PControl is off if not specified * --conf= - Config file (default: ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf) This parameter will be silently ignored if the specified config file does not exist. Options specified on the command line take precedence over those in the config file. From f3548daede3ff809f3926648524da23a9be361f1 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Jan 2015 16:11:40 -0500 Subject: [PATCH 0026/6300] I2PControl Authenticate and Echo --- I2PControl.cpp | 69 +++++++++++++++++++++++++++++++++++++------------- I2PControl.h | 12 ++++++++- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index e95a6bbd..6529c0e1 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -1,7 +1,9 @@ #include +#include #include #include #include "Log.h" +#include "Timestamp.h" #include "I2PControl.h" namespace i2p @@ -12,6 +14,8 @@ namespace client m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)) { + m_MethodHanders[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlService::AuthenticateHandler; + m_MethodHanders[I2P_CONTROL_METHOD_ECHO] = &I2PControlService::EchoHandler; m_MethodHanders[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlService::RouterInfoHandler; } @@ -100,26 +104,37 @@ namespace client } else { - std::stringstream ss; - ss.write (buf->data (), bytes_transferred); - boost::property_tree::ptree pt; - boost::property_tree::read_json (ss, pt); - std::string method = pt.get(I2P_CONTROL_PROPERTY_METHOD); - auto it = m_MethodHanders.find (method); - if (it != m_MethodHanders.end ()) + try { - std::map params; - for (auto& v: pt.get_child (I2P_CONTROL_PROPERTY_PARAMS)) + std::stringstream ss; + ss.write (buf->data (), bytes_transferred); + boost::property_tree::ptree pt; + boost::property_tree::read_json (ss, pt); + std::string method = pt.get(I2P_CONTROL_PROPERTY_METHOD); + auto it = m_MethodHanders.find (method); + if (it != m_MethodHanders.end ()) { - if (!v.first.empty()) - params[v.first] = v.second.data (); - } - std::map results; - (this->*(it->second))(params, results); - SendResponse (socket, buf, pt.get(I2P_CONTROL_PROPERTY_ID), results); - } - else - LogPrint (eLogWarning, "Unknown I2PControl method ", method); + std::map params; + for (auto& v: pt.get_child (I2P_CONTROL_PROPERTY_PARAMS)) + { + if (!v.first.empty()) + params[v.first] = v.second.data (); + } + std::map results; + (this->*(it->second))(params, results); + SendResponse (socket, buf, pt.get(I2P_CONTROL_PROPERTY_ID), results); + } + else + LogPrint (eLogWarning, "Unknown I2PControl method ", method); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "I2PControl handle request: ", ex.what ()); + } + catch (...) + { + LogPrint (eLogError, "I2PControl handle request unknown exception"); + } } } @@ -154,6 +169,24 @@ namespace client socket->close (); } +// handlers + + void I2PControlService::AuthenticateHandler (const std::map& params, std::map results) + { + const std::string& api = params.at (I2P_CONTROL_PARAM_API); + const std::string& password = params.at (I2P_CONTROL_PARAM_PASSWORD); + LogPrint (eLogDebug, "I2PControl Authenticate API=", api, " Password=", password); + results[I2P_CONTROL_PARAM_API] = api; + results[I2P_CONTROL_PARAM_TOKEN] = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); + } + + void I2PControlService::EchoHandler (const std::map& params, std::map results) + { + const std::string& echo = params.at (I2P_CONTROL_PARAM_ECHO); + LogPrint (eLogDebug, "I2PControl Echo Echo=", echo); + results[I2P_CONTROL_PARAM_RESULT] = echo; + } + void I2PControlService::RouterInfoHandler (const std::map& params, std::map results) { } diff --git a/I2PControl.h b/I2PControl.h index 600e44d9..59174f29 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -18,13 +18,21 @@ namespace client const char I2P_CONTROL_PROPERTY_ID[] = "id"; const char I2P_CONTROL_PROPERTY_METHOD[] = "method"; - const char I2P_CONTROL_PROPERTY_TOKEN[] = "Token"; const char I2P_CONTROL_PROPERTY_PARAMS[] = "params"; const char I2P_CONTROL_PROPERTY_RESULT[] = "result"; // methods + const char I2P_CONTROL_METHOD_AUTHENTICATE[] = "Authenticate"; + const char I2P_CONTROL_METHOD_ECHO[] = "Echo"; const char I2P_CONTROL_METHOD_ROUTER_INFO[] = "RouterInfo"; + // params + const char I2P_CONTROL_PARAM_API[] = "API"; + const char I2P_CONTROL_PARAM_PASSWORD[] = "Password"; + const char I2P_CONTROL_PARAM_TOKEN[] = "Token"; + const char I2P_CONTROL_PARAM_ECHO[] = "Echo"; + const char I2P_CONTROL_PARAM_RESULT[] = "Result"; + class I2PControlService { public: @@ -51,6 +59,8 @@ namespace client private: + void AuthenticateHandler (const std::map& params, std::map results); + void EchoHandler (const std::map& params, std::map results); void RouterInfoHandler (const std::map& params, std::map results); private: From 2a76f1decd6dd7719d1c78184633e8eceed71a40 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Jan 2015 16:14:05 -0500 Subject: [PATCH 0027/6300] publish own RouterInfo every 40 minutes if nothing changed --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index c959edb8..612cdfb2 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -162,7 +162,7 @@ namespace data } lastSave = ts; } - if (ts - lastPublish >= 600) // publish every 10 minutes + if (ts - lastPublish >= 2400) // publish every 40 minutes { Publish (); lastPublish = ts; From 1bbb86d304ce3942facde34f31924c156a6da0f7 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Jan 2015 16:16:56 -0500 Subject: [PATCH 0028/6300] changed addressbook subscription update interval to 12 hours --- AddressBook.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AddressBook.h b/AddressBook.h index 1d22018e..dc4ac54c 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -20,7 +20,7 @@ namespace client const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p/hosts.txt"; const int INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT = 3; // in minutes const int INITIAL_SUBSCRIPTION_RETRY_TIMEOUT = 1; // in minutes - const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 240; // in minutes + const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours) const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes const int SUBSCRIPTION_REQUEST_TIMEOUT = 60; //in second From c61ed150b77b24cbd5afc14bf84fe7e485475f31 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Jan 2015 22:04:41 -0500 Subject: [PATCH 0029/6300] check for pending LeaseSet request --- Destination.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 77cae1d0..bee54062 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -464,14 +464,24 @@ namespace client { LeaseSetRequest * request = new LeaseSetRequest (m_Service); request->requestComplete = requestComplete; - m_LeaseSetRequests[dest] = request; - if (!SendLeaseSetRequest (dest, floodfill, request)) + auto ret = m_LeaseSetRequests.insert (std::pair(dest,request)); + if (ret.second) // inserted { - // request failed + if (!SendLeaseSetRequest (dest, floodfill, request)) + { + // request failed + if (request->requestComplete) request->requestComplete (false); + delete request; + m_LeaseSetRequests.erase (dest); + } + } + else // duplicate + { + LogPrint (eLogError, "Request of ", dest.ToBase64 (), " is pending already"); + // TODO: queue up requests if (request->requestComplete) request->requestComplete (false); delete request; - m_LeaseSetRequests.erase (dest); - } + } } else LogPrint (eLogError, "No floodfills found"); From 8910412068c32512693bc0a88cb5bcf626052f77 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 9 Jan 2015 10:28:16 -0500 Subject: [PATCH 0030/6300] pass results by reference --- I2PControl.cpp | 6 +++--- I2PControl.h | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 6529c0e1..18fe469b 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -171,7 +171,7 @@ namespace client // handlers - void I2PControlService::AuthenticateHandler (const std::map& params, std::map results) + void I2PControlService::AuthenticateHandler (const std::map& params, std::map& results) { const std::string& api = params.at (I2P_CONTROL_PARAM_API); const std::string& password = params.at (I2P_CONTROL_PARAM_PASSWORD); @@ -180,14 +180,14 @@ namespace client results[I2P_CONTROL_PARAM_TOKEN] = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); } - void I2PControlService::EchoHandler (const std::map& params, std::map results) + void I2PControlService::EchoHandler (const std::map& params, std::map& results) { const std::string& echo = params.at (I2P_CONTROL_PARAM_ECHO); LogPrint (eLogDebug, "I2PControl Echo Echo=", echo); results[I2P_CONTROL_PARAM_RESULT] = echo; } - void I2PControlService::RouterInfoHandler (const std::map& params, std::map results) + void I2PControlService::RouterInfoHandler (const std::map& params, std::map& results) { } } diff --git a/I2PControl.h b/I2PControl.h index 59174f29..f203466d 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -59,9 +59,9 @@ namespace client private: - void AuthenticateHandler (const std::map& params, std::map results); - void EchoHandler (const std::map& params, std::map results); - void RouterInfoHandler (const std::map& params, std::map results); + void AuthenticateHandler (const std::map& params, std::map& results); + void EchoHandler (const std::map& params, std::map& results); + void RouterInfoHandler (const std::map& params, std::map& results); private: @@ -71,7 +71,7 @@ namespace client boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; - typedef void (I2PControlService::*MethodHandler)(const std::map& params, std::map results); + typedef void (I2PControlService::*MethodHandler)(const std::map& params, std::map& results); std::map m_MethodHanders; }; } From 4ce3817d280ee0bd3ac8869a7196da034ef8f943 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 9 Jan 2015 11:11:35 -0500 Subject: [PATCH 0031/6300] put dot-separated params --- I2PControl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/I2PControl.cpp b/I2PControl.cpp index 18fe469b..9d2540ec 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -145,6 +145,7 @@ namespace client boost::property_tree::ptree ptr; for (auto& result: results) ptr.put (result.first, result.second); + ptr.put (boost::property_tree::ptree::path_type ("xxx.yyy",'/'), "zzz"); boost::property_tree::ptree pt; pt.put (I2P_CONTROL_PROPERTY_ID, id); From 047f08b482ad1484b3fd3f2e23240f1b1ae59f53 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 9 Jan 2015 11:12:22 -0500 Subject: [PATCH 0032/6300] put dot-separated params --- I2PControl.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 9d2540ec..7a03ab86 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -144,8 +144,7 @@ namespace client { boost::property_tree::ptree ptr; for (auto& result: results) - ptr.put (result.first, result.second); - ptr.put (boost::property_tree::ptree::path_type ("xxx.yyy",'/'), "zzz"); + ptr.put (boost::property_tree::ptree::path_type (result.first, '/'), result.second); boost::property_tree::ptree pt; pt.put (I2P_CONTROL_PROPERTY_ID, id); From 70b6c024bfeab157455fbc4871fb7d9f06ab4934 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 9 Jan 2015 11:58:14 -0500 Subject: [PATCH 0033/6300] handle i2p.router.netdb.knownpeers RouterInfo request --- I2PControl.cpp | 9 +++++++++ I2PControl.h | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/I2PControl.cpp b/I2PControl.cpp index 7a03ab86..af4c8796 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -3,6 +3,7 @@ #include #include #include "Log.h" +#include "NetDb.h" #include "Timestamp.h" #include "I2PControl.h" @@ -117,6 +118,7 @@ namespace client std::map params; for (auto& v: pt.get_child (I2P_CONTROL_PROPERTY_PARAMS)) { + LogPrint (eLogInfo, v.first); if (!v.first.empty()) params[v.first] = v.second.data (); } @@ -189,6 +191,13 @@ namespace client void I2PControlService::RouterInfoHandler (const std::map& params, std::map& results) { + LogPrint (eLogDebug, "I2PControl RouterInfo"); + for (auto& it :params) + { + LogPrint (eLogDebug, it.first); + if (it.first == I2P_CONTROL_PARAM_RI_NETDB_KNOWNPEERS) + results[I2P_CONTROL_PARAM_RI_NETDB_KNOWNPEERS] = boost::lexical_cast(i2p::data::netdb.GetNumRouters ()); + } } } } diff --git a/I2PControl.h b/I2PControl.h index f203466d..b5aed649 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -33,6 +33,10 @@ namespace client const char I2P_CONTROL_PARAM_ECHO[] = "Echo"; const char I2P_CONTROL_PARAM_RESULT[] = "Result"; + // RouterInfo params + const char I2P_CONTROL_PARAM_RI_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; + + class I2PControlService { public: From 912146b1c991534b4fd8f413a77f22ff35fb643c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 9 Jan 2015 22:27:52 -0500 Subject: [PATCH 0034/6300] shutdown and graceful shutdown through I2PControl --- I2PControl.cpp | 42 ++++++++++++++++++++++++++++++++++++++++-- I2PControl.h | 14 ++++++++++---- Tunnel.cpp | 13 +++++++++++++ Tunnel.h | 1 + 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index af4c8796..49085f5e 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -4,6 +4,9 @@ #include #include "Log.h" #include "NetDb.h" +#include "RouterContext.h" +#include "Daemon.h" +#include "Tunnel.h" #include "Timestamp.h" #include "I2PControl.h" @@ -13,11 +16,13 @@ namespace client { I2PControlService::I2PControlService (int port): m_IsRunning (false), m_Thread (nullptr), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)) + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)), + m_ShutdownTimer (m_Service) { m_MethodHanders[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlService::AuthenticateHandler; m_MethodHanders[I2P_CONTROL_METHOD_ECHO] = &I2PControlService::EchoHandler; m_MethodHanders[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlService::RouterInfoHandler; + m_MethodHanders[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlService::RouterManagerHandler; } I2PControlService::~I2PControlService () @@ -192,12 +197,45 @@ namespace client void I2PControlService::RouterInfoHandler (const std::map& params, std::map& results) { LogPrint (eLogDebug, "I2PControl RouterInfo"); - for (auto& it :params) + for (auto& it: params) { LogPrint (eLogDebug, it.first); if (it.first == I2P_CONTROL_PARAM_RI_NETDB_KNOWNPEERS) results[I2P_CONTROL_PARAM_RI_NETDB_KNOWNPEERS] = boost::lexical_cast(i2p::data::netdb.GetNumRouters ()); } } + + void I2PControlService::RouterManagerHandler (const std::map& params, std::map& results) + { + LogPrint (eLogDebug, "I2PControl RouterManager"); + for (auto& it: params) + { + LogPrint (eLogDebug, it.first); + if (it.first == I2P_CONTROL_PARAM_ROUTER_MANAGER_SHUTDOWN) + { + LogPrint (eLogInfo, "Shutdown requested"); + results[I2P_CONTROL_PARAM_ROUTER_MANAGER_SHUTDOWN] = ""; + m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent + m_ShutdownTimer.async_wait ( + [](const boost::system::error_code& ecode) + { + Daemon.running = 0; + }); + } + else if (it.first == I2P_CONTROL_PARAM_ROUTER_MANAGER_SHUTDOWN_GRACEFUL) + { + i2p::context.SetAcceptsTunnels (false); + int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout (); + LogPrint (eLogInfo, "Graceful shutdown requested. Will shutdown after ", timeout, " seconds"); + results[I2P_CONTROL_PARAM_ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = ""; + m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second + m_ShutdownTimer.async_wait ( + [](const boost::system::error_code& ecode) + { + Daemon.running = 0; + }); + } + } + } } } diff --git a/I2PControl.h b/I2PControl.h index b5aed649..7874a3a2 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -25,7 +25,8 @@ namespace client const char I2P_CONTROL_METHOD_AUTHENTICATE[] = "Authenticate"; const char I2P_CONTROL_METHOD_ECHO[] = "Echo"; const char I2P_CONTROL_METHOD_ROUTER_INFO[] = "RouterInfo"; - + const char I2P_CONTROL_METHOD_ROUTER_MANAGER[] = "RouterManager"; + // params const char I2P_CONTROL_PARAM_API[] = "API"; const char I2P_CONTROL_PARAM_PASSWORD[] = "Password"; @@ -36,7 +37,10 @@ namespace client // RouterInfo params const char I2P_CONTROL_PARAM_RI_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; - + // RouterManager params + const char I2P_CONTROL_PARAM_ROUTER_MANAGER_SHUTDOWN[] = "Shutdown"; + const char I2P_CONTROL_PARAM_ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful"; + class I2PControlService { public: @@ -66,14 +70,16 @@ namespace client void AuthenticateHandler (const std::map& params, std::map& results); void EchoHandler (const std::map& params, std::map& results); void RouterInfoHandler (const std::map& params, std::map& results); - + void RouterManagerHandler (const std::map& params, std::map& results); + private: bool m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; - boost::asio::ip::tcp::acceptor m_Acceptor; + boost::asio::ip::tcp::acceptor m_Acceptor; + boost::asio::deadline_timer m_ShutdownTimer; typedef void (I2PControlService::*MethodHandler)(const std::map& params, std::map& results); std::map m_MethodHanders; diff --git a/Tunnel.cpp b/Tunnel.cpp index 1e6aacd2..a569a329 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -610,5 +610,18 @@ namespace tunnel i2p::context.GetSharedRouterInfo () })); } + + int Tunnels::GetTransitTunnelsExpirationTimeout () + { + int timeout = 0; + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + std::unique_lock l(m_TransitTunnelsMutex); + for (auto it: m_TransitTunnels) + { + int t = it.second->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; + if (t > timeout) timeout = t; + } + return timeout; + } } } diff --git a/Tunnel.h b/Tunnel.h index d5c48ee7..99f44976 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -123,6 +123,7 @@ namespace tunnel OutboundTunnel * GetNextOutboundTunnel (); TunnelPool * GetExploratoryPool () const { return m_ExploratoryPool; }; TransitTunnel * GetTransitTunnel (uint32_t tunnelID); + int GetTransitTunnelsExpirationTimeout (); void AddTransitTunnel (TransitTunnel * tunnel); void AddOutboundTunnel (OutboundTunnel * newTunnel); void AddInboundTunnel (InboundTunnel * newTunnel); From 717940d969191a2a666cced8400d6e19ac4c9995 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 10 Jan 2015 09:07:07 -0500 Subject: [PATCH 0035/6300] some cleanup --- NetDb.cpp | 79 +++++++++++++++---------------------------------------- NetDb.h | 2 -- 2 files changed, 21 insertions(+), 60 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 612cdfb2..088cb495 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -29,7 +29,6 @@ namespace data replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory, &m_ExcludedPeers); m_ExcludedPeers.insert (router->GetIdentHash ()); - m_LastRouter = router; m_CreationTime = i2p::util::GetSecondsSinceEpoch (); return msg; } @@ -39,7 +38,6 @@ namespace data I2NPMessage * msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers); m_ExcludedPeers.insert (floodfill); - m_LastRouter = nullptr; m_CreationTime = i2p::util::GetSecondsSinceEpoch (); return msg; } @@ -508,51 +506,7 @@ namespace data LogPrint (key, " was not found on 7 floodfills"); } } - - for (int i = 0; i < num; i++) - { - uint8_t * router = buf + 33 + i*32; - char peerHash[48]; - int l1 = i2p::data::ByteStreamToBase64 (router, 32, peerHash, 48); - peerHash[l1] = 0; - LogPrint (i,": ", peerHash); - if (dest->IsExploratory ()) - { - auto r = FindRouter (router); - if (!r || i2p::util::GetMillisecondsSinceEpoch () > r->GetTimestamp () + 3600*1000LL) - { - // router with ident not found or too old (1 hour) - LogPrint ("Found new/outdated router. Requesting RouterInfo ..."); - if (outbound && inbound && dest->GetLastRouter ()) - { - RequestedDestination * d1 = CreateRequestedDestination (router, false); - auto msg = d1->CreateRequestMessage (dest->GetLastRouter (), inbound); - msgs.push_back (i2p::tunnel::TunnelMessageBlock - { - i2p::tunnel::eDeliveryTypeRouter, - dest->GetLastRouter ()->GetIdentHash (), 0, msg - }); - } - else - RequestDestination (router); - } - else - LogPrint ("Bayan"); - } - else - { - auto r = FindRouter (router); - // do we have that floodfill router in our database? - if (!r) - { - // request router - LogPrint ("Found new floodfill. Request it"); - RequestDestination (router); - } - } - } - if (outbound && msgs.size () > 0) outbound->SendTunnelDataMsg (msgs); if (deleteDest) @@ -569,20 +523,29 @@ namespace data m_RequestedDestinations.erase (it); } } - else - { + else LogPrint ("Requested destination for ", key, " not found"); - // it might contain new routers - for (int i = 0; i < num; i++) - { - IdentHash router (buf + 33 + i*32); - if (!FindRouter (router)) - { - LogPrint ("New router ", router.ToBase64 (), " found. Request it"); - RequestDestination (router); - } - } + + // try responses + for (int i = 0; i < num; i++) + { + uint8_t * router = buf + 33 + i*32; + char peerHash[48]; + int l1 = i2p::data::ByteStreamToBase64 (router, 32, peerHash, 48); + peerHash[l1] = 0; + LogPrint (i,": ", peerHash); + + auto r = FindRouter (router); + if (!r || i2p::util::GetMillisecondsSinceEpoch () > r->GetTimestamp () + 3600*1000LL) + { + // router with ident not found or too old (1 hour) + LogPrint ("Found new/outdated router. Requesting RouterInfo ..."); + RequestDestination (router); + } + else + LogPrint ("Bayan"); } + i2p::DeleteI2NPMessage (msg); } diff --git a/NetDb.h b/NetDb.h index 5aa6acec..24d0a499 100644 --- a/NetDb.h +++ b/NetDb.h @@ -31,7 +31,6 @@ namespace data int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); }; const std::set& GetExcludedPeers () { return m_ExcludedPeers; }; void ClearExcludedPeers (); - std::shared_ptr GetLastRouter () const { return m_LastRouter; }; bool IsExploratory () const { return m_IsExploratory; }; bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); }; uint64_t GetCreationTime () const { return m_CreationTime; }; @@ -43,7 +42,6 @@ namespace data IdentHash m_Destination; bool m_IsExploratory; std::set m_ExcludedPeers; - std::shared_ptr m_LastRouter; uint64_t m_CreationTime; }; From 4d25634b6640bc7c45bd5144d2e8dd5e882bb31c Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 10 Jan 2015 16:08:13 -0500 Subject: [PATCH 0036/6300] less agressive exploratory --- NetDb.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 088cb495..dc68bc22 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -109,7 +109,7 @@ namespace data void NetDb::Run () { - uint32_t lastSave = 0, lastPublish = 0; + uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0; m_IsRunning = true; while (m_IsRunning) { @@ -144,11 +144,8 @@ namespace data else { if (!m_IsRunning) break; - // if no new DatabaseStore coming, explore it ManageRequests (); - auto numRouters = m_RouterInfos.size (); - Explore (numRouters < 1500 ? 5 : 1); - } + } uint64_t ts = i2p::util::GetSecondsSinceEpoch (); if (ts - lastSave >= 60) // save routers, manage leasesets and validate subscriptions every minute @@ -165,6 +162,18 @@ namespace data Publish (); lastPublish = ts; } + if (ts - lastExploratory >= 30) // exploratory every 30 seconds + { + auto numRouters = m_RouterInfos.size (); + if (numRouters < 2500 || ts - lastExploratory >= 90) + { + numRouters = 800/numRouters; + if (numRouters < 1) numRouters = 1; + if (numRouters > 9) numRouters = 9; + Explore (numRouters); + lastExploratory = ts; + } + } } catch (std::exception& ex) { @@ -641,8 +650,8 @@ namespace data { // new requests auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : i2p::tunnel::tunnels.GetNextOutboundTunnel (); - auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : i2p::tunnel::tunnels.GetNextInboundTunnel (); + auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; + auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : nullptr; bool throughTunnels = outbound && inbound; CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); From d8942a335917a5a466ab314766bfe96ee6533572 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 10 Jan 2015 23:00:27 -0500 Subject: [PATCH 0037/6300] use TransportSession for sending messages --- TransportSession.h | 3 +++ Transports.cpp | 40 ++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/TransportSession.h b/TransportSession.h index f4079590..99d8d868 100644 --- a/TransportSession.h +++ b/TransportSession.h @@ -6,6 +6,7 @@ #include #include "Identity.h" #include "RouterInfo.h" +#include "I2NPProtocol.h" namespace i2p { @@ -64,6 +65,8 @@ namespace transport std::shared_ptr GetRemoteRouter () { return m_RemoteRouter; }; const i2p::data::IdentityEx& GetRemoteIdentity () { return m_RemoteIdentity; }; + virtual void SendI2NPMessage (I2NPMessage * msg) = 0; + protected: std::shared_ptr m_RemoteRouter; diff --git a/Transports.cpp b/Transports.cpp index 3992290a..680389e7 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -285,27 +285,26 @@ namespace transport void Transports::SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) { - if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) - // we send it to ourself - i2p::HandleI2NPMessage (msg); - else - m_Service.post (boost::bind (&Transports::PostMessage, this, ident, msg)); + m_Service.post (boost::bind (&Transports::PostMessage, this, ident, msg)); } void Transports::PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) { - auto session = FindNTCPSession (ident); - if (session) - session->SendI2NPMessage (msg); - else + if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) + { + // we send it to ourself + i2p::HandleI2NPMessage (msg); + return; + } + std::shared_ptr session = FindNTCPSession (ident); + if (!session) { auto r = netdb.FindRouter (ident); if (r) { - auto ssuSession = m_SSUServer ? m_SSUServer->FindSession (r) : nullptr; - if (ssuSession) - ssuSession->SendI2NPMessage (msg); - else + if (m_SSUServer) + session = m_SSUServer->FindSession (r); + if (!session) { // existing session not found. create new // try NTCP first if message size < 16K @@ -314,20 +313,19 @@ namespace transport { auto s = std::make_shared (m_Service, r); AddNTCPSession (s); - s->SendI2NPMessage (msg); + session = s; Connect (address->host, address->port, s); } else { // then SSU - auto s = m_SSUServer ? m_SSUServer->GetSession (r) : nullptr; - if (s) - s->SendI2NPMessage (msg); - else + if (m_SSUServer) + session = m_SSUServer->GetSession (r); + if (!session) { LogPrint ("No NTCP and SSU addresses available"); - DeleteI2NPMessage (msg); - } + DeleteI2NPMessage (msg); + } } } } @@ -341,6 +339,8 @@ namespace transport this, boost::asio::placeholders::error, resendTimer, ident, msg)); } } + if (session) + session->SendI2NPMessage (msg); } void Transports::HandleResendTimer (const boost::system::error_code& ecode, From e3e07028134451471fac350a114bf766e428259a Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 11 Jan 2015 17:40:11 -0500 Subject: [PATCH 0038/6300] fxied crash at startup --- NetDb.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index dc68bc22..85589700 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -478,8 +478,8 @@ namespace data if (num > 0) { auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = pool->GetNextOutboundTunnel (); - auto inbound = pool->GetNextInboundTunnel (); + auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; + auto inbound = pool ? pool->GetNextInboundTunnel () : nullptr; std::vector msgs; if (!dest->IsExploratory ()) { From 6683a9cf76cd141b5b613ec9279912a03f21273d Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 11 Jan 2015 17:41:56 -0500 Subject: [PATCH 0039/6300] moved NTCP to separate thread --- HTTPServer.cpp | 32 +++++---- NTCPSession.cpp | 182 ++++++++++++++++++++++++++++++++++++++++++++++-- NTCPSession.h | 48 ++++++++++++- Transports.cpp | 139 +++++------------------------------- Transports.h | 16 +---- 5 files changed, 261 insertions(+), 156 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 9da6f206..042a03c4 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -709,22 +709,26 @@ namespace util void HTTPConnection::ShowTransports (std::stringstream& s) { - s << "NTCP
"; - for (auto it: i2p::transport::transports.GetNTCPSessions ()) - { - if (it.second && it.second->IsEstablished ()) + auto ntcpServer = i2p::transport::transports.GetNTCPServer (); + if (ntcpServer) + { + s << "NTCP
"; + for (auto it: ntcpServer->GetNTCPSessions ()) { - // incoming connection doesn't have remote RI - auto outgoing = it.second->GetRemoteRouter (); - if (outgoing) s << "-->"; - s << it.second->GetRemoteIdentity ().GetIdentHash ().ToBase64 ().substr (0, 4) << ": " - << it.second->GetSocket ().remote_endpoint().address ().to_string (); - if (!outgoing) s << "-->"; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - s << "
"; + if (it.second && it.second->IsEstablished ()) + { + // incoming connection doesn't have remote RI + auto outgoing = it.second->GetRemoteRouter (); + if (outgoing) s << "-->"; + s << it.second->GetRemoteIdentity ().GetIdentHash ().ToBase64 ().substr (0, 4) << ": " + << it.second->GetSocket ().remote_endpoint().address ().to_string (); + if (!outgoing) s << "-->"; + s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + s << "
"; + } + s << std::endl; } - s << std::endl; - } + } auto ssuServer = i2p::transport::transports.GetSSUServer (); if (ssuServer) { diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 3f326098..753d1950 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -18,9 +18,9 @@ namespace i2p { namespace transport { - NTCPSession::NTCPSession (boost::asio::io_service& service, std::shared_ptr in_RemoteRouter): - TransportSession (in_RemoteRouter), m_Socket (service), - m_TerminationTimer (service), m_IsEstablished (false), m_ReceiveBufferOffset (0), + NTCPSession::NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter): + TransportSession (in_RemoteRouter), m_Server (server), m_Socket (m_Server.GetService ()), + m_TerminationTimer (m_Server.GetService ()), m_IsEstablished (false), m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0) { m_DHKeysPair = transports.GetNextDHKeysPair (); @@ -89,7 +89,7 @@ namespace transport if (numDelayed > 0) LogPrint (eLogWarning, "NTCP session ", numDelayed, " not sent"); // TODO: notify tunnels - transports.RemoveNTCPSession (shared_from_this ()); + m_Server.RemoveNTCPSession (shared_from_this ()); LogPrint ("NTCP session terminated"); } @@ -427,7 +427,7 @@ namespace transport { LogPrint (eLogDebug, "Phase 4 sent: ", bytes_transferred); LogPrint ("NTCP server session connected"); - transports.AddNTCPSession (shared_from_this ()); + m_Server.AddNTCPSession (shared_from_this ()); Connected (); m_ReceiveBufferOffset = 0; @@ -654,5 +654,177 @@ namespace transport m_Socket.close ();// invoke Terminate () from HandleReceive } } + +//----------------------------------------- + NTCPServer::NTCPServer (int port): + m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), + m_NTCPAcceptor (nullptr), m_NTCPV6Acceptor (nullptr) + { + } + + NTCPServer::~NTCPServer () + { + Stop (); + } + + void NTCPServer::Start () + { + if (!m_IsRunning) + { + m_IsRunning = true; + m_Thread = new std::thread (std::bind (&NTCPServer::Run, this)); + // create acceptors + auto addresses = context.GetRouterInfo ().GetAddresses (); + for (auto& address : addresses) + { + if (address.transportStyle == i2p::data::RouterInfo::eTransportNTCP && address.host.is_v4 ()) + { + m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, + boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address.port)); + + LogPrint ("Start listening TCP port ", address.port); + auto conn = std::make_shared(*this); + m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, + conn, std::placeholders::_1)); + + if (context.SupportsV6 ()) + { + m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service); + m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6()); + m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true)); + m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address.port)); + m_NTCPV6Acceptor->listen (); + + LogPrint ("Start listening V6 TCP port ", address.port); + auto conn = std::make_shared (*this); + m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, + this, conn, std::placeholders::_1)); + } + } + } + } + } + + void NTCPServer::Stop () + { + m_NTCPSessions.clear (); + + if (m_IsRunning) + { + m_IsRunning = false; + delete m_NTCPAcceptor; + m_NTCPAcceptor = nullptr; + delete m_NTCPV6Acceptor; + m_NTCPV6Acceptor = nullptr; + + m_Service.stop (); + if (m_Thread) + { + m_Thread->join (); + delete m_Thread; + m_Thread = nullptr; + } + } + } + + + void NTCPServer::Run () + { + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint ("NTCP server: ", ex.what ()); + } + } + } + + void NTCPServer::AddNTCPSession (std::shared_ptr session) + { + if (session) + m_NTCPSessions[session->GetRemoteIdentity ().GetIdentHash ()] = session; + } + + void NTCPServer::RemoveNTCPSession (std::shared_ptr session) + { + if (session) + m_NTCPSessions.erase (session->GetRemoteIdentity ().GetIdentHash ()); + } + + std::shared_ptr NTCPServer::FindNTCPSession (const i2p::data::IdentHash& ident) + { + auto it = m_NTCPSessions.find (ident); + if (it != m_NTCPSessions.end ()) + return it->second; + return nullptr; + } + + void NTCPServer::HandleAccept (std::shared_ptr conn, const boost::system::error_code& error) + { + if (!error) + { + LogPrint ("Connected from ", conn->GetSocket ().remote_endpoint().address ().to_string ()); + conn->ServerLogin (); + } + + + if (error != boost::asio::error::operation_aborted) + { + conn = std::make_shared (*this); + m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, + conn, std::placeholders::_1)); + } + } + + void NTCPServer::HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error) + { + if (!error) + { + LogPrint ("Connected from ", conn->GetSocket ().remote_endpoint().address ().to_string ()); + conn->ServerLogin (); + } + + if (error != boost::asio::error::operation_aborted) + { + conn = std::make_shared (*this); + m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this, + conn, std::placeholders::_1)); + } + } + + void NTCPServer::Connect (const boost::asio::ip::address& address, int port, std::shared_ptr conn) + { + LogPrint ("Connecting to ", address ,":", port); + m_Service.post([conn, this]() + { + this->AddNTCPSession (conn); + }); + conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port), + std::bind (&NTCPServer::HandleConnect, this, std::placeholders::_1, conn)); + } + + void NTCPServer::HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn) + { + if (ecode) + { + LogPrint ("Connect error: ", ecode.message ()); + if (ecode != boost::asio::error::operation_aborted) + { + i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ().GetIdentHash (), true); + conn->Terminate (); + } + } + else + { + LogPrint ("Connected"); + if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6 + context.UpdateNTCPV6Address (conn->GetSocket ().local_endpoint ().address ()); + conn->ClientLogin (); + } + } } } diff --git a/NTCPSession.h b/NTCPSession.h index 1d94475d..b08f6e44 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -3,7 +3,10 @@ #include #include +#include #include +#include +#include #include #include #include @@ -43,11 +46,12 @@ namespace transport const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes const size_t NTCP_DEFAULT_PHASE3_SIZE = 2/*size*/ + i2p::data::DEFAULT_IDENTITY_SIZE/*387*/ + 4/*ts*/ + 15/*padding*/ + 40/*signature*/; // 448 + class NTCPServer; class NTCPSession: public TransportSession, public std::enable_shared_from_this { public: - NTCPSession (boost::asio::io_service& service, std::shared_ptr in_RemoteRouter = nullptr); + NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter = nullptr); ~NTCPSession (); void Terminate (); @@ -103,6 +107,7 @@ namespace transport private: + NTCPServer& m_Server; boost::asio::ip::tcp::socket m_Socket; boost::asio::deadline_timer m_TerminationTimer; bool m_IsEstablished; @@ -127,6 +132,47 @@ namespace transport size_t m_NumSentBytes, m_NumReceivedBytes; }; + + // TODO: move to NTCP.h/.cpp + class NTCPServer + { + public: + + NTCPServer (int port); + ~NTCPServer (); + + void Start (); + void Stop (); + + void AddNTCPSession (std::shared_ptr session); + void RemoveNTCPSession (std::shared_ptr session); + std::shared_ptr FindNTCPSession (const i2p::data::IdentHash& ident); + void Connect (const boost::asio::ip::address& address, int port, std::shared_ptr conn); + + boost::asio::io_service& GetService () { return m_Service; }; + + private: + + void Run (); + void HandleAccept (std::shared_ptr conn, const boost::system::error_code& error); + void HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error); + + void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn); + + private: + + bool m_IsRunning; + std::thread * m_Thread; + boost::asio::io_service m_Service; + boost::asio::io_service::work m_Work; + boost::asio::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor; + std::map > m_NTCPSessions; + + public: + + // for HTTP/I2PControl + const decltype(m_NTCPSessions)& GetNTCPSessions () const { return m_NTCPSessions; }; + }; } } diff --git a/Transports.cpp b/Transports.cpp index 680389e7..f36cfd2a 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -96,8 +96,9 @@ namespace transport Transports transports; Transports::Transports (): - m_Thread (nullptr), m_Work (m_Service), m_NTCPAcceptor (nullptr), m_NTCPV6Acceptor (nullptr), - m_SSUServer (nullptr), m_DHKeysPairSupplier (5) // 5 pre-generated keys + m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), + m_NTCPServer (nullptr), m_SSUServer (nullptr), + m_DHKeysPairSupplier (5) // 5 pre-generated keys { } @@ -115,31 +116,13 @@ namespace transport auto addresses = context.GetRouterInfo ().GetAddresses (); for (auto& address : addresses) { - if (address.transportStyle == RouterInfo::eTransportNTCP && address.host.is_v4 ()) + if (!m_NTCPServer) { - m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, - boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address.port)); - - LogPrint ("Start listening TCP port ", address.port); - auto conn = std::make_shared(m_Service); - m_NTCPAcceptor->async_accept(conn->GetSocket (), boost::bind (&Transports::HandleAccept, this, - conn, boost::asio::placeholders::error)); - - if (context.SupportsV6 ()) - { - m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service); - m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6()); - m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true)); - m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address.port)); - m_NTCPV6Acceptor->listen (); - - LogPrint ("Start listening V6 TCP port ", address.port); - auto conn = std::make_shared (m_Service); - m_NTCPV6Acceptor->async_accept(conn->GetSocket (), boost::bind (&Transports::HandleAcceptV6, - this, conn, boost::asio::placeholders::error)); - } + m_NTCPServer = new NTCPServer (address.port); + m_NTCPServer->Start (); } - else if (address.transportStyle == RouterInfo::eTransportSSU && address.host.is_v4 ()) + + if (address.transportStyle == RouterInfo::eTransportSSU && address.host.is_v4 ()) { if (!m_SSUServer) { @@ -162,12 +145,12 @@ namespace transport delete m_SSUServer; m_SSUServer = nullptr; } - m_NTCPSessions.clear (); - - delete m_NTCPAcceptor; - m_NTCPAcceptor = nullptr; - delete m_NTCPV6Acceptor; - m_NTCPV6Acceptor = nullptr; + if (m_NTCPServer) + { + m_NTCPServer->Stop (); + delete m_NTCPServer; + m_NTCPServer = nullptr; + } m_DHKeysPairSupplier.Stop (); m_IsRunning = false; @@ -195,93 +178,6 @@ namespace transport } } - void Transports::AddNTCPSession (std::shared_ptr session) - { - if (session) - m_NTCPSessions[session->GetRemoteIdentity ().GetIdentHash ()] = session; - } - - void Transports::RemoveNTCPSession (std::shared_ptr session) - { - if (session) - m_NTCPSessions.erase (session->GetRemoteIdentity ().GetIdentHash ()); - } - - void Transports::HandleAccept (std::shared_ptr conn, const boost::system::error_code& error) - { - if (!error) - { - LogPrint ("Connected from ", conn->GetSocket ().remote_endpoint().address ().to_string ()); - conn->ServerLogin (); - } - - - if (error != boost::asio::error::operation_aborted) - { - conn = std::make_shared (m_Service); - m_NTCPAcceptor->async_accept(conn->GetSocket (), boost::bind (&Transports::HandleAccept, this, - conn, boost::asio::placeholders::error)); - } - } - - void Transports::HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error) - { - if (!error) - { - LogPrint ("Connected from ", conn->GetSocket ().remote_endpoint().address ().to_string ()); - conn->ServerLogin (); - } - - if (error != boost::asio::error::operation_aborted) - { - conn = std::make_shared (m_Service); - m_NTCPV6Acceptor->async_accept(conn->GetSocket (), boost::bind (&Transports::HandleAcceptV6, this, - conn, boost::asio::placeholders::error)); - } - } - - void Transports::Connect (const boost::asio::ip::address& address, int port, std::shared_ptr conn) - { - LogPrint ("Connecting to ", address ,":", port); - conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port), - boost::bind (&Transports::HandleConnect, this, boost::asio::placeholders::error, conn)); - } - - void Transports::HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn) - { - if (ecode) - { - LogPrint ("Connect error: ", ecode.message ()); - if (ecode != boost::asio::error::operation_aborted) - { - i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ().GetIdentHash (), true); - conn->Terminate (); - } - } - else - { - LogPrint ("Connected"); - if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6 - context.UpdateNTCPV6Address (conn->GetSocket ().local_endpoint ().address ()); - conn->ClientLogin (); - } - } - - std::shared_ptr Transports::GetNextNTCPSession () - { - for (auto session: m_NTCPSessions) - if (session.second->IsEstablished ()) - return session.second; - return 0; - } - - std::shared_ptr Transports::FindNTCPSession (const i2p::data::IdentHash& ident) - { - auto it = m_NTCPSessions.find (ident); - if (it != m_NTCPSessions.end ()) - return it->second; - return 0; - } void Transports::SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) { @@ -296,7 +192,7 @@ namespace transport i2p::HandleI2NPMessage (msg); return; } - std::shared_ptr session = FindNTCPSession (ident); + std::shared_ptr session = m_NTCPServer->FindNTCPSession (ident); if (!session) { auto r = netdb.FindRouter (ident); @@ -311,10 +207,9 @@ namespace transport auto address = r->GetNTCPAddress (!context.SupportsV6 ()); if (address && !r->UsesIntroducer () && !r->IsUnreachable () && msg->GetLength () < NTCP_MAX_MESSAGE_SIZE) { - auto s = std::make_shared (m_Service, r); - AddNTCPSession (s); + auto s = std::make_shared (*m_NTCPServer, r); session = s; - Connect (address->host, address->port, s); + m_NTCPServer->Connect (address->host, address->port, s); } else { diff --git a/Transports.h b/Transports.h index 3b5b2d4e..07f2afd3 100644 --- a/Transports.h +++ b/Transports.h @@ -64,27 +64,16 @@ namespace transport i2p::transport::DHKeysPair * GetNextDHKeysPair (); void ReuseDHKeysPair (DHKeysPair * pair); - void AddNTCPSession (std::shared_ptr session); - void RemoveNTCPSession (std::shared_ptr session); - - std::shared_ptr GetNextNTCPSession (); - std::shared_ptr FindNTCPSession (const i2p::data::IdentHash& ident); - void SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); void CloseSession (std::shared_ptr router); private: void Run (); - void HandleAccept (std::shared_ptr conn, const boost::system::error_code& error); - void HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error); void HandleResendTimer (const boost::system::error_code& ecode, boost::asio::deadline_timer * timer, const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); void PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); void PostCloseSession (std::shared_ptr router); - - void Connect (const boost::asio::ip::address& address, int port, std::shared_ptr conn); - void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn); void DetectExternalIP (); @@ -94,9 +83,8 @@ namespace transport std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; - boost::asio::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor; - std::map > m_NTCPSessions; + NTCPServer * m_NTCPServer; SSUServer * m_SSUServer; DHKeysPairSupplier m_DHKeysPairSupplier; @@ -104,7 +92,7 @@ namespace transport public: // for HTTP only - const decltype(m_NTCPSessions)& GetNTCPSessions () const { return m_NTCPSessions; }; + const NTCPServer * GetNTCPServer () const { return m_NTCPServer; }; const SSUServer * GetSSUServer () const { return m_SSUServer; }; }; From a85cc6aa773ed2246165517bee7bb30eb548450f Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 11 Jan 2015 21:00:38 -0500 Subject: [PATCH 0040/6300] fixed race condition --- NTCPSession.cpp | 9 +++++++-- NTCPSession.h | 1 + SSU.h | 3 ++- SSUSession.cpp | 3 ++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 753d1950..870d67df 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -627,6 +627,11 @@ namespace transport } void NTCPSession::SendI2NPMessage (I2NPMessage * msg) + { + m_Server.GetService ().post (std::bind (&NTCPSession::PostI2NPMessage, shared_from_this (), msg)); + } + + void NTCPSession::PostI2NPMessage (I2NPMessage * msg) { if (msg) { @@ -634,9 +639,9 @@ namespace transport Send (msg); else m_DelayedMessages.push_back (msg); - } + } } - + void NTCPSession::ScheduleTermination () { m_TerminationTimer.cancel (); diff --git a/NTCPSession.h b/NTCPSession.h index b08f6e44..6938f0ae 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -67,6 +67,7 @@ namespace transport protected: + void PostI2NPMessage (I2NPMessage * msg); void Connected (); void SendTimeSyncMessage (); void SetIsEstablished (bool isEstablished) { m_IsEstablished = isEstablished; } diff --git a/SSU.h b/SSU.h index 1e3624a4..057a42a2 100644 --- a/SSU.h +++ b/SSU.h @@ -38,7 +38,8 @@ namespace transport void DeleteSession (std::shared_ptr session); void DeleteAllSessions (); - boost::asio::io_service& GetService () { return m_Socket.get_io_service(); }; + boost::asio::io_service& GetService () { return m_Service; }; + boost::asio::io_service& GetServiceV6 () { return m_ServiceV6; }; const boost::asio::ip::udp::endpoint& GetEndpoint () const { return m_Endpoint; }; void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to); void AddRelay (uint32_t tag, const boost::asio::ip::udp::endpoint& relay); diff --git a/SSUSession.cpp b/SSUSession.cpp index 41a24470..11044921 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -828,7 +828,8 @@ namespace transport void SSUSession::SendI2NPMessage (I2NPMessage * msg) { - m_Server.GetService ().post (std::bind (&SSUSession::PostI2NPMessage, shared_from_this (), msg)); + boost::asio::io_service& service = IsV6 () ? m_Server.GetServiceV6 () : m_Server.GetService (); + service.post (std::bind (&SSUSession::PostI2NPMessage, shared_from_this (), msg)); } void SSUSession::PostI2NPMessage (I2NPMessage * msg) From 9ebe38e59dfbba9ddd67b3bd4cd3bb0c1fa79f64 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 12 Jan 2015 08:50:22 +0400 Subject: [PATCH 0041/6300] added I2PService to VS project --- Win32/i2pd.vcxproj | 12 +++++++----- Win32/i2pd.vcxproj.filters | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Win32/i2pd.vcxproj b/Win32/i2pd.vcxproj index 745ebe10..e34ead3d 100644 --- a/Win32/i2pd.vcxproj +++ b/Win32/i2pd.vcxproj @@ -22,7 +22,7 @@ - + @@ -32,6 +32,7 @@ + @@ -57,14 +58,14 @@ - + - + @@ -73,6 +74,7 @@ + @@ -103,7 +105,7 @@ - + @@ -282,4 +284,4 @@ - + \ No newline at end of file diff --git a/Win32/i2pd.vcxproj.filters b/Win32/i2pd.vcxproj.filters index 31d2a995..5392e604 100644 --- a/Win32/i2pd.vcxproj.filters +++ b/Win32/i2pd.vcxproj.filters @@ -135,6 +135,15 @@ Source Files + + Source Files + + + Source Files + + + Source Files + @@ -275,6 +284,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + From bf443265ff7cf5b54cfa6c0bff80c27c2bed6328 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 12 Jan 2015 08:53:24 +0400 Subject: [PATCH 0042/6300] fixed boost\asio\detail\socket_types.hpp(24) fatal error C1189: "WinSock.h has already been included" --- I2PControl.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 49085f5e..31e5dbf4 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -1,3 +1,5 @@ +#include "I2PControl.h" + #include #include #include @@ -8,7 +10,6 @@ #include "Daemon.h" #include "Tunnel.h" #include "Timestamp.h" -#include "I2PControl.h" namespace i2p { From a15c2c5d8611bfbe62f47b9aeb51e1ef451e4f4c Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Mon, 12 Jan 2015 17:54:09 +0200 Subject: [PATCH 0043/6300] license -> BSD 3-clause --- LICENSE | 354 ++++-------------------------------------------------- README.md | 6 + 2 files changed, 27 insertions(+), 333 deletions(-) diff --git a/LICENSE b/LICENSE index e26c6733..43610a26 100644 --- a/LICENSE +++ b/LICENSE @@ -1,339 +1,27 @@ -GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 +Copyright (c) 2013-2015, The PrivacySolutions Project - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. +All rights reserved. - Preamble +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. +1. Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. +2. Redistributions in binary form must reproduce the above copyright notice, this list of +conditions and the following disclaimer in the documentation and/or other materials +provided with the distribution. - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. +3. Neither the name of the copyright holder nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior written +permission. - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - i2p router for Linux written on C++ - Copyright (C) 2013 orignal - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index f5bcab00..b85e2ec6 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,12 @@ i2pd I2P router written in C++ +License +------- + +This project is licensed under the BSD 3-clause license, which can be found in the file +LICENSE in the root of the project source code. + Requirements for Linux/FreeBSD/OSX ---------------------------------- From ff856d2f201b155a81591abd8a21e77fa96bf208 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 12 Jan 2015 12:15:54 -0500 Subject: [PATCH 0044/6300] fixed race condition --- NTCPSession.cpp | 7 +++++++ NTCPSession.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 870d67df..435d0ace 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -751,17 +751,24 @@ namespace transport void NTCPServer::AddNTCPSession (std::shared_ptr session) { if (session) + { + std::unique_lock l(m_NTCPSessionsMutex); m_NTCPSessions[session->GetRemoteIdentity ().GetIdentHash ()] = session; + } } void NTCPServer::RemoveNTCPSession (std::shared_ptr session) { if (session) + { + std::unique_lock l(m_NTCPSessionsMutex); m_NTCPSessions.erase (session->GetRemoteIdentity ().GetIdentHash ()); + } } std::shared_ptr NTCPServer::FindNTCPSession (const i2p::data::IdentHash& ident) { + std::unique_lock l(m_NTCPSessionsMutex); auto it = m_NTCPSessions.find (ident); if (it != m_NTCPSessions.end ()) return it->second; diff --git a/NTCPSession.h b/NTCPSession.h index 6938f0ae..16d4e11d 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -167,6 +168,7 @@ namespace transport boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; boost::asio::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor; + std::mutex m_NTCPSessionsMutex; std::map > m_NTCPSessions; public: From aeb2e235e5e335d444c2e4ea4c5358a289e0673c Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 12 Jan 2015 13:38:16 -0500 Subject: [PATCH 0045/6300] use lookup tables for requests --- I2PControl.cpp | 92 ++++++++++++++++++++++++++++++++------------------ I2PControl.h | 28 +++++++++++---- 2 files changed, 81 insertions(+), 39 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 31e5dbf4..030676f3 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -20,10 +20,17 @@ namespace client m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)), m_ShutdownTimer (m_Service) { - m_MethodHanders[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlService::AuthenticateHandler; - m_MethodHanders[I2P_CONTROL_METHOD_ECHO] = &I2PControlService::EchoHandler; - m_MethodHanders[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlService::RouterInfoHandler; - m_MethodHanders[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlService::RouterManagerHandler; + m_MethodHandlers[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlService::AuthenticateHandler; + m_MethodHandlers[I2P_CONTROL_METHOD_ECHO] = &I2PControlService::EchoHandler; + m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlService::RouterInfoHandler; + m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlService::RouterManagerHandler; + + // RouterInfo + m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = &I2PControlService::NetDbKnownPeersHandler; + + // RouterManager + m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = &I2PControlService::ShutdownHandler; + m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = &I2PControlService::ShutdownGracefulHandler; } I2PControlService::~I2PControlService () @@ -118,8 +125,8 @@ namespace client boost::property_tree::ptree pt; boost::property_tree::read_json (ss, pt); std::string method = pt.get(I2P_CONTROL_PROPERTY_METHOD); - auto it = m_MethodHanders.find (method); - if (it != m_MethodHanders.end ()) + auto it = m_MethodHandlers.find (method); + if (it != m_MethodHandlers.end ()) { std::map params; for (auto& v: pt.get_child (I2P_CONTROL_PROPERTY_PARAMS)) @@ -195,48 +202,69 @@ namespace client results[I2P_CONTROL_PARAM_RESULT] = echo; } +// RouterInfo + void I2PControlService::RouterInfoHandler (const std::map& params, std::map& results) { LogPrint (eLogDebug, "I2PControl RouterInfo"); for (auto& it: params) { LogPrint (eLogDebug, it.first); - if (it.first == I2P_CONTROL_PARAM_RI_NETDB_KNOWNPEERS) - results[I2P_CONTROL_PARAM_RI_NETDB_KNOWNPEERS] = boost::lexical_cast(i2p::data::netdb.GetNumRouters ()); + auto it1 = m_RouterInfoHandlers.find (it.first); + if (it1 != m_RouterInfoHandlers.end ()) + (this->*(it1->second))(results); + else + LogPrint (eLogError, "I2PControl RouterInfo unknown request ", it.first); + } } + void I2PControlService::NetDbKnownPeersHandler (std::map& results) + { + results[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = boost::lexical_cast(i2p::data::netdb.GetNumRouters ()); + } + +// RouterManager + void I2PControlService::RouterManagerHandler (const std::map& params, std::map& results) { LogPrint (eLogDebug, "I2PControl RouterManager"); for (auto& it: params) { LogPrint (eLogDebug, it.first); - if (it.first == I2P_CONTROL_PARAM_ROUTER_MANAGER_SHUTDOWN) - { - LogPrint (eLogInfo, "Shutdown requested"); - results[I2P_CONTROL_PARAM_ROUTER_MANAGER_SHUTDOWN] = ""; - m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent - m_ShutdownTimer.async_wait ( - [](const boost::system::error_code& ecode) - { - Daemon.running = 0; - }); - } - else if (it.first == I2P_CONTROL_PARAM_ROUTER_MANAGER_SHUTDOWN_GRACEFUL) - { - i2p::context.SetAcceptsTunnels (false); - int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout (); - LogPrint (eLogInfo, "Graceful shutdown requested. Will shutdown after ", timeout, " seconds"); - results[I2P_CONTROL_PARAM_ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = ""; - m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second - m_ShutdownTimer.async_wait ( - [](const boost::system::error_code& ecode) - { - Daemon.running = 0; - }); - } + auto it1 = m_RouterManagerHandlers.find (it.first); + if (it1 != m_RouterManagerHandlers.end ()) + (this->*(it1->second))(results); + else + LogPrint (eLogError, "I2PControl RouterManager unknown request ", it.first); } } + + + void I2PControlService::ShutdownHandler (std::map& results) + { + LogPrint (eLogInfo, "Shutdown requested"); + results[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = ""; + m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent + m_ShutdownTimer.async_wait ( + [](const boost::system::error_code& ecode) + { + Daemon.running = 0; + }); + } + + void I2PControlService::ShutdownGracefulHandler (std::map& results) + { + i2p::context.SetAcceptsTunnels (false); + int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout (); + LogPrint (eLogInfo, "Graceful shutdown requested. Will shutdown after ", timeout, " seconds"); + results[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = ""; + m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second + m_ShutdownTimer.async_wait ( + [](const boost::system::error_code& ecode) + { + Daemon.running = 0; + }); + } } } diff --git a/I2PControl.h b/I2PControl.h index 7874a3a2..db530b09 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -34,12 +34,12 @@ namespace client const char I2P_CONTROL_PARAM_ECHO[] = "Echo"; const char I2P_CONTROL_PARAM_RESULT[] = "Result"; - // RouterInfo params - const char I2P_CONTROL_PARAM_RI_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; + // RouterInfo requests + const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; - // RouterManager params - const char I2P_CONTROL_PARAM_ROUTER_MANAGER_SHUTDOWN[] = "Shutdown"; - const char I2P_CONTROL_PARAM_ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful"; + // RouterManager requests + const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN[] = "Shutdown"; + const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful"; class I2PControlService { @@ -67,11 +67,23 @@ namespace client private: + // methods + typedef void (I2PControlService::*MethodHandler)(const std::map& params, std::map& results); + void AuthenticateHandler (const std::map& params, std::map& results); void EchoHandler (const std::map& params, std::map& results); void RouterInfoHandler (const std::map& params, std::map& results); void RouterManagerHandler (const std::map& params, std::map& results); + // RouterInfo + typedef void (I2PControlService::*RouterInfoRequestHandler)(std::map& results); + void NetDbKnownPeersHandler (std::map& results); + + // RouterManager + typedef void (I2PControlService::*RouterManagerRequestHandler)(std::map& results); + void ShutdownHandler (std::map& results); + void ShutdownGracefulHandler (std::map& results); + private: bool m_IsRunning; @@ -81,8 +93,10 @@ namespace client boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::deadline_timer m_ShutdownTimer; - typedef void (I2PControlService::*MethodHandler)(const std::map& params, std::map& results); - std::map m_MethodHanders; + + std::map m_MethodHandlers; + std::map m_RouterInfoHandlers; + std::map m_RouterManagerHandlers; }; } } From dcae7fc5412204814bd9fc39aae8cbd603d92903 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 12 Jan 2015 14:03:20 -0500 Subject: [PATCH 0046/6300] participating request --- I2PControl.cpp | 6 ++++++ I2PControl.h | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 030676f3..a614ff37 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -27,6 +27,7 @@ namespace client // RouterInfo m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = &I2PControlService::NetDbKnownPeersHandler; + m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlService::TunnelsParticipatingHandler; // RouterManager m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = &I2PControlService::ShutdownHandler; @@ -224,6 +225,11 @@ namespace client results[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = boost::lexical_cast(i2p::data::netdb.GetNumRouters ()); } + void I2PControlService::TunnelsParticipatingHandler (std::map& results) + { + results[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = boost::lexical_cast(i2p::tunnel::tunnels.GetTransitTunnels ().size ()); + } + // RouterManager void I2PControlService::RouterManagerHandler (const std::map& params, std::map& results) diff --git a/I2PControl.h b/I2PControl.h index db530b09..a2b976ac 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -36,7 +36,8 @@ namespace client // RouterInfo requests const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; - + const char I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating"; + // RouterManager requests const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN[] = "Shutdown"; const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful"; @@ -78,6 +79,7 @@ namespace client // RouterInfo typedef void (I2PControlService::*RouterInfoRequestHandler)(std::map& results); void NetDbKnownPeersHandler (std::map& results); + void TunnelsParticipatingHandler (std::map& results); // RouterManager typedef void (I2PControlService::*RouterManagerRequestHandler)(std::map& results); From 1eef9967010c782f6d0e3dbe432198fa129198d2 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 12 Jan 2015 14:31:45 -0500 Subject: [PATCH 0047/6300] NetworkSetting method --- I2PControl.cpp | 17 +++++++++++++++++ I2PControl.h | 11 ++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index a614ff37..aa36817f 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -24,6 +24,7 @@ namespace client m_MethodHandlers[I2P_CONTROL_METHOD_ECHO] = &I2PControlService::EchoHandler; m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlService::RouterInfoHandler; m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlService::RouterManagerHandler; + m_MethodHandlers[I2P_CONTROL_METHOD_NETWORK_SETTING] = &I2PControlService::NetworkSettingHandler; // RouterInfo m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = &I2PControlService::NetDbKnownPeersHandler; @@ -272,5 +273,21 @@ namespace client Daemon.running = 0; }); } + + // network setting + void I2PControlService::NetworkSettingHandler (const std::map& params, std::map& results) + { + LogPrint (eLogDebug, "I2PControl NetworkSetting"); + for (auto& it: params) + { + LogPrint (eLogDebug, it.first); + auto it1 = m_NetworkSettingHandlers.find (it.first); + if (it1 != m_NetworkSettingHandlers.end ()) + (this->*(it1->second))(it.second, results); + else + LogPrint (eLogError, "I2PControl NetworkSetting unknown request ", it.first); + } + } + } } diff --git a/I2PControl.h b/I2PControl.h index a2b976ac..2f51dab0 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -26,7 +26,8 @@ namespace client const char I2P_CONTROL_METHOD_ECHO[] = "Echo"; const char I2P_CONTROL_METHOD_ROUTER_INFO[] = "RouterInfo"; const char I2P_CONTROL_METHOD_ROUTER_MANAGER[] = "RouterManager"; - + const char I2P_CONTROL_METHOD_NETWORK_SETTING[] = "NetworkSetting"; + // params const char I2P_CONTROL_PARAM_API[] = "API"; const char I2P_CONTROL_PARAM_PASSWORD[] = "Password"; @@ -75,7 +76,8 @@ namespace client void EchoHandler (const std::map& params, std::map& results); void RouterInfoHandler (const std::map& params, std::map& results); void RouterManagerHandler (const std::map& params, std::map& results); - + void NetworkSettingHandler (const std::map& params, std::map& results); + // RouterInfo typedef void (I2PControlService::*RouterInfoRequestHandler)(std::map& results); void NetDbKnownPeersHandler (std::map& results); @@ -86,6 +88,9 @@ namespace client void ShutdownHandler (std::map& results); void ShutdownGracefulHandler (std::map& results); + // NetworkSetting + typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::map& results); + private: bool m_IsRunning; @@ -94,11 +99,11 @@ namespace client boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::deadline_timer m_ShutdownTimer; - std::map m_MethodHandlers; std::map m_RouterInfoHandlers; std::map m_RouterManagerHandlers; + std::map m_NetworkSettingHandlers; }; } } From d971dff59373fde05325993afca3e66f5b9cb2c9 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 12 Jan 2015 22:53:35 -0500 Subject: [PATCH 0048/6300] introduced Peer --- NTCPSession.cpp | 3 +++ SSUSession.cpp | 2 ++ Transports.cpp | 30 ++++++++++++++++++++++++++++++ Transports.h | 20 +++++++++++++++++++- 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 435d0ace..c0ba4451 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -77,6 +77,7 @@ namespace transport { m_IsEstablished = false; m_Socket.close (); + transports.PeerDisconnected (shared_from_this ()); int numDelayed = 0; for (auto it :m_DelayedMessages) { @@ -106,6 +107,8 @@ namespace transport SendTimeSyncMessage (); SendI2NPMessage (CreateDatabaseStoreMsg ()); // we tell immediately who we are + transports.PeerConnected (shared_from_this ()); + if (!m_DelayedMessages.empty ()) { for (auto it :m_DelayedMessages) diff --git a/SSUSession.cpp b/SSUSession.cpp index 11044921..4618624b 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -756,6 +756,7 @@ namespace transport void SSUSession::Close () { SendSesionDestroyed (); + transports.PeerDisconnected (shared_from_this ()); if (!m_DelayedMessages.empty ()) { for (auto it :m_DelayedMessages) @@ -773,6 +774,7 @@ namespace transport m_DHKeysPair = nullptr; } SendI2NPMessage (CreateDatabaseStoreMsg ()); + transports.PeerConnected (shared_from_this ()); if (!m_DelayedMessages.empty ()) { for (auto it :m_DelayedMessages) diff --git a/Transports.cpp b/Transports.cpp index f36cfd2a..b8e35bcb 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -139,6 +139,7 @@ namespace transport void Transports::Stop () { + m_Peers.clear (); if (m_SSUServer) { m_SSUServer->Stop (); @@ -291,6 +292,35 @@ namespace transport { m_DHKeysPairSupplier.Return (pair); } + + void Transports::PeerConnected (std::shared_ptr session) + { + m_Service.post([session, this]() + { + auto ident = session->GetRemoteIdentity ().GetIdentHash (); + auto it = m_Peers.find (ident); + if (it != m_Peers.end ()) + { + it->second.session = session; + for (auto it1: it->second.delayedMessages) + session->SendI2NPMessage (it1); + } + /* else // incoming connection + m_Peers[ident] = { nullptr, session };*/ + }); + } + + void Transports::PeerDisconnected (std::shared_ptr session) + { + m_Service.post([session, this]() + { + auto ident = session->GetRemoteIdentity ().GetIdentHash (); + auto it = m_Peers.find (ident); + if (it != m_Peers.end ()) + m_Peers.erase (it); + // TODO:: check for delayed messages + }); + } } } diff --git a/Transports.h b/Transports.h index 07f2afd3..6ab9330e 100644 --- a/Transports.h +++ b/Transports.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,19 @@ namespace transport CryptoPP::AutoSeededRandomPool m_Rnd; }; + struct Peer + { + std::shared_ptr router; + std::shared_ptr session; + std::list delayedMessages; + + ~Peer () + { + for (auto it :delayedMessages) + i2p::DeleteI2NPMessage (it); + } + }; + class Transports { public: @@ -66,6 +80,9 @@ namespace transport void SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); void CloseSession (std::shared_ptr router); + + void PeerConnected (std::shared_ptr session); + void PeerDisconnected (std::shared_ptr session); private: @@ -86,7 +103,8 @@ namespace transport NTCPServer * m_NTCPServer; SSUServer * m_SSUServer; - + std::map m_Peers; + DHKeysPairSupplier m_DHKeysPairSupplier; public: From 3a30c00dae254ffc8a32ab4945388a9e0f878b8f Mon Sep 17 00:00:00 2001 From: Timofey Titovets Date: Tue, 13 Jan 2015 12:55:14 +0300 Subject: [PATCH 0049/6300] Fix: mkdir exist in multi thread building --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 87a55fdb..d256c36e 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ endif all: mk_build_dir $(SHLIB) $(I2PD) mk_build_dir: - test -d obj || mkdir obj + mkdir -p obj api: $(SHLIB) @@ -37,12 +37,12 @@ api: $(SHLIB) ## custom FLAGS to work at build-time. deps: - @test -d obj || mkdir obj + @mkdir -p obj $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) -MM *.cpp > $(DEPS) @sed -i -e '/\.o:/ s/^/obj\//' $(DEPS) obj/%.o : %.cpp - @test -d obj || mkdir obj + @mkdir -p obj $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS) -c -o $@ $< # '-' is 'ignore if missing' on first run From 214eb0caa5b3f5694d168dbde679eb44cd7fef3a Mon Sep 17 00:00:00 2001 From: Timofey Titovets Date: Tue, 13 Jan 2015 15:14:52 +0300 Subject: [PATCH 0050/6300] Not overwrite -fPIC with CXXFLAGS --- Makefile.linux | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile.linux b/Makefile.linux index 100d6b6a..62445902 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -1,4 +1,4 @@ -CXXFLAGS = -g -Wall -fPIC +CXXFLAGS = -g -Wall INCFLAGS = ## NOTE: The NEEDED_CXXFLAGS are here so that custom CXXFLAGS can be specified at build time @@ -22,6 +22,8 @@ else # not supported $(error Compiler too old) endif +NEEDED_CXXFLAGS += -fPIC + ifeq ($(USE_STATIC),yes) LIBDIR := /usr/lib LDLIBS = $(LIBDIR)/libboost_system.a From 34811616160a305201b089a30e12dc4fa4ae9167 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 13 Jan 2015 21:31:39 -0500 Subject: [PATCH 0051/6300] send messages through Peer --- Transports.cpp | 128 +++++++++++++++++++++++++++++-------------------- Transports.h | 6 ++- 2 files changed, 79 insertions(+), 55 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index b8e35bcb..7414bc1d 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -193,65 +193,82 @@ namespace transport i2p::HandleI2NPMessage (msg); return; } - std::shared_ptr session = m_NTCPServer->FindNTCPSession (ident); - if (!session) + + auto it = m_Peers.find (ident); + if (it == m_Peers.end ()) { auto r = netdb.FindRouter (ident); - if (r) - { - if (m_SSUServer) - session = m_SSUServer->FindSession (r); - if (!session) - { - // existing session not found. create new - // try NTCP first if message size < 16K - auto address = r->GetNTCPAddress (!context.SupportsV6 ()); - if (address && !r->UsesIntroducer () && !r->IsUnreachable () && msg->GetLength () < NTCP_MAX_MESSAGE_SIZE) - { - auto s = std::make_shared (*m_NTCPServer, r); - session = s; - m_NTCPServer->Connect (address->host, address->port, s); - } - else - { - // then SSU - if (m_SSUServer) - session = m_SSUServer->GetSession (r); - if (!session) - { - LogPrint ("No NTCP and SSU addresses available"); - DeleteI2NPMessage (msg); - } - } - } - } - else + it = m_Peers.insert (std::pair(ident, { 0, r, nullptr})).first; + if (!ConnectToPeer (ident, it->second)) { - LogPrint ("Router not found. Requested"); - i2p::data::netdb.RequestDestination (ident); - auto resendTimer = new boost::asio::deadline_timer (m_Service); - resendTimer->expires_from_now (boost::posix_time::seconds(5)); // 5 seconds - resendTimer->async_wait (boost::bind (&Transports::HandleResendTimer, - this, boost::asio::placeholders::error, resendTimer, ident, msg)); + DeleteI2NPMessage (msg); + return; } } - if (session) - session->SendI2NPMessage (msg); + if (it->second.session) + it->second.session->SendI2NPMessage (msg); + else + it->second.delayedMessages.push_back (msg); } - void Transports::HandleResendTimer (const boost::system::error_code& ecode, - boost::asio::deadline_timer * timer, const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) + bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer) { - auto r = netdb.FindRouter (ident); - if (r) - { - LogPrint ("Router found. Sending message"); - PostMessage (ident, msg); + if (peer.router) // we have RI already + { + if (!peer.numAttempts) // NTCP + { + peer.numAttempts++; + auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ()); + if (address && !peer.router->UsesIntroducer () && !peer.router->IsUnreachable ()) + { + auto s = std::make_shared (*m_NTCPServer, peer.router); + m_NTCPServer->Connect (address->host, address->port, s); + return true; + } + } + else if (peer.numAttempts == 1)// SSU + { + peer.numAttempts++; + if (m_SSUServer) + { + if (m_SSUServer->GetSession (peer.router)) + return true; + } + } + LogPrint (eLogError, "No NTCP and SSU addresses available"); + m_Peers.erase (ident); + return false; } - else + else // otherwise request RI { - LogPrint ("Router not found. Failed to send message"); - DeleteI2NPMessage (msg); + LogPrint ("Router not found. Requested"); + i2p::data::netdb.RequestDestination (ident); + auto resendTimer = new boost::asio::deadline_timer (m_Service); + resendTimer->expires_from_now (boost::posix_time::seconds(5)); // 5 seconds + resendTimer->async_wait (boost::bind (&Transports::HandleResendTimer, + this, boost::asio::placeholders::error, resendTimer, ident)); + } + return true; + } + + void Transports::HandleResendTimer (const boost::system::error_code& ecode, + boost::asio::deadline_timer * timer, const i2p::data::IdentHash& ident) + { + auto it = m_Peers.find (ident); + if (it != m_Peers.end ()) + { + auto r = netdb.FindRouter (ident); + if (r) + { + LogPrint ("Router found. Trying to connect"); + it->second.router = r; + ConnectToPeer (ident, it->second); + } + else + { + LogPrint ("Router not found. Failed to send messages"); + m_Peers.erase (it); + } } delete timer; } @@ -304,9 +321,10 @@ namespace transport it->second.session = session; for (auto it1: it->second.delayedMessages) session->SendI2NPMessage (it1); + it->second.delayedMessages.clear (); } - /* else // incoming connection - m_Peers[ident] = { nullptr, session };*/ + else // incoming connection + m_Peers[ident] = { 0, nullptr, session }; }); } @@ -317,8 +335,12 @@ namespace transport auto ident = session->GetRemoteIdentity ().GetIdentHash (); auto it = m_Peers.find (ident); if (it != m_Peers.end ()) - m_Peers.erase (it); - // TODO:: check for delayed messages + { + if (it->second.delayedMessages.size () > 0) + ConnectToPeer (ident, it->second); + else + m_Peers.erase (it); + } }); } } diff --git a/Transports.h b/Transports.h index 6ab9330e..2ea375e2 100644 --- a/Transports.h +++ b/Transports.h @@ -53,6 +53,7 @@ namespace transport struct Peer { + int numAttempts; std::shared_ptr router; std::shared_ptr session; std::list delayedMessages; @@ -88,10 +89,11 @@ namespace transport void Run (); void HandleResendTimer (const boost::system::error_code& ecode, boost::asio::deadline_timer * timer, - const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); + const i2p::data::IdentHash& ident); void PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); void PostCloseSession (std::shared_ptr router); - + bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); + void DetectExternalIP (); private: From 98e930bd46e0ca490551e02c86af98da4b04d58f Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 13 Jan 2015 22:19:13 -0500 Subject: [PATCH 0052/6300] moved delayed queue to Peer --- NTCPSession.cpp | 29 +---------------------------- NTCPSession.h | 2 -- SSUSession.cpp | 19 +------------------ SSUSession.h | 2 -- 4 files changed, 2 insertions(+), 50 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index c0ba4451..30cdf8fd 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -32,9 +32,6 @@ namespace transport delete m_Establisher; if (m_NextMessage) i2p::DeleteI2NPMessage (m_NextMessage); - for (auto it :m_DelayedMessages) - i2p::DeleteI2NPMessage (it); - m_DelayedMessages.clear (); } void NTCPSession::CreateAESKey (uint8_t * pubKey, i2p::crypto::AESKey& key) @@ -78,18 +75,6 @@ namespace transport m_IsEstablished = false; m_Socket.close (); transports.PeerDisconnected (shared_from_this ()); - int numDelayed = 0; - for (auto it :m_DelayedMessages) - { - // try to send them again - if (m_RemoteRouter) - transports.SendMessage (m_RemoteRouter->GetIdentHash (), it); - numDelayed++; - } - m_DelayedMessages.clear (); - if (numDelayed > 0) - LogPrint (eLogWarning, "NTCP session ", numDelayed, " not sent"); - // TODO: notify tunnels m_Server.RemoveNTCPSession (shared_from_this ()); LogPrint ("NTCP session terminated"); } @@ -108,13 +93,6 @@ namespace transport SendI2NPMessage (CreateDatabaseStoreMsg ()); // we tell immediately who we are transports.PeerConnected (shared_from_this ()); - - if (!m_DelayedMessages.empty ()) - { - for (auto it :m_DelayedMessages) - SendI2NPMessage (it); - m_DelayedMessages.clear (); - } } void NTCPSession::ClientLogin () @@ -637,12 +615,7 @@ namespace transport void NTCPSession::PostI2NPMessage (I2NPMessage * msg) { if (msg) - { - if (m_IsEstablished) - Send (msg); - else - m_DelayedMessages.push_back (msg); - } + Send (msg); } void NTCPSession::ScheduleTermination () diff --git a/NTCPSession.h b/NTCPSession.h index 16d4e11d..43c7dc58 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -2,7 +2,6 @@ #define NTCP_SESSION_H__ #include -#include #include #include #include @@ -129,7 +128,6 @@ namespace transport int m_ReceiveBufferOffset; i2p::I2NPMessage * m_NextMessage; - std::list m_DelayedMessages; size_t m_NextMessageOffset; size_t m_NumSentBytes, m_NumReceivedBytes; diff --git a/SSUSession.cpp b/SSUSession.cpp index 4618624b..3bf95808 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -757,12 +757,6 @@ namespace transport { SendSesionDestroyed (); transports.PeerDisconnected (shared_from_this ()); - if (!m_DelayedMessages.empty ()) - { - for (auto it :m_DelayedMessages) - DeleteI2NPMessage (it); - m_DelayedMessages.clear (); - } } void SSUSession::Established () @@ -775,12 +769,6 @@ namespace transport } SendI2NPMessage (CreateDatabaseStoreMsg ()); transports.PeerConnected (shared_from_this ()); - if (!m_DelayedMessages.empty ()) - { - for (auto it :m_DelayedMessages) - m_Data.Send (it); - m_DelayedMessages.clear (); - } if (m_PeerTest && (m_RemoteRouter && m_RemoteRouter->IsPeerTesting ())) SendPeerTest (); ScheduleTermination (); @@ -837,12 +825,7 @@ namespace transport void SSUSession::PostI2NPMessage (I2NPMessage * msg) { if (msg) - { - if (m_State == eSessionStateEstablished) - m_Data.Send (msg); - else - m_DelayedMessages.push_back (msg); - } + m_Data.Send (msg); } void SSUSession::ProcessData (uint8_t * buf, size_t len) diff --git a/SSUSession.h b/SSUSession.h index e9848840..ce840636 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -3,7 +3,6 @@ #include #include -#include #include #include "aes.h" #include "hmac.h" @@ -132,7 +131,6 @@ namespace transport i2p::crypto::CBCDecryption m_SessionKeyDecryption; i2p::crypto::AESKey m_SessionKey; i2p::crypto::MACKey m_MacKey; - std::list m_DelayedMessages; SSUData m_Data; size_t m_NumSentBytes, m_NumReceivedBytes; uint32_t m_CreationTime; // seconds since epoch From 02b7cd71c50dbd6ca0bd35b259dbc78b453efcda Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 14 Jan 2015 13:21:41 -0500 Subject: [PATCH 0053/6300] handle i2p.router.netdb.activepeers --- I2PControl.cpp | 9 ++++++++- I2PControl.h | 4 +++- Transports.h | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index aa36817f..fae0283e 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -10,6 +10,7 @@ #include "Daemon.h" #include "Tunnel.h" #include "Timestamp.h" +#include "Transports.h" namespace i2p { @@ -28,6 +29,7 @@ namespace client // RouterInfo m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = &I2PControlService::NetDbKnownPeersHandler; + m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlService::NetDbActivePeersHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlService::TunnelsParticipatingHandler; // RouterManager @@ -226,9 +228,14 @@ namespace client results[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = boost::lexical_cast(i2p::data::netdb.GetNumRouters ()); } + void I2PControlService::NetDbActivePeersHandler (std::map& results) + { + results[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = boost::lexical_cast(i2p::data::netdb.GetNumRouters ()); + } + void I2PControlService::TunnelsParticipatingHandler (std::map& results) { - results[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = boost::lexical_cast(i2p::tunnel::tunnels.GetTransitTunnels ().size ()); + results[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = boost::lexical_cast(i2p::transport::transports.GetPeers ().size ()); } // RouterManager diff --git a/I2PControl.h b/I2PControl.h index 2f51dab0..142742f5 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -37,6 +37,7 @@ namespace client // RouterInfo requests const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; + const char I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers"; const char I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating"; // RouterManager requests @@ -80,7 +81,8 @@ namespace client // RouterInfo typedef void (I2PControlService::*RouterInfoRequestHandler)(std::map& results); - void NetDbKnownPeersHandler (std::map& results); + void NetDbKnownPeersHandler (std::map& results); + void NetDbActivePeersHandler (std::map& results); void TunnelsParticipatingHandler (std::map& results); // RouterManager diff --git a/Transports.h b/Transports.h index 2ea375e2..5aa225f2 100644 --- a/Transports.h +++ b/Transports.h @@ -114,6 +114,7 @@ namespace transport // for HTTP only const NTCPServer * GetNTCPServer () const { return m_NTCPServer; }; const SSUServer * GetSSUServer () const { return m_SSUServer; }; + const decltype(m_Peers)& GetPeers () const { return m_Peers; }; }; extern Transports transports; From fb3c5776017dc7368fd68a0564651054f45205c0 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 14 Jan 2015 13:24:25 -0500 Subject: [PATCH 0054/6300] handle i2p.router.netdb.activepeers --- I2PControl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index fae0283e..fdf81c30 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -230,12 +230,12 @@ namespace client void I2PControlService::NetDbActivePeersHandler (std::map& results) { - results[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = boost::lexical_cast(i2p::data::netdb.GetNumRouters ()); + results[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = boost::lexical_cast(i2p::transport::transports.GetPeers ().size ()); } void I2PControlService::TunnelsParticipatingHandler (std::map& results) { - results[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = boost::lexical_cast(i2p::transport::transports.GetPeers ().size ()); + results[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = boost::lexical_cast(i2p::tunnel::tunnels.GetTransitTunnels ().size ());; } // RouterManager From ad9d7931f577394a5e61c0758013cbfdf23dbc5c Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 14 Jan 2015 16:11:09 -0500 Subject: [PATCH 0055/6300] RequestComplete for RouterInfo --- NetDb.cpp | 57 ++++++++++++++++++++++++++++++++++++------------------- NetDb.h | 17 ++++++++++++----- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 85589700..dcb72ac0 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -47,6 +47,24 @@ namespace data m_ExcludedPeers.clear (); } + void RequestedDestination::Success (std::shared_ptr r) + { + if (m_RequestComplete) + { + m_RequestComplete (r); + m_RequestComplete = nullptr; + } + } + + void RequestedDestination::Fail () + { + if (m_RequestComplete) + { + m_RequestComplete (nullptr); + m_RequestComplete = nullptr; + } + } + #ifndef _WIN32 const char NetDb::m_NetDbPath[] = "/netDb"; #else @@ -191,7 +209,6 @@ namespace data void NetDb::AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len) { - DeleteRequestedDestination (ident); auto r = FindRouter (ident); if (r) { @@ -203,23 +220,31 @@ namespace data else { LogPrint ("New RouterInfo added"); - auto newRouter = std::make_shared (buf, len); + auto r = std::make_shared (buf, len); { std::unique_lock l(m_RouterInfosMutex); - m_RouterInfos[newRouter->GetIdentHash ()] = newRouter; + m_RouterInfos[r->GetIdentHash ()] = r; } - if (newRouter->IsFloodfill ()) + if (r->IsFloodfill ()) { std::unique_lock l(m_FloodfillsMutex); - m_Floodfills.push_back (newRouter); + m_Floodfills.push_back (r); } } + // take care about requested destination + auto it = m_RequestedDestinations.find (ident); + if (it != m_RequestedDestinations.end ()) + { + it->second->Success (r); + std::unique_lock l(m_RequestedDestinationsMutex); + delete it->second; + m_RequestedDestinations.erase (it); + } } void NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, i2p::tunnel::InboundTunnel * from) { - DeleteRequestedDestination (ident); if (!from) // unsolicited LS must be received directly { auto it = m_LeaseSets.find(ident); @@ -414,16 +439,19 @@ namespace data } } - void NetDb::RequestDestination (const IdentHash& destination) + void NetDb::RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete) { // request RouterInfo directly RequestedDestination * dest = CreateRequestedDestination (destination, false); + if (requestComplete) + dest->SetRequestComplete (requestComplete); auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ()); if (floodfill) transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); else { LogPrint (eLogError, "No floodfills found"); + dest->Fail (); DeleteRequestedDestination (dest); } } @@ -521,6 +549,7 @@ namespace data if (deleteDest) { // no more requests for the destinationation. delete it + it->second->Fail (); delete it->second; m_RequestedDestinations.erase (it); } @@ -528,6 +557,7 @@ namespace data else { // no more requests for detination possible. delete it + it->second->Fail (); delete it->second; m_RequestedDestinations.erase (it); } @@ -720,19 +750,6 @@ namespace data else return it->second; } - - bool NetDb::DeleteRequestedDestination (const IdentHash& dest) - { - auto it = m_RequestedDestinations.find (dest); - if (it != m_RequestedDestinations.end ()) - { - std::unique_lock l(m_RequestedDestinationsMutex); - delete it->second; - m_RequestedDestinations.erase (it); - return true; - } - return false; - } void NetDb::DeleteRequestedDestination (RequestedDestination * dest) { diff --git a/NetDb.h b/NetDb.h index 24d0a499..c74ea113 100644 --- a/NetDb.h +++ b/NetDb.h @@ -21,12 +21,15 @@ namespace i2p namespace data { class RequestedDestination - { + { public: + typedef std::function)> RequestComplete; + RequestedDestination (const IdentHash& destination, bool isExploratory = false): m_Destination (destination), m_IsExploratory (isExploratory), m_CreationTime (0) {}; - + ~RequestedDestination () { if (m_RequestComplete) m_RequestComplete (nullptr); }; + const IdentHash& GetDestination () const { return m_Destination; }; int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); }; const std::set& GetExcludedPeers () { return m_ExcludedPeers; }; @@ -36,13 +39,18 @@ namespace data uint64_t GetCreationTime () const { return m_CreationTime; }; I2NPMessage * CreateRequestMessage (std::shared_ptr, const i2p::tunnel::InboundTunnel * replyTunnel); I2NPMessage * CreateRequestMessage (const IdentHash& floodfill); - + + void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; }; + void Success (std::shared_ptr r); + void Fail (); + private: IdentHash m_Destination; bool m_IsExploratory; std::set m_ExcludedPeers; uint64_t m_CreationTime; + RequestComplete m_RequestComplete; }; class NetDb @@ -61,7 +69,7 @@ namespace data std::shared_ptr FindRouter (const IdentHash& ident) const; LeaseSet * FindLeaseSet (const IdentHash& destination) const; - void RequestDestination (const IdentHash& destination); + void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr); void HandleDatabaseStoreMsg (I2NPMessage * msg); void HandleDatabaseSearchReplyMsg (I2NPMessage * msg); @@ -92,7 +100,6 @@ namespace data void ManageRequests (); RequestedDestination * CreateRequestedDestination (const IdentHash& dest, bool isExploratory = false); - bool DeleteRequestedDestination (const IdentHash& dest); // returns true if found void DeleteRequestedDestination (RequestedDestination * dest); template From e898e6bf825f8d94320f2012b6c7807cc3424a73 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 14 Jan 2015 16:37:03 -0500 Subject: [PATCH 0056/6300] use RouterInfo request callback instead timeout --- Transports.cpp | 19 +++++++++---------- Transports.h | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 7414bc1d..99d9808f 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -242,22 +242,22 @@ namespace transport else // otherwise request RI { LogPrint ("Router not found. Requested"); - i2p::data::netdb.RequestDestination (ident); - auto resendTimer = new boost::asio::deadline_timer (m_Service); - resendTimer->expires_from_now (boost::posix_time::seconds(5)); // 5 seconds - resendTimer->async_wait (boost::bind (&Transports::HandleResendTimer, - this, boost::asio::placeholders::error, resendTimer, ident)); + i2p::data::netdb.RequestDestination (ident, std::bind ( + &Transports::RequestComplete, this, std::placeholders::_1, ident)); } return true; } - - void Transports::HandleResendTimer (const boost::system::error_code& ecode, - boost::asio::deadline_timer * timer, const i2p::data::IdentHash& ident) + + void Transports::RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident) + { + m_Service.post (std::bind (&Transports::HandleRequestComplete, this, r, ident)); + } + + void Transports::HandleRequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident) { auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { - auto r = netdb.FindRouter (ident); if (r) { LogPrint ("Router found. Trying to connect"); @@ -270,7 +270,6 @@ namespace transport m_Peers.erase (it); } } - delete timer; } void Transports::CloseSession (std::shared_ptr router) diff --git a/Transports.h b/Transports.h index 5aa225f2..6bed164b 100644 --- a/Transports.h +++ b/Transports.h @@ -88,8 +88,8 @@ namespace transport private: void Run (); - void HandleResendTimer (const boost::system::error_code& ecode, boost::asio::deadline_timer * timer, - const i2p::data::IdentHash& ident); + void RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); + void HandleRequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); void PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); void PostCloseSession (std::shared_ptr router); bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); From 72a4f8a9a1333bd2977dffb25d765483a292ebb7 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 14 Jan 2015 20:27:19 -0500 Subject: [PATCH 0057/6300] fixed crash on shutdown --- NetDb.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index dcb72ac0..3c593685 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -79,10 +79,6 @@ namespace data NetDb::~NetDb () { Stop (); - for (auto l:m_LeaseSets) - delete l.second; - for (auto r:m_RequestedDestinations) - delete r.second; } void NetDb::Start () @@ -110,6 +106,7 @@ namespace data Load (m_NetDbPath); } } + m_IsRunning = true; m_Thread = new std::thread (std::bind (&NetDb::Run, this)); } @@ -122,13 +119,18 @@ namespace data m_Thread->join (); delete m_Thread; m_Thread = 0; - } + } + for (auto l: m_LeaseSets) + delete l.second; + m_LeaseSets.clear(); + for (auto r: m_RequestedDestinations) + delete r.second; + m_RequestedDestinations.clear (); } void NetDb::Run () { uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0; - m_IsRunning = true; while (m_IsRunning) { try @@ -220,7 +222,7 @@ namespace data else { LogPrint ("New RouterInfo added"); - auto r = std::make_shared (buf, len); + r = std::make_shared (buf, len); { std::unique_lock l(m_RouterInfosMutex); m_RouterInfos[r->GetIdentHash ()] = r; From 25cc118890d9c40910745c81e532407730032a38 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 15 Jan 2015 16:42:28 -0500 Subject: [PATCH 0058/6300] I2PControl method --- I2PControl.cpp | 22 +++++++++++++++++++++- I2PControl.h | 16 +++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index fdf81c30..5e58bb63 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -17,12 +17,13 @@ namespace i2p namespace client { I2PControlService::I2PControlService (int port): - m_IsRunning (false), m_Thread (nullptr), + m_Password (I2P_CONTROL_DEFAULT_PASSWORD), m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)), m_ShutdownTimer (m_Service) { m_MethodHandlers[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlService::AuthenticateHandler; m_MethodHandlers[I2P_CONTROL_METHOD_ECHO] = &I2PControlService::EchoHandler; + m_MethodHandlers[I2P_CONTROL_METHOD_I2PCONTROL] = &I2PControlService::I2PControlHandler; m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlService::RouterInfoHandler; m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlService::RouterManagerHandler; m_MethodHandlers[I2P_CONTROL_METHOD_NETWORK_SETTING] = &I2PControlService::NetworkSettingHandler; @@ -195,6 +196,8 @@ namespace client const std::string& api = params.at (I2P_CONTROL_PARAM_API); const std::string& password = params.at (I2P_CONTROL_PARAM_PASSWORD); LogPrint (eLogDebug, "I2PControl Authenticate API=", api, " Password=", password); + if (password != m_Password) + LogPrint (eLogError, "I2PControl Authenticate Invalid password ", password, " expected ", m_Password); results[I2P_CONTROL_PARAM_API] = api; results[I2P_CONTROL_PARAM_TOKEN] = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); } @@ -206,6 +209,23 @@ namespace client results[I2P_CONTROL_PARAM_RESULT] = echo; } + +// I2PControl + + void I2PControlService::I2PControlHandler (const std::map& params, std::map& results) + { + LogPrint (eLogDebug, "I2PControl I2PControl"); + for (auto& it: params) + { + LogPrint (eLogDebug, it.first); + auto it1 = m_I2PControlHandlers.find (it.first); + if (it1 != m_I2PControlHandlers.end ()) + (this->*(it1->second))(it.second); + else + LogPrint (eLogError, "I2PControl NetworkSetting unknown request ", it.first); + } + } + // RouterInfo void I2PControlService::RouterInfoHandler (const std::map& params, std::map& results) diff --git a/I2PControl.h b/I2PControl.h index 142742f5..59aff07b 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -16,6 +16,8 @@ namespace client const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024; typedef std::array I2PControlBuffer; + const char I2P_CONTROL_DEFAULT_PASSWORD[] = "itoopie"; + const char I2P_CONTROL_PROPERTY_ID[] = "id"; const char I2P_CONTROL_PROPERTY_METHOD[] = "method"; const char I2P_CONTROL_PROPERTY_PARAMS[] = "params"; @@ -23,7 +25,8 @@ namespace client // methods const char I2P_CONTROL_METHOD_AUTHENTICATE[] = "Authenticate"; - const char I2P_CONTROL_METHOD_ECHO[] = "Echo"; + const char I2P_CONTROL_METHOD_ECHO[] = "Echo"; + const char I2P_CONTROL_METHOD_I2PCONTROL[] = "I2PControl"; const char I2P_CONTROL_METHOD_ROUTER_INFO[] = "RouterInfo"; const char I2P_CONTROL_METHOD_ROUTER_MANAGER[] = "RouterManager"; const char I2P_CONTROL_METHOD_NETWORK_SETTING[] = "NetworkSetting"; @@ -35,6 +38,11 @@ namespace client const char I2P_CONTROL_PARAM_ECHO[] = "Echo"; const char I2P_CONTROL_PARAM_RESULT[] = "Result"; + // I2PControl + const char I2P_CONTROL_I2PCONTROL_ADDRESS[] = "i2pcontrol.address"; + const char I2P_CONTROL_I2PCONTROL_PASSWORD[] = "i2pcontrol.password"; + const char I2P_CONTROL_I2PCONTROL_PORT[] = "i2pcontrol.port"; + // RouterInfo requests const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; const char I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers"; @@ -75,10 +83,14 @@ namespace client void AuthenticateHandler (const std::map& params, std::map& results); void EchoHandler (const std::map& params, std::map& results); + void I2PControlHandler (const std::map& params, std::map& results); void RouterInfoHandler (const std::map& params, std::map& results); void RouterManagerHandler (const std::map& params, std::map& results); void NetworkSettingHandler (const std::map& params, std::map& results); + // I2PControl + typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value); + // RouterInfo typedef void (I2PControlService::*RouterInfoRequestHandler)(std::map& results); void NetDbKnownPeersHandler (std::map& results); @@ -95,6 +107,7 @@ namespace client private: + std::string m_Password; bool m_IsRunning; std::thread * m_Thread; @@ -103,6 +116,7 @@ namespace client boost::asio::deadline_timer m_ShutdownTimer; std::map m_MethodHandlers; + std::map m_I2PControlHandlers; std::map m_RouterInfoHandlers; std::map m_RouterManagerHandlers; std::map m_NetworkSettingHandlers; From 905aa7cdc49fd4fc706418e4ce82585c5d082783 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Fri, 16 Jan 2015 02:44:18 +0100 Subject: [PATCH 0059/6300] Adding download link --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b85e2ec6..eb5a54eb 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,17 @@ VS2013 (known to work with 12.0.21005.1 or newer), Boost 1.46 or newer, crypto++ 5.62. See Win32/README-Build.txt for instructions on how to build i2pd and its dependencies. +Downloads +------------ + +Official binary releases could be found at: +http://download.i2p.io/purplei2p/i2pd/releases/ + + Build Statuses --------------- -- Linux x64 - [![Build Status](https://jenkins.nordcloud.no/buildStatus/icon?job=i2pd-linux)](https://jenkins.nordcloud.no/job/i2pd-linux/) +- Linux x64 - [![Build Status](https://jenkins.greyhat.no/buildStatus/icon?job=i2pd-linux)](https://jenkins.nordcloud.no/job/i2pd-linux/) - Linux ARM - To be added - Mac OS X - Got it working, but not well tested. (Only works with clang, not GCC.) - Microsoft VC13 - To be added From 0f227e8317cb593f89727078d1e7809c104e8ba2 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Fri, 16 Jan 2015 20:37:52 +0100 Subject: [PATCH 0060/6300] For some reason, the daemon src don't include I2PControl.cpp for OSX builds. --- filelist.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filelist.mk b/filelist.mk index 0038da97..96115bd0 100644 --- a/filelist.mk +++ b/filelist.mk @@ -11,7 +11,7 @@ ifeq ($(UNAME),Darwin) # Else will get linker error about unknown symbols. - torkel COMMON_SRC += \ BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp I2PService.cpp SAM.cpp SOCKS.cpp \ - UPnP.cpp HTTPServer.cpp HTTPProxy.cpp i2p.cpp DaemonLinux.cpp + UPnP.cpp HTTPServer.cpp HTTPProxy.cpp i2p.cpp DaemonLinux.cpp I2PControl.cpp endif From f3fbf6bd89c6ceb65448b970c65fe28668948bbd Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 16 Jan 2015 15:25:44 -0500 Subject: [PATCH 0061/6300] address resolver for NTCP address --- RouterInfo.h | 4 ++++ Transports.cpp | 64 +++++++++++++++++++++++++++++++++++++++++++------- Transports.h | 4 ++++ 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/RouterInfo.h b/RouterInfo.h index d6492455..d73ede73 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -69,6 +69,7 @@ namespace data { TransportStyle transportStyle; boost::asio::ip::address host; + char * addressString; int port, mtu; uint64_t date; uint8_t cost; @@ -76,6 +77,9 @@ namespace data Tag<32> key; // intro key for SSU std::vector introducers; + Address (): addressString (nullptr) {}; + ~Address () { if (addressString) delete[] addressString; }; + bool IsCompatible (const boost::asio::ip::address& other) const { return (host.is_v4 () && other.is_v4 ()) || diff --git a/Transports.cpp b/Transports.cpp index 99d9808f..9f5353b1 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -96,7 +96,7 @@ namespace transport Transports transports; Transports::Transports (): - m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), + m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_NTCPServer (nullptr), m_SSUServer (nullptr), m_DHKeysPairSupplier (5) // 5 pre-generated keys { @@ -218,12 +218,27 @@ namespace transport if (!peer.numAttempts) // NTCP { peer.numAttempts++; - auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ()); - if (address && !peer.router->UsesIntroducer () && !peer.router->IsUnreachable ()) - { - auto s = std::make_shared (*m_NTCPServer, peer.router); - m_NTCPServer->Connect (address->host, address->port, s); - return true; + auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ()); + if (address) + { + if (!address->host.is_unspecified ()) // we have address now + { + if (!peer.router->UsesIntroducer () && !peer.router->IsUnreachable ()) + { + auto s = std::make_shared (*m_NTCPServer, peer.router); + m_NTCPServer->Connect (address->host, address->port, s); + return true; + } + } + else // we don't have address + { + if (address->addressString) // trying to resolve + { + LogPrint (eLogInfo, "Resolving ", address->addressString); + NTCPResolve (address->addressString, ident); + return true; + } + } } } else if (peer.numAttempts == 1)// SSU @@ -271,7 +286,40 @@ namespace transport } } } - + + void Transports::NTCPResolve (const char * addr, const i2p::data::IdentHash& ident) + { + auto resolver = std::make_shared(m_Service); + resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr), + std::bind (&Transports::HandleNTCPResolve, this, + std::placeholders::_1, std::placeholders::_2, ident, resolver)); + } + + void Transports::HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + const i2p::data::IdentHash& ident, std::shared_ptr resolver) + { + auto it1 = m_Peers.find (ident); + if (it1 != m_Peers.end () && it1->second.router) + { + auto& peer = it1->second; + if (!ecode) + { + auto address = (*it).endpoint ().address (); + LogPrint (eLogInfo, (*it).host_name (), " has been resolved to ", address); + auto addr = peer.router->GetNTCPAddress (); + if (addr) + { + auto s = std::make_shared (*m_NTCPServer, peer.router); + m_NTCPServer->Connect (address, addr->port, s); + return; + } + } + } + + LogPrint (eLogError, "Unable to resolve NTCP address: ", ecode.message ()); + m_Peers.erase (it1); + } + void Transports::CloseSession (std::shared_ptr router) { if (!router) return; diff --git a/Transports.h b/Transports.h index 6bed164b..9c751dc3 100644 --- a/Transports.h +++ b/Transports.h @@ -94,6 +94,10 @@ namespace transport void PostCloseSession (std::shared_ptr router); bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); + void NTCPResolve (const char * addr, const i2p::data::IdentHash& ident); + void HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + const i2p::data::IdentHash& ident, std::shared_ptr resolver); + void DetectExternalIP (); private: From 514947ba49288979442211900eacd2161ce70cab Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 16 Jan 2015 16:19:17 -0500 Subject: [PATCH 0062/6300] skip HTTP header --- I2PControl.cpp | 16 ++++++++++++++-- I2PControl.h | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 5e58bb63..30efc9bd 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -125,8 +125,20 @@ namespace client { try { + bool isHtml = !memcmp (buf->data (), "POST", 4); std::stringstream ss; ss.write (buf->data (), bytes_transferred); + if (isHtml) + { + std::string header; + while (!ss.eof () && header != "\r") + std::getline(ss, header); + if (ss.eof ()) + { + LogPrint (eLogError, "Malformed I2PControl request. HTTP header expected"); + return; // TODO: + } + } boost::property_tree::ptree pt; boost::property_tree::read_json (ss, pt); std::string method = pt.get(I2P_CONTROL_PROPERTY_METHOD); @@ -142,7 +154,7 @@ namespace client } std::map results; (this->*(it->second))(params, results); - SendResponse (socket, buf, pt.get(I2P_CONTROL_PROPERTY_ID), results); + SendResponse (socket, buf, pt.get(I2P_CONTROL_PROPERTY_ID), results, isHtml); } else LogPrint (eLogWarning, "Unknown I2PControl method ", method); @@ -160,7 +172,7 @@ namespace client void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, const std::string& id, - const std::map& results) + const std::map& results, bool isHtml) { boost::property_tree::ptree ptr; for (auto& result: results) diff --git a/I2PControl.h b/I2PControl.h index 59aff07b..007ac00e 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -72,7 +72,7 @@ namespace client std::shared_ptr socket, std::shared_ptr buf); void SendResponse (std::shared_ptr socket, std::shared_ptr buf, const std::string& id, - const std::map& results); + const std::map& results, bool isHtml); void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); From 07c6f2a20bc6428e3b35f1fa6e7fd6fb23169595 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 16 Jan 2015 16:51:52 -0500 Subject: [PATCH 0063/6300] make HTTP header if necessary --- I2PControl.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 30efc9bd..e8f98cbe 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include "Log.h" @@ -185,8 +187,23 @@ namespace client std::ostringstream ss; boost::property_tree::write_json (ss, pt, false); - size_t len = ss.str ().length (); - memcpy (buf->data (), ss.str ().c_str (), len); + size_t len = ss.str ().length (), offset = 0; + if (isHtml) + { + std::ostringstream header; + header << "HTTP/1.1 200 OK\r\n"; + header << "Connection: close\r\n"; + header << "Content-Length: " << boost::lexical_cast(len) << "\r\n"; + header << "Content-Type: application/json\r\n"; + header << "Date: "; + auto facet = new boost::local_time::local_time_facet ("%a, %d %b %Y %H:%M:%S GMT"); + header.imbue(std::locale (header.getloc(), facet)); + header << boost::posix_time::second_clock::local_time() << "\r\n"; + header << "\r\n"; + offset = header.str ().size (); + memcpy (buf->data (), header.str ().c_str (), offset); + } + memcpy (buf->data () + offset, ss.str ().c_str (), len); boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), len), boost::asio::transfer_all (), std::bind(&I2PControlService::HandleResponseSent, this, From bf7b53a2a671a97afb61244ae37d1afa88e9bf16 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 16 Jan 2015 23:01:40 -0500 Subject: [PATCH 0064/6300] resolve address for NTCP --- RouterInfo.cpp | 14 +++++++++++--- RouterInfo.h | 5 +---- Transports.cpp | 4 ++-- Transports.h | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 14d338e9..91f793fb 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -150,9 +150,17 @@ namespace data address.host = boost::asio::ip::address::from_string (value, ecode); if (ecode) { - // TODO: we should try to resolve address here - LogPrint (eLogWarning, "Unexpected address ", value); - isValidAddress = false; + if (address.transportStyle == eTransportNTCP) + { + m_SupportedTransports |= eNTCPV4; // TODO: + address.addressString = value; + } + else + { + // TODO: resolve address for SSU + LogPrint (eLogWarning, "Unexpected SSU address ", value); + isValidAddress = false; + } } else { diff --git a/RouterInfo.h b/RouterInfo.h index d73ede73..bf1a9dc6 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -69,7 +69,7 @@ namespace data { TransportStyle transportStyle; boost::asio::ip::address host; - char * addressString; + std::string addressString; int port, mtu; uint64_t date; uint8_t cost; @@ -77,9 +77,6 @@ namespace data Tag<32> key; // intro key for SSU std::vector introducers; - Address (): addressString (nullptr) {}; - ~Address () { if (addressString) delete[] addressString; }; - bool IsCompatible (const boost::asio::ip::address& other) const { return (host.is_v4 () && other.is_v4 ()) || diff --git a/Transports.cpp b/Transports.cpp index 9f5353b1..63eca69d 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -232,7 +232,7 @@ namespace transport } else // we don't have address { - if (address->addressString) // trying to resolve + if (address->addressString.length () > 0) // trying to resolve { LogPrint (eLogInfo, "Resolving ", address->addressString); NTCPResolve (address->addressString, ident); @@ -287,7 +287,7 @@ namespace transport } } - void Transports::NTCPResolve (const char * addr, const i2p::data::IdentHash& ident) + void Transports::NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident) { auto resolver = std::make_shared(m_Service); resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr), diff --git a/Transports.h b/Transports.h index 9c751dc3..ffc561ff 100644 --- a/Transports.h +++ b/Transports.h @@ -94,7 +94,7 @@ namespace transport void PostCloseSession (std::shared_ptr router); bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); - void NTCPResolve (const char * addr, const i2p::data::IdentHash& ident); + void NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident); void HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, const i2p::data::IdentHash& ident, std::shared_ptr resolver); From 284fb5458e0250916781ffa29821f97c87b5c53f Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 17 Jan 2015 08:22:43 -0500 Subject: [PATCH 0065/6300] fixed resolve bug --- Transports.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 63eca69d..4cb20eef 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -1,5 +1,4 @@ #include -#include #include "Log.h" #include "CryptoConst.h" #include "RouterContext.h" @@ -182,7 +181,7 @@ namespace transport void Transports::SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) { - m_Service.post (boost::bind (&Transports::PostMessage, this, ident, msg)); + m_Service.post (std::bind (&Transports::PostMessage, this, ident, msg)); } void Transports::PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) @@ -290,7 +289,7 @@ namespace transport void Transports::NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident) { auto resolver = std::make_shared(m_Service); - resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr), + resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""), std::bind (&Transports::HandleNTCPResolve, this, std::placeholders::_1, std::placeholders::_2, ident, resolver)); } @@ -323,7 +322,7 @@ namespace transport void Transports::CloseSession (std::shared_ptr router) { if (!router) return; - m_Service.post (boost::bind (&Transports::PostCloseSession, this, router)); + m_Service.post (std::bind (&Transports::PostCloseSession, this, router)); } void Transports::PostCloseSession (std::shared_ptr router) From 3c9e6054b562967cb51a3748c2fc50499bf2bbd3 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 17 Jan 2015 09:42:44 -0500 Subject: [PATCH 0066/6300] use shared local destination for proxies --- HTTPProxy.cpp | 5 +++++ HTTPProxy.h | 2 +- SOCKS.cpp | 5 +++++ SOCKS.h | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 5659a80b..cadbe768 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -231,6 +231,11 @@ namespace proxy } } + HTTPProxyServer::HTTPProxyServer(int port): + TCPIPAcceptor(port, i2p::client::context.GetSharedLocalDestination ()) + { + } + std::shared_ptr HTTPProxyServer::CreateHandler(boost::asio::ip::tcp::socket * socket) { return std::make_shared (this, socket); diff --git a/HTTPProxy.h b/HTTPProxy.h index a7b81553..5c226926 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -19,7 +19,7 @@ namespace proxy const char* GetName() { return "HTTP Proxy"; } public: - HTTPProxyServer(int port) : TCPIPAcceptor(port, i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) {} + HTTPProxyServer(int port); ~HTTPProxyServer() {} }; diff --git a/SOCKS.cpp b/SOCKS.cpp index ac30b6bd..9db8ee69 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -514,6 +514,11 @@ namespace proxy } } + SOCKSServer::SOCKSServer(int port) : + TCPIPAcceptor (port, i2p::client::context.GetSharedLocalDestination ()) + { + } + std::shared_ptr SOCKSServer::CreateHandler(boost::asio::ip::tcp::socket * socket) { return std::make_shared (this, socket); diff --git a/SOCKS.h b/SOCKS.h index 3d107f1c..c946e2a2 100644 --- a/SOCKS.h +++ b/SOCKS.h @@ -19,7 +19,7 @@ namespace proxy const char* GetName() { return "SOCKS"; } public: - SOCKSServer(int port) : TCPIPAcceptor(port) {} + SOCKSServer(int port); ~SOCKSServer() {} }; From f36229bd95f57f746884eb80383901af9a5287b2 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 18 Jan 2015 18:46:22 -0500 Subject: [PATCH 0067/6300] make sure DatabaseStore message is first --- NTCPSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 30cdf8fd..932d16bf 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -90,7 +90,7 @@ namespace transport m_DHKeysPair = nullptr; SendTimeSyncMessage (); - SendI2NPMessage (CreateDatabaseStoreMsg ()); // we tell immediately who we are + PostI2NPMessage (CreateDatabaseStoreMsg ()); // we tell immediately who we are transports.PeerConnected (shared_from_this ()); } From f5b937667afb9d1bf1eef9d7cc3ae70283321fd3 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 19 Jan 2015 12:31:14 -0500 Subject: [PATCH 0068/6300] don't try to re-request expired LeaseSet --- Destination.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index bee54062..a27ddba1 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -129,10 +129,7 @@ namespace client if (it->second->HasNonExpiredLeases ()) return it->second; else - { - LogPrint ("All leases of remote LeaseSet expired. Request it"); - RequestDestination (ident); - } + LogPrint ("All leases of remote LeaseSet expired"); } else { From 027c43c99c8955759fa49850b29d43cab286d8c3 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 19 Jan 2015 13:57:37 -0500 Subject: [PATCH 0069/6300] Reseed through I2PControl --- I2PControl.cpp | 10 +++++++++- I2PControl.h | 4 +++- NetDb.cpp | 44 +++++++++++++++++++++++++++----------------- NetDb.h | 5 +++++ 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index e8f98cbe..913f823c 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -38,6 +38,7 @@ namespace client // RouterManager m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = &I2PControlService::ShutdownHandler; m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = &I2PControlService::ShutdownGracefulHandler; + m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_RESEED] = &I2PControlService::ReseedHandler; } I2PControlService::~I2PControlService () @@ -330,7 +331,14 @@ namespace client }); } - // network setting + void I2PControlService::ReseedHandler (std::map& results) + { + LogPrint (eLogInfo, "Reseed requested"); + results[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = ""; + i2p::data::netdb.Reseed (); + } + +// network setting void I2PControlService::NetworkSettingHandler (const std::map& params, std::map& results) { LogPrint (eLogDebug, "I2PControl NetworkSetting"); diff --git a/I2PControl.h b/I2PControl.h index 007ac00e..e032c6fc 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -51,7 +51,8 @@ namespace client // RouterManager requests const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN[] = "Shutdown"; const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful"; - + const char I2P_CONTROL_ROUTER_MANAGER_RESEED[] = "Reseed"; + class I2PControlService { public: @@ -101,6 +102,7 @@ namespace client typedef void (I2PControlService::*RouterManagerRequestHandler)(std::map& results); void ShutdownHandler (std::map& results); void ShutdownGracefulHandler (std::map& results); + void ReseedHandler (std::map& results); // NetworkSetting typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::map& results); diff --git a/NetDb.cpp b/NetDb.cpp index 3c593685..2c2ae884 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -13,7 +13,6 @@ #include "RouterContext.h" #include "Garlic.h" #include "NetDb.h" -#include "Reseed.h" #include "util.h" using namespace i2p::transport; @@ -72,13 +71,14 @@ namespace data #endif NetDb netdb; - NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr) + NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr) { } NetDb::~NetDb () { Stop (); + delete m_Reseeder; } void NetDb::Start () @@ -86,24 +86,20 @@ namespace data Load (m_NetDbPath); if (m_RouterInfos.size () < 50) // reseed if # of router less than 50 { - Reseeder reseeder; - reseeder.LoadCertificates (); // we need certificates for SU3 verification - // try SU3 first - int reseedRetries = 0; - while (m_RouterInfos.size () < 50 && reseedRetries < 10) - { - reseeder.ReseedNowSU3(); - reseedRetries++; - } + Reseed (); - // if still not enough download .dat files - reseedRetries = 0; - while (m_RouterInfos.size () < 50 && reseedRetries < 10) + // deprecated + if (m_Reseeder) { - reseeder.reseedNow(); - reseedRetries++; - Load (m_NetDbPath); + // if still not enough download .dat files + int reseedRetries = 0; + while (m_RouterInfos.size () < 50 && reseedRetries < 10) + { + m_Reseeder->reseedNow(); + reseedRetries++; + Load (m_NetDbPath); + } } } m_IsRunning = true; @@ -314,6 +310,20 @@ namespace data return true; } + void NetDb::Reseed () + { + if (!m_Reseeder) + { + m_Reseeder = new Reseeder (); + m_Reseeder->LoadCertificates (); // we need certificates for SU3 verification + } + int reseedRetries = 0; + while (reseedRetries < 10 && !m_Reseeder->ReseedNowSU3 ()) + reseedRetries++; + if (reseedRetries >= 10) + LogPrint (eLogWarning, "Failed to reseed after 10 attempts"); + } + void NetDb::Load (const char * directory) { boost::filesystem::path p (i2p::util::filesystem::GetDataDir()); diff --git a/NetDb.h b/NetDb.h index c74ea113..afcae70d 100644 --- a/NetDb.h +++ b/NetDb.h @@ -15,6 +15,7 @@ #include "LeaseSet.h" #include "Tunnel.h" #include "TunnelPool.h" +#include "Reseed.h" namespace i2p { @@ -83,6 +84,8 @@ namespace data void PostI2NPMsg (I2NPMessage * msg); + void Reseed (); + // for web interface int GetNumRouters () const { return m_RouterInfos.size (); }; int GetNumFloodfills () const { return m_Floodfills.size (); }; @@ -119,6 +122,8 @@ namespace data std::thread * m_Thread; i2p::util::Queue m_Queue; // of I2NPDatabaseStoreMsg + Reseeder * m_Reseeder; + static const char m_NetDbPath[]; }; From e09da5cb54e0e71ceae441e1635d646b7077da68 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 19 Jan 2015 14:30:30 -0500 Subject: [PATCH 0070/6300] correct CRC32 verification at big endian CPU --- Reseed.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 9c72dc84..fd57b2e9 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -266,9 +266,9 @@ namespace data s.read ((char *)&compressionMethod, 2); compressionMethod = le16toh (compressionMethod); s.seekg (4, std::ios::cur); // skip fields we don't care about - uint32_t crc32, compressedSize, uncompressedSize; - s.read ((char *)&crc32, 4); - crc32 = le32toh (crc32); + uint32_t compressedSize, uncompressedSize; + uint8_t crc32[4]; + s.read ((char *)crc32, 4); s.read ((char *)&compressedSize, 4); compressedSize = le32toh (compressedSize); s.read ((char *)&uncompressedSize, 4); @@ -292,8 +292,7 @@ namespace data return numFiles; } - s.read ((char *)&crc32, 4); - crc32 = le32toh (crc32); + s.read ((char *)crc32, 4); s.read ((char *)&compressedSize, 4); compressedSize = le32toh (compressedSize) + 4; // ??? we must consider signature as part of compressed data s.read ((char *)&uncompressedSize, 4); @@ -321,7 +320,7 @@ namespace data { uint8_t * uncompressed = new uint8_t[uncompressedSize]; decompressor.Get (uncompressed, uncompressedSize); - if (CryptoPP::CRC32().VerifyDigest ((uint8_t *)&crc32, uncompressed, uncompressedSize)) + if (CryptoPP::CRC32().VerifyDigest (crc32, uncompressed, uncompressedSize)) { i2p::data::netdb.AddRouterInfo (uncompressed, uncompressedSize); numFiles++; From 676892cb00b4a0313fe6f969b027da65933370da Mon Sep 17 00:00:00 2001 From: Chris Barry Date: Mon, 19 Jan 2015 21:06:30 -0500 Subject: [PATCH 0071/6300] Update copyright Changing Debian information to match information in root directory. --- debian/copyright | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/debian/copyright b/debian/copyright index ec52d972..4dbbc6a4 100644 --- a/debian/copyright +++ b/debian/copyright @@ -4,12 +4,37 @@ Source: https://github.com/PrivacySolutions Files: * Copyright: 2013-2014 PrivacySolutions -License: GPL-2.0+ +License: BSD-3-clause +Copyright (c) 2013-2015, The PrivacySolutions Project + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of +conditions and the following disclaimer in the documentation and/or other materials +provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Files: debian/* Copyright: 2014 hagen -License: GPL-2.0+ - License: GPL-2.0+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From ebb5c53c3a1a7ca100461eedd49f98531b4c3929 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 19 Jan 2015 22:28:13 -0500 Subject: [PATCH 0072/6300] use shared_ptr for TunnelPool --- Destination.h | 4 ++-- Tunnel.cpp | 21 +++++++-------------- Tunnel.h | 19 ++++++++++--------- TunnelPool.cpp | 8 ++++---- TunnelPool.h | 3 ++- 5 files changed, 25 insertions(+), 30 deletions(-) diff --git a/Destination.h b/Destination.h index 14580e6b..ffb96d2a 100644 --- a/Destination.h +++ b/Destination.h @@ -61,7 +61,7 @@ namespace client virtual void Stop (); bool IsRunning () const { return m_IsRunning; }; boost::asio::io_service& GetService () { return m_Service; }; - i2p::tunnel::TunnelPool * GetTunnelPool () { return m_Pool; }; + std::shared_ptr GetTunnelPool () { return m_Pool; }; bool IsReady () const { return m_LeaseSet && m_LeaseSet->HasNonExpiredLeases (); }; const i2p::data::LeaseSet * FindLeaseSet (const i2p::data::IdentHash& ident); bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); @@ -121,7 +121,7 @@ namespace client std::map m_RemoteLeaseSets; std::map m_LeaseSetRequests; - i2p::tunnel::TunnelPool * m_Pool; + std::shared_ptr m_Pool; i2p::data::LeaseSet * m_LeaseSet; bool m_IsPublic; uint32_t m_PublishReplyToken; diff --git a/Tunnel.cpp b/Tunnel.cpp index a569a329..b6e93eb5 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -191,7 +191,7 @@ namespace tunnel Tunnels tunnels; - Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), m_ExploratoryPool (nullptr) + Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr) { } @@ -213,9 +213,6 @@ namespace tunnel delete it.second; m_PendingTunnels.clear ();*/ - for (auto& it: m_Pools) - delete it; - m_Pools.clear (); } InboundTunnel * Tunnels::GetInboundTunnel (uint32_t tunnelID) @@ -280,15 +277,15 @@ namespace tunnel return tunnel; } - TunnelPool * Tunnels::CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops) + std::shared_ptr Tunnels::CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops) { - auto pool = new TunnelPool (localDestination, numInboundHops, numOutboundHops); + auto pool = std::make_shared (localDestination, numInboundHops, numOutboundHops); std::unique_lock l(m_PoolsMutex); m_Pools.push_back (pool); return pool; } - void Tunnels::DeleteTunnelPool (TunnelPool * pool) + void Tunnels::DeleteTunnelPool (std::shared_ptr pool) { if (pool) { @@ -297,14 +294,10 @@ namespace tunnel std::unique_lock l(m_PoolsMutex); m_Pools.remove (pool); } - for (auto it: m_PendingTunnels) - if (it.second->GetTunnelPool () == pool) - it.second->SetTunnelPool (nullptr); - delete pool; } } - void Tunnels::StopTunnelPool (TunnelPool * pool) + void Tunnels::StopTunnelPool (std::shared_ptr pool) { if (pool) { @@ -547,8 +540,8 @@ namespace tunnel std::unique_lock l(m_PoolsMutex); for (auto it: m_Pools) { - TunnelPool * pool = it; - if (pool->IsActive ()) + auto pool = it; + if (pool && pool->IsActive ()) { pool->CreateTunnels (); pool->TestTunnels (); diff --git a/Tunnel.h b/Tunnel.h index 99f44976..527e8827 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "Queue.h" #include "TunnelConfig.h" #include "TunnelPool.h" @@ -54,8 +55,8 @@ namespace tunnel bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; bool IsFailed () const { return m_State == eTunnelStateFailed; }; - TunnelPool * GetTunnelPool () const { return m_Pool; }; - void SetTunnelPool (TunnelPool * pool) { m_Pool = pool; }; + std::shared_ptr GetTunnelPool () const { return m_Pool; }; + void SetTunnelPool (std::shared_ptr pool) { m_Pool = pool; }; bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); @@ -67,7 +68,7 @@ namespace tunnel private: TunnelConfig * m_Config; - TunnelPool * m_Pool; // pool, tunnel belongs to, or null + std::shared_ptr m_Pool; // pool, tunnel belongs to, or null TunnelState m_State; }; @@ -121,7 +122,7 @@ namespace tunnel Tunnel * GetPendingTunnel (uint32_t replyMsgID); InboundTunnel * GetNextInboundTunnel (); OutboundTunnel * GetNextOutboundTunnel (); - TunnelPool * GetExploratoryPool () const { return m_ExploratoryPool; }; + std::shared_ptr GetExploratoryPool () const { return m_ExploratoryPool; }; TransitTunnel * GetTransitTunnel (uint32_t tunnelID); int GetTransitTunnelsExpirationTimeout (); void AddTransitTunnel (TransitTunnel * tunnel); @@ -130,9 +131,9 @@ namespace tunnel void PostTunnelData (I2NPMessage * msg); template TTunnel * CreateTunnel (TunnelConfig * config, OutboundTunnel * outboundTunnel = 0); - TunnelPool * CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOuboundHops); - void DeleteTunnelPool (TunnelPool * pool); - void StopTunnelPool (TunnelPool * pool); + std::shared_ptr CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOuboundHops); + void DeleteTunnelPool (std::shared_ptr pool); + void StopTunnelPool (std::shared_ptr pool); private: @@ -158,8 +159,8 @@ namespace tunnel std::mutex m_TransitTunnelsMutex; std::map m_TransitTunnels; std::mutex m_PoolsMutex; - std::list m_Pools; - TunnelPool * m_ExploratoryPool; + std::list> m_Pools; + std::shared_ptr m_ExploratoryPool; i2p::util::Queue m_Queue; public: diff --git a/TunnelPool.cpp b/TunnelPool.cpp index fd176841..2482d670 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -300,7 +300,7 @@ namespace tunnel } std::reverse (hops.begin (), hops.end ()); auto * tunnel = tunnels.CreateTunnel (new TunnelConfig (hops), outboundTunnel); - tunnel->SetTunnelPool (this); + tunnel->SetTunnelPool (shared_from_this ()); } void TunnelPool::RecreateInboundTunnel (InboundTunnel * tunnel) @@ -310,7 +310,7 @@ namespace tunnel outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint ("Re-creating destination inbound tunnel..."); auto * newTunnel = tunnels.CreateTunnel (tunnel->GetTunnelConfig ()->Clone (), outboundTunnel); - newTunnel->SetTunnelPool (this); + newTunnel->SetTunnelPool (shared_from_this()); } void TunnelPool::CreateOutboundTunnel () @@ -333,7 +333,7 @@ namespace tunnel auto * tunnel = tunnels.CreateTunnel ( new TunnelConfig (hops, inboundTunnel->GetTunnelConfig ())); - tunnel->SetTunnelPool (this); + tunnel->SetTunnelPool (shared_from_this ()); } else LogPrint ("Can't create outbound tunnel. No inbound tunnels found"); @@ -349,7 +349,7 @@ namespace tunnel LogPrint ("Re-creating destination outbound tunnel..."); auto * newTunnel = tunnels.CreateTunnel ( tunnel->GetTunnelConfig ()->Clone (inboundTunnel->GetTunnelConfig ())); - newTunnel->SetTunnelPool (this); + newTunnel->SetTunnelPool (shared_from_this ()); } else LogPrint ("Can't re-create outbound tunnel. No inbound tunnels found"); diff --git a/TunnelPool.h b/TunnelPool.h index b2d75950..2bc645b3 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "Identity.h" #include "LeaseSet.h" #include "RouterInfo.h" @@ -22,7 +23,7 @@ namespace tunnel class InboundTunnel; class OutboundTunnel; - class TunnelPool // per local destination + class TunnelPool: public std::enable_shared_from_this // per local destination { public: From 42354ee5d549be15a61846795e29eab952cf1d66 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 20 Jan 2015 07:50:25 -0500 Subject: [PATCH 0073/6300] removed useless mutex lock --- Tunnel.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index b6e93eb5..22f92061 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -209,7 +209,7 @@ namespace tunnel delete it.second; m_TransitTunnels.clear (); - /*for (auto& it : m_PendingTunnels) + /* for (auto& it : m_PendingTunnels) delete it.second; m_PendingTunnels.clear ();*/ @@ -428,7 +428,6 @@ namespace tunnel { LogPrint ("Tunnel ", tunnel->GetTunnelID (), " expired"); { - std::unique_lock l(m_PoolsMutex); auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); @@ -474,7 +473,6 @@ namespace tunnel { LogPrint ("Tunnel ", tunnel->GetTunnelID (), " expired"); { - std::unique_lock l(m_PoolsMutex); auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); @@ -522,8 +520,8 @@ namespace tunnel { if (ts > it->second->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - LogPrint ("Transit tunnel ", it->second->GetTunnelID (), " expired"); auto tmp = it->second; + LogPrint ("Transit tunnel ", tmp->GetTunnelID (), " expired"); { std::unique_lock l(m_TransitTunnelsMutex); it = m_TransitTunnels.erase (it); From 74c89ce06edbd1fadfc7ce97840a22149a53dfd8 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 20 Jan 2015 18:06:42 -0500 Subject: [PATCH 0074/6300] proper cleanup of pending tunnels --- Tunnel.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 22f92061..516a0c3d 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -209,9 +209,10 @@ namespace tunnel delete it.second; m_TransitTunnels.clear (); - /* for (auto& it : m_PendingTunnels) + ManagePendingTunnels (); + for (auto& it : m_PendingTunnels) delete it.second; - m_PendingTunnels.clear ();*/ + m_PendingTunnels.clear (); } From ea353ac3bafec8b27950481af8025ff741b810a6 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 20 Jan 2015 21:05:57 -0500 Subject: [PATCH 0075/6300] send batch of I2NP messages --- NTCPSession.cpp | 11 +++++++++++ NTCPSession.h | 2 ++ SSUSession.cpp | 14 +++++++++++++- SSUSession.h | 2 ++ TransportSession.h | 2 ++ Transports.cpp | 38 ++++++++++++++++++++++++++++++++++++-- Transports.h | 6 ++++-- TunnelGateway.cpp | 2 +- 8 files changed, 71 insertions(+), 6 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 932d16bf..9b283612 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -617,6 +617,17 @@ namespace transport if (msg) Send (msg); } + + void NTCPSession::SendI2NPMessages (const std::vector& msgs) + { + m_Server.GetService ().post (std::bind (&NTCPSession::PostI2NPMessages, shared_from_this (), msgs)); + } + + void NTCPSession::PostI2NPMessages (std::vector msgs) + { + for (auto it: msgs) + if (it) Send (it); + } void NTCPSession::ScheduleTermination () { diff --git a/NTCPSession.h b/NTCPSession.h index 43c7dc58..8435d5d9 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -61,6 +61,7 @@ namespace transport void ClientLogin (); void ServerLogin (); void SendI2NPMessage (I2NPMessage * msg); + void SendI2NPMessages (const std::vector& msgs); size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; @@ -68,6 +69,7 @@ namespace transport protected: void PostI2NPMessage (I2NPMessage * msg); + void PostI2NPMessages (std::vector msgs); void Connected (); void SendTimeSyncMessage (); void SetIsEstablished (bool isEstablished) { m_IsEstablished = isEstablished; } diff --git a/SSUSession.cpp b/SSUSession.cpp index 3bf95808..0cbbc818 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -827,7 +827,19 @@ namespace transport if (msg) m_Data.Send (msg); } - + + void SSUSession::SendI2NPMessages (const std::vector& msgs) + { + boost::asio::io_service& service = IsV6 () ? m_Server.GetServiceV6 () : m_Server.GetService (); + service.post (std::bind (&SSUSession::PostI2NPMessages, shared_from_this (), msgs)); + } + + void SSUSession::PostI2NPMessages (std::vector msgs) + { + for (auto it: msgs) + if (it) m_Data.Send (it); + } + void SSUSession::ProcessData (uint8_t * buf, size_t len) { m_Data.ProcessMessage (buf, len); diff --git a/SSUSession.h b/SSUSession.h index ce840636..565afc1f 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -66,6 +66,7 @@ namespace transport boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; void SendI2NPMessage (I2NPMessage * msg); + void SendI2NPMessages (const std::vector& msgs); void SendPeerTest (); // Alice SessionState GetState () const { return m_State; }; @@ -81,6 +82,7 @@ namespace transport void CreateAESandMacKey (const uint8_t * pubKey); void PostI2NPMessage (I2NPMessage * msg); + void PostI2NPMessages (std::vector msgs); void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session void ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); void SendSessionRequest (); diff --git a/TransportSession.h b/TransportSession.h index 99d8d868..45ce9366 100644 --- a/TransportSession.h +++ b/TransportSession.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "Identity.h" #include "RouterInfo.h" #include "I2NPProtocol.h" @@ -66,6 +67,7 @@ namespace transport const i2p::data::IdentityEx& GetRemoteIdentity () { return m_RemoteIdentity; }; virtual void SendI2NPMessage (I2NPMessage * msg) = 0; + virtual void SendI2NPMessages (const std::vector& msgs) = 0; protected: diff --git a/Transports.cpp b/Transports.cpp index 4cb20eef..dfcfe253 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -184,6 +184,11 @@ namespace transport m_Service.post (std::bind (&Transports::PostMessage, this, ident, msg)); } + void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector& msgs) + { + m_Service.post (std::bind (&Transports::PostMessages, this, ident, msgs)); + } + void Transports::PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) { if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) @@ -210,6 +215,36 @@ namespace transport it->second.delayedMessages.push_back (msg); } + void Transports::PostMessages (const i2p::data::IdentHash& ident, std::vector msgs) + { + if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) + { + // we send it to ourself + for (auto it: msgs) + i2p::HandleI2NPMessage (it); + return; + } + auto it = m_Peers.find (ident); + if (it == m_Peers.end ()) + { + auto r = netdb.FindRouter (ident); + it = m_Peers.insert (std::pair(ident, { 0, r, nullptr})).first; + if (!ConnectToPeer (ident, it->second)) + { + for (auto it1: msgs) + DeleteI2NPMessage (it1); + return; + } + } + if (it->second.session) + it->second.session->SendI2NPMessages (msgs); + else + { + for (auto it1: msgs) + it->second.delayedMessages.push_back (it1); + } + } + bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer) { if (peer.router) // we have RI already @@ -365,8 +400,7 @@ namespace transport if (it != m_Peers.end ()) { it->second.session = session; - for (auto it1: it->second.delayedMessages) - session->SendI2NPMessage (it1); + session->SendI2NPMessages (it->second.delayedMessages); it->second.delayedMessages.clear (); } else // incoming connection diff --git a/Transports.h b/Transports.h index ffc561ff..d09699d6 100644 --- a/Transports.h +++ b/Transports.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include #include @@ -56,7 +56,7 @@ namespace transport int numAttempts; std::shared_ptr router; std::shared_ptr session; - std::list delayedMessages; + std::vector delayedMessages; ~Peer () { @@ -80,6 +80,7 @@ namespace transport void ReuseDHKeysPair (DHKeysPair * pair); void SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); + void SendMessages (const i2p::data::IdentHash& ident, const std::vector& msgs); void CloseSession (std::shared_ptr router); void PeerConnected (std::shared_ptr session); @@ -91,6 +92,7 @@ namespace transport void RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); void HandleRequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); void PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); + void PostMessages (const i2p::data::IdentHash& ident, std::vector msgs); void PostCloseSession (std::shared_ptr router); bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index 146c007c..a2352031 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -188,9 +188,9 @@ namespace tunnel { m_Tunnel->EncryptTunnelMsg (tunnelMsg); FillI2NPMessageHeader (tunnelMsg, eI2NPTunnelData); - i2p::transport::transports.SendMessage (m_Tunnel->GetNextIdentHash (), tunnelMsg); m_NumSentBytes += TUNNEL_DATA_MSG_SIZE; } + i2p::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), tunnelMsgs); m_Buffer.ClearTunnelDataMsgs (); } } From c61cd350eebf573d48e31e475d6484854e7111e1 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 20 Jan 2015 22:35:27 -0500 Subject: [PATCH 0076/6300] send multiple messages though single write call --- NTCPSession.cpp | 43 +++++++++++++++++++++++++++++++++++++------ NTCPSession.h | 7 +++++-- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 9b283612..6b040c8f 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -547,6 +547,12 @@ namespace transport } void NTCPSession::Send (i2p::I2NPMessage * msg) + { + boost::asio::async_write (m_Socket, CreateMsgBuffer (msg), boost::asio::transfer_all (), + std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msg)); + } + + boost::asio::const_buffers_1 NTCPSession::CreateMsgBuffer (I2NPMessage * msg) { uint8_t * sendBuffer; int len; @@ -579,10 +585,8 @@ namespace transport int l = len + padding + 6; m_Encryption.Encrypt(sendBuffer, l, sendBuffer); - - boost::asio::async_write (m_Socket, boost::asio::buffer (sendBuffer, l), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msg)); - } + return boost::asio::buffer ((const uint8_t *)sendBuffer, l); + } void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, i2p::I2NPMessage * msg) { @@ -602,6 +606,34 @@ namespace transport } } + void NTCPSession::Send (const std::vector& msgs) + { + std::vector bufs; + for (auto it: msgs) + bufs.push_back (CreateMsgBuffer (it)); + boost::asio::async_write (m_Socket, bufs, boost::asio::transfer_all (), + std::bind(&NTCPSession::HandleBatchSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msgs)); + } + + void NTCPSession::HandleBatchSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector msgs) + { + for (auto it: msgs) + if (it) i2p::DeleteI2NPMessage (it); + if (ecode) + { + LogPrint (eLogWarning, "Couldn't send msgs: ", ecode.message ()); + // we shouldn't call Terminate () here, because HandleReceive takes care + // TODO: 'delete this' statement in Terminate () must be eliminated later + // Terminate (); + } + else + { + m_NumSentBytes += bytes_transferred; + ScheduleTermination (); // reset termination timer + } + } + + void NTCPSession::SendTimeSyncMessage () { Send (nullptr); @@ -625,8 +657,7 @@ namespace transport void NTCPSession::PostI2NPMessages (std::vector msgs) { - for (auto it: msgs) - if (it) Send (it); + Send (msgs); } void NTCPSession::ScheduleTermination () diff --git a/NTCPSession.h b/NTCPSession.h index 8435d5d9..d9082dab 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -101,9 +101,12 @@ namespace transport bool DecryptNextBlock (const uint8_t * encrypted); void Send (i2p::I2NPMessage * msg); + boost::asio::const_buffers_1 CreateMsgBuffer (I2NPMessage * msg); void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, i2p::I2NPMessage * msg); - - + void Send (const std::vector& msgs); + void HandleBatchSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector msgs); + + // timer void ScheduleTermination (); void HandleTerminationTimer (const boost::system::error_code& ecode); From e7f849184cd7727530a62a2cd5c560885a61c995 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Jan 2015 12:08:15 -0500 Subject: [PATCH 0077/6300] some cleanup --- NTCPSession.cpp | 66 ++++++++++++++----------------------------------- NTCPSession.h | 7 ++---- 2 files changed, 21 insertions(+), 52 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 6b040c8f..94f209df 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -76,7 +76,7 @@ namespace transport m_Socket.close (); transports.PeerDisconnected (shared_from_this ()); m_Server.RemoveNTCPSession (shared_from_this ()); - LogPrint ("NTCP session terminated"); + LogPrint (eLogInfo, "NTCP session wiht ", m_Socket.remote_endpoint ()," terminated"); } void NTCPSession::Connected () @@ -123,13 +123,12 @@ namespace transport { if (ecode) { - LogPrint (eLogWarning, "Couldn't send Phase 1 message: ", ecode.message ()); + LogPrint (eLogError, "Couldn't send Phase 1 message: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else { - LogPrint (eLogDebug, "Phase 1 sent: ", bytes_transferred); boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase2, sizeof (NTCPPhase2)), boost::asio::transfer_all (), std::bind(&NTCPSession::HandlePhase2Received, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -146,7 +145,6 @@ namespace transport } else { - LogPrint (eLogDebug, "Phase 1 received: ", bytes_transferred); // verify ident uint8_t digest[32]; CryptoPP::SHA256().CalculateDigest(digest, m_Establisher->phase1.pubKey, 256); @@ -196,13 +194,12 @@ namespace transport { if (ecode) { - LogPrint (eLogWarning, "Couldn't send Phase 2 message: ", ecode.message ()); + LogPrint (eLogError, "Couldn't send Phase 2 message: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else { - LogPrint (eLogDebug, "Phase 2 sent: ", bytes_transferred); boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer, NTCP_DEFAULT_PHASE3_SIZE), boost::asio::transfer_all (), std::bind(&NTCPSession::HandlePhase3Received, shared_from_this (), std::placeholders::_1, std::placeholders::_2, tsB)); @@ -213,7 +210,7 @@ namespace transport { if (ecode) { - LogPrint ("Phase 2 read error: ", ecode.message (), ". Wrong ident assumed"); + LogPrint (eLogError, "Phase 2 read error: ", ecode.message (), ". Wrong ident assumed"); if (ecode != boost::asio::error::operation_aborted) { // this RI is not valid @@ -225,8 +222,6 @@ namespace transport } else { - LogPrint (eLogDebug, "Phase 2 received: ", bytes_transferred); - i2p::crypto::AESKey aesKey; CreateAESKey (m_Establisher->phase2.pubKey, aesKey); m_Decryption.SetKey (aesKey); @@ -289,13 +284,12 @@ namespace transport { if (ecode) { - LogPrint (eLogWarning, "Couldn't send Phase 3 message: ", ecode.message ()); + LogPrint (eLogError, "Couldn't send Phase 3 message: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else { - LogPrint (eLogDebug, "Phase 3 sent: ", bytes_transferred); // wait for phase4 auto signatureLen = m_RemoteIdentity.GetSignatureLen (); size_t paddingSize = signatureLen & 0x0F; // %16 @@ -316,7 +310,6 @@ namespace transport } else { - LogPrint (eLogDebug, "Phase 3 received: ", bytes_transferred); m_Decryption.Decrypt (m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer); uint8_t * buf = m_ReceiveBuffer; uint16_t size = bufbe16toh (buf); @@ -328,7 +321,6 @@ namespace transport { // we need more bytes for Phase3 expectedSize += paddingLen; - LogPrint (eLogDebug, "Wait for ", expectedSize, " more bytes for Phase3"); boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, expectedSize), boost::asio::transfer_all (), std::bind(&NTCPSession::HandlePhase3ExtraReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2, tsB, paddingLen)); @@ -348,7 +340,6 @@ namespace transport } else { - LogPrint (eLogDebug, "Phase 3 extra received: ", bytes_transferred); m_Decryption.Decrypt (m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, bytes_transferred, m_ReceiveBuffer+ NTCP_DEFAULT_PHASE3_SIZE); HandlePhase3 (tsB, paddingLen); } @@ -406,8 +397,7 @@ namespace transport } else { - LogPrint (eLogDebug, "Phase 4 sent: ", bytes_transferred); - LogPrint ("NTCP server session connected"); + LogPrint (eLogInfo, "NTCP server session from ", m_Socket.remote_endpoint (), " connected"); m_Server.AddNTCPSession (shared_from_this ()); Connected (); @@ -421,7 +411,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "Phase 4 read error: ", ecode.message ()); + LogPrint (eLogError, "Phase 4 read error: ", ecode.message (), ". Check your clock"); if (ecode != boost::asio::error::operation_aborted) { // this router doesn't like us @@ -431,7 +421,6 @@ namespace transport } else { - LogPrint (eLogDebug, "Phase 4 received: ", bytes_transferred); m_Decryption.Decrypt(m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer); // verify signature @@ -448,7 +437,7 @@ namespace transport Terminate (); return; } - LogPrint ("NTCP session connected"); + LogPrint (eLogInfo, "NTCP session to ", m_Socket.remote_endpoint (), " connected"); Connected (); m_ReceiveBufferOffset = 0; @@ -549,7 +538,7 @@ namespace transport void NTCPSession::Send (i2p::I2NPMessage * msg) { boost::asio::async_write (m_Socket, CreateMsgBuffer (msg), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msg)); + std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::vector{ msg })); } boost::asio::const_buffers_1 NTCPSession::CreateMsgBuffer (I2NPMessage * msg) @@ -587,24 +576,7 @@ namespace transport m_Encryption.Encrypt(sendBuffer, l, sendBuffer); return boost::asio::buffer ((const uint8_t *)sendBuffer, l); } - - void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, i2p::I2NPMessage * msg) - { - if (msg) - i2p::DeleteI2NPMessage (msg); - if (ecode) - { - LogPrint (eLogWarning, "Couldn't send msg: ", ecode.message ()); - // we shouldn't call Terminate () here, because HandleReceive takes care - // TODO: 'delete this' statement in Terminate () must be eliminated later - // Terminate (); - } - else - { - m_NumSentBytes += bytes_transferred; - ScheduleTermination (); // reset termination timer - } - } + void NTCPSession::Send (const std::vector& msgs) { @@ -612,10 +584,10 @@ namespace transport for (auto it: msgs) bufs.push_back (CreateMsgBuffer (it)); boost::asio::async_write (m_Socket, bufs, boost::asio::transfer_all (), - std::bind(&NTCPSession::HandleBatchSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msgs)); + std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msgs)); } - void NTCPSession::HandleBatchSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector msgs) + void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector msgs) { for (auto it: msgs) if (it) i2p::DeleteI2NPMessage (it); @@ -705,7 +677,7 @@ namespace transport m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address.port)); - LogPrint ("Start listening TCP port ", address.port); + LogPrint (eLogInfo, "Start listening TCP port ", address.port); auto conn = std::make_shared(*this); m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, conn, std::placeholders::_1)); @@ -718,7 +690,7 @@ namespace transport m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address.port)); m_NTCPV6Acceptor->listen (); - LogPrint ("Start listening V6 TCP port ", address.port); + LogPrint (eLogInfo, "Start listening V6 TCP port ", address.port); auto conn = std::make_shared (*this); m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this, conn, std::placeholders::_1)); @@ -797,7 +769,7 @@ namespace transport { if (!error) { - LogPrint ("Connected from ", conn->GetSocket ().remote_endpoint().address ().to_string ()); + LogPrint (eLogInfo, "Connected from ", conn->GetSocket ().remote_endpoint()); conn->ServerLogin (); } @@ -814,7 +786,7 @@ namespace transport { if (!error) { - LogPrint ("Connected from ", conn->GetSocket ().remote_endpoint().address ().to_string ()); + LogPrint (eLogInfo, "Connected from ", conn->GetSocket ().remote_endpoint()); conn->ServerLogin (); } @@ -828,7 +800,7 @@ namespace transport void NTCPServer::Connect (const boost::asio::ip::address& address, int port, std::shared_ptr conn) { - LogPrint ("Connecting to ", address ,":", port); + LogPrint (eLogInfo, "Connecting to ", address ,":", port); m_Service.post([conn, this]() { this->AddNTCPSession (conn); @@ -841,7 +813,7 @@ namespace transport { if (ecode) { - LogPrint ("Connect error: ", ecode.message ()); + LogPrint (eLogError, "Connect error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) { i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ().GetIdentHash (), true); @@ -850,7 +822,7 @@ namespace transport } else { - LogPrint ("Connected"); + LogPrint (eLogInfo, "Connected to ", conn->GetSocket ().remote_endpoint ()); if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6 context.UpdateNTCPV6Address (conn->GetSocket ().local_endpoint ().address ()); conn->ClientLogin (); diff --git a/NTCPSession.h b/NTCPSession.h index d9082dab..6098a596 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -66,15 +66,13 @@ namespace transport size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; - protected: + private: void PostI2NPMessage (I2NPMessage * msg); void PostI2NPMessages (std::vector msgs); void Connected (); void SendTimeSyncMessage (); void SetIsEstablished (bool isEstablished) { m_IsEstablished = isEstablished; } - - private: void CreateAESKey (uint8_t * pubKey, i2p::crypto::AESKey& key); @@ -102,9 +100,8 @@ namespace transport void Send (i2p::I2NPMessage * msg); boost::asio::const_buffers_1 CreateMsgBuffer (I2NPMessage * msg); - void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, i2p::I2NPMessage * msg); void Send (const std::vector& msgs); - void HandleBatchSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector msgs); + void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector msgs); // timer From 89dead79c4f2011c2846fab645d2f5c098f90ae7 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Jan 2015 14:40:48 -0500 Subject: [PATCH 0078/6300] common HandleTunnelData for own and transit tunnels --- TransitTunnel.h | 2 +- Tunnel.cpp | 23 +++++++++++++---------- Tunnel.h | 1 + TunnelBase.h | 1 + 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/TransitTunnel.h b/TransitTunnel.h index e71c16e1..3d41e4fe 100644 --- a/TransitTunnel.h +++ b/TransitTunnel.h @@ -21,13 +21,13 @@ namespace tunnel const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey); - virtual void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg); virtual void SendTunnelDataMsg (i2p::I2NPMessage * msg); virtual size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; }; uint32_t GetTunnelID () const { return m_TunnelID; }; // implements TunnelBase + void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg); void EncryptTunnelMsg (I2NPMessage * tunnelMsg); uint32_t GetNextTunnelID () const { return m_NextTunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; }; diff --git a/Tunnel.cpp b/Tunnel.cpp index 516a0c3d..5b672849 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -189,6 +189,12 @@ namespace tunnel m_Gateway.SendBuffer (); } + void OutboundTunnel::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) + { + LogPrint (eLogError, "Incoming message for outbound tunnel ", GetTunnelID ()); + DeleteI2NPMessage (tunnelMsg); + } + Tunnels tunnels; Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr) @@ -344,20 +350,17 @@ namespace tunnel while (msg) { uint32_t tunnelID = bufbe32toh (msg->GetPayload ()); - InboundTunnel * tunnel = GetInboundTunnel (tunnelID); + TunnelBase * tunnel = GetInboundTunnel (tunnelID); + if (!tunnel) + tunnel = GetTransitTunnel (tunnelID); if (tunnel) tunnel->HandleTunnelDataMsg (msg); - else + else { - TransitTunnel * transitTunnel = GetTransitTunnel (tunnelID); - if (transitTunnel) - transitTunnel->HandleTunnelDataMsg (msg); - else - { - LogPrint ("Tunnel ", tunnelID, " not found"); - i2p::DeleteI2NPMessage (msg); - } + LogPrint ("Tunnel ", tunnelID, " not found"); + DeleteI2NPMessage (msg); } + msg = m_Queue.Get (); } diff --git a/Tunnel.h b/Tunnel.h index 527e8827..91d6330a 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -85,6 +85,7 @@ namespace tunnel size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; // implements TunnelBase + void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg); uint32_t GetTunnelID () const { return GetNextTunnelID (); }; private: diff --git a/TunnelBase.h b/TunnelBase.h index 24704d46..affeb6f9 100644 --- a/TunnelBase.h +++ b/TunnelBase.h @@ -36,6 +36,7 @@ namespace tunnel TunnelBase (): m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {}; virtual ~TunnelBase () {}; + virtual void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) = 0; virtual void EncryptTunnelMsg (I2NPMessage * tunnelMsg) = 0; virtual uint32_t GetNextTunnelID () const = 0; virtual const i2p::data::IdentHash& GetNextIdentHash () const = 0; From ec980edf56253d37c1bca94b317b733273aeb057 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Jan 2015 15:13:46 -0500 Subject: [PATCH 0079/6300] don't look for tunnel again if tunnelID is the same as for previous message --- Tunnel.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 5b672849..ed82e3e8 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -347,10 +347,16 @@ namespace tunnel try { I2NPMessage * msg = m_Queue.GetNextWithTimeout (1000); // 1 sec + uint32_t prevTunnelID = 0; + TunnelBase * prevTunnel = nullptr; while (msg) { - uint32_t tunnelID = bufbe32toh (msg->GetPayload ()); - TunnelBase * tunnel = GetInboundTunnel (tunnelID); + uint32_t tunnelID = bufbe32toh (msg->GetPayload ()); + TunnelBase * tunnel = nullptr; + if (tunnelID == prevTunnelID) + tunnel = prevTunnel; + if (!tunnel) + tunnel = GetInboundTunnel (tunnelID); if (!tunnel) tunnel = GetTransitTunnel (tunnelID); if (tunnel) @@ -362,6 +368,11 @@ namespace tunnel } msg = m_Queue.Get (); + if (msg) + { + prevTunnelID = tunnelID; + prevTunnel = tunnel; + } } uint64_t ts = i2p::util::GetSecondsSinceEpoch (); From 276d5097c1d5630fdba65cdedd3223e7845834b9 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Jan 2015 15:59:05 -0500 Subject: [PATCH 0080/6300] fixed 'bad descriptor' exception --- NTCPSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 94f209df..0a0a52fa 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -76,7 +76,7 @@ namespace transport m_Socket.close (); transports.PeerDisconnected (shared_from_this ()); m_Server.RemoveNTCPSession (shared_from_this ()); - LogPrint (eLogInfo, "NTCP session wiht ", m_Socket.remote_endpoint ()," terminated"); + LogPrint (eLogInfo, "NTCP session terminated"); } void NTCPSession::Connected () From 46a36f766f77f40268bfbcc32103a6025965d82f Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Jan 2015 16:34:50 -0500 Subject: [PATCH 0081/6300] don't restart subscriptions update timer if no subscription presented --- AddressBook.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index f78666a0..a77b7194 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -355,10 +355,13 @@ namespace client void AddressBook::DownloadComplete (bool success) { m_IsDownloading = false; - m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes( - success ? CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT : CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT)); - m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, - this, std::placeholders::_1)); + if (m_SubscriptionsUpdateTimer) + { + m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes( + success ? CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT : CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT)); + m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, + this, std::placeholders::_1)); + } } void AddressBook::StartSubscriptions () From 2ab0ff8aea88ff9b340d1defa2a2dafe20f1ae09 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Jan 2015 21:50:46 -0500 Subject: [PATCH 0082/6300] TransitTunnelParticipant --- TransitTunnel.cpp | 47 +++++++++++++++++++++++++++----------- TransitTunnel.h | 27 +++++++++++++++++++--- Tunnel.cpp | 57 +++++++++++++++++++++++++++-------------------- TunnelBase.h | 1 + 4 files changed, 92 insertions(+), 40 deletions(-) diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index 1f755be5..66120c51 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -15,7 +15,7 @@ namespace tunnel const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey): m_TunnelID (receiveTunnelID), m_NextTunnelID (nextTunnelID), - m_NextIdent (nextIdent), m_NumTransmittedBytes (0) + m_NextIdent (nextIdent) { m_Encryption.SetKeys (layerKey, ivKey); } @@ -24,25 +24,46 @@ namespace tunnel { m_Encryption.Encrypt (tunnelMsg->GetPayload () + 4); } - - void TransitTunnel::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) + + TransitTunnelParticipant::~TransitTunnelParticipant () + { + for (auto it: m_TunnelDataMsgs) + i2p::DeleteI2NPMessage (it); + } + + void TransitTunnelParticipant::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) { EncryptTunnelMsg (tunnelMsg); - LogPrint ("TransitTunnel: ",m_TunnelID,"->", m_NextTunnelID); + LogPrint (eLogDebug, "TransitTunnel: ",GetTunnelID (),"->", GetNextTunnelID ()); m_NumTransmittedBytes += tunnelMsg->GetLength (); - htobe32buf (tunnelMsg->GetPayload (), m_NextTunnelID); + htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); FillI2NPMessageHeader (tunnelMsg, eI2NPTunnelData); - - i2p::transport::transports.SendMessage (m_NextIdent, tunnelMsg); + m_TunnelDataMsgs.push_back (tunnelMsg); } + void TransitTunnelParticipant::FlushTunnelDataMsgs () + { + LogPrint (eLogDebug, "TransitTunnel: flush"); + if (!m_TunnelDataMsgs.empty ()) + { + i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); + m_TunnelDataMsgs.clear (); + } + } + void TransitTunnel::SendTunnelDataMsg (i2p::I2NPMessage * msg) { - LogPrint ("We are not a gateway for transit tunnel ", m_TunnelID); + LogPrint (eLogError, "We are not a gateway for transit tunnel ", m_TunnelID); i2p::DeleteI2NPMessage (msg); } + void TransitTunnel::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) + { + LogPrint (eLogError, "Incoming tunnel message is not supported ", m_TunnelID); + DeleteI2NPMessage (tunnelMsg); + } + void TransitTunnelGateway::SendTunnelDataMsg (i2p::I2NPMessage * msg) { TunnelMessageBlock block; @@ -56,7 +77,7 @@ namespace tunnel { EncryptTunnelMsg (tunnelMsg); - LogPrint ("TransitTunnel endpoint for ", GetTunnelID ()); + LogPrint (eLogDebug, "TransitTunnel endpoint for ", GetTunnelID ()); m_Endpoint.HandleDecryptedTunnelDataMsg (tunnelMsg); } @@ -67,18 +88,18 @@ namespace tunnel { if (isEndpoint) { - LogPrint ("TransitTunnel endpoint: ", receiveTunnelID, " created"); + LogPrint (eLogInfo, "TransitTunnel endpoint: ", receiveTunnelID, " created"); return new TransitTunnelEndpoint (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else if (isGateway) { - LogPrint ("TransitTunnel gateway: ", receiveTunnelID, " created"); + LogPrint (eLogInfo, "TransitTunnel gateway: ", receiveTunnelID, " created"); return new TransitTunnelGateway (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else { - LogPrint ("TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created"); - return new TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); + LogPrint (eLogInfo, "TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created"); + return new TransitTunnelParticipant (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } } } diff --git a/TransitTunnel.h b/TransitTunnel.h index 3d41e4fe..93b72034 100644 --- a/TransitTunnel.h +++ b/TransitTunnel.h @@ -2,6 +2,7 @@ #define TRANSIT_TUNNEL_H__ #include +#include #include #include "aes.h" #include "I2NPProtocol.h" @@ -13,7 +14,7 @@ namespace i2p { namespace tunnel { - class TransitTunnel: public TunnelBase // tunnel patricipant + class TransitTunnel: public TunnelBase { public: @@ -22,7 +23,7 @@ namespace tunnel const uint8_t * layerKey,const uint8_t * ivKey); virtual void SendTunnelDataMsg (i2p::I2NPMessage * msg); - virtual size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; }; + virtual size_t GetNumTransmittedBytes () const { return 0; }; uint32_t GetTunnelID () const { return m_TunnelID; }; @@ -36,11 +37,31 @@ namespace tunnel uint32_t m_TunnelID, m_NextTunnelID; i2p::data::IdentHash m_NextIdent; - size_t m_NumTransmittedBytes; i2p::crypto::TunnelEncryption m_Encryption; }; + class TransitTunnelParticipant: public TransitTunnel + { + public: + + TransitTunnelParticipant (uint32_t receiveTunnelID, + const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * layerKey,const uint8_t * ivKey): + TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, + layerKey, ivKey), m_NumTransmittedBytes (0) {}; + ~TransitTunnelParticipant (); + + size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; }; + void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg); + void FlushTunnelDataMsgs (); + + private: + + size_t m_NumTransmittedBytes; + std::vector m_TunnelDataMsgs; + }; + class TransitTunnelGateway: public TransitTunnel { public: diff --git a/Tunnel.cpp b/Tunnel.cpp index ed82e3e8..5eb461ff 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -347,32 +347,41 @@ namespace tunnel try { I2NPMessage * msg = m_Queue.GetNextWithTimeout (1000); // 1 sec - uint32_t prevTunnelID = 0; - TunnelBase * prevTunnel = nullptr; - while (msg) - { - uint32_t tunnelID = bufbe32toh (msg->GetPayload ()); - TunnelBase * tunnel = nullptr; - if (tunnelID == prevTunnelID) - tunnel = prevTunnel; - if (!tunnel) - tunnel = GetInboundTunnel (tunnelID); - if (!tunnel) - tunnel = GetTransitTunnel (tunnelID); - if (tunnel) - tunnel->HandleTunnelDataMsg (msg); - else - { - LogPrint ("Tunnel ", tunnelID, " not found"); - DeleteI2NPMessage (msg); - } - - msg = m_Queue.Get (); - if (msg) + if (msg) + { + uint32_t prevTunnelID = 0; + TunnelBase * prevTunnel = nullptr; + do { - prevTunnelID = tunnelID; - prevTunnel = tunnel; + uint32_t tunnelID = bufbe32toh (msg->GetPayload ()); + TunnelBase * tunnel = nullptr; + if (tunnelID == prevTunnelID) + tunnel = prevTunnel; + else if (prevTunnel) + prevTunnel->FlushTunnelDataMsgs (); + + if (!tunnel) + tunnel = GetInboundTunnel (tunnelID); + if (!tunnel) + tunnel = GetTransitTunnel (tunnelID); + if (tunnel) + tunnel->HandleTunnelDataMsg (msg); + else + { + LogPrint ("Tunnel ", tunnelID, " not found"); + DeleteI2NPMessage (msg); + } + + msg = m_Queue.Get (); + if (msg) + { + prevTunnelID = tunnelID; + prevTunnel = tunnel; + } + else if (tunnel) + tunnel->FlushTunnelDataMsgs (); } + while (msg); } uint64_t ts = i2p::util::GetSecondsSinceEpoch (); diff --git a/TunnelBase.h b/TunnelBase.h index affeb6f9..8be6302c 100644 --- a/TunnelBase.h +++ b/TunnelBase.h @@ -37,6 +37,7 @@ namespace tunnel virtual ~TunnelBase () {}; virtual void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) = 0; + virtual void FlushTunnelDataMsgs () {}; virtual void EncryptTunnelMsg (I2NPMessage * tunnelMsg) = 0; virtual uint32_t GetNextTunnelID () const = 0; virtual const i2p::data::IdentHash& GetNextIdentHash () const = 0; From b269bda52b2f30454335f58bf577636144aba03c Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 22 Jan 2015 15:31:34 -0500 Subject: [PATCH 0083/6300] shared_ptr for GarlicRouting Session --- Garlic.cpp | 15 ++++++--------- Garlic.h | 10 +++++----- Streaming.cpp | 4 ++-- Streaming.h | 2 +- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index ff773978..a0a6747e 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -197,7 +197,7 @@ namespace garlic { (*numCloves)++; m_UnconfirmedTagsMsgs[msgID] = newTags; - m_Owner->DeliveryStatusSent (this, msgID); + m_Owner->DeliveryStatusSent (shared_from_this (), msgID); } else LogPrint ("DeliveryStatus clove was not created"); @@ -306,9 +306,6 @@ namespace garlic GarlicDestination::~GarlicDestination () { - for (auto it: m_Sessions) - delete it.second; - m_Sessions.clear (); } void GarlicDestination::AddSessionKey (const uint8_t * key, const uint8_t * tag) @@ -503,23 +500,23 @@ namespace garlic } } - GarlicRoutingSession * GarlicDestination::GetRoutingSession ( + std::shared_ptr GarlicDestination::GetRoutingSession ( const i2p::data::RoutingDestination& destination, int numTags) { auto it = m_Sessions.find (destination.GetIdentHash ()); - GarlicRoutingSession * session = nullptr; + std::shared_ptr session; if (it != m_Sessions.end ()) session = it->second; if (!session) { - session = new GarlicRoutingSession (this, &destination, numTags); + session = std::make_shared (this, &destination, numTags); std::unique_lock l(m_SessionsMutex); m_Sessions[destination.GetIdentHash ()] = session; } return session; } - void GarlicDestination::DeliveryStatusSent (GarlicRoutingSession * session, uint32_t msgID) + void GarlicDestination::DeliveryStatusSent (std::shared_ptr session, uint32_t msgID) { m_CreatedSessions[msgID] = session; } @@ -533,7 +530,7 @@ namespace garlic { it->second->TagsConfirmed (msgID); m_CreatedSessions.erase (it); - LogPrint ("Garlic message ", msgID, " acknowledged"); + LogPrint (eLogInfo, "Garlic message ", msgID, " acknowledged"); } } DeleteI2NPMessage (msg); diff --git a/Garlic.h b/Garlic.h index 88d47280..dc18ade3 100644 --- a/Garlic.h +++ b/Garlic.h @@ -54,7 +54,7 @@ namespace garlic }; class GarlicDestination; - class GarlicRoutingSession + class GarlicRoutingSession: public std::enable_shared_from_this { struct UnconfirmedTags { @@ -105,13 +105,13 @@ namespace garlic GarlicDestination (): m_LastTagsCleanupTime (0) {}; ~GarlicDestination (); - GarlicRoutingSession * GetRoutingSession (const i2p::data::RoutingDestination& destination, int numTags); + std::shared_ptr GetRoutingSession (const i2p::data::RoutingDestination& destination, int numTags); I2NPMessage * WrapMessage (const i2p::data::RoutingDestination& destination, I2NPMessage * msg, bool attachLeaseSet = false); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread - void DeliveryStatusSent (GarlicRoutingSession * session, uint32_t msgID); + void DeliveryStatusSent (std::shared_ptr session, uint32_t msgID); virtual void ProcessGarlicMessage (I2NPMessage * msg); virtual void ProcessDeliveryStatusMessage (I2NPMessage * msg); @@ -135,12 +135,12 @@ namespace garlic // outgoing sessions std::mutex m_SessionsMutex; - std::map m_Sessions; + std::map > m_Sessions; // incoming std::map> m_Tags; uint32_t m_LastTagsCleanupTime; // DeliveryStatus - std::map m_CreatedSessions; // msgID -> session + std::map > m_CreatedSessions; // msgID -> session }; } } diff --git a/Streaming.cpp b/Streaming.cpp index 79296fe4..7c0e32f8 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -15,7 +15,7 @@ namespace stream const i2p::data::LeaseSet& remote, int port): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_IsOpen (false), m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local), - m_RemoteLeaseSet (&remote), m_RoutingSession (nullptr), m_CurrentOutboundTunnel (nullptr), + m_RemoteLeaseSet (&remote), m_CurrentOutboundTunnel (nullptr), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port) { @@ -26,7 +26,7 @@ namespace stream Stream::Stream (boost::asio::io_service& service, StreamingDestination& local): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_IsOpen (false), m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local), - m_RemoteLeaseSet (nullptr), m_RoutingSession (nullptr), m_CurrentOutboundTunnel (nullptr), + m_RemoteLeaseSet (nullptr), m_CurrentOutboundTunnel (nullptr), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0) { diff --git a/Streaming.h b/Streaming.h index 944e40de..9797a9ba 100644 --- a/Streaming.h +++ b/Streaming.h @@ -142,7 +142,7 @@ namespace stream StreamingDestination& m_LocalDestination; i2p::data::IdentityEx m_RemoteIdentity; const i2p::data::LeaseSet * m_RemoteLeaseSet; - i2p::garlic::GarlicRoutingSession * m_RoutingSession; + std::shared_ptr m_RoutingSession; i2p::data::Lease m_CurrentRemoteLease; i2p::tunnel::OutboundTunnel * m_CurrentOutboundTunnel; std::queue m_ReceiveQueue; From 3ed1fee7ce6e6c8ba0e1f636e4e5f2820c702499 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 22 Jan 2015 19:02:28 -0500 Subject: [PATCH 0084/6300] remove stream on close --- Streaming.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 7c0e32f8..753faf42 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -417,9 +417,9 @@ namespace stream p->len = size; m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p)); LogPrint ("FIN sent"); - m_ReceiveTimer.cancel (); - m_LocalDestination.DeleteStream (shared_from_this ()); } + m_ReceiveTimer.cancel (); + m_LocalDestination.DeleteStream (shared_from_this ()); } size_t Stream::ConcatenatePackets (uint8_t * buf, size_t len) From 0c73aff0a2f9a2b467f4cb0307130d6c469e02d9 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 22 Jan 2015 22:00:41 -0500 Subject: [PATCH 0085/6300] I2NPMessagesHandler --- I2NPProtocol.cpp | 28 ++++++++++++++++++++++++++++ I2NPProtocol.h | 13 +++++++++++++ NTCPSession.cpp | 3 ++- NTCPSession.h | 5 +++-- Queue.h | 12 ++++++++++++ Tunnel.cpp | 5 +++++ Tunnel.h | 1 + 7 files changed, 64 insertions(+), 3 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 51e566e7..c15ff95a 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -579,4 +579,32 @@ namespace i2p } } } + + I2NPMessagesHandler::~I2NPMessagesHandler () + { + Flush (); + } + + void I2NPMessagesHandler::PutNextMessage (I2NPMessage * msg) + { + if (msg) + { + if (msg->GetTypeID () == eI2NPTunnelData) + { + LogPrint ("TunnelData"); + m_TunnelMsgs.push_back (msg); + } + else + HandleI2NPMessage (msg); + } + } + + void I2NPMessagesHandler::Flush () + { + if (!m_TunnelMsgs.empty ()) + { + i2p::tunnel::tunnels.PostTunnelData (m_TunnelMsgs); + m_TunnelMsgs.clear (); + } + } } diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 8f8dddd3..f5509cbd 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -216,6 +216,19 @@ namespace tunnel size_t GetI2NPMessageLength (const uint8_t * msg); void HandleI2NPMessage (uint8_t * msg, size_t len); void HandleI2NPMessage (I2NPMessage * msg); + + class I2NPMessagesHandler + { + public: + + ~I2NPMessagesHandler (); + void PutNextMessage (I2NPMessage * msg); + void Flush (); + + private: + + std::vector m_TunnelMsgs; + }; } #endif diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 0a0a52fa..df1e9bc9 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -481,6 +481,7 @@ namespace transport } if (m_ReceiveBufferOffset > 0) memcpy (m_ReceiveBuffer, nextBlock, m_ReceiveBufferOffset); + m_Handler.Flush (); } ScheduleTermination (); // reset termination timer @@ -529,7 +530,7 @@ namespace transport if (m_NextMessageOffset >= m_NextMessage->len + 4) // +checksum { // we have a complete I2NP message - i2p::HandleI2NPMessage (m_NextMessage); + m_Handler.PutNextMessage (m_NextMessage); m_NextMessage = nullptr; } return true; diff --git a/NTCPSession.h b/NTCPSession.h index 6098a596..bf2cb17e 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -42,7 +42,7 @@ namespace transport #pragma pack() const size_t NTCP_MAX_MESSAGE_SIZE = 16384; - const size_t NTCP_BUFFER_SIZE = 1040; // fits one tunnel message (1028) + const size_t NTCP_BUFFER_SIZE = 4160; // fits 4 tunnel messages (4*1028) const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes const size_t NTCP_DEFAULT_PHASE3_SIZE = 2/*size*/ + i2p::data::DEFAULT_IDENTITY_SIZE/*387*/ + 4/*ts*/ + 15/*padding*/ + 40/*signature*/; // 448 @@ -131,7 +131,8 @@ namespace transport i2p::I2NPMessage * m_NextMessage; size_t m_NextMessageOffset; - + i2p::I2NPMessagesHandler m_Handler; + size_t m_NumSentBytes, m_NumReceivedBytes; }; diff --git a/Queue.h b/Queue.h index f98d7178..2749da6a 100644 --- a/Queue.h +++ b/Queue.h @@ -2,6 +2,7 @@ #define QUEUE_H__ #include +#include #include #include #include @@ -23,6 +24,17 @@ namespace util m_NonEmpty.notify_one (); } + void Put (const std::vector& vec) + { + if (!vec.empty ()) + { + std::unique_lock l(m_QueueMutex); + for (auto it: vec) + m_Queue.push (it); + m_NonEmpty.notify_one (); + } + } + Element * GetNext () { std::unique_lock l(m_QueueMutex); diff --git a/Tunnel.cpp b/Tunnel.cpp index 5eb461ff..8ce376a4 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -576,6 +576,11 @@ namespace tunnel if (msg) m_Queue.Put (msg); } + void Tunnels::PostTunnelData (const std::vector& msgs) + { + m_Queue.Put (msgs); + } + template TTunnel * Tunnels::CreateTunnel (TunnelConfig * config, OutboundTunnel * outboundTunnel) { diff --git a/Tunnel.h b/Tunnel.h index 91d6330a..4925df18 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -130,6 +130,7 @@ namespace tunnel void AddOutboundTunnel (OutboundTunnel * newTunnel); void AddInboundTunnel (InboundTunnel * newTunnel); void PostTunnelData (I2NPMessage * msg); + void PostTunnelData (const std::vector& msgs); template TTunnel * CreateTunnel (TunnelConfig * config, OutboundTunnel * outboundTunnel = 0); std::shared_ptr CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOuboundHops); From a25646a129ef82e0551ad9aef232cad7d4ed0b71 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 23 Jan 2015 10:07:11 -0500 Subject: [PATCH 0086/6300] cleanup routing sessions --- Garlic.cpp | 39 ++++++++++++++++++++++++++++++++++++--- Garlic.h | 3 +++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index a0a6747e..fea0b5af 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -67,18 +67,34 @@ namespace garlic m_UnconfirmedTagsMsgs.erase (it); delete tags; } + CleanupExpiredTags (); + } + + bool GarlicRoutingSession::CleanupExpiredTags () + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_SessionTags.begin (); it != m_SessionTags.end ();) + { + if (ts >= it->creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) + it = m_SessionTags.erase (it); + else + it++; + } // delete expired unconfirmed tags for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();) { if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) { + if (m_Owner) + m_Owner->RemoveCreatedSession (it->first); delete it->second; it = m_UnconfirmedTagsMsgs.erase (it); } else it++; } - } + return !m_SessionTags.empty () || m_UnconfirmedTagsMsgs.empty (); + } I2NPMessage * GarlicRoutingSession::WrapSingleMessage (I2NPMessage * msg) { @@ -149,7 +165,7 @@ namespace garlic size_t GarlicRoutingSession::CreateAESBlock (uint8_t * buf, const I2NPMessage * msg) { size_t blockSize = 0; - bool createNewTags = m_Owner && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags/2); + bool createNewTags = m_Owner && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags*2/3); UnconfirmedTags * newTags = createNewTags ? GenerateSessionTags () : nullptr; htobuf16 (buf, newTags ? htobe16 (newTags->numTags) : 0); // tag count blockSize += 2; @@ -515,7 +531,24 @@ namespace garlic } return session; } - + + void GarlicDestination::CleanupRoutingSessions () + { + std::unique_lock l(m_SessionsMutex); + for (auto it = m_Sessions.begin (); it != m_Sessions.end ();) + { + if (!it->second->CleanupExpiredTags ()) + it = m_Sessions.erase (it); + else + it++; + } + } + + void GarlicDestination::RemoveCreatedSession (uint32_t msgID) + { + m_CreatedSessions.erase (msgID); + } + void GarlicDestination::DeliveryStatusSent (std::shared_ptr session, uint32_t msgID) { m_CreatedSessions[msgID] = session; diff --git a/Garlic.h b/Garlic.h index dc18ade3..523e57ef 100644 --- a/Garlic.h +++ b/Garlic.h @@ -72,6 +72,7 @@ namespace garlic ~GarlicRoutingSession (); I2NPMessage * WrapSingleMessage (I2NPMessage * msg); void TagsConfirmed (uint32_t msgID); + bool CleanupExpiredTags (); // returns true if something left void SetLeaseSetUpdated () { m_LeaseSetUpdated = true; }; @@ -106,6 +107,8 @@ namespace garlic ~GarlicDestination (); std::shared_ptr GetRoutingSession (const i2p::data::RoutingDestination& destination, int numTags); + void CleanupRoutingSessions (); + void RemoveCreatedSession (uint32_t msgID); I2NPMessage * WrapMessage (const i2p::data::RoutingDestination& destination, I2NPMessage * msg, bool attachLeaseSet = false); From 24c00b09857233771b7615ca47a716e40f42c658 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 23 Jan 2015 12:48:25 -0500 Subject: [PATCH 0087/6300] schedule routing session cleanup --- Destination.cpp | 18 +++++++++++++++++- Destination.h | 4 +++- Garlic.cpp | 3 +++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index a27ddba1..23567817 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -17,7 +17,7 @@ namespace client const std::map * params): m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_Keys (keys), m_LeaseSet (nullptr), m_IsPublic (isPublic), m_PublishReplyToken (0), - m_DatagramDestination (nullptr), m_PublishConfirmationTimer (m_Service) + m_DatagramDestination (nullptr), m_PublishConfirmationTimer (m_Service), m_CleanupTimer (m_Service) { i2p::crypto::GenerateElGamalKeyPair(i2p::context.GetRandomNumberGenerator (), m_EncryptionPrivateKey, m_EncryptionPublicKey); int inboundTunnelLen = DEFAULT_INBOUND_TUNNEL_LENGTH; @@ -91,6 +91,10 @@ namespace client m_Pool->SetActive (true); m_Thread = new std::thread (std::bind (&ClientDestination::Run, this)); m_StreamingDestination->Start (); + + m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); + m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer, + this, std::placeholders::_1)); } } @@ -98,6 +102,7 @@ namespace client { if (m_IsRunning) { + m_CleanupTimer.cancel (); m_IsRunning = false; m_StreamingDestination->Stop (); if (m_DatagramDestination) @@ -556,6 +561,17 @@ namespace client } } } + } + + void ClientDestination::HandleCleanupTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + CleanupRoutingSessions (); + m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); + m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer, + this, std::placeholders::_1)); + } } } } diff --git a/Destination.h b/Destination.h index ffb96d2a..4104c426 100644 --- a/Destination.h +++ b/Destination.h @@ -29,6 +29,7 @@ namespace client const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds const int MAX_NUM_FLOODFILLS_PER_REQUEST = 7; + const int DESTINATION_CLEANUP_TIMEOUT = 20; // in minutes // I2CP const char I2CP_PARAM_INBOUND_TUNNEL_LENGTH[] = "inbound.length"; @@ -109,6 +110,7 @@ namespace client void RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete); bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, LeaseSetRequest * request); void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); + void HandleCleanupTimer (const boost::system::error_code& ecode); private: @@ -130,7 +132,7 @@ namespace client i2p::stream::StreamingDestination * m_StreamingDestination; i2p::datagram::DatagramDestination * m_DatagramDestination; - boost::asio::deadline_timer m_PublishConfirmationTimer; + boost::asio::deadline_timer m_PublishConfirmationTimer, m_CleanupTimer; public: diff --git a/Garlic.cpp b/Garlic.cpp index fea0b5af..a4ca69cd 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -538,7 +538,10 @@ namespace garlic for (auto it = m_Sessions.begin (); it != m_Sessions.end ();) { if (!it->second->CleanupExpiredTags ()) + { + LogPrint (eLogInfo, "Routing session to ", it->first.ToBase32 (), " deleted"); it = m_Sessions.erase (it); + } else it++; } From aeb3c8b8b9446f10350d458ee4d8892a4d39fba1 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 23 Jan 2015 14:02:37 -0500 Subject: [PATCH 0088/6300] Update LICENSE name changed to PurpleI2P --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 43610a26..2cb10225 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2015, The PrivacySolutions Project +Copyright (c) 2013-2015, The PurpleI2P Project All rights reserved. @@ -24,4 +24,4 @@ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 33bce67a4ee3f381ab61b112d2281bb1ef8d5440 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 23 Jan 2015 14:03:41 -0500 Subject: [PATCH 0089/6300] Update copyright name changed to PurpleI2P --- debian/copyright | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/debian/copyright b/debian/copyright index 4dbbc6a4..01fef78e 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,11 +1,11 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: i2pd -Source: https://github.com/PrivacySolutions +Source: https://github.com/PurpleI2P Files: * -Copyright: 2013-2014 PrivacySolutions +Copyright: 2013-2014 PurpleI2P License: BSD-3-clause -Copyright (c) 2013-2015, The PrivacySolutions Project +Copyright (c) 2013-2015, The PurpleI2P Project All rights reserved. From 82103e6a3912e00c0465a641c0b8bba14038a9a8 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 23 Jan 2015 16:26:39 -0500 Subject: [PATCH 0090/6300] process TunnelGateway message in tunnel thread --- I2NPProtocol.cpp | 31 +------------------------------ I2NPProtocol.h | 1 - TransitTunnel.h | 2 +- Tunnel.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++--- Tunnel.h | 3 +++ TunnelBase.h | 1 + 6 files changed, 48 insertions(+), 35 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index c15ff95a..d0466e75 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -465,35 +465,6 @@ namespace i2p FillI2NPMessageHeader (msg, eI2NPTunnelGateway); // gateway message return msg; } - - void HandleTunnelGatewayMsg (I2NPMessage * msg) - { - const uint8_t * payload = msg->GetPayload (); - uint32_t tunnelID = bufbe32toh(payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET); - uint16_t len = bufbe16toh(payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET); - // we make payload as new I2NP message to send - msg->offset += I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE; - msg->len = msg->offset + len; - auto typeID = msg->GetTypeID (); - LogPrint ("TunnelGateway of ", (int)len, " bytes for tunnel ", (unsigned int)tunnelID, ". Msg type ", (int)typeID); - - if (typeID == eI2NPDatabaseStore || typeID == eI2NPDatabaseSearchReply) - { - // transit DatabaseStore my contain new/updated RI - // or DatabaseSearchReply with new routers - auto ds = NewI2NPMessage (); - *ds = *msg; - i2p::data::netdb.PostI2NPMsg (ds); - } - i2p::tunnel::TransitTunnel * tunnel = i2p::tunnel::tunnels.GetTransitTunnel (tunnelID); - if (tunnel) - tunnel->SendTunnelDataMsg (msg); - else - { - LogPrint ("Tunnel ", (unsigned int)tunnelID, " not found"); - i2p::DeleteI2NPMessage (msg); - } - } size_t GetI2NPMessageLength (const uint8_t * msg) { @@ -543,7 +514,7 @@ namespace i2p break; case eI2NPTunnelGateway: LogPrint ("TunnelGateway"); - HandleTunnelGatewayMsg (msg); + i2p::tunnel::tunnels.PostTunnelData (msg); break; case eI2NPGarlic: LogPrint ("Garlic"); diff --git a/I2NPProtocol.h b/I2NPProtocol.h index f5509cbd..137f649f 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -207,7 +207,6 @@ namespace tunnel I2NPMessage * CreateTunnelDataMsg (const uint8_t * buf); I2NPMessage * CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload); - void HandleTunnelGatewayMsg (I2NPMessage * msg); I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len); I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0); diff --git a/TransitTunnel.h b/TransitTunnel.h index 93b72034..1ac3be93 100644 --- a/TransitTunnel.h +++ b/TransitTunnel.h @@ -22,12 +22,12 @@ namespace tunnel const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey); - virtual void SendTunnelDataMsg (i2p::I2NPMessage * msg); virtual size_t GetNumTransmittedBytes () const { return 0; }; uint32_t GetTunnelID () const { return m_TunnelID; }; // implements TunnelBase + void SendTunnelDataMsg (i2p::I2NPMessage * msg); void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg); void EncryptTunnelMsg (I2NPMessage * tunnelMsg); uint32_t GetNextTunnelID () const { return m_NextTunnelID; }; diff --git a/Tunnel.cpp b/Tunnel.cpp index 8ce376a4..efa0e8db 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -150,7 +150,13 @@ namespace tunnel hop = hop->prev; } } - + + void Tunnel::SendTunnelDataMsg (i2p::I2NPMessage * msg) + { + LogPrint (eLogInfo, "Can't send I2NP messages without delivery instructions"); + DeleteI2NPMessage (msg); + } + void InboundTunnel::HandleTunnelDataMsg (I2NPMessage * msg) { if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive @@ -349,6 +355,7 @@ namespace tunnel I2NPMessage * msg = m_Queue.GetNextWithTimeout (1000); // 1 sec if (msg) { + uint8_t typeID = msg->GetTypeID (); uint32_t prevTunnelID = 0; TunnelBase * prevTunnel = nullptr; do @@ -360,12 +367,17 @@ namespace tunnel else if (prevTunnel) prevTunnel->FlushTunnelDataMsgs (); - if (!tunnel) + if (!tunnel && typeID == eI2NPTunnelData) tunnel = GetInboundTunnel (tunnelID); if (!tunnel) tunnel = GetTransitTunnel (tunnelID); if (tunnel) - tunnel->HandleTunnelDataMsg (msg); + { + if (typeID == eI2NPTunnelData) + tunnel->HandleTunnelDataMsg (msg); + else // tunnel gateway assumed + HandleTunnelGatewayMsg (tunnel, msg); + } else { LogPrint ("Tunnel ", tunnelID, " not found"); @@ -398,6 +410,33 @@ namespace tunnel } } + void Tunnels::HandleTunnelGatewayMsg (TunnelBase * tunnel, I2NPMessage * msg) + { + if (!tunnel) + { + LogPrint (eLogError, "Missing tunnel for TunnelGateway"); + i2p::DeleteI2NPMessage (msg); + return; + } + const uint8_t * payload = msg->GetPayload (); + uint16_t len = bufbe16toh(payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET); + // we make payload as new I2NP message to send + msg->offset += I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE; + msg->len = msg->offset + len; + auto typeID = msg->GetTypeID (); + LogPrint (eLogDebug, "TunnelGateway of ", (int)len, " bytes for tunnel ", tunnel->GetTunnelID (), ". Msg type ", (int)typeID); + + if (typeID == eI2NPDatabaseStore || typeID == eI2NPDatabaseSearchReply) + { + // transit DatabaseStore my contain new/updated RI + // or DatabaseSearchReply with new routers + auto ds = NewI2NPMessage (); + *ds = *msg; + i2p::data::netdb.PostI2NPMsg (ds); + } + tunnel->SendTunnelDataMsg (msg); + } + void Tunnels::ManageTunnels () { ManagePendingTunnels (); diff --git a/Tunnel.h b/Tunnel.h index 4925df18..6f7cf709 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -61,6 +61,7 @@ namespace tunnel bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); // implements TunnelBase + void SendTunnelDataMsg (i2p::I2NPMessage * msg); void EncryptTunnelMsg (I2NPMessage * tunnelMsg); uint32_t GetNextTunnelID () const { return m_Config->GetFirstHop ()->tunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return m_Config->GetFirstHop ()->router->GetIdentHash (); }; @@ -139,6 +140,8 @@ namespace tunnel private: + void HandleTunnelGatewayMsg (TunnelBase * tunnel, I2NPMessage * msg); + void Run (); void ManageTunnels (); void ManageOutboundTunnels (); diff --git a/TunnelBase.h b/TunnelBase.h index 8be6302c..dbb9c361 100644 --- a/TunnelBase.h +++ b/TunnelBase.h @@ -37,6 +37,7 @@ namespace tunnel virtual ~TunnelBase () {}; virtual void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) = 0; + virtual void SendTunnelDataMsg (i2p::I2NPMessage * msg) = 0; virtual void FlushTunnelDataMsgs () {}; virtual void EncryptTunnelMsg (I2NPMessage * tunnelMsg) = 0; virtual uint32_t GetNextTunnelID () const = 0; From 1dc166f0f8334b935fa1070cb4a438753f882cf8 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 23 Jan 2015 22:05:33 -0500 Subject: [PATCH 0091/6300] transit tunnel gateway batching --- I2NPProtocol.cpp | 23 ++++++++++++++++------- I2NPProtocol.h | 2 +- TransitTunnel.cpp | 8 +++++++- TransitTunnel.h | 1 + 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index d0466e75..dc058d33 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -560,13 +560,17 @@ namespace i2p { if (msg) { - if (msg->GetTypeID () == eI2NPTunnelData) - { - LogPrint ("TunnelData"); - m_TunnelMsgs.push_back (msg); - } - else - HandleI2NPMessage (msg); + switch (msg->GetTypeID ()) + { + case eI2NPTunnelData: + LogPrint ("TunnelData"); + m_TunnelMsgs.push_back (msg); + break; + LogPrint ("TunnelGateway"); + m_TunnelGatewayMsgs.push_back (msg); + default: + HandleI2NPMessage (msg); + } } } @@ -577,5 +581,10 @@ namespace i2p i2p::tunnel::tunnels.PostTunnelData (m_TunnelMsgs); m_TunnelMsgs.clear (); } + if (!m_TunnelGatewayMsgs.empty ()) + { + i2p::tunnel::tunnels.PostTunnelData (m_TunnelMsgs); + m_TunnelGatewayMsgs.clear (); + } } } diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 137f649f..9ae3998d 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -226,7 +226,7 @@ namespace tunnel private: - std::vector m_TunnelMsgs; + std::vector m_TunnelMsgs, m_TunnelGatewayMsgs; }; } diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index 66120c51..c1657930 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -70,9 +70,15 @@ namespace tunnel block.deliveryType = eDeliveryTypeLocal; block.data = msg; std::unique_lock l(m_SendMutex); - m_Gateway.SendTunnelDataMsg (block); + m_Gateway.PutTunnelDataMsg (block); } + void TransitTunnelGateway::FlushTunnelDataMsgs () + { + LogPrint (eLogDebug, "TransitTunnel: gateway flush"); + m_Gateway.SendBuffer (); + } + void TransitTunnelEndpoint::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) { EncryptTunnelMsg (tunnelMsg); diff --git a/TransitTunnel.h b/TransitTunnel.h index 1ac3be93..a2a0347f 100644 --- a/TransitTunnel.h +++ b/TransitTunnel.h @@ -73,6 +73,7 @@ namespace tunnel layerKey, ivKey), m_Gateway(this) {}; void SendTunnelDataMsg (i2p::I2NPMessage * msg); + void FlushTunnelDataMsgs (); size_t GetNumTransmittedBytes () const { return m_Gateway.GetNumSentBytes (); }; private: From 588c61304343868f94b123b56a8f7e2d6e57e59a Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 24 Jan 2015 15:34:46 -0500 Subject: [PATCH 0092/6300] naming lookup of .b32 address through LeaseSet request --- SAM.cpp | 48 +++++++++++++++++++++++++++++++++++++----------- SAM.h | 4 ++-- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index 0432b245..fbcc1791 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -326,7 +326,7 @@ namespace client else { m_Session->localDestination->RequestDestination (dest.GetIdentHash (), - std::bind (&SAMSocket::HandleLeaseSetRequestComplete, + std::bind (&SAMSocket::HandleConnectLeaseSetRequestComplete, shared_from_this (), std::placeholders::_1, dest.GetIdentHash ())); } } @@ -344,7 +344,7 @@ namespace client SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } - void SAMSocket::HandleLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident) + void SAMSocket::HandleConnectLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident) { const i2p::data::LeaseSet * leaseSet = nullptr; if (success) // timeout expired @@ -409,14 +409,25 @@ namespace client std::map params; ExtractParams (buf, len, params); std::string& name = params[SAM_PARAM_NAME]; - i2p::data::IdentHash ident; i2p::data::IdentityEx identity; + i2p::data::IdentHash ident; if (name == "ME") - SendNamingLookupReply (nullptr); + SendNamingLookupReply (m_Session->localDestination->GetIdentity ()); else if (context.GetAddressBook ().GetAddress (name, identity)) SendNamingLookupReply (identity); + else if (context.GetAddressBook ().GetIdentHash (name, ident)) + { + auto leaseSet = i2p::data::netdb.FindLeaseSet (ident); + if (leaseSet) + SendNamingLookupReply (leaseSet->GetIdentity ()); + else + m_Session->localDestination->RequestDestination (ident, + std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, + shared_from_this (), std::placeholders::_1, ident)); + } else { + LogPrint ("SAM naming failed. Unknown address ", name); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #else @@ -426,15 +437,30 @@ namespace client } } - void SAMSocket::SendNamingLookupReply (const i2p::data::LeaseSet * leaseSet) + void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident) { - const i2p::data::IdentityEx& identity = leaseSet ? leaseSet->GetIdentity () : m_Session->localDestination->GetIdentity (); + const i2p::data::LeaseSet * leaseSet = nullptr; + if (success) + leaseSet = m_Session->localDestination->FindLeaseSet (ident); if (leaseSet) - // we found LeaseSet for our address, store it to addressbook - context.GetAddressBook ().InsertAddress (identity); - SendNamingLookupReply (identity); - } - + { + context.GetAddressBook ().InsertAddress (leaseSet->GetIdentity ()); + SendNamingLookupReply (leaseSet->GetIdentity ()); + } + else + { + LogPrint (eLogInfo, "SAM naming lookup failed. LeaseSet for ", ident.ToBase32 (), " not found"); +#ifdef _MSC_VER + size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, + context.GetAddressBook ().ToAddress (ident).c_str()); +#else + size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, + context.GetAddressBook ().ToAddress (ident).c_str()); +#endif + SendMessageReply (m_Buffer, len, false); + } + } + void SAMSocket::SendNamingLookupReply (const i2p::data::IdentityEx& identity) { auto base64 = identity.ToBase64 (); diff --git a/SAM.h b/SAM.h index 348070e6..e6de9102 100644 --- a/SAM.h +++ b/SAM.h @@ -105,9 +105,9 @@ namespace client void ExtractParams (char * buf, size_t len, std::map& params); void Connect (const i2p::data::LeaseSet& remote); - void HandleLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident); - void SendNamingLookupReply (const i2p::data::LeaseSet * leaseSet); + void HandleConnectLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident); void SendNamingLookupReply (const i2p::data::IdentityEx& identity); + void HandleNamingLookupLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident); void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode); void SendSessionCreateReplyOk (); From b8740c008bbbfe8b26787a32e2e07e821b578aac Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 25 Jan 2015 10:05:19 -0500 Subject: [PATCH 0093/6300] fixed crash --- SAM.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SAM.cpp b/SAM.cpp index fbcc1791..d8386fcc 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -415,7 +415,8 @@ namespace client SendNamingLookupReply (m_Session->localDestination->GetIdentity ()); else if (context.GetAddressBook ().GetAddress (name, identity)) SendNamingLookupReply (identity); - else if (context.GetAddressBook ().GetIdentHash (name, ident)) + else if (m_Session && m_Session->localDestination && + context.GetAddressBook ().GetIdentHash (name, ident)) { auto leaseSet = i2p::data::netdb.FindLeaseSet (ident); if (leaseSet) From 9c23d03d8d101c36009605a7aff726031dcf52a2 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 25 Jan 2015 10:05:50 -0500 Subject: [PATCH 0094/6300] fixed typo --- I2NPProtocol.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index dc058d33..f9b11bcb 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -566,8 +566,10 @@ namespace i2p LogPrint ("TunnelData"); m_TunnelMsgs.push_back (msg); break; + case eI2NPTunnelGateway: LogPrint ("TunnelGateway"); m_TunnelGatewayMsgs.push_back (msg); + break; default: HandleI2NPMessage (msg); } From 724c417f09b98ef0ddf7142a0d28ef87ddfa9762 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 25 Jan 2015 11:43:27 -0500 Subject: [PATCH 0095/6300] fixed typo --- I2NPProtocol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index f9b11bcb..b560daa3 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -585,7 +585,7 @@ namespace i2p } if (!m_TunnelGatewayMsgs.empty ()) { - i2p::tunnel::tunnels.PostTunnelData (m_TunnelMsgs); + i2p::tunnel::tunnels.PostTunnelData (m_TunnelGatewayMsgs); m_TunnelGatewayMsgs.clear (); } } From a061f3339e4965297139bf497244a2244404a3bf Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 25 Jan 2015 16:18:26 -0500 Subject: [PATCH 0096/6300] use send buffer for a stream --- Streaming.cpp | 88 +++++++++++++++++++++++++++++++++++++++++++++++++-- Streaming.h | 6 ++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 753faf42..92f30ac2 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -248,7 +248,14 @@ namespace stream size_t Stream::Send (const uint8_t * buf, size_t len) { - bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet + if (len > 0 && buf) + { + std::unique_lock l(m_SendBufferMutex); + m_SendBuffer.clear (); + m_SendBuffer.write ((const char *)buf, len); + } + m_Service.post (std::bind (&Stream::SendBuffer, shared_from_this ())); + /*bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet std::vector packets; while (!m_IsOpen || len > 0) { @@ -317,10 +324,87 @@ namespace stream packets.push_back (p); } if (packets.size () > 0) - m_Service.post (std::bind (&Stream::PostPackets, shared_from_this (), packets)); + m_Service.post (std::bind (&Stream::PostPackets, shared_from_this (), packets));*/ return len; } + void Stream::SendBuffer () + { + bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet + std::vector packets; + { + std::unique_lock l(m_SendBufferMutex); + while (!m_IsOpen || !m_SendBuffer.eof ()) + { + Packet * p = new Packet (); + uint8_t * packet = p->GetBuffer (); + // TODO: implement setters + size_t size = 0; + htobe32buf (packet + size, m_SendStreamID); + size += 4; // sendStreamID + htobe32buf (packet + size, m_RecvStreamID); + size += 4; // receiveStreamID + htobe32buf (packet + size, m_SequenceNumber++); + size += 4; // sequenceNum + if (isNoAck) + htobe32buf (packet + size, m_LastReceivedSequenceNumber); + else + htobuf32 (packet + size, 0); + size += 4; // ack Through + packet[size] = 0; + size++; // NACK count + packet[size] = RESEND_TIMEOUT; + size++; // resend delay + if (!m_IsOpen) + { + // initial packet + m_IsOpen = true; m_IsReset = false; + uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED | + PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED; + if (isNoAck) flags |= PACKET_FLAG_NO_ACK; + htobe16buf (packet + size, flags); + size += 2; // flags + size_t identityLen = m_LocalDestination.GetOwner ().GetIdentity ().GetFullLen (); + size_t signatureLen = m_LocalDestination.GetOwner ().GetIdentity ().GetSignatureLen (); + htobe16buf (packet + size, identityLen + signatureLen + 2); // identity + signature + packet size + size += 2; // options size + m_LocalDestination.GetOwner ().GetIdentity ().ToBuffer (packet + size, identityLen); + size += identityLen; // from + htobe16buf (packet + size, STREAMING_MTU); + size += 2; // max packet size + uint8_t * signature = packet + size; // set it later + memset (signature, 0, signatureLen); // zeroes for now + size += signatureLen; // signature + m_SendBuffer.read ((char *)(packet + size), STREAMING_MTU - size); + size += m_SendBuffer.gcount (); // payload + m_LocalDestination.GetOwner ().Sign (packet, size, signature); + } + else + { + // follow on packet + htobuf16 (packet + size, 0); + size += 2; // flags + htobuf16 (packet + size, 0); // no options + size += 2; // options size + m_SendBuffer.read((char *)(packet + size), STREAMING_MTU - size); + size += m_SendBuffer.gcount (); // payload + } + p->len = size; + packets.push_back (p); + } + } + if (packets.size () > 0) + { + m_IsAckSendScheduled = false; + m_AckSendTimer.cancel (); + bool isEmpty = m_SentPackets.empty (); + for (auto it: packets) + m_SentPackets.insert (it); + SendPackets (packets); + if (isEmpty) + ScheduleResend (); + } + } void Stream::SendQuickAck () { diff --git a/Streaming.h b/Streaming.h index 9797a9ba..ab94febf 100644 --- a/Streaming.h +++ b/Streaming.h @@ -3,11 +3,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include "I2PEndian.h" #include "Identity.h" @@ -112,6 +114,7 @@ namespace stream private: + void SendBuffer (); void SendQuickAck (); bool SendPacket (Packet * packet); void PostPackets (const std::vector packets); @@ -151,6 +154,9 @@ namespace stream boost::asio::deadline_timer m_ReceiveTimer, m_ResendTimer, m_AckSendTimer; size_t m_NumSentBytes, m_NumReceivedBytes; uint16_t m_Port; + + std::mutex m_SendBufferMutex; + std::stringstream m_SendBuffer; }; class StreamingDestination From f75de6af82d5fc525d797518f9cf30342a8d7a16 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 25 Jan 2015 17:43:34 -0500 Subject: [PATCH 0097/6300] window --- Streaming.cpp | 108 +++++--------------------------------------------- Streaming.h | 2 +- 2 files changed, 12 insertions(+), 98 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 92f30ac2..f6b50176 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -211,6 +211,7 @@ namespace stream void Stream::ProcessAck (Packet * packet) { + bool acknowledged = false; uint32_t ackThrough = packet->GetAckThrough (); int nackCount = packet->GetNACKCount (); for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();) @@ -238,12 +239,15 @@ namespace stream LogPrint (eLogDebug, "Packet ", seqn, " acknowledged"); m_SentPackets.erase (it++); delete sentPacket; + acknowledged = true; } else break; } if (m_SentPackets.empty ()) m_ResendTimer.cancel (); + if (acknowledged) + SendBuffer (); } size_t Stream::Send (const uint8_t * buf, size_t len) @@ -255,86 +259,19 @@ namespace stream m_SendBuffer.write ((const char *)buf, len); } m_Service.post (std::bind (&Stream::SendBuffer, shared_from_this ())); - /*bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet - std::vector packets; - while (!m_IsOpen || len > 0) - { - Packet * p = new Packet (); - uint8_t * packet = p->GetBuffer (); - // TODO: implement setters - size_t size = 0; - htobe32buf (packet + size, m_SendStreamID); - size += 4; // sendStreamID - htobe32buf (packet + size, m_RecvStreamID); - size += 4; // receiveStreamID - htobe32buf (packet + size, m_SequenceNumber++); - size += 4; // sequenceNum - if (isNoAck) - htobe32buf (packet + size, m_LastReceivedSequenceNumber); - else - htobuf32 (packet + size, 0); - size += 4; // ack Through - packet[size] = 0; - size++; // NACK count - packet[size] = RESEND_TIMEOUT; - size++; // resend delay - if (!m_IsOpen) - { - // initial packet - m_IsOpen = true; m_IsReset = false; - uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED | - PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED; - if (isNoAck) flags |= PACKET_FLAG_NO_ACK; - htobe16buf (packet + size, flags); - size += 2; // flags - size_t identityLen = m_LocalDestination.GetOwner ().GetIdentity ().GetFullLen (); - size_t signatureLen = m_LocalDestination.GetOwner ().GetIdentity ().GetSignatureLen (); - htobe16buf (packet + size, identityLen + signatureLen + 2); // identity + signature + packet size - size += 2; // options size - m_LocalDestination.GetOwner ().GetIdentity ().ToBuffer (packet + size, identityLen); - size += identityLen; // from - htobe16buf (packet + size, STREAMING_MTU); - size += 2; // max packet size - uint8_t * signature = packet + size; // set it later - memset (signature, 0, signatureLen); // zeroes for now - size += signatureLen; // signature - size_t sentLen = STREAMING_MTU - size; - if (len < sentLen) sentLen = len; - memcpy (packet + size, buf, sentLen); - buf += sentLen; - len -= sentLen; - size += sentLen; // payload - m_LocalDestination.GetOwner ().Sign (packet, size, signature); - } - else - { - // follow on packet - htobuf16 (packet + size, 0); - size += 2; // flags - htobuf16 (packet + size, 0); // no options - size += 2; // options size - size_t sentLen = STREAMING_MTU - size; - if (len < sentLen) sentLen = len; - memcpy (packet + size, buf, sentLen); - buf += sentLen; - len -= sentLen; - size += sentLen; // payload - } - p->len = size; - packets.push_back (p); - } - if (packets.size () > 0) - m_Service.post (std::bind (&Stream::PostPackets, shared_from_this (), packets));*/ return len; } void Stream::SendBuffer () - { + { + int numMsgs = WINDOW_SIZE - m_SentPackets.size (); + if (numMsgs <= 0) return; // window is full + bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet std::vector packets; { std::unique_lock l(m_SendBufferMutex); - while (!m_IsOpen || !m_SendBuffer.eof ()) + while (!m_IsOpen || (IsEstablished () && !m_SendBuffer.eof () && numMsgs > 0)) { Packet * p = new Packet (); uint8_t * packet = p->GetBuffer (); @@ -391,6 +328,7 @@ namespace stream } p->len = size; packets.push_back (p); + numMsgs--; } } if (packets.size () > 0) @@ -549,30 +487,6 @@ namespace stream else return false; } - - void Stream::PostPackets (const std::vector packets) - { - if (m_IsOpen) - { - if (packets.size () > 0) - { - m_IsAckSendScheduled = false; - m_AckSendTimer.cancel (); - } - bool isEmpty = m_SentPackets.empty (); - for (auto it: packets) - m_SentPackets.insert (it); - SendPackets (packets); - if (isEmpty) - ScheduleResend (); - } - else - { - // delete - for (auto it: packets) - delete it; - } - } void Stream::SendPackets (const std::vector& packets) { @@ -635,7 +549,7 @@ namespace stream packets.push_back (it); else { - LogPrint (eLogWarning, "Packet ", it->GetSeqn (), "was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts. Terminate"); + LogPrint (eLogWarning, "Packet ", it->GetSeqn (), " was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts. Terminate"); m_IsOpen = false; m_IsReset = true; m_ReceiveTimer.cancel (); diff --git a/Streaming.h b/Streaming.h index ab94febf..83514966 100644 --- a/Streaming.h +++ b/Streaming.h @@ -44,6 +44,7 @@ namespace stream const int RESEND_TIMEOUT = 10; // in seconds const int ACK_SEND_TIMEOUT = 200; // in milliseconds const int MAX_NUM_RESEND_ATTEMPTS = 5; + const int WINDOW_SIZE = 6; // in messages struct Packet { @@ -117,7 +118,6 @@ namespace stream void SendBuffer (); void SendQuickAck (); bool SendPacket (Packet * packet); - void PostPackets (const std::vector packets); void SendPackets (const std::vector& packets); void SavePacket (Packet * packet); From 8a478e4616fc92c7fd96750da8c503a2a9af9839 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 25 Jan 2015 22:01:09 -0500 Subject: [PATCH 0098/6300] check for max number of NACKs --- Streaming.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Streaming.cpp b/Streaming.cpp index f6b50176..680444b6 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -378,6 +378,12 @@ namespace stream for (auto it: m_SavedPackets) { auto seqn = it->GetSeqn (); + if (numNacks + (seqn - nextSeqn) >= 256) + { + LogPrint (eLogError, "Number of NACKs exceeds 256"); + htobe32buf (packet + 12, nextSeqn); // change ack Through + break; + } for (uint32_t i = nextSeqn; i < seqn; i++) { htobe32buf (nacks, i); From 8f562215b052bedb92d4d38854163d4e87263b66 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 26 Jan 2015 11:56:10 -0500 Subject: [PATCH 0099/6300] separate inbound and outbound pending tunnels --- I2NPProtocol.cpp | 8 ++++---- Tunnel.cpp | 53 ++++++++++++++++++++++++++++++++++++++---------- Tunnel.h | 15 +++++++++++--- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index b560daa3..638122b5 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -312,7 +312,7 @@ namespace i2p int num = buf[0]; LogPrint ("VariableTunnelBuild ", num, " records"); - i2p::tunnel::Tunnel * tunnel = i2p::tunnel::tunnels.GetPendingTunnel (replyMsgID); + auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); if (tunnel) { // endpoint of inbound tunnel @@ -321,7 +321,7 @@ namespace i2p { LogPrint ("Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); - i2p::tunnel::tunnels.AddInboundTunnel (static_cast(tunnel)); + i2p::tunnel::tunnels.AddInboundTunnel (tunnel); } else { @@ -373,7 +373,7 @@ namespace i2p void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) { LogPrint ("VariableTunnelBuildReplyMsg replyMsgID=", replyMsgID); - i2p::tunnel::Tunnel * tunnel = i2p::tunnel::tunnels.GetPendingTunnel (replyMsgID); + auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID); if (tunnel) { // reply for outbound tunnel @@ -381,7 +381,7 @@ namespace i2p { LogPrint ("Outbound tunnel ", tunnel->GetTunnelID (), " has been created"); tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); - i2p::tunnel::tunnels.AddOutboundTunnel (static_cast(tunnel)); + i2p::tunnel::tunnels.AddOutboundTunnel (tunnel); } else { diff --git a/Tunnel.cpp b/Tunnel.cpp index efa0e8db..3027bcad 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -222,10 +222,13 @@ namespace tunnel m_TransitTunnels.clear (); ManagePendingTunnels (); - for (auto& it : m_PendingTunnels) + for (auto& it : m_PendingInboundTunnels) delete it.second; - m_PendingTunnels.clear (); + m_PendingInboundTunnels.clear (); + for (auto& it : m_PendingOutboundTunnels) + delete it.second; + m_PendingOutboundTunnels.clear (); } InboundTunnel * Tunnels::GetInboundTunnel (uint32_t tunnelID) @@ -243,11 +246,22 @@ namespace tunnel return it->second; return nullptr; } - - Tunnel * Tunnels::GetPendingTunnel (uint32_t replyMsgID) + + InboundTunnel * Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID) { - auto it = m_PendingTunnels.find(replyMsgID); - if (it != m_PendingTunnels.end () && it->second->GetState () == eTunnelStatePending) + return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels); + } + + OutboundTunnel * Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID) + { + return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels); + } + + template + TTunnel * Tunnels::GetPendingTunnel (uint32_t replyMsgID, const std::map& pendingTunnels) + { + auto it = pendingTunnels.find(replyMsgID); + if (it != pendingTunnels.end () && it->second->GetState () == eTunnelStatePending) { it->second->SetState (eTunnelStateBuildReplyReceived); return it->second; @@ -447,10 +461,17 @@ namespace tunnel } void Tunnels::ManagePendingTunnels () + { + ManagePendingTunnels (m_PendingInboundTunnels); + ManagePendingTunnels (m_PendingOutboundTunnels); + } + + template + void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels) { // check pending tunnel. delete failed or timeout uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_PendingTunnels.begin (); it != m_PendingTunnels.end ();) + for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();) { auto tunnel = it->second; switch (tunnel->GetState ()) @@ -460,7 +481,7 @@ namespace tunnel { LogPrint ("Pending tunnel build request ", it->first, " timeout. Deleted"); delete tunnel; - it = m_PendingTunnels.erase (it); + it = pendingTunnels.erase (it); } else it++; @@ -468,14 +489,14 @@ namespace tunnel case eTunnelStateBuildFailed: LogPrint ("Pending tunnel build request ", it->first, " failed. Deleted"); delete tunnel; - it = m_PendingTunnels.erase (it); + it = pendingTunnels.erase (it); break; case eTunnelStateBuildReplyReceived: // intermidiate state, will be either established of build failed it++; break; default: - it = m_PendingTunnels.erase (it); + it = pendingTunnels.erase (it); } } } @@ -625,11 +646,21 @@ namespace tunnel { TTunnel * newTunnel = new TTunnel (config); uint32_t replyMsgID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); - m_PendingTunnels[replyMsgID] = newTunnel; + AddPendingTunnel (replyMsgID, newTunnel); newTunnel->Build (replyMsgID, outboundTunnel); return newTunnel; } + void Tunnels::AddPendingTunnel (uint32_t replyMsgID, InboundTunnel * tunnel) + { + m_PendingInboundTunnels[replyMsgID] = tunnel; + } + + void Tunnels::AddPendingTunnel (uint32_t replyMsgID, OutboundTunnel * tunnel) + { + m_PendingOutboundTunnels[replyMsgID] = tunnel; + } + void Tunnels::AddOutboundTunnel (OutboundTunnel * newTunnel) { std::unique_lock l(m_OutboundTunnelsMutex); diff --git a/Tunnel.h b/Tunnel.h index 6f7cf709..2cd9c0fb 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -121,7 +121,8 @@ namespace tunnel void Stop (); InboundTunnel * GetInboundTunnel (uint32_t tunnelID); - Tunnel * GetPendingTunnel (uint32_t replyMsgID); + InboundTunnel * GetPendingInboundTunnel (uint32_t replyMsgID); + OutboundTunnel * GetPendingOutboundTunnel (uint32_t replyMsgID); InboundTunnel * GetNextInboundTunnel (); OutboundTunnel * GetNextOutboundTunnel (); std::shared_ptr GetExploratoryPool () const { return m_ExploratoryPool; }; @@ -134,12 +135,17 @@ namespace tunnel void PostTunnelData (const std::vector& msgs); template TTunnel * CreateTunnel (TunnelConfig * config, OutboundTunnel * outboundTunnel = 0); + void AddPendingTunnel (uint32_t replyMsgID, InboundTunnel * tunnel); + void AddPendingTunnel (uint32_t replyMsgID, OutboundTunnel * tunnel); std::shared_ptr CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOuboundHops); void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); private: - + + template + TTunnel * GetPendingTunnel (uint32_t replyMsgID, const std::map& pendingTunnels); + void HandleTunnelGatewayMsg (TunnelBase * tunnel, I2NPMessage * msg); void Run (); @@ -148,6 +154,8 @@ namespace tunnel void ManageInboundTunnels (); void ManageTransitTunnels (); void ManagePendingTunnels (); + template + void ManagePendingTunnels (PendingTunnels& pendingTunnels); void ManageTunnelPools (); void CreateZeroHopsInboundTunnel (); @@ -156,7 +164,8 @@ namespace tunnel bool m_IsRunning; std::thread * m_Thread; - std::map m_PendingTunnels; // by replyMsgID + std::map m_PendingInboundTunnels; // by replyMsgID + std::map m_PendingOutboundTunnels; // by replyMsgID std::mutex m_InboundTunnelsMutex; std::map m_InboundTunnels; std::mutex m_OutboundTunnelsMutex; From 562cdc12d1e0dd87e204eacb22db052812f56b40 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 26 Jan 2015 14:48:24 -0500 Subject: [PATCH 0100/6300] check message type of follow on message --- Tunnel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Tunnel.cpp b/Tunnel.cpp index 3027bcad..dd088fa6 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -401,6 +401,7 @@ namespace tunnel msg = m_Queue.Get (); if (msg) { + typeID = msg->GetTypeID (); prevTunnelID = tunnelID; prevTunnel = tunnel; } From 4b094b2156818a4fbfe6db7963d514d107b30eeb Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 26 Jan 2015 20:49:16 -0500 Subject: [PATCH 0101/6300] handle tunnel build messages in tunnels thread --- I2NPProtocol.cpp | 7 ++++ Tunnel.cpp | 86 +++++++++++++++++++++++++++--------------------- Tunnel.h | 2 -- 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 638122b5..9bddbb7b 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -544,6 +544,13 @@ namespace i2p else i2p::context.ProcessDeliveryStatusMessage (msg); break; + case eI2NPVariableTunnelBuild: + case eI2NPVariableTunnelBuildReply: + case eI2NPTunnelBuild: + case eI2NPTunnelBuildReply: + // forward to tunnel thread + i2p::tunnel::tunnels.PostTunnelData (msg); + break; default: HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); DeleteI2NPMessage (msg); diff --git a/Tunnel.cpp b/Tunnel.cpp index dd088fa6..75259ba5 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -273,7 +273,6 @@ namespace tunnel { InboundTunnel * tunnel = nullptr; size_t minReceived = 0; - std::unique_lock l(m_InboundTunnelsMutex); for (auto it : m_InboundTunnels) { if (!it.second->IsEstablished ()) continue; @@ -291,7 +290,6 @@ namespace tunnel CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); uint32_t ind = rnd.GenerateWord32 (0, m_OutboundTunnels.size () - 1), i = 0; OutboundTunnel * tunnel = nullptr; - std::unique_lock l(m_OutboundTunnelsMutex); for (auto it: m_OutboundTunnels) { if (it->IsEstablished ()) @@ -369,39 +367,59 @@ namespace tunnel I2NPMessage * msg = m_Queue.GetNextWithTimeout (1000); // 1 sec if (msg) { - uint8_t typeID = msg->GetTypeID (); - uint32_t prevTunnelID = 0; - TunnelBase * prevTunnel = nullptr; + uint32_t prevTunnelID = 0, tunnelID = 0; + TunnelBase * prevTunnel = nullptr, * tunnel = nullptr; do { - uint32_t tunnelID = bufbe32toh (msg->GetPayload ()); - TunnelBase * tunnel = nullptr; - if (tunnelID == prevTunnelID) - tunnel = prevTunnel; - else if (prevTunnel) - prevTunnel->FlushTunnelDataMsgs (); + uint8_t typeID = msg->GetTypeID (); + switch (typeID) + { + case eI2NPTunnelData: + case eI2NPTunnelGateway: + { + tunnelID = bufbe32toh (msg->GetPayload ()); + if (tunnelID == prevTunnelID) + tunnel = prevTunnel; + else if (prevTunnel) + prevTunnel->FlushTunnelDataMsgs (); - if (!tunnel && typeID == eI2NPTunnelData) - tunnel = GetInboundTunnel (tunnelID); - if (!tunnel) - tunnel = GetTransitTunnel (tunnelID); - if (tunnel) - { - if (typeID == eI2NPTunnelData) - tunnel->HandleTunnelDataMsg (msg); - else // tunnel gateway assumed - HandleTunnelGatewayMsg (tunnel, msg); + if (!tunnel && typeID == eI2NPTunnelData) + tunnel = GetInboundTunnel (tunnelID); + if (!tunnel) + tunnel = GetTransitTunnel (tunnelID); + if (tunnel) + { + if (typeID == eI2NPTunnelData) + tunnel->HandleTunnelDataMsg (msg); + else // tunnel gateway assumed + HandleTunnelGatewayMsg (tunnel, msg); + } + else + { + LogPrint (eLogWarning, "Tunnel ", tunnelID, " not found"); + DeleteI2NPMessage (msg); + } + break; + } + case eI2NPVariableTunnelBuild: + case eI2NPVariableTunnelBuildReply: + case eI2NPTunnelBuild: + case eI2NPTunnelBuildReply: + { + HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); + DeleteI2NPMessage (msg); + break; + } + default: + { + LogPrint (eLogError, "Unexpected messsage type ", (int)typeID); + DeleteI2NPMessage (msg); + } } - else - { - LogPrint ("Tunnel ", tunnelID, " not found"); - DeleteI2NPMessage (msg); - } - + msg = m_Queue.Get (); if (msg) { - typeID = msg->GetTypeID (); prevTunnelID = tunnelID; prevTunnel = tunnel; } @@ -517,10 +535,7 @@ namespace tunnel if (pool) pool->TunnelExpired (tunnel); } - { - std::unique_lock l(m_OutboundTunnelsMutex); - it = m_OutboundTunnels.erase (it); - } + it = m_OutboundTunnels.erase (it); delete tunnel; } else @@ -562,10 +577,7 @@ namespace tunnel if (pool) pool->TunnelExpired (tunnel); } - { - std::unique_lock l(m_InboundTunnelsMutex); - it = m_InboundTunnels.erase (it); - } + it = m_InboundTunnels.erase (it); delete tunnel; } else @@ -664,7 +676,6 @@ namespace tunnel void Tunnels::AddOutboundTunnel (OutboundTunnel * newTunnel) { - std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (pool && pool->IsActive ()) @@ -675,7 +686,6 @@ namespace tunnel void Tunnels::AddInboundTunnel (InboundTunnel * newTunnel) { - std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels[newTunnel->GetTunnelID ()] = newTunnel; auto pool = newTunnel->GetTunnelPool (); if (!pool) diff --git a/Tunnel.h b/Tunnel.h index 2cd9c0fb..892f7430 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -166,9 +166,7 @@ namespace tunnel std::thread * m_Thread; std::map m_PendingInboundTunnels; // by replyMsgID std::map m_PendingOutboundTunnels; // by replyMsgID - std::mutex m_InboundTunnelsMutex; std::map m_InboundTunnels; - std::mutex m_OutboundTunnelsMutex; std::list m_OutboundTunnels; std::mutex m_TransitTunnelsMutex; std::map m_TransitTunnels; From d1d6797d3e5f62652742b875b70cc8ff9f0a0f83 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 27 Jan 2015 11:27:58 -0500 Subject: [PATCH 0102/6300] store LeaseSet as shared_ptr --- AddressBook.cpp | 4 ++-- BOB.cpp | 2 +- BOB.h | 2 +- Destination.cpp | 18 ++++++++---------- Destination.h | 6 +++--- HTTPServer.cpp | 4 ++-- HTTPServer.h | 2 +- I2PTunnel.cpp | 4 ++-- I2PTunnel.h | 2 +- NetDb.cpp | 9 +++------ NetDb.h | 4 ++-- SAM.cpp | 12 ++++++------ SAM.h | 2 +- Streaming.cpp | 10 +++++----- Streaming.h | 8 ++++---- api.cpp | 2 +- 16 files changed, 43 insertions(+), 48 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index a77b7194..609b9aa7 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -435,7 +435,7 @@ namespace client { std::condition_variable newDataReceived; std::mutex newDataReceivedMutex; - const i2p::data::LeaseSet * leaseSet = i2p::data::netdb.FindLeaseSet (ident); + auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (ident); if (!leaseSet) { bool found = false; @@ -462,7 +462,7 @@ namespace client 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_); + auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, u.port_); stream->Send ((uint8_t *)request.str ().c_str (), request.str ().length ()); uint8_t buf[4095]; diff --git a/BOB.cpp b/BOB.cpp index f925979f..3b31569d 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -132,7 +132,7 @@ namespace client delete receiver; } - void BOBI2PInboundTunnel::CreateConnection (AddressReceiver * receiver, const i2p::data::LeaseSet * leaseSet) + void BOBI2PInboundTunnel::CreateConnection (AddressReceiver * receiver, std::shared_ptr leaseSet) { LogPrint ("New BOB inbound connection"); auto connection = std::make_shared(this, receiver->socket, leaseSet); diff --git a/BOB.h b/BOB.h index bb75e40d..5858cdb8 100644 --- a/BOB.h +++ b/BOB.h @@ -84,7 +84,7 @@ namespace client void HandleDestinationRequestTimer (const boost::system::error_code& ecode, AddressReceiver * receiver, i2p::data::IdentHash ident); - void CreateConnection (AddressReceiver * receiver, const i2p::data::LeaseSet * leaseSet); + void CreateConnection (AddressReceiver * receiver, std::shared_ptr leaseSet); private: diff --git a/Destination.cpp b/Destination.cpp index 23567817..8d5d59bb 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -57,8 +57,6 @@ namespace client Stop (); for (auto it: m_LeaseSetRequests) delete it.second; - for (auto it: m_RemoteLeaseSets) - delete it.second; if (m_Pool) i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool); if (m_StreamingDestination) @@ -126,7 +124,7 @@ namespace client } } - const i2p::data::LeaseSet * ClientDestination::FindLeaseSet (const i2p::data::IdentHash& ident) + std::shared_ptr ClientDestination::FindLeaseSet (const i2p::data::IdentHash& ident) { auto it = m_RemoteLeaseSets.find (ident); if (it != m_RemoteLeaseSets.end ()) @@ -141,7 +139,7 @@ namespace client auto ls = i2p::data::netdb.FindLeaseSet (ident); if (ls) { - ls = new i2p::data::LeaseSet (*ls); + ls = std::make_shared (*ls); m_RemoteLeaseSets[ident] = ls; return ls; } @@ -232,7 +230,7 @@ namespace client else { LogPrint (eLogDebug, "New remote LeaseSet added"); - m_RemoteLeaseSets[buf + DATABASE_STORE_KEY_OFFSET] = new i2p::data::LeaseSet (buf + offset, len - offset); + m_RemoteLeaseSets[buf + DATABASE_STORE_KEY_OFFSET] = std::make_shared (buf + offset, len - offset); } } else @@ -391,9 +389,9 @@ namespace client void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) { assert(streamRequestComplete); - const i2p::data::LeaseSet * leaseSet = FindLeaseSet (dest); + auto leaseSet = FindLeaseSet (dest); if (leaseSet) - streamRequestComplete(CreateStream (*leaseSet, port)); + streamRequestComplete(CreateStream (leaseSet, port)); else { RequestDestination (dest, @@ -403,9 +401,9 @@ namespace client streamRequestComplete (nullptr); else { - const i2p::data::LeaseSet * leaseSet = FindLeaseSet (dest); + auto leaseSet = FindLeaseSet (dest); if (leaseSet) - streamRequestComplete(CreateStream (*leaseSet, port)); + streamRequestComplete(CreateStream (leaseSet, port)); else streamRequestComplete (nullptr); } @@ -413,7 +411,7 @@ namespace client } } - std::shared_ptr ClientDestination::CreateStream (const i2p::data::LeaseSet& remote, int port) + std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, int port) { if (m_StreamingDestination) return m_StreamingDestination->CreateNewOutgoingStream (remote, port); diff --git a/Destination.h b/Destination.h index 4104c426..1e49e932 100644 --- a/Destination.h +++ b/Destination.h @@ -64,13 +64,13 @@ namespace client boost::asio::io_service& GetService () { return m_Service; }; std::shared_ptr GetTunnelPool () { return m_Pool; }; bool IsReady () const { return m_LeaseSet && m_LeaseSet->HasNonExpiredLeases (); }; - const i2p::data::LeaseSet * FindLeaseSet (const i2p::data::IdentHash& ident); + std::shared_ptr FindLeaseSet (const i2p::data::IdentHash& ident); bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); // streaming i2p::stream::StreamingDestination * GetStreamingDestination () const { return m_StreamingDestination; }; void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); - std::shared_ptr CreateStream (const i2p::data::LeaseSet& remote, int port = 0); + std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); void StopAcceptingStreams (); bool IsAcceptingStreams () const; @@ -120,7 +120,7 @@ namespace client boost::asio::io_service::work m_Work; i2p::data::PrivateKeys m_Keys; uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; - std::map m_RemoteLeaseSets; + std::map > m_RemoteLeaseSets; std::map m_LeaseSetRequests; std::shared_ptr m_Pool; diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 042a03c4..f55396a6 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -901,10 +901,10 @@ namespace util } } - void HTTPConnection::SendToDestination (const i2p::data::LeaseSet * remote, int port, const char * buf, size_t len) + void HTTPConnection::SendToDestination (std::shared_ptr remote, int port, const char * buf, size_t len) { if (!m_Stream) - m_Stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (*remote, port); + m_Stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (remote, port); if (m_Stream) { m_Stream->Send ((uint8_t *)buf, len); diff --git a/HTTPServer.h b/HTTPServer.h index 51bbc98b..658a9f7f 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -93,7 +93,7 @@ namespace util void SendToAddress (const std::string& address, int port, const char * buf, size_t len); void HandleDestinationRequestTimeout (const boost::system::error_code& ecode, i2p::data::IdentHash destination, int port, const char * buf, size_t len); - void SendToDestination (const i2p::data::LeaseSet * remote, int port, const char * buf, size_t len); + void SendToDestination (std::shared_ptr remote, int port, const char * buf, size_t len); public: diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index bfd7cfce..8177b18e 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -10,11 +10,11 @@ namespace i2p namespace client { I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, - boost::asio::ip::tcp::socket * socket, const i2p::data::LeaseSet * leaseSet): + boost::asio::ip::tcp::socket * socket, std::shared_ptr leaseSet): I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { - m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (*leaseSet); + m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet); } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, diff --git a/I2PTunnel.h b/I2PTunnel.h index 010f4843..51de3b73 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -25,7 +25,7 @@ namespace client public: I2PTunnelConnection (I2PService * owner, boost::asio::ip::tcp::socket * socket, - const i2p::data::LeaseSet * leaseSet); // to I2P + std::shared_ptr leaseSet); // to I2P I2PTunnelConnection (I2PService * owner, boost::asio::ip::tcp::socket * socket, std::shared_ptr stream); // to I2P using simplified API :) I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, boost::asio::ip::tcp::socket * socket, diff --git a/NetDb.cpp b/NetDb.cpp index 2c2ae884..3783bcbd 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -116,8 +116,6 @@ namespace data delete m_Thread; m_Thread = 0; } - for (auto l: m_LeaseSets) - delete l.second; m_LeaseSets.clear(); for (auto r: m_RequestedDestinations) delete r.second; @@ -254,7 +252,7 @@ namespace data else { LogPrint ("New LeaseSet added"); - m_LeaseSets[ident] = new LeaseSet (buf, len); + m_LeaseSets[ident] = std::make_shared (buf, len); } } } @@ -269,7 +267,7 @@ namespace data return nullptr; } - LeaseSet * NetDb::FindLeaseSet (const IdentHash& destination) const + std::shared_ptr NetDb::FindLeaseSet (const IdentHash& destination) const { auto it = m_LeaseSets.find (destination); if (it != m_LeaseSets.end ()) @@ -641,7 +639,7 @@ namespace data if (leaseSet) // we don't send back our LeaseSets { LogPrint ("Requested LeaseSet ", key, " found"); - replyMsg = CreateDatabaseStoreMsg (leaseSet); + replyMsg = CreateDatabaseStoreMsg (leaseSet.get ()); } } if (!replyMsg) @@ -862,7 +860,6 @@ namespace data if (it->second->HasNonExpiredLeases ()) // all leases expired { LogPrint ("LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); - delete it->second; it = m_LeaseSets.erase (it); } else diff --git a/NetDb.h b/NetDb.h index afcae70d..f2e20661 100644 --- a/NetDb.h +++ b/NetDb.h @@ -68,7 +68,7 @@ namespace data void AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len); void AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, i2p::tunnel::InboundTunnel * from); std::shared_ptr FindRouter (const IdentHash& ident) const; - LeaseSet * FindLeaseSet (const IdentHash& destination) const; + std::shared_ptr FindLeaseSet (const IdentHash& destination) const; void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr); @@ -110,7 +110,7 @@ namespace data private: - std::map m_LeaseSets; + std::map > m_LeaseSets; mutable std::mutex m_RouterInfosMutex; std::map > m_RouterInfos; mutable std::mutex m_FloodfillsMutex; diff --git a/SAM.cpp b/SAM.cpp index d8386fcc..b85fc4ee 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -322,7 +322,7 @@ namespace client context.GetAddressBook ().InsertAddress (dest); auto leaseSet = i2p::data::netdb.FindLeaseSet (dest.GetIdentHash ()); if (leaseSet) - Connect (*leaseSet); + Connect (leaseSet); else { m_Session->localDestination->RequestDestination (dest.GetIdentHash (), @@ -334,7 +334,7 @@ namespace client SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } - void SAMSocket::Connect (const i2p::data::LeaseSet& remote) + void SAMSocket::Connect (std::shared_ptr remote) { m_SocketType = eSAMSocketTypeStream; m_Session->sockets.push_back (shared_from_this ()); @@ -346,11 +346,11 @@ namespace client void SAMSocket::HandleConnectLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident) { - const i2p::data::LeaseSet * leaseSet = nullptr; + std::shared_ptr leaseSet; if (success) // timeout expired leaseSet = m_Session->localDestination->FindLeaseSet (ident); if (leaseSet) - Connect (*leaseSet); + Connect (leaseSet); else { LogPrint ("SAM destination to connect not found"); @@ -418,7 +418,7 @@ namespace client else if (m_Session && m_Session->localDestination && context.GetAddressBook ().GetIdentHash (name, ident)) { - auto leaseSet = i2p::data::netdb.FindLeaseSet (ident); + auto leaseSet = m_Session->localDestination->FindLeaseSet (ident); if (leaseSet) SendNamingLookupReply (leaseSet->GetIdentity ()); else @@ -440,7 +440,7 @@ namespace client void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident) { - const i2p::data::LeaseSet * leaseSet = nullptr; + std::shared_ptr leaseSet; if (success) leaseSet = m_Session->localDestination->FindLeaseSet (ident); if (leaseSet) diff --git a/SAM.h b/SAM.h index e6de9102..0660f35c 100644 --- a/SAM.h +++ b/SAM.h @@ -104,7 +104,7 @@ namespace client void ProcessNamingLookup (char * buf, size_t len); void ExtractParams (char * buf, size_t len, std::map& params); - void Connect (const i2p::data::LeaseSet& remote); + void Connect (std::shared_ptr remote); void HandleConnectLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident); void SendNamingLookupReply (const i2p::data::IdentityEx& identity); void HandleNamingLookupLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident); diff --git a/Streaming.cpp b/Streaming.cpp index 680444b6..74807cae 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -12,10 +12,10 @@ namespace i2p namespace stream { Stream::Stream (boost::asio::io_service& service, StreamingDestination& local, - const i2p::data::LeaseSet& remote, int port): m_Service (service), m_SendStreamID (0), + std::shared_ptr remote, int port): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_IsOpen (false), m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local), - m_RemoteLeaseSet (&remote), m_CurrentOutboundTunnel (nullptr), + m_RemoteLeaseSet (remote), m_CurrentOutboundTunnel (nullptr), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port) { @@ -26,8 +26,8 @@ namespace stream Stream::Stream (boost::asio::io_service& service, StreamingDestination& local): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_IsOpen (false), m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local), - m_RemoteLeaseSet (nullptr), m_CurrentOutboundTunnel (nullptr), - m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), + m_CurrentOutboundTunnel (nullptr), m_ReceiveTimer (m_Service), + m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0) { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); @@ -692,7 +692,7 @@ namespace stream } } - std::shared_ptr StreamingDestination::CreateNewOutgoingStream (const i2p::data::LeaseSet& remote, int port) + std::shared_ptr StreamingDestination::CreateNewOutgoingStream (std::shared_ptr remote, int port) { auto s = std::make_shared (m_Owner.GetService (), *this, remote, port); std::unique_lock l(m_StreamsMutex); diff --git a/Streaming.h b/Streaming.h index 83514966..76c0f73d 100644 --- a/Streaming.h +++ b/Streaming.h @@ -86,13 +86,13 @@ namespace stream public: Stream (boost::asio::io_service& service, StreamingDestination& local, - const i2p::data::LeaseSet& remote, int port = 0); // outgoing + std::shared_ptr remote, int port = 0); // outgoing Stream (boost::asio::io_service& service, StreamingDestination& local); // incoming ~Stream (); uint32_t GetSendStreamID () const { return m_SendStreamID; }; uint32_t GetRecvStreamID () const { return m_RecvStreamID; }; - const i2p::data::LeaseSet * GetRemoteLeaseSet () const { return m_RemoteLeaseSet; }; + std::shared_ptr GetRemoteLeaseSet () const { return m_RemoteLeaseSet; }; const i2p::data::IdentityEx& GetRemoteIdentity () const { return m_RemoteIdentity; }; bool IsOpen () const { return m_IsOpen; }; bool IsEstablished () const { return m_SendStreamID; }; @@ -144,7 +144,7 @@ namespace stream bool m_IsOpen, m_IsReset, m_IsAckSendScheduled; StreamingDestination& m_LocalDestination; i2p::data::IdentityEx m_RemoteIdentity; - const i2p::data::LeaseSet * m_RemoteLeaseSet; + std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_RoutingSession; i2p::data::Lease m_CurrentRemoteLease; i2p::tunnel::OutboundTunnel * m_CurrentOutboundTunnel; @@ -171,7 +171,7 @@ namespace stream void Start (); void Stop (); - std::shared_ptr CreateNewOutgoingStream (const i2p::data::LeaseSet& remote, int port = 0); + std::shared_ptr CreateNewOutgoingStream (std::shared_ptr remote, int port = 0); void DeleteStream (std::shared_ptr stream); void SetAcceptor (const Acceptor& acceptor) { m_Acceptor = acceptor; }; void ResetAcceptor () { m_Acceptor = nullptr; }; diff --git a/api.cpp b/api.cpp index 3cddf4f5..0fb4256f 100644 --- a/api.cpp +++ b/api.cpp @@ -85,7 +85,7 @@ namespace api auto leaseSet = dest->FindLeaseSet (remote); if (leaseSet) { - auto stream = dest->CreateStream (*leaseSet); + auto stream = dest->CreateStream (leaseSet); stream->Send (nullptr, 0); // connect return stream; } From b3e08b2cf462ec9b0761c8aad9d8a7fc48bed5cb Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 27 Jan 2015 14:55:46 -0500 Subject: [PATCH 0103/6300] shared_ptr for tunnels --- Destination.cpp | 2 +- Garlic.cpp | 2 +- NetDb.cpp | 2 +- NetDb.h | 2 +- Streaming.cpp | 7 +++--- Streaming.h | 2 +- Tunnel.cpp | 57 ++++++++++++++++--------------------------------- Tunnel.h | 32 +++++++++++++-------------- TunnelBase.h | 3 ++- TunnelPool.cpp | 36 +++++++++++++++---------------- TunnelPool.h | 24 ++++++++++----------- 11 files changed, 74 insertions(+), 95 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 8d5d59bb..edf8bc6f 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -510,7 +510,7 @@ namespace client I2NPMessage * msg = WrapMessage (*nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, - replyTunnel, replyKey, replyTag)); + replyTunnel.get (), replyKey, replyTag)); outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock diff --git a/Garlic.cpp b/Garlic.cpp index a4ca69cd..6b2dc700 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -475,7 +475,7 @@ namespace garlic buf += 32; uint32_t gwTunnel = bufbe32toh (buf); buf += 4; - i2p::tunnel::OutboundTunnel * tunnel = nullptr; + std::shared_ptr tunnel; if (from && from->GetTunnelPool ()) tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel (); if (tunnel) // we have send it through an outbound tunnel diff --git a/NetDb.cpp b/NetDb.cpp index 3783bcbd..ec3fa113 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -22,7 +22,7 @@ namespace i2p namespace data { I2NPMessage * RequestedDestination::CreateRequestMessage (std::shared_ptr router, - const i2p::tunnel::InboundTunnel * replyTunnel) + std::shared_ptr replyTunnel) { I2NPMessage * msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory, diff --git a/NetDb.h b/NetDb.h index f2e20661..dfa7c7fb 100644 --- a/NetDb.h +++ b/NetDb.h @@ -38,7 +38,7 @@ namespace data bool IsExploratory () const { return m_IsExploratory; }; bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); }; uint64_t GetCreationTime () const { return m_CreationTime; }; - I2NPMessage * CreateRequestMessage (std::shared_ptr, const i2p::tunnel::InboundTunnel * replyTunnel); + I2NPMessage * CreateRequestMessage (std::shared_ptr, std::shared_ptr replyTunnel); I2NPMessage * CreateRequestMessage (const IdentHash& floodfill); void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; }; diff --git a/Streaming.cpp b/Streaming.cpp index 74807cae..bd6bac48 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -15,8 +15,8 @@ namespace stream std::shared_ptr remote, int port): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_IsOpen (false), m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local), - m_RemoteLeaseSet (remote), m_CurrentOutboundTunnel (nullptr), - m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), + m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), + m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port) { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); @@ -26,8 +26,7 @@ namespace stream Stream::Stream (boost::asio::io_service& service, StreamingDestination& local): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_IsOpen (false), m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local), - m_CurrentOutboundTunnel (nullptr), m_ReceiveTimer (m_Service), - m_ResendTimer (m_Service), m_AckSendTimer (m_Service), + m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0) { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); diff --git a/Streaming.h b/Streaming.h index 76c0f73d..62452e6b 100644 --- a/Streaming.h +++ b/Streaming.h @@ -147,7 +147,7 @@ namespace stream std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_RoutingSession; i2p::data::Lease m_CurrentRemoteLease; - i2p::tunnel::OutboundTunnel * m_CurrentOutboundTunnel; + std::shared_ptr m_CurrentOutboundTunnel; std::queue m_ReceiveQueue; std::set m_SavedPackets; std::set m_SentPackets; diff --git a/Tunnel.cpp b/Tunnel.cpp index 75259ba5..fe983ae1 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -27,7 +27,7 @@ namespace tunnel delete m_Config; } - void Tunnel::Build (uint32_t replyMsgID, OutboundTunnel * outboundTunnel) + void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); auto numHops = m_Config->GetNumHops (); @@ -209,29 +209,12 @@ namespace tunnel Tunnels::~Tunnels () { - for (auto& it : m_OutboundTunnels) - delete it; - m_OutboundTunnels.clear (); - - for (auto& it : m_InboundTunnels) - delete it.second; - m_InboundTunnels.clear (); - for (auto& it : m_TransitTunnels) delete it.second; m_TransitTunnels.clear (); - - ManagePendingTunnels (); - for (auto& it : m_PendingInboundTunnels) - delete it.second; - m_PendingInboundTunnels.clear (); - - for (auto& it : m_PendingOutboundTunnels) - delete it.second; - m_PendingOutboundTunnels.clear (); } - InboundTunnel * Tunnels::GetInboundTunnel (uint32_t tunnelID) + std::shared_ptr Tunnels::GetInboundTunnel (uint32_t tunnelID) { auto it = m_InboundTunnels.find(tunnelID); if (it != m_InboundTunnels.end ()) @@ -247,18 +230,18 @@ namespace tunnel return nullptr; } - InboundTunnel * Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID) + std::shared_ptr Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID) { return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels); } - OutboundTunnel * Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID) + std::shared_ptr Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID) { return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels); } template - TTunnel * Tunnels::GetPendingTunnel (uint32_t replyMsgID, const std::map& pendingTunnels) + std::shared_ptr Tunnels::GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels) { auto it = pendingTunnels.find(replyMsgID); if (it != pendingTunnels.end () && it->second->GetState () == eTunnelStatePending) @@ -269,9 +252,9 @@ namespace tunnel return nullptr; } - InboundTunnel * Tunnels::GetNextInboundTunnel () + std::shared_ptr Tunnels::GetNextInboundTunnel () { - InboundTunnel * tunnel = nullptr; + std::shared_ptr tunnel; size_t minReceived = 0; for (auto it : m_InboundTunnels) { @@ -285,11 +268,11 @@ namespace tunnel return tunnel; } - OutboundTunnel * Tunnels::GetNextOutboundTunnel () + std::shared_ptr Tunnels::GetNextOutboundTunnel () { CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); uint32_t ind = rnd.GenerateWord32 (0, m_OutboundTunnels.size () - 1), i = 0; - OutboundTunnel * tunnel = nullptr; + std::shared_ptr tunnel; for (auto it: m_OutboundTunnels) { if (it->IsEstablished ()) @@ -384,7 +367,7 @@ namespace tunnel prevTunnel->FlushTunnelDataMsgs (); if (!tunnel && typeID == eI2NPTunnelData) - tunnel = GetInboundTunnel (tunnelID); + tunnel = GetInboundTunnel (tunnelID).get (); if (!tunnel) tunnel = GetTransitTunnel (tunnelID); if (tunnel) @@ -499,7 +482,6 @@ namespace tunnel if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT) { LogPrint ("Pending tunnel build request ", it->first, " timeout. Deleted"); - delete tunnel; it = pendingTunnels.erase (it); } else @@ -507,11 +489,10 @@ namespace tunnel break; case eTunnelStateBuildFailed: LogPrint ("Pending tunnel build request ", it->first, " failed. Deleted"); - delete tunnel; it = pendingTunnels.erase (it); break; case eTunnelStateBuildReplyReceived: - // intermidiate state, will be either established of build failed + // intermediate state, will be either established of build failed it++; break; default: @@ -536,7 +517,6 @@ namespace tunnel pool->TunnelExpired (tunnel); } it = m_OutboundTunnels.erase (it); - delete tunnel; } else { @@ -550,7 +530,7 @@ namespace tunnel if (m_OutboundTunnels.size () < 5) { // trying to create one more oubound tunnel - InboundTunnel * inboundTunnel = GetNextInboundTunnel (); + auto inboundTunnel = GetNextInboundTunnel (); if (!inboundTunnel) return; LogPrint ("Creating one hop outbound tunnel..."); CreateTunnel ( @@ -578,7 +558,6 @@ namespace tunnel pool->TunnelExpired (tunnel); } it = m_InboundTunnels.erase (it); - delete tunnel; } else { @@ -655,26 +634,26 @@ namespace tunnel } template - TTunnel * Tunnels::CreateTunnel (TunnelConfig * config, OutboundTunnel * outboundTunnel) + std::shared_ptr Tunnels::CreateTunnel (TunnelConfig * config, std::shared_ptr outboundTunnel) { - TTunnel * newTunnel = new TTunnel (config); + auto newTunnel = std::make_shared (config); uint32_t replyMsgID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); AddPendingTunnel (replyMsgID, newTunnel); newTunnel->Build (replyMsgID, outboundTunnel); return newTunnel; } - void Tunnels::AddPendingTunnel (uint32_t replyMsgID, InboundTunnel * tunnel) + void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { m_PendingInboundTunnels[replyMsgID] = tunnel; } - void Tunnels::AddPendingTunnel (uint32_t replyMsgID, OutboundTunnel * tunnel) + void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { m_PendingOutboundTunnels[replyMsgID] = tunnel; } - void Tunnels::AddOutboundTunnel (OutboundTunnel * newTunnel) + void Tunnels::AddOutboundTunnel (std::shared_ptr newTunnel) { m_OutboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); @@ -684,7 +663,7 @@ namespace tunnel newTunnel->SetTunnelPool (nullptr); } - void Tunnels::AddInboundTunnel (InboundTunnel * newTunnel) + void Tunnels::AddInboundTunnel (std::shared_ptr newTunnel) { m_InboundTunnels[newTunnel->GetTunnelID ()] = newTunnel; auto pool = newTunnel->GetTunnelPool (); diff --git a/Tunnel.h b/Tunnel.h index 892f7430..db29b52a 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -47,7 +47,7 @@ namespace tunnel Tunnel (TunnelConfig * config); ~Tunnel (); - void Build (uint32_t replyMsgID, OutboundTunnel * outboundTunnel = 0); + void Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel = nullptr); TunnelConfig * GetTunnelConfig () const { return m_Config; } TunnelState GetState () const { return m_State; }; @@ -120,23 +120,23 @@ namespace tunnel void Start (); void Stop (); - InboundTunnel * GetInboundTunnel (uint32_t tunnelID); - InboundTunnel * GetPendingInboundTunnel (uint32_t replyMsgID); - OutboundTunnel * GetPendingOutboundTunnel (uint32_t replyMsgID); - InboundTunnel * GetNextInboundTunnel (); - OutboundTunnel * GetNextOutboundTunnel (); + std::shared_ptr GetInboundTunnel (uint32_t tunnelID); + std::shared_ptr GetPendingInboundTunnel (uint32_t replyMsgID); + std::shared_ptr GetPendingOutboundTunnel (uint32_t replyMsgID); + std::shared_ptr GetNextInboundTunnel (); + std::shared_ptr GetNextOutboundTunnel (); std::shared_ptr GetExploratoryPool () const { return m_ExploratoryPool; }; TransitTunnel * GetTransitTunnel (uint32_t tunnelID); int GetTransitTunnelsExpirationTimeout (); void AddTransitTunnel (TransitTunnel * tunnel); - void AddOutboundTunnel (OutboundTunnel * newTunnel); - void AddInboundTunnel (InboundTunnel * newTunnel); + void AddOutboundTunnel (std::shared_ptr newTunnel); + void AddInboundTunnel (std::shared_ptr newTunnel); void PostTunnelData (I2NPMessage * msg); void PostTunnelData (const std::vector& msgs); template - TTunnel * CreateTunnel (TunnelConfig * config, OutboundTunnel * outboundTunnel = 0); - void AddPendingTunnel (uint32_t replyMsgID, InboundTunnel * tunnel); - void AddPendingTunnel (uint32_t replyMsgID, OutboundTunnel * tunnel); + std::shared_ptr CreateTunnel (TunnelConfig * config, std::shared_ptr outboundTunnel = nullptr); + void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); + void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); std::shared_ptr CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOuboundHops); void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); @@ -144,7 +144,7 @@ namespace tunnel private: template - TTunnel * GetPendingTunnel (uint32_t replyMsgID, const std::map& pendingTunnels); + std::shared_ptr GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels); void HandleTunnelGatewayMsg (TunnelBase * tunnel, I2NPMessage * msg); @@ -164,10 +164,10 @@ namespace tunnel bool m_IsRunning; std::thread * m_Thread; - std::map m_PendingInboundTunnels; // by replyMsgID - std::map m_PendingOutboundTunnels; // by replyMsgID - std::map m_InboundTunnels; - std::list m_OutboundTunnels; + std::map > m_PendingInboundTunnels; // by replyMsgID + std::map > m_PendingOutboundTunnels; // by replyMsgID + std::map > m_InboundTunnels; + std::list > m_OutboundTunnels; std::mutex m_TransitTunnelsMutex; std::map m_TransitTunnels; std::mutex m_PoolsMutex; diff --git a/TunnelBase.h b/TunnelBase.h index dbb9c361..d867bf9a 100644 --- a/TunnelBase.h +++ b/TunnelBase.h @@ -2,6 +2,7 @@ #define TUNNEL_BASE_H__ #include +#include #include "Timestamp.h" #include "I2NPProtocol.h" #include "Identity.h" @@ -54,7 +55,7 @@ namespace tunnel struct TunnelCreationTimeCmp { - bool operator() (const TunnelBase * t1, const TunnelBase * t2) const + bool operator() (std::shared_ptr t1, std::shared_ptr t2) const { if (t1->GetCreationTime () != t2->GetCreationTime ()) return t1->GetCreationTime () > t2->GetCreationTime (); diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 2482d670..1445ee99 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -38,7 +38,7 @@ namespace tunnel m_Tests.clear (); } - void TunnelPool::TunnelCreated (InboundTunnel * createdTunnel) + void TunnelPool::TunnelCreated (std::shared_ptr createdTunnel) { if (!m_IsActive) return; { @@ -49,7 +49,7 @@ namespace tunnel m_LocalDestination->SetLeaseSetUpdated (); } - void TunnelPool::TunnelExpired (InboundTunnel * expiredTunnel) + void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) { if (expiredTunnel) { @@ -63,14 +63,14 @@ namespace tunnel } } - void TunnelPool::TunnelCreated (OutboundTunnel * createdTunnel) + void TunnelPool::TunnelCreated (std::shared_ptr createdTunnel) { if (!m_IsActive) return; std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.insert (createdTunnel); } - void TunnelPool::TunnelExpired (OutboundTunnel * expiredTunnel) + void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) { if (expiredTunnel) { @@ -84,9 +84,9 @@ namespace tunnel } } - std::vector TunnelPool::GetInboundTunnels (int num) const + std::vector > TunnelPool::GetInboundTunnels (int num) const { - std::vector v; + std::vector > v; int i = 0; std::unique_lock l(m_InboundTunnelsMutex); for (auto it : m_InboundTunnels) @@ -101,13 +101,13 @@ namespace tunnel return v; } - OutboundTunnel * TunnelPool::GetNextOutboundTunnel (OutboundTunnel * suggested) const + std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr suggested) const { std::unique_lock l(m_OutboundTunnelsMutex); return GetNextTunnel (m_OutboundTunnels, suggested); } - InboundTunnel * TunnelPool::GetNextInboundTunnel (InboundTunnel * suggested) const + std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr suggested) const { std::unique_lock l(m_InboundTunnelsMutex); return GetNextTunnel (m_InboundTunnels, suggested); @@ -274,7 +274,7 @@ namespace tunnel void TunnelPool::CreateInboundTunnel () { - OutboundTunnel * outboundTunnel = GetNextOutboundTunnel (); + auto outboundTunnel = GetNextOutboundTunnel (); if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint ("Creating destination inbound tunnel..."); @@ -299,23 +299,23 @@ namespace tunnel hops.push_back (hop); } std::reverse (hops.begin (), hops.end ()); - auto * tunnel = tunnels.CreateTunnel (new TunnelConfig (hops), outboundTunnel); + auto tunnel = tunnels.CreateTunnel (new TunnelConfig (hops), outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); } - void TunnelPool::RecreateInboundTunnel (InboundTunnel * tunnel) + void TunnelPool::RecreateInboundTunnel (std::shared_ptr tunnel) { - OutboundTunnel * outboundTunnel = GetNextOutboundTunnel (); + auto outboundTunnel = GetNextOutboundTunnel (); if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint ("Re-creating destination inbound tunnel..."); - auto * newTunnel = tunnels.CreateTunnel (tunnel->GetTunnelConfig ()->Clone (), outboundTunnel); + auto newTunnel = tunnels.CreateTunnel (tunnel->GetTunnelConfig ()->Clone (), outboundTunnel); newTunnel->SetTunnelPool (shared_from_this()); } void TunnelPool::CreateOutboundTunnel () { - InboundTunnel * inboundTunnel = GetNextInboundTunnel (); + auto inboundTunnel = GetNextInboundTunnel (); if (!inboundTunnel) inboundTunnel = tunnels.GetNextInboundTunnel (); if (inboundTunnel) @@ -331,7 +331,7 @@ namespace tunnel hops.push_back (hop); } - auto * tunnel = tunnels.CreateTunnel ( + auto tunnel = tunnels.CreateTunnel ( new TunnelConfig (hops, inboundTunnel->GetTunnelConfig ())); tunnel->SetTunnelPool (shared_from_this ()); } @@ -339,15 +339,15 @@ namespace tunnel LogPrint ("Can't create outbound tunnel. No inbound tunnels found"); } - void TunnelPool::RecreateOutboundTunnel (OutboundTunnel * tunnel) + void TunnelPool::RecreateOutboundTunnel (std::shared_ptr tunnel) { - InboundTunnel * inboundTunnel = GetNextInboundTunnel (); + auto inboundTunnel = GetNextInboundTunnel (); if (!inboundTunnel) inboundTunnel = tunnels.GetNextInboundTunnel (); if (inboundTunnel) { LogPrint ("Re-creating destination outbound tunnel..."); - auto * newTunnel = tunnels.CreateTunnel ( + auto newTunnel = tunnels.CreateTunnel ( tunnel->GetTunnelConfig ()->Clone (inboundTunnel->GetTunnelConfig ())); newTunnel->SetTunnelPool (shared_from_this ()); } diff --git a/TunnelPool.h b/TunnelPool.h index 2bc645b3..fb057dc0 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -34,13 +34,13 @@ namespace tunnel void SetLocalDestination (i2p::garlic::GarlicDestination * destination) { m_LocalDestination = destination; }; void CreateTunnels (); - void TunnelCreated (InboundTunnel * createdTunnel); - void TunnelExpired (InboundTunnel * expiredTunnel); - void TunnelCreated (OutboundTunnel * createdTunnel); - void TunnelExpired (OutboundTunnel * expiredTunnel); - std::vector GetInboundTunnels (int num) const; - OutboundTunnel * GetNextOutboundTunnel (OutboundTunnel * suggested = nullptr) const; - InboundTunnel * GetNextInboundTunnel (InboundTunnel * suggested = nullptr) const; + void TunnelCreated (std::shared_ptr createdTunnel); + void TunnelExpired (std::shared_ptr expiredTunnel); + void TunnelCreated (std::shared_ptr createdTunnel); + void TunnelExpired (std::shared_ptr expiredTunnel); + std::vector > GetInboundTunnels (int num) const; + std::shared_ptr GetNextOutboundTunnel (std::shared_ptr suggested = nullptr) const; + std::shared_ptr GetNextInboundTunnel (std::shared_ptr suggested = nullptr) const; void TestTunnels (); void ProcessGarlicMessage (I2NPMessage * msg); @@ -54,8 +54,8 @@ namespace tunnel void CreateInboundTunnel (); void CreateOutboundTunnel (); - void RecreateInboundTunnel (InboundTunnel * tunnel); - void RecreateOutboundTunnel (OutboundTunnel * tunnel); + void RecreateInboundTunnel (std::shared_ptr tunnel); + void RecreateOutboundTunnel (std::shared_ptr tunnel); template typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type suggested = nullptr) const; @@ -66,10 +66,10 @@ namespace tunnel i2p::garlic::GarlicDestination * m_LocalDestination; int m_NumInboundHops, m_NumOutboundHops, m_NumTunnels; mutable std::mutex m_InboundTunnelsMutex; - std::set m_InboundTunnels; // recent tunnel appears first + std::set, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first mutable std::mutex m_OutboundTunnelsMutex; - std::set m_OutboundTunnels; - std::map > m_Tests; + std::set, TunnelCreationTimeCmp> m_OutboundTunnels; + std::map, std::shared_ptr > > m_Tests; bool m_IsActive; public: From 763547f465507299ae1b6425bb5d6df7f36d8559 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 27 Jan 2015 19:12:27 -0500 Subject: [PATCH 0104/6300] fixed corrupted NTCP messages --- NTCPSession.cpp | 32 +++++++++++++++++++++++++++----- NTCPSession.h | 3 +++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index df1e9bc9..83e43c18 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -21,7 +21,7 @@ namespace transport NTCPSession::NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter): TransportSession (in_RemoteRouter), m_Server (server), m_Socket (m_Server.GetService ()), m_TerminationTimer (m_Server.GetService ()), m_IsEstablished (false), m_ReceiveBufferOffset (0), - m_NextMessage (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0) + m_NextMessage (nullptr), m_IsSending (false), m_NumSentBytes (0), m_NumReceivedBytes (0) { m_DHKeysPair = transports.GetNextDHKeysPair (); m_Establisher = new Establisher; @@ -32,6 +32,8 @@ namespace transport delete m_Establisher; if (m_NextMessage) i2p::DeleteI2NPMessage (m_NextMessage); + for (auto it: m_SendQueue) + DeleteI2NPMessage (it); } void NTCPSession::CreateAESKey (uint8_t * pubKey, i2p::crypto::AESKey& key) @@ -538,6 +540,7 @@ namespace transport void NTCPSession::Send (i2p::I2NPMessage * msg) { + m_IsSending = true; boost::asio::async_write (m_Socket, CreateMsgBuffer (msg), boost::asio::transfer_all (), std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::vector{ msg })); } @@ -581,6 +584,7 @@ namespace transport void NTCPSession::Send (const std::vector& msgs) { + m_IsSending = true; std::vector bufs; for (auto it: msgs) bufs.push_back (CreateMsgBuffer (it)); @@ -590,6 +594,7 @@ namespace transport void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector msgs) { + m_IsSending = false; for (auto it: msgs) if (it) i2p::DeleteI2NPMessage (it); if (ecode) @@ -602,8 +607,14 @@ namespace transport else { m_NumSentBytes += bytes_transferred; - ScheduleTermination (); // reset termination timer - } + if (!m_SendQueue.empty()) + { + Send (m_SendQueue); + m_SendQueue.clear (); + } + else + ScheduleTermination (); // reset termination timer + } } @@ -620,7 +631,12 @@ namespace transport void NTCPSession::PostI2NPMessage (I2NPMessage * msg) { if (msg) - Send (msg); + { + if (m_IsSending) + m_SendQueue.push_back (msg); + else + Send (msg); + } } void NTCPSession::SendI2NPMessages (const std::vector& msgs) @@ -630,7 +646,13 @@ namespace transport void NTCPSession::PostI2NPMessages (std::vector msgs) { - Send (msgs); + if (m_IsSending) + { + for (auto it: msgs) + m_SendQueue.push_back (it); + } + else + Send (msgs); } void NTCPSession::ScheduleTermination () diff --git a/NTCPSession.h b/NTCPSession.h index bf2cb17e..66f38538 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -132,6 +132,9 @@ namespace transport i2p::I2NPMessage * m_NextMessage; size_t m_NextMessageOffset; i2p::I2NPMessagesHandler m_Handler; + + bool m_IsSending; + std::vector m_SendQueue; size_t m_NumSentBytes, m_NumReceivedBytes; }; From 192a08b5bfb7c9b124fc35b020f909c3a10eac5d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 27 Jan 2015 22:31:57 -0500 Subject: [PATCH 0105/6300] check tunnel status instead fidning it every time --- Streaming.cpp | 3 ++- TunnelPool.cpp | 16 ++++++---------- TunnelPool.h | 7 +++---- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index bd6bac48..6e89fa64 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -504,7 +504,8 @@ namespace stream return; } } - m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ().GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); + if (!m_CurrentOutboundTunnel || !m_CurrentOutboundTunnel->IsEstablished ()) + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ().GetTunnelPool ()->GetNextOutboundTunnel (); if (!m_CurrentOutboundTunnel) { LogPrint ("No outbound tunnels in the pool"); diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 1445ee99..7a98621c 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -101,26 +101,22 @@ namespace tunnel return v; } - std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr suggested) const + std::shared_ptr TunnelPool::GetNextOutboundTunnel () const { std::unique_lock l(m_OutboundTunnelsMutex); - return GetNextTunnel (m_OutboundTunnels, suggested); + return GetNextTunnel (m_OutboundTunnels); } - std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr suggested) const + std::shared_ptr TunnelPool::GetNextInboundTunnel () const { std::unique_lock l(m_InboundTunnelsMutex); - return GetNextTunnel (m_InboundTunnels, suggested); + return GetNextTunnel (m_InboundTunnels); } template - typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, - typename TTunnels::value_type suggested) const + typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels) const { - if (tunnels.empty ()) return nullptr; - if (suggested && tunnels.count (suggested) > 0 && suggested->IsEstablished ()) - return suggested; - + if (tunnels.empty ()) return nullptr; CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); uint32_t ind = rnd.GenerateWord32 (0, tunnels.size ()/2), i = 0; typename TTunnels::value_type tunnel = nullptr; diff --git a/TunnelPool.h b/TunnelPool.h index fb057dc0..14f70841 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -39,8 +39,8 @@ namespace tunnel void TunnelCreated (std::shared_ptr createdTunnel); void TunnelExpired (std::shared_ptr expiredTunnel); std::vector > GetInboundTunnels (int num) const; - std::shared_ptr GetNextOutboundTunnel (std::shared_ptr suggested = nullptr) const; - std::shared_ptr GetNextInboundTunnel (std::shared_ptr suggested = nullptr) const; + std::shared_ptr GetNextOutboundTunnel () const; + std::shared_ptr GetNextInboundTunnel () const; void TestTunnels (); void ProcessGarlicMessage (I2NPMessage * msg); @@ -57,8 +57,7 @@ namespace tunnel void RecreateInboundTunnel (std::shared_ptr tunnel); void RecreateOutboundTunnel (std::shared_ptr tunnel); template - typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, - typename TTunnels::value_type suggested = nullptr) const; + typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels) const; std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; private: From 2ed69ef6025eed77ec38bb2ea95eed03b262060f Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 28 Jan 2015 14:20:28 -0500 Subject: [PATCH 0106/6300] clean up expired LeaseSets --- Destination.cpp | 15 +++++++++++++++ Destination.h | 1 + NetDb.cpp | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Destination.cpp b/Destination.cpp index edf8bc6f..b122172e 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -566,10 +566,25 @@ namespace client if (ecode != boost::asio::error::operation_aborted) { CleanupRoutingSessions (); + CleanupRemoteLeaseSets (); m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer, this, std::placeholders::_1)); } } + + void ClientDestination::CleanupRemoteLeaseSets () + { + for (auto it = m_RemoteLeaseSets.begin (); it != m_RemoteLeaseSets.end ();) + { + if (!it->second->HasNonExpiredLeases ()) // all leases expired + { + LogPrint ("Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); + it = m_RemoteLeaseSets.erase (it); + } + else + it++; + } + } } } diff --git a/Destination.h b/Destination.h index 1e49e932..db7a3089 100644 --- a/Destination.h +++ b/Destination.h @@ -111,6 +111,7 @@ namespace client bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, LeaseSetRequest * request); void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleCleanupTimer (const boost::system::error_code& ecode); + void CleanupRemoteLeaseSets (); private: diff --git a/NetDb.cpp b/NetDb.cpp index ec3fa113..90744fca 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -857,7 +857,7 @@ namespace data { for (auto it = m_LeaseSets.begin (); it != m_LeaseSets.end ();) { - if (it->second->HasNonExpiredLeases ()) // all leases expired + if (!it->second->HasNonExpiredLeases ()) // all leases expired { LogPrint ("LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); it = m_LeaseSets.erase (it); From 7e45233c7d431294e5d2b3a1e93d722ac4522bd8 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 28 Jan 2015 15:12:15 -0500 Subject: [PATCH 0107/6300] floodfill parameter --- Daemon.cpp | 3 +++ RouterContext.cpp | 13 ++++++++++++- RouterContext.h | 6 ++++-- RouterInfo.cpp | 9 +++++++-- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index cbfaf31f..dbc65053 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -77,6 +77,9 @@ namespace i2p if (i2p::util::config::GetArg("-unreachable", 0)) i2p::context.SetUnreachable (); + if (i2p::util::config::GetArg("-floodfill", 0)) + i2p::context.SetFloodfill (true); + i2p::context.SetSupportsV6 (i2p::util::config::GetArg("-v6", 0)); LogPrint("CMD parameters:"); diff --git a/RouterContext.cpp b/RouterContext.cpp index 90f804df..2d42d3da 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -13,7 +13,8 @@ namespace i2p RouterContext context; RouterContext::RouterContext (): - m_LastUpdateTime (0), m_IsUnreachable (false), m_AcceptsTunnels (true) + m_LastUpdateTime (0), m_IsUnreachable (false), m_AcceptsTunnels (true), + m_IsFloodfill (false) { } @@ -107,6 +108,16 @@ namespace i2p UpdateRouterInfo (); } + void RouterContext::SetFloodfill (bool floodfill) + { + m_IsFloodfill = floodfill; + if (floodfill) + m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill); + else + m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill); + UpdateRouterInfo (); + } + void RouterContext::SetUnreachable () { m_IsUnreachable = true; diff --git a/RouterContext.h b/RouterContext.h index 80928901..10512d67 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -37,7 +37,9 @@ namespace i2p bool AddIntroducer (const i2p::data::RouterInfo& routerInfo, uint32_t tag); void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); bool IsUnreachable () const { return m_IsUnreachable; }; - void SetUnreachable (); + void SetUnreachable (); + bool IsFloodfill () const { return m_IsFloodfill; }; + void SetFloodfill (bool floodfill); bool AcceptsTunnels () const { return m_AcceptsTunnels; }; void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; }; bool SupportsV6 () const { return m_RouterInfo.IsV6 (); }; @@ -68,7 +70,7 @@ namespace i2p i2p::data::PrivateKeys m_Keys; CryptoPP::AutoSeededRandomPool m_Rnd; uint64_t m_LastUpdateTime; - bool m_IsUnreachable, m_AcceptsTunnels; + bool m_IsUnreachable, m_AcceptsTunnels, m_IsFloodfill; }; extern RouterContext context; diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 91f793fb..56a9327a 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -276,8 +276,13 @@ namespace data void RouterInfo::UpdateCapsProperty () { std::string caps; - caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH1 : CAPS_FLAG_LOW_BANDWIDTH2; // bandwidth - if (m_Caps & eFloodfill) caps += CAPS_FLAG_FLOODFILL; // floodfill + if (m_Caps & eFloodfill) + { + caps += CAPS_FLAG_HIGH_BANDWIDTH3; // highest bandwidth + caps += CAPS_FLAG_FLOODFILL; // floodfill + } + else + caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH1 : CAPS_FLAG_LOW_BANDWIDTH2; // bandwidth if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable if (m_Caps & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable From 679faf5149e5e02ba584e78171d6979ec480d6ec Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 28 Jan 2015 16:16:25 -0500 Subject: [PATCH 0108/6300] specify reply token for RIs DatabaseStore --- I2NPProtocol.cpp | 20 ++++++++++++++------ I2NPProtocol.h | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 9bddbb7b..087caa0b 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -205,7 +205,7 @@ namespace i2p return m; } - I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::RouterInfo * router) + I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::RouterInfo * router, uint32_t replyToken) { if (!router) // we send own RouterInfo router = &context.GetRouterInfo (); @@ -214,19 +214,27 @@ namespace i2p uint8_t * payload = m->GetPayload (); memcpy (payload + DATABASE_STORE_KEY_OFFSET, router->GetIdentHash (), 32); - payload[DATABASE_STORE_TYPE_OFFSET] = 0; - htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); - + payload[DATABASE_STORE_TYPE_OFFSET] = 0; // RouterInfo + htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, replyToken); + uint8_t * buf = payload + DATABASE_STORE_HEADER_SIZE; + if (replyToken) + { + memset (buf, 0, 4); // zero tunnelID means direct reply + buf += 4; + memcpy (buf, router->GetIdentHash (), 32); + buf += 32; + } + CryptoPP::Gzip compressor; compressor.Put (router->GetBuffer (), router->GetBufferLen ()); compressor.MessageEnd(); auto size = compressor.MaxRetrievable (); - uint8_t * buf = payload + DATABASE_STORE_HEADER_SIZE; htobe16buf (buf, size); // size buf += 2; // TODO: check if size doesn't exceed buffer compressor.Get (buf, size); - m->len += DATABASE_STORE_HEADER_SIZE + 2 + size; // payload size + buf += size; + m->len += (buf - payload); // payload size FillI2NPMessageHeader (m, eI2NPDatabaseStore); return m; diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 9ae3998d..5b68792c 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -196,7 +196,7 @@ namespace tunnel const i2p::tunnel::InboundTunnel * replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag); I2NPMessage * CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, const i2p::data::RouterInfo * floodfill); - I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::RouterInfo * router = nullptr); + I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::RouterInfo * router = nullptr, uint32_t replyToken = 0); I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::LeaseSet * leaseSet, uint32_t replyToken = 0); bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); From 938fa004691ab7a490a54dd55f885239b23e7c21 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 28 Jan 2015 21:01:16 -0500 Subject: [PATCH 0109/6300] publishing with flood --- NetDb.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 90744fca..6b0a36d0 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -735,13 +735,14 @@ namespace data void NetDb::Publish () { std::set excluded; // TODO: fill up later - for (int i = 0; i < 3; i++) + for (int i = 0; i < 2; i++) { auto floodfill = GetClosestFloodfill (i2p::context.GetRouterInfo ().GetIdentHash (), excluded); if (floodfill) { - LogPrint ("Publishing our RouterInfo to ", floodfill->GetIdentHashAbbreviation ()); - transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg ()); + uint32_t replyToken = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); + LogPrint ("Publishing our RouterInfo to ", floodfill->GetIdentHashAbbreviation (), ". reply token=", replyToken); + transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg ((RouterInfo *)nullptr, replyToken)); excluded.insert (floodfill->GetIdentHash ()); } } From 974a7ff3f56977b0a1075624acb3c99b0e1688eb Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 28 Jan 2015 21:37:08 -0500 Subject: [PATCH 0110/6300] shared_ptr for RoutingDestination --- Datagram.cpp | 6 +++--- Datagram.h | 5 +++-- Destination.cpp | 4 ++-- Garlic.cpp | 14 +++++++------- Garlic.h | 8 ++++---- SAM.cpp | 2 +- Streaming.cpp | 2 +- 7 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 410b9740..2378659a 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -17,7 +17,7 @@ namespace datagram { } - void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::LeaseSet& remote) + void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, std::shared_ptr remote) { uint8_t buf[MAX_DATAGRAM_SIZE]; auto identityLen = m_Owner.GetIdentity ().ToBuffer (buf, MAX_DATAGRAM_SIZE); @@ -40,10 +40,10 @@ namespace datagram CreateDataMessage (buf, len + headerLen), remote)); } - void DatagramDestination::SendMsg (I2NPMessage * msg, const i2p::data::LeaseSet& remote) + void DatagramDestination::SendMsg (I2NPMessage * msg, std::shared_ptr remote) { auto outboundTunnel = m_Owner.GetTunnelPool ()->GetNextOutboundTunnel (); - auto leases = remote.GetNonExpiredLeases (); + auto leases = remote->GetNonExpiredLeases (); if (!leases.empty () && outboundTunnel) { std::vector msgs; diff --git a/Datagram.h b/Datagram.h index b3eaa9e5..279f5d96 100644 --- a/Datagram.h +++ b/Datagram.h @@ -2,6 +2,7 @@ #define DATAGRAM_H__ #include +#include #include #include "Identity.h" #include "LeaseSet.h" @@ -25,7 +26,7 @@ namespace datagram DatagramDestination (i2p::client::ClientDestination& owner); ~DatagramDestination () {}; - void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::LeaseSet& remote); + void SendDatagramTo (const uint8_t * payload, size_t len, std::shared_ptr remote); void HandleDataMessagePayload (const uint8_t * buf, size_t len); void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; @@ -34,7 +35,7 @@ namespace datagram private: I2NPMessage * CreateDataMessage (const uint8_t * payload, size_t len); - void SendMsg (I2NPMessage * msg, const i2p::data::LeaseSet& remote); + void SendMsg (I2NPMessage * msg, std::shared_ptr remote); void HandleDatagram (const uint8_t * buf, size_t len); private: diff --git a/Destination.cpp b/Destination.cpp index b122172e..6414a6b3 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -341,7 +341,7 @@ namespace client m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); m_PublishReplyToken = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); - auto msg = WrapMessage (*floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken)); + auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&ClientDestination::HandlePublishConfirmationTimer, this, std::placeholders::_1)); @@ -508,7 +508,7 @@ namespace client rnd.GenerateBlock (replyTag, 32); // random session tag AddSessionKey (replyKey, replyTag); - I2NPMessage * msg = WrapMessage (*nextFloodfill, + I2NPMessage * msg = WrapMessage (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, replyTunnel.get (), replyKey, replyTag)); outboundTunnel->SendTunnelDataMsg ( diff --git a/Garlic.cpp b/Garlic.cpp index 6b2dc700..1bd3a8da 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -15,7 +15,7 @@ namespace i2p namespace garlic { GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner, - const i2p::data::RoutingDestination * destination, int numTags): + std::shared_ptr destination, int numTags): m_Owner (owner), m_Destination (destination), m_NumTags (numTags), m_LeaseSetUpdated (numTags > 0) { @@ -501,7 +501,7 @@ namespace garlic } } - I2NPMessage * GarlicDestination::WrapMessage (const i2p::data::RoutingDestination& destination, + I2NPMessage * GarlicDestination::WrapMessage (std::shared_ptr destination, I2NPMessage * msg, bool attachLeaseSet) { if (attachLeaseSet) // we should maintain this session @@ -511,23 +511,23 @@ namespace garlic } else // one time session { - GarlicRoutingSession session (this, &destination, 0); // don't use tag if no LeaseSet + GarlicRoutingSession session (this, destination, 0); // don't use tag if no LeaseSet return session.WrapSingleMessage (msg); } } std::shared_ptr GarlicDestination::GetRoutingSession ( - const i2p::data::RoutingDestination& destination, int numTags) + std::shared_ptr destination, int numTags) { - auto it = m_Sessions.find (destination.GetIdentHash ()); + auto it = m_Sessions.find (destination->GetIdentHash ()); std::shared_ptr session; if (it != m_Sessions.end ()) session = it->second; if (!session) { - session = std::make_shared (this, &destination, numTags); + session = std::make_shared (this, destination, numTags); std::unique_lock l(m_SessionsMutex); - m_Sessions[destination.GetIdentHash ()] = session; + m_Sessions[destination->GetIdentHash ()] = session; } return session; } diff --git a/Garlic.h b/Garlic.h index 523e57ef..69c6e237 100644 --- a/Garlic.h +++ b/Garlic.h @@ -67,7 +67,7 @@ namespace garlic public: - GarlicRoutingSession (GarlicDestination * owner, const i2p::data::RoutingDestination * destination, int numTags); + GarlicRoutingSession (GarlicDestination * owner, std::shared_ptr destination, int numTags); GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption ~GarlicRoutingSession (); I2NPMessage * WrapSingleMessage (I2NPMessage * msg); @@ -88,7 +88,7 @@ namespace garlic private: GarlicDestination * m_Owner; - const i2p::data::RoutingDestination * m_Destination; + std::shared_ptr m_Destination; i2p::crypto::AESKey m_SessionKey; std::list m_SessionTags; int m_NumTags; @@ -106,10 +106,10 @@ namespace garlic GarlicDestination (): m_LastTagsCleanupTime (0) {}; ~GarlicDestination (); - std::shared_ptr GetRoutingSession (const i2p::data::RoutingDestination& destination, int numTags); + std::shared_ptr GetRoutingSession (std::shared_ptr destination, int numTags); void CleanupRoutingSessions (); void RemoveCreatedSession (uint32_t msgID); - I2NPMessage * WrapMessage (const i2p::data::RoutingDestination& destination, + I2NPMessage * WrapMessage (std::shared_ptr destination, I2NPMessage * msg, bool attachLeaseSet = false); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag diff --git a/SAM.cpp b/SAM.cpp index b85fc4ee..10169808 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -776,7 +776,7 @@ namespace client auto leaseSet = i2p::data::netdb.FindLeaseSet (dest.GetIdentHash ()); if (leaseSet) session->localDestination->GetDatagramDestination ()-> - SendDatagramTo ((uint8_t *)eol, payloadLen, *leaseSet); + SendDatagramTo ((uint8_t *)eol, payloadLen, leaseSet); else { LogPrint ("SAM datagram destination not found"); diff --git a/Streaming.cpp b/Streaming.cpp index 6e89fa64..0b20c0e3 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -593,7 +593,7 @@ namespace stream if (m_RemoteLeaseSet) { if (!m_RoutingSession) - m_RoutingSession = m_LocalDestination.GetOwner ().GetRoutingSession (*m_RemoteLeaseSet, 32); + m_RoutingSession = m_LocalDestination.GetOwner ().GetRoutingSession (m_RemoteLeaseSet, 32); auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (); if (!leases.empty ()) { From 5887e8c8c4dce372dfd2f770eaf206dae094e7a0 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 29 Jan 2015 15:34:43 -0500 Subject: [PATCH 0111/6300] slow start and congestion avoidance --- Streaming.cpp | 27 ++++++++++++++++++++++++--- Streaming.h | 5 +++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 0b20c0e3..58335731 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -17,7 +17,8 @@ namespace stream m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local), m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), - m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port) + m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), + m_WindowSize (MIN_WINDOW_SIZE), m_LastWindowSizeIncreaseTime (0) { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); UpdateCurrentRemoteLease (); @@ -27,7 +28,8 @@ namespace stream m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_IsOpen (false), m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), - m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0) + m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), + m_WindowSize (MIN_WINDOW_SIZE), m_LastWindowSizeIncreaseTime (0) { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); } @@ -239,10 +241,29 @@ namespace stream m_SentPackets.erase (it++); delete sentPacket; acknowledged = true; + if (m_WindowSize < WINDOW_SIZE) + m_WindowSize++; // slow start + else + { + // linear growth + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (ts > m_LastWindowSizeIncreaseTime + INITIAL_RTT) + { + m_WindowSize++; + if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; + m_LastWindowSizeIncreaseTime = ts; + } + } } else break; } + if (nackCount > 0) + { + // congesion avoidance + m_WindowSize -= nackCount; + if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; + } if (m_SentPackets.empty ()) m_ResendTimer.cancel (); if (acknowledged) @@ -263,7 +284,7 @@ namespace stream void Stream::SendBuffer () { - int numMsgs = WINDOW_SIZE - m_SentPackets.size (); + int numMsgs = m_WindowSize - m_SentPackets.size (); if (numMsgs <= 0) return; // window is full bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet diff --git a/Streaming.h b/Streaming.h index 62452e6b..3d31f3e6 100644 --- a/Streaming.h +++ b/Streaming.h @@ -45,6 +45,9 @@ namespace stream const int ACK_SEND_TIMEOUT = 200; // in milliseconds const int MAX_NUM_RESEND_ATTEMPTS = 5; const int WINDOW_SIZE = 6; // in messages + const int MIN_WINDOW_SIZE = 1; + const int MAX_WINDOW_SIZE = 128; + const int INITIAL_RTT = 8000; // in milliseconds struct Packet { @@ -157,6 +160,8 @@ namespace stream std::mutex m_SendBufferMutex; std::stringstream m_SendBuffer; + int m_WindowSize; + uint64_t m_LastWindowSizeIncreaseTime; }; class StreamingDestination From aae837f6422458cc3edc7a6ea7cb4e16ead88426 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 29 Jan 2015 19:17:44 -0500 Subject: [PATCH 0112/6300] congesion control during retransmission --- Streaming.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 58335731..19f23f96 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -114,10 +114,8 @@ namespace stream { if (receivedSeqn <= m_LastReceivedSequenceNumber) { - // we have received duplicate. Most likely our outbound tunnel is dead + // we have received duplicate LogPrint (eLogWarning, "Duplicate message ", receivedSeqn, " received"); - m_CurrentOutboundTunnel = nullptr; // pick another outbound tunnel - UpdateCurrentRemoteLease (); // pick another lease SendQuickAck (); // resend ack for previous message again delete packet; // packet dropped } @@ -258,12 +256,6 @@ namespace stream else break; } - if (nackCount > 0) - { - // congesion avoidance - m_WindowSize -= nackCount; - if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; - } if (m_SentPackets.empty ()) m_ResendTimer.cancel (); if (acknowledged) @@ -568,10 +560,13 @@ namespace stream { if (ecode != boost::asio::error::operation_aborted) { + bool congesion = false; std::vector packets; for (auto it : m_SentPackets) { it->numResendAttempts++; + if (it->numResendAttempts == 1) // detect congesion at first attempt only + congesion = true; if (it->numResendAttempts <= MAX_NUM_RESEND_ATTEMPTS) packets.push_back (it); else @@ -585,8 +580,18 @@ namespace stream } if (packets.size () > 0) { - m_CurrentOutboundTunnel = nullptr; // pick another outbound tunnel - UpdateCurrentRemoteLease (); // pick another lease + if (congesion) + { + // congesion avoidance + m_WindowSize /= 2; + if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; + } + else + { + // congesion avoidance didn't help + m_CurrentOutboundTunnel = nullptr; // pick another outbound tunnel + UpdateCurrentRemoteLease (); // pick another lease + } SendPackets (packets); } ScheduleResend (); From 0b911a5caa5128b4b07b861409f882a14adf879a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 29 Jan 2015 22:35:57 -0500 Subject: [PATCH 0113/6300] use I2NPMessagesHandler for SSU --- SSUData.cpp | 4 +++- SSUData.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/SSUData.cpp b/SSUData.cpp index a4c47559..69b4287f 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -235,7 +235,7 @@ namespace transport { if (m_ReceivedMessages.size () > 100) m_ReceivedMessages.clear (); m_ReceivedMessages.insert (msgID); - i2p::HandleI2NPMessage (msg); + m_Handler.PutNextMessage (msg); } else { @@ -260,6 +260,8 @@ namespace transport SendFragmentAck (msgID, fragmentNum); buf += fragmentSize; } + if (numFragments > 0) + m_Handler.Flush (); } void SSUData::ProcessMessage (uint8_t * buf, size_t len) diff --git a/SSUData.h b/SSUData.h index e362acd3..57e14832 100644 --- a/SSUData.h +++ b/SSUData.h @@ -106,6 +106,7 @@ namespace transport std::set m_ReceivedMessages; boost::asio::deadline_timer m_ResendTimer; int m_MaxPacketSize, m_PacketSize; + i2p::I2NPMessagesHandler m_Handler; }; } } From 79af7c22d9df07efb63dfec45e80fff9b7bb45f0 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 30 Jan 2015 15:13:09 -0500 Subject: [PATCH 0114/6300] send DatabaseStore reply --- NetDb.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 6b0a36d0..29e616db 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -473,7 +473,28 @@ namespace data uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET); size_t offset = DATABASE_STORE_HEADER_SIZE; if (replyToken) - offset += 36; + { + auto deliveryStatus = CreateDeliveryStatusMsg (replyToken); + offset += 4; + uint32_t tunnelID = bufbe32toh (buf + offset); + offset += 32; + if (!tunnelID) // send response directly + transports.SendMessage (buf + offset, deliveryStatus); + else + { + auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); + auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; + if (outbound) + outbound->SendTunnelDataMsg (buf + offset, tunnelID, deliveryStatus); + else + { + LogPrint (eLogError, "No outbound tunnels for DatabaseStore reply found"); + DeleteI2NPMessage (deliveryStatus); + } + } + + // TODO: flood in case of floodfill. replyToken must be set to zero + } if (buf[DATABASE_STORE_TYPE_OFFSET]) // type { LogPrint ("LeaseSet"); From 9a43f0d54cb854a2a4b85a3521dba759496028a9 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 30 Jan 2015 15:29:33 -0500 Subject: [PATCH 0115/6300] don't send DatabaseStore reply for local destinations --- Destination.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Destination.cpp b/Destination.cpp index 6414a6b3..9b02f3fb 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -216,8 +216,11 @@ namespace client { uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET); size_t offset = DATABASE_STORE_HEADER_SIZE; - if (replyToken) // TODO: + if (replyToken) + { + LogPrint (eLogInfo, "Reply token is ignored for DatabaseStore"); offset += 36; + } if (buf[DATABASE_STORE_TYPE_OFFSET] == 1) // LeaseSet { LogPrint (eLogDebug, "Remote LeaseSet"); From 79087f6942931d92cf090343a7ec512541dfff23 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 30 Jan 2015 16:43:31 -0500 Subject: [PATCH 0116/6300] detect congesion for first message in sned queue only --- Streaming.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 19f23f96..b69b2d48 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -560,13 +560,14 @@ namespace stream { if (ecode != boost::asio::error::operation_aborted) { - bool congesion = false; + bool congesion = false, first = true; std::vector packets; for (auto it : m_SentPackets) { it->numResendAttempts++; - if (it->numResendAttempts == 1) // detect congesion at first attempt only + if (first && it->numResendAttempts == 1) // detect congesion at first attempt of first packet only congesion = true; + first = false; if (it->numResendAttempts <= MAX_NUM_RESEND_ATTEMPTS) packets.push_back (it); else From 618abd6320b80d593ba60dd0f028493d8e265e61 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 30 Jan 2015 20:30:39 -0500 Subject: [PATCH 0117/6300] use unique_ptr for sent fragments --- SSUData.cpp | 9 +++------ SSUData.h | 5 ++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/SSUData.cpp b/SSUData.cpp index 69b4287f..f60d7f13 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -115,10 +115,7 @@ namespace transport if (bitfield & mask) { if (fragment < numSentFragments) - { - delete it->second->fragments[fragment]; - it->second->fragments[fragment] = nullptr; - } + it->second->fragments[fragment].reset (nullptr); } fragment++; mask <<= 1; @@ -312,7 +309,6 @@ namespace transport Fragment * fragment = new Fragment; fragment->fragmentNum = fragmentNum; uint8_t * buf = fragment->buf; - fragments.push_back (fragment); uint8_t * payload = buf + sizeof (SSUHeader); *payload = DATA_FLAG_WANT_REPLY; // for compatibility payload++; @@ -336,6 +332,7 @@ namespace transport if (size & 0x0F) // make sure 16 bytes boundary size = ((size >> 4) + 1) << 4; // (/16 + 1)*16 fragment->len = size; + fragments.push_back (std::unique_ptr (fragment)); // encrypt message with session key m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, size); @@ -417,7 +414,7 @@ namespace transport { if (ts >= it.second->nextResendTime && it.second->numResends < MAX_NUM_RESENDS) { - for (auto f: it.second->fragments) + for (auto& f: it.second->fragments) if (f) m_Session.Send (f->buf, f->len); // resend it.second->numResends++; diff --git a/SSUData.h b/SSUData.h index 57e14832..421a646d 100644 --- a/SSUData.h +++ b/SSUData.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "I2NPProtocol.h" #include "Identity.h" @@ -65,11 +66,9 @@ namespace transport struct SentMessage { - std::vector fragments; + std::vector > fragments; uint32_t nextResendTime; // in seconds int numResends; - - ~SentMessage () { for (auto it: fragments) { delete it; }; }; }; class SSUSession; From 908404ab62655a073cf195b6caafa1d9bec44bef Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 30 Jan 2015 21:41:32 -0500 Subject: [PATCH 0118/6300] show send buffer size --- HTTPServer.cpp | 1 + Streaming.h | 1 + 2 files changed, 2 insertions(+) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index f55396a6..f675d525 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -839,6 +839,7 @@ namespace util s << it.first << "->" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << " "; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]"; + s << " [buf:" << it.second->GetSendBufferSize () << "]"; s << "
"<< std::endl; } } diff --git a/Streaming.h b/Streaming.h index 3d31f3e6..d3b4e20e 100644 --- a/Streaming.h +++ b/Streaming.h @@ -115,6 +115,7 @@ namespace stream size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; size_t GetSendQueueSize () const { return m_SentPackets.size (); }; size_t GetReceiveQueueSize () const { return m_ReceiveQueue.size (); }; + size_t GetSendBufferSize () const { return m_SendBuffer.rdbuf ()->in_avail (); }; private: From feffbc93308541e9dbdb200a89711840266f42f5 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 31 Jan 2015 10:39:29 -0500 Subject: [PATCH 0119/6300] fixed typo --- Destination.cpp | 1 - Tunnel.cpp | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 9b02f3fb..df6fe152 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -139,7 +139,6 @@ namespace client auto ls = i2p::data::netdb.FindLeaseSet (ident); if (ls) { - ls = std::make_shared (*ls); m_RemoteLeaseSets[ident] = ls; return ls; } diff --git a/Tunnel.cpp b/Tunnel.cpp index fe983ae1..229a43b8 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -351,9 +351,10 @@ namespace tunnel if (msg) { uint32_t prevTunnelID = 0, tunnelID = 0; - TunnelBase * prevTunnel = nullptr, * tunnel = nullptr; + TunnelBase * prevTunnel = nullptr; do { + TunnelBase * tunnel = nullptr; uint8_t typeID = msg->GetTypeID (); switch (typeID) { From ac438fbd7ddac7f69c120bd3e0a399c98196c229 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 31 Jan 2015 21:49:54 -0500 Subject: [PATCH 0120/6300] use stream buffer for sending identity --- SAM.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index 10169808..3564ce9c 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -148,7 +148,7 @@ namespace client void SAMSocket::SendMessageReply (const char * msg, size_t len, bool close) { - if (!m_IsSilent || m_SocketType == eSAMSocketTypeAcceptor) + if (!m_IsSilent) boost::asio::async_write (m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (), std::bind(&SAMSocket::HandleMessageReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, close)); @@ -320,7 +320,7 @@ namespace client i2p::data::IdentityEx dest; dest.FromBase64 (destination); context.GetAddressBook ().InsertAddress (dest); - auto leaseSet = i2p::data::netdb.FindLeaseSet (dest.GetIdentHash ()); + auto leaseSet = m_Session->localDestination->FindLeaseSet (dest.GetIdentHash ()); if (leaseSet) Connect (leaseSet); else @@ -347,7 +347,7 @@ namespace client void SAMSocket::HandleConnectLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident) { std::shared_ptr leaseSet; - if (success) // timeout expired + if (success) leaseSet = m_Session->localDestination->FindLeaseSet (ident); if (leaseSet) Connect (leaseSet); @@ -566,11 +566,12 @@ namespace client // send remote peer address uint8_t ident[1024]; size_t l = stream->GetRemoteIdentity ().ToBuffer (ident, 1024); - size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, m_Buffer, SAM_SOCKET_BUFFER_SIZE); - m_Buffer[l1] = '\n'; - SendMessageReply (m_Buffer, l1 + 1, false); + size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE); + m_StreamBuffer[l1] = '\n'; + HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream } - I2PReceive (); + else + I2PReceive (); } } From 2c2acae50db410a138fc43b2a0e0af9ed6adc416 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 1 Feb 2015 09:34:32 -0500 Subject: [PATCH 0121/6300] invoke acceptor on reset --- SAM.cpp | 5 ++++- Streaming.h | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index 3564ce9c..e2ba5c15 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -573,6 +573,8 @@ namespace client else I2PReceive (); } + else + LogPrint (eLogInfo, "SAM I2P acceptor has been reset"); } void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& ident, const uint8_t * buf, size_t len) @@ -709,7 +711,7 @@ namespace client // TODO: extract string values signatureType = boost::lexical_cast (it->second); } - localDestination = i2p::client::context.CreateNewLocalDestination (false, signatureType, params); + localDestination = i2p::client::context.CreateNewLocalDestination (true, signatureType, params); } if (localDestination) { @@ -729,6 +731,7 @@ namespace client if (it != m_Sessions.end ()) { auto session = it->second; + session->localDestination->StopAcceptingStreams (); session->CloseStreams (); m_Sessions.erase (it); delete session; diff --git a/Streaming.h b/Streaming.h index d3b4e20e..7b68dba7 100644 --- a/Streaming.h +++ b/Streaming.h @@ -180,7 +180,7 @@ namespace stream std::shared_ptr CreateNewOutgoingStream (std::shared_ptr remote, int port = 0); void DeleteStream (std::shared_ptr stream); void SetAcceptor (const Acceptor& acceptor) { m_Acceptor = acceptor; }; - void ResetAcceptor () { m_Acceptor = nullptr; }; + void ResetAcceptor () { if (m_Acceptor) m_Acceptor (nullptr); m_Acceptor = nullptr; }; bool IsAcceptorSet () const { return m_Acceptor != nullptr; }; i2p::client::ClientDestination& GetOwner () { return m_Owner; }; From 02ed7d60592ee5c98f0b20489940378cd7e88fb6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 1 Feb 2015 16:18:08 -0500 Subject: [PATCH 0122/6300] fixed non-responding destination request --- Destination.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Destination.cpp b/Destination.cpp index df6fe152..bc15bb78 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -543,7 +543,7 @@ namespace client { auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded); if (floodfill) - SendLeaseSetRequest (dest, floodfill, it->second); + done = !SendLeaseSetRequest (dest, floodfill, it->second); else done = true; } From b46c3036d884010be72daff9358d3d368ee49cf8 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 1 Feb 2015 18:30:27 -0500 Subject: [PATCH 0123/6300] flood --- NetDb.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 29e616db..da564ed2 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -493,8 +493,28 @@ namespace data } } - // TODO: flood in case of floodfill. replyToken must be set to zero + if (context.IsFloodfill ()) + { + // flood it + std::set excluded; + for (int i = 0; i < 3; i++) + { + auto floodfill = GetClosestFloodfill (buf + DATABASE_STORE_KEY_OFFSET, excluded); + if (floodfill) + { + auto floodMsg = NewI2NPShortMessage (); + uint8_t * payload = floodMsg->GetPayload (); + memcpy (payload, buf, 33); // key + type + htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); // zero reply token + memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + offset, len - offset); + floodMsg->len += DATABASE_STORE_HEADER_SIZE + len -offset; + FillI2NPMessageHeader (floodMsg, eI2NPDatabaseStore); + transports.SendMessage (floodfill->GetIdentHash (), floodMsg); + } + } + } } + if (buf[DATABASE_STORE_TYPE_OFFSET]) // type { LogPrint ("LeaseSet"); From 8ad9f2681cb643ed8df753602d43b020cb3ba9e3 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 1 Feb 2015 19:58:26 -0500 Subject: [PATCH 0124/6300] send 3 closest floodfills --- I2NPProtocol.cpp | 8 ++++---- I2NPProtocol.h | 2 +- NetDb.cpp | 24 +++++++++++++++--------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 087caa0b..ff434924 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -184,18 +184,18 @@ namespace i2p I2NPMessage * CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, - const i2p::data::RouterInfo * floodfill) + std::vector routers) { I2NPMessage * m = NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); size_t len = 0; memcpy (buf, ident, 32); len += 32; - buf[len] = floodfill ? 1 : 0; // 1 router for now + buf[len] = routers.size (); len++; - if (floodfill) + for (auto it: routers) { - memcpy (buf + len, floodfill->GetIdentHash (), 32); + memcpy (buf + len, it, 32); len += 32; } memcpy (buf + len, i2p::context.GetRouterInfo ().GetIdentHash (), 32); diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 5b68792c..31d7dbf9 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -194,7 +194,7 @@ namespace tunnel I2NPMessage * CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, const std::set& excludedFloodfills, const i2p::tunnel::InboundTunnel * replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag); - I2NPMessage * CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, const i2p::data::RouterInfo * floodfill); + I2NPMessage * CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::RouterInfo * router = nullptr, uint32_t replyToken = 0); I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::LeaseSet * leaseSet, uint32_t replyToken = 0); diff --git a/NetDb.cpp b/NetDb.cpp index da564ed2..d03b994c 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -664,16 +664,15 @@ namespace data I2NPMessage * replyMsg = nullptr; + auto router = FindRouter (buf); + if (router) { - auto router = FindRouter (buf); - if (router) - { - LogPrint ("Requested RouterInfo ", key, " found"); - router->LoadBuffer (); - if (router->GetBuffer ()) - replyMsg = CreateDatabaseStoreMsg (router.get ()); - } + LogPrint ("Requested RouterInfo ", key, " found"); + router->LoadBuffer (); + if (router->GetBuffer ()) + replyMsg = CreateDatabaseStoreMsg (router.get ()); } + if (!replyMsg) { auto leaseSet = FindLeaseSet (buf); @@ -693,7 +692,14 @@ namespace data excludedRouters.insert (excluded); excluded += 32; } - replyMsg = CreateDatabaseSearchReply (buf, GetClosestFloodfill (buf, excludedRouters).get ()); + std::vector routers; + for (int i = 0; i < 3; i++) + { + auto floodfill = GetClosestFloodfill (buf, excludedRouters); + if (floodfill) + routers.push_back (floodfill->GetIdentHash ()); + } + replyMsg = CreateDatabaseSearchReply (buf, routers); } else excluded += numExcluded*32; // we don't care about exluded From 803737011a14227b514609881d9197000ea4d0eb Mon Sep 17 00:00:00 2001 From: ygrishin Date: Sun, 1 Feb 2015 22:24:47 -0700 Subject: [PATCH 0125/6300] fixed HTTP connection being reset on Windows --- HTTPServer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index f675d525..569ebe51 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -605,7 +605,8 @@ namespace util { if (ecode != boost::asio::error::operation_aborted) { - m_Socket->close (); + boost::system::error_code ignored_ec; + m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); Terminate (); } } From ba11971513765ba3dcbb9923891ee6c2b9e2eba7 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Feb 2015 09:40:03 -0500 Subject: [PATCH 0126/6300] don't delete updated RouterInfo --- NetDb.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/NetDb.cpp b/NetDb.cpp index d03b994c..e9224c2c 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -401,6 +401,7 @@ namespace data { it.second->SaveToFile (GetFilePath(fullDirectory, it.second.get ())); it.second->SetUpdated (false); + it.second->SetUnreachable (false); it.second->DeleteBuffer (); count++; } From 24564f1faad1bf6094fe18d9b96b4caf42c6ea5f Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Feb 2015 09:58:34 -0500 Subject: [PATCH 0127/6300] moved UPNP to common files --- filelist.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/filelist.mk b/filelist.mk index 96115bd0..072864a0 100644 --- a/filelist.mk +++ b/filelist.mk @@ -3,7 +3,7 @@ COMMON_SRC = \ LeaseSet.cpp Log.cpp NTCPSession.cpp NetDb.cpp Reseed.cpp RouterContext.cpp \ RouterInfo.cpp SSU.cpp SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp \ TransitTunnel.cpp Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp \ - TunnelGateway.cpp Destination.cpp util.cpp aes.cpp base64.cpp + TunnelGateway.cpp Destination.cpp UPnP.cpp util.cpp aes.cpp base64.cpp ifeq ($(UNAME),Darwin) @@ -17,7 +17,7 @@ endif # also: Daemon{Linux,Win32}.cpp will be added later DAEMON_SRC = $(COMMON_SRC) \ - BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp I2PService.cpp SAM.cpp SOCKS.cpp UPnP.cpp \ + BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp I2PService.cpp SAM.cpp SOCKS.cpp \ HTTPServer.cpp HTTPProxy.cpp I2PControl.cpp i2p.cpp LIB_SRC := $(COMMON_SRC) \ From 07ce9c41bf5e87d24bc6e5a1b3e021c7703e7e2b Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Feb 2015 11:06:36 -0500 Subject: [PATCH 0128/6300] DatabaseLookup flags --- I2NPProtocol.cpp | 6 +++--- I2NPProtocol.h | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index ff434924..0fb85566 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -108,10 +108,10 @@ namespace i2p buf += 32; memcpy (buf, from, 32); // from buf += 32; - uint8_t flag = exploratory ? 0x0C : 0x08; // 1000 - RI, 1100 -exporatory + uint8_t flag = exploratory ? DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP : DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP; if (replyTunnelID) { - *buf = flag | 0x01; // set delivery flag + *buf = flag | DATABASE_LOOKUP_DELIVERY_FLAG; // set delivery flag htobe32buf (buf+1, replyTunnelID); buf += 5; } @@ -154,7 +154,7 @@ namespace i2p buf += 32; memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW buf += 32; - *buf = 7; // flags (01 - tunnel, 10 - encrypted, 0100 - LS lookup + *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags htobe32buf (buf + 1, replyTunnel->GetNextTunnelID ()); // reply tunnel ID buf += 5; diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 31d7dbf9..86aacd76 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -87,6 +87,15 @@ namespace i2p const int NUM_TUNNEL_BUILD_RECORDS = 8; + // DatabaseLookup flags + const uint8_t DATABASE_LOOKUP_DELIVERY_FLAG = 0x01; + const uint8_t DATABASE_LOOKUP_ENCYPTION_FLAG = 0x02; + const uint8_t DATABASE_LOOKUP_TYPE_FLAGS_MASK = 0x0C; + const uint8_t DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP = 0; + const uint8_t DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP = 0x04; // 0100 + const uint8_t DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP = 0x08; // 1000 + const uint8_t DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP = 0x0C; // 1100 + namespace tunnel { class InboundTunnel; From 2215dd7bd7b07c93b8e96458ef5b86b7d04b8881 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Feb 2015 11:14:09 -0500 Subject: [PATCH 0129/6300] ability to turn floodfill off --- Daemon.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index dbc65053..6fd54f0e 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -77,10 +77,8 @@ namespace i2p if (i2p::util::config::GetArg("-unreachable", 0)) i2p::context.SetUnreachable (); - if (i2p::util::config::GetArg("-floodfill", 0)) - i2p::context.SetFloodfill (true); - i2p::context.SetSupportsV6 (i2p::util::config::GetArg("-v6", 0)); + i2p::context.SetFloodfill (i2p::util::config::GetArg("-floodfill", 0)); LogPrint("CMD parameters:"); for (int i = 0; i < argc; ++i) From 60ba7be31977e6dd901ce9ae73eff65ccd9fe8ea Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Feb 2015 11:15:38 -0500 Subject: [PATCH 0130/6300] floodfill parameter --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index eb5a54eb..dbb9b5de 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ Cmdline options * --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). * --unreachable= - 1 if router is declared as unreachable and works through introducers. * --v6= - 1 if supports communication through ipv6, off by default +* --floodfill= - 1 if router is floodfill, off by default * --httpproxyport= - The port to listen on (HTTP Proxy) * --socksproxyport= - The port to listen on (SOCKS Proxy) * --ircport= - The local port of IRC tunnel to listen on. 6668 by default From d7c5c24ce4da0ca6b8a7cc31606ef77e9acb4b98 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Feb 2015 12:01:21 -0500 Subject: [PATCH 0131/6300] pass ident hash by value to transports thread --- Transports.cpp | 4 ++-- Transports.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index dfcfe253..2f0c95c0 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -189,7 +189,7 @@ namespace transport m_Service.post (std::bind (&Transports::PostMessages, this, ident, msgs)); } - void Transports::PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) + void Transports::PostMessage (i2p::data::IdentHash ident, i2p::I2NPMessage * msg) { if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) { @@ -215,7 +215,7 @@ namespace transport it->second.delayedMessages.push_back (msg); } - void Transports::PostMessages (const i2p::data::IdentHash& ident, std::vector msgs) + void Transports::PostMessages (i2p::data::IdentHash ident, std::vector msgs) { if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) { diff --git a/Transports.h b/Transports.h index d09699d6..e265f344 100644 --- a/Transports.h +++ b/Transports.h @@ -91,8 +91,8 @@ namespace transport void Run (); void RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); void HandleRequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); - void PostMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); - void PostMessages (const i2p::data::IdentHash& ident, std::vector msgs); + void PostMessage (i2p::data::IdentHash ident, i2p::I2NPMessage * msg); + void PostMessages (i2p::data::IdentHash ident, std::vector msgs); void PostCloseSession (std::shared_ptr router); bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); From e3764bef379d2fe3e9bcfc7f93010b9b448adfad Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Feb 2015 13:06:02 -0500 Subject: [PATCH 0132/6300] fixed incorect reply data parsing for DatabaseStore --- NetDb.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index e9224c2c..9693f2dc 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -475,10 +475,9 @@ namespace data size_t offset = DATABASE_STORE_HEADER_SIZE; if (replyToken) { - auto deliveryStatus = CreateDeliveryStatusMsg (replyToken); - offset += 4; + auto deliveryStatus = CreateDeliveryStatusMsg (replyToken); uint32_t tunnelID = bufbe32toh (buf + offset); - offset += 32; + offset += 4; if (!tunnelID) // send response directly transports.SendMessage (buf + offset, deliveryStatus); else @@ -493,6 +492,7 @@ namespace data DeleteI2NPMessage (deliveryStatus); } } + offset += 32; if (context.IsFloodfill ()) { From f846b87590a9ac41e1f6183cd008d28ae48b20f2 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Feb 2015 16:08:35 -0500 Subject: [PATCH 0133/6300] make sure DeliveryStatus and DatabseStore are sent first --- SSUData.cpp | 3 +-- SSUSession.cpp | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/SSUData.cpp b/SSUData.cpp index f60d7f13..72d283ee 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -298,7 +298,6 @@ namespace transport sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL; sentMessage->numResends = 0; auto& fragments = sentMessage->fragments; - msgID = htobe32 (msgID); size_t payloadSize = m_PacketSize - sizeof (SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3) size_t len = msg->GetLength (); uint8_t * msgBuf = msg->GetSSUHeader (); @@ -314,7 +313,7 @@ namespace transport payload++; *payload = 1; // always 1 message fragment per message payload++; - *(uint32_t *)payload = msgID; + htobe32buf (payload, msgID); payload += 4; bool isLast = (len <= payloadSize); size_t size = isLast ? len : payloadSize; diff --git a/SSUSession.cpp b/SSUSession.cpp index 0cbbc818..c5e04d23 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -255,7 +255,7 @@ namespace transport if (paddingSize > 0) paddingSize = 16 - paddingSize; payload += paddingSize; // TODO: verify signature (need data from session request), payload points to signature - SendI2NPMessage (CreateDeliveryStatusMsg (0)); + m_Data.Send (CreateDeliveryStatusMsg (0)); Established (); } @@ -767,7 +767,7 @@ namespace transport delete m_DHKeysPair; m_DHKeysPair = nullptr; } - SendI2NPMessage (CreateDatabaseStoreMsg ()); + m_Data.Send (CreateDatabaseStoreMsg ()); transports.PeerConnected (shared_from_this ()); if (m_PeerTest && (m_RemoteRouter && m_RemoteRouter->IsPeerTesting ())) SendPeerTest (); From 95dbc20350f65c0e7b3fbd359e4850c0a49df34e Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Feb 2015 20:15:49 -0500 Subject: [PATCH 0134/6300] log more disgnostics data --- NetDb.cpp | 11 ++++++----- SSUData.cpp | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 9693f2dc..efb5fdab 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -525,12 +525,13 @@ namespace data { LogPrint ("RouterInfo"); size_t size = bufbe16toh (buf + offset); - if (size > 2048) + offset += 2; + if (size > 2048 || size > len - offset) { LogPrint ("Invalid RouterInfo length ", (int)size); + i2p::DeleteI2NPMessage (m); return; } - offset += 2; CryptoPP::Gunzip decompressor; decompressor.Put (buf + offset, size); decompressor.MessageEnd(); @@ -646,11 +647,11 @@ namespace data char key[48]; int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48); key[l] = 0; - LogPrint ("DatabaseLookup for ", key, " recieved"); uint8_t flag = buf[64]; + LogPrint ("DatabaseLookup for ", key, " recieved flags=", (int)flag); uint8_t * excluded = buf + 65; uint32_t replyTunnelID = 0; - if (flag & 0x01) //reply to tunnel + if (flag & DATABASE_LOOKUP_DELIVERY_FLAG) //reply to tunnel { replyTunnelID = bufbe32toh (buf + 64); excluded += 4; @@ -710,7 +711,7 @@ namespace data if (replyTunnelID) { // encryption might be used though tunnel only - if (flag & 0x02) // encrypted reply requested + if (flag & DATABASE_LOOKUP_ENCYPTION_FLAG) // encrypted reply requested { uint8_t * sessionKey = excluded; uint8_t numTags = sessionKey[32]; diff --git a/SSUData.cpp b/SSUData.cpp index 72d283ee..9902448e 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -266,7 +266,7 @@ namespace transport //uint8_t * start = buf; uint8_t flag = *buf; buf++; - LogPrint (eLogDebug, "Process SSU data flags=", (int)flag); + LogPrint (eLogDebug, "Process SSU data flags=", (int)flag, " len=", len); // process acks if presented if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED)) ProcessAcks (buf, flag); From c5a3832eaed2b2370e36e6fa2b0f31e1de9ad0f4 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Feb 2015 22:34:55 -0500 Subject: [PATCH 0135/6300] handle exploratory lookups --- NetDb.cpp | 105 +++++++++++++++++++++++++++++++++++++++--------------- NetDb.h | 1 + 2 files changed, 77 insertions(+), 29 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index efb5fdab..35377e99 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -649,6 +649,7 @@ namespace data key[l] = 0; uint8_t flag = buf[64]; LogPrint ("DatabaseLookup for ", key, " recieved flags=", (int)flag); + uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK; uint8_t * excluded = buf + 65; uint32_t replyTunnelID = 0; if (flag & DATABASE_LOOKUP_DELIVERY_FLAG) //reply to tunnel @@ -663,49 +664,72 @@ namespace data LogPrint ("Number of excluded peers", numExcluded, " exceeds 512"); numExcluded = 0; // TODO: } - - I2NPMessage * replyMsg = nullptr; - - auto router = FindRouter (buf); - if (router) - { - LogPrint ("Requested RouterInfo ", key, " found"); - router->LoadBuffer (); - if (router->GetBuffer ()) - replyMsg = CreateDatabaseStoreMsg (router.get ()); - } - if (!replyMsg) + I2NPMessage * replyMsg = nullptr; + if (lookupType == DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP) { - auto leaseSet = FindLeaseSet (buf); - if (leaseSet) // we don't send back our LeaseSets - { - LogPrint ("Requested LeaseSet ", key, " found"); - replyMsg = CreateDatabaseStoreMsg (leaseSet.get ()); - } - } - if (!replyMsg) - { - LogPrint ("Requested ", key, " not found. ", numExcluded, " excluded"); + LogPrint ("Exploratory close to ", key, " ", numExcluded, " excluded"); std::set excludedRouters; for (int i = 0; i < numExcluded; i++) { - // TODO: check for all zeroes (exploratory) excludedRouters.insert (excluded); excluded += 32; } std::vector routers; for (int i = 0; i < 3; i++) { - auto floodfill = GetClosestFloodfill (buf, excludedRouters); - if (floodfill) - routers.push_back (floodfill->GetIdentHash ()); + auto r = GetClosestNonFloodfill (buf, excludedRouters); + if (r) + { + routers.push_back (r->GetIdentHash ()); + excludedRouters.insert (r->GetIdentHash ()); + } } replyMsg = CreateDatabaseSearchReply (buf, routers); - } + } else - excluded += numExcluded*32; // we don't care about exluded - + { + auto router = FindRouter (buf); + if (router) + { + LogPrint ("Requested RouterInfo ", key, " found"); + router->LoadBuffer (); + if (router->GetBuffer ()) + replyMsg = CreateDatabaseStoreMsg (router.get ()); + } + + if (!replyMsg) + { + auto leaseSet = FindLeaseSet (buf); + if (leaseSet) // we don't send back our LeaseSets + { + LogPrint ("Requested LeaseSet ", key, " found"); + replyMsg = CreateDatabaseStoreMsg (leaseSet.get ()); + } + } + if (!replyMsg) + { + LogPrint ("Requested ", key, " not found. ", numExcluded, " excluded"); + std::set excludedRouters; + for (int i = 0; i < numExcluded; i++) + { + excludedRouters.insert (excluded); + excluded += 32; + } + std::vector routers; + for (int i = 0; i < 3; i++) + { + auto floodfill = GetClosestFloodfill (buf, excludedRouters); + if (floodfill) + { + routers.push_back (floodfill->GetIdentHash ()); + excludedRouters.insert (floodfill->GetIdentHash ()); + } + } + replyMsg = CreateDatabaseSearchReply (buf, routers); + } + } + if (replyMsg) { if (replyTunnelID) @@ -903,6 +927,29 @@ namespace data return r; } + std::shared_ptr NetDb::GetClosestNonFloodfill (const IdentHash& destination, + const std::set& excluded) const + { + std::shared_ptr r; + XORMetric minMetric; + IdentHash destKey = CreateRoutingKey (destination); + minMetric.SetMax (); + // must be called from NetDb thread only + for (auto it: m_RouterInfos) + { + if (!it.second->IsFloodfill () && !excluded.count (it.first)) + { + XORMetric m = destKey ^ it.first; + if (m < minMetric) + { + minMetric = m; + r = it.second; + } + } + } + return r; + } + void NetDb::ManageLeaseSets () { for (auto it = m_LeaseSets.begin (); it != m_LeaseSets.end ();) diff --git a/NetDb.h b/NetDb.h index dfa7c7fb..2c00d3c0 100644 --- a/NetDb.h +++ b/NetDb.h @@ -80,6 +80,7 @@ namespace data std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith) const; std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith) const; std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded) const; + std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; void SetUnreachable (const IdentHash& ident, bool unreachable); void PostI2NPMsg (I2NPMessage * msg); From 4a6847da8d2342222df3f60fb48e2322040ae083 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Feb 2015 13:46:44 -0500 Subject: [PATCH 0136/6300] RTT --- HTTPServer.cpp | 3 ++- Streaming.cpp | 27 ++++++++++++++++++--------- Streaming.h | 7 +++++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 569ebe51..2e50e0fd 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -840,7 +840,8 @@ namespace util s << it.first << "->" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << " "; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]"; - s << " [buf:" << it.second->GetSendBufferSize () << "]"; + s << "[buf:" << it.second->GetSendBufferSize () << "]"; + s << "[RTT:" << it.second->GetRTT () << "]"; s << "
"<< std::endl; } } diff --git a/Streaming.cpp b/Streaming.cpp index b69b2d48..d4fbc208 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -15,10 +15,9 @@ namespace stream std::shared_ptr remote, int port): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_IsOpen (false), m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local), - m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), - m_ResendTimer (m_Service), m_AckSendTimer (m_Service), - m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), - m_WindowSize (MIN_WINDOW_SIZE), m_LastWindowSizeIncreaseTime (0) + m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), + m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), + m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_LastWindowSizeIncreaseTime (0) { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); UpdateCurrentRemoteLease (); @@ -28,8 +27,8 @@ namespace stream m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_IsOpen (false), m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), - m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), - m_WindowSize (MIN_WINDOW_SIZE), m_LastWindowSizeIncreaseTime (0) + m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE), + m_RTT (INITIAL_RTT), m_LastWindowSizeIncreaseTime (0) { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); } @@ -211,6 +210,7 @@ namespace stream void Stream::ProcessAck (Packet * packet) { bool acknowledged = false; + auto ts = i2p::util::GetMillisecondsSinceEpoch (); uint32_t ackThrough = packet->GetAckThrough (); int nackCount = packet->GetNACKCount (); for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();) @@ -235,7 +235,9 @@ namespace stream } } auto sentPacket = *it; - LogPrint (eLogDebug, "Packet ", seqn, " acknowledged"); + uint64_t rtt = ts - sentPacket->sendTime; + m_RTT = (m_RTT*seqn + rtt)/(seqn + 1); + LogPrint (eLogDebug, "Packet ", seqn, " acknowledged rtt=", rtt); m_SentPackets.erase (it++); delete sentPacket; acknowledged = true; @@ -244,8 +246,7 @@ namespace stream else { // linear growth - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (ts > m_LastWindowSizeIncreaseTime + INITIAL_RTT) + if (ts > m_LastWindowSizeIncreaseTime + m_RTT) { m_WindowSize++; if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; @@ -348,8 +349,12 @@ namespace stream m_IsAckSendScheduled = false; m_AckSendTimer.cancel (); bool isEmpty = m_SentPackets.empty (); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it: packets) + { + it->sendTime = ts; m_SentPackets.insert (it); + } SendPackets (packets); if (isEmpty) ScheduleResend (); @@ -560,6 +565,7 @@ namespace stream { if (ecode != boost::asio::error::operation_aborted) { + auto ts = i2p::util::GetMillisecondsSinceEpoch (); bool congesion = false, first = true; std::vector packets; for (auto it : m_SentPackets) @@ -569,7 +575,10 @@ namespace stream congesion = true; first = false; if (it->numResendAttempts <= MAX_NUM_RESEND_ATTEMPTS) + { + it->sendTime = ts; packets.push_back (it); + } else { LogPrint (eLogWarning, "Packet ", it->GetSeqn (), " was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts. Terminate"); diff --git a/Streaming.h b/Streaming.h index 7b68dba7..e91b11c7 100644 --- a/Streaming.h +++ b/Streaming.h @@ -54,8 +54,9 @@ namespace stream size_t len, offset; uint8_t buf[MAX_PACKET_SIZE]; int numResendAttempts; + uint64_t sendTime; - Packet (): len (0), offset (0), numResendAttempts (0) {}; + Packet (): len (0), offset (0), numResendAttempts (0), sendTime (0) {}; uint8_t * GetBuffer () { return buf + offset; }; size_t GetLength () const { return len - offset; }; @@ -116,6 +117,8 @@ namespace stream size_t GetSendQueueSize () const { return m_SentPackets.size (); }; size_t GetReceiveQueueSize () const { return m_ReceiveQueue.size (); }; size_t GetSendBufferSize () const { return m_SendBuffer.rdbuf ()->in_avail (); }; + int GetWindowSize () const { return m_WindowSize; }; + int GetRTT () const { return m_RTT; }; private: @@ -161,7 +164,7 @@ namespace stream std::mutex m_SendBufferMutex; std::stringstream m_SendBuffer; - int m_WindowSize; + int m_WindowSize, m_RTT; uint64_t m_LastWindowSizeIncreaseTime; }; From cd8e9e59fa75476aae649af1e065488665855b65 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Feb 2015 16:14:33 -0500 Subject: [PATCH 0137/6300] don't request same RouterInfo twice --- NetDb.cpp | 11 ++++++++++- NetDb.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 35377e99..ff166746 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -455,7 +455,16 @@ namespace data // request RouterInfo directly RequestedDestination * dest = CreateRequestedDestination (destination, false); if (requestComplete) - dest->SetRequestComplete (requestComplete); + { + if (dest->IsRequestComplete ()) // if set already + { + LogPrint (eLogWarning, "Destination ", destination.ToBase64(), " is requested already"); + requestComplete (nullptr); // TODO: implement it better + return; + } + else + dest->SetRequestComplete (requestComplete); + } auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ()); if (floodfill) transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); diff --git a/NetDb.h b/NetDb.h index 2c00d3c0..a1287d63 100644 --- a/NetDb.h +++ b/NetDb.h @@ -42,6 +42,7 @@ namespace data I2NPMessage * CreateRequestMessage (const IdentHash& floodfill); void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; }; + bool IsRequestComplete () const { return m_RequestComplete != nullptr; }; void Success (std::shared_ptr r); void Fail (); From 14f448f4c7d650534e1b86837caecafb3f86db80 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Feb 2015 16:45:19 -0500 Subject: [PATCH 0138/6300] show tunnels queue size --- HTTPServer.cpp | 2 ++ Queue.h | 8 +++++++- Tunnel.h | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 2e50e0fd..7eeb47e7 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -751,6 +751,8 @@ namespace util void HTTPConnection::ShowTunnels (std::stringstream& s) { + s << "Queue size:" << i2p::tunnel::tunnels.GetQueueSize () << "
"; + for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) { it->GetTunnelConfig ()->Print (s); diff --git a/Queue.h b/Queue.h index 2749da6a..086d6d81 100644 --- a/Queue.h +++ b/Queue.h @@ -76,7 +76,13 @@ namespace util std::unique_lock l(m_QueueMutex); return m_Queue.empty (); } - + + int GetSize () + { + std::unique_lock l(m_QueueMutex); + return m_Queue.size (); + } + void WakeUp () { m_NonEmpty.notify_all (); }; Element * Get () diff --git a/Tunnel.h b/Tunnel.h index db29b52a..0db74277 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -181,6 +181,7 @@ namespace tunnel const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; }; const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; }; + int GetQueueSize () { return m_Queue.GetSize (); }; }; extern Tunnels tunnels; From b923b1e31dab338490e70942dd3859372f8065be Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Feb 2015 20:59:12 -0500 Subject: [PATCH 0139/6300] Update version.h release 0.7.0 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 668b5798..49613de6 100644 --- a/version.h +++ b/version.h @@ -2,7 +2,7 @@ #define _VERSION_H_ #define CODENAME "Purple" -#define VERSION "0.6.0" +#define VERSION "0.7.0" #define I2P_VERSION "0.9.17" #endif From bd035e1c3defe458ea06d482a5664c105722f819 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 4 Feb 2015 09:40:00 -0500 Subject: [PATCH 0140/6300] fixed build for boost below 1.49 --- Transports.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Transports.cpp b/Transports.cpp index 2f0c95c0..ddfde44b 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -255,7 +255,13 @@ namespace transport auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ()); if (address) { +#if BOOST_VERSION >= 104900 if (!address->host.is_unspecified ()) // we have address now +#else + boost::system::error_code ecode; + address->host.to_string (ecode); + if (!ecode) +#endif { if (!peer.router->UsesIntroducer () && !peer.router->IsUnreachable ()) { From 02c22b850bc66eaf2d431b18cd7f682514079b4b Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 4 Feb 2015 11:08:26 -0500 Subject: [PATCH 0141/6300] fixed compilation bug for boost < 1.49 and boost 1.49 with gcc 4.7 --- I2PControl.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 913f823c..be32be41 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -1,11 +1,15 @@ -#include "I2PControl.h" +// There is bug in boost 1.49 with gcc 4.7 coming with Debian Wheezy +#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ != 4) && (__GNUC_MINOR__ != 7)) +#include "I2PControl.h" #include #include #include #include #include +#if !GCC47_BOOST149 #include +#endif #include "Log.h" #include "NetDb.h" #include "RouterContext.h" @@ -111,7 +115,12 @@ namespace client void I2PControlService::ReadRequest (std::shared_ptr socket) { auto request = std::make_shared(); - socket->async_read_some (boost::asio::buffer (*request), + socket->async_read_some ( +#if BOOST_VERSION >= 104900 + boost::asio::buffer (*request), +#else + boost::asio::buffer (request->data (), request->size ()), +#endif std::bind(&I2PControlService::HandleRequestReceived, this, std::placeholders::_1, std::placeholders::_2, socket, request)); } @@ -142,8 +151,12 @@ namespace client return; // TODO: } } +#if GCC47_BOOST149 + LogPrint (eLogError, "json_read is not supported due bug in boost 1.49 with gcc 4.7"); +#else boost::property_tree::ptree pt; boost::property_tree::read_json (ss, pt); + std::string method = pt.get(I2P_CONTROL_PROPERTY_METHOD); auto it = m_MethodHandlers.find (method); if (it != m_MethodHandlers.end ()) @@ -161,6 +174,7 @@ namespace client } else LogPrint (eLogWarning, "Unknown I2PControl method ", method); +#endif } catch (std::exception& ex) { @@ -187,7 +201,11 @@ namespace client pt.put ("jsonrpc", "2.0"); std::ostringstream ss; +#if GCC47_BOOST149 + LogPrint (eLogError, "json_write is not supported due bug in boost 1.49 with gcc 4.7"); +#else boost::property_tree::write_json (ss, pt, false); +#endif size_t len = ss.str ().length (), offset = 0; if (isHtml) { From 778d1afda0ef5e3cd1cb06e8ff1c686b53b52d8d Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 4 Feb 2015 15:34:52 -0500 Subject: [PATCH 0142/6300] cleand destination requests every 15 seconds --- NetDb.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index ff166746..da3f0214 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -124,7 +124,7 @@ namespace data void NetDb::Run () { - uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0; + uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0; while (m_IsRunning) { try @@ -156,12 +156,14 @@ namespace data } } else - { if (!m_IsRunning) break; - ManageRequests (); - } uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + if (ts - lastManageRequest >= 15) // manage requests every 15 seconds + { + ManageRequests (); + lastManageRequest = ts; + } if (ts - lastSave >= 60) // save routers, manage leasesets and validate subscriptions every minute { if (lastSave) @@ -183,7 +185,8 @@ namespace data { numRouters = 800/numRouters; if (numRouters < 1) numRouters = 1; - if (numRouters > 9) numRouters = 9; + if (numRouters > 9) numRouters = 9; + ManageRequests (); Explore (numRouters); lastExploratory = ts; } @@ -976,16 +979,17 @@ namespace data void NetDb::ManageRequests () { uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + std::unique_lock l(m_RequestedDestinationsMutex); for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();) { auto dest = it->second; bool done = false; - if (!dest->IsExploratory () && ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute + if (ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute { if (ts > dest->GetCreationTime () + 5) // no response for 5 seconds { auto count = dest->GetExcludedPeers ().size (); - if (count < 7) + if (!dest->IsExploratory () && count < 7) { auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); auto outbound = pool->GetNextOutboundTunnel (); @@ -1004,17 +1008,18 @@ namespace data } else { - LogPrint (eLogWarning, dest->GetDestination ().ToBase64 (), " not found after 7 attempts"); + if (!dest->IsExploratory ()) + LogPrint (eLogWarning, dest->GetDestination ().ToBase64 (), " not found after 7 attempts"); done = true; } } } - else // delete previous exploratory + else // delete obsolete request done = true; if (done) { - delete it->second; + delete dest; it = m_RequestedDestinations.erase (it); } else From 9639ab7f1e57e9867bfccb42b02efc700bfd7e8a Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 4 Feb 2015 21:24:48 -0500 Subject: [PATCH 0143/6300] fixed memory leak --- TunnelGateway.cpp | 6 ++++++ TunnelGateway.h | 1 + 2 files changed, 7 insertions(+) diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index a2352031..561a1068 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -10,6 +10,12 @@ namespace i2p { namespace tunnel { + TunnelGatewayBuffer::~TunnelGatewayBuffer () + { + for (auto it: m_TunnelDataMsgs) + DeleteI2NPMessage (it); + } + void TunnelGatewayBuffer::PutI2NPMsg (const TunnelMessageBlock& block) { bool messageCreated = false; diff --git a/TunnelGateway.h b/TunnelGateway.h index abed9288..b81c01d3 100644 --- a/TunnelGateway.h +++ b/TunnelGateway.h @@ -15,6 +15,7 @@ namespace tunnel public: TunnelGatewayBuffer (uint32_t tunnelID): m_TunnelID (tunnelID), m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0) {}; + ~TunnelGatewayBuffer (); void PutI2NPMsg (const TunnelMessageBlock& block); const std::vector& GetTunnelDataMsgs () const { return m_TunnelDataMsgs; }; void ClearTunnelDataMsgs (); From 9896570e327d03994a969f72b82269da7a926e5d Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 4 Feb 2015 22:05:09 -0500 Subject: [PATCH 0144/6300] cleanup from extra log messages --- TransitTunnel.cpp | 2 -- TunnelEndpoint.cpp | 35 +++++++++++++++++------------------ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index c1657930..31cbb4f8 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -44,7 +44,6 @@ namespace tunnel void TransitTunnelParticipant::FlushTunnelDataMsgs () { - LogPrint (eLogDebug, "TransitTunnel: flush"); if (!m_TunnelDataMsgs.empty ()) { i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); @@ -75,7 +74,6 @@ namespace tunnel void TransitTunnelGateway::FlushTunnelDataMsgs () { - LogPrint (eLogDebug, "TransitTunnel: gateway flush"); m_Gateway.SendBuffer (); } diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index d7fa821b..c9a642be 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -27,7 +27,6 @@ namespace tunnel uint8_t * zero = (uint8_t *)memchr (decrypted + 4, 0, TUNNEL_DATA_ENCRYPTED_SIZE - 4); // witout 4-byte checksum if (zero) { - LogPrint ("TunnelMessage: zero found at ", (int)(zero-decrypted)); uint8_t * fragment = zero + 1; // verify checksum memcpy (msg->GetPayload () + TUNNEL_DATA_MSG_SIZE, msg->GetPayload () + 4, 16); // copy iv to the end @@ -35,7 +34,7 @@ namespace tunnel CryptoPP::SHA256().CalculateDigest (hash, fragment, TUNNEL_DATA_MSG_SIZE -(fragment - msg->GetPayload ()) + 16); // payload + iv if (memcmp (hash, decrypted, 4)) { - LogPrint ("TunnelMessage: checksum verification failed"); + LogPrint (eLogError, "TunnelMessage: checksum verification failed"); i2p::DeleteI2NPMessage (msg); return; } @@ -57,17 +56,14 @@ namespace tunnel switch (m.deliveryType) { case eDeliveryTypeLocal: // 0 - LogPrint ("Delivery type local"); break; case eDeliveryTypeTunnel: // 1 - LogPrint ("Delivery type tunnel"); m.tunnelID = bufbe32toh (fragment); fragment += 4; // tunnelID m.hash = i2p::data::IdentHash (fragment); fragment += 32; // hash break; case eDeliveryTypeRouter: // 2 - LogPrint ("Delivery type router"); m.hash = i2p::data::IdentHash (fragment); fragment += 32; // to hash break; @@ -81,7 +77,6 @@ namespace tunnel // Message ID msgID = bufbe32toh (fragment); fragment += 4; - LogPrint ("Fragmented message ", msgID); isLastFragment = false; } } @@ -92,12 +87,10 @@ namespace tunnel fragment += 4; fragmentNum = (flag >> 1) & 0x3F; // 6 bits isLastFragment = flag & 0x01; - LogPrint ("Follow on fragment ", fragmentNum, " of message ", msgID, isLastFragment ? " last" : " non-last"); } uint16_t size = bufbe16toh (fragment); fragment += 2; - LogPrint ("Fragment size=", (int)size); msg->offset = fragment - msg->buf; msg->len = msg->offset + size; @@ -131,8 +124,11 @@ namespace tunnel HandleFollowOnFragment (msgID, isLastFragment, m); } } - else - LogPrint ("Message is fragmented, but msgID is not presented"); + else + { + LogPrint (eLogError, "Message is fragmented, but msgID is not presented"); + DeleteI2NPMessage (m.data); + } } fragment += size; @@ -140,7 +136,7 @@ namespace tunnel } else { - LogPrint ("TunnelMessage: zero not found"); + LogPrint (eLogError, "TunnelMessage: zero not found"); i2p::DeleteI2NPMessage (msg); } } @@ -173,7 +169,7 @@ namespace tunnel } else { - LogPrint ("Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size. Message dropped"); + LogPrint (eLogError, "Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size. Message dropped"); i2p::DeleteI2NPMessage (msg.data); m_IncompleteMessages.erase (it); } @@ -181,13 +177,13 @@ namespace tunnel } else { - LogPrint ("Unexpected fragment ", (int)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ". Saved"); + LogPrint (eLogInfo, "Unexpected fragment ", (int)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ". Saved"); AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); } } else { - LogPrint ("First fragment of message ", msgID, " not found. Saved"); + LogPrint (eLogInfo, "First fragment of message ", msgID, " not found. Saved"); AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); } } @@ -208,7 +204,7 @@ namespace tunnel { if (it->second.fragmentNum == msg.nextFragmentNum) { - LogPrint ("Out-of-sequence fragment ", (int)it->second.fragmentNum, " of message ", msgID, " found"); + LogPrint (eLogInfo, "Out-of-sequence fragment ", (int)it->second.fragmentNum, " of message ", msgID, " found"); auto size = it->second.data->GetLength (); memcpy (msg.data->buf + msg.data->len, it->second.data->GetBuffer (), size); // concatenate out-of-sync fragment msg.data->len += size; @@ -228,7 +224,7 @@ namespace tunnel void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg) { - LogPrint ("TunnelMessage: handle fragment of ", msg.data->GetLength ()," bytes. Msg type ", (int)msg.data->GetTypeID ()); + LogPrint (eLogInfo, "TunnelMessage: handle fragment of ", msg.data->GetLength ()," bytes. Msg type ", (int)msg.data->GetTypeID ()); switch (msg.deliveryType) { case eDeliveryTypeLocal: @@ -257,13 +253,16 @@ namespace tunnel } else // we shouldn't send this message. possible leakage { - LogPrint ("Message to another router arrived from an inbound tunnel. Dropped"); + LogPrint (eLogError, "Message to another router arrived from an inbound tunnel. Dropped"); i2p::DeleteI2NPMessage (msg.data); } } break; default: - LogPrint ("TunnelMessage: Unknown delivery type ", (int)msg.deliveryType); + { + LogPrint (eLogError, "TunnelMessage: Unknown delivery type ", (int)msg.deliveryType); + i2p::DeleteI2NPMessage (msg.data); + } }; } } From 0dac2a74d30dfc20d7f6c81271ad5ec88b459780 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 4 Feb 2015 22:16:44 -0500 Subject: [PATCH 0145/6300] check for duplicate msgID --- TunnelEndpoint.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index c9a642be..0ed86e4a 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -114,9 +114,14 @@ namespace tunnel if (!isFollowOnFragment) // create new incomlete message { m.nextFragmentNum = 1; - auto& msg = m_IncompleteMessages[msgID]; - msg = m; - HandleOutOfSequenceFragment (msgID, msg); + auto ret = m_IncompleteMessages.insert (std::pair(msgID, m)); + if (ret.second) + HandleOutOfSequenceFragment (msgID, ret.first->second); + else + { + LogPrint (eLogError, "Incomplete message ", msgID, "already exists"); + DeleteI2NPMessage (m.data); + } } else { From 408a67f34fe9c772e673d787d1ec0e5ab8a8ee78 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 5 Feb 2015 12:13:37 -0500 Subject: [PATCH 0146/6300] use unique_ptr for incomplete message --- SSUData.cpp | 15 ++++----------- SSUData.h | 6 +++--- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/SSUData.cpp b/SSUData.cpp index 9902448e..5bdb9702 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -23,11 +23,7 @@ namespace transport SSUData::~SSUData () { for (auto it: m_IncomleteMessages) - if (it.second) - { - DeleteI2NPMessage (it.second->msg); - delete it.second; - } + delete it.second; for (auto it: m_SentMessages) delete it.second; } @@ -182,7 +178,7 @@ namespace transport // try saved fragments for (auto it1 = incompleteMessage->savedFragments.begin (); it1 != incompleteMessage->savedFragments.end ();) { - auto savedFragment = *it1; + auto& savedFragment = *it1; if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum) { memcpy (msg->buf + msg->len, savedFragment->buf, savedFragment->len); @@ -190,7 +186,6 @@ namespace transport isLast = savedFragment->isLast; incompleteMessage->nextFragmentNum++; incompleteMessage->savedFragments.erase (it1++); - delete savedFragment; } else break; @@ -209,11 +204,8 @@ namespace transport // missing fragment LogPrint (eLogWarning, "Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast); - if (!incompleteMessage->savedFragments.insert (savedFragment).second) - { + if (!incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) LogPrint (eLogWarning, "Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); - delete savedFragment; - } } isLast = false; } @@ -221,6 +213,7 @@ namespace transport if (isLast) { // delete incomplete message + incompleteMessage->msg = nullptr; delete incompleteMessage; m_IncomleteMessages.erase (msgID); // process message diff --git a/SSUData.h b/SSUData.h index 421a646d..ec94abd0 100644 --- a/SSUData.h +++ b/SSUData.h @@ -48,7 +48,7 @@ namespace transport struct FragmentCmp { - bool operator() (const Fragment * f1, const Fragment * f2) const + bool operator() (const std::unique_ptr& f1, const std::unique_ptr& f2) const { return f1->fragmentNum < f2->fragmentNum; }; @@ -58,10 +58,10 @@ namespace transport { I2NPMessage * msg; int nextFragmentNum; - std::set savedFragments; + std::set, FragmentCmp> savedFragments; IncompleteMessage (I2NPMessage * m): msg (m), nextFragmentNum (0) {}; - ~IncompleteMessage () { for (auto it: savedFragments) { delete it; }; }; + ~IncompleteMessage () { if (msg) DeleteI2NPMessage (msg); }; }; struct SentMessage From 7945126e86637d6cbf293801ee38309d4cf8c335 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 5 Feb 2015 13:36:28 -0500 Subject: [PATCH 0147/6300] use unique_ptr for requested destination --- NetDb.cpp | 38 +++++++++++++------------------------- NetDb.h | 6 +++--- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index da3f0214..a35d50c6 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -117,8 +117,6 @@ namespace data m_Thread = 0; } m_LeaseSets.clear(); - for (auto r: m_RequestedDestinations) - delete r.second; m_RequestedDestinations.clear (); } @@ -236,7 +234,6 @@ namespace data { it->second->Success (r); std::unique_lock l(m_RequestedDestinationsMutex); - delete it->second; m_RequestedDestinations.erase (it); } } @@ -456,7 +453,7 @@ namespace data void NetDb::RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete) { // request RouterInfo directly - RequestedDestination * dest = CreateRequestedDestination (destination, false); + auto& dest = CreateRequestedDestination (destination, false); if (requestComplete) { if (dest->IsRequestComplete ()) // if set already @@ -475,7 +472,7 @@ namespace data { LogPrint (eLogError, "No floodfills found"); dest->Fail (); - DeleteRequestedDestination (dest); + DeleteRequestedDestination (destination); } } @@ -566,7 +563,7 @@ namespace data auto it = m_RequestedDestinations.find (IdentHash (buf)); if (it != m_RequestedDestinations.end ()) { - RequestedDestination * dest = it->second; + auto& dest = it->second; bool deleteDest = true; if (num > 0) { @@ -615,7 +612,6 @@ namespace data { // no more requests for the destinationation. delete it it->second->Fail (); - delete it->second; m_RequestedDestinations.erase (it); } } @@ -623,7 +619,6 @@ namespace data { // no more requests for detination possible. delete it it->second->Fail (); - delete it->second; m_RequestedDestinations.erase (it); } } @@ -787,7 +782,7 @@ namespace data for (int i = 0; i < numDestinations; i++) { rnd.GenerateBlock (randomHash, 32); - RequestedDestination * dest = CreateRequestedDestination (IdentHash (randomHash), true); + auto& dest = CreateRequestedDestination (IdentHash (randomHash), true); auto floodfill = GetClosestFloodfill (randomHash, dest->GetExcludedPeers ()); if (floodfill && !floodfills.count (floodfill.get ())) // request floodfill only once { @@ -811,7 +806,7 @@ namespace data i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); } else - DeleteRequestedDestination (dest); + DeleteRequestedDestination (dest->GetDestination ()); } if (throughTunnels && msgs.size () > 0) outbound->SendTunnelDataMsg (msgs); @@ -833,28 +828,24 @@ namespace data } } - RequestedDestination * NetDb::CreateRequestedDestination (const IdentHash& dest, bool isExploratory) + std::unique_ptr& NetDb::CreateRequestedDestination (const IdentHash& dest, bool isExploratory) { std::unique_lock l(m_RequestedDestinationsMutex); auto it = m_RequestedDestinations.find (dest); if (it == m_RequestedDestinations.end ()) // not exist yet { - RequestedDestination * d = new RequestedDestination (dest, isExploratory); - m_RequestedDestinations[dest] = d; - return d; + auto d = new RequestedDestination (dest, isExploratory); + return m_RequestedDestinations.insert (std::make_pair (dest, + std::unique_ptr (d))).first->second; } else return it->second; } - void NetDb::DeleteRequestedDestination (RequestedDestination * dest) + void NetDb::DeleteRequestedDestination (IdentHash ident) { - if (dest) - { - std::unique_lock l(m_RequestedDestinationsMutex); - m_RequestedDestinations.erase (dest->GetDestination ()); - delete dest; - } + std::unique_lock l(m_RequestedDestinationsMutex); + m_RequestedDestinations.erase (ident); } std::shared_ptr NetDb::GetRandomRouter () const @@ -982,7 +973,7 @@ namespace data std::unique_lock l(m_RequestedDestinationsMutex); for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();) { - auto dest = it->second; + auto& dest = it->second; bool done = false; if (ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute { @@ -1018,10 +1009,7 @@ namespace data done = true; if (done) - { - delete dest; it = m_RequestedDestinations.erase (it); - } else it++; } diff --git a/NetDb.h b/NetDb.h index a1287d63..f7efbd2c 100644 --- a/NetDb.h +++ b/NetDb.h @@ -104,8 +104,8 @@ namespace data void ManageLeaseSets (); void ManageRequests (); - RequestedDestination * CreateRequestedDestination (const IdentHash& dest, bool isExploratory = false); - void DeleteRequestedDestination (RequestedDestination * dest); + std::unique_ptr& CreateRequestedDestination (const IdentHash& dest, bool isExploratory = false); + void DeleteRequestedDestination (IdentHash ident); template std::shared_ptr GetRandomRouter (Filter filter) const; @@ -118,7 +118,7 @@ namespace data mutable std::mutex m_FloodfillsMutex; std::list > m_Floodfills; std::mutex m_RequestedDestinationsMutex; - std::map m_RequestedDestinations; + std::map > m_RequestedDestinations; bool m_IsRunning; std::thread * m_Thread; From b9e3931e80ab7c1ab42b528b92e159ff76189cb6 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 5 Feb 2015 18:53:43 -0500 Subject: [PATCH 0148/6300] use shared_ptr for inbound tunnels --- Destination.cpp | 2 +- Destination.h | 2 +- Garlic.cpp | 4 ++-- Garlic.h | 6 +++--- I2NPProtocol.cpp | 2 +- I2NPProtocol.h | 9 +++++---- NetDb.cpp | 2 +- NetDb.h | 2 +- RouterContext.cpp | 2 +- RouterContext.h | 2 +- Tunnel.cpp | 2 +- Tunnel.h | 2 +- 12 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index bc15bb78..0995352b 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -192,7 +192,7 @@ namespace client m_Service.post (std::bind (&ClientDestination::HandleDeliveryStatusMessage, this, msg)); } - void ClientDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from) + void ClientDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { uint8_t typeID = buf[I2NP_HEADER_TYPEID_OFFSET]; switch (typeID) diff --git a/Destination.h b/Destination.h index db7a3089..45c29bb0 100644 --- a/Destination.h +++ b/Destination.h @@ -86,7 +86,7 @@ namespace client // implements GarlicDestination const i2p::data::LeaseSet * GetLeaseSet (); - void HandleI2NPMessage (const uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from); + void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); // override GarlicDestination bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); diff --git a/Garlic.cpp b/Garlic.cpp index 1bd3a8da..53281fa2 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -400,7 +400,7 @@ namespace garlic } void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr decryption, - i2p::tunnel::InboundTunnel * from) + std::shared_ptr from) { uint16_t tagCount = bufbe16toh (buf); buf += 2; len -= 2; @@ -439,7 +439,7 @@ namespace garlic HandleGarlicPayload (buf, payloadSize, from); } - void GarlicDestination::HandleGarlicPayload (uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from) + void GarlicDestination::HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr from) { int numCloves = buf[0]; LogPrint (numCloves," cloves"); diff --git a/Garlic.h b/Garlic.h index 69c6e237..43214e75 100644 --- a/Garlic.h +++ b/Garlic.h @@ -121,7 +121,7 @@ namespace garlic virtual void SetLeaseSetUpdated (); virtual const i2p::data::LeaseSet * GetLeaseSet () = 0; // TODO - virtual void HandleI2NPMessage (const uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from) = 0; + virtual void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) = 0; protected: @@ -131,8 +131,8 @@ namespace garlic private: void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr decryption, - i2p::tunnel::InboundTunnel * from); - void HandleGarlicPayload (uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from); + std::shared_ptr from); + void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr from); private: diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 0fb85566..7216e864 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -71,7 +71,7 @@ namespace i2p return msg; } - I2NPMessage * CreateI2NPMessage (const uint8_t * buf, int len, i2p::tunnel::InboundTunnel * from) + I2NPMessage * CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from) { I2NPMessage * msg = NewI2NPMessage (); memcpy (msg->GetBuffer (), buf, len); diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 86aacd76..de91df73 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -2,9 +2,10 @@ #define I2NP_PROTOCOL_H__ #include -#include -#include #include +#include +#include +#include #include "I2PEndian.h" #include "Identity.h" #include "RouterInfo.h" @@ -108,7 +109,7 @@ namespace tunnel { uint8_t * buf; size_t len, offset, maxLen; - i2p::tunnel::InboundTunnel * from; + std::shared_ptr from; I2NPMessage (): buf (nullptr),len (I2NP_HEADER_SIZE + 2), offset(2), maxLen (0), from (nullptr) {}; // reserve 2 bytes for NTCP header @@ -195,7 +196,7 @@ namespace tunnel void FillI2NPMessageHeader (I2NPMessage * msg, I2NPMessageType msgType, uint32_t replyMsgID = 0); void RenewI2NPMessageHeader (I2NPMessage * msg); I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID = 0); - I2NPMessage * CreateI2NPMessage (const uint8_t * buf, int len, i2p::tunnel::InboundTunnel * from = nullptr); + I2NPMessage * CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from = nullptr); I2NPMessage * CreateDeliveryStatusMsg (uint32_t msgID); I2NPMessage * CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, diff --git a/NetDb.cpp b/NetDb.cpp index a35d50c6..0b68708d 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -239,7 +239,7 @@ namespace data } void NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, - i2p::tunnel::InboundTunnel * from) + std::shared_ptr from) { if (!from) // unsolicited LS must be received directly { diff --git a/NetDb.h b/NetDb.h index f7efbd2c..346ece28 100644 --- a/NetDb.h +++ b/NetDb.h @@ -67,7 +67,7 @@ namespace data void AddRouterInfo (const uint8_t * buf, int len); void AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len); - void AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, i2p::tunnel::InboundTunnel * from); + void AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr from); std::shared_ptr FindRouter (const IdentHash& ident) const; std::shared_ptr FindLeaseSet (const IdentHash& destination) const; diff --git a/RouterContext.cpp b/RouterContext.cpp index 2d42d3da..36cb06bf 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -215,7 +215,7 @@ namespace i2p fk.write ((char *)&keys, sizeof (keys)); } - void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from) + void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); } diff --git a/RouterContext.h b/RouterContext.h index 10512d67..e483646c 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -54,7 +54,7 @@ namespace i2p // implements GarlicDestination const i2p::data::LeaseSet * GetLeaseSet () { return nullptr; }; - void HandleI2NPMessage (const uint8_t * buf, size_t len, i2p::tunnel::InboundTunnel * from); + void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); private: diff --git a/Tunnel.cpp b/Tunnel.cpp index 229a43b8..6ce8348a 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -160,7 +160,7 @@ namespace tunnel void InboundTunnel::HandleTunnelDataMsg (I2NPMessage * msg) { if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive - msg->from = this; + msg->from = shared_from_this (); EncryptTunnelMsg (msg); m_Endpoint.HandleDecryptedTunnelDataMsg (msg); } diff --git a/Tunnel.h b/Tunnel.h index 0db74277..83334b42 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -95,7 +95,7 @@ namespace tunnel TunnelGateway m_Gateway; }; - class InboundTunnel: public Tunnel + class InboundTunnel: public Tunnel, public std::enable_shared_from_this { public: From 6beb527058f38f5ccdd915b684b2608d80c0630f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 6 Feb 2015 11:14:41 -0500 Subject: [PATCH 0149/6300] teminate non-connected NTCP session --- NTCPSession.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 83e43c18..6f4ebfb0 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -838,10 +838,8 @@ namespace transport { LogPrint (eLogError, "Connect error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) - { i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ().GetIdentHash (), true); - conn->Terminate (); - } + conn->Terminate (); } else { From 8e75d8c39ab61b450b25e0fd35606dc0f3b71c4c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 6 Feb 2015 13:49:00 -0500 Subject: [PATCH 0150/6300] check accepted socket for error --- NTCPSession.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 6f4ebfb0..1ffe6b7a 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -792,8 +792,15 @@ namespace transport { if (!error) { - LogPrint (eLogInfo, "Connected from ", conn->GetSocket ().remote_endpoint()); - conn->ServerLogin (); + boost::system::error_code ec; + auto ep = conn->GetSocket ().remote_endpoint(ec); + if (!ec) + { + LogPrint (eLogInfo, "Connected from ", ep); + conn->ServerLogin (); + } + else + LogPrint (eLogError, "Connected from error ", ec.message ()); } @@ -809,8 +816,15 @@ namespace transport { if (!error) { - LogPrint (eLogInfo, "Connected from ", conn->GetSocket ().remote_endpoint()); - conn->ServerLogin (); + boost::system::error_code ec; + auto ep = conn->GetSocket ().remote_endpoint(ec); + if (!ec) + { + LogPrint (eLogInfo, "Connected from ", ep); + conn->ServerLogin (); + } + else + LogPrint (eLogError, "Connected from error ", ec.message ()); } if (error != boost::asio::error::operation_aborted) From 0518b08ca66fb94c3bc31843f84920e163694a0c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 6 Feb 2015 16:03:47 -0500 Subject: [PATCH 0151/6300] drop second incoming connection with same identity --- SSUSession.cpp | 5 +++++ SSUSession.h | 1 + TransportSession.h | 1 + Transports.cpp | 16 ++++++++++++---- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index c5e04d23..7aedef07 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -759,6 +759,11 @@ namespace transport transports.PeerDisconnected (shared_from_this ()); } + void SSUSession::Terminate () + { + m_Server.DeleteSession (shared_from_this ()); + } + void SSUSession::Established () { m_State = eSessionStateEstablished; diff --git a/SSUSession.h b/SSUSession.h index 565afc1f..35cffbe5 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -63,6 +63,7 @@ namespace transport void Introduce (uint32_t iTag, const uint8_t * iKey); void WaitForIntroduction (); void Close (); + void Terminate (); boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; void SendI2NPMessage (I2NPMessage * msg); diff --git a/TransportSession.h b/TransportSession.h index 45ce9366..1acc9493 100644 --- a/TransportSession.h +++ b/TransportSession.h @@ -62,6 +62,7 @@ namespace transport } virtual ~TransportSession () { delete m_DHKeysPair; }; + virtual void Terminate () = 0; std::shared_ptr GetRemoteRouter () { return m_RemoteRouter; }; const i2p::data::IdentityEx& GetRemoteIdentity () { return m_RemoteIdentity; }; diff --git a/Transports.cpp b/Transports.cpp index ddfde44b..b163922c 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -405,9 +405,17 @@ namespace transport auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { - it->second.session = session; - session->SendI2NPMessages (it->second.delayedMessages); - it->second.delayedMessages.clear (); + if (!it->second.session) + { + it->second.session = session; + session->SendI2NPMessages (it->second.delayedMessages); + it->second.delayedMessages.clear (); + } + else + { + LogPrint (eLogError, "Session for ", ident.ToBase64 ().substr (0, 4), " already exists"); + session->Terminate (); + } } else // incoming connection m_Peers[ident] = { 0, nullptr, session }; @@ -420,7 +428,7 @@ namespace transport { auto ident = session->GetRemoteIdentity ().GetIdentHash (); auto it = m_Peers.find (ident); - if (it != m_Peers.end ()) + if (it != m_Peers.end () && it->second.session == session) { if (it->second.delayedMessages.size () > 0) ConnectToPeer (ident, it->second); From 8305fd5f82444bcbda63f419c8ad3b4140acec39 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 6 Feb 2015 18:37:37 -0500 Subject: [PATCH 0152/6300] check if connection to peer exists already --- NTCPSession.cpp | 5 +++++ Transports.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 1ffe6b7a..491e239e 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -316,6 +316,11 @@ namespace transport uint8_t * buf = m_ReceiveBuffer; uint16_t size = bufbe16toh (buf); m_RemoteIdentity.FromBuffer (buf + 2, size); + if (m_Server.FindNTCPSession (m_RemoteIdentity.GetIdentHash ())) + { + LogPrint (eLogError, "NTCP session already exists"); + Terminate (); + } size_t expectedSize = size + 2/*size*/ + 4/*timestamp*/ + m_RemoteIdentity.GetSignatureLen (); size_t paddingLen = expectedSize & 0x0F; if (paddingLen) paddingLen = (16 - paddingLen); diff --git a/Transports.cpp b/Transports.cpp index b163922c..0285fe1b 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -428,7 +428,7 @@ namespace transport { auto ident = session->GetRemoteIdentity ().GetIdentHash (); auto it = m_Peers.find (ident); - if (it != m_Peers.end () && it->second.session == session) + if (it != m_Peers.end () && (!it->second.session || it->second.session == session)) { if (it->second.delayedMessages.size () > 0) ConnectToPeer (ident, it->second); From 5730b15f01309d55f18eca2af288e5a6a650e06f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 6 Feb 2015 20:53:48 -0500 Subject: [PATCH 0153/6300] fixed race condition --- NTCPSession.cpp | 36 +++++++++++++++++++++++++----------- NTCPSession.h | 3 ++- SSUSession.cpp | 5 +++-- SSUSession.h | 2 +- TransportSession.h | 2 +- Transports.cpp | 2 +- Transports.h | 2 +- 7 files changed, 34 insertions(+), 18 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 491e239e..5c1883c1 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -20,8 +20,9 @@ namespace transport { NTCPSession::NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter): TransportSession (in_RemoteRouter), m_Server (server), m_Socket (m_Server.GetService ()), - m_TerminationTimer (m_Server.GetService ()), m_IsEstablished (false), m_ReceiveBufferOffset (0), - m_NextMessage (nullptr), m_IsSending (false), m_NumSentBytes (0), m_NumReceivedBytes (0) + m_TerminationTimer (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false), + m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_IsSending (false), + m_NumSentBytes (0), m_NumReceivedBytes (0) { m_DHKeysPair = transports.GetNextDHKeysPair (); m_Establisher = new Establisher; @@ -30,10 +31,6 @@ namespace transport NTCPSession::~NTCPSession () { delete m_Establisher; - if (m_NextMessage) - i2p::DeleteI2NPMessage (m_NextMessage); - for (auto it: m_SendQueue) - DeleteI2NPMessage (it); } void NTCPSession::CreateAESKey (uint8_t * pubKey, i2p::crypto::AESKey& key) @@ -72,13 +69,30 @@ namespace transport } } + void NTCPSession::Done () + { + m_Server.GetService ().post (std::bind (&NTCPSession::Terminate, shared_from_this ())); + } + void NTCPSession::Terminate () { - m_IsEstablished = false; - m_Socket.close (); - transports.PeerDisconnected (shared_from_this ()); - m_Server.RemoveNTCPSession (shared_from_this ()); - LogPrint (eLogInfo, "NTCP session terminated"); + if (!m_IsTerminated) + { + m_IsTerminated = true; + m_IsEstablished = false; + m_Socket.close (); + transports.PeerDisconnected (shared_from_this ()); + m_Server.RemoveNTCPSession (shared_from_this ()); + for (auto it: m_SendQueue) + DeleteI2NPMessage (it); + m_SendQueue.clear (); + if (m_NextMessage) + { + i2p::DeleteI2NPMessage (m_NextMessage); + m_NextMessage = nullptr; + } + LogPrint (eLogInfo, "NTCP session terminated"); + } } void NTCPSession::Connected () diff --git a/NTCPSession.h b/NTCPSession.h index 66f38538..c264b7b0 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -54,6 +54,7 @@ namespace transport NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter = nullptr); ~NTCPSession (); void Terminate (); + void Done (); boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; bool IsEstablished () const { return m_IsEstablished; }; @@ -113,7 +114,7 @@ namespace transport NTCPServer& m_Server; boost::asio::ip::tcp::socket m_Socket; boost::asio::deadline_timer m_TerminationTimer; - bool m_IsEstablished; + bool m_IsEstablished, m_IsTerminated; i2p::crypto::CBCDecryption m_Decryption; i2p::crypto::CBCEncryption m_Encryption; diff --git a/SSUSession.cpp b/SSUSession.cpp index 7aedef07..1df785e6 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -759,9 +759,10 @@ namespace transport transports.PeerDisconnected (shared_from_this ()); } - void SSUSession::Terminate () + void SSUSession::Done () { - m_Server.DeleteSession (shared_from_this ()); + boost::asio::io_service& service = IsV6 () ? m_Server.GetServiceV6 () : m_Server.GetService (); + service.post (std::bind (&SSUSession::Failed, shared_from_this ())); } void SSUSession::Established () diff --git a/SSUSession.h b/SSUSession.h index 35cffbe5..2f323c3f 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -63,7 +63,7 @@ namespace transport void Introduce (uint32_t iTag, const uint8_t * iKey); void WaitForIntroduction (); void Close (); - void Terminate (); + void Done (); boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; void SendI2NPMessage (I2NPMessage * msg); diff --git a/TransportSession.h b/TransportSession.h index 1acc9493..1067f763 100644 --- a/TransportSession.h +++ b/TransportSession.h @@ -62,7 +62,7 @@ namespace transport } virtual ~TransportSession () { delete m_DHKeysPair; }; - virtual void Terminate () = 0; + virtual void Done () = 0; std::shared_ptr GetRemoteRouter () { return m_RemoteRouter; }; const i2p::data::IdentityEx& GetRemoteIdentity () { return m_RemoteIdentity; }; diff --git a/Transports.cpp b/Transports.cpp index 0285fe1b..afa3de5c 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -414,7 +414,7 @@ namespace transport else { LogPrint (eLogError, "Session for ", ident.ToBase64 ().substr (0, 4), " already exists"); - session->Terminate (); + session->Done (); } } else // incoming connection diff --git a/Transports.h b/Transports.h index e265f344..c9b882c5 100644 --- a/Transports.h +++ b/Transports.h @@ -61,7 +61,7 @@ namespace transport ~Peer () { for (auto it :delayedMessages) - i2p::DeleteI2NPMessage (it); + i2p::DeleteI2NPMessage (it); } }; From b22423e2d59257772e203f89d563836b8273fef4 Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Sat, 7 Feb 2015 17:57:16 +0100 Subject: [PATCH 0154/6300] Get Jump services working again although, at what price\! --- HTTPProxy.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index cadbe768..7f6237f3 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -35,6 +35,7 @@ namespace proxy void HTTPRequestFailed(/*std::string message*/); 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 HandleStreamRequestComplete (std::shared_ptr stream); @@ -124,9 +125,40 @@ namespace proxy return true; } + void HTTPProxyHandler::HandleJumpServices() { + static const char * helpermark1 = "?i2paddresshelper="; + static const char * helpermark2 = "&i2paddresshelper="; + size_t addressHelperPos1 = m_path.rfind (helpermark1); + size_t addressHelperPos2 = m_path.rfind (helpermark2); + size_t addressHelperPos; + if (addressHelperPos1 == std::string::npos) + { + if (addressHelperPos2 == std::string::npos) + return; //Not a jump service + else + addressHelperPos = addressHelperPos2; + } + else + { + if (addressHelperPos2 == std::string::npos) + addressHelperPos = addressHelperPos1; + else if ( addressHelperPos1 > addressHelperPos2 ) + addressHelperPos = addressHelperPos1; + else + addressHelperPos = addressHelperPos2; + } + auto base64 = m_path.substr (addressHelperPos + strlen(helpermark1)); + LogPrint (eLogDebug,"Jump service for ", m_address, " found at ", base64, ". 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 HTTPProxyHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { ExtractRequest(); //TODO: parse earlier if (!ValidateHTTPRequest()) return false; + HandleJumpServices(); m_request = m_method; m_request.push_back(' '); m_request += m_path; From 1bbaa5ba2271a8dc8d8f3e0ded7c40817001029e Mon Sep 17 00:00:00 2001 From: "Francisco Blas (klondike) Izquierdo Riera" Date: Sat, 7 Feb 2015 18:34:25 +0100 Subject: [PATCH 0155/6300] URLdecode the base64 part of the key --- HTTPProxy.cpp | 2 ++ util.cpp | 11 +++++++++++ util.h | 1 + 3 files changed, 14 insertions(+) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 7f6237f3..8f2a1654 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -5,6 +5,7 @@ #include #include #include "HTTPProxy.h" +#include "util.h" #include "Identity.h" #include "Streaming.h" #include "Destination.h" @@ -148,6 +149,7 @@ namespace proxy addressHelperPos = addressHelperPos2; } auto base64 = m_path.substr (addressHelperPos + strlen(helpermark1)); + base64 = i2p::util::http::urlDecode(base64); //Some of the symbols may be urlencoded LogPrint (eLogDebug,"Jump service for ", m_address, " found at ", base64, ". 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 diff --git a/util.cpp b/util.cpp index cf5628f3..e3b4d894 100644 --- a/util.cpp +++ b/util.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -444,6 +445,16 @@ namespace http query_.assign(query_i, url_s.end()); } + std::string urlDecode(const std::string& data) + { + std::string res(data); + for (size_t pos = res.find('%'); pos != std::string::npos; pos = res.find('%',pos+1)) + { + char c = strtol(res.substr(pos+1,2).c_str(), NULL, 16); + res.replace(pos,3,1,c); + } + return res; + } } namespace net diff --git a/util.h b/util.h index e07b746e..8aee0560 100644 --- a/util.h +++ b/util.h @@ -49,6 +49,7 @@ namespace util std::string httpRequest(const std::string& address); void MergeChunkedResponse (std::istream& response, std::ostream& merged); int httpRequestViaI2pProxy(const std::string& address, std::string &content); // return http code + std::string urlDecode(const std::string& data); struct url { url(const std::string& url_s); // omitted copy, ==, accessors, ... From ab0bd908ec6aef3a86a32f16372a46cce9c76177 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 7 Feb 2015 15:25:06 -0500 Subject: [PATCH 0156/6300] fixed race condition --- SSU.cpp | 15 ++++++++++++--- SSU.h | 2 ++ SSUSession.cpp | 18 +++++++++++------- SSUSession.h | 2 ++ 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 6f9a80a6..91856345 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -161,7 +161,10 @@ namespace transport { session = std::make_shared (*this, from); session->WaitForConnect (); - m_Sessions[from] = session; + { + std::unique_lock l(m_SessionsMutex); + m_Sessions[from] = session; + } LogPrint ("New SSU session from ", from.address ().to_string (), ":", from.port (), " created"); } session->ProcessNextMessage (buf, bytes_transferred, from); @@ -206,8 +209,10 @@ namespace transport { // otherwise create new session session = std::make_shared (*this, remoteEndpoint, router, peerTest); - m_Sessions[remoteEndpoint] = session; - + { + std::unique_lock l(m_SessionsMutex); + m_Sessions[remoteEndpoint] = session; + } if (!router->UsesIntroducer ()) { // connect directly @@ -243,6 +248,7 @@ namespace transport introducer = &(address->introducers[0]); // TODO: boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); introducerSession = std::make_shared (*this, introducerEndpoint, router); + std::unique_lock l(m_SessionsMutex); m_Sessions[introducerEndpoint] = introducerSession; } // introduce @@ -256,6 +262,7 @@ namespace transport else { LogPrint (eLogWarning, "Can't connect to unreachable router. No introducers presented"); + std::unique_lock l(m_SessionsMutex); m_Sessions.erase (remoteEndpoint); session.reset (); } @@ -273,12 +280,14 @@ namespace transport if (session) { session->Close (); + std::unique_lock l(m_SessionsMutex); m_Sessions.erase (session->GetRemoteEndpoint ()); } } void SSUServer::DeleteAllSessions () { + std::unique_lock l(m_SessionsMutex); for (auto it: m_Sessions) it.second->Close (); m_Sessions.clear (); diff --git a/SSU.h b/SSU.h index 057a42a2..a1511f51 100644 --- a/SSU.h +++ b/SSU.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "aes.h" #include "I2PEndian.h" @@ -75,6 +76,7 @@ namespace transport std::list m_Introducers; // introducers we are connected to i2p::crypto::AESAlignedBuffer<2*SSU_MTU_V4> m_ReceiveBuffer; i2p::crypto::AESAlignedBuffer<2*SSU_MTU_V6> m_ReceiveBufferV6; + std::mutex m_SessionsMutex; std::map > m_Sessions; std::map m_Relays; // we are introducer diff --git a/SSUSession.cpp b/SSUSession.cpp index 1df785e6..4c7d08cd 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -16,7 +16,7 @@ namespace transport SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, std::shared_ptr router, bool peerTest ): TransportSession (router), m_Server (server), m_RemoteEndpoint (remoteEndpoint), - m_Timer (m_Server.GetService ()), m_PeerTest (peerTest), + m_Timer (GetService ()), m_PeerTest (peerTest), m_State (eSessionStateUnknown), m_IsSessionKey (false), m_RelayTag (0), m_Data (*this), m_NumSentBytes (0), m_NumReceivedBytes (0) { @@ -26,6 +26,11 @@ namespace transport SSUSession::~SSUSession () { } + + boost::asio::io_service& SSUSession::GetService () + { + return IsV6 () ? m_Server.GetServiceV6 () : m_Server.GetService (); + } void SSUSession::CreateAESandMacKey (const uint8_t * pubKey) { @@ -755,14 +760,15 @@ namespace transport void SSUSession::Close () { + m_State = eSessionStateClosed; SendSesionDestroyed (); transports.PeerDisconnected (shared_from_this ()); + m_Timer.cancel (); } void SSUSession::Done () { - boost::asio::io_service& service = IsV6 () ? m_Server.GetServiceV6 () : m_Server.GetService (); - service.post (std::bind (&SSUSession::Failed, shared_from_this ())); + GetService ().post (std::bind (&SSUSession::Failed, shared_from_this ())); } void SSUSession::Established () @@ -824,8 +830,7 @@ namespace transport void SSUSession::SendI2NPMessage (I2NPMessage * msg) { - boost::asio::io_service& service = IsV6 () ? m_Server.GetServiceV6 () : m_Server.GetService (); - service.post (std::bind (&SSUSession::PostI2NPMessage, shared_from_this (), msg)); + GetService ().post (std::bind (&SSUSession::PostI2NPMessage, shared_from_this (), msg)); } void SSUSession::PostI2NPMessage (I2NPMessage * msg) @@ -836,8 +841,7 @@ namespace transport void SSUSession::SendI2NPMessages (const std::vector& msgs) { - boost::asio::io_service& service = IsV6 () ? m_Server.GetServiceV6 () : m_Server.GetService (); - service.post (std::bind (&SSUSession::PostI2NPMessages, shared_from_this (), msgs)); + GetService ().post (std::bind (&SSUSession::PostI2NPMessages, shared_from_this (), msgs)); } void SSUSession::PostI2NPMessages (std::vector msgs) diff --git a/SSUSession.h b/SSUSession.h index 2f323c3f..0ff1a9f1 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -45,6 +45,7 @@ namespace transport eSessionStateUnknown, eSessionStateIntroduced, eSessionStateEstablished, + eSessionStateClosed, eSessionStateFailed }; @@ -80,6 +81,7 @@ namespace transport private: + boost::asio::io_service& GetService (); void CreateAESandMacKey (const uint8_t * pubKey); void PostI2NPMessage (I2NPMessage * msg); From a80e4ef0ea290907dc1e5275f4e438dedafd2c7a Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 7 Feb 2015 17:58:29 -0500 Subject: [PATCH 0157/6300] fixed memory leak --- HTTPServer.cpp | 2 ++ SSUData.cpp | 5 +++++ SSUData.h | 3 ++- SSUSession.cpp | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 7eeb47e7..77585039 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -743,6 +743,8 @@ namespace util s << endpoint.address ().to_string () << ":" << endpoint.port (); if (!outgoing) s << "-->"; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + if (it.second->GetRelayTag ()) + s << " [itag:" << it.second->GetRelayTag () << "]"; s << "
"; s << std::endl; } diff --git a/SSUData.cpp b/SSUData.cpp index 5bdb9702..58387a07 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -28,6 +28,11 @@ namespace transport delete it.second; } + void SSUData::Stop () + { + m_ResendTimer.cancel (); + } + void SSUData::AdjustPacketSize (const i2p::data::RouterInfo& remoteRouter) { auto ssuAddress = remoteRouter.GetSSUAddress (); diff --git a/SSUData.h b/SSUData.h index ec94abd0..021c5bf1 100644 --- a/SSUData.h +++ b/SSUData.h @@ -78,7 +78,8 @@ namespace transport SSUData (SSUSession& session); ~SSUData (); - + void Stop (); + void ProcessMessage (uint8_t * buf, size_t len); void Send (i2p::I2NPMessage * msg); diff --git a/SSUSession.cpp b/SSUSession.cpp index 4c7d08cd..d360baf3 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -763,6 +763,7 @@ namespace transport m_State = eSessionStateClosed; SendSesionDestroyed (); transports.PeerDisconnected (shared_from_this ()); + m_Data.Stop (); m_Timer.cancel (); } From e5fdee272b497b76ad8966c409aeeddb68b600b2 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 8 Feb 2015 08:50:05 -0500 Subject: [PATCH 0158/6300] clean obsolete SSU data --- SSUData.cpp | 89 ++++++++++++++++++++++++++++++++++++++++++++------ SSUData.h | 16 +++++++-- SSUSession.cpp | 8 ++--- SSUSession.h | 2 +- 4 files changed, 98 insertions(+), 17 deletions(-) diff --git a/SSUData.cpp b/SSUData.cpp index 58387a07..a7ff413e 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -11,7 +11,8 @@ namespace i2p namespace transport { SSUData::SSUData (SSUSession& session): - m_Session (session), m_ResendTimer (session.m_Server.GetService ()) + m_Session (session), m_ResendTimer (session.GetService ()), m_DecayTimer (session.GetService ()), + m_IncompleteMessagesCleanupTimer (session.GetService ()) { m_MaxPacketSize = session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE; m_PacketSize = m_MaxPacketSize; @@ -28,9 +29,16 @@ namespace transport delete it.second; } + void SSUData::Start () + { + ScheduleIncompleteMessagesCleanup (); + } + void SSUData::Stop () { m_ResendTimer.cancel (); + m_DecayTimer.cancel (); + m_IncompleteMessagesCleanupTimer.cancel (); } void SSUData::AdjustPacketSize (const i2p::data::RouterInfo& remoteRouter) @@ -209,7 +217,9 @@ namespace transport // missing fragment LogPrint (eLogWarning, "Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast); - if (!incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) + if (incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) + incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); + else LogPrint (eLogWarning, "Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); } isLast = false; @@ -228,7 +238,10 @@ namespace transport { if (!m_ReceivedMessages.count (msgID)) { - if (m_ReceivedMessages.size () > 100) m_ReceivedMessages.clear (); + if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES) + m_ReceivedMessages.clear (); + else + ScheduleDecay (); m_ReceivedMessages.insert (msgID); m_Handler.PutNextMessage (msg); } @@ -401,26 +414,82 @@ namespace transport m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode) { s->m_Data.HandleResendTimer (ecode); }); } - + void SSUData::HandleResendTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it : m_SentMessages) + for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();) { - if (ts >= it.second->nextResendTime && it.second->numResends < MAX_NUM_RESENDS) + if (ts >= it->second->nextResendTime) { - for (auto& f: it.second->fragments) - if (f) m_Session.Send (f->buf, f->len); // resend + if (it->second->numResends < MAX_NUM_RESENDS) + { + for (auto& f: it->second->fragments) + if (f) m_Session.Send (f->buf, f->len); // resend - it.second->numResends++; - it.second->nextResendTime += it.second->numResends*RESEND_INTERVAL; + it->second->numResends++; + it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL; + it++; + } + else + { + LogPrint (eLogError, "SSU message has not been ACKed after ", MAX_NUM_RESENDS, " attempts. Deleted"); + delete it->second; + it = m_SentMessages.erase (it); + } } + else + it++; } ScheduleResend (); } } + + void SSUData::ScheduleDecay () + { + m_DecayTimer.cancel (); + m_DecayTimer.expires_from_now (boost::posix_time::seconds(DECAY_INTERVAL)); + auto s = m_Session.shared_from_this(); + m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode) + { s->m_Data.HandleDecayTimer (ecode); }); + } + + void SSUData::HandleDecayTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + m_ReceivedMessages.clear (); + } + + void SSUData::ScheduleIncompleteMessagesCleanup () + { + m_IncompleteMessagesCleanupTimer.cancel (); + m_IncompleteMessagesCleanupTimer.expires_from_now (boost::posix_time::seconds(INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT)); + auto s = m_Session.shared_from_this(); + m_IncompleteMessagesCleanupTimer.async_wait ([s](const boost::system::error_code& ecode) + { s->m_Data.HandleIncompleteMessagesCleanupTimer (ecode); }); + } + + void SSUData::HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_IncomleteMessages.begin (); it != m_IncomleteMessages.end ();) + { + if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) + { + LogPrint (eLogError, "SSU message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " .Deleted"); + delete it->second; + it = m_IncomleteMessages.erase (it); + } + else + it++; + } + ScheduleIncompleteMessagesCleanup (); + } + } } } diff --git a/SSUData.h b/SSUData.h index 021c5bf1..f56c81d2 100644 --- a/SSUData.h +++ b/SSUData.h @@ -26,6 +26,9 @@ namespace transport const size_t SSU_V6_MAX_PACKET_SIZE = SSU_MTU_V6 - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; // 1424 const int RESEND_INTERVAL = 3; // in seconds const int MAX_NUM_RESENDS = 5; + const int DECAY_INTERVAL = 20; // in seconds + const int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check + const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds // data flags const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02; const uint8_t DATA_FLAG_WANT_REPLY = 0x04; @@ -58,9 +61,10 @@ namespace transport { I2NPMessage * msg; int nextFragmentNum; + uint32_t lastFragmentInsertTime; // in seconds std::set, FragmentCmp> savedFragments; - IncompleteMessage (I2NPMessage * m): msg (m), nextFragmentNum (0) {}; + IncompleteMessage (I2NPMessage * m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0) {}; ~IncompleteMessage () { if (msg) DeleteI2NPMessage (msg); }; }; @@ -78,6 +82,8 @@ namespace transport SSUData (SSUSession& session); ~SSUData (); + + void Start (); void Stop (); void ProcessMessage (uint8_t * buf, size_t len); @@ -95,6 +101,12 @@ namespace transport void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); + + void ScheduleDecay (); + void HandleDecayTimer (const boost::system::error_code& ecode); + + void ScheduleIncompleteMessagesCleanup (); + void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode); void AdjustPacketSize (const i2p::data::RouterInfo& remoteRouter); @@ -104,7 +116,7 @@ namespace transport std::map m_IncomleteMessages; std::map m_SentMessages; std::set m_ReceivedMessages; - boost::asio::deadline_timer m_ResendTimer; + boost::asio::deadline_timer m_ResendTimer, m_DecayTimer, m_IncompleteMessagesCleanupTimer; int m_MaxPacketSize, m_PacketSize; i2p::I2NPMessagesHandler m_Handler; }; diff --git a/SSUSession.cpp b/SSUSession.cpp index d360baf3..ed7fa9a4 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -15,10 +15,9 @@ namespace transport { SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, std::shared_ptr router, bool peerTest ): TransportSession (router), - m_Server (server), m_RemoteEndpoint (remoteEndpoint), - m_Timer (GetService ()), m_PeerTest (peerTest), - m_State (eSessionStateUnknown), m_IsSessionKey (false), m_RelayTag (0), - m_Data (*this), m_NumSentBytes (0), m_NumReceivedBytes (0) + m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_Timer (GetService ()), + m_PeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), m_RelayTag (0), + m_NumSentBytes (0), m_NumReceivedBytes (0), m_Data (*this) { m_CreationTime = i2p::util::GetSecondsSinceEpoch (); } @@ -780,6 +779,7 @@ namespace transport delete m_DHKeysPair; m_DHKeysPair = nullptr; } + m_Data.Start (); m_Data.Send (CreateDatabaseStoreMsg ()); transports.PeerConnected (shared_from_this ()); if (m_PeerTest && (m_RemoteRouter && m_RemoteRouter->IsPeerTesting ())) diff --git a/SSUSession.h b/SSUSession.h index 0ff1a9f1..1a2690f1 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -136,9 +136,9 @@ namespace transport i2p::crypto::CBCDecryption m_SessionKeyDecryption; i2p::crypto::AESKey m_SessionKey; i2p::crypto::MACKey m_MacKey; - SSUData m_Data; size_t m_NumSentBytes, m_NumReceivedBytes; uint32_t m_CreationTime; // seconds since epoch + SSUData m_Data; }; From 9b7820a09d8e584e2148aa584f3b7a99363dedd3 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 8 Feb 2015 09:15:13 -0500 Subject: [PATCH 0159/6300] clean obsolete SSU data --- SSUData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SSUData.cpp b/SSUData.cpp index a7ff413e..c66241b7 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -480,7 +480,7 @@ namespace transport { if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) { - LogPrint (eLogError, "SSU message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " .Deleted"); + LogPrint (eLogError, "SSU message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds. Deleted"); delete it->second; it = m_IncomleteMessages.erase (it); } From 6064d12af3500ba6509a8ff4c99b5557259d16ed Mon Sep 17 00:00:00 2001 From: Kill Your TV Date: Sun, 8 Feb 2015 16:25:47 +0000 Subject: [PATCH 0160/6300] Sync reseed servers with the java router contrib/certificates/ssl/netdb.i2p2.no2.crt will be needed when netdb.i2p2.no updates to a stronger certificate later this year --- Reseed.cpp | 29 ++++++------ .../reseed/cheezybudz_at_mail.i2p.crt | 32 ++++++++++++++ .../certificates/reseed/swat_at_mail.i2p.crt | 31 ------------- .../certificates/ssl/cert.smartcom.org.crt | 44 ------------------- contrib/certificates/ssl/i2p.feared.eu.crt | 21 --------- .../ssl/i2pseed.zarrenspry.info.crt | 27 ++++++++++++ contrib/certificates/ssl/netdb.i2p2.no2.crt | 23 ++++++++++ contrib/certificates/ssl/reseed.info.crt | 20 --------- 8 files changed, 95 insertions(+), 132 deletions(-) create mode 100644 contrib/certificates/reseed/cheezybudz_at_mail.i2p.crt delete mode 100644 contrib/certificates/reseed/swat_at_mail.i2p.crt delete mode 100644 contrib/certificates/ssl/cert.smartcom.org.crt delete mode 100644 contrib/certificates/ssl/i2p.feared.eu.crt create mode 100644 contrib/certificates/ssl/i2pseed.zarrenspry.info.crt create mode 100644 contrib/certificates/ssl/netdb.i2p2.no2.crt delete mode 100644 contrib/certificates/ssl/reseed.info.crt diff --git a/Reseed.cpp b/Reseed.cpp index fd57b2e9..ec43c10e 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -23,29 +23,26 @@ namespace data { static std::vector httpReseedHostList = { - "http://193.150.121.66/netDb/", - "http://netdb.i2p2.no/", - "http://reseed.i2p-projekt.de/", - "http://cowpuncher.drollette.com/netdb/", + // "http://193.150.121.66/netDb/", // unstable + // "http://us.reseed.i2p2.no/", // misconfigured, not serving reseed data + // "http://jp.reseed.i2p2.no/", // Really outdated RIs + "http://netdb.i2p2.no/", // only SU3 (v2) support "http://i2p.mooo.com/netDb/", - "http://reseed.info/", "http://uk.reseed.i2p2.no/", - "http://us.reseed.i2p2.no/", - "http://jp.reseed.i2p2.no/", - "http://i2p-netdb.innovatio.no/", - "http://ieb9oopo.mooo.com" + "http://i2p-netdb.innovatio.no/" }; //TODO: Remember to add custom port support. Not all serves on 443 static std::vector httpsReseedHostList = { - "https://193.150.121.66/netDb/", - "https://netdb.i2p2.no/", - "https://reseed.i2p-projekt.de/", - "https://cowpuncher.drollette.com/netdb/", + // "https://193.150.121.66/netDb/", // unstable + // "https://i2p-netdb.innovatio.no/",// Vuln to POODLE + "https://netdb.i2p2.no/", // Only SU3 (v2) support + "https://reseed.i2p-projekt.de/", // Only HTTPS + "https://cowpuncher.drollette.com/netdb/", // Only HTTPS and SU3 (v2) support -- will move to a new location "https://i2p.mooo.com/netDb/", - "https://reseed.info/", - "https://i2p-netdb.innovatio.no/", - "https://ieb9oopo.mooo.com/", + "https://link.mx24.eu/", // Only HTTPS and SU3 (v2) support + "https://i2pseed.zarrenspry.info/", // Only HTTPS and SU3 (v2) support + "https://ieb9oopo.mooo.com/", // Only HTTPS and SU3 (v2) support "https://ssl.webpack.de/ivae2he9.sg4.e-plaza.de/" // Only HTTPS and SU3 (v2) support }; diff --git a/contrib/certificates/reseed/cheezybudz_at_mail.i2p.crt b/contrib/certificates/reseed/cheezybudz_at_mail.i2p.crt new file mode 100644 index 00000000..fecdf0b9 --- /dev/null +++ b/contrib/certificates/reseed/cheezybudz_at_mail.i2p.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFhTCCA22gAwIBAgIEeCJXkjANBgkqhkiG9w0BAQ0FADBzMQswCQYDVQQGEwJY +WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt +b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEcMBoGA1UEAwwTY2hlZXp5YnVkekBt +YWlsLmkycDAeFw0xNDEyMTYyMTU3MTZaFw0yNDEyMTUyMTU3MTZaMHMxCzAJBgNV +BAYTAlhYMQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBB +bm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRwwGgYDVQQDDBNjaGVlenli +dWR6QG1haWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAgLkj +Jsp8pjRi5N/JHHz+MXisgbI9G0vpd3yDhHvae3oF87iiQbxflcdcoH0l5RZL0cAn +w4amhqoOk2qhf+NSAEkiPWhk7CzPBRwDExEM/gmHYLWXbfnoHGaEls9ORGuDlDmN +hCFJVrxaZocIOi/7gZ4A+tC8wq+1aoe0Yhr381OW59w9AdUAWjBWibO3V59dEklL +7HqfOc2v7AMKDLJWgPekj8ZbqA9lRxHM6Djtjz4d9QXeQa8j3xLXTX1QbkvJBBX1 +9Rzi/Nzv622lzoZ/Z/61jW7Bz+h9aJ6qp4on9K4ygUw/VduTOH/1ryQmw87x4MFQ +Z/Y86lOl7XZxBjtpYpGQW/5LmBe2BCfWgIYe9N5ionNgAe5TNEIDngP9AvJmcTyF +KcGgOgXQO9EeHEdgf4nC6RbGrb2sBtRjWJv5nOhHRG9tpwYkw/Zc5ZNHOymYpPMg +wce3me+1psJFt+gXhDcvxpRgTZpXfz91K/nKt3+szcYFluqhJLi6nL1TmXQVn51X +lGD1bcy1VUof+uKyb223JX5rm9WQ48GzUfy5cK4o+khEo0RLb21FwG5iJwVzhtoN +xQS1TO6pwLn8Si1ePRwntzlOm8DPIwdUkPBQNJ9DDkcdVia2GgbVM6LH8lrukekq +soYfwmOTsFRkGo04ujDI/IeMrl3zmJphyQkGx18CAwEAAaMhMB8wHQYDVR0OBBYE +FJ2MHeHnfCpEuYvC/9eK2ML9ne2eMA0GCSqGSIb3DQEBDQUAA4ICAQA3XUS7Zw1i +RJWPSu2oLzV7oTtIW5po2Gd5BL3oU6BvlK1zLw/z/soF/LopeHQudBYxYckyv4MG +gTNS9fcKkVdhNyLI/R2S0nQ/VFhTzuvq8HnnTOpvopA/cXTQlgrhGB2ajIZMYsXe +lei0V5H23etXTbYZWK6/IgoALk5vowde9lpJEIBhupIafqFg0tAo4LX07/eNxDOp +nXrShsYhHNaRhglS+0Gqj1UK0WvgMJxQKJm/VLi7jx8vfRkqXs/b76XT+VMQuUJd +l5llQwpOicQhX/ZTAO+iWrDaO7mz/ZDweLxnfWd3m2JwDJlE9K5l98zdcve96NRZ +ePnK8vBoAPQ9iHhwFSC5GpirK1KmT/BDLjqEF3H/HgPdPWSh97AUFpBryEIdZk1q +Czi9DCvwHNpbpI20Fo48+2N7sbvq4onZZqx5V0SjTj/9bHSSDwG9ok1JqWoZmRvo +p4MIywAJowlvPNc++jSHT3R7segeNUi/UdYCmm70j1av+0aEknmvPtF6atsHJ22X +5OMBhiPi1pudFWFJFWk4WOjrK/juwHHfHNgFVyziva4q6wPKrPno0gO5pCpfRUld +QAoSPgo8LAB3dugt5Xfsuone2GhLi1SLQlWFJWHswd/ypWa0FB+xn6Edkc1noOWY +06dwfEP/gTCAnSplLyrFWxnyHManBxq/bQ== +-----END CERTIFICATE----- diff --git a/contrib/certificates/reseed/swat_at_mail.i2p.crt b/contrib/certificates/reseed/swat_at_mail.i2p.crt deleted file mode 100644 index 276c8be3..00000000 --- a/contrib/certificates/reseed/swat_at_mail.i2p.crt +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFVjCCAz6gAwIBAgIEU71jgDANBgkqhkiG9w0BAQ0FADBtMQswCQYDVQQGEwJY -WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt -b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEWMBQGA1UEAwwNc3dhdEBtYWlsLmky -cDAeFw0xNDA3MDkxNTQ1MDRaFw0yNDA3MDgxNTQ1MDRaMG0xCzAJBgNVBAYTAlhY -MQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBBbm9ueW1v -dXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRYwFAYDVQQDDA1zd2F0QG1haWwuaTJw -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjSj53hsbTqtzbnlf5LbR -HmfdC2br9QZaB9e5IQKprlTptdzqTrt2LRS6ZaJ06BKJgX3AfLflvyeUDUPoyg63 -I9a1kb1AsrcxvMkHXTUwPaO09caO5/CiQ3zx/iuTl4e0MmGe3cz7jsvZNdOVH0ba -B691GB2UBq2QjobGx01qjWtACCmoQIcEur2ns/l+VzAextBL70dSPN6EJAonQnWc -JvHf1vhXp5DWasaIovm8haNo48QpCo7NllsAjiONQM9rrJITvzFG9hX9cv/B1Kr4 -LCebTXv58ViXXFsnxYhktAFwP33fn1eCLraJ/BpaDR4+s3ovMC/7S+g5//+sQTd1 -pR/kXx4BmZWZdzs083Z/2skQON75j/qnMhUQqpFCqUImm4lOhlIGbzFJ4GQM6VCR -V4BbvC3XuDc6vlivLzWpUEU7Kc6YnfGgi2G//ZCj2CAoR7qZs/n9997C8oAvGY5z -XGVC/GqIFHuFvnDfPDvxGovYjLJ0KrNtAmp2Rb5812glnVdPwbRYRUBg3ICbNey3 -rmGPURDq0aHMTzX4gtM+/hCYYVnkzNMQvYuw9EZLZrK/XdM1a0U4kajZSKKJsTmW -uQwXSUVjTKQh//yL4zPoELucFk5r7apLePEm6aCeWuY2wVkR8KEFgNanwDckWQAm -Lk9r2t+Y/l2jS2NqBWFyr+cCAwEAATANBgkqhkiG9w0BAQ0FAAOCAgEAgFSquj/0 -iZYpFI1XarSIVpGMo0WLAmb9GZCn5yoXSeE6eypI/hHhXA4Bjdk2Ae33pXPNcCV8 -oT/gHj8943Wx7CTxty2zHzIsd92/HG2EG6U/HPp5l7yIJQaWoe/9tjQoaBhipZOK -+MiytkoBWkyXFqXnKQPExiadWB8axHtt66vrikOcSx6Ur3u5DPKybvY4fsuvo4+I -cLLgoueFm6I1WhmkVmjtm4k2yZ/Z3NEYjg52rv8NuYhRwK2JrQeRzMZv/zt5KhOt -05woHrzymjfFBu0M8uxX7EGZBIsc8zcEY7JL/NSMArw/QCgLU5bQF6+CsyxWUkt1 -obMRXU1oS9GjC/1F0kw52NOz2qzBn9tZBc1zs8+GLpYBUf9KiUMFOfJpkr706VqC -orgxRYwncicq+de2PlesxJb3DNPFuAzUNzAqxcVYDoFPAiL1zCEl0nhBrbN+x93X -ojTfV3UlbMjMkQKveYJxsi5/+jO1dHIkXpzK4bwFwHmJ2RCa6PualWhuXldX6mR+ -APoY6xeoPRlyKk+POrSwU+hywUudyPuFyzDMo8n1w4CyqL+/ky3YsLfGBM1phbb2 -GEnZ0J1HW34Pnie1rzaCak+3RfaZsImCwh1xXl/H7Ka9bLeUIfOuipSSroctdaiG -84wIiEjxgjW2ldM37gTX2XtE/blB1YPIZ5U= ------END CERTIFICATE----- diff --git a/contrib/certificates/ssl/cert.smartcom.org.crt b/contrib/certificates/ssl/cert.smartcom.org.crt deleted file mode 100644 index 960f2657..00000000 --- a/contrib/certificates/ssl/cert.smartcom.org.crt +++ /dev/null @@ -1,44 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW -MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg -Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9 -MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi -U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh -cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk -pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf -OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C -Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT -Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi -HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM -Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w -+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ -Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 -Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B -26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID -AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE -FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j -ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js -LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM -BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0 -Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy -dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh -cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh -YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg -dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp -bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ -YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT -TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ -9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8 -jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW -FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz -ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1 -ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L -EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu -L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq -yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC -O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V -um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh -NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14= ------END CERTIFICATE----- diff --git a/contrib/certificates/ssl/i2p.feared.eu.crt b/contrib/certificates/ssl/i2p.feared.eu.crt deleted file mode 100644 index 628c6290..00000000 --- a/contrib/certificates/ssl/i2p.feared.eu.crt +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDhTCCAm2gAwIBAgIJAPVgXcMcr3zqMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV -BAYTAkVVMQ8wDQYDVQQIDAZFdXJvcGUxDDAKBgNVBAoMA0kyUDETMBEGA1UECwwK -T3V0cHJveGllczEWMBQGA1UEAwwNaTJwLmZlYXJlZC5ldTAeFw0xMjEwMjkxNzMw -MDZaFw0yMTAxMTUxNzMwMDZaMFkxCzAJBgNVBAYTAkVVMQ8wDQYDVQQIDAZFdXJv -cGUxDDAKBgNVBAoMA0kyUDETMBEGA1UECwwKT3V0cHJveGllczEWMBQGA1UEAwwN -aTJwLmZlYXJlZC5ldTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOUh -y2+6Q4RO+b5WPXX/cZ/9fiI7aWGe/C7z0083HOEqnkgGCYgxFWUCed6/eZbYoZ7/ -PV1BAuEereNwTp+Ov7fQB2H73O9sSAEejW6O4C2PZiZWaPxpZiTJNENbLOZxJnIN -+fSqmA5pqvGkYAJ2heZH4v4tayun7Vib58GWuizhzJ4EvhOrOrLq/YHrxMn++r4e -kNNbq4QzWpfxNa7ocDY9OJh5qFzuc+6wKj1m1syK6euDqs5d6X+y0aDTMgRxey2b -tkmNx9wC0flLg1oMcv9o1zN+dENy7Inkd/SqbSjLUqDTJzdq6xURVsgLoV63pb6r -B4gbGIlriYWK/mOPTTkCAwEAAaNQME4wHQYDVR0OBBYEFOI94JZ3Rb2RVmr8QjOp -u3KfVSrNMB8GA1UdIwQYMBaAFOI94JZ3Rb2RVmr8QjOpu3KfVSrNMAwGA1UdEwQF -MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAD7bI05zg9nf9qanq4ZNw/rvEzYQRBmy -MqzZjcwBMGvbcEbS+zYAdAkfxmN3l/AT4I4z138Om0ud4ZJUQTVlRsJkMlmLD4Rt -Jbi2rl7mrY7Qupgu5hvgH+ZaEWr7LTq+tFjPycRS+zijw9NToKeAsgEex9zYIOYD -BxDUn/trvyA41ItvegWh803IsZUBb45Via+bopid9aFFkejRrck9hhcQ6fVh2yju -nuVwHrxNvGc0NmmJ7zI+nPESFS+TAYbWXikDhc5Vtyiuoz47WZU1cgXYYMejK4WA -+3GLvei7qKm4GOJSg7BngF5Iyj/n7ML1rBqTlN3KA1YOgpGCwJlKzto= ------END CERTIFICATE----- diff --git a/contrib/certificates/ssl/i2pseed.zarrenspry.info.crt b/contrib/certificates/ssl/i2pseed.zarrenspry.info.crt new file mode 100644 index 00000000..e855d6ed --- /dev/null +++ b/contrib/certificates/ssl/i2pseed.zarrenspry.info.crt @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEkzCCA3ugAwIBAgIJAKsW7idQxp0aMA0GCSqGSIb3DQEBCwUAMIHfMQswCQYD +VQQGEwJVSzEgMB4GA1UECAwXaTJwc2VlZC56YXJyZW5zcHJ5LmluZm8xIDAeBgNV +BAcMF2kycHNlZWQuemFycmVuc3ByeS5pbmZvMSAwHgYDVQQKDBdpMnBzZWVkLnph +cnJlbnNwcnkuaW5mbzEgMB4GA1UECwwXaTJwc2VlZC56YXJyZW5zcHJ5LmluZm8x +IDAeBgNVBAMMF2kycHNlZWQuemFycmVuc3ByeS5pbmZvMSYwJAYJKoZIhvcNAQkB +FhdpMnBzZWVkLnphcnJlbnNwcnkuaW5mbzAeFw0xNDEyMjgxOTI3MDdaFw0xOTAy +MDUxOTI3MDdaMIHfMQswCQYDVQQGEwJVSzEgMB4GA1UECAwXaTJwc2VlZC56YXJy +ZW5zcHJ5LmluZm8xIDAeBgNVBAcMF2kycHNlZWQuemFycmVuc3ByeS5pbmZvMSAw +HgYDVQQKDBdpMnBzZWVkLnphcnJlbnNwcnkuaW5mbzEgMB4GA1UECwwXaTJwc2Vl +ZC56YXJyZW5zcHJ5LmluZm8xIDAeBgNVBAMMF2kycHNlZWQuemFycmVuc3ByeS5p +bmZvMSYwJAYJKoZIhvcNAQkBFhdpMnBzZWVkLnphcnJlbnNwcnkuaW5mbzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANrEncHwS+7R0Ti/jZa2Ex7ujglV +huYO59nLxeAOpEQwn6V41X5+L0hmhM0zuYavuP1jzKfF/Cn0CG1PqkGbEnXrTOGf +4gMj2wy/UVVFXaPQwldi+CEiNo6nI5S+T/upg5VK6M5/ahYbfIbX5xF27QNPV5qW +RnM0VK4gIQkFFtpiI0dFcEU9VYe+cg7a4Jvxc5LzqaIBZHWMX6alPfBT70LkYiiQ +76IRw5oBmqZjfIdiudRhFkezMkDomKSgLR2/0HJbekq2WeLXJLMPM1rdpCYldBEi +t6Zng9uAJa1mA6Al4RhO1aQEPj9Vo5h+Vj6FHJAJJcb+YW6wLKBkJVGLF4UCAwEA +AaNQME4wHQYDVR0OBBYEFL538Fr1l/9YQgG+iZvJUuOzAaVaMB8GA1UdIwQYMBaA +FL538Fr1l/9YQgG+iZvJUuOzAaVaMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAKq7KEnR0V43PsA5D23Lhawy5W/BDs4RO3LkYSxi+zR4EAMC8RhafrmG +6IZVp+ykplZtFK3Kkw1osakcvmHRLoPCXPWLibXtWMEpmH4GhWJKf5Ct1kY0VkEE +ALP7vCtjDm5l6WBaNOZYv25wwg5wgjyhzfJtLxzyRRPOjUuv0M3FFwJEAauzoo+4 +nle91IHNcWPIq1kgWUwWBHpLgZ2RpSOZS9MBOCkjHwQhoebhpgwSPgUHvBJ7FoLb +AeAdwpgPdIQ9gZEZEPfCPfG/Qp60yLAhkT2CF7F1h47VYe8LGBDbd1HGpSwjulq/ +lnvV4zDIoKhbQhUpxwgHo79nxcgddOA= +-----END CERTIFICATE----- diff --git a/contrib/certificates/ssl/netdb.i2p2.no2.crt b/contrib/certificates/ssl/netdb.i2p2.no2.crt new file mode 100644 index 00000000..7c792d99 --- /dev/null +++ b/contrib/certificates/ssl/netdb.i2p2.no2.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID1TCCAr2gAwIBAgIJAOd9wIt+w/I5MA0GCSqGSIb3DQEBCwUAMIGAMQswCQYD +VQQGEwJOTzENMAsGA1UECAwET3NsbzENMAsGA1UEBwwET3NsbzEMMAoGA1UECgwD +STJQMQwwCgYDVQQLDANJMlAxFjAUBgNVBAMMDW5ldGRiLmkycDIubm8xHzAdBgkq +hkiG9w0BCQEWEG1lZWhAaTJwbWFpbC5vcmcwHhcNMTQxMjA2MjM1OTM1WhcNMjAw +NTI4MjM1OTM1WjCBgDELMAkGA1UEBhMCTk8xDTALBgNVBAgMBE9zbG8xDTALBgNV +BAcMBE9zbG8xDDAKBgNVBAoMA0kyUDEMMAoGA1UECwwDSTJQMRYwFAYDVQQDDA1u +ZXRkYi5pMnAyLm5vMR8wHQYJKoZIhvcNAQkBFhBtZWVoQGkycG1haWwub3JnMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmtRtAALMImh0G0X+AtMpJNBa +HduNkg5t+0juitKRboXXAp5k7yN9qnimlBxlAicNb+QubcDuL+WV91NKz43dd6Xp +SAewqMFRPUAki8uYzoh+hQEfzyd3NmadUKquYZsYwomhHnraOmLZLbxD6ED3FEwl +hGBJwYnhyMZUCgB5+DEEHg8RdLz+H0bMrwz3e7/0lMtH6lM1lIHz0KBULWLp7Om0 +sk3rmmhPUIXqfoY8X3vClI74o0KcslMVaF4rt3lAHdoi3lwA6Qbdqq9nC9rPWHUS +USQQ/MKsNfDTGsHkbW2l0VgNvJkw92DwHTXSJrsEqgkdV/B1hHxCKgL44c/CbwID +AQABo1AwTjAdBgNVHQ4EFgQUCkebDZE05yKMbXORa6gO+aLdCscwHwYDVR0jBBgw +FoAUCkebDZE05yKMbXORa6gO+aLdCscwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOCAQEAfHO0g5M//X5xDIuXGCeqQMUrF3r1N45a+0kqo2b/rd9USueNGrJl +KE7MfDgShy2d4strZ1m0M4StW0RlUUZ4V4FYwzcknF6VXbOQK3BTrAeOwuxsrHoT +abrMZ36ABYur5WakOYtPyQ5oXFUAIpGBe9LH7q3XLegSOfftvc2xdJ+VK0n4MEfY +GfaRGMNW/pxGYLWvao3soOJMtp6cQ5KIYGuX92DMon/UgPBqEygeUj7aIqjhRss0 +b0dUZQyHccAG+e5NeTF2ifHCEh2rZY18VGxPL7KLrCQigu5lif1TTv5CDO5rKrHl +TuTOsnooMxUH4ThIVI9cxXk6bzRMehLghA== +-----END CERTIFICATE----- diff --git a/contrib/certificates/ssl/reseed.info.crt b/contrib/certificates/ssl/reseed.info.crt deleted file mode 100644 index 31302c52..00000000 --- a/contrib/certificates/ssl/reseed.info.crt +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDRDCCAiwCCQDCm/Zrmali9zANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJB -VTETMBEGA1UECBMKU29tZS1TdGF0ZTELMAkGA1UEBxMCSEgxDDAKBgNVBAoTA0ky -UDEPMA0GA1UECxMGcmVzZWVkMRQwEgYDVQQDEwtyZXNlZWQuaW5mbzAeFw0xMjEw -MjcxODU3NDNaFw0xNjEyMDUxODU3NDNaMGQxCzAJBgNVBAYTAkFVMRMwEQYDVQQI -EwpTb21lLVN0YXRlMQswCQYDVQQHEwJISDEMMAoGA1UEChMDSTJQMQ8wDQYDVQQL -EwZyZXNlZWQxFDASBgNVBAMTC3Jlc2VlZC5pbmZvMIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAt9nz0iUvjdX4Hkhfk0FbBOeEP4i/FG3V4VrEdQfcviSF -XgzGYeRtGsvrFWP/5+6bcGnOkIy/jrKJfij3AjKJh8gTzqiNNNnV8VcHwFSNp+hZ -D4BM+UHPACV1Pjd3HQe6f0+LvcTs3HQgIkNkwUyqRuXOm/5Mk6SWSu1740aSwHCj -Kk0x1FByzI0YBvXCPX6TVk6sJqKkQyLzK0CSGSeqUq8GvGCq+jT9k62Su7ooxCwi -GzxaFjMdVYxuI8cuT5Cni+SUw1Ia8vhESnIy6slwzk37xNI80VuMvRT6rD2KcXDH -mK7ml1qL0rJWoF5AE+x/nen4V41mouv1W9rk3wTlTQIDAQABMA0GCSqGSIb3DQEB -BQUAA4IBAQAr6RBviBDW4bnPDTcdtstTDdaYX9yzoh+zzeGB0dUR26GKoOjpSItb -B9nrsW1eJ2wbblfGBUoXhcmNByKHXXHejMhmurHjdei2BuLbTsknN8DPKXu5UF9z -cg4cKQkxgzXOcNYlaF4+sfwFXDHJ4we/8vduVgkyo8R66543/Sh/nIMvq2slRT4w -wIBOVcMb2XxlbdwHW9XALAz9sto+4GH9GAC24f8ngluOpHijMnOOIo4dHibQ5hM9 -KcDpHezP0ugMTAxS2NmtVahwAqa2IjpqR7aEQ2wLvxQzDqrXo93L93+b2FKRUQXH -Duud/n/w0kVV3DaIGikOsJayoanR+9HD ------END CERTIFICATE----- From 1ad04bf8bf6700a236bfa4ed81835b35fdfa8dff Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 8 Feb 2015 11:52:03 -0500 Subject: [PATCH 0161/6300] read more data from socket if available --- NTCPSession.cpp | 49 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 5c1883c1..029ae905 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -489,19 +489,48 @@ namespace transport if (m_ReceiveBufferOffset >= 16) { - uint8_t * nextBlock = m_ReceiveBuffer; - while (m_ReceiveBufferOffset >= 16) - { - if (!DecryptNextBlock (nextBlock)) // 16 bytes + int numReloads = 0; + do + { + uint8_t * nextBlock = m_ReceiveBuffer; + while (m_ReceiveBufferOffset >= 16) { - Terminate (); - return; + if (!DecryptNextBlock (nextBlock)) // 16 bytes + { + Terminate (); + return; + } + nextBlock += 16; + m_ReceiveBufferOffset -= 16; + } + if (m_ReceiveBufferOffset > 0) + memcpy (m_ReceiveBuffer, nextBlock, m_ReceiveBufferOffset); + + // try to read more + if (numReloads < 5) + { + boost::asio::socket_base::bytes_readable command (true); + m_Socket.io_control (command); + size_t moreBytes = command.get(); + if (moreBytes) + { + if (moreBytes > NTCP_BUFFER_SIZE - m_ReceiveBufferOffset) + moreBytes = NTCP_BUFFER_SIZE - m_ReceiveBufferOffset; + boost::system::error_code ec; + moreBytes = m_Socket.read_some (boost::asio::buffer (m_ReceiveBuffer + m_ReceiveBufferOffset, moreBytes)); + if (ec) + { + LogPrint (eLogError, "Read more bytes error: ", ec.message ()); + Terminate (); + return; + } + m_NumReceivedBytes += moreBytes; + m_ReceiveBufferOffset += moreBytes; + numReloads++; + } } - nextBlock += 16; - m_ReceiveBufferOffset -= 16; } - if (m_ReceiveBufferOffset > 0) - memcpy (m_ReceiveBuffer, nextBlock, m_ReceiveBufferOffset); + while (m_ReceiveBufferOffset >= 16); m_Handler.Flush (); } From 7dd159add2e0c01e6c8a2365bcfb9ce21cd4d71f Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 8 Feb 2015 20:27:17 -0500 Subject: [PATCH 0162/6300] use 'available' method --- NTCPSession.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 029ae905..43816d2d 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -509,9 +509,7 @@ namespace transport // try to read more if (numReloads < 5) { - boost::asio::socket_base::bytes_readable command (true); - m_Socket.io_control (command); - size_t moreBytes = command.get(); + size_t moreBytes = m_Socket.available(); if (moreBytes) { if (moreBytes > NTCP_BUFFER_SIZE - m_ReceiveBufferOffset) From f9731a76ec6dbf6e448780b55fdae438513a18b4 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 8 Feb 2015 20:28:18 -0500 Subject: [PATCH 0163/6300] separate receivers thread --- SSU.cpp | 79 ++++++++++++++++++++++++++++++++++++++++++--------------- SSU.h | 23 ++++++++++------- 2 files changed, 72 insertions(+), 30 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 91856345..54c4fb6c 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -9,10 +9,11 @@ namespace i2p { namespace transport { - SSUServer::SSUServer (int port): m_Thread (nullptr), m_ThreadV6 (nullptr), m_Work (m_Service), - m_WorkV6 (m_ServiceV6),m_Endpoint (boost::asio::ip::udp::v4 (), port), - m_EndpointV6 (boost::asio::ip::udp::v6 (), port), m_Socket (m_Service, m_Endpoint), - m_SocketV6 (m_ServiceV6), m_IntroducersUpdateTimer (m_Service) + SSUServer::SSUServer (int port): m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr), + m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService), + m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), + m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversService), + m_IntroducersUpdateTimer (m_Service) { m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (65535)); m_Socket.set_option (boost::asio::socket_base::send_buffer_size (65535)); @@ -33,12 +34,13 @@ namespace transport void SSUServer::Start () { m_IsRunning = true; + m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); - m_Service.post (std::bind (&SSUServer::Receive, this)); + m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); if (context.SupportsV6 ()) { m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this)); - m_ServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); + m_ReceiversService.post (std::bind (&SSUServer::ReceiveV6, this)); } if (i2p::context.IsUnreachable ()) ScheduleIntroducersUpdateTimer (); @@ -52,6 +54,12 @@ namespace transport m_Socket.close (); m_ServiceV6.stop (); m_SocketV6.close (); + if (m_ReceiversThread) + { + m_ReceiversThread->join (); + delete m_ReceiversThread; + m_ReceiversThread = nullptr; + } if (m_Thread) { m_Thread->join (); @@ -95,6 +103,21 @@ namespace transport } } } + + void SSUServer::RunReceivers () + { + while (m_IsRunning) + { + try + { + m_ReceiversService.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SSU receivers: ", ex.what ()); + } + } + } void SSUServer::AddRelay (uint32_t tag, const boost::asio::ip::udp::endpoint& relay) { @@ -119,55 +142,66 @@ namespace transport void SSUServer::Receive () { - m_Socket.async_receive_from (boost::asio::buffer (m_ReceiveBuffer, SSU_MTU_V4), m_SenderEndpoint, - boost::bind (&SSUServer::HandleReceivedFrom, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); + SSUPacket * packet = new SSUPacket (); + m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, + std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet)); } void SSUServer::ReceiveV6 () { - m_SocketV6.async_receive_from (boost::asio::buffer (m_ReceiveBufferV6, SSU_MTU_V6), m_SenderEndpointV6, - boost::bind (&SSUServer::HandleReceivedFromV6, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); + SSUPacket * packet = new SSUPacket (); + m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, + std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet)); } - void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred) + void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) { if (!ecode) { - HandleReceivedBuffer (m_SenderEndpoint, m_ReceiveBuffer, bytes_transferred); + packet->len = bytes_transferred; + m_Service.post (std::bind (&SSUServer::HandleReceivedBuffer, this, packet)); Receive (); } else + { LogPrint ("SSU receive error: ", ecode.message ()); + delete packet; + } } - void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred) + void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) { if (!ecode) { - HandleReceivedBuffer (m_SenderEndpointV6, m_ReceiveBufferV6, bytes_transferred); + packet->len = bytes_transferred; + m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedBuffer, this, packet)); ReceiveV6 (); } else + { LogPrint ("SSU V6 receive error: ", ecode.message ()); + delete packet; + } } - void SSUServer::HandleReceivedBuffer (boost::asio::ip::udp::endpoint& from, uint8_t * buf, std::size_t bytes_transferred) + void SSUServer::HandleReceivedBuffer (SSUPacket * packet) { std::shared_ptr session; - auto it = m_Sessions.find (from); + auto it = m_Sessions.find (packet->from); if (it != m_Sessions.end ()) session = it->second; if (!session) { - session = std::make_shared (*this, from); + session = std::make_shared (*this, packet->from); session->WaitForConnect (); { std::unique_lock l(m_SessionsMutex); - m_Sessions[from] = session; + m_Sessions[packet->from] = session; } - LogPrint ("New SSU session from ", from.address ().to_string (), ":", from.port (), " created"); + LogPrint ("New SSU session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); } - session->ProcessNextMessage (buf, bytes_transferred, from); + session->ProcessNextMessage (packet->buf, packet->len, packet->from); + delete packet; } std::shared_ptr SSUServer::FindSession (std::shared_ptr router) const @@ -256,7 +290,10 @@ namespace transport "] through introducer ", introducer->iHost, ":", introducer->iPort); session->WaitForIntroduction (); if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable - Send (m_ReceiveBuffer, 0, remoteEndpoint); // send HolePunch + { + uint8_t buf[1]; + Send (buf, 0, remoteEndpoint); // send HolePunch + } introducerSession->Introduce (introducer->iTag, introducer->iKey); } else diff --git a/SSU.h b/SSU.h index a1511f51..01ca2607 100644 --- a/SSU.h +++ b/SSU.h @@ -23,6 +23,13 @@ namespace transport const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour const size_t SSU_MAX_NUM_INTRODUCERS = 3; + + struct SSUPacket + { + i2p::crypto::AESAlignedBuffer<1500> buf; + boost::asio::ip::udp::endpoint from; + size_t len; + }; class SSUServer { @@ -50,11 +57,12 @@ namespace transport void Run (); void RunV6 (); + void RunReceivers (); void Receive (); void ReceiveV6 (); - void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandleReceivedBuffer (boost::asio::ip::udp::endpoint& from, uint8_t * buf, std::size_t bytes_transferred); + void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); + void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); + void HandleReceivedBuffer (SSUPacket * packet); template std::shared_ptr GetRandomSession (Filter filter); @@ -66,16 +74,13 @@ namespace transport private: bool m_IsRunning; - std::thread * m_Thread, * m_ThreadV6; - boost::asio::io_service m_Service, m_ServiceV6; - boost::asio::io_service::work m_Work, m_WorkV6; + std::thread * m_Thread, * m_ThreadV6, * m_ReceiversThread; + boost::asio::io_service m_Service, m_ServiceV6, m_ReceiversService; + boost::asio::io_service::work m_Work, m_WorkV6, m_ReceiversWork; boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6; boost::asio::ip::udp::socket m_Socket, m_SocketV6; - boost::asio::ip::udp::endpoint m_SenderEndpoint, m_SenderEndpointV6; boost::asio::deadline_timer m_IntroducersUpdateTimer; std::list m_Introducers; // introducers we are connected to - i2p::crypto::AESAlignedBuffer<2*SSU_MTU_V4> m_ReceiveBuffer; - i2p::crypto::AESAlignedBuffer<2*SSU_MTU_V6> m_ReceiveBufferV6; std::mutex m_SessionsMutex; std::map > m_Sessions; std::map m_Relays; // we are introducer From c349652a27079e0ea03630b3b7d6e3c17232f0fb Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 8 Feb 2015 21:12:38 -0500 Subject: [PATCH 0164/6300] terminate receivers --- SSU.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/SSU.cpp b/SSU.cpp index 54c4fb6c..440b4f5b 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -54,6 +54,7 @@ namespace transport m_Socket.close (); m_ServiceV6.stop (); m_SocketV6.close (); + m_ReceiversService.stop (); if (m_ReceiversThread) { m_ReceiversThread->join (); From c6c414c382ccc506af6c52cc1fe2f3bb07ef3c08 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 9 Feb 2015 11:53:11 -0500 Subject: [PATCH 0165/6300] receive multiple UDP packets --- SSU.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++--------------- SSU.h | 2 +- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 440b4f5b..1ad3c3fd 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -160,7 +160,19 @@ namespace transport if (!ecode) { packet->len = bytes_transferred; - m_Service.post (std::bind (&SSUServer::HandleReceivedBuffer, this, packet)); + std::vector packets; + packets.push_back (packet); + + size_t moreBytes = m_Socket.available(); + while (moreBytes && packets.size () < 25) + { + packet = new SSUPacket (); + packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from); + packets.push_back (packet); + moreBytes = m_Socket.available(); + } + + m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets)); Receive (); } else @@ -175,7 +187,19 @@ namespace transport if (!ecode) { packet->len = bytes_transferred; - m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedBuffer, this, packet)); + std::vector packets; + packets.push_back (packet); + + size_t moreBytes = m_SocketV6.available (); + while (moreBytes && packets.size () < 25) + { + packet = new SSUPacket (); + packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from); + packets.push_back (packet); + moreBytes = m_SocketV6.available(); + } + + m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets)); ReceiveV6 (); } else @@ -185,24 +209,28 @@ namespace transport } } - void SSUServer::HandleReceivedBuffer (SSUPacket * packet) + void SSUServer::HandleReceivedPackets (std::vector packets) { - std::shared_ptr session; - auto it = m_Sessions.find (packet->from); - if (it != m_Sessions.end ()) - session = it->second; - if (!session) + for (auto it1: packets) { - session = std::make_shared (*this, packet->from); - session->WaitForConnect (); + auto packet = it1; + std::shared_ptr session; + auto it = m_Sessions.find (packet->from); + if (it != m_Sessions.end ()) + session = it->second; + if (!session) { - std::unique_lock l(m_SessionsMutex); - m_Sessions[packet->from] = session; - } - LogPrint ("New SSU session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); + session = std::make_shared (*this, packet->from); + session->WaitForConnect (); + { + std::unique_lock l(m_SessionsMutex); + m_Sessions[packet->from] = session; + } + LogPrint ("New SSU session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); + } + session->ProcessNextMessage (packet->buf, packet->len, packet->from); + delete packet; } - session->ProcessNextMessage (packet->buf, packet->len, packet->from); - delete packet; } std::shared_ptr SSUServer::FindSession (std::shared_ptr router) const diff --git a/SSU.h b/SSU.h index 01ca2607..70cea541 100644 --- a/SSU.h +++ b/SSU.h @@ -62,7 +62,7 @@ namespace transport void ReceiveV6 (); void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); - void HandleReceivedBuffer (SSUPacket * packet); + void HandleReceivedPackets (std::vector packets); template std::shared_ptr GetRandomSession (Filter filter); From 23907e6cb103071bc18dc1c1c4f4085a424566fc Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 9 Feb 2015 14:48:01 -0500 Subject: [PATCH 0166/6300] don't look for session if a packet is from same endpoint as previous --- SSU.cpp | 25 ++++++++++++++----------- SSUData.cpp | 3 +-- SSUSession.cpp | 1 - 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 1ad3c3fd..bb515ac9 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -211,22 +211,25 @@ namespace transport void SSUServer::HandleReceivedPackets (std::vector packets) { + std::shared_ptr session; for (auto it1: packets) { auto packet = it1; - std::shared_ptr session; - auto it = m_Sessions.find (packet->from); - if (it != m_Sessions.end ()) - session = it->second; - if (!session) + if (session && session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous { - session = std::make_shared (*this, packet->from); - session->WaitForConnect (); + auto it = m_Sessions.find (packet->from); + if (it != m_Sessions.end ()) + session = it->second; + if (!session) { - std::unique_lock l(m_SessionsMutex); - m_Sessions[packet->from] = session; - } - LogPrint ("New SSU session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); + session = std::make_shared (*this, packet->from); + session->WaitForConnect (); + { + std::unique_lock l(m_SessionsMutex); + m_Sessions[packet->from] = session; + } + LogPrint ("New SSU session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); + } } session->ProcessNextMessage (packet->buf, packet->len, packet->from); delete packet; diff --git a/SSUData.cpp b/SSUData.cpp index c66241b7..bfc17d31 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -152,8 +152,7 @@ namespace transport uint32_t fragmentInfo = bufbe32toh (frag); // fragment info uint16_t fragmentSize = fragmentInfo & 0x1FFF; // bits 0 - 13 bool isLast = fragmentInfo & 0x010000; // bit 16 - uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17 - LogPrint (eLogDebug, "SSU data fragment ", (int)fragmentNum, " of message ", msgID, " size=", (int)fragmentSize, isLast ? " last" : " non-last"); + uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17 if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE) { LogPrint (eLogError, "Fragment size ", fragmentSize, "exceeds max SSU packet size"); diff --git a/SSUSession.cpp b/SSUSession.cpp index ed7fa9a4..1ee5d5f5 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -130,7 +130,6 @@ namespace transport switch (header->GetPayloadType ()) { case PAYLOAD_TYPE_DATA: - LogPrint (eLogDebug, "SSU data received"); ProcessData (buf + sizeof (SSUHeader), len - sizeof (SSUHeader)); break; case PAYLOAD_TYPE_SESSION_REQUEST: From 89883e20789b14ec2aab81ab036cf87c34d34f8b Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 9 Feb 2015 14:50:19 -0500 Subject: [PATCH 0167/6300] don't look for session if a packet is from same endpoint as previous --- SSU.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SSU.cpp b/SSU.cpp index bb515ac9..51eccf83 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -215,7 +215,7 @@ namespace transport for (auto it1: packets) { auto packet = it1; - if (session && session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous + if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous { auto it = m_Sessions.find (packet->from); if (it != m_Sessions.end ()) From 923b64eb4872aad9ac6978efe37e4be2337e4bfb Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 9 Feb 2015 16:33:52 -0500 Subject: [PATCH 0168/6300] delete messages sent to disconnected session --- NTCPSession.cpp | 11 +++++++++++ SSUSession.cpp | 21 +++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 43816d2d..6184a377 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -678,6 +678,11 @@ namespace transport { if (msg) { + if (m_IsTerminated) + { + DeleteI2NPMessage (msg); + return; + } if (m_IsSending) m_SendQueue.push_back (msg); else @@ -692,6 +697,12 @@ namespace transport void NTCPSession::PostI2NPMessages (std::vector msgs) { + if (m_IsTerminated) + { + for (auto it: msgs) + DeleteI2NPMessage (it); + return; + } if (m_IsSending) { for (auto it: msgs) diff --git a/SSUSession.cpp b/SSUSession.cpp index 1ee5d5f5..7b81903c 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -830,13 +830,18 @@ namespace transport void SSUSession::SendI2NPMessage (I2NPMessage * msg) { - GetService ().post (std::bind (&SSUSession::PostI2NPMessage, shared_from_this (), msg)); + GetService ().post (std::bind (&SSUSession::PostI2NPMessage, shared_from_this (), msg)); } void SSUSession::PostI2NPMessage (I2NPMessage * msg) { if (msg) - m_Data.Send (msg); + { + if (m_State == eSessionStateEstablished) + m_Data.Send (msg); + else + DeleteI2NPMessage (msg); + } } void SSUSession::SendI2NPMessages (const std::vector& msgs) @@ -846,8 +851,16 @@ namespace transport void SSUSession::PostI2NPMessages (std::vector msgs) { - for (auto it: msgs) - if (it) m_Data.Send (it); + if (m_State == eSessionStateEstablished) + { + for (auto it: msgs) + if (it) m_Data.Send (it); + } + else + { + for (auto it: msgs) + DeleteI2NPMessage (it); + } } void SSUSession::ProcessData (uint8_t * buf, size_t len) From 8492e87d291035c99bcf9b365f472adc0a38d1e4 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 9 Feb 2015 22:19:29 -0500 Subject: [PATCH 0169/6300] fxied race condition --- NetDb.cpp | 57 ++++++++++++++++++++++++------------------------------- NetDb.h | 3 --- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 0b68708d..703155ef 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -453,18 +453,18 @@ namespace data void NetDb::RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete) { // request RouterInfo directly - auto& dest = CreateRequestedDestination (destination, false); - if (requestComplete) + auto dest = new RequestedDestination (destination, false); // non-exploratory + dest->SetRequestComplete (requestComplete); { - if (dest->IsRequestComplete ()) // if set already - { + std::unique_lock l(m_RequestedDestinationsMutex); + if (!m_RequestedDestinations.insert (std::make_pair (destination, + std::unique_ptr (dest))).second) // not inserted + { LogPrint (eLogWarning, "Destination ", destination.ToBase64(), " is requested already"); - requestComplete (nullptr); // TODO: implement it better return; } - else - dest->SetRequestComplete (requestComplete); - } + } + auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ()); if (floodfill) transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); @@ -472,7 +472,8 @@ namespace data { LogPrint (eLogError, "No floodfills found"); dest->Fail (); - DeleteRequestedDestination (destination); + std::unique_lock l(m_RequestedDestinationsMutex); + m_RequestedDestinations.erase (destination); } } @@ -782,7 +783,16 @@ namespace data for (int i = 0; i < numDestinations; i++) { rnd.GenerateBlock (randomHash, 32); - auto& dest = CreateRequestedDestination (IdentHash (randomHash), true); + auto dest = new RequestedDestination (randomHash, true); // exploratory + { + std::unique_lock l(m_RequestedDestinationsMutex); + if (!m_RequestedDestinations.insert (std::make_pair (randomHash, + std::unique_ptr (dest))).second) // not inserted + { + LogPrint (eLogWarning, "Exploratory destination is requested already"); + return; + } + } auto floodfill = GetClosestFloodfill (randomHash, dest->GetExcludedPeers ()); if (floodfill && !floodfills.count (floodfill.get ())) // request floodfill only once { @@ -806,7 +816,10 @@ namespace data i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); } else - DeleteRequestedDestination (dest->GetDestination ()); + { + std::unique_lock l(m_RequestedDestinationsMutex); + m_RequestedDestinations.erase (dest->GetDestination ()); + } } if (throughTunnels && msgs.size () > 0) outbound->SendTunnelDataMsg (msgs); @@ -826,27 +839,7 @@ namespace data excluded.insert (floodfill->GetIdentHash ()); } } - } - - std::unique_ptr& NetDb::CreateRequestedDestination (const IdentHash& dest, bool isExploratory) - { - std::unique_lock l(m_RequestedDestinationsMutex); - auto it = m_RequestedDestinations.find (dest); - if (it == m_RequestedDestinations.end ()) // not exist yet - { - auto d = new RequestedDestination (dest, isExploratory); - return m_RequestedDestinations.insert (std::make_pair (dest, - std::unique_ptr (d))).first->second; - } - else - return it->second; - } - - void NetDb::DeleteRequestedDestination (IdentHash ident) - { - std::unique_lock l(m_RequestedDestinationsMutex); - m_RequestedDestinations.erase (ident); - } + } std::shared_ptr NetDb::GetRandomRouter () const { diff --git a/NetDb.h b/NetDb.h index 346ece28..ee0411de 100644 --- a/NetDb.h +++ b/NetDb.h @@ -104,9 +104,6 @@ namespace data void ManageLeaseSets (); void ManageRequests (); - std::unique_ptr& CreateRequestedDestination (const IdentHash& dest, bool isExploratory = false); - void DeleteRequestedDestination (IdentHash ident); - template std::shared_ptr GetRandomRouter (Filter filter) const; From d01f3b094bceb641ee523bfd42aff286d45da5a1 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Feb 2015 09:30:48 -0500 Subject: [PATCH 0170/6300] allow netDb cleanup after every 500 messages --- NetDb.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 703155ef..2f659e07 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -130,7 +130,8 @@ namespace data I2NPMessage * msg = m_Queue.GetNextWithTimeout (15000); // 15 sec if (msg) { - while (msg) + int numMsgs = 0; + while (msg && numMsgs < 500) { switch (msg->GetTypeID ()) { @@ -151,10 +152,10 @@ namespace data i2p::HandleI2NPMessage (msg); } msg = m_Queue.Get (); + numMsgs++; } - } - else - if (!m_IsRunning) break; + } + if (!m_IsRunning) break; uint64_t ts = i2p::util::GetSecondsSinceEpoch (); if (ts - lastManageRequest >= 15) // manage requests every 15 seconds From a3c1c314d06429e0979a191e447e994f72ccb601 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Feb 2015 09:39:49 -0500 Subject: [PATCH 0171/6300] don't insert same transit tunnel twice --- Tunnel.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 6ce8348a..de3096d6 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -317,7 +317,11 @@ namespace tunnel void Tunnels::AddTransitTunnel (TransitTunnel * tunnel) { std::unique_lock l(m_TransitTunnelsMutex); - m_TransitTunnels[tunnel->GetTunnelID ()] = tunnel; + if (!m_TransitTunnels.insert (std::make_pair (tunnel->GetTunnelID (), tunnel)).second) + { + LogPrint (eLogError, "Transit tunnel ", tunnel->GetTunnelID (), " already exists"); + delete tunnel; + } } void Tunnels::Start () From 6783b22ad008a2363338bd146d0af723b04af843 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Feb 2015 10:54:04 -0500 Subject: [PATCH 0172/6300] terminate non-responding NTCP sessions by timeout --- NTCPSession.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 6184a377..570c5e56 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -91,6 +91,7 @@ namespace transport i2p::DeleteI2NPMessage (m_NextMessage); m_NextMessage = nullptr; } + m_TerminationTimer.cancel (); LogPrint (eLogInfo, "NTCP session terminated"); } } @@ -125,6 +126,7 @@ namespace transport boost::asio::async_write (m_Socket, boost::asio::buffer (&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (), std::bind(&NTCPSession::HandlePhase1Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + ScheduleTermination (); } void NTCPSession::ServerLogin () @@ -133,6 +135,7 @@ namespace transport boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (), std::bind(&NTCPSession::HandlePhase1Received, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + ScheduleTermination (); } void NTCPSession::HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred) From 52f9d5f0aa82d3e79a679c3299537f5fb1b2a153 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Feb 2015 13:05:08 -0500 Subject: [PATCH 0173/6300] ban abusing IPs --- NTCPSession.cpp | 39 +++++++++++++++++++++++++++++++++------ NTCPSession.h | 6 +++++- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 570c5e56..b770de58 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -131,11 +131,17 @@ namespace transport void NTCPSession::ServerLogin () { - // receive Phase1 - boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandlePhase1Received, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); - ScheduleTermination (); + boost::system::error_code ec; + auto ep = m_Socket.remote_endpoint(ec); + if (!ec) + { + m_ConnectedFrom = ep.address (); + // receive Phase1 + boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (), + std::bind(&NTCPSession::HandlePhase1Received, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); + ScheduleTermination (); + } } void NTCPSession::HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred) @@ -482,6 +488,7 @@ namespace transport if (ecode) { LogPrint (eLogError, "Read error: ", ecode.message ()); + if (!m_NumReceivedBytes) m_Server.Ban (m_ConnectedFrom); //if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -857,7 +864,20 @@ namespace transport if (!ec) { LogPrint (eLogInfo, "Connected from ", ep); - conn->ServerLogin (); + auto it = m_BanList.find (ep.address ()); + if (it != m_BanList.end ()) + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + if (ts < it->second) + { + LogPrint (eLogInfo, ep.address (), " is banned for ", it->second - ts, " more seconds"); + conn = nullptr; + } + else + m_BanList.erase (it); + } + if (conn) + conn->ServerLogin (); } else LogPrint (eLogError, "Connected from error ", ec.message ()); @@ -923,5 +943,12 @@ namespace transport conn->ClientLogin (); } } + + void NTCPServer::Ban (const boost::asio::ip::address& addr) + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + m_BanList[addr] = ts + NTCP_BAN_EXPIRATION_TIMEOUT; + LogPrint (eLogInfo, addr, " has been banned for ", NTCP_BAN_EXPIRATION_TIMEOUT, " seconds"); + } } } diff --git a/NTCPSession.h b/NTCPSession.h index c264b7b0..608a2c7c 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -45,6 +45,7 @@ namespace transport const size_t NTCP_BUFFER_SIZE = 4160; // fits 4 tunnel messages (4*1028) const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes 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 class NTCPServer; class NTCPSession: public TransportSession, public std::enable_shared_from_this @@ -138,6 +139,7 @@ namespace transport std::vector m_SendQueue; size_t m_NumSentBytes, m_NumReceivedBytes; + boost::asio::ip::address m_ConnectedFrom; // for ban }; // TODO: move to NTCP.h/.cpp @@ -157,7 +159,8 @@ namespace transport void Connect (const boost::asio::ip::address& address, int port, std::shared_ptr conn); boost::asio::io_service& GetService () { return m_Service; }; - + void Ban (const boost::asio::ip::address& addr); + private: void Run (); @@ -175,6 +178,7 @@ namespace transport boost::asio::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor; std::mutex m_NTCPSessionsMutex; std::map > m_NTCPSessions; + std::map m_BanList; // IP -> ban expiration time in seconds public: From a27dd17cef1d1da2d39c0152f83b6c81e3ed782a Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Feb 2015 15:51:56 -0500 Subject: [PATCH 0174/6300] fixed memory leak --- Transports.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Transports.h b/Transports.h index c9b882c5..8c368a6d 100644 --- a/Transports.h +++ b/Transports.h @@ -60,6 +60,7 @@ namespace transport ~Peer () { + if (session) session->Done (); for (auto it :delayedMessages) i2p::DeleteI2NPMessage (it); } From 3263f6fefca43a4da6abc550554342f5b251cc44 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Feb 2015 15:54:07 -0500 Subject: [PATCH 0175/6300] ban abusing ipv6 addresses --- NTCPSession.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index b770de58..aab7f9cd 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -901,7 +901,20 @@ namespace transport if (!ec) { LogPrint (eLogInfo, "Connected from ", ep); - conn->ServerLogin (); + auto it = m_BanList.find (ep.address ()); + if (it != m_BanList.end ()) + { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + if (ts < it->second) + { + LogPrint (eLogInfo, ep.address (), " is banned for ", it->second - ts, " more seconds"); + conn = nullptr; + } + else + m_BanList.erase (it); + } + if (conn) + conn->ServerLogin (); } else LogPrint (eLogError, "Connected from error ", ec.message ()); From 9eba7923a7f85b5b6541cff66654f905cdaac2ec Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Feb 2015 16:51:47 -0500 Subject: [PATCH 0176/6300] use insert instead [] --- Transports.cpp | 2 +- Transports.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index afa3de5c..961b172b 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -418,7 +418,7 @@ namespace transport } } else // incoming connection - m_Peers[ident] = { 0, nullptr, session }; + m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, session })); }); } diff --git a/Transports.h b/Transports.h index 8c368a6d..6dad320e 100644 --- a/Transports.h +++ b/Transports.h @@ -60,7 +60,7 @@ namespace transport ~Peer () { - if (session) session->Done (); + //if (session) session->Done (); for (auto it :delayedMessages) i2p::DeleteI2NPMessage (it); } From 0ed9dc4e933a37159f3156ee00fcb7697c58ae7b Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Feb 2015 17:36:39 -0500 Subject: [PATCH 0177/6300] complete session if presented --- Transports.cpp | 1 + Transports.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Transports.cpp b/Transports.cpp index 961b172b..70c6b758 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -291,6 +291,7 @@ namespace transport } } LogPrint (eLogError, "No NTCP and SSU addresses available"); + if (peer.session) peer.session->Done (); m_Peers.erase (ident); return false; } diff --git a/Transports.h b/Transports.h index 6dad320e..c9b882c5 100644 --- a/Transports.h +++ b/Transports.h @@ -60,7 +60,6 @@ namespace transport ~Peer () { - //if (session) session->Done (); for (auto it :delayedMessages) i2p::DeleteI2NPMessage (it); } From 53adf7a79398fa9fbde7e3236fe641753f21e032 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Feb 2015 14:45:25 -0500 Subject: [PATCH 0178/6300] cleanup dead peers --- Transports.cpp | 30 ++++++++++++++++++++++++++---- Transports.h | 6 +++++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 70c6b758..713f4ea1 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -95,7 +95,7 @@ namespace transport Transports transports; Transports::Transports (): - m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), + 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 { @@ -134,10 +134,13 @@ namespace transport LogPrint ("SSU server already exists"); } } + m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); + m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); } void Transports::Stop () { + m_PeerCleanupTimer.cancel (); m_Peers.clear (); if (m_SSUServer) { @@ -202,7 +205,8 @@ namespace transport if (it == m_Peers.end ()) { auto r = netdb.FindRouter (ident); - it = m_Peers.insert (std::pair(ident, { 0, r, nullptr})).first; + it = m_Peers.insert (std::pair(ident, { 0, r, nullptr, + i2p::util::GetSecondsSinceEpoch () })).first; if (!ConnectToPeer (ident, it->second)) { DeleteI2NPMessage (msg); @@ -228,7 +232,8 @@ namespace transport if (it == m_Peers.end ()) { auto r = netdb.FindRouter (ident); - it = m_Peers.insert (std::pair(ident, { 0, r, nullptr})).first; + it = m_Peers.insert (std::pair(ident, { 0, r, nullptr, + i2p::util::GetSecondsSinceEpoch () })).first; if (!ConnectToPeer (ident, it->second)) { for (auto it1: msgs) @@ -419,7 +424,7 @@ namespace transport } } else // incoming connection - m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, session })); + m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, session, i2p::util::GetSecondsSinceEpoch () })); }); } @@ -438,6 +443,23 @@ namespace transport } }); } + + void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode) + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_Peers.begin (); it != m_Peers.end (); ) + { + if (!it->second.session && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) + { + LogPrint (eLogError, "Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); + it = m_Peers.erase (it); + } + else + it++; + } + m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); + m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); + } } } diff --git a/Transports.h b/Transports.h index c9b882c5..e1c85374 100644 --- a/Transports.h +++ b/Transports.h @@ -56,6 +56,7 @@ namespace transport int numAttempts; std::shared_ptr router; std::shared_ptr session; + uint64_t creationTime; std::vector delayedMessages; ~Peer () @@ -65,6 +66,7 @@ namespace transport } }; + const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds class Transports { public: @@ -95,7 +97,8 @@ namespace transport void PostMessages (i2p::data::IdentHash ident, std::vector msgs); void PostCloseSession (std::shared_ptr router); bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); - + void HandlePeerCleanupTimer (const boost::system::error_code& ecode); + void NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident); void HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, const i2p::data::IdentHash& ident, std::shared_ptr resolver); @@ -108,6 +111,7 @@ namespace transport std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; + boost::asio::deadline_timer m_PeerCleanupTimer; NTCPServer * m_NTCPServer; SSUServer * m_SSUServer; From 816e4d533df8d1bf16a67bb84dbccc613e62d20b Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Feb 2015 18:47:20 -0500 Subject: [PATCH 0179/6300] fixed race condition --- TransitTunnel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index 31cbb4f8..770ab246 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -74,6 +74,7 @@ namespace tunnel void TransitTunnelGateway::FlushTunnelDataMsgs () { + std::unique_lock l(m_SendMutex); m_Gateway.SendBuffer (); } From dcad550a2f976f7ee116b86845e72501d8bc2ab8 Mon Sep 17 00:00:00 2001 From: multikatt Date: Wed, 11 Feb 2015 21:06:52 -0500 Subject: [PATCH 0180/6300] typo: stoped->stopped --- ClientContext.cpp | 14 +++++++------- Daemon.cpp | 10 +++++----- api.cpp | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 4a34cbdc..0b1c7344 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -93,45 +93,45 @@ namespace client m_HttpProxy->Stop(); delete m_HttpProxy; m_HttpProxy = nullptr; - LogPrint("HTTP Proxy stoped"); + LogPrint("HTTP Proxy stopped"); m_SocksProxy->Stop(); delete m_SocksProxy; m_SocksProxy = nullptr; - LogPrint("SOCKS Proxy stoped"); + LogPrint("SOCKS Proxy stopped"); if (m_IrcTunnel) { m_IrcTunnel->Stop (); delete m_IrcTunnel; m_IrcTunnel = nullptr; - LogPrint("IRC tunnel stoped"); + LogPrint("IRC tunnel stopped"); } if (m_ServerTunnel) { m_ServerTunnel->Stop (); delete m_ServerTunnel; m_ServerTunnel = nullptr; - LogPrint("Server tunnel stoped"); + LogPrint("Server tunnel stopped"); } if (m_SamBridge) { m_SamBridge->Stop (); delete m_SamBridge; m_SamBridge = nullptr; - LogPrint("SAM brdige stoped"); + LogPrint("SAM brdige stopped"); } if (m_BOBCommandChannel) { m_BOBCommandChannel->Stop (); delete m_BOBCommandChannel; m_BOBCommandChannel = nullptr; - LogPrint("BOB command channel stoped"); + LogPrint("BOB command channel stopped"); } if (m_I2PControlService) { m_I2PControlService->Stop (); delete m_I2PControlService; m_I2PControlService = nullptr; - LogPrint("I2PControl stoped"); + LogPrint("I2PControl stopped"); } for (auto it: m_Destinations) diff --git a/Daemon.cpp b/Daemon.cpp index 6fd54f0e..1dbbc82f 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -128,15 +128,15 @@ namespace i2p { LogPrint("Shutdown started."); i2p::client::context.Stop(); - LogPrint("Client stoped"); + LogPrint("Client stopped"); i2p::tunnel::tunnels.Stop(); - LogPrint("Tunnels stoped"); + LogPrint("Tunnels stopped"); i2p::transport::transports.Stop(); - LogPrint("Transports stoped"); + LogPrint("Transports stopped"); i2p::data::netdb.Stop(); - LogPrint("NetDB stoped"); + LogPrint("NetDB stopped"); d.httpServer->Stop(); - LogPrint("HTTP Server stoped"); + LogPrint("HTTP Server stopped"); #ifdef USE_UPNP i2p::UPnP::upnpc.Stop(); #endif diff --git a/api.cpp b/api.cpp index 0fb4256f..6f3c8c4c 100644 --- a/api.cpp +++ b/api.cpp @@ -39,11 +39,11 @@ namespace api { LogPrint("Shutdown started."); i2p::tunnel::tunnels.Stop(); - LogPrint("Tunnels stoped"); + LogPrint("Tunnels stopped"); i2p::transport::transports.Stop(); - LogPrint("Transports stoped"); + LogPrint("Transports stopped"); i2p::data::netdb.Stop(); - LogPrint("NetDB stoped"); + LogPrint("NetDB stopped"); StopLog (); } From 93857b690a42d2b24e2fef3d94b430a35a2bc76f Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Feb 2015 22:48:26 -0500 Subject: [PATCH 0181/6300] fixed hand at shutdown --- Transports.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 713f4ea1..1340d8f5 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -446,19 +446,22 @@ namespace transport void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode) { - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_Peers.begin (); it != m_Peers.end (); ) + if (ecode != boost::asio::error::operation_aborted) { - if (!it->second.session && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_Peers.begin (); it != m_Peers.end (); ) { - LogPrint (eLogError, "Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); - it = m_Peers.erase (it); + if (!it->second.session && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) + { + LogPrint (eLogError, "Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); + it = m_Peers.erase (it); + } + else + it++; } - else - it++; - } - m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); - m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); + m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); + m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); + } } } } From b11fd250c1169b25c328e4d84f527e6ca1ff9575 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 Feb 2015 11:40:42 -0500 Subject: [PATCH 0182/6300] fixed race condition --- I2NPProtocol.cpp | 2 +- NetDb.cpp | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 7216e864..862b97e9 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -82,7 +82,7 @@ namespace i2p I2NPMessage * CreateDeliveryStatusMsg (uint32_t msgID) { - I2NPMessage * m = NewI2NPMessage (); + I2NPMessage * m = NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); if (msgID) { diff --git a/NetDb.cpp b/NetDb.cpp index 2f659e07..ba324b37 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -131,7 +131,7 @@ namespace data if (msg) { int numMsgs = 0; - while (msg && numMsgs < 500) + while (msg) { switch (msg->GetTypeID ()) { @@ -151,6 +151,7 @@ namespace data LogPrint ("NetDb: unexpected message type ", msg->GetTypeID ()); i2p::HandleI2NPMessage (msg); } + if (numMsgs > 100) break; msg = m_Queue.Get (); numMsgs++; } @@ -572,12 +573,12 @@ namespace data auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; auto inbound = pool ? pool->GetNextInboundTunnel () : nullptr; - std::vector msgs; if (!dest->IsExploratory ()) { // reply to our destination. Try other floodfills if (outbound && inbound ) { + std::vector msgs; auto count = dest->GetExcludedPeers ().size (); if (count < 7) { @@ -605,11 +606,12 @@ namespace data } else LogPrint (key, " was not found on 7 floodfills"); + + if (msgs.size () > 0) + outbound->SendTunnelDataMsg (msgs); } } - if (outbound && msgs.size () > 0) - outbound->SendTunnelDataMsg (msgs); if (deleteDest) { // no more requests for the destinationation. delete it From 93c2c13f96958deeddf7968405ccbab619caebb5 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 Feb 2015 14:03:59 -0500 Subject: [PATCH 0183/6300] fixed memory leak --- NetDb.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index ba324b37..f417809b 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -515,6 +515,7 @@ namespace data auto floodfill = GetClosestFloodfill (buf + DATABASE_STORE_KEY_OFFSET, excluded); if (floodfill) { + excluded.insert (floodfill->GetIdentHash ()); auto floodMsg = NewI2NPShortMessage (); uint8_t * payload = floodMsg->GetPayload (); memcpy (payload, buf, 33); // key + type @@ -544,13 +545,20 @@ namespace data i2p::DeleteI2NPMessage (m); return; } - CryptoPP::Gunzip decompressor; - decompressor.Put (buf + offset, size); - decompressor.MessageEnd(); - uint8_t uncompressed[2048]; - size_t uncomressedSize = decompressor.MaxRetrievable (); - decompressor.Get (uncompressed, uncomressedSize); - AddRouterInfo (buf + DATABASE_STORE_KEY_OFFSET, uncompressed, uncomressedSize); + try + { + CryptoPP::Gunzip decompressor; + decompressor.Put (buf + offset, size); + decompressor.MessageEnd(); + uint8_t uncompressed[2048]; + size_t uncomressedSize = decompressor.MaxRetrievable (); + decompressor.Get (uncompressed, uncomressedSize); + AddRouterInfo (buf + DATABASE_STORE_KEY_OFFSET, uncompressed, uncomressedSize); + } + catch (CryptoPP::Exception& ex) + { + LogPrint (eLogError, "DatabaseStore: ", ex.what ()); + } } i2p::DeleteI2NPMessage (m); } From 18121a99ca36920a38acc3af20fb2868fa38e9b6 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 Feb 2015 16:11:56 -0500 Subject: [PATCH 0184/6300] config file for I2PTunnels --- ClientContext.cpp | 81 ++++++++++++++++++++++++++++++++++++++++------- ClientContext.h | 16 ++++++++-- 2 files changed, 84 insertions(+), 13 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 0b1c7344..85b0c43b 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -1,4 +1,7 @@ #include +#include +#include +#include #include "util.h" #include "Log.h" #include "Identity.h" @@ -11,9 +14,8 @@ namespace client ClientContext context; ClientContext::ClientContext (): m_SharedLocalDestination (nullptr), - m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_IrcTunnel (nullptr), - m_ServerTunnel (nullptr), m_SamBridge (nullptr), m_BOBCommandChannel (nullptr), - m_I2PControlService (nullptr) + m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_ServerTunnel (nullptr), + m_SamBridge (nullptr), m_BOBCommandChannel (nullptr), m_I2PControlService (nullptr) { } @@ -21,7 +23,6 @@ namespace client { delete m_HttpProxy; delete m_SocksProxy; - delete m_IrcTunnel; delete m_ServerTunnel; delete m_SamBridge; delete m_BOBCommandChannel; @@ -37,12 +38,15 @@ namespace client m_SharedLocalDestination->Start (); } + // proxies m_HttpProxy = new i2p::proxy::HTTPProxy(i2p::util::config::GetArg("-httpproxyport", 4446)); m_HttpProxy->Start(); LogPrint("HTTP Proxy started"); m_SocksProxy = new i2p::proxy::SOCKSProxy(i2p::util::config::GetArg("-socksproxyport", 4447)); m_SocksProxy->Start(); LogPrint("SOCKS Proxy Started"); + + // I2P tunnels std::string ircDestination = i2p::util::config::GetArg("-ircdest", ""); if (ircDestination.length () > 0) // ircdest is presented { @@ -50,8 +54,9 @@ namespace client std::string ircKeys = i2p::util::config::GetArg("-irckeys", ""); if (ircKeys.length () > 0) localDestination = LoadLocalDestination (ircKeys, false); - m_IrcTunnel = new I2PClientTunnel (ircDestination, i2p::util::config::GetArg("-ircport", 6668), localDestination); - m_IrcTunnel->Start (); + auto ircTunnel = new I2PClientTunnel (ircDestination, i2p::util::config::GetArg("-ircport", 6668), localDestination); + ircTunnel->Start (); + m_ClientTunnels.push_back (std::unique_ptr(ircTunnel)); LogPrint("IRC tunnel started"); } std::string eepKeys = i2p::util::config::GetArg("-eepkeys", ""); @@ -63,6 +68,9 @@ namespace client m_ServerTunnel->Start (); LogPrint("Server tunnel started"); } + ReadTunnels (); + + // SAM int samPort = i2p::util::config::GetArg("-samport", 0); if (samPort) { @@ -70,6 +78,8 @@ namespace client m_SamBridge->Start (); LogPrint("SAM bridge started"); } + + // BOB int bobPort = i2p::util::config::GetArg("-bobport", 0); if (bobPort) { @@ -77,6 +87,8 @@ namespace client m_BOBCommandChannel->Start (); LogPrint("BOB command channel started"); } + + // I2P Control int i2pcontrolPort = i2p::util::config::GetArg("-i2pcontrolport", 0); if (i2pcontrolPort) { @@ -98,13 +110,12 @@ namespace client delete m_SocksProxy; m_SocksProxy = nullptr; LogPrint("SOCKS Proxy stopped"); - if (m_IrcTunnel) + for (auto& it: m_ClientTunnels) { - m_IrcTunnel->Stop (); - delete m_IrcTunnel; - m_IrcTunnel = nullptr; - LogPrint("IRC tunnel stopped"); + it->Stop (); + LogPrint("I2P client tunnel stopped"); } + m_ClientTunnels.clear (); if (m_ServerTunnel) { m_ServerTunnel->Stop (); @@ -235,5 +246,53 @@ namespace client return it->second; return nullptr; } + + void ClientContext::ReadTunnels () + { + std::ifstream ifs (i2p::util::filesystem::GetFullPath (TUNNELS_CONFIG_FILENAME)); + if (ifs.good ()) + { + boost::program_options::options_description params ("I2P tunnels parameters"); + params.add_options () + (I2P_CLIENT_TUNNEL_NAME, boost::program_options::value()->default_value ("I2P Tunnel"), "tunnel name") + (I2P_CLIENT_TUNNEL_PORT, boost::program_options::value(), "Local port") + (I2P_CLIENT_TUNNEL_DESTINATION, boost::program_options::value(), "destination") + (I2P_CLIENT_TUNNEL_KEYS, boost::program_options::value()->default_value (""), "keys") + ; + + + boost::program_options::variables_map vm; + try + { + boost::program_options::store (boost::program_options::parse_config_file (ifs, params), vm); + boost::program_options::notify (vm); + } + catch (boost::program_options::error& ex) + { + LogPrint (eLogError, "Can't parse ", TUNNELS_CONFIG_FILENAME,": ", ex.what ()); + return; + } + + int numClientTunnels = vm.count (I2P_CLIENT_TUNNEL_NAME); + if (numClientTunnels > 0) + { + // auto& names = vm[I2P_CLIENT_TUNNEL_NAME].as >(); + auto& ports = vm[I2P_CLIENT_TUNNEL_PORT].as >(); + auto& destinations = vm[I2P_CLIENT_TUNNEL_DESTINATION].as >(); + auto& keys = vm[I2P_CLIENT_TUNNEL_KEYS].as >(); + + for (int i = 0; i < numClientTunnels; i++) + { + ClientDestination * localDestination = nullptr; + if (keys[i].length () > 0) + localDestination = LoadLocalDestination (keys[i], false); + auto clientTunnel = new I2PClientTunnel (destinations[i], ports[i], localDestination); + clientTunnel->Start (); + m_ClientTunnels.push_back (std::unique_ptr(clientTunnel)); + } + LogPrint (eLogInfo, numClientTunnels, " I2P client tunnels created"); + } + } + } } } diff --git a/ClientContext.h b/ClientContext.h index f472dc32..94a7fe0e 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -1,7 +1,9 @@ #ifndef CLIENT_CONTEXT_H__ #define CLIENT_CONTEXT_H__ +#include #include +#include #include "Destination.h" #include "HTTPProxy.h" #include "SOCKS.h" @@ -15,6 +17,12 @@ namespace i2p { namespace client { + const char I2P_CLIENT_TUNNEL_NAME[] = "client.name"; + const char I2P_CLIENT_TUNNEL_PORT[] = "client.port"; + const char I2P_CLIENT_TUNNEL_DESTINATION[] = "client.destination"; + const char I2P_CLIENT_TUNNEL_KEYS[] = "client.keys"; + const char TUNNELS_CONFIG_FILENAME[] = "tunnels.cfg"; + class ClientContext { public: @@ -35,7 +43,11 @@ namespace client ClientDestination * LoadLocalDestination (const std::string& filename, bool isPublic); AddressBook& GetAddressBook () { return m_AddressBook; }; - + + private: + + void ReadTunnels (); + private: std::mutex m_DestinationsMutex; @@ -46,7 +58,7 @@ namespace client i2p::proxy::HTTPProxy * m_HttpProxy; i2p::proxy::SOCKSProxy * m_SocksProxy; - I2PClientTunnel * m_IrcTunnel; + std::list > m_ClientTunnels; I2PServerTunnel * m_ServerTunnel; SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; From 242bc3624a76e9d0c7624e45aef90ec5756683ed Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 Feb 2015 22:20:07 -0500 Subject: [PATCH 0185/6300] read tunnels.cfg --- ClientContext.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 85b0c43b..70493c14 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -254,10 +254,10 @@ namespace client { boost::program_options::options_description params ("I2P tunnels parameters"); params.add_options () - (I2P_CLIENT_TUNNEL_NAME, boost::program_options::value()->default_value ("I2P Tunnel"), "tunnel name") - (I2P_CLIENT_TUNNEL_PORT, boost::program_options::value(), "Local port") - (I2P_CLIENT_TUNNEL_DESTINATION, boost::program_options::value(), "destination") - (I2P_CLIENT_TUNNEL_KEYS, boost::program_options::value()->default_value (""), "keys") + (I2P_CLIENT_TUNNEL_NAME, boost::program_options::value >(), "tunnel name") + (I2P_CLIENT_TUNNEL_PORT, boost::program_options::value >(), "Local port") + (I2P_CLIENT_TUNNEL_DESTINATION, boost::program_options::value >(), "destination") + (I2P_CLIENT_TUNNEL_KEYS, boost::program_options::value >(), "keys") ; @@ -276,10 +276,10 @@ namespace client int numClientTunnels = vm.count (I2P_CLIENT_TUNNEL_NAME); if (numClientTunnels > 0) { - // auto& names = vm[I2P_CLIENT_TUNNEL_NAME].as >(); - auto& ports = vm[I2P_CLIENT_TUNNEL_PORT].as >(); - auto& destinations = vm[I2P_CLIENT_TUNNEL_DESTINATION].as >(); - auto& keys = vm[I2P_CLIENT_TUNNEL_KEYS].as >(); + //auto names = vm[I2P_CLIENT_TUNNEL_NAME].as >(); + auto ports = vm[I2P_CLIENT_TUNNEL_PORT].as >(); + auto destinations = vm[I2P_CLIENT_TUNNEL_DESTINATION].as >(); + auto keys = vm[I2P_CLIENT_TUNNEL_KEYS].as >(); for (int i = 0; i < numClientTunnels; i++) { From ed692ffba3767c9f2701350af2cf021867e40d3b Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 13 Feb 2015 10:18:42 -0500 Subject: [PATCH 0186/6300] make sure only one I2P tunnel per local prot --- ClientContext.cpp | 15 +++++++++------ ClientContext.h | 4 ++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 70493c14..2764722e 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -54,9 +54,10 @@ namespace client std::string ircKeys = i2p::util::config::GetArg("-irckeys", ""); if (ircKeys.length () > 0) localDestination = LoadLocalDestination (ircKeys, false); - auto ircTunnel = new I2PClientTunnel (ircDestination, i2p::util::config::GetArg("-ircport", 6668), localDestination); + auto ircPort = i2p::util::config::GetArg("-ircport", 6668); + auto ircTunnel = new I2PClientTunnel (ircDestination, ircPort, localDestination); ircTunnel->Start (); - m_ClientTunnels.push_back (std::unique_ptr(ircTunnel)); + m_ClientTunnels.insert (std::make_pair(ircPort, std::unique_ptr(ircTunnel))); LogPrint("IRC tunnel started"); } std::string eepKeys = i2p::util::config::GetArg("-eepkeys", ""); @@ -112,8 +113,8 @@ namespace client LogPrint("SOCKS Proxy stopped"); for (auto& it: m_ClientTunnels) { - it->Stop (); - LogPrint("I2P client tunnel stopped"); + it.second->Stop (); + LogPrint("I2P client tunnel on port ", it.first, " stopped"); } m_ClientTunnels.clear (); if (m_ServerTunnel) @@ -287,8 +288,10 @@ namespace client if (keys[i].length () > 0) localDestination = LoadLocalDestination (keys[i], false); auto clientTunnel = new I2PClientTunnel (destinations[i], ports[i], localDestination); - clientTunnel->Start (); - m_ClientTunnels.push_back (std::unique_ptr(clientTunnel)); + if (m_ClientTunnels.insert (std::make_pair (ports[i], std::unique_ptr(clientTunnel))).second) + clientTunnel->Start (); + else + LogPrint (eLogError, "I2P client tunnel with port ", ports[i], " already exists"); } LogPrint (eLogInfo, numClientTunnels, " I2P client tunnels created"); } diff --git a/ClientContext.h b/ClientContext.h index 94a7fe0e..68a0ae64 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -1,7 +1,7 @@ #ifndef CLIENT_CONTEXT_H__ #define CLIENT_CONTEXT_H__ -#include +#include #include #include #include "Destination.h" @@ -58,7 +58,7 @@ namespace client i2p::proxy::HTTPProxy * m_HttpProxy; i2p::proxy::SOCKSProxy * m_SocksProxy; - std::list > m_ClientTunnels; + std::map > m_ClientTunnels; // port->tunnel I2PServerTunnel * m_ServerTunnel; SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; From b0052eae056735d440172ff282046a5c1f5c6ea2 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 13 Feb 2015 14:55:48 -0500 Subject: [PATCH 0187/6300] doesn't create same local destination twice --- ClientContext.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 2764722e..220aea6b 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -185,10 +185,20 @@ namespace client LogPrint ("New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " created"); } - auto localDestination = new ClientDestination (keys, isPublic); + ClientDestination * localDestination = nullptr; std::unique_lock l(m_DestinationsMutex); - m_Destinations[localDestination->GetIdentHash ()] = localDestination; - localDestination->Start (); + auto it = m_Destinations.find (keys.GetPublic ().GetIdentHash ()); + if (it != m_Destinations.end ()) + { + LogPrint (eLogWarning, "Local destination ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " alreday exists"); + localDestination = it->second; + } + else + { + localDestination = new ClientDestination (keys, isPublic); + m_Destinations[localDestination->GetIdentHash ()] = localDestination; + localDestination->Start (); + } return localDestination; } From d2d7b3b348c2bd04afda46d3b7c52763f923e2a4 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 13 Feb 2015 15:56:57 -0500 Subject: [PATCH 0188/6300] multiple server I2P tunnels --- ClientContext.cpp | 47 +++++++++++++++++++++++++++++++++++------------ ClientContext.h | 6 +++++- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 220aea6b..be1d7051 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -14,8 +14,8 @@ namespace client ClientContext context; ClientContext::ClientContext (): m_SharedLocalDestination (nullptr), - m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_ServerTunnel (nullptr), - m_SamBridge (nullptr), m_BOBCommandChannel (nullptr), m_I2PControlService (nullptr) + m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr), + m_BOBCommandChannel (nullptr), m_I2PControlService (nullptr) { } @@ -23,7 +23,6 @@ namespace client { delete m_HttpProxy; delete m_SocksProxy; - delete m_ServerTunnel; delete m_SamBridge; delete m_BOBCommandChannel; delete m_I2PControlService; @@ -64,9 +63,10 @@ namespace client if (eepKeys.length () > 0) // eepkeys file is presented { auto localDestination = LoadLocalDestination (eepKeys, true); - m_ServerTunnel = new I2PServerTunnel (i2p::util::config::GetArg("-eephost", "127.0.0.1"), + auto serverTunnel = new I2PServerTunnel (i2p::util::config::GetArg("-eephost", "127.0.0.1"), i2p::util::config::GetArg("-eepport", 80), localDestination); - m_ServerTunnel->Start (); + serverTunnel->Start (); + m_ServerTunnels.insert (std::make_pair(localDestination->GetIdentHash (), std::unique_ptr(serverTunnel))); LogPrint("Server tunnel started"); } ReadTunnels (); @@ -116,14 +116,13 @@ namespace client it.second->Stop (); LogPrint("I2P client tunnel on port ", it.first, " stopped"); } - m_ClientTunnels.clear (); - if (m_ServerTunnel) + m_ClientTunnels.clear (); + for (auto& it: m_ServerTunnels) { - m_ServerTunnel->Stop (); - delete m_ServerTunnel; - m_ServerTunnel = nullptr; - LogPrint("Server tunnel stopped"); - } + it.second->Stop (); + LogPrint("I2P server tunnel stopped"); + } + m_ServerTunnels.clear (); if (m_SamBridge) { m_SamBridge->Stop (); @@ -265,10 +264,16 @@ namespace client { boost::program_options::options_description params ("I2P tunnels parameters"); params.add_options () + // client (I2P_CLIENT_TUNNEL_NAME, boost::program_options::value >(), "tunnel name") (I2P_CLIENT_TUNNEL_PORT, boost::program_options::value >(), "Local port") (I2P_CLIENT_TUNNEL_DESTINATION, boost::program_options::value >(), "destination") (I2P_CLIENT_TUNNEL_KEYS, boost::program_options::value >(), "keys") + // server + (I2P_SERVER_TUNNEL_NAME, boost::program_options::value >(), "tunnel name") + (I2P_SERVER_TUNNEL_HOST, boost::program_options::value >(), "host") + (I2P_SERVER_TUNNEL_PORT, boost::program_options::value >(), "port") + (I2P_SERVER_TUNNEL_KEYS, boost::program_options::value >(), "keys") ; @@ -305,6 +310,24 @@ namespace client } LogPrint (eLogInfo, numClientTunnels, " I2P client tunnels created"); } + + int numServerTunnels = vm.count (I2P_SERVER_TUNNEL_NAME); + if (numServerTunnels > 0) + { + auto hosts = vm[I2P_SERVER_TUNNEL_HOST].as >(); + auto ports = vm[I2P_SERVER_TUNNEL_PORT].as >(); + auto keys = vm[I2P_SERVER_TUNNEL_KEYS].as >(); + for (int i = 0; i < numServerTunnels; i++) + { + auto localDestination = LoadLocalDestination (keys[i], true); + auto serverTunnel = new I2PServerTunnel (hosts[i], ports[i], localDestination); + if (m_ServerTunnels.insert (std::make_pair (localDestination->GetIdentHash (), std::unique_ptr(serverTunnel))).second) + serverTunnel->Start (); + else + LogPrint (eLogError, "I2P server tunnel for destination ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), " already exists"); + } + LogPrint (eLogInfo, numServerTunnels, " I2P server tunnels created"); + } } } } diff --git a/ClientContext.h b/ClientContext.h index 68a0ae64..0cffcf33 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -21,6 +21,10 @@ namespace client const char I2P_CLIENT_TUNNEL_PORT[] = "client.port"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "client.destination"; const char I2P_CLIENT_TUNNEL_KEYS[] = "client.keys"; + const char I2P_SERVER_TUNNEL_NAME[] = "server.name"; + const char I2P_SERVER_TUNNEL_HOST[] = "server.host"; + const char I2P_SERVER_TUNNEL_PORT[] = "server.port"; + const char I2P_SERVER_TUNNEL_KEYS[] = "server.keys"; const char TUNNELS_CONFIG_FILENAME[] = "tunnels.cfg"; class ClientContext @@ -59,7 +63,7 @@ namespace client i2p::proxy::HTTPProxy * m_HttpProxy; i2p::proxy::SOCKSProxy * m_SocksProxy; std::map > m_ClientTunnels; // port->tunnel - I2PServerTunnel * m_ServerTunnel; + std::map > m_ServerTunnels; // destination->tunnel SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; I2PControlService * m_I2PControlService; From 97ec65cccdd62b9f3ff6ce244f956a0b1595b80e Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 13 Feb 2015 19:00:17 -0500 Subject: [PATCH 0189/6300] read all sections from tunnels.cfg --- ClientContext.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index be1d7051..a5bb6298 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -289,10 +289,10 @@ namespace client return; } - int numClientTunnels = vm.count (I2P_CLIENT_TUNNEL_NAME); - if (numClientTunnels > 0) + if (vm.count (I2P_CLIENT_TUNNEL_NAME) > 0) { - //auto names = vm[I2P_CLIENT_TUNNEL_NAME].as >(); + auto names = vm[I2P_CLIENT_TUNNEL_NAME].as >(); + int numClientTunnels = names.size (); auto ports = vm[I2P_CLIENT_TUNNEL_PORT].as >(); auto destinations = vm[I2P_CLIENT_TUNNEL_DESTINATION].as >(); auto keys = vm[I2P_CLIENT_TUNNEL_KEYS].as >(); @@ -311,9 +311,10 @@ namespace client LogPrint (eLogInfo, numClientTunnels, " I2P client tunnels created"); } - int numServerTunnels = vm.count (I2P_SERVER_TUNNEL_NAME); - if (numServerTunnels > 0) + if (vm.count (I2P_SERVER_TUNNEL_NAME) > 0) { + auto names = vm[I2P_SERVER_TUNNEL_NAME].as >(); + int numServerTunnels = names.size (); auto hosts = vm[I2P_SERVER_TUNNEL_HOST].as >(); auto ports = vm[I2P_SERVER_TUNNEL_PORT].as >(); auto keys = vm[I2P_SERVER_TUNNEL_KEYS].as >(); From 10b733d215865d07a73e99f47957b636aa762e71 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 14 Feb 2015 17:20:21 -0500 Subject: [PATCH 0190/6300] don't throw exception --- NTCPSession.cpp | 4 ++-- SSU.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index aab7f9cd..93f262d6 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -519,12 +519,12 @@ namespace transport // try to read more if (numReloads < 5) { - size_t moreBytes = m_Socket.available(); + boost::system::error_code ec; + size_t moreBytes = m_Socket.available(ec); if (moreBytes) { if (moreBytes > NTCP_BUFFER_SIZE - m_ReceiveBufferOffset) moreBytes = NTCP_BUFFER_SIZE - m_ReceiveBufferOffset; - boost::system::error_code ec; moreBytes = m_Socket.read_some (boost::asio::buffer (m_ReceiveBuffer + m_ReceiveBufferOffset, moreBytes)); if (ec) { diff --git a/SSU.cpp b/SSU.cpp index 51eccf83..f5c6287d 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -163,7 +163,8 @@ namespace transport std::vector packets; packets.push_back (packet); - size_t moreBytes = m_Socket.available(); + boost::system::error_code ec; + size_t moreBytes = m_Socket.available(ec); while (moreBytes && packets.size () < 25) { packet = new SSUPacket (); From 018daa8837d8c99336b9992b11635d199378400e Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 14 Feb 2015 17:23:15 -0500 Subject: [PATCH 0191/6300] catch sendto exception --- SSUData.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/SSUData.cpp b/SSUData.cpp index bfc17d31..aa5629e0 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -345,8 +345,14 @@ namespace transport // encrypt message with session key m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, size); - m_Session.Send (buf, size); - + try + { + m_Session.Send (buf, size); + } + catch (boost::system::system_error& ec) + { + LogPrint (eLogError, "Can't send SSU fragment ", ec.what ()); + } if (!isLast) { len -= payloadSize; @@ -426,7 +432,17 @@ namespace transport if (it->second->numResends < MAX_NUM_RESENDS) { for (auto& f: it->second->fragments) - if (f) m_Session.Send (f->buf, f->len); // resend + if (f) + { + try + { + m_Session.Send (f->buf, f->len); // resend + } + catch (boost::system::system_error& ec) + { + LogPrint (eLogError, "Can't resend SSU fragment ", ec.what ()); + } + } it->second->numResends++; it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL; From 776582e019b02703b46d41bdc5f4a495689cdc04 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 14 Feb 2015 21:00:40 -0500 Subject: [PATCH 0192/6300] fixed typo --- I2PControl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index be32be41..c60104b9 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -1,5 +1,5 @@ // There is bug in boost 1.49 with gcc 4.7 coming with Debian Wheezy -#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ != 4) && (__GNUC_MINOR__ != 7)) +#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ == 7)) #include "I2PControl.h" #include From e038467c89a55332d900f0e60dacfd3e5d450181 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 14 Feb 2015 22:24:10 -0500 Subject: [PATCH 0193/6300] unique_ptr for sent and incomplete messages --- SSUData.cpp | 43 ++++++++++++++++++------------------------- SSUData.h | 4 ++-- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/SSUData.cpp b/SSUData.cpp index aa5629e0..f59a172d 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -23,10 +23,6 @@ namespace transport SSUData::~SSUData () { - for (auto it: m_IncomleteMessages) - delete it.second; - for (auto it: m_SentMessages) - delete it.second; } void SSUData::Start () @@ -78,7 +74,6 @@ namespace transport auto it = m_SentMessages.find (msgID); if (it != m_SentMessages.end ()) { - delete it->second; m_SentMessages.erase (it); if (m_SentMessages.empty ()) m_ResendTimer.cancel (); @@ -161,22 +156,19 @@ namespace transport // find message with msgID I2NPMessage * msg = nullptr; - IncompleteMessage * incompleteMessage = nullptr; - auto it = m_IncomleteMessages.find (msgID); - if (it != m_IncomleteMessages.end ()) - { + auto it = m_IncompleteMessages.find (msgID); + if (it != m_IncompleteMessages.end ()) // message exists - incompleteMessage = it->second; - msg = incompleteMessage->msg; - } + msg = it->second->msg; else { // create new message msg = NewI2NPMessage (); msg->len -= I2NP_SHORT_HEADER_SIZE; - incompleteMessage = new IncompleteMessage (msg); - m_IncomleteMessages[msgID] = incompleteMessage; - } + it = m_IncompleteMessages.insert (std::make_pair (msgID, + std::unique_ptr(new IncompleteMessage (msg)))).first; + } + std::unique_ptr& incompleteMessage = it->second; // handle current fragment if (fragmentNum == incompleteMessage->nextFragmentNum) @@ -228,8 +220,7 @@ namespace transport { // delete incomplete message incompleteMessage->msg = nullptr; - delete incompleteMessage; - m_IncomleteMessages.erase (msgID); + m_IncompleteMessages.erase (msgID); // process message SendMsgAck (msgID); msg->FromSSU (msgID); @@ -303,10 +294,14 @@ namespace transport } if (m_SentMessages.empty ()) // schedule resend at first message only ScheduleResend (); - SentMessage * sentMessage = new SentMessage; - m_SentMessages[msgID] = sentMessage; - sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL; - sentMessage->numResends = 0; + + auto ret = m_SentMessages.insert (std::make_pair (msgID, std::unique_ptr(new SentMessage))); + std::unique_ptr& sentMessage = ret.first->second; + if (ret.second) + { + sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL; + sentMessage->numResends = 0; + } auto& fragments = sentMessage->fragments; size_t payloadSize = m_PacketSize - sizeof (SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3) size_t len = msg->GetLength (); @@ -451,7 +446,6 @@ namespace transport else { LogPrint (eLogError, "SSU message has not been ACKed after ", MAX_NUM_RESENDS, " attempts. Deleted"); - delete it->second; it = m_SentMessages.erase (it); } } @@ -491,13 +485,12 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_IncomleteMessages.begin (); it != m_IncomleteMessages.end ();) + for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) { if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) { LogPrint (eLogError, "SSU message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds. Deleted"); - delete it->second; - it = m_IncomleteMessages.erase (it); + it = m_IncompleteMessages.erase (it); } else it++; diff --git a/SSUData.h b/SSUData.h index f56c81d2..b7d1a9e2 100644 --- a/SSUData.h +++ b/SSUData.h @@ -113,8 +113,8 @@ namespace transport private: SSUSession& m_Session; - std::map m_IncomleteMessages; - std::map m_SentMessages; + std::map > m_IncompleteMessages; + std::map > m_SentMessages; std::set m_ReceivedMessages; boost::asio::deadline_timer m_ResendTimer, m_DecayTimer, m_IncompleteMessagesCleanupTimer; int m_MaxPacketSize, m_PacketSize; From e901307d8da322c41b5617dbe0e7e551d7c66fbd Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 15 Feb 2015 10:23:06 -0500 Subject: [PATCH 0194/6300] fixed crash --- Transports.cpp | 11 +++++------ Transports.h | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 1340d8f5..47839a1c 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -342,13 +342,13 @@ namespace transport } void Transports::HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, - const i2p::data::IdentHash& ident, std::shared_ptr resolver) + i2p::data::IdentHash ident, std::shared_ptr resolver) { auto it1 = m_Peers.find (ident); - if (it1 != m_Peers.end () && it1->second.router) + if (it1 != m_Peers.end ()) { auto& peer = it1->second; - if (!ecode) + if (!ecode && peer.router) { auto address = (*it).endpoint ().address (); LogPrint (eLogInfo, (*it).host_name (), " has been resolved to ", address); @@ -360,10 +360,9 @@ namespace transport return; } } + LogPrint (eLogError, "Unable to resolve NTCP address: ", ecode.message ()); + m_Peers.erase (it1); } - - LogPrint (eLogError, "Unable to resolve NTCP address: ", ecode.message ()); - m_Peers.erase (it1); } void Transports::CloseSession (std::shared_ptr router) diff --git a/Transports.h b/Transports.h index e1c85374..208eeece 100644 --- a/Transports.h +++ b/Transports.h @@ -101,7 +101,7 @@ namespace transport void NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident); void HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, - const i2p::data::IdentHash& ident, std::shared_ptr resolver); + i2p::data::IdentHash ident, std::shared_ptr resolver); void DetectExternalIP (); From f2c849703a69aed7e741baf02c080bcdb779017b Mon Sep 17 00:00:00 2001 From: Kill Your TV Date: Sun, 15 Feb 2015 19:17:41 +0000 Subject: [PATCH 0195/6300] Remove reseed host, no longer in use --- Reseed.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index ec43c10e..219d18e7 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -42,8 +42,7 @@ namespace data "https://i2p.mooo.com/netDb/", "https://link.mx24.eu/", // Only HTTPS and SU3 (v2) support "https://i2pseed.zarrenspry.info/", // Only HTTPS and SU3 (v2) support - "https://ieb9oopo.mooo.com/", // Only HTTPS and SU3 (v2) support - "https://ssl.webpack.de/ivae2he9.sg4.e-plaza.de/" // Only HTTPS and SU3 (v2) support + "https://ieb9oopo.mooo.com/" // Only HTTPS and SU3 (v2) support }; //TODO: Implement v2 reseeding. Lightweight zip library is needed. From e3190a4ca9c931a65b6286211fda0ff23c8dc22a Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 15 Feb 2015 14:17:55 -0500 Subject: [PATCH 0196/6300] SSU data receive batching --- SSU.cpp | 2 ++ SSUData.cpp | 7 +++++-- SSUData.h | 1 + SSUSession.cpp | 11 ++++++++++- SSUSession.h | 3 +++ 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index f5c6287d..20c86a81 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -218,6 +218,7 @@ namespace transport auto packet = it1; if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous { + if (session) session->FlushData (); auto it = m_Sessions.find (packet->from); if (it != m_Sessions.end ()) session = it->second; @@ -235,6 +236,7 @@ namespace transport session->ProcessNextMessage (packet->buf, packet->len, packet->from); delete packet; } + if (session) session->FlushData (); } std::shared_ptr SSUServer::FindSession (std::shared_ptr router) const diff --git a/SSUData.cpp b/SSUData.cpp index f59a172d..c34fb690 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -258,10 +258,13 @@ namespace transport SendFragmentAck (msgID, fragmentNum); buf += fragmentSize; } - if (numFragments > 0) - m_Handler.Flush (); } + void SSUData::FlushReceivedMessage () + { + m_Handler.Flush (); + } + void SSUData::ProcessMessage (uint8_t * buf, size_t len) { //uint8_t * start = buf; diff --git a/SSUData.h b/SSUData.h index b7d1a9e2..8419f501 100644 --- a/SSUData.h +++ b/SSUData.h @@ -87,6 +87,7 @@ namespace transport void Stop (); void ProcessMessage (uint8_t * buf, size_t len); + void FlushReceivedMessage (); void Send (i2p::I2NPMessage * msg); void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent); diff --git a/SSUSession.cpp b/SSUSession.cpp index 7b81903c..a6fc2b52 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -17,7 +17,7 @@ namespace transport std::shared_ptr router, bool peerTest ): TransportSession (router), m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_Timer (GetService ()), m_PeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), m_RelayTag (0), - m_NumSentBytes (0), m_NumReceivedBytes (0), m_Data (*this) + m_NumSentBytes (0), m_NumReceivedBytes (0), m_Data (*this), m_IsDataReceived (false) { m_CreationTime = i2p::util::GetSecondsSinceEpoch (); } @@ -866,8 +866,17 @@ namespace transport void SSUSession::ProcessData (uint8_t * buf, size_t len) { m_Data.ProcessMessage (buf, len); + m_IsDataReceived = true; } + void SSUSession::FlushData () + { + if (m_IsDataReceived) + { + m_Data.FlushReceivedMessage (); + m_IsDataReceived = false; + } + } void SSUSession::ProcessPeerTest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { diff --git a/SSUSession.h b/SSUSession.h index 1a2690f1..e8ca5a7f 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -79,6 +79,8 @@ namespace transport uint32_t GetRelayTag () const { return m_RelayTag; }; uint32_t GetCreationTime () const { return m_CreationTime; }; + void FlushData (); + private: boost::asio::io_service& GetService (); @@ -139,6 +141,7 @@ namespace transport size_t m_NumSentBytes, m_NumReceivedBytes; uint32_t m_CreationTime; // seconds since epoch SSUData m_Data; + bool m_IsDataReceived; }; From 732ff6707685056156c162027a58598cb720d806 Mon Sep 17 00:00:00 2001 From: Kill Your TV Date: Sun, 15 Feb 2015 19:19:09 +0000 Subject: [PATCH 0197/6300] New certificate; will be switched on the server for a future release. --- contrib/certificates/ssl/i2p.mooo.com2.crt | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 contrib/certificates/ssl/i2p.mooo.com2.crt diff --git a/contrib/certificates/ssl/i2p.mooo.com2.crt b/contrib/certificates/ssl/i2p.mooo.com2.crt new file mode 100644 index 00000000..839767b3 --- /dev/null +++ b/contrib/certificates/ssl/i2p.mooo.com2.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDvTCCAqWgAwIBAgIJAOeW0ejPrHimMA0GCSqGSIb3DQEBCwUAMHUxCzAJBgNV +BAYTAlVTMQ0wCwYDVQQIDARub25lMQ0wCwYDVQQHDARub25lMQ0wCwYDVQQKDARu +b25lMQ0wCwYDVQQLDARub25lMRUwEwYDVQQDDAxpMnAubW9vby5jb20xEzARBgkq +hkiG9w0BCQEWBG5vbmUwHhcNMTUwMjA4MTczMzA5WhcNMTkwMzE5MTczMzA5WjB1 +MQswCQYDVQQGEwJVUzENMAsGA1UECAwEbm9uZTENMAsGA1UEBwwEbm9uZTENMAsG +A1UECgwEbm9uZTENMAsGA1UECwwEbm9uZTEVMBMGA1UEAwwMaTJwLm1vb28uY29t +MRMwEQYJKoZIhvcNAQkBFgRub25lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAqxej7oRl9GOb8benIBCENrJXoow1iWhI9M+2nU0SaonrCDql5M2YMlwd +HzYUWtFbRjz2NinjB0fgFq9cfzHfr1Sc8k/OeGg1jvNfqt8wWo9tryQNjiHtDQUZ +6lQ5T13I+lj0CBasowgbApKQfrYjvaeuTaVYTfP8IVA60hoUQ+sy9JN+Unsx3/0Y +PLLd98+bT27qYuBNRB1g/ifUTd9Wosj2PevGBlCxYDaUjmCG4Q8kcQr87KvM6RTu +3AV61s/Wyy1j2YemlGG/ZhJ44YnlVMSu1vTjt9HInVf3lRRx/+RzbQO3lqeVC8LC +Bq3KbSlfJVx4vHslfHwBFw9A4rmD1QIDAQABo1AwTjAdBgNVHQ4EFgQUsSUvX0ED +yivB67iksVwZ+b8vLtQwHwYDVR0jBBgwFoAUsSUvX0EDyivB67iksVwZ+b8vLtQw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAde4wts7Q8TylFEc38ftJ +2f285fFIR7P1SSbBcHPK2eBwLEg0zJyFrCeiHuEpPrn+d5GqL2zOskjfcESGmDBT +aFajj8jPBJj/AmpkdWJG6a1YKro5tu9wrlenGwHOHu2/Cl0IJvafxrOs2x4G+2Nl +5Hcw/FIy8mK7eIch4pACfi0zNMZ6KMCKfX9bxPrQo78WdBfVjbrIBlgyOQJ5NJEF +JlWvS7Butv7eERi4I2huN5VRJSCFzjbuO+tjP3I8IB6WgdBmTeqq8ObtXRgahBuD +ZmkvqVSfIzK5JN4GjO8FOdCBomuwm9A92kgmAptwQwAHM9qCDJpH8L07/7poxlGb +iA== +-----END CERTIFICATE----- From c5f0be126e3d795cf7e520bf3944f75998b0555e Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 15 Feb 2015 23:03:04 -0500 Subject: [PATCH 0198/6300] client hello for HTTPS --- Reseed.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ Reseed.h | 6 ++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 219d18e7..de854fab 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -491,6 +491,53 @@ namespace data } LogPrint (eLogInfo, numCertificates, " certificates loaded"); } + + std::string Reseeder::HttpsRequest (const std::string& address) + { + static uint8_t clientHello[] = + { + 0x16, // handshake + 0x03, 0x02, // version (TSL 1.2) + 0x00, 0x2F, // length of handshake + // handshake + 0x01, // client hello + 0x00, 0x00, 0x2B, // length of client hello + // client hello + 0x03, 0x02, // highest version supported (TSL 1.2) + 0x01, 0x01, 0x01, 0x01, // date, can be anything + 0x74, 0x55, 0x18, 0x36, 0x42, 0x05, 0xC1, 0xDD, 0x4A, 0x21, 0x80, 0x80, 0xEC, 0x37, + 0x11, 0x93, 0x16, 0xF4, 0x66, 0x00, 0x12, 0x67, 0xAB, 0xBA, 0xFF, 0x29, 0x13, 0x9E, // 28 random bytes + 0x00, // session id length + 0x00, 0x04, // chiper suites length + 0x00, 0x00, // NULL_WITH_NULL_NULL + 0x00, 0x35, // RSA_WITH_AES_256_CBC_SHA + 0x01, // compression methods length + 0x00 // no complression + }; + + i2p::util::http::url u(address); + boost::asio::ip::tcp::iostream site; + site.connect(u.host_, "443"); + if (site.good ()) + { + // send ClientHello + site.write ((char *)clientHello, sizeof (clientHello)); + // read ServerHello + uint8_t type; + site.read ((char *)&type, 1); + uint16_t version; + site.read ((char *)&version, 2); + uint16_t length; + site.read ((char *)&length, 2); + length = be16toh (length); + char * serverHello = new char[length]; + site.read (serverHello, length); + delete[] serverHello; + } + else + LogPrint (eLogError, "Can't connect to ", address); + return ""; + } } } diff --git a/Reseed.h b/Reseed.h index 3857d057..c2f32c5b 100644 --- a/Reseed.h +++ b/Reseed.h @@ -24,7 +24,9 @@ namespace data int ReseedNowSU3 (); void LoadCertificates (); - + + std::string HttpsRequest (const std::string& address); // TODO: move to private section + private: void LoadCertificate (const std::string& filename); @@ -34,7 +36,7 @@ namespace data int ProcessSU3Stream (std::istream& s); bool FindZipDataDescriptor (std::istream& s); - + private: std::map m_SigningKeys; From 6ea037cc4782dc58fa161bffefd13ba182f22f79 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 16 Feb 2015 11:21:33 -0500 Subject: [PATCH 0199/6300] process server certificate --- Reseed.cpp | 156 +++++++++++++++++++++++++++++++---------------------- Reseed.h | 1 + 2 files changed, 92 insertions(+), 65 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index de854fab..d074396b 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -400,75 +400,80 @@ namespace data decoder.Attach (new CryptoPP::Redirector (queue)); decoder.Put ((const uint8_t *)base64.data(), base64.length()); decoder.MessageEnd (); - - // extract X.509 - CryptoPP::BERSequenceDecoder x509Cert (queue); - CryptoPP::BERSequenceDecoder tbsCert (x509Cert); - // version - uint32_t ver; - CryptoPP::BERGeneralDecoder context (tbsCert, CryptoPP::CONTEXT_SPECIFIC | CryptoPP::CONSTRUCTED); - CryptoPP::BERDecodeUnsigned(context, ver, CryptoPP::INTEGER); - // serial - CryptoPP::Integer serial; - serial.BERDecode(tbsCert); - // signature - CryptoPP::BERSequenceDecoder signature (tbsCert); - signature.SkipAll(); - // issuer - std::string name; - CryptoPP::BERSequenceDecoder issuer (tbsCert); - { - CryptoPP::BERSetDecoder c (issuer); c.SkipAll(); - CryptoPP::BERSetDecoder st (issuer); st.SkipAll(); - CryptoPP::BERSetDecoder l (issuer); l.SkipAll(); - CryptoPP::BERSetDecoder o (issuer); o.SkipAll(); - CryptoPP::BERSetDecoder ou (issuer); ou.SkipAll(); - CryptoPP::BERSetDecoder cn (issuer); - { - CryptoPP::BERSequenceDecoder attributes (cn); - { - CryptoPP::BERGeneralDecoder ident(attributes, CryptoPP::OBJECT_IDENTIFIER); - ident.SkipAll (); - CryptoPP::BERDecodeTextString (attributes, name, CryptoPP::UTF8_STRING); - } - } - } - issuer.SkipAll(); - // validity - CryptoPP::BERSequenceDecoder validity (tbsCert); - validity.SkipAll(); - // subject - CryptoPP::BERSequenceDecoder subject (tbsCert); - subject.SkipAll(); - // public key - CryptoPP::BERSequenceDecoder publicKey (tbsCert); - { - CryptoPP::BERSequenceDecoder ident (publicKey); - ident.SkipAll (); - CryptoPP::BERGeneralDecoder key (publicKey, CryptoPP::BIT_STRING); - key.Skip (1); // FIXME: probably bug in crypto++ - CryptoPP::BERSequenceDecoder keyPair (key); - CryptoPP::Integer n; - n.BERDecode (keyPair); - if (name.length () > 0) - { - PublicKey value; - n.Encode (value, 512); - m_SigningKeys[name] = value; - } - else - LogPrint (eLogWarning, "Unknown issuer. Skipped"); - } - publicKey.SkipAll(); - - tbsCert.SkipAll(); - x509Cert.SkipAll(); + LoadCertificate (queue); } else LogPrint (eLogError, "Can't open certificate file ", filename); } + void Reseeder::LoadCertificate (CryptoPP::ByteQueue& queue) + { + // extract X.509 + CryptoPP::BERSequenceDecoder x509Cert (queue); + CryptoPP::BERSequenceDecoder tbsCert (x509Cert); + // version + uint32_t ver; + CryptoPP::BERGeneralDecoder context (tbsCert, CryptoPP::CONTEXT_SPECIFIC | CryptoPP::CONSTRUCTED); + CryptoPP::BERDecodeUnsigned(context, ver, CryptoPP::INTEGER); + // serial + CryptoPP::Integer serial; + serial.BERDecode(tbsCert); + // signature + CryptoPP::BERSequenceDecoder signature (tbsCert); + signature.SkipAll(); + + // issuer + std::string name; + CryptoPP::BERSequenceDecoder issuer (tbsCert); + { + CryptoPP::BERSetDecoder c (issuer); c.SkipAll(); + CryptoPP::BERSetDecoder st (issuer); st.SkipAll(); + CryptoPP::BERSetDecoder l (issuer); l.SkipAll(); + CryptoPP::BERSetDecoder o (issuer); o.SkipAll(); + CryptoPP::BERSetDecoder ou (issuer); ou.SkipAll(); + CryptoPP::BERSetDecoder cn (issuer); + { + CryptoPP::BERSequenceDecoder attributes (cn); + { + CryptoPP::BERGeneralDecoder ident(attributes, CryptoPP::OBJECT_IDENTIFIER); + ident.SkipAll (); + CryptoPP::BERDecodeTextString (attributes, name, CryptoPP::UTF8_STRING); + } + } + } + issuer.SkipAll(); + // validity + CryptoPP::BERSequenceDecoder validity (tbsCert); + validity.SkipAll(); + // subject + CryptoPP::BERSequenceDecoder subject (tbsCert); + subject.SkipAll(); + // public key + CryptoPP::BERSequenceDecoder publicKey (tbsCert); + { + CryptoPP::BERSequenceDecoder ident (publicKey); + ident.SkipAll (); + CryptoPP::BERGeneralDecoder key (publicKey, CryptoPP::BIT_STRING); + key.Skip (1); // FIXME: probably bug in crypto++ + CryptoPP::BERSequenceDecoder keyPair (key); + CryptoPP::Integer n; + n.BERDecode (keyPair); + if (name.length () > 0) + { + PublicKey value; + n.Encode (value, 512); + m_SigningKeys[name] = value; + } + else + LogPrint (eLogWarning, "Unknown issuer. Skipped"); + } + publicKey.SkipAll(); + + tbsCert.SkipAll(); + x509Cert.SkipAll(); + } + void Reseeder::LoadCertificates () { boost::filesystem::path reseedDir = i2p::util::filesystem::GetCertificatesDir() / "reseed"; @@ -500,8 +505,8 @@ namespace data 0x03, 0x02, // version (TSL 1.2) 0x00, 0x2F, // length of handshake // handshake - 0x01, // client hello - 0x00, 0x00, 0x2B, // length of client hello + 0x01, // handshake type (client hello) + 0x00, 0x00, 0x2B, // length of handshake payload // client hello 0x03, 0x02, // highest version supported (TSL 1.2) 0x01, 0x01, 0x01, 0x01, // date, can be anything @@ -533,6 +538,27 @@ namespace data char * serverHello = new char[length]; site.read (serverHello, length); delete[] serverHello; + // read Certificate + site.read ((char *)&type, 1); + site.read ((char *)&version, 2); + site.read ((char *)&length, 2); + length = be16toh (length); + char * certificate = new char[length]; + site.read (certificate, length); + // 0 - handshake type + // 1 - 3 - handshake payload length + // 4 - 6 - length of array of certificates + // 7 - 9 - length of certificate + if (certificate[0] == 0x0B) // handshake type certificate + { + CryptoPP::ByteQueue queue; + queue.Put ((uint8_t *)certificate + 10, length - 10); + queue.MessageEnd (); + LoadCertificate (queue); + } + else + LogPrint (eLogError, "Unexpected handshake type ", (int)certificate[0]); + delete[] certificate; } else LogPrint (eLogError, "Can't connect to ", address); diff --git a/Reseed.h b/Reseed.h index c2f32c5b..5db9ccb6 100644 --- a/Reseed.h +++ b/Reseed.h @@ -30,6 +30,7 @@ namespace data private: void LoadCertificate (const std::string& filename); + void LoadCertificate (CryptoPP::ByteQueue& queue); int ReseedFromSU3 (const std::string& host); int ProcessSU3File (const char * filename); From 9968485cdd73a3dedb3dab2c64e92c908b97cebc Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 16 Feb 2015 21:28:37 -0500 Subject: [PATCH 0200/6300] send ClientKeyExchange --- Reseed.cpp | 52 +++++++++++++++++++++++++++++++++++++++++++++++++--- Reseed.h | 2 +- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index d074396b..4e9043ad 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -407,7 +407,7 @@ namespace data LogPrint (eLogError, "Can't open certificate file ", filename); } - void Reseeder::LoadCertificate (CryptoPP::ByteQueue& queue) + std::string Reseeder::LoadCertificate (CryptoPP::ByteQueue& queue) { // extract X.509 CryptoPP::BERSequenceDecoder x509Cert (queue); @@ -472,6 +472,7 @@ namespace data tbsCert.SkipAll(); x509Cert.SkipAll(); + return name; } void Reseeder::LoadCertificates () @@ -517,7 +518,19 @@ namespace data 0x00, 0x00, // NULL_WITH_NULL_NULL 0x00, 0x35, // RSA_WITH_AES_256_CBC_SHA 0x01, // compression methods length - 0x00 // no complression + 0x00 // no compression + }; + + static uint8_t clientKeyExchange[] = + { + 0x16, // handshake + 0x03, 0x02, // version (TSL 1.2) + 0x02, 0x04, // length of handshake + // handshake + 0x10, // handshake type (client key exchange) + 0x00, 0x02, 0x00, // length of handshake payload + // client key exchange RSA + // 512 RSA encrypted 48 bytes ( 2 bytes version + 46 random bytes) }; i2p::util::http::url u(address); @@ -537,6 +550,11 @@ namespace data length = be16toh (length); char * serverHello = new char[length]; site.read (serverHello, length); + uint8_t serverRandom[32]; + if (serverHello[0] == 0x02) // handshake type server hello + memcpy (serverRandom, serverHello + 6, 32); + else + LogPrint (eLogError, "Unexpected handshake type ", (int)serverHello[0]); delete[] serverHello; // read Certificate site.read ((char *)&type, 1); @@ -545,6 +563,7 @@ namespace data length = be16toh (length); char * certificate = new char[length]; site.read (certificate, length); + CryptoPP::RSA::PublicKey publicKey; // 0 - handshake type // 1 - 3 - handshake payload length // 4 - 6 - length of array of certificates @@ -554,11 +573,38 @@ namespace data CryptoPP::ByteQueue queue; queue.Put ((uint8_t *)certificate + 10, length - 10); queue.MessageEnd (); - LoadCertificate (queue); + auto issuer = LoadCertificate (queue); + auto it = m_SigningKeys.find (issuer); + if (it != m_SigningKeys.end ()) + publicKey.Initialize (CryptoPP::Integer (it->second, 512), CryptoPP::Integer (i2p::crypto::rsae)); + else + LogPrint (eLogError, "Can't find public key for ", issuer); } else LogPrint (eLogError, "Unexpected handshake type ", (int)certificate[0]); delete[] certificate; + // read ServerHelloDone + site.read ((char *)&type, 1); + site.read ((char *)&version, 2); + site.read ((char *)&length, 2); + length = be16toh (length); + char * serverHelloDone = new char[length]; + site.read (serverHelloDone, length); + if (serverHelloDone[0] != 0x0E) // handshake type hello done + LogPrint (eLogError, "Unexpected handshake type ", (int)serverHelloDone[0]); + delete[] serverHelloDone; + // our turn now + // generate secret key + CryptoPP::AutoSeededRandomPool rnd; + CryptoPP::RSAES_PKCS1v15_Encryptor encryptor(publicKey); + // encryptor.CiphertextLength (48); + uint8_t secret[48], encrypted[512]; + secret[0] = clientKeyExchange[1]; secret[1] = clientKeyExchange[2]; // version + rnd.GenerateBlock (secret + 2, 46); // 46 random bytes + encryptor.Encrypt (rnd, secret, 48, encrypted); + // send ClientKeyExchange + site.write ((char *)clientKeyExchange, sizeof (clientKeyExchange)); + site.write ((char *)encrypted, 512); } else LogPrint (eLogError, "Can't connect to ", address); diff --git a/Reseed.h b/Reseed.h index 5db9ccb6..7ae2f069 100644 --- a/Reseed.h +++ b/Reseed.h @@ -30,7 +30,7 @@ namespace data private: void LoadCertificate (const std::string& filename); - void LoadCertificate (CryptoPP::ByteQueue& queue); + std::string LoadCertificate (CryptoPP::ByteQueue& queue); // returns issuer's name int ReseedFromSU3 (const std::string& host); int ProcessSU3File (const char * filename); From 47e8cfd91ebcd0151b4f9f2368cf4769fcad624b Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 17 Feb 2015 15:34:30 -0500 Subject: [PATCH 0201/6300] calculate master secret --- Reseed.cpp | 29 +++++++++++++++++++++++++++++ Reseed.h | 3 +++ 2 files changed, 32 insertions(+) diff --git a/Reseed.cpp b/Reseed.cpp index 4e9043ad..2fd7cbe3 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -605,12 +606,40 @@ namespace data // send ClientKeyExchange site.write ((char *)clientKeyExchange, sizeof (clientKeyExchange)); site.write ((char *)encrypted, 512); + uint8_t masterSecret[48], random[64]; + memcpy (random, clientHello + 11, 32); + memcpy (random + 32, serverRandom, 32); + PRF (secret, "master secret", random, 48, masterSecret); } else LogPrint (eLogError, "Can't connect to ", address); return ""; } + void Reseeder::PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t len, uint8_t * buf) + { + // secret is assumed 48 bytes + // random is 64 bytes + // output is 48 bytes (buffer size should be 64) + CryptoPP::HMAC hmac (secret, 48); + uint8_t seed[96]; size_t seedLen; + seedLen = strlen (label); + memcpy (seed, label, seedLen); + memcpy (seed + seedLen, random, 64); + seedLen += 64; + + size_t offset = 0; + uint8_t a[128]; + hmac.CalculateDigest (a, seed, seedLen); + while (offset < len) + { + memcpy (a + 32, seed, seedLen); + hmac.CalculateDigest (buf + offset, a, seedLen + 32); + offset += 32; + hmac.CalculateDigest (a, a, 32); + } + } + } } diff --git a/Reseed.h b/Reseed.h index 7ae2f069..0a72ea44 100644 --- a/Reseed.h +++ b/Reseed.h @@ -38,6 +38,9 @@ namespace data bool FindZipDataDescriptor (std::istream& s); + // for HTTPS + void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t len, uint8_t * buf); + private: std::map m_SigningKeys; From fbf672288fa297f0183b762d92b99f4590420fc2 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 17 Feb 2015 16:50:37 -0500 Subject: [PATCH 0202/6300] MAC and encryption keys --- Reseed.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Reseed.cpp b/Reseed.cpp index 2fd7cbe3..b2dea6f5 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -517,7 +517,7 @@ namespace data 0x00, // session id length 0x00, 0x04, // chiper suites length 0x00, 0x00, // NULL_WITH_NULL_NULL - 0x00, 0x35, // RSA_WITH_AES_256_CBC_SHA + 0x00, 0x3D, // RSA_WITH_AES_256_CBC_SHA256 0x01, // compression methods length 0x00 // no compression }; @@ -610,6 +610,16 @@ namespace data memcpy (random, clientHello + 11, 32); memcpy (random + 32, serverRandom, 32); PRF (secret, "master secret", random, 48, masterSecret); + struct + { + uint8_t clientMACKey[32]; + uint8_t serverMACKey[32]; + uint8_t clientKey[32]; + uint8_t serverKey[32]; + } keys; + memcpy (random, serverRandom, 32); + memcpy (random + 32, clientHello + 11, 32); + PRF (masterSecret, "key expansion", random, 128, (uint8_t *)&keys); } else LogPrint (eLogError, "Can't connect to ", address); From ec50b97aa8bfcd68f841bd2763cd953aac534cec Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 17 Feb 2015 19:14:31 -0500 Subject: [PATCH 0203/6300] reduced amount of logging --- I2NPProtocol.cpp | 2 -- TransitTunnel.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 862b97e9..8326dc55 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -578,11 +578,9 @@ namespace i2p switch (msg->GetTypeID ()) { case eI2NPTunnelData: - LogPrint ("TunnelData"); m_TunnelMsgs.push_back (msg); break; case eI2NPTunnelGateway: - LogPrint ("TunnelGateway"); m_TunnelGatewayMsgs.push_back (msg); break; default: diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index 770ab246..ae11b286 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -35,7 +35,6 @@ namespace tunnel { EncryptTunnelMsg (tunnelMsg); - LogPrint (eLogDebug, "TransitTunnel: ",GetTunnelID (),"->", GetNextTunnelID ()); m_NumTransmittedBytes += tunnelMsg->GetLength (); htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); FillI2NPMessageHeader (tunnelMsg, eI2NPTunnelData); @@ -46,6 +45,7 @@ namespace tunnel { if (!m_TunnelDataMsgs.empty ()) { + LogPrint (eLogDebug, "TransitTunnel: ",GetTunnelID (),"->", GetNextTunnelID (), " ", m_TunnelDataMsgs.size ()); i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); m_TunnelDataMsgs.clear (); } From 29d118a19a887d3f76991d1fe53b8a68b8c0081b Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 17 Feb 2015 22:45:55 -0500 Subject: [PATCH 0204/6300] hanshakes hash and finishes message --- Reseed.cpp | 73 ++++++++++++++++++++++++++++++++++++++++-------------- Reseed.h | 3 ++- 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index b2dea6f5..e406181e 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -517,7 +517,7 @@ namespace data 0x00, // session id length 0x00, 0x04, // chiper suites length 0x00, 0x00, // NULL_WITH_NULL_NULL - 0x00, 0x3D, // RSA_WITH_AES_256_CBC_SHA256 + 0x00, 0x35, // RSA_WITH_AES_256_CBC_SHA 0x01, // compression methods length 0x00 // no compression }; @@ -533,24 +533,38 @@ namespace data // client key exchange RSA // 512 RSA encrypted 48 bytes ( 2 bytes version + 46 random bytes) }; + + static uint8_t finished[] = + { + 0x16, // handshake + 0x03, 0x02, // version (TSL 1.2) + 0x00, 0x10, // length of handshake + // handshake + 0x14, // handshake type (finished) + 0x00, 0x00, 0x0C, // length of handshake payload + // 12 bytes of verified data + }; i2p::util::http::url u(address); boost::asio::ip::tcp::iostream site; site.connect(u.host_, "443"); if (site.good ()) { + CryptoPP::SHA256 finishedHash; // send ClientHello site.write ((char *)clientHello, sizeof (clientHello)); + finishedHash.Update (clientHello, sizeof (clientHello)); // read ServerHello uint8_t type; - site.read ((char *)&type, 1); + site.read ((char *)&type, 1); finishedHash.Update ((uint8_t *)&type, 1); uint16_t version; - site.read ((char *)&version, 2); + site.read ((char *)&version, 2); finishedHash.Update ((uint8_t *)&version, 2); uint16_t length; - site.read ((char *)&length, 2); + site.read ((char *)&length, 2); finishedHash.Update ((uint8_t *)&length, 2); length = be16toh (length); char * serverHello = new char[length]; site.read (serverHello, length); + finishedHash.Update ((uint8_t *)serverHello, length); uint8_t serverRandom[32]; if (serverHello[0] == 0x02) // handshake type server hello memcpy (serverRandom, serverHello + 6, 32); @@ -558,12 +572,13 @@ namespace data LogPrint (eLogError, "Unexpected handshake type ", (int)serverHello[0]); delete[] serverHello; // read Certificate - site.read ((char *)&type, 1); - site.read ((char *)&version, 2); - site.read ((char *)&length, 2); + site.read ((char *)&type, 1); finishedHash.Update ((uint8_t *)&type, 1); + site.read ((char *)&version, 2); finishedHash.Update ((uint8_t *)&version, 2); + site.read ((char *)&length, 2); finishedHash.Update ((uint8_t *)&length, 2); length = be16toh (length); char * certificate = new char[length]; site.read (certificate, length); + finishedHash.Update ((uint8_t *)certificate, length); CryptoPP::RSA::PublicKey publicKey; // 0 - handshake type // 1 - 3 - handshake payload length @@ -585,12 +600,13 @@ namespace data LogPrint (eLogError, "Unexpected handshake type ", (int)certificate[0]); delete[] certificate; // read ServerHelloDone - site.read ((char *)&type, 1); - site.read ((char *)&version, 2); - site.read ((char *)&length, 2); + site.read ((char *)&type, 1); finishedHash.Update ((uint8_t *)&type, 1); + site.read ((char *)&version, 2); finishedHash.Update ((uint8_t *)&version, 2); + site.read ((char *)&length, 2); finishedHash.Update ((uint8_t *)&length, 2); length = be16toh (length); char * serverHelloDone = new char[length]; site.read (serverHelloDone, length); + finishedHash.Update ((uint8_t *)serverHelloDone, length); if (serverHelloDone[0] != 0x0E) // handshake type hello done LogPrint (eLogError, "Unexpected handshake type ", (int)serverHelloDone[0]); delete[] serverHelloDone; @@ -606,37 +622,56 @@ namespace data // send ClientKeyExchange site.write ((char *)clientKeyExchange, sizeof (clientKeyExchange)); site.write ((char *)encrypted, 512); + finishedHash.Update (clientKeyExchange, sizeof (clientKeyExchange)); + finishedHash.Update (encrypted, 512); uint8_t masterSecret[48], random[64]; memcpy (random, clientHello + 11, 32); memcpy (random + 32, serverRandom, 32); - PRF (secret, "master secret", random, 48, masterSecret); + + // calculate master secret + PRF (secret, "master secret", random, 64, 48, masterSecret); + // send finished + uint8_t finishedHashDigest[32], verifyData[32]; + finishedHash.Final (finishedHashDigest); + PRF (masterSecret, "client finished", finishedHashDigest, 32, 12, verifyData); + site.write ((char *)finished, sizeof (finished)); + site.write ((char *)finishedHashDigest, 12); + // read finished + site.read ((char *)&type, 1); + site.read ((char *)&version, 2); + site.read ((char *)&length, 2); + length = be16toh (length); + char * finished1 = new char[length]; + site.read (finished1, length); + delete[] finished1; + struct { - uint8_t clientMACKey[32]; - uint8_t serverMACKey[32]; + uint8_t clientMACKey[20]; + uint8_t serverMACKey[20]; uint8_t clientKey[32]; uint8_t serverKey[32]; } keys; memcpy (random, serverRandom, 32); memcpy (random + 32, clientHello + 11, 32); - PRF (masterSecret, "key expansion", random, 128, (uint8_t *)&keys); + PRF (masterSecret, "key expansion", random, 64, sizeof (keys), (uint8_t *)&keys); } else LogPrint (eLogError, "Can't connect to ", address); return ""; } - void Reseeder::PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t len, uint8_t * buf) + void Reseeder::PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, + size_t len, uint8_t * buf) { // secret is assumed 48 bytes - // random is 64 bytes - // output is 48 bytes (buffer size should be 64) + // random is not more than 64 bytes CryptoPP::HMAC hmac (secret, 48); uint8_t seed[96]; size_t seedLen; seedLen = strlen (label); memcpy (seed, label, seedLen); - memcpy (seed + seedLen, random, 64); - seedLen += 64; + memcpy (seed + seedLen, random, randomLen); + seedLen += randomLen; size_t offset = 0; uint8_t a[128]; diff --git a/Reseed.h b/Reseed.h index 0a72ea44..cd0a1f00 100644 --- a/Reseed.h +++ b/Reseed.h @@ -39,7 +39,8 @@ namespace data bool FindZipDataDescriptor (std::istream& s); // for HTTPS - void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t len, uint8_t * buf); + void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, + size_t len, uint8_t * buf); private: From dce8cf1af2ffe6904ccbe67cc3bbab0b748b6825 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 Feb 2015 10:28:29 -0500 Subject: [PATCH 0205/6300] tls 1.2 and RSA_WITH_AES_256_CBC_SHA256 --- Reseed.cpp | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index e406181e..e9b77c90 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -504,40 +504,42 @@ namespace data static uint8_t clientHello[] = { 0x16, // handshake - 0x03, 0x02, // version (TSL 1.2) - 0x00, 0x2F, // length of handshake + 0x03, 0x03, // version (TSL 1.2) + 0x00, 0x31, // length of handshake // handshake 0x01, // handshake type (client hello) - 0x00, 0x00, 0x2B, // length of handshake payload + 0x00, 0x00, 0x2D, // length of handshake payload // client hello - 0x03, 0x02, // highest version supported (TSL 1.2) - 0x01, 0x01, 0x01, 0x01, // date, can be anything - 0x74, 0x55, 0x18, 0x36, 0x42, 0x05, 0xC1, 0xDD, 0x4A, 0x21, 0x80, 0x80, 0xEC, 0x37, - 0x11, 0x93, 0x16, 0xF4, 0x66, 0x00, 0x12, 0x67, 0xAB, 0xBA, 0xFF, 0x29, 0x13, 0x9E, // 28 random bytes + 0x03, 0x03, // highest version supported (TSL 1.2) + 0x45, 0xFA, 0x01, 0x19, 0x74, 0x55, 0x18, 0x36, + 0x42, 0x05, 0xC1, 0xDD, 0x4A, 0x21, 0x80, 0x80, + 0xEC, 0x37, 0x11, 0x93, 0x16, 0xF4, 0x66, 0x00, + 0x12, 0x67, 0xAB, 0xBA, 0xFF, 0x29, 0x13, 0x9E, // 32 random bytes 0x00, // session id length 0x00, 0x04, // chiper suites length 0x00, 0x00, // NULL_WITH_NULL_NULL - 0x00, 0x35, // RSA_WITH_AES_256_CBC_SHA + 0x00, 0x3D, // RSA_WITH_AES_256_CBC_SHA256 0x01, // compression methods length - 0x00 // no compression + 0x00, // no compression + 0x00, 0x00 // extensions length }; static uint8_t clientKeyExchange[] = { 0x16, // handshake - 0x03, 0x02, // version (TSL 1.2) - 0x02, 0x04, // length of handshake + 0x03, 0x03, // version (TSL 1.2) + 0x01, 0x04, // length of handshake // handshake 0x10, // handshake type (client key exchange) - 0x00, 0x02, 0x00, // length of handshake payload + 0x00, 0x01, 0x00, // length of handshake payload // client key exchange RSA - // 512 RSA encrypted 48 bytes ( 2 bytes version + 46 random bytes) + // 256 RSA encrypted 48 bytes ( 2 bytes version + 46 random bytes) }; static uint8_t finished[] = { 0x16, // handshake - 0x03, 0x02, // version (TSL 1.2) + 0x03, 0x03, // version (TSL 1.2) 0x00, 0x10, // length of handshake // handshake 0x14, // handshake type (finished) @@ -615,15 +617,15 @@ namespace data CryptoPP::AutoSeededRandomPool rnd; CryptoPP::RSAES_PKCS1v15_Encryptor encryptor(publicKey); // encryptor.CiphertextLength (48); - uint8_t secret[48], encrypted[512]; + uint8_t secret[48], encrypted[256]; secret[0] = clientKeyExchange[1]; secret[1] = clientKeyExchange[2]; // version rnd.GenerateBlock (secret + 2, 46); // 46 random bytes encryptor.Encrypt (rnd, secret, 48, encrypted); // send ClientKeyExchange site.write ((char *)clientKeyExchange, sizeof (clientKeyExchange)); - site.write ((char *)encrypted, 512); + site.write ((char *)encrypted, 256); finishedHash.Update (clientKeyExchange, sizeof (clientKeyExchange)); - finishedHash.Update (encrypted, 512); + finishedHash.Update (encrypted, 256); uint8_t masterSecret[48], random[64]; memcpy (random, clientHello + 11, 32); memcpy (random + 32, serverRandom, 32); @@ -647,8 +649,8 @@ namespace data struct { - uint8_t clientMACKey[20]; - uint8_t serverMACKey[20]; + uint8_t clientMACKey[32]; + uint8_t serverMACKey[32]; uint8_t clientKey[32]; uint8_t serverKey[32]; } keys; From 68a03c213419e89f9cd0a4fdc32b3037a4fbb014 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 Feb 2015 11:44:32 -0500 Subject: [PATCH 0206/6300] send ChangeCipherSpecs --- Reseed.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Reseed.cpp b/Reseed.cpp index e9b77c90..be1bae7a 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -536,6 +536,14 @@ namespace data // 256 RSA encrypted 48 bytes ( 2 bytes version + 46 random bytes) }; + static uint8_t changeCipherSpecs[] = + { + 0x14, // change chiper specs + 0x03, 0x03, // version (TSL 1.2) + 0x00, 0x01, // length + 0x01 // type + }; + static uint8_t finished[] = { 0x16, // handshake @@ -629,6 +637,9 @@ namespace data uint8_t masterSecret[48], random[64]; memcpy (random, clientHello + 11, 32); memcpy (random + 32, serverRandom, 32); + // send ChangeCipherSpecs + site.write ((char *)changeCipherSpecs, sizeof (changeCipherSpecs)); + finishedHash.Update (changeCipherSpecs, sizeof (changeCipherSpecs)); // calculate master secret PRF (secret, "master secret", random, 64, 48, masterSecret); @@ -637,7 +648,10 @@ namespace data finishedHash.Final (finishedHashDigest); PRF (masterSecret, "client finished", finishedHashDigest, 32, 12, verifyData); site.write ((char *)finished, sizeof (finished)); - site.write ((char *)finishedHashDigest, 12); + site.write ((char *)finishedHashDigest, 12); + // read ChangeCipherSpecs + uint8_t changeCipherSpecs1[6]; + site.read ((char *)changeCipherSpecs1, 6); // read finished site.read ((char *)&type, 1); site.read ((char *)&version, 2); From a3736fc06e369fcc50e645d8c4a403cf67cfb62b Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 Feb 2015 16:52:00 -0500 Subject: [PATCH 0207/6300] tls encrypt and decrypt --- Reseed.cpp | 62 ++++++++++++++++++++++++++++++++++++------------------ Reseed.h | 11 ++++++++++ 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index be1bae7a..3c7c7dbf 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -128,8 +127,7 @@ namespace data int Reseeder::ReseedNowSU3 () { - CryptoPP::AutoSeededRandomPool rnd; - auto ind = rnd.GenerateWord32 (0, httpReseedHostList.size() - 1); + auto ind = m_Rnd.GenerateWord32 (0, httpReseedHostList.size() - 1); std::string reseedHost = httpReseedHostList[ind]; return ReseedFromSU3 (reseedHost); } @@ -528,11 +526,12 @@ namespace data { 0x16, // handshake 0x03, 0x03, // version (TSL 1.2) - 0x01, 0x04, // length of handshake + 0x01, 0x06, // length of handshake // handshake 0x10, // handshake type (client key exchange) - 0x00, 0x01, 0x00, // length of handshake payload + 0x00, 0x01, 0x02, // length of handshake payload // client key exchange RSA + 0x01, 0x00, // length of RSA encrypted // 256 RSA encrypted 48 bytes ( 2 bytes version + 46 random bytes) }; @@ -548,8 +547,8 @@ namespace data { 0x16, // handshake 0x03, 0x03, // version (TSL 1.2) - 0x00, 0x10, // length of handshake - // handshake + 0x00, 0x50, // length of handshake + // handshake (encrypted) 0x14, // handshake type (finished) 0x00, 0x00, 0x0C, // length of handshake payload // 12 bytes of verified data @@ -627,7 +626,7 @@ namespace data // encryptor.CiphertextLength (48); uint8_t secret[48], encrypted[256]; secret[0] = clientKeyExchange[1]; secret[1] = clientKeyExchange[2]; // version - rnd.GenerateBlock (secret + 2, 46); // 46 random bytes + m_Rnd.GenerateBlock (secret + 2, 46); // 46 random bytes encryptor.Encrypt (rnd, secret, 48, encrypted); // send ClientKeyExchange site.write ((char *)clientKeyExchange, sizeof (clientKeyExchange)); @@ -640,9 +639,17 @@ namespace data // send ChangeCipherSpecs site.write ((char *)changeCipherSpecs, sizeof (changeCipherSpecs)); finishedHash.Update (changeCipherSpecs, sizeof (changeCipherSpecs)); - // calculate master secret PRF (secret, "master secret", random, 64, 48, masterSecret); + // expand master secret + uint8_t keys[128]; // clientMACKey, serverMACKey, clientKey, serverKey + memcpy (random, serverRandom, 32); + memcpy (random + 32, clientHello + 11, 32); + PRF (masterSecret, "key expansion", random, 64, sizeof (keys), keys); + memcpy (m_MacKey, keys, 32); + m_Encryption.SetKey (keys + 64); + m_Decryption.SetKey (keys + 96); + // send finished uint8_t finishedHashDigest[32], verifyData[32]; finishedHash.Final (finishedHashDigest); @@ -660,17 +667,6 @@ namespace data char * finished1 = new char[length]; site.read (finished1, length); delete[] finished1; - - struct - { - uint8_t clientMACKey[32]; - uint8_t serverMACKey[32]; - uint8_t clientKey[32]; - uint8_t serverKey[32]; - } keys; - memcpy (random, serverRandom, 32); - memcpy (random + 32, clientHello + 11, 32); - PRF (masterSecret, "key expansion", random, 64, sizeof (keys), (uint8_t *)&keys); } else LogPrint (eLogError, "Can't connect to ", address); @@ -701,6 +697,32 @@ namespace data } } + size_t Reseeder::Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) + { + size_t size = 0; + m_Rnd.GenerateBlock (out, 16); // iv + size += 16; + m_Encryption.SetIV (out); + memcpy (out + size, in, len); + size += len; + memcpy (out + size, mac, 32); + size += 32; + uint8_t paddingSize = len + 1; + paddingSize &= 0x0F; // %16 + if (paddingSize > 0) paddingSize = 16 - paddingSize; + memset (out + size, paddingSize, paddingSize + 1); // paddind and last byte are equal to padding size + size += paddingSize + 1; + m_Encryption.Encrypt (out + 16, size - 16, out + 16); + return size; + } + + size_t Reseeder::Decrypt (uint8_t * in, size_t len, uint8_t * out) + { + m_Decryption.SetIV (in); + m_Decryption.Decrypt (in + 16, len - 16, in + 16); + memcpy (out, in + 16, len - 48); // skip 32 bytes mac + return len - 48 - in[len -1] - 1; + } } } diff --git a/Reseed.h b/Reseed.h index cd0a1f00..04715142 100644 --- a/Reseed.h +++ b/Reseed.h @@ -5,7 +5,9 @@ #include #include #include +#include #include "Identity.h" +#include "aes.h" namespace i2p { @@ -41,10 +43,19 @@ namespace data // for HTTPS void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, size_t len, uint8_t * buf); + size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out); + size_t Decrypt (uint8_t * in, size_t len, uint8_t * out); private: std::map m_SigningKeys; + + // for HTTPS + CryptoPP::AutoSeededRandomPool m_Rnd; + i2p::crypto::CBCEncryption m_Encryption; + i2p::crypto::CBCDecryption m_Decryption; + uint8_t m_MacKey[32]; // client + }; } } From cf5499375ea159ca629be82182efd69c0fe28717 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 Feb 2015 22:25:30 -0500 Subject: [PATCH 0208/6300] encrypt finishes message --- Reseed.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 3c7c7dbf..43af7b99 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -547,10 +547,11 @@ namespace data { 0x16, // handshake 0x03, 0x03, // version (TSL 1.2) - 0x00, 0x50, // length of handshake + 0x00, 0x50, // length of handshake (80 bytes) // handshake (encrypted) - 0x14, // handshake type (finished) - 0x00, 0x00, 0x0C, // length of handshake payload + // unencrypted context + // 0x14 handshake type (finished) + // 0x00, 0x00, 0x0C length of handshake payload // 12 bytes of verified data }; @@ -651,11 +652,14 @@ namespace data m_Decryption.SetKey (keys + 96); // send finished - uint8_t finishedHashDigest[32], verifyData[32]; + uint8_t finishedHashDigest[32], finishedPayload[40], encryptedPayload[80]; + finishedPayload[0] = 0x14; // handshake type (finished) + finishedPayload[1] = 0; finishedPayload[2] = 0; finishedPayload[3] = 0x0C; // 12 bytes finishedHash.Final (finishedHashDigest); - PRF (masterSecret, "client finished", finishedHashDigest, 32, 12, verifyData); + PRF (masterSecret, "client finished", finishedHashDigest, 32, 12, finishedPayload + 4); + Encrypt (finishedPayload, 16, finishedHashDigest/*TODO*/, encryptedPayload); site.write ((char *)finished, sizeof (finished)); - site.write ((char *)finishedHashDigest, 12); + site.write ((char *)encryptedPayload, 80); // read ChangeCipherSpecs uint8_t changeCipherSpecs1[6]; site.read ((char *)changeCipherSpecs1, 6); From e1f64e24761eb8951e2d7c6ad6219b38dff3f530 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Feb 2015 10:06:11 -0500 Subject: [PATCH 0209/6300] moved https code to TlsSession --- Reseed.cpp | 68 ++++++++++++++++++++++++++++++++++++++++-------------- Reseed.h | 26 ++++++++++++++------- 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 43af7b99..00a3d99e 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -127,7 +127,8 @@ namespace data int Reseeder::ReseedNowSU3 () { - auto ind = m_Rnd.GenerateWord32 (0, httpReseedHostList.size() - 1); + CryptoPP::AutoSeededRandomPool rnd; + auto ind = rnd.GenerateWord32 (0, httpReseedHostList.size() - 1); std::string reseedHost = httpReseedHostList[ind]; return ReseedFromSU3 (reseedHost); } @@ -497,7 +498,7 @@ namespace data LogPrint (eLogInfo, numCertificates, " certificates loaded"); } - std::string Reseeder::HttpsRequest (const std::string& address) + TlsSession::TlsSession (const std::string& address) { static uint8_t clientHello[] = { @@ -595,17 +596,7 @@ namespace data // 4 - 6 - length of array of certificates // 7 - 9 - length of certificate if (certificate[0] == 0x0B) // handshake type certificate - { - CryptoPP::ByteQueue queue; - queue.Put ((uint8_t *)certificate + 10, length - 10); - queue.MessageEnd (); - auto issuer = LoadCertificate (queue); - auto it = m_SigningKeys.find (issuer); - if (it != m_SigningKeys.end ()) - publicKey.Initialize (CryptoPP::Integer (it->second, 512), CryptoPP::Integer (i2p::crypto::rsae)); - else - LogPrint (eLogError, "Can't find public key for ", issuer); - } + publicKey = ExtractPublicKey ((uint8_t *)certificate + 10, length - 10); else LogPrint (eLogError, "Unexpected handshake type ", (int)certificate[0]); delete[] certificate; @@ -674,10 +665,9 @@ namespace data } else LogPrint (eLogError, "Can't connect to ", address); - return ""; } - void Reseeder::PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, + void TlsSession::PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, size_t len, uint8_t * buf) { // secret is assumed 48 bytes @@ -701,7 +691,7 @@ namespace data } } - size_t Reseeder::Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) + size_t TlsSession::Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) { size_t size = 0; m_Rnd.GenerateBlock (out, 16); // iv @@ -720,13 +710,57 @@ namespace data return size; } - size_t Reseeder::Decrypt (uint8_t * in, size_t len, uint8_t * out) + size_t TlsSession::Decrypt (uint8_t * in, size_t len, uint8_t * out) { m_Decryption.SetIV (in); m_Decryption.Decrypt (in + 16, len - 16, in + 16); memcpy (out, in + 16, len - 48); // skip 32 bytes mac return len - 48 - in[len -1] - 1; } + + CryptoPP::RSA::PublicKey TlsSession::ExtractPublicKey (const uint8_t * certificate, size_t len) + { + CryptoPP::ByteQueue queue; + queue.Put (certificate, len); + queue.MessageEnd (); + // extract X.509 + CryptoPP::BERSequenceDecoder x509Cert (queue); + CryptoPP::BERSequenceDecoder tbsCert (x509Cert); + // version + uint32_t ver; + CryptoPP::BERGeneralDecoder context (tbsCert, CryptoPP::CONTEXT_SPECIFIC | CryptoPP::CONSTRUCTED); + CryptoPP::BERDecodeUnsigned(context, ver, CryptoPP::INTEGER); + // serial + CryptoPP::Integer serial; + serial.BERDecode(tbsCert); + // signature + CryptoPP::BERSequenceDecoder signature (tbsCert); + signature.SkipAll(); + // issuer + CryptoPP::BERSequenceDecoder issuer (tbsCert); + issuer.SkipAll(); + // validity + CryptoPP::BERSequenceDecoder validity (tbsCert); + validity.SkipAll(); + // subject + CryptoPP::BERSequenceDecoder subject (tbsCert); + subject.SkipAll(); + // public key + CryptoPP::BERSequenceDecoder publicKey (tbsCert); + CryptoPP::BERSequenceDecoder ident (publicKey); + ident.SkipAll (); + CryptoPP::BERGeneralDecoder key (publicKey, CryptoPP::BIT_STRING); + key.Skip (1); // FIXME: probably bug in crypto++ + CryptoPP::BERSequenceDecoder keyPair (key); + CryptoPP::Integer n, e; + n.BERDecode (keyPair); + e.BERDecode (keyPair); + + CryptoPP::RSA::PublicKey ret; + ret.Initialize (n, e); + return ret; + } + } } diff --git a/Reseed.h b/Reseed.h index 04715142..3c67ff22 100644 --- a/Reseed.h +++ b/Reseed.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "Identity.h" #include "aes.h" @@ -40,22 +41,31 @@ namespace data bool FindZipDataDescriptor (std::istream& s); - // for HTTPS - void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, - size_t len, uint8_t * buf); - size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out); - size_t Decrypt (uint8_t * in, size_t len, uint8_t * out); - private: std::map m_SigningKeys; + }; + + class TlsSession + { + public: + + TlsSession (const std::string& address); + + private: + + CryptoPP::RSA::PublicKey ExtractPublicKey (const uint8_t * certificate, size_t len); + void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, + size_t len, uint8_t * buf); + size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out); + size_t Decrypt (uint8_t * in, size_t len, uint8_t * out); + + private: - // for HTTPS CryptoPP::AutoSeededRandomPool m_Rnd; i2p::crypto::CBCEncryption m_Encryption; i2p::crypto::CBCDecryption m_Decryption; uint8_t m_MacKey[32]; // client - }; } } From 5deccd7833344b2ec11add8af4910c17eb834016 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Feb 2015 11:26:37 -0500 Subject: [PATCH 0210/6300] calculate MAC --- Reseed.cpp | 19 ++++++++++++++++--- Reseed.h | 2 ++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 00a3d99e..43a32ba2 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -648,12 +647,14 @@ namespace data finishedPayload[1] = 0; finishedPayload[2] = 0; finishedPayload[3] = 0x0C; // 12 bytes finishedHash.Final (finishedHashDigest); PRF (masterSecret, "client finished", finishedHashDigest, 32, 12, finishedPayload + 4); - Encrypt (finishedPayload, 16, finishedHashDigest/*TODO*/, encryptedPayload); + uint8_t mac[32]; + CalculateMACKey (0x16, 0, finishedPayload, 16, mac); + Encrypt (finishedPayload, 16, mac, encryptedPayload); site.write ((char *)finished, sizeof (finished)); site.write ((char *)encryptedPayload, 80); // read ChangeCipherSpecs uint8_t changeCipherSpecs1[6]; - site.read ((char *)changeCipherSpecs1, 6); + site.read ((char *)changeCipherSpecs1, 6); // read finished site.read ((char *)&type, 1); site.read ((char *)&version, 2); @@ -718,6 +719,18 @@ namespace data return len - 48 - in[len -1] - 1; } + void TlsSession::CalculateMACKey (uint8_t type, uint64_t seqn, const uint8_t * buf, size_t len, uint8_t * mac) + { + uint8_t header[13]; // seqn (8) + type (1) + version (2) + length (2) + htobuf64 (header, seqn); + header[8] = type; header[9] = 3; header[10] = 3; // 3,3 means TLS 1.2 + htobuf16 (header + 11, len); + CryptoPP::HMAC hmac (m_MacKey, 32); + hmac.Update (header, 13); + hmac.Update (buf, len); + hmac.Final (mac); + } + CryptoPP::RSA::PublicKey TlsSession::ExtractPublicKey (const uint8_t * certificate, size_t len) { CryptoPP::ByteQueue queue; diff --git a/Reseed.h b/Reseed.h index 3c67ff22..46d21b51 100644 --- a/Reseed.h +++ b/Reseed.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "Identity.h" #include "aes.h" @@ -57,6 +58,7 @@ namespace data CryptoPP::RSA::PublicKey ExtractPublicKey (const uint8_t * certificate, size_t len); void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, size_t len, uint8_t * buf); + void CalculateMACKey (uint8_t type, uint64_t seqn, const uint8_t * buf, size_t len, uint8_t * mac); size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out); size_t Decrypt (uint8_t * in, size_t len, uint8_t * out); From e5d9c26868b4475b51181507ee9e9a030d30d6d8 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Feb 2015 13:50:04 -0500 Subject: [PATCH 0211/6300] use 256 bytes block for keys expansion --- Reseed.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 43a32ba2..011f2702 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -633,10 +633,10 @@ namespace data // calculate master secret PRF (secret, "master secret", random, 64, 48, masterSecret); // expand master secret - uint8_t keys[128]; // clientMACKey, serverMACKey, clientKey, serverKey + uint8_t keys[256]; // clientMACKey, serverMACKey, clientKey, serverKey memcpy (random, serverRandom, 32); memcpy (random + 32, clientHello + 11, 32); - PRF (masterSecret, "key expansion", random, 64, sizeof (keys), keys); + PRF (masterSecret, "key expansion", random, 64, 256, keys); memcpy (m_MacKey, keys, 32); m_Encryption.SetKey (keys + 64); m_Decryption.SetKey (keys + 96); From 64a4799c8c1412285d0a661d581261947ce4a810 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Feb 2015 16:35:07 -0500 Subject: [PATCH 0212/6300] fixed incorrect MAC calculation --- Reseed.cpp | 8 ++++---- Reseed.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 011f2702..d24456ed 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -648,7 +648,7 @@ namespace data finishedHash.Final (finishedHashDigest); PRF (masterSecret, "client finished", finishedHashDigest, 32, 12, finishedPayload + 4); uint8_t mac[32]; - CalculateMACKey (0x16, 0, finishedPayload, 16, mac); + CalculateMAC (0x16, 0, finishedPayload, 16, mac); Encrypt (finishedPayload, 16, mac, encryptedPayload); site.write ((char *)finished, sizeof (finished)); site.write ((char *)encryptedPayload, 80); @@ -719,12 +719,12 @@ namespace data return len - 48 - in[len -1] - 1; } - void TlsSession::CalculateMACKey (uint8_t type, uint64_t seqn, const uint8_t * buf, size_t len, uint8_t * mac) + void TlsSession::CalculateMAC (uint8_t type, uint64_t seqn, const uint8_t * buf, size_t len, uint8_t * mac) { uint8_t header[13]; // seqn (8) + type (1) + version (2) + length (2) - htobuf64 (header, seqn); + htobe64buf (header, seqn); header[8] = type; header[9] = 3; header[10] = 3; // 3,3 means TLS 1.2 - htobuf16 (header + 11, len); + htobe16buf (header + 11, len); CryptoPP::HMAC hmac (m_MacKey, 32); hmac.Update (header, 13); hmac.Update (buf, len); diff --git a/Reseed.h b/Reseed.h index 46d21b51..5b302044 100644 --- a/Reseed.h +++ b/Reseed.h @@ -58,7 +58,7 @@ namespace data CryptoPP::RSA::PublicKey ExtractPublicKey (const uint8_t * certificate, size_t len); void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, size_t len, uint8_t * buf); - void CalculateMACKey (uint8_t type, uint64_t seqn, const uint8_t * buf, size_t len, uint8_t * mac); + void CalculateMAC (uint8_t type, uint64_t seqn, const uint8_t * buf, size_t len, uint8_t * mac); size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out); size_t Decrypt (uint8_t * in, size_t len, uint8_t * out); From d1b26b72e324c8b8cee4168793db9c8c5bf27b4f Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Feb 2015 22:13:41 -0500 Subject: [PATCH 0213/6300] proper handshake messages hash calculations --- Reseed.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index d24456ed..fbf32fc4 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -563,14 +563,14 @@ namespace data CryptoPP::SHA256 finishedHash; // send ClientHello site.write ((char *)clientHello, sizeof (clientHello)); - finishedHash.Update (clientHello, sizeof (clientHello)); + finishedHash.Update (clientHello + 5, sizeof (clientHello) - 5); // read ServerHello uint8_t type; - site.read ((char *)&type, 1); finishedHash.Update ((uint8_t *)&type, 1); + site.read ((char *)&type, 1); uint16_t version; - site.read ((char *)&version, 2); finishedHash.Update ((uint8_t *)&version, 2); + site.read ((char *)&version, 2); uint16_t length; - site.read ((char *)&length, 2); finishedHash.Update ((uint8_t *)&length, 2); + site.read ((char *)&length, 2); length = be16toh (length); char * serverHello = new char[length]; site.read (serverHello, length); @@ -582,9 +582,9 @@ namespace data LogPrint (eLogError, "Unexpected handshake type ", (int)serverHello[0]); delete[] serverHello; // read Certificate - site.read ((char *)&type, 1); finishedHash.Update ((uint8_t *)&type, 1); - site.read ((char *)&version, 2); finishedHash.Update ((uint8_t *)&version, 2); - site.read ((char *)&length, 2); finishedHash.Update ((uint8_t *)&length, 2); + site.read ((char *)&type, 1); + site.read ((char *)&version, 2); + site.read ((char *)&length, 2); length = be16toh (length); char * certificate = new char[length]; site.read (certificate, length); @@ -600,9 +600,9 @@ namespace data LogPrint (eLogError, "Unexpected handshake type ", (int)certificate[0]); delete[] certificate; // read ServerHelloDone - site.read ((char *)&type, 1); finishedHash.Update ((uint8_t *)&type, 1); - site.read ((char *)&version, 2); finishedHash.Update ((uint8_t *)&version, 2); - site.read ((char *)&length, 2); finishedHash.Update ((uint8_t *)&length, 2); + site.read ((char *)&type, 1); + site.read ((char *)&version, 2); + site.read ((char *)&length, 2); length = be16toh (length); char * serverHelloDone = new char[length]; site.read (serverHelloDone, length); @@ -622,14 +622,13 @@ namespace data // send ClientKeyExchange site.write ((char *)clientKeyExchange, sizeof (clientKeyExchange)); site.write ((char *)encrypted, 256); - finishedHash.Update (clientKeyExchange, sizeof (clientKeyExchange)); + finishedHash.Update (clientKeyExchange + 5, sizeof (clientKeyExchange) - 5); finishedHash.Update (encrypted, 256); uint8_t masterSecret[48], random[64]; memcpy (random, clientHello + 11, 32); memcpy (random + 32, serverRandom, 32); // send ChangeCipherSpecs site.write ((char *)changeCipherSpecs, sizeof (changeCipherSpecs)); - finishedHash.Update (changeCipherSpecs, sizeof (changeCipherSpecs)); // calculate master secret PRF (secret, "master secret", random, 64, 48, masterSecret); // expand master secret From 71dae290774fef7f9090d0e400a18849ab5ea1d7 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Feb 2015 10:47:44 -0500 Subject: [PATCH 0214/6300] send and receive data --- Reseed.cpp | 287 +++++++++++++++++++++++++++++++---------------------- Reseed.h | 13 ++- 2 files changed, 175 insertions(+), 125 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index fbf32fc4..c443a4fe 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -3,9 +3,11 @@ #include #include #include +#include #include #include #include +#include #include #include "I2PEndian.h" #include "Reseed.h" @@ -497,7 +499,30 @@ namespace data LogPrint (eLogInfo, numCertificates, " certificates loaded"); } - TlsSession::TlsSession (const std::string& address) + std::string Reseeder::HttpsRequest (const std::string& address) + { + i2p::util::http::url u(address); + TlsSession session (u.host_, 443); + 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"; + session.Send ((uint8_t *)ss.str ().c_str (), ss.str ().length ()); + return ""; + } + + TlsSession::TlsSession (const std::string& host, int port): + m_Seqn (0) + { + m_Site.connect(host, boost::lexical_cast(port)); + if (m_Site.good ()) + { + Handshake (); + } + else + LogPrint (eLogError, "Can't connect to ", host, ":", port); + } + + void TlsSession::Handshake () { static uint8_t clientHello[] = { @@ -553,120 +578,112 @@ namespace data // 0x14 handshake type (finished) // 0x00, 0x00, 0x0C length of handshake payload // 12 bytes of verified data - }; - - i2p::util::http::url u(address); - boost::asio::ip::tcp::iostream site; - site.connect(u.host_, "443"); - if (site.good ()) - { - CryptoPP::SHA256 finishedHash; - // send ClientHello - site.write ((char *)clientHello, sizeof (clientHello)); - finishedHash.Update (clientHello + 5, sizeof (clientHello) - 5); - // read ServerHello - uint8_t type; - site.read ((char *)&type, 1); - uint16_t version; - site.read ((char *)&version, 2); - uint16_t length; - site.read ((char *)&length, 2); - length = be16toh (length); - char * serverHello = new char[length]; - site.read (serverHello, length); - finishedHash.Update ((uint8_t *)serverHello, length); - uint8_t serverRandom[32]; - if (serverHello[0] == 0x02) // handshake type server hello - memcpy (serverRandom, serverHello + 6, 32); - else - LogPrint (eLogError, "Unexpected handshake type ", (int)serverHello[0]); - delete[] serverHello; - // read Certificate - site.read ((char *)&type, 1); - site.read ((char *)&version, 2); - site.read ((char *)&length, 2); - length = be16toh (length); - char * certificate = new char[length]; - site.read (certificate, length); - finishedHash.Update ((uint8_t *)certificate, length); - CryptoPP::RSA::PublicKey publicKey; - // 0 - handshake type - // 1 - 3 - handshake payload length - // 4 - 6 - length of array of certificates - // 7 - 9 - length of certificate - if (certificate[0] == 0x0B) // handshake type certificate - publicKey = ExtractPublicKey ((uint8_t *)certificate + 10, length - 10); - else - LogPrint (eLogError, "Unexpected handshake type ", (int)certificate[0]); - delete[] certificate; - // read ServerHelloDone - site.read ((char *)&type, 1); - site.read ((char *)&version, 2); - site.read ((char *)&length, 2); - length = be16toh (length); - char * serverHelloDone = new char[length]; - site.read (serverHelloDone, length); - finishedHash.Update ((uint8_t *)serverHelloDone, length); - if (serverHelloDone[0] != 0x0E) // handshake type hello done - LogPrint (eLogError, "Unexpected handshake type ", (int)serverHelloDone[0]); - delete[] serverHelloDone; - // our turn now - // generate secret key - CryptoPP::AutoSeededRandomPool rnd; - CryptoPP::RSAES_PKCS1v15_Encryptor encryptor(publicKey); - // encryptor.CiphertextLength (48); - uint8_t secret[48], encrypted[256]; - secret[0] = clientKeyExchange[1]; secret[1] = clientKeyExchange[2]; // version - m_Rnd.GenerateBlock (secret + 2, 46); // 46 random bytes - encryptor.Encrypt (rnd, secret, 48, encrypted); - // send ClientKeyExchange - site.write ((char *)clientKeyExchange, sizeof (clientKeyExchange)); - site.write ((char *)encrypted, 256); - finishedHash.Update (clientKeyExchange + 5, sizeof (clientKeyExchange) - 5); - finishedHash.Update (encrypted, 256); - uint8_t masterSecret[48], random[64]; - memcpy (random, clientHello + 11, 32); - memcpy (random + 32, serverRandom, 32); - // send ChangeCipherSpecs - site.write ((char *)changeCipherSpecs, sizeof (changeCipherSpecs)); - // calculate master secret - PRF (secret, "master secret", random, 64, 48, masterSecret); - // expand master secret - uint8_t keys[256]; // clientMACKey, serverMACKey, clientKey, serverKey - memcpy (random, serverRandom, 32); - memcpy (random + 32, clientHello + 11, 32); - PRF (masterSecret, "key expansion", random, 64, 256, keys); - memcpy (m_MacKey, keys, 32); - m_Encryption.SetKey (keys + 64); - m_Decryption.SetKey (keys + 96); - - // send finished - uint8_t finishedHashDigest[32], finishedPayload[40], encryptedPayload[80]; - finishedPayload[0] = 0x14; // handshake type (finished) - finishedPayload[1] = 0; finishedPayload[2] = 0; finishedPayload[3] = 0x0C; // 12 bytes - finishedHash.Final (finishedHashDigest); - PRF (masterSecret, "client finished", finishedHashDigest, 32, 12, finishedPayload + 4); - uint8_t mac[32]; - CalculateMAC (0x16, 0, finishedPayload, 16, mac); - Encrypt (finishedPayload, 16, mac, encryptedPayload); - site.write ((char *)finished, sizeof (finished)); - site.write ((char *)encryptedPayload, 80); - // read ChangeCipherSpecs - uint8_t changeCipherSpecs1[6]; - site.read ((char *)changeCipherSpecs1, 6); - // read finished - site.read ((char *)&type, 1); - site.read ((char *)&version, 2); - site.read ((char *)&length, 2); - length = be16toh (length); - char * finished1 = new char[length]; - site.read (finished1, length); - delete[] finished1; - } - else - LogPrint (eLogError, "Can't connect to ", address); - } + }; + CryptoPP::SHA256 finishedHash; + // send ClientHello + m_Site.write ((char *)clientHello, sizeof (clientHello)); + finishedHash.Update (clientHello + 5, sizeof (clientHello) - 5); + // read ServerHello + uint8_t type; + m_Site.read ((char *)&type, 1); + uint16_t version; + m_Site.read ((char *)&version, 2); + uint16_t length; + m_Site.read ((char *)&length, 2); + length = be16toh (length); + char * serverHello = new char[length]; + m_Site.read (serverHello, length); + finishedHash.Update ((uint8_t *)serverHello, length); + uint8_t serverRandom[32]; + if (serverHello[0] == 0x02) // handshake type server hello + memcpy (serverRandom, serverHello + 6, 32); + else + LogPrint (eLogError, "Unexpected handshake type ", (int)serverHello[0]); + delete[] serverHello; + // read Certificate + m_Site.read ((char *)&type, 1); + m_Site.read ((char *)&version, 2); + m_Site.read ((char *)&length, 2); + length = be16toh (length); + char * certificate = new char[length]; + m_Site.read (certificate, length); + finishedHash.Update ((uint8_t *)certificate, length); + CryptoPP::RSA::PublicKey publicKey; + // 0 - handshake type + // 1 - 3 - handshake payload length + // 4 - 6 - length of array of certificates + // 7 - 9 - length of certificate + if (certificate[0] == 0x0B) // handshake type certificate + publicKey = ExtractPublicKey ((uint8_t *)certificate + 10, length - 10); + else + LogPrint (eLogError, "Unexpected handshake type ", (int)certificate[0]); + delete[] certificate; + // read ServerHelloDone + m_Site.read ((char *)&type, 1); + m_Site.read ((char *)&version, 2); + m_Site.read ((char *)&length, 2); + length = be16toh (length); + char * serverHelloDone = new char[length]; + m_Site.read (serverHelloDone, length); + finishedHash.Update ((uint8_t *)serverHelloDone, length); + if (serverHelloDone[0] != 0x0E) // handshake type hello done + LogPrint (eLogError, "Unexpected handshake type ", (int)serverHelloDone[0]); + delete[] serverHelloDone; + // our turn now + // generate secret key + CryptoPP::AutoSeededRandomPool rnd; + CryptoPP::RSAES_PKCS1v15_Encryptor encryptor(publicKey); + // encryptor.CiphertextLength (48); + uint8_t secret[48], encrypted[256]; + secret[0] = 3; secret[1] = 3; // version + m_Rnd.GenerateBlock (secret + 2, 46); // 46 random bytes + encryptor.Encrypt (rnd, secret, 48, encrypted); + // send ClientKeyExchange + m_Site.write ((char *)clientKeyExchange, sizeof (clientKeyExchange)); + m_Site.write ((char *)encrypted, 256); + finishedHash.Update (clientKeyExchange + 5, sizeof (clientKeyExchange) - 5); + finishedHash.Update (encrypted, 256); + uint8_t masterSecret[48], random[64]; + memcpy (random, clientHello + 11, 32); + memcpy (random + 32, serverRandom, 32); + // send ChangeCipherSpecs + m_Site.write ((char *)changeCipherSpecs, sizeof (changeCipherSpecs)); + // calculate master secret + PRF (secret, "master secret", random, 64, 48, masterSecret); + // expand master secret + uint8_t keys[256]; // clientMACKey(32), serverMACKey(32), clientKey(32), serverKey(32) + memcpy (random, serverRandom, 32); + memcpy (random + 32, clientHello + 11, 32); + PRF (masterSecret, "key expansion", random, 64, 256, keys); + memcpy (m_MacKey, keys, 32); + m_Encryption.SetKey (keys + 64); + m_Decryption.SetKey (keys + 96); + + // send finished + uint8_t finishedHashDigest[32], finishedPayload[40], encryptedPayload[80]; + finishedPayload[0] = 0x14; // handshake type (finished) + finishedPayload[1] = 0; finishedPayload[2] = 0; finishedPayload[3] = 0x0C; // 12 bytes + finishedHash.Final (finishedHashDigest); + PRF (masterSecret, "client finished", finishedHashDigest, 32, 12, finishedPayload + 4); + uint8_t mac[32]; + CalculateMAC (0x16, finishedPayload, 16, mac); + Encrypt (finishedPayload, 16, mac, encryptedPayload); + m_Site.write ((char *)finished, sizeof (finished)); + m_Site.write ((char *)encryptedPayload, 80); + // read ChangeCipherSpecs + uint8_t changeCipherSpecs1[6]; + m_Site.read ((char *)changeCipherSpecs1, 6); + // read finished + m_Site.read ((char *)&type, 1); + m_Site.read ((char *)&version, 2); + m_Site.read ((char *)&length, 2); + length = be16toh (length); + char * finished1 = new char[length]; + m_Site.read (finished1, length); + delete[] finished1; + } + void TlsSession::PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, size_t len, uint8_t * buf) { @@ -710,24 +727,24 @@ namespace data return size; } - size_t TlsSession::Decrypt (uint8_t * in, size_t len, uint8_t * out) + size_t TlsSession::Decrypt (uint8_t * buf, size_t len) { - m_Decryption.SetIV (in); - m_Decryption.Decrypt (in + 16, len - 16, in + 16); - memcpy (out, in + 16, len - 48); // skip 32 bytes mac - return len - 48 - in[len -1] - 1; + m_Decryption.SetIV (buf); + m_Decryption.Decrypt (buf + 16, len - 16, buf + 16); + return len - 48 - buf[len -1] - 1; // IV(16), mac(32) and padding } - void TlsSession::CalculateMAC (uint8_t type, uint64_t seqn, const uint8_t * buf, size_t len, uint8_t * mac) + void TlsSession::CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac) { uint8_t header[13]; // seqn (8) + type (1) + version (2) + length (2) - htobe64buf (header, seqn); + htobe64buf (header, m_Seqn); header[8] = type; header[9] = 3; header[10] = 3; // 3,3 means TLS 1.2 htobe16buf (header + 11, len); CryptoPP::HMAC hmac (m_MacKey, 32); hmac.Update (header, 13); hmac.Update (buf, len); hmac.Final (mac); + m_Seqn++; } CryptoPP::RSA::PublicKey TlsSession::ExtractPublicKey (const uint8_t * certificate, size_t len) @@ -773,6 +790,34 @@ namespace data return ret; } + void TlsSession::Send (const uint8_t * buf, size_t len) + { + uint8_t * out = new uint8_t[len + 64 + 5]; // 64 = 32 mac + 16 iv + upto 16 padding, 5 = header + out[0] = 0x17; // application data + out[1] = 0x03; out[2] = 0x03; // version + uint8_t mac[32]; + CalculateMAC (0x17, buf, len, mac); + size_t encryptedLen = Encrypt (buf, len, mac, out); + htobe16buf (out + 3, encryptedLen); + m_Site.write ((char *)out, encryptedLen + 5); + delete[] out; + } + + std::string TlsSession::Receive () + { + if (m_Site.eof ()) return ""; + uint8_t type; uint16_t version, length; + m_Site.read ((char *)&type, 1); + m_Site.read ((char *)&version, 2); + m_Site.read ((char *)&length, 2); + length = be16toh (length); + uint8_t * buf = new uint8_t[length]; + m_Site.read ((char *)buf, length); + size_t decryptedLen = Decrypt (buf, length); + std::string str ((char *)buf + 16, decryptedLen); + delete[] buf; + return str; + } } } diff --git a/Reseed.h b/Reseed.h index 5b302044..a5a61366 100644 --- a/Reseed.h +++ b/Reseed.h @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include "Identity.h" #include "aes.h" @@ -51,19 +51,24 @@ namespace data { public: - TlsSession (const std::string& address); + TlsSession (const std::string& host, int port); + void Send (const uint8_t * buf, size_t len); + std::string Receive (); private: + void Handshake (); CryptoPP::RSA::PublicKey ExtractPublicKey (const uint8_t * certificate, size_t len); void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, size_t len, uint8_t * buf); - void CalculateMAC (uint8_t type, uint64_t seqn, const uint8_t * buf, size_t len, uint8_t * mac); + void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac); size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out); - size_t Decrypt (uint8_t * in, size_t len, uint8_t * out); + size_t Decrypt (uint8_t * buf, size_t len); // pyaload is buf + 16 private: + uint64_t m_Seqn; + boost::asio::ip::tcp::iostream m_Site; CryptoPP::AutoSeededRandomPool m_Rnd; i2p::crypto::CBCEncryption m_Encryption; i2p::crypto::CBCDecryption m_Decryption; From 0d468a8f48921fafe6034c5bba41f6223619f4c8 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Feb 2015 12:21:33 -0500 Subject: [PATCH 0215/6300] extract https content --- Reseed.cpp | 19 +++++++++----- Reseed.h | 2 +- util.cpp | 73 +++++++++++++++++++++++++++++------------------------- util.h | 1 + 4 files changed, 54 insertions(+), 41 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index c443a4fe..ff979da2 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -503,11 +503,18 @@ namespace data { i2p::util::http::url u(address); TlsSession session (u.host_, 443); + + // 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"; session.Send ((uint8_t *)ss.str ().c_str (), ss.str ().length ()); - return ""; + + // read response + std::stringstream rs; + while (session.Receive (rs)) + ; + return i2p::util::http::GetHttpContent (rs); } TlsSession::TlsSession (const std::string& host, int port): @@ -797,15 +804,15 @@ namespace data out[1] = 0x03; out[2] = 0x03; // version uint8_t mac[32]; CalculateMAC (0x17, buf, len, mac); - size_t encryptedLen = Encrypt (buf, len, mac, out); + size_t encryptedLen = Encrypt (buf, len, mac, out + 5); htobe16buf (out + 3, encryptedLen); m_Site.write ((char *)out, encryptedLen + 5); delete[] out; } - std::string TlsSession::Receive () + bool TlsSession::Receive (std::ostream& rs) { - if (m_Site.eof ()) return ""; + if (m_Site.eof ()) return false; uint8_t type; uint16_t version, length; m_Site.read ((char *)&type, 1); m_Site.read ((char *)&version, 2); @@ -814,9 +821,9 @@ namespace data uint8_t * buf = new uint8_t[length]; m_Site.read ((char *)buf, length); size_t decryptedLen = Decrypt (buf, length); - std::string str ((char *)buf + 16, decryptedLen); + rs.write ((char *)buf + 16, decryptedLen); delete[] buf; - return str; + return true; } } } diff --git a/Reseed.h b/Reseed.h index a5a61366..8b315d01 100644 --- a/Reseed.h +++ b/Reseed.h @@ -53,7 +53,7 @@ namespace data TlsSession (const std::string& host, int port); void Send (const uint8_t * buf, size_t len); - std::string Receive (); + bool Receive (std::ostream& rs); private: diff --git a/util.cpp b/util.cpp index e3b4d894..42541a75 100644 --- a/util.cpp +++ b/util.cpp @@ -242,40 +242,8 @@ namespace http // User-Agent is needed to get the server list routerInfo files. site << "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"; - // read response - std::string version, statusMessage; - site >> version; // HTTP version - int status; - site >> status; // status - std::getline (site, statusMessage); - if (status == 200) // OK - { - bool isChunked = false; - std::string header; - while (!site.eof () && header != "\r") - { - std::getline(site, header); - auto colon = header.find (':'); - if (colon != std::string::npos) - { - std::string field = header.substr (0, colon); - if (field == i2p::util::http::TRANSFER_ENCODING) - isChunked = (header.find ("chunked", colon + 1) != std::string::npos); - } - } - - std::stringstream ss; - if (isChunked) - MergeChunkedResponse (site, ss); - else - ss << site.rdbuf(); - return ss.str(); - } - else - { - LogPrint ("HTTP response ", status); - return ""; - } + // read response and extract content + return GetHttpContent (site); } else { @@ -290,6 +258,43 @@ namespace http } } + std::string GetHttpContent (std::istream& response) + { + std::string version, statusMessage; + response >> version; // HTTP version + int status; + response >> status; // status + std::getline (response, statusMessage); + if (status == 200) // OK + { + bool isChunked = false; + std::string header; + while (!response.eof () && header != "\r") + { + std::getline(response, header); + auto colon = header.find (':'); + if (colon != std::string::npos) + { + std::string field = header.substr (0, colon); + if (field == i2p::util::http::TRANSFER_ENCODING) + isChunked = (header.find ("chunked", colon + 1) != std::string::npos); + } + } + + std::stringstream ss; + if (isChunked) + MergeChunkedResponse (response, ss); + else + ss << response.rdbuf(); + return ss.str(); + } + else + { + LogPrint ("HTTP response ", status); + return ""; + } + } + void MergeChunkedResponse (std::istream& response, std::ostream& merged) { while (!response.eof ()) diff --git a/util.h b/util.h index 8aee0560..4b229380 100644 --- a/util.h +++ b/util.h @@ -47,6 +47,7 @@ namespace util const char TRANSFER_ENCODING[] = "Transfer-Encoding"; std::string httpRequest(const std::string& address); + std::string GetHttpContent (std::istream& response); void MergeChunkedResponse (std::istream& response, std::ostream& merged); int httpRequestViaI2pProxy(const std::string& address, std::string &content); // return http code std::string urlDecode(const std::string& data); From ad5bac6598763a131f2e8d5602a59817f7d27c98 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Feb 2015 14:26:49 -0500 Subject: [PATCH 0216/6300] ClientKeyExchange length depend on key length from ceritifcate --- Reseed.cpp | 84 ++++++++++++++++++++++++------------------------------ Reseed.h | 2 ++ 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index ff979da2..ea26f3fc 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -46,8 +46,6 @@ namespace data "https://ieb9oopo.mooo.com/" // Only HTTPS and SU3 (v2) support }; - //TODO: Implement v2 reseeding. Lightweight zip library is needed. - //TODO: Implement SU3, utils. Reseeder::Reseeder() { } @@ -58,17 +56,9 @@ namespace data bool Reseeder::reseedNow() { + // This method is deprecated try { - // Seems like the best place to try to intercept with SSL - /*ssl_server = true; - try { - // SSL - } - catch (std::exception& e) - { - LogPrint("Exception in SSL: ", e.what()); - }*/ std::string reseedHost = httpReseedHostList[(rand() % httpReseedHostList.size())]; LogPrint("Reseeding from ", reseedHost); std::string content = i2p::util::http::httpRequest(reseedHost); @@ -535,10 +525,10 @@ namespace data { 0x16, // handshake 0x03, 0x03, // version (TSL 1.2) - 0x00, 0x31, // length of handshake + 0x00, 0x2F, // length of handshake // handshake 0x01, // handshake type (client hello) - 0x00, 0x00, 0x2D, // length of handshake payload + 0x00, 0x00, 0x2B, // length of handshake payload // client hello 0x03, 0x03, // highest version supported (TSL 1.2) 0x45, 0xFA, 0x01, 0x19, 0x74, 0x55, 0x18, 0x36, @@ -546,27 +536,13 @@ namespace data 0xEC, 0x37, 0x11, 0x93, 0x16, 0xF4, 0x66, 0x00, 0x12, 0x67, 0xAB, 0xBA, 0xFF, 0x29, 0x13, 0x9E, // 32 random bytes 0x00, // session id length - 0x00, 0x04, // chiper suites length - 0x00, 0x00, // NULL_WITH_NULL_NULL + 0x00, 0x02, // chiper suites length 0x00, 0x3D, // RSA_WITH_AES_256_CBC_SHA256 0x01, // compression methods length 0x00, // no compression 0x00, 0x00 // extensions length }; - static uint8_t clientKeyExchange[] = - { - 0x16, // handshake - 0x03, 0x03, // version (TSL 1.2) - 0x01, 0x06, // length of handshake - // handshake - 0x10, // handshake type (client key exchange) - 0x00, 0x01, 0x02, // length of handshake payload - // client key exchange RSA - 0x01, 0x00, // length of RSA encrypted - // 256 RSA encrypted 48 bytes ( 2 bytes version + 46 random bytes) - }; - static uint8_t changeCipherSpecs[] = { 0x14, // change chiper specs @@ -587,10 +563,9 @@ namespace data // 12 bytes of verified data }; - CryptoPP::SHA256 finishedHash; // send ClientHello m_Site.write ((char *)clientHello, sizeof (clientHello)); - finishedHash.Update (clientHello + 5, sizeof (clientHello) - 5); + m_FinishedHash.Update (clientHello + 5, sizeof (clientHello) - 5); // read ServerHello uint8_t type; m_Site.read ((char *)&type, 1); @@ -601,7 +576,7 @@ namespace data length = be16toh (length); char * serverHello = new char[length]; m_Site.read (serverHello, length); - finishedHash.Update ((uint8_t *)serverHello, length); + m_FinishedHash.Update ((uint8_t *)serverHello, length); uint8_t serverRandom[32]; if (serverHello[0] == 0x02) // handshake type server hello memcpy (serverRandom, serverHello + 6, 32); @@ -615,7 +590,7 @@ namespace data length = be16toh (length); char * certificate = new char[length]; m_Site.read (certificate, length); - finishedHash.Update ((uint8_t *)certificate, length); + m_FinishedHash.Update ((uint8_t *)certificate, length); CryptoPP::RSA::PublicKey publicKey; // 0 - handshake type // 1 - 3 - handshake payload length @@ -633,30 +608,31 @@ namespace data length = be16toh (length); char * serverHelloDone = new char[length]; m_Site.read (serverHelloDone, length); - finishedHash.Update ((uint8_t *)serverHelloDone, length); + m_FinishedHash.Update ((uint8_t *)serverHelloDone, length); if (serverHelloDone[0] != 0x0E) // handshake type hello done LogPrint (eLogError, "Unexpected handshake type ", (int)serverHelloDone[0]); delete[] serverHelloDone; // our turn now // generate secret key - CryptoPP::AutoSeededRandomPool rnd; - CryptoPP::RSAES_PKCS1v15_Encryptor encryptor(publicKey); - // encryptor.CiphertextLength (48); - uint8_t secret[48], encrypted[256]; + uint8_t secret[48]; secret[0] = 3; secret[1] = 3; // version m_Rnd.GenerateBlock (secret + 2, 46); // 46 random bytes - encryptor.Encrypt (rnd, secret, 48, encrypted); + // encrypt RSA + CryptoPP::RSAES_PKCS1v15_Encryptor encryptor(publicKey); + size_t encryptedLen = encryptor.CiphertextLength (48); // number of bytes for encrypted 48 bytes, usually 256 (2048 bits key) + uint8_t * encrypted = new uint8_t[encryptedLen + 2]; // + 2 bytes for length + htobe16buf (encrypted, encryptedLen); // first two bytes means length + encryptor.Encrypt (m_Rnd, secret, 48, encrypted + 2); // send ClientKeyExchange - m_Site.write ((char *)clientKeyExchange, sizeof (clientKeyExchange)); - m_Site.write ((char *)encrypted, 256); - finishedHash.Update (clientKeyExchange + 5, sizeof (clientKeyExchange) - 5); - finishedHash.Update (encrypted, 256); - uint8_t masterSecret[48], random[64]; - memcpy (random, clientHello + 11, 32); - memcpy (random + 32, serverRandom, 32); + // 0x10 - handshake type "client key exchange" + SendHandshakeMsg (0x10, encrypted, encryptedLen + 2); + delete[] encrypted; // send ChangeCipherSpecs m_Site.write ((char *)changeCipherSpecs, sizeof (changeCipherSpecs)); // calculate master secret + uint8_t masterSecret[48], random[64]; + memcpy (random, clientHello + 11, 32); + memcpy (random + 32, serverRandom, 32); PRF (secret, "master secret", random, 64, 48, masterSecret); // expand master secret uint8_t keys[256]; // clientMACKey(32), serverMACKey(32), clientKey(32), serverKey(32) @@ -671,7 +647,7 @@ namespace data uint8_t finishedHashDigest[32], finishedPayload[40], encryptedPayload[80]; finishedPayload[0] = 0x14; // handshake type (finished) finishedPayload[1] = 0; finishedPayload[2] = 0; finishedPayload[3] = 0x0C; // 12 bytes - finishedHash.Final (finishedHashDigest); + m_FinishedHash.Final (finishedHashDigest); PRF (masterSecret, "client finished", finishedHashDigest, 32, 12, finishedPayload + 4); uint8_t mac[32]; CalculateMAC (0x16, finishedPayload, 16, mac); @@ -691,6 +667,22 @@ namespace data delete[] finished1; } + void TlsSession::SendHandshakeMsg (uint8_t handshakeType, uint8_t * data, size_t len) + { + uint8_t handshakeHeader[9]; + handshakeHeader[0] = 0x16; // handshake + handshakeHeader[1] = 0x03; handshakeHeader[2] = 0x03; // version is always TLS 1.2 (3,3) + htobe16buf (handshakeHeader + 3, len + 4); // length of payload + //payload starts + handshakeHeader[5] = handshakeType; // handshake type + handshakeHeader[6] = 0; // highest byte of payload length is always zero + htobe16buf (handshakeHeader + 7, len); // length of data + m_Site.write ((char *)handshakeHeader, 9); + m_FinishedHash.Update (handshakeHeader + 5, 4); // only payload counts + m_Site.write ((char *)data, len); + m_FinishedHash.Update (data, len); + } + void TlsSession::PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, size_t len, uint8_t * buf) { diff --git a/Reseed.h b/Reseed.h index 8b315d01..07ef9dea 100644 --- a/Reseed.h +++ b/Reseed.h @@ -58,6 +58,7 @@ namespace data private: void Handshake (); + void SendHandshakeMsg (uint8_t handshakeType, uint8_t * data, size_t len); CryptoPP::RSA::PublicKey ExtractPublicKey (const uint8_t * certificate, size_t len); void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, size_t len, uint8_t * buf); @@ -69,6 +70,7 @@ namespace data uint64_t m_Seqn; boost::asio::ip::tcp::iostream m_Site; + CryptoPP::SHA256 m_FinishedHash; CryptoPP::AutoSeededRandomPool m_Rnd; i2p::crypto::CBCEncryption m_Encryption; i2p::crypto::CBCDecryption m_Decryption; From 8e795cc2aa8404afa9f35324e1750e3110bb9a34 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Feb 2015 15:11:00 -0500 Subject: [PATCH 0217/6300] include https hosts to reseeder's list --- Reseed.cpp | 14 ++++++++------ Reseed.h | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index ea26f3fc..540d0ebc 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -40,10 +40,11 @@ namespace data "https://netdb.i2p2.no/", // Only SU3 (v2) support "https://reseed.i2p-projekt.de/", // Only HTTPS "https://cowpuncher.drollette.com/netdb/", // Only HTTPS and SU3 (v2) support -- will move to a new location - "https://i2p.mooo.com/netDb/", + // following hosts are fine but don't support AES256 + /*"https://i2p.mooo.com/netDb/", "https://link.mx24.eu/", // Only HTTPS and SU3 (v2) support "https://i2pseed.zarrenspry.info/", // Only HTTPS and SU3 (v2) support - "https://ieb9oopo.mooo.com/" // Only HTTPS and SU3 (v2) support + "https://ieb9oopo.mooo.com/" // Only HTTPS and SU3 (v2) support*/ }; Reseeder::Reseeder() @@ -119,16 +120,17 @@ namespace data int Reseeder::ReseedNowSU3 () { CryptoPP::AutoSeededRandomPool rnd; - auto ind = rnd.GenerateWord32 (0, httpReseedHostList.size() - 1); - std::string reseedHost = httpReseedHostList[ind]; + auto ind = rnd.GenerateWord32 (0, httpReseedHostList.size() - 1 + httpsReseedHostList.size () - 1); + std::string reseedHost = (ind < httpReseedHostList.size()) ? httpReseedHostList[ind] : + httpsReseedHostList[ind - httpReseedHostList.size()]; return ReseedFromSU3 (reseedHost); } - int Reseeder::ReseedFromSU3 (const std::string& host) + int Reseeder::ReseedFromSU3 (const std::string& host, bool https) { std::string url = host + "i2pseeds.su3"; LogPrint (eLogInfo, "Dowloading SU3 from ", host); - std::string su3 = i2p::util::http::httpRequest (url); + std::string su3 = https ? HttpsRequest (url) : i2p::util::http::httpRequest (url); if (su3.length () > 0) { std::stringstream s(su3); diff --git a/Reseed.h b/Reseed.h index 07ef9dea..d43227a5 100644 --- a/Reseed.h +++ b/Reseed.h @@ -28,20 +28,20 @@ namespace data int ReseedNowSU3 (); void LoadCertificates (); - - std::string HttpsRequest (const std::string& address); // TODO: move to private section private: void LoadCertificate (const std::string& filename); std::string LoadCertificate (CryptoPP::ByteQueue& queue); // returns issuer's name - int ReseedFromSU3 (const std::string& host); + int ReseedFromSU3 (const std::string& host, bool https = false); int ProcessSU3File (const char * filename); int ProcessSU3Stream (std::istream& s); bool FindZipDataDescriptor (std::istream& s); + std::string HttpsRequest (const std::string& address); + private: std::map m_SigningKeys; From c88c6a9b63d0bf69d3353eb24e48b9fcb1fe1c87 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Feb 2015 15:15:23 -0500 Subject: [PATCH 0218/6300] include https hosts to reseeder's list --- Reseed.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Reseed.cpp b/Reseed.cpp index 540d0ebc..6d789509 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -123,7 +123,7 @@ namespace data auto ind = rnd.GenerateWord32 (0, httpReseedHostList.size() - 1 + httpsReseedHostList.size () - 1); std::string reseedHost = (ind < httpReseedHostList.size()) ? httpReseedHostList[ind] : httpsReseedHostList[ind - httpReseedHostList.size()]; - return ReseedFromSU3 (reseedHost); + return ReseedFromSU3 (reseedHost, ind >= httpReseedHostList.size()); } int Reseeder::ReseedFromSU3 (const std::string& host, bool https) From 05e49bbeab8271e7181a3f2c6e83971a67328e57 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Feb 2015 22:47:36 -0500 Subject: [PATCH 0219/6300] show SAM sessions through web interface --- ClientContext.h | 1 + HTTPServer.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++++++++- HTTPServer.h | 2 ++ SAM.cpp | 2 +- SAM.h | 10 ++++++-- 5 files changed, 73 insertions(+), 4 deletions(-) diff --git a/ClientContext.h b/ClientContext.h index 0cffcf33..6da2cd30 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -47,6 +47,7 @@ namespace client ClientDestination * LoadLocalDestination (const std::string& filename, bool isPublic); AddressBook& GetAddressBook () { return m_AddressBook; }; + const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; private: diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 77585039..c83894aa 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -469,6 +469,9 @@ namespace util const char HTTP_COMMAND_LOCAL_DESTINATIONS[] = "local_destinations"; const char HTTP_COMMAND_LOCAL_DESTINATION[] = "local_destination"; const char HTTP_PARAM_BASE32_ADDRESS[] = "b32"; + const char HTTP_COMMAND_SAM_SESSIONS[] = "sam_sessions"; + const char HTTP_COMMAND_SAM_SESSION[] = "sam_session"; + const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; namespace misc_strings { @@ -673,7 +676,10 @@ namespace util s << "
Local destinations"; s << "
Tunnels"; s << "
Transit tunnels"; - s << "
Transports
"; + s << "
Transports"; + if (i2p::client::context.GetSAMBridge ()) + s << "
SAM sessions"; + s << "
"; if (i2p::context.AcceptsTunnels ()) s << "
Stop accepting tunnels
"; @@ -706,6 +712,15 @@ namespace util auto b32 = params[HTTP_PARAM_BASE32_ADDRESS]; ShowLocalDestination (b32, s); } + else if (cmd == HTTP_COMMAND_SAM_SESSIONS) + ShowSAMSessions (s); + else if (cmd == HTTP_COMMAND_SAM_SESSION) + { + std::map params; + ExtractParams (command.substr (paramsPos), params); + auto id = params[HTTP_PARAM_SAM_SESSION_ID]; + ShowSAMSession (id, s); + } } void HTTPConnection::ShowTransports (std::stringstream& s) @@ -850,6 +865,51 @@ namespace util } } } + + void HTTPConnection::ShowSAMSessions (std::stringstream& s) + { + auto sam = i2p::client::context.GetSAMBridge (); + if (sam) + { + for (auto& it: sam->GetSessions ()) + { + s << ""; + s << it.first << "
" << std::endl; + } + } + } + + void HTTPConnection::ShowSAMSession (const std::string& id, std::stringstream& s) + { + auto sam = i2p::client::context.GetSAMBridge (); + if (sam) + { + auto session = sam->FindSession (id); + if (session) + { + for (auto it: session->sockets) + { + switch (it->GetSocketType ()) + { + case i2p::client::eSAMSocketTypeSession: + s << "session"; + break; + case i2p::client::eSAMSocketTypeStream: + s << "stream"; + break; + case i2p::client::eSAMSocketTypeAcceptor: + s << "acceptor"; + break; + default: + s << "unknown"; + } + s << " [" << it->GetSocket ().remote_endpoint() << "]"; + s << "
" << std::endl; + } + } + } + } void HTTPConnection::StartAcceptingTunnels (std::stringstream& s) { diff --git a/HTTPServer.h b/HTTPServer.h index 658a9f7f..4db3486b 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -69,6 +69,8 @@ namespace util void ShowTransitTunnels (std::stringstream& s); void ShowLocalDestinations (std::stringstream& s); void ShowLocalDestination (const std::string& b32, std::stringstream& s); + void ShowSAMSessions (std::stringstream& s); + void ShowSAMSession (const std::string& id, std::stringstream& s); void StartAcceptingTunnels (std::stringstream& s); void StopAcceptingTunnels (std::stringstream& s); void FillContent (std::stringstream& s); diff --git a/SAM.cpp b/SAM.cpp index e2ba5c15..44702245 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -738,7 +738,7 @@ namespace client } } - SAMSession * SAMBridge::FindSession (const std::string& id) + SAMSession * SAMBridge::FindSession (const std::string& id) const { std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (id); diff --git a/SAM.h b/SAM.h index 0660f35c..07583153 100644 --- a/SAM.h +++ b/SAM.h @@ -79,6 +79,7 @@ namespace client boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; void ReceiveHandshake (); void SetSocketType (SAMSocketType socketType) { m_SocketType = socketType; }; + SAMSocketType GetSocketType () const { return m_SocketType; }; private: @@ -150,7 +151,7 @@ namespace client SAMSession * CreateSession (const std::string& id, const std::string& destination, // empty string means transient const std::map * params); void CloseSession (const std::string& id); - SAMSession * FindSession (const std::string& id); + SAMSession * FindSession (const std::string& id) const; private: @@ -170,9 +171,14 @@ namespace client boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::ip::udp::endpoint m_DatagramEndpoint, m_SenderEndpoint; boost::asio::ip::udp::socket m_DatagramSocket; - std::mutex m_SessionsMutex; + mutable std::mutex m_SessionsMutex; std::map m_Sessions; uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1]; + + public: + + // for HTTP + const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; }; } } From b187babd200ebb9968e365eba37dfe45dc18e04e Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 21 Feb 2015 22:07:55 -0500 Subject: [PATCH 0220/6300] 128 bytes key expansion --- Reseed.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 6d789509..c7347841 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -526,13 +526,13 @@ namespace data static uint8_t clientHello[] = { 0x16, // handshake - 0x03, 0x03, // version (TSL 1.2) + 0x03, 0x03, // version (TLS 1.2) 0x00, 0x2F, // length of handshake // handshake 0x01, // handshake type (client hello) 0x00, 0x00, 0x2B, // length of handshake payload // client hello - 0x03, 0x03, // highest version supported (TSL 1.2) + 0x03, 0x03, // highest version supported (TLS 1.2) 0x45, 0xFA, 0x01, 0x19, 0x74, 0x55, 0x18, 0x36, 0x42, 0x05, 0xC1, 0xDD, 0x4A, 0x21, 0x80, 0x80, 0xEC, 0x37, 0x11, 0x93, 0x16, 0xF4, 0x66, 0x00, @@ -547,8 +547,8 @@ namespace data static uint8_t changeCipherSpecs[] = { - 0x14, // change chiper specs - 0x03, 0x03, // version (TSL 1.2) + 0x14, // change cipher specs + 0x03, 0x03, // version (TLS 1.2) 0x00, 0x01, // length 0x01 // type }; @@ -556,7 +556,7 @@ namespace data static uint8_t finished[] = { 0x16, // handshake - 0x03, 0x03, // version (TSL 1.2) + 0x03, 0x03, // version (TLS 1.2) 0x00, 0x50, // length of handshake (80 bytes) // handshake (encrypted) // unencrypted context @@ -637,10 +637,10 @@ namespace data memcpy (random + 32, serverRandom, 32); PRF (secret, "master secret", random, 64, 48, masterSecret); // expand master secret - uint8_t keys[256]; // clientMACKey(32), serverMACKey(32), clientKey(32), serverKey(32) + uint8_t keys[128]; // clientMACKey(32), serverMACKey(32), clientKey(32), serverKey(32) memcpy (random, serverRandom, 32); memcpy (random + 32, clientHello + 11, 32); - PRF (masterSecret, "key expansion", random, 64, 256, keys); + PRF (masterSecret, "key expansion", random, 64, 128, keys); memcpy (m_MacKey, keys, 32); m_Encryption.SetKey (keys + 64); m_Decryption.SetKey (keys + 96); From 5b5c06179cdb19c1e96b7d64d4d647969bef1ef6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Feb 2015 09:12:43 -0500 Subject: [PATCH 0221/6300] original_at_mail.i2p.crt added --- .../router/orignal_at_mail.i2p.crt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 contrib/certificates/router/orignal_at_mail.i2p.crt diff --git a/contrib/certificates/router/orignal_at_mail.i2p.crt b/contrib/certificates/router/orignal_at_mail.i2p.crt new file mode 100644 index 00000000..c1229f3b --- /dev/null +++ b/contrib/certificates/router/orignal_at_mail.i2p.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFVDCCAzwCCQC2r1XWYtqtAzANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJY +WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMRMwEQYDVQQKDApQdXJwbGUgSTJQ +MQ0wCwYDVQQLDARJMlBEMR8wHQYJKoZIhvcNAQkBFhBvcmlnbmFsQG1haWwuaTJw +MB4XDTE1MDIyMjEzNTgxOFoXDTI1MDIxOTEzNTgxOFowbDELMAkGA1UEBhMCWFgx +CzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEGA1UECgwKUHVycGxlIEkyUDEN +MAsGA1UECwwESTJQRDEfMB0GCSqGSIb3DQEJARYQb3JpZ25hbEBtYWlsLmkycDCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALp3D/gdvFjrMm+IE8tHZCWE +hQ6Pp0CCgCGDBC3WQFLqR98bqVPl4UwRG/MKY/LY7Woai06JNmGcpfw0LMoNnHxT +bvKtDRe/8kQdhdLHhgIkWKSbMvTAl7uUdV6FzsPgDR0x7scoFVWEhkF0wfmzGF2V +yr/WCBQejFPu69z03m5tRQ8Xjp2txWV45RawUmFu50bgbZvLCSLfTkIvxmfJzgPN +pJ3sPa/g7TBZl2uEiAu4uaEKvTuuzStOWCGgFaHYFVlTfFXTvmhFMqHfaidtzrlu +H35WGrmIWTDl6uGPC5QkSppvkj73rDj5aEyPzWMz5DN3YeECoVSchN+OJJCM6m7+ +rLFYXghVEp2h+T9O1GBRfcHlQ2E3CrWWvxhmK8dfteJmd501dyNX2paeuIg/aPFO +54/8m2r11uyF29hgY8VWLdXtqvwhKuK36PCzofEwDp9QQX8GRsEV4pZTrn4bDhGo +kb9BF7TZTqtL3uyiRmIyBXrNNiYlA1Xm4fyKRtxl0mrPaUXdgdnCt3KxOAJ8WM2B +7L/kk9U8C/nexHbMxIZfTap49XcUg5dxSO9kOBosIOcCUms8sAzBPDV2tWAByhYF +jI/Tutbd3F0+fvcmTcIFOlGbOxKgO2SfwXjv/44g/3LMK6IAMFB9UOc8KhnnJP0f +uAHvMXn1ahRs4pM1VizLAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAIOxdaXT+wfu +nv/+1hy5T4TlRMNNsuj79ROcy6Mp+JwMG50HjTc0qTlXh8C7nHybDJn4v7DA+Nyn +RxT0J5I+Gqn+Na9TaC9mLeX/lwe8/KomyhBWxjrsyWj1V6v/cLO924S2rtcfzMDm +l3SFh9YHM1KF/R9N1XYBwtMzr3bupWDnE1yycYp1F4sMLr5SMzMQ0svQpQEM2/y5 +kly8+eUzryhm+ag9x1686uEG5gxhQ1eHQoZEaClHUOsV+28+d5If7cqcYx9Hf5Tt +CiVjJQzdxBF+6GeiJtKxnLtevqlkbyIJt6Cm9/7YIy/ovRGF2AKSYN6oCwmZQ6i1 +8nRnFq5zE7O94m+GXconWZxy0wVqA6472HThMi7S+Tk/eLYen2ilGY+KCb9a0FH5 +5MOuWSoJZ8/HfW2VeQmL8EjhWm5F2ybg28wgXK4BOGR3jQi03Fsc+AFidnWxSKo0 +aiJoPgOsfyu8/fnCcAi07kSmjzUKIWskApgcpGQLNXHFK9mtg7+VA8esRnfLlKtP +tJf+nNAPY1sqHfGBzh7WWGWal5RGHF5nEm3ta3oiFF5sMKCJ6C87zVwFkEcRytGC +xOGmiG1O1RPrO5NG7rZUaQ4y1OKl2Y1H+nGONzZ3mvoAOvxEq6JtUnU2kZscpPlk +fpeOSDoGBYJGbIpzDreBDhxaZrwGq36k +-----END CERTIFICATE----- From 621bfde961934428817d037371fb50f2127b5f17 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Feb 2015 17:04:42 -0500 Subject: [PATCH 0222/6300] handle incoming connection failure --- SAM.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index 44702245..3760fd95 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -680,8 +680,15 @@ namespace client { if (!ecode) { - LogPrint ("New SAM connection from ", socket->GetSocket ().remote_endpoint ()); - socket->ReceiveHandshake (); + boost::system::error_code ec; + auto ep = socket->GetSocket ().remote_endpoint (ec); + if (!ec) + { + LogPrint ("New SAM connection from ", ep); + socket->ReceiveHandshake (); + } + else + LogPrint (eLogError, "SAM connection from error ", ec.message ()); } else LogPrint ("SAM accept error: ", ecode.message ()); From 6401ed71ae695803c53f2fc2b0efaa606203394e Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Feb 2015 17:05:52 -0500 Subject: [PATCH 0223/6300] Update version.h 0.8.0 --- version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index 49613de6..64386003 100644 --- a/version.h +++ b/version.h @@ -2,7 +2,7 @@ #define _VERSION_H_ #define CODENAME "Purple" -#define VERSION "0.7.0" -#define I2P_VERSION "0.9.17" +#define VERSION "0.8.0" +#define I2P_VERSION "0.9.18" #endif From c46a82420db80989b7190d69d1894ad20e6489c4 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Feb 2015 14:41:56 -0500 Subject: [PATCH 0224/6300] show uptime --- HTTPServer.cpp | 6 +++++- RouterContext.cpp | 8 +++++++- RouterContext.h | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index c83894aa..fc2fc060 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "base64.h" #include "Log.h" #include "Tunnel.h" @@ -646,8 +647,11 @@ namespace util void HTTPConnection::FillContent (std::stringstream& s) { s << "

Welcome to the Webconsole!



"; + s << "Uptime: " << s << "Data path: " << i2p::util::filesystem::GetDataDir().string() << "
" << "
"; - s << "Our external address:" << "
"; + s << "Our external address:" << boost::posix_time::to_simple_string ( + boost::posix_time::time_duration (boost::posix_time::seconds ( + i2p::context.GetUptime ()))) << "

"; for (auto& address : i2p::context.GetRouterInfo().GetAddresses()) { switch (address.transportStyle) diff --git a/RouterContext.cpp b/RouterContext.cpp index 36cb06bf..d7e20aac 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -14,12 +14,13 @@ namespace i2p RouterContext::RouterContext (): m_LastUpdateTime (0), m_IsUnreachable (false), m_AcceptsTunnels (true), - m_IsFloodfill (false) + m_IsFloodfill (false), m_StartupTime (0) { } void RouterContext::Init () { + m_StartupTime = i2p::util::GetSecondsSinceEpoch (); if (!Load ()) CreateNewRouter (); UpdateRouterInfo (); @@ -218,5 +219,10 @@ namespace i2p void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); + } + + uint32_t RouterContext::GetUptime () const + { + return i2p::util::GetSecondsSinceEpoch () - m_StartupTime; } } diff --git a/RouterContext.h b/RouterContext.h index e483646c..0073d538 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -31,6 +31,7 @@ namespace i2p [](const i2p::data::RouterInfo *) {}); } CryptoPP::RandomNumberGenerator& GetRandomNumberGenerator () { return m_Rnd; }; + uint32_t GetUptime () const; void UpdatePort (int port); // called from Daemon void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon @@ -71,6 +72,7 @@ namespace i2p CryptoPP::AutoSeededRandomPool m_Rnd; uint64_t m_LastUpdateTime; bool m_IsUnreachable, m_AcceptsTunnels, m_IsFloodfill; + uint64_t m_StartupTime; // in seconds since epoch }; extern RouterContext context; From dc18c012ed53d2a4e8fc59e5959c2f7eb47e50b4 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Feb 2015 14:57:57 -0500 Subject: [PATCH 0225/6300] show uptime through I2PControl --- I2PControl.cpp | 6 ++++++ I2PControl.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/I2PControl.cpp b/I2PControl.cpp index c60104b9..0432ea34 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -35,6 +35,7 @@ namespace client m_MethodHandlers[I2P_CONTROL_METHOD_NETWORK_SETTING] = &I2PControlService::NetworkSettingHandler; // RouterInfo + m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_UPTIME] = &I2PControlService::UptimeHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = &I2PControlService::NetDbKnownPeersHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlService::NetDbActivePeersHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlService::TunnelsParticipatingHandler; @@ -291,6 +292,11 @@ namespace client } } + void I2PControlService::UptimeHandler (std::map& results) + { + results[I2P_CONTROL_ROUTER_INFO_UPTIME] = boost::lexical_cast(i2p::context.GetUptime ()*1000); + } + void I2PControlService::NetDbKnownPeersHandler (std::map& results) { results[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = boost::lexical_cast(i2p::data::netdb.GetNumRouters ()); diff --git a/I2PControl.h b/I2PControl.h index e032c6fc..ffd06dba 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -44,6 +44,7 @@ namespace client const char I2P_CONTROL_I2PCONTROL_PORT[] = "i2pcontrol.port"; // RouterInfo requests + const char I2P_CONTROL_ROUTER_INFO_UPTIME[] = "i2p.router.uptime"; const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; const char I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers"; const char I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating"; @@ -94,6 +95,7 @@ namespace client // RouterInfo typedef void (I2PControlService::*RouterInfoRequestHandler)(std::map& results); + void UptimeHandler (std::map& results); void NetDbKnownPeersHandler (std::map& results); void NetDbActivePeersHandler (std::map& results); void TunnelsParticipatingHandler (std::map& results); From 1a307f309314dcef4daa02858e26b498eeb9e00b Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Feb 2015 15:06:14 -0500 Subject: [PATCH 0226/6300] limit number of transit tunnels --- I2NPProtocol.cpp | 3 ++- I2NPProtocol.h | 2 ++ I2PControl.cpp | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 8326dc55..f2ef9ec1 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -280,7 +280,8 @@ namespace i2p i2p::crypto::ElGamalDecrypt (i2p::context.GetEncryptionPrivateKey (), record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText); // replace record to reply - if (i2p::context.AcceptsTunnels ()) + if (i2p::context.AcceptsTunnels () && + i2p::tunnel::tunnels.GetTransitTunnels ().size () <= MAX_NUM_TRANSIT_TUNNELS) { i2p::tunnel::TransitTunnel * transitTunnel = i2p::tunnel::CreateTransitTunnel ( diff --git a/I2NPProtocol.h b/I2NPProtocol.h index de91df73..5bb94062 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -97,6 +97,8 @@ namespace i2p const uint8_t DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP = 0x08; // 1000 const uint8_t DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP = 0x0C; // 1100 + const int MAX_NUM_TRANSIT_TUNNELS = 1500; + namespace tunnel { class InboundTunnel; diff --git a/I2PControl.cpp b/I2PControl.cpp index 0432ea34..4183476d 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -309,7 +309,7 @@ namespace client void I2PControlService::TunnelsParticipatingHandler (std::map& results) { - results[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = boost::lexical_cast(i2p::tunnel::tunnels.GetTransitTunnels ().size ());; + results[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = boost::lexical_cast(i2p::tunnel::tunnels.GetTransitTunnels ().size ()); } // RouterManager From d08d1c91271b12f4deecd775a621326f376d0e84 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Feb 2015 17:54:51 -0500 Subject: [PATCH 0227/6300] show uptime properly --- HTTPServer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index fc2fc060..adf62298 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -646,12 +646,12 @@ namespace util void HTTPConnection::FillContent (std::stringstream& s) { - s << "

Welcome to the Webconsole!



"; - s << "Uptime: " << - s << "Data path: " << i2p::util::filesystem::GetDataDir().string() << "
" << "
"; - s << "Our external address:" << boost::posix_time::to_simple_string ( + s << "

Welcome to the Webconsole!


"; + s << "Uptime: " << boost::posix_time::to_simple_string ( boost::posix_time::time_duration (boost::posix_time::seconds ( - i2p::context.GetUptime ()))) << "

"; + i2p::context.GetUptime ()))) << "
"; + s << "Data path: " << i2p::util::filesystem::GetDataDir().string() << "

"; + s << "Our external address:" << "
" ; for (auto& address : i2p::context.GetRouterInfo().GetAddresses()) { switch (address.transportStyle) From 58ebd8cc5915da9e46431081a6b652cc67699ac9 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 24 Feb 2015 11:58:39 -0500 Subject: [PATCH 0228/6300] 1 hours expiration of RI in case of floodfill --- NetDb.cpp | 26 ++++++++++++++++++++------ RouterContext.h | 1 + 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index f417809b..e2d1d3b5 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -410,16 +410,30 @@ namespace data else { // RouterInfo expires after 1 hour if uses introducer - if ((it.second->UsesIntroducer () && ts > it.second->GetTimestamp () + 3600*1000LL) // 1 hour - // RouterInfo expires in 72 hours if more than 300 - || (total > 300 && ts > it.second->GetTimestamp () + 3*24*3600*1000LL)) // 3 days - { - total--; + if (it.second->UsesIntroducer () && ts > it.second->GetTimestamp () + 3600*1000LL) // 1 hour it.second->SetUnreachable (true); - } + else if (total > 25 && ts > (i2p::context.GetStartupTime () + 600)*1000LL) // routers don't expire if less than 25 or uptime is less than 10 minutes + { + if (i2p::context.IsFloodfill ()) + { + if (ts > it.second->GetTimestamp () + 3600*1000LL) // 1 hours + it.second->SetUnreachable (true); + } + else if (total > 300) + { + if (ts > it.second->GetTimestamp () + 30*3600*1000LL) // 30 hours + it.second->SetUnreachable (true); + } + else if (total > 120) + { + if (ts > it.second->GetTimestamp () + 72*3600*1000LL) // 72 hours + it.second->SetUnreachable (true); + } + } if (it.second->IsUnreachable ()) { + total--; // delete RI file if (boost::filesystem::exists (GetFilePath (fullDirectory, it.second.get ()))) { diff --git a/RouterContext.h b/RouterContext.h index 0073d538..c2924814 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -32,6 +32,7 @@ namespace i2p } CryptoPP::RandomNumberGenerator& GetRandomNumberGenerator () { return m_Rnd; }; uint32_t GetUptime () const; + uint32_t GetStartupTime () const { return m_StartupTime; }; void UpdatePort (int port); // called from Daemon void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon From 52f806ff945b113b0fde4f415f95a46b94ece005 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 24 Feb 2015 15:40:50 -0500 Subject: [PATCH 0229/6300] use shared_ptr for ClientDestination --- BOB.cpp | 16 ++++++++-------- BOB.h | 12 ++++++------ ClientContext.cpp | 28 ++++++++++++---------------- ClientContext.h | 16 ++++++++-------- I2PService.cpp | 2 +- I2PService.h | 10 +++++----- I2PTunnel.cpp | 4 ++-- I2PTunnel.h | 4 ++-- SAM.cpp | 4 ++-- SAM.h | 4 ++-- 10 files changed, 48 insertions(+), 52 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index 3b31569d..2f881a69 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -8,7 +8,7 @@ namespace i2p { namespace client { - BOBI2PInboundTunnel::BOBI2PInboundTunnel (int port, ClientDestination * localDestination): + BOBI2PInboundTunnel::BOBI2PInboundTunnel (int port, std::shared_ptr localDestination): BOBI2PTunnel (localDestination), m_Acceptor (localDestination->GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), m_Timer (localDestination->GetService ()) { @@ -142,7 +142,7 @@ namespace client } BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& address, int port, - ClientDestination * localDestination, bool quiet): BOBI2PTunnel (localDestination), + std::shared_ptr localDestination, bool quiet): BOBI2PTunnel (localDestination), m_Endpoint (boost::asio::ip::address::from_string (address), port), m_IsQuiet (quiet) { } @@ -176,7 +176,7 @@ namespace client } } - BOBDestination::BOBDestination (ClientDestination& localDestination): + BOBDestination::BOBDestination (std::shared_ptr localDestination): m_LocalDestination (localDestination), m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr) { @@ -186,7 +186,7 @@ namespace client { delete m_OutboundTunnel; delete m_InboundTunnel; - i2p::client::context.DeleteLocalDestination (&m_LocalDestination); + i2p::client::context.DeleteLocalDestination (m_LocalDestination); } void BOBDestination::Start () @@ -198,7 +198,7 @@ namespace client void BOBDestination::Stop () { StopTunnels (); - m_LocalDestination.Stop (); + m_LocalDestination->Stop (); } void BOBDestination::StopTunnels () @@ -220,13 +220,13 @@ namespace client void BOBDestination::CreateInboundTunnel (int port) { if (!m_InboundTunnel) - m_InboundTunnel = new BOBI2PInboundTunnel (port, &m_LocalDestination); + m_InboundTunnel = new BOBI2PInboundTunnel (port, m_LocalDestination); } void BOBDestination::CreateOutboundTunnel (const std::string& address, int port, bool quiet) { if (!m_OutboundTunnel) - m_OutboundTunnel = new BOBI2POutboundTunnel (address, port, &m_LocalDestination, quiet); + m_OutboundTunnel = new BOBI2POutboundTunnel (address, port, m_LocalDestination, quiet); } BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner): @@ -384,7 +384,7 @@ namespace client LogPrint (eLogDebug, "BOB: start ", m_Nickname); if (!m_CurrentDestination) { - m_CurrentDestination = new BOBDestination (*i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options)); + m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options)); m_Owner.AddDestination (m_Nickname, m_CurrentDestination); } if (m_InPort) diff --git a/BOB.h b/BOB.h index 5858cdb8..cd42c9b8 100644 --- a/BOB.h +++ b/BOB.h @@ -46,7 +46,7 @@ namespace client { public: - BOBI2PTunnel (ClientDestination * localDestination): + BOBI2PTunnel (std::shared_ptr localDestination): I2PService (localDestination) {}; virtual void Start () {}; @@ -67,7 +67,7 @@ namespace client public: - BOBI2PInboundTunnel (int port, ClientDestination * localDestination); + BOBI2PInboundTunnel (int port, std::shared_ptr localDestination); ~BOBI2PInboundTunnel (); void Start (); @@ -96,7 +96,7 @@ namespace client { public: - BOBI2POutboundTunnel (const std::string& address, int port, ClientDestination * localDestination, bool quiet); + BOBI2POutboundTunnel (const std::string& address, int port, std::shared_ptr localDestination, bool quiet); void Start (); void Stop (); @@ -119,7 +119,7 @@ namespace client { public: - BOBDestination (ClientDestination& localDestination); + BOBDestination (std::shared_ptr localDestination); ~BOBDestination (); void Start (); @@ -127,11 +127,11 @@ namespace client void StopTunnels (); void CreateInboundTunnel (int port); void CreateOutboundTunnel (const std::string& address, int port, bool quiet); - const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination.GetPrivateKeys (); }; + const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); }; private: - ClientDestination& m_LocalDestination; + std::shared_ptr m_LocalDestination; BOBI2POutboundTunnel * m_OutboundTunnel; BOBI2PInboundTunnel * m_InboundTunnel; }; diff --git a/ClientContext.cpp b/ClientContext.cpp index a5bb6298..fb6e4791 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -49,7 +49,7 @@ namespace client std::string ircDestination = i2p::util::config::GetArg("-ircdest", ""); if (ircDestination.length () > 0) // ircdest is presented { - ClientDestination * localDestination = nullptr; + std::shared_ptr localDestination = nullptr; std::string ircKeys = i2p::util::config::GetArg("-irckeys", ""); if (ircKeys.length () > 0) localDestination = LoadLocalDestination (ircKeys, false); @@ -146,15 +146,12 @@ namespace client } for (auto it: m_Destinations) - { it.second->Stop (); - delete it.second; - } m_Destinations.clear (); - m_SharedLocalDestination = 0; // deleted through m_Destination + m_SharedLocalDestination = nullptr; } - ClientDestination * ClientContext::LoadLocalDestination (const std::string& filename, bool isPublic) + std::shared_ptr ClientContext::LoadLocalDestination (const std::string& filename, bool isPublic) { i2p::data::PrivateKeys keys; std::string fullPath = i2p::util::filesystem::GetFullPath (filename); @@ -184,7 +181,7 @@ namespace client LogPrint ("New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " created"); } - ClientDestination * localDestination = nullptr; + std::shared_ptr localDestination = nullptr; std::unique_lock l(m_DestinationsMutex); auto it = m_Destinations.find (keys.GetPublic ().GetIdentHash ()); if (it != m_Destinations.end ()) @@ -194,25 +191,25 @@ namespace client } else { - localDestination = new ClientDestination (keys, isPublic); + localDestination = std::make_shared (keys, isPublic); m_Destinations[localDestination->GetIdentHash ()] = localDestination; localDestination->Start (); } return localDestination; } - ClientDestination * ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, + std::shared_ptr ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, const std::map * params) { i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType); - auto localDestination = new ClientDestination (keys, isPublic, params); + auto localDestination = std::make_shared (keys, isPublic, params); std::unique_lock l(m_DestinationsMutex); m_Destinations[localDestination->GetIdentHash ()] = localDestination; localDestination->Start (); return localDestination; } - void ClientContext::DeleteLocalDestination (ClientDestination * destination) + void ClientContext::DeleteLocalDestination (std::shared_ptr destination) { if (!destination) return; auto it = m_Destinations.find (destination->GetIdentHash ()); @@ -224,11 +221,10 @@ namespace client m_Destinations.erase (it); } d->Stop (); - delete d; } } - ClientDestination * ClientContext::CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, + std::shared_ptr ClientContext::CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { auto it = m_Destinations.find (keys.GetPublic ().GetIdentHash ()); @@ -242,14 +238,14 @@ namespace client } return nullptr; } - auto localDestination = new ClientDestination (keys, isPublic, params); + auto localDestination = std::make_shared (keys, isPublic, params); std::unique_lock l(m_DestinationsMutex); m_Destinations[keys.GetPublic ().GetIdentHash ()] = localDestination; localDestination->Start (); return localDestination; } - ClientDestination * ClientContext::FindLocalDestination (const i2p::data::IdentHash& destination) const + std::shared_ptr ClientContext::FindLocalDestination (const i2p::data::IdentHash& destination) const { auto it = m_Destinations.find (destination); if (it != m_Destinations.end ()) @@ -299,7 +295,7 @@ namespace client for (int i = 0; i < numClientTunnels; i++) { - ClientDestination * localDestination = nullptr; + std::shared_ptr localDestination = nullptr; if (keys[i].length () > 0) localDestination = LoadLocalDestination (keys[i], false); auto clientTunnel = new I2PClientTunnel (destinations[i], ports[i], localDestination); diff --git a/ClientContext.h b/ClientContext.h index 6da2cd30..007aa359 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -37,14 +37,14 @@ namespace client void Start (); void Stop (); - ClientDestination * GetSharedLocalDestination () const { return m_SharedLocalDestination; }; - ClientDestination * CreateNewLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1, + std::shared_ptr GetSharedLocalDestination () const { return m_SharedLocalDestination; }; + std::shared_ptr CreateNewLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1, const std::map * params = nullptr); // transient - ClientDestination * CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, + std::shared_ptr CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); - void DeleteLocalDestination (ClientDestination * destination); - ClientDestination * FindLocalDestination (const i2p::data::IdentHash& destination) const; - ClientDestination * LoadLocalDestination (const std::string& filename, bool isPublic); + void DeleteLocalDestination (std::shared_ptr destination); + std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; + std::shared_ptr LoadLocalDestination (const std::string& filename, bool isPublic); AddressBook& GetAddressBook () { return m_AddressBook; }; const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; @@ -56,8 +56,8 @@ namespace client private: std::mutex m_DestinationsMutex; - std::map m_Destinations; - ClientDestination * m_SharedLocalDestination; + std::map > m_Destinations; + std::shared_ptr m_SharedLocalDestination; AddressBook m_AddressBook; diff --git a/I2PService.cpp b/I2PService.cpp index 69cee2bc..6b74fe1a 100644 --- a/I2PService.cpp +++ b/I2PService.cpp @@ -10,7 +10,7 @@ namespace client { static const i2p::data::SigningKeyType I2P_SERVICE_DEFAULT_KEY_TYPE = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; - I2PService::I2PService (ClientDestination * localDestination): + I2PService::I2PService (std::shared_ptr localDestination): m_LocalDestination (localDestination ? localDestination : i2p::client::context.CreateNewLocalDestination (false, I2P_SERVICE_DEFAULT_KEY_TYPE)) { diff --git a/I2PService.h b/I2PService.h index a03f16ed..1e4fc10b 100644 --- a/I2PService.h +++ b/I2PService.h @@ -17,7 +17,7 @@ namespace client class I2PService { public: - I2PService (ClientDestination * localDestination = nullptr); + I2PService (std::shared_ptr localDestination = nullptr); I2PService (i2p::data::SigningKeyType kt); virtual ~I2PService () { ClearHandlers (); } @@ -37,8 +37,8 @@ namespace client m_Handlers.clear(); } - inline ClientDestination * GetLocalDestination () { return m_LocalDestination; } - inline void SetLocalDestination (ClientDestination * dest) { m_LocalDestination = dest; } + inline std::shared_ptr GetLocalDestination () { return m_LocalDestination; } + inline void SetLocalDestination (std::shared_ptr dest) { m_LocalDestination = dest; } void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port = 0); inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); } @@ -49,7 +49,7 @@ namespace client virtual const char* GetName() { return "Generic I2P Service"; } private: - ClientDestination * m_LocalDestination; + std::shared_ptr m_LocalDestination; std::unordered_set > m_Handlers; std::mutex m_HandlersMutex; }; @@ -81,7 +81,7 @@ namespace client class TCPIPAcceptor: public I2PService { public: - TCPIPAcceptor (int port, ClientDestination * localDestination = nullptr) : + TCPIPAcceptor (int port, std::shared_ptr localDestination = nullptr) : I2PService(localDestination), m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), m_Timer (GetService ()) {} diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 8177b18e..15d3b6ad 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -202,7 +202,7 @@ namespace client Done(shared_from_this()); } - I2PClientTunnel::I2PClientTunnel (const std::string& destination, int port, ClientDestination * localDestination): + I2PClientTunnel::I2PClientTunnel (const std::string& destination, int port, std::shared_ptr localDestination): TCPIPAcceptor (port,localDestination), m_Destination (destination), m_DestinationIdentHash (nullptr) {} @@ -243,7 +243,7 @@ namespace client return nullptr; } - I2PServerTunnel::I2PServerTunnel (const std::string& address, int port, ClientDestination * localDestination): + I2PServerTunnel::I2PServerTunnel (const std::string& address, int port, std::shared_ptr localDestination): I2PService (localDestination), m_Endpoint (boost::asio::ip::address::from_string (address), port) { } diff --git a/I2PTunnel.h b/I2PTunnel.h index 51de3b73..d4be85c7 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -65,7 +65,7 @@ namespace client public: - I2PClientTunnel (const std::string& destination, int port, ClientDestination * localDestination = nullptr); + I2PClientTunnel (const std::string& destination, int port, std::shared_ptr localDestination = nullptr); ~I2PClientTunnel () {} void Start (); @@ -83,7 +83,7 @@ namespace client { public: - I2PServerTunnel (const std::string& address, int port, ClientDestination * localDestination); + I2PServerTunnel (const std::string& address, int port, std::shared_ptr localDestination); void Start (); void Stop (); diff --git a/SAM.cpp b/SAM.cpp index 3760fd95..5f4c23af 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -595,7 +595,7 @@ namespace client LogPrint (eLogWarning, "Datagram size ", len," exceeds buffer"); } - SAMSession::SAMSession (ClientDestination * dest): + SAMSession::SAMSession (std::shared_ptr dest): localDestination (dest) { } @@ -700,7 +700,7 @@ namespace client SAMSession * SAMBridge::CreateSession (const std::string& id, const std::string& destination, const std::map * params) { - ClientDestination * localDestination = nullptr; + std::shared_ptr localDestination = nullptr; if (destination != "") { i2p::data::PrivateKeys keys; diff --git a/SAM.h b/SAM.h index 07583153..5bc1320a 100644 --- a/SAM.h +++ b/SAM.h @@ -128,10 +128,10 @@ namespace client struct SAMSession { - ClientDestination * localDestination; + std::shared_ptr localDestination; std::list > sockets; - SAMSession (ClientDestination * localDestination); + SAMSession (std::shared_ptr dest); ~SAMSession (); void CloseStreams (); From eee968ce56342be60e7876f11e4e29edd93aa753 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 24 Feb 2015 22:17:46 -0500 Subject: [PATCH 0230/6300] fixed incorrect instantiation in gcc 4.6 --- SSUSession.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index a6fc2b52..4835abeb 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -223,7 +223,7 @@ namespace transport s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 else s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP v6 - s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port + s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port s.Insert (payload, 8); // relayTag and signed on time m_RelayTag = bufbe32toh (payload); payload += 4; // relayTag @@ -370,7 +370,7 @@ namespace transport s.Insert (address->host.to_v4 ().to_bytes ().data (), 4); // our IP V4 else s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6 - s.Insert (htobe16 (address->port)); // our port + s.Insert (htobe16 (address->port)); // our port uint32_t relayTag = 0; if (i2p::context.GetRouterInfo ().IsIntroducer ()) { @@ -431,7 +431,7 @@ namespace transport s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP V4 else s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP V6 - s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port + s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port s.Insert (htobe32 (m_RelayTag)); // relay tag s.Insert (htobe32 (signedOnTime)); // signed on time s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature From e250628174dc24e4867ac4bd6aa40ddfbe36b03d Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 Feb 2015 15:26:06 -0500 Subject: [PATCH 0231/6300] global peer tests --- SSU.cpp | 10 ++++++++++ SSU.h | 6 +++++- SSUSession.cpp | 5 +++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/SSU.cpp b/SSU.cpp index 20c86a81..39197293 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -461,6 +461,16 @@ namespace transport m_Introducers = newList; ScheduleIntroducersUpdateTimer (); } + } + + void SSUServer::NewPeerTest (uint32_t nonce) + { + m_PeerTests[nonce] = i2p::util::GetMillisecondsSinceEpoch (); + } + + void SSUServer::PeerTestComplete (uint32_t nonce) + { + m_PeerTests.erase (nonce); } } } diff --git a/SSU.h b/SSU.h index 70cea541..12cbb775 100644 --- a/SSU.h +++ b/SSU.h @@ -53,6 +53,9 @@ namespace transport void AddRelay (uint32_t tag, const boost::asio::ip::udp::endpoint& relay); std::shared_ptr FindRelaySession (uint32_t tag); + void NewPeerTest (uint32_t nonce); + void PeerTestComplete (uint32_t nonce); + private: void Run (); @@ -70,7 +73,7 @@ namespace transport std::set FindIntroducers (int maxNumIntroducers); void ScheduleIntroducersUpdateTimer (); void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode); - + private: bool m_IsRunning; @@ -84,6 +87,7 @@ namespace transport std::mutex m_SessionsMutex; std::map > m_Sessions; std::map m_Relays; // we are introducer + std::map m_PeerTests; // nonce -> creation time in milliseconds public: // for HTTP only diff --git a/SSUSession.cpp b/SSUSession.cpp index 4835abeb..72ede036 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -942,7 +942,10 @@ namespace transport } } else + { LogPrint (eLogDebug, "SSU peer test from Charlie. We are Alice"); + m_Server.PeerTestComplete (nonce); + } } } @@ -989,6 +992,7 @@ namespace transport void SSUSession::SendPeerTest () { + // we are Alice LogPrint (eLogDebug, "SSU sending peer test"); auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); if (!address) @@ -999,6 +1003,7 @@ namespace transport uint32_t nonce = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); if (!nonce) nonce = 1; m_PeerTestNonces.insert (nonce); + m_Server.NewPeerTest (nonce); SendPeerTest (nonce, 0, 0, address->key, false); // address and port always zero for Alice } From 34e31f3d7876327f8baac5bf94d9ed2c5f66bac7 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 Feb 2015 16:39:48 -0500 Subject: [PATCH 0232/6300] PeerTest from Alice to Charlie --- SSUSession.cpp | 59 ++++++++++++++++++++++++++++++++++---------------- SSUSession.h | 2 +- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index 72ede036..42c6a17f 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -898,26 +898,35 @@ namespace transport } if (m_PeerTestNonces.count (nonce) > 0) { - // existing test - if (m_PeerTest) + // existing test + if (m_PeerTest) // Alice { - LogPrint (eLogDebug, "SSU peer test from Bob. We are Alice"); - m_PeerTestNonces.erase (nonce); - m_PeerTest = false; + if (m_State == eSessionStateEstablished) + LogPrint (eLogDebug, "SSU peer test from Bob. We are Alice"); + else + { + LogPrint (eLogDebug, "SSU first peer test from Charlie. We are Alice"); + m_PeerTest = false; + m_PeerTestNonces.erase (nonce); + m_Server.PeerTestComplete (nonce); + SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), + senderEndpoint.port (), introKey, true, false); // to Charlie + } } - else if (port) + else if (port) // Bob { LogPrint (eLogDebug, "SSU peer test from Charlie. We are Bob"); + m_PeerTestNonces.erase (nonce); // nonce has been used boost::asio::ip::udp::endpoint ep (boost::asio::ip::address_v4 (be32toh (address)), be16toh (port)); // Alice's address/port auto session = m_Server.FindSession (ep); // find session with Alice if (session) session->Send (PAYLOAD_TYPE_PEER_TEST, buf1, len); // back to Alice } - else + else // Charlie { LogPrint (eLogDebug, "SSU peer test from Alice. We are Charlie"); SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), - senderEndpoint.port (), introKey); // to Alice + senderEndpoint.port (), introKey); // to Alice with her actual address } } else @@ -930,34 +939,34 @@ namespace transport { LogPrint (eLogDebug, "SSU peer test from Bob. We are Charlie"); Send (PAYLOAD_TYPE_PEER_TEST, buf1, len); // back to Bob - SendPeerTest (nonce, be32toh (address), be16toh (port), introKey); // to Alice + SendPeerTest (nonce, be32toh (address), be16toh (port), introKey); // to Alice with her address received from Bob } else { LogPrint (eLogDebug, "SSU peer test from Alice. We are Bob"); - auto session = m_Server.GetRandomEstablishedSession (shared_from_this ()); // charlie + auto session = m_Server.GetRandomEstablishedSession (shared_from_this ()); // Charlie if (session) session->SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), - senderEndpoint.port (), introKey, false); + senderEndpoint.port (), introKey, false); // to Charlie with Alice's actual address } } else - { - LogPrint (eLogDebug, "SSU peer test from Charlie. We are Alice"); - m_Server.PeerTestComplete (nonce); - } + LogPrint (eLogDebug, "SSU second peer test from Charlie. We are Alice"); } } void SSUSession::SendPeerTest (uint32_t nonce, uint32_t address, uint16_t port, - const uint8_t * introKey, bool toAddress) + const uint8_t * introKey, bool toAddress, bool sendAddress) + // toAddress is true for Alice<->Chalie communications only + // sendAddress is false if message comes from Alice { uint8_t buf[80 + 18]; uint8_t iv[16]; uint8_t * payload = buf + sizeof (SSUHeader); htobe32buf (payload, nonce); payload += 4; // nonce - if (address) + // address and port + if (sendAddress && address) { *payload = 4; payload++; // size @@ -971,8 +980,20 @@ namespace transport } htobe16buf (payload, port); payload += 2; // port - memcpy (payload, introKey, 32); // intro key + // intro key + if (toAddress) + { + // send our intro key to address instead it's own + auto addr = i2p::context.GetRouterInfo ().GetSSUAddress (); + if (addr) + memcpy (payload, addr->key, 32); // intro key + else + LogPrint (eLogError, "SSU is not supported. Can't send peer test"); + } + else + memcpy (payload, introKey, 32); // intro key + // send CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); rnd.GenerateBlock (iv, 16); // random iv if (toAddress) @@ -1004,7 +1025,7 @@ namespace transport if (!nonce) nonce = 1; m_PeerTestNonces.insert (nonce); m_Server.NewPeerTest (nonce); - SendPeerTest (nonce, 0, 0, address->key, false); // address and port always zero for Alice + SendPeerTest (nonce, 0, 0, address->key, false, false); // address and port always zero for Alice } void SSUSession::SendKeepAlive () diff --git a/SSUSession.h b/SSUSession.h index e8ca5a7f..a8e5c54a 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -107,7 +107,7 @@ namespace transport void ScheduleConnectTimer (); void HandleConnectTimer (const boost::system::error_code& ecode); void ProcessPeerTest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); - void SendPeerTest (uint32_t nonce, uint32_t address, uint16_t port, const uint8_t * introKey, bool toAddress = true); + void SendPeerTest (uint32_t nonce, uint32_t address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true); void ProcessData (uint8_t * buf, size_t len); void SendSesionDestroyed (); void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key From 51aea367c343bd7660770831cb91b11738d53f77 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 Feb 2015 21:56:51 -0500 Subject: [PATCH 0233/6300] full implementation of peer test --- SSU.cpp | 22 +++++++++++-- SSU.h | 14 +++++++-- SSUSession.cpp | 84 ++++++++++++++++++++++++++++++-------------------- SSUSession.h | 10 +++++- 4 files changed, 90 insertions(+), 40 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 39197293..c735d912 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -463,12 +463,28 @@ namespace transport } } - void SSUServer::NewPeerTest (uint32_t nonce) + void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role) { - m_PeerTests[nonce] = i2p::util::GetMillisecondsSinceEpoch (); + m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role }; } - void SSUServer::PeerTestComplete (uint32_t nonce) + PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce) + { + auto it = m_PeerTests.find (nonce); + if (it != m_PeerTests.end ()) + return it->second.role; + else + return ePeerTestParticipantUnknown; + } + + void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role) + { + auto it = m_PeerTests.find (nonce); + if (it != m_PeerTests.end ()) + it->second.role = role; + } + + void SSUServer::RemovePeerTest (uint32_t nonce) { m_PeerTests.erase (nonce); } diff --git a/SSU.h b/SSU.h index 12cbb775..f5f3cc87 100644 --- a/SSU.h +++ b/SSU.h @@ -53,8 +53,10 @@ namespace transport void AddRelay (uint32_t tag, const boost::asio::ip::udp::endpoint& relay); std::shared_ptr FindRelaySession (uint32_t tag); - void NewPeerTest (uint32_t nonce); - void PeerTestComplete (uint32_t nonce); + void NewPeerTest (uint32_t nonce, PeerTestParticipant role); + PeerTestParticipant GetPeerTestParticipant (uint32_t nonce); + void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role); + void RemovePeerTest (uint32_t nonce); private: @@ -76,6 +78,12 @@ namespace transport private: + struct PeerTest + { + uint64_t creationTime; + PeerTestParticipant role; + }; + bool m_IsRunning; std::thread * m_Thread, * m_ThreadV6, * m_ReceiversThread; boost::asio::io_service m_Service, m_ServiceV6, m_ReceiversService; @@ -87,7 +95,7 @@ namespace transport std::mutex m_SessionsMutex; std::map > m_Sessions; std::map m_Relays; // we are introducer - std::map m_PeerTests; // nonce -> creation time in milliseconds + std::map m_PeerTests; // nonce -> creation time in milliseconds public: // for HTTP only diff --git a/SSUSession.cpp b/SSUSession.cpp index 42c6a17f..4e6f85f5 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -896,62 +896,80 @@ namespace transport LogPrint (eLogWarning, "Address of ", size, " bytes not supported"); return; } - if (m_PeerTestNonces.count (nonce) > 0) - { + switch (m_Server.GetPeerTestParticipant (nonce)) + { // existing test - if (m_PeerTest) // Alice - { + case ePeerTestParticipantAlice1: + { if (m_State == eSessionStateEstablished) LogPrint (eLogDebug, "SSU peer test from Bob. We are Alice"); else { LogPrint (eLogDebug, "SSU first peer test from Charlie. We are Alice"); - m_PeerTest = false; - m_PeerTestNonces.erase (nonce); - m_Server.PeerTestComplete (nonce); + m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2); SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), senderEndpoint.port (), introKey, true, false); // to Charlie } - } - else if (port) // Bob + break; + } + case ePeerTestParticipantAlice2: + { + if (m_State == eSessionStateEstablished) + LogPrint (eLogDebug, "SSU peer test from Bob. We are Alice"); + else + { + // peer test successive + LogPrint (eLogDebug, "SSU second peer test from Charlie. We are Alice"); + m_Server.RemovePeerTest (nonce); + } + break; + } + case ePeerTestParticipantBob: { LogPrint (eLogDebug, "SSU peer test from Charlie. We are Bob"); - m_PeerTestNonces.erase (nonce); // nonce has been used + m_Server.RemovePeerTest (nonce); // nonce has been used boost::asio::ip::udp::endpoint ep (boost::asio::ip::address_v4 (be32toh (address)), be16toh (port)); // Alice's address/port auto session = m_Server.FindSession (ep); // find session with Alice if (session) session->Send (PAYLOAD_TYPE_PEER_TEST, buf1, len); // back to Alice + break; } - else // Charlie - { + case ePeerTestParticipantCharlie: + { LogPrint (eLogDebug, "SSU peer test from Alice. We are Charlie"); + m_Server.RemovePeerTest (nonce); // nonce has been used SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), senderEndpoint.port (), introKey); // to Alice with her actual address + break; } - } - else - { - if (m_State == eSessionStateEstablished) + // test not found + case ePeerTestParticipantUnknown: { - // new test - m_PeerTestNonces.insert (nonce); - if (port) + if (m_State == eSessionStateEstablished) { - LogPrint (eLogDebug, "SSU peer test from Bob. We are Charlie"); - Send (PAYLOAD_TYPE_PEER_TEST, buf1, len); // back to Bob - SendPeerTest (nonce, be32toh (address), be16toh (port), introKey); // to Alice with her address received from Bob + // new test + if (port) + { + LogPrint (eLogDebug, "SSU peer test from Bob. We are Charlie"); + m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); + Send (PAYLOAD_TYPE_PEER_TEST, buf1, len); // back to Bob + SendPeerTest (nonce, be32toh (address), be16toh (port), introKey); // to Alice with her address received from Bob + } + else + { + LogPrint (eLogDebug, "SSU peer test from Alice. We are Bob"); + auto session = m_Server.GetRandomEstablishedSession (shared_from_this ()); // Charlie + if (session) + { + m_Server.NewPeerTest (nonce, ePeerTestParticipantBob); + session->SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), + senderEndpoint.port (), introKey, false); // to Charlie with Alice's actual address + } + } } else - { - LogPrint (eLogDebug, "SSU peer test from Alice. We are Bob"); - auto session = m_Server.GetRandomEstablishedSession (shared_from_this ()); // Charlie - if (session) - session->SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), - senderEndpoint.port (), introKey, false); // to Charlie with Alice's actual address - } + LogPrint (eLogError, "SSU unexpected peer test"); } - else - LogPrint (eLogDebug, "SSU second peer test from Charlie. We are Alice"); } } @@ -1023,8 +1041,8 @@ namespace transport } uint32_t nonce = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); if (!nonce) nonce = 1; - m_PeerTestNonces.insert (nonce); - m_Server.NewPeerTest (nonce); + m_PeerTest = false; + m_Server.NewPeerTest (nonce, ePeerTestParticipantAlice1); SendPeerTest (nonce, 0, 0, address->key, false, false); // address and port always zero for Alice } diff --git a/SSUSession.h b/SSUSession.h index a8e5c54a..0889f416 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -49,6 +49,15 @@ namespace transport eSessionStateFailed }; + enum PeerTestParticipant + { + ePeerTestParticipantUnknown = 0, + ePeerTestParticipantAlice1, + ePeerTestParticipantAlice2, + ePeerTestParticipantBob, + ePeerTestParticipantCharlie + }; + class SSUServer; class SSUSession: public TransportSession, public std::enable_shared_from_this { @@ -133,7 +142,6 @@ namespace transport SessionState m_State; bool m_IsSessionKey; uint32_t m_RelayTag; - std::set m_PeerTestNonces; i2p::crypto::CBCEncryption m_SessionKeyEncryption; i2p::crypto::CBCDecryption m_SessionKeyDecryption; i2p::crypto::AESKey m_SessionKey; From 30715c251285c0aa43c4d5a68e848813548876cb Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Feb 2015 13:44:18 -0500 Subject: [PATCH 0234/6300] firewall detection --- HTTPServer.cpp | 9 +++++++++ RouterContext.cpp | 2 +- RouterContext.h | 12 +++++++++++- SSUSession.cpp | 6 ++++++ Transports.cpp | 1 + 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index adf62298..01907c25 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -650,6 +650,15 @@ namespace util s << "Uptime: " << boost::posix_time::to_simple_string ( boost::posix_time::time_duration (boost::posix_time::seconds ( i2p::context.GetUptime ()))) << "
"; + s << "Status: "; + switch (i2p::context.GetStatus ()) + { + case eRouterStatusOK: s << "OK"; break; + case eRouterStatusTesting: s << "Testing"; break; + case eRouterStatusFirewalled: s << "Firewalled"; break; + default: s << "Unknown"; + } + s << "
"; s << "Data path: " << i2p::util::filesystem::GetDataDir().string() << "

"; s << "Our external address:" << "
" ; for (auto& address : i2p::context.GetRouterInfo().GetAddresses()) diff --git a/RouterContext.cpp b/RouterContext.cpp index d7e20aac..3a894e6b 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -14,7 +14,7 @@ namespace i2p RouterContext::RouterContext (): m_LastUpdateTime (0), m_IsUnreachable (false), m_AcceptsTunnels (true), - m_IsFloodfill (false), m_StartupTime (0) + m_IsFloodfill (false), m_StartupTime (0), m_Status (eRouterStatusOK ) { } diff --git a/RouterContext.h b/RouterContext.h index c2924814..f1e8056c 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -17,6 +17,13 @@ namespace i2p const char ROUTER_KEYS[] = "router.keys"; const int ROUTER_INFO_UPDATE_INTERVAL = 1800; // 30 minutes + enum RouterStatus + { + eRouterStatusOK = 0, + eRouterStatusTesting = 1, + eRouterStatusFirewalled = 2 + }; + class RouterContext: public i2p::garlic::GarlicDestination { public: @@ -33,12 +40,14 @@ namespace i2p CryptoPP::RandomNumberGenerator& GetRandomNumberGenerator () { return m_Rnd; }; uint32_t GetUptime () const; uint32_t GetStartupTime () const { return m_StartupTime; }; + RouterStatus GetStatus () const { return m_Status; }; + void SetStatus (RouterStatus status) { m_Status = status; }; void UpdatePort (int port); // called from Daemon void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon bool AddIntroducer (const i2p::data::RouterInfo& routerInfo, uint32_t tag); void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); - bool IsUnreachable () const { return m_IsUnreachable; }; + bool IsUnreachable () const { return m_IsUnreachable || m_Status == eRouterStatusFirewalled; }; void SetUnreachable (); bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); @@ -74,6 +83,7 @@ namespace i2p uint64_t m_LastUpdateTime; bool m_IsUnreachable, m_AcceptsTunnels, m_IsFloodfill; uint64_t m_StartupTime; // in seconds since epoch + RouterStatus m_Status; }; extern RouterContext context; diff --git a/SSUSession.cpp b/SSUSession.cpp index 4e6f85f5..7915b65d 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -902,10 +902,15 @@ namespace transport case ePeerTestParticipantAlice1: { if (m_State == eSessionStateEstablished) + { LogPrint (eLogDebug, "SSU peer test from Bob. We are Alice"); + if (i2p::context.GetStatus () == eRouterStatusTesting) // still not OK + i2p::context.SetStatus (eRouterStatusFirewalled); + } else { LogPrint (eLogDebug, "SSU first peer test from Charlie. We are Alice"); + i2p::context.SetStatus (eRouterStatusOK); m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2); SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), senderEndpoint.port (), introKey, true, false); // to Charlie @@ -920,6 +925,7 @@ namespace transport { // peer test successive LogPrint (eLogDebug, "SSU second peer test from Charlie. We are Alice"); + i2p::context.SetStatus (eRouterStatusOK); m_Server.RemovePeerTest (nonce); } break; diff --git a/Transports.cpp b/Transports.cpp index 47839a1c..f54edf0b 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -384,6 +384,7 @@ namespace transport void Transports::DetectExternalIP () { + i2p::context.SetStatus (eRouterStatusTesting); for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomRouter (); From 582daffd7f1991d5477f4bfd8179b7142216dd38 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Feb 2015 14:17:16 -0500 Subject: [PATCH 0235/6300] select peer test capable routers --- NetDb.cpp | 9 +++++++++ NetDb.h | 1 + Transports.cpp | 24 ++++++++++++++++++------ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index e2d1d3b5..21660eb6 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -885,6 +885,15 @@ namespace data }); } + std::shared_ptr NetDb::GetRandomPeerTestRouter () const + { + return GetRandomRouter ( + [](std::shared_ptr router)->bool + { + return !router->IsHidden () && router->IsPeerTesting (); + }); + } + std::shared_ptr NetDb::GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith) const { return GetRandomRouter ( diff --git a/NetDb.h b/NetDb.h index ee0411de..6a0211d5 100644 --- a/NetDb.h +++ b/NetDb.h @@ -80,6 +80,7 @@ namespace data std::shared_ptr GetRandomRouter () const; std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith) const; std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith) const; + std::shared_ptr GetRandomPeerTestRouter () const; std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded) const; std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; void SetUnreachable (const IdentHash& ident, bool unreachable); diff --git a/Transports.cpp b/Transports.cpp index f54edf0b..c9b981c9 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -384,13 +384,25 @@ namespace transport void Transports::DetectExternalIP () { - i2p::context.SetStatus (eRouterStatusTesting); - for (int i = 0; i < 5; i++) + if (m_SSUServer) { - auto router = i2p::data::netdb.GetRandomRouter (); - if (router && router->IsSSU () && m_SSUServer) - m_SSUServer->GetSession (router, true); // peer test - } + i2p::context.SetStatus (eRouterStatusTesting); + for (int i = 0; i < 5; i++) + { + auto router = i2p::data::netdb.GetRandomPeerTestRouter (); + if (router) + m_SSUServer->GetSession (router, true); // peer test + else + { + // if not peer test capable routers found pick any + router = i2p::data::netdb.GetRandomRouter (); + if (router && router->IsSSU ()) + m_SSUServer->GetSession (router); // no peer test + } + } + } + else + LogPrint (eLogError, "Can't detect external IP. SSU is not available"); } DHKeysPair * Transports::GetNextDHKeysPair () From dc8209837cc4fb7881a91cd55ff25ad9afef05db Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Feb 2015 14:54:28 -0500 Subject: [PATCH 0236/6300] cleanup expired peer tests --- SSU.cpp | 32 +++++++++++++++++++++++++++++++- SSU.h | 6 +++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index c735d912..1987eb0e 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -13,7 +13,7 @@ namespace transport m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService), m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversService), - m_IntroducersUpdateTimer (m_Service) + m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service) { m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (65535)); m_Socket.set_option (boost::asio::socket_base::send_buffer_size (65535)); @@ -44,6 +44,7 @@ namespace transport } if (i2p::context.IsUnreachable ()) ScheduleIntroducersUpdateTimer (); + SchedulePeerTestsCleanupTimer (); } void SSUServer::Stop () @@ -488,6 +489,35 @@ namespace transport { m_PeerTests.erase (nonce); } + + void SSUServer::SchedulePeerTestsCleanupTimer () + { + m_PeerTestsCleanupTimer.expires_from_now (boost::posix_time::seconds(SSU_PEER_TEST_TIMEOUT)); + m_PeerTestsCleanupTimer.async_wait (std::bind (&SSUServer::HandlePeerTestsCleanupTimer, + this, std::placeholders::_1)); + } + + void SSUServer::HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + int numDeleted = 0; + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); + for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) + { + if (ts > it->second.creationTime + SSU_PEER_TEST_TIMEOUT*1000LL) + { + numDeleted++; + it = m_PeerTests.erase (it); + } + else + it++; + } + if (numDeleted > 0) + LogPrint (eLogInfo, numDeleted, " peer tests have been expired"); + SchedulePeerTestsCleanupTimer (); + } + } } } diff --git a/SSU.h b/SSU.h index f5f3cc87..01afe606 100644 --- a/SSU.h +++ b/SSU.h @@ -21,6 +21,7 @@ namespace i2p namespace transport { const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds + const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour const size_t SSU_MAX_NUM_INTRODUCERS = 3; @@ -76,6 +77,9 @@ namespace transport void ScheduleIntroducersUpdateTimer (); void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode); + void SchedulePeerTestsCleanupTimer (); + void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode); + private: struct PeerTest @@ -90,7 +94,7 @@ namespace transport boost::asio::io_service::work m_Work, m_WorkV6, m_ReceiversWork; boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6; boost::asio::ip::udp::socket m_Socket, m_SocketV6; - boost::asio::deadline_timer m_IntroducersUpdateTimer; + boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer; std::list m_Introducers; // introducers we are connected to std::mutex m_SessionsMutex; std::map > m_Sessions; From b95b49bd193c34c59a6aedf75a599fc300492890 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Feb 2015 19:19:03 -0500 Subject: [PATCH 0237/6300] start unsing introducers after firewall detection --- SSU.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 1987eb0e..be5902b1 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -41,10 +41,9 @@ namespace transport { m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this)); m_ReceiversService.post (std::bind (&SSUServer::ReceiveV6, this)); - } - if (i2p::context.IsUnreachable ()) - ScheduleIntroducersUpdateTimer (); + } SchedulePeerTestsCleanupTimer (); + ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers } void SSUServer::Stop () @@ -423,9 +422,10 @@ namespace transport void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode) { - if (!ecode) + if (ecode != boost::asio::error::operation_aborted) { // timeout expired + if (!i2p::context.IsUnreachable ()) return; // we don't need introducers anymore std::list newList; size_t numIntroducers = 0; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); From ccf689ffd5038b47c0753201f8cb1f0692c92d82 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Feb 2015 21:05:35 -0500 Subject: [PATCH 0238/6300] connect to introducer if not any yet --- NetDb.cpp | 9 +++++++++ NetDb.h | 1 + SSU.cpp | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/NetDb.cpp b/NetDb.cpp index 21660eb6..1bb3b822 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -894,6 +894,15 @@ namespace data }); } + std::shared_ptr NetDb::GetRandomIntroducer () const + { + return GetRandomRouter ( + [](std::shared_ptr router)->bool + { + return !router->IsHidden () && router->IsIntroducer (); + }); + } + std::shared_ptr NetDb::GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith) const { return GetRandomRouter ( diff --git a/NetDb.h b/NetDb.h index 6a0211d5..7b515e2a 100644 --- a/NetDb.h +++ b/NetDb.h @@ -81,6 +81,7 @@ namespace data std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith) const; std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith) const; std::shared_ptr GetRandomPeerTestRouter () const; + std::shared_ptr GetRandomIntroducer () const; std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded) const; std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; void SetUnreachable (const IdentHash& ident, bool unreachable); diff --git a/SSU.cpp b/SSU.cpp index be5902b1..7b5a22ff 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -3,6 +3,7 @@ #include "Log.h" #include "Timestamp.h" #include "RouterContext.h" +#include "NetDb.h" #include "SSU.h" namespace i2p @@ -460,6 +461,12 @@ namespace transport } } m_Introducers = newList; + if (m_Introducers.empty ()) + { + auto introducer = i2p::data::netdb.GetRandomIntroducer (); + if (introducer) + GetSession (introducer); + } ScheduleIntroducersUpdateTimer (); } } From bd9e68e69f7ce241db75a60948c8858831c73abc Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Feb 2015 11:47:32 -0500 Subject: [PATCH 0239/6300] fixed api build --- Destination.cpp | 3 +-- filelist.mk | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 0995352b..db2628cb 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -6,7 +6,6 @@ #include "ElGamal.h" #include "Timestamp.h" #include "NetDb.h" -#include "AddressBook.h" #include "Destination.h" namespace i2p @@ -47,7 +46,7 @@ namespace client } m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (this, inboundTunnelLen, outboundTunnelLen); if (m_IsPublic) - LogPrint (eLogInfo, "Local address ", i2p::client::GetB32Address(GetIdentHash()), " created"); + LogPrint (eLogInfo, "Local address ", GetIdentHash().ToBase32 (), ".b32.i2p created"); m_StreamingDestination = new i2p::stream::StreamingDestination (*this); // TODO: } diff --git a/filelist.mk b/filelist.mk index 072864a0..a8a3557e 100644 --- a/filelist.mk +++ b/filelist.mk @@ -1,5 +1,5 @@ COMMON_SRC = \ - AddressBook.cpp CryptoConst.cpp Datagram.cpp Garlic.cpp I2NPProtocol.cpp \ + CryptoConst.cpp Datagram.cpp Garlic.cpp I2NPProtocol.cpp \ LeaseSet.cpp Log.cpp NTCPSession.cpp NetDb.cpp Reseed.cpp RouterContext.cpp \ RouterInfo.cpp SSU.cpp SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp \ TransitTunnel.cpp Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp \ @@ -10,15 +10,15 @@ ifeq ($(UNAME),Darwin) # This is needed on OS X for some reason I don't understand (yet). # Else will get linker error about unknown symbols. - torkel COMMON_SRC += \ - BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp I2PService.cpp SAM.cpp SOCKS.cpp \ + AddressBook.cpp BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp I2PService.cpp SAM.cpp SOCKS.cpp \ UPnP.cpp HTTPServer.cpp HTTPProxy.cpp i2p.cpp DaemonLinux.cpp I2PControl.cpp endif # also: Daemon{Linux,Win32}.cpp will be added later DAEMON_SRC = $(COMMON_SRC) \ - BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp I2PService.cpp SAM.cpp SOCKS.cpp \ - HTTPServer.cpp HTTPProxy.cpp I2PControl.cpp i2p.cpp + AddressBook.cpp BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp I2PService.cpp \ + SAM.cpp SOCKS.cpp HTTPServer.cpp HTTPProxy.cpp I2PControl.cpp i2p.cpp LIB_SRC := $(COMMON_SRC) \ api.cpp From 7949ffe41e4bb945b92dc641b9ef70120e8f32b4 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Feb 2015 13:07:32 -0500 Subject: [PATCH 0240/6300] fixed crash --- SSU.cpp | 1 + SSU.h | 2 +- SSUSession.cpp | 9 ++++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 7b5a22ff..17c76068 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -256,6 +256,7 @@ namespace transport std::shared_ptr SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const { + std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (e); if (it != m_Sessions.end ()) return it->second; diff --git a/SSU.h b/SSU.h index 01afe606..1b954b21 100644 --- a/SSU.h +++ b/SSU.h @@ -96,7 +96,7 @@ namespace transport boost::asio::ip::udp::socket m_Socket, m_SocketV6; boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer; std::list m_Introducers; // introducers we are connected to - std::mutex m_SessionsMutex; + mutable std::mutex m_SessionsMutex; std::map > m_Sessions; std::map m_Relays; // we are introducer std::map m_PeerTests; // nonce -> creation time in milliseconds diff --git a/SSUSession.cpp b/SSUSession.cpp index 7915b65d..4fe1fc39 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -1076,7 +1076,14 @@ namespace transport uint8_t buf[48 + 18]; // encrypt message with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_DESTROYED, buf, 48); - Send (buf, 48); + try + { + Send (buf, 48); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SSU send session destoriyed exception ", ex.what ()); + } LogPrint (eLogDebug, "SSU session destroyed sent"); } } From 3977cec4080142f2f31d661efd6851d26c1c455d Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Feb 2015 17:58:01 -0500 Subject: [PATCH 0241/6300] fixed crash at startup --- Transports.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Transports.cpp b/Transports.cpp index c9b981c9..dff8768d 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -390,7 +390,7 @@ namespace transport for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomPeerTestRouter (); - if (router) + if (router && router->IsSSU ()) m_SSUServer->GetSession (router, true); // peer test else { From 6f9d8ed01bb6e7bd2064b55bde87cf7ef488baf2 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 28 Feb 2015 07:59:34 -0500 Subject: [PATCH 0242/6300] show tunlel creation success ratio --- HTTPServer.cpp | 1 + Tunnel.cpp | 7 ++++++- Tunnel.h | 8 ++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 01907c25..0579ca31 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -659,6 +659,7 @@ namespace util default: s << "Unknown"; } s << "
"; + s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
"; s << "Data path: " << i2p::util::filesystem::GetDataDir().string() << "

"; s << "Our external address:" << "
" ; for (auto& address : i2p::context.GetRouterInfo().GetAddresses()) diff --git a/Tunnel.cpp b/Tunnel.cpp index de3096d6..5a0a96a9 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -203,7 +203,8 @@ namespace tunnel Tunnels tunnels; - Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr) + Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), + m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0) { } @@ -488,6 +489,7 @@ namespace tunnel { LogPrint ("Pending tunnel build request ", it->first, " timeout. Deleted"); it = pendingTunnels.erase (it); + m_NumFailedTunnelCreations++; } else it++; @@ -495,13 +497,16 @@ namespace tunnel case eTunnelStateBuildFailed: LogPrint ("Pending tunnel build request ", it->first, " failed. Deleted"); it = pendingTunnels.erase (it); + m_NumFailedTunnelCreations++; break; case eTunnelStateBuildReplyReceived: // intermediate state, will be either established of build failed it++; break; default: + // success it = pendingTunnels.erase (it); + m_NumSuccesiveTunnelCreations++; } } } diff --git a/Tunnel.h b/Tunnel.h index 83334b42..b59e3d7f 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -175,6 +175,9 @@ namespace tunnel std::shared_ptr m_ExploratoryPool; i2p::util::Queue m_Queue; + // some stats + int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; + public: // for HTTP only @@ -182,6 +185,11 @@ namespace tunnel const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; }; int GetQueueSize () { return m_Queue.GetSize (); }; + int GetTunnelCreationSuccessRate () const // in percents + { + int totalNum = m_NumSuccesiveTunnelCreations + m_NumFailedTunnelCreations; + return totalNum ? m_NumSuccesiveTunnelCreations*100/totalNum : 0; + } }; extern Tunnels tunnels; From 3c10ba4511daacf0f46920d586effcccc7c5c6dc Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 28 Feb 2015 20:12:21 -0500 Subject: [PATCH 0243/6300] show base64 address --- HTTPServer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 0579ca31..c99640a0 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -841,6 +841,7 @@ namespace util auto dest = i2p::client::context.FindLocalDestination (ident); if (dest) { + s << "Base64:
" << dest->GetIdentity ().ToBase64 () << "

"; s << "LeaseSets: " << dest->GetNumRemoteLeaseSets () << "
"; auto pool = dest->GetTunnelPool (); if (pool) From 513dc2fcc5935913ffe1c5607388c008c9c3b995 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 1 Mar 2015 07:55:03 -0500 Subject: [PATCH 0244/6300] don't use explicit unreachable parameter anymore --- Daemon.cpp | 3 --- README.md | 1 - RouterContext.cpp | 45 +++++++++++++++++++++++++++++++++++++++++---- RouterContext.h | 5 +++-- SSU.cpp | 3 ++- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 1dbbc82f..42a78280 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -74,9 +74,6 @@ namespace i2p if (host && host[0]) i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); - if (i2p::util::config::GetArg("-unreachable", 0)) - i2p::context.SetUnreachable (); - i2p::context.SetSupportsV6 (i2p::util::config::GetArg("-v6", 0)); i2p::context.SetFloodfill (i2p::util::config::GetArg("-floodfill", 0)); diff --git a/README.md b/README.md index dbb9b5de..9afb84fc 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,6 @@ Cmdline options * --log= - Enable or disable logging to file. 1 for yes, 0 for no. * --daemon= - Enable or disable daemon mode. 1 for yes, 0 for no. * --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). -* --unreachable= - 1 if router is declared as unreachable and works through introducers. * --v6= - 1 if supports communication through ipv6, off by default * --floodfill= - 1 if router is floodfill, off by default * --httpproxyport= - The port to listen on (HTTP Proxy) diff --git a/RouterContext.cpp b/RouterContext.cpp index 3a894e6b..1a0121ed 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -13,8 +13,8 @@ namespace i2p RouterContext context; RouterContext::RouterContext (): - m_LastUpdateTime (0), m_IsUnreachable (false), m_AcceptsTunnels (true), - m_IsFloodfill (false), m_StartupTime (0), m_Status (eRouterStatusOK ) + m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false), + m_StartupTime (0), m_Status (eRouterStatusOK ) { } @@ -119,9 +119,13 @@ namespace i2p UpdateRouterInfo (); } + bool RouterContext::IsUnreachable () const + { + return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable; + } + void RouterContext::SetUnreachable () { - m_IsUnreachable = true; // set caps m_RouterInfo.SetCaps (i2p::data::RouterInfo::eUnreachable | i2p::data::RouterInfo::eSSUTesting); // LU, B // remove NTCP address @@ -137,11 +141,41 @@ namespace i2p // delete previous introducers for (auto& addr : addresses) addr.introducers.clear (); - + // update UpdateRouterInfo (); } + void RouterContext::SetReachable () + { + // update caps + uint8_t caps = m_RouterInfo.GetCaps (); + caps &= ~i2p::data::RouterInfo::eUnreachable; + caps |= i2p::data::RouterInfo::eReachable; + caps |= i2p::data::RouterInfo::eSSUIntroducer; + if (m_IsFloodfill) + caps |= i2p::data::RouterInfo::eFloodfill; + m_RouterInfo.SetCaps (caps); + + // insert NTCP back + auto& addresses = m_RouterInfo.GetAddresses (); + for (size_t i = 0; i < addresses.size (); i++) + { + if (addresses[i].transportStyle == i2p::data::RouterInfo::eTransportSSU) + { + // insert NTCP address with host/port form SSU + m_RouterInfo.AddNTCPAddress (addresses[i].host.to_string ().c_str (), addresses[i].port); + break; + } + } + // delete previous introducers + for (auto& addr : addresses) + addr.introducers.clear (); + + // update + UpdateRouterInfo (); + } + void RouterContext::SetSupportsV6 (bool supportsV6) { if (supportsV6) @@ -200,6 +234,9 @@ namespace i2p m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION); m_RouterInfo.SetProperty ("router.version", I2P_VERSION); + + if (IsUnreachable ()) + SetReachable (); // we assume reachable until we discover firewall through peer tests return true; } diff --git a/RouterContext.h b/RouterContext.h index f1e8056c..24b761f9 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -47,8 +47,9 @@ namespace i2p void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon bool AddIntroducer (const i2p::data::RouterInfo& routerInfo, uint32_t tag); void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); - bool IsUnreachable () const { return m_IsUnreachable || m_Status == eRouterStatusFirewalled; }; + bool IsUnreachable () const; void SetUnreachable (); + void SetReachable (); bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); bool AcceptsTunnels () const { return m_AcceptsTunnels; }; @@ -81,7 +82,7 @@ namespace i2p i2p::data::PrivateKeys m_Keys; CryptoPP::AutoSeededRandomPool m_Rnd; uint64_t m_LastUpdateTime; - bool m_IsUnreachable, m_AcceptsTunnels, m_IsFloodfill; + bool m_AcceptsTunnels, m_IsFloodfill; uint64_t m_StartupTime; // in seconds since epoch RouterStatus m_Status; }; diff --git a/SSU.cpp b/SSU.cpp index 17c76068..dfea196b 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -427,7 +427,8 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { // timeout expired - if (!i2p::context.IsUnreachable ()) return; // we don't need introducers anymore + if (!i2p::context.GetStatus () != eRouterStatusFirewalled) return; // we don't need introducers anymore + if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (); std::list newList; size_t numIntroducers = 0; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); From ac57e7ced28c300d791b86c3e28a4e38641bfd41 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 1 Mar 2015 17:13:55 -0500 Subject: [PATCH 0245/6300] fixed typo --- SSU.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SSU.cpp b/SSU.cpp index dfea196b..02e15425 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -427,7 +427,7 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { // timeout expired - if (!i2p::context.GetStatus () != eRouterStatusFirewalled) return; // we don't need introducers anymore + if (i2p::context.GetStatus () != eRouterStatusFirewalled) return; // we don't need introducers anymore if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (); std::list newList; size_t numIntroducers = 0; From 0f8ea92a532fad2d586999db499db2d681b8b39e Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 1 Mar 2015 21:08:34 -0500 Subject: [PATCH 0246/6300] handle local destination port --- Datagram.cpp | 6 +++--- Datagram.h | 4 ++-- Destination.cpp | 7 +++++-- Streaming.cpp | 2 +- Streaming.h | 5 ++++- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 2378659a..9a44f2d3 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -67,7 +67,7 @@ namespace datagram } } - void DatagramDestination::HandleDatagram (const uint8_t * buf, size_t len) + void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { i2p::data::IdentityEx identity; size_t identityLen = identity.FromBuffer (buf, len); @@ -91,7 +91,7 @@ namespace datagram LogPrint (eLogWarning, "Datagram signature verification failed"); } - void DatagramDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len) + void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { // unzip it CryptoPP::Gunzip decompressor; @@ -102,7 +102,7 @@ namespace datagram if (uncompressedLen <= MAX_DATAGRAM_SIZE) { decompressor.Get (uncompressed, uncompressedLen); - HandleDatagram (uncompressed, uncompressedLen); + HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen); } else LogPrint ("Received datagram size ", uncompressedLen, " exceeds max size"); diff --git a/Datagram.h b/Datagram.h index 279f5d96..0d8aafba 100644 --- a/Datagram.h +++ b/Datagram.h @@ -27,7 +27,7 @@ namespace datagram ~DatagramDestination () {}; void SendDatagramTo (const uint8_t * payload, size_t len, std::shared_ptr remote); - void HandleDataMessagePayload (const uint8_t * buf, size_t len); + void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; void ResetReceiver () { m_Receiver = nullptr; }; @@ -36,7 +36,7 @@ namespace datagram I2NPMessage * CreateDataMessage (const uint8_t * payload, size_t len); void SendMsg (I2NPMessage * msg, std::shared_ptr remote); - void HandleDatagram (const uint8_t * buf, size_t len); + void HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); private: diff --git a/Destination.cpp b/Destination.cpp index db2628cb..0271282c 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -6,6 +6,7 @@ #include "ElGamal.h" #include "Timestamp.h" #include "NetDb.h" +#include "AddressBook.h" #include "Destination.h" namespace i2p @@ -46,7 +47,7 @@ namespace client } m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (this, inboundTunnelLen, outboundTunnelLen); if (m_IsPublic) - LogPrint (eLogInfo, "Local address ", GetIdentHash().ToBase32 (), ".b32.i2p created"); + LogPrint (eLogInfo, "Local address ", i2p::client::GetB32Address(GetIdentHash()), " created"); m_StreamingDestination = new i2p::stream::StreamingDestination (*this); // TODO: } @@ -367,6 +368,8 @@ namespace client uint32_t length = bufbe32toh (buf); buf += 4; // we assume I2CP payload + uint16_t fromPort = bufbe16toh (buf + 4), // source + toPort = bufbe16toh (buf + 6); // destination switch (buf[9]) { case PROTOCOL_TYPE_STREAMING: @@ -379,7 +382,7 @@ namespace client case PROTOCOL_TYPE_DATAGRAM: // datagram protocol if (m_DatagramDestination) - m_DatagramDestination->HandleDataMessagePayload (buf, length); + m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length); else LogPrint ("Missing streaming destination"); break; diff --git a/Streaming.cpp b/Streaming.cpp index d4fbc208..f5376f06 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -661,7 +661,7 @@ namespace stream htobe32buf (buf, size); // length buf += 4; compressor.Get (buf, size); - htobuf16(buf + 4, 0); // source port + htobe16buf (buf + 4, m_LocalDestination.GetLocalPort ()); // source port htobe16buf (buf + 6, m_Port); // destination port buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol msg->len += size + 4; diff --git a/Streaming.h b/Streaming.h index e91b11c7..d48a9389 100644 --- a/Streaming.h +++ b/Streaming.h @@ -174,7 +174,8 @@ namespace stream typedef std::function)> Acceptor; - StreamingDestination (i2p::client::ClientDestination& owner): m_Owner (owner) {}; + StreamingDestination (i2p::client::ClientDestination& owner, uint16_t localPort = 0): + m_Owner (owner), m_LocalPort (localPort) {}; ~StreamingDestination () {}; void Start (); @@ -186,6 +187,7 @@ namespace stream void ResetAcceptor () { if (m_Acceptor) m_Acceptor (nullptr); m_Acceptor = nullptr; }; bool IsAcceptorSet () const { return m_Acceptor != nullptr; }; i2p::client::ClientDestination& GetOwner () { return m_Owner; }; + uint16_t GetLocalPort () const { return m_LocalPort; }; void HandleDataMessagePayload (const uint8_t * buf, size_t len); @@ -197,6 +199,7 @@ namespace stream private: i2p::client::ClientDestination& m_Owner; + uint16_t m_LocalPort; std::mutex m_StreamsMutex; std::map > m_Streams; Acceptor m_Acceptor; From 45cb98c8de72600848a604ef2bce7c17f97e6b30 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Mar 2015 15:54:38 -0500 Subject: [PATCH 0247/6300] shared_ptr for streaming destination --- Destination.cpp | 4 +--- Destination.h | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 0271282c..24671df8 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -48,7 +48,7 @@ namespace client m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (this, inboundTunnelLen, outboundTunnelLen); if (m_IsPublic) LogPrint (eLogInfo, "Local address ", i2p::client::GetB32Address(GetIdentHash()), " created"); - m_StreamingDestination = new i2p::stream::StreamingDestination (*this); // TODO: + m_StreamingDestination = std::make_shared (*this); // TODO: } ClientDestination::~ClientDestination () @@ -59,8 +59,6 @@ namespace client delete it.second; if (m_Pool) i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool); - if (m_StreamingDestination) - delete m_StreamingDestination; if (m_DatagramDestination) delete m_DatagramDestination; } diff --git a/Destination.h b/Destination.h index 45c29bb0..f0e414a8 100644 --- a/Destination.h +++ b/Destination.h @@ -68,7 +68,7 @@ namespace client bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); // streaming - i2p::stream::StreamingDestination * GetStreamingDestination () const { return m_StreamingDestination; }; + std::shared_ptr GetStreamingDestination () const { return m_StreamingDestination; }; void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); @@ -130,7 +130,7 @@ namespace client uint32_t m_PublishReplyToken; std::set m_ExcludedFloodfills; // for publishing - i2p::stream::StreamingDestination * m_StreamingDestination; + std::shared_ptr m_StreamingDestination; i2p::datagram::DatagramDestination * m_DatagramDestination; boost::asio::deadline_timer m_PublishConfirmationTimer, m_CleanupTimer; From 7b938b246a048ab86410d61ac0dd430a7fa2be7a Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Mar 2015 16:09:59 -0500 Subject: [PATCH 0248/6300] choose streaming destination for destination port --- Destination.cpp | 13 +++++++++++++ Destination.h | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Destination.cpp b/Destination.cpp index 24671df8..7265f96c 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -371,11 +371,24 @@ namespace client switch (buf[9]) { case PROTOCOL_TYPE_STREAMING: + { // streaming protocol + if (toPort) // not null + { + auto it = m_StreamingDestinationsByPorts.find (toPort); + if (it != m_StreamingDestinationsByPorts.end ()) + { + // found destination for specific port + it->second->HandleDataMessagePayload (buf, length); + break; + } + } + // if port is zero, or destination for port not found, use default if (m_StreamingDestination) m_StreamingDestination->HandleDataMessagePayload (buf, length); else LogPrint ("Missing streaming destination"); + } break; case PROTOCOL_TYPE_DATAGRAM: // datagram protocol diff --git a/Destination.h b/Destination.h index f0e414a8..5650f325 100644 --- a/Destination.h +++ b/Destination.h @@ -130,7 +130,8 @@ namespace client uint32_t m_PublishReplyToken; std::set m_ExcludedFloodfills; // for publishing - std::shared_ptr m_StreamingDestination; + std::shared_ptr m_StreamingDestination; // default + std::map > m_StreamingDestinationsByPorts; i2p::datagram::DatagramDestination * m_DatagramDestination; boost::asio::deadline_timer m_PublishConfirmationTimer, m_CleanupTimer; From 6fc0b2ecfbdd2749d88c2acdd6d079577a2e281d Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Mar 2015 22:04:59 -0500 Subject: [PATCH 0249/6300] create additional streaming destination --- Destination.cpp | 46 +++++++++++++++++++++++++++++++--------------- Destination.h | 6 ++++-- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 7265f96c..4add4066 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -87,7 +87,9 @@ namespace client m_Pool->SetActive (true); m_Thread = new std::thread (std::bind (&ClientDestination::Run, this)); m_StreamingDestination->Start (); - + for (auto it: m_StreamingDestinationsByPorts) + it.second->Start (); + m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer, this, std::placeholders::_1)); @@ -101,6 +103,8 @@ namespace client m_CleanupTimer.cancel (); m_IsRunning = false; m_StreamingDestination->Stop (); + for (auto it: m_StreamingDestinationsByPorts) + it.second->Stop (); if (m_DatagramDestination) { auto d = m_DatagramDestination; @@ -373,19 +377,9 @@ namespace client case PROTOCOL_TYPE_STREAMING: { // streaming protocol - if (toPort) // not null - { - auto it = m_StreamingDestinationsByPorts.find (toPort); - if (it != m_StreamingDestinationsByPorts.end ()) - { - // found destination for specific port - it->second->HandleDataMessagePayload (buf, length); - break; - } - } - // if port is zero, or destination for port not found, use default - if (m_StreamingDestination) - m_StreamingDestination->HandleDataMessagePayload (buf, length); + auto dest = GetStreamingDestination (toPort); + if (dest) + dest->HandleDataMessagePayload (buf, length); else LogPrint ("Missing streaming destination"); } @@ -434,6 +428,18 @@ namespace client return nullptr; } + std::shared_ptr ClientDestination::GetStreamingDestination (int port) const + { + if (port) + { + auto it = m_StreamingDestinationsByPorts.find (port); + if (it != m_StreamingDestinationsByPorts.end ()) + return it->second; + } + // if port is zero or not found, use default destination + return m_StreamingDestination; + } + void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor) { if (m_StreamingDestination) @@ -445,7 +451,7 @@ namespace client if (m_StreamingDestination) m_StreamingDestination->ResetAcceptor (); } - + bool ClientDestination::IsAcceptingStreams () const { if (m_StreamingDestination) @@ -453,6 +459,16 @@ namespace client return false; } + std::shared_ptr ClientDestination::CreateStreamingDestination (int port) + { + auto dest = std::make_shared (*this, port); + if (port) + m_StreamingDestinationsByPorts[port] = dest; + else // update default + m_StreamingDestination = dest; + return dest; + } + i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination () { if (!m_DatagramDestination) diff --git a/Destination.h b/Destination.h index 5650f325..7f3a904a 100644 --- a/Destination.h +++ b/Destination.h @@ -68,13 +68,15 @@ namespace client bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); // streaming - std::shared_ptr GetStreamingDestination () const { return m_StreamingDestination; }; + std::shared_ptr CreateStreamingDestination (int port); // additional + std::shared_ptr GetStreamingDestination (int port = 0) const; + // following methods operate with default streaming destination void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); void StopAcceptingStreams (); bool IsAcceptingStreams () const; - + // datagram i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; i2p::datagram::DatagramDestination * CreateDatagramDestination (); From c66ba370d52cf8204be19894c6e836a5f4eb7a0d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Mar 2015 14:52:16 -0500 Subject: [PATCH 0250/6300] bind streaming destination to port for server tunnel --- I2PTunnel.cpp | 9 ++++++++- I2PTunnel.h | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 15d3b6ad..4cd4a673 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -246,6 +246,7 @@ namespace client I2PServerTunnel::I2PServerTunnel (const std::string& address, int port, std::shared_ptr localDestination): I2PService (localDestination), m_Endpoint (boost::asio::ip::address::from_string (address), port) { + m_PortDestination = localDestination->CreateStreamingDestination (port); } void I2PServerTunnel::Start () @@ -260,9 +261,15 @@ namespace client void I2PServerTunnel::Accept () { + if (m_PortDestination) + m_PortDestination->SetAcceptor (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1)); + auto localDestination = GetLocalDestination (); if (localDestination) - localDestination->AcceptStreams (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1)); + { + if (!localDestination->IsAcceptingStreams ()) // set it as default if not set yet + localDestination->AcceptStreams (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1)); + } else LogPrint ("Local destination not set for server tunnel"); } diff --git a/I2PTunnel.h b/I2PTunnel.h index d4be85c7..3d91eec5 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -95,7 +95,8 @@ namespace client private: - boost::asio::ip::tcp::endpoint m_Endpoint; + boost::asio::ip::tcp::endpoint m_Endpoint; + std::shared_ptr m_PortDestination; }; } } From d9218134e24ac2c0489e77d0a96cab16e40e5a3f Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Mar 2015 15:31:49 -0500 Subject: [PATCH 0251/6300] pass source and destination ports to datagram receiver --- Datagram.cpp | 2 +- Datagram.h | 2 +- SAM.cpp | 6 +++--- SAM.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 9a44f2d3..412ef318 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -83,7 +83,7 @@ namespace datagram if (verified) { if (m_Receiver != nullptr) - m_Receiver (identity, buf + headerLen, len -headerLen); + m_Receiver (identity, fromPort, toPort, buf + headerLen, len -headerLen); else LogPrint (eLogWarning, "Receiver for datagram is not set"); } diff --git a/Datagram.h b/Datagram.h index 0d8aafba..7df5e3cd 100644 --- a/Datagram.h +++ b/Datagram.h @@ -19,7 +19,7 @@ namespace datagram const size_t MAX_DATAGRAM_SIZE = 32768; class DatagramDestination { - typedef std::function Receiver; + typedef std::function Receiver; public: diff --git a/SAM.cpp b/SAM.cpp index 5f4c23af..dea72ad3 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -259,7 +259,7 @@ namespace client { auto dest = m_Session->localDestination->CreateDatagramDestination (); dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); } SendSessionCreateReplyOk (); } @@ -577,9 +577,9 @@ namespace client LogPrint (eLogInfo, "SAM I2P acceptor has been reset"); } - void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& ident, const uint8_t * buf, size_t len) + void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { - auto base64 = ident.ToBase64 (); + auto base64 = from.ToBase64 (); #ifdef _MSC_VER size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), len); #else diff --git a/SAM.h b/SAM.h index 5bc1320a..8c364814 100644 --- a/SAM.h +++ b/SAM.h @@ -96,7 +96,7 @@ namespace client void HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleI2PAccept (std::shared_ptr stream); void HandleWriteI2PData (const boost::system::error_code& ecode); - void HandleI2PDatagramReceive (const i2p::data::IdentityEx& ident, const uint8_t * buf, size_t len); + void HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void ProcessSessionCreate (char * buf, size_t len); void ProcessStreamConnect (char * buf, size_t len); From 756e15fe1925b864265b4ff5fbd3dbf00bb5c1e2 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Mar 2015 16:00:02 -0500 Subject: [PATCH 0252/6300] fixed typo --- SAM.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAM.h b/SAM.h index 8c364814..de3cc8e0 100644 --- a/SAM.h +++ b/SAM.h @@ -39,7 +39,7 @@ namespace client const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n"; const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP"; const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n"; - const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM_RECEIVED DESTINATION=%s SIZE=%lu\n"; + const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n"; const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n"; const char SAM_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=INVALID_KEY_NOT_FOUND NAME=%s\n"; const char SAM_PARAM_MIN[] = "MIN"; From c0043e5098d2ca4dcb5249e39fc5a158f68dd5bb Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Mar 2015 22:46:52 -0500 Subject: [PATCH 0253/6300] send outstanding data on close --- Streaming.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index f5376f06..a5bce1d2 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -201,9 +201,9 @@ namespace stream if (flags & PACKET_FLAG_CLOSE) { LogPrint (eLogInfo, "Closed"); - Close (); m_IsOpen = false; m_IsReset = true; + Close (); } } @@ -261,6 +261,8 @@ namespace stream m_ResendTimer.cancel (); if (acknowledged) SendBuffer (); + if (!m_IsOpen) + Close (); // all outgoing messages have been sent } size_t Stream::Send (const uint8_t * buf, size_t len) @@ -463,8 +465,15 @@ namespace stream m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p)); LogPrint ("FIN sent"); } - m_ReceiveTimer.cancel (); - m_LocalDestination.DeleteStream (shared_from_this ()); + if (m_IsReset || (m_SentPackets.empty () && m_SendBuffer.eof ())) + { + // no more outgoing data or closed by peer + m_ReceiveTimer.cancel (); + m_LocalDestination.DeleteStream (shared_from_this ()); + } + else + LogPrint (eLogInfo, "Trying to send stream data before closing"); + } size_t Stream::ConcatenatePackets (uint8_t * buf, size_t len) @@ -584,7 +593,7 @@ namespace stream LogPrint (eLogWarning, "Packet ", it->GetSeqn (), " was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts. Terminate"); m_IsOpen = false; m_IsReset = true; - m_ReceiveTimer.cancel (); + Close (); return; } } From 7fbe2754a495f540161d00dfc99f615ca999a621 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 4 Mar 2015 12:50:31 -0500 Subject: [PATCH 0254/6300] use v4 only sessions between Bob and Charlie --- SSU.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SSU.cpp b/SSU.cpp index 02e15425..8c7948ee 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -386,7 +386,7 @@ namespace transport return GetRandomSession ( [excluded](std::shared_ptr session)->bool { - return session->GetState () == eSessionStateEstablished && + return session->GetState () == eSessionStateEstablished && !session->IsV6 () && session != excluded; } ); From 9d273acd423a0f1a50df7399ba72c2b67b7f0ec1 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 4 Mar 2015 21:55:40 -0500 Subject: [PATCH 0255/6300] resend message immediately if NACK recieved --- Streaming.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index a5bce1d2..d3c3e225 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -230,6 +230,7 @@ namespace stream if (nacked) { LogPrint (eLogDebug, "Packet ", seqn, " NACK"); + SendPackets (std::vector { *it }); it++; continue; } @@ -399,7 +400,7 @@ namespace stream auto seqn = it->GetSeqn (); if (numNacks + (seqn - nextSeqn) >= 256) { - LogPrint (eLogError, "Number of NACKs exceeds 256"); + LogPrint (eLogError, "Number of NACKs exceeds 256. seqn=", seqn, " nextSeqn=", nextSeqn); htobe32buf (packet + 12, nextSeqn); // change ack Through break; } From 42f314669fcbf438d09274ade55189924a8c6c3a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 5 Mar 2015 15:17:16 -0500 Subject: [PATCH 0256/6300] show router status through I2PControl --- I2PControl.cpp | 6 ++++++ I2PControl.h | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 4183476d..5f5995bc 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -38,6 +38,7 @@ namespace client m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_UPTIME] = &I2PControlService::UptimeHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = &I2PControlService::NetDbKnownPeersHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlService::NetDbActivePeersHandler; + m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_STATUS] = &I2PControlService::StatusHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlService::TunnelsParticipatingHandler; // RouterManager @@ -307,6 +308,11 @@ namespace client results[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = boost::lexical_cast(i2p::transport::transports.GetPeers ().size ()); } + void I2PControlService::StatusHandler (std::map& results) + { + results[I2P_CONTROL_ROUTER_INFO_STATUS] = boost::lexical_cast((int)i2p::context.GetStatus ()); + } + void I2PControlService::TunnelsParticipatingHandler (std::map& results) { results[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = boost::lexical_cast(i2p::tunnel::tunnels.GetTransitTunnels ().size ()); diff --git a/I2PControl.h b/I2PControl.h index ffd06dba..96bc7d1b 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -47,6 +47,7 @@ namespace client const char I2P_CONTROL_ROUTER_INFO_UPTIME[] = "i2p.router.uptime"; const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; const char I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers"; + const char I2P_CONTROL_ROUTER_INFO_STATUS[] = "i2p.router.net.status"; const char I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating"; // RouterManager requests @@ -97,7 +98,8 @@ namespace client typedef void (I2PControlService::*RouterInfoRequestHandler)(std::map& results); void UptimeHandler (std::map& results); void NetDbKnownPeersHandler (std::map& results); - void NetDbActivePeersHandler (std::map& results); + void NetDbActivePeersHandler (std::map& results); + void StatusHandler (std::map& results); void TunnelsParticipatingHandler (std::map& results); // RouterManager From 55f6fe6d3a8d41ab2781f588ab56943e516e5d66 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 5 Mar 2015 15:33:45 -0500 Subject: [PATCH 0257/6300] exclude Token from requests --- I2PControl.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 5f5995bc..c39164a2 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -168,7 +168,10 @@ namespace client { LogPrint (eLogInfo, v.first); if (!v.first.empty()) - params[v.first] = v.second.data (); + { + if (v.first != I2P_CONTROL_PARAM_TOKEN) // exclude Token. TODO: verify it + params[v.first] = v.second.data (); + } } std::map results; (this->*(it->second))(params, results); From e0f80c9f91b347f7a59369d80e1f3a5969f16c48 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 6 Mar 2015 11:33:54 -0500 Subject: [PATCH 0258/6300] save and check token --- I2PControl.cpp | 15 +++++++++++++-- I2PControl.h | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index c39164a2..4954f804 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -169,7 +169,16 @@ namespace client LogPrint (eLogInfo, v.first); if (!v.first.empty()) { - if (v.first != I2P_CONTROL_PARAM_TOKEN) // exclude Token. TODO: verify it + if (v.first == I2P_CONTROL_PARAM_TOKEN) + { + if (!m_Tokens.count (v.second.data ())) + { + LogPrint (eLogWarning, "Unknown token ", v.second.data ()); + return; + } + + } + else params[v.first] = v.second.data (); } } @@ -252,7 +261,9 @@ namespace client if (password != m_Password) LogPrint (eLogError, "I2PControl Authenticate Invalid password ", password, " expected ", m_Password); results[I2P_CONTROL_PARAM_API] = api; - results[I2P_CONTROL_PARAM_TOKEN] = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); + std::string token = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); + m_Tokens.insert (token); + results[I2P_CONTROL_PARAM_TOKEN] = token; } void I2PControlService::EchoHandler (const std::map& params, std::map& results) diff --git a/I2PControl.h b/I2PControl.h index 96bc7d1b..f7e76949 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace i2p @@ -120,6 +121,7 @@ namespace client boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::deadline_timer m_ShutdownTimer; + std::set m_Tokens; std::map m_MethodHandlers; std::map m_I2PControlHandlers; From 36de881041b66017d5da4d89b3eb67403d13943b Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 6 Mar 2015 21:39:05 -0500 Subject: [PATCH 0259/6300] send close after buffer --- Streaming.cpp | 66 +++++++++++++++++++++++++++++---------------------- Streaming.h | 1 + 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index d3c3e225..520800f6 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -359,6 +359,8 @@ namespace stream m_SentPackets.insert (it); } SendPackets (packets); + if (!m_IsOpen && m_SendBuffer.eof ()) + SendClose (); if (isEmpty) ScheduleResend (); } @@ -438,37 +440,12 @@ namespace stream if (m_IsOpen) { m_IsOpen = false; - Packet * p = new Packet (); - uint8_t * packet = p->GetBuffer (); - size_t size = 0; - htobe32buf (packet + size, m_SendStreamID); - size += 4; // sendStreamID - htobe32buf (packet + size, m_RecvStreamID); - size += 4; // receiveStreamID - htobe32buf (packet + size, m_SequenceNumber++); - size += 4; // sequenceNum - htobe32buf (packet + size, m_LastReceivedSequenceNumber); - size += 4; // ack Through - packet[size] = 0; - size++; // NACK count - size++; // resend delay - htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); - size += 2; // flags - size_t signatureLen = m_LocalDestination.GetOwner ().GetIdentity ().GetSignatureLen (); - htobe16buf (packet + size, signatureLen); // signature only - size += 2; // options size - uint8_t * signature = packet + size; - memset (packet + size, 0, signatureLen); - size += signatureLen; // signature - m_LocalDestination.GetOwner ().Sign (packet, size, signature); - - p->len = size; - m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p)); - LogPrint ("FIN sent"); + if (m_SendBuffer.eof ()) // nothing to send + SendClose (); } - if (m_IsReset || (m_SentPackets.empty () && m_SendBuffer.eof ())) + if (m_IsReset || m_SentPackets.empty ()) { - // no more outgoing data or closed by peer + // closed by peer or everything has been acknowledged m_ReceiveTimer.cancel (); m_LocalDestination.DeleteStream (shared_from_this ()); } @@ -477,6 +454,37 @@ namespace stream } + void Stream::SendClose () + { + Packet * p = new Packet (); + uint8_t * packet = p->GetBuffer (); + size_t size = 0; + htobe32buf (packet + size, m_SendStreamID); + size += 4; // sendStreamID + htobe32buf (packet + size, m_RecvStreamID); + size += 4; // receiveStreamID + htobe32buf (packet + size, m_SequenceNumber++); + size += 4; // sequenceNum + htobe32buf (packet + size, m_LastReceivedSequenceNumber); + size += 4; // ack Through + packet[size] = 0; + size++; // NACK count + size++; // resend delay + htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); + size += 2; // flags + size_t signatureLen = m_LocalDestination.GetOwner ().GetIdentity ().GetSignatureLen (); + htobe16buf (packet + size, signatureLen); // signature only + size += 2; // options size + uint8_t * signature = packet + size; + memset (packet + size, 0, signatureLen); + size += signatureLen; // signature + m_LocalDestination.GetOwner ().Sign (packet, size, signature); + + p->len = size; + m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p)); + LogPrint ("FIN sent"); + } + size_t Stream::ConcatenatePackets (uint8_t * buf, size_t len) { size_t pos = 0; diff --git a/Streaming.h b/Streaming.h index d48a9389..73db7692 100644 --- a/Streaming.h +++ b/Streaming.h @@ -124,6 +124,7 @@ namespace stream void SendBuffer (); void SendQuickAck (); + void SendClose (); bool SendPacket (Packet * packet); void SendPackets (const std::vector& packets); From 4b82e90ffb9fd87dd35006c0b6bf8bc0a52ea16a Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 7 Mar 2015 20:16:31 -0500 Subject: [PATCH 0260/6300] send proper send time for NACK reply --- Streaming.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Streaming.cpp b/Streaming.cpp index 520800f6..3c9edbb3 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -230,6 +230,8 @@ namespace stream if (nacked) { LogPrint (eLogDebug, "Packet ", seqn, " NACK"); + (*it)->numResendAttempts++; + (*it)->sendTime = ts; SendPackets (std::vector { *it }); it++; continue; From dc599bbc636a7d0b290d953eaab37aedee3808f9 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 8 Mar 2015 19:36:33 -0400 Subject: [PATCH 0261/6300] stream status --- Streaming.cpp | 41 +++++++++++++++++++++-------------------- Streaming.h | 18 ++++++++++++++---- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 3c9edbb3..448b8c10 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -12,9 +12,9 @@ namespace i2p namespace stream { Stream::Stream (boost::asio::io_service& service, StreamingDestination& local, - std::shared_ptr remote, int port): m_Service (service), m_SendStreamID (0), - m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_IsOpen (false), - m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local), + std::shared_ptr remote, int port): m_Service (service), + m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), + m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_LastWindowSizeIncreaseTime (0) @@ -25,7 +25,7 @@ namespace stream Stream::Stream (boost::asio::io_service& service, StreamingDestination& local): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), - m_IsOpen (false), m_IsReset (false), m_IsAckSendScheduled (false), m_LocalDestination (local), + m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_LastWindowSizeIncreaseTime (0) @@ -95,7 +95,7 @@ namespace stream } // schedule ack for last message - if (m_IsOpen) + if (m_Status == eStreamStatusOpen) { if (!m_IsAckSendScheduled) { @@ -107,7 +107,7 @@ namespace stream } else if (isSyn) // we have to send SYN back to incoming connection - Send (nullptr, 0); // also sets m_IsOpen + SendBuffer (); // also sets m_IsOpen } else { @@ -201,8 +201,7 @@ namespace stream if (flags & PACKET_FLAG_CLOSE) { LogPrint (eLogInfo, "Closed"); - m_IsOpen = false; - m_IsReset = true; + m_Status = eStreamStatusReset; Close (); } } @@ -264,7 +263,7 @@ namespace stream m_ResendTimer.cancel (); if (acknowledged) SendBuffer (); - if (!m_IsOpen) + if (m_Status == eStreamStatusClosing) Close (); // all outgoing messages have been sent } @@ -289,7 +288,7 @@ namespace stream std::vector packets; { std::unique_lock l(m_SendBufferMutex); - while (!m_IsOpen || (IsEstablished () && !m_SendBuffer.eof () && numMsgs > 0)) + while ((m_Status == eStreamStatusNew) || (IsEstablished () && !m_SendBuffer.eof () && numMsgs > 0)) { Packet * p = new Packet (); uint8_t * packet = p->GetBuffer (); @@ -310,10 +309,10 @@ namespace stream size++; // NACK count packet[size] = RESEND_TIMEOUT; size++; // resend delay - if (!m_IsOpen) + if (m_Status == eStreamStatusNew) { // initial packet - m_IsOpen = true; m_IsReset = false; + m_Status = eStreamStatusOpen; uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED | PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED; if (isNoAck) flags |= PACKET_FLAG_NO_ACK; @@ -361,7 +360,7 @@ namespace stream m_SentPackets.insert (it); } SendPackets (packets); - if (!m_IsOpen && m_SendBuffer.eof ()) + if (m_Status == eStreamStatusClosing && m_SendBuffer.eof ()) SendClose (); if (isEmpty) ScheduleResend (); @@ -439,15 +438,17 @@ namespace stream void Stream::Close () { - if (m_IsOpen) + if (m_Status == eStreamStatusOpen) { - m_IsOpen = false; + m_Status = eStreamStatusClosing; if (m_SendBuffer.eof ()) // nothing to send SendClose (); } - if (m_IsReset || m_SentPackets.empty ()) + if (m_Status == eStreamStatusReset || m_SentPackets.empty ()) { // closed by peer or everything has been acknowledged + if (m_Status == eStreamStatusClosing) + SendClose (); m_ReceiveTimer.cancel (); m_LocalDestination.DeleteStream (shared_from_this ()); } @@ -458,6 +459,7 @@ namespace stream void Stream::SendClose () { + m_Status = eStreamStatusClosed; Packet * p = new Packet (); uint8_t * packet = p->GetBuffer (); size_t size = 0; @@ -516,7 +518,7 @@ namespace stream m_AckSendTimer.cancel (); } SendPackets (std::vector { packet }); - if (m_IsOpen) + if (m_Status == eStreamStatusOpen) { bool isEmpty = m_SentPackets.empty (); m_SentPackets.insert (packet); @@ -602,8 +604,7 @@ namespace stream else { LogPrint (eLogWarning, "Packet ", it->GetSeqn (), " was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts. Terminate"); - m_IsOpen = false; - m_IsReset = true; + m_Status = eStreamStatusReset; Close (); return; } @@ -632,7 +633,7 @@ namespace stream { if (m_IsAckSendScheduled) { - if (m_IsOpen) + if (m_Status == eStreamStatusOpen) SendQuickAck (); m_IsAckSendScheduled = false; } diff --git a/Streaming.h b/Streaming.h index 73db7692..73508a71 100644 --- a/Streaming.h +++ b/Streaming.h @@ -83,6 +83,15 @@ namespace stream return p1->GetSeqn () < p2->GetSeqn (); }; }; + + enum StreamStatus + { + eStreamStatusNew, + eStreamStatusOpen, + eStreamStatusReset, + eStreamStatusClosing, + eStreamStatusClosed + }; class StreamingDestination; class Stream: public std::enable_shared_from_this @@ -98,7 +107,7 @@ namespace stream uint32_t GetRecvStreamID () const { return m_RecvStreamID; }; std::shared_ptr GetRemoteLeaseSet () const { return m_RemoteLeaseSet; }; const i2p::data::IdentityEx& GetRemoteIdentity () const { return m_RemoteIdentity; }; - bool IsOpen () const { return m_IsOpen; }; + bool IsOpen () const { return m_Status == eStreamStatusOpen; }; bool IsEstablished () const { return m_SendStreamID; }; StreamingDestination& GetLocalDestination () { return m_LocalDestination; }; @@ -149,7 +158,8 @@ namespace stream boost::asio::io_service& m_Service; uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber; int32_t m_LastReceivedSequenceNumber; - bool m_IsOpen, m_IsReset, m_IsAckSendScheduled; + StreamStatus m_Status; + bool m_IsAckSendScheduled; StreamingDestination& m_LocalDestination; i2p::data::IdentityEx m_RemoteIdentity; std::shared_ptr m_RemoteLeaseSet; @@ -239,13 +249,13 @@ namespace stream if (ecode == boost::asio::error::operation_aborted) { // timeout not expired - if (m_IsOpen) + if (m_Status == eStreamStatusOpen) // no error handler (boost::system::error_code (), received); else { // stream closed - if (m_IsReset) + if (m_Status == eStreamStatusReset) { // stream closed by peer handler (received > 0 ? boost::system::error_code () : // we still have some data From cd0933522dea3f86e2b6dad50dfbc4779d89341a Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 9 Mar 2015 12:06:35 -0400 Subject: [PATCH 0262/6300] handle stream close depending on state --- Streaming.cpp | 52 +++++++++++++++++++++++++++++++++------------------ Streaming.h | 2 ++ 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 448b8c10..72669f49 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -35,6 +35,12 @@ namespace stream Stream::~Stream () { + Terminate (); + LogPrint (eLogDebug, "Stream deleted"); + } + + void Stream::Terminate () + { m_AckSendTimer.cancel (); while (!m_ReceiveQueue.empty ()) { @@ -52,7 +58,6 @@ namespace stream for (auto it: m_SavedPackets) delete it; m_SavedPackets.clear (); - LogPrint (eLogDebug, "Stream deleted"); } void Stream::HandleNextPacket (Packet * packet) @@ -438,23 +443,34 @@ namespace stream void Stream::Close () { - if (m_Status == eStreamStatusOpen) - { - m_Status = eStreamStatusClosing; - if (m_SendBuffer.eof ()) // nothing to send - SendClose (); - } - if (m_Status == eStreamStatusReset || m_SentPackets.empty ()) - { - // closed by peer or everything has been acknowledged - if (m_Status == eStreamStatusClosing) - SendClose (); - m_ReceiveTimer.cancel (); - m_LocalDestination.DeleteStream (shared_from_this ()); - } - else - LogPrint (eLogInfo, "Trying to send stream data before closing"); - + switch (m_Status) + { + case eStreamStatusOpen: + m_Status = eStreamStatusClosing; + Close (); // recursion + if (m_Status == eStreamStatusClosing) //still closing + LogPrint (eLogInfo, "Trying to send stream data before closing"); + break; + case eStreamStatusReset: + Terminate (); + m_LocalDestination.DeleteStream (shared_from_this ()); + break; + case eStreamStatusClosing: + if (m_SentPackets.empty () && m_SendBuffer.eof ()) // nothing to send + { + SendClose (); + Terminate (); + m_LocalDestination.DeleteStream (shared_from_this ()); + } + break; + case eStreamStatusClosed: + // already closed + Terminate (); + m_LocalDestination.DeleteStream (shared_from_this ()); + break; + default: + LogPrint (eLogWarning, "Unexpected stream status ", (int)m_Status); + }; } void Stream::SendClose () diff --git a/Streaming.h b/Streaming.h index 73508a71..541a931a 100644 --- a/Streaming.h +++ b/Streaming.h @@ -131,6 +131,8 @@ namespace stream private: + void Terminate (); + void SendBuffer (); void SendQuickAck (); void SendClose (); From d13b4f669866097e2bc7f9d6b7981b5e67456b7b Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 9 Mar 2015 21:37:51 -0400 Subject: [PATCH 0263/6300] send FIN if closed by peer --- Streaming.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index 72669f49..6e1b813d 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -452,12 +452,14 @@ namespace stream LogPrint (eLogInfo, "Trying to send stream data before closing"); break; case eStreamStatusReset: + SendClose (); Terminate (); m_LocalDestination.DeleteStream (shared_from_this ()); break; case eStreamStatusClosing: if (m_SentPackets.empty () && m_SendBuffer.eof ()) // nothing to send { + m_Status = eStreamStatusClosed; SendClose (); Terminate (); m_LocalDestination.DeleteStream (shared_from_this ()); @@ -475,7 +477,6 @@ namespace stream void Stream::SendClose () { - m_Status = eStreamStatusClosed; Packet * p = new Packet (); uint8_t * packet = p->GetBuffer (); size_t size = 0; From 9c338a5c81ba88d107c19939de51a0cbcc12e6aa Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 9 Mar 2015 22:05:26 -0400 Subject: [PATCH 0264/6300] don't lost received data --- Streaming.cpp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 6e1b813d..63d3320c 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -36,28 +36,29 @@ namespace stream Stream::~Stream () { Terminate (); + while (!m_ReceiveQueue.empty ()) + { + auto packet = m_ReceiveQueue.front (); + m_ReceiveQueue.pop (); + delete packet; + } + + for (auto it: m_SentPackets) + delete it; + m_SentPackets.clear (); + + for (auto it: m_SavedPackets) + delete it; + m_SavedPackets.clear (); + LogPrint (eLogDebug, "Stream deleted"); } void Stream::Terminate () { m_AckSendTimer.cancel (); - while (!m_ReceiveQueue.empty ()) - { - auto packet = m_ReceiveQueue.front (); - m_ReceiveQueue.pop (); - delete packet; - } m_ReceiveTimer.cancel (); - - for (auto it: m_SentPackets) - delete it; - m_SentPackets.clear (); m_ResendTimer.cancel (); - - for (auto it: m_SavedPackets) - delete it; - m_SavedPackets.clear (); } void Stream::HandleNextPacket (Packet * packet) From 3e889ee06c4b6a55265c9769c5b47456d37b2f5d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Mar 2015 11:11:42 -0400 Subject: [PATCH 0265/6300] resend packing with RTO interval --- Streaming.cpp | 14 +++++++------- Streaming.h | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 63d3320c..0826a110 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -17,7 +17,8 @@ namespace stream m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), - m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_LastWindowSizeIncreaseTime (0) + m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), + m_LastWindowSizeIncreaseTime (0) { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); UpdateCurrentRemoteLease (); @@ -28,7 +29,7 @@ namespace stream m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE), - m_RTT (INITIAL_RTT), m_LastWindowSizeIncreaseTime (0) + m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_LastWindowSizeIncreaseTime (0) { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); } @@ -235,9 +236,6 @@ namespace stream if (nacked) { LogPrint (eLogDebug, "Packet ", seqn, " NACK"); - (*it)->numResendAttempts++; - (*it)->sendTime = ts; - SendPackets (std::vector { *it }); it++; continue; } @@ -245,6 +243,7 @@ namespace stream auto sentPacket = *it; uint64_t rtt = ts - sentPacket->sendTime; m_RTT = (m_RTT*seqn + rtt)/(seqn + 1); + m_RTO = m_RTT*1.5; // TODO: implement it better LogPrint (eLogDebug, "Packet ", seqn, " acknowledged rtt=", rtt); m_SentPackets.erase (it++); delete sentPacket; @@ -313,7 +312,7 @@ namespace stream size += 4; // ack Through packet[size] = 0; size++; // NACK count - packet[size] = RESEND_TIMEOUT; + packet[size] = m_RTO/1000; size++; // resend delay if (m_Status == eStreamStatusNew) { @@ -596,7 +595,7 @@ namespace stream void Stream::ScheduleResend () { m_ResendTimer.cancel (); - m_ResendTimer.expires_from_now (boost::posix_time::seconds(RESEND_TIMEOUT)); + m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, shared_from_this (), std::placeholders::_1)); } @@ -610,6 +609,7 @@ namespace stream std::vector packets; for (auto it : m_SentPackets) { + if (ts < it->sendTime + m_RTO) continue; // don't resend too early it->numResendAttempts++; if (first && it->numResendAttempts == 1) // detect congesion at first attempt of first packet only congesion = true; diff --git a/Streaming.h b/Streaming.h index 541a931a..2f73ad92 100644 --- a/Streaming.h +++ b/Streaming.h @@ -41,13 +41,13 @@ namespace stream const size_t STREAMING_MTU = 1730; const size_t MAX_PACKET_SIZE = 4096; const size_t COMPRESSION_THRESHOLD_SIZE = 66; - const int RESEND_TIMEOUT = 10; // in seconds const int ACK_SEND_TIMEOUT = 200; // in milliseconds const int MAX_NUM_RESEND_ATTEMPTS = 5; const int WINDOW_SIZE = 6; // in messages const int MIN_WINDOW_SIZE = 1; const int MAX_WINDOW_SIZE = 128; const int INITIAL_RTT = 8000; // in milliseconds + const int INITIAL_RTO = 9000; // in milliseconds struct Packet { @@ -177,7 +177,7 @@ namespace stream std::mutex m_SendBufferMutex; std::stringstream m_SendBuffer; - int m_WindowSize, m_RTT; + int m_WindowSize, m_RTT, m_RTO; uint64_t m_LastWindowSizeIncreaseTime; }; From 5bc854bedc71a94ff04318eafef6916aa137653e Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Mar 2015 15:38:37 -0400 Subject: [PATCH 0266/6300] reduced memory usage --- ElGamal.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ElGamal.h b/ElGamal.h index e69c5993..d7105c41 100644 --- a/ElGamal.h +++ b/ElGamal.h @@ -18,10 +18,12 @@ namespace crypto { public: - ElGamalEncryption (const uint8_t * key): - y (key, 256), k (rnd, CryptoPP::Integer::One(), elgp-1), - a (a_exp_b_mod_c (elgg, k, elgp)), b1 (a_exp_b_mod_c (y, k, elgp)) + ElGamalEncryption (const uint8_t * key) { + CryptoPP::AutoSeededRandomPool rnd; + CryptoPP::Integer y (key, 256), k (rnd, CryptoPP::Integer::One(), elgp-1); + a = a_exp_b_mod_c (elgg, k, elgp); + b1 = a_exp_b_mod_c (y, k, elgp); } void Encrypt (const uint8_t * data, int len, uint8_t * encrypted, bool zeroPadding = false) @@ -50,8 +52,7 @@ namespace crypto private: - CryptoPP::AutoSeededRandomPool rnd; - CryptoPP::Integer y, k, a, b1; + CryptoPP::Integer a, b1; }; inline bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, From c2edbdc4872ca706298c7e47ae5fa38daa9da63c Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Mar 2015 18:51:04 -0400 Subject: [PATCH 0267/6300] drop RTO to initial if tunnels pair changes --- Streaming.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Streaming.cpp b/Streaming.cpp index 0826a110..3f32e9a0 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -640,6 +640,7 @@ namespace stream // congesion avoidance didn't help m_CurrentOutboundTunnel = nullptr; // pick another outbound tunnel UpdateCurrentRemoteLease (); // pick another lease + m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change } SendPackets (packets); } From d51b87e80aa6049e1c5ca010baf2c8edd2ae321d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Mar 2015 21:54:25 -0400 Subject: [PATCH 0268/6300] reduced memory usage --- I2NPProtocol.cpp | 8 ++++---- I2NPProtocol.h | 2 +- Tunnel.cpp | 2 +- TunnelGateway.cpp | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index f2ef9ec1..3aa012ce 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -102,7 +102,7 @@ namespace i2p I2NPMessage * CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, uint32_t replyTunnelID, bool exploratory, std::set * excludedPeers) { - I2NPMessage * m = NewI2NPMessage (); + I2NPMessage * m = NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); memcpy (buf, key, 32); // key buf += 32; @@ -148,7 +148,7 @@ namespace i2p const std::set& excludedFloodfills, const i2p::tunnel::InboundTunnel * replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag) { - I2NPMessage * m = NewI2NPMessage (); + I2NPMessage * m = NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); memcpy (buf, dest, 32); // key buf += 32; @@ -405,7 +405,7 @@ namespace i2p I2NPMessage * CreateTunnelDataMsg (const uint8_t * buf) { - I2NPMessage * msg = NewI2NPMessage (); + I2NPMessage * msg = NewI2NPShortMessage (); memcpy (msg->GetPayload (), buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE); msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; FillI2NPMessageHeader (msg, eI2NPTunnelData); @@ -414,7 +414,7 @@ namespace i2p I2NPMessage * CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload) { - I2NPMessage * msg = NewI2NPMessage (); + I2NPMessage * msg = NewI2NPShortMessage (); memcpy (msg->GetPayload () + 4, payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4); htobe32buf (msg->GetPayload (), tunnelID); msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 5bb94062..4f426b9f 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -106,7 +106,7 @@ namespace tunnel } const size_t I2NP_MAX_MESSAGE_SIZE = 32768; - const size_t I2NP_MAX_SHORT_MESSAGE_SIZE = 2400; + const size_t I2NP_MAX_SHORT_MESSAGE_SIZE = 4096; struct I2NPMessage { uint8_t * buf; diff --git a/Tunnel.cpp b/Tunnel.cpp index 5a0a96a9..f04c7f05 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -32,7 +32,7 @@ namespace tunnel CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); auto numHops = m_Config->GetNumHops (); int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops; - I2NPMessage * msg = NewI2NPMessage (); + I2NPMessage * msg = NewI2NPShortMessage (); *msg->GetPayload () = numRecords; msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1; diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index 561a1068..fbef1208 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -138,7 +138,7 @@ namespace tunnel void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage () { - m_CurrentTunnelDataMsg = NewI2NPMessage (); + m_CurrentTunnelDataMsg = NewI2NPShortMessage (); m_CurrentTunnelDataMsg->Align (12); // we reserve space for padding m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE; From 08f5af68f0a57514ffa00b5bde5f9f45adc5210f Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Mar 2015 11:28:51 -0400 Subject: [PATCH 0269/6300] reduce memory usage --- SSUData.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/SSUData.cpp b/SSUData.cpp index c34fb690..d8f5ff17 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -163,7 +163,7 @@ namespace transport else { // create new message - msg = NewI2NPMessage (); + msg = NewI2NPShortMessage (); msg->len -= I2NP_SHORT_HEADER_SIZE; it = m_IncompleteMessages.insert (std::make_pair (msgID, std::unique_ptr(new IncompleteMessage (msg)))).first; @@ -174,6 +174,15 @@ namespace transport if (fragmentNum == incompleteMessage->nextFragmentNum) { // expected fragment + if (msg->len + fragmentSize > msg->maxLen) + { + LogPrint (eLogInfo, "Short I2NP message of size ", msg->maxLen, " is not enough"); + I2NPMessage * newMsg = NewI2NPMessage (); + *newMsg = *msg; + DeleteI2NPMessage (msg); + msg = newMsg; + it->second->msg = msg; + } memcpy (msg->buf + msg->len, buf, fragmentSize); msg->len += fragmentSize; incompleteMessage->nextFragmentNum++; From ccb4e967cf944969cf725c684ceb46a0aff8dbc5 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Mar 2015 12:17:38 -0400 Subject: [PATCH 0270/6300] fixed crash --- I2NPProtocol.h | 1 + 1 file changed, 1 insertion(+) diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 4f426b9f..2dfa8fbb 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -145,6 +145,7 @@ namespace tunnel void Align (size_t alignment) { + if (len + alignment > maxLen) return; size_t rem = ((size_t)GetBuffer ()) % alignment; if (rem) { From 1d7fd8ac96fb9ce0cada815b760985debde7d164 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Mar 2015 13:24:13 -0400 Subject: [PATCH 0271/6300] reduce memory usage --- SSUData.cpp | 2 +- TunnelEndpoint.cpp | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/SSUData.cpp b/SSUData.cpp index d8f5ff17..0070fccd 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -176,7 +176,7 @@ namespace transport // expected fragment if (msg->len + fragmentSize > msg->maxLen) { - LogPrint (eLogInfo, "Short I2NP message of size ", msg->maxLen, " is not enough"); + LogPrint (eLogInfo, "SSU I2NP message size ", msg->maxLen, " is not enough"); I2NPMessage * newMsg = NewI2NPMessage (); *newMsg = *msg; DeleteI2NPMessage (msg); diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 0ed86e4a..24d86e57 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -97,7 +97,7 @@ namespace tunnel if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) { // this is not last message. we have to copy it - m.data = NewI2NPMessage (); + m.data = NewI2NPShortMessage (); m.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header m.data->len += TUNNEL_GATEWAY_HEADER_SIZE; *(m.data) = *msg; @@ -156,8 +156,16 @@ namespace tunnel auto& msg = it->second; if (m.nextFragmentNum == msg.nextFragmentNum) { - if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if messega is not too long + if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long { + if (msg.data->len + size > msg.data->maxLen) + { + LogPrint (eLogInfo, "Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); + I2NPMessage * newMsg = NewI2NPMessage (); + *newMsg = *(msg.data); + DeleteI2NPMessage (msg.data); + msg.data = newMsg; + } memcpy (msg.data->buf + msg.data->len, fragment, size); // concatenate fragment msg.data->len += size; if (isLastFragment) @@ -211,6 +219,14 @@ namespace tunnel { LogPrint (eLogInfo, "Out-of-sequence fragment ", (int)it->second.fragmentNum, " of message ", msgID, " found"); auto size = it->second.data->GetLength (); + if (msg.data->len + size > msg.data->maxLen) + { + LogPrint (eLogInfo, "Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); + I2NPMessage * newMsg = NewI2NPMessage (); + *newMsg = *(msg.data); + DeleteI2NPMessage (msg.data); + msg.data = newMsg; + } memcpy (msg.data->buf + msg.data->len, it->second.data->GetBuffer (), size); // concatenate out-of-sync fragment msg.data->len += size; if (it->second.isLastFragment) @@ -250,7 +266,7 @@ namespace tunnel if (typeID == eI2NPDatabaseStore || typeID == eI2NPDatabaseSearchReply ) { // catch RI or reply with new list of routers - auto ds = NewI2NPMessage (); + auto ds = NewI2NPShortMessage (); *ds = *(msg.data); i2p::data::netdb.PostI2NPMsg (ds); } From 2f9e510f4f100e69ea67853dcfaf85fcca0e0e77 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Mar 2015 14:34:39 -0400 Subject: [PATCH 0272/6300] check I2NP message size for saved fragments --- SSUData.cpp | 41 ++++++++++++++++++++--------------------- SSUData.h | 1 + 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/SSUData.cpp b/SSUData.cpp index 0070fccd..4cacfd68 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -10,6 +10,21 @@ namespace i2p { namespace transport { + void IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize) + { + if (msg->len + fragmentSize > msg->maxLen) + { + LogPrint (eLogInfo, "SSU I2NP message size ", msg->maxLen, " is not enough"); + I2NPMessage * newMsg = NewI2NPMessage (); + *newMsg = *msg; + DeleteI2NPMessage (msg); + msg = newMsg; + } + memcpy (msg->buf + msg->len, fragment, fragmentSize); + msg->len += fragmentSize; + nextFragmentNum++; + } + SSUData::SSUData (SSUSession& session): m_Session (session), m_ResendTimer (session.GetService ()), m_DecayTimer (session.GetService ()), m_IncompleteMessagesCleanupTimer (session.GetService ()) @@ -155,15 +170,11 @@ namespace transport } // find message with msgID - I2NPMessage * msg = nullptr; auto it = m_IncompleteMessages.find (msgID); - if (it != m_IncompleteMessages.end ()) - // message exists - msg = it->second->msg; - else + if (it == m_IncompleteMessages.end ()) { // create new message - msg = NewI2NPShortMessage (); + auto msg = NewI2NPShortMessage (); msg->len -= I2NP_SHORT_HEADER_SIZE; it = m_IncompleteMessages.insert (std::make_pair (msgID, std::unique_ptr(new IncompleteMessage (msg)))).first; @@ -174,18 +185,7 @@ namespace transport if (fragmentNum == incompleteMessage->nextFragmentNum) { // expected fragment - if (msg->len + fragmentSize > msg->maxLen) - { - LogPrint (eLogInfo, "SSU I2NP message size ", msg->maxLen, " is not enough"); - I2NPMessage * newMsg = NewI2NPMessage (); - *newMsg = *msg; - DeleteI2NPMessage (msg); - msg = newMsg; - it->second->msg = msg; - } - memcpy (msg->buf + msg->len, buf, fragmentSize); - msg->len += fragmentSize; - incompleteMessage->nextFragmentNum++; + incompleteMessage->AttachNextFragment (buf, fragmentSize); if (!isLast && !incompleteMessage->savedFragments.empty ()) { // try saved fragments @@ -194,10 +194,8 @@ namespace transport auto& savedFragment = *it1; if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum) { - memcpy (msg->buf + msg->len, savedFragment->buf, savedFragment->len); - msg->len += savedFragment->len; + incompleteMessage->AttachNextFragment (savedFragment->buf, savedFragment->len); isLast = savedFragment->isLast; - incompleteMessage->nextFragmentNum++; incompleteMessage->savedFragments.erase (it1++); } else @@ -228,6 +226,7 @@ namespace transport if (isLast) { // delete incomplete message + auto msg = incompleteMessage->msg; incompleteMessage->msg = nullptr; m_IncompleteMessages.erase (msgID); // process message diff --git a/SSUData.h b/SSUData.h index 8419f501..ff7bb96c 100644 --- a/SSUData.h +++ b/SSUData.h @@ -66,6 +66,7 @@ namespace transport IncompleteMessage (I2NPMessage * m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0) {}; ~IncompleteMessage () { if (msg) DeleteI2NPMessage (msg); }; + void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); }; struct SentMessage From f3aab569cab85ae0cd577aaf80ff8ff24e8178f5 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Mar 2015 21:43:30 -0400 Subject: [PATCH 0273/6300] don't return error if received data is available --- Streaming.h | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/Streaming.h b/Streaming.h index 2f73ad92..9cccd19d 100644 --- a/Streaming.h +++ b/Streaming.h @@ -248,26 +248,15 @@ namespace stream void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler) { size_t received = ConcatenatePackets (boost::asio::buffer_cast(buffer), boost::asio::buffer_size(buffer)); - if (ecode == boost::asio::error::operation_aborted) + if (received > 0) + handler (boost::system::error_code (), received); + else if (ecode == boost::asio::error::operation_aborted) { // timeout not expired - if (m_Status == eStreamStatusOpen) - // no error - handler (boost::system::error_code (), received); + if (m_Status == eStreamStatusReset) + handler (boost::asio::error::make_error_code (boost::asio::error::connection_reset), 0); else - { - // stream closed - if (m_Status == eStreamStatusReset) - { - // stream closed by peer - handler (received > 0 ? boost::system::error_code () : // we still have some data - boost::asio::error::make_error_code (boost::asio::error::connection_reset), // no more data - received); - - } - else // stream closed by us - handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), received); - } + handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), 0); } else // timeout expired From 550f9de41ac86fc0403d1cc5208f1cede8b7445c Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 Mar 2015 07:31:46 -0400 Subject: [PATCH 0274/6300] fixed memory corruption --- I2NPProtocol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 2dfa8fbb..294abcc9 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -189,7 +189,7 @@ namespace tunnel struct I2NPMessageBuffer: public I2NPMessage { I2NPMessageBuffer () { buf = m_Buffer; maxLen = sz; }; - uint8_t m_Buffer[sz]; + uint8_t m_Buffer[sz + 16]; }; I2NPMessage * NewI2NPMessage (); From 57d4ccfdfd36d72a00d033facb4bc266fa4bdbe7 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 Mar 2015 11:43:36 -0400 Subject: [PATCH 0275/6300] reduced memory usage --- NTCPSession.cpp | 2 ++ SSUSession.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 93f262d6..79fdba4c 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -394,6 +394,7 @@ namespace transport Terminate (); return; } + m_RemoteIdentity.DropVerifier (); SendPhase4 (tsA, tsB); } @@ -467,6 +468,7 @@ namespace transport Terminate (); return; } + m_RemoteIdentity.DropVerifier (); LogPrint (eLogInfo, "NTCP session to ", m_Socket.remote_endpoint (), " connected"); Connected (); diff --git a/SSUSession.cpp b/SSUSession.cpp index 4fe1fc39..9fa71558 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -238,6 +238,7 @@ namespace transport // verify if (!s.Verify (m_RemoteIdentity, payload)) LogPrint (eLogError, "SSU signature verification failed"); + m_RemoteIdentity.DropVerifier (); SendSessionConfirmed (y, ourAddress, addressSize + 2); } From c7edc361060f82fc281905ddd89092d081e9f1ef Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 Mar 2015 16:26:08 -0400 Subject: [PATCH 0276/6300] reduce memory usage --- NetDb.cpp | 10 ++++++++-- RouterInfo.cpp | 8 -------- RouterInfo.h | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 1bb3b822..fbddb57f 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -356,6 +356,7 @@ namespace data if (!r->IsUnreachable () && (!r->UsesIntroducer () || ts < r->GetTimestamp () + 3600*1000LL)) // 1 hour { r->DeleteBuffer (); + r->ClearProperties (); // properties are not used for regular routers m_RouterInfos[r->GetIdentHash ()] = r; if (r->IsFloodfill ()) m_Floodfills.push_back (r); @@ -566,8 +567,13 @@ namespace data decompressor.MessageEnd(); uint8_t uncompressed[2048]; size_t uncomressedSize = decompressor.MaxRetrievable (); - decompressor.Get (uncompressed, uncomressedSize); - AddRouterInfo (buf + DATABASE_STORE_KEY_OFFSET, uncompressed, uncomressedSize); + if (uncomressedSize <= 2048) + { + decompressor.Get (uncompressed, uncomressedSize); + AddRouterInfo (buf + DATABASE_STORE_KEY_OFFSET, uncompressed, uncomressedSize); + } + else + LogPrint ("Invalid RouterInfo uncomressed length ", (int)uncomressedSize); } catch (CryptoPP::Exception& ex) { diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 56a9327a..e9105db1 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -553,14 +553,6 @@ namespace data m_Properties[key] = value; } - const char * RouterInfo::GetProperty (const char * key) const - { - auto it = m_Properties.find (key); - if (it != m_Properties.end ()) - return it->second.c_str (); - return 0; - } - bool RouterInfo::IsFloodfill () const { return m_Caps & Caps::eFloodfill; diff --git a/RouterInfo.h b/RouterInfo.h index bf1a9dc6..1211a257 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -105,8 +105,8 @@ namespace data void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); bool AddIntroducer (const Address * address, uint32_t tag); bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); - void SetProperty (const char * key, const char * value); - const char * GetProperty (const char * key) const; + void SetProperty (const char * key, const char * value); // called from RouterContext only + void ClearProperties () { m_Properties.clear (); }; bool IsFloodfill () const; bool IsNTCP (bool v4only = true) const; bool IsSSU (bool v4only = true) const; From ad649aba480d42dc3eebe246b04d0db0eff23629 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 Mar 2015 22:38:22 -0400 Subject: [PATCH 0277/6300] reduce memory usage --- NTCPSession.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 79fdba4c..963bcb45 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -553,22 +553,21 @@ namespace transport { if (!m_NextMessage) // new message, header expected { - m_NextMessage = i2p::NewI2NPMessage (); - m_NextMessageOffset = 0; - - m_Decryption.Decrypt (encrypted, m_NextMessage->buf); - uint16_t dataSize = bufbe16toh (m_NextMessage->buf); + // descrypt header and extract length + uint8_t buf[16]; + m_Decryption.Decrypt (encrypted, buf); + uint16_t dataSize = bufbe16toh (buf); if (dataSize) { // new message if (dataSize > NTCP_MAX_MESSAGE_SIZE) { LogPrint (eLogError, "NTCP data size ", dataSize, " exceeds max size"); - i2p::DeleteI2NPMessage (m_NextMessage); - m_NextMessage = nullptr; return false; } - m_NextMessageOffset += 16; + m_NextMessage = dataSize <= I2NP_MAX_SHORT_MESSAGE_SIZE - 2 ? NewI2NPShortMessage () : NewI2NPMessage (); + memcpy (m_NextMessage->buf, buf, 16); + m_NextMessageOffset = 16; m_NextMessage->offset = 2; // size field m_NextMessage->len = dataSize + 2; } @@ -576,8 +575,6 @@ namespace transport { // timestamp LogPrint ("Timestamp"); - i2p::DeleteI2NPMessage (m_NextMessage); - m_NextMessage = nullptr; return true; } } From 09f1966e5f3d81fd68bcc753948c6402e0b3e83f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 13 Mar 2015 13:29:27 -0400 Subject: [PATCH 0278/6300] pass destination port to client tunnel --- I2PTunnel.cpp | 23 +++++++++++++---------- I2PTunnel.h | 5 +++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 4cd4a673..43398318 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -9,12 +9,12 @@ namespace i2p { namespace client { - I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, - boost::asio::ip::tcp::socket * socket, std::shared_ptr leaseSet): + I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, boost::asio::ip::tcp::socket * socket, + std::shared_ptr leaseSet, int port): I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { - m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet); + m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, @@ -156,20 +156,23 @@ namespace client { public: I2PClientTunnelHandler (I2PClientTunnel * parent, i2p::data::IdentHash destination, - boost::asio::ip::tcp::socket * socket): - I2PServiceHandler(parent), m_DestinationIdentHash(destination), m_Socket(socket) {} + int destinationPort, boost::asio::ip::tcp::socket * socket): + I2PServiceHandler(parent), m_DestinationIdentHash(destination), + m_DestinationPort (destinationPort), m_Socket(socket) {}; void Handle(); void Terminate(); private: void HandleStreamRequestComplete (std::shared_ptr stream); i2p::data::IdentHash m_DestinationIdentHash; + int m_DestinationPort; boost::asio::ip::tcp::socket * m_Socket; }; void I2PClientTunnelHandler::Handle() { - GetOwner()->GetLocalDestination ()->CreateStream (std::bind (&I2PClientTunnelHandler::HandleStreamRequestComplete, - shared_from_this(), std::placeholders::_1), m_DestinationIdentHash); + GetOwner()->GetLocalDestination ()->CreateStream ( + std::bind (&I2PClientTunnelHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), + m_DestinationIdentHash, m_DestinationPort); } void I2PClientTunnelHandler::HandleStreamRequestComplete (std::shared_ptr stream) @@ -202,8 +205,8 @@ namespace client Done(shared_from_this()); } - I2PClientTunnel::I2PClientTunnel (const std::string& destination, int port, std::shared_ptr localDestination): - TCPIPAcceptor (port,localDestination), m_Destination (destination), m_DestinationIdentHash (nullptr) + I2PClientTunnel::I2PClientTunnel (const std::string& destination, int port, std::shared_ptr localDestination, int destinationPort): + TCPIPAcceptor (port,localDestination), m_Destination (destination), m_DestinationIdentHash (nullptr), m_DestinationPort (destinationPort) {} void I2PClientTunnel::Start () @@ -238,7 +241,7 @@ namespace client { const i2p::data::IdentHash *identHash = GetIdentHash(); if (identHash) - return std::make_shared(this, *identHash, socket); + return std::make_shared(this, *identHash, m_DestinationPort, socket); else return nullptr; } diff --git a/I2PTunnel.h b/I2PTunnel.h index 3d91eec5..7c36f543 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -25,7 +25,7 @@ namespace client public: I2PTunnelConnection (I2PService * owner, boost::asio::ip::tcp::socket * socket, - std::shared_ptr leaseSet); // to I2P + std::shared_ptr leaseSet, int port = 0); // to I2P I2PTunnelConnection (I2PService * owner, boost::asio::ip::tcp::socket * socket, std::shared_ptr stream); // to I2P using simplified API :) I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, boost::asio::ip::tcp::socket * socket, @@ -65,7 +65,7 @@ namespace client public: - I2PClientTunnel (const std::string& destination, int port, std::shared_ptr localDestination = nullptr); + I2PClientTunnel (const std::string& destination, int port, std::shared_ptr localDestination, int destinationPort = 0); ~I2PClientTunnel () {} void Start (); @@ -77,6 +77,7 @@ namespace client std::string m_Destination; const i2p::data::IdentHash * m_DestinationIdentHash; + int m_DestinationPort; }; class I2PServerTunnel: public I2PService From 5a31d2e817974e2a4da487a540effd3804763610 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 13 Mar 2015 16:05:39 -0400 Subject: [PATCH 0279/6300] read tunnels.cfg using property tree --- ClientContext.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++++ ClientContext.h | 8 +++++++ 2 files changed, 63 insertions(+) diff --git a/ClientContext.cpp b/ClientContext.cpp index fb6e4791..3e242ef6 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include "util.h" #include "Log.h" #include "Identity.h" @@ -327,5 +329,58 @@ namespace client } } } + + void ClientContext::ReadTunnels1 () + { + boost::property_tree::ptree pt; + try + { + boost::property_tree::read_ini (i2p::util::filesystem::GetFullPath (TUNNELS_CONFIG_FILENAME), pt); + } + catch (std::exception& ex) + { + LogPrint (eLogWarning, "Can't read ", TUNNELS_CONFIG_FILENAME, ": ", ex.what ()); + return; + } + + int numClientTunnels = 0, numServerTunnels = 0; + for (auto& section: pt) + { + if (section.first == I2P_TUNNELS_SECTION_CLIENT) + { + try + { + // mandatory params + std::string dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION1); + int port = section.second.get (I2P_CLIENT_TUNNEL_PORT1); + std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS1); + // optional params + //int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT1, 0); + + std::shared_ptr localDestination = nullptr; + if (keys.length () > 0) + localDestination = LoadLocalDestination (keys, false); + auto clientTunnel = new I2PClientTunnel (dest, port, localDestination); + if (m_ClientTunnels.insert (std::make_pair (port, std::unique_ptr(clientTunnel))).second) + clientTunnel->Start (); + else + LogPrint (eLogError, "I2P client tunnel with port ", port, " already exists"); + numClientTunnels++; + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Can't read client tunnel params: ", ex.what ()); + } + } + else if (section.first == I2P_TUNNELS_SECTION_SERVER) + { + numServerTunnels++; + } + else + LogPrint (eLogWarning, "Unknown section ", section.first, " in ", TUNNELS_CONFIG_FILENAME); + } + LogPrint (eLogInfo, numClientTunnels, " I2P client tunnels created"); + LogPrint (eLogInfo, numServerTunnels, " I2P server tunnels created"); + } } } diff --git a/ClientContext.h b/ClientContext.h index 007aa359..d535c0c9 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -17,6 +17,13 @@ namespace i2p { namespace client { + const char I2P_TUNNELS_SECTION_CLIENT[] = "client"; + const char I2P_TUNNELS_SECTION_SERVER[] = "server"; + const char I2P_CLIENT_TUNNEL_PORT1[] = "port"; + const char I2P_CLIENT_TUNNEL_DESTINATION1[] = "destination"; + const char I2P_CLIENT_TUNNEL_KEYS1[] = "keys"; + const char I2P_CLIENT_TUNNEL_DESTINATION_PORT1[] = "destinationport"; + const char I2P_CLIENT_TUNNEL_NAME[] = "client.name"; const char I2P_CLIENT_TUNNEL_PORT[] = "client.port"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "client.destination"; @@ -52,6 +59,7 @@ namespace client private: void ReadTunnels (); + void ReadTunnels1 (); // using propery tree private: From ea6597c3ad1d894453ea8d2fbdfa5f4970734949 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 13 Mar 2015 19:51:31 -0400 Subject: [PATCH 0280/6300] .ini file format for tunnel.cfg --- ClientContext.cpp | 49 ++++++++++++++++++++++++++++++----------------- ClientContext.h | 26 ++++++++++--------------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 3e242ef6..d0955c0c 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -1,7 +1,5 @@ #include #include -#include -#include #include #include #include "util.h" @@ -255,7 +253,7 @@ namespace client return nullptr; } - void ClientContext::ReadTunnels () + /*void ClientContext::ReadTunnels () { std::ifstream ifs (i2p::util::filesystem::GetFullPath (TUNNELS_CONFIG_FILENAME)); if (ifs.good ()) @@ -328,9 +326,9 @@ namespace client LogPrint (eLogInfo, numServerTunnels, " I2P server tunnels created"); } } - } + }*/ - void ClientContext::ReadTunnels1 () + void ClientContext::ReadTunnels () { boost::property_tree::ptree pt; try @@ -346,16 +344,18 @@ namespace client int numClientTunnels = 0, numServerTunnels = 0; for (auto& section: pt) { - if (section.first == I2P_TUNNELS_SECTION_CLIENT) + std::string name = section.first; + try { - try + std::string type = section.second.get (I2P_TUNNELS_SECTION_TYPE); + if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT) { // mandatory params - std::string dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION1); - int port = section.second.get (I2P_CLIENT_TUNNEL_PORT1); - std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS1); + std::string dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); + int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); + std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS); // optional params - //int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT1, 0); + //int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); std::shared_ptr localDestination = nullptr; if (keys.length () > 0) @@ -367,17 +367,30 @@ namespace client LogPrint (eLogError, "I2P client tunnel with port ", port, " already exists"); numClientTunnels++; } - catch (std::exception& ex) - { - LogPrint (eLogError, "Can't read client tunnel params: ", ex.what ()); + else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER) + { + // mandatory params + std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); + int port = section.second.get (I2P_SERVER_TUNNEL_PORT); + std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); + // optional params + + auto localDestination = LoadLocalDestination (keys, true); + auto serverTunnel = new I2PServerTunnel (host, port, localDestination); + if (m_ServerTunnels.insert (std::make_pair (localDestination->GetIdentHash (), std::unique_ptr(serverTunnel))).second) + serverTunnel->Start (); + else + LogPrint (eLogError, "I2P server tunnel for destination ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), " already exists"); + numServerTunnels++; } + else + LogPrint (eLogWarning, "Unknown section type=", type, " of ", name, " in ", TUNNELS_CONFIG_FILENAME); + } - else if (section.first == I2P_TUNNELS_SECTION_SERVER) + catch (std::exception& ex) { - numServerTunnels++; + LogPrint (eLogError, "Can't read tunnel ", name, " params: ", ex.what ()); } - else - LogPrint (eLogWarning, "Unknown section ", section.first, " in ", TUNNELS_CONFIG_FILENAME); } LogPrint (eLogInfo, numClientTunnels, " I2P client tunnels created"); LogPrint (eLogInfo, numServerTunnels, " I2P server tunnels created"); diff --git a/ClientContext.h b/ClientContext.h index d535c0c9..d0f57edc 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -17,21 +17,16 @@ namespace i2p { namespace client { - const char I2P_TUNNELS_SECTION_CLIENT[] = "client"; - const char I2P_TUNNELS_SECTION_SERVER[] = "server"; - const char I2P_CLIENT_TUNNEL_PORT1[] = "port"; - const char I2P_CLIENT_TUNNEL_DESTINATION1[] = "destination"; - const char I2P_CLIENT_TUNNEL_KEYS1[] = "keys"; - const char I2P_CLIENT_TUNNEL_DESTINATION_PORT1[] = "destinationport"; - - const char I2P_CLIENT_TUNNEL_NAME[] = "client.name"; - const char I2P_CLIENT_TUNNEL_PORT[] = "client.port"; - const char I2P_CLIENT_TUNNEL_DESTINATION[] = "client.destination"; - const char I2P_CLIENT_TUNNEL_KEYS[] = "client.keys"; - const char I2P_SERVER_TUNNEL_NAME[] = "server.name"; - const char I2P_SERVER_TUNNEL_HOST[] = "server.host"; - const char I2P_SERVER_TUNNEL_PORT[] = "server.port"; - const char I2P_SERVER_TUNNEL_KEYS[] = "server.keys"; + const char I2P_TUNNELS_SECTION_TYPE[] = "type"; + const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; + const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; + const char I2P_CLIENT_TUNNEL_PORT[] = "port"; + const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination"; + const char I2P_CLIENT_TUNNEL_KEYS[] = "keys"; + const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport"; + const char I2P_SERVER_TUNNEL_HOST[] = "host"; + const char I2P_SERVER_TUNNEL_PORT[] = "port"; + const char I2P_SERVER_TUNNEL_KEYS[] = "keys"; const char TUNNELS_CONFIG_FILENAME[] = "tunnels.cfg"; class ClientContext @@ -59,7 +54,6 @@ namespace client private: void ReadTunnels (); - void ReadTunnels1 (); // using propery tree private: From 897af615f9bdd536225941b3f0d64ce24ab5ce4c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 13 Mar 2015 22:11:51 -0400 Subject: [PATCH 0281/6300] create persistent destinations with ECDSA --- ClientContext.cpp | 77 +---------------------------------------------- 1 file changed, 1 insertion(+), 76 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index d0955c0c..cbc916fc 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -170,7 +170,7 @@ namespace client else { LogPrint ("Can't open file ", fullPath, " Creating new one"); - keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_DSA_SHA1); + keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); size_t len = keys.GetFullLen (); uint8_t * buf = new uint8_t[len]; @@ -253,81 +253,6 @@ namespace client return nullptr; } - /*void ClientContext::ReadTunnels () - { - std::ifstream ifs (i2p::util::filesystem::GetFullPath (TUNNELS_CONFIG_FILENAME)); - if (ifs.good ()) - { - boost::program_options::options_description params ("I2P tunnels parameters"); - params.add_options () - // client - (I2P_CLIENT_TUNNEL_NAME, boost::program_options::value >(), "tunnel name") - (I2P_CLIENT_TUNNEL_PORT, boost::program_options::value >(), "Local port") - (I2P_CLIENT_TUNNEL_DESTINATION, boost::program_options::value >(), "destination") - (I2P_CLIENT_TUNNEL_KEYS, boost::program_options::value >(), "keys") - // server - (I2P_SERVER_TUNNEL_NAME, boost::program_options::value >(), "tunnel name") - (I2P_SERVER_TUNNEL_HOST, boost::program_options::value >(), "host") - (I2P_SERVER_TUNNEL_PORT, boost::program_options::value >(), "port") - (I2P_SERVER_TUNNEL_KEYS, boost::program_options::value >(), "keys") - ; - - - boost::program_options::variables_map vm; - try - { - boost::program_options::store (boost::program_options::parse_config_file (ifs, params), vm); - boost::program_options::notify (vm); - } - catch (boost::program_options::error& ex) - { - LogPrint (eLogError, "Can't parse ", TUNNELS_CONFIG_FILENAME,": ", ex.what ()); - return; - } - - if (vm.count (I2P_CLIENT_TUNNEL_NAME) > 0) - { - auto names = vm[I2P_CLIENT_TUNNEL_NAME].as >(); - int numClientTunnels = names.size (); - auto ports = vm[I2P_CLIENT_TUNNEL_PORT].as >(); - auto destinations = vm[I2P_CLIENT_TUNNEL_DESTINATION].as >(); - auto keys = vm[I2P_CLIENT_TUNNEL_KEYS].as >(); - - for (int i = 0; i < numClientTunnels; i++) - { - std::shared_ptr localDestination = nullptr; - if (keys[i].length () > 0) - localDestination = LoadLocalDestination (keys[i], false); - auto clientTunnel = new I2PClientTunnel (destinations[i], ports[i], localDestination); - if (m_ClientTunnels.insert (std::make_pair (ports[i], std::unique_ptr(clientTunnel))).second) - clientTunnel->Start (); - else - LogPrint (eLogError, "I2P client tunnel with port ", ports[i], " already exists"); - } - LogPrint (eLogInfo, numClientTunnels, " I2P client tunnels created"); - } - - if (vm.count (I2P_SERVER_TUNNEL_NAME) > 0) - { - auto names = vm[I2P_SERVER_TUNNEL_NAME].as >(); - int numServerTunnels = names.size (); - auto hosts = vm[I2P_SERVER_TUNNEL_HOST].as >(); - auto ports = vm[I2P_SERVER_TUNNEL_PORT].as >(); - auto keys = vm[I2P_SERVER_TUNNEL_KEYS].as >(); - for (int i = 0; i < numServerTunnels; i++) - { - auto localDestination = LoadLocalDestination (keys[i], true); - auto serverTunnel = new I2PServerTunnel (hosts[i], ports[i], localDestination); - if (m_ServerTunnels.insert (std::make_pair (localDestination->GetIdentHash (), std::unique_ptr(serverTunnel))).second) - serverTunnel->Start (); - else - LogPrint (eLogError, "I2P server tunnel for destination ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), " already exists"); - } - LogPrint (eLogInfo, numServerTunnels, " I2P server tunnels created"); - } - } - }*/ - void ClientContext::ReadTunnels () { boost::property_tree::ptree pt; From 1d737409ecf611f53a3a052cd2ee777e6c82df3f Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 14 Mar 2015 17:15:23 -0400 Subject: [PATCH 0282/6300] fill padding with random data --- Identity.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Identity.cpp b/Identity.cpp index 410ddeb7..fa8eabde 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -52,12 +52,14 @@ namespace data case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: { size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64 + i2p::context.GetRandomNumberGenerator ().GenerateBlock (m_StandardIdentity.signingKey, padding); memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP256_KEY_LENGTH); break; } case SIGNING_KEY_TYPE_ECDSA_SHA384_P384: { size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96 + i2p::context.GetRandomNumberGenerator ().GenerateBlock (m_StandardIdentity.signingKey, padding); memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP384_KEY_LENGTH); break; } From fee68cf333997daa3f7c6db0d4ab3cf3b30826b0 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 15 Mar 2015 11:28:30 -0400 Subject: [PATCH 0283/6300] read optional destination port form config --- ClientContext.cpp | 9 +++++---- ClientContext.h | 1 + I2PTunnel.cpp | 5 +++-- I2PTunnel.h | 3 ++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index cbc916fc..fb85f88c 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -280,12 +280,12 @@ namespace client int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS); // optional params - //int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); + int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); std::shared_ptr localDestination = nullptr; if (keys.length () > 0) localDestination = LoadLocalDestination (keys, false); - auto clientTunnel = new I2PClientTunnel (dest, port, localDestination); + auto clientTunnel = new I2PClientTunnel (dest, port, localDestination, destinationPort); if (m_ClientTunnels.insert (std::make_pair (port, std::unique_ptr(clientTunnel))).second) clientTunnel->Start (); else @@ -299,9 +299,10 @@ namespace client int port = section.second.get (I2P_SERVER_TUNNEL_PORT); std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); // optional params - + int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); + auto localDestination = LoadLocalDestination (keys, true); - auto serverTunnel = new I2PServerTunnel (host, port, localDestination); + auto serverTunnel = new I2PServerTunnel (host, port, localDestination, inPort); if (m_ServerTunnels.insert (std::make_pair (localDestination->GetIdentHash (), std::unique_ptr(serverTunnel))).second) serverTunnel->Start (); else diff --git a/ClientContext.h b/ClientContext.h index d0f57edc..87ce4519 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -27,6 +27,7 @@ namespace client const char I2P_SERVER_TUNNEL_HOST[] = "host"; const char I2P_SERVER_TUNNEL_PORT[] = "port"; const char I2P_SERVER_TUNNEL_KEYS[] = "keys"; + const char I2P_SERVER_TUNNEL_INPORT[] = "inport"; const char TUNNELS_CONFIG_FILENAME[] = "tunnels.cfg"; class ClientContext diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 43398318..b4925438 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -246,10 +246,11 @@ namespace client return nullptr; } - I2PServerTunnel::I2PServerTunnel (const std::string& address, int port, std::shared_ptr localDestination): + I2PServerTunnel::I2PServerTunnel (const std::string& address, int port, + std::shared_ptr localDestination, int inport): I2PService (localDestination), m_Endpoint (boost::asio::ip::address::from_string (address), port) { - m_PortDestination = localDestination->CreateStreamingDestination (port); + m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port); } void I2PServerTunnel::Start () diff --git a/I2PTunnel.h b/I2PTunnel.h index 7c36f543..88e0083a 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -84,7 +84,8 @@ namespace client { public: - I2PServerTunnel (const std::string& address, int port, std::shared_ptr localDestination); + I2PServerTunnel (const std::string& address, int port, + std::shared_ptr localDestination, int inport = 0); void Start (); void Stop (); From f2665262f794a80c4eddb76503d9eb0c02ff4b09 Mon Sep 17 00:00:00 2001 From: Yuriy Grishin Date: Sun, 15 Mar 2015 08:51:07 -0700 Subject: [PATCH 0284/6300] Added MTU detection code for Windows --- util.cpp | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 179 insertions(+), 5 deletions(-) diff --git a/util.cpp b/util.cpp index 42541a75..6f6b2fc8 100644 --- a/util.cpp +++ b/util.cpp @@ -19,11 +19,45 @@ #if defined(__linux__) || defined(__FreeBSD_kernel__) #include #include -#endif - -#ifdef WIN32 -#include +#elif defined(WIN32) +#include +#include +#include +#include +#include +#include #include + +#pragma comment(lib, "IPHLPAPI.lib") + +#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) +#define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) + +int inet_pton(int af, const char *src, void *dst) +{ /* This function was written by Petar Korponai?. See +http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found */ + struct sockaddr_storage ss; + int size = sizeof (ss); + char src_copy[INET6_ADDRSTRLEN + 1]; + + ZeroMemory (&ss, sizeof (ss)); + strncpy_s (src_copy, src, INET6_ADDRSTRLEN + 1); + src_copy[INET6_ADDRSTRLEN] = 0; + + if (WSAStringToAddress (src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0) + { + switch (af) + { + case AF_INET: + *(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr; + return 1; + case AF_INET6: + *(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr; + return 1; + } + } + return 0; +} #endif namespace i2p @@ -514,8 +548,148 @@ namespace net freeifaddrs (ifaddr); return mtu; +#elif defined(WIN32) + + int result = 576; // fallback MTU + + DWORD dwRetVal = 0; + ULONG outBufLen = 0; + PIP_ADAPTER_ADDRESSES pAddresses = nullptr; + PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; + +#ifdef UNICODE + string localAddress_temporary = localAddress.to_string(); + wstring localAddressUniversal (localAddress_temporary.begin(), localAddress_temporary.end()); #else - return 0; + std::string localAddressUniversal = localAddress.to_string(); +#endif + + if (localAddress.is_v4()) + { + struct sockaddr_in inputAddress; + inet_pton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); + + if (GetAdaptersAddresses (AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) + == ERROR_BUFFER_OVERFLOW) + { + FREE (pAddresses); + pAddresses = (IP_ADAPTER_ADDRESSES *)MALLOC (outBufLen); + } + + dwRetVal = GetAdaptersAddresses (AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen); + if (dwRetVal == NO_ERROR) + { + pCurrAddresses = pAddresses; + while (pCurrAddresses) + { + PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; + + pUnicast = pCurrAddresses->FirstUnicastAddress; + if (pUnicast != nullptr) + { + for (int i = 0; pUnicast != nullptr; ++i) + { + LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; + struct sockaddr_in *localInterfaceAddress = (struct sockaddr_in*) lpAddr; + if (localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) + { + result = pAddresses->Mtu; + FREE (pAddresses); + pAddresses = nullptr; + return result; + } + pUnicast = pUnicast->Next; + } + } + else + { + LogPrint (eLogError, "GetMTU() has failed: not a unicast ipv4 address, this is not supported"); + } + + pCurrAddresses = pCurrAddresses->Next; + } + + } + else + { + LogPrint (eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed"); + } + + } + else if (localAddress.is_v6()) + { + struct sockaddr_in6 inputAddress; + inet_pton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); + + if (GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) + == ERROR_BUFFER_OVERFLOW) + { + FREE (pAddresses); + pAddresses = (IP_ADAPTER_ADDRESSES *)MALLOC(outBufLen); + } + + dwRetVal = GetAdaptersAddresses (AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen); + if (dwRetVal == NO_ERROR) + { + bool found_address = false; + pCurrAddresses = pAddresses; + while (pCurrAddresses) + { + PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; + + pUnicast = pCurrAddresses->FirstUnicastAddress; + if (pUnicast != nullptr) + { + for (int i = 0; pUnicast != nullptr; ++i) + { + LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; + struct sockaddr_in6 *localInterfaceAddress = (struct sockaddr_in6*) lpAddr; + + for (int j = 0; j != 8; ++j) + { + if (localInterfaceAddress->sin6_addr.u.Word[j] != inputAddress.sin6_addr.u.Word[j]) + { + break; + } + else + { + found_address = true; + } + } + if (found_address) + { + result = pAddresses->Mtu; + FREE (pAddresses); + pAddresses = nullptr; + return result; + } + pUnicast = pUnicast->Next; + } + } + else + { + LogPrint (eLogError, "GetMTU() has failed: not a unicast ipv6 address, this is not supported"); + } + + pCurrAddresses = pCurrAddresses->Next; + } + + } + else + { + LogPrint (eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed"); + } + } + else + { + LogPrint (eLogError, "GetMTU() has failed: address family is not supported"); + } + + FREE (pAddresses); + pAddresses = nullptr; + LogPrint(eLogError, "GetMTU() error: control flow should never reach this line"); + return result; #endif } } From f32399454cf0a91d0579ef7ed8cac71024c0cf07 Mon Sep 17 00:00:00 2001 From: Yuriy Grishin Date: Sun, 15 Mar 2015 10:07:39 -0700 Subject: [PATCH 0285/6300] Whitespace to follow coding style convention --- util.cpp | 188 +++++++++++++++++++++++++++---------------------------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/util.cpp b/util.cpp index 6f6b2fc8..fbaf2dbf 100644 --- a/util.cpp +++ b/util.cpp @@ -550,115 +550,50 @@ namespace net return mtu; #elif defined(WIN32) - int result = 576; // fallback MTU + int result = 576; // fallback MTU - DWORD dwRetVal = 0; - ULONG outBufLen = 0; - PIP_ADAPTER_ADDRESSES pAddresses = nullptr; - PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; - PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; + DWORD dwRetVal = 0; + ULONG outBufLen = 0; + PIP_ADAPTER_ADDRESSES pAddresses = nullptr; + PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; #ifdef UNICODE - string localAddress_temporary = localAddress.to_string(); - wstring localAddressUniversal (localAddress_temporary.begin(), localAddress_temporary.end()); + string localAddress_temporary = localAddress.to_string(); + wstring localAddressUniversal (localAddress_temporary.begin(), localAddress_temporary.end()); #else - std::string localAddressUniversal = localAddress.to_string(); + std::string localAddressUniversal = localAddress.to_string(); #endif - if (localAddress.is_v4()) - { - struct sockaddr_in inputAddress; + if (localAddress.is_v4()) + { + struct sockaddr_in inputAddress; inet_pton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); - if (GetAdaptersAddresses (AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) - == ERROR_BUFFER_OVERFLOW) - { - FREE (pAddresses); - pAddresses = (IP_ADAPTER_ADDRESSES *)MALLOC (outBufLen); - } - - dwRetVal = GetAdaptersAddresses (AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen); - if (dwRetVal == NO_ERROR) - { - pCurrAddresses = pAddresses; - while (pCurrAddresses) - { - PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; - - pUnicast = pCurrAddresses->FirstUnicastAddress; - if (pUnicast != nullptr) - { - for (int i = 0; pUnicast != nullptr; ++i) - { - LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; - struct sockaddr_in *localInterfaceAddress = (struct sockaddr_in*) lpAddr; - if (localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) - { - result = pAddresses->Mtu; - FREE (pAddresses); - pAddresses = nullptr; - return result; - } - pUnicast = pUnicast->Next; - } - } - else - { - LogPrint (eLogError, "GetMTU() has failed: not a unicast ipv4 address, this is not supported"); - } - - pCurrAddresses = pCurrAddresses->Next; - } - - } - else - { - LogPrint (eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed"); - } - - } - else if (localAddress.is_v6()) - { - struct sockaddr_in6 inputAddress; - inet_pton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); - - if (GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) + if (GetAdaptersAddresses (AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) - { + { FREE (pAddresses); - pAddresses = (IP_ADAPTER_ADDRESSES *)MALLOC(outBufLen); + pAddresses = (IP_ADAPTER_ADDRESSES *)MALLOC (outBufLen); } - dwRetVal = GetAdaptersAddresses (AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen); + dwRetVal = GetAdaptersAddresses (AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen); if (dwRetVal == NO_ERROR) - { - bool found_address = false; + { pCurrAddresses = pAddresses; while (pCurrAddresses) - { + { PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; if (pUnicast != nullptr) - { + { for (int i = 0; pUnicast != nullptr; ++i) - { + { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; - struct sockaddr_in6 *localInterfaceAddress = (struct sockaddr_in6*) lpAddr; - - for (int j = 0; j != 8; ++j) - { - if (localInterfaceAddress->sin6_addr.u.Word[j] != inputAddress.sin6_addr.u.Word[j]) - { - break; - } - else - { - found_address = true; - } - } - if (found_address) - { + struct sockaddr_in *localInterfaceAddress = (struct sockaddr_in*) lpAddr; + if (localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) + { result = pAddresses->Mtu; FREE (pAddresses); pAddresses = nullptr; @@ -668,7 +603,72 @@ namespace net } } else - { + { + LogPrint (eLogError, "GetMTU() has failed: not a unicast ipv4 address, this is not supported"); + } + + pCurrAddresses = pCurrAddresses->Next; + } + + } + else + { + LogPrint (eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed"); + } + + } + else if (localAddress.is_v6()) + { + struct sockaddr_in6 inputAddress; + inet_pton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); + + if (GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) + == ERROR_BUFFER_OVERFLOW) + { + FREE (pAddresses); + pAddresses = (IP_ADAPTER_ADDRESSES *)MALLOC (outBufLen); + } + + dwRetVal = GetAdaptersAddresses (AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen); + if (dwRetVal == NO_ERROR) + { + bool found_address = false; + pCurrAddresses = pAddresses; + while (pCurrAddresses) + { + PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; + + pUnicast = pCurrAddresses->FirstUnicastAddress; + if (pUnicast != nullptr) + { + for (int i = 0; pUnicast != nullptr; ++i) + { + LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; + struct sockaddr_in6 *localInterfaceAddress = (struct sockaddr_in6*) lpAddr; + + for (int j = 0; j != 8; ++j) + { + if (localInterfaceAddress->sin6_addr.u.Word[j] != inputAddress.sin6_addr.u.Word[j]) + { + break; + } + else + { + found_address = true; + } + } + if (found_address) + { + result = pAddresses->Mtu; + FREE (pAddresses); + pAddresses = nullptr; + return result; + } + pUnicast = pUnicast->Next; + } + } + else + { LogPrint (eLogError, "GetMTU() has failed: not a unicast ipv6 address, this is not supported"); } @@ -677,20 +677,20 @@ namespace net } else - { - LogPrint (eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed"); + { + LogPrint (eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed"); } } else - { - LogPrint (eLogError, "GetMTU() has failed: address family is not supported"); + { + LogPrint (eLogError, "GetMTU() has failed: address family is not supported"); } FREE (pAddresses); pAddresses = nullptr; LogPrint(eLogError, "GetMTU() error: control flow should never reach this line"); return result; -#endif +#endif } } From 76ad7f24eeee0c536333987f4b433af8b6e3c69d Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 16 Mar 2015 14:52:42 -0400 Subject: [PATCH 0286/6300] access list for server tunnels --- ClientContext.cpp | 18 +++++++++++++++++- ClientContext.h | 1 + I2PTunnel.cpp | 17 ++++++++++++++++- I2PTunnel.h | 6 +++++- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index fb85f88c..4073421b 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -300,9 +300,25 @@ namespace client std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); // optional params int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); - + std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); + auto localDestination = LoadLocalDestination (keys, true); auto serverTunnel = new I2PServerTunnel (host, port, localDestination, inPort); + if (accessList.length () > 0) + { + std::set idents; + size_t pos = 0, comma; + do + { + comma = accessList.find (',', pos); + i2p::data::IdentHash ident; + ident.FromBase32 (accessList.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); + idents.insert (ident); + pos = comma + 1; + } + while (comma != std::string::npos); + serverTunnel->SetAccessList (idents); + } if (m_ServerTunnels.insert (std::make_pair (localDestination->GetIdentHash (), std::unique_ptr(serverTunnel))).second) serverTunnel->Start (); else diff --git a/ClientContext.h b/ClientContext.h index 87ce4519..a034e541 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -28,6 +28,7 @@ namespace client const char I2P_SERVER_TUNNEL_PORT[] = "port"; const char I2P_SERVER_TUNNEL_KEYS[] = "keys"; const char I2P_SERVER_TUNNEL_INPORT[] = "inport"; + const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist"; const char TUNNELS_CONFIG_FILENAME[] = "tunnels.cfg"; class ClientContext diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index b4925438..3eb89f45 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -248,7 +248,7 @@ namespace client I2PServerTunnel::I2PServerTunnel (const std::string& address, int port, std::shared_ptr localDestination, int inport): - I2PService (localDestination), m_Endpoint (boost::asio::ip::address::from_string (address), port) + I2PService (localDestination), m_Endpoint (boost::asio::ip::address::from_string (address), port), m_IsAccessList (false) { m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port); } @@ -263,6 +263,12 @@ namespace client ClearHandlers (); } + void I2PServerTunnel::SetAccessList (const std::set& accessList) + { + m_AccessList = accessList; + m_IsAccessList = true; + } + void I2PServerTunnel::Accept () { if (m_PortDestination) @@ -282,6 +288,15 @@ namespace client { if (stream) { + if (m_IsAccessList) + { + if (!m_AccessList.count (stream->GetRemoteIdentity ().GetIdentHash ())) + { + LogPrint (eLogWarning, "Address ", stream->GetRemoteIdentity ().GetIdentHash ().ToBase32 (), " is not in white list. Incoming connection dropped"); + stream->Close (); + return; + } + } auto conn = std::make_shared (this, stream, new boost::asio::ip::tcp::socket (GetService ()), m_Endpoint); AddHandler (conn); conn->Connect (); diff --git a/I2PTunnel.h b/I2PTunnel.h index 88e0083a..41a067f8 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -90,6 +90,8 @@ namespace client void Start (); void Stop (); + void SetAccessList (const std::set& accessList); + private: void Accept (); @@ -98,7 +100,9 @@ namespace client private: boost::asio::ip::tcp::endpoint m_Endpoint; - std::shared_ptr m_PortDestination; + std::shared_ptr m_PortDestination; + std::set m_AccessList; + bool m_IsAccessList; }; } } From a72d7652af7bc44818c85aa6f6400084d3fcff8a Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 16 Mar 2015 18:57:22 -0400 Subject: [PATCH 0287/6300] moved num sent/received bytes to TransportSession --- NTCPSession.cpp | 3 +-- NTCPSession.h | 4 ---- SSUSession.cpp | 4 ++-- SSUSession.h | 1 - TransportSession.h | 7 ++++++- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 963bcb45..537958d8 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -21,8 +21,7 @@ namespace transport NTCPSession::NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter): TransportSession (in_RemoteRouter), m_Server (server), m_Socket (m_Server.GetService ()), m_TerminationTimer (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false), - m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_IsSending (false), - m_NumSentBytes (0), m_NumReceivedBytes (0) + m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_IsSending (false) { m_DHKeysPair = transports.GetNextDHKeysPair (); m_Establisher = new Establisher; diff --git a/NTCPSession.h b/NTCPSession.h index 608a2c7c..85a0aeb7 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -64,9 +64,6 @@ namespace transport void ServerLogin (); void SendI2NPMessage (I2NPMessage * msg); void SendI2NPMessages (const std::vector& msgs); - - size_t GetNumSentBytes () const { return m_NumSentBytes; }; - size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; private: @@ -138,7 +135,6 @@ namespace transport bool m_IsSending; std::vector m_SendQueue; - size_t m_NumSentBytes, m_NumReceivedBytes; boost::asio::ip::address m_ConnectedFrom; // for ban }; diff --git a/SSUSession.cpp b/SSUSession.cpp index 9fa71558..efd7db85 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -16,8 +16,8 @@ namespace transport SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, std::shared_ptr router, bool peerTest ): TransportSession (router), m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_Timer (GetService ()), - m_PeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), m_RelayTag (0), - m_NumSentBytes (0), m_NumReceivedBytes (0), m_Data (*this), m_IsDataReceived (false) + m_PeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), + m_RelayTag (0),m_Data (*this), m_IsDataReceived (false) { m_CreationTime = i2p::util::GetSecondsSinceEpoch (); } diff --git a/SSUSession.h b/SSUSession.h index 0889f416..82fa0f33 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -146,7 +146,6 @@ namespace transport i2p::crypto::CBCDecryption m_SessionKeyDecryption; i2p::crypto::AESKey m_SessionKey; i2p::crypto::MACKey m_MacKey; - size_t m_NumSentBytes, m_NumReceivedBytes; uint32_t m_CreationTime; // seconds since epoch SSUData m_Data; bool m_IsDataReceived; diff --git a/TransportSession.h b/TransportSession.h index 1067f763..95267452 100644 --- a/TransportSession.h +++ b/TransportSession.h @@ -55,7 +55,8 @@ namespace transport public: TransportSession (std::shared_ptr in_RemoteRouter): - m_RemoteRouter (in_RemoteRouter), m_DHKeysPair (nullptr) + m_RemoteRouter (in_RemoteRouter), m_DHKeysPair (nullptr), + m_NumSentBytes (0), m_NumReceivedBytes (0) { if (m_RemoteRouter) m_RemoteIdentity = m_RemoteRouter->GetRouterIdentity (); @@ -67,6 +68,9 @@ namespace transport std::shared_ptr GetRemoteRouter () { return m_RemoteRouter; }; const i2p::data::IdentityEx& GetRemoteIdentity () { return m_RemoteIdentity; }; + size_t GetNumSentBytes () const { return m_NumSentBytes; }; + size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; + virtual void SendI2NPMessage (I2NPMessage * msg) = 0; virtual void SendI2NPMessages (const std::vector& msgs) = 0; @@ -75,6 +79,7 @@ namespace transport std::shared_ptr m_RemoteRouter; i2p::data::IdentityEx m_RemoteIdentity; DHKeysPair * m_DHKeysPair; // X - for client and Y - for server + size_t m_NumSentBytes, m_NumReceivedBytes; }; } } From 028e3a6c35f70ac948cde74c54dbe09c1d13c31b Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 16 Mar 2015 19:33:59 -0400 Subject: [PATCH 0288/6300] show total send/received bytes --- HTTPServer.cpp | 2 ++ NTCPSession.cpp | 2 ++ SSUSession.cpp | 2 ++ Transports.cpp | 4 ++-- Transports.h | 9 ++++++++- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index c99640a0..3a40482f 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -660,6 +660,8 @@ namespace util } s << "
"; s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
"; + s << "Received: " << i2p::transport::transports.GetTotalReceivedBytes ()/1000 << "K
"; + s << "Sent: " << i2p::transport::transports.GetTotalSentBytes ()/1000 << "K
"; s << "Data path: " << i2p::util::filesystem::GetDataDir().string() << "

"; s << "Our external address:" << "
" ; for (auto& address : i2p::context.GetRouterInfo().GetAddresses()) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 537958d8..4d718450 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -496,6 +496,7 @@ namespace transport else { m_NumReceivedBytes += bytes_transferred; + i2p::transport::transports.UpdateReceivedBytes (bytes_transferred); m_ReceiveBufferOffset += bytes_transferred; if (m_ReceiveBufferOffset >= 16) @@ -661,6 +662,7 @@ namespace transport else { m_NumSentBytes += bytes_transferred; + i2p::transport::transports.UpdateSentBytes (bytes_transferred); if (!m_SendQueue.empty()) { Send (m_SendQueue); diff --git a/SSUSession.cpp b/SSUSession.cpp index efd7db85..d012bcd9 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -78,6 +78,7 @@ namespace transport void SSUSession::ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { m_NumReceivedBytes += len; + i2p::transport::transports.UpdateReceivedBytes (len); if (m_State == eSessionStateIntroduced) { // HolePunch received @@ -1107,6 +1108,7 @@ namespace transport void SSUSession::Send (const uint8_t * buf, size_t size) { m_NumSentBytes += size; + i2p::transport::transports.UpdateSentBytes (size); m_Server.Send (buf, size, m_RemoteEndpoint); } } diff --git a/Transports.cpp b/Transports.cpp index dff8768d..9d8a55e6 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -96,8 +96,8 @@ namespace transport Transports::Transports (): 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) { } diff --git a/Transports.h b/Transports.h index 208eeece..35416329 100644 --- a/Transports.h +++ b/Transports.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include "TransportSession.h" @@ -87,6 +88,11 @@ namespace transport void PeerConnected (std::shared_ptr session); void PeerDisconnected (std::shared_ptr session); + + void UpdateSentBytes (uint64_t numBytes) { m_TotalSentBytes += numBytes; }; + void UpdateReceivedBytes (uint64_t numBytes) { m_TotalReceivedBytes += numBytes; }; + uint64_t GetTotalSentBytes () const { return m_TotalSentBytes; }; + uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; }; private: @@ -118,7 +124,8 @@ namespace transport std::map m_Peers; DHKeysPairSupplier m_DHKeysPairSupplier; - + std::atomic m_TotalSentBytes, m_TotalReceivedBytes; + public: // for HTTP only From a09c67772c66db14c645c091fade7c680ed7c564 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 17 Mar 2015 11:44:01 -0400 Subject: [PATCH 0289/6300] specify keys file for proxy's local destination --- ClientContext.cpp | 12 ++- HTTPProxy.cpp | 84 ++++++++++------- HTTPProxy.h | 12 +-- README.md | 3 +- SOCKS.cpp | 223 ++++++++++++++++++++++++++++------------------ SOCKS.h | 9 +- 6 files changed, 209 insertions(+), 134 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 4073421b..051d12bc 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -37,11 +37,15 @@ namespace client m_SharedLocalDestination->Start (); } + std::shared_ptr localDestination; // proxies - m_HttpProxy = new i2p::proxy::HTTPProxy(i2p::util::config::GetArg("-httpproxyport", 4446)); + std::string proxyKeys = i2p::util::config::GetArg("-proxykeys", ""); + if (proxyKeys.length () > 0) + localDestination = LoadLocalDestination (proxyKeys, false); + m_HttpProxy = new i2p::proxy::HTTPProxy(i2p::util::config::GetArg("-httpproxyport", 4446), localDestination); m_HttpProxy->Start(); LogPrint("HTTP Proxy started"); - m_SocksProxy = new i2p::proxy::SOCKSProxy(i2p::util::config::GetArg("-socksproxyport", 4447)); + m_SocksProxy = new i2p::proxy::SOCKSProxy(i2p::util::config::GetArg("-socksproxyport", 4447), localDestination); m_SocksProxy->Start(); LogPrint("SOCKS Proxy Started"); @@ -49,7 +53,7 @@ namespace client std::string ircDestination = i2p::util::config::GetArg("-ircdest", ""); if (ircDestination.length () > 0) // ircdest is presented { - std::shared_ptr localDestination = nullptr; + localDestination = nullptr; std::string ircKeys = i2p::util::config::GetArg("-irckeys", ""); if (ircKeys.length () > 0) localDestination = LoadLocalDestination (ircKeys, false); @@ -62,7 +66,7 @@ namespace client std::string eepKeys = i2p::util::config::GetArg("-eepkeys", ""); if (eepKeys.length () > 0) // eepkeys file is presented { - auto localDestination = LoadLocalDestination (eepKeys, true); + localDestination = LoadLocalDestination (eepKeys, true); auto serverTunnel = new I2PServerTunnel (i2p::util::config::GetArg("-eephost", "127.0.0.1"), i2p::util::config::GetArg("-eepport", 80), localDestination); serverTunnel->Start (); diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 8f2a1654..8c7fb299 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -18,9 +18,11 @@ namespace i2p namespace proxy { static const size_t http_buffer_size = 8192; - class HTTPProxyHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { + class HTTPProxyHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this + { private: - enum state { + enum state + { GET_METHOD, GET_HOSTNAME, GET_HTTPV, @@ -53,6 +55,7 @@ namespace proxy state m_state;//Parsing state public: + HTTPProxyHandler(HTTPProxyServer * parent, boost::asio::ip::tcp::socket * sock) : I2PServiceHandler(parent), m_sock(sock) { EnterState(GET_METHOD); } @@ -92,7 +95,8 @@ namespace proxy std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPProxyHandler::EnterState(HTTPProxyHandler::state nstate) { + void HTTPProxyHandler::EnterState(HTTPProxyHandler::state nstate) + { m_state = nstate; } @@ -104,11 +108,10 @@ namespace proxy boost::regex rHTTP("http://(.*?)(:(\\d+))?(/.*)"); boost::smatch m; std::string path; - if(boost::regex_search(m_url, m, rHTTP, boost::match_extra)) { + if(boost::regex_search(m_url, m, rHTTP, boost::match_extra)) + { server=m[1].str(); - if(m[2].str() != "") { - port=m[3].str(); - } + if (m[2].str() != "") port=m[3].str(); path=m[4].str(); } LogPrint(eLogDebug,"--- HTTP Proxy server is: ",server, " port is: ", port, "\n path is: ",path); @@ -117,8 +120,10 @@ namespace proxy m_path = path; } - bool HTTPProxyHandler::ValidateHTTPRequest() { - if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) { + bool HTTPProxyHandler::ValidateHTTPRequest() + { + if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) + { LogPrint(eLogError,"--- HTTP Proxy unsupported version: ", m_version); HTTPRequestFailed(); //TODO: send right stuff return false; @@ -126,7 +131,8 @@ namespace proxy return true; } - void HTTPProxyHandler::HandleJumpServices() { + void HTTPProxyHandler::HandleJumpServices() + { static const char * helpermark1 = "?i2paddresshelper="; static const char * helpermark2 = "&i2paddresshelper="; size_t addressHelperPos1 = m_path.rfind (helpermark1); @@ -157,7 +163,8 @@ namespace proxy m_path.erase(addressHelperPos); } - bool HTTPProxyHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { + bool HTTPProxyHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) + { ExtractRequest(); //TODO: parse earlier if (!ValidateHTTPRequest()) return false; HandleJumpServices(); @@ -176,36 +183,42 @@ namespace proxy bool HTTPProxyHandler::HandleData(uint8_t *http_buff, std::size_t len) { assert(len); // This should always be called with a least a byte left to parse - while (len > 0) { + while (len > 0) + { //TODO: fallback to finding HOst: header if needed - switch (m_state) { + switch (m_state) + { case GET_METHOD: - switch (*http_buff) { + switch (*http_buff) + { case ' ': EnterState(GET_HOSTNAME); break; default: m_method.push_back(*http_buff); break; } - break; + break; case GET_HOSTNAME: - switch (*http_buff) { + switch (*http_buff) + { case ' ': EnterState(GET_HTTPV); break; default: m_url.push_back(*http_buff); break; } - break; + break; case GET_HTTPV: - switch (*http_buff) { + switch (*http_buff) + { case '\r': EnterState(GET_HTTPVNL); break; default: m_version.push_back(*http_buff); break; } - break; + break; case GET_HTTPVNL: - switch (*http_buff) { + switch (*http_buff) + { case '\n': EnterState(DONE); break; default: LogPrint(eLogError,"--- HTTP Proxy rejected invalid request ending with: ", ((int)*http_buff)); HTTPRequestFailed(); //TODO: add correct code return false; } - break; + break; default: LogPrint(eLogError,"--- HTTP Proxy invalid state: ", m_state); HTTPRequestFailed(); //TODO: add correct code 500 @@ -222,29 +235,33 @@ namespace proxy void HTTPProxyHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { LogPrint(eLogDebug,"--- HTTP Proxy sock recv: ", len); - if(ecode) { + if(ecode) + { LogPrint(eLogWarning," --- HTTP Proxy sock recv got error: ", ecode); Terminate(); return; } - if (HandleData(m_http_buff, len)) { - if (m_state == DONE) { + if (HandleData(m_http_buff, len)) + { + if (m_state == DONE) + { LogPrint(eLogInfo,"--- HTTP Proxy requested: ", m_url); GetOwner()->CreateStream (std::bind (&HTTPProxyHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_address, m_port); - } else { + } + else AsyncSockRead(); - } } } void HTTPProxyHandler::SentHTTPFailed(const boost::system::error_code & ecode) { - if (!ecode) { + if (!ecode) Terminate(); - } else { + else + { LogPrint (eLogError,"--- HTTP Proxy Closing socket after sending failure because: ", ecode.message ()); Terminate(); } @@ -252,21 +269,24 @@ namespace proxy void HTTPProxyHandler::HandleStreamRequestComplete (std::shared_ptr stream) { - if (stream) { + if (stream) + { if (Kill()) return; LogPrint (eLogInfo,"--- HTTP Proxy New I2PTunnel connection"); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); Done(shared_from_this()); - } else { + } + else + { LogPrint (eLogError,"--- HTTP Proxy Issue when creating the stream, check the previous warnings for more info."); HTTPRequestFailed(); // TODO: Send correct error message host unreachable } } - HTTPProxyServer::HTTPProxyServer(int port): - TCPIPAcceptor(port, i2p::client::context.GetSharedLocalDestination ()) + HTTPProxyServer::HTTPProxyServer(int port, std::shared_ptr localDestination): + TCPIPAcceptor(port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()) { } diff --git a/HTTPProxy.h b/HTTPProxy.h index 5c226926..e1e6e2fa 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -6,6 +6,7 @@ #include #include #include "I2PService.h" +#include "Destination.h" namespace i2p { @@ -13,18 +14,19 @@ namespace proxy { class HTTPProxyServer: public i2p::client::TCPIPAcceptor { + public: + + HTTPProxyServer(int port, std::shared_ptr localDestination = nullptr); + ~HTTPProxyServer() {}; + protected: // Implements TCPIPAcceptor std::shared_ptr CreateHandler(boost::asio::ip::tcp::socket * socket); const char* GetName() { return "HTTP Proxy"; } - - public: - HTTPProxyServer(int port); - ~HTTPProxyServer() {} }; typedef HTTPProxyServer HTTPProxy; } } -#endif \ No newline at end of file +#endif diff --git a/README.md b/README.md index 9afb84fc..d1b89b8c 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,10 @@ Cmdline options * --floodfill= - 1 if router is floodfill, off by default * --httpproxyport= - The port to listen on (HTTP Proxy) * --socksproxyport= - The port to listen on (SOCKS Proxy) +* --proxykeys= - optional keys file for proxy's local destination * --ircport= - The local port of IRC tunnel to listen on. 6668 by default * --ircdest= - I2P destination address of IRC server. For example irc.postman.i2p -* --irckeys= - optional keys file for local destination +* --irckeys= - optional keys file for tunnel's local destination * --eepkeys= - File name containing destination keys, for example privKeys.dat. The file will be created if it does not already exist (issue #110). * --eephost= - Address incoming trafic forward to. 127.0.0.1 by default diff --git a/SOCKS.cpp b/SOCKS.cpp index 9db8ee69..351853a6 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -17,10 +17,12 @@ namespace proxy static const size_t socks_buffer_size = 8192; static const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse - struct SOCKSDnsAddress { + struct SOCKSDnsAddress + { uint8_t size; char value[max_socks_hostname_size]; - void FromString (std::string str) { + void FromString (std::string str) + { size = str.length(); if (str.length() > max_socks_hostname_size) size = max_socks_hostname_size; memcpy(value,str.c_str(),size); @@ -30,9 +32,11 @@ namespace proxy }; class SOCKSServer; - class SOCKSHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { + class SOCKSHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this + { private: - enum state { + enum state + { GET_SOCKSV, GET_COMMAND, GET_PORT, @@ -49,18 +53,21 @@ namespace proxy GET5_HOST, DONE }; - enum authMethods { + enum authMethods + { AUTH_NONE = 0, //No authentication, skip to next step AUTH_GSSAPI = 1, //GSSAPI authentication AUTH_USERPASSWD = 2, //Username and password AUTH_UNACCEPTABLE = 0xff //No acceptable method found }; - enum addrTypes { + enum addrTypes + { ADDR_IPV4 = 1, //IPv4 address (4 octets) ADDR_DNS = 3, // DNS name (up to 255 octets) ADDR_IPV6 = 4 //IPV6 address (16 octets) }; - enum errTypes { + enum errTypes + { SOCKS5_OK = 0, // No error for SOCKS5 SOCKS5_GEN_FAIL = 1, // General server failure SOCKS5_RULE_DENIED = 2, // Connection disallowed by ruleset @@ -75,16 +82,19 @@ namespace proxy SOCKS4_IDENTD_MISSING = 92, // Couldn't connect to the identd server SOCKS4_IDENTD_DIFFER = 93 // The ID reported by the application and by identd differ }; - enum cmdTypes { + enum cmdTypes + { CMD_CONNECT = 1, // TCP Connect CMD_BIND = 2, // TCP Bind CMD_UDP = 3 // UDP associate }; - enum socksVersions { + enum socksVersions + { SOCKS4 = 4, // SOCKS4 SOCKS5 = 5 // SOCKS5 }; - union address { + union address + { uint32_t ip; SOCKSDnsAddress dns; uint8_t ipv6[16]; @@ -136,24 +146,26 @@ namespace proxy void SOCKSHandler::AsyncSockRead() { LogPrint(eLogDebug,"--- SOCKS async sock read"); - if(m_sock) { + if(m_sock) m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size), std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); - } else { + else LogPrint(eLogError,"--- SOCKS no socket for read"); - } } - void SOCKSHandler::Terminate() { + void SOCKSHandler::Terminate() + { if (Kill()) return; - if (m_sock) { + if (m_sock) + { LogPrint(eLogDebug,"--- SOCKS close sock"); m_sock->close(); delete m_sock; m_sock = nullptr; } - if (m_stream) { + if (m_stream) + { LogPrint(eLogDebug,"--- SOCKS close stream"); m_stream.reset (); } @@ -170,8 +182,7 @@ namespace proxy return boost::asio::const_buffers_1(m_response,8); } - boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, - const SOCKSHandler::address &addr, uint16_t port) + boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port) { size_t size = 6; assert(error <= SOCKS5_ADDR_UNSUP); @@ -179,7 +190,8 @@ namespace proxy m_response[1] = error; //Response code m_response[2] = '\x00'; //RSV m_response[3] = type; //Address type - switch (type) { + switch (type) + { case ADDR_IPV4: size = 10; htobe32buf(m_response+4,addr.ip); @@ -203,12 +215,15 @@ namespace proxy m_response[0] = '\x05'; //Version m_response[1] = m_authchosen; //Response code boost::asio::const_buffers_1 response(m_response,2); - if (m_authchosen == AUTH_UNACCEPTABLE) { + if (m_authchosen == AUTH_UNACCEPTABLE) + { LogPrint(eLogWarning,"--- SOCKS5 authentication negotiation failed"); boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, shared_from_this(), std::placeholders::_1)); return false; - } else { + } + else + { LogPrint(eLogDebug,"--- SOCKS5 choosing authentication method: ", m_authchosen); boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse, shared_from_this(), std::placeholders::_1)); @@ -221,16 +236,17 @@ namespace proxy { boost::asio::const_buffers_1 response(nullptr,0); assert(error != SOCKS4_OK && error != SOCKS5_OK); - switch (m_socksv) { + switch (m_socksv) + { case SOCKS4: LogPrint(eLogWarning,"--- SOCKS4 failed: ", error); if (error < SOCKS4_OK) error = SOCKS4_FAIL; //Transparently map SOCKS5 errors response = GenerateSOCKS4Response(error, m_4aip, m_port); - break; + break; case SOCKS5: LogPrint(eLogWarning,"--- SOCKS5 failed: ", error); response = GenerateSOCKS5Response(error, m_addrtype, m_address, m_port); - break; + break; } boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, shared_from_this(), std::placeholders::_1)); @@ -240,25 +256,27 @@ namespace proxy { boost::asio::const_buffers_1 response(nullptr,0); //TODO: this should depend on things like the command type and callbacks may change - switch (m_socksv) { + switch (m_socksv) + { case SOCKS4: LogPrint(eLogInfo,"--- SOCKS4 connection success"); response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port); - break; + break; case SOCKS5: LogPrint(eLogInfo,"--- SOCKS5 connection success"); auto s = i2p::client::context.GetAddressBook().ToAddress(GetOwner()->GetLocalDestination()->GetIdentHash()); address ad; ad.dns.FromString(s); //HACK only 16 bits passed in port as SOCKS5 doesn't allow for more response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, ad, m_stream->GetRecvStreamID()); - break; + break; } boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone, shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::EnterState(SOCKSHandler::state nstate, uint8_t parseleft) { - switch (nstate) { + switch (nstate) + { case GET_PORT: parseleft = 2; break; case GET_IPV4: m_addrtype = ADDR_IPV4; m_address.ip = 0; parseleft = 4; break; case GET4_IDENT: m_4aip = m_address.ip; break; @@ -271,28 +289,33 @@ namespace proxy m_state = nstate; } - bool SOCKSHandler::ValidateSOCKSRequest() { - if ( m_cmd != CMD_CONNECT ) { + bool SOCKSHandler::ValidateSOCKSRequest() + { + if ( m_cmd != CMD_CONNECT ) + { //TODO: we need to support binds and other shit! LogPrint(eLogError,"--- SOCKS unsupported command: ", m_cmd); SocksRequestFailed(SOCKS5_CMD_UNSUP); return false; } //TODO: we may want to support other address types! - if ( m_addrtype != ADDR_DNS ) { - switch (m_socksv) { + if ( m_addrtype != ADDR_DNS ) + { + switch (m_socksv) + { case SOCKS5: LogPrint(eLogError,"--- SOCKS5 unsupported address type: ", m_addrtype); - break; + break; case SOCKS4: LogPrint(eLogError,"--- SOCKS4a rejected because it's actually SOCKS4"); - break; + break; } SocksRequestFailed(SOCKS5_ADDR_UNSUP); return false; } //TODO: we may want to support other domains - if(m_addrtype == ADDR_DNS && m_address.dns.ToString().find(".i2p") == std::string::npos) { + if(m_addrtype == ADDR_DNS && m_address.dns.ToString().find(".i2p") == std::string::npos) + { LogPrint(eLogError,"--- SOCKS invalid hostname: ", m_address.dns.ToString()); SocksRequestFailed(SOCKS5_ADDR_UNSUP); return false; @@ -303,40 +326,45 @@ namespace proxy bool SOCKSHandler::HandleData(uint8_t *sock_buff, std::size_t len) { assert(len); // This should always be called with a least a byte left to parse - while (len > 0) { - switch (m_state) { + while (len > 0) + { + switch (m_state) + { case GET_SOCKSV: m_socksv = (SOCKSHandler::socksVersions) *sock_buff; - switch (*sock_buff) { + switch (*sock_buff) + { case SOCKS4: EnterState(GET_COMMAND); //Initialize the parser at the right position - break; + break; case SOCKS5: EnterState(GET5_AUTHNUM); //Initialize the parser at the right position - break; + break; default: LogPrint(eLogError,"--- SOCKS rejected invalid version: ", ((int)*sock_buff)); Terminate(); return false; } - break; + break; case GET5_AUTHNUM: EnterState(GET5_AUTH, *sock_buff); - break; + break; case GET5_AUTH: m_parseleft --; if (*sock_buff == AUTH_NONE) m_authchosen = AUTH_NONE; - if ( m_parseleft == 0 ) { + if ( m_parseleft == 0 ) + { if (!Socks5ChooseAuth()) return false; EnterState(GET5_REQUESTV); } - break; + break; case GET_COMMAND: - switch (*sock_buff) { + switch (*sock_buff) + { case CMD_CONNECT: case CMD_BIND: - break; + break; case CMD_UDP: if (m_socksv == SOCKS5) break; default: @@ -345,70 +373,80 @@ namespace proxy return false; } m_cmd = (SOCKSHandler::cmdTypes)*sock_buff; - switch (m_socksv) { + switch (m_socksv) + { case SOCKS5: EnterState(GET5_GETRSV); break; case SOCKS4: EnterState(GET_PORT); break; } - break; + break; case GET_PORT: m_port = (m_port << 8)|((uint16_t)*sock_buff); m_parseleft--; - if (m_parseleft == 0) { - switch (m_socksv) { + if (m_parseleft == 0) + { + switch (m_socksv) + { case SOCKS5: EnterState(DONE); break; case SOCKS4: EnterState(GET_IPV4); break; } } - break; + break; case GET_IPV4: m_address.ip = (m_address.ip << 8)|((uint32_t)*sock_buff); m_parseleft--; - if (m_parseleft == 0) { - switch (m_socksv) { + if (m_parseleft == 0) + { + switch (m_socksv) + { case SOCKS5: EnterState(GET_PORT); break; case SOCKS4: EnterState(GET4_IDENT); m_4aip = m_address.ip; break; } } - break; + break; case GET4_IDENT: - if (!*sock_buff) { - if( m_4aip == 0 || m_4aip > 255 ) { + if (!*sock_buff) + { + if( m_4aip == 0 || m_4aip > 255 ) EnterState(DONE); - } else { + else EnterState(GET4A_HOST); - } } - break; + break; case GET4A_HOST: - if (!*sock_buff) { + if (!*sock_buff) + { EnterState(DONE); break; } - if (m_address.dns.size >= max_socks_hostname_size) { + if (m_address.dns.size >= max_socks_hostname_size) + { LogPrint(eLogError,"--- SOCKS4a destination is too large"); SocksRequestFailed(SOCKS4_FAIL); return false; } m_address.dns.push_back(*sock_buff); - break; + break; case GET5_REQUESTV: - if (*sock_buff != SOCKS5) { + if (*sock_buff != SOCKS5) + { LogPrint(eLogError,"--- SOCKS5 rejected unknown request version: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } EnterState(GET_COMMAND); - break; + break; case GET5_GETRSV: - if ( *sock_buff != 0 ) { + if ( *sock_buff != 0 ) + { LogPrint(eLogError,"--- SOCKS5 unknown reserved field: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } EnterState(GET5_GETADDRTYPE); - break; + break; case GET5_GETADDRTYPE: - switch (*sock_buff) { + switch (*sock_buff) + { case ADDR_IPV4: EnterState(GET_IPV4); break; case ADDR_IPV6: EnterState(GET5_IPV6); break; case ADDR_DNS : EnterState(GET5_HOST_SIZE); break; @@ -417,20 +455,20 @@ namespace proxy SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } - break; + break; case GET5_IPV6: m_address.ipv6[16-m_parseleft] = *sock_buff; m_parseleft--; if (m_parseleft == 0) EnterState(GET_PORT); - break; + break; case GET5_HOST_SIZE: EnterState(GET5_HOST, *sock_buff); - break; + break; case GET5_HOST: m_address.dns.push_back(*sock_buff); m_parseleft--; if (m_parseleft == 0) EnterState(GET_PORT); - break; + break; default: LogPrint(eLogError,"--- SOCKS parse state?? ", m_state); Terminate(); @@ -438,7 +476,8 @@ namespace proxy } sock_buff++; len--; - if (m_state == DONE) { + if (m_state == DONE) + { m_remaining_data_len = len; m_remaining_data = sock_buff; return ValidateSOCKSRequest(); @@ -450,29 +489,32 @@ namespace proxy void SOCKSHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { LogPrint(eLogDebug,"--- SOCKS sock recv: ", len); - if(ecode) { + if(ecode) + { LogPrint(eLogWarning," --- SOCKS sock recv got error: ", ecode); - Terminate(); + Terminate(); return; } - if (HandleData(m_sock_buff, len)) { - if (m_state == DONE) { + if (HandleData(m_sock_buff, len)) + { + if (m_state == DONE) + { LogPrint(eLogInfo,"--- SOCKS requested ", m_address.dns.ToString(), ":" , m_port); GetOwner()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_address.dns.ToString(), m_port); - } else { + } + else AsyncSockRead(); - } } - } void SOCKSHandler::SentSocksFailed(const boost::system::error_code & ecode) { - if (!ecode) { + if (!ecode) Terminate(); - } else { + else + { LogPrint (eLogError,"--- SOCKS Closing socket after sending failure because: ", ecode.message ()); Terminate(); } @@ -480,8 +522,9 @@ namespace proxy void SOCKSHandler::SentSocksDone(const boost::system::error_code & ecode) { - if (!ecode) { - if (Kill()) return; + if (!ecode) + { + if (Kill()) return; LogPrint (eLogInfo,"--- SOCKS New I2PTunnel connection"); auto connection = std::make_shared(GetOwner(), m_sock, m_stream); GetOwner()->AddHandler (connection); @@ -497,7 +540,8 @@ namespace proxy void SOCKSHandler::SentSocksResponse(const boost::system::error_code & ecode) { - if (ecode) { + if (ecode) + { LogPrint (eLogError,"--- SOCKS Closing socket after sending reply because: ", ecode.message ()); Terminate(); } @@ -505,17 +549,20 @@ namespace proxy void SOCKSHandler::HandleStreamRequestComplete (std::shared_ptr stream) { - if (stream) { + if (stream) + { m_stream = stream; SocksRequestSuccess(); - } else { + } + else + { LogPrint (eLogError,"--- SOCKS Issue when creating the stream, check the previous warnings for more info."); SocksRequestFailed(SOCKS5_HOST_UNREACH); } } - SOCKSServer::SOCKSServer(int port) : - TCPIPAcceptor (port, i2p::client::context.GetSharedLocalDestination ()) + SOCKSServer::SOCKSServer(int port, std::shared_ptr localDestination) : + TCPIPAcceptor (port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()) { } diff --git a/SOCKS.h b/SOCKS.h index c946e2a2..deb3a58c 100644 --- a/SOCKS.h +++ b/SOCKS.h @@ -13,14 +13,15 @@ namespace proxy { class SOCKSServer: public i2p::client::TCPIPAcceptor { + public: + + SOCKSServer(int port, std::shared_ptr localDestination = nullptr); + ~SOCKSServer() {}; + protected: // Implements TCPIPAcceptor std::shared_ptr CreateHandler(boost::asio::ip::tcp::socket * socket); const char* GetName() { return "SOCKS"; } - - public: - SOCKSServer(int port); - ~SOCKSServer() {} }; typedef SOCKSServer SOCKSProxy; From 60c60b4db102217f88e26362bd3cb063f35b3728 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 17 Mar 2015 15:19:38 -0400 Subject: [PATCH 0290/6300] calculate bandwidth --- HTTPServer.cpp | 6 ++++-- Transports.cpp | 21 ++++++++++++++++++++- Transports.h | 13 ++++++++++--- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 3a40482f..ce336fe7 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -660,8 +660,10 @@ namespace util } s << "
"; s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
"; - s << "Received: " << i2p::transport::transports.GetTotalReceivedBytes ()/1000 << "K
"; - s << "Sent: " << i2p::transport::transports.GetTotalSentBytes ()/1000 << "K
"; + s << "Received: " << i2p::transport::transports.GetTotalReceivedBytes ()/1000 << "K"; + s << " (" << i2p::transport::transports.GetInBandwidth () <<" Bps)
"; + s << "Sent: " << i2p::transport::transports.GetTotalSentBytes ()/1000 << "K"; + s << " (" << i2p::transport::transports.GetOutBandwidth () <<" Bps)
"; s << "Data path: " << i2p::util::filesystem::GetDataDir().string() << "

"; s << "Our external address:" << "
" ; for (auto& address : i2p::context.GetRouterInfo().GetAddresses()) diff --git a/Transports.cpp b/Transports.cpp index 9d8a55e6..5afb6f36 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -97,7 +97,8 @@ namespace transport Transports::Transports (): m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_PeerCleanupTimer (m_Service), m_NTCPServer (nullptr), m_SSUServer (nullptr), m_DHKeysPairSupplier (5), // 5 pre-generated keys - m_TotalSentBytes(0), m_TotalReceivedBytes(0) + m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_InBandwidth (0), m_OutBandwidth (0), + m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0) { } @@ -181,6 +182,23 @@ namespace transport } } + void Transports::UpdateBandwidth () + { + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); + if (m_LastBandwidthUpdateTime > 0) + { + auto delta = ts - m_LastBandwidthUpdateTime; + if (delta > 0) + { + m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/ts; // per second + m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/ts; // per second + } + } + m_LastBandwidthUpdateTime = ts; + m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes; + m_LastOutBandwidthUpdateBytes = m_TotalSentBytes; + } + void Transports::SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) { @@ -471,6 +489,7 @@ namespace transport else it++; } + UpdateBandwidth (); // TODO: use separate timer(s) for it m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); } diff --git a/Transports.h b/Transports.h index 35416329..173ff87d 100644 --- a/Transports.h +++ b/Transports.h @@ -92,8 +92,10 @@ namespace transport void UpdateSentBytes (uint64_t numBytes) { m_TotalSentBytes += numBytes; }; void UpdateReceivedBytes (uint64_t numBytes) { m_TotalReceivedBytes += numBytes; }; uint64_t GetTotalSentBytes () const { return m_TotalSentBytes; }; - uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; }; - + uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; }; + uint32_t GetInBandwidth () const { return m_InBandwidth; }; // bytes per second + uint32_t GetOutBandwidth () const { return m_OutBandwidth; }; // bytes per second + private: void Run (); @@ -109,6 +111,7 @@ namespace transport void HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, i2p::data::IdentHash ident, std::shared_ptr resolver); + void UpdateBandwidth (); void DetectExternalIP (); private: @@ -124,8 +127,12 @@ namespace transport std::map m_Peers; DHKeysPairSupplier m_DHKeysPairSupplier; + std::atomic m_TotalSentBytes, m_TotalReceivedBytes; - + uint32_t m_InBandwidth, m_OutBandwidth; + uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes; + uint64_t m_LastBandwidthUpdateTime; + public: // for HTTP only From 708e17162c5c53554fa1a8e34bbe4c2c149962e2 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 17 Mar 2015 15:36:15 -0400 Subject: [PATCH 0291/6300] handle i2p.router.net.bw requests --- I2PControl.cpp | 12 ++++++++++++ I2PControl.h | 8 ++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 4954f804..971c6b9f 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -40,6 +40,8 @@ namespace client m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlService::NetDbActivePeersHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_STATUS] = &I2PControlService::StatusHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlService::TunnelsParticipatingHandler; + m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_IB_1S] = &I2PControlService::InboundBandwidth1S ; + m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_OB_1S] = &I2PControlService::OutboundBandwidth1S ; // RouterManager m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = &I2PControlService::ShutdownHandler; @@ -332,6 +334,16 @@ namespace client results[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = boost::lexical_cast(i2p::tunnel::tunnels.GetTransitTunnels ().size ()); } + void I2PControlService::InboundBandwidth1S (std::map& results) + { + results[I2P_CONTROL_ROUTER_INFO_BW_IB_1S] = boost::lexical_cast(i2p::transport::transports.GetInBandwidth ()); + } + + void I2PControlService::OutboundBandwidth1S (std::map& results) + { + results[I2P_CONTROL_ROUTER_INFO_BW_OB_1S] = boost::lexical_cast(i2p::transport::transports.GetOutBandwidth ()); + } + // RouterManager void I2PControlService::RouterManagerHandler (const std::map& params, std::map& results) diff --git a/I2PControl.h b/I2PControl.h index f7e76949..11ab3dc3 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -49,8 +49,10 @@ namespace client const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; const char I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers"; const char I2P_CONTROL_ROUTER_INFO_STATUS[] = "i2p.router.net.status"; - const char I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating"; - + const char I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating"; + const char I2P_CONTROL_ROUTER_INFO_BW_IB_1S[] = "i2p.router.net.bw.inbound.1s"; + const char I2P_CONTROL_ROUTER_INFO_BW_OB_1S[] = "i2p.router.net.bw.outbound.1s"; + // RouterManager requests const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN[] = "Shutdown"; const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful"; @@ -102,6 +104,8 @@ namespace client void NetDbActivePeersHandler (std::map& results); void StatusHandler (std::map& results); void TunnelsParticipatingHandler (std::map& results); + void InboundBandwidth1S (std::map& results); + void OutboundBandwidth1S (std::map& results); // RouterManager typedef void (I2PControlService::*RouterManagerRequestHandler)(std::map& results); From 1e2f038ef5a7d450ed89e3819eee467574f1fbc0 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 17 Mar 2015 19:04:58 -0400 Subject: [PATCH 0292/6300] proper badnwidth calculation --- Transports.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 5afb6f36..00fd6bf0 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -190,8 +190,8 @@ namespace transport auto delta = ts - m_LastBandwidthUpdateTime; if (delta > 0) { - m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/ts; // per second - m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/ts; // per second + m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/delta; // per second + m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/delta; // per second } } m_LastBandwidthUpdateTime = ts; From 60351f267715d0a3e15963c6e3f5283af5f36ba4 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 17 Mar 2015 20:56:51 -0400 Subject: [PATCH 0293/6300] send exporatory message directly if connected to a floodfill --- NetDb.cpp | 2 ++ Transports.cpp | 6 ++++++ Transports.h | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index fbddb57f..061fe1e2 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -828,6 +828,8 @@ namespace data if (floodfill && !floodfills.count (floodfill.get ())) // request floodfill only once { floodfills.insert (floodfill.get ()); + if (i2p::transport::transports.IsConnected (floodfill->GetIdentHash ())) + throughTunnels = false; if (throughTunnels) { msgs.push_back (i2p::tunnel::TunnelMessageBlock diff --git a/Transports.cpp b/Transports.cpp index 00fd6bf0..c8d413e7 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -474,6 +474,12 @@ namespace transport }); } + bool Transports::IsConnected (const i2p::data::IdentHash& ident) const + { + auto it = m_Peers.find (ident); + return it != m_Peers.end (); + } + void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) diff --git a/Transports.h b/Transports.h index 173ff87d..bcec3daa 100644 --- a/Transports.h +++ b/Transports.h @@ -88,7 +88,8 @@ namespace transport void PeerConnected (std::shared_ptr session); void PeerDisconnected (std::shared_ptr session); - + bool IsConnected (const i2p::data::IdentHash& ident) const; + void UpdateSentBytes (uint64_t numBytes) { m_TotalSentBytes += numBytes; }; void UpdateReceivedBytes (uint64_t numBytes) { m_TotalReceivedBytes += numBytes; }; uint64_t GetTotalSentBytes () const { return m_TotalSentBytes; }; From a5ada6f487a2e71f9b6dff98acd12d20512cfa04 Mon Sep 17 00:00:00 2001 From: Ilya Beda Date: Wed, 18 Mar 2015 11:26:17 +0600 Subject: [PATCH 0294/6300] Add libi2pd.so to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a059c66e..2da26ecd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ obj/*.o router.info router.keys i2p +libi2pd.so netDb # Autotools From 47c3d5ed231df6137b2e2803412c460743e1e9af Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 Mar 2015 13:07:11 -0400 Subject: [PATCH 0295/6300] don't accept tunnels if bandwidth is exceeded --- I2NPProtocol.cpp | 3 ++- NetDb.cpp | 2 +- RouterInfo.h | 1 + Transports.cpp | 5 +++++ Transports.h | 2 ++ 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 3aa012ce..42c66f9a 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -281,7 +281,8 @@ namespace i2p i2p::crypto::ElGamalDecrypt (i2p::context.GetEncryptionPrivateKey (), record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText); // replace record to reply if (i2p::context.AcceptsTunnels () && - i2p::tunnel::tunnels.GetTransitTunnels ().size () <= MAX_NUM_TRANSIT_TUNNELS) + i2p::tunnel::tunnels.GetTransitTunnels ().size () <= MAX_NUM_TRANSIT_TUNNELS && + !i2p::transport::transports.IsBandwidthExceeded ()) { i2p::tunnel::TransitTunnel * transitTunnel = i2p::tunnel::CreateTransitTunnel ( diff --git a/NetDb.cpp b/NetDb.cpp index 061fe1e2..147d0217 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -917,7 +917,7 @@ namespace data [compatibleWith](std::shared_ptr router)->bool { return !router->IsHidden () && router != compatibleWith && - router->IsCompatible (*compatibleWith) && (router->GetCaps () & RouterInfo::eHighBandwidth); + router->IsCompatible (*compatibleWith) && router->IsHighBandwidth (); }); } diff --git a/RouterInfo.h b/RouterInfo.h index 1211a257..007c1383 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -118,6 +118,7 @@ namespace data bool IsIntroducer () const { return m_Caps & eSSUIntroducer; }; bool IsPeerTesting () const { return m_Caps & eSSUTesting; }; bool IsHidden () const { return m_Caps & eHidden; }; + bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; uint8_t GetCaps () const { return m_Caps; }; void SetCaps (uint8_t caps); diff --git a/Transports.cpp b/Transports.cpp index c8d413e7..058acd27 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -199,6 +199,11 @@ namespace transport m_LastOutBandwidthUpdateBytes = m_TotalSentBytes; } + bool Transports::IsBandwidthExceeded () const + { + if (i2p::context.GetRouterInfo ().IsHighBandwidth ()) return false; + return std::max (m_InBandwidth, m_OutBandwidth) > LOW_BANDWIDTH_LIMIT; + } void Transports::SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) { diff --git a/Transports.h b/Transports.h index bcec3daa..64760dde 100644 --- a/Transports.h +++ b/Transports.h @@ -68,6 +68,7 @@ namespace transport }; const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds + const uint32_t LOW_BANDWIDTH_LIMIT = 32*1024; // 32KBs class Transports { public: @@ -96,6 +97,7 @@ namespace transport uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; }; uint32_t GetInBandwidth () const { return m_InBandwidth; }; // bytes per second uint32_t GetOutBandwidth () const { return m_OutBandwidth; }; // bytes per second + bool IsBandwidthExceeded () const; private: From 647bb501d19c61985edcc6aecd278faf322c1bda Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 Mar 2015 13:30:38 -0400 Subject: [PATCH 0296/6300] make sure packet size is multiple of 16 bytes --- SSUSession.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SSUSession.cpp b/SSUSession.cpp index d012bcd9..a5ba57f3 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -1094,6 +1094,8 @@ namespace transport { uint8_t buf[SSU_MTU_V4 + 18]; size_t msgSize = len + sizeof (SSUHeader); + size_t paddingSize = msgSize >> 4; // %16 + if (paddingSize > 0) msgSize += (16 - paddingSize); if (msgSize > SSU_MTU_V4) { LogPrint (eLogWarning, "SSU payload size ", msgSize, " exceeds MTU"); From 0b754ec65d39203623079803d84bd6490973c154 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 Mar 2015 15:06:15 -0400 Subject: [PATCH 0297/6300] publish number of leasets and routers for floodfill --- NetDb.cpp | 3 ++- NetDb.h | 2 +- RouterContext.cpp | 13 +++++++++++++ RouterContext.h | 4 ++++ RouterInfo.cpp | 7 ++++++- RouterInfo.h | 3 ++- 6 files changed, 28 insertions(+), 4 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 147d0217..9655b6c3 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -173,7 +173,8 @@ namespace data } lastSave = ts; } - if (ts - lastPublish >= 2400) // publish every 40 minutes + if (i2p::context.GetLastUpdateTime () > lastPublish || // our router has been updated + ts - lastPublish >= 2400) // or publish every 40 minutes { Publish (); lastPublish = ts; diff --git a/NetDb.h b/NetDb.h index 7b515e2a..c912049b 100644 --- a/NetDb.h +++ b/NetDb.h @@ -90,7 +90,7 @@ namespace data void Reseed (); - // for web interface + // for web interface and stats int GetNumRouters () const { return m_RouterInfos.size (); }; int GetNumFloodfills () const { return m_Floodfills.size (); }; int GetNumLeaseSets () const { return m_LeaseSets.size (); }; diff --git a/RouterContext.cpp b/RouterContext.cpp index 1a0121ed..681ca29f 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -1,10 +1,12 @@ #include #include #include +#include #include "CryptoConst.h" #include "RouterContext.h" #include "Timestamp.h" #include "I2NPProtocol.h" +#include "NetDb.h" #include "util.h" #include "version.h" @@ -54,6 +56,12 @@ namespace i2p void RouterContext::UpdateRouterInfo () { + if (m_IsFloodfill) + { + // update routers and leasesets + m_RouterInfo.SetProperty (ROUTER_INFO_PROPERTY_LEASESETS, boost::lexical_cast(i2p::data::netdb.GetNumLeaseSets ())); + m_RouterInfo.SetProperty (ROUTER_INFO_PROPERTY_ROUTERS, boost::lexical_cast(i2p::data::netdb.GetNumRouters ())); + } m_RouterInfo.CreateBuffer (m_Keys); m_RouterInfo.SaveToFile (i2p::util::filesystem::GetFullPath (ROUTER_INFO)); m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch (); @@ -115,7 +123,12 @@ namespace i2p if (floodfill) m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill); else + { m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill); + // we don't publish number of routers and leaseset for non-floodfill + m_RouterInfo.DeleteProperty (ROUTER_INFO_PROPERTY_LEASESETS); + m_RouterInfo.DeleteProperty (ROUTER_INFO_PROPERTY_ROUTERS); + } UpdateRouterInfo (); } diff --git a/RouterContext.h b/RouterContext.h index 24b761f9..1003f6cf 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -17,6 +17,9 @@ namespace i2p const char ROUTER_KEYS[] = "router.keys"; const int ROUTER_INFO_UPDATE_INTERVAL = 1800; // 30 minutes + const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets"; + const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters"; + enum RouterStatus { eRouterStatusOK = 0, @@ -40,6 +43,7 @@ namespace i2p CryptoPP::RandomNumberGenerator& GetRandomNumberGenerator () { return m_Rnd; }; uint32_t GetUptime () const; uint32_t GetStartupTime () const { return m_StartupTime; }; + uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; }; RouterStatus GetStatus () const { return m_Status; }; void SetStatus (RouterStatus status) { m_Status = status; }; diff --git a/RouterInfo.cpp b/RouterInfo.cpp index e9105db1..fb12ff9f 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -548,11 +548,16 @@ namespace data ExtractCaps (caps); } - void RouterInfo::SetProperty (const char * key, const char * value) + void RouterInfo::SetProperty (const std::string& key, const std::string& value) { m_Properties[key] = value; } + void RouterInfo::DeleteProperty (const std::string& key) + { + m_Properties.erase (key); + } + bool RouterInfo::IsFloodfill () const { return m_Caps & Caps::eFloodfill; diff --git a/RouterInfo.h b/RouterInfo.h index 007c1383..fb9a6f13 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -105,7 +105,8 @@ namespace data void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); bool AddIntroducer (const Address * address, uint32_t tag); bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); - void SetProperty (const char * key, const char * value); // called from RouterContext only + void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only + void DeleteProperty (const std::string& key); // called from RouterContext only void ClearProperties () { m_Properties.clear (); }; bool IsFloodfill () const; bool IsNTCP (bool v4only = true) const; From 1caacaacf080028905b92f0779fbe024727aa50e Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 Mar 2015 15:36:07 -0400 Subject: [PATCH 0298/6300] UpdateStats for RouterContext --- NetDb.cpp | 1 + RouterContext.cpp | 17 +++++++++++------ RouterContext.h | 3 ++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 9655b6c3..2f30890b 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -861,6 +861,7 @@ namespace data void NetDb::Publish () { + i2p::context.UpdateStats (); std::set excluded; // TODO: fill up later for (int i = 0; i < 2; i++) { diff --git a/RouterContext.cpp b/RouterContext.cpp index 681ca29f..c524486d 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -56,12 +56,6 @@ namespace i2p void RouterContext::UpdateRouterInfo () { - if (m_IsFloodfill) - { - // update routers and leasesets - m_RouterInfo.SetProperty (ROUTER_INFO_PROPERTY_LEASESETS, boost::lexical_cast(i2p::data::netdb.GetNumLeaseSets ())); - m_RouterInfo.SetProperty (ROUTER_INFO_PROPERTY_ROUTERS, boost::lexical_cast(i2p::data::netdb.GetNumRouters ())); - } m_RouterInfo.CreateBuffer (m_Keys); m_RouterInfo.SaveToFile (i2p::util::filesystem::GetFullPath (ROUTER_INFO)); m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch (); @@ -233,6 +227,17 @@ namespace i2p if (updated) UpdateRouterInfo (); } + + void RouterContext::UpdateStats () + { + if (m_IsFloodfill) + { + // update routers and leasesets + m_RouterInfo.SetProperty (ROUTER_INFO_PROPERTY_LEASESETS, boost::lexical_cast(i2p::data::netdb.GetNumLeaseSets ())); + m_RouterInfo.SetProperty (ROUTER_INFO_PROPERTY_ROUTERS, boost::lexical_cast(i2p::data::netdb.GetNumRouters ())); + UpdateRouterInfo (); + } + } bool RouterContext::Load () { diff --git a/RouterContext.h b/RouterContext.h index 1003f6cf..483f3feb 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -60,7 +60,8 @@ namespace i2p void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; }; bool SupportsV6 () const { return m_RouterInfo.IsV6 (); }; void SetSupportsV6 (bool supportsV6); - void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session + void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session + void UpdateStats (); // implements LocalDestination const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; From 123e6b7de46d6f754ae72985b27d4add25549566 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Mar 2015 10:22:01 -0400 Subject: [PATCH 0299/6300] made keys parameter optional for client tunnels --- ClientContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 051d12bc..a1a1e395 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -282,8 +282,8 @@ namespace client // mandatory params std::string dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); - std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS); // optional params + std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); std::shared_ptr localDestination = nullptr; From 3ccc8d9508f9dc292045cc46db9604f0701026bf Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Mar 2015 11:14:21 -0400 Subject: [PATCH 0300/6300] badwidth parameter --- Daemon.cpp | 10 +++++++++- README.md | 1 + RouterContext.cpp | 18 ++++++++++++++++++ RouterContext.h | 2 ++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Daemon.cpp b/Daemon.cpp index 42a78280..ed0c74d3 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -76,7 +76,15 @@ namespace i2p i2p::context.SetSupportsV6 (i2p::util::config::GetArg("-v6", 0)); i2p::context.SetFloodfill (i2p::util::config::GetArg("-floodfill", 0)); - + auto bandwidth = i2p::util::config::GetArg("-badnwidth", ""); + if (bandwidth.length () > 0) + { + if (bandwidth[0] > 'L') + i2p::context.SetHighBandwidth (); + else + i2p::context.SetLowBandwidth (); + } + LogPrint("CMD parameters:"); for (int i = 0; i < argc; ++i) LogPrint(i, " ", argv[i]); diff --git a/README.md b/README.md index d1b89b8c..5120899c 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ Cmdline options * --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). * --v6= - 1 if supports communication through ipv6, off by default * --floodfill= - 1 if router is floodfill, off by default +* --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O if not. Always O if floodfill, otherwise L by default. * --httpproxyport= - The port to listen on (HTTP Proxy) * --socksproxyport= - The port to listen on (SOCKS Proxy) * --proxykeys= - optional keys file for proxy's local destination diff --git a/RouterContext.cpp b/RouterContext.cpp index c524486d..e3cd358d 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -126,6 +126,24 @@ namespace i2p UpdateRouterInfo (); } + void RouterContext::SetHighBandwidth () + { + if (!m_RouterInfo.IsHighBandwidth ()) + { + m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eHighBandwidth); + UpdateRouterInfo (); + } + } + + void RouterContext::SetLowBandwidth () + { + if (m_RouterInfo.IsHighBandwidth ()) + { + m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eHighBandwidth); + UpdateRouterInfo (); + } + } + bool RouterContext::IsUnreachable () const { return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable; diff --git a/RouterContext.h b/RouterContext.h index 483f3feb..7492a4e2 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -56,6 +56,8 @@ namespace i2p void SetReachable (); bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); + void SetHighBandwidth (); + void SetLowBandwidth (); bool AcceptsTunnels () const { return m_AcceptsTunnels; }; void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; }; bool SupportsV6 () const { return m_RouterInfo.IsV6 (); }; From 4e1e47d9e5889e3351e6087251b3d9e01dc40892 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Mar 2015 11:52:46 -0400 Subject: [PATCH 0301/6300] always use 'O' class for high-bandwidth --- RouterInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index fb12ff9f..66eca3b4 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -282,7 +282,7 @@ namespace data caps += CAPS_FLAG_FLOODFILL; // floodfill } else - caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH1 : CAPS_FLAG_LOW_BANDWIDTH2; // bandwidth + caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 : CAPS_FLAG_LOW_BANDWIDTH2; // bandwidth if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable if (m_Caps & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable From 7ea12ddd6171fc89855586c04d0e83d843d0faa7 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Mar 2015 21:51:18 -0400 Subject: [PATCH 0302/6300] RouterInfo version and status --- I2PControl.cpp | 26 +++++++++++++++++++------- I2PControl.h | 8 ++++++-- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 971c6b9f..a3bdd6a0 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -17,6 +17,7 @@ #include "Tunnel.h" #include "Timestamp.h" #include "Transports.h" +#include "version.h" namespace i2p { @@ -36,9 +37,11 @@ namespace client // RouterInfo m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_UPTIME] = &I2PControlService::UptimeHandler; + m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_VERSION] = &I2PControlService::VersionHandler; + m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_STATUS] = &I2PControlService::StatusHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = &I2PControlService::NetDbKnownPeersHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlService::NetDbActivePeersHandler; - m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_STATUS] = &I2PControlService::StatusHandler; + m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NET_STATUS] = &I2PControlService::NetStatusHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlService::TunnelsParticipatingHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_IB_1S] = &I2PControlService::InboundBandwidth1S ; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_OB_1S] = &I2PControlService::OutboundBandwidth1S ; @@ -110,6 +113,7 @@ namespace client if (!ecode) { LogPrint (eLogInfo, "New I2PControl request from ", socket->remote_endpoint ()); + std::this_thread::sleep_for (std::chrono::milliseconds(5)); ReadRequest (socket); } else @@ -168,17 +172,15 @@ namespace client std::map params; for (auto& v: pt.get_child (I2P_CONTROL_PROPERTY_PARAMS)) { - LogPrint (eLogInfo, v.first); if (!v.first.empty()) { if (v.first == I2P_CONTROL_PARAM_TOKEN) { - if (!m_Tokens.count (v.second.data ())) + if (!v.second.empty () && !m_Tokens.count (v.second.data ())) { LogPrint (eLogWarning, "Unknown token ", v.second.data ()); return; - } - + } } else params[v.first] = v.second.data (); @@ -314,6 +316,16 @@ namespace client results[I2P_CONTROL_ROUTER_INFO_UPTIME] = boost::lexical_cast(i2p::context.GetUptime ()*1000); } + void I2PControlService::VersionHandler (std::map& results) + { + results[I2P_CONTROL_ROUTER_INFO_VERSION] = VERSION; + } + + void I2PControlService::StatusHandler (std::map& results) + { + results[I2P_CONTROL_ROUTER_INFO_STATUS] = "???"; // TODO: + } + void I2PControlService::NetDbKnownPeersHandler (std::map& results) { results[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = boost::lexical_cast(i2p::data::netdb.GetNumRouters ()); @@ -324,9 +336,9 @@ namespace client results[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = boost::lexical_cast(i2p::transport::transports.GetPeers ().size ()); } - void I2PControlService::StatusHandler (std::map& results) + void I2PControlService::NetStatusHandler (std::map& results) { - results[I2P_CONTROL_ROUTER_INFO_STATUS] = boost::lexical_cast((int)i2p::context.GetStatus ()); + results[I2P_CONTROL_ROUTER_INFO_NET_STATUS] = boost::lexical_cast((int)i2p::context.GetStatus ()); } void I2PControlService::TunnelsParticipatingHandler (std::map& results) diff --git a/I2PControl.h b/I2PControl.h index 11ab3dc3..daffe53f 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -46,9 +46,11 @@ namespace client // RouterInfo requests const char I2P_CONTROL_ROUTER_INFO_UPTIME[] = "i2p.router.uptime"; + const char I2P_CONTROL_ROUTER_INFO_VERSION[] = "i2p.router.version"; + const char I2P_CONTROL_ROUTER_INFO_STATUS[] = "i2p.router.status"; const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; const char I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers"; - const char I2P_CONTROL_ROUTER_INFO_STATUS[] = "i2p.router.net.status"; + const char I2P_CONTROL_ROUTER_INFO_NET_STATUS[] = "i2p.router.net.status"; const char I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating"; const char I2P_CONTROL_ROUTER_INFO_BW_IB_1S[] = "i2p.router.net.bw.inbound.1s"; const char I2P_CONTROL_ROUTER_INFO_BW_OB_1S[] = "i2p.router.net.bw.outbound.1s"; @@ -100,9 +102,11 @@ namespace client // RouterInfo typedef void (I2PControlService::*RouterInfoRequestHandler)(std::map& results); void UptimeHandler (std::map& results); + void VersionHandler (std::map& results); + void StatusHandler (std::map& results); void NetDbKnownPeersHandler (std::map& results); void NetDbActivePeersHandler (std::map& results); - void StatusHandler (std::map& results); + void NetStatusHandler (std::map& results); void TunnelsParticipatingHandler (std::map& results); void InboundBandwidth1S (std::map& results); void OutboundBandwidth1S (std::map& results); From b26c960450975c9bcb27f14b2a91d0541e7fc7bc Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Mar 2015 22:18:18 -0400 Subject: [PATCH 0303/6300] fixed outgoing message size --- I2PControl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index a3bdd6a0..662a5cbc 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -241,7 +241,7 @@ namespace client memcpy (buf->data (), header.str ().c_str (), offset); } memcpy (buf->data () + offset, ss.str ().c_str (), len); - boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), len), + boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), boost::asio::transfer_all (), std::bind(&I2PControlService::HandleResponseSent, this, std::placeholders::_1, std::placeholders::_2, socket, buf)); From 41ad5d12a32a4713c29fa48b22aac549a08b4be9 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Mar 2015 11:36:57 -0400 Subject: [PATCH 0304/6300] use propery tree for params --- I2PControl.cpp | 112 +++++++++++++++++++++---------------------------- I2PControl.h | 52 ++++++++++++----------- 2 files changed, 76 insertions(+), 88 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 662a5cbc..2863e2c7 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #if !GCC47_BOOST149 #include #endif @@ -169,25 +168,8 @@ namespace client auto it = m_MethodHandlers.find (method); if (it != m_MethodHandlers.end ()) { - std::map params; - for (auto& v: pt.get_child (I2P_CONTROL_PROPERTY_PARAMS)) - { - if (!v.first.empty()) - { - if (v.first == I2P_CONTROL_PARAM_TOKEN) - { - if (!v.second.empty () && !m_Tokens.count (v.second.data ())) - { - LogPrint (eLogWarning, "Unknown token ", v.second.data ()); - return; - } - } - else - params[v.first] = v.second.data (); - } - } - std::map results; - (this->*(it->second))(params, results); + boost::property_tree::ptree results; + (this->*(it->second))(pt.get_child (I2P_CONTROL_PROPERTY_PARAMS), results); SendResponse (socket, buf, pt.get(I2P_CONTROL_PROPERTY_ID), results, isHtml); } else @@ -205,17 +187,19 @@ namespace client } } + template + void I2PControlService::InsertParam (boost::property_tree::ptree& pt, const std::string& name, T value) const + { + pt.put (boost::property_tree::ptree::path_type (name, '/'), value); + } + void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, const std::string& id, - const std::map& results, bool isHtml) + boost::property_tree::ptree& results, bool isHtml) { - boost::property_tree::ptree ptr; - for (auto& result: results) - ptr.put (boost::property_tree::ptree::path_type (result.first, '/'), result.second); - boost::property_tree::ptree pt; pt.put (I2P_CONTROL_PROPERTY_ID, id); - pt.put_child (I2P_CONTROL_PROPERTY_RESULT, ptr); + pt.put_child (I2P_CONTROL_PROPERTY_RESULT, results); pt.put ("jsonrpc", "2.0"); std::ostringstream ss; @@ -257,30 +241,30 @@ namespace client // handlers - void I2PControlService::AuthenticateHandler (const std::map& params, std::map& results) + void I2PControlService::AuthenticateHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results) { - const std::string& api = params.at (I2P_CONTROL_PARAM_API); - const std::string& password = params.at (I2P_CONTROL_PARAM_PASSWORD); + int api = params.get (I2P_CONTROL_PARAM_API); + auto password = params.get (I2P_CONTROL_PARAM_PASSWORD); LogPrint (eLogDebug, "I2PControl Authenticate API=", api, " Password=", password); if (password != m_Password) LogPrint (eLogError, "I2PControl Authenticate Invalid password ", password, " expected ", m_Password); - results[I2P_CONTROL_PARAM_API] = api; + InsertParam (results, I2P_CONTROL_PARAM_API, api); std::string token = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); m_Tokens.insert (token); - results[I2P_CONTROL_PARAM_TOKEN] = token; + InsertParam (results, I2P_CONTROL_PARAM_TOKEN, token); } - void I2PControlService::EchoHandler (const std::map& params, std::map& results) + void I2PControlService::EchoHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results) { - const std::string& echo = params.at (I2P_CONTROL_PARAM_ECHO); + auto echo = params.get (I2P_CONTROL_PARAM_ECHO); LogPrint (eLogDebug, "I2PControl Echo Echo=", echo); - results[I2P_CONTROL_PARAM_RESULT] = echo; + InsertParam (results, I2P_CONTROL_PARAM_RESULT, echo); } // I2PControl - void I2PControlService::I2PControlHandler (const std::map& params, std::map& results) + void I2PControlService::I2PControlHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results) { LogPrint (eLogDebug, "I2PControl I2PControl"); for (auto& it: params) @@ -288,7 +272,7 @@ namespace client LogPrint (eLogDebug, it.first); auto it1 = m_I2PControlHandlers.find (it.first); if (it1 != m_I2PControlHandlers.end ()) - (this->*(it1->second))(it.second); + (this->*(it1->second))(it.second.data ()); else LogPrint (eLogError, "I2PControl NetworkSetting unknown request ", it.first); } @@ -296,7 +280,7 @@ namespace client // RouterInfo - void I2PControlService::RouterInfoHandler (const std::map& params, std::map& results) + void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results) { LogPrint (eLogDebug, "I2PControl RouterInfo"); for (auto& it: params) @@ -311,54 +295,54 @@ namespace client } } - void I2PControlService::UptimeHandler (std::map& results) + void I2PControlService::UptimeHandler (boost::property_tree::ptree& results) { - results[I2P_CONTROL_ROUTER_INFO_UPTIME] = boost::lexical_cast(i2p::context.GetUptime ()*1000); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_UPTIME, i2p::context.GetUptime ()*1000); } - void I2PControlService::VersionHandler (std::map& results) + void I2PControlService::VersionHandler (boost::property_tree::ptree& results) { - results[I2P_CONTROL_ROUTER_INFO_VERSION] = VERSION; + InsertParam (results, I2P_CONTROL_ROUTER_INFO_VERSION, VERSION); } - void I2PControlService::StatusHandler (std::map& results) + void I2PControlService::StatusHandler (boost::property_tree::ptree& results) { - results[I2P_CONTROL_ROUTER_INFO_STATUS] = "???"; // TODO: + InsertParam (results, I2P_CONTROL_ROUTER_INFO_STATUS, "???"); // TODO: } - void I2PControlService::NetDbKnownPeersHandler (std::map& results) + void I2PControlService::NetDbKnownPeersHandler (boost::property_tree::ptree& results) { - results[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = boost::lexical_cast(i2p::data::netdb.GetNumRouters ()); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS, i2p::data::netdb.GetNumRouters ()); } - void I2PControlService::NetDbActivePeersHandler (std::map& results) + void I2PControlService::NetDbActivePeersHandler (boost::property_tree::ptree& results) { - results[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = boost::lexical_cast(i2p::transport::transports.GetPeers ().size ()); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS, i2p::transport::transports.GetPeers ().size ()); } - void I2PControlService::NetStatusHandler (std::map& results) + void I2PControlService::NetStatusHandler (boost::property_tree::ptree& results) { - results[I2P_CONTROL_ROUTER_INFO_NET_STATUS] = boost::lexical_cast((int)i2p::context.GetStatus ()); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_NET_STATUS, (int)i2p::context.GetStatus ()); } - void I2PControlService::TunnelsParticipatingHandler (std::map& results) + void I2PControlService::TunnelsParticipatingHandler (boost::property_tree::ptree& results) { - results[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = boost::lexical_cast(i2p::tunnel::tunnels.GetTransitTunnels ().size ()); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING, i2p::tunnel::tunnels.GetTransitTunnels ().size ()); } - void I2PControlService::InboundBandwidth1S (std::map& results) + void I2PControlService::InboundBandwidth1S (boost::property_tree::ptree& results) { - results[I2P_CONTROL_ROUTER_INFO_BW_IB_1S] = boost::lexical_cast(i2p::transport::transports.GetInBandwidth ()); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_BW_IB_1S, i2p::transport::transports.GetInBandwidth ()); } - void I2PControlService::OutboundBandwidth1S (std::map& results) + void I2PControlService::OutboundBandwidth1S (boost::property_tree::ptree& results) { - results[I2P_CONTROL_ROUTER_INFO_BW_OB_1S] = boost::lexical_cast(i2p::transport::transports.GetOutBandwidth ()); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_BW_OB_1S, i2p::transport::transports.GetOutBandwidth ()); } // RouterManager - void I2PControlService::RouterManagerHandler (const std::map& params, std::map& results) + void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results) { LogPrint (eLogDebug, "I2PControl RouterManager"); for (auto& it: params) @@ -373,10 +357,10 @@ namespace client } - void I2PControlService::ShutdownHandler (std::map& results) + void I2PControlService::ShutdownHandler (boost::property_tree::ptree& results) { LogPrint (eLogInfo, "Shutdown requested"); - results[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = ""; + InsertParam (results, I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) @@ -385,12 +369,12 @@ namespace client }); } - void I2PControlService::ShutdownGracefulHandler (std::map& results) + void I2PControlService::ShutdownGracefulHandler (boost::property_tree::ptree& results) { i2p::context.SetAcceptsTunnels (false); int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout (); LogPrint (eLogInfo, "Graceful shutdown requested. Will shutdown after ", timeout, " seconds"); - results[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = ""; + InsertParam (results, I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL, ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) @@ -399,15 +383,15 @@ namespace client }); } - void I2PControlService::ReseedHandler (std::map& results) + void I2PControlService::ReseedHandler (boost::property_tree::ptree& results) { LogPrint (eLogInfo, "Reseed requested"); - results[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = ""; + InsertParam (results, I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, ""); i2p::data::netdb.Reseed (); } // network setting - void I2PControlService::NetworkSettingHandler (const std::map& params, std::map& results) + void I2PControlService::NetworkSettingHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results) { LogPrint (eLogDebug, "I2PControl NetworkSetting"); for (auto& it: params) @@ -415,7 +399,7 @@ namespace client LogPrint (eLogDebug, it.first); auto it1 = m_NetworkSettingHandlers.find (it.first); if (it1 != m_NetworkSettingHandlers.end ()) - (this->*(it1->second))(it.second, results); + (this->*(it1->second))(it.second.data (), results); else LogPrint (eLogError, "I2PControl NetworkSetting unknown request ", it.first); } diff --git a/I2PControl.h b/I2PControl.h index daffe53f..c9d22d59 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace i2p { @@ -80,45 +81,48 @@ namespace client std::shared_ptr socket, std::shared_ptr buf); void SendResponse (std::shared_ptr socket, std::shared_ptr buf, const std::string& id, - const std::map& results, bool isHtml); + boost::property_tree::ptree& results, bool isHtml); void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); private: - // methods - typedef void (I2PControlService::*MethodHandler)(const std::map& params, std::map& results); + template + void InsertParam (boost::property_tree::ptree& pt, const std::string& name, T value) const; - void AuthenticateHandler (const std::map& params, std::map& results); - void EchoHandler (const std::map& params, std::map& results); - void I2PControlHandler (const std::map& params, std::map& results); - void RouterInfoHandler (const std::map& params, std::map& results); - void RouterManagerHandler (const std::map& params, std::map& results); - void NetworkSettingHandler (const std::map& params, std::map& results); + // methods + typedef void (I2PControlService::*MethodHandler)(const boost::property_tree::ptree& params, boost::property_tree::ptree& results); + + void AuthenticateHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results); + void EchoHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results); + void I2PControlHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results); + void RouterInfoHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results); + void RouterManagerHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results); + void NetworkSettingHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results); // I2PControl typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value); // RouterInfo - typedef void (I2PControlService::*RouterInfoRequestHandler)(std::map& results); - void UptimeHandler (std::map& results); - void VersionHandler (std::map& results); - void StatusHandler (std::map& results); - void NetDbKnownPeersHandler (std::map& results); - void NetDbActivePeersHandler (std::map& results); - void NetStatusHandler (std::map& results); - void TunnelsParticipatingHandler (std::map& results); - void InboundBandwidth1S (std::map& results); - void OutboundBandwidth1S (std::map& results); + typedef void (I2PControlService::*RouterInfoRequestHandler)(boost::property_tree::ptree& results); + void UptimeHandler (boost::property_tree::ptree& results); + void VersionHandler (boost::property_tree::ptree& results); + void StatusHandler (boost::property_tree::ptree& results); + void NetDbKnownPeersHandler (boost::property_tree::ptree& results); + void NetDbActivePeersHandler (boost::property_tree::ptree& results); + void NetStatusHandler (boost::property_tree::ptree& results); + void TunnelsParticipatingHandler (boost::property_tree::ptree& results); + void InboundBandwidth1S (boost::property_tree::ptree& results); + void OutboundBandwidth1S (boost::property_tree::ptree& results); // RouterManager - typedef void (I2PControlService::*RouterManagerRequestHandler)(std::map& results); - void ShutdownHandler (std::map& results); - void ShutdownGracefulHandler (std::map& results); - void ReseedHandler (std::map& results); + typedef void (I2PControlService::*RouterManagerRequestHandler)(boost::property_tree::ptree& results); + void ShutdownHandler (boost::property_tree::ptree& results); + void ShutdownGracefulHandler (boost::property_tree::ptree& results); + void ReseedHandler (boost::property_tree::ptree& results); // NetworkSetting - typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::map& results); + typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, boost::property_tree::ptree& results); private: From 1cc3dd5ee49f76bec35413e72e715025225c21c6 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Mar 2015 14:45:22 -0400 Subject: [PATCH 0305/6300] send I2PControl response without write_json --- I2PControl.cpp | 123 ++++++++++++++++++++++++++----------------------- I2PControl.h | 53 ++++++++++----------- 2 files changed, 92 insertions(+), 84 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 2863e2c7..1dae4997 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -168,9 +168,12 @@ namespace client auto it = m_MethodHandlers.find (method); if (it != m_MethodHandlers.end ()) { - boost::property_tree::ptree results; - (this->*(it->second))(pt.get_child (I2P_CONTROL_PROPERTY_PARAMS), results); - SendResponse (socket, buf, pt.get(I2P_CONTROL_PROPERTY_ID), results, isHtml); + std::ostringstream response; + response << "{\"id\":" << pt.get(I2P_CONTROL_PROPERTY_ID) << ",\"result\":{"; + + (this->*(it->second))(pt.get_child (I2P_CONTROL_PROPERTY_PARAMS), response); + response << "},\"jsonrpc\":\"2.0\"}"; + SendResponse (socket, buf, response, isHtml); } else LogPrint (eLogWarning, "Unknown I2PControl method ", method); @@ -187,28 +190,29 @@ namespace client } } - template - void I2PControlService::InsertParam (boost::property_tree::ptree& pt, const std::string& name, T value) const + void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, int value) const { - pt.put (boost::property_tree::ptree::path_type (name, '/'), value); + ss << "\"" << name << "\":" << value; } - void I2PControlService::SendResponse (std::shared_ptr socket, - std::shared_ptr buf, const std::string& id, - boost::property_tree::ptree& results, bool isHtml) + void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const { - boost::property_tree::ptree pt; - pt.put (I2P_CONTROL_PROPERTY_ID, id); - pt.put_child (I2P_CONTROL_PROPERTY_RESULT, results); - pt.put ("jsonrpc", "2.0"); + ss << "\"" << name << "\":"; + if (value.length () > 0) + ss << "\"" << value << "\""; + else + ss << "null"; + } + + void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, double value) const + { + ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; + } - std::ostringstream ss; -#if GCC47_BOOST149 - LogPrint (eLogError, "json_write is not supported due bug in boost 1.49 with gcc 4.7"); -#else - boost::property_tree::write_json (ss, pt, false); -#endif - size_t len = ss.str ().length (), offset = 0; + void I2PControlService::SendResponse (std::shared_ptr socket, + std::shared_ptr buf, std::ostringstream& response, bool isHtml) + { + size_t len = response.str ().length (), offset = 0; if (isHtml) { std::ostringstream header; @@ -224,7 +228,7 @@ namespace client offset = header.str ().size (); memcpy (buf->data (), header.str ().c_str (), offset); } - memcpy (buf->data () + offset, ss.str ().c_str (), len); + memcpy (buf->data () + offset, response.str ().c_str (), len); boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), boost::asio::transfer_all (), std::bind(&I2PControlService::HandleResponseSent, this, @@ -241,7 +245,7 @@ namespace client // handlers - void I2PControlService::AuthenticateHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results) + void I2PControlService::AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { int api = params.get (I2P_CONTROL_PARAM_API); auto password = params.get (I2P_CONTROL_PARAM_PASSWORD); @@ -249,12 +253,13 @@ namespace client if (password != m_Password) LogPrint (eLogError, "I2PControl Authenticate Invalid password ", password, " expected ", m_Password); InsertParam (results, I2P_CONTROL_PARAM_API, api); + results << ","; std::string token = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); m_Tokens.insert (token); InsertParam (results, I2P_CONTROL_PARAM_TOKEN, token); } - void I2PControlService::EchoHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results) + void I2PControlService::EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { auto echo = params.get (I2P_CONTROL_PARAM_ECHO); LogPrint (eLogDebug, "I2PControl Echo Echo=", echo); @@ -264,7 +269,7 @@ namespace client // I2PControl - void I2PControlService::I2PControlHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results) + void I2PControlService::I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { LogPrint (eLogDebug, "I2PControl I2PControl"); for (auto& it: params) @@ -280,84 +285,85 @@ namespace client // RouterInfo - void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results) + void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { LogPrint (eLogDebug, "I2PControl RouterInfo"); - for (auto& it: params) + for (auto it = params.begin (); it != params.end (); it++) { - LogPrint (eLogDebug, it.first); - auto it1 = m_RouterInfoHandlers.find (it.first); + if (it != params.begin ()) results << ","; + LogPrint (eLogDebug, it->first); + auto it1 = m_RouterInfoHandlers.find (it->first); if (it1 != m_RouterInfoHandlers.end ()) (this->*(it1->second))(results); else - LogPrint (eLogError, "I2PControl RouterInfo unknown request ", it.first); - + LogPrint (eLogError, "I2PControl RouterInfo unknown request ", it->first); } } - void I2PControlService::UptimeHandler (boost::property_tree::ptree& results) + void I2PControlService::UptimeHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_UPTIME, i2p::context.GetUptime ()*1000); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_UPTIME, (int)i2p::context.GetUptime ()*1000); } - void I2PControlService::VersionHandler (boost::property_tree::ptree& results) + void I2PControlService::VersionHandler (std::ostringstream& results) { InsertParam (results, I2P_CONTROL_ROUTER_INFO_VERSION, VERSION); } - void I2PControlService::StatusHandler (boost::property_tree::ptree& results) + void I2PControlService::StatusHandler (std::ostringstream& results) { InsertParam (results, I2P_CONTROL_ROUTER_INFO_STATUS, "???"); // TODO: } - void I2PControlService::NetDbKnownPeersHandler (boost::property_tree::ptree& results) + void I2PControlService::NetDbKnownPeersHandler (std::ostringstream& results) { InsertParam (results, I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS, i2p::data::netdb.GetNumRouters ()); } - void I2PControlService::NetDbActivePeersHandler (boost::property_tree::ptree& results) + void I2PControlService::NetDbActivePeersHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS, i2p::transport::transports.GetPeers ().size ()); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS, (int)i2p::transport::transports.GetPeers ().size ()); } - void I2PControlService::NetStatusHandler (boost::property_tree::ptree& results) + void I2PControlService::NetStatusHandler (std::ostringstream& results) { InsertParam (results, I2P_CONTROL_ROUTER_INFO_NET_STATUS, (int)i2p::context.GetStatus ()); } - void I2PControlService::TunnelsParticipatingHandler (boost::property_tree::ptree& results) + void I2PControlService::TunnelsParticipatingHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING, i2p::tunnel::tunnels.GetTransitTunnels ().size ()); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING, (int)i2p::tunnel::tunnels.GetTransitTunnels ().size ()); } - void I2PControlService::InboundBandwidth1S (boost::property_tree::ptree& results) + void I2PControlService::InboundBandwidth1S (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_BW_IB_1S, i2p::transport::transports.GetInBandwidth ()); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_BW_IB_1S, (double)i2p::transport::transports.GetInBandwidth ()); } - void I2PControlService::OutboundBandwidth1S (boost::property_tree::ptree& results) + void I2PControlService::OutboundBandwidth1S (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_BW_OB_1S, i2p::transport::transports.GetOutBandwidth ()); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_BW_OB_1S, (double)i2p::transport::transports.GetOutBandwidth ()); } // RouterManager - void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results) + void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { LogPrint (eLogDebug, "I2PControl RouterManager"); - for (auto& it: params) + for (auto it = params.begin (); it != params.end (); it++) { - LogPrint (eLogDebug, it.first); - auto it1 = m_RouterManagerHandlers.find (it.first); + if (it != params.begin ()) results << ","; + LogPrint (eLogDebug, it->first); + auto it1 = m_RouterManagerHandlers.find (it->first); if (it1 != m_RouterManagerHandlers.end ()) (this->*(it1->second))(results); else - LogPrint (eLogError, "I2PControl RouterManager unknown request ", it.first); + LogPrint (eLogError, "I2PControl RouterManager unknown request ", it->first); } } - void I2PControlService::ShutdownHandler (boost::property_tree::ptree& results) + void I2PControlService::ShutdownHandler (std::ostringstream& results) { LogPrint (eLogInfo, "Shutdown requested"); InsertParam (results, I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, ""); @@ -369,7 +375,7 @@ namespace client }); } - void I2PControlService::ShutdownGracefulHandler (boost::property_tree::ptree& results) + void I2PControlService::ShutdownGracefulHandler (std::ostringstream& results) { i2p::context.SetAcceptsTunnels (false); int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout (); @@ -383,7 +389,7 @@ namespace client }); } - void I2PControlService::ReseedHandler (boost::property_tree::ptree& results) + void I2PControlService::ReseedHandler (std::ostringstream& results) { LogPrint (eLogInfo, "Reseed requested"); InsertParam (results, I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, ""); @@ -391,17 +397,18 @@ namespace client } // network setting - void I2PControlService::NetworkSettingHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results) + void I2PControlService::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { LogPrint (eLogDebug, "I2PControl NetworkSetting"); - for (auto& it: params) + for (auto it = params.begin (); it != params.end (); it++) { - LogPrint (eLogDebug, it.first); - auto it1 = m_NetworkSettingHandlers.find (it.first); + if (it != params.begin ()) results << ","; + LogPrint (eLogDebug, it->first); + auto it1 = m_NetworkSettingHandlers.find (it->first); if (it1 != m_NetworkSettingHandlers.end ()) - (this->*(it1->second))(it.second.data (), results); + (this->*(it1->second))(it->second.data (), results); else - LogPrint (eLogError, "I2PControl NetworkSetting unknown request ", it.first); + LogPrint (eLogError, "I2PControl NetworkSetting unknown request ", it->first); } } diff --git a/I2PControl.h b/I2PControl.h index c9d22d59..0c3e36a1 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -80,49 +81,49 @@ namespace client void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); void SendResponse (std::shared_ptr socket, - std::shared_ptr buf, const std::string& id, - boost::property_tree::ptree& results, bool isHtml); + std::shared_ptr buf, std::ostringstream& response, bool isHtml); void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); private: - template - void InsertParam (boost::property_tree::ptree& pt, const std::string& name, T value) const; + void InsertParam (std::ostringstream& ss, const std::string& name, int value) const; + void InsertParam (std::ostringstream& ss, const std::string& name, double value) const; + void InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const; // methods - typedef void (I2PControlService::*MethodHandler)(const boost::property_tree::ptree& params, boost::property_tree::ptree& results); + typedef void (I2PControlService::*MethodHandler)(const boost::property_tree::ptree& params, std::ostringstream& results); - void AuthenticateHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results); - void EchoHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results); - void I2PControlHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results); - void RouterInfoHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results); - void RouterManagerHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results); - void NetworkSettingHandler (const boost::property_tree::ptree& params, boost::property_tree::ptree& results); + void AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results); // I2PControl typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value); // RouterInfo - typedef void (I2PControlService::*RouterInfoRequestHandler)(boost::property_tree::ptree& results); - void UptimeHandler (boost::property_tree::ptree& results); - void VersionHandler (boost::property_tree::ptree& results); - void StatusHandler (boost::property_tree::ptree& results); - void NetDbKnownPeersHandler (boost::property_tree::ptree& results); - void NetDbActivePeersHandler (boost::property_tree::ptree& results); - void NetStatusHandler (boost::property_tree::ptree& results); - void TunnelsParticipatingHandler (boost::property_tree::ptree& results); - void InboundBandwidth1S (boost::property_tree::ptree& results); - void OutboundBandwidth1S (boost::property_tree::ptree& results); + typedef void (I2PControlService::*RouterInfoRequestHandler)(std::ostringstream& results); + void UptimeHandler (std::ostringstream& results); + void VersionHandler (std::ostringstream& results); + void StatusHandler (std::ostringstream& results); + void NetDbKnownPeersHandler (std::ostringstream& results); + void NetDbActivePeersHandler (std::ostringstream& results); + void NetStatusHandler (std::ostringstream& results); + void TunnelsParticipatingHandler (std::ostringstream& results); + void InboundBandwidth1S (std::ostringstream& results); + void OutboundBandwidth1S (std::ostringstream& results); // RouterManager - typedef void (I2PControlService::*RouterManagerRequestHandler)(boost::property_tree::ptree& results); - void ShutdownHandler (boost::property_tree::ptree& results); - void ShutdownGracefulHandler (boost::property_tree::ptree& results); - void ReseedHandler (boost::property_tree::ptree& results); + typedef void (I2PControlService::*RouterManagerRequestHandler)(std::ostringstream& results); + void ShutdownHandler (std::ostringstream& results); + void ShutdownGracefulHandler (std::ostringstream& results); + void ReseedHandler (std::ostringstream& results); // NetworkSetting - typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, boost::property_tree::ptree& results); + typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); private: From 77fd2960955743c171efc2d7a32f6778f560782c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Mar 2015 15:53:53 -0400 Subject: [PATCH 0306/6300] wait for LeaseSet request completion --- BOB.cpp | 13 +++++-------- BOB.h | 3 +-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index 2f881a69..edf6995e 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -10,7 +10,7 @@ namespace client { BOBI2PInboundTunnel::BOBI2PInboundTunnel (int port, std::shared_ptr localDestination): BOBI2PTunnel (localDestination), - m_Acceptor (localDestination->GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), m_Timer (localDestination->GetService ()) + m_Acceptor (localDestination->GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)) { } @@ -94,12 +94,9 @@ namespace client if (leaseSet) CreateConnection (receiver, leaseSet); else - { - GetLocalDestination ()->RequestDestination (ident); - m_Timer.expires_from_now (boost::posix_time::seconds (I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT)); - m_Timer.async_wait (std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestTimer, + GetLocalDestination ()->RequestDestination (ident, + std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete, this, std::placeholders::_1, receiver, ident)); - } } else { @@ -115,9 +112,9 @@ namespace client } } - void BOBI2PInboundTunnel::HandleDestinationRequestTimer (const boost::system::error_code& ecode, AddressReceiver * receiver, i2p::data::IdentHash ident) + void BOBI2PInboundTunnel::HandleDestinationRequestComplete (bool success, AddressReceiver * receiver, i2p::data::IdentHash ident) { - if (ecode != boost::asio::error::operation_aborted) + if (success) { auto leaseSet = GetLocalDestination ()->FindLeaseSet (ident); if (leaseSet) diff --git a/BOB.h b/BOB.h index cd42c9b8..5bd4c24a 100644 --- a/BOB.h +++ b/BOB.h @@ -82,14 +82,13 @@ namespace client void HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred, AddressReceiver * receiver); - void HandleDestinationRequestTimer (const boost::system::error_code& ecode, AddressReceiver * receiver, i2p::data::IdentHash ident); + void HandleDestinationRequestComplete (bool success, AddressReceiver * receiver, i2p::data::IdentHash ident); void CreateConnection (AddressReceiver * receiver, std::shared_ptr leaseSet); private: boost::asio::ip::tcp::acceptor m_Acceptor; - boost::asio::deadline_timer m_Timer; }; class BOBI2POutboundTunnel: public BOBI2PTunnel From 02de91f7f2e2db8b137266d13fa0a7b688fc5132 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Mar 2015 16:04:48 -0400 Subject: [PATCH 0307/6300] use unique_ptr for socket in I2PTunnelConnection --- I2PTunnel.cpp | 1 - I2PTunnel.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 3eb89f45..51392819 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -33,7 +33,6 @@ namespace client I2PTunnelConnection::~I2PTunnelConnection () { - delete m_Socket; } void I2PTunnelConnection::I2PConnect (const uint8_t * msg, size_t len) diff --git a/I2PTunnel.h b/I2PTunnel.h index 41a067f8..39136587 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -49,7 +49,7 @@ namespace client private: uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; - boost::asio::ip::tcp::socket * m_Socket; + std::unique_ptr m_Socket; std::shared_ptr m_Stream; boost::asio::ip::tcp::endpoint m_RemoteEndpoint; bool m_IsQuiet; // don't send destination From ac79d8ed3e8829f07109ba06f9a439bc8dbbdc5e Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Mar 2015 21:13:06 -0400 Subject: [PATCH 0308/6300] fixed misspelling --- Daemon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Daemon.cpp b/Daemon.cpp index ed0c74d3..e3ae8bbc 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -76,7 +76,7 @@ namespace i2p i2p::context.SetSupportsV6 (i2p::util::config::GetArg("-v6", 0)); i2p::context.SetFloodfill (i2p::util::config::GetArg("-floodfill", 0)); - auto bandwidth = i2p::util::config::GetArg("-badnwidth", ""); + auto bandwidth = i2p::util::config::GetArg("-bandwidth", ""); if (bandwidth.length () > 0) { if (bandwidth[0] > 'L') From 70396240c31b2ae4f8ea727f11bc05718352b1e8 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 21 Mar 2015 12:34:20 +0000 Subject: [PATCH 0309/6300] * README : describe format of tunnels.cfg --- README.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5120899c..ed6b3203 100644 --- a/README.md +++ b/README.md @@ -92,12 +92,37 @@ Cmdline options This parameter will be silently ignored if the specified config file does not exist. Options specified on the command line take precedence over those in the config file. -Config file ------------ +Config files +------------ INI-like, syntax is the following : = . All command-line parameters are allowed as keys, for example: +i2p.conf: + log = 1 v6 = 0 ircdest = irc.postman.i2p + +tunnels.cfg (filename of this config is subject of change): + + ; outgoing tunnel, to remote service + [tunnel1] + type = client ; mandatory + port = ; mandatory, bind our side of tunnel to this local port + keys = ; optional + destination = ; mandatory + destinationport = ; optional, port of remote i2p service + ; + ; incoming tunnel, for local service(s) + [tunnel2] + type = server ; mandatory + host = ; mandatory, hostname of our i2p service + keys = ; mandatory, hostname keys + port = ; mandatory, forward incoming connections from i2p + ; to this port + inport = ; optional, i2p service port + accesslist = [,] ; optional, comma-separated list of i2p idents, + ; allowed to connect to service + +Note: '' type is a string like or From 5f3b17af64096554a069faba667a3896ab3c3681 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 21 Mar 2015 16:26:14 -0400 Subject: [PATCH 0310/6300] better resend and tunnel reselection algorithm --- Streaming.cpp | 67 ++++++++++++++++++++++++++++---------------------- Streaming.h | 6 ++--- TunnelPool.cpp | 15 +++++------ TunnelPool.h | 6 ++--- 4 files changed, 52 insertions(+), 42 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 3f32e9a0..e11df406 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -18,7 +18,7 @@ namespace stream m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), - m_LastWindowSizeIncreaseTime (0) + m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0) { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); UpdateCurrentRemoteLease (); @@ -29,7 +29,7 @@ namespace stream m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE), - m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_LastWindowSizeIncreaseTime (0) + m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0) { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); } @@ -267,7 +267,10 @@ namespace stream if (m_SentPackets.empty ()) m_ResendTimer.cancel (); if (acknowledged) + { + m_NumResendAttempts = 0; SendBuffer (); + } if (m_Status == eStreamStatusClosing) Close (); // all outgoing messages have been sent } @@ -602,45 +605,51 @@ namespace stream void Stream::HandleResendTimer (const boost::system::error_code& ecode) { - if (ecode != boost::asio::error::operation_aborted) + if (ecode != boost::asio::error::operation_aborted) { + // check for resend attempts + if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS) + { + LogPrint (eLogWarning, "Stream packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts. Terminate"); + m_Status = eStreamStatusReset; + Close (); + return; + } + + // collect packets to resend auto ts = i2p::util::GetMillisecondsSinceEpoch (); - bool congesion = false, first = true; std::vector packets; for (auto it : m_SentPackets) { - if (ts < it->sendTime + m_RTO) continue; // don't resend too early - it->numResendAttempts++; - if (first && it->numResendAttempts == 1) // detect congesion at first attempt of first packet only - congesion = true; - first = false; - if (it->numResendAttempts <= MAX_NUM_RESEND_ATTEMPTS) + if (ts >= it->sendTime + m_RTO) { it->sendTime = ts; packets.push_back (it); - } - else - { - LogPrint (eLogWarning, "Packet ", it->GetSeqn (), " was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts. Terminate"); - m_Status = eStreamStatusReset; - Close (); - return; - } + } } + + // select tunnels if necessary and send if (packets.size () > 0) { - if (congesion) - { - // congesion avoidance - m_WindowSize /= 2; - if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; - } - else + m_NumResendAttempts++; + switch (m_NumResendAttempts) { - // congesion avoidance didn't help - m_CurrentOutboundTunnel = nullptr; // pick another outbound tunnel - UpdateCurrentRemoteLease (); // pick another lease - m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change + case 1: // congesion avoidance + m_WindowSize /= 2; + if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; + break; + case 2: + case 4: + UpdateCurrentRemoteLease (); // pick another lease + m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change + LogPrint (eLogWarning, "Another remote lease has been selected stream"); + break; + case 3: + // pick another outbound tunnel + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ().GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); + LogPrint (eLogWarning, "Another outbound tunnel has been selected for stream"); + break; + default: ; } SendPackets (packets); } diff --git a/Streaming.h b/Streaming.h index 9cccd19d..1ba01ea0 100644 --- a/Streaming.h +++ b/Streaming.h @@ -42,7 +42,7 @@ namespace stream const size_t MAX_PACKET_SIZE = 4096; const size_t COMPRESSION_THRESHOLD_SIZE = 66; const int ACK_SEND_TIMEOUT = 200; // in milliseconds - const int MAX_NUM_RESEND_ATTEMPTS = 5; + const int MAX_NUM_RESEND_ATTEMPTS = 6; const int WINDOW_SIZE = 6; // in messages const int MIN_WINDOW_SIZE = 1; const int MAX_WINDOW_SIZE = 128; @@ -53,10 +53,9 @@ namespace stream { size_t len, offset; uint8_t buf[MAX_PACKET_SIZE]; - int numResendAttempts; uint64_t sendTime; - Packet (): len (0), offset (0), numResendAttempts (0), sendTime (0) {}; + Packet (): len (0), offset (0), sendTime (0) {}; uint8_t * GetBuffer () { return buf + offset; }; size_t GetLength () const { return len - offset; }; @@ -179,6 +178,7 @@ namespace stream std::stringstream m_SendBuffer; int m_WindowSize, m_RTT, m_RTO; uint64_t m_LastWindowSizeIncreaseTime; + int m_NumResendAttempts; }; class StreamingDestination diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 7a98621c..82de6267 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -101,20 +101,20 @@ namespace tunnel return v; } - std::shared_ptr TunnelPool::GetNextOutboundTunnel () const + std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded) const { std::unique_lock l(m_OutboundTunnelsMutex); - return GetNextTunnel (m_OutboundTunnels); + return GetNextTunnel (m_OutboundTunnels, excluded); } - std::shared_ptr TunnelPool::GetNextInboundTunnel () const + std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded) const { std::unique_lock l(m_InboundTunnelsMutex); - return GetNextTunnel (m_InboundTunnels); + return GetNextTunnel (m_InboundTunnels, excluded); } template - typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels) const + typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const { if (tunnels.empty ()) return nullptr; CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); @@ -122,13 +122,14 @@ namespace tunnel typename TTunnels::value_type tunnel = nullptr; for (auto it: tunnels) { - if (it->IsEstablished ()) + if (it->IsEstablished () && it != excluded) { tunnel = it; i++; } if (i > ind && tunnel) break; - } + } + if (!tunnel && excluded && excluded->IsEstablished ()) tunnel = excluded; return tunnel; } diff --git a/TunnelPool.h b/TunnelPool.h index 14f70841..ee0687cc 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -39,8 +39,8 @@ namespace tunnel void TunnelCreated (std::shared_ptr createdTunnel); void TunnelExpired (std::shared_ptr expiredTunnel); std::vector > GetInboundTunnels (int num) const; - std::shared_ptr GetNextOutboundTunnel () const; - std::shared_ptr GetNextInboundTunnel () const; + std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr) const; + std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr) const; void TestTunnels (); void ProcessGarlicMessage (I2NPMessage * msg); @@ -57,7 +57,7 @@ namespace tunnel void RecreateInboundTunnel (std::shared_ptr tunnel); void RecreateOutboundTunnel (std::shared_ptr tunnel); template - typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels) const; + typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; private: From 05d7b79d0738fbf875bb970d235a74e07638da42 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 21 Mar 2015 18:35:17 -0400 Subject: [PATCH 0311/6300] proper formatting --- README.md | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index ed6b3203..4445de8b 100644 --- a/README.md +++ b/README.md @@ -106,23 +106,21 @@ i2p.conf: tunnels.cfg (filename of this config is subject of change): - ; outgoing tunnel, to remote service - [tunnel1] - type = client ; mandatory - port = ; mandatory, bind our side of tunnel to this local port - keys = ; optional - destination = ; mandatory - destinationport = ; optional, port of remote i2p service - ; - ; incoming tunnel, for local service(s) - [tunnel2] - type = server ; mandatory - host = ; mandatory, hostname of our i2p service - keys = ; mandatory, hostname keys - port = ; mandatory, forward incoming connections from i2p - ; to this port - inport = ; optional, i2p service port - accesslist = [,] ; optional, comma-separated list of i2p idents, - ; allowed to connect to service + ; outgoing tunnel, to remote service + [tunnel1] + type = client ; mandatory + port = ; mandatory, bind our side of tunnel to this local port + keys = ; optional + destination = ; mandatory + destinationport = ; optional, port of remote i2p service + + ; incoming tunnel, for local service(s) + [tunnel2] + type = server ; mandatory + host = ; mandatory, hostname of our i2p service + keys = ; mandatory, hostname keys + port = ; mandatory, forward incoming connections from i2p to this port + inport = ; optional, i2p service port + accesslist = [,] ; optional, comma-separated list of i2p idents, allowed to connect to service -Note: '' type is a string like or +Note: '' type is a string like or From e58ebb86562081ce6e86eb893634f35a18f3fae4 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 21 Mar 2015 18:39:48 -0400 Subject: [PATCH 0312/6300] increased transit tunnels # to 2500 --- I2NPProtocol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 294abcc9..19597d52 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -97,7 +97,7 @@ namespace i2p const uint8_t DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP = 0x08; // 1000 const uint8_t DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP = 0x0C; // 1100 - const int MAX_NUM_TRANSIT_TUNNELS = 1500; + const int MAX_NUM_TRANSIT_TUNNELS = 2500; namespace tunnel { From 18deb8b4f2885edcb4af3409377b4a31f4e863a3 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Mar 2015 14:59:27 -0400 Subject: [PATCH 0313/6300] DeliveryStatus for LeaseSet --- Garlic.cpp | 41 +++++++++++++++++++++++++++++++---------- Garlic.h | 22 +++++++++++++++++----- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index 53281fa2..87c7ae2a 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -17,7 +17,7 @@ namespace garlic GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner, std::shared_ptr destination, int numTags): m_Owner (owner), m_Destination (destination), m_NumTags (numTags), - m_LeaseSetUpdated (numTags > 0) + m_LeaseSetUpdateStatus (numTags > 0 ? eLeaseSetUpdated : eLeaseSetUpToDate) { // create new session tags and session key m_Rnd.GenerateBlock (m_SessionKey, 32); @@ -25,7 +25,7 @@ namespace garlic } GarlicRoutingSession::GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag): - m_Owner (nullptr), m_Destination (nullptr), m_NumTags (1), m_LeaseSetUpdated (false) + m_Owner (nullptr), m_Destination (nullptr), m_NumTags (1), m_LeaseSetUpdateStatus (eLeaseSetUpToDate) { memcpy (m_SessionKey, sessionKey, 32); m_Encryption.SetKey (m_SessionKey); @@ -51,13 +51,25 @@ namespace garlic } return tags; } - + + void GarlicRoutingSession::MessageConfirmed (uint32_t msgID) + { + TagsConfirmed (msgID); + if (msgID == m_LeaseSetUpdateMsgID) + { + m_LeaseSetUpdateStatus = eLeaseSetUpToDate; + LogPrint (eLogInfo, "LeaseSet update confirmed"); + } + else + CleanupExpiredTags (); + } + void GarlicRoutingSession::TagsConfirmed (uint32_t msgID) { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); auto it = m_UnconfirmedTagsMsgs.find (msgID); if (it != m_UnconfirmedTagsMsgs.end ()) { + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); UnconfirmedTags * tags = it->second; if (ts < tags->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) { @@ -67,7 +79,6 @@ namespace garlic m_UnconfirmedTagsMsgs.erase (it); delete tags; } - CleanupExpiredTags (); } bool GarlicRoutingSession::CleanupExpiredTags () @@ -205,22 +216,32 @@ namespace garlic if (m_Owner) { - if (newTags) // new session + // resubmit non-confirmed LeaseSet + if (m_LeaseSetUpdateStatus == eLeaseSetSubmitted && + i2p::util::GetMillisecondsSinceEpoch () > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT) + m_LeaseSetUpdateStatus = eLeaseSetUpdated; + + // attach DeviveryStatus if necessary + if (newTags || m_LeaseSetUpdateStatus == eLeaseSetUpdated) // new tags created or leaseset updated { // clove is DeliveryStatus size += CreateDeliveryStatusClove (payload + size, msgID); if (size > 0) // successive? { (*numCloves)++; - m_UnconfirmedTagsMsgs[msgID] = newTags; + if (newTags) // new tags created + m_UnconfirmedTagsMsgs[msgID] = newTags; m_Owner->DeliveryStatusSent (shared_from_this (), msgID); } else LogPrint ("DeliveryStatus clove was not created"); } - if (m_LeaseSetUpdated) + // attach LeaseSet + if (m_LeaseSetUpdateStatus == eLeaseSetUpdated) { - m_LeaseSetUpdated = false; + m_LeaseSetUpdateStatus = eLeaseSetSubmitted; + m_LeaseSetUpdateMsgID = msgID; + m_LeaseSetSubmissionTime = i2p::util::GetMillisecondsSinceEpoch (); // clove if our leaseSet must be attached auto leaseSet = CreateDatabaseStoreMsg (m_Owner->GetLeaseSet ()); size += CreateGarlicClove (payload + size, leaseSet, false); @@ -564,7 +585,7 @@ namespace garlic auto it = m_CreatedSessions.find (msgID); if (it != m_CreatedSessions.end ()) { - it->second->TagsConfirmed (msgID); + it->second->MessageConfirmed (msgID); m_CreatedSessions.erase (it); LogPrint (eLogInfo, "Garlic message ", msgID, " acknowledged"); } diff --git a/Garlic.h b/Garlic.h index 43214e75..4b02d987 100644 --- a/Garlic.h +++ b/Garlic.h @@ -39,6 +39,7 @@ namespace garlic const int INCOMING_TAGS_EXPIRATION_TIMEOUT = 960; // 16 minutes const int OUTGOING_TAGS_EXPIRATION_TIMEOUT = 720; // 12 minutes + const int LEASET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds struct SessionTag: public i2p::data::Tag<32> { @@ -56,6 +57,13 @@ namespace garlic class GarlicDestination; class GarlicRoutingSession: public std::enable_shared_from_this { + enum LeaseSetUpdateStatus + { + eLeaseSetUpToDate = 0, + eLeaseSetUpdated, + eLeaseSetSubmitted + }; + struct UnconfirmedTags { UnconfirmedTags (int n): numTags (n), tagsCreationTime (0) { sessionTags = new SessionTag[numTags]; }; @@ -71,10 +79,10 @@ namespace garlic GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption ~GarlicRoutingSession (); I2NPMessage * WrapSingleMessage (I2NPMessage * msg); - void TagsConfirmed (uint32_t msgID); + void MessageConfirmed (uint32_t msgID); bool CleanupExpiredTags (); // returns true if something left - void SetLeaseSetUpdated () { m_LeaseSetUpdated = true; }; + void SetLeaseSetUpdated () { m_LeaseSetUpdateStatus = eLeaseSetUpdated; }; private: @@ -82,7 +90,8 @@ namespace garlic size_t CreateGarlicPayload (uint8_t * payload, const I2NPMessage * msg, UnconfirmedTags * newTags); size_t CreateGarlicClove (uint8_t * buf, const I2NPMessage * msg, bool isDestination); size_t CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID); - + + void TagsConfirmed (uint32_t msgID); UnconfirmedTags * GenerateSessionTags (); private: @@ -93,8 +102,11 @@ namespace garlic std::list m_SessionTags; int m_NumTags; std::map m_UnconfirmedTagsMsgs; - bool m_LeaseSetUpdated; - + + LeaseSetUpdateStatus m_LeaseSetUpdateStatus; + uint32_t m_LeaseSetUpdateMsgID; + uint64_t m_LeaseSetSubmissionTime; // in milliseconds + i2p::crypto::CBCEncryption m_Encryption; CryptoPP::AutoSeededRandomPool m_Rnd; }; From 4b47bfb5dbce218dd71ab2f96abb867afc077ddd Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Mar 2015 18:34:39 -0400 Subject: [PATCH 0314/6300] re-request remote LeaseSet --- Streaming.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index e11df406..e91e5f40 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -560,7 +560,7 @@ namespace stream UpdateCurrentRemoteLease (); if (!m_RemoteLeaseSet) { - LogPrint ("Can't send packets. Missing remote LeaseSet"); + LogPrint (eLogError, "Can't send packets. Missing remote LeaseSet"); return; } } @@ -568,7 +568,7 @@ namespace stream m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ().GetTunnelPool ()->GetNextOutboundTunnel (); if (!m_CurrentOutboundTunnel) { - LogPrint ("No outbound tunnels in the pool"); + LogPrint (eLogError, "No outbound tunnels in the pool"); return; } @@ -582,17 +582,29 @@ namespace stream { auto msg = m_RoutingSession->WrapSingleMessage (CreateDataMessage (it->GetBuffer (), it->GetLength ())); msgs.push_back (i2p::tunnel::TunnelMessageBlock - { - i2p::tunnel::eDeliveryTypeTunnel, - m_CurrentRemoteLease.tunnelGateway, m_CurrentRemoteLease.tunnelID, - msg - }); + { + i2p::tunnel::eDeliveryTypeTunnel, + m_CurrentRemoteLease.tunnelGateway, m_CurrentRemoteLease.tunnelID, + msg + }); m_NumSentBytes += it->GetLength (); } m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs); } else - LogPrint ("All leases are expired"); + { + LogPrint (eLogInfo, "All leases are expired. Trying to request"); + m_RemoteLeaseSet = nullptr; + m_LocalDestination.GetOwner ().RequestDestination (m_RemoteIdentity.GetIdentHash (), + [packets, this](bool success) + { + if (success) + { + LogPrint (eLogInfo, "New LeaseSet found. Sending packets"); + SendPackets (packets); + } + }); + } } void Stream::ScheduleResend () @@ -642,7 +654,7 @@ namespace stream case 4: UpdateCurrentRemoteLease (); // pick another lease m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change - LogPrint (eLogWarning, "Another remote lease has been selected stream"); + LogPrint (eLogWarning, "Another remote lease has been selected for stream"); break; case 3: // pick another outbound tunnel From cdb42b33cea93a97fc61fdc2c6f7a8c02df5d474 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Mar 2015 18:42:18 -0400 Subject: [PATCH 0315/6300] Removed external IP detection step --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4445de8b..99db4028 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,9 @@ On Ubuntu/Debian based * $ cd i2pd * $ make -Next, find out your public ip. (find it for example at http://www.whatismyip.com/) +Then, run it: -Then, run it with: - -$ ./i2p --host=YOUR_PUBLIC_IP +$ ./i2p The client should now reseed by itself. From 9a7e21778e25a0529cb8142990af9323d25ab966 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Mar 2015 12:55:42 -0400 Subject: [PATCH 0316/6300] eliminate misalignment for LeaseSet --- LeaseSet.cpp | 22 ++++++++++++---------- LeaseSet.h | 7 +------ 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 1a2c7d39..f8560d69 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -42,14 +42,14 @@ namespace data // leases for (auto it: tunnels) { - Lease lease; - memcpy (lease.tunnelGateway, it->GetNextIdentHash (), 32); - lease.tunnelID = htobe32 (it->GetNextTunnelID ()); + memcpy (m_Buffer + m_BufferLen, it->GetNextIdentHash (), 32); + m_BufferLen += 32; // gateway id + htobe32buf (m_Buffer + m_BufferLen, it->GetNextTunnelID ()); + m_BufferLen += 4; // tunnel id uint64_t ts = it->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - 60; // 1 minute before expiration ts *= 1000; // in milliseconds - lease.endDate = htobe64 (ts); - memcpy(m_Buffer + m_BufferLen, &lease, sizeof(Lease)); - m_BufferLen += sizeof (Lease); + htobe64buf (m_Buffer + m_BufferLen, ts); + m_BufferLen += 8; // end date } // signature localDestination->Sign (m_Buffer, m_BufferLen, m_Buffer + m_BufferLen); @@ -82,11 +82,13 @@ namespace data for (int i = 0; i < num; i++) { Lease lease; - memcpy (&lease, leases, sizeof(Lease)); - lease.tunnelID = be32toh (lease.tunnelID); - lease.endDate = be64toh (lease.endDate); + lease.tunnelGateway = leases; + leases += 32; // gateway + lease.tunnelID = bufbe32toh (leases); + leases += 4; // tunnel ID + lease.endDate = bufbe64toh (leases); + leases += 8; // end date m_Leases.push_back (lease); - leases += sizeof (Lease); // check if lease's gateway is in our netDb if (!netdb.FindRouter (lease.tunnelGateway)) diff --git a/LeaseSet.h b/LeaseSet.h index 9b555daa..10fa71e9 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -16,12 +16,9 @@ namespace tunnel namespace data { - -#pragma pack(1) - struct Lease { - uint8_t tunnelGateway[32]; + IdentHash tunnelGateway; uint32_t tunnelID; uint64_t endDate; @@ -33,8 +30,6 @@ namespace data return tunnelID < other.tunnelID; } }; - -#pragma pack() const int MAX_LS_BUFFER_SIZE = 3072; class LeaseSet: public RoutingDestination From 217ddfe98d9c523962586740b4d5d87d4eef7a08 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Mar 2015 13:08:04 -0400 Subject: [PATCH 0317/6300] fixed crash --- Streaming.cpp | 19 +++++++++++-------- Streaming.h | 3 ++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index e91e5f40..28ea7557 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -596,17 +596,20 @@ namespace stream LogPrint (eLogInfo, "All leases are expired. Trying to request"); m_RemoteLeaseSet = nullptr; m_LocalDestination.GetOwner ().RequestDestination (m_RemoteIdentity.GetIdentHash (), - [packets, this](bool success) - { - if (success) - { - LogPrint (eLogInfo, "New LeaseSet found. Sending packets"); - SendPackets (packets); - } - }); + std::bind (&Stream::HandleLeaseSetRequestComplete, shared_from_this (), + std::placeholders::_1, packets)); } } + void Stream::HandleLeaseSetRequestComplete (bool success, std::vector packets) + { + if (success) + { + LogPrint (eLogInfo, "New LeaseSet found. Sending packets"); + SendPackets (packets); + } + } + void Stream::ScheduleResend () { m_ResendTimer.cancel (); diff --git a/Streaming.h b/Streaming.h index 1ba01ea0..12a674bc 100644 --- a/Streaming.h +++ b/Streaming.h @@ -147,7 +147,8 @@ namespace stream template void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler); - + void HandleLeaseSetRequestComplete (bool success, std::vector packets); + void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); void HandleAckSendTimer (const boost::system::error_code& ecode); From ec319f950f70044238e67190ce93471836dc5682 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Mar 2015 15:53:20 -0400 Subject: [PATCH 0318/6300] add random milliseconds to end date --- LeaseSet.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index f8560d69..0d6d3fb8 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -1,6 +1,7 @@ #include #include "I2PEndian.h" #include +#include #include "CryptoConst.h" #include "Log.h" #include "Timestamp.h" @@ -40,6 +41,7 @@ namespace data m_Buffer[m_BufferLen] = tunnels.size (); // num leases m_BufferLen++; // leases + CryptoPP::AutoSeededRandomPool rnd; for (auto it: tunnels) { memcpy (m_Buffer + m_BufferLen, it->GetNextIdentHash (), 32); @@ -48,6 +50,7 @@ namespace data m_BufferLen += 4; // tunnel id uint64_t ts = it->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - 60; // 1 minute before expiration ts *= 1000; // in milliseconds + ts += rnd.GenerateWord32 (0, 5); // + random milliseconds htobe64buf (m_Buffer + m_BufferLen, ts); m_BufferLen += 8; // end date } From 7f91c9e63e4325bbc0abc802d761f16b937ac25d Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Mar 2015 18:07:43 -0400 Subject: [PATCH 0319/6300] close stream is SYN has not been received --- Streaming.cpp | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 28ea7557..510d0f14 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -21,6 +21,7 @@ namespace stream m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0) { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); + m_RemoteIdentity = remote->GetIdentity (); UpdateCurrentRemoteLease (); } @@ -130,13 +131,24 @@ namespace stream LogPrint (eLogWarning, "Missing messages from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1); // save message and wait for missing message again SavePacket (packet); - // send NACKs for missing messages ASAP - if (m_IsAckSendScheduled) + if (m_LastReceivedSequenceNumber >= 0) + { + // send NACKs for missing messages ASAP + if (m_IsAckSendScheduled) + { + m_IsAckSendScheduled = false; + m_AckSendTimer.cancel (); + } + SendQuickAck (); + } + else { - m_IsAckSendScheduled = false; - m_AckSendTimer.cancel (); - } - SendQuickAck (); + // wait for SYN + m_IsAckSendScheduled = true; + m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ACK_SEND_TIMEOUT)); + m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, + shared_from_this (), std::placeholders::_1)); + } } } } @@ -676,6 +688,13 @@ namespace stream { if (m_IsAckSendScheduled) { + if (m_LastReceivedSequenceNumber < 0) + { + LogPrint (eLogWarning, "SYN has not been recived after ", ACK_SEND_TIMEOUT, " milliseconds after follow on. Terminate"); + m_Status = eStreamStatusReset; + Close (); + return; + } if (m_Status == eStreamStatusOpen) SendQuickAck (); m_IsAckSendScheduled = false; From 6125288e95f7a199113cd0f0dc649a2a664401e2 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Mar 2015 22:18:30 -0400 Subject: [PATCH 0320/6300] select really other remote lease --- Streaming.cpp | 14 ++++++++------ Streaming.h | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 510d0f14..9420bd4b 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -608,17 +608,16 @@ namespace stream LogPrint (eLogInfo, "All leases are expired. Trying to request"); m_RemoteLeaseSet = nullptr; m_LocalDestination.GetOwner ().RequestDestination (m_RemoteIdentity.GetIdentHash (), - std::bind (&Stream::HandleLeaseSetRequestComplete, shared_from_this (), - std::placeholders::_1, packets)); + std::bind (&Stream::HandleLeaseSetRequestComplete, shared_from_this (), std::placeholders::_1)); } } - void Stream::HandleLeaseSetRequestComplete (bool success, std::vector packets) + void Stream::HandleLeaseSetRequestComplete (bool success) { if (success) { - LogPrint (eLogInfo, "New LeaseSet found. Sending packets"); - SendPackets (packets); + LogPrint (eLogInfo, "New LeaseSet found"); + UpdateCurrentRemoteLease (); } } @@ -717,7 +716,10 @@ namespace stream if (!leases.empty ()) { uint32_t i = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, leases.size () - 1); - m_CurrentRemoteLease = leases[i]; + if (m_CurrentRemoteLease.endDate && leases[i].tunnelID == m_CurrentRemoteLease.tunnelID) + // make sure we don't select previous + i = (i + 1) % leases.size (); // if so, pick next + m_CurrentRemoteLease = leases[i]; } else { diff --git a/Streaming.h b/Streaming.h index 12a674bc..cfb2ce90 100644 --- a/Streaming.h +++ b/Streaming.h @@ -147,7 +147,7 @@ namespace stream template void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler); - void HandleLeaseSetRequestComplete (bool success, std::vector packets); + void HandleLeaseSetRequestComplete (bool success); void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); From 3f4bd130916ddff1b96d0a18308ec5113f1ccfc1 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Mar 2015 22:23:40 -0400 Subject: [PATCH 0321/6300] don't wait for remote LeaseSet request complete --- Streaming.cpp | 11 +---------- Streaming.h | 1 - 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 9420bd4b..8b73e849 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -607,19 +607,10 @@ namespace stream { LogPrint (eLogInfo, "All leases are expired. Trying to request"); m_RemoteLeaseSet = nullptr; - m_LocalDestination.GetOwner ().RequestDestination (m_RemoteIdentity.GetIdentHash (), - std::bind (&Stream::HandleLeaseSetRequestComplete, shared_from_this (), std::placeholders::_1)); + m_LocalDestination.GetOwner ().RequestDestination (m_RemoteIdentity.GetIdentHash ()); } } - void Stream::HandleLeaseSetRequestComplete (bool success) - { - if (success) - { - LogPrint (eLogInfo, "New LeaseSet found"); - UpdateCurrentRemoteLease (); - } - } void Stream::ScheduleResend () { diff --git a/Streaming.h b/Streaming.h index cfb2ce90..d36da2aa 100644 --- a/Streaming.h +++ b/Streaming.h @@ -147,7 +147,6 @@ namespace stream template void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler); - void HandleLeaseSetRequestComplete (bool success); void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); From 21d4f49d31866e6547601a10cba6d0aeb4106c66 Mon Sep 17 00:00:00 2001 From: multikatt Date: Tue, 24 Mar 2015 12:47:43 -0400 Subject: [PATCH 0322/6300] adding docker and fig files --- Dockerfile | 12 ++++++++++++ fig.yml | 2 ++ 2 files changed, 14 insertions(+) create mode 100644 Dockerfile create mode 100644 fig.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..2989e852 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM ubuntu + +RUN apt-get update && apt-get install -y libboost-dev libboost-filesystem-dev \ + libboost-program-options-dev libboost-regex-dev libcrypto++-dev \ + libboost-date-time-dev git build-essential curl + +RUN git clone https://github.com/PurpleI2P/i2pd.git +WORKDIR /i2pd +RUN make + +CMD ./i2p --host=$(curl curlmyip.com) + diff --git a/fig.yml b/fig.yml new file mode 100644 index 00000000..372ef350 --- /dev/null +++ b/fig.yml @@ -0,0 +1,2 @@ +i2pd: + build: . From a6f78134c0253c7bc8bdfa432d3132252b3c638f Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 24 Mar 2015 12:47:57 -0400 Subject: [PATCH 0323/6300] Profiling added --- Profiling.cpp | 19 +++++++++++++++++++ Profiling.h | 29 +++++++++++++++++++++++++++++ RouterInfo.h | 2 ++ Win32/i2pd.vcxproj | 4 +++- build/CMakeLists.txt | 1 + build/autotools/Makefile.in | 6 +++--- filelist.mk | 4 ++-- 7 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 Profiling.cpp create mode 100644 Profiling.h diff --git a/Profiling.cpp b/Profiling.cpp new file mode 100644 index 00000000..269ad773 --- /dev/null +++ b/Profiling.cpp @@ -0,0 +1,19 @@ +#include +#include +#include "Profiling.h" + +namespace i2p +{ +namespace data +{ + RouterProfile::RouterProfile (const IdentHash& identHash): + m_IdentHash (identHash), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0) + { + } + + std::shared_ptr GetProfile (const IdentHash& identHash) + { + return std::make_shared (identHash); + } +} +} \ No newline at end of file diff --git a/Profiling.h b/Profiling.h new file mode 100644 index 00000000..c20934a7 --- /dev/null +++ b/Profiling.h @@ -0,0 +1,29 @@ +#ifndef PROFILING_H__ +#define PROFILING_H__ + +#include +#include "Identity.h" + +namespace i2p +{ +namespace data +{ + class RouterProfile + { + public: + + RouterProfile (const IdentHash& identHash); + + private: + + IdentHash m_IdentHash; + // participation + uint32_t m_NumTunnelsAgreed; + uint32_t m_NumTunnelsDeclined; + }; + + std::shared_ptr GetProfile (const IdentHash& identHash); +} +} + +#endif diff --git a/RouterInfo.h b/RouterInfo.h index fb9a6f13..53ddb5ab 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -8,6 +8,7 @@ #include #include #include "Identity.h" +#include "Profiling.h" namespace i2p { @@ -170,6 +171,7 @@ namespace data std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; uint8_t m_SupportedTransports, m_Caps; + std::shared_ptr m_Profile; }; } } diff --git a/Win32/i2pd.vcxproj b/Win32/i2pd.vcxproj index e34ead3d..4fc942ef 100644 --- a/Win32/i2pd.vcxproj +++ b/Win32/i2pd.vcxproj @@ -38,6 +38,7 @@ + @@ -82,6 +83,7 @@ + @@ -284,4 +286,4 @@ - \ No newline at end of file + diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 15eac7d9..1a660f81 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -22,6 +22,7 @@ set (COMMON_SRC "${CMAKE_SOURCE_DIR}/Log.cpp" "${CMAKE_SOURCE_DIR}/NTCPSession.cpp" "${CMAKE_SOURCE_DIR}/NetDb.cpp" + "${CMAKE_SOURCE_DIR}/Profiling.cpp" "${CMAKE_SOURCE_DIR}/Reseed.cpp" "${CMAKE_SOURCE_DIR}/RouterContext.cpp" "${CMAKE_SOURCE_DIR}/RouterInfo.cpp" diff --git a/build/autotools/Makefile.in b/build/autotools/Makefile.in index b28c620d..053ca6a9 100644 --- a/build/autotools/Makefile.in +++ b/build/autotools/Makefile.in @@ -116,7 +116,7 @@ am_i2p_OBJECTS = AddressBook.$(OBJEXT) CryptoConst.$(OBJEXT) \ aes.$(OBJEXT) base64.$(OBJEXT) i2p.$(OBJEXT) util.$(OBJEXT) \ SAM.$(OBJEXT) Destination.$(OBJEXT) ClientContext.$(OBJEXT) \ Datagram.$(OBJEXT) SSUSession.$(OBJEXT) BOB.$(OBJEXT) \ - I2PControl.$(OBJEXT) + I2PControl.$(OBJEXT) Profiling.$(OBJEXT) i2p_OBJECTS = $(am_i2p_OBJECTS) i2p_LDADD = $(LDADD) AM_V_P = $(am__v_P_@AM_V@) @@ -328,7 +328,7 @@ i2p_SOURCES = AddressBook.cpp CryptoConst.cpp Daemon.cpp \ TunnelGateway.cpp TunnelPool.cpp UPnP.cpp aes.cpp \ base64.cpp i2p.cpp util.cpp SAM.cpp Destination.cpp \ ClientContext.cpp DataFram.cpp SSUSession.cpp BOB.cpp \ - I2PControl.cpp \ + I2PControl.cpp Profiling.cpp \ \ AddressBook.h CryptoConst.h Daemon.h ElGamal.h \ Garlic.h HTTPProxy.h HTTPServer.h I2NPProtocol.h \ @@ -341,7 +341,7 @@ i2p_SOURCES = AddressBook.cpp CryptoConst.cpp Daemon.cpp \ TunnelPool.h UPnP.h aes.h base64.h config.h hmac.h \ util.h version.h Destination.h ClientContext.h \ TransportSession.h Datagram.h SSUSession.h BOB.h \ - I2PControl.h + I2PControl.h Profiling.h AM_LDFLAGS = @BOOST_DATE_TIME_LIB@ @BOOST_FILESYSTEM_LIB@ \ @BOOST_PROGRAM_OPTIONS_LIB@ @BOOST_REGEX_LIB@ \ diff --git a/filelist.mk b/filelist.mk index a8a3557e..876734c6 100644 --- a/filelist.mk +++ b/filelist.mk @@ -1,6 +1,6 @@ COMMON_SRC = \ - CryptoConst.cpp Datagram.cpp Garlic.cpp I2NPProtocol.cpp \ - LeaseSet.cpp Log.cpp NTCPSession.cpp NetDb.cpp Reseed.cpp RouterContext.cpp \ + CryptoConst.cpp Datagram.cpp Garlic.cpp I2NPProtocol.cpp LeaseSet.cpp \ + Log.cpp NTCPSession.cpp NetDb.cpp Profiling.cpp Reseed.cpp RouterContext.cpp \ RouterInfo.cpp SSU.cpp SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp \ TransitTunnel.cpp Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp \ TunnelGateway.cpp Destination.cpp UPnP.cpp util.cpp aes.cpp base64.cpp From 02903cc98c573c3ef73001c2db291ce8c7b6020e Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 24 Mar 2015 13:31:01 -0400 Subject: [PATCH 0324/6300] moved Dockerfile to build --- Dockerfile => build/Dockerfile | 0 fig.yml => build/fig.yml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Dockerfile => build/Dockerfile (100%) rename fig.yml => build/fig.yml (100%) diff --git a/Dockerfile b/build/Dockerfile similarity index 100% rename from Dockerfile rename to build/Dockerfile diff --git a/fig.yml b/build/fig.yml similarity index 100% rename from fig.yml rename to build/fig.yml From f8a7beb00189df9910cf7483d02ae85911332829 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 24 Mar 2015 13:31:56 -0400 Subject: [PATCH 0325/6300] --host is deprectaed --- build/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 2989e852..de486e6f 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -2,11 +2,11 @@ FROM ubuntu RUN apt-get update && apt-get install -y libboost-dev libboost-filesystem-dev \ libboost-program-options-dev libboost-regex-dev libcrypto++-dev \ - libboost-date-time-dev git build-essential curl + libboost-date-time-dev git build-essential RUN git clone https://github.com/PurpleI2P/i2pd.git WORKDIR /i2pd RUN make -CMD ./i2p --host=$(curl curlmyip.com) +CMD ./i2p From 5fd179524fab9d69879cfdab4e99a80950942c9b Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 24 Mar 2015 13:33:29 -0400 Subject: [PATCH 0326/6300] --host is deprecated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99db4028..bd2f5670 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ http://localhost:7070/4oes3rlgrpbkmzv4lqcfili23h3cvpwslqcfjlk6vvguxyggspwa.b32.i Cmdline options --------------- -* --host= - The external IP +* --host= - The external IP (deprecated). * --port= - The port to listen on * --httpport= - The http port to listen on * --log= - Enable or disable logging to file. 1 for yes, 0 for no. From fd3dab35cc2a8e6969815d8a9e7dbb0fac44f179 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 24 Mar 2015 18:48:16 -0400 Subject: [PATCH 0327/6300] collect and save participation agreed/declined stats --- NetDb.cpp | 12 ++++++---- Profiling.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++++++-- Profiling.h | 15 +++++++++++-- RouterInfo.cpp | 7 ++++++ RouterInfo.h | 5 ++++- Tunnel.cpp | 1 + 6 files changed, 90 insertions(+), 9 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 2f30890b..c8237865 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -108,6 +108,9 @@ namespace data void NetDb::Stop () { + for (auto it: m_RouterInfos) + it.second->SaveProfile (); + m_RouterInfos.clear (); if (m_Thread) { m_IsRunning = false; @@ -173,8 +176,7 @@ namespace data } lastSave = ts; } - if (i2p::context.GetLastUpdateTime () > lastPublish || // our router has been updated - ts - lastPublish >= 2400) // or publish every 40 minutes + if (ts - lastPublish >= 2400) // publish every 40 minutes { Publish (); lastPublish = ts; @@ -461,7 +463,10 @@ namespace data for (auto it = m_RouterInfos.begin (); it != m_RouterInfos.end ();) { if (it->second->IsUnreachable ()) + { + it->second->SaveProfile (); it = m_RouterInfos.erase (it); + } else it++; } @@ -861,7 +866,6 @@ namespace data void NetDb::Publish () { - i2p::context.UpdateStats (); std::set excluded; // TODO: fill up later for (int i = 0; i < 2; i++) { @@ -919,7 +923,7 @@ namespace data [compatibleWith](std::shared_ptr router)->bool { return !router->IsHidden () && router != compatibleWith && - router->IsCompatible (*compatibleWith) && router->IsHighBandwidth (); + router->IsCompatible (*compatibleWith) && (router->GetCaps () & RouterInfo::eHighBandwidth); }); } diff --git a/Profiling.cpp b/Profiling.cpp index 269ad773..b5558972 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -1,5 +1,8 @@ +#include #include #include +#include "base64.h" +#include "util.h" #include "Profiling.h" namespace i2p @@ -10,8 +13,60 @@ namespace data m_IdentHash (identHash), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0) { } - - std::shared_ptr GetProfile (const IdentHash& identHash) + + void RouterProfile::Save () + { + // fill sections + boost::property_tree::ptree participation; + participation.put (PEER_PROFILE_PARTICIPATION_AGREED, m_NumTunnelsAgreed); + participation.put (PEER_PROFILE_PARTICIPATION_DECLINED, m_NumTunnelsDeclined); + // fill property tree + boost::property_tree::ptree pt; + pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation); + + // save to file + auto path = i2p::util::filesystem::GetDefaultDataDir() / PEER_PROFILES_DIRECTORY; + if (!boost::filesystem::exists (path)) + { + // Create directory is necessary + if (!boost::filesystem::create_directory (path)) + { + LogPrint (eLogError, "Failed to create directory ", path); + return; + } + const char * chars = GetBase64SubstitutionTable (); // 64 bytes + for (int i = 0; i < 64; i++) + { + auto path1 = path / (std::string ("p") + chars[i]); + if (!boost::filesystem::create_directory (path1)) + { + LogPrint (eLogError, "Failed to create directory ", path1); + return; + } + } + } + std::string base64 = m_IdentHash.ToBase64 (); + path = path / (std::string ("p") + base64[0]); + auto filename = path / (std::string (PEER_PROFILE_PREFIX) + base64 + ".txt"); + try + { + boost::property_tree::write_ini (filename.string (), pt); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Can't write ", filename, ": ", ex.what ()); + } + } + + void RouterProfile::TunnelBuildResponse (uint8_t ret) + { + if (ret > 0) + m_NumTunnelsDeclined++; + else + m_NumTunnelsAgreed++; + } + + std::shared_ptr GetRouterProfile (const IdentHash& identHash) { return std::make_shared (identHash); } diff --git a/Profiling.h b/Profiling.h index c20934a7..4e048ef0 100644 --- a/Profiling.h +++ b/Profiling.h @@ -7,12 +7,23 @@ namespace i2p { namespace data -{ +{ + const char PEER_PROFILES_DIRECTORY[] = "peerProfiles"; + const char PEER_PROFILE_PREFIX[] = "profile-"; + // sections + const char PEER_PROFILE_SECTION_PARTICIPATION[] = "participation"; + // params + const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed"; + const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined"; + class RouterProfile { public: RouterProfile (const IdentHash& identHash); + void Save (); + + void TunnelBuildResponse (uint8_t ret); private: @@ -22,7 +33,7 @@ namespace data uint32_t m_NumTunnelsDeclined; }; - std::shared_ptr GetProfile (const IdentHash& identHash); + std::shared_ptr GetRouterProfile (const IdentHash& identHash); } } diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 66eca3b4..a1c3533f 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -652,5 +652,12 @@ namespace data } return nullptr; } + + std::shared_ptr RouterInfo::GetProfile () const + { + if (!m_Profile) + m_Profile = GetRouterProfile (GetIdentHash ()); + return m_Profile; + } } } diff --git a/RouterInfo.h b/RouterInfo.h index 53ddb5ab..4934da32 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -138,6 +138,9 @@ namespace data void SetUpdated (bool updated) { m_IsUpdated = updated; }; void SaveToFile (const std::string& fullPath); + std::shared_ptr GetProfile () const; + void SaveProfile () { if (m_Profile) m_Profile->Save (); }; + void Update (const uint8_t * buf, int len); void DeleteBuffer () { delete m_Buffer; m_Buffer = nullptr; }; @@ -171,7 +174,7 @@ namespace data std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; uint8_t m_SupportedTransports, m_Caps; - std::shared_ptr m_Profile; + mutable std::shared_ptr m_Profile; }; } } diff --git a/Tunnel.cpp b/Tunnel.cpp index f04c7f05..4852c7bd 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -121,6 +121,7 @@ namespace tunnel const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE; uint8_t ret = record[BUILD_RESPONSE_RECORD_RET_OFFSET]; LogPrint ("Ret code=", (int)ret); + hop->router->GetProfile ()->TunnelBuildResponse (ret); if (ret) // if any of participants declined the tunnel is not established established = false; From 0a7bf4db47ca0f4b182a891f2a2a69a9c649355d Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 Mar 2015 08:45:50 -0400 Subject: [PATCH 0328/6300] load profiles --- Profiling.cpp | 36 +++++++++++++++++++++++++++++++++++- Profiling.h | 2 ++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Profiling.cpp b/Profiling.cpp index b5558972..fb45e0af 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -57,6 +57,38 @@ namespace data LogPrint (eLogError, "Can't write ", filename, ": ", ex.what ()); } } + + void RouterProfile::Load () + { + std::string base64 = m_IdentHash.ToBase64 (); + auto path = i2p::util::filesystem::GetDefaultDataDir() / PEER_PROFILES_DIRECTORY; + path /= std::string ("p") + base64[0]; + auto filename = path / (std::string (PEER_PROFILE_PREFIX) + base64 + ".txt"); + if (boost::filesystem::exists (filename)) + { + boost::property_tree::ptree pt; + try + { + boost::property_tree::read_ini (filename.string (), pt); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Can't read ", filename, ": ", ex.what ()); + return; + } + try + { + // read participations + auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION); + m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0); + m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Can't read profile ", base64, " :", ex.what ()); + } + } + } void RouterProfile::TunnelBuildResponse (uint8_t ret) { @@ -68,7 +100,9 @@ namespace data std::shared_ptr GetRouterProfile (const IdentHash& identHash) { - return std::make_shared (identHash); + auto profile = std::make_shared (identHash); + profile->Load (); // if possible + return profile; } } } \ No newline at end of file diff --git a/Profiling.h b/Profiling.h index 4e048ef0..1f175dc7 100644 --- a/Profiling.h +++ b/Profiling.h @@ -21,7 +21,9 @@ namespace data public: RouterProfile (const IdentHash& identHash); + void Save (); + void Load (); void TunnelBuildResponse (uint8_t ret); From 6e9149afd49a718576cbe99023aa5e3263fb4aad Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 Mar 2015 09:04:19 -0400 Subject: [PATCH 0329/6300] fixed misalignment --- SSUSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index a5ba57f3..37fd259e 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -1094,7 +1094,7 @@ namespace transport { uint8_t buf[SSU_MTU_V4 + 18]; size_t msgSize = len + sizeof (SSUHeader); - size_t paddingSize = msgSize >> 4; // %16 + size_t paddingSize = msgSize & 0x0F; // %16 if (paddingSize > 0) msgSize += (16 - paddingSize); if (msgSize > SSU_MTU_V4) { From 1a0abfbc06435e393edf07c7efdd792bf4ea0fea Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 Mar 2015 19:13:30 -0400 Subject: [PATCH 0330/6300] create keys only by DEST GENERATE --- SAM.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index dea72ad3..0153049b 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -387,20 +387,15 @@ namespace client void SAMSocket::ProcessDestGenerate () { LogPrint ("SAM dest generate"); - auto localDestination = i2p::client::context.CreateNewLocalDestination (); - if (localDestination) - { - auto priv = localDestination->GetPrivateKeys ().ToBase64 (); - auto pub = localDestination->GetIdentity ().ToBase64 (); + auto keys = i2p::data::PrivateKeys::CreateRandomKeys (); #ifdef _MSC_VER - size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, pub.c_str (), priv.c_str ()); + size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, + keys.GetPublic ().ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); #else - size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, pub.c_str (), priv.c_str ()); + size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, + keys.GetPublic ().ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); #endif - SendMessageReply (m_Buffer, len, true); - } - else - SendMessageReply (SAM_DEST_REPLY_I2P_ERROR, strlen(SAM_DEST_REPLY_I2P_ERROR), true); + SendMessageReply (m_Buffer, len, false); } void SAMSocket::ProcessNamingLookup (char * buf, size_t len) From ca3b9f253d90837f2f647fefc81ed549c5013f59 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 Mar 2015 22:23:41 -0400 Subject: [PATCH 0331/6300] pass data for streams only --- SAM.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SAM.cpp b/SAM.cpp index 0153049b..04826623 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -490,7 +490,7 @@ namespace client void SAMSocket::Receive () { m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE), - std::bind((m_SocketType == eSAMSocketTypeSession) ? &SAMSocket::HandleMessage : &SAMSocket::HandleReceived, + std::bind((m_SocketType == eSAMSocketTypeStream) ? &SAMSocket::HandleReceived : &SAMSocket::HandleMessage, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -556,6 +556,7 @@ namespace client auto session = m_Owner.FindSession (m_ID); if (session) session->localDestination->StopAcceptingStreams (); + m_SocketType = eSAMSocketTypeStream; if (!m_IsSilent) { // send remote peer address From 2a23537dbd5f8057d2e4d751a7f7fdbda72dd1d6 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Mar 2015 10:30:29 -0400 Subject: [PATCH 0332/6300] check lease expiration with threshold --- LeaseSet.cpp | 11 ++++++++--- LeaseSet.h | 2 +- Streaming.cpp | 18 ++++++++++-------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 0d6d3fb8..b9c2b132 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -48,7 +48,7 @@ namespace data m_BufferLen += 32; // gateway id htobe32buf (m_Buffer + m_BufferLen, it->GetNextTunnelID ()); m_BufferLen += 4; // tunnel id - uint64_t ts = it->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - 60; // 1 minute before expiration + uint64_t ts = it->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration ts *= 1000; // in milliseconds ts += rnd.GenerateWord32 (0, 5); // + random milliseconds htobe64buf (m_Buffer + m_BufferLen, ts); @@ -107,13 +107,18 @@ namespace data LogPrint ("LeaseSet verification failed"); } - const std::vector LeaseSet::GetNonExpiredLeases () const + const std::vector LeaseSet::GetNonExpiredLeases (bool withThreshold) const { auto ts = i2p::util::GetMillisecondsSinceEpoch (); std::vector leases; for (auto& it: m_Leases) - if (ts < it.endDate) + { + auto endDate = it.endDate; + if (!withThreshold) + endDate -= i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000; + if (ts < endDate) leases.push_back (it); + } return leases; } diff --git a/LeaseSet.h b/LeaseSet.h index 10fa71e9..eac5fc28 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -49,7 +49,7 @@ namespace data // implements RoutingDestination const IdentHash& GetIdentHash () const { return m_Identity.GetIdentHash (); }; const std::vector& GetLeases () const { return m_Leases; }; - const std::vector GetNonExpiredLeases () const; + const std::vector GetNonExpiredLeases (bool withThreshold = true) const; bool HasExpiredLeases () const; bool HasNonExpiredLeases () const; const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionKey; }; diff --git a/Streaming.cpp b/Streaming.cpp index 8b73e849..5f110fd5 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -585,7 +585,7 @@ namespace stream } auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (ts >= m_CurrentRemoteLease.endDate) + if (ts >= m_CurrentRemoteLease.endDate - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000) UpdateCurrentRemoteLease (); if (ts < m_CurrentRemoteLease.endDate) { @@ -604,11 +604,7 @@ namespace stream m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs); } else - { - LogPrint (eLogInfo, "All leases are expired. Trying to request"); - m_RemoteLeaseSet = nullptr; - m_LocalDestination.GetOwner ().RequestDestination (m_RemoteIdentity.GetIdentHash ()); - } + LogPrint (eLogWarning, "All leases are expired"); } @@ -703,7 +699,12 @@ namespace stream { if (!m_RoutingSession) m_RoutingSession = m_LocalDestination.GetOwner ().GetRoutingSession (m_RemoteLeaseSet, 32); - auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (); + auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (false); // try without threshold first + if (leases.empty ()) + { + m_LocalDestination.GetOwner ().RequestDestination (m_RemoteIdentity.GetIdentHash ()); // time to re-request + leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold + } if (!leases.empty ()) { uint32_t i = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, leases.size () - 1); @@ -714,8 +715,9 @@ namespace stream } else { - m_RemoteLeaseSet = m_LocalDestination.GetOwner ().FindLeaseSet (m_RemoteIdentity.GetIdentHash ()); // re-request expired + m_RemoteLeaseSet = nullptr; m_CurrentRemoteLease.endDate = 0; + // re-request expired } } else From 39641f05b94a8dcc1701202570c6688363741f34 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Mar 2015 11:15:29 -0400 Subject: [PATCH 0333/6300] create datagram destination for DATAGRAM session --- SAM.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index 04826623..41499dd5 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -253,16 +253,15 @@ namespace client if (m_Session) { m_SocketType = eSAMSocketTypeSession; - if (m_Session->localDestination->IsReady ()) + if (style == SAM_VALUE_DATAGRAM) { - if (style == SAM_VALUE_DATAGRAM) - { - auto dest = m_Session->localDestination->CreateDatagramDestination (); - dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); - } - SendSessionCreateReplyOk (); + auto dest = m_Session->localDestination->CreateDatagramDestination (); + dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); } + + if (m_Session->localDestination->IsReady ()) + SendSessionCreateReplyOk (); else { m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL)); From fce74a710f2bcd87623c9e8e3af7ebd4ffc1dcaa Mon Sep 17 00:00:00 2001 From: guanqun Date: Thu, 26 Mar 2015 23:31:03 +0800 Subject: [PATCH 0334/6300] fix tab in README.md --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index bd2f5670..7de735d6 100644 --- a/README.md +++ b/README.md @@ -104,21 +104,21 @@ i2p.conf: tunnels.cfg (filename of this config is subject of change): - ; outgoing tunnel, to remote service - [tunnel1] - type = client ; mandatory - port = ; mandatory, bind our side of tunnel to this local port - keys = ; optional - destination = ; mandatory - destinationport = ; optional, port of remote i2p service - - ; incoming tunnel, for local service(s) - [tunnel2] - type = server ; mandatory - host = ; mandatory, hostname of our i2p service - keys = ; mandatory, hostname keys - port = ; mandatory, forward incoming connections from i2p to this port - inport = ; optional, i2p service port - accesslist = [,] ; optional, comma-separated list of i2p idents, allowed to connect to service + ; outgoing tunnel, to remote service + [tunnel1] + type = client ; mandatory + port = ; mandatory, bind our side of tunnel to this local port + keys = ; optional + destination = ; mandatory + destinationport = ; optional, port of remote i2p service + + ; incoming tunnel, for local service(s) + [tunnel2] + type = server ; mandatory + host = ; mandatory, hostname of our i2p service + keys = ; mandatory, hostname keys + port = ; mandatory, forward incoming connections from i2p to this port + inport = ; optional, i2p service port + accesslist = [,] ; optional, comma-separated list of i2p idents, allowed to connect to service Note: '' type is a string like or From 4831e9705c088d5a15f06592d7bf30ae6fe43d4a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Mar 2015 14:35:20 -0400 Subject: [PATCH 0335/6300] const buffer for ProcessPeerTest --- SSUSession.cpp | 10 +++++----- SSUSession.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index 37fd259e..5b3e9cb2 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -880,9 +880,9 @@ namespace transport } } - void SSUSession::ProcessPeerTest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { - uint8_t * buf1 = buf; + const uint8_t * buf1 = buf; uint32_t nonce = bufbe32toh (buf); buf += 4; // nonce uint8_t size = *buf; @@ -892,7 +892,7 @@ namespace transport buf += size; // address uint16_t port = buf16toh(buf); // use it as is buf += 2; // port - uint8_t * introKey = buf; + const uint8_t * introKey = buf; if (port && !address) { LogPrint (eLogWarning, "Address of ", size, " bytes not supported"); @@ -935,19 +935,19 @@ namespace transport case ePeerTestParticipantBob: { LogPrint (eLogDebug, "SSU peer test from Charlie. We are Bob"); - m_Server.RemovePeerTest (nonce); // nonce has been used boost::asio::ip::udp::endpoint ep (boost::asio::ip::address_v4 (be32toh (address)), be16toh (port)); // Alice's address/port auto session = m_Server.FindSession (ep); // find session with Alice if (session) session->Send (PAYLOAD_TYPE_PEER_TEST, buf1, len); // back to Alice + m_Server.RemovePeerTest (nonce); // nonce has been used break; } case ePeerTestParticipantCharlie: { LogPrint (eLogDebug, "SSU peer test from Alice. We are Charlie"); - m_Server.RemovePeerTest (nonce); // nonce has been used SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), senderEndpoint.port (), introKey); // to Alice with her actual address + m_Server.RemovePeerTest (nonce); // nonce has been used break; } // test not found diff --git a/SSUSession.h b/SSUSession.h index 82fa0f33..5f980784 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -115,7 +115,7 @@ namespace transport void Failed (); void ScheduleConnectTimer (); void HandleConnectTimer (const boost::system::error_code& ecode); - void ProcessPeerTest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); + void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); void SendPeerTest (uint32_t nonce, uint32_t address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true); void ProcessData (uint8_t * buf, size_t len); void SendSesionDestroyed (); From c62659cdbc9d907258c3854df0922c25317ca647 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Mar 2015 15:05:52 -0400 Subject: [PATCH 0336/6300] single buf for ProcessPeerTest --- SSUSession.cpp | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index 5b3e9cb2..ed4362ea 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -882,17 +882,11 @@ namespace transport void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { - const uint8_t * buf1 = buf; - uint32_t nonce = bufbe32toh (buf); - buf += 4; // nonce - uint8_t size = *buf; - buf++; // size - - uint32_t address = (size == 4) ? buf32toh(buf) : 0; // use it as is - buf += size; // address - uint16_t port = buf16toh(buf); // use it as is - buf += 2; // port - const uint8_t * introKey = buf; + uint32_t nonce = bufbe32toh (buf); // 4 bytes + uint8_t size = buf[4]; // 1 byte + uint32_t address = (size == 4) ? buf32toh(buf + 5) : 0; // big endian, size bytes + uint16_t port = buf16toh(buf + size + 5); // big endian, 2 bytes + const uint8_t * introKey = buf + size + 7; if (port && !address) { LogPrint (eLogWarning, "Address of ", size, " bytes not supported"); @@ -938,7 +932,7 @@ namespace transport boost::asio::ip::udp::endpoint ep (boost::asio::ip::address_v4 (be32toh (address)), be16toh (port)); // Alice's address/port auto session = m_Server.FindSession (ep); // find session with Alice if (session) - session->Send (PAYLOAD_TYPE_PEER_TEST, buf1, len); // back to Alice + session->Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Alice m_Server.RemovePeerTest (nonce); // nonce has been used break; } @@ -960,7 +954,7 @@ namespace transport { LogPrint (eLogDebug, "SSU peer test from Bob. We are Charlie"); m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); - Send (PAYLOAD_TYPE_PEER_TEST, buf1, len); // back to Bob + Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob SendPeerTest (nonce, be32toh (address), be16toh (port), introKey); // to Alice with her address received from Bob } else From d226a4a0b759027653d3da8e603fbfe49df87cd9 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Mar 2015 16:10:52 -0400 Subject: [PATCH 0337/6300] store session with Alice with PeerTest --- SSU.cpp | 14 +++++++++++--- SSU.h | 4 +++- SSUSession.cpp | 7 +++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 8c7948ee..457c1213 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -256,7 +256,6 @@ namespace transport std::shared_ptr SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const { - std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (e); if (it != m_Sessions.end ()) return it->second; @@ -473,9 +472,9 @@ namespace transport } } - void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role) + void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session) { - m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role }; + m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role, session }; } PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce) @@ -487,6 +486,15 @@ namespace transport return ePeerTestParticipantUnknown; } + std::shared_ptr SSUServer::GetPeerTestSession (uint32_t nonce) + { + auto it = m_PeerTests.find (nonce); + if (it != m_PeerTests.end ()) + return it->second.session; + else + return nullptr; + } + void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role) { auto it = m_PeerTests.find (nonce); diff --git a/SSU.h b/SSU.h index 1b954b21..1033a2bf 100644 --- a/SSU.h +++ b/SSU.h @@ -54,8 +54,9 @@ namespace transport void AddRelay (uint32_t tag, const boost::asio::ip::udp::endpoint& relay); std::shared_ptr FindRelaySession (uint32_t tag); - void NewPeerTest (uint32_t nonce, PeerTestParticipant role); + void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session = nullptr); PeerTestParticipant GetPeerTestParticipant (uint32_t nonce); + std::shared_ptr GetPeerTestSession (uint32_t nonce); void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role); void RemovePeerTest (uint32_t nonce); @@ -86,6 +87,7 @@ namespace transport { uint64_t creationTime; PeerTestParticipant role; + std::shared_ptr session; // for Bob to Alice }; bool m_IsRunning; diff --git a/SSUSession.cpp b/SSUSession.cpp index ed4362ea..edc5cb9f 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -929,9 +929,8 @@ namespace transport case ePeerTestParticipantBob: { LogPrint (eLogDebug, "SSU peer test from Charlie. We are Bob"); - boost::asio::ip::udp::endpoint ep (boost::asio::ip::address_v4 (be32toh (address)), be16toh (port)); // Alice's address/port - auto session = m_Server.FindSession (ep); // find session with Alice - if (session) + auto session = m_Server.GetPeerTestSession (nonce); // session with Alice from PeerTest + if (session && session->m_State == eSessionStateEstablished) session->Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Alice m_Server.RemovePeerTest (nonce); // nonce has been used break; @@ -963,7 +962,7 @@ namespace transport auto session = m_Server.GetRandomEstablishedSession (shared_from_this ()); // Charlie if (session) { - m_Server.NewPeerTest (nonce, ePeerTestParticipantBob); + m_Server.NewPeerTest (nonce, ePeerTestParticipantBob, shared_from_this ()); session->SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), senderEndpoint.port (), introKey, false); // to Charlie with Alice's actual address } From f07d29bc8a8e7d63b5f522da811e9f839b135279 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Mar 2015 21:23:59 -0400 Subject: [PATCH 0338/6300] handle DATAGRAM SEND --- Datagram.cpp | 27 ++++++++++++++++++++++---- Datagram.h | 4 +++- SAM.cpp | 54 +++++++++++++++++++++++++++++++++++++--------------- SAM.h | 7 +++++-- 4 files changed, 70 insertions(+), 22 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 412ef318..9faf9ab2 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -17,7 +17,7 @@ namespace datagram { } - void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, std::shared_ptr remote) + void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident) { uint8_t buf[MAX_DATAGRAM_SIZE]; auto identityLen = m_Owner.GetIdentity ().ToBuffer (buf, MAX_DATAGRAM_SIZE); @@ -35,11 +35,30 @@ namespace datagram } else m_Owner.Sign (buf1, len, signature); - - m_Owner.GetService ().post (std::bind (&DatagramDestination::SendMsg, this, - CreateDataMessage (buf, len + headerLen), remote)); + + auto msg = CreateDataMessage (buf, len + headerLen); + auto remote = m_Owner.FindLeaseSet (ident); + if (remote) + m_Owner.GetService ().post (std::bind (&DatagramDestination::SendMsg, this, msg, remote)); + else + m_Owner.RequestDestination (ident, std::bind (&DatagramDestination::HandleLeaseSetRequestComplete, + this, std::placeholders::_1, msg, ident)); } + void DatagramDestination::HandleLeaseSetRequestComplete (bool success, I2NPMessage * msg, i2p::data::IdentHash ident) + { + if (success) + { + auto remote = m_Owner.FindLeaseSet (ident); + if (remote) + { + SendMsg (msg, remote); + return; + } + } + DeleteI2NPMessage (msg); + } + void DatagramDestination::SendMsg (I2NPMessage * msg, std::shared_ptr remote) { auto outboundTunnel = m_Owner.GetTunnelPool ()->GetNextOutboundTunnel (); diff --git a/Datagram.h b/Datagram.h index 7df5e3cd..dd84b656 100644 --- a/Datagram.h +++ b/Datagram.h @@ -26,7 +26,7 @@ namespace datagram DatagramDestination (i2p::client::ClientDestination& owner); ~DatagramDestination () {}; - void SendDatagramTo (const uint8_t * payload, size_t len, std::shared_ptr remote); + void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident); void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; @@ -34,6 +34,8 @@ namespace datagram private: + void HandleLeaseSetRequestComplete (bool success, I2NPMessage * msg, i2p::data::IdentHash ident); + I2NPMessage * CreateDataMessage (const uint8_t * payload, size_t len); void SendMsg (I2NPMessage * msg, std::shared_ptr remote); void HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); diff --git a/SAM.cpp b/SAM.cpp index 41499dd5..f90bac19 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -102,7 +102,7 @@ namespace client { separator++; std::map params; - ExtractParams (separator, bytes_transferred - (separator - m_Buffer), params); + ExtractParams (separator, params); auto it = params.find (SAM_PARAM_MAX); // TODO: check MIN as well if (it != params.end ()) @@ -208,6 +208,8 @@ namespace client ProcessStreamConnect (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_STREAM_ACCEPT)) ProcessStreamAccept (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND)) + ProcessDatagramSend (separator + 1, bytes_transferred - (separator - m_Buffer) - 1, eol + 1); else if (!strcmp (m_Buffer, SAM_DEST_GENERATE)) ProcessDestGenerate (); else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP)) @@ -236,7 +238,7 @@ namespace client { LogPrint ("SAM session create: ", buf); std::map params; - ExtractParams (buf, len, params); + ExtractParams (buf, params); std::string& style = params[SAM_PARAM_STYLE]; std::string& id = params[SAM_PARAM_ID]; std::string& destination = params[SAM_PARAM_DESTINATION]; @@ -307,7 +309,7 @@ namespace client { LogPrint ("SAM stream connect: ", buf); std::map params; - ExtractParams (buf, len, params); + ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; std::string& destination = params[SAM_PARAM_DESTINATION]; std::string& silent = params[SAM_PARAM_SILENT]; @@ -361,7 +363,7 @@ namespace client { LogPrint ("SAM stream accept: ", buf); std::map params; - ExtractParams (buf, len, params); + ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; std::string& silent = params[SAM_PARAM_SILENT]; if (silent == SAM_VALUE_TRUE) m_IsSilent = true; @@ -383,6 +385,35 @@ namespace client SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } + void SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data) + { + LogPrint ("SAM datagram send: ", buf); + std::map params; + ExtractParams (buf, params); + size_t size = boost::lexical_cast(params[SAM_PARAM_SIZE]); + if (size < len) + { + if (m_Session) + { + auto d = m_Session->localDestination->GetDatagramDestination (); + if (d) + { + i2p::data::IdentityEx dest; + dest.FromBase64 (params[SAM_PARAM_DESTINATION]); + d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); + } + else + LogPrint (eLogError, "SAM missing datagram destination"); + } + else + LogPrint (eLogError, "SAM session is not created from DATAGRAM SEND"); + } + else + LogPrint (eLogError, "SAM datagram size ", size, " exceeds buffer"); + // since it's SAM v1 reply is not expected + Receive (); + } + void SAMSocket::ProcessDestGenerate () { LogPrint ("SAM dest generate"); @@ -401,7 +432,7 @@ namespace client { LogPrint ("SAM naming lookup: ", buf); std::map params; - ExtractParams (buf, len, params); + ExtractParams (buf, params); std::string& name = params[SAM_PARAM_NAME]; i2p::data::IdentityEx identity; i2p::data::IdentHash ident; @@ -467,7 +498,7 @@ namespace client SendMessageReply (m_Buffer, l, false); } - void SAMSocket::ExtractParams (char * buf, size_t len, std::map& params) + void SAMSocket::ExtractParams (char * buf, std::map& params) { char * separator; do @@ -779,15 +810,8 @@ namespace client { i2p::data::IdentityEx dest; dest.FromBase64 (destination); - auto leaseSet = i2p::data::netdb.FindLeaseSet (dest.GetIdentHash ()); - if (leaseSet) - session->localDestination->GetDatagramDestination ()-> - SendDatagramTo ((uint8_t *)eol, payloadLen, leaseSet); - else - { - LogPrint ("SAM datagram destination not found"); - session->localDestination->RequestDestination (dest.GetIdentHash ()); - } + session->localDestination->GetDatagramDestination ()-> + SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); } else LogPrint ("Session ", sessionID, " not found"); diff --git a/SAM.h b/SAM.h index de3cc8e0..5da4539b 100644 --- a/SAM.h +++ b/SAM.h @@ -34,6 +34,7 @@ namespace client const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER\n"; const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR\n"; const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT"; + const char SAM_DATAGRAM_SEND[] = "DATAGRAM SEND"; const char SAM_DEST_GENERATE[] = "DEST GENERATE"; const char SAM_DEST_REPLY[] = "DEST REPLY PUB=%s PRIV=%s\n"; const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n"; @@ -49,7 +50,8 @@ namespace client const char SAM_PARAM_SILENT[] = "SILENT"; const char SAM_PARAM_DESTINATION[] = "DESTINATION"; const char SAM_PARAM_NAME[] = "NAME"; - const char SAM_PARAM_SIGNATURE_TYPE[] = "SIGNATURE_TYPE"; + const char SAM_PARAM_SIGNATURE_TYPE[] = "SIGNATURE_TYPE"; + const char SAM_PARAM_SIZE[] = "SIZE"; const char SAM_VALUE_TRANSIENT[] = "TRANSIENT"; const char SAM_VALUE_STREAM[] = "STREAM"; const char SAM_VALUE_DATAGRAM[] = "DATAGRAM"; @@ -101,9 +103,10 @@ namespace client void ProcessSessionCreate (char * buf, size_t len); void ProcessStreamConnect (char * buf, size_t len); void ProcessStreamAccept (char * buf, size_t len); + void ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0 void ProcessDestGenerate (); void ProcessNamingLookup (char * buf, size_t len); - void ExtractParams (char * buf, size_t len, std::map& params); + void ExtractParams (char * buf, std::map& params); void Connect (std::shared_ptr remote); void HandleConnectLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident); From c92e00c21fd69294fce1b889867657381a2f293c Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Mar 2015 22:17:26 -0400 Subject: [PATCH 0339/6300] proper verification for DSA_SHA1 --- Datagram.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Datagram.cpp b/Datagram.cpp index 9faf9ab2..b9fd6a2b 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -95,7 +95,11 @@ namespace datagram bool verified = false; if (identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) - verified = CryptoPP::SHA256().VerifyDigest (signature, buf + headerLen, len - headerLen); + { + uint8_t hash[32]; + CryptoPP::SHA256().CalculateDigest (hash, buf + headerLen, len - headerLen); + verified = identity.Verify (hash, 32, signature); + } else verified = identity.Verify (buf + headerLen, len - headerLen, signature); From eddb5fa91ed6e42e4593604a9cc12e123455d006 Mon Sep 17 00:00:00 2001 From: guanqun Date: Fri, 27 Mar 2015 10:48:26 +0800 Subject: [PATCH 0340/6300] fix the 'sleep_for' missing error on linux platform --- build/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 1a660f81..78efe80e 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -103,6 +103,8 @@ endif () # compiler flags customization (by system) if (CMAKE_SYSTEM_NAME STREQUAL "Linux") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonLinux.cpp") + # "'sleep_for' is not a member of 'std::this_thread'" in gcc 4.7/4.8 + add_definitions( "-D_GLIBCXX_USE_NANOSLEEP=1" ) elseif (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonLinux.cpp") # "'sleep_for' is not a member of 'std::this_thread'" in gcc 4.7/4.8 From 96ace89789fedd7edeb335c0181b862197544524 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Mar 2015 09:44:27 -0400 Subject: [PATCH 0341/6300] increase SAM connection buffer size --- SAM.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAM.h b/SAM.h index 5da4539b..70ffe4b0 100644 --- a/SAM.h +++ b/SAM.h @@ -18,7 +18,7 @@ namespace i2p { namespace client { - const size_t SAM_SOCKET_BUFFER_SIZE = 4096; + const size_t SAM_SOCKET_BUFFER_SIZE = 8192; const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds const int SAM_SESSION_READINESS_CHECK_INTERVAL = 20; // in seconds const char SAM_HANDSHAKE[] = "HELLO VERSION"; From cc91a6d96fb8b83e847f664133e3fbb8c3b59b13 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Mar 2015 11:29:40 -0400 Subject: [PATCH 0342/6300] send ports with datagram --- Datagram.cpp | 9 +++++---- Datagram.h | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index b9fd6a2b..92a01b7f 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -17,7 +17,7 @@ namespace datagram { } - void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident) + void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort) { uint8_t buf[MAX_DATAGRAM_SIZE]; auto identityLen = m_Owner.GetIdentity ().ToBuffer (buf, MAX_DATAGRAM_SIZE); @@ -36,7 +36,7 @@ namespace datagram else m_Owner.Sign (buf1, len, signature); - auto msg = CreateDataMessage (buf, len + headerLen); + auto msg = CreateDataMessage (buf, len + headerLen, fromPort, toPort); auto remote = m_Owner.FindLeaseSet (ident); if (remote) m_Owner.GetService ().post (std::bind (&DatagramDestination::SendMsg, this, msg, remote)); @@ -132,7 +132,7 @@ namespace datagram } - I2NPMessage * DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len) + I2NPMessage * DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) { I2NPMessage * msg = NewI2NPMessage (); CryptoPP::Gzip compressor; // default level @@ -143,7 +143,8 @@ namespace datagram htobe32buf (buf, size); // length buf += 4; compressor.Get (buf, size); - memset (buf + 4, 0, 4); // source and destination are zeroes + htobe16buf (buf + 4, fromPort); // source port + htobe16buf (buf + 6, toPort); // destination port buf[9] = i2p::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol msg->len += size + 4; FillI2NPMessageHeader (msg, eI2NPData); diff --git a/Datagram.h b/Datagram.h index dd84b656..5acd79b2 100644 --- a/Datagram.h +++ b/Datagram.h @@ -26,7 +26,7 @@ namespace datagram DatagramDestination (i2p::client::ClientDestination& owner); ~DatagramDestination () {}; - void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident); + void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort = 0, uint16_t toPort = 0); void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; @@ -36,7 +36,7 @@ namespace datagram void HandleLeaseSetRequestComplete (bool success, I2NPMessage * msg, i2p::data::IdentHash ident); - I2NPMessage * CreateDataMessage (const uint8_t * payload, size_t len); + I2NPMessage * CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); void SendMsg (I2NPMessage * msg, std::shared_ptr remote); void HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); From 2a4d1c978e2b87a5d9476c72d31da8c396da3770 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Mar 2015 14:02:27 -0400 Subject: [PATCH 0343/6300] process multiple DATAGRAM SEND message --- SAM.cpp | 45 ++++++++++++++++++++++++++++----------------- SAM.h | 3 ++- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index f90bac19..f0fad542 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -17,8 +17,8 @@ namespace client { SAMSocket::SAMSocket (SAMBridge& owner): m_Owner (owner), m_Socket (m_Owner.GetService ()), m_Timer (m_Owner.GetService ()), - m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false), m_Stream (nullptr), - m_Session (nullptr) + m_BufferOffset (0), m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false), + m_Stream (nullptr), m_Session (nullptr) { } @@ -188,6 +188,8 @@ namespace client } else { + bytes_transferred += m_BufferOffset; + m_BufferOffset = 0; m_Buffer[bytes_transferred] = 0; char * eol = strchr (m_Buffer, '\n'); if (eol) @@ -208,12 +210,21 @@ namespace client ProcessStreamConnect (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_STREAM_ACCEPT)) ProcessStreamAccept (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); - else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND)) - ProcessDatagramSend (separator + 1, bytes_transferred - (separator - m_Buffer) - 1, eol + 1); else if (!strcmp (m_Buffer, SAM_DEST_GENERATE)) ProcessDestGenerate (); else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP)) ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND)) + { + size_t processed = ProcessDatagramSend (separator + 1, bytes_transferred - (separator - m_Buffer) - 1, eol + 1); + if (processed < bytes_transferred) + { + m_BufferOffset = bytes_transferred - processed; + memmove (m_Buffer, m_Buffer + processed, m_BufferOffset); + } + // since it's SAM v1 reply is not expected + Receive (); + } else { LogPrint ("SAM unexpected message ", m_Buffer); @@ -307,7 +318,7 @@ namespace client void SAMSocket::ProcessStreamConnect (char * buf, size_t len) { - LogPrint ("SAM stream connect: ", buf); + LogPrint (eLogDebug, "SAM stream connect: ", buf); std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; @@ -361,7 +372,7 @@ namespace client void SAMSocket::ProcessStreamAccept (char * buf, size_t len) { - LogPrint ("SAM stream accept: ", buf); + LogPrint (eLogDebug, "SAM stream accept: ", buf); std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; @@ -385,13 +396,13 @@ namespace client SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } - void SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data) + size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data) { - LogPrint ("SAM datagram send: ", buf); + LogPrint (eLogDebug, "SAM datagram send: ", buf); std::map params; ExtractParams (buf, params); - size_t size = boost::lexical_cast(params[SAM_PARAM_SIZE]); - if (size < len) + size_t size = boost::lexical_cast(params[SAM_PARAM_SIZE]), offset = data - buf; + if (offset + size < len) { if (m_Session) { @@ -409,14 +420,13 @@ namespace client LogPrint (eLogError, "SAM session is not created from DATAGRAM SEND"); } else - LogPrint (eLogError, "SAM datagram size ", size, " exceeds buffer"); - // since it's SAM v1 reply is not expected - Receive (); + LogPrint (eLogError, "SAM sent datagram size ", size, " exceeds buffer"); + return offset + size; } void SAMSocket::ProcessDestGenerate () { - LogPrint ("SAM dest generate"); + LogPrint (eLogDebug, "SAM dest generate"); auto keys = i2p::data::PrivateKeys::CreateRandomKeys (); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, @@ -430,7 +440,7 @@ namespace client void SAMSocket::ProcessNamingLookup (char * buf, size_t len) { - LogPrint ("SAM naming lookup: ", buf); + LogPrint (eLogDebug, "SAM naming lookup: ", buf); std::map params; ExtractParams (buf, params); std::string& name = params[SAM_PARAM_NAME]; @@ -519,7 +529,7 @@ namespace client void SAMSocket::Receive () { - m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE), + m_Socket.async_read_some (boost::asio::buffer(m_Buffer + m_BufferOffset, SAM_SOCKET_BUFFER_SIZE - m_BufferOffset), std::bind((m_SocketType == eSAMSocketTypeStream) ? &SAMSocket::HandleReceived : &SAMSocket::HandleMessage, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -605,6 +615,7 @@ namespace client void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { + LogPrint (eLogDebug, "SAM datagram received ", len); auto base64 = from.ToBase64 (); #ifdef _MSC_VER size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), len); @@ -618,7 +629,7 @@ namespace client std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1)); } else - LogPrint (eLogWarning, "Datagram size ", len," exceeds buffer"); + LogPrint (eLogWarning, "SAM received datagram size ", len," exceeds buffer"); } SAMSession::SAMSession (std::shared_ptr dest): diff --git a/SAM.h b/SAM.h index 70ffe4b0..092b4954 100644 --- a/SAM.h +++ b/SAM.h @@ -103,9 +103,9 @@ namespace client void ProcessSessionCreate (char * buf, size_t len); void ProcessStreamConnect (char * buf, size_t len); void ProcessStreamAccept (char * buf, size_t len); - void ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0 void ProcessDestGenerate (); void ProcessNamingLookup (char * buf, size_t len); + size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0 void ExtractParams (char * buf, std::map& params); void Connect (std::shared_ptr remote); @@ -121,6 +121,7 @@ namespace client boost::asio::ip::tcp::socket m_Socket; boost::asio::deadline_timer m_Timer; char m_Buffer[SAM_SOCKET_BUFFER_SIZE + 1]; + size_t m_BufferOffset; uint8_t m_StreamBuffer[SAM_SOCKET_BUFFER_SIZE]; SAMSocketType m_SocketType; std::string m_ID; // nickname From 8c174cd5484f28efef1bf4d40f4ee34486caf282 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Mar 2015 14:36:54 -0400 Subject: [PATCH 0344/6300] merge incomplete DATAGRAM SEND message --- SAM.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index f0fad542..bace6b5d 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -220,7 +220,8 @@ namespace client if (processed < bytes_transferred) { m_BufferOffset = bytes_transferred - processed; - memmove (m_Buffer, m_Buffer + processed, m_BufferOffset); + if (processed > 0) + memmove (m_Buffer, m_Buffer + processed, m_BufferOffset); } // since it's SAM v1 reply is not expected Receive (); @@ -420,7 +421,10 @@ namespace client LogPrint (eLogError, "SAM session is not created from DATAGRAM SEND"); } else - LogPrint (eLogError, "SAM sent datagram size ", size, " exceeds buffer"); + { + LogPrint (eLogWarning, "SAM sent datagram size ", size, " exceeds buffer"); + return 0; // try to receive more + } return offset + size; } @@ -529,6 +533,12 @@ namespace client void SAMSocket::Receive () { + if (m_BufferOffset >= SAM_SOCKET_BUFFER_SIZE) + { + LogPrint (eLogError, "Buffer is full. Terminate"); + Terminate (); + return; + } m_Socket.async_read_some (boost::asio::buffer(m_Buffer + m_BufferOffset, SAM_SOCKET_BUFFER_SIZE - m_BufferOffset), std::bind((m_SocketType == eSAMSocketTypeStream) ? &SAMSocket::HandleReceived : &SAMSocket::HandleMessage, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); From 3e59535a535073f73623ab3d83387f27336df5d1 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Mar 2015 14:58:26 -0400 Subject: [PATCH 0345/6300] try to receive remanining message --- SAM.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index bace6b5d..4495ae39 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -228,20 +228,22 @@ namespace client } else { - LogPrint ("SAM unexpected message ", m_Buffer); + LogPrint (eLogError, "SAM unexpected message ", m_Buffer); Terminate (); } } else { - LogPrint ("SAM malformed message ", m_Buffer); + LogPrint (eLogError, "SAM malformed message ", m_Buffer); Terminate (); } } else { - LogPrint ("SAM malformed message ", m_Buffer); - Terminate (); + LogPrint (eLogWarning, "SAM incomplete message ", m_Buffer); + m_BufferOffset = bytes_transferred; + // try to receive remaining message + Receive (); } } } @@ -422,7 +424,7 @@ namespace client } else { - LogPrint (eLogWarning, "SAM sent datagram size ", size, " exceeds buffer"); + LogPrint (eLogWarning, "SAM sent datagram size ", size, " exceeds buffer ", len - offset); return 0; // try to receive more } return offset + size; From 1824a9a38e2cf60cd6feca6f6b6ca1a1a1b22258 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Mar 2015 15:22:56 -0400 Subject: [PATCH 0346/6300] try to receive remanining message --- SAM.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index 4495ae39..d9057f23 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -191,7 +191,7 @@ namespace client bytes_transferred += m_BufferOffset; m_BufferOffset = 0; m_Buffer[bytes_transferred] = 0; - char * eol = strchr (m_Buffer, '\n'); + char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred); if (eol) { *eol = 0; @@ -240,7 +240,7 @@ namespace client } else { - LogPrint (eLogWarning, "SAM incomplete message ", m_Buffer); + LogPrint (eLogWarning, "SAM incomplete message ", bytes_transferred); m_BufferOffset = bytes_transferred; // try to receive remaining message Receive (); From 4c1e12921a21fc2d15284109167d0c3a2a92e869 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Mar 2015 15:29:46 -0400 Subject: [PATCH 0347/6300] restore imcomplete string back --- SAM.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SAM.cpp b/SAM.cpp index d9057f23..85d3ca98 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -222,6 +222,12 @@ namespace client m_BufferOffset = bytes_transferred - processed; if (processed > 0) memmove (m_Buffer, m_Buffer + processed, m_BufferOffset); + else + { + // restore string back + *separator = ' '; + *eol = '\n'; + } } // since it's SAM v1 reply is not expected Receive (); From e80d09aa17466c47291120a33e90c66a3abb631b Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Mar 2015 15:50:24 -0400 Subject: [PATCH 0348/6300] fixed typo --- SAM.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAM.cpp b/SAM.cpp index 85d3ca98..cc0e187c 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -411,7 +411,7 @@ namespace client std::map params; ExtractParams (buf, params); size_t size = boost::lexical_cast(params[SAM_PARAM_SIZE]), offset = data - buf; - if (offset + size < len) + if (offset + size <= len) { if (m_Session) { From 8b10fc497ddb8b6b836745684512cf28cd383b03 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Mar 2015 16:09:43 -0400 Subject: [PATCH 0349/6300] pass correct datagram size --- SAM.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index cc0e187c..212bd45f 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -216,7 +216,7 @@ namespace client ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND)) { - size_t processed = ProcessDatagramSend (separator + 1, bytes_transferred - (separator - m_Buffer) - 1, eol + 1); + size_t processed = ProcessDatagramSend (separator + 1, bytes_transferred, eol + 1); if (processed < bytes_transferred) { m_BufferOffset = bytes_transferred - processed; @@ -407,7 +407,7 @@ namespace client size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data) { - LogPrint (eLogDebug, "SAM datagram send: ", buf); + LogPrint (eLogDebug, "SAM datagram send: ", buf, " ", len); std::map params; ExtractParams (buf, params); size_t size = boost::lexical_cast(params[SAM_PARAM_SIZE]), offset = data - buf; From 5f199432f0567233b9b207982c5fc4726185e7ab Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Mar 2015 16:31:53 -0400 Subject: [PATCH 0350/6300] proper size of remaining data --- SAM.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index 212bd45f..868874eb 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -216,12 +216,13 @@ namespace client ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND)) { - size_t processed = ProcessDatagramSend (separator + 1, bytes_transferred, eol + 1); - if (processed < bytes_transferred) + size_t len = bytes_transferred - (separator - m_Buffer) - 1; + size_t processed = ProcessDatagramSend (separator + 1, len, eol + 1); + if (processed < len) { - m_BufferOffset = bytes_transferred - processed; + m_BufferOffset = len - processed; if (processed > 0) - memmove (m_Buffer, m_Buffer + processed, m_BufferOffset); + memmove (m_Buffer, separator + 1 + processed, m_BufferOffset); else { // restore string back From 577ba9b397761703e237f62d1e4ffe1a29ff1c7c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Mar 2015 20:34:31 -0400 Subject: [PATCH 0351/6300] initial filtration of bad peers --- Profiling.h | 2 ++ TunnelPool.cpp | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Profiling.h b/Profiling.h index 1f175dc7..2f9d80c5 100644 --- a/Profiling.h +++ b/Profiling.h @@ -24,6 +24,8 @@ namespace data void Save (); void Load (); + + bool IsBad () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; }; void TunnelBuildResponse (uint8_t ret); diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 82de6267..12f1a41d 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -261,8 +261,13 @@ namespace tunnel std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop) const { bool isExploratory = (m_LocalDestination == &i2p::context); // TODO: implement it better - auto hop = isExploratory ? i2p::data::netdb.GetRandomRouter (prevHop): + auto hop = isExploratory ? i2p::data::netdb.GetRandomRouter (prevHop): i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop); + if (!isExploratory && hop && hop->GetProfile ()->IsBad ()) + { + LogPrint (eLogInfo, "Selected peer for tunnel has bad profile. Selecting another"); + hop = i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop); + } if (!hop) hop = i2p::data::netdb.GetRandomRouter (); From b8acce115f38073c474bc6fcb9bd7c7c4f4a3fc8 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 28 Mar 2015 14:57:39 -0400 Subject: [PATCH 0352/6300] repeat peer test if previous was not successive --- Transports.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Transports.cpp b/Transports.cpp index 058acd27..104df4a8 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -501,6 +501,8 @@ namespace transport it++; } UpdateBandwidth (); // TODO: use separate timer(s) for it + if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test + DetectExternalIP (); m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); } From 1a440e3a83ef771a97883b53fb44394a8215dd1b Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 28 Mar 2015 16:09:34 -0400 Subject: [PATCH 0353/6300] count non-replied tunnel build requests --- Profiling.cpp | 10 +++++++++- Profiling.h | 5 ++++- Tunnel.cpp | 13 +++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Profiling.cpp b/Profiling.cpp index fb45e0af..65eb1260 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -10,7 +10,8 @@ namespace i2p namespace data { RouterProfile::RouterProfile (const IdentHash& identHash): - m_IdentHash (identHash), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0) + m_IdentHash (identHash), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), + m_NumTunnelsNonReplied (0) { } @@ -20,6 +21,7 @@ namespace data boost::property_tree::ptree participation; participation.put (PEER_PROFILE_PARTICIPATION_AGREED, m_NumTunnelsAgreed); participation.put (PEER_PROFILE_PARTICIPATION_DECLINED, m_NumTunnelsDeclined); + participation.put (PEER_PROFILE_PARTICIPATION_NON_REPLIED, m_NumTunnelsNonReplied); // fill property tree boost::property_tree::ptree pt; pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation); @@ -82,6 +84,7 @@ namespace data auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION); m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0); m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0); + m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0); } catch (std::exception& ex) { @@ -97,6 +100,11 @@ namespace data else m_NumTunnelsAgreed++; } + + void RouterProfile::TunnelNonReplied () + { + m_NumTunnelsNonReplied++; + } std::shared_ptr GetRouterProfile (const IdentHash& identHash) { diff --git a/Profiling.h b/Profiling.h index 2f9d80c5..f130e1e1 100644 --- a/Profiling.h +++ b/Profiling.h @@ -15,6 +15,7 @@ namespace data // params const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed"; const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined"; + const char PEER_PROFILE_PARTICIPATION_NON_REPLIED[] = "nonreplied"; class RouterProfile { @@ -28,13 +29,15 @@ namespace data bool IsBad () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; }; void TunnelBuildResponse (uint8_t ret); + void TunnelNonReplied (); private: IdentHash m_IdentHash; // participation uint32_t m_NumTunnelsAgreed; - uint32_t m_NumTunnelsDeclined; + uint32_t m_NumTunnelsDeclined; + uint32_t m_NumTunnelsNonReplied; }; std::shared_ptr GetRouterProfile (const IdentHash& identHash); diff --git a/Tunnel.cpp b/Tunnel.cpp index 4852c7bd..110446b4 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -489,6 +489,19 @@ namespace tunnel if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT) { LogPrint ("Pending tunnel build request ", it->first, " timeout. Deleted"); + // update stats + auto config = tunnel->GetTunnelConfig (); + if (config) + { + auto hop = config->GetFirstHop (); + while (hop) + { + if (hop->router) + hop->router->GetProfile ()->TunnelNonReplied (); + hop = hop->next; + } + } + // delete it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; } From f2078f6d5be2eb03ed41336b016b4689ce730f18 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 29 Mar 2015 21:05:12 -0400 Subject: [PATCH 0354/6300] schedule interoducers again if still testing --- SSU.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/SSU.cpp b/SSU.cpp index 457c1213..4b316e65 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -426,7 +426,14 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { // timeout expired - if (i2p::context.GetStatus () != eRouterStatusFirewalled) return; // we don't need introducers anymore + if (i2p::context.GetStatus () != eRouterStatusTesting) + { + // we still don't know if we need introducers + ScheduleIntroducersUpdateTimer (); + return; + } + if (i2p::context.GetStatus () == eRouterStatusOK) return; // we don't need introducers anymore + // we are firewalled if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (); std::list newList; size_t numIntroducers = 0; From ab7550947aeca6af54f94d310665aabc31af5227 Mon Sep 17 00:00:00 2001 From: guanqun Date: Mon, 30 Mar 2015 09:26:05 +0800 Subject: [PATCH 0355/6300] setup the correct buffer size --- AddressBook.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 609b9aa7..ff410ba5 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -465,7 +465,7 @@ namespace client auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, u.port_); stream->Send ((uint8_t *)request.str ().c_str (), request.str ().length ()); - uint8_t buf[4095]; + uint8_t buf[4096]; bool end = false; while (!end) { From 629b5ff17124c4c7812786e62b576cc1df40a402 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 Mar 2015 07:21:47 -0400 Subject: [PATCH 0356/6300] fixed typo --- SSU.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SSU.cpp b/SSU.cpp index 4b316e65..08dd7f9d 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -426,7 +426,7 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { // timeout expired - if (i2p::context.GetStatus () != eRouterStatusTesting) + if (i2p::context.GetStatus () == eRouterStatusTesting) { // we still don't know if we need introducers ScheduleIntroducersUpdateTimer (); From 12641ab0c0cc7e0c9cc6d7c8ea0c6c4649d52cdb Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 Mar 2015 10:21:52 -0400 Subject: [PATCH 0357/6300] fixed addressbook crash at shutdown --- AddressBook.cpp | 32 +++++++++++++++++++++++++++----- AddressBook.h | 7 +++++-- ClientContext.cpp | 5 ++--- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index ff410ba5..ffa6883d 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -151,13 +151,29 @@ namespace client } //--------------------------------------------------------------------- - AddressBook::AddressBook (): m_IsLoaded (false), m_IsDownloading (false), + AddressBook::AddressBook (): m_Storage (nullptr), m_IsLoaded (false), m_IsDownloading (false), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr) { } AddressBook::~AddressBook () { + Stop (); + } + + void AddressBook::Start () + { + StartSubscriptions (); + } + + void AddressBook::Stop () + { + StopSubscriptions (); + if (m_SubscriptionsUpdateTimer) + { + delete m_SubscriptionsUpdateTimer; + m_SubscriptionsUpdateTimer = nullptr; + } if (m_IsDownloading) { LogPrint (eLogInfo, "Subscription is downloading. Waiting for temination..."); @@ -171,18 +187,24 @@ namespace client std::this_thread::sleep_for (std::chrono::seconds (1)); // wait for 1 seconds } LogPrint (eLogError, "Subscription download hangs"); + m_IsDownloading = false; } if (m_Storage) { m_Storage->Save (m_Addresses); delete m_Storage; + m_Storage = nullptr; } - delete m_DefaultSubscription; + if (m_DefaultSubscription) + { + delete m_DefaultSubscription; + m_DefaultSubscription = nullptr; + } for (auto it: m_Subscriptions) delete it; - delete m_SubscriptionsUpdateTimer; - } - + m_Subscriptions.clear (); + } + AddressBookStorage * AddressBook::CreateStorage () { return new AddressBookFilesystemStorage (); diff --git a/AddressBook.h b/AddressBook.h index dc4ac54c..6fdae9b1 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -46,14 +46,14 @@ namespace client AddressBook (); ~AddressBook (); + void Start (); + void Stop (); bool GetIdentHash (const std::string& address, i2p::data::IdentHash& ident); bool GetAddress (const std::string& address, i2p::data::IdentityEx& identity); const i2p::data::IdentHash * FindAddress (const std::string& address); void InsertAddress (const std::string& address, const std::string& base64); // for jump service void InsertAddress (const i2p::data::IdentityEx& address); - void StartSubscriptions (); - void StopSubscriptions (); void LoadHostsFromStream (std::istream& f); void DownloadComplete (bool success); //This method returns the ".b32.i2p" address @@ -61,6 +61,9 @@ namespace client std::string ToAddress(const i2p::data::IdentityEx& ident) { return ToAddress(ident.GetIdentHash ()); } private: + void StartSubscriptions (); + void StopSubscriptions (); + AddressBookStorage * CreateStorage (); void LoadHosts (); void LoadSubscriptions (); diff --git a/ClientContext.cpp b/ClientContext.cpp index a1a1e395..4df5fe40 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -101,12 +101,11 @@ namespace client m_I2PControlService->Start (); LogPrint("I2PControl started"); } - m_AddressBook.StartSubscriptions (); + m_AddressBook.Start (); } void ClientContext::Stop () { - m_AddressBook.StopSubscriptions (); m_HttpProxy->Stop(); delete m_HttpProxy; m_HttpProxy = nullptr; @@ -148,7 +147,7 @@ namespace client m_I2PControlService = nullptr; LogPrint("I2PControl stopped"); } - + m_AddressBook.Stop (); for (auto it: m_Destinations) it.second->Stop (); m_Destinations.clear (); From 00ac1f7ec912b83693d442954b131d05492f37e0 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 Mar 2015 11:56:24 -0400 Subject: [PATCH 0358/6300] check # of block to encrypt/decrypt for zero --- aes.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/aes.cpp b/aes.cpp index d1c9d984..8d270521 100644 --- a/aes.cpp +++ b/aes.cpp @@ -192,7 +192,9 @@ namespace crypto void CBCEncryption::Encrypt (const uint8_t * in, std::size_t len, uint8_t * out) { // len/16 - Encrypt (len >> 4, (const ChipherBlock *)in, (ChipherBlock *)out); + int numBlocks = len >> 4; + if (numBlocks > 0) + Encrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); } void CBCEncryption::Encrypt (const uint8_t * in, uint8_t * out) @@ -252,7 +254,9 @@ namespace crypto void CBCDecryption::Decrypt (const uint8_t * in, std::size_t len, uint8_t * out) { - Decrypt (len >> 4, (const ChipherBlock *)in, (ChipherBlock *)out); + int numBlocks = len >> 4; + if (numBlocks > 0) + Decrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); } void CBCDecryption::Decrypt (const uint8_t * in, uint8_t * out) From 92bd29ebf18b988c448945bbc012e85e3630af80 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 Mar 2015 13:10:36 -0400 Subject: [PATCH 0359/6300] delete trailing paddings of SSU packtes --- SSUSession.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SSUSession.cpp b/SSUSession.cpp index edc5cb9f..1e4f0fe7 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -126,6 +126,8 @@ namespace transport void SSUSession::ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { + len -= (len & 0x0F); // %16, delete extra padding + if (len <= sizeof (SSUHeader)) return; // drop empty message //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved SSUHeader * header = (SSUHeader *)buf; switch (header->GetPayloadType ()) From d9911f4314dae17ef43258a307a6984f19e531fc Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 Mar 2015 21:05:04 -0400 Subject: [PATCH 0360/6300] save last update time --- Profiling.cpp | 14 +++++++++++++- Profiling.h | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Profiling.cpp b/Profiling.cpp index 65eb1260..19c9e2e7 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -10,10 +10,16 @@ namespace i2p namespace data { RouterProfile::RouterProfile (const IdentHash& identHash): - m_IdentHash (identHash), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), + m_IdentHash (identHash), m_LastUpdateTime (boost::posix_time::second_clock::local_time()), + m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0) { } + + void RouterProfile::UpdateTime () + { + m_LastUpdateTime = boost::posix_time::second_clock::local_time(); + } void RouterProfile::Save () { @@ -24,6 +30,7 @@ namespace data participation.put (PEER_PROFILE_PARTICIPATION_NON_REPLIED, m_NumTunnelsNonReplied); // fill property tree boost::property_tree::ptree pt; + pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime)); pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation); // save to file @@ -80,6 +87,9 @@ namespace data } try { + auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, ""); + if (t.length () > 0) + m_LastUpdateTime = boost::posix_time::time_from_string (t); // read participations auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION); m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0); @@ -99,11 +109,13 @@ namespace data m_NumTunnelsDeclined++; else m_NumTunnelsAgreed++; + UpdateTime (); } void RouterProfile::TunnelNonReplied () { m_NumTunnelsNonReplied++; + UpdateTime (); } std::shared_ptr GetRouterProfile (const IdentHash& identHash) diff --git a/Profiling.h b/Profiling.h index f130e1e1..03928925 100644 --- a/Profiling.h +++ b/Profiling.h @@ -2,6 +2,7 @@ #define PROFILING_H__ #include +#include #include "Identity.h" namespace i2p @@ -13,6 +14,7 @@ namespace data // sections const char PEER_PROFILE_SECTION_PARTICIPATION[] = "participation"; // params + const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime"; const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed"; const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined"; const char PEER_PROFILE_PARTICIPATION_NON_REPLIED[] = "nonreplied"; @@ -30,10 +32,15 @@ namespace data void TunnelBuildResponse (uint8_t ret); void TunnelNonReplied (); + + private: + + void UpdateTime (); private: IdentHash m_IdentHash; + boost::posix_time::ptime m_LastUpdateTime; // participation uint32_t m_NumTunnelsAgreed; uint32_t m_NumTunnelsDeclined; From d0ac6345c2bdde200f7e66ba3884878a2dbc9c8d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 31 Mar 2015 08:34:55 -0400 Subject: [PATCH 0361/6300] 0.9.0 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 64386003..e6fee5ff 100644 --- a/version.h +++ b/version.h @@ -2,7 +2,7 @@ #define _VERSION_H_ #define CODENAME "Purple" -#define VERSION "0.8.0" +#define VERSION "0.9.0" #define I2P_VERSION "0.9.18" #endif From de5c55160b5555654f13d386e7b5c0408da67328 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 31 Mar 2015 17:13:01 -0400 Subject: [PATCH 0362/6300] count tunnel acceptance ratio for peer selection --- Profiling.cpp | 35 +++++++++++++++++++++++++++++------ Profiling.h | 8 +++++++- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/Profiling.cpp b/Profiling.cpp index 19c9e2e7..b572f3bb 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -16,9 +16,14 @@ namespace data { } + boost::posix_time::ptime RouterProfile::GetTime () const + { + return boost::posix_time::second_clock::local_time(); + } + void RouterProfile::UpdateTime () { - m_LastUpdateTime = boost::posix_time::second_clock::local_time(); + m_LastUpdateTime = GetTime (); } void RouterProfile::Save () @@ -90,11 +95,16 @@ namespace data auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, ""); if (t.length () > 0) m_LastUpdateTime = boost::posix_time::time_from_string (t); - // read participations - auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION); - m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0); - m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0); - m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0); + if ((GetTime () - m_LastUpdateTime).hours () < 72) // profile becomes obsolete after 3 days of inactivity + { + // read participations + auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION); + m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0); + m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0); + m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0); + } + else + *this = RouterProfile (m_IdentHash); } catch (std::exception& ex) { @@ -117,6 +127,19 @@ namespace data m_NumTunnelsNonReplied++; UpdateTime (); } + + bool RouterProfile::IsLowPartcipationRate () const + { + if ((GetTime () - m_LastUpdateTime).total_seconds () < 900) // if less than 15 minutes + return m_NumTunnelsAgreed < m_NumTunnelsDeclined; // 50% rate + else + return 4*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // 20% rate + } + + bool RouterProfile::IsBad () const + { + return IsAlwaysDeclining () || IsNonResponding () || IsLowPartcipationRate (); + } std::shared_ptr GetRouterProfile (const IdentHash& identHash) { diff --git a/Profiling.h b/Profiling.h index 03928925..9ff03f4b 100644 --- a/Profiling.h +++ b/Profiling.h @@ -24,18 +24,24 @@ namespace data public: RouterProfile (const IdentHash& identHash); + RouterProfile& operator= (const RouterProfile& ) = default; void Save (); void Load (); - bool IsBad () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; }; + bool IsBad () const; void TunnelBuildResponse (uint8_t ret); void TunnelNonReplied (); private: + boost::posix_time::ptime GetTime () const; void UpdateTime (); + + bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; }; + bool IsNonResponding () const { return m_NumTunnelsNonReplied > 20 && !(m_NumTunnelsAgreed + m_NumTunnelsDeclined); }; + bool IsLowPartcipationRate () const; private: From fda68c114c1356ddb0a9a698480d19d2f6d8a782 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 1 Apr 2015 13:48:54 +0000 Subject: [PATCH 0363/6300] * README.md --- README.md | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 7de735d6..1d5d85ff 100644 --- a/README.md +++ b/README.md @@ -104,21 +104,34 @@ i2p.conf: tunnels.cfg (filename of this config is subject of change): - ; outgoing tunnel, to remote service - [tunnel1] - type = client ; mandatory - port = ; mandatory, bind our side of tunnel to this local port - keys = ; optional - destination = ; mandatory - destinationport = ; optional, port of remote i2p service - - ; incoming tunnel, for local service(s) - [tunnel2] - type = server ; mandatory - host = ; mandatory, hostname of our i2p service - keys = ; mandatory, hostname keys - port = ; mandatory, forward incoming connections from i2p to this port - inport = ; optional, i2p service port - accesslist = [,] ; optional, comma-separated list of i2p idents, allowed to connect to service + ; outgoing tunnel sample, to remote service + ; mandatory parameters: + ; * type -- always "client" + ; * port -- local port to listen to + ; * destination -- i2p hostname + ; optional parameters (may be omitted) + ; * keys -- our identity, if unset, will be generated on every startup, + ; if set and file missing, keys will be generated and placed to this file + [IRC] + type = client + port = 6668 + destination = irc.echelon.i2p + keys = irc-keys.dat -Note: '' type is a string like or + ; incoming tunnel sample, for local service + ; mandatory parameters: + ; * type -- always "server" + ; * host -- ip address of our service + ; * port -- port of our service + ; * keys -- file with LeaseSet of address in i2p + ; optional parameters (may be omitted) + ; * inport -- optional, i2p service port, if unset - the same as 'port' + ; * accesslist -- comma-separated list of i2p addresses, allowed to connect + ; every address is b32 without '.b32.i2p' part + [LOCALSITE] + type = server + host = 127.0.0.1 + port = 80 + keys = site-keys.dat + inport = 81 + accesslist = [,] From 0446a5ae7335b05537989bfb524f3739d69a0464 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Apr 2015 12:03:58 -0400 Subject: [PATCH 0364/6300] moved encryption/decryption to TlsCipher --- Reseed.cpp | 126 +++++++++++++++++++++++++++++------------------------ Reseed.h | 36 ++++++++++----- 2 files changed, 96 insertions(+), 66 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index c7347841..ad4ba10d 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -510,7 +510,7 @@ namespace data } TlsSession::TlsSession (const std::string& host, int port): - m_Seqn (0) + m_Cipher (nullptr) { m_Site.connect(host, boost::lexical_cast(port)); if (m_Site.good ()) @@ -521,6 +521,11 @@ namespace data LogPrint (eLogError, "Can't connect to ", host, ":", port); } + TlsSession::~TlsSession () + { + delete m_Cipher; + } + void TlsSession::Handshake () { static uint8_t clientHello[] = @@ -618,13 +623,14 @@ namespace data // generate secret key uint8_t secret[48]; secret[0] = 3; secret[1] = 3; // version - m_Rnd.GenerateBlock (secret + 2, 46); // 46 random bytes + CryptoPP::AutoSeededRandomPool rnd; + rnd.GenerateBlock (secret + 2, 46); // 46 random bytes // encrypt RSA CryptoPP::RSAES_PKCS1v15_Encryptor encryptor(publicKey); size_t encryptedLen = encryptor.CiphertextLength (48); // number of bytes for encrypted 48 bytes, usually 256 (2048 bits key) uint8_t * encrypted = new uint8_t[encryptedLen + 2]; // + 2 bytes for length htobe16buf (encrypted, encryptedLen); // first two bytes means length - encryptor.Encrypt (m_Rnd, secret, 48, encrypted + 2); + encryptor.Encrypt (rnd, secret, 48, encrypted + 2); // send ClientKeyExchange // 0x10 - handshake type "client key exchange" SendHandshakeMsg (0x10, encrypted, encryptedLen + 2); @@ -635,15 +641,12 @@ namespace data uint8_t masterSecret[48], random[64]; memcpy (random, clientHello + 11, 32); memcpy (random + 32, serverRandom, 32); - PRF (secret, "master secret", random, 64, 48, masterSecret); - // expand master secret - uint8_t keys[128]; // clientMACKey(32), serverMACKey(32), clientKey(32), serverKey(32) + PRF (secret, "master secret", random, 64, 48, masterSecret); + // invert random for keys memcpy (random, serverRandom, 32); - memcpy (random + 32, clientHello + 11, 32); - PRF (masterSecret, "key expansion", random, 64, 128, keys); - memcpy (m_MacKey, keys, 32); - m_Encryption.SetKey (keys + 64); - m_Decryption.SetKey (keys + 96); + memcpy (random + 32, clientHello + 11, 32); + // create cipher + m_Cipher = new TlsCipher_AES_256_CBC_SHA256 (masterSecret, random); // send finished uint8_t finishedHashDigest[32], finishedPayload[40], encryptedPayload[80]; @@ -652,10 +655,10 @@ namespace data m_FinishedHash.Final (finishedHashDigest); PRF (masterSecret, "client finished", finishedHashDigest, 32, 12, finishedPayload + 4); uint8_t mac[32]; - CalculateMAC (0x16, finishedPayload, 16, mac); - Encrypt (finishedPayload, 16, mac, encryptedPayload); + m_Cipher->CalculateMAC (0x16, finishedPayload, 16, mac); + size_t encryptedPayloadSize = m_Cipher->Encrypt (finishedPayload, 16, mac, encryptedPayload); m_Site.write ((char *)finished, sizeof (finished)); - m_Site.write ((char *)encryptedPayload, 80); + m_Site.write ((char *)encryptedPayload, encryptedPayloadSize); // read ChangeCipherSpecs uint8_t changeCipherSpecs1[6]; m_Site.read ((char *)changeCipherSpecs1, 6); @@ -709,45 +712,6 @@ namespace data } } - size_t TlsSession::Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) - { - size_t size = 0; - m_Rnd.GenerateBlock (out, 16); // iv - size += 16; - m_Encryption.SetIV (out); - memcpy (out + size, in, len); - size += len; - memcpy (out + size, mac, 32); - size += 32; - uint8_t paddingSize = len + 1; - paddingSize &= 0x0F; // %16 - if (paddingSize > 0) paddingSize = 16 - paddingSize; - memset (out + size, paddingSize, paddingSize + 1); // paddind and last byte are equal to padding size - size += paddingSize + 1; - m_Encryption.Encrypt (out + 16, size - 16, out + 16); - return size; - } - - size_t TlsSession::Decrypt (uint8_t * buf, size_t len) - { - m_Decryption.SetIV (buf); - m_Decryption.Decrypt (buf + 16, len - 16, buf + 16); - return len - 48 - buf[len -1] - 1; // IV(16), mac(32) and padding - } - - void TlsSession::CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac) - { - uint8_t header[13]; // seqn (8) + type (1) + version (2) + length (2) - htobe64buf (header, m_Seqn); - header[8] = type; header[9] = 3; header[10] = 3; // 3,3 means TLS 1.2 - htobe16buf (header + 11, len); - CryptoPP::HMAC hmac (m_MacKey, 32); - hmac.Update (header, 13); - hmac.Update (buf, len); - hmac.Final (mac); - m_Seqn++; - } - CryptoPP::RSA::PublicKey TlsSession::ExtractPublicKey (const uint8_t * certificate, size_t len) { CryptoPP::ByteQueue queue; @@ -797,8 +761,8 @@ namespace data out[0] = 0x17; // application data out[1] = 0x03; out[2] = 0x03; // version uint8_t mac[32]; - CalculateMAC (0x17, buf, len, mac); - size_t encryptedLen = Encrypt (buf, len, mac, out + 5); + m_Cipher->CalculateMAC (0x17, buf, len, mac); + size_t encryptedLen = m_Cipher->Encrypt (buf, len, mac, out + 5); htobe16buf (out + 3, encryptedLen); m_Site.write ((char *)out, encryptedLen + 5); delete[] out; @@ -814,11 +778,61 @@ namespace data length = be16toh (length); uint8_t * buf = new uint8_t[length]; m_Site.read ((char *)buf, length); - size_t decryptedLen = Decrypt (buf, length); + size_t decryptedLen = m_Cipher->Decrypt (buf, length); rs.write ((char *)buf + 16, decryptedLen); delete[] buf; return true; } + + TlsCipher_AES_256_CBC_SHA256::TlsCipher_AES_256_CBC_SHA256 (uint8_t * masterSecret, uint8_t * random): + m_Seqn (0) + { + uint8_t keys[128]; // clientMACKey(32), serverMACKey(32), clientKey(32), serverKey(32) + // expand master secret + TlsSession::PRF (masterSecret, "key expansion", random, 64, 128, keys); + memcpy (m_MacKey, keys, 32); + m_Encryption.SetKey (keys + 64); + m_Decryption.SetKey (keys + 96); + } + + void TlsCipher_AES_256_CBC_SHA256::CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac) + { + uint8_t header[13]; // seqn (8) + type (1) + version (2) + length (2) + htobe64buf (header, m_Seqn); + header[8] = type; header[9] = 3; header[10] = 3; // 3,3 means TLS 1.2 + htobe16buf (header + 11, len); + CryptoPP::HMAC hmac (m_MacKey, 32); + hmac.Update (header, 13); + hmac.Update (buf, len); + hmac.Final (mac); + m_Seqn++; + } + + size_t TlsCipher_AES_256_CBC_SHA256::Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) + { + size_t size = 0; + m_Rnd.GenerateBlock (out, 16); // iv + size += 16; + m_Encryption.SetIV (out); + memcpy (out + size, in, len); + size += len; + memcpy (out + size, mac, 32); + size += 32; + uint8_t paddingSize = len + 1; + paddingSize &= 0x0F; // %16 + if (paddingSize > 0) paddingSize = 16 - paddingSize; + memset (out + size, paddingSize, paddingSize + 1); // paddind and last byte are equal to padding size + size += paddingSize + 1; + m_Encryption.Encrypt (out + 16, size - 16, out + 16); + return size; + } + + size_t TlsCipher_AES_256_CBC_SHA256::Decrypt (uint8_t * buf, size_t len) + { + m_Decryption.SetIV (buf); + m_Decryption.Decrypt (buf + 16, len - 16, buf + 16); + return len - 48 - buf[len -1] - 1; // IV(16), mac(32) and padding + } } } diff --git a/Reseed.h b/Reseed.h index d43227a5..4b5347eb 100644 --- a/Reseed.h +++ b/Reseed.h @@ -47,34 +47,50 @@ namespace data std::map m_SigningKeys; }; + + class TlsCipher_AES_256_CBC_SHA256 + { + public: + + TlsCipher_AES_256_CBC_SHA256 (uint8_t * masterSecret, uint8_t * random); // master secret - 48 bytes, random - 64 bytes + + void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac); + size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out); + size_t Decrypt (uint8_t * buf, size_t len); // payload is buf + 16 + + private: + + uint64_t m_Seqn; + CryptoPP::AutoSeededRandomPool m_Rnd; + i2p::crypto::CBCEncryption m_Encryption; + i2p::crypto::CBCDecryption m_Decryption; + uint8_t m_MacKey[32]; // client + }; + + class TlsSession { public: TlsSession (const std::string& host, int port); + ~TlsSession (); void Send (const uint8_t * buf, size_t len); bool Receive (std::ostream& rs); + static void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, + size_t len, uint8_t * buf); + private: void Handshake (); void SendHandshakeMsg (uint8_t handshakeType, uint8_t * data, size_t len); CryptoPP::RSA::PublicKey ExtractPublicKey (const uint8_t * certificate, size_t len); - void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, - size_t len, uint8_t * buf); - void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac); - size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out); - size_t Decrypt (uint8_t * buf, size_t len); // pyaload is buf + 16 private: - uint64_t m_Seqn; boost::asio::ip::tcp::iostream m_Site; CryptoPP::SHA256 m_FinishedHash; - CryptoPP::AutoSeededRandomPool m_Rnd; - i2p::crypto::CBCEncryption m_Encryption; - i2p::crypto::CBCDecryption m_Decryption; - uint8_t m_MacKey[32]; // client + TlsCipher_AES_256_CBC_SHA256 * m_Cipher; }; } } From 4e81083bb433e22f3db4e6b9261a09ef6e534b65 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Apr 2015 12:56:41 -0400 Subject: [PATCH 0365/6300] AES256 cipher template --- Reseed.cpp | 117 ++++++++++++++++++++++++++++++----------------------- Reseed.h | 22 ++++------ 2 files changed, 73 insertions(+), 66 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index ad4ba10d..c8f9bb59 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -509,6 +509,71 @@ namespace data return i2p::util::http::GetHttpContent (rs); } +//------------------------------------------------------------- + template + class TlsCipher_AES_256_CBC: public TlsCipher + { + public: + + TlsCipher_AES_256_CBC (uint8_t * masterSecret, uint8_t * random): // master secret - 48 bytes, random - 64 bytes + m_Seqn (0) + { + uint8_t keys[128]; // clientMACKey(32 or 20), serverMACKey(32 or 20), clientKey(32), serverKey(32) + // expand master secret + TlsSession::PRF (masterSecret, "key expansion", random, 64, 128, keys); + memcpy (m_MacKey, keys, Hash::DIGESTSIZE); + m_Encryption.SetKey (keys + 2*Hash::DIGESTSIZE); + m_Decryption.SetKey (keys + 2*Hash::DIGESTSIZE + 32); + } + + void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac) + { + uint8_t header[13]; // seqn (8) + type (1) + version (2) + length (2) + htobe64buf (header, m_Seqn); + header[8] = type; header[9] = 3; header[10] = 3; // 3,3 means TLS 1.2 + htobe16buf (header + 11, len); + CryptoPP::HMAC hmac (m_MacKey, Hash::DIGESTSIZE); + hmac.Update (header, 13); + hmac.Update (buf, len); + hmac.Final (mac); + m_Seqn++; + } + + size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) + { + size_t size = 0; + m_Rnd.GenerateBlock (out, 16); // iv + size += 16; + m_Encryption.SetIV (out); + memcpy (out + size, in, len); + size += len; + memcpy (out + size, mac, Hash::DIGESTSIZE); + size += Hash::DIGESTSIZE; + uint8_t paddingSize = size + 1; + paddingSize &= 0x0F; // %16 + if (paddingSize > 0) paddingSize = 16 - paddingSize; + memset (out + size, paddingSize, paddingSize + 1); // paddind and last byte are equal to padding size + size += paddingSize + 1; + m_Encryption.Encrypt (out + 16, size - 16, out + 16); + return size; + } + + size_t Decrypt (uint8_t * buf, size_t len) // payload is buf + 16 + { + m_Decryption.SetIV (buf); + m_Decryption.Decrypt (buf + 16, len - 16, buf + 16); + return len - 16 - Hash::DIGESTSIZE - buf[len -1] - 1; // IV(16), mac(32 or 20) and padding + } + + private: + + uint64_t m_Seqn; + CryptoPP::AutoSeededRandomPool m_Rnd; + i2p::crypto::CBCEncryption m_Encryption; + i2p::crypto::CBCDecryption m_Decryption; + uint8_t m_MacKey[Hash::DIGESTSIZE]; // client + }; + TlsSession::TlsSession (const std::string& host, int port): m_Cipher (nullptr) { @@ -646,7 +711,7 @@ namespace data memcpy (random, serverRandom, 32); memcpy (random + 32, clientHello + 11, 32); // create cipher - m_Cipher = new TlsCipher_AES_256_CBC_SHA256 (masterSecret, random); + m_Cipher = new TlsCipher_AES_256_CBC (masterSecret, random); // send finished uint8_t finishedHashDigest[32], finishedPayload[40], encryptedPayload[80]; @@ -783,56 +848,6 @@ namespace data delete[] buf; return true; } - - TlsCipher_AES_256_CBC_SHA256::TlsCipher_AES_256_CBC_SHA256 (uint8_t * masterSecret, uint8_t * random): - m_Seqn (0) - { - uint8_t keys[128]; // clientMACKey(32), serverMACKey(32), clientKey(32), serverKey(32) - // expand master secret - TlsSession::PRF (masterSecret, "key expansion", random, 64, 128, keys); - memcpy (m_MacKey, keys, 32); - m_Encryption.SetKey (keys + 64); - m_Decryption.SetKey (keys + 96); - } - - void TlsCipher_AES_256_CBC_SHA256::CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac) - { - uint8_t header[13]; // seqn (8) + type (1) + version (2) + length (2) - htobe64buf (header, m_Seqn); - header[8] = type; header[9] = 3; header[10] = 3; // 3,3 means TLS 1.2 - htobe16buf (header + 11, len); - CryptoPP::HMAC hmac (m_MacKey, 32); - hmac.Update (header, 13); - hmac.Update (buf, len); - hmac.Final (mac); - m_Seqn++; - } - - size_t TlsCipher_AES_256_CBC_SHA256::Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) - { - size_t size = 0; - m_Rnd.GenerateBlock (out, 16); // iv - size += 16; - m_Encryption.SetIV (out); - memcpy (out + size, in, len); - size += len; - memcpy (out + size, mac, 32); - size += 32; - uint8_t paddingSize = len + 1; - paddingSize &= 0x0F; // %16 - if (paddingSize > 0) paddingSize = 16 - paddingSize; - memset (out + size, paddingSize, paddingSize + 1); // paddind and last byte are equal to padding size - size += paddingSize + 1; - m_Encryption.Encrypt (out + 16, size - 16, out + 16); - return size; - } - - size_t TlsCipher_AES_256_CBC_SHA256::Decrypt (uint8_t * buf, size_t len) - { - m_Decryption.SetIV (buf); - m_Decryption.Decrypt (buf + 16, len - 16, buf + 16); - return len - 48 - buf[len -1] - 1; // IV(16), mac(32) and padding - } } } diff --git a/Reseed.h b/Reseed.h index 4b5347eb..f4761acd 100644 --- a/Reseed.h +++ b/Reseed.h @@ -48,24 +48,16 @@ namespace data }; - class TlsCipher_AES_256_CBC_SHA256 + class TlsCipher { public: - TlsCipher_AES_256_CBC_SHA256 (uint8_t * masterSecret, uint8_t * random); // master secret - 48 bytes, random - 64 bytes - - void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac); - size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out); - size_t Decrypt (uint8_t * buf, size_t len); // payload is buf + 16 + virtual ~TlsCipher () {}; - private: - - uint64_t m_Seqn; - CryptoPP::AutoSeededRandomPool m_Rnd; - i2p::crypto::CBCEncryption m_Encryption; - i2p::crypto::CBCDecryption m_Decryption; - uint8_t m_MacKey[32]; // client - }; + virtual void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac) = 0; + virtual size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) = 0; + virtual size_t Decrypt (uint8_t * buf, size_t len) = 0; + }; class TlsSession @@ -90,7 +82,7 @@ namespace data boost::asio::ip::tcp::iostream m_Site; CryptoPP::SHA256 m_FinishedHash; - TlsCipher_AES_256_CBC_SHA256 * m_Cipher; + TlsCipher * m_Cipher; }; } } From e7d4f6388434a3fc253206c78c2570e0c0247483 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Apr 2015 13:55:50 -0400 Subject: [PATCH 0366/6300] use SendFinished --- Reseed.cpp | 57 +++++++++++++++++++++++++++++------------------------- Reseed.h | 2 ++ 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index c8f9bb59..995d1f2c 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -622,18 +622,6 @@ namespace data 0x00, 0x01, // length 0x01 // type }; - - static uint8_t finished[] = - { - 0x16, // handshake - 0x03, 0x03, // version (TLS 1.2) - 0x00, 0x50, // length of handshake (80 bytes) - // handshake (encrypted) - // unencrypted context - // 0x14 handshake type (finished) - // 0x00, 0x00, 0x0C length of handshake payload - // 12 bytes of verified data - }; // send ClientHello m_Site.write ((char *)clientHello, sizeof (clientHello)); @@ -703,27 +691,17 @@ namespace data // send ChangeCipherSpecs m_Site.write ((char *)changeCipherSpecs, sizeof (changeCipherSpecs)); // calculate master secret - uint8_t masterSecret[48], random[64]; + uint8_t random[64]; memcpy (random, clientHello + 11, 32); memcpy (random + 32, serverRandom, 32); - PRF (secret, "master secret", random, 64, 48, masterSecret); + PRF (secret, "master secret", random, 64, 48, m_MasterSecret); // invert random for keys memcpy (random, serverRandom, 32); memcpy (random + 32, clientHello + 11, 32); // create cipher - m_Cipher = new TlsCipher_AES_256_CBC (masterSecret, random); - + m_Cipher = new TlsCipher_AES_256_CBC (m_MasterSecret, random); // send finished - uint8_t finishedHashDigest[32], finishedPayload[40], encryptedPayload[80]; - finishedPayload[0] = 0x14; // handshake type (finished) - finishedPayload[1] = 0; finishedPayload[2] = 0; finishedPayload[3] = 0x0C; // 12 bytes - m_FinishedHash.Final (finishedHashDigest); - PRF (masterSecret, "client finished", finishedHashDigest, 32, 12, finishedPayload + 4); - uint8_t mac[32]; - m_Cipher->CalculateMAC (0x16, finishedPayload, 16, mac); - size_t encryptedPayloadSize = m_Cipher->Encrypt (finishedPayload, 16, mac, encryptedPayload); - m_Site.write ((char *)finished, sizeof (finished)); - m_Site.write ((char *)encryptedPayload, encryptedPayloadSize); + SendFinishedMsg (); // read ChangeCipherSpecs uint8_t changeCipherSpecs1[6]; m_Site.read ((char *)changeCipherSpecs1, 6); @@ -753,6 +731,33 @@ namespace data m_FinishedHash.Update (data, len); } + void TlsSession::SendFinishedMsg () + { + // 0x16 handshake + // 0x03, 0x03 version (TLS 1.2) + // 2 bytes length of handshake (80 or 64 bytes) + // handshake (encrypted) + // unencrypted context + // 0x14 handshake type (finished) + // 0x00, 0x00, 0x0C length of handshake payload + // 12 bytes of verified data + + uint8_t finishedHashDigest[32], finishedPayload[40], encryptedPayload[80]; + finishedPayload[0] = 0x14; // handshake type (finished) + finishedPayload[1] = 0; finishedPayload[2] = 0; finishedPayload[3] = 0x0C; // 12 bytes + m_FinishedHash.Final (finishedHashDigest); + PRF (m_MasterSecret, "client finished", finishedHashDigest, 32, 12, finishedPayload + 4); + uint8_t mac[32]; + m_Cipher->CalculateMAC (0x16, finishedPayload, 16, mac); + size_t encryptedPayloadSize = m_Cipher->Encrypt (finishedPayload, 16, mac, encryptedPayload); + uint8_t finished[5]; + finished[0] = 0x16; // handshake + finished[1] = 0x03; finished[2] = 0x03; // version is always TLS 1.2 (3,3) + htobe16buf (finished + 3, encryptedPayloadSize); // length of payload + m_Site.write ((char *)finished, sizeof (finished)); + m_Site.write ((char *)encryptedPayload, encryptedPayloadSize); + } + void TlsSession::PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, size_t len, uint8_t * buf) { diff --git a/Reseed.h b/Reseed.h index f4761acd..7df99f21 100644 --- a/Reseed.h +++ b/Reseed.h @@ -76,12 +76,14 @@ namespace data void Handshake (); void SendHandshakeMsg (uint8_t handshakeType, uint8_t * data, size_t len); + void SendFinishedMsg (); CryptoPP::RSA::PublicKey ExtractPublicKey (const uint8_t * certificate, size_t len); private: boost::asio::ip::tcp::iostream m_Site; CryptoPP::SHA256 m_FinishedHash; + uint8_t m_MasterSecret[64]; // actual size is 48, but must be multiple of 32 TlsCipher * m_Cipher; }; } From f47831c3395c8a7940392c3641914bf6e9da55ef Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Apr 2015 14:41:36 -0400 Subject: [PATCH 0367/6300] RSA_WITH_AES_256_CBC_SHA support --- Reseed.cpp | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 995d1f2c..7e49cc93 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -597,10 +597,10 @@ namespace data { 0x16, // handshake 0x03, 0x03, // version (TLS 1.2) - 0x00, 0x2F, // length of handshake + 0x00, 0x31, // length of handshake // handshake 0x01, // handshake type (client hello) - 0x00, 0x00, 0x2B, // length of handshake payload + 0x00, 0x00, 0x2D, // length of handshake payload // client hello 0x03, 0x03, // highest version supported (TLS 1.2) 0x45, 0xFA, 0x01, 0x19, 0x74, 0x55, 0x18, 0x36, @@ -608,8 +608,9 @@ namespace data 0xEC, 0x37, 0x11, 0x93, 0x16, 0xF4, 0x66, 0x00, 0x12, 0x67, 0xAB, 0xBA, 0xFF, 0x29, 0x13, 0x9E, // 32 random bytes 0x00, // session id length - 0x00, 0x02, // chiper suites length + 0x00, 0x04, // chiper suites length 0x00, 0x3D, // RSA_WITH_AES_256_CBC_SHA256 + 0x00, 0x35, // RSA_WITH_AES_256_CBC_SHA 0x01, // compression methods length 0x00, // no compression 0x00, 0x00 // extensions length @@ -642,7 +643,10 @@ namespace data memcpy (serverRandom, serverHello + 6, 32); else LogPrint (eLogError, "Unexpected handshake type ", (int)serverHello[0]); - delete[] serverHello; + uint8_t sessionIDLen = serverHello[38]; // 6 + 32 + char * cipherSuite = serverHello + 39 + sessionIDLen; + if (cipherSuite[1] != 0x3D && cipherSuite[1] != 0x35) + LogPrint (eLogError, "Unsupported cipher ", (int)cipherSuite[0], ",", (int)cipherSuite[1]); // read Certificate m_Site.read ((char *)&type, 1); m_Site.read ((char *)&version, 2); @@ -660,7 +664,6 @@ namespace data publicKey = ExtractPublicKey ((uint8_t *)certificate + 10, length - 10); else LogPrint (eLogError, "Unexpected handshake type ", (int)certificate[0]); - delete[] certificate; // read ServerHelloDone m_Site.read ((char *)&type, 1); m_Site.read ((char *)&version, 2); @@ -671,7 +674,6 @@ namespace data m_FinishedHash.Update ((uint8_t *)serverHelloDone, length); if (serverHelloDone[0] != 0x0E) // handshake type hello done LogPrint (eLogError, "Unexpected handshake type ", (int)serverHelloDone[0]); - delete[] serverHelloDone; // our turn now // generate secret key uint8_t secret[48]; @@ -699,7 +701,18 @@ namespace data memcpy (random, serverRandom, 32); memcpy (random + 32, clientHello + 11, 32); // create cipher - m_Cipher = new TlsCipher_AES_256_CBC (m_MasterSecret, random); + if (cipherSuite[1] == 0x3D) + { + LogPrint (eLogInfo, "Chiper suite is RSA_WITH_AES_256_CBC_SHA256"); + m_Cipher = new TlsCipher_AES_256_CBC (m_MasterSecret, random); + } + else + { + // TODO: + if (cipherSuite[1] == 0x35) + LogPrint (eLogInfo, "Chiper suite is RSA_WITH_AES_256_CBC_SHA"); + m_Cipher = new TlsCipher_AES_256_CBC (m_MasterSecret, random); + } // send finished SendFinishedMsg (); // read ChangeCipherSpecs @@ -713,6 +726,10 @@ namespace data char * finished1 = new char[length]; m_Site.read (finished1, length); delete[] finished1; + + delete[] serverHello; + delete[] certificate; + delete[] serverHelloDone; } void TlsSession::SendHandshakeMsg (uint8_t handshakeType, uint8_t * data, size_t len) From 173b35f77d0f7bd7534b5fa2ad4dd569cb01f55e Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Apr 2015 15:07:45 -0400 Subject: [PATCH 0368/6300] https://netdb.rows.io:444/ added --- Reseed.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Reseed.cpp b/Reseed.cpp index 7e49cc93..31ce3672 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -40,6 +40,7 @@ namespace data "https://netdb.i2p2.no/", // Only SU3 (v2) support "https://reseed.i2p-projekt.de/", // Only HTTPS "https://cowpuncher.drollette.com/netdb/", // Only HTTPS and SU3 (v2) support -- will move to a new location + "https://netdb.rows.io:444/" // following hosts are fine but don't support AES256 /*"https://i2p.mooo.com/netDb/", "https://link.mx24.eu/", // Only HTTPS and SU3 (v2) support @@ -494,7 +495,8 @@ namespace data std::string Reseeder::HttpsRequest (const std::string& address) { i2p::util::http::url u(address); - TlsSession session (u.host_, 443); + if (u.port_ == 80) u.port_ = 443; + TlsSession session (u.host_, u.port_); // send request std::stringstream ss; From ec68338a2015bc868d848b7e5b48320fe1078fb9 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Apr 2015 15:42:26 -0400 Subject: [PATCH 0369/6300] fixed typo --- Reseed.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 31ce3672..3b180c75 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -39,7 +39,7 @@ namespace data // "https://i2p-netdb.innovatio.no/",// Vuln to POODLE "https://netdb.i2p2.no/", // Only SU3 (v2) support "https://reseed.i2p-projekt.de/", // Only HTTPS - "https://cowpuncher.drollette.com/netdb/", // Only HTTPS and SU3 (v2) support -- will move to a new location + //"https://cowpuncher.drollette.com/netdb/", // returns error "https://netdb.rows.io:444/" // following hosts are fine but don't support AES256 /*"https://i2p.mooo.com/netDb/", @@ -713,7 +713,7 @@ namespace data // TODO: if (cipherSuite[1] == 0x35) LogPrint (eLogInfo, "Chiper suite is RSA_WITH_AES_256_CBC_SHA"); - m_Cipher = new TlsCipher_AES_256_CBC (m_MasterSecret, random); + m_Cipher = new TlsCipher_AES_256_CBC (m_MasterSecret, random); } // send finished SendFinishedMsg (); From 0a150885723243e20c226fb3d907c460b394c58c Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Apr 2015 20:23:06 -0400 Subject: [PATCH 0370/6300] RC4_SHA cipher suite --- Reseed.cpp | 100 +++++++++++++++++++++++++++++++++++++++++------------ Reseed.h | 9 ++--- 2 files changed, 82 insertions(+), 27 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 3b180c75..fbc59472 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -9,6 +9,8 @@ #include #include #include +#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1 +#include #include "I2PEndian.h" #include "Reseed.h" #include "Log.h" @@ -33,14 +35,14 @@ namespace data "http://i2p-netdb.innovatio.no/" }; - //TODO: Remember to add custom port support. Not all serves on 443 static std::vector httpsReseedHostList = { // "https://193.150.121.66/netDb/", // unstable // "https://i2p-netdb.innovatio.no/",// Vuln to POODLE "https://netdb.i2p2.no/", // Only SU3 (v2) support "https://reseed.i2p-projekt.de/", // Only HTTPS //"https://cowpuncher.drollette.com/netdb/", // returns error - "https://netdb.rows.io:444/" + "https://netdb.rows.io:444/", + "https://uk.reseed.i2p2.no:444/" // following hosts are fine but don't support AES256 /*"https://i2p.mooo.com/netDb/", "https://link.mx24.eu/", // Only HTTPS and SU3 (v2) support @@ -512,22 +514,17 @@ namespace data } //------------------------------------------------------------- + template - class TlsCipher_AES_256_CBC: public TlsCipher + class TlsCipherMAC: public TlsCipher { public: - TlsCipher_AES_256_CBC (uint8_t * masterSecret, uint8_t * random): // master secret - 48 bytes, random - 64 bytes - m_Seqn (0) + TlsCipherMAC (const uint8_t * keys): m_Seqn (0) { - uint8_t keys[128]; // clientMACKey(32 or 20), serverMACKey(32 or 20), clientKey(32), serverKey(32) - // expand master secret - TlsSession::PRF (masterSecret, "key expansion", random, 64, 128, keys); memcpy (m_MacKey, keys, Hash::DIGESTSIZE); - m_Encryption.SetKey (keys + 2*Hash::DIGESTSIZE); - m_Decryption.SetKey (keys + 2*Hash::DIGESTSIZE + 32); } - + void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac) { uint8_t header[13]; // seqn (8) + type (1) + version (2) + length (2) @@ -540,6 +537,23 @@ namespace data hmac.Final (mac); m_Seqn++; } + + private: + + uint64_t m_Seqn; + uint8_t m_MacKey[Hash::DIGESTSIZE]; // client + }; + + template + class TlsCipher_AES_256_CBC: public TlsCipherMAC + { + public: + + TlsCipher_AES_256_CBC (const uint8_t * keys): TlsCipherMAC (keys) + { + m_Encryption.SetKey (keys + 2*Hash::DIGESTSIZE); + m_Decryption.SetKey (keys + 2*Hash::DIGESTSIZE + 32); + } size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) { @@ -567,15 +581,46 @@ namespace data return len - 16 - Hash::DIGESTSIZE - buf[len -1] - 1; // IV(16), mac(32 or 20) and padding } + size_t GetIVSize () const { return 16; }; + private: - uint64_t m_Seqn; CryptoPP::AutoSeededRandomPool m_Rnd; i2p::crypto::CBCEncryption m_Encryption; i2p::crypto::CBCDecryption m_Decryption; - uint8_t m_MacKey[Hash::DIGESTSIZE]; // client }; + + class TlsCipher_RC4_SHA: public TlsCipherMAC + { + public: + + TlsCipher_RC4_SHA (const uint8_t * keys): TlsCipherMAC (keys) + { + m_Encryption.SetKey (keys + 40, 16); // 20 + 20 + m_Decryption.SetKey (keys + 56, 16); // 20 + 20 + 16 + } + + size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) + { + memcpy (out, in, len); + memcpy (out + len, mac, 20); + m_Encryption.ProcessData (out, out, len + 20); + return len + 20; + } + + size_t Decrypt (uint8_t * buf, size_t len) + { + m_Decryption.ProcessData (buf, buf, len); + return len - 20; + } + + private: + + CryptoPP::Weak1::ARC4 m_Encryption, m_Decryption; + }; + + TlsSession::TlsSession (const std::string& host, int port): m_Cipher (nullptr) { @@ -599,10 +644,10 @@ namespace data { 0x16, // handshake 0x03, 0x03, // version (TLS 1.2) - 0x00, 0x31, // length of handshake + 0x00, 0x33, // length of handshake // handshake 0x01, // handshake type (client hello) - 0x00, 0x00, 0x2D, // length of handshake payload + 0x00, 0x00, 0x2F, // length of handshake payload // client hello 0x03, 0x03, // highest version supported (TLS 1.2) 0x45, 0xFA, 0x01, 0x19, 0x74, 0x55, 0x18, 0x36, @@ -610,9 +655,10 @@ namespace data 0xEC, 0x37, 0x11, 0x93, 0x16, 0xF4, 0x66, 0x00, 0x12, 0x67, 0xAB, 0xBA, 0xFF, 0x29, 0x13, 0x9E, // 32 random bytes 0x00, // session id length - 0x00, 0x04, // chiper suites length + 0x00, 0x06, // chiper suites length 0x00, 0x3D, // RSA_WITH_AES_256_CBC_SHA256 0x00, 0x35, // RSA_WITH_AES_256_CBC_SHA + 0x00, 0x05, // RSA_WITH_RC4_128_SHA 0x01, // compression methods length 0x00, // no compression 0x00, 0x00 // extensions length @@ -647,7 +693,7 @@ namespace data LogPrint (eLogError, "Unexpected handshake type ", (int)serverHello[0]); uint8_t sessionIDLen = serverHello[38]; // 6 + 32 char * cipherSuite = serverHello + 39 + sessionIDLen; - if (cipherSuite[1] != 0x3D && cipherSuite[1] != 0x35) + if (cipherSuite[1] != 0x3D && cipherSuite[1] != 0x35 && cipherSuite[1] != 0x05) LogPrint (eLogError, "Unsupported cipher ", (int)cipherSuite[0], ",", (int)cipherSuite[1]); // read Certificate m_Site.read ((char *)&type, 1); @@ -699,21 +745,28 @@ namespace data memcpy (random, clientHello + 11, 32); memcpy (random + 32, serverRandom, 32); PRF (secret, "master secret", random, 64, 48, m_MasterSecret); - // invert random for keys + // create keys memcpy (random, serverRandom, 32); memcpy (random + 32, clientHello + 11, 32); + uint8_t keys[128]; // clientMACKey(32 or 20), serverMACKey(32 or 20), clientKey(32), serverKey(32) + PRF (m_MasterSecret, "key expansion", random, 64, 128, keys); // create cipher if (cipherSuite[1] == 0x3D) { LogPrint (eLogInfo, "Chiper suite is RSA_WITH_AES_256_CBC_SHA256"); - m_Cipher = new TlsCipher_AES_256_CBC (m_MasterSecret, random); + m_Cipher = new TlsCipher_AES_256_CBC (keys); } + else if (cipherSuite[1] == 0x35) + { + LogPrint (eLogInfo, "Chiper suite is RSA_WITH_AES_256_CBC_SHA"); + m_Cipher = new TlsCipher_AES_256_CBC (keys); + } else { // TODO: - if (cipherSuite[1] == 0x35) - LogPrint (eLogInfo, "Chiper suite is RSA_WITH_AES_256_CBC_SHA"); - m_Cipher = new TlsCipher_AES_256_CBC (m_MasterSecret, random); + if (cipherSuite[1] == 0x05) + LogPrint (eLogInfo, "Chiper suite is RSA_WITH_RC4_128_SHA"); + m_Cipher = new TlsCipher_RC4_SHA (keys); } // send finished SendFinishedMsg (); @@ -727,6 +780,7 @@ namespace data length = be16toh (length); char * finished1 = new char[length]; m_Site.read (finished1, length); + m_Cipher->Decrypt ((uint8_t *)finished1, length); // for streaming ciphers delete[] finished1; delete[] serverHello; @@ -868,7 +922,7 @@ namespace data uint8_t * buf = new uint8_t[length]; m_Site.read ((char *)buf, length); size_t decryptedLen = m_Cipher->Decrypt (buf, length); - rs.write ((char *)buf + 16, decryptedLen); + rs.write ((char *)buf + m_Cipher->GetIVSize (), decryptedLen); delete[] buf; return true; } diff --git a/Reseed.h b/Reseed.h index 7df99f21..d26561dd 100644 --- a/Reseed.h +++ b/Reseed.h @@ -57,6 +57,7 @@ namespace data virtual void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac) = 0; virtual size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) = 0; virtual size_t Decrypt (uint8_t * buf, size_t len) = 0; + virtual size_t GetIVSize () const { return 0; }; // override for AES }; @@ -68,10 +69,7 @@ namespace data ~TlsSession (); void Send (const uint8_t * buf, size_t len); bool Receive (std::ostream& rs); - - static void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, - size_t len, uint8_t * buf); - + private: void Handshake (); @@ -79,6 +77,9 @@ namespace data void SendFinishedMsg (); CryptoPP::RSA::PublicKey ExtractPublicKey (const uint8_t * certificate, size_t len); + void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, + size_t len, uint8_t * buf); + private: boost::asio::ip::tcp::iostream m_Site; From cb9f78540ac2d3e1d530e546f179dfbfeaf20e2d Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 2 Apr 2015 09:01:15 -0400 Subject: [PATCH 0371/6300] Update README.md --- README.md | 62 +++++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 1d5d85ff..aba463f0 100644 --- a/README.md +++ b/README.md @@ -104,34 +104,34 @@ i2p.conf: tunnels.cfg (filename of this config is subject of change): - ; outgoing tunnel sample, to remote service - ; mandatory parameters: - ; * type -- always "client" - ; * port -- local port to listen to - ; * destination -- i2p hostname - ; optional parameters (may be omitted) - ; * keys -- our identity, if unset, will be generated on every startup, - ; if set and file missing, keys will be generated and placed to this file - [IRC] - type = client - port = 6668 - destination = irc.echelon.i2p - keys = irc-keys.dat - - ; incoming tunnel sample, for local service - ; mandatory parameters: - ; * type -- always "server" - ; * host -- ip address of our service - ; * port -- port of our service - ; * keys -- file with LeaseSet of address in i2p - ; optional parameters (may be omitted) - ; * inport -- optional, i2p service port, if unset - the same as 'port' - ; * accesslist -- comma-separated list of i2p addresses, allowed to connect - ; every address is b32 without '.b32.i2p' part - [LOCALSITE] - type = server - host = 127.0.0.1 - port = 80 - keys = site-keys.dat - inport = 81 - accesslist = [,] + ; outgoing tunnel sample, to remote service + ; mandatory parameters: + ; * type -- always "client" + ; * port -- local port to listen to + ; * destination -- i2p hostname + ; optional parameters (may be omitted) + ; * keys -- our identity, if unset, will be generated on every startup, + ; if set and file missing, keys will be generated and placed to this file + [IRC] + type = client + port = 6668 + destination = irc.echelon.i2p + keys = irc-keys.dat + + ; incoming tunnel sample, for local service + ; mandatory parameters: + ; * type -- always "server" + ; * host -- ip address of our service + ; * port -- port of our service + ; * keys -- file with LeaseSet of address in i2p + ; optional parameters (may be omitted) + ; * inport -- optional, i2p service port, if unset - the same as 'port' + ; * accesslist -- comma-separated list of i2p addresses, allowed to connect + ; every address is b32 without '.b32.i2p' part + [LOCALSITE] + type = server + host = 127.0.0.1 + port = 80 + keys = site-keys.dat + inport = 81 + accesslist = [,] From 43f8ec46ccf9a07077deb4cbce6b20c96784c370 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 2 Apr 2015 10:27:07 -0400 Subject: [PATCH 0372/6300] fixed crash if can't connect to a reseed --- Reseed.cpp | 33 +++++++++++++++++++-------------- Reseed.h | 2 ++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index fbc59472..b02839ec 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -500,17 +500,22 @@ namespace data if (u.port_ == 80) u.port_ = 443; TlsSession session (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"; - session.Send ((uint8_t *)ss.str ().c_str (), ss.str ().length ()); + if (session.IsEstablished ()) + { + // 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"; + session.Send ((uint8_t *)ss.str ().c_str (), ss.str ().length ()); - // read response - std::stringstream rs; - while (session.Receive (rs)) - ; - return i2p::util::http::GetHttpContent (rs); + // read response + std::stringstream rs; + while (session.Receive (rs)) + ; + return i2p::util::http::GetHttpContent (rs); + } + else + return ""; } //------------------------------------------------------------- @@ -622,13 +627,11 @@ namespace data TlsSession::TlsSession (const std::string& host, int port): - m_Cipher (nullptr) + m_IsEstablished (false), m_Cipher (nullptr) { m_Site.connect(host, boost::lexical_cast(port)); if (m_Site.good ()) - { Handshake (); - } else LogPrint (eLogError, "Can't connect to ", host, ":", port); } @@ -693,7 +696,9 @@ namespace data LogPrint (eLogError, "Unexpected handshake type ", (int)serverHello[0]); uint8_t sessionIDLen = serverHello[38]; // 6 + 32 char * cipherSuite = serverHello + 39 + sessionIDLen; - if (cipherSuite[1] != 0x3D && cipherSuite[1] != 0x35 && cipherSuite[1] != 0x05) + if (cipherSuite[1] == 0x3D || cipherSuite[1] == 0x35 || cipherSuite[1] == 0x05) + m_IsEstablished = true; + else LogPrint (eLogError, "Unsupported cipher ", (int)cipherSuite[0], ",", (int)cipherSuite[1]); // read Certificate m_Site.read ((char *)&type, 1); diff --git a/Reseed.h b/Reseed.h index d26561dd..3c92d066 100644 --- a/Reseed.h +++ b/Reseed.h @@ -69,6 +69,7 @@ namespace data ~TlsSession (); void Send (const uint8_t * buf, size_t len); bool Receive (std::ostream& rs); + bool IsEstablished () const { return m_IsEstablished; }; private: @@ -82,6 +83,7 @@ namespace data private: + bool m_IsEstablished; boost::asio::ip::tcp::iostream m_Site; CryptoPP::SHA256 m_FinishedHash; uint8_t m_MasterSecret[64]; // actual size is 48, but must be multiple of 32 From 19325d552a73dd2355db1e7fe521bcf7f84cd305 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 2 Apr 2015 15:10:02 -0400 Subject: [PATCH 0373/6300] fixed race condition --- Streaming.h | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Streaming.h b/Streaming.h index d36da2aa..d1de1e41 100644 --- a/Streaming.h +++ b/Streaming.h @@ -228,20 +228,18 @@ namespace stream template void Stream::AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout) { - if (!m_ReceiveQueue.empty ()) + auto s = shared_from_this(); + m_Service.post ([=](void) { - auto s = shared_from_this(); - m_Service.post ([=](void) { s->HandleReceiveTimer ( - boost::asio::error::make_error_code (boost::asio::error::operation_aborted), - buffer, handler); }); - } - else - { - m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(timeout)); - auto s = shared_from_this(); - m_ReceiveTimer.async_wait ([=](const boost::system::error_code& ecode) - { s->HandleReceiveTimer (ecode, buffer, handler); }); - } + if (!m_ReceiveQueue.empty () || m_Status == eStreamStatusReset) + s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler); + else + { + m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(timeout)); + m_ReceiveTimer.async_wait ([=](const boost::system::error_code& ecode) + { s->HandleReceiveTimer (ecode, buffer, handler); }); + } + }); } template From 321dd252eac1029c27fee167b6cc19581130c650 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 3 Apr 2015 10:02:45 -0400 Subject: [PATCH 0374/6300] fixed crash if no routers available --- Tunnel.cpp | 20 +++++++++----------- TunnelPool.cpp | 12 +++++++++++- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 110446b4..29c494f2 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -555,14 +555,13 @@ namespace tunnel { // trying to create one more oubound tunnel auto inboundTunnel = GetNextInboundTunnel (); - if (!inboundTunnel) return; + auto router = i2p::data::netdb.GetRandomRouter (); + if (!inboundTunnel || !router) return; LogPrint ("Creating one hop outbound tunnel..."); CreateTunnel ( - new TunnelConfig (std::vector > - { - i2p::data::netdb.GetRandomRouter () - }, - inboundTunnel->GetTunnelConfig ())); + new TunnelConfig (std::vector > { router }, + inboundTunnel->GetTunnelConfig ()) + ); } } @@ -603,13 +602,12 @@ namespace tunnel if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 5) { - // trying to create one more inbound tunnel + // trying to create one more inbound tunnel + auto router = i2p::data::netdb.GetRandomRouter (); LogPrint ("Creating one hop inbound tunnel..."); CreateTunnel ( - new TunnelConfig (std::vector > - { - i2p::data::netdb.GetRandomRouter () - })); + new TunnelConfig (std::vector > { router }) + ); } } diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 12f1a41d..15606959 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -297,6 +297,11 @@ namespace tunnel for (int i = 0; i < numHops; i++) { auto hop = SelectNextHop (prevHop); + if (!hop) + { + LogPrint (eLogError, "Can't select next hop for inbound tunnel"); + return; + } prevHop = hop; hops.push_back (hop); } @@ -329,6 +334,11 @@ namespace tunnel for (int i = 0; i < m_NumOutboundHops; i++) { auto hop = SelectNextHop (prevHop); + if (!hop) + { + LogPrint (eLogError, "Can't select next hop for outbound tunnel"); + return; + } prevHop = hop; hops.push_back (hop); } @@ -338,7 +348,7 @@ namespace tunnel tunnel->SetTunnelPool (shared_from_this ()); } else - LogPrint ("Can't create outbound tunnel. No inbound tunnels found"); + LogPrint (eLogError, "Can't create outbound tunnel. No inbound tunnels found"); } void TunnelPool::RecreateOutboundTunnel (std::shared_ptr tunnel) From 89ed8c2173cce778b31cbac81d18eff71f7d227d Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 3 Apr 2015 20:34:37 -0400 Subject: [PATCH 0375/6300] set datagram receiver per port --- Datagram.cpp | 7 +++++-- Datagram.h | 7 ++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 92a01b7f..a79bb069 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -16,7 +16,7 @@ namespace datagram m_Owner (owner), m_Receiver (nullptr) { } - + void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort) { uint8_t buf[MAX_DATAGRAM_SIZE]; @@ -105,7 +105,10 @@ namespace datagram if (verified) { - if (m_Receiver != nullptr) + auto it = m_ReceiversByPorts.find (toPort); + if (it != m_ReceiversByPorts.end ()) + it->second (identity, fromPort, toPort, buf + headerLen, len -headerLen); + else if (m_Receiver != nullptr) m_Receiver (identity, fromPort, toPort, buf + headerLen, len -headerLen); else LogPrint (eLogWarning, "Receiver for datagram is not set"); diff --git a/Datagram.h b/Datagram.h index 5acd79b2..969c7033 100644 --- a/Datagram.h +++ b/Datagram.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "Identity.h" #include "LeaseSet.h" #include "I2NPProtocol.h" @@ -32,6 +33,9 @@ namespace datagram void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; void ResetReceiver () { m_Receiver = nullptr; }; + void SetReceiver (const Receiver& receiver, uint16_t port) { m_ReceiversByPorts[port] = receiver; }; + void ResetReceiver (uint16_t port) { m_ReceiversByPorts.erase (port); }; + private: void HandleLeaseSetRequestComplete (bool success, I2NPMessage * msg, i2p::data::IdentHash ident); @@ -43,7 +47,8 @@ namespace datagram private: i2p::client::ClientDestination& m_Owner; - Receiver m_Receiver; + Receiver m_Receiver; // default + std::map m_ReceiversByPorts; }; } } From 62593f60c5edda8df92b473a669bf43de1e36d11 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 4 Apr 2015 15:44:29 -0400 Subject: [PATCH 0376/6300] fixed memory leak --- HTTPServer.cpp | 23 +++++++++++------------ HTTPServer.h | 10 +++++----- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index ce336fe7..b99a748c 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -526,15 +526,14 @@ namespace util { m_Stream.reset (); m_Stream = nullptr; - // delete this }); } void HTTPConnection::Receive () { m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), - boost::bind(&HTTPConnection::HandleReceive, this, - boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); + std::bind(&HTTPConnection::HandleReceive, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); } void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) @@ -968,8 +967,8 @@ namespace util m_BufferLen = len; i2p::client::context.GetSharedLocalDestination ()->RequestDestination (destination); m_Timer.expires_from_now (boost::posix_time::seconds(HTTP_DESTINATION_REQUEST_TIMEOUT)); - m_Timer.async_wait (boost::bind (&HTTPConnection::HandleDestinationRequestTimeout, - this, boost::asio::placeholders::error, destination, port, m_Buffer, m_BufferLen)); + m_Timer.async_wait (std::bind (&HTTPConnection::HandleDestinationRequestTimeout, + shared_from_this (), std::placeholders::_1, destination, port, m_Buffer, m_BufferLen)); } } @@ -1002,8 +1001,8 @@ namespace util { if (m_Stream) m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, 8192), - boost::bind (&HTTPConnection::HandleStreamReceive, this, - boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred), + std::bind (&HTTPConnection::HandleStreamReceive, shared_from_this (), + std::placeholders::_1, std::placeholders::_2), 45); // 45 seconds timeout } @@ -1012,7 +1011,7 @@ namespace util if (!ecode) { boost::asio::async_write (*m_Socket, boost::asio::buffer (m_StreamBuffer, bytes_transferred), - boost::bind (&HTTPConnection::HandleWrite, this, boost::asio::placeholders::error)); + std::bind (&HTTPConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); } else { @@ -1033,8 +1032,7 @@ namespace util m_Reply.headers[1].value = "text/html"; boost::asio::async_write (*m_Socket, m_Reply.to_buffers(status), - boost::bind (&HTTPConnection::HandleWriteReply, this, - boost::asio::placeholders::error)); + std::bind (&HTTPConnection::HandleWriteReply, shared_from_this (), std::placeholders::_1)); } HTTPServer::HTTPServer (int port): @@ -1085,14 +1083,15 @@ namespace util { if (!ecode) { - CreateConnection(m_NewSocket); // new HTTPConnection(m_NewSocket); + CreateConnection(m_NewSocket); Accept (); } } void HTTPServer::CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket) { - new HTTPConnection (m_NewSocket); + auto conn = std::make_shared (m_NewSocket); + conn->Receive (); } } } diff --git a/HTTPServer.h b/HTTPServer.h index 4db3486b..b289cbc5 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -15,7 +15,7 @@ namespace util { const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192; const int HTTP_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds - class HTTPConnection + class HTTPConnection: public std::enable_shared_from_this { protected: @@ -48,13 +48,13 @@ namespace util HTTPConnection (boost::asio::ip::tcp::socket * socket): m_Socket (socket), m_Timer (socket->get_io_service ()), - m_Stream (nullptr), m_BufferLen (0) { Receive (); }; - virtual ~HTTPConnection() { delete m_Socket; } - + m_Stream (nullptr), m_BufferLen (0) {}; + ~HTTPConnection() { delete m_Socket; } + void Receive (); + private: void Terminate (); - void Receive (); void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void AsyncStreamReceive (); void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); From 10577cd1e520eca755c780bc9e0c4d7d16b8b46d Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 5 Apr 2015 12:54:15 -0400 Subject: [PATCH 0377/6300] select tunnel from TunnelPool rather than from LeaseSet for DeliveryStatus --- Destination.h | 1 + Garlic.cpp | 11 +++++------ Garlic.h | 3 ++- RouterContext.cpp | 5 +++++ RouterContext.h | 1 + 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Destination.h b/Destination.h index 7f3a904a..229a5943 100644 --- a/Destination.h +++ b/Destination.h @@ -88,6 +88,7 @@ namespace client // implements GarlicDestination const i2p::data::LeaseSet * GetLeaseSet (); + std::shared_ptr GetTunnelPool () const { return m_Pool; } void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); // override GarlicDestination diff --git a/Garlic.cpp b/Garlic.cpp index 87c7ae2a..c86947d1 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -297,16 +297,15 @@ namespace garlic size_t size = 0; if (m_Owner) { - auto leases = m_Owner->GetLeaseSet ()->GetNonExpiredLeases (); - if (!leases.empty ()) + auto inboundTunnel = m_Owner->GetTunnelPool ()->GetNextInboundTunnel (); + if (inboundTunnel) { buf[size] = eGarlicDeliveryTypeTunnel << 5; // delivery instructions flag tunnel size++; - uint32_t i = m_Rnd.GenerateWord32 (0, leases.size () - 1); // hash and tunnelID sequence is reversed for Garlic - memcpy (buf + size, leases[i].tunnelGateway, 32); // To Hash + memcpy (buf + size, inboundTunnel->GetNextIdentHash (), 32); // To Hash size += 32; - htobe32buf (buf + size, leases[i].tunnelID); // tunnelID + htobe32buf (buf + size, inboundTunnel->GetNextTunnelID ()); // tunnelID size += 4; // create msg I2NPMessage * msg = CreateDeliveryStatusMsg (msgID); @@ -333,7 +332,7 @@ namespace garlic size += 3; } else - LogPrint ("All tunnels of local LeaseSet expired"); + LogPrint (eLogError, "No inbound tunnels in the pool for DeliveryStatus"); } else LogPrint ("Missing local LeaseSet"); diff --git a/Garlic.h b/Garlic.h index 4b02d987..bb60a776 100644 --- a/Garlic.h +++ b/Garlic.h @@ -110,7 +110,7 @@ namespace garlic i2p::crypto::CBCEncryption m_Encryption; CryptoPP::AutoSeededRandomPool m_Rnd; }; - + class GarlicDestination: public i2p::data::LocalDestination { public: @@ -133,6 +133,7 @@ namespace garlic virtual void SetLeaseSetUpdated (); virtual const i2p::data::LeaseSet * GetLeaseSet () = 0; // TODO + virtual std::shared_ptr GetTunnelPool () const = 0; virtual void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) = 0; protected: diff --git a/RouterContext.cpp b/RouterContext.cpp index e3cd358d..d2659016 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -289,6 +289,11 @@ namespace i2p fk.write ((char *)&keys, sizeof (keys)); } + std::shared_ptr RouterContext::GetTunnelPool () const + { + return i2p::tunnel::tunnels.GetExploratoryPool (); + } + void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); diff --git a/RouterContext.h b/RouterContext.h index 7492a4e2..81ef4867 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -73,6 +73,7 @@ namespace i2p // implements GarlicDestination const i2p::data::LeaseSet * GetLeaseSet () { return nullptr; }; + std::shared_ptr GetTunnelPool () const; void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); private: From 250af7f24748344762b88a2ebf97502e819af5f1 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 5 Apr 2015 13:56:41 -0400 Subject: [PATCH 0378/6300] fixed race condition --- NetDb.h | 2 +- Streaming.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NetDb.h b/NetDb.h index c912049b..7b515e2a 100644 --- a/NetDb.h +++ b/NetDb.h @@ -90,7 +90,7 @@ namespace data void Reseed (); - // for web interface and stats + // for web interface int GetNumRouters () const { return m_RouterInfos.size (); }; int GetNumFloodfills () const { return m_Floodfills.size (); }; int GetNumLeaseSets () const { return m_LeaseSets.size (); }; diff --git a/Streaming.h b/Streaming.h index d1de1e41..ff1527f9 100644 --- a/Streaming.h +++ b/Streaming.h @@ -235,8 +235,8 @@ namespace stream s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler); else { - m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(timeout)); - m_ReceiveTimer.async_wait ([=](const boost::system::error_code& ecode) + s->m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(timeout)); + s->m_ReceiveTimer.async_wait ([=](const boost::system::error_code& ecode) { s->HandleReceiveTimer (ecode, buffer, handler); }); } }); From be301dc0903b5f8381d5373f04ceb522aa8568f4 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 5 Apr 2015 20:07:32 -0400 Subject: [PATCH 0379/6300] 4 tags for LeaseSet request --- Garlic.cpp | 23 ++++++++--------------- Garlic.h | 13 +++++++++---- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index c86947d1..113aaa8d 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -15,9 +15,9 @@ namespace i2p namespace garlic { GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner, - std::shared_ptr destination, int numTags): + std::shared_ptr destination, int numTags, bool attachLeaseSet): m_Owner (owner), m_Destination (destination), m_NumTags (numTags), - m_LeaseSetUpdateStatus (numTags > 0 ? eLeaseSetUpdated : eLeaseSetUpToDate) + m_LeaseSetUpdateStatus (attachLeaseSet ? eLeaseSetUpdated : eLeaseSetDoNotSend) { // create new session tags and session key m_Rnd.GenerateBlock (m_SessionKey, 32); @@ -25,7 +25,7 @@ namespace garlic } GarlicRoutingSession::GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag): - m_Owner (nullptr), m_Destination (nullptr), m_NumTags (1), m_LeaseSetUpdateStatus (eLeaseSetUpToDate) + m_Owner (nullptr), m_Destination (nullptr), m_NumTags (1), m_LeaseSetUpdateStatus (eLeaseSetDoNotSend) { memcpy (m_SessionKey, sessionKey, 32); m_Encryption.SetKey (m_SessionKey); @@ -524,20 +524,12 @@ namespace garlic I2NPMessage * GarlicDestination::WrapMessage (std::shared_ptr destination, I2NPMessage * msg, bool attachLeaseSet) { - if (attachLeaseSet) // we should maintain this session - { - auto session = GetRoutingSession (destination, 32); // 32 tags by default - return session->WrapSingleMessage (msg); - } - else // one time session - { - GarlicRoutingSession session (this, destination, 0); // don't use tag if no LeaseSet - return session.WrapSingleMessage (msg); - } + auto session = GetRoutingSession (destination, attachLeaseSet); // 32 tags by default + return session->WrapSingleMessage (msg); } std::shared_ptr GarlicDestination::GetRoutingSession ( - std::shared_ptr destination, int numTags) + std::shared_ptr destination, bool attachLeaseSet) { auto it = m_Sessions.find (destination->GetIdentHash ()); std::shared_ptr session; @@ -545,7 +537,8 @@ namespace garlic session = it->second; if (!session) { - session = std::make_shared (this, destination, numTags); + session = std::make_shared (this, destination, + attachLeaseSet ? 40 : 4, attachLeaseSet); // 40 tags for connections and 4 for LS requests std::unique_lock l(m_SessionsMutex); m_Sessions[destination->GetIdentHash ()] = session; } diff --git a/Garlic.h b/Garlic.h index bb60a776..6f96d4e7 100644 --- a/Garlic.h +++ b/Garlic.h @@ -61,7 +61,8 @@ namespace garlic { eLeaseSetUpToDate = 0, eLeaseSetUpdated, - eLeaseSetSubmitted + eLeaseSetSubmitted, + eLeaseSetDoNotSend }; struct UnconfirmedTags @@ -75,14 +76,18 @@ namespace garlic public: - GarlicRoutingSession (GarlicDestination * owner, std::shared_ptr destination, int numTags); + GarlicRoutingSession (GarlicDestination * owner, std::shared_ptr destination, + int numTags, bool attachLeaseSet); GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption ~GarlicRoutingSession (); I2NPMessage * WrapSingleMessage (I2NPMessage * msg); void MessageConfirmed (uint32_t msgID); bool CleanupExpiredTags (); // returns true if something left - void SetLeaseSetUpdated () { m_LeaseSetUpdateStatus = eLeaseSetUpdated; }; + void SetLeaseSetUpdated () + { + if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated; + }; private: @@ -118,7 +123,7 @@ namespace garlic GarlicDestination (): m_LastTagsCleanupTime (0) {}; ~GarlicDestination (); - std::shared_ptr GetRoutingSession (std::shared_ptr destination, int numTags); + std::shared_ptr GetRoutingSession (std::shared_ptr destination, bool attachLeaseSet); void CleanupRoutingSessions (); void RemoveCreatedSession (uint32_t msgID); I2NPMessage * WrapMessage (std::shared_ptr destination, From fbe4e64e444d95a1a800d36b588d4767a7d98e65 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 6 Apr 2015 12:22:13 -0400 Subject: [PATCH 0380/6300] lookup always takes full address from LeaseSet --- BOB.cpp | 30 ++++++++++++++++++++++++++---- BOB.h | 1 + 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index edf6995e..3ff5358d 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -493,13 +493,35 @@ namespace client void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: lookup ", operand); - i2p::data::IdentityEx addr; - if (!context.GetAddressBook ().GetAddress (operand, addr)) + i2p::data::IdentHash ident; + if (!context.GetAddressBook ().GetIdentHash (operand, ident) && !m_CurrentDestination) { SendReplyError ("Address Not found"); return; - } - SendReplyOK (addr.ToBase64 ().c_str ()); + } + auto localDestination = m_CurrentDestination->GetLocalDestination (); + auto leaseSet = localDestination->FindLeaseSet (ident); + if (leaseSet) + SendReplyOK (leaseSet->GetIdentity ().ToBase64 ().c_str ()); + else + { + auto s = shared_from_this (); + m_CurrentDestination->GetLocalDestination ()->RequestDestination (ident, + [s, ident, localDestination](bool success) + { + if (success) + { + auto leaseSet = localDestination->FindLeaseSet (ident); + if (leaseSet) + s->SendReplyOK (leaseSet->GetIdentity ().ToBase64 ().c_str ()); + else + s->SendReplyError ("Missing LeaseSet"); + } + else + s->SendReplyError ("LeaseSet Not found"); + } + ); + } } void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len) diff --git a/BOB.h b/BOB.h index 5bd4c24a..23a56875 100644 --- a/BOB.h +++ b/BOB.h @@ -127,6 +127,7 @@ namespace client void CreateInboundTunnel (int port); void CreateOutboundTunnel (const std::string& address, int port, bool quiet); const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); }; + std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; private: From e72eb35cc210fbcfc12cc42c4ee16c0287f48f6a Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 6 Apr 2015 14:41:07 -0400 Subject: [PATCH 0381/6300] use shared_ptr for socket in I2PTunnelConnection --- BOB.cpp | 11 ++--------- BOB.h | 2 +- HTTPProxy.cpp | 10 +++++----- HTTPProxy.h | 2 +- I2PService.cpp | 13 ++++++------- I2PService.h | 4 ++-- I2PTunnel.cpp | 15 +++++++-------- I2PTunnel.h | 10 +++++----- SOCKS.cpp | 7 +++---- SOCKS.h | 2 +- 10 files changed, 33 insertions(+), 43 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index 3ff5358d..89ff6ce5 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -34,7 +34,7 @@ namespace client void BOBI2PInboundTunnel::Accept () { auto receiver = new AddressReceiver (); - receiver->socket = new boost::asio::ip::tcp::socket (GetService ()); + receiver->socket = std::make_shared (GetService ()); m_Acceptor.async_accept (*receiver->socket, std::bind (&BOBI2PInboundTunnel::HandleAccept, this, std::placeholders::_1, receiver)); } @@ -47,10 +47,7 @@ namespace client ReceiveAddress (receiver); } else - { - delete receiver->socket; delete receiver; - } } void BOBI2PInboundTunnel::ReceiveAddress (AddressReceiver * receiver) @@ -68,7 +65,6 @@ namespace client if (ecode) { LogPrint ("BOB inbound tunnel read error: ", ecode.message ()); - delete receiver->socket; delete receiver; } else @@ -86,7 +82,6 @@ namespace client if (!context.GetAddressBook ().GetIdentHash (receiver->buffer, ident)) { LogPrint (eLogError, "BOB address ", receiver->buffer, " not found"); - delete receiver->socket; delete receiver; return; } @@ -105,7 +100,6 @@ namespace client else { LogPrint ("BOB missing inbound address "); - delete receiver->socket; delete receiver; } } @@ -125,7 +119,6 @@ namespace client else LogPrint ("LeaseSet for BOB inbound destination not found"); } - delete receiver->socket; delete receiver; } @@ -167,7 +160,7 @@ namespace client { if (stream) { - auto conn = std::make_shared (this, stream, new boost::asio::ip::tcp::socket (GetService ()), m_Endpoint, m_IsQuiet); + auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), m_Endpoint, m_IsQuiet); AddHandler (conn); conn->Connect (); } diff --git a/BOB.h b/BOB.h index 23a56875..4b0f09fa 100644 --- a/BOB.h +++ b/BOB.h @@ -57,7 +57,7 @@ namespace client { struct AddressReceiver { - boost::asio::ip::tcp::socket * socket; + std::shared_ptr socket; char buffer[BOB_COMMAND_BUFFER_SIZE + 1]; // for destination base64 address uint8_t * data; size_t dataLen, bufferOffset; diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 8c7fb299..a575cc41 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -44,7 +44,7 @@ namespace proxy void HandleStreamRequestComplete (std::shared_ptr stream); uint8_t m_http_buff[http_buffer_size]; - boost::asio::ip::tcp::socket * m_sock; + std::shared_ptr m_sock; std::string m_request; //Data left to be sent std::string m_url; //URL std::string m_method; //Method @@ -56,7 +56,7 @@ namespace proxy public: - HTTPProxyHandler(HTTPProxyServer * parent, boost::asio::ip::tcp::socket * sock) : + HTTPProxyHandler(HTTPProxyServer * parent, std::shared_ptr sock) : I2PServiceHandler(parent), m_sock(sock) { EnterState(GET_METHOD); } ~HTTPProxyHandler() { Terminate(); } @@ -77,10 +77,10 @@ namespace proxy void HTTPProxyHandler::Terminate() { if (Kill()) return; - if (m_sock) { + if (m_sock) + { LogPrint(eLogDebug,"--- HTTP Proxy close sock"); m_sock->close(); - delete m_sock; m_sock = nullptr; } Done(shared_from_this()); @@ -290,7 +290,7 @@ namespace proxy { } - std::shared_ptr HTTPProxyServer::CreateHandler(boost::asio::ip::tcp::socket * socket) + std::shared_ptr HTTPProxyServer::CreateHandler(std::shared_ptr socket) { return std::make_shared (this, socket); } diff --git a/HTTPProxy.h b/HTTPProxy.h index e1e6e2fa..6c0d0a6f 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -21,7 +21,7 @@ namespace proxy protected: // Implements TCPIPAcceptor - std::shared_ptr CreateHandler(boost::asio::ip::tcp::socket * socket); + std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return "HTTP Proxy"; } }; diff --git a/I2PService.cpp b/I2PService.cpp index 6b74fe1a..0715a39f 100644 --- a/I2PService.cpp +++ b/I2PService.cpp @@ -48,31 +48,30 @@ namespace client void TCPIPAcceptor::Accept () { - auto newSocket = new boost::asio::ip::tcp::socket (GetService ()); + auto newSocket = std::make_shared (GetService ()); m_Acceptor.async_accept (*newSocket, std::bind (&TCPIPAcceptor::HandleAccept, this, std::placeholders::_1, newSocket)); } - void TCPIPAcceptor::HandleAccept (const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket) + void TCPIPAcceptor::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr socket) { if (!ecode) { LogPrint(eLogDebug,"--- ",GetName()," accepted"); auto handler = CreateHandler(socket); - if (handler) { + if (handler) + { AddHandler(handler); handler->Handle(); - } else { + } + else socket->close(); - delete socket; - } Accept(); } else { if (ecode != boost::asio::error::operation_aborted) LogPrint (eLogError,"--- ",GetName()," Closing socket on accept because: ", ecode.message ()); - delete socket; } } diff --git a/I2PService.h b/I2PService.h index 1e4fc10b..37c06828 100644 --- a/I2PService.h +++ b/I2PService.h @@ -95,11 +95,11 @@ namespace client //If you override this make sure you call it from the children void Stop (); protected: - virtual std::shared_ptr CreateHandler(boost::asio::ip::tcp::socket * socket) = 0; + virtual std::shared_ptr CreateHandler(std::shared_ptr socket) = 0; virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; } private: void Accept(); - void HandleAccept(const boost::system::error_code& ecode, boost::asio::ip::tcp::socket * socket); + void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::deadline_timer m_Timer; }; diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 51392819..7a2e181e 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -9,7 +9,7 @@ namespace i2p { namespace client { - I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, boost::asio::ip::tcp::socket * socket, + I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr leaseSet, int port): I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) @@ -18,14 +18,14 @@ namespace client } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, - boost::asio::ip::tcp::socket * socket, std::shared_ptr stream): + std::shared_ptr socket, std::shared_ptr stream): I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, - boost::asio::ip::tcp::socket * socket, const boost::asio::ip::tcp::endpoint& target, bool quiet): + std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, bool quiet): I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), m_RemoteEndpoint (target), m_IsQuiet (quiet) { @@ -155,7 +155,7 @@ namespace client { public: I2PClientTunnelHandler (I2PClientTunnel * parent, i2p::data::IdentHash destination, - int destinationPort, boost::asio::ip::tcp::socket * socket): + int destinationPort, std::shared_ptr socket): I2PServiceHandler(parent), m_DestinationIdentHash(destination), m_DestinationPort (destinationPort), m_Socket(socket) {}; void Handle(); @@ -164,7 +164,7 @@ namespace client void HandleStreamRequestComplete (std::shared_ptr stream); i2p::data::IdentHash m_DestinationIdentHash; int m_DestinationPort; - boost::asio::ip::tcp::socket * m_Socket; + std::shared_ptr m_Socket; }; void I2PClientTunnelHandler::Handle() @@ -198,7 +198,6 @@ namespace client if (m_Socket) { m_Socket->close(); - delete m_Socket; m_Socket = nullptr; } Done(shared_from_this()); @@ -236,7 +235,7 @@ namespace client return m_DestinationIdentHash; } - std::shared_ptr I2PClientTunnel::CreateHandler(boost::asio::ip::tcp::socket * socket) + std::shared_ptr I2PClientTunnel::CreateHandler(std::shared_ptr socket) { const i2p::data::IdentHash *identHash = GetIdentHash(); if (identHash) @@ -296,7 +295,7 @@ namespace client return; } } - auto conn = std::make_shared (this, stream, new boost::asio::ip::tcp::socket (GetService ()), m_Endpoint); + auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), m_Endpoint); AddHandler (conn); conn->Connect (); } diff --git a/I2PTunnel.h b/I2PTunnel.h index 39136587..e512a319 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -24,11 +24,11 @@ namespace client { public: - I2PTunnelConnection (I2PService * owner, boost::asio::ip::tcp::socket * socket, + I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr leaseSet, int port = 0); // to I2P - I2PTunnelConnection (I2PService * owner, boost::asio::ip::tcp::socket * socket, + I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream); // to I2P using simplified API :) - I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, boost::asio::ip::tcp::socket * socket, + I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, bool quiet = true); // from I2P ~I2PTunnelConnection (); void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0); @@ -49,7 +49,7 @@ namespace client private: uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; - std::unique_ptr m_Socket; + std::shared_ptr m_Socket; std::shared_ptr m_Stream; boost::asio::ip::tcp::endpoint m_RemoteEndpoint; bool m_IsQuiet; // don't send destination @@ -60,7 +60,7 @@ namespace client protected: // Implements TCPIPAcceptor - std::shared_ptr CreateHandler(boost::asio::ip::tcp::socket * socket); + std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return "I2P Client Tunnel"; } public: diff --git a/SOCKS.cpp b/SOCKS.cpp index 351853a6..8cf85bbd 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -118,7 +118,7 @@ namespace proxy void HandleStreamRequestComplete (std::shared_ptr stream); uint8_t m_sock_buff[socks_buffer_size]; - boost::asio::ip::tcp::socket * m_sock; + std::shared_ptr m_sock; std::shared_ptr m_stream; uint8_t *m_remaining_data; //Data left to be sent uint8_t m_response[7+max_socks_hostname_size]; @@ -135,7 +135,7 @@ namespace proxy state m_state; public: - SOCKSHandler(SOCKSServer * parent, boost::asio::ip::tcp::socket * sock) : + SOCKSHandler(SOCKSServer * parent, std::shared_ptr sock) : I2PServiceHandler(parent), m_sock(sock), m_stream(nullptr), m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4) { m_address.ip = 0; EnterState(GET_SOCKSV); } @@ -161,7 +161,6 @@ namespace proxy { LogPrint(eLogDebug,"--- SOCKS close sock"); m_sock->close(); - delete m_sock; m_sock = nullptr; } if (m_stream) @@ -566,7 +565,7 @@ namespace proxy { } - std::shared_ptr SOCKSServer::CreateHandler(boost::asio::ip::tcp::socket * socket) + std::shared_ptr SOCKSServer::CreateHandler(std::shared_ptr socket) { return std::make_shared (this, socket); } diff --git a/SOCKS.h b/SOCKS.h index deb3a58c..7854ce3a 100644 --- a/SOCKS.h +++ b/SOCKS.h @@ -20,7 +20,7 @@ namespace proxy protected: // Implements TCPIPAcceptor - std::shared_ptr CreateHandler(boost::asio::ip::tcp::socket * socket); + std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return "SOCKS"; } }; From bc21f5955fe9cbe5b18c90cec114e501269150d0 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 6 Apr 2015 15:02:37 -0400 Subject: [PATCH 0382/6300] use shared_ptr for AddressReceiver --- BOB.cpp | 26 ++++++-------------------- BOB.h | 12 ++++++------ 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index 89ff6ce5..dcd74f3b 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -33,24 +33,22 @@ namespace client void BOBI2PInboundTunnel::Accept () { - auto receiver = new AddressReceiver (); + auto receiver = std::make_shared (); receiver->socket = std::make_shared (GetService ()); m_Acceptor.async_accept (*receiver->socket, std::bind (&BOBI2PInboundTunnel::HandleAccept, this, std::placeholders::_1, receiver)); } - void BOBI2PInboundTunnel::HandleAccept (const boost::system::error_code& ecode, AddressReceiver * receiver) + void BOBI2PInboundTunnel::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr receiver) { if (!ecode) { Accept (); ReceiveAddress (receiver); } - else - delete receiver; } - void BOBI2PInboundTunnel::ReceiveAddress (AddressReceiver * receiver) + void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr receiver) { receiver->socket->async_read_some (boost::asio::buffer( receiver->buffer + receiver->bufferOffset, @@ -60,13 +58,10 @@ namespace client } void BOBI2PInboundTunnel::HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred, - AddressReceiver * receiver) + std::shared_ptr receiver) { if (ecode) - { LogPrint ("BOB inbound tunnel read error: ", ecode.message ()); - delete receiver; - } else { receiver->bufferOffset += bytes_transferred; @@ -82,7 +77,6 @@ namespace client if (!context.GetAddressBook ().GetIdentHash (receiver->buffer, ident)) { LogPrint (eLogError, "BOB address ", receiver->buffer, " not found"); - delete receiver; return; } auto leaseSet = GetLocalDestination ()->FindLeaseSet (ident); @@ -98,37 +92,29 @@ namespace client if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE) ReceiveAddress (receiver); else - { LogPrint ("BOB missing inbound address "); - delete receiver; - } } } } - void BOBI2PInboundTunnel::HandleDestinationRequestComplete (bool success, AddressReceiver * receiver, i2p::data::IdentHash ident) + void BOBI2PInboundTunnel::HandleDestinationRequestComplete (bool success, std::shared_ptr receiver, i2p::data::IdentHash ident) { if (success) { auto leaseSet = GetLocalDestination ()->FindLeaseSet (ident); if (leaseSet) - { CreateConnection (receiver, leaseSet); - return; - } else LogPrint ("LeaseSet for BOB inbound destination not found"); } - delete receiver; } - void BOBI2PInboundTunnel::CreateConnection (AddressReceiver * receiver, std::shared_ptr leaseSet) + void BOBI2PInboundTunnel::CreateConnection (std::shared_ptr receiver, std::shared_ptr leaseSet) { LogPrint ("New BOB inbound connection"); auto connection = std::make_shared(this, receiver->socket, leaseSet); AddHandler (connection); connection->I2PConnect (receiver->data, receiver->dataLen); - delete receiver; } BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& address, int port, diff --git a/BOB.h b/BOB.h index 4b0f09fa..58633714 100644 --- a/BOB.h +++ b/BOB.h @@ -59,7 +59,7 @@ namespace client { std::shared_ptr socket; char buffer[BOB_COMMAND_BUFFER_SIZE + 1]; // for destination base64 address - uint8_t * data; + uint8_t * data; // pointer to buffer size_t dataLen, bufferOffset; AddressReceiver (): data (nullptr), dataLen (0), bufferOffset (0) {}; @@ -76,15 +76,15 @@ namespace client private: void Accept (); - void HandleAccept (const boost::system::error_code& ecode, AddressReceiver * receiver); + void HandleAccept (const boost::system::error_code& ecode, std::shared_ptr receiver); - void ReceiveAddress (AddressReceiver * receiver); + void ReceiveAddress (std::shared_ptr receiver); void HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred, - AddressReceiver * receiver); + std::shared_ptr receiver); - void HandleDestinationRequestComplete (bool success, AddressReceiver * receiver, i2p::data::IdentHash ident); + void HandleDestinationRequestComplete (bool success, std::shared_ptr receiver, i2p::data::IdentHash ident); - void CreateConnection (AddressReceiver * receiver, std::shared_ptr leaseSet); + void CreateConnection (std::shared_ptr receiver, std::shared_ptr leaseSet); private: From 634976cdde359ae5168fed24908e4d4260d55654 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 7 Apr 2015 12:02:25 -0400 Subject: [PATCH 0383/6300] pass LeaseSet to callback of RequestDestination --- AddressBook.cpp | 7 ++----- BOB.cpp | 28 +++++++++------------------- BOB.h | 2 +- Datagram.cpp | 19 ++++++------------- Datagram.h | 2 +- Destination.cpp | 29 +++++++++++++---------------- Destination.h | 3 ++- SAM.cpp | 12 +++--------- SAM.h | 4 ++-- 9 files changed, 39 insertions(+), 67 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index ffa6883d..eba7f6b7 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -460,18 +460,15 @@ namespace client auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (ident); if (!leaseSet) { - bool found = false; std::unique_lock l(newDataReceivedMutex); i2p::client::context.GetSharedLocalDestination ()->RequestDestination (ident, - [&newDataReceived, &found](bool success) + [&newDataReceived, &leaseSet](std::shared_ptr ls) { - found = success; + leaseSet = ls; newDataReceived.notify_all (); }); if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) LogPrint (eLogError, "Subscription LeseseSet request timeout expired"); - if (found) - leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (ident); } if (leaseSet) { diff --git a/BOB.cpp b/BOB.cpp index dcd74f3b..86f652f8 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -85,7 +85,7 @@ namespace client else GetLocalDestination ()->RequestDestination (ident, std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete, - this, std::placeholders::_1, receiver, ident)); + this, std::placeholders::_1, receiver)); } else { @@ -97,16 +97,12 @@ namespace client } } - void BOBI2PInboundTunnel::HandleDestinationRequestComplete (bool success, std::shared_ptr receiver, i2p::data::IdentHash ident) + void BOBI2PInboundTunnel::HandleDestinationRequestComplete (std::shared_ptr leaseSet, std::shared_ptr receiver) { - if (success) - { - auto leaseSet = GetLocalDestination ()->FindLeaseSet (ident); - if (leaseSet) - CreateConnection (receiver, leaseSet); - else - LogPrint ("LeaseSet for BOB inbound destination not found"); - } + if (leaseSet) + CreateConnection (receiver, leaseSet); + else + LogPrint ("LeaseSet for BOB inbound destination not found"); } void BOBI2PInboundTunnel::CreateConnection (std::shared_ptr receiver, std::shared_ptr leaseSet) @@ -486,16 +482,10 @@ namespace client { auto s = shared_from_this (); m_CurrentDestination->GetLocalDestination ()->RequestDestination (ident, - [s, ident, localDestination](bool success) + [s, localDestination](std::shared_ptr ls) { - if (success) - { - auto leaseSet = localDestination->FindLeaseSet (ident); - if (leaseSet) - s->SendReplyOK (leaseSet->GetIdentity ().ToBase64 ().c_str ()); - else - s->SendReplyError ("Missing LeaseSet"); - } + if (ls) + s->SendReplyOK (ls->GetIdentity ().ToBase64 ().c_str ()); else s->SendReplyError ("LeaseSet Not found"); } diff --git a/BOB.h b/BOB.h index 58633714..ddc64707 100644 --- a/BOB.h +++ b/BOB.h @@ -82,7 +82,7 @@ namespace client void HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr receiver); - void HandleDestinationRequestComplete (bool success, std::shared_ptr receiver, i2p::data::IdentHash ident); + void HandleDestinationRequestComplete (std::shared_ptr leaseSet, std::shared_ptr receiver); void CreateConnection (std::shared_ptr receiver, std::shared_ptr leaseSet); diff --git a/Datagram.cpp b/Datagram.cpp index a79bb069..adb76d58 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -41,22 +41,15 @@ namespace datagram if (remote) m_Owner.GetService ().post (std::bind (&DatagramDestination::SendMsg, this, msg, remote)); else - m_Owner.RequestDestination (ident, std::bind (&DatagramDestination::HandleLeaseSetRequestComplete, - this, std::placeholders::_1, msg, ident)); + m_Owner.RequestDestination (ident, std::bind (&DatagramDestination::HandleLeaseSetRequestComplete, this, std::placeholders::_1, msg)); } - void DatagramDestination::HandleLeaseSetRequestComplete (bool success, I2NPMessage * msg, i2p::data::IdentHash ident) + void DatagramDestination::HandleLeaseSetRequestComplete (std::shared_ptr remote, I2NPMessage * msg) { - if (success) - { - auto remote = m_Owner.FindLeaseSet (ident); - if (remote) - { - SendMsg (msg, remote); - return; - } - } - DeleteI2NPMessage (msg); + if (remote) + SendMsg (msg, remote); + else + DeleteI2NPMessage (msg); } void DatagramDestination::SendMsg (I2NPMessage * msg, std::shared_ptr remote) diff --git a/Datagram.h b/Datagram.h index 969c7033..f2e31304 100644 --- a/Datagram.h +++ b/Datagram.h @@ -38,7 +38,7 @@ namespace datagram private: - void HandleLeaseSetRequestComplete (bool success, I2NPMessage * msg, i2p::data::IdentHash ident); + void HandleLeaseSetRequestComplete (std::shared_ptr leaseSet, I2NPMessage * msg); I2NPMessage * CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); void SendMsg (I2NPMessage * msg, std::shared_ptr remote); diff --git a/Destination.cpp b/Destination.cpp index 4add4066..15fb8a5b 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -222,19 +222,22 @@ namespace client LogPrint (eLogInfo, "Reply token is ignored for DatabaseStore"); offset += 36; } + std::shared_ptr leaseSet; if (buf[DATABASE_STORE_TYPE_OFFSET] == 1) // LeaseSet { LogPrint (eLogDebug, "Remote LeaseSet"); auto it = m_RemoteLeaseSets.find (buf + DATABASE_STORE_KEY_OFFSET); if (it != m_RemoteLeaseSets.end ()) { - it->second->Update (buf + offset, len - offset); + leaseSet = it->second; + leaseSet->Update (buf + offset, len - offset); LogPrint (eLogDebug, "Remote LeaseSet updated"); } else { LogPrint (eLogDebug, "New remote LeaseSet added"); - m_RemoteLeaseSets[buf + DATABASE_STORE_KEY_OFFSET] = std::make_shared (buf + offset, len - offset); + leaseSet = std::make_shared (buf + offset, len - offset); + m_RemoteLeaseSets[buf + DATABASE_STORE_KEY_OFFSET] = leaseSet; } } else @@ -244,7 +247,7 @@ namespace client if (it1 != m_LeaseSetRequests.end ()) { it1->second->requestTimeoutTimer.cancel (); - if (it1->second->requestComplete) it1->second->requestComplete (true); + if (it1->second->requestComplete) it1->second->requestComplete (leaseSet); delete it1->second; m_LeaseSetRequests.erase (it1); } @@ -285,7 +288,7 @@ namespace client LogPrint (eLogInfo, key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST," floodfills"); if (!found) { - if (request->requestComplete) request->requestComplete (false); + if (request->requestComplete) request->requestComplete (nullptr); delete request; m_LeaseSetRequests.erase (key); } @@ -404,18 +407,12 @@ namespace client else { RequestDestination (dest, - [this, streamRequestComplete, dest, port](bool success) + [this, streamRequestComplete, port](std::shared_ptr ls) { - if (!success) - streamRequestComplete (nullptr); + if (ls) + streamRequestComplete(CreateStream (ls, port)); else - { - auto leaseSet = FindLeaseSet (dest); - if (leaseSet) - streamRequestComplete(CreateStream (leaseSet, port)); - else - streamRequestComplete (nullptr); - } + streamRequestComplete (nullptr); }); } } @@ -501,7 +498,7 @@ namespace client if (!SendLeaseSetRequest (dest, floodfill, request)) { // request failed - if (request->requestComplete) request->requestComplete (false); + if (request->requestComplete) request->requestComplete (nullptr); delete request; m_LeaseSetRequests.erase (dest); } @@ -510,7 +507,7 @@ namespace client { LogPrint (eLogError, "Request of ", dest.ToBase64 (), " is pending already"); // TODO: queue up requests - if (request->requestComplete) request->requestComplete (false); + if (request->requestComplete) request->requestComplete (nullptr); delete request; } } diff --git a/Destination.h b/Destination.h index 229a5943..2c51cc30 100644 --- a/Destination.h +++ b/Destination.h @@ -42,7 +42,8 @@ namespace client class ClientDestination: public i2p::garlic::GarlicDestination { - typedef std::function RequestComplete; + typedef std::function leaseSet)> RequestComplete; + // leaseSet = nullptr means not found struct LeaseSetRequest { LeaseSetRequest (boost::asio::io_service& service): requestTime (0), requestTimeoutTimer (service) {}; diff --git a/SAM.cpp b/SAM.cpp index 868874eb..c241641e 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -349,7 +349,7 @@ namespace client { m_Session->localDestination->RequestDestination (dest.GetIdentHash (), std::bind (&SAMSocket::HandleConnectLeaseSetRequestComplete, - shared_from_this (), std::placeholders::_1, dest.GetIdentHash ())); + shared_from_this (), std::placeholders::_1)); } } else @@ -366,11 +366,8 @@ namespace client SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } - void SAMSocket::HandleConnectLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident) + void SAMSocket::HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet) { - std::shared_ptr leaseSet; - if (success) - leaseSet = m_Session->localDestination->FindLeaseSet (ident); if (leaseSet) Connect (leaseSet); else @@ -486,11 +483,8 @@ namespace client } } - void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident) + void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, i2p::data::IdentHash ident) { - std::shared_ptr leaseSet; - if (success) - leaseSet = m_Session->localDestination->FindLeaseSet (ident); if (leaseSet) { context.GetAddressBook ().InsertAddress (leaseSet->GetIdentity ()); diff --git a/SAM.h b/SAM.h index 092b4954..d51f5aa3 100644 --- a/SAM.h +++ b/SAM.h @@ -109,9 +109,9 @@ namespace client void ExtractParams (char * buf, std::map& params); void Connect (std::shared_ptr remote); - void HandleConnectLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident); + void HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet); void SendNamingLookupReply (const i2p::data::IdentityEx& identity); - void HandleNamingLookupLeaseSetRequestComplete (bool success, i2p::data::IdentHash ident); + void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, i2p::data::IdentHash ident); void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode); void SendSessionCreateReplyOk (); From 3a26383c4d1429796e328a5d1dd3b86c02cd5d4d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 7 Apr 2015 14:40:36 -0400 Subject: [PATCH 0384/6300] made Encrypt const --- ElGamal.h | 2 +- Identity.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ElGamal.h b/ElGamal.h index d7105c41..3eed1ce6 100644 --- a/ElGamal.h +++ b/ElGamal.h @@ -26,7 +26,7 @@ namespace crypto b1 = a_exp_b_mod_c (y, k, elgp); } - void Encrypt (const uint8_t * data, int len, uint8_t * encrypted, bool zeroPadding = false) + void Encrypt (const uint8_t * data, int len, uint8_t * encrypted, bool zeroPadding = false) const { // calculate b = b1*m mod p uint8_t m[255]; diff --git a/Identity.h b/Identity.h index 9fed07a1..b5109c35 100644 --- a/Identity.h +++ b/Identity.h @@ -226,7 +226,7 @@ namespace data virtual const uint8_t * GetEncryptionPublicKey () const = 0; virtual bool IsDestination () const = 0; // for garlic - i2p::crypto::ElGamalEncryption * GetElGamalEncryption () const + const i2p::crypto::ElGamalEncryption * GetElGamalEncryption () const { if (!m_ElGamalEncryption) m_ElGamalEncryption = new i2p::crypto::ElGamalEncryption (GetEncryptionPublicKey ()); From 8c47bf9dd3435bb171c7e91c4b55a1b6d45f6f56 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 7 Apr 2015 15:02:00 -0400 Subject: [PATCH 0385/6300] use shared_ptr for local LeaseSet --- Destination.cpp | 14 +++----------- Destination.h | 4 ++-- Garlic.h | 2 +- I2NPProtocol.cpp | 2 +- I2NPProtocol.h | 2 +- NetDb.cpp | 2 +- RouterContext.h | 2 +- 7 files changed, 10 insertions(+), 18 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 15fb8a5b..7cd97d45 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -16,7 +16,7 @@ namespace client ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), - m_Keys (keys), m_LeaseSet (nullptr), m_IsPublic (isPublic), m_PublishReplyToken (0), + m_Keys (keys), m_IsPublic (isPublic), m_PublishReplyToken (0), m_DatagramDestination (nullptr), m_PublishConfirmationTimer (m_Service), m_CleanupTimer (m_Service) { i2p::crypto::GenerateElGamalKeyPair(i2p::context.GetRandomNumberGenerator (), m_EncryptionPrivateKey, m_EncryptionPublicKey); @@ -148,7 +148,7 @@ namespace client return nullptr; } - const i2p::data::LeaseSet * ClientDestination::GetLeaseSet () + std::shared_ptr ClientDestination::GetLeaseSet () { if (!m_Pool) return nullptr; if (!m_LeaseSet) @@ -158,15 +158,7 @@ namespace client void ClientDestination::UpdateLeaseSet () { - auto newLeaseSet = new i2p::data::LeaseSet (*m_Pool); - if (!m_LeaseSet) - m_LeaseSet = newLeaseSet; - else - { - // TODO: implement it better - *m_LeaseSet = *newLeaseSet; - delete newLeaseSet; - } + m_LeaseSet.reset (new i2p::data::LeaseSet (*m_Pool)); } bool ClientDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) diff --git a/Destination.h b/Destination.h index 2c51cc30..b5fcd520 100644 --- a/Destination.h +++ b/Destination.h @@ -88,7 +88,7 @@ namespace client const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; // implements GarlicDestination - const i2p::data::LeaseSet * GetLeaseSet (); + std::shared_ptr GetLeaseSet (); std::shared_ptr GetTunnelPool () const { return m_Pool; } void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); @@ -129,7 +129,7 @@ namespace client std::map m_LeaseSetRequests; std::shared_ptr m_Pool; - i2p::data::LeaseSet * m_LeaseSet; + std::shared_ptr m_LeaseSet; bool m_IsPublic; uint32_t m_PublishReplyToken; std::set m_ExcludedFloodfills; // for publishing diff --git a/Garlic.h b/Garlic.h index 6f96d4e7..489e532b 100644 --- a/Garlic.h +++ b/Garlic.h @@ -137,7 +137,7 @@ namespace garlic virtual void ProcessDeliveryStatusMessage (I2NPMessage * msg); virtual void SetLeaseSetUpdated (); - virtual const i2p::data::LeaseSet * GetLeaseSet () = 0; // TODO + virtual std::shared_ptr GetLeaseSet () = 0; // TODO virtual std::shared_ptr GetTunnelPool () const = 0; virtual void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) = 0; diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 42c66f9a..369ad160 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -240,7 +240,7 @@ namespace i2p return m; } - I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::LeaseSet * leaseSet, uint32_t replyToken) + I2NPMessage * CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken) { if (!leaseSet) return nullptr; I2NPMessage * m = NewI2NPShortMessage (); diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 19597d52..7d4ee4e5 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -210,7 +210,7 @@ namespace tunnel I2NPMessage * CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::RouterInfo * router = nullptr, uint32_t replyToken = 0); - I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::LeaseSet * leaseSet, uint32_t replyToken = 0); + I2NPMessage * CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0); bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); diff --git a/NetDb.cpp b/NetDb.cpp index c8237865..9a0f1a0f 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -749,7 +749,7 @@ namespace data if (leaseSet) // we don't send back our LeaseSets { LogPrint ("Requested LeaseSet ", key, " found"); - replyMsg = CreateDatabaseStoreMsg (leaseSet.get ()); + replyMsg = CreateDatabaseStoreMsg (leaseSet); } } if (!replyMsg) diff --git a/RouterContext.h b/RouterContext.h index 81ef4867..9f6cd00b 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -72,7 +72,7 @@ namespace i2p void SetLeaseSetUpdated () {}; // implements GarlicDestination - const i2p::data::LeaseSet * GetLeaseSet () { return nullptr; }; + std::shared_ptr GetLeaseSet () { return nullptr; }; std::shared_ptr GetTunnelPool () const; void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); From 1e74ff8a8544cada70997a9c70de254859e0c3c0 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 7 Apr 2015 15:15:27 -0400 Subject: [PATCH 0386/6300] use shared_ptr for CreateDatabaseStore --- I2NPProtocol.cpp | 4 ++-- I2NPProtocol.h | 2 +- NetDb.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 369ad160..588332a3 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -205,10 +205,10 @@ namespace i2p return m; } - I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::RouterInfo * router, uint32_t replyToken) + I2NPMessage * CreateDatabaseStoreMsg (std::shared_ptr router, uint32_t replyToken) { if (!router) // we send own RouterInfo - router = &context.GetRouterInfo (); + router = context.GetSharedRouterInfo (); I2NPMessage * m = NewI2NPShortMessage (); uint8_t * payload = m->GetPayload (); diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 7d4ee4e5..fde33186 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -209,7 +209,7 @@ namespace tunnel const i2p::tunnel::InboundTunnel * replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag); I2NPMessage * CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); - I2NPMessage * CreateDatabaseStoreMsg (const i2p::data::RouterInfo * router = nullptr, uint32_t replyToken = 0); + I2NPMessage * CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0); I2NPMessage * CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0); bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); diff --git a/NetDb.cpp b/NetDb.cpp index 9a0f1a0f..a79489ab 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -740,7 +740,7 @@ namespace data LogPrint ("Requested RouterInfo ", key, " found"); router->LoadBuffer (); if (router->GetBuffer ()) - replyMsg = CreateDatabaseStoreMsg (router.get ()); + replyMsg = CreateDatabaseStoreMsg (router); } if (!replyMsg) @@ -874,7 +874,7 @@ namespace data { uint32_t replyToken = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); LogPrint ("Publishing our RouterInfo to ", floodfill->GetIdentHashAbbreviation (), ". reply token=", replyToken); - transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg ((RouterInfo *)nullptr, replyToken)); + transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (std::make_shared(nullptr), replyToken)); excluded.insert (floodfill->GetIdentHash ()); } } From 39838386943f4f392f5b3a55481f691787360473 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 7 Apr 2015 16:02:07 -0400 Subject: [PATCH 0387/6300] use unique_ptr for ElGamalEncryption --- Identity.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Identity.h b/Identity.h index b5109c35..16c9423e 100644 --- a/Identity.h +++ b/Identity.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "base64.h" #include "ElGamal.h" #include "Signature.h" @@ -219,23 +220,23 @@ namespace data { public: - RoutingDestination (): m_ElGamalEncryption (nullptr) {}; - virtual ~RoutingDestination () { delete m_ElGamalEncryption; }; + RoutingDestination () {}; + virtual ~RoutingDestination () {}; virtual const IdentHash& GetIdentHash () const = 0; virtual const uint8_t * GetEncryptionPublicKey () const = 0; virtual bool IsDestination () const = 0; // for garlic - const i2p::crypto::ElGamalEncryption * GetElGamalEncryption () const + std::unique_ptr& GetElGamalEncryption () const { if (!m_ElGamalEncryption) - m_ElGamalEncryption = new i2p::crypto::ElGamalEncryption (GetEncryptionPublicKey ()); + m_ElGamalEncryption.reset (new i2p::crypto::ElGamalEncryption (GetEncryptionPublicKey ())); return m_ElGamalEncryption; } private: - mutable i2p::crypto::ElGamalEncryption * m_ElGamalEncryption; // use lazy initialization + mutable std::unique_ptr m_ElGamalEncryption; // use lazy initialization }; class LocalDestination From 2c480bee9a8d3a799fe02ed2175f5fa82d357f3f Mon Sep 17 00:00:00 2001 From: Robert Foss Date: Tue, 7 Apr 2015 22:26:35 +0200 Subject: [PATCH 0388/6300] Fixed memory leak: delete -> delete[] --- RouterInfo.cpp | 2 +- RouterInfo.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index a1c3533f..7bc0161e 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -36,7 +36,7 @@ namespace data RouterInfo::~RouterInfo () { - delete m_Buffer; + delete[] m_Buffer; } void RouterInfo::Update (const uint8_t * buf, int len) diff --git a/RouterInfo.h b/RouterInfo.h index 4934da32..376dfdc7 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -142,7 +142,7 @@ namespace data void SaveProfile () { if (m_Profile) m_Profile->Save (); }; void Update (const uint8_t * buf, int len); - void DeleteBuffer () { delete m_Buffer; m_Buffer = nullptr; }; + void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; }; // implements RoutingDestination const IdentHash& GetIdentHash () const { return m_RouterIdentity.GetIdentHash (); }; From 56822d9424725c8a28a1281325f602337c6dcf24 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 7 Apr 2015 17:22:14 -0400 Subject: [PATCH 0389/6300] fixed null pointer exception --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index a79489ab..38bc56e9 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -874,7 +874,7 @@ namespace data { uint32_t replyToken = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); LogPrint ("Publishing our RouterInfo to ", floodfill->GetIdentHashAbbreviation (), ". reply token=", replyToken); - transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (std::make_shared(nullptr), replyToken)); + transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken)); excluded.insert (floodfill->GetIdentHash ()); } } From 9ce9d9b7fc9ceb55be54f5175849d4136923c4bc Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Apr 2015 09:39:02 -0400 Subject: [PATCH 0390/6300] variable length buffer for LeaseSet --- LeaseSet.cpp | 13 +++++++++++-- LeaseSet.h | 9 ++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index b9c2b132..3bc5d306 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -14,8 +14,9 @@ namespace i2p namespace data { - LeaseSet::LeaseSet (const uint8_t * buf, int len) + LeaseSet::LeaseSet (const uint8_t * buf, size_t len) { + m_Buffer = new uint8_t[len]; memcpy (m_Buffer, buf, len); m_BufferLen = len; ReadFromBuffer (); @@ -27,10 +28,12 @@ namespace data const i2p::data::LocalDestination * localDestination = pool.GetLocalDestination (); if (!localDestination) { + m_Buffer = nullptr; m_BufferLen = 0; LogPrint (eLogError, "Destination for local LeaseSet doesn't exist"); return; } + m_Buffer = new uint8_t[localDestination->GetIdentity ().GetFullLen ()]; m_BufferLen = localDestination->GetIdentity ().ToBuffer (m_Buffer, MAX_LS_BUFFER_SIZE); memcpy (m_Buffer + m_BufferLen, localDestination->GetEncryptionPublicKey (), 256); m_BufferLen += 256; @@ -62,9 +65,15 @@ namespace data ReadFromBuffer (); } - void LeaseSet::Update (const uint8_t * buf, int len) + void LeaseSet::Update (const uint8_t * buf, size_t len) { m_Leases.clear (); + if (len > m_BufferLen) + { + auto oldBuffer = m_Buffer; + m_Buffer = new uint8_t[len]; + delete[] oldBuffer; + } memcpy (m_Buffer, buf, len); m_BufferLen = len; ReadFromBuffer (); diff --git a/LeaseSet.h b/LeaseSet.h index eac5fc28..d1164449 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -36,11 +36,10 @@ namespace data { public: - LeaseSet (const uint8_t * buf, int len); - LeaseSet (const LeaseSet& ) = default; + LeaseSet (const uint8_t * buf, size_t len); LeaseSet (const i2p::tunnel::TunnelPool& pool); - LeaseSet& operator=(const LeaseSet& ) = default; - void Update (const uint8_t * buf, int len); + ~LeaseSet () { delete[] m_Buffer; }; + void Update (const uint8_t * buf, size_t len); const IdentityEx& GetIdentity () const { return m_Identity; }; const uint8_t * GetBuffer () const { return m_Buffer; }; @@ -64,7 +63,7 @@ namespace data std::vector m_Leases; IdentityEx m_Identity; uint8_t m_EncryptionKey[256]; - uint8_t m_Buffer[MAX_LS_BUFFER_SIZE]; + uint8_t * m_Buffer; size_t m_BufferLen; }; } From d5e1d5db9c011faf68b7def8644d55b0c8108bb4 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Apr 2015 10:34:16 -0400 Subject: [PATCH 0391/6300] validate leaseset for zero leases --- Destination.cpp | 21 ++++++++++++++++++--- LeaseSet.cpp | 15 +++++++++++---- LeaseSet.h | 2 ++ NetDb.cpp | 18 +++++++++++++++--- 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 7cd97d45..4c7d382a 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -223,13 +223,28 @@ namespace client { leaseSet = it->second; leaseSet->Update (buf + offset, len - offset); - LogPrint (eLogDebug, "Remote LeaseSet updated"); + if (leaseSet->IsValid ()) + LogPrint (eLogDebug, "Remote LeaseSet updated"); + else + { + LogPrint (eLogDebug, "Remote LeaseSet update failed"); + m_RemoteLeaseSets.erase (it); + leaseSet = nullptr; + } } else { - LogPrint (eLogDebug, "New remote LeaseSet added"); leaseSet = std::make_shared (buf + offset, len - offset); - m_RemoteLeaseSets[buf + DATABASE_STORE_KEY_OFFSET] = leaseSet; + if (leaseSet->IsValid ()) + { + LogPrint (eLogDebug, "New remote LeaseSet added"); + m_RemoteLeaseSets[buf + DATABASE_STORE_KEY_OFFSET] = leaseSet; + } + else + { + LogPrint (eLogError, "New remote LeaseSet verification failed"); + leaseSet = nullptr; + } } } else diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 3bc5d306..b2e84d23 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -14,7 +14,8 @@ namespace i2p namespace data { - LeaseSet::LeaseSet (const uint8_t * buf, size_t len) + LeaseSet::LeaseSet (const uint8_t * buf, size_t len): + m_IsValid (true) { m_Buffer = new uint8_t[len]; memcpy (m_Buffer, buf, len); @@ -22,7 +23,8 @@ namespace data ReadFromBuffer (); } - LeaseSet::LeaseSet (const i2p::tunnel::TunnelPool& pool) + LeaseSet::LeaseSet (const i2p::tunnel::TunnelPool& pool): + m_IsValid (true) { // header const i2p::data::LocalDestination * localDestination = pool.GetLocalDestination (); @@ -30,6 +32,7 @@ namespace data { m_Buffer = nullptr; m_BufferLen = 0; + m_IsValid = false; LogPrint (eLogError, "Destination for local LeaseSet doesn't exist"); return; } @@ -88,6 +91,7 @@ namespace data uint8_t num = m_Buffer[size]; size++; // num LogPrint ("LeaseSet num=", (int)num); + if (!num) m_IsValid = false; // process leases const uint8_t * leases = m_Buffer + size; @@ -106,14 +110,17 @@ namespace data if (!netdb.FindRouter (lease.tunnelGateway)) { // if not found request it - LogPrint ("Lease's tunnel gateway not found. Requested"); + LogPrint (eLogInfo, "Lease's tunnel gateway not found. Requested"); netdb.RequestDestination (lease.tunnelGateway); } } // verify if (!m_Identity.Verify (m_Buffer, leases - m_Buffer, leases)) - LogPrint ("LeaseSet verification failed"); + { + LogPrint (eLogWarning, "LeaseSet verification failed"); + m_IsValid = false; + } } const std::vector LeaseSet::GetNonExpiredLeases (bool withThreshold) const diff --git a/LeaseSet.h b/LeaseSet.h index d1164449..fee130ab 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -44,6 +44,7 @@ namespace data const uint8_t * GetBuffer () const { return m_Buffer; }; size_t GetBufferLen () const { return m_BufferLen; }; + bool IsValid () const { return m_IsValid; }; // implements RoutingDestination const IdentHash& GetIdentHash () const { return m_Identity.GetIdentHash (); }; @@ -60,6 +61,7 @@ namespace data private: + bool m_IsValid; std::vector m_Leases; IdentityEx m_Identity; uint8_t m_EncryptionKey[256]; diff --git a/NetDb.cpp b/NetDb.cpp index 38bc56e9..a647ba5b 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -252,12 +252,24 @@ namespace data if (it != m_LeaseSets.end ()) { it->second->Update (buf, len); - LogPrint ("LeaseSet updated"); + if (it->second->IsValid ()) + LogPrint (eLogInfo, "LeaseSet updated"); + else + { + LogPrint (eLogInfo, "LeaseSet update failed"); + m_LeaseSets.erase (it); + } } else { - LogPrint ("New LeaseSet added"); - m_LeaseSets[ident] = std::make_shared (buf, len); + auto leaseSet = std::make_shared (buf, len); + if (leaseSet->IsValid ()) + { + LogPrint (eLogInfo, "New LeaseSet added"); + m_LeaseSets[ident] = leaseSet; + } + else + LogPrint (eLogError, "New LeaseSet validation failed"); } } } From 75d45ae988e67721a1854281edff05354686863d Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Apr 2015 13:21:49 -0400 Subject: [PATCH 0392/6300] initial code for Ed25519 added --- Signature.cpp | 44 +++++++++++++++++++++++++++++++++++++ Win32/i2pd.vcxproj | 1 + build/CMakeLists.txt | 3 ++- build/autotools/Makefile.in | 6 +++-- filelist.mk | 7 +++--- 5 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 Signature.cpp diff --git a/Signature.cpp b/Signature.cpp new file mode 100644 index 00000000..a81d0603 --- /dev/null +++ b/Signature.cpp @@ -0,0 +1,44 @@ +#include +#include +#include "Signature.h" + +namespace i2p +{ +namespace crypto +{ + class Ed25519 + { + public: + + Ed25519 (): b(256) + { + q = CryptoPP::Integer::Power2 (255) - CryptoPP::Integer (19); // 2^255-19 + l = CryptoPP::Integer::Power2 (252) + CryptoPP::Integer ("27742317777372353535851937790883648493"); + // 2^252 + 27742317777372353535851937790883648493 + d = CryptoPP::Integer (-121665) * CryptoPP::Integer (121666).InverseMod (q); // -121665/121666 + } + + private: + + CryptoPP::ECP::Point Sum (CryptoPP::ECP::Point p1, CryptoPP::ECP::Point p2) + { + CryptoPP::Integer m = d*p1.x*p2.x*p1.y*p2.y, + x = a_times_b_mod_c (p1.x*p2.y + p2.x*p1.y, (CryptoPP::Integer::One() + m).InverseMod (q), q), + y = a_times_b_mod_c (p1.y*p2.y + p1.x*p2.x, (CryptoPP::Integer::One() - m).InverseMod (q), q); + return CryptoPP::ECP::Point {x, y}; + } + + CryptoPP::ECP::Point Mul (CryptoPP::ECP::Point p, CryptoPP::Integer e) + { + if (e.IsZero ()) return CryptoPP::ECP::Point {0, 1}; + return p; // TODO + } + + private: + + CryptoPP::Integer b, q, l, d; + }; +} +} + + diff --git a/Win32/i2pd.vcxproj b/Win32/i2pd.vcxproj index 4fc942ef..0eb82f3d 100644 --- a/Win32/i2pd.vcxproj +++ b/Win32/i2pd.vcxproj @@ -42,6 +42,7 @@ + diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 78efe80e..4fb0ad37 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -40,7 +40,8 @@ set (COMMON_SRC "${CMAKE_SOURCE_DIR}/aes.cpp" "${CMAKE_SOURCE_DIR}/base64.cpp" "${CMAKE_SOURCE_DIR}/util.cpp" - "${CMAKE_SOURCE_DIR}/Datagram.cpp" + "${CMAKE_SOURCE_DIR}/Datagram.cpp" + "${CMAKE_SOURCE_DIR}/Signature.cpp" ) set (DAEMON_SRC diff --git a/build/autotools/Makefile.in b/build/autotools/Makefile.in index 053ca6a9..7bee7218 100644 --- a/build/autotools/Makefile.in +++ b/build/autotools/Makefile.in @@ -116,7 +116,7 @@ am_i2p_OBJECTS = AddressBook.$(OBJEXT) CryptoConst.$(OBJEXT) \ aes.$(OBJEXT) base64.$(OBJEXT) i2p.$(OBJEXT) util.$(OBJEXT) \ SAM.$(OBJEXT) Destination.$(OBJEXT) ClientContext.$(OBJEXT) \ Datagram.$(OBJEXT) SSUSession.$(OBJEXT) BOB.$(OBJEXT) \ - I2PControl.$(OBJEXT) Profiling.$(OBJEXT) + I2PControl.$(OBJEXT) Profiling.$(OBJEXT) Signature.$(OBJEXT) i2p_OBJECTS = $(am_i2p_OBJECTS) i2p_LDADD = $(LDADD) AM_V_P = $(am__v_P_@AM_V@) @@ -328,7 +328,7 @@ i2p_SOURCES = AddressBook.cpp CryptoConst.cpp Daemon.cpp \ TunnelGateway.cpp TunnelPool.cpp UPnP.cpp aes.cpp \ base64.cpp i2p.cpp util.cpp SAM.cpp Destination.cpp \ ClientContext.cpp DataFram.cpp SSUSession.cpp BOB.cpp \ - I2PControl.cpp Profiling.cpp \ + I2PControl.cpp Profiling.cpp Signature.cpp \ \ AddressBook.h CryptoConst.h Daemon.h ElGamal.h \ Garlic.h HTTPProxy.h HTTPServer.h I2NPProtocol.h \ @@ -493,6 +493,8 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ClientContext.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Datagram.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SSUSession.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Profiling.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Signature.Po@am__quote@ .cpp.o: @am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< diff --git a/filelist.mk b/filelist.mk index 876734c6..48298bc5 100644 --- a/filelist.mk +++ b/filelist.mk @@ -1,9 +1,10 @@ COMMON_SRC = \ CryptoConst.cpp Datagram.cpp Garlic.cpp I2NPProtocol.cpp LeaseSet.cpp \ Log.cpp NTCPSession.cpp NetDb.cpp Profiling.cpp Reseed.cpp RouterContext.cpp \ - RouterInfo.cpp SSU.cpp SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp \ - TransitTunnel.cpp Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp \ - TunnelGateway.cpp Destination.cpp UPnP.cpp util.cpp aes.cpp base64.cpp + RouterInfo.cpp Signature.cpp SSU.cpp SSUSession.cpp SSUData.cpp Streaming.cpp \ + Identity.cpp TransitTunnel.cpp Transports.cpp Tunnel.cpp TunnelEndpoint.cpp \ + TunnelPool.cpp TunnelGateway.cpp Destination.cpp UPnP.cpp util.cpp aes.cpp \ + base64.cpp ifeq ($(UNAME),Darwin) From 8c92c50f9ae855329122454b35d202c4dd3fefc8 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Apr 2015 13:49:27 -0400 Subject: [PATCH 0393/6300] multiplication by integer --- Signature.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index a81d0603..5ea61bec 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -20,7 +20,7 @@ namespace crypto private: - CryptoPP::ECP::Point Sum (CryptoPP::ECP::Point p1, CryptoPP::ECP::Point p2) + CryptoPP::ECP::Point Sum (const CryptoPP::ECP::Point& p1, const CryptoPP::ECP::Point& p2) { CryptoPP::Integer m = d*p1.x*p2.x*p1.y*p2.y, x = a_times_b_mod_c (p1.x*p2.y + p2.x*p1.y, (CryptoPP::Integer::One() + m).InverseMod (q), q), @@ -28,10 +28,19 @@ namespace crypto return CryptoPP::ECP::Point {x, y}; } - CryptoPP::ECP::Point Mul (CryptoPP::ECP::Point p, CryptoPP::Integer e) + CryptoPP::ECP::Point Mul (const CryptoPP::ECP::Point& p, const CryptoPP::Integer& e) { - if (e.IsZero ()) return CryptoPP::ECP::Point {0, 1}; - return p; // TODO + CryptoPP::ECP::Point res {0, 1}; + if (!e.IsZero ()) + { + auto bitCount = e.BitCount (); + for (int i = bitCount - 1; i >= 0; i--) + { + res = Sum (res, res); + if (e.GetBit (i)) res = Sum (res, p); + } + } + return res; } private: From 49d59fc116266edbfcebcfb24576cedbb7d50247 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Apr 2015 14:07:45 -0400 Subject: [PATCH 0394/6300] IsOnCurve added --- Signature.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Signature.cpp b/Signature.cpp index 5ea61bec..fdaeff54 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -43,6 +43,12 @@ namespace crypto return res; } + bool IsOnCurve (const CryptoPP::ECP::Point& p) + { + auto x2 = p.x.Squared(), y2 = p.y.Squared (); + return (y2 - x2 - CryptoPP::Integer::One() - d*x2*y2).Modulo (q).IsZero (); + } + private: CryptoPP::Integer b, q, l, d; From 8891d9aa4dc10d532accd23e984ea30f0fcdb521 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Apr 2015 15:31:13 -0400 Subject: [PATCH 0395/6300] Decode point --- Signature.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index fdaeff54..ac3c35ea 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -1,5 +1,6 @@ #include #include +#include "Log.h" #include "Signature.h" namespace i2p @@ -10,12 +11,13 @@ namespace crypto { public: - Ed25519 (): b(256) + Ed25519 () { q = CryptoPP::Integer::Power2 (255) - CryptoPP::Integer (19); // 2^255-19 l = CryptoPP::Integer::Power2 (252) + CryptoPP::Integer ("27742317777372353535851937790883648493"); // 2^252 + 27742317777372353535851937790883648493 d = CryptoPP::Integer (-121665) * CryptoPP::Integer (121666).InverseMod (q); // -121665/121666 + I = a_exp_b_mod_c (CryptoPP::Integer::Two (), (q - CryptoPP::Integer::One ()).DividedBy (4), q); } private: @@ -49,9 +51,32 @@ namespace crypto return (y2 - x2 - CryptoPP::Integer::One() - d*x2*y2).Modulo (q).IsZero (); } + CryptoPP::Integer RecoverX (const CryptoPP::Integer& y) + { + auto y2 = y.Squared (); + auto xx = (y2 - CryptoPP::Integer::One())*(d*y2 + CryptoPP::Integer::One()).InverseMod (q); + auto x = a_exp_b_mod_c (xx, (q + CryptoPP::Integer (3)).DividedBy (8), q); + if (!(x.Squared () - xx).Modulo (q).IsZero ()) + x = a_times_b_mod_c (x, I, q); + if (x.IsOdd ()) x = q - x; + return x; + } + + CryptoPP::ECP::Point DecodePoint (const CryptoPP::Integer& y) + { + auto x = RecoverX (y); + CryptoPP::ECP::Point p {x, y}; + if (!IsOnCurve (p)) + { + LogPrint (eLogError, "Decoded point is not on 25519"); + return CryptoPP::ECP::Point {0, 1}; + } + return p; + } + private: - CryptoPP::Integer b, q, l, d; + CryptoPP::Integer q, l, d, I; }; } } From 454f2dabbda3267d8e8a92de8ed442546c2adcd9 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Apr 2015 16:18:16 -0400 Subject: [PATCH 0396/6300] EdDSA signature type added --- Identity.cpp | 17 +++++++++++++++-- Identity.h | 1 + Signature.cpp | 7 ++++++- Signature.h | 18 ++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index fa8eabde..43decec1 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -94,7 +94,14 @@ namespace data excessBuf = new uint8_t[excessLen]; memcpy (excessBuf, signingKey + 128, excessLen); break; - } + } + case SIGNING_KEY_TYPE_EDDSA_SHA512: + { + size_t padding = 128 - i2p::crypto::EDDSA_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 + i2p::context.GetRandomNumberGenerator ().GenerateBlock (m_StandardIdentity.signingKey, padding); + memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA_PUBLIC_KEY_LENGTH); + break; + } default: LogPrint ("Signing key type ", (int)type, " is not supported"); } @@ -344,7 +351,13 @@ namespace data memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types m_Verifier = new i2p::crypto:: RSASHA5124096Verifier (signingKey); break; - } + } + case SIGNING_KEY_TYPE_EDDSA_SHA512: + { + size_t padding = 128 - i2p::crypto::EDDSA_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 + m_Verifier = new i2p::crypto::EDDSAVerifier (m_StandardIdentity.signingKey + padding); + break; + } default: LogPrint ("Signing key type ", (int)keyType, " is not supported"); } diff --git a/Identity.h b/Identity.h index 16c9423e..beaf910e 100644 --- a/Identity.h +++ b/Identity.h @@ -117,6 +117,7 @@ namespace data const uint16_t SIGNING_KEY_TYPE_RSA_SHA256_2048 = 4; const uint16_t SIGNING_KEY_TYPE_RSA_SHA384_3072 = 5; const uint16_t SIGNING_KEY_TYPE_RSA_SHA512_4096 = 6; + const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512 = 7; typedef uint16_t SigningKeyType; typedef uint16_t CryptoKeyType; diff --git a/Signature.cpp b/Signature.cpp index ac3c35ea..f29a18be 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -77,7 +77,12 @@ namespace crypto private: CryptoPP::Integer q, l, d, I; - }; + }; + + bool EDDSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const + { + return true; // TODO: + } } } diff --git a/Signature.h b/Signature.h index 23840562..24032708 100644 --- a/Signature.h +++ b/Signature.h @@ -410,6 +410,24 @@ namespace crypto { } }; + + // EdDSA + const size_t EDDSA_PUBLIC_KEY_LENGTH = 32; + const size_t EDDSA_SIGNATURE_LENGTH = 64; + const size_t EDDSA_PRIVATE_KEY_LENGTH = 32; + class EDDSAVerifier: public Verifier + { + public: + + EDDSAVerifier (const uint8_t * signingKey) + { + } + + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; + + size_t GetPublicKeyLen () const { return EDDSA_PUBLIC_KEY_LENGTH; }; + size_t GetSignatureLen () const { return EDDSA_SIGNATURE_LENGTH; }; + }; } } From 48289845df311ef28ac2bb9eba659c2966dee605 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Apr 2015 16:28:52 -0400 Subject: [PATCH 0397/6300] EdDSA signature type added --- Identity.cpp | 12 ++++++------ Identity.h | 2 +- Signature.cpp | 2 +- Signature.h | 14 +++++++------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index 43decec1..3b64b563 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -95,11 +95,11 @@ namespace data memcpy (excessBuf, signingKey + 128, excessLen); break; } - case SIGNING_KEY_TYPE_EDDSA_SHA512: + case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: { - size_t padding = 128 - i2p::crypto::EDDSA_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 + size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 i2p::context.GetRandomNumberGenerator ().GenerateBlock (m_StandardIdentity.signingKey, padding); - memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA_PUBLIC_KEY_LENGTH); + memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH); break; } default: @@ -352,10 +352,10 @@ namespace data m_Verifier = new i2p::crypto:: RSASHA5124096Verifier (signingKey); break; } - case SIGNING_KEY_TYPE_EDDSA_SHA512: + case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: { - size_t padding = 128 - i2p::crypto::EDDSA_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 - m_Verifier = new i2p::crypto::EDDSAVerifier (m_StandardIdentity.signingKey + padding); + size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 + m_Verifier = new i2p::crypto::EDDSA25519Verifier (m_StandardIdentity.signingKey + padding); break; } default: diff --git a/Identity.h b/Identity.h index beaf910e..5859f988 100644 --- a/Identity.h +++ b/Identity.h @@ -117,7 +117,7 @@ namespace data const uint16_t SIGNING_KEY_TYPE_RSA_SHA256_2048 = 4; const uint16_t SIGNING_KEY_TYPE_RSA_SHA384_3072 = 5; const uint16_t SIGNING_KEY_TYPE_RSA_SHA512_4096 = 6; - const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512 = 7; + const uint16_t SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 = 7; typedef uint16_t SigningKeyType; typedef uint16_t CryptoKeyType; diff --git a/Signature.cpp b/Signature.cpp index f29a18be..20f0c289 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -79,7 +79,7 @@ namespace crypto CryptoPP::Integer q, l, d, I; }; - bool EDDSAVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const + bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { return true; // TODO: } diff --git a/Signature.h b/Signature.h index 24032708..3aa739e4 100644 --- a/Signature.h +++ b/Signature.h @@ -412,21 +412,21 @@ namespace crypto }; // EdDSA - const size_t EDDSA_PUBLIC_KEY_LENGTH = 32; - const size_t EDDSA_SIGNATURE_LENGTH = 64; - const size_t EDDSA_PRIVATE_KEY_LENGTH = 32; - class EDDSAVerifier: public Verifier + const size_t EDDSA25519_PUBLIC_KEY_LENGTH = 32; + const size_t EDDSA25519_SIGNATURE_LENGTH = 64; + const size_t EDDSA25519_PRIVATE_KEY_LENGTH = 32; + class EDDSA25519Verifier: public Verifier { public: - EDDSAVerifier (const uint8_t * signingKey) + EDDSA25519Verifier (const uint8_t * signingKey) { } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; - size_t GetPublicKeyLen () const { return EDDSA_PUBLIC_KEY_LENGTH; }; - size_t GetSignatureLen () const { return EDDSA_SIGNATURE_LENGTH; }; + size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; }; + size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; }; }; } } From e0b19a6383ac43027ad17df13370e722abc265d5 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Apr 2015 19:06:47 -0400 Subject: [PATCH 0398/6300] fixed crash --- LeaseSet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index b2e84d23..77d11e1a 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -36,7 +36,7 @@ namespace data LogPrint (eLogError, "Destination for local LeaseSet doesn't exist"); return; } - m_Buffer = new uint8_t[localDestination->GetIdentity ().GetFullLen ()]; + m_Buffer = new uint8_t[MAX_LS_BUFFER_SIZE]; m_BufferLen = localDestination->GetIdentity ().ToBuffer (m_Buffer, MAX_LS_BUFFER_SIZE); memcpy (m_Buffer + m_BufferLen, localDestination->GetEncryptionPublicKey (), 256); m_BufferLen += 256; From 01913d2b14c4077885369462d380d204e8a2ee0d Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 9 Apr 2015 10:03:21 -0400 Subject: [PATCH 0399/6300] EdDSA signer added --- Identity.cpp | 3 +++ Signature.cpp | 42 +++++++++++++++++++++++++++++++++++++----- Signature.h | 20 +++++++++++++++----- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index 3b64b563..9dc96d01 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -470,6 +470,9 @@ namespace data case SIGNING_KEY_TYPE_RSA_SHA512_4096: m_Signer = new i2p::crypto::RSASHA5124096Signer (m_SigningPrivateKey); break; + case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: + m_Signer = new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey); + break; default: LogPrint ("Signing key type ", (int)m_Public.GetSigningKeyType (), " is not supported"); } diff --git a/Signature.cpp b/Signature.cpp index 20f0c289..603ba6eb 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -1,3 +1,4 @@ +#include #include #include #include "Log.h" @@ -18,11 +19,22 @@ namespace crypto // 2^252 + 27742317777372353535851937790883648493 d = CryptoPP::Integer (-121665) * CryptoPP::Integer (121666).InverseMod (q); // -121665/121666 I = a_exp_b_mod_c (CryptoPP::Integer::Two (), (q - CryptoPP::Integer::One ()).DividedBy (4), q); + B = DecodePoint (CryptoPP::Integer (4)*CryptoPP::Integer (5).InverseMod (q)); + } + + CryptoPP::ECP::Point DecodePublicKey (const uint8_t * key) const + { + return DecodePoint (CryptoPP::Integer (key, 32)); + } + + CryptoPP::ECP::Point GeneratePublicKey (const uint8_t * privateKey) const + { + return Mul (B, CryptoPP::Integer (privateKey, 32)); } private: - CryptoPP::ECP::Point Sum (const CryptoPP::ECP::Point& p1, const CryptoPP::ECP::Point& p2) + CryptoPP::ECP::Point Sum (const CryptoPP::ECP::Point& p1, const CryptoPP::ECP::Point& p2) const { CryptoPP::Integer m = d*p1.x*p2.x*p1.y*p2.y, x = a_times_b_mod_c (p1.x*p2.y + p2.x*p1.y, (CryptoPP::Integer::One() + m).InverseMod (q), q), @@ -30,7 +42,7 @@ namespace crypto return CryptoPP::ECP::Point {x, y}; } - CryptoPP::ECP::Point Mul (const CryptoPP::ECP::Point& p, const CryptoPP::Integer& e) + CryptoPP::ECP::Point Mul (const CryptoPP::ECP::Point& p, const CryptoPP::Integer& e) const { CryptoPP::ECP::Point res {0, 1}; if (!e.IsZero ()) @@ -45,13 +57,13 @@ namespace crypto return res; } - bool IsOnCurve (const CryptoPP::ECP::Point& p) + bool IsOnCurve (const CryptoPP::ECP::Point& p) const { auto x2 = p.x.Squared(), y2 = p.y.Squared (); return (y2 - x2 - CryptoPP::Integer::One() - d*x2*y2).Modulo (q).IsZero (); } - CryptoPP::Integer RecoverX (const CryptoPP::Integer& y) + CryptoPP::Integer RecoverX (const CryptoPP::Integer& y) const { auto y2 = y.Squared (); auto xx = (y2 - CryptoPP::Integer::One())*(d*y2 + CryptoPP::Integer::One()).InverseMod (q); @@ -62,7 +74,7 @@ namespace crypto return x; } - CryptoPP::ECP::Point DecodePoint (const CryptoPP::Integer& y) + CryptoPP::ECP::Point DecodePoint (const CryptoPP::Integer& y) const { auto x = RecoverX (y); CryptoPP::ECP::Point p {x, y}; @@ -77,11 +89,31 @@ namespace crypto private: CryptoPP::Integer q, l, d, I; + CryptoPP::ECP::Point B; // base point }; + static std::unique_ptr g_Ed25519; + std::unique_ptr& GetEd25519 () + { + if (!g_Ed25519) + g_Ed25519.reset (new Ed25519 ()); + return g_Ed25519; + } + + + EDDSA25519Verifier::EDDSA25519Verifier (const uint8_t * signingKey): + m_PublicKey (GetEd25519 ()->DecodePublicKey (signingKey)) + { + } + bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { return true; // TODO: + } + + void EDDSA25519Signer::Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const + { + // TODO } } } diff --git a/Signature.h b/Signature.h index 3aa739e4..acfaa62f 100644 --- a/Signature.h +++ b/Signature.h @@ -419,14 +419,24 @@ namespace crypto { public: - EDDSA25519Verifier (const uint8_t * signingKey) - { - } - + EDDSA25519Verifier (const uint8_t * signingKey); bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; }; - size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; }; + size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; }; + + private: + + CryptoPP::ECP::Point m_PublicKey; + }; + + class EDDSA25519Signer: public Signer + { + public: + + EDDSA25519Signer (const uint8_t * signingPrivateKey) {}; + + void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const; }; } } From 950f250d66d04cf40fc4ff978395c4c43f7536dc Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 9 Apr 2015 12:45:00 -0400 Subject: [PATCH 0400/6300] NetDb/NetDbRequests split --- NetDb.cpp | 162 +++++------------------------------- NetDb.h | 39 +-------- NetDbRequests.cpp | 149 +++++++++++++++++++++++++++++++++ NetDbRequests.h | 69 +++++++++++++++ Win32/i2pd.vcxproj | 2 + build/CMakeLists.txt | 1 + build/autotools/Makefile.in | 6 +- filelist.mk | 10 +-- 8 files changed, 256 insertions(+), 182 deletions(-) create mode 100644 NetDbRequests.cpp create mode 100644 NetDbRequests.h diff --git a/NetDb.cpp b/NetDb.cpp index a647ba5b..c5d0fde8 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -21,49 +21,6 @@ namespace i2p { namespace data { - I2NPMessage * RequestedDestination::CreateRequestMessage (std::shared_ptr router, - std::shared_ptr replyTunnel) - { - I2NPMessage * msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, - replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory, - &m_ExcludedPeers); - m_ExcludedPeers.insert (router->GetIdentHash ()); - m_CreationTime = i2p::util::GetSecondsSinceEpoch (); - return msg; - } - - I2NPMessage * RequestedDestination::CreateRequestMessage (const IdentHash& floodfill) - { - I2NPMessage * msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, - i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers); - m_ExcludedPeers.insert (floodfill); - m_CreationTime = i2p::util::GetSecondsSinceEpoch (); - return msg; - } - - void RequestedDestination::ClearExcludedPeers () - { - m_ExcludedPeers.clear (); - } - - void RequestedDestination::Success (std::shared_ptr r) - { - if (m_RequestComplete) - { - m_RequestComplete (r); - m_RequestComplete = nullptr; - } - } - - void RequestedDestination::Fail () - { - if (m_RequestComplete) - { - m_RequestComplete (nullptr); - m_RequestComplete = nullptr; - } - } - #ifndef _WIN32 const char NetDb::m_NetDbPath[] = "/netDb"; #else @@ -120,7 +77,7 @@ namespace data m_Thread = 0; } m_LeaseSets.clear(); - m_RequestedDestinations.clear (); + m_Requests.Stop (); } void NetDb::Run () @@ -164,7 +121,7 @@ namespace data uint64_t ts = i2p::util::GetSecondsSinceEpoch (); if (ts - lastManageRequest >= 15) // manage requests every 15 seconds { - ManageRequests (); + m_Requests.ManageRequests (); lastManageRequest = ts; } if (ts - lastSave >= 60) // save routers, manage leasesets and validate subscriptions every minute @@ -189,7 +146,7 @@ namespace data numRouters = 800/numRouters; if (numRouters < 1) numRouters = 1; if (numRouters > 9) numRouters = 9; - ManageRequests (); + m_Requests.ManageRequests (); Explore (numRouters); lastExploratory = ts; } @@ -234,13 +191,7 @@ namespace data } } // take care about requested destination - auto it = m_RequestedDestinations.find (ident); - if (it != m_RequestedDestinations.end ()) - { - it->second->Success (r); - std::unique_lock l(m_RequestedDestinationsMutex); - m_RequestedDestinations.erase (it); - } + m_Requests.RequestComplete (ident, r); } void NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, @@ -487,28 +438,20 @@ namespace data void NetDb::RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete) { - // request RouterInfo directly - auto dest = new RequestedDestination (destination, false); // non-exploratory - dest->SetRequestComplete (requestComplete); + auto dest = m_Requests.CreateRequest (destination, false, requestComplete); // non-exploratory + if (!dest) { - std::unique_lock l(m_RequestedDestinationsMutex); - if (!m_RequestedDestinations.insert (std::make_pair (destination, - std::unique_ptr (dest))).second) // not inserted - { - LogPrint (eLogWarning, "Destination ", destination.ToBase64(), " is requested already"); - return; - } + LogPrint (eLogWarning, "Destination ", destination.ToBase64(), " is requested already"); + return; } - - auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ()); + + auto floodfill = netdb.GetClosestFloodfill (destination, dest->GetExcludedPeers ()); if (floodfill) transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); else { LogPrint (eLogError, "No floodfills found"); - dest->Fail (); - std::unique_lock l(m_RequestedDestinationsMutex); - m_RequestedDestinations.erase (destination); + m_Requests.RequestComplete (destination, nullptr); } } @@ -609,10 +552,10 @@ namespace data key[l] = 0; int num = buf[32]; // num LogPrint ("DatabaseSearchReply for ", key, " num=", num); - auto it = m_RequestedDestinations.find (IdentHash (buf)); - if (it != m_RequestedDestinations.end ()) + IdentHash ident (buf); + auto dest = m_Requests.FindRequest (ident); + if (dest) { - auto& dest = it->second; bool deleteDest = true; if (num > 0) { @@ -659,18 +602,12 @@ namespace data } if (deleteDest) - { // no more requests for the destinationation. delete it - it->second->Fail (); - m_RequestedDestinations.erase (it); - } + m_Requests.RequestComplete (ident, nullptr); } else - { // no more requests for detination possible. delete it - it->second->Fail (); - m_RequestedDestinations.erase (it); - } + m_Requests.RequestComplete (ident, nullptr); } else LogPrint ("Requested destination for ", key, " not found"); @@ -832,15 +769,11 @@ namespace data for (int i = 0; i < numDestinations; i++) { rnd.GenerateBlock (randomHash, 32); - auto dest = new RequestedDestination (randomHash, true); // exploratory - { - std::unique_lock l(m_RequestedDestinationsMutex); - if (!m_RequestedDestinations.insert (std::make_pair (randomHash, - std::unique_ptr (dest))).second) // not inserted - { - LogPrint (eLogWarning, "Exploratory destination is requested already"); - return; - } + auto dest = m_Requests.CreateRequest (randomHash, true); // exploratory + if (!dest) + { + LogPrint (eLogWarning, "Exploratory destination is requested already"); + return; } auto floodfill = GetClosestFloodfill (randomHash, dest->GetExcludedPeers ()); if (floodfill && !floodfills.count (floodfill.get ())) // request floodfill only once @@ -867,10 +800,7 @@ namespace data i2p::transport::transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); } else - { - std::unique_lock l(m_RequestedDestinationsMutex); - m_RequestedDestinations.erase (dest->GetDestination ()); - } + m_Requests.RequestComplete (randomHash, nullptr); } if (throughTunnels && msgs.size () > 0) outbound->SendTunnelDataMsg (msgs); @@ -1028,53 +958,5 @@ namespace data it++; } } - - void NetDb::ManageRequests () - { - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - std::unique_lock l(m_RequestedDestinationsMutex); - for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();) - { - auto& dest = it->second; - bool done = false; - if (ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute - { - if (ts > dest->GetCreationTime () + 5) // no response for 5 seconds - { - auto count = dest->GetExcludedPeers ().size (); - if (!dest->IsExploratory () && count < 7) - { - auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = pool->GetNextOutboundTunnel (); - auto inbound = pool->GetNextInboundTunnel (); - auto nextFloodfill = GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ()); - if (nextFloodfill && outbound && inbound) - outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0, - dest->CreateRequestMessage (nextFloodfill, inbound)); - else - { - done = true; - if (!inbound) LogPrint (eLogWarning, "No inbound tunnels"); - if (!outbound) LogPrint (eLogWarning, "No outbound tunnels"); - if (!nextFloodfill) LogPrint (eLogWarning, "No more floodfills"); - } - } - else - { - if (!dest->IsExploratory ()) - LogPrint (eLogWarning, dest->GetDestination ().ToBase64 (), " not found after 7 attempts"); - done = true; - } - } - } - else // delete obsolete request - done = true; - - if (done) - it = m_RequestedDestinations.erase (it); - else - it++; - } - } } } diff --git a/NetDb.h b/NetDb.h index 7b515e2a..106a529e 100644 --- a/NetDb.h +++ b/NetDb.h @@ -16,44 +16,12 @@ #include "Tunnel.h" #include "TunnelPool.h" #include "Reseed.h" +#include "NetDbRequests.h" namespace i2p { namespace data { - class RequestedDestination - { - public: - - typedef std::function)> RequestComplete; - - RequestedDestination (const IdentHash& destination, bool isExploratory = false): - m_Destination (destination), m_IsExploratory (isExploratory), m_CreationTime (0) {}; - ~RequestedDestination () { if (m_RequestComplete) m_RequestComplete (nullptr); }; - - const IdentHash& GetDestination () const { return m_Destination; }; - int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); }; - const std::set& GetExcludedPeers () { return m_ExcludedPeers; }; - void ClearExcludedPeers (); - bool IsExploratory () const { return m_IsExploratory; }; - bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); }; - uint64_t GetCreationTime () const { return m_CreationTime; }; - I2NPMessage * CreateRequestMessage (std::shared_ptr, std::shared_ptr replyTunnel); - I2NPMessage * CreateRequestMessage (const IdentHash& floodfill); - - void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; }; - bool IsRequestComplete () const { return m_RequestComplete != nullptr; }; - void Success (std::shared_ptr r); - void Fail (); - - private: - - IdentHash m_Destination; - bool m_IsExploratory; - std::set m_ExcludedPeers; - uint64_t m_CreationTime; - RequestComplete m_RequestComplete; - }; class NetDb { @@ -116,8 +84,6 @@ namespace data std::map > m_RouterInfos; mutable std::mutex m_FloodfillsMutex; std::list > m_Floodfills; - std::mutex m_RequestedDestinationsMutex; - std::map > m_RequestedDestinations; bool m_IsRunning; std::thread * m_Thread; @@ -125,6 +91,9 @@ namespace data Reseeder * m_Reseeder; + friend NetDbRequests; + NetDbRequests m_Requests; + static const char m_NetDbPath[]; }; diff --git a/NetDbRequests.cpp b/NetDbRequests.cpp new file mode 100644 index 00000000..5e71e3f7 --- /dev/null +++ b/NetDbRequests.cpp @@ -0,0 +1,149 @@ +#include "Log.h" +#include "I2NPProtocol.h" +#include "Transports.h" +#include "NetDb.h" +#include "NetDbRequests.h" + +namespace i2p +{ +namespace data +{ + I2NPMessage * RequestedDestination::CreateRequestMessage (std::shared_ptr router, + std::shared_ptr replyTunnel) + { + I2NPMessage * msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, + replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory, + &m_ExcludedPeers); + m_ExcludedPeers.insert (router->GetIdentHash ()); + m_CreationTime = i2p::util::GetSecondsSinceEpoch (); + return msg; + } + + I2NPMessage * RequestedDestination::CreateRequestMessage (const IdentHash& floodfill) + { + I2NPMessage * msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, + i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers); + m_ExcludedPeers.insert (floodfill); + m_CreationTime = i2p::util::GetSecondsSinceEpoch (); + return msg; + } + + void RequestedDestination::ClearExcludedPeers () + { + m_ExcludedPeers.clear (); + } + + void RequestedDestination::Success (std::shared_ptr r) + { + if (m_RequestComplete) + { + m_RequestComplete (r); + m_RequestComplete = nullptr; + } + } + + void RequestedDestination::Fail () + { + if (m_RequestComplete) + { + m_RequestComplete (nullptr); + m_RequestComplete = nullptr; + } + } + + void NetDbRequests::Start () + { + } + + void NetDbRequests::Stop () + { + m_RequestedDestinations.clear (); + } + + + std::shared_ptr NetDbRequests::CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete) + { + // request RouterInfo directly + auto dest = std::make_shared (destination, isExploratory); + dest->SetRequestComplete (requestComplete); + { + std::unique_lock l(m_RequestedDestinationsMutex); + if (!m_RequestedDestinations.insert (std::make_pair (destination, + std::shared_ptr (dest))).second) // not inserted + return nullptr; + } + return dest; + } + + void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr r) + { + auto it = m_RequestedDestinations.find (ident); + if (it != m_RequestedDestinations.end ()) + { + if (r) + it->second->Success (r); + else + it->second->Fail (); + std::unique_lock l(m_RequestedDestinationsMutex); + m_RequestedDestinations.erase (it); + } + } + + std::shared_ptr NetDbRequests::FindRequest (const IdentHash& ident) const + { + auto it = m_RequestedDestinations.find (ident); + if (it != m_RequestedDestinations.end ()) + return it->second; + return nullptr; + } + + void NetDbRequests::ManageRequests () + { + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + std::unique_lock l(m_RequestedDestinationsMutex); + for (auto it = m_RequestedDestinations.begin (); it != m_RequestedDestinations.end ();) + { + auto& dest = it->second; + bool done = false; + if (ts < dest->GetCreationTime () + 60) // request is worthless after 1 minute + { + if (ts > dest->GetCreationTime () + 5) // no response for 5 seconds + { + auto count = dest->GetExcludedPeers ().size (); + if (!dest->IsExploratory () && count < 7) + { + auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); + auto outbound = pool->GetNextOutboundTunnel (); + auto inbound = pool->GetNextInboundTunnel (); + auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ()); + if (nextFloodfill && outbound && inbound) + outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0, + dest->CreateRequestMessage (nextFloodfill, inbound)); + else + { + done = true; + if (!inbound) LogPrint (eLogWarning, "No inbound tunnels"); + if (!outbound) LogPrint (eLogWarning, "No outbound tunnels"); + if (!nextFloodfill) LogPrint (eLogWarning, "No more floodfills"); + } + } + else + { + if (!dest->IsExploratory ()) + LogPrint (eLogWarning, dest->GetDestination ().ToBase64 (), " not found after 7 attempts"); + done = true; + } + } + } + else // delete obsolete request + done = true; + + if (done) + it = m_RequestedDestinations.erase (it); + else + it++; + } + } +} +} + diff --git a/NetDbRequests.h b/NetDbRequests.h new file mode 100644 index 00000000..4f7555d4 --- /dev/null +++ b/NetDbRequests.h @@ -0,0 +1,69 @@ +#ifndef NETDB_REQUESTS_H__ +#define NETDB_REQUESTS_H__ + +#include +#include +#include +#include "Identity.h" +#include "RouterInfo.h" + +namespace i2p +{ +namespace data +{ + class RequestedDestination + { + public: + + typedef std::function)> RequestComplete; + + RequestedDestination (const IdentHash& destination, bool isExploratory = false): + m_Destination (destination), m_IsExploratory (isExploratory), m_CreationTime (0) {}; + ~RequestedDestination () { if (m_RequestComplete) m_RequestComplete (nullptr); }; + + const IdentHash& GetDestination () const { return m_Destination; }; + int GetNumExcludedPeers () const { return m_ExcludedPeers.size (); }; + const std::set& GetExcludedPeers () { return m_ExcludedPeers; }; + void ClearExcludedPeers (); + bool IsExploratory () const { return m_IsExploratory; }; + bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); }; + uint64_t GetCreationTime () const { return m_CreationTime; }; + I2NPMessage * CreateRequestMessage (std::shared_ptr, std::shared_ptr replyTunnel); + I2NPMessage * CreateRequestMessage (const IdentHash& floodfill); + + void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; }; + bool IsRequestComplete () const { return m_RequestComplete != nullptr; }; + void Success (std::shared_ptr r); + void Fail (); + + private: + + IdentHash m_Destination; + bool m_IsExploratory; + std::set m_ExcludedPeers; + uint64_t m_CreationTime; + RequestComplete m_RequestComplete; + }; + + class NetDbRequests + { + public: + + void Start (); + void Stop (); + + std::shared_ptr CreateRequest (const IdentHash& destination, bool isExploratory, RequestedDestination::RequestComplete requestComplete = nullptr); + void RequestComplete (const IdentHash& ident, std::shared_ptr r); + std::shared_ptr FindRequest (const IdentHash& ident) const; + void ManageRequests (); + + private: + + std::mutex m_RequestedDestinationsMutex; + std::map > m_RequestedDestinations; + }; +} +} + +#endif + diff --git a/Win32/i2pd.vcxproj b/Win32/i2pd.vcxproj index 0eb82f3d..a8f3b486 100644 --- a/Win32/i2pd.vcxproj +++ b/Win32/i2pd.vcxproj @@ -37,6 +37,7 @@ + @@ -81,6 +82,7 @@ + diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 4fb0ad37..5adb0f01 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -21,6 +21,7 @@ set (COMMON_SRC "${CMAKE_SOURCE_DIR}/LeaseSet.cpp" "${CMAKE_SOURCE_DIR}/Log.cpp" "${CMAKE_SOURCE_DIR}/NTCPSession.cpp" + "${CMAKE_SOURCE_DIR}/NetDbRequests.cpp" "${CMAKE_SOURCE_DIR}/NetDb.cpp" "${CMAKE_SOURCE_DIR}/Profiling.cpp" "${CMAKE_SOURCE_DIR}/Reseed.cpp" diff --git a/build/autotools/Makefile.in b/build/autotools/Makefile.in index 7bee7218..31a73cab 100644 --- a/build/autotools/Makefile.in +++ b/build/autotools/Makefile.in @@ -328,7 +328,8 @@ i2p_SOURCES = AddressBook.cpp CryptoConst.cpp Daemon.cpp \ TunnelGateway.cpp TunnelPool.cpp UPnP.cpp aes.cpp \ base64.cpp i2p.cpp util.cpp SAM.cpp Destination.cpp \ ClientContext.cpp DataFram.cpp SSUSession.cpp BOB.cpp \ - I2PControl.cpp Profiling.cpp Signature.cpp \ + I2PControl.cpp Profiling.cpp Signature.cpp \ + NetDbRequests.cpp \ \ AddressBook.h CryptoConst.h Daemon.h ElGamal.h \ Garlic.h HTTPProxy.h HTTPServer.h I2NPProtocol.h \ @@ -341,7 +342,7 @@ i2p_SOURCES = AddressBook.cpp CryptoConst.cpp Daemon.cpp \ TunnelPool.h UPnP.h aes.h base64.h config.h hmac.h \ util.h version.h Destination.h ClientContext.h \ TransportSession.h Datagram.h SSUSession.h BOB.h \ - I2PControl.h Profiling.h + I2PControl.h Profiling.h NetDbRequests.h AM_LDFLAGS = @BOOST_DATE_TIME_LIB@ @BOOST_FILESYSTEM_LIB@ \ @BOOST_PROGRAM_OPTIONS_LIB@ @BOOST_REGEX_LIB@ \ @@ -495,6 +496,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SSUSession.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Profiling.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Signature.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NetDbRequests.Po@am__quote@ .cpp.o: @am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< diff --git a/filelist.mk b/filelist.mk index 48298bc5..623a1b33 100644 --- a/filelist.mk +++ b/filelist.mk @@ -1,10 +1,10 @@ COMMON_SRC = \ CryptoConst.cpp Datagram.cpp Garlic.cpp I2NPProtocol.cpp LeaseSet.cpp \ - Log.cpp NTCPSession.cpp NetDb.cpp Profiling.cpp Reseed.cpp RouterContext.cpp \ - RouterInfo.cpp Signature.cpp SSU.cpp SSUSession.cpp SSUData.cpp Streaming.cpp \ - Identity.cpp TransitTunnel.cpp Transports.cpp Tunnel.cpp TunnelEndpoint.cpp \ - TunnelPool.cpp TunnelGateway.cpp Destination.cpp UPnP.cpp util.cpp aes.cpp \ - base64.cpp + Log.cpp NTCPSession.cpp NetDb.cpp NetDbRequests.cpp Profiling.cpp \ + Reseed.cpp RouterContext.cpp RouterInfo.cpp Signature.cpp SSU.cpp \ + SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp TransitTunnel.cpp \ + Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp TunnelGateway.cpp \ + Destination.cpp UPnP.cpp util.cpp aes.cpp base64.cpp ifeq ($(UNAME),Darwin) From 451b0382eaef2d3f1448aa1f8c5849b3997a8abb Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 9 Apr 2015 15:07:25 -0400 Subject: [PATCH 0401/6300] implemented AsyncSend --- Streaming.cpp | 19 +++++++++++++++++++ Streaming.h | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/Streaming.cpp b/Streaming.cpp index 5f110fd5..6f431264 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -61,6 +61,11 @@ namespace stream m_AckSendTimer.cancel (); m_ReceiveTimer.cancel (); m_ResendTimer.cancel (); + if (m_SendHandler) + { + m_SendHandler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted)); + m_SendHandler = nullptr; + } } void Stream::HandleNextPacket (Packet * packet) @@ -299,6 +304,15 @@ namespace stream return len; } + void Stream::AsyncSend (const uint8_t * buf, size_t len, SendHandler handler) + { + if (m_SendHandler) + handler (boost::asio::error::make_error_code (boost::asio::error::in_progress)); + else + m_SendHandler = handler; + Send (buf, len); + } + void Stream::SendBuffer () { int numMsgs = m_WindowSize - m_SentPackets.size (); @@ -367,6 +381,11 @@ namespace stream packets.push_back (p); numMsgs--; } + if (m_SendBuffer.eof () && m_SendHandler) + { + m_SendHandler (boost::system::error_code ()); + m_SendHandler = nullptr; + } } if (packets.size () > 0) { diff --git a/Streaming.h b/Streaming.h index ff1527f9..88268249 100644 --- a/Streaming.h +++ b/Streaming.h @@ -97,6 +97,8 @@ namespace stream { public: + typedef std::function SendHandler; + Stream (boost::asio::io_service& service, StreamingDestination& local, std::shared_ptr remote, int port = 0); // outgoing Stream (boost::asio::io_service& service, StreamingDestination& local); // incoming @@ -112,6 +114,7 @@ namespace stream void HandleNextPacket (Packet * packet); size_t Send (const uint8_t * buf, size_t len); + void AsyncSend (const uint8_t * buf, size_t len, SendHandler handler); template void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0); @@ -179,6 +182,7 @@ namespace stream int m_WindowSize, m_RTT, m_RTO; uint64_t m_LastWindowSizeIncreaseTime; int m_NumResendAttempts; + SendHandler m_SendHandler; }; class StreamingDestination From da006a1d6efb4d42495838a7800dc44f8f0465eb Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 9 Apr 2015 18:40:23 -0400 Subject: [PATCH 0402/6300] use AsyncSend --- I2PTunnel.cpp | 13 +++++++++++-- SAM.cpp | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 7a2e181e..4be5517b 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -85,8 +85,17 @@ namespace client else { if (m_Stream) - m_Stream->Send (m_Buffer, bytes_transferred); - Receive (); + { + auto s = shared_from_this (); + m_Stream->AsyncSend (m_Buffer, bytes_transferred, + [s](const boost::system::error_code& ecode) + { + if (!ecode) + s->Receive (); + else + s->Terminate (); + }); + } } } diff --git a/SAM.cpp b/SAM.cpp index c241641e..971ed45e 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -558,8 +558,17 @@ namespace client else { if (m_Stream) - m_Stream->Send ((uint8_t *)m_Buffer, bytes_transferred); - Receive (); + { + auto s = shared_from_this (); + m_Stream->AsyncSend ((uint8_t *)m_Buffer, bytes_transferred, + [s](const boost::system::error_code& ecode) + { + if (!ecode) + s->Receive (); + else + s->Terminate (); + }); + } } } From 11177d37ea531c008cdbfcf46900f25bd93c7ece Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 9 Apr 2015 21:09:30 -0400 Subject: [PATCH 0403/6300] send and handle RESET flag --- Streaming.cpp | 10 +++++----- Streaming.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 6f431264..c7b38f51 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -222,9 +222,9 @@ namespace stream m_LastReceivedSequenceNumber = receivedSeqn; - if (flags & PACKET_FLAG_CLOSE) + if (flags & (PACKET_FLAG_CLOSE | PACKET_FLAG_RESET)) { - LogPrint (eLogInfo, "Closed"); + LogPrint (eLogInfo, (flags & PACKET_FLAG_RESET) ? "Reset" : "Closed"); m_Status = eStreamStatusReset; Close (); } @@ -486,7 +486,7 @@ namespace stream LogPrint (eLogInfo, "Trying to send stream data before closing"); break; case eStreamStatusReset: - SendClose (); + SendClose (true); // send reset Terminate (); m_LocalDestination.DeleteStream (shared_from_this ()); break; @@ -509,7 +509,7 @@ namespace stream }; } - void Stream::SendClose () + void Stream::SendClose (bool reset) { Packet * p = new Packet (); uint8_t * packet = p->GetBuffer (); @@ -525,7 +525,7 @@ namespace stream packet[size] = 0; size++; // NACK count size++; // resend delay - htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); + htobe16buf (packet + size, (reset ? PACKET_FLAG_RESET : PACKET_FLAG_CLOSE) | PACKET_FLAG_SIGNATURE_INCLUDED); size += 2; // flags size_t signatureLen = m_LocalDestination.GetOwner ().GetIdentity ().GetSignatureLen (); htobe16buf (packet + size, signatureLen); // signature only diff --git a/Streaming.h b/Streaming.h index 88268249..a3619627 100644 --- a/Streaming.h +++ b/Streaming.h @@ -137,7 +137,7 @@ namespace stream void SendBuffer (); void SendQuickAck (); - void SendClose (); + void SendClose (bool reset = false); bool SendPacket (Packet * packet); void SendPackets (const std::vector& packets); From 20310cb1099b0c72cb446a3dab88f59d54378c3d Mon Sep 17 00:00:00 2001 From: Yuri Sevatz Date: Fri, 10 Apr 2015 00:10:35 -0400 Subject: [PATCH 0404/6300] Fix -lboost_date_time missing from CMakeLists.txt --- build/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 5adb0f01..c91eb703 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -124,7 +124,7 @@ endif() # libraries find_package ( Threads REQUIRED ) -find_package ( Boost COMPONENTS system filesystem regex program_options REQUIRED ) +find_package ( Boost COMPONENTS system filesystem regex program_options date_time REQUIRED ) if(NOT DEFINED Boost_INCLUDE_DIRS) message(SEND_ERROR "Boost is not found, or your boost version was bellow 1.46. Please download Boost!") endif() From b29e94005d4568c7b295923f18038d7eadb5ae59 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Apr 2015 09:58:08 -0400 Subject: [PATCH 0405/6300] fixed crash --- BOB.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index 86f652f8..f5f11d39 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -469,7 +469,7 @@ namespace client { LogPrint (eLogDebug, "BOB: lookup ", operand); i2p::data::IdentHash ident; - if (!context.GetAddressBook ().GetIdentHash (operand, ident) && !m_CurrentDestination) + if (!context.GetAddressBook ().GetIdentHash (operand, ident) || !m_CurrentDestination) { SendReplyError ("Address Not found"); return; @@ -481,8 +481,8 @@ namespace client else { auto s = shared_from_this (); - m_CurrentDestination->GetLocalDestination ()->RequestDestination (ident, - [s, localDestination](std::shared_ptr ls) + localDestination->RequestDestination (ident, + [s](std::shared_ptr ls) { if (ls) s->SendReplyOK (ls->GetIdentity ().ToBase64 ().c_str ()); From 51b850aa850bdd1141a08f378914cea81b34bc2c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Apr 2015 11:52:14 -0400 Subject: [PATCH 0406/6300] show windows size and connection status --- HTTPServer.cpp | 4 +++- Streaming.h | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index b99a748c..8cdc7453 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -878,7 +878,9 @@ namespace util s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]"; s << "[buf:" << it.second->GetSendBufferSize () << "]"; - s << "[RTT:" << it.second->GetRTT () << "]"; + s << "[RTT:" << it.second->GetRTT () << "]"; + s << "[Window:" << it.second->GetWindowSize () << "]"; + s << "[Status:" << (int)it.second->GetStatus () << "]"; s << "
"<< std::endl; } } diff --git a/Streaming.h b/Streaming.h index a3619627..9c32a3c2 100644 --- a/Streaming.h +++ b/Streaming.h @@ -85,7 +85,7 @@ namespace stream enum StreamStatus { - eStreamStatusNew, + eStreamStatusNew = 0, eStreamStatusOpen, eStreamStatusReset, eStreamStatusClosing, @@ -108,8 +108,9 @@ namespace stream uint32_t GetRecvStreamID () const { return m_RecvStreamID; }; std::shared_ptr GetRemoteLeaseSet () const { return m_RemoteLeaseSet; }; const i2p::data::IdentityEx& GetRemoteIdentity () const { return m_RemoteIdentity; }; - bool IsOpen () const { return m_Status == eStreamStatusOpen; }; + bool IsOpen () const { return m_Status == eStreamStatusOpen; }; bool IsEstablished () const { return m_SendStreamID; }; + StreamStatus GetStatus () const { return m_Status; }; StreamingDestination& GetLocalDestination () { return m_LocalDestination; }; void HandleNextPacket (Packet * packet); From 7c660ee55669eb502ea1917237dcbf86d0da6fea Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Apr 2015 12:11:10 -0400 Subject: [PATCH 0407/6300] show local destination for SAM sessions --- HTTPServer.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 8cdc7453..c9048727 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -830,17 +830,17 @@ namespace util { for (auto& it: i2p::client::context.GetDestinations ()) { - std::string b32 = it.first.ToBase32 (); + auto ident = it.second->GetIdentHash ();; s << ""; - s << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetIdentHash()) << "
" << std::endl; + s << "&" << HTTP_PARAM_BASE32_ADDRESS << "=" << ident.ToBase32 () << ">"; + s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
" << std::endl; } } void HTTPConnection::ShowLocalDestination (const std::string& b32, std::stringstream& s) { i2p::data::IdentHash ident; - i2p::data::Base32ToByteStream (b32.c_str (), b32.length (), ident, 32); + ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); if (dest) { @@ -908,6 +908,11 @@ namespace util auto session = sam->FindSession (id); if (session) { + auto& ident = session->localDestination->GetIdentHash(); + s << ""; + s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
" << std::endl; + s << "Streams:
"; for (auto it: session->sockets) { switch (it->GetSocketType ()) From 2741e94a729c216e2c6a15442a81defc1b644f83 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Apr 2015 13:19:23 -0400 Subject: [PATCH 0408/6300] fixed infinite loop --- Streaming.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index c7b38f51..a8f5a919 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -63,8 +63,9 @@ namespace stream m_ResendTimer.cancel (); if (m_SendHandler) { - m_SendHandler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted)); + auto handler = m_SendHandler; m_SendHandler = nullptr; + handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted)); } } From 2a997d94bf37dd12c94e41fdb9ef48af42d0fc21 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Apr 2015 16:15:13 -0400 Subject: [PATCH 0409/6300] GetClosestFloodfills added --- NetDb.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ NetDb.h | 1 + 2 files changed, 45 insertions(+) diff --git a/NetDb.cpp b/NetDb.cpp index c5d0fde8..b5868600 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -922,6 +922,50 @@ namespace data return r; } + std::vector NetDb::GetClosestFloodfills (const IdentHash& destination, size_t num) const + { + struct Sorted + { + std::shared_ptr r; + XORMetric metric; + bool operator< (const Sorted& other) const { return metric < other.metric; }; + }; + + std::set sorted; + IdentHash destKey = CreateRoutingKey (destination); + { + std::unique_lock l(m_FloodfillsMutex); + for (auto it: m_Floodfills) + { + if (!it->IsUnreachable ()) + { + XORMetric m = destKey ^ it->GetIdentHash (); + if (sorted.size () < num) + sorted.insert ({it, m}); + else if (m < sorted.rbegin ()->metric) + { + sorted.insert ({it, m}); + sorted.erase (std::prev (sorted.end ())); + } + } + } + } + + std::vector res; + size_t i = 0; + for (auto it: sorted) + { + if (i < num) + { + res.push_back (it.r->GetIdentHash ()); + i++; + } + else + break; + } + return res; + } + std::shared_ptr NetDb::GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const { diff --git a/NetDb.h b/NetDb.h index 106a529e..44efa454 100644 --- a/NetDb.h +++ b/NetDb.h @@ -51,6 +51,7 @@ namespace data std::shared_ptr GetRandomPeerTestRouter () const; std::shared_ptr GetRandomIntroducer () const; std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded) const; + std::vector GetClosestFloodfills (const IdentHash& destination, size_t num) const; std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; void SetUnreachable (const IdentHash& ident, bool unreachable); From 9072a018dd57f804bdd6d077c4f3d3994a087d1c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Apr 2015 18:13:11 -0400 Subject: [PATCH 0410/6300] reduced CPU load at floodfill --- NetDb.cpp | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index b5868600..1fc0abe6 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -704,22 +704,27 @@ namespace data if (!replyMsg) { LogPrint ("Requested ", key, " not found. ", numExcluded, " excluded"); - std::set excludedRouters; - for (int i = 0; i < numExcluded; i++) - { - excludedRouters.insert (excluded); - excluded += 32; - } std::vector routers; - for (int i = 0; i < 3; i++) - { - auto floodfill = GetClosestFloodfill (buf, excludedRouters); - if (floodfill) - { - routers.push_back (floodfill->GetIdentHash ()); - excludedRouters.insert (floodfill->GetIdentHash ()); + if (numExcluded > 0) + { + std::set excludedRouters; + for (int i = 0; i < numExcluded; i++) + { + excludedRouters.insert (excluded); + excluded += 32; + } + for (int i = 0; i < 3; i++) + { + auto floodfill = GetClosestFloodfill (buf, excludedRouters); + if (floodfill) + { + routers.push_back (floodfill->GetIdentHash ()); + excludedRouters.insert (floodfill->GetIdentHash ()); + } } } + else + routers = GetClosestFloodfills (buf, 3); replyMsg = CreateDatabaseSearchReply (buf, routers); } } From 1d2950b4a75350b567b50c7f737a7f2bc9de2ff6 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Apr 2015 19:49:58 -0400 Subject: [PATCH 0411/6300] reduced CPU load at floodfill --- NetDb.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 1fc0abe6..293905f9 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -445,7 +445,7 @@ namespace data return; } - auto floodfill = netdb.GetClosestFloodfill (destination, dest->GetExcludedPeers ()); + auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ()); if (floodfill) transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); else @@ -914,10 +914,10 @@ namespace data std::unique_lock l(m_FloodfillsMutex); for (auto it: m_Floodfills) { - if (!it->IsUnreachable () && !excluded.count (it->GetIdentHash ())) + if (!it->IsUnreachable ()) { XORMetric m = destKey ^ it->GetIdentHash (); - if (m < minMetric) + if (m < minMetric && !excluded.count (it->GetIdentHash ())) { minMetric = m; r = it; @@ -981,10 +981,10 @@ namespace data // must be called from NetDb thread only for (auto it: m_RouterInfos) { - if (!it.second->IsFloodfill () && !excluded.count (it.first)) + if (!it.second->IsFloodfill ()) { XORMetric m = destKey ^ it.first; - if (m < minMetric) + if (m < minMetric && !excluded.count (it.first)) { minMetric = m; r = it.second; From 624bff3036557ee27cf20683104d469ac9086296 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Apr 2015 19:58:32 -0400 Subject: [PATCH 0412/6300] reduced log file size --- TransitTunnel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index ae11b286..a3135db1 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -45,7 +45,9 @@ namespace tunnel { if (!m_TunnelDataMsgs.empty ()) { - LogPrint (eLogDebug, "TransitTunnel: ",GetTunnelID (),"->", GetNextTunnelID (), " ", m_TunnelDataMsgs.size ()); + auto num = m_TunnelDataMsgs.size (); + if (num > 1) + LogPrint (eLogDebug, "TransitTunnel: ",GetTunnelID (),"->", GetNextTunnelID (), " ", num); i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); m_TunnelDataMsgs.clear (); } From f0f154cd105de6c475886afb1a27e25053dbd000 Mon Sep 17 00:00:00 2001 From: 7histle Date: Sat, 11 Apr 2015 13:47:49 +0300 Subject: [PATCH 0413/6300] Fix for #179 --- build/BUILD_NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/build/BUILD_NOTES.md b/build/BUILD_NOTES.md index aea939da..be0730dd 100644 --- a/build/BUILD_NOTES.md +++ b/build/BUILD_NOTES.md @@ -24,6 +24,7 @@ Required "-dev" packages: * libboost-program-options-dev * libboost-regex-dev * libboost-system-dev +* libboost-date-time-dev * libcrypto++-dev FreeBSD From 128a8f3b486c8ff803b7ce51b144694ffe343b7c Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 11 Apr 2015 15:39:23 -0400 Subject: [PATCH 0414/6300] delete obsolete profiles --- NetDb.cpp | 29 +++++++++++++++++------------ Profiling.cpp | 31 +++++++++++++++++++++++++++++-- Profiling.h | 3 +++ 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 293905f9..39a5b4ff 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -65,19 +65,24 @@ namespace data void NetDb::Stop () { - for (auto it: m_RouterInfos) - it.second->SaveProfile (); - m_RouterInfos.clear (); - if (m_Thread) + if (m_IsRunning) { - m_IsRunning = false; - m_Queue.WakeUp (); - m_Thread->join (); - delete m_Thread; - m_Thread = 0; - } - m_LeaseSets.clear(); - m_Requests.Stop (); + for (auto it: m_RouterInfos) + it.second->SaveProfile (); + DeleteObsoleteProfiles (); + m_RouterInfos.clear (); + m_Floodfills.clear (); + if (m_Thread) + { + m_IsRunning = false; + m_Queue.WakeUp (); + m_Thread->join (); + delete m_Thread; + m_Thread = 0; + } + m_LeaseSets.clear(); + m_Requests.Stop (); + } } void NetDb::Run () diff --git a/Profiling.cpp b/Profiling.cpp index b572f3bb..027635ae 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -95,7 +95,7 @@ namespace data auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, ""); if (t.length () > 0) m_LastUpdateTime = boost::posix_time::time_from_string (t); - if ((GetTime () - m_LastUpdateTime).hours () < 72) // profile becomes obsolete after 3 days of inactivity + if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT) { // read participations auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION); @@ -133,7 +133,7 @@ namespace data if ((GetTime () - m_LastUpdateTime).total_seconds () < 900) // if less than 15 minutes return m_NumTunnelsAgreed < m_NumTunnelsDeclined; // 50% rate else - return 4*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // 20% rate + return 3*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // 25% rate } bool RouterProfile::IsBad () const @@ -147,5 +147,32 @@ namespace data profile->Load (); // if possible return profile; } + + void DeleteObsoleteProfiles () + { + int num = 0; + auto ts = boost::posix_time::second_clock::local_time(); + boost::filesystem::path p (i2p::util::filesystem::GetDataDir()/PEER_PROFILES_DIRECTORY); + if (boost::filesystem::exists (p)) + { + boost::filesystem::directory_iterator end; + for (boost::filesystem::directory_iterator it (p); it != end; ++it) + { + if (boost::filesystem::is_directory (it->status())) + { + for (boost::filesystem::directory_iterator it1 (it->path ()); it1 != end; ++it1) + { + auto lastModified = boost::posix_time::from_time_t (boost::filesystem::last_write_time (it1->path ())); + if ((ts - lastModified).hours () >= PEER_PROFILE_EXPIRATION_TIMEOUT) + { + boost::filesystem::remove (it1->path ()); + num++; + } + } + } + } + } + LogPrint (eLogInfo, num, " obsolete profiles deleted"); + } } } \ No newline at end of file diff --git a/Profiling.h b/Profiling.h index 9ff03f4b..d16f8954 100644 --- a/Profiling.h +++ b/Profiling.h @@ -18,6 +18,8 @@ namespace data const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed"; const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined"; const char PEER_PROFILE_PARTICIPATION_NON_REPLIED[] = "nonreplied"; + + const int PEER_PROFILE_EXPIRATION_TIMEOUT = 72; // in hours (3 days) class RouterProfile { @@ -54,6 +56,7 @@ namespace data }; std::shared_ptr GetRouterProfile (const IdentHash& identHash); + void DeleteObsoleteProfiles (); } } From 562de3514a816d51c392563a6feecc50e610c42b Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 12 Apr 2015 15:54:28 -0400 Subject: [PATCH 0415/6300] check database lookup type --- NetDb.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 39a5b4ff..7cf0498e 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -688,16 +688,21 @@ namespace data } else { - auto router = FindRouter (buf); - if (router) - { - LogPrint ("Requested RouterInfo ", key, " found"); - router->LoadBuffer (); - if (router->GetBuffer ()) - replyMsg = CreateDatabaseStoreMsg (router); + if (lookupType == DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP || + lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP) + { + auto router = FindRouter (buf); + if (router) + { + LogPrint ("Requested RouterInfo ", key, " found"); + router->LoadBuffer (); + if (router->GetBuffer ()) + replyMsg = CreateDatabaseStoreMsg (router); + } } - - if (!replyMsg) + + if (!replyMsg && (lookupType == DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP || + lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP)) { auto leaseSet = FindLeaseSet (buf); if (leaseSet) // we don't send back our LeaseSets @@ -706,6 +711,7 @@ namespace data replyMsg = CreateDatabaseStoreMsg (leaseSet); } } + if (!replyMsg) { LogPrint ("Requested ", key, " not found. ", numExcluded, " excluded"); From ce99357ebe314954c428bbc82335f1b33b34a470 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 12 Apr 2015 16:59:59 -0400 Subject: [PATCH 0416/6300] check for zero ident --- Identity.h | 7 +++++++ NetDb.cpp | 34 ++++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/Identity.h b/Identity.h index 5859f988..02752d99 100644 --- a/Identity.h +++ b/Identity.h @@ -41,6 +41,13 @@ namespace data bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); }; bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; }; + bool IsZero () const + { + for (int i = 0; i < sz/8; i++) + if (ll[i]) return false; + return true; + } + std::string ToBase64 () const { char str[sz*2]; diff --git a/NetDb.cpp b/NetDb.cpp index 7cf0498e..b8eb14ba 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -464,6 +464,13 @@ namespace data { const uint8_t * buf = m->GetPayload (); size_t len = m->GetSize (); + IdentHash ident (buf + DATABASE_STORE_KEY_OFFSET); + if (ident.IsZero ()) + { + LogPrint (eLogError, "Database store with zero ident. Dropped"); + i2p::DeleteI2NPMessage (m); + return; + } uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET); size_t offset = DATABASE_STORE_HEADER_SIZE; if (replyToken) @@ -493,7 +500,7 @@ namespace data std::set excluded; for (int i = 0; i < 3; i++) { - auto floodfill = GetClosestFloodfill (buf + DATABASE_STORE_KEY_OFFSET, excluded); + auto floodfill = GetClosestFloodfill (ident, excluded); if (floodfill) { excluded.insert (floodfill->GetIdentHash ()); @@ -513,7 +520,7 @@ namespace data if (buf[DATABASE_STORE_TYPE_OFFSET]) // type { LogPrint ("LeaseSet"); - AddLeaseSet (buf + DATABASE_STORE_KEY_OFFSET, buf + offset, len - offset, m->from); + AddLeaseSet (ident, buf + offset, len - offset, m->from); } else { @@ -536,7 +543,7 @@ namespace data if (uncomressedSize <= 2048) { decompressor.Get (uncompressed, uncomressedSize); - AddRouterInfo (buf + DATABASE_STORE_KEY_OFFSET, uncompressed, uncomressedSize); + AddRouterInfo (ident, uncompressed, uncomressedSize); } else LogPrint ("Invalid RouterInfo uncomressed length ", (int)uncomressedSize); @@ -643,6 +650,13 @@ namespace data void NetDb::HandleDatabaseLookupMsg (I2NPMessage * msg) { uint8_t * buf = msg->GetPayload (); + IdentHash ident (buf); + if (ident.IsZero ()) + { + LogPrint (eLogError, "DatabaseLookup for zero ident. Ignored"); + i2p::DeleteI2NPMessage (msg); + return; + } char key[48]; int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48); key[l] = 0; @@ -677,21 +691,21 @@ namespace data std::vector routers; for (int i = 0; i < 3; i++) { - auto r = GetClosestNonFloodfill (buf, excludedRouters); + auto r = GetClosestNonFloodfill (ident, excludedRouters); if (r) { routers.push_back (r->GetIdentHash ()); excludedRouters.insert (r->GetIdentHash ()); } } - replyMsg = CreateDatabaseSearchReply (buf, routers); + replyMsg = CreateDatabaseSearchReply (ident, routers); } else { if (lookupType == DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP || lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP) { - auto router = FindRouter (buf); + auto router = FindRouter (ident); if (router) { LogPrint ("Requested RouterInfo ", key, " found"); @@ -704,7 +718,7 @@ namespace data if (!replyMsg && (lookupType == DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP || lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP)) { - auto leaseSet = FindLeaseSet (buf); + auto leaseSet = FindLeaseSet (ident); if (leaseSet) // we don't send back our LeaseSets { LogPrint ("Requested LeaseSet ", key, " found"); @@ -726,7 +740,7 @@ namespace data } for (int i = 0; i < 3; i++) { - auto floodfill = GetClosestFloodfill (buf, excludedRouters); + auto floodfill = GetClosestFloodfill (ident, excludedRouters); if (floodfill) { routers.push_back (floodfill->GetIdentHash ()); @@ -735,8 +749,8 @@ namespace data } } else - routers = GetClosestFloodfills (buf, 3); - replyMsg = CreateDatabaseSearchReply (buf, routers); + routers = GetClosestFloodfills (ident, 3); + replyMsg = CreateDatabaseSearchReply (ident, routers); } } From c873e9dd6873786eaf19a9328ece81d8c2bf89a5 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 13 Apr 2015 11:38:23 -0400 Subject: [PATCH 0417/6300] don't send reset message due problem at other side --- Streaming.cpp | 6 +++--- Streaming.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index a8f5a919..c26c6b40 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -487,7 +487,7 @@ namespace stream LogPrint (eLogInfo, "Trying to send stream data before closing"); break; case eStreamStatusReset: - SendClose (true); // send reset + SendClose (); Terminate (); m_LocalDestination.DeleteStream (shared_from_this ()); break; @@ -510,7 +510,7 @@ namespace stream }; } - void Stream::SendClose (bool reset) + void Stream::SendClose () { Packet * p = new Packet (); uint8_t * packet = p->GetBuffer (); @@ -526,7 +526,7 @@ namespace stream packet[size] = 0; size++; // NACK count size++; // resend delay - htobe16buf (packet + size, (reset ? PACKET_FLAG_RESET : PACKET_FLAG_CLOSE) | PACKET_FLAG_SIGNATURE_INCLUDED); + htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); size += 2; // flags size_t signatureLen = m_LocalDestination.GetOwner ().GetIdentity ().GetSignatureLen (); htobe16buf (packet + size, signatureLen); // signature only diff --git a/Streaming.h b/Streaming.h index 9c32a3c2..5e397c62 100644 --- a/Streaming.h +++ b/Streaming.h @@ -138,7 +138,7 @@ namespace stream void SendBuffer (); void SendQuickAck (); - void SendClose (bool reset = false); + void SendClose (); bool SendPacket (Packet * packet); void SendPackets (const std::vector& packets); From 76c54ffdef43e5d95ae94eb947c84c6d36204183 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 13 Apr 2015 18:41:19 -0400 Subject: [PATCH 0418/6300] always check profile for peer selection --- NetDb.cpp | 3 ++- Profiling.cpp | 16 +++++++++++++--- Profiling.h | 4 ++-- TunnelPool.cpp | 5 ----- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index b8eb14ba..f07d17ba 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -912,7 +912,8 @@ namespace data { if (i >= ind) { - if (!it.second->IsUnreachable () && filter (it.second)) + if (!it.second->IsUnreachable () && filter (it.second) && + (j || !it.second->GetProfile ()->IsBad ())) return it.second; } else diff --git a/Profiling.cpp b/Profiling.cpp index 027635ae..bac162e0 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -128,17 +128,27 @@ namespace data UpdateTime (); } - bool RouterProfile::IsLowPartcipationRate () const + bool RouterProfile::IsLowPartcipationRate (int elapsedTime) const { - if ((GetTime () - m_LastUpdateTime).total_seconds () < 900) // if less than 15 minutes + if (elapsedTime < 900) // if less than 15 minutes return m_NumTunnelsAgreed < m_NumTunnelsDeclined; // 50% rate else return 3*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // 25% rate } + + bool RouterProfile::IsLowReplyRate (int elapsedTime) const + { + auto total = m_NumTunnelsAgreed + m_NumTunnelsDeclined; + if (elapsedTime < 300) // if less than 5 minutes + return m_NumTunnelsNonReplied > 10*total; + else + return !total && m_NumTunnelsNonReplied > 20; + } bool RouterProfile::IsBad () const { - return IsAlwaysDeclining () || IsNonResponding () || IsLowPartcipationRate (); + auto elapsedTime = (GetTime () - m_LastUpdateTime).total_seconds (); + return IsAlwaysDeclining () || IsLowPartcipationRate (elapsedTime) || IsLowReplyRate (elapsedTime); } std::shared_ptr GetRouterProfile (const IdentHash& identHash) diff --git a/Profiling.h b/Profiling.h index d16f8954..93fb5be2 100644 --- a/Profiling.h +++ b/Profiling.h @@ -42,8 +42,8 @@ namespace data void UpdateTime (); bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; }; - bool IsNonResponding () const { return m_NumTunnelsNonReplied > 20 && !(m_NumTunnelsAgreed + m_NumTunnelsDeclined); }; - bool IsLowPartcipationRate () const; + bool IsLowPartcipationRate (int elapsedTime) const; + bool IsLowReplyRate (int elapsedTime) const; private: diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 15606959..e412d09c 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -263,11 +263,6 @@ namespace tunnel bool isExploratory = (m_LocalDestination == &i2p::context); // TODO: implement it better auto hop = isExploratory ? i2p::data::netdb.GetRandomRouter (prevHop): i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop); - if (!isExploratory && hop && hop->GetProfile ()->IsBad ()) - { - LogPrint (eLogInfo, "Selected peer for tunnel has bad profile. Selecting another"); - hop = i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop); - } if (!hop) hop = i2p::data::netdb.GetRandomRouter (); From 4d27399ce33e7b9bdc409e7cabac8a3491f3ed46 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 13 Apr 2015 18:51:31 -0400 Subject: [PATCH 0419/6300] check profile for high bandwidth peer selection only --- NetDb.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index f07d17ba..e26440dd 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -895,7 +895,9 @@ namespace data [compatibleWith](std::shared_ptr router)->bool { return !router->IsHidden () && router != compatibleWith && - router->IsCompatible (*compatibleWith) && (router->GetCaps () & RouterInfo::eHighBandwidth); + router->IsCompatible (*compatibleWith) && + (router->GetCaps () & RouterInfo::eHighBandwidth) && + !router->GetProfile ()->IsBad (); }); } @@ -912,8 +914,7 @@ namespace data { if (i >= ind) { - if (!it.second->IsUnreachable () && filter (it.second) && - (j || !it.second->GetProfile ()->IsBad ())) + if (!it.second->IsUnreachable () && filter (it.second)) return it.second; } else From 864aba9f4ea80bf373ad388607a17796ec07a58a Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 13 Apr 2015 18:54:13 -0400 Subject: [PATCH 0420/6300] version 0.9.19 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index e6fee5ff..6ce0cfd7 100644 --- a/version.h +++ b/version.h @@ -3,6 +3,6 @@ #define CODENAME "Purple" #define VERSION "0.9.0" -#define I2P_VERSION "0.9.18" +#define I2P_VERSION "0.9.19" #endif From 5d2f9f9f0b075859f2adc1034b98b8c5869bf90f Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 14 Apr 2015 10:40:46 -0400 Subject: [PATCH 0421/6300] fixed potential memory leak --- Transports.cpp | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 104df4a8..3270c174 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -227,10 +227,19 @@ namespace transport auto it = m_Peers.find (ident); if (it == m_Peers.end ()) { - auto r = netdb.FindRouter (ident); - it = m_Peers.insert (std::pair(ident, { 0, r, nullptr, - i2p::util::GetSecondsSinceEpoch () })).first; - if (!ConnectToPeer (ident, it->second)) + bool connected = false; + try + { + auto r = netdb.FindRouter (ident); + it = m_Peers.insert (std::pair(ident, { 0, r, nullptr, + i2p::util::GetSecondsSinceEpoch () })).first; + connected= ConnectToPeer (ident, it->second); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Transports::PostMessage ", ex.what ()); + } + if (!connected) { DeleteI2NPMessage (msg); return; @@ -254,10 +263,19 @@ namespace transport auto it = m_Peers.find (ident); if (it == m_Peers.end ()) { - auto r = netdb.FindRouter (ident); - it = m_Peers.insert (std::pair(ident, { 0, r, nullptr, - i2p::util::GetSecondsSinceEpoch () })).first; - if (!ConnectToPeer (ident, it->second)) + bool connected = false; + try + { + auto r = netdb.FindRouter (ident); + it = m_Peers.insert (std::pair(ident, { 0, r, nullptr, + i2p::util::GetSecondsSinceEpoch () })).first; + connected = ConnectToPeer (ident, it->second); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Transports::PostMessages ", ex.what ()); + } + if (!connected) { for (auto it1: msgs) DeleteI2NPMessage (it1); From c56ddce2f6ff482c3f95adfb7dc96f1f9d6ce8b4 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 14 Apr 2015 10:46:44 -0400 Subject: [PATCH 0422/6300] some cleanup --- Transports.cpp | 38 +------------------------------------- Transports.h | 1 - 2 files changed, 1 insertion(+), 38 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 3270c174..860d5611 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -207,49 +207,13 @@ namespace transport void Transports::SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) { - m_Service.post (std::bind (&Transports::PostMessage, this, ident, msg)); + m_Service.post (std::bind (&Transports::PostMessages, this, ident, std::vector {msg})); } void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector& msgs) { m_Service.post (std::bind (&Transports::PostMessages, this, ident, msgs)); } - - void Transports::PostMessage (i2p::data::IdentHash ident, i2p::I2NPMessage * msg) - { - if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) - { - // we send it to ourself - i2p::HandleI2NPMessage (msg); - return; - } - - auto it = m_Peers.find (ident); - if (it == m_Peers.end ()) - { - bool connected = false; - try - { - auto r = netdb.FindRouter (ident); - it = m_Peers.insert (std::pair(ident, { 0, r, nullptr, - i2p::util::GetSecondsSinceEpoch () })).first; - connected= ConnectToPeer (ident, it->second); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "Transports::PostMessage ", ex.what ()); - } - if (!connected) - { - DeleteI2NPMessage (msg); - return; - } - } - if (it->second.session) - it->second.session->SendI2NPMessage (msg); - else - it->second.delayedMessages.push_back (msg); - } void Transports::PostMessages (i2p::data::IdentHash ident, std::vector msgs) { diff --git a/Transports.h b/Transports.h index 64760dde..b5ee61fc 100644 --- a/Transports.h +++ b/Transports.h @@ -104,7 +104,6 @@ namespace transport void Run (); void RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); void HandleRequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); - void PostMessage (i2p::data::IdentHash ident, i2p::I2NPMessage * msg); void PostMessages (i2p::data::IdentHash ident, std::vector msgs); void PostCloseSession (std::shared_ptr router); bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); From 27bd19370864244543f585cf5adbcb925789fc12 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 14 Apr 2015 21:37:21 -0400 Subject: [PATCH 0423/6300] re-create tunnel before expiration --- Tunnel.cpp | 26 ++++++++++++++++---------- TunnelPool.cpp | 2 -- TunnelPool.h | 4 ++-- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 29c494f2..f189f5c3 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -535,17 +535,20 @@ namespace tunnel if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { LogPrint ("Tunnel ", tunnel->GetTunnelID (), " expired"); - { - auto pool = tunnel->GetTunnelPool (); - if (pool) - pool->TunnelExpired (tunnel); - } + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->TunnelExpired (tunnel); it = m_OutboundTunnels.erase (it); } else { if (tunnel->IsEstablished () && ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + { tunnel->SetState (eTunnelStateExpiring); + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->RecreateOutboundTunnel (tunnel); + } it++; } } @@ -575,17 +578,20 @@ namespace tunnel if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { LogPrint ("Tunnel ", tunnel->GetTunnelID (), " expired"); - { - auto pool = tunnel->GetTunnelPool (); - if (pool) - pool->TunnelExpired (tunnel); - } + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->TunnelExpired (tunnel); it = m_InboundTunnels.erase (it); } else { if (tunnel->IsEstablished () && ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + { tunnel->SetState (eTunnelStateExpiring); + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->RecreateInboundTunnel (tunnel); + } it++; } } diff --git a/TunnelPool.cpp b/TunnelPool.cpp index e412d09c..fd40565a 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -56,7 +56,6 @@ namespace tunnel expiredTunnel->SetTunnelPool (nullptr); for (auto it: m_Tests) if (it.second.second == expiredTunnel) it.second.second = nullptr; - RecreateInboundTunnel (expiredTunnel); std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.erase (expiredTunnel); @@ -77,7 +76,6 @@ namespace tunnel expiredTunnel->SetTunnelPool (nullptr); for (auto it: m_Tests) if (it.second.first == expiredTunnel) it.second.first = nullptr; - RecreateOutboundTunnel (expiredTunnel); std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.erase (expiredTunnel); diff --git a/TunnelPool.h b/TunnelPool.h index ee0687cc..fa7b1c16 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -38,6 +38,8 @@ namespace tunnel void TunnelExpired (std::shared_ptr expiredTunnel); void TunnelCreated (std::shared_ptr createdTunnel); void TunnelExpired (std::shared_ptr expiredTunnel); + void RecreateInboundTunnel (std::shared_ptr tunnel); + void RecreateOutboundTunnel (std::shared_ptr tunnel); std::vector > GetInboundTunnels (int num) const; std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr) const; std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr) const; @@ -54,8 +56,6 @@ namespace tunnel void CreateInboundTunnel (); void CreateOutboundTunnel (); - void RecreateInboundTunnel (std::shared_ptr tunnel); - void RecreateOutboundTunnel (std::shared_ptr tunnel); template typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; From a8b4f388654ead882e7632a4f5a9385d9d8aa5b8 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 15 Apr 2015 07:30:37 -0400 Subject: [PATCH 0424/6300] router don't expire if less than 75 --- NetDb.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index e26440dd..48405088 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -41,7 +41,7 @@ namespace data void NetDb::Start () { Load (m_NetDbPath); - if (m_RouterInfos.size () < 50) // reseed if # of router less than 50 + if (m_RouterInfos.size () < 25) // reseed if # of router less than 50 { // try SU3 first Reseed (); @@ -51,7 +51,7 @@ namespace data { // if still not enough download .dat files int reseedRetries = 0; - while (m_RouterInfos.size () < 50 && reseedRetries < 10) + while (m_RouterInfos.size () < 25 && reseedRetries < 5) { m_Reseeder->reseedNow(); reseedRetries++; @@ -384,22 +384,31 @@ namespace data // RouterInfo expires after 1 hour if uses introducer if (it.second->UsesIntroducer () && ts > it.second->GetTimestamp () + 3600*1000LL) // 1 hour it.second->SetUnreachable (true); - else if (total > 25 && ts > (i2p::context.GetStartupTime () + 600)*1000LL) // routers don't expire if less than 25 or uptime is less than 10 minutes + else if (total > 75 && ts > (i2p::context.GetStartupTime () + 600)*1000LL) // routers don't expire if less than 25 or uptime is less than 10 minutes { if (i2p::context.IsFloodfill ()) { if (ts > it.second->GetTimestamp () + 3600*1000LL) // 1 hours + { it.second->SetUnreachable (true); + total--; + } } else if (total > 300) { if (ts > it.second->GetTimestamp () + 30*3600*1000LL) // 30 hours + { it.second->SetUnreachable (true); + total--; + } } else if (total > 120) { if (ts > it.second->GetTimestamp () + 72*3600*1000LL) // 72 hours + { it.second->SetUnreachable (true); + total--; + } } } From e8c9d2db103fd41351a90d420b86d4dae85f285f Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 15 Apr 2015 11:52:49 -0400 Subject: [PATCH 0425/6300] double RTO after every resend attempt --- Streaming.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index c26c6b40..2ee33421 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -665,6 +665,7 @@ namespace stream if (packets.size () > 0) { m_NumResendAttempts++; + m_RTO *= 2; switch (m_NumResendAttempts) { case 1: // congesion avoidance @@ -672,9 +673,10 @@ namespace stream if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; break; case 2: + m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change first time + // no break here case 4: UpdateCurrentRemoteLease (); // pick another lease - m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change LogPrint (eLogWarning, "Another remote lease has been selected for stream"); break; case 3: From 12465f840a179532843514713180d51eb79a0277 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 15 Apr 2015 18:25:05 -0400 Subject: [PATCH 0426/6300] check outbound tunnles for readiness --- Destination.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Destination.h b/Destination.h index b5fcd520..29c3b230 100644 --- a/Destination.h +++ b/Destination.h @@ -64,7 +64,7 @@ namespace client bool IsRunning () const { return m_IsRunning; }; boost::asio::io_service& GetService () { return m_Service; }; std::shared_ptr GetTunnelPool () { return m_Pool; }; - bool IsReady () const { return m_LeaseSet && m_LeaseSet->HasNonExpiredLeases (); }; + bool IsReady () const { return m_LeaseSet && m_LeaseSet->HasNonExpiredLeases () && m_Pool->GetOutboundTunnels ().size () > 0; }; std::shared_ptr FindLeaseSet (const i2p::data::IdentHash& ident); bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); From 873754c6ca7eee95520b638bbd3a5ccf0d11bfa0 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 16 Apr 2015 11:38:36 -0400 Subject: [PATCH 0427/6300] select next lease with same gateway if possible --- Streaming.cpp | 29 ++++++++++++++++++++++------- Streaming.h | 2 +- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 2ee33421..868e53b0 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -606,7 +606,7 @@ namespace stream auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (ts >= m_CurrentRemoteLease.endDate - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000) - UpdateCurrentRemoteLease (); + UpdateCurrentRemoteLease (true); if (ts < m_CurrentRemoteLease.endDate) { std::vector msgs; @@ -709,7 +709,7 @@ namespace stream } } - void Stream::UpdateCurrentRemoteLease () + void Stream::UpdateCurrentRemoteLease (bool expired) { if (!m_RemoteLeaseSet) { @@ -724,16 +724,31 @@ namespace stream auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (false); // try without threshold first if (leases.empty ()) { + expired = false; m_LocalDestination.GetOwner ().RequestDestination (m_RemoteIdentity.GetIdentHash ()); // time to re-request leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold } if (!leases.empty ()) { - uint32_t i = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, leases.size () - 1); - if (m_CurrentRemoteLease.endDate && leases[i].tunnelID == m_CurrentRemoteLease.tunnelID) - // make sure we don't select previous - i = (i + 1) % leases.size (); // if so, pick next - m_CurrentRemoteLease = leases[i]; + bool updated = false; + if (expired) + { + for (auto it: leases) + if ((it.tunnelGateway == m_CurrentRemoteLease.tunnelGateway) && (it.tunnelID != m_CurrentRemoteLease.tunnelID)) + { + m_CurrentRemoteLease = it; + updated = true; + break; + } + } + if (!updated) + { + uint32_t i = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, leases.size () - 1); + if (m_CurrentRemoteLease.endDate && leases[i].tunnelID == m_CurrentRemoteLease.tunnelID) + // make sure we don't select previous + i = (i + 1) % leases.size (); // if so, pick next + m_CurrentRemoteLease = leases[i]; + } } else { diff --git a/Streaming.h b/Streaming.h index 5e397c62..b3cd4b30 100644 --- a/Streaming.h +++ b/Streaming.h @@ -147,7 +147,7 @@ namespace stream void ProcessAck (Packet * packet); size_t ConcatenatePackets (uint8_t * buf, size_t len); - void UpdateCurrentRemoteLease (); + void UpdateCurrentRemoteLease (bool expired = false); template void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler); From fcb56db224d2c874713770edbbc36ec41f298abd Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 17 Apr 2015 10:11:51 -0400 Subject: [PATCH 0428/6300] try to pick an outbound tunnel with same endpoint instead expired --- Streaming.cpp | 2 +- TunnelPool.cpp | 17 +++++++++++++++++ TunnelPool.h | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index 868e53b0..4d9342c1 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -597,7 +597,7 @@ namespace stream } } if (!m_CurrentOutboundTunnel || !m_CurrentOutboundTunnel->IsEstablished ()) - m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ().GetTunnelPool ()->GetNextOutboundTunnel (); + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ().GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); if (!m_CurrentOutboundTunnel) { LogPrint (eLogError, "No outbound tunnels in the pool"); diff --git a/TunnelPool.cpp b/TunnelPool.cpp index fd40565a..6315e501 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -131,6 +131,23 @@ namespace tunnel return tunnel; } + std::shared_ptr TunnelPool::GetNewOutboundTunnel (std::shared_ptr old) const + { + if (old && old->IsEstablished ()) return old; + std::shared_ptr tunnel; + if (old) + { + std::unique_lock l(m_OutboundTunnelsMutex); + for (auto it: m_OutboundTunnels) + if (it->IsEstablished () && old->GetEndpointRouter ()->GetIdentHash () == it->GetEndpointRouter ()->GetIdentHash ()) + tunnel = it; + } + + if (!tunnel) + tunnel = GetNextOutboundTunnel (); + return tunnel; + } + void TunnelPool::CreateTunnels () { int num = 0; diff --git a/TunnelPool.h b/TunnelPool.h index fa7b1c16..947bd3cc 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -43,6 +43,7 @@ namespace tunnel std::vector > GetInboundTunnels (int num) const; std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr) const; std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr) const; + std::shared_ptr GetNewOutboundTunnel (std::shared_ptr old) const; void TestTunnels (); void ProcessGarlicMessage (I2NPMessage * msg); From 3987d5e5a067aeaffb1dd6fe8938b8db4d29297a Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 17 Apr 2015 11:36:42 -0400 Subject: [PATCH 0429/6300] recreate tunnel after 9.5 minutes --- Tunnel.cpp | 33 ++++++++++++++++++++++----------- Tunnel.h | 4 ++++ TunnelPool.cpp | 3 +++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index f189f5c3..c44921c1 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -18,7 +18,7 @@ namespace tunnel { Tunnel::Tunnel (TunnelConfig * config): - m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending) + m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false) { } @@ -542,12 +542,17 @@ namespace tunnel } else { - if (tunnel->IsEstablished () && ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + if (tunnel->IsEstablished ()) { - tunnel->SetState (eTunnelStateExpiring); - auto pool = tunnel->GetTunnelPool (); - if (pool) - pool->RecreateOutboundTunnel (tunnel); + if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + { + tunnel->SetIsRecreated (); + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->RecreateOutboundTunnel (tunnel); + } + if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + tunnel->SetState (eTunnelStateExpiring); } it++; } @@ -585,12 +590,18 @@ namespace tunnel } else { - if (tunnel->IsEstablished () && ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + if (tunnel->IsEstablished ()) { - tunnel->SetState (eTunnelStateExpiring); - auto pool = tunnel->GetTunnelPool (); - if (pool) - pool->RecreateInboundTunnel (tunnel); + if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + { + tunnel->SetIsRecreated (); + auto pool = tunnel->GetTunnelPool (); + if (pool) + pool->RecreateInboundTunnel (tunnel); + } + + if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + tunnel->SetState (eTunnelStateExpiring); } it++; } diff --git a/Tunnel.h b/Tunnel.h index b59e3d7f..cd8a1379 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -24,6 +24,7 @@ namespace tunnel { const int TUNNEL_EXPIRATION_TIMEOUT = 660; // 11 minutes const int TUNNEL_EXPIRATION_THRESHOLD = 60; // 1 minute + const int TUNNEL_RECREATION_THRESHOLD = 90; // 1.5 minutes const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds const int STANDARD_NUM_RECORDS = 5; // in VariableTunnelBuild message @@ -54,6 +55,8 @@ namespace tunnel void SetState (TunnelState state) { m_State = state; }; bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; bool IsFailed () const { return m_State == eTunnelStateFailed; }; + bool IsRecreated () const { return m_IsRecreated; }; + void SetIsRecreated () { m_IsRecreated = true; }; std::shared_ptr GetTunnelPool () const { return m_Pool; }; void SetTunnelPool (std::shared_ptr pool) { m_Pool = pool; }; @@ -71,6 +74,7 @@ namespace tunnel TunnelConfig * m_Config; std::shared_ptr m_Pool; // pool, tunnel belongs to, or null TunnelState m_State; + bool m_IsRecreated; }; class OutboundTunnel: public Tunnel diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 6315e501..505b82f3 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -140,7 +140,10 @@ namespace tunnel std::unique_lock l(m_OutboundTunnelsMutex); for (auto it: m_OutboundTunnels) if (it->IsEstablished () && old->GetEndpointRouter ()->GetIdentHash () == it->GetEndpointRouter ()->GetIdentHash ()) + { tunnel = it; + break; + } } if (!tunnel) From 5f8356741e48bb27f1d6a5c67951f2904194299c Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 18 Apr 2015 13:55:15 -0400 Subject: [PATCH 0430/6300] fixed potential memory leak --- SSU.cpp | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 08dd7f9d..9c12441a 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -217,24 +217,33 @@ namespace transport for (auto it1: packets) { auto packet = it1; - if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous - { - if (session) session->FlushData (); - auto it = m_Sessions.find (packet->from); - if (it != m_Sessions.end ()) - session = it->second; - if (!session) + try + { + if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous { - session = std::make_shared (*this, packet->from); - session->WaitForConnect (); + if (session) session->FlushData (); + auto it = m_Sessions.find (packet->from); + if (it != m_Sessions.end ()) + session = it->second; + if (!session) { - std::unique_lock l(m_SessionsMutex); - m_Sessions[packet->from] = session; - } - LogPrint ("New SSU session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); + session = std::make_shared (*this, packet->from); + session->WaitForConnect (); + { + std::unique_lock l(m_SessionsMutex); + m_Sessions[packet->from] = session; + } + LogPrint (eLogInfo, "New SSU session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); + } } - } - session->ProcessNextMessage (packet->buf, packet->len, packet->from); + session->ProcessNextMessage (packet->buf, packet->len, packet->from); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ()); + if (session) session->FlushData (); + session = nullptr; + } delete packet; } if (session) session->FlushData (); From c96b81206d2ee03d6f3ed34813fe95b2f91b8b9c Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 21 Apr 2015 15:59:40 -0400 Subject: [PATCH 0431/6300] changed some profiling parameters --- Profiling.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Profiling.cpp b/Profiling.cpp index bac162e0..839406c2 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -140,9 +140,9 @@ namespace data { auto total = m_NumTunnelsAgreed + m_NumTunnelsDeclined; if (elapsedTime < 300) // if less than 5 minutes - return m_NumTunnelsNonReplied > 10*total; + return m_NumTunnelsNonReplied > 5*(total + 1); else - return !total && m_NumTunnelsNonReplied > 20; + return !total && m_NumTunnelsNonReplied*15 > elapsedTime; } bool RouterProfile::IsBad () const @@ -185,4 +185,4 @@ namespace data LogPrint (eLogInfo, num, " obsolete profiles deleted"); } } -} \ No newline at end of file +} From 7ec701a81699b0d697e5fa6198e47271d6906dae Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 21 Apr 2015 18:33:04 -0400 Subject: [PATCH 0432/6300] uin32_t for elapsed time --- Profiling.cpp | 4 ++-- Profiling.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Profiling.cpp b/Profiling.cpp index 839406c2..70df0e76 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -128,7 +128,7 @@ namespace data UpdateTime (); } - bool RouterProfile::IsLowPartcipationRate (int elapsedTime) const + bool RouterProfile::IsLowPartcipationRate (uint32_t elapsedTime) const { if (elapsedTime < 900) // if less than 15 minutes return m_NumTunnelsAgreed < m_NumTunnelsDeclined; // 50% rate @@ -136,7 +136,7 @@ namespace data return 3*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // 25% rate } - bool RouterProfile::IsLowReplyRate (int elapsedTime) const + bool RouterProfile::IsLowReplyRate (uint32_t elapsedTime) const { auto total = m_NumTunnelsAgreed + m_NumTunnelsDeclined; if (elapsedTime < 300) // if less than 5 minutes diff --git a/Profiling.h b/Profiling.h index 93fb5be2..3a5a0844 100644 --- a/Profiling.h +++ b/Profiling.h @@ -42,8 +42,8 @@ namespace data void UpdateTime (); bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; }; - bool IsLowPartcipationRate (int elapsedTime) const; - bool IsLowReplyRate (int elapsedTime) const; + bool IsLowPartcipationRate (uint32_t elapsedTime) const; + bool IsLowReplyRate (uint32_t elapsedTime) const; private: From 969695f318e4a96535f3e2109c828d6ea6085c0a Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 21 Apr 2015 18:59:35 -0400 Subject: [PATCH 0433/6300] check garlic clove length --- Garlic.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Garlic.cpp b/Garlic.cpp index 113aaa8d..261619eb 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -461,6 +461,7 @@ namespace garlic void GarlicDestination::HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr from) { + const uint8_t * buf1 = buf; int numCloves = buf[0]; LogPrint (numCloves," cloves"); buf++; @@ -518,6 +519,11 @@ namespace garlic buf += 4; // CloveID buf += 8; // Date buf += 3; // Certificate + if (buf - buf1 > (int)len) + { + LogPrint (eLogError, "Gralic clove is too long"); + break; + } } } From b8b8d70c7f103deec614cdb6290a676afec544e1 Mon Sep 17 00:00:00 2001 From: Kill Your TV Date: Sat, 2 May 2015 21:00:43 +0000 Subject: [PATCH 0434/6300] reseed certificate updates --- .../ssl/i2p-netdb.innovatio.no.crt | 23 ------------- .../certificates/ssl/ieb9oopo.mooo.com.crt | 32 +++++++++--------- .../certificates/ssl/ieb9oopo.mooo.com2.crt | 25 -------------- .../certificates/ssl/jp.reseed.i2p2.no.crt | 33 ------------------- contrib/certificates/ssl/netdb.i2p2.no.crt | 31 +++++++++-------- contrib/certificates/ssl/netdb.i2p2.no2.crt | 23 ------------- contrib/certificates/ssl/netdb.rows.io.crt | 33 +++++++++++++++++++ 7 files changed, 67 insertions(+), 133 deletions(-) delete mode 100644 contrib/certificates/ssl/i2p-netdb.innovatio.no.crt delete mode 100644 contrib/certificates/ssl/ieb9oopo.mooo.com2.crt delete mode 100644 contrib/certificates/ssl/jp.reseed.i2p2.no.crt delete mode 100644 contrib/certificates/ssl/netdb.i2p2.no2.crt create mode 100644 contrib/certificates/ssl/netdb.rows.io.crt diff --git a/contrib/certificates/ssl/i2p-netdb.innovatio.no.crt b/contrib/certificates/ssl/i2p-netdb.innovatio.no.crt deleted file mode 100644 index d9bac193..00000000 --- a/contrib/certificates/ssl/i2p-netdb.innovatio.no.crt +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID2zCCApOgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBCMR8wHQYDVQQDExZpMnAt -bmV0ZGIuaW5ub3ZhdGlvLm5vMRIwEAYDVQQKEwlJbm5vdmF0aW8xCzAJBgNVBAYT -Ak5PMCIYDzIwMTQwMTIxMDUzMzMxWhgPMjAyNDAxMTkwNTMzMzFaMEIxHzAdBgNV -BAMTFmkycC1uZXRkYi5pbm5vdmF0aW8ubm8xEjAQBgNVBAoTCUlubm92YXRpbzEL -MAkGA1UEBhMCTk8wggFSMA0GCSqGSIb3DQEBAQUAA4IBPwAwggE6AoIBMQC9WVet -EFeKAHmwgTUxJ/bRI4Gtjke3uj897eeZ15Y0SiqdHzypsEIWtXqx4G3W801xZzhv -UiAculvwRY4kpv3DnQE4sNTzbkAlvC6z4+CpFM2mhZ7o+YmozrIsNmQNCsvlxqJV -AD1mzqTFl/OB7LVtLmpSSd36IQFGmsh24XXa4pVH33e+NCZIGsdVwGsa4GoRuC9a -s/DiLI+x6zYRoY9cfOF2DuuOfKNMjSl65QUe4uHZCsRTb1q08NnPIidEFHr94kZH -Hph+MQs6MUVK1eT4yYt084S3cEWmWBQZVyAvWQ9q8EW+MoniOM7bBG2Bn9wu2F5x -kAKWTYfKSStW5CKSox9VSoopiUAtEIqhwgFGTISqhQyfOfyY97X2M47wvyWsl6dE -NxTgdvLD/o24rejBAgMBAAGjeDB2MAwGA1UdEwEB/wQCMAAwIQYDVR0RBBowGIIW -aTJwLW5ldGRiLmlubm92YXRpby5ubzATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNV -HQ8BAf8EBQMDByAAMB0GA1UdDgQWBBTaTuBpTAinNiT9PfKNRdIn3HrwxTANBgkq -hkiG9w0BAQsFAAOCATEAsYtojwAHiFwUqvCMIMJa5YN0Ms/QpjqZiENuGBpxvmVF -WVImX4s8K/qoBAKED8uKcRbMQd0FeDea7kMisJt5cblDzYuSv6wfeLXYkaT8/9H2 -X1pXhO/eghJ2U42RTgBkaW3mCI8ohk/GehU4tEXnbWRPHt6XDoSYDJdf2X8BPcgB -ZE10owLCw9c80QTuU+LCvbt8/F2USyNplUrogJGThzxrxZvxjGq6EcDj0iA0RRoG -5CUNrCB+JgFc+4bagI3E5B0skk/wn3Nl7mM8/Nf8b1QENmc8eYBZx2InA9769DHL -tNxzvE+OeMNlKy4M9WvLieIh6KmpYhHBG8ubJT8X+bZpJkh4rH6RzVYiXeCpX2iL -eoeSriGO0+8CJTBRbd5cXYd/COT0iAomMTelhcGVTA== ------END CERTIFICATE----- diff --git a/contrib/certificates/ssl/ieb9oopo.mooo.com.crt b/contrib/certificates/ssl/ieb9oopo.mooo.com.crt index 78772210..8be9eef8 100644 --- a/contrib/certificates/ssl/ieb9oopo.mooo.com.crt +++ b/contrib/certificates/ssl/ieb9oopo.mooo.com.crt @@ -1,25 +1,25 @@ -----BEGIN CERTIFICATE----- -MIIESzCCAzOgAwIBAgIJALGqvElYEEqyMA0GCSqGSIb3DQEBBQUAMIG7MQswCQYD +MIIESzCCAzOgAwIBAgIJAKII1waVnWddMA0GCSqGSIb3DQEBCwUAMIG7MQswCQYD VQQGEwJERTEaMBgGA1UECAwRaWViOW9vcG8ubW9vby5jb20xGjAYBgNVBAcMEWll Yjlvb3BvLm1vb28uY29tMRowGAYDVQQKDBFpZWI5b29wby5tb29vLmNvbTEaMBgG A1UECwwRaWViOW9vcG8ubW9vby5jb20xGjAYBgNVBAMMEWllYjlvb3BvLm1vb28u -Y29tMSAwHgYJKoZIhvcNAQkBFhFpZWI5b29wby5tb29vLmNvbTAeFw0xNDA0MTMx -NDI3MThaFw0zNDA0MDgxNDI3MThaMIG7MQswCQYDVQQGEwJERTEaMBgGA1UECAwR +Y29tMSAwHgYJKoZIhvcNAQkBFhFpZWI5b29wby5tb29vLmNvbTAeFw0xNDExMjIx +MzQzNThaFw0yMDA1MTQxMzQzNThaMIG7MQswCQYDVQQGEwJERTEaMBgGA1UECAwR aWViOW9vcG8ubW9vby5jb20xGjAYBgNVBAcMEWllYjlvb3BvLm1vb28uY29tMRow GAYDVQQKDBFpZWI5b29wby5tb29vLmNvbTEaMBgGA1UECwwRaWViOW9vcG8ubW9v by5jb20xGjAYBgNVBAMMEWllYjlvb3BvLm1vb28uY29tMSAwHgYJKoZIhvcNAQkB FhFpZWI5b29wby5tb29vLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAN1CusoQ3OxJFTytbVMe7LPY8lnR7GIIQt5eCs0XTLl3ECclET1KLRo1c8Mv -vj5AwDRvL3Kw/oeDS+QvSRhgxG3lJ8i0soWDC/1LIX1NS/ZXG7hbycZ7YqAovCxU -958WPjUios73sAWUJrI8BbWFTYPWBB5lbyTaCooxtf6k7yB+CSwZnstEP/lbPNkf -Iupj+9B18ba21D2kQqZXyQRLX02d/rXq963BSkPX14Dxa7abw4lgDltvh2CzcoQH -VbQh3eGfPIIQGfvAAVEGsoy9fFt+xOUxp3KO7Y1VMKzegWqa2vBtWK+2nhpHfswq -eaE1qeVh12cG5kaVNP0o0hOUxkECAwEAAaNQME4wHQYDVR0OBBYEFIQ5U4EKfr0t -4L+RFe/RBFcDVoijMB8GA1UdIwQYMBaAFIQ5U4EKfr0t4L+RFe/RBFcDVoijMAwG -A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGlNHzxF9tbS/Xr8jGpSdRLm -oIIoTr7+dIfFIy2TMAow0+gx1qEtWASUYIwbCP5mdMfhUjL8POfSwtTvgjw3LMoM -2zsGBvgGWs6VJbMPAgkgfsyjdJTM/IOiJtze6degn2bdZvAr1XiInLGMRMko7WyE -QQ1WJPcTUt8rNYVaCkq3CYioIlkb/C6M6ObHGSia0kwoZa6grD3CNUCa/c/dlFKO -E06qN72fsYihp0ghHkBaCxb+YfYpIAPIpfuuTSGkVYyjpY0a8EQ1JlzbJctQkX5r -sd9jfHsrZriAGCzdsL/diG8bUi9Yex/G0ip8GGEuHs5e2bJ6+7O/mPlL6SL/0tI= +ggEBAMhcnkSifOMw5bd66UlvYVsc42H22Nuy64qhtJHtggofrwBooF38kRCBVFL8 +9Xjzr0xsSshvO6p7E+CEUtA8v55l5vNbUTAvGP9WmzeZyZuCFg9Heo3orNMbIK7m +ppwKhwh6tFEIEpUTz/+xF5NRt0+CqcS4aNHuH3JPwNugfTBuSa86GeSaqL7K4eEZ +bZXqQ16Onvi0yyMqRJDp/ijRFxr2eKGPWb55kuRSET9PxVhlgRKULZkr39Dh9q1c +wb9lAMLMRZIzPVnyvC9jWkIqSDl5bkAAto0n1Jkw92rRp6EVKgSLA/4vl9wTb6xf +WfT5cs7pykAE0WXBr9TqpS3okncCAwEAAaNQME4wHQYDVR0OBBYEFGeEOHhWiKwZ +TGbc7uuK3DD7YjYZMB8GA1UdIwQYMBaAFGeEOHhWiKwZTGbc7uuK3DD7YjYZMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAzRA/0OpJtCO4kQkTn/hux9 +dRi9T6B54Xav5jG53iAPLTeMxsaLkvweh2pZ3kvEUrQhvW0JF8QBrHTsgxzb4Wd6 +FNDHSgJbZv3uCjFtWeuUh+GTG1k9uwgNIEnx7J9Vp0JCi4ezi/HMNI7c+LjinM9f +hrAzclkeRPLYg645DkxckLyDUbrc9v1qWFoTpezXSBPO7n3Wk4sCytdoA1FkTdXh +RF4BWCl/3uOxcrn0TqoC9vCh8RcxnllOiOO5j4+PQ1Z6NkQ/5oRCK/jjaWc3Lr6/ +FicOZJe29BVnrPGynqe0Ky1o+kTdXFflKowfr7g8dwn8k9YavjtGbl1ZSHeuMF8= -----END CERTIFICATE----- diff --git a/contrib/certificates/ssl/ieb9oopo.mooo.com2.crt b/contrib/certificates/ssl/ieb9oopo.mooo.com2.crt deleted file mode 100644 index 8be9eef8..00000000 --- a/contrib/certificates/ssl/ieb9oopo.mooo.com2.crt +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIESzCCAzOgAwIBAgIJAKII1waVnWddMA0GCSqGSIb3DQEBCwUAMIG7MQswCQYD -VQQGEwJERTEaMBgGA1UECAwRaWViOW9vcG8ubW9vby5jb20xGjAYBgNVBAcMEWll -Yjlvb3BvLm1vb28uY29tMRowGAYDVQQKDBFpZWI5b29wby5tb29vLmNvbTEaMBgG -A1UECwwRaWViOW9vcG8ubW9vby5jb20xGjAYBgNVBAMMEWllYjlvb3BvLm1vb28u -Y29tMSAwHgYJKoZIhvcNAQkBFhFpZWI5b29wby5tb29vLmNvbTAeFw0xNDExMjIx -MzQzNThaFw0yMDA1MTQxMzQzNThaMIG7MQswCQYDVQQGEwJERTEaMBgGA1UECAwR -aWViOW9vcG8ubW9vby5jb20xGjAYBgNVBAcMEWllYjlvb3BvLm1vb28uY29tMRow -GAYDVQQKDBFpZWI5b29wby5tb29vLmNvbTEaMBgGA1UECwwRaWViOW9vcG8ubW9v -by5jb20xGjAYBgNVBAMMEWllYjlvb3BvLm1vb28uY29tMSAwHgYJKoZIhvcNAQkB -FhFpZWI5b29wby5tb29vLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAMhcnkSifOMw5bd66UlvYVsc42H22Nuy64qhtJHtggofrwBooF38kRCBVFL8 -9Xjzr0xsSshvO6p7E+CEUtA8v55l5vNbUTAvGP9WmzeZyZuCFg9Heo3orNMbIK7m -ppwKhwh6tFEIEpUTz/+xF5NRt0+CqcS4aNHuH3JPwNugfTBuSa86GeSaqL7K4eEZ -bZXqQ16Onvi0yyMqRJDp/ijRFxr2eKGPWb55kuRSET9PxVhlgRKULZkr39Dh9q1c -wb9lAMLMRZIzPVnyvC9jWkIqSDl5bkAAto0n1Jkw92rRp6EVKgSLA/4vl9wTb6xf -WfT5cs7pykAE0WXBr9TqpS3okncCAwEAAaNQME4wHQYDVR0OBBYEFGeEOHhWiKwZ -TGbc7uuK3DD7YjYZMB8GA1UdIwQYMBaAFGeEOHhWiKwZTGbc7uuK3DD7YjYZMAwG -A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAzRA/0OpJtCO4kQkTn/hux9 -dRi9T6B54Xav5jG53iAPLTeMxsaLkvweh2pZ3kvEUrQhvW0JF8QBrHTsgxzb4Wd6 -FNDHSgJbZv3uCjFtWeuUh+GTG1k9uwgNIEnx7J9Vp0JCi4ezi/HMNI7c+LjinM9f -hrAzclkeRPLYg645DkxckLyDUbrc9v1qWFoTpezXSBPO7n3Wk4sCytdoA1FkTdXh -RF4BWCl/3uOxcrn0TqoC9vCh8RcxnllOiOO5j4+PQ1Z6NkQ/5oRCK/jjaWc3Lr6/ -FicOZJe29BVnrPGynqe0Ky1o+kTdXFflKowfr7g8dwn8k9YavjtGbl1ZSHeuMF8= ------END CERTIFICATE----- diff --git a/contrib/certificates/ssl/jp.reseed.i2p2.no.crt b/contrib/certificates/ssl/jp.reseed.i2p2.no.crt deleted file mode 100644 index 1cb5abec..00000000 --- a/contrib/certificates/ssl/jp.reseed.i2p2.no.crt +++ /dev/null @@ -1,33 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFqTCCA5GgAwIBAgIJAPsJOCng4aEOMA0GCSqGSIb3DQEBBQUAMGsxCzAJBgNV -BAYTAk5PMQ0wCwYDVQQIDARPc2xvMQ4wDAYDVQQHDAVKYXBhbjEMMAoGA1UECgwD -STJQMRMwEQYDVQQLDApJMlAgUmVzZWVkMRowGAYDVQQDDBFqcC5yZXNlZWQuaTJw -Mi5ubzAeFw0xNDA2MjgyMDQ3MThaFw0yNDA2MjUyMDQ3MThaMGsxCzAJBgNVBAYT -Ak5PMQ0wCwYDVQQIDARPc2xvMQ4wDAYDVQQHDAVKYXBhbjEMMAoGA1UECgwDSTJQ -MRMwEQYDVQQLDApJMlAgUmVzZWVkMRowGAYDVQQDDBFqcC5yZXNlZWQuaTJwMi5u -bzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALlZBIbb4MHqLPpUy298 -PG5z7RFo4bO7CxW2LO8DgE93KNbdkZuYpt6KcAW/gkDfKCiXTxDjyqmzZeBeIbcS -ea96jFc/pTknkiiSm9NX4ATcyRwvKXn6DR/iOiofP3G/sEkxgIdv3BgEYsF5jPQY -KefG0Jvl612cIX+1jC3lRRePYPUZnWxIuokXglLApeyhNzTK/KHIsLzR+56ScltM -pwGlroTky5ekPx4ZnJCxI+qFXWcgqdoNixPUkOtceYm7u5gd1D+mQDuCB5zfB+Pw -Iy9BTARot9M3fMrcRRVfEIGWwN1+bRnZvr0A/sqYMUqGPiUVOMi/mzyqZVQ14CGy -0idRhUKdOb/HNSmcep0Jwp0cP+VD/nCNU4JLptTLdErpTJrmDn+zkuQPPSMTzTWg -Fh3ktRsJ5zKfrnrBGxeKbciZgRkVHyWpv3+0AgbD8C3HvDj4qpkIO6D2gf+TREyM -frDXN6luqkThQcUFv+huMaL3Iul2doYqw5YPAdel6/cCD12n/FoEj5UJ47O/77DA -ITYfKCFRKDh/Ew7Ih3bH66uNaUUp+a0Sd6fNXLmWWr7gcn5B5CvrXhjPPror+Xyz -EZVByPTTfU1BkMQuxv20GG65M9g9wtXrD79N8di2wOfr4EG5i6L9ZvNTThwgWGGd -9f745WCnPL/ulLT9Glnlfk01AgMBAAGjUDBOMB0GA1UdDgQWBBR5Ed38JID7+JwB -hpOLi1Xt1ORyoTAfBgNVHSMEGDAWgBR5Ed38JID7+JwBhpOLi1Xt1ORyoTAMBgNV -HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQBRT/ourx4xT0n/qHF1vy8Ii02v -Hg5lHmzmiVn/3/S4q+t4HrOF6MSjjvVzVNk6JIIYb3+hwGUO31OLNZX60JfSMW+C -ffPiaNDLm/xE4yt4B/QZZDs0kv0RMjGau6k2XJPGvxVNl6LhM6LLvzMEtOGzBr0J -Ai4ZU+WqHk3nnXYHbg7C7Iuu8CT8tBpkaeW/VEN82dcLEulHFxA5Ia6HlwqrgvIW -w77oaOhOh5LKkdS8uHx4OUP8Mv25H2cMBblUdubbeqREyOGRGTkVXfRekD8K95ol -PfT9PhnhUXKRaSwy0nRqvRMiwk/CyIJTbMMflDET1P785diSvtJP0fOhkev9Uprz -FvLsPUTvANUz+vd2KJiLuGbR/d/LaJm18vdWx13vKVO60TBnyFQDS4RZbeuNp73v -x5fPTmiiPZYZj13m2xyes7SXJqhVbms0F39soThpuYrjCaHXyFgqah27+9Ivmbhu -EefPgLmkOx3v0kSfXdJYdji/mrKxERmqT1L34U6M32tCoSbjO7lakAV2opisbHEw -EehCuI83cGp/m3z8yjMoWV8Z4VoB+qMzxzgrXc5C2lxYAT4JggAV2SGcY9MszETI -/S7y5UypV4rutyJvHFrlJZKtp2B7Xi8N8n2NG1WGppYh9UShbFhDaVIMQpjZokAg -MHk7ixe0rSbnHcNF8g== ------END CERTIFICATE----- diff --git a/contrib/certificates/ssl/netdb.i2p2.no.crt b/contrib/certificates/ssl/netdb.i2p2.no.crt index 2ab5a131..7c792d99 100644 --- a/contrib/certificates/ssl/netdb.i2p2.no.crt +++ b/contrib/certificates/ssl/netdb.i2p2.no.crt @@ -1,18 +1,23 @@ -----BEGIN CERTIFICATE----- -MIIC0DCCAjmgAwIBAgIJANdBFbakbhhOMA0GCSqGSIb3DQEBBQUAMIGAMQswCQYD +MIID1TCCAr2gAwIBAgIJAOd9wIt+w/I5MA0GCSqGSIb3DQEBCwUAMIGAMQswCQYD VQQGEwJOTzENMAsGA1UECAwET3NsbzENMAsGA1UEBwwET3NsbzEMMAoGA1UECgwD STJQMQwwCgYDVQQLDANJMlAxFjAUBgNVBAMMDW5ldGRiLmkycDIubm8xHzAdBgkq -hkiG9w0BCQEWEG1lZWhAaTJwbWFpbC5vcmcwHhcNMTIxMDIzMDExMjIyWhcNMTkx -MDIyMDExMjIyWjCBgDELMAkGA1UEBhMCTk8xDTALBgNVBAgMBE9zbG8xDTALBgNV +hkiG9w0BCQEWEG1lZWhAaTJwbWFpbC5vcmcwHhcNMTQxMjA2MjM1OTM1WhcNMjAw +NTI4MjM1OTM1WjCBgDELMAkGA1UEBhMCTk8xDTALBgNVBAgMBE9zbG8xDTALBgNV BAcMBE9zbG8xDDAKBgNVBAoMA0kyUDEMMAoGA1UECwwDSTJQMRYwFAYDVQQDDA1u -ZXRkYi5pMnAyLm5vMR8wHQYJKoZIhvcNAQkBFhBtZWVoQGkycG1haWwub3JnMIGf -MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCDvmjTpff6/XpiNuqoa9ZEKMlyq1o -kas9fHwnZax/0QTM3xusSQQ9DzeVMSx1ueYxhTZ6VLmE1mTr0aIndzugxGK/g85H -Y+cUl3nw7+5gLPMCUrKAXqQokE3mYxSNY3AUeend7nmHvm9iciw4+Sa2+6ROvQQy -kD31CEN6/I04rwIDAQABo1AwTjAdBgNVHQ4EFgQUV83dJhEcLbfJ+uh+MDYNPdah -RoQwHwYDVR0jBBgwFoAUV83dJhEcLbfJ+uh+MDYNPdahRoQwDAYDVR0TBAUwAwEB -/zANBgkqhkiG9w0BAQUFAAOBgQBQQlJym7mUMUM2ryKu20z2PSUzFyq5U4rWHeo3 -elbNaTsFBwi+Ot/Lg/A5I4V8gywH1fBTG5bYKDUYvWohz1qIg66G57B1zT1zK9yh -Byz9go44M3y1/kXXSsJlY9llG9DDicr1y6LfldwZJ5zFAd3iiB8D8UadP5YLqb7v -wb1F1g== +ZXRkYi5pMnAyLm5vMR8wHQYJKoZIhvcNAQkBFhBtZWVoQGkycG1haWwub3JnMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmtRtAALMImh0G0X+AtMpJNBa +HduNkg5t+0juitKRboXXAp5k7yN9qnimlBxlAicNb+QubcDuL+WV91NKz43dd6Xp +SAewqMFRPUAki8uYzoh+hQEfzyd3NmadUKquYZsYwomhHnraOmLZLbxD6ED3FEwl +hGBJwYnhyMZUCgB5+DEEHg8RdLz+H0bMrwz3e7/0lMtH6lM1lIHz0KBULWLp7Om0 +sk3rmmhPUIXqfoY8X3vClI74o0KcslMVaF4rt3lAHdoi3lwA6Qbdqq9nC9rPWHUS +USQQ/MKsNfDTGsHkbW2l0VgNvJkw92DwHTXSJrsEqgkdV/B1hHxCKgL44c/CbwID +AQABo1AwTjAdBgNVHQ4EFgQUCkebDZE05yKMbXORa6gO+aLdCscwHwYDVR0jBBgw +FoAUCkebDZE05yKMbXORa6gO+aLdCscwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOCAQEAfHO0g5M//X5xDIuXGCeqQMUrF3r1N45a+0kqo2b/rd9USueNGrJl +KE7MfDgShy2d4strZ1m0M4StW0RlUUZ4V4FYwzcknF6VXbOQK3BTrAeOwuxsrHoT +abrMZ36ABYur5WakOYtPyQ5oXFUAIpGBe9LH7q3XLegSOfftvc2xdJ+VK0n4MEfY +GfaRGMNW/pxGYLWvao3soOJMtp6cQ5KIYGuX92DMon/UgPBqEygeUj7aIqjhRss0 +b0dUZQyHccAG+e5NeTF2ifHCEh2rZY18VGxPL7KLrCQigu5lif1TTv5CDO5rKrHl +TuTOsnooMxUH4ThIVI9cxXk6bzRMehLghA== -----END CERTIFICATE----- diff --git a/contrib/certificates/ssl/netdb.i2p2.no2.crt b/contrib/certificates/ssl/netdb.i2p2.no2.crt deleted file mode 100644 index 7c792d99..00000000 --- a/contrib/certificates/ssl/netdb.i2p2.no2.crt +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID1TCCAr2gAwIBAgIJAOd9wIt+w/I5MA0GCSqGSIb3DQEBCwUAMIGAMQswCQYD -VQQGEwJOTzENMAsGA1UECAwET3NsbzENMAsGA1UEBwwET3NsbzEMMAoGA1UECgwD -STJQMQwwCgYDVQQLDANJMlAxFjAUBgNVBAMMDW5ldGRiLmkycDIubm8xHzAdBgkq -hkiG9w0BCQEWEG1lZWhAaTJwbWFpbC5vcmcwHhcNMTQxMjA2MjM1OTM1WhcNMjAw -NTI4MjM1OTM1WjCBgDELMAkGA1UEBhMCTk8xDTALBgNVBAgMBE9zbG8xDTALBgNV -BAcMBE9zbG8xDDAKBgNVBAoMA0kyUDEMMAoGA1UECwwDSTJQMRYwFAYDVQQDDA1u -ZXRkYi5pMnAyLm5vMR8wHQYJKoZIhvcNAQkBFhBtZWVoQGkycG1haWwub3JnMIIB -IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmtRtAALMImh0G0X+AtMpJNBa -HduNkg5t+0juitKRboXXAp5k7yN9qnimlBxlAicNb+QubcDuL+WV91NKz43dd6Xp -SAewqMFRPUAki8uYzoh+hQEfzyd3NmadUKquYZsYwomhHnraOmLZLbxD6ED3FEwl -hGBJwYnhyMZUCgB5+DEEHg8RdLz+H0bMrwz3e7/0lMtH6lM1lIHz0KBULWLp7Om0 -sk3rmmhPUIXqfoY8X3vClI74o0KcslMVaF4rt3lAHdoi3lwA6Qbdqq9nC9rPWHUS -USQQ/MKsNfDTGsHkbW2l0VgNvJkw92DwHTXSJrsEqgkdV/B1hHxCKgL44c/CbwID -AQABo1AwTjAdBgNVHQ4EFgQUCkebDZE05yKMbXORa6gO+aLdCscwHwYDVR0jBBgw -FoAUCkebDZE05yKMbXORa6gO+aLdCscwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B -AQsFAAOCAQEAfHO0g5M//X5xDIuXGCeqQMUrF3r1N45a+0kqo2b/rd9USueNGrJl -KE7MfDgShy2d4strZ1m0M4StW0RlUUZ4V4FYwzcknF6VXbOQK3BTrAeOwuxsrHoT -abrMZ36ABYur5WakOYtPyQ5oXFUAIpGBe9LH7q3XLegSOfftvc2xdJ+VK0n4MEfY -GfaRGMNW/pxGYLWvao3soOJMtp6cQ5KIYGuX92DMon/UgPBqEygeUj7aIqjhRss0 -b0dUZQyHccAG+e5NeTF2ifHCEh2rZY18VGxPL7KLrCQigu5lif1TTv5CDO5rKrHl -TuTOsnooMxUH4ThIVI9cxXk6bzRMehLghA== ------END CERTIFICATE----- diff --git a/contrib/certificates/ssl/netdb.rows.io.crt b/contrib/certificates/ssl/netdb.rows.io.crt new file mode 100644 index 00000000..5d99233c --- /dev/null +++ b/contrib/certificates/ssl/netdb.rows.io.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFszCCA52gAwIBAgIRALWZzF745GPT8GVUcZ0RMg0wCwYJKoZIhvcNAQELMG0x +CzAJBgNVBAYTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAK +BgNVBAsTA0kyUDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMRYwFAYDVQQDEw1u +ZXRkYi5yb3dzLmlvMB4XDTE0MTIyMDE2NDIwNVoXDTE2MTIxOTE2NDIwNVowbTEL +MAkGA1UEBhMCWFgxHjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoG +A1UECxMDSTJQMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxFjAUBgNVBAMTDW5l +dGRiLnJvd3MuaW8wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCTZyJF +Im9pnc7OO5DfQy4SuotUztO5BJX7xniTqD4fKLQQXzZFeT4XHrkDste8TsTzUfxt +CWDEBH3af5cpnwWMT28rlRw2DlPr+LnAgt7VjFXdhFZr1N5VfNlTI1K3OiZ/DRlB +92CoTypyx4ebNfLtZfh+TPLOdg5UqROpHIrybsUj2IaG3IpGHJK8FuH79b/X5oVI +FlDZJs5QsJEARzq2QMJd6fnNqkCBSSjNpeL7TtDar9EKa6+O7s351kH8MVFNSogB +F0Hqu8LYaRC1L1JCz5lsOYKepp3MMIOdDOhy+FTd8NuNZXYkUTdTNI4dB6w4Z6o+ +xlnHEPpezIAAlPXLiupvlEi0om69/TMS+pLDBLAOlCZ2YaXS18UrSbmYYlekg40J +nEeALt8ZdsU/is7Q6SJZ3UltFIPCuMD+ixvaIvakkhNiqEWPxdg0XxAK1ZJYFup+ +2aVtPLQIzWePkG/VbdA5cxQKNtRwOgvCoKIE29nUbxuq2PCmMhLAfXHeieSzP5c7 +Q8A23qX94hwCIePj1YA9uNtStjECfVS1wjyXV4M1tTFUdSJv4aVtFjtya7PY+6SG +Srz11SqBWSqyJ/C14Su0QY/HquglzMRnkJ49Scwb+79hl7kPslO1iIgPLE5S2fIW +ZwJ/4AgGb6BZT8XPEYYANEA5y7KGanYNo8KdYwIDAQABo1IwUDAOBgNVHQ8BAf8E +BAMCAKQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAYBgNV +HREEETAPgg1uZXRkYi5yb3dzLmlvMAsGCSqGSIb3DQEBCwOCAgEAMjQSHPR/p9If +mJA1y489D1NB2CxfwO+CgAIs9HA7OsdneQBZTldMgBHoQGifkpD1uSl8DHoZqnJ8 +wo5YWcT1rYkP+V1jGfZj92VvfQL0/R4G4hWdQwYY0CcXN8ixS36UDQVSFKb4zvNG +j9iIN57WToEmxp5noHguKrpViwhXCMCpAXr3ZIv/Fd+QACNEXuvdZgbtwfOTPLKh +ZlkUPgVHiQopeQnZhZCT3aLZ5lndrUtWlQYiGN/OolVyRie+ysuxjRR4L5brt4Rz +hrwFBswbQZlgxJ3Nod9/wEdEJWP4+X69ggzOkBB+PgpOFpuDlJxNTcPA/WFIlsm0 +CzCv/o8Vg+MMWFPMwEZrk6UQXXACr1AEF+MUnZq3o5JaLvHoUcikewbZPcTCNvDp +nqT1RN9vq/MGdlRfPJkF028IXPz7T9DXXPXhJvv+FAfnOkREeUYpzBIftyYf92ol +l63z0FooVUTKWYPvFFgl5ShNnINTMVXPCZp8j7myLGSLOAFFwiaL1OtvftgxXfzC +B7Qj42SNhFUrHmO9fH3H2ptm/iW/Xe5eqgeb6MVGQ/eQJpdp0AvpDa50/AYNt1Iq +CcMKmBgzUezrIN24XXW/LZwazlc7I8e5RzgbEgXEDBZu21TApTKlmOqEYle8294W +fWThMdwk1kTrWxLooiVrS5A1hXqADqE= +-----END CERTIFICATE----- From 7f172964f6f7685a3fc7034e06b2eaf7678250c0 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 4 May 2015 13:01:27 -0400 Subject: [PATCH 0435/6300] check profile only once --- NetDb.cpp | 3 +-- TunnelPool.cpp | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 48405088..6408b7f8 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -905,8 +905,7 @@ namespace data { return !router->IsHidden () && router != compatibleWith && router->IsCompatible (*compatibleWith) && - (router->GetCaps () & RouterInfo::eHighBandwidth) && - !router->GetProfile ()->IsBad (); + (router->GetCaps () & RouterInfo::eHighBandwidth); }); } diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 505b82f3..95906472 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -281,8 +281,8 @@ namespace tunnel bool isExploratory = (m_LocalDestination == &i2p::context); // TODO: implement it better auto hop = isExploratory ? i2p::data::netdb.GetRandomRouter (prevHop): i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop); - - if (!hop) + + if (!hop || hop->GetProfile ()->IsBad ()) hop = i2p::data::netdb.GetRandomRouter (); return hop; } From d2b4a6fd50d8aa17d99d06f37ad288e32f99b200 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 May 2015 10:33:19 -0400 Subject: [PATCH 0436/6300] select first hop from existing connections if applicable --- Transports.cpp | 8 ++++++++ Transports.h | 2 ++ TunnelPool.cpp | 15 +++++++++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 860d5611..0cb4a970 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -489,6 +489,14 @@ namespace transport m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); } } + + std::shared_ptr Transports::GetRandomPeer () const + { + CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + auto it = m_Peers.begin (); + std::advance (it, rnd.GenerateWord32 (0, m_Peers.size () - 1)); + return it != m_Peers.end () ? it->second.router : nullptr; + } } } diff --git a/Transports.h b/Transports.h index b5ee61fc..00bf5b6f 100644 --- a/Transports.h +++ b/Transports.h @@ -98,6 +98,8 @@ namespace transport uint32_t GetInBandwidth () const { return m_InBandwidth; }; // bytes per second uint32_t GetOutBandwidth () const { return m_OutBandwidth; }; // bytes per second bool IsBandwidthExceeded () const; + size_t GetNumPeers () const { return m_Peers.size (); }; + std::shared_ptr GetRandomPeer () const; private: diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 95906472..e7d75f11 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -4,6 +4,7 @@ #include "NetDb.h" #include "Timestamp.h" #include "Garlic.h" +#include "Transports.h" #include "TunnelPool.h" namespace i2p @@ -341,10 +342,20 @@ namespace tunnel if (inboundTunnel) { LogPrint ("Creating destination outbound tunnel..."); - + int numHops = m_NumOutboundHops; auto prevHop = i2p::context.GetSharedRouterInfo (); std::vector > hops; - for (int i = 0; i < m_NumOutboundHops; i++) + if (i2p::transport::transports.GetNumPeers () > 25) + { + auto r = i2p::transport::transports.GetRandomPeer (); + if (r) + { + prevHop = r; + hops.push_back (r); + numHops--; + } + } + for (int i = 0; i < numHops; i++) { auto hop = SelectNextHop (prevHop); if (!hop) From 42d49bde869037c0ad8e0564bb4f4287d6236a6e Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 May 2015 12:32:13 -0400 Subject: [PATCH 0437/6300] handle tunnels quantity params --- Destination.cpp | 24 +++++++++++++++++++++++- Destination.h | 4 ++++ Tunnel.cpp | 6 +++--- Tunnel.h | 2 +- TunnelPool.cpp | 8 ++++---- TunnelPool.h | 4 ++-- 6 files changed, 37 insertions(+), 11 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 4c7d382a..606bd619 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -22,6 +22,8 @@ namespace client i2p::crypto::GenerateElGamalKeyPair(i2p::context.GetRandomNumberGenerator (), m_EncryptionPrivateKey, m_EncryptionPublicKey); int inboundTunnelLen = DEFAULT_INBOUND_TUNNEL_LENGTH; int outboundTunnelLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH; + int inboundTunnelsQuantity = DEFAULT_INBOUND_TUNNELS_QUANTITY; + int outboundTunnelsQuantity = DEFAULT_OUTBOUND_TUNNELS_QUANTITY; if (params) { auto it = params->find (I2CP_PARAM_INBOUND_TUNNEL_LENGTH); @@ -44,8 +46,28 @@ namespace client LogPrint (eLogInfo, "Outbound tunnel length set to ", len); } } + it = params->find (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY); + if (it != params->end ()) + { + int quantity = boost::lexical_cast(it->second); + if (quantity > 0) + { + inboundTunnelsQuantity = quantity; + LogPrint (eLogInfo, "Inbound tunnels quantity set to ", quantity); + } + } + it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY); + if (it != params->end ()) + { + int quantity = boost::lexical_cast(it->second); + if (quantity > 0) + { + outboundTunnelsQuantity = quantity; + LogPrint (eLogInfo, "Outbound tunnels quantity set to ", quantity); + } + } } - m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (this, inboundTunnelLen, outboundTunnelLen); + m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (this, inboundTunnelLen, outboundTunnelLen, inboundTunnelsQuantity, outboundTunnelsQuantity); if (m_IsPublic) LogPrint (eLogInfo, "Local address ", i2p::client::GetB32Address(GetIdentHash()), " created"); m_StreamingDestination = std::make_shared (*this); // TODO: diff --git a/Destination.h b/Destination.h index 29c3b230..bf2dfcca 100644 --- a/Destination.h +++ b/Destination.h @@ -36,6 +36,10 @@ namespace client const int DEFAULT_INBOUND_TUNNEL_LENGTH = 3; const char I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH[] = "outbound.length"; const int DEFAULT_OUTBOUND_TUNNEL_LENGTH = 3; + const char I2CP_PARAM_INBOUND_TUNNELS_QUANTITY[] = "inbound.quantity"; + const int DEFAULT_INBOUND_TUNNELS_QUANTITY = 5; + const char I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY[] = "outbound.quantity"; + const int DEFAULT_OUTBOUND_TUNNELS_QUANTITY = 5; const int STREAM_REQUEST_TIMEOUT = 60; //in seconds typedef std::function stream)> StreamRequestComplete; diff --git a/Tunnel.cpp b/Tunnel.cpp index c44921c1..65638040 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -287,9 +287,9 @@ namespace tunnel return tunnel; } - std::shared_ptr Tunnels::CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops) + std::shared_ptr Tunnels::CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels) { - auto pool = std::make_shared (localDestination, numInboundHops, numOutboundHops); + auto pool = std::make_shared (localDestination, numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels); std::unique_lock l(m_PoolsMutex); m_Pools.push_back (pool); return pool; @@ -613,7 +613,7 @@ namespace tunnel LogPrint ("Creating zero hops inbound tunnel..."); CreateZeroHopsInboundTunnel (); if (!m_ExploratoryPool) - m_ExploratoryPool = CreateTunnelPool (&i2p::context, 2, 2); // 2-hop exploratory + m_ExploratoryPool = CreateTunnelPool (&i2p::context, 2, 2, 5, 5); // 2-hop exploratory, 5 tunnels return; } diff --git a/Tunnel.h b/Tunnel.h index cd8a1379..adf9a30b 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -141,7 +141,7 @@ namespace tunnel std::shared_ptr CreateTunnel (TunnelConfig * config, std::shared_ptr outboundTunnel = nullptr); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); - std::shared_ptr CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOuboundHops); + std::shared_ptr CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOuboundHops, int numInboundTunnels, int numOutboundTunnels); void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); diff --git a/TunnelPool.cpp b/TunnelPool.cpp index e7d75f11..8ed90403 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -11,9 +11,9 @@ namespace i2p { namespace tunnel { - TunnelPool::TunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops, int numTunnels): + TunnelPool::TunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): m_LocalDestination (localDestination), m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), - m_NumTunnels (numTunnels), m_IsActive (true) + m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_IsActive (true) { } @@ -160,7 +160,7 @@ namespace tunnel for (auto it : m_InboundTunnels) if (it->IsEstablished ()) num++; } - for (int i = num; i < m_NumTunnels; i++) + for (int i = num; i < m_NumInboundTunnels; i++) CreateInboundTunnel (); num = 0; @@ -169,7 +169,7 @@ namespace tunnel for (auto it : m_OutboundTunnels) if (it->IsEstablished ()) num++; } - for (int i = num; i < m_NumTunnels; i++) + for (int i = num; i < m_NumOutboundTunnels; i++) CreateOutboundTunnel (); } diff --git a/TunnelPool.h b/TunnelPool.h index 947bd3cc..407558f7 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -27,7 +27,7 @@ namespace tunnel { public: - TunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops, int numTunnels = 5); + TunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels); ~TunnelPool (); i2p::garlic::GarlicDestination * GetLocalDestination () const { return m_LocalDestination; }; @@ -64,7 +64,7 @@ namespace tunnel private: i2p::garlic::GarlicDestination * m_LocalDestination; - int m_NumInboundHops, m_NumOutboundHops, m_NumTunnels; + int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels; mutable std::mutex m_InboundTunnelsMutex; std::set, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first mutable std::mutex m_OutboundTunnelsMutex; From 0b2654f6b1069c9b0cb7a63a25c021327b66fe5f Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 May 2015 17:30:14 -0400 Subject: [PATCH 0438/6300] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index aba463f0..334dc876 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ License This project is licensed under the BSD 3-clause license, which can be found in the file LICENSE in the root of the project source code. +Donations +--------- + +BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY +LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 + Requirements for Linux/FreeBSD/OSX ---------------------------------- From 0ae7bbd34d2c68ebb2295d56eb38779b9aa1a208 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 May 2015 17:30:32 -0400 Subject: [PATCH 0439/6300] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 334dc876..1361cee2 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ LICENSE in the root of the project source code. Donations --------- -BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY -LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 +BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY +LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 Requirements for Linux/FreeBSD/OSX ---------------------------------- From 7c13194d5a78cb500b632b40da560ab2f6965f7b Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 May 2015 11:24:35 -0400 Subject: [PATCH 0440/6300] don't recalculate timestamp for each log message --- Log.cpp | 21 ++++++++++++++++++--- Log.h | 12 ++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Log.cpp b/Log.cpp index ac2ba756..2fccb7d6 100644 --- a/Log.cpp +++ b/Log.cpp @@ -1,5 +1,5 @@ -#include "Log.h" #include +#include "Log.h" Log * g_Log = nullptr; @@ -13,11 +13,26 @@ static const char * g_LogLevelStr[eNumLogLevels] = void LogMsg::Process() { - output << boost::posix_time::second_clock::local_time().time_of_day () << - "/" << g_LogLevelStr[level] << " - "; + auto& output = (log && log->GetLogStream ()) ? *log->GetLogStream () : std::cout; + if (log) + output << log->GetTimestamp (); + else + output << boost::posix_time::second_clock::local_time().time_of_day (); + output << "/" << g_LogLevelStr[level] << " - "; output << s.str(); } +const std::string& Log::GetTimestamp () +{ + auto ts = std::chrono::steady_clock::now (); + if (ts > m_LastTimestampUpdate + std::chrono::milliseconds (500)) // 0.5 second + { + m_LastTimestampUpdate = ts; + m_Timestamp = boost::posix_time::to_simple_string (boost::posix_time::second_clock::local_time().time_of_day ()); + } + return m_Timestamp; +} + void Log::Flush () { if (m_LogStream) diff --git a/Log.h b/Log.h index b77528c5..103b84ac 100644 --- a/Log.h +++ b/Log.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "Queue.h" enum LogLevel @@ -17,13 +18,14 @@ enum LogLevel eNumLogLevels }; +class Log; struct LogMsg { std::stringstream s; - std::ostream& output; + Log * log; LogLevel level; - LogMsg (std::ostream& o = std::cout, LogLevel l = eLogInfo): output (o), level (l) {}; + LogMsg (Log * l = nullptr, LogLevel lv = eLogInfo): log (l), level (lv) {}; void Process(); }; @@ -38,6 +40,7 @@ class Log: public i2p::util::MsgQueue void SetLogFile (const std::string& fullFilePath); void SetLogStream (std::ostream * logStream); std::ostream * GetLogStream () const { return m_LogStream; }; + const std::string& GetTimestamp (); private: @@ -46,6 +49,8 @@ class Log: public i2p::util::MsgQueue private: std::ostream * m_LogStream; + std::string m_Timestamp; + std::chrono::steady_clock::time_point m_LastTimestampUpdate; }; extern Log * g_Log; @@ -95,8 +100,7 @@ void LogPrint (std::stringstream& s, TValue arg, TArgs... args) template void LogPrint (LogLevel level, TArgs... args) { - LogMsg * msg = (g_Log && g_Log->GetLogStream ()) ? new LogMsg (*g_Log->GetLogStream (), level) : - new LogMsg (std::cout, level); + LogMsg * msg = new LogMsg (g_Log, level); LogPrint (msg->s, args...); msg->s << std::endl; if (g_Log) From 2442d0e9107751caa1db071da689bd3da50d0595 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 May 2015 12:19:20 -0400 Subject: [PATCH 0441/6300] moved UPnP instance to Transports. Use actual port from RouterContext --- Daemon.cpp | 13 +------------ Transports.cpp | 8 ++++++++ Transports.h | 8 ++++++++ UPnP.cpp | 29 +++++++++++++---------------- UPnP.h | 8 +++----- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index e3ae8bbc..77cd0899 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -18,11 +18,6 @@ #include "HTTPServer.h" #include "ClientContext.h" -#ifdef USE_UPNP -#include "UPnP.h" -#endif - - namespace i2p { namespace util @@ -122,10 +117,6 @@ namespace i2p LogPrint("Tunnels started"); i2p::client::context.Start (); LogPrint("Client started"); -#ifdef USE_UPNP - i2p::UPnP::upnpc.Start(); - LogPrint("UPnP module loaded"); -#endif return true; } @@ -142,9 +133,7 @@ namespace i2p LogPrint("NetDB stopped"); d.httpServer->Stop(); LogPrint("HTTP Server stopped"); -#ifdef USE_UPNP - i2p::UPnP::upnpc.Stop(); -#endif + StopLog (); delete d.httpServer; d.httpServer = nullptr; diff --git a/Transports.cpp b/Transports.cpp index 0cb4a970..f871607e 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -109,6 +109,10 @@ namespace transport void Transports::Start () { +#ifdef USE_UPNP + m_UPnP.Start (); + LogPrint(eLogInfo, "UPnP started"); +#endif m_DHKeysPairSupplier.Start (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); @@ -141,6 +145,10 @@ namespace transport void Transports::Stop () { +#ifdef USE_UPNP + m_UPnP.Stop (); + LogPrint(eLogInfo, "UPnP stopped"); +#endif m_PeerCleanupTimer.cancel (); m_Peers.clear (); if (m_SSUServer) diff --git a/Transports.h b/Transports.h index 00bf5b6f..11f05190 100644 --- a/Transports.h +++ b/Transports.h @@ -20,6 +20,10 @@ #include "I2NPProtocol.h" #include "Identity.h" +#ifdef USE_UPNP +#include "UPnP.h" +#endif + namespace i2p { namespace transport @@ -137,6 +141,10 @@ namespace transport uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes; uint64_t m_LastBandwidthUpdateTime; +#ifdef USE_UPNP + UPnP m_UPnP; +#endif + public: // for HTTP only diff --git a/UPnP.cpp b/UPnP.cpp index e5b8ff79..227bbbd7 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -39,10 +39,8 @@ typedef void (*upnp_FreeUPNPUrlsFunc) (struct UPNPUrls *); namespace i2p { -namespace UPnP +namespace transport { - UPnP upnpc; - UPnP::UPnP () : m_Thread (nullptr) , m_IsModuleLoaded (false) { } @@ -99,15 +97,14 @@ namespace UPnP { if (!address.host.is_v6 ()) { - m_Port = std::to_string (util::config::GetArg ("-port", address.port)); Discover (); if (address.transportStyle == data::RouterInfo::eTransportSSU ) { - TryPortMapping (I2P_UPNP_UDP); + TryPortMapping (I2P_UPNP_UDP, address.port); } else if (address.transportStyle == data::RouterInfo::eTransportNTCP ) { - TryPortMapping (I2P_UPNP_TCP); + TryPortMapping (I2P_UPNP_TCP, address.port); } } } @@ -169,9 +166,9 @@ namespace UPnP } } - void UPnP::TryPortMapping (int type) + void UPnP::TryPortMapping (int type, int port) { - std::string strType; + std::string strType, strPort (std::to_string (port)); switch (type) { case I2P_UPNP_TCP: @@ -192,19 +189,19 @@ namespace UPnP #endif #ifndef UPNPDISCOVER_SUCCESS /* miniupnpc 1.5 */ - r = UPNP_AddPortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_Port.c_str (), m_Port.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0); + r = UPNP_AddPortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0); #else /* miniupnpc 1.6 */ - r = UPNP_AddPortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_Port.c_str (), m_Port.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0"); + r = UPNP_AddPortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0"); #endif if (r!=UPNPCOMMAND_SUCCESS) { - LogPrint ("AddPortMapping (", m_Port.c_str () ,", ", m_Port.c_str () ,", ", m_NetworkAddr, ") failed with code ", r); + LogPrint ("AddPortMapping (", strPort.c_str () ,", ", strPort.c_str () ,", ", m_NetworkAddr, ") failed with code ", r); return; } else { - LogPrint ("UPnP Port Mapping successful. (", m_NetworkAddr ,":", m_Port.c_str(), " type ", strType.c_str () ," -> ", m_externalIPAddress ,":", m_Port.c_str() ,")"); + LogPrint ("UPnP Port Mapping successful. (", m_NetworkAddr ,":", strPort.c_str(), " type ", strType.c_str () ," -> ", m_externalIPAddress ,":", strPort.c_str() ,")"); return; } sleep(20*60); @@ -212,15 +209,15 @@ namespace UPnP } catch (boost::thread_interrupted) { - CloseMapping(type); + CloseMapping(type, port); Close(); throw; } } - void UPnP::CloseMapping (int type) + void UPnP::CloseMapping (int type, int port) { - std::string strType; + std::string strType, strPort (std::to_string (port)); switch (type) { case I2P_UPNP_TCP: @@ -236,7 +233,7 @@ namespace UPnP #else upnp_UPNP_DeletePortMappingFunc UPNP_DeletePortMappingFunc = (upnp_UPNP_DeletePortMappingFunc) dlsym (m_Module, "UPNP_DeletePortMapping"); #endif - r = UPNP_DeletePortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_Port.c_str (), strType.c_str (), 0); + r = UPNP_DeletePortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), 0); LogPrint ("UPNP_DeletePortMapping() returned : ", r, "\n"); } diff --git a/UPnP.h b/UPnP.h index 8dc62b8d..4884c423 100644 --- a/UPnP.h +++ b/UPnP.h @@ -19,7 +19,7 @@ namespace i2p { -namespace UPnP +namespace transport { class UPnP { @@ -33,8 +33,8 @@ namespace UPnP void Stop (); void Discover (); - void TryPortMapping (int type); - void CloseMapping (int type); + void TryPortMapping (int type, int port); + void CloseMapping (int type, int port); private: void Run (); @@ -49,14 +49,12 @@ namespace UPnP char m_NetworkAddr[64]; char m_externalIPAddress[40]; bool m_IsModuleLoaded; - std::string m_Port = std::to_string (util::config::GetArg ("-port", 17070)); #ifndef _WIN32 void *m_Module; #else HINSTANCE *m_Module; #endif }; - extern UPnP upnpc; } } From 4c91d08ceabac3477cd0ca0b9ff543d01a674b97 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 May 2015 16:17:48 -0400 Subject: [PATCH 0442/6300] pass TunnelConfig as shared_ptr --- Tunnel.cpp | 11 +++++------ Tunnel.h | 12 ++++++------ TunnelConfig.h | 14 +++++++------- TunnelPool.cpp | 4 ++-- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 65638040..59a9de2b 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -17,14 +17,13 @@ namespace i2p namespace tunnel { - Tunnel::Tunnel (TunnelConfig * config): + Tunnel::Tunnel (std::shared_ptr config): m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false) { } Tunnel::~Tunnel () { - delete m_Config; } void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) @@ -567,7 +566,7 @@ namespace tunnel if (!inboundTunnel || !router) return; LogPrint ("Creating one hop outbound tunnel..."); CreateTunnel ( - new TunnelConfig (std::vector > { router }, + std::make_shared (std::vector > { router }, inboundTunnel->GetTunnelConfig ()) ); } @@ -623,7 +622,7 @@ namespace tunnel auto router = i2p::data::netdb.GetRandomRouter (); LogPrint ("Creating one hop inbound tunnel..."); CreateTunnel ( - new TunnelConfig (std::vector > { router }) + std::make_shared (std::vector > { router }) ); } } @@ -673,7 +672,7 @@ namespace tunnel } template - std::shared_ptr Tunnels::CreateTunnel (TunnelConfig * config, std::shared_ptr outboundTunnel) + std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) { auto newTunnel = std::make_shared (config); uint32_t replyMsgID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); @@ -724,7 +723,7 @@ namespace tunnel void Tunnels::CreateZeroHopsInboundTunnel () { CreateTunnel ( - new TunnelConfig (std::vector > + std::make_shared (std::vector > { i2p::context.GetSharedRouterInfo () })); diff --git a/Tunnel.h b/Tunnel.h index adf9a30b..be15aa91 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -45,12 +45,12 @@ namespace tunnel { public: - Tunnel (TunnelConfig * config); + Tunnel (std::shared_ptr config); ~Tunnel (); void Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel = nullptr); - TunnelConfig * GetTunnelConfig () const { return m_Config; } + std::shared_ptr GetTunnelConfig () const { return m_Config; } TunnelState GetState () const { return m_State; }; void SetState (TunnelState state) { m_State = state; }; bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; @@ -71,7 +71,7 @@ namespace tunnel private: - TunnelConfig * m_Config; + std::shared_ptr m_Config; std::shared_ptr m_Pool; // pool, tunnel belongs to, or null TunnelState m_State; bool m_IsRecreated; @@ -81,7 +81,7 @@ namespace tunnel { public: - OutboundTunnel (TunnelConfig * config): Tunnel (config), m_Gateway (this) {}; + OutboundTunnel (std::shared_ptr config): Tunnel (config), m_Gateway (this) {}; void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, i2p::I2NPMessage * msg); void SendTunnelDataMsg (const std::vector& msgs); // multiple messages @@ -103,7 +103,7 @@ namespace tunnel { public: - InboundTunnel (TunnelConfig * config): Tunnel (config), m_Endpoint (true) {}; + InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; void HandleTunnelDataMsg (I2NPMessage * msg); size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; @@ -138,7 +138,7 @@ namespace tunnel void PostTunnelData (I2NPMessage * msg); void PostTunnelData (const std::vector& msgs); template - std::shared_ptr CreateTunnel (TunnelConfig * config, std::shared_ptr outboundTunnel = nullptr); + std::shared_ptr CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel = nullptr); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); std::shared_ptr CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOuboundHops, int numInboundTunnels, int numOutboundTunnels); diff --git a/TunnelConfig.h b/TunnelConfig.h index 49a1d622..76434f9c 100644 --- a/TunnelConfig.h +++ b/TunnelConfig.h @@ -84,7 +84,7 @@ namespace tunnel } } - void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID) + void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID) const { uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); @@ -113,7 +113,7 @@ namespace tunnel TunnelConfig (std::vector > peers, - const TunnelConfig * replyTunnelConfig = nullptr) // replyTunnelConfig=nullptr means inbound + std::shared_ptr replyTunnelConfig = nullptr) // replyTunnelConfig=nullptr means inbound { TunnelHopConfig * prev = nullptr; for (auto it: peers) @@ -189,9 +189,9 @@ namespace tunnel s << ":me"; } - TunnelConfig * Invert () const + std::shared_ptr Invert () const { - TunnelConfig * newConfig = new TunnelConfig (); + auto newConfig = new TunnelConfig (); TunnelHopConfig * hop = m_FirstHop, * nextNewHop = nullptr; while (hop) { @@ -214,10 +214,10 @@ namespace tunnel hop = hop->next; } - return newConfig; + return std::shared_ptr(newConfig); } - TunnelConfig * Clone (const TunnelConfig * replyTunnelConfig = nullptr) const + std::shared_ptr Clone (std::shared_ptr replyTunnelConfig = nullptr) const { std::vector > peers; TunnelHopConfig * hop = m_FirstHop; @@ -226,7 +226,7 @@ namespace tunnel peers.push_back (hop->router); hop = hop->next; } - return new TunnelConfig (peers, replyTunnelConfig); + return std::make_shared (peers, replyTunnelConfig); } private: diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 8ed90403..82c66fe6 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -320,7 +320,7 @@ namespace tunnel hops.push_back (hop); } std::reverse (hops.begin (), hops.end ()); - auto tunnel = tunnels.CreateTunnel (new TunnelConfig (hops), outboundTunnel); + auto tunnel = tunnels.CreateTunnel (std::make_shared (hops), outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); } @@ -368,7 +368,7 @@ namespace tunnel } auto tunnel = tunnels.CreateTunnel ( - new TunnelConfig (hops, inboundTunnel->GetTunnelConfig ())); + std::make_shared (hops, inboundTunnel->GetTunnelConfig ())); tunnel->SetTunnelPool (shared_from_this ()); } else From 6bad2daa623f95fe5a675cd6583285db44256ae4 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 May 2015 19:18:00 -0400 Subject: [PATCH 0443/6300] fixed build errors for gcc 4.6 --- Log.cpp | 4 ++++ Log.h | 6 +++++- NetDb.h | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Log.cpp b/Log.cpp index 2fccb7d6..2507258e 100644 --- a/Log.cpp +++ b/Log.cpp @@ -24,7 +24,11 @@ void LogMsg::Process() const std::string& Log::GetTimestamp () { +#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) + auto ts = std::chrono::monotonic_clock::now (); +#else auto ts = std::chrono::steady_clock::now (); +#endif if (ts > m_LastTimestampUpdate + std::chrono::milliseconds (500)) // 0.5 second { m_LastTimestampUpdate = ts; diff --git a/Log.h b/Log.h index 103b84ac..5fda7b21 100644 --- a/Log.h +++ b/Log.h @@ -50,7 +50,11 @@ class Log: public i2p::util::MsgQueue std::ostream * m_LogStream; std::string m_Timestamp; - std::chrono::steady_clock::time_point m_LastTimestampUpdate; +#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) // gcc 4.6 + std::chrono::monotonic_clock::time_point m_LastTimestampUpdate; +#else + std::chrono::steady_clock::time_point m_LastTimestampUpdate; +#endif }; extern Log * g_Log; diff --git a/NetDb.h b/NetDb.h index 44efa454..1de70000 100644 --- a/NetDb.h +++ b/NetDb.h @@ -92,7 +92,7 @@ namespace data Reseeder * m_Reseeder; - friend NetDbRequests; + friend class NetDbRequests; NetDbRequests m_Requests; static const char m_NetDbPath[]; From 846128a791f0a7fc3b3504d4daa0771c80d2fd82 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 7 May 2015 03:40:19 +0000 Subject: [PATCH 0444/6300] * add gcc5 to supported compilers --- Makefile.linux | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Makefile.linux b/Makefile.linux index 62445902..8af84edf 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -8,15 +8,17 @@ INCFLAGS = ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. -# detect proper flag for c++11 support by gcc +# detect proper flag for c++11 support by compilers CXXVER := $(shell $(CXX) -dumpversion) -ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # >= 4.10 +ifeq ($(shell expr match $(CXX) 'clang'),5) + NEEDED_CXXFLAGS += -std=c++11 +else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10 NEEDED_CXXFLAGS += -std=c++11 else ifeq ($(shell expr match ${CXXVER} "4\.[7-9]"),3) # >= 4.7 NEEDED_CXXFLAGS += -std=c++11 else ifeq ($(shell expr match ${CXXVER} "4\.6"),3) # = 4.6 NEEDED_CXXFLAGS += -std=c++0x -else ifeq ($(shell expr match $(CXX) 'clang'),5) +else ifeq ($(shell expr match ${CXXVER} "5\.[0-9]"),3) # gcc >= 5.0 NEEDED_CXXFLAGS += -std=c++11 else # not supported $(error Compiler too old) From 39c346df1033dcf1eb7707bce4490d3b2b8646d4 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 7 May 2015 16:03:12 -0400 Subject: [PATCH 0445/6300] created paired inbound tunnel after outbound --- TunnelPool.cpp | 16 +++++++++++++--- TunnelPool.h | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 82c66fe6..cc7b4eb0 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -66,8 +66,11 @@ namespace tunnel void TunnelPool::TunnelCreated (std::shared_ptr createdTunnel) { if (!m_IsActive) return; - std::unique_lock l(m_OutboundTunnelsMutex); - m_OutboundTunnels.insert (createdTunnel); + { + std::unique_lock l(m_OutboundTunnelsMutex); + m_OutboundTunnels.insert (createdTunnel); + } + CreatePairedInboundTunnel (createdTunnel); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) @@ -389,6 +392,13 @@ namespace tunnel } else LogPrint ("Can't re-create outbound tunnel. No inbound tunnels found"); - } + } + + void TunnelPool::CreatePairedInboundTunnel (std::shared_ptr outboundTunnel) + { + LogPrint (eLogInfo, "Creating paired inbound tunnel..."); + auto tunnel = tunnels.CreateTunnel (outboundTunnel->GetTunnelConfig ()->Invert (), outboundTunnel); + tunnel->SetTunnelPool (shared_from_this ()); + } } } diff --git a/TunnelPool.h b/TunnelPool.h index 407558f7..5f2dd5ce 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -57,6 +57,7 @@ namespace tunnel void CreateInboundTunnel (); void CreateOutboundTunnel (); + void CreatePairedInboundTunnel (std::shared_ptr outboundTunnel); template typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; From 188f1fcff800dde41cf2e2f6af26fade135be0f8 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 8 May 2015 14:07:33 -0400 Subject: [PATCH 0446/6300] rewrite tunnel path inversion code --- TunnelConfig.h | 55 +++++++++++++++++++------------------------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/TunnelConfig.h b/TunnelConfig.h index 76434f9c..d9827fe8 100644 --- a/TunnelConfig.h +++ b/TunnelConfig.h @@ -107,7 +107,7 @@ namespace tunnel } }; - class TunnelConfig + class TunnelConfig: public std::enable_shared_from_this { public: @@ -170,10 +170,24 @@ namespace tunnel return num; } + bool IsInbound () const { return m_FirstHop->isGateway; } + + std::vector > GetPeers () const + { + std::vector > peers; + TunnelHopConfig * hop = m_FirstHop; + while (hop) + { + peers.push_back (hop->router); + hop = hop->next; + } + return peers; + } + void Print (std::stringstream& s) const { TunnelHopConfig * hop = m_FirstHop; - if (!m_FirstHop->isGateway) + if (!IsInbound ()) // outbound s << "me"; s << "-->" << m_FirstHop->tunnelID; while (hop) @@ -191,42 +205,15 @@ namespace tunnel std::shared_ptr Invert () const { - auto newConfig = new TunnelConfig (); - TunnelHopConfig * hop = m_FirstHop, * nextNewHop = nullptr; - while (hop) - { - TunnelHopConfig * newHop = new TunnelHopConfig (hop->router); - if (nextNewHop) - newHop->SetNext (nextNewHop); - nextNewHop = newHop; - newHop->isEndpoint = hop->isGateway; - newHop->isGateway = hop->isEndpoint; - - if (!hop->prev) // first hop - { - newConfig->m_LastHop = newHop; - if (hop->isGateway) // inbound tunnel - newHop->SetReplyHop (m_FirstHop); // use it as reply tunnel - else - newHop->SetNextRouter (i2p::context.GetSharedRouterInfo ()); - } - if (!hop->next) newConfig->m_FirstHop = newHop; // last hop - - hop = hop->next; - } - return std::shared_ptr(newConfig); + auto peers = GetPeers (); + std::reverse (peers.begin (), peers.end ()); + // we use ourself as reply tunnel for outbound tunnel + return IsInbound () ? std::make_shared(peers, shared_from_this ()) : std::make_shared(peers); } std::shared_ptr Clone (std::shared_ptr replyTunnelConfig = nullptr) const { - std::vector > peers; - TunnelHopConfig * hop = m_FirstHop; - while (hop) - { - peers.push_back (hop->router); - hop = hop->next; - } - return std::make_shared (peers, replyTunnelConfig); + return std::make_shared (GetPeers (), replyTunnelConfig); } private: From 7b5a7e10a962a9ec57cb7166208c21855b6fc22b Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 8 May 2015 21:42:28 -0400 Subject: [PATCH 0447/6300] fixed log crash at shutdown --- Log.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Log.h b/Log.h index 5fda7b21..ea821954 100644 --- a/Log.h +++ b/Log.h @@ -63,9 +63,10 @@ inline void StartLog (const std::string& fullFilePath) { if (!g_Log) { - g_Log = new Log (); + auto log = new Log (); if (fullFilePath.length () > 0) - g_Log->SetLogFile (fullFilePath); + log->SetLogFile (fullFilePath); + g_Log = log; } } @@ -73,9 +74,10 @@ inline void StartLog (std::ostream * s) { if (!g_Log) { - g_Log = new Log (); + auto log = new Log (); if (s) - g_Log->SetLogStream (s); + log->SetLogStream (s); + g_Log = log; } } @@ -83,8 +85,10 @@ inline void StopLog () { if (g_Log) { - delete g_Log; + auto log = g_Log; g_Log = nullptr; + log->Stop (); + delete log; } } From ec772c5d463f203140f8dfa462dcf5ef8ce325aa Mon Sep 17 00:00:00 2001 From: David Date: Sat, 9 May 2015 19:25:11 -0400 Subject: [PATCH 0448/6300] typo: Gralic -> Garlic --- Garlic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Garlic.cpp b/Garlic.cpp index 261619eb..3afde6a8 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -521,7 +521,7 @@ namespace garlic buf += 3; // Certificate if (buf - buf1 > (int)len) { - LogPrint (eLogError, "Gralic clove is too long"); + LogPrint (eLogError, "Garlic clove is too long"); break; } } From c266cff956a7b78f3a5d0d79727be7a5c7f27412 Mon Sep 17 00:00:00 2001 From: apprb Date: Mon, 11 May 2015 15:56:13 +0600 Subject: [PATCH 0449/6300] CMakeLists.txt: compilation speed up --- build/CMakeLists.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index c91eb703..d753c58f 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -42,9 +42,11 @@ set (COMMON_SRC "${CMAKE_SOURCE_DIR}/base64.cpp" "${CMAKE_SOURCE_DIR}/util.cpp" "${CMAKE_SOURCE_DIR}/Datagram.cpp" - "${CMAKE_SOURCE_DIR}/Signature.cpp" + "${CMAKE_SOURCE_DIR}/Signature.cpp" ) +add_library(common ${COMMON_SRC}) + set (DAEMON_SRC "${CMAKE_SOURCE_DIR}/BOB.cpp" "${CMAKE_SOURCE_DIR}/ClientContext.cpp" @@ -156,7 +158,7 @@ message(STATUS "---------------------------------------") include(GNUInstallDirs) if (WITH_BINARY) - add_executable ( "${PROJECT_NAME}-bin" ${COMMON_SRC} ${DAEMON_SRC}) + add_executable ( "${PROJECT_NAME}-bin" ${DAEMON_SRC} ) set_target_properties("${PROJECT_NAME}-bin" PROPERTIES OUTPUT_NAME "${PROJECT_NAME}") if (WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -168,12 +170,13 @@ if (WITH_BINARY) set_target_properties("${PROJECT_NAME}-bin" PROPERTIES LINK_FLAGS "-static" ) endif () - target_link_libraries( "${PROJECT_NAME}-bin" ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) + target_link_libraries( "${PROJECT_NAME}-bin" common ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) install(TARGETS "${PROJECT_NAME}-bin" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) endif () if (WITH_LIBRARY) - add_library(${PROJECT_NAME} SHARED ${COMMON_SRC} ${LIBRARY_SRC}) + add_library(${PROJECT_NAME} SHARED ${LIBRARY_SRC}) + target_link_libraries( ${PROJECT_NAME} common ) install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) endif () From 5cd557ef9ddad37cbdffca514bab26e7d1a4df8d Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 11 May 2015 12:53:08 -0400 Subject: [PATCH 0450/6300] check for I2NP message buffer boudary --- I2NPProtocol.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 588332a3..05b98882 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -65,8 +65,13 @@ namespace i2p I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID) { I2NPMessage * msg = NewI2NPMessage (len); - memcpy (msg->GetPayload (), buf, len); - msg->len += len; + if (msg->len + len < msg->maxLen) + { + memcpy (msg->GetPayload (), buf, len); + msg->len += len; + } + else + LogPrint (eLogError, "I2NP message length ", len, " exceeds max length"); FillI2NPMessageHeader (msg, msgType, replyMsgID); return msg; } @@ -74,9 +79,14 @@ namespace i2p I2NPMessage * CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from) { I2NPMessage * msg = NewI2NPMessage (); - memcpy (msg->GetBuffer (), buf, len); - msg->len = msg->offset + len; - msg->from = from; + if (msg->offset + len < msg->maxLen) + { + memcpy (msg->GetBuffer (), buf, len); + msg->len = msg->offset + len; + msg->from = from; + } + else + LogPrint (eLogError, "I2NP message length ", len, " exceeds max length"); return msg; } From 2b797fcd540b1e39cc2aaf617f80ac2c80c31c93 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 11 May 2015 15:17:43 -0400 Subject: [PATCH 0451/6300] use shared_ptr for NetDb's I2NPMessages --- I2NPProtocol.cpp | 5 +++++ I2NPProtocol.h | 2 ++ NetDb.cpp | 21 +++++++-------------- NetDb.h | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 05b98882..4ad95d9e 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -36,6 +36,11 @@ namespace i2p delete msg; } + std::shared_ptr ToSharedI2NPMessage (I2NPMessage * msg) + { + return std::shared_ptr(msg, DeleteI2NPMessage); + } + static std::atomic I2NPmsgID(0); // TODO: create class void FillI2NPMessageHeader (I2NPMessage * msg, I2NPMessageType msgType, uint32_t replyMsgID) { diff --git a/I2NPProtocol.h b/I2NPProtocol.h index fde33186..093d66ed 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -196,6 +196,8 @@ namespace tunnel I2NPMessage * NewI2NPShortMessage (); I2NPMessage * NewI2NPMessage (size_t len); void DeleteI2NPMessage (I2NPMessage * msg); + std::shared_ptr ToSharedI2NPMessage (I2NPMessage * msg); + void FillI2NPMessageHeader (I2NPMessage * msg, I2NPMessageType msgType, uint32_t replyMsgID = 0); void RenewI2NPMessageHeader (I2NPMessage * msg); I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID = 0); diff --git a/NetDb.cpp b/NetDb.cpp index 6408b7f8..0ec8c41d 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -102,18 +102,18 @@ namespace data { case eI2NPDatabaseStore: LogPrint ("DatabaseStore"); - HandleDatabaseStoreMsg (msg); + HandleDatabaseStoreMsg (ToSharedI2NPMessage (msg)); break; case eI2NPDatabaseSearchReply: LogPrint ("DatabaseSearchReply"); - HandleDatabaseSearchReplyMsg (msg); + HandleDatabaseSearchReplyMsg (ToSharedI2NPMessage (msg)); break; case eI2NPDatabaseLookup: LogPrint ("DatabaseLookup"); - HandleDatabaseLookupMsg (msg); + HandleDatabaseLookupMsg (ToSharedI2NPMessage (msg)); break; default: // WTF? - LogPrint ("NetDb: unexpected message type ", msg->GetTypeID ()); + LogPrint (eLogError, "NetDb: unexpected message type ", msg->GetTypeID ()); i2p::HandleI2NPMessage (msg); } if (numMsgs > 100) break; @@ -469,7 +469,7 @@ namespace data } } - void NetDb::HandleDatabaseStoreMsg (I2NPMessage * m) + void NetDb::HandleDatabaseStoreMsg (std::shared_ptr m) { const uint8_t * buf = m->GetPayload (); size_t len = m->GetSize (); @@ -477,7 +477,6 @@ namespace data if (ident.IsZero ()) { LogPrint (eLogError, "Database store with zero ident. Dropped"); - i2p::DeleteI2NPMessage (m); return; } uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET); @@ -539,7 +538,6 @@ namespace data if (size > 2048 || size > len - offset) { LogPrint ("Invalid RouterInfo length ", (int)size); - i2p::DeleteI2NPMessage (m); return; } try @@ -562,10 +560,9 @@ namespace data LogPrint (eLogError, "DatabaseStore: ", ex.what ()); } } - i2p::DeleteI2NPMessage (m); } - void NetDb::HandleDatabaseSearchReplyMsg (I2NPMessage * msg) + void NetDb::HandleDatabaseSearchReplyMsg (std::shared_ptr msg) { uint8_t * buf = msg->GetPayload (); char key[48]; @@ -652,18 +649,15 @@ namespace data else LogPrint ("Bayan"); } - - i2p::DeleteI2NPMessage (msg); } - void NetDb::HandleDatabaseLookupMsg (I2NPMessage * msg) + void NetDb::HandleDatabaseLookupMsg (std::shared_ptr msg) { uint8_t * buf = msg->GetPayload (); IdentHash ident (buf); if (ident.IsZero ()) { LogPrint (eLogError, "DatabaseLookup for zero ident. Ignored"); - i2p::DeleteI2NPMessage (msg); return; } char key[48]; @@ -789,7 +783,6 @@ namespace data else transports.SendMessage (buf+32, replyMsg); } - i2p::DeleteI2NPMessage (msg); } void NetDb::Explore (int numDestinations) diff --git a/NetDb.h b/NetDb.h index 1de70000..1a7ee0de 100644 --- a/NetDb.h +++ b/NetDb.h @@ -41,9 +41,9 @@ namespace data void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr); - void HandleDatabaseStoreMsg (I2NPMessage * msg); - void HandleDatabaseSearchReplyMsg (I2NPMessage * msg); - void HandleDatabaseLookupMsg (I2NPMessage * msg); + void HandleDatabaseStoreMsg (std::shared_ptr msg); + void HandleDatabaseSearchReplyMsg (std::shared_ptr msg); + void HandleDatabaseLookupMsg (std::shared_ptr msg); std::shared_ptr GetRandomRouter () const; std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith) const; From dfd41385b1e7b545e2594affa4e964983884ff78 Mon Sep 17 00:00:00 2001 From: ipslot Date: Tue, 12 May 2015 13:27:02 +0600 Subject: [PATCH 0452/6300] Update Log.cpp set default log to std::cerr stream --- Log.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Log.cpp b/Log.cpp index 2507258e..0a561e0c 100644 --- a/Log.cpp +++ b/Log.cpp @@ -13,7 +13,7 @@ static const char * g_LogLevelStr[eNumLogLevels] = void LogMsg::Process() { - auto& output = (log && log->GetLogStream ()) ? *log->GetLogStream () : std::cout; + auto& output = (log && log->GetLogStream ()) ? *log->GetLogStream () : std::cerr; if (log) output << log->GetTimestamp (); else From eb559f7b6ab149a91959d0ffc1b3434125f6dac7 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 May 2015 11:51:03 -0400 Subject: [PATCH 0453/6300] excluded dead reseeds --- Reseed.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index b02839ec..8e13976d 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -26,12 +26,7 @@ namespace data { static std::vector httpReseedHostList = { - // "http://193.150.121.66/netDb/", // unstable - // "http://us.reseed.i2p2.no/", // misconfigured, not serving reseed data - // "http://jp.reseed.i2p2.no/", // Really outdated RIs "http://netdb.i2p2.no/", // only SU3 (v2) support - "http://i2p.mooo.com/netDb/", - "http://uk.reseed.i2p2.no/", "http://i2p-netdb.innovatio.no/" }; From 9510bba3b02f4e77947c1ca8be248e6e0d053261 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 May 2015 11:56:42 -0400 Subject: [PATCH 0454/6300] excluded dead reseeds --- Reseed.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Reseed.cpp b/Reseed.cpp index 8e13976d..9f137432 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -27,7 +27,8 @@ namespace data static std::vector httpReseedHostList = { "http://netdb.i2p2.no/", // only SU3 (v2) support - "http://i2p-netdb.innovatio.no/" + "http://i2p-netdb.innovatio.no/", + "http://193.150.121.66/netDb/" }; static std::vector httpsReseedHostList = { From 6d586bde6ca1cc27a1560dc52fbd9ada1d379e1e Mon Sep 17 00:00:00 2001 From: Kill Your TV Date: Thu, 14 May 2015 08:29:17 +0000 Subject: [PATCH 0455/6300] Note that Boost 1.58 works --- Win32/README-Build.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Win32/README-Build.txt b/Win32/README-Build.txt index dbdc30a2..da5b25de 100644 --- a/Win32/README-Build.txt +++ b/Win32/README-Build.txt @@ -3,8 +3,8 @@ Building i2pd for Windows Requirements for building: -* Visual Studio 2013 (tested with VS2013 Update 1, Update 3, and Update 4 RC) -* Boost (tested with 1.56 and 1.57) +* Visual Studio 2013 (tested with VS2013 Update 1, Update 3, and Update 4) +* Boost (tested with 1.56, 1.57, and 1.58) * Crypto++ (tested with 5.6.2) @@ -31,7 +31,7 @@ After Boost is compiled, set the environment variable `BOOST` to the directory Boost was installed to. If you followed the instructions outlined here, you should set it to `C:\Boost`. Additionally, set the BOOSTVER variable to the version of Boost that you're using, but instead of a '.' use a '_'. For -example, I have `BOOSTVER` set to `1_57`. +example, I have `BOOSTVER` set to `1_58`. Building Crypto++ ----------------- From 2a59ae294dc5bcc8a647448daaae2400edb4ee3e Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 17 May 2015 19:40:46 -0400 Subject: [PATCH 0456/6300] check length of garlic message --- Garlic.cpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index 3afde6a8..34ed84b3 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -365,23 +365,34 @@ namespace garlic { uint8_t * buf = msg->GetPayload (); uint32_t length = bufbe32toh (buf); + if (length > msg->GetLength ()) + { + LogPrint (eLogError, "Garlic message length ", length, " exceeds I2NP message length ", msg->GetLength ()); + DeleteI2NPMessage (msg); + return; + } buf += 4; // length auto it = m_Tags.find (SessionTag(buf)); if (it != m_Tags.end ()) { // tag found. Use AES - uint8_t iv[32]; // IV is first 16 bytes - CryptoPP::SHA256().CalculateDigest(iv, buf, 32); - it->second->SetIV (iv); - it->second->Decrypt (buf + 32, length - 32, buf + 32); - HandleAESBlock (buf + 32, length - 32, it->second, msg->from); - m_Tags.erase (it); // tag might be used only once + if (length >= 32) + { + uint8_t iv[32]; // IV is first 16 bytes + CryptoPP::SHA256().CalculateDigest(iv, buf, 32); + it->second->SetIV (iv); + it->second->Decrypt (buf + 32, length - 32, buf + 32); + HandleAESBlock (buf + 32, length - 32, it->second, msg->from); + } + else + LogPrint (eLogError, "Garlic message length ", length, " is less than 32 bytes"); + m_Tags.erase (it); // tag might be used only once } else { // tag not found. Use ElGamal ElGamalBlock elGamal; - if (i2p::crypto::ElGamalDecrypt (GetEncryptionPrivateKey (), buf, (uint8_t *)&elGamal, true)) + if (length >= 514 && i2p::crypto::ElGamalDecrypt (GetEncryptionPrivateKey (), buf, (uint8_t *)&elGamal, true)) { auto decryption = std::make_shared(); decryption->SetKey (elGamal.sessionKey); @@ -392,7 +403,7 @@ namespace garlic HandleAESBlock (buf + 514, length - 514, decryption, msg->from); } else - LogPrint ("Failed to decrypt garlic"); + LogPrint (eLogError, "Failed to decrypt garlic"); } DeleteI2NPMessage (msg); From 019af7bd3a2ac84f688edfef35a402f8a159ed78 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 20 May 2015 16:00:09 -0400 Subject: [PATCH 0457/6300] http server tunnel added --- ClientContext.cpp | 4 ++-- ClientContext.h | 1 + I2PTunnel.cpp | 5 +++++ I2PTunnel.h | 8 ++++++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 4df5fe40..9f14da90 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -295,7 +295,7 @@ namespace client LogPrint (eLogError, "I2P client tunnel with port ", port, " already exists"); numClientTunnels++; } - else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER) + else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP) { // mandatory params std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); @@ -306,7 +306,7 @@ namespace client std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); auto localDestination = LoadLocalDestination (keys, true); - auto serverTunnel = new I2PServerTunnel (host, port, localDestination, inPort); + I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ? new I2PServerTunnelHTTP (host, port, localDestination, inPort) : new I2PServerTunnel (host, port, localDestination, inPort); if (accessList.length () > 0) { std::set idents; diff --git a/ClientContext.h b/ClientContext.h index a034e541..0fe89fb8 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -20,6 +20,7 @@ namespace client const char I2P_TUNNELS_SECTION_TYPE[] = "type"; const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; + const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; const char I2P_CLIENT_TUNNEL_PORT[] = "port"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination"; const char I2P_CLIENT_TUNNEL_KEYS[] = "keys"; diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 4be5517b..4784f73b 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -309,5 +309,10 @@ namespace client conn->Connect (); } } + + I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& host, int port, std::shared_ptr localDestination, int inport): + I2PServerTunnel (host, port, localDestination, inport) + { + } } } diff --git a/I2PTunnel.h b/I2PTunnel.h index e512a319..e89b9088 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -104,6 +104,14 @@ namespace client std::set m_AccessList; bool m_IsAccessList; }; + + class I2PServerTunnelHTTP: public I2PServerTunnel + { + public: + + I2PServerTunnelHTTP (const std::string& host, int port, + std::shared_ptr localDestination, int inport = 0); + }; } } From 6a043649f5bce21e65c43702d04e15fb4384185c Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 27 May 2015 13:35:54 -0400 Subject: [PATCH 0458/6300] use random msg_id for I2NP messages --- I2NPProtocol.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 4ad95d9e..104fdef1 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -41,17 +41,13 @@ namespace i2p return std::shared_ptr(msg, DeleteI2NPMessage); } - static std::atomic I2NPmsgID(0); // TODO: create class void FillI2NPMessageHeader (I2NPMessage * msg, I2NPMessageType msgType, uint32_t replyMsgID) { msg->SetTypeID (msgType); if (replyMsgID) // for tunnel creation msg->SetMsgID (replyMsgID); - else - { - msg->SetMsgID (I2NPmsgID); - I2NPmsgID++; - } + else + msg->SetMsgID (i2p::context.GetRandomNumberGenerator ().GenerateWord32 ()); msg->SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); // TODO: 5 secs is a magic number msg->UpdateSize (); msg->UpdateChks (); @@ -61,8 +57,7 @@ namespace i2p { if (msg) { - msg->SetMsgID (I2NPmsgID); - I2NPmsgID++; + msg->SetMsgID (i2p::context.GetRandomNumberGenerator ().GenerateWord32 ()); msg->SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); } } From 8a3c276e663432e3220dbb7cf0a788ee2b136372 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 2 Jun 2015 13:03:22 -0400 Subject: [PATCH 0459/6300] I2PTunnelConnectionHTTP added --- I2PTunnel.cpp | 44 ++++++++++++++++++++++++++++++++++---------- I2PTunnel.h | 27 ++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 4784f73b..97507582 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -129,10 +129,13 @@ namespace client Terminate (); } else - { - boost::asio::async_write (*m_Socket, boost::asio::buffer (m_StreamBuffer, bytes_transferred), - std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); - } + Write (m_StreamBuffer, bytes_transferred); + } + + void I2PTunnelConnection::Write (const uint8_t * buf, size_t len) + { + m_Socket->async_send (boost::asio::buffer (buf, len), + std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); } void I2PTunnelConnection::HandleConnect (const boost::system::error_code& ecode) @@ -159,6 +162,13 @@ namespace client } } + I2PTunnelConnectionHTTP::I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, + std::shared_ptr socket, + const boost::asio::ip::tcp::endpoint& target, const std::string& host): + I2PTunnelConnection (owner, stream, socket, target) + { + } + /* This handler tries to stablish a connection with the desired server and dies if it fails to do so */ class I2PClientTunnelHandler: public I2PServiceHandler, public std::enable_shared_from_this { @@ -255,13 +265,15 @@ namespace client I2PServerTunnel::I2PServerTunnel (const std::string& address, int port, std::shared_ptr localDestination, int inport): - I2PService (localDestination), m_Endpoint (boost::asio::ip::address::from_string (address), port), m_IsAccessList (false) + I2PService (localDestination), m_Address (address), m_Port (port), m_IsAccessList (false) { m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port); } void I2PServerTunnel::Start () { + m_Endpoint.address (boost::asio::ip::address::from_string (m_Address)); + m_Endpoint.port (m_Port); Accept (); } @@ -304,15 +316,27 @@ namespace client return; } } - auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), m_Endpoint); - AddHandler (conn); - conn->Connect (); + CreateI2PConnection (stream); } } - I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& host, int port, std::shared_ptr localDestination, int inport): - I2PServerTunnel (host, port, localDestination, inport) + void I2PServerTunnel::CreateI2PConnection (std::shared_ptr stream) { + auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint ()); + AddHandler (conn); + conn->Connect (); + } + + I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& address, int port, std::shared_ptr localDestination, int inport): + I2PServerTunnel (address, port, localDestination, inport) + { + } + + void I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr stream) + { + auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), GetAddress ()); + AddHandler (conn); + conn->Connect (); } } } diff --git a/I2PTunnel.h b/I2PTunnel.h index e89b9088..be73594f 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -27,19 +27,20 @@ namespace client I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr leaseSet, int port = 0); // to I2P I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, - std::shared_ptr stream); // to I2P using simplified API :) + std::shared_ptr stream); // to I2P using simplified API I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, bool quiet = true); // from I2P ~I2PTunnelConnection (); void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0); void Connect (); - private: + protected: void Terminate (); void Receive (); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); + virtual void Write (const uint8_t * buf, size_t len); // can be overloaded void HandleWrite (const boost::system::error_code& ecode); void StreamReceive (); @@ -55,6 +56,15 @@ namespace client bool m_IsQuiet; // don't send destination }; + class I2PTunnelConnectionHTTP: public I2PTunnelConnection + { + public: + + I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, + std::shared_ptr socket, + const boost::asio::ip::tcp::endpoint& target, const std::string& host); + }; + class I2PClientTunnel: public TCPIPAcceptor { protected: @@ -92,13 +102,20 @@ namespace client void SetAccessList (const std::set& accessList); + const std::string& GetAddress() const { return m_Address; } + int GetPort () const { return m_Port; }; + const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; } + private: void Accept (); void HandleAccept (std::shared_ptr stream); + virtual void CreateI2PConnection (std::shared_ptr stream); private: + std::string m_Address; + int m_Port; boost::asio::ip::tcp::endpoint m_Endpoint; std::shared_ptr m_PortDestination; std::set m_AccessList; @@ -109,8 +126,12 @@ namespace client { public: - I2PServerTunnelHTTP (const std::string& host, int port, + I2PServerTunnelHTTP (const std::string& address, int port, std::shared_ptr localDestination, int inport = 0); + + private: + + void CreateI2PConnection (std::shared_ptr stream); }; } } From 68834df271e640371a851b8fa56687fa7aab5db5 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 2 Jun 2015 13:18:41 -0400 Subject: [PATCH 0460/6300] use addresses in server tunnel configuration --- I2PTunnel.cpp | 32 +++++++++++++++++++++++++++++--- I2PTunnel.h | 3 +++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 97507582..9bf5c658 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -272,9 +272,21 @@ namespace client void I2PServerTunnel::Start () { - m_Endpoint.address (boost::asio::ip::address::from_string (m_Address)); - m_Endpoint.port (m_Port); - Accept (); + m_Endpoint.port (m_Port); + boost::system::error_code ec; + auto addr = boost::asio::ip::address::from_string (m_Address, ec); + if (!ec) + { + m_Endpoint.address (addr); + Accept (); + } + else + { + auto resolver = std::make_shared(GetService ()); + resolver->async_resolve (boost::asio::ip::tcp::resolver::query (m_Address, ""), + std::bind (&I2PServerTunnel::HandleResolve, this, + std::placeholders::_1, std::placeholders::_2, resolver)); + } } void I2PServerTunnel::Stop () @@ -282,6 +294,20 @@ namespace client ClearHandlers (); } + void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + std::shared_ptr resolver) + { + if (!ecode) + { + auto addr = (*it).endpoint ().address (); + LogPrint (eLogInfo, "server tunnel ", (*it).host_name (), " has been resolved to ", addr); + m_Endpoint.address (addr); + Accept (); + } + else + LogPrint (eLogError, "Unable to resolve server tunnel address: ", ecode.message ()); + } + void I2PServerTunnel::SetAccessList (const std::set& accessList) { m_AccessList = accessList; diff --git a/I2PTunnel.h b/I2PTunnel.h index be73594f..f6f9d3ec 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -108,6 +108,9 @@ namespace client private: + void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + std::shared_ptr resolver); + void Accept (); void HandleAccept (std::shared_ptr stream); virtual void CreateI2PConnection (std::shared_ptr stream); From d7deb938c59d1ac150f26e95e58677e985c7f18c Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 2 Jun 2015 16:21:38 -0400 Subject: [PATCH 0461/6300] catch HTTP header of HTTP server tunnel connection --- I2PTunnel.cpp | 14 +++++++++++++- I2PTunnel.h | 10 ++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 9bf5c658..b6cabd30 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -165,10 +165,22 @@ namespace client I2PTunnelConnectionHTTP::I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& host): - I2PTunnelConnection (owner, stream, socket, target) + I2PTunnelConnection (owner, stream, socket, target), m_HeaderSent (false) { } + void I2PTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) + { + if (m_HeaderSent) + I2PTunnelConnection::Write (buf, len); + else + { + m_Header.write ((const char *)buf, len); + I2PTunnelConnection::Write ((uint8_t *)m_Header.str ().c_str (), m_Header.str ().length ()); + m_HeaderSent = true; + } + } + /* This handler tries to stablish a connection with the desired server and dies if it fails to do so */ class I2PClientTunnelHandler: public I2PServiceHandler, public std::enable_shared_from_this { diff --git a/I2PTunnel.h b/I2PTunnel.h index f6f9d3ec..127f5c4f 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "Identity.h" #include "Destination.h" @@ -63,6 +64,15 @@ namespace client I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& host); + + protected: + + void Write (const uint8_t * buf, size_t len); + + private: + + std::stringstream m_Header; + bool m_HeaderSent; }; class I2PClientTunnel: public TCPIPAcceptor From 09fd0baf7885acd02c5d107bca79f72f672438e2 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 3 Jun 2015 12:30:15 -0400 Subject: [PATCH 0462/6300] replace Host: for server http tunnels --- I2PTunnel.cpp | 32 +++++++++++++++++++++++++++----- I2PTunnel.h | 3 ++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index b6cabd30..a33a536d 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -165,7 +165,7 @@ namespace client I2PTunnelConnectionHTTP::I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& host): - I2PTunnelConnection (owner, stream, socket, target), m_HeaderSent (false) + I2PTunnelConnection (owner, stream, socket, target), m_Host (host), m_HeaderSent (false) { } @@ -174,10 +174,32 @@ namespace client if (m_HeaderSent) I2PTunnelConnection::Write (buf, len); else - { - m_Header.write ((const char *)buf, len); - I2PTunnelConnection::Write ((uint8_t *)m_Header.str ().c_str (), m_Header.str ().length ()); - m_HeaderSent = true; + { + m_InHeader.clear (); + m_InHeader.write ((const char *)buf, len); + std::string line; + bool endOfHeader = false; + while (!endOfHeader) + { + std::getline(m_InHeader, line); + if (!m_InHeader.fail ()) + { + if (line.find ("Host:") != std::string::npos) + m_OutHeader << "Host: " << m_Host << "\r\n"; + else + m_OutHeader << line << "\n"; + if (line == "\r") endOfHeader = true; + } + else + break; + } + + if (endOfHeader) + { + m_OutHeader << m_InHeader.str (); // data right after header + m_HeaderSent = true; + I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); + } } } diff --git a/I2PTunnel.h b/I2PTunnel.h index 127f5c4f..2071b89d 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -71,7 +71,8 @@ namespace client private: - std::stringstream m_Header; + std::string m_Host; + std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent; }; From abc05b448544b5088c2eb60e4a9d39baff0e0c43 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 4 Jun 2015 09:54:46 -0400 Subject: [PATCH 0463/6300] version 0.9.20 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 6ce0cfd7..d5406b0e 100644 --- a/version.h +++ b/version.h @@ -3,6 +3,6 @@ #define CODENAME "Purple" #define VERSION "0.9.0" -#define I2P_VERSION "0.9.19" +#define I2P_VERSION "0.9.20" #endif From da56397b392f7266f49803a8a4137415d4a2bc79 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 4 Jun 2015 11:31:22 -0400 Subject: [PATCH 0464/6300] fixed bug with zero-size clove --- Garlic.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index 34ed84b3..bbfaed91 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -225,9 +225,10 @@ namespace garlic if (newTags || m_LeaseSetUpdateStatus == eLeaseSetUpdated) // new tags created or leaseset updated { // clove is DeliveryStatus - size += CreateDeliveryStatusClove (payload + size, msgID); - if (size > 0) // successive? + auto cloveSize = CreateDeliveryStatusClove (payload + size, msgID); + if (cloveSize > 0) // successive? { + size += cloveSize; (*numCloves)++; if (newTags) // new tags created m_UnconfirmedTagsMsgs[msgID] = newTags; From 10e78785cd083ca51c6b61cc05f5026f4766157f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 5 Jun 2015 15:55:21 -0400 Subject: [PATCH 0465/6300] additional statistics for profiling --- Profiling.cpp | 43 ++++++++++++++++++++++++++++--------------- Profiling.h | 12 +++++++++--- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/Profiling.cpp b/Profiling.cpp index 70df0e76..390a1536 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -11,8 +11,8 @@ namespace data { RouterProfile::RouterProfile (const IdentHash& identHash): m_IdentHash (identHash), m_LastUpdateTime (boost::posix_time::second_clock::local_time()), - m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), - m_NumTunnelsNonReplied (0) + m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0), + m_NumTimesTaken (0), m_NumTimesRejected (0) { } @@ -33,11 +33,15 @@ namespace data participation.put (PEER_PROFILE_PARTICIPATION_AGREED, m_NumTunnelsAgreed); participation.put (PEER_PROFILE_PARTICIPATION_DECLINED, m_NumTunnelsDeclined); participation.put (PEER_PROFILE_PARTICIPATION_NON_REPLIED, m_NumTunnelsNonReplied); + boost::property_tree::ptree usage; + usage.put (PEER_PROFILE_USAGE_TAKEN, m_NumTimesTaken); + usage.put (PEER_PROFILE_USAGE_REJECTED, m_NumTimesRejected); // fill property tree boost::property_tree::ptree pt; pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime)); pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation); - + pt.put_child (PEER_PROFILE_SECTION_USAGE, usage); + // save to file auto path = i2p::util::filesystem::GetDefaultDataDir() / PEER_PROFILES_DIRECTORY; if (!boost::filesystem::exists (path)) @@ -102,6 +106,10 @@ namespace data m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0); m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0); m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0); + // read usage + auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE); + m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0); + m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0); } else *this = RouterProfile (m_IdentHash); @@ -128,27 +136,32 @@ namespace data UpdateTime (); } - bool RouterProfile::IsLowPartcipationRate (uint32_t elapsedTime) const + bool RouterProfile::IsLowPartcipationRate () const { - if (elapsedTime < 900) // if less than 15 minutes - return m_NumTunnelsAgreed < m_NumTunnelsDeclined; // 50% rate - else - return 3*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // 25% rate + return 4*m_NumTunnelsAgreed < m_NumTunnelsDeclined; // < 20% rate } - bool RouterProfile::IsLowReplyRate (uint32_t elapsedTime) const + bool RouterProfile::IsLowReplyRate () const { auto total = m_NumTunnelsAgreed + m_NumTunnelsDeclined; - if (elapsedTime < 300) // if less than 5 minutes - return m_NumTunnelsNonReplied > 5*(total + 1); - else - return !total && m_NumTunnelsNonReplied*15 > elapsedTime; + return m_NumTunnelsNonReplied > 10*(total + 1); } - bool RouterProfile::IsBad () const + bool RouterProfile::IsBad () { auto elapsedTime = (GetTime () - m_LastUpdateTime).total_seconds (); - return IsAlwaysDeclining () || IsLowPartcipationRate (elapsedTime) || IsLowReplyRate (elapsedTime); + if (elapsedTime > 1800) + { + m_NumTunnelsNonReplied = 0; // drop non-replied after 30 minutes of inactivity + if (elapsedTime > 14400) // drop agreed and declined after 4 hours of inactivity + { + m_NumTunnelsAgreed = 0; + m_NumTunnelsDeclined = 0; + } + } + auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () || IsLowReplyRate (); + if (isBad) m_NumTimesRejected++; else m_NumTimesTaken++; + return isBad; } std::shared_ptr GetRouterProfile (const IdentHash& identHash) diff --git a/Profiling.h b/Profiling.h index 3a5a0844..0690d6cb 100644 --- a/Profiling.h +++ b/Profiling.h @@ -13,11 +13,14 @@ namespace data const char PEER_PROFILE_PREFIX[] = "profile-"; // sections const char PEER_PROFILE_SECTION_PARTICIPATION[] = "participation"; + const char PEER_PROFILE_SECTION_USAGE[] = "usage"; // params const char PEER_PROFILE_LAST_UPDATE_TIME[] = "lastupdatetime"; const char PEER_PROFILE_PARTICIPATION_AGREED[] = "agreed"; const char PEER_PROFILE_PARTICIPATION_DECLINED[] = "declined"; const char PEER_PROFILE_PARTICIPATION_NON_REPLIED[] = "nonreplied"; + const char PEER_PROFILE_USAGE_TAKEN[] = "taken"; + const char PEER_PROFILE_USAGE_REJECTED[] = "rejected"; const int PEER_PROFILE_EXPIRATION_TIMEOUT = 72; // in hours (3 days) @@ -31,7 +34,7 @@ namespace data void Save (); void Load (); - bool IsBad () const; + bool IsBad (); void TunnelBuildResponse (uint8_t ret); void TunnelNonReplied (); @@ -42,8 +45,8 @@ namespace data void UpdateTime (); bool IsAlwaysDeclining () const { return !m_NumTunnelsAgreed && m_NumTunnelsDeclined >= 5; }; - bool IsLowPartcipationRate (uint32_t elapsedTime) const; - bool IsLowReplyRate (uint32_t elapsedTime) const; + bool IsLowPartcipationRate () const; + bool IsLowReplyRate () const; private: @@ -53,6 +56,9 @@ namespace data uint32_t m_NumTunnelsAgreed; uint32_t m_NumTunnelsDeclined; uint32_t m_NumTunnelsNonReplied; + // usage + uint32_t m_NumTimesTaken; + uint32_t m_NumTimesRejected; }; std::shared_ptr GetRouterProfile (const IdentHash& identHash); From a96482b186ec55a56ba7214ef3791a64b6142153 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 5 Jun 2015 21:15:02 -0400 Subject: [PATCH 0466/6300] skip missing sections --- Profiling.cpp | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Profiling.cpp b/Profiling.cpp index 390a1536..c5587f9a 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -101,15 +101,29 @@ namespace data m_LastUpdateTime = boost::posix_time::time_from_string (t); if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT) { - // read participations - auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION); - m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0); - m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0); - m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0); - // read usage - auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE); - m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0); - m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0); + try + { + // read participations + auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION); + m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0); + m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0); + m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0); + } + catch (boost::property_tree::ptree_bad_path& ex) + { + LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_PARTICIPATION); + } + try + { + // read usage + auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE); + m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0); + m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0); + } + catch (boost::property_tree::ptree_bad_path& ex) + { + LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_USAGE); + } } else *this = RouterProfile (m_IdentHash); From d9c0f52846331217823c82b04cc7931308c0d96b Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 5 Jun 2015 22:09:16 -0400 Subject: [PATCH 0467/6300] don't pick node for 5 minutes if declined --- Profiling.cpp | 12 +++++++++--- Profiling.h | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Profiling.cpp b/Profiling.cpp index c5587f9a..43dad505 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -11,6 +11,7 @@ namespace data { RouterProfile::RouterProfile (const IdentHash& identHash): m_IdentHash (identHash), m_LastUpdateTime (boost::posix_time::second_clock::local_time()), + m_LastDeclinedTime (boost::posix_time::min_date_time), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0), m_NumTimesTaken (0), m_NumTimesRejected (0) { @@ -137,11 +138,14 @@ namespace data void RouterProfile::TunnelBuildResponse (uint8_t ret) { + UpdateTime (); if (ret > 0) + { + m_LastDeclinedTime = m_LastUpdateTime; m_NumTunnelsDeclined++; + } else m_NumTunnelsAgreed++; - UpdateTime (); } void RouterProfile::TunnelNonReplied () @@ -163,7 +167,8 @@ namespace data bool RouterProfile::IsBad () { - auto elapsedTime = (GetTime () - m_LastUpdateTime).total_seconds (); + auto t = GetTime (); + auto elapsedTime = (t - m_LastUpdateTime).total_seconds (); if (elapsedTime > 1800) { m_NumTunnelsNonReplied = 0; // drop non-replied after 30 minutes of inactivity @@ -173,7 +178,8 @@ namespace data m_NumTunnelsDeclined = 0; } } - auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () || IsLowReplyRate (); + auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () || IsLowReplyRate () || + ((t - m_LastDeclinedTime).total_seconds () < 300); // declined in last 5 minutes if (isBad) m_NumTimesRejected++; else m_NumTimesTaken++; return isBad; } diff --git a/Profiling.h b/Profiling.h index 0690d6cb..09dc35c4 100644 --- a/Profiling.h +++ b/Profiling.h @@ -51,7 +51,7 @@ namespace data private: IdentHash m_IdentHash; - boost::posix_time::ptime m_LastUpdateTime; + boost::posix_time::ptime m_LastUpdateTime, m_LastDeclinedTime; // participation uint32_t m_NumTunnelsAgreed; uint32_t m_NumTunnelsDeclined; From a3b08c0016fa4889c378e70d21308c70ed03b7f0 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sat, 6 Jun 2015 03:33:15 -0500 Subject: [PATCH 0468/6300] Fix Win32 build with CMake and MSVC --- build/CMakeLists.txt | 27 +++++++++++++++++++++++--- build/cmake_modules/FindCryptoPP.cmake | 27 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index d753c58f..5d2e04bf 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -45,6 +45,10 @@ set (COMMON_SRC "${CMAKE_SOURCE_DIR}/Signature.cpp" ) +if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + list (APPEND COMMON_SRC "${CMAKE_SOURCE_DIR}/I2PEndian.cpp") +endif () + add_library(common ${COMMON_SRC}) set (DAEMON_SRC @@ -80,7 +84,9 @@ else () endif () # compiler flags customization (by vendor) +if (NOT MSVC) add_definitions ( "-Wall -Wextra -fPIC" ) +endif () # check for c++11 support include(CheckCXXCompilerFlag) @@ -90,7 +96,7 @@ if (CXX11_SUPPORTED) add_definitions( "-std=c++11" ) elseif (CXX0X_SUPPORTED) # gcc 4.6 add_definitions( "-std=c++0x" ) -else () +elseif (NOT MSVC) message(SEND_ERROR "C++11 standart not seems to be supported by compiler. Too old version?") endif () @@ -117,6 +123,7 @@ elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonLinux.cpp") elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonWin32.cpp") + list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32Service.cpp") endif () if (WITH_AESNI) @@ -126,6 +133,10 @@ endif() # libraries find_package ( Threads REQUIRED ) +if (WITH_STATIC) + set(Boost_USE_STATIC_LIBS ON) +endif () + find_package ( Boost COMPONENTS system filesystem regex program_options date_time REQUIRED ) if(NOT DEFINED Boost_INCLUDE_DIRS) message(SEND_ERROR "Boost is not found, or your boost version was bellow 1.46. Please download Boost!") @@ -159,7 +170,9 @@ include(GNUInstallDirs) if (WITH_BINARY) add_executable ( "${PROJECT_NAME}-bin" ${DAEMON_SRC} ) + if(NOT MSVC) # FIXME: incremental linker file name (.ilk) collision for dll & exe set_target_properties("${PROJECT_NAME}-bin" PROPERTIES OUTPUT_NAME "${PROJECT_NAME}") + endif() if (WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set_target_properties("${PROJECT_NAME}-bin" PROPERTIES LINK_FLAGS "-z relro -z now" ) @@ -168,15 +181,23 @@ if (WITH_BINARY) if (WITH_STATIC) set(BUILD_SHARED_LIBS OFF) set_target_properties("${PROJECT_NAME}-bin" PROPERTIES LINK_FLAGS "-static" ) + else() + add_definitions(-DBOOST_ALL_DYN_LINK) endif () target_link_libraries( "${PROJECT_NAME}-bin" common ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) install(TARGETS "${PROJECT_NAME}-bin" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) + install(FILES $ DESTINATION "bin" CONFIGURATIONS DEBUG) endif () if (WITH_LIBRARY) + if (MSVC) + # FIXME: DLL would not have any symbols unless we use __declspec(dllexport) through out the code + add_library(${PROJECT_NAME} ${LIBRARY_SRC}) + else () add_library(${PROJECT_NAME} SHARED ${LIBRARY_SRC}) - target_link_libraries( ${PROJECT_NAME} common ) - install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) + target_link_libraries( ${PROJECT_NAME} common ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES}) + endif () + install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif () diff --git a/build/cmake_modules/FindCryptoPP.cmake b/build/cmake_modules/FindCryptoPP.cmake index 7a8ac317..3435487d 100644 --- a/build/cmake_modules/FindCryptoPP.cmake +++ b/build/cmake_modules/FindCryptoPP.cmake @@ -12,6 +12,9 @@ else(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) /opt/local/include/crypto++ /opt/local/include/cryptopp $ENV{SystemDrive}/Crypto++/include + $ENV{CRYPTOPP} + $ENV{CRYPTOPP}/include + ${PROJECT_SOURCE_DIR}/../../cryptopp ) find_library(CRYPTO++_LIBRARIES NAMES cryptopp @@ -20,8 +23,32 @@ else(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) /usr/local/lib /opt/local/lib $ENV{SystemDrive}/Crypto++/lib + $ENV{CRYPTOPP}/lib ) + if(MSVC AND NOT CRYPTO++_LIBRARIES) # Give a chance for MSVC multiconfig + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(PLATFORM x64) + else() + set(PLATFORM Win32) + endif() + find_library(CRYPTO++_LIBRARIES_RELEASE NAMES cryptlib cryptopp + PATHS + $ENV{CRYPTOPP}/Win32/Output/Release + ${PROJECT_SOURCE_DIR}/../../cryptopp/${PLATFORM}/Output/Release + ) + find_library(CRYPTO++_LIBRARIES_DEBUG NAMES cryptlib cryptopp + PATHS + $ENV{CRYPTOPP}/Win32/Output/Debug + ${PROJECT_SOURCE_DIR}/../../cryptopp/${PLATFORM}/Output/Debug + ) + set(CRYPTO++_LIBRARIES + debug ${CRYPTO++_LIBRARIES_DEBUG} + optimized ${CRYPTO++_LIBRARIES_RELEASE} + CACHE PATH "Path to Crypto++ library" FORCE + ) + endif() + if(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) set(CRYPTO++_FOUND TRUE) message(STATUS "Found Crypto++: ${CRYPTO++_INCLUDE_DIR}, ${CRYPTO++_LIBRARIES}") From 2d3493a2257b82991efabfcd7c6a798f232575a9 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sat, 6 Jun 2015 12:34:06 -0500 Subject: [PATCH 0469/6300] Perhaps bitness detection is an introspection http://www.cmake.org/cmake/help/v3.0/command/find_library.html --- build/cmake_modules/FindCryptoPP.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build/cmake_modules/FindCryptoPP.cmake b/build/cmake_modules/FindCryptoPP.cmake index 3435487d..09b72184 100644 --- a/build/cmake_modules/FindCryptoPP.cmake +++ b/build/cmake_modules/FindCryptoPP.cmake @@ -33,14 +33,16 @@ else(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) set(PLATFORM Win32) endif() find_library(CRYPTO++_LIBRARIES_RELEASE NAMES cryptlib cryptopp + HINTS + ${PROJECT_SOURCE_DIR}/../../cryptopp/${PLATFORM}/Output/Release PATHS $ENV{CRYPTOPP}/Win32/Output/Release - ${PROJECT_SOURCE_DIR}/../../cryptopp/${PLATFORM}/Output/Release ) find_library(CRYPTO++_LIBRARIES_DEBUG NAMES cryptlib cryptopp + HINTS + ${PROJECT_SOURCE_DIR}/../../cryptopp/${PLATFORM}/Output/Debug PATHS $ENV{CRYPTOPP}/Win32/Output/Debug - ${PROJECT_SOURCE_DIR}/../../cryptopp/${PLATFORM}/Output/Debug ) set(CRYPTO++_LIBRARIES debug ${CRYPTO++_LIBRARIES_DEBUG} From 046ffd8648c3f631ab14c820f1286226eff6e7bf Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sat, 6 Jun 2015 13:53:22 -0500 Subject: [PATCH 0470/6300] Fix UPnP for Win32 * find_package for headers * Swap includes order to pass compilation with MSVC 2013 * Enforce SO address resolution checks * Change SO/DLL name on Windows * Portable sleep from C++11 This closes #186 --- UPnP.cpp | 142 ++++++++++-------------- UPnP.h | 2 +- build/CMakeLists.txt | 15 ++- build/cmake_modules/FindMiniUPnPc.cmake | 25 +++++ 4 files changed, 100 insertions(+), 84 deletions(-) create mode 100644 build/cmake_modules/FindMiniUPnPc.cmake diff --git a/UPnP.cpp b/UPnP.cpp index 227bbbd7..21f7e76d 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -2,15 +2,19 @@ #include #include -#ifdef _WIN32 -#include -#endif - #include #include #include +#ifdef _WIN32 +#include +#define dlsym GetProcAddress +#else +#include +#endif + #include "Log.h" + #include "RouterContext.h" #include "UPnP.h" #include "NetDb.h" @@ -18,24 +22,36 @@ #include #include -#include +// These are per-process and are safe to reuse for all threads #ifndef UPNPDISCOVER_SUCCESS /* miniupnpc 1.5 */ -typedef UPNPDev* (*upnp_upnpDiscoverFunc) (int, const char *, const char *, int); -typedef int (*upnp_UPNP_AddPortMappingFunc) (const char *, const char *, const char *, const char *, +UPNPDev* (*upnpDiscoverFunc) (int, const char *, const char *, int); +int (*UPNP_AddPortMappingFunc) (const char *, const char *, const char *, const char *, const char *, const char *, const char *, const char *); #else /* miniupnpc 1.6 */ -typedef UPNPDev* (*upnp_upnpDiscoverFunc) (int, const char *, const char *, int, int, int *); -typedef int (*upnp_UPNP_AddPortMappingFunc) (const char *, const char *, const char *, const char *, +UPNPDev* (*upnpDiscoverFunc) (int, const char *, const char *, int, int, int *); +int (*UPNP_AddPortMappingFunc) (const char *, const char *, const char *, const char *, const char *, const char *, const char *, const char *, const char *); #endif -typedef int (*upnp_UPNP_GetValidIGDFunc) (struct UPNPDev *, struct UPNPUrls *, struct IGDdatas *, char *, int); -typedef int (*upnp_UPNP_GetExternalIPAddressFunc) (const char *, const char *, char *); -typedef int (*upnp_UPNP_DeletePortMappingFunc) (const char *, const char *, const char *, const char *, const char *); -typedef void (*upnp_freeUPNPDevlistFunc) (struct UPNPDev *); -typedef void (*upnp_FreeUPNPUrlsFunc) (struct UPNPUrls *); +int (*UPNP_GetValidIGDFunc) (struct UPNPDev *, struct UPNPUrls *, struct IGDdatas *, char *, int); +int (*UPNP_GetExternalIPAddressFunc) (const char *, const char *, char *); +int (*UPNP_DeletePortMappingFunc) (const char *, const char *, const char *, const char *, const char *); +void (*freeUPNPDevlistFunc) (struct UPNPDev *); +void (*FreeUPNPUrlsFunc) (struct UPNPUrls *); + +// Nice approach http://stackoverflow.com/a/21517513/673826 +template +F GetKnownProcAddressImpl(HMODULE hmod, const char *name, F) { + auto proc = reinterpret_cast(dlsym(hmod, name)); + if (!proc) { + LogPrint("Error resolving ", name, " from UPNP library. This often happens if there is version mismatch!"); + } + return proc; +} +#define GetKnownProcAddress(hmod, func) GetKnownProcAddressImpl(hmod, #func, func##Func); + namespace i2p { @@ -57,6 +73,33 @@ namespace transport void UPnP::Start() { + if (!m_IsModuleLoaded) { +#ifdef MAC_OSX + m_Module = dlopen ("libminiupnpc.dylib", RTLD_LAZY); +#elif _WIN32 + m_Module = LoadLibrary ("miniupnpc.dll"); // official prebuilt binary, e.g., in upnpc-exe-win32-20140422.zip +#else + m_Module = dlopen ("libminiupnpc.so", RTLD_LAZY); +#endif + if (m_Module == NULL) + { + LogPrint ("Error loading UPNP library. This often happens if there is version mismatch!"); + return; + } + else + { + upnpDiscoverFunc = GetKnownProcAddress (m_Module, upnpDiscover); + UPNP_GetValidIGDFunc = GetKnownProcAddress (m_Module, UPNP_GetValidIGD); + UPNP_GetExternalIPAddressFunc = GetKnownProcAddress (m_Module, UPNP_GetExternalIPAddress); + UPNP_AddPortMappingFunc = GetKnownProcAddress (m_Module, UPNP_AddPortMapping); + UPNP_DeletePortMappingFunc = GetKnownProcAddress (m_Module, UPNP_DeletePortMapping); + freeUPNPDevlistFunc = GetKnownProcAddress (m_Module, freeUPNPDevlist); + FreeUPNPUrlsFunc = GetKnownProcAddress (m_Module, FreeUPNPUrls); + if (upnpDiscoverFunc && UPNP_GetValidIGDFunc && UPNP_GetExternalIPAddressFunc && UPNP_AddPortMappingFunc && + UPNP_DeletePortMappingFunc && freeUPNPDevlistFunc && FreeUPNPUrlsFunc) + m_IsModuleLoaded = true; + } + } m_Thread = new std::thread (std::bind (&UPnP::Run, this)); } @@ -66,33 +109,6 @@ namespace transport void UPnP::Run () { -#ifdef MAC_OSX - m_Module = dlopen ("libminiupnpc.dylib", RTLD_LAZY); -#elif _WIN32 - m_Module = LoadLibrary ("libminiupnpc.dll"); - if (m_Module == NULL) - { - LogPrint ("Error loading UPNP library. This often happens if there is version mismatch!"); - return; - } - else - { - m_IsModuleLoaded = true; - } -#else - m_Module = dlopen ("libminiupnpc.so", RTLD_LAZY); -#endif -#ifndef _WIN32 - if (!m_Module) - { - LogPrint ("no UPnP module available (", dlerror (), ")"); - return; - } - else - { - m_IsModuleLoaded = true; - } -#endif for (auto& address : context.GetRouterInfo ().GetAddresses ()) { if (!address.host.is_v6 ()) @@ -112,18 +128,6 @@ namespace transport void UPnP::Discover () { - const char *error; -#ifdef _WIN32 - upnp_upnpDiscoverFunc upnpDiscoverFunc = (upnp_upnpDiscoverFunc) GetProcAddress (m_Module, "upnpDiscover"); -#else - upnp_upnpDiscoverFunc upnpDiscoverFunc = (upnp_upnpDiscoverFunc) dlsym (m_Module, "upnpDiscover"); - // reinterpret_cast (dlsym(...)); - if ( (error = dlerror ())) - { - LogPrint ("Error loading UPNP library. This often happens if there is version mismatch!"); - return; - } -#endif // _WIN32 #ifndef UPNPDISCOVER_SUCCESS /* miniupnpc 1.5 */ m_Devlist = upnpDiscoverFunc (2000, m_MulticastIf, m_Minissdpdpath, 0); @@ -134,15 +138,9 @@ namespace transport #endif int r; -#ifdef _WIN32 - upnp_UPNP_GetValidIGDFunc UPNP_GetValidIGDFunc = (upnp_UPNP_GetValidIGDFunc) GetProcAddress (m_Module, "UPNP_GetValidIGD"); -#else - upnp_UPNP_GetValidIGDFunc UPNP_GetValidIGDFunc = (upnp_UPNP_GetValidIGDFunc) dlsym (m_Module, "UPNP_GetValidIGD"); -#endif - r = (*UPNP_GetValidIGDFunc) (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); + r = UPNP_GetValidIGDFunc (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); if (r == 1) { - upnp_UPNP_GetExternalIPAddressFunc UPNP_GetExternalIPAddressFunc = (upnp_UPNP_GetExternalIPAddressFunc) dlsym (m_Module, "UPNP_GetExternalIPAddress"); r = UPNP_GetExternalIPAddressFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); if(r != UPNPCOMMAND_SUCCESS) { @@ -182,11 +180,6 @@ namespace transport std::string strDesc = "I2Pd"; try { for (;;) { -#ifdef _WIN32 - upnp_UPNP_AddPortMappingFunc UPNP_AddPortMappingFunc = (upnp_UPNP_AddPortMappingFunc) GetProcAddress (m_Module, "UPNP_AddPortMapping"); -#else - upnp_UPNP_AddPortMappingFunc UPNP_AddPortMappingFunc = (upnp_UPNP_AddPortMappingFunc) dlsym (m_Module, "UPNP_AddPortMapping"); -#endif #ifndef UPNPDISCOVER_SUCCESS /* miniupnpc 1.5 */ r = UPNP_AddPortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0); @@ -204,7 +197,9 @@ namespace transport LogPrint ("UPnP Port Mapping successful. (", m_NetworkAddr ,":", strPort.c_str(), " type ", strType.c_str () ," -> ", m_externalIPAddress ,":", strPort.c_str() ,")"); return; } - sleep(20*60); + 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) @@ -228,29 +223,14 @@ namespace transport strType = "UDP"; } int r = 0; -#ifdef _WIN32 - upnp_UPNP_DeletePortMappingFunc UPNP_DeletePortMappingFunc = (upnp_UPNP_DeletePortMappingFunc) GetProcAddress (m_Module, "UPNP_DeletePortMapping"); -#else - upnp_UPNP_DeletePortMappingFunc UPNP_DeletePortMappingFunc = (upnp_UPNP_DeletePortMappingFunc) dlsym (m_Module, "UPNP_DeletePortMapping"); -#endif r = UPNP_DeletePortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), 0); LogPrint ("UPNP_DeletePortMapping() returned : ", r, "\n"); } void UPnP::Close () { -#ifdef _WIN32 - upnp_freeUPNPDevlistFunc freeUPNPDevlistFunc = (upnp_freeUPNPDevlistFunc) GetProcAddress (m_Module, "freeUPNPDevlist"); -#else - upnp_freeUPNPDevlistFunc freeUPNPDevlistFunc = (upnp_freeUPNPDevlistFunc) dlsym (m_Module, "freeUPNPDevlist"); -#endif freeUPNPDevlistFunc (m_Devlist); m_Devlist = 0; -#ifdef _WIN32 - upnp_FreeUPNPUrlsFunc FreeUPNPUrlsFunc = (upnp_FreeUPNPUrlsFunc) GetProcAddress (m_Module, "FreeUPNPUrlsFunc"); -#else - upnp_FreeUPNPUrlsFunc FreeUPNPUrlsFunc = (upnp_FreeUPNPUrlsFunc) dlsym (m_Module, "FreeUPNPUrlsFunc"); -#endif FreeUPNPUrlsFunc (&m_upnpUrls); #ifndef _WIN32 dlclose (m_Module); diff --git a/UPnP.h b/UPnP.h index 4884c423..1a7b55c5 100644 --- a/UPnP.h +++ b/UPnP.h @@ -52,7 +52,7 @@ namespace transport #ifndef _WIN32 void *m_Module; #else - HINSTANCE *m_Module; + HINSTANCE m_Module; #endif }; } diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 5d2e04bf..5c9e5a25 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -7,6 +7,7 @@ option(WITH_HARDENING "Use hardening compiler flags" OFF) option(WITH_LIBRARY "Build library" ON) option(WITH_BINARY "Build binary" ON) option(WITH_STATIC "Static build" OFF) +option(WITH_UPNP "Include support for UPnP client" OFF) # paths set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) @@ -43,6 +44,7 @@ set (COMMON_SRC "${CMAKE_SOURCE_DIR}/util.cpp" "${CMAKE_SOURCE_DIR}/Datagram.cpp" "${CMAKE_SOURCE_DIR}/Signature.cpp" + "${CMAKE_SOURCE_DIR}/UPnP.cpp" ) if (CMAKE_SYSTEM_NAME STREQUAL "Windows") @@ -62,10 +64,13 @@ set (DAEMON_SRC "${CMAKE_SOURCE_DIR}/I2PTunnel.cpp" "${CMAKE_SOURCE_DIR}/SAM.cpp" "${CMAKE_SOURCE_DIR}/SOCKS.cpp" - "${CMAKE_SOURCE_DIR}/UPnP.cpp" "${CMAKE_SOURCE_DIR}/i2p.cpp" ) +if (WITH_UPNP) + add_definitions(-DUSE_UPNP) +endif () + set (LIBRARY_SRC "${CMAKE_SOURCE_DIR}/api.cpp" ) @@ -137,7 +142,7 @@ if (WITH_STATIC) set(Boost_USE_STATIC_LIBS ON) endif () -find_package ( Boost COMPONENTS system filesystem regex program_options date_time REQUIRED ) +find_package ( Boost COMPONENTS system filesystem regex program_options date_time thread chrono REQUIRED ) if(NOT DEFINED Boost_INCLUDE_DIRS) message(SEND_ERROR "Boost is not found, or your boost version was bellow 1.46. Please download Boost!") endif() @@ -147,6 +152,11 @@ if(NOT DEFINED CRYPTO++_INCLUDE_DIR) message(SEND_ERROR "Could not find Crypto++. Please download and install it first!") endif() +find_package ( MiniUPnPc ) +if (NOT ${MINIUPNPC_FOUND}) + set(WITH_UPNP OFF) +endif() + # load includes include_directories( ${Boost_INCLUDE_DIRS} ${CRYPTO++_INCLUDE_DIR} "${CMAKE_SOURCE_DIR}/..") @@ -163,6 +173,7 @@ message(STATUS " HARDENING : ${WITH_HARDENING}") message(STATUS " LIBRARY : ${WITH_LIBRARY}") message(STATUS " BINARY : ${WITH_BINARY}") message(STATUS " STATIC BUILD : ${WITH_STATIC}") +message(STATUS " UPnP : ${WITH_UPNP}") message(STATUS "---------------------------------------") #Handle paths nicely diff --git a/build/cmake_modules/FindMiniUPnPc.cmake b/build/cmake_modules/FindMiniUPnPc.cmake new file mode 100644 index 00000000..7ecef75d --- /dev/null +++ b/build/cmake_modules/FindMiniUPnPc.cmake @@ -0,0 +1,25 @@ +# - Find MINIUPNPC + +if(MINIUPNPC_INCLUDE_DIR) + set(MINIUPNPC_FOUND TRUE) + +else() + find_path(MINIUPNPC_INCLUDE_DIR miniupnpc.h + /usr/include/miniupnpc + /usr/local/include/miniupnpc + /opt/local/include/miniupnpc + $ENV{SystemDrive}/miniupnpc + ${PROJECT_SOURCE_DIR}/../../miniupnpc + ) + + if(MINIUPNPC_INCLUDE_DIR) + set(MINIUPNPC_FOUND TRUE) + message(STATUS "Found MiniUPnP headers: ${MINIUPNPC_INCLUDE_DIR}") + else() + set(MINIUPNPC_FOUND FALSE) + message(STATUS "MiniUPnP not found.") + endif() + + mark_as_advanced(MINIUPNPC_INCLUDE_DIR) + +endif() From b5ee997da98a31cefa2d249ba39e3626b2268cab Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sat, 6 Jun 2015 14:16:29 -0500 Subject: [PATCH 0471/6300] MSVC specific debug symbols don't belong to other platforms --- build/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 5d2e04bf..c9dbcbd4 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -188,7 +188,9 @@ if (WITH_BINARY) target_link_libraries( "${PROJECT_NAME}-bin" common ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) install(TARGETS "${PROJECT_NAME}-bin" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) - install(FILES $ DESTINATION "bin" CONFIGURATIONS DEBUG) + if (MSVC) + install(FILES $ DESTINATION "bin" CONFIGURATIONS DEBUG) + endif () endif () if (WITH_LIBRARY) From c896f6d0d7c6d26e49b6a0c1fd0b969b34791c57 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 7 Jun 2015 08:37:34 -0400 Subject: [PATCH 0472/6300] select first hop for inbound tunnel from connected peers --- TunnelPool.cpp | 97 +++++++++++++++++++++++--------------------------- TunnelPool.h | 1 + 2 files changed, 45 insertions(+), 53 deletions(-) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index cc7b4eb0..0eaed3a3 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -70,7 +70,7 @@ namespace tunnel std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.insert (createdTunnel); } - CreatePairedInboundTunnel (createdTunnel); + //CreatePairedInboundTunnel (createdTunnel); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) @@ -290,6 +290,35 @@ namespace tunnel hop = i2p::data::netdb.GetRandomRouter (); return hop; } + + bool TunnelPool::SelectPeers (std::vector >& hops) + { + auto prevHop = i2p::context.GetSharedRouterInfo (); + int numHops = m_NumInboundHops; + if (i2p::transport::transports.GetNumPeers () > 25) + { + auto r = i2p::transport::transports.GetRandomPeer (); + if (r && !r->GetProfile ()->IsBad ()) + { + prevHop = r; + hops.push_back (r); + numHops--; + } + } + + for (int i = 0; i < numHops; i++) + { + auto hop = SelectNextHop (prevHop); + if (!hop) + { + LogPrint (eLogError, "Can't select next hop"); + return false; + } + prevHop = hop; + hops.push_back (hop); + } + return true; + } void TunnelPool::CreateInboundTunnel () { @@ -297,34 +326,15 @@ namespace tunnel if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint ("Creating destination inbound tunnel..."); - auto prevHop = i2p::context.GetSharedRouterInfo (); std::vector > hops; - int numHops = m_NumInboundHops; - if (outboundTunnel) - { - // last hop - auto hop = outboundTunnel->GetTunnelConfig ()->GetFirstHop ()->router; - if (hop->GetIdentHash () != i2p::context.GetIdentHash ()) // outbound shouldn't be zero-hop tunnel - { - prevHop = hop; - hops.push_back (prevHop); - numHops--; - } - } - for (int i = 0; i < numHops; i++) + if (SelectPeers (hops)) { - auto hop = SelectNextHop (prevHop); - if (!hop) - { - LogPrint (eLogError, "Can't select next hop for inbound tunnel"); - return; - } - prevHop = hop; - hops.push_back (hop); - } - std::reverse (hops.begin (), hops.end ()); - auto tunnel = tunnels.CreateTunnel (std::make_shared (hops), outboundTunnel); - tunnel->SetTunnelPool (shared_from_this ()); + std::reverse (hops.begin (), hops.end ()); + auto tunnel = tunnels.CreateTunnel (std::make_shared (hops), outboundTunnel); + tunnel->SetTunnelPool (shared_from_this ()); + } + else + LogPrint (eLogError, "Can't create inbound tunnel. No peers available"); } void TunnelPool::RecreateInboundTunnel (std::shared_ptr tunnel) @@ -345,34 +355,15 @@ namespace tunnel if (inboundTunnel) { LogPrint ("Creating destination outbound tunnel..."); - int numHops = m_NumOutboundHops; - auto prevHop = i2p::context.GetSharedRouterInfo (); std::vector > hops; - if (i2p::transport::transports.GetNumPeers () > 25) - { - auto r = i2p::transport::transports.GetRandomPeer (); - if (r) - { - prevHop = r; - hops.push_back (r); - numHops--; - } - } - for (int i = 0; i < numHops; i++) - { - auto hop = SelectNextHop (prevHop); - if (!hop) - { - LogPrint (eLogError, "Can't select next hop for outbound tunnel"); - return; - } - prevHop = hop; - hops.push_back (hop); + if (SelectPeers (hops)) + { + auto tunnel = tunnels.CreateTunnel ( + std::make_shared (hops, inboundTunnel->GetTunnelConfig ())); + tunnel->SetTunnelPool (shared_from_this ()); } - - auto tunnel = tunnels.CreateTunnel ( - std::make_shared (hops, inboundTunnel->GetTunnelConfig ())); - tunnel->SetTunnelPool (shared_from_this ()); + else + LogPrint (eLogError, "Can't create outbound tunnel. No peers available"); } else LogPrint (eLogError, "Can't create outbound tunnel. No inbound tunnels found"); diff --git a/TunnelPool.h b/TunnelPool.h index 5f2dd5ce..b20cfaf2 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -61,6 +61,7 @@ namespace tunnel template typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; + bool SelectPeers (std::vector >& hops); private: From e461982a316ea8dcc9363c6654a85cdc585cebc5 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Jun 2015 11:00:37 -0400 Subject: [PATCH 0473/6300] support multiple transport sessions to the same peer --- Transports.cpp | 40 ++++++++++++++++++---------------------- Transports.h | 8 +++++++- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index f871607e..f288d113 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -239,7 +239,7 @@ namespace transport try { auto r = netdb.FindRouter (ident); - it = m_Peers.insert (std::pair(ident, { 0, r, nullptr, + it = m_Peers.insert (std::pair(ident, { 0, r, {}, i2p::util::GetSecondsSinceEpoch () })).first; connected = ConnectToPeer (ident, it->second); } @@ -254,8 +254,8 @@ namespace transport return; } } - if (it->second.session) - it->second.session->SendI2NPMessages (msgs); + if (!it->second.sessions.empty ()) + it->second.sessions.front ()->SendI2NPMessages (msgs); else { for (auto it1: msgs) @@ -309,7 +309,7 @@ namespace transport } } LogPrint (eLogError, "No NTCP and SSU addresses available"); - if (peer.session) peer.session->Done (); + peer.Done (); m_Peers.erase (ident); return false; } @@ -436,20 +436,12 @@ namespace transport auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { - if (!it->second.session) - { - it->second.session = session; - session->SendI2NPMessages (it->second.delayedMessages); - it->second.delayedMessages.clear (); - } - else - { - LogPrint (eLogError, "Session for ", ident.ToBase64 ().substr (0, 4), " already exists"); - session->Done (); - } + it->second.sessions.push_back (session); + session->SendI2NPMessages (it->second.delayedMessages); + it->second.delayedMessages.clear (); } else // incoming connection - m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, session, i2p::util::GetSecondsSinceEpoch () })); + m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch () })); }); } @@ -459,12 +451,16 @@ namespace transport { auto ident = session->GetRemoteIdentity ().GetIdentHash (); auto it = m_Peers.find (ident); - if (it != m_Peers.end () && (!it->second.session || it->second.session == session)) + if (it != m_Peers.end ()) { - if (it->second.delayedMessages.size () > 0) - ConnectToPeer (ident, it->second); - else - m_Peers.erase (it); + it->second.sessions.remove (session); + if (it->second.sessions.empty ()) // TODO: why? + { + if (it->second.delayedMessages.size () > 0) + ConnectToPeer (ident, it->second); + else + m_Peers.erase (it); + } } }); } @@ -482,7 +478,7 @@ namespace transport auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_Peers.begin (); it != m_Peers.end (); ) { - if (!it->second.session && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) + if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) { LogPrint (eLogError, "Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); it = m_Peers.erase (it); diff --git a/Transports.h b/Transports.h index 11f05190..ec04f6c7 100644 --- a/Transports.h +++ b/Transports.h @@ -60,10 +60,16 @@ namespace transport { int numAttempts; std::shared_ptr router; - std::shared_ptr session; + std::list > sessions; uint64_t creationTime; std::vector delayedMessages; + void Done () + { + for (auto it: sessions) + it->Done (); + } + ~Peer () { for (auto it :delayedMessages) From e8d80e16ba10b1df78fef7f35e323f7713444ad0 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Jun 2015 13:02:37 -0400 Subject: [PATCH 0474/6300] very hash in one pass --- ElGamal.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ElGamal.h b/ElGamal.h index 3eed1ce6..359de358 100644 --- a/ElGamal.h +++ b/ElGamal.h @@ -60,15 +60,13 @@ namespace crypto { CryptoPP::Integer x(key, 256), a(zeroPadding? encrypted +1 : encrypted, 256), b(zeroPadding? encrypted + 258 :encrypted + 256, 256); - uint8_t m[255], hash[32]; + uint8_t m[255]; a_times_b_mod_c (b, a_exp_b_mod_c (a, elgp - x - 1, elgp), elgp).Encode (m, 255); - CryptoPP::SHA256().CalculateDigest(hash, m+33, 222); - for (int i = 0; i < 32; i++) - if (hash[i] != m[i+1]) - { - LogPrint ("ElGamal decrypt hash doesn't match"); - return false; - } + if (!CryptoPP::SHA256().VerifyDigest (m + 1, m + 33, 222)) + { + LogPrint ("ElGamal decrypt hash doesn't match"); + return false; + } memcpy (data, m + 33, 222); return true; } From 09298d7457bd547959b3f82bee0ac1a3b5ad6706 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Jun 2015 14:04:25 -0400 Subject: [PATCH 0475/6300] changed profiling algorithm --- Profiling.cpp | 24 ++++++++---------------- Profiling.h | 2 +- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/Profiling.cpp b/Profiling.cpp index 43dad505..d0b8e85d 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -11,7 +11,6 @@ namespace data { RouterProfile::RouterProfile (const IdentHash& identHash): m_IdentHash (identHash), m_LastUpdateTime (boost::posix_time::second_clock::local_time()), - m_LastDeclinedTime (boost::posix_time::min_date_time), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0), m_NumTimesTaken (0), m_NumTimesRejected (0) { @@ -140,10 +139,7 @@ namespace data { UpdateTime (); if (ret > 0) - { - m_LastDeclinedTime = m_LastUpdateTime; m_NumTunnelsDeclined++; - } else m_NumTunnelsAgreed++; } @@ -167,19 +163,15 @@ namespace data bool RouterProfile::IsBad () { - auto t = GetTime (); - auto elapsedTime = (t - m_LastUpdateTime).total_seconds (); - if (elapsedTime > 1800) + auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () /*|| IsLowReplyRate ()*/; + if (isBad && m_NumTimesRejected > 10*(m_NumTimesTaken + 1)) { - m_NumTunnelsNonReplied = 0; // drop non-replied after 30 minutes of inactivity - if (elapsedTime > 14400) // drop agreed and declined after 4 hours of inactivity - { - m_NumTunnelsAgreed = 0; - m_NumTunnelsDeclined = 0; - } - } - auto isBad = IsAlwaysDeclining () || IsLowPartcipationRate () || IsLowReplyRate () || - ((t - m_LastDeclinedTime).total_seconds () < 300); // declined in last 5 minutes + // reset profile + m_NumTunnelsAgreed = 0; + m_NumTunnelsDeclined = 0; + m_NumTunnelsNonReplied = 0; + isBad = false; + } if (isBad) m_NumTimesRejected++; else m_NumTimesTaken++; return isBad; } diff --git a/Profiling.h b/Profiling.h index 09dc35c4..0690d6cb 100644 --- a/Profiling.h +++ b/Profiling.h @@ -51,7 +51,7 @@ namespace data private: IdentHash m_IdentHash; - boost::posix_time::ptime m_LastUpdateTime, m_LastDeclinedTime; + boost::posix_time::ptime m_LastUpdateTime; // participation uint32_t m_NumTunnelsAgreed; uint32_t m_NumTunnelsDeclined; From 0e8bdf8299d9dcf1e050df59f9734a8f1cbb104e Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Jun 2015 22:14:31 -0400 Subject: [PATCH 0476/6300] fixed race condition --- RouterContext.cpp | 12 ++++++++++++ RouterContext.h | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/RouterContext.cpp b/RouterContext.cpp index d2659016..495f1181 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -299,6 +299,18 @@ namespace i2p i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); } + void RouterContext::ProcessGarlicMessage (I2NPMessage * msg) + { + std::unique_lock l(m_GarlicMutex); + i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); + } + + void RouterContext::ProcessDeliveryStatusMessage (I2NPMessage * msg) + { + std::unique_lock l(m_GarlicMutex); + i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); + } + uint32_t RouterContext::GetUptime () const { return i2p::util::GetSecondsSinceEpoch () - m_StartupTime; diff --git a/RouterContext.h b/RouterContext.h index 9f6cd00b..840d75ad 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +76,10 @@ namespace i2p std::shared_ptr GetLeaseSet () { return nullptr; }; std::shared_ptr GetTunnelPool () const; void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); + + // override GarlicDestination + void ProcessGarlicMessage (I2NPMessage * msg); + void ProcessDeliveryStatusMessage (I2NPMessage * msg); private: @@ -93,6 +98,7 @@ namespace i2p bool m_AcceptsTunnels, m_IsFloodfill; uint64_t m_StartupTime; // in seconds since epoch RouterStatus m_Status; + std::mutex m_GarlicMutex; }; extern RouterContext context; From 44768e92ad5757ce36b852d21336a70d3c0118a9 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 10 Jun 2015 01:07:39 -0500 Subject: [PATCH 0477/6300] CMake: fix static builds, add LTO for MinSizeRel --- build/CMakeLists.txt | 64 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index b31607f0..19509a74 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -82,15 +82,18 @@ source_group ("Header Files" FILES ${HEADERS}) source_group ("Source Files" FILES ${COMMON_SRC} ${DAEMON_SRC} ${LIBRARY_SRC}) # Default build is Debug -if (CMAKE_BUILD_TYPE STREQUAL "Release") - add_definitions( "-pedantic" ) -else () +if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif () # compiler flags customization (by vendor) if (NOT MSVC) -add_definitions ( "-Wall -Wextra -fPIC" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic" ) + # TODO: The following is incompatible with static build and enabled hardening for OpenWRT. + # Multiple definitions of __stack_chk_fail (libssp & libc) + set( CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -flto -s -ffunction-sections -fdata-sections" ) + set( CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections" ) # -flto is added from above endif () # check for c++11 support @@ -136,10 +139,44 @@ if (WITH_AESNI) endif() # libraries +# TODO: once CMake 3.1+ becomes mainstream, see e.g. http://stackoverflow.com/a/29871891/673826 +# use imported Threads::Threads instead +set(THREADS_PREFER_PTHREAD_FLAG ON) find_package ( Threads REQUIRED ) +if(THREADS_HAVE_PTHREAD_ARG) # compile time flag + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") +endif() if (WITH_STATIC) set(Boost_USE_STATIC_LIBS ON) + set(Boost_USE_STATIC_RUNTIME ON) + if (WIN32) + # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace + # Note that you might need to rebuild Crypto++ + foreach(flag_var + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if(${flag_var} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endif(${flag_var} MATCHES "/MD") + endforeach(flag_var) + else () + set(CMAKE_FIND_LIBRARY_SUFFIXES .a) + endif () + set(BUILD_SHARED_LIBS OFF) + if (${CMAKE_CXX_COMPILER} MATCHES ".*-openwrt-.*") + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread" ) + # set( CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,--whole-archive -lpthread -Wl,--no-whole-archive" ) + set( CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,-u,pthread_create,-u,pthread_once,-u,pthread_mutex_lock,-u,pthread_mutex_unlock,-u,pthread_join,-u,pthread_equal,-u,pthread_detach,-u,pthread_cond_wait,-u,pthread_cond_signal,-u,pthread_cond_destroy,-u,pthread_cond_broadcast,-u,pthread_cancel" ) + endif () +else() + if (NOT WIN32) + # TODO: Consider separate compilation for COMMON_SRC for library. + # No need in -fPIC overhead for binary if not interested in library + # HINT: revert c266cff CMakeLists.txt: compilation speed up + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" ) + endif () + add_definitions(-DBOOST_ALL_DYN_LINK) endif () find_package ( Boost COMPONENTS system filesystem regex program_options date_time thread chrono REQUIRED ) @@ -183,19 +220,20 @@ if (WITH_BINARY) add_executable ( "${PROJECT_NAME}-bin" ${DAEMON_SRC} ) if(NOT MSVC) # FIXME: incremental linker file name (.ilk) collision for dll & exe set_target_properties("${PROJECT_NAME}-bin" PROPERTIES OUTPUT_NAME "${PROJECT_NAME}") + if (WITH_STATIC) + set_target_properties("${PROJECT_NAME}-bin" PROPERTIES LINK_FLAGS "-static" ) + endif () endif() if (WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set_target_properties("${PROJECT_NAME}-bin" PROPERTIES LINK_FLAGS "-z relro -z now" ) endif () - if (WITH_STATIC) - set(BUILD_SHARED_LIBS OFF) - set_target_properties("${PROJECT_NAME}-bin" PROPERTIES LINK_FLAGS "-static" ) - else() - add_definitions(-DBOOST_ALL_DYN_LINK) - endif () - + # FindBoost pulls pthread for thread which is broken for static linking at least on Ubuntu 15.04 + list(GET Boost_LIBRARIES -1 LAST_Boost_LIBRARIES) + if(${LAST_Boost_LIBRARIES} MATCHES ".*pthread.*") + list(REMOVE_AT Boost_LIBRARIES -1) + endif() target_link_libraries( "${PROJECT_NAME}-bin" common ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) install(TARGETS "${PROJECT_NAME}-bin" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) @@ -207,9 +245,9 @@ endif () if (WITH_LIBRARY) if (MSVC) # FIXME: DLL would not have any symbols unless we use __declspec(dllexport) through out the code - add_library(${PROJECT_NAME} ${LIBRARY_SRC}) + add_library(${PROJECT_NAME} STATIC ${LIBRARY_SRC}) else () - add_library(${PROJECT_NAME} SHARED ${LIBRARY_SRC}) + add_library(${PROJECT_NAME} ${LIBRARY_SRC}) target_link_libraries( ${PROJECT_NAME} common ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES}) endif () install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) From ba2b792916013b0833c064f6b0fccf769f539018 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 10 Jun 2015 01:12:43 -0500 Subject: [PATCH 0478/6300] Cleanup cryptopp headers path search --- build/CMakeLists.txt | 2 +- build/cmake_modules/FindCryptoPP.cmake | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 19509a74..4e125d58 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -195,7 +195,7 @@ if (NOT ${MINIUPNPC_FOUND}) endif() # load includes -include_directories( ${Boost_INCLUDE_DIRS} ${CRYPTO++_INCLUDE_DIR} "${CMAKE_SOURCE_DIR}/..") +include_directories( ${Boost_INCLUDE_DIRS} ${CRYPTO++_INCLUDE_DIR} ) # show summary message(STATUS "---------------------------------------") diff --git a/build/cmake_modules/FindCryptoPP.cmake b/build/cmake_modules/FindCryptoPP.cmake index 09b72184..396be144 100644 --- a/build/cmake_modules/FindCryptoPP.cmake +++ b/build/cmake_modules/FindCryptoPP.cmake @@ -4,17 +4,14 @@ if(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) set(CRYPTO++_FOUND TRUE) else(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) - find_path(CRYPTO++_INCLUDE_DIR cryptlib.h - /usr/include/crypto++ - /usr/include/cryptopp - /usr/local/include/crypto++ - /usr/local/include/cryptopp - /opt/local/include/crypto++ - /opt/local/include/cryptopp + find_path(CRYPTO++_INCLUDE_DIR cryptopp/cryptlib.h + /usr/include + /usr/local/include $ENV{SystemDrive}/Crypto++/include $ENV{CRYPTOPP} + $ENV{CRYPTOPP}/.. $ENV{CRYPTOPP}/include - ${PROJECT_SOURCE_DIR}/../../cryptopp + ${PROJECT_SOURCE_DIR}/../.. ) find_library(CRYPTO++_LIBRARIES NAMES cryptopp From 0354685e35c921ee457741b195b57ca57a788771 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 10 Jun 2015 01:04:33 -0500 Subject: [PATCH 0479/6300] Precompiled headers Sample times: MSVC 2013, debug x64: 5min 15sec -> 2min 15sec Ubuntu 15.04, with hardening, static, release: 5min 21sec -> 3min 24sec --- build/CMakeLists.txt | 46 ++++++++++++++++++++++++++++-- stdafx.cpp | 1 + stdafx.h | 67 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 stdafx.cpp create mode 100644 stdafx.h diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 4e125d58..254574fe 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required ( VERSION 2.8.5 ) +cmake_minimum_required ( VERSION 2.8.12 ) project ( "i2pd" ) # configurale options @@ -8,6 +8,7 @@ option(WITH_LIBRARY "Build library" ON) option(WITH_BINARY "Build binary" ON) option(WITH_STATIC "Static build" OFF) option(WITH_UPNP "Include support for UPnP client" OFF) +option(WITH_PCH "Use precompiled header" OFF) # paths set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) @@ -88,7 +89,7 @@ endif () # compiler flags customization (by vendor) if (NOT MSVC) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch" ) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic" ) # TODO: The following is incompatible with static build and enabled hardening for OpenWRT. # Multiple definitions of __stack_chk_fail (libssp & libc) @@ -179,6 +180,30 @@ else() add_definitions(-DBOOST_ALL_DYN_LINK) endif () +if (WITH_PCH) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + add_library(stdafx STATIC "${CMAKE_SOURCE_DIR}/stdafx.cpp") + if(MSVC) + target_compile_options(stdafx PRIVATE /Ycstdafx.h /Zm135) + add_custom_command(TARGET stdafx POST_BUILD + COMMAND xcopy /y stdafx.dir\\$\\*.pdb common.dir\\$\\ + COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pd-bin.dir\\$\\ + COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pd.dir\\$\\ + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + target_compile_options(common PRIVATE /FIstdafx.h /Yustdafx.h /Zm135 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") + else() + string(TOUPPER ${CMAKE_BUILD_TYPE} BTU) + get_directory_property(DEFS DEFINITIONS) + string(REPLACE " " ";" FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BTU}} ${DEFS}") + add_custom_command(TARGET stdafx PRE_BUILD + COMMAND ${CMAKE_CXX_COMPILER} ${FLAGS} -c ${CMAKE_CURRENT_SOURCE_DIR}/../stdafx.h + ) + target_compile_options(common PRIVATE -include stdafx.h) + endif() + target_link_libraries(common stdafx) +endif() + find_package ( Boost COMPONENTS system filesystem regex program_options date_time thread chrono REQUIRED ) if(NOT DEFINED Boost_INCLUDE_DIRS) message(SEND_ERROR "Boost is not found, or your boost version was bellow 1.46. Please download Boost!") @@ -211,6 +236,7 @@ message(STATUS " LIBRARY : ${WITH_LIBRARY}") message(STATUS " BINARY : ${WITH_BINARY}") message(STATUS " STATIC BUILD : ${WITH_STATIC}") message(STATUS " UPnP : ${WITH_UPNP}") +message(STATUS " PCH : ${WITH_PCH}") message(STATUS "---------------------------------------") #Handle paths nicely @@ -225,6 +251,14 @@ if (WITH_BINARY) endif () endif() + if (WITH_PCH) + if (MSVC) + target_compile_options("${PROJECT_NAME}-bin" PRIVATE /FIstdafx.h /Yustdafx.h /Zm135 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") + else() + target_compile_options("${PROJECT_NAME}-bin" PRIVATE -include stdafx.h) + endif() + endif() + if (WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set_target_properties("${PROJECT_NAME}-bin" PROPERTIES LINK_FLAGS "-z relro -z now" ) endif () @@ -250,5 +284,13 @@ if (WITH_LIBRARY) add_library(${PROJECT_NAME} ${LIBRARY_SRC}) target_link_libraries( ${PROJECT_NAME} common ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES}) endif () + if (WITH_PCH) + if (MSVC) + add_dependencies(${PROJECT_NAME} stdafx) + target_compile_options(${PROJECT_NAME} PRIVATE /FIstdafx.h /Yustdafx.h /Zm135 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") + else() + target_compile_options(${PROJECT_NAME} PRIVATE -include stdafx.h) + endif() + endif() install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif () diff --git a/stdafx.cpp b/stdafx.cpp new file mode 100644 index 00000000..fd4f341c --- /dev/null +++ b/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/stdafx.h b/stdafx.h new file mode 100644 index 00000000..edfac630 --- /dev/null +++ b/stdafx.h @@ -0,0 +1,67 @@ +#ifndef STDAFX_H__ +#define STDAFX_H__ + +#include +#include +#include +#include +#include + +#include // TODO: replace with cstring and std:: through out +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif From ef6a0384513bb90627625db0242e08629cbf8786 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 10 Jun 2015 15:32:55 -0400 Subject: [PATCH 0480/6300] handle explicitPeers I2CP parameter --- Destination.cpp | 17 +++++++++++++++ Destination.h | 1 + Identity.h | 5 +++++ TunnelPool.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++----- TunnelPool.h | 7 ++++-- 5 files changed, 80 insertions(+), 7 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 606bd619..63c89fff 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -24,6 +24,7 @@ namespace client int outboundTunnelLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH; int inboundTunnelsQuantity = DEFAULT_INBOUND_TUNNELS_QUANTITY; int outboundTunnelsQuantity = DEFAULT_OUTBOUND_TUNNELS_QUANTITY; + std::shared_ptr > explicitPeers; if (params) { auto it = params->find (I2CP_PARAM_INBOUND_TUNNEL_LENGTH); @@ -66,8 +67,24 @@ namespace client LogPrint (eLogInfo, "Outbound tunnels quantity set to ", quantity); } } + it = params->find (I2CP_PARAM_EXPLICIT_PEERS); + if (it != params->end ()) + { + explicitPeers = std::make_shared >(); + std::stringstream ss(it->second); + std::string b64; + while (std::getline (ss, b64, ',')) + { + i2p::data::IdentHash ident; + ident.FromBase64 (b64); + explicitPeers->push_back (ident); + } + LogPrint (eLogInfo, "Explicit peers set to ", it->second); + } } m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (this, inboundTunnelLen, outboundTunnelLen, inboundTunnelsQuantity, outboundTunnelsQuantity); + if (explicitPeers) + m_Pool->SetExplicitPeers (explicitPeers); if (m_IsPublic) LogPrint (eLogInfo, "Local address ", i2p::client::GetB32Address(GetIdentHash()), " created"); m_StreamingDestination = std::make_shared (*this); // TODO: diff --git a/Destination.h b/Destination.h index bf2dfcca..1f48ded0 100644 --- a/Destination.h +++ b/Destination.h @@ -40,6 +40,7 @@ namespace client const int DEFAULT_INBOUND_TUNNELS_QUANTITY = 5; const char I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY[] = "outbound.quantity"; const int DEFAULT_OUTBOUND_TUNNELS_QUANTITY = 5; + const char I2CP_PARAM_EXPLICIT_PEERS[] = "explicitPeers"; const int STREAM_REQUEST_TIMEOUT = 60; //in seconds typedef std::function stream)> StreamRequestComplete; diff --git a/Identity.h b/Identity.h index 02752d99..632c414a 100644 --- a/Identity.h +++ b/Identity.h @@ -69,6 +69,11 @@ namespace data i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); } + void FromBase64 (const std::string& s) + { + i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); + } + private: union // 8 bytes alignment diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 0eaed3a3..478d6f11 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -1,3 +1,4 @@ +#include #include "I2PEndian.h" #include "CryptoConst.h" #include "Tunnel.h" @@ -22,6 +23,27 @@ namespace tunnel DetachTunnels (); } + void TunnelPool::SetExplicitPeers (std::shared_ptr > explicitPeers) + { + m_ExplicitPeers = explicitPeers; + if (m_ExplicitPeers) + { + int size = m_ExplicitPeers->size (); + if (m_NumInboundHops > size) + { + m_NumInboundHops = size; + LogPrint (eLogInfo, "Inbound tunnel length has beed adjusted to ", size, " for explicit peers"); + } + if (m_NumOutboundHops > size) + { + m_NumOutboundHops = size; + LogPrint (eLogInfo, "Outbound tunnel length has beed adjusted to ", size, " for explicit peers"); + } + m_NumInboundTunnels = 1; + m_NumOutboundTunnels = 1; + } + } + void TunnelPool::DetachTunnels () { { @@ -291,10 +313,11 @@ namespace tunnel return hop; } - bool TunnelPool::SelectPeers (std::vector >& hops) + bool TunnelPool::SelectPeers (std::vector >& hops, bool isInbound) { + if (m_ExplicitPeers) return SelectExplicitPeers (hops, isInbound); auto prevHop = i2p::context.GetSharedRouterInfo (); - int numHops = m_NumInboundHops; + int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; if (i2p::transport::transports.GetNumPeers () > 25) { auto r = i2p::transport::transports.GetRandomPeer (); @@ -319,7 +342,31 @@ namespace tunnel } return true; } - + + bool TunnelPool::SelectExplicitPeers (std::vector >& hops, bool isInbound) + { + int size = m_ExplicitPeers->size (); + std::vector peerIndicies; + for (int i = 0; i < size; i++) peerIndicies.push_back(i); + std::random_shuffle (peerIndicies.begin(), peerIndicies.end()); + + int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; + for (int i = 0; i < numHops; i++) + { + auto& ident = (*m_ExplicitPeers)[peerIndicies[i]]; + auto r = i2p::data::netdb.FindRouter (ident); + if (r) + hops.push_back (r); + else + { + LogPrint (eLogInfo, "Can't find router for ", ident.ToBase64 ()); + i2p::data::netdb.RequestDestination (ident); + return false; + } + } + return true; + } + void TunnelPool::CreateInboundTunnel () { auto outboundTunnel = GetNextOutboundTunnel (); @@ -327,7 +374,7 @@ namespace tunnel outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint ("Creating destination inbound tunnel..."); std::vector > hops; - if (SelectPeers (hops)) + if (SelectPeers (hops, true)) { std::reverse (hops.begin (), hops.end ()); auto tunnel = tunnels.CreateTunnel (std::make_shared (hops), outboundTunnel); @@ -356,7 +403,7 @@ namespace tunnel { LogPrint ("Creating destination outbound tunnel..."); std::vector > hops; - if (SelectPeers (hops)) + if (SelectPeers (hops, false)) { auto tunnel = tunnels.CreateTunnel ( std::make_shared (hops, inboundTunnel->GetTunnelConfig ())); diff --git a/TunnelPool.h b/TunnelPool.h index b20cfaf2..2232a787 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -32,6 +32,7 @@ namespace tunnel i2p::garlic::GarlicDestination * GetLocalDestination () const { return m_LocalDestination; }; void SetLocalDestination (i2p::garlic::GarlicDestination * destination) { m_LocalDestination = destination; }; + void SetExplicitPeers (std::shared_ptr > explicitPeers); void CreateTunnels (); void TunnelCreated (std::shared_ptr createdTunnel); @@ -61,12 +62,14 @@ namespace tunnel template typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; - bool SelectPeers (std::vector >& hops); - + bool SelectPeers (std::vector >& hops, bool isInbound); + bool SelectExplicitPeers (std::vector >& hops, bool isInbound); + private: i2p::garlic::GarlicDestination * m_LocalDestination; int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels; + std::shared_ptr > m_ExplicitPeers; mutable std::mutex m_InboundTunnelsMutex; std::set, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first mutable std::mutex m_OutboundTunnelsMutex; From 23a3d4861146dfb6316c4f649e47134b7f59ff44 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 10 Jun 2015 16:08:22 -0500 Subject: [PATCH 0481/6300] This closes #201 --- SAM.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SAM.cpp b/SAM.cpp index 971ed45e..7de1a8a8 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -101,6 +101,9 @@ namespace client if (separator) { separator++; + char *eol = strchr (separator, '\n'); + if (eol) + *eol = 0; std::map params; ExtractParams (separator, params); auto it = params.find (SAM_PARAM_MAX); From 20e43951e5be07e083e6f7b8f2ff624288f8efd0 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 11 Jun 2015 11:43:35 -0400 Subject: [PATCH 0482/6300] reduce CPU usage --- NetDb.cpp | 40 +++++++++++++++------------------------- NetDb.h | 3 ++- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 0ec8c41d..5ba3c17d 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -732,28 +732,13 @@ namespace data if (!replyMsg) { LogPrint ("Requested ", key, " not found. ", numExcluded, " excluded"); - std::vector routers; - if (numExcluded > 0) - { - std::set excludedRouters; - for (int i = 0; i < numExcluded; i++) - { - excludedRouters.insert (excluded); - excluded += 32; - } - for (int i = 0; i < 3; i++) - { - auto floodfill = GetClosestFloodfill (ident, excludedRouters); - if (floodfill) - { - routers.push_back (floodfill->GetIdentHash ()); - excludedRouters.insert (floodfill->GetIdentHash ()); - } - } - } - else - routers = GetClosestFloodfills (ident, 3); - replyMsg = CreateDatabaseSearchReply (ident, routers); + std::set excludedRouters; + for (int i = 0; i < numExcluded; i++) + { + excludedRouters.insert (excluded); + excluded += 32; + } + replyMsg = CreateDatabaseSearchReply (ident, GetClosestFloodfills (ident, 3, excludedRouters)); } } @@ -955,7 +940,8 @@ namespace data return r; } - std::vector NetDb::GetClosestFloodfills (const IdentHash& destination, size_t num) const + std::vector NetDb::GetClosestFloodfills (const IdentHash& destination, size_t num, + std::set& excluded) const { struct Sorted { @@ -990,8 +976,12 @@ namespace data { if (i < num) { - res.push_back (it.r->GetIdentHash ()); - i++; + auto& ident = it.r->GetIdentHash (); + if (!excluded.count (ident)) + { + res.push_back (ident); + i++; + } } else break; diff --git a/NetDb.h b/NetDb.h index 1a7ee0de..2c6e78be 100644 --- a/NetDb.h +++ b/NetDb.h @@ -51,7 +51,8 @@ namespace data std::shared_ptr GetRandomPeerTestRouter () const; std::shared_ptr GetRandomIntroducer () const; std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded) const; - std::vector GetClosestFloodfills (const IdentHash& destination, size_t num) const; + std::vector GetClosestFloodfills (const IdentHash& destination, size_t num, + std::set& excluded) const; std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; void SetUnreachable (const IdentHash& ident, bool unreachable); From b48682012d8d23c479dbc1158270847238ae2e9b Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 14 Jun 2015 10:37:15 -0400 Subject: [PATCH 0483/6300] verify adler checksum --- NTCPSession.cpp | 11 +++++++++-- NTCPSession.h | 2 -- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 4d718450..c5dac0a2 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -2,6 +2,7 @@ #include #include "I2PEndian.h" #include +#include #include "base64.h" #include "Log.h" #include "Timestamp.h" @@ -587,7 +588,13 @@ namespace transport if (m_NextMessageOffset >= m_NextMessage->len + 4) // +checksum { // we have a complete I2NP message - m_Handler.PutNextMessage (m_NextMessage); + if (CryptoPP::Adler32().VerifyDigest (m_NextMessage->buf + m_NextMessageOffset - 4, m_NextMessage->buf, m_NextMessageOffset - 4)) + m_Handler.PutNextMessage (m_NextMessage); + else + { + LogPrint (eLogWarning, "Incorrect adler checksum of NTCP message. Dropped"); + DeleteI2NPMessage (m_NextMessage); + } m_NextMessage = nullptr; } return true; @@ -629,7 +636,7 @@ namespace transport int padding = 0; if (rem > 0) padding = 16 - rem; // TODO: fill padding - m_Adler.CalculateDigest (sendBuffer + len + 2 + padding, sendBuffer, len + 2+ padding); + CryptoPP::Adler32().CalculateDigest (sendBuffer + len + 2 + padding, sendBuffer, len + 2+ padding); int l = len + padding + 6; m_Encryption.Encrypt(sendBuffer, l, sendBuffer); diff --git a/NTCPSession.h b/NTCPSession.h index 85a0aeb7..8e3e9073 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -9,7 +9,6 @@ #include #include #include -#include #include "aes.h" #include "Identity.h" #include "RouterInfo.h" @@ -116,7 +115,6 @@ namespace transport i2p::crypto::CBCDecryption m_Decryption; i2p::crypto::CBCEncryption m_Encryption; - CryptoPP::Adler32 m_Adler; struct Establisher { From a0de60e179e60ced49c76b4da3e8ca8536111b4b Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 16 Jun 2015 10:14:14 -0400 Subject: [PATCH 0484/6300] use share_ptr for garlic messages --- Destination.cpp | 7 +++---- Destination.h | 6 +++--- Garlic.cpp | 11 ++++------- Garlic.h | 8 ++++---- I2NPProtocol.cpp | 21 ++++++++++++--------- RouterContext.cpp | 4 ++-- RouterContext.h | 4 ++-- TunnelPool.cpp | 11 ++--------- TunnelPool.h | 4 ++-- 9 files changed, 34 insertions(+), 42 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 63c89fff..27f2564c 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -215,12 +215,12 @@ namespace client return true; } - void ClientDestination::ProcessGarlicMessage (I2NPMessage * msg) + void ClientDestination::ProcessGarlicMessage (std::shared_ptr msg) { m_Service.post (std::bind (&ClientDestination::HandleGarlicMessage, this, msg)); } - void ClientDestination::ProcessDeliveryStatusMessage (I2NPMessage * msg) + void ClientDestination::ProcessDeliveryStatusMessage (std::shared_ptr msg) { m_Service.post (std::bind (&ClientDestination::HandleDeliveryStatusMessage, this, msg)); } @@ -343,7 +343,7 @@ namespace client LogPrint ("Request for ", key.ToBase64 (), " not found"); } - void ClientDestination::HandleDeliveryStatusMessage (I2NPMessage * msg) + void ClientDestination::HandleDeliveryStatusMessage (std::shared_ptr msg) { uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET); if (msgID == m_PublishReplyToken) @@ -351,7 +351,6 @@ namespace client LogPrint (eLogDebug, "Publishing confirmed"); m_ExcludedFloodfills.clear (); m_PublishReplyToken = 0; - i2p::DeleteI2NPMessage (msg); } else i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msg); diff --git a/Destination.h b/Destination.h index 1f48ded0..c41ee9ca 100644 --- a/Destination.h +++ b/Destination.h @@ -99,8 +99,8 @@ namespace client // override GarlicDestination bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); - void ProcessGarlicMessage (I2NPMessage * msg); - void ProcessDeliveryStatusMessage (I2NPMessage * msg); + void ProcessGarlicMessage (std::shared_ptr msg); + void ProcessDeliveryStatusMessage (std::shared_ptr msg); void SetLeaseSetUpdated (); // I2CP @@ -114,7 +114,7 @@ namespace client void HandlePublishConfirmationTimer (const boost::system::error_code& ecode); void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len); void HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len); - void HandleDeliveryStatusMessage (I2NPMessage * msg); + void HandleDeliveryStatusMessage (std::shared_ptr msg); void RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete); bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, LeaseSetRequest * request); diff --git a/Garlic.cpp b/Garlic.cpp index bbfaed91..8bdfb10c 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -362,14 +362,13 @@ namespace garlic return true; } - void GarlicDestination::HandleGarlicMessage (I2NPMessage * msg) + void GarlicDestination::HandleGarlicMessage (std::shared_ptr msg) { uint8_t * buf = msg->GetPayload (); uint32_t length = bufbe32toh (buf); if (length > msg->GetLength ()) { LogPrint (eLogError, "Garlic message length ", length, " exceeds I2NP message length ", msg->GetLength ()); - DeleteI2NPMessage (msg); return; } buf += 4; // length @@ -406,7 +405,6 @@ namespace garlic else LogPrint (eLogError, "Failed to decrypt garlic"); } - DeleteI2NPMessage (msg); // cleanup expired tags uint32_t ts = i2p::util::GetSecondsSinceEpoch (); @@ -588,7 +586,7 @@ namespace garlic m_CreatedSessions[msgID] = session; } - void GarlicDestination::HandleDeliveryStatusMessage (I2NPMessage * msg) + void GarlicDestination::HandleDeliveryStatusMessage (std::shared_ptr msg) { uint32_t msgID = bufbe32toh (msg->GetPayload ()); { @@ -600,7 +598,6 @@ namespace garlic LogPrint (eLogInfo, "Garlic message ", msgID, " acknowledged"); } } - DeleteI2NPMessage (msg); } void GarlicDestination::SetLeaseSetUpdated () @@ -610,12 +607,12 @@ namespace garlic it.second->SetLeaseSetUpdated (); } - void GarlicDestination::ProcessGarlicMessage (I2NPMessage * msg) + void GarlicDestination::ProcessGarlicMessage (std::shared_ptr msg) { HandleGarlicMessage (msg); } - void GarlicDestination::ProcessDeliveryStatusMessage (I2NPMessage * msg) + void GarlicDestination::ProcessDeliveryStatusMessage (std::shared_ptr msg) { HandleDeliveryStatusMessage (msg); } diff --git a/Garlic.h b/Garlic.h index 489e532b..d5330735 100644 --- a/Garlic.h +++ b/Garlic.h @@ -133,8 +133,8 @@ namespace garlic virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread void DeliveryStatusSent (std::shared_ptr session, uint32_t msgID); - virtual void ProcessGarlicMessage (I2NPMessage * msg); - virtual void ProcessDeliveryStatusMessage (I2NPMessage * msg); + virtual void ProcessGarlicMessage (std::shared_ptr msg); + virtual void ProcessDeliveryStatusMessage (std::shared_ptr msg); virtual void SetLeaseSetUpdated (); virtual std::shared_ptr GetLeaseSet () = 0; // TODO @@ -143,8 +143,8 @@ namespace garlic protected: - void HandleGarlicMessage (I2NPMessage * msg); - void HandleDeliveryStatusMessage (I2NPMessage * msg); + void HandleGarlicMessage (std::shared_ptr msg); + void HandleDeliveryStatusMessage (std::shared_ptr msg); private: diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 104fdef1..3948afec 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -537,20 +537,20 @@ namespace i2p i2p::tunnel::tunnels.PostTunnelData (msg); break; case eI2NPGarlic: + { LogPrint ("Garlic"); + auto sharedMsg = ToSharedI2NPMessage (msg); if (msg->from) { if (msg->from->GetTunnelPool ()) - msg->from->GetTunnelPool ()->ProcessGarlicMessage (msg); + msg->from->GetTunnelPool ()->ProcessGarlicMessage (sharedMsg); else - { LogPrint (eLogInfo, "Local destination for garlic doesn't exist anymore"); - DeleteI2NPMessage (msg); - } } else - i2p::context.ProcessGarlicMessage (msg); - break; + i2p::context.ProcessGarlicMessage (sharedMsg); + break; + } case eI2NPDatabaseStore: case eI2NPDatabaseSearchReply: case eI2NPDatabaseLookup: @@ -558,12 +558,15 @@ namespace i2p i2p::data::netdb.PostI2NPMsg (msg); break; case eI2NPDeliveryStatus: + { LogPrint ("DeliveryStatus"); + auto sharedMsg = ToSharedI2NPMessage (msg); if (msg->from && msg->from->GetTunnelPool ()) - msg->from->GetTunnelPool ()->ProcessDeliveryStatus (msg); + msg->from->GetTunnelPool ()->ProcessDeliveryStatus (sharedMsg); else - i2p::context.ProcessDeliveryStatusMessage (msg); - break; + i2p::context.ProcessDeliveryStatusMessage (sharedMsg); + break; + } case eI2NPVariableTunnelBuild: case eI2NPVariableTunnelBuildReply: case eI2NPTunnelBuild: diff --git a/RouterContext.cpp b/RouterContext.cpp index 495f1181..c9328b5c 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -299,13 +299,13 @@ namespace i2p i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); } - void RouterContext::ProcessGarlicMessage (I2NPMessage * msg) + void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) { std::unique_lock l(m_GarlicMutex); i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); } - void RouterContext::ProcessDeliveryStatusMessage (I2NPMessage * msg) + void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr msg) { std::unique_lock l(m_GarlicMutex); i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); diff --git a/RouterContext.h b/RouterContext.h index 840d75ad..2689d025 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -78,8 +78,8 @@ namespace i2p void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); // override GarlicDestination - void ProcessGarlicMessage (I2NPMessage * msg); - void ProcessDeliveryStatusMessage (I2NPMessage * msg); + void ProcessGarlicMessage (std::shared_ptr msg); + void ProcessDeliveryStatusMessage (std::shared_ptr msg); private: diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 478d6f11..0f0eb709 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -260,18 +260,15 @@ namespace tunnel } } - void TunnelPool::ProcessGarlicMessage (I2NPMessage * msg) + void TunnelPool::ProcessGarlicMessage (std::shared_ptr msg) { if (m_LocalDestination) m_LocalDestination->ProcessGarlicMessage (msg); else - { LogPrint (eLogWarning, "Local destination doesn't exist. Dropped"); - DeleteI2NPMessage (msg); - } } - void TunnelPool::ProcessDeliveryStatus (I2NPMessage * msg) + void TunnelPool::ProcessDeliveryStatus (std::shared_ptr msg) { const uint8_t * buf = msg->GetPayload (); uint32_t msgID = bufbe32toh (buf); @@ -288,17 +285,13 @@ namespace tunnel it->second.second->SetState (eTunnelStateEstablished); LogPrint ("Tunnel test ", it->first, " successive. ", i2p::util::GetMillisecondsSinceEpoch () - timestamp, " milliseconds"); m_Tests.erase (it); - DeleteI2NPMessage (msg); } else { if (m_LocalDestination) m_LocalDestination->ProcessDeliveryStatusMessage (msg); else - { LogPrint (eLogWarning, "Local destination doesn't exist. Dropped"); - DeleteI2NPMessage (msg); - } } } diff --git a/TunnelPool.h b/TunnelPool.h index 2232a787..2d4203ef 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -47,8 +47,8 @@ namespace tunnel std::shared_ptr GetNewOutboundTunnel (std::shared_ptr old) const; void TestTunnels (); - void ProcessGarlicMessage (I2NPMessage * msg); - void ProcessDeliveryStatus (I2NPMessage * msg); + void ProcessGarlicMessage (std::shared_ptr msg); + void ProcessDeliveryStatus (std::shared_ptr msg); bool IsActive () const { return m_IsActive; }; void SetActive (bool isActive) { m_IsActive = isActive; }; From 465945f8a8e67ad6ec50b6a38220a831e05180c5 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 16 Jun 2015 13:14:33 -0400 Subject: [PATCH 0485/6300] more generic queue --- NetDb.h | 2 +- Queue.h | 30 +++++++++++++++--------------- Tunnel.h | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/NetDb.h b/NetDb.h index 2c6e78be..6b96f155 100644 --- a/NetDb.h +++ b/NetDb.h @@ -89,7 +89,7 @@ namespace data bool m_IsRunning; std::thread * m_Thread; - i2p::util::Queue m_Queue; // of I2NPDatabaseStoreMsg + i2p::util::Queue m_Queue; // of I2NPDatabaseStoreMsg Reseeder * m_Reseeder; diff --git a/Queue.h b/Queue.h index 086d6d81..6f50e189 100644 --- a/Queue.h +++ b/Queue.h @@ -17,14 +17,14 @@ namespace util { public: - void Put (Element * e) + void Put (Element e) { std::unique_lock l(m_QueueMutex); m_Queue.push (e); m_NonEmpty.notify_one (); } - void Put (const std::vector& vec) + void Put (const std::vector& vec) { if (!vec.empty ()) { @@ -35,10 +35,10 @@ namespace util } } - Element * GetNext () + Element GetNext () { std::unique_lock l(m_QueueMutex); - Element * el = GetNonThreadSafe (); + auto el = GetNonThreadSafe (); if (!el) { m_NonEmpty.wait (l); @@ -47,10 +47,10 @@ namespace util return el; } - Element * GetNextWithTimeout (int usec) + Element GetNextWithTimeout (int usec) { std::unique_lock l(m_QueueMutex); - Element * el = GetNonThreadSafe (); + auto el = GetNonThreadSafe (); if (!el) { m_NonEmpty.wait_for (l, std::chrono::milliseconds (usec)); @@ -85,13 +85,13 @@ namespace util void WakeUp () { m_NonEmpty.notify_all (); }; - Element * Get () + Element Get () { std::unique_lock l(m_QueueMutex); return GetNonThreadSafe (); } - Element * Peek () + Element Peek () { std::unique_lock l(m_QueueMutex); return GetNonThreadSafe (true); @@ -99,11 +99,11 @@ namespace util private: - Element * GetNonThreadSafe (bool peek = false) + Element GetNonThreadSafe (bool peek = false) { if (!m_Queue.empty ()) { - Element * el = m_Queue.front (); + auto el = m_Queue.front (); if (!peek) m_Queue.pop (); return el; @@ -113,13 +113,13 @@ namespace util private: - std::queue m_Queue; + std::queue m_Queue; std::mutex m_QueueMutex; std::condition_variable m_NonEmpty; }; template - class MsgQueue: public Queue + class MsgQueue: public Queue { public: @@ -132,7 +132,7 @@ namespace util if (m_IsRunning) { m_IsRunning = false; - Queue::WakeUp (); + Queue::WakeUp (); m_Thread.join(); } } @@ -145,7 +145,7 @@ namespace util { while (m_IsRunning) { - while (Msg * msg = Queue::Get ()) + while (auto msg = Queue::Get ()) { msg->Process (); delete msg; @@ -153,7 +153,7 @@ namespace util if (m_OnEmpty != nullptr) m_OnEmpty (); if (m_IsRunning) - Queue::Wait (); + Queue::Wait (); } } diff --git a/Tunnel.h b/Tunnel.h index be15aa91..b6362997 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -177,7 +177,7 @@ namespace tunnel std::mutex m_PoolsMutex; std::list> m_Pools; std::shared_ptr m_ExploratoryPool; - i2p::util::Queue m_Queue; + i2p::util::Queue m_Queue; // some stats int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; From d65257c7b0a15c25c189b17f7327960a781260ad Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 16 Jun 2015 13:32:42 -0400 Subject: [PATCH 0486/6300] pass I2NP as shared_ptr to netDB --- I2NPProtocol.cpp | 2 +- NetDb.cpp | 12 ++++++------ NetDb.h | 4 ++-- Tunnel.cpp | 4 ++-- TunnelEndpoint.cpp | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 3948afec..5d7afdfd 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -555,7 +555,7 @@ namespace i2p case eI2NPDatabaseSearchReply: case eI2NPDatabaseLookup: // forward to netDb - i2p::data::netdb.PostI2NPMsg (msg); + i2p::data::netdb.PostI2NPMsg (ToSharedI2NPMessage (msg)); break; case eI2NPDeliveryStatus: { diff --git a/NetDb.cpp b/NetDb.cpp index 5ba3c17d..a72e919a 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -92,7 +92,7 @@ namespace data { try { - I2NPMessage * msg = m_Queue.GetNextWithTimeout (15000); // 15 sec + auto msg = m_Queue.GetNextWithTimeout (15000); // 15 sec if (msg) { int numMsgs = 0; @@ -102,19 +102,19 @@ namespace data { case eI2NPDatabaseStore: LogPrint ("DatabaseStore"); - HandleDatabaseStoreMsg (ToSharedI2NPMessage (msg)); + HandleDatabaseStoreMsg (msg); break; case eI2NPDatabaseSearchReply: LogPrint ("DatabaseSearchReply"); - HandleDatabaseSearchReplyMsg (ToSharedI2NPMessage (msg)); + HandleDatabaseSearchReplyMsg (msg); break; case eI2NPDatabaseLookup: LogPrint ("DatabaseLookup"); - HandleDatabaseLookupMsg (ToSharedI2NPMessage (msg)); + HandleDatabaseLookupMsg (msg); break; default: // WTF? LogPrint (eLogError, "NetDb: unexpected message type ", msg->GetTypeID ()); - i2p::HandleI2NPMessage (msg); + //i2p::HandleI2NPMessage (msg); } if (numMsgs > 100) break; msg = m_Queue.Get (); @@ -912,7 +912,7 @@ namespace data return nullptr; // seems we have too few routers } - void NetDb::PostI2NPMsg (I2NPMessage * msg) + void NetDb::PostI2NPMsg (std::shared_ptr msg) { if (msg) m_Queue.Put (msg); } diff --git a/NetDb.h b/NetDb.h index 6b96f155..5028819e 100644 --- a/NetDb.h +++ b/NetDb.h @@ -56,7 +56,7 @@ namespace data std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; void SetUnreachable (const IdentHash& ident, bool unreachable); - void PostI2NPMsg (I2NPMessage * msg); + void PostI2NPMsg (std::shared_ptr msg); void Reseed (); @@ -89,7 +89,7 @@ namespace data bool m_IsRunning; std::thread * m_Thread; - i2p::util::Queue m_Queue; // of I2NPDatabaseStoreMsg + i2p::util::Queue > m_Queue; // of I2NPDatabaseStoreMsg Reseeder * m_Reseeder; diff --git a/Tunnel.cpp b/Tunnel.cpp index 59a9de2b..7c8714d9 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -453,8 +453,8 @@ namespace tunnel // transit DatabaseStore my contain new/updated RI // or DatabaseSearchReply with new routers auto ds = NewI2NPMessage (); - *ds = *msg; - i2p::data::netdb.PostI2NPMsg (ds); + *ds = *msg; // TODO: don't copy once msg is shared_ptr + i2p::data::netdb.PostI2NPMsg (ToSharedI2NPMessage (ds)); } tunnel->SendTunnelDataMsg (msg); } diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 24d86e57..db7ed7a6 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -267,8 +267,8 @@ namespace tunnel { // catch RI or reply with new list of routers auto ds = NewI2NPShortMessage (); - *ds = *(msg.data); - i2p::data::netdb.PostI2NPMsg (ds); + *ds = *(msg.data); // TODO: don't copy once msg.data is shared_ptr + i2p::data::netdb.PostI2NPMsg (ToSharedI2NPMessage (ds)); } i2p::transport::transports.SendMessage (msg.hash, msg.data); } From 3a63f6775a1624c906d096d04eb147f5f7f9e912 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Jun 2015 10:47:26 -0400 Subject: [PATCH 0487/6300] pass I2NP message to transport session as shared_ptr --- NTCPSession.cpp | 42 +++++++++++++----------------------------- NTCPSession.h | 18 +++++++++--------- SSUData.cpp | 4 +--- SSUData.h | 2 +- SSUSession.cpp | 26 ++++++++------------------ SSUSession.h | 8 ++++---- TransportSession.h | 4 ++-- Transports.cpp | 10 ++++++++-- Transports.h | 8 +------- 9 files changed, 47 insertions(+), 75 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index c5dac0a2..b7596997 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -83,8 +83,6 @@ namespace transport m_Socket.close (); transports.PeerDisconnected (shared_from_this ()); m_Server.RemoveNTCPSession (shared_from_this ()); - for (auto it: m_SendQueue) - DeleteI2NPMessage (it); m_SendQueue.clear (); if (m_NextMessage) { @@ -107,7 +105,7 @@ namespace transport m_DHKeysPair = nullptr; SendTimeSyncMessage (); - PostI2NPMessage (CreateDatabaseStoreMsg ()); // we tell immediately who we are + PostI2NPMessage (ToSharedI2NPMessage(CreateDatabaseStoreMsg ())); // we tell immediately who we are transports.PeerConnected (shared_from_this ()); } @@ -600,14 +598,14 @@ namespace transport return true; } - void NTCPSession::Send (i2p::I2NPMessage * msg) + void NTCPSession::Send (std::shared_ptr msg) { m_IsSending = true; boost::asio::async_write (m_Socket, CreateMsgBuffer (msg), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::vector{ msg })); + std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::vector >{ msg })); } - boost::asio::const_buffers_1 NTCPSession::CreateMsgBuffer (I2NPMessage * msg) + boost::asio::const_buffers_1 NTCPSession::CreateMsgBuffer (std::shared_ptr msg) { uint8_t * sendBuffer; int len; @@ -616,10 +614,7 @@ namespace transport { // regular I2NP if (msg->offset < 2) - { - LogPrint (eLogError, "Malformed I2NP message"); - i2p::DeleteI2NPMessage (msg); - } + LogPrint (eLogError, "Malformed I2NP message"); // TODO: sendBuffer = msg->GetBuffer () - 2; len = msg->GetLength (); htobe16buf (sendBuffer, len); @@ -644,7 +639,7 @@ namespace transport } - void NTCPSession::Send (const std::vector& msgs) + void NTCPSession::Send (const std::vector >& msgs) { m_IsSending = true; std::vector bufs; @@ -654,11 +649,9 @@ namespace transport std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msgs)); } - void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector msgs) + void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector > msgs) { m_IsSending = false; - for (auto it: msgs) - if (it) i2p::DeleteI2NPMessage (it); if (ecode) { LogPrint (eLogWarning, "Couldn't send msgs: ", ecode.message ()); @@ -686,20 +679,16 @@ namespace transport Send (nullptr); } - void NTCPSession::SendI2NPMessage (I2NPMessage * msg) + void NTCPSession::SendI2NPMessage (std::shared_ptr msg) { m_Server.GetService ().post (std::bind (&NTCPSession::PostI2NPMessage, shared_from_this (), msg)); } - void NTCPSession::PostI2NPMessage (I2NPMessage * msg) + void NTCPSession::PostI2NPMessage (std::shared_ptr msg) { if (msg) { - if (m_IsTerminated) - { - DeleteI2NPMessage (msg); - return; - } + if (m_IsTerminated) return; if (m_IsSending) m_SendQueue.push_back (msg); else @@ -707,19 +696,14 @@ namespace transport } } - void NTCPSession::SendI2NPMessages (const std::vector& msgs) + void NTCPSession::SendI2NPMessages (const std::vector >& msgs) { m_Server.GetService ().post (std::bind (&NTCPSession::PostI2NPMessages, shared_from_this (), msgs)); } - void NTCPSession::PostI2NPMessages (std::vector msgs) + void NTCPSession::PostI2NPMessages (std::vector > msgs) { - if (m_IsTerminated) - { - for (auto it: msgs) - DeleteI2NPMessage (it); - return; - } + if (m_IsTerminated) return; if (m_IsSending) { for (auto it: msgs) diff --git a/NTCPSession.h b/NTCPSession.h index 8e3e9073..b09713e4 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -61,13 +61,13 @@ namespace transport void ClientLogin (); void ServerLogin (); - void SendI2NPMessage (I2NPMessage * msg); - void SendI2NPMessages (const std::vector& msgs); + void SendI2NPMessage (std::shared_ptr msg); + void SendI2NPMessages (const std::vector >& msgs); private: - void PostI2NPMessage (I2NPMessage * msg); - void PostI2NPMessages (std::vector msgs); + void PostI2NPMessage (std::shared_ptr msg); + void PostI2NPMessages (std::vector > msgs); void Connected (); void SendTimeSyncMessage (); void SetIsEstablished (bool isEstablished) { m_IsEstablished = isEstablished; } @@ -96,10 +96,10 @@ namespace transport void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); bool DecryptNextBlock (const uint8_t * encrypted); - void Send (i2p::I2NPMessage * msg); - boost::asio::const_buffers_1 CreateMsgBuffer (I2NPMessage * msg); - void Send (const std::vector& msgs); - void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector msgs); + void Send (std::shared_ptr msg); + boost::asio::const_buffers_1 CreateMsgBuffer (std::shared_ptr msg); + void Send (const std::vector >& msgs); + void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector > msgs); // timer @@ -131,7 +131,7 @@ namespace transport i2p::I2NPMessagesHandler m_Handler; bool m_IsSending; - std::vector m_SendQueue; + std::vector > m_SendQueue; boost::asio::ip::address m_ConnectedFrom; // for ban }; diff --git a/SSUData.cpp b/SSUData.cpp index 4cacfd68..83b82aab 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -294,13 +294,12 @@ namespace transport ProcessFragments (buf); } - void SSUData::Send (i2p::I2NPMessage * msg) + void SSUData::Send (std::shared_ptr msg) { uint32_t msgID = msg->ToSSU (); if (m_SentMessages.count (msgID) > 0) { LogPrint (eLogWarning, "SSU message ", msgID, " already sent"); - DeleteI2NPMessage (msg); return; } if (m_SentMessages.empty ()) // schedule resend at first message only @@ -368,7 +367,6 @@ namespace transport len = 0; fragmentNum++; } - DeleteI2NPMessage (msg); } void SSUData::SendMsgAck (uint32_t msgID) diff --git a/SSUData.h b/SSUData.h index ff7bb96c..39d14cc4 100644 --- a/SSUData.h +++ b/SSUData.h @@ -89,7 +89,7 @@ namespace transport void ProcessMessage (uint8_t * buf, size_t len); void FlushReceivedMessage (); - void Send (i2p::I2NPMessage * msg); + void Send (std::shared_ptr msg); void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent); diff --git a/SSUSession.cpp b/SSUSession.cpp index 1e4f0fe7..39ef9df0 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -262,7 +262,7 @@ namespace transport if (paddingSize > 0) paddingSize = 16 - paddingSize; payload += paddingSize; // TODO: verify signature (need data from session request), payload points to signature - m_Data.Send (CreateDeliveryStatusMsg (0)); + m_Data.Send (ToSharedI2NPMessage(CreateDeliveryStatusMsg (0))); Established (); } @@ -783,7 +783,7 @@ namespace transport m_DHKeysPair = nullptr; } m_Data.Start (); - m_Data.Send (CreateDatabaseStoreMsg ()); + m_Data.Send (ToSharedI2NPMessage(CreateDatabaseStoreMsg ())); transports.PeerConnected (shared_from_this ()); if (m_PeerTest && (m_RemoteRouter && m_RemoteRouter->IsPeerTesting ())) SendPeerTest (); @@ -832,39 +832,29 @@ namespace transport } } - void SSUSession::SendI2NPMessage (I2NPMessage * msg) + void SSUSession::SendI2NPMessage (std::shared_ptr msg) { GetService ().post (std::bind (&SSUSession::PostI2NPMessage, shared_from_this (), msg)); } - void SSUSession::PostI2NPMessage (I2NPMessage * msg) + void SSUSession::PostI2NPMessage (std::shared_ptr msg) { - if (msg) - { - if (m_State == eSessionStateEstablished) - m_Data.Send (msg); - else - DeleteI2NPMessage (msg); - } + if (msg &&m_State == eSessionStateEstablished) + m_Data.Send (msg); } - void SSUSession::SendI2NPMessages (const std::vector& msgs) + void SSUSession::SendI2NPMessages (const std::vector >& msgs) { GetService ().post (std::bind (&SSUSession::PostI2NPMessages, shared_from_this (), msgs)); } - void SSUSession::PostI2NPMessages (std::vector msgs) + void SSUSession::PostI2NPMessages (std::vector > msgs) { if (m_State == eSessionStateEstablished) { for (auto it: msgs) if (it) m_Data.Send (it); } - else - { - for (auto it: msgs) - DeleteI2NPMessage (it); - } } void SSUSession::ProcessData (uint8_t * buf, size_t len) diff --git a/SSUSession.h b/SSUSession.h index 5f980784..fd1ba39e 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -76,8 +76,8 @@ namespace transport void Done (); boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; - void SendI2NPMessage (I2NPMessage * msg); - void SendI2NPMessages (const std::vector& msgs); + void SendI2NPMessage (std::shared_ptr msg); + void SendI2NPMessages (const std::vector >& msgs); void SendPeerTest (); // Alice SessionState GetState () const { return m_State; }; @@ -95,8 +95,8 @@ namespace transport boost::asio::io_service& GetService (); void CreateAESandMacKey (const uint8_t * pubKey); - void PostI2NPMessage (I2NPMessage * msg); - void PostI2NPMessages (std::vector msgs); + void PostI2NPMessage (std::shared_ptr msg); + void PostI2NPMessages (std::vector > msgs); void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session void ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); void SendSessionRequest (); diff --git a/TransportSession.h b/TransportSession.h index 95267452..dcd9f66c 100644 --- a/TransportSession.h +++ b/TransportSession.h @@ -71,8 +71,8 @@ namespace transport size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; - virtual void SendI2NPMessage (I2NPMessage * msg) = 0; - virtual void SendI2NPMessages (const std::vector& msgs) = 0; + virtual void SendI2NPMessage (std::shared_ptr msg) = 0; + virtual void SendI2NPMessages (const std::vector >& msgs) = 0; protected: diff --git a/Transports.cpp b/Transports.cpp index f288d113..c24bd508 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -255,11 +255,17 @@ namespace transport } } if (!it->second.sessions.empty ()) - it->second.sessions.front ()->SendI2NPMessages (msgs); + { + // TODO: remove this copy operation later + std::vector > msgs1; + for (auto it1: msgs) + msgs1.push_back (ToSharedI2NPMessage(it1)); + it->second.sessions.front ()->SendI2NPMessages (msgs1); + } else { for (auto it1: msgs) - it->second.delayedMessages.push_back (it1); + it->second.delayedMessages.push_back (ToSharedI2NPMessage(it1)); } } diff --git a/Transports.h b/Transports.h index ec04f6c7..d660a36c 100644 --- a/Transports.h +++ b/Transports.h @@ -62,19 +62,13 @@ namespace transport std::shared_ptr router; std::list > sessions; uint64_t creationTime; - std::vector delayedMessages; + std::vector > delayedMessages; void Done () { for (auto it: sessions) it->Done (); } - - ~Peer () - { - for (auto it :delayedMessages) - i2p::DeleteI2NPMessage (it); - } }; const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds From 25a163cdeb39d3fd02c3dbeb16c640b3d76c0f60 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Jun 2015 11:41:07 -0400 Subject: [PATCH 0488/6300] send I2NP messages as shared_ptr --- Transports.cpp | 41 ++++++++++++++++++++++++----------------- Transports.h | 8 +++++--- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index c24bd508..a4c8d1a8 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -215,21 +215,39 @@ namespace transport void Transports::SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) { - m_Service.post (std::bind (&Transports::PostMessages, this, ident, std::vector {msg})); + SendMessage (ident, ToSharedI2NPMessage (msg)); } void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector& msgs) + { + std::vector > msgs1; + for (auto it: msgs) + msgs1.push_back (ToSharedI2NPMessage (it)); + SendMessages (ident, msgs1); + } + + void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) + { + m_Service.post (std::bind (&Transports::PostMessages, this, ident, std::vector > {msg })); + } + + void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs) { m_Service.post (std::bind (&Transports::PostMessages, this, ident, msgs)); } - void Transports::PostMessages (i2p::data::IdentHash ident, std::vector msgs) + void Transports::PostMessages (i2p::data::IdentHash ident, std::vector > msgs) { if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) { // we send it to ourself for (auto it: msgs) - i2p::HandleI2NPMessage (it); + { + // TODO: + auto m = NewI2NPMessage (); + *m = *(it); + i2p::HandleI2NPMessage (m); + } return; } auto it = m_Peers.find (ident); @@ -247,25 +265,14 @@ namespace transport { LogPrint (eLogError, "Transports::PostMessages ", ex.what ()); } - if (!connected) - { - for (auto it1: msgs) - DeleteI2NPMessage (it1); - return; - } + if (!connected) return; } if (!it->second.sessions.empty ()) - { - // TODO: remove this copy operation later - std::vector > msgs1; - for (auto it1: msgs) - msgs1.push_back (ToSharedI2NPMessage(it1)); - it->second.sessions.front ()->SendI2NPMessages (msgs1); - } + it->second.sessions.front ()->SendI2NPMessages (msgs); else { for (auto it1: msgs) - it->second.delayedMessages.push_back (ToSharedI2NPMessage(it1)); + it->second.delayedMessages.push_back (it1); } } diff --git a/Transports.h b/Transports.h index d660a36c..b29d7f1a 100644 --- a/Transports.h +++ b/Transports.h @@ -87,8 +87,10 @@ namespace transport i2p::transport::DHKeysPair * GetNextDHKeysPair (); void ReuseDHKeysPair (DHKeysPair * pair); - void SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); - void SendMessages (const i2p::data::IdentHash& ident, const std::vector& msgs); + void SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); // deprecated + void SendMessages (const i2p::data::IdentHash& ident, const std::vector& msgs); // deprecated + void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); + void SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs); void CloseSession (std::shared_ptr router); void PeerConnected (std::shared_ptr session); @@ -110,7 +112,7 @@ namespace transport void Run (); void RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); void HandleRequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); - void PostMessages (i2p::data::IdentHash ident, std::vector msgs); + void PostMessages (i2p::data::IdentHash ident, std::vector > msgs); void PostCloseSession (std::shared_ptr router); bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); void HandlePeerCleanupTimer (const boost::system::error_code& ecode); From 5ca86b87f5476737fbad14df9daa7ad84fff5986 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Jun 2015 12:08:06 -0400 Subject: [PATCH 0489/6300] create shared I2NP tunnel message in OBGW --- TunnelGateway.cpp | 8 +++----- TunnelGateway.h | 7 ++++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index fbef1208..33935259 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -12,8 +12,6 @@ namespace tunnel { TunnelGatewayBuffer::~TunnelGatewayBuffer () { - for (auto it: m_TunnelDataMsgs) - DeleteI2NPMessage (it); } void TunnelGatewayBuffer::PutI2NPMsg (const TunnelMessageBlock& block) @@ -138,7 +136,7 @@ namespace tunnel void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage () { - m_CurrentTunnelDataMsg = NewI2NPShortMessage (); + m_CurrentTunnelDataMsg = ToSharedI2NPMessage (NewI2NPShortMessage ()); m_CurrentTunnelDataMsg->Align (12); // we reserve space for padding m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE; @@ -192,8 +190,8 @@ namespace tunnel auto tunnelMsgs = m_Buffer.GetTunnelDataMsgs (); for (auto tunnelMsg : tunnelMsgs) { - m_Tunnel->EncryptTunnelMsg (tunnelMsg); - FillI2NPMessageHeader (tunnelMsg, eI2NPTunnelData); + m_Tunnel->EncryptTunnelMsg (tunnelMsg.get ()); // TODO: + FillI2NPMessageHeader (tunnelMsg.get (), eI2NPTunnelData); // TODO: m_NumSentBytes += TUNNEL_DATA_MSG_SIZE; } i2p::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), tunnelMsgs); diff --git a/TunnelGateway.h b/TunnelGateway.h index b81c01d3..cfad17b5 100644 --- a/TunnelGateway.h +++ b/TunnelGateway.h @@ -3,6 +3,7 @@ #include #include +#include #include "I2NPProtocol.h" #include "TunnelBase.h" @@ -17,7 +18,7 @@ namespace tunnel m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0) {}; ~TunnelGatewayBuffer (); void PutI2NPMsg (const TunnelMessageBlock& block); - const std::vector& GetTunnelDataMsgs () const { return m_TunnelDataMsgs; }; + const std::vector >& GetTunnelDataMsgs () const { return m_TunnelDataMsgs; }; void ClearTunnelDataMsgs (); void CompleteCurrentTunnelDataMessage (); @@ -28,8 +29,8 @@ namespace tunnel private: uint32_t m_TunnelID; - std::vector m_TunnelDataMsgs; - I2NPMessage * m_CurrentTunnelDataMsg; + std::vector > m_TunnelDataMsgs; + std::shared_ptr m_CurrentTunnelDataMsg; size_t m_RemainingSize; }; From a7cd16c1590683995751d891360dbc3dbceb8fbe Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Jun 2015 12:25:02 -0400 Subject: [PATCH 0490/6300] use shared_ptr for direct DatabaseLookup message --- NetDbRequests.cpp | 4 ++-- NetDbRequests.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NetDbRequests.cpp b/NetDbRequests.cpp index 5e71e3f7..3509bcc7 100644 --- a/NetDbRequests.cpp +++ b/NetDbRequests.cpp @@ -19,13 +19,13 @@ namespace data return msg; } - I2NPMessage * RequestedDestination::CreateRequestMessage (const IdentHash& floodfill) + std::shared_ptr RequestedDestination::CreateRequestMessage (const IdentHash& floodfill) { I2NPMessage * msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers); m_ExcludedPeers.insert (floodfill); m_CreationTime = i2p::util::GetSecondsSinceEpoch (); - return msg; + return ToSharedI2NPMessage (msg); } void RequestedDestination::ClearExcludedPeers () diff --git a/NetDbRequests.h b/NetDbRequests.h index 4f7555d4..9dc49b26 100644 --- a/NetDbRequests.h +++ b/NetDbRequests.h @@ -29,7 +29,7 @@ namespace data bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); }; uint64_t GetCreationTime () const { return m_CreationTime; }; I2NPMessage * CreateRequestMessage (std::shared_ptr, std::shared_ptr replyTunnel); - I2NPMessage * CreateRequestMessage (const IdentHash& floodfill); + std::shared_ptr CreateRequestMessage (const IdentHash& floodfill); void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; }; bool IsRequestComplete () const { return m_RequestComplete != nullptr; }; From 98c91a01e39e8e430123438cab237a9a7a512734 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Jun 2015 12:26:07 -0400 Subject: [PATCH 0491/6300] use shared_ptr for outbound tunnel build messages --- Tunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 7c8714d9..6568c509 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -83,7 +83,7 @@ namespace tunnel if (outboundTunnel) outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); else - i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); + i2p::transport::transports.SendMessage (GetNextIdentHash (), ToSharedI2NPMessage (msg)); } bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) From 122b8c2a84b7a03b2712327251d04a6eb8dfd5e0 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Jun 2015 12:31:28 -0400 Subject: [PATCH 0492/6300] use shared_ptr for transit tunnel participant --- TransitTunnel.cpp | 4 +--- TransitTunnel.h | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index a3135db1..e0828f13 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -27,8 +27,6 @@ namespace tunnel TransitTunnelParticipant::~TransitTunnelParticipant () { - for (auto it: m_TunnelDataMsgs) - i2p::DeleteI2NPMessage (it); } void TransitTunnelParticipant::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) @@ -38,7 +36,7 @@ namespace tunnel m_NumTransmittedBytes += tunnelMsg->GetLength (); htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); FillI2NPMessageHeader (tunnelMsg, eI2NPTunnelData); - m_TunnelDataMsgs.push_back (tunnelMsg); + m_TunnelDataMsgs.push_back (ToSharedI2NPMessage (tunnelMsg)); } void TransitTunnelParticipant::FlushTunnelDataMsgs () diff --git a/TransitTunnel.h b/TransitTunnel.h index a2a0347f..3f7d8676 100644 --- a/TransitTunnel.h +++ b/TransitTunnel.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "aes.h" #include "I2NPProtocol.h" #include "TunnelEndpoint.h" @@ -59,7 +60,7 @@ namespace tunnel private: size_t m_NumTransmittedBytes; - std::vector m_TunnelDataMsgs; + std::vector > m_TunnelDataMsgs; }; class TransitTunnelGateway: public TransitTunnel From 4ed7e29896bf00f73282be3bdb61bc23482b8b54 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 19 Jun 2015 14:38:31 -0400 Subject: [PATCH 0493/6300] use shared_ptr for I2NP messages through tunnels --- Datagram.cpp | 2 +- Destination.cpp | 4 ++-- I2NPProtocol.cpp | 30 +++++++++++++----------------- I2NPProtocol.h | 6 +++--- NetDb.cpp | 10 +++++----- RouterContext.cpp | 2 +- Streaming.cpp | 2 +- TransitTunnel.cpp | 18 ++++++++---------- TransitTunnel.h | 12 ++++++------ Transports.cpp | 7 +------ Tunnel.cpp | 36 ++++++++++++------------------------ Tunnel.h | 16 ++++++++-------- TunnelBase.h | 8 ++++---- TunnelEndpoint.cpp | 37 +++++-------------------------------- TunnelEndpoint.h | 6 +++--- TunnelGateway.cpp | 6 ++---- 16 files changed, 75 insertions(+), 127 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index adb76d58..9179fb80 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -65,7 +65,7 @@ namespace datagram { i2p::tunnel::eDeliveryTypeTunnel, leases[i].tunnelGateway, leases[i].tunnelID, - garlic + ToSharedI2NPMessage (garlic) }); outboundTunnel->SendTunnelDataMsg (msgs); } diff --git a/Destination.cpp b/Destination.cpp index 27f2564c..107bcf0a 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -240,7 +240,7 @@ namespace client HandleDatabaseSearchReplyMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET)); break; default: - i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); + i2p::HandleI2NPMessage (ToSharedI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from))); } } @@ -589,7 +589,7 @@ namespace client i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeRouter, - nextFloodfill->GetIdentHash (), 0, msg + nextFloodfill->GetIdentHash (), 0, ToSharedI2NPMessage (msg) } }); request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT)); diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 5d7afdfd..e4685c7e 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -445,7 +445,7 @@ namespace i2p return msg; } - I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessage * msg) + std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr msg) { if (msg->offset >= I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE) { @@ -456,14 +456,13 @@ namespace i2p htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len); msg->offset -= (I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE); msg->len = msg->offset + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE +len; - FillI2NPMessageHeader (msg, eI2NPTunnelGateway); + FillI2NPMessageHeader (msg.get(), eI2NPTunnelGateway); // TODO return msg; } else { I2NPMessage * msg1 = CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ()); - DeleteI2NPMessage (msg); - return msg1; + return ToSharedI2NPMessage (msg1); } } @@ -522,7 +521,7 @@ namespace i2p } } - void HandleI2NPMessage (I2NPMessage * msg) + void HandleI2NPMessage (std::shared_ptr msg) { if (msg) { @@ -539,32 +538,30 @@ namespace i2p case eI2NPGarlic: { LogPrint ("Garlic"); - auto sharedMsg = ToSharedI2NPMessage (msg); if (msg->from) { if (msg->from->GetTunnelPool ()) - msg->from->GetTunnelPool ()->ProcessGarlicMessage (sharedMsg); + msg->from->GetTunnelPool ()->ProcessGarlicMessage (msg); else LogPrint (eLogInfo, "Local destination for garlic doesn't exist anymore"); } else - i2p::context.ProcessGarlicMessage (sharedMsg); + i2p::context.ProcessGarlicMessage (msg); break; } case eI2NPDatabaseStore: case eI2NPDatabaseSearchReply: case eI2NPDatabaseLookup: // forward to netDb - i2p::data::netdb.PostI2NPMsg (ToSharedI2NPMessage (msg)); + i2p::data::netdb.PostI2NPMsg (msg); break; case eI2NPDeliveryStatus: { LogPrint ("DeliveryStatus"); - auto sharedMsg = ToSharedI2NPMessage (msg); if (msg->from && msg->from->GetTunnelPool ()) - msg->from->GetTunnelPool ()->ProcessDeliveryStatus (sharedMsg); + msg->from->GetTunnelPool ()->ProcessDeliveryStatus (msg); else - i2p::context.ProcessDeliveryStatusMessage (sharedMsg); + i2p::context.ProcessDeliveryStatusMessage (msg); break; } case eI2NPVariableTunnelBuild: @@ -576,7 +573,6 @@ namespace i2p break; default: HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); - DeleteI2NPMessage (msg); } } } @@ -586,20 +582,20 @@ namespace i2p Flush (); } - void I2NPMessagesHandler::PutNextMessage (I2NPMessage * msg) + void I2NPMessagesHandler::PutNextMessage (I2NPMessage * msg) { if (msg) { switch (msg->GetTypeID ()) { case eI2NPTunnelData: - m_TunnelMsgs.push_back (msg); + m_TunnelMsgs.push_back (ToSharedI2NPMessage (msg)); break; case eI2NPTunnelGateway: - m_TunnelGatewayMsgs.push_back (msg); + m_TunnelGatewayMsgs.push_back (ToSharedI2NPMessage (msg)); break; default: - HandleI2NPMessage (msg); + HandleI2NPMessage (ToSharedI2NPMessage (msg)); } } } diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 093d66ed..cee97bec 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -225,11 +225,11 @@ namespace tunnel I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len); I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0); - I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessage * msg); + std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr msg); size_t GetI2NPMessageLength (const uint8_t * msg); void HandleI2NPMessage (uint8_t * msg, size_t len); - void HandleI2NPMessage (I2NPMessage * msg); + void HandleI2NPMessage (std::shared_ptr msg); class I2NPMessagesHandler { @@ -241,7 +241,7 @@ namespace tunnel private: - std::vector m_TunnelMsgs, m_TunnelGatewayMsgs; + std::vector > m_TunnelMsgs, m_TunnelGatewayMsgs; }; } diff --git a/NetDb.cpp b/NetDb.cpp index a72e919a..e97f6985 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -597,7 +597,7 @@ namespace data { i2p::tunnel::eDeliveryTypeRouter, nextFloodfill->GetIdentHash (), 0, - CreateDatabaseStoreMsg () + ToSharedI2NPMessage (CreateDatabaseStoreMsg ()) }); // request destination @@ -606,7 +606,7 @@ namespace data msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeRouter, - nextFloodfill->GetIdentHash (), 0, msg + nextFloodfill->GetIdentHash (), 0, ToSharedI2NPMessage (msg) }); deleteDest = false; } @@ -763,7 +763,7 @@ namespace data if (outbound) outbound->SendTunnelDataMsg (buf+32, replyTunnelID, replyMsg); else - transports.SendMessage (buf+32, i2p::CreateTunnelGatewayMsg (replyTunnelID, replyMsg)); + transports.SendMessage (buf+32, i2p::CreateTunnelGatewayMsg (replyTunnelID, ToSharedI2NPMessage(replyMsg))); } else transports.SendMessage (buf+32, replyMsg); @@ -804,13 +804,13 @@ namespace data { i2p::tunnel::eDeliveryTypeRouter, floodfill->GetIdentHash (), 0, - CreateDatabaseStoreMsg () // tell floodfill about us + ToSharedI2NPMessage (CreateDatabaseStoreMsg ()) // tell floodfill about us }); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeRouter, floodfill->GetIdentHash (), 0, - dest->CreateRequestMessage (floodfill, inbound) // explore + ToSharedI2NPMessage (dest->CreateRequestMessage (floodfill, inbound)) // explore }); } else diff --git a/RouterContext.cpp b/RouterContext.cpp index c9328b5c..29815b06 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -296,7 +296,7 @@ namespace i2p void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { - i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); + i2p::HandleI2NPMessage (ToSharedI2NPMessage(CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from))); } void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) diff --git a/Streaming.cpp b/Streaming.cpp index 4d9342c1..e20efffb 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -617,7 +617,7 @@ namespace stream { i2p::tunnel::eDeliveryTypeTunnel, m_CurrentRemoteLease.tunnelGateway, m_CurrentRemoteLease.tunnelID, - msg + ToSharedI2NPMessage (msg) }); m_NumSentBytes += it->GetLength (); } diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index e0828f13..aa4636d6 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -20,7 +20,7 @@ namespace tunnel m_Encryption.SetKeys (layerKey, ivKey); } - void TransitTunnel::EncryptTunnelMsg (I2NPMessage * tunnelMsg) + void TransitTunnel::EncryptTunnelMsg (std::shared_ptr tunnelMsg) { m_Encryption.Encrypt (tunnelMsg->GetPayload () + 4); } @@ -29,14 +29,14 @@ namespace tunnel { } - void TransitTunnelParticipant::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) + void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { EncryptTunnelMsg (tunnelMsg); m_NumTransmittedBytes += tunnelMsg->GetLength (); htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); - FillI2NPMessageHeader (tunnelMsg, eI2NPTunnelData); - m_TunnelDataMsgs.push_back (ToSharedI2NPMessage (tunnelMsg)); + FillI2NPMessageHeader (tunnelMsg.get (), eI2NPTunnelData); // TODO + m_TunnelDataMsgs.push_back (tunnelMsg); } void TransitTunnelParticipant::FlushTunnelDataMsgs () @@ -51,19 +51,17 @@ namespace tunnel } } - void TransitTunnel::SendTunnelDataMsg (i2p::I2NPMessage * msg) + void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) { LogPrint (eLogError, "We are not a gateway for transit tunnel ", m_TunnelID); - i2p::DeleteI2NPMessage (msg); } - void TransitTunnel::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) + void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { LogPrint (eLogError, "Incoming tunnel message is not supported ", m_TunnelID); - DeleteI2NPMessage (tunnelMsg); } - void TransitTunnelGateway::SendTunnelDataMsg (i2p::I2NPMessage * msg) + void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr msg) { TunnelMessageBlock block; block.deliveryType = eDeliveryTypeLocal; @@ -78,7 +76,7 @@ namespace tunnel m_Gateway.SendBuffer (); } - void TransitTunnelEndpoint::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) + void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { EncryptTunnelMsg (tunnelMsg); diff --git a/TransitTunnel.h b/TransitTunnel.h index 3f7d8676..e6e0601d 100644 --- a/TransitTunnel.h +++ b/TransitTunnel.h @@ -28,9 +28,9 @@ namespace tunnel uint32_t GetTunnelID () const { return m_TunnelID; }; // implements TunnelBase - void SendTunnelDataMsg (i2p::I2NPMessage * msg); - void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg); - void EncryptTunnelMsg (I2NPMessage * tunnelMsg); + void SendTunnelDataMsg (std::shared_ptr msg); + void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void EncryptTunnelMsg (std::shared_ptr tunnelMsg); uint32_t GetNextTunnelID () const { return m_NextTunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; }; @@ -54,7 +54,7 @@ namespace tunnel ~TransitTunnelParticipant (); size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; }; - void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); void FlushTunnelDataMsgs (); private: @@ -73,7 +73,7 @@ namespace tunnel TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_Gateway(this) {}; - void SendTunnelDataMsg (i2p::I2NPMessage * msg); + void SendTunnelDataMsg (std::shared_ptr msg); void FlushTunnelDataMsgs (); size_t GetNumTransmittedBytes () const { return m_Gateway.GetNumSentBytes (); }; @@ -93,7 +93,7 @@ namespace tunnel TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_Endpoint (false) {}; // transit endpoint is always outbound - void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); } private: diff --git a/Transports.cpp b/Transports.cpp index a4c8d1a8..381d31e8 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -242,12 +242,7 @@ namespace transport { // we send it to ourself for (auto it: msgs) - { - // TODO: - auto m = NewI2NPMessage (); - *m = *(it); - i2p::HandleI2NPMessage (m); - } + i2p::HandleI2NPMessage (it); return; } auto it = m_Peers.find (ident); diff --git a/Tunnel.cpp b/Tunnel.cpp index 6568c509..5341da32 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -31,7 +31,7 @@ namespace tunnel CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); auto numHops = m_Config->GetNumHops (); int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops; - I2NPMessage * msg = NewI2NPShortMessage (); + auto msg = NewI2NPShortMessage (); *msg->GetPayload () = numRecords; msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1; @@ -140,7 +140,7 @@ namespace tunnel return established; } - void Tunnel::EncryptTunnelMsg (I2NPMessage * tunnelMsg) + void Tunnel::EncryptTunnelMsg (std::shared_ptr tunnelMsg) { uint8_t * payload = tunnelMsg->GetPayload () + 4; TunnelHopConfig * hop = m_Config->GetLastHop (); @@ -151,13 +151,12 @@ namespace tunnel } } - void Tunnel::SendTunnelDataMsg (i2p::I2NPMessage * msg) + void Tunnel::SendTunnelDataMsg (std::shared_ptr msg) { LogPrint (eLogInfo, "Can't send I2NP messages without delivery instructions"); - DeleteI2NPMessage (msg); } - void InboundTunnel::HandleTunnelDataMsg (I2NPMessage * msg) + void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) { if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive msg->from = shared_from_this (); @@ -181,7 +180,7 @@ namespace tunnel } else block.deliveryType = eDeliveryTypeLocal; - block.data = msg; + block.data = ToSharedI2NPMessage (msg); std::unique_lock l(m_SendMutex); m_Gateway.SendTunnelDataMsg (block); @@ -195,10 +194,9 @@ namespace tunnel m_Gateway.SendBuffer (); } - void OutboundTunnel::HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) + void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { LogPrint (eLogError, "Incoming message for outbound tunnel ", GetTunnelID ()); - DeleteI2NPMessage (tunnelMsg); } Tunnels tunnels; @@ -352,7 +350,7 @@ namespace tunnel { try { - I2NPMessage * msg = m_Queue.GetNextWithTimeout (1000); // 1 sec + auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec if (msg) { uint32_t prevTunnelID = 0, tunnelID = 0; @@ -383,27 +381,18 @@ namespace tunnel else // tunnel gateway assumed HandleTunnelGatewayMsg (tunnel, msg); } - else - { + else LogPrint (eLogWarning, "Tunnel ", tunnelID, " not found"); - DeleteI2NPMessage (msg); - } break; } case eI2NPVariableTunnelBuild: case eI2NPVariableTunnelBuildReply: case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: - { HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); - DeleteI2NPMessage (msg); - break; - } + break; default: - { LogPrint (eLogError, "Unexpected messsage type ", (int)typeID); - DeleteI2NPMessage (msg); - } } msg = m_Queue.Get (); @@ -432,12 +421,11 @@ namespace tunnel } } - void Tunnels::HandleTunnelGatewayMsg (TunnelBase * tunnel, I2NPMessage * msg) + void Tunnels::HandleTunnelGatewayMsg (TunnelBase * tunnel, std::shared_ptr msg) { if (!tunnel) { LogPrint (eLogError, "Missing tunnel for TunnelGateway"); - i2p::DeleteI2NPMessage (msg); return; } const uint8_t * payload = msg->GetPayload (); @@ -661,12 +649,12 @@ namespace tunnel } } - void Tunnels::PostTunnelData (I2NPMessage * msg) + void Tunnels::PostTunnelData (std::shared_ptr msg) { if (msg) m_Queue.Put (msg); } - void Tunnels::PostTunnelData (const std::vector& msgs) + void Tunnels::PostTunnelData (const std::vector >& msgs) { m_Queue.Put (msgs); } diff --git a/Tunnel.h b/Tunnel.h index b6362997..f8c5e9cc 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -64,8 +64,8 @@ namespace tunnel bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); // implements TunnelBase - void SendTunnelDataMsg (i2p::I2NPMessage * msg); - void EncryptTunnelMsg (I2NPMessage * tunnelMsg); + void SendTunnelDataMsg (std::shared_ptr msg); + void EncryptTunnelMsg (std::shared_ptr tunnelMsg); uint32_t GetNextTunnelID () const { return m_Config->GetFirstHop ()->tunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return m_Config->GetFirstHop ()->router->GetIdentHash (); }; @@ -90,7 +90,7 @@ namespace tunnel size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; // implements TunnelBase - void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); uint32_t GetTunnelID () const { return GetNextTunnelID (); }; private: @@ -104,7 +104,7 @@ namespace tunnel public: InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; - void HandleTunnelDataMsg (I2NPMessage * msg); + void HandleTunnelDataMsg (std::shared_ptr msg); size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; // implements TunnelBase @@ -135,8 +135,8 @@ namespace tunnel void AddTransitTunnel (TransitTunnel * tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); - void PostTunnelData (I2NPMessage * msg); - void PostTunnelData (const std::vector& msgs); + void PostTunnelData (std::shared_ptr msg); + void PostTunnelData (const std::vector >& msgs); template std::shared_ptr CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel = nullptr); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); @@ -150,7 +150,7 @@ namespace tunnel template std::shared_ptr GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels); - void HandleTunnelGatewayMsg (TunnelBase * tunnel, I2NPMessage * msg); + void HandleTunnelGatewayMsg (TunnelBase * tunnel, std::shared_ptr msg); void Run (); void ManageTunnels (); @@ -177,7 +177,7 @@ namespace tunnel std::mutex m_PoolsMutex; std::list> m_Pools; std::shared_ptr m_ExploratoryPool; - i2p::util::Queue m_Queue; + i2p::util::Queue > m_Queue; // some stats int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; diff --git a/TunnelBase.h b/TunnelBase.h index d867bf9a..5470f139 100644 --- a/TunnelBase.h +++ b/TunnelBase.h @@ -26,7 +26,7 @@ namespace tunnel TunnelDeliveryType deliveryType; i2p::data::IdentHash hash; uint32_t tunnelID; - I2NPMessage * data; + std::shared_ptr data; }; class TunnelBase @@ -37,10 +37,10 @@ namespace tunnel TunnelBase (): m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {}; virtual ~TunnelBase () {}; - virtual void HandleTunnelDataMsg (i2p::I2NPMessage * tunnelMsg) = 0; - virtual void SendTunnelDataMsg (i2p::I2NPMessage * msg) = 0; + virtual void HandleTunnelDataMsg (std::shared_ptr tunnelMsg) = 0; + virtual void SendTunnelDataMsg (std::shared_ptr msg) = 0; virtual void FlushTunnelDataMsgs () {}; - virtual void EncryptTunnelMsg (I2NPMessage * tunnelMsg) = 0; + virtual void EncryptTunnelMsg (std::shared_ptr tunnelMsg) = 0; virtual uint32_t GetNextTunnelID () const = 0; virtual const i2p::data::IdentHash& GetNextIdentHash () const = 0; virtual uint32_t GetTunnelID () const = 0; // as known at our side diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index db7ed7a6..e493e168 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -13,13 +13,9 @@ namespace tunnel { TunnelEndpoint::~TunnelEndpoint () { - for (auto it: m_IncompleteMessages) - i2p::DeleteI2NPMessage (it.second.data); - for (auto it: m_OutOfSequenceFragments) - i2p::DeleteI2NPMessage (it.second.data); } - void TunnelEndpoint::HandleDecryptedTunnelDataMsg (I2NPMessage * msg) + void TunnelEndpoint::HandleDecryptedTunnelDataMsg (std::shared_ptr msg) { m_NumReceivedBytes += TUNNEL_DATA_MSG_SIZE; @@ -35,7 +31,6 @@ namespace tunnel if (memcmp (hash, decrypted, 4)) { LogPrint (eLogError, "TunnelMessage: checksum verification failed"); - i2p::DeleteI2NPMessage (msg); return; } // process fragments @@ -97,7 +92,7 @@ namespace tunnel if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) { // this is not last message. we have to copy it - m.data = NewI2NPShortMessage (); + m.data = ToSharedI2NPMessage (NewI2NPShortMessage ()); m.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header m.data->len += TUNNEL_GATEWAY_HEADER_SIZE; *(m.data) = *msg; @@ -118,10 +113,7 @@ namespace tunnel if (ret.second) HandleOutOfSequenceFragment (msgID, ret.first->second); else - { LogPrint (eLogError, "Incomplete message ", msgID, "already exists"); - DeleteI2NPMessage (m.data); - } } else { @@ -130,20 +122,14 @@ namespace tunnel } } else - { LogPrint (eLogError, "Message is fragmented, but msgID is not presented"); - DeleteI2NPMessage (m.data); - } } fragment += size; } } else - { LogPrint (eLogError, "TunnelMessage: zero not found"); - i2p::DeleteI2NPMessage (msg); - } } void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m) @@ -161,9 +147,8 @@ namespace tunnel if (msg.data->len + size > msg.data->maxLen) { LogPrint (eLogInfo, "Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); - I2NPMessage * newMsg = NewI2NPMessage (); + auto newMsg = ToSharedI2NPMessage (NewI2NPMessage ()); *newMsg = *(msg.data); - DeleteI2NPMessage (msg.data); msg.data = newMsg; } memcpy (msg.data->buf + msg.data->len, fragment, size); // concatenate fragment @@ -183,10 +168,8 @@ namespace tunnel else { LogPrint (eLogError, "Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size. Message dropped"); - i2p::DeleteI2NPMessage (msg.data); m_IncompleteMessages.erase (it); } - i2p::DeleteI2NPMessage (m.data); } else { @@ -201,13 +184,11 @@ namespace tunnel } } - void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, I2NPMessage * data) + void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data) { auto it = m_OutOfSequenceFragments.find (msgID); if (it == m_OutOfSequenceFragments.end ()) m_OutOfSequenceFragments.insert (std::pair (msgID, {fragmentNum, isLastFragment, data})); - else - i2p::DeleteI2NPMessage (data); } void TunnelEndpoint::HandleOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg) @@ -222,9 +203,8 @@ namespace tunnel if (msg.data->len + size > msg.data->maxLen) { LogPrint (eLogInfo, "Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); - I2NPMessage * newMsg = NewI2NPMessage (); + auto newMsg = ToSharedI2NPMessage (NewI2NPMessage ()); *newMsg = *(msg.data); - DeleteI2NPMessage (msg.data); msg.data = newMsg; } memcpy (msg.data->buf + msg.data->len, it->second.data->GetBuffer (), size); // concatenate out-of-sync fragment @@ -237,7 +217,6 @@ namespace tunnel } else msg.nextFragmentNum++; - i2p::DeleteI2NPMessage (it->second.data); m_OutOfSequenceFragments.erase (it); } } @@ -273,17 +252,11 @@ namespace tunnel i2p::transport::transports.SendMessage (msg.hash, msg.data); } else // we shouldn't send this message. possible leakage - { LogPrint (eLogError, "Message to another router arrived from an inbound tunnel. Dropped"); - i2p::DeleteI2NPMessage (msg.data); - } } break; default: - { LogPrint (eLogError, "TunnelMessage: Unknown delivery type ", (int)msg.deliveryType); - i2p::DeleteI2NPMessage (msg.data); - } }; } } diff --git a/TunnelEndpoint.h b/TunnelEndpoint.h index 61d0a9ab..20b9105f 100644 --- a/TunnelEndpoint.h +++ b/TunnelEndpoint.h @@ -22,7 +22,7 @@ namespace tunnel { uint8_t fragmentNum; bool isLastFragment; - I2NPMessage * data; + std::shared_ptr data; }; public: @@ -31,14 +31,14 @@ namespace tunnel ~TunnelEndpoint (); size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; - void HandleDecryptedTunnelDataMsg (I2NPMessage * msg); + void HandleDecryptedTunnelDataMsg (std::shared_ptr msg); private: void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m); void HandleNextMessage (const TunnelMessageBlock& msg); - void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, I2NPMessage * data); + void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data); void HandleOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); private: diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index 33935259..2763ae17 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -40,7 +40,7 @@ namespace tunnel di[0] = block.deliveryType << 5; // set delivery type // create fragments - I2NPMessage * msg = block.data; + std::shared_ptr msg = block.data; auto fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length if (fullMsgLen <= m_RemainingSize) { @@ -53,7 +53,6 @@ namespace tunnel m_RemainingSize -= diLen + msg->GetLength (); if (!m_RemainingSize) CompleteCurrentTunnelDataMessage (); - DeleteI2NPMessage (msg); } else { @@ -117,7 +116,6 @@ namespace tunnel size += s; fragmentNumber++; } - DeleteI2NPMessage (msg); } else { @@ -190,7 +188,7 @@ namespace tunnel auto tunnelMsgs = m_Buffer.GetTunnelDataMsgs (); for (auto tunnelMsg : tunnelMsgs) { - m_Tunnel->EncryptTunnelMsg (tunnelMsg.get ()); // TODO: + m_Tunnel->EncryptTunnelMsg (tunnelMsg); FillI2NPMessageHeader (tunnelMsg.get (), eI2NPTunnelData); // TODO: m_NumSentBytes += TUNNEL_DATA_MSG_SIZE; } From 38ebe28923f77cd3d8a21669b14873756f0f0f4a Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Mon, 15 Jun 2015 16:55:21 -0500 Subject: [PATCH 0494/6300] Rearrange eol removal for handshake --- SAM.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index 7de1a8a8..cb2cac3e 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -85,6 +85,9 @@ namespace client else { m_Buffer[bytes_transferred] = 0; + char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred); + if (eol) + *eol = 0; LogPrint ("SAM handshake ", m_Buffer); char * separator = strchr (m_Buffer, ' '); if (separator) @@ -101,9 +104,6 @@ namespace client if (separator) { separator++; - char *eol = strchr (separator, '\n'); - if (eol) - *eol = 0; std::map params; ExtractParams (separator, params); auto it = params.find (SAM_PARAM_MAX); From 490b65dfe233fe298df05b75294abeb1f7bc00a3 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Fri, 19 Jun 2015 14:44:50 -0500 Subject: [PATCH 0495/6300] Materialize temporary string obtained from boost path --- DaemonLinux.cpp | 3 ++- NetDb.cpp | 42 +++++++++++++----------------------------- NetDb.h | 4 ++-- RouterInfo.cpp | 9 ++++++--- 4 files changed, 23 insertions(+), 35 deletions(-) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 48a05031..5824430e 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -62,7 +62,8 @@ namespace i2p LogPrint("Error, could not create process group."); return false; } - chdir(i2p::util::filesystem::GetDataDir().string().c_str()); + std::string d(i2p::util::filesystem::GetDataDir()); // make a copy + chdir(d.c_str()); // close stdin/stdout/stderr descriptors ::close (0); diff --git a/NetDb.cpp b/NetDb.cpp index e97f6985..b802f3dd 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -21,11 +21,7 @@ namespace i2p { namespace data { -#ifndef _WIN32 - const char NetDb::m_NetDbPath[] = "/netDb"; -#else - const char NetDb::m_NetDbPath[] = "\\netDb"; -#endif + const char NetDb::m_NetDbPath[] = "netDb"; NetDb netdb; NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr) @@ -40,7 +36,7 @@ namespace data void NetDb::Start () { - Load (m_NetDbPath); + Load (); if (m_RouterInfos.size () < 25) // reseed if # of router less than 50 { // try SU3 first @@ -55,7 +51,7 @@ namespace data { m_Reseeder->reseedNow(); reseedRetries++; - Load (m_NetDbPath); + Load (); } } } @@ -133,7 +129,7 @@ namespace data { if (lastSave) { - SaveUpdated (m_NetDbPath); + SaveUpdated (); ManageLeaseSets (); } lastSave = ts; @@ -295,10 +291,9 @@ namespace data LogPrint (eLogWarning, "Failed to reseed after 10 attempts"); } - void NetDb::Load (const char * directory) + void NetDb::Load () { - boost::filesystem::path p (i2p::util::filesystem::GetDataDir()); - p /= (directory); + boost::filesystem::path p(i2p::util::filesystem::GetDataDir() / m_NetDbPath); if (!boost::filesystem::exists (p)) { // seems netDb doesn't exist yet @@ -345,27 +340,15 @@ namespace data LogPrint (m_Floodfills.size (), " floodfills loaded"); } - void NetDb::SaveUpdated (const char * directory) + void NetDb::SaveUpdated () { - auto GetFilePath = [](const char * directory, const RouterInfo * routerInfo) + auto GetFilePath = [](const boost::filesystem::path& directory, const RouterInfo * routerInfo) { -#ifndef _WIN32 - return std::string (directory) + "/r" + - routerInfo->GetIdentHashBase64 ()[0] + "/routerInfo-" + -#else - return std::string (directory) + "\\r" + - routerInfo->GetIdentHashBase64 ()[0] + "\\routerInfo-" + -#endif - routerInfo->GetIdentHashBase64 () + ".dat"; + std::string s(routerInfo->GetIdentHashBase64()); + return directory / (std::string("r") + s[0]) / ("routerInfo-" + s + ".dat"); }; - boost::filesystem::path p (i2p::util::filesystem::GetDataDir()); - p /= (directory); -#if BOOST_VERSION > 10500 - const char * fullDirectory = p.string().c_str (); -#else - const char * fullDirectory = p.c_str (); -#endif + boost::filesystem::path fullDirectory (i2p::util::filesystem::GetDataDir() / m_NetDbPath); int count = 0, deletedCount = 0; auto total = m_RouterInfos.size (); uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); @@ -373,7 +356,8 @@ namespace data { if (it.second->IsUpdated ()) { - it.second->SaveToFile (GetFilePath(fullDirectory, it.second.get ())); + std::string f = GetFilePath(fullDirectory, it.second.get()).string(); + it.second->SaveToFile (f); it.second->SetUpdated (false); it.second->SetUnreachable (false); it.second->DeleteBuffer (); diff --git a/NetDb.h b/NetDb.h index 5028819e..adeee675 100644 --- a/NetDb.h +++ b/NetDb.h @@ -68,8 +68,8 @@ namespace data private: bool CreateNetDb(boost::filesystem::path directory); - void Load (const char * directory); - void SaveUpdated (const char * directory); + void Load (); + void SaveUpdated (); void Run (); // exploratory thread void Explore (int numDestinations); void Publish (); diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 7bc0161e..403b711c 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -447,10 +447,13 @@ namespace data if (m_Buffer) { std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); - f.write ((char *)m_Buffer, m_BufferLen); - } + if (f.is_open ()) + f.write ((char *)m_Buffer, m_BufferLen); + else + LogPrint(eLogError, "Can't save RouterInfo to ", fullPath); + } else - LogPrint (eLogError, "Can't save to file"); + LogPrint (eLogError, "Can't save RouterInfo m_Buffer==NULL"); } size_t RouterInfo::ReadString (char * str, std::istream& s) From 2738169a9d5c644c9cc63449ffdd55da5ca1fb68 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Mon, 15 Jun 2015 16:57:02 -0500 Subject: [PATCH 0496/6300] Use static for now while returning HTTP 500 error --- HTTPProxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index a575cc41..8e0ef9ad 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -90,7 +90,7 @@ namespace proxy //TODO: handle this apropriately void HTTPProxyHandler::HTTPRequestFailed(/*HTTPProxyHandler::errTypes error*/) { - std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n"; + static std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n"; boost::asio::async_write(*m_sock, boost::asio::buffer(response,response.size()), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } From 4fab07b4da3b06c6b5545f4561cc009659ec63fa Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 19 Jun 2015 16:06:14 -0400 Subject: [PATCH 0497/6300] fixed build error --- DaemonLinux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 5824430e..ac7a3402 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -62,7 +62,7 @@ namespace i2p LogPrint("Error, could not create process group."); return false; } - std::string d(i2p::util::filesystem::GetDataDir()); // make a copy + std::string d(i2p::util::filesystem::GetDataDir().string ()); // make a copy chdir(d.c_str()); // close stdin/stdout/stderr descriptors From 60e2722a21114182e04670c7c1227c3be88e4691 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sat, 20 Jun 2015 00:49:24 -0500 Subject: [PATCH 0498/6300] fixup! Fix UPnP for Win32 --- UPnP.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UPnP.cpp b/UPnP.cpp index 21f7e76d..6773b514 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -42,8 +42,8 @@ void (*freeUPNPDevlistFunc) (struct UPNPDev *); void (*FreeUPNPUrlsFunc) (struct UPNPUrls *); // Nice approach http://stackoverflow.com/a/21517513/673826 -template -F GetKnownProcAddressImpl(HMODULE hmod, const char *name, F) { +template +F GetKnownProcAddressImpl(M hmod, const char *name, F) { auto proc = reinterpret_cast(dlsym(hmod, name)); if (!proc) { LogPrint("Error resolving ", name, " from UPNP library. This often happens if there is version mismatch!"); From efe7e469cef95e77d6d239373f78cf72bd2d5b35 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sat, 20 Jun 2015 11:48:48 -0500 Subject: [PATCH 0499/6300] Missing libdl for UPnP --- build/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 254574fe..ed8a281f 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -70,6 +70,9 @@ set (DAEMON_SRC if (WITH_UPNP) add_definitions(-DUSE_UPNP) + if (NOT MSVC) + set(DL_LIB ${CMAKE_DL_LIBS}) + endif () endif () set (LIBRARY_SRC @@ -268,7 +271,7 @@ if (WITH_BINARY) if(${LAST_Boost_LIBRARIES} MATCHES ".*pthread.*") list(REMOVE_AT Boost_LIBRARIES -1) endif() - target_link_libraries( "${PROJECT_NAME}-bin" common ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) + target_link_libraries( "${PROJECT_NAME}-bin" common ${DL_LIB} ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) install(TARGETS "${PROJECT_NAME}-bin" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) if (MSVC) From 9c9401ce2f0e3bd8158c1879988805dc2a79b51a Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 21 Jun 2015 15:08:22 -0400 Subject: [PATCH 0500/6300] use shared_ptr for all incoming I2NP messages --- I2NPProtocol.cpp | 8 ++++---- I2NPProtocol.h | 2 +- NTCPSession.cpp | 12 +++--------- NTCPSession.h | 2 +- SSUData.cpp | 9 ++------- SSUData.h | 5 ++--- 6 files changed, 13 insertions(+), 25 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index e4685c7e..c0806d53 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -582,20 +582,20 @@ namespace i2p Flush (); } - void I2NPMessagesHandler::PutNextMessage (I2NPMessage * msg) + void I2NPMessagesHandler::PutNextMessage (std::shared_ptr msg) { if (msg) { switch (msg->GetTypeID ()) { case eI2NPTunnelData: - m_TunnelMsgs.push_back (ToSharedI2NPMessage (msg)); + m_TunnelMsgs.push_back (msg); break; case eI2NPTunnelGateway: - m_TunnelGatewayMsgs.push_back (ToSharedI2NPMessage (msg)); + m_TunnelGatewayMsgs.push_back (msg); break; default: - HandleI2NPMessage (ToSharedI2NPMessage (msg)); + HandleI2NPMessage (msg); } } } diff --git a/I2NPProtocol.h b/I2NPProtocol.h index cee97bec..4e8a8f8d 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -236,7 +236,7 @@ namespace tunnel public: ~I2NPMessagesHandler (); - void PutNextMessage (I2NPMessage * msg); + void PutNextMessage (std::shared_ptr msg); void Flush (); private: diff --git a/NTCPSession.cpp b/NTCPSession.cpp index b7596997..4b622c73 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -84,11 +84,7 @@ namespace transport transports.PeerDisconnected (shared_from_this ()); m_Server.RemoveNTCPSession (shared_from_this ()); m_SendQueue.clear (); - if (m_NextMessage) - { - i2p::DeleteI2NPMessage (m_NextMessage); - m_NextMessage = nullptr; - } + m_NextMessage = nullptr; m_TerminationTimer.cancel (); LogPrint (eLogInfo, "NTCP session terminated"); } @@ -564,7 +560,8 @@ namespace transport LogPrint (eLogError, "NTCP data size ", dataSize, " exceeds max size"); return false; } - m_NextMessage = dataSize <= I2NP_MAX_SHORT_MESSAGE_SIZE - 2 ? NewI2NPShortMessage () : NewI2NPMessage (); + auto msg = dataSize <= I2NP_MAX_SHORT_MESSAGE_SIZE - 2 ? NewI2NPShortMessage () : NewI2NPMessage (); + m_NextMessage = ToSharedI2NPMessage (msg); memcpy (m_NextMessage->buf, buf, 16); m_NextMessageOffset = 16; m_NextMessage->offset = 2; // size field @@ -589,10 +586,7 @@ namespace transport if (CryptoPP::Adler32().VerifyDigest (m_NextMessage->buf + m_NextMessageOffset - 4, m_NextMessage->buf, m_NextMessageOffset - 4)) m_Handler.PutNextMessage (m_NextMessage); else - { LogPrint (eLogWarning, "Incorrect adler checksum of NTCP message. Dropped"); - DeleteI2NPMessage (m_NextMessage); - } m_NextMessage = nullptr; } return true; diff --git a/NTCPSession.h b/NTCPSession.h index b09713e4..96644eea 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -126,7 +126,7 @@ namespace transport i2p::crypto::AESAlignedBuffer<16> m_TimeSyncBuffer; int m_ReceiveBufferOffset; - i2p::I2NPMessage * m_NextMessage; + std::shared_ptr m_NextMessage; size_t m_NextMessageOffset; i2p::I2NPMessagesHandler m_Handler; diff --git a/SSUData.cpp b/SSUData.cpp index 83b82aab..5c0c03dd 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -15,9 +15,8 @@ namespace transport if (msg->len + fragmentSize > msg->maxLen) { LogPrint (eLogInfo, "SSU I2NP message size ", msg->maxLen, " is not enough"); - I2NPMessage * newMsg = NewI2NPMessage (); + auto newMsg = ToSharedI2NPMessage(NewI2NPMessage ()); *newMsg = *msg; - DeleteI2NPMessage (msg); msg = newMsg; } memcpy (msg->buf + msg->len, fragment, fragmentSize); @@ -174,7 +173,7 @@ namespace transport if (it == m_IncompleteMessages.end ()) { // create new message - auto msg = NewI2NPShortMessage (); + auto msg = ToSharedI2NPMessage (NewI2NPShortMessage ()); msg->len -= I2NP_SHORT_HEADER_SIZE; it = m_IncompleteMessages.insert (std::make_pair (msgID, std::unique_ptr(new IncompleteMessage (msg)))).first; @@ -244,10 +243,7 @@ namespace transport m_Handler.PutNextMessage (msg); } else - { LogPrint (eLogWarning, "SSU message ", msgID, " already received"); - i2p::DeleteI2NPMessage (msg); - } } else { @@ -259,7 +255,6 @@ namespace transport } else LogPrint (eLogError, "SSU unexpected message ", (int)msg->GetTypeID ()); - DeleteI2NPMessage (msg); } } else diff --git a/SSUData.h b/SSUData.h index 39d14cc4..60d5057e 100644 --- a/SSUData.h +++ b/SSUData.h @@ -59,13 +59,12 @@ namespace transport struct IncompleteMessage { - I2NPMessage * msg; + std::shared_ptr msg; int nextFragmentNum; uint32_t lastFragmentInsertTime; // in seconds std::set, FragmentCmp> savedFragments; - IncompleteMessage (I2NPMessage * m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0) {}; - ~IncompleteMessage () { if (msg) DeleteI2NPMessage (msg); }; + IncompleteMessage (std::shared_ptr m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0) {}; void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); }; From 1fc50a59f54f7cb649376eb97230fc990a257e48 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 21 Jun 2015 17:05:01 -0400 Subject: [PATCH 0501/6300] different in and out buffers for tunnel encryption --- TransitTunnel.cpp | 2 +- Tunnel.cpp | 2 +- aes.cpp | 46 ++++++++++++++++++++++++---------------------- aes.h | 4 ++-- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index aa4636d6..6fc6c5c0 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -22,7 +22,7 @@ namespace tunnel void TransitTunnel::EncryptTunnelMsg (std::shared_ptr tunnelMsg) { - m_Encryption.Encrypt (tunnelMsg->GetPayload () + 4); + m_Encryption.Encrypt (tunnelMsg->GetPayload () + 4, tunnelMsg->GetPayload () + 4); } TransitTunnelParticipant::~TransitTunnelParticipant () diff --git a/Tunnel.cpp b/Tunnel.cpp index 5341da32..de95ad68 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -146,7 +146,7 @@ namespace tunnel TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) { - hop->decryption.Decrypt (payload); + hop->decryption.Decrypt (payload, payload); hop = hop->prev; } } diff --git a/aes.cpp b/aes.cpp index 8d270521..506e7ca8 100644 --- a/aes.cpp +++ b/aes.cpp @@ -280,74 +280,76 @@ namespace crypto #endif } - void TunnelEncryption::Encrypt (uint8_t * payload) + void TunnelEncryption::Encrypt (const uint8_t * in, uint8_t * out) { #ifdef AESNI __asm__ ( // encrypt IV - "movups (%[payload]), %%xmm0 \n" + "movups (%[in]), %%xmm0 \n" EncryptAES256(sched_iv) "movaps %%xmm0, %%xmm1 \n" // double IV encryption EncryptAES256(sched_iv) - "movups %%xmm0, (%[payload]) \n" + "movups %%xmm0, (%[out]) \n" // encrypt data, IV is xmm1 "1: \n" - "add $16, %[payload] \n" - "movups (%[payload]), %%xmm0 \n" + "add $16, %[in] \n" + "add $16, %[out] \n" + "movups (%[in]), %%xmm0 \n" "pxor %%xmm1, %%xmm0 \n" EncryptAES256(sched_l) "movaps %%xmm0, %%xmm1 \n" - "movups %%xmm0, (%[payload]) \n" + "movups %%xmm0, (%[out]) \n" "dec %[num] \n" "jnz 1b \n" : : [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.GetKeySchedule ()), - [payload]"r"(payload), [num]"r"(63) // 63 blocks = 1008 bytes + [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes : "%xmm0", "%xmm1", "cc", "memory" ); #else - m_IVEncryption.Encrypt ((ChipherBlock *)payload, (ChipherBlock *)payload); // iv - m_LayerEncryption.SetIV (payload); - m_LayerEncryption.Encrypt (payload + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, payload + 16); // data - m_IVEncryption.Encrypt ((ChipherBlock *)payload, (ChipherBlock *)payload); // double iv + m_IVEncryption.Encrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv + m_LayerEncryption.SetIV (out); + m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data + m_IVEncryption.Encrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv #endif } - void TunnelDecryption::Decrypt (uint8_t * payload) + void TunnelDecryption::Decrypt (const uint8_t * in, uint8_t * out) { #ifdef AESNI __asm__ ( // decrypt IV - "movups (%[payload]), %%xmm0 \n" + "movups (%[in]), %%xmm0 \n" DecryptAES256(sched_iv) "movaps %%xmm0, %%xmm1 \n" // double IV encryption DecryptAES256(sched_iv) - "movups %%xmm0, (%[payload]) \n" + "movups %%xmm0, (%[out]) \n" // decrypt data, IV is xmm1 "1: \n" - "add $16, %[payload] \n" - "movups (%[payload]), %%xmm0 \n" + "add $16, %[in] \n" + "add $16, %[out] \n" + "movups (%[in]), %%xmm0 \n" "movaps %%xmm0, %%xmm2 \n" DecryptAES256(sched_l) "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[payload]) \n" + "movups %%xmm0, (%[out]) \n" "movaps %%xmm2, %%xmm1 \n" "dec %[num] \n" "jnz 1b \n" : : [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.GetKeySchedule ()), - [payload]"r"(payload), [num]"r"(63) // 63 blocks = 1008 bytes + [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes : "%xmm0", "%xmm1", "%xmm2", "cc", "memory" ); #else - m_IVDecryption.Decrypt ((ChipherBlock *)payload, (ChipherBlock *)payload); // iv - m_LayerDecryption.SetIV (payload); - m_LayerDecryption.Decrypt (payload + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, payload + 16); // data - m_IVDecryption.Decrypt ((ChipherBlock *)payload, (ChipherBlock *)payload); // double iv + m_IVDecryption.Decrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv + m_LayerDecryption.SetIV (out); + m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data + m_IVDecryption.Decrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv #endif } } diff --git a/aes.h b/aes.h index 95180fd6..84ca1f17 100644 --- a/aes.h +++ b/aes.h @@ -185,7 +185,7 @@ namespace crypto m_IVEncryption.SetKey (ivKey); } - void Encrypt (uint8_t * payload); // 1024 bytes (16 IV + 1008 data) + void Encrypt (const uint8_t * in, uint8_t * out); // 1024 bytes (16 IV + 1008 data) private: @@ -207,7 +207,7 @@ namespace crypto m_IVDecryption.SetKey (ivKey); } - void Decrypt (uint8_t * payload); // 1024 bytes (16 IV + 1008 data) + void Decrypt (const uint8_t * in, uint8_t * out); // 1024 bytes (16 IV + 1008 data) private: From 2cbd6e85c664bc36532a921b009b5534acf7fc81 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 21 Jun 2015 22:29:50 -0400 Subject: [PATCH 0502/6300] use shared_ptr for garlic messages --- Datagram.cpp | 4 ++-- Destination.cpp | 10 +++++----- Garlic.cpp | 19 ++++++++----------- Garlic.h | 6 +++--- NetDb.cpp | 14 +++++++------- NetDbRequests.cpp | 2 +- Streaming.cpp | 8 ++++---- Streaming.h | 2 +- Tunnel.cpp | 6 +++--- Tunnel.h | 2 +- TunnelPool.cpp | 2 +- 11 files changed, 36 insertions(+), 39 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 9179fb80..32499232 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -60,12 +60,12 @@ namespace datagram { std::vector msgs; uint32_t i = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, leases.size () - 1); - auto garlic = m_Owner.WrapMessage (remote, msg, true); + auto garlic = m_Owner.WrapMessage (remote, ToSharedI2NPMessage (msg), true); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, leases[i].tunnelGateway, leases[i].tunnelID, - ToSharedI2NPMessage (garlic) + garlic }); outboundTunnel->SendTunnelDataMsg (msgs); } diff --git a/Destination.cpp b/Destination.cpp index 107bcf0a..d98de3f7 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -393,7 +393,7 @@ namespace client m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); m_PublishReplyToken = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); - auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken)); + auto msg = WrapMessage (floodfill, ToSharedI2NPMessage (i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken))); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&ClientDestination::HandlePublishConfirmationTimer, this, std::placeholders::_1)); @@ -581,15 +581,15 @@ namespace client rnd.GenerateBlock (replyTag, 32); // random session tag AddSessionKey (replyKey, replyTag); - I2NPMessage * msg = WrapMessage (nextFloodfill, - CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, - replyTunnel.get (), replyKey, replyTag)); + auto msg = WrapMessage (nextFloodfill, + ToSharedI2NPMessage (CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, + replyTunnel.get (), replyKey, replyTag))); outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeRouter, - nextFloodfill->GetIdentHash (), 0, ToSharedI2NPMessage (msg) + nextFloodfill->GetIdentHash (), 0, msg } }); request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT)); diff --git a/Garlic.cpp b/Garlic.cpp index 8bdfb10c..4b89b147 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -107,9 +107,9 @@ namespace garlic return !m_SessionTags.empty () || m_UnconfirmedTagsMsgs.empty (); } - I2NPMessage * GarlicRoutingSession::WrapSingleMessage (I2NPMessage * msg) + std::shared_ptr GarlicRoutingSession::WrapSingleMessage (std::shared_ptr msg) { - I2NPMessage * m = NewI2NPMessage (); + auto m = ToSharedI2NPMessage(NewI2NPMessage ()); m->Align (12); // in order to get buf aligned to 16 (12 + 4) size_t len = 0; uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length @@ -164,12 +164,10 @@ namespace garlic len += 32; } // AES block - len += CreateAESBlock (buf, msg); + len += CreateAESBlock (buf, msg.get ()); // TODO htobe32buf (m->GetPayload (), len); m->len += len + 4; - FillI2NPMessageHeader (m, eI2NPGarlic); - if (msg) - DeleteI2NPMessage (msg); + FillI2NPMessageHeader (m.get (), eI2NPGarlic); // TODO return m; } @@ -309,7 +307,7 @@ namespace garlic htobe32buf (buf + size, inboundTunnel->GetNextTunnelID ()); // tunnelID size += 4; // create msg - I2NPMessage * msg = CreateDeliveryStatusMsg (msgID); + auto msg = ToSharedI2NPMessage (CreateDeliveryStatusMsg (msgID)); if (m_Owner) { //encrypt @@ -322,7 +320,6 @@ namespace garlic } memcpy (buf + size, msg->GetBuffer (), msg->GetLength ()); size += msg->GetLength (); - DeleteI2NPMessage (msg); // fill clove uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 sec htobe32buf (buf + size, m_Rnd.GenerateWord32 ()); // CloveID @@ -512,7 +509,7 @@ namespace garlic if (tunnel) // we have send it through an outbound tunnel { I2NPMessage * msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from); - tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg); + tunnel->SendTunnelDataMsg (gwHash, gwTunnel, ToSharedI2NPMessage (msg)); } else LogPrint ("No outbound tunnels available for garlic clove"); @@ -537,8 +534,8 @@ namespace garlic } } - I2NPMessage * GarlicDestination::WrapMessage (std::shared_ptr destination, - I2NPMessage * msg, bool attachLeaseSet) + std::shared_ptr GarlicDestination::WrapMessage (std::shared_ptr destination, + std::shared_ptr msg, bool attachLeaseSet) { auto session = GetRoutingSession (destination, attachLeaseSet); // 32 tags by default return session->WrapSingleMessage (msg); diff --git a/Garlic.h b/Garlic.h index d5330735..580cabec 100644 --- a/Garlic.h +++ b/Garlic.h @@ -80,7 +80,7 @@ namespace garlic int numTags, bool attachLeaseSet); GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption ~GarlicRoutingSession (); - I2NPMessage * WrapSingleMessage (I2NPMessage * msg); + std::shared_ptr WrapSingleMessage (std::shared_ptr msg); void MessageConfirmed (uint32_t msgID); bool CleanupExpiredTags (); // returns true if something left @@ -126,8 +126,8 @@ namespace garlic std::shared_ptr GetRoutingSession (std::shared_ptr destination, bool attachLeaseSet); void CleanupRoutingSessions (); void RemoveCreatedSession (uint32_t msgID); - I2NPMessage * WrapMessage (std::shared_ptr destination, - I2NPMessage * msg, bool attachLeaseSet = false); + std::shared_ptr WrapMessage (std::shared_ptr destination, + std::shared_ptr msg, bool attachLeaseSet = false); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread diff --git a/NetDb.cpp b/NetDb.cpp index b802f3dd..90dcfc96 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -477,7 +477,7 @@ namespace data auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; if (outbound) - outbound->SendTunnelDataMsg (buf + offset, tunnelID, deliveryStatus); + outbound->SendTunnelDataMsg (buf + offset, tunnelID, ToSharedI2NPMessage (deliveryStatus)); else { LogPrint (eLogError, "No outbound tunnels for DatabaseStore reply found"); @@ -665,7 +665,7 @@ namespace data numExcluded = 0; // TODO: } - I2NPMessage * replyMsg = nullptr; + std::shared_ptr replyMsg; if (lookupType == DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP) { LogPrint ("Exploratory close to ", key, " ", numExcluded, " excluded"); @@ -685,7 +685,7 @@ namespace data excludedRouters.insert (r->GetIdentHash ()); } } - replyMsg = CreateDatabaseSearchReply (ident, routers); + replyMsg = ToSharedI2NPMessage (CreateDatabaseSearchReply (ident, routers)); } else { @@ -698,7 +698,7 @@ namespace data LogPrint ("Requested RouterInfo ", key, " found"); router->LoadBuffer (); if (router->GetBuffer ()) - replyMsg = CreateDatabaseStoreMsg (router); + replyMsg = ToSharedI2NPMessage (CreateDatabaseStoreMsg (router)); } } @@ -709,7 +709,7 @@ namespace data if (leaseSet) // we don't send back our LeaseSets { LogPrint ("Requested LeaseSet ", key, " found"); - replyMsg = CreateDatabaseStoreMsg (leaseSet); + replyMsg = ToSharedI2NPMessage (CreateDatabaseStoreMsg (leaseSet)); } } @@ -722,7 +722,7 @@ namespace data excludedRouters.insert (excluded); excluded += 32; } - replyMsg = CreateDatabaseSearchReply (ident, GetClosestFloodfills (ident, 3, excludedRouters)); + replyMsg = ToSharedI2NPMessage (CreateDatabaseSearchReply (ident, GetClosestFloodfills (ident, 3, excludedRouters))); } } @@ -747,7 +747,7 @@ namespace data if (outbound) outbound->SendTunnelDataMsg (buf+32, replyTunnelID, replyMsg); else - transports.SendMessage (buf+32, i2p::CreateTunnelGatewayMsg (replyTunnelID, ToSharedI2NPMessage(replyMsg))); + transports.SendMessage (buf+32, i2p::CreateTunnelGatewayMsg (replyTunnelID, replyMsg)); } else transports.SendMessage (buf+32, replyMsg); diff --git a/NetDbRequests.cpp b/NetDbRequests.cpp index 3509bcc7..670070fd 100644 --- a/NetDbRequests.cpp +++ b/NetDbRequests.cpp @@ -118,7 +118,7 @@ namespace data auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ()); if (nextFloodfill && outbound && inbound) outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0, - dest->CreateRequestMessage (nextFloodfill, inbound)); + ToSharedI2NPMessage (dest->CreateRequestMessage (nextFloodfill, inbound))); else { done = true; diff --git a/Streaming.cpp b/Streaming.cpp index e20efffb..bd6cb4bb 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -617,7 +617,7 @@ namespace stream { i2p::tunnel::eDeliveryTypeTunnel, m_CurrentRemoteLease.tunnelGateway, m_CurrentRemoteLease.tunnelID, - ToSharedI2NPMessage (msg) + msg }); m_NumSentBytes += it->GetLength (); } @@ -761,9 +761,9 @@ namespace stream m_CurrentRemoteLease.endDate = 0; } - I2NPMessage * Stream::CreateDataMessage (const uint8_t * payload, size_t len) + std::shared_ptr Stream::CreateDataMessage (const uint8_t * payload, size_t len) { - I2NPMessage * msg = NewI2NPShortMessage (); + auto msg = ToSharedI2NPMessage (NewI2NPShortMessage ()); CryptoPP::Gzip compressor; if (len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE) compressor.SetDeflateLevel (CryptoPP::Gzip::MIN_DEFLATE_LEVEL); @@ -780,7 +780,7 @@ namespace stream htobe16buf (buf + 6, m_Port); // destination port buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol msg->len += size + 4; - FillI2NPMessageHeader (msg, eI2NPData); + FillI2NPMessageHeader (msg.get (), eI2NPData); // TODO: return msg; } diff --git a/Streaming.h b/Streaming.h index b3cd4b30..7e2aaa71 100644 --- a/Streaming.h +++ b/Streaming.h @@ -156,7 +156,7 @@ namespace stream void HandleResendTimer (const boost::system::error_code& ecode); void HandleAckSendTimer (const boost::system::error_code& ecode); - I2NPMessage * CreateDataMessage (const uint8_t * payload, size_t len); + std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len); private: diff --git a/Tunnel.cpp b/Tunnel.cpp index de95ad68..8b0d6cfd 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -81,7 +81,7 @@ namespace tunnel // send message if (outboundTunnel) - outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); + outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, ToSharedI2NPMessage (msg)); else i2p::transport::transports.SendMessage (GetNextIdentHash (), ToSharedI2NPMessage (msg)); } @@ -164,7 +164,7 @@ namespace tunnel m_Endpoint.HandleDecryptedTunnelDataMsg (msg); } - void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, i2p::I2NPMessage * msg) + void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) { TunnelMessageBlock block; if (gwHash) @@ -180,7 +180,7 @@ namespace tunnel } else block.deliveryType = eDeliveryTypeLocal; - block.data = ToSharedI2NPMessage (msg); + block.data = msg; std::unique_lock l(m_SendMutex); m_Gateway.SendTunnelDataMsg (block); diff --git a/Tunnel.h b/Tunnel.h index f8c5e9cc..4ce44240 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -83,7 +83,7 @@ namespace tunnel OutboundTunnel (std::shared_ptr config): Tunnel (config), m_Gateway (this) {}; - void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, i2p::I2NPMessage * msg); + void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg); void SendTunnelDataMsg (const std::vector& msgs); // multiple messages std::shared_ptr GetEndpointRouter () const { return GetTunnelConfig ()->GetLastHop ()->router; }; diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 0f0eb709..689d31e8 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -254,7 +254,7 @@ namespace tunnel uint32_t msgID = rnd.GenerateWord32 (); m_Tests[msgID] = std::make_pair (*it1, *it2); (*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), - CreateDeliveryStatusMsg (msgID)); + ToSharedI2NPMessage (CreateDeliveryStatusMsg (msgID))); it1++; it2++; } } From ff12421d60a75c122fe76e2ef6a852246329549e Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 22 Jun 2015 15:47:45 -0400 Subject: [PATCH 0503/6300] shared_ptr for lookup messages --- NetDb.cpp | 4 ++-- NetDbRequests.cpp | 6 +++--- NetDbRequests.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 90dcfc96..8abb317e 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -590,7 +590,7 @@ namespace data msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeRouter, - nextFloodfill->GetIdentHash (), 0, ToSharedI2NPMessage (msg) + nextFloodfill->GetIdentHash (), 0, msg }); deleteDest = false; } @@ -794,7 +794,7 @@ namespace data { i2p::tunnel::eDeliveryTypeRouter, floodfill->GetIdentHash (), 0, - ToSharedI2NPMessage (dest->CreateRequestMessage (floodfill, inbound)) // explore + dest->CreateRequestMessage (floodfill, inbound) // explore }); } else diff --git a/NetDbRequests.cpp b/NetDbRequests.cpp index 670070fd..5f4a3e75 100644 --- a/NetDbRequests.cpp +++ b/NetDbRequests.cpp @@ -8,7 +8,7 @@ namespace i2p { namespace data { - I2NPMessage * RequestedDestination::CreateRequestMessage (std::shared_ptr router, + std::shared_ptr RequestedDestination::CreateRequestMessage (std::shared_ptr router, std::shared_ptr replyTunnel) { I2NPMessage * msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, @@ -16,7 +16,7 @@ namespace data &m_ExcludedPeers); m_ExcludedPeers.insert (router->GetIdentHash ()); m_CreationTime = i2p::util::GetSecondsSinceEpoch (); - return msg; + return ToSharedI2NPMessage (msg); } std::shared_ptr RequestedDestination::CreateRequestMessage (const IdentHash& floodfill) @@ -118,7 +118,7 @@ namespace data auto nextFloodfill = netdb.GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ()); if (nextFloodfill && outbound && inbound) outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0, - ToSharedI2NPMessage (dest->CreateRequestMessage (nextFloodfill, inbound))); + dest->CreateRequestMessage (nextFloodfill, inbound)); else { done = true; diff --git a/NetDbRequests.h b/NetDbRequests.h index 9dc49b26..0bab7080 100644 --- a/NetDbRequests.h +++ b/NetDbRequests.h @@ -28,7 +28,7 @@ namespace data bool IsExploratory () const { return m_IsExploratory; }; bool IsExcluded (const IdentHash& ident) const { return m_ExcludedPeers.count (ident); }; uint64_t GetCreationTime () const { return m_CreationTime; }; - I2NPMessage * CreateRequestMessage (std::shared_ptr, std::shared_ptr replyTunnel); + std::shared_ptr CreateRequestMessage (std::shared_ptr, std::shared_ptr replyTunnel); std::shared_ptr CreateRequestMessage (const IdentHash& floodfill); void SetRequestComplete (const RequestComplete& requestComplete) { m_RequestComplete = requestComplete; }; From a05a20440eb95f077ca1eff3dde89299df712f6e Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 24 Jun 2015 10:25:05 -0400 Subject: [PATCH 0504/6300] deleted deprecated SendMessage --- I2NPProtocol.cpp | 16 ++++++++-------- NetDb.cpp | 6 +++--- Transports.cpp | 13 ------------- Transports.h | 2 -- 4 files changed, 11 insertions(+), 26 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index c0806d53..57646b17 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -358,14 +358,14 @@ namespace i2p { // so we send it to reply tunnel transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + ToSharedI2NPMessage (CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), eI2NPVariableTunnelBuildReply, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)))); } else transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + ToSharedI2NPMessage (CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)))); } } } @@ -379,14 +379,14 @@ namespace i2p { // so we send it to reply tunnel transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + ToSharedI2NPMessage (CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), eI2NPTunnelBuildReply, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)))); } else transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPTunnelBuild, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + ToSharedI2NPMessage (CreateI2NPMessage (eI2NPTunnelBuild, buf, len, + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)))); } } diff --git a/NetDb.cpp b/NetDb.cpp index 8abb317e..36e637f1 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -471,7 +471,7 @@ namespace data uint32_t tunnelID = bufbe32toh (buf + offset); offset += 4; if (!tunnelID) // send response directly - transports.SendMessage (buf + offset, deliveryStatus); + transports.SendMessage (buf + offset, ToSharedI2NPMessage (deliveryStatus)); else { auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); @@ -503,7 +503,7 @@ namespace data memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + offset, len - offset); floodMsg->len += DATABASE_STORE_HEADER_SIZE + len -offset; FillI2NPMessageHeader (floodMsg, eI2NPDatabaseStore); - transports.SendMessage (floodfill->GetIdentHash (), floodMsg); + transports.SendMessage (floodfill->GetIdentHash (), ToSharedI2NPMessage (floodMsg)); } } } @@ -817,7 +817,7 @@ namespace data { uint32_t replyToken = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); LogPrint ("Publishing our RouterInfo to ", floodfill->GetIdentHashAbbreviation (), ". reply token=", replyToken); - transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken)); + transports.SendMessage (floodfill->GetIdentHash (), ToSharedI2NPMessage (CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken))); excluded.insert (floodfill->GetIdentHash ()); } } diff --git a/Transports.cpp b/Transports.cpp index 381d31e8..738db896 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -213,19 +213,6 @@ namespace transport return std::max (m_InBandwidth, m_OutBandwidth) > LOW_BANDWIDTH_LIMIT; } - void Transports::SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg) - { - SendMessage (ident, ToSharedI2NPMessage (msg)); - } - - void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector& msgs) - { - std::vector > msgs1; - for (auto it: msgs) - msgs1.push_back (ToSharedI2NPMessage (it)); - SendMessages (ident, msgs1); - } - void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) { m_Service.post (std::bind (&Transports::PostMessages, this, ident, std::vector > {msg })); diff --git a/Transports.h b/Transports.h index b29d7f1a..15ad980e 100644 --- a/Transports.h +++ b/Transports.h @@ -87,8 +87,6 @@ namespace transport i2p::transport::DHKeysPair * GetNextDHKeysPair (); void ReuseDHKeysPair (DHKeysPair * pair); - void SendMessage (const i2p::data::IdentHash& ident, i2p::I2NPMessage * msg); // deprecated - void SendMessages (const i2p::data::IdentHash& ident, const std::vector& msgs); // deprecated void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); void SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs); void CloseSession (std::shared_ptr router); From 206f094dd48e689adef7881518d78ee5a5b3d2bb Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 24 Jun 2015 10:45:58 -0400 Subject: [PATCH 0505/6300] use shared_ptr for DeliverStatus --- Garlic.cpp | 2 +- I2NPProtocol.cpp | 4 ++-- I2NPProtocol.h | 2 +- NetDb.cpp | 7 ++----- SSUSession.cpp | 2 +- TunnelPool.cpp | 2 +- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index 4b89b147..d6216461 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -307,7 +307,7 @@ namespace garlic htobe32buf (buf + size, inboundTunnel->GetNextTunnelID ()); // tunnelID size += 4; // create msg - auto msg = ToSharedI2NPMessage (CreateDeliveryStatusMsg (msgID)); + auto msg = CreateDeliveryStatusMsg (msgID); if (m_Owner) { //encrypt diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 57646b17..9057500b 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -90,7 +90,7 @@ namespace i2p return msg; } - I2NPMessage * CreateDeliveryStatusMsg (uint32_t msgID) + std::shared_ptr CreateDeliveryStatusMsg (uint32_t msgID) { I2NPMessage * m = NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); @@ -106,7 +106,7 @@ namespace i2p } m->len += DELIVERY_STATUS_SIZE; FillI2NPMessageHeader (m, eI2NPDeliveryStatus); - return m; + return ToSharedI2NPMessage (m); } I2NPMessage * CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 4e8a8f8d..4c299f5e 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -203,7 +203,7 @@ namespace tunnel I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID = 0); I2NPMessage * CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from = nullptr); - I2NPMessage * CreateDeliveryStatusMsg (uint32_t msgID); + std::shared_ptr CreateDeliveryStatusMsg (uint32_t msgID); I2NPMessage * CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, uint32_t replyTunnelID, bool exploratory = false, std::set * excludedPeers = nullptr); I2NPMessage * CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, diff --git a/NetDb.cpp b/NetDb.cpp index 36e637f1..06fd3c37 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -471,18 +471,15 @@ namespace data uint32_t tunnelID = bufbe32toh (buf + offset); offset += 4; if (!tunnelID) // send response directly - transports.SendMessage (buf + offset, ToSharedI2NPMessage (deliveryStatus)); + transports.SendMessage (buf + offset, deliveryStatus); else { auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; if (outbound) - outbound->SendTunnelDataMsg (buf + offset, tunnelID, ToSharedI2NPMessage (deliveryStatus)); + outbound->SendTunnelDataMsg (buf + offset, tunnelID, deliveryStatus); else - { LogPrint (eLogError, "No outbound tunnels for DatabaseStore reply found"); - DeleteI2NPMessage (deliveryStatus); - } } offset += 32; diff --git a/SSUSession.cpp b/SSUSession.cpp index 39ef9df0..c8c9fb2e 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -262,7 +262,7 @@ namespace transport if (paddingSize > 0) paddingSize = 16 - paddingSize; payload += paddingSize; // TODO: verify signature (need data from session request), payload points to signature - m_Data.Send (ToSharedI2NPMessage(CreateDeliveryStatusMsg (0))); + m_Data.Send (CreateDeliveryStatusMsg (0)); Established (); } diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 689d31e8..0f0eb709 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -254,7 +254,7 @@ namespace tunnel uint32_t msgID = rnd.GenerateWord32 (); m_Tests[msgID] = std::make_pair (*it1, *it2); (*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), - ToSharedI2NPMessage (CreateDeliveryStatusMsg (msgID))); + CreateDeliveryStatusMsg (msgID)); it1++; it2++; } } From 95c4a87cccc46d96dde5f5f16071c6787e1f5c4d Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 24 Jun 2015 14:19:10 -0500 Subject: [PATCH 0506/6300] Check for invalid SAM destination This closes #208 --- SAM.cpp | 25 +++++++++++++++---------- SAM.h | 1 + 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index cb2cac3e..7db1e1bb 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -343,19 +343,24 @@ namespace client if (m_Session) { i2p::data::IdentityEx dest; - dest.FromBase64 (destination); - context.GetAddressBook ().InsertAddress (dest); - auto leaseSet = m_Session->localDestination->FindLeaseSet (dest.GetIdentHash ()); - if (leaseSet) - Connect (leaseSet); - else + size_t len = dest.FromBase64(destination); + if (len > 0) { - m_Session->localDestination->RequestDestination (dest.GetIdentHash (), - std::bind (&SAMSocket::HandleConnectLeaseSetRequestComplete, - shared_from_this (), std::placeholders::_1)); + context.GetAddressBook().InsertAddress(dest); + auto leaseSet = m_Session->localDestination->FindLeaseSet(dest.GetIdentHash()); + if (leaseSet) + Connect(leaseSet); + else + { + m_Session->localDestination->RequestDestination(dest.GetIdentHash(), + std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, + shared_from_this(), std::placeholders::_1)); + } } + else + SendMessageReply(SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true); } - else + else SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } diff --git a/SAM.h b/SAM.h index d51f5aa3..7684d461 100644 --- a/SAM.h +++ b/SAM.h @@ -28,6 +28,7 @@ namespace client const char SAM_SESSION_CREATE_REPLY_OK[] = "SESSION STATUS RESULT=OK DESTINATION=%s\n"; const char SAM_SESSION_CREATE_DUPLICATED_ID[] = "SESSION STATUS RESULT=DUPLICATED_ID\n"; const char SAM_SESSION_CREATE_DUPLICATED_DEST[] = "SESSION STATUS RESULT=DUPLICATED_DEST\n"; + const char SAM_SESSION_STATUS_INVALID_KEY[] = "SESSION STATUS RESULT=INVALID_KEY\n"; const char SAM_STREAM_CONNECT[] = "STREAM CONNECT"; const char SAM_STREAM_STATUS_OK[] = "STREAM STATUS RESULT=OK\n"; const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n"; From d8cd2afd12699c6605e4dffed37127ae2f75c7bd Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 24 Jun 2015 22:19:56 -0400 Subject: [PATCH 0507/6300] different input anf output I2NP message for tunnel encryption --- I2NPProtocol.h | 1 + TransitTunnel.cpp | 8 ++++---- TransitTunnel.h | 2 +- Tunnel.cpp | 10 ++++++---- Tunnel.h | 2 +- TunnelBase.h | 2 +- TunnelGateway.cpp | 2 +- 7 files changed, 15 insertions(+), 12 deletions(-) diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 4c299f5e..be8e4fbf 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -138,6 +138,7 @@ namespace tunnel // payload uint8_t * GetPayload () { return GetBuffer () + I2NP_HEADER_SIZE; }; + const uint8_t * GetPayload () const { return GetBuffer () + I2NP_HEADER_SIZE; }; uint8_t * GetBuffer () { return buf + offset; }; const uint8_t * GetBuffer () const { return buf + offset; }; size_t GetLength () const { return len - offset; }; diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index 6fc6c5c0..6cc3491a 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -20,9 +20,9 @@ namespace tunnel m_Encryption.SetKeys (layerKey, ivKey); } - void TransitTunnel::EncryptTunnelMsg (std::shared_ptr tunnelMsg) + void TransitTunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { - m_Encryption.Encrypt (tunnelMsg->GetPayload () + 4, tunnelMsg->GetPayload () + 4); + m_Encryption.Encrypt (in->GetPayload () + 4, out->GetPayload () + 4); } TransitTunnelParticipant::~TransitTunnelParticipant () @@ -31,7 +31,7 @@ namespace tunnel void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { - EncryptTunnelMsg (tunnelMsg); + EncryptTunnelMsg (tunnelMsg, tunnelMsg); m_NumTransmittedBytes += tunnelMsg->GetLength (); htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); @@ -78,7 +78,7 @@ namespace tunnel void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { - EncryptTunnelMsg (tunnelMsg); + EncryptTunnelMsg (tunnelMsg, tunnelMsg); LogPrint (eLogDebug, "TransitTunnel endpoint for ", GetTunnelID ()); m_Endpoint.HandleDecryptedTunnelDataMsg (tunnelMsg); diff --git a/TransitTunnel.h b/TransitTunnel.h index e6e0601d..e63ce9dc 100644 --- a/TransitTunnel.h +++ b/TransitTunnel.h @@ -30,7 +30,7 @@ namespace tunnel // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); - void EncryptTunnelMsg (std::shared_ptr tunnelMsg); + void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); uint32_t GetNextTunnelID () const { return m_NextTunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; }; diff --git a/Tunnel.cpp b/Tunnel.cpp index 8b0d6cfd..cd3f69b8 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -140,14 +140,16 @@ namespace tunnel return established; } - void Tunnel::EncryptTunnelMsg (std::shared_ptr tunnelMsg) + void Tunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { - uint8_t * payload = tunnelMsg->GetPayload () + 4; + const uint8_t * inPayload = in->GetPayload () + 4; + uint8_t * outPayload = out->GetPayload () + 4; TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) { - hop->decryption.Decrypt (payload, payload); + hop->decryption.Decrypt (inPayload, outPayload); hop = hop->prev; + inPayload = outPayload; } } @@ -160,7 +162,7 @@ namespace tunnel { if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive msg->from = shared_from_this (); - EncryptTunnelMsg (msg); + EncryptTunnelMsg (msg, msg); m_Endpoint.HandleDecryptedTunnelDataMsg (msg); } diff --git a/Tunnel.h b/Tunnel.h index 4ce44240..b5cde16a 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -65,7 +65,7 @@ namespace tunnel // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); - void EncryptTunnelMsg (std::shared_ptr tunnelMsg); + void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); uint32_t GetNextTunnelID () const { return m_Config->GetFirstHop ()->tunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return m_Config->GetFirstHop ()->router->GetIdentHash (); }; diff --git a/TunnelBase.h b/TunnelBase.h index 5470f139..876d6d93 100644 --- a/TunnelBase.h +++ b/TunnelBase.h @@ -40,7 +40,7 @@ namespace tunnel virtual void HandleTunnelDataMsg (std::shared_ptr tunnelMsg) = 0; virtual void SendTunnelDataMsg (std::shared_ptr msg) = 0; virtual void FlushTunnelDataMsgs () {}; - virtual void EncryptTunnelMsg (std::shared_ptr tunnelMsg) = 0; + virtual void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) = 0; virtual uint32_t GetNextTunnelID () const = 0; virtual const i2p::data::IdentHash& GetNextIdentHash () const = 0; virtual uint32_t GetTunnelID () const = 0; // as known at our side diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index 2763ae17..cedbff10 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -188,7 +188,7 @@ namespace tunnel auto tunnelMsgs = m_Buffer.GetTunnelDataMsgs (); for (auto tunnelMsg : tunnelMsgs) { - m_Tunnel->EncryptTunnelMsg (tunnelMsg); + m_Tunnel->EncryptTunnelMsg (tunnelMsg, tunnelMsg); FillI2NPMessageHeader (tunnelMsg.get (), eI2NPTunnelData); // TODO: m_NumSentBytes += TUNNEL_DATA_MSG_SIZE; } From be1a4548e619592f33234c15605bde14c91261c6 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 25 Jun 2015 21:49:16 -0400 Subject: [PATCH 0508/6300] pass const I2NP message to HandleTunnelDataMsg --- I2NPProtocol.cpp | 7 +++++++ I2NPProtocol.h | 1 + TransitTunnel.cpp | 20 +++++++++++--------- TransitTunnel.h | 6 +++--- Tunnel.cpp | 13 +++++++------ Tunnel.h | 4 ++-- TunnelBase.h | 2 +- 7 files changed, 32 insertions(+), 21 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 9057500b..98c008d8 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -432,6 +432,13 @@ namespace i2p FillI2NPMessageHeader (msg, eI2NPTunnelData); return msg; } + + std::shared_ptr CreateEmptyTunnelDataMsg () + { + I2NPMessage * msg = NewI2NPShortMessage (); + msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; + return ToSharedI2NPMessage (msg); + } I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len) { diff --git a/I2NPProtocol.h b/I2NPProtocol.h index be8e4fbf..026801c5 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -222,6 +222,7 @@ namespace tunnel I2NPMessage * CreateTunnelDataMsg (const uint8_t * buf); I2NPMessage * CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload); + std::shared_ptr CreateEmptyTunnelDataMsg (); I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len); I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index 6cc3491a..7c58dabb 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -29,14 +29,15 @@ namespace tunnel { } - void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { - EncryptTunnelMsg (tunnelMsg, tunnelMsg); + auto newMsg = CreateEmptyTunnelDataMsg (); + EncryptTunnelMsg (tunnelMsg, newMsg); m_NumTransmittedBytes += tunnelMsg->GetLength (); - htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); - FillI2NPMessageHeader (tunnelMsg.get (), eI2NPTunnelData); // TODO - m_TunnelDataMsgs.push_back (tunnelMsg); + htobe32buf (newMsg->GetPayload (), GetNextTunnelID ()); + FillI2NPMessageHeader (newMsg.get (), eI2NPTunnelData); // TODO + m_TunnelDataMsgs.push_back (newMsg); } void TransitTunnelParticipant::FlushTunnelDataMsgs () @@ -56,7 +57,7 @@ namespace tunnel LogPrint (eLogError, "We are not a gateway for transit tunnel ", m_TunnelID); } - void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { LogPrint (eLogError, "Incoming tunnel message is not supported ", m_TunnelID); } @@ -76,12 +77,13 @@ namespace tunnel m_Gateway.SendBuffer (); } - void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { - EncryptTunnelMsg (tunnelMsg, tunnelMsg); + auto newMsg = CreateEmptyTunnelDataMsg (); + EncryptTunnelMsg (tunnelMsg, newMsg); LogPrint (eLogDebug, "TransitTunnel endpoint for ", GetTunnelID ()); - m_Endpoint.HandleDecryptedTunnelDataMsg (tunnelMsg); + m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } TransitTunnel * CreateTransitTunnel (uint32_t receiveTunnelID, diff --git a/TransitTunnel.h b/TransitTunnel.h index e63ce9dc..79bb99bb 100644 --- a/TransitTunnel.h +++ b/TransitTunnel.h @@ -29,7 +29,7 @@ namespace tunnel // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); uint32_t GetNextTunnelID () const { return m_NextTunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; }; @@ -54,7 +54,7 @@ namespace tunnel ~TransitTunnelParticipant (); size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; }; - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); void FlushTunnelDataMsgs (); private: @@ -93,7 +93,7 @@ namespace tunnel TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_Endpoint (false) {}; // transit endpoint is always outbound - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); } private: diff --git a/Tunnel.cpp b/Tunnel.cpp index cd3f69b8..74780b6c 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -158,12 +158,13 @@ namespace tunnel LogPrint (eLogInfo, "Can't send I2NP messages without delivery instructions"); } - void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) + void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) { - if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive - msg->from = shared_from_this (); - EncryptTunnelMsg (msg, msg); - m_Endpoint.HandleDecryptedTunnelDataMsg (msg); + if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive + auto newMsg = CreateEmptyTunnelDataMsg (); + EncryptTunnelMsg (msg, newMsg); + newMsg->from = shared_from_this (); + m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) @@ -196,7 +197,7 @@ namespace tunnel m_Gateway.SendBuffer (); } - void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { LogPrint (eLogError, "Incoming message for outbound tunnel ", GetTunnelID ()); } diff --git a/Tunnel.h b/Tunnel.h index b5cde16a..e75de74b 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -90,7 +90,7 @@ namespace tunnel size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; // implements TunnelBase - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); uint32_t GetTunnelID () const { return GetNextTunnelID (); }; private: @@ -104,7 +104,7 @@ namespace tunnel public: InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; - void HandleTunnelDataMsg (std::shared_ptr msg); + void HandleTunnelDataMsg (std::shared_ptr msg); size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; // implements TunnelBase diff --git a/TunnelBase.h b/TunnelBase.h index 876d6d93..76175d0a 100644 --- a/TunnelBase.h +++ b/TunnelBase.h @@ -37,7 +37,7 @@ namespace tunnel TunnelBase (): m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {}; virtual ~TunnelBase () {}; - virtual void HandleTunnelDataMsg (std::shared_ptr tunnelMsg) = 0; + virtual void HandleTunnelDataMsg (std::shared_ptr tunnelMsg) = 0; virtual void SendTunnelDataMsg (std::shared_ptr msg) = 0; virtual void FlushTunnelDataMsgs () {}; virtual void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) = 0; From bf4c33325cf7c447890c90a4c1361c8bd0a5bada Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 26 Jun 2015 16:06:59 -0400 Subject: [PATCH 0509/6300] random non-zero padding --- TunnelGateway.cpp | 16 ++++++++++++++-- TunnelGateway.h | 6 +++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index cedbff10..ffbb8fe3 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -10,6 +10,14 @@ namespace i2p { namespace tunnel { + TunnelGatewayBuffer::TunnelGatewayBuffer (uint32_t tunnelID): m_TunnelID (tunnelID), + m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0) + { + context.GetRandomNumberGenerator ().GenerateBlock (m_NonZeroRandomBuffer, TUNNEL_DATA_MAX_PAYLOAD_SIZE); + for (size_t i = 0; i < TUNNEL_DATA_MAX_PAYLOAD_SIZE; i++) + if (!m_NonZeroRandomBuffer[i]) m_NonZeroRandomBuffer[i] = 1; + } + TunnelGatewayBuffer::~TunnelGatewayBuffer () { } @@ -160,13 +168,17 @@ namespace tunnel payload[-1] = 0; // zero ptrdiff_t paddingSize = payload - buf - 25; // 25 = 24 + 1 if (paddingSize > 0) - memset (buf + 24, 1, paddingSize); // padding TODO: fill with random data + { + // non-zero padding + auto randomOffset = rnd.GenerateWord32 (0, TUNNEL_DATA_MAX_PAYLOAD_SIZE - paddingSize); + memcpy (buf + 24, m_NonZeroRandomBuffer + randomOffset, paddingSize); + } // we can't fill message header yet because encryption is required m_TunnelDataMsgs.push_back (m_CurrentTunnelDataMsg); m_CurrentTunnelDataMsg = nullptr; } - + void TunnelGateway::SendTunnelDataMsg (const TunnelMessageBlock& block) { if (block.data) diff --git a/TunnelGateway.h b/TunnelGateway.h index cfad17b5..ea88317b 100644 --- a/TunnelGateway.h +++ b/TunnelGateway.h @@ -14,8 +14,7 @@ namespace tunnel class TunnelGatewayBuffer { public: - TunnelGatewayBuffer (uint32_t tunnelID): m_TunnelID (tunnelID), - m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0) {}; + TunnelGatewayBuffer (uint32_t tunnelID); ~TunnelGatewayBuffer (); void PutI2NPMsg (const TunnelMessageBlock& block); const std::vector >& GetTunnelDataMsgs () const { return m_TunnelDataMsgs; }; @@ -32,13 +31,14 @@ namespace tunnel std::vector > m_TunnelDataMsgs; std::shared_ptr m_CurrentTunnelDataMsg; size_t m_RemainingSize; + uint8_t m_NonZeroRandomBuffer[TUNNEL_DATA_MAX_PAYLOAD_SIZE]; }; class TunnelGateway { public: - TunnelGateway (TunnelBase * tunnel): + TunnelGateway (TunnelBase * tunnel): m_Tunnel (tunnel), m_Buffer (tunnel->GetNextTunnelID ()), m_NumSentBytes (0) {}; void SendTunnelDataMsg (const TunnelMessageBlock& block); void PutTunnelDataMsg (const TunnelMessageBlock& block); From 047c6a93a3d949af56bed8e499e4ebe626505b7f Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 27 Jun 2015 22:02:00 -0400 Subject: [PATCH 0510/6300] don't copy transit DatabaseStore --- Tunnel.cpp | 6 +----- TunnelEndpoint.cpp | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 74780b6c..b773cbfc 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -440,13 +440,9 @@ namespace tunnel LogPrint (eLogDebug, "TunnelGateway of ", (int)len, " bytes for tunnel ", tunnel->GetTunnelID (), ". Msg type ", (int)typeID); if (typeID == eI2NPDatabaseStore || typeID == eI2NPDatabaseSearchReply) - { // transit DatabaseStore my contain new/updated RI // or DatabaseSearchReply with new routers - auto ds = NewI2NPMessage (); - *ds = *msg; // TODO: don't copy once msg is shared_ptr - i2p::data::netdb.PostI2NPMsg (ToSharedI2NPMessage (ds)); - } + i2p::data::netdb.PostI2NPMsg (msg); tunnel->SendTunnelDataMsg (msg); } diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index e493e168..a8206d3f 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -243,12 +243,8 @@ namespace tunnel { auto typeID = msg.data->GetTypeID (); if (typeID == eI2NPDatabaseStore || typeID == eI2NPDatabaseSearchReply ) - { // catch RI or reply with new list of routers - auto ds = NewI2NPShortMessage (); - *ds = *(msg.data); // TODO: don't copy once msg.data is shared_ptr - i2p::data::netdb.PostI2NPMsg (ToSharedI2NPMessage (ds)); - } + i2p::data::netdb.PostI2NPMsg (msg.data); i2p::transport::transports.SendMessage (msg.hash, msg.data); } else // we shouldn't send this message. possible leakage From adf12b6084196c49f2ee8620003b6492a58ff3a3 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 29 Jun 2015 21:40:43 -0400 Subject: [PATCH 0511/6300] handle DeliveryStatus garlic clove directly --- Destination.cpp | 6 +++++- Garlic.cpp | 4 ++-- I2NPProtocol.cpp | 4 ++-- I2NPProtocol.h | 2 +- RouterContext.cpp | 2 +- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index d98de3f7..e1c639c2 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -233,6 +233,10 @@ namespace client case eI2NPData: HandleDataMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET)); break; + case eI2NPDeliveryStatus: + // we assume tunnel tests non-encrypted + HandleDeliveryStatusMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); + break; case eI2NPDatabaseStore: HandleDatabaseStoreMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET)); break; @@ -240,7 +244,7 @@ namespace client HandleDatabaseSearchReplyMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET)); break; default: - i2p::HandleI2NPMessage (ToSharedI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from))); + i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); } } diff --git a/Garlic.cpp b/Garlic.cpp index d6216461..00a943e2 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -508,8 +508,8 @@ namespace garlic tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel (); if (tunnel) // we have send it through an outbound tunnel { - I2NPMessage * msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from); - tunnel->SendTunnelDataMsg (gwHash, gwTunnel, ToSharedI2NPMessage (msg)); + auto msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from); + tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg); } else LogPrint ("No outbound tunnels available for garlic clove"); diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 98c008d8..0c9d82da 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -76,7 +76,7 @@ namespace i2p return msg; } - I2NPMessage * CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from) + std::shared_ptr CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from) { I2NPMessage * msg = NewI2NPMessage (); if (msg->offset + len < msg->maxLen) @@ -87,7 +87,7 @@ namespace i2p } else LogPrint (eLogError, "I2NP message length ", len, " exceeds max length"); - return msg; + return ToSharedI2NPMessage(msg); } std::shared_ptr CreateDeliveryStatusMsg (uint32_t msgID) diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 026801c5..98e5f1c5 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -202,7 +202,7 @@ namespace tunnel void FillI2NPMessageHeader (I2NPMessage * msg, I2NPMessageType msgType, uint32_t replyMsgID = 0); void RenewI2NPMessageHeader (I2NPMessage * msg); I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID = 0); - I2NPMessage * CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from = nullptr); + std::shared_ptr CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from = nullptr); std::shared_ptr CreateDeliveryStatusMsg (uint32_t msgID); I2NPMessage * CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, diff --git a/RouterContext.cpp b/RouterContext.cpp index 29815b06..c9328b5c 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -296,7 +296,7 @@ namespace i2p void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { - i2p::HandleI2NPMessage (ToSharedI2NPMessage(CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from))); + i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); } void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) From 83e76c6b5326596b677a74e30dbf8bac6561d1c5 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Jul 2015 14:13:42 -0400 Subject: [PATCH 0512/6300] use shared flood message --- NetDb.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 06fd3c37..db163002 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -486,22 +486,19 @@ namespace data if (context.IsFloodfill ()) { // flood it + auto floodMsg = ToSharedI2NPMessage (NewI2NPShortMessage ()); + uint8_t * payload = floodMsg->GetPayload (); + memcpy (payload, buf, 33); // key + type + htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); // zero reply token + memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + offset, len - offset); + floodMsg->len += DATABASE_STORE_HEADER_SIZE + len -offset; + FillI2NPMessageHeader (floodMsg.get (), eI2NPDatabaseStore); // TODO std::set excluded; for (int i = 0; i < 3; i++) { auto floodfill = GetClosestFloodfill (ident, excluded); if (floodfill) - { - excluded.insert (floodfill->GetIdentHash ()); - auto floodMsg = NewI2NPShortMessage (); - uint8_t * payload = floodMsg->GetPayload (); - memcpy (payload, buf, 33); // key + type - htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); // zero reply token - memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + offset, len - offset); - floodMsg->len += DATABASE_STORE_HEADER_SIZE + len -offset; - FillI2NPMessageHeader (floodMsg, eI2NPDatabaseStore); - transports.SendMessage (floodfill->GetIdentHash (), ToSharedI2NPMessage (floodMsg)); - } + transports.SendMessage (floodfill->GetIdentHash (), floodMsg); } } } From fbebdd3055f0b3395abf66c3c469af5defa1d92a Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Jul 2015 17:20:41 -0400 Subject: [PATCH 0513/6300] fixed race condition --- Streaming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index bd6cb4bb..41d39a63 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -22,7 +22,7 @@ namespace stream { m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); m_RemoteIdentity = remote->GetIdentity (); - UpdateCurrentRemoteLease (); + m_CurrentRemoteLease.endDate = 0; } Stream::Stream (boost::asio::io_service& service, StreamingDestination& local): From 654357f5cee727408c2afb0768d28ac3267d3b2b Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 2 Jul 2015 13:43:03 -0400 Subject: [PATCH 0514/6300] copy shared_ptr --- Transports.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Transports.cpp b/Transports.cpp index 738db896..da07dc1d 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -215,7 +215,7 @@ namespace transport void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) { - m_Service.post (std::bind (&Transports::PostMessages, this, ident, std::vector > {msg })); + SendMessages (ident, std::vector > {msg }); } void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs) From 17acdcc4d547d772fa0b76f7aeb89f7b20d0eff7 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 2 Jul 2015 14:11:30 -0400 Subject: [PATCH 0515/6300] temporary fix of crash --- TunnelEndpoint.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index a8206d3f..9d1425e1 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -241,10 +241,10 @@ namespace tunnel // to somebody else if (!m_IsInbound) // outbound transit tunnel { - auto typeID = msg.data->GetTypeID (); + /* auto typeID = msg.data->GetTypeID (); if (typeID == eI2NPDatabaseStore || typeID == eI2NPDatabaseSearchReply ) // catch RI or reply with new list of routers - i2p::data::netdb.PostI2NPMsg (msg.data); + i2p::data::netdb.PostI2NPMsg (msg.data);*/ i2p::transport::transports.SendMessage (msg.hash, msg.data); } else // we shouldn't send this message. possible leakage From 0c8fb376db133e95b1207ae49c65dc6cb4bda3e2 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 3 Jul 2015 10:11:55 -0400 Subject: [PATCH 0516/6300] some cleanup --- NTCPSession.cpp | 18 +----------------- NTCPSession.h | 2 -- SSUSession.cpp | 11 ----------- SSUSession.h | 2 -- TransportSession.h | 1 - 5 files changed, 1 insertion(+), 33 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 4b622c73..dfd69be9 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -101,7 +101,7 @@ namespace transport m_DHKeysPair = nullptr; SendTimeSyncMessage (); - PostI2NPMessage (ToSharedI2NPMessage(CreateDatabaseStoreMsg ())); // we tell immediately who we are + Send (ToSharedI2NPMessage(CreateDatabaseStoreMsg ())); // we tell immediately who we are transports.PeerConnected (shared_from_this ()); } @@ -673,22 +673,6 @@ namespace transport Send (nullptr); } - void NTCPSession::SendI2NPMessage (std::shared_ptr msg) - { - m_Server.GetService ().post (std::bind (&NTCPSession::PostI2NPMessage, shared_from_this (), msg)); - } - - void NTCPSession::PostI2NPMessage (std::shared_ptr msg) - { - if (msg) - { - if (m_IsTerminated) return; - if (m_IsSending) - m_SendQueue.push_back (msg); - else - Send (msg); - } - } void NTCPSession::SendI2NPMessages (const std::vector >& msgs) { diff --git a/NTCPSession.h b/NTCPSession.h index 96644eea..2921e923 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -61,12 +61,10 @@ namespace transport void ClientLogin (); void ServerLogin (); - void SendI2NPMessage (std::shared_ptr msg); void SendI2NPMessages (const std::vector >& msgs); private: - void PostI2NPMessage (std::shared_ptr msg); void PostI2NPMessages (std::vector > msgs); void Connected (); void SendTimeSyncMessage (); diff --git a/SSUSession.cpp b/SSUSession.cpp index c8c9fb2e..3900e7de 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -832,17 +832,6 @@ namespace transport } } - void SSUSession::SendI2NPMessage (std::shared_ptr msg) - { - GetService ().post (std::bind (&SSUSession::PostI2NPMessage, shared_from_this (), msg)); - } - - void SSUSession::PostI2NPMessage (std::shared_ptr msg) - { - if (msg &&m_State == eSessionStateEstablished) - m_Data.Send (msg); - } - void SSUSession::SendI2NPMessages (const std::vector >& msgs) { GetService ().post (std::bind (&SSUSession::PostI2NPMessages, shared_from_this (), msgs)); diff --git a/SSUSession.h b/SSUSession.h index fd1ba39e..6c222185 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -76,7 +76,6 @@ namespace transport void Done (); boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; - void SendI2NPMessage (std::shared_ptr msg); void SendI2NPMessages (const std::vector >& msgs); void SendPeerTest (); // Alice @@ -95,7 +94,6 @@ namespace transport boost::asio::io_service& GetService (); void CreateAESandMacKey (const uint8_t * pubKey); - void PostI2NPMessage (std::shared_ptr msg); void PostI2NPMessages (std::vector > msgs); void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session void ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); diff --git a/TransportSession.h b/TransportSession.h index dcd9f66c..f8da2b4d 100644 --- a/TransportSession.h +++ b/TransportSession.h @@ -71,7 +71,6 @@ namespace transport size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; - virtual void SendI2NPMessage (std::shared_ptr msg) = 0; virtual void SendI2NPMessages (const std::vector >& msgs) = 0; protected: From bf14b7da9a92e96622ffda33ce5973863fe907ce Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 3 Jul 2015 11:11:07 -0400 Subject: [PATCH 0517/6300] move FillI2NPMessageHeader into I2NPMessage --- Datagram.cpp | 2 +- Garlic.cpp | 2 +- I2NPProtocol.cpp | 49 ++++++++++++++++++++++------------------------- I2NPProtocol.h | 5 +++-- NetDb.cpp | 2 +- Streaming.cpp | 2 +- TransitTunnel.cpp | 2 +- Tunnel.cpp | 2 +- TunnelGateway.cpp | 2 +- 9 files changed, 33 insertions(+), 35 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 32499232..63b487ff 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -143,7 +143,7 @@ namespace datagram htobe16buf (buf + 6, toPort); // destination port buf[9] = i2p::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol msg->len += size + 4; - FillI2NPMessageHeader (msg, eI2NPData); + msg->FillI2NPMessageHeader (eI2NPData); return msg; } } diff --git a/Garlic.cpp b/Garlic.cpp index 00a943e2..fb67b0ae 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -167,7 +167,7 @@ namespace garlic len += CreateAESBlock (buf, msg.get ()); // TODO htobe32buf (m->GetPayload (), len); m->len += len + 4; - FillI2NPMessageHeader (m.get (), eI2NPGarlic); // TODO + m->FillI2NPMessageHeader (eI2NPGarlic); return m; } diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 0c9d82da..f98bdc1c 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -41,25 +41,22 @@ namespace i2p return std::shared_ptr(msg, DeleteI2NPMessage); } - void FillI2NPMessageHeader (I2NPMessage * msg, I2NPMessageType msgType, uint32_t replyMsgID) + void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID) { - msg->SetTypeID (msgType); + SetTypeID (msgType); if (replyMsgID) // for tunnel creation - msg->SetMsgID (replyMsgID); + SetMsgID (replyMsgID); else - msg->SetMsgID (i2p::context.GetRandomNumberGenerator ().GenerateWord32 ()); - msg->SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); // TODO: 5 secs is a magic number - msg->UpdateSize (); - msg->UpdateChks (); + SetMsgID (i2p::context.GetRandomNumberGenerator ().GenerateWord32 ()); + SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); // TODO: 5 secs is a magic number + UpdateSize (); + UpdateChks (); } - void RenewI2NPMessageHeader (I2NPMessage * msg) + void I2NPMessage::RenewI2NPMessageHeader () { - if (msg) - { - msg->SetMsgID (i2p::context.GetRandomNumberGenerator ().GenerateWord32 ()); - msg->SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); - } + SetMsgID (i2p::context.GetRandomNumberGenerator ().GenerateWord32 ()); + SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); } I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID) @@ -72,7 +69,7 @@ namespace i2p } else LogPrint (eLogError, "I2NP message length ", len, " exceeds max length"); - FillI2NPMessageHeader (msg, msgType, replyMsgID); + msg->FillI2NPMessageHeader (msgType, replyMsgID); return msg; } @@ -105,7 +102,7 @@ namespace i2p htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, 2); // netID = 2 } m->len += DELIVERY_STATUS_SIZE; - FillI2NPMessageHeader (m, eI2NPDeliveryStatus); + m->FillI2NPMessageHeader (eI2NPDeliveryStatus); return ToSharedI2NPMessage (m); } @@ -150,7 +147,7 @@ namespace i2p } m->len += (buf - m->GetPayload ()); - FillI2NPMessageHeader (m, eI2NPDatabaseLookup); + m->FillI2NPMessageHeader (eI2NPDatabaseLookup); return m; } @@ -187,7 +184,7 @@ namespace i2p buf += 65; m->len += (buf - m->GetPayload ()); - FillI2NPMessageHeader (m, eI2NPDatabaseLookup); + m->FillI2NPMessageHeader (eI2NPDatabaseLookup); return m; } @@ -211,7 +208,7 @@ namespace i2p memcpy (buf + len, i2p::context.GetRouterInfo ().GetIdentHash (), 32); len += 32; m->len += len; - FillI2NPMessageHeader (m, eI2NPDatabaseSearchReply); + m->FillI2NPMessageHeader (eI2NPDatabaseSearchReply); return m; } @@ -245,7 +242,7 @@ namespace i2p compressor.Get (buf, size); buf += size; m->len += (buf - payload); // payload size - FillI2NPMessageHeader (m, eI2NPDatabaseStore); + m->FillI2NPMessageHeader (eI2NPDatabaseStore); return m; } @@ -275,7 +272,7 @@ namespace i2p memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ()); size += leaseSet->GetBufferLen (); m->len += size; - FillI2NPMessageHeader (m, eI2NPDatabaseStore); + m->FillI2NPMessageHeader (eI2NPDatabaseStore); return m; } @@ -419,7 +416,7 @@ namespace i2p I2NPMessage * msg = NewI2NPShortMessage (); memcpy (msg->GetPayload (), buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE); msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; - FillI2NPMessageHeader (msg, eI2NPTunnelData); + msg->FillI2NPMessageHeader (eI2NPTunnelData); return msg; } @@ -429,7 +426,7 @@ namespace i2p memcpy (msg->GetPayload () + 4, payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4); htobe32buf (msg->GetPayload (), tunnelID); msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; - FillI2NPMessageHeader (msg, eI2NPTunnelData); + msg->FillI2NPMessageHeader (eI2NPTunnelData); return msg; } @@ -448,7 +445,7 @@ namespace i2p htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len); memcpy (payload + TUNNEL_GATEWAY_HEADER_SIZE, buf, len); msg->len += TUNNEL_GATEWAY_HEADER_SIZE + len; - FillI2NPMessageHeader (msg, eI2NPTunnelGateway); + msg->FillI2NPMessageHeader (eI2NPTunnelGateway); return msg; } @@ -463,7 +460,7 @@ namespace i2p htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len); msg->offset -= (I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE); msg->len = msg->offset + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE +len; - FillI2NPMessageHeader (msg.get(), eI2NPTunnelGateway); // TODO + msg->FillI2NPMessageHeader (eI2NPTunnelGateway); return msg; } else @@ -482,13 +479,13 @@ namespace i2p msg->len += gatewayMsgOffset; memcpy (msg->GetPayload (), buf, len); msg->len += len; - FillI2NPMessageHeader (msg, msgType, replyMsgID); // create content message + msg->FillI2NPMessageHeader (msgType, replyMsgID); // create content message len = msg->GetLength (); msg->offset -= gatewayMsgOffset; uint8_t * payload = msg->GetPayload (); htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID); htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len); - FillI2NPMessageHeader (msg, eI2NPTunnelGateway); // gateway message + msg->FillI2NPMessageHeader (eI2NPTunnelGateway); // gateway message return msg; } diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 98e5f1c5..0e6f5621 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -184,6 +184,9 @@ namespace tunnel len = offset + I2NP_SHORT_HEADER_SIZE + bufbe16toh (header + I2NP_HEADER_SIZE_OFFSET); return bufbe32toh (header + I2NP_HEADER_MSGID_OFFSET); } + + void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0); + void RenewI2NPMessageHeader (); }; template @@ -199,8 +202,6 @@ namespace tunnel void DeleteI2NPMessage (I2NPMessage * msg); std::shared_ptr ToSharedI2NPMessage (I2NPMessage * msg); - void FillI2NPMessageHeader (I2NPMessage * msg, I2NPMessageType msgType, uint32_t replyMsgID = 0); - void RenewI2NPMessageHeader (I2NPMessage * msg); I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID = 0); std::shared_ptr CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from = nullptr); diff --git a/NetDb.cpp b/NetDb.cpp index db163002..dad122a2 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -492,7 +492,7 @@ namespace data htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); // zero reply token memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + offset, len - offset); floodMsg->len += DATABASE_STORE_HEADER_SIZE + len -offset; - FillI2NPMessageHeader (floodMsg.get (), eI2NPDatabaseStore); // TODO + floodMsg->FillI2NPMessageHeader (eI2NPDatabaseStore); std::set excluded; for (int i = 0; i < 3; i++) { diff --git a/Streaming.cpp b/Streaming.cpp index 41d39a63..785e2987 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -780,7 +780,7 @@ namespace stream htobe16buf (buf + 6, m_Port); // destination port buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol msg->len += size + 4; - FillI2NPMessageHeader (msg.get (), eI2NPData); // TODO: + msg->FillI2NPMessageHeader (eI2NPData); return msg; } diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index 7c58dabb..ad6aa0b3 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -36,7 +36,7 @@ namespace tunnel m_NumTransmittedBytes += tunnelMsg->GetLength (); htobe32buf (newMsg->GetPayload (), GetNextTunnelID ()); - FillI2NPMessageHeader (newMsg.get (), eI2NPTunnelData); // TODO + newMsg->FillI2NPMessageHeader (eI2NPTunnelData); m_TunnelDataMsgs.push_back (newMsg); } diff --git a/Tunnel.cpp b/Tunnel.cpp index b773cbfc..0c5600e7 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -77,7 +77,7 @@ namespace tunnel } hop = hop->prev; } - FillI2NPMessageHeader (msg, eI2NPVariableTunnelBuild); + msg->FillI2NPMessageHeader (eI2NPVariableTunnelBuild); // send message if (outboundTunnel) diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index ffbb8fe3..b05a342c 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -201,7 +201,7 @@ namespace tunnel for (auto tunnelMsg : tunnelMsgs) { m_Tunnel->EncryptTunnelMsg (tunnelMsg, tunnelMsg); - FillI2NPMessageHeader (tunnelMsg.get (), eI2NPTunnelData); // TODO: + tunnelMsg->FillI2NPMessageHeader (eI2NPTunnelData); m_NumSentBytes += TUNNEL_DATA_MSG_SIZE; } i2p::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), tunnelMsgs); From c5644e0e32d799d59c3de6df1ecbf18598c870ca Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 3 Jul 2015 21:27:40 -0400 Subject: [PATCH 0518/6300] const I2NP messages --- NetDb.cpp | 20 ++++++++++---------- NetDb.h | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index dad122a2..39dd6430 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -453,7 +453,7 @@ namespace data } } - void NetDb::HandleDatabaseStoreMsg (std::shared_ptr m) + void NetDb::HandleDatabaseStoreMsg (std::shared_ptr m) { const uint8_t * buf = m->GetPayload (); size_t len = m->GetSize (); @@ -540,9 +540,9 @@ namespace data } } - void NetDb::HandleDatabaseSearchReplyMsg (std::shared_ptr msg) + void NetDb::HandleDatabaseSearchReplyMsg (std::shared_ptr msg) { - uint8_t * buf = msg->GetPayload (); + const uint8_t * buf = msg->GetPayload (); char key[48]; int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48); key[l] = 0; @@ -611,7 +611,7 @@ namespace data // try responses for (int i = 0; i < num; i++) { - uint8_t * router = buf + 33 + i*32; + const uint8_t * router = buf + 33 + i*32; char peerHash[48]; int l1 = i2p::data::ByteStreamToBase64 (router, 32, peerHash, 48); peerHash[l1] = 0; @@ -629,9 +629,9 @@ namespace data } } - void NetDb::HandleDatabaseLookupMsg (std::shared_ptr msg) + void NetDb::HandleDatabaseLookupMsg (std::shared_ptr msg) { - uint8_t * buf = msg->GetPayload (); + const uint8_t * buf = msg->GetPayload (); IdentHash ident (buf); if (ident.IsZero ()) { @@ -644,7 +644,7 @@ namespace data uint8_t flag = buf[64]; LogPrint ("DatabaseLookup for ", key, " recieved flags=", (int)flag); uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK; - uint8_t * excluded = buf + 65; + const uint8_t * excluded = buf + 65; uint32_t replyTunnelID = 0; if (flag & DATABASE_LOOKUP_DELIVERY_FLAG) //reply to tunnel { @@ -727,11 +727,11 @@ namespace data // encryption might be used though tunnel only if (flag & DATABASE_LOOKUP_ENCYPTION_FLAG) // encrypted reply requested { - uint8_t * sessionKey = excluded; + const uint8_t * sessionKey = excluded; uint8_t numTags = sessionKey[32]; if (numTags > 0) { - uint8_t * sessionTag = sessionKey + 33; // take first tag + const uint8_t * sessionTag = sessionKey + 33; // take first tag i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag); replyMsg = garlic.WrapSingleMessage (replyMsg); } @@ -890,7 +890,7 @@ namespace data return nullptr; // seems we have too few routers } - void NetDb::PostI2NPMsg (std::shared_ptr msg) + void NetDb::PostI2NPMsg (std::shared_ptr msg) { if (msg) m_Queue.Put (msg); } diff --git a/NetDb.h b/NetDb.h index adeee675..71db2e52 100644 --- a/NetDb.h +++ b/NetDb.h @@ -41,9 +41,9 @@ namespace data void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr); - void HandleDatabaseStoreMsg (std::shared_ptr msg); - void HandleDatabaseSearchReplyMsg (std::shared_ptr msg); - void HandleDatabaseLookupMsg (std::shared_ptr msg); + void HandleDatabaseStoreMsg (std::shared_ptr msg); + void HandleDatabaseSearchReplyMsg (std::shared_ptr msg); + void HandleDatabaseLookupMsg (std::shared_ptr msg); std::shared_ptr GetRandomRouter () const; std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith) const; @@ -56,7 +56,7 @@ namespace data std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; void SetUnreachable (const IdentHash& ident, bool unreachable); - void PostI2NPMsg (std::shared_ptr msg); + void PostI2NPMsg (std::shared_ptr msg); void Reseed (); @@ -89,7 +89,7 @@ namespace data bool m_IsRunning; std::thread * m_Thread; - i2p::util::Queue > m_Queue; // of I2NPDatabaseStoreMsg + i2p::util::Queue > m_Queue; // of I2NPDatabaseStoreMsg Reseeder * m_Reseeder; From e03f1597a00a89df6efd59103187f3d5cf4b35fe Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 3 Jul 2015 21:50:26 -0400 Subject: [PATCH 0519/6300] don't send DatabaseStore until time sync complete --- NTCPSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index dfd69be9..86418749 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -101,7 +101,7 @@ namespace transport m_DHKeysPair = nullptr; SendTimeSyncMessage (); - Send (ToSharedI2NPMessage(CreateDatabaseStoreMsg ())); // we tell immediately who we are + m_SendQueue.push_back (ToSharedI2NPMessage(CreateDatabaseStoreMsg ())); // we tell immediately who we are transports.PeerConnected (shared_from_this ()); } From 3405ffd8d8fa6aa8cc42517ce6148d535a21d248 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 5 Jul 2015 07:59:38 -0400 Subject: [PATCH 0520/6300] check for buffer size --- I2NPProtocol.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index f98bdc1c..8fd493f1 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -109,7 +109,7 @@ namespace i2p I2NPMessage * CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, uint32_t replyTunnelID, bool exploratory, std::set * excludedPeers) { - I2NPMessage * m = NewI2NPShortMessage (); + I2NPMessage * m = excludedPeers ? NewI2NPMessage () : NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); memcpy (buf, key, 32); // key buf += 32; @@ -155,7 +155,8 @@ namespace i2p const std::set& excludedFloodfills, const i2p::tunnel::InboundTunnel * replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag) { - I2NPMessage * m = NewI2NPShortMessage (); + int cnt = excludedFloodfills.size (); + I2NPMessage * m = cnt > 0 ? NewI2NPMessage () : NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); memcpy (buf, dest, 32); // key buf += 32; @@ -166,7 +167,6 @@ namespace i2p buf += 5; // excluded - int cnt = excludedFloodfills.size (); htobe16buf (buf, cnt); buf += 2; if (cnt > 0) @@ -186,9 +186,7 @@ namespace i2p m->len += (buf - m->GetPayload ()); m->FillI2NPMessageHeader (eI2NPDatabaseLookup); return m; - } - - + } I2NPMessage * CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers) @@ -238,10 +236,18 @@ namespace i2p auto size = compressor.MaxRetrievable (); htobe16buf (buf, size); // size buf += 2; - // TODO: check if size doesn't exceed buffer - compressor.Get (buf, size); - buf += size; m->len += (buf - payload); // payload size + if (m->len + size > m->maxLen) + { + LogPrint (eLogInfo, "DatabaseStore message size is not enough for ", m->len + size); + auto newMsg = NewI2NPMessage (); + *newMsg = *m; + DeleteI2NPMessage (m); + m = newMsg; + buf = m->buf + m->len; + } + compressor.Get (buf, size); + m->len += size; m->FillI2NPMessageHeader (eI2NPDatabaseStore); return m; From 73d402525636276ac78aa92ea88535911e21928c Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 6 Jul 2015 12:11:17 -0400 Subject: [PATCH 0521/6300] version 0.10.0 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index d5406b0e..bdff9c2e 100644 --- a/version.h +++ b/version.h @@ -2,7 +2,7 @@ #define _VERSION_H_ #define CODENAME "Purple" -#define VERSION "0.9.0" +#define VERSION "0.10.0" #define I2P_VERSION "0.9.20" #endif From 62cf83921bb009c2a86c4862b5722304712de4b2 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Nov 2015 09:15:49 -0500 Subject: [PATCH 0522/6300] cumulative update from bitbucket --- AddressBook.cpp | 52 +- AddressBook.h | 13 +- BOB.cpp | 10 +- base64.cpp => Base.cpp | 68 +- Base.h | 123 +++ ClientContext.cpp | 34 +- ClientContext.h | 2 - aes.cpp => Crypto.cpp | 278 ++++++- aes.h => Crypto.h | 131 ++- Daemon.cpp | 53 +- Datagram.cpp | 79 +- Datagram.h | 10 +- Destination.cpp | 36 +- Destination.h | 5 +- ElGamal.h | 87 -- Garlic.cpp | 57 +- Garlic.h | 13 +- HTTPServer.cpp | 38 +- HTTPServer.h | 1 + I2NPProtocol.cpp | 71 +- I2NPProtocol.h | 14 +- I2PService.h | 4 +- I2PTunnel.cpp | 8 +- Identity.cpp | 134 ++- Identity.h | 113 +-- LeaseSet.cpp | 25 +- LeaseSet.h | 8 +- Makefile | 20 +- Makefile.bsd | 2 +- Makefile.linux | 6 +- NTCPSession.cpp | 90 +- NTCPSession.h | 4 +- NetDb.cpp | 75 +- NetDb.h | 3 + NetDbRequests.cpp | 8 +- Profiling.cpp | 3 +- Reseed.cpp | 774 +++--------------- Reseed.h | 52 +- RouterContext.cpp | 48 +- RouterContext.h | 10 +- RouterInfo.cpp | 83 +- RouterInfo.h | 28 +- SAM.cpp | 24 +- SAM.h | 2 +- SSU.cpp | 25 +- SSU.h | 2 +- SSUData.cpp | 18 +- SSUData.h | 4 +- SSUSession.cpp | 170 ++-- SSUSession.h | 19 +- Signature.cpp | 398 +++++++-- Signature.h | 509 ++++++------ Streaming.cpp | 103 +-- Streaming.h | 17 +- TransitTunnel.cpp | 7 +- TransitTunnel.h | 12 +- TransportSession.h | 41 +- Transports.cpp | 66 +- Transports.h | 22 +- Tunnel.cpp | 97 ++- Tunnel.h | 31 +- TunnelBase.h | 15 +- TunnelConfig.h | 138 ++-- TunnelEndpoint.cpp | 3 +- TunnelGateway.cpp | 12 +- TunnelPool.cpp | 46 +- TunnelPool.h | 4 +- api.cpp | 26 +- api.h | 15 +- base64.h | 22 - .../certificates/reseed/parg_at_mail.i2p.crt | 34 + .../certificates/reseed/sindu_at_mail.i2p.crt | 32 - filelist.mk | 25 +- hmac.h | 61 -- i2p.cpp => i2pd.cpp | 3 +- version.h | 4 +- 76 files changed, 2408 insertions(+), 2272 deletions(-) rename base64.cpp => Base.cpp (77%) create mode 100644 Base.h rename aes.cpp => Crypto.cpp (51%) rename aes.h => Crypto.h (62%) delete mode 100644 ElGamal.h delete mode 100644 base64.h create mode 100644 contrib/certificates/reseed/parg_at_mail.i2p.crt delete mode 100644 contrib/certificates/reseed/sindu_at_mail.i2p.crt delete mode 100644 hmac.h rename i2p.cpp => i2pd.cpp (80%) diff --git a/AddressBook.cpp b/AddressBook.cpp index eba7f6b7..ed7b56ae 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -7,8 +7,7 @@ #include #include #include -#include -#include "base64.h" +#include "Base.h" #include "util.h" #include "Identity.h" #include "Log.h" @@ -26,8 +25,8 @@ namespace client public: AddressBookFilesystemStorage (); - bool GetAddress (const i2p::data::IdentHash& ident, i2p::data::IdentityEx& address) const; - void AddAddress (const i2p::data::IdentityEx& address); + std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const; + void AddAddress (std::shared_ptr address); void RemoveAddress (const i2p::data::IdentHash& ident); int Load (std::map& addresses); @@ -50,7 +49,7 @@ namespace client } } - bool AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident, i2p::data::IdentityEx& address) const + std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const { auto filename = GetPath () / (ident.ToBase32() + ".b32"); std::ifstream f(filename.c_str (), std::ifstream::binary); @@ -61,28 +60,28 @@ namespace client if (len < i2p::data::DEFAULT_IDENTITY_SIZE) { LogPrint (eLogError, "File ", filename, " is too short. ", len); - return false; + return nullptr; } f.seekg(0, std::ios::beg); uint8_t * buf = new uint8_t[len]; f.read((char *)buf, len); - address.FromBuffer (buf, len); + auto address = std::make_shared(buf, len); delete[] buf; - return true; + return address; } else - return false; + return nullptr; } - void AddressBookFilesystemStorage::AddAddress (const i2p::data::IdentityEx& address) + void AddressBookFilesystemStorage::AddAddress (std::shared_ptr address) { - auto filename = GetPath () / (address.GetIdentHash ().ToBase32() + ".b32"); + auto filename = GetPath () / (address->GetIdentHash ().ToBase32() + ".b32"); std::ofstream f (filename.c_str (), std::ofstream::binary | std::ofstream::out); if (f.is_open ()) { - size_t len = address.GetFullLen (); + size_t len = address->GetFullLen (); uint8_t * buf = new uint8_t[len]; - address.ToBuffer (buf, len); + address->ToBuffer (buf, len); f.write ((char *)buf, len); delete[] buf; } @@ -256,29 +255,29 @@ namespace client void AddressBook::InsertAddress (const std::string& address, const std::string& base64) { - i2p::data::IdentityEx ident; - ident.FromBase64 (base64); + auto ident = std::make_shared(); + ident->FromBase64 (base64); if (!m_Storage) m_Storage = CreateStorage (); m_Storage->AddAddress (ident); - m_Addresses[address] = ident.GetIdentHash (); - LogPrint (address,"->", ToAddress(ident.GetIdentHash ()), " added"); + m_Addresses[address] = ident->GetIdentHash (); + LogPrint (address,"->", ToAddress(ident->GetIdentHash ()), " added"); } - void AddressBook::InsertAddress (const i2p::data::IdentityEx& address) + void AddressBook::InsertAddress (std::shared_ptr address) { if (!m_Storage) m_Storage = CreateStorage (); m_Storage->AddAddress (address); } - bool AddressBook::GetAddress (const std::string& address, i2p::data::IdentityEx& identity) + std::shared_ptr AddressBook::GetAddress (const std::string& address) { if (!m_Storage) m_Storage = CreateStorage (); i2p::data::IdentHash ident; - if (!GetIdentHash (address, ident)) return false; - return m_Storage->GetAddress (ident, identity); + if (!GetIdentHash (address, ident)) return nullptr; + return m_Storage->GetAddress (ident); } void AddressBook::LoadHosts () @@ -332,10 +331,10 @@ namespace client std::string name = s.substr(0, pos++); std::string addr = s.substr(pos); - i2p::data::IdentityEx ident; - if (ident.FromBase64(addr)) + auto ident = std::make_shared (); + if (ident->FromBase64(addr)) { - m_Addresses[name] = ident.GetIdentHash (); + m_Addresses[name] = ident->GetIdentHash (); m_Storage->AddAddress (ident); numAddresses++; } @@ -415,11 +414,10 @@ namespace client { auto dest = i2p::client::context.GetSharedLocalDestination (); if (!dest) return; - if (m_IsLoaded && !m_IsDownloading && dest->IsReady ()) + if (m_IsLoaded && !m_IsDownloading && dest->IsReady () && !m_Subscriptions.empty ()) { // pick random subscription - CryptoPP::AutoSeededRandomPool rnd; - auto ind = rnd.GenerateWord32 (0, m_Subscriptions.size() - 1); + auto ind = rand () % m_Subscriptions.size(); m_IsDownloading = true; m_Subscriptions[ind]->CheckSubscription (); } diff --git a/AddressBook.h b/AddressBook.h index 6fdae9b1..b50b9d9b 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -7,8 +7,9 @@ #include #include #include +#include #include -#include "base64.h" +#include "Base.h" #include "util.h" #include "Identity.h" #include "Log.h" @@ -31,8 +32,8 @@ namespace client public: virtual ~AddressBookStorage () {}; - virtual bool GetAddress (const i2p::data::IdentHash& ident, i2p::data::IdentityEx& address) const = 0; - virtual void AddAddress (const i2p::data::IdentityEx& address) = 0; + virtual std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const = 0; + virtual void AddAddress (std::shared_ptr address) = 0; virtual void RemoveAddress (const i2p::data::IdentHash& ident) = 0; virtual int Load (std::map& addresses) = 0; @@ -49,16 +50,16 @@ namespace client void Start (); void Stop (); bool GetIdentHash (const std::string& address, i2p::data::IdentHash& ident); - bool GetAddress (const std::string& address, i2p::data::IdentityEx& identity); + std::shared_ptr GetAddress (const std::string& address); const i2p::data::IdentHash * FindAddress (const std::string& address); void InsertAddress (const std::string& address, const std::string& base64); // for jump service - void InsertAddress (const i2p::data::IdentityEx& address); + void InsertAddress (std::shared_ptr address); void LoadHostsFromStream (std::istream& f); void DownloadComplete (bool success); //This method returns the ".b32.i2p" address std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); } - std::string ToAddress(const i2p::data::IdentityEx& ident) { return ToAddress(ident.GetIdentHash ()); } + std::string ToAddress(std::shared_ptr ident) { return ToAddress(ident->GetIdentHash ()); } private: void StartSubscriptions (); diff --git a/BOB.cpp b/BOB.cpp index f5f11d39..15df456e 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -408,14 +408,14 @@ namespace client { LogPrint (eLogDebug, "BOB: newkeys"); m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (); - SendReplyOK (m_Keys.GetPublic ().ToBase64 ().c_str ()); + SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } void BOBCommandSession::SetkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: setkeys ", operand); m_Keys.FromBase64 (operand); - SendReplyOK (m_Keys.GetPublic ().ToBase64 ().c_str ()); + SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } void BOBCommandSession::GetkeysCommandHandler (const char * operand, size_t len) @@ -427,7 +427,7 @@ namespace client void BOBCommandSession::GetdestCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getdest"); - SendReplyOK (m_Keys.GetPublic ().ToBase64 ().c_str ()); + SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len) @@ -477,7 +477,7 @@ namespace client auto localDestination = m_CurrentDestination->GetLocalDestination (); auto leaseSet = localDestination->FindLeaseSet (ident); if (leaseSet) - SendReplyOK (leaseSet->GetIdentity ().ToBase64 ().c_str ()); + SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ()); else { auto s = shared_from_this (); @@ -485,7 +485,7 @@ namespace client [s](std::shared_ptr ls) { if (ls) - s->SendReplyOK (ls->GetIdentity ().ToBase64 ().c_str ()); + s->SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); else s->SendReplyError ("LeaseSet Not found"); } diff --git a/base64.cpp b/Base.cpp similarity index 77% rename from base64.cpp rename to Base.cpp index 3d1ec07e..0bc424e3 100644 --- a/base64.cpp +++ b/Base.cpp @@ -1,11 +1,11 @@ #include -#include "base64.h" +#include "Log.h" +#include "Base.h" namespace i2p { namespace data { - static void iT64Build(void); /* @@ -265,5 +265,69 @@ namespace data } return ret; } + + GzipInflator::GzipInflator (): m_IsDirty (false) + { + memset (&m_Inflator, 0, sizeof (m_Inflator)); + inflateInit2 (&m_Inflator, MAX_WBITS + 16); // gzip + } + + GzipInflator::~GzipInflator () + { + inflateEnd (&m_Inflator); + } + + size_t GzipInflator::Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen) + { + if (m_IsDirty) inflateReset (&m_Inflator); + m_IsDirty = true; + m_Inflator.next_in = const_cast(in); + m_Inflator.avail_in = inLen; + m_Inflator.next_out = out; + m_Inflator.avail_out = outLen; + int err; + if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END) + return outLen - m_Inflator.avail_out; + else + { + LogPrint (eLogError, "Decompression error ", err); + return 0; + } + } + + GzipDeflator::GzipDeflator (): m_IsDirty (false) + { + memset (&m_Deflator, 0, sizeof (m_Deflator)); + deflateInit2 (&m_Deflator, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); // 15 + 16 sets gzip + } + + GzipDeflator::~GzipDeflator () + { + deflateEnd (&m_Deflator); + } + + void GzipDeflator::SetCompressionLevel (int level) + { + deflateParams (&m_Deflator, level, Z_DEFAULT_STRATEGY); + } + + size_t GzipDeflator::Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen) + { + if (m_IsDirty) deflateReset (&m_Deflator); + m_IsDirty = true; + m_Deflator.next_in = const_cast(in); + m_Deflator.avail_in = inLen; + m_Deflator.next_out = out; + m_Deflator.avail_out = outLen; + int err; + if ((err = deflate (&m_Deflator, Z_FINISH)) == Z_STREAM_END) + return outLen - m_Deflator.avail_out; + else + { + LogPrint (eLogError, "Compression error ", err); + return 0; + } + } } } + diff --git a/Base.h b/Base.h new file mode 100644 index 00000000..0c59ef03 --- /dev/null +++ b/Base.h @@ -0,0 +1,123 @@ +#ifndef BASE_H__ +#define BASE_H__ + +#include +#include +#include +#include + +namespace i2p +{ +namespace data +{ + size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len); + size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len ); + const char * GetBase64SubstitutionTable (); + + size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen); + size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen); + + template + class Tag + { + public: + + Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); }; + Tag (const Tag& ) = default; +#ifndef _WIN32 // FIXME!!! msvs 2013 can't compile it + Tag (Tag&& ) = default; +#endif + Tag () = default; + + Tag& operator= (const Tag& ) = default; +#ifndef _WIN32 + Tag& operator= (Tag&& ) = default; +#endif + + uint8_t * operator()() { return m_Buf; }; + const uint8_t * operator()() const { return m_Buf; }; + + operator uint8_t * () { return m_Buf; }; + operator const uint8_t * () const { return m_Buf; }; + + const uint64_t * GetLL () const { return ll; }; + + bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); }; + bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; }; + + bool IsZero () const + { + for (int i = 0; i < sz/8; i++) + if (ll[i]) return false; + return true; + } + + std::string ToBase64 () const + { + char str[sz*2]; + int l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2); + str[l] = 0; + return std::string (str); + } + + std::string ToBase32 () const + { + char str[sz*2]; + int l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2); + str[l] = 0; + return std::string (str); + } + + void FromBase32 (const std::string& s) + { + i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); + } + + void FromBase64 (const std::string& s) + { + i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); + } + + private: + + union // 8 bytes alignment + { + uint8_t m_Buf[sz]; + uint64_t ll[sz/8]; + }; + }; + + class GzipInflator + { + public: + + GzipInflator (); + ~GzipInflator (); + + size_t Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen); + + private: + + z_stream m_Inflator; + bool m_IsDirty; + }; + + class GzipDeflator + { + public: + + GzipDeflator (); + ~GzipDeflator (); + + void SetCompressionLevel (int level); + size_t Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen); + + private: + + z_stream m_Deflator; + bool m_IsDirty; + }; +} +} + +#endif diff --git a/ClientContext.cpp b/ClientContext.cpp index 9f14da90..0adeee9b 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -15,7 +15,7 @@ namespace client ClientContext::ClientContext (): m_SharedLocalDestination (nullptr), m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr), - m_BOBCommandChannel (nullptr), m_I2PControlService (nullptr) + m_BOBCommandChannel (nullptr) { } @@ -25,7 +25,6 @@ namespace client delete m_SocksProxy; delete m_SamBridge; delete m_BOBCommandChannel; - delete m_I2PControlService; } void ClientContext::Start () @@ -33,7 +32,7 @@ namespace client if (!m_SharedLocalDestination) { m_SharedLocalDestination = CreateNewLocalDestination (); // non-public, DSA - m_Destinations[m_SharedLocalDestination->GetIdentity ().GetIdentHash ()] = m_SharedLocalDestination; + m_Destinations[m_SharedLocalDestination->GetIdentity ()->GetIdentHash ()] = m_SharedLocalDestination; m_SharedLocalDestination->Start (); } @@ -93,14 +92,6 @@ namespace client LogPrint("BOB command channel started"); } - // I2P Control - int i2pcontrolPort = i2p::util::config::GetArg("-i2pcontrolport", 0); - if (i2pcontrolPort) - { - m_I2PControlService = new I2PControlService (i2pcontrolPort); - m_I2PControlService->Start (); - LogPrint("I2PControl started"); - } m_AddressBook.Start (); } @@ -140,13 +131,6 @@ namespace client m_BOBCommandChannel = nullptr; LogPrint("BOB command channel stopped"); } - if (m_I2PControlService) - { - m_I2PControlService->Stop (); - delete m_I2PControlService; - m_I2PControlService = nullptr; - LogPrint("I2PControl stopped"); - } m_AddressBook.Stop (); for (auto it: m_Destinations) it.second->Stop (); @@ -168,7 +152,7 @@ namespace client s.read ((char *)buf, len); keys.FromBuffer (buf, len); delete[] buf; - LogPrint ("Local address ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " loaded"); + LogPrint ("Local address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " loaded"); } else { @@ -181,15 +165,15 @@ namespace client f.write ((char *)buf, len); delete[] buf; - LogPrint ("New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " created"); + LogPrint ("New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); } std::shared_ptr localDestination = nullptr; std::unique_lock l(m_DestinationsMutex); - auto it = m_Destinations.find (keys.GetPublic ().GetIdentHash ()); + auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ()); if (it != m_Destinations.end ()) { - LogPrint (eLogWarning, "Local destination ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " alreday exists"); + LogPrint (eLogWarning, "Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " alreday exists"); localDestination = it->second; } else @@ -230,10 +214,10 @@ namespace client std::shared_ptr ClientContext::CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { - auto it = m_Destinations.find (keys.GetPublic ().GetIdentHash ()); + auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ()); if (it != m_Destinations.end ()) { - LogPrint ("Local destination ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " exists"); + LogPrint ("Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " exists"); if (!it->second->IsRunning ()) { it->second->Start (); @@ -243,7 +227,7 @@ namespace client } auto localDestination = std::make_shared (keys, isPublic, params); std::unique_lock l(m_DestinationsMutex); - m_Destinations[keys.GetPublic ().GetIdentHash ()] = localDestination; + m_Destinations[keys.GetPublic ()->GetIdentHash ()] = localDestination; localDestination->Start (); return localDestination; } diff --git a/ClientContext.h b/ClientContext.h index 0fe89fb8..762b2e75 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -11,7 +11,6 @@ #include "SAM.h" #include "BOB.h" #include "AddressBook.h" -#include "I2PControl.h" namespace i2p { @@ -72,7 +71,6 @@ namespace client std::map > m_ServerTunnels; // destination->tunnel SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; - I2PControlService * m_I2PControlService; public: // for HTTP diff --git a/aes.cpp b/Crypto.cpp similarity index 51% rename from aes.cpp rename to Crypto.cpp index 506e7ca8..831ecf30 100644 --- a/aes.cpp +++ b/Crypto.cpp @@ -1,13 +1,275 @@ -#include -#include "TunnelBase.h" -#include "aes.h" +#include +#include +#include +#include +#include +#include +#include "Log.h" +//#include "TunnelBase.h" +#include "Crypto.h" namespace i2p { namespace crypto -{ +{ + const uint8_t elgp_[256]= + { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, + 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, + 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, + 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, + 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, + 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, + 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, + 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, + 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, + 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, + 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; -#ifdef AESNI + const int elgg_ = 2; + + const uint8_t dsap_[128]= + { + 0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c, + 0x30, 0x26, 0xe9, 0xb8, 0xed, 0x92, 0xfa, 0xd0, 0xa6, 0x9c, 0xc8, 0x86, 0xd5, 0xbf, 0x80, 0x15, + 0xfc, 0xad, 0xae, 0x31, 0xa0, 0xad, 0x18, 0xfa, 0xb3, 0xf0, 0x1b, 0x00, 0xa3, 0x58, 0xde, 0x23, + 0x76, 0x55, 0xc4, 0x96, 0x4a, 0xfa, 0xa2, 0xb3, 0x37, 0xe9, 0x6a, 0xd3, 0x16, 0xb9, 0xfb, 0x1c, + 0xc5, 0x64, 0xb5, 0xae, 0xc5, 0xb6, 0x9a, 0x9f, 0xf6, 0xc3, 0xe4, 0x54, 0x87, 0x07, 0xfe, 0xf8, + 0x50, 0x3d, 0x91, 0xdd, 0x86, 0x02, 0xe8, 0x67, 0xe6, 0xd3, 0x5d, 0x22, 0x35, 0xc1, 0x86, 0x9c, + 0xe2, 0x47, 0x9c, 0x3b, 0x9d, 0x54, 0x01, 0xde, 0x04, 0xe0, 0x72, 0x7f, 0xb3, 0x3d, 0x65, 0x11, + 0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93 + }; + + const uint8_t dsaq_[20]= + { + 0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d, + 0x68, 0x40, 0x46, 0xb7 + }; + + const uint8_t dsag_[128]= + { + 0x0c, 0x1f, 0x4d, 0x27, 0xd4, 0x00, 0x93, 0xb4, 0x29, 0xe9, 0x62, 0xd7, 0x22, 0x38, 0x24, 0xe0, + 0xbb, 0xc4, 0x7e, 0x7c, 0x83, 0x2a, 0x39, 0x23, 0x6f, 0xc6, 0x83, 0xaf, 0x84, 0x88, 0x95, 0x81, + 0x07, 0x5f, 0xf9, 0x08, 0x2e, 0xd3, 0x23, 0x53, 0xd4, 0x37, 0x4d, 0x73, 0x01, 0xcd, 0xa1, 0xd2, + 0x3c, 0x43, 0x1f, 0x46, 0x98, 0x59, 0x9d, 0xda, 0x02, 0x45, 0x18, 0x24, 0xff, 0x36, 0x97, 0x52, + 0x59, 0x36, 0x47, 0xcc, 0x3d, 0xdc, 0x19, 0x7d, 0xe9, 0x85, 0xe4, 0x3d, 0x13, 0x6c, 0xdc, 0xfc, + 0x6b, 0xd5, 0x40, 0x9c, 0xd2, 0xf4, 0x50, 0x82, 0x11, 0x42, 0xa5, 0xe6, 0xf8, 0xeb, 0x1c, 0x3a, + 0xb5, 0xd0, 0x48, 0x4b, 0x81, 0x29, 0xfc, 0xf1, 0x7b, 0xce, 0x4f, 0x7f, 0x33, 0x32, 0x1c, 0x3c, + 0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82 + }; + + const int rsae_ = 65537; + + const CryptoConstants& GetCryptoConstants () + { + static CryptoConstants cryptoConstants (elgp_, elgg_, dsap_, dsaq_, dsag_, rsae_); + return cryptoConstants; + } + + bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len) + { + int offset = len - BN_num_bytes (bn); + if (offset < 0) return false; + BN_bn2bin (bn, buf + offset); + memset (buf, 0, offset); + return true; + } + +// DH + + DHKeys::DHKeys (): m_IsUpdated (true) + { + m_DH = DH_new (); + m_DH->p = BN_dup (elgp); + m_DH->g = BN_dup (elgg); + m_DH->priv_key = NULL; + m_DH->pub_key = NULL; + } + + DHKeys::~DHKeys () + { + DH_free (m_DH); + } + + void DHKeys::GenerateKeys (uint8_t * priv, uint8_t * pub) + { + if (m_DH->priv_key) { BN_free (m_DH->priv_key); m_DH->priv_key = NULL; }; + if (m_DH->pub_key) { BN_free (m_DH->pub_key); m_DH->pub_key = NULL; }; + DH_generate_key (m_DH); + if (priv) bn2buf (m_DH->priv_key, priv, 256); + if (pub) bn2buf (m_DH->pub_key, pub, 256); + m_IsUpdated = true; + } + + const uint8_t * DHKeys::GetPublicKey () + { + if (m_IsUpdated) + { + bn2buf (m_DH->pub_key, m_PublicKey, 256); + BN_free (m_DH->pub_key); m_DH->pub_key = NULL; + m_IsUpdated= false; + } + return m_PublicKey; + } + + void DHKeys::Agree (const uint8_t * pub, uint8_t * shared) + { + BIGNUM * pk = BN_bin2bn (pub, 256, NULL); + DH_compute_key (shared, pk, m_DH); + BN_free (pk); + } + +// ElGamal + + ElGamalEncryption::ElGamalEncryption (const uint8_t * key) + { + ctx = BN_CTX_new (); + // select random k + BIGNUM * k = BN_new (); + BN_rand_range (k, elgp); + if (BN_is_zero (k)) BN_one (k); + // caulculate a + a = BN_new (); + BN_mod_exp (a, elgg, k, elgp, ctx); + BIGNUM * y = BN_new (); + BN_bin2bn (key, 256, y); + // calculate b1 + b1 = BN_new (); + BN_mod_exp (b1, y, k, elgp, ctx); + BN_free (y); + BN_free (k); + } + + ElGamalEncryption::~ElGamalEncryption () + { + BN_CTX_free (ctx); + BN_free (a); + BN_free (b1); + } + + void ElGamalEncryption::Encrypt (const uint8_t * data, int len, uint8_t * encrypted, bool zeroPadding) const + { + // create m + uint8_t m[255]; + m[0] = 0xFF; + memcpy (m+33, data, len); + SHA256 (m+33, 222, m+1); + // calculate b = b1*m mod p + BIGNUM * b = BN_new (); + BN_bin2bn (m, 255, b); + BN_mod_mul (b, b1, b, elgp, ctx); + // copy a and b + if (zeroPadding) + { + encrypted[0] = 0; + bn2buf (a, encrypted + 1, 256); + encrypted[257] = 0; + bn2buf (b, encrypted + 258, 256); + } + else + { + bn2buf (a, encrypted, 256); + bn2buf (b, encrypted + 256, 256); + } + BN_free (b); + } + + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, + uint8_t * data, bool zeroPadding) + { + BN_CTX * ctx = BN_CTX_new (); + BIGNUM * x = BN_new (), * a = BN_new (), * b = BN_new (); + BN_bin2bn (key, 256, x); + BN_sub (x, elgp, x); BN_sub_word (x, 1); // x = elgp - x- 1 + BN_bin2bn (zeroPadding ? encrypted + 1 : encrypted, 256, a); + BN_bin2bn (zeroPadding ? encrypted + 258 : encrypted + 256, 256, b); + // m = b*(a^x mod p) mod p + BN_mod_exp (x, a, x, elgp, ctx); + BN_mod_mul (b, b, x, elgp, ctx); + uint8_t m[255]; + bn2buf (b, m, 255); + BN_free (x); BN_free (a); BN_free (b); + BN_CTX_free (ctx); + uint8_t hash[32]; + SHA256 (m + 33, 222, hash); + if (memcmp (m + 1, hash, 32)) + { + LogPrint (eLogError, "ElGamal decrypt hash doesn't match"); + return false; + } + memcpy (data, m + 33, 222); + return true; + } + + void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub) + { +#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER) + RAND_bytes (priv, 256); + BN_CTX * ctx = BN_CTX_new (); + BIGNUM * p = BN_new (); + BN_bin2bn (priv, 256, p); + BN_mod_exp (p, elgg, p, elgp, ctx); + bn2buf (p, pub, 256); + BN_free (p); + BN_CTX_free (ctx); +#else + DHKeys dh; + dh.GenerateKeys (priv, pub); + +#endif + } + +// HMAC + const uint64_t IPAD = 0x3636363636363636; + const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C; + + void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest) + // key is 32 bytes + // digest is 16 bytes + // block size is 64 bytes + { + uint64_t buf[256]; + // ikeypad + buf[0] = key.GetLL ()[0] ^ IPAD; + buf[1] = key.GetLL ()[1] ^ IPAD; + buf[2] = key.GetLL ()[2] ^ IPAD; + buf[3] = key.GetLL ()[3] ^ IPAD; + buf[4] = IPAD; + buf[5] = IPAD; + buf[6] = IPAD; + buf[7] = IPAD; + // concatenate with msg + memcpy (buf + 8, msg, len); + // calculate first hash + uint8_t hash[16]; // MD5 + MD5((uint8_t *)buf, len + 64, hash); + + // okeypad + buf[0] = key.GetLL ()[0] ^ OPAD; + buf[1] = key.GetLL ()[1] ^ OPAD; + buf[2] = key.GetLL ()[2] ^ OPAD; + buf[3] = key.GetLL ()[3] ^ OPAD; + buf[4] = OPAD; + buf[5] = OPAD; + buf[6] = OPAD; + buf[7] = OPAD; + // copy first hash after okeypad + memcpy (buf + 8, hash, 16); + // fill next 16 bytes with zeros (first hash size assumed 32 bytes in I2P) + memset (buf + 10, 0, 16); + + // calculate digest + MD5((uint8_t *)buf, 96, digest); + } + +// AES + #ifdef AESNI #define KeyExpansion256(round0,round1) \ "pshufd $0xff, %%xmm2, %%xmm2 \n" \ @@ -311,7 +573,7 @@ namespace crypto #else m_IVEncryption.Encrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv m_LayerEncryption.SetIV (out); - m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data + m_LayerEncryption.Encrypt (in + 16, /*i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE*/1008, out + 16); // data m_IVEncryption.Encrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv #endif } @@ -348,10 +610,10 @@ namespace crypto #else m_IVDecryption.Decrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv m_LayerDecryption.SetIV (out); - m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data + m_LayerDecryption.Decrypt (in + 16, /*i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE*/1008, out + 16); // data m_IVDecryption.Decrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv #endif - } + } } } diff --git a/aes.h b/Crypto.h similarity index 62% rename from aes.h rename to Crypto.h index 84ca1f17..d08e3b3c 100644 --- a/aes.h +++ b/Crypto.h @@ -1,15 +1,113 @@ -#ifndef AES_H__ -#define AES_H__ +#ifndef CRYPTO_H__ +#define CRYPTO_H__ #include -#include -#include -#include "Identity.h" +#include +#include +#include +#include +#include "Base.h" namespace i2p { namespace crypto -{ +{ + struct CryptoConstants + { + // DH/ElGamal + BIGNUM * elgp; + BIGNUM * elgg; + + // DSA + BIGNUM * dsap; + BIGNUM * dsaq; + BIGNUM * dsag; + + // RSA + BIGNUM * rsae; + + CryptoConstants (const uint8_t * elgp_, int elgg_, const uint8_t * dsap_, + const uint8_t * dsaq_, const uint8_t * dsag_, int rsae_) + { + elgp = BN_new (); + BN_bin2bn (elgp_, 256, elgp); + elgg = BN_new (); + BN_set_word (elgg, elgg_); + dsap = BN_new (); + BN_bin2bn (dsap_, 128, dsap); + dsaq = BN_new (); + BN_bin2bn (dsaq_, 20, dsaq); + dsag = BN_new (); + BN_bin2bn (dsag_, 128, dsag); + rsae = BN_new (); + BN_set_word (rsae, rsae_); + } + + ~CryptoConstants () + { + BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae); + } + }; + + const CryptoConstants& GetCryptoConstants (); + + // DH/ElGamal + #define elgp GetCryptoConstants ().elgp + #define elgg GetCryptoConstants ().elgg + + // DSA + #define dsap GetCryptoConstants ().dsap + #define dsaq GetCryptoConstants ().dsaq + #define dsag GetCryptoConstants ().dsag + + // RSA + #define rsae GetCryptoConstants ().rsae + + bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len); + + // DH + class DHKeys + { + public: + + DHKeys (); + ~DHKeys (); + + void GenerateKeys (uint8_t * priv = nullptr, uint8_t * pub = nullptr); + const uint8_t * GetPublicKey (); + void Agree (const uint8_t * pub, uint8_t * shared); + + private: + + DH * m_DH; + uint8_t m_PublicKey[256]; + bool m_IsUpdated; + }; + + // ElGamal + class ElGamalEncryption + { + public: + + ElGamalEncryption (const uint8_t * key); + ~ElGamalEncryption (); + + void Encrypt (const uint8_t * data, int len, uint8_t * encrypted, bool zeroPadding = false) const; + + private: + + BN_CTX * ctx; + BIGNUM * a, * b1; + }; + + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data, bool zeroPadding = false); + void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub); + + // HMAC + typedef i2p::data::Tag<32> MACKey; + void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest); + + // AES struct ChipherBlock { uint8_t buf[16]; @@ -95,7 +193,7 @@ namespace crypto typedef ECBEncryptionAESNI ECBEncryption; typedef ECBDecryptionAESNI ECBDecryption; -#else // use crypto++ +#else // use openssl class ECBEncryption { @@ -103,16 +201,16 @@ namespace crypto void SetKey (const AESKey& key) { - m_Encryption.SetKey (key, 32); + AES_set_encrypt_key (key, 256, &m_Key); } void Encrypt (const ChipherBlock * in, ChipherBlock * out) { - m_Encryption.ProcessData (out->buf, in->buf, 16); + AES_encrypt (in->buf, out->buf, &m_Key); } private: - CryptoPP::ECB_Mode::Encryption m_Encryption; + AES_KEY m_Key; }; class ECBDecryption @@ -121,16 +219,16 @@ namespace crypto void SetKey (const AESKey& key) { - m_Decryption.SetKey (key, 32); + AES_set_decrypt_key (key, 256, &m_Key); } void Decrypt (const ChipherBlock * in, ChipherBlock * out) { - m_Decryption.ProcessData (out->buf, in->buf, 16); + AES_decrypt (in->buf, out->buf, &m_Key); } private: - CryptoPP::ECB_Mode::Decryption m_Decryption; + AES_KEY m_Key; }; @@ -217,9 +315,8 @@ namespace crypto #else CBCDecryption m_LayerDecryption; #endif - }; -} -} + }; +} +} #endif - diff --git a/Daemon.cpp b/Daemon.cpp index 77cd0899..ee3dbe76 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -1,9 +1,11 @@ #include +#include +#include #include "Daemon.h" #include "Log.h" -#include "base64.h" +#include "Base.h" #include "version.h" #include "Transports.h" #include "NTCPSession.h" @@ -16,8 +18,13 @@ #include "Streaming.h" #include "Destination.h" #include "HTTPServer.h" +#include "I2PControl.h" #include "ClientContext.h" +#ifdef USE_UPNP +#include "UPnP.h" +#endif + namespace i2p { namespace util @@ -25,14 +32,15 @@ namespace i2p class Daemon_Singleton::Daemon_Singleton_Private { public: - Daemon_Singleton_Private() : httpServer(nullptr) - {}; - ~Daemon_Singleton_Private() - { - delete httpServer; - }; + Daemon_Singleton_Private() {}; + ~Daemon_Singleton_Private() {}; - i2p::util::HTTPServer *httpServer; + std::unique_ptr httpServer; + std::unique_ptr m_I2PControlService; + +#ifdef USE_UPNP + UPnP m_UPnP; +#endif }; Daemon_Singleton::Daemon_Singleton() : running(1), d(*new Daemon_Singleton_Private()) {}; @@ -51,6 +59,7 @@ namespace i2p bool Daemon_Singleton::init(int argc, char* argv[]) { + SSL_library_init (); i2p::util::config::OptionParser(argc, argv); i2p::context.Init (); @@ -106,17 +115,29 @@ namespace i2p StartLog (""); // write to stdout } - d.httpServer = new i2p::util::HTTPServer(i2p::util::config::GetArg("-httpport", 7070)); + d.httpServer = std::unique_ptr(new i2p::util::HTTPServer(i2p::util::config::GetArg("-httpport", 7070))); d.httpServer->Start(); LogPrint("HTTP Server started"); i2p::data::netdb.Start(); LogPrint("NetDB started"); +#ifdef USE_UPNP + d.m_UPnP.Start (); + LogPrint(eLogInfo, "UPnP started"); +#endif i2p::transport::transports.Start(); LogPrint("Transports started"); i2p::tunnel::tunnels.Start(); LogPrint("Tunnels started"); i2p::client::context.Start (); LogPrint("Client started"); + // I2P Control + int i2pcontrolPort = i2p::util::config::GetArg("-i2pcontrolport", 0); + if (i2pcontrolPort) + { + d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcontrolPort)); + d.m_I2PControlService->Start (); + LogPrint("I2PControl started"); + } return true; } @@ -127,17 +148,25 @@ namespace i2p LogPrint("Client stopped"); i2p::tunnel::tunnels.Stop(); LogPrint("Tunnels stopped"); +#ifdef USE_UPNP + d.m_UPnP.Stop (); + LogPrint(eLogInfo, "UPnP stopped"); +#endif i2p::transport::transports.Stop(); LogPrint("Transports stopped"); i2p::data::netdb.Stop(); LogPrint("NetDB stopped"); d.httpServer->Stop(); + d.httpServer = nullptr; LogPrint("HTTP Server stopped"); - + if (d.m_I2PControlService) + { + d.m_I2PControlService->Stop (); + d.m_I2PControlService = nullptr; + LogPrint("I2PControl stopped"); + } StopLog (); - delete d.httpServer; d.httpServer = nullptr; - return true; } } diff --git a/Datagram.cpp b/Datagram.cpp index 63b487ff..e065e3e7 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -1,7 +1,7 @@ #include #include -#include -#include +#include +#include #include "Log.h" #include "TunnelBase.h" #include "RouterContext.h" @@ -12,36 +12,40 @@ namespace i2p { namespace datagram { - DatagramDestination::DatagramDestination (i2p::client::ClientDestination& owner): + DatagramDestination::DatagramDestination (std::shared_ptr owner): m_Owner (owner), m_Receiver (nullptr) { } - + + DatagramDestination::~DatagramDestination () + { + } + void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort) { uint8_t buf[MAX_DATAGRAM_SIZE]; - auto identityLen = m_Owner.GetIdentity ().ToBuffer (buf, MAX_DATAGRAM_SIZE); + auto identityLen = m_Owner->GetIdentity ()->ToBuffer (buf, MAX_DATAGRAM_SIZE); uint8_t * signature = buf + identityLen; - auto signatureLen = m_Owner.GetIdentity ().GetSignatureLen (); + auto signatureLen = m_Owner->GetIdentity ()->GetSignatureLen (); uint8_t * buf1 = signature + signatureLen; size_t headerLen = identityLen + signatureLen; memcpy (buf1, payload, len); - if (m_Owner.GetIdentity ().GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) + if (m_Owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) { uint8_t hash[32]; - CryptoPP::SHA256().CalculateDigest (hash, buf1, len); - m_Owner.Sign (hash, 32, signature); + SHA256(buf1, len, hash); + m_Owner->Sign (hash, 32, signature); } else - m_Owner.Sign (buf1, len, signature); + m_Owner->Sign (buf1, len, signature); auto msg = CreateDataMessage (buf, len + headerLen, fromPort, toPort); - auto remote = m_Owner.FindLeaseSet (ident); + auto remote = m_Owner->FindLeaseSet (ident); if (remote) - m_Owner.GetService ().post (std::bind (&DatagramDestination::SendMsg, this, msg, remote)); + m_Owner->GetService ().post (std::bind (&DatagramDestination::SendMsg, this, msg, remote)); else - m_Owner.RequestDestination (ident, std::bind (&DatagramDestination::HandleLeaseSetRequestComplete, this, std::placeholders::_1, msg)); + m_Owner->RequestDestination (ident, std::bind (&DatagramDestination::HandleLeaseSetRequestComplete, this, std::placeholders::_1, msg)); } void DatagramDestination::HandleLeaseSetRequestComplete (std::shared_ptr remote, I2NPMessage * msg) @@ -54,13 +58,13 @@ namespace datagram void DatagramDestination::SendMsg (I2NPMessage * msg, std::shared_ptr remote) { - auto outboundTunnel = m_Owner.GetTunnelPool ()->GetNextOutboundTunnel (); + auto outboundTunnel = m_Owner->GetTunnelPool ()->GetNextOutboundTunnel (); auto leases = remote->GetNonExpiredLeases (); if (!leases.empty () && outboundTunnel) { std::vector msgs; - uint32_t i = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, leases.size () - 1); - auto garlic = m_Owner.WrapMessage (remote, ToSharedI2NPMessage (msg), true); + uint32_t i = rand () % leases.size (); + auto garlic = m_Owner->WrapMessage (remote, ToSharedI2NPMessage (msg), true); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, @@ -90,7 +94,7 @@ namespace datagram if (identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) { uint8_t hash[32]; - CryptoPP::SHA256().CalculateDigest (hash, buf + headerLen, len - headerLen); + SHA256(buf + headerLen, len - headerLen, hash); verified = identity.Verify (hash, 32, signature); } else @@ -113,37 +117,32 @@ namespace datagram void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { // unzip it - CryptoPP::Gunzip decompressor; - decompressor.Put (buf, len); - decompressor.MessageEnd(); uint8_t uncompressed[MAX_DATAGRAM_SIZE]; - auto uncompressedLen = decompressor.MaxRetrievable (); - if (uncompressedLen <= MAX_DATAGRAM_SIZE) - { - decompressor.Get (uncompressed, uncompressedLen); + size_t uncompressedLen = m_Inflator.Inflate (buf, len, uncompressed, MAX_DATAGRAM_SIZE); + if (uncompressedLen) HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen); - } - else - LogPrint ("Received datagram size ", uncompressedLen, " exceeds max size"); - } I2NPMessage * DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) { I2NPMessage * msg = NewI2NPMessage (); - CryptoPP::Gzip compressor; // default level - compressor.Put (payload, len); - compressor.MessageEnd(); - int size = compressor.MaxRetrievable (); uint8_t * buf = msg->GetPayload (); - htobe32buf (buf, size); // length - buf += 4; - compressor.Get (buf, size); - htobe16buf (buf + 4, fromPort); // source port - htobe16buf (buf + 6, toPort); // destination port - buf[9] = i2p::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol - msg->len += size + 4; - msg->FillI2NPMessageHeader (eI2NPData); + buf += 4; // reserve for length + size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); + if (size) + { + htobe32buf (msg->GetPayload (), size); // length + htobe16buf (buf + 4, fromPort); // source port + htobe16buf (buf + 6, toPort); // destination port + buf[9] = i2p::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol + msg->len += size + 4; + msg->FillI2NPMessageHeader (eI2NPData); + } + else + { + DeleteI2NPMessage (msg); + msg = nullptr; + } return msg; } } diff --git a/Datagram.h b/Datagram.h index f2e31304..7f6df921 100644 --- a/Datagram.h +++ b/Datagram.h @@ -5,6 +5,7 @@ #include #include #include +#include "Base.h" #include "Identity.h" #include "LeaseSet.h" #include "I2NPProtocol.h" @@ -24,8 +25,8 @@ namespace datagram public: - DatagramDestination (i2p::client::ClientDestination& owner); - ~DatagramDestination () {}; + DatagramDestination (std::shared_ptr owner); + ~DatagramDestination (); void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort = 0, uint16_t toPort = 0); void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); @@ -46,9 +47,12 @@ namespace datagram private: - i2p::client::ClientDestination& m_Owner; + std::shared_ptr m_Owner; Receiver m_Receiver; // default std::map m_ReceiversByPorts; + + i2p::data::GzipInflator m_Inflator; + i2p::data::GzipDeflator m_Deflator; }; } } diff --git a/Destination.cpp b/Destination.cpp index e1c639c2..479006c8 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -1,12 +1,12 @@ #include #include #include +#include #include "Log.h" #include "util.h" -#include "ElGamal.h" +#include "Crypto.h" #include "Timestamp.h" #include "NetDb.h" -#include "AddressBook.h" #include "Destination.h" namespace i2p @@ -19,7 +19,7 @@ namespace client m_Keys (keys), m_IsPublic (isPublic), m_PublishReplyToken (0), m_DatagramDestination (nullptr), m_PublishConfirmationTimer (m_Service), m_CleanupTimer (m_Service) { - i2p::crypto::GenerateElGamalKeyPair(i2p::context.GetRandomNumberGenerator (), m_EncryptionPrivateKey, m_EncryptionPublicKey); + i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); int inboundTunnelLen = DEFAULT_INBOUND_TUNNEL_LENGTH; int outboundTunnelLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH; int inboundTunnelsQuantity = DEFAULT_INBOUND_TUNNELS_QUANTITY; @@ -86,8 +86,7 @@ namespace client if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); if (m_IsPublic) - LogPrint (eLogInfo, "Local address ", i2p::client::GetB32Address(GetIdentHash()), " created"); - m_StreamingDestination = std::make_shared (*this); // TODO: + LogPrint (eLogInfo, "Local address ", GetIdentHash().ToBase32 (), " created"); } ClientDestination::~ClientDestination () @@ -123,8 +122,9 @@ namespace client { m_IsRunning = true; m_Pool->SetLocalDestination (this); - m_Pool->SetActive (true); + m_Pool->SetActive (true); m_Thread = new std::thread (std::bind (&ClientDestination::Run, this)); + m_StreamingDestination = std::make_shared (shared_from_this ()); // TODO: m_StreamingDestination->Start (); for (auto it: m_StreamingDestinationsByPorts) it.second->Start (); @@ -141,7 +141,8 @@ namespace client { m_CleanupTimer.cancel (); m_IsRunning = false; - m_StreamingDestination->Stop (); + m_StreamingDestination->Stop (); + m_StreamingDestination = nullptr; for (auto it: m_StreamingDestinationsByPorts) it.second->Stop (); if (m_DatagramDestination) @@ -396,8 +397,8 @@ namespace client } m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); - m_PublishReplyToken = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); - auto msg = WrapMessage (floodfill, ToSharedI2NPMessage (i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken))); + RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); + auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&ClientDestination::HandlePublishConfirmationTimer, this, std::placeholders::_1)); @@ -507,7 +508,7 @@ namespace client std::shared_ptr ClientDestination::CreateStreamingDestination (int port) { - auto dest = std::make_shared (*this, port); + auto dest = std::make_shared (shared_from_this (), port); if (port) m_StreamingDestinationsByPorts[port] = dest; else // update default @@ -518,7 +519,7 @@ namespace client i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination () { if (!m_DatagramDestination) - m_DatagramDestination = new i2p::datagram::DatagramDestination (*this); + m_DatagramDestination = new i2p::datagram::DatagramDestination (shared_from_this ()); return m_DatagramDestination; } @@ -529,7 +530,7 @@ namespace client if (requestComplete) requestComplete (false); return false; } - m_Service.post (std::bind (&ClientDestination::RequestLeaseSet, this, dest, requestComplete)); + m_Service.post (std::bind (&ClientDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete)); return true; } @@ -579,15 +580,14 @@ namespace client request->requestTime = i2p::util::GetSecondsSinceEpoch (); request->requestTimeoutTimer.cancel (); - CryptoPP::AutoSeededRandomPool rnd; uint8_t replyKey[32], replyTag[32]; - rnd.GenerateBlock (replyKey, 32); // random session key - rnd.GenerateBlock (replyTag, 32); // random session tag + RAND_bytes (replyKey, 32); // random session key + RAND_bytes (replyTag, 32); // random session tag AddSessionKey (replyKey, replyTag); auto msg = WrapMessage (nextFloodfill, - ToSharedI2NPMessage (CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, - replyTunnel.get (), replyKey, replyTag))); + CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, + replyTunnel.get (), replyKey, replyTag)); outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock @@ -646,7 +646,7 @@ namespace client CleanupRemoteLeaseSets (); m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer, - this, std::placeholders::_1)); + shared_from_this (), std::placeholders::_1)); } } diff --git a/Destination.h b/Destination.h index c41ee9ca..0000014b 100644 --- a/Destination.h +++ b/Destination.h @@ -11,7 +11,7 @@ #include #include "Identity.h" #include "TunnelPool.h" -#include "CryptoConst.h" +#include "Crypto.h" #include "LeaseSet.h" #include "Garlic.h" #include "NetDb.h" @@ -45,7 +45,8 @@ namespace client typedef std::function stream)> StreamRequestComplete; - class ClientDestination: public i2p::garlic::GarlicDestination + class ClientDestination: public i2p::garlic::GarlicDestination, + public std::enable_shared_from_this { typedef std::function leaseSet)> RequestComplete; // leaseSet = nullptr means not found diff --git a/ElGamal.h b/ElGamal.h deleted file mode 100644 index 359de358..00000000 --- a/ElGamal.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef EL_GAMAL_H__ -#define EL_GAMAL_H__ - -#include -#include -#include -#include -#include -#include "CryptoConst.h" -#include "Log.h" - -namespace i2p -{ -namespace crypto -{ - - class ElGamalEncryption - { - public: - - ElGamalEncryption (const uint8_t * key) - { - CryptoPP::AutoSeededRandomPool rnd; - CryptoPP::Integer y (key, 256), k (rnd, CryptoPP::Integer::One(), elgp-1); - a = a_exp_b_mod_c (elgg, k, elgp); - b1 = a_exp_b_mod_c (y, k, elgp); - } - - void Encrypt (const uint8_t * data, int len, uint8_t * encrypted, bool zeroPadding = false) const - { - // calculate b = b1*m mod p - uint8_t m[255]; - m[0] = 0xFF; - memcpy (m+33, data, len); - CryptoPP::SHA256().CalculateDigest(m+1, m+33, 222); - CryptoPP::Integer b (a_times_b_mod_c (b1, CryptoPP::Integer (m, 255), elgp)); - - // copy a and b - if (zeroPadding) - { - encrypted[0] = 0; - a.Encode (encrypted + 1, 256); - encrypted[257] = 0; - b.Encode (encrypted + 258, 256); - } - else - { - a.Encode (encrypted, 256); - b.Encode (encrypted + 256, 256); - } - } - - private: - - CryptoPP::Integer a, b1; - }; - - inline bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, - uint8_t * data, bool zeroPadding = false) - { - CryptoPP::Integer x(key, 256), a(zeroPadding? encrypted +1 : encrypted, 256), - b(zeroPadding? encrypted + 258 :encrypted + 256, 256); - uint8_t m[255]; - a_times_b_mod_c (b, a_exp_b_mod_c (a, elgp - x - 1, elgp), elgp).Encode (m, 255); - if (!CryptoPP::SHA256().VerifyDigest (m + 1, m + 33, 222)) - { - LogPrint ("ElGamal decrypt hash doesn't match"); - return false; - } - memcpy (data, m + 33, 222); - return true; - } - - inline void GenerateElGamalKeyPair (CryptoPP::RandomNumberGenerator& rnd, uint8_t * priv, uint8_t * pub) - { -#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER) - rnd.GenerateBlock (priv, 256); - a_exp_b_mod_c (elgg, CryptoPP::Integer (priv, 256), elgp).Encode (pub, 256); -#else - CryptoPP::DH dh (elgp, elgg); - dh.GenerateKeyPair(rnd, priv, pub); -#endif - } -} -} - -#endif diff --git a/Garlic.cpp b/Garlic.cpp index fb67b0ae..f8acbe39 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -2,12 +2,14 @@ #include "I2PEndian.h" #include #include +#include +#include #include "RouterContext.h" #include "I2NPProtocol.h" #include "Tunnel.h" #include "TunnelPool.h" #include "Timestamp.h" -#include "Destination.h" +#include "Log.h" #include "Garlic.h" namespace i2p @@ -17,10 +19,11 @@ namespace garlic GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner, std::shared_ptr destination, int numTags, bool attachLeaseSet): m_Owner (owner), m_Destination (destination), m_NumTags (numTags), - m_LeaseSetUpdateStatus (attachLeaseSet ? eLeaseSetUpdated : eLeaseSetDoNotSend) + m_LeaseSetUpdateStatus (attachLeaseSet ? eLeaseSetUpdated : eLeaseSetDoNotSend), + m_ElGamalEncryption (new i2p::crypto::ElGamalEncryption (destination->GetEncryptionPublicKey ())) { // create new session tags and session key - m_Rnd.GenerateBlock (m_SessionKey, 32); + RAND_bytes (m_SessionKey, 32); m_Encryption.SetKey (m_SessionKey); } @@ -46,7 +49,7 @@ namespace garlic tags->tagsCreationTime = i2p::util::GetSecondsSinceEpoch (); for (int i = 0; i < m_NumTags; i++) { - m_Rnd.GenerateBlock (tags->sessionTags[i], 32); + RAND_bytes (tags->sessionTags[i], 32); tags->sessionTags[i].creationTime = tags->tagsCreationTime; } return tags; @@ -107,7 +110,7 @@ namespace garlic return !m_SessionTags.empty () || m_UnconfirmedTagsMsgs.empty (); } - std::shared_ptr GarlicRoutingSession::WrapSingleMessage (std::shared_ptr msg) + std::shared_ptr GarlicRoutingSession::WrapSingleMessage (std::shared_ptr msg) { auto m = ToSharedI2NPMessage(NewI2NPMessage ()); m->Align (12); // in order to get buf aligned to 16 (12 + 4) @@ -145,10 +148,10 @@ namespace garlic // create ElGamal block ElGamalBlock elGamal; memcpy (elGamal.sessionKey, m_SessionKey, 32); - m_Rnd.GenerateBlock (elGamal.preIV, 32); // Pre-IV + RAND_bytes (elGamal.preIV, 32); // Pre-IV uint8_t iv[32]; // IV is first 16 bytes - CryptoPP::SHA256().CalculateDigest(iv, elGamal.preIV, 32); - m_Destination->GetElGamalEncryption ()->Encrypt ((uint8_t *)&elGamal, sizeof(elGamal), buf, true); + SHA256(elGamal.preIV, 32, iv); + m_ElGamalEncryption->Encrypt ((uint8_t *)&elGamal, sizeof(elGamal), buf, true); m_Encryption.SetIV (iv); buf += 514; len += 514; @@ -158,20 +161,20 @@ namespace garlic // session tag memcpy (buf, tag, 32); uint8_t iv[32]; // IV is first 16 bytes - CryptoPP::SHA256().CalculateDigest(iv, tag, 32); + SHA256(tag, 32, iv); m_Encryption.SetIV (iv); buf += 32; len += 32; } // AES block - len += CreateAESBlock (buf, msg.get ()); // TODO + len += CreateAESBlock (buf, msg); htobe32buf (m->GetPayload (), len); m->len += len + 4; m->FillI2NPMessageHeader (eI2NPGarlic); return m; } - size_t GarlicRoutingSession::CreateAESBlock (uint8_t * buf, const I2NPMessage * msg) + size_t GarlicRoutingSession::CreateAESBlock (uint8_t * buf, std::shared_ptr msg) { size_t blockSize = 0; bool createNewTags = m_Owner && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags*2/3); @@ -194,7 +197,7 @@ namespace garlic blockSize++; size_t len = CreateGarlicPayload (buf + blockSize, msg, newTags); htobe32buf (payloadSize, len); - CryptoPP::SHA256().CalculateDigest(payloadHash, buf + blockSize, len); + SHA256(buf + blockSize, len, payloadHash); blockSize += len; size_t rem = blockSize % 16; if (rem) @@ -203,10 +206,11 @@ namespace garlic return blockSize; } - size_t GarlicRoutingSession::CreateGarlicPayload (uint8_t * payload, const I2NPMessage * msg, UnconfirmedTags * newTags) + size_t GarlicRoutingSession::CreateGarlicPayload (uint8_t * payload, std::shared_ptr msg, UnconfirmedTags * newTags) { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 sec - uint32_t msgID = m_Rnd.GenerateWord32 (); + uint32_t msgID; + RAND_bytes ((uint8_t *)&msgID, 4); size_t size = 0; uint8_t * numCloves = payload + size; *numCloves = 0; @@ -243,8 +247,7 @@ namespace garlic m_LeaseSetSubmissionTime = i2p::util::GetMillisecondsSinceEpoch (); // clove if our leaseSet must be attached auto leaseSet = CreateDatabaseStoreMsg (m_Owner->GetLeaseSet ()); - size += CreateGarlicClove (payload + size, leaseSet, false); - DeleteI2NPMessage (leaseSet); + size += CreateGarlicClove (payload + size, leaseSet, false); (*numCloves)++; } } @@ -263,7 +266,7 @@ namespace garlic return size; } - size_t GarlicRoutingSession::CreateGarlicClove (uint8_t * buf, const I2NPMessage * msg, bool isDestination) + size_t GarlicRoutingSession::CreateGarlicClove (uint8_t * buf, std::shared_ptr msg, bool isDestination) { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 sec size_t size = 0; @@ -282,7 +285,9 @@ namespace garlic memcpy (buf + size, msg->GetBuffer (), msg->GetLength ()); size += msg->GetLength (); - htobe32buf (buf + size, m_Rnd.GenerateWord32 ()); // CloveID + uint32_t cloveID; + RAND_bytes ((uint8_t *)&cloveID, 4); + htobe32buf (buf + size, cloveID); // CloveID size += 4; htobe64buf (buf + size, ts); // Expiration of clove size += 8; @@ -312,8 +317,8 @@ namespace garlic { //encrypt uint8_t key[32], tag[32]; - m_Rnd.GenerateBlock (key, 32); // random session key - m_Rnd.GenerateBlock (tag, 32); // random session tag + RAND_bytes (key, 32); // random session key + RAND_bytes (tag, 32); // random session tag m_Owner->SubmitSessionKey (key, tag); GarlicRoutingSession garlic (key, tag); msg = garlic.WrapSingleMessage (msg); @@ -322,7 +327,9 @@ namespace garlic size += msg->GetLength (); // fill clove uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 sec - htobe32buf (buf + size, m_Rnd.GenerateWord32 ()); // CloveID + uint32_t cloveID; + RAND_bytes ((uint8_t *)&cloveID, 4); + htobe32buf (buf + size, cloveID); // CloveID size += 4; htobe64buf (buf + size, ts); // Expiration of clove size += 8; @@ -376,7 +383,7 @@ namespace garlic if (length >= 32) { uint8_t iv[32]; // IV is first 16 bytes - CryptoPP::SHA256().CalculateDigest(iv, buf, 32); + SHA256(buf, 32, iv); it->second->SetIV (iv); it->second->Decrypt (buf + 32, length - 32, buf + 32); HandleAESBlock (buf + 32, length - 32, it->second, msg->from); @@ -394,7 +401,7 @@ namespace garlic auto decryption = std::make_shared(); decryption->SetKey (elGamal.sessionKey); uint8_t iv[32]; // IV is first 16 bytes - CryptoPP::SHA256().CalculateDigest(iv, elGamal.preIV, 32); + SHA256(elGamal.preIV, 32, iv); decryption->SetIV (iv); decryption->Decrypt(buf + 514, length - 514, buf + 514); HandleAESBlock (buf + 514, length - 514, decryption, msg->from); @@ -458,7 +465,9 @@ namespace garlic buf++; // flag // payload - if (!CryptoPP::SHA256().VerifyDigest (payloadHash, buf, payloadSize)) // payload hash doesn't match + uint8_t digest[32]; + SHA256 (buf, payloadSize, digest); + if (memcmp (payloadHash, digest, 32)) // payload hash doesn't match { LogPrint ("Wrong payload hash"); return; diff --git a/Garlic.h b/Garlic.h index 580cabec..716bc466 100644 --- a/Garlic.h +++ b/Garlic.h @@ -8,8 +8,7 @@ #include #include #include -#include -#include "aes.h" +#include "Crypto.h" #include "I2NPProtocol.h" #include "LeaseSet.h" #include "Queue.h" @@ -80,7 +79,7 @@ namespace garlic int numTags, bool attachLeaseSet); GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption ~GarlicRoutingSession (); - std::shared_ptr WrapSingleMessage (std::shared_ptr msg); + std::shared_ptr WrapSingleMessage (std::shared_ptr msg); void MessageConfirmed (uint32_t msgID); bool CleanupExpiredTags (); // returns true if something left @@ -91,9 +90,9 @@ namespace garlic private: - size_t CreateAESBlock (uint8_t * buf, const I2NPMessage * msg); - size_t CreateGarlicPayload (uint8_t * payload, const I2NPMessage * msg, UnconfirmedTags * newTags); - size_t CreateGarlicClove (uint8_t * buf, const I2NPMessage * msg, bool isDestination); + size_t CreateAESBlock (uint8_t * buf, std::shared_ptr msg); + size_t CreateGarlicPayload (uint8_t * payload, std::shared_ptr msg, UnconfirmedTags * newTags); + size_t CreateGarlicClove (uint8_t * buf, std::shared_ptr msg, bool isDestination); size_t CreateDeliveryStatusClove (uint8_t * buf, uint32_t msgID); void TagsConfirmed (uint32_t msgID); @@ -113,7 +112,7 @@ namespace garlic uint64_t m_LeaseSetSubmissionTime; // in milliseconds i2p::crypto::CBCEncryption m_Encryption; - CryptoPP::AutoSeededRandomPool m_Rnd; + std::unique_ptr m_ElGamalEncryption; }; class GarlicDestination: public i2p::data::LocalDestination diff --git a/HTTPServer.cpp b/HTTPServer.cpp index c9048727..575b06b8 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -1,7 +1,7 @@ #include #include #include -#include "base64.h" +#include "Base.h" #include "Log.h" #include "Tunnel.h" #include "TransitTunnel.h" @@ -466,7 +466,8 @@ namespace util const char HTTP_COMMAND_TRANSIT_TUNNELS[] = "transit_tunnels"; const char HTTP_COMMAND_TRANSPORTS[] = "transports"; const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels"; - const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels"; + const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels"; + const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; const char HTTP_COMMAND_LOCAL_DESTINATIONS[] = "local_destinations"; const char HTTP_COMMAND_LOCAL_DESTINATION[] = "local_destination"; const char HTTP_PARAM_BASE32_ADDRESS[] = "b32"; @@ -702,6 +703,7 @@ namespace util s << "
Stop accepting tunnels
"; else s << "
Start accepting tunnels
"; + s << "
Run peer test
"; s << "

Flibusta

"; } @@ -720,6 +722,8 @@ namespace util StartAcceptingTunnels (s); else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS) StopAcceptingTunnels (s); + else if (cmd == HTTP_COMMAND_RUN_PEER_TEST) + RunPeerTest (s); else if (cmd == HTTP_COMMAND_LOCAL_DESTINATIONS) ShowLocalDestinations (s); else if (cmd == HTTP_COMMAND_LOCAL_DESTINATION) @@ -751,11 +755,10 @@ namespace util if (it.second && it.second->IsEstablished ()) { // incoming connection doesn't have remote RI - auto outgoing = it.second->GetRemoteRouter (); - if (outgoing) s << "-->"; - s << it.second->GetRemoteIdentity ().GetIdentHash ().ToBase64 ().substr (0, 4) << ": " + if (it.second->IsOutgoing ()) s << "-->"; + s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " << it.second->GetSocket ().remote_endpoint().address ().to_string (); - if (!outgoing) s << "-->"; + if (!it.second->IsOutgoing ()) s << "-->"; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; s << "
"; } @@ -769,11 +772,10 @@ namespace util for (auto it: ssuServer->GetSessions ()) { // incoming connections don't have remote router - auto outgoing = it.second->GetRemoteRouter (); auto endpoint = it.second->GetRemoteEndpoint (); - if (outgoing) s << "-->"; + if (it.second->IsOutgoing ()) s << "-->"; s << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!outgoing) s << "-->"; + if (!it.second->IsOutgoing ()) s << "-->"; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; if (it.second->GetRelayTag ()) s << " [itag:" << it.second->GetRelayTag () << "]"; @@ -789,7 +791,7 @@ namespace util for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) { - it->GetTunnelConfig ()->Print (s); + it->Print (s); auto state = it->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; @@ -801,7 +803,7 @@ namespace util for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ()) { - it.second->GetTunnelConfig ()->Print (s); + it.second->Print (s); auto state = it.second->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; @@ -844,7 +846,7 @@ namespace util auto dest = i2p::client::context.FindLocalDestination (ident); if (dest) { - s << "Base64:
" << dest->GetIdentity ().ToBase64 () << "

"; + s << "Base64:
" << dest->GetIdentity ()->ToBase64 () << "

"; s << "LeaseSets: " << dest->GetNumRemoteLeaseSets () << "
"; auto pool = dest->GetTunnelPool (); if (pool) @@ -852,7 +854,7 @@ namespace util s << "Tunnels:
"; for (auto it: pool->GetOutboundTunnels ()) { - it->GetTunnelConfig ()->Print (s); + it->Print (s); auto state = it->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; @@ -862,7 +864,7 @@ namespace util } for (auto it: pool->GetInboundTunnels ()) { - it->GetTunnelConfig ()->Print (s); + it->Print (s); auto state = it->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; @@ -948,6 +950,12 @@ namespace util s << "Accepting tunnels stopped" << std::endl; } + void HTTPConnection::RunPeerTest (std::stringstream& s) + { + i2p::transport::transports.PeerTest (); + s << "Peer test" << std::endl; + } + void HTTPConnection::HandleDestinationRequest (const std::string& address, const std::string& uri) { std::string request = "GET " + uri + " HTTP/1.1\r\nHost:" + address + "\r\n"; @@ -1044,7 +1052,7 @@ namespace util HTTPServer::HTTPServer (int port): m_Thread (nullptr), m_Work (m_Service), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4 (), port)), m_NewSocket (nullptr) { diff --git a/HTTPServer.h b/HTTPServer.h index b289cbc5..977938fb 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -73,6 +73,7 @@ namespace util void ShowSAMSession (const std::string& id, std::stringstream& s); void StartAcceptingTunnels (std::stringstream& s); void StopAcceptingTunnels (std::stringstream& s); + void RunPeerTest (std::stringstream& s); void FillContent (std::stringstream& s); std::string ExtractAddress (); void ExtractParams (const std::string& str, std::map& params); diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 8fd493f1..890ec6af 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -1,13 +1,15 @@ #include #include +#include +#include +#include "Base.h" +#include "Log.h" +#include "Crypto.h" #include "I2PEndian.h" -#include -#include "ElGamal.h" #include "Timestamp.h" #include "RouterContext.h" #include "NetDb.h" #include "Tunnel.h" -#include "base64.h" #include "Transports.h" #include "Garlic.h" #include "I2NPProtocol.h" @@ -44,10 +46,8 @@ namespace i2p void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID) { SetTypeID (msgType); - if (replyMsgID) // for tunnel creation - SetMsgID (replyMsgID); - else - SetMsgID (i2p::context.GetRandomNumberGenerator ().GenerateWord32 ()); + if (!replyMsgID) RAND_bytes ((uint8_t *)&replyMsgID, 4); + SetMsgID (replyMsgID); SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); // TODO: 5 secs is a magic number UpdateSize (); UpdateChks (); @@ -55,7 +55,9 @@ namespace i2p void I2NPMessage::RenewI2NPMessageHeader () { - SetMsgID (i2p::context.GetRandomNumberGenerator ().GenerateWord32 ()); + uint32_t msgID; + RAND_bytes ((uint8_t *)&msgID, 4); + SetMsgID (msgID); SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); } @@ -98,7 +100,8 @@ namespace i2p } else // for SSU establishment { - htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, i2p::context.GetRandomNumberGenerator ().GenerateWord32 ()); + RAND_bytes ((uint8_t *)&msgID, 4); + htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID); htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, 2); // netID = 2 } m->len += DELIVERY_STATUS_SIZE; @@ -106,10 +109,10 @@ namespace i2p return ToSharedI2NPMessage (m); } - I2NPMessage * CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, + std::shared_ptr CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, uint32_t replyTunnelID, bool exploratory, std::set * excludedPeers) { - I2NPMessage * m = excludedPeers ? NewI2NPMessage () : NewI2NPShortMessage (); + auto m = ToSharedI2NPMessage (excludedPeers ? NewI2NPMessage () : NewI2NPShortMessage ()); uint8_t * buf = m->GetPayload (); memcpy (buf, key, 32); // key buf += 32; @@ -151,12 +154,12 @@ namespace i2p return m; } - I2NPMessage * CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, + std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, const std::set& excludedFloodfills, const i2p::tunnel::InboundTunnel * replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag) { int cnt = excludedFloodfills.size (); - I2NPMessage * m = cnt > 0 ? NewI2NPMessage () : NewI2NPShortMessage (); + auto m = ToSharedI2NPMessage (cnt > 0 ? NewI2NPMessage () : NewI2NPShortMessage ()); uint8_t * buf = m->GetPayload (); memcpy (buf, dest, 32); // key buf += 32; @@ -188,10 +191,10 @@ namespace i2p return m; } - I2NPMessage * CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, + std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers) { - I2NPMessage * m = NewI2NPShortMessage (); + auto m = ToSharedI2NPMessage (NewI2NPShortMessage ()); uint8_t * buf = m->GetPayload (); size_t len = 0; memcpy (buf, ident, 32); @@ -210,12 +213,12 @@ namespace i2p return m; } - I2NPMessage * CreateDatabaseStoreMsg (std::shared_ptr router, uint32_t replyToken) + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router, uint32_t replyToken) { if (!router) // we send own RouterInfo router = context.GetSharedRouterInfo (); - I2NPMessage * m = NewI2NPShortMessage (); + auto m = ToSharedI2NPMessage (NewI2NPShortMessage ()); uint8_t * payload = m->GetPayload (); memcpy (payload + DATABASE_STORE_KEY_OFFSET, router->GetIdentHash (), 32); @@ -230,33 +233,27 @@ namespace i2p buf += 32; } - CryptoPP::Gzip compressor; - compressor.Put (router->GetBuffer (), router->GetBufferLen ()); - compressor.MessageEnd(); - auto size = compressor.MaxRetrievable (); - htobe16buf (buf, size); // size + uint8_t * sizePtr = buf; buf += 2; m->len += (buf - payload); // payload size - if (m->len + size > m->maxLen) + i2p::data::GzipDeflator deflator; + size_t size = deflator.Deflate (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); + if (size) { - LogPrint (eLogInfo, "DatabaseStore message size is not enough for ", m->len + size); - auto newMsg = NewI2NPMessage (); - *newMsg = *m; - DeleteI2NPMessage (m); - m = newMsg; - buf = m->buf + m->len; + htobe16buf (sizePtr, size); // size + m->len += size; } - compressor.Get (buf, size); - m->len += size; - m->FillI2NPMessageHeader (eI2NPDatabaseStore); - + else + m = nullptr; + if (m) + m->FillI2NPMessageHeader (eI2NPDatabaseStore); return m; } - I2NPMessage * CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken) + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken) { if (!leaseSet) return nullptr; - I2NPMessage * m = NewI2NPShortMessage (); + auto m = ToSharedI2NPMessage (NewI2NPShortMessage ()); uint8_t * payload = m->GetPayload (); memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32); payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet @@ -313,8 +310,8 @@ namespace i2p record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 30; // always reject with bandwidth reason (30) //TODO: fill filler - CryptoPP::SHA256().CalculateDigest(record + BUILD_RESPONSE_RECORD_HASH_OFFSET, - record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1); // + 1 byte of ret + SHA256 (record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1, // + 1 byte of ret + record + BUILD_RESPONSE_RECORD_HASH_OFFSET); // encrypt reply i2p::crypto::CBCEncryption encryption; for (int j = 0; j < num; j++) diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 0e6f5621..a4840217 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include "I2PEndian.h" #include "Identity.h" #include "RouterInfo.h" @@ -132,7 +132,7 @@ namespace tunnel void UpdateChks () { uint8_t hash[32]; - CryptoPP::SHA256().CalculateDigest(hash, GetPayload (), GetPayloadLength ()); + SHA256(GetPayload (), GetPayloadLength (), hash); GetHeader ()[I2NP_HEADER_CHKS_OFFSET] = hash[0]; } @@ -206,15 +206,15 @@ namespace tunnel std::shared_ptr CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from = nullptr); std::shared_ptr CreateDeliveryStatusMsg (uint32_t msgID); - I2NPMessage * CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, + std::shared_ptr CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, uint32_t replyTunnelID, bool exploratory = false, std::set * excludedPeers = nullptr); - I2NPMessage * CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, + std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, const std::set& excludedFloodfills, const i2p::tunnel::InboundTunnel * replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag); - I2NPMessage * CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); + std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); - I2NPMessage * CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0); - I2NPMessage * CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0); + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0); + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0); bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); diff --git a/I2PService.h b/I2PService.h index 37c06828..afac4ea4 100644 --- a/I2PService.h +++ b/I2PService.h @@ -83,11 +83,11 @@ namespace client public: TCPIPAcceptor (int port, std::shared_ptr localDestination = nullptr) : I2PService(localDestination), - m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), + m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4 (), port)), m_Timer (GetService ()) {} TCPIPAcceptor (int port, i2p::data::SigningKeyType kt) : I2PService(kt), - m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port)), + m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4 (), port)), m_Timer (GetService ()) {} virtual ~TCPIPAcceptor () { TCPIPAcceptor::Stop(); } //If you override this make sure you call it from the children diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index a33a536d..dd2c31df 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -1,5 +1,5 @@ #include -#include "base64.h" +#include "Base.h" #include "Log.h" #include "Destination.h" #include "ClientContext.h" @@ -153,7 +153,7 @@ namespace client else { // send destination first like received from I2P - std::string dest = m_Stream->GetRemoteIdentity ().ToBase64 (); + std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 (); dest += "\n"; memcpy (m_StreamBuffer, dest.c_str (), dest.size ()); HandleStreamReceive (boost::system::error_code (), dest.size ()); @@ -369,9 +369,9 @@ namespace client { if (m_IsAccessList) { - if (!m_AccessList.count (stream->GetRemoteIdentity ().GetIdentHash ())) + if (!m_AccessList.count (stream->GetRemoteIdentity ()->GetIdentHash ())) { - LogPrint (eLogWarning, "Address ", stream->GetRemoteIdentity ().GetIdentHash ().ToBase32 (), " is not in white list. Incoming connection dropped"); + LogPrint (eLogWarning, "Address ", stream->GetRemoteIdentity ()->GetIdentHash ().ToBase32 (), " is not in white list. Incoming connection dropped"); stream->Close (); return; } diff --git a/Identity.cpp b/Identity.cpp index 9dc96d01..78aeedd3 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -1,14 +1,12 @@ #include #include -#include -#include -#include -#include "base64.h" -#include "CryptoConst.h" -#include "ElGamal.h" -#include "RouterContext.h" -#include "Identity.h" +#include +#include +#include +#include "Crypto.h" #include "I2PEndian.h" +#include "Log.h" +#include "Identity.h" namespace i2p { @@ -31,12 +29,12 @@ namespace data IdentHash Identity::Hash () const { IdentHash hash; - CryptoPP::SHA256().CalculateDigest(hash, publicKey, DEFAULT_IDENTITY_SIZE); + SHA256(publicKey, DEFAULT_IDENTITY_SIZE, hash); return hash; } IdentityEx::IdentityEx (): - m_Verifier (nullptr), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { } @@ -52,14 +50,14 @@ namespace data case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: { size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64 - i2p::context.GetRandomNumberGenerator ().GenerateBlock (m_StandardIdentity.signingKey, padding); + RAND_bytes (m_StandardIdentity.signingKey, padding); memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP256_KEY_LENGTH); break; } case SIGNING_KEY_TYPE_ECDSA_SHA384_P384: { size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96 - i2p::context.GetRandomNumberGenerator ().GenerateBlock (m_StandardIdentity.signingKey, padding); + RAND_bytes (m_StandardIdentity.signingKey, padding); memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::ECDSAP384_KEY_LENGTH); break; } @@ -98,7 +96,7 @@ namespace data case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: { size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 - i2p::context.GetRandomNumberGenerator ().GenerateBlock (m_StandardIdentity.signingKey, padding); + RAND_bytes (m_StandardIdentity.signingKey, padding); memcpy (m_StandardIdentity.signingKey + padding, signingKey, i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH); break; } @@ -121,7 +119,7 @@ namespace data // calculate ident hash uint8_t * buf = new uint8_t[GetFullLen ()]; ToBuffer (buf, GetFullLen ()); - CryptoPP::SHA256().CalculateDigest(m_IdentHash, buf, GetFullLen ()); + SHA256(buf, GetFullLen (), m_IdentHash); delete[] buf; } else // DSA-SHA1 @@ -136,20 +134,25 @@ namespace data } IdentityEx::IdentityEx (const uint8_t * buf, size_t len): - m_Verifier (nullptr), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { FromBuffer (buf, len); } IdentityEx::IdentityEx (const IdentityEx& other): - m_Verifier (nullptr), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { *this = other; } + + IdentityEx::IdentityEx (const Identity& standard): + m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + { + *this = standard; + } IdentityEx::~IdentityEx () { - delete m_Verifier; delete[] m_ExtendedBuffer; } @@ -168,7 +171,6 @@ namespace data else m_ExtendedBuffer = nullptr; - delete m_Verifier; m_Verifier = nullptr; return *this; @@ -183,7 +185,6 @@ namespace data m_ExtendedBuffer = nullptr; m_ExtendedLen = 0; - delete m_Verifier; m_Verifier = nullptr; return *this; @@ -218,9 +219,8 @@ namespace data m_ExtendedLen = 0; m_ExtendedBuffer = nullptr; } - CryptoPP::SHA256().CalculateDigest(m_IdentHash, buf, GetFullLen ()); + SHA256(buf, GetFullLen (), m_IdentHash); - delete m_Verifier; m_Verifier = nullptr; return GetFullLen (); @@ -302,18 +302,18 @@ namespace data switch (keyType) { case SIGNING_KEY_TYPE_DSA_SHA1: - m_Verifier = new i2p::crypto::DSAVerifier (m_StandardIdentity.signingKey); + m_Verifier.reset (new i2p::crypto::DSAVerifier (m_StandardIdentity.signingKey)); break; case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: { size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64 - m_Verifier = new i2p::crypto::ECDSAP256Verifier (m_StandardIdentity.signingKey + padding); + m_Verifier.reset (new i2p::crypto::ECDSAP256Verifier (m_StandardIdentity.signingKey + padding)); break; } case SIGNING_KEY_TYPE_ECDSA_SHA384_P384: { size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96 - m_Verifier = new i2p::crypto::ECDSAP384Verifier (m_StandardIdentity.signingKey + padding); + m_Verifier.reset (new i2p::crypto::ECDSAP384Verifier (m_StandardIdentity.signingKey + padding)); break; } case SIGNING_KEY_TYPE_ECDSA_SHA512_P521: @@ -322,7 +322,7 @@ namespace data memcpy (signingKey, m_StandardIdentity.signingKey, 128); size_t excessLen = i2p::crypto::ECDSAP521_KEY_LENGTH - 128; // 4 = 132- 128 memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - m_Verifier = new i2p::crypto::ECDSAP521Verifier (signingKey); + m_Verifier.reset (new i2p::crypto::ECDSAP521Verifier (signingKey)); break; } case SIGNING_KEY_TYPE_RSA_SHA256_2048: @@ -331,7 +331,7 @@ namespace data memcpy (signingKey, m_StandardIdentity.signingKey, 128); size_t excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256- 128 memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - m_Verifier = new i2p::crypto:: RSASHA2562048Verifier (signingKey); + m_Verifier.reset (new i2p::crypto:: RSASHA2562048Verifier (signingKey)); break; } case SIGNING_KEY_TYPE_RSA_SHA384_3072: @@ -340,7 +340,7 @@ namespace data memcpy (signingKey, m_StandardIdentity.signingKey, 128); size_t excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384- 128 memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - m_Verifier = new i2p::crypto:: RSASHA3843072Verifier (signingKey); + m_Verifier.reset (new i2p::crypto:: RSASHA3843072Verifier (signingKey)); break; } case SIGNING_KEY_TYPE_RSA_SHA512_4096: @@ -349,13 +349,13 @@ namespace data memcpy (signingKey, m_StandardIdentity.signingKey, 128); size_t excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512- 128 memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - m_Verifier = new i2p::crypto:: RSASHA5124096Verifier (signingKey); + m_Verifier.reset (new i2p::crypto:: RSASHA5124096Verifier (signingKey)); break; } case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: { size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 - m_Verifier = new i2p::crypto::EDDSA25519Verifier (m_StandardIdentity.signingKey + padding); + m_Verifier.reset (new i2p::crypto::EDDSA25519Verifier (m_StandardIdentity.signingKey + padding)); break; } default: @@ -363,19 +363,16 @@ namespace data } } - void IdentityEx::DropVerifier () + void IdentityEx::DropVerifier () const { - auto verifier = m_Verifier; - m_Verifier = nullptr; // TODO: make this atomic - delete verifier; + m_Verifier = nullptr; } PrivateKeys& PrivateKeys::operator=(const Keys& keys) { - m_Public = Identity (keys); + m_Public = std::make_shared(Identity (keys)); memcpy (m_PrivateKey, keys.privateKey, 256); // 256 - memcpy (m_SigningPrivateKey, keys.signingPrivateKey, m_Public.GetSigningPrivateKeyLen ()); - delete m_Signer; + memcpy (m_SigningPrivateKey, keys.signingPrivateKey, m_Public->GetSigningPrivateKeyLen ()); m_Signer = nullptr; CreateSigner (); return *this; @@ -383,10 +380,9 @@ namespace data PrivateKeys& PrivateKeys::operator=(const PrivateKeys& other) { - m_Public = other.m_Public; + m_Public = std::make_shared(*other.m_Public); memcpy (m_PrivateKey, other.m_PrivateKey, 256); // 256 - memcpy (m_SigningPrivateKey, other.m_SigningPrivateKey, m_Public.GetSigningPrivateKeyLen ()); - delete m_Signer; + memcpy (m_SigningPrivateKey, other.m_SigningPrivateKey, m_Public->GetSigningPrivateKeyLen ()); m_Signer = nullptr; CreateSigner (); return *this; @@ -394,13 +390,13 @@ namespace data size_t PrivateKeys::FromBuffer (const uint8_t * buf, size_t len) { - size_t ret = m_Public.FromBuffer (buf, len); + m_Public = std::make_shared(buf, len); + size_t ret = m_Public->GetFullLen (); memcpy (m_PrivateKey, buf + ret, 256); // private key always 256 ret += 256; - size_t signingPrivateKeySize = m_Public.GetSigningPrivateKeyLen (); + size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen (); memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize); ret += signingPrivateKeySize; - delete m_Signer; m_Signer = nullptr; CreateSigner (); return ret; @@ -408,10 +404,10 @@ namespace data size_t PrivateKeys::ToBuffer (uint8_t * buf, size_t len) const { - size_t ret = m_Public.ToBuffer (buf, len); + size_t ret = m_Public->ToBuffer (buf, len); memcpy (buf + ret, m_PrivateKey, 256); // private key always 256 ret += 256; - size_t signingPrivateKeySize = m_Public.GetSigningPrivateKeyLen (); + size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen (); memcpy (buf + ret, m_SigningPrivateKey, signingPrivateKeySize); ret += signingPrivateKeySize; return ret; @@ -442,39 +438,39 @@ namespace data void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const { if (m_Signer) - m_Signer->Sign (i2p::context.GetRandomNumberGenerator (), buf, len, signature); + m_Signer->Sign (buf, len, signature); } void PrivateKeys::CreateSigner () { - switch (m_Public.GetSigningKeyType ()) + switch (m_Public->GetSigningKeyType ()) { case SIGNING_KEY_TYPE_DSA_SHA1: - m_Signer = new i2p::crypto::DSASigner (m_SigningPrivateKey); + m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey)); break; case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: - m_Signer = new i2p::crypto::ECDSAP256Signer (m_SigningPrivateKey); + m_Signer.reset (new i2p::crypto::ECDSAP256Signer (m_SigningPrivateKey)); break; case SIGNING_KEY_TYPE_ECDSA_SHA384_P384: - m_Signer = new i2p::crypto::ECDSAP384Signer (m_SigningPrivateKey); + m_Signer.reset (new i2p::crypto::ECDSAP384Signer (m_SigningPrivateKey)); break; case SIGNING_KEY_TYPE_ECDSA_SHA512_P521: - m_Signer = new i2p::crypto::ECDSAP521Signer (m_SigningPrivateKey); + m_Signer.reset (new i2p::crypto::ECDSAP521Signer (m_SigningPrivateKey)); break; case SIGNING_KEY_TYPE_RSA_SHA256_2048: - m_Signer = new i2p::crypto::RSASHA2562048Signer (m_SigningPrivateKey); + m_Signer.reset (new i2p::crypto::RSASHA2562048Signer (m_SigningPrivateKey)); break; case SIGNING_KEY_TYPE_RSA_SHA384_3072: - m_Signer = new i2p::crypto::RSASHA3843072Signer (m_SigningPrivateKey); + m_Signer.reset (new i2p::crypto::RSASHA3843072Signer (m_SigningPrivateKey)); break; case SIGNING_KEY_TYPE_RSA_SHA512_4096: - m_Signer = new i2p::crypto::RSASHA5124096Signer (m_SigningPrivateKey); + m_Signer.reset (new i2p::crypto::RSASHA5124096Signer (m_SigningPrivateKey)); break; case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: - m_Signer = new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey); + m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey)); break; default: - LogPrint ("Signing key type ", (int)m_Public.GetSigningKeyType (), " is not supported"); + LogPrint ("Signing key type ", (int)m_Public->GetSigningKeyType (), " is not supported"); } } @@ -483,39 +479,40 @@ namespace data if (type != SIGNING_KEY_TYPE_DSA_SHA1) { PrivateKeys keys; - auto& rnd = i2p::context.GetRandomNumberGenerator (); // signature uint8_t signingPublicKey[512]; // signing public key is 512 bytes max switch (type) { case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: - i2p::crypto::CreateECDSAP256RandomKeys (rnd, keys.m_SigningPrivateKey, signingPublicKey); + i2p::crypto::CreateECDSAP256RandomKeys (keys.m_SigningPrivateKey, signingPublicKey); break; case SIGNING_KEY_TYPE_ECDSA_SHA384_P384: - i2p::crypto::CreateECDSAP384RandomKeys (rnd, keys.m_SigningPrivateKey, signingPublicKey); + i2p::crypto::CreateECDSAP384RandomKeys (keys.m_SigningPrivateKey, signingPublicKey); break; case SIGNING_KEY_TYPE_ECDSA_SHA512_P521: - i2p::crypto::CreateECDSAP521RandomKeys (rnd, keys.m_SigningPrivateKey, signingPublicKey); + i2p::crypto::CreateECDSAP521RandomKeys (keys.m_SigningPrivateKey, signingPublicKey); break; case SIGNING_KEY_TYPE_RSA_SHA256_2048: - i2p::crypto::CreateRSARandomKeys (rnd, i2p::crypto::RSASHA2562048_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey); + i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA2562048_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey); break; case SIGNING_KEY_TYPE_RSA_SHA384_3072: - i2p::crypto::CreateRSARandomKeys (rnd, i2p::crypto::RSASHA3843072_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey); + i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA3843072_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey); break; case SIGNING_KEY_TYPE_RSA_SHA512_4096: - i2p::crypto::CreateRSARandomKeys (rnd, i2p::crypto::RSASHA5124096_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey); + i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA5124096_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey); break; + case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: + i2p::crypto::CreateEDDSA25519RandomKeys (keys.m_SigningPrivateKey, signingPublicKey); + break; default: LogPrint ("Signing key type ", (int)type, " is not supported. Create DSA-SHA1"); return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1 } // encryption uint8_t publicKey[256]; - CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg); - dh.GenerateKeyPair(rnd, keys.m_PrivateKey, publicKey); + i2p::crypto::GenerateElGamalKeyPair (keys.m_PrivateKey, publicKey); // identity - keys.m_Public = IdentityEx (publicKey, signingPublicKey, type); + keys.m_Public = std::make_shared (publicKey, signingPublicKey, type); keys.CreateSigner (); return keys; @@ -526,11 +523,10 @@ namespace data Keys CreateRandomKeys () { Keys keys; - auto& rnd = i2p::context.GetRandomNumberGenerator (); // encryption - i2p::crypto::GenerateElGamalKeyPair(rnd, keys.privateKey, keys.publicKey); + i2p::crypto::GenerateElGamalKeyPair(keys.privateKey, keys.publicKey); // signing - i2p::crypto::CreateDSARandomKeys (rnd, keys.signingPrivateKey, keys.signingKey); + i2p::crypto::CreateDSARandomKeys (keys.signingPrivateKey, keys.signingKey); return keys; } @@ -548,7 +544,7 @@ namespace data sprintf((char *)(buf + 32), "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); #endif IdentHash key; - CryptoPP::SHA256().CalculateDigest((uint8_t *)key, buf, 40); + SHA256(buf, 40, key); return key; } diff --git a/Identity.h b/Identity.h index 632c414a..1e7316af 100644 --- a/Identity.h +++ b/Identity.h @@ -5,84 +5,18 @@ #include #include #include -#include "base64.h" -#include "ElGamal.h" +#include "Base.h" #include "Signature.h" namespace i2p { namespace data { - template - class Tag - { - public: - - Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); }; - Tag (const Tag& ) = default; -#ifndef _WIN32 // FIXME!!! msvs 2013 can't compile it - Tag (Tag&& ) = default; -#endif - Tag () = default; - - Tag& operator= (const Tag& ) = default; -#ifndef _WIN32 - Tag& operator= (Tag&& ) = default; -#endif - - uint8_t * operator()() { return m_Buf; }; - const uint8_t * operator()() const { return m_Buf; }; - - operator uint8_t * () { return m_Buf; }; - operator const uint8_t * () const { return m_Buf; }; - - const uint64_t * GetLL () const { return ll; }; - - bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); }; - bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; }; - - bool IsZero () const - { - for (int i = 0; i < sz/8; i++) - if (ll[i]) return false; - return true; - } - - std::string ToBase64 () const - { - char str[sz*2]; - int l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2); - str[l] = 0; - return std::string (str); - } - - std::string ToBase32 () const - { - char str[sz*2]; - int l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2); - str[l] = 0; - return std::string (str); - } - - void FromBase32 (const std::string& s) - { - i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); - } - - void FromBase64 (const std::string& s) - { - i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); - } - - private: - - union // 8 bytes alignment - { - uint8_t m_Buf[sz]; - uint64_t ll[sz/8]; - }; - }; typedef Tag<32> IdentHash; + inline std::string GetIdentHashAbbreviation (const IdentHash& ident) + { + return ident.ToBase64 ().substr (0, 4); + } #pragma pack(1) struct Keys @@ -142,6 +76,7 @@ namespace data SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1); IdentityEx (const uint8_t * buf, size_t len); IdentityEx (const IdentityEx& other); + IdentityEx (const Identity& standard); ~IdentityEx (); IdentityEx& operator=(const IdentityEx& other); IdentityEx& operator=(const Identity& standard); @@ -152,6 +87,7 @@ namespace data std::string ToBase64 () const; const Identity& GetStandardIdentity () const { return m_StandardIdentity; }; const IdentHash& GetIdentHash () const { return m_IdentHash; }; + const uint8_t * GetEncryptionPublicKey () const { return m_StandardIdentity.publicKey; }; size_t GetFullLen () const { return m_ExtendedLen + DEFAULT_IDENTITY_SIZE; }; size_t GetSigningPublicKeyLen () const; size_t GetSigningPrivateKeyLen () const; @@ -159,7 +95,7 @@ namespace data bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; SigningKeyType GetSigningKeyType () const; CryptoKeyType GetCryptoKeyType () const; - void DropVerifier (); // to save memory + void DropVerifier () const; // to save memory private: @@ -169,7 +105,7 @@ namespace data Identity m_StandardIdentity; IdentHash m_IdentHash; - mutable i2p::crypto::Verifier * m_Verifier; + mutable std::unique_ptr m_Verifier; size_t m_ExtendedLen; uint8_t * m_ExtendedBuffer; }; @@ -178,19 +114,19 @@ namespace data { public: - PrivateKeys (): m_Signer (nullptr) {}; - PrivateKeys (const PrivateKeys& other): m_Signer (nullptr) { *this = other; }; - PrivateKeys (const Keys& keys): m_Signer (nullptr) { *this = keys; }; + PrivateKeys () = default; + PrivateKeys (const PrivateKeys& other) { *this = other; }; + PrivateKeys (const Keys& keys) { *this = keys; }; PrivateKeys& operator=(const Keys& keys); PrivateKeys& operator=(const PrivateKeys& other); - ~PrivateKeys () { delete m_Signer; }; + ~PrivateKeys () = default; - const IdentityEx& GetPublic () const { return m_Public; }; + std::shared_ptr GetPublic () const { return m_Public; }; const uint8_t * GetPrivateKey () const { return m_PrivateKey; }; const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; }; void Sign (const uint8_t * buf, int len, uint8_t * signature) const; - size_t GetFullLen () const { return m_Public.GetFullLen () + 256 + m_Public.GetSigningPrivateKeyLen (); }; + size_t GetFullLen () const { return m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen (); }; size_t FromBuffer (const uint8_t * buf, size_t len); size_t ToBuffer (uint8_t * buf, size_t len) const; @@ -205,10 +141,10 @@ namespace data private: - IdentityEx m_Public; + std::shared_ptr m_Public; uint8_t m_PrivateKey[256]; uint8_t m_SigningPrivateKey[1024]; // assume private key doesn't exceed 1024 bytes - i2p::crypto::Signer * m_Signer; + std::unique_ptr m_Signer; }; // kademlia @@ -239,17 +175,6 @@ namespace data virtual const IdentHash& GetIdentHash () const = 0; virtual const uint8_t * GetEncryptionPublicKey () const = 0; virtual bool IsDestination () const = 0; // for garlic - - std::unique_ptr& GetElGamalEncryption () const - { - if (!m_ElGamalEncryption) - m_ElGamalEncryption.reset (new i2p::crypto::ElGamalEncryption (GetEncryptionPublicKey ())); - return m_ElGamalEncryption; - } - - private: - - mutable std::unique_ptr m_ElGamalEncryption; // use lazy initialization }; class LocalDestination @@ -261,8 +186,8 @@ namespace data virtual const uint8_t * GetEncryptionPrivateKey () const = 0; virtual const uint8_t * GetEncryptionPublicKey () const = 0; - const IdentityEx& GetIdentity () const { return GetPrivateKeys ().GetPublic (); }; - const IdentHash& GetIdentHash () const { return GetIdentity ().GetIdentHash (); }; + std::shared_ptr GetIdentity () const { return GetPrivateKeys ().GetPublic (); }; + const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { GetPrivateKeys ().Sign (buf, len, signature); diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 77d11e1a..9a88401c 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -1,8 +1,6 @@ #include #include "I2PEndian.h" -#include -#include -#include "CryptoConst.h" +#include "Crypto.h" #include "Log.h" #include "Timestamp.h" #include "NetDb.h" @@ -37,17 +35,16 @@ namespace data return; } m_Buffer = new uint8_t[MAX_LS_BUFFER_SIZE]; - m_BufferLen = localDestination->GetIdentity ().ToBuffer (m_Buffer, MAX_LS_BUFFER_SIZE); + m_BufferLen = localDestination->GetIdentity ()->ToBuffer (m_Buffer, MAX_LS_BUFFER_SIZE); memcpy (m_Buffer + m_BufferLen, localDestination->GetEncryptionPublicKey (), 256); m_BufferLen += 256; - auto signingKeyLen = localDestination->GetIdentity ().GetSigningPublicKeyLen (); + auto signingKeyLen = localDestination->GetIdentity ()->GetSigningPublicKeyLen (); memset (m_Buffer + m_BufferLen, 0, signingKeyLen); m_BufferLen += signingKeyLen; auto tunnels = pool.GetInboundTunnels (5); // 5 tunnels maximum m_Buffer[m_BufferLen] = tunnels.size (); // num leases m_BufferLen++; // leases - CryptoPP::AutoSeededRandomPool rnd; for (auto it: tunnels) { memcpy (m_Buffer + m_BufferLen, it->GetNextIdentHash (), 32); @@ -56,13 +53,13 @@ namespace data m_BufferLen += 4; // tunnel id uint64_t ts = it->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration ts *= 1000; // in milliseconds - ts += rnd.GenerateWord32 (0, 5); // + random milliseconds + ts += rand () % 6; // + random milliseconds 0-5 htobe64buf (m_Buffer + m_BufferLen, ts); m_BufferLen += 8; // end date } // signature localDestination->Sign (m_Buffer, m_BufferLen, m_Buffer + m_BufferLen); - m_BufferLen += localDestination->GetIdentity ().GetSignatureLen (); + m_BufferLen += localDestination->GetIdentity ()->GetSignatureLen (); LogPrint ("Local LeaseSet of ", tunnels.size (), " leases created"); ReadFromBuffer (); @@ -79,15 +76,17 @@ namespace data } memcpy (m_Buffer, buf, len); m_BufferLen = len; - ReadFromBuffer (); + ReadFromBuffer (false); } - void LeaseSet::ReadFromBuffer () + void LeaseSet::ReadFromBuffer (bool readIdentity) { - size_t size = m_Identity.FromBuffer (m_Buffer, m_BufferLen); + if (readIdentity || !m_Identity) + m_Identity = std::make_shared(m_Buffer, m_BufferLen); + size_t size = m_Identity->GetFullLen (); memcpy (m_EncryptionKey, m_Buffer + size, 256); size += 256; // encryption key - size += m_Identity.GetSigningPublicKeyLen (); // unused signing key + size += m_Identity->GetSigningPublicKeyLen (); // unused signing key uint8_t num = m_Buffer[size]; size++; // num LogPrint ("LeaseSet num=", (int)num); @@ -116,7 +115,7 @@ namespace data } // verify - if (!m_Identity.Verify (m_Buffer, leases - m_Buffer, leases)) + if (!m_Identity->Verify (m_Buffer, leases - m_Buffer, leases)) { LogPrint (eLogWarning, "LeaseSet verification failed"); m_IsValid = false; diff --git a/LeaseSet.h b/LeaseSet.h index fee130ab..eec5d89e 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -40,14 +40,14 @@ namespace data LeaseSet (const i2p::tunnel::TunnelPool& pool); ~LeaseSet () { delete[] m_Buffer; }; void Update (const uint8_t * buf, size_t len); - const IdentityEx& GetIdentity () const { return m_Identity; }; + std::shared_ptr GetIdentity () const { return m_Identity; }; const uint8_t * GetBuffer () const { return m_Buffer; }; size_t GetBufferLen () const { return m_BufferLen; }; bool IsValid () const { return m_IsValid; }; // implements RoutingDestination - const IdentHash& GetIdentHash () const { return m_Identity.GetIdentHash (); }; + const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); }; const std::vector& GetLeases () const { return m_Leases; }; const std::vector GetNonExpiredLeases (bool withThreshold = true) const; bool HasExpiredLeases () const; @@ -57,13 +57,13 @@ namespace data private: - void ReadFromBuffer (); + void ReadFromBuffer (bool readIdentity = true); private: bool m_IsValid; std::vector m_Leases; - IdentityEx m_Identity; + std::shared_ptr m_Identity; uint8_t m_EncryptionKey[256]; uint8_t * m_Buffer; size_t m_BufferLen; diff --git a/Makefile b/Makefile index d256c36e..f65d7a13 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ UNAME := $(shell uname -s) SHLIB := libi2pd.so +ARLIB := libi2pd.a +SHLIB_CLIENT := libi2pdclient.so +ARLIB_CLIENT := libi2pdclient.a I2PD := i2p GREP := fgrep DEPS := obj/make.dep @@ -22,12 +25,12 @@ else # win32 DAEMON_SRC += DaemonWin32.cpp endif -all: mk_build_dir $(SHLIB) $(I2PD) +all: mk_build_dir $(SHLIB) $(SHLIB_CLIENT) $(ARLIB) $(ARLIB_CLIENT) $(I2PD) mk_build_dir: mkdir -p obj -api: $(SHLIB) +api: $(SHLIB) $(ARLIB) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. @@ -48,7 +51,7 @@ obj/%.o : %.cpp # '-' is 'ignore if missing' on first run -include $(DEPS) -$(I2PD): $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) +$(I2PD): $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) $(ARLIB) $(ARLIB_CLIENT) $(CXX) -o $@ $^ $(LDLIBS) $(LDFLAGS) $(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) @@ -56,9 +59,18 @@ ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ endif +$(SHLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) + $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ + +$(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) + ar -r $@ $^ + +$(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) + ar -r $@ $^ + clean: rm -rf obj - $(RM) $(I2PD) $(SHLIB) + $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) LATEST_TAG=$(shell git describe --tags --abbrev=0 master) dist: diff --git a/Makefile.bsd b/Makefile.bsd index c6c3ce65..72820069 100644 --- a/Makefile.bsd +++ b/Makefile.bsd @@ -9,4 +9,4 @@ CXXFLAGS = -O2 NEEDED_CXXFLAGS = -std=c++11 INCFLAGS = -I/usr/include/ -I/usr/local/include/ LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -LDLIBS = -lcryptopp -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread +LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread diff --git a/Makefile.linux b/Makefile.linux index 8af84edf..78dcaaf0 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -33,11 +33,13 @@ ifeq ($(USE_STATIC),yes) LDLIBS += $(LIBDIR)/libboost_filesystem.a LDLIBS += $(LIBDIR)/libboost_regex.a LDLIBS += $(LIBDIR)/libboost_program_options.a - LDLIBS += $(LIBDIR)/libcryptopp.a + LDLIBS += $(LIBDIR)/libcrypto.a + LDLIBS += $(LIBDIR)/libssl.a + LDLIBS += $(LIBDIR)/libz.a LDLIBS += -lpthread -static-libstdc++ -static-libgcc USE_AESNI := no else - LDLIBS = -lcryptopp -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread + LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread endif # UPNP Support (miniupnpc 1.5 or 1.6) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 86418749..70fb8d25 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -1,12 +1,13 @@ #include #include +#include +#include +#include #include "I2PEndian.h" -#include -#include -#include "base64.h" +#include "Base.h" #include "Log.h" #include "Timestamp.h" -#include "CryptoConst.h" +#include "Crypto.h" #include "I2NPProtocol.h" #include "RouterContext.h" #include "Transports.h" @@ -35,15 +36,9 @@ namespace transport void NTCPSession::CreateAESKey (uint8_t * pubKey, i2p::crypto::AESKey& key) { - CryptoPP::DH dh (elgp, elgg); uint8_t sharedKey[256]; - if (!dh.Agree (sharedKey, m_DHKeysPair->privateKey, pubKey)) - { - LogPrint (eLogError, "Couldn't create shared key"); - Terminate (); - return; - }; - + m_DHKeysPair->Agree (pubKey, sharedKey); + uint8_t * aesKey = key; if (sharedKey[0] & 0x80) { @@ -97,11 +92,10 @@ namespace transport delete m_Establisher; m_Establisher = nullptr; - delete m_DHKeysPair; m_DHKeysPair = nullptr; SendTimeSyncMessage (); - m_SendQueue.push_back (ToSharedI2NPMessage(CreateDatabaseStoreMsg ())); // we tell immediately who we are + m_SendQueue.push_back (CreateDatabaseStoreMsg ()); // we tell immediately who we are transports.PeerConnected (shared_from_this ()); } @@ -111,10 +105,10 @@ namespace transport if (!m_DHKeysPair) m_DHKeysPair = transports.GetNextDHKeysPair (); // send Phase1 - const uint8_t * x = m_DHKeysPair->publicKey; + const uint8_t * x = m_DHKeysPair->GetPublicKey (); memcpy (m_Establisher->phase1.pubKey, x, 256); - CryptoPP::SHA256().CalculateDigest(m_Establisher->phase1.HXxorHI, x, 256); - const uint8_t * ident = m_RemoteIdentity.GetIdentHash (); + SHA256(x, 256, m_Establisher->phase1.HXxorHI); + const uint8_t * ident = m_RemoteIdentity->GetIdentHash (); for (int i = 0; i < 32; i++) m_Establisher->phase1.HXxorHI[i] ^= ident[i]; @@ -166,8 +160,8 @@ namespace transport { // verify ident uint8_t digest[32]; - CryptoPP::SHA256().CalculateDigest(digest, m_Establisher->phase1.pubKey, 256); - const uint8_t * ident = i2p::context.GetRouterInfo ().GetIdentHash (); + SHA256(m_Establisher->phase1.pubKey, 256, digest); + const uint8_t * ident = i2p::context.GetIdentHash (); for (int i = 0; i < 32; i++) { if ((m_Establisher->phase1.HXxorHI[i] ^ ident[i]) != digest[i]) @@ -186,12 +180,12 @@ namespace transport { if (!m_DHKeysPair) m_DHKeysPair = transports.GetNextDHKeysPair (); - const uint8_t * y = m_DHKeysPair->publicKey; + const uint8_t * y = m_DHKeysPair->GetPublicKey (); memcpy (m_Establisher->phase2.pubKey, y, 256); uint8_t xy[512]; memcpy (xy, m_Establisher->phase1.pubKey, 256); memcpy (xy + 256, y, 256); - CryptoPP::SHA256().CalculateDigest(m_Establisher->phase2.encrypted.hxy, xy, 512); + SHA256(xy, 512, m_Establisher->phase2.encrypted.hxy); uint32_t tsB = htobe32 (i2p::util::GetSecondsSinceEpoch ()); m_Establisher->phase2.encrypted.timestamp = tsB; // TODO: fill filler @@ -233,7 +227,7 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { // this RI is not valid - i2p::data::netdb.SetUnreachable (GetRemoteIdentity ().GetIdentHash (), true); + i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); transports.ReuseDHKeysPair (m_DHKeysPair); m_DHKeysPair = nullptr; Terminate (); @@ -251,9 +245,11 @@ namespace transport m_Decryption.Decrypt((uint8_t *)&m_Establisher->phase2.encrypted, sizeof(m_Establisher->phase2.encrypted), (uint8_t *)&m_Establisher->phase2.encrypted); // verify uint8_t xy[512]; - memcpy (xy, m_DHKeysPair->publicKey, 256); + memcpy (xy, m_DHKeysPair->GetPublicKey (), 256); memcpy (xy + 256, m_Establisher->phase2.pubKey, 256); - if (!CryptoPP::SHA256().VerifyDigest(m_Establisher->phase2.encrypted.hxy, xy, 512)) + uint8_t digest[32]; + SHA256 (xy, 512, digest); + if (memcmp(m_Establisher->phase2.encrypted.hxy, digest, 32)) { LogPrint (eLogError, "Incorrect hash"); transports.ReuseDHKeysPair (m_DHKeysPair); @@ -269,13 +265,13 @@ namespace transport { auto keys = i2p::context.GetPrivateKeys (); uint8_t * buf = m_ReceiveBuffer; - htobe16buf (buf, keys.GetPublic ().GetFullLen ()); + htobe16buf (buf, keys.GetPublic ()->GetFullLen ()); buf += 2; - buf += i2p::context.GetIdentity ().ToBuffer (buf, NTCP_BUFFER_SIZE); + buf += i2p::context.GetIdentity ()->ToBuffer (buf, NTCP_BUFFER_SIZE); uint32_t tsA = htobe32 (i2p::util::GetSecondsSinceEpoch ()); htobuf32(buf,tsA); buf += 4; - size_t signatureLen = keys.GetPublic ().GetSignatureLen (); + size_t signatureLen = keys.GetPublic ()->GetSignatureLen (); size_t len = (buf - m_ReceiveBuffer) + signatureLen; size_t paddingSize = len & 0x0F; // %16 if (paddingSize > 0) @@ -289,7 +285,7 @@ namespace transport SignedData s; s.Insert (m_Establisher->phase1.pubKey, 256); // x s.Insert (m_Establisher->phase2.pubKey, 256); // y - s.Insert (m_RemoteIdentity.GetIdentHash (), 32); // ident + s.Insert (m_RemoteIdentity->GetIdentHash (), 32); // ident s.Insert (tsA); // tsA s.Insert (m_Establisher->phase2.encrypted.timestamp); // tsB s.Sign (keys, buf); @@ -310,7 +306,7 @@ namespace transport else { // wait for phase4 - auto signatureLen = m_RemoteIdentity.GetSignatureLen (); + auto signatureLen = m_RemoteIdentity->GetSignatureLen (); size_t paddingSize = signatureLen & 0x0F; // %16 if (paddingSize > 0) signatureLen += (16 - paddingSize); boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer, signatureLen), boost::asio::transfer_all (), @@ -332,20 +328,20 @@ namespace transport m_Decryption.Decrypt (m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer); uint8_t * buf = m_ReceiveBuffer; uint16_t size = bufbe16toh (buf); - m_RemoteIdentity.FromBuffer (buf + 2, size); - if (m_Server.FindNTCPSession (m_RemoteIdentity.GetIdentHash ())) + SetRemoteIdentity (std::make_shared (buf + 2, size)); + if (m_Server.FindNTCPSession (m_RemoteIdentity->GetIdentHash ())) { LogPrint (eLogError, "NTCP session already exists"); Terminate (); } - size_t expectedSize = size + 2/*size*/ + 4/*timestamp*/ + m_RemoteIdentity.GetSignatureLen (); + size_t expectedSize = size + 2/*size*/ + 4/*timestamp*/ + m_RemoteIdentity->GetSignatureLen (); size_t paddingLen = expectedSize & 0x0F; if (paddingLen) paddingLen = (16 - paddingLen); if (expectedSize > NTCP_DEFAULT_PHASE3_SIZE) { // we need more bytes for Phase3 expectedSize += paddingLen; - boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, expectedSize), boost::asio::transfer_all (), + boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, expectedSize - NTCP_DEFAULT_PHASE3_SIZE), boost::asio::transfer_all (), std::bind(&NTCPSession::HandlePhase3ExtraReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2, tsB, paddingLen)); } @@ -371,7 +367,7 @@ namespace transport void NTCPSession::HandlePhase3 (uint32_t tsB, size_t paddingLen) { - uint8_t * buf = m_ReceiveBuffer + m_RemoteIdentity.GetFullLen () + 2 /*size*/; + uint8_t * buf = m_ReceiveBuffer + m_RemoteIdentity->GetFullLen () + 2 /*size*/; uint32_t tsA = buf32toh(buf); buf += 4; buf += paddingLen; @@ -388,7 +384,6 @@ namespace transport Terminate (); return; } - m_RemoteIdentity.DropVerifier (); SendPhase4 (tsA, tsB); } @@ -398,11 +393,11 @@ namespace transport SignedData s; s.Insert (m_Establisher->phase1.pubKey, 256); // x s.Insert (m_Establisher->phase2.pubKey, 256); // y - s.Insert (m_RemoteIdentity.GetIdentHash (), 32); // ident + s.Insert (m_RemoteIdentity->GetIdentHash (), 32); // ident s.Insert (tsA); // tsA s.Insert (tsB); // tsB auto keys = i2p::context.GetPrivateKeys (); - auto signatureLen = keys.GetPublic ().GetSignatureLen (); + auto signatureLen = keys.GetPublic ()->GetSignatureLen (); s.Sign (keys, m_ReceiveBuffer); size_t paddingSize = signatureLen & 0x0F; // %16 if (paddingSize > 0) signatureLen += (16 - paddingSize); @@ -440,7 +435,7 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { // this router doesn't like us - i2p::data::netdb.SetUnreachable (GetRemoteIdentity ().GetIdentHash (), true); + i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); Terminate (); } } @@ -452,7 +447,7 @@ namespace transport SignedData s; s.Insert (m_Establisher->phase1.pubKey, 256); // x s.Insert (m_Establisher->phase2.pubKey, 256); // y - s.Insert (i2p::context.GetRouterInfo ().GetIdentHash (), 32); // ident + s.Insert (i2p::context.GetIdentHash (), 32); // ident s.Insert (tsA); // tsA s.Insert (m_Establisher->phase2.encrypted.timestamp); // tsB @@ -462,7 +457,6 @@ namespace transport Terminate (); return; } - m_RemoteIdentity.DropVerifier (); LogPrint (eLogInfo, "NTCP session to ", m_Socket.remote_endpoint (), " connected"); Connected (); @@ -583,7 +577,9 @@ namespace transport if (m_NextMessageOffset >= m_NextMessage->len + 4) // +checksum { // we have a complete I2NP message - if (CryptoPP::Adler32().VerifyDigest (m_NextMessage->buf + m_NextMessageOffset - 4, m_NextMessage->buf, m_NextMessageOffset - 4)) + uint8_t checksum[4]; + htobe32buf (checksum, adler32 (adler32 (0, Z_NULL, 0), m_NextMessage->buf, m_NextMessageOffset - 4)); + if (!memcmp (m_NextMessage->buf + m_NextMessageOffset - 4, checksum, 4)) m_Handler.PutNextMessage (m_NextMessage); else LogPrint (eLogWarning, "Incorrect adler checksum of NTCP message. Dropped"); @@ -625,7 +621,7 @@ namespace transport int padding = 0; if (rem > 0) padding = 16 - rem; // TODO: fill padding - CryptoPP::Adler32().CalculateDigest (sendBuffer + len + 2 + padding, sendBuffer, len + 2+ padding); + htobe32buf (sendBuffer + len + 2 + padding, adler32 (adler32 (0, Z_NULL, 0), sendBuffer, len + 2+ padding)); int l = len + padding + 6; m_Encryption.Encrypt(sendBuffer, l, sendBuffer); @@ -799,19 +795,19 @@ namespace transport void NTCPServer::AddNTCPSession (std::shared_ptr session) { - if (session) + if (session && session->GetRemoteIdentity ()) { std::unique_lock l(m_NTCPSessionsMutex); - m_NTCPSessions[session->GetRemoteIdentity ().GetIdentHash ()] = session; + m_NTCPSessions[session->GetRemoteIdentity ()->GetIdentHash ()] = session; } } void NTCPServer::RemoveNTCPSession (std::shared_ptr session) { - if (session) + if (session && session->GetRemoteIdentity ()) { std::unique_lock l(m_NTCPSessionsMutex); - m_NTCPSessions.erase (session->GetRemoteIdentity ().GetIdentHash ()); + m_NTCPSessions.erase (session->GetRemoteIdentity ()->GetIdentHash ()); } } @@ -914,7 +910,7 @@ namespace transport { LogPrint (eLogError, "Connect error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) - i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ().GetIdentHash (), true); + i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); conn->Terminate (); } else diff --git a/NTCPSession.h b/NTCPSession.h index 2921e923..13e1656f 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -7,9 +7,7 @@ #include #include #include -#include -#include -#include "aes.h" +#include "Crypto.h" #include "Identity.h" #include "RouterInfo.h" #include "I2NPProtocol.h" diff --git a/NetDb.cpp b/NetDb.cpp index 39dd6430..5517ffdf 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -3,8 +3,9 @@ #include #include #include -#include -#include "base64.h" +#include +#include +#include "Base.h" #include "Log.h" #include "Timestamp.h" #include "I2NPProtocol.h" @@ -38,23 +39,8 @@ namespace data { Load (); if (m_RouterInfos.size () < 25) // reseed if # of router less than 50 - { - // try SU3 first Reseed (); - // deprecated - if (m_Reseeder) - { - // if still not enough download .dat files - int reseedRetries = 0; - while (m_RouterInfos.size () < 25 && reseedRetries < 5) - { - m_Reseeder->reseedNow(); - reseedRetries++; - Load (); - } - } - } m_IsRunning = true; m_Thread = new std::thread (std::bind (&NetDb::Run, this)); } @@ -245,6 +231,12 @@ namespace data return nullptr; } + std::shared_ptr NetDb::FindRouterProfile (const IdentHash& ident) const + { + auto router = FindRouter (ident); + return router ? router->GetProfile () : nullptr; + } + void NetDb::SetUnreachable (const IdentHash& ident, bool unreachable) { auto it = m_RouterInfos.find (ident); @@ -518,25 +510,10 @@ namespace data LogPrint ("Invalid RouterInfo length ", (int)size); return; } - try - { - CryptoPP::Gunzip decompressor; - decompressor.Put (buf + offset, size); - decompressor.MessageEnd(); - uint8_t uncompressed[2048]; - size_t uncomressedSize = decompressor.MaxRetrievable (); - if (uncomressedSize <= 2048) - { - decompressor.Get (uncompressed, uncomressedSize); - AddRouterInfo (ident, uncompressed, uncomressedSize); - } - else - LogPrint ("Invalid RouterInfo uncomressed length ", (int)uncomressedSize); - } - catch (CryptoPP::Exception& ex) - { - LogPrint (eLogError, "DatabaseStore: ", ex.what ()); - } + uint8_t uncompressed[2048]; + size_t uncompressedSize = m_Inflator.Inflate (buf + offset, size, uncompressed, 2048); + if (uncompressedSize) + AddRouterInfo (ident, uncompressed, uncompressedSize); } } @@ -575,7 +552,7 @@ namespace data { i2p::tunnel::eDeliveryTypeRouter, nextFloodfill->GetIdentHash (), 0, - ToSharedI2NPMessage (CreateDatabaseStoreMsg ()) + CreateDatabaseStoreMsg () }); // request destination @@ -679,7 +656,7 @@ namespace data excludedRouters.insert (r->GetIdentHash ()); } } - replyMsg = ToSharedI2NPMessage (CreateDatabaseSearchReply (ident, routers)); + replyMsg = CreateDatabaseSearchReply (ident, routers); } else { @@ -692,7 +669,7 @@ namespace data LogPrint ("Requested RouterInfo ", key, " found"); router->LoadBuffer (); if (router->GetBuffer ()) - replyMsg = ToSharedI2NPMessage (CreateDatabaseStoreMsg (router)); + replyMsg = CreateDatabaseStoreMsg (router); } } @@ -703,7 +680,7 @@ namespace data if (leaseSet) // we don't send back our LeaseSets { LogPrint ("Requested LeaseSet ", key, " found"); - replyMsg = ToSharedI2NPMessage (CreateDatabaseStoreMsg (leaseSet)); + replyMsg = CreateDatabaseStoreMsg (leaseSet); } } @@ -716,7 +693,7 @@ namespace data excludedRouters.insert (excluded); excluded += 32; } - replyMsg = ToSharedI2NPMessage (CreateDatabaseSearchReply (ident, GetClosestFloodfills (ident, 3, excludedRouters))); + replyMsg = CreateDatabaseSearchReply (ident, GetClosestFloodfills (ident, 3, excludedRouters)); } } @@ -756,14 +733,13 @@ namespace data auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : nullptr; bool throughTunnels = outbound && inbound; - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); uint8_t randomHash[32]; std::vector msgs; std::set floodfills; LogPrint ("Exploring new ", numDestinations, " routers ..."); for (int i = 0; i < numDestinations; i++) { - rnd.GenerateBlock (randomHash, 32); + RAND_bytes (randomHash, 32); auto dest = m_Requests.CreateRequest (randomHash, true); // exploratory if (!dest) { @@ -782,7 +758,7 @@ namespace data { i2p::tunnel::eDeliveryTypeRouter, floodfill->GetIdentHash (), 0, - ToSharedI2NPMessage (CreateDatabaseStoreMsg ()) // tell floodfill about us + CreateDatabaseStoreMsg () // tell floodfill about us }); msgs.push_back (i2p::tunnel::TunnelMessageBlock { @@ -809,9 +785,10 @@ namespace data auto floodfill = GetClosestFloodfill (i2p::context.GetRouterInfo ().GetIdentHash (), excluded); if (floodfill) { - uint32_t replyToken = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); - LogPrint ("Publishing our RouterInfo to ", floodfill->GetIdentHashAbbreviation (), ". reply token=", replyToken); - transports.SendMessage (floodfill->GetIdentHash (), ToSharedI2NPMessage (CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken))); + uint32_t replyToken; + RAND_bytes ((uint8_t *)&replyToken, 4); + LogPrint ("Publishing our RouterInfo to ", i2p::data::GetIdentHashAbbreviation(floodfill->GetIdentHash ()), ". reply token=", replyToken); + transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken)); excluded.insert (floodfill->GetIdentHash ()); } } @@ -868,8 +845,8 @@ namespace data template std::shared_ptr NetDb::GetRandomRouter (Filter filter) const { - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - uint32_t ind = rnd.GenerateWord32 (0, m_RouterInfos.size () - 1); + if (!m_RouterInfos.size ()) return 0; + uint32_t ind = rand () % m_RouterInfos.size (); for (int j = 0; j < 2; j++) { uint32_t i = 0; diff --git a/NetDb.h b/NetDb.h index 71db2e52..237e5be1 100644 --- a/NetDb.h +++ b/NetDb.h @@ -9,6 +9,7 @@ #include #include #include +#include "Base.h" #include "Queue.h" #include "I2NPProtocol.h" #include "RouterInfo.h" @@ -38,6 +39,7 @@ namespace data void AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr from); std::shared_ptr FindRouter (const IdentHash& ident) const; std::shared_ptr FindLeaseSet (const IdentHash& destination) const; + std::shared_ptr FindRouterProfile (const IdentHash& ident) const; void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr); @@ -91,6 +93,7 @@ namespace data std::thread * m_Thread; i2p::util::Queue > m_Queue; // of I2NPDatabaseStoreMsg + GzipInflator m_Inflator; Reseeder * m_Reseeder; friend class NetDbRequests; diff --git a/NetDbRequests.cpp b/NetDbRequests.cpp index 5f4a3e75..a8ccf3f9 100644 --- a/NetDbRequests.cpp +++ b/NetDbRequests.cpp @@ -11,21 +11,21 @@ namespace data std::shared_ptr RequestedDestination::CreateRequestMessage (std::shared_ptr router, std::shared_ptr replyTunnel) { - I2NPMessage * msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, + auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory, &m_ExcludedPeers); m_ExcludedPeers.insert (router->GetIdentHash ()); m_CreationTime = i2p::util::GetSecondsSinceEpoch (); - return ToSharedI2NPMessage (msg); + return msg; } std::shared_ptr RequestedDestination::CreateRequestMessage (const IdentHash& floodfill) { - I2NPMessage * msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, + auto msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, i2p::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers); m_ExcludedPeers.insert (floodfill); m_CreationTime = i2p::util::GetSecondsSinceEpoch (); - return ToSharedI2NPMessage (msg); + return msg; } void RequestedDestination::ClearExcludedPeers () diff --git a/Profiling.cpp b/Profiling.cpp index d0b8e85d..8b1a6bc3 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -1,8 +1,9 @@ #include #include #include -#include "base64.h" +#include "Base.h" #include "util.h" +#include "Log.h" #include "Profiling.h" namespace i2p diff --git a/Reseed.cpp b/Reseed.cpp index 9f137432..8d4baf8a 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -3,19 +3,17 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1 -#include +#include +#include +#include +#include +#include +#include #include "I2PEndian.h" #include "Reseed.h" #include "Log.h" #include "Identity.h" -#include "CryptoConst.h" +#include "Crypto.h" #include "NetDb.h" #include "util.h" @@ -24,27 +22,18 @@ namespace i2p { namespace data { - - static std::vector httpReseedHostList = { - "http://netdb.i2p2.no/", // only SU3 (v2) support - "http://i2p-netdb.innovatio.no/", - "http://193.150.121.66/netDb/" - }; - - static std::vector httpsReseedHostList = { - // "https://193.150.121.66/netDb/", // unstable - // "https://i2p-netdb.innovatio.no/",// Vuln to POODLE - "https://netdb.i2p2.no/", // Only SU3 (v2) support - "https://reseed.i2p-projekt.de/", // Only HTTPS - //"https://cowpuncher.drollette.com/netdb/", // returns error - "https://netdb.rows.io:444/", - "https://uk.reseed.i2p2.no:444/" - // following hosts are fine but don't support AES256 - /*"https://i2p.mooo.com/netDb/", - "https://link.mx24.eu/", // Only HTTPS and SU3 (v2) support - "https://i2pseed.zarrenspry.info/", // Only HTTPS and SU3 (v2) support - "https://ieb9oopo.mooo.com/" // Only HTTPS and SU3 (v2) support*/ - }; + static std::vector httpsReseedHostList = + { + "https://reseed.i2p-projekt.de/", // Only HTTPS + "https://i2pseed.zarrenspry.info/", // Only HTTPS and SU3 (v3) support + "https://i2p.mooo.com/netDb/", + "https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required + "https://us.reseed.i2p2.no:444/", + "https://uk.reseed.i2p2.no:444/", + "https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support + "https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support + "https://ieb9oopo.mooo.com/" // Only HTTPS and SU3 (v3) support + }; Reseeder::Reseeder() { @@ -54,75 +43,11 @@ namespace data { } - bool Reseeder::reseedNow() - { - // This method is deprecated - try - { - std::string reseedHost = httpReseedHostList[(rand() % httpReseedHostList.size())]; - LogPrint("Reseeding from ", reseedHost); - std::string content = i2p::util::http::httpRequest(reseedHost); - if (content == "") - { - LogPrint("Reseed failed"); - return false; - } - boost::regex e("<\\s*A\\s+[^>]*href\\s*=\\s*\"([^\"]*)\"", boost::regex::normal | boost::regbase::icase); - boost::sregex_token_iterator i(content.begin(), content.end(), e, 1); - boost::sregex_token_iterator j; - //TODO: Ugly code, try to clean up. - //TODO: Try to reduce N number of variables - std::string name; - std::string routerInfo; - std::string tmpUrl; - std::string filename; - std::string ignoreFileSuffix = ".su3"; - boost::filesystem::path root = i2p::util::filesystem::GetDataDir(); - while (i != j) - { - name = *i++; - if (name.find(ignoreFileSuffix)!=std::string::npos) - continue; - LogPrint("Downloading ", name); - tmpUrl = reseedHost; - tmpUrl.append(name); - routerInfo = i2p::util::http::httpRequest(tmpUrl); - if (routerInfo.size()==0) - continue; - filename = root.string(); -#ifndef _WIN32 - filename += "/netDb/r"; -#else - filename += "\\netDb\\r"; -#endif - filename += name.at(11); // first char in id -#ifndef _WIN32 - filename.append("/"); -#else - filename.append("\\"); -#endif - filename.append(name.c_str()); - std::ofstream outfile (filename, std::ios::binary); - outfile << routerInfo; - outfile.close(); - } - return true; - } - catch (std::exception& ex) - { - //TODO: error reporting - return false; - } - return false; - } - int Reseeder::ReseedNowSU3 () { - CryptoPP::AutoSeededRandomPool rnd; - auto ind = rnd.GenerateWord32 (0, httpReseedHostList.size() - 1 + httpsReseedHostList.size () - 1); - std::string reseedHost = (ind < httpReseedHostList.size()) ? httpReseedHostList[ind] : - httpsReseedHostList[ind - httpReseedHostList.size()]; - return ReseedFromSU3 (reseedHost, ind >= httpReseedHostList.size()); + auto ind = rand () % httpsReseedHostList.size (); + std::string& reseedHost = httpsReseedHostList[ind]; + return ReseedFromSU3 (reseedHost, true); } int Reseeder::ReseedFromSU3 (const std::string& host, bool https) @@ -221,10 +146,27 @@ namespace data uint8_t * signature = new uint8_t[signatureLength]; s.read ((char *)signature, signatureLength); // RSA-raw - i2p::crypto::RSASHA5124096RawVerifier verifier(it->second); - verifier.Update (tbs, tbsLen); - if (!verifier.Verify (signature)) - LogPrint (eLogWarning, "SU3 signature verification failed"); + { + // calculate digest + uint8_t digest[64]; + SHA512 (tbs, tbsLen, digest); + // encrypt signature + BN_CTX * bnctx = BN_CTX_new (); + BIGNUM * s = BN_new (), * n = BN_new (); + BN_bin2bn (signature, signatureLength, s); + BN_bin2bn (it->second, i2p::crypto::RSASHA5124096_KEY_LENGTH, n); + BN_mod_exp (s, s, i2p::crypto::rsae, n, bnctx); // s = s^e mod n + uint8_t * enSigBuf = new uint8_t[signatureLength]; + i2p::crypto::bn2buf (s, enSigBuf, signatureLength); + // digest is right aligned + // we can't use RSA_verify due wrong padding in SU3 + if (memcmp (enSigBuf + (signatureLength - 64), digest, 64)) + LogPrint (eLogWarning, "SU3 signature verification failed"); + delete[] enSigBuf; + BN_free (s); BN_free (n); + BN_CTX_free (bnctx); + } + delete[] signature; delete[] tbs; s.seekg (pos, std::ios::beg); @@ -255,8 +197,9 @@ namespace data compressionMethod = le16toh (compressionMethod); s.seekg (4, std::ios::cur); // skip fields we don't care about uint32_t compressedSize, uncompressedSize; - uint8_t crc32[4]; - s.read ((char *)crc32, 4); + uint32_t crc_32; + s.read ((char *)&crc_32, 4); + crc_32 = le32toh (crc_32); s.read ((char *)&compressedSize, 4); compressedSize = le32toh (compressedSize); s.read ((char *)&uncompressedSize, 4); @@ -278,9 +221,9 @@ namespace data { LogPrint (eLogError, "SU3 archive data descriptor not found"); return numFiles; - } - - s.read ((char *)crc32, 4); + } + s.read ((char *)&crc_32, 4); + crc_32 = le32toh (crc_32); s.read ((char *)&compressedSize, 4); compressedSize = le32toh (compressedSize) + 4; // ??? we must consider signature as part of compressed data s.read ((char *)&uncompressedSize, 4); @@ -301,25 +244,31 @@ namespace data s.read ((char *)compressed, compressedSize); if (compressionMethod) // we assume Deflate { - CryptoPP::Inflator decompressor; - decompressor.Put (compressed, compressedSize); - decompressor.MessageEnd(); - if (decompressor.MaxRetrievable () <= uncompressedSize) - { - uint8_t * uncompressed = new uint8_t[uncompressedSize]; - decompressor.Get (uncompressed, uncompressedSize); - if (CryptoPP::CRC32().VerifyDigest (crc32, uncompressed, uncompressedSize)) + z_stream inflator; + memset (&inflator, 0, sizeof (inflator)); + inflateInit2 (&inflator, -MAX_WBITS); // no zlib header + uint8_t * uncompressed = new uint8_t[uncompressedSize]; + inflator.next_in = compressed; + inflator.avail_in = compressedSize; + inflator.next_out = uncompressed; + inflator.avail_out = uncompressedSize; + int err; + if ((err = inflate (&inflator, Z_SYNC_FLUSH)) >= 0) + { + uncompressedSize -= inflator.avail_out; + if (crc32 (0, uncompressed, uncompressedSize) == crc_32) { i2p::data::netdb.AddRouterInfo (uncompressed, uncompressedSize); numFiles++; - } + } else LogPrint (eLogError, "CRC32 verification failed"); - delete[] uncompressed; - } + } else - LogPrint (eLogError, "Actual uncompressed size ", decompressor.MaxRetrievable (), " exceed ", uncompressedSize, " from header"); - } + LogPrint (eLogError, "decompression error ", err); + delete[] uncompressed; + inflateEnd (&inflator); + } else // no compression { i2p::data::netdb.AddRouterInfo (compressed, compressedSize); @@ -362,111 +311,33 @@ namespace data return false; } - const char CERTIFICATE_HEADER[] = "-----BEGIN CERTIFICATE-----"; - const char CERTIFICATE_FOOTER[] = "-----END CERTIFICATE-----"; void Reseeder::LoadCertificate (const std::string& filename) { - std::ifstream s(filename, std::ifstream::binary); - if (s.is_open ()) - { - s.seekg (0, std::ios::end); - size_t len = s.tellg (); - s.seekg (0, std::ios::beg); - char buf[2048]; - s.read (buf, len); - std::string cert (buf, len); - // assume file in pem format - auto pos1 = cert.find (CERTIFICATE_HEADER); - auto pos2 = cert.find (CERTIFICATE_FOOTER); - if (pos1 == std::string::npos || pos2 == std::string::npos) - { - LogPrint (eLogError, "Malformed certificate file"); - return; + SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ()); + int ret = SSL_CTX_use_certificate_file (ctx, filename.c_str (), SSL_FILETYPE_PEM); + if (ret) + { + SSL * ssl = SSL_new (ctx); + X509 * cert = SSL_get_certificate (ssl); + // verify + if (cert) + { + // extract issuer name + char name[100]; + X509_NAME_oneline (X509_get_issuer_name(cert), name, 100); + // extract RSA key (we need n only, e = 65537) + RSA * key = X509_get_pubkey (cert)->pkey.rsa; + PublicKey value; + i2p::crypto::bn2buf (key->n, value, 512); + m_SigningKeys[name] = value; } - pos1 += strlen (CERTIFICATE_HEADER); - pos2 -= pos1; - std::string base64 = cert.substr (pos1, pos2); - - CryptoPP::ByteQueue queue; - CryptoPP::Base64Decoder decoder; // regular base64 rather than I2P - decoder.Attach (new CryptoPP::Redirector (queue)); - decoder.Put ((const uint8_t *)base64.data(), base64.length()); - decoder.MessageEnd (); - - LoadCertificate (queue); - } + SSL_free (ssl); + } else LogPrint (eLogError, "Can't open certificate file ", filename); + SSL_CTX_free (ctx); } - std::string Reseeder::LoadCertificate (CryptoPP::ByteQueue& queue) - { - // extract X.509 - CryptoPP::BERSequenceDecoder x509Cert (queue); - CryptoPP::BERSequenceDecoder tbsCert (x509Cert); - // version - uint32_t ver; - CryptoPP::BERGeneralDecoder context (tbsCert, CryptoPP::CONTEXT_SPECIFIC | CryptoPP::CONSTRUCTED); - CryptoPP::BERDecodeUnsigned(context, ver, CryptoPP::INTEGER); - // serial - CryptoPP::Integer serial; - serial.BERDecode(tbsCert); - // signature - CryptoPP::BERSequenceDecoder signature (tbsCert); - signature.SkipAll(); - - // issuer - std::string name; - CryptoPP::BERSequenceDecoder issuer (tbsCert); - { - CryptoPP::BERSetDecoder c (issuer); c.SkipAll(); - CryptoPP::BERSetDecoder st (issuer); st.SkipAll(); - CryptoPP::BERSetDecoder l (issuer); l.SkipAll(); - CryptoPP::BERSetDecoder o (issuer); o.SkipAll(); - CryptoPP::BERSetDecoder ou (issuer); ou.SkipAll(); - CryptoPP::BERSetDecoder cn (issuer); - { - CryptoPP::BERSequenceDecoder attributes (cn); - { - CryptoPP::BERGeneralDecoder ident(attributes, CryptoPP::OBJECT_IDENTIFIER); - ident.SkipAll (); - CryptoPP::BERDecodeTextString (attributes, name, CryptoPP::UTF8_STRING); - } - } - } - issuer.SkipAll(); - // validity - CryptoPP::BERSequenceDecoder validity (tbsCert); - validity.SkipAll(); - // subject - CryptoPP::BERSequenceDecoder subject (tbsCert); - subject.SkipAll(); - // public key - CryptoPP::BERSequenceDecoder publicKey (tbsCert); - { - CryptoPP::BERSequenceDecoder ident (publicKey); - ident.SkipAll (); - CryptoPP::BERGeneralDecoder key (publicKey, CryptoPP::BIT_STRING); - key.Skip (1); // FIXME: probably bug in crypto++ - CryptoPP::BERSequenceDecoder keyPair (key); - CryptoPP::Integer n; - n.BERDecode (keyPair); - if (name.length () > 0) - { - PublicKey value; - n.Encode (value, 512); - m_SigningKeys[name] = value; - } - else - LogPrint (eLogWarning, "Unknown issuer. Skipped"); - } - publicKey.SkipAll(); - - tbsCert.SkipAll(); - x509Cert.SkipAll(); - return name; - } - void Reseeder::LoadCertificates () { boost::filesystem::path reseedDir = i2p::util::filesystem::GetCertificatesDir() / "reseed"; @@ -494,439 +365,50 @@ namespace data { i2p::util::http::url u(address); if (u.port_ == 80) u.port_ = 443; - TlsSession session (u.host_, u.port_); - - if (session.IsEstablished ()) - { - // 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"; - session.Send ((uint8_t *)ss.str ().c_str (), ss.str ().length ()); - // read response - std::stringstream rs; - while (session.Receive (rs)) - ; - return i2p::util::http::GetHttpContent (rs); + 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); + if (!ecode) + { + boost::asio::ssl::context ctx(service, boost::asio::ssl::context::tlsv12); + ctx.set_verify_mode(boost::asio::ssl::context::verify_none); + boost::asio::ssl::stream s(service, ctx); + s.lowest_layer().connect (*it, ecode); + if (!ecode) + { + s.handshake (boost::asio::ssl::stream_base::client, ecode); + if (!ecode) + { + LogPrint (eLogInfo, "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 ())); + // 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); + // process response + return i2p::util::http::GetHttpContent (rs); + } + else + LogPrint (eLogError, "SSL handshake failed: ", ecode.message ()); + } + else + LogPrint (eLogError, "Couldn't connect to ", u.host_, ": ", ecode.message ()); } else - return ""; + LogPrint (eLogError, "Couldn't resolve address ", u.host_, ": ", ecode.message ()); + return ""; } - -//------------------------------------------------------------- - - template - class TlsCipherMAC: public TlsCipher - { - public: - - TlsCipherMAC (const uint8_t * keys): m_Seqn (0) - { - memcpy (m_MacKey, keys, Hash::DIGESTSIZE); - } - - void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac) - { - uint8_t header[13]; // seqn (8) + type (1) + version (2) + length (2) - htobe64buf (header, m_Seqn); - header[8] = type; header[9] = 3; header[10] = 3; // 3,3 means TLS 1.2 - htobe16buf (header + 11, len); - CryptoPP::HMAC hmac (m_MacKey, Hash::DIGESTSIZE); - hmac.Update (header, 13); - hmac.Update (buf, len); - hmac.Final (mac); - m_Seqn++; - } - - private: - - uint64_t m_Seqn; - uint8_t m_MacKey[Hash::DIGESTSIZE]; // client - }; - - template - class TlsCipher_AES_256_CBC: public TlsCipherMAC - { - public: - - TlsCipher_AES_256_CBC (const uint8_t * keys): TlsCipherMAC (keys) - { - m_Encryption.SetKey (keys + 2*Hash::DIGESTSIZE); - m_Decryption.SetKey (keys + 2*Hash::DIGESTSIZE + 32); - } - - size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) - { - size_t size = 0; - m_Rnd.GenerateBlock (out, 16); // iv - size += 16; - m_Encryption.SetIV (out); - memcpy (out + size, in, len); - size += len; - memcpy (out + size, mac, Hash::DIGESTSIZE); - size += Hash::DIGESTSIZE; - uint8_t paddingSize = size + 1; - paddingSize &= 0x0F; // %16 - if (paddingSize > 0) paddingSize = 16 - paddingSize; - memset (out + size, paddingSize, paddingSize + 1); // paddind and last byte are equal to padding size - size += paddingSize + 1; - m_Encryption.Encrypt (out + 16, size - 16, out + 16); - return size; - } - - size_t Decrypt (uint8_t * buf, size_t len) // payload is buf + 16 - { - m_Decryption.SetIV (buf); - m_Decryption.Decrypt (buf + 16, len - 16, buf + 16); - return len - 16 - Hash::DIGESTSIZE - buf[len -1] - 1; // IV(16), mac(32 or 20) and padding - } - - size_t GetIVSize () const { return 16; }; - - private: - - CryptoPP::AutoSeededRandomPool m_Rnd; - i2p::crypto::CBCEncryption m_Encryption; - i2p::crypto::CBCDecryption m_Decryption; - }; - - - class TlsCipher_RC4_SHA: public TlsCipherMAC - { - public: - - TlsCipher_RC4_SHA (const uint8_t * keys): TlsCipherMAC (keys) - { - m_Encryption.SetKey (keys + 40, 16); // 20 + 20 - m_Decryption.SetKey (keys + 56, 16); // 20 + 20 + 16 - } - - size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) - { - memcpy (out, in, len); - memcpy (out + len, mac, 20); - m_Encryption.ProcessData (out, out, len + 20); - return len + 20; - } - - size_t Decrypt (uint8_t * buf, size_t len) - { - m_Decryption.ProcessData (buf, buf, len); - return len - 20; - } - - private: - - CryptoPP::Weak1::ARC4 m_Encryption, m_Decryption; - }; - - - TlsSession::TlsSession (const std::string& host, int port): - m_IsEstablished (false), m_Cipher (nullptr) - { - m_Site.connect(host, boost::lexical_cast(port)); - if (m_Site.good ()) - Handshake (); - else - LogPrint (eLogError, "Can't connect to ", host, ":", port); - } - - TlsSession::~TlsSession () - { - delete m_Cipher; - } - - void TlsSession::Handshake () - { - static uint8_t clientHello[] = - { - 0x16, // handshake - 0x03, 0x03, // version (TLS 1.2) - 0x00, 0x33, // length of handshake - // handshake - 0x01, // handshake type (client hello) - 0x00, 0x00, 0x2F, // length of handshake payload - // client hello - 0x03, 0x03, // highest version supported (TLS 1.2) - 0x45, 0xFA, 0x01, 0x19, 0x74, 0x55, 0x18, 0x36, - 0x42, 0x05, 0xC1, 0xDD, 0x4A, 0x21, 0x80, 0x80, - 0xEC, 0x37, 0x11, 0x93, 0x16, 0xF4, 0x66, 0x00, - 0x12, 0x67, 0xAB, 0xBA, 0xFF, 0x29, 0x13, 0x9E, // 32 random bytes - 0x00, // session id length - 0x00, 0x06, // chiper suites length - 0x00, 0x3D, // RSA_WITH_AES_256_CBC_SHA256 - 0x00, 0x35, // RSA_WITH_AES_256_CBC_SHA - 0x00, 0x05, // RSA_WITH_RC4_128_SHA - 0x01, // compression methods length - 0x00, // no compression - 0x00, 0x00 // extensions length - }; - - static uint8_t changeCipherSpecs[] = - { - 0x14, // change cipher specs - 0x03, 0x03, // version (TLS 1.2) - 0x00, 0x01, // length - 0x01 // type - }; - - // send ClientHello - m_Site.write ((char *)clientHello, sizeof (clientHello)); - m_FinishedHash.Update (clientHello + 5, sizeof (clientHello) - 5); - // read ServerHello - uint8_t type; - m_Site.read ((char *)&type, 1); - uint16_t version; - m_Site.read ((char *)&version, 2); - uint16_t length; - m_Site.read ((char *)&length, 2); - length = be16toh (length); - char * serverHello = new char[length]; - m_Site.read (serverHello, length); - m_FinishedHash.Update ((uint8_t *)serverHello, length); - uint8_t serverRandom[32]; - if (serverHello[0] == 0x02) // handshake type server hello - memcpy (serverRandom, serverHello + 6, 32); - else - LogPrint (eLogError, "Unexpected handshake type ", (int)serverHello[0]); - uint8_t sessionIDLen = serverHello[38]; // 6 + 32 - char * cipherSuite = serverHello + 39 + sessionIDLen; - if (cipherSuite[1] == 0x3D || cipherSuite[1] == 0x35 || cipherSuite[1] == 0x05) - m_IsEstablished = true; - else - LogPrint (eLogError, "Unsupported cipher ", (int)cipherSuite[0], ",", (int)cipherSuite[1]); - // read Certificate - m_Site.read ((char *)&type, 1); - m_Site.read ((char *)&version, 2); - m_Site.read ((char *)&length, 2); - length = be16toh (length); - char * certificate = new char[length]; - m_Site.read (certificate, length); - m_FinishedHash.Update ((uint8_t *)certificate, length); - CryptoPP::RSA::PublicKey publicKey; - // 0 - handshake type - // 1 - 3 - handshake payload length - // 4 - 6 - length of array of certificates - // 7 - 9 - length of certificate - if (certificate[0] == 0x0B) // handshake type certificate - publicKey = ExtractPublicKey ((uint8_t *)certificate + 10, length - 10); - else - LogPrint (eLogError, "Unexpected handshake type ", (int)certificate[0]); - // read ServerHelloDone - m_Site.read ((char *)&type, 1); - m_Site.read ((char *)&version, 2); - m_Site.read ((char *)&length, 2); - length = be16toh (length); - char * serverHelloDone = new char[length]; - m_Site.read (serverHelloDone, length); - m_FinishedHash.Update ((uint8_t *)serverHelloDone, length); - if (serverHelloDone[0] != 0x0E) // handshake type hello done - LogPrint (eLogError, "Unexpected handshake type ", (int)serverHelloDone[0]); - // our turn now - // generate secret key - uint8_t secret[48]; - secret[0] = 3; secret[1] = 3; // version - CryptoPP::AutoSeededRandomPool rnd; - rnd.GenerateBlock (secret + 2, 46); // 46 random bytes - // encrypt RSA - CryptoPP::RSAES_PKCS1v15_Encryptor encryptor(publicKey); - size_t encryptedLen = encryptor.CiphertextLength (48); // number of bytes for encrypted 48 bytes, usually 256 (2048 bits key) - uint8_t * encrypted = new uint8_t[encryptedLen + 2]; // + 2 bytes for length - htobe16buf (encrypted, encryptedLen); // first two bytes means length - encryptor.Encrypt (rnd, secret, 48, encrypted + 2); - // send ClientKeyExchange - // 0x10 - handshake type "client key exchange" - SendHandshakeMsg (0x10, encrypted, encryptedLen + 2); - delete[] encrypted; - // send ChangeCipherSpecs - m_Site.write ((char *)changeCipherSpecs, sizeof (changeCipherSpecs)); - // calculate master secret - uint8_t random[64]; - memcpy (random, clientHello + 11, 32); - memcpy (random + 32, serverRandom, 32); - PRF (secret, "master secret", random, 64, 48, m_MasterSecret); - // create keys - memcpy (random, serverRandom, 32); - memcpy (random + 32, clientHello + 11, 32); - uint8_t keys[128]; // clientMACKey(32 or 20), serverMACKey(32 or 20), clientKey(32), serverKey(32) - PRF (m_MasterSecret, "key expansion", random, 64, 128, keys); - // create cipher - if (cipherSuite[1] == 0x3D) - { - LogPrint (eLogInfo, "Chiper suite is RSA_WITH_AES_256_CBC_SHA256"); - m_Cipher = new TlsCipher_AES_256_CBC (keys); - } - else if (cipherSuite[1] == 0x35) - { - LogPrint (eLogInfo, "Chiper suite is RSA_WITH_AES_256_CBC_SHA"); - m_Cipher = new TlsCipher_AES_256_CBC (keys); - } - else - { - // TODO: - if (cipherSuite[1] == 0x05) - LogPrint (eLogInfo, "Chiper suite is RSA_WITH_RC4_128_SHA"); - m_Cipher = new TlsCipher_RC4_SHA (keys); - } - // send finished - SendFinishedMsg (); - // read ChangeCipherSpecs - uint8_t changeCipherSpecs1[6]; - m_Site.read ((char *)changeCipherSpecs1, 6); - // read finished - m_Site.read ((char *)&type, 1); - m_Site.read ((char *)&version, 2); - m_Site.read ((char *)&length, 2); - length = be16toh (length); - char * finished1 = new char[length]; - m_Site.read (finished1, length); - m_Cipher->Decrypt ((uint8_t *)finished1, length); // for streaming ciphers - delete[] finished1; - - delete[] serverHello; - delete[] certificate; - delete[] serverHelloDone; - } - - void TlsSession::SendHandshakeMsg (uint8_t handshakeType, uint8_t * data, size_t len) - { - uint8_t handshakeHeader[9]; - handshakeHeader[0] = 0x16; // handshake - handshakeHeader[1] = 0x03; handshakeHeader[2] = 0x03; // version is always TLS 1.2 (3,3) - htobe16buf (handshakeHeader + 3, len + 4); // length of payload - //payload starts - handshakeHeader[5] = handshakeType; // handshake type - handshakeHeader[6] = 0; // highest byte of payload length is always zero - htobe16buf (handshakeHeader + 7, len); // length of data - m_Site.write ((char *)handshakeHeader, 9); - m_FinishedHash.Update (handshakeHeader + 5, 4); // only payload counts - m_Site.write ((char *)data, len); - m_FinishedHash.Update (data, len); - } - - void TlsSession::SendFinishedMsg () - { - // 0x16 handshake - // 0x03, 0x03 version (TLS 1.2) - // 2 bytes length of handshake (80 or 64 bytes) - // handshake (encrypted) - // unencrypted context - // 0x14 handshake type (finished) - // 0x00, 0x00, 0x0C length of handshake payload - // 12 bytes of verified data - - uint8_t finishedHashDigest[32], finishedPayload[40], encryptedPayload[80]; - finishedPayload[0] = 0x14; // handshake type (finished) - finishedPayload[1] = 0; finishedPayload[2] = 0; finishedPayload[3] = 0x0C; // 12 bytes - m_FinishedHash.Final (finishedHashDigest); - PRF (m_MasterSecret, "client finished", finishedHashDigest, 32, 12, finishedPayload + 4); - uint8_t mac[32]; - m_Cipher->CalculateMAC (0x16, finishedPayload, 16, mac); - size_t encryptedPayloadSize = m_Cipher->Encrypt (finishedPayload, 16, mac, encryptedPayload); - uint8_t finished[5]; - finished[0] = 0x16; // handshake - finished[1] = 0x03; finished[2] = 0x03; // version is always TLS 1.2 (3,3) - htobe16buf (finished + 3, encryptedPayloadSize); // length of payload - m_Site.write ((char *)finished, sizeof (finished)); - m_Site.write ((char *)encryptedPayload, encryptedPayloadSize); - } - - void TlsSession::PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, - size_t len, uint8_t * buf) - { - // secret is assumed 48 bytes - // random is not more than 64 bytes - CryptoPP::HMAC hmac (secret, 48); - uint8_t seed[96]; size_t seedLen; - seedLen = strlen (label); - memcpy (seed, label, seedLen); - memcpy (seed + seedLen, random, randomLen); - seedLen += randomLen; - - size_t offset = 0; - uint8_t a[128]; - hmac.CalculateDigest (a, seed, seedLen); - while (offset < len) - { - memcpy (a + 32, seed, seedLen); - hmac.CalculateDigest (buf + offset, a, seedLen + 32); - offset += 32; - hmac.CalculateDigest (a, a, 32); - } - } - - CryptoPP::RSA::PublicKey TlsSession::ExtractPublicKey (const uint8_t * certificate, size_t len) - { - CryptoPP::ByteQueue queue; - queue.Put (certificate, len); - queue.MessageEnd (); - // extract X.509 - CryptoPP::BERSequenceDecoder x509Cert (queue); - CryptoPP::BERSequenceDecoder tbsCert (x509Cert); - // version - uint32_t ver; - CryptoPP::BERGeneralDecoder context (tbsCert, CryptoPP::CONTEXT_SPECIFIC | CryptoPP::CONSTRUCTED); - CryptoPP::BERDecodeUnsigned(context, ver, CryptoPP::INTEGER); - // serial - CryptoPP::Integer serial; - serial.BERDecode(tbsCert); - // signature - CryptoPP::BERSequenceDecoder signature (tbsCert); - signature.SkipAll(); - // issuer - CryptoPP::BERSequenceDecoder issuer (tbsCert); - issuer.SkipAll(); - // validity - CryptoPP::BERSequenceDecoder validity (tbsCert); - validity.SkipAll(); - // subject - CryptoPP::BERSequenceDecoder subject (tbsCert); - subject.SkipAll(); - // public key - CryptoPP::BERSequenceDecoder publicKey (tbsCert); - CryptoPP::BERSequenceDecoder ident (publicKey); - ident.SkipAll (); - CryptoPP::BERGeneralDecoder key (publicKey, CryptoPP::BIT_STRING); - key.Skip (1); // FIXME: probably bug in crypto++ - CryptoPP::BERSequenceDecoder keyPair (key); - CryptoPP::Integer n, e; - n.BERDecode (keyPair); - e.BERDecode (keyPair); - - CryptoPP::RSA::PublicKey ret; - ret.Initialize (n, e); - return ret; - } - - void TlsSession::Send (const uint8_t * buf, size_t len) - { - uint8_t * out = new uint8_t[len + 64 + 5]; // 64 = 32 mac + 16 iv + upto 16 padding, 5 = header - out[0] = 0x17; // application data - out[1] = 0x03; out[2] = 0x03; // version - uint8_t mac[32]; - m_Cipher->CalculateMAC (0x17, buf, len, mac); - size_t encryptedLen = m_Cipher->Encrypt (buf, len, mac, out + 5); - htobe16buf (out + 3, encryptedLen); - m_Site.write ((char *)out, encryptedLen + 5); - delete[] out; - } - - bool TlsSession::Receive (std::ostream& rs) - { - if (m_Site.eof ()) return false; - uint8_t type; uint16_t version, length; - m_Site.read ((char *)&type, 1); - m_Site.read ((char *)&version, 2); - m_Site.read ((char *)&length, 2); - length = be16toh (length); - uint8_t * buf = new uint8_t[length]; - m_Site.read ((char *)buf, length); - size_t decryptedLen = m_Cipher->Decrypt (buf, length); - rs.write ((char *)buf + m_Cipher->GetIVSize (), decryptedLen); - delete[] buf; - return true; - } } } diff --git a/Reseed.h b/Reseed.h index 3c92d066..aec8389c 100644 --- a/Reseed.h +++ b/Reseed.h @@ -5,11 +5,8 @@ #include #include #include -#include -#include -#include #include "Identity.h" -#include "aes.h" +#include "Crypto.h" namespace i2p { @@ -24,7 +21,6 @@ namespace data Reseeder(); ~Reseeder(); - bool reseedNow(); // depreacted int ReseedNowSU3 (); void LoadCertificates (); @@ -32,8 +28,7 @@ namespace data private: void LoadCertificate (const std::string& filename); - std::string LoadCertificate (CryptoPP::ByteQueue& queue); // returns issuer's name - + int ReseedFromSU3 (const std::string& host, bool https = false); int ProcessSU3File (const char * filename); int ProcessSU3Stream (std::istream& s); @@ -46,49 +41,6 @@ namespace data std::map m_SigningKeys; }; - - - class TlsCipher - { - public: - - virtual ~TlsCipher () {}; - - virtual void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac) = 0; - virtual size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out) = 0; - virtual size_t Decrypt (uint8_t * buf, size_t len) = 0; - virtual size_t GetIVSize () const { return 0; }; // override for AES - }; - - - class TlsSession - { - public: - - TlsSession (const std::string& host, int port); - ~TlsSession (); - void Send (const uint8_t * buf, size_t len); - bool Receive (std::ostream& rs); - bool IsEstablished () const { return m_IsEstablished; }; - - private: - - void Handshake (); - void SendHandshakeMsg (uint8_t handshakeType, uint8_t * data, size_t len); - void SendFinishedMsg (); - CryptoPP::RSA::PublicKey ExtractPublicKey (const uint8_t * certificate, size_t len); - - void PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen, - size_t len, uint8_t * buf); - - private: - - bool m_IsEstablished; - boost::asio::ip::tcp::iostream m_Site; - CryptoPP::SHA256 m_FinishedHash; - uint8_t m_MasterSecret[64]; // actual size is 48, but must be multiple of 32 - TlsCipher * m_Cipher; - }; } } diff --git a/RouterContext.cpp b/RouterContext.cpp index c9328b5c..ca4b269a 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -1,14 +1,13 @@ #include -#include -#include #include -#include "CryptoConst.h" -#include "RouterContext.h" +#include "Crypto.h" #include "Timestamp.h" #include "I2NPProtocol.h" #include "NetDb.h" #include "util.h" #include "version.h" +#include "Log.h" +#include "RouterContext.h" namespace i2p { @@ -22,6 +21,7 @@ namespace i2p void RouterContext::Init () { + srand (i2p::util::GetMillisecondsSinceEpoch () % 1000); m_StartupTime = i2p::util::GetSecondsSinceEpoch (); if (!Load ()) CreateNewRouter (); @@ -41,7 +41,7 @@ namespace i2p routerInfo.SetRouterIdentity (GetIdentity ()); int port = i2p::util::config::GetArg("-port", 0); if (!port) - port = m_Rnd.GenerateWord32 (9111, 30777); // I2P network ports range + port = rand () % (30777 - 9111) + 9111; // I2P network ports range routerInfo.AddSSUAddress (i2p::util::config::GetCharArg("-host", "127.0.0.1"), port, routerInfo.GetIdentHash ()); routerInfo.AddNTCPAddress (i2p::util::config::GetCharArg("-host", "127.0.0.1"), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | @@ -51,6 +51,7 @@ namespace i2p routerInfo.SetProperty ("router.version", I2P_VERSION); routerInfo.SetProperty ("stat_uptime", "90m"); routerInfo.CreateBuffer (m_Keys); + m_RouterInfo.SetRouterIdentity (GetIdentity ()); m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); } @@ -61,6 +62,25 @@ namespace i2p m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch (); } + void RouterContext::SetStatus (RouterStatus status) + { + if (status != m_Status) + { + m_Status = status; + switch (m_Status) + { + case eRouterStatusOK: + SetReachable (); + break; + case eRouterStatusFirewalled: + SetUnreachable (); + break; + default: + ; + } + } + } + void RouterContext::UpdatePort (int port) { bool updated = false; @@ -92,16 +112,11 @@ namespace i2p UpdateRouterInfo (); } - bool RouterContext::AddIntroducer (const i2p::data::RouterInfo& routerInfo, uint32_t tag) + bool RouterContext::AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer) { - bool ret = false; - auto address = routerInfo.GetSSUAddress (); - if (address) - { - ret = m_RouterInfo.AddIntroducer (address, tag); - if (ret) - UpdateRouterInfo (); - } + bool ret = m_RouterInfo.AddIntroducer (introducer); + if (ret) + UpdateRouterInfo (); return ret; } @@ -188,7 +203,7 @@ namespace i2p { if (addresses[i].transportStyle == i2p::data::RouterInfo::eTransportSSU) { - // insert NTCP address with host/port form SSU + // insert NTCP address with host/port from SSU m_RouterInfo.AddNTCPAddress (addresses[i].host.to_string ().c_str (), addresses[i].port); break; } @@ -267,6 +282,7 @@ namespace i2p m_Keys = keys; i2p::data::RouterInfo routerInfo(i2p::util::filesystem::GetFullPath (ROUTER_INFO)); // TODO + m_RouterInfo.SetRouterIdentity (GetIdentity ()); m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION); m_RouterInfo.SetProperty ("router.version", I2P_VERSION); @@ -283,7 +299,7 @@ namespace i2p i2p::data::Keys keys; memcpy (keys.privateKey, m_Keys.GetPrivateKey (), sizeof (keys.privateKey)); memcpy (keys.signingPrivateKey, m_Keys.GetSigningPrivateKey (), sizeof (keys.signingPrivateKey)); - auto& ident = GetIdentity ().GetStandardIdentity (); + auto& ident = GetIdentity ()->GetStandardIdentity (); memcpy (keys.publicKey, ident.publicKey, sizeof (keys.publicKey)); memcpy (keys.signingKey, ident.signingKey, sizeof (keys.signingKey)); fk.write ((char *)&keys, sizeof (keys)); diff --git a/RouterContext.h b/RouterContext.h index 2689d025..541b78d1 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -6,8 +6,6 @@ #include #include #include -#include -#include #include "Identity.h" #include "RouterInfo.h" #include "Garlic.h" @@ -41,16 +39,15 @@ namespace i2p return std::shared_ptr (&m_RouterInfo, [](const i2p::data::RouterInfo *) {}); } - CryptoPP::RandomNumberGenerator& GetRandomNumberGenerator () { return m_Rnd; }; uint32_t GetUptime () const; uint32_t GetStartupTime () const { return m_StartupTime; }; uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; }; RouterStatus GetStatus () const { return m_Status; }; - void SetStatus (RouterStatus status) { m_Status = status; }; + void SetStatus (RouterStatus status); void UpdatePort (int port); // called from Daemon void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon - bool AddIntroducer (const i2p::data::RouterInfo& routerInfo, uint32_t tag); + bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer); void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); bool IsUnreachable () const; void SetUnreachable (); @@ -69,7 +66,7 @@ namespace i2p // implements LocalDestination const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; const uint8_t * GetEncryptionPrivateKey () const { return m_Keys.GetPrivateKey (); }; - const uint8_t * GetEncryptionPublicKey () const { return GetIdentity ().GetStandardIdentity ().publicKey; }; + const uint8_t * GetEncryptionPublicKey () const { return GetIdentity ()->GetStandardIdentity ().publicKey; }; void SetLeaseSetUpdated () {}; // implements GarlicDestination @@ -93,7 +90,6 @@ namespace i2p i2p::data::RouterInfo m_RouterInfo; i2p::data::PrivateKeys m_Keys; - CryptoPP::AutoSeededRandomPool m_Rnd; uint64_t m_LastUpdateTime; bool m_AcceptsTunnels, m_IsFloodfill; uint64_t m_StartupTime; // in seconds since epoch diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 403b711c..037297e0 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -3,15 +3,11 @@ #include "I2PEndian.h" #include #include -#include -#include -#include "CryptoConst.h" -#include "base64.h" +#include "Crypto.h" +#include "Base.h" #include "Timestamp.h" #include "Log.h" #include "RouterInfo.h" -#include "RouterContext.h" - namespace i2p { @@ -41,21 +37,38 @@ namespace data void RouterInfo::Update (const uint8_t * buf, int len) { - if (!m_Buffer) - m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; - m_IsUpdated = true; - m_IsUnreachable = false; - m_SupportedTransports = 0; - m_Caps = 0; - m_Addresses.clear (); - m_Properties.clear (); - memcpy (m_Buffer, buf, len); - m_BufferLen = len; - ReadFromBuffer (true); - // don't delete buffer until save to file + // verify signature since we have indentity already + int l = len - m_RouterIdentity->GetSignatureLen (); + if (m_RouterIdentity->Verify (buf, l, buf + l)) + { + // clean up + m_IsUpdated = true; + m_IsUnreachable = false; + m_SupportedTransports = 0; + m_Caps = 0; + m_Addresses.clear (); + m_Properties.clear (); + // copy buffer + if (!m_Buffer) + m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; + memcpy (m_Buffer, buf, len); + m_BufferLen = len; + // skip identity + size_t identityLen = m_RouterIdentity->GetFullLen (); + // read new RI + std::stringstream str (std::string ((char *)m_Buffer + identityLen, m_BufferLen - identityLen)); + ReadFromStream (str); + // don't delete buffer until saved to the file + } + else + { + LogPrint (eLogError, "RouterInfo signature verification failed"); + m_IsUnreachable = true; + } + m_RouterIdentity->DropVerifier (); } - void RouterInfo::SetRouterIdentity (const IdentityEx& identity) + void RouterInfo::SetRouterIdentity (std::shared_ptr identity) { m_RouterIdentity = identity; m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); @@ -94,19 +107,20 @@ namespace data void RouterInfo::ReadFromBuffer (bool verifySignature) { - size_t identityLen = m_RouterIdentity.FromBuffer (m_Buffer, m_BufferLen); + m_RouterIdentity = std::make_shared(m_Buffer, m_BufferLen); + size_t identityLen = m_RouterIdentity->GetFullLen (); std::stringstream str (std::string ((char *)m_Buffer + identityLen, m_BufferLen - identityLen)); ReadFromStream (str); if (verifySignature) { // verify signature - int l = m_BufferLen - m_RouterIdentity.GetSignatureLen (); - if (!m_RouterIdentity.Verify ((uint8_t *)m_Buffer, l, (uint8_t *)m_Buffer + l)) + int l = m_BufferLen - m_RouterIdentity->GetSignatureLen (); + if (!m_RouterIdentity->Verify ((uint8_t *)m_Buffer, l, (uint8_t *)m_Buffer + l)) { - LogPrint (eLogError, "signature verification failed"); + LogPrint (eLogError, "RouterInfo signature verification failed"); m_IsUnreachable = true; } - m_RouterIdentity.DropVerifier (); + m_RouterIdentity->DropVerifier (); } } @@ -419,7 +433,7 @@ namespace data if (!m_Buffer) { if (LoadFile ()) - LogPrint ("Buffer for ", GetIdentHashAbbreviation (), " loaded from file"); + LogPrint ("Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file"); } return m_Buffer; } @@ -429,7 +443,7 @@ namespace data m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); // refresh timstamp std::stringstream s; uint8_t ident[1024]; - auto identLen = privateKeys.GetPublic ().ToBuffer (ident, 1024); + auto identLen = privateKeys.GetPublic ()->ToBuffer (ident, 1024); s.write ((char *)ident, identLen); WriteToStream (s); m_BufferLen = s.str ().size (); @@ -438,7 +452,7 @@ namespace data memcpy (m_Buffer, s.str ().c_str (), m_BufferLen); // signature privateKeys.Sign ((uint8_t *)m_Buffer, m_BufferLen, (uint8_t *)m_Buffer + m_BufferLen); - m_BufferLen += privateKeys.GetPublic ().GetSignatureLen (); + m_BufferLen += privateKeys.GetPublic ()->GetSignatureLen (); } void RouterInfo::SaveToFile (const std::string& fullPath) @@ -481,6 +495,8 @@ namespace data addr.cost = 2; addr.date = 0; addr.mtu = 0; + for (auto it: m_Addresses) // don't insert same address twice + if (it == addr) return; m_Addresses.push_back(addr); m_SupportedTransports |= addr.host.is_v6 () ? eNTCPV6 : eNTCPV4; } @@ -495,26 +511,23 @@ namespace data addr.date = 0; addr.mtu = mtu; memcpy (addr.key, key, 32); + for (auto it: m_Addresses) // don't insert same address twice + if (it == addr) return; m_Addresses.push_back(addr); m_SupportedTransports |= addr.host.is_v6 () ? eNTCPV6 : eSSUV4; m_Caps |= eSSUTesting; m_Caps |= eSSUIntroducer; } - bool RouterInfo::AddIntroducer (const Address * address, uint32_t tag) + bool RouterInfo::AddIntroducer (const Introducer& introducer) { for (auto& addr : m_Addresses) { if (addr.transportStyle == eTransportSSU && addr.host.is_v4 ()) { for (auto intro: addr.introducers) - if (intro.iTag == tag) return false; // already presented - Introducer x; - x.iHost = address->host; - x.iPort = address->port; - x.iTag = tag; - memcpy (x.iKey, address->key, 32); // TODO: replace to Tag<32> - addr.introducers.push_back (x); + if (intro.iTag == introducer.iTag) return false; // already presented + addr.introducers.push_back (introducer); return true; } } diff --git a/RouterInfo.h b/RouterInfo.h index 376dfdc7..a7ab2102 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -58,11 +58,12 @@ namespace data eTransportSSU }; + typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey struct Introducer { boost::asio::ip::address iHost; int iPort; - Tag<32> iKey; + IntroKey iKey; uint32_t iTag; }; @@ -75,7 +76,7 @@ namespace data uint64_t date; uint8_t cost; // SSU only - Tag<32> key; // intro key for SSU + IntroKey key; // intro key for SSU std::vector introducers; bool IsCompatible (const boost::asio::ip::address& other) const @@ -83,6 +84,16 @@ namespace data return (host.is_v4 () && other.is_v4 ()) || (host.is_v6 () && other.is_v6 ()); } + + bool operator==(const Address& other) const + { + return transportStyle == other.transportStyle && host == other.host && port == other.port; + } + + bool operator!=(const Address& other) const + { + return !(*this == other); + } }; RouterInfo (const std::string& fullPath); @@ -92,10 +103,9 @@ namespace data RouterInfo (const uint8_t * buf, int len); ~RouterInfo (); - const IdentityEx& GetRouterIdentity () const { return m_RouterIdentity; }; - void SetRouterIdentity (const IdentityEx& identity); + std::shared_ptr GetRouterIdentity () const { return m_RouterIdentity; }; + void SetRouterIdentity (std::shared_ptr identity); std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; - std::string GetIdentHashAbbreviation () const { return GetIdentHash ().ToBase64 ().substr (0, 4); }; uint64_t GetTimestamp () const { return m_Timestamp; }; std::vector
& GetAddresses () { return m_Addresses; }; const Address * GetNTCPAddress (bool v4only = true) const; @@ -104,7 +114,7 @@ namespace data void AddNTCPAddress (const char * host, int port); void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); - bool AddIntroducer (const Address * address, uint32_t tag); + bool AddIntroducer (const Introducer& introducer); bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only void DeleteProperty (const std::string& key); // called from RouterContext only @@ -145,8 +155,8 @@ namespace data void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; }; // implements RoutingDestination - const IdentHash& GetIdentHash () const { return m_RouterIdentity.GetIdentHash (); }; - const uint8_t * GetEncryptionPublicKey () const { return m_RouterIdentity.GetStandardIdentity ().publicKey; }; + const IdentHash& GetIdentHash () const { return m_RouterIdentity->GetIdentHash (); }; + const uint8_t * GetEncryptionPublicKey () const { return m_RouterIdentity->GetStandardIdentity ().publicKey; }; bool IsDestination () const { return false; }; @@ -166,7 +176,7 @@ namespace data private: std::string m_FullPath; - IdentityEx m_RouterIdentity; + std::shared_ptr m_RouterIdentity; uint8_t * m_Buffer; int m_BufferLen; uint64_t m_Timestamp; diff --git a/SAM.cpp b/SAM.cpp index 7db1e1bb..dc592f51 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -4,7 +4,7 @@ #include #endif #include -#include "base64.h" +#include "Base.h" #include "Identity.h" #include "Log.h" #include "Destination.h" @@ -189,6 +189,8 @@ namespace client if (ecode != boost::asio::error::operation_aborted) Terminate (); } + else if (m_SocketType == eSAMSocketTypeStream) + HandleReceived (ecode, bytes_transferred); else { bytes_transferred += m_BufferOffset; @@ -342,17 +344,17 @@ namespace client m_Session = m_Owner.FindSession (id); if (m_Session) { - i2p::data::IdentityEx dest; - size_t len = dest.FromBase64(destination); + auto dest = std::make_shared (); + size_t len = dest->FromBase64(destination); if (len > 0) { context.GetAddressBook().InsertAddress(dest); - auto leaseSet = m_Session->localDestination->FindLeaseSet(dest.GetIdentHash()); + auto leaseSet = m_Session->localDestination->FindLeaseSet(dest->GetIdentHash()); if (leaseSet) Connect(leaseSet); else { - m_Session->localDestination->RequestDestination(dest.GetIdentHash(), + m_Session->localDestination->RequestDestination(dest->GetIdentHash(), std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, shared_from_this(), std::placeholders::_1)); } @@ -451,7 +453,7 @@ namespace client keys.GetPublic ().ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); #else size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, - keys.GetPublic ().ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); + keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); #endif SendMessageReply (m_Buffer, len, false); } @@ -462,11 +464,11 @@ namespace client std::map params; ExtractParams (buf, params); std::string& name = params[SAM_PARAM_NAME]; - i2p::data::IdentityEx identity; + std::shared_ptr identity; i2p::data::IdentHash ident; if (name == "ME") SendNamingLookupReply (m_Session->localDestination->GetIdentity ()); - else if (context.GetAddressBook ().GetAddress (name, identity)) + else if ((identity = context.GetAddressBook ().GetAddress (name)) != nullptr) SendNamingLookupReply (identity); else if (m_Session && m_Session->localDestination && context.GetAddressBook ().GetIdentHash (name, ident)) @@ -512,9 +514,9 @@ namespace client } } - void SAMSocket::SendNamingLookupReply (const i2p::data::IdentityEx& identity) + void SAMSocket::SendNamingLookupReply (std::shared_ptr identity) { - auto base64 = identity.ToBase64 (); + auto base64 = identity->ToBase64 (); #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ()); #else @@ -631,7 +633,7 @@ namespace client { // send remote peer address uint8_t ident[1024]; - size_t l = stream->GetRemoteIdentity ().ToBuffer (ident, 1024); + size_t l = stream->GetRemoteIdentity ()->ToBuffer (ident, 1024); size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE); m_StreamBuffer[l1] = '\n'; HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream diff --git a/SAM.h b/SAM.h index 7684d461..c5017b83 100644 --- a/SAM.h +++ b/SAM.h @@ -111,7 +111,7 @@ namespace client void Connect (std::shared_ptr remote); void HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet); - void SendNamingLookupReply (const i2p::data::IdentityEx& identity); + void SendNamingLookupReply (std::shared_ptr identity); void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, i2p::data::IdentHash ident); void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode); void SendSessionCreateReplyOk (); diff --git a/SSU.cpp b/SSU.cpp index 9c12441a..d4b2b05d 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -283,7 +283,11 @@ namespace transport boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); auto it = m_Sessions.find (remoteEndpoint); if (it != m_Sessions.end ()) + { session = it->second; + if (peerTest && session->GetState () == eSessionStateEstablished) + session->SendPeerTest (); + } else { // otherwise create new session @@ -295,7 +299,7 @@ namespace transport if (!router->UsesIntroducer ()) { // connect directly - LogPrint ("Creating new SSU session to [", router->GetIdentHashAbbreviation (), "] ", + LogPrint ("Creating new SSU session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); session->Connect (); } @@ -331,7 +335,7 @@ namespace transport m_Sessions[introducerEndpoint] = introducerSession; } // introduce - LogPrint ("Introduce new SSU session to [", router->GetIdentHashAbbreviation (), + LogPrint ("Introduce new SSU session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] through introducer ", introducer->iHost, ":", introducer->iPort); session->WaitForIntroduction (); if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable @@ -339,7 +343,7 @@ namespace transport uint8_t buf[1]; Send (buf, 0, remoteEndpoint); // send HolePunch } - introducerSession->Introduce (introducer->iTag, introducer->iKey); + introducerSession->Introduce (*introducer); } else { @@ -352,7 +356,7 @@ namespace transport } } else - LogPrint (eLogWarning, "Router ", router->GetIdentHashAbbreviation (), " doesn't have SSU address"); + LogPrint (eLogWarning, "Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); } return session; } @@ -383,7 +387,7 @@ namespace transport if (filter (s.second)) filteredSessions.push_back (s.second); if (filteredSessions.size () > 0) { - auto ind = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, filteredSessions.size ()-1); + auto ind = rand () % filteredSessions.size (); return filteredSessions[ind]; } return nullptr; @@ -468,10 +472,15 @@ namespace transport { for (auto it1: introducers) { - auto router = it1->GetRemoteRouter (); - if (router && i2p::context.AddIntroducer (*router, it1->GetRelayTag ())) + auto& ep = it1->GetRemoteEndpoint (); + i2p::data::RouterInfo::Introducer introducer; + introducer.iHost = ep.address (); + introducer.iPort = ep.port (); + introducer.iTag = it1->GetRelayTag (); + introducer.iKey = it1->GetIntroKey (); + if (i2p::context.AddIntroducer (introducer)) { - newList.push_back (it1->GetRemoteEndpoint ()); + newList.push_back (ep); if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break; } } diff --git a/SSU.h b/SSU.h index 1033a2bf..db9d57eb 100644 --- a/SSU.h +++ b/SSU.h @@ -9,7 +9,7 @@ #include #include #include -#include "aes.h" +#include "Crypto.h" #include "I2PEndian.h" #include "Identity.h" #include "RouterInfo.h" diff --git a/SSUData.cpp b/SSUData.cpp index 5c0c03dd..77c2470f 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -26,13 +26,10 @@ namespace transport SSUData::SSUData (SSUSession& session): m_Session (session), m_ResendTimer (session.GetService ()), m_DecayTimer (session.GetService ()), - m_IncompleteMessagesCleanupTimer (session.GetService ()) + m_IncompleteMessagesCleanupTimer (session.GetService ()), + m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE), + m_PacketSize (m_MaxPacketSize) { - m_MaxPacketSize = session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE; - m_PacketSize = m_MaxPacketSize; - auto remoteRouter = session.GetRemoteRouter (); - if (remoteRouter) - AdjustPacketSize (*remoteRouter); } SSUData::~SSUData () @@ -51,9 +48,10 @@ namespace transport m_IncompleteMessagesCleanupTimer.cancel (); } - void SSUData::AdjustPacketSize (const i2p::data::RouterInfo& remoteRouter) + void SSUData::AdjustPacketSize (std::shared_ptr remoteRouter) { - auto ssuAddress = remoteRouter.GetSSUAddress (); + if (remoteRouter) return; + auto ssuAddress = remoteRouter->GetSSUAddress (); if (ssuAddress && ssuAddress->mtu) { if (m_Session.IsV6 ()) @@ -80,7 +78,7 @@ namespace transport { auto routerInfo = i2p::data::netdb.FindRouter (remoteIdent); if (routerInfo) - AdjustPacketSize (*routerInfo); + AdjustPacketSize (routerInfo); } void SSUData::ProcessSentMessageAck (uint32_t msgID) @@ -372,7 +370,7 @@ namespace transport payload++; *payload = 1; // number of ACKs payload++; - *(uint32_t *)(payload) = htobe32 (msgID); // msgID + htobe32buf (payload, msgID); // msgID payload += 4; *payload = 0; // number of fragments diff --git a/SSUData.h b/SSUData.h index 60d5057e..d571bed3 100644 --- a/SSUData.h +++ b/SSUData.h @@ -80,7 +80,7 @@ namespace transport { public: - SSUData (SSUSession& session); + SSUData (SSUSession& session); ~SSUData (); void Start (); @@ -90,6 +90,7 @@ namespace transport void FlushReceivedMessage (); void Send (std::shared_ptr msg); + void AdjustPacketSize (std::shared_ptr remoteRouter); void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent); private: @@ -109,7 +110,6 @@ namespace transport void ScheduleIncompleteMessagesCleanup (); void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode); - void AdjustPacketSize (const i2p::data::RouterInfo& remoteRouter); private: diff --git a/SSUSession.cpp b/SSUSession.cpp index 3900e7de..be020729 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -1,7 +1,7 @@ #include -#include -#include -#include "CryptoConst.h" +#include +#include +#include #include "Log.h" #include "Timestamp.h" #include "RouterContext.h" @@ -16,9 +16,22 @@ namespace transport SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, std::shared_ptr router, bool peerTest ): TransportSession (router), m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_Timer (GetService ()), - m_PeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), + m_IsPeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), m_RelayTag (0),m_Data (*this), m_IsDataReceived (false) - { + { + if (router) + { + // we are client + auto address = router->GetSSUAddress (); + if (address) m_IntroKey = address->key; + m_Data.AdjustPacketSize (router); // mtu + } + else + { + // we are server + auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); + if (address) m_IntroKey = address->key; + } m_CreationTime = i2p::util::GetSecondsSinceEpoch (); } @@ -33,13 +46,8 @@ namespace transport void SSUSession::CreateAESandMacKey (const uint8_t * pubKey) { - CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg); uint8_t sharedKey[256]; - if (!dh.Agree (sharedKey, m_DHKeysPair->privateKey, pubKey)) - { - LogPrint (eLogError, "Couldn't create shared key"); - return; - }; + m_DHKeysPair->Agree (pubKey, sharedKey); uint8_t * sessionKey = m_SessionKey, * macKey = m_MacKey; if (sharedKey[0] & 0x80) @@ -68,7 +76,7 @@ namespace transport } memcpy (sessionKey, nonZero, 32); - CryptoPP::SHA256().CalculateDigest(macKey, nonZero, 64 - (nonZero - sharedKey)); + SHA256(nonZero, 64 - (nonZero - sharedKey), macKey); } m_IsSessionKey = true; m_SessionKeyEncryption.SetKey (m_SessionKey); @@ -97,9 +105,8 @@ namespace transport else { // try intro key depending on side - auto introKey = GetIntroKey (); - if (introKey && Validate (buf, len, introKey)) - Decrypt (buf, len, introKey); + if (Validate (buf, len, m_IntroKey)) + Decrypt (buf, len, m_IntroKey); else { // try own intro key @@ -184,7 +191,7 @@ namespace transport void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) { - if (!m_RemoteRouter || !m_DHKeysPair) + if (!IsOutgoing () || !m_DHKeysPair) { LogPrint (eLogWarning, "Unsolicited session created message"); return; @@ -196,7 +203,7 @@ namespace transport uint8_t * payload = buf + sizeof (SSUHeader); uint8_t * y = payload; CreateAESandMacKey (y); - s.Insert (m_DHKeysPair->publicKey, 256); // x + s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x s.Insert (y, 256); // y payload += 256; uint8_t addressSize = *payload; @@ -232,7 +239,7 @@ namespace transport payload += 4; // relayTag payload += 4; // signed on time // decrypt signature - size_t signatureLen = m_RemoteIdentity.GetSignatureLen (); + size_t signatureLen = m_RemoteIdentity->GetSignatureLen (); size_t paddingSize = signatureLen & 0x0F; // %16 if (paddingSize > 0) signatureLen += (16 - paddingSize); //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved @@ -240,8 +247,7 @@ namespace transport m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); // verify if (!s.Verify (m_RemoteIdentity, payload)) - LogPrint (eLogError, "SSU signature verification failed"); - m_RemoteIdentity.DropVerifier (); + LogPrint (eLogError, "Session created SSU signature verification failed"); SendSessionConfirmed (y, ourAddress, addressSize + 2); } @@ -253,31 +259,28 @@ namespace transport payload++; // identity fragment info uint16_t identitySize = bufbe16toh (payload); payload += 2; // size of identity fragment - m_RemoteIdentity.FromBuffer (payload, identitySize); - m_Data.UpdatePacketSize (m_RemoteIdentity.GetIdentHash ()); + SetRemoteIdentity (std::make_shared (payload, identitySize)); + m_Data.UpdatePacketSize (m_RemoteIdentity->GetIdentHash ()); payload += identitySize; // identity + if (m_SignedData) + m_SignedData->Insert (payload, 4); // insert Alice's signed on time payload += 4; // signed-on time - size_t paddingSize = (payload - buf) + m_RemoteIdentity.GetSignatureLen (); + size_t paddingSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen (); paddingSize &= 0x0F; // %16 if (paddingSize > 0) paddingSize = 16 - paddingSize; payload += paddingSize; - // TODO: verify signature (need data from session request), payload points to signature + // verify + if (m_SignedData && !m_SignedData->Verify (m_RemoteIdentity, payload)) + LogPrint (eLogError, "Session confirmed SSU signature verification failed"); m_Data.Send (CreateDeliveryStatusMsg (0)); Established (); } void SSUSession::SendSessionRequest () - { - auto introKey = GetIntroKey (); - if (!introKey) - { - LogPrint (eLogError, "SSU is not supported"); - return; - } - + { uint8_t buf[320 + 18]; // 304 bytes for ipv4, 320 for ipv6 uint8_t * payload = buf + sizeof (SSUHeader); - memcpy (payload, m_DHKeysPair->publicKey, 256); // x + memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); // x bool isV4 = m_RemoteEndpoint.address ().is_v4 (); if (isV4) { @@ -291,13 +294,12 @@ namespace transport } uint8_t iv[16]; - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - rnd.GenerateBlock (iv, 16); // random iv - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, isV4 ? 304 : 320, introKey, iv, introKey); + RAND_bytes (iv, 16); // random iv + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, isV4 ? 304 : 320, m_IntroKey, iv, m_IntroKey); m_Server.Send (buf, isV4 ? 304 : 320, m_RemoteEndpoint); } - void SSUSession::SendRelayRequest (uint32_t iTag, const uint8_t * iKey) + void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer) { auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); if (!address) @@ -308,7 +310,7 @@ namespace transport uint8_t buf[96 + 18]; uint8_t * payload = buf + sizeof (SSUHeader); - htobe32buf (payload, iTag); + htobe32buf (payload, introducer.iTag); payload += 4; *payload = 0; // no address payload++; @@ -318,35 +320,32 @@ namespace transport payload++; memcpy (payload, (const uint8_t *)address->key, 32); payload += 32; - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - htobe32buf (payload, rnd.GenerateWord32 ()); // nonce + RAND_bytes (payload, 4); // nonce uint8_t iv[16]; - rnd.GenerateBlock (iv, 16); // random iv + RAND_bytes (iv, 16); // random iv if (m_State == eSessionStateEstablished) FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, m_SessionKey, iv, m_MacKey); else - FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, iKey, iv, iKey); + FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, introducer.iKey, iv, introducer.iKey); m_Server.Send (buf, 96, m_RemoteEndpoint); } void SSUSession::SendSessionCreated (const uint8_t * x) { - auto introKey = GetIntroKey (); auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : i2p::context.GetRouterInfo ().GetSSUAddress (true); //v4 only - if (!introKey || !address) + if (!address) { LogPrint (eLogError, "SSU is not supported"); return; } - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); SignedData s; // x,y, remote IP, remote port, our IP, our port, relayTag, signed on time s.Insert (x, 256); // x uint8_t buf[384 + 18]; uint8_t * payload = buf + sizeof (SSUHeader); - memcpy (payload, m_DHKeysPair->publicKey, 256); + memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); s.Insert (payload, 256); // y payload += 256; if (m_RemoteEndpoint.address ().is_v4 ()) @@ -378,7 +377,7 @@ namespace transport uint32_t relayTag = 0; if (i2p::context.GetRouterInfo ().IsIntroducer ()) { - relayTag = rnd.GenerateWord32 (); + RAND_bytes((uint8_t *)&relayTag, 4); if (!relayTag) relayTag = 1; m_Server.AddRelay (relayTag, m_RemoteEndpoint); } @@ -386,14 +385,18 @@ namespace transport payload += 4; // relay tag htobe32buf (payload, i2p::util::GetSecondsSinceEpoch ()); // signed on time payload += 4; - s.Insert (payload - 8, 8); // relayTag and signed on time + s.Insert (payload - 8, 4); // relayTag + // we have to store this signed data for session confirmed + // same data but signed on time, it will Alice's there + m_SignedData = std::unique_ptr(new SignedData (s)); + s.Insert (payload - 4, 4); // BOB's signed on time s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature // TODO: fill padding with random data uint8_t iv[16]; - rnd.GenerateBlock (iv, 16); // random iv + RAND_bytes (iv, 16); // random iv // encrypt signature and padding with newly created session key - size_t signatureLen = i2p::context.GetIdentity ().GetSignatureLen (); + size_t signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); size_t paddingSize = signatureLen & 0x0F; // %16 if (paddingSize > 0) signatureLen += (16 - paddingSize); m_SessionKeyEncryption.SetIV (iv); @@ -402,7 +405,7 @@ namespace transport size_t msgLen = payload - buf; // encrypt message with intro key - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CREATED, buf, msgLen, introKey, iv, introKey); + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CREATED, buf, msgLen, m_IntroKey, iv, m_IntroKey); Send (buf, msgLen); } @@ -412,15 +415,15 @@ namespace transport uint8_t * payload = buf + sizeof (SSUHeader); *payload = 1; // 1 fragment payload++; // info - size_t identLen = i2p::context.GetIdentity ().GetFullLen (); // 387+ bytes + size_t identLen = i2p::context.GetIdentity ()->GetFullLen (); // 387+ bytes htobe16buf (payload, identLen); payload += 2; // cursize - i2p::context.GetIdentity ().ToBuffer (payload, identLen); + i2p::context.GetIdentity ()->ToBuffer (payload, identLen); payload += identLen; uint32_t signedOnTime = i2p::util::GetSecondsSinceEpoch (); htobe32buf (payload, signedOnTime); // signed on time payload += 4; - auto signatureLen = i2p::context.GetIdentity ().GetSignatureLen (); + auto signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); size_t paddingSize = ((payload - buf) + signatureLen)%16; if (paddingSize > 0) paddingSize = 16 - paddingSize; // TODO: fill padding @@ -428,7 +431,7 @@ namespace transport // signature SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, our signed on time - s.Insert (m_DHKeysPair->publicKey, 256); // x + s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x s.Insert (y, 256); // y s.Insert (ourAddress, ourAddressLen); // our address/port as seem by party if (m_RemoteEndpoint.address ().is_v4 ()) @@ -443,8 +446,7 @@ namespace transport size_t msgLen = payload - buf; uint8_t iv[16]; - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - rnd.GenerateBlock (iv, 16); // random iv + RAND_bytes (iv, 16); // random iv // encrypt message with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CONFIRMED, buf, msgLen, m_SessionKey, iv, m_MacKey); Send (buf, msgLen); @@ -519,8 +521,7 @@ namespace transport { // ecrypt with Alice's intro key uint8_t iv[16]; - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - rnd.GenerateBlock (iv, 16); // random iv + RAND_bytes (iv, 16); // random iv FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80, introKey, iv, introKey); m_Server.Send (buf, isV4 ? 64 : 80, from); } @@ -546,8 +547,7 @@ namespace transport payload += 2; // port *payload = 0; // challenge size uint8_t iv[16]; - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - rnd.GenerateBlock (iv, 16); // random iv + RAND_bytes (iv, 16); // random iv FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, 48, session->m_SessionKey, iv, session->m_MacKey); m_Server.Send (buf, 48, session->m_RemoteEndpoint); LogPrint (eLogDebug, "SSU relay intro sent"); @@ -602,7 +602,7 @@ namespace transport } void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, - const uint8_t * aesKey, const uint8_t * iv, const uint8_t * macKey) + const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey) { if (len < sizeof (SSUHeader)) { @@ -635,7 +635,7 @@ namespace transport } //TODO: we are using a dirty solution here but should work for now SSUHeader * header = (SSUHeader *)buf; - i2p::context.GetRandomNumberGenerator ().GenerateBlock (header->iv, 16); // random iv + RAND_bytes (header->iv, 16); // random iv m_SessionKeyEncryption.SetIV (header->iv); header->flag = payloadType << 4; // MSB is 0 htobe32buf (&(header->time), i2p::util::GetSecondsSinceEpoch ()); @@ -648,7 +648,7 @@ namespace transport i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, m_MacKey, header->mac); } - void SSUSession::Decrypt (uint8_t * buf, size_t len, const uint8_t * aesKey) + void SSUSession::Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey) { if (len < sizeof (SSUHeader)) { @@ -683,7 +683,7 @@ namespace transport } } - bool SSUSession::Validate (uint8_t * buf, size_t len, const uint8_t * macKey) + bool SSUSession::Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey) { if (len < sizeof (SSUHeader)) { @@ -715,7 +715,7 @@ namespace transport void SSUSession::WaitForConnect () { - if (!m_RemoteRouter) // incoming session + if (!IsOutgoing ()) // incoming session ScheduleConnectTimer (); else LogPrint (eLogError, "SSU wait for connect for outgoing session"); @@ -739,7 +739,7 @@ namespace transport } } - void SSUSession::Introduce (uint32_t iTag, const uint8_t * iKey) + void SSUSession::Introduce (const i2p::data::RouterInfo::Introducer& introducer) { if (m_State == eSessionStateUnknown) { @@ -748,7 +748,7 @@ namespace transport m_Timer.async_wait (std::bind (&SSUSession::HandleConnectTimer, shared_from_this (), std::placeholders::_1)); } - SendRelayRequest (iTag, iKey); + SendRelayRequest (introducer); } void SSUSession::WaitForIntroduction () @@ -777,15 +777,12 @@ namespace transport void SSUSession::Established () { m_State = eSessionStateEstablished; - if (m_DHKeysPair) - { - delete m_DHKeysPair; - m_DHKeysPair = nullptr; - } + m_DHKeysPair = nullptr; + m_SignedData = nullptr; m_Data.Start (); - m_Data.Send (ToSharedI2NPMessage(CreateDatabaseStoreMsg ())); + m_Data.Send (CreateDatabaseStoreMsg ()); transports.PeerConnected (shared_from_this ()); - if (m_PeerTest && (m_RemoteRouter && m_RemoteRouter->IsPeerTesting ())) + if (m_IsPeerTest) SendPeerTest (); ScheduleTermination (); } @@ -816,21 +813,6 @@ namespace transport } } - const uint8_t * SSUSession::GetIntroKey () const - { - if (m_RemoteRouter) - { - // we are client - auto address = m_RemoteRouter->GetSSUAddress (); - return address ? (const uint8_t *)address->key : nullptr; - } - else - { - // we are server - auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); - return address ? (const uint8_t *)address->key : nullptr; - } - } void SSUSession::SendI2NPMessages (const std::vector >& msgs) { @@ -994,8 +976,7 @@ namespace transport memcpy (payload, introKey, 32); // intro key // send - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - rnd.GenerateBlock (iv, 16); // random iv + RAND_bytes (iv, 16); // random iv if (toAddress) { // encrypt message with specified intro key @@ -1021,9 +1002,10 @@ namespace transport LogPrint (eLogError, "SSU is not supported. Can't send peer test"); return; } - uint32_t nonce = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); + uint32_t nonce; + RAND_bytes ((uint8_t *)&nonce, 4); if (!nonce) nonce = 1; - m_PeerTest = false; + m_IsPeerTest = false; m_Server.NewPeerTest (nonce, ePeerTestParticipantAlice1); SendPeerTest (nonce, 0, 0, address->key, false, false); // address and port always zero for Alice } diff --git a/SSUSession.h b/SSUSession.h index 6c222185..99be99a8 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -4,8 +4,7 @@ #include #include #include -#include "aes.h" -#include "hmac.h" +#include "Crypto.h" #include "I2NPProtocol.h" #include "TransportSession.h" #include "SSUData.h" @@ -70,7 +69,7 @@ namespace transport void Connect (); void WaitForConnect (); - void Introduce (uint32_t iTag, const uint8_t * iKey); + void Introduce (const i2p::data::RouterInfo::Introducer& introducer); void WaitForIntroduction (); void Close (); void Done (); @@ -85,6 +84,7 @@ namespace transport void SendKeepAlive (); uint32_t GetRelayTag () const { return m_RelayTag; }; + const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; }; uint32_t GetCreationTime () const { return m_CreationTime; }; void FlushData (); @@ -98,7 +98,7 @@ namespace transport void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session void ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); void SendSessionRequest (); - void SendRelayRequest (uint32_t iTag, const uint8_t * iKey); + void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer); void ProcessSessionCreated (uint8_t * buf, size_t len); void SendSessionCreated (const uint8_t * x); void ProcessSessionConfirmed (uint8_t * buf, size_t len); @@ -120,12 +120,11 @@ namespace transport void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key void Send (const uint8_t * buf, size_t size); - void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const uint8_t * aesKey, const uint8_t * iv, const uint8_t * macKey); + void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey); void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key - void Decrypt (uint8_t * buf, size_t len, const uint8_t * aesKey); + void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey); void DecryptSessionKey (uint8_t * buf, size_t len); - bool Validate (uint8_t * buf, size_t len, const uint8_t * macKey); - const uint8_t * GetIntroKey () const; + bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey); void ScheduleTermination (); void HandleTerminationTimer (const boost::system::error_code& ecode); @@ -136,7 +135,7 @@ namespace transport SSUServer& m_Server; boost::asio::ip::udp::endpoint m_RemoteEndpoint; boost::asio::deadline_timer m_Timer; - bool m_PeerTest; + bool m_IsPeerTest; SessionState m_State; bool m_IsSessionKey; uint32_t m_RelayTag; @@ -144,9 +143,11 @@ namespace transport i2p::crypto::CBCDecryption m_SessionKeyDecryption; i2p::crypto::AESKey m_SessionKey; i2p::crypto::MACKey m_MacKey; + i2p::data::RouterInfo::IntroKey m_IntroKey; uint32_t m_CreationTime; // seconds since epoch SSUData m_Data; bool m_IsDataReceived; + std::unique_ptr m_SignedData; // we need it for SessionConfirmed only }; diff --git a/Signature.cpp b/Signature.cpp index 603ba6eb..40947aa6 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -1,6 +1,4 @@ #include -#include -#include #include "Log.h" #include "Signature.h" @@ -14,82 +12,360 @@ namespace crypto Ed25519 () { - q = CryptoPP::Integer::Power2 (255) - CryptoPP::Integer (19); // 2^255-19 - l = CryptoPP::Integer::Power2 (252) + CryptoPP::Integer ("27742317777372353535851937790883648493"); + BN_CTX * ctx = BN_CTX_new (); + BIGNUM * two = BN_new (), * tmp = BN_new (); + BN_set_word (two, 2); + + q = BN_new (); + // 2^255-19 + BN_set_word (tmp, 255); + BN_exp (q, two, tmp, ctx); + BN_sub_word (q, 19); + // q_2 = q-2 + q_2 = BN_dup (q); + BN_sub_word (q_2, 2); + + l = BN_new (); // 2^252 + 27742317777372353535851937790883648493 - d = CryptoPP::Integer (-121665) * CryptoPP::Integer (121666).InverseMod (q); // -121665/121666 - I = a_exp_b_mod_c (CryptoPP::Integer::Two (), (q - CryptoPP::Integer::One ()).DividedBy (4), q); - B = DecodePoint (CryptoPP::Integer (4)*CryptoPP::Integer (5).InverseMod (q)); + BN_set_word (tmp, 252); + BN_exp (l, two, tmp, ctx); + two_252_2 = BN_dup (l); + BN_dec2bn (&tmp, "27742317777372353535851937790883648493"); + BN_add (l, l, tmp); + BN_sub_word (two_252_2, 2); // 2^252 - 2 + + // -121665*inv(121666) + d = BN_new (); + BN_set_word (tmp, 121666); + Inv (tmp, ctx); + BN_set_word (d, 121665); + BN_set_negative (d, -1); + BN_mul (d, d, tmp, ctx); + + // 2^((q-1)/4) + I = BN_new (); + BN_free (tmp); + tmp = BN_dup (q); + BN_sub_word (tmp, 1); + BN_div_word (tmp, 4); + BN_mod_exp (I, two, tmp, q, ctx); + + // 4*inv(5) + BIGNUM * By = BN_new (); + BN_set_word (By, 5); + Inv (By, ctx); + BN_mul_word (By, 4); + BIGNUM * Bx = RecoverX (By, ctx); + BN_mod (Bx, Bx, q, ctx); // % q + BN_mod (By, By, q, ctx); // % q + B = {Bx, By}; + + BN_free (two); + BN_free (tmp); + + // precalculate Bi + Bi[0] = { BN_dup (Bx), BN_dup (By) }; + for (int i = 1; i < 256; i++) + Bi[i] = Double (Bi[i-1], ctx); + + BN_CTX_free (ctx); } - CryptoPP::ECP::Point DecodePublicKey (const uint8_t * key) const + ~Ed25519 () { - return DecodePoint (CryptoPP::Integer (key, 32)); + BN_free (q); + BN_free (l); + BN_free (d); + BN_free (I); + BN_free (q_2); + BN_free (two_252_2); } - CryptoPP::ECP::Point GeneratePublicKey (const uint8_t * privateKey) const + + EDDSAPoint GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const { - return Mul (B, CryptoPP::Integer (privateKey, 32)); + return MulB (expandedPrivateKey, ctx); // left half of expanded key, considered as Little Endian } - private: - - CryptoPP::ECP::Point Sum (const CryptoPP::ECP::Point& p1, const CryptoPP::ECP::Point& p2) const + EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const { - CryptoPP::Integer m = d*p1.x*p2.x*p1.y*p2.y, - x = a_times_b_mod_c (p1.x*p2.y + p2.x*p1.y, (CryptoPP::Integer::One() + m).InverseMod (q), q), - y = a_times_b_mod_c (p1.y*p2.y + p1.x*p2.x, (CryptoPP::Integer::One() - m).InverseMod (q), q); - return CryptoPP::ECP::Point {x, y}; + return DecodePoint (buf, ctx); } - CryptoPP::ECP::Point Mul (const CryptoPP::ECP::Point& p, const CryptoPP::Integer& e) const + void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf) const { - CryptoPP::ECP::Point res {0, 1}; - if (!e.IsZero ()) + EncodePoint (publicKey, buf); + } + + bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature, BN_CTX * ctx) const + { + BIGNUM * h = DecodeBN (digest, 64); + // signature 0..31 - R, 32..63 - S + bool passed = MulB (signature + EDDSA25519_SIGNATURE_LENGTH/2, ctx) /*S*/ == + Sum (DecodePoint (signature, ctx) /*R*/, Mul (publicKey, h, ctx), ctx); + BN_free (h); + if (!passed) + LogPrint (eLogError, "25519 signature verification failed"); + return passed; + } + + void Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, + uint8_t * signature, BN_CTX * bnCtx) const + { + // calculate r + SHA512_CTX ctx; + SHA512_Init (&ctx); + SHA512_Update (&ctx, expandedPrivateKey + EDDSA25519_PRIVATE_KEY_LENGTH, EDDSA25519_PRIVATE_KEY_LENGTH); // right half of expanded key + SHA512_Update (&ctx, buf, len); // data + uint8_t digest[64]; + SHA512_Final (digest, &ctx); + BIGNUM * r = DecodeBN (digest, 32); // DecodeBN (digest, 64); // for test vectors + // calculate R + uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf + EncodePoint (MulB (digest, bnCtx), R); // EncodePoint (Mul (B, r, bnCtx), R); // for test vectors + // calculate S + SHA512_Init (&ctx); + SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R + SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key + SHA512_Update (&ctx, buf, len); // data + SHA512_Final (digest, &ctx); + BIGNUM * s = DecodeBN (digest, 64); + // S = (r + s*a) % l + BIGNUM * a = DecodeBN (expandedPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH); // left half of expanded key + BN_mul (s, s, a, bnCtx); + BN_add (s, s, r); + BN_mod (s, s, l, bnCtx); // % l + memcpy (signature, R, EDDSA25519_SIGNATURE_LENGTH/2); + EncodeBN (s, signature + EDDSA25519_SIGNATURE_LENGTH/2, EDDSA25519_SIGNATURE_LENGTH/2); // S + BN_free (r); BN_free (s); BN_free (a); + } + + private: + + EDDSAPoint Sum (const EDDSAPoint& p1, const EDDSAPoint& p2, BN_CTX * ctx) const + { + BIGNUM * xx = BN_new (), * yy = BN_new (); + // m = d*p1.x*p2.x*p1.y*p2.y + BN_mul (xx, p1.x, p2.x, ctx); + BN_mul (yy, p1.y, p2.y, ctx); + BIGNUM * m = BN_dup (d); + BN_mul (m, m, xx, ctx); + BN_mul (m, m, yy, ctx); + // x = (p1.x*p2.y + p2.x*p1.y)*inv(1 + m) + // y = (p1.y*p2.y + p1.x*p2.x)*inv(1 - m) + + // use one inversion instead two + // m1 = 1-m + BIGNUM * m1 = BN_new (); + BN_one (m1); + BN_sub (m1, m1, m); + // m = m+1 + BN_add_word (m, 1); + // y = (p1.y*p2.y + p1.x*p2.x)*m + BIGNUM * y = BN_new (); + BN_add (y, xx, yy); + BN_mod_mul (y, y, m, q, ctx); + // x = (p1.x*p2.y + p2.x*p1.y)*m1 + BIGNUM * x = BN_new (); + BN_mul (yy, p1.x, p2.y, ctx); + BN_mul (xx, p2.x, p1.y, ctx); + BN_add (x, xx, yy); + BN_mod_mul (x, x, m1, q, ctx); + // denominator m = m*m1 + BN_mod_mul (m, m, m1, q, ctx); + Inv (m, ctx); + BN_mod_mul (x, x, m, q, ctx); // x = x/m + BN_mod_mul (y, y, m, q, ctx); // y = y/m + + BN_free (xx);BN_free (yy); BN_free (m); BN_free (m1); + return EDDSAPoint {x, y}; + } + + EDDSAPoint Double (const EDDSAPoint& p, BN_CTX * ctx) const + { + BIGNUM * pxy = BN_new (); + BN_mul (pxy, p.x, p.y, ctx); + // m = d*(p.x*p.y)^2 + BIGNUM * m = BN_new (); + BN_sqr (m, pxy, ctx); + BN_mul (m, m, d, ctx); + // x = (2*p.x*p.y)*inv(1 + m) + // y = (p.x^2 + p.y^2)*inv(1 - m) + + // use one inversion instead two + // m1 = 1-m + BIGNUM * m1 = BN_new (); + BN_one (m1); + BN_sub (m1, m1, m); + // m = m+1 + BN_add_word (m, 1); + // x = 2*p.x*p.y*m1 + BN_mul_word (pxy, 2); + BIGNUM * x = BN_new (); + BN_mod_mul (x, pxy, m1, q, ctx); + // y = (p.x^2 + p.y^2)*m + BIGNUM * y = BN_new (); + BN_sqr (pxy, p.x, ctx); + BN_sqr (y, p.y, ctx); + BN_add (pxy, pxy, y); + BN_mod_mul (y, pxy, m, q, ctx); + // denominator m = m*m1 + BN_mod_mul (m, m, m1, q, ctx); + Inv (m, ctx); + BN_mod_mul (x, x, m, q, ctx); // x = x/m + BN_mod_mul (y, y, m, q, ctx); // y = y/m + + BN_free (pxy); BN_free (m); BN_free (m1); + return EDDSAPoint {x, y}; + } + + EDDSAPoint Mul (const EDDSAPoint& p, const BIGNUM * e, BN_CTX * ctx) const + { + BIGNUM * zero = BN_new (), * one = BN_new (); + BN_zero (zero); BN_one (one); + EDDSAPoint res {zero, one}; + if (!BN_is_zero (e)) { - auto bitCount = e.BitCount (); + int bitCount = BN_num_bits (e); for (int i = bitCount - 1; i >= 0; i--) { - res = Sum (res, res); - if (e.GetBit (i)) res = Sum (res, p); + res = Double (res, ctx); + if (BN_is_bit_set (e, i)) res = Sum (res, p, ctx); } } return res; } - - bool IsOnCurve (const CryptoPP::ECP::Point& p) const + + EDDSAPoint MulB (const uint8_t * e, BN_CTX * ctx) const // B*e. e is 32 bytes Little Endian { - auto x2 = p.x.Squared(), y2 = p.y.Squared (); - return (y2 - x2 - CryptoPP::Integer::One() - d*x2*y2).Modulo (q).IsZero (); + BIGNUM * zero = BN_new (), * one = BN_new (); + BN_zero (zero); BN_one (one); + EDDSAPoint res {zero, one}; + for (int i = 0; i < 32; i++) + { + for (int j = 0; j < 8; j++) + if (e[i] & (1 << j)) // from lowest to highest bit + res = Sum (res, Bi[i*8 + j], ctx); + } + return res; + } + + void Inv (BIGNUM * x, BN_CTX * ctx) const + { + BN_mod_exp (x, x, q_2, q, ctx); + } + + bool IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const + { + BIGNUM * x2 = BN_new (); + BN_sqr (x2, p.x, ctx); // x^2 + BIGNUM * y2 = BN_new (); + BN_sqr (y2, p.y, ctx); // y^2 + // y^2 - x^2 - 1 - d*x^2*y^2 + BIGNUM * tmp = BN_new (); + BN_mul (tmp, d, x2, ctx); + BN_mul (tmp, tmp, y2, ctx); + BN_sub (tmp, y2, tmp); + BN_sub (tmp, tmp, x2); + BN_sub_word (tmp, 1); + BN_mod (tmp, tmp, q, ctx); // % q + bool ret = BN_is_zero (tmp); + BN_free (x2); + BN_free (y2); + BN_free (tmp); + return ret; } - CryptoPP::Integer RecoverX (const CryptoPP::Integer& y) const + BIGNUM * RecoverX (const BIGNUM * y, BN_CTX * ctx) const { - auto y2 = y.Squared (); - auto xx = (y2 - CryptoPP::Integer::One())*(d*y2 + CryptoPP::Integer::One()).InverseMod (q); - auto x = a_exp_b_mod_c (xx, (q + CryptoPP::Integer (3)).DividedBy (8), q); - if (!(x.Squared () - xx).Modulo (q).IsZero ()) - x = a_times_b_mod_c (x, I, q); - if (x.IsOdd ()) x = q - x; + BIGNUM * y2 = BN_new (); + BN_sqr (y2, y, ctx); // y^2 + // xx = (y^2 -1)*inv(d*y^2 +1) + BIGNUM * xx = BN_new (); + BN_mul (xx, d, y2, ctx); + BN_add_word (xx, 1); + Inv (xx, ctx); + BN_sub_word (y2, 1); + BN_mul (xx, y2, xx, ctx); + // x = srqt(xx) = xx^(2^252-2) + BIGNUM * x = BN_new (); + BN_mod_exp (x, xx, two_252_2, q, ctx); + // check (x^2 -xx) % q + BN_sqr (y2, x, ctx); + BN_mod_sub (y2, y2, xx, q, ctx); + if (!BN_is_zero (y2)) + BN_mod_mul (x, x, I, q, ctx); + if (BN_is_odd (x)) + BN_sub (x, q, x); + BN_free (y2); + BN_free (xx); return x; } - CryptoPP::ECP::Point DecodePoint (const CryptoPP::Integer& y) const + EDDSAPoint DecodePoint (const uint8_t * buf, BN_CTX * ctx) const { - auto x = RecoverX (y); - CryptoPP::ECP::Point p {x, y}; - if (!IsOnCurve (p)) + // buf is 32 bytes Little Endian, convert it to Big Endian + uint8_t buf1[EDDSA25519_PUBLIC_KEY_LENGTH]; + for (size_t i = 0; i < EDDSA25519_PUBLIC_KEY_LENGTH/2; i++) // invert bytes { - LogPrint (eLogError, "Decoded point is not on 25519"); - return CryptoPP::ECP::Point {0, 1}; + buf1[i] = buf[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i]; + buf1[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i] = buf[i]; } + bool isHighestBitSet = buf1[0] & 0x80; + if (isHighestBitSet) + buf1[0] &= 0x7f; // clear highest bit + BIGNUM * y = BN_new (); + BN_bin2bn (buf1, EDDSA25519_PUBLIC_KEY_LENGTH, y); + auto x = RecoverX (y, ctx); + if (BN_is_bit_set (x, 0) != isHighestBitSet) + BN_sub (x, q, x); // x = q - x + EDDSAPoint p {x, y}; + if (!IsOnCurve (p, ctx)) + LogPrint (eLogError, "Decoded point is not on 25519"); return p; } + + void EncodePoint (const EDDSAPoint& p, uint8_t * buf) const + { + EncodeBN (p.y, buf,EDDSA25519_PUBLIC_KEY_LENGTH); + if (BN_is_bit_set (p.x, 0)) // highest bit + buf[EDDSA25519_PUBLIC_KEY_LENGTH - 1] |= 0x80; // set highest bit + } + + BIGNUM * DecodeBN (const uint8_t * buf, size_t len) const + { + // buf is Little Endian convert it to Big Endian + uint8_t buf1[len]; + for (size_t i = 0; i < len/2; i++) // invert bytes + { + buf1[i] = buf[len -1 - i]; + buf1[len -1 - i] = buf[i]; + } + BIGNUM * res = BN_new (); + BN_bin2bn (buf1, len, res); + return res; + } + + void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const + { + bn2buf (bn, buf, len); + // To Little Endian + for (size_t i = 0; i < len/2; i++) // invert bytes + { + uint8_t tmp = buf[i]; + buf[i] = buf[len -1 - i]; + buf[len -1 - i] = tmp; + } + } private: - - CryptoPP::Integer q, l, d, I; - CryptoPP::ECP::Point B; // base point + + BIGNUM * q, * l, * d, * I; + EDDSAPoint B; // base point + // transient values + BIGNUM * q_2; // q-2 + BIGNUM * two_252_2; // 2^252-2 + EDDSAPoint Bi[256]; // m_Bi[i] = 2^i*B for i-th bit }; static std::unique_ptr g_Ed25519; @@ -98,22 +374,44 @@ namespace crypto if (!g_Ed25519) g_Ed25519.reset (new Ed25519 ()); return g_Ed25519; - } + } - EDDSA25519Verifier::EDDSA25519Verifier (const uint8_t * signingKey): - m_PublicKey (GetEd25519 ()->DecodePublicKey (signingKey)) + EDDSA25519Verifier::EDDSA25519Verifier (const uint8_t * signingKey): + m_Ctx (BN_CTX_new ()), + m_PublicKey (GetEd25519 ()->DecodePublicKey (signingKey, m_Ctx)) { + memcpy (m_PublicKeyEncoded, signingKey, EDDSA25519_PUBLIC_KEY_LENGTH); } bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { - return true; // TODO: + SHA512_CTX ctx; + SHA512_Init (&ctx); + SHA512_Update (&ctx, signature, EDDSA25519_SIGNATURE_LENGTH/2); // R + SHA512_Update (&ctx, m_PublicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key + SHA512_Update (&ctx, buf, len); // data + uint8_t digest[64]; + SHA512_Final (digest, &ctx); + return GetEd25519 ()->Verify (m_PublicKey, digest, signature, m_Ctx); } - void EDDSA25519Signer::Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const + EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey): + m_Ctx (BN_CTX_new ()) + { + // expand key + SHA512 (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH, m_ExpandedPrivateKey); + m_ExpandedPrivateKey[0] &= 0xF8; // drop last 3 bits + m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0x1F; // drop first 3 bits + m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] |= 0x40; // set second bit + // generate and encode public key + auto publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, m_Ctx); + GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded); + } + + void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const { - // TODO + GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature, m_Ctx); } } } diff --git a/Signature.h b/Signature.h index acfaa62f..423559a3 100644 --- a/Signature.h +++ b/Signature.h @@ -2,13 +2,15 @@ #define SIGNATURE_H__ #include -#include -#include -#include -#include -#include -#include -#include "CryptoConst.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "Crypto.h" namespace i2p { @@ -30,7 +32,7 @@ namespace crypto public: virtual ~Signer () {}; - virtual void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const = 0; + virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0; }; const size_t DSA_PUBLIC_KEY_LENGTH = 128; @@ -42,13 +44,32 @@ namespace crypto DSAVerifier (const uint8_t * signingKey) { - m_PublicKey.Initialize (dsap, dsaq, dsag, CryptoPP::Integer (signingKey, DSA_PUBLIC_KEY_LENGTH)); + m_PublicKey = DSA_new (); + m_PublicKey->p = BN_dup (dsap); + m_PublicKey->q = BN_dup (dsaq); + m_PublicKey->g = BN_dup (dsag); + m_PublicKey->priv_key = NULL; + m_PublicKey->pub_key = BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL); } - + + ~DSAVerifier () + { + DSA_free (m_PublicKey); + } + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { - CryptoPP::DSA::Verifier verifier (m_PublicKey); - return verifier.VerifyMessage (buf, len, signature, DSA_SIGNATURE_LENGTH); + // calculate SHA1 digest + uint8_t digest[20]; + SHA1 (buf, len, digest); + // signature + DSA_SIG * sig = DSA_SIG_new(); + sig->r = BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL); + sig->s = BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL); + // DSA verification + int ret = DSA_do_verify (digest, 20, sig, m_PublicKey); + DSA_SIG_free(sig); + return ret; } size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; }; @@ -56,7 +77,7 @@ namespace crypto private: - CryptoPP::DSA::PublicKey m_PublicKey; + DSA * m_PublicKey; }; class DSASigner: public Signer @@ -65,189 +86,219 @@ namespace crypto DSASigner (const uint8_t * signingPrivateKey) { - m_PrivateKey.Initialize (dsap, dsaq, dsag, CryptoPP::Integer (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH)); + m_PrivateKey = DSA_new (); + m_PrivateKey->p = BN_dup (dsap); + m_PrivateKey->q = BN_dup (dsaq); + m_PrivateKey->g = BN_dup (dsag); + m_PrivateKey->priv_key = BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL); + m_PrivateKey->pub_key = NULL; } - void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const + ~DSASigner () { - CryptoPP::DSA::Signer signer (m_PrivateKey); - signer.SignMessage (rnd, buf, len, signature); + DSA_free (m_PrivateKey); + } + + void Sign (const uint8_t * buf, int len, uint8_t * signature) const + { + uint8_t digest[20]; + SHA1 (buf, len, digest); + DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey); + bn2buf (sig->r, signature, DSA_SIGNATURE_LENGTH/2); + bn2buf (sig->s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2); + DSA_SIG_free(sig); } private: - CryptoPP::DSA::PrivateKey m_PrivateKey; + DSA * m_PrivateKey; }; - inline void CreateDSARandomKeys (CryptoPP::RandomNumberGenerator& rnd, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + inline void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { - CryptoPP::DSA::PrivateKey privateKey; - CryptoPP::DSA::PublicKey publicKey; - privateKey.Initialize (rnd, dsap, dsaq, dsag); - privateKey.MakePublicKey (publicKey); - privateKey.GetPrivateExponent ().Encode (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); - publicKey.GetPublicElement ().Encode (signingPublicKey, DSA_PUBLIC_KEY_LENGTH); + DSA * dsa = DSA_new (); + dsa->p = BN_dup (dsap); + dsa->q = BN_dup (dsaq); + dsa->g = BN_dup (dsag); + dsa->priv_key = NULL; + dsa->pub_key = NULL; + DSA_generate_key (dsa); + bn2buf (dsa->priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); + bn2buf (dsa->pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH); + DSA_free (dsa); + } - template + struct SHA256Hash + { + static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) + { + SHA256 (buf, len, digest); + } + + enum { hashLen = 32 }; + }; + + struct SHA384Hash + { + static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) + { + SHA384 (buf, len, digest); + } + + enum { hashLen = 48 }; + }; + + struct SHA512Hash + { + static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) + { + SHA512 (buf, len, digest); + } + + enum { hashLen = 64 }; + }; + + template class ECDSAVerifier: public Verifier { public: - template - ECDSAVerifier (Curve curve, const uint8_t * signingKey) + ECDSAVerifier (const uint8_t * signingKey) { - m_PublicKey.Initialize (curve, - CryptoPP::ECP::Point (CryptoPP::Integer (signingKey, keyLen/2), - CryptoPP::Integer (signingKey + keyLen/2, keyLen/2))); + m_PublicKey = EC_KEY_new_by_curve_name (curve); + EC_KEY_set_public_key_affine_coordinates (m_PublicKey, + BN_bin2bn (signingKey, keyLen/2, NULL), + BN_bin2bn (signingKey + keyLen/2, keyLen/2, NULL)); } + ~ECDSAVerifier () + { + EC_KEY_free (m_PublicKey); + } + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { - typename CryptoPP::ECDSA::Verifier verifier (m_PublicKey); - return verifier.VerifyMessage (buf, len, signature, keyLen); // signature length + uint8_t digest[Hash::hashLen]; + Hash::CalculateHash (buf, len, digest); + ECDSA_SIG * sig = ECDSA_SIG_new(); + sig->r = BN_bin2bn (signature, GetSignatureLen ()/2, NULL); + sig->s = BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL); + // ECDSA verification + int ret = ECDSA_do_verify (digest, Hash::hashLen, sig, m_PublicKey); + ECDSA_SIG_free(sig); + return ret; } - + size_t GetPublicKeyLen () const { return keyLen; }; size_t GetSignatureLen () const { return keyLen; }; // signature length = key length + private: - typename CryptoPP::ECDSA::PublicKey m_PublicKey; + EC_KEY * m_PublicKey; }; - template + template class ECDSASigner: public Signer { public: - template - ECDSASigner (Curve curve, const uint8_t * signingPrivateKey, size_t keyLen) + ECDSASigner (const uint8_t * signingPrivateKey) { - m_PrivateKey.Initialize (curve, CryptoPP::Integer (signingPrivateKey, keyLen/2)); // private key length + m_PrivateKey = EC_KEY_new_by_curve_name (curve); + EC_KEY_set_private_key (m_PrivateKey, BN_bin2bn (signingPrivateKey, keyLen/2, NULL)); } - void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const + ~ECDSASigner () { - typename CryptoPP::ECDSA::Signer signer (m_PrivateKey); - signer.SignMessage (rnd, buf, len, signature); + EC_KEY_free (m_PrivateKey); + } + + void Sign (const uint8_t * buf, int len, uint8_t * signature) const + { + uint8_t digest[Hash::hashLen]; + Hash::CalculateHash (buf, len, digest); + ECDSA_SIG * sig = ECDSA_do_sign (digest, Hash::hashLen, m_PrivateKey); + // signatureLen = keyLen + bn2buf (sig->r, signature, keyLen/2); + bn2buf (sig->s, signature + keyLen/2, keyLen/2); + ECDSA_SIG_free(sig); } private: - typename CryptoPP::ECDSA::PrivateKey m_PrivateKey; + EC_KEY * m_PrivateKey; }; - template - inline void CreateECDSARandomKeys (CryptoPP::RandomNumberGenerator& rnd, Curve curve, - size_t keyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + inline void CreateECDSARandomKeys (int curve, size_t keyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { - typename CryptoPP::ECDSA::PrivateKey privateKey; - typename CryptoPP::ECDSA::PublicKey publicKey; - privateKey.Initialize (rnd, curve); - privateKey.MakePublicKey (publicKey); - privateKey.GetPrivateExponent ().Encode (signingPrivateKey, keyLen/2); - auto q = publicKey.GetPublicElement (); - q.x.Encode (signingPublicKey, keyLen/2); - q.y.Encode (signingPublicKey + keyLen/2, keyLen/2); - } + EC_KEY * signingKey = EC_KEY_new_by_curve_name (curve); + EC_KEY_generate_key (signingKey); + bn2buf (EC_KEY_get0_private_key (signingKey), signingPrivateKey, keyLen/2); + BIGNUM * x = BN_new(), * y = BN_new(); + EC_POINT_get_affine_coordinates_GFp (EC_KEY_get0_group(signingKey), + EC_KEY_get0_public_key (signingKey), x, y, NULL); + bn2buf (x, signingPublicKey, keyLen/2); + bn2buf (y, signingPublicKey + keyLen/2, keyLen/2); + BN_free (x); BN_free (y); + EC_KEY_free (signingKey); + } // ECDSA_SHA256_P256 const size_t ECDSAP256_KEY_LENGTH = 64; - class ECDSAP256Verifier: public ECDSAVerifier + typedef ECDSAVerifier ECDSAP256Verifier; + typedef ECDSASigner ECDSAP256Signer; + + inline void CreateECDSAP256RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { - public: - - ECDSAP256Verifier (const uint8_t * signingKey): - ECDSAVerifier (CryptoPP::ASN1::secp256r1(), signingKey) - { - } - }; - - class ECDSAP256Signer: public ECDSASigner - { - public: - - ECDSAP256Signer (const uint8_t * signingPrivateKey): - ECDSASigner (CryptoPP::ASN1::secp256r1(), signingPrivateKey, ECDSAP256_KEY_LENGTH) - { - } - }; - - inline void CreateECDSAP256RandomKeys (CryptoPP::RandomNumberGenerator& rnd, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) - { - CreateECDSARandomKeys (rnd, CryptoPP::ASN1::secp256r1(), ECDSAP256_KEY_LENGTH, signingPrivateKey, signingPublicKey); + CreateECDSARandomKeys (NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH, signingPrivateKey, signingPublicKey); } // ECDSA_SHA384_P384 const size_t ECDSAP384_KEY_LENGTH = 96; - class ECDSAP384Verifier: public ECDSAVerifier + typedef ECDSAVerifier ECDSAP384Verifier; + typedef ECDSASigner ECDSAP384Signer; + + inline void CreateECDSAP384RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { - public: - - ECDSAP384Verifier (const uint8_t * signingKey): - ECDSAVerifier (CryptoPP::ASN1::secp384r1(), signingKey) - { - } - }; - - class ECDSAP384Signer: public ECDSASigner - { - public: - - ECDSAP384Signer (const uint8_t * signingPrivateKey): - ECDSASigner (CryptoPP::ASN1::secp384r1(), signingPrivateKey, ECDSAP384_KEY_LENGTH) - { - } - }; - - inline void CreateECDSAP384RandomKeys (CryptoPP::RandomNumberGenerator& rnd, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) - { - CreateECDSARandomKeys (rnd, CryptoPP::ASN1::secp384r1(), ECDSAP384_KEY_LENGTH, signingPrivateKey, signingPublicKey); + CreateECDSARandomKeys (NID_secp384r1, ECDSAP384_KEY_LENGTH, signingPrivateKey, signingPublicKey); } // ECDSA_SHA512_P521 const size_t ECDSAP521_KEY_LENGTH = 132; - class ECDSAP521Verifier: public ECDSAVerifier + typedef ECDSAVerifier ECDSAP521Verifier; + typedef ECDSASigner ECDSAP521Signer; + + inline void CreateECDSAP521RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { - public: - - ECDSAP521Verifier (const uint8_t * signingKey): - ECDSAVerifier (CryptoPP::ASN1::secp521r1(), signingKey) - { - } - }; - - class ECDSAP521Signer: public ECDSASigner - { - public: - - ECDSAP521Signer (const uint8_t * signingPrivateKey): - ECDSASigner (CryptoPP::ASN1::secp521r1(), signingPrivateKey, ECDSAP521_KEY_LENGTH) - { - } - }; - - inline void CreateECDSAP521RandomKeys (CryptoPP::RandomNumberGenerator& rnd, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) - { - CreateECDSARandomKeys (rnd, CryptoPP::ASN1::secp521r1(), ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey); + CreateECDSARandomKeys (NID_secp521r1, ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey); } // RSA - template + template class RSAVerifier: public Verifier { public: RSAVerifier (const uint8_t * signingKey) { - m_PublicKey.Initialize (CryptoPP::Integer (signingKey, keyLen), CryptoPP::Integer (rsae)); + m_PublicKey = RSA_new (); + memset (m_PublicKey, 0, sizeof (RSA)); + m_PublicKey->e = BN_dup (rsae); + m_PublicKey->n = BN_bin2bn (signingKey, keyLen, NULL); } + ~RSAVerifier () + { + RSA_free (m_PublicKey); + } + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { - typename CryptoPP::RSASS::Verifier verifier (m_PublicKey); - return verifier.VerifyMessage (buf, len, signature, keyLen); // signature length + uint8_t digest[Hash::hashLen]; + Hash::CalculateHash (buf, len, digest); + return RSA_verify (type, digest, Hash::hashLen, signature, GetSignatureLen (), m_PublicKey); } size_t GetPublicKeyLen () const { return keyLen; } size_t GetSignatureLen () const { return keyLen; } @@ -255,163 +306,92 @@ namespace crypto private: - CryptoPP::RSA::PublicKey m_PublicKey; + RSA * m_PublicKey; }; - template + template class RSASigner: public Signer { public: - RSASigner (const uint8_t * signingPrivateKey, size_t keyLen) + RSASigner (const uint8_t * signingPrivateKey) { - m_PrivateKey.Initialize (CryptoPP::Integer (signingPrivateKey, keyLen/2), - rsae, - CryptoPP::Integer (signingPrivateKey + keyLen/2, keyLen/2)); + m_PrivateKey = RSA_new (); + memset (m_PrivateKey, 0, sizeof (RSA)); + m_PrivateKey->e = BN_dup (rsae); + m_PrivateKey->n = BN_bin2bn (signingPrivateKey, keyLen, NULL); + m_PrivateKey->d = BN_bin2bn (signingPrivateKey + keyLen, keyLen, NULL); } - void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const + ~RSASigner () { - typename CryptoPP::RSASS::Signer signer (m_PrivateKey); - signer.SignMessage (rnd, buf, len, signature); + RSA_free (m_PrivateKey); + } + + void Sign (const uint8_t * buf, int len, uint8_t * signature) const + { + uint8_t digest[Hash::hashLen]; + Hash::CalculateHash (buf, len, digest); + unsigned int signatureLen = keyLen; + RSA_sign (type, digest, Hash::hashLen, signature, &signatureLen, m_PrivateKey); } private: - CryptoPP::RSA::PrivateKey m_PrivateKey; + RSA * m_PrivateKey; }; - inline void CreateRSARandomKeys (CryptoPP::RandomNumberGenerator& rnd, - size_t publicKeyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + inline void CreateRSARandomKeys (size_t publicKeyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { - CryptoPP::RSA::PrivateKey privateKey; - privateKey.Initialize (rnd, publicKeyLen*8, rsae); - privateKey.GetModulus ().Encode (signingPrivateKey, publicKeyLen); - privateKey.GetPrivateExponent ().Encode (signingPrivateKey + publicKeyLen, publicKeyLen); - privateKey.GetModulus ().Encode (signingPublicKey, publicKeyLen); + RSA * rsa = RSA_new (); + RSA_generate_key_ex (rsa, publicKeyLen*8, rsae, NULL); + bn2buf (rsa->n, signingPrivateKey, publicKeyLen); + bn2buf (rsa->d, signingPrivateKey + publicKeyLen, publicKeyLen); + bn2buf (rsa->n, signingPublicKey, publicKeyLen); + RSA_free (rsa); } - // RSA_SHA256_2048 const size_t RSASHA2562048_KEY_LENGTH = 256; - class RSASHA2562048Verifier: public RSAVerifier - { - public: - - RSASHA2562048Verifier (const uint8_t * signingKey): RSAVerifier (signingKey) - { - } - }; - - class RSASHA2562048Signer: public RSASigner - { - public: - - RSASHA2562048Signer (const uint8_t * signingPrivateKey): - RSASigner (signingPrivateKey, RSASHA2562048_KEY_LENGTH*2) - { - } - }; + typedef RSAVerifier RSASHA2562048Verifier; + typedef RSASigner RSASHA2562048Signer; // RSA_SHA384_3072 const size_t RSASHA3843072_KEY_LENGTH = 384; - class RSASHA3843072Verifier: public RSAVerifier - { - public: - - RSASHA3843072Verifier (const uint8_t * signingKey): RSAVerifier (signingKey) - { - } - }; - - class RSASHA3843072Signer: public RSASigner - { - public: - - RSASHA3843072Signer (const uint8_t * signingPrivateKey): - RSASigner (signingPrivateKey, RSASHA3843072_KEY_LENGTH*2) - { - } - }; + typedef RSAVerifier RSASHA3843072Verifier; + typedef RSASigner RSASHA3843072Signer; // RSA_SHA512_4096 const size_t RSASHA5124096_KEY_LENGTH = 512; - class RSASHA5124096Verifier: public RSAVerifier - { - public: - - RSASHA5124096Verifier (const uint8_t * signingKey): RSAVerifier (signingKey) - { - } - }; - - class RSASHA5124096Signer: public RSASigner - { - public: - - RSASHA5124096Signer (const uint8_t * signingPrivateKey): - RSASigner (signingPrivateKey, RSASHA5124096_KEY_LENGTH*2) - { - } - }; - -// Raw verifiers - class RawVerifier - { - public: - - virtual ~RawVerifier () {}; - virtual void Update (const uint8_t * buf, size_t len) = 0; - virtual bool Verify (const uint8_t * signature) = 0; - }; - - template - class RSARawVerifier: public RawVerifier - { - public: - - RSARawVerifier (const uint8_t * signingKey): - n (signingKey, keyLen) - { - } - - void Update (const uint8_t * buf, size_t len) - { - m_Hash.Update (buf, len); - } - - bool Verify (const uint8_t * signature) - { - // RSA encryption first - CryptoPP::Integer enSig (a_exp_b_mod_c (CryptoPP::Integer (signature, keyLen), - CryptoPP::Integer (i2p::crypto::rsae), n)); // s^e mod n - uint8_t enSigBuf[keyLen]; - enSig.Encode (enSigBuf, keyLen); - - uint8_t digest[Hash::DIGESTSIZE]; - m_Hash.Final (digest); - if ((int)keyLen < Hash::DIGESTSIZE) return false; // can't verify digest longer than key - // we assume digest is right aligned, at least for PKCS#1 v1.5 padding - return !memcmp (enSigBuf + (keyLen - Hash::DIGESTSIZE), digest, Hash::DIGESTSIZE); - } - - private: - - CryptoPP::Integer n; // RSA modulus - Hash m_Hash; - }; - - class RSASHA5124096RawVerifier: public RSARawVerifier - { - public: - - RSASHA5124096RawVerifier (const uint8_t * signingKey): RSARawVerifier (signingKey) - { - } - }; + typedef RSAVerifier RSASHA5124096Verifier; + typedef RSASigner RSASHA5124096Signer; // EdDSA + struct EDDSAPoint + { + BIGNUM * x, * y; + EDDSAPoint (): x(nullptr), y(nullptr) {}; + EDDSAPoint (EDDSAPoint&& other): x(nullptr), y(nullptr) + { *this = std::move (other); }; + EDDSAPoint (BIGNUM * x1, BIGNUM * y1): x(x1), y(y1) {}; + ~EDDSAPoint () { BN_free (x); BN_free (y); }; + + EDDSAPoint& operator= (EDDSAPoint&& other) + { + if (x) BN_free (x); + if (y) BN_free (y); + x = other.x; other.x = nullptr; + y = other.y; other.y = nullptr; + return *this; + } + + bool operator== (const EDDSAPoint& other) const + { + return !BN_cmp (x, other.x) && !BN_cmp (y, other.y); + } + }; + const size_t EDDSA25519_PUBLIC_KEY_LENGTH = 32; const size_t EDDSA25519_SIGNATURE_LENGTH = 64; const size_t EDDSA25519_PRIVATE_KEY_LENGTH = 32; @@ -420,24 +400,41 @@ namespace crypto public: EDDSA25519Verifier (const uint8_t * signingKey); + ~EDDSA25519Verifier () { BN_CTX_free (m_Ctx); }; bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; }; size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; }; private: - - CryptoPP::ECP::Point m_PublicKey; + + BN_CTX * m_Ctx; + EDDSAPoint m_PublicKey; + uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; class EDDSA25519Signer: public Signer { public: - EDDSA25519Signer (const uint8_t * signingPrivateKey) {}; + EDDSA25519Signer (const uint8_t * signingPrivateKey); + ~EDDSA25519Signer () { BN_CTX_free (m_Ctx); }; + void Sign (const uint8_t * buf, int len, uint8_t * signature) const; + const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; + + private: - void Sign (CryptoPP::RandomNumberGenerator& rnd, const uint8_t * buf, int len, uint8_t * signature) const; + BN_CTX * m_Ctx; + uint8_t m_ExpandedPrivateKey[64]; + uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; + + inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) + { + RAND_bytes (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH); + EDDSA25519Signer signer (signingPrivateKey); + memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); + } } } diff --git a/Streaming.cpp b/Streaming.cpp index 785e2987..bb84a955 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -1,4 +1,4 @@ -#include +#include #include "Log.h" #include "RouterInfo.h" #include "RouterContext.h" @@ -20,7 +20,7 @@ namespace stream m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0) { - m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); + RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); m_RemoteIdentity = remote->GetIdentity (); m_CurrentRemoteLease.endDate = 0; } @@ -32,7 +32,7 @@ namespace stream m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0) { - m_RecvStreamID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); + RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); } Stream::~Stream () @@ -182,10 +182,11 @@ namespace stream if (flags & PACKET_FLAG_FROM_INCLUDED) { - optionData += m_RemoteIdentity.FromBuffer (optionData, packet->GetOptionSize ()); - LogPrint (eLogInfo, "From identity ", m_RemoteIdentity.GetIdentHash ().ToBase64 ()); + m_RemoteIdentity = std::make_shared(optionData, packet->GetOptionSize ()); + optionData += m_RemoteIdentity->GetFullLen (); + LogPrint (eLogInfo, "From identity ", m_RemoteIdentity->GetIdentHash ().ToBase64 ()); if (!m_RemoteLeaseSet) - LogPrint (eLogDebug, "Incoming stream from ", m_RemoteIdentity.GetIdentHash ().ToBase64 ()); + LogPrint (eLogDebug, "Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 ()); } if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED) @@ -199,10 +200,10 @@ namespace stream { LogPrint (eLogDebug, "Signature"); uint8_t signature[256]; - auto signatureLen = m_RemoteIdentity.GetSignatureLen (); + auto signatureLen = m_RemoteIdentity->GetSignatureLen (); memcpy (signature, optionData, signatureLen); memset (const_cast(optionData), 0, signatureLen); - if (!m_RemoteIdentity.Verify (packet->GetBuffer (), packet->GetLength (), signature)) + if (!m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature)) { LogPrint (eLogError, "Signature verification failed"); Close (); @@ -353,11 +354,11 @@ namespace stream if (isNoAck) flags |= PACKET_FLAG_NO_ACK; htobe16buf (packet + size, flags); size += 2; // flags - size_t identityLen = m_LocalDestination.GetOwner ().GetIdentity ().GetFullLen (); - size_t signatureLen = m_LocalDestination.GetOwner ().GetIdentity ().GetSignatureLen (); + size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen (); + size_t signatureLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetSignatureLen (); htobe16buf (packet + size, identityLen + signatureLen + 2); // identity + signature + packet size size += 2; // options size - m_LocalDestination.GetOwner ().GetIdentity ().ToBuffer (packet + size, identityLen); + m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen); size += identityLen; // from htobe16buf (packet + size, STREAMING_MTU); size += 2; // max packet size @@ -366,7 +367,7 @@ namespace stream size += signatureLen; // signature m_SendBuffer.read ((char *)(packet + size), STREAMING_MTU - size); size += m_SendBuffer.gcount (); // payload - m_LocalDestination.GetOwner ().Sign (packet, size, signature); + m_LocalDestination.GetOwner ()->Sign (packet, size, signature); } else { @@ -528,13 +529,13 @@ namespace stream size++; // resend delay htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); size += 2; // flags - size_t signatureLen = m_LocalDestination.GetOwner ().GetIdentity ().GetSignatureLen (); + size_t signatureLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetSignatureLen (); htobe16buf (packet + size, signatureLen); // signature only size += 2; // options size uint8_t * signature = packet + size; memset (packet + size, 0, signatureLen); size += signatureLen; // signature - m_LocalDestination.GetOwner ().Sign (packet, size, signature); + m_LocalDestination.GetOwner ()->Sign (packet, size, signature); p->len = size; m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p)); @@ -597,15 +598,15 @@ namespace stream } } if (!m_CurrentOutboundTunnel || !m_CurrentOutboundTunnel->IsEstablished ()) - m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ().GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); if (!m_CurrentOutboundTunnel) { LogPrint (eLogError, "No outbound tunnels in the pool"); return; } - auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (ts >= m_CurrentRemoteLease.endDate - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000) + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (!m_CurrentRemoteLease.endDate || ts >= m_CurrentRemoteLease.endDate - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000) UpdateCurrentRemoteLease (true); if (ts < m_CurrentRemoteLease.endDate) { @@ -681,7 +682,7 @@ namespace stream break; case 3: // pick another outbound tunnel - m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ().GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); LogPrint (eLogWarning, "Another outbound tunnel has been selected for stream"); break; default: ; @@ -713,19 +714,19 @@ namespace stream { if (!m_RemoteLeaseSet) { - m_RemoteLeaseSet = m_LocalDestination.GetOwner ().FindLeaseSet (m_RemoteIdentity.GetIdentHash ()); + m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); if (!m_RemoteLeaseSet) - LogPrint ("LeaseSet ", m_RemoteIdentity.GetIdentHash ().ToBase64 (), " not found"); + LogPrint ("LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); } if (m_RemoteLeaseSet) { if (!m_RoutingSession) - m_RoutingSession = m_LocalDestination.GetOwner ().GetRoutingSession (m_RemoteLeaseSet, 32); + m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, 32); auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (false); // try without threshold first if (leases.empty ()) { expired = false; - m_LocalDestination.GetOwner ().RequestDestination (m_RemoteIdentity.GetIdentHash ()); // time to re-request + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // time to re-request leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold } if (!leases.empty ()) @@ -743,7 +744,7 @@ namespace stream } if (!updated) { - uint32_t i = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (0, leases.size () - 1); + uint32_t i = rand () % leases.size (); if (m_CurrentRemoteLease.endDate && leases[i].tunnelID == m_CurrentRemoteLease.tunnelID) // make sure we don't select previous i = (i + 1) % leases.size (); // if so, pick next @@ -764,27 +765,36 @@ namespace stream std::shared_ptr Stream::CreateDataMessage (const uint8_t * payload, size_t len) { auto msg = ToSharedI2NPMessage (NewI2NPShortMessage ()); - CryptoPP::Gzip compressor; if (len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE) - compressor.SetDeflateLevel (CryptoPP::Gzip::MIN_DEFLATE_LEVEL); + m_LocalDestination.m_Deflator.SetCompressionLevel (Z_NO_COMPRESSION); else - compressor.SetDeflateLevel (CryptoPP::Gzip::DEFAULT_DEFLATE_LEVEL); - compressor.Put (payload, len); - compressor.MessageEnd(); - int size = compressor.MaxRetrievable (); + m_LocalDestination.m_Deflator.SetCompressionLevel (Z_DEFAULT_COMPRESSION); uint8_t * buf = msg->GetPayload (); - htobe32buf (buf, size); // length - buf += 4; - compressor.Get (buf, size); - htobe16buf (buf + 4, m_LocalDestination.GetLocalPort ()); // source port - htobe16buf (buf + 6, m_Port); // destination port - buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol - msg->len += size + 4; - msg->FillI2NPMessageHeader (eI2NPData); - + buf += 4; // reserve for lengthlength + size_t size = m_LocalDestination.m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); + if (size) + { + htobe32buf (msg->GetPayload (), size); // length + htobe16buf (buf + 4, m_LocalDestination.GetLocalPort ()); // source port + htobe16buf (buf + 6, m_Port); // destination port + buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol + msg->len += size + 4; + msg->FillI2NPMessageHeader (eI2NPData); + } + else + msg = nullptr; return msg; } + + StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort): + m_Owner (owner), m_LocalPort (localPort) + { + } + StreamingDestination::~StreamingDestination () + { + } + void StreamingDestination::Start () { } @@ -845,7 +855,7 @@ namespace stream std::shared_ptr StreamingDestination::CreateNewOutgoingStream (std::shared_ptr remote, int port) { - auto s = std::make_shared (m_Owner.GetService (), *this, remote, port); + auto s = std::make_shared (m_Owner->GetService (), *this, remote, port); std::unique_lock l(m_StreamsMutex); m_Streams[s->GetRecvStreamID ()] = s; return s; @@ -853,7 +863,7 @@ namespace stream std::shared_ptr StreamingDestination::CreateNewIncomingStream () { - auto s = std::make_shared (m_Owner.GetService (), *this); + auto s = std::make_shared (m_Owner->GetService (), *this); std::unique_lock l(m_StreamsMutex); m_Streams[s->GetRecvStreamID ()] = s; return s; @@ -873,22 +883,13 @@ namespace stream void StreamingDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len) { // unzip it - CryptoPP::Gunzip decompressor; - decompressor.Put (buf, len); - decompressor.MessageEnd(); Packet * uncompressed = new Packet; uncompressed->offset = 0; - uncompressed->len = decompressor.MaxRetrievable (); - if (uncompressed->len <= MAX_PACKET_SIZE) - { - decompressor.Get (uncompressed->buf, uncompressed->len); + uncompressed->len = m_Inflator.Inflate (buf, len, uncompressed->buf, MAX_PACKET_SIZE); + if (uncompressed->len) HandleNextPacket (uncompressed); - } else - { - LogPrint ("Received packet size ", uncompressed->len, " exceeds max packet size. Skipped"); delete uncompressed; - } } } } diff --git a/Streaming.h b/Streaming.h index 7e2aaa71..580ddf7c 100644 --- a/Streaming.h +++ b/Streaming.h @@ -11,6 +11,7 @@ #include #include #include +#include "Base.h" #include "I2PEndian.h" #include "Identity.h" #include "LeaseSet.h" @@ -107,7 +108,7 @@ namespace stream uint32_t GetSendStreamID () const { return m_SendStreamID; }; uint32_t GetRecvStreamID () const { return m_RecvStreamID; }; std::shared_ptr GetRemoteLeaseSet () const { return m_RemoteLeaseSet; }; - const i2p::data::IdentityEx& GetRemoteIdentity () const { return m_RemoteIdentity; }; + std::shared_ptr GetRemoteIdentity () const { return m_RemoteIdentity; }; bool IsOpen () const { return m_Status == eStreamStatusOpen; }; bool IsEstablished () const { return m_SendStreamID; }; StreamStatus GetStatus () const { return m_Status; }; @@ -166,7 +167,7 @@ namespace stream StreamStatus m_Status; bool m_IsAckSendScheduled; StreamingDestination& m_LocalDestination; - i2p::data::IdentityEx m_RemoteIdentity; + std::shared_ptr m_RemoteIdentity; std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_RoutingSession; i2p::data::Lease m_CurrentRemoteLease; @@ -192,9 +193,8 @@ namespace stream typedef std::function)> Acceptor; - StreamingDestination (i2p::client::ClientDestination& owner, uint16_t localPort = 0): - m_Owner (owner), m_LocalPort (localPort) {}; - ~StreamingDestination () {}; + StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0); + ~StreamingDestination (); void Start (); void Stop (); @@ -204,7 +204,7 @@ namespace stream void SetAcceptor (const Acceptor& acceptor) { m_Acceptor = acceptor; }; void ResetAcceptor () { if (m_Acceptor) m_Acceptor (nullptr); m_Acceptor = nullptr; }; bool IsAcceptorSet () const { return m_Acceptor != nullptr; }; - i2p::client::ClientDestination& GetOwner () { return m_Owner; }; + std::shared_ptr GetOwner () const { return m_Owner; }; uint16_t GetLocalPort () const { return m_LocalPort; }; void HandleDataMessagePayload (const uint8_t * buf, size_t len); @@ -216,7 +216,7 @@ namespace stream private: - i2p::client::ClientDestination& m_Owner; + std::shared_ptr m_Owner; uint16_t m_LocalPort; std::mutex m_StreamsMutex; std::map > m_Streams; @@ -224,6 +224,9 @@ namespace stream public: + i2p::data::GzipInflator m_Inflator; + i2p::data::GzipDeflator m_Deflator; + // for HTTP only const decltype(m_Streams)& GetStreams () const { return m_Streams; }; }; diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index ad6aa0b3..beb09da7 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -14,8 +14,7 @@ namespace tunnel TransitTunnel::TransitTunnel (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey): - m_TunnelID (receiveTunnelID), m_NextTunnelID (nextTunnelID), - m_NextIdent (nextIdent) + TunnelBase (receiveTunnelID, nextTunnelID, nextIdent) { m_Encryption.SetKeys (layerKey, ivKey); } @@ -54,12 +53,12 @@ namespace tunnel void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) { - LogPrint (eLogError, "We are not a gateway for transit tunnel ", m_TunnelID); + LogPrint (eLogError, "We are not a gateway for transit tunnel ", GetTunnelID ()); } void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { - LogPrint (eLogError, "Incoming tunnel message is not supported ", m_TunnelID); + LogPrint (eLogError, "Incoming tunnel message is not supported ", GetTunnelID ()); } void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr msg) diff --git a/TransitTunnel.h b/TransitTunnel.h index 79bb99bb..e611d685 100644 --- a/TransitTunnel.h +++ b/TransitTunnel.h @@ -5,7 +5,7 @@ #include #include #include -#include "aes.h" +#include "Crypto.h" #include "I2NPProtocol.h" #include "TunnelEndpoint.h" #include "TunnelGateway.h" @@ -24,20 +24,12 @@ namespace tunnel const uint8_t * layerKey,const uint8_t * ivKey); virtual size_t GetNumTransmittedBytes () const { return 0; }; - - uint32_t GetTunnelID () const { return m_TunnelID; }; // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); - void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); - uint32_t GetNextTunnelID () const { return m_NextTunnelID; }; - const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; }; - + void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); private: - - uint32_t m_TunnelID, m_NextTunnelID; - i2p::data::IdentHash m_NextIdent; i2p::crypto::TunnelEncryption m_Encryption; }; diff --git a/TransportSession.h b/TransportSession.h index f8da2b4d..608e72d1 100644 --- a/TransportSession.h +++ b/TransportSession.h @@ -6,6 +6,7 @@ #include #include #include "Identity.h" +#include "Crypto.h" #include "RouterInfo.h" #include "I2NPProtocol.h" @@ -13,17 +14,15 @@ namespace i2p { namespace transport { - struct DHKeysPair // transient keys for transport sessions - { - uint8_t publicKey[256]; - uint8_t privateKey[256]; - }; - class SignedData { public: - SignedData () {}; + SignedData () {} + SignedData (const SignedData& other) + { + m_Stream << other.m_Stream.rdbuf (); + } void Insert (const uint8_t * buf, size_t len) { m_Stream.write ((char *)buf, len); @@ -35,9 +34,9 @@ namespace transport m_Stream.write ((char *)&t, sizeof (T)); } - bool Verify (const i2p::data::IdentityEx& ident, const uint8_t * signature) const + bool Verify (std::shared_ptr ident, const uint8_t * signature) const { - return ident.Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); + return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); } void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const @@ -54,31 +53,31 @@ namespace transport { public: - TransportSession (std::shared_ptr in_RemoteRouter): - m_RemoteRouter (in_RemoteRouter), m_DHKeysPair (nullptr), - m_NumSentBytes (0), m_NumReceivedBytes (0) + TransportSession (std::shared_ptr router): + m_DHKeysPair (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router) { - if (m_RemoteRouter) - m_RemoteIdentity = m_RemoteRouter->GetRouterIdentity (); + if (router) + m_RemoteIdentity = router->GetRouterIdentity (); } - virtual ~TransportSession () { delete m_DHKeysPair; }; + virtual ~TransportSession () {}; virtual void Done () = 0; - std::shared_ptr GetRemoteRouter () { return m_RemoteRouter; }; - const i2p::data::IdentityEx& GetRemoteIdentity () { return m_RemoteIdentity; }; - + std::shared_ptr GetRemoteIdentity () { return m_RemoteIdentity; }; + void SetRemoteIdentity (std::shared_ptr ident) { m_RemoteIdentity = ident; }; + size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; + bool IsOutgoing () const { return m_IsOutgoing; }; virtual void SendI2NPMessages (const std::vector >& msgs) = 0; protected: - std::shared_ptr m_RemoteRouter; - i2p::data::IdentityEx m_RemoteIdentity; - DHKeysPair * m_DHKeysPair; // X - for client and Y - for server + std::shared_ptr m_RemoteIdentity; + std::shared_ptr m_DHKeysPair; // X - for client and Y - for server size_t m_NumSentBytes, m_NumReceivedBytes; + bool m_IsOutgoing; }; } } diff --git a/Transports.cpp b/Transports.cpp index da07dc1d..635c5e04 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -1,6 +1,6 @@ -#include +#include #include "Log.h" -#include "CryptoConst.h" +#include "Crypto.h" #include "RouterContext.h" #include "I2NPProtocol.h" #include "NetDb.h" @@ -56,18 +56,18 @@ namespace transport { if (num > 0) { - CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg); + i2p::crypto::DHKeys dh; for (int i = 0; i < num; i++) { - i2p::transport::DHKeysPair * pair = new i2p::transport::DHKeysPair (); - dh.GenerateKeyPair(m_Rnd, pair->privateKey, pair->publicKey); + auto pair = std::make_shared (); + pair->GenerateKeys (); std::unique_lock l(m_AcquiredMutex); m_Queue.push (pair); } } } - DHKeysPair * DHKeysPairSupplier::Acquire () + std::shared_ptr DHKeysPairSupplier::Acquire () { if (!m_Queue.empty ()) { @@ -78,15 +78,14 @@ namespace transport return pair; } else // queue is empty, create new - { - DHKeysPair * pair = new DHKeysPair (); - CryptoPP::DH dh (i2p::crypto::elgp, i2p::crypto::elgg); - dh.GenerateKeyPair(m_Rnd, pair->privateKey, pair->publicKey); + { + auto pair = std::make_shared (); + pair->GenerateKeys (); return pair; } } - void DHKeysPairSupplier::Return (DHKeysPair * pair) + void DHKeysPairSupplier::Return (std::shared_ptr pair) { std::unique_lock l(m_AcquiredMutex); m_Queue.push (pair); @@ -109,10 +108,6 @@ namespace transport void Transports::Start () { -#ifdef USE_UPNP - m_UPnP.Start (); - LogPrint(eLogInfo, "UPnP started"); -#endif m_DHKeysPairSupplier.Start (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); @@ -145,10 +140,6 @@ namespace transport void Transports::Stop () { -#ifdef USE_UPNP - m_UPnP.Stop (); - LogPrint(eLogInfo, "UPnP stopped"); -#endif m_PeerCleanupTimer.cancel (); m_Peers.clear (); if (m_SSUServer) @@ -412,13 +403,34 @@ namespace transport else LogPrint (eLogError, "Can't detect external IP. SSU is not available"); } - - DHKeysPair * Transports::GetNextDHKeysPair () + + void Transports::PeerTest () + { + if (m_SSUServer) + { + bool statusChanged = false; + for (int i = 0; i < 5; i++) + { + auto router = i2p::data::netdb.GetRandomPeerTestRouter (); + if (router && router->IsSSU ()) + { + if (!statusChanged) + { + statusChanged = true; + i2p::context.SetStatus (eRouterStatusTesting); // first time only + } + m_SSUServer->GetSession (router, true); // peer test + } + } + } + } + + std::shared_ptr Transports::GetNextDHKeysPair () { return m_DHKeysPairSupplier.Acquire (); } - void Transports::ReuseDHKeysPair (DHKeysPair * pair) + void Transports::ReuseDHKeysPair (std::shared_ptr pair) { m_DHKeysPairSupplier.Return (pair); } @@ -427,7 +439,8 @@ namespace transport { m_Service.post([session, this]() { - auto ident = session->GetRemoteIdentity ().GetIdentHash (); + if (!session->GetRemoteIdentity ()) return; + auto ident = session->GetRemoteIdentity ()->GetIdentHash (); auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { @@ -444,7 +457,8 @@ namespace transport { m_Service.post([session, this]() { - auto ident = session->GetRemoteIdentity ().GetIdentHash (); + if (!session->GetRemoteIdentity ()) return; + auto ident = session->GetRemoteIdentity ()->GetIdentHash (); auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { @@ -491,9 +505,9 @@ namespace transport std::shared_ptr Transports::GetRandomPeer () const { - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); + if (!m_Peers.size ()) return nullptr; auto it = m_Peers.begin (); - std::advance (it, rnd.GenerateWord32 (0, m_Peers.size () - 1)); + std::advance (it, rand () % m_Peers.size ()); return it != m_Peers.end () ? it->second.router : nullptr; } } diff --git a/Transports.h b/Transports.h index 15ad980e..63506c13 100644 --- a/Transports.h +++ b/Transports.h @@ -11,7 +11,6 @@ #include #include #include -#include #include #include "TransportSession.h" #include "NTCPSession.h" @@ -20,10 +19,6 @@ #include "I2NPProtocol.h" #include "Identity.h" -#ifdef USE_UPNP -#include "UPnP.h" -#endif - namespace i2p { namespace transport @@ -36,8 +31,8 @@ namespace transport ~DHKeysPairSupplier (); void Start (); void Stop (); - DHKeysPair * Acquire (); - void Return (DHKeysPair * pair); + std::shared_ptr Acquire (); + void Return (std::shared_ptr pair); private: @@ -47,13 +42,12 @@ namespace transport private: const int m_QueueSize; - std::queue m_Queue; + std::queue > m_Queue; bool m_IsRunning; std::thread * m_Thread; std::condition_variable m_Acquired; std::mutex m_AcquiredMutex; - CryptoPP::AutoSeededRandomPool m_Rnd; }; struct Peer @@ -84,8 +78,8 @@ namespace transport void Stop (); boost::asio::io_service& GetService () { return m_Service; }; - i2p::transport::DHKeysPair * GetNextDHKeysPair (); - void ReuseDHKeysPair (DHKeysPair * pair); + std::shared_ptr GetNextDHKeysPair (); + void ReuseDHKeysPair (std::shared_ptr pair); void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); void SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs); @@ -105,6 +99,8 @@ namespace transport size_t GetNumPeers () const { return m_Peers.size (); }; std::shared_ptr GetRandomPeer () const; + void PeerTest (); + private: void Run (); @@ -141,10 +137,6 @@ namespace transport uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes; uint64_t m_LastBandwidthUpdateTime; -#ifdef USE_UPNP - UPnP m_UPnP; -#endif - public: // for HTTP only diff --git a/Tunnel.cpp b/Tunnel.cpp index 0c5600e7..6443180f 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include "RouterContext.h" #include "Log.h" #include "Timestamp.h" @@ -18,6 +18,7 @@ namespace tunnel { Tunnel::Tunnel (std::shared_ptr config): + TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()), m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false) { } @@ -28,7 +29,6 @@ namespace tunnel void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); auto numHops = m_Config->GetNumHops (); int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops; auto msg = NewI2NPShortMessage (); @@ -46,9 +46,13 @@ namespace tunnel int i = 0; while (hop) { + uint32_t msgID; + if (hop->next) // we set replyMsgID for last hop only + RAND_bytes ((uint8_t *)&msgID, 4); + else + msgID = replyMsgID; int idx = recordIndicies[i]; - hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, - hop->next ? rnd.GenerateWord32 () : replyMsgID); // we set replyMsgID for last hop only + hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, msgID); hop->recordIndex = idx; i++; hop = hop->next; @@ -57,7 +61,7 @@ namespace tunnel for (int i = numHops; i < numRecords; i++) { int idx = recordIndicies[i]; - rnd.GenerateBlock (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE); + RAND_bytes (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE); } // decrypt real records @@ -120,7 +124,9 @@ namespace tunnel const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE; uint8_t ret = record[BUILD_RESPONSE_RECORD_RET_OFFSET]; LogPrint ("Ret code=", (int)ret); - hop->router->GetProfile ()->TunnelBuildResponse (ret); + auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); + if (profile) + profile->TunnelBuildResponse (ret); if (ret) // if any of participants declined the tunnel is not established established = false; @@ -128,13 +134,16 @@ namespace tunnel } if (established) { - // change reply keys to layer keys - hop = m_Config->GetFirstHop (); + // create tunnel decryptions from layer and iv keys in reverse order + hop = m_Config->GetLastHop (); while (hop) { - hop->decryption.SetKeys (hop->layerKey, hop->ivKey); - hop = hop->next; + auto tunnelHop = new TunnelHop{ .ident = hop->ident }; + tunnelHop->decryption.SetKeys (hop->layerKey, hop->ivKey); + m_Hops.push_back (std::unique_ptr(tunnelHop)); + hop = hop->prev; } + m_Config = nullptr; } if (established) m_State = eTunnelStateEstablished; return established; @@ -144,13 +153,11 @@ namespace tunnel { const uint8_t * inPayload = in->GetPayload () + 4; uint8_t * outPayload = out->GetPayload () + 4; - TunnelHopConfig * hop = m_Config->GetLastHop (); - while (hop) - { - hop->decryption.Decrypt (inPayload, outPayload); - hop = hop->prev; + for (auto& it: m_Hops) + { + it->decryption.Decrypt (inPayload, outPayload); inPayload = outPayload; - } + } } void Tunnel::SendTunnelDataMsg (std::shared_ptr msg) @@ -158,6 +165,22 @@ namespace tunnel LogPrint (eLogInfo, "Can't send I2NP messages without delivery instructions"); } + std::vector > Tunnel::GetPeers () const + { + auto peers = GetInvertedPeers (); + std::reverse (peers.begin (), peers.end ()); + return peers; + } + + std::vector > Tunnel::GetInvertedPeers () const + { + // hops are in inverted order + std::vector > ret; + for (auto& it: m_Hops) + ret.push_back (it->ident); + return ret; + } + void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) { if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive @@ -167,6 +190,13 @@ namespace tunnel m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } + void InboundTunnel::Print (std::stringstream& s) const + { + s << "-->" << i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ()) << ":" << GetNextTunnelID (); + s << "-->" << (GetNumHops () - 1) << " hops "; + s << GetTunnelID () << ":me"; + } + void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) { TunnelMessageBlock block; @@ -202,6 +232,12 @@ namespace tunnel LogPrint (eLogError, "Incoming message for outbound tunnel ", GetTunnelID ()); } + void OutboundTunnel::Print (std::stringstream& s) const + { + s << "me-->" << i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ()) << ":" << GetNextTunnelID (); + s << "-->" << (GetNumHops () - 1) << " hops-->"; + } + Tunnels tunnels; Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), @@ -272,8 +308,8 @@ namespace tunnel std::shared_ptr Tunnels::GetNextOutboundTunnel () { - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - uint32_t ind = rnd.GenerateWord32 (0, m_OutboundTunnels.size () - 1), i = 0; + if (!m_OutboundTunnels.size ()) return nullptr; + uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0; std::shared_ptr tunnel; for (auto it: m_OutboundTunnels) { @@ -482,8 +518,12 @@ namespace tunnel auto hop = config->GetFirstHop (); while (hop) { - if (hop->router) - hop->router->GetProfile ()->TunnelNonReplied (); + if (hop->ident) + { + auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); + if (profile) + profile->TunnelNonReplied (); + } hop = hop->next; } } @@ -553,8 +593,8 @@ namespace tunnel if (!inboundTunnel || !router) return; LogPrint ("Creating one hop outbound tunnel..."); CreateTunnel ( - std::make_shared (std::vector > { router }, - inboundTunnel->GetTunnelConfig ()) + std::make_shared (std::vector > { router->GetRouterIdentity () }, + inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) ); } } @@ -609,7 +649,7 @@ namespace tunnel auto router = i2p::data::netdb.GetRandomRouter (); LogPrint ("Creating one hop inbound tunnel..."); CreateTunnel ( - std::make_shared (std::vector > { router }) + std::make_shared (std::vector > { router->GetRouterIdentity () }) ); } } @@ -662,7 +702,8 @@ namespace tunnel std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) { auto newTunnel = std::make_shared (config); - uint32_t replyMsgID = i2p::context.GetRandomNumberGenerator ().GenerateWord32 (); + uint32_t replyMsgID; + RAND_bytes ((uint8_t *)&replyMsgID, 4); AddPendingTunnel (replyMsgID, newTunnel); newTunnel->Build (replyMsgID, outboundTunnel); return newTunnel; @@ -695,7 +736,9 @@ namespace tunnel if (!pool) { // build symmetric outbound tunnel - CreateTunnel (newTunnel->GetTunnelConfig ()->Invert (), GetNextOutboundTunnel ()); + CreateTunnel (std::make_shared(newTunnel->GetInvertedPeers (), + newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), + GetNextOutboundTunnel ()); } else { @@ -710,9 +753,9 @@ namespace tunnel void Tunnels::CreateZeroHopsInboundTunnel () { CreateTunnel ( - std::make_shared (std::vector > + std::make_shared (std::vector > { - i2p::context.GetSharedRouterInfo () + i2p::context.GetIdentity () })); } diff --git a/Tunnel.h b/Tunnel.h index e75de74b..20e02c1b 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -10,6 +10,7 @@ #include #include #include "Queue.h" +#include "Crypto.h" #include "TunnelConfig.h" #include "TunnelPool.h" #include "TransitTunnel.h" @@ -43,6 +44,12 @@ namespace tunnel class InboundTunnel; class Tunnel: public TunnelBase { + struct TunnelHop + { + std::shared_ptr ident; + i2p::crypto::TunnelDecryption decryption; + }; + public: Tunnel (std::shared_ptr config); @@ -51,8 +58,11 @@ namespace tunnel void Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel = nullptr); std::shared_ptr GetTunnelConfig () const { return m_Config; } + std::vector > GetPeers () const; + std::vector > GetInvertedPeers () const; TunnelState GetState () const { return m_State; }; void SetState (TunnelState state) { m_State = state; }; + int GetNumHops () const { return m_Hops.size (); } bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; bool IsFailed () const { return m_State == eTunnelStateFailed; }; bool IsRecreated () const { return m_IsRecreated; }; @@ -66,12 +76,11 @@ namespace tunnel // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); - uint32_t GetNextTunnelID () const { return m_Config->GetFirstHop ()->tunnelID; }; - const i2p::data::IdentHash& GetNextIdentHash () const { return m_Config->GetFirstHop ()->router->GetIdentHash (); }; - + private: std::shared_ptr m_Config; + std::vector > m_Hops; std::shared_ptr m_Pool; // pool, tunnel belongs to, or null TunnelState m_State; bool m_IsRecreated; @@ -81,22 +90,23 @@ namespace tunnel { public: - OutboundTunnel (std::shared_ptr config): Tunnel (config), m_Gateway (this) {}; + OutboundTunnel (std::shared_ptr config): + Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {}; void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg); void SendTunnelDataMsg (const std::vector& msgs); // multiple messages - std::shared_ptr GetEndpointRouter () const - { return GetTunnelConfig ()->GetLastHop ()->router; }; + const i2p::data::IdentHash& GetEndpointIdentHash () const { return m_EndpointIdentHash; }; size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; - + void Print (std::stringstream& s) const; + // implements TunnelBase void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); - uint32_t GetTunnelID () const { return GetNextTunnelID (); }; private: std::mutex m_SendMutex; TunnelGateway m_Gateway; + i2p::data::IdentHash m_EndpointIdentHash; }; class InboundTunnel: public Tunnel, public std::enable_shared_from_this @@ -106,9 +116,8 @@ namespace tunnel InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; void HandleTunnelDataMsg (std::shared_ptr msg); size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; - - // implements TunnelBase - uint32_t GetTunnelID () const { return GetTunnelConfig ()->GetLastHop ()->nextTunnelID; }; + void Print (std::stringstream& s) const; + private: TunnelEndpoint m_Endpoint; diff --git a/TunnelBase.h b/TunnelBase.h index 76175d0a..bec06400 100644 --- a/TunnelBase.h +++ b/TunnelBase.h @@ -33,23 +33,26 @@ namespace tunnel { public: - //WARNING!!! GetSecondsSinceEpoch() return uint64_t - TunnelBase (): m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {}; + TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, i2p::data::IdentHash nextIdent): + m_TunnelID (tunnelID), m_NextTunnelID (nextTunnelID), m_NextIdent (nextIdent), + m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {}; virtual ~TunnelBase () {}; virtual void HandleTunnelDataMsg (std::shared_ptr tunnelMsg) = 0; virtual void SendTunnelDataMsg (std::shared_ptr msg) = 0; virtual void FlushTunnelDataMsgs () {}; virtual void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) = 0; - virtual uint32_t GetNextTunnelID () const = 0; - virtual const i2p::data::IdentHash& GetNextIdentHash () const = 0; - virtual uint32_t GetTunnelID () const = 0; // as known at our side + uint32_t GetNextTunnelID () const { return m_NextTunnelID; }; + const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; }; + virtual uint32_t GetTunnelID () const { return m_TunnelID; }; // as known at our side uint32_t GetCreationTime () const { return m_CreationTime; }; void SetCreationTime (uint32_t t) { m_CreationTime = t; }; private: - + + uint32_t m_TunnelID, m_NextTunnelID; + i2p::data::IdentHash m_NextIdent; uint32_t m_CreationTime; // seconds since epoch }; diff --git a/TunnelConfig.h b/TunnelConfig.h index d9827fe8..d2164d86 100644 --- a/TunnelConfig.h +++ b/TunnelConfig.h @@ -5,8 +5,9 @@ #include #include #include -#include "aes.h" -#include "RouterInfo.h" +#include +#include "Crypto.h" +#include "Identity.h" #include "RouterContext.h" #include "Timestamp.h" @@ -16,7 +17,8 @@ namespace tunnel { struct TunnelHopConfig { - std::shared_ptr router, nextRouter; + std::shared_ptr ident; + i2p::data::IdentHash nextIdent; uint32_t tunnelID, nextTunnelID; uint8_t layerKey[32]; uint8_t ivKey[32]; @@ -25,19 +27,17 @@ namespace tunnel bool isGateway, isEndpoint; TunnelHopConfig * next, * prev; - i2p::crypto::TunnelDecryption decryption; int recordIndex; // record # in tunnel build message - TunnelHopConfig (std::shared_ptr r) + TunnelHopConfig (std::shared_ptr r) { - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - rnd.GenerateBlock (layerKey, 32); - rnd.GenerateBlock (ivKey, 32); - rnd.GenerateBlock (replyIV, 16); - tunnelID = rnd.GenerateWord32 (); + RAND_bytes (layerKey, 32); + RAND_bytes (ivKey, 32); + RAND_bytes (replyIV, 16); + RAND_bytes ((uint8_t *)&tunnelID, 4); isGateway = true; isEndpoint = true; - router = r; + ident = r; //nextRouter = nullptr; nextTunnelID = 0; @@ -45,18 +45,17 @@ namespace tunnel prev = nullptr; } - void SetNextRouter (std::shared_ptr r) + void SetNextIdent (const i2p::data::IdentHash& ident) { - nextRouter = r; + nextIdent = ident; isEndpoint = false; - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - nextTunnelID = rnd.GenerateWord32 (); + RAND_bytes ((uint8_t *)&nextTunnelID, 4); } - void SetReplyHop (const TunnelHopConfig * replyFirstHop) + void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) { - nextRouter = replyFirstHop->router; - nextTunnelID = replyFirstHop->tunnelID; + nextIdent = replyIdent; + nextTunnelID = replyTunnelID; isEndpoint = true; } @@ -68,7 +67,7 @@ namespace tunnel next->prev = this; next->isGateway = false; isEndpoint = false; - nextRouter = next->router; + nextIdent = next->ident->GetIdentHash (); nextTunnelID = next->tunnelID; } } @@ -88,9 +87,9 @@ namespace tunnel { uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, router->GetIdentHash (), 32); + memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextRouter->GetIdentHash (), 32); + memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); @@ -102,8 +101,9 @@ namespace tunnel htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); // TODO: fill padding - router->GetElGamalEncryption ()->Encrypt (clearText, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET); - memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)router->GetIdentHash (), 16); + i2p::crypto::ElGamalEncryption elGamalEncryption (ident->GetEncryptionPublicKey ()); + elGamalEncryption.Encrypt (clearText, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET); + memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } }; @@ -111,29 +111,18 @@ namespace tunnel { public: - - TunnelConfig (std::vector > peers, - std::shared_ptr replyTunnelConfig = nullptr) // replyTunnelConfig=nullptr means inbound + TunnelConfig (std::vector > peers) // inbound { - TunnelHopConfig * prev = nullptr; - for (auto it: peers) - { - auto hop = new TunnelHopConfig (it); - if (prev) - prev->SetNext (hop); - else - m_FirstHop = hop; - prev = hop; - } - m_LastHop = prev; - - if (replyTunnelConfig) // outbound - { - m_FirstHop->isGateway = false; - m_LastHop->SetReplyHop (replyTunnelConfig->GetFirstHop ()); - } - else // inbound - m_LastHop->SetNextRouter (i2p::context.GetSharedRouterInfo ()); + CreatePeers (peers); + m_LastHop->SetNextIdent (i2p::context.GetIdentHash ()); + } + + TunnelConfig (std::vector > peers, + uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) // outbound + { + CreatePeers (peers); + m_FirstHop->isGateway = false; + m_LastHop->SetReplyHop (replyTunnelID, replyIdent); } ~TunnelConfig () @@ -172,13 +161,35 @@ namespace tunnel bool IsInbound () const { return m_FirstHop->isGateway; } - std::vector > GetPeers () const + uint32_t GetTunnelID () const + { + if (!m_FirstHop) return 0; + return IsInbound () ? m_LastHop->nextTunnelID : m_FirstHop->tunnelID; + } + + uint32_t GetNextTunnelID () const + { + if (!m_FirstHop) return 0; + return m_FirstHop->tunnelID; + } + + const i2p::data::IdentHash& GetNextIdentHash () const + { + return m_FirstHop->ident->GetIdentHash (); + } + + const i2p::data::IdentHash& GetLastIdentHash () const + { + return m_LastHop->ident->GetIdentHash (); + } + + std::vector > GetPeers () const { - std::vector > peers; + std::vector > peers; TunnelHopConfig * hop = m_FirstHop; while (hop) { - peers.push_back (hop->router); + peers.push_back (hop->ident); hop = hop->next; } return peers; @@ -192,7 +203,7 @@ namespace tunnel s << "-->" << m_FirstHop->tunnelID; while (hop) { - s << ":" << hop->router->GetIdentHashAbbreviation () << "-->"; + s << ":" << GetIdentHashAbbreviation (hop->ident->GetIdentHash ()) << "-->"; if (!hop->isEndpoint) s << hop->nextTunnelID; else @@ -202,19 +213,6 @@ namespace tunnel // we didn't reach enpoint that mean we are last hop s << ":me"; } - - std::shared_ptr Invert () const - { - auto peers = GetPeers (); - std::reverse (peers.begin (), peers.end ()); - // we use ourself as reply tunnel for outbound tunnel - return IsInbound () ? std::make_shared(peers, shared_from_this ()) : std::make_shared(peers); - } - - std::shared_ptr Clone (std::shared_ptr replyTunnelConfig = nullptr) const - { - return std::make_shared (GetPeers (), replyTunnelConfig); - } private: @@ -222,6 +220,22 @@ namespace tunnel TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr) { } + + template + void CreatePeers (const Peers& peers) + { + TunnelHopConfig * prev = nullptr; + for (auto it: peers) + { + auto hop = new TunnelHopConfig (it); + if (prev) + prev->SetNext (hop); + else + m_FirstHop = hop; + prev = hop; + } + m_LastHop = prev; + } private: diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 9d1425e1..fe161652 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -1,5 +1,6 @@ #include "I2PEndian.h" #include +#include #include "Log.h" #include "NetDb.h" #include "I2NPProtocol.h" @@ -27,7 +28,7 @@ namespace tunnel // verify checksum memcpy (msg->GetPayload () + TUNNEL_DATA_MSG_SIZE, msg->GetPayload () + 4, 16); // copy iv to the end uint8_t hash[32]; - CryptoPP::SHA256().CalculateDigest (hash, fragment, TUNNEL_DATA_MSG_SIZE -(fragment - msg->GetPayload ()) + 16); // payload + iv + SHA256(fragment, TUNNEL_DATA_MSG_SIZE -(fragment - msg->GetPayload ()) + 16, hash); // payload + iv if (memcmp (hash, decrypted, 4)) { LogPrint (eLogError, "TunnelMessage: checksum verification failed"); diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index b05a342c..adc1bef6 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -1,6 +1,7 @@ #include +#include +#include #include "I2PEndian.h" -#include #include "Log.h" #include "RouterContext.h" #include "Transports.h" @@ -13,7 +14,7 @@ namespace tunnel TunnelGatewayBuffer::TunnelGatewayBuffer (uint32_t tunnelID): m_TunnelID (tunnelID), m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0) { - context.GetRandomNumberGenerator ().GenerateBlock (m_NonZeroRandomBuffer, TUNNEL_DATA_MAX_PAYLOAD_SIZE); + RAND_bytes (m_NonZeroRandomBuffer, TUNNEL_DATA_MAX_PAYLOAD_SIZE); for (size_t i = 0; i < TUNNEL_DATA_MAX_PAYLOAD_SIZE; i++) if (!m_NonZeroRandomBuffer[i]) m_NonZeroRandomBuffer[i] = 1; } @@ -159,18 +160,17 @@ namespace tunnel m_CurrentTunnelDataMsg->offset = m_CurrentTunnelDataMsg->len - TUNNEL_DATA_MSG_SIZE - I2NP_HEADER_SIZE; uint8_t * buf = m_CurrentTunnelDataMsg->GetPayload (); htobe32buf (buf, m_TunnelID); - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - rnd.GenerateBlock (buf + 4, 16); // original IV + RAND_bytes (buf + 4, 16); // original IV memcpy (payload + size, buf + 4, 16); // copy IV for checksum uint8_t hash[32]; - CryptoPP::SHA256().CalculateDigest (hash, payload, size+16); + SHA256(payload, size+16, hash); memcpy (buf+20, hash, 4); // checksum payload[-1] = 0; // zero ptrdiff_t paddingSize = payload - buf - 25; // 25 = 24 + 1 if (paddingSize > 0) { // non-zero padding - auto randomOffset = rnd.GenerateWord32 (0, TUNNEL_DATA_MAX_PAYLOAD_SIZE - paddingSize); + auto randomOffset = rand () % (TUNNEL_DATA_MAX_PAYLOAD_SIZE - paddingSize + 1); memcpy (buf + 24, m_NonZeroRandomBuffer + randomOffset, paddingSize); } diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 0f0eb709..922ae34b 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -1,11 +1,13 @@ #include +#include #include "I2PEndian.h" -#include "CryptoConst.h" +#include "Crypto.h" #include "Tunnel.h" #include "NetDb.h" #include "Timestamp.h" #include "Garlic.h" #include "Transports.h" +#include "Log.h" #include "TunnelPool.h" namespace i2p @@ -141,8 +143,7 @@ namespace tunnel typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const { if (tunnels.empty ()) return nullptr; - CryptoPP::RandomNumberGenerator& rnd = i2p::context.GetRandomNumberGenerator (); - uint32_t ind = rnd.GenerateWord32 (0, tunnels.size ()/2), i = 0; + uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0; typename TTunnels::value_type tunnel = nullptr; for (auto it: tunnels) { @@ -165,7 +166,7 @@ namespace tunnel { std::unique_lock l(m_OutboundTunnelsMutex); for (auto it: m_OutboundTunnels) - if (it->IsEstablished () && old->GetEndpointRouter ()->GetIdentHash () == it->GetEndpointRouter ()->GetIdentHash ()) + if (it->IsEstablished () && old->GetEndpointIdentHash () == it->GetEndpointIdentHash ()) { tunnel = it; break; @@ -200,7 +201,6 @@ namespace tunnel void TunnelPool::TestTunnels () { - auto& rnd = i2p::context.GetRandomNumberGenerator (); for (auto it: m_Tests) { LogPrint ("Tunnel test ", (int)it.first, " failed"); @@ -251,7 +251,8 @@ namespace tunnel } if (!failed) { - uint32_t msgID = rnd.GenerateWord32 (); + uint32_t msgID; + RAND_bytes ((uint8_t *)&msgID, 4); m_Tests[msgID] = std::make_pair (*it1, *it2); (*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), CreateDeliveryStatusMsg (msgID)); @@ -306,9 +307,9 @@ namespace tunnel return hop; } - bool TunnelPool::SelectPeers (std::vector >& hops, bool isInbound) + bool TunnelPool::SelectPeers (std::vector >& peers, bool isInbound) { - if (m_ExplicitPeers) return SelectExplicitPeers (hops, isInbound); + if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); auto prevHop = i2p::context.GetSharedRouterInfo (); int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; if (i2p::transport::transports.GetNumPeers () > 25) @@ -317,7 +318,7 @@ namespace tunnel if (r && !r->GetProfile ()->IsBad ()) { prevHop = r; - hops.push_back (r); + peers.push_back (r->GetRouterIdentity ()); numHops--; } } @@ -331,12 +332,12 @@ namespace tunnel return false; } prevHop = hop; - hops.push_back (hop); + peers.push_back (hop->GetRouterIdentity ()); } return true; } - bool TunnelPool::SelectExplicitPeers (std::vector >& hops, bool isInbound) + bool TunnelPool::SelectExplicitPeers (std::vector >& peers, bool isInbound) { int size = m_ExplicitPeers->size (); std::vector peerIndicies; @@ -349,7 +350,7 @@ namespace tunnel auto& ident = (*m_ExplicitPeers)[peerIndicies[i]]; auto r = i2p::data::netdb.FindRouter (ident); if (r) - hops.push_back (r); + peers.push_back (r->GetRouterIdentity ()); else { LogPrint (eLogInfo, "Can't find router for ", ident.ToBase64 ()); @@ -366,11 +367,11 @@ namespace tunnel if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint ("Creating destination inbound tunnel..."); - std::vector > hops; - if (SelectPeers (hops, true)) + std::vector > peers; + if (SelectPeers (peers, true)) { - std::reverse (hops.begin (), hops.end ()); - auto tunnel = tunnels.CreateTunnel (std::make_shared (hops), outboundTunnel); + std::reverse (peers.begin (), peers.end ()); + auto tunnel = tunnels.CreateTunnel (std::make_shared (peers), outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); } else @@ -383,7 +384,7 @@ namespace tunnel if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint ("Re-creating destination inbound tunnel..."); - auto newTunnel = tunnels.CreateTunnel (tunnel->GetTunnelConfig ()->Clone (), outboundTunnel); + auto newTunnel = tunnels.CreateTunnel (std::make_shared(tunnel->GetPeers ()), outboundTunnel); newTunnel->SetTunnelPool (shared_from_this()); } @@ -395,11 +396,11 @@ namespace tunnel if (inboundTunnel) { LogPrint ("Creating destination outbound tunnel..."); - std::vector > hops; - if (SelectPeers (hops, false)) + std::vector > peers; + if (SelectPeers (peers, false)) { auto tunnel = tunnels.CreateTunnel ( - std::make_shared (hops, inboundTunnel->GetTunnelConfig ())); + std::make_shared (peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())); tunnel->SetTunnelPool (shared_from_this ()); } else @@ -418,7 +419,8 @@ namespace tunnel { LogPrint ("Re-creating destination outbound tunnel..."); auto newTunnel = tunnels.CreateTunnel ( - tunnel->GetTunnelConfig ()->Clone (inboundTunnel->GetTunnelConfig ())); + std::make_shared (tunnel->GetPeers (), + inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())); newTunnel->SetTunnelPool (shared_from_this ()); } else @@ -428,7 +430,7 @@ namespace tunnel void TunnelPool::CreatePairedInboundTunnel (std::shared_ptr outboundTunnel) { LogPrint (eLogInfo, "Creating paired inbound tunnel..."); - auto tunnel = tunnels.CreateTunnel (outboundTunnel->GetTunnelConfig ()->Invert (), outboundTunnel); + auto tunnel = tunnels.CreateTunnel (std::make_shared(outboundTunnel->GetInvertedPeers ()), outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); } } diff --git a/TunnelPool.h b/TunnelPool.h index 2d4203ef..a836162f 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -62,8 +62,8 @@ namespace tunnel template typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; - bool SelectPeers (std::vector >& hops, bool isInbound); - bool SelectExplicitPeers (std::vector >& hops, bool isInbound); + bool SelectPeers (std::vector >& hops, bool isInbound); + bool SelectExplicitPeers (std::vector >& hops, bool isInbound); private: diff --git a/api.cpp b/api.cpp index 6f3c8c4c..eb64309a 100644 --- a/api.cpp +++ b/api.cpp @@ -26,7 +26,7 @@ namespace api if (logStream) StartLog (logStream); else - StartLog (i2p::util::filesystem::GetAppName () + ".log"); + StartLog (i2p::util::filesystem::GetFullPath (i2p::util::filesystem::GetAppName () + ".log")); i2p::data::netdb.Start(); LogPrint("NetDB started"); i2p::transport::transports.Start(); @@ -47,39 +47,41 @@ namespace api StopLog (); } - i2p::client::ClientDestination * CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, + void RunPeerTest () + { + i2p::transport::transports.PeerTest (); + } + + std::shared_ptr CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { - auto localDestination = new i2p::client::ClientDestination (keys, isPublic, params); + auto localDestination = std::make_shared (keys, isPublic, params); localDestination->Start (); return localDestination; } - i2p::client::ClientDestination * CreateLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, + std::shared_ptr CreateLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, const std::map * params) { i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType); - auto localDestination = new i2p::client::ClientDestination (keys, isPublic, params); + auto localDestination = std::make_shared (keys, isPublic, params); localDestination->Start (); return localDestination; } - void DestroyLocalDestination (i2p::client::ClientDestination * dest) + void DestroyLocalDestination (std::shared_ptr dest) { if (dest) - { dest->Stop (); - delete dest; - } } - void RequestLeaseSet (i2p::client::ClientDestination * dest, const i2p::data::IdentHash& remote) + void RequestLeaseSet (std::shared_ptr dest, const i2p::data::IdentHash& remote) { if (dest) dest->RequestDestination (remote); } - std::shared_ptr CreateStream (i2p::client::ClientDestination * dest, const i2p::data::IdentHash& remote) + std::shared_ptr CreateStream (std::shared_ptr dest, const i2p::data::IdentHash& remote) { if (!dest) return nullptr; auto leaseSet = dest->FindLeaseSet (remote); @@ -96,7 +98,7 @@ namespace api } } - void AcceptStream (i2p::client::ClientDestination * dest, const i2p::stream::StreamingDestination::Acceptor& acceptor) + void AcceptStream (std::shared_ptr dest, const i2p::stream::StreamingDestination::Acceptor& acceptor) { if (dest) dest->AcceptStreams (acceptor); diff --git a/api.h b/api.h index 894aff49..d34f8ae4 100644 --- a/api.h +++ b/api.h @@ -16,18 +16,19 @@ namespace api void StartI2P (std::ostream * logStream = nullptr); // write system log to logStream, if not specified to .log in application's folder void StopI2P (); - + void RunPeerTest (); // should be called after UPnP + // destinations - i2p::client::ClientDestination * CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, + std::shared_ptr CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); - i2p::client::ClientDestination * CreateLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, + std::shared_ptr CreateLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, const std::map * params = nullptr); // transient destinations usually not published - void DestroyLocalDestination (i2p::client::ClientDestination * dest); + void DestroyLocalDestination (std::shared_ptr dest); // streams - void RequestLeaseSet (i2p::client::ClientDestination * dest, const i2p::data::IdentHash& remote); - std::shared_ptr CreateStream (i2p::client::ClientDestination * dest, const i2p::data::IdentHash& remote); - void AcceptStream (i2p::client::ClientDestination * dest, const i2p::stream::StreamingDestination::Acceptor& acceptor); + void RequestLeaseSet (std::shared_ptr dest, const i2p::data::IdentHash& remote); + std::shared_ptr CreateStream (std::shared_ptr dest, const i2p::data::IdentHash& remote); + void AcceptStream (std::shared_ptr dest, const i2p::stream::StreamingDestination::Acceptor& acceptor); void DestroyStream (std::shared_ptr stream); } } diff --git a/base64.h b/base64.h deleted file mode 100644 index c0ce1495..00000000 --- a/base64.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef BASE64_H -#define BASE64_H - -#include -#include - -namespace i2p -{ -namespace data -{ - - size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len); - size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len ); - const char * GetBase64SubstitutionTable (); - - size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen); - size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen); -} -} - -#endif - diff --git a/contrib/certificates/reseed/parg_at_mail.i2p.crt b/contrib/certificates/reseed/parg_at_mail.i2p.crt new file mode 100644 index 00000000..e78b264c --- /dev/null +++ b/contrib/certificates/reseed/parg_at_mail.i2p.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +N1tGWBZt9Prq +-----END CERTIFICATE----- diff --git a/contrib/certificates/reseed/sindu_at_mail.i2p.crt b/contrib/certificates/reseed/sindu_at_mail.i2p.crt deleted file mode 100644 index be1010f9..00000000 --- a/contrib/certificates/reseed/sindu_at_mail.i2p.crt +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFezCCA2OgAwIBAgIEPSs1SjANBgkqhkiG9w0BAQ0FADBuMQswCQYDVQQGEwJY -WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt -b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOc2luZHVAbWFpbC5p -MnAwHhcNMTQwNzIxMDAwNjUwWhcNMjQwNzIwMDAwNjUwWjBuMQswCQYDVQQGEwJY -WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt -b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOc2luZHVAbWFpbC5p -MnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnYMNclgj1gIJfot6f -65jbcXpcqf9hhL/uYiU4+uxVevWyJvNHpMzhPWNN4l3HihJJsINsPs/MsZ83Guva -GW5S/93I617kyjs/ZVpEtfABGewho0m9VCBV2/N1mJpHvvR+9OR+YVuWlTB/8sTG -2smdRj/dkKvewN5PSTQH350yT18NR/DmZUU1Iwa7vrNw8ol3rP3qx9UGpFN3JE7V -Q9cA1nktMiFUm76eOPOoln04WDqW2rvArXzvhSApvt0JsLBrZDzM3cx2Rc2UdjIC -h+Ha+G4CLjszfZfQAFJYPred38Gg6wuXiza/wCBSPiB92i94hIQF/OSeukaMiqwG -dRAcBT84/U9bddqHlIICw14PkNHOGUyJGjGKWQl/2bLX43ghWkUJmsTXS3iVcOTc -gb/7MoCRBdL0q2GyEJXuAoKXD9VqD3g+EdcBTQxS9lhZ0iTR7423pg6FP43VMEUC -HUi/BOX1tCY6iRzD1Su6ISIx7klH/sAWWa+SybLFXWtZJxHXXJICiBHJWRbWgtlu -5V+at66yg/LNpyfW3Am08gDV0kiWUBN2Ct4TX9PAQmNDisNgi2AzdZHIfX6tRpU8 -UnNcnZGOh4+HXQwJtI0y83C8TsXJUFYfGFWqXN69sMEmgtX8/w+YUqjtb2GcX1HN -6z9u9lH40JCFHTA/clPqOSQ+MQIDAQABoyEwHzAdBgNVHQ4EFgQU4R6x7ArVpSVs -b8VTBXmodXzyraEwDQYJKoZIhvcNAQENBQADggIBAJEHLSDBRU2x6EalmN2W952p -MEO5lGD+ZfUVK0c44t1O53naffwZx9QmDmrC4TjeQrLOpAXLQ8GJHAGeZVmYRgkf -OioKde5uuqVcxqNxArO8VyYGwsuNVPCaBV+SyIO+EmWogidSIrOP2WsRRS2NBhLV -2dp3TvMeod9bPwRl00guvv9iqL0UVSpQSlfGkAQTVpyADOaQHOzeoCpmtPOfB6OK -syB/Z/6HElKoUbvhynaASLgmo3wM93PVJQ2Ho294bQHtDl2qcOksJQvWfCgi7Zrt -KuHaM/a2kItzI6JmyNFXgsKQSDJ4UvoppppgD7K48zOtSipGuZAADC5w5HdVvIGJ -1Czva8kTcmC6AMc+4tACGqYZEAEokkeXn+pIIqKVj2eQukT/0dLGGHbKmxp3Z0f2 -pIH2Draq8JPdacr9P/xqEWUuViaOuC5OBjY8Fg3fmVCpwefIuk+DBhbJjEugB0Cu -brJpqNznoYahkbyAXIA8T+QJYMhoGWmaIcaPWK6K3nArvaxzwJbb9Egyivhyp9Rr -r2QMEZ+cPO8p1mEhKpL/wGqAzYyla8SJ06PzLc1lQeGiClu1nbZj5AgkZ1DLa8SD -iO7+e6rS0q1bzc7smE5JzZRiOVqKij/ReKa2uebLLI4wgAhz5ymaD1HfZY+3dV9T -WX89Xn2UyQf5kHifiDKL ------END CERTIFICATE----- diff --git a/filelist.mk b/filelist.mk index 623a1b33..f040183f 100644 --- a/filelist.mk +++ b/filelist.mk @@ -1,25 +1,16 @@ -COMMON_SRC = \ - CryptoConst.cpp Datagram.cpp Garlic.cpp I2NPProtocol.cpp LeaseSet.cpp \ +LIB_SRC = \ + Crypto.cpp Datagram.cpp Garlic.cpp I2NPProtocol.cpp LeaseSet.cpp \ Log.cpp NTCPSession.cpp NetDb.cpp NetDbRequests.cpp Profiling.cpp \ Reseed.cpp RouterContext.cpp RouterInfo.cpp Signature.cpp SSU.cpp \ SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp TransitTunnel.cpp \ Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp TunnelGateway.cpp \ - Destination.cpp UPnP.cpp util.cpp aes.cpp base64.cpp - - -ifeq ($(UNAME),Darwin) -# This is needed on OS X for some reason I don't understand (yet). -# Else will get linker error about unknown symbols. - torkel - COMMON_SRC += \ - AddressBook.cpp BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp I2PService.cpp SAM.cpp SOCKS.cpp \ - UPnP.cpp HTTPServer.cpp HTTPProxy.cpp i2p.cpp DaemonLinux.cpp I2PControl.cpp -endif + Destination.cpp Base.cpp util.cpp api.cpp +LIB_CLIENT_SRC = \ + AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \ + SAM.cpp SOCKS.cpp HTTPProxy.cpp # also: Daemon{Linux,Win32}.cpp will be added later -DAEMON_SRC = $(COMMON_SRC) \ - AddressBook.cpp BOB.cpp ClientContext.cpp Daemon.cpp I2PTunnel.cpp I2PService.cpp \ - SAM.cpp SOCKS.cpp HTTPServer.cpp HTTPProxy.cpp I2PControl.cpp i2p.cpp +DAEMON_SRC = \ + HTTPServer.cpp I2PControl.cpp UPnP.cpp Daemon.cpp i2pd.cpp -LIB_SRC := $(COMMON_SRC) \ - api.cpp diff --git a/hmac.h b/hmac.h deleted file mode 100644 index 0b76ceee..00000000 --- a/hmac.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef HMAC_H__ -#define HMAC_H__ - -#include -#include -#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1 -#include -#include "Identity.h" - -namespace i2p -{ -namespace crypto -{ - const uint64_t IPAD = 0x3636363636363636; - const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C; - - typedef i2p::data::Tag<32> MACKey; - - inline void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest) - // key is 32 bytes - // digest is 16 bytes - // block size is 64 bytes - { - uint64_t buf[256]; - // ikeypad - buf[0] = key.GetLL ()[0] ^ IPAD; - buf[1] = key.GetLL ()[1] ^ IPAD; - buf[2] = key.GetLL ()[2] ^ IPAD; - buf[3] = key.GetLL ()[3] ^ IPAD; - buf[4] = IPAD; - buf[5] = IPAD; - buf[6] = IPAD; - buf[7] = IPAD; - // concatenate with msg - memcpy (buf + 8, msg, len); - // calculate first hash - uint8_t hash[16]; // MD5 - CryptoPP::Weak1::MD5().CalculateDigest (hash, (uint8_t *)buf, len + 64); - - // okeypad - buf[0] = key.GetLL ()[0] ^ OPAD; - buf[1] = key.GetLL ()[1] ^ OPAD; - buf[2] = key.GetLL ()[2] ^ OPAD; - buf[3] = key.GetLL ()[3] ^ OPAD; - buf[4] = OPAD; - buf[5] = OPAD; - buf[6] = OPAD; - buf[7] = OPAD; - // copy first hash after okeypad - memcpy (buf + 8, hash, 16); - // fill next 16 bytes with zeros (first hash size assumed 32 bytes in I2P) - memset (buf + 10, 0, 16); - - // calculate digest - CryptoPP::Weak1::MD5().CalculateDigest (digest, (uint8_t *)buf, 96); - } -} -} - -#endif - diff --git a/i2p.cpp b/i2pd.cpp similarity index 80% rename from i2p.cpp rename to i2pd.cpp index 25fe29b0..32749d16 100644 --- a/i2p.cpp +++ b/i2pd.cpp @@ -1,7 +1,6 @@ #include #include #include "Daemon.h" -#include "Reseed.h" int main( int argc, char* argv[] ) { @@ -10,10 +9,10 @@ int main( int argc, char* argv[] ) { while (Daemon.running) { - //TODO Meeh: Find something better to do here. std::this_thread::sleep_for (std::chrono::seconds(1)); } } Daemon.stop(); return EXIT_SUCCESS; } + diff --git a/version.h b/version.h index bdff9c2e..141664b5 100644 --- a/version.h +++ b/version.h @@ -2,7 +2,7 @@ #define _VERSION_H_ #define CODENAME "Purple" -#define VERSION "0.10.0" -#define I2P_VERSION "0.9.20" +#define VERSION "2.0.0" +#define I2P_VERSION "0.9.22" #endif From 01dd982587a0d0b3cf5459228bd2311facc7e56b Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Nov 2015 09:22:09 -0500 Subject: [PATCH 0523/6300] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1361cee2..aadb9fcb 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ i2pd ==== I2P router written in C++ +Mirror of https://bitbucket.org/orignal/i2pd/src License ------- @@ -14,11 +15,12 @@ Donations BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 +ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z Requirements for Linux/FreeBSD/OSX ---------------------------------- -GCC 4.6 or newer, Boost 1.46 or newer, crypto++. Clang can be used instead of +GCC 4.6 or newer, Boost 1.46 or newer, openssl, zlib. Clang can be used instead of GCC. Requirements for Windows @@ -50,7 +52,7 @@ Testing First, build it. On Ubuntu/Debian based -* sudo apt-get install libboost-dev libboost-filesystem-dev libboost-program-options-dev libboost-regex-dev libcrypto++-dev libboost-date-time-dev +* sudo apt-get install libboost-dev libboost-filesystem-dev libboost-program-options-dev libboost-regex-dev libboost-date-time-dev libssl-dev zlib1g-dev * $ cd i2pd * $ make From 8a7536378488a22fdc0fbdb6057866d8a4a2c026 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Nov 2015 09:34:44 -0500 Subject: [PATCH 0524/6300] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aadb9fcb..393aab79 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ i2pd ==== I2P router written in C++ -Mirror of https://bitbucket.org/orignal/i2pd/src +Contains all ongoing changes from https://bitbucket.org/orignal/i2pd/src License ------- From aa12eb4ed4aede1d4ca1bd1685e13103e451dd2d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Nov 2015 09:37:08 -0500 Subject: [PATCH 0525/6300] removed autotools build --- build/autotools/.gitignore | 230 - build/autotools/Makefile.am | 26 - build/autotools/Makefile.in | 861 -- build/autotools/aclocal.m4 | 1039 -- build/autotools/config.guess | 1431 --- build/autotools/config.h.in | 175 - build/autotools/config.sub | 1811 ---- build/autotools/configure | 9297 ----------------- build/autotools/configure.ac | 72 - build/autotools/depcomp | 791 -- build/autotools/install-sh | 527 - build/autotools/m4/ax_boost_base.m4 | 272 - build/autotools/m4/ax_boost_date_time.m4 | 113 - build/autotools/m4/ax_boost_filesystem.m4 | 118 - .../autotools/m4/ax_boost_program_options.m4 | 108 - build/autotools/m4/ax_boost_regex.m4 | 111 - build/autotools/m4/ax_boost_system.m4 | 120 - .../autotools/m4/ax_cxx_compile_stdcxx_11.m4 | 141 - build/autotools/m4/ax_pthread.m4 | 332 - build/autotools/missing | 215 - 20 files changed, 17790 deletions(-) delete mode 100644 build/autotools/.gitignore delete mode 100644 build/autotools/Makefile.am delete mode 100644 build/autotools/Makefile.in delete mode 100644 build/autotools/aclocal.m4 delete mode 100755 build/autotools/config.guess delete mode 100644 build/autotools/config.h.in delete mode 100755 build/autotools/config.sub delete mode 100755 build/autotools/configure delete mode 100644 build/autotools/configure.ac delete mode 100755 build/autotools/depcomp delete mode 100755 build/autotools/install-sh delete mode 100644 build/autotools/m4/ax_boost_base.m4 delete mode 100644 build/autotools/m4/ax_boost_date_time.m4 delete mode 100644 build/autotools/m4/ax_boost_filesystem.m4 delete mode 100644 build/autotools/m4/ax_boost_program_options.m4 delete mode 100644 build/autotools/m4/ax_boost_regex.m4 delete mode 100644 build/autotools/m4/ax_boost_system.m4 delete mode 100644 build/autotools/m4/ax_cxx_compile_stdcxx_11.m4 delete mode 100644 build/autotools/m4/ax_pthread.m4 delete mode 100755 build/autotools/missing diff --git a/build/autotools/.gitignore b/build/autotools/.gitignore deleted file mode 100644 index a0fc14e4..00000000 --- a/build/autotools/.gitignore +++ /dev/null @@ -1,230 +0,0 @@ -# i2pd -obj/*.o -router.info -router.keys -i2p -netDb - -# Autotools -autom4te.cache -.deps -stamp-h1 -Makefile -config.h -config.h.in~ -config.log -config.status - -################# -## Eclipse -################# - -*.pydevproject -.project -.metadata -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.classpath -.settings/ -.loadpath - -# External tool builders -.externalToolBuilders/ - -# Locally stored "Eclipse launch configurations" -*.launch - -# CDT-specific -.cproject - -# PDT-specific -.buildpath - - -################# -## Visual Studio -################# - -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.sln.docstates - -# Build results - -[Dd]ebug/ -[Rr]elease/ -x64/ -[Bb]in/ -[Oo]bj/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -*_i.c -*_p.c -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.log -*.scc - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -*.ncrunch* -.*crunch*.local.xml - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.Publish.xml -*.pubxml - -# NuGet Packages Directory -## TODO: If you have NuGet Package Restore enabled, uncomment the next line -#packages/ - -# Windows Azure Build Output -csx -*.build.csdef - -# Windows Store app package directory -AppPackages/ - -# Others -sql/ -*.Cache -ClientBin/ -[Ss]tyle[Cc]op.* -~$* -*~ -*.dbmdl -*.[Pp]ublish.xml -*.pfx -*.publishsettings - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file to a newer -# Visual Studio version. Backup files are not needed, because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -App_Data/*.mdf -App_Data/*.ldf - -############# -## Windows detritus -############# - -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Mac crap -.DS_Store - - -############# -## Python -############# - -*.py[co] - -# Packages -*.egg -*.egg-info -dist/ -eggs/ -parts/ -var/ -sdist/ -develop-eggs/ -.installed.cfg - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.coverage -.tox - -#Translations -*.mo - -#Mr Developer -.mr.developer.cfg diff --git a/build/autotools/Makefile.am b/build/autotools/Makefile.am deleted file mode 100644 index 06006bc0..00000000 --- a/build/autotools/Makefile.am +++ /dev/null @@ -1,26 +0,0 @@ -bin_PROGRAMS = i2p -i2p_SOURCES = AddressBook.cpp CryptoConst.cpp Daemon.cpp \ - DaemonLinux.cpp DaemonWin32.cpp Garlic.cpp \ - HTTPProxy.cpp HTTPServer.cpp I2NPProtocol.cpp \ - I2PTunnel.cpp Identity.cpp LeaseSet.cpp Log.cpp \ - NTCPSession.cpp NetDb.cpp Reseed.cpp \ - RouterContext.cpp RouterInfo.cpp SOCKS.cpp SSU.cpp \ - SSUData.cpp Streaming.cpp TransitTunnel.cpp \ - Transports.cpp Tunnel.cpp TunnelEndpoint.cpp \ - TunnelGateway.cpp TunnelPool.cpp UPnP.cpp aes.cpp \ - base64.cpp i2p.cpp util.cpp I2PService.cpp \ - \ - AddressBook.h CryptoConst.h Daemon.h ElGamal.h \ - Garlic.h HTTPProxy.h HTTPServer.h I2NPProtocol.h \ - I2PEndian.h I2PTunnel.h Identity.h LeaseSet.h \ - LittleBigEndian.h Log.h NTCPSession.h NetDb.h Queue.h \ - Reseed.h RouterContext.h RouterInfo.h SOCKS.h SSU.h \ - SSUData.h Signature.h Streaming.h Timestamp.h \ - TransitTunnel.h Transports.h Tunnel.h TunnelBase.h \ - TunnelConfig.h TunnelEndpoint.h TunnelGateway.h \ - TunnelPool.h UPnP.h aes.h base64.h config.h hmac.h \ - util.h version.h I2PService.h - -AM_LDFLAGS = @BOOST_DATE_TIME_LIB@ @BOOST_FILESYSTEM_LIB@ \ - @BOOST_PROGRAM_OPTIONS_LIB@ @BOOST_REGEX_LIB@ \ - @BOOST_SYSTEM_LIB@ diff --git a/build/autotools/Makefile.in b/build/autotools/Makefile.in deleted file mode 100644 index 31a73cab..00000000 --- a/build/autotools/Makefile.in +++ /dev/null @@ -1,861 +0,0 @@ -# Makefile.in generated by automake 1.13.4 from Makefile.am. -# @configure_input@ - -# Copyright (C) 1994-2013 Free Software Foundation, Inc. - -# This Makefile.in is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY, to the extent permitted by law; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A -# PARTICULAR PURPOSE. - -@SET_MAKE@ - -VPATH = @srcdir@ -am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' -am__make_running_with_option = \ - case $${target_option-} in \ - ?) ;; \ - *) echo "am__make_running_with_option: internal error: invalid" \ - "target option '$${target_option-}' specified" >&2; \ - exit 1;; \ - esac; \ - has_opt=no; \ - sane_makeflags=$$MAKEFLAGS; \ - if $(am__is_gnu_make); then \ - sane_makeflags=$$MFLAGS; \ - else \ - case $$MAKEFLAGS in \ - *\\[\ \ ]*) \ - bs=\\; \ - sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ - | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ - esac; \ - fi; \ - skip_next=no; \ - strip_trailopt () \ - { \ - flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ - }; \ - for flg in $$sane_makeflags; do \ - test $$skip_next = yes && { skip_next=no; continue; }; \ - case $$flg in \ - *=*|--*) continue;; \ - -*I) strip_trailopt 'I'; skip_next=yes;; \ - -*I?*) strip_trailopt 'I';; \ - -*O) strip_trailopt 'O'; skip_next=yes;; \ - -*O?*) strip_trailopt 'O';; \ - -*l) strip_trailopt 'l'; skip_next=yes;; \ - -*l?*) strip_trailopt 'l';; \ - -[dEDm]) skip_next=yes;; \ - -[JT]) skip_next=yes;; \ - esac; \ - case $$flg in \ - *$$target_option*) has_opt=yes; break;; \ - esac; \ - done; \ - test $$has_opt = yes -am__make_dryrun = (target_option=n; $(am__make_running_with_option)) -am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) -pkgdatadir = $(datadir)/@PACKAGE@ -pkgincludedir = $(includedir)/@PACKAGE@ -pkglibdir = $(libdir)/@PACKAGE@ -pkglibexecdir = $(libexecdir)/@PACKAGE@ -am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd -install_sh_DATA = $(install_sh) -c -m 644 -install_sh_PROGRAM = $(install_sh) -c -install_sh_SCRIPT = $(install_sh) -c -INSTALL_HEADER = $(INSTALL_DATA) -transform = $(program_transform_name) -NORMAL_INSTALL = : -PRE_INSTALL = : -POST_INSTALL = : -NORMAL_UNINSTALL = : -PRE_UNINSTALL = : -POST_UNINSTALL = : -build_triplet = @build@ -host_triplet = @host@ -bin_PROGRAMS = i2p$(EXEEXT) -subdir = . -DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ - $(top_srcdir)/configure $(am__configure_deps) \ - $(srcdir)/config.h.in depcomp config.guess config.sub \ - install-sh missing -ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 -am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ - $(top_srcdir)/m4/ax_boost_base.m4 \ - $(top_srcdir)/m4/ax_boost_date_time.m4 \ - $(top_srcdir)/m4/ax_boost_filesystem.m4 \ - $(top_srcdir)/m4/ax_boost_program_options.m4 \ - $(top_srcdir)/m4/ax_boost_regex.m4 \ - $(top_srcdir)/m4/ax_boost_system.m4 \ - $(top_srcdir)/m4/ax_pthread.m4 $(top_srcdir)/configure.ac -am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ - $(ACLOCAL_M4) -am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ - configure.lineno config.status.lineno -mkinstalldirs = $(install_sh) -d -CONFIG_HEADER = config.h -CONFIG_CLEAN_FILES = -CONFIG_CLEAN_VPATH_FILES = -am__installdirs = "$(DESTDIR)$(bindir)" -PROGRAMS = $(bin_PROGRAMS) -am_i2p_OBJECTS = AddressBook.$(OBJEXT) CryptoConst.$(OBJEXT) \ - Daemon.$(OBJEXT) DaemonLinux.$(OBJEXT) DaemonWin32.$(OBJEXT) \ - Garlic.$(OBJEXT) HTTPProxy.$(OBJEXT) HTTPServer.$(OBJEXT) \ - I2NPProtocol.$(OBJEXT) I2PTunnel.$(OBJEXT) Identity.$(OBJEXT) \ - LeaseSet.$(OBJEXT) Log.$(OBJEXT) NTCPSession.$(OBJEXT) \ - NetDb.$(OBJEXT) Reseed.$(OBJEXT) RouterContext.$(OBJEXT) \ - RouterInfo.$(OBJEXT) SOCKS.$(OBJEXT) SSU.$(OBJEXT) \ - SSUData.$(OBJEXT) Streaming.$(OBJEXT) TransitTunnel.$(OBJEXT) \ - Transports.$(OBJEXT) Tunnel.$(OBJEXT) TunnelEndpoint.$(OBJEXT) \ - TunnelGateway.$(OBJEXT) TunnelPool.$(OBJEXT) UPnP.$(OBJEXT) \ - aes.$(OBJEXT) base64.$(OBJEXT) i2p.$(OBJEXT) util.$(OBJEXT) \ - SAM.$(OBJEXT) Destination.$(OBJEXT) ClientContext.$(OBJEXT) \ - Datagram.$(OBJEXT) SSUSession.$(OBJEXT) BOB.$(OBJEXT) \ - I2PControl.$(OBJEXT) Profiling.$(OBJEXT) Signature.$(OBJEXT) -i2p_OBJECTS = $(am_i2p_OBJECTS) -i2p_LDADD = $(LDADD) -AM_V_P = $(am__v_P_@AM_V@) -am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) -am__v_P_0 = false -am__v_P_1 = : -AM_V_GEN = $(am__v_GEN_@AM_V@) -am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) -am__v_GEN_0 = @echo " GEN " $@; -am__v_GEN_1 = -AM_V_at = $(am__v_at_@AM_V@) -am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) -am__v_at_0 = @ -am__v_at_1 = -DEFAULT_INCLUDES = -I.@am__isrc@ -depcomp = $(SHELL) $(top_srcdir)/depcomp -am__depfiles_maybe = depfiles -am__mv = mv -f -CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ - $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -AM_V_CXX = $(am__v_CXX_@AM_V@) -am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) -am__v_CXX_0 = @echo " CXX " $@; -am__v_CXX_1 = -CXXLD = $(CXX) -CXXLINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ - -o $@ -AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) -am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) -am__v_CXXLD_0 = @echo " CXXLD " $@; -am__v_CXXLD_1 = -COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ - $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -AM_V_CC = $(am__v_CC_@AM_V@) -am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) -am__v_CC_0 = @echo " CC " $@; -am__v_CC_1 = -CCLD = $(CC) -LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ -AM_V_CCLD = $(am__v_CCLD_@AM_V@) -am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) -am__v_CCLD_0 = @echo " CCLD " $@; -am__v_CCLD_1 = -SOURCES = $(i2p_SOURCES) -DIST_SOURCES = $(i2p_SOURCES) -am__can_run_installinfo = \ - case $$AM_UPDATE_INFO_DIR in \ - n|no|NO) false;; \ - *) (install-info --version) >/dev/null 2>&1;; \ - esac -am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) \ - $(LISP)config.h.in -# Read a list of newline-separated strings from the standard input, -# and print each of them once, without duplicates. Input order is -# *not* preserved. -am__uniquify_input = $(AWK) '\ - BEGIN { nonempty = 0; } \ - { items[$$0] = 1; nonempty = 1; } \ - END { if (nonempty) { for (i in items) print i; }; } \ -' -# Make sure the list of sources is unique. This is necessary because, -# e.g., the same source file might be shared among _SOURCES variables -# for different programs/libraries. -am__define_uniq_tagged_files = \ - list='$(am__tagged_files)'; \ - unique=`for i in $$list; do \ - if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ - done | $(am__uniquify_input)` -ETAGS = etags -CTAGS = ctags -CSCOPE = cscope -AM_RECURSIVE_TARGETS = cscope -DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) -distdir = $(PACKAGE)-$(VERSION) -top_distdir = $(distdir) -am__remove_distdir = \ - if test -d "$(distdir)"; then \ - find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ - && rm -rf "$(distdir)" \ - || { sleep 5 && rm -rf "$(distdir)"; }; \ - else :; fi -am__post_remove_distdir = $(am__remove_distdir) -DIST_ARCHIVES = $(distdir).tar.gz -GZIP_ENV = --best -DIST_TARGETS = dist-gzip -distuninstallcheck_listfiles = find . -type f -print -am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ - | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' -distcleancheck_listfiles = find . -type f -print -ACLOCAL = @ACLOCAL@ -AMTAR = @AMTAR@ -AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ -AUTOCONF = @AUTOCONF@ -AUTOHEADER = @AUTOHEADER@ -AUTOMAKE = @AUTOMAKE@ -AWK = @AWK@ -BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ -BOOST_DATE_TIME_LIB = @BOOST_DATE_TIME_LIB@ -BOOST_FILESYSTEM_LIB = @BOOST_FILESYSTEM_LIB@ -BOOST_LDFLAGS = @BOOST_LDFLAGS@ -BOOST_PROGRAM_OPTIONS_LIB = @BOOST_PROGRAM_OPTIONS_LIB@ -BOOST_REGEX_LIB = @BOOST_REGEX_LIB@ -BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ -CC = @CC@ -CCDEPMODE = @CCDEPMODE@ -CFLAGS = @CFLAGS@ -CPP = @CPP@ -CPPFLAGS = @CPPFLAGS@ -CXX = @CXX@ -CXXDEPMODE = @CXXDEPMODE@ -CXXFLAGS = @CXXFLAGS@ -CYGPATH_W = @CYGPATH_W@ -DEFS = @DEFS@ -DEPDIR = @DEPDIR@ -ECHO_C = @ECHO_C@ -ECHO_N = @ECHO_N@ -ECHO_T = @ECHO_T@ -EGREP = @EGREP@ -EXEEXT = @EXEEXT@ -GREP = @GREP@ -HAVE_CXX11 = @HAVE_CXX11@ -INSTALL = @INSTALL@ -INSTALL_DATA = @INSTALL_DATA@ -INSTALL_PROGRAM = @INSTALL_PROGRAM@ -INSTALL_SCRIPT = @INSTALL_SCRIPT@ -INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ -LDFLAGS = @LDFLAGS@ -LIBOBJS = @LIBOBJS@ -LIBS = @LIBS@ -LTLIBOBJS = @LTLIBOBJS@ -MAKEINFO = @MAKEINFO@ -MKDIR_P = @MKDIR_P@ -OBJEXT = @OBJEXT@ -PACKAGE = @PACKAGE@ -PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ -PACKAGE_NAME = @PACKAGE_NAME@ -PACKAGE_STRING = @PACKAGE_STRING@ -PACKAGE_TARNAME = @PACKAGE_TARNAME@ -PACKAGE_URL = @PACKAGE_URL@ -PACKAGE_VERSION = @PACKAGE_VERSION@ -PATH_SEPARATOR = @PATH_SEPARATOR@ -PTHREAD_CC = @PTHREAD_CC@ -PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ -PTHREAD_LIBS = @PTHREAD_LIBS@ -SET_MAKE = @SET_MAKE@ -SHELL = @SHELL@ -STRIP = @STRIP@ -VERSION = @VERSION@ -abs_builddir = @abs_builddir@ -abs_srcdir = @abs_srcdir@ -abs_top_builddir = @abs_top_builddir@ -abs_top_srcdir = @abs_top_srcdir@ -ac_ct_CC = @ac_ct_CC@ -ac_ct_CXX = @ac_ct_CXX@ -am__include = @am__include@ -am__leading_dot = @am__leading_dot@ -am__quote = @am__quote@ -am__tar = @am__tar@ -am__untar = @am__untar@ -ax_pthread_config = @ax_pthread_config@ -bindir = @bindir@ -build = @build@ -build_alias = @build_alias@ -build_cpu = @build_cpu@ -build_os = @build_os@ -build_vendor = @build_vendor@ -builddir = @builddir@ -datadir = @datadir@ -datarootdir = @datarootdir@ -docdir = @docdir@ -dvidir = @dvidir@ -exec_prefix = @exec_prefix@ -host = @host@ -host_alias = @host_alias@ -host_cpu = @host_cpu@ -host_os = @host_os@ -host_vendor = @host_vendor@ -htmldir = @htmldir@ -includedir = @includedir@ -infodir = @infodir@ -install_sh = @install_sh@ -libdir = @libdir@ -libexecdir = @libexecdir@ -localedir = @localedir@ -localstatedir = @localstatedir@ -mandir = @mandir@ -mkdir_p = @mkdir_p@ -oldincludedir = @oldincludedir@ -pdfdir = @pdfdir@ -prefix = @prefix@ -program_transform_name = @program_transform_name@ -psdir = @psdir@ -sbindir = @sbindir@ -sharedstatedir = @sharedstatedir@ -srcdir = @srcdir@ -sysconfdir = @sysconfdir@ -target_alias = @target_alias@ -top_build_prefix = @top_build_prefix@ -top_builddir = @top_builddir@ -top_srcdir = @top_srcdir@ -i2p_SOURCES = AddressBook.cpp CryptoConst.cpp Daemon.cpp \ - DaemonLinux.cpp DaemonWin32.cpp Garlic.cpp \ - HTTPProxy.cpp HTTPServer.cpp I2NPProtocol.cpp \ - I2PTunnel.cpp Identity.cpp LeaseSet.cpp Log.cpp \ - NTCPSession.cpp NetDb.cpp Reseed.cpp \ - RouterContext.cpp RouterInfo.cpp SOCKS.cpp SSU.cpp \ - SSUData.cpp Streaming.cpp TransitTunnel.cpp \ - Transports.cpp Tunnel.cpp TunnelEndpoint.cpp \ - TunnelGateway.cpp TunnelPool.cpp UPnP.cpp aes.cpp \ - base64.cpp i2p.cpp util.cpp SAM.cpp Destination.cpp \ - ClientContext.cpp DataFram.cpp SSUSession.cpp BOB.cpp \ - I2PControl.cpp Profiling.cpp Signature.cpp \ - NetDbRequests.cpp \ - \ - AddressBook.h CryptoConst.h Daemon.h ElGamal.h \ - Garlic.h HTTPProxy.h HTTPServer.h I2NPProtocol.h \ - I2PEndian.h I2PTunnel.h Identity.h LeaseSet.h \ - LittleBigEndian.h Log.h NTCPSession.h NetDb.h Queue.h \ - Reseed.h RouterContext.h RouterInfo.h SOCKS.h SSU.h \ - SSUData.h Signature.h Streaming.h Timestamp.h \ - TransitTunnel.h Transports.h Tunnel.h TunnelBase.h \ - TunnelConfig.h TunnelEndpoint.h TunnelGateway.h \ - TunnelPool.h UPnP.h aes.h base64.h config.h hmac.h \ - util.h version.h Destination.h ClientContext.h \ - TransportSession.h Datagram.h SSUSession.h BOB.h \ - I2PControl.h Profiling.h NetDbRequests.h - -AM_LDFLAGS = @BOOST_DATE_TIME_LIB@ @BOOST_FILESYSTEM_LIB@ \ - @BOOST_PROGRAM_OPTIONS_LIB@ @BOOST_REGEX_LIB@ \ - @BOOST_SYSTEM_LIB@ - -all: config.h - $(MAKE) $(AM_MAKEFLAGS) all-am - -.SUFFIXES: -.SUFFIXES: .cpp .o .obj -am--refresh: Makefile - @: -$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) - @for dep in $?; do \ - case '$(am__configure_deps)' in \ - *$$dep*) \ - echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \ - $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \ - && exit 0; \ - exit 1;; \ - esac; \ - done; \ - echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ - $(am__cd) $(top_srcdir) && \ - $(AUTOMAKE) --foreign Makefile -.PRECIOUS: Makefile -Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status - @case '$?' in \ - *config.status*) \ - echo ' $(SHELL) ./config.status'; \ - $(SHELL) ./config.status;; \ - *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \ - esac; - -$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) - $(SHELL) ./config.status --recheck - -$(top_srcdir)/configure: $(am__configure_deps) - $(am__cd) $(srcdir) && $(AUTOCONF) -$(ACLOCAL_M4): $(am__aclocal_m4_deps) - $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) -$(am__aclocal_m4_deps): - -config.h: stamp-h1 - @if test ! -f $@; then rm -f stamp-h1; else :; fi - @if test ! -f $@; then $(MAKE) $(AM_MAKEFLAGS) stamp-h1; else :; fi - -stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status - @rm -f stamp-h1 - cd $(top_builddir) && $(SHELL) ./config.status config.h -$(srcdir)/config.h.in: $(am__configure_deps) - ($(am__cd) $(top_srcdir) && $(AUTOHEADER)) - rm -f stamp-h1 - touch $@ - -distclean-hdr: - -rm -f config.h stamp-h1 -install-binPROGRAMS: $(bin_PROGRAMS) - @$(NORMAL_INSTALL) - @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ - if test -n "$$list"; then \ - echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ - $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ - fi; \ - for p in $$list; do echo "$$p $$p"; done | \ - sed 's/$(EXEEXT)$$//' | \ - while read p p1; do if test -f $$p \ - ; then echo "$$p"; echo "$$p"; else :; fi; \ - done | \ - sed -e 'p;s,.*/,,;n;h' \ - -e 's|.*|.|' \ - -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ - sed 'N;N;N;s,\n, ,g' | \ - $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ - { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ - if ($$2 == $$4) files[d] = files[d] " " $$1; \ - else { print "f", $$3 "/" $$4, $$1; } } \ - END { for (d in files) print "f", d, files[d] }' | \ - while read type dir files; do \ - if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ - test -z "$$files" || { \ - echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ - $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ - } \ - ; done - -uninstall-binPROGRAMS: - @$(NORMAL_UNINSTALL) - @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ - files=`for p in $$list; do echo "$$p"; done | \ - sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ - -e 's/$$/$(EXEEXT)/' \ - `; \ - test -n "$$list" || exit 0; \ - echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ - cd "$(DESTDIR)$(bindir)" && rm -f $$files - -clean-binPROGRAMS: - -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) - -i2p$(EXEEXT): $(i2p_OBJECTS) $(i2p_DEPENDENCIES) $(EXTRA_i2p_DEPENDENCIES) - @rm -f i2p$(EXEEXT) - $(AM_V_CXXLD)$(CXXLINK) $(i2p_OBJECTS) $(i2p_LDADD) $(LIBS) - -mostlyclean-compile: - -rm -f *.$(OBJEXT) - -distclean-compile: - -rm -f *.tab.c - -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/AddressBook.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CryptoConst.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Daemon.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DaemonLinux.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DaemonWin32.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Garlic.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HTTPProxy.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HTTPServer.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/I2NPProtocol.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/I2PTunnel.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Identity.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/LeaseSet.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Log.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NTCPSession.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NetDb.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Reseed.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RouterContext.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RouterInfo.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SOCKS.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SSU.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SSUData.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Streaming.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Destination.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TransitTunnel.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Transports.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Tunnel.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TunnelEndpoint.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TunnelGateway.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TunnelPool.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/UPnP.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/aes.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base64.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/i2p.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/util.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SAM.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BOB.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ClientContext.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Datagram.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SSUSession.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Profiling.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Signature.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NetDbRequests.Po@am__quote@ - -.cpp.o: -@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< - -.cpp.obj: -@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` -@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` - -ID: $(am__tagged_files) - $(am__define_uniq_tagged_files); mkid -fID $$unique -tags: tags-am -TAGS: tags - -tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) - set x; \ - here=`pwd`; \ - $(am__define_uniq_tagged_files); \ - shift; \ - if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ - test -n "$$unique" || unique=$$empty_fix; \ - if test $$# -gt 0; then \ - $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - "$$@" $$unique; \ - else \ - $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - $$unique; \ - fi; \ - fi -ctags: ctags-am - -CTAGS: ctags -ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) - $(am__define_uniq_tagged_files); \ - test -z "$(CTAGS_ARGS)$$unique" \ - || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ - $$unique - -GTAGS: - here=`$(am__cd) $(top_builddir) && pwd` \ - && $(am__cd) $(top_srcdir) \ - && gtags -i $(GTAGS_ARGS) "$$here" -cscope: cscope.files - test ! -s cscope.files \ - || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS) -clean-cscope: - -rm -f cscope.files -cscope.files: clean-cscope cscopelist -cscopelist: cscopelist-am - -cscopelist-am: $(am__tagged_files) - list='$(am__tagged_files)'; \ - case "$(srcdir)" in \ - [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ - *) sdir=$(subdir)/$(srcdir) ;; \ - esac; \ - for i in $$list; do \ - if test -f "$$i"; then \ - echo "$(subdir)/$$i"; \ - else \ - echo "$$sdir/$$i"; \ - fi; \ - done >> $(top_builddir)/cscope.files - -distclean-tags: - -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags - -rm -f cscope.out cscope.in.out cscope.po.out cscope.files - -distdir: $(DISTFILES) - $(am__remove_distdir) - test -d "$(distdir)" || mkdir "$(distdir)" - @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ - topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ - list='$(DISTFILES)'; \ - dist_files=`for file in $$list; do echo $$file; done | \ - sed -e "s|^$$srcdirstrip/||;t" \ - -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ - case $$dist_files in \ - */*) $(MKDIR_P) `echo "$$dist_files" | \ - sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ - sort -u` ;; \ - esac; \ - for file in $$dist_files; do \ - if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ - if test -d $$d/$$file; then \ - dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ - if test -d "$(distdir)/$$file"; then \ - find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ - fi; \ - if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ - cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ - find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ - fi; \ - cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ - else \ - test -f "$(distdir)/$$file" \ - || cp -p $$d/$$file "$(distdir)/$$file" \ - || exit 1; \ - fi; \ - done - -test -n "$(am__skip_mode_fix)" \ - || find "$(distdir)" -type d ! -perm -755 \ - -exec chmod u+rwx,go+rx {} \; -o \ - ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ - ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ - ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ - || chmod -R a+r "$(distdir)" -dist-gzip: distdir - tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz - $(am__post_remove_distdir) - -dist-bzip2: distdir - tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 - $(am__post_remove_distdir) - -dist-lzip: distdir - tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz - $(am__post_remove_distdir) - -dist-xz: distdir - tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz - $(am__post_remove_distdir) - -dist-tarZ: distdir - tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z - $(am__post_remove_distdir) - -dist-shar: distdir - shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz - $(am__post_remove_distdir) - -dist-zip: distdir - -rm -f $(distdir).zip - zip -rq $(distdir).zip $(distdir) - $(am__post_remove_distdir) - -dist dist-all: - $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:' - $(am__post_remove_distdir) - -# This target untars the dist file and tries a VPATH configuration. Then -# it guarantees that the distribution is self-contained by making another -# tarfile. -distcheck: dist - case '$(DIST_ARCHIVES)' in \ - *.tar.gz*) \ - GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\ - *.tar.bz2*) \ - bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ - *.tar.lz*) \ - lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ - *.tar.xz*) \ - xz -dc $(distdir).tar.xz | $(am__untar) ;;\ - *.tar.Z*) \ - uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ - *.shar.gz*) \ - GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\ - *.zip*) \ - unzip $(distdir).zip ;;\ - esac - chmod -R a-w $(distdir) - chmod u+w $(distdir) - mkdir $(distdir)/_build $(distdir)/_inst - chmod a-w $(distdir) - test -d $(distdir)/_build || exit 0; \ - dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ - && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ - && am__cwd=`pwd` \ - && $(am__cd) $(distdir)/_build \ - && ../configure --srcdir=.. --prefix="$$dc_install_base" \ - $(AM_DISTCHECK_CONFIGURE_FLAGS) \ - $(DISTCHECK_CONFIGURE_FLAGS) \ - && $(MAKE) $(AM_MAKEFLAGS) \ - && $(MAKE) $(AM_MAKEFLAGS) dvi \ - && $(MAKE) $(AM_MAKEFLAGS) check \ - && $(MAKE) $(AM_MAKEFLAGS) install \ - && $(MAKE) $(AM_MAKEFLAGS) installcheck \ - && $(MAKE) $(AM_MAKEFLAGS) uninstall \ - && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ - distuninstallcheck \ - && chmod -R a-w "$$dc_install_base" \ - && ({ \ - (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ - && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ - && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ - && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ - distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ - } || { rm -rf "$$dc_destdir"; exit 1; }) \ - && rm -rf "$$dc_destdir" \ - && $(MAKE) $(AM_MAKEFLAGS) dist \ - && rm -rf $(DIST_ARCHIVES) \ - && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ - && cd "$$am__cwd" \ - || exit 1 - $(am__post_remove_distdir) - @(echo "$(distdir) archives ready for distribution: "; \ - list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ - sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' -distuninstallcheck: - @test -n '$(distuninstallcheck_dir)' || { \ - echo 'ERROR: trying to run $@ with an empty' \ - '$$(distuninstallcheck_dir)' >&2; \ - exit 1; \ - }; \ - $(am__cd) '$(distuninstallcheck_dir)' || { \ - echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \ - exit 1; \ - }; \ - test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \ - || { echo "ERROR: files left after uninstall:" ; \ - if test -n "$(DESTDIR)"; then \ - echo " (check DESTDIR support)"; \ - fi ; \ - $(distuninstallcheck_listfiles) ; \ - exit 1; } >&2 -distcleancheck: distclean - @if test '$(srcdir)' = . ; then \ - echo "ERROR: distcleancheck can only run from a VPATH build" ; \ - exit 1 ; \ - fi - @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ - || { echo "ERROR: files left in build directory after distclean:" ; \ - $(distcleancheck_listfiles) ; \ - exit 1; } >&2 -check-am: all-am -check: check-am -all-am: Makefile $(PROGRAMS) config.h -installdirs: - for dir in "$(DESTDIR)$(bindir)"; do \ - test -z "$$dir" || $(MKDIR_P) "$$dir"; \ - done -install: install-am -install-exec: install-exec-am -install-data: install-data-am -uninstall: uninstall-am - -install-am: all-am - @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am - -installcheck: installcheck-am -install-strip: - if test -z '$(STRIP)'; then \ - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - install; \ - else \ - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ - fi -mostlyclean-generic: - -clean-generic: - -distclean-generic: - -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) - -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) - -maintainer-clean-generic: - @echo "This command is intended for maintainers to use" - @echo "it deletes files that may require special tools to rebuild." -clean: clean-am - -clean-am: clean-binPROGRAMS clean-generic mostlyclean-am - -distclean: distclean-am - -rm -f $(am__CONFIG_DISTCLEAN_FILES) - -rm -rf ./$(DEPDIR) - -rm -f Makefile -distclean-am: clean-am distclean-compile distclean-generic \ - distclean-hdr distclean-tags - -dvi: dvi-am - -dvi-am: - -html: html-am - -html-am: - -info: info-am - -info-am: - -install-data-am: - -install-dvi: install-dvi-am - -install-dvi-am: - -install-exec-am: install-binPROGRAMS - -install-html: install-html-am - -install-html-am: - -install-info: install-info-am - -install-info-am: - -install-man: - -install-pdf: install-pdf-am - -install-pdf-am: - -install-ps: install-ps-am - -install-ps-am: - -installcheck-am: - -maintainer-clean: maintainer-clean-am - -rm -f $(am__CONFIG_DISTCLEAN_FILES) - -rm -rf $(top_srcdir)/autom4te.cache - -rm -rf ./$(DEPDIR) - -rm -f Makefile -maintainer-clean-am: distclean-am maintainer-clean-generic - -mostlyclean: mostlyclean-am - -mostlyclean-am: mostlyclean-compile mostlyclean-generic - -pdf: pdf-am - -pdf-am: - -ps: ps-am - -ps-am: - -uninstall-am: uninstall-binPROGRAMS - -.MAKE: all install-am install-strip - -.PHONY: CTAGS GTAGS TAGS all all-am am--refresh check check-am clean \ - clean-binPROGRAMS clean-cscope clean-generic cscope \ - cscopelist-am ctags ctags-am dist dist-all dist-bzip2 \ - dist-gzip dist-lzip dist-shar dist-tarZ dist-xz dist-zip \ - distcheck distclean distclean-compile distclean-generic \ - distclean-hdr distclean-tags distcleancheck distdir \ - distuninstallcheck dvi dvi-am html html-am info info-am \ - install install-am install-binPROGRAMS install-data \ - install-data-am install-dvi install-dvi-am install-exec \ - install-exec-am install-html install-html-am install-info \ - install-info-am install-man install-pdf install-pdf-am \ - install-ps install-ps-am install-strip installcheck \ - installcheck-am installdirs maintainer-clean \ - maintainer-clean-generic mostlyclean mostlyclean-compile \ - mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \ - uninstall-am uninstall-binPROGRAMS - - -# Tell versions [3.59,3.63) of GNU make to not export all variables. -# Otherwise a system limit (for SysV at least) may be exceeded. -.NOEXPORT: diff --git a/build/autotools/aclocal.m4 b/build/autotools/aclocal.m4 deleted file mode 100644 index f6192c71..00000000 --- a/build/autotools/aclocal.m4 +++ /dev/null @@ -1,1039 +0,0 @@ -# generated automatically by aclocal 1.13.4 -*- Autoconf -*- - -# Copyright (C) 1996-2013 Free Software Foundation, Inc. - -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY, to the extent permitted by law; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A -# PARTICULAR PURPOSE. - -m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) -m4_ifndef([AC_AUTOCONF_VERSION], - [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl -m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.69],, -[m4_warning([this file was generated for autoconf 2.69. -You have another version of autoconf. It may work, but is not guaranteed to. -If you have problems, you may need to regenerate the build system entirely. -To do so, use the procedure documented by the package, typically 'autoreconf'.])]) - -# Copyright (C) 2002-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# AM_AUTOMAKE_VERSION(VERSION) -# ---------------------------- -# Automake X.Y traces this macro to ensure aclocal.m4 has been -# generated from the m4 files accompanying Automake X.Y. -# (This private macro should not be called outside this file.) -AC_DEFUN([AM_AUTOMAKE_VERSION], -[am__api_version='1.13' -dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to -dnl require some minimum version. Point them to the right macro. -m4_if([$1], [1.13.4], [], - [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl -]) - -# _AM_AUTOCONF_VERSION(VERSION) -# ----------------------------- -# aclocal traces this macro to find the Autoconf version. -# This is a private macro too. Using m4_define simplifies -# the logic in aclocal, which can simply ignore this definition. -m4_define([_AM_AUTOCONF_VERSION], []) - -# AM_SET_CURRENT_AUTOMAKE_VERSION -# ------------------------------- -# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. -# This function is AC_REQUIREd by AM_INIT_AUTOMAKE. -AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], -[AM_AUTOMAKE_VERSION([1.13.4])dnl -m4_ifndef([AC_AUTOCONF_VERSION], - [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl -_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) - -# AM_AUX_DIR_EXPAND -*- Autoconf -*- - -# Copyright (C) 2001-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets -# $ac_aux_dir to '$srcdir/foo'. In other projects, it is set to -# '$srcdir', '$srcdir/..', or '$srcdir/../..'. -# -# Of course, Automake must honor this variable whenever it calls a -# tool from the auxiliary directory. The problem is that $srcdir (and -# therefore $ac_aux_dir as well) can be either absolute or relative, -# depending on how configure is run. This is pretty annoying, since -# it makes $ac_aux_dir quite unusable in subdirectories: in the top -# source directory, any form will work fine, but in subdirectories a -# relative path needs to be adjusted first. -# -# $ac_aux_dir/missing -# fails when called from a subdirectory if $ac_aux_dir is relative -# $top_srcdir/$ac_aux_dir/missing -# fails if $ac_aux_dir is absolute, -# fails when called from a subdirectory in a VPATH build with -# a relative $ac_aux_dir -# -# The reason of the latter failure is that $top_srcdir and $ac_aux_dir -# are both prefixed by $srcdir. In an in-source build this is usually -# harmless because $srcdir is '.', but things will broke when you -# start a VPATH build or use an absolute $srcdir. -# -# So we could use something similar to $top_srcdir/$ac_aux_dir/missing, -# iff we strip the leading $srcdir from $ac_aux_dir. That would be: -# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` -# and then we would define $MISSING as -# MISSING="\${SHELL} $am_aux_dir/missing" -# This will work as long as MISSING is not called from configure, because -# unfortunately $(top_srcdir) has no meaning in configure. -# However there are other variables, like CC, which are often used in -# configure, and could therefore not use this "fixed" $ac_aux_dir. -# -# Another solution, used here, is to always expand $ac_aux_dir to an -# absolute PATH. The drawback is that using absolute paths prevent a -# configured tree to be moved without reconfiguration. - -AC_DEFUN([AM_AUX_DIR_EXPAND], -[dnl Rely on autoconf to set up CDPATH properly. -AC_PREREQ([2.50])dnl -# expand $ac_aux_dir to an absolute path -am_aux_dir=`cd $ac_aux_dir && pwd` -]) - -# AM_CONDITIONAL -*- Autoconf -*- - -# Copyright (C) 1997-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# AM_CONDITIONAL(NAME, SHELL-CONDITION) -# ------------------------------------- -# Define a conditional. -AC_DEFUN([AM_CONDITIONAL], -[AC_PREREQ([2.52])dnl - m4_if([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], - [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl -AC_SUBST([$1_TRUE])dnl -AC_SUBST([$1_FALSE])dnl -_AM_SUBST_NOTMAKE([$1_TRUE])dnl -_AM_SUBST_NOTMAKE([$1_FALSE])dnl -m4_define([_AM_COND_VALUE_$1], [$2])dnl -if $2; then - $1_TRUE= - $1_FALSE='#' -else - $1_TRUE='#' - $1_FALSE= -fi -AC_CONFIG_COMMANDS_PRE( -[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then - AC_MSG_ERROR([[conditional "$1" was never defined. -Usually this means the macro was only invoked conditionally.]]) -fi])]) - -# Copyright (C) 1999-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - - -# There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be -# written in clear, in which case automake, when reading aclocal.m4, -# will think it sees a *use*, and therefore will trigger all it's -# C support machinery. Also note that it means that autoscan, seeing -# CC etc. in the Makefile, will ask for an AC_PROG_CC use... - - -# _AM_DEPENDENCIES(NAME) -# ---------------------- -# See how the compiler implements dependency checking. -# NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC". -# We try a few techniques and use that to set a single cache variable. -# -# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was -# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular -# dependency, and given that the user is not expected to run this macro, -# just rely on AC_PROG_CC. -AC_DEFUN([_AM_DEPENDENCIES], -[AC_REQUIRE([AM_SET_DEPDIR])dnl -AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl -AC_REQUIRE([AM_MAKE_INCLUDE])dnl -AC_REQUIRE([AM_DEP_TRACK])dnl - -m4_if([$1], [CC], [depcc="$CC" am_compiler_list=], - [$1], [CXX], [depcc="$CXX" am_compiler_list=], - [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'], - [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'], - [$1], [UPC], [depcc="$UPC" am_compiler_list=], - [$1], [GCJ], [depcc="$GCJ" am_compiler_list='gcc3 gcc'], - [depcc="$$1" am_compiler_list=]) - -AC_CACHE_CHECK([dependency style of $depcc], - [am_cv_$1_dependencies_compiler_type], -[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then - # We make a subdir and do the tests there. Otherwise we can end up - # making bogus files that we don't know about and never remove. For - # instance it was reported that on HP-UX the gcc test will end up - # making a dummy file named 'D' -- because '-MD' means "put the output - # in D". - rm -rf conftest.dir - mkdir conftest.dir - # Copy depcomp to subdir because otherwise we won't find it if we're - # using a relative directory. - cp "$am_depcomp" conftest.dir - cd conftest.dir - # We will build objects and dependencies in a subdirectory because - # it helps to detect inapplicable dependency modes. For instance - # both Tru64's cc and ICC support -MD to output dependencies as a - # side effect of compilation, but ICC will put the dependencies in - # the current directory while Tru64 will put them in the object - # directory. - mkdir sub - - am_cv_$1_dependencies_compiler_type=none - if test "$am_compiler_list" = ""; then - am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` - fi - am__universal=false - m4_case([$1], [CC], - [case " $depcc " in #( - *\ -arch\ *\ -arch\ *) am__universal=true ;; - esac], - [CXX], - [case " $depcc " in #( - *\ -arch\ *\ -arch\ *) am__universal=true ;; - esac]) - - for depmode in $am_compiler_list; do - # Setup a source with many dependencies, because some compilers - # like to wrap large dependency lists on column 80 (with \), and - # we should not choose a depcomp mode which is confused by this. - # - # We need to recreate these files for each test, as the compiler may - # overwrite some of them when testing with obscure command lines. - # This happens at least with the AIX C compiler. - : > sub/conftest.c - for i in 1 2 3 4 5 6; do - echo '#include "conftst'$i'.h"' >> sub/conftest.c - # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with - # Solaris 10 /bin/sh. - echo '/* dummy */' > sub/conftst$i.h - done - echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf - - # We check with '-c' and '-o' for the sake of the "dashmstdout" - # mode. It turns out that the SunPro C++ compiler does not properly - # handle '-M -o', and we need to detect this. Also, some Intel - # versions had trouble with output in subdirs. - am__obj=sub/conftest.${OBJEXT-o} - am__minus_obj="-o $am__obj" - case $depmode in - gcc) - # This depmode causes a compiler race in universal mode. - test "$am__universal" = false || continue - ;; - nosideeffect) - # After this tag, mechanisms are not by side-effect, so they'll - # only be used when explicitly requested. - if test "x$enable_dependency_tracking" = xyes; then - continue - else - break - fi - ;; - msvc7 | msvc7msys | msvisualcpp | msvcmsys) - # This compiler won't grok '-c -o', but also, the minuso test has - # not run yet. These depmodes are late enough in the game, and - # so weak that their functioning should not be impacted. - am__obj=conftest.${OBJEXT-o} - am__minus_obj= - ;; - none) break ;; - esac - if depmode=$depmode \ - source=sub/conftest.c object=$am__obj \ - depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ - $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ - >/dev/null 2>conftest.err && - grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && - grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && - grep $am__obj sub/conftest.Po > /dev/null 2>&1 && - ${MAKE-make} -s -f confmf > /dev/null 2>&1; then - # icc doesn't choke on unknown options, it will just issue warnings - # or remarks (even with -Werror). So we grep stderr for any message - # that says an option was ignored or not supported. - # When given -MP, icc 7.0 and 7.1 complain thusly: - # icc: Command line warning: ignoring option '-M'; no argument required - # The diagnosis changed in icc 8.0: - # icc: Command line remark: option '-MP' not supported - if (grep 'ignoring option' conftest.err || - grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else - am_cv_$1_dependencies_compiler_type=$depmode - break - fi - fi - done - - cd .. - rm -rf conftest.dir -else - am_cv_$1_dependencies_compiler_type=none -fi -]) -AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) -AM_CONDITIONAL([am__fastdep$1], [ - test "x$enable_dependency_tracking" != xno \ - && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) -]) - - -# AM_SET_DEPDIR -# ------------- -# Choose a directory name for dependency files. -# This macro is AC_REQUIREd in _AM_DEPENDENCIES. -AC_DEFUN([AM_SET_DEPDIR], -[AC_REQUIRE([AM_SET_LEADING_DOT])dnl -AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl -]) - - -# AM_DEP_TRACK -# ------------ -AC_DEFUN([AM_DEP_TRACK], -[AC_ARG_ENABLE([dependency-tracking], [dnl -AS_HELP_STRING( - [--enable-dependency-tracking], - [do not reject slow dependency extractors]) -AS_HELP_STRING( - [--disable-dependency-tracking], - [speeds up one-time build])]) -if test "x$enable_dependency_tracking" != xno; then - am_depcomp="$ac_aux_dir/depcomp" - AMDEPBACKSLASH='\' - am__nodep='_no' -fi -AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) -AC_SUBST([AMDEPBACKSLASH])dnl -_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl -AC_SUBST([am__nodep])dnl -_AM_SUBST_NOTMAKE([am__nodep])dnl -]) - -# Generate code to set up dependency tracking. -*- Autoconf -*- - -# Copyright (C) 1999-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - - -# _AM_OUTPUT_DEPENDENCY_COMMANDS -# ------------------------------ -AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], -[{ - # Older Autoconf quotes --file arguments for eval, but not when files - # are listed without --file. Let's play safe and only enable the eval - # if we detect the quoting. - case $CONFIG_FILES in - *\'*) eval set x "$CONFIG_FILES" ;; - *) set x $CONFIG_FILES ;; - esac - shift - for mf - do - # Strip MF so we end up with the name of the file. - mf=`echo "$mf" | sed -e 's/:.*$//'` - # Check whether this is an Automake generated Makefile or not. - # We used to match only the files named 'Makefile.in', but - # some people rename them; so instead we look at the file content. - # Grep'ing the first line is not enough: some people post-process - # each Makefile.in and add a new line on top of each file to say so. - # Grep'ing the whole file is not good either: AIX grep has a line - # limit of 2048, but all sed's we know have understand at least 4000. - if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then - dirpart=`AS_DIRNAME("$mf")` - else - continue - fi - # Extract the definition of DEPDIR, am__include, and am__quote - # from the Makefile without running 'make'. - DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` - test -z "$DEPDIR" && continue - am__include=`sed -n 's/^am__include = //p' < "$mf"` - test -z "$am__include" && continue - am__quote=`sed -n 's/^am__quote = //p' < "$mf"` - # Find all dependency output files, they are included files with - # $(DEPDIR) in their names. We invoke sed twice because it is the - # simplest approach to changing $(DEPDIR) to its actual value in the - # expansion. - for file in `sed -n " - s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ - sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do - # Make sure the directory exists. - test -f "$dirpart/$file" && continue - fdir=`AS_DIRNAME(["$file"])` - AS_MKDIR_P([$dirpart/$fdir]) - # echo "creating $dirpart/$file" - echo '# dummy' > "$dirpart/$file" - done - done -} -])# _AM_OUTPUT_DEPENDENCY_COMMANDS - - -# AM_OUTPUT_DEPENDENCY_COMMANDS -# ----------------------------- -# This macro should only be invoked once -- use via AC_REQUIRE. -# -# This code is only required when automatic dependency tracking -# is enabled. FIXME. This creates each '.P' file that we will -# need in order to bootstrap the dependency handling code. -AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], -[AC_CONFIG_COMMANDS([depfiles], - [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], - [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"]) -]) - -# Do all the work for Automake. -*- Autoconf -*- - -# Copyright (C) 1996-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# This macro actually does too much. Some checks are only needed if -# your package does certain things. But this isn't really a big deal. - -# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) -# AM_INIT_AUTOMAKE([OPTIONS]) -# ----------------------------------------------- -# The call with PACKAGE and VERSION arguments is the old style -# call (pre autoconf-2.50), which is being phased out. PACKAGE -# and VERSION should now be passed to AC_INIT and removed from -# the call to AM_INIT_AUTOMAKE. -# We support both call styles for the transition. After -# the next Automake release, Autoconf can make the AC_INIT -# arguments mandatory, and then we can depend on a new Autoconf -# release and drop the old call support. -AC_DEFUN([AM_INIT_AUTOMAKE], -[AC_PREREQ([2.65])dnl -dnl Autoconf wants to disallow AM_ names. We explicitly allow -dnl the ones we care about. -m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl -AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl -AC_REQUIRE([AC_PROG_INSTALL])dnl -if test "`cd $srcdir && pwd`" != "`pwd`"; then - # Use -I$(srcdir) only when $(srcdir) != ., so that make's output - # is not polluted with repeated "-I." - AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl - # test to see if srcdir already configured - if test -f $srcdir/config.status; then - AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) - fi -fi - -# test whether we have cygpath -if test -z "$CYGPATH_W"; then - if (cygpath --version) >/dev/null 2>/dev/null; then - CYGPATH_W='cygpath -w' - else - CYGPATH_W=echo - fi -fi -AC_SUBST([CYGPATH_W]) - -# Define the identity of the package. -dnl Distinguish between old-style and new-style calls. -m4_ifval([$2], -[AC_DIAGNOSE([obsolete], - [$0: two- and three-arguments forms are deprecated.]) -m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl - AC_SUBST([PACKAGE], [$1])dnl - AC_SUBST([VERSION], [$2])], -[_AM_SET_OPTIONS([$1])dnl -dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. -m4_if( - m4_ifdef([AC_PACKAGE_NAME], [ok]):m4_ifdef([AC_PACKAGE_VERSION], [ok]), - [ok:ok],, - [m4_fatal([AC_INIT should be called with package and version arguments])])dnl - AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl - AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl - -_AM_IF_OPTION([no-define],, -[AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package]) - AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl - -# Some tools Automake needs. -AC_REQUIRE([AM_SANITY_CHECK])dnl -AC_REQUIRE([AC_ARG_PROGRAM])dnl -AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}]) -AM_MISSING_PROG([AUTOCONF], [autoconf]) -AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}]) -AM_MISSING_PROG([AUTOHEADER], [autoheader]) -AM_MISSING_PROG([MAKEINFO], [makeinfo]) -AC_REQUIRE([AM_PROG_INSTALL_SH])dnl -AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl -AC_REQUIRE([AC_PROG_MKDIR_P])dnl -# For better backward compatibility. To be removed once Automake 1.9.x -# dies out for good. For more background, see: -# -# -AC_SUBST([mkdir_p], ['$(MKDIR_P)']) -# We need awk for the "check" target. The system "awk" is bad on -# some platforms. -AC_REQUIRE([AC_PROG_AWK])dnl -AC_REQUIRE([AC_PROG_MAKE_SET])dnl -AC_REQUIRE([AM_SET_LEADING_DOT])dnl -_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], - [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], - [_AM_PROG_TAR([v7])])]) -_AM_IF_OPTION([no-dependencies],, -[AC_PROVIDE_IFELSE([AC_PROG_CC], - [_AM_DEPENDENCIES([CC])], - [m4_define([AC_PROG_CC], - m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl -AC_PROVIDE_IFELSE([AC_PROG_CXX], - [_AM_DEPENDENCIES([CXX])], - [m4_define([AC_PROG_CXX], - m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl -AC_PROVIDE_IFELSE([AC_PROG_OBJC], - [_AM_DEPENDENCIES([OBJC])], - [m4_define([AC_PROG_OBJC], - m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl -AC_PROVIDE_IFELSE([AC_PROG_OBJCXX], - [_AM_DEPENDENCIES([OBJCXX])], - [m4_define([AC_PROG_OBJCXX], - m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl -]) -AC_REQUIRE([AM_SILENT_RULES])dnl -dnl The testsuite driver may need to know about EXEEXT, so add the -dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This -dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below. -AC_CONFIG_COMMANDS_PRE(dnl -[m4_provide_if([_AM_COMPILER_EXEEXT], - [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl -]) - -dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion. Do not -dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further -dnl mangled by Autoconf and run in a shell conditional statement. -m4_define([_AC_COMPILER_EXEEXT], -m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])]) - - -# When config.status generates a header, we must update the stamp-h file. -# This file resides in the same directory as the config header -# that is generated. The stamp files are numbered to have different names. - -# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the -# loop where config.status creates the headers, so we can generate -# our stamp files there. -AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], -[# Compute $1's index in $config_headers. -_am_arg=$1 -_am_stamp_count=1 -for _am_header in $config_headers :; do - case $_am_header in - $_am_arg | $_am_arg:* ) - break ;; - * ) - _am_stamp_count=`expr $_am_stamp_count + 1` ;; - esac -done -echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) - -# Copyright (C) 2001-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# AM_PROG_INSTALL_SH -# ------------------ -# Define $install_sh. -AC_DEFUN([AM_PROG_INSTALL_SH], -[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl -if test x"${install_sh}" != xset; then - case $am_aux_dir in - *\ * | *\ *) - install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; - *) - install_sh="\${SHELL} $am_aux_dir/install-sh" - esac -fi -AC_SUBST([install_sh])]) - -# Copyright (C) 2003-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# Check whether the underlying file-system supports filenames -# with a leading dot. For instance MS-DOS doesn't. -AC_DEFUN([AM_SET_LEADING_DOT], -[rm -rf .tst 2>/dev/null -mkdir .tst 2>/dev/null -if test -d .tst; then - am__leading_dot=. -else - am__leading_dot=_ -fi -rmdir .tst 2>/dev/null -AC_SUBST([am__leading_dot])]) - -# Check to see how 'make' treats includes. -*- Autoconf -*- - -# Copyright (C) 2001-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# AM_MAKE_INCLUDE() -# ----------------- -# Check to see how make treats includes. -AC_DEFUN([AM_MAKE_INCLUDE], -[am_make=${MAKE-make} -cat > confinc << 'END' -am__doit: - @echo this is the am__doit target -.PHONY: am__doit -END -# If we don't find an include directive, just comment out the code. -AC_MSG_CHECKING([for style of include used by $am_make]) -am__include="#" -am__quote= -_am_result=none -# First try GNU make style include. -echo "include confinc" > confmf -# Ignore all kinds of additional output from 'make'. -case `$am_make -s -f confmf 2> /dev/null` in #( -*the\ am__doit\ target*) - am__include=include - am__quote= - _am_result=GNU - ;; -esac -# Now try BSD make style include. -if test "$am__include" = "#"; then - echo '.include "confinc"' > confmf - case `$am_make -s -f confmf 2> /dev/null` in #( - *the\ am__doit\ target*) - am__include=.include - am__quote="\"" - _am_result=BSD - ;; - esac -fi -AC_SUBST([am__include]) -AC_SUBST([am__quote]) -AC_MSG_RESULT([$_am_result]) -rm -f confinc confmf -]) - -# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- - -# Copyright (C) 1997-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# AM_MISSING_PROG(NAME, PROGRAM) -# ------------------------------ -AC_DEFUN([AM_MISSING_PROG], -[AC_REQUIRE([AM_MISSING_HAS_RUN]) -$1=${$1-"${am_missing_run}$2"} -AC_SUBST($1)]) - -# AM_MISSING_HAS_RUN -# ------------------ -# Define MISSING if not defined so far and test if it is modern enough. -# If it is, set am_missing_run to use it, otherwise, to nothing. -AC_DEFUN([AM_MISSING_HAS_RUN], -[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl -AC_REQUIRE_AUX_FILE([missing])dnl -if test x"${MISSING+set}" != xset; then - case $am_aux_dir in - *\ * | *\ *) - MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; - *) - MISSING="\${SHELL} $am_aux_dir/missing" ;; - esac -fi -# Use eval to expand $SHELL -if eval "$MISSING --is-lightweight"; then - am_missing_run="$MISSING " -else - am_missing_run= - AC_MSG_WARN(['missing' script is too old or missing]) -fi -]) - -# Helper functions for option handling. -*- Autoconf -*- - -# Copyright (C) 2001-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# _AM_MANGLE_OPTION(NAME) -# ----------------------- -AC_DEFUN([_AM_MANGLE_OPTION], -[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) - -# _AM_SET_OPTION(NAME) -# -------------------- -# Set option NAME. Presently that only means defining a flag for this option. -AC_DEFUN([_AM_SET_OPTION], -[m4_define(_AM_MANGLE_OPTION([$1]), [1])]) - -# _AM_SET_OPTIONS(OPTIONS) -# ------------------------ -# OPTIONS is a space-separated list of Automake options. -AC_DEFUN([_AM_SET_OPTIONS], -[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) - -# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) -# ------------------------------------------- -# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. -AC_DEFUN([_AM_IF_OPTION], -[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) - -# Check to make sure that the build environment is sane. -*- Autoconf -*- - -# Copyright (C) 1996-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# AM_SANITY_CHECK -# --------------- -AC_DEFUN([AM_SANITY_CHECK], -[AC_MSG_CHECKING([whether build environment is sane]) -# Reject unsafe characters in $srcdir or the absolute working directory -# name. Accept space and tab only in the latter. -am_lf=' -' -case `pwd` in - *[[\\\"\#\$\&\'\`$am_lf]]*) - AC_MSG_ERROR([unsafe absolute working directory name]);; -esac -case $srcdir in - *[[\\\"\#\$\&\'\`$am_lf\ \ ]]*) - AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);; -esac - -# Do 'set' in a subshell so we don't clobber the current shell's -# arguments. Must try -L first in case configure is actually a -# symlink; some systems play weird games with the mod time of symlinks -# (eg FreeBSD returns the mod time of the symlink's containing -# directory). -if ( - am_has_slept=no - for am_try in 1 2; do - echo "timestamp, slept: $am_has_slept" > conftest.file - set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` - if test "$[*]" = "X"; then - # -L didn't work. - set X `ls -t "$srcdir/configure" conftest.file` - fi - if test "$[*]" != "X $srcdir/configure conftest.file" \ - && test "$[*]" != "X conftest.file $srcdir/configure"; then - - # If neither matched, then we have a broken ls. This can happen - # if, for instance, CONFIG_SHELL is bash and it inherits a - # broken ls alias from the environment. This has actually - # happened. Such a system could not be considered "sane". - AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken - alias in your environment]) - fi - if test "$[2]" = conftest.file || test $am_try -eq 2; then - break - fi - # Just in case. - sleep 1 - am_has_slept=yes - done - test "$[2]" = conftest.file - ) -then - # Ok. - : -else - AC_MSG_ERROR([newly created file is older than distributed files! -Check your system clock]) -fi -AC_MSG_RESULT([yes]) -# If we didn't sleep, we still need to ensure time stamps of config.status and -# generated files are strictly newer. -am_sleep_pid= -if grep 'slept: no' conftest.file >/dev/null 2>&1; then - ( sleep 1 ) & - am_sleep_pid=$! -fi -AC_CONFIG_COMMANDS_PRE( - [AC_MSG_CHECKING([that generated files are newer than configure]) - if test -n "$am_sleep_pid"; then - # Hide warnings about reused PIDs. - wait $am_sleep_pid 2>/dev/null - fi - AC_MSG_RESULT([done])]) -rm -f conftest.file -]) - -# Copyright (C) 2009-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# AM_SILENT_RULES([DEFAULT]) -# -------------------------- -# Enable less verbose build rules; with the default set to DEFAULT -# ("yes" being less verbose, "no" or empty being verbose). -AC_DEFUN([AM_SILENT_RULES], -[AC_ARG_ENABLE([silent-rules], [dnl -AS_HELP_STRING( - [--enable-silent-rules], - [less verbose build output (undo: "make V=1")]) -AS_HELP_STRING( - [--disable-silent-rules], - [verbose build output (undo: "make V=0")])dnl -]) -case $enable_silent_rules in @%:@ ((( - yes) AM_DEFAULT_VERBOSITY=0;; - no) AM_DEFAULT_VERBOSITY=1;; - *) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);; -esac -dnl -dnl A few 'make' implementations (e.g., NonStop OS and NextStep) -dnl do not support nested variable expansions. -dnl See automake bug#9928 and bug#10237. -am_make=${MAKE-make} -AC_CACHE_CHECK([whether $am_make supports nested variables], - [am_cv_make_support_nested_variables], - [if AS_ECHO([['TRUE=$(BAR$(V)) -BAR0=false -BAR1=true -V=1 -am__doit: - @$(TRUE) -.PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then - am_cv_make_support_nested_variables=yes -else - am_cv_make_support_nested_variables=no -fi]) -if test $am_cv_make_support_nested_variables = yes; then - dnl Using '$V' instead of '$(V)' breaks IRIX make. - AM_V='$(V)' - AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' -else - AM_V=$AM_DEFAULT_VERBOSITY - AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY -fi -AC_SUBST([AM_V])dnl -AM_SUBST_NOTMAKE([AM_V])dnl -AC_SUBST([AM_DEFAULT_V])dnl -AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl -AC_SUBST([AM_DEFAULT_VERBOSITY])dnl -AM_BACKSLASH='\' -AC_SUBST([AM_BACKSLASH])dnl -_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl -]) - -# Copyright (C) 2001-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# AM_PROG_INSTALL_STRIP -# --------------------- -# One issue with vendor 'install' (even GNU) is that you can't -# specify the program used to strip binaries. This is especially -# annoying in cross-compiling environments, where the build's strip -# is unlikely to handle the host's binaries. -# Fortunately install-sh will honor a STRIPPROG variable, so we -# always use install-sh in "make install-strip", and initialize -# STRIPPROG with the value of the STRIP variable (set by the user). -AC_DEFUN([AM_PROG_INSTALL_STRIP], -[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl -# Installed binaries are usually stripped using 'strip' when the user -# run "make install-strip". However 'strip' might not be the right -# tool to use in cross-compilation environments, therefore Automake -# will honor the 'STRIP' environment variable to overrule this program. -dnl Don't test for $cross_compiling = yes, because it might be 'maybe'. -if test "$cross_compiling" != no; then - AC_CHECK_TOOL([STRIP], [strip], :) -fi -INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" -AC_SUBST([INSTALL_STRIP_PROGRAM])]) - -# Copyright (C) 2006-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# _AM_SUBST_NOTMAKE(VARIABLE) -# --------------------------- -# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. -# This macro is traced by Automake. -AC_DEFUN([_AM_SUBST_NOTMAKE]) - -# AM_SUBST_NOTMAKE(VARIABLE) -# -------------------------- -# Public sister of _AM_SUBST_NOTMAKE. -AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) - -# Check how to create a tarball. -*- Autoconf -*- - -# Copyright (C) 2004-2013 Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# _AM_PROG_TAR(FORMAT) -# -------------------- -# Check how to create a tarball in format FORMAT. -# FORMAT should be one of 'v7', 'ustar', or 'pax'. -# -# Substitute a variable $(am__tar) that is a command -# writing to stdout a FORMAT-tarball containing the directory -# $tardir. -# tardir=directory && $(am__tar) > result.tar -# -# Substitute a variable $(am__untar) that extract such -# a tarball read from stdin. -# $(am__untar) < result.tar -# -AC_DEFUN([_AM_PROG_TAR], -[# Always define AMTAR for backward compatibility. Yes, it's still used -# in the wild :-( We should find a proper way to deprecate it ... -AC_SUBST([AMTAR], ['$${TAR-tar}']) - -# We'll loop over all known methods to create a tar archive until one works. -_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' - -m4_if([$1], [v7], - [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'], - - [m4_case([$1], - [ustar], - [# The POSIX 1988 'ustar' format is defined with fixed-size fields. - # There is notably a 21 bits limit for the UID and the GID. In fact, - # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343 - # and bug#13588). - am_max_uid=2097151 # 2^21 - 1 - am_max_gid=$am_max_uid - # The $UID and $GID variables are not portable, so we need to resort - # to the POSIX-mandated id(1) utility. Errors in the 'id' calls - # below are definitely unexpected, so allow the users to see them - # (that is, avoid stderr redirection). - am_uid=`id -u || echo unknown` - am_gid=`id -g || echo unknown` - AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format]) - if test $am_uid -le $am_max_uid; then - AC_MSG_RESULT([yes]) - else - AC_MSG_RESULT([no]) - _am_tools=none - fi - AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format]) - if test $am_gid -le $am_max_gid; then - AC_MSG_RESULT([yes]) - else - AC_MSG_RESULT([no]) - _am_tools=none - fi], - - [pax], - [], - - [m4_fatal([Unknown tar format])]) - - AC_MSG_CHECKING([how to create a $1 tar archive]) - - # Go ahead even if we have the value already cached. We do so because we - # need to set the values for the 'am__tar' and 'am__untar' variables. - _am_tools=${am_cv_prog_tar_$1-$_am_tools} - - for _am_tool in $_am_tools; do - case $_am_tool in - gnutar) - for _am_tar in tar gnutar gtar; do - AM_RUN_LOG([$_am_tar --version]) && break - done - am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' - am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' - am__untar="$_am_tar -xf -" - ;; - plaintar) - # Must skip GNU tar: if it does not support --format= it doesn't create - # ustar tarball either. - (tar --version) >/dev/null 2>&1 && continue - am__tar='tar chf - "$$tardir"' - am__tar_='tar chf - "$tardir"' - am__untar='tar xf -' - ;; - pax) - am__tar='pax -L -x $1 -w "$$tardir"' - am__tar_='pax -L -x $1 -w "$tardir"' - am__untar='pax -r' - ;; - cpio) - am__tar='find "$$tardir" -print | cpio -o -H $1 -L' - am__tar_='find "$tardir" -print | cpio -o -H $1 -L' - am__untar='cpio -i -H $1 -d' - ;; - none) - am__tar=false - am__tar_=false - am__untar=false - ;; - esac - - # If the value was cached, stop now. We just wanted to have am__tar - # and am__untar set. - test -n "${am_cv_prog_tar_$1}" && break - - # tar/untar a dummy directory, and stop if the command works. - rm -rf conftest.dir - mkdir conftest.dir - echo GrepMe > conftest.dir/file - AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) - rm -rf conftest.dir - if test -s conftest.tar; then - AM_RUN_LOG([$am__untar /dev/null 2>&1 && break - fi - done - rm -rf conftest.dir - - AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) - AC_MSG_RESULT([$am_cv_prog_tar_$1])]) - -AC_SUBST([am__tar]) -AC_SUBST([am__untar]) -]) # _AM_PROG_TAR - diff --git a/build/autotools/config.guess b/build/autotools/config.guess deleted file mode 100755 index 9c9d05bb..00000000 --- a/build/autotools/config.guess +++ /dev/null @@ -1,1431 +0,0 @@ -#! /bin/sh -# Attempt to guess a canonical system name. -# Copyright 1992-2014 Free Software Foundation, Inc. - -timestamp='2014-02-12' - -# This file is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, see . -# -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that -# program. This Exception is an additional permission under section 7 -# of the GNU General Public License, version 3 ("GPLv3"). -# -# Originally written by Per Bothner. -# -# You can get the latest version of this script from: -# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD -# -# Please send patches with a ChangeLog entry to config-patches@gnu.org. - - -me=`echo "$0" | sed -e 's,.*/,,'` - -usage="\ -Usage: $0 [OPTION] - -Output the configuration name of the system \`$me' is run on. - -Operation modes: - -h, --help print this help, then exit - -t, --time-stamp print date of last modification, then exit - -v, --version print version number, then exit - -Report bugs and patches to ." - -version="\ -GNU config.guess ($timestamp) - -Originally written by Per Bothner. -Copyright 1992-2014 Free Software Foundation, Inc. - -This is free software; see the source for copying conditions. There is NO -warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." - -help=" -Try \`$me --help' for more information." - -# Parse command line -while test $# -gt 0 ; do - case $1 in - --time-stamp | --time* | -t ) - echo "$timestamp" ; exit ;; - --version | -v ) - echo "$version" ; exit ;; - --help | --h* | -h ) - echo "$usage"; exit ;; - -- ) # Stop option processing - shift; break ;; - - ) # Use stdin as input. - break ;; - -* ) - echo "$me: invalid option $1$help" >&2 - exit 1 ;; - * ) - break ;; - esac -done - -if test $# != 0; then - echo "$me: too many arguments$help" >&2 - exit 1 -fi - -trap 'exit 1' 1 2 15 - -# CC_FOR_BUILD -- compiler used by this script. Note that the use of a -# compiler to aid in system detection is discouraged as it requires -# temporary files to be created and, as you can see below, it is a -# headache to deal with in a portable fashion. - -# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still -# use `HOST_CC' if defined, but it is deprecated. - -# Portable tmp directory creation inspired by the Autoconf team. - -set_cc_for_build=' -trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; -trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; -: ${TMPDIR=/tmp} ; - { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || - { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || - { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || - { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; -dummy=$tmp/dummy ; -tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; -case $CC_FOR_BUILD,$HOST_CC,$CC in - ,,) echo "int x;" > $dummy.c ; - for c in cc gcc c89 c99 ; do - if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then - CC_FOR_BUILD="$c"; break ; - fi ; - done ; - if test x"$CC_FOR_BUILD" = x ; then - CC_FOR_BUILD=no_compiler_found ; - fi - ;; - ,,*) CC_FOR_BUILD=$CC ;; - ,*,*) CC_FOR_BUILD=$HOST_CC ;; -esac ; set_cc_for_build= ;' - -# This is needed to find uname on a Pyramid OSx when run in the BSD universe. -# (ghazi@noc.rutgers.edu 1994-08-24) -if (test -f /.attbin/uname) >/dev/null 2>&1 ; then - PATH=$PATH:/.attbin ; export PATH -fi - -UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown -UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown -UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown -UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown - -case "${UNAME_SYSTEM}" in -Linux|GNU|GNU/*) - # If the system lacks a compiler, then just pick glibc. - # We could probably try harder. - LIBC=gnu - - eval $set_cc_for_build - cat <<-EOF > $dummy.c - #include - #if defined(__UCLIBC__) - LIBC=uclibc - #elif defined(__dietlibc__) - LIBC=dietlibc - #else - LIBC=gnu - #endif - EOF - eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` - ;; -esac - -# Note: order is significant - the case branches are not exclusive. - -case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in - *:NetBSD:*:*) - # NetBSD (nbsd) targets should (where applicable) match one or - # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, - # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently - # switched to ELF, *-*-netbsd* would select the old - # object file format. This provides both forward - # compatibility and a consistent mechanism for selecting the - # object file format. - # - # Note: NetBSD doesn't particularly care about the vendor - # portion of the name. We always set it to "unknown". - sysctl="sysctl -n hw.machine_arch" - UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ - /usr/sbin/$sysctl 2>/dev/null || echo unknown)` - case "${UNAME_MACHINE_ARCH}" in - armeb) machine=armeb-unknown ;; - arm*) machine=arm-unknown ;; - sh3el) machine=shl-unknown ;; - sh3eb) machine=sh-unknown ;; - sh5el) machine=sh5le-unknown ;; - *) machine=${UNAME_MACHINE_ARCH}-unknown ;; - esac - # The Operating System including object format, if it has switched - # to ELF recently, or will in the future. - case "${UNAME_MACHINE_ARCH}" in - arm*|i386|m68k|ns32k|sh3*|sparc|vax) - eval $set_cc_for_build - if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ - | grep -q __ELF__ - then - # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). - # Return netbsd for either. FIX? - os=netbsd - else - os=netbsdelf - fi - ;; - *) - os=netbsd - ;; - esac - # The OS release - # Debian GNU/NetBSD machines have a different userland, and - # thus, need a distinct triplet. However, they do not need - # kernel version information, so it can be replaced with a - # suitable tag, in the style of linux-gnu. - case "${UNAME_VERSION}" in - Debian*) - release='-gnu' - ;; - *) - release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` - ;; - esac - # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: - # contains redundant information, the shorter form: - # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. - echo "${machine}-${os}${release}" - exit ;; - *:Bitrig:*:*) - UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` - echo ${UNAME_MACHINE_ARCH}-unknown-bitrig${UNAME_RELEASE} - exit ;; - *:OpenBSD:*:*) - UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` - echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} - exit ;; - *:ekkoBSD:*:*) - echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} - exit ;; - *:SolidBSD:*:*) - echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE} - exit ;; - macppc:MirBSD:*:*) - echo powerpc-unknown-mirbsd${UNAME_RELEASE} - exit ;; - *:MirBSD:*:*) - echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} - exit ;; - alpha:OSF1:*:*) - case $UNAME_RELEASE in - *4.0) - UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` - ;; - *5.*) - UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` - ;; - esac - # According to Compaq, /usr/sbin/psrinfo has been available on - # OSF/1 and Tru64 systems produced since 1995. I hope that - # covers most systems running today. This code pipes the CPU - # types through head -n 1, so we only detect the type of CPU 0. - ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` - case "$ALPHA_CPU_TYPE" in - "EV4 (21064)") - UNAME_MACHINE="alpha" ;; - "EV4.5 (21064)") - UNAME_MACHINE="alpha" ;; - "LCA4 (21066/21068)") - UNAME_MACHINE="alpha" ;; - "EV5 (21164)") - UNAME_MACHINE="alphaev5" ;; - "EV5.6 (21164A)") - UNAME_MACHINE="alphaev56" ;; - "EV5.6 (21164PC)") - UNAME_MACHINE="alphapca56" ;; - "EV5.7 (21164PC)") - UNAME_MACHINE="alphapca57" ;; - "EV6 (21264)") - UNAME_MACHINE="alphaev6" ;; - "EV6.7 (21264A)") - UNAME_MACHINE="alphaev67" ;; - "EV6.8CB (21264C)") - UNAME_MACHINE="alphaev68" ;; - "EV6.8AL (21264B)") - UNAME_MACHINE="alphaev68" ;; - "EV6.8CX (21264D)") - UNAME_MACHINE="alphaev68" ;; - "EV6.9A (21264/EV69A)") - UNAME_MACHINE="alphaev69" ;; - "EV7 (21364)") - UNAME_MACHINE="alphaev7" ;; - "EV7.9 (21364A)") - UNAME_MACHINE="alphaev79" ;; - esac - # A Pn.n version is a patched version. - # A Vn.n version is a released version. - # A Tn.n version is a released field test version. - # A Xn.n version is an unreleased experimental baselevel. - # 1.2 uses "1.2" for uname -r. - echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` - # Reset EXIT trap before exiting to avoid spurious non-zero exit code. - exitcode=$? - trap '' 0 - exit $exitcode ;; - Alpha\ *:Windows_NT*:*) - # How do we know it's Interix rather than the generic POSIX subsystem? - # Should we change UNAME_MACHINE based on the output of uname instead - # of the specific Alpha model? - echo alpha-pc-interix - exit ;; - 21064:Windows_NT:50:3) - echo alpha-dec-winnt3.5 - exit ;; - Amiga*:UNIX_System_V:4.0:*) - echo m68k-unknown-sysv4 - exit ;; - *:[Aa]miga[Oo][Ss]:*:*) - echo ${UNAME_MACHINE}-unknown-amigaos - exit ;; - *:[Mm]orph[Oo][Ss]:*:*) - echo ${UNAME_MACHINE}-unknown-morphos - exit ;; - *:OS/390:*:*) - echo i370-ibm-openedition - exit ;; - *:z/VM:*:*) - echo s390-ibm-zvmoe - exit ;; - *:OS400:*:*) - echo powerpc-ibm-os400 - exit ;; - arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) - echo arm-acorn-riscix${UNAME_RELEASE} - exit ;; - arm*:riscos:*:*|arm*:RISCOS:*:*) - echo arm-unknown-riscos - exit ;; - SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) - echo hppa1.1-hitachi-hiuxmpp - exit ;; - Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) - # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. - if test "`(/bin/universe) 2>/dev/null`" = att ; then - echo pyramid-pyramid-sysv3 - else - echo pyramid-pyramid-bsd - fi - exit ;; - NILE*:*:*:dcosx) - echo pyramid-pyramid-svr4 - exit ;; - DRS?6000:unix:4.0:6*) - echo sparc-icl-nx6 - exit ;; - DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) - case `/usr/bin/uname -p` in - sparc) echo sparc-icl-nx7; exit ;; - esac ;; - s390x:SunOS:*:*) - echo ${UNAME_MACHINE}-ibm-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` - exit ;; - sun4H:SunOS:5.*:*) - echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` - exit ;; - sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) - echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` - exit ;; - i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) - echo i386-pc-auroraux${UNAME_RELEASE} - exit ;; - i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) - eval $set_cc_for_build - SUN_ARCH="i386" - # If there is a compiler, see if it is configured for 64-bit objects. - # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. - # This test works for both compilers. - if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then - if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ - (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ - grep IS_64BIT_ARCH >/dev/null - then - SUN_ARCH="x86_64" - fi - fi - echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` - exit ;; - sun4*:SunOS:6*:*) - # According to config.sub, this is the proper way to canonicalize - # SunOS6. Hard to guess exactly what SunOS6 will be like, but - # it's likely to be more like Solaris than SunOS4. - echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` - exit ;; - sun4*:SunOS:*:*) - case "`/usr/bin/arch -k`" in - Series*|S4*) - UNAME_RELEASE=`uname -v` - ;; - esac - # Japanese Language versions have a version number like `4.1.3-JL'. - echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` - exit ;; - sun3*:SunOS:*:*) - echo m68k-sun-sunos${UNAME_RELEASE} - exit ;; - sun*:*:4.2BSD:*) - UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` - test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 - case "`/bin/arch`" in - sun3) - echo m68k-sun-sunos${UNAME_RELEASE} - ;; - sun4) - echo sparc-sun-sunos${UNAME_RELEASE} - ;; - esac - exit ;; - aushp:SunOS:*:*) - echo sparc-auspex-sunos${UNAME_RELEASE} - exit ;; - # The situation for MiNT is a little confusing. The machine name - # can be virtually everything (everything which is not - # "atarist" or "atariste" at least should have a processor - # > m68000). The system name ranges from "MiNT" over "FreeMiNT" - # to the lowercase version "mint" (or "freemint"). Finally - # the system name "TOS" denotes a system which is actually not - # MiNT. But MiNT is downward compatible to TOS, so this should - # be no problem. - atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) - echo m68k-atari-mint${UNAME_RELEASE} - exit ;; - atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) - echo m68k-atari-mint${UNAME_RELEASE} - exit ;; - *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) - echo m68k-atari-mint${UNAME_RELEASE} - exit ;; - milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) - echo m68k-milan-mint${UNAME_RELEASE} - exit ;; - hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) - echo m68k-hades-mint${UNAME_RELEASE} - exit ;; - *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) - echo m68k-unknown-mint${UNAME_RELEASE} - exit ;; - m68k:machten:*:*) - echo m68k-apple-machten${UNAME_RELEASE} - exit ;; - powerpc:machten:*:*) - echo powerpc-apple-machten${UNAME_RELEASE} - exit ;; - RISC*:Mach:*:*) - echo mips-dec-mach_bsd4.3 - exit ;; - RISC*:ULTRIX:*:*) - echo mips-dec-ultrix${UNAME_RELEASE} - exit ;; - VAX*:ULTRIX*:*:*) - echo vax-dec-ultrix${UNAME_RELEASE} - exit ;; - 2020:CLIX:*:* | 2430:CLIX:*:*) - echo clipper-intergraph-clix${UNAME_RELEASE} - exit ;; - mips:*:*:UMIPS | mips:*:*:RISCos) - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c -#ifdef __cplusplus -#include /* for printf() prototype */ - int main (int argc, char *argv[]) { -#else - int main (argc, argv) int argc; char *argv[]; { -#endif - #if defined (host_mips) && defined (MIPSEB) - #if defined (SYSTYPE_SYSV) - printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); - #endif - #if defined (SYSTYPE_SVR4) - printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); - #endif - #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) - printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); - #endif - #endif - exit (-1); - } -EOF - $CC_FOR_BUILD -o $dummy $dummy.c && - dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && - SYSTEM_NAME=`$dummy $dummyarg` && - { echo "$SYSTEM_NAME"; exit; } - echo mips-mips-riscos${UNAME_RELEASE} - exit ;; - Motorola:PowerMAX_OS:*:*) - echo powerpc-motorola-powermax - exit ;; - Motorola:*:4.3:PL8-*) - echo powerpc-harris-powermax - exit ;; - Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) - echo powerpc-harris-powermax - exit ;; - Night_Hawk:Power_UNIX:*:*) - echo powerpc-harris-powerunix - exit ;; - m88k:CX/UX:7*:*) - echo m88k-harris-cxux7 - exit ;; - m88k:*:4*:R4*) - echo m88k-motorola-sysv4 - exit ;; - m88k:*:3*:R3*) - echo m88k-motorola-sysv3 - exit ;; - AViiON:dgux:*:*) - # DG/UX returns AViiON for all architectures - UNAME_PROCESSOR=`/usr/bin/uname -p` - if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] - then - if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ - [ ${TARGET_BINARY_INTERFACE}x = x ] - then - echo m88k-dg-dgux${UNAME_RELEASE} - else - echo m88k-dg-dguxbcs${UNAME_RELEASE} - fi - else - echo i586-dg-dgux${UNAME_RELEASE} - fi - exit ;; - M88*:DolphinOS:*:*) # DolphinOS (SVR3) - echo m88k-dolphin-sysv3 - exit ;; - M88*:*:R3*:*) - # Delta 88k system running SVR3 - echo m88k-motorola-sysv3 - exit ;; - XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) - echo m88k-tektronix-sysv3 - exit ;; - Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) - echo m68k-tektronix-bsd - exit ;; - *:IRIX*:*:*) - echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` - exit ;; - ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. - echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id - exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' - i*86:AIX:*:*) - echo i386-ibm-aix - exit ;; - ia64:AIX:*:*) - if [ -x /usr/bin/oslevel ] ; then - IBM_REV=`/usr/bin/oslevel` - else - IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} - fi - echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} - exit ;; - *:AIX:2:3) - if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - #include - - main() - { - if (!__power_pc()) - exit(1); - puts("powerpc-ibm-aix3.2.5"); - exit(0); - } -EOF - if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` - then - echo "$SYSTEM_NAME" - else - echo rs6000-ibm-aix3.2.5 - fi - elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then - echo rs6000-ibm-aix3.2.4 - else - echo rs6000-ibm-aix3.2 - fi - exit ;; - *:AIX:*:[4567]) - IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` - if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then - IBM_ARCH=rs6000 - else - IBM_ARCH=powerpc - fi - if [ -x /usr/bin/oslevel ] ; then - IBM_REV=`/usr/bin/oslevel` - else - IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} - fi - echo ${IBM_ARCH}-ibm-aix${IBM_REV} - exit ;; - *:AIX:*:*) - echo rs6000-ibm-aix - exit ;; - ibmrt:4.4BSD:*|romp-ibm:BSD:*) - echo romp-ibm-bsd4.4 - exit ;; - ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and - echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to - exit ;; # report: romp-ibm BSD 4.3 - *:BOSX:*:*) - echo rs6000-bull-bosx - exit ;; - DPX/2?00:B.O.S.:*:*) - echo m68k-bull-sysv3 - exit ;; - 9000/[34]??:4.3bsd:1.*:*) - echo m68k-hp-bsd - exit ;; - hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) - echo m68k-hp-bsd4.4 - exit ;; - 9000/[34678]??:HP-UX:*:*) - HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` - case "${UNAME_MACHINE}" in - 9000/31? ) HP_ARCH=m68000 ;; - 9000/[34]?? ) HP_ARCH=m68k ;; - 9000/[678][0-9][0-9]) - if [ -x /usr/bin/getconf ]; then - sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` - sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` - case "${sc_cpu_version}" in - 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 - 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 - 532) # CPU_PA_RISC2_0 - case "${sc_kernel_bits}" in - 32) HP_ARCH="hppa2.0n" ;; - 64) HP_ARCH="hppa2.0w" ;; - '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 - esac ;; - esac - fi - if [ "${HP_ARCH}" = "" ]; then - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - - #define _HPUX_SOURCE - #include - #include - - int main () - { - #if defined(_SC_KERNEL_BITS) - long bits = sysconf(_SC_KERNEL_BITS); - #endif - long cpu = sysconf (_SC_CPU_VERSION); - - switch (cpu) - { - case CPU_PA_RISC1_0: puts ("hppa1.0"); break; - case CPU_PA_RISC1_1: puts ("hppa1.1"); break; - case CPU_PA_RISC2_0: - #if defined(_SC_KERNEL_BITS) - switch (bits) - { - case 64: puts ("hppa2.0w"); break; - case 32: puts ("hppa2.0n"); break; - default: puts ("hppa2.0"); break; - } break; - #else /* !defined(_SC_KERNEL_BITS) */ - puts ("hppa2.0"); break; - #endif - default: puts ("hppa1.0"); break; - } - exit (0); - } -EOF - (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` - test -z "$HP_ARCH" && HP_ARCH=hppa - fi ;; - esac - if [ ${HP_ARCH} = "hppa2.0w" ] - then - eval $set_cc_for_build - - # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating - # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler - # generating 64-bit code. GNU and HP use different nomenclature: - # - # $ CC_FOR_BUILD=cc ./config.guess - # => hppa2.0w-hp-hpux11.23 - # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess - # => hppa64-hp-hpux11.23 - - if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | - grep -q __LP64__ - then - HP_ARCH="hppa2.0w" - else - HP_ARCH="hppa64" - fi - fi - echo ${HP_ARCH}-hp-hpux${HPUX_REV} - exit ;; - ia64:HP-UX:*:*) - HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` - echo ia64-hp-hpux${HPUX_REV} - exit ;; - 3050*:HI-UX:*:*) - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - #include - int - main () - { - long cpu = sysconf (_SC_CPU_VERSION); - /* The order matters, because CPU_IS_HP_MC68K erroneously returns - true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct - results, however. */ - if (CPU_IS_PA_RISC (cpu)) - { - switch (cpu) - { - case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; - case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; - case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; - default: puts ("hppa-hitachi-hiuxwe2"); break; - } - } - else if (CPU_IS_HP_MC68K (cpu)) - puts ("m68k-hitachi-hiuxwe2"); - else puts ("unknown-hitachi-hiuxwe2"); - exit (0); - } -EOF - $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && - { echo "$SYSTEM_NAME"; exit; } - echo unknown-hitachi-hiuxwe2 - exit ;; - 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) - echo hppa1.1-hp-bsd - exit ;; - 9000/8??:4.3bsd:*:*) - echo hppa1.0-hp-bsd - exit ;; - *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) - echo hppa1.0-hp-mpeix - exit ;; - hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) - echo hppa1.1-hp-osf - exit ;; - hp8??:OSF1:*:*) - echo hppa1.0-hp-osf - exit ;; - i*86:OSF1:*:*) - if [ -x /usr/sbin/sysversion ] ; then - echo ${UNAME_MACHINE}-unknown-osf1mk - else - echo ${UNAME_MACHINE}-unknown-osf1 - fi - exit ;; - parisc*:Lites*:*:*) - echo hppa1.1-hp-lites - exit ;; - C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) - echo c1-convex-bsd - exit ;; - C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) - if getsysinfo -f scalar_acc - then echo c32-convex-bsd - else echo c2-convex-bsd - fi - exit ;; - C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) - echo c34-convex-bsd - exit ;; - C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) - echo c38-convex-bsd - exit ;; - C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) - echo c4-convex-bsd - exit ;; - CRAY*Y-MP:*:*:*) - echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' - exit ;; - CRAY*[A-Z]90:*:*:*) - echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ - | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ - -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ - -e 's/\.[^.]*$/.X/' - exit ;; - CRAY*TS:*:*:*) - echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' - exit ;; - CRAY*T3E:*:*:*) - echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' - exit ;; - CRAY*SV1:*:*:*) - echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' - exit ;; - *:UNICOS/mp:*:*) - echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' - exit ;; - F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) - FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` - FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` - FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` - echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" - exit ;; - 5000:UNIX_System_V:4.*:*) - FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` - FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` - echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" - exit ;; - i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) - echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} - exit ;; - sparc*:BSD/OS:*:*) - echo sparc-unknown-bsdi${UNAME_RELEASE} - exit ;; - *:BSD/OS:*:*) - echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} - exit ;; - *:FreeBSD:*:*) - UNAME_PROCESSOR=`/usr/bin/uname -p` - case ${UNAME_PROCESSOR} in - amd64) - echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; - *) - echo ${UNAME_PROCESSOR}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; - esac - exit ;; - i*:CYGWIN*:*) - echo ${UNAME_MACHINE}-pc-cygwin - exit ;; - *:MINGW64*:*) - echo ${UNAME_MACHINE}-pc-mingw64 - exit ;; - *:MINGW*:*) - echo ${UNAME_MACHINE}-pc-mingw32 - exit ;; - i*:MSYS*:*) - echo ${UNAME_MACHINE}-pc-msys - exit ;; - i*:windows32*:*) - # uname -m includes "-pc" on this system. - echo ${UNAME_MACHINE}-mingw32 - exit ;; - i*:PW*:*) - echo ${UNAME_MACHINE}-pc-pw32 - exit ;; - *:Interix*:*) - case ${UNAME_MACHINE} in - x86) - echo i586-pc-interix${UNAME_RELEASE} - exit ;; - authenticamd | genuineintel | EM64T) - echo x86_64-unknown-interix${UNAME_RELEASE} - exit ;; - IA64) - echo ia64-unknown-interix${UNAME_RELEASE} - exit ;; - esac ;; - [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) - echo i${UNAME_MACHINE}-pc-mks - exit ;; - 8664:Windows_NT:*) - echo x86_64-pc-mks - exit ;; - i*:Windows_NT*:* | Pentium*:Windows_NT*:*) - # How do we know it's Interix rather than the generic POSIX subsystem? - # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we - # UNAME_MACHINE based on the output of uname instead of i386? - echo i586-pc-interix - exit ;; - i*:UWIN*:*) - echo ${UNAME_MACHINE}-pc-uwin - exit ;; - amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) - echo x86_64-unknown-cygwin - exit ;; - p*:CYGWIN*:*) - echo powerpcle-unknown-cygwin - exit ;; - prep*:SunOS:5.*:*) - echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` - exit ;; - *:GNU:*:*) - # the GNU system - echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-${LIBC}`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` - exit ;; - *:GNU/*:*:*) - # other systems with GNU libc and userland - echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-${LIBC} - exit ;; - i*86:Minix:*:*) - echo ${UNAME_MACHINE}-pc-minix - exit ;; - aarch64:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - aarch64_be:Linux:*:*) - UNAME_MACHINE=aarch64_be - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - alpha:Linux:*:*) - case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in - EV5) UNAME_MACHINE=alphaev5 ;; - EV56) UNAME_MACHINE=alphaev56 ;; - PCA56) UNAME_MACHINE=alphapca56 ;; - PCA57) UNAME_MACHINE=alphapca56 ;; - EV6) UNAME_MACHINE=alphaev6 ;; - EV67) UNAME_MACHINE=alphaev67 ;; - EV68*) UNAME_MACHINE=alphaev68 ;; - esac - objdump --private-headers /bin/sh | grep -q ld.so.1 - if test "$?" = 0 ; then LIBC="gnulibc1" ; fi - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - arc:Linux:*:* | arceb:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - arm*:Linux:*:*) - eval $set_cc_for_build - if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ - | grep -q __ARM_EABI__ - then - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - else - if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ - | grep -q __ARM_PCS_VFP - then - echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabi - else - echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabihf - fi - fi - exit ;; - avr32*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - cris:Linux:*:*) - echo ${UNAME_MACHINE}-axis-linux-${LIBC} - exit ;; - crisv32:Linux:*:*) - echo ${UNAME_MACHINE}-axis-linux-${LIBC} - exit ;; - frv:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - hexagon:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - i*86:Linux:*:*) - echo ${UNAME_MACHINE}-pc-linux-${LIBC} - exit ;; - ia64:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - m32r*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - m68*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - mips:Linux:*:* | mips64:Linux:*:*) - eval $set_cc_for_build - sed 's/^ //' << EOF >$dummy.c - #undef CPU - #undef ${UNAME_MACHINE} - #undef ${UNAME_MACHINE}el - #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) - CPU=${UNAME_MACHINE}el - #else - #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) - CPU=${UNAME_MACHINE} - #else - CPU= - #endif - #endif -EOF - eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'` - test x"${CPU}" != x && { echo "${CPU}-unknown-linux-${LIBC}"; exit; } - ;; - or1k:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - or32:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - padre:Linux:*:*) - echo sparc-unknown-linux-${LIBC} - exit ;; - parisc64:Linux:*:* | hppa64:Linux:*:*) - echo hppa64-unknown-linux-${LIBC} - exit ;; - parisc:Linux:*:* | hppa:Linux:*:*) - # Look for CPU level - case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in - PA7*) echo hppa1.1-unknown-linux-${LIBC} ;; - PA8*) echo hppa2.0-unknown-linux-${LIBC} ;; - *) echo hppa-unknown-linux-${LIBC} ;; - esac - exit ;; - ppc64:Linux:*:*) - echo powerpc64-unknown-linux-${LIBC} - exit ;; - ppc:Linux:*:*) - echo powerpc-unknown-linux-${LIBC} - exit ;; - ppc64le:Linux:*:*) - echo powerpc64le-unknown-linux-${LIBC} - exit ;; - ppcle:Linux:*:*) - echo powerpcle-unknown-linux-${LIBC} - exit ;; - s390:Linux:*:* | s390x:Linux:*:*) - echo ${UNAME_MACHINE}-ibm-linux-${LIBC} - exit ;; - sh64*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - sh*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - sparc:Linux:*:* | sparc64:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - tile*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - vax:Linux:*:*) - echo ${UNAME_MACHINE}-dec-linux-${LIBC} - exit ;; - x86_64:Linux:*:*) - eval $set_cc_for_build - X86_64_ABI= - # If there is a compiler, see if it is configured for 32-bit objects. - if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then - if (echo '#ifdef __ILP32__'; echo IS_X32; echo '#endif') | \ - (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ - grep IS_X32 >/dev/null - then - X86_64_ABI=x32 - fi - fi - echo x86_64-unknown-linux-gnu${X86_64_ABI} - exit ;; - xtensa*:Linux:*:*) - echo ${UNAME_MACHINE}-unknown-linux-${LIBC} - exit ;; - i*86:DYNIX/ptx:4*:*) - # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. - # earlier versions are messed up and put the nodename in both - # sysname and nodename. - echo i386-sequent-sysv4 - exit ;; - i*86:UNIX_SV:4.2MP:2.*) - # Unixware is an offshoot of SVR4, but it has its own version - # number series starting with 2... - # I am not positive that other SVR4 systems won't match this, - # I just have to hope. -- rms. - # Use sysv4.2uw... so that sysv4* matches it. - echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} - exit ;; - i*86:OS/2:*:*) - # If we were able to find `uname', then EMX Unix compatibility - # is probably installed. - echo ${UNAME_MACHINE}-pc-os2-emx - exit ;; - i*86:XTS-300:*:STOP) - echo ${UNAME_MACHINE}-unknown-stop - exit ;; - i*86:atheos:*:*) - echo ${UNAME_MACHINE}-unknown-atheos - exit ;; - i*86:syllable:*:*) - echo ${UNAME_MACHINE}-pc-syllable - exit ;; - i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) - echo i386-unknown-lynxos${UNAME_RELEASE} - exit ;; - i*86:*DOS:*:*) - echo ${UNAME_MACHINE}-pc-msdosdjgpp - exit ;; - i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) - UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` - if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then - echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} - else - echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} - fi - exit ;; - i*86:*:5:[678]*) - # UnixWare 7.x, OpenUNIX and OpenServer 6. - case `/bin/uname -X | grep "^Machine"` in - *486*) UNAME_MACHINE=i486 ;; - *Pentium) UNAME_MACHINE=i586 ;; - *Pent*|*Celeron) UNAME_MACHINE=i686 ;; - esac - echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} - exit ;; - i*86:*:3.2:*) - if test -f /usr/options/cb.name; then - UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then - UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` - (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 - (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ - && UNAME_MACHINE=i586 - (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ - && UNAME_MACHINE=i686 - (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ - && UNAME_MACHINE=i686 - echo ${UNAME_MACHINE}-pc-sco$UNAME_REL - else - echo ${UNAME_MACHINE}-pc-sysv32 - fi - exit ;; - pc:*:*:*) - # Left here for compatibility: - # uname -m prints for DJGPP always 'pc', but it prints nothing about - # the processor, so we play safe by assuming i586. - # Note: whatever this is, it MUST be the same as what config.sub - # prints for the "djgpp" host, or else GDB configury will decide that - # this is a cross-build. - echo i586-pc-msdosdjgpp - exit ;; - Intel:Mach:3*:*) - echo i386-pc-mach3 - exit ;; - paragon:*:*:*) - echo i860-intel-osf1 - exit ;; - i860:*:4.*:*) # i860-SVR4 - if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then - echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 - else # Add other i860-SVR4 vendors below as they are discovered. - echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 - fi - exit ;; - mini*:CTIX:SYS*5:*) - # "miniframe" - echo m68010-convergent-sysv - exit ;; - mc68k:UNIX:SYSTEM5:3.51m) - echo m68k-convergent-sysv - exit ;; - M680?0:D-NIX:5.3:*) - echo m68k-diab-dnix - exit ;; - M68*:*:R3V[5678]*:*) - test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; - 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) - OS_REL='' - test -r /etc/.relid \ - && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` - /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ - && { echo i486-ncr-sysv4.3${OS_REL}; exit; } - /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ - && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; - 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) - /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ - && { echo i486-ncr-sysv4; exit; } ;; - NCR*:*:4.2:* | MPRAS*:*:4.2:*) - OS_REL='.3' - test -r /etc/.relid \ - && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` - /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ - && { echo i486-ncr-sysv4.3${OS_REL}; exit; } - /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ - && { echo i586-ncr-sysv4.3${OS_REL}; exit; } - /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ - && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; - m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) - echo m68k-unknown-lynxos${UNAME_RELEASE} - exit ;; - mc68030:UNIX_System_V:4.*:*) - echo m68k-atari-sysv4 - exit ;; - TSUNAMI:LynxOS:2.*:*) - echo sparc-unknown-lynxos${UNAME_RELEASE} - exit ;; - rs6000:LynxOS:2.*:*) - echo rs6000-unknown-lynxos${UNAME_RELEASE} - exit ;; - PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) - echo powerpc-unknown-lynxos${UNAME_RELEASE} - exit ;; - SM[BE]S:UNIX_SV:*:*) - echo mips-dde-sysv${UNAME_RELEASE} - exit ;; - RM*:ReliantUNIX-*:*:*) - echo mips-sni-sysv4 - exit ;; - RM*:SINIX-*:*:*) - echo mips-sni-sysv4 - exit ;; - *:SINIX-*:*:*) - if uname -p 2>/dev/null >/dev/null ; then - UNAME_MACHINE=`(uname -p) 2>/dev/null` - echo ${UNAME_MACHINE}-sni-sysv4 - else - echo ns32k-sni-sysv - fi - exit ;; - PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort - # says - echo i586-unisys-sysv4 - exit ;; - *:UNIX_System_V:4*:FTX*) - # From Gerald Hewes . - # How about differentiating between stratus architectures? -djm - echo hppa1.1-stratus-sysv4 - exit ;; - *:*:*:FTX*) - # From seanf@swdc.stratus.com. - echo i860-stratus-sysv4 - exit ;; - i*86:VOS:*:*) - # From Paul.Green@stratus.com. - echo ${UNAME_MACHINE}-stratus-vos - exit ;; - *:VOS:*:*) - # From Paul.Green@stratus.com. - echo hppa1.1-stratus-vos - exit ;; - mc68*:A/UX:*:*) - echo m68k-apple-aux${UNAME_RELEASE} - exit ;; - news*:NEWS-OS:6*:*) - echo mips-sony-newsos6 - exit ;; - R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) - if [ -d /usr/nec ]; then - echo mips-nec-sysv${UNAME_RELEASE} - else - echo mips-unknown-sysv${UNAME_RELEASE} - fi - exit ;; - BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. - echo powerpc-be-beos - exit ;; - BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. - echo powerpc-apple-beos - exit ;; - BePC:BeOS:*:*) # BeOS running on Intel PC compatible. - echo i586-pc-beos - exit ;; - BePC:Haiku:*:*) # Haiku running on Intel PC compatible. - echo i586-pc-haiku - exit ;; - x86_64:Haiku:*:*) - echo x86_64-unknown-haiku - exit ;; - SX-4:SUPER-UX:*:*) - echo sx4-nec-superux${UNAME_RELEASE} - exit ;; - SX-5:SUPER-UX:*:*) - echo sx5-nec-superux${UNAME_RELEASE} - exit ;; - SX-6:SUPER-UX:*:*) - echo sx6-nec-superux${UNAME_RELEASE} - exit ;; - SX-7:SUPER-UX:*:*) - echo sx7-nec-superux${UNAME_RELEASE} - exit ;; - SX-8:SUPER-UX:*:*) - echo sx8-nec-superux${UNAME_RELEASE} - exit ;; - SX-8R:SUPER-UX:*:*) - echo sx8r-nec-superux${UNAME_RELEASE} - exit ;; - Power*:Rhapsody:*:*) - echo powerpc-apple-rhapsody${UNAME_RELEASE} - exit ;; - *:Rhapsody:*:*) - echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} - exit ;; - *:Darwin:*:*) - UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown - eval $set_cc_for_build - if test "$UNAME_PROCESSOR" = unknown ; then - UNAME_PROCESSOR=powerpc - fi - if test `echo "$UNAME_RELEASE" | sed -e 's/\..*//'` -le 10 ; then - if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then - if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ - (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ - grep IS_64BIT_ARCH >/dev/null - then - case $UNAME_PROCESSOR in - i386) UNAME_PROCESSOR=x86_64 ;; - powerpc) UNAME_PROCESSOR=powerpc64 ;; - esac - fi - fi - elif test "$UNAME_PROCESSOR" = i386 ; then - # Avoid executing cc on OS X 10.9, as it ships with a stub - # that puts up a graphical alert prompting to install - # developer tools. Any system running Mac OS X 10.7 or - # later (Darwin 11 and later) is required to have a 64-bit - # processor. This is not true of the ARM version of Darwin - # that Apple uses in portable devices. - UNAME_PROCESSOR=x86_64 - fi - echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} - exit ;; - *:procnto*:*:* | *:QNX:[0123456789]*:*) - UNAME_PROCESSOR=`uname -p` - if test "$UNAME_PROCESSOR" = "x86"; then - UNAME_PROCESSOR=i386 - UNAME_MACHINE=pc - fi - echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} - exit ;; - *:QNX:*:4*) - echo i386-pc-qnx - exit ;; - NEO-?:NONSTOP_KERNEL:*:*) - echo neo-tandem-nsk${UNAME_RELEASE} - exit ;; - NSE-*:NONSTOP_KERNEL:*:*) - echo nse-tandem-nsk${UNAME_RELEASE} - exit ;; - NSR-?:NONSTOP_KERNEL:*:*) - echo nsr-tandem-nsk${UNAME_RELEASE} - exit ;; - *:NonStop-UX:*:*) - echo mips-compaq-nonstopux - exit ;; - BS2000:POSIX*:*:*) - echo bs2000-siemens-sysv - exit ;; - DS/*:UNIX_System_V:*:*) - echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} - exit ;; - *:Plan9:*:*) - # "uname -m" is not consistent, so use $cputype instead. 386 - # is converted to i386 for consistency with other x86 - # operating systems. - if test "$cputype" = "386"; then - UNAME_MACHINE=i386 - else - UNAME_MACHINE="$cputype" - fi - echo ${UNAME_MACHINE}-unknown-plan9 - exit ;; - *:TOPS-10:*:*) - echo pdp10-unknown-tops10 - exit ;; - *:TENEX:*:*) - echo pdp10-unknown-tenex - exit ;; - KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) - echo pdp10-dec-tops20 - exit ;; - XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) - echo pdp10-xkl-tops20 - exit ;; - *:TOPS-20:*:*) - echo pdp10-unknown-tops20 - exit ;; - *:ITS:*:*) - echo pdp10-unknown-its - exit ;; - SEI:*:*:SEIUX) - echo mips-sei-seiux${UNAME_RELEASE} - exit ;; - *:DragonFly:*:*) - echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` - exit ;; - *:*VMS:*:*) - UNAME_MACHINE=`(uname -p) 2>/dev/null` - case "${UNAME_MACHINE}" in - A*) echo alpha-dec-vms ; exit ;; - I*) echo ia64-dec-vms ; exit ;; - V*) echo vax-dec-vms ; exit ;; - esac ;; - *:XENIX:*:SysV) - echo i386-pc-xenix - exit ;; - i*86:skyos:*:*) - echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' - exit ;; - i*86:rdos:*:*) - echo ${UNAME_MACHINE}-pc-rdos - exit ;; - i*86:AROS:*:*) - echo ${UNAME_MACHINE}-pc-aros - exit ;; - x86_64:VMkernel:*:*) - echo ${UNAME_MACHINE}-unknown-esx - exit ;; -esac - -cat >&2 < in order to provide the needed -information to handle your system. - -config.guess timestamp = $timestamp - -uname -m = `(uname -m) 2>/dev/null || echo unknown` -uname -r = `(uname -r) 2>/dev/null || echo unknown` -uname -s = `(uname -s) 2>/dev/null || echo unknown` -uname -v = `(uname -v) 2>/dev/null || echo unknown` - -/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` -/bin/uname -X = `(/bin/uname -X) 2>/dev/null` - -hostinfo = `(hostinfo) 2>/dev/null` -/bin/universe = `(/bin/universe) 2>/dev/null` -/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` -/bin/arch = `(/bin/arch) 2>/dev/null` -/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` -/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` - -UNAME_MACHINE = ${UNAME_MACHINE} -UNAME_RELEASE = ${UNAME_RELEASE} -UNAME_SYSTEM = ${UNAME_SYSTEM} -UNAME_VERSION = ${UNAME_VERSION} -EOF - -exit 1 - -# Local variables: -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "timestamp='" -# time-stamp-format: "%:y-%02m-%02d" -# time-stamp-end: "'" -# End: diff --git a/build/autotools/config.h.in b/build/autotools/config.h.in deleted file mode 100644 index bb48fdc2..00000000 --- a/build/autotools/config.h.in +++ /dev/null @@ -1,175 +0,0 @@ -/* config.h.in. Generated from configure.ac by autoheader. */ - -/* define if the Boost library is available */ -#undef HAVE_BOOST - -/* define if the Boost::Date_Time library is available */ -#undef HAVE_BOOST_DATE_TIME - -/* define if the Boost::Filesystem library is available */ -#undef HAVE_BOOST_FILESYSTEM - -/* define if the Boost::PROGRAM_OPTIONS library is available */ -#undef HAVE_BOOST_PROGRAM_OPTIONS - -/* define if the Boost::Regex library is available */ -#undef HAVE_BOOST_REGEX - -/* define if the Boost::System library is available */ -#undef HAVE_BOOST_SYSTEM - -/* define if the compiler supports basic C++11 syntax */ -#undef HAVE_CXX11 - -/* Define to 1 if you have the header file. */ -#undef HAVE_FCNTL_H - -/* Define to 1 if you have the `fork' function. */ -#undef HAVE_FORK - -/* Define to 1 if you have the header file. */ -#undef HAVE_INTTYPES_H - -/* Define to 1 if you have the `memchr' function. */ -#undef HAVE_MEMCHR - -/* Define to 1 if you have the header file. */ -#undef HAVE_MEMORY_H - -/* Define to 1 if you have the `memset' function. */ -#undef HAVE_MEMSET - -/* Define if you have POSIX threads libraries and header files. */ -#undef HAVE_PTHREAD - -/* Have PTHREAD_PRIO_INHERIT. */ -#undef HAVE_PTHREAD_PRIO_INHERIT - -/* Define to 1 if the system has the type `ptrdiff_t'. */ -#undef HAVE_PTRDIFF_T - -/* Define to 1 if you have the `setlocale' function. */ -#undef HAVE_SETLOCALE - -/* Define to 1 if you have the `socket' function. */ -#undef HAVE_SOCKET - -/* Define to 1 if you have the header file. */ -#undef HAVE_STDINT_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_STDLIB_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_STRINGS_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_STRING_H - -/* Define to 1 if you have the `strstr' function. */ -#undef HAVE_STRSTR - -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_STAT_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_TYPES_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_UNISTD_H - -/* Define to 1 if you have the `vfork' function. */ -#undef HAVE_VFORK - -/* Define to 1 if you have the header file. */ -#undef HAVE_VFORK_H - -/* Define to 1 if `fork' works. */ -#undef HAVE_WORKING_FORK - -/* Define to 1 if `vfork' works. */ -#undef HAVE_WORKING_VFORK - -/* Define to 1 if the system has the type `_Bool'. */ -#undef HAVE__BOOL - -/* Name of package */ -#undef PACKAGE - -/* Define to the address where bug reports for this package should be sent. */ -#undef PACKAGE_BUGREPORT - -/* Define to the full name of this package. */ -#undef PACKAGE_NAME - -/* Define to the full name and version of this package. */ -#undef PACKAGE_STRING - -/* Define to the one symbol short name of this package. */ -#undef PACKAGE_TARNAME - -/* Define to the home page for this package. */ -#undef PACKAGE_URL - -/* Define to the version of this package. */ -#undef PACKAGE_VERSION - -/* Define to necessary symbol if this constant uses a non-standard name on - your system. */ -#undef PTHREAD_CREATE_JOINABLE - -/* Define to 1 if you have the ANSI C header files. */ -#undef STDC_HEADERS - -/* Version number of package */ -#undef VERSION - -/* Define for Solaris 2.5.1 so the uint32_t typedef from , - , or is not used. If the typedef were allowed, the - #define below would cause a syntax error. */ -#undef _UINT32_T - -/* Define for Solaris 2.5.1 so the uint64_t typedef from , - , or is not used. If the typedef were allowed, the - #define below would cause a syntax error. */ -#undef _UINT64_T - -/* Define for Solaris 2.5.1 so the uint8_t typedef from , - , or is not used. If the typedef were allowed, the - #define below would cause a syntax error. */ -#undef _UINT8_T - -/* Define to `__inline__' or `__inline' if that's what the C compiler - calls it, or to nothing if 'inline' is not supported under any name. */ -#ifndef __cplusplus -#undef inline -#endif - -/* Define to the type of a signed integer type of width exactly 32 bits if - such a type exists and the standard includes do not define it. */ -#undef int32_t - -/* Define to `int' if does not define. */ -#undef pid_t - -/* Define to `unsigned int' if does not define. */ -#undef size_t - -/* Define to the type of an unsigned integer type of width exactly 16 bits if - such a type exists and the standard includes do not define it. */ -#undef uint16_t - -/* Define to the type of an unsigned integer type of width exactly 32 bits if - such a type exists and the standard includes do not define it. */ -#undef uint32_t - -/* Define to the type of an unsigned integer type of width exactly 64 bits if - such a type exists and the standard includes do not define it. */ -#undef uint64_t - -/* Define to the type of an unsigned integer type of width exactly 8 bits if - such a type exists and the standard includes do not define it. */ -#undef uint8_t - -/* Define as `fork' if `vfork' does not work. */ -#undef vfork diff --git a/build/autotools/config.sub b/build/autotools/config.sub deleted file mode 100755 index 34c510d3..00000000 --- a/build/autotools/config.sub +++ /dev/null @@ -1,1811 +0,0 @@ -#! /bin/sh -# Configuration validation subroutine script. -# Copyright 1992-2014 Free Software Foundation, Inc. - -timestamp='2014-01-01' - -# This file is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, see . -# -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that -# program. This Exception is an additional permission under section 7 -# of the GNU General Public License, version 3 ("GPLv3"). - - -# Please send patches with a ChangeLog entry to config-patches@gnu.org. -# -# Configuration subroutine to validate and canonicalize a configuration type. -# Supply the specified configuration type as an argument. -# If it is invalid, we print an error message on stderr and exit with code 1. -# Otherwise, we print the canonical config type on stdout and succeed. - -# You can get the latest version of this script from: -# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD - -# This file is supposed to be the same for all GNU packages -# and recognize all the CPU types, system types and aliases -# that are meaningful with *any* GNU software. -# Each package is responsible for reporting which valid configurations -# it does not support. The user should be able to distinguish -# a failure to support a valid configuration from a meaningless -# configuration. - -# The goal of this file is to map all the various variations of a given -# machine specification into a single specification in the form: -# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM -# or in some cases, the newer four-part form: -# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM -# It is wrong to echo any other type of specification. - -me=`echo "$0" | sed -e 's,.*/,,'` - -usage="\ -Usage: $0 [OPTION] CPU-MFR-OPSYS - $0 [OPTION] ALIAS - -Canonicalize a configuration name. - -Operation modes: - -h, --help print this help, then exit - -t, --time-stamp print date of last modification, then exit - -v, --version print version number, then exit - -Report bugs and patches to ." - -version="\ -GNU config.sub ($timestamp) - -Copyright 1992-2014 Free Software Foundation, Inc. - -This is free software; see the source for copying conditions. There is NO -warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." - -help=" -Try \`$me --help' for more information." - -# Parse command line -while test $# -gt 0 ; do - case $1 in - --time-stamp | --time* | -t ) - echo "$timestamp" ; exit ;; - --version | -v ) - echo "$version" ; exit ;; - --help | --h* | -h ) - echo "$usage"; exit ;; - -- ) # Stop option processing - shift; break ;; - - ) # Use stdin as input. - break ;; - -* ) - echo "$me: invalid option $1$help" - exit 1 ;; - - *local*) - # First pass through any local machine types. - echo $1 - exit ;; - - * ) - break ;; - esac -done - -case $# in - 0) echo "$me: missing argument$help" >&2 - exit 1;; - 1) ;; - *) echo "$me: too many arguments$help" >&2 - exit 1;; -esac - -# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). -# Here we must recognize all the valid KERNEL-OS combinations. -maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` -case $maybe_os in - nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \ - linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ - knetbsd*-gnu* | netbsd*-gnu* | \ - kopensolaris*-gnu* | \ - storm-chaos* | os2-emx* | rtmk-nova*) - os=-$maybe_os - basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` - ;; - android-linux) - os=-linux-android - basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown - ;; - *) - basic_machine=`echo $1 | sed 's/-[^-]*$//'` - if [ $basic_machine != $1 ] - then os=`echo $1 | sed 's/.*-/-/'` - else os=; fi - ;; -esac - -### Let's recognize common machines as not being operating systems so -### that things like config.sub decstation-3100 work. We also -### recognize some manufacturers as not being operating systems, so we -### can provide default operating systems below. -case $os in - -sun*os*) - # Prevent following clause from handling this invalid input. - ;; - -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ - -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ - -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ - -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ - -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ - -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ - -apple | -axis | -knuth | -cray | -microblaze*) - os= - basic_machine=$1 - ;; - -bluegene*) - os=-cnk - ;; - -sim | -cisco | -oki | -wec | -winbond) - os= - basic_machine=$1 - ;; - -scout) - ;; - -wrs) - os=-vxworks - basic_machine=$1 - ;; - -chorusos*) - os=-chorusos - basic_machine=$1 - ;; - -chorusrdb) - os=-chorusrdb - basic_machine=$1 - ;; - -hiux*) - os=-hiuxwe2 - ;; - -sco6) - os=-sco5v6 - basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` - ;; - -sco5) - os=-sco3.2v5 - basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` - ;; - -sco4) - os=-sco3.2v4 - basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` - ;; - -sco3.2.[4-9]*) - os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` - basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` - ;; - -sco3.2v[4-9]*) - # Don't forget version if it is 3.2v4 or newer. - basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` - ;; - -sco5v6*) - # Don't forget version if it is 3.2v4 or newer. - basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` - ;; - -sco*) - os=-sco3.2v2 - basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` - ;; - -udk*) - basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` - ;; - -isc) - os=-isc2.2 - basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` - ;; - -clix*) - basic_machine=clipper-intergraph - ;; - -isc*) - basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` - ;; - -lynx*178) - os=-lynxos178 - ;; - -lynx*5) - os=-lynxos5 - ;; - -lynx*) - os=-lynxos - ;; - -ptx*) - basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` - ;; - -windowsnt*) - os=`echo $os | sed -e 's/windowsnt/winnt/'` - ;; - -psos*) - os=-psos - ;; - -mint | -mint[0-9]*) - basic_machine=m68k-atari - os=-mint - ;; -esac - -# Decode aliases for certain CPU-COMPANY combinations. -case $basic_machine in - # Recognize the basic CPU types without company name. - # Some are omitted here because they have special meanings below. - 1750a | 580 \ - | a29k \ - | aarch64 | aarch64_be \ - | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ - | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ - | am33_2.0 \ - | arc | arceb \ - | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \ - | avr | avr32 \ - | be32 | be64 \ - | bfin \ - | c4x | c8051 | clipper \ - | d10v | d30v | dlx | dsp16xx | dvp \ - | epiphany \ - | fido | fr30 | frv \ - | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ - | hexagon \ - | i370 | i860 | i960 | ia64 \ - | ip2k | iq2000 \ - | k1om \ - | le32 | le64 \ - | lm32 \ - | m32c | m32r | m32rle | m68000 | m68k | m88k \ - | maxq | mb | microblaze | microblazeel | mcore | mep | metag \ - | mips | mipsbe | mipseb | mipsel | mipsle \ - | mips16 \ - | mips64 | mips64el \ - | mips64octeon | mips64octeonel \ - | mips64orion | mips64orionel \ - | mips64r5900 | mips64r5900el \ - | mips64vr | mips64vrel \ - | mips64vr4100 | mips64vr4100el \ - | mips64vr4300 | mips64vr4300el \ - | mips64vr5000 | mips64vr5000el \ - | mips64vr5900 | mips64vr5900el \ - | mipsisa32 | mipsisa32el \ - | mipsisa32r2 | mipsisa32r2el \ - | mipsisa64 | mipsisa64el \ - | mipsisa64r2 | mipsisa64r2el \ - | mipsisa64sb1 | mipsisa64sb1el \ - | mipsisa64sr71k | mipsisa64sr71kel \ - | mipsr5900 | mipsr5900el \ - | mipstx39 | mipstx39el \ - | mn10200 | mn10300 \ - | moxie \ - | mt \ - | msp430 \ - | nds32 | nds32le | nds32be \ - | nios | nios2 | nios2eb | nios2el \ - | ns16k | ns32k \ - | open8 \ - | or1k | or32 \ - | pdp10 | pdp11 | pj | pjl \ - | powerpc | powerpc64 | powerpc64le | powerpcle \ - | pyramid \ - | rl78 | rx \ - | score \ - | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ - | sh64 | sh64le \ - | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \ - | sparcv8 | sparcv9 | sparcv9b | sparcv9v \ - | spu \ - | tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \ - | ubicom32 \ - | v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \ - | we32k \ - | x86 | xc16x | xstormy16 | xtensa \ - | z8k | z80) - basic_machine=$basic_machine-unknown - ;; - c54x) - basic_machine=tic54x-unknown - ;; - c55x) - basic_machine=tic55x-unknown - ;; - c6x) - basic_machine=tic6x-unknown - ;; - m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip) - basic_machine=$basic_machine-unknown - os=-none - ;; - m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) - ;; - ms1) - basic_machine=mt-unknown - ;; - - strongarm | thumb | xscale) - basic_machine=arm-unknown - ;; - xgate) - basic_machine=$basic_machine-unknown - os=-none - ;; - xscaleeb) - basic_machine=armeb-unknown - ;; - - xscaleel) - basic_machine=armel-unknown - ;; - - # We use `pc' rather than `unknown' - # because (1) that's what they normally are, and - # (2) the word "unknown" tends to confuse beginning users. - i*86 | x86_64) - basic_machine=$basic_machine-pc - ;; - # Object if more than one company name word. - *-*-*) - echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 - exit 1 - ;; - # Recognize the basic CPU types with company name. - 580-* \ - | a29k-* \ - | aarch64-* | aarch64_be-* \ - | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ - | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ - | alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \ - | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ - | avr-* | avr32-* \ - | be32-* | be64-* \ - | bfin-* | bs2000-* \ - | c[123]* | c30-* | [cjt]90-* | c4x-* \ - | c8051-* | clipper-* | craynv-* | cydra-* \ - | d10v-* | d30v-* | dlx-* \ - | elxsi-* \ - | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \ - | h8300-* | h8500-* \ - | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ - | hexagon-* \ - | i*86-* | i860-* | i960-* | ia64-* \ - | ip2k-* | iq2000-* \ - | k1om-* \ - | le32-* | le64-* \ - | lm32-* \ - | m32c-* | m32r-* | m32rle-* \ - | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ - | m88110-* | m88k-* | maxq-* | mcore-* | metag-* \ - | microblaze-* | microblazeel-* \ - | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ - | mips16-* \ - | mips64-* | mips64el-* \ - | mips64octeon-* | mips64octeonel-* \ - | mips64orion-* | mips64orionel-* \ - | mips64r5900-* | mips64r5900el-* \ - | mips64vr-* | mips64vrel-* \ - | mips64vr4100-* | mips64vr4100el-* \ - | mips64vr4300-* | mips64vr4300el-* \ - | mips64vr5000-* | mips64vr5000el-* \ - | mips64vr5900-* | mips64vr5900el-* \ - | mipsisa32-* | mipsisa32el-* \ - | mipsisa32r2-* | mipsisa32r2el-* \ - | mipsisa64-* | mipsisa64el-* \ - | mipsisa64r2-* | mipsisa64r2el-* \ - | mipsisa64sb1-* | mipsisa64sb1el-* \ - | mipsisa64sr71k-* | mipsisa64sr71kel-* \ - | mipsr5900-* | mipsr5900el-* \ - | mipstx39-* | mipstx39el-* \ - | mmix-* \ - | mt-* \ - | msp430-* \ - | nds32-* | nds32le-* | nds32be-* \ - | nios-* | nios2-* | nios2eb-* | nios2el-* \ - | none-* | np1-* | ns16k-* | ns32k-* \ - | open8-* \ - | orion-* \ - | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ - | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \ - | pyramid-* \ - | rl78-* | romp-* | rs6000-* | rx-* \ - | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ - | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ - | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \ - | sparclite-* \ - | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx?-* \ - | tahoe-* \ - | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ - | tile*-* \ - | tron-* \ - | ubicom32-* \ - | v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \ - | vax-* \ - | we32k-* \ - | x86-* | x86_64-* | xc16x-* | xps100-* \ - | xstormy16-* | xtensa*-* \ - | ymp-* \ - | z8k-* | z80-*) - ;; - # Recognize the basic CPU types without company name, with glob match. - xtensa*) - basic_machine=$basic_machine-unknown - ;; - # Recognize the various machine names and aliases which stand - # for a CPU type and a company and sometimes even an OS. - 386bsd) - basic_machine=i386-unknown - os=-bsd - ;; - 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) - basic_machine=m68000-att - ;; - 3b*) - basic_machine=we32k-att - ;; - a29khif) - basic_machine=a29k-amd - os=-udi - ;; - abacus) - basic_machine=abacus-unknown - ;; - adobe68k) - basic_machine=m68010-adobe - os=-scout - ;; - alliant | fx80) - basic_machine=fx80-alliant - ;; - altos | altos3068) - basic_machine=m68k-altos - ;; - am29k) - basic_machine=a29k-none - os=-bsd - ;; - amd64) - basic_machine=x86_64-pc - ;; - amd64-*) - basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - amdahl) - basic_machine=580-amdahl - os=-sysv - ;; - amiga | amiga-*) - basic_machine=m68k-unknown - ;; - amigaos | amigados) - basic_machine=m68k-unknown - os=-amigaos - ;; - amigaunix | amix) - basic_machine=m68k-unknown - os=-sysv4 - ;; - apollo68) - basic_machine=m68k-apollo - os=-sysv - ;; - apollo68bsd) - basic_machine=m68k-apollo - os=-bsd - ;; - aros) - basic_machine=i386-pc - os=-aros - ;; - aux) - basic_machine=m68k-apple - os=-aux - ;; - balance) - basic_machine=ns32k-sequent - os=-dynix - ;; - blackfin) - basic_machine=bfin-unknown - os=-linux - ;; - blackfin-*) - basic_machine=bfin-`echo $basic_machine | sed 's/^[^-]*-//'` - os=-linux - ;; - bluegene*) - basic_machine=powerpc-ibm - os=-cnk - ;; - c54x-*) - basic_machine=tic54x-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - c55x-*) - basic_machine=tic55x-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - c6x-*) - basic_machine=tic6x-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - c90) - basic_machine=c90-cray - os=-unicos - ;; - cegcc) - basic_machine=arm-unknown - os=-cegcc - ;; - convex-c1) - basic_machine=c1-convex - os=-bsd - ;; - convex-c2) - basic_machine=c2-convex - os=-bsd - ;; - convex-c32) - basic_machine=c32-convex - os=-bsd - ;; - convex-c34) - basic_machine=c34-convex - os=-bsd - ;; - convex-c38) - basic_machine=c38-convex - os=-bsd - ;; - cray | j90) - basic_machine=j90-cray - os=-unicos - ;; - craynv) - basic_machine=craynv-cray - os=-unicosmp - ;; - cr16 | cr16-*) - basic_machine=cr16-unknown - os=-elf - ;; - crds | unos) - basic_machine=m68k-crds - ;; - crisv32 | crisv32-* | etraxfs*) - basic_machine=crisv32-axis - ;; - cris | cris-* | etrax*) - basic_machine=cris-axis - ;; - crx) - basic_machine=crx-unknown - os=-elf - ;; - da30 | da30-*) - basic_machine=m68k-da30 - ;; - decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) - basic_machine=mips-dec - ;; - decsystem10* | dec10*) - basic_machine=pdp10-dec - os=-tops10 - ;; - decsystem20* | dec20*) - basic_machine=pdp10-dec - os=-tops20 - ;; - delta | 3300 | motorola-3300 | motorola-delta \ - | 3300-motorola | delta-motorola) - basic_machine=m68k-motorola - ;; - delta88) - basic_machine=m88k-motorola - os=-sysv3 - ;; - dicos) - basic_machine=i686-pc - os=-dicos - ;; - djgpp) - basic_machine=i586-pc - os=-msdosdjgpp - ;; - dpx20 | dpx20-*) - basic_machine=rs6000-bull - os=-bosx - ;; - dpx2* | dpx2*-bull) - basic_machine=m68k-bull - os=-sysv3 - ;; - ebmon29k) - basic_machine=a29k-amd - os=-ebmon - ;; - elxsi) - basic_machine=elxsi-elxsi - os=-bsd - ;; - encore | umax | mmax) - basic_machine=ns32k-encore - ;; - es1800 | OSE68k | ose68k | ose | OSE) - basic_machine=m68k-ericsson - os=-ose - ;; - fx2800) - basic_machine=i860-alliant - ;; - genix) - basic_machine=ns32k-ns - ;; - gmicro) - basic_machine=tron-gmicro - os=-sysv - ;; - go32) - basic_machine=i386-pc - os=-go32 - ;; - h3050r* | hiux*) - basic_machine=hppa1.1-hitachi - os=-hiuxwe2 - ;; - h8300hms) - basic_machine=h8300-hitachi - os=-hms - ;; - h8300xray) - basic_machine=h8300-hitachi - os=-xray - ;; - h8500hms) - basic_machine=h8500-hitachi - os=-hms - ;; - harris) - basic_machine=m88k-harris - os=-sysv3 - ;; - hp300-*) - basic_machine=m68k-hp - ;; - hp300bsd) - basic_machine=m68k-hp - os=-bsd - ;; - hp300hpux) - basic_machine=m68k-hp - os=-hpux - ;; - hp3k9[0-9][0-9] | hp9[0-9][0-9]) - basic_machine=hppa1.0-hp - ;; - hp9k2[0-9][0-9] | hp9k31[0-9]) - basic_machine=m68000-hp - ;; - hp9k3[2-9][0-9]) - basic_machine=m68k-hp - ;; - hp9k6[0-9][0-9] | hp6[0-9][0-9]) - basic_machine=hppa1.0-hp - ;; - hp9k7[0-79][0-9] | hp7[0-79][0-9]) - basic_machine=hppa1.1-hp - ;; - hp9k78[0-9] | hp78[0-9]) - # FIXME: really hppa2.0-hp - basic_machine=hppa1.1-hp - ;; - hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) - # FIXME: really hppa2.0-hp - basic_machine=hppa1.1-hp - ;; - hp9k8[0-9][13679] | hp8[0-9][13679]) - basic_machine=hppa1.1-hp - ;; - hp9k8[0-9][0-9] | hp8[0-9][0-9]) - basic_machine=hppa1.0-hp - ;; - hppa-next) - os=-nextstep3 - ;; - hppaosf) - basic_machine=hppa1.1-hp - os=-osf - ;; - hppro) - basic_machine=hppa1.1-hp - os=-proelf - ;; - i370-ibm* | ibm*) - basic_machine=i370-ibm - ;; - i*86v32) - basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` - os=-sysv32 - ;; - i*86v4*) - basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` - os=-sysv4 - ;; - i*86v) - basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` - os=-sysv - ;; - i*86sol2) - basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` - os=-solaris2 - ;; - i386mach) - basic_machine=i386-mach - os=-mach - ;; - i386-vsta | vsta) - basic_machine=i386-unknown - os=-vsta - ;; - iris | iris4d) - basic_machine=mips-sgi - case $os in - -irix*) - ;; - *) - os=-irix4 - ;; - esac - ;; - isi68 | isi) - basic_machine=m68k-isi - os=-sysv - ;; - m68knommu) - basic_machine=m68k-unknown - os=-linux - ;; - m68knommu-*) - basic_machine=m68k-`echo $basic_machine | sed 's/^[^-]*-//'` - os=-linux - ;; - m88k-omron*) - basic_machine=m88k-omron - ;; - magnum | m3230) - basic_machine=mips-mips - os=-sysv - ;; - merlin) - basic_machine=ns32k-utek - os=-sysv - ;; - microblaze*) - basic_machine=microblaze-xilinx - ;; - mingw64) - basic_machine=x86_64-pc - os=-mingw64 - ;; - mingw32) - basic_machine=i686-pc - os=-mingw32 - ;; - mingw32ce) - basic_machine=arm-unknown - os=-mingw32ce - ;; - miniframe) - basic_machine=m68000-convergent - ;; - *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) - basic_machine=m68k-atari - os=-mint - ;; - mipsEE* | ee | ps2) - basic_machine=mips64r5900el-scei - case $os in - -linux*) - ;; - *) - os=-elf - ;; - esac - ;; - iop) - basic_machine=mipsel-scei - os=-irx - ;; - dvp) - basic_machine=dvp-scei - os=-elf - ;; - mips3*-*) - basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` - ;; - mips3*) - basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown - ;; - monitor) - basic_machine=m68k-rom68k - os=-coff - ;; - morphos) - basic_machine=powerpc-unknown - os=-morphos - ;; - msdos) - basic_machine=i386-pc - os=-msdos - ;; - ms1-*) - basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'` - ;; - msys) - basic_machine=i686-pc - os=-msys - ;; - mvs) - basic_machine=i370-ibm - os=-mvs - ;; - nacl) - basic_machine=le32-unknown - os=-nacl - ;; - ncr3000) - basic_machine=i486-ncr - os=-sysv4 - ;; - netbsd386) - basic_machine=i386-unknown - os=-netbsd - ;; - netwinder) - basic_machine=armv4l-rebel - os=-linux - ;; - news | news700 | news800 | news900) - basic_machine=m68k-sony - os=-newsos - ;; - news1000) - basic_machine=m68030-sony - os=-newsos - ;; - news-3600 | risc-news) - basic_machine=mips-sony - os=-newsos - ;; - necv70) - basic_machine=v70-nec - os=-sysv - ;; - next | m*-next ) - basic_machine=m68k-next - case $os in - -nextstep* ) - ;; - -ns2*) - os=-nextstep2 - ;; - *) - os=-nextstep3 - ;; - esac - ;; - nh3000) - basic_machine=m68k-harris - os=-cxux - ;; - nh[45]000) - basic_machine=m88k-harris - os=-cxux - ;; - nindy960) - basic_machine=i960-intel - os=-nindy - ;; - mon960) - basic_machine=i960-intel - os=-mon960 - ;; - nonstopux) - basic_machine=mips-compaq - os=-nonstopux - ;; - np1) - basic_machine=np1-gould - ;; - neo-tandem) - basic_machine=neo-tandem - ;; - nse-tandem) - basic_machine=nse-tandem - ;; - nsr-tandem) - basic_machine=nsr-tandem - ;; - op50n-* | op60c-*) - basic_machine=hppa1.1-oki - os=-proelf - ;; - openrisc | openrisc-*) - basic_machine=or32-unknown - ;; - os400) - basic_machine=powerpc-ibm - os=-os400 - ;; - OSE68000 | ose68000) - basic_machine=m68000-ericsson - os=-ose - ;; - os68k) - basic_machine=m68k-none - os=-os68k - ;; - pa-hitachi) - basic_machine=hppa1.1-hitachi - os=-hiuxwe2 - ;; - paragon) - basic_machine=i860-intel - os=-osf - ;; - parisc) - basic_machine=hppa-unknown - os=-linux - ;; - parisc-*) - basic_machine=hppa-`echo $basic_machine | sed 's/^[^-]*-//'` - os=-linux - ;; - pbd) - basic_machine=sparc-tti - ;; - pbb) - basic_machine=m68k-tti - ;; - pc532 | pc532-*) - basic_machine=ns32k-pc532 - ;; - pc98) - basic_machine=i386-pc - ;; - pc98-*) - basic_machine=i386-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - pentium | p5 | k5 | k6 | nexgen | viac3) - basic_machine=i586-pc - ;; - pentiumpro | p6 | 6x86 | athlon | athlon_*) - basic_machine=i686-pc - ;; - pentiumii | pentium2 | pentiumiii | pentium3) - basic_machine=i686-pc - ;; - pentium4) - basic_machine=i786-pc - ;; - pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) - basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - pentiumpro-* | p6-* | 6x86-* | athlon-*) - basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) - basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - pentium4-*) - basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - pn) - basic_machine=pn-gould - ;; - power) basic_machine=power-ibm - ;; - ppc | ppcbe) basic_machine=powerpc-unknown - ;; - ppc-* | ppcbe-*) - basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - ppcle | powerpclittle | ppc-le | powerpc-little) - basic_machine=powerpcle-unknown - ;; - ppcle-* | powerpclittle-*) - basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - ppc64) basic_machine=powerpc64-unknown - ;; - ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - ppc64le | powerpc64little | ppc64-le | powerpc64-little) - basic_machine=powerpc64le-unknown - ;; - ppc64le-* | powerpc64little-*) - basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - ps2) - basic_machine=i386-ibm - ;; - pw32) - basic_machine=i586-unknown - os=-pw32 - ;; - rdos | rdos64) - basic_machine=x86_64-pc - os=-rdos - ;; - rdos32) - basic_machine=i386-pc - os=-rdos - ;; - rom68k) - basic_machine=m68k-rom68k - os=-coff - ;; - rm[46]00) - basic_machine=mips-siemens - ;; - rtpc | rtpc-*) - basic_machine=romp-ibm - ;; - s390 | s390-*) - basic_machine=s390-ibm - ;; - s390x | s390x-*) - basic_machine=s390x-ibm - ;; - sa29200) - basic_machine=a29k-amd - os=-udi - ;; - sb1) - basic_machine=mipsisa64sb1-unknown - ;; - sb1el) - basic_machine=mipsisa64sb1el-unknown - ;; - sde) - basic_machine=mipsisa32-sde - os=-elf - ;; - sei) - basic_machine=mips-sei - os=-seiux - ;; - sequent) - basic_machine=i386-sequent - ;; - sh) - basic_machine=sh-hitachi - os=-hms - ;; - sh5el) - basic_machine=sh5le-unknown - ;; - sh64) - basic_machine=sh64-unknown - ;; - sparclite-wrs | simso-wrs) - basic_machine=sparclite-wrs - os=-vxworks - ;; - sps7) - basic_machine=m68k-bull - os=-sysv2 - ;; - spur) - basic_machine=spur-unknown - ;; - st2000) - basic_machine=m68k-tandem - ;; - stratus) - basic_machine=i860-stratus - os=-sysv4 - ;; - strongarm-* | thumb-*) - basic_machine=arm-`echo $basic_machine | sed 's/^[^-]*-//'` - ;; - sun2) - basic_machine=m68000-sun - ;; - sun2os3) - basic_machine=m68000-sun - os=-sunos3 - ;; - sun2os4) - basic_machine=m68000-sun - os=-sunos4 - ;; - sun3os3) - basic_machine=m68k-sun - os=-sunos3 - ;; - sun3os4) - basic_machine=m68k-sun - os=-sunos4 - ;; - sun4os3) - basic_machine=sparc-sun - os=-sunos3 - ;; - sun4os4) - basic_machine=sparc-sun - os=-sunos4 - ;; - sun4sol2) - basic_machine=sparc-sun - os=-solaris2 - ;; - sun3 | sun3-*) - basic_machine=m68k-sun - ;; - sun4) - basic_machine=sparc-sun - ;; - sun386 | sun386i | roadrunner) - basic_machine=i386-sun - ;; - sv1) - basic_machine=sv1-cray - os=-unicos - ;; - symmetry) - basic_machine=i386-sequent - os=-dynix - ;; - t3e) - basic_machine=alphaev5-cray - os=-unicos - ;; - t90) - basic_machine=t90-cray - os=-unicos - ;; - tile*) - basic_machine=$basic_machine-unknown - os=-linux-gnu - ;; - tx39) - basic_machine=mipstx39-unknown - ;; - tx39el) - basic_machine=mipstx39el-unknown - ;; - toad1) - basic_machine=pdp10-xkl - os=-tops20 - ;; - tower | tower-32) - basic_machine=m68k-ncr - ;; - tpf) - basic_machine=s390x-ibm - os=-tpf - ;; - udi29k) - basic_machine=a29k-amd - os=-udi - ;; - ultra3) - basic_machine=a29k-nyu - os=-sym1 - ;; - v810 | necv810) - basic_machine=v810-nec - os=-none - ;; - vaxv) - basic_machine=vax-dec - os=-sysv - ;; - vms) - basic_machine=vax-dec - os=-vms - ;; - vpp*|vx|vx-*) - basic_machine=f301-fujitsu - ;; - vxworks960) - basic_machine=i960-wrs - os=-vxworks - ;; - vxworks68) - basic_machine=m68k-wrs - os=-vxworks - ;; - vxworks29k) - basic_machine=a29k-wrs - os=-vxworks - ;; - w65*) - basic_machine=w65-wdc - os=-none - ;; - w89k-*) - basic_machine=hppa1.1-winbond - os=-proelf - ;; - xbox) - basic_machine=i686-pc - os=-mingw32 - ;; - xps | xps100) - basic_machine=xps100-honeywell - ;; - xscale-* | xscalee[bl]-*) - basic_machine=`echo $basic_machine | sed 's/^xscale/arm/'` - ;; - ymp) - basic_machine=ymp-cray - os=-unicos - ;; - z8k-*-coff) - basic_machine=z8k-unknown - os=-sim - ;; - z80-*-coff) - basic_machine=z80-unknown - os=-sim - ;; - none) - basic_machine=none-none - os=-none - ;; - -# Here we handle the default manufacturer of certain CPU types. It is in -# some cases the only manufacturer, in others, it is the most popular. - w89k) - basic_machine=hppa1.1-winbond - ;; - op50n) - basic_machine=hppa1.1-oki - ;; - op60c) - basic_machine=hppa1.1-oki - ;; - romp) - basic_machine=romp-ibm - ;; - mmix) - basic_machine=mmix-knuth - ;; - rs6000) - basic_machine=rs6000-ibm - ;; - vax) - basic_machine=vax-dec - ;; - pdp10) - # there are many clones, so DEC is not a safe bet - basic_machine=pdp10-unknown - ;; - pdp11) - basic_machine=pdp11-dec - ;; - we32k) - basic_machine=we32k-att - ;; - sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele) - basic_machine=sh-unknown - ;; - sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v) - basic_machine=sparc-sun - ;; - cydra) - basic_machine=cydra-cydrome - ;; - orion) - basic_machine=orion-highlevel - ;; - orion105) - basic_machine=clipper-highlevel - ;; - mac | mpw | mac-mpw) - basic_machine=m68k-apple - ;; - pmac | pmac-mpw) - basic_machine=powerpc-apple - ;; - *-unknown) - # Make sure to match an already-canonicalized machine name. - ;; - *) - echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 - exit 1 - ;; -esac - -# Here we canonicalize certain aliases for manufacturers. -case $basic_machine in - *-digital*) - basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` - ;; - *-commodore*) - basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` - ;; - *) - ;; -esac - -# Decode manufacturer-specific aliases for certain operating systems. - -if [ x"$os" != x"" ] -then -case $os in - # First match some system type aliases - # that might get confused with valid system types. - # -solaris* is a basic system type, with this one exception. - -auroraux) - os=-auroraux - ;; - -solaris1 | -solaris1.*) - os=`echo $os | sed -e 's|solaris1|sunos4|'` - ;; - -solaris) - os=-solaris2 - ;; - -svr4*) - os=-sysv4 - ;; - -unixware*) - os=-sysv4.2uw - ;; - -gnu/linux*) - os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` - ;; - # First accept the basic system types. - # The portable systems comes first. - # Each alternative MUST END IN A *, to match a version number. - # -sysv* is not here because it comes later, after sysvr4. - -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ - | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\ - | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \ - | -sym* | -kopensolaris* | -plan9* \ - | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ - | -aos* | -aros* \ - | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ - | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ - | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \ - | -bitrig* | -openbsd* | -solidbsd* \ - | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ - | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ - | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ - | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ - | -chorusos* | -chorusrdb* | -cegcc* \ - | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ - | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \ - | -linux-newlib* | -linux-musl* | -linux-uclibc* \ - | -uxpv* | -beos* | -mpeix* | -udk* \ - | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ - | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ - | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* | -irx* \ - | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ - | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ - | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ - | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es*) - # Remember, each alternative MUST END IN *, to match a version number. - ;; - -qnx*) - case $basic_machine in - x86-* | i*86-*) - ;; - *) - os=-nto$os - ;; - esac - ;; - -nto-qnx*) - ;; - -nto*) - os=`echo $os | sed -e 's|nto|nto-qnx|'` - ;; - -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ - | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \ - | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) - ;; - -mac*) - os=`echo $os | sed -e 's|mac|macos|'` - ;; - -linux-dietlibc) - os=-linux-dietlibc - ;; - -linux*) - os=`echo $os | sed -e 's|linux|linux-gnu|'` - ;; - -sunos5*) - os=`echo $os | sed -e 's|sunos5|solaris2|'` - ;; - -sunos6*) - os=`echo $os | sed -e 's|sunos6|solaris3|'` - ;; - -opened*) - os=-openedition - ;; - -os400*) - os=-os400 - ;; - -wince*) - os=-wince - ;; - -osfrose*) - os=-osfrose - ;; - -osf*) - os=-osf - ;; - -utek*) - os=-bsd - ;; - -dynix*) - os=-bsd - ;; - -acis*) - os=-aos - ;; - -atheos*) - os=-atheos - ;; - -syllable*) - os=-syllable - ;; - -386bsd) - os=-bsd - ;; - -ctix* | -uts*) - os=-sysv - ;; - -nova*) - os=-rtmk-nova - ;; - -ns2 ) - os=-nextstep2 - ;; - -nsk*) - os=-nsk - ;; - # Preserve the version number of sinix5. - -sinix5.*) - os=`echo $os | sed -e 's|sinix|sysv|'` - ;; - -sinix*) - os=-sysv4 - ;; - -tpf*) - os=-tpf - ;; - -triton*) - os=-sysv3 - ;; - -oss*) - os=-sysv3 - ;; - -svr4) - os=-sysv4 - ;; - -svr3) - os=-sysv3 - ;; - -sysvr4) - os=-sysv4 - ;; - # This must come after -sysvr4. - -sysv*) - ;; - -ose*) - os=-ose - ;; - -es1800*) - os=-ose - ;; - -xenix) - os=-xenix - ;; - -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) - os=-mint - ;; - -aros*) - os=-aros - ;; - -zvmoe) - os=-zvmoe - ;; - -dicos*) - os=-dicos - ;; - -nacl*) - ;; - -none) - ;; - *) - # Get rid of the `-' at the beginning of $os. - os=`echo $os | sed 's/[^-]*-//'` - echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 - exit 1 - ;; -esac -else - -# Here we handle the default operating systems that come with various machines. -# The value should be what the vendor currently ships out the door with their -# machine or put another way, the most popular os provided with the machine. - -# Note that if you're going to try to match "-MANUFACTURER" here (say, -# "-sun"), then you have to tell the case statement up towards the top -# that MANUFACTURER isn't an operating system. Otherwise, code above -# will signal an error saying that MANUFACTURER isn't an operating -# system, and we'll never get to this point. - -case $basic_machine in - score-*) - os=-elf - ;; - spu-*) - os=-elf - ;; - *-acorn) - os=-riscix1.2 - ;; - arm*-rebel) - os=-linux - ;; - arm*-semi) - os=-aout - ;; - c4x-* | tic4x-*) - os=-coff - ;; - c8051-*) - os=-elf - ;; - hexagon-*) - os=-elf - ;; - tic54x-*) - os=-coff - ;; - tic55x-*) - os=-coff - ;; - tic6x-*) - os=-coff - ;; - # This must come before the *-dec entry. - pdp10-*) - os=-tops20 - ;; - pdp11-*) - os=-none - ;; - *-dec | vax-*) - os=-ultrix4.2 - ;; - m68*-apollo) - os=-domain - ;; - i386-sun) - os=-sunos4.0.2 - ;; - m68000-sun) - os=-sunos3 - ;; - m68*-cisco) - os=-aout - ;; - mep-*) - os=-elf - ;; - mips*-cisco) - os=-elf - ;; - mips*-*) - os=-elf - ;; - or1k-*) - os=-elf - ;; - or32-*) - os=-coff - ;; - *-tti) # must be before sparc entry or we get the wrong os. - os=-sysv3 - ;; - sparc-* | *-sun) - os=-sunos4.1.1 - ;; - *-be) - os=-beos - ;; - *-haiku) - os=-haiku - ;; - *-ibm) - os=-aix - ;; - *-knuth) - os=-mmixware - ;; - *-wec) - os=-proelf - ;; - *-winbond) - os=-proelf - ;; - *-oki) - os=-proelf - ;; - *-hp) - os=-hpux - ;; - *-hitachi) - os=-hiux - ;; - i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) - os=-sysv - ;; - *-cbm) - os=-amigaos - ;; - *-dg) - os=-dgux - ;; - *-dolphin) - os=-sysv3 - ;; - m68k-ccur) - os=-rtu - ;; - m88k-omron*) - os=-luna - ;; - *-next ) - os=-nextstep - ;; - *-sequent) - os=-ptx - ;; - *-crds) - os=-unos - ;; - *-ns) - os=-genix - ;; - i370-*) - os=-mvs - ;; - *-next) - os=-nextstep3 - ;; - *-gould) - os=-sysv - ;; - *-highlevel) - os=-bsd - ;; - *-encore) - os=-bsd - ;; - *-sgi) - os=-irix - ;; - *-siemens) - os=-sysv4 - ;; - *-masscomp) - os=-rtu - ;; - f30[01]-fujitsu | f700-fujitsu) - os=-uxpv - ;; - *-rom68k) - os=-coff - ;; - *-*bug) - os=-coff - ;; - *-apple) - os=-macos - ;; - *-atari*) - os=-mint - ;; - *) - os=-none - ;; -esac -fi - -# Here we handle the case where we know the os, and the CPU type, but not the -# manufacturer. We pick the logical manufacturer. -vendor=unknown -case $basic_machine in - *-unknown) - case $os in - -riscix*) - vendor=acorn - ;; - -sunos*) - vendor=sun - ;; - -cnk*|-aix*) - vendor=ibm - ;; - -beos*) - vendor=be - ;; - -hpux*) - vendor=hp - ;; - -mpeix*) - vendor=hp - ;; - -hiux*) - vendor=hitachi - ;; - -unos*) - vendor=crds - ;; - -dgux*) - vendor=dg - ;; - -luna*) - vendor=omron - ;; - -genix*) - vendor=ns - ;; - -mvs* | -opened*) - vendor=ibm - ;; - -os400*) - vendor=ibm - ;; - -ptx*) - vendor=sequent - ;; - -tpf*) - vendor=ibm - ;; - -vxsim* | -vxworks* | -windiss*) - vendor=wrs - ;; - -aux*) - vendor=apple - ;; - -hms*) - vendor=hitachi - ;; - -mpw* | -macos*) - vendor=apple - ;; - -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) - vendor=atari - ;; - -vos*) - vendor=stratus - ;; - esac - basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` - ;; -esac - -echo $basic_machine$os -exit - -# Local variables: -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "timestamp='" -# time-stamp-format: "%:y-%02m-%02d" -# time-stamp-end: "'" -# End: diff --git a/build/autotools/configure b/build/autotools/configure deleted file mode 100755 index 50560353..00000000 --- a/build/autotools/configure +++ /dev/null @@ -1,9297 +0,0 @@ -#! /bin/sh -# Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for i2pd 0.0.0. -# -# Report bugs to . -# -# -# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. -# -# -# This configure script is free software; the Free Software Foundation -# gives unlimited permission to copy, distribute and modify it. -## -------------------- ## -## M4sh Initialization. ## -## -------------------- ## - -# Be more Bourne compatible -DUALCASE=1; export DUALCASE # for MKS sh -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : - emulate sh - NULLCMD=: - # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which - # is contrary to our usage. Disable this feature. - alias -g '${1+"$@"}'='"$@"' - setopt NO_GLOB_SUBST -else - case `(set -o) 2>/dev/null` in #( - *posix*) : - set -o posix ;; #( - *) : - ;; -esac -fi - - -as_nl=' -' -export as_nl -# Printing a long string crashes Solaris 7 /usr/bin/printf. -as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' -as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo -as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo -# Prefer a ksh shell builtin over an external printf program on Solaris, -# but without wasting forks for bash or zsh. -if test -z "$BASH_VERSION$ZSH_VERSION" \ - && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then - as_echo='print -r --' - as_echo_n='print -rn --' -elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then - as_echo='printf %s\n' - as_echo_n='printf %s' -else - if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then - as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' - as_echo_n='/usr/ucb/echo -n' - else - as_echo_body='eval expr "X$1" : "X\\(.*\\)"' - as_echo_n_body='eval - arg=$1; - case $arg in #( - *"$as_nl"*) - expr "X$arg" : "X\\(.*\\)$as_nl"; - arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; - esac; - expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" - ' - export as_echo_n_body - as_echo_n='sh -c $as_echo_n_body as_echo' - fi - export as_echo_body - as_echo='sh -c $as_echo_body as_echo' -fi - -# The user is always right. -if test "${PATH_SEPARATOR+set}" != set; then - PATH_SEPARATOR=: - (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { - (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || - PATH_SEPARATOR=';' - } -fi - - -# IFS -# We need space, tab and new line, in precisely that order. Quoting is -# there to prevent editors from complaining about space-tab. -# (If _AS_PATH_WALK were called with IFS unset, it would disable word -# splitting by setting IFS to empty value.) -IFS=" "" $as_nl" - -# Find who we are. Look in the path if we contain no directory separator. -as_myself= -case $0 in #(( - *[\\/]* ) as_myself=$0 ;; - *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break - done -IFS=$as_save_IFS - - ;; -esac -# We did not find ourselves, most probably we were run as `sh COMMAND' -# in which case we are not to be found in the path. -if test "x$as_myself" = x; then - as_myself=$0 -fi -if test ! -f "$as_myself"; then - $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 - exit 1 -fi - -# Unset variables that we do not need and which cause bugs (e.g. in -# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" -# suppresses any "Segmentation fault" message there. '((' could -# trigger a bug in pdksh 5.2.14. -for as_var in BASH_ENV ENV MAIL MAILPATH -do eval test x\${$as_var+set} = xset \ - && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : -done -PS1='$ ' -PS2='> ' -PS4='+ ' - -# NLS nuisances. -LC_ALL=C -export LC_ALL -LANGUAGE=C -export LANGUAGE - -# CDPATH. -(unset CDPATH) >/dev/null 2>&1 && unset CDPATH - -# Use a proper internal environment variable to ensure we don't fall - # into an infinite loop, continuously re-executing ourselves. - if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then - _as_can_reexec=no; export _as_can_reexec; - # We cannot yet assume a decent shell, so we have to provide a -# neutralization value for shells without unset; and this also -# works around shells that cannot unset nonexistent variables. -# Preserve -v and -x to the replacement shell. -BASH_ENV=/dev/null -ENV=/dev/null -(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV -case $- in # (((( - *v*x* | *x*v* ) as_opts=-vx ;; - *v* ) as_opts=-v ;; - *x* ) as_opts=-x ;; - * ) as_opts= ;; -esac -exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} -# Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed `exec'. -$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 -as_fn_exit 255 - fi - # We don't want this to propagate to other subprocesses. - { _as_can_reexec=; unset _as_can_reexec;} -if test "x$CONFIG_SHELL" = x; then - as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : - emulate sh - NULLCMD=: - # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which - # is contrary to our usage. Disable this feature. - alias -g '\${1+\"\$@\"}'='\"\$@\"' - setopt NO_GLOB_SUBST -else - case \`(set -o) 2>/dev/null\` in #( - *posix*) : - set -o posix ;; #( - *) : - ;; -esac -fi -" - as_required="as_fn_return () { (exit \$1); } -as_fn_success () { as_fn_return 0; } -as_fn_failure () { as_fn_return 1; } -as_fn_ret_success () { return 0; } -as_fn_ret_failure () { return 1; } - -exitcode=0 -as_fn_success || { exitcode=1; echo as_fn_success failed.; } -as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } -as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } -as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } -if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : - -else - exitcode=1; echo positional parameters were not saved. -fi -test x\$exitcode = x0 || exit 1 -test -x / || exit 1" - as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO - as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO - eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && - test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 -test \$(( 1 + 1 )) = 2 || exit 1" - if (eval "$as_required") 2>/dev/null; then : - as_have_required=yes -else - as_have_required=no -fi - if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : - -else - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -as_found=false -for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - as_found=: - case $as_dir in #( - /*) - for as_base in sh bash ksh sh5; do - # Try only shells that exist, to save several forks. - as_shell=$as_dir/$as_base - if { test -f "$as_shell" || test -f "$as_shell.exe"; } && - { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : - CONFIG_SHELL=$as_shell as_have_required=yes - if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : - break 2 -fi -fi - done;; - esac - as_found=false -done -$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && - { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : - CONFIG_SHELL=$SHELL as_have_required=yes -fi; } -IFS=$as_save_IFS - - - if test "x$CONFIG_SHELL" != x; then : - export CONFIG_SHELL - # We cannot yet assume a decent shell, so we have to provide a -# neutralization value for shells without unset; and this also -# works around shells that cannot unset nonexistent variables. -# Preserve -v and -x to the replacement shell. -BASH_ENV=/dev/null -ENV=/dev/null -(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV -case $- in # (((( - *v*x* | *x*v* ) as_opts=-vx ;; - *v* ) as_opts=-v ;; - *x* ) as_opts=-x ;; - * ) as_opts= ;; -esac -exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} -# Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed `exec'. -$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 -exit 255 -fi - - if test x$as_have_required = xno; then : - $as_echo "$0: This script requires a shell more modern than all" - $as_echo "$0: the shells that I found on your system." - if test x${ZSH_VERSION+set} = xset ; then - $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" - $as_echo "$0: be upgraded to zsh 4.3.4 or later." - else - $as_echo "$0: Please tell bug-autoconf@gnu.org and -$0: https://track.privacysolutions.no/projects/i2pd/issues -$0: about your system, including any error possibly output -$0: before this message. Then install a modern shell, or -$0: manually run the script under such a shell if you do -$0: have one." - fi - exit 1 -fi -fi -fi -SHELL=${CONFIG_SHELL-/bin/sh} -export SHELL -# Unset more variables known to interfere with behavior of common tools. -CLICOLOR_FORCE= GREP_OPTIONS= -unset CLICOLOR_FORCE GREP_OPTIONS - -## --------------------- ## -## M4sh Shell Functions. ## -## --------------------- ## -# as_fn_unset VAR -# --------------- -# Portably unset VAR. -as_fn_unset () -{ - { eval $1=; unset $1;} -} -as_unset=as_fn_unset - -# as_fn_set_status STATUS -# ----------------------- -# Set $? to STATUS, without forking. -as_fn_set_status () -{ - return $1 -} # as_fn_set_status - -# as_fn_exit STATUS -# ----------------- -# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. -as_fn_exit () -{ - set +e - as_fn_set_status $1 - exit $1 -} # as_fn_exit - -# as_fn_mkdir_p -# ------------- -# Create "$as_dir" as a directory, including parents if necessary. -as_fn_mkdir_p () -{ - - case $as_dir in #( - -*) as_dir=./$as_dir;; - esac - test -d "$as_dir" || eval $as_mkdir_p || { - as_dirs= - while :; do - case $as_dir in #( - *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( - *) as_qdir=$as_dir;; - esac - as_dirs="'$as_qdir' $as_dirs" - as_dir=`$as_dirname -- "$as_dir" || -$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$as_dir" : 'X\(//\)[^/]' \| \ - X"$as_dir" : 'X\(//\)$' \| \ - X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$as_dir" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - test -d "$as_dir" && break - done - test -z "$as_dirs" || eval "mkdir $as_dirs" - } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" - - -} # as_fn_mkdir_p - -# as_fn_executable_p FILE -# ----------------------- -# Test if FILE is an executable regular file. -as_fn_executable_p () -{ - test -f "$1" && test -x "$1" -} # as_fn_executable_p -# as_fn_append VAR VALUE -# ---------------------- -# Append the text in VALUE to the end of the definition contained in VAR. Take -# advantage of any shell optimizations that allow amortized linear growth over -# repeated appends, instead of the typical quadratic growth present in naive -# implementations. -if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : - eval 'as_fn_append () - { - eval $1+=\$2 - }' -else - as_fn_append () - { - eval $1=\$$1\$2 - } -fi # as_fn_append - -# as_fn_arith ARG... -# ------------------ -# Perform arithmetic evaluation on the ARGs, and store the result in the -# global $as_val. Take advantage of shells that can avoid forks. The arguments -# must be portable across $(()) and expr. -if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : - eval 'as_fn_arith () - { - as_val=$(( $* )) - }' -else - as_fn_arith () - { - as_val=`expr "$@" || test $? -eq 1` - } -fi # as_fn_arith - - -# as_fn_error STATUS ERROR [LINENO LOG_FD] -# ---------------------------------------- -# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are -# provided, also output the error to LOG_FD, referencing LINENO. Then exit the -# script with STATUS, using 1 if that was 0. -as_fn_error () -{ - as_status=$1; test $as_status -eq 0 && as_status=1 - if test "$4"; then - as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 - fi - $as_echo "$as_me: error: $2" >&2 - as_fn_exit $as_status -} # as_fn_error - -if expr a : '\(a\)' >/dev/null 2>&1 && - test "X`expr 00001 : '.*\(...\)'`" = X001; then - as_expr=expr -else - as_expr=false -fi - -if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then - as_basename=basename -else - as_basename=false -fi - -if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then - as_dirname=dirname -else - as_dirname=false -fi - -as_me=`$as_basename -- "$0" || -$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ - X"$0" : 'X\(//\)$' \| \ - X"$0" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X/"$0" | - sed '/^.*\/\([^/][^/]*\)\/*$/{ - s//\1/ - q - } - /^X\/\(\/\/\)$/{ - s//\1/ - q - } - /^X\/\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - -# Avoid depending upon Character Ranges. -as_cr_letters='abcdefghijklmnopqrstuvwxyz' -as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' -as_cr_Letters=$as_cr_letters$as_cr_LETTERS -as_cr_digits='0123456789' -as_cr_alnum=$as_cr_Letters$as_cr_digits - - - as_lineno_1=$LINENO as_lineno_1a=$LINENO - as_lineno_2=$LINENO as_lineno_2a=$LINENO - eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && - test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { - # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) - sed -n ' - p - /[$]LINENO/= - ' <$as_myself | - sed ' - s/[$]LINENO.*/&-/ - t lineno - b - :lineno - N - :loop - s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ - t loop - s/-\n.*// - ' >$as_me.lineno && - chmod +x "$as_me.lineno" || - { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } - - # If we had to re-execute with $CONFIG_SHELL, we're ensured to have - # already done that, so ensure we don't try to do so again and fall - # in an infinite loop. This has already happened in practice. - _as_can_reexec=no; export _as_can_reexec - # Don't try to exec as it changes $[0], causing all sort of problems - # (the dirname of $[0] is not the place where we might find the - # original and so on. Autoconf is especially sensitive to this). - . "./$as_me.lineno" - # Exit status is that of the last command. - exit -} - -ECHO_C= ECHO_N= ECHO_T= -case `echo -n x` in #((((( --n*) - case `echo 'xy\c'` in - *c*) ECHO_T=' ';; # ECHO_T is single tab character. - xy) ECHO_C='\c';; - *) echo `echo ksh88 bug on AIX 6.1` > /dev/null - ECHO_T=' ';; - esac;; -*) - ECHO_N='-n';; -esac - -rm -f conf$$ conf$$.exe conf$$.file -if test -d conf$$.dir; then - rm -f conf$$.dir/conf$$.file -else - rm -f conf$$.dir - mkdir conf$$.dir 2>/dev/null -fi -if (echo >conf$$.file) 2>/dev/null; then - if ln -s conf$$.file conf$$ 2>/dev/null; then - as_ln_s='ln -s' - # ... but there are two gotchas: - # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -pR'. - ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || - as_ln_s='cp -pR' - elif ln conf$$.file conf$$ 2>/dev/null; then - as_ln_s=ln - else - as_ln_s='cp -pR' - fi -else - as_ln_s='cp -pR' -fi -rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file -rmdir conf$$.dir 2>/dev/null - -if mkdir -p . 2>/dev/null; then - as_mkdir_p='mkdir -p "$as_dir"' -else - test -d ./-p && rmdir ./-p - as_mkdir_p=false -fi - -as_test_x='test -x' -as_executable_p=as_fn_executable_p - -# Sed expression to map a string onto a valid CPP name. -as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" - -# Sed expression to map a string onto a valid variable name. -as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" - - -test -n "$DJDIR" || exec 7<&0 &1 - -# Name of the host. -# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, -# so uname gets run too. -ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` - -# -# Initializations. -# -ac_default_prefix=/usr/local -ac_clean_files= -ac_config_libobj_dir=. -LIBOBJS= -cross_compiling=no -subdirs= -MFLAGS= -MAKEFLAGS= - -# Identity of this package. -PACKAGE_NAME='i2pd' -PACKAGE_TARNAME='i2pd' -PACKAGE_VERSION='0.0.0' -PACKAGE_STRING='i2pd 0.0.0' -PACKAGE_BUGREPORT='https://track.privacysolutions.no/projects/i2pd/issues' -PACKAGE_URL='' - -ac_unique_file="I2NPProtocol.cpp" -# Factoring default headers for most tests. -ac_includes_default="\ -#include -#ifdef HAVE_SYS_TYPES_H -# include -#endif -#ifdef HAVE_SYS_STAT_H -# include -#endif -#ifdef STDC_HEADERS -# include -# include -#else -# ifdef HAVE_STDLIB_H -# include -# endif -#endif -#ifdef HAVE_STRING_H -# if !defined STDC_HEADERS && defined HAVE_MEMORY_H -# include -# endif -# include -#endif -#ifdef HAVE_STRINGS_H -# include -#endif -#ifdef HAVE_INTTYPES_H -# include -#endif -#ifdef HAVE_STDINT_H -# include -#endif -#ifdef HAVE_UNISTD_H -# include -#endif" - -ac_subst_vars='am__EXEEXT_FALSE -am__EXEEXT_TRUE -LTLIBOBJS -LIBOBJS -EGREP -GREP -CPP -PTHREAD_CFLAGS -PTHREAD_LIBS -PTHREAD_CC -ax_pthread_config -BOOST_SYSTEM_LIB -BOOST_REGEX_LIB -BOOST_PROGRAM_OPTIONS_LIB -BOOST_FILESYSTEM_LIB -BOOST_DATE_TIME_LIB -BOOST_LDFLAGS -BOOST_CPPFLAGS -host_os -host_vendor -host_cpu -host -build_os -build_vendor -build_cpu -build -HAVE_CXX11 -am__fastdepCC_FALSE -am__fastdepCC_TRUE -CCDEPMODE -ac_ct_CC -CFLAGS -CC -am__fastdepCXX_FALSE -am__fastdepCXX_TRUE -CXXDEPMODE -am__nodep -AMDEPBACKSLASH -AMDEP_FALSE -AMDEP_TRUE -am__quote -am__include -DEPDIR -OBJEXT -EXEEXT -ac_ct_CXX -CPPFLAGS -LDFLAGS -CXXFLAGS -CXX -AM_BACKSLASH -AM_DEFAULT_VERBOSITY -AM_DEFAULT_V -AM_V -am__untar -am__tar -AMTAR -am__leading_dot -SET_MAKE -AWK -mkdir_p -MKDIR_P -INSTALL_STRIP_PROGRAM -STRIP -install_sh -MAKEINFO -AUTOHEADER -AUTOMAKE -AUTOCONF -ACLOCAL -VERSION -PACKAGE -CYGPATH_W -am__isrc -INSTALL_DATA -INSTALL_SCRIPT -INSTALL_PROGRAM -target_alias -host_alias -build_alias -LIBS -ECHO_T -ECHO_N -ECHO_C -DEFS -mandir -localedir -libdir -psdir -pdfdir -dvidir -htmldir -infodir -docdir -oldincludedir -includedir -localstatedir -sharedstatedir -sysconfdir -datadir -datarootdir -libexecdir -sbindir -bindir -program_transform_name -prefix -exec_prefix -PACKAGE_URL -PACKAGE_BUGREPORT -PACKAGE_STRING -PACKAGE_VERSION -PACKAGE_TARNAME -PACKAGE_NAME -PATH_SEPARATOR -SHELL' -ac_subst_files='' -ac_user_opts=' -enable_option_checking -enable_silent_rules -enable_dependency_tracking -with_boost -with_boost_libdir -with_boost_date_time -with_boost_filesystem -with_boost_program_options -with_boost_regex -with_boost_system -' - ac_precious_vars='build_alias -host_alias -target_alias -CXX -CXXFLAGS -LDFLAGS -LIBS -CPPFLAGS -CCC -CC -CFLAGS -CPP' - - -# Initialize some variables set by options. -ac_init_help= -ac_init_version=false -ac_unrecognized_opts= -ac_unrecognized_sep= -# The variables have the same names as the options, with -# dashes changed to underlines. -cache_file=/dev/null -exec_prefix=NONE -no_create= -no_recursion= -prefix=NONE -program_prefix=NONE -program_suffix=NONE -program_transform_name=s,x,x, -silent= -site= -srcdir= -verbose= -x_includes=NONE -x_libraries=NONE - -# Installation directory options. -# These are left unexpanded so users can "make install exec_prefix=/foo" -# and all the variables that are supposed to be based on exec_prefix -# by default will actually change. -# Use braces instead of parens because sh, perl, etc. also accept them. -# (The list follows the same order as the GNU Coding Standards.) -bindir='${exec_prefix}/bin' -sbindir='${exec_prefix}/sbin' -libexecdir='${exec_prefix}/libexec' -datarootdir='${prefix}/share' -datadir='${datarootdir}' -sysconfdir='${prefix}/etc' -sharedstatedir='${prefix}/com' -localstatedir='${prefix}/var' -includedir='${prefix}/include' -oldincludedir='/usr/include' -docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' -infodir='${datarootdir}/info' -htmldir='${docdir}' -dvidir='${docdir}' -pdfdir='${docdir}' -psdir='${docdir}' -libdir='${exec_prefix}/lib' -localedir='${datarootdir}/locale' -mandir='${datarootdir}/man' - -ac_prev= -ac_dashdash= -for ac_option -do - # If the previous option needs an argument, assign it. - if test -n "$ac_prev"; then - eval $ac_prev=\$ac_option - ac_prev= - continue - fi - - case $ac_option in - *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; - *=) ac_optarg= ;; - *) ac_optarg=yes ;; - esac - - # Accept the important Cygnus configure options, so we can diagnose typos. - - case $ac_dashdash$ac_option in - --) - ac_dashdash=yes ;; - - -bindir | --bindir | --bindi | --bind | --bin | --bi) - ac_prev=bindir ;; - -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) - bindir=$ac_optarg ;; - - -build | --build | --buil | --bui | --bu) - ac_prev=build_alias ;; - -build=* | --build=* | --buil=* | --bui=* | --bu=*) - build_alias=$ac_optarg ;; - - -cache-file | --cache-file | --cache-fil | --cache-fi \ - | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) - ac_prev=cache_file ;; - -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ - | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) - cache_file=$ac_optarg ;; - - --config-cache | -C) - cache_file=config.cache ;; - - -datadir | --datadir | --datadi | --datad) - ac_prev=datadir ;; - -datadir=* | --datadir=* | --datadi=* | --datad=*) - datadir=$ac_optarg ;; - - -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ - | --dataroo | --dataro | --datar) - ac_prev=datarootdir ;; - -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ - | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) - datarootdir=$ac_optarg ;; - - -disable-* | --disable-*) - ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` - # Reject names that are not valid shell variable names. - expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: $ac_useropt" - ac_useropt_orig=$ac_useropt - ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` - case $ac_user_opts in - *" -"enable_$ac_useropt" -"*) ;; - *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" - ac_unrecognized_sep=', ';; - esac - eval enable_$ac_useropt=no ;; - - -docdir | --docdir | --docdi | --doc | --do) - ac_prev=docdir ;; - -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) - docdir=$ac_optarg ;; - - -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) - ac_prev=dvidir ;; - -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) - dvidir=$ac_optarg ;; - - -enable-* | --enable-*) - ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` - # Reject names that are not valid shell variable names. - expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: $ac_useropt" - ac_useropt_orig=$ac_useropt - ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` - case $ac_user_opts in - *" -"enable_$ac_useropt" -"*) ;; - *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" - ac_unrecognized_sep=', ';; - esac - eval enable_$ac_useropt=\$ac_optarg ;; - - -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ - | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ - | --exec | --exe | --ex) - ac_prev=exec_prefix ;; - -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ - | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ - | --exec=* | --exe=* | --ex=*) - exec_prefix=$ac_optarg ;; - - -gas | --gas | --ga | --g) - # Obsolete; use --with-gas. - with_gas=yes ;; - - -help | --help | --hel | --he | -h) - ac_init_help=long ;; - -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) - ac_init_help=recursive ;; - -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) - ac_init_help=short ;; - - -host | --host | --hos | --ho) - ac_prev=host_alias ;; - -host=* | --host=* | --hos=* | --ho=*) - host_alias=$ac_optarg ;; - - -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) - ac_prev=htmldir ;; - -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ - | --ht=*) - htmldir=$ac_optarg ;; - - -includedir | --includedir | --includedi | --included | --include \ - | --includ | --inclu | --incl | --inc) - ac_prev=includedir ;; - -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ - | --includ=* | --inclu=* | --incl=* | --inc=*) - includedir=$ac_optarg ;; - - -infodir | --infodir | --infodi | --infod | --info | --inf) - ac_prev=infodir ;; - -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) - infodir=$ac_optarg ;; - - -libdir | --libdir | --libdi | --libd) - ac_prev=libdir ;; - -libdir=* | --libdir=* | --libdi=* | --libd=*) - libdir=$ac_optarg ;; - - -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ - | --libexe | --libex | --libe) - ac_prev=libexecdir ;; - -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ - | --libexe=* | --libex=* | --libe=*) - libexecdir=$ac_optarg ;; - - -localedir | --localedir | --localedi | --localed | --locale) - ac_prev=localedir ;; - -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) - localedir=$ac_optarg ;; - - -localstatedir | --localstatedir | --localstatedi | --localstated \ - | --localstate | --localstat | --localsta | --localst | --locals) - ac_prev=localstatedir ;; - -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ - | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) - localstatedir=$ac_optarg ;; - - -mandir | --mandir | --mandi | --mand | --man | --ma | --m) - ac_prev=mandir ;; - -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) - mandir=$ac_optarg ;; - - -nfp | --nfp | --nf) - # Obsolete; use --without-fp. - with_fp=no ;; - - -no-create | --no-create | --no-creat | --no-crea | --no-cre \ - | --no-cr | --no-c | -n) - no_create=yes ;; - - -no-recursion | --no-recursion | --no-recursio | --no-recursi \ - | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) - no_recursion=yes ;; - - -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ - | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ - | --oldin | --oldi | --old | --ol | --o) - ac_prev=oldincludedir ;; - -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ - | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ - | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) - oldincludedir=$ac_optarg ;; - - -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) - ac_prev=prefix ;; - -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) - prefix=$ac_optarg ;; - - -program-prefix | --program-prefix | --program-prefi | --program-pref \ - | --program-pre | --program-pr | --program-p) - ac_prev=program_prefix ;; - -program-prefix=* | --program-prefix=* | --program-prefi=* \ - | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) - program_prefix=$ac_optarg ;; - - -program-suffix | --program-suffix | --program-suffi | --program-suff \ - | --program-suf | --program-su | --program-s) - ac_prev=program_suffix ;; - -program-suffix=* | --program-suffix=* | --program-suffi=* \ - | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) - program_suffix=$ac_optarg ;; - - -program-transform-name | --program-transform-name \ - | --program-transform-nam | --program-transform-na \ - | --program-transform-n | --program-transform- \ - | --program-transform | --program-transfor \ - | --program-transfo | --program-transf \ - | --program-trans | --program-tran \ - | --progr-tra | --program-tr | --program-t) - ac_prev=program_transform_name ;; - -program-transform-name=* | --program-transform-name=* \ - | --program-transform-nam=* | --program-transform-na=* \ - | --program-transform-n=* | --program-transform-=* \ - | --program-transform=* | --program-transfor=* \ - | --program-transfo=* | --program-transf=* \ - | --program-trans=* | --program-tran=* \ - | --progr-tra=* | --program-tr=* | --program-t=*) - program_transform_name=$ac_optarg ;; - - -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) - ac_prev=pdfdir ;; - -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) - pdfdir=$ac_optarg ;; - - -psdir | --psdir | --psdi | --psd | --ps) - ac_prev=psdir ;; - -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) - psdir=$ac_optarg ;; - - -q | -quiet | --quiet | --quie | --qui | --qu | --q \ - | -silent | --silent | --silen | --sile | --sil) - silent=yes ;; - - -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) - ac_prev=sbindir ;; - -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ - | --sbi=* | --sb=*) - sbindir=$ac_optarg ;; - - -sharedstatedir | --sharedstatedir | --sharedstatedi \ - | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ - | --sharedst | --shareds | --shared | --share | --shar \ - | --sha | --sh) - ac_prev=sharedstatedir ;; - -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ - | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ - | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ - | --sha=* | --sh=*) - sharedstatedir=$ac_optarg ;; - - -site | --site | --sit) - ac_prev=site ;; - -site=* | --site=* | --sit=*) - site=$ac_optarg ;; - - -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) - ac_prev=srcdir ;; - -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) - srcdir=$ac_optarg ;; - - -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ - | --syscon | --sysco | --sysc | --sys | --sy) - ac_prev=sysconfdir ;; - -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ - | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) - sysconfdir=$ac_optarg ;; - - -target | --target | --targe | --targ | --tar | --ta | --t) - ac_prev=target_alias ;; - -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) - target_alias=$ac_optarg ;; - - -v | -verbose | --verbose | --verbos | --verbo | --verb) - verbose=yes ;; - - -version | --version | --versio | --versi | --vers | -V) - ac_init_version=: ;; - - -with-* | --with-*) - ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` - # Reject names that are not valid shell variable names. - expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: $ac_useropt" - ac_useropt_orig=$ac_useropt - ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` - case $ac_user_opts in - *" -"with_$ac_useropt" -"*) ;; - *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" - ac_unrecognized_sep=', ';; - esac - eval with_$ac_useropt=\$ac_optarg ;; - - -without-* | --without-*) - ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` - # Reject names that are not valid shell variable names. - expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: $ac_useropt" - ac_useropt_orig=$ac_useropt - ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` - case $ac_user_opts in - *" -"with_$ac_useropt" -"*) ;; - *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" - ac_unrecognized_sep=', ';; - esac - eval with_$ac_useropt=no ;; - - --x) - # Obsolete; use --with-x. - with_x=yes ;; - - -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ - | --x-incl | --x-inc | --x-in | --x-i) - ac_prev=x_includes ;; - -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ - | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) - x_includes=$ac_optarg ;; - - -x-libraries | --x-libraries | --x-librarie | --x-librari \ - | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) - ac_prev=x_libraries ;; - -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ - | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) - x_libraries=$ac_optarg ;; - - -*) as_fn_error $? "unrecognized option: \`$ac_option' -Try \`$0 --help' for more information" - ;; - - *=*) - ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` - # Reject names that are not valid shell variable names. - case $ac_envvar in #( - '' | [0-9]* | *[!_$as_cr_alnum]* ) - as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; - esac - eval $ac_envvar=\$ac_optarg - export $ac_envvar ;; - - *) - # FIXME: should be removed in autoconf 3.0. - $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 - expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && - $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 - : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" - ;; - - esac -done - -if test -n "$ac_prev"; then - ac_option=--`echo $ac_prev | sed 's/_/-/g'` - as_fn_error $? "missing argument to $ac_option" -fi - -if test -n "$ac_unrecognized_opts"; then - case $enable_option_checking in - no) ;; - fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; - *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; - esac -fi - -# Check all directory arguments for consistency. -for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ - datadir sysconfdir sharedstatedir localstatedir includedir \ - oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir -do - eval ac_val=\$$ac_var - # Remove trailing slashes. - case $ac_val in - */ ) - ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` - eval $ac_var=\$ac_val;; - esac - # Be sure to have absolute directory names. - case $ac_val in - [\\/$]* | ?:[\\/]* ) continue;; - NONE | '' ) case $ac_var in *prefix ) continue;; esac;; - esac - as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" -done - -# There might be people who depend on the old broken behavior: `$host' -# used to hold the argument of --host etc. -# FIXME: To remove some day. -build=$build_alias -host=$host_alias -target=$target_alias - -# FIXME: To remove some day. -if test "x$host_alias" != x; then - if test "x$build_alias" = x; then - cross_compiling=maybe - elif test "x$build_alias" != "x$host_alias"; then - cross_compiling=yes - fi -fi - -ac_tool_prefix= -test -n "$host_alias" && ac_tool_prefix=$host_alias- - -test "$silent" = yes && exec 6>/dev/null - - -ac_pwd=`pwd` && test -n "$ac_pwd" && -ac_ls_di=`ls -di .` && -ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || - as_fn_error $? "working directory cannot be determined" -test "X$ac_ls_di" = "X$ac_pwd_ls_di" || - as_fn_error $? "pwd does not report name of working directory" - - -# Find the source files, if location was not specified. -if test -z "$srcdir"; then - ac_srcdir_defaulted=yes - # Try the directory containing this script, then the parent directory. - ac_confdir=`$as_dirname -- "$as_myself" || -$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$as_myself" : 'X\(//\)[^/]' \| \ - X"$as_myself" : 'X\(//\)$' \| \ - X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$as_myself" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - srcdir=$ac_confdir - if test ! -r "$srcdir/$ac_unique_file"; then - srcdir=.. - fi -else - ac_srcdir_defaulted=no -fi -if test ! -r "$srcdir/$ac_unique_file"; then - test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." - as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" -fi -ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" -ac_abs_confdir=`( - cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" - pwd)` -# When building in place, set srcdir=. -if test "$ac_abs_confdir" = "$ac_pwd"; then - srcdir=. -fi -# Remove unnecessary trailing slashes from srcdir. -# Double slashes in file names in object file debugging info -# mess up M-x gdb in Emacs. -case $srcdir in -*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; -esac -for ac_var in $ac_precious_vars; do - eval ac_env_${ac_var}_set=\${${ac_var}+set} - eval ac_env_${ac_var}_value=\$${ac_var} - eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} - eval ac_cv_env_${ac_var}_value=\$${ac_var} -done - -# -# Report the --help message. -# -if test "$ac_init_help" = "long"; then - # Omit some internal or obsolete options to make the list less imposing. - # This message is too long to be a string in the A/UX 3.1 sh. - cat <<_ACEOF -\`configure' configures i2pd 0.0.0 to adapt to many kinds of systems. - -Usage: $0 [OPTION]... [VAR=VALUE]... - -To assign environment variables (e.g., CC, CFLAGS...), specify them as -VAR=VALUE. See below for descriptions of some of the useful variables. - -Defaults for the options are specified in brackets. - -Configuration: - -h, --help display this help and exit - --help=short display options specific to this package - --help=recursive display the short help of all the included packages - -V, --version display version information and exit - -q, --quiet, --silent do not print \`checking ...' messages - --cache-file=FILE cache test results in FILE [disabled] - -C, --config-cache alias for \`--cache-file=config.cache' - -n, --no-create do not create output files - --srcdir=DIR find the sources in DIR [configure dir or \`..'] - -Installation directories: - --prefix=PREFIX install architecture-independent files in PREFIX - [$ac_default_prefix] - --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX - [PREFIX] - -By default, \`make install' will install all the files in -\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify -an installation prefix other than \`$ac_default_prefix' using \`--prefix', -for instance \`--prefix=\$HOME'. - -For better control, use the options below. - -Fine tuning of the installation directories: - --bindir=DIR user executables [EPREFIX/bin] - --sbindir=DIR system admin executables [EPREFIX/sbin] - --libexecdir=DIR program executables [EPREFIX/libexec] - --sysconfdir=DIR read-only single-machine data [PREFIX/etc] - --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] - --localstatedir=DIR modifiable single-machine data [PREFIX/var] - --libdir=DIR object code libraries [EPREFIX/lib] - --includedir=DIR C header files [PREFIX/include] - --oldincludedir=DIR C header files for non-gcc [/usr/include] - --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] - --datadir=DIR read-only architecture-independent data [DATAROOTDIR] - --infodir=DIR info documentation [DATAROOTDIR/info] - --localedir=DIR locale-dependent data [DATAROOTDIR/locale] - --mandir=DIR man documentation [DATAROOTDIR/man] - --docdir=DIR documentation root [DATAROOTDIR/doc/i2pd] - --htmldir=DIR html documentation [DOCDIR] - --dvidir=DIR dvi documentation [DOCDIR] - --pdfdir=DIR pdf documentation [DOCDIR] - --psdir=DIR ps documentation [DOCDIR] -_ACEOF - - cat <<\_ACEOF - -Program names: - --program-prefix=PREFIX prepend PREFIX to installed program names - --program-suffix=SUFFIX append SUFFIX to installed program names - --program-transform-name=PROGRAM run sed PROGRAM on installed program names - -System types: - --build=BUILD configure for building on BUILD [guessed] - --host=HOST cross-compile to build programs to run on HOST [BUILD] -_ACEOF -fi - -if test -n "$ac_init_help"; then - case $ac_init_help in - short | recursive ) echo "Configuration of i2pd 0.0.0:";; - esac - cat <<\_ACEOF - -Optional Features: - --disable-option-checking ignore unrecognized --enable/--with options - --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) - --enable-FEATURE[=ARG] include FEATURE [ARG=yes] - --enable-silent-rules less verbose build output (undo: "make V=1") - --disable-silent-rules verbose build output (undo: "make V=0") - --enable-dependency-tracking - do not reject slow dependency extractors - --disable-dependency-tracking - speeds up one-time build - -Optional Packages: - --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] - --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) - --with-boost[=ARG] use Boost library from a standard location - (ARG=yes), from the specified location (ARG=), - or disable it (ARG=no) [ARG=yes] - --with-boost-libdir=LIB_DIR - Force given directory for boost libraries. Note that - this will override library path detection, so use - this parameter only if default library detection - fails and you know exactly where your boost - libraries are located. - --with-boost-date-time[=special-lib] - use the Date_Time library from boost - it is - possible to specify a certain library for the linker - e.g. - --with-boost-date-time=boost_date_time-gcc-mt-d-1_33_1 - --with-boost-filesystem[=special-lib] - use the Filesystem library from boost - it is - possible to specify a certain library for the linker - e.g. --with-boost-filesystem=boost_filesystem-gcc-mt - --with-boost-program-options[=special-lib] - use the program options library from boost - it is - possible to specify a certain library for the linker - e.g. - --with-boost-program-options=boost_program_options-gcc-mt-1_33_1 - --with-boost-regex[=special-lib] - use the Regex library from boost - it is possible to - specify a certain library for the linker e.g. - --with-boost-regex=boost_regex-gcc-mt-d-1_33_1 - --with-boost-system[=special-lib] - use the System library from boost - it is possible - to specify a certain library for the linker e.g. - --with-boost-system=boost_system-gcc-mt - -Some influential environment variables: - CXX C++ compiler command - CXXFLAGS C++ compiler flags - LDFLAGS linker flags, e.g. -L if you have libraries in a - nonstandard directory - LIBS libraries to pass to the linker, e.g. -l - CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if - you have headers in a nonstandard directory - CC C compiler command - CFLAGS C compiler flags - CPP C preprocessor - -Use these variables to override the choices made by `configure' or to help -it to find libraries and programs with nonstandard names/locations. - -Report bugs to . -_ACEOF -ac_status=$? -fi - -if test "$ac_init_help" = "recursive"; then - # If there are subdirs, report their specific --help. - for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue - test -d "$ac_dir" || - { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || - continue - ac_builddir=. - -case "$ac_dir" in -.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; -*) - ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` - # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` - case $ac_top_builddir_sub in - "") ac_top_builddir_sub=. ac_top_build_prefix= ;; - *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; - esac ;; -esac -ac_abs_top_builddir=$ac_pwd -ac_abs_builddir=$ac_pwd$ac_dir_suffix -# for backward compatibility: -ac_top_builddir=$ac_top_build_prefix - -case $srcdir in - .) # We are building in place. - ac_srcdir=. - ac_top_srcdir=$ac_top_builddir_sub - ac_abs_top_srcdir=$ac_pwd ;; - [\\/]* | ?:[\\/]* ) # Absolute name. - ac_srcdir=$srcdir$ac_dir_suffix; - ac_top_srcdir=$srcdir - ac_abs_top_srcdir=$srcdir ;; - *) # Relative name. - ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix - ac_top_srcdir=$ac_top_build_prefix$srcdir - ac_abs_top_srcdir=$ac_pwd/$srcdir ;; -esac -ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix - - cd "$ac_dir" || { ac_status=$?; continue; } - # Check for guested configure. - if test -f "$ac_srcdir/configure.gnu"; then - echo && - $SHELL "$ac_srcdir/configure.gnu" --help=recursive - elif test -f "$ac_srcdir/configure"; then - echo && - $SHELL "$ac_srcdir/configure" --help=recursive - else - $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 - fi || ac_status=$? - cd "$ac_pwd" || { ac_status=$?; break; } - done -fi - -test -n "$ac_init_help" && exit $ac_status -if $ac_init_version; then - cat <<\_ACEOF -i2pd configure 0.0.0 -generated by GNU Autoconf 2.69 - -Copyright (C) 2012 Free Software Foundation, Inc. -This configure script is free software; the Free Software Foundation -gives unlimited permission to copy, distribute and modify it. -_ACEOF - exit -fi - -## ------------------------ ## -## Autoconf initialization. ## -## ------------------------ ## - -# ac_fn_cxx_try_compile LINENO -# ---------------------------- -# Try to compile conftest.$ac_ext, and return whether this succeeded. -ac_fn_cxx_try_compile () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - rm -f conftest.$ac_objext - if { { ac_try="$ac_compile" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_compile") 2>conftest.err - ac_status=$? - if test -s conftest.err; then - grep -v '^ *+' conftest.err >conftest.er1 - cat conftest.er1 >&5 - mv -f conftest.er1 conftest.err - fi - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } && { - test -z "$ac_cxx_werror_flag" || - test ! -s conftest.err - } && test -s conftest.$ac_objext; then : - ac_retval=0 -else - $as_echo "$as_me: failed program was:" >&5 -sed 's/^/| /' conftest.$ac_ext >&5 - - ac_retval=1 -fi - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - as_fn_set_status $ac_retval - -} # ac_fn_cxx_try_compile - -# ac_fn_c_try_compile LINENO -# -------------------------- -# Try to compile conftest.$ac_ext, and return whether this succeeded. -ac_fn_c_try_compile () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - rm -f conftest.$ac_objext - if { { ac_try="$ac_compile" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_compile") 2>conftest.err - ac_status=$? - if test -s conftest.err; then - grep -v '^ *+' conftest.err >conftest.er1 - cat conftest.er1 >&5 - mv -f conftest.er1 conftest.err - fi - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } && { - test -z "$ac_c_werror_flag" || - test ! -s conftest.err - } && test -s conftest.$ac_objext; then : - ac_retval=0 -else - $as_echo "$as_me: failed program was:" >&5 -sed 's/^/| /' conftest.$ac_ext >&5 - - ac_retval=1 -fi - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - as_fn_set_status $ac_retval - -} # ac_fn_c_try_compile - -# ac_fn_c_try_link LINENO -# ----------------------- -# Try to link conftest.$ac_ext, and return whether this succeeded. -ac_fn_c_try_link () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - rm -f conftest.$ac_objext conftest$ac_exeext - if { { ac_try="$ac_link" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_link") 2>conftest.err - ac_status=$? - if test -s conftest.err; then - grep -v '^ *+' conftest.err >conftest.er1 - cat conftest.er1 >&5 - mv -f conftest.er1 conftest.err - fi - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } && { - test -z "$ac_c_werror_flag" || - test ! -s conftest.err - } && test -s conftest$ac_exeext && { - test "$cross_compiling" = yes || - test -x conftest$ac_exeext - }; then : - ac_retval=0 -else - $as_echo "$as_me: failed program was:" >&5 -sed 's/^/| /' conftest.$ac_ext >&5 - - ac_retval=1 -fi - # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information - # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would - # interfere with the next link command; also delete a directory that is - # left behind by Apple's compiler. We do this before executing the actions. - rm -rf conftest.dSYM conftest_ipa8_conftest.oo - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - as_fn_set_status $ac_retval - -} # ac_fn_c_try_link - -# ac_fn_c_try_cpp LINENO -# ---------------------- -# Try to preprocess conftest.$ac_ext, and return whether this succeeded. -ac_fn_c_try_cpp () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - if { { ac_try="$ac_cpp conftest.$ac_ext" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err - ac_status=$? - if test -s conftest.err; then - grep -v '^ *+' conftest.err >conftest.er1 - cat conftest.er1 >&5 - mv -f conftest.er1 conftest.err - fi - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } > conftest.i && { - test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || - test ! -s conftest.err - }; then : - ac_retval=0 -else - $as_echo "$as_me: failed program was:" >&5 -sed 's/^/| /' conftest.$ac_ext >&5 - - ac_retval=1 -fi - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - as_fn_set_status $ac_retval - -} # ac_fn_c_try_cpp - -# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES -# ------------------------------------------------------- -# Tests whether HEADER exists, giving a warning if it cannot be compiled using -# the include files in INCLUDES and setting the cache variable VAR -# accordingly. -ac_fn_c_check_header_mongrel () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - if eval \${$3+:} false; then : - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -$as_echo_n "checking for $2... " >&6; } -if eval \${$3+:} false; then : - $as_echo_n "(cached) " >&6 -fi -eval ac_res=\$$3 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -else - # Is the header compilable? -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 -$as_echo_n "checking $2 usability... " >&6; } -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$4 -#include <$2> -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - ac_header_compiler=yes -else - ac_header_compiler=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 -$as_echo "$ac_header_compiler" >&6; } - -# Is the header present? -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 -$as_echo_n "checking $2 presence... " >&6; } -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <$2> -_ACEOF -if ac_fn_c_try_cpp "$LINENO"; then : - ac_header_preproc=yes -else - ac_header_preproc=no -fi -rm -f conftest.err conftest.i conftest.$ac_ext -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 -$as_echo "$ac_header_preproc" >&6; } - -# So? What about this header? -case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( - yes:no: ) - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 -$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 -$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} - ;; - no:yes:* ) - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 -$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 -$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 -$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 -$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 -$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} -( $as_echo "## --------------------------------------------------------------------- ## -## Report this to https://track.privacysolutions.no/projects/i2pd/issues ## -## --------------------------------------------------------------------- ##" - ) | sed "s/^/$as_me: WARNING: /" >&2 - ;; -esac - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -$as_echo_n "checking for $2... " >&6; } -if eval \${$3+:} false; then : - $as_echo_n "(cached) " >&6 -else - eval "$3=\$ac_header_compiler" -fi -eval ac_res=\$$3 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -fi - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - -} # ac_fn_c_check_header_mongrel - -# ac_fn_c_try_run LINENO -# ---------------------- -# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes -# that executables *can* be run. -ac_fn_c_try_run () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - if { { ac_try="$ac_link" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_link") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' - { { case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_try") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; }; then : - ac_retval=0 -else - $as_echo "$as_me: program exited with status $ac_status" >&5 - $as_echo "$as_me: failed program was:" >&5 -sed 's/^/| /' conftest.$ac_ext >&5 - - ac_retval=$ac_status -fi - rm -rf conftest.dSYM conftest_ipa8_conftest.oo - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - as_fn_set_status $ac_retval - -} # ac_fn_c_try_run - -# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES -# ------------------------------------------------------- -# Tests whether HEADER exists and can be compiled using the include files in -# INCLUDES, setting the cache variable VAR accordingly. -ac_fn_c_check_header_compile () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -$as_echo_n "checking for $2... " >&6; } -if eval \${$3+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$4 -#include <$2> -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - eval "$3=yes" -else - eval "$3=no" -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -eval ac_res=\$$3 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - -} # ac_fn_c_check_header_compile - -# ac_fn_c_check_type LINENO TYPE VAR INCLUDES -# ------------------------------------------- -# Tests whether TYPE exists after having included INCLUDES, setting cache -# variable VAR accordingly. -ac_fn_c_check_type () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -$as_echo_n "checking for $2... " >&6; } -if eval \${$3+:} false; then : - $as_echo_n "(cached) " >&6 -else - eval "$3=no" - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$4 -int -main () -{ -if (sizeof ($2)) - return 0; - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$4 -int -main () -{ -if (sizeof (($2))) - return 0; - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - -else - eval "$3=yes" -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -eval ac_res=\$$3 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - -} # ac_fn_c_check_type - -# ac_fn_c_find_intX_t LINENO BITS VAR -# ----------------------------------- -# Finds a signed integer type with width BITS, setting cache variable VAR -# accordingly. -ac_fn_c_find_intX_t () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for int$2_t" >&5 -$as_echo_n "checking for int$2_t... " >&6; } -if eval \${$3+:} false; then : - $as_echo_n "(cached) " >&6 -else - eval "$3=no" - # Order is important - never check a type that is potentially smaller - # than half of the expected target width. - for ac_type in int$2_t 'int' 'long int' \ - 'long long int' 'short int' 'signed char'; do - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$ac_includes_default - enum { N = $2 / 2 - 1 }; -int -main () -{ -static int test_array [1 - 2 * !(0 < ($ac_type) ((((($ac_type) 1 << N) << N) - 1) * 2 + 1))]; -test_array [0] = 0; -return test_array [0]; - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$ac_includes_default - enum { N = $2 / 2 - 1 }; -int -main () -{ -static int test_array [1 - 2 * !(($ac_type) ((((($ac_type) 1 << N) << N) - 1) * 2 + 1) - < ($ac_type) ((((($ac_type) 1 << N) << N) - 1) * 2 + 2))]; -test_array [0] = 0; -return test_array [0]; - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - -else - case $ac_type in #( - int$2_t) : - eval "$3=yes" ;; #( - *) : - eval "$3=\$ac_type" ;; -esac -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - if eval test \"x\$"$3"\" = x"no"; then : - -else - break -fi - done -fi -eval ac_res=\$$3 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - -} # ac_fn_c_find_intX_t - -# ac_fn_c_find_uintX_t LINENO BITS VAR -# ------------------------------------ -# Finds an unsigned integer type with width BITS, setting cache variable VAR -# accordingly. -ac_fn_c_find_uintX_t () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for uint$2_t" >&5 -$as_echo_n "checking for uint$2_t... " >&6; } -if eval \${$3+:} false; then : - $as_echo_n "(cached) " >&6 -else - eval "$3=no" - # Order is important - never check a type that is potentially smaller - # than half of the expected target width. - for ac_type in uint$2_t 'unsigned int' 'unsigned long int' \ - 'unsigned long long int' 'unsigned short int' 'unsigned char'; do - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$ac_includes_default -int -main () -{ -static int test_array [1 - 2 * !((($ac_type) -1 >> ($2 / 2 - 1)) >> ($2 / 2 - 1) == 3)]; -test_array [0] = 0; -return test_array [0]; - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - case $ac_type in #( - uint$2_t) : - eval "$3=yes" ;; #( - *) : - eval "$3=\$ac_type" ;; -esac -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - if eval test \"x\$"$3"\" = x"no"; then : - -else - break -fi - done -fi -eval ac_res=\$$3 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - -} # ac_fn_c_find_uintX_t - -# ac_fn_c_check_func LINENO FUNC VAR -# ---------------------------------- -# Tests whether FUNC exists, setting the cache variable VAR accordingly -ac_fn_c_check_func () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -$as_echo_n "checking for $2... " >&6; } -if eval \${$3+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -/* Define $2 to an innocuous variant, in case declares $2. - For example, HP-UX 11i declares gettimeofday. */ -#define $2 innocuous_$2 - -/* System header to define __stub macros and hopefully few prototypes, - which can conflict with char $2 (); below. - Prefer to if __STDC__ is defined, since - exists even on freestanding compilers. */ - -#ifdef __STDC__ -# include -#else -# include -#endif - -#undef $2 - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char $2 (); -/* The GNU C library defines this for functions which it implements - to always fail with ENOSYS. Some functions are actually named - something starting with __ and the normal name is an alias. */ -#if defined __stub_$2 || defined __stub___$2 -choke me -#endif - -int -main () -{ -return $2 (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$3=yes" -else - eval "$3=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -fi -eval ac_res=\$$3 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - -} # ac_fn_c_check_func -cat >config.log <<_ACEOF -This file contains any messages produced by compilers while -running configure, to aid debugging if configure makes a mistake. - -It was created by i2pd $as_me 0.0.0, which was -generated by GNU Autoconf 2.69. Invocation command line was - - $ $0 $@ - -_ACEOF -exec 5>>config.log -{ -cat <<_ASUNAME -## --------- ## -## Platform. ## -## --------- ## - -hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` -uname -m = `(uname -m) 2>/dev/null || echo unknown` -uname -r = `(uname -r) 2>/dev/null || echo unknown` -uname -s = `(uname -s) 2>/dev/null || echo unknown` -uname -v = `(uname -v) 2>/dev/null || echo unknown` - -/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` -/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` - -/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` -/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` -/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` -/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` -/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` -/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` -/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` - -_ASUNAME - -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - $as_echo "PATH: $as_dir" - done -IFS=$as_save_IFS - -} >&5 - -cat >&5 <<_ACEOF - - -## ----------- ## -## Core tests. ## -## ----------- ## - -_ACEOF - - -# Keep a trace of the command line. -# Strip out --no-create and --no-recursion so they do not pile up. -# Strip out --silent because we don't want to record it for future runs. -# Also quote any args containing shell meta-characters. -# Make two passes to allow for proper duplicate-argument suppression. -ac_configure_args= -ac_configure_args0= -ac_configure_args1= -ac_must_keep_next=false -for ac_pass in 1 2 -do - for ac_arg - do - case $ac_arg in - -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; - -q | -quiet | --quiet | --quie | --qui | --qu | --q \ - | -silent | --silent | --silen | --sile | --sil) - continue ;; - *\'*) - ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; - esac - case $ac_pass in - 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; - 2) - as_fn_append ac_configure_args1 " '$ac_arg'" - if test $ac_must_keep_next = true; then - ac_must_keep_next=false # Got value, back to normal. - else - case $ac_arg in - *=* | --config-cache | -C | -disable-* | --disable-* \ - | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ - | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ - | -with-* | --with-* | -without-* | --without-* | --x) - case "$ac_configure_args0 " in - "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; - esac - ;; - -* ) ac_must_keep_next=true ;; - esac - fi - as_fn_append ac_configure_args " '$ac_arg'" - ;; - esac - done -done -{ ac_configure_args0=; unset ac_configure_args0;} -{ ac_configure_args1=; unset ac_configure_args1;} - -# When interrupted or exit'd, cleanup temporary files, and complete -# config.log. We remove comments because anyway the quotes in there -# would cause problems or look ugly. -# WARNING: Use '\'' to represent an apostrophe within the trap. -# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. -trap 'exit_status=$? - # Save into config.log some information that might help in debugging. - { - echo - - $as_echo "## ---------------- ## -## Cache variables. ## -## ---------------- ##" - echo - # The following way of writing the cache mishandles newlines in values, -( - for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do - eval ac_val=\$$ac_var - case $ac_val in #( - *${as_nl}*) - case $ac_var in #( - *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; - esac - case $ac_var in #( - _ | IFS | as_nl) ;; #( - BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( - *) { eval $ac_var=; unset $ac_var;} ;; - esac ;; - esac - done - (set) 2>&1 | - case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( - *${as_nl}ac_space=\ *) - sed -n \ - "s/'\''/'\''\\\\'\'''\''/g; - s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" - ;; #( - *) - sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" - ;; - esac | - sort -) - echo - - $as_echo "## ----------------- ## -## Output variables. ## -## ----------------- ##" - echo - for ac_var in $ac_subst_vars - do - eval ac_val=\$$ac_var - case $ac_val in - *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; - esac - $as_echo "$ac_var='\''$ac_val'\''" - done | sort - echo - - if test -n "$ac_subst_files"; then - $as_echo "## ------------------- ## -## File substitutions. ## -## ------------------- ##" - echo - for ac_var in $ac_subst_files - do - eval ac_val=\$$ac_var - case $ac_val in - *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; - esac - $as_echo "$ac_var='\''$ac_val'\''" - done | sort - echo - fi - - if test -s confdefs.h; then - $as_echo "## ----------- ## -## confdefs.h. ## -## ----------- ##" - echo - cat confdefs.h - echo - fi - test "$ac_signal" != 0 && - $as_echo "$as_me: caught signal $ac_signal" - $as_echo "$as_me: exit $exit_status" - } >&5 - rm -f core *.core core.conftest.* && - rm -f -r conftest* confdefs* conf$$* $ac_clean_files && - exit $exit_status -' 0 -for ac_signal in 1 2 13 15; do - trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal -done -ac_signal=0 - -# confdefs.h avoids OS command line length limits that DEFS can exceed. -rm -f -r conftest* confdefs.h - -$as_echo "/* confdefs.h */" > confdefs.h - -# Predefined preprocessor variables. - -cat >>confdefs.h <<_ACEOF -#define PACKAGE_NAME "$PACKAGE_NAME" -_ACEOF - -cat >>confdefs.h <<_ACEOF -#define PACKAGE_TARNAME "$PACKAGE_TARNAME" -_ACEOF - -cat >>confdefs.h <<_ACEOF -#define PACKAGE_VERSION "$PACKAGE_VERSION" -_ACEOF - -cat >>confdefs.h <<_ACEOF -#define PACKAGE_STRING "$PACKAGE_STRING" -_ACEOF - -cat >>confdefs.h <<_ACEOF -#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" -_ACEOF - -cat >>confdefs.h <<_ACEOF -#define PACKAGE_URL "$PACKAGE_URL" -_ACEOF - - -# Let the site file select an alternate cache file if it wants to. -# Prefer an explicitly selected file to automatically selected ones. -ac_site_file1=NONE -ac_site_file2=NONE -if test -n "$CONFIG_SITE"; then - # We do not want a PATH search for config.site. - case $CONFIG_SITE in #(( - -*) ac_site_file1=./$CONFIG_SITE;; - */*) ac_site_file1=$CONFIG_SITE;; - *) ac_site_file1=./$CONFIG_SITE;; - esac -elif test "x$prefix" != xNONE; then - ac_site_file1=$prefix/share/config.site - ac_site_file2=$prefix/etc/config.site -else - ac_site_file1=$ac_default_prefix/share/config.site - ac_site_file2=$ac_default_prefix/etc/config.site -fi -for ac_site_file in "$ac_site_file1" "$ac_site_file2" -do - test "x$ac_site_file" = xNONE && continue - if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 -$as_echo "$as_me: loading site script $ac_site_file" >&6;} - sed 's/^/| /' "$ac_site_file" >&5 - . "$ac_site_file" \ - || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -as_fn_error $? "failed to load site script $ac_site_file -See \`config.log' for more details" "$LINENO" 5; } - fi -done - -if test -r "$cache_file"; then - # Some versions of bash will fail to source /dev/null (special files - # actually), so we avoid doing that. DJGPP emulates it as a regular file. - if test /dev/null != "$cache_file" && test -f "$cache_file"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 -$as_echo "$as_me: loading cache $cache_file" >&6;} - case $cache_file in - [\\/]* | ?:[\\/]* ) . "$cache_file";; - *) . "./$cache_file";; - esac - fi -else - { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 -$as_echo "$as_me: creating cache $cache_file" >&6;} - >$cache_file -fi - -# Check that the precious variables saved in the cache have kept the same -# value. -ac_cache_corrupted=false -for ac_var in $ac_precious_vars; do - eval ac_old_set=\$ac_cv_env_${ac_var}_set - eval ac_new_set=\$ac_env_${ac_var}_set - eval ac_old_val=\$ac_cv_env_${ac_var}_value - eval ac_new_val=\$ac_env_${ac_var}_value - case $ac_old_set,$ac_new_set in - set,) - { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 -$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} - ac_cache_corrupted=: ;; - ,set) - { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 -$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} - ac_cache_corrupted=: ;; - ,);; - *) - if test "x$ac_old_val" != "x$ac_new_val"; then - # differences in whitespace do not lead to failure. - ac_old_val_w=`echo x $ac_old_val` - ac_new_val_w=`echo x $ac_new_val` - if test "$ac_old_val_w" != "$ac_new_val_w"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 -$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} - ac_cache_corrupted=: - else - { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 -$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} - eval $ac_var=\$ac_old_val - fi - { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 -$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 -$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} - fi;; - esac - # Pass precious variables to config.status. - if test "$ac_new_set" = set; then - case $ac_new_val in - *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; - *) ac_arg=$ac_var=$ac_new_val ;; - esac - case " $ac_configure_args " in - *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. - *) as_fn_append ac_configure_args " '$ac_arg'" ;; - esac - fi -done -if $ac_cache_corrupted; then - { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 -$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} - as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 -fi -## -------------------- ## -## Main body of script. ## -## -------------------- ## - -ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - -am__api_version='1.13' - -ac_aux_dir= -for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do - if test -f "$ac_dir/install-sh"; then - ac_aux_dir=$ac_dir - ac_install_sh="$ac_aux_dir/install-sh -c" - break - elif test -f "$ac_dir/install.sh"; then - ac_aux_dir=$ac_dir - ac_install_sh="$ac_aux_dir/install.sh -c" - break - elif test -f "$ac_dir/shtool"; then - ac_aux_dir=$ac_dir - ac_install_sh="$ac_aux_dir/shtool install -c" - break - fi -done -if test -z "$ac_aux_dir"; then - as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5 -fi - -# These three variables are undocumented and unsupported, -# and are intended to be withdrawn in a future Autoconf release. -# They can cause serious problems if a builder's source tree is in a directory -# whose full name contains unusual characters. -ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. -ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. -ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. - - -# Find a good install program. We prefer a C program (faster), -# so one script is as good as another. But avoid the broken or -# incompatible versions: -# SysV /etc/install, /usr/sbin/install -# SunOS /usr/etc/install -# IRIX /sbin/install -# AIX /bin/install -# AmigaOS /C/install, which installs bootblocks on floppy discs -# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag -# AFS /usr/afsws/bin/install, which mishandles nonexistent args -# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" -# OS/2's system install, which has a completely different semantic -# ./install, which can be erroneously created by make from ./install.sh. -# Reject install programs that cannot install multiple files. -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 -$as_echo_n "checking for a BSD-compatible install... " >&6; } -if test -z "$INSTALL"; then -if ${ac_cv_path_install+:} false; then : - $as_echo_n "(cached) " >&6 -else - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - # Account for people who put trailing slashes in PATH elements. -case $as_dir/ in #(( - ./ | .// | /[cC]/* | \ - /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ - ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ - /usr/ucb/* ) ;; - *) - # OSF1 and SCO ODT 3.0 have their own names for install. - # Don't use installbsd from OSF since it installs stuff as root - # by default. - for ac_prog in ginstall scoinst install; do - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then - if test $ac_prog = install && - grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then - # AIX install. It has an incompatible calling convention. - : - elif test $ac_prog = install && - grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then - # program-specific install script used by HP pwplus--don't use. - : - else - rm -rf conftest.one conftest.two conftest.dir - echo one > conftest.one - echo two > conftest.two - mkdir conftest.dir - if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && - test -s conftest.one && test -s conftest.two && - test -s conftest.dir/conftest.one && - test -s conftest.dir/conftest.two - then - ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" - break 3 - fi - fi - fi - done - done - ;; -esac - - done -IFS=$as_save_IFS - -rm -rf conftest.one conftest.two conftest.dir - -fi - if test "${ac_cv_path_install+set}" = set; then - INSTALL=$ac_cv_path_install - else - # As a last resort, use the slow shell script. Don't cache a - # value for INSTALL within a source directory, because that will - # break other packages using the cache if that directory is - # removed, or if the value is a relative name. - INSTALL=$ac_install_sh - fi -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 -$as_echo "$INSTALL" >&6; } - -# Use test -z because SunOS4 sh mishandles braces in ${var-val}. -# It thinks the first close brace ends the variable substitution. -test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' - -test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' - -test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5 -$as_echo_n "checking whether build environment is sane... " >&6; } -# Reject unsafe characters in $srcdir or the absolute working directory -# name. Accept space and tab only in the latter. -am_lf=' -' -case `pwd` in - *[\\\"\#\$\&\'\`$am_lf]*) - as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;; -esac -case $srcdir in - *[\\\"\#\$\&\'\`$am_lf\ \ ]*) - as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;; -esac - -# Do 'set' in a subshell so we don't clobber the current shell's -# arguments. Must try -L first in case configure is actually a -# symlink; some systems play weird games with the mod time of symlinks -# (eg FreeBSD returns the mod time of the symlink's containing -# directory). -if ( - am_has_slept=no - for am_try in 1 2; do - echo "timestamp, slept: $am_has_slept" > conftest.file - set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` - if test "$*" = "X"; then - # -L didn't work. - set X `ls -t "$srcdir/configure" conftest.file` - fi - if test "$*" != "X $srcdir/configure conftest.file" \ - && test "$*" != "X conftest.file $srcdir/configure"; then - - # If neither matched, then we have a broken ls. This can happen - # if, for instance, CONFIG_SHELL is bash and it inherits a - # broken ls alias from the environment. This has actually - # happened. Such a system could not be considered "sane". - as_fn_error $? "ls -t appears to fail. Make sure there is not a broken - alias in your environment" "$LINENO" 5 - fi - if test "$2" = conftest.file || test $am_try -eq 2; then - break - fi - # Just in case. - sleep 1 - am_has_slept=yes - done - test "$2" = conftest.file - ) -then - # Ok. - : -else - as_fn_error $? "newly created file is older than distributed files! -Check your system clock" "$LINENO" 5 -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } -# If we didn't sleep, we still need to ensure time stamps of config.status and -# generated files are strictly newer. -am_sleep_pid= -if grep 'slept: no' conftest.file >/dev/null 2>&1; then - ( sleep 1 ) & - am_sleep_pid=$! -fi - -rm -f conftest.file - -test "$program_prefix" != NONE && - program_transform_name="s&^&$program_prefix&;$program_transform_name" -# Use a double $ so make ignores it. -test "$program_suffix" != NONE && - program_transform_name="s&\$&$program_suffix&;$program_transform_name" -# Double any \ or $. -# By default was `s,x,x', remove it if useless. -ac_script='s/[\\$]/&&/g;s/;s,x,x,$//' -program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"` - -# expand $ac_aux_dir to an absolute path -am_aux_dir=`cd $ac_aux_dir && pwd` - -if test x"${MISSING+set}" != xset; then - case $am_aux_dir in - *\ * | *\ *) - MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; - *) - MISSING="\${SHELL} $am_aux_dir/missing" ;; - esac -fi -# Use eval to expand $SHELL -if eval "$MISSING --is-lightweight"; then - am_missing_run="$MISSING " -else - am_missing_run= - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5 -$as_echo "$as_me: WARNING: 'missing' script is too old or missing" >&2;} -fi - -if test x"${install_sh}" != xset; then - case $am_aux_dir in - *\ * | *\ *) - install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; - *) - install_sh="\${SHELL} $am_aux_dir/install-sh" - esac -fi - -# Installed binaries are usually stripped using 'strip' when the user -# run "make install-strip". However 'strip' might not be the right -# tool to use in cross-compilation environments, therefore Automake -# will honor the 'STRIP' environment variable to overrule this program. -if test "$cross_compiling" != no; then - if test -n "$ac_tool_prefix"; then - # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. -set dummy ${ac_tool_prefix}strip; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_STRIP+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$STRIP"; then - ac_cv_prog_STRIP="$STRIP" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_STRIP="${ac_tool_prefix}strip" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -STRIP=$ac_cv_prog_STRIP -if test -n "$STRIP"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 -$as_echo "$STRIP" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - -fi -if test -z "$ac_cv_prog_STRIP"; then - ac_ct_STRIP=$STRIP - # Extract the first word of "strip", so it can be a program name with args. -set dummy strip; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_ac_ct_STRIP+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$ac_ct_STRIP"; then - ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_ac_ct_STRIP="strip" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP -if test -n "$ac_ct_STRIP"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 -$as_echo "$ac_ct_STRIP" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - if test "x$ac_ct_STRIP" = x; then - STRIP=":" - else - case $cross_compiling:$ac_tool_warned in -yes:) -{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} -ac_tool_warned=yes ;; -esac - STRIP=$ac_ct_STRIP - fi -else - STRIP="$ac_cv_prog_STRIP" -fi - -fi -INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5 -$as_echo_n "checking for a thread-safe mkdir -p... " >&6; } -if test -z "$MKDIR_P"; then - if ${ac_cv_path_mkdir+:} false; then : - $as_echo_n "(cached) " >&6 -else - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_prog in mkdir gmkdir; do - for ac_exec_ext in '' $ac_executable_extensions; do - as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext" || continue - case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #( - 'mkdir (GNU coreutils) '* | \ - 'mkdir (coreutils) '* | \ - 'mkdir (fileutils) '4.1*) - ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext - break 3;; - esac - done - done - done -IFS=$as_save_IFS - -fi - - test -d ./--version && rmdir ./--version - if test "${ac_cv_path_mkdir+set}" = set; then - MKDIR_P="$ac_cv_path_mkdir -p" - else - # As a last resort, use the slow shell script. Don't cache a - # value for MKDIR_P within a source directory, because that will - # break other packages using the cache if that directory is - # removed, or if the value is a relative name. - MKDIR_P="$ac_install_sh -d" - fi -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5 -$as_echo "$MKDIR_P" >&6; } - -for ac_prog in gawk mawk nawk awk -do - # Extract the first word of "$ac_prog", so it can be a program name with args. -set dummy $ac_prog; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_AWK+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$AWK"; then - ac_cv_prog_AWK="$AWK" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_AWK="$ac_prog" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -AWK=$ac_cv_prog_AWK -if test -n "$AWK"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 -$as_echo "$AWK" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - - test -n "$AWK" && break -done - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 -$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } -set x ${MAKE-make} -ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` -if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat >conftest.make <<\_ACEOF -SHELL = /bin/sh -all: - @echo '@@@%%%=$(MAKE)=@@@%%%' -_ACEOF -# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. -case `${MAKE-make} -f conftest.make 2>/dev/null` in - *@@@%%%=?*=@@@%%%*) - eval ac_cv_prog_make_${ac_make}_set=yes;; - *) - eval ac_cv_prog_make_${ac_make}_set=no;; -esac -rm -f conftest.make -fi -if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } - SET_MAKE= -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - SET_MAKE="MAKE=${MAKE-make}" -fi - -rm -rf .tst 2>/dev/null -mkdir .tst 2>/dev/null -if test -d .tst; then - am__leading_dot=. -else - am__leading_dot=_ -fi -rmdir .tst 2>/dev/null - -# Check whether --enable-silent-rules was given. -if test "${enable_silent_rules+set}" = set; then : - enableval=$enable_silent_rules; -fi - -case $enable_silent_rules in # ((( - yes) AM_DEFAULT_VERBOSITY=0;; - no) AM_DEFAULT_VERBOSITY=1;; - *) AM_DEFAULT_VERBOSITY=1;; -esac -am_make=${MAKE-make} -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 -$as_echo_n "checking whether $am_make supports nested variables... " >&6; } -if ${am_cv_make_support_nested_variables+:} false; then : - $as_echo_n "(cached) " >&6 -else - if $as_echo 'TRUE=$(BAR$(V)) -BAR0=false -BAR1=true -V=1 -am__doit: - @$(TRUE) -.PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then - am_cv_make_support_nested_variables=yes -else - am_cv_make_support_nested_variables=no -fi -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 -$as_echo "$am_cv_make_support_nested_variables" >&6; } -if test $am_cv_make_support_nested_variables = yes; then - AM_V='$(V)' - AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' -else - AM_V=$AM_DEFAULT_VERBOSITY - AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY -fi -AM_BACKSLASH='\' - -if test "`cd $srcdir && pwd`" != "`pwd`"; then - # Use -I$(srcdir) only when $(srcdir) != ., so that make's output - # is not polluted with repeated "-I." - am__isrc=' -I$(srcdir)' - # test to see if srcdir already configured - if test -f $srcdir/config.status; then - as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5 - fi -fi - -# test whether we have cygpath -if test -z "$CYGPATH_W"; then - if (cygpath --version) >/dev/null 2>/dev/null; then - CYGPATH_W='cygpath -w' - else - CYGPATH_W=echo - fi -fi - - -# Define the identity of the package. - PACKAGE='i2pd' - VERSION='0.0.0' - - -cat >>confdefs.h <<_ACEOF -#define PACKAGE "$PACKAGE" -_ACEOF - - -cat >>confdefs.h <<_ACEOF -#define VERSION "$VERSION" -_ACEOF - -# Some tools Automake needs. - -ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} - - -AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} - - -AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} - - -AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} - - -MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} - -# For better backward compatibility. To be removed once Automake 1.9.x -# dies out for good. For more background, see: -# -# -mkdir_p='$(MKDIR_P)' - -# We need awk for the "check" target. The system "awk" is bad on -# some platforms. -# Always define AMTAR for backward compatibility. Yes, it's still used -# in the wild :-( We should find a proper way to deprecate it ... -AMTAR='$${TAR-tar}' - - -# We'll loop over all known methods to create a tar archive until one works. -_am_tools='gnutar pax cpio none' - -am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -' - - - - - - - -ac_config_headers="$ac_config_headers config.h" - - -# Checks for programs. -ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu -if test -z "$CXX"; then - if test -n "$CCC"; then - CXX=$CCC - else - if test -n "$ac_tool_prefix"; then - for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC - do - # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. -set dummy $ac_tool_prefix$ac_prog; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_CXX+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$CXX"; then - ac_cv_prog_CXX="$CXX" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -CXX=$ac_cv_prog_CXX -if test -n "$CXX"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5 -$as_echo "$CXX" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - - test -n "$CXX" && break - done -fi -if test -z "$CXX"; then - ac_ct_CXX=$CXX - for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC -do - # Extract the first word of "$ac_prog", so it can be a program name with args. -set dummy $ac_prog; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_ac_ct_CXX+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$ac_ct_CXX"; then - ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_ac_ct_CXX="$ac_prog" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -ac_ct_CXX=$ac_cv_prog_ac_ct_CXX -if test -n "$ac_ct_CXX"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5 -$as_echo "$ac_ct_CXX" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - - test -n "$ac_ct_CXX" && break -done - - if test "x$ac_ct_CXX" = x; then - CXX="g++" - else - case $cross_compiling:$ac_tool_warned in -yes:) -{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} -ac_tool_warned=yes ;; -esac - CXX=$ac_ct_CXX - fi -fi - - fi -fi -# Provide some information about the compiler. -$as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5 -set X $ac_compile -ac_compiler=$2 -for ac_option in --version -v -V -qversion; do - { { ac_try="$ac_compiler $ac_option >&5" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_compiler $ac_option >&5") 2>conftest.err - ac_status=$? - if test -s conftest.err; then - sed '10a\ -... rest of stderr output deleted ... - 10q' conftest.err >conftest.er1 - cat conftest.er1 >&5 - fi - rm -f conftest.er1 conftest.err - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } -done - -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -int -main () -{ - - ; - return 0; -} -_ACEOF -ac_clean_files_save=$ac_clean_files -ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" -# Try to create an executable without -o first, disregard a.out. -# It will help us diagnose broken compilers, and finding out an intuition -# of exeext. -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C++ compiler works" >&5 -$as_echo_n "checking whether the C++ compiler works... " >&6; } -ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` - -# The possible output files: -ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" - -ac_rmfiles= -for ac_file in $ac_files -do - case $ac_file in - *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; - * ) ac_rmfiles="$ac_rmfiles $ac_file";; - esac -done -rm -f $ac_rmfiles - -if { { ac_try="$ac_link_default" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_link_default") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then : - # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. -# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' -# in a Makefile. We should not override ac_cv_exeext if it was cached, -# so that the user can short-circuit this test for compilers unknown to -# Autoconf. -for ac_file in $ac_files '' -do - test -f "$ac_file" || continue - case $ac_file in - *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) - ;; - [ab].out ) - # We found the default executable, but exeext='' is most - # certainly right. - break;; - *.* ) - if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; - then :; else - ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` - fi - # We set ac_cv_exeext here because the later test for it is not - # safe: cross compilers may not add the suffix if given an `-o' - # argument, so we may need to know it at that point already. - # Even if this section looks crufty: it has the advantage of - # actually working. - break;; - * ) - break;; - esac -done -test "$ac_cv_exeext" = no && ac_cv_exeext= - -else - ac_file='' -fi -if test -z "$ac_file"; then : - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -$as_echo "$as_me: failed program was:" >&5 -sed 's/^/| /' conftest.$ac_ext >&5 - -{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -as_fn_error 77 "C++ compiler cannot create executables -See \`config.log' for more details" "$LINENO" 5; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler default output file name" >&5 -$as_echo_n "checking for C++ compiler default output file name... " >&6; } -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 -$as_echo "$ac_file" >&6; } -ac_exeext=$ac_cv_exeext - -rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out -ac_clean_files=$ac_clean_files_save -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 -$as_echo_n "checking for suffix of executables... " >&6; } -if { { ac_try="$ac_link" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_link") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then : - # If both `conftest.exe' and `conftest' are `present' (well, observable) -# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will -# work properly (i.e., refer to `conftest.exe'), while it won't with -# `rm'. -for ac_file in conftest.exe conftest conftest.*; do - test -f "$ac_file" || continue - case $ac_file in - *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; - *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` - break;; - * ) break;; - esac -done -else - { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -as_fn_error $? "cannot compute suffix of executables: cannot compile and link -See \`config.log' for more details" "$LINENO" 5; } -fi -rm -f conftest conftest$ac_cv_exeext -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 -$as_echo "$ac_cv_exeext" >&6; } - -rm -f conftest.$ac_ext -EXEEXT=$ac_cv_exeext -ac_exeext=$EXEEXT -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -int -main () -{ -FILE *f = fopen ("conftest.out", "w"); - return ferror (f) || fclose (f) != 0; - - ; - return 0; -} -_ACEOF -ac_clean_files="$ac_clean_files conftest.out" -# Check that the compiler produces executables we can run. If not, either -# the compiler is broken, or we cross compile. -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 -$as_echo_n "checking whether we are cross compiling... " >&6; } -if test "$cross_compiling" != yes; then - { { ac_try="$ac_link" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_link") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } - if { ac_try='./conftest$ac_cv_exeext' - { { case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_try") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; }; then - cross_compiling=no - else - if test "$cross_compiling" = maybe; then - cross_compiling=yes - else - { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -as_fn_error $? "cannot run C++ compiled programs. -If you meant to cross compile, use \`--host'. -See \`config.log' for more details" "$LINENO" 5; } - fi - fi -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 -$as_echo "$cross_compiling" >&6; } - -rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out -ac_clean_files=$ac_clean_files_save -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 -$as_echo_n "checking for suffix of object files... " >&6; } -if ${ac_cv_objext+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -int -main () -{ - - ; - return 0; -} -_ACEOF -rm -f conftest.o conftest.obj -if { { ac_try="$ac_compile" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_compile") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then : - for ac_file in conftest.o conftest.obj conftest.*; do - test -f "$ac_file" || continue; - case $ac_file in - *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; - *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` - break;; - esac -done -else - $as_echo "$as_me: failed program was:" >&5 -sed 's/^/| /' conftest.$ac_ext >&5 - -{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -as_fn_error $? "cannot compute suffix of object files: cannot compile -See \`config.log' for more details" "$LINENO" 5; } -fi -rm -f conftest.$ac_cv_objext conftest.$ac_ext -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 -$as_echo "$ac_cv_objext" >&6; } -OBJEXT=$ac_cv_objext -ac_objext=$OBJEXT -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C++ compiler" >&5 -$as_echo_n "checking whether we are using the GNU C++ compiler... " >&6; } -if ${ac_cv_cxx_compiler_gnu+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -int -main () -{ -#ifndef __GNUC__ - choke me -#endif - - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - ac_compiler_gnu=yes -else - ac_compiler_gnu=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -ac_cv_cxx_compiler_gnu=$ac_compiler_gnu - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5 -$as_echo "$ac_cv_cxx_compiler_gnu" >&6; } -if test $ac_compiler_gnu = yes; then - GXX=yes -else - GXX= -fi -ac_test_CXXFLAGS=${CXXFLAGS+set} -ac_save_CXXFLAGS=$CXXFLAGS -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5 -$as_echo_n "checking whether $CXX accepts -g... " >&6; } -if ${ac_cv_prog_cxx_g+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_save_cxx_werror_flag=$ac_cxx_werror_flag - ac_cxx_werror_flag=yes - ac_cv_prog_cxx_g=no - CXXFLAGS="-g" - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - ac_cv_prog_cxx_g=yes -else - CXXFLAGS="" - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - -else - ac_cxx_werror_flag=$ac_save_cxx_werror_flag - CXXFLAGS="-g" - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - ac_cv_prog_cxx_g=yes -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - ac_cxx_werror_flag=$ac_save_cxx_werror_flag -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5 -$as_echo "$ac_cv_prog_cxx_g" >&6; } -if test "$ac_test_CXXFLAGS" = set; then - CXXFLAGS=$ac_save_CXXFLAGS -elif test $ac_cv_prog_cxx_g = yes; then - if test "$GXX" = yes; then - CXXFLAGS="-g -O2" - else - CXXFLAGS="-g" - fi -else - if test "$GXX" = yes; then - CXXFLAGS="-O2" - else - CXXFLAGS= - fi -fi -ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu -DEPDIR="${am__leading_dot}deps" - -ac_config_commands="$ac_config_commands depfiles" - - -am_make=${MAKE-make} -cat > confinc << 'END' -am__doit: - @echo this is the am__doit target -.PHONY: am__doit -END -# If we don't find an include directive, just comment out the code. -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for style of include used by $am_make" >&5 -$as_echo_n "checking for style of include used by $am_make... " >&6; } -am__include="#" -am__quote= -_am_result=none -# First try GNU make style include. -echo "include confinc" > confmf -# Ignore all kinds of additional output from 'make'. -case `$am_make -s -f confmf 2> /dev/null` in #( -*the\ am__doit\ target*) - am__include=include - am__quote= - _am_result=GNU - ;; -esac -# Now try BSD make style include. -if test "$am__include" = "#"; then - echo '.include "confinc"' > confmf - case `$am_make -s -f confmf 2> /dev/null` in #( - *the\ am__doit\ target*) - am__include=.include - am__quote="\"" - _am_result=BSD - ;; - esac -fi - - -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $_am_result" >&5 -$as_echo "$_am_result" >&6; } -rm -f confinc confmf - -# Check whether --enable-dependency-tracking was given. -if test "${enable_dependency_tracking+set}" = set; then : - enableval=$enable_dependency_tracking; -fi - -if test "x$enable_dependency_tracking" != xno; then - am_depcomp="$ac_aux_dir/depcomp" - AMDEPBACKSLASH='\' - am__nodep='_no' -fi - if test "x$enable_dependency_tracking" != xno; then - AMDEP_TRUE= - AMDEP_FALSE='#' -else - AMDEP_TRUE='#' - AMDEP_FALSE= -fi - - - -depcc="$CXX" am_compiler_list= - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 -$as_echo_n "checking dependency style of $depcc... " >&6; } -if ${am_cv_CXX_dependencies_compiler_type+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then - # We make a subdir and do the tests there. Otherwise we can end up - # making bogus files that we don't know about and never remove. For - # instance it was reported that on HP-UX the gcc test will end up - # making a dummy file named 'D' -- because '-MD' means "put the output - # in D". - rm -rf conftest.dir - mkdir conftest.dir - # Copy depcomp to subdir because otherwise we won't find it if we're - # using a relative directory. - cp "$am_depcomp" conftest.dir - cd conftest.dir - # We will build objects and dependencies in a subdirectory because - # it helps to detect inapplicable dependency modes. For instance - # both Tru64's cc and ICC support -MD to output dependencies as a - # side effect of compilation, but ICC will put the dependencies in - # the current directory while Tru64 will put them in the object - # directory. - mkdir sub - - am_cv_CXX_dependencies_compiler_type=none - if test "$am_compiler_list" = ""; then - am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` - fi - am__universal=false - case " $depcc " in #( - *\ -arch\ *\ -arch\ *) am__universal=true ;; - esac - - for depmode in $am_compiler_list; do - # Setup a source with many dependencies, because some compilers - # like to wrap large dependency lists on column 80 (with \), and - # we should not choose a depcomp mode which is confused by this. - # - # We need to recreate these files for each test, as the compiler may - # overwrite some of them when testing with obscure command lines. - # This happens at least with the AIX C compiler. - : > sub/conftest.c - for i in 1 2 3 4 5 6; do - echo '#include "conftst'$i'.h"' >> sub/conftest.c - # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with - # Solaris 10 /bin/sh. - echo '/* dummy */' > sub/conftst$i.h - done - echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf - - # We check with '-c' and '-o' for the sake of the "dashmstdout" - # mode. It turns out that the SunPro C++ compiler does not properly - # handle '-M -o', and we need to detect this. Also, some Intel - # versions had trouble with output in subdirs. - am__obj=sub/conftest.${OBJEXT-o} - am__minus_obj="-o $am__obj" - case $depmode in - gcc) - # This depmode causes a compiler race in universal mode. - test "$am__universal" = false || continue - ;; - nosideeffect) - # After this tag, mechanisms are not by side-effect, so they'll - # only be used when explicitly requested. - if test "x$enable_dependency_tracking" = xyes; then - continue - else - break - fi - ;; - msvc7 | msvc7msys | msvisualcpp | msvcmsys) - # This compiler won't grok '-c -o', but also, the minuso test has - # not run yet. These depmodes are late enough in the game, and - # so weak that their functioning should not be impacted. - am__obj=conftest.${OBJEXT-o} - am__minus_obj= - ;; - none) break ;; - esac - if depmode=$depmode \ - source=sub/conftest.c object=$am__obj \ - depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ - $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ - >/dev/null 2>conftest.err && - grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && - grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && - grep $am__obj sub/conftest.Po > /dev/null 2>&1 && - ${MAKE-make} -s -f confmf > /dev/null 2>&1; then - # icc doesn't choke on unknown options, it will just issue warnings - # or remarks (even with -Werror). So we grep stderr for any message - # that says an option was ignored or not supported. - # When given -MP, icc 7.0 and 7.1 complain thusly: - # icc: Command line warning: ignoring option '-M'; no argument required - # The diagnosis changed in icc 8.0: - # icc: Command line remark: option '-MP' not supported - if (grep 'ignoring option' conftest.err || - grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else - am_cv_CXX_dependencies_compiler_type=$depmode - break - fi - fi - done - - cd .. - rm -rf conftest.dir -else - am_cv_CXX_dependencies_compiler_type=none -fi - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CXX_dependencies_compiler_type" >&5 -$as_echo "$am_cv_CXX_dependencies_compiler_type" >&6; } -CXXDEPMODE=depmode=$am_cv_CXX_dependencies_compiler_type - - if - test "x$enable_dependency_tracking" != xno \ - && test "$am_cv_CXX_dependencies_compiler_type" = gcc3; then - am__fastdepCXX_TRUE= - am__fastdepCXX_FALSE='#' -else - am__fastdepCXX_TRUE='#' - am__fastdepCXX_FALSE= -fi - - -ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu -if test -n "$ac_tool_prefix"; then - # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. -set dummy ${ac_tool_prefix}gcc; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_CC+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$CC"; then - ac_cv_prog_CC="$CC" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_CC="${ac_tool_prefix}gcc" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -CC=$ac_cv_prog_CC -if test -n "$CC"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -$as_echo "$CC" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - -fi -if test -z "$ac_cv_prog_CC"; then - ac_ct_CC=$CC - # Extract the first word of "gcc", so it can be a program name with args. -set dummy gcc; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_ac_ct_CC+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$ac_ct_CC"; then - ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_ac_ct_CC="gcc" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -ac_ct_CC=$ac_cv_prog_ac_ct_CC -if test -n "$ac_ct_CC"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 -$as_echo "$ac_ct_CC" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - if test "x$ac_ct_CC" = x; then - CC="" - else - case $cross_compiling:$ac_tool_warned in -yes:) -{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} -ac_tool_warned=yes ;; -esac - CC=$ac_ct_CC - fi -else - CC="$ac_cv_prog_CC" -fi - -if test -z "$CC"; then - if test -n "$ac_tool_prefix"; then - # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. -set dummy ${ac_tool_prefix}cc; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_CC+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$CC"; then - ac_cv_prog_CC="$CC" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_CC="${ac_tool_prefix}cc" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -CC=$ac_cv_prog_CC -if test -n "$CC"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -$as_echo "$CC" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - - fi -fi -if test -z "$CC"; then - # Extract the first word of "cc", so it can be a program name with args. -set dummy cc; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_CC+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$CC"; then - ac_cv_prog_CC="$CC" # Let the user override the test. -else - ac_prog_rejected=no -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then - ac_prog_rejected=yes - continue - fi - ac_cv_prog_CC="cc" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -if test $ac_prog_rejected = yes; then - # We found a bogon in the path, so make sure we never use it. - set dummy $ac_cv_prog_CC - shift - if test $# != 0; then - # We chose a different compiler from the bogus one. - # However, it has the same basename, so the bogon will be chosen - # first if we set CC to just the basename; use the full file name. - shift - ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" - fi -fi -fi -fi -CC=$ac_cv_prog_CC -if test -n "$CC"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -$as_echo "$CC" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - -fi -if test -z "$CC"; then - if test -n "$ac_tool_prefix"; then - for ac_prog in cl.exe - do - # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. -set dummy $ac_tool_prefix$ac_prog; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_CC+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$CC"; then - ac_cv_prog_CC="$CC" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_CC="$ac_tool_prefix$ac_prog" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -CC=$ac_cv_prog_CC -if test -n "$CC"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 -$as_echo "$CC" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - - test -n "$CC" && break - done -fi -if test -z "$CC"; then - ac_ct_CC=$CC - for ac_prog in cl.exe -do - # Extract the first word of "$ac_prog", so it can be a program name with args. -set dummy $ac_prog; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_ac_ct_CC+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$ac_ct_CC"; then - ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_ac_ct_CC="$ac_prog" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -ac_ct_CC=$ac_cv_prog_ac_ct_CC -if test -n "$ac_ct_CC"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 -$as_echo "$ac_ct_CC" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - - test -n "$ac_ct_CC" && break -done - - if test "x$ac_ct_CC" = x; then - CC="" - else - case $cross_compiling:$ac_tool_warned in -yes:) -{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} -ac_tool_warned=yes ;; -esac - CC=$ac_ct_CC - fi -fi - -fi - - -test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -as_fn_error $? "no acceptable C compiler found in \$PATH -See \`config.log' for more details" "$LINENO" 5; } - -# Provide some information about the compiler. -$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 -set X $ac_compile -ac_compiler=$2 -for ac_option in --version -v -V -qversion; do - { { ac_try="$ac_compiler $ac_option >&5" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_compiler $ac_option >&5") 2>conftest.err - ac_status=$? - if test -s conftest.err; then - sed '10a\ -... rest of stderr output deleted ... - 10q' conftest.err >conftest.er1 - cat conftest.er1 >&5 - fi - rm -f conftest.er1 conftest.err - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } -done - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 -$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } -if ${ac_cv_c_compiler_gnu+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -int -main () -{ -#ifndef __GNUC__ - choke me -#endif - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - ac_compiler_gnu=yes -else - ac_compiler_gnu=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -ac_cv_c_compiler_gnu=$ac_compiler_gnu - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 -$as_echo "$ac_cv_c_compiler_gnu" >&6; } -if test $ac_compiler_gnu = yes; then - GCC=yes -else - GCC= -fi -ac_test_CFLAGS=${CFLAGS+set} -ac_save_CFLAGS=$CFLAGS -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 -$as_echo_n "checking whether $CC accepts -g... " >&6; } -if ${ac_cv_prog_cc_g+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_save_c_werror_flag=$ac_c_werror_flag - ac_c_werror_flag=yes - ac_cv_prog_cc_g=no - CFLAGS="-g" - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - ac_cv_prog_cc_g=yes -else - CFLAGS="" - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - -else - ac_c_werror_flag=$ac_save_c_werror_flag - CFLAGS="-g" - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - ac_cv_prog_cc_g=yes -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - ac_c_werror_flag=$ac_save_c_werror_flag -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 -$as_echo "$ac_cv_prog_cc_g" >&6; } -if test "$ac_test_CFLAGS" = set; then - CFLAGS=$ac_save_CFLAGS -elif test $ac_cv_prog_cc_g = yes; then - if test "$GCC" = yes; then - CFLAGS="-g -O2" - else - CFLAGS="-g" - fi -else - if test "$GCC" = yes; then - CFLAGS="-O2" - else - CFLAGS= - fi -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 -$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } -if ${ac_cv_prog_cc_c89+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_cv_prog_cc_c89=no -ac_save_CC=$CC -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -#include -struct stat; -/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ -struct buf { int x; }; -FILE * (*rcsopen) (struct buf *, struct stat *, int); -static char *e (p, i) - char **p; - int i; -{ - return p[i]; -} -static char *f (char * (*g) (char **, int), char **p, ...) -{ - char *s; - va_list v; - va_start (v,p); - s = g (p, va_arg (v,int)); - va_end (v); - return s; -} - -/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has - function prototypes and stuff, but not '\xHH' hex character constants. - These don't provoke an error unfortunately, instead are silently treated - as 'x'. The following induces an error, until -std is added to get - proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an - array size at least. It's necessary to write '\x00'==0 to get something - that's true only with -std. */ -int osf4_cc_array ['\x00' == 0 ? 1 : -1]; - -/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters - inside strings and character constants. */ -#define FOO(x) 'x' -int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; - -int test (int i, double x); -struct s1 {int (*f) (int a);}; -struct s2 {int (*f) (double a);}; -int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); -int argc; -char **argv; -int -main () -{ -return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; - ; - return 0; -} -_ACEOF -for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ - -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" -do - CC="$ac_save_CC $ac_arg" - if ac_fn_c_try_compile "$LINENO"; then : - ac_cv_prog_cc_c89=$ac_arg -fi -rm -f core conftest.err conftest.$ac_objext - test "x$ac_cv_prog_cc_c89" != "xno" && break -done -rm -f conftest.$ac_ext -CC=$ac_save_CC - -fi -# AC_CACHE_VAL -case "x$ac_cv_prog_cc_c89" in - x) - { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 -$as_echo "none needed" >&6; } ;; - xno) - { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 -$as_echo "unsupported" >&6; } ;; - *) - CC="$CC $ac_cv_prog_cc_c89" - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 -$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; -esac -if test "x$ac_cv_prog_cc_c89" != xno; then : - -fi - -ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - -depcc="$CC" am_compiler_list= - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 -$as_echo_n "checking dependency style of $depcc... " >&6; } -if ${am_cv_CC_dependencies_compiler_type+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then - # We make a subdir and do the tests there. Otherwise we can end up - # making bogus files that we don't know about and never remove. For - # instance it was reported that on HP-UX the gcc test will end up - # making a dummy file named 'D' -- because '-MD' means "put the output - # in D". - rm -rf conftest.dir - mkdir conftest.dir - # Copy depcomp to subdir because otherwise we won't find it if we're - # using a relative directory. - cp "$am_depcomp" conftest.dir - cd conftest.dir - # We will build objects and dependencies in a subdirectory because - # it helps to detect inapplicable dependency modes. For instance - # both Tru64's cc and ICC support -MD to output dependencies as a - # side effect of compilation, but ICC will put the dependencies in - # the current directory while Tru64 will put them in the object - # directory. - mkdir sub - - am_cv_CC_dependencies_compiler_type=none - if test "$am_compiler_list" = ""; then - am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` - fi - am__universal=false - case " $depcc " in #( - *\ -arch\ *\ -arch\ *) am__universal=true ;; - esac - - for depmode in $am_compiler_list; do - # Setup a source with many dependencies, because some compilers - # like to wrap large dependency lists on column 80 (with \), and - # we should not choose a depcomp mode which is confused by this. - # - # We need to recreate these files for each test, as the compiler may - # overwrite some of them when testing with obscure command lines. - # This happens at least with the AIX C compiler. - : > sub/conftest.c - for i in 1 2 3 4 5 6; do - echo '#include "conftst'$i'.h"' >> sub/conftest.c - # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with - # Solaris 10 /bin/sh. - echo '/* dummy */' > sub/conftst$i.h - done - echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf - - # We check with '-c' and '-o' for the sake of the "dashmstdout" - # mode. It turns out that the SunPro C++ compiler does not properly - # handle '-M -o', and we need to detect this. Also, some Intel - # versions had trouble with output in subdirs. - am__obj=sub/conftest.${OBJEXT-o} - am__minus_obj="-o $am__obj" - case $depmode in - gcc) - # This depmode causes a compiler race in universal mode. - test "$am__universal" = false || continue - ;; - nosideeffect) - # After this tag, mechanisms are not by side-effect, so they'll - # only be used when explicitly requested. - if test "x$enable_dependency_tracking" = xyes; then - continue - else - break - fi - ;; - msvc7 | msvc7msys | msvisualcpp | msvcmsys) - # This compiler won't grok '-c -o', but also, the minuso test has - # not run yet. These depmodes are late enough in the game, and - # so weak that their functioning should not be impacted. - am__obj=conftest.${OBJEXT-o} - am__minus_obj= - ;; - none) break ;; - esac - if depmode=$depmode \ - source=sub/conftest.c object=$am__obj \ - depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ - $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ - >/dev/null 2>conftest.err && - grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && - grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && - grep $am__obj sub/conftest.Po > /dev/null 2>&1 && - ${MAKE-make} -s -f confmf > /dev/null 2>&1; then - # icc doesn't choke on unknown options, it will just issue warnings - # or remarks (even with -Werror). So we grep stderr for any message - # that says an option was ignored or not supported. - # When given -MP, icc 7.0 and 7.1 complain thusly: - # icc: Command line warning: ignoring option '-M'; no argument required - # The diagnosis changed in icc 8.0: - # icc: Command line remark: option '-MP' not supported - if (grep 'ignoring option' conftest.err || - grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else - am_cv_CC_dependencies_compiler_type=$depmode - break - fi - fi - done - - cd .. - rm -rf conftest.dir -else - am_cv_CC_dependencies_compiler_type=none -fi - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 -$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; } -CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type - - if - test "x$enable_dependency_tracking" != xno \ - && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then - am__fastdepCC_TRUE= - am__fastdepCC_FALSE='#' -else - am__fastdepCC_TRUE='#' - am__fastdepCC_FALSE= -fi - - - -# Check for C++11 -# ============================================================================ -# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html -# ============================================================================ -# -# SYNOPSIS -# -# AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) -# -# DESCRIPTION -# -# Check for baseline language coverage in the compiler for the C++11 -# standard; if necessary, add switches to CXXFLAGS to enable support. -# -# The first argument, if specified, indicates whether you insist on an -# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. -# -std=c++11). If neither is specified, you get whatever works, with -# preference for an extended mode. -# -# The second argument, if specified 'mandatory' or if left unspecified, -# indicates that baseline C++11 support is required and that the macro -# should error out if no mode with that support is found. If specified -# 'optional', then configuration proceeds regardless, after defining -# HAVE_CXX11 if and only if a supporting mode is found. -# -# LICENSE -# -# Copyright (c) 2008 Benjamin Kosnik -# Copyright (c) 2012 Zack Weinberg -# Copyright (c) 2013 Roy Stogner -# Copyright (c) 2014 Alexey Sokolov -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 4 - - - - - - - ax_cxx_compile_cxx11_required=true - ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu - ac_success=no - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++11 features by default" >&5 -$as_echo_n "checking whether $CXX supports C++11 features by default... " >&6; } -if ${ax_cv_cxx_compile_cxx11+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - - template - struct check - { - static_assert(sizeof(int) <= sizeof(T), "not big enough"); - }; - - struct Base { - virtual void f() {} - }; - struct Child : public Base { - virtual void f() {} - }; - - typedef check> right_angle_brackets; - - int a; - decltype(a) b; - - typedef check check_type; - check_type c; - check_type&& cr = static_cast(c); - - auto d = a; - -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - ax_cv_cxx_compile_cxx11=yes -else - ax_cv_cxx_compile_cxx11=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_cxx_compile_cxx11" >&5 -$as_echo "$ax_cv_cxx_compile_cxx11" >&6; } - if test x$ax_cv_cxx_compile_cxx11 = xyes; then - ac_success=yes - fi - - if test x$ac_success = xno; then - for switch in -std=gnu++11 -std=gnu++0x; do - cachevar=`$as_echo "ax_cv_cxx_compile_cxx11_$switch" | $as_tr_sh` - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++11 features with $switch" >&5 -$as_echo_n "checking whether $CXX supports C++11 features with $switch... " >&6; } -if eval \${$cachevar+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_save_CXXFLAGS="$CXXFLAGS" - CXXFLAGS="$CXXFLAGS $switch" - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - - template - struct check - { - static_assert(sizeof(int) <= sizeof(T), "not big enough"); - }; - - struct Base { - virtual void f() {} - }; - struct Child : public Base { - virtual void f() {} - }; - - typedef check> right_angle_brackets; - - int a; - decltype(a) b; - - typedef check check_type; - check_type c; - check_type&& cr = static_cast(c); - - auto d = a; - -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - eval $cachevar=yes -else - eval $cachevar=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - CXXFLAGS="$ac_save_CXXFLAGS" -fi -eval ac_res=\$$cachevar - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } - if eval test x\$$cachevar = xyes; then - CXXFLAGS="$CXXFLAGS $switch" - ac_success=yes - break - fi - done - fi - - if test x$ac_success = xno; then - for switch in -std=c++11 -std=c++0x; do - cachevar=`$as_echo "ax_cv_cxx_compile_cxx11_$switch" | $as_tr_sh` - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++11 features with $switch" >&5 -$as_echo_n "checking whether $CXX supports C++11 features with $switch... " >&6; } -if eval \${$cachevar+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_save_CXXFLAGS="$CXXFLAGS" - CXXFLAGS="$CXXFLAGS $switch" - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - - template - struct check - { - static_assert(sizeof(int) <= sizeof(T), "not big enough"); - }; - - struct Base { - virtual void f() {} - }; - struct Child : public Base { - virtual void f() {} - }; - - typedef check> right_angle_brackets; - - int a; - decltype(a) b; - - typedef check check_type; - check_type c; - check_type&& cr = static_cast(c); - - auto d = a; - -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - eval $cachevar=yes -else - eval $cachevar=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - CXXFLAGS="$ac_save_CXXFLAGS" -fi -eval ac_res=\$$cachevar - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } - if eval test x\$$cachevar = xyes; then - CXXFLAGS="$CXXFLAGS $switch" - ac_success=yes - break - fi - done - fi - ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - if test x$ax_cxx_compile_cxx11_required = xtrue; then - if test x$ac_success = xno; then - as_fn_error $? "*** A compiler with support for C++11 language features is required." "$LINENO" 5 - fi - else - if test x$ac_success = xno; then - HAVE_CXX11=0 - { $as_echo "$as_me:${as_lineno-$LINENO}: No compiler with C++11 support was found" >&5 -$as_echo "$as_me: No compiler with C++11 support was found" >&6;} - else - HAVE_CXX11=1 - -$as_echo "#define HAVE_CXX11 1" >>confdefs.h - - fi - - - fi - - -# Set platform specific flags -if test "$(uname -s)" = "Darwin" -then - CXXFLAGS="-DCRYPTOPP_DISABLE_ASM -DAESNI ${CXXFLAGS}" - -elif test "$(uname -s)" = "Linux" -then - if test -n "$(grep aes /proc/cpuinfo)" - then - CXXFLAGS="-DAESNI ${CXXFLAGS}" - fi -else - # emtpy - true -fi - -# Checks for libraries. - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing s_sosemanukMulTables" >&5 -$as_echo_n "checking for library containing s_sosemanukMulTables... " >&6; } -if ${ac_cv_search_s_sosemanukMulTables+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_func_search_save_LIBS=$LIBS -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char s_sosemanukMulTables (); -int -main () -{ -return s_sosemanukMulTables (); - ; - return 0; -} -_ACEOF -for ac_lib in '' cryptopp; do - if test -z "$ac_lib"; then - ac_res="none required" - else - ac_res=-l$ac_lib - LIBS="-l$ac_lib $ac_func_search_save_LIBS" - fi - if ac_fn_c_try_link "$LINENO"; then : - ac_cv_search_s_sosemanukMulTables=$ac_res -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext - if ${ac_cv_search_s_sosemanukMulTables+:} false; then : - break -fi -done -if ${ac_cv_search_s_sosemanukMulTables+:} false; then : - -else - ac_cv_search_s_sosemanukMulTables=no -fi -rm conftest.$ac_ext -LIBS=$ac_func_search_save_LIBS -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_s_sosemanukMulTables" >&5 -$as_echo "$ac_cv_search_s_sosemanukMulTables" >&6; } -ac_res=$ac_cv_search_s_sosemanukMulTables -if test "$ac_res" != no; then : - test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" - -else - as_fn_error $? "Unable to find crypto++" "$LINENO" 5 -fi - - -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_boost_base.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_BOOST_BASE([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) -# -# DESCRIPTION -# -# Test for the Boost C++ libraries of a particular version (or newer) -# -# If no path to the installed boost library is given the macro searchs -# under /usr, /usr/local, /opt and /opt/local and evaluates the -# $BOOST_ROOT environment variable. Further documentation is available at -# . -# -# This macro calls: -# -# AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS) -# -# And sets: -# -# HAVE_BOOST -# -# LICENSE -# -# Copyright (c) 2008 Thomas Porschberg -# Copyright (c) 2009 Peter Adolphs -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 23 - - - -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_boost_date_time.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_BOOST_DATE_TIME -# -# DESCRIPTION -# -# Test for Date_Time library from the Boost C++ libraries. The macro -# requires a preceding call to AX_BOOST_BASE. Further documentation is -# available at . -# -# This macro calls: -# -# AC_SUBST(BOOST_DATE_TIME_LIB) -# -# And sets: -# -# HAVE_BOOST_DATE_TIME -# -# LICENSE -# -# Copyright (c) 2008 Thomas Porschberg -# Copyright (c) 2008 Michael Tindal -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 21 - - - -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_boost_filesystem.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_BOOST_FILESYSTEM -# -# DESCRIPTION -# -# Test for Filesystem library from the Boost C++ libraries. The macro -# requires a preceding call to AX_BOOST_BASE. Further documentation is -# available at . -# -# This macro calls: -# -# AC_SUBST(BOOST_FILESYSTEM_LIB) -# -# And sets: -# -# HAVE_BOOST_FILESYSTEM -# -# LICENSE -# -# Copyright (c) 2009 Thomas Porschberg -# Copyright (c) 2009 Michael Tindal -# Copyright (c) 2009 Roman Rybalko -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 26 - - - -# ============================================================================ -# http://www.gnu.org/software/autoconf-archive/ax_boost_program_options.html -# ============================================================================ -# -# SYNOPSIS -# -# AX_BOOST_PROGRAM_OPTIONS -# -# DESCRIPTION -# -# Test for program options library from the Boost C++ libraries. The macro -# requires a preceding call to AX_BOOST_BASE. Further documentation is -# available at . -# -# This macro calls: -# -# AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB) -# -# And sets: -# -# HAVE_BOOST_PROGRAM_OPTIONS -# -# LICENSE -# -# Copyright (c) 2009 Thomas Porschberg -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 22 - - - -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_boost_regex.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_BOOST_REGEX -# -# DESCRIPTION -# -# Test for Regex library from the Boost C++ libraries. The macro requires -# a preceding call to AX_BOOST_BASE. Further documentation is available at -# . -# -# This macro calls: -# -# AC_SUBST(BOOST_REGEX_LIB) -# -# And sets: -# -# HAVE_BOOST_REGEX -# -# LICENSE -# -# Copyright (c) 2008 Thomas Porschberg -# Copyright (c) 2008 Michael Tindal -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 22 - - - -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_boost_system.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_BOOST_SYSTEM -# -# DESCRIPTION -# -# Test for System library from the Boost C++ libraries. The macro requires -# a preceding call to AX_BOOST_BASE. Further documentation is available at -# . -# -# This macro calls: -# -# AC_SUBST(BOOST_SYSTEM_LIB) -# -# And sets: -# -# HAVE_BOOST_SYSTEM -# -# LICENSE -# -# Copyright (c) 2008 Thomas Porschberg -# Copyright (c) 2008 Michael Tindal -# Copyright (c) 2008 Daniel Casimiro -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 17 - - - -# Make sure we can run config.sub. -$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || - as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5 - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 -$as_echo_n "checking build system type... " >&6; } -if ${ac_cv_build+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_build_alias=$build_alias -test "x$ac_build_alias" = x && - ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` -test "x$ac_build_alias" = x && - as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 -ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || - as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5 - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 -$as_echo "$ac_cv_build" >&6; } -case $ac_cv_build in -*-*-*) ;; -*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; -esac -build=$ac_cv_build -ac_save_IFS=$IFS; IFS='-' -set x $ac_cv_build -shift -build_cpu=$1 -build_vendor=$2 -shift; shift -# Remember, the first character of IFS is used to create $*, -# except with old shells: -build_os=$* -IFS=$ac_save_IFS -case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac - - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 -$as_echo_n "checking host system type... " >&6; } -if ${ac_cv_host+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test "x$host_alias" = x; then - ac_cv_host=$ac_cv_build -else - ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || - as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5 -fi - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 -$as_echo "$ac_cv_host" >&6; } -case $ac_cv_host in -*-*-*) ;; -*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; -esac -host=$ac_cv_host -ac_save_IFS=$IFS; IFS='-' -set x $ac_cv_host -shift -host_cpu=$1 -host_vendor=$2 -shift; shift -# Remember, the first character of IFS is used to create $*, -# except with old shells: -host_os=$* -IFS=$ac_save_IFS -case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac - - - - -# Check whether --with-boost was given. -if test "${with_boost+set}" = set; then : - withval=$with_boost; - if test "$withval" = "no"; then - want_boost="no" - elif test "$withval" = "yes"; then - want_boost="yes" - ac_boost_path="" - else - want_boost="yes" - ac_boost_path="$withval" - fi - -else - want_boost="yes" -fi - - - - -# Check whether --with-boost-libdir was given. -if test "${with_boost_libdir+set}" = set; then : - withval=$with_boost_libdir; - if test -d "$withval" - then - ac_boost_lib_path="$withval" - else - as_fn_error $? "--with-boost-libdir expected directory name" "$LINENO" 5 - fi - -else - ac_boost_lib_path="" - -fi - - -if test "x$want_boost" = "xyes"; then - boost_lib_version_req=1.46 - boost_lib_version_req_shorten=`expr $boost_lib_version_req : '\([0-9]*\.[0-9]*\)'` - boost_lib_version_req_major=`expr $boost_lib_version_req : '\([0-9]*\)'` - boost_lib_version_req_minor=`expr $boost_lib_version_req : '[0-9]*\.\([0-9]*\)'` - boost_lib_version_req_sub_minor=`expr $boost_lib_version_req : '[0-9]*\.[0-9]*\.\([0-9]*\)'` - if test "x$boost_lib_version_req_sub_minor" = "x" ; then - boost_lib_version_req_sub_minor="0" - fi - WANT_BOOST_VERSION=`expr $boost_lib_version_req_major \* 100000 \+ $boost_lib_version_req_minor \* 100 \+ $boost_lib_version_req_sub_minor` - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for boostlib >= $boost_lib_version_req" >&5 -$as_echo_n "checking for boostlib >= $boost_lib_version_req... " >&6; } - succeeded=no - - libsubdirs="lib" - ax_arch=`uname -m` - case $ax_arch in - x86_64|ppc64|s390x|sparc64|aarch64) - libsubdirs="lib64 lib lib64" - ;; - esac - - - libsubdirs="lib/${host_cpu}-${host_os} $libsubdirs" - - case ${host_cpu} in - i?86) - libsubdirs="lib/i386-${host_os} $libsubdirs" - ;; - esac - - if test "$ac_boost_path" != ""; then - BOOST_CPPFLAGS="-I$ac_boost_path/include" - for ac_boost_path_tmp in $libsubdirs; do - if test -d "$ac_boost_path"/"$ac_boost_path_tmp" ; then - BOOST_LDFLAGS="-L$ac_boost_path/$ac_boost_path_tmp" - break - fi - done - elif test "$cross_compiling" != yes; then - for ac_boost_path_tmp in /usr /usr/local /opt /opt/local ; do - if test -d "$ac_boost_path_tmp/include/boost" && test -r "$ac_boost_path_tmp/include/boost"; then - for libsubdir in $libsubdirs ; do - if ls "$ac_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi - done - BOOST_LDFLAGS="-L$ac_boost_path_tmp/$libsubdir" - BOOST_CPPFLAGS="-I$ac_boost_path_tmp/include" - break; - fi - done - fi - - if test "$ac_boost_lib_path" != ""; then - BOOST_LDFLAGS="-L$ac_boost_lib_path" - fi - - CPPFLAGS_SAVED="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - - - ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu - - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - - #include - -int -main () -{ - - #if BOOST_VERSION >= $WANT_BOOST_VERSION - // Everything is okay - #else - # error Boost version is too old - #endif - - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } - succeeded=yes - found_system=yes - -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - - - - if test "x$succeeded" != "xyes"; then - _version=0 - if test "$ac_boost_path" != ""; then - if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then - for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do - _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` - V_CHECK=`expr $_version_tmp \> $_version` - if test "$V_CHECK" = "1" ; then - _version=$_version_tmp - fi - VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` - BOOST_CPPFLAGS="-I$ac_boost_path/include/boost-$VERSION_UNDERSCORE" - done - fi - else - if test "$cross_compiling" != yes; then - for ac_boost_path in /usr /usr/local /opt /opt/local ; do - if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then - for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do - _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` - V_CHECK=`expr $_version_tmp \> $_version` - if test "$V_CHECK" = "1" ; then - _version=$_version_tmp - best_path=$ac_boost_path - fi - done - fi - done - - VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` - BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE" - if test "$ac_boost_lib_path" = ""; then - for libsubdir in $libsubdirs ; do - if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi - done - BOOST_LDFLAGS="-L$best_path/$libsubdir" - fi - fi - - if test "x$BOOST_ROOT" != "x"; then - for libsubdir in $libsubdirs ; do - if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi - done - if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then - version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'` - stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'` - stage_version_shorten=`expr $stage_version : '\([0-9]*\.[0-9]*\)'` - V_CHECK=`expr $stage_version_shorten \>\= $_version` - if test "$V_CHECK" = "1" -a "$ac_boost_lib_path" = "" ; then - { $as_echo "$as_me:${as_lineno-$LINENO}: We will use a staged boost library from $BOOST_ROOT" >&5 -$as_echo "$as_me: We will use a staged boost library from $BOOST_ROOT" >&6;} - BOOST_CPPFLAGS="-I$BOOST_ROOT" - BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir" - fi - fi - fi - fi - - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - - ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu - - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - - #include - -int -main () -{ - - #if BOOST_VERSION >= $WANT_BOOST_VERSION - // Everything is okay - #else - # error Boost version is too old - #endif - - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } - succeeded=yes - found_system=yes - -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - fi - - if test "$succeeded" != "yes" ; then - if test "$_version" = "0" ; then - { $as_echo "$as_me:${as_lineno-$LINENO}: We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation." >&5 -$as_echo "$as_me: We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation." >&6;} - else - { $as_echo "$as_me:${as_lineno-$LINENO}: Your boost libraries seems to old (version $_version)." >&5 -$as_echo "$as_me: Your boost libraries seems to old (version $_version)." >&6;} - fi - # execute ACTION-IF-NOT-FOUND (if present): - : - else - - - -$as_echo "#define HAVE_BOOST /**/" >>confdefs.h - - # execute ACTION-IF-FOUND (if present): - : - fi - - CPPFLAGS="$CPPFLAGS_SAVED" - LDFLAGS="$LDFLAGS_SAVED" -fi - - - - -# Check whether --with-boost-date-time was given. -if test "${with_boost_date_time+set}" = set; then : - withval=$with_boost_date_time; - if test "$withval" = "no"; then - want_boost="no" - elif test "$withval" = "yes"; then - want_boost="yes" - ax_boost_user_date_time_lib="" - else - want_boost="yes" - ax_boost_user_date_time_lib="$withval" - fi - -else - want_boost="yes" - -fi - - - if test "x$want_boost" = "xyes"; then - - CPPFLAGS_SAVED="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the Boost::Date_Time library is available" >&5 -$as_echo_n "checking whether the Boost::Date_Time library is available... " >&6; } -if ${ax_cv_boost_date_time+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu - - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -int -main () -{ -using namespace boost::gregorian; date d(2002,Jan,10); - return 0; - - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - ax_cv_boost_date_time=yes -else - ax_cv_boost_date_time=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_boost_date_time" >&5 -$as_echo "$ax_cv_boost_date_time" >&6; } - if test "x$ax_cv_boost_date_time" = "xyes"; then - -$as_echo "#define HAVE_BOOST_DATE_TIME /**/" >>confdefs.h - - BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/[^\/]*//'` - if test "x$ax_boost_user_date_time_lib" = "x"; then - for libextension in `ls $BOOSTLIBDIR/libboost_date_time*.so* $BOOSTLIBDIR/libboost_date_time*.dylib* $BOOSTLIBDIR/libboost_date_time*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_date_time.*\)\.so.*$;\1;' -e 's;^lib\(boost_date_time.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_date_time.*\)\.a*$;\1;'` ; do - ax_lib=${libextension} - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 -$as_echo_n "checking for exit in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char exit (); -int -main () -{ -return exit (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_DATE_TIME_LIB="-l$ax_lib"; link_date_time="yes"; break -else - link_date_time="no" -fi - - done - if test "x$link_date_time" != "xyes"; then - for libextension in `ls $BOOSTLIBDIR/boost_date_time*.dll* $BOOSTLIBDIR/boost_date_time*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_date_time.*\)\.dll.*$;\1;' -e 's;^\(boost_date_time.*\)\.a.*$;\1;'` ; do - ax_lib=${libextension} - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 -$as_echo_n "checking for exit in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char exit (); -int -main () -{ -return exit (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_DATE_TIME_LIB="-l$ax_lib"; link_date_time="yes"; break -else - link_date_time="no" -fi - - done - fi - - else - for ax_lib in $ax_boost_user_date_time_lib boost_date_time-$ax_boost_user_date_time_lib; do - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_main" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -l$ax_lib" >&5 -$as_echo_n "checking for main in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - - -int -main () -{ -return main (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_DATE_TIME_LIB="-l$ax_lib"; link_date_time="yes"; break -else - link_date_time="no" -fi - - done - - fi - if test "x$ax_lib" = "x"; then - as_fn_error $? "Could not find a version of the library!" "$LINENO" 5 - fi - if test "x$link_date_time" != "xyes"; then - as_fn_error $? "Could not link against $ax_lib !" "$LINENO" 5 - fi - fi - - CPPFLAGS="$CPPFLAGS_SAVED" - LDFLAGS="$LDFLAGS_SAVED" - fi - - - -# Check whether --with-boost-filesystem was given. -if test "${with_boost_filesystem+set}" = set; then : - withval=$with_boost_filesystem; - if test "$withval" = "no"; then - want_boost="no" - elif test "$withval" = "yes"; then - want_boost="yes" - ax_boost_user_filesystem_lib="" - else - want_boost="yes" - ax_boost_user_filesystem_lib="$withval" - fi - -else - want_boost="yes" - -fi - - - if test "x$want_boost" = "xyes"; then - - CPPFLAGS_SAVED="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - - LIBS_SAVED=$LIBS - LIBS="$LIBS $BOOST_SYSTEM_LIB" - export LIBS - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the Boost::Filesystem library is available" >&5 -$as_echo_n "checking whether the Boost::Filesystem library is available... " >&6; } -if ${ax_cv_boost_filesystem+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu - - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -int -main () -{ -using namespace boost::filesystem; - path my_path( "foo/bar/data.txt" ); - return 0; - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - ax_cv_boost_filesystem=yes -else - ax_cv_boost_filesystem=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_boost_filesystem" >&5 -$as_echo "$ax_cv_boost_filesystem" >&6; } - if test "x$ax_cv_boost_filesystem" = "xyes"; then - -$as_echo "#define HAVE_BOOST_FILESYSTEM /**/" >>confdefs.h - - BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/[^\/]*//'` - if test "x$ax_boost_user_filesystem_lib" = "x"; then - for libextension in `ls -r $BOOSTLIBDIR/libboost_filesystem* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'` ; do - ax_lib=${libextension} - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 -$as_echo_n "checking for exit in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char exit (); -int -main () -{ -return exit (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_FILESYSTEM_LIB="-l$ax_lib"; link_filesystem="yes"; break -else - link_filesystem="no" -fi - - done - if test "x$link_filesystem" != "xyes"; then - for libextension in `ls -r $BOOSTLIBDIR/boost_filesystem* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\..*,,'` ; do - ax_lib=${libextension} - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 -$as_echo_n "checking for exit in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char exit (); -int -main () -{ -return exit (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_FILESYSTEM_LIB="-l$ax_lib"; link_filesystem="yes"; break -else - link_filesystem="no" -fi - - done - fi - else - for ax_lib in $ax_boost_user_filesystem_lib boost_filesystem-$ax_boost_user_filesystem_lib; do - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 -$as_echo_n "checking for exit in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char exit (); -int -main () -{ -return exit (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_FILESYSTEM_LIB="-l$ax_lib"; link_filesystem="yes"; break -else - link_filesystem="no" -fi - - done - - fi - if test "x$ax_lib" = "x"; then - as_fn_error $? "Could not find a version of the library!" "$LINENO" 5 - fi - if test "x$link_filesystem" != "xyes"; then - as_fn_error $? "Could not link against $ax_lib !" "$LINENO" 5 - fi - fi - - CPPFLAGS="$CPPFLAGS_SAVED" - LDFLAGS="$LDFLAGS_SAVED" - LIBS="$LIBS_SAVED" - fi - - - -# Check whether --with-boost-program-options was given. -if test "${with_boost_program_options+set}" = set; then : - withval=$with_boost_program_options; - if test "$withval" = "no"; then - want_boost="no" - elif test "$withval" = "yes"; then - want_boost="yes" - ax_boost_user_program_options_lib="" - else - want_boost="yes" - ax_boost_user_program_options_lib="$withval" - fi - -else - want_boost="yes" - -fi - - - if test "x$want_boost" = "xyes"; then - - export want_boost - CPPFLAGS_SAVED="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the Boost::Program_Options library is available" >&5 -$as_echo_n "checking whether the Boost::Program_Options library is available... " >&6; } -if ${ax_cv_boost_program_options+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu - - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include - -int -main () -{ -boost::program_options::options_description generic("Generic options"); - return 0; - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - ax_cv_boost_program_options=yes -else - ax_cv_boost_program_options=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_boost_program_options" >&5 -$as_echo "$ax_cv_boost_program_options" >&6; } - if test "$ax_cv_boost_program_options" = yes; then - -$as_echo "#define HAVE_BOOST_PROGRAM_OPTIONS /**/" >>confdefs.h - - BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/[^\/]*//'` - if test "x$ax_boost_user_program_options_lib" = "x"; then - for libextension in `ls $BOOSTLIBDIR/libboost_program_options*.so* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.so.*$;\1;'` `ls $BOOSTLIBDIR/libboost_program_options*.dylib* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.dylib.*$;\1;'` `ls $BOOSTLIBDIR/libboost_program_options*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.a.*$;\1;'` ; do - ax_lib=${libextension} - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 -$as_echo_n "checking for exit in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char exit (); -int -main () -{ -return exit (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_PROGRAM_OPTIONS_LIB="-l$ax_lib"; link_program_options="yes"; break -else - link_program_options="no" -fi - - done - if test "x$link_program_options" != "xyes"; then - for libextension in `ls $BOOSTLIBDIR/boost_program_options*.dll* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_program_options.*\)\.dll.*$;\1;'` `ls $BOOSTLIBDIR/boost_program_options*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_program_options.*\)\.a.*$;\1;'` ; do - ax_lib=${libextension} - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 -$as_echo_n "checking for exit in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char exit (); -int -main () -{ -return exit (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_PROGRAM_OPTIONS_LIB="-l$ax_lib"; link_program_options="yes"; break -else - link_program_options="no" -fi - - done - fi - else - for ax_lib in $ax_boost_user_program_options_lib boost_program_options-$ax_boost_user_program_options_lib; do - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_main" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -l$ax_lib" >&5 -$as_echo_n "checking for main in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - - -int -main () -{ -return main (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_PROGRAM_OPTIONS_LIB="-l$ax_lib"; link_program_options="yes"; break -else - link_program_options="no" -fi - - done - fi - if test "x$ax_lib" = "x"; then - as_fn_error $? "Could not find a version of the library!" "$LINENO" 5 - fi - if test "x$link_program_options" != "xyes"; then - as_fn_error $? "Could not link against $ax_lib !" "$LINENO" 5 - fi - fi - CPPFLAGS="$CPPFLAGS_SAVED" - LDFLAGS="$LDFLAGS_SAVED" - fi - - - -# Check whether --with-boost-regex was given. -if test "${with_boost_regex+set}" = set; then : - withval=$with_boost_regex; - if test "$withval" = "no"; then - want_boost="no" - elif test "$withval" = "yes"; then - want_boost="yes" - ax_boost_user_regex_lib="" - else - want_boost="yes" - ax_boost_user_regex_lib="$withval" - fi - -else - want_boost="yes" - -fi - - - if test "x$want_boost" = "xyes"; then - - CPPFLAGS_SAVED="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the Boost::Regex library is available" >&5 -$as_echo_n "checking whether the Boost::Regex library is available... " >&6; } -if ${ax_cv_boost_regex+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu - - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include - -int -main () -{ -boost::regex r(); return 0; - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - ax_cv_boost_regex=yes -else - ax_cv_boost_regex=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_boost_regex" >&5 -$as_echo "$ax_cv_boost_regex" >&6; } - if test "x$ax_cv_boost_regex" = "xyes"; then - -$as_echo "#define HAVE_BOOST_REGEX /**/" >>confdefs.h - - BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/[^\/]*//'` - if test "x$ax_boost_user_regex_lib" = "x"; then - for libextension in `ls $BOOSTLIBDIR/libboost_regex*.so* $BOOSTLIBDIR/libboost_regex*.dylib* $BOOSTLIBDIR/libboost_regex*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_regex.*\)\.so.*$;\1;' -e 's;^lib\(boost_regex.*\)\.dylib.*;\1;' -e 's;^lib\(boost_regex.*\)\.a.*$;\1;'` ; do - ax_lib=${libextension} - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 -$as_echo_n "checking for exit in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char exit (); -int -main () -{ -return exit (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_REGEX_LIB="-l$ax_lib"; link_regex="yes"; break -else - link_regex="no" -fi - - done - if test "x$link_regex" != "xyes"; then - for libextension in `ls $BOOSTLIBDIR/boost_regex*.dll* $BOOSTLIBDIR/boost_regex*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_regex.*\)\.dll.*$;\1;' -e 's;^\(boost_regex.*\)\.a.*$;\1;'` ; do - ax_lib=${libextension} - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 -$as_echo_n "checking for exit in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char exit (); -int -main () -{ -return exit (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_REGEX_LIB="-l$ax_lib"; link_regex="yes"; break -else - link_regex="no" -fi - - done - fi - - else - for ax_lib in $ax_boost_user_regex_lib boost_regex-$ax_boost_user_regex_lib; do - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_main" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -l$ax_lib" >&5 -$as_echo_n "checking for main in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - - -int -main () -{ -return main (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_REGEX_LIB="-l$ax_lib"; link_regex="yes"; break -else - link_regex="no" -fi - - done - fi - if test "x$ax_lib" = "x"; then - as_fn_error $? "Could not find a version of the Boost::Regex library!" "$LINENO" 5 - fi - if test "x$link_regex" != "xyes"; then - as_fn_error $? "Could not link against $ax_lib !" "$LINENO" 5 - fi - fi - - CPPFLAGS="$CPPFLAGS_SAVED" - LDFLAGS="$LDFLAGS_SAVED" - fi - - - -# Check whether --with-boost-system was given. -if test "${with_boost_system+set}" = set; then : - withval=$with_boost_system; - if test "$withval" = "no"; then - want_boost="no" - elif test "$withval" = "yes"; then - want_boost="yes" - ax_boost_user_system_lib="" - else - want_boost="yes" - ax_boost_user_system_lib="$withval" - fi - -else - want_boost="yes" - -fi - - - if test "x$want_boost" = "xyes"; then - - - CPPFLAGS_SAVED="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the Boost::System library is available" >&5 -$as_echo_n "checking whether the Boost::System library is available... " >&6; } -if ${ax_cv_boost_system+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu - - CXXFLAGS_SAVE=$CXXFLAGS - - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -int -main () -{ -boost::system::system_category - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - ax_cv_boost_system=yes -else - ax_cv_boost_system=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - CXXFLAGS=$CXXFLAGS_SAVE - ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_boost_system" >&5 -$as_echo "$ax_cv_boost_system" >&6; } - if test "x$ax_cv_boost_system" = "xyes"; then - - - -$as_echo "#define HAVE_BOOST_SYSTEM /**/" >>confdefs.h - - BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/[^\/]*//'` - - LDFLAGS_SAVE=$LDFLAGS - if test "x$ax_boost_user_system_lib" = "x"; then - for libextension in `ls -r $BOOSTLIBDIR/libboost_system* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'` ; do - ax_lib=${libextension} - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 -$as_echo_n "checking for exit in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char exit (); -int -main () -{ -return exit (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_SYSTEM_LIB="-l$ax_lib"; link_system="yes"; break -else - link_system="no" -fi - - done - if test "x$link_system" != "xyes"; then - for libextension in `ls -r $BOOSTLIBDIR/boost_system* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\..*,,'` ; do - ax_lib=${libextension} - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 -$as_echo_n "checking for exit in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char exit (); -int -main () -{ -return exit (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_SYSTEM_LIB="-l$ax_lib"; link_system="yes"; break -else - link_system="no" -fi - - done - fi - - else - for ax_lib in $ax_boost_user_system_lib boost_system-$ax_boost_user_system_lib; do - as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 -$as_echo_n "checking for exit in -l$ax_lib... " >&6; } -if eval \${$as_ac_Lib+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-l$ax_lib $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char exit (); -int -main () -{ -return exit (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - eval "$as_ac_Lib=yes" -else - eval "$as_ac_Lib=no" -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -eval ac_res=\$$as_ac_Lib - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : - BOOST_SYSTEM_LIB="-l$ax_lib"; link_system="yes"; break -else - link_system="no" -fi - - done - - fi - if test "x$ax_lib" = "x"; then - as_fn_error $? "Could not find a version of the library!" "$LINENO" 5 - fi - if test "x$link_system" = "xno"; then - as_fn_error $? "Could not link against $ax_lib !" "$LINENO" 5 - fi - fi - - CPPFLAGS="$CPPFLAGS_SAVED" - LDFLAGS="$LDFLAGS_SAVED" - fi - - -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_pthread.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) -# -# DESCRIPTION -# -# This macro figures out how to build C programs using POSIX threads. It -# sets the PTHREAD_LIBS output variable to the threads library and linker -# flags, and the PTHREAD_CFLAGS output variable to any special C compiler -# flags that are needed. (The user can also force certain compiler -# flags/libs to be tested by setting these environment variables.) -# -# Also sets PTHREAD_CC to any special C compiler that is needed for -# multi-threaded programs (defaults to the value of CC otherwise). (This -# is necessary on AIX to use the special cc_r compiler alias.) -# -# NOTE: You are assumed to not only compile your program with these flags, -# but also link it with them as well. e.g. you should link with -# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS -# -# If you are only building threads programs, you may wish to use these -# variables in your default LIBS, CFLAGS, and CC: -# -# LIBS="$PTHREAD_LIBS $LIBS" -# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" -# CC="$PTHREAD_CC" -# -# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant -# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name -# (e.g. PTHREAD_CREATE_UNDETACHED on AIX). -# -# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the -# PTHREAD_PRIO_INHERIT symbol is defined when compiling with -# PTHREAD_CFLAGS. -# -# ACTION-IF-FOUND is a list of shell commands to run if a threads library -# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it -# is not found. If ACTION-IF-FOUND is not specified, the default action -# will define HAVE_PTHREAD. -# -# Please let the authors know if this macro fails on any platform, or if -# you have any other suggestions or comments. This macro was based on work -# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help -# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by -# Alejandro Forero Cuervo to the autoconf macro repository. We are also -# grateful for the helpful feedback of numerous users. -# -# Updated for Autoconf 2.68 by Daniel Richard G. -# -# LICENSE -# -# Copyright (c) 2008 Steven G. Johnson -# Copyright (c) 2011 Daniel Richard G. -# -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation, either version 3 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see . -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. - -#serial 21 - -# This is what autoupdate's m4 run will expand. It fires -# the warning (with _au_warn_XXX), outputs it into the -# updated configure.ac (with AC_DIAGNOSE), and then outputs -# the replacement expansion. - - -# This is an auxiliary macro that is also run when -# autoupdate runs m4. It simply calls m4_warning, but -# we need a wrapper so that each warning is emitted only -# once. We break the quoting in m4_warning's argument in -# order to expand this macro's arguments, not AU_DEFUN's. - - -# Finally, this is the expansion that is picked up by -# autoconf. It tells the user to run autoupdate, and -# then outputs the replacement expansion. We do not care -# about autoupdate's warning because that contains -# information on what to do *after* running autoupdate. - - - - -ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - -ax_pthread_ok=no - -# We used to check for pthread.h first, but this fails if pthread.h -# requires special compiler flags (e.g. on True64 or Sequent). -# It gets checked for in the link test anyway. - -# First of all, check if the user has set any of the PTHREAD_LIBS, -# etcetera environment variables, and if threads linking works using -# them: -if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then - save_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS $PTHREAD_CFLAGS" - save_LIBS="$LIBS" - LIBS="$PTHREAD_LIBS $LIBS" - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS" >&5 -$as_echo_n "checking for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS... " >&6; } - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char pthread_join (); -int -main () -{ -return pthread_join (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - ax_pthread_ok=yes -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5 -$as_echo "$ax_pthread_ok" >&6; } - if test x"$ax_pthread_ok" = xno; then - PTHREAD_LIBS="" - PTHREAD_CFLAGS="" - fi - LIBS="$save_LIBS" - CFLAGS="$save_CFLAGS" -fi - -# We must check for the threads library under a number of different -# names; the ordering is very important because some systems -# (e.g. DEC) have both -lpthread and -lpthreads, where one of the -# libraries is broken (non-POSIX). - -# Create a list of thread flags to try. Items starting with a "-" are -# C compiler flags, and other items are library names, except for "none" -# which indicates that we try without any flags at all, and "pthread-config" -# which is a program returning the flags for the Pth emulation library. - -ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" - -# The ordering *is* (sometimes) important. Some notes on the -# individual items follow: - -# pthreads: AIX (must check this before -lpthread) -# none: in case threads are in libc; should be tried before -Kthread and -# other compiler flags to prevent continual compiler warnings -# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) -# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) -# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) -# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) -# -pthreads: Solaris/gcc -# -mthreads: Mingw32/gcc, Lynx/gcc -# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it -# doesn't hurt to check since this sometimes defines pthreads too; -# also defines -D_REENTRANT) -# ... -mt is also the pthreads flag for HP/aCC -# pthread: Linux, etcetera -# --thread-safe: KAI C++ -# pthread-config: use pthread-config program (for GNU Pth library) - -case ${host_os} in - solaris*) - - # On Solaris (at least, for some versions), libc contains stubbed - # (non-functional) versions of the pthreads routines, so link-based - # tests will erroneously succeed. (We need to link with -pthreads/-mt/ - # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather - # a function called by this macro, so we could check for that, but - # who knows whether they'll stub that too in a future libc.) So, - # we'll just look for -pthreads and -lpthread first: - - ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" - ;; - - darwin*) - ax_pthread_flags="-pthread $ax_pthread_flags" - ;; -esac - -# Clang doesn't consider unrecognized options an error unless we specify -# -Werror. We throw in some extra Clang-specific options to ensure that -# this doesn't happen for GCC, which also accepts -Werror. - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler needs -Werror to reject unknown flags" >&5 -$as_echo_n "checking if compiler needs -Werror to reject unknown flags... " >&6; } -save_CFLAGS="$CFLAGS" -ax_pthread_extra_flags="-Werror" -CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -int foo(void); -int -main () -{ -foo() - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } -else - ax_pthread_extra_flags= - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -CFLAGS="$save_CFLAGS" - -if test x"$ax_pthread_ok" = xno; then -for flag in $ax_pthread_flags; do - - case $flag in - none) - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthreads work without any flags" >&5 -$as_echo_n "checking whether pthreads work without any flags... " >&6; } - ;; - - -*) - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthreads work with $flag" >&5 -$as_echo_n "checking whether pthreads work with $flag... " >&6; } - PTHREAD_CFLAGS="$flag" - ;; - - pthread-config) - # Extract the first word of "pthread-config", so it can be a program name with args. -set dummy pthread-config; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_ax_pthread_config+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$ax_pthread_config"; then - ac_cv_prog_ax_pthread_config="$ax_pthread_config" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_ax_pthread_config="yes" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - - test -z "$ac_cv_prog_ax_pthread_config" && ac_cv_prog_ax_pthread_config="no" -fi -fi -ax_pthread_config=$ac_cv_prog_ax_pthread_config -if test -n "$ax_pthread_config"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_config" >&5 -$as_echo "$ax_pthread_config" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - - if test x"$ax_pthread_config" = xno; then continue; fi - PTHREAD_CFLAGS="`pthread-config --cflags`" - PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" - ;; - - *) - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the pthreads library -l$flag" >&5 -$as_echo_n "checking for the pthreads library -l$flag... " >&6; } - PTHREAD_LIBS="-l$flag" - ;; - esac - - save_LIBS="$LIBS" - save_CFLAGS="$CFLAGS" - LIBS="$PTHREAD_LIBS $LIBS" - CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags" - - # Check for various functions. We must include pthread.h, - # since some functions may be macros. (On the Sequent, we - # need a special flag -Kthread to make this header compile.) - # We check for pthread_join because it is in -lpthread on IRIX - # while pthread_create is in libc. We check for pthread_attr_init - # due to DEC craziness with -lpthreads. We check for - # pthread_cleanup_push because it is one of the few pthread - # functions on Solaris that doesn't have a non-functional libc stub. - # We try pthread_create on general principles. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include - static void routine(void *a) { a = 0; } - static void *start_routine(void *a) { return a; } -int -main () -{ -pthread_t th; pthread_attr_t attr; - pthread_create(&th, 0, start_routine, 0); - pthread_join(th, 0); - pthread_attr_init(&attr); - pthread_cleanup_push(routine, 0); - pthread_cleanup_pop(0) /* ; */ - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - ax_pthread_ok=yes -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext - - LIBS="$save_LIBS" - CFLAGS="$save_CFLAGS" - - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5 -$as_echo "$ax_pthread_ok" >&6; } - if test "x$ax_pthread_ok" = xyes; then - break; - fi - - PTHREAD_LIBS="" - PTHREAD_CFLAGS="" -done -fi - -# Various other checks: -if test "x$ax_pthread_ok" = xyes; then - save_LIBS="$LIBS" - LIBS="$PTHREAD_LIBS $LIBS" - save_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS $PTHREAD_CFLAGS" - - # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for joinable pthread attribute" >&5 -$as_echo_n "checking for joinable pthread attribute... " >&6; } - attr_name=unknown - for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -int -main () -{ -int attr = $attr; return attr /* ; */ - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - attr_name=$attr; break -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext - done - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $attr_name" >&5 -$as_echo "$attr_name" >&6; } - if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then - -cat >>confdefs.h <<_ACEOF -#define PTHREAD_CREATE_JOINABLE $attr_name -_ACEOF - - fi - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking if more special flags are required for pthreads" >&5 -$as_echo_n "checking if more special flags are required for pthreads... " >&6; } - flag=no - case ${host_os} in - aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; - osf* | hpux*) flag="-D_REENTRANT";; - solaris*) - if test "$GCC" = "yes"; then - flag="-D_REENTRANT" - else - # TODO: What about Clang on Solaris? - flag="-mt -D_REENTRANT" - fi - ;; - esac - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $flag" >&5 -$as_echo "$flag" >&6; } - if test "x$flag" != xno; then - PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" - fi - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PTHREAD_PRIO_INHERIT" >&5 -$as_echo_n "checking for PTHREAD_PRIO_INHERIT... " >&6; } -if ${ax_cv_PTHREAD_PRIO_INHERIT+:} false; then : - $as_echo_n "(cached) " >&6 -else - - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -int -main () -{ -int i = PTHREAD_PRIO_INHERIT; - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - ax_cv_PTHREAD_PRIO_INHERIT=yes -else - ax_cv_PTHREAD_PRIO_INHERIT=no -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_PRIO_INHERIT" >&5 -$as_echo "$ax_cv_PTHREAD_PRIO_INHERIT" >&6; } - if test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"; then : - -$as_echo "#define HAVE_PTHREAD_PRIO_INHERIT 1" >>confdefs.h - -fi - - LIBS="$save_LIBS" - CFLAGS="$save_CFLAGS" - - # More AIX lossage: compile with *_r variant - if test "x$GCC" != xyes; then - case $host_os in - aix*) - case "x/$CC" in #( - x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6) : - #handle absolute path differently from PATH based program lookup - case "x$CC" in #( - x/*) : - if as_fn_executable_p ${CC}_r; then : - PTHREAD_CC="${CC}_r" -fi ;; #( - *) : - for ac_prog in ${CC}_r -do - # Extract the first word of "$ac_prog", so it can be a program name with args. -set dummy $ac_prog; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_PTHREAD_CC+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$PTHREAD_CC"; then - ac_cv_prog_PTHREAD_CC="$PTHREAD_CC" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_PTHREAD_CC="$ac_prog" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -PTHREAD_CC=$ac_cv_prog_PTHREAD_CC -if test -n "$PTHREAD_CC"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PTHREAD_CC" >&5 -$as_echo "$PTHREAD_CC" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - - test -n "$PTHREAD_CC" && break -done -test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" - ;; -esac ;; #( - *) : - ;; -esac - ;; - esac - fi -fi - -test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" - - - - - -# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: -if test x"$ax_pthread_ok" = xyes; then - -$as_echo "#define HAVE_PTHREAD 1" >>confdefs.h - - : -else - ax_pthread_ok=no - -fi -ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - - -# Checks for header files. -ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 -$as_echo_n "checking how to run the C preprocessor... " >&6; } -# On Suns, sometimes $CPP names a directory. -if test -n "$CPP" && test -d "$CPP"; then - CPP= -fi -if test -z "$CPP"; then - if ${ac_cv_prog_CPP+:} false; then : - $as_echo_n "(cached) " >&6 -else - # Double quotes because CPP needs to be expanded - for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" - do - ac_preproc_ok=false -for ac_c_preproc_warn_flag in '' yes -do - # Use a header file that comes with gcc, so configuring glibc - # with a fresh cross-compiler works. - # Prefer to if __STDC__ is defined, since - # exists even on freestanding compilers. - # On the NeXT, cc -E runs the code through the compiler's parser, - # not just through cpp. "Syntax error" is here to catch this case. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#ifdef __STDC__ -# include -#else -# include -#endif - Syntax error -_ACEOF -if ac_fn_c_try_cpp "$LINENO"; then : - -else - # Broken: fails on valid input. -continue -fi -rm -f conftest.err conftest.i conftest.$ac_ext - - # OK, works on sane cases. Now check whether nonexistent headers - # can be detected and how. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -_ACEOF -if ac_fn_c_try_cpp "$LINENO"; then : - # Broken: success on invalid input. -continue -else - # Passes both tests. -ac_preproc_ok=: -break -fi -rm -f conftest.err conftest.i conftest.$ac_ext - -done -# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. -rm -f conftest.i conftest.err conftest.$ac_ext -if $ac_preproc_ok; then : - break -fi - - done - ac_cv_prog_CPP=$CPP - -fi - CPP=$ac_cv_prog_CPP -else - ac_cv_prog_CPP=$CPP -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 -$as_echo "$CPP" >&6; } -ac_preproc_ok=false -for ac_c_preproc_warn_flag in '' yes -do - # Use a header file that comes with gcc, so configuring glibc - # with a fresh cross-compiler works. - # Prefer to if __STDC__ is defined, since - # exists even on freestanding compilers. - # On the NeXT, cc -E runs the code through the compiler's parser, - # not just through cpp. "Syntax error" is here to catch this case. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#ifdef __STDC__ -# include -#else -# include -#endif - Syntax error -_ACEOF -if ac_fn_c_try_cpp "$LINENO"; then : - -else - # Broken: fails on valid input. -continue -fi -rm -f conftest.err conftest.i conftest.$ac_ext - - # OK, works on sane cases. Now check whether nonexistent headers - # can be detected and how. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -_ACEOF -if ac_fn_c_try_cpp "$LINENO"; then : - # Broken: success on invalid input. -continue -else - # Passes both tests. -ac_preproc_ok=: -break -fi -rm -f conftest.err conftest.i conftest.$ac_ext - -done -# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. -rm -f conftest.i conftest.err conftest.$ac_ext -if $ac_preproc_ok; then : - -else - { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -as_fn_error $? "C preprocessor \"$CPP\" fails sanity check -See \`config.log' for more details" "$LINENO" 5; } -fi - -ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 -$as_echo_n "checking for grep that handles long lines and -e... " >&6; } -if ${ac_cv_path_GREP+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -z "$GREP"; then - ac_path_GREP_found=false - # Loop through the user's path and test for each of PROGNAME-LIST - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_prog in grep ggrep; do - for ac_exec_ext in '' $ac_executable_extensions; do - ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" - as_fn_executable_p "$ac_path_GREP" || continue -# Check for GNU ac_path_GREP and select it if it is found. - # Check for GNU $ac_path_GREP -case `"$ac_path_GREP" --version 2>&1` in -*GNU*) - ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; -*) - ac_count=0 - $as_echo_n 0123456789 >"conftest.in" - while : - do - cat "conftest.in" "conftest.in" >"conftest.tmp" - mv "conftest.tmp" "conftest.in" - cp "conftest.in" "conftest.nl" - $as_echo 'GREP' >> "conftest.nl" - "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break - diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break - as_fn_arith $ac_count + 1 && ac_count=$as_val - if test $ac_count -gt ${ac_path_GREP_max-0}; then - # Best one so far, save it but keep looking for a better one - ac_cv_path_GREP="$ac_path_GREP" - ac_path_GREP_max=$ac_count - fi - # 10*(2^10) chars as input seems more than enough - test $ac_count -gt 10 && break - done - rm -f conftest.in conftest.tmp conftest.nl conftest.out;; -esac - - $ac_path_GREP_found && break 3 - done - done - done -IFS=$as_save_IFS - if test -z "$ac_cv_path_GREP"; then - as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 - fi -else - ac_cv_path_GREP=$GREP -fi - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 -$as_echo "$ac_cv_path_GREP" >&6; } - GREP="$ac_cv_path_GREP" - - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 -$as_echo_n "checking for egrep... " >&6; } -if ${ac_cv_path_EGREP+:} false; then : - $as_echo_n "(cached) " >&6 -else - if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 - then ac_cv_path_EGREP="$GREP -E" - else - if test -z "$EGREP"; then - ac_path_EGREP_found=false - # Loop through the user's path and test for each of PROGNAME-LIST - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_prog in egrep; do - for ac_exec_ext in '' $ac_executable_extensions; do - ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" - as_fn_executable_p "$ac_path_EGREP" || continue -# Check for GNU ac_path_EGREP and select it if it is found. - # Check for GNU $ac_path_EGREP -case `"$ac_path_EGREP" --version 2>&1` in -*GNU*) - ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; -*) - ac_count=0 - $as_echo_n 0123456789 >"conftest.in" - while : - do - cat "conftest.in" "conftest.in" >"conftest.tmp" - mv "conftest.tmp" "conftest.in" - cp "conftest.in" "conftest.nl" - $as_echo 'EGREP' >> "conftest.nl" - "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break - diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break - as_fn_arith $ac_count + 1 && ac_count=$as_val - if test $ac_count -gt ${ac_path_EGREP_max-0}; then - # Best one so far, save it but keep looking for a better one - ac_cv_path_EGREP="$ac_path_EGREP" - ac_path_EGREP_max=$ac_count - fi - # 10*(2^10) chars as input seems more than enough - test $ac_count -gt 10 && break - done - rm -f conftest.in conftest.tmp conftest.nl conftest.out;; -esac - - $ac_path_EGREP_found && break 3 - done - done - done -IFS=$as_save_IFS - if test -z "$ac_cv_path_EGREP"; then - as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 - fi -else - ac_cv_path_EGREP=$EGREP -fi - - fi -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 -$as_echo "$ac_cv_path_EGREP" >&6; } - EGREP="$ac_cv_path_EGREP" - - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 -$as_echo_n "checking for ANSI C header files... " >&6; } -if ${ac_cv_header_stdc+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -#include -#include -#include - -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - ac_cv_header_stdc=yes -else - ac_cv_header_stdc=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - -if test $ac_cv_header_stdc = yes; then - # SunOS 4.x string.h does not declare mem*, contrary to ANSI. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include - -_ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "memchr" >/dev/null 2>&1; then : - -else - ac_cv_header_stdc=no -fi -rm -f conftest* - -fi - -if test $ac_cv_header_stdc = yes; then - # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include - -_ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "free" >/dev/null 2>&1; then : - -else - ac_cv_header_stdc=no -fi -rm -f conftest* - -fi - -if test $ac_cv_header_stdc = yes; then - # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. - if test "$cross_compiling" = yes; then : - : -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -#include -#if ((' ' & 0x0FF) == 0x020) -# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') -# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) -#else -# define ISLOWER(c) \ - (('a' <= (c) && (c) <= 'i') \ - || ('j' <= (c) && (c) <= 'r') \ - || ('s' <= (c) && (c) <= 'z')) -# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) -#endif - -#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) -int -main () -{ - int i; - for (i = 0; i < 256; i++) - if (XOR (islower (i), ISLOWER (i)) - || toupper (i) != TOUPPER (i)) - return 2; - return 0; -} -_ACEOF -if ac_fn_c_try_run "$LINENO"; then : - -else - ac_cv_header_stdc=no -fi -rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ - conftest.$ac_objext conftest.beam conftest.$ac_ext -fi - -fi -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 -$as_echo "$ac_cv_header_stdc" >&6; } -if test $ac_cv_header_stdc = yes; then - -$as_echo "#define STDC_HEADERS 1" >>confdefs.h - -fi - -# On IRIX 5.3, sys/types and inttypes.h are conflicting. -for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ - inttypes.h stdint.h unistd.h -do : - as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` -ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default -" -if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : - cat >>confdefs.h <<_ACEOF -#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 -_ACEOF - -fi - -done - - -for ac_header in fcntl.h inttypes.h stdlib.h string.h unistd.h -do : - as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` -ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" -if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : - cat >>confdefs.h <<_ACEOF -#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 -_ACEOF - -fi - -done - - -# Checks for typedefs, structures, and compiler characteristics. -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for stdbool.h that conforms to C99" >&5 -$as_echo_n "checking for stdbool.h that conforms to C99... " >&6; } -if ${ac_cv_header_stdbool_h+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - - #include - #ifndef bool - "error: bool is not defined" - #endif - #ifndef false - "error: false is not defined" - #endif - #if false - "error: false is not 0" - #endif - #ifndef true - "error: true is not defined" - #endif - #if true != 1 - "error: true is not 1" - #endif - #ifndef __bool_true_false_are_defined - "error: __bool_true_false_are_defined is not defined" - #endif - - struct s { _Bool s: 1; _Bool t; } s; - - char a[true == 1 ? 1 : -1]; - char b[false == 0 ? 1 : -1]; - char c[__bool_true_false_are_defined == 1 ? 1 : -1]; - char d[(bool) 0.5 == true ? 1 : -1]; - /* See body of main program for 'e'. */ - char f[(_Bool) 0.0 == false ? 1 : -1]; - char g[true]; - char h[sizeof (_Bool)]; - char i[sizeof s.t]; - enum { j = false, k = true, l = false * true, m = true * 256 }; - /* The following fails for - HP aC++/ANSI C B3910B A.05.55 [Dec 04 2003]. */ - _Bool n[m]; - char o[sizeof n == m * sizeof n[0] ? 1 : -1]; - char p[-1 - (_Bool) 0 < 0 && -1 - (bool) 0 < 0 ? 1 : -1]; - /* Catch a bug in an HP-UX C compiler. See - http://gcc.gnu.org/ml/gcc-patches/2003-12/msg02303.html - http://lists.gnu.org/archive/html/bug-coreutils/2005-11/msg00161.html - */ - _Bool q = true; - _Bool *pq = &q; - -int -main () -{ - - bool e = &s; - *pq |= q; - *pq |= ! q; - /* Refer to every declared value, to avoid compiler optimizations. */ - return (!a + !b + !c + !d + !e + !f + !g + !h + !i + !!j + !k + !!l - + !m + !n + !o + !p + !q + !pq); - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - ac_cv_header_stdbool_h=yes -else - ac_cv_header_stdbool_h=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdbool_h" >&5 -$as_echo "$ac_cv_header_stdbool_h" >&6; } - ac_fn_c_check_type "$LINENO" "_Bool" "ac_cv_type__Bool" "$ac_includes_default" -if test "x$ac_cv_type__Bool" = xyes; then : - -cat >>confdefs.h <<_ACEOF -#define HAVE__BOOL 1 -_ACEOF - - -fi - - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for inline" >&5 -$as_echo_n "checking for inline... " >&6; } -if ${ac_cv_c_inline+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_cv_c_inline=no -for ac_kw in inline __inline__ __inline; do - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#ifndef __cplusplus -typedef int foo_t; -static $ac_kw foo_t static_foo () {return 0; } -$ac_kw foo_t foo () {return 0; } -#endif - -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - ac_cv_c_inline=$ac_kw -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - test "$ac_cv_c_inline" != no && break -done - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_inline" >&5 -$as_echo "$ac_cv_c_inline" >&6; } - -case $ac_cv_c_inline in - inline | yes) ;; - *) - case $ac_cv_c_inline in - no) ac_val=;; - *) ac_val=$ac_cv_c_inline;; - esac - cat >>confdefs.h <<_ACEOF -#ifndef __cplusplus -#define inline $ac_val -#endif -_ACEOF - ;; -esac - -ac_fn_c_find_intX_t "$LINENO" "32" "ac_cv_c_int32_t" -case $ac_cv_c_int32_t in #( - no|yes) ;; #( - *) - -cat >>confdefs.h <<_ACEOF -#define int32_t $ac_cv_c_int32_t -_ACEOF -;; -esac - -ac_fn_c_check_type "$LINENO" "pid_t" "ac_cv_type_pid_t" "$ac_includes_default" -if test "x$ac_cv_type_pid_t" = xyes; then : - -else - -cat >>confdefs.h <<_ACEOF -#define pid_t int -_ACEOF - -fi - -ac_fn_c_check_type "$LINENO" "size_t" "ac_cv_type_size_t" "$ac_includes_default" -if test "x$ac_cv_type_size_t" = xyes; then : - -else - -cat >>confdefs.h <<_ACEOF -#define size_t unsigned int -_ACEOF - -fi - -ac_fn_c_find_uintX_t "$LINENO" "16" "ac_cv_c_uint16_t" -case $ac_cv_c_uint16_t in #( - no|yes) ;; #( - *) - - -cat >>confdefs.h <<_ACEOF -#define uint16_t $ac_cv_c_uint16_t -_ACEOF -;; - esac - -ac_fn_c_find_uintX_t "$LINENO" "32" "ac_cv_c_uint32_t" -case $ac_cv_c_uint32_t in #( - no|yes) ;; #( - *) - -$as_echo "#define _UINT32_T 1" >>confdefs.h - - -cat >>confdefs.h <<_ACEOF -#define uint32_t $ac_cv_c_uint32_t -_ACEOF -;; - esac - -ac_fn_c_find_uintX_t "$LINENO" "64" "ac_cv_c_uint64_t" -case $ac_cv_c_uint64_t in #( - no|yes) ;; #( - *) - -$as_echo "#define _UINT64_T 1" >>confdefs.h - - -cat >>confdefs.h <<_ACEOF -#define uint64_t $ac_cv_c_uint64_t -_ACEOF -;; - esac - -ac_fn_c_find_uintX_t "$LINENO" "8" "ac_cv_c_uint8_t" -case $ac_cv_c_uint8_t in #( - no|yes) ;; #( - *) - -$as_echo "#define _UINT8_T 1" >>confdefs.h - - -cat >>confdefs.h <<_ACEOF -#define uint8_t $ac_cv_c_uint8_t -_ACEOF -;; - esac - -ac_fn_c_check_type "$LINENO" "ptrdiff_t" "ac_cv_type_ptrdiff_t" "$ac_includes_default" -if test "x$ac_cv_type_ptrdiff_t" = xyes; then : - -cat >>confdefs.h <<_ACEOF -#define HAVE_PTRDIFF_T 1 -_ACEOF - - -fi - - -# Checks for library functions. -for ac_header in vfork.h -do : - ac_fn_c_check_header_mongrel "$LINENO" "vfork.h" "ac_cv_header_vfork_h" "$ac_includes_default" -if test "x$ac_cv_header_vfork_h" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE_VFORK_H 1 -_ACEOF - -fi - -done - -for ac_func in fork vfork -do : - as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` -ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" -if eval test \"x\$"$as_ac_var"\" = x"yes"; then : - cat >>confdefs.h <<_ACEOF -#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 -_ACEOF - -fi -done - -if test "x$ac_cv_func_fork" = xyes; then - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for working fork" >&5 -$as_echo_n "checking for working fork... " >&6; } -if ${ac_cv_func_fork_works+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test "$cross_compiling" = yes; then : - ac_cv_func_fork_works=cross -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$ac_includes_default -int -main () -{ - - /* By Ruediger Kuhlmann. */ - return fork () < 0; - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_run "$LINENO"; then : - ac_cv_func_fork_works=yes -else - ac_cv_func_fork_works=no -fi -rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ - conftest.$ac_objext conftest.beam conftest.$ac_ext -fi - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_fork_works" >&5 -$as_echo "$ac_cv_func_fork_works" >&6; } - -else - ac_cv_func_fork_works=$ac_cv_func_fork -fi -if test "x$ac_cv_func_fork_works" = xcross; then - case $host in - *-*-amigaos* | *-*-msdosdjgpp*) - # Override, as these systems have only a dummy fork() stub - ac_cv_func_fork_works=no - ;; - *) - ac_cv_func_fork_works=yes - ;; - esac - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: result $ac_cv_func_fork_works guessed because of cross compilation" >&5 -$as_echo "$as_me: WARNING: result $ac_cv_func_fork_works guessed because of cross compilation" >&2;} -fi -ac_cv_func_vfork_works=$ac_cv_func_vfork -if test "x$ac_cv_func_vfork" = xyes; then - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for working vfork" >&5 -$as_echo_n "checking for working vfork... " >&6; } -if ${ac_cv_func_vfork_works+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test "$cross_compiling" = yes; then : - ac_cv_func_vfork_works=cross -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -/* Thanks to Paul Eggert for this test. */ -$ac_includes_default -#include -#ifdef HAVE_VFORK_H -# include -#endif -/* On some sparc systems, changes by the child to local and incoming - argument registers are propagated back to the parent. The compiler - is told about this with #include , but some compilers - (e.g. gcc -O) don't grok . Test for this by using a - static variable whose address is put into a register that is - clobbered by the vfork. */ -static void -#ifdef __cplusplus -sparc_address_test (int arg) -# else -sparc_address_test (arg) int arg; -#endif -{ - static pid_t child; - if (!child) { - child = vfork (); - if (child < 0) { - perror ("vfork"); - _exit(2); - } - if (!child) { - arg = getpid(); - write(-1, "", 0); - _exit (arg); - } - } -} - -int -main () -{ - pid_t parent = getpid (); - pid_t child; - - sparc_address_test (0); - - child = vfork (); - - if (child == 0) { - /* Here is another test for sparc vfork register problems. This - test uses lots of local variables, at least as many local - variables as main has allocated so far including compiler - temporaries. 4 locals are enough for gcc 1.40.3 on a Solaris - 4.1.3 sparc, but we use 8 to be safe. A buggy compiler should - reuse the register of parent for one of the local variables, - since it will think that parent can't possibly be used any more - in this routine. Assigning to the local variable will thus - munge parent in the parent process. */ - pid_t - p = getpid(), p1 = getpid(), p2 = getpid(), p3 = getpid(), - p4 = getpid(), p5 = getpid(), p6 = getpid(), p7 = getpid(); - /* Convince the compiler that p..p7 are live; otherwise, it might - use the same hardware register for all 8 local variables. */ - if (p != p1 || p != p2 || p != p3 || p != p4 - || p != p5 || p != p6 || p != p7) - _exit(1); - - /* On some systems (e.g. IRIX 3.3), vfork doesn't separate parent - from child file descriptors. If the child closes a descriptor - before it execs or exits, this munges the parent's descriptor - as well. Test for this by closing stdout in the child. */ - _exit(close(fileno(stdout)) != 0); - } else { - int status; - struct stat st; - - while (wait(&status) != child) - ; - return ( - /* Was there some problem with vforking? */ - child < 0 - - /* Did the child fail? (This shouldn't happen.) */ - || status - - /* Did the vfork/compiler bug occur? */ - || parent != getpid() - - /* Did the file descriptor bug occur? */ - || fstat(fileno(stdout), &st) != 0 - ); - } -} -_ACEOF -if ac_fn_c_try_run "$LINENO"; then : - ac_cv_func_vfork_works=yes -else - ac_cv_func_vfork_works=no -fi -rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ - conftest.$ac_objext conftest.beam conftest.$ac_ext -fi - -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_vfork_works" >&5 -$as_echo "$ac_cv_func_vfork_works" >&6; } - -fi; -if test "x$ac_cv_func_fork_works" = xcross; then - ac_cv_func_vfork_works=$ac_cv_func_vfork - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: result $ac_cv_func_vfork_works guessed because of cross compilation" >&5 -$as_echo "$as_me: WARNING: result $ac_cv_func_vfork_works guessed because of cross compilation" >&2;} -fi - -if test "x$ac_cv_func_vfork_works" = xyes; then - -$as_echo "#define HAVE_WORKING_VFORK 1" >>confdefs.h - -else - -$as_echo "#define vfork fork" >>confdefs.h - -fi -if test "x$ac_cv_func_fork_works" = xyes; then - -$as_echo "#define HAVE_WORKING_FORK 1" >>confdefs.h - -fi - -for ac_func in memchr memset setlocale socket strstr -do : - as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` -ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" -if eval test \"x\$"$as_ac_var"\" = x"yes"; then : - cat >>confdefs.h <<_ACEOF -#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 -_ACEOF - -fi -done - - -ac_config_files="$ac_config_files Makefile" - -cat >confcache <<\_ACEOF -# This file is a shell script that caches the results of configure -# tests run on this system so they can be shared between configure -# scripts and configure runs, see configure's option --config-cache. -# It is not useful on other systems. If it contains results you don't -# want to keep, you may remove or edit it. -# -# config.status only pays attention to the cache file if you give it -# the --recheck option to rerun configure. -# -# `ac_cv_env_foo' variables (set or unset) will be overridden when -# loading this file, other *unset* `ac_cv_foo' will be assigned the -# following values. - -_ACEOF - -# The following way of writing the cache mishandles newlines in values, -# but we know of no workaround that is simple, portable, and efficient. -# So, we kill variables containing newlines. -# Ultrix sh set writes to stderr and can't be redirected directly, -# and sets the high bit in the cache file unless we assign to the vars. -( - for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do - eval ac_val=\$$ac_var - case $ac_val in #( - *${as_nl}*) - case $ac_var in #( - *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; - esac - case $ac_var in #( - _ | IFS | as_nl) ;; #( - BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( - *) { eval $ac_var=; unset $ac_var;} ;; - esac ;; - esac - done - - (set) 2>&1 | - case $as_nl`(ac_space=' '; set) 2>&1` in #( - *${as_nl}ac_space=\ *) - # `set' does not quote correctly, so add quotes: double-quote - # substitution turns \\\\ into \\, and sed turns \\ into \. - sed -n \ - "s/'/'\\\\''/g; - s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" - ;; #( - *) - # `set' quotes correctly as required by POSIX, so do not add quotes. - sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" - ;; - esac | - sort -) | - sed ' - /^ac_cv_env_/b end - t clear - :clear - s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ - t end - s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ - :end' >>confcache -if diff "$cache_file" confcache >/dev/null 2>&1; then :; else - if test -w "$cache_file"; then - if test "x$cache_file" != "x/dev/null"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 -$as_echo "$as_me: updating cache $cache_file" >&6;} - if test ! -f "$cache_file" || test -h "$cache_file"; then - cat confcache >"$cache_file" - else - case $cache_file in #( - */* | ?:*) - mv -f confcache "$cache_file"$$ && - mv -f "$cache_file"$$ "$cache_file" ;; #( - *) - mv -f confcache "$cache_file" ;; - esac - fi - fi - else - { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 -$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} - fi -fi -rm -f confcache - -test "x$prefix" = xNONE && prefix=$ac_default_prefix -# Let make expand exec_prefix. -test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' - -DEFS=-DHAVE_CONFIG_H - -ac_libobjs= -ac_ltlibobjs= -U= -for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue - # 1. Remove the extension, and $U if already installed. - ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' - ac_i=`$as_echo "$ac_i" | sed "$ac_script"` - # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR - # will be set to the directory where LIBOBJS objects are built. - as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" - as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' -done -LIBOBJS=$ac_libobjs - -LTLIBOBJS=$ac_ltlibobjs - - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5 -$as_echo_n "checking that generated files are newer than configure... " >&6; } - if test -n "$am_sleep_pid"; then - # Hide warnings about reused PIDs. - wait $am_sleep_pid 2>/dev/null - fi - { $as_echo "$as_me:${as_lineno-$LINENO}: result: done" >&5 -$as_echo "done" >&6; } - if test -n "$EXEEXT"; then - am__EXEEXT_TRUE= - am__EXEEXT_FALSE='#' -else - am__EXEEXT_TRUE='#' - am__EXEEXT_FALSE= -fi - -if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then - as_fn_error $? "conditional \"AMDEP\" was never defined. -Usually this means the macro was only invoked conditionally." "$LINENO" 5 -fi -if test -z "${am__fastdepCXX_TRUE}" && test -z "${am__fastdepCXX_FALSE}"; then - as_fn_error $? "conditional \"am__fastdepCXX\" was never defined. -Usually this means the macro was only invoked conditionally." "$LINENO" 5 -fi -if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then - as_fn_error $? "conditional \"am__fastdepCC\" was never defined. -Usually this means the macro was only invoked conditionally." "$LINENO" 5 -fi - -: "${CONFIG_STATUS=./config.status}" -ac_write_fail=0 -ac_clean_files_save=$ac_clean_files -ac_clean_files="$ac_clean_files $CONFIG_STATUS" -{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 -$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} -as_write_fail=0 -cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 -#! $SHELL -# Generated by $as_me. -# Run this file to recreate the current configuration. -# Compiler output produced by configure, useful for debugging -# configure, is in config.log if it exists. - -debug=false -ac_cs_recheck=false -ac_cs_silent=false - -SHELL=\${CONFIG_SHELL-$SHELL} -export SHELL -_ASEOF -cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 -## -------------------- ## -## M4sh Initialization. ## -## -------------------- ## - -# Be more Bourne compatible -DUALCASE=1; export DUALCASE # for MKS sh -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : - emulate sh - NULLCMD=: - # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which - # is contrary to our usage. Disable this feature. - alias -g '${1+"$@"}'='"$@"' - setopt NO_GLOB_SUBST -else - case `(set -o) 2>/dev/null` in #( - *posix*) : - set -o posix ;; #( - *) : - ;; -esac -fi - - -as_nl=' -' -export as_nl -# Printing a long string crashes Solaris 7 /usr/bin/printf. -as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' -as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo -as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo -# Prefer a ksh shell builtin over an external printf program on Solaris, -# but without wasting forks for bash or zsh. -if test -z "$BASH_VERSION$ZSH_VERSION" \ - && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then - as_echo='print -r --' - as_echo_n='print -rn --' -elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then - as_echo='printf %s\n' - as_echo_n='printf %s' -else - if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then - as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' - as_echo_n='/usr/ucb/echo -n' - else - as_echo_body='eval expr "X$1" : "X\\(.*\\)"' - as_echo_n_body='eval - arg=$1; - case $arg in #( - *"$as_nl"*) - expr "X$arg" : "X\\(.*\\)$as_nl"; - arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; - esac; - expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" - ' - export as_echo_n_body - as_echo_n='sh -c $as_echo_n_body as_echo' - fi - export as_echo_body - as_echo='sh -c $as_echo_body as_echo' -fi - -# The user is always right. -if test "${PATH_SEPARATOR+set}" != set; then - PATH_SEPARATOR=: - (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { - (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || - PATH_SEPARATOR=';' - } -fi - - -# IFS -# We need space, tab and new line, in precisely that order. Quoting is -# there to prevent editors from complaining about space-tab. -# (If _AS_PATH_WALK were called with IFS unset, it would disable word -# splitting by setting IFS to empty value.) -IFS=" "" $as_nl" - -# Find who we are. Look in the path if we contain no directory separator. -as_myself= -case $0 in #(( - *[\\/]* ) as_myself=$0 ;; - *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break - done -IFS=$as_save_IFS - - ;; -esac -# We did not find ourselves, most probably we were run as `sh COMMAND' -# in which case we are not to be found in the path. -if test "x$as_myself" = x; then - as_myself=$0 -fi -if test ! -f "$as_myself"; then - $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 - exit 1 -fi - -# Unset variables that we do not need and which cause bugs (e.g. in -# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" -# suppresses any "Segmentation fault" message there. '((' could -# trigger a bug in pdksh 5.2.14. -for as_var in BASH_ENV ENV MAIL MAILPATH -do eval test x\${$as_var+set} = xset \ - && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : -done -PS1='$ ' -PS2='> ' -PS4='+ ' - -# NLS nuisances. -LC_ALL=C -export LC_ALL -LANGUAGE=C -export LANGUAGE - -# CDPATH. -(unset CDPATH) >/dev/null 2>&1 && unset CDPATH - - -# as_fn_error STATUS ERROR [LINENO LOG_FD] -# ---------------------------------------- -# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are -# provided, also output the error to LOG_FD, referencing LINENO. Then exit the -# script with STATUS, using 1 if that was 0. -as_fn_error () -{ - as_status=$1; test $as_status -eq 0 && as_status=1 - if test "$4"; then - as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 - fi - $as_echo "$as_me: error: $2" >&2 - as_fn_exit $as_status -} # as_fn_error - - -# as_fn_set_status STATUS -# ----------------------- -# Set $? to STATUS, without forking. -as_fn_set_status () -{ - return $1 -} # as_fn_set_status - -# as_fn_exit STATUS -# ----------------- -# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. -as_fn_exit () -{ - set +e - as_fn_set_status $1 - exit $1 -} # as_fn_exit - -# as_fn_unset VAR -# --------------- -# Portably unset VAR. -as_fn_unset () -{ - { eval $1=; unset $1;} -} -as_unset=as_fn_unset -# as_fn_append VAR VALUE -# ---------------------- -# Append the text in VALUE to the end of the definition contained in VAR. Take -# advantage of any shell optimizations that allow amortized linear growth over -# repeated appends, instead of the typical quadratic growth present in naive -# implementations. -if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : - eval 'as_fn_append () - { - eval $1+=\$2 - }' -else - as_fn_append () - { - eval $1=\$$1\$2 - } -fi # as_fn_append - -# as_fn_arith ARG... -# ------------------ -# Perform arithmetic evaluation on the ARGs, and store the result in the -# global $as_val. Take advantage of shells that can avoid forks. The arguments -# must be portable across $(()) and expr. -if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : - eval 'as_fn_arith () - { - as_val=$(( $* )) - }' -else - as_fn_arith () - { - as_val=`expr "$@" || test $? -eq 1` - } -fi # as_fn_arith - - -if expr a : '\(a\)' >/dev/null 2>&1 && - test "X`expr 00001 : '.*\(...\)'`" = X001; then - as_expr=expr -else - as_expr=false -fi - -if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then - as_basename=basename -else - as_basename=false -fi - -if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then - as_dirname=dirname -else - as_dirname=false -fi - -as_me=`$as_basename -- "$0" || -$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ - X"$0" : 'X\(//\)$' \| \ - X"$0" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X/"$0" | - sed '/^.*\/\([^/][^/]*\)\/*$/{ - s//\1/ - q - } - /^X\/\(\/\/\)$/{ - s//\1/ - q - } - /^X\/\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - -# Avoid depending upon Character Ranges. -as_cr_letters='abcdefghijklmnopqrstuvwxyz' -as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' -as_cr_Letters=$as_cr_letters$as_cr_LETTERS -as_cr_digits='0123456789' -as_cr_alnum=$as_cr_Letters$as_cr_digits - -ECHO_C= ECHO_N= ECHO_T= -case `echo -n x` in #((((( --n*) - case `echo 'xy\c'` in - *c*) ECHO_T=' ';; # ECHO_T is single tab character. - xy) ECHO_C='\c';; - *) echo `echo ksh88 bug on AIX 6.1` > /dev/null - ECHO_T=' ';; - esac;; -*) - ECHO_N='-n';; -esac - -rm -f conf$$ conf$$.exe conf$$.file -if test -d conf$$.dir; then - rm -f conf$$.dir/conf$$.file -else - rm -f conf$$.dir - mkdir conf$$.dir 2>/dev/null -fi -if (echo >conf$$.file) 2>/dev/null; then - if ln -s conf$$.file conf$$ 2>/dev/null; then - as_ln_s='ln -s' - # ... but there are two gotchas: - # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -pR'. - ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || - as_ln_s='cp -pR' - elif ln conf$$.file conf$$ 2>/dev/null; then - as_ln_s=ln - else - as_ln_s='cp -pR' - fi -else - as_ln_s='cp -pR' -fi -rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file -rmdir conf$$.dir 2>/dev/null - - -# as_fn_mkdir_p -# ------------- -# Create "$as_dir" as a directory, including parents if necessary. -as_fn_mkdir_p () -{ - - case $as_dir in #( - -*) as_dir=./$as_dir;; - esac - test -d "$as_dir" || eval $as_mkdir_p || { - as_dirs= - while :; do - case $as_dir in #( - *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( - *) as_qdir=$as_dir;; - esac - as_dirs="'$as_qdir' $as_dirs" - as_dir=`$as_dirname -- "$as_dir" || -$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$as_dir" : 'X\(//\)[^/]' \| \ - X"$as_dir" : 'X\(//\)$' \| \ - X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$as_dir" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - test -d "$as_dir" && break - done - test -z "$as_dirs" || eval "mkdir $as_dirs" - } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" - - -} # as_fn_mkdir_p -if mkdir -p . 2>/dev/null; then - as_mkdir_p='mkdir -p "$as_dir"' -else - test -d ./-p && rmdir ./-p - as_mkdir_p=false -fi - - -# as_fn_executable_p FILE -# ----------------------- -# Test if FILE is an executable regular file. -as_fn_executable_p () -{ - test -f "$1" && test -x "$1" -} # as_fn_executable_p -as_test_x='test -x' -as_executable_p=as_fn_executable_p - -# Sed expression to map a string onto a valid CPP name. -as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" - -# Sed expression to map a string onto a valid variable name. -as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" - - -exec 6>&1 -## ----------------------------------- ## -## Main body of $CONFIG_STATUS script. ## -## ----------------------------------- ## -_ASEOF -test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 - -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 -# Save the log message, to keep $0 and so on meaningful, and to -# report actual input values of CONFIG_FILES etc. instead of their -# values after options handling. -ac_log=" -This file was extended by i2pd $as_me 0.0.0, which was -generated by GNU Autoconf 2.69. Invocation command line was - - CONFIG_FILES = $CONFIG_FILES - CONFIG_HEADERS = $CONFIG_HEADERS - CONFIG_LINKS = $CONFIG_LINKS - CONFIG_COMMANDS = $CONFIG_COMMANDS - $ $0 $@ - -on `(hostname || uname -n) 2>/dev/null | sed 1q` -" - -_ACEOF - -case $ac_config_files in *" -"*) set x $ac_config_files; shift; ac_config_files=$*;; -esac - -case $ac_config_headers in *" -"*) set x $ac_config_headers; shift; ac_config_headers=$*;; -esac - - -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -# Files that config.status was made for. -config_files="$ac_config_files" -config_headers="$ac_config_headers" -config_commands="$ac_config_commands" - -_ACEOF - -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 -ac_cs_usage="\ -\`$as_me' instantiates files and other configuration actions -from templates according to the current configuration. Unless the files -and actions are specified as TAGs, all are instantiated by default. - -Usage: $0 [OPTION]... [TAG]... - - -h, --help print this help, then exit - -V, --version print version number and configuration settings, then exit - --config print configuration, then exit - -q, --quiet, --silent - do not print progress messages - -d, --debug don't remove temporary files - --recheck update $as_me by reconfiguring in the same conditions - --file=FILE[:TEMPLATE] - instantiate the configuration file FILE - --header=FILE[:TEMPLATE] - instantiate the configuration header FILE - -Configuration files: -$config_files - -Configuration headers: -$config_headers - -Configuration commands: -$config_commands - -Report bugs to ." - -_ACEOF -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" -ac_cs_version="\\ -i2pd config.status 0.0.0 -configured by $0, generated by GNU Autoconf 2.69, - with options \\"\$ac_cs_config\\" - -Copyright (C) 2012 Free Software Foundation, Inc. -This config.status script is free software; the Free Software Foundation -gives unlimited permission to copy, distribute and modify it." - -ac_pwd='$ac_pwd' -srcdir='$srcdir' -INSTALL='$INSTALL' -MKDIR_P='$MKDIR_P' -AWK='$AWK' -test -n "\$AWK" || AWK=awk -_ACEOF - -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 -# The default lists apply if the user does not specify any file. -ac_need_defaults=: -while test $# != 0 -do - case $1 in - --*=?*) - ac_option=`expr "X$1" : 'X\([^=]*\)='` - ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` - ac_shift=: - ;; - --*=) - ac_option=`expr "X$1" : 'X\([^=]*\)='` - ac_optarg= - ac_shift=: - ;; - *) - ac_option=$1 - ac_optarg=$2 - ac_shift=shift - ;; - esac - - case $ac_option in - # Handling of the options. - -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) - ac_cs_recheck=: ;; - --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) - $as_echo "$ac_cs_version"; exit ;; - --config | --confi | --conf | --con | --co | --c ) - $as_echo "$ac_cs_config"; exit ;; - --debug | --debu | --deb | --de | --d | -d ) - debug=: ;; - --file | --fil | --fi | --f ) - $ac_shift - case $ac_optarg in - *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; - '') as_fn_error $? "missing file argument" ;; - esac - as_fn_append CONFIG_FILES " '$ac_optarg'" - ac_need_defaults=false;; - --header | --heade | --head | --hea ) - $ac_shift - case $ac_optarg in - *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; - esac - as_fn_append CONFIG_HEADERS " '$ac_optarg'" - ac_need_defaults=false;; - --he | --h) - # Conflict between --help and --header - as_fn_error $? "ambiguous option: \`$1' -Try \`$0 --help' for more information.";; - --help | --hel | -h ) - $as_echo "$ac_cs_usage"; exit ;; - -q | -quiet | --quiet | --quie | --qui | --qu | --q \ - | -silent | --silent | --silen | --sile | --sil | --si | --s) - ac_cs_silent=: ;; - - # This is an error. - -*) as_fn_error $? "unrecognized option: \`$1' -Try \`$0 --help' for more information." ;; - - *) as_fn_append ac_config_targets " $1" - ac_need_defaults=false ;; - - esac - shift -done - -ac_configure_extra_args= - -if $ac_cs_silent; then - exec 6>/dev/null - ac_configure_extra_args="$ac_configure_extra_args --silent" -fi - -_ACEOF -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -if \$ac_cs_recheck; then - set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion - shift - \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 - CONFIG_SHELL='$SHELL' - export CONFIG_SHELL - exec "\$@" -fi - -_ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 -exec 5>>config.log -{ - echo - sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX -## Running $as_me. ## -_ASBOX - $as_echo "$ac_log" -} >&5 - -_ACEOF -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -# -# INIT-COMMANDS -# -AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir" - -_ACEOF - -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 - -# Handling of arguments. -for ac_config_target in $ac_config_targets -do - case $ac_config_target in - "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; - "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; - "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; - - *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; - esac -done - - -# If the user did not use the arguments to specify the items to instantiate, -# then the envvar interface is used. Set only those that are not. -# We use the long form for the default assignment because of an extremely -# bizarre bug on SunOS 4.1.3. -if $ac_need_defaults; then - test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files - test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers - test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands -fi - -# Have a temporary directory for convenience. Make it in the build tree -# simply because there is no reason against having it here, and in addition, -# creating and moving files from /tmp can sometimes cause problems. -# Hook for its removal unless debugging. -# Note that there is a small window in which the directory will not be cleaned: -# after its creation but before its name has been assigned to `$tmp'. -$debug || -{ - tmp= ac_tmp= - trap 'exit_status=$? - : "${ac_tmp:=$tmp}" - { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status -' 0 - trap 'as_fn_exit 1' 1 2 13 15 -} -# Create a (secure) tmp directory for tmp files. - -{ - tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && - test -d "$tmp" -} || -{ - tmp=./conf$$-$RANDOM - (umask 077 && mkdir "$tmp") -} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 -ac_tmp=$tmp - -# Set up the scripts for CONFIG_FILES section. -# No need to generate them if there are no CONFIG_FILES. -# This happens for instance with `./config.status config.h'. -if test -n "$CONFIG_FILES"; then - - -ac_cr=`echo X | tr X '\015'` -# On cygwin, bash can eat \r inside `` if the user requested igncr. -# But we know of no other shell where ac_cr would be empty at this -# point, so we can use a bashism as a fallback. -if test "x$ac_cr" = x; then - eval ac_cr=\$\'\\r\' -fi -ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` -if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then - ac_cs_awk_cr='\\r' -else - ac_cs_awk_cr=$ac_cr -fi - -echo 'BEGIN {' >"$ac_tmp/subs1.awk" && -_ACEOF - - -{ - echo "cat >conf$$subs.awk <<_ACEOF" && - echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && - echo "_ACEOF" -} >conf$$subs.sh || - as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 -ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` -ac_delim='%!_!# ' -for ac_last_try in false false false false false :; do - . ./conf$$subs.sh || - as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 - - ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` - if test $ac_delim_n = $ac_delim_num; then - break - elif $ac_last_try; then - as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 - else - ac_delim="$ac_delim!$ac_delim _$ac_delim!! " - fi -done -rm -f conf$$subs.sh - -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && -_ACEOF -sed -n ' -h -s/^/S["/; s/!.*/"]=/ -p -g -s/^[^!]*!// -:repl -t repl -s/'"$ac_delim"'$// -t delim -:nl -h -s/\(.\{148\}\)..*/\1/ -t more1 -s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ -p -n -b repl -:more1 -s/["\\]/\\&/g; s/^/"/; s/$/"\\/ -p -g -s/.\{148\}// -t nl -:delim -h -s/\(.\{148\}\)..*/\1/ -t more2 -s/["\\]/\\&/g; s/^/"/; s/$/"/ -p -b -:more2 -s/["\\]/\\&/g; s/^/"/; s/$/"\\/ -p -g -s/.\{148\}// -t delim -' >$CONFIG_STATUS || ac_write_fail=1 -rm -f conf$$subs.awk -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -_ACAWK -cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && - for (key in S) S_is_set[key] = 1 - FS = "" - -} -{ - line = $ 0 - nfields = split(line, field, "@") - substed = 0 - len = length(field[1]) - for (i = 2; i < nfields; i++) { - key = field[i] - keylen = length(key) - if (S_is_set[key]) { - value = S[key] - line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) - len += length(value) + length(field[++i]) - substed = 1 - } else - len += 1 + keylen - } - - print line -} - -_ACAWK -_ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 -if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then - sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" -else - cat -fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ - || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 -_ACEOF - -# VPATH may cause trouble with some makes, so we remove sole $(srcdir), -# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and -# trailing colons and then remove the whole line if VPATH becomes empty -# (actually we leave an empty line to preserve line numbers). -if test "x$srcdir" = x.; then - ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ -h -s/// -s/^/:/ -s/[ ]*$/:/ -s/:\$(srcdir):/:/g -s/:\${srcdir}:/:/g -s/:@srcdir@:/:/g -s/^:*// -s/:*$// -x -s/\(=[ ]*\).*/\1/ -G -s/\n// -s/^[^=]*=[ ]*$// -}' -fi - -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 -fi # test -n "$CONFIG_FILES" - -# Set up the scripts for CONFIG_HEADERS section. -# No need to generate them if there are no CONFIG_HEADERS. -# This happens for instance with `./config.status Makefile'. -if test -n "$CONFIG_HEADERS"; then -cat >"$ac_tmp/defines.awk" <<\_ACAWK || -BEGIN { -_ACEOF - -# Transform confdefs.h into an awk script `defines.awk', embedded as -# here-document in config.status, that substitutes the proper values into -# config.h.in to produce config.h. - -# Create a delimiter string that does not exist in confdefs.h, to ease -# handling of long lines. -ac_delim='%!_!# ' -for ac_last_try in false false :; do - ac_tt=`sed -n "/$ac_delim/p" confdefs.h` - if test -z "$ac_tt"; then - break - elif $ac_last_try; then - as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 - else - ac_delim="$ac_delim!$ac_delim _$ac_delim!! " - fi -done - -# For the awk script, D is an array of macro values keyed by name, -# likewise P contains macro parameters if any. Preserve backslash -# newline sequences. - -ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* -sed -n ' -s/.\{148\}/&'"$ac_delim"'/g -t rset -:rset -s/^[ ]*#[ ]*define[ ][ ]*/ / -t def -d -:def -s/\\$// -t bsnl -s/["\\]/\\&/g -s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ -D["\1"]=" \3"/p -s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p -d -:bsnl -s/["\\]/\\&/g -s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ -D["\1"]=" \3\\\\\\n"\\/p -t cont -s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p -t cont -d -:cont -n -s/.\{148\}/&'"$ac_delim"'/g -t clear -:clear -s/\\$// -t bsnlc -s/["\\]/\\&/g; s/^/"/; s/$/"/p -d -:bsnlc -s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p -b cont -' >$CONFIG_STATUS || ac_write_fail=1 - -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 - for (key in D) D_is_set[key] = 1 - FS = "" -} -/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { - line = \$ 0 - split(line, arg, " ") - if (arg[1] == "#") { - defundef = arg[2] - mac1 = arg[3] - } else { - defundef = substr(arg[1], 2) - mac1 = arg[2] - } - split(mac1, mac2, "(") #) - macro = mac2[1] - prefix = substr(line, 1, index(line, defundef) - 1) - if (D_is_set[macro]) { - # Preserve the white space surrounding the "#". - print prefix "define", macro P[macro] D[macro] - next - } else { - # Replace #undef with comments. This is necessary, for example, - # in the case of _POSIX_SOURCE, which is predefined and required - # on some systems where configure will not decide to define it. - if (defundef == "undef") { - print "/*", prefix defundef, macro, "*/" - next - } - } -} -{ print } -_ACAWK -_ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 - as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 -fi # test -n "$CONFIG_HEADERS" - - -eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS" -shift -for ac_tag -do - case $ac_tag in - :[FHLC]) ac_mode=$ac_tag; continue;; - esac - case $ac_mode$ac_tag in - :[FHL]*:*);; - :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; - :[FH]-) ac_tag=-:-;; - :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; - esac - ac_save_IFS=$IFS - IFS=: - set x $ac_tag - IFS=$ac_save_IFS - shift - ac_file=$1 - shift - - case $ac_mode in - :L) ac_source=$1;; - :[FH]) - ac_file_inputs= - for ac_f - do - case $ac_f in - -) ac_f="$ac_tmp/stdin";; - *) # Look for the file first in the build tree, then in the source tree - # (if the path is not absolute). The absolute path cannot be DOS-style, - # because $ac_f cannot contain `:'. - test -f "$ac_f" || - case $ac_f in - [\\/$]*) false;; - *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; - esac || - as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; - esac - case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac - as_fn_append ac_file_inputs " '$ac_f'" - done - - # Let's still pretend it is `configure' which instantiates (i.e., don't - # use $as_me), people would be surprised to read: - # /* config.h. Generated by config.status. */ - configure_input='Generated from '` - $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' - `' by configure.' - if test x"$ac_file" != x-; then - configure_input="$ac_file. $configure_input" - { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 -$as_echo "$as_me: creating $ac_file" >&6;} - fi - # Neutralize special characters interpreted by sed in replacement strings. - case $configure_input in #( - *\&* | *\|* | *\\* ) - ac_sed_conf_input=`$as_echo "$configure_input" | - sed 's/[\\\\&|]/\\\\&/g'`;; #( - *) ac_sed_conf_input=$configure_input;; - esac - - case $ac_tag in - *:-:* | *:-) cat >"$ac_tmp/stdin" \ - || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; - esac - ;; - esac - - ac_dir=`$as_dirname -- "$ac_file" || -$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$ac_file" : 'X\(//\)[^/]' \| \ - X"$ac_file" : 'X\(//\)$' \| \ - X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$ac_file" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - as_dir="$ac_dir"; as_fn_mkdir_p - ac_builddir=. - -case "$ac_dir" in -.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; -*) - ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` - # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` - case $ac_top_builddir_sub in - "") ac_top_builddir_sub=. ac_top_build_prefix= ;; - *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; - esac ;; -esac -ac_abs_top_builddir=$ac_pwd -ac_abs_builddir=$ac_pwd$ac_dir_suffix -# for backward compatibility: -ac_top_builddir=$ac_top_build_prefix - -case $srcdir in - .) # We are building in place. - ac_srcdir=. - ac_top_srcdir=$ac_top_builddir_sub - ac_abs_top_srcdir=$ac_pwd ;; - [\\/]* | ?:[\\/]* ) # Absolute name. - ac_srcdir=$srcdir$ac_dir_suffix; - ac_top_srcdir=$srcdir - ac_abs_top_srcdir=$srcdir ;; - *) # Relative name. - ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix - ac_top_srcdir=$ac_top_build_prefix$srcdir - ac_abs_top_srcdir=$ac_pwd/$srcdir ;; -esac -ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix - - - case $ac_mode in - :F) - # - # CONFIG_FILE - # - - case $INSTALL in - [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; - *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; - esac - ac_MKDIR_P=$MKDIR_P - case $MKDIR_P in - [\\/$]* | ?:[\\/]* ) ;; - */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;; - esac -_ACEOF - -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 -# If the template does not know about datarootdir, expand it. -# FIXME: This hack should be removed a few years after 2.60. -ac_datarootdir_hack=; ac_datarootdir_seen= -ac_sed_dataroot=' -/datarootdir/ { - p - q -} -/@datadir@/p -/@docdir@/p -/@infodir@/p -/@localedir@/p -/@mandir@/p' -case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in -*datarootdir*) ac_datarootdir_seen=yes;; -*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 -$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} -_ACEOF -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 - ac_datarootdir_hack=' - s&@datadir@&$datadir&g - s&@docdir@&$docdir&g - s&@infodir@&$infodir&g - s&@localedir@&$localedir&g - s&@mandir@&$mandir&g - s&\\\${datarootdir}&$datarootdir&g' ;; -esac -_ACEOF - -# Neutralize VPATH when `$srcdir' = `.'. -# Shell code in configure.ac might set extrasub. -# FIXME: do we really want to maintain this feature? -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -ac_sed_extra="$ac_vpsub -$extrasub -_ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 -:t -/@[a-zA-Z_][a-zA-Z_0-9]*@/!b -s|@configure_input@|$ac_sed_conf_input|;t t -s&@top_builddir@&$ac_top_builddir_sub&;t t -s&@top_build_prefix@&$ac_top_build_prefix&;t t -s&@srcdir@&$ac_srcdir&;t t -s&@abs_srcdir@&$ac_abs_srcdir&;t t -s&@top_srcdir@&$ac_top_srcdir&;t t -s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t -s&@builddir@&$ac_builddir&;t t -s&@abs_builddir@&$ac_abs_builddir&;t t -s&@abs_top_builddir@&$ac_abs_top_builddir&;t t -s&@INSTALL@&$ac_INSTALL&;t t -s&@MKDIR_P@&$ac_MKDIR_P&;t t -$ac_datarootdir_hack -" -eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ - >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 - -test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && - { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && - { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ - "$ac_tmp/out"`; test -z "$ac_out"; } && - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' -which seems to be undefined. Please make sure it is defined" >&5 -$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' -which seems to be undefined. Please make sure it is defined" >&2;} - - rm -f "$ac_tmp/stdin" - case $ac_file in - -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; - *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; - esac \ - || as_fn_error $? "could not create $ac_file" "$LINENO" 5 - ;; - :H) - # - # CONFIG_HEADER - # - if test x"$ac_file" != x-; then - { - $as_echo "/* $configure_input */" \ - && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" - } >"$ac_tmp/config.h" \ - || as_fn_error $? "could not create $ac_file" "$LINENO" 5 - if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then - { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 -$as_echo "$as_me: $ac_file is unchanged" >&6;} - else - rm -f "$ac_file" - mv "$ac_tmp/config.h" "$ac_file" \ - || as_fn_error $? "could not create $ac_file" "$LINENO" 5 - fi - else - $as_echo "/* $configure_input */" \ - && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ - || as_fn_error $? "could not create -" "$LINENO" 5 - fi -# Compute "$ac_file"'s index in $config_headers. -_am_arg="$ac_file" -_am_stamp_count=1 -for _am_header in $config_headers :; do - case $_am_header in - $_am_arg | $_am_arg:* ) - break ;; - * ) - _am_stamp_count=`expr $_am_stamp_count + 1` ;; - esac -done -echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" || -$as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$_am_arg" : 'X\(//\)[^/]' \| \ - X"$_am_arg" : 'X\(//\)$' \| \ - X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$_am_arg" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'`/stamp-h$_am_stamp_count - ;; - - :C) { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 -$as_echo "$as_me: executing $ac_file commands" >&6;} - ;; - esac - - - case $ac_file$ac_mode in - "depfiles":C) test x"$AMDEP_TRUE" != x"" || { - # Older Autoconf quotes --file arguments for eval, but not when files - # are listed without --file. Let's play safe and only enable the eval - # if we detect the quoting. - case $CONFIG_FILES in - *\'*) eval set x "$CONFIG_FILES" ;; - *) set x $CONFIG_FILES ;; - esac - shift - for mf - do - # Strip MF so we end up with the name of the file. - mf=`echo "$mf" | sed -e 's/:.*$//'` - # Check whether this is an Automake generated Makefile or not. - # We used to match only the files named 'Makefile.in', but - # some people rename them; so instead we look at the file content. - # Grep'ing the first line is not enough: some people post-process - # each Makefile.in and add a new line on top of each file to say so. - # Grep'ing the whole file is not good either: AIX grep has a line - # limit of 2048, but all sed's we know have understand at least 4000. - if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then - dirpart=`$as_dirname -- "$mf" || -$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$mf" : 'X\(//\)[^/]' \| \ - X"$mf" : 'X\(//\)$' \| \ - X"$mf" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$mf" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - else - continue - fi - # Extract the definition of DEPDIR, am__include, and am__quote - # from the Makefile without running 'make'. - DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` - test -z "$DEPDIR" && continue - am__include=`sed -n 's/^am__include = //p' < "$mf"` - test -z "$am__include" && continue - am__quote=`sed -n 's/^am__quote = //p' < "$mf"` - # Find all dependency output files, they are included files with - # $(DEPDIR) in their names. We invoke sed twice because it is the - # simplest approach to changing $(DEPDIR) to its actual value in the - # expansion. - for file in `sed -n " - s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ - sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do - # Make sure the directory exists. - test -f "$dirpart/$file" && continue - fdir=`$as_dirname -- "$file" || -$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$file" : 'X\(//\)[^/]' \| \ - X"$file" : 'X\(//\)$' \| \ - X"$file" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$file" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - as_dir=$dirpart/$fdir; as_fn_mkdir_p - # echo "creating $dirpart/$file" - echo '# dummy' > "$dirpart/$file" - done - done -} - ;; - - esac -done # for ac_tag - - -as_fn_exit 0 -_ACEOF -ac_clean_files=$ac_clean_files_save - -test $ac_write_fail = 0 || - as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 - - -# configure is writing to config.log, and then calls config.status. -# config.status does its own redirection, appending to config.log. -# Unfortunately, on DOS this fails, as config.log is still kept open -# by configure, so config.status won't be able to write to it; its -# output is simply discarded. So we exec the FD to /dev/null, -# effectively closing config.log, so it can be properly (re)opened and -# appended to by config.status. When coming back to configure, we -# need to make the FD available again. -if test "$no_create" != yes; then - ac_cs_success=: - ac_config_status_args= - test "$silent" = yes && - ac_config_status_args="$ac_config_status_args --quiet" - exec 5>/dev/null - $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false - exec 5>>config.log - # Use ||, not &&, to avoid exiting from the if with $? = 1, which - # would make configure fail if this is the last instruction. - $ac_cs_success || as_fn_exit 1 -fi -if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 -$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} -fi - diff --git a/build/autotools/configure.ac b/build/autotools/configure.ac deleted file mode 100644 index c44eed29..00000000 --- a/build/autotools/configure.ac +++ /dev/null @@ -1,72 +0,0 @@ -AC_PREREQ([2.69]) -AC_INIT([i2pd], [0.0.0], - [https://track.privacysolutions.no/projects/i2pd/issues]) -AM_INIT_AUTOMAKE([-Wall foreign]) -AC_CONFIG_SRCDIR([I2NPProtocol.cpp]) -AC_CONFIG_HEADERS([config.h]) - -# Checks for programs. -AC_PROG_CXX -AC_PROG_CC - -# Check for C++11 -m4_include([m4/ax_cxx_compile_stdcxx_11.m4]) -AX_CXX_COMPILE_STDCXX_11([],[mandatory]) - -# Set platform specific flags -if test "$(uname -s)" = "Darwin" -then - CXXFLAGS="-DCRYPTOPP_DISABLE_ASM -DAESNI ${CXXFLAGS}" - -elif test "$(uname -s)" = "Linux" -then - if test -n "$(grep aes /proc/cpuinfo)" - then - CXXFLAGS="-DAESNI ${CXXFLAGS}" - fi -else - # emtpy - true -fi - -# Checks for libraries. -AC_SEARCH_LIBS([s_sosemanukMulTables], [cryptopp], - [], [AC_MSG_ERROR([Unable to find crypto++])]) - -m4_include([m4/ax_boost_base.m4]) -m4_include([m4/ax_boost_date_time.m4]) -m4_include([m4/ax_boost_filesystem.m4]) -m4_include([m4/ax_boost_program_options.m4]) -m4_include([m4/ax_boost_regex.m4]) -m4_include([m4/ax_boost_system.m4]) -AX_BOOST_BASE([1.46]) -AX_BOOST_DATE_TIME -AX_BOOST_FILESYSTEM -AX_BOOST_PROGRAM_OPTIONS -AX_BOOST_REGEX -AX_BOOST_SYSTEM - -m4_include([m4/ax_pthread.m4]) -AX_PTHREAD() - -# Checks for header files. -AC_CHECK_HEADERS([fcntl.h inttypes.h stdlib.h string.h unistd.h]) - -# Checks for typedefs, structures, and compiler characteristics. -AC_CHECK_HEADER_STDBOOL -AC_C_INLINE -AC_TYPE_INT32_T -AC_TYPE_PID_T -AC_TYPE_SIZE_T -AC_TYPE_UINT16_T -AC_TYPE_UINT32_T -AC_TYPE_UINT64_T -AC_TYPE_UINT8_T -AC_CHECK_TYPES([ptrdiff_t]) - -# Checks for library functions. -AC_FUNC_FORK -AC_CHECK_FUNCS([memchr memset setlocale socket strstr]) - -AC_CONFIG_FILES([Makefile]) -AC_OUTPUT diff --git a/build/autotools/depcomp b/build/autotools/depcomp deleted file mode 100755 index 4ebd5b3a..00000000 --- a/build/autotools/depcomp +++ /dev/null @@ -1,791 +0,0 @@ -#! /bin/sh -# depcomp - compile a program generating dependencies as side-effects - -scriptversion=2013-05-30.07; # UTC - -# Copyright (C) 1999-2013 Free Software Foundation, Inc. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -# Originally written by Alexandre Oliva . - -case $1 in - '') - echo "$0: No command. Try '$0 --help' for more information." 1>&2 - exit 1; - ;; - -h | --h*) - cat <<\EOF -Usage: depcomp [--help] [--version] PROGRAM [ARGS] - -Run PROGRAMS ARGS to compile a file, generating dependencies -as side-effects. - -Environment variables: - depmode Dependency tracking mode. - source Source file read by 'PROGRAMS ARGS'. - object Object file output by 'PROGRAMS ARGS'. - DEPDIR directory where to store dependencies. - depfile Dependency file to output. - tmpdepfile Temporary file to use when outputting dependencies. - libtool Whether libtool is used (yes/no). - -Report bugs to . -EOF - exit $? - ;; - -v | --v*) - echo "depcomp $scriptversion" - exit $? - ;; -esac - -# Get the directory component of the given path, and save it in the -# global variables '$dir'. Note that this directory component will -# be either empty or ending with a '/' character. This is deliberate. -set_dir_from () -{ - case $1 in - */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; - *) dir=;; - esac -} - -# Get the suffix-stripped basename of the given path, and save it the -# global variable '$base'. -set_base_from () -{ - base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` -} - -# If no dependency file was actually created by the compiler invocation, -# we still have to create a dummy depfile, to avoid errors with the -# Makefile "include basename.Plo" scheme. -make_dummy_depfile () -{ - echo "#dummy" > "$depfile" -} - -# Factor out some common post-processing of the generated depfile. -# Requires the auxiliary global variable '$tmpdepfile' to be set. -aix_post_process_depfile () -{ - # If the compiler actually managed to produce a dependency file, - # post-process it. - if test -f "$tmpdepfile"; then - # Each line is of the form 'foo.o: dependency.h'. - # Do two passes, one to just change these to - # $object: dependency.h - # and one to simply output - # dependency.h: - # which is needed to avoid the deleted-header problem. - { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" - sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" - } > "$depfile" - rm -f "$tmpdepfile" - else - make_dummy_depfile - fi -} - -# A tabulation character. -tab=' ' -# A newline character. -nl=' -' -# Character ranges might be problematic outside the C locale. -# These definitions help. -upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ -lower=abcdefghijklmnopqrstuvwxyz -digits=0123456789 -alpha=${upper}${lower} - -if test -z "$depmode" || test -z "$source" || test -z "$object"; then - echo "depcomp: Variables source, object and depmode must be set" 1>&2 - exit 1 -fi - -# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. -depfile=${depfile-`echo "$object" | - sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} -tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} - -rm -f "$tmpdepfile" - -# Avoid interferences from the environment. -gccflag= dashmflag= - -# Some modes work just like other modes, but use different flags. We -# parameterize here, but still list the modes in the big case below, -# to make depend.m4 easier to write. Note that we *cannot* use a case -# here, because this file can only contain one case statement. -if test "$depmode" = hp; then - # HP compiler uses -M and no extra arg. - gccflag=-M - depmode=gcc -fi - -if test "$depmode" = dashXmstdout; then - # This is just like dashmstdout with a different argument. - dashmflag=-xM - depmode=dashmstdout -fi - -cygpath_u="cygpath -u -f -" -if test "$depmode" = msvcmsys; then - # This is just like msvisualcpp but w/o cygpath translation. - # Just convert the backslash-escaped backslashes to single forward - # slashes to satisfy depend.m4 - cygpath_u='sed s,\\\\,/,g' - depmode=msvisualcpp -fi - -if test "$depmode" = msvc7msys; then - # This is just like msvc7 but w/o cygpath translation. - # Just convert the backslash-escaped backslashes to single forward - # slashes to satisfy depend.m4 - cygpath_u='sed s,\\\\,/,g' - depmode=msvc7 -fi - -if test "$depmode" = xlc; then - # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. - gccflag=-qmakedep=gcc,-MF - depmode=gcc -fi - -case "$depmode" in -gcc3) -## gcc 3 implements dependency tracking that does exactly what -## we want. Yay! Note: for some reason libtool 1.4 doesn't like -## it if -MD -MP comes after the -MF stuff. Hmm. -## Unfortunately, FreeBSD c89 acceptance of flags depends upon -## the command line argument order; so add the flags where they -## appear in depend2.am. Note that the slowdown incurred here -## affects only configure: in makefiles, %FASTDEP% shortcuts this. - for arg - do - case $arg in - -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; - *) set fnord "$@" "$arg" ;; - esac - shift # fnord - shift # $arg - done - "$@" - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - mv "$tmpdepfile" "$depfile" - ;; - -gcc) -## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. -## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. -## (see the conditional assignment to $gccflag above). -## There are various ways to get dependency output from gcc. Here's -## why we pick this rather obscure method: -## - Don't want to use -MD because we'd like the dependencies to end -## up in a subdir. Having to rename by hand is ugly. -## (We might end up doing this anyway to support other compilers.) -## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like -## -MM, not -M (despite what the docs say). Also, it might not be -## supported by the other compilers which use the 'gcc' depmode. -## - Using -M directly means running the compiler twice (even worse -## than renaming). - if test -z "$gccflag"; then - gccflag=-MD, - fi - "$@" -Wp,"$gccflag$tmpdepfile" - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - echo "$object : \\" > "$depfile" - # The second -e expression handles DOS-style file names with drive - # letters. - sed -e 's/^[^:]*: / /' \ - -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" -## This next piece of magic avoids the "deleted header file" problem. -## The problem is that when a header file which appears in a .P file -## is deleted, the dependency causes make to die (because there is -## typically no way to rebuild the header). We avoid this by adding -## dummy dependencies for each header file. Too bad gcc doesn't do -## this for us directly. -## Some versions of gcc put a space before the ':'. On the theory -## that the space means something, we add a space to the output as -## well. hp depmode also adds that space, but also prefixes the VPATH -## to the object. Take care to not repeat it in the output. -## Some versions of the HPUX 10.20 sed can't process this invocation -## correctly. Breaking it into two sed invocations is a workaround. - tr ' ' "$nl" < "$tmpdepfile" \ - | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ - | sed -e 's/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -hp) - # This case exists only to let depend.m4 do its work. It works by - # looking at the text of this script. This case will never be run, - # since it is checked for above. - exit 1 - ;; - -sgi) - if test "$libtool" = yes; then - "$@" "-Wp,-MDupdate,$tmpdepfile" - else - "$@" -MDupdate "$tmpdepfile" - fi - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - - if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files - echo "$object : \\" > "$depfile" - # Clip off the initial element (the dependent). Don't try to be - # clever and replace this with sed code, as IRIX sed won't handle - # lines with more than a fixed number of characters (4096 in - # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; - # the IRIX cc adds comments like '#:fec' to the end of the - # dependency line. - tr ' ' "$nl" < "$tmpdepfile" \ - | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ - | tr "$nl" ' ' >> "$depfile" - echo >> "$depfile" - # The second pass generates a dummy entry for each header file. - tr ' ' "$nl" < "$tmpdepfile" \ - | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ - >> "$depfile" - else - make_dummy_depfile - fi - rm -f "$tmpdepfile" - ;; - -xlc) - # This case exists only to let depend.m4 do its work. It works by - # looking at the text of this script. This case will never be run, - # since it is checked for above. - exit 1 - ;; - -aix) - # The C for AIX Compiler uses -M and outputs the dependencies - # in a .u file. In older versions, this file always lives in the - # current directory. Also, the AIX compiler puts '$object:' at the - # start of each line; $object doesn't have directory information. - # Version 6 uses the directory in both cases. - set_dir_from "$object" - set_base_from "$object" - if test "$libtool" = yes; then - tmpdepfile1=$dir$base.u - tmpdepfile2=$base.u - tmpdepfile3=$dir.libs/$base.u - "$@" -Wc,-M - else - tmpdepfile1=$dir$base.u - tmpdepfile2=$dir$base.u - tmpdepfile3=$dir$base.u - "$@" -M - fi - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" - exit $stat - fi - - for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" - do - test -f "$tmpdepfile" && break - done - aix_post_process_depfile - ;; - -tcc) - # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 - # FIXME: That version still under development at the moment of writing. - # Make that this statement remains true also for stable, released - # versions. - # It will wrap lines (doesn't matter whether long or short) with a - # trailing '\', as in: - # - # foo.o : \ - # foo.c \ - # foo.h \ - # - # It will put a trailing '\' even on the last line, and will use leading - # spaces rather than leading tabs (at least since its commit 0394caf7 - # "Emit spaces for -MD"). - "$@" -MD -MF "$tmpdepfile" - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. - # We have to change lines of the first kind to '$object: \'. - sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" - # And for each line of the second kind, we have to emit a 'dep.h:' - # dummy dependency, to avoid the deleted-header problem. - sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" - rm -f "$tmpdepfile" - ;; - -## The order of this option in the case statement is important, since the -## shell code in configure will try each of these formats in the order -## listed in this file. A plain '-MD' option would be understood by many -## compilers, so we must ensure this comes after the gcc and icc options. -pgcc) - # Portland's C compiler understands '-MD'. - # Will always output deps to 'file.d' where file is the root name of the - # source file under compilation, even if file resides in a subdirectory. - # The object file name does not affect the name of the '.d' file. - # pgcc 10.2 will output - # foo.o: sub/foo.c sub/foo.h - # and will wrap long lines using '\' : - # foo.o: sub/foo.c ... \ - # sub/foo.h ... \ - # ... - set_dir_from "$object" - # Use the source, not the object, to determine the base name, since - # that's sadly what pgcc will do too. - set_base_from "$source" - tmpdepfile=$base.d - - # For projects that build the same source file twice into different object - # files, the pgcc approach of using the *source* file root name can cause - # problems in parallel builds. Use a locking strategy to avoid stomping on - # the same $tmpdepfile. - lockdir=$base.d-lock - trap " - echo '$0: caught signal, cleaning up...' >&2 - rmdir '$lockdir' - exit 1 - " 1 2 13 15 - numtries=100 - i=$numtries - while test $i -gt 0; do - # mkdir is a portable test-and-set. - if mkdir "$lockdir" 2>/dev/null; then - # This process acquired the lock. - "$@" -MD - stat=$? - # Release the lock. - rmdir "$lockdir" - break - else - # If the lock is being held by a different process, wait - # until the winning process is done or we timeout. - while test -d "$lockdir" && test $i -gt 0; do - sleep 1 - i=`expr $i - 1` - done - fi - i=`expr $i - 1` - done - trap - 1 2 13 15 - if test $i -le 0; then - echo "$0: failed to acquire lock after $numtries attempts" >&2 - echo "$0: check lockdir '$lockdir'" >&2 - exit 1 - fi - - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - # Each line is of the form `foo.o: dependent.h', - # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. - # Do two passes, one to just change these to - # `$object: dependent.h' and one to simply `dependent.h:'. - sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" - # Some versions of the HPUX 10.20 sed can't process this invocation - # correctly. Breaking it into two sed invocations is a workaround. - sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ - | sed -e 's/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -hp2) - # The "hp" stanza above does not work with aCC (C++) and HP's ia64 - # compilers, which have integrated preprocessors. The correct option - # to use with these is +Maked; it writes dependencies to a file named - # 'foo.d', which lands next to the object file, wherever that - # happens to be. - # Much of this is similar to the tru64 case; see comments there. - set_dir_from "$object" - set_base_from "$object" - if test "$libtool" = yes; then - tmpdepfile1=$dir$base.d - tmpdepfile2=$dir.libs/$base.d - "$@" -Wc,+Maked - else - tmpdepfile1=$dir$base.d - tmpdepfile2=$dir$base.d - "$@" +Maked - fi - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile1" "$tmpdepfile2" - exit $stat - fi - - for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" - do - test -f "$tmpdepfile" && break - done - if test -f "$tmpdepfile"; then - sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" - # Add 'dependent.h:' lines. - sed -ne '2,${ - s/^ *// - s/ \\*$// - s/$/:/ - p - }' "$tmpdepfile" >> "$depfile" - else - make_dummy_depfile - fi - rm -f "$tmpdepfile" "$tmpdepfile2" - ;; - -tru64) - # The Tru64 compiler uses -MD to generate dependencies as a side - # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. - # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put - # dependencies in 'foo.d' instead, so we check for that too. - # Subdirectories are respected. - set_dir_from "$object" - set_base_from "$object" - - if test "$libtool" = yes; then - # Libtool generates 2 separate objects for the 2 libraries. These - # two compilations output dependencies in $dir.libs/$base.o.d and - # in $dir$base.o.d. We have to check for both files, because - # one of the two compilations can be disabled. We should prefer - # $dir$base.o.d over $dir.libs/$base.o.d because the latter is - # automatically cleaned when .libs/ is deleted, while ignoring - # the former would cause a distcleancheck panic. - tmpdepfile1=$dir$base.o.d # libtool 1.5 - tmpdepfile2=$dir.libs/$base.o.d # Likewise. - tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 - "$@" -Wc,-MD - else - tmpdepfile1=$dir$base.d - tmpdepfile2=$dir$base.d - tmpdepfile3=$dir$base.d - "$@" -MD - fi - - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" - exit $stat - fi - - for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" - do - test -f "$tmpdepfile" && break - done - # Same post-processing that is required for AIX mode. - aix_post_process_depfile - ;; - -msvc7) - if test "$libtool" = yes; then - showIncludes=-Wc,-showIncludes - else - showIncludes=-showIncludes - fi - "$@" $showIncludes > "$tmpdepfile" - stat=$? - grep -v '^Note: including file: ' "$tmpdepfile" - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - echo "$object : \\" > "$depfile" - # The first sed program below extracts the file names and escapes - # backslashes for cygpath. The second sed program outputs the file - # name when reading, but also accumulates all include files in the - # hold buffer in order to output them again at the end. This only - # works with sed implementations that can handle large buffers. - sed < "$tmpdepfile" -n ' -/^Note: including file: *\(.*\)/ { - s//\1/ - s/\\/\\\\/g - p -}' | $cygpath_u | sort -u | sed -n ' -s/ /\\ /g -s/\(.*\)/'"$tab"'\1 \\/p -s/.\(.*\) \\/\1:/ -H -$ { - s/.*/'"$tab"'/ - G - p -}' >> "$depfile" - echo >> "$depfile" # make sure the fragment doesn't end with a backslash - rm -f "$tmpdepfile" - ;; - -msvc7msys) - # This case exists only to let depend.m4 do its work. It works by - # looking at the text of this script. This case will never be run, - # since it is checked for above. - exit 1 - ;; - -#nosideeffect) - # This comment above is used by automake to tell side-effect - # dependency tracking mechanisms from slower ones. - -dashmstdout) - # Important note: in order to support this mode, a compiler *must* - # always write the preprocessed file to stdout, regardless of -o. - "$@" || exit $? - - # Remove the call to Libtool. - if test "$libtool" = yes; then - while test "X$1" != 'X--mode=compile'; do - shift - done - shift - fi - - # Remove '-o $object'. - IFS=" " - for arg - do - case $arg in - -o) - shift - ;; - $object) - shift - ;; - *) - set fnord "$@" "$arg" - shift # fnord - shift # $arg - ;; - esac - done - - test -z "$dashmflag" && dashmflag=-M - # Require at least two characters before searching for ':' - # in the target name. This is to cope with DOS-style filenames: - # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. - "$@" $dashmflag | - sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" - rm -f "$depfile" - cat < "$tmpdepfile" > "$depfile" - # Some versions of the HPUX 10.20 sed can't process this sed invocation - # correctly. Breaking it into two sed invocations is a workaround. - tr ' ' "$nl" < "$tmpdepfile" \ - | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ - | sed -e 's/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -dashXmstdout) - # This case only exists to satisfy depend.m4. It is never actually - # run, as this mode is specially recognized in the preamble. - exit 1 - ;; - -makedepend) - "$@" || exit $? - # Remove any Libtool call - if test "$libtool" = yes; then - while test "X$1" != 'X--mode=compile'; do - shift - done - shift - fi - # X makedepend - shift - cleared=no eat=no - for arg - do - case $cleared in - no) - set ""; shift - cleared=yes ;; - esac - if test $eat = yes; then - eat=no - continue - fi - case "$arg" in - -D*|-I*) - set fnord "$@" "$arg"; shift ;; - # Strip any option that makedepend may not understand. Remove - # the object too, otherwise makedepend will parse it as a source file. - -arch) - eat=yes ;; - -*|$object) - ;; - *) - set fnord "$@" "$arg"; shift ;; - esac - done - obj_suffix=`echo "$object" | sed 's/^.*\././'` - touch "$tmpdepfile" - ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" - rm -f "$depfile" - # makedepend may prepend the VPATH from the source file name to the object. - # No need to regex-escape $object, excess matching of '.' is harmless. - sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" - # Some versions of the HPUX 10.20 sed can't process the last invocation - # correctly. Breaking it into two sed invocations is a workaround. - sed '1,2d' "$tmpdepfile" \ - | tr ' ' "$nl" \ - | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ - | sed -e 's/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" "$tmpdepfile".bak - ;; - -cpp) - # Important note: in order to support this mode, a compiler *must* - # always write the preprocessed file to stdout. - "$@" || exit $? - - # Remove the call to Libtool. - if test "$libtool" = yes; then - while test "X$1" != 'X--mode=compile'; do - shift - done - shift - fi - - # Remove '-o $object'. - IFS=" " - for arg - do - case $arg in - -o) - shift - ;; - $object) - shift - ;; - *) - set fnord "$@" "$arg" - shift # fnord - shift # $arg - ;; - esac - done - - "$@" -E \ - | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ - -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ - | sed '$ s: \\$::' > "$tmpdepfile" - rm -f "$depfile" - echo "$object : \\" > "$depfile" - cat < "$tmpdepfile" >> "$depfile" - sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -msvisualcpp) - # Important note: in order to support this mode, a compiler *must* - # always write the preprocessed file to stdout. - "$@" || exit $? - - # Remove the call to Libtool. - if test "$libtool" = yes; then - while test "X$1" != 'X--mode=compile'; do - shift - done - shift - fi - - IFS=" " - for arg - do - case "$arg" in - -o) - shift - ;; - $object) - shift - ;; - "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") - set fnord "$@" - shift - shift - ;; - *) - set fnord "$@" "$arg" - shift - shift - ;; - esac - done - "$@" -E 2>/dev/null | - sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" - rm -f "$depfile" - echo "$object : \\" > "$depfile" - sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" - echo "$tab" >> "$depfile" - sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -msvcmsys) - # This case exists only to let depend.m4 do its work. It works by - # looking at the text of this script. This case will never be run, - # since it is checked for above. - exit 1 - ;; - -none) - exec "$@" - ;; - -*) - echo "Unknown depmode $depmode" 1>&2 - exit 1 - ;; -esac - -exit 0 - -# Local Variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC" -# time-stamp-end: "; # UTC" -# End: diff --git a/build/autotools/install-sh b/build/autotools/install-sh deleted file mode 100755 index 377bb868..00000000 --- a/build/autotools/install-sh +++ /dev/null @@ -1,527 +0,0 @@ -#!/bin/sh -# install - install a program, script, or datafile - -scriptversion=2011-11-20.07; # UTC - -# This originates from X11R5 (mit/util/scripts/install.sh), which was -# later released in X11R6 (xc/config/util/install.sh) with the -# following copyright and license. -# -# Copyright (C) 1994 X Consortium -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to -# deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -# sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- -# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the name of the X Consortium shall not -# be used in advertising or otherwise to promote the sale, use or other deal- -# ings in this Software without prior written authorization from the X Consor- -# tium. -# -# -# FSF changes to this file are in the public domain. -# -# Calling this script install-sh is preferred over install.sh, to prevent -# 'make' implicit rules from creating a file called install from it -# when there is no Makefile. -# -# This script is compatible with the BSD install script, but was written -# from scratch. - -nl=' -' -IFS=" "" $nl" - -# set DOITPROG to echo to test this script - -# Don't use :- since 4.3BSD and earlier shells don't like it. -doit=${DOITPROG-} -if test -z "$doit"; then - doit_exec=exec -else - doit_exec=$doit -fi - -# Put in absolute file names if you don't have them in your path; -# or use environment vars. - -chgrpprog=${CHGRPPROG-chgrp} -chmodprog=${CHMODPROG-chmod} -chownprog=${CHOWNPROG-chown} -cmpprog=${CMPPROG-cmp} -cpprog=${CPPROG-cp} -mkdirprog=${MKDIRPROG-mkdir} -mvprog=${MVPROG-mv} -rmprog=${RMPROG-rm} -stripprog=${STRIPPROG-strip} - -posix_glob='?' -initialize_posix_glob=' - test "$posix_glob" != "?" || { - if (set -f) 2>/dev/null; then - posix_glob= - else - posix_glob=: - fi - } -' - -posix_mkdir= - -# Desired mode of installed file. -mode=0755 - -chgrpcmd= -chmodcmd=$chmodprog -chowncmd= -mvcmd=$mvprog -rmcmd="$rmprog -f" -stripcmd= - -src= -dst= -dir_arg= -dst_arg= - -copy_on_change=false -no_target_directory= - -usage="\ -Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE - or: $0 [OPTION]... SRCFILES... DIRECTORY - or: $0 [OPTION]... -t DIRECTORY SRCFILES... - or: $0 [OPTION]... -d DIRECTORIES... - -In the 1st form, copy SRCFILE to DSTFILE. -In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. -In the 4th, create DIRECTORIES. - -Options: - --help display this help and exit. - --version display version info and exit. - - -c (ignored) - -C install only if different (preserve the last data modification time) - -d create directories instead of installing files. - -g GROUP $chgrpprog installed files to GROUP. - -m MODE $chmodprog installed files to MODE. - -o USER $chownprog installed files to USER. - -s $stripprog installed files. - -t DIRECTORY install into DIRECTORY. - -T report an error if DSTFILE is a directory. - -Environment variables override the default commands: - CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG - RMPROG STRIPPROG -" - -while test $# -ne 0; do - case $1 in - -c) ;; - - -C) copy_on_change=true;; - - -d) dir_arg=true;; - - -g) chgrpcmd="$chgrpprog $2" - shift;; - - --help) echo "$usage"; exit $?;; - - -m) mode=$2 - case $mode in - *' '* | *' '* | *' -'* | *'*'* | *'?'* | *'['*) - echo "$0: invalid mode: $mode" >&2 - exit 1;; - esac - shift;; - - -o) chowncmd="$chownprog $2" - shift;; - - -s) stripcmd=$stripprog;; - - -t) dst_arg=$2 - # Protect names problematic for 'test' and other utilities. - case $dst_arg in - -* | [=\(\)!]) dst_arg=./$dst_arg;; - esac - shift;; - - -T) no_target_directory=true;; - - --version) echo "$0 $scriptversion"; exit $?;; - - --) shift - break;; - - -*) echo "$0: invalid option: $1" >&2 - exit 1;; - - *) break;; - esac - shift -done - -if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then - # When -d is used, all remaining arguments are directories to create. - # When -t is used, the destination is already specified. - # Otherwise, the last argument is the destination. Remove it from $@. - for arg - do - if test -n "$dst_arg"; then - # $@ is not empty: it contains at least $arg. - set fnord "$@" "$dst_arg" - shift # fnord - fi - shift # arg - dst_arg=$arg - # Protect names problematic for 'test' and other utilities. - case $dst_arg in - -* | [=\(\)!]) dst_arg=./$dst_arg;; - esac - done -fi - -if test $# -eq 0; then - if test -z "$dir_arg"; then - echo "$0: no input file specified." >&2 - exit 1 - fi - # It's OK to call 'install-sh -d' without argument. - # This can happen when creating conditional directories. - exit 0 -fi - -if test -z "$dir_arg"; then - do_exit='(exit $ret); exit $ret' - trap "ret=129; $do_exit" 1 - trap "ret=130; $do_exit" 2 - trap "ret=141; $do_exit" 13 - trap "ret=143; $do_exit" 15 - - # Set umask so as not to create temps with too-generous modes. - # However, 'strip' requires both read and write access to temps. - case $mode in - # Optimize common cases. - *644) cp_umask=133;; - *755) cp_umask=22;; - - *[0-7]) - if test -z "$stripcmd"; then - u_plus_rw= - else - u_plus_rw='% 200' - fi - cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; - *) - if test -z "$stripcmd"; then - u_plus_rw= - else - u_plus_rw=,u+rw - fi - cp_umask=$mode$u_plus_rw;; - esac -fi - -for src -do - # Protect names problematic for 'test' and other utilities. - case $src in - -* | [=\(\)!]) src=./$src;; - esac - - if test -n "$dir_arg"; then - dst=$src - dstdir=$dst - test -d "$dstdir" - dstdir_status=$? - else - - # Waiting for this to be detected by the "$cpprog $src $dsttmp" command - # might cause directories to be created, which would be especially bad - # if $src (and thus $dsttmp) contains '*'. - if test ! -f "$src" && test ! -d "$src"; then - echo "$0: $src does not exist." >&2 - exit 1 - fi - - if test -z "$dst_arg"; then - echo "$0: no destination specified." >&2 - exit 1 - fi - dst=$dst_arg - - # If destination is a directory, append the input filename; won't work - # if double slashes aren't ignored. - if test -d "$dst"; then - if test -n "$no_target_directory"; then - echo "$0: $dst_arg: Is a directory" >&2 - exit 1 - fi - dstdir=$dst - dst=$dstdir/`basename "$src"` - dstdir_status=0 - else - # Prefer dirname, but fall back on a substitute if dirname fails. - dstdir=` - (dirname "$dst") 2>/dev/null || - expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$dst" : 'X\(//\)[^/]' \| \ - X"$dst" : 'X\(//\)$' \| \ - X"$dst" : 'X\(/\)' \| . 2>/dev/null || - echo X"$dst" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q' - ` - - test -d "$dstdir" - dstdir_status=$? - fi - fi - - obsolete_mkdir_used=false - - if test $dstdir_status != 0; then - case $posix_mkdir in - '') - # Create intermediate dirs using mode 755 as modified by the umask. - # This is like FreeBSD 'install' as of 1997-10-28. - umask=`umask` - case $stripcmd.$umask in - # Optimize common cases. - *[2367][2367]) mkdir_umask=$umask;; - .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; - - *[0-7]) - mkdir_umask=`expr $umask + 22 \ - - $umask % 100 % 40 + $umask % 20 \ - - $umask % 10 % 4 + $umask % 2 - `;; - *) mkdir_umask=$umask,go-w;; - esac - - # With -d, create the new directory with the user-specified mode. - # Otherwise, rely on $mkdir_umask. - if test -n "$dir_arg"; then - mkdir_mode=-m$mode - else - mkdir_mode= - fi - - posix_mkdir=false - case $umask in - *[123567][0-7][0-7]) - # POSIX mkdir -p sets u+wx bits regardless of umask, which - # is incompatible with FreeBSD 'install' when (umask & 300) != 0. - ;; - *) - tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ - trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 - - if (umask $mkdir_umask && - exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 - then - if test -z "$dir_arg" || { - # Check for POSIX incompatibilities with -m. - # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or - # other-writable bit of parent directory when it shouldn't. - # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. - ls_ld_tmpdir=`ls -ld "$tmpdir"` - case $ls_ld_tmpdir in - d????-?r-*) different_mode=700;; - d????-?--*) different_mode=755;; - *) false;; - esac && - $mkdirprog -m$different_mode -p -- "$tmpdir" && { - ls_ld_tmpdir_1=`ls -ld "$tmpdir"` - test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" - } - } - then posix_mkdir=: - fi - rmdir "$tmpdir/d" "$tmpdir" - else - # Remove any dirs left behind by ancient mkdir implementations. - rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null - fi - trap '' 0;; - esac;; - esac - - if - $posix_mkdir && ( - umask $mkdir_umask && - $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" - ) - then : - else - - # The umask is ridiculous, or mkdir does not conform to POSIX, - # or it failed possibly due to a race condition. Create the - # directory the slow way, step by step, checking for races as we go. - - case $dstdir in - /*) prefix='/';; - [-=\(\)!]*) prefix='./';; - *) prefix='';; - esac - - eval "$initialize_posix_glob" - - oIFS=$IFS - IFS=/ - $posix_glob set -f - set fnord $dstdir - shift - $posix_glob set +f - IFS=$oIFS - - prefixes= - - for d - do - test X"$d" = X && continue - - prefix=$prefix$d - if test -d "$prefix"; then - prefixes= - else - if $posix_mkdir; then - (umask=$mkdir_umask && - $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break - # Don't fail if two instances are running concurrently. - test -d "$prefix" || exit 1 - else - case $prefix in - *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; - *) qprefix=$prefix;; - esac - prefixes="$prefixes '$qprefix'" - fi - fi - prefix=$prefix/ - done - - if test -n "$prefixes"; then - # Don't fail if two instances are running concurrently. - (umask $mkdir_umask && - eval "\$doit_exec \$mkdirprog $prefixes") || - test -d "$dstdir" || exit 1 - obsolete_mkdir_used=true - fi - fi - fi - - if test -n "$dir_arg"; then - { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && - { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && - { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || - test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 - else - - # Make a couple of temp file names in the proper directory. - dsttmp=$dstdir/_inst.$$_ - rmtmp=$dstdir/_rm.$$_ - - # Trap to clean up those temp files at exit. - trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 - - # Copy the file name to the temp name. - (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && - - # and set any options; do chmod last to preserve setuid bits. - # - # If any of these fail, we abort the whole thing. If we want to - # ignore errors from any of these, just make sure not to ignore - # errors from the above "$doit $cpprog $src $dsttmp" command. - # - { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && - { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && - { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && - { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && - - # If -C, don't bother to copy if it wouldn't change the file. - if $copy_on_change && - old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && - new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && - - eval "$initialize_posix_glob" && - $posix_glob set -f && - set X $old && old=:$2:$4:$5:$6 && - set X $new && new=:$2:$4:$5:$6 && - $posix_glob set +f && - - test "$old" = "$new" && - $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 - then - rm -f "$dsttmp" - else - # Rename the file to the real destination. - $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || - - # The rename failed, perhaps because mv can't rename something else - # to itself, or perhaps because mv is so ancient that it does not - # support -f. - { - # Now remove or move aside any old file at destination location. - # We try this two ways since rm can't unlink itself on some - # systems and the destination file might be busy for other - # reasons. In this case, the final cleanup might fail but the new - # file should still install successfully. - { - test ! -f "$dst" || - $doit $rmcmd -f "$dst" 2>/dev/null || - { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && - { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } - } || - { echo "$0: cannot unlink or rename $dst" >&2 - (exit 1); exit 1 - } - } && - - # Now rename the file to the real destination. - $doit $mvcmd "$dsttmp" "$dst" - } - fi || exit 1 - - trap '' 0 - fi -done - -# Local variables: -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC" -# time-stamp-end: "; # UTC" -# End: diff --git a/build/autotools/m4/ax_boost_base.m4 b/build/autotools/m4/ax_boost_base.m4 deleted file mode 100644 index 8e6ee9a9..00000000 --- a/build/autotools/m4/ax_boost_base.m4 +++ /dev/null @@ -1,272 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_boost_base.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_BOOST_BASE([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) -# -# DESCRIPTION -# -# Test for the Boost C++ libraries of a particular version (or newer) -# -# If no path to the installed boost library is given the macro searchs -# under /usr, /usr/local, /opt and /opt/local and evaluates the -# $BOOST_ROOT environment variable. Further documentation is available at -# . -# -# This macro calls: -# -# AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS) -# -# And sets: -# -# HAVE_BOOST -# -# LICENSE -# -# Copyright (c) 2008 Thomas Porschberg -# Copyright (c) 2009 Peter Adolphs -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 23 - -AC_DEFUN([AX_BOOST_BASE], -[ -AC_ARG_WITH([boost], - [AS_HELP_STRING([--with-boost@<:@=ARG@:>@], - [use Boost library from a standard location (ARG=yes), - from the specified location (ARG=), - or disable it (ARG=no) - @<:@ARG=yes@:>@ ])], - [ - if test "$withval" = "no"; then - want_boost="no" - elif test "$withval" = "yes"; then - want_boost="yes" - ac_boost_path="" - else - want_boost="yes" - ac_boost_path="$withval" - fi - ], - [want_boost="yes"]) - - -AC_ARG_WITH([boost-libdir], - AS_HELP_STRING([--with-boost-libdir=LIB_DIR], - [Force given directory for boost libraries. Note that this will override library path detection, so use this parameter only if default library detection fails and you know exactly where your boost libraries are located.]), - [ - if test -d "$withval" - then - ac_boost_lib_path="$withval" - else - AC_MSG_ERROR(--with-boost-libdir expected directory name) - fi - ], - [ac_boost_lib_path=""] -) - -if test "x$want_boost" = "xyes"; then - boost_lib_version_req=ifelse([$1], ,1.20.0,$1) - boost_lib_version_req_shorten=`expr $boost_lib_version_req : '\([[0-9]]*\.[[0-9]]*\)'` - boost_lib_version_req_major=`expr $boost_lib_version_req : '\([[0-9]]*\)'` - boost_lib_version_req_minor=`expr $boost_lib_version_req : '[[0-9]]*\.\([[0-9]]*\)'` - boost_lib_version_req_sub_minor=`expr $boost_lib_version_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'` - if test "x$boost_lib_version_req_sub_minor" = "x" ; then - boost_lib_version_req_sub_minor="0" - fi - WANT_BOOST_VERSION=`expr $boost_lib_version_req_major \* 100000 \+ $boost_lib_version_req_minor \* 100 \+ $boost_lib_version_req_sub_minor` - AC_MSG_CHECKING(for boostlib >= $boost_lib_version_req) - succeeded=no - - dnl On 64-bit systems check for system libraries in both lib64 and lib. - dnl The former is specified by FHS, but e.g. Debian does not adhere to - dnl this (as it rises problems for generic multi-arch support). - dnl The last entry in the list is chosen by default when no libraries - dnl are found, e.g. when only header-only libraries are installed! - libsubdirs="lib" - ax_arch=`uname -m` - case $ax_arch in - x86_64|ppc64|s390x|sparc64|aarch64) - libsubdirs="lib64 lib lib64" - ;; - esac - - dnl allow for real multi-arch paths e.g. /usr/lib/x86_64-linux-gnu. Give - dnl them priority over the other paths since, if libs are found there, they - dnl are almost assuredly the ones desired. - AC_REQUIRE([AC_CANONICAL_HOST]) - libsubdirs="lib/${host_cpu}-${host_os} $libsubdirs" - - case ${host_cpu} in - i?86) - libsubdirs="lib/i386-${host_os} $libsubdirs" - ;; - esac - - dnl first we check the system location for boost libraries - dnl this location ist chosen if boost libraries are installed with the --layout=system option - dnl or if you install boost with RPM - if test "$ac_boost_path" != ""; then - BOOST_CPPFLAGS="-I$ac_boost_path/include" - for ac_boost_path_tmp in $libsubdirs; do - if test -d "$ac_boost_path"/"$ac_boost_path_tmp" ; then - BOOST_LDFLAGS="-L$ac_boost_path/$ac_boost_path_tmp" - break - fi - done - elif test "$cross_compiling" != yes; then - for ac_boost_path_tmp in /usr /usr/local /opt /opt/local ; do - if test -d "$ac_boost_path_tmp/include/boost" && test -r "$ac_boost_path_tmp/include/boost"; then - for libsubdir in $libsubdirs ; do - if ls "$ac_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi - done - BOOST_LDFLAGS="-L$ac_boost_path_tmp/$libsubdir" - BOOST_CPPFLAGS="-I$ac_boost_path_tmp/include" - break; - fi - done - fi - - dnl overwrite ld flags if we have required special directory with - dnl --with-boost-libdir parameter - if test "$ac_boost_lib_path" != ""; then - BOOST_LDFLAGS="-L$ac_boost_lib_path" - fi - - CPPFLAGS_SAVED="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - - AC_REQUIRE([AC_PROG_CXX]) - AC_LANG_PUSH(C++) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ - @%:@include - ]], [[ - #if BOOST_VERSION >= $WANT_BOOST_VERSION - // Everything is okay - #else - # error Boost version is too old - #endif - ]])],[ - AC_MSG_RESULT(yes) - succeeded=yes - found_system=yes - ],[ - ]) - AC_LANG_POP([C++]) - - - - dnl if we found no boost with system layout we search for boost libraries - dnl built and installed without the --layout=system option or for a staged(not installed) version - if test "x$succeeded" != "xyes"; then - _version=0 - if test "$ac_boost_path" != ""; then - if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then - for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do - _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` - V_CHECK=`expr $_version_tmp \> $_version` - if test "$V_CHECK" = "1" ; then - _version=$_version_tmp - fi - VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` - BOOST_CPPFLAGS="-I$ac_boost_path/include/boost-$VERSION_UNDERSCORE" - done - fi - else - if test "$cross_compiling" != yes; then - for ac_boost_path in /usr /usr/local /opt /opt/local ; do - if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then - for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do - _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` - V_CHECK=`expr $_version_tmp \> $_version` - if test "$V_CHECK" = "1" ; then - _version=$_version_tmp - best_path=$ac_boost_path - fi - done - fi - done - - VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` - BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE" - if test "$ac_boost_lib_path" = ""; then - for libsubdir in $libsubdirs ; do - if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi - done - BOOST_LDFLAGS="-L$best_path/$libsubdir" - fi - fi - - if test "x$BOOST_ROOT" != "x"; then - for libsubdir in $libsubdirs ; do - if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi - done - if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then - version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'` - stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'` - stage_version_shorten=`expr $stage_version : '\([[0-9]]*\.[[0-9]]*\)'` - V_CHECK=`expr $stage_version_shorten \>\= $_version` - if test "$V_CHECK" = "1" -a "$ac_boost_lib_path" = "" ; then - AC_MSG_NOTICE(We will use a staged boost library from $BOOST_ROOT) - BOOST_CPPFLAGS="-I$BOOST_ROOT" - BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir" - fi - fi - fi - fi - - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - - AC_LANG_PUSH(C++) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ - @%:@include - ]], [[ - #if BOOST_VERSION >= $WANT_BOOST_VERSION - // Everything is okay - #else - # error Boost version is too old - #endif - ]])],[ - AC_MSG_RESULT(yes) - succeeded=yes - found_system=yes - ],[ - ]) - AC_LANG_POP([C++]) - fi - - if test "$succeeded" != "yes" ; then - if test "$_version" = "0" ; then - AC_MSG_NOTICE([[We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation.]]) - else - AC_MSG_NOTICE([Your boost libraries seems to old (version $_version).]) - fi - # execute ACTION-IF-NOT-FOUND (if present): - ifelse([$3], , :, [$3]) - else - AC_SUBST(BOOST_CPPFLAGS) - AC_SUBST(BOOST_LDFLAGS) - AC_DEFINE(HAVE_BOOST,,[define if the Boost library is available]) - # execute ACTION-IF-FOUND (if present): - ifelse([$2], , :, [$2]) - fi - - CPPFLAGS="$CPPFLAGS_SAVED" - LDFLAGS="$LDFLAGS_SAVED" -fi - -]) diff --git a/build/autotools/m4/ax_boost_date_time.m4 b/build/autotools/m4/ax_boost_date_time.m4 deleted file mode 100644 index ec9c0445..00000000 --- a/build/autotools/m4/ax_boost_date_time.m4 +++ /dev/null @@ -1,113 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_boost_date_time.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_BOOST_DATE_TIME -# -# DESCRIPTION -# -# Test for Date_Time library from the Boost C++ libraries. The macro -# requires a preceding call to AX_BOOST_BASE. Further documentation is -# available at . -# -# This macro calls: -# -# AC_SUBST(BOOST_DATE_TIME_LIB) -# -# And sets: -# -# HAVE_BOOST_DATE_TIME -# -# LICENSE -# -# Copyright (c) 2008 Thomas Porschberg -# Copyright (c) 2008 Michael Tindal -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 21 - -AC_DEFUN([AX_BOOST_DATE_TIME], -[ - AC_ARG_WITH([boost-date-time], - AS_HELP_STRING([--with-boost-date-time@<:@=special-lib@:>@], - [use the Date_Time library from boost - it is possible to specify a certain library for the linker - e.g. --with-boost-date-time=boost_date_time-gcc-mt-d-1_33_1 ]), - [ - if test "$withval" = "no"; then - want_boost="no" - elif test "$withval" = "yes"; then - want_boost="yes" - ax_boost_user_date_time_lib="" - else - want_boost="yes" - ax_boost_user_date_time_lib="$withval" - fi - ], - [want_boost="yes"] - ) - - if test "x$want_boost" = "xyes"; then - AC_REQUIRE([AC_PROG_CC]) - CPPFLAGS_SAVED="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - - AC_CACHE_CHECK(whether the Boost::Date_Time library is available, - ax_cv_boost_date_time, - [AC_LANG_PUSH([C++]) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include ]], - [[using namespace boost::gregorian; date d(2002,Jan,10); - return 0; - ]])], - ax_cv_boost_date_time=yes, ax_cv_boost_date_time=no) - AC_LANG_POP([C++]) - ]) - if test "x$ax_cv_boost_date_time" = "xyes"; then - AC_DEFINE(HAVE_BOOST_DATE_TIME,,[define if the Boost::Date_Time library is available]) - BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` - if test "x$ax_boost_user_date_time_lib" = "x"; then - for libextension in `ls $BOOSTLIBDIR/libboost_date_time*.so* $BOOSTLIBDIR/libboost_date_time*.dylib* $BOOSTLIBDIR/libboost_date_time*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_date_time.*\)\.so.*$;\1;' -e 's;^lib\(boost_date_time.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_date_time.*\)\.a*$;\1;'` ; do - ax_lib=${libextension} - AC_CHECK_LIB($ax_lib, exit, - [BOOST_DATE_TIME_LIB="-l$ax_lib"; AC_SUBST(BOOST_DATE_TIME_LIB) link_date_time="yes"; break], - [link_date_time="no"]) - done - if test "x$link_date_time" != "xyes"; then - for libextension in `ls $BOOSTLIBDIR/boost_date_time*.dll* $BOOSTLIBDIR/boost_date_time*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_date_time.*\)\.dll.*$;\1;' -e 's;^\(boost_date_time.*\)\.a.*$;\1;'` ; do - ax_lib=${libextension} - AC_CHECK_LIB($ax_lib, exit, - [BOOST_DATE_TIME_LIB="-l$ax_lib"; AC_SUBST(BOOST_DATE_TIME_LIB) link_date_time="yes"; break], - [link_date_time="no"]) - done - fi - - else - for ax_lib in $ax_boost_user_date_time_lib boost_date_time-$ax_boost_user_date_time_lib; do - AC_CHECK_LIB($ax_lib, main, - [BOOST_DATE_TIME_LIB="-l$ax_lib"; AC_SUBST(BOOST_DATE_TIME_LIB) link_date_time="yes"; break], - [link_date_time="no"]) - done - - fi - if test "x$ax_lib" = "x"; then - AC_MSG_ERROR(Could not find a version of the library!) - fi - if test "x$link_date_time" != "xyes"; then - AC_MSG_ERROR(Could not link against $ax_lib !) - fi - fi - - CPPFLAGS="$CPPFLAGS_SAVED" - LDFLAGS="$LDFLAGS_SAVED" - fi -]) diff --git a/build/autotools/m4/ax_boost_filesystem.m4 b/build/autotools/m4/ax_boost_filesystem.m4 deleted file mode 100644 index f162163c..00000000 --- a/build/autotools/m4/ax_boost_filesystem.m4 +++ /dev/null @@ -1,118 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_boost_filesystem.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_BOOST_FILESYSTEM -# -# DESCRIPTION -# -# Test for Filesystem library from the Boost C++ libraries. The macro -# requires a preceding call to AX_BOOST_BASE. Further documentation is -# available at . -# -# This macro calls: -# -# AC_SUBST(BOOST_FILESYSTEM_LIB) -# -# And sets: -# -# HAVE_BOOST_FILESYSTEM -# -# LICENSE -# -# Copyright (c) 2009 Thomas Porschberg -# Copyright (c) 2009 Michael Tindal -# Copyright (c) 2009 Roman Rybalko -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 26 - -AC_DEFUN([AX_BOOST_FILESYSTEM], -[ - AC_ARG_WITH([boost-filesystem], - AS_HELP_STRING([--with-boost-filesystem@<:@=special-lib@:>@], - [use the Filesystem library from boost - it is possible to specify a certain library for the linker - e.g. --with-boost-filesystem=boost_filesystem-gcc-mt ]), - [ - if test "$withval" = "no"; then - want_boost="no" - elif test "$withval" = "yes"; then - want_boost="yes" - ax_boost_user_filesystem_lib="" - else - want_boost="yes" - ax_boost_user_filesystem_lib="$withval" - fi - ], - [want_boost="yes"] - ) - - if test "x$want_boost" = "xyes"; then - AC_REQUIRE([AC_PROG_CC]) - CPPFLAGS_SAVED="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - - LIBS_SAVED=$LIBS - LIBS="$LIBS $BOOST_SYSTEM_LIB" - export LIBS - - AC_CACHE_CHECK(whether the Boost::Filesystem library is available, - ax_cv_boost_filesystem, - [AC_LANG_PUSH([C++]) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include ]], - [[using namespace boost::filesystem; - path my_path( "foo/bar/data.txt" ); - return 0;]])], - ax_cv_boost_filesystem=yes, ax_cv_boost_filesystem=no) - AC_LANG_POP([C++]) - ]) - if test "x$ax_cv_boost_filesystem" = "xyes"; then - AC_DEFINE(HAVE_BOOST_FILESYSTEM,,[define if the Boost::Filesystem library is available]) - BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` - if test "x$ax_boost_user_filesystem_lib" = "x"; then - for libextension in `ls -r $BOOSTLIBDIR/libboost_filesystem* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'` ; do - ax_lib=${libextension} - AC_CHECK_LIB($ax_lib, exit, - [BOOST_FILESYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_FILESYSTEM_LIB) link_filesystem="yes"; break], - [link_filesystem="no"]) - done - if test "x$link_filesystem" != "xyes"; then - for libextension in `ls -r $BOOSTLIBDIR/boost_filesystem* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\..*,,'` ; do - ax_lib=${libextension} - AC_CHECK_LIB($ax_lib, exit, - [BOOST_FILESYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_FILESYSTEM_LIB) link_filesystem="yes"; break], - [link_filesystem="no"]) - done - fi - else - for ax_lib in $ax_boost_user_filesystem_lib boost_filesystem-$ax_boost_user_filesystem_lib; do - AC_CHECK_LIB($ax_lib, exit, - [BOOST_FILESYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_FILESYSTEM_LIB) link_filesystem="yes"; break], - [link_filesystem="no"]) - done - - fi - if test "x$ax_lib" = "x"; then - AC_MSG_ERROR(Could not find a version of the library!) - fi - if test "x$link_filesystem" != "xyes"; then - AC_MSG_ERROR(Could not link against $ax_lib !) - fi - fi - - CPPFLAGS="$CPPFLAGS_SAVED" - LDFLAGS="$LDFLAGS_SAVED" - LIBS="$LIBS_SAVED" - fi -]) diff --git a/build/autotools/m4/ax_boost_program_options.m4 b/build/autotools/m4/ax_boost_program_options.m4 deleted file mode 100644 index 65a39c8c..00000000 --- a/build/autotools/m4/ax_boost_program_options.m4 +++ /dev/null @@ -1,108 +0,0 @@ -# ============================================================================ -# http://www.gnu.org/software/autoconf-archive/ax_boost_program_options.html -# ============================================================================ -# -# SYNOPSIS -# -# AX_BOOST_PROGRAM_OPTIONS -# -# DESCRIPTION -# -# Test for program options library from the Boost C++ libraries. The macro -# requires a preceding call to AX_BOOST_BASE. Further documentation is -# available at . -# -# This macro calls: -# -# AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB) -# -# And sets: -# -# HAVE_BOOST_PROGRAM_OPTIONS -# -# LICENSE -# -# Copyright (c) 2009 Thomas Porschberg -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 22 - -AC_DEFUN([AX_BOOST_PROGRAM_OPTIONS], -[ - AC_ARG_WITH([boost-program-options], - AS_HELP_STRING([--with-boost-program-options@<:@=special-lib@:>@], - [use the program options library from boost - it is possible to specify a certain library for the linker - e.g. --with-boost-program-options=boost_program_options-gcc-mt-1_33_1 ]), - [ - if test "$withval" = "no"; then - want_boost="no" - elif test "$withval" = "yes"; then - want_boost="yes" - ax_boost_user_program_options_lib="" - else - want_boost="yes" - ax_boost_user_program_options_lib="$withval" - fi - ], - [want_boost="yes"] - ) - - if test "x$want_boost" = "xyes"; then - AC_REQUIRE([AC_PROG_CC]) - export want_boost - CPPFLAGS_SAVED="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - AC_CACHE_CHECK([whether the Boost::Program_Options library is available], - ax_cv_boost_program_options, - [AC_LANG_PUSH(C++) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include - ]], - [[boost::program_options::options_description generic("Generic options"); - return 0;]])], - ax_cv_boost_program_options=yes, ax_cv_boost_program_options=no) - AC_LANG_POP([C++]) - ]) - if test "$ax_cv_boost_program_options" = yes; then - AC_DEFINE(HAVE_BOOST_PROGRAM_OPTIONS,,[define if the Boost::PROGRAM_OPTIONS library is available]) - BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` - if test "x$ax_boost_user_program_options_lib" = "x"; then - for libextension in `ls $BOOSTLIBDIR/libboost_program_options*.so* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.so.*$;\1;'` `ls $BOOSTLIBDIR/libboost_program_options*.dylib* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.dylib.*$;\1;'` `ls $BOOSTLIBDIR/libboost_program_options*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.a.*$;\1;'` ; do - ax_lib=${libextension} - AC_CHECK_LIB($ax_lib, exit, - [BOOST_PROGRAM_OPTIONS_LIB="-l$ax_lib"; AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB) link_program_options="yes"; break], - [link_program_options="no"]) - done - if test "x$link_program_options" != "xyes"; then - for libextension in `ls $BOOSTLIBDIR/boost_program_options*.dll* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_program_options.*\)\.dll.*$;\1;'` `ls $BOOSTLIBDIR/boost_program_options*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_program_options.*\)\.a.*$;\1;'` ; do - ax_lib=${libextension} - AC_CHECK_LIB($ax_lib, exit, - [BOOST_PROGRAM_OPTIONS_LIB="-l$ax_lib"; AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB) link_program_options="yes"; break], - [link_program_options="no"]) - done - fi - else - for ax_lib in $ax_boost_user_program_options_lib boost_program_options-$ax_boost_user_program_options_lib; do - AC_CHECK_LIB($ax_lib, main, - [BOOST_PROGRAM_OPTIONS_LIB="-l$ax_lib"; AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB) link_program_options="yes"; break], - [link_program_options="no"]) - done - fi - if test "x$ax_lib" = "x"; then - AC_MSG_ERROR(Could not find a version of the library!) - fi - if test "x$link_program_options" != "xyes"; then - AC_MSG_ERROR([Could not link against [$ax_lib] !]) - fi - fi - CPPFLAGS="$CPPFLAGS_SAVED" - LDFLAGS="$LDFLAGS_SAVED" - fi -]) diff --git a/build/autotools/m4/ax_boost_regex.m4 b/build/autotools/m4/ax_boost_regex.m4 deleted file mode 100644 index 918f16a4..00000000 --- a/build/autotools/m4/ax_boost_regex.m4 +++ /dev/null @@ -1,111 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_boost_regex.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_BOOST_REGEX -# -# DESCRIPTION -# -# Test for Regex library from the Boost C++ libraries. The macro requires -# a preceding call to AX_BOOST_BASE. Further documentation is available at -# . -# -# This macro calls: -# -# AC_SUBST(BOOST_REGEX_LIB) -# -# And sets: -# -# HAVE_BOOST_REGEX -# -# LICENSE -# -# Copyright (c) 2008 Thomas Porschberg -# Copyright (c) 2008 Michael Tindal -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 22 - -AC_DEFUN([AX_BOOST_REGEX], -[ - AC_ARG_WITH([boost-regex], - AS_HELP_STRING([--with-boost-regex@<:@=special-lib@:>@], - [use the Regex library from boost - it is possible to specify a certain library for the linker - e.g. --with-boost-regex=boost_regex-gcc-mt-d-1_33_1 ]), - [ - if test "$withval" = "no"; then - want_boost="no" - elif test "$withval" = "yes"; then - want_boost="yes" - ax_boost_user_regex_lib="" - else - want_boost="yes" - ax_boost_user_regex_lib="$withval" - fi - ], - [want_boost="yes"] - ) - - if test "x$want_boost" = "xyes"; then - AC_REQUIRE([AC_PROG_CC]) - CPPFLAGS_SAVED="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - - AC_CACHE_CHECK(whether the Boost::Regex library is available, - ax_cv_boost_regex, - [AC_LANG_PUSH([C++]) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include - ]], - [[boost::regex r(); return 0;]])], - ax_cv_boost_regex=yes, ax_cv_boost_regex=no) - AC_LANG_POP([C++]) - ]) - if test "x$ax_cv_boost_regex" = "xyes"; then - AC_DEFINE(HAVE_BOOST_REGEX,,[define if the Boost::Regex library is available]) - BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` - if test "x$ax_boost_user_regex_lib" = "x"; then - for libextension in `ls $BOOSTLIBDIR/libboost_regex*.so* $BOOSTLIBDIR/libboost_regex*.dylib* $BOOSTLIBDIR/libboost_regex*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_regex.*\)\.so.*$;\1;' -e 's;^lib\(boost_regex.*\)\.dylib.*;\1;' -e 's;^lib\(boost_regex.*\)\.a.*$;\1;'` ; do - ax_lib=${libextension} - AC_CHECK_LIB($ax_lib, exit, - [BOOST_REGEX_LIB="-l$ax_lib"; AC_SUBST(BOOST_REGEX_LIB) link_regex="yes"; break], - [link_regex="no"]) - done - if test "x$link_regex" != "xyes"; then - for libextension in `ls $BOOSTLIBDIR/boost_regex*.dll* $BOOSTLIBDIR/boost_regex*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_regex.*\)\.dll.*$;\1;' -e 's;^\(boost_regex.*\)\.a.*$;\1;'` ; do - ax_lib=${libextension} - AC_CHECK_LIB($ax_lib, exit, - [BOOST_REGEX_LIB="-l$ax_lib"; AC_SUBST(BOOST_REGEX_LIB) link_regex="yes"; break], - [link_regex="no"]) - done - fi - - else - for ax_lib in $ax_boost_user_regex_lib boost_regex-$ax_boost_user_regex_lib; do - AC_CHECK_LIB($ax_lib, main, - [BOOST_REGEX_LIB="-l$ax_lib"; AC_SUBST(BOOST_REGEX_LIB) link_regex="yes"; break], - [link_regex="no"]) - done - fi - if test "x$ax_lib" = "x"; then - AC_MSG_ERROR(Could not find a version of the Boost::Regex library!) - fi - if test "x$link_regex" != "xyes"; then - AC_MSG_ERROR(Could not link against $ax_lib !) - fi - fi - - CPPFLAGS="$CPPFLAGS_SAVED" - LDFLAGS="$LDFLAGS_SAVED" - fi -]) diff --git a/build/autotools/m4/ax_boost_system.m4 b/build/autotools/m4/ax_boost_system.m4 deleted file mode 100644 index c4c45559..00000000 --- a/build/autotools/m4/ax_boost_system.m4 +++ /dev/null @@ -1,120 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_boost_system.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_BOOST_SYSTEM -# -# DESCRIPTION -# -# Test for System library from the Boost C++ libraries. The macro requires -# a preceding call to AX_BOOST_BASE. Further documentation is available at -# . -# -# This macro calls: -# -# AC_SUBST(BOOST_SYSTEM_LIB) -# -# And sets: -# -# HAVE_BOOST_SYSTEM -# -# LICENSE -# -# Copyright (c) 2008 Thomas Porschberg -# Copyright (c) 2008 Michael Tindal -# Copyright (c) 2008 Daniel Casimiro -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 17 - -AC_DEFUN([AX_BOOST_SYSTEM], -[ - AC_ARG_WITH([boost-system], - AS_HELP_STRING([--with-boost-system@<:@=special-lib@:>@], - [use the System library from boost - it is possible to specify a certain library for the linker - e.g. --with-boost-system=boost_system-gcc-mt ]), - [ - if test "$withval" = "no"; then - want_boost="no" - elif test "$withval" = "yes"; then - want_boost="yes" - ax_boost_user_system_lib="" - else - want_boost="yes" - ax_boost_user_system_lib="$withval" - fi - ], - [want_boost="yes"] - ) - - if test "x$want_boost" = "xyes"; then - AC_REQUIRE([AC_PROG_CC]) - AC_REQUIRE([AC_CANONICAL_BUILD]) - CPPFLAGS_SAVED="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - export CPPFLAGS - - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" - export LDFLAGS - - AC_CACHE_CHECK(whether the Boost::System library is available, - ax_cv_boost_system, - [AC_LANG_PUSH([C++]) - CXXFLAGS_SAVE=$CXXFLAGS - - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include ]], - [[boost::system::system_category]])], - ax_cv_boost_system=yes, ax_cv_boost_system=no) - CXXFLAGS=$CXXFLAGS_SAVE - AC_LANG_POP([C++]) - ]) - if test "x$ax_cv_boost_system" = "xyes"; then - AC_SUBST(BOOST_CPPFLAGS) - - AC_DEFINE(HAVE_BOOST_SYSTEM,,[define if the Boost::System library is available]) - BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` - - LDFLAGS_SAVE=$LDFLAGS - if test "x$ax_boost_user_system_lib" = "x"; then - for libextension in `ls -r $BOOSTLIBDIR/libboost_system* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'` ; do - ax_lib=${libextension} - AC_CHECK_LIB($ax_lib, exit, - [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], - [link_system="no"]) - done - if test "x$link_system" != "xyes"; then - for libextension in `ls -r $BOOSTLIBDIR/boost_system* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\..*,,'` ; do - ax_lib=${libextension} - AC_CHECK_LIB($ax_lib, exit, - [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], - [link_system="no"]) - done - fi - - else - for ax_lib in $ax_boost_user_system_lib boost_system-$ax_boost_user_system_lib; do - AC_CHECK_LIB($ax_lib, exit, - [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], - [link_system="no"]) - done - - fi - if test "x$ax_lib" = "x"; then - AC_MSG_ERROR(Could not find a version of the library!) - fi - if test "x$link_system" = "xno"; then - AC_MSG_ERROR(Could not link against $ax_lib !) - fi - fi - - CPPFLAGS="$CPPFLAGS_SAVED" - LDFLAGS="$LDFLAGS_SAVED" - fi -]) diff --git a/build/autotools/m4/ax_cxx_compile_stdcxx_11.m4 b/build/autotools/m4/ax_cxx_compile_stdcxx_11.m4 deleted file mode 100644 index af31b86b..00000000 --- a/build/autotools/m4/ax_cxx_compile_stdcxx_11.m4 +++ /dev/null @@ -1,141 +0,0 @@ -# ============================================================================ -# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html -# ============================================================================ -# -# SYNOPSIS -# -# AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) -# -# DESCRIPTION -# -# Check for baseline language coverage in the compiler for the C++11 -# standard; if necessary, add switches to CXXFLAGS to enable support. -# -# The first argument, if specified, indicates whether you insist on an -# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. -# -std=c++11). If neither is specified, you get whatever works, with -# preference for an extended mode. -# -# The second argument, if specified 'mandatory' or if left unspecified, -# indicates that baseline C++11 support is required and that the macro -# should error out if no mode with that support is found. If specified -# 'optional', then configuration proceeds regardless, after defining -# HAVE_CXX11 if and only if a supporting mode is found. -# -# LICENSE -# -# Copyright (c) 2008 Benjamin Kosnik -# Copyright (c) 2012 Zack Weinberg -# Copyright (c) 2013 Roy Stogner -# Copyright (c) 2014 Alexey Sokolov -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 4 - -m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[ - template - struct check - { - static_assert(sizeof(int) <= sizeof(T), "not big enough"); - }; - - struct Base { - virtual void f() {} - }; - struct Child : public Base { - virtual void f() {} - }; - - typedef check> right_angle_brackets; - - int a; - decltype(a) b; - - typedef check check_type; - check_type c; - check_type&& cr = static_cast(c); - - auto d = a; -]]) - -AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl - m4_if([$1], [], [], - [$1], [ext], [], - [$1], [noext], [], - [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl - m4_if([$2], [], [ax_cxx_compile_cxx11_required=true], - [$2], [mandatory], [ax_cxx_compile_cxx11_required=true], - [$2], [optional], [ax_cxx_compile_cxx11_required=false], - [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])]) - AC_LANG_PUSH([C++])dnl - ac_success=no - AC_CACHE_CHECK(whether $CXX supports C++11 features by default, - ax_cv_cxx_compile_cxx11, - [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], - [ax_cv_cxx_compile_cxx11=yes], - [ax_cv_cxx_compile_cxx11=no])]) - if test x$ax_cv_cxx_compile_cxx11 = xyes; then - ac_success=yes - fi - - m4_if([$1], [noext], [], [dnl - if test x$ac_success = xno; then - for switch in -std=gnu++11 -std=gnu++0x; do - cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) - AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, - $cachevar, - [ac_save_CXXFLAGS="$CXXFLAGS" - CXXFLAGS="$CXXFLAGS $switch" - AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], - [eval $cachevar=yes], - [eval $cachevar=no]) - CXXFLAGS="$ac_save_CXXFLAGS"]) - if eval test x\$$cachevar = xyes; then - CXXFLAGS="$CXXFLAGS $switch" - ac_success=yes - break - fi - done - fi]) - - m4_if([$1], [ext], [], [dnl - if test x$ac_success = xno; then - for switch in -std=c++11 -std=c++0x; do - cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) - AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, - $cachevar, - [ac_save_CXXFLAGS="$CXXFLAGS" - CXXFLAGS="$CXXFLAGS $switch" - AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], - [eval $cachevar=yes], - [eval $cachevar=no]) - CXXFLAGS="$ac_save_CXXFLAGS"]) - if eval test x\$$cachevar = xyes; then - CXXFLAGS="$CXXFLAGS $switch" - ac_success=yes - break - fi - done - fi]) - AC_LANG_POP([C++]) - if test x$ax_cxx_compile_cxx11_required = xtrue; then - if test x$ac_success = xno; then - AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) - fi - else - if test x$ac_success = xno; then - HAVE_CXX11=0 - AC_MSG_NOTICE([No compiler with C++11 support was found]) - else - HAVE_CXX11=1 - AC_DEFINE(HAVE_CXX11,1, - [define if the compiler supports basic C++11 syntax]) - fi - - AC_SUBST(HAVE_CXX11) - fi -]) diff --git a/build/autotools/m4/ax_pthread.m4 b/build/autotools/m4/ax_pthread.m4 deleted file mode 100644 index d383ad5c..00000000 --- a/build/autotools/m4/ax_pthread.m4 +++ /dev/null @@ -1,332 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_pthread.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) -# -# DESCRIPTION -# -# This macro figures out how to build C programs using POSIX threads. It -# sets the PTHREAD_LIBS output variable to the threads library and linker -# flags, and the PTHREAD_CFLAGS output variable to any special C compiler -# flags that are needed. (The user can also force certain compiler -# flags/libs to be tested by setting these environment variables.) -# -# Also sets PTHREAD_CC to any special C compiler that is needed for -# multi-threaded programs (defaults to the value of CC otherwise). (This -# is necessary on AIX to use the special cc_r compiler alias.) -# -# NOTE: You are assumed to not only compile your program with these flags, -# but also link it with them as well. e.g. you should link with -# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS -# -# If you are only building threads programs, you may wish to use these -# variables in your default LIBS, CFLAGS, and CC: -# -# LIBS="$PTHREAD_LIBS $LIBS" -# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" -# CC="$PTHREAD_CC" -# -# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant -# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name -# (e.g. PTHREAD_CREATE_UNDETACHED on AIX). -# -# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the -# PTHREAD_PRIO_INHERIT symbol is defined when compiling with -# PTHREAD_CFLAGS. -# -# ACTION-IF-FOUND is a list of shell commands to run if a threads library -# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it -# is not found. If ACTION-IF-FOUND is not specified, the default action -# will define HAVE_PTHREAD. -# -# Please let the authors know if this macro fails on any platform, or if -# you have any other suggestions or comments. This macro was based on work -# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help -# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by -# Alejandro Forero Cuervo to the autoconf macro repository. We are also -# grateful for the helpful feedback of numerous users. -# -# Updated for Autoconf 2.68 by Daniel Richard G. -# -# LICENSE -# -# Copyright (c) 2008 Steven G. Johnson -# Copyright (c) 2011 Daniel Richard G. -# -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation, either version 3 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see . -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. - -#serial 21 - -AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) -AC_DEFUN([AX_PTHREAD], [ -AC_REQUIRE([AC_CANONICAL_HOST]) -AC_LANG_PUSH([C]) -ax_pthread_ok=no - -# We used to check for pthread.h first, but this fails if pthread.h -# requires special compiler flags (e.g. on True64 or Sequent). -# It gets checked for in the link test anyway. - -# First of all, check if the user has set any of the PTHREAD_LIBS, -# etcetera environment variables, and if threads linking works using -# them: -if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then - save_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS $PTHREAD_CFLAGS" - save_LIBS="$LIBS" - LIBS="$PTHREAD_LIBS $LIBS" - AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) - AC_TRY_LINK_FUNC([pthread_join], [ax_pthread_ok=yes]) - AC_MSG_RESULT([$ax_pthread_ok]) - if test x"$ax_pthread_ok" = xno; then - PTHREAD_LIBS="" - PTHREAD_CFLAGS="" - fi - LIBS="$save_LIBS" - CFLAGS="$save_CFLAGS" -fi - -# We must check for the threads library under a number of different -# names; the ordering is very important because some systems -# (e.g. DEC) have both -lpthread and -lpthreads, where one of the -# libraries is broken (non-POSIX). - -# Create a list of thread flags to try. Items starting with a "-" are -# C compiler flags, and other items are library names, except for "none" -# which indicates that we try without any flags at all, and "pthread-config" -# which is a program returning the flags for the Pth emulation library. - -ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" - -# The ordering *is* (sometimes) important. Some notes on the -# individual items follow: - -# pthreads: AIX (must check this before -lpthread) -# none: in case threads are in libc; should be tried before -Kthread and -# other compiler flags to prevent continual compiler warnings -# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) -# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) -# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) -# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) -# -pthreads: Solaris/gcc -# -mthreads: Mingw32/gcc, Lynx/gcc -# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it -# doesn't hurt to check since this sometimes defines pthreads too; -# also defines -D_REENTRANT) -# ... -mt is also the pthreads flag for HP/aCC -# pthread: Linux, etcetera -# --thread-safe: KAI C++ -# pthread-config: use pthread-config program (for GNU Pth library) - -case ${host_os} in - solaris*) - - # On Solaris (at least, for some versions), libc contains stubbed - # (non-functional) versions of the pthreads routines, so link-based - # tests will erroneously succeed. (We need to link with -pthreads/-mt/ - # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather - # a function called by this macro, so we could check for that, but - # who knows whether they'll stub that too in a future libc.) So, - # we'll just look for -pthreads and -lpthread first: - - ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" - ;; - - darwin*) - ax_pthread_flags="-pthread $ax_pthread_flags" - ;; -esac - -# Clang doesn't consider unrecognized options an error unless we specify -# -Werror. We throw in some extra Clang-specific options to ensure that -# this doesn't happen for GCC, which also accepts -Werror. - -AC_MSG_CHECKING([if compiler needs -Werror to reject unknown flags]) -save_CFLAGS="$CFLAGS" -ax_pthread_extra_flags="-Werror" -CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument" -AC_COMPILE_IFELSE([AC_LANG_PROGRAM([int foo(void);],[foo()])], - [AC_MSG_RESULT([yes])], - [ax_pthread_extra_flags= - AC_MSG_RESULT([no])]) -CFLAGS="$save_CFLAGS" - -if test x"$ax_pthread_ok" = xno; then -for flag in $ax_pthread_flags; do - - case $flag in - none) - AC_MSG_CHECKING([whether pthreads work without any flags]) - ;; - - -*) - AC_MSG_CHECKING([whether pthreads work with $flag]) - PTHREAD_CFLAGS="$flag" - ;; - - pthread-config) - AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) - if test x"$ax_pthread_config" = xno; then continue; fi - PTHREAD_CFLAGS="`pthread-config --cflags`" - PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" - ;; - - *) - AC_MSG_CHECKING([for the pthreads library -l$flag]) - PTHREAD_LIBS="-l$flag" - ;; - esac - - save_LIBS="$LIBS" - save_CFLAGS="$CFLAGS" - LIBS="$PTHREAD_LIBS $LIBS" - CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags" - - # Check for various functions. We must include pthread.h, - # since some functions may be macros. (On the Sequent, we - # need a special flag -Kthread to make this header compile.) - # We check for pthread_join because it is in -lpthread on IRIX - # while pthread_create is in libc. We check for pthread_attr_init - # due to DEC craziness with -lpthreads. We check for - # pthread_cleanup_push because it is one of the few pthread - # functions on Solaris that doesn't have a non-functional libc stub. - # We try pthread_create on general principles. - AC_LINK_IFELSE([AC_LANG_PROGRAM([#include - static void routine(void *a) { a = 0; } - static void *start_routine(void *a) { return a; }], - [pthread_t th; pthread_attr_t attr; - pthread_create(&th, 0, start_routine, 0); - pthread_join(th, 0); - pthread_attr_init(&attr); - pthread_cleanup_push(routine, 0); - pthread_cleanup_pop(0) /* ; */])], - [ax_pthread_ok=yes], - []) - - LIBS="$save_LIBS" - CFLAGS="$save_CFLAGS" - - AC_MSG_RESULT([$ax_pthread_ok]) - if test "x$ax_pthread_ok" = xyes; then - break; - fi - - PTHREAD_LIBS="" - PTHREAD_CFLAGS="" -done -fi - -# Various other checks: -if test "x$ax_pthread_ok" = xyes; then - save_LIBS="$LIBS" - LIBS="$PTHREAD_LIBS $LIBS" - save_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS $PTHREAD_CFLAGS" - - # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. - AC_MSG_CHECKING([for joinable pthread attribute]) - attr_name=unknown - for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do - AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], - [int attr = $attr; return attr /* ; */])], - [attr_name=$attr; break], - []) - done - AC_MSG_RESULT([$attr_name]) - if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then - AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$attr_name], - [Define to necessary symbol if this constant - uses a non-standard name on your system.]) - fi - - AC_MSG_CHECKING([if more special flags are required for pthreads]) - flag=no - case ${host_os} in - aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; - osf* | hpux*) flag="-D_REENTRANT";; - solaris*) - if test "$GCC" = "yes"; then - flag="-D_REENTRANT" - else - # TODO: What about Clang on Solaris? - flag="-mt -D_REENTRANT" - fi - ;; - esac - AC_MSG_RESULT([$flag]) - if test "x$flag" != xno; then - PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" - fi - - AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], - [ax_cv_PTHREAD_PRIO_INHERIT], [ - AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], - [[int i = PTHREAD_PRIO_INHERIT;]])], - [ax_cv_PTHREAD_PRIO_INHERIT=yes], - [ax_cv_PTHREAD_PRIO_INHERIT=no]) - ]) - AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], - [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])]) - - LIBS="$save_LIBS" - CFLAGS="$save_CFLAGS" - - # More AIX lossage: compile with *_r variant - if test "x$GCC" != xyes; then - case $host_os in - aix*) - AS_CASE(["x/$CC"], - [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], - [#handle absolute path differently from PATH based program lookup - AS_CASE(["x$CC"], - [x/*], - [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], - [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) - ;; - esac - fi -fi - -test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" - -AC_SUBST([PTHREAD_LIBS]) -AC_SUBST([PTHREAD_CFLAGS]) -AC_SUBST([PTHREAD_CC]) - -# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: -if test x"$ax_pthread_ok" = xyes; then - ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) - : -else - ax_pthread_ok=no - $2 -fi -AC_LANG_POP -])dnl AX_PTHREAD diff --git a/build/autotools/missing b/build/autotools/missing deleted file mode 100755 index cdea5149..00000000 --- a/build/autotools/missing +++ /dev/null @@ -1,215 +0,0 @@ -#! /bin/sh -# Common wrapper for a few potentially missing GNU programs. - -scriptversion=2012-06-26.16; # UTC - -# Copyright (C) 1996-2013 Free Software Foundation, Inc. -# Originally written by Fran,cois Pinard , 1996. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -if test $# -eq 0; then - echo 1>&2 "Try '$0 --help' for more information" - exit 1 -fi - -case $1 in - - --is-lightweight) - # Used by our autoconf macros to check whether the available missing - # script is modern enough. - exit 0 - ;; - - --run) - # Back-compat with the calling convention used by older automake. - shift - ;; - - -h|--h|--he|--hel|--help) - echo "\ -$0 [OPTION]... PROGRAM [ARGUMENT]... - -Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due -to PROGRAM being missing or too old. - -Options: - -h, --help display this help and exit - -v, --version output version information and exit - -Supported PROGRAM values: - aclocal autoconf autoheader autom4te automake makeinfo - bison yacc flex lex help2man - -Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and -'g' are ignored when checking the name. - -Send bug reports to ." - exit $? - ;; - - -v|--v|--ve|--ver|--vers|--versi|--versio|--version) - echo "missing $scriptversion (GNU Automake)" - exit $? - ;; - - -*) - echo 1>&2 "$0: unknown '$1' option" - echo 1>&2 "Try '$0 --help' for more information" - exit 1 - ;; - -esac - -# Run the given program, remember its exit status. -"$@"; st=$? - -# If it succeeded, we are done. -test $st -eq 0 && exit 0 - -# Also exit now if we it failed (or wasn't found), and '--version' was -# passed; such an option is passed most likely to detect whether the -# program is present and works. -case $2 in --version|--help) exit $st;; esac - -# Exit code 63 means version mismatch. This often happens when the user -# tries to use an ancient version of a tool on a file that requires a -# minimum version. -if test $st -eq 63; then - msg="probably too old" -elif test $st -eq 127; then - # Program was missing. - msg="missing on your system" -else - # Program was found and executed, but failed. Give up. - exit $st -fi - -perl_URL=http://www.perl.org/ -flex_URL=http://flex.sourceforge.net/ -gnu_software_URL=http://www.gnu.org/software - -program_details () -{ - case $1 in - aclocal|automake) - echo "The '$1' program is part of the GNU Automake package:" - echo "<$gnu_software_URL/automake>" - echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:" - echo "<$gnu_software_URL/autoconf>" - echo "<$gnu_software_URL/m4/>" - echo "<$perl_URL>" - ;; - autoconf|autom4te|autoheader) - echo "The '$1' program is part of the GNU Autoconf package:" - echo "<$gnu_software_URL/autoconf/>" - echo "It also requires GNU m4 and Perl in order to run:" - echo "<$gnu_software_URL/m4/>" - echo "<$perl_URL>" - ;; - esac -} - -give_advice () -{ - # Normalize program name to check for. - normalized_program=`echo "$1" | sed ' - s/^gnu-//; t - s/^gnu//; t - s/^g//; t'` - - printf '%s\n' "'$1' is $msg." - - configure_deps="'configure.ac' or m4 files included by 'configure.ac'" - case $normalized_program in - autoconf*) - echo "You should only need it if you modified 'configure.ac'," - echo "or m4 files included by it." - program_details 'autoconf' - ;; - autoheader*) - echo "You should only need it if you modified 'acconfig.h' or" - echo "$configure_deps." - program_details 'autoheader' - ;; - automake*) - echo "You should only need it if you modified 'Makefile.am' or" - echo "$configure_deps." - program_details 'automake' - ;; - aclocal*) - echo "You should only need it if you modified 'acinclude.m4' or" - echo "$configure_deps." - program_details 'aclocal' - ;; - autom4te*) - echo "You might have modified some maintainer files that require" - echo "the 'automa4te' program to be rebuilt." - program_details 'autom4te' - ;; - bison*|yacc*) - echo "You should only need it if you modified a '.y' file." - echo "You may want to install the GNU Bison package:" - echo "<$gnu_software_URL/bison/>" - ;; - lex*|flex*) - echo "You should only need it if you modified a '.l' file." - echo "You may want to install the Fast Lexical Analyzer package:" - echo "<$flex_URL>" - ;; - help2man*) - echo "You should only need it if you modified a dependency" \ - "of a man page." - echo "You may want to install the GNU Help2man package:" - echo "<$gnu_software_URL/help2man/>" - ;; - makeinfo*) - echo "You should only need it if you modified a '.texi' file, or" - echo "any other file indirectly affecting the aspect of the manual." - echo "You might want to install the Texinfo package:" - echo "<$gnu_software_URL/texinfo/>" - echo "The spurious makeinfo call might also be the consequence of" - echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might" - echo "want to install GNU make:" - echo "<$gnu_software_URL/make/>" - ;; - *) - echo "You might have modified some files without having the proper" - echo "tools for further handling them. Check the 'README' file, it" - echo "often tells you about the needed prerequisites for installing" - echo "this package. You may also peek at any GNU archive site, in" - echo "case some other package contains this missing '$1' program." - ;; - esac -} - -give_advice "$1" | sed -e '1s/^/WARNING: /' \ - -e '2,$s/^/ /' >&2 - -# Propagate the correct exit status (expected to be 127 for a program -# not found, 63 for a program that failed due to version mismatch). -exit $st - -# Local variables: -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC" -# time-stamp-end: "; # UTC" -# End: From 4dea2ef1a4b1e8d14f7bad74508f12d94002a47a Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Nov 2015 13:05:37 -0500 Subject: [PATCH 0526/6300] use EdDSA as default for RouterInfo --- RouterContext.cpp | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index ca4b269a..148b87e0 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -30,7 +30,7 @@ namespace i2p void RouterContext::CreateNewRouter () { - m_Keys = i2p::data::CreateRandomKeys (); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); SaveKeys (); NewRouterInfo (); } @@ -276,10 +276,23 @@ namespace i2p { std::ifstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ifstream::binary | std::ofstream::in); if (!fk.is_open ()) return false; - - i2p::data::Keys keys; - fk.read ((char *)&keys, sizeof (keys)); - m_Keys = keys; + fk.seekg (0, std::ios::end); + size_t len = fk.tellg(); + fk.seekg (0, std::ios::beg); + + if (len == sizeof (i2p::data::Keys)) // old keys file format + { + i2p::data::Keys keys; + fk.read ((char *)&keys, sizeof (keys)); + m_Keys = keys; + } + else // new keys file format + { + uint8_t * buf = new uint8_t[len]; + fk.read ((char *)buf, len); + m_Keys.FromBuffer (buf, len); + delete[] buf; + } i2p::data::RouterInfo routerInfo(i2p::util::filesystem::GetFullPath (ROUTER_INFO)); // TODO m_RouterInfo.SetRouterIdentity (GetIdentity ()); @@ -295,14 +308,13 @@ namespace i2p void RouterContext::SaveKeys () { + // save in the same format as .dat files std::ofstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ofstream::binary | std::ofstream::out); - i2p::data::Keys keys; - memcpy (keys.privateKey, m_Keys.GetPrivateKey (), sizeof (keys.privateKey)); - memcpy (keys.signingPrivateKey, m_Keys.GetSigningPrivateKey (), sizeof (keys.signingPrivateKey)); - auto& ident = GetIdentity ()->GetStandardIdentity (); - memcpy (keys.publicKey, ident.publicKey, sizeof (keys.publicKey)); - memcpy (keys.signingKey, ident.signingKey, sizeof (keys.signingKey)); - fk.write ((char *)&keys, sizeof (keys)); + size_t len = m_Keys.GetFullLen (); + uint8_t * buf = new uint8_t[len]; + m_Keys.ToBuffer (buf, len); + fk.write ((char *)buf, len); + delete[] buf; } std::shared_ptr RouterContext::GetTunnelPool () const From 962261fee75abbdf4bfd7af1cf945e152e539432 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 4 Nov 2015 13:48:30 -0500 Subject: [PATCH 0527/6300] EdDSA speed improvement --- RouterContext.cpp | 4 ++++ Signature.cpp | 34 +++++++++++++++++++++++----------- Signature.h | 12 ++++++++++-- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index 148b87e0..6629f774 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -30,7 +30,11 @@ namespace i2p void RouterContext::CreateNewRouter () { +#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER) m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); +#else + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_DSA_SHA1); +#endif SaveKeys (); NewRouterInfo (); } diff --git a/Signature.cpp b/Signature.cpp index 40947aa6..a14099ac 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -39,7 +39,7 @@ namespace crypto BN_set_word (tmp, 121666); Inv (tmp, ctx); BN_set_word (d, 121665); - BN_set_negative (d, -1); + BN_set_negative (d, 1); BN_mul (d, d, tmp, ctx); // 2^((q-1)/4) @@ -63,10 +63,14 @@ namespace crypto BN_free (two); BN_free (tmp); - // precalculate Bi - Bi[0] = { BN_dup (Bx), BN_dup (By) }; - for (int i = 1; i < 256; i++) - Bi[i] = Double (Bi[i-1], ctx); + // precalculate Bi16 table + Bi16[0][0] = { BN_dup (Bx), BN_dup (By) }; + for (int i = 0; i < 64; i++) + { + if (i) Bi16[i][0] = Sum (Bi16[i-1][14], Bi16[i-1][0], ctx); + for (int j = 1; j < 15; j++) + Bi16[i][j] = Sum (Bi16[i][j-1], Bi16[i][0], ctx); // (16+j+1)^i*B + } BN_CTX_free (ctx); } @@ -101,8 +105,13 @@ namespace crypto { BIGNUM * h = DecodeBN (digest, 64); // signature 0..31 - R, 32..63 - S - bool passed = MulB (signature + EDDSA25519_SIGNATURE_LENGTH/2, ctx) /*S*/ == - Sum (DecodePoint (signature, ctx) /*R*/, Mul (publicKey, h, ctx), ctx); + // B*S = R + PK*h => R = B*S - PK*h + // we don't decode R, but encode (B*S - PK*h) + auto Bs = MulB (signature + EDDSA25519_SIGNATURE_LENGTH/2, ctx); // B*S; + auto PKh = Mul (publicKey, h, ctx); // PK*h + uint8_t diff[32]; + EncodePoint (Sum (Bs, -PKh, ctx), diff); // Bs - PKh encoded + bool passed = !memcmp (signature, diff, 32); // R BN_free (h); if (!passed) LogPrint (eLogError, "25519 signature verification failed"); @@ -243,9 +252,12 @@ namespace crypto EDDSAPoint res {zero, one}; for (int i = 0; i < 32; i++) { - for (int j = 0; j < 8; j++) - if (e[i] & (1 << j)) // from lowest to highest bit - res = Sum (res, Bi[i*8 + j], ctx); + uint8_t x = e[i] & 0x0F; // 4 low bits + if (x > 0) + res = Sum (res, Bi16[i*2][x-1], ctx); + x = e[i] >> 4; // 4 high bits + if (x > 0) + res = Sum (res, Bi16[i*2+1][x-1], ctx); } return res; } @@ -365,7 +377,7 @@ namespace crypto // transient values BIGNUM * q_2; // q-2 BIGNUM * two_252_2; // 2^252-2 - EDDSAPoint Bi[256]; // m_Bi[i] = 2^i*B for i-th bit + EDDSAPoint Bi16[64][15]; // per 4-bits, Bi16[i][j] = (16+j+1)^i*B, we don't store zeroes }; static std::unique_ptr g_Ed25519; diff --git a/Signature.h b/Signature.h index 423559a3..c4ccf8fc 100644 --- a/Signature.h +++ b/Signature.h @@ -377,7 +377,7 @@ namespace crypto EDDSAPoint (BIGNUM * x1, BIGNUM * y1): x(x1), y(y1) {}; ~EDDSAPoint () { BN_free (x); BN_free (y); }; - EDDSAPoint& operator= (EDDSAPoint&& other) + EDDSAPoint& operator=(EDDSAPoint&& other) { if (x) BN_free (x); if (y) BN_free (y); @@ -386,10 +386,18 @@ namespace crypto return *this; } - bool operator== (const EDDSAPoint& other) const + bool operator==(const EDDSAPoint& other) const { return !BN_cmp (x, other.x) && !BN_cmp (y, other.y); } + + EDDSAPoint operator-() const + { + BIGNUM * x1 = NULL, * y1 = NULL; + if (x) { x1 = BN_dup (x); BN_set_negative (x1, !BN_is_negative (x)); }; + if (y) y1 = BN_dup (y); + return EDDSAPoint {x1, y1}; + } }; const size_t EDDSA25519_PUBLIC_KEY_LENGTH = 32; From d50ba1259c6d12b6ed2b8a563a6e8677448d9c45 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 5 Nov 2015 15:02:10 -0500 Subject: [PATCH 0528/6300] calculations in projective coordinates --- Signature.cpp | 171 +++++++++++++++++++++++++++----------------------- Signature.h | 24 +++---- 2 files changed, 104 insertions(+), 91 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index a14099ac..97ecea2d 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -21,9 +21,6 @@ namespace crypto BN_set_word (tmp, 255); BN_exp (q, two, tmp, ctx); BN_sub_word (q, 19); - // q_2 = q-2 - q_2 = BN_dup (q); - BN_sub_word (q_2, 2); l = BN_new (); // 2^252 + 27742317777372353535851937790883648493 @@ -37,7 +34,7 @@ namespace crypto // -121665*inv(121666) d = BN_new (); BN_set_word (tmp, 121666); - Inv (tmp, ctx); + BN_mod_inverse (tmp, tmp, q, ctx); BN_set_word (d, 121665); BN_set_negative (d, 1); BN_mul (d, d, tmp, ctx); @@ -53,7 +50,7 @@ namespace crypto // 4*inv(5) BIGNUM * By = BN_new (); BN_set_word (By, 5); - Inv (By, ctx); + BN_mod_inverse (By, By, q, ctx); BN_mul_word (By, 4); BIGNUM * Bx = RecoverX (By, ctx); BN_mod (Bx, Bx, q, ctx); // % q @@ -81,7 +78,6 @@ namespace crypto BN_free (l); BN_free (d); BN_free (I); - BN_free (q_2); BN_free (two_252_2); } @@ -96,9 +92,9 @@ namespace crypto return DecodePoint (buf, ctx); } - void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf) const + void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const { - EncodePoint (publicKey, buf); + EncodePoint (Normalize (publicKey, ctx), buf); } bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature, BN_CTX * ctx) const @@ -110,7 +106,7 @@ namespace crypto auto Bs = MulB (signature + EDDSA25519_SIGNATURE_LENGTH/2, ctx); // B*S; auto PKh = Mul (publicKey, h, ctx); // PK*h uint8_t diff[32]; - EncodePoint (Sum (Bs, -PKh, ctx), diff); // Bs - PKh encoded + EncodePoint (Normalize (Sum (Bs, -PKh, ctx), ctx), diff); // Bs - PKh encoded bool passed = !memcmp (signature, diff, 32); // R BN_free (h); if (!passed) @@ -131,7 +127,7 @@ namespace crypto BIGNUM * r = DecodeBN (digest, 32); // DecodeBN (digest, 64); // for test vectors // calculate R uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf - EncodePoint (MulB (digest, bnCtx), R); // EncodePoint (Mul (B, r, bnCtx), R); // for test vectors + EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); // EncodePoint (Mul (B, r, bnCtx), R); // for test vectors // calculate S SHA512_Init (&ctx); SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R @@ -153,79 +149,86 @@ namespace crypto EDDSAPoint Sum (const EDDSAPoint& p1, const EDDSAPoint& p2, BN_CTX * ctx) const { - BIGNUM * xx = BN_new (), * yy = BN_new (); - // m = d*p1.x*p2.x*p1.y*p2.y - BN_mul (xx, p1.x, p2.x, ctx); - BN_mul (yy, p1.y, p2.y, ctx); - BIGNUM * m = BN_dup (d); - BN_mul (m, m, xx, ctx); - BN_mul (m, m, yy, ctx); - // x = (p1.x*p2.y + p2.x*p1.y)*inv(1 + m) - // y = (p1.y*p2.y + p1.x*p2.x)*inv(1 - m) + // x3 = (x1*y2+y1*x2)*(z1*z2-d*t1*t2) + // y3 = (y1*y2+x1*x2)*(z1*z2+d*t1*t2) + // z3 = (z1*z2-d*t1*t2)*(z1*z2+d*t1*t2) + // t3 = (y1*y2+x1*x2)*(x1*y2+y1*x2) + BIGNUM * x3 = BN_new (), * y3 = BN_new (), * z3 = BN_new (), * t3 = BN_new (); + BIGNUM * z1 = p1.z, * t1 = p1.t; + if (!z1) { z1 = BN_new (); BN_one (z1); } + if (!t1) { t1 = BN_new (); BN_mul (t1, p1.x, p1.y, ctx); } - // use one inversion instead two - // m1 = 1-m - BIGNUM * m1 = BN_new (); - BN_one (m1); - BN_sub (m1, m1, m); - // m = m+1 - BN_add_word (m, 1); - // y = (p1.y*p2.y + p1.x*p2.x)*m - BIGNUM * y = BN_new (); - BN_add (y, xx, yy); - BN_mod_mul (y, y, m, q, ctx); - // x = (p1.x*p2.y + p2.x*p1.y)*m1 - BIGNUM * x = BN_new (); - BN_mul (yy, p1.x, p2.y, ctx); - BN_mul (xx, p2.x, p1.y, ctx); - BN_add (x, xx, yy); - BN_mod_mul (x, x, m1, q, ctx); - // denominator m = m*m1 - BN_mod_mul (m, m, m1, q, ctx); - Inv (m, ctx); - BN_mod_mul (x, x, m, q, ctx); // x = x/m - BN_mod_mul (y, y, m, q, ctx); // y = y/m + BIGNUM * z2 = p2.z, * t2 = p2.t; + if (!z2) { z2 = BN_new (); BN_one (z2); } + if (!t2) { t2 = BN_new (); BN_mul (t2, p2.x, p2.y, ctx); } + + BIGNUM * A = BN_new (), * B = BN_new (), * C = BN_new (), * D = BN_new (); + BN_mul (A, p1.x, p2.x, ctx); // A = x1*x2 + BN_mul (B, p1.y, p2.y, ctx); // B = y1*y2 + BN_mul (C, t1, t2, ctx); + BN_mul (C, C, d, ctx); // C = d*t1*t2 + BN_mul (D, z1, z2, ctx); // D = z1*z2 - BN_free (xx);BN_free (yy); BN_free (m); BN_free (m1); - return EDDSAPoint {x, y}; + BIGNUM * E = BN_new (), * F = BN_new (), * G = BN_new (), * H = BN_new (); + BN_add (x3, p1.x, p1.y); + BN_add (y3, p2.x, p2.y); + BN_mul (E, x3, y3, ctx); // (x1 + y1)*(x2 + y2) + BN_sub (E, E, A); + BN_sub (E, E, B); // E = (x1 + y1)*(x2 + y2) - A - B + BN_sub (F, D, C); // F = D - C + BN_add (G, D, C); // G = D + C + BN_add (H, B, A); // H = B + A + + BN_free (A); BN_free (B); BN_free (C); BN_free (D); + if (!p1.z) BN_free (z1); + if (!p1.t) BN_free (t1); + if (!p2.z) BN_free (z2); + if (!p2.t) BN_free (t2); + + BN_mod_mul (x3, E, F, q, ctx); // x3 = E*F + BN_mod_mul (y3, G, H, q, ctx); // y3 = G*H + BN_mod_mul (z3, F, G, q, ctx); // z3 = F*G + BN_mod_mul (t3, E, H, q, ctx); // t3 = E*H + + BN_free (E); BN_free (F); BN_free (G); BN_free (H); + + return EDDSAPoint {x3, y3, z3, t3}; } EDDSAPoint Double (const EDDSAPoint& p, BN_CTX * ctx) const { - BIGNUM * pxy = BN_new (); - BN_mul (pxy, p.x, p.y, ctx); - // m = d*(p.x*p.y)^2 - BIGNUM * m = BN_new (); - BN_sqr (m, pxy, ctx); - BN_mul (m, m, d, ctx); - // x = (2*p.x*p.y)*inv(1 + m) - // y = (p.x^2 + p.y^2)*inv(1 - m) + BIGNUM * x2 = BN_new (), * y2 = BN_new (), * z2 = BN_new (), * t2 = BN_new (); + BIGNUM * z = p.z, * t = p.t; + if (!z) { z = BN_new (); BN_one (z); } + BN_sqr (z, z, ctx); // z^2 (D) + if (!t) { t = BN_new (); BN_mul (t, p.x, p.y, ctx); } + BN_sqr (t, t, ctx); + BN_mul (t, t, d, ctx); // d*t^2 (C) - // use one inversion instead two - // m1 = 1-m - BIGNUM * m1 = BN_new (); - BN_one (m1); - BN_sub (m1, m1, m); - // m = m+1 - BN_add_word (m, 1); - // x = 2*p.x*p.y*m1 - BN_mul_word (pxy, 2); - BIGNUM * x = BN_new (); - BN_mod_mul (x, pxy, m1, q, ctx); - // y = (p.x^2 + p.y^2)*m - BIGNUM * y = BN_new (); - BN_sqr (pxy, p.x, ctx); - BN_sqr (y, p.y, ctx); - BN_add (pxy, pxy, y); - BN_mod_mul (y, pxy, m, q, ctx); - // denominator m = m*m1 - BN_mod_mul (m, m, m1, q, ctx); - Inv (m, ctx); - BN_mod_mul (x, x, m, q, ctx); // x = x/m - BN_mod_mul (y, y, m, q, ctx); // y = y/m + BIGNUM * A = BN_new (), * B = BN_new (); + BN_sqr (A, p.x, ctx); // A = x^2 + BN_sqr (B, p.y, ctx); // B = y^2 - BN_free (pxy); BN_free (m); BN_free (m1); - return EDDSAPoint {x, y}; + BIGNUM * E = BN_new (), * F = BN_new (), * G = BN_new (), * H = BN_new (); + // E = (x+y)*(x+y)-A-B = x^2+y^2+2xy-A-B = 2xy + BN_mul (E, p.x, p.y, ctx); + BN_mul_word (E, 2); // E =2*x*y + BN_sub (F, z, t); // F = D - C = z - t + BN_add (G, z, t); // G = D + C = z + t + BN_add (H, B, A); // H = B + A + + BN_free (A); BN_free (B); + if (!p.z) BN_free (z); + if (!p.t) BN_free (t); + + BN_mod_mul (x2, E, F, q, ctx); // x2 = E*F + BN_mod_mul (y2, G, H, q, ctx); // y2 = G*H + BN_mod_mul (z2, F, G, q, ctx); // z2 = F*G + BN_mod_mul (t2, E, H, q, ctx); // t2 = E*H + + BN_free (E); BN_free (F); BN_free (G); BN_free (H); + + return EDDSAPoint {x2, y2, z2, t2}; } EDDSAPoint Mul (const EDDSAPoint& p, const BIGNUM * e, BN_CTX * ctx) const @@ -262,9 +265,18 @@ namespace crypto return res; } - void Inv (BIGNUM * x, BN_CTX * ctx) const + EDDSAPoint Normalize (const EDDSAPoint& p, BN_CTX * ctx) const { - BN_mod_exp (x, x, q_2, q, ctx); + if (p.z) + { + BIGNUM * x = BN_new (), * y = BN_new (); + BN_mod_inverse (y, p.z, q, ctx); + BN_mod_mul (x, p.x, y, q, ctx); // x = x/z + BN_mod_mul (y, p.y, y, q, ctx); // y = y/z + return EDDSAPoint{x, y}; + } + else + return EDDSAPoint{BN_dup (p.x), BN_dup (p.y)}; } bool IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const @@ -296,7 +308,7 @@ namespace crypto BIGNUM * xx = BN_new (); BN_mul (xx, d, y2, ctx); BN_add_word (xx, 1); - Inv (xx, ctx); + BN_mod_inverse (xx, xx, q, ctx); BN_sub_word (y2, 1); BN_mul (xx, y2, xx, ctx); // x = srqt(xx) = xx^(2^252-2) @@ -375,7 +387,6 @@ namespace crypto BIGNUM * q, * l, * d, * I; EDDSAPoint B; // base point // transient values - BIGNUM * q_2; // q-2 BIGNUM * two_252_2; // 2^252-2 EDDSAPoint Bi16[64][15]; // per 4-bits, Bi16[i][j] = (16+j+1)^i*B, we don't store zeroes }; @@ -418,7 +429,7 @@ namespace crypto m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] |= 0x40; // set second bit // generate and encode public key auto publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, m_Ctx); - GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded); + GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, m_Ctx); } void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const diff --git a/Signature.h b/Signature.h index c4ccf8fc..9f60d406 100644 --- a/Signature.h +++ b/Signature.h @@ -371,32 +371,34 @@ namespace crypto struct EDDSAPoint { BIGNUM * x, * y; - EDDSAPoint (): x(nullptr), y(nullptr) {}; - EDDSAPoint (EDDSAPoint&& other): x(nullptr), y(nullptr) + BIGNUM * z, * t; // projective coordinates + EDDSAPoint (): x(nullptr), y(nullptr), z(nullptr), t(nullptr) {}; + EDDSAPoint (EDDSAPoint&& other): x(nullptr), y(nullptr), z(nullptr), t(nullptr) { *this = std::move (other); }; - EDDSAPoint (BIGNUM * x1, BIGNUM * y1): x(x1), y(y1) {}; - ~EDDSAPoint () { BN_free (x); BN_free (y); }; + EDDSAPoint (BIGNUM * x1, BIGNUM * y1, BIGNUM * z1 = nullptr, BIGNUM * t1 = nullptr): x(x1), y(y1), z(z1), t(t1) {}; + ~EDDSAPoint () { BN_free (x); BN_free (y); BN_free(z); BN_free(t); }; EDDSAPoint& operator=(EDDSAPoint&& other) { if (x) BN_free (x); if (y) BN_free (y); + if (z) BN_free (z); + if (t) BN_free (t); x = other.x; other.x = nullptr; y = other.y; other.y = nullptr; + z = other.z; other.z = nullptr; + t = other.t; other.t = nullptr; return *this; } - bool operator==(const EDDSAPoint& other) const - { - return !BN_cmp (x, other.x) && !BN_cmp (y, other.y); - } - EDDSAPoint operator-() const { - BIGNUM * x1 = NULL, * y1 = NULL; + BIGNUM * x1 = NULL, * y1 = NULL, * z1 = NULL, * t1 = NULL; if (x) { x1 = BN_dup (x); BN_set_negative (x1, !BN_is_negative (x)); }; if (y) y1 = BN_dup (y); - return EDDSAPoint {x1, y1}; + if (z) z1 = BN_dup (z); + if (t) { t1 = BN_dup (t); BN_set_negative (t1, !BN_is_negative (t)); }; + return EDDSAPoint {x1, y1, z1, t1}; } }; From 73037b86ac7aabf4c9cca6fc7afd3e8bff70d08e Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 6 Nov 2015 09:01:02 -0500 Subject: [PATCH 0529/6300] fixed build for gcc 4.6 and boost 1.46 --- Reseed.cpp | 2 +- Tunnel.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 8d4baf8a..2c97cbd3 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -372,7 +372,7 @@ namespace data boost::asio::ip::tcp::resolver::query (u.host_, std::to_string (u.port_)), ecode); if (!ecode) { - boost::asio::ssl::context ctx(service, boost::asio::ssl::context::tlsv12); + boost::asio::ssl::context ctx(service, boost::asio::ssl::context::sslv23); ctx.set_verify_mode(boost::asio::ssl::context::verify_none); boost::asio::ssl::stream s(service, ctx); s.lowest_layer().connect (*it, ecode); diff --git a/Tunnel.cpp b/Tunnel.cpp index 6443180f..375fe6b0 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -138,7 +138,8 @@ namespace tunnel hop = m_Config->GetLastHop (); while (hop) { - auto tunnelHop = new TunnelHop{ .ident = hop->ident }; + auto tunnelHop = new TunnelHop; + tunnelHop->ident = hop->ident; tunnelHop->decryption.SetKeys (hop->layerKey, hop->ivKey); m_Hops.push_back (std::unique_ptr(tunnelHop)); hop = hop->prev; From 7749319c75c92a86814e27a14fe6277f1f470d1a Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 7 Nov 2015 18:07:59 -0500 Subject: [PATCH 0530/6300] h%l for verification --- Signature.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Signature.cpp b/Signature.cpp index 97ecea2d..3d6e8a03 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -104,6 +104,7 @@ namespace crypto // B*S = R + PK*h => R = B*S - PK*h // we don't decode R, but encode (B*S - PK*h) auto Bs = MulB (signature + EDDSA25519_SIGNATURE_LENGTH/2, ctx); // B*S; + BN_mod (h, h, l, ctx); // public key is multiple of B, but B%l = 0 auto PKh = Mul (publicKey, h, ctx); // PK*h uint8_t diff[32]; EncodePoint (Normalize (Sum (Bs, -PKh, ctx), ctx), diff); // Bs - PKh encoded From 73ae6cf1640ac1d17be38ec1f81f63f52329a924 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 9 Nov 2015 14:41:04 -0500 Subject: [PATCH 0531/6300] (h*a)%l for signing --- Signature.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index 3d6e8a03..44f48531 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -135,15 +135,14 @@ namespace crypto SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key SHA512_Update (&ctx, buf, len); // data SHA512_Final (digest, &ctx); - BIGNUM * s = DecodeBN (digest, 64); - // S = (r + s*a) % l + BIGNUM * h = DecodeBN (digest, 64); + // S = (r + h*a) % l BIGNUM * a = DecodeBN (expandedPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH); // left half of expanded key - BN_mul (s, s, a, bnCtx); - BN_add (s, s, r); - BN_mod (s, s, l, bnCtx); // % l + BN_mod_mul (h, h, a, l, bnCtx); // %l + BN_mod_add (h, h, r, l, bnCtx); // %l memcpy (signature, R, EDDSA25519_SIGNATURE_LENGTH/2); - EncodeBN (s, signature + EDDSA25519_SIGNATURE_LENGTH/2, EDDSA25519_SIGNATURE_LENGTH/2); // S - BN_free (r); BN_free (s); BN_free (a); + EncodeBN (h, signature + EDDSA25519_SIGNATURE_LENGTH/2, EDDSA25519_SIGNATURE_LENGTH/2); // S + BN_free (r); BN_free (h); BN_free (a); } private: From 5c58bf44c0eb0b3641714a51cf79a548ffb717a6 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Nov 2015 14:09:26 -0500 Subject: [PATCH 0532/6300] Makefile.mingw added --- Makefile.mingw | 6 ++++++ filelist.mk | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 Makefile.mingw diff --git a/Makefile.mingw b/Makefile.mingw new file mode 100644 index 00000000..4f17e68b --- /dev/null +++ b/Makefile.mingw @@ -0,0 +1,6 @@ +CXX = g++ +CXXFLAGS = -O2 -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN +NEEDED_CXXFLAGS = -std=c++11 +INCFLAGS = -I/usr/include/ -I/usr/local/include/ -I/c/dev/openssl/include -I/c/dev/boost/include/boost-1_59 +LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/c/dev/openssl -L/c/dev/boost/lib +LDLIBS = -lboost_system-mgw48-mt-1_59 -lboost_date_time-mgw48-mt-1_59 -lboost_filesystem-mgw48-mt-1_59 -lboost_regex-mgw48-mt-1_59 -lboost_program_options-mgw48-mt-1_59 -lssl -lcrypto -lz -lpthread -lwsock32 -lws2_32 -lgdi32 diff --git a/filelist.mk b/filelist.mk index f040183f..147ef259 100644 --- a/filelist.mk +++ b/filelist.mk @@ -4,7 +4,7 @@ LIB_SRC = \ Reseed.cpp RouterContext.cpp RouterInfo.cpp Signature.cpp SSU.cpp \ SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp TransitTunnel.cpp \ Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp TunnelGateway.cpp \ - Destination.cpp Base.cpp util.cpp api.cpp + Destination.cpp Base.cpp I2PEndian.cpp util.cpp api.cpp LIB_CLIENT_SRC = \ AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \ From 593b25a5cd358857f5f4774089846aa7fb5a6db3 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Nov 2015 13:32:58 -0500 Subject: [PATCH 0533/6300] fix build error --- Signature.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index 44f48531..81510979 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -99,7 +99,7 @@ namespace crypto bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature, BN_CTX * ctx) const { - BIGNUM * h = DecodeBN (digest, 64); + BIGNUM * h = DecodeBN<64> (digest); // signature 0..31 - R, 32..63 - S // B*S = R + PK*h => R = B*S - PK*h // we don't decode R, but encode (B*S - PK*h) @@ -125,7 +125,7 @@ namespace crypto SHA512_Update (&ctx, buf, len); // data uint8_t digest[64]; SHA512_Final (digest, &ctx); - BIGNUM * r = DecodeBN (digest, 32); // DecodeBN (digest, 64); // for test vectors + BIGNUM * r = DecodeBN<32> (digest); // DecodeBN<64> (digest); // for test vectors // calculate R uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); // EncodePoint (Mul (B, r, bnCtx), R); // for test vectors @@ -135,9 +135,9 @@ namespace crypto SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key SHA512_Update (&ctx, buf, len); // data SHA512_Final (digest, &ctx); - BIGNUM * h = DecodeBN (digest, 64); + BIGNUM * h = DecodeBN<64> (digest); // S = (r + h*a) % l - BIGNUM * a = DecodeBN (expandedPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH); // left half of expanded key + BIGNUM * a = DecodeBN (expandedPrivateKey); // left half of expanded key BN_mod_mul (h, h, a, l, bnCtx); // %l BN_mod_add (h, h, r, l, bnCtx); // %l memcpy (signature, R, EDDSA25519_SIGNATURE_LENGTH/2); @@ -356,7 +356,8 @@ namespace crypto buf[EDDSA25519_PUBLIC_KEY_LENGTH - 1] |= 0x80; // set highest bit } - BIGNUM * DecodeBN (const uint8_t * buf, size_t len) const + template + BIGNUM * DecodeBN (const uint8_t * buf) const { // buf is Little Endian convert it to Big Endian uint8_t buf1[len]; From 88db99e59308cd769edd5c8c369949d63ccfd406 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 11 Nov 2015 13:21:52 -0600 Subject: [PATCH 0534/6300] Minor omissions * Missing UPnP namespace * Public key pointer dereferencing for MSVC * Redundant WIN32_LEAN_AND_MEAN found in Makefile.mingw as well --- Daemon.cpp | 2 +- SAM.cpp | 2 +- Win32/Win32Service.h | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index ee3dbe76..31448d3d 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -39,7 +39,7 @@ namespace i2p std::unique_ptr m_I2PControlService; #ifdef USE_UPNP - UPnP m_UPnP; + i2p::transport::UPnP m_UPnP; #endif }; diff --git a/SAM.cpp b/SAM.cpp index dc592f51..ee8b514a 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -450,7 +450,7 @@ namespace client auto keys = i2p::data::PrivateKeys::CreateRandomKeys (); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, - keys.GetPublic ().ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); + keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); #else size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); diff --git a/Win32/Win32Service.h b/Win32/Win32Service.h index 868528f6..097cb111 100644 --- a/Win32/Win32Service.h +++ b/Win32/Win32Service.h @@ -2,7 +2,6 @@ #define WIN_32_SERVICE_H__ #include -#define WIN32_LEAN_AND_MEAN #include From 94d09150046b28345b93ffe315913ae63b44c613 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 11 Nov 2015 13:44:52 -0600 Subject: [PATCH 0535/6300] Reorder ssl/boost includes to avoid winsock complains --- Daemon.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Daemon.cpp b/Daemon.cpp index 31448d3d..d0f254e2 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -1,6 +1,5 @@ #include #include -#include #include "Daemon.h" @@ -20,6 +19,8 @@ #include "HTTPServer.h" #include "I2PControl.h" #include "ClientContext.h" +// ssl.h somehow pulls Windows.h stuff that has to go after asio +#include #ifdef USE_UPNP #include "UPnP.h" From 7f27580f1bad38191bc7f4231f2e065310db572b Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 11 Nov 2015 13:45:37 -0600 Subject: [PATCH 0536/6300] Proper miniupnpc CMake detection --- build/cmake_modules/FindMiniUPnPc.cmake | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build/cmake_modules/FindMiniUPnPc.cmake b/build/cmake_modules/FindMiniUPnPc.cmake index 7ecef75d..8d5d3860 100644 --- a/build/cmake_modules/FindMiniUPnPc.cmake +++ b/build/cmake_modules/FindMiniUPnPc.cmake @@ -4,12 +4,12 @@ if(MINIUPNPC_INCLUDE_DIR) set(MINIUPNPC_FOUND TRUE) else() - find_path(MINIUPNPC_INCLUDE_DIR miniupnpc.h - /usr/include/miniupnpc - /usr/local/include/miniupnpc - /opt/local/include/miniupnpc - $ENV{SystemDrive}/miniupnpc - ${PROJECT_SOURCE_DIR}/../../miniupnpc + find_path(MINIUPNPC_INCLUDE_DIR miniupnpc/miniupnpc.h + /usr/include + /usr/local/include + /opt/local/include + $ENV{SystemDrive} + ${PROJECT_SOURCE_DIR}/../.. ) if(MINIUPNPC_INCLUDE_DIR) From 4a2fcb9debd28a324113e929b92b2cce50473f70 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 11 Nov 2015 13:46:29 -0600 Subject: [PATCH 0537/6300] Use OpenSSL & zlib with CMake instead of Crypto++ --- build/CMakeLists.txt | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index ed8a281f..d8fae295 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -16,7 +16,7 @@ set ( CMAKE_SOURCE_DIR ".." ) set (COMMON_SRC "${CMAKE_SOURCE_DIR}/AddressBook.cpp" - "${CMAKE_SOURCE_DIR}/CryptoConst.cpp" + "${CMAKE_SOURCE_DIR}/Crypto.cpp" "${CMAKE_SOURCE_DIR}/Garlic.cpp" "${CMAKE_SOURCE_DIR}/I2NPProtocol.cpp" "${CMAKE_SOURCE_DIR}/Identity.cpp" @@ -40,8 +40,7 @@ set (COMMON_SRC "${CMAKE_SOURCE_DIR}/Transports.cpp" "${CMAKE_SOURCE_DIR}/TunnelEndpoint.cpp" "${CMAKE_SOURCE_DIR}/TunnelPool.cpp" - "${CMAKE_SOURCE_DIR}/aes.cpp" - "${CMAKE_SOURCE_DIR}/base64.cpp" + "${CMAKE_SOURCE_DIR}/Base.cpp" "${CMAKE_SOURCE_DIR}/util.cpp" "${CMAKE_SOURCE_DIR}/Datagram.cpp" "${CMAKE_SOURCE_DIR}/Signature.cpp" @@ -65,7 +64,7 @@ set (DAEMON_SRC "${CMAKE_SOURCE_DIR}/I2PTunnel.cpp" "${CMAKE_SOURCE_DIR}/SAM.cpp" "${CMAKE_SOURCE_DIR}/SOCKS.cpp" - "${CMAKE_SOURCE_DIR}/i2p.cpp" + "${CMAKE_SOURCE_DIR}/i2pd.cpp" ) if (WITH_UPNP) @@ -91,7 +90,9 @@ if (NOT CMAKE_BUILD_TYPE) endif () # compiler flags customization (by vendor) -if (NOT MSVC) +if (MSVC) + add_definitions( -D_WIN32_WINNT=_WIN32_WINNT_WINXP -DWIN32_LEAN_AND_MEAN -DNOMINMAX ) #-DOPENSSL_NO_SSL2 -DOPENSSL_USE_DEPRECATED +else() set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch" ) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic" ) # TODO: The following is incompatible with static build and enabled hardening for OpenWRT. @@ -212,18 +213,34 @@ if(NOT DEFINED Boost_INCLUDE_DIRS) message(SEND_ERROR "Boost is not found, or your boost version was bellow 1.46. Please download Boost!") endif() -find_package ( CryptoPP REQUIRED ) -if(NOT DEFINED CRYPTO++_INCLUDE_DIR) - message(SEND_ERROR "Could not find Crypto++. Please download and install it first!") +find_package ( OpenSSL REQUIRED ) +if(NOT DEFINED OPENSSL_INCLUDE_DIR) + message(SEND_ERROR "Could not find OpenSSL. Please download and install it first!") endif() find_package ( MiniUPnPc ) -if (NOT ${MINIUPNPC_FOUND}) +if (MINIUPNPC_FOUND) + include_directories( ${MINIUPNPC_INCLUDE_DIR} ) +else () set(WITH_UPNP OFF) endif() +find_package ( ZLIB ) +if (NOT DEFINED ZLIB-FOUND ) + include( ExternalProject ) + ExternalProject_Add(zlib + URL http://zlib.net/zlib-1.2.8.tar.gz + PREFIX ${CMAKE_CURRENT_BINARY_DIR}/zlib + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= + ) + add_dependencies( common zlib ) + ExternalProject_Get_Property(zlib install_dir) + set (ZLIB_ROOT ${install_dir} ) + find_package ( ZLIB REQUIRED ) +endif () + # load includes -include_directories( ${Boost_INCLUDE_DIRS} ${CRYPTO++_INCLUDE_DIR} ) +include_directories( ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ) # show summary message(STATUS "---------------------------------------") @@ -271,7 +288,7 @@ if (WITH_BINARY) if(${LAST_Boost_LIBRARIES} MATCHES ".*pthread.*") list(REMOVE_AT Boost_LIBRARIES -1) endif() - target_link_libraries( "${PROJECT_NAME}-bin" common ${DL_LIB} ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) + target_link_libraries( "${PROJECT_NAME}-bin" common ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ) install(TARGETS "${PROJECT_NAME}-bin" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) if (MSVC) @@ -285,7 +302,7 @@ if (WITH_LIBRARY) add_library(${PROJECT_NAME} STATIC ${LIBRARY_SRC}) else () add_library(${PROJECT_NAME} ${LIBRARY_SRC}) - target_link_libraries( ${PROJECT_NAME} common ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES}) + target_link_libraries( ${PROJECT_NAME} common ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES}) endif () if (WITH_PCH) if (MSVC) From cc2816aaf53dc958eae325d0910c167f0e38f9a7 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 11 Nov 2015 13:48:49 -0600 Subject: [PATCH 0538/6300] Use OpenSSL & zlib in precompiled headers --- stdafx.h | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/stdafx.h b/stdafx.h index edfac630..ed13bf8b 100644 --- a/stdafx.h +++ b/stdafx.h @@ -48,20 +48,20 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #endif From 79517a0ba3fbf09a42338206168d07b0782e9793 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Nov 2015 15:19:00 -0500 Subject: [PATCH 0539/6300] fixed clobbed z and t for Double --- Signature.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index 81510979..5951c74f 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -200,24 +200,24 @@ namespace crypto BIGNUM * x2 = BN_new (), * y2 = BN_new (), * z2 = BN_new (), * t2 = BN_new (); BIGNUM * z = p.z, * t = p.t; if (!z) { z = BN_new (); BN_one (z); } - BN_sqr (z, z, ctx); // z^2 (D) - if (!t) { t = BN_new (); BN_mul (t, p.x, p.y, ctx); } - BN_sqr (t, t, ctx); - BN_mul (t, t, d, ctx); // d*t^2 (C) + if (!t) { t = BN_new (); BN_mul (t, p.x, p.y, ctx); } - BIGNUM * A = BN_new (), * B = BN_new (); + BIGNUM * A = BN_new (), * B = BN_new (), * C = BN_new (), * D = BN_new (); BN_sqr (A, p.x, ctx); // A = x^2 - BN_sqr (B, p.y, ctx); // B = y^2 + BN_sqr (B, p.y, ctx); // B = y^2 + BN_sqr (C, t, ctx); + BN_mul (C, C, d, ctx); // C = d*t^2 + BN_sqr (D, z, ctx); // D = z^2 BIGNUM * E = BN_new (), * F = BN_new (), * G = BN_new (), * H = BN_new (); // E = (x+y)*(x+y)-A-B = x^2+y^2+2xy-A-B = 2xy BN_mul (E, p.x, p.y, ctx); BN_mul_word (E, 2); // E =2*x*y - BN_sub (F, z, t); // F = D - C = z - t - BN_add (G, z, t); // G = D + C = z + t + BN_sub (F, D, C); // F = D - C + BN_add (G, D, C); // G = D + C BN_add (H, B, A); // H = B + A - BN_free (A); BN_free (B); + BN_free (A); BN_free (B); BN_free (C); BN_free (D); if (!p.z) BN_free (z); if (!p.t) BN_free (t); From 5d94760cce509f7e98694589d979255139715195 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Nov 2015 16:24:56 -0500 Subject: [PATCH 0540/6300] eliminate some transient BIGNUM allocations --- Signature.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index 5951c74f..a415775f 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -202,22 +202,20 @@ namespace crypto if (!z) { z = BN_new (); BN_one (z); } if (!t) { t = BN_new (); BN_mul (t, p.x, p.y, ctx); } - BIGNUM * A = BN_new (), * B = BN_new (), * C = BN_new (), * D = BN_new (); - BN_sqr (A, p.x, ctx); // A = x^2 - BN_sqr (B, p.y, ctx); // B = y^2 - BN_sqr (C, t, ctx); - BN_mul (C, C, d, ctx); // C = d*t^2 - BN_sqr (D, z, ctx); // D = z^2 + BN_sqr (x2, p.x, ctx); // x2 = A = x^2 + BN_sqr (y2, p.y, ctx); // y2 = B = y^2 + BN_sqr (t2, t, ctx); + BN_mul (t2, t2, d, ctx); // t2 = C = d*t^2 + BN_sqr (z2, z, ctx); // z2 = D = z^2 BIGNUM * E = BN_new (), * F = BN_new (), * G = BN_new (), * H = BN_new (); // E = (x+y)*(x+y)-A-B = x^2+y^2+2xy-A-B = 2xy BN_mul (E, p.x, p.y, ctx); BN_mul_word (E, 2); // E =2*x*y - BN_sub (F, D, C); // F = D - C - BN_add (G, D, C); // G = D + C - BN_add (H, B, A); // H = B + A + BN_sub (F, z2, t2); // F = D - C + BN_add (G, z2, t2); // G = D + C + BN_add (H, y2, x2); // H = B + A - BN_free (A); BN_free (B); BN_free (C); BN_free (D); if (!p.z) BN_free (z); if (!p.t) BN_free (t); From c15c26a233c7fa2dd6e4f209a54fa62e9bd396c9 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 11 Nov 2015 21:24:53 -0600 Subject: [PATCH 0541/6300] Bring CMake stuff in agreement with #294 discussion --- build/CMakeLists.txt | 109 +++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 62 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index d8fae295..9365c4e6 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -14,8 +14,7 @@ option(WITH_PCH "Use precompiled header" OFF) set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) set ( CMAKE_SOURCE_DIR ".." ) -set (COMMON_SRC - "${CMAKE_SOURCE_DIR}/AddressBook.cpp" +set (LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/Crypto.cpp" "${CMAKE_SOURCE_DIR}/Garlic.cpp" "${CMAKE_SOURCE_DIR}/I2NPProtocol.cpp" @@ -44,27 +43,34 @@ set (COMMON_SRC "${CMAKE_SOURCE_DIR}/util.cpp" "${CMAKE_SOURCE_DIR}/Datagram.cpp" "${CMAKE_SOURCE_DIR}/Signature.cpp" - "${CMAKE_SOURCE_DIR}/UPnP.cpp" + "${CMAKE_SOURCE_DIR}/api.cpp" ) if (CMAKE_SYSTEM_NAME STREQUAL "Windows") - list (APPEND COMMON_SRC "${CMAKE_SOURCE_DIR}/I2PEndian.cpp") + list (APPEND LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/I2PEndian.cpp") endif () -add_library(common ${COMMON_SRC}) +add_library(libi2pd ${LIBI2PD_SRC}) -set (DAEMON_SRC +set (CLIENT_SRC + "${CMAKE_SOURCE_DIR}/AddressBook.cpp" "${CMAKE_SOURCE_DIR}/BOB.cpp" "${CMAKE_SOURCE_DIR}/ClientContext.cpp" - "${CMAKE_SOURCE_DIR}/Daemon.cpp" - "${CMAKE_SOURCE_DIR}/HTTPProxy.cpp" - "${CMAKE_SOURCE_DIR}/HTTPServer.cpp" - "${CMAKE_SOURCE_DIR}/I2PService.cpp" - "${CMAKE_SOURCE_DIR}/I2PControl.cpp" "${CMAKE_SOURCE_DIR}/I2PTunnel.cpp" + "${CMAKE_SOURCE_DIR}/I2PService.cpp" "${CMAKE_SOURCE_DIR}/SAM.cpp" "${CMAKE_SOURCE_DIR}/SOCKS.cpp" + "${CMAKE_SOURCE_DIR}/HTTPProxy.cpp" + ) + +add_library(i2pdclient ${CLIENT_SRC}) + +set (DAEMON_SRC + "${CMAKE_SOURCE_DIR}/Daemon.cpp" + "${CMAKE_SOURCE_DIR}/HTTPServer.cpp" + "${CMAKE_SOURCE_DIR}/I2PControl.cpp" "${CMAKE_SOURCE_DIR}/i2pd.cpp" + "${CMAKE_SOURCE_DIR}/UPnP.cpp" ) if (WITH_UPNP) @@ -74,16 +80,6 @@ if (WITH_UPNP) endif () endif () -set (LIBRARY_SRC - "${CMAKE_SOURCE_DIR}/api.cpp" -) - -file (GLOB HEADERS "${CMAKE_SOURCE_DIR}/*.h") - -# MSVS grouping -source_group ("Header Files" FILES ${HEADERS}) -source_group ("Source Files" FILES ${COMMON_SRC} ${DAEMON_SRC} ${LIBRARY_SRC}) - # Default build is Debug if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) @@ -176,7 +172,7 @@ if (WITH_STATIC) endif () else() if (NOT WIN32) - # TODO: Consider separate compilation for COMMON_SRC for library. + # TODO: Consider separate compilation for LIBI2PD_SRC for library. # No need in -fPIC overhead for binary if not interested in library # HINT: revert c266cff CMakeLists.txt: compilation speed up set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" ) @@ -190,12 +186,13 @@ if (WITH_PCH) if(MSVC) target_compile_options(stdafx PRIVATE /Ycstdafx.h /Zm135) add_custom_command(TARGET stdafx POST_BUILD - COMMAND xcopy /y stdafx.dir\\$\\*.pdb common.dir\\$\\ - COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pd-bin.dir\\$\\ + COMMAND xcopy /y stdafx.dir\\$\\*.pdb libi2pd.dir\\$\\ + COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pdclient.dir\\$\\ COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pd.dir\\$\\ WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) - target_compile_options(common PRIVATE /FIstdafx.h /Yustdafx.h /Zm135 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") + target_compile_options(libi2pd PRIVATE /FIstdafx.h /Yustdafx.h /Zm135 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") + target_compile_options(i2pdclient PRIVATE /FIstdafx.h /Yustdafx.h /Zm135 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") else() string(TOUPPER ${CMAKE_BUILD_TYPE} BTU) get_directory_property(DEFS DEFINITIONS) @@ -203,9 +200,11 @@ if (WITH_PCH) add_custom_command(TARGET stdafx PRE_BUILD COMMAND ${CMAKE_CXX_COMPILER} ${FLAGS} -c ${CMAKE_CURRENT_SOURCE_DIR}/../stdafx.h ) - target_compile_options(common PRIVATE -include stdafx.h) + target_compile_options(libi2pd PRIVATE -include stdafx.h) + target_compile_options(i2pdclient PRIVATE -include stdafx.h) endif() - target_link_libraries(common stdafx) + target_link_libraries(libi2pd stdafx) + target_link_libraries(i2pdclient stdafx) endif() find_package ( Boost COMPONENTS system filesystem regex program_options date_time thread chrono REQUIRED ) @@ -226,18 +225,24 @@ else () endif() find_package ( ZLIB ) -if (NOT DEFINED ZLIB-FOUND ) +if (NOT ZLIB_FOUND ) + # We are probably on Windows include( ExternalProject ) - ExternalProject_Add(zlib + ExternalProject_Add(zlib-project URL http://zlib.net/zlib-1.2.8.tar.gz PREFIX ${CMAKE_CURRENT_BINARY_DIR}/zlib CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= - ) - add_dependencies( common zlib ) - ExternalProject_Get_Property(zlib install_dir) - set (ZLIB_ROOT ${install_dir} ) - find_package ( ZLIB REQUIRED ) + ) + if (WITH_PCH) + add_dependencies( stdafx zlib-project ) + else () + add_dependencies( libi2pd zlib-project ) + endif () + # ExternalProject_Get_Property(zlib-project install_dir) + set ( ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/zlib/include" CACHE FILEPATH "zlib include dir" FORCE) + set ( ZLIB_LIBRARY debug zlibd optimized zlib CACHE STRING "zlib libraries" FORCE) endif () +link_directories("${CMAKE_CURRENT_BINARY_DIR}/zlib/lib") # load includes include_directories( ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ) @@ -263,24 +268,23 @@ message(STATUS "---------------------------------------") include(GNUInstallDirs) if (WITH_BINARY) - add_executable ( "${PROJECT_NAME}-bin" ${DAEMON_SRC} ) + add_executable ( "${PROJECT_NAME}" ${DAEMON_SRC} ) if(NOT MSVC) # FIXME: incremental linker file name (.ilk) collision for dll & exe - set_target_properties("${PROJECT_NAME}-bin" PROPERTIES OUTPUT_NAME "${PROJECT_NAME}") if (WITH_STATIC) - set_target_properties("${PROJECT_NAME}-bin" PROPERTIES LINK_FLAGS "-static" ) + set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static" ) endif () endif() if (WITH_PCH) if (MSVC) - target_compile_options("${PROJECT_NAME}-bin" PRIVATE /FIstdafx.h /Yustdafx.h /Zm135 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") + target_compile_options("${PROJECT_NAME}" PRIVATE /FIstdafx.h /Yustdafx.h /Zm135 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") else() - target_compile_options("${PROJECT_NAME}-bin" PRIVATE -include stdafx.h) + target_compile_options("${PROJECT_NAME}" PRIVATE -include stdafx.h) endif() endif() if (WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set_target_properties("${PROJECT_NAME}-bin" PROPERTIES LINK_FLAGS "-z relro -z now" ) + set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-z relro -z now" ) endif () # FindBoost pulls pthread for thread which is broken for static linking at least on Ubuntu 15.04 @@ -288,29 +292,10 @@ if (WITH_BINARY) if(${LAST_Boost_LIBRARIES} MATCHES ".*pthread.*") list(REMOVE_AT Boost_LIBRARIES -1) endif() - target_link_libraries( "${PROJECT_NAME}-bin" common ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ) + target_link_libraries( "${PROJECT_NAME}" libi2pd i2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ) - install(TARGETS "${PROJECT_NAME}-bin" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) + install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) if (MSVC) - install(FILES $ DESTINATION "bin" CONFIGURATIONS DEBUG) + install(FILES $ DESTINATION "bin" CONFIGURATIONS DEBUG) endif () endif () - -if (WITH_LIBRARY) - if (MSVC) - # FIXME: DLL would not have any symbols unless we use __declspec(dllexport) through out the code - add_library(${PROJECT_NAME} STATIC ${LIBRARY_SRC}) - else () - add_library(${PROJECT_NAME} ${LIBRARY_SRC}) - target_link_libraries( ${PROJECT_NAME} common ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES}) - endif () - if (WITH_PCH) - if (MSVC) - add_dependencies(${PROJECT_NAME} stdafx) - target_compile_options(${PROJECT_NAME} PRIVATE /FIstdafx.h /Yustdafx.h /Zm135 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") - else() - target_compile_options(${PROJECT_NAME} PRIVATE -include stdafx.h) - endif() - endif() - install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) -endif () From 11b7e637e904a33c5043fb5397dab1662d48501c Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 Nov 2015 15:39:48 -0500 Subject: [PATCH 0542/6300] fixed complation error for boost 1.49 and gcc 4.7 and higher --- I2PControl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 1dae4997..9a7543ca 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -1,5 +1,5 @@ // There is bug in boost 1.49 with gcc 4.7 coming with Debian Wheezy -#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ == 7)) +#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) #include "I2PControl.h" #include From 302df75d83b5b61ae383e35461c6a8ed17499ee1 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 16 Nov 2015 13:27:27 -0500 Subject: [PATCH 0543/6300] skip extended options in SSU header --- SSUSession.cpp | 17 +++++++++++++---- SSUSession.h | 4 +++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index be020729..33534ec1 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -131,16 +131,25 @@ namespace transport } } + size_t SSUSession::GetSSUHeaderSize (uint8_t * buf) const + { + size_t s = sizeof (SSUHeader); + if (((SSUHeader *)buf)->IsExtendedOptions ()) + s += buf[s] + 1; // byte right after header is extended options length + return s; + } + void SSUSession::ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { len -= (len & 0x0F); // %16, delete extra padding if (len <= sizeof (SSUHeader)) return; // drop empty message //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved + auto headerSize = GetSSUHeaderSize (buf); SSUHeader * header = (SSUHeader *)buf; switch (header->GetPayloadType ()) { case PAYLOAD_TYPE_DATA: - ProcessData (buf + sizeof (SSUHeader), len - sizeof (SSUHeader)); + ProcessData (buf + headerSize, len - headerSize); break; case PAYLOAD_TYPE_SESSION_REQUEST: ProcessSessionRequest (buf, len, senderEndpoint); @@ -153,7 +162,7 @@ namespace transport break; case PAYLOAD_TYPE_PEER_TEST: LogPrint (eLogDebug, "SSU peer test received"); - ProcessPeerTest (buf + sizeof (SSUHeader), len - sizeof (SSUHeader), senderEndpoint); + ProcessPeerTest (buf + headerSize, len - headerSize, senderEndpoint); break; case PAYLOAD_TYPE_SESSION_DESTROYED: { @@ -168,11 +177,11 @@ namespace transport break; case PAYLOAD_TYPE_RELAY_REQUEST: LogPrint (eLogDebug, "SSU relay request received"); - ProcessRelayRequest (buf + sizeof (SSUHeader), len - sizeof (SSUHeader), senderEndpoint); + ProcessRelayRequest (buf + headerSize, len - headerSize, senderEndpoint); break; case PAYLOAD_TYPE_RELAY_INTRO: LogPrint (eLogDebug, "SSU relay intro received"); - ProcessRelayIntro (buf + sizeof (SSUHeader), len - sizeof (SSUHeader)); + ProcessRelayIntro (buf + headerSize, len - headerSize); break; default: LogPrint (eLogWarning, "Unexpected SSU payload type ", (int)header->GetPayloadType ()); diff --git a/SSUSession.h b/SSUSession.h index 99be99a8..cf6e688f 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -13,6 +13,7 @@ namespace i2p { namespace transport { + const uint8_t SSU_HEADER_EXTENDED_OPTIONS_INCLUDED = 0x04; #pragma pack(1) struct SSUHeader { @@ -22,6 +23,7 @@ namespace transport uint32_t time; uint8_t GetPayloadType () const { return flag >> 4; }; + bool IsExtendedOptions () const { return flag & SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; }; }; #pragma pack() @@ -93,7 +95,7 @@ namespace transport boost::asio::io_service& GetService (); void CreateAESandMacKey (const uint8_t * pubKey); - + size_t GetSSUHeaderSize (uint8_t * buf) const; void PostI2NPMessages (std::vector > msgs); void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session void ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); From 66d51a9eb1598282df886e5de9948f48a38a75f2 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Nov 2015 09:26:38 -0500 Subject: [PATCH 0544/6300] 0.9.23/2.1.0 version update --- version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index 141664b5..825be574 100644 --- a/version.h +++ b/version.h @@ -2,7 +2,7 @@ #define _VERSION_H_ #define CODENAME "Purple" -#define VERSION "2.0.0" -#define I2P_VERSION "0.9.22" +#define VERSION "2.1.0" +#define I2P_VERSION "0.9.23" #endif From 24d9dacfd9eaaed8af54d913cfbf3bca7d978ef4 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Nov 2015 21:02:55 -0500 Subject: [PATCH 0545/6300] fixed mingw build --- Makefile.mingw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.mingw b/Makefile.mingw index 4f17e68b..9a79b976 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -3,4 +3,4 @@ CXXFLAGS = -O2 -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN NEEDED_CXXFLAGS = -std=c++11 INCFLAGS = -I/usr/include/ -I/usr/local/include/ -I/c/dev/openssl/include -I/c/dev/boost/include/boost-1_59 LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/c/dev/openssl -L/c/dev/boost/lib -LDLIBS = -lboost_system-mgw48-mt-1_59 -lboost_date_time-mgw48-mt-1_59 -lboost_filesystem-mgw48-mt-1_59 -lboost_regex-mgw48-mt-1_59 -lboost_program_options-mgw48-mt-1_59 -lssl -lcrypto -lz -lpthread -lwsock32 -lws2_32 -lgdi32 +LDLIBS = -lboost_system-mgw48-mt-1_59 -lboost_date_time-mgw48-mt-1_59 -lboost_filesystem-mgw48-mt-1_59 -lboost_regex-mgw48-mt-1_59 -lboost_program_options-mgw48-mt-1_59 -lssl -lcrypto -lz -lpthread -lwsock32 -lws2_32 -lgdi32 -liphlpapi From f6eabd695b80bd46d225c71a973cb16164ac8028 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Nov 2015 22:38:18 -0500 Subject: [PATCH 0546/6300] don't store B explicitly --- Signature.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index a415775f..2a3cb780 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -47,6 +47,9 @@ namespace crypto BN_div_word (tmp, 4); BN_mod_exp (I, two, tmp, q, ctx); + BN_free (two); + BN_free (tmp); + // 4*inv(5) BIGNUM * By = BN_new (); BN_set_word (By, 5); @@ -54,14 +57,10 @@ namespace crypto BN_mul_word (By, 4); BIGNUM * Bx = RecoverX (By, ctx); BN_mod (Bx, Bx, q, ctx); // % q - BN_mod (By, By, q, ctx); // % q - B = {Bx, By}; - - BN_free (two); - BN_free (tmp); + BN_mod (By, By, q, ctx); // % q // precalculate Bi16 table - Bi16[0][0] = { BN_dup (Bx), BN_dup (By) }; + Bi16[0][0] = { Bx, By }; // B for (int i = 0; i < 64; i++) { if (i) Bi16[i][0] = Sum (Bi16[i-1][14], Bi16[i-1][0], ctx); @@ -384,10 +383,10 @@ namespace crypto private: BIGNUM * q, * l, * d, * I; - EDDSAPoint B; // base point // transient values BIGNUM * two_252_2; // 2^252-2 EDDSAPoint Bi16[64][15]; // per 4-bits, Bi16[i][j] = (16+j+1)^i*B, we don't store zeroes + // Bi16[0][0] = B, base point }; static std::unique_ptr g_Ed25519; From 4109ab1590791f3c1bf4e9eceec2d43be7b5ea47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edward=20Tj=C3=B6rnhammar?= Date: Fri, 20 Nov 2015 13:52:38 +0100 Subject: [PATCH 0547/6300] Make tunnels.cfg configurable --- ClientContext.cpp | 7 ++++--- ClientContext.h | 1 - README.md | 1 + util.cpp | 8 ++++++++ util.h | 1 + 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 0adeee9b..84ffb538 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -243,13 +243,14 @@ namespace client void ClientContext::ReadTunnels () { boost::property_tree::ptree pt; + std::string pathTunnelsConfigFile = i2p::util::filesystem::GetTunnelsConfigFile().string(); try { - boost::property_tree::read_ini (i2p::util::filesystem::GetFullPath (TUNNELS_CONFIG_FILENAME), pt); + boost::property_tree::read_ini (pathTunnelsConfigFile, pt); } catch (std::exception& ex) { - LogPrint (eLogWarning, "Can't read ", TUNNELS_CONFIG_FILENAME, ": ", ex.what ()); + LogPrint (eLogWarning, "Can't read ", pathTunnelsConfigFile, ": ", ex.what ()); return; } @@ -313,7 +314,7 @@ namespace client numServerTunnels++; } else - LogPrint (eLogWarning, "Unknown section type=", type, " of ", name, " in ", TUNNELS_CONFIG_FILENAME); + LogPrint (eLogWarning, "Unknown section type=", type, " of ", name, " in ", pathTunnelsConfigFile); } catch (std::exception& ex) diff --git a/ClientContext.h b/ClientContext.h index 762b2e75..842ee918 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -29,7 +29,6 @@ namespace client const char I2P_SERVER_TUNNEL_KEYS[] = "keys"; const char I2P_SERVER_TUNNEL_INPORT[] = "inport"; const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist"; - const char TUNNELS_CONFIG_FILENAME[] = "tunnels.cfg"; class ClientContext { diff --git a/README.md b/README.md index 393aab79..631bd55d 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ Cmdline options * --samport= - Port of SAM bridge. Usually 7656. SAM is off if not specified * --bobport= - Port of BOB command channel. Usually 2827. BOB is off if not specified * --i2pcontrolport= - Port of I2P control service. Usually 7650. I2PControl is off if not specified +* --tunnelscfg= - Tunnels Config file (default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg) * --conf= - Config file (default: ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf) This parameter will be silently ignored if the specified config file does not exist. Options specified on the command line take precedence over those in the config file. diff --git a/util.cpp b/util.cpp index fbaf2dbf..097dc90c 100644 --- a/util.cpp +++ b/util.cpp @@ -193,6 +193,14 @@ namespace filesystem return pathConfigFile; } + boost::filesystem::path GetTunnelsConfigFile() + { + boost::filesystem::path pathTunnelsConfigFile(i2p::util::config::GetArg("-tunnelscfg", "tunnels.cfg")); + if (!pathTunnelsConfigFile.is_complete()) + pathTunnelsConfigFile = GetDataDir() / pathTunnelsConfigFile; + return pathTunnelsConfigFile; + } + void ReadConfigFile(std::map& mapSettingsRet, std::map >& mapMultiSettingsRet) { diff --git a/util.h b/util.h index 4b229380..1a579283 100644 --- a/util.h +++ b/util.h @@ -33,6 +33,7 @@ namespace util std::string GetFullPath (const std::string& filename); boost::filesystem::path GetDefaultDataDir(); boost::filesystem::path GetConfigFile(); + boost::filesystem::path GetTunnelsConfigFile(); void ReadConfigFile(std::map& mapSettingsRet, std::map >& mapMultiSettingsRet); boost::filesystem::path GetCertificatesDir(); From d01a21a86733296d739825e13527c6408538b10d Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Nov 2015 09:55:34 -0500 Subject: [PATCH 0548/6300] backport openbsd support --- I2PEndian.h | 2 +- build/CMakeLists.txt | 2 ++ util.cpp | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/I2PEndian.h b/I2PEndian.h index 687dbc63..2b34895f 100644 --- a/I2PEndian.h +++ b/I2PEndian.h @@ -3,7 +3,7 @@ #include #include -#if defined(__linux__) || defined(__FreeBSD_kernel__) +#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) #include #elif __FreeBSD__ #include diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 9365c4e6..c910f8f6 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -130,6 +130,8 @@ elseif (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") add_definitions( "-D_GLIBCXX_USE_NANOSLEEP=1" ) elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonLinux.cpp") +elseif (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonLinux.cpp") elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonWin32.cpp") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32Service.cpp") diff --git a/util.cpp b/util.cpp index 097dc90c..a56383d5 100644 --- a/util.cpp +++ b/util.cpp @@ -16,7 +16,7 @@ #include "util.h" #include "Log.h" -#if defined(__linux__) || defined(__FreeBSD_kernel__) +#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) #include #include #elif defined(WIN32) @@ -508,7 +508,7 @@ namespace net { int GetMTU (const boost::asio::ip::address& localAddress) { -#if defined(__linux__) || defined(__FreeBSD_kernel__) +#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) ifaddrs * ifaddr, * ifa = nullptr; if (getifaddrs(&ifaddr) == -1) { From 54b2c8bd7e7796bb401a6c9f3fd0b457db201145 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Nov 2015 10:02:54 -0500 Subject: [PATCH 0549/6300] backport fix build for clang --- Destination.cpp | 4 ++-- Log.cpp | 2 +- Log.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 479006c8..6e2550eb 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -527,7 +527,7 @@ namespace client { if (!m_Pool || !IsReady ()) { - if (requestComplete) requestComplete (false); + if (requestComplete) requestComplete (nullptr); return false; } m_Service.post (std::bind (&ClientDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete)); @@ -630,7 +630,7 @@ namespace client if (done) { - if (it->second->requestComplete) it->second->requestComplete (false); + if (it->second->requestComplete) it->second->requestComplete (nullptr); delete it->second; m_LeaseSetRequests.erase (it); } diff --git a/Log.cpp b/Log.cpp index 0a561e0c..a25b25e8 100644 --- a/Log.cpp +++ b/Log.cpp @@ -24,7 +24,7 @@ void LogMsg::Process() const std::string& Log::GetTimestamp () { -#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) +#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) && !defined(__clang__) auto ts = std::chrono::monotonic_clock::now (); #else auto ts = std::chrono::steady_clock::now (); diff --git a/Log.h b/Log.h index ea821954..8728149f 100644 --- a/Log.h +++ b/Log.h @@ -50,7 +50,7 @@ class Log: public i2p::util::MsgQueue std::ostream * m_LogStream; std::string m_Timestamp; -#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) // gcc 4.6 +#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) && !defined(__clang__) // gcc 4.6 std::chrono::monotonic_clock::time_point m_LastTimestampUpdate; #else std::chrono::steady_clock::time_point m_LastTimestampUpdate; From c42636b0eeeead208cc4b50dcc1296a536839547 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Nov 2015 10:10:13 -0500 Subject: [PATCH 0550/6300] check for zero-length --- Base.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Base.cpp b/Base.cpp index 0bc424e3..c9ba407a 100644 --- a/Base.cpp +++ b/Base.cpp @@ -154,7 +154,7 @@ namespace data if (isFirstTime) iT64Build(); n = InCount/4; m = InCount%4; - if (!m) + if (InCount && !m) outCount = 3*n; else { outCount = 0; From a8f223949556f0bae783b148877c8f340ee99eda Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Nov 2015 10:36:04 -0500 Subject: [PATCH 0551/6300] backport GetMTU --- util.cpp | 367 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 192 insertions(+), 175 deletions(-) diff --git a/util.cpp b/util.cpp index a56383d5..244413ab 100644 --- a/util.cpp +++ b/util.cpp @@ -506,200 +506,217 @@ namespace http namespace net { - int GetMTU (const boost::asio::ip::address& localAddress) - { -#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) - ifaddrs * ifaddr, * ifa = nullptr; - if (getifaddrs(&ifaddr) == -1) +#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) + + int GetMTUUnix(const boost::asio::ip::address& localAddress, int fallback) + { + ifaddrs* ifaddr, *ifa = nullptr; + if(getifaddrs(&ifaddr) == -1) { - LogPrint (eLogError, "Can't excute getifaddrs"); - return 0; + LogPrint(eLogError, "Can't excute getifaddrs"); + return fallback; } - int family = 0; - // loook for interface matching local address - for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + + int family = 0; + // look for interface matching local address + for(ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { - if (!ifa->ifa_addr) continue; - family = ifa->ifa_addr->sa_family; - if (family == AF_INET && localAddress.is_v4 ()) + if(!ifa->ifa_addr) + continue; + + family = ifa->ifa_addr->sa_family; + if(family == AF_INET && localAddress.is_v4()) { - sockaddr_in * sa = (sockaddr_in *)ifa->ifa_addr; - if (!memcmp (&sa->sin_addr, localAddress.to_v4 ().to_bytes ().data (), 4)) - break; // address matches - } - else if (family == AF_INET6 && localAddress.is_v6 ()) + sockaddr_in* sa = (sockaddr_in*) ifa->ifa_addr; + if(!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4)) + break; // address matches + } + else if(family == AF_INET6 && localAddress.is_v6()) { - sockaddr_in6 * sa = (sockaddr_in6 *)ifa->ifa_addr; - if (!memcmp (&sa->sin6_addr, localAddress.to_v6 ().to_bytes ().data (), 16)) - break; // address matches - } - } - int mtu = 0; - if (ifa && family) // interface found? - { - int fd = socket (family, SOCK_DGRAM, 0); - if (fd > 0) + sockaddr_in6* sa = (sockaddr_in6*) ifa->ifa_addr; + if(!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16)) + break; // address matches + } + } + int mtu = fallback; + if(ifa && family) + { // interface found? + int fd = socket(family, SOCK_DGRAM, 0); + if(fd > 0) { - ifreq ifr; - strncpy (ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query - if (ioctl (fd, SIOCGIFMTU, &ifr) >= 0) - mtu = ifr.ifr_mtu; // MTU - else - LogPrint (eLogError, "Failed to run ioctl"); - close (fd); - } + ifreq ifr; + strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query + if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) + mtu = ifr.ifr_mtu; // MTU + else + LogPrint (eLogError, "Failed to run ioctl"); + close(fd); + } else - LogPrint (eLogError, "Failed to create datagram socket"); - } - else - LogPrint (eLogWarning, "Interface for local address", localAddress.to_string (), " not found"); + LogPrint(eLogError, "Failed to create datagram socket"); + } + else + LogPrint(eLogWarning, "Interface for local address", localAddress.to_string(), " not found"); + freeifaddrs(ifaddr); - freeifaddrs (ifaddr); - return mtu; -#elif defined(WIN32) + return mtu; + } - int result = 576; // fallback MTU +#elif defined(_WIN32) + int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback) + { + ULONG outBufLen = 0; + PIP_ADAPTER_ADDRESSES pAddresses = nullptr; + PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; - DWORD dwRetVal = 0; - ULONG outBufLen = 0; - PIP_ADAPTER_ADDRESSES pAddresses = nullptr; - PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; - PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; + if(GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) + == ERROR_BUFFER_OVERFLOW) { + FREE(pAddresses); + pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); + } + + DWORD dwRetVal = GetAdaptersAddresses( + AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen + ); + + if(dwRetVal != NO_ERROR) { + LogPrint( + eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed" + ); + FREE(pAddresses); + return fallback; + } + + pCurrAddresses = pAddresses; + while(pCurrAddresses) { + PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; + + pUnicast = pCurrAddresses->FirstUnicastAddress; + if(pUnicast == nullptr) { + LogPrint( + eLogError, "GetMTU() has failed: not a unicast ipv4 address, this is not supported" + ); + } + for(int i = 0; pUnicast != nullptr; ++i) { + LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; + sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr; + if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) { + auto result = pAddresses->Mtu; + FREE(pAddresses); + return result; + } + pUnicast = pUnicast->Next; + } + pCurrAddresses = pCurrAddresses->Next; + } + + LogPrint(eLogError, "GetMTU() error: no usable unicast ipv4 addresses found"); + FREE(pAddresses); + return fallback; + } + + int GetMTUWindowsIpv6(sockaddr_in6 inputAddress, int fallback) + { + ULONG outBufLen = 0; + PIP_ADAPTER_ADDRESSES pAddresses = nullptr; + PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; + + if(GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) + == ERROR_BUFFER_OVERFLOW) { + FREE(pAddresses); + pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); + } + + DWORD dwRetVal = GetAdaptersAddresses( + AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen + ); + + if(dwRetVal != NO_ERROR) { + LogPrint( + eLogError, + "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed" + ); + FREE(pAddresses); + return fallback; + } + + bool found_address = false; + pCurrAddresses = pAddresses; + while(pCurrAddresses) { + PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; + pUnicast = pCurrAddresses->FirstUnicastAddress; + if(pUnicast == nullptr) { + LogPrint( + eLogError, + "GetMTU() has failed: not a unicast ipv6 address, this is not supported" + ); + } + for(int i = 0; pUnicast != nullptr; ++i) { + LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; + sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; + + for (int j = 0; j != 8; ++j) { + if (localInterfaceAddress->sin6_addr.u.Word[j] != inputAddress.sin6_addr.u.Word[j]) { + break; + } else { + found_address = true; + } + } if (found_address) { + auto result = pAddresses->Mtu; + FREE(pAddresses); + pAddresses = nullptr; + return result; + } + pUnicast = pUnicast->Next; + } + + pCurrAddresses = pCurrAddresses->Next; + } + + LogPrint(eLogError, "GetMTU() error: no usable unicast ipv6 addresses found"); + FREE(pAddresses); + return fallback; + } + + int GetMTUWindows(const boost::asio::ip::address& localAddress, int fallback) + { #ifdef UNICODE - string localAddress_temporary = localAddress.to_string(); - wstring localAddressUniversal (localAddress_temporary.begin(), localAddress_temporary.end()); + string localAddress_temporary = localAddress.to_string(); + wstring localAddressUniversal(localAddress_temporary.begin(), localAddress_temporary.end()); #else - std::string localAddressUniversal = localAddress.to_string(); + std::string localAddressUniversal = localAddress.to_string(); #endif - if (localAddress.is_v4()) - { - struct sockaddr_in inputAddress; - inet_pton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); + if(localAddress.is_v4()) { + sockaddr_in inputAddress; + inet_pton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); + return GetMTUWindowsIpv4(inputAddress, fallback); + } else if(localAddress.is_v6()) { + sockaddr_in6 inputAddress; + inet_pton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); + return GetMTUWindowsIpv6(inputAddress, fallback); + } else { + LogPrint(eLogError, "GetMTU() has failed: address family is not supported"); + return fallback; + } - if (GetAdaptersAddresses (AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) - == ERROR_BUFFER_OVERFLOW) - { - FREE (pAddresses); - pAddresses = (IP_ADAPTER_ADDRESSES *)MALLOC (outBufLen); - } + } +#endif // WIN32 - dwRetVal = GetAdaptersAddresses (AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen); - if (dwRetVal == NO_ERROR) - { - pCurrAddresses = pAddresses; - while (pCurrAddresses) - { - PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; + int GetMTU(const boost::asio::ip::address& localAddress) + { + const int fallback = 576; // fallback MTU - pUnicast = pCurrAddresses->FirstUnicastAddress; - if (pUnicast != nullptr) - { - for (int i = 0; pUnicast != nullptr; ++i) - { - LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; - struct sockaddr_in *localInterfaceAddress = (struct sockaddr_in*) lpAddr; - if (localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) - { - result = pAddresses->Mtu; - FREE (pAddresses); - pAddresses = nullptr; - return result; - } - pUnicast = pUnicast->Next; - } - } - else - { - LogPrint (eLogError, "GetMTU() has failed: not a unicast ipv4 address, this is not supported"); - } - - pCurrAddresses = pCurrAddresses->Next; - } - - } - else - { - LogPrint (eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed"); - } - - } - else if (localAddress.is_v6()) - { - struct sockaddr_in6 inputAddress; - inet_pton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); - - if (GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) - == ERROR_BUFFER_OVERFLOW) - { - FREE (pAddresses); - pAddresses = (IP_ADAPTER_ADDRESSES *)MALLOC (outBufLen); - } - - dwRetVal = GetAdaptersAddresses (AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen); - if (dwRetVal == NO_ERROR) - { - bool found_address = false; - pCurrAddresses = pAddresses; - while (pCurrAddresses) - { - PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; - - pUnicast = pCurrAddresses->FirstUnicastAddress; - if (pUnicast != nullptr) - { - for (int i = 0; pUnicast != nullptr; ++i) - { - LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; - struct sockaddr_in6 *localInterfaceAddress = (struct sockaddr_in6*) lpAddr; - - for (int j = 0; j != 8; ++j) - { - if (localInterfaceAddress->sin6_addr.u.Word[j] != inputAddress.sin6_addr.u.Word[j]) - { - break; - } - else - { - found_address = true; - } - } - if (found_address) - { - result = pAddresses->Mtu; - FREE (pAddresses); - pAddresses = nullptr; - return result; - } - pUnicast = pUnicast->Next; - } - } - else - { - LogPrint (eLogError, "GetMTU() has failed: not a unicast ipv6 address, this is not supported"); - } - - pCurrAddresses = pCurrAddresses->Next; - } - - } - else - { - LogPrint (eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed"); - } - } - else - { - LogPrint (eLogError, "GetMTU() has failed: address family is not supported"); - } - - FREE (pAddresses); - pAddresses = nullptr; - LogPrint(eLogError, "GetMTU() error: control flow should never reach this line"); - return result; +#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) + return GetMTUUnix(localAddress, fallback); +#elif defined(WIN32) + return GetMTUWindows(localAddress, fallback); #endif - } + return fallback; + } } } // util From 50dda4263f216642ab3276f4c27d72811839a41c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Nov 2015 11:42:38 -0500 Subject: [PATCH 0552/6300] fixed mingw build error --- util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util.cpp b/util.cpp index 244413ab..b2faddc2 100644 --- a/util.cpp +++ b/util.cpp @@ -41,7 +41,7 @@ http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found */ char src_copy[INET6_ADDRSTRLEN + 1]; ZeroMemory (&ss, sizeof (ss)); - strncpy_s (src_copy, src, INET6_ADDRSTRLEN + 1); + strncpy (src_copy, src, INET6_ADDRSTRLEN + 1); src_copy[INET6_ADDRSTRLEN] = 0; if (WSAStringToAddress (src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0) From 1588d2734c8f383b459099ae56221154fb2fadc9 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Nov 2015 12:30:20 -0500 Subject: [PATCH 0553/6300] use path.string () instead path.c_str () --- AddressBook.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index ed7b56ae..c06186ed 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -52,7 +52,7 @@ namespace client std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const { auto filename = GetPath () / (ident.ToBase32() + ".b32"); - std::ifstream f(filename.c_str (), std::ifstream::binary); + std::ifstream f(filename.string (), std::ifstream::binary); if (f.is_open ()) { f.seekg (0,std::ios::end); @@ -76,7 +76,7 @@ namespace client void AddressBookFilesystemStorage::AddAddress (std::shared_ptr address) { auto filename = GetPath () / (address->GetIdentHash ().ToBase32() + ".b32"); - std::ofstream f (filename.c_str (), std::ofstream::binary | std::ofstream::out); + std::ofstream f (filename.string (), std::ofstream::binary | std::ofstream::out); if (f.is_open ()) { size_t len = address->GetFullLen (); @@ -100,7 +100,7 @@ namespace client { int num = 0; auto filename = GetPath () / "addresses.csv"; - std::ifstream f (filename.c_str (), std::ofstream::in); // in text mode + std::ifstream f (filename.string (), std::ofstream::in); // in text mode if (f.is_open ()) { addresses.clear (); @@ -134,7 +134,7 @@ namespace client { int num = 0; auto filename = GetPath () / "addresses.csv"; - std::ofstream f (filename.c_str (), std::ofstream::out); // in text mode + std::ofstream f (filename.string (), std::ofstream::out); // in text mode if (f.is_open ()) { for (auto it: addresses) From c1e2ee32b476ddf832d3c338269b7981ed31f56a Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Nov 2015 12:34:53 -0500 Subject: [PATCH 0554/6300] fixed mingw build error --- Win32/Win32Service.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Win32/Win32Service.cpp b/Win32/Win32Service.cpp index e5abc72a..bf7794e8 100644 --- a/Win32/Win32Service.cpp +++ b/Win32/Win32Service.cpp @@ -76,7 +76,7 @@ I2PService::I2PService(PSTR pszServiceName, BOOL fCanShutdown, BOOL fCanPauseContinue) { - m_name = (pszServiceName == NULL) ? "" : pszServiceName; + m_name = (pszServiceName == NULL) ? (PSTR)"" : pszServiceName; m_statusHandle = NULL; From ad9ade7849c8ba0390724468e06ea43001664d8a Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Nov 2015 21:27:16 -0500 Subject: [PATCH 0555/6300] reduce number of transient BIGNUM allocations --- Signature.cpp | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index 2a3cb780..6a92eff6 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -161,24 +161,22 @@ namespace crypto if (!z2) { z2 = BN_new (); BN_one (z2); } if (!t2) { t2 = BN_new (); BN_mul (t2, p2.x, p2.y, ctx); } - BIGNUM * A = BN_new (), * B = BN_new (), * C = BN_new (), * D = BN_new (); - BN_mul (A, p1.x, p2.x, ctx); // A = x1*x2 - BN_mul (B, p1.y, p2.y, ctx); // B = y1*y2 - BN_mul (C, t1, t2, ctx); - BN_mul (C, C, d, ctx); // C = d*t1*t2 - BN_mul (D, z1, z2, ctx); // D = z1*z2 + BN_mul (x3, p1.x, p2.x, ctx); // A = x1*x2 + BN_mul (y3, p1.y, p2.y, ctx); // B = y1*y2 + BN_mul (t3, t1, t2, ctx); + BN_mul (t3, t3, d, ctx); // C = d*t1*t2 + BN_mul (z3, z1, z2, ctx); // D = z1*z2 BIGNUM * E = BN_new (), * F = BN_new (), * G = BN_new (), * H = BN_new (); - BN_add (x3, p1.x, p1.y); - BN_add (y3, p2.x, p2.y); - BN_mul (E, x3, y3, ctx); // (x1 + y1)*(x2 + y2) - BN_sub (E, E, A); - BN_sub (E, E, B); // E = (x1 + y1)*(x2 + y2) - A - B - BN_sub (F, D, C); // F = D - C - BN_add (G, D, C); // G = D + C - BN_add (H, B, A); // H = B + A + BN_add (E, p1.x, p1.y); + BN_add (F, p2.x, p2.y); + BN_mul (E, E, F, ctx); // (x1 + y1)*(x2 + y2) + BN_sub (E, E, x3); + BN_sub (E, E, y3); // E = (x1 + y1)*(x2 + y2) - A - B + BN_sub (F, z3, t3); // F = D - C + BN_add (G, z3, t3); // G = D + C + BN_add (H, y3, x3); // H = B + A - BN_free (A); BN_free (B); BN_free (C); BN_free (D); if (!p1.z) BN_free (z1); if (!p1.t) BN_free (t1); if (!p2.z) BN_free (z2); From 1d37745c0c80b174f39f9db5e73cf79ef905f93d Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 21 Nov 2015 17:04:40 -0500 Subject: [PATCH 0556/6300] more separation between api and executable builds --- Makefile | 11 +++++++---- Makefile.mingw | 5 +++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index f65d7a13..2ed1668c 100644 --- a/Makefile +++ b/Makefile @@ -21,16 +21,18 @@ else ifeq ($(shell echo $(UNAME) | $(GREP) -c FreeBSD),1) else ifeq ($(UNAME),Linux) DAEMON_SRC += DaemonLinux.cpp include Makefile.linux -else # win32 - DAEMON_SRC += DaemonWin32.cpp +else # win32 mingw + DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp + include Makefile.mingw endif -all: mk_build_dir $(SHLIB) $(SHLIB_CLIENT) $(ARLIB) $(ARLIB_CLIENT) $(I2PD) +all: mk_build_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) mk_build_dir: mkdir -p obj -api: $(SHLIB) $(ARLIB) +api: mk_build_dir $(SHLIB) $(ARLIB) +api_client: mk_build_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. @@ -82,4 +84,5 @@ dist: .PHONY: deps .PHONY: dist .PHONY: api +.PHONY: api_client .PHONY: mk_build_dir diff --git a/Makefile.mingw b/Makefile.mingw index 9a79b976..33cefe5e 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,6 +1,7 @@ CXX = g++ CXXFLAGS = -O2 -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN -NEEDED_CXXFLAGS = -std=c++11 +NEEDED_CXXFLAGS = -std=c++11 +BOOST_SUFFIX = -mgw48-mt-1_59 INCFLAGS = -I/usr/include/ -I/usr/local/include/ -I/c/dev/openssl/include -I/c/dev/boost/include/boost-1_59 LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/c/dev/openssl -L/c/dev/boost/lib -LDLIBS = -lboost_system-mgw48-mt-1_59 -lboost_date_time-mgw48-mt-1_59 -lboost_filesystem-mgw48-mt-1_59 -lboost_regex-mgw48-mt-1_59 -lboost_program_options-mgw48-mt-1_59 -lssl -lcrypto -lz -lpthread -lwsock32 -lws2_32 -lgdi32 -liphlpapi +LDLIBS = -lboost_system$(BOOST_SUFFIX) -lboost_date_time$(BOOST_SUFFIX) -lboost_filesystem$(BOOST_SUFFIX) -lboost_regex$(BOOST_SUFFIX) -lboost_program_options$(BOOST_SUFFIX) -lssl -lcrypto -lz -lpthread -lwsock32 -lws2_32 -lgdi32 -liphlpapi From 0d8487103712ef1085418a35279b017cea7bb0a9 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 21 Nov 2015 17:26:12 -0500 Subject: [PATCH 0557/6300] backport of 'make http server http/1.1 compliant' --- HTTPServer.cpp | 51 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 575b06b8..09e298fb 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -1,3 +1,5 @@ +#include +#include #include #include #include @@ -488,21 +490,26 @@ namespace util std::vector buffers; if (headers.size () > 0) { + buffers.push_back(boost::asio::buffer("HTTP/1.1 ", 9)); + buffers.push_back(boost::asio::buffer(boost::lexical_cast(status), 3)); + buffers.push_back(boost::asio::buffer(" ", 1)); + std::string status_string; switch (status) { - case 105: buffers.push_back(boost::asio::buffer("HTTP/1.1 105 Name Not Resolved\r\n")); break; - case 200: buffers.push_back(boost::asio::buffer("HTTP/1.1 200 OK\r\n")); break; - case 400: buffers.push_back(boost::asio::buffer("HTTP/1.1 400 Bad Request\r\n")); break; - case 404: buffers.push_back(boost::asio::buffer("HTTP/1.1 404 Not Found\r\n")); break; - case 408: buffers.push_back(boost::asio::buffer("HTTP/1.1 408 Request Timeout\r\n")); break; - case 500: buffers.push_back(boost::asio::buffer("HTTP/1.1 500 Internal Server Error\r\n")); break; - case 502: buffers.push_back(boost::asio::buffer("HTTP/1.1 502 Bad Gateway\r\n")); break; - case 503: buffers.push_back(boost::asio::buffer("HTTP/1.1 503 Not Implemented\r\n")); break; - case 504: buffers.push_back(boost::asio::buffer("HTTP/1.1 504 Gateway Timeout\r\n")); break; - default: - buffers.push_back(boost::asio::buffer("HTTP/1.1 200 OK\r\n")); + case 105: status_string = "Name Not Resolved"; break; + case 200: status_string = "OK"; break; + case 400: status_string = "Bad Request"; break; + case 404: status_string = "Not Found"; break; + case 408: status_string = "Request Timeout"; break; + case 500: status_string = "Internal Server Error"; break; + case 502: status_string = "Bad Gateway"; break; + case 503: status_string = "Not Implemented"; break; + case 504: status_string = "Gateway Timeout"; break; + default: status_string = "WTF"; } - + buffers.push_back(boost::asio::buffer(status_string, status_string.size())); + buffers.push_back(boost::asio::buffer(misc_strings::crlf)); + for (std::size_t i = 0; i < headers.size(); ++i) { header& h = headers[i]; @@ -1040,12 +1047,20 @@ namespace util void HTTPConnection::SendReply (const std::string& content, int status) { m_Reply.content = content; - m_Reply.headers.resize(2); - m_Reply.headers[0].name = "Content-Length"; - m_Reply.headers[0].value = boost::lexical_cast(m_Reply.content.size()); - m_Reply.headers[1].name = "Content-Type"; - m_Reply.headers[1].value = "text/html"; - + m_Reply.headers.resize(3); + // we need the date header to be complaint with http 1.1 + std::time_t time_now = std::time(nullptr); + char time_buff[128]; + if (std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now))) + { + m_Reply.headers[0].name = "Date"; + m_Reply.headers[0].value = std::string(time_buff); + m_Reply.headers[1].name = "Content-Length"; + m_Reply.headers[1].value = boost::lexical_cast(m_Reply.content.size()); + m_Reply.headers[2].name = "Content-Type"; + m_Reply.headers[2].value = "text/html"; + } + boost::asio::async_write (*m_Socket, m_Reply.to_buffers(status), std::bind (&HTTPConnection::HandleWriteReply, shared_from_this (), std::placeholders::_1)); } From e5cb70972e7974999a8dabe4f861209d2f39e5fc Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Nov 2015 10:58:57 -0500 Subject: [PATCH 0558/6300] moved status_string to reply structure --- HTTPServer.cpp | 27 +++++++++++++-------------- HTTPServer.h | 3 +-- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 09e298fb..07457549 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -490,22 +490,21 @@ namespace util std::vector buffers; if (headers.size () > 0) { - buffers.push_back(boost::asio::buffer("HTTP/1.1 ", 9)); - buffers.push_back(boost::asio::buffer(boost::lexical_cast(status), 3)); - buffers.push_back(boost::asio::buffer(" ", 1)); - std::string status_string; + status_string = "HTTP/1.1 "; + status_string += std::to_string (status); + status_string += " "; switch (status) { - case 105: status_string = "Name Not Resolved"; break; - case 200: status_string = "OK"; break; - case 400: status_string = "Bad Request"; break; - case 404: status_string = "Not Found"; break; - case 408: status_string = "Request Timeout"; break; - case 500: status_string = "Internal Server Error"; break; - case 502: status_string = "Bad Gateway"; break; - case 503: status_string = "Not Implemented"; break; - case 504: status_string = "Gateway Timeout"; break; - default: status_string = "WTF"; + case 105: status_string += "Name Not Resolved"; break; + case 200: status_string += "OK"; break; + case 400: status_string += "Bad Request"; break; + case 404: status_string += "Not Found"; break; + case 408: status_string += "Request Timeout"; break; + case 500: status_string += "Internal Server Error"; break; + case 502: status_string += "Bad Gateway"; break; + case 503: status_string += "Not Implemented"; break; + case 504: status_string += "Gateway Timeout"; break; + default: status_string += "WTF"; } buffers.push_back(boost::asio::buffer(status_string, status_string.size())); buffers.push_back(boost::asio::buffer(misc_strings::crlf)); diff --git a/HTTPServer.h b/HTTPServer.h index 977938fb..e2ca3774 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -39,8 +39,7 @@ namespace util struct reply { std::vector
headers; - std::string content; - + std::string status_string, content; std::vector to_buffers (int status); }; From 53e9335bb0a4f3b2a10d9e6f87d2b027d0e26199 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Nov 2015 16:47:55 -0500 Subject: [PATCH 0559/6300] Update README.md --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 631bd55d..7f1f5b35 100644 --- a/README.md +++ b/README.md @@ -61,12 +61,8 @@ Then, run it: $ ./i2p The client should now reseed by itself. - -To visit an I2P page, you need to find the b32 address of your destination. -After that, go to the webconsole and add it behind the url. (Remove http:// from the address) - -This should resulting in for example: -http://localhost:7070/4oes3rlgrpbkmzv4lqcfili23h3cvpwslqcfjlk6vvguxyggspwa.b32.i2p +To visit an eepsite use HTTP proxy port 4446. +For tunnels follow [intructions](https://github.com/PurpleI2P/i2pd/wiki/tunnels.cfg) Cmdline options From 50a7cd19b4d52280c006975df27caf772eb48619 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Nov 2015 16:48:38 -0500 Subject: [PATCH 0560/6300] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f1f5b35..70577cea 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ $ ./i2p The client should now reseed by itself. To visit an eepsite use HTTP proxy port 4446. -For tunnels follow [intructions](https://github.com/PurpleI2P/i2pd/wiki/tunnels.cfg) +For tunnels follow [instructions](https://github.com/PurpleI2P/i2pd/wiki/tunnels.cfg) Cmdline options From 7943b1389181d9047e00ab946350e89107cace50 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Nov 2015 17:01:37 -0500 Subject: [PATCH 0561/6300] use shared_ptr for sockets --- HTTPServer.cpp | 22 ++++++++++------------ HTTPServer.h | 13 ++++++------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 07457549..1429c686 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -1066,10 +1066,8 @@ namespace util HTTPServer::HTTPServer (int port): m_Thread (nullptr), m_Work (m_Service), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4 (), port)), - m_NewSocket (nullptr) + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4 (), port)) { - } HTTPServer::~HTTPServer () @@ -1079,7 +1077,7 @@ namespace util void HTTPServer::Start () { - m_Thread = new std::thread (std::bind (&HTTPServer::Run, this)); + m_Thread = std::unique_ptr(new std::thread (std::bind (&HTTPServer::Run, this))); m_Acceptor.listen (); Accept (); } @@ -1091,7 +1089,6 @@ namespace util if (m_Thread) { m_Thread->join (); - delete m_Thread; m_Thread = nullptr; } } @@ -1103,23 +1100,24 @@ namespace util void HTTPServer::Accept () { - m_NewSocket = new boost::asio::ip::tcp::socket (m_Service); - m_Acceptor.async_accept (*m_NewSocket, boost::bind (&HTTPServer::HandleAccept, this, - boost::asio::placeholders::error)); + auto newSocket = std::make_shared (m_Service); + m_Acceptor.async_accept (*newSocket, boost::bind (&HTTPServer::HandleAccept, this, + boost::asio::placeholders::error, newSocket)); } - void HTTPServer::HandleAccept(const boost::system::error_code& ecode) + void HTTPServer::HandleAccept(const boost::system::error_code& ecode, + std::shared_ptr newSocket) { if (!ecode) { - CreateConnection(m_NewSocket); + CreateConnection(newSocket); Accept (); } } - void HTTPServer::CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket) + void HTTPServer::CreateConnection(std::shared_ptr newSocket) { - auto conn = std::make_shared (m_NewSocket); + auto conn = std::make_shared (newSocket); conn->Receive (); } } diff --git a/HTTPServer.h b/HTTPServer.h index e2ca3774..98383644 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -45,10 +45,9 @@ namespace util public: - HTTPConnection (boost::asio::ip::tcp::socket * socket): + HTTPConnection (std::shared_ptr socket): m_Socket (socket), m_Timer (socket->get_io_service ()), m_Stream (nullptr), m_BufferLen (0) {}; - ~HTTPConnection() { delete m_Socket; } void Receive (); private: @@ -80,7 +79,7 @@ namespace util protected: - boost::asio::ip::tcp::socket * m_Socket; + std::shared_ptr m_Socket; boost::asio::deadline_timer m_Timer; std::shared_ptr m_Stream; char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1], m_StreamBuffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; @@ -117,18 +116,18 @@ namespace util void Run (); void Accept (); - void HandleAccept(const boost::system::error_code& ecode); + void HandleAccept(const boost::system::error_code& ecode, + std::shared_ptr newSocket); private: - std::thread * m_Thread; + std::unique_ptr m_Thread; boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; boost::asio::ip::tcp::acceptor m_Acceptor; - boost::asio::ip::tcp::socket * m_NewSocket; protected: - virtual void CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket); + virtual void CreateConnection(std::shared_ptr newSocket); }; } } From 387ce4b6fa37e963f63061ccd74d8865d0e4bb00 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Nov 2015 21:02:02 -0500 Subject: [PATCH 0562/6300] fixed access to eepsites from webconsole --- HTTPServer.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 1429c686..1b5d1939 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -526,14 +526,9 @@ namespace util void HTTPConnection::Terminate () { if (!m_Stream) return; - m_Socket->close (); m_Stream->Close (); - - m_Socket->get_io_service ().post ([=](void) - { - m_Stream.reset (); - m_Stream = nullptr; - }); + m_Stream = nullptr; + m_Socket->close (); } void HTTPConnection::Receive () @@ -964,7 +959,7 @@ namespace util void HTTPConnection::HandleDestinationRequest (const std::string& address, const std::string& uri) { - std::string request = "GET " + uri + " HTTP/1.1\r\nHost:" + address + "\r\n"; + std::string request = "GET " + uri + " HTTP/1.1\r\nHost:" + address + "\r\n\r\n"; LogPrint("HTTP Client Request: ", request); SendToAddress (address, 80, request.c_str (), request.size ()); } From 90d6c5c5bb0541d50fa3c1faff77c76d0ed6c072 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Nov 2015 09:26:32 -0500 Subject: [PATCH 0563/6300] fixed race condition --- Signature.cpp | 39 ++++++++++++++++++++++++--------------- Signature.h | 4 ---- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index 6a92eff6..13151508 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -396,27 +396,32 @@ namespace crypto } - EDDSA25519Verifier::EDDSA25519Verifier (const uint8_t * signingKey): - m_Ctx (BN_CTX_new ()), - m_PublicKey (GetEd25519 ()->DecodePublicKey (signingKey, m_Ctx)) + EDDSA25519Verifier::EDDSA25519Verifier (const uint8_t * signingKey) { memcpy (m_PublicKeyEncoded, signingKey, EDDSA25519_PUBLIC_KEY_LENGTH); + BN_CTX * ctx = BN_CTX_new (); + m_PublicKey = GetEd25519 ()->DecodePublicKey (m_PublicKeyEncoded, ctx); + BN_CTX_free (ctx); } bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { - SHA512_CTX ctx; - SHA512_Init (&ctx); - SHA512_Update (&ctx, signature, EDDSA25519_SIGNATURE_LENGTH/2); // R - SHA512_Update (&ctx, m_PublicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key - SHA512_Update (&ctx, buf, len); // data uint8_t digest[64]; - SHA512_Final (digest, &ctx); - return GetEd25519 ()->Verify (m_PublicKey, digest, signature, m_Ctx); + { + SHA512_CTX ctx; + SHA512_Init (&ctx); + SHA512_Update (&ctx, signature, EDDSA25519_SIGNATURE_LENGTH/2); // R + SHA512_Update (&ctx, m_PublicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key + SHA512_Update (&ctx, buf, len); // data + SHA512_Final (digest, &ctx); + } + BN_CTX * ctx = BN_CTX_new (); + bool passed = GetEd25519 ()->Verify (m_PublicKey, digest, signature, ctx); + BN_CTX_free (ctx); + return passed; } - EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey): - m_Ctx (BN_CTX_new ()) + EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey) { // expand key SHA512 (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH, m_ExpandedPrivateKey); @@ -424,13 +429,17 @@ namespace crypto m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0x1F; // drop first 3 bits m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] |= 0x40; // set second bit // generate and encode public key - auto publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, m_Ctx); - GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, m_Ctx); + BN_CTX * ctx = BN_CTX_new (); + auto publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, ctx); + GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx); + BN_CTX_free (ctx); } void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const { - GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature, m_Ctx); + BN_CTX * ctx = BN_CTX_new (); + GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature, ctx); + BN_CTX_free (ctx); } } } diff --git a/Signature.h b/Signature.h index 9f60d406..f46193aa 100644 --- a/Signature.h +++ b/Signature.h @@ -410,7 +410,6 @@ namespace crypto public: EDDSA25519Verifier (const uint8_t * signingKey); - ~EDDSA25519Verifier () { BN_CTX_free (m_Ctx); }; bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; }; @@ -418,7 +417,6 @@ namespace crypto private: - BN_CTX * m_Ctx; EDDSAPoint m_PublicKey; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; @@ -428,13 +426,11 @@ namespace crypto public: EDDSA25519Signer (const uint8_t * signingPrivateKey); - ~EDDSA25519Signer () { BN_CTX_free (m_Ctx); }; void Sign (const uint8_t * buf, int len, uint8_t * signature) const; const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; private: - BN_CTX * m_Ctx; uint8_t m_ExpandedPrivateKey[64]; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; From d015538bb4c3124c3c1ecaebf423d88f771e14bf Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Nov 2015 11:56:00 -0500 Subject: [PATCH 0564/6300] create certificate for https --- I2PControl.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++++-- I2PControl.h | 14 ++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 9a7543ca..19db608c 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -1,8 +1,9 @@ // There is bug in boost 1.49 with gcc 4.7 coming with Debian Wheezy #define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) - -#include "I2PControl.h" +#include #include +#include +#include #include #include #include @@ -17,6 +18,7 @@ #include "Timestamp.h" #include "Transports.h" #include "version.h" +#include "I2PControl.h" namespace i2p { @@ -27,6 +29,13 @@ namespace client m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)), m_ShutdownTimer (m_Service) { + auto path = GetPath (); + if (!boost::filesystem::exists (path)) + { + if (!boost::filesystem::create_directory (path)) + LogPrint (eLogError, "Failed to create i2pcontrol directory"); + } + m_MethodHandlers[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlService::AuthenticateHandler; m_MethodHandlers[I2P_CONTROL_METHOD_ECHO] = &I2PControlService::EchoHandler; m_MethodHandlers[I2P_CONTROL_METHOD_I2PCONTROL] = &I2PControlService::I2PControlHandler; @@ -412,5 +421,53 @@ namespace client } } + // certificate + void I2PControlService::CreateCertificate () + { + EVP_PKEY * pkey = EVP_PKEY_new (); + RSA * rsa = RSA_generate_key (4096, RSA_F4, NULL, NULL); + if (rsa) + { + EVP_PKEY_assign_RSA (pkey, rsa); + X509 * x509 = X509_new (); + ASN1_INTEGER_set (X509_get_serialNumber (x509), 1); + X509_gmtime_adj (X509_get_notBefore (x509), 0); + X509_gmtime_adj (X509_get_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration + X509_set_pubkey (x509, pkey); // public key + X509_NAME * name = X509_get_subject_name (x509); + X509_NAME_add_entry_by_txt (name, "C", MBSTRING_ASC, (unsigned char *)"RU", -1, -1, 0); // country (Russia by default) + X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization + X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name + X509_set_issuer_name (x509, name); // set issuer to ourselves + X509_sign (x509, pkey, EVP_sha1 ()); // sign + // save key and certificate + // keys + auto filename = GetPath () / I2P_CONTROL_KEY_FILE; + FILE * f= fopen (filename.string ().c_str (), "wb"); + if (f) + { + PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); + fclose (f); + } + else + LogPrint (eLogError, "Can't open file ", filename); + // certificate + filename = GetPath () / I2P_CONTROL_CERT_FILE; + f= fopen (filename.string ().c_str (), "wb"); + if (f) + { + PEM_write_X509 (f, x509); + fclose (f); + } + else + LogPrint (eLogError, "Can't open file ", filename); + + X509_free (x509); + RSA_free (rsa); + } + LogPrint (eLogError, "Couldn't create RSA key for certificate"); + EVP_PKEY_free (pkey); + } + } } diff --git a/I2PControl.h b/I2PControl.h index 0c3e36a1..97a1f9f5 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include "util.h" namespace i2p { @@ -19,6 +21,9 @@ namespace client const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024; typedef std::array I2PControlBuffer; + const char I2P_CONTROL_PATH[] = "ipcontrol"; + const char I2P_CONTROL_KEY_FILE[] = "key.pem"; + const char I2P_CONTROL_CERT_FILE[] = "cert.pem"; const char I2P_CONTROL_DEFAULT_PASSWORD[] = "itoopie"; const char I2P_CONTROL_PROPERTY_ID[] = "id"; @@ -62,6 +67,11 @@ namespace client const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful"; const char I2P_CONTROL_ROUTER_MANAGER_RESEED[] = "Reseed"; + // Certificate + const long I2P_CONTROL_CERTIFICATE_VALIDITY = 365*10; // 10 years + const char I2P_CONTROL_CERTIFICATE_COMMON_NAME[] = "i2pd.i2pcontrol"; + const char I2P_CONTROL_CERTIFICATE_ORGANIZATION[] = "Purple I2P"; + class I2PControlService { public: @@ -85,6 +95,10 @@ namespace client void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); + boost::filesystem::path GetPath () const { return i2p::util::filesystem::GetDefaultDataDir() / I2P_CONTROL_PATH; }; + void CreateCertificate (); + + private: void InsertParam (std::ostringstream& ss, const std::string& name, int value) const; From c9d03a8094da77f32e47ee22c12f1b358b9332eb Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Nov 2015 13:22:02 -0500 Subject: [PATCH 0565/6300] I2PControl through SSL --- I2PControl.cpp | 37 ++++++++++++++++++++++++++----------- I2PControl.h | 13 ++++++++----- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 19db608c..172c0c05 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -27,15 +27,24 @@ namespace client I2PControlService::I2PControlService (int port): m_Password (I2P_CONTROL_DEFAULT_PASSWORD), m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)), + m_SSLContext (m_Service, boost::asio::ssl::context::sslv23), m_ShutdownTimer (m_Service) { + // certificate auto path = GetPath (); if (!boost::filesystem::exists (path)) { if (!boost::filesystem::create_directory (path)) LogPrint (eLogError, "Failed to create i2pcontrol directory"); } + if (!boost::filesystem::exists (path / I2P_CONTROL_KEY_FILE) || + !boost::filesystem::exists (path / I2P_CONTROL_CERT_FILE)) + // create new certificate + CreateCertificate (); + m_SSLContext.use_certificate_chain_file ((path / I2P_CONTROL_CERT_FILE).string ()); + m_SSLContext.use_private_key_file ((path / I2P_CONTROL_KEY_FILE).string (), boost::asio::ssl::context::pem); + // handlers m_MethodHandlers[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlService::AuthenticateHandler; m_MethodHandlers[I2P_CONTROL_METHOD_ECHO] = &I2PControlService::EchoHandler; m_MethodHandlers[I2P_CONTROL_METHOD_I2PCONTROL] = &I2PControlService::I2PControlHandler; @@ -108,27 +117,34 @@ namespace client void I2PControlService::Accept () { - auto newSocket = std::make_shared (m_Service); - m_Acceptor.async_accept (*newSocket, std::bind (&I2PControlService::HandleAccept, this, + auto newSocket = std::make_shared (m_Service, m_SSLContext); + m_Acceptor.async_accept (newSocket->lowest_layer(), std::bind (&I2PControlService::HandleAccept, this, std::placeholders::_1, newSocket)); } - void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) + void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) { if (ecode != boost::asio::error::operation_aborted) Accept (); if (!ecode) { - LogPrint (eLogInfo, "New I2PControl request from ", socket->remote_endpoint ()); - std::this_thread::sleep_for (std::chrono::milliseconds(5)); - ReadRequest (socket); + LogPrint (eLogInfo, "New I2PControl request from ", socket->lowest_layer ().remote_endpoint ()); + boost::system::error_code ec; + socket->handshake (boost::asio::ssl::stream_base::client, ec); + if (!ec) + { + std::this_thread::sleep_for (std::chrono::milliseconds(5)); + ReadRequest (socket); + } + else + LogPrint (eLogError, "I2PControl handshake error: ", ecode.message ()); } else LogPrint (eLogError, "I2PControl accept error: ", ecode.message ()); } - void I2PControlService::ReadRequest (std::shared_ptr socket) + void I2PControlService::ReadRequest (std::shared_ptr socket) { auto request = std::make_shared(); socket->async_read_some ( @@ -142,7 +158,7 @@ namespace client } void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode, - size_t bytes_transferred, std::shared_ptr socket, + size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf) { if (ecode) @@ -218,7 +234,7 @@ namespace client ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; } - void I2PControlService::SendResponse (std::shared_ptr socket, + void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) { size_t len = response.str ().length (), offset = 0; @@ -245,11 +261,10 @@ namespace client } void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, - std::shared_ptr socket, std::shared_ptr buf) + std::shared_ptr socket, std::shared_ptr buf) { if (ecode) LogPrint (eLogError, "I2PControl write error: ", ecode.message ()); - socket->close (); } // handlers diff --git a/I2PControl.h b/I2PControl.h index 97a1f9f5..94b03b60 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include "util.h" @@ -74,6 +75,7 @@ namespace client class I2PControlService { + typedef boost::asio::ssl::stream ssl_socket; public: I2PControlService (int port); @@ -86,14 +88,14 @@ namespace client void Run (); void Accept (); - void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); - void ReadRequest (std::shared_ptr socket); + void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); + void ReadRequest (std::shared_ptr socket); void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, - std::shared_ptr socket, std::shared_ptr buf); - void SendResponse (std::shared_ptr socket, + std::shared_ptr socket, std::shared_ptr buf); + void SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml); void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, - std::shared_ptr socket, std::shared_ptr buf); + std::shared_ptr socket, std::shared_ptr buf); boost::filesystem::path GetPath () const { return i2p::util::filesystem::GetDefaultDataDir() / I2P_CONTROL_PATH; }; void CreateCertificate (); @@ -147,6 +149,7 @@ namespace client boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; + boost::asio::ssl::context m_SSLContext; boost::asio::deadline_timer m_ShutdownTimer; std::set m_Tokens; From 942b699bb9380b9d425c085992200a277b7ff78d Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Nov 2015 14:48:56 -0500 Subject: [PATCH 0566/6300] fixed few SSL errors --- I2PControl.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 172c0c05..a0b9081b 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -39,9 +39,13 @@ namespace client } if (!boost::filesystem::exists (path / I2P_CONTROL_KEY_FILE) || !boost::filesystem::exists (path / I2P_CONTROL_CERT_FILE)) + { // create new certificate CreateCertificate (); - m_SSLContext.use_certificate_chain_file ((path / I2P_CONTROL_CERT_FILE).string ()); + LogPrint (eLogInfo, "I2PControl certificates created"); + } + m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); + m_SSLContext.use_certificate_file ((path / I2P_CONTROL_CERT_FILE).string (), boost::asio::ssl::context::pem); m_SSLContext.use_private_key_file ((path / I2P_CONTROL_KEY_FILE).string (), boost::asio::ssl::context::pem); // handlers @@ -131,14 +135,14 @@ namespace client { LogPrint (eLogInfo, "New I2PControl request from ", socket->lowest_layer ().remote_endpoint ()); boost::system::error_code ec; - socket->handshake (boost::asio::ssl::stream_base::client, ec); + socket->handshake (boost::asio::ssl::stream_base::server, ec); if (!ec) { std::this_thread::sleep_for (std::chrono::milliseconds(5)); ReadRequest (socket); } else - LogPrint (eLogError, "I2PControl handshake error: ", ecode.message ()); + LogPrint (eLogError, "I2PControl handshake error: ", ec.message ()); } else LogPrint (eLogError, "I2PControl accept error: ", ecode.message ()); @@ -440,7 +444,8 @@ namespace client void I2PControlService::CreateCertificate () { EVP_PKEY * pkey = EVP_PKEY_new (); - RSA * rsa = RSA_generate_key (4096, RSA_F4, NULL, NULL); + RSA * rsa = RSA_new (); + RSA_generate_key_ex (rsa, 4096, i2p::crypto::rsae, NULL); if (rsa) { EVP_PKEY_assign_RSA (pkey, rsa); @@ -478,9 +483,9 @@ namespace client LogPrint (eLogError, "Can't open file ", filename); X509_free (x509); - RSA_free (rsa); } - LogPrint (eLogError, "Couldn't create RSA key for certificate"); + else + LogPrint (eLogError, "Couldn't create RSA key for certificate"); EVP_PKEY_free (pkey); } From 9e2a770a26d8b83f8099bd70f19ff8410086d8c1 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Nov 2015 16:40:06 -0500 Subject: [PATCH 0567/6300] read complete request --- I2PControl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index a0b9081b..a2b5f590 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -151,7 +151,7 @@ namespace client void I2PControlService::ReadRequest (std::shared_ptr socket) { auto request = std::make_shared(); - socket->async_read_some ( + boost::asio::async_read (*socket, #if BOOST_VERSION >= 104900 boost::asio::buffer (*request), #else From 885d57138ae8f1c02a58e4d94f4289adf8c6b366 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Nov 2015 19:47:08 -0500 Subject: [PATCH 0568/6300] read Content-Length from http header --- I2PControl.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index a2b5f590..6954d5d1 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -151,7 +151,7 @@ namespace client void I2PControlService::ReadRequest (std::shared_ptr socket) { auto request = std::make_shared(); - boost::asio::async_read (*socket, + socket->async_read_some ( #if BOOST_VERSION >= 104900 boost::asio::buffer (*request), #else @@ -179,13 +179,25 @@ namespace client if (isHtml) { std::string header; + size_t contentLength = 0; while (!ss.eof () && header != "\r") + { std::getline(ss, header); + auto colon = header.find (':'); + if (colon != std::string::npos && header.substr (0, colon) == "Content-Length") + contentLength = std::stoi (header.substr (colon + 1)); + } if (ss.eof ()) { LogPrint (eLogError, "Malformed I2PControl request. HTTP header expected"); return; // TODO: } + ssize_t rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read + if (rem > 0) + { + bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), rem)); + ss.write (buf->data (), bytes_transferred); + } } #if GCC47_BOOST149 LogPrint (eLogError, "json_read is not supported due bug in boost 1.49 with gcc 4.7"); From 06c4aca4908d5f508c777e207586b1bd9d6bbddb Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 24 Nov 2015 13:09:12 -0500 Subject: [PATCH 0569/6300] always use shared_ptr for I2NPMessage --- Datagram.cpp | 16 +++------ Datagram.h | 6 ++-- Garlic.cpp | 2 +- I2NPProtocol.cpp | 83 +++++++++++++++++++--------------------------- I2NPProtocol.h | 18 +++++----- NTCPSession.cpp | 2 +- NetDb.cpp | 2 +- SSUData.cpp | 4 +-- Streaming.cpp | 2 +- Tunnel.cpp | 4 +-- TunnelEndpoint.cpp | 6 ++-- TunnelGateway.cpp | 2 +- 12 files changed, 63 insertions(+), 84 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index e065e3e7..b944cf11 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -48,15 +48,13 @@ namespace datagram m_Owner->RequestDestination (ident, std::bind (&DatagramDestination::HandleLeaseSetRequestComplete, this, std::placeholders::_1, msg)); } - void DatagramDestination::HandleLeaseSetRequestComplete (std::shared_ptr remote, I2NPMessage * msg) + void DatagramDestination::HandleLeaseSetRequestComplete (std::shared_ptr remote, std::shared_ptr msg) { if (remote) SendMsg (msg, remote); - else - DeleteI2NPMessage (msg); } - void DatagramDestination::SendMsg (I2NPMessage * msg, std::shared_ptr remote) + void DatagramDestination::SendMsg (std::shared_ptr msg, std::shared_ptr remote) { auto outboundTunnel = m_Owner->GetTunnelPool ()->GetNextOutboundTunnel (); auto leases = remote->GetNonExpiredLeases (); @@ -64,7 +62,7 @@ namespace datagram { std::vector msgs; uint32_t i = rand () % leases.size (); - auto garlic = m_Owner->WrapMessage (remote, ToSharedI2NPMessage (msg), true); + auto garlic = m_Owner->WrapMessage (remote, msg, true); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, @@ -79,7 +77,6 @@ namespace datagram LogPrint (eLogWarning, "Failed to send datagram. All leases expired"); else LogPrint (eLogWarning, "Failed to send datagram. No outbound tunnels"); - DeleteI2NPMessage (msg); } } @@ -123,9 +120,9 @@ namespace datagram HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen); } - I2NPMessage * DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) + std::shared_ptr DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) { - I2NPMessage * msg = NewI2NPMessage (); + auto msg = NewI2NPMessage (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for length size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); @@ -139,10 +136,7 @@ namespace datagram msg->FillI2NPMessageHeader (eI2NPData); } else - { - DeleteI2NPMessage (msg); msg = nullptr; - } return msg; } } diff --git a/Datagram.h b/Datagram.h index 7f6df921..c593fad2 100644 --- a/Datagram.h +++ b/Datagram.h @@ -39,10 +39,10 @@ namespace datagram private: - void HandleLeaseSetRequestComplete (std::shared_ptr leaseSet, I2NPMessage * msg); + void HandleLeaseSetRequestComplete (std::shared_ptr leaseSet, std::shared_ptr msg); - I2NPMessage * CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); - void SendMsg (I2NPMessage * msg, std::shared_ptr remote); + std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); + void SendMsg (std::shared_ptr msg, std::shared_ptr remote); void HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); private: diff --git a/Garlic.cpp b/Garlic.cpp index f8acbe39..c94139e0 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -112,7 +112,7 @@ namespace garlic std::shared_ptr GarlicRoutingSession::WrapSingleMessage (std::shared_ptr msg) { - auto m = ToSharedI2NPMessage(NewI2NPMessage ()); + auto m = NewI2NPMessage (); m->Align (12); // in order to get buf aligned to 16 (12 + 4) size_t len = 0; uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 890ec6af..6f7c98f5 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -18,30 +18,20 @@ using namespace i2p::transport; namespace i2p { - I2NPMessage * NewI2NPMessage () + std::shared_ptr NewI2NPMessage () { - return new I2NPMessageBuffer(); + return std::make_shared >(); } - I2NPMessage * NewI2NPShortMessage () + std::shared_ptr NewI2NPShortMessage () { - return new I2NPMessageBuffer(); + return std::make_shared >(); } - I2NPMessage * NewI2NPMessage (size_t len) + std::shared_ptr NewI2NPMessage (size_t len) { return (len < I2NP_MAX_SHORT_MESSAGE_SIZE/2) ? NewI2NPShortMessage () : NewI2NPMessage (); } - - void DeleteI2NPMessage (I2NPMessage * msg) - { - delete msg; - } - - std::shared_ptr ToSharedI2NPMessage (I2NPMessage * msg) - { - return std::shared_ptr(msg, DeleteI2NPMessage); - } void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID) { @@ -61,9 +51,9 @@ namespace i2p SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); } - I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID) + std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID) { - I2NPMessage * msg = NewI2NPMessage (len); + auto msg = NewI2NPMessage (len); if (msg->len + len < msg->maxLen) { memcpy (msg->GetPayload (), buf, len); @@ -77,7 +67,7 @@ namespace i2p std::shared_ptr CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from) { - I2NPMessage * msg = NewI2NPMessage (); + auto msg = NewI2NPMessage (); if (msg->offset + len < msg->maxLen) { memcpy (msg->GetBuffer (), buf, len); @@ -86,12 +76,12 @@ namespace i2p } else LogPrint (eLogError, "I2NP message length ", len, " exceeds max length"); - return ToSharedI2NPMessage(msg); + return msg; } std::shared_ptr CreateDeliveryStatusMsg (uint32_t msgID) { - I2NPMessage * m = NewI2NPShortMessage (); + auto m = NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); if (msgID) { @@ -106,13 +96,13 @@ namespace i2p } m->len += DELIVERY_STATUS_SIZE; m->FillI2NPMessageHeader (eI2NPDeliveryStatus); - return ToSharedI2NPMessage (m); + return m; } std::shared_ptr CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, uint32_t replyTunnelID, bool exploratory, std::set * excludedPeers) { - auto m = ToSharedI2NPMessage (excludedPeers ? NewI2NPMessage () : NewI2NPShortMessage ()); + auto m = excludedPeers ? NewI2NPMessage () : NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); memcpy (buf, key, 32); // key buf += 32; @@ -159,7 +149,7 @@ namespace i2p const i2p::tunnel::InboundTunnel * replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag) { int cnt = excludedFloodfills.size (); - auto m = ToSharedI2NPMessage (cnt > 0 ? NewI2NPMessage () : NewI2NPShortMessage ()); + auto m = cnt > 0 ? NewI2NPMessage () : NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); memcpy (buf, dest, 32); // key buf += 32; @@ -194,7 +184,7 @@ namespace i2p std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers) { - auto m = ToSharedI2NPMessage (NewI2NPShortMessage ()); + auto m = NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); size_t len = 0; memcpy (buf, ident, 32); @@ -218,7 +208,7 @@ namespace i2p if (!router) // we send own RouterInfo router = context.GetSharedRouterInfo (); - auto m = ToSharedI2NPMessage (NewI2NPShortMessage ()); + auto m = NewI2NPShortMessage (); uint8_t * payload = m->GetPayload (); memcpy (payload + DATABASE_STORE_KEY_OFFSET, router->GetIdentHash (), 32); @@ -253,7 +243,7 @@ namespace i2p std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken) { if (!leaseSet) return nullptr; - auto m = ToSharedI2NPMessage (NewI2NPShortMessage ()); + auto m = NewI2NPShortMessage (); uint8_t * payload = m->GetPayload (); memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32); payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet @@ -358,14 +348,14 @@ namespace i2p { // so we send it to reply tunnel transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - ToSharedI2NPMessage (CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), eI2NPVariableTunnelBuildReply, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)))); + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } else transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - ToSharedI2NPMessage (CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)))); + CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } } } @@ -379,14 +369,14 @@ namespace i2p { // so we send it to reply tunnel transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - ToSharedI2NPMessage (CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), eI2NPTunnelBuildReply, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)))); + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } else transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - ToSharedI2NPMessage (CreateI2NPMessage (eI2NPTunnelBuild, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)))); + CreateI2NPMessage (eI2NPTunnelBuild, buf, len, + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } } @@ -414,18 +404,18 @@ namespace i2p } - I2NPMessage * CreateTunnelDataMsg (const uint8_t * buf) + std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf) { - I2NPMessage * msg = NewI2NPShortMessage (); + auto msg = NewI2NPShortMessage (); memcpy (msg->GetPayload (), buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE); msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; msg->FillI2NPMessageHeader (eI2NPTunnelData); return msg; } - I2NPMessage * CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload) + std::shared_ptr CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload) { - I2NPMessage * msg = NewI2NPShortMessage (); + auto msg = NewI2NPShortMessage (); memcpy (msg->GetPayload () + 4, payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4); htobe32buf (msg->GetPayload (), tunnelID); msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; @@ -435,14 +425,14 @@ namespace i2p std::shared_ptr CreateEmptyTunnelDataMsg () { - I2NPMessage * msg = NewI2NPShortMessage (); + auto msg = NewI2NPShortMessage (); msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; - return ToSharedI2NPMessage (msg); + return msg; } - I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len) + std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len) { - I2NPMessage * msg = NewI2NPMessage (len); + auto msg = NewI2NPMessage (len); uint8_t * payload = msg->GetPayload (); htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID); htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len); @@ -467,16 +457,13 @@ namespace i2p return msg; } else - { - I2NPMessage * msg1 = CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ()); - return ToSharedI2NPMessage (msg1); - } + return CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ()); } - I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, + std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID) { - I2NPMessage * msg = NewI2NPMessage (len); + auto msg = NewI2NPMessage (len); size_t gatewayMsgOffset = I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE; msg->offset += gatewayMsgOffset; msg->len += gatewayMsgOffset; diff --git a/I2NPProtocol.h b/I2NPProtocol.h index a4840217..a3f75d7a 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -196,13 +196,11 @@ namespace tunnel uint8_t m_Buffer[sz + 16]; }; - I2NPMessage * NewI2NPMessage (); - I2NPMessage * NewI2NPShortMessage (); - I2NPMessage * NewI2NPMessage (size_t len); - void DeleteI2NPMessage (I2NPMessage * msg); - std::shared_ptr ToSharedI2NPMessage (I2NPMessage * msg); + std::shared_ptr NewI2NPMessage (); + std::shared_ptr NewI2NPShortMessage (); + std::shared_ptr NewI2NPMessage (size_t len); - I2NPMessage * CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID = 0); + std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID = 0); std::shared_ptr CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from = nullptr); std::shared_ptr CreateDeliveryStatusMsg (uint32_t msgID); @@ -221,12 +219,12 @@ namespace tunnel void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); void HandleTunnelBuildMsg (uint8_t * buf, size_t len); - I2NPMessage * CreateTunnelDataMsg (const uint8_t * buf); - I2NPMessage * CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload); + std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf); + std::shared_ptr CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload); std::shared_ptr CreateEmptyTunnelDataMsg (); - I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len); - I2NPMessage * CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, + std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len); + std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0); std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr msg); diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 70fb8d25..9de41727 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -555,7 +555,7 @@ namespace transport return false; } auto msg = dataSize <= I2NP_MAX_SHORT_MESSAGE_SIZE - 2 ? NewI2NPShortMessage () : NewI2NPMessage (); - m_NextMessage = ToSharedI2NPMessage (msg); + m_NextMessage = msg; memcpy (m_NextMessage->buf, buf, 16); m_NextMessageOffset = 16; m_NextMessage->offset = 2; // size field diff --git a/NetDb.cpp b/NetDb.cpp index 5517ffdf..bf9a64f0 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -478,7 +478,7 @@ namespace data if (context.IsFloodfill ()) { // flood it - auto floodMsg = ToSharedI2NPMessage (NewI2NPShortMessage ()); + auto floodMsg = NewI2NPShortMessage (); uint8_t * payload = floodMsg->GetPayload (); memcpy (payload, buf, 33); // key + type htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); // zero reply token diff --git a/SSUData.cpp b/SSUData.cpp index 77c2470f..1106cf89 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -15,7 +15,7 @@ namespace transport if (msg->len + fragmentSize > msg->maxLen) { LogPrint (eLogInfo, "SSU I2NP message size ", msg->maxLen, " is not enough"); - auto newMsg = ToSharedI2NPMessage(NewI2NPMessage ()); + auto newMsg = NewI2NPMessage (); *newMsg = *msg; msg = newMsg; } @@ -171,7 +171,7 @@ namespace transport if (it == m_IncompleteMessages.end ()) { // create new message - auto msg = ToSharedI2NPMessage (NewI2NPShortMessage ()); + auto msg = NewI2NPShortMessage (); msg->len -= I2NP_SHORT_HEADER_SIZE; it = m_IncompleteMessages.insert (std::make_pair (msgID, std::unique_ptr(new IncompleteMessage (msg)))).first; diff --git a/Streaming.cpp b/Streaming.cpp index bb84a955..0b4e514d 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -764,7 +764,7 @@ namespace stream std::shared_ptr Stream::CreateDataMessage (const uint8_t * payload, size_t len) { - auto msg = ToSharedI2NPMessage (NewI2NPShortMessage ()); + auto msg = NewI2NPShortMessage (); if (len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE) m_LocalDestination.m_Deflator.SetCompressionLevel (Z_NO_COMPRESSION); else diff --git a/Tunnel.cpp b/Tunnel.cpp index 375fe6b0..b899fdf7 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -85,9 +85,9 @@ namespace tunnel // send message if (outboundTunnel) - outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, ToSharedI2NPMessage (msg)); + outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); else - i2p::transport::transports.SendMessage (GetNextIdentHash (), ToSharedI2NPMessage (msg)); + i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); } bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index fe161652..6f765a86 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -93,7 +93,7 @@ namespace tunnel if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) { // this is not last message. we have to copy it - m.data = ToSharedI2NPMessage (NewI2NPShortMessage ()); + m.data = NewI2NPShortMessage (); m.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header m.data->len += TUNNEL_GATEWAY_HEADER_SIZE; *(m.data) = *msg; @@ -148,7 +148,7 @@ namespace tunnel if (msg.data->len + size > msg.data->maxLen) { LogPrint (eLogInfo, "Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); - auto newMsg = ToSharedI2NPMessage (NewI2NPMessage ()); + auto newMsg = NewI2NPMessage (); *newMsg = *(msg.data); msg.data = newMsg; } @@ -204,7 +204,7 @@ namespace tunnel if (msg.data->len + size > msg.data->maxLen) { LogPrint (eLogInfo, "Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); - auto newMsg = ToSharedI2NPMessage (NewI2NPMessage ()); + auto newMsg = NewI2NPMessage (); *newMsg = *(msg.data); msg.data = newMsg; } diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index adc1bef6..4f517eb8 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -143,7 +143,7 @@ namespace tunnel void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage () { - m_CurrentTunnelDataMsg = ToSharedI2NPMessage (NewI2NPShortMessage ()); + m_CurrentTunnelDataMsg = NewI2NPShortMessage (); m_CurrentTunnelDataMsg->Align (12); // we reserve space for padding m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE; From d01d0332095e2a6af7c0dffc869f34a405a5e8bb Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 Nov 2015 11:51:35 -0500 Subject: [PATCH 0570/6300] eliminate session creation collision --- NTCPSession.cpp | 29 +++++++++++++++-------------- NTCPSession.h | 5 ++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 9de41727..90733d73 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -793,27 +793,28 @@ namespace transport } } - void NTCPServer::AddNTCPSession (std::shared_ptr session) + bool NTCPServer::AddNTCPSession (std::shared_ptr session) { - if (session && session->GetRemoteIdentity ()) + if (!session || !session->GetRemoteIdentity ()) return false; + auto& ident = session->GetRemoteIdentity ()->GetIdentHash (); + auto it = m_NTCPSessions.find (ident); + if (it != m_NTCPSessions.end ()) { - std::unique_lock l(m_NTCPSessionsMutex); - m_NTCPSessions[session->GetRemoteIdentity ()->GetIdentHash ()] = session; + LogPrint (eLogWarning, "NTCP session to ", ident.ToBase64 (), " already exists"); + return false; } + m_NTCPSessions.insert (std::pair >(ident, session)); + return true; } void NTCPServer::RemoveNTCPSession (std::shared_ptr session) { if (session && session->GetRemoteIdentity ()) - { - std::unique_lock l(m_NTCPSessionsMutex); m_NTCPSessions.erase (session->GetRemoteIdentity ()->GetIdentHash ()); - } } std::shared_ptr NTCPServer::FindNTCPSession (const i2p::data::IdentHash& ident) { - std::unique_lock l(m_NTCPSessionsMutex); auto it = m_NTCPSessions.find (ident); if (it != m_NTCPSessions.end ()) return it->second; @@ -896,12 +897,12 @@ namespace transport void NTCPServer::Connect (const boost::asio::ip::address& address, int port, std::shared_ptr conn) { LogPrint (eLogInfo, "Connecting to ", address ,":", port); - m_Service.post([conn, this]() - { - this->AddNTCPSession (conn); - }); - conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port), - std::bind (&NTCPServer::HandleConnect, this, std::placeholders::_1, conn)); + m_Service.post([=]() + { + if (this->AddNTCPSession (conn)) + conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port), + std::bind (&NTCPServer::HandleConnect, this, std::placeholders::_1, conn)); + }); } void NTCPServer::HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn) diff --git a/NTCPSession.h b/NTCPSession.h index 13e1656f..b8efa532 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -143,7 +143,7 @@ namespace transport void Start (); void Stop (); - void AddNTCPSession (std::shared_ptr session); + bool AddNTCPSession (std::shared_ptr session); void RemoveNTCPSession (std::shared_ptr session); std::shared_ptr FindNTCPSession (const i2p::data::IdentHash& ident); void Connect (const boost::asio::ip::address& address, int port, std::shared_ptr conn); @@ -166,8 +166,7 @@ namespace transport boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; boost::asio::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor; - std::mutex m_NTCPSessionsMutex; - std::map > m_NTCPSessions; + std::map > m_NTCPSessions; // access from m_Thread only std::map m_BanList; // IP -> ban expiration time in seconds public: From e194854c6d7d25e439f2938592c8299815a99203 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 Nov 2015 12:51:35 -0500 Subject: [PATCH 0571/6300] replace GetSession to CreateSession --- SSU.cpp | 6 ++---- SSU.h | 2 +- Transports.cpp | 15 +++++++++------ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index d4b2b05d..41946c0a 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -272,7 +272,7 @@ namespace transport return nullptr; } - std::shared_ptr SSUServer::GetSession (std::shared_ptr router, bool peerTest) + void SSUServer::CreateSession (std::shared_ptr router, bool peerTest) { std::shared_ptr session; if (router) @@ -350,7 +350,6 @@ namespace transport LogPrint (eLogWarning, "Can't connect to unreachable router. No introducers presented"); std::unique_lock l(m_SessionsMutex); m_Sessions.erase (remoteEndpoint); - session.reset (); } } } @@ -358,7 +357,6 @@ namespace transport else LogPrint (eLogWarning, "Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); } - return session; } void SSUServer::DeleteSession (std::shared_ptr session) @@ -491,7 +489,7 @@ namespace transport { auto introducer = i2p::data::netdb.GetRandomIntroducer (); if (introducer) - GetSession (introducer); + CreateSession (introducer); } ScheduleIntroducersUpdateTimer (); } diff --git a/SSU.h b/SSU.h index db9d57eb..bf660523 100644 --- a/SSU.h +++ b/SSU.h @@ -40,7 +40,7 @@ namespace transport ~SSUServer (); void Start (); void Stop (); - std::shared_ptr GetSession (std::shared_ptr router, bool peerTest = false); + void CreateSession (std::shared_ptr router, bool peerTest = false); std::shared_ptr FindSession (std::shared_ptr router) const; std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; std::shared_ptr GetRandomEstablishedSession (std::shared_ptr excluded); diff --git a/Transports.cpp b/Transports.cpp index 635c5e04..1b74093c 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -290,8 +290,11 @@ namespace transport peer.numAttempts++; if (m_SSUServer) { - if (m_SSUServer->GetSession (peer.router)) + if (peer.router->IsSSU (!context.SupportsV6 ())) + { + m_SSUServer->CreateSession (peer.router); return true; + } } } LogPrint (eLogError, "No NTCP and SSU addresses available"); @@ -389,14 +392,14 @@ namespace transport for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomPeerTestRouter (); - if (router && router->IsSSU ()) - m_SSUServer->GetSession (router, true); // peer test + if (router && router->IsSSU (!context.SupportsV6 ())) + m_SSUServer->CreateSession (router, true); // peer test else { // if not peer test capable routers found pick any router = i2p::data::netdb.GetRandomRouter (); if (router && router->IsSSU ()) - m_SSUServer->GetSession (router); // no peer test + m_SSUServer->CreateSession (router); // no peer test } } } @@ -412,14 +415,14 @@ namespace transport for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomPeerTestRouter (); - if (router && router->IsSSU ()) + if (router && router->IsSSU (!context.SupportsV6 ())) { if (!statusChanged) { statusChanged = true; i2p::context.SetStatus (eRouterStatusTesting); // first time only } - m_SSUServer->GetSession (router, true); // peer test + m_SSUServer->CreateSession (router, true); // peer test } } } From ad79ec7b1f514e92781194d663f870c2d1debfdc Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 Nov 2015 13:11:02 -0500 Subject: [PATCH 0572/6300] async handshake --- I2PControl.cpp | 27 ++++++++++++++++++--------- I2PControl.h | 2 ++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 6954d5d1..f050b297 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -134,20 +134,29 @@ namespace client if (!ecode) { LogPrint (eLogInfo, "New I2PControl request from ", socket->lowest_layer ().remote_endpoint ()); - boost::system::error_code ec; - socket->handshake (boost::asio::ssl::stream_base::server, ec); - if (!ec) - { - std::this_thread::sleep_for (std::chrono::milliseconds(5)); - ReadRequest (socket); - } - else - LogPrint (eLogError, "I2PControl handshake error: ", ec.message ()); + Handshake (socket); } else LogPrint (eLogError, "I2PControl accept error: ", ecode.message ()); } + void I2PControlService::Handshake (std::shared_ptr socket) + { + socket->async_handshake(boost::asio::ssl::stream_base::server, + std::bind( &I2PControlService::HandleHandshake, this, std::placeholders::_1, socket)); + } + + void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket) + { + if (!ecode) + { + //std::this_thread::sleep_for (std::chrono::milliseconds(5)); + ReadRequest (socket); + } + else + LogPrint (eLogError, "I2PControl handshake error: ", ecode.message ()); + } + void I2PControlService::ReadRequest (std::shared_ptr socket) { auto request = std::make_shared(); diff --git a/I2PControl.h b/I2PControl.h index 94b03b60..fd6bffbe 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -89,6 +89,8 @@ namespace client void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); + void Handshake (std::shared_ptr socket); + void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket); void ReadRequest (std::shared_ptr socket); void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); From 448073cdd6bfa9d2fefcb5ecf046c8f2c9b6cd6f Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 25 Nov 2015 05:49:13 +0000 Subject: [PATCH 0573/6300] format '%lu' expects argument of type 'long unsigned int', but argument 5 has type 'size_t {aka unsigned int} --- SAM.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAM.h b/SAM.h index c5017b83..4baa01d4 100644 --- a/SAM.h +++ b/SAM.h @@ -41,7 +41,7 @@ namespace client const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n"; const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP"; const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n"; - const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n"; + const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%zu\n"; const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n"; const char SAM_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=INVALID_KEY_NOT_FOUND NAME=%s\n"; const char SAM_PARAM_MIN[] = "MIN"; From 4a0f8689419e8f1b1ac5f8cb953d327ac4be4f60 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 25 Nov 2015 05:50:10 +0000 Subject: [PATCH 0574/6300] fix Dockerfile : drop crypto++, add openssl --- build/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index de486e6f..5c48930b 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,12 +1,11 @@ FROM ubuntu RUN apt-get update && apt-get install -y libboost-dev libboost-filesystem-dev \ - libboost-program-options-dev libboost-regex-dev libcrypto++-dev \ - libboost-date-time-dev git build-essential + libboost-program-options-dev libboost-regex-dev libboost-date-time-dev \ + libssl-dev git build-essential RUN git clone https://github.com/PurpleI2P/i2pd.git WORKDIR /i2pd RUN make CMD ./i2p - From b7a0e23309042cb56e4210c1453cbc9e07d4ec4f Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 25 Nov 2015 05:50:46 +0000 Subject: [PATCH 0575/6300] fix BUILD_NOTES: url, md-format, crypto++ reference --- build/BUILD_NOTES.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/build/BUILD_NOTES.md b/build/BUILD_NOTES.md index be0730dd..51296e43 100644 --- a/build/BUILD_NOTES.md +++ b/build/BUILD_NOTES.md @@ -3,7 +3,7 @@ Build notes Common build/install process: -* git clone https://github.com/PrivacySolutions/i2pd.git +* git clone https://github.com/PurpleI2P/i2pd.git * cd i2pd/build * cmake -DCMAKE_BUILD_TYPE=Release . * make @@ -19,13 +19,13 @@ Debian ------ Required "-dev" packages: -* cmake + * libboost-filesystem-dev * libboost-program-options-dev * libboost-regex-dev * libboost-system-dev * libboost-date-time-dev -* libcrypto++-dev +* libssl-dev FreeBSD ------- @@ -37,12 +37,11 @@ Required ports: * devel/cmake * devel/boost-libs * lang/gcc47 # or later version -* security/cryptopp To use newer compiler you should set these variables: - export CC=/usr/local/bin/gcc47 - export CXX=/usr/local/bin/g++47 + export CC=/usr/local/bin/gcc47 + export CXX=/usr/local/bin/g++47 Replace "47" with your actual gcc version From 1af8d873bb5ec5eaf88e5b64d176dd9add5a4cbc Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 25 Nov 2015 07:51:03 +0000 Subject: [PATCH 0576/6300] delete build/cmake_modules/FindCryptoPP.cmake (now using openssl) --- build/cmake_modules/FindCryptoPP.cmake | 61 -------------------------- 1 file changed, 61 deletions(-) delete mode 100644 build/cmake_modules/FindCryptoPP.cmake diff --git a/build/cmake_modules/FindCryptoPP.cmake b/build/cmake_modules/FindCryptoPP.cmake deleted file mode 100644 index 396be144..00000000 --- a/build/cmake_modules/FindCryptoPP.cmake +++ /dev/null @@ -1,61 +0,0 @@ -# - Find Crypto++ - -if(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) - set(CRYPTO++_FOUND TRUE) - -else(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) - find_path(CRYPTO++_INCLUDE_DIR cryptopp/cryptlib.h - /usr/include - /usr/local/include - $ENV{SystemDrive}/Crypto++/include - $ENV{CRYPTOPP} - $ENV{CRYPTOPP}/.. - $ENV{CRYPTOPP}/include - ${PROJECT_SOURCE_DIR}/../.. - ) - - find_library(CRYPTO++_LIBRARIES NAMES cryptopp - PATHS - /usr/lib - /usr/local/lib - /opt/local/lib - $ENV{SystemDrive}/Crypto++/lib - $ENV{CRYPTOPP}/lib - ) - - if(MSVC AND NOT CRYPTO++_LIBRARIES) # Give a chance for MSVC multiconfig - if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(PLATFORM x64) - else() - set(PLATFORM Win32) - endif() - find_library(CRYPTO++_LIBRARIES_RELEASE NAMES cryptlib cryptopp - HINTS - ${PROJECT_SOURCE_DIR}/../../cryptopp/${PLATFORM}/Output/Release - PATHS - $ENV{CRYPTOPP}/Win32/Output/Release - ) - find_library(CRYPTO++_LIBRARIES_DEBUG NAMES cryptlib cryptopp - HINTS - ${PROJECT_SOURCE_DIR}/../../cryptopp/${PLATFORM}/Output/Debug - PATHS - $ENV{CRYPTOPP}/Win32/Output/Debug - ) - set(CRYPTO++_LIBRARIES - debug ${CRYPTO++_LIBRARIES_DEBUG} - optimized ${CRYPTO++_LIBRARIES_RELEASE} - CACHE PATH "Path to Crypto++ library" FORCE - ) - endif() - - if(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) - set(CRYPTO++_FOUND TRUE) - message(STATUS "Found Crypto++: ${CRYPTO++_INCLUDE_DIR}, ${CRYPTO++_LIBRARIES}") - else(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) - set(CRYPTO++_FOUND FALSE) - message(STATUS "Crypto++ not found.") - endif(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) - - mark_as_advanced(CRYPTO++_INCLUDE_DIR CRYPTO++_LIBRARIES) - -endif(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) From 654371cb6a0556a7d3f5f5f981ed2af6cdda65a9 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 25 Nov 2015 07:54:45 +0000 Subject: [PATCH 0577/6300] fix debian/ directory --- debian/changelog | 11 ++---- debian/control | 10 +++--- debian/copyright | 58 +++++++++++++++--------------- debian/patches/rename-binary.patch | 30 ++++++---------- debian/watch | 2 +- 5 files changed, 48 insertions(+), 63 deletions(-) diff --git a/debian/changelog b/debian/changelog index 23e93bd4..0b1e1871 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,11 +1,6 @@ -i2pd (20140919-2) unstable; urgency=low +i2pd (2.1.0-1) unstable; urgency=low - * updated to latest sources + * updated to version 2.1.0/0.9.23 + * updated deps -- hagen Fri, 19 Sep 2014 05:16:12 +0000 - -i2pd (20140919-1) unstable; urgency=low - - * Initial release (Closes: #nnnn) - - -- hagen Mon, 19 Sep 2014 00:00:00 +0000 diff --git a/debian/control b/debian/control index 98312c87..fc347044 100644 --- a/debian/control +++ b/debian/control @@ -3,17 +3,17 @@ Section: net Priority: extra Maintainer: hagen Build-Depends: debhelper (>= 8.0.0), dpkg-dev (>= 1.16.1~), - cmake (>= 2.8), gcc (>= 4.6) | clang (>= 3.3), + gcc (>= 4.6) | clang (>= 3.3), libboost-regex-dev, libboost-system-dev (>= 1.46), libboost-date-time-dev, libboost-filesystem-dev, libboost-program-options-dev, - libcrypto++-dev + libssl-dev Standards-Version: 3.9.3 -Homepage: https://github.com/PrivacySolutions/i2pd -Vcs-Git: git://github.com/PrivacySolutions/i2pd.git -Vcs-Browser: https://github.com/PrivacySolutions/i2pd.git +Homepage: https://github.com/PurpleI2P/i2pd +Vcs-Git: git://github.com/PurpleI2P/i2pd.git +Vcs-Browser: https://github.com/PurpleI2P/i2pd.git Package: i2pd Architecture: any diff --git a/debian/copyright b/debian/copyright index 01fef78e..00fdac77 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,38 +3,38 @@ Upstream-Name: i2pd Source: https://github.com/PurpleI2P Files: * -Copyright: 2013-2014 PurpleI2P +Copyright: 2013-2015 PurpleI2P License: BSD-3-clause -Copyright (c) 2013-2015, The PurpleI2P Project - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are -permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of -conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of -conditions and the following disclaimer in the documentation and/or other materials -provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors may be used -to endorse or promote products derived from this software without specific prior written -permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Copyright (c) 2013-2015, The PurpleI2P Project + . + All rights reserved. + . + Redistribution and use in source and binary forms, with or without modification, are + permitted provided that the following conditions are met: + . + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + . + 2. Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + . + 3. Neither the name of the copyright holder nor the names of its contributors may be used + to endorse or promote products derived from this software without specific prior written + permission. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Files: debian/* -Copyright: 2014 hagen +Copyright: 2014-2015 hagen License: GPL-2.0+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/debian/patches/rename-binary.patch b/debian/patches/rename-binary.patch index 70973dec..7b55e474 100644 --- a/debian/patches/rename-binary.patch +++ b/debian/patches/rename-binary.patch @@ -1,23 +1,13 @@ +diff --git a/Makefile b/Makefile +index f65d7a1..6f998bf 100644 --- a/Makefile +++ b/Makefile -@@ -10,9 +10,9 @@ - include Makefile.linux - endif +@@ -3,7 +3,7 @@ SHLIB := libi2pd.so + ARLIB := libi2pd.a + SHLIB_CLIENT := libi2pdclient.so + ARLIB_CLIENT := libi2pdclient.a +-I2PD := i2p ++I2PD := i2pd + GREP := fgrep + DEPS := obj/make.dep --all: obj i2p -+all: obj i2pd - --i2p: $(OBJECTS:obj/%=obj/%) -+i2pd: $(OBJECTS:obj/%=obj/%) - $(CXX) -o $@ $^ $(LDFLAGS) $(LIBS) - - .SUFFIXES: -@@ -25,7 +25,7 @@ - mkdir -p obj - - clean: -- rm -fr obj i2p -+ rm -fr obj i2pd - - .PHONY: all - .PHONY: clean diff --git a/debian/watch b/debian/watch index f3faedd2..55cda021 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,3 @@ version=3 opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/i2pd-$1\.tar\.gz/ \ - https://github.com/PrivacySolutions/i2pd/tags .*/v?(\d\S*)\.tar\.gz + https://github.com/PurpleI2P/i2pd/tags .*/v?(\d\S*)\.tar\.gz From dac2e8c79e670b81f5a80edbf93307df01c88c47 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Nov 2015 09:48:06 -0500 Subject: [PATCH 0578/6300] use left sift instead multipilication by 2 --- Signature.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index 13151508..30c81a94 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -13,19 +13,16 @@ namespace crypto Ed25519 () { BN_CTX * ctx = BN_CTX_new (); - BIGNUM * two = BN_new (), * tmp = BN_new (); - BN_set_word (two, 2); + BIGNUM * tmp = BN_new (); q = BN_new (); // 2^255-19 - BN_set_word (tmp, 255); - BN_exp (q, two, tmp, ctx); + BN_set_bit (q, 255); // 2^255 BN_sub_word (q, 19); l = BN_new (); // 2^252 + 27742317777372353535851937790883648493 - BN_set_word (tmp, 252); - BN_exp (l, two, tmp, ctx); + BN_set_bit (l, 252); two_252_2 = BN_dup (l); BN_dec2bn (&tmp, "27742317777372353535851937790883648493"); BN_add (l, l, tmp); @@ -45,9 +42,8 @@ namespace crypto tmp = BN_dup (q); BN_sub_word (tmp, 1); BN_div_word (tmp, 4); - BN_mod_exp (I, two, tmp, q, ctx); - - BN_free (two); + BN_set_word (I, 2); + BN_mod_exp (I, I, tmp, q, ctx); BN_free (tmp); // 4*inv(5) @@ -208,7 +204,7 @@ namespace crypto BIGNUM * E = BN_new (), * F = BN_new (), * G = BN_new (), * H = BN_new (); // E = (x+y)*(x+y)-A-B = x^2+y^2+2xy-A-B = 2xy BN_mul (E, p.x, p.y, ctx); - BN_mul_word (E, 2); // E =2*x*y + BN_lshift1 (E, E); // E =2*x*y BN_sub (F, z2, t2); // F = D - C BN_add (G, z2, t2); // G = D + C BN_add (H, y2, x2); // H = B + A @@ -387,7 +383,7 @@ namespace crypto // Bi16[0][0] = B, base point }; - static std::unique_ptr g_Ed25519; + static thread_local std::unique_ptr g_Ed25519; std::unique_ptr& GetEd25519 () { if (!g_Ed25519) From 56453f6b5cf78c61b8bcbf593fe51add05c8f160 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Nov 2015 10:25:51 -0500 Subject: [PATCH 0579/6300] moved BN_CTX creation to curve's Verify and Sign --- Signature.cpp | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index 30c81a94..9ea92309 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -92,8 +92,9 @@ namespace crypto EncodePoint (Normalize (publicKey, ctx), buf); } - bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature, BN_CTX * ctx) const + bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature) const { + BN_CTX * ctx = BN_CTX_new (); BIGNUM * h = DecodeBN<64> (digest); // signature 0..31 - R, 32..63 - S // B*S = R + PK*h => R = B*S - PK*h @@ -105,14 +106,16 @@ namespace crypto EncodePoint (Normalize (Sum (Bs, -PKh, ctx), ctx), diff); // Bs - PKh encoded bool passed = !memcmp (signature, diff, 32); // R BN_free (h); + BN_CTX_free (ctx); if (!passed) LogPrint (eLogError, "25519 signature verification failed"); return passed; } void Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, - uint8_t * signature, BN_CTX * bnCtx) const + uint8_t * signature) const { + BN_CTX * bnCtx = BN_CTX_new (); // calculate r SHA512_CTX ctx; SHA512_Init (&ctx); @@ -138,6 +141,7 @@ namespace crypto memcpy (signature, R, EDDSA25519_SIGNATURE_LENGTH/2); EncodeBN (h, signature + EDDSA25519_SIGNATURE_LENGTH/2, EDDSA25519_SIGNATURE_LENGTH/2); // S BN_free (r); BN_free (h); BN_free (a); + BN_CTX_free (bnCtx); } private: @@ -403,18 +407,14 @@ namespace crypto bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { uint8_t digest[64]; - { - SHA512_CTX ctx; - SHA512_Init (&ctx); - SHA512_Update (&ctx, signature, EDDSA25519_SIGNATURE_LENGTH/2); // R - SHA512_Update (&ctx, m_PublicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key - SHA512_Update (&ctx, buf, len); // data - SHA512_Final (digest, &ctx); - } - BN_CTX * ctx = BN_CTX_new (); - bool passed = GetEd25519 ()->Verify (m_PublicKey, digest, signature, ctx); - BN_CTX_free (ctx); - return passed; + SHA512_CTX ctx; + SHA512_Init (&ctx); + SHA512_Update (&ctx, signature, EDDSA25519_SIGNATURE_LENGTH/2); // R + SHA512_Update (&ctx, m_PublicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key + SHA512_Update (&ctx, buf, len); // data + SHA512_Final (digest, &ctx); + + return GetEd25519 ()->Verify (m_PublicKey, digest, signature); } EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey) @@ -433,9 +433,7 @@ namespace crypto void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const { - BN_CTX * ctx = BN_CTX_new (); - GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature, ctx); - BN_CTX_free (ctx); + GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature); } } } From d169471e8c9cafb3dc7002d04e57831ac492a7b6 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Nov 2015 13:31:30 -0500 Subject: [PATCH 0580/6300] copy constructor for Ed22519 --- Signature.cpp | 10 +++++++++- Signature.h | 23 +++++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index 9ea92309..a6d519a1 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -67,6 +67,14 @@ namespace crypto BN_CTX_free (ctx); } + Ed25519 (const Ed25519& other): q (BN_dup (other.q)), l (BN_dup (other.l)), + d (BN_dup (other.d)), I (BN_dup (other.I)), two_252_2 (BN_dup (other.two_252_2)) + { + for (int i = 0; i < 64; i++) + for (int j = 0; j < 15; j++) + Bi16[i][j] = other.Bi16[i][j]; + } + ~Ed25519 () { BN_free (q); @@ -387,7 +395,7 @@ namespace crypto // Bi16[0][0] = B, base point }; - static thread_local std::unique_ptr g_Ed25519; + static std::unique_ptr g_Ed25519; std::unique_ptr& GetEd25519 () { if (!g_Ed25519) diff --git a/Signature.h b/Signature.h index f46193aa..1e7ac2eb 100644 --- a/Signature.h +++ b/Signature.h @@ -373,6 +373,8 @@ namespace crypto BIGNUM * x, * y; BIGNUM * z, * t; // projective coordinates EDDSAPoint (): x(nullptr), y(nullptr), z(nullptr), t(nullptr) {}; + EDDSAPoint (const EDDSAPoint& other): x(nullptr), y(nullptr), z(nullptr), t(nullptr) + { *this = other; }; EDDSAPoint (EDDSAPoint&& other): x(nullptr), y(nullptr), z(nullptr), t(nullptr) { *this = std::move (other); }; EDDSAPoint (BIGNUM * x1, BIGNUM * y1, BIGNUM * z1 = nullptr, BIGNUM * t1 = nullptr): x(x1), y(y1), z(z1), t(t1) {}; @@ -380,17 +382,22 @@ namespace crypto EDDSAPoint& operator=(EDDSAPoint&& other) { - if (x) BN_free (x); - if (y) BN_free (y); - if (z) BN_free (z); - if (t) BN_free (t); - x = other.x; other.x = nullptr; - y = other.y; other.y = nullptr; - z = other.z; other.z = nullptr; - t = other.t; other.t = nullptr; + if (x) BN_free (x); x = other.x; other.x = nullptr; + if (y) BN_free (y); y = other.y; other.y = nullptr; + if (z) BN_free (z); z = other.z; other.z = nullptr; + if (t) BN_free (t); t = other.t; other.t = nullptr; return *this; } + EDDSAPoint& operator=(const EDDSAPoint& other) + { + if (x) BN_free (x); x = other.x ? BN_dup (other.x) : nullptr; + if (y) BN_free (y); y = other.y ? BN_dup (other.y) : nullptr; + if (z) BN_free (z); z = other.z ? BN_dup (other.z) : nullptr; + if (t) BN_free (t); t = other.t ? BN_dup (other.t) : nullptr; + return *this; + } + EDDSAPoint operator-() const { BIGNUM * x1 = NULL, * y1 = NULL, * z1 = NULL, * t1 = NULL; From 3c8e331809f9080ea1f34cce9efb76804f7c18f8 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Nov 2015 14:00:40 -0500 Subject: [PATCH 0581/6300] Ed25519 per thread --- Signature.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index a6d519a1..45b3c303 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -395,12 +395,26 @@ namespace crypto // Bi16[0][0] = B, base point }; - static std::unique_ptr g_Ed25519; - std::unique_ptr& GetEd25519 () + static std::shared_ptr g_Ed25519; +#if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)) // gcc 4.8 and higer + static thread_local std::shared_ptr g_Ed25519ThisThread; +#else + static std::shared_ptr g_Ed25519ThisThread; +#endif + std::shared_ptr& GetEd25519 () { - if (!g_Ed25519) - g_Ed25519.reset (new Ed25519 ()); - return g_Ed25519; + // TODO: implement it better + if (!g_Ed25519ThisThread) + { + if (!g_Ed25519) + { + g_Ed25519 = std::make_shared(); + g_Ed25519ThisThread = g_Ed25519; + } + else + g_Ed25519ThisThread = std::make_shared(*g_Ed25519); + } + return g_Ed25519ThisThread; } From d69f297c05de2f2ce78c38226a07949e1a1e0cb9 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Nov 2015 16:20:24 -0500 Subject: [PATCH 0582/6300] split between CreateSession and CreateSessionThrough Introducer --- SSU.cpp | 132 +++++++++++++++++++++++++++++++++----------------------- SSU.h | 1 + 2 files changed, 79 insertions(+), 54 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 41946c0a..3ee9c69f 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -274,12 +274,17 @@ namespace transport void SSUServer::CreateSession (std::shared_ptr router, bool peerTest) { - std::shared_ptr session; if (router) { + if (router->UsesIntroducer ()) + { + CreateSessionThroughIntroducer (router, peerTest); + return; + } auto address = router->GetSSUAddress (!context.SupportsV6 ()); if (address) { + std::shared_ptr session; boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); auto it = m_Sessions.find (remoteEndpoint); if (it != m_Sessions.end ()) @@ -296,63 +301,82 @@ namespace transport std::unique_lock l(m_SessionsMutex); m_Sessions[remoteEndpoint] = session; } - if (!router->UsesIntroducer ()) - { - // connect directly - LogPrint ("Creating new SSU session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", - remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); - session->Connect (); - } - else - { - // connect through introducer - int numIntroducers = address->introducers.size (); - if (numIntroducers > 0) - { - std::shared_ptr introducerSession; - const i2p::data::RouterInfo::Introducer * introducer = nullptr; - // we might have a session to introducer already - for (int i = 0; i < numIntroducers; i++) - { - introducer = &(address->introducers[i]); - it = m_Sessions.find (boost::asio::ip::udp::endpoint (introducer->iHost, introducer->iPort)); - if (it != m_Sessions.end ()) - { - introducerSession = it->second; - break; - } - } + // connect + LogPrint ("Creating new SSU session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", + remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); + session->Connect (); + } + } + else + LogPrint (eLogWarning, "Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); + } + } - if (introducerSession) // session found - LogPrint ("Session to introducer already exists"); - else // create new - { - LogPrint ("Creating new session to introducer"); - introducer = &(address->introducers[0]); // TODO: - boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); - introducerSession = std::make_shared (*this, introducerEndpoint, router); - std::unique_lock l(m_SessionsMutex); - m_Sessions[introducerEndpoint] = introducerSession; - } - // introduce - LogPrint ("Introduce new SSU session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), - "] through introducer ", introducer->iHost, ":", introducer->iPort); - session->WaitForIntroduction (); - if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable - { - uint8_t buf[1]; - Send (buf, 0, remoteEndpoint); // send HolePunch - } - introducerSession->Introduce (*introducer); - } - else - { - LogPrint (eLogWarning, "Can't connect to unreachable router. No introducers presented"); - std::unique_lock l(m_SessionsMutex); - m_Sessions.erase (remoteEndpoint); + void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest) + { + if (router && router->UsesIntroducer ()) + { + auto address = router->GetSSUAddress (!context.SupportsV6 ()); + if (address) + { + boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); + auto it = m_Sessions.find (remoteEndpoint); + // check if session if presented alredy + if (it != m_Sessions.end ()) + { + auto session = it->second; + if (peerTest && session->GetState () == eSessionStateEstablished) + session->SendPeerTest (); + return; + } + // create new session + int numIntroducers = address->introducers.size (); + if (numIntroducers > 0) + { + std::shared_ptr introducerSession; + const i2p::data::RouterInfo::Introducer * introducer = nullptr; + // we might have a session to introducer already + for (int i = 0; i < numIntroducers; i++) + { + introducer = &(address->introducers[i]); + it = m_Sessions.find (boost::asio::ip::udp::endpoint (introducer->iHost, introducer->iPort)); + if (it != m_Sessions.end ()) + { + introducerSession = it->second; + break; } } + + if (introducerSession) // session found + LogPrint ("Session to introducer already exists"); + else // create new + { + LogPrint ("Creating new session to introducer"); + introducer = &(address->introducers[0]); // TODO: + boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); + introducerSession = std::make_shared (*this, introducerEndpoint, router); + std::unique_lock l(m_SessionsMutex); + m_Sessions[introducerEndpoint] = introducerSession; + } + // create session + auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); + { + std::unique_lock l(m_SessionsMutex); + m_Sessions[remoteEndpoint] = session; + } + // introduce + LogPrint ("Introduce new SSU session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), + "] through introducer ", introducer->iHost, ":", introducer->iPort); + session->WaitForIntroduction (); + if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable + { + uint8_t buf[1]; + Send (buf, 0, remoteEndpoint); // send HolePunch + } + introducerSession->Introduce (*introducer); } + else + LogPrint (eLogWarning, "Can't connect to unreachable router. No introducers presented"); } else LogPrint (eLogWarning, "Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); diff --git a/SSU.h b/SSU.h index bf660523..96b9199d 100644 --- a/SSU.h +++ b/SSU.h @@ -41,6 +41,7 @@ namespace transport void Start (); void Stop (); void CreateSession (std::shared_ptr router, bool peerTest = false); + void CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest = false); std::shared_ptr FindSession (std::shared_ptr router) const; std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; std::shared_ptr GetRandomEstablishedSession (std::shared_ptr excluded); From b24959205b0ba8f8df55645a41c741b89b590706 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Nov 2015 21:03:58 -0500 Subject: [PATCH 0583/6300] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 70577cea..c6694568 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ Downloads ------------ Official binary releases could be found at: +http://i2pd.website/releases/ +older releases http://download.i2p.io/purplei2p/i2pd/releases/ From 0a5745c559a56ae79a07e0b1dad264537a7fcd92 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Nov 2015 21:04:19 -0500 Subject: [PATCH 0584/6300] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6694568..d3b2e711 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Downloads ------------ Official binary releases could be found at: -http://i2pd.website/releases/ +http://i2pd.website/releases/ older releases http://download.i2p.io/purplei2p/i2pd/releases/ From d4d1768575a7df888bcab80c86d86b4ca9c499c2 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 Nov 2015 11:42:44 +0000 Subject: [PATCH 0585/6300] * Makefile.* : fix build with gcc 4.7.2 (#299) --- Makefile.bsd | 2 +- Makefile.linux | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.bsd b/Makefile.bsd index 72820069..08994a24 100644 --- a/Makefile.bsd +++ b/Makefile.bsd @@ -6,7 +6,7 @@ CXXFLAGS = -O2 ## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. -NEEDED_CXXFLAGS = -std=c++11 +NEEDED_CXXFLAGS = -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 INCFLAGS = -I/usr/include/ -I/usr/local/include/ LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread diff --git a/Makefile.linux b/Makefile.linux index 78dcaaf0..724be15d 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -15,7 +15,7 @@ ifeq ($(shell expr match $(CXX) 'clang'),5) else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10 NEEDED_CXXFLAGS += -std=c++11 else ifeq ($(shell expr match ${CXXVER} "4\.[7-9]"),3) # >= 4.7 - NEEDED_CXXFLAGS += -std=c++11 + NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 else ifeq ($(shell expr match ${CXXVER} "4\.6"),3) # = 4.6 NEEDED_CXXFLAGS += -std=c++0x else ifeq ($(shell expr match ${CXXVER} "5\.[0-9]"),3) # gcc >= 5.0 From e755a32b23b15ffe3905a50ca1b07ceb9c1489ef Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 Nov 2015 12:09:31 +0000 Subject: [PATCH 0586/6300] * take some enchancements for debian/ dir from kytv (#1) --- debian/control | 25 +++++++++++++++++++++++-- debian/copyright | 1 + debian/i2pd.default | 2 +- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/debian/control b/debian/control index fc347044..eb86f1cf 100644 --- a/debian/control +++ b/debian/control @@ -18,5 +18,26 @@ Vcs-Browser: https://github.com/PurpleI2P/i2pd.git Package: i2pd Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} -Description: i2p router written in C++ - Mainly runs on linux, but also supports OSX, BSD and Windows +Recommends: privoxy +Suggests: tor +Description: load-balanced unspoofable packet switching network - C++ port + I2P is an anonymizing network, offering a simple layer that identity-sensitive + applications can use to securely communicate. All data is wrapped with several + layers of encryption, and the network is both distributed and dynamic, with no + trusted parties. + . + This package contains the port of the I2P router to C++. Unless willing + to test and report problems, you should install the 'i2p' package instead. + +Package: i2pd-dbg +Architecture: any +Priority: extra +Section: debug +Depends: ${shlibs:Depends}, ${misc:Depends}, i2pd (= ${binary:Version}) +Description: i2pd debugging symbols + I2P is an anonymizing network, offering a simple layer that identity-sensitive + applications can use to securely communicate. All data is wrapped with several + layers of encryption, and the network is both distributed and dynamic, with no + trusted parties. + . + This package contains symbols required for debugging. diff --git a/debian/copyright b/debian/copyright index 00fdac77..117fcffd 100644 --- a/debian/copyright +++ b/debian/copyright @@ -35,6 +35,7 @@ License: BSD-3-clause Files: debian/* Copyright: 2014-2015 hagen + 2013-2015 Kill Your TV License: GPL-2.0+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/debian/i2pd.default b/debian/i2pd.default index b76000ab..3919c61e 100644 --- a/debian/i2pd.default +++ b/debian/i2pd.default @@ -3,5 +3,5 @@ # installed at /etc/default/i2pd by the maintainer scripts # Additional options that are passed to the Daemon. -DAEMON_OPTS="--host=1.2.3.4 --port=4567" +DAEMON_OPTS="--host=1.2.3.4 --port=4567 --ircdest=irc.postman.i2p" # change ip and port above to your external address and port From c73c8fdc471f536fe7560577b6ecb4f143f0482c Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 Nov 2015 12:19:57 +0000 Subject: [PATCH 0587/6300] * fix building of empty -dbg package --- debian/rules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/debian/rules b/debian/rules index bf31e452..4375650c 100755 --- a/debian/rules +++ b/debian/rules @@ -11,3 +11,6 @@ PREFIX=/usr %: dh $@ + +override_dh_strip: + dh_strip --dbg-package=i2pd-dbg From cd515a2e5412bb483d37a44e9244bf39624214a9 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 Nov 2015 13:35:09 +0000 Subject: [PATCH 0588/6300] * fix Depends: for i2pd-dbg --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index eb86f1cf..50967142 100644 --- a/debian/control +++ b/debian/control @@ -33,7 +33,7 @@ Package: i2pd-dbg Architecture: any Priority: extra Section: debug -Depends: ${shlibs:Depends}, ${misc:Depends}, i2pd (= ${binary:Version}) +Depends: i2pd (= ${binary:Version}), ${misc:Depends} Description: i2pd debugging symbols I2P is an anonymizing network, offering a simple layer that identity-sensitive applications can use to securely communicate. All data is wrapped with several From f84ac1847283d1fed3875d9acac53165a11fb70e Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 Nov 2015 12:56:05 +0000 Subject: [PATCH 0589/6300] * set defaults to *FLAGS instead redefining them --- Makefile.linux | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile.linux b/Makefile.linux index 724be15d..24816770 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -1,5 +1,6 @@ -CXXFLAGS = -g -Wall -INCFLAGS = +# set defaults instead redefine +CXXFLAGS ?= -g -Wall +INCFLAGS ?= ## NOTE: The NEEDED_CXXFLAGS are here so that custom CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. From 9bc477e1b6acd2ad0f10cb1ba223bd6755e36ad5 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 Nov 2015 13:54:22 +0000 Subject: [PATCH 0590/6300] * use stricter linker options for .deb packages --- debian/rules | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/rules b/debian/rules index 4375650c..e7342142 100755 --- a/debian/rules +++ b/debian/rules @@ -4,6 +4,7 @@ # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 +DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow DPKG_EXPORT_BUILDFLAGS = 1 include /usr/share/dpkg/buildflags.mk CXXFLAGS+=$(CPPFLAGS) From 7bfb4995494e97b57ebb560771771bfcd21a027e Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Nov 2015 13:19:45 -0500 Subject: [PATCH 0591/6300] reduce number of precalculated points --- Signature.cpp | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index 45b3c303..e60daa9c 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -56,22 +56,26 @@ namespace crypto BN_mod (By, By, q, ctx); // % q // precalculate Bi16 table - Bi16[0][0] = { Bx, By }; // B + Bi16Carry = { Bx, By }; // B for (int i = 0; i < 64; i++) { - if (i) Bi16[i][0] = Sum (Bi16[i-1][14], Bi16[i-1][0], ctx); - for (int j = 1; j < 15; j++) + Bi16[i][0] = Bi16Carry; + for (int j = 1; j < 8; j++) Bi16[i][j] = Sum (Bi16[i][j-1], Bi16[i][0], ctx); // (16+j+1)^i*B + Bi16Carry = Bi16[i][7]; + for (int j = 8; j < 16; j++) + Bi16Carry = Sum (Bi16Carry, Bi16[i][0], ctx); } BN_CTX_free (ctx); } Ed25519 (const Ed25519& other): q (BN_dup (other.q)), l (BN_dup (other.l)), - d (BN_dup (other.d)), I (BN_dup (other.I)), two_252_2 (BN_dup (other.two_252_2)) + d (BN_dup (other.d)), I (BN_dup (other.I)), two_252_2 (BN_dup (other.two_252_2)), + Bi16Carry (other.Bi16Carry) { for (int i = 0; i < 64; i++) - for (int j = 0; j < 15; j++) + for (int j = 0; j < 8; j++) Bi16[i][j] = other.Bi16[i][j]; } @@ -251,20 +255,40 @@ namespace crypto return res; } - EDDSAPoint MulB (const uint8_t * e, BN_CTX * ctx) const // B*e. e is 32 bytes Little Endian + EDDSAPoint MulB (const uint8_t * e, BN_CTX * ctx) const // B*e, e is 32 bytes Little Endian { BIGNUM * zero = BN_new (), * one = BN_new (); BN_zero (zero); BN_one (one); EDDSAPoint res {zero, one}; + bool carry = false; for (int i = 0; i < 32; i++) { uint8_t x = e[i] & 0x0F; // 4 low bits + if (carry) { x++; if (x <= 15) carry = false; else x = 0; }; if (x > 0) - res = Sum (res, Bi16[i*2][x-1], ctx); + { + if (x <= 8) + res = Sum (res, Bi16[i*2][x-1], ctx); + else + { + res = Sum (res, -Bi16[i*2][15-x], ctx); // -Bi[16-x] + carry = true; + } + } x = e[i] >> 4; // 4 high bits + if (carry) { x++; if (x <= 15) carry = false; else x = 0; }; if (x > 0) - res = Sum (res, Bi16[i*2+1][x-1], ctx); + { + if (x <= 8) + res = Sum (res, Bi16[i*2+1][x-1], ctx); + else + { + res = Sum (res, -Bi16[i*2+1][15-x], ctx); // -Bi[16-x] + carry = true; + } + } } + if (carry) res = Sum (res, Bi16Carry, ctx); return res; } @@ -391,8 +415,10 @@ namespace crypto BIGNUM * q, * l, * d, * I; // transient values BIGNUM * two_252_2; // 2^252-2 - EDDSAPoint Bi16[64][15]; // per 4-bits, Bi16[i][j] = (16+j+1)^i*B, we don't store zeroes + EDDSAPoint Bi16[64][8]; // per 4-bits, Bi16[i][j] = (16+j+1)^i*B, we don't store zeroes + // if j > 8 we use 16 - j and carry 1 to next 4-bits // Bi16[0][0] = B, base point + EDDSAPoint Bi16Carry; // Bi16[64][0] }; static std::shared_ptr g_Ed25519; From 430368de97bf96d3e7a40db9f8c008c9c694d9c3 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Nov 2015 15:46:30 -0500 Subject: [PATCH 0592/6300] temporary disable Ed25519 per thread --- Signature.cpp | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index e60daa9c..de36b64d 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -421,26 +421,13 @@ namespace crypto EDDSAPoint Bi16Carry; // Bi16[64][0] }; - static std::shared_ptr g_Ed25519; -#if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)) // gcc 4.8 and higer - static thread_local std::shared_ptr g_Ed25519ThisThread; -#else - static std::shared_ptr g_Ed25519ThisThread; -#endif - std::shared_ptr& GetEd25519 () + static std::unique_ptr g_Ed25519; + std::unique_ptr& GetEd25519 () { - // TODO: implement it better - if (!g_Ed25519ThisThread) - { - if (!g_Ed25519) - { - g_Ed25519 = std::make_shared(); - g_Ed25519ThisThread = g_Ed25519; - } - else - g_Ed25519ThisThread = std::make_shared(*g_Ed25519); - } - return g_Ed25519ThisThread; + if (!g_Ed25519) + g_Ed25519.reset (new Ed25519()); + + return g_Ed25519; } From a94a05fac9af8b1e04efb00b0dcfda166f11ecb4 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Nov 2015 19:02:54 -0500 Subject: [PATCH 0593/6300] replaced radix-16 to radix-256 --- Signature.cpp | 67 ++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index de36b64d..aa993033 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -55,16 +55,16 @@ namespace crypto BN_mod (Bx, Bx, q, ctx); // % q BN_mod (By, By, q, ctx); // % q - // precalculate Bi16 table - Bi16Carry = { Bx, By }; // B - for (int i = 0; i < 64; i++) + // precalculate Bi256 table + Bi256Carry = { Bx, By }; // B + for (int i = 0; i < 32; i++) { - Bi16[i][0] = Bi16Carry; - for (int j = 1; j < 8; j++) - Bi16[i][j] = Sum (Bi16[i][j-1], Bi16[i][0], ctx); // (16+j+1)^i*B - Bi16Carry = Bi16[i][7]; - for (int j = 8; j < 16; j++) - Bi16Carry = Sum (Bi16Carry, Bi16[i][0], ctx); + Bi256[i][0] = Bi256Carry; + for (int j = 1; j < 128; j++) + Bi256[i][j] = Sum (Bi256[i][j-1], Bi256[i][0], ctx); // (256+j+1)^i*B + Bi256Carry = Bi256[i][127]; + for (int j = 128; j < 256; j++) + Bi256Carry = Sum (Bi256Carry, Bi256[i][0], ctx); } BN_CTX_free (ctx); @@ -72,11 +72,11 @@ namespace crypto Ed25519 (const Ed25519& other): q (BN_dup (other.q)), l (BN_dup (other.l)), d (BN_dup (other.d)), I (BN_dup (other.I)), two_252_2 (BN_dup (other.two_252_2)), - Bi16Carry (other.Bi16Carry) + Bi256Carry (other.Bi256Carry) { - for (int i = 0; i < 64; i++) - for (int j = 0; j < 8; j++) - Bi16[i][j] = other.Bi16[i][j]; + for (int i = 0; i < 32; i++) + for (int j = 0; j < 128; j++) + Bi256[i][j] = other.Bi256[i][j]; } ~Ed25519 () @@ -263,32 +263,29 @@ namespace crypto bool carry = false; for (int i = 0; i < 32; i++) { - uint8_t x = e[i] & 0x0F; // 4 low bits - if (carry) { x++; if (x <= 15) carry = false; else x = 0; }; + uint8_t x = e[i]; + if (carry) + { + if (x < 255) + { + x++; + carry = false; + } + else + x = 0; + } if (x > 0) { - if (x <= 8) - res = Sum (res, Bi16[i*2][x-1], ctx); + if (x <= 128) + res = Sum (res, Bi256[i][x-1], ctx); else { - res = Sum (res, -Bi16[i*2][15-x], ctx); // -Bi[16-x] + res = Sum (res, -Bi256[i][255-x], ctx); // -Bi[256-x] carry = true; } } - x = e[i] >> 4; // 4 high bits - if (carry) { x++; if (x <= 15) carry = false; else x = 0; }; - if (x > 0) - { - if (x <= 8) - res = Sum (res, Bi16[i*2+1][x-1], ctx); - else - { - res = Sum (res, -Bi16[i*2+1][15-x], ctx); // -Bi[16-x] - carry = true; - } - } } - if (carry) res = Sum (res, Bi16Carry, ctx); + if (carry) res = Sum (res, Bi256Carry, ctx); return res; } @@ -415,10 +412,10 @@ namespace crypto BIGNUM * q, * l, * d, * I; // transient values BIGNUM * two_252_2; // 2^252-2 - EDDSAPoint Bi16[64][8]; // per 4-bits, Bi16[i][j] = (16+j+1)^i*B, we don't store zeroes - // if j > 8 we use 16 - j and carry 1 to next 4-bits - // Bi16[0][0] = B, base point - EDDSAPoint Bi16Carry; // Bi16[64][0] + EDDSAPoint Bi256[32][128]; // per byte, Bi256[i][j] = (256+j+1)^i*B, we don't store zeroes + // if j > 128 we use 256 - j and carry 1 to next byte + // Bi256[0][0] = B, base point + EDDSAPoint Bi256Carry; // Bi256[32][0] }; static std::unique_ptr g_Ed25519; From 72785f6740bd91bca9e6ff03f11fe79f585ef70f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Nov 2015 22:16:10 -0500 Subject: [PATCH 0594/6300] eliminate some unnecessary calculations --- Signature.cpp | 59 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index aa993033..40f8ce6c 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -59,11 +59,11 @@ namespace crypto Bi256Carry = { Bx, By }; // B for (int i = 0; i < 32; i++) { - Bi256[i][0] = Bi256Carry; + Bi256[i][0] = Bi256Carry; // first point for (int j = 1; j < 128; j++) Bi256[i][j] = Sum (Bi256[i][j-1], Bi256[i][0], ctx); // (256+j+1)^i*B Bi256Carry = Bi256[i][127]; - for (int j = 128; j < 256; j++) + for (int j = 0; j < 128; j++) // add first point 128 more times Bi256Carry = Sum (Bi256Carry, Bi256[i][0], ctx); } @@ -165,19 +165,32 @@ namespace crypto // z3 = (z1*z2-d*t1*t2)*(z1*z2+d*t1*t2) // t3 = (y1*y2+x1*x2)*(x1*y2+y1*x2) BIGNUM * x3 = BN_new (), * y3 = BN_new (), * z3 = BN_new (), * t3 = BN_new (); - BIGNUM * z1 = p1.z, * t1 = p1.t; - if (!z1) { z1 = BN_new (); BN_one (z1); } - if (!t1) { t1 = BN_new (); BN_mul (t1, p1.x, p1.y, ctx); } - - BIGNUM * z2 = p2.z, * t2 = p2.t; - if (!z2) { z2 = BN_new (); BN_one (z2); } - if (!t2) { t2 = BN_new (); BN_mul (t2, p2.x, p2.y, ctx); } BN_mul (x3, p1.x, p2.x, ctx); // A = x1*x2 BN_mul (y3, p1.y, p2.y, ctx); // B = y1*y2 + + BIGNUM * t1 = p1.t, * t2 = p2.t; + if (!t1) { t1 = BN_new (); BN_mul (t1, p1.x, p1.y, ctx); } + if (!t2) { t2 = BN_new (); BN_mul (t2, p2.x, p2.y, ctx); } BN_mul (t3, t1, t2, ctx); BN_mul (t3, t3, d, ctx); // C = d*t1*t2 - BN_mul (z3, z1, z2, ctx); // D = z1*z2 + if (!p1.t) BN_free (t1); + if (!p2.t) BN_free (t2); + + if (p1.z) + { + if (p2.z) + BN_mul (z3, p1.z, p2.z, ctx); // D = z1*z2 + else + BN_copy (z3, p1.z); // D = z1 + } + else + { + if (p2.z) + BN_copy (z3, p2.z); // D = z2 + else + BN_one (z3); // D = 1 + } BIGNUM * E = BN_new (), * F = BN_new (), * G = BN_new (), * H = BN_new (); BN_add (E, p1.x, p1.y); @@ -189,11 +202,6 @@ namespace crypto BN_add (G, z3, t3); // G = D + C BN_add (H, y3, x3); // H = B + A - if (!p1.z) BN_free (z1); - if (!p1.t) BN_free (t1); - if (!p2.z) BN_free (z2); - if (!p2.t) BN_free (t2); - BN_mod_mul (x3, E, F, q, ctx); // x3 = E*F BN_mod_mul (y3, G, H, q, ctx); // y3 = G*H BN_mod_mul (z3, F, G, q, ctx); // z3 = F*G @@ -207,15 +215,21 @@ namespace crypto EDDSAPoint Double (const EDDSAPoint& p, BN_CTX * ctx) const { BIGNUM * x2 = BN_new (), * y2 = BN_new (), * z2 = BN_new (), * t2 = BN_new (); - BIGNUM * z = p.z, * t = p.t; - if (!z) { z = BN_new (); BN_one (z); } - if (!t) { t = BN_new (); BN_mul (t, p.x, p.y, ctx); } BN_sqr (x2, p.x, ctx); // x2 = A = x^2 BN_sqr (y2, p.y, ctx); // y2 = B = y^2 - BN_sqr (t2, t, ctx); - BN_mul (t2, t2, d, ctx); // t2 = C = d*t^2 - BN_sqr (z2, z, ctx); // z2 = D = z^2 + if (p.t) + BN_sqr (t2, p.t, ctx); // t2 = t^2 + else + { + BN_mul (t2, p.x, p.y, ctx); // t = x*y + BN_sqr (t2, t2, ctx); // t2 = t^2 + } + BN_mul (t2, t2, d, ctx); // t2 = C = d*t^2 + if (p.z) + BN_sqr (z2, p.z, ctx); // z2 = D = z^2 + else + BN_one (z2); // z2 = 1 BIGNUM * E = BN_new (), * F = BN_new (), * G = BN_new (), * H = BN_new (); // E = (x+y)*(x+y)-A-B = x^2+y^2+2xy-A-B = 2xy @@ -225,9 +239,6 @@ namespace crypto BN_add (G, z2, t2); // G = D + C BN_add (H, y2, x2); // H = B + A - if (!p.z) BN_free (z); - if (!p.t) BN_free (t); - BN_mod_mul (x2, E, F, q, ctx); // x2 = E*F BN_mod_mul (y2, G, H, q, ctx); // y2 = G*H BN_mod_mul (z2, F, G, q, ctx); // z2 = F*G From fb2bdfb9ee8f169e6787421b165ce2e84f02f120 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 29 Nov 2015 09:10:49 -0500 Subject: [PATCH 0595/6300] create SSU session in SSU thread --- SSU.cpp | 47 ++++++++++++++++++++++++++--------------------- SSU.h | 3 ++- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 3ee9c69f..17aa0b2d 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -284,34 +284,39 @@ namespace transport auto address = router->GetSSUAddress (!context.SupportsV6 ()); if (address) { - std::shared_ptr session; boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); - auto it = m_Sessions.find (remoteEndpoint); - if (it != m_Sessions.end ()) - { - session = it->second; - if (peerTest && session->GetState () == eSessionStateEstablished) - session->SendPeerTest (); - } - else - { - // otherwise create new session - session = std::make_shared (*this, remoteEndpoint, router, peerTest); - { - std::unique_lock l(m_SessionsMutex); - m_Sessions[remoteEndpoint] = session; - } - // connect - LogPrint ("Creating new SSU session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", - remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); - session->Connect (); - } + auto& s = remoteEndpoint.address ().is_v6 () ? m_ServiceV6 : m_Service; + s.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); } else LogPrint (eLogWarning, "Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); } } + void SSUServer::CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest) + { + auto it = m_Sessions.find (remoteEndpoint); + if (it != m_Sessions.end ()) + { + auto session = it->second; + if (peerTest && session->GetState () == eSessionStateEstablished) + session->SendPeerTest (); + } + else + { + // otherwise create new session + auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); + { + std::unique_lock l(m_SessionsMutex); + m_Sessions[remoteEndpoint] = session; + } + // connect + LogPrint ("Creating new SSU session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", + remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); + session->Connect (); + } + } + void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest) { if (router && router->UsesIntroducer ()) diff --git a/SSU.h b/SSU.h index 96b9199d..08f54642 100644 --- a/SSU.h +++ b/SSU.h @@ -41,7 +41,6 @@ namespace transport void Start (); void Stop (); void CreateSession (std::shared_ptr router, bool peerTest = false); - void CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest = false); std::shared_ptr FindSession (std::shared_ptr router) const; std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; std::shared_ptr GetRandomEstablishedSession (std::shared_ptr excluded); @@ -72,6 +71,8 @@ namespace transport void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); void HandleReceivedPackets (std::vector packets); + void CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest = false); + void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); template std::shared_ptr GetRandomSession (Filter filter); From 4e2ba71d5991b987acacfdc87557ffefe09b281d Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 29 Nov 2015 17:25:42 -0500 Subject: [PATCH 0596/6300] more introducers --- SSU.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SSU.cpp b/SSU.cpp index 17aa0b2d..2f266a99 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -514,7 +514,7 @@ namespace transport } } m_Introducers = newList; - if (m_Introducers.empty ()) + if (m_Introducers.size () < SSU_MAX_NUM_INTRODUCERS) { auto introducer = i2p::data::netdb.GetRandomIntroducer (); if (introducer) From cee1b8a64a80c7a7fbe5cd79e1328ecde5294bb5 Mon Sep 17 00:00:00 2001 From: erlend1 Date: Mon, 30 Nov 2015 16:44:32 +0200 Subject: [PATCH 0597/6300] Configurable addresses from master --- BOB.cpp | 4 ++-- BOB.h | 2 +- ClientContext.cpp | 13 +++++++------ ClientContext.h | 1 + Daemon.cpp | 4 ++-- HTTPProxy.cpp | 4 ++-- HTTPProxy.h | 2 +- HTTPServer.cpp | 4 ++-- HTTPServer.h | 2 +- I2PControl.cpp | 4 ++-- I2PControl.h | 2 +- I2PService.h | 8 ++++---- I2PTunnel.cpp | 5 ++--- I2PTunnel.h | 2 +- README.md | 15 +++++++++++---- SAM.cpp | 6 +++--- SAM.h | 2 +- SOCKS.cpp | 4 ++-- SOCKS.h | 2 +- 19 files changed, 47 insertions(+), 39 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index 15df456e..38777092 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -524,9 +524,9 @@ namespace client SendReplyError ("malformed"); } - BOBCommandChannel::BOBCommandChannel (int port): + BOBCommandChannel::BOBCommandChannel (const std::string& address, int port): m_IsRunning (false), m_Thread (nullptr), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)) + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)) { // command -> handler m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler; diff --git a/BOB.h b/BOB.h index ddc64707..b73e390e 100644 --- a/BOB.h +++ b/BOB.h @@ -199,7 +199,7 @@ namespace client { public: - BOBCommandChannel (int port); + BOBCommandChannel (const std::string& address, int port); ~BOBCommandChannel (); void Start (); diff --git a/ClientContext.cpp b/ClientContext.cpp index 84ffb538..557deb38 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -41,10 +41,10 @@ namespace client std::string proxyKeys = i2p::util::config::GetArg("-proxykeys", ""); if (proxyKeys.length () > 0) localDestination = LoadLocalDestination (proxyKeys, false); - m_HttpProxy = new i2p::proxy::HTTPProxy(i2p::util::config::GetArg("-httpproxyport", 4446), localDestination); + m_HttpProxy = new i2p::proxy::HTTPProxy(i2p::util::config::GetArg("-httpproxyaddress", "127.0.0.1"), i2p::util::config::GetArg("-httpproxyport", 4446), localDestination); m_HttpProxy->Start(); LogPrint("HTTP Proxy started"); - m_SocksProxy = new i2p::proxy::SOCKSProxy(i2p::util::config::GetArg("-socksproxyport", 4447), localDestination); + m_SocksProxy = new i2p::proxy::SOCKSProxy(i2p::util::config::GetArg("-socksproxyaddress", "127.0.0.1"), i2p::util::config::GetArg("-socksproxyport", 4447), localDestination); m_SocksProxy->Start(); LogPrint("SOCKS Proxy Started"); @@ -57,7 +57,7 @@ namespace client if (ircKeys.length () > 0) localDestination = LoadLocalDestination (ircKeys, false); auto ircPort = i2p::util::config::GetArg("-ircport", 6668); - auto ircTunnel = new I2PClientTunnel (ircDestination, ircPort, localDestination); + auto ircTunnel = new I2PClientTunnel (ircDestination, i2p::util::config::GetArg("-ircaddress", "127.0.0.1"), ircPort, localDestination); ircTunnel->Start (); m_ClientTunnels.insert (std::make_pair(ircPort, std::unique_ptr(ircTunnel))); LogPrint("IRC tunnel started"); @@ -78,7 +78,7 @@ namespace client int samPort = i2p::util::config::GetArg("-samport", 0); if (samPort) { - m_SamBridge = new SAMBridge (samPort); + m_SamBridge = new SAMBridge (i2p::util::config::GetArg("-samaddress", "127.0.0.1"), samPort); m_SamBridge->Start (); LogPrint("SAM bridge started"); } @@ -87,7 +87,7 @@ namespace client int bobPort = i2p::util::config::GetArg("-bobport", 0); if (bobPort) { - m_BOBCommandChannel = new BOBCommandChannel (bobPort); + m_BOBCommandChannel = new BOBCommandChannel (i2p::util::config::GetArg("-bobaddress", "127.0.0.1"), bobPort); m_BOBCommandChannel->Start (); LogPrint("BOB command channel started"); } @@ -268,12 +268,13 @@ namespace client int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); // optional params std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); + std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); std::shared_ptr localDestination = nullptr; if (keys.length () > 0) localDestination = LoadLocalDestination (keys, false); - auto clientTunnel = new I2PClientTunnel (dest, port, localDestination, destinationPort); + auto clientTunnel = new I2PClientTunnel (dest, address, port, localDestination, destinationPort); if (m_ClientTunnels.insert (std::make_pair (port, std::unique_ptr(clientTunnel))).second) clientTunnel->Start (); else diff --git a/ClientContext.h b/ClientContext.h index 842ee918..6684ea0e 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -21,6 +21,7 @@ namespace client const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; const char I2P_CLIENT_TUNNEL_PORT[] = "port"; + const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination"; const char I2P_CLIENT_TUNNEL_KEYS[] = "keys"; const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport"; diff --git a/Daemon.cpp b/Daemon.cpp index d0f254e2..f698e90b 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -116,7 +116,7 @@ namespace i2p StartLog (""); // write to stdout } - d.httpServer = std::unique_ptr(new i2p::util::HTTPServer(i2p::util::config::GetArg("-httpport", 7070))); + d.httpServer = std::unique_ptr(new i2p::util::HTTPServer(i2p::util::config::GetArg("-httpaddress", "127.0.0.1"), i2p::util::config::GetArg("-httpport", 7070))); d.httpServer->Start(); LogPrint("HTTP Server started"); i2p::data::netdb.Start(); @@ -135,7 +135,7 @@ namespace i2p int i2pcontrolPort = i2p::util::config::GetArg("-i2pcontrolport", 0); if (i2pcontrolPort) { - d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcontrolPort)); + d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2p::util::config::GetArg("-i2pcontroladdress", "127.0.0.1"), i2pcontrolPort)); d.m_I2PControlService->Start (); LogPrint("I2PControl started"); } diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 8e0ef9ad..12d2c765 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -285,8 +285,8 @@ namespace proxy } } - HTTPProxyServer::HTTPProxyServer(int port, std::shared_ptr localDestination): - TCPIPAcceptor(port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()) + HTTPProxyServer::HTTPProxyServer(const std::string& address, int port, std::shared_ptr localDestination): + TCPIPAcceptor(address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()) { } diff --git a/HTTPProxy.h b/HTTPProxy.h index 6c0d0a6f..b5ed77b9 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -16,7 +16,7 @@ namespace proxy { public: - HTTPProxyServer(int port, std::shared_ptr localDestination = nullptr); + HTTPProxyServer(const std::string& address, int port, std::shared_ptr localDestination = nullptr); ~HTTPProxyServer() {}; protected: diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 1b5d1939..7121d17b 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -1059,9 +1059,9 @@ namespace util std::bind (&HTTPConnection::HandleWriteReply, shared_from_this (), std::placeholders::_1)); } - HTTPServer::HTTPServer (int port): + HTTPServer::HTTPServer (const std::string& address, int port): m_Thread (nullptr), m_Work (m_Service), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4 (), port)) + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)) { } diff --git a/HTTPServer.h b/HTTPServer.h index 98383644..b4349c5b 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -106,7 +106,7 @@ namespace util { public: - HTTPServer (int port); + HTTPServer (const std::string& address, int port); virtual ~HTTPServer (); void Start (); diff --git a/I2PControl.cpp b/I2PControl.cpp index f050b297..d04b91d1 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -24,9 +24,9 @@ namespace i2p { namespace client { - I2PControlService::I2PControlService (int port): + I2PControlService::I2PControlService (const std::string& address, int port): m_Password (I2P_CONTROL_DEFAULT_PASSWORD), m_IsRunning (false), m_Thread (nullptr), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)), + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), m_SSLContext (m_Service, boost::asio::ssl::context::sslv23), m_ShutdownTimer (m_Service) { diff --git a/I2PControl.h b/I2PControl.h index fd6bffbe..565281ac 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -78,7 +78,7 @@ namespace client typedef boost::asio::ssl::stream ssl_socket; public: - I2PControlService (int port); + I2PControlService (const std::string& address, int port); ~I2PControlService (); void Start (); diff --git a/I2PService.h b/I2PService.h index afac4ea4..9d91b086 100644 --- a/I2PService.h +++ b/I2PService.h @@ -81,13 +81,13 @@ namespace client class TCPIPAcceptor: public I2PService { public: - TCPIPAcceptor (int port, std::shared_ptr localDestination = nullptr) : + TCPIPAcceptor (const std::string& address, int port, std::shared_ptr localDestination = nullptr) : I2PService(localDestination), - m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4 (), port)), + m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)), m_Timer (GetService ()) {} - TCPIPAcceptor (int port, i2p::data::SigningKeyType kt) : + TCPIPAcceptor (const std::string& address, int port, i2p::data::SigningKeyType kt) : I2PService(kt), - m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4 (), port)), + m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)), m_Timer (GetService ()) {} virtual ~TCPIPAcceptor () { TCPIPAcceptor::Stop(); } //If you override this make sure you call it from the children diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index dd2c31df..09edafc5 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -256,9 +256,8 @@ namespace client Done(shared_from_this()); } - I2PClientTunnel::I2PClientTunnel (const std::string& destination, int port, std::shared_ptr localDestination, int destinationPort): - TCPIPAcceptor (port,localDestination), m_Destination (destination), m_DestinationIdentHash (nullptr), m_DestinationPort (destinationPort) - {} + I2PClientTunnel::I2PClientTunnel (const std::string& destination, const std::string& address, int port, std::shared_ptr localDestination, int destinationPort): + TCPIPAcceptor (address, port, localDestination), m_Destination (destination), m_DestinationIdentHash (nullptr), m_DestinationPort (destinationPort) {} void I2PClientTunnel::Start () { diff --git a/I2PTunnel.h b/I2PTunnel.h index 2071b89d..0ca3d674 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -86,7 +86,7 @@ namespace client public: - I2PClientTunnel (const std::string& destination, int port, std::shared_ptr localDestination, int destinationPort = 0); + I2PClientTunnel (const std::string& destination, const std::string& address, int port, std::shared_ptr localDestination, int destinationPort = 0); ~I2PClientTunnel () {} void Start (); diff --git a/README.md b/README.md index d3b2e711..141e2f8c 100644 --- a/README.md +++ b/README.md @@ -72,25 +72,32 @@ Cmdline options * --host= - The external IP (deprecated). * --port= - The port to listen on -* --httpport= - The http port to listen on +* --httpaddress= - The address to listen on (HTTP server) +* --httpport= - The port to listen on (HTTP server) * --log= - Enable or disable logging to file. 1 for yes, 0 for no. * --daemon= - Enable or disable daemon mode. 1 for yes, 0 for no. * --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). * --v6= - 1 if supports communication through ipv6, off by default * --floodfill= - 1 if router is floodfill, off by default * --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O if not. Always O if floodfill, otherwise L by default. -* --httpproxyport= - The port to listen on (HTTP Proxy) -* --socksproxyport= - The port to listen on (SOCKS Proxy) +* --httpproxyaddress= - The address to listen on (HTTP Proxy) +* --httpproxyport= - The port to listen on (HTTP Proxy) 4446 by default +* --socksproxyaddress= - The address to listen on (SOCKS Proxy) +* --socksproxyport= - The port to listen on (SOCKS Proxy). 4447 by default * --proxykeys= - optional keys file for proxy's local destination -* --ircport= - The local port of IRC tunnel to listen on. 6668 by default +* --ircaddress= - The address to listen on (IRC tunnel) +* --ircport= - The port listen on (IRC tunnel). 6668 by default * --ircdest= - I2P destination address of IRC server. For example irc.postman.i2p * --irckeys= - optional keys file for tunnel's local destination * --eepkeys= - File name containing destination keys, for example privKeys.dat. The file will be created if it does not already exist (issue #110). * --eephost= - Address incoming trafic forward to. 127.0.0.1 by default * --eepport= - Port incoming trafic forward to. 80 by default +* --samaddress= - The address to listen on (SAM bridge) * --samport= - Port of SAM bridge. Usually 7656. SAM is off if not specified +* --bobaddress= - The address to listen on (BOB command channel) * --bobport= - Port of BOB command channel. Usually 2827. BOB is off if not specified +* --i2pcontroladdress= - The address to listen on (I2P control service) * --i2pcontrolport= - Port of I2P control service. Usually 7650. I2PControl is off if not specified * --tunnelscfg= - Tunnels Config file (default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg) * --conf= - Config file (default: ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf) diff --git a/SAM.cpp b/SAM.cpp index ee8b514a..0c0cb714 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -686,10 +686,10 @@ namespace client sockets.clear (); } - SAMBridge::SAMBridge (int port): + SAMBridge::SAMBridge (const std::string& address, int port): m_IsRunning (false), m_Thread (nullptr), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)), - m_DatagramEndpoint (boost::asio::ip::udp::v4 (), port-1), m_DatagramSocket (m_Service, m_DatagramEndpoint) + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), + m_DatagramEndpoint (boost::asio::ip::address::from_string(address), port-1), m_DatagramSocket (m_Service, m_DatagramEndpoint) { } diff --git a/SAM.h b/SAM.h index 4baa01d4..e82e8b7d 100644 --- a/SAM.h +++ b/SAM.h @@ -146,7 +146,7 @@ namespace client { public: - SAMBridge (int port); + SAMBridge (const std::string& address, int port); ~SAMBridge (); void Start (); diff --git a/SOCKS.cpp b/SOCKS.cpp index 8cf85bbd..d82cd0f4 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -560,8 +560,8 @@ namespace proxy } } - SOCKSServer::SOCKSServer(int port, std::shared_ptr localDestination) : - TCPIPAcceptor (port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()) + SOCKSServer::SOCKSServer(const std::string& address, int port, std::shared_ptr localDestination) : + TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()) { } diff --git a/SOCKS.h b/SOCKS.h index 7854ce3a..cc2cfa24 100644 --- a/SOCKS.h +++ b/SOCKS.h @@ -15,7 +15,7 @@ namespace proxy { public: - SOCKSServer(int port, std::shared_ptr localDestination = nullptr); + SOCKSServer(const std::string& address, int port, std::shared_ptr localDestination = nullptr); ~SOCKSServer() {}; protected: From 0a6d849435390e1995e419b3d86394817c44baeb Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 Nov 2015 10:23:05 -0500 Subject: [PATCH 0598/6300] pass shared_ptr to SendRelayIntro --- SSUSession.cpp | 4 ++-- SSUSession.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index 33534ec1..b09dcf1f 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -479,7 +479,7 @@ namespace transport buf += 32; // introkey uint32_t nonce = bufbe32toh (buf); SendRelayResponse (nonce, from, introKey, session->m_RemoteEndpoint); - SendRelayIntro (session.get (), from); + SendRelayIntro (session, from); } } @@ -537,7 +537,7 @@ namespace transport LogPrint (eLogDebug, "SSU relay response sent"); } - void SSUSession::SendRelayIntro (SSUSession * session, const boost::asio::ip::udp::endpoint& from) + void SSUSession::SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from) { if (!session) return; // Alice's address always v4 diff --git a/SSUSession.h b/SSUSession.h index cf6e688f..0c1c5254 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -108,7 +108,7 @@ namespace transport void ProcessRelayRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to); - void SendRelayIntro (SSUSession * session, const boost::asio::ip::udp::endpoint& from); + void SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from); void ProcessRelayResponse (uint8_t * buf, size_t len); void ProcessRelayIntro (uint8_t * buf, size_t len); void Established (); From 2b8e662f8105a44656fd129a49bc94ff3905acfa Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 Nov 2015 14:59:32 -0500 Subject: [PATCH 0599/6300] connect through introducer in v4 thread --- SSU.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 2f266a99..4005e228 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -278,7 +278,7 @@ namespace transport { if (router->UsesIntroducer ()) { - CreateSessionThroughIntroducer (router, peerTest); + m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, peerTest)); // always V4 thread return; } auto address = router->GetSSUAddress (!context.SupportsV6 ()); @@ -321,7 +321,7 @@ namespace transport { if (router && router->UsesIntroducer ()) { - auto address = router->GetSSUAddress (!context.SupportsV6 ()); + auto address = router->GetSSUAddress (true); // v4 only for now if (address) { boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); From c5308e3f2f52042c0e93514eeaa0a474551d82cc Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 Nov 2015 15:53:07 -0500 Subject: [PATCH 0600/6300] separate SSU sessions lists for V4 and V6 --- HTTPServer.cpp | 14 ++++++++++--- SSU.cpp | 57 +++++++++++++++++++++++++------------------------- SSU.h | 11 +++++----- SSUSession.cpp | 2 +- 4 files changed, 46 insertions(+), 38 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 7121d17b..f4cd7b82 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -772,7 +772,6 @@ namespace util s << "
SSU
"; for (auto it: ssuServer->GetSessions ()) { - // incoming connections don't have remote router auto endpoint = it.second->GetRemoteEndpoint (); if (it.second->IsOutgoing ()) s << "-->"; s << endpoint.address ().to_string () << ":" << endpoint.port (); @@ -780,8 +779,17 @@ namespace util s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; if (it.second->GetRelayTag ()) s << " [itag:" << it.second->GetRelayTag () << "]"; - s << "
"; - s << std::endl; + s << "
" << std::endl; + } + s << "
SSU6
"; + for (auto it: ssuServer->GetSessionsV6 ()) + { + auto endpoint = it.second->GetRemoteEndpoint (); + if (it.second->IsOutgoing ()) s << "-->"; + s << endpoint.address ().to_string () << ":" << endpoint.port (); + if (!it.second->IsOutgoing ()) s << "-->"; + s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + s << "
" << std::endl; } } } diff --git a/SSU.cpp b/SSU.cpp index 4005e228..4d5ae9db 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -174,7 +174,7 @@ namespace transport moreBytes = m_Socket.available(); } - m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets)); + m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, m_Sessions)); Receive (); } else @@ -201,7 +201,7 @@ namespace transport moreBytes = m_SocketV6.available(); } - m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets)); + m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, m_SessionsV6)); ReceiveV6 (); } else @@ -211,7 +211,8 @@ namespace transport } } - void SSUServer::HandleReceivedPackets (std::vector packets) + void SSUServer::HandleReceivedPackets (std::vector packets, + std::map >& sessions) { std::shared_ptr session; for (auto it1: packets) @@ -222,17 +223,14 @@ namespace transport if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous { if (session) session->FlushData (); - auto it = m_Sessions.find (packet->from); - if (it != m_Sessions.end ()) + auto it = sessions.find (packet->from); + if (it != sessions.end ()) session = it->second; if (!session) { session = std::make_shared (*this, packet->from); session->WaitForConnect (); - { - std::unique_lock l(m_SessionsMutex); - m_Sessions[packet->from] = session; - } + sessions[packet->from] = session; LogPrint (eLogInfo, "New SSU session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); } } @@ -265,8 +263,9 @@ namespace transport std::shared_ptr SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const { - auto it = m_Sessions.find (e); - if (it != m_Sessions.end ()) + auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions; + auto it = sessions.find (e); + if (it != sessions.end ()) return it->second; else return nullptr; @@ -295,8 +294,9 @@ namespace transport void SSUServer::CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest) { - auto it = m_Sessions.find (remoteEndpoint); - if (it != m_Sessions.end ()) + auto& sessions = remoteEndpoint.address ().is_v6 () ? m_SessionsV6 : m_Sessions; + auto it = sessions.find (remoteEndpoint); + if (it != sessions.end ()) { auto session = it->second; if (peerTest && session->GetState () == eSessionStateEstablished) @@ -306,10 +306,7 @@ namespace transport { // otherwise create new session auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); - { - std::unique_lock l(m_SessionsMutex); - m_Sessions[remoteEndpoint] = session; - } + sessions[remoteEndpoint] = session; // connect LogPrint ("Creating new SSU session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); @@ -360,15 +357,11 @@ namespace transport introducer = &(address->introducers[0]); // TODO: boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); introducerSession = std::make_shared (*this, introducerEndpoint, router); - std::unique_lock l(m_SessionsMutex); m_Sessions[introducerEndpoint] = introducerSession; } // create session auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); - { - std::unique_lock l(m_SessionsMutex); - m_Sessions[remoteEndpoint] = session; - } + m_Sessions[remoteEndpoint] = session; // introduce LogPrint ("Introduce new SSU session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] through introducer ", introducer->iHost, ":", introducer->iPort); @@ -393,21 +386,27 @@ namespace transport if (session) { session->Close (); - std::unique_lock l(m_SessionsMutex); - m_Sessions.erase (session->GetRemoteEndpoint ()); + auto& ep = session->GetRemoteEndpoint (); + if (ep.address ().is_v6 ()) + m_SessionsV6.erase (ep); + else + m_Sessions.erase (ep); } } void SSUServer::DeleteAllSessions () { - std::unique_lock l(m_SessionsMutex); for (auto it: m_Sessions) it.second->Close (); m_Sessions.clear (); + + for (auto it: m_SessionsV6) + it.second->Close (); + m_SessionsV6.clear (); } template - std::shared_ptr SSUServer::GetRandomSession (Filter filter) + std::shared_ptr SSUServer::GetRandomV4Session (Filter filter) // v4 only { std::vector > filteredSessions; for (auto s :m_Sessions) @@ -420,9 +419,9 @@ namespace transport return nullptr; } - std::shared_ptr SSUServer::GetRandomEstablishedSession (std::shared_ptr excluded) + std::shared_ptr SSUServer::GetRandomEstablishedV4Session (std::shared_ptr excluded) // v4 only { - return GetRandomSession ( + return GetRandomV4Session ( [excluded](std::shared_ptr session)->bool { return session->GetState () == eSessionStateEstablished && !session->IsV6 () && @@ -437,7 +436,7 @@ namespace transport std::set ret; for (int i = 0; i < maxNumIntroducers; i++) { - auto session = GetRandomSession ( + auto session = GetRandomV4Session ( [&ret, ts](std::shared_ptr session)->bool { return session->GetRelayTag () && !ret.count (session.get ()) && diff --git a/SSU.h b/SSU.h index 08f54642..fc01644f 100644 --- a/SSU.h +++ b/SSU.h @@ -43,7 +43,7 @@ namespace transport void CreateSession (std::shared_ptr router, bool peerTest = false); std::shared_ptr FindSession (std::shared_ptr router) const; std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; - std::shared_ptr GetRandomEstablishedSession (std::shared_ptr excluded); + std::shared_ptr GetRandomEstablishedV4Session (std::shared_ptr excluded); void DeleteSession (std::shared_ptr session); void DeleteAllSessions (); @@ -69,12 +69,13 @@ namespace transport void ReceiveV6 (); void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); - void HandleReceivedPackets (std::vector packets); + void HandleReceivedPackets (std::vector packets, + std::map >& sessions); void CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest = false); void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); template - std::shared_ptr GetRandomSession (Filter filter); + std::shared_ptr GetRandomV4Session (Filter filter); std::set FindIntroducers (int maxNumIntroducers); void ScheduleIntroducersUpdateTimer (); @@ -100,14 +101,14 @@ namespace transport boost::asio::ip::udp::socket m_Socket, m_SocketV6; boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer; std::list m_Introducers; // introducers we are connected to - mutable std::mutex m_SessionsMutex; - std::map > m_Sessions; + std::map > m_Sessions, m_SessionsV6; std::map m_Relays; // we are introducer std::map m_PeerTests; // nonce -> creation time in milliseconds public: // for HTTP only const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; + const decltype(m_SessionsV6)& GetSessionsV6 () const { return m_SessionsV6; }; }; } } diff --git a/SSUSession.cpp b/SSUSession.cpp index b09dcf1f..ff5d1182 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -931,7 +931,7 @@ namespace transport else { LogPrint (eLogDebug, "SSU peer test from Alice. We are Bob"); - auto session = m_Server.GetRandomEstablishedSession (shared_from_this ()); // Charlie + auto session = m_Server.GetRandomEstablishedV4Session (shared_from_this ()); // Charlie, TODO: implement v6 support if (session) { m_Server.NewPeerTest (nonce, ePeerTestParticipantBob, shared_from_this ()); From f5aea766a781722d8da48f4f3d34a9a0e5caf0c1 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 30 Nov 2015 06:57:24 +0000 Subject: [PATCH 0601/6300] * move 'Requirements' sections to BUILD_NOTES.md --- README.md | 13 ------------- build/BUILD_NOTES.md | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 141e2f8c..970d0844 100644 --- a/README.md +++ b/README.md @@ -17,19 +17,6 @@ BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z -Requirements for Linux/FreeBSD/OSX ----------------------------------- - -GCC 4.6 or newer, Boost 1.46 or newer, openssl, zlib. Clang can be used instead of -GCC. - -Requirements for Windows ------------------------- - -VS2013 (known to work with 12.0.21005.1 or newer), Boost 1.46 or newer, -crypto++ 5.62. See Win32/README-Build.txt for instructions on how to build i2pd -and its dependencies. - Downloads ------------ diff --git a/build/BUILD_NOTES.md b/build/BUILD_NOTES.md index 51296e43..e116ba5d 100644 --- a/build/BUILD_NOTES.md +++ b/build/BUILD_NOTES.md @@ -1,3 +1,18 @@ +Requirements +============ + +Linux/FreeBSD/OSX +----------------- + +GCC 4.6 or newer, Boost 1.46 or newer, openssl, zlib. Clang can be used instead of GCC. + +Windows +------- + +VS2013 (known to work with 12.0.21005.1 or newer), Boost 1.46 or newer, +crypto++ 5.62. See Win32/README-Build.txt for instructions on how to build i2pd +and its dependencies. + Build notes =========== From 43299aea10c378326fd71ad4574534e76d96af61 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 30 Nov 2015 06:57:50 +0000 Subject: [PATCH 0602/6300] * BUILD_NOTES.md : update --- build/BUILD_NOTES.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/build/BUILD_NOTES.md b/build/BUILD_NOTES.md index e116ba5d..376f7457 100644 --- a/build/BUILD_NOTES.md +++ b/build/BUILD_NOTES.md @@ -16,11 +16,11 @@ and its dependencies. Build notes =========== -Common build/install process: +Common build/install process from sources: * git clone https://github.com/PurpleI2P/i2pd.git -* cd i2pd/build -* cmake -DCMAKE_BUILD_TYPE=Release . +* mkdir -p 'i2pd/build/tmp' && cd 'i2pd/build/tmp' +* cmake -DCMAKE_BUILD_TYPE=Release .. * make * make install @@ -29,18 +29,27 @@ Available cmake options: * CMAKE_BUILD_TYPE -- build profile (Debug/Release) * WITH_AESNI -- AES-NI support (ON/OFF) * WITH_HARDENING -- enable hardening features (ON/OFF) (gcc only) +* WITH_BINARY -- build i2pd itself +* WITH_LIBRARY -- build libi2pd +* WITH_STATIC -- build static versions of library and i2pd binary +* WITH_UPNP -- build with UPnP support (requires libupnp) +* WITH_PCH -- use pre-compiled header (experimental, speeds up build) Debian ------ -Required "-dev" packages: +For building from source on debian system you will need the following "-dev" packages: +* libboost-chrono-dev +* libboost-date-time-dev * libboost-filesystem-dev * libboost-program-options-dev * libboost-regex-dev * libboost-system-dev -* libboost-date-time-dev -* libssl-dev +* libboost-thread-dev +* libssl-dev (e.g. openssl) +* zlib1g-dev (libssl-dev already depends on it) +* libminiupnpc-dev (optional, if WITH_UPNP=ON) FreeBSD ------- From 80a0a3d4fb78f3dee2491e702cfa832ef7c5f95d Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 30 Nov 2015 07:06:59 +0000 Subject: [PATCH 0603/6300] * BUILD_NOTES.md : add hints for building deb --- build/BUILD_NOTES.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/build/BUILD_NOTES.md b/build/BUILD_NOTES.md index 376f7457..dc652437 100644 --- a/build/BUILD_NOTES.md +++ b/build/BUILD_NOTES.md @@ -35,8 +35,8 @@ Available cmake options: * WITH_UPNP -- build with UPnP support (requires libupnp) * WITH_PCH -- use pre-compiled header (experimental, speeds up build) -Debian ------- +Debian/Ubuntu +------------- For building from source on debian system you will need the following "-dev" packages: @@ -51,6 +51,12 @@ For building from source on debian system you will need the following "-dev" pac * zlib1g-dev (libssl-dev already depends on it) * libminiupnpc-dev (optional, if WITH_UPNP=ON) +You may also build deb-package with the following: + + apt-get install build-essential fakeroot devscripts + cd i2pd + debuild --no-tgz-check # building from git repo + FreeBSD ------- From 710439e83c19de3b79181844e7f0b68ca1a41e40 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 1 Dec 2015 00:05:13 +0000 Subject: [PATCH 0604/6300] * cleanup README * move all docs to single dir --- README.md | 107 +----------------- .../build_notes_unix.md | 0 .../build_notes_windows.md | 2 + docs/configuration.md | 84 ++++++++++++++ 4 files changed, 92 insertions(+), 101 deletions(-) rename build/BUILD_NOTES.md => docs/build_notes_unix.md (100%) rename Win32/README-Build.txt => docs/build_notes_windows.md (98%) create mode 100644 docs/configuration.md diff --git a/README.md b/README.md index 970d0844..4c3de21f 100644 --- a/README.md +++ b/README.md @@ -34,105 +34,10 @@ Build Statuses - Mac OS X - Got it working, but not well tested. (Only works with clang, not GCC.) - Microsoft VC13 - To be added +More documentation +------------------ -Testing -------- - -First, build it. - -On Ubuntu/Debian based -* sudo apt-get install libboost-dev libboost-filesystem-dev libboost-program-options-dev libboost-regex-dev libboost-date-time-dev libssl-dev zlib1g-dev -* $ cd i2pd -* $ make - -Then, run it: - -$ ./i2p - -The client should now reseed by itself. -To visit an eepsite use HTTP proxy port 4446. -For tunnels follow [instructions](https://github.com/PurpleI2P/i2pd/wiki/tunnels.cfg) - - -Cmdline options ---------------- - -* --host= - The external IP (deprecated). -* --port= - The port to listen on -* --httpaddress= - The address to listen on (HTTP server) -* --httpport= - The port to listen on (HTTP server) -* --log= - Enable or disable logging to file. 1 for yes, 0 for no. -* --daemon= - Enable or disable daemon mode. 1 for yes, 0 for no. -* --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). -* --v6= - 1 if supports communication through ipv6, off by default -* --floodfill= - 1 if router is floodfill, off by default -* --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O if not. Always O if floodfill, otherwise L by default. -* --httpproxyaddress= - The address to listen on (HTTP Proxy) -* --httpproxyport= - The port to listen on (HTTP Proxy) 4446 by default -* --socksproxyaddress= - The address to listen on (SOCKS Proxy) -* --socksproxyport= - The port to listen on (SOCKS Proxy). 4447 by default -* --proxykeys= - optional keys file for proxy's local destination -* --ircaddress= - The address to listen on (IRC tunnel) -* --ircport= - The port listen on (IRC tunnel). 6668 by default -* --ircdest= - I2P destination address of IRC server. For example irc.postman.i2p -* --irckeys= - optional keys file for tunnel's local destination -* --eepkeys= - File name containing destination keys, for example privKeys.dat. - The file will be created if it does not already exist (issue #110). -* --eephost= - Address incoming trafic forward to. 127.0.0.1 by default -* --eepport= - Port incoming trafic forward to. 80 by default -* --samaddress= - The address to listen on (SAM bridge) -* --samport= - Port of SAM bridge. Usually 7656. SAM is off if not specified -* --bobaddress= - The address to listen on (BOB command channel) -* --bobport= - Port of BOB command channel. Usually 2827. BOB is off if not specified -* --i2pcontroladdress= - The address to listen on (I2P control service) -* --i2pcontrolport= - Port of I2P control service. Usually 7650. I2PControl is off if not specified -* --tunnelscfg= - Tunnels Config file (default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg) -* --conf= - Config file (default: ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf) - This parameter will be silently ignored if the specified config file does not exist. - Options specified on the command line take precedence over those in the config file. - -Config files ------------- - -INI-like, syntax is the following : = . -All command-line parameters are allowed as keys, for example: - -i2p.conf: - - log = 1 - v6 = 0 - ircdest = irc.postman.i2p - -tunnels.cfg (filename of this config is subject of change): - - ; outgoing tunnel sample, to remote service - ; mandatory parameters: - ; * type -- always "client" - ; * port -- local port to listen to - ; * destination -- i2p hostname - ; optional parameters (may be omitted) - ; * keys -- our identity, if unset, will be generated on every startup, - ; if set and file missing, keys will be generated and placed to this file - [IRC] - type = client - port = 6668 - destination = irc.echelon.i2p - keys = irc-keys.dat - - ; incoming tunnel sample, for local service - ; mandatory parameters: - ; * type -- always "server" - ; * host -- ip address of our service - ; * port -- port of our service - ; * keys -- file with LeaseSet of address in i2p - ; optional parameters (may be omitted) - ; * inport -- optional, i2p service port, if unset - the same as 'port' - ; * accesslist -- comma-separated list of i2p addresses, allowed to connect - ; every address is b32 without '.b32.i2p' part - [LOCALSITE] - type = server - host = 127.0.0.1 - port = 80 - keys = site-keys.dat - inport = 81 - accesslist = [,] +* [Building from source / unix](docs/build_notes_unix.md) +* [Building from source / windows](docs/build_notes_windows.md) +* [Configuring your i2pd](docs/configuration.md) +* [Github wiki](https://github.com/PurpleI2P/i2pd/wiki/) diff --git a/build/BUILD_NOTES.md b/docs/build_notes_unix.md similarity index 100% rename from build/BUILD_NOTES.md rename to docs/build_notes_unix.md diff --git a/Win32/README-Build.txt b/docs/build_notes_windows.md similarity index 98% rename from Win32/README-Build.txt rename to docs/build_notes_windows.md index da5b25de..5e9e465e 100644 --- a/Win32/README-Build.txt +++ b/docs/build_notes_windows.md @@ -1,6 +1,8 @@ Building i2pd for Windows ========================= +!!! WARNING This file may be outdated. + Requirements for building: * Visual Studio 2013 (tested with VS2013 Update 1, Update 3, and Update 4) diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..79e30dc9 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,84 @@ +i2pd cmdline options +-------------------- + +* --host= - The external IP (deprecated). +* --port= - The port to listen on +* --httpaddress= - The address to listen on (HTTP server) +* --httpport= - The port to listen on (HTTP server) +* --log= - Enable or disable logging to file. 1 for yes, 0 for no. +* --daemon= - Enable or disable daemon mode. 1 for yes, 0 for no. +* --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). +* --v6= - 1 if supports communication through ipv6, off by default +* --floodfill= - 1 if router is floodfill, off by default +* --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O if not. Always O if floodfill, otherwise L by default. +* --httpproxyaddress= - The address to listen on (HTTP Proxy) +* --httpproxyport= - The port to listen on (HTTP Proxy) 4446 by default +* --socksproxyaddress= - The address to listen on (SOCKS Proxy) +* --socksproxyport= - The port to listen on (SOCKS Proxy). 4447 by default +* --proxykeys= - optional keys file for proxy's local destination +* --ircaddress= - The address to listen on (IRC tunnel) +* --ircport= - The port listen on (IRC tunnel). 6668 by default +* --ircdest= - I2P destination address of IRC server. For example irc.postman.i2p +* --irckeys= - optional keys file for tunnel's local destination +* --eepkeys= - File name containing destination keys, for example privKeys.dat. + The file will be created if it does not already exist (issue #110). +* --eephost= - Address incoming trafic forward to. 127.0.0.1 by default +* --eepport= - Port incoming trafic forward to. 80 by default +* --samaddress= - The address to listen on (SAM bridge) +* --samport= - Port of SAM bridge. Usually 7656. SAM is off if not specified +* --bobaddress= - The address to listen on (BOB command channel) +* --bobport= - Port of BOB command channel. Usually 2827. BOB is off if not specified +* --i2pcontroladdress= - The address to listen on (I2P control service) +* --i2pcontrolport= - Port of I2P control service. Usually 7650. I2PControl is off if not specified +* --tunnelscfg= - Tunnels Config file (default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg) +* --conf= - Config file (default: ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf) + This parameter will be silently ignored if the specified config file does not exist. + Options specified on the command line take precedence over those in the config file. + +Config files +------------ + +INI-like, syntax is the following : = . +All command-line parameters are allowed as keys, for example: + +i2p.conf: + + log = 1 + v6 = 0 + ircdest = irc.postman.i2p + +tunnels.cfg (filename of this config is subject of change): + + ; outgoing tunnel sample, to remote service + ; mandatory parameters: + ; * type -- always "client" + ; * port -- local port to listen to + ; * destination -- i2p hostname + ; optional parameters (may be omitted) + ; * keys -- our identity, if unset, will be generated on every startup, + ; if set and file missing, keys will be generated and placed to this file + [IRC] + type = client + port = 6668 + destination = irc.echelon.i2p + keys = irc-keys.dat + ; + ; incoming tunnel sample, for local service + ; mandatory parameters: + ; * type -- always "server" + ; * host -- ip address of our service + ; * port -- port of our service + ; * keys -- file with LeaseSet of address in i2p + ; optional parameters (may be omitted) + ; * inport -- optional, i2p service port, if unset - the same as 'port' + ; * accesslist -- comma-separated list of i2p addresses, allowed to connect + ; every address is b32 without '.b32.i2p' part + [LOCALSITE] + type = server + host = 127.0.0.1 + port = 80 + keys = site-keys.dat + inport = 81 + accesslist = [,] + +Also see [this page](https://github.com/PurpleI2P/i2pd/wiki/tunnels.cfg) for more tunnel examples. From 988007a8c9105b5ff94888d908458fd7cf71a72f Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 Nov 2015 19:45:57 -0500 Subject: [PATCH 0605/6300] pass correct pointer to sessions table --- SSU.cpp | 12 ++++++------ SSU.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 4d5ae9db..12b720df 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -174,7 +174,7 @@ namespace transport moreBytes = m_Socket.available(); } - m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, m_Sessions)); + m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_Sessions)); Receive (); } else @@ -201,7 +201,7 @@ namespace transport moreBytes = m_SocketV6.available(); } - m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, m_SessionsV6)); + m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6)); ReceiveV6 (); } else @@ -212,7 +212,7 @@ namespace transport } void SSUServer::HandleReceivedPackets (std::vector packets, - std::map >& sessions) + std::map > * sessions) { std::shared_ptr session; for (auto it1: packets) @@ -223,14 +223,14 @@ namespace transport if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous { if (session) session->FlushData (); - auto it = sessions.find (packet->from); - if (it != sessions.end ()) + auto it = sessions->find (packet->from); + if (it != sessions->end ()) session = it->second; if (!session) { session = std::make_shared (*this, packet->from); session->WaitForConnect (); - sessions[packet->from] = session; + (*sessions)[packet->from] = session; LogPrint (eLogInfo, "New SSU session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); } } diff --git a/SSU.h b/SSU.h index fc01644f..8f9bec77 100644 --- a/SSU.h +++ b/SSU.h @@ -70,7 +70,7 @@ namespace transport void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); void HandleReceivedPackets (std::vector packets, - std::map >& sessions); + std::map >* sessions); void CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest = false); void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); From da8a6a4c2b11d2d3f02c4c551c104abcf9dc3fd1 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 1 Dec 2015 09:21:02 -0500 Subject: [PATCH 0606/6300] make sure to use ipv4 introducers only --- SSU.cpp | 29 +++++++++++++++++++---------- SSUSession.cpp | 2 +- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 12b720df..7e51538f 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -340,21 +340,30 @@ namespace transport // we might have a session to introducer already for (int i = 0; i < numIntroducers; i++) { - introducer = &(address->introducers[i]); - it = m_Sessions.find (boost::asio::ip::udp::endpoint (introducer->iHost, introducer->iPort)); - if (it != m_Sessions.end ()) - { - introducerSession = it->second; - break; - } + auto intr = &(address->introducers[i]); + boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort); + if (ep.address ().is_v4 ()) // ipv4 only + { + if (!introducer) introducer = intr; // we pick first one for now + it = m_Sessions.find (ep); + if (it != m_Sessions.end ()) + { + introducerSession = it->second; + break; + } + } } + if (!introducer) + { + LogPrint (eLogWarning, "Can't connect to unreachable router. No ipv4 introducers presented"); + return; + } if (introducerSession) // session found - LogPrint ("Session to introducer already exists"); + LogPrint (eLogInfo, "Session to introducer already exists"); else // create new { - LogPrint ("Creating new session to introducer"); - introducer = &(address->introducers[0]); // TODO: + LogPrint (eLogInfo, "Creating new session to introducer"); boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); introducerSession = std::make_shared (*this, introducerEndpoint, router); m_Sessions[introducerEndpoint] = introducerSession; diff --git a/SSUSession.cpp b/SSUSession.cpp index ff5d1182..346f5dd8 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -384,7 +384,7 @@ namespace transport s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6 s.Insert (htobe16 (address->port)); // our port uint32_t relayTag = 0; - if (i2p::context.GetRouterInfo ().IsIntroducer ()) + if (i2p::context.GetRouterInfo ().IsIntroducer () && !IsV6 ()) { RAND_bytes((uint8_t *)&relayTag, 4); if (!relayTag) relayTag = 1; From 0ef42870e56808d225b0673f19f4a17570901385 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 2 Dec 2015 12:48:10 -0500 Subject: [PATCH 0607/6300] try SSU if NTCP address is not presented --- SSU.cpp | 24 ++++++++++++++---------- SSU.h | 2 ++ Transports.cpp | 19 ++++++++++++++----- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 7e51538f..6fe03dd2 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -270,25 +270,29 @@ namespace transport else return nullptr; } - + void SSUServer::CreateSession (std::shared_ptr router, bool peerTest) + { + auto address = router->GetSSUAddress (!context.SupportsV6 ()); + if (address) + CreateSession (router, address->host, address->port, peerTest); + else + LogPrint (eLogWarning, "Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); + } + + void SSUServer::CreateSession (std::shared_ptr router, + const boost::asio::ip::address& addr, int port, bool peerTest) { if (router) { if (router->UsesIntroducer ()) - { m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, peerTest)); // always V4 thread - return; - } - auto address = router->GetSSUAddress (!context.SupportsV6 ()); - if (address) + else { - boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); - auto& s = remoteEndpoint.address ().is_v6 () ? m_ServiceV6 : m_Service; + boost::asio::ip::udp::endpoint remoteEndpoint (addr, port); + auto& s = addr.is_v6 () ? m_ServiceV6 : m_Service; s.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); } - else - LogPrint (eLogWarning, "Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); } } diff --git a/SSU.h b/SSU.h index 8f9bec77..efe6203d 100644 --- a/SSU.h +++ b/SSU.h @@ -41,6 +41,8 @@ namespace transport void Start (); void Stop (); void CreateSession (std::shared_ptr router, bool peerTest = false); + void CreateSession (std::shared_ptr router, + const boost::asio::ip::address& addr, int port, bool peerTest = false); std::shared_ptr FindSession (std::shared_ptr router) const; std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; std::shared_ptr GetRandomEstablishedV4Session (std::shared_ptr excluded); diff --git a/Transports.cpp b/Transports.cpp index 1b74093c..702be15d 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -284,15 +284,24 @@ namespace transport } } } + else + LogPrint (eLogInfo, "NTCP address is not presented. Trying SSU"); } - else if (peer.numAttempts == 1)// SSU + if (peer.numAttempts == 1)// SSU { peer.numAttempts++; - if (m_SSUServer) - { - if (peer.router->IsSSU (!context.SupportsV6 ())) + if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ())) + { + auto address = peer.router->GetSSUAddress (!context.SupportsV6 ()); +#if BOOST_VERSION >= 104900 + if (!address->host.is_unspecified ()) // we have address now +#else + boost::system::error_code ecode; + address->host.to_string (ecode); + if (!ecode) +#endif { - m_SSUServer->CreateSession (peer.router); + m_SSUServer->CreateSession (peer.router, address->host, address->port); return true; } } From bf47df46c952622c80783befab532d3a768d5321 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 3 Dec 2015 15:45:01 -0500 Subject: [PATCH 0608/6300] allow DNS names for SSU --- RouterInfo.cpp | 5 ++--- Transports.cpp | 42 +++++++++++++++++++++++++++++++++++++++++- Transports.h | 3 +++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 037297e0..82b44f23 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -171,9 +171,8 @@ namespace data } else { - // TODO: resolve address for SSU - LogPrint (eLogWarning, "Unexpected SSU address ", value); - isValidAddress = false; + m_SupportedTransports |= eSSUV4; // TODO: + address.addressString = value; } } else diff --git a/Transports.cpp b/Transports.cpp index 702be15d..cdd35a42 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -278,7 +278,7 @@ namespace transport { if (address->addressString.length () > 0) // trying to resolve { - LogPrint (eLogInfo, "Resolving ", address->addressString); + LogPrint (eLogInfo, "Resolving NTCP ", address->addressString); NTCPResolve (address->addressString, ident); return true; } @@ -304,6 +304,15 @@ namespace transport m_SSUServer->CreateSession (peer.router, address->host, address->port); return true; } + else // we don't have address + { + if (address->addressString.length () > 0) // trying to resolve + { + LogPrint (eLogInfo, "Resolving SSU ", address->addressString); + SSUResolve (address->addressString, ident); + return true; + } + } } } LogPrint (eLogError, "No NTCP and SSU addresses available"); @@ -376,6 +385,37 @@ namespace transport } } + void Transports::SSUResolve (const std::string& addr, const i2p::data::IdentHash& ident) + { + auto resolver = std::make_shared(m_Service); + resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""), + std::bind (&Transports::HandleSSUResolve, this, + std::placeholders::_1, std::placeholders::_2, ident, resolver)); + } + + void Transports::HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + i2p::data::IdentHash ident, std::shared_ptr resolver) + { + auto it1 = m_Peers.find (ident); + if (it1 != m_Peers.end ()) + { + auto& peer = it1->second; + if (!ecode && peer.router) + { + auto address = (*it).endpoint ().address (); + LogPrint (eLogInfo, (*it).host_name (), " has been resolved to ", address); + auto addr = peer.router->GetSSUAddress (!context.SupportsV6 ());; + if (addr) + { + m_SSUServer->CreateSession (peer.router, address, addr->port); + return; + } + } + LogPrint (eLogError, "Unable to resolve SSU address: ", ecode.message ()); + m_Peers.erase (it1); + } + } + void Transports::CloseSession (std::shared_ptr router) { if (!router) return; diff --git a/Transports.h b/Transports.h index 63506c13..c94ae650 100644 --- a/Transports.h +++ b/Transports.h @@ -114,6 +114,9 @@ namespace transport void NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident); void HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, i2p::data::IdentHash ident, std::shared_ptr resolver); + void SSUResolve (const std::string& addr, const i2p::data::IdentHash& ident); + void HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, + i2p::data::IdentHash ident, std::shared_ptr resolver); void UpdateBandwidth (); void DetectExternalIP (); From e5c72cae836bda0deb98be547e3d15968804440a Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 25 Nov 2015 14:06:26 -0600 Subject: [PATCH 0609/6300] Fix CMake stuff for msys2 --- build/CMakeLists.txt | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index c910f8f6..46213363 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -1,4 +1,6 @@ cmake_minimum_required ( VERSION 2.8.12 ) +# this addresses CMP0059 with CMake > 3.3 for PCH flags +cmake_policy( VERSION 2.8.12 ) project ( "i2pd" ) # configurale options @@ -46,7 +48,7 @@ set (LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/api.cpp" ) -if (CMAKE_SYSTEM_NAME STREQUAL "Windows") +if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR MSYS) list (APPEND LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/I2PEndian.cpp") endif () @@ -75,7 +77,7 @@ set (DAEMON_SRC if (WITH_UPNP) add_definitions(-DUSE_UPNP) - if (NOT MSVC) + if (NOT MSVC AND NOT MSYS) set(DL_LIB ${CMAKE_DL_LIBS}) endif () endif () @@ -110,6 +112,7 @@ elseif (NOT MSVC) endif () if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe") if (WITH_HARDENING) add_definitions( "-D_FORTIFY_SOURCE=2" ) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat -Wformat-security -Werror=format-security" ) @@ -132,7 +135,7 @@ elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonLinux.cpp") elseif (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonLinux.cpp") -elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") +elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR MSYS) list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonWin32.cpp") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32Service.cpp") endif () @@ -153,7 +156,7 @@ endif() if (WITH_STATIC) set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_RUNTIME ON) - if (WIN32) + if (WIN32 OR MSYS) # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace # Note that you might need to rebuild Crypto++ foreach(flag_var @@ -173,7 +176,7 @@ if (WITH_STATIC) set( CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,-u,pthread_create,-u,pthread_once,-u,pthread_mutex_lock,-u,pthread_mutex_unlock,-u,pthread_join,-u,pthread_equal,-u,pthread_detach,-u,pthread_cond_wait,-u,pthread_cond_signal,-u,pthread_cond_destroy,-u,pthread_cond_broadcast,-u,pthread_cancel" ) endif () else() - if (NOT WIN32) + if (NOT WIN32 AND NOT MSYS) # TODO: Consider separate compilation for LIBI2PD_SRC for library. # No need in -fPIC overhead for binary if not interested in library # HINT: revert c266cff CMakeLists.txt: compilation speed up @@ -206,9 +209,10 @@ if (WITH_PCH) target_compile_options(i2pdclient PRIVATE -include stdafx.h) endif() target_link_libraries(libi2pd stdafx) - target_link_libraries(i2pdclient stdafx) endif() +target_link_libraries(i2pdclient libi2pd) + find_package ( Boost COMPONENTS system filesystem regex program_options date_time thread chrono REQUIRED ) if(NOT DEFINED Boost_INCLUDE_DIRS) message(SEND_ERROR "Boost is not found, or your boost version was bellow 1.46. Please download Boost!") @@ -294,7 +298,11 @@ if (WITH_BINARY) if(${LAST_Boost_LIBRARIES} MATCHES ".*pthread.*") list(REMOVE_AT Boost_LIBRARIES -1) endif() - target_link_libraries( "${PROJECT_NAME}" libi2pd i2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ) + + if (MSYS OR MINGW) + set (MINGW_EXTRA -lws2_32 -lmswsock -liphlpapi ) + endif () + target_link_libraries( "${PROJECT_NAME}" libi2pd i2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) if (MSVC) From 76549d0a4a1b146c037de62ac2dbf7cfde006540 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 25 Nov 2015 14:14:26 -0600 Subject: [PATCH 0610/6300] Fix win32 resource compilation with msys squash! Fix win32 resource winres.h is missing for mingw --- Win32/Resource.rc | Bin 3294 -> 2602 bytes Win32/resource.h | Bin 904 -> 451 bytes Win32/winres.h | 6 ++++++ build/CMakeLists.txt | 8 +++++--- 4 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 Win32/winres.h diff --git a/Win32/Resource.rc b/Win32/Resource.rc index a4fa890b129dafe6d1ed2497070a94f247581074..e8b77bef01dc1ac2e75324d75bcbfde476e782d3 100644 GIT binary patch literal 2602 zcmcImOK+n{5WZXTf2bvwM8jf?Glv{8U@r;*%NT1SMMAyMwtIye)U+oXX@2}xW6a>J zH?s$)4;EdouKMcb(FmScCd#_pNLaJ_Knifae1R?HR1isX5VS52BBM}ef>qKO4o0Kl z;D+(6IOG(x6W`cr!vQjKx?!Axy@*$d>qqI*v)w2Rp40gGX&MEw(!b(4x-%NVyz_V? z%00*(g_ZYh<%dp?xE@$tApeTsm2nKB_5X^lH8>qO0`d_WpMVE=d|R-32e%1lDu=Pe zRMvML1a*@swt?S{*~)M2**DBo93@M~OWh#2gC9Sj&s^EddFqA9-Ea`t;XJYD9;gqb z%>zIFLx(t-_6cc%slKTM+3pEsWlqzIZ0YUT`f8yKRY99GWc+X)J$mjZz@bd`YvEWj z!@;;ugyCT7&HZpV06e{_elfAGy$~H64y+F}es+Y`6_oZOUO1{ZCi{ja9~k;Cog2z? zOc@>wr)#ecHs#-yi&qsezU5~nKVjBh;g!i96y+L5wy+fUgz*|HgV(HkYTz@?H2NGs)>PCO%n6fqoUXlP>_=hhhqDOUi~7dY&-}n^DPZYi-PBE_E|!s- zII9!G)E{(f(r}dgEU@SClN~>T`S)ZKbj^~w-ZYuFMjIg%7%M(4YWj|DHgr`pEhCCi z`rb6nd*n2Jt)3TNYoWPg3%4Yt-0O#F5IK*a#ZoZ7ow0)Ye4`LnsQ1L0>rvFaHy_OV zKUMQ6sygok6=`h;i3gff?kmFILb9it4%*@%s)B-VEqdBLapizZkNXJopj6b(LV3su z-6Q4aqwmz()F~Cq7#pV1cd#exb4dj!#mS{*y9EuAT{a%jEh(I`dJ}BBlZ{KK3*0() zV435|@A&gohZU}-V~EP*g(&}`nY?T@LTe1&P!aPM9cESeWoa~(A2NCJ`ByNu;yp-A z9w}2OY+isk-1~wjHl$6YhWPuq$`B onxIXqov-X}!e05gcDnxJf6Js>+BguE$|ubEGu(M8FK(BA004PvJ^%m! literal 3294 zcmds(ZEq4m5Xa}WiQi#`H*GYa_7ju`2`QzfK&uG}At03q9O1ym#IM`0q5fu;E!T4A z1N!1(Hg~f(H@iCz|Cycd-^x-JC+9Mik*p+=m}equcvtXZ*~n04d{yN+!^w?&kOj{Q z{u(-lw}R@r4YLt*$CvIll_fGIc`8phrEIhacfsEo(qoe??5!`!M=6-Jt}L3|#MbHF zr}9=h(v_anq$w9NkV~n^=+|!Si9? ze9@qtBhC?#y~WlWqBX?s7uZ~Z`w3*+S)LxmXac=7>9CAk5Jx~DKdOIYmP(k{9_u={*VW(x5 zMkfbq^rz<(txAv4SSybo8nv`4w2!tBqhy@(HCGK)qd*;5Iil7q?m0;tc7L5kQEw?n zMT)Fm#nw?Y!Htv8)=_Ni4qGBIw4Oox2)*N1^bXp6tXb*I!`g!=A{>%S#tf+u^EFhp zarT|m%in)GETI`dN#uTS+C(84UWx(9ca_`bBCaGkDEnM zSvNlZK#q2p#}+qZdzt&*szap>0wYYaMDPfnpD2ucZ?WR|J%Lj-b3egn)6Zx@4I>!* zg5mgq?PM3ud28i3i1$TV5hf(#wRojuS(v*`PGcf?dXU8-dl6}I%O#y!a{5-voED`sn6mFlviO8~;lY16+VR)%=?R@ig#miiz4|wzs?LyK=Yk=j4R%kk{+!KHI EFDV~;hX4Qo literal 904 zcmb7?-Acni5QWdR;5#hzQm|Hhf!gGv*rw7y^eWicNTpb48h;R9UHxX)(pGA)44c^* zX3w0NldsRZ>Uzf#^^_`7tZPoM#N;R8RCqO`phml^|ul*8G~0`I<*F zEbHgs9dS}li^&o-$Q@!0o$eEw=Ci?-}(0&#B4w2O4VR*_$G5x{jIP3LPTvu&tQ$C_Y~8f)!KTj^V=`l^I>a$k?V9cD{NKo?Nf&FLs;9qRjeJThhc|Jnikc#ww=vCR6~7J%#;h;-;KWkR}+1N diff --git a/Win32/winres.h b/Win32/winres.h new file mode 100644 index 00000000..e9afee91 --- /dev/null +++ b/Win32/winres.h @@ -0,0 +1,6 @@ +#ifndef WINRES_H__ +#define WINRES_H__ + +#include + +#endif diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 46213363..1a378cbf 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -104,9 +104,9 @@ include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG("-std=c++11" CXX11_SUPPORTED) CHECK_CXX_COMPILER_FLAG("-std=c++0x" CXX0X_SUPPORTED) if (CXX11_SUPPORTED) - add_definitions( "-std=c++11" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11" ) elseif (CXX0X_SUPPORTED) # gcc 4.6 - add_definitions( "-std=c++0x" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x" ) elseif (NOT MSVC) message(SEND_ERROR "C++11 standart not seems to be supported by compiler. Too old version?") endif () @@ -138,10 +138,12 @@ elseif (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR MSYS) list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonWin32.cpp") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32Service.cpp") + list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Resource.rc") endif () if (WITH_AESNI) - add_definitions ( "-maes -DAESNI" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes" ) + add_definitions ( -DAESNI ) endif() # libraries From b2a6c1bc68ac75a02c7f208a6733fcd1e0dba1e9 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 25 Nov 2015 16:11:43 -0600 Subject: [PATCH 0611/6300] fixup! read Content-Length from http header MSVC++ complains on ssize_t --- I2PControl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index d04b91d1..6f2c21ba 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -201,7 +201,7 @@ namespace client LogPrint (eLogError, "Malformed I2PControl request. HTTP header expected"); return; // TODO: } - ssize_t rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read + std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read if (rem > 0) { bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), rem)); From ff356b1f21e13b59e3e02f0938ab8a68e207c659 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 25 Nov 2015 17:42:44 -0600 Subject: [PATCH 0612/6300] Use assembly language when building zlib for MSVC++ --- build/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 1a378cbf..5cd3b55d 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -240,6 +240,7 @@ if (NOT ZLIB_FOUND ) URL http://zlib.net/zlib-1.2.8.tar.gz PREFIX ${CMAKE_CURRENT_BINARY_DIR}/zlib CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= + -DAMD64=ON -DASM686=ON "-DCMAKE_ASM_MASM_FLAGS=/W0 /safeseh" ) if (WITH_PCH) add_dependencies( stdafx zlib-project ) From 759dfb28ce3d270ac5fa6d2ec2c709048535d9f3 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Tue, 1 Dec 2015 19:02:20 -0600 Subject: [PATCH 0613/6300] Increase PCH heap limit for MSVC --- build/CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 5cd3b55d..0843f82b 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -191,21 +191,21 @@ if (WITH_PCH) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) add_library(stdafx STATIC "${CMAKE_SOURCE_DIR}/stdafx.cpp") if(MSVC) - target_compile_options(stdafx PRIVATE /Ycstdafx.h /Zm135) + target_compile_options(stdafx PRIVATE /Ycstdafx.h /Zm155) add_custom_command(TARGET stdafx POST_BUILD COMMAND xcopy /y stdafx.dir\\$\\*.pdb libi2pd.dir\\$\\ COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pdclient.dir\\$\\ COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pd.dir\\$\\ WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) - target_compile_options(libi2pd PRIVATE /FIstdafx.h /Yustdafx.h /Zm135 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") - target_compile_options(i2pdclient PRIVATE /FIstdafx.h /Yustdafx.h /Zm135 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") + target_compile_options(libi2pd PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") + target_compile_options(i2pdclient PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") else() string(TOUPPER ${CMAKE_BUILD_TYPE} BTU) get_directory_property(DEFS DEFINITIONS) string(REPLACE " " ";" FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BTU}} ${DEFS}") add_custom_command(TARGET stdafx PRE_BUILD - COMMAND ${CMAKE_CXX_COMPILER} ${FLAGS} -c ${CMAKE_CURRENT_SOURCE_DIR}/../stdafx.h + COMMAND ${CMAKE_CXX_COMPILER} ${FLAGS} -c ${CMAKE_CURRENT_SOURCE_DIR}/../stdafx.h -o ${CMAKE_BINARY_DIR}/stdafx.h.gch ) target_compile_options(libi2pd PRIVATE -include stdafx.h) target_compile_options(i2pdclient PRIVATE -include stdafx.h) @@ -286,7 +286,7 @@ if (WITH_BINARY) if (WITH_PCH) if (MSVC) - target_compile_options("${PROJECT_NAME}" PRIVATE /FIstdafx.h /Yustdafx.h /Zm135 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") + target_compile_options("${PROJECT_NAME}" PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") else() target_compile_options("${PROJECT_NAME}" PRIVATE -include stdafx.h) endif() From 9d70851eb9e981c33f193d350eef3b2e3e0e8456 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Thu, 3 Dec 2015 23:23:26 -0600 Subject: [PATCH 0614/6300] Respect static for zlib with CMake --- build/CMakeLists.txt | 13 +++++++++++-- build/cmake-zlib-static.patch | 28 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 build/cmake-zlib-static.patch diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 0843f82b..43d8348f 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -238,8 +238,12 @@ if (NOT ZLIB_FOUND ) include( ExternalProject ) ExternalProject_Add(zlib-project URL http://zlib.net/zlib-1.2.8.tar.gz + URL_MD5 44d667c142d7cda120332623eab69f40 PREFIX ${CMAKE_CURRENT_BINARY_DIR}/zlib + # patch on Windows might be found in C:/Program Files/Git/usr/bin + PATCH_COMMAND patch -p0 < ${CMAKE_CURRENT_SOURCE_DIR}/cmake-zlib-static.patch CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= + -DWITH_STATIC=${WITH_STATIC} -DAMD64=ON -DASM686=ON "-DCMAKE_ASM_MASM_FLAGS=/W0 /safeseh" ) if (WITH_PCH) @@ -249,9 +253,14 @@ if (NOT ZLIB_FOUND ) endif () # ExternalProject_Get_Property(zlib-project install_dir) set ( ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/zlib/include" CACHE FILEPATH "zlib include dir" FORCE) - set ( ZLIB_LIBRARY debug zlibd optimized zlib CACHE STRING "zlib libraries" FORCE) + if (NOT WITH_STATIC) + set ( ZLIB_LIBRARY debug zlibd optimized zlib CACHE STRING "zlib libraries" FORCE) + endif () endif () -link_directories("${CMAKE_CURRENT_BINARY_DIR}/zlib/lib") +if (WITH_STATIC AND (MSVC OR MSYS)) + set ( ZLIB_LIBRARY debug zlibstaticd optimized zlibstatic CACHE STRING "zlib libraries" FORCE) +endif () +link_directories(${CMAKE_CURRENT_BINARY_DIR}/zlib/lib ${ZLIB_ROOT}/lib) # load includes include_directories( ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ) diff --git a/build/cmake-zlib-static.patch b/build/cmake-zlib-static.patch new file mode 100644 index 00000000..68f1400e --- /dev/null +++ b/build/cmake-zlib-static.patch @@ -0,0 +1,28 @@ +--- CMakeLists.txt.orig 2013-04-28 17:57:10.000000000 -0500 ++++ CMakeLists.txt 2015-12-03 12:53:52.371087900 -0600 +@@ -7,6 +7,7 @@ + + option(ASM686 "Enable building i686 assembly implementation") + option(AMD64 "Enable building amd64 assembly implementation") ++option(WITH_STATIC "Static runtime on Windows" OFF) + + set(INSTALL_BIN_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Installation directory for executables") + set(INSTALL_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Installation directory for libraries") +@@ -66,6 +67,17 @@ + include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + endif() + ++if(WITH_STATIC AND (MSVC OR MSYS)) ++ # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace ++ foreach(flag_var ++ CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE ++ CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) ++ if(${flag_var} MATCHES "/MD") ++ string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") ++ endif(${flag_var} MATCHES "/MD") ++ endforeach(flag_var) ++endif() ++ + if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + # If we're doing an out of source build and the user has a zconf.h + # in their source tree... From 23b8a60242a08f33c845258312d2f38251550451 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Thu, 3 Dec 2015 23:39:09 -0600 Subject: [PATCH 0615/6300] Appveyor status badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4c3de21f..b936cc8d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ i2pd ==== +[![Build status](https://ci.appveyor.com/api/projects/status/458fhbki14gaplyj/branch/openssl?svg=true)](https://ci.appveyor.com/project/mlt/i2pd/branch/openssl) + I2P router written in C++ Contains all ongoing changes from https://bitbucket.org/orignal/i2pd/src From c82ef1ee8ff8de8701ebd80d2aeda01c2a1fa85e Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 4 Dec 2015 13:19:08 -0500 Subject: [PATCH 0616/6300] link against openssl for Mac OS X --- Makefile.osx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile.osx b/Makefile.osx index 7af2247b..809a7ccc 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -1,9 +1,9 @@ CXX = clang++ -CXXFLAGS = -g -Wall -std=c++11 -DCRYPTOPP_DISABLE_ASM -DMAC_OSX -#CXXFLAGS = -g -O2 -Wall -std=c++11 -DCRYPTOPP_DISABLE_ASM +CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX +#CXXFLAGS = -g -O2 -Wall -std=c++11 INCFLAGS = -I/usr/local/include LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -LDLIBS = -lcryptopp -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread +LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread ifeq ($(USE_UPNP),1) LDFLAGS += -ldl From ef3ec33ba31d2270b0562e85aba9029d238ee5ee Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 4 Dec 2015 14:06:37 -0500 Subject: [PATCH 0617/6300] create all subdirectories for non-case sensitive systems --- NetDb.cpp | 15 +++++++-------- Profiling.cpp | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index bf9a64f0..d1f8ef14 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -250,21 +250,20 @@ namespace data LogPrint (directory.string(), " doesn't exist, trying to create it."); if (!boost::filesystem::create_directory (directory)) { - LogPrint("Failed to create directory ", directory.string()); + LogPrint (eLogError, "Failed to create directory ", directory); return false; } // list of chars might appear in base64 string const char * chars = GetBase64SubstitutionTable (); // 64 bytes - boost::filesystem::path suffix; for (int i = 0; i < 64; i++) { -#ifndef _WIN32 - suffix = std::string ("/r") + chars[i]; -#else - suffix = std::string ("\\r") + chars[i]; -#endif - if (!boost::filesystem::create_directory( boost::filesystem::path (directory / suffix) )) return false; + auto p = directory / (std::string ("r") + chars[i]); + if (!boost::filesystem::exists (p) && !boost::filesystem::create_directory (p)) + { + LogPrint (eLogError, "Failed to create directory ", p); + return false; + } } return true; } diff --git a/Profiling.cpp b/Profiling.cpp index 8b1a6bc3..9bfd4de9 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -57,7 +57,7 @@ namespace data for (int i = 0; i < 64; i++) { auto path1 = path / (std::string ("p") + chars[i]); - if (!boost::filesystem::create_directory (path1)) + if (!boost::filesystem::exists (path1) && !boost::filesystem::create_directory (path1)) { LogPrint (eLogError, "Failed to create directory ", path1); return; From 1ef12f0645352d62e157981c1ccbfb06f7dc4123 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 4 Dec 2015 14:59:31 -0500 Subject: [PATCH 0618/6300] updated reseeders list --- Reseed.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Reseed.cpp b/Reseed.cpp index 2c97cbd3..1bbd2750 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -30,6 +30,7 @@ namespace data "https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required "https://us.reseed.i2p2.no:444/", "https://uk.reseed.i2p2.no:444/", + "https://www.torontocrypto.org:8443/" "https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support "https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support "https://ieb9oopo.mooo.com/" // Only HTTPS and SU3 (v3) support From c6a6035bb9ef8622282054dc795c5d74562582e8 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 30 Nov 2015 06:23:29 +0000 Subject: [PATCH 0619/6300] * debian/control : compat level -> 9 --- debian/compat | 2 +- debian/control | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/compat b/debian/compat index 45a4fb75..ec635144 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -8 +9 diff --git a/debian/control b/debian/control index 50967142..add1060f 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: i2pd Section: net Priority: extra Maintainer: hagen -Build-Depends: debhelper (>= 8.0.0), dpkg-dev (>= 1.16.1~), +Build-Depends: debhelper (>= 9.0.0), dpkg-dev (>= 1.16.1~), gcc (>= 4.6) | clang (>= 3.3), libboost-regex-dev, libboost-system-dev (>= 1.46), From b87f986a49737da86284591980446a044f50c2bc Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 5 Dec 2015 10:16:09 +0000 Subject: [PATCH 0620/6300] * added manpage (thanks to kytv) --- debian/i2pd.1 | 119 +++++++++++++++++++++++++++++++++++++++++++ debian/i2pd.manpages | 1 + 2 files changed, 120 insertions(+) create mode 100644 debian/i2pd.1 create mode 100644 debian/i2pd.manpages diff --git a/debian/i2pd.1 b/debian/i2pd.1 new file mode 100644 index 00000000..b2e83c83 --- /dev/null +++ b/debian/i2pd.1 @@ -0,0 +1,119 @@ +.TH I2PD "1" "March 31, 2015" + +.SH NAME +i2pd \- Load-balanced unspoofable packet switching network + +.SH SYNOPSIS +.B i2pd +[\fIOPTION1\fR) [\fIOPTION2\fR]... + +.SH DESCRIPTION +i2pd +is a C++ implementation of the router for the I2P anonymizing network, offering +a simple layer that identity-sensitive applications can use to securely +communicate. All data is wrapped with several layers of encryption, and the +network is both distributed and dynamic, with no trusted parties. + +.PP +Any of the configuration options below can be used in the \fBDAEMON_ARGS\fR variable in \fI/etc/default/i2pd\fR. +.BR +.TP +\fB\-\-host=\fR +The external IP (deprecated) +.TP +\fB\-\-port=\fR +The external port to listen on +.TP +\fB\-\-httpport=\fR +The HTTP port to listen on +.TP +\fB\-\-log=\fR[\fI1\fR|\fI0\fR] +.br +Enable of disable logging to a file. \fI1\fR for yes, \fI0\fR for no. (default: \fI0\fR, off) +.TP +\fB\-\-daemon=\fR[\fI1\fR|\fI0\fR] +Enable or disable daemon mode. Daemon mode is enabled with \fI1\fR and disabled with \fI0\fR. (default: \fI0\fR, off) +.TP +\fB\-\-service=\fR[\fI1\fR|\fI0\fR] +If enabled, system folders (\fB/var/run/i2pd.pid\fR, \fB/var/log/i2pd.log\fR, \fB/var/lib/i2pd\fR) will be used. If off, \fB$HOME/.i2pd\fR will be used instead. (default: \fI0\fR, off). +.TP +\fB\-\-unreachable=\fR[\fI1\fR|\fI0\fR] +\fI1\fR if router is declared as unreachable and works through introducers. (default: \fI0\fR, off) +.TP +\fB\-\-v6=\fR[\fI1\fR|\fI0\fR] +\fI1\fR if \fBi2pd\fR should communicate via IPv6. (default: \fI0\fR, off) +.TP +\fB\-\-floodfill=\fR[\fI1\fR|\fI0\fR] +\fI1\fR if \fBi2pd\fR should become a floodfill. (default: \fI0\fR, off) +.TP +\fB\-\-bandwidth=\fR[\fI1\fR|\fI0\fR] +\fIL\fR if \fBi2pd\fR should be limited to 32KiB/s. Enabling floodfill will automatically set this to \fI0\fR (default: \fI0\fR, no limit) +.TP +\fB\-\-httpproxyport=\fR +The local port for the HTTP Proxy to listen on (default: \fI4446\fR) +.TP +\fB\-\-socksproxyport=\fR +The local port for the SOCKS proxy to listen on (default: \fI4447\fR) +.TP +\fB\-\-proxykeys=\fR +An optional keys file for tunnel's local destination +.TP +\fB\-\-ircport=\fR +The local port of IRC tunnel to listen on. (default: \fI6668\fR) +.TP +\fB\-\-ircdest=\fR +I2P destination address of an IRC server to connect to, e.g. \fIirc.postman.i2p\fR +.TP +\fB\-\-irckeys=\fR +optional keys file for local destination +.TP +\fB\-\-eepkeys=\fR +File name containing destination keys. For example \fIprivKeys.dat\fR +.TP +\fB\-\-eephost=\fR +Address incoming trafic is forwarded to, \fI127.0.0.1\fR by default +.TP +\fB\-\-eepport=\fR +Port incoming trafic forward to. \fI80\fR by default +.TP +\fB\-\-samport=\fR +Port of SAM bridge. Usually \fI7656\fR. SAM will not be enabled if this is not set. (default: unset) +.TP +\fB\-\-bobport=\fR +Port of BOB command channel. Usually \fI2827\fR. BOB will not be enabled if this is not set. (default: unset) +.TP +\fB\-\-i2pcontrolport=\fR +Port of I2P control service. Usually \fI7650\fR. I2PControl will not be enabled if this is not set. (default: unset) +.TP +\fB\-\-conf=\fR +Config file (default: \fI~/.i2pd/i2p.conf\fR or \fI/var/lib/i2pd/i2p.conf\fR) +This parameter will be silently ignored if the specified config file does not exist. +Options specified on the command line take precedence over those in the config file. + +.SH FILES +.PP +/etc/i2pd/i2pd.conf, /etc/i2pd/tunnels.conf, /etc/default/i2pd +.RS 4 +i2pd configuration files (when running as a system service) + +.RE +.PP +/var/lib/i2pd/ +.RS 4 +i2pd profile directory (when running as a system service, see \fB\-\-service=\fR above) +.RE +.PP +$HOME/.i2pd +.RS 4 +i2pd profile directory (when running as a normal user) +.RE +.PP +/usr/share/doc/i2pd/examples/hosts.txt.gz +.RS 4 +default I2P hosts file +.SH AUTHOR +This manual page was written by kytv for the Debian system (but may be used by others). +.BR +Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License, Version 2 or any later version published by the Free Software Foundation +.BR +On Debian systems, the complete text of the GNU General Public License can be found in /usr/share/common-licenses/GPL diff --git a/debian/i2pd.manpages b/debian/i2pd.manpages new file mode 100644 index 00000000..1de1d6a4 --- /dev/null +++ b/debian/i2pd.manpages @@ -0,0 +1 @@ +debian/i2pd.1 From 0c87dd5624675411c0d1f739200abe47f3b7d7fb Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 5 Dec 2015 11:22:18 +0000 Subject: [PATCH 0621/6300] * added debian/logrotate (thanks to kytv) --- debian/logrotate | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 debian/logrotate diff --git a/debian/logrotate b/debian/logrotate new file mode 100644 index 00000000..a3fed79a --- /dev/null +++ b/debian/logrotate @@ -0,0 +1,13 @@ +/var/log/i2pd.log { + rotate 4 + weekly + missingok + notifempty + compress + delaycompress + copytruncate + create 640 i2pd adm + postrotate + /etc/init.d/i2pd restart >/dev/null + endscript +} From 58124ebaab8b8960ea3dbdb8bd7d489d555ec8de Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 5 Dec 2015 08:40:24 +0000 Subject: [PATCH 0622/6300] * update debian/docs --- debian/docs | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/docs b/debian/docs index b43bf86b..ccf51f76 100644 --- a/debian/docs +++ b/debian/docs @@ -1 +1,2 @@ README.md +docs/configuration.md From 045558bedec17618be265a5a3522c8edf8898b3b Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 6 Dec 2015 22:48:08 -0500 Subject: [PATCH 0623/6300] correct path to openssl --- Makefile.osx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.osx b/Makefile.osx index 809a7ccc..71f95a7f 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -1,8 +1,8 @@ CXX = clang++ CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX #CXXFLAGS = -g -O2 -Wall -std=c++11 -INCFLAGS = -I/usr/local/include -LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib +INCFLAGS = -I/usr/local/include -I/usr/local/ssl/include +LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/usr/local/ssl/lib LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread ifeq ($(USE_UPNP),1) From 8f9cea54c58294ca99d1d80c20bfd65a04cb155a Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 5 Dec 2015 08:16:25 +0000 Subject: [PATCH 0624/6300] * rename main binary --- Makefile | 2 +- build/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2ed1668c..2e86fd88 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ SHLIB := libi2pd.so ARLIB := libi2pd.a SHLIB_CLIENT := libi2pdclient.so ARLIB_CLIENT := libi2pdclient.a -I2PD := i2p +I2PD := i2pd GREP := fgrep DEPS := obj/make.dep diff --git a/build/Dockerfile b/build/Dockerfile index 5c48930b..f570bd79 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -8,4 +8,4 @@ RUN git clone https://github.com/PurpleI2P/i2pd.git WORKDIR /i2pd RUN make -CMD ./i2p +CMD ./i2pd From a96b7d2a8083187c252dc9adee42f060ee0a4c24 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 5 Dec 2015 08:17:13 +0000 Subject: [PATCH 0625/6300] * drop patch not needed anymore --- debian/patches/rename-binary.patch | 13 ------------- debian/patches/series | 1 - 2 files changed, 14 deletions(-) delete mode 100644 debian/patches/rename-binary.patch delete mode 100644 debian/patches/series diff --git a/debian/patches/rename-binary.patch b/debian/patches/rename-binary.patch deleted file mode 100644 index 7b55e474..00000000 --- a/debian/patches/rename-binary.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/Makefile b/Makefile -index f65d7a1..6f998bf 100644 ---- a/Makefile -+++ b/Makefile -@@ -3,7 +3,7 @@ SHLIB := libi2pd.so - ARLIB := libi2pd.a - SHLIB_CLIENT := libi2pdclient.so - ARLIB_CLIENT := libi2pdclient.a --I2PD := i2p -+I2PD := i2pd - GREP := fgrep - DEPS := obj/make.dep - diff --git a/debian/patches/series b/debian/patches/series deleted file mode 100644 index 714c2e62..00000000 --- a/debian/patches/series +++ /dev/null @@ -1 +0,0 @@ -rename-binary.patch From 91aa2d7f6f8b7f738b0839d0558c4fc65aaaa86b Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 5 Dec 2015 10:20:21 +0000 Subject: [PATCH 0626/6300] + add example config files --- debian/i2pd.conf | 20 ++++++++++++++++++++ debian/i2pd.dirs | 2 ++ debian/i2pd.install | 2 ++ debian/i2pd.links | 2 ++ debian/tunnels.conf | 1 + 5 files changed, 27 insertions(+) create mode 100644 debian/i2pd.conf create mode 100644 debian/i2pd.dirs create mode 100644 debian/i2pd.links create mode 100644 debian/tunnels.conf diff --git a/debian/i2pd.conf b/debian/i2pd.conf new file mode 100644 index 00000000..c32716ce --- /dev/null +++ b/debian/i2pd.conf @@ -0,0 +1,20 @@ +floodfill=0 +v6=0 + +httpproxyaddress=127.0.0.1 +httpproxyport=4444 + +ircaddress=127.0.0.1 +ircport=6668 +ircdest=irc.postman.i2p + +; other services (disabled by default) +; +; samaddress=127.0.0.1 +; samport=7656 +; +; bobaddress=127.0.0.1 +; bobport=2827 +; +; i2pcontroladdress=127.0.0.1 +; i2pcontrolport=7650 diff --git a/debian/i2pd.dirs b/debian/i2pd.dirs new file mode 100644 index 00000000..3b643352 --- /dev/null +++ b/debian/i2pd.dirs @@ -0,0 +1,2 @@ +etc/i2pd +var/lib/i2pd diff --git a/debian/i2pd.install b/debian/i2pd.install index 66af1abd..fa79ce1b 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1 +1,3 @@ i2pd usr/sbin/ +debian/i2pd.conf etc/i2pd/ +debian/tunnels.conf etc/i2pd/ diff --git a/debian/i2pd.links b/debian/i2pd.links new file mode 100644 index 00000000..259219e7 --- /dev/null +++ b/debian/i2pd.links @@ -0,0 +1,2 @@ +etc/i2pd/i2pd.conf var/lib/i2pd/i2p.conf +etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.cfg diff --git a/debian/tunnels.conf b/debian/tunnels.conf new file mode 100644 index 00000000..43cb495a --- /dev/null +++ b/debian/tunnels.conf @@ -0,0 +1 @@ +; see examples in /usr/share/doc/i2pd/configuration.md.gz From 45fd95e02b7340839403767ac511167c863255e9 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 5 Dec 2015 08:41:26 +0000 Subject: [PATCH 0627/6300] * update default/i2pd and traditional init script --- debian/i2pd.default | 8 ++++++-- debian/i2pd.init | 13 +++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/debian/i2pd.default b/debian/i2pd.default index 3919c61e..bf6eb005 100644 --- a/debian/i2pd.default +++ b/debian/i2pd.default @@ -1,7 +1,11 @@ # Defaults for i2pd initscript # sourced by /etc/init.d/i2pd # installed at /etc/default/i2pd by the maintainer scripts +I2PD_ENABLED="yes" + +# port to listen for incoming connections +I2PD_PORT="4567" # Additional options that are passed to the Daemon. -DAEMON_OPTS="--host=1.2.3.4 --port=4567 --ircdest=irc.postman.i2p" -# change ip and port above to your external address and port +# see possible switches in /usr/share/doc/i2pd/configuration.md.gz +DAEMON_OPTS="" diff --git a/debian/i2pd.init b/debian/i2pd.init index eda135c6..ed061b04 100644 --- a/debian/i2pd.init +++ b/debian/i2pd.init @@ -13,10 +13,12 @@ PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC=i2pd # Introduce a short description here NAME=i2pd # Introduce the short server's name here -DAEMON=/usr/sbin/i2pd # Introduce the server's location here +DAEMON=/usr/sbin/$NAME # Introduce the server's location here DAEMON_OPTS="" # Arguments to run the daemon with PIDFILE=/var/run/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME +I2PCONF=/etc/$NAME/i2pd.conf +TUNCONF=/etc/$NAME/tunnels.conf # Exit if the package is not installed [ -x $DAEMON ] || exit 0 @@ -32,10 +34,17 @@ do_start() # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started + + if [ "x$I2PD_ENABLED" != "xyes" ]; then + log_warning_msg "disabled in config" + return 2 + fi + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ - --daemon=1 --log=1 $DAEMON_OPTS \ + --service=1 --daemon=1 --log=1 --conf=$I2PCONF --tunnelscfg=$TUNCONF \ + --port=$I2PD_PORT $DAEMON_OPTS \ || return 2 } From 0dda4728b6d08769a20f0a0968d7c6c0fb08285e Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 8 Dec 2015 12:16:36 +0000 Subject: [PATCH 0628/6300] * update README --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b936cc8d..e467c732 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ i2pd ==== -[![Build status](https://ci.appveyor.com/api/projects/status/458fhbki14gaplyj/branch/openssl?svg=true)](https://ci.appveyor.com/project/mlt/i2pd/branch/openssl) - I2P router written in C++ Contains all ongoing changes from https://bitbucket.org/orignal/i2pd/src @@ -27,14 +25,14 @@ http://i2pd.website/releases/ older releases http://download.i2p.io/purplei2p/i2pd/releases/ +Supported OS +------------ -Build Statuses ---------------- +[![Build status](https://ci.appveyor.com/api/projects/status/458fhbki14gaplyj/branch/openssl?svg=true)](https://ci.appveyor.com/project/mlt/i2pd/branch/openssl) -- Linux x64 - [![Build Status](https://jenkins.greyhat.no/buildStatus/icon?job=i2pd-linux)](https://jenkins.nordcloud.no/job/i2pd-linux/) -- Linux ARM - To be added -- Mac OS X - Got it working, but not well tested. (Only works with clang, not GCC.) -- Microsoft VC13 - To be added +-- Linux x86/x64 - Proved working. +-- Mac OS X - Not well tested. (Only works with clang, not GCC) +-- Windows - At least builds and runs. More documentation ------------------ From f122da14858619092ecca95018aa5b4163aee460 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 8 Dec 2015 10:40:43 -0500 Subject: [PATCH 0629/6300] change and save I2PControl password --- I2PControl.cpp | 61 +++++++++++++++++++++++++++++++++++++++++++++++++- I2PControl.h | 5 +++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 6f2c21ba..5b262e54 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #if !GCC47_BOOST149 #include #endif @@ -30,6 +31,7 @@ namespace client m_SSLContext (m_Service, boost::asio::ssl::context::sslv23), m_ShutdownTimer (m_Service) { + LoadConfig (); // certificate auto path = GetPath (); if (!boost::filesystem::exists (path)) @@ -56,6 +58,9 @@ namespace client m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlService::RouterManagerHandler; m_MethodHandlers[I2P_CONTROL_METHOD_NETWORK_SETTING] = &I2PControlService::NetworkSettingHandler; + // I2PControl + m_I2PControlHandlers[I2P_CONTROL_I2PCONTROL_PASSWORD] = &I2PControlService::PasswordHandler; + // RouterInfo m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_UPTIME] = &I2PControlService::UptimeHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_VERSION] = &I2PControlService::VersionHandler; @@ -78,6 +83,49 @@ namespace client Stop (); } + void I2PControlService::LoadConfig () + { + auto path = GetPath (); + if (!boost::filesystem::exists (path)) + { + if (!boost::filesystem::create_directory (path)) + LogPrint (eLogError, "Failed to create i2pcontrol directory"); + } + boost::property_tree::ptree pt; + auto filename = path / I2P_CONTROL_CONFIG_FILE; + bool isNew = true; + if (boost::filesystem::exists (filename)) + { + try + { + boost::property_tree::read_ini (filename.string (), pt); + isNew = false; + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Can't read ", filename, ": ", ex.what ()); + } + } + m_Password = pt.get (I2P_CONTROL_I2PCONTROL_PASSWORD, I2P_CONTROL_DEFAULT_PASSWORD); + if (isNew) SaveConfig (); + } + + void I2PControlService::SaveConfig () + { + boost::property_tree::ptree pt; + pt.put (I2P_CONTROL_I2PCONTROL_PASSWORD, m_Password); + auto filename = GetPath () / I2P_CONTROL_CONFIG_FILE; + // we take care about directory in LoadConfig + try + { + boost::property_tree::write_ini (filename.string (), pt); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Can't write ", filename, ": ", ex.what ()); + } + } + void I2PControlService::Start () { if (!m_IsRunning) @@ -326,12 +374,23 @@ namespace client LogPrint (eLogDebug, it.first); auto it1 = m_I2PControlHandlers.find (it.first); if (it1 != m_I2PControlHandlers.end ()) + { (this->*(it1->second))(it.second.data ()); + InsertParam (results, it.first, ""); + } else - LogPrint (eLogError, "I2PControl NetworkSetting unknown request ", it.first); + LogPrint (eLogError, "I2PControl I2PControl unknown request ", it.first); } } + void I2PControlService::PasswordHandler (const std::string& value) + { + LogPrint (eLogDebug, "I2PControl new password=", value); + m_Password = value; + m_Tokens.clear (); + SaveConfig (); + } + // RouterInfo void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) diff --git a/I2PControl.h b/I2PControl.h index 565281ac..10b6b651 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -25,6 +25,7 @@ namespace client const char I2P_CONTROL_PATH[] = "ipcontrol"; const char I2P_CONTROL_KEY_FILE[] = "key.pem"; const char I2P_CONTROL_CERT_FILE[] = "cert.pem"; + const char I2P_CONTROL_CONFIG_FILE[] = "i2pcontrol.conf"; const char I2P_CONTROL_DEFAULT_PASSWORD[] = "itoopie"; const char I2P_CONTROL_PROPERTY_ID[] = "id"; @@ -86,6 +87,9 @@ namespace client private: + void LoadConfig (); + void SaveConfig (); + void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); @@ -121,6 +125,7 @@ namespace client // I2PControl typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value); + void PasswordHandler (const std::string& value); // RouterInfo typedef void (I2PControlService::*RouterInfoRequestHandler)(std::ostringstream& results); From be358f3f2edf97e11c2b8f85138d5f8793ce6835 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 9 Dec 2015 10:03:51 -0500 Subject: [PATCH 0630/6300] enable RI catch for OBEP back --- TunnelEndpoint.cpp | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 6f765a86..73530557 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -225,36 +225,32 @@ namespace tunnel void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg) { - LogPrint (eLogInfo, "TunnelMessage: handle fragment of ", msg.data->GetLength ()," bytes. Msg type ", (int)msg.data->GetTypeID ()); + auto typeID = msg.data->GetTypeID (); + LogPrint (eLogInfo, "TunnelMessage: handle fragment of ", msg.data->GetLength ()," bytes. Msg type ", (int)typeID); switch (msg.deliveryType) { case eDeliveryTypeLocal: i2p::HandleI2NPMessage (msg.data); break; case eDeliveryTypeTunnel: - i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); - break; - case eDeliveryTypeRouter: - if (msg.hash == i2p::context.GetRouterInfo ().GetIdentHash ()) // check if message is sent to us - i2p::HandleI2NPMessage (msg.data); + if (!m_IsInbound) // outbound transit tunnel + i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); else - { - // to somebody else - if (!m_IsInbound) // outbound transit tunnel - { - /* auto typeID = msg.data->GetTypeID (); - if (typeID == eI2NPDatabaseStore || typeID == eI2NPDatabaseSearchReply ) - // catch RI or reply with new list of routers - i2p::data::netdb.PostI2NPMsg (msg.data);*/ - i2p::transport::transports.SendMessage (msg.hash, msg.data); - } - else // we shouldn't send this message. possible leakage - LogPrint (eLogError, "Message to another router arrived from an inbound tunnel. Dropped"); - } + LogPrint (eLogError, "Delivery type tunnel arrived from an inbound tunnel. Dropped"); + break; + case eDeliveryTypeRouter: + if (!m_IsInbound) // outbound transit tunnel + i2p::transport::transports.SendMessage (msg.hash, msg.data); + else // we shouldn't send this message. possible leakage + LogPrint (eLogError, "Delivery type router arrived from an inbound tunnel. Dropped"); break; default: LogPrint (eLogError, "TunnelMessage: Unknown delivery type ", (int)msg.deliveryType); }; + // catch RI or reply with new list of routers + if ((typeID == eI2NPDatabaseStore || typeID == eI2NPDatabaseSearchReply) && + !m_IsInbound && msg.deliveryType != eDeliveryTypeLocal) + i2p::data::netdb.PostI2NPMsg (msg.data); } } } From d89f0f51dfb90425591f9567bbb31e617581dce6 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 9 Dec 2015 10:35:04 -0500 Subject: [PATCH 0631/6300] show full tunnel path --- HTTPServer.cpp | 8 ++++---- Tunnel.cpp | 13 ------------- Tunnel.h | 2 -- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index f4cd7b82..157019de 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -800,7 +800,7 @@ namespace util for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) { - it->Print (s); + it->GetTunnelConfig ()->Print (s); auto state = it->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; @@ -812,7 +812,7 @@ namespace util for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ()) { - it.second->Print (s); + it.second->GetTunnelConfig ()->Print (s); auto state = it.second->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; @@ -863,7 +863,7 @@ namespace util s << "Tunnels:
"; for (auto it: pool->GetOutboundTunnels ()) { - it->Print (s); + it->GetTunnelConfig ()->Print (s); auto state = it->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; @@ -873,7 +873,7 @@ namespace util } for (auto it: pool->GetInboundTunnels ()) { - it->Print (s); + it->GetTunnelConfig ()->Print (s); auto state = it->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; diff --git a/Tunnel.cpp b/Tunnel.cpp index b899fdf7..e636712e 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -190,13 +190,6 @@ namespace tunnel newMsg->from = shared_from_this (); m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } - - void InboundTunnel::Print (std::stringstream& s) const - { - s << "-->" << i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ()) << ":" << GetNextTunnelID (); - s << "-->" << (GetNumHops () - 1) << " hops "; - s << GetTunnelID () << ":me"; - } void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) { @@ -232,12 +225,6 @@ namespace tunnel { LogPrint (eLogError, "Incoming message for outbound tunnel ", GetTunnelID ()); } - - void OutboundTunnel::Print (std::stringstream& s) const - { - s << "me-->" << i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ()) << ":" << GetNextTunnelID (); - s << "-->" << (GetNumHops () - 1) << " hops-->"; - } Tunnels tunnels; diff --git a/Tunnel.h b/Tunnel.h index 20e02c1b..3c01eb00 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -97,7 +97,6 @@ namespace tunnel void SendTunnelDataMsg (const std::vector& msgs); // multiple messages const i2p::data::IdentHash& GetEndpointIdentHash () const { return m_EndpointIdentHash; }; size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; - void Print (std::stringstream& s) const; // implements TunnelBase void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); @@ -116,7 +115,6 @@ namespace tunnel InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; void HandleTunnelDataMsg (std::shared_ptr msg); size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; - void Print (std::stringstream& s) const; private: From a7b8b52dbda32dda7d9210c4c60d849cae7689e7 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 9 Dec 2015 18:01:42 -0500 Subject: [PATCH 0632/6300] fixed crash --- HTTPServer.cpp | 8 ++++---- Tunnel.cpp | 13 +++++++++++++ Tunnel.h | 2 ++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 157019de..f4cd7b82 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -800,7 +800,7 @@ namespace util for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) { - it->GetTunnelConfig ()->Print (s); + it->Print (s); auto state = it->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; @@ -812,7 +812,7 @@ namespace util for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ()) { - it.second->GetTunnelConfig ()->Print (s); + it.second->Print (s); auto state = it.second->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; @@ -863,7 +863,7 @@ namespace util s << "Tunnels:
"; for (auto it: pool->GetOutboundTunnels ()) { - it->GetTunnelConfig ()->Print (s); + it->Print (s); auto state = it->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; @@ -873,7 +873,7 @@ namespace util } for (auto it: pool->GetInboundTunnels ()) { - it->GetTunnelConfig ()->Print (s); + it->Print (s); auto state = it->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; diff --git a/Tunnel.cpp b/Tunnel.cpp index e636712e..b899fdf7 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -190,6 +190,13 @@ namespace tunnel newMsg->from = shared_from_this (); m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } + + void InboundTunnel::Print (std::stringstream& s) const + { + s << "-->" << i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ()) << ":" << GetNextTunnelID (); + s << "-->" << (GetNumHops () - 1) << " hops "; + s << GetTunnelID () << ":me"; + } void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) { @@ -225,6 +232,12 @@ namespace tunnel { LogPrint (eLogError, "Incoming message for outbound tunnel ", GetTunnelID ()); } + + void OutboundTunnel::Print (std::stringstream& s) const + { + s << "me-->" << i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ()) << ":" << GetNextTunnelID (); + s << "-->" << (GetNumHops () - 1) << " hops-->"; + } Tunnels tunnels; diff --git a/Tunnel.h b/Tunnel.h index 3c01eb00..20e02c1b 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -97,6 +97,7 @@ namespace tunnel void SendTunnelDataMsg (const std::vector& msgs); // multiple messages const i2p::data::IdentHash& GetEndpointIdentHash () const { return m_EndpointIdentHash; }; size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; + void Print (std::stringstream& s) const; // implements TunnelBase void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); @@ -115,6 +116,7 @@ namespace tunnel InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; void HandleTunnelDataMsg (std::shared_ptr msg); size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; + void Print (std::stringstream& s) const; private: From badcd64b621d62476a9e3612645fb6bce97abcf5 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 9 Dec 2015 19:07:12 -0500 Subject: [PATCH 0633/6300] print full tunnel path --- Tunnel.cpp | 19 ++++++++++++++----- Tunnel.h | 7 +++++-- TunnelConfig.h | 19 ------------------- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index b899fdf7..0f1608f2 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -181,6 +181,15 @@ namespace tunnel ret.push_back (it->ident); return ret; } + + void Tunnel::PrintHops (std::stringstream& s) const + { + for (auto& it: m_Hops) + { + s << "-->"; + s << i2p::data::GetIdentHashAbbreviation (it->ident->GetIdentHash ()); + } + } void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) { @@ -193,9 +202,8 @@ namespace tunnel void InboundTunnel::Print (std::stringstream& s) const { - s << "-->" << i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ()) << ":" << GetNextTunnelID (); - s << "-->" << (GetNumHops () - 1) << " hops "; - s << GetTunnelID () << ":me"; + PrintHops (s); + s << "-->" << GetTunnelID () << ":me"; } void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) @@ -235,8 +243,9 @@ namespace tunnel void OutboundTunnel::Print (std::stringstream& s) const { - s << "me-->" << i2p::data::GetIdentHashAbbreviation (GetNextIdentHash ()) << ":" << GetNextTunnelID (); - s << "-->" << (GetNumHops () - 1) << " hops-->"; + s << GetTunnelID () << ":me"; + PrintHops (s); + s << "-->"; } Tunnels tunnels; diff --git a/Tunnel.h b/Tunnel.h index 20e02c1b..daf0f054 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -62,7 +62,6 @@ namespace tunnel std::vector > GetInvertedPeers () const; TunnelState GetState () const { return m_State; }; void SetState (TunnelState state) { m_State = state; }; - int GetNumHops () const { return m_Hops.size (); } bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; bool IsFailed () const { return m_State == eTunnelStateFailed; }; bool IsRecreated () const { return m_IsRecreated; }; @@ -76,7 +75,11 @@ namespace tunnel // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); - + + protected: + + void PrintHops (std::stringstream& s) const; + private: std::shared_ptr m_Config; diff --git a/TunnelConfig.h b/TunnelConfig.h index d2164d86..1628b4b4 100644 --- a/TunnelConfig.h +++ b/TunnelConfig.h @@ -194,25 +194,6 @@ namespace tunnel } return peers; } - - void Print (std::stringstream& s) const - { - TunnelHopConfig * hop = m_FirstHop; - if (!IsInbound ()) // outbound - s << "me"; - s << "-->" << m_FirstHop->tunnelID; - while (hop) - { - s << ":" << GetIdentHashAbbreviation (hop->ident->GetIdentHash ()) << "-->"; - if (!hop->isEndpoint) - s << hop->nextTunnelID; - else - return; - hop = hop->next; - } - // we didn't reach enpoint that mean we are last hop - s << ":me"; - } private: From 6096d572f3790b4f41f4bcb9762a5cff8fe9683c Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 9 Dec 2015 22:17:43 -0500 Subject: [PATCH 0634/6300] handle RelayResponse --- SSU.cpp | 2 +- SSU.h | 4 ++-- SSUSession.cpp | 39 ++++++++++++++++++++++++++++++++------- SSUSession.h | 6 ++++-- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 6fe03dd2..64045ae4 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -384,7 +384,7 @@ namespace transport uint8_t buf[1]; Send (buf, 0, remoteEndpoint); // send HolePunch } - introducerSession->Introduce (*introducer); + introducerSession->Introduce (*introducer, router); } else LogPrint (eLogWarning, "Can't connect to unreachable router. No introducers presented"); diff --git a/SSU.h b/SSU.h index efe6203d..8ee58ffa 100644 --- a/SSU.h +++ b/SSU.h @@ -43,6 +43,7 @@ namespace transport void CreateSession (std::shared_ptr router, bool peerTest = false); void CreateSession (std::shared_ptr router, const boost::asio::ip::address& addr, int port, bool peerTest = false); + void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); std::shared_ptr FindSession (std::shared_ptr router) const; std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; std::shared_ptr GetRandomEstablishedV4Session (std::shared_ptr excluded); @@ -74,8 +75,7 @@ namespace transport void HandleReceivedPackets (std::vector packets, std::map >* sessions); - void CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest = false); - void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); + void CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest = false); template std::shared_ptr GetRandomV4Session (Filter filter); diff --git a/SSUSession.cpp b/SSUSession.cpp index 346f5dd8..62c79b72 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -308,7 +308,7 @@ namespace transport m_Server.Send (buf, isV4 ? 304 : 320, m_RemoteEndpoint); } - void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer) + void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce) { auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); if (!address) @@ -329,7 +329,7 @@ namespace transport payload++; memcpy (payload, (const uint8_t *)address->key, 32); payload += 32; - RAND_bytes (payload, 4); // nonce + htobe32buf (payload, nonce); // nonce uint8_t iv[16]; RAND_bytes (iv, 16); // random iv @@ -568,9 +568,9 @@ namespace transport uint8_t * payload = buf + sizeof (SSUHeader); uint8_t remoteSize = *payload; payload++; // remote size - //boost::asio::ip::address_v4 remoteIP (bufbe32toh (payload)); + boost::asio::ip::address_v4 remoteIP (bufbe32toh (payload)); payload += remoteSize; // remote address - //uint16_t remotePort = bufbe16toh (payload); + uint16_t remotePort = bufbe16toh (payload); payload += 2; // remote port uint8_t ourSize = *payload; payload++; // our size @@ -592,6 +592,27 @@ namespace transport payload += 2; // our port LogPrint ("Our external address is ", ourIP.to_string (), ":", ourPort); i2p::context.UpdateAddress (ourIP); + uint32_t nonce = bufbe32toh (payload); + payload += 4; // nonce + auto it = m_RelayRequests.find (nonce); + if (it != m_RelayRequests.end ()) + { + // check if we are waiting for introduction + boost::asio::ip::udp::endpoint remoteEndpoint (remoteIP, remotePort); + if (!m_Server.FindSession (remoteEndpoint)) + { + // we didn't have correct endpoint when sent relay request + // now we do + LogPrint (eLogInfo, "RelayReponse connecting to endpoint ", remoteEndpoint); + if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable + m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch + m_Server.CreateDirectSession (it->second, remoteEndpoint, false); + } + // delete request + m_RelayRequests.erase (it); + } + else + LogPrint (eLogError, "Unsolicited RelayResponse, nonce=", nonce); } void SSUSession::ProcessRelayIntro (uint8_t * buf, size_t len) @@ -748,7 +769,8 @@ namespace transport } } - void SSUSession::Introduce (const i2p::data::RouterInfo::Introducer& introducer) + void SSUSession::Introduce (const i2p::data::RouterInfo::Introducer& introducer, + std::shared_ptr to) { if (m_State == eSessionStateUnknown) { @@ -756,8 +778,11 @@ namespace transport m_Timer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); m_Timer.async_wait (std::bind (&SSUSession::HandleConnectTimer, shared_from_this (), std::placeholders::_1)); - } - SendRelayRequest (introducer); + } + uint32_t nonce; + RAND_bytes ((uint8_t *)&nonce, 4); + m_RelayRequests[nonce] = to; + SendRelayRequest (introducer, nonce); } void SSUSession::WaitForIntroduction () diff --git a/SSUSession.h b/SSUSession.h index 0c1c5254..b77f4801 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -71,7 +71,8 @@ namespace transport void Connect (); void WaitForConnect (); - void Introduce (const i2p::data::RouterInfo::Introducer& introducer); + void Introduce (const i2p::data::RouterInfo::Introducer& introducer, + std::shared_ptr to); // Alice to Charlie void WaitForIntroduction (); void Close (); void Done (); @@ -100,7 +101,7 @@ namespace transport void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session void ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); void SendSessionRequest (); - void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer); + void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce); void ProcessSessionCreated (uint8_t * buf, size_t len); void SendSessionCreated (const uint8_t * x); void ProcessSessionConfirmed (uint8_t * buf, size_t len); @@ -150,6 +151,7 @@ namespace transport SSUData m_Data; bool m_IsDataReceived; std::unique_ptr m_SignedData; // we need it for SessionConfirmed only + std::map > m_RelayRequests; // nonce->Charlie }; From 1b6ad8413ed73083fcb4647549d87f141f05914f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 11 Dec 2015 15:48:33 -0500 Subject: [PATCH 0635/6300] spread addresses between subdirectories --- AddressBook.cpp | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index c06186ed..f98371f3 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -34,8 +34,15 @@ namespace client private: - boost::filesystem::path GetPath () const { return i2p::util::filesystem::GetDefaultDataDir() / "addressbook"; }; - + boost::filesystem::path GetPath () const + { + return i2p::util::filesystem::GetDefaultDataDir() / "addressbook"; + } + boost::filesystem::path GetAddressPath (const i2p::data::IdentHash& ident) const + { + auto b32 = ident.ToBase32(); + return GetPath () / (std::string ("b") + b32[0]) / (b32 + ".b32"); + } }; AddressBookFilesystemStorage::AddressBookFilesystemStorage () @@ -47,11 +54,27 @@ namespace client if (!boost::filesystem::create_directory (path)) LogPrint (eLogError, "Failed to create addressbook directory"); } + } std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const { - auto filename = GetPath () / (ident.ToBase32() + ".b32"); + auto filename = GetAddressPath (ident); + if (!boost::filesystem::exists (filename)) + { + boost::filesystem::create_directory (filename.parent_path ()); + // try to find in main folder + auto filename1 = GetPath () / (ident.ToBase32 () + ".b32"); + if (!boost::filesystem::exists (filename1)) + { + boost::system::error_code ec; + boost::filesystem::rename (filename1, filename, ec); + if (ec) + LogPrint (eLogError, "Couldn't move file ", ec.message ()); + } + else + return nullptr; // address doesn't exist + } std::ifstream f(filename.string (), std::ifstream::binary); if (f.is_open ()) { @@ -75,8 +98,14 @@ namespace client void AddressBookFilesystemStorage::AddAddress (std::shared_ptr address) { - auto filename = GetPath () / (address->GetIdentHash ().ToBase32() + ".b32"); + auto filename = GetAddressPath (address->GetIdentHash ()); std::ofstream f (filename.string (), std::ofstream::binary | std::ofstream::out); + if (!f.is_open ()) + { + // create subdirectory + if (boost::filesystem::create_directory (filename.parent_path ())) + f.open (filename.string (), std::ofstream::binary | std::ofstream::out); // and try to open again + } if (f.is_open ()) { size_t len = address->GetFullLen (); @@ -91,7 +120,7 @@ namespace client void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident) { - auto filename = GetPath () / (ident.ToBase32() + ".b32"); + auto filename = GetAddressPath (ident); if (boost::filesystem::exists (filename)) boost::filesystem::remove (filename); } From de6dd770465809d500dd682fb5425df8a965f7ce Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 13 Dec 2015 10:51:43 -0500 Subject: [PATCH 0636/6300] use shared_ptr for LeaseSet request --- Destination.cpp | 16 ++++++---------- Destination.h | 4 ++-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 6e2550eb..826af4ef 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -94,7 +94,8 @@ namespace client if (m_IsRunning) Stop (); for (auto it: m_LeaseSetRequests) - delete it.second; + if (it.second->requestComplete) it.second->requestComplete (nullptr); + m_LeaseSetRequests.clear (); if (m_Pool) i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool); if (m_DatagramDestination) @@ -299,7 +300,6 @@ namespace client { it1->second->requestTimeoutTimer.cancel (); if (it1->second->requestComplete) it1->second->requestComplete (leaseSet); - delete it1->second; m_LeaseSetRequests.erase (it1); } } @@ -312,7 +312,7 @@ namespace client auto it = m_LeaseSetRequests.find (key); if (it != m_LeaseSetRequests.end ()) { - LeaseSetRequest * request = it->second; + auto request = it->second; bool found = false; if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST) { @@ -340,7 +340,6 @@ namespace client if (!found) { if (request->requestComplete) request->requestComplete (nullptr); - delete request; m_LeaseSetRequests.erase (key); } } @@ -540,16 +539,15 @@ namespace client auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, excluded); if (floodfill) { - LeaseSetRequest * request = new LeaseSetRequest (m_Service); + auto request = std::make_shared (m_Service); request->requestComplete = requestComplete; - auto ret = m_LeaseSetRequests.insert (std::pair(dest,request)); + auto ret = m_LeaseSetRequests.insert (std::pair >(dest,request)); if (ret.second) // inserted { if (!SendLeaseSetRequest (dest, floodfill, request)) { // request failed if (request->requestComplete) request->requestComplete (nullptr); - delete request; m_LeaseSetRequests.erase (dest); } } @@ -558,7 +556,6 @@ namespace client LogPrint (eLogError, "Request of ", dest.ToBase64 (), " is pending already"); // TODO: queue up requests if (request->requestComplete) request->requestComplete (nullptr); - delete request; } } else @@ -566,7 +563,7 @@ namespace client } bool ClientDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest, - std::shared_ptr nextFloodfill, LeaseSetRequest * request) + std::shared_ptr nextFloodfill, std::shared_ptr request) { auto replyTunnel = m_Pool->GetNextInboundTunnel (); if (!replyTunnel) LogPrint (eLogError, "No inbound tunnels found"); @@ -631,7 +628,6 @@ namespace client if (done) { if (it->second->requestComplete) it->second->requestComplete (nullptr); - delete it->second; m_LeaseSetRequests.erase (it); } } diff --git a/Destination.h b/Destination.h index 0000014b..aa7123e7 100644 --- a/Destination.h +++ b/Destination.h @@ -118,7 +118,7 @@ namespace client void HandleDeliveryStatusMessage (std::shared_ptr msg); void RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete); - bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, LeaseSetRequest * request); + bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, std::shared_ptr request); void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleCleanupTimer (const boost::system::error_code& ecode); void CleanupRemoteLeaseSets (); @@ -132,7 +132,7 @@ namespace client i2p::data::PrivateKeys m_Keys; uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; std::map > m_RemoteLeaseSets; - std::map m_LeaseSetRequests; + std::map > m_LeaseSetRequests; std::shared_ptr m_Pool; std::shared_ptr m_LeaseSet; From fdd96975fb573e7fc259e2e51ffd5b333fb0c421 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 13 Dec 2015 14:40:43 -0500 Subject: [PATCH 0637/6300] cancel destination request --- AddressBook.cpp | 3 +++ Destination.cpp | 11 +++++++++++ Destination.h | 1 + 3 files changed, 15 insertions(+) diff --git a/AddressBook.cpp b/AddressBook.cpp index f98371f3..49551f10 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -495,7 +495,10 @@ namespace client newDataReceived.notify_all (); }); if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) + { LogPrint (eLogError, "Subscription LeseseSet request timeout expired"); + i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (ident); + } } if (leaseSet) { diff --git a/Destination.cpp b/Destination.cpp index 826af4ef..9c5bb700 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -533,6 +533,17 @@ namespace client return true; } + void ClientDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest) + { + auto s = shared_from_this (); + m_Service.post ([dest, s](void) + { + auto it = s->m_LeaseSetRequests.find (dest); + if (it != s->m_LeaseSetRequests.end ()) + s->m_LeaseSetRequests.erase (it); + }); + } + void ClientDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete) { std::set excluded; diff --git a/Destination.h b/Destination.h index aa7123e7..d38fe30e 100644 --- a/Destination.h +++ b/Destination.h @@ -73,6 +73,7 @@ namespace client bool IsReady () const { return m_LeaseSet && m_LeaseSet->HasNonExpiredLeases () && m_Pool->GetOutboundTunnels ().size () > 0; }; std::shared_ptr FindLeaseSet (const i2p::data::IdentHash& ident); bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); + void CancelDestinationRequest (const i2p::data::IdentHash& dest); // streaming std::shared_ptr CreateStreamingDestination (int port); // additional From 5930e2d221ef3eb09e848ed7a92fb2fa3b692b8b Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 14 Dec 2015 22:23:28 -0500 Subject: [PATCH 0638/6300] keep pending incoming streams if acceptor is not set --- Streaming.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++++---- Streaming.h | 9 +++++++-- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 0b4e514d..deed2adb 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -787,7 +787,7 @@ namespace stream } StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort): - m_Owner (owner), m_LocalPort (localPort) + m_Owner (owner), m_LocalPort (localPort), m_PendingIncomingTimer (m_Owner->GetService ()) { } @@ -802,6 +802,7 @@ namespace stream void StreamingDestination::Stop () { ResetAcceptor (); + m_PendingIncomingTimer.cancel (); { std::unique_lock l(m_StreamsMutex); m_Streams.clear (); @@ -832,8 +833,21 @@ namespace stream m_Acceptor (incomingStream); else { - LogPrint ("Acceptor for incoming stream is not set"); - DeleteStream (incomingStream); + LogPrint (eLogInfo, "Acceptor for incoming stream is not set"); + if (m_PendingIncomingStreams.size () < MAX_PENDING_INCOMING_BACKLOG) + { + m_PendingIncomingStreams.push_back (incomingStream); + m_PendingIncomingTimer.cancel (); + m_PendingIncomingTimer.expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT)); + m_PendingIncomingTimer.async_wait (std::bind (&StreamingDestination::HandlePendingIncomingTimer, + this, std::placeholders::_1)); + LogPrint (eLogInfo, "Pending incoming stream added"); + } + else + { + LogPrint (eLogError, "Pending incoming streams backlog exceeds ", MAX_PENDING_INCOMING_BACKLOG); + incomingStream->Close (); + } } } else // follow on packet without SYN @@ -852,7 +866,7 @@ namespace stream } } } - + std::shared_ptr StreamingDestination::CreateNewOutgoingStream (std::shared_ptr remote, int port) { auto s = std::make_shared (m_Owner->GetService (), *this, remote, port); @@ -880,6 +894,39 @@ namespace stream } } + void StreamingDestination::SetAcceptor (const Acceptor& acceptor) + { + m_Owner->GetService ().post([acceptor, this](void) + { + m_Acceptor = acceptor; + for (auto it: m_PendingIncomingStreams) + if (it->GetStatus () == eStreamStatusOpen) // still open? + m_Acceptor (it); + m_PendingIncomingStreams.clear (); + m_PendingIncomingTimer.cancel (); + }); + } + + void StreamingDestination::ResetAcceptor () + { + m_Owner->GetService ().post([this](void) + { + if (m_Acceptor) m_Acceptor (nullptr); + m_Acceptor = nullptr; + }); + } + + void StreamingDestination::HandlePendingIncomingTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + LogPrint (eLogInfo, "Pending incoming timeout expired"); + for (auto it: m_PendingIncomingStreams) + it->Close (); + m_PendingIncomingStreams.clear (); + } + } + void StreamingDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len) { // unzip it diff --git a/Streaming.h b/Streaming.h index 580ddf7c..519bda31 100644 --- a/Streaming.h +++ b/Streaming.h @@ -49,6 +49,8 @@ namespace stream const int MAX_WINDOW_SIZE = 128; const int INITIAL_RTT = 8000; // in milliseconds const int INITIAL_RTO = 9000; // in milliseconds + const size_t MAX_PENDING_INCOMING_BACKLOG = 128; + const int PENDING_INCOMING_TIMEOUT = 10; // in seconds struct Packet { @@ -201,8 +203,8 @@ namespace stream std::shared_ptr CreateNewOutgoingStream (std::shared_ptr remote, int port = 0); void DeleteStream (std::shared_ptr stream); - void SetAcceptor (const Acceptor& acceptor) { m_Acceptor = acceptor; }; - void ResetAcceptor () { if (m_Acceptor) m_Acceptor (nullptr); m_Acceptor = nullptr; }; + void SetAcceptor (const Acceptor& acceptor); + void ResetAcceptor (); bool IsAcceptorSet () const { return m_Acceptor != nullptr; }; std::shared_ptr GetOwner () const { return m_Owner; }; uint16_t GetLocalPort () const { return m_LocalPort; }; @@ -213,6 +215,7 @@ namespace stream void HandleNextPacket (Packet * packet); std::shared_ptr CreateNewIncomingStream (); + void HandlePendingIncomingTimer (const boost::system::error_code& ecode); private: @@ -221,6 +224,8 @@ namespace stream std::mutex m_StreamsMutex; std::map > m_Streams; Acceptor m_Acceptor; + std::list > m_PendingIncomingStreams; + boost::asio::deadline_timer m_PendingIncomingTimer; public: From 9fa6b1ebe16058e742808c64c9a240ed22a64958 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 14 Dec 2015 22:36:23 -0500 Subject: [PATCH 0639/6300] keep pending incoming streams if acceptor is not set --- Streaming.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index deed2adb..99223512 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -908,12 +908,9 @@ namespace stream } void StreamingDestination::ResetAcceptor () - { - m_Owner->GetService ().post([this](void) - { - if (m_Acceptor) m_Acceptor (nullptr); - m_Acceptor = nullptr; - }); + { + if (m_Acceptor) m_Acceptor (nullptr); + m_Acceptor = nullptr; } void StreamingDestination::HandlePendingIncomingTimer (const boost::system::error_code& ecode) From 638a69e5f0ce6488b1f66c75bd2ee9b7cdbb0050 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 16 Dec 2015 00:19:52 +0000 Subject: [PATCH 0640/6300] * fix comments in default config (#311) --- README.md | 6 ++-- debian/i2pd.conf | 20 ++++++------- debian/tunnels.conf | 2 +- docs/configuration.md | 69 ++++++++++++++++++++++--------------------- 4 files changed, 49 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index e467c732..b890029b 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,9 @@ Supported OS [![Build status](https://ci.appveyor.com/api/projects/status/458fhbki14gaplyj/branch/openssl?svg=true)](https://ci.appveyor.com/project/mlt/i2pd/branch/openssl) --- Linux x86/x64 - Proved working. --- Mac OS X - Not well tested. (Only works with clang, not GCC) --- Windows - At least builds and runs. +* Linux x86/x64 - Proved working. +* Mac OS X - Not well tested. (Only works with clang, not GCC) +* Windows - At least builds and runs. More documentation ------------------ diff --git a/debian/i2pd.conf b/debian/i2pd.conf index c32716ce..69b02ba0 100644 --- a/debian/i2pd.conf +++ b/debian/i2pd.conf @@ -8,13 +8,13 @@ ircaddress=127.0.0.1 ircport=6668 ircdest=irc.postman.i2p -; other services (disabled by default) -; -; samaddress=127.0.0.1 -; samport=7656 -; -; bobaddress=127.0.0.1 -; bobport=2827 -; -; i2pcontroladdress=127.0.0.1 -; i2pcontrolport=7650 +# other services (disabled by default) +# +# samaddress=127.0.0.1 +# samport=7656 +# +# bobaddress=127.0.0.1 +# bobport=2827 +# +# i2pcontroladdress=127.0.0.1 +# i2pcontrolport=7650 diff --git a/debian/tunnels.conf b/debian/tunnels.conf index 43cb495a..3cf35552 100644 --- a/debian/tunnels.conf +++ b/debian/tunnels.conf @@ -1 +1 @@ -; see examples in /usr/share/doc/i2pd/configuration.md.gz +# see examples in /usr/share/doc/i2pd/configuration.md.gz diff --git a/docs/configuration.md b/docs/configuration.md index 79e30dc9..44ec9d8d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -39,46 +39,47 @@ Config files ------------ INI-like, syntax is the following : = . +Comments are "#", not ";" as you may expect. See [boost ticket](https://svn.boost.org/trac/boost/ticket/808) All command-line parameters are allowed as keys, for example: i2p.conf: - log = 1 - v6 = 0 - ircdest = irc.postman.i2p + log = 1 + v6 = 0 + ircdest = irc.postman.i2p tunnels.cfg (filename of this config is subject of change): - ; outgoing tunnel sample, to remote service - ; mandatory parameters: - ; * type -- always "client" - ; * port -- local port to listen to - ; * destination -- i2p hostname - ; optional parameters (may be omitted) - ; * keys -- our identity, if unset, will be generated on every startup, - ; if set and file missing, keys will be generated and placed to this file - [IRC] - type = client - port = 6668 - destination = irc.echelon.i2p - keys = irc-keys.dat - ; - ; incoming tunnel sample, for local service - ; mandatory parameters: - ; * type -- always "server" - ; * host -- ip address of our service - ; * port -- port of our service - ; * keys -- file with LeaseSet of address in i2p - ; optional parameters (may be omitted) - ; * inport -- optional, i2p service port, if unset - the same as 'port' - ; * accesslist -- comma-separated list of i2p addresses, allowed to connect - ; every address is b32 without '.b32.i2p' part - [LOCALSITE] - type = server - host = 127.0.0.1 - port = 80 - keys = site-keys.dat - inport = 81 - accesslist = [,] + # outgoing tunnel sample, to remote service + # mandatory parameters: + # * type -- always "client" + # * port -- local port to listen to + # * destination -- i2p hostname + # optional parameters (may be omitted) + # * keys -- our identity, if unset, will be generated on every startup, + # if set and file missing, keys will be generated and placed to this file + [IRC] + type = client + port = 6668 + destination = irc.echelon.i2p + keys = irc-keys.dat + # + # incoming tunnel sample, for local service + # mandatory parameters: + # * type -- always "server" + # * host -- ip address of our service + # * port -- port of our service + # * keys -- file with LeaseSet of address in i2p + # optional parameters (may be omitted) + # * inport -- optional, i2p service port, if unset - the same as 'port' + # * accesslist -- comma-separated list of i2p addresses, allowed to connect + # every address is b32 without '.b32.i2p' part + [LOCALSITE] + type = server + host = 127.0.0.1 + port = 80 + keys = site-keys.dat + inport = 81 + accesslist = [,] Also see [this page](https://github.com/PurpleI2P/i2pd/wiki/tunnels.cfg) for more tunnel examples. From 8ad20c0db3d3d759283c4fb4fb72e153273b8117 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 16 Dec 2015 01:12:14 +0000 Subject: [PATCH 0641/6300] * allow parallel builds (#310) --- debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index e7342142..1e3535da 100755 --- a/debian/rules +++ b/debian/rules @@ -11,7 +11,7 @@ CXXFLAGS+=$(CPPFLAGS) PREFIX=/usr %: - dh $@ + dh $@ --parallel override_dh_strip: dh_strip --dbg-package=i2pd-dbg From 4599f6919c4ceead56a0e042162cec0e2a681cff Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 16 Dec 2015 14:52:48 -0500 Subject: [PATCH 0642/6300] shared_ptr for local destination in TunnelPool --- Destination.cpp | 6 +++--- LeaseSet.cpp | 7 ++++--- LeaseSet.h | 2 +- RouterContext.h | 6 ++++++ Tunnel.cpp | 10 +++++++--- Tunnel.h | 3 ++- TunnelPool.cpp | 6 +++--- TunnelPool.h | 8 ++++---- 8 files changed, 30 insertions(+), 18 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 9c5bb700..67042276 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -82,7 +82,7 @@ namespace client LogPrint (eLogInfo, "Explicit peers set to ", it->second); } } - m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (this, inboundTunnelLen, outboundTunnelLen, inboundTunnelsQuantity, outboundTunnelsQuantity); + m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inboundTunnelLen, outboundTunnelLen, inboundTunnelsQuantity, outboundTunnelsQuantity); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); if (m_IsPublic) @@ -122,7 +122,7 @@ namespace client if (!m_IsRunning) { m_IsRunning = true; - m_Pool->SetLocalDestination (this); + m_Pool->SetLocalDestination (shared_from_this ()); m_Pool->SetActive (true); m_Thread = new std::thread (std::bind (&ClientDestination::Run, this)); m_StreamingDestination = std::make_shared (shared_from_this ()); // TODO: @@ -199,7 +199,7 @@ namespace client void ClientDestination::UpdateLeaseSet () { - m_LeaseSet.reset (new i2p::data::LeaseSet (*m_Pool)); + m_LeaseSet.reset (new i2p::data::LeaseSet (m_Pool)); } bool ClientDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 9a88401c..caeec262 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -21,11 +21,12 @@ namespace data ReadFromBuffer (); } - LeaseSet::LeaseSet (const i2p::tunnel::TunnelPool& pool): + LeaseSet::LeaseSet (std::shared_ptr pool): m_IsValid (true) { + if (!pool) return; // header - const i2p::data::LocalDestination * localDestination = pool.GetLocalDestination (); + auto localDestination = pool->GetLocalDestination (); if (!localDestination) { m_Buffer = nullptr; @@ -41,7 +42,7 @@ namespace data auto signingKeyLen = localDestination->GetIdentity ()->GetSigningPublicKeyLen (); memset (m_Buffer + m_BufferLen, 0, signingKeyLen); m_BufferLen += signingKeyLen; - auto tunnels = pool.GetInboundTunnels (5); // 5 tunnels maximum + auto tunnels = pool->GetInboundTunnels (5); // 5 tunnels maximum m_Buffer[m_BufferLen] = tunnels.size (); // num leases m_BufferLen++; // leases diff --git a/LeaseSet.h b/LeaseSet.h index eec5d89e..8135a7a1 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -37,7 +37,7 @@ namespace data public: LeaseSet (const uint8_t * buf, size_t len); - LeaseSet (const i2p::tunnel::TunnelPool& pool); + LeaseSet (std::shared_ptr pool); ~LeaseSet () { delete[] m_Buffer; }; void Update (const uint8_t * buf, size_t len); std::shared_ptr GetIdentity () const { return m_Identity; }; diff --git a/RouterContext.h b/RouterContext.h index 541b78d1..13027b44 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -39,6 +39,12 @@ namespace i2p return std::shared_ptr (&m_RouterInfo, [](const i2p::data::RouterInfo *) {}); } + std::shared_ptr GetSharedDestination () + { + return std::shared_ptr (this, + [](i2p::garlic::GarlicDestination *) {}); + } + uint32_t GetUptime () const; uint32_t GetStartupTime () const { return m_StartupTime; }; uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; }; diff --git a/Tunnel.cpp b/Tunnel.cpp index 0f1608f2..8e5510f1 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -333,9 +333,10 @@ namespace tunnel return tunnel; } - std::shared_ptr Tunnels::CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels) + std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, + int numOutboundHops, int numInboundTunnels, int numOutboundTunnels) { - auto pool = std::make_shared (localDestination, numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels); + auto pool = std::make_shared (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels); std::unique_lock l(m_PoolsMutex); m_Pools.push_back (pool); return pool; @@ -649,7 +650,10 @@ namespace tunnel LogPrint ("Creating zero hops inbound tunnel..."); CreateZeroHopsInboundTunnel (); if (!m_ExploratoryPool) - m_ExploratoryPool = CreateTunnelPool (&i2p::context, 2, 2, 5, 5); // 2-hop exploratory, 5 tunnels + { + m_ExploratoryPool = CreateTunnelPool (2, 2, 5, 5); // 2-hop exploratory, 5 tunnels + m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ()); + } return; } diff --git a/Tunnel.h b/Tunnel.h index daf0f054..138a48fc 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -153,7 +153,8 @@ namespace tunnel std::shared_ptr CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel = nullptr); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); - std::shared_ptr CreateTunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOuboundHops, int numInboundTunnels, int numOutboundTunnels); + std::shared_ptr CreateTunnelPool (int numInboundHops, + int numOuboundHops, int numInboundTunnels, int numOutboundTunnels); void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 922ae34b..fcce1c22 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -14,8 +14,8 @@ namespace i2p { namespace tunnel { - TunnelPool::TunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): - m_LocalDestination (localDestination), m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), + TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): + m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_IsActive (true) { } @@ -298,7 +298,7 @@ namespace tunnel std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop) const { - bool isExploratory = (m_LocalDestination == &i2p::context); // TODO: implement it better + bool isExploratory = (i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this ()); auto hop = isExploratory ? i2p::data::netdb.GetRandomRouter (prevHop): i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop); diff --git a/TunnelPool.h b/TunnelPool.h index a836162f..83b163d4 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -27,11 +27,11 @@ namespace tunnel { public: - TunnelPool (i2p::garlic::GarlicDestination * localDestination, int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels); + TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels); ~TunnelPool (); - i2p::garlic::GarlicDestination * GetLocalDestination () const { return m_LocalDestination; }; - void SetLocalDestination (i2p::garlic::GarlicDestination * destination) { m_LocalDestination = destination; }; + std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; + void SetLocalDestination (std::shared_ptr destination) { m_LocalDestination = destination; }; void SetExplicitPeers (std::shared_ptr > explicitPeers); void CreateTunnels (); @@ -67,7 +67,7 @@ namespace tunnel private: - i2p::garlic::GarlicDestination * m_LocalDestination; + std::shared_ptr m_LocalDestination; int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels; std::shared_ptr > m_ExplicitPeers; mutable std::mutex m_InboundTunnelsMutex; From c605fd57aacfbc4d8cfe0bee5dae1e03466659ee Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 16 Dec 2015 12:42:20 +0000 Subject: [PATCH 0643/6300] * AddressBook.cpp : mistype in log message --- AddressBook.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 49551f10..49177e8e 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -496,7 +496,7 @@ namespace client }); if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) { - LogPrint (eLogError, "Subscription LeseseSet request timeout expired"); + LogPrint (eLogError, "Subscription LeaseSet request timeout expired"); i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (ident); } } From 3676304751a40260fdda57f2778bde94ab3d31ef Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 16 Dec 2015 12:07:17 +0000 Subject: [PATCH 0644/6300] * provide default subscriptions.txt --- debian/i2pd.install | 1 + debian/i2pd.links | 1 + debian/subscriptions.txt | 3 +++ 3 files changed, 5 insertions(+) create mode 100644 debian/subscriptions.txt diff --git a/debian/i2pd.install b/debian/i2pd.install index fa79ce1b..9947f5a5 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,3 +1,4 @@ i2pd usr/sbin/ debian/i2pd.conf etc/i2pd/ debian/tunnels.conf etc/i2pd/ +debian/subscriptions.txt etc/i2pd/ diff --git a/debian/i2pd.links b/debian/i2pd.links index 259219e7..36e4bb31 100644 --- a/debian/i2pd.links +++ b/debian/i2pd.links @@ -1,2 +1,3 @@ etc/i2pd/i2pd.conf var/lib/i2pd/i2p.conf etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.cfg +etc/i2pd/subscriptions.txt var/lib/i2pd/subscriptions.txt diff --git a/debian/subscriptions.txt b/debian/subscriptions.txt new file mode 100644 index 00000000..8f4afb03 --- /dev/null +++ b/debian/subscriptions.txt @@ -0,0 +1,3 @@ +http://inr.i2p/export/alive-hosts.txt +http://stats.i2p/cgi-bin/newhosts.txt +http://i2p-projekt.i2p/hosts.txt From 8f218141f4633bf8f5c4aa2d266fabf7b2dc58b1 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 16 Dec 2015 13:22:55 +0000 Subject: [PATCH 0645/6300] * add 'i2pd' user (#313) --- debian/control | 1 + debian/i2pd.init | 16 ++++++++++------ debian/postinst | 36 ++++++++++++++++++++++++++++++++++++ debian/postrm | 12 ++++++++++++ 4 files changed, 59 insertions(+), 6 deletions(-) create mode 100755 debian/postinst create mode 100755 debian/postrm diff --git a/debian/control b/debian/control index add1060f..e76f9c7b 100644 --- a/debian/control +++ b/debian/control @@ -17,6 +17,7 @@ Vcs-Browser: https://github.com/PurpleI2P/i2pd.git Package: i2pd Architecture: any +Pre-Depends: adduser Depends: ${shlibs:Depends}, ${misc:Depends} Recommends: privoxy Suggests: tor diff --git a/debian/i2pd.init b/debian/i2pd.init index ed061b04..609a3407 100644 --- a/debian/i2pd.init +++ b/debian/i2pd.init @@ -16,9 +16,9 @@ NAME=i2pd # Introduce the short server's name here DAEMON=/usr/sbin/$NAME # Introduce the server's location here DAEMON_OPTS="" # Arguments to run the daemon with PIDFILE=/var/run/$NAME.pid -SCRIPTNAME=/etc/init.d/$NAME I2PCONF=/etc/$NAME/i2pd.conf TUNCONF=/etc/$NAME/tunnels.conf +USER="i2pd" # Exit if the package is not installed [ -x $DAEMON ] || exit 0 @@ -36,16 +36,20 @@ do_start() # 2 if daemon could not be started if [ "x$I2PD_ENABLED" != "xyes" ]; then - log_warning_msg "disabled in config" + log_warning_msg "$NAME disabled in config" return 2 fi - start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + touch "$PIDFILE" + chown -f $USER:adm "$PIDFILE" + + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" --test > /dev/null \ || return 1 - start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" -- \ --service=1 --daemon=1 --log=1 --conf=$I2PCONF --tunnelscfg=$TUNCONF \ - --port=$I2PD_PORT $DAEMON_OPTS \ + --port=$I2PD_PORT $DAEMON_OPTS > /dev/null 2>&1 \ || return 2 + return $? } # Function that stops the daemon/service @@ -115,7 +119,7 @@ case "$1" in esac ;; *) - echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + echo "Usage: $0 {start|stop|status|restart|force-reload}" >&2 exit 3 ;; esac diff --git a/debian/postinst b/debian/postinst new file mode 100755 index 00000000..ddb5ef7c --- /dev/null +++ b/debian/postinst @@ -0,0 +1,36 @@ +#!/bin/sh + +I2PDHOME='/var/lib/i2pd' +I2PDUSER='i2pd' + +case "$1" in + configure|reconfigure) + # Older versions of adduser created the home directory. + # The version of adduser in Debian unstable does not. + # Create user and group as a system user. + getent passwd ${I2PDUSER} > /dev/null 2>&1 + if [ $? -eq 2 ]; then + adduser --system --quiet --group --home $I2PDHOME $I2PDUSER + else + groupadd -f $I2PDUSER || true + usermod -s "/bin/false" -e 1 $I2PDUSER > /dev/null || true + fi + + touch /var/log/i2pd.log + chown -f ${I2PDUSER}:adm /var/log/i2pd.log + mkdir -p -m0750 $I2PDHOME + chown -f -R ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} + ;; + abort-upgrade|abort-remove|abort-deconfigure) + echo "Aborting upgrade" + exit 0 + ;; + *) + echo "postinst called with unknown argument '$1'" >&2 + exit 0 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/postrm b/debian/postrm new file mode 100755 index 00000000..7015e547 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e + +if [ "$1" = "purge" ]; then + rm -f /etc/default/i2pd /var/log/i2pd.log + rm -rf /var/lib/i2pd +fi + +#DEBHELPER# + +exit 0 From 214cc8b810b15733a3ccffd91929e11e9d8b65a6 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 17 Dec 2015 04:45:31 +0000 Subject: [PATCH 0646/6300] * install reseed certs --- debian/i2pd.install | 1 + debian/i2pd.links | 1 + 2 files changed, 2 insertions(+) diff --git a/debian/i2pd.install b/debian/i2pd.install index 9947f5a5..f4e7e4f1 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -2,3 +2,4 @@ i2pd usr/sbin/ debian/i2pd.conf etc/i2pd/ debian/tunnels.conf etc/i2pd/ debian/subscriptions.txt etc/i2pd/ +contrib/certificates/ usr/share/i2pd/ diff --git a/debian/i2pd.links b/debian/i2pd.links index 36e4bb31..369cce5b 100644 --- a/debian/i2pd.links +++ b/debian/i2pd.links @@ -1,3 +1,4 @@ etc/i2pd/i2pd.conf var/lib/i2pd/i2p.conf etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.cfg etc/i2pd/subscriptions.txt var/lib/i2pd/subscriptions.txt +usr/share/i2pd/certificates var/lib/i2pd/certificates From 632d26e398084b40fcea599a2a63c9b5478e600b Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 17 Dec 2015 05:05:27 +0000 Subject: [PATCH 0647/6300] * update maintainer scripts --- debian/postinst | 10 +++++----- debian/postrm | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/debian/postinst b/debian/postinst index ddb5ef7c..4be6bf3f 100755 --- a/debian/postinst +++ b/debian/postinst @@ -1,4 +1,5 @@ #!/bin/sh +set -e I2PDHOME='/var/lib/i2pd' I2PDUSER='i2pd' @@ -8,18 +9,17 @@ case "$1" in # Older versions of adduser created the home directory. # The version of adduser in Debian unstable does not. # Create user and group as a system user. - getent passwd ${I2PDUSER} > /dev/null 2>&1 - if [ $? -eq 2 ]; then - adduser --system --quiet --group --home $I2PDHOME $I2PDUSER - else + if getent passwd $I2PDUSER > /dev/null 2>&1; then groupadd -f $I2PDUSER || true usermod -s "/bin/false" -e 1 $I2PDUSER > /dev/null || true + else + adduser --system --quiet --group --home $I2PDHOME $I2PDUSER fi touch /var/log/i2pd.log chown -f ${I2PDUSER}:adm /var/log/i2pd.log mkdir -p -m0750 $I2PDHOME - chown -f -R ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} + chown -f -R -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} ;; abort-upgrade|abort-remove|abort-deconfigure) echo "Aborting upgrade" diff --git a/debian/postrm b/debian/postrm index 7015e547..a188b52c 100755 --- a/debian/postrm +++ b/debian/postrm @@ -1,9 +1,9 @@ #!/bin/sh - set -e if [ "$1" = "purge" ]; then rm -f /etc/default/i2pd /var/log/i2pd.log + rm -rf /etc/i2pd rm -rf /var/lib/i2pd fi From c3238f4d0b561935a8838a42d807abd7650581ed Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 17 Dec 2015 07:58:09 +0000 Subject: [PATCH 0648/6300] * fix warnings of type mismatch (#298) --- Destination.h | 2 +- I2NPProtocol.h | 2 +- SSUData.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Destination.h b/Destination.h index d38fe30e..f75e3fc0 100644 --- a/Destination.h +++ b/Destination.h @@ -28,8 +28,8 @@ namespace client const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds - const int MAX_NUM_FLOODFILLS_PER_REQUEST = 7; const int DESTINATION_CLEANUP_TIMEOUT = 20; // in minutes + const unsigned int MAX_NUM_FLOODFILLS_PER_REQUEST = 7; // I2CP const char I2CP_PARAM_INBOUND_TUNNEL_LENGTH[] = "inbound.length"; diff --git a/I2NPProtocol.h b/I2NPProtocol.h index a3f75d7a..a38ff23b 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -97,7 +97,7 @@ namespace i2p const uint8_t DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP = 0x08; // 1000 const uint8_t DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP = 0x0C; // 1100 - const int MAX_NUM_TRANSIT_TUNNELS = 2500; + const unsigned int MAX_NUM_TRANSIT_TUNNELS = 2500; namespace tunnel { diff --git a/SSUData.h b/SSUData.h index d571bed3..392bfce6 100644 --- a/SSUData.h +++ b/SSUData.h @@ -27,8 +27,8 @@ namespace transport const int RESEND_INTERVAL = 3; // in seconds const int MAX_NUM_RESENDS = 5; const int DECAY_INTERVAL = 20; // in seconds - const int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds + const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check // data flags const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02; const uint8_t DATA_FLAG_WANT_REPLY = 0x04; From a994bbc36b241e78f0f486a35b4c7faae885441f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 18 Dec 2015 10:09:25 -0500 Subject: [PATCH 0649/6300] call CryptoConstants from Crypto.cpp only --- Crypto.cpp | 65 +++++++++++++++++++++++++++++++++++++++++++++++++- Crypto.h | 52 ++++------------------------------------ I2PControl.cpp | 4 +++- Reseed.cpp | 2 +- Signature.h | 27 +++++++-------------- 5 files changed, 80 insertions(+), 70 deletions(-) diff --git a/Crypto.cpp b/Crypto.cpp index 831ecf30..75ece1b7 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -66,7 +66,44 @@ namespace crypto const int rsae_ = 65537; - const CryptoConstants& GetCryptoConstants () + struct CryptoConstants + { + // DH/ElGamal + BIGNUM * elgp; + BIGNUM * elgg; + + // DSA + BIGNUM * dsap; + BIGNUM * dsaq; + BIGNUM * dsag; + + // RSA + BIGNUM * rsae; + + CryptoConstants (const uint8_t * elgp_, int elgg_, const uint8_t * dsap_, + const uint8_t * dsaq_, const uint8_t * dsag_, int rsae_) + { + elgp = BN_new (); + BN_bin2bn (elgp_, 256, elgp); + elgg = BN_new (); + BN_set_word (elgg, elgg_); + dsap = BN_new (); + BN_bin2bn (dsap_, 128, dsap); + dsaq = BN_new (); + BN_bin2bn (dsaq_, 20, dsaq); + dsag = BN_new (); + BN_bin2bn (dsag_, 128, dsag); + rsae = BN_new (); + BN_set_word (rsae, rsae_); + } + + ~CryptoConstants () + { + BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae); + } + }; + + static const CryptoConstants& GetCryptoConstants () { static CryptoConstants cryptoConstants (elgp_, elgg_, dsap_, dsaq_, dsag_, rsae_); return cryptoConstants; @@ -81,6 +118,32 @@ namespace crypto return true; } +// RSA + #define rsae GetCryptoConstants ().rsae + const BIGNUM * GetRSAE () + { + return rsae; + } + +// DSA + #define dsap GetCryptoConstants ().dsap + #define dsaq GetCryptoConstants ().dsaq + #define dsag GetCryptoConstants ().dsag + DSA * CreateDSA () + { + DSA * dsa = DSA_new (); + dsa->p = BN_dup (dsap); + dsa->q = BN_dup (dsaq); + dsa->g = BN_dup (dsag); + dsa->priv_key = NULL; + dsa->pub_key = NULL; + return dsa; + } + +// DH/ElGamal + #define elgp GetCryptoConstants ().elgp + #define elgg GetCryptoConstants ().elgg + // DH DHKeys::DHKeys (): m_IsUpdated (true) diff --git a/Crypto.h b/Crypto.h index d08e3b3c..fd49ebc5 100644 --- a/Crypto.h +++ b/Crypto.h @@ -6,64 +6,20 @@ #include #include #include +#include #include "Base.h" namespace i2p { namespace crypto { - struct CryptoConstants - { - // DH/ElGamal - BIGNUM * elgp; - BIGNUM * elgg; - - // DSA - BIGNUM * dsap; - BIGNUM * dsaq; - BIGNUM * dsag; - - // RSA - BIGNUM * rsae; - - CryptoConstants (const uint8_t * elgp_, int elgg_, const uint8_t * dsap_, - const uint8_t * dsaq_, const uint8_t * dsag_, int rsae_) - { - elgp = BN_new (); - BN_bin2bn (elgp_, 256, elgp); - elgg = BN_new (); - BN_set_word (elgg, elgg_); - dsap = BN_new (); - BN_bin2bn (dsap_, 128, dsap); - dsaq = BN_new (); - BN_bin2bn (dsaq_, 20, dsaq); - dsag = BN_new (); - BN_bin2bn (dsag_, 128, dsag); - rsae = BN_new (); - BN_set_word (rsae, rsae_); - } - - ~CryptoConstants () - { - BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae); - } - }; - - const CryptoConstants& GetCryptoConstants (); - - // DH/ElGamal - #define elgp GetCryptoConstants ().elgp - #define elgg GetCryptoConstants ().elgg + bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len); // DSA - #define dsap GetCryptoConstants ().dsap - #define dsaq GetCryptoConstants ().dsaq - #define dsag GetCryptoConstants ().dsag + DSA * CreateDSA (); // RSA - #define rsae GetCryptoConstants ().rsae - - bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len); + const BIGNUM * GetRSAE (); // DH class DHKeys diff --git a/I2PControl.cpp b/I2PControl.cpp index 5b262e54..4760caec 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -525,7 +525,9 @@ namespace client { EVP_PKEY * pkey = EVP_PKEY_new (); RSA * rsa = RSA_new (); - RSA_generate_key_ex (rsa, 4096, i2p::crypto::rsae, NULL); + BIGNUM * e = BN_dup (i2p::crypto::GetRSAE ()); + RSA_generate_key_ex (rsa, 4096, e, NULL); + BN_free (e); if (rsa) { EVP_PKEY_assign_RSA (pkey, rsa); diff --git a/Reseed.cpp b/Reseed.cpp index 1bbd2750..0aac168f 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -156,7 +156,7 @@ namespace data BIGNUM * s = BN_new (), * n = BN_new (); BN_bin2bn (signature, signatureLength, s); BN_bin2bn (it->second, i2p::crypto::RSASHA5124096_KEY_LENGTH, n); - BN_mod_exp (s, s, i2p::crypto::rsae, n, bnctx); // s = s^e mod n + BN_mod_exp (s, s, i2p::crypto::GetRSAE (), n, bnctx); // s = s^e mod n uint8_t * enSigBuf = new uint8_t[signatureLength]; i2p::crypto::bn2buf (s, enSigBuf, signatureLength); // digest is right aligned diff --git a/Signature.h b/Signature.h index 1e7ac2eb..a0b54468 100644 --- a/Signature.h +++ b/Signature.h @@ -44,11 +44,7 @@ namespace crypto DSAVerifier (const uint8_t * signingKey) { - m_PublicKey = DSA_new (); - m_PublicKey->p = BN_dup (dsap); - m_PublicKey->q = BN_dup (dsaq); - m_PublicKey->g = BN_dup (dsag); - m_PublicKey->priv_key = NULL; + m_PublicKey = CreateDSA (); m_PublicKey->pub_key = BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL); } @@ -86,12 +82,8 @@ namespace crypto DSASigner (const uint8_t * signingPrivateKey) { - m_PrivateKey = DSA_new (); - m_PrivateKey->p = BN_dup (dsap); - m_PrivateKey->q = BN_dup (dsaq); - m_PrivateKey->g = BN_dup (dsag); + m_PrivateKey = CreateDSA (); m_PrivateKey->priv_key = BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL); - m_PrivateKey->pub_key = NULL; } ~DSASigner () @@ -116,12 +108,7 @@ namespace crypto inline void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { - DSA * dsa = DSA_new (); - dsa->p = BN_dup (dsap); - dsa->q = BN_dup (dsaq); - dsa->g = BN_dup (dsag); - dsa->priv_key = NULL; - dsa->pub_key = NULL; + DSA * dsa = CreateDSA (); DSA_generate_key (dsa); bn2buf (dsa->priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); bn2buf (dsa->pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH); @@ -285,7 +272,7 @@ namespace crypto { m_PublicKey = RSA_new (); memset (m_PublicKey, 0, sizeof (RSA)); - m_PublicKey->e = BN_dup (rsae); + m_PublicKey->e = BN_dup (GetRSAE ()); m_PublicKey->n = BN_bin2bn (signingKey, keyLen, NULL); } @@ -319,7 +306,7 @@ namespace crypto { m_PrivateKey = RSA_new (); memset (m_PrivateKey, 0, sizeof (RSA)); - m_PrivateKey->e = BN_dup (rsae); + m_PrivateKey->e = BN_dup (GetRSAE ()); m_PrivateKey->n = BN_bin2bn (signingPrivateKey, keyLen, NULL); m_PrivateKey->d = BN_bin2bn (signingPrivateKey + keyLen, keyLen, NULL); } @@ -345,10 +332,12 @@ namespace crypto inline void CreateRSARandomKeys (size_t publicKeyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { RSA * rsa = RSA_new (); - RSA_generate_key_ex (rsa, publicKeyLen*8, rsae, NULL); + BIGNUM * e = BN_dup (GetRSAE ()); // make it non-const + RSA_generate_key_ex (rsa, publicKeyLen*8, e, NULL); bn2buf (rsa->n, signingPrivateKey, publicKeyLen); bn2buf (rsa->d, signingPrivateKey + publicKeyLen, publicKeyLen); bn2buf (rsa->n, signingPublicKey, publicKeyLen); + BN_free (e); // this e is not assigned to rsa->e RSA_free (rsa); } From c36a810bcbe00a8f2eb60249e4de72079bbbf05c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 18 Dec 2015 11:52:44 -0500 Subject: [PATCH 0650/6300] ignore extended options for SessionCreated and SessionConfirmed --- SSUSession.cpp | 84 ++++++++++++++++++++++++++++++-------------------- SSUSession.h | 12 ++++---- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index 62c79b72..1516b6c2 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -131,10 +131,10 @@ namespace transport } } - size_t SSUSession::GetSSUHeaderSize (uint8_t * buf) const + size_t SSUSession::GetSSUHeaderSize (const uint8_t * buf) const { size_t s = sizeof (SSUHeader); - if (((SSUHeader *)buf)->IsExtendedOptions ()) + if (((const SSUHeader *)buf)->IsExtendedOptions ()) s += buf[s] + 1; // byte right after header is extended options length return s; } @@ -145,6 +145,11 @@ namespace transport if (len <= sizeof (SSUHeader)) return; // drop empty message //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved auto headerSize = GetSSUHeaderSize (buf); + if (headerSize >= len) + { + LogPrint (eLogError, "SSU header size ", headerSize, " exceeds packet length ", len); + return; + } SSUHeader * header = (SSUHeader *)buf; switch (header->GetPayloadType ()) { @@ -152,13 +157,13 @@ namespace transport ProcessData (buf + headerSize, len - headerSize); break; case PAYLOAD_TYPE_SESSION_REQUEST: - ProcessSessionRequest (buf, len, senderEndpoint); + ProcessSessionRequest (buf + headerSize, len - headerSize, senderEndpoint); break; case PAYLOAD_TYPE_SESSION_CREATED: - ProcessSessionCreated (buf, len); + ProcessSessionCreated (buf, len); // buf with header break; case PAYLOAD_TYPE_SESSION_CONFIRMED: - ProcessSessionConfirmed (buf, len); + ProcessSessionConfirmed (buf, len); // buf with header break; case PAYLOAD_TYPE_PEER_TEST: LogPrint (eLogDebug, "SSU peer test received"); @@ -171,7 +176,7 @@ namespace transport break; } case PAYLOAD_TYPE_RELAY_RESPONSE: - ProcessRelayResponse (buf, len); + ProcessRelayResponse (buf + headerSize, len - headerSize); if (m_State != eSessionStateEstablished) m_Server.DeleteSession (shared_from_this ()); break; @@ -188,14 +193,14 @@ namespace transport } } - void SSUSession::ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) + void SSUSession::ProcessSessionRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { LogPrint (eLogDebug, "Session request received"); m_RemoteEndpoint = senderEndpoint; if (!m_DHKeysPair) m_DHKeysPair = transports.GetNextDHKeysPair (); - CreateAESandMacKey (buf + sizeof (SSUHeader)); - SendSessionCreated (buf + sizeof (SSUHeader)); + CreateAESandMacKey (buf); + SendSessionCreated (buf); } void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) @@ -209,7 +214,13 @@ namespace transport LogPrint (eLogDebug, "Session created received"); m_Timer.cancel (); // connect timer SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, signed on time - uint8_t * payload = buf + sizeof (SSUHeader); + auto headerSize = GetSSUHeaderSize (buf); + if (headerSize >= len) + { + LogPrint (eLogError, "Session created header size ", headerSize, " exceeds packet length ", len); + return; + } + uint8_t * payload = buf + headerSize; uint8_t * y = payload; CreateAESandMacKey (y); s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x @@ -253,7 +264,7 @@ namespace transport if (paddingSize > 0) signatureLen += (16 - paddingSize); //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved m_SessionKeyDecryption.SetIV (((SSUHeader *)buf)->iv); - m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); + m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); // TODO: non-const payload // verify if (!s.Verify (m_RemoteIdentity, payload)) LogPrint (eLogError, "Session created SSU signature verification failed"); @@ -261,10 +272,16 @@ namespace transport SendSessionConfirmed (y, ourAddress, addressSize + 2); } - void SSUSession::ProcessSessionConfirmed (uint8_t * buf, size_t len) + void SSUSession::ProcessSessionConfirmed (const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "Session confirmed received"); - uint8_t * payload = buf + sizeof (SSUHeader); + auto headerSize = GetSSUHeaderSize (buf); + if (headerSize >= len) + { + LogPrint (eLogError, "Session confirmed header size ", len, " exceeds packet length ", len); + return; + } + const uint8_t * payload = buf + headerSize; payload++; // identity fragment info uint16_t identitySize = bufbe16toh (payload); payload += 2; // size of identity fragment @@ -461,7 +478,7 @@ namespace transport Send (buf, msgLen); } - void SSUSession::ProcessRelayRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) + void SSUSession::ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) { uint32_t relayTag = bufbe32toh (buf); auto session = m_Server.FindRelaySession (relayTag); @@ -475,7 +492,7 @@ namespace transport uint8_t challengeSize = *buf; buf++; // challenge size buf += challengeSize; - uint8_t * introKey = buf; + const uint8_t * introKey = buf; buf += 32; // introkey uint32_t nonce = bufbe32toh (buf); SendRelayResponse (nonce, from, introKey, session->m_RemoteEndpoint); @@ -562,38 +579,37 @@ namespace transport LogPrint (eLogDebug, "SSU relay intro sent"); } - void SSUSession::ProcessRelayResponse (uint8_t * buf, size_t len) + void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "Relay response received"); - uint8_t * payload = buf + sizeof (SSUHeader); - uint8_t remoteSize = *payload; - payload++; // remote size - boost::asio::ip::address_v4 remoteIP (bufbe32toh (payload)); - payload += remoteSize; // remote address - uint16_t remotePort = bufbe16toh (payload); - payload += 2; // remote port - uint8_t ourSize = *payload; - payload++; // our size + uint8_t remoteSize = *buf; + buf++; // remote size + boost::asio::ip::address_v4 remoteIP (bufbe32toh (buf)); + buf += remoteSize; // remote address + uint16_t remotePort = bufbe16toh (buf); + buf += 2; // remote port + uint8_t ourSize = *buf; + buf++; // our size boost::asio::ip::address ourIP; if (ourSize == 4) { boost::asio::ip::address_v4::bytes_type bytes; - memcpy (bytes.data (), payload, 4); + memcpy (bytes.data (), buf, 4); ourIP = boost::asio::ip::address_v4 (bytes); } else { boost::asio::ip::address_v6::bytes_type bytes; - memcpy (bytes.data (), payload, 16); + memcpy (bytes.data (), buf, 16); ourIP = boost::asio::ip::address_v6 (bytes); } - payload += ourSize; // our address - uint16_t ourPort = bufbe16toh (payload); - payload += 2; // our port + buf += ourSize; // our address + uint16_t ourPort = bufbe16toh (buf); + buf += 2; // our port LogPrint ("Our external address is ", ourIP.to_string (), ":", ourPort); i2p::context.UpdateAddress (ourIP); - uint32_t nonce = bufbe32toh (payload); - payload += 4; // nonce + uint32_t nonce = bufbe32toh (buf); + buf += 4; // nonce auto it = m_RelayRequests.find (nonce); if (it != m_RelayRequests.end ()) { @@ -615,7 +631,7 @@ namespace transport LogPrint (eLogError, "Unsolicited RelayResponse, nonce=", nonce); } - void SSUSession::ProcessRelayIntro (uint8_t * buf, size_t len) + void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) { uint8_t size = *buf; if (size == 4) @@ -624,7 +640,7 @@ namespace transport boost::asio::ip::address_v4 address (bufbe32toh (buf)); buf += 4; // address uint16_t port = bufbe16toh (buf); - // send hole punch of 1 byte + // send hole punch of 0 bytes m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (address, port)); } else diff --git a/SSUSession.h b/SSUSession.h index b77f4801..59e834a0 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -96,22 +96,22 @@ namespace transport boost::asio::io_service& GetService (); void CreateAESandMacKey (const uint8_t * pubKey); - size_t GetSSUHeaderSize (uint8_t * buf) const; + size_t GetSSUHeaderSize (const uint8_t * buf) const; void PostI2NPMessages (std::vector > msgs); void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session - void ProcessSessionRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); + void ProcessSessionRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); void SendSessionRequest (); void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce); void ProcessSessionCreated (uint8_t * buf, size_t len); void SendSessionCreated (const uint8_t * x); - void ProcessSessionConfirmed (uint8_t * buf, size_t len); + void ProcessSessionConfirmed (const uint8_t * buf, size_t len); void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen); - void ProcessRelayRequest (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); + void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to); void SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from); - void ProcessRelayResponse (uint8_t * buf, size_t len); - void ProcessRelayIntro (uint8_t * buf, size_t len); + void ProcessRelayResponse (const uint8_t * buf, size_t len); + void ProcessRelayIntro (const uint8_t * buf, size_t len); void Established (); void Failed (); void ScheduleConnectTimer (); From 4b2bd6e18fa17687423f760edb81c0703ef6c944 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Fri, 4 Dec 2015 13:58:55 -0600 Subject: [PATCH 0651/6300] Include dir for precompiled headers with gcc --- build/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 43d8348f..7b1314ea 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -188,7 +188,7 @@ else() endif () if (WITH_PCH) - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + include_directories(${CMAKE_BINARY_DIR}) add_library(stdafx STATIC "${CMAKE_SOURCE_DIR}/stdafx.cpp") if(MSVC) target_compile_options(stdafx PRIVATE /Ycstdafx.h /Zm155) From bc775140bb750bdbbcaa814a2c0e734e6022c0e0 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sat, 12 Dec 2015 18:05:14 -0600 Subject: [PATCH 0652/6300] appveyor.yml --- appveyor.yml | 196 +++++++++++++++++++++++++++++++++++++++++++ build/CMakeLists.txt | 12 +++ 2 files changed, 208 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..ae02b812 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,196 @@ +version: 1.0.{build} +pull_requests: + do_not_increment_build_number: true +branches: + only: + - openssl +skip_tags: true +os: Visual Studio 2015 +shallow_clone: true +clone_depth: 1 +init: +- cmd: >- + mkdir \projects\instdir + + rem Appveyor has win32 openssl pre-installed that is picked up erroneously even for 64-bit. Cleaning the mess... Should happen before restoring cache. + + rem Might consider passing OPENSSL_ROOT_DIR + + if exist \OpenSSL-Win32 rmdir /S /Q \OpenSSL-Win32 + + if exist \OpenSSL-Win64 rmdir /S /Q \OpenSSL-Win64 + + if exist \OpenSSL rmdir /S /Q \OpenSSL +environment: + BOOST_ROOT: C:\Libraries\boost_1_59_0 + MINIUPNPC: miniupnpc-1.9.20151026 + OPENSSL: OpenSSL_1_0_2e + ZLIB: zlib-1.2.8 + matrix: + # - type: static + # msvc: 14 + # x64: 0 + # - type: static + # variant: Release + # # FIXME why is this necessary with Appveyor??? + # cmake: -DSSL_EAY=/mingw32/lib/libssl.a -DLIB_EAY=/mingw32/lib/libcrypto.a + - type: shared + variant: Release + - type: static + msvc: 12 + x64: 1 + variant: RelWithDebInfo + - type: static + msvc: 14 + variant: RelWithDebInfo + cmake: -DWITH_PCH=ON + # - type: static + # msvc: 12 + # - type: shared + # msvc: 14 + # variant: Debug + # - type: shared + # variant: Release + # cmake: -DWITH_PCH=ON + # x64: 1 +install: +- if not exist \projects\miniupnpc\ ( + mkdir \projects\miniupnpc + && curl -sL http://miniupnp.free.fr/files/download.php?file=%MINIUPNPC%.tar.gz -o \projects\miniupnpc\%MINIUPNPC%.tar.gz + ) +- tar --strip-components=1 --directory=\projects\miniupnpc -xzf \projects\miniupnpc\%MINIUPNPC%.tar.gz +- if not exist \projects\zlib\ ( + mkdir \projects\zlib + && cd \projects\zlib + && curl -sLO http://zlib.net/%ZLIB%.tar.gz + ) +- tar --strip-components=1 --directory=\projects\zlib -xzf \projects\zlib\%ZLIB%.tar.gz +- patch -p0 C:/projects/zlib/CMakeLists.txt %APPVEYOR_BUILD_FOLDER%/build/cmake-zlib-static.patch +- patch -p0 C:/projects/zlib/CMakeLists.txt %APPVEYOR_BUILD_FOLDER%/build/cmake-zlib-amd64.patch +- if "%type%" == "static" ( + set "static=ON" + && set "boostlib=lib" + ) else ( + set "static=OFF" + && set "dll=dll" + ) +- if "%x64%"=="1" ( + set "bitness=64" + && set "openssl_target=VC-WIN64A" + && set "zlib_asm=-DAMD64=ON" + ) else ( + set "bitness=32" + && set "openssl_target=VC-WIN32" + && set "zlib_asm=-DASM686=ON "-DCMAKE_ASM_MASM_FLAGS=/W0 /safeseh"" + ) +- set /a generator=%msvc%+2001 +- if defined msvc ( + ( + if "%x64%" == "1" ( + call "C:\Program Files (x86)\Microsoft Visual Studio %msvc%.0\VC\vcvarsall.bat" amd64 + && set "generator=Visual Studio %msvc% %generator% Win64" + ) else ( + call "C:\Program Files (x86)\Microsoft Visual Studio %msvc%.0\VC\vcvarsall.bat" x86 + && set "generator=Visual Studio %msvc% %generator%" + ) + ) + && set "zlib_root=C:/stage/zlib-Win%bitness%-vc%msvc%-%type%" + && if "%variant%" neq "Debug" ( + set "boost_variant=variant=release" + && set "boostdbg=-gd" + ) + ) else ( + set "generator=Unix Makefiles" + ) +- if defined msvc if not exist %zlib_root% ( + mkdir \projects\zlib-build + && cd \projects\zlib-build + && cmake ../zlib -G "%generator%" %zlib_asm% -DWITH_STATIC=%static% -DCMAKE_INSTALL_PREFIX=%zlib_root% > c:\projects\instdir\build_zlib.log + && cmake --build . --config Release --target INSTALL >> c:\projects\instdir\build_zlib.log + || type c:\projects\instdir\build_zlib.log + ) +- cmd: >- + rem cinst nasm + + cd \projects + + if not exist nasm-2.11.08-installer.exe curl --silent --location --remote-name http://www.nasm.us/pub/nasm/releasebuilds/2.11.08/win32/nasm-2.11.08-installer.exe + + nasm-2.11.08-installer.exe /S + + set "PATH=%PATH%;C:\Program Files (x86)\nasm" + + if not exist %OPENSSL%.zip curl --silent --location --remote-name https://github.com/openssl/openssl/archive/%OPENSSL%.zip +- cd %BOOST_ROOT% +- if defined msvc if not exist "stage%bitness%\lib\%boostlib%boost_system-vc%msvc%0-mt%boostdbg%*" ( + bootstrap > c:\projects\instdir\build_boost.log + && b2 toolset=msvc-%msvc%.0 %boost_variant% link=%type% runtime-link=%type% address-model=%bitness% --build-type=minimal --with-filesystem --with-program_options --with-regex --with-date_time --stagedir=stage%bitness% >> c:\projects\instdir\build_boost.log + || type c:\projects\instdir\build_boost.log + ) +- if defined msvc if not exist C:\stage\OpenSSL-Win%bitness%-vc%msvc%-%type%\ ( + cd \projects + && 7z x %OPENSSL%.zip > NUL + && cd openssl-%OPENSSL% + && perl Configure %openssl_target% no-rc2 no-rc4 no-rc5 no-idea no-bf no-cast no-whirlpool no-md2 no-md4 no-ripemd no-mdc2 no-camellia no-seed no-comp no-krb5 no-gmp no-rfc3779 no-ec2m no-ssl2 no-jpake no-srp no-sctp no-srtp --prefix=c:\stage\OpenSSL-Win%bitness%-vc%msvc%-%type% > c:\projects\instdir\build_openssl.log + && ( if "%x64%" == "1" ( ms\do_win64a >> c:\projects\instdir\build_openssl.log ) else ( ms\do_nasm >> c:\projects\instdir\build_openssl.log ) ) + && nmake -f ms\nt%dll%.mak install >> c:\projects\instdir\build_openssl.log 2>&1 + && mklink /J \OpenSSL \stage\OpenSSL-Win%bitness%-vc%msvc%-%type% + || type c:\projects\instdir\build_openssl.log + ) +- rem already there: mingw-w64-i686-openssl mingw-w64-i686-gcc cmake +- if not defined msvc ( + C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -Sy bash pacman pacman-mirrors msys2-runtime msys2-runtime-devel" + && if "%x64%" == "1" ( + C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-x86_64-boost mingw-w64-x86_64-miniupnpc" + ) else ( + C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-i686-boost mingw-w64-i686-miniupnpc" + ) + ) +cache: +- C:\projects\%OPENSSL%.zip +- C:\projects\nasm-2.11.08-installer.exe +- C:\projects\miniupnpc\%MINIUPNPC%.tar.gz +- C:\stage +- '%BOOST_ROOT%\stage32' +- '%BOOST_ROOT%\stage64' +- C:\projects\zlib\%ZLIB%.tar.gz +build_script: +- cmd: >- + mkdir \projects\build + + rem FIXME use fixup_bundle in cmake + + rem msbuild i2pd.sln /p:Configuration=Release + + if defined variant ( set cmake_extra=-DCMAKE_BUILD_TYPE=%variant% && set "cmake_build=--config %variant%" ) + + echo "bitness=%bitness%; static=%static%; dll=%dll%; type=%type%; generator=%generator%; variant=%variant%; cmake=%cmake%; cmake_extra=%cmake_extra%" +- if not defined msvc ( + C:\msys64\usr\bin\bash -lc "export PATH=/mingw%bitness%/bin:/usr/bin && cd /c/projects/build && cmake /c/projects/i2pd/build -G 'Unix Makefiles' -DWITH_AESNI=ON -DWITH_UPNP=ON %cmake% %cmake_extra% -DWITH_STATIC=%static% -DWITH_HARDENING=ON -DCMAKE_INSTALL_PREFIX:PATH=/c/projects/instdir -DCMAKE_FIND_ROOT_PATH=/mingw%bitness% && make install" + && 7z a -tzip -mx9 -mmt C:\projects\i2pd\i2pd-mingw-win%bitness%-%type%.zip C:\projects\instdir\* C:\msys64\mingw%bitness%\bin\zlib1.dll C:\msys64\mingw%bitness%\bin\*eay32.dll + ) +- rem We are fine with multiple generated configurations in MS solution. Will use later +- if defined msvc ( + cd \projects\build + && cmake ..\i2pd\build -G "%generator%" -DWITH_UPNP=ON %cmake% -DWITH_STATIC=%static% -DZLIB_ROOT=%zlib_root% -DBoost_LIBRARY_DIR:PATH=%BOOST_ROOT%/stage%bitness%/lib -DCMAKE_INSTALL_PREFIX:PATH=c:/projects/instdir + && cmake --build . %cmake_build% --target install + && 7z a -tzip -mx9 -mmt C:\projects\i2pd\i2pd-vc%msvc%-win%bitness%-%type%.zip C:\projects\instdir\bin\*.exe %zlib_root%\bin\*.dll %BOOST_ROOT%\stage%bitness%\lib\*.dll %APPVEYOR_BUILD_FOLDER%\LICENSE + && cmake --build . %cmake_build% --target package + && xcopy i2pd*win*.exe ..\i2pd\ + ) +test: off +artifacts: +- path: i2pd-vc12-win64-static.zip +- path: i2pd-vc12-win32-static.zip +- path: i2pd-vc12-win64-shared.zip +- path: i2pd-vc12-win32-shared.zip +- path: i2pd-vc14-win64-static.zip +- path: i2pd-vc14-win32-static.zip +- path: i2pd-vc14-win64-shared.zip +- path: i2pd-vc14-win32-shared.zip +- path: i2pd-mingw-win64-static.zip +- path: i2pd-mingw-win32-static.zip +- path: i2pd-mingw-win64-shared.zip +- path: i2pd-mingw-win32-shared.zip +- path: i2pd-2.1.0-win64.exe +- path: i2pd-2.1.0-win32.exe diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 7b1314ea..e4deba38 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -321,3 +321,15 @@ if (WITH_BINARY) install(FILES $ DESTINATION "bin" CONFIGURATIONS DEBUG) endif () endif () + +install(FILES ../LICENSE + DESTINATION . + COMPONENT Runtime + ) +# Take a copy on Appveyor +install(FILES "C:/projects/openssl-$ENV{OPENSSL}/LICENSE" + DESTINATION . + COMPONENT Runtime + RENAME LICENSE_OPENSSL + OPTIONAL # for local builds only! + ) From 65f993677f228c094df50a75bc6a6fa244ddc88f Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sat, 12 Dec 2015 18:06:11 -0600 Subject: [PATCH 0653/6300] Remove unnecessary thread & chrono Boost libs Prevent boost thread auto-link erroneous attempts with MSVC --- build/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index e4deba38..8b93944b 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -90,6 +90,8 @@ endif () # compiler flags customization (by vendor) if (MSVC) add_definitions( -D_WIN32_WINNT=_WIN32_WINNT_WINXP -DWIN32_LEAN_AND_MEAN -DNOMINMAX ) #-DOPENSSL_NO_SSL2 -DOPENSSL_USE_DEPRECATED + # TODO Check & report to Boost dev, there should be no need for these two + add_definitions( -DBOOST_THREAD_NO_LIB -DBOOST_CHRONO_NO_LIB ) else() set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch" ) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic" ) @@ -184,7 +186,7 @@ else() # HINT: revert c266cff CMakeLists.txt: compilation speed up set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" ) endif () - add_definitions(-DBOOST_ALL_DYN_LINK) + add_definitions(-DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_DATE_TIME_DYN_LINK -DBOOST_REGEX_DYN_LINK) endif () if (WITH_PCH) @@ -215,7 +217,7 @@ endif() target_link_libraries(i2pdclient libi2pd) -find_package ( Boost COMPONENTS system filesystem regex program_options date_time thread chrono REQUIRED ) +find_package ( Boost COMPONENTS system filesystem regex program_options date_time REQUIRED ) if(NOT DEFINED Boost_INCLUDE_DIRS) message(SEND_ERROR "Boost is not found, or your boost version was bellow 1.46. Please download Boost!") endif() From edd9a18257a0dac39cdf4d2a75468d4620d496bb Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sat, 12 Dec 2015 18:11:48 -0600 Subject: [PATCH 0654/6300] Cleanup some CMake msys specifics * Exclude MSYS from -z relro * WIN32_LEAN_AND_MEAN --- build/CMakeLists.txt | 8 +++++--- util.cpp | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 8b93944b..d8e5de8b 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -93,6 +93,9 @@ if (MSVC) # TODO Check & report to Boost dev, there should be no need for these two add_definitions( -DBOOST_THREAD_NO_LIB -DBOOST_CHRONO_NO_LIB ) else() + if (MSYS OR MINGW) + add_definitions( -DWIN32_LEAN_AND_MEAN ) + endif () set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch" ) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic" ) # TODO: The following is incompatible with static build and enabled hardening for OpenWRT. @@ -160,9 +163,8 @@ endif() if (WITH_STATIC) set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_RUNTIME ON) - if (WIN32 OR MSYS) + if (WIN32 AND NOT MSYS AND NOT MINGW) # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace - # Note that you might need to rebuild Crypto++ foreach(flag_var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) @@ -303,7 +305,7 @@ if (WITH_BINARY) endif() endif() - if (WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if (WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT MSYS AND NOT MINGW) set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-z relro -z now" ) endif () diff --git a/util.cpp b/util.cpp index b2faddc2..6d2ee500 100644 --- a/util.cpp +++ b/util.cpp @@ -28,7 +28,9 @@ #include #include +#ifdef _MSC_VER #pragma comment(lib, "IPHLPAPI.lib") +#endif #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) #define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) From e1c69a625076a4c7ab8ff5f898620d10e425acaa Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Fri, 4 Dec 2015 18:58:14 -0600 Subject: [PATCH 0655/6300] Transparency in icon --- Win32/Itoopie.cmd | 14 ++++++++++++++ Win32/ictoopie.bmp | Bin 0 -> 25818 bytes Win32/ictoopie.ico | Bin 99678 -> 180626 bytes 3 files changed, 14 insertions(+) create mode 100644 Win32/Itoopie.cmd create mode 100644 Win32/ictoopie.bmp diff --git a/Win32/Itoopie.cmd b/Win32/Itoopie.cmd new file mode 100644 index 00000000..f7d895c8 --- /dev/null +++ b/Win32/Itoopie.cmd @@ -0,0 +1,14 @@ +@echo off +convert Itoopie.svg ^ + -fuzz 90%% -fill transparent -floodfill 2x2 white -fuzz 20%% -fill #AE0E99 -opaque red ^ + -fill #FBBC11 -opaque yellow ^ + ( -clone 0 -resize 256x256 ) ^ + ( -clone 0 -resize 128x128 ) ^ + ( -clone 0 -resize 64x64 ) ^ + ( -clone 0 -resize 48x48 ) ^ + ( -clone 0 -resize 32x32 ) ^ + ( -clone 0 -resize 24x24 ) ^ + ( -clone 0 -resize 16x16 ) ^ + ( -size 150x57 xc:white -clone 0 -geometry 57x57+46+0 -composite -gravity center -write BMP3:ictoopie.bmp +delete ) ^ + ( -clone 0 -write Itoopie_purple.png +delete ) ^ + -delete 0 ictoopie.ico diff --git a/Win32/ictoopie.bmp b/Win32/ictoopie.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c92f7c583975d802536ed3def0ae5a733f891137 GIT binary patch literal 25818 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 literal 0 HcmV?d00001 diff --git a/Win32/ictoopie.ico b/Win32/ictoopie.ico index d92a598a5d60b8ff738ca108ce275de53d605fa0..077479a2ab8c7304f6dca1ac1770176394134cc6 100644 GIT binary patch literal 180626 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 iJmHh&v-Y;wb literal 99678 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 Date: Sun, 6 Dec 2015 15:46:42 -0600 Subject: [PATCH 0656/6300] Sync Windows VERSIONINFO resource with version.h --- Win32/Resource.rc | 39 +------------------------------------- Win32/Resource.rc2 | 47 ++++++++++++++++++++++++++++++++++++++++++++++ version.h | 18 ++++++++++++++++-- 3 files changed, 64 insertions(+), 40 deletions(-) create mode 100644 Win32/Resource.rc2 diff --git a/Win32/Resource.rc b/Win32/Resource.rc index e8b77bef..c8643e8d 100644 --- a/Win32/Resource.rc +++ b/Win32/Resource.rc @@ -54,44 +54,6 @@ END // remains consistent on all systems. MAINICON ICON "ictoopie.ico" -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION 2,1,0,0 - PRODUCTVERSION 0,9,23,0 - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS 0x1L -#else - FILEFLAGS 0x0L -#endif - FILEOS 0x40004L - FILETYPE 0x1L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "CompanyName", "Purple I2P" - VALUE "FileDescription", "C++ I2P daemon" - VALUE "FileVersion", "2.1.0.0" - VALUE "InternalName", "Resource.rc" - VALUE "LegalCopyright", "Copyright (C) 2013-2015, The PurpleI2P Project" - VALUE "OriginalFilename", "i2pd" - VALUE "ProductName", "Purple I2P" - VALUE "ProductVersion", "0.9.23.0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END - #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// @@ -103,6 +65,7 @@ END // Generated from the TEXTINCLUDE 3 resource. // +#include "Resource.rc2" ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED diff --git a/Win32/Resource.rc2 b/Win32/Resource.rc2 new file mode 100644 index 00000000..6b9e4aa7 --- /dev/null +++ b/Win32/Resource.rc2 @@ -0,0 +1,47 @@ +// +// Resource.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED +#error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + +#include "../version.h" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION I2PD_VERSION_MAJOR,I2PD_VERSION_MINOR,I2PD_VERSION_MICRO,I2PD_VERSION_PATCH + PRODUCTVERSION I2P_VERSION_MAJOR,I2P_VERSION_MINOR,I2P_VERSION_MICRO,I2P_VERSION_PATCH + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Purple I2P" + VALUE "FileDescription", "C++ I2P daemon" + VALUE "FileVersion", I2PD_VERSION + VALUE "InternalName", CODENAME + VALUE "LegalCopyright", "Copyright (C) 2013-2015, The PurpleI2P Project" + VALUE "OriginalFilename", "i2pd" + VALUE "ProductName", "Purple I2P" + VALUE "ProductVersion", I2P_VERSION + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/version.h b/version.h index 825be574..eedde45c 100644 --- a/version.h +++ b/version.h @@ -2,7 +2,21 @@ #define _VERSION_H_ #define CODENAME "Purple" -#define VERSION "2.1.0" -#define I2P_VERSION "0.9.23" + +#define STRINGIZE(x) #x +#define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) + +#define I2PD_VERSION_MAJOR 2 +#define I2PD_VERSION_MINOR 1 +#define I2PD_VERSION_MICRO 0 +#define I2PD_VERSION_PATCH 0 +#define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) +#define VERSION I2PD_VERSION + +#define I2P_VERSION_MAJOR 0 +#define I2P_VERSION_MINOR 9 +#define I2P_VERSION_MICRO 23 +#define I2P_VERSION_PATCH 0 +#define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #endif From 3334281949cf80ef836c59c78a970c7fc060b1c1 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sun, 6 Dec 2015 15:51:40 -0600 Subject: [PATCH 0657/6300] Search for patch tool with CMake This is to enable static build of "bundled" zlib with MSVC --- build/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index d8e5de8b..ba12f9ea 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -239,13 +239,13 @@ endif() find_package ( ZLIB ) if (NOT ZLIB_FOUND ) # We are probably on Windows + find_program( PATCH patch C:/Program Files/Git/usr/bin C:/msys64/usr/bin C:/msys32/usr/bin C:/Strawberry/c/bin ) include( ExternalProject ) ExternalProject_Add(zlib-project URL http://zlib.net/zlib-1.2.8.tar.gz URL_MD5 44d667c142d7cda120332623eab69f40 PREFIX ${CMAKE_CURRENT_BINARY_DIR}/zlib - # patch on Windows might be found in C:/Program Files/Git/usr/bin - PATCH_COMMAND patch -p0 < ${CMAKE_CURRENT_SOURCE_DIR}/cmake-zlib-static.patch + PATCH_COMMAND "${PATCH}" -p0 < ${CMAKE_CURRENT_SOURCE_DIR}/cmake-zlib-static.patch CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= -DWITH_STATIC=${WITH_STATIC} -DAMD64=ON -DASM686=ON "-DCMAKE_ASM_MASM_FLAGS=/W0 /safeseh" From 51146d4152f6825c5e3791c41495bcc671bc453c Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sun, 6 Dec 2015 20:45:44 -0600 Subject: [PATCH 0658/6300] MSVC optimization & hardening --- build/CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index ba12f9ea..f8556e09 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -92,6 +92,10 @@ if (MSVC) add_definitions( -D_WIN32_WINNT=_WIN32_WINNT_WINXP -DWIN32_LEAN_AND_MEAN -DNOMINMAX ) #-DOPENSSL_NO_SSL2 -DOPENSSL_USE_DEPRECATED # TODO Check & report to Boost dev, there should be no need for these two add_definitions( -DBOOST_THREAD_NO_LIB -DBOOST_CHRONO_NO_LIB ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL" ) + set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /INCREMENTAL:NO /LTCG" ) + set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} /GL" ) + set( CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /INCREMENTAL:NO /LTCG" ) else() if (MSYS OR MINGW) add_definitions( -DWIN32_LEAN_AND_MEAN ) @@ -127,6 +131,11 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # more tweaks endif () +if (WITH_HARDENING AND MSVC) + # Most security options like dynamic base, buffer & stack checks are ON by default + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /guard:cf" ) +endif () + # compiler flags customization (by system) if (CMAKE_SYSTEM_NAME STREQUAL "Linux") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonLinux.cpp") From 5f8223ebb529ff753f09abdba4c4179c4df086c7 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Mon, 7 Dec 2015 14:53:38 -0600 Subject: [PATCH 0659/6300] Patch for 64-bit zlib build with MSVC assembly --- build/CMakeLists.txt | 9 +++++++-- build/cmake-zlib-amd64.patch | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 build/cmake-zlib-amd64.patch diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index f8556e09..a12623dd 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -250,14 +250,19 @@ if (NOT ZLIB_FOUND ) # We are probably on Windows find_program( PATCH patch C:/Program Files/Git/usr/bin C:/msys64/usr/bin C:/msys32/usr/bin C:/Strawberry/c/bin ) include( ExternalProject ) + if( CMAKE_SIZEOF_VOID_P EQUAL 8 ) + set( ZLIB_EXTRA -DAMD64=ON ) + else() + set( ZLIB_EXTRA -DASM686=ON "-DCMAKE_ASM_MASM_FLAGS=/W0 /safeseh" ) + endif() ExternalProject_Add(zlib-project URL http://zlib.net/zlib-1.2.8.tar.gz URL_MD5 44d667c142d7cda120332623eab69f40 PREFIX ${CMAKE_CURRENT_BINARY_DIR}/zlib PATCH_COMMAND "${PATCH}" -p0 < ${CMAKE_CURRENT_SOURCE_DIR}/cmake-zlib-static.patch + && "${PATCH}" -p0 < ${CMAKE_CURRENT_SOURCE_DIR}/cmake-zlib-amd64.patch CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= - -DWITH_STATIC=${WITH_STATIC} - -DAMD64=ON -DASM686=ON "-DCMAKE_ASM_MASM_FLAGS=/W0 /safeseh" + -DWITH_STATIC=${WITH_STATIC} ${ZLIB_EXTRA} ) if (WITH_PCH) add_dependencies( stdafx zlib-project ) diff --git a/build/cmake-zlib-amd64.patch b/build/cmake-zlib-amd64.patch new file mode 100644 index 00000000..7ea1d9fe --- /dev/null +++ b/build/cmake-zlib-amd64.patch @@ -0,0 +1,10 @@ +--- CMakeLists.txt.orig 2015-12-07 14:19:36.447689600 -0600 ++++ CMakeLists.txt 2015-12-07 14:18:23.004419900 -0600 +@@ -165,6 +165,7 @@ + ENABLE_LANGUAGE(ASM_MASM) + set(ZLIB_ASMS + contrib/masmx64/gvmat64.asm ++ contrib/masmx64/inffas8664.c + contrib/masmx64/inffasx64.asm + ) + endif() From 66ceb573dccd92da4df0d9d02368f5faa760c2eb Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Fri, 11 Dec 2015 14:16:43 -0600 Subject: [PATCH 0660/6300] Update Windows build docs --- docs/build_notes_windows.md | 287 ++++++++++++++++++++++++++++-------- 1 file changed, 223 insertions(+), 64 deletions(-) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index 5e9e465e..d1b2f81f 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -1,86 +1,245 @@ -Building i2pd for Windows +Building i2pd on Windows ========================= -!!! WARNING This file may be outdated. +There are two approaches available to build i2pd on Windows. The best +one depends on your needs and personal preferences. One is to use +msys2 and [unix alike infrastructure](build_notes_unix.md). Another +one is to use Visual Studio. While there might be no difference for +end users of i2pd daemon, developers, however, shall be wary of +differences in C++ name mangling between the two compilers when making +a choice to be able to link their software against libi2pd. + +If you are a stranger to C++ with no development tools installed on +your system and your only goal is to have i2pd up and running from the +most recent source, consider using msys2. Although it relies on +command line operations, it should be straight forward. + +In this guide, we will use CMake for both approaches and we will +assume that you typically have your projects in C:\dev\ as your +development location for the sake of convenience. Adjust paths +accordingly if it is not the case. Note that msys uses unix-alike +paths like /c/dev/ for C:\dev\. + +msys2 +----- + +Get it from https://msys2.github.io and update it as described +there. Use the installer appropriate for the bitness of your Windows +OS. You will be able to build 32-bit applications if you install +64-bit version of msys2. For 64-bit, use *mingw-w64-x86_64* prefix +instead of *mingw-w64-i686* for the packages mentioned below, and use +*/mingw64* as CMake find root. + +Install all prerequisites and download i2pd source: + +```bash +pacman -S mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-gcc mingw-w64-i686-miniupnpc cmake git +mkdir -p /c/dev/i2pd +cd /c/dev/i2pd +git clone https://github.com/PurpleI2P/i2pd.git +cd i2pd +``` + +Check with `git status` that you are on *openssl* branch. If it is not +the case, do `git checkout openssl`. + +```sh +git pull origin openssl --ff-only # to update sources if you are rebuilding after a while +mkdir -p mingw32.build # CMake build folder +cd mingw32.build +export PATH=/mingw32/bin:/usr/bin # we need compiler on PATH which is usually heavily cluttered on Windows +cmake ../build -G "Unix Makefiles" -DWITH_UPNP=ON -DWITH_PCH=ON \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=../mingw32.stage -DCMAKE_FIND_ROOT_PATH=/mingw32 +``` + +If your processor has +[AES instruction set](https://en.wikipedia.org/wiki/AES_instruction_set), +you may try adding `-DWITH_AESNI=ON`. No check is done however, it +will compile but will crash with `Illegal instruction` if not supported. + +Make sure CMake found proper libraries and compiler. This might be the +case if you have Strawberry Perl installed as it alters PATH and you +failed to override it like mentioned above. You should see something +like + +``` +-- The C compiler identification is GNU 5.2.0 +-- The CXX compiler identification is GNU 5.2.0 +-- Check for working C compiler: /mingw32/bin/gcc.exe +-- Check for working C compiler: /mingw32/bin/gcc.exe -- works +-- Detecting C compiler ABI info +-- Detecting C compiler ABI info - done +-- Detecting C compile features +-- Detecting C compile features - done +-- Check for working CXX compiler: /mingw32/bin/c++.exe +-- Check for working CXX compiler: /mingw32/bin/c++.exe -- works +-- Detecting CXX compiler ABI info +-- Detecting CXX compiler ABI info - done +-- Detecting CXX compile features +-- Detecting CXX compile features - done +-- Performing Test CXX11_SUPPORTED +-- Performing Test CXX11_SUPPORTED - Success +-- Performing Test CXX0X_SUPPORTED +-- Performing Test CXX0X_SUPPORTED - Success +-- Looking for include file pthread.h +-- Looking for include file pthread.h - found +-- Looking for pthread_create +-- Looking for pthread_create - found +-- Found Threads: TRUE +-- Boost version: 1.59.0 +-- Found the following Boost libraries: +-- system +-- filesystem +-- regex +-- program_options +-- date_time +-- thread +-- chrono +-- Found OpenSSL: /mingw32/lib/libssl.dll.a;/mingw32/lib/libcrypto.dll.a (found version "1.0.2d") +-- Found MiniUPnP headers: /mingw32/include +-- Found ZLIB: /mingw32/lib/libz.dll.a (found version "1.2.8") +-- --------------------------------------- +-- Build type : RelWithDebInfo +-- Compiler vendor : GNU +-- Compiler version : 5.2.0 +-- Compiler path : /mingw32/bin/c++.exe +-- Install prefix: : ../mingw32.stage +-- Options: +-- AESNI : OFF +-- HARDENING : OFF +-- LIBRARY : ON +-- BINARY : ON +-- STATIC BUILD : OFF +-- UPnP : ON +-- PCH : ON +-- --------------------------------------- +-- Configuring done +-- Generating done +-- Build files have been written to: /c/dev/i2pd/i2pd/mingw32.build +``` + +Now it is time to compile everything. If you have a multicore processor +you can add `-j` flag. + + make -j4 install + +You should be able to run ./i2pd . If you need to start from the new +shell, consider starting *MinGW-w64 Win32 Shell* instead of *MSYS2 Shell* as +it adds`/minw32/bin` to the PATH. + +### Caveats + +It is important to restrict PATH as described above. If you have +Strawberry Perl and/or Mercurial installed, it will pick up gcc & +openssl from the wrong places. + +If you do use precompiled headers to speed up compilation +(recommended), things can go wrong if compiler options have changed +for whatever reason. Just delete `stdafx.h.gch` found in your build +folder, note the file extension. + +If you are an Arch Linux user, refrain from updating system with +`pacman -Syu`. Always update runtime separately as described on the +home page, otherwise you might end up with DLLs incompatibility +problems. + + +Using Visual Studio +------------------- Requirements for building: -* Visual Studio 2013 (tested with VS2013 Update 1, Update 3, and Update 4) -* Boost (tested with 1.56, 1.57, and 1.58) -* Crypto++ (tested with 5.6.2) +* [CMake](https://cmake.org/) (tested with 3.1.3) +* [Visual Studio Community Edition](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) (tested with VS2013 Update 4) +* [Boost](http://www.boost.org/) (tested with 1.59) +* Optionally [MiniUPnP](http://miniupnp.free.f) (tested with 1.9), we need only few client headers +* OpenSSL (tested with 1.0.1p and 1.0.2e), if building from sources (recommended), you'll need as well + * [Netwide assembler](www.nasm.us) + * Strawberry Perl or ActiveState Perl, do NOT try msys2 perl, it won't work -Building Boost (32-bit) ------------------------ +## Building Boost -Open a Visual Studio x86 command prompt and run the following: +Open a Command Prompt (there is no need to start Visual Studio command +prompt to build Boost) and run the following: - cd C:\path\to\boost\sources + cd C:\dev\boost bootstrap - b2 toolset=msvc-12.0 --build-type=complete --libdir=C:\Boost\lib\Win32 install --with-filesystem --with-program_options --with-regex --with-date_time + b2 toolset=msvc-12.0 --build-type=complete --with-filesystem --with-program_options --with-regex --with-date_time +If you are on 64-bit Windows and you want to build 64-bit version as well -Building Boost (64-bit) ------------------------ + b2 toolset=msvc-12.0 --build-type=complete --stagedir=stage64 address-model=64 --with-filesystem --with-program_options --with-regex --with-date_time -Open a Visual Studio x64 command prompt and run the following: +After Boost is compiled, set the environment variable `BOOST_ROOT` to +the directory Boost was unpacked to, e.g., C:\dev\boost. - cd C:\path\to\boost\sources - bootstrap - b2 toolset=msvc-12.0 --build-type=complete --libdir=C:\Boost\lib\x64 architecture=x86 address-model=64 install --with-filesystem --with-program_options --with-regex --with-date_time +If you are planning on building only particular variant, e.g. Debug +only and static linking, and/or you are out of space/time, you might +consider `--build-type=minimal`. Take a look at +[appveyor.yml](../appveyor.yml) for details on how test builds are done. -After Boost is compiled, set the environment variable `BOOST` to the directory -Boost was installed to. If you followed the instructions outlined here, you -should set it to `C:\Boost`. Additionally, set the BOOSTVER variable to the -version of Boost that you're using, but instead of a '.' use a '_'. For -example, I have `BOOSTVER` set to `1_58`. - -Building Crypto++ +Building OpenSSL ----------------- -* Open the crypttest Solution in VS2013 -* Visual Studio will ask to update the Solution/Project. Allow it. -* Build the `cryptopp` project, both the Debug and Release targets and for both - Win32 and x64. -* Create a folder called `cryptopp` in the crypto++ source directory, then copy - the header files to this new directory. -* Set the `CRYPTOPP` environment variable pointing to the Crypto++ source directory. +Download OpenSSL, e.g. with git + git clone https://github.com/openssl/openssl.git + cd openssl + git checkout OpenSSL_1_0_1p + +Now open Visual Studio command prompt and change directory to that with OpenSSL + + set "PATH=%PATH%;C:\Program Files (x86)\nasm" + perl Configure VC-WIN32 --prefix=c:\OpenSSL-Win32 + ms\do_nasm + nmake -f ms\ntdll.mak + nmake -f ms\ntdll.mak install + +You should have it installed into C:\OpenSSL-Win32 by now. + +Note that you might consider providing `-DOPENSSL_ROOT_DIR` to CMake +and/or create a symlink (with mklink /J) to C:\OpenSSL if you plan on +maintaining multiple versions, e.g. 64 bit and/or +static/shared. Consult `C:\Program Files +(x86)\CMake\share\cmake-3.3\Modules\FindOpenSSL.cmake` for details. + +Get miniupnpc +------------- + +If you are behind a UPnP enabled router and don't feel like manually +configuring port forwarding, you should consider using +[MiniUPnP](http://miniupnp.free.fr) client. I2pd can be built capable +of using miniupnpc shared library (DLL) to open up necessary +port. You'd want to have include headers around to build i2pd with +support for this. Unpack client source code in a sibling folder, +e.g. C:\dev\miniupnpc . You may want to remove version number from +folder name included in downloaded archive. + +Note that you might need to build DLL yourself for 64-bit systems +using msys2 as 64-bit DLLs are not provided by the project. + + +Creating Visual Studio project +------------------------------ + +Start CMake GUI, navigate to i2pd directory, choose building directory, e.g. ./out, and configure options. + +Alternatively, if you feel adventurous, try that from the command line + +``` +cd +mkdir out +cd out +cmake ..\build -G "Visual Studio 12 2013" -DWITH_UPNP=ON -DWITH_PCH=ON -DCMAKE_INSTALL_PREFIX:PATH=C:\dev\Debug_Win32_stage +``` + +WITH_UPNP will stay off, if necessary files are not found. Building i2pd ------------- -## Prep work ## - -I strongly advise setting up your own `INCLUDES` and `LIBS` instead of relying -on the settings in the i2pd project file. By using your own settings, if the -i2pd devs change the paths in the project file, your builds will still work. - -To do this, create or edit the file -`%localappdata%\Microsoft\MSBuild\v4.0\Microsoft.Cpp.Win32.user`. - -For comparison, my file is reproduced below: - - - - - - - - $(CRYPTOPP)\$(Platform)\Output\$(Configuration);$(BOOST)\lib\$(Platform);$(LibraryPath) - $(CRYPTOPP);$(BOOST)\include\boost-$(BOOSTVER);$(IncludePath) - - - - - - -If you want to build x64 binaries as well, you'll want to edit or create the -file `%localappdata%\Microsoft\MSBuild\v4.0\Microsoft.Cpp.x64.user`. If you -followed the steps outlined earlier you can copy (or link) the win32 file to -the x64 one. - -## Anti-Climatic End ## - -After following the above instructions, you'll be able to build Debug Win32, -Debug x64, Release Win32, and Release x64 i2pd binaries. +You can open generated solution/project with Visual Studio and build +from there, alternatively you can use `cmake --build . --config Release --target install` or +[MSBuild tool](https://msdn.microsoft.com/en-us/library/dd293626.aspx) +`msbuild i2pd.sln /p:Configuration=Release`. From 95a5473051c20a7dc7adfe45251a8727a6ce388b Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Fri, 11 Dec 2015 20:15:48 -0600 Subject: [PATCH 0661/6300] Initial CMake based packaging --- appveyor.yml | 2 +- build/CMakeLists.txt | 94 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ae02b812..67200f07 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -174,7 +174,7 @@ build_script: cd \projects\build && cmake ..\i2pd\build -G "%generator%" -DWITH_UPNP=ON %cmake% -DWITH_STATIC=%static% -DZLIB_ROOT=%zlib_root% -DBoost_LIBRARY_DIR:PATH=%BOOST_ROOT%/stage%bitness%/lib -DCMAKE_INSTALL_PREFIX:PATH=c:/projects/instdir && cmake --build . %cmake_build% --target install - && 7z a -tzip -mx9 -mmt C:\projects\i2pd\i2pd-vc%msvc%-win%bitness%-%type%.zip C:\projects\instdir\bin\*.exe %zlib_root%\bin\*.dll %BOOST_ROOT%\stage%bitness%\lib\*.dll %APPVEYOR_BUILD_FOLDER%\LICENSE + && 7z a -tzip -mx9 -mmt C:\projects\i2pd\i2pd-vc%msvc%-win%bitness%-%type%.zip C:\projects\instdir\* && cmake --build . %cmake_build% --target package && xcopy i2pd*win*.exe ..\i2pd\ ) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index a12623dd..452019e9 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -53,6 +53,13 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR MSYS) endif () add_library(libi2pd ${LIBI2PD_SRC}) +install(TARGETS libi2pd + EXPORT libi2pd + ARCHIVE DESTINATION lib + COMPONENT Libraries) +# TODO Make libi2pd available to 3rd party projects via CMake as imported target +# FIXME This pulls stdafx +# install(EXPORT libi2pd DESTINATION ${CMAKE_INSTALL_LIBDIR}) set (CLIENT_SRC "${CMAKE_SOURCE_DIR}/AddressBook.cpp" @@ -334,9 +341,16 @@ if (WITH_BINARY) endif () target_link_libraries( "${PROJECT_NAME}" libi2pd i2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ) - install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) + install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) + set (APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") + set (DIRS "${Boost_LIBRARY_DIR};${OPENSSL_INCLUDE_DIR}/../bin;${ZLIB_INCLUDE_DIR}/../bin;/mingw32/bin") if (MSVC) - install(FILES $ DESTINATION "bin" CONFIGURATIONS DEBUG) + install(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} CONFIGURATIONS DEBUG RELWITHDEBINFO COMPONENT Symbols) + # TODO Somehow this picks lots of unrelevant stuff with MSYS. OS X testing needed. + INSTALL(CODE " + include(BundleUtilities) + fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\") + " COMPONENT Runtime) endif () endif () @@ -351,3 +365,79 @@ install(FILES "C:/projects/openssl-$ENV{OPENSSL}/LICENSE" RENAME LICENSE_OPENSSL OPTIONAL # for local builds only! ) + +file(GLOB_RECURSE I2PD_SOURCES "../*.cpp" "../build" "../Win32" "../Makefile*") +install(FILES ${I2PD_SOURCES} DESTINATION src/ COMPONENT Source) +# install(DIRECTORY ../ DESTINATION src/ +# # OPTIONAL +# COMPONENT Source FILES_MATCHING +# PATTERN .git EXCLUDE +# PATTERN "*.cpp" +# ) + +file(GLOB I2PD_HEADERS "../*.h") +install(FILES ${I2PD_HEADERS} DESTINATION src/ COMPONENT Headers) +# install(DIRECTORY ../ DESTINATION src/ +# # OPTIONAL +# COMPONENT Headers FILES_MATCHING +# PATTERN .git EXCLUDE +# PATTERN "*.h" +# ) + +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Purple I2P, a C++ I2P daemon") +set(CPACK_PACKAGE_VENDOR "Purple I2P") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../README.md") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE") +file(READ ../version.h version_h) +string(REGEX REPLACE ".*I2PD_VERSION_MAJOR ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_MAJOR "${version_h}") +string(REGEX REPLACE ".*I2PD_VERSION_MINOR ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_MINOR "${version_h}") +string(REGEX REPLACE ".*I2PD_VERSION_MICRO ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_MICRO "${version_h}") +string(REGEX REPLACE ".*I2PD_VERSION_PATCH ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_PATCH "${version_h}") +set(CPACK_PACKAGE_INSTALL_DIRECTORY "Purple I2P")# ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}") +include(CPackComponent) +cpack_add_component(Runtime + DESCRIPTION "Main files" + REQUIRED INSTALL_TYPES minimal) +cpack_add_component(Symbols + DISPLAY_NAME "Debug symbols" + DESCRIPTION "Debug symbols for use with WinDbg or Visual Studio" + INSTALL_TYPES recommended full + ) +cpack_add_component(Libraries + DESCRIPTION "Binary libraries for development" + INSTALL_TYPES full dev3rd + ) +cpack_add_component(Source + DISPLAY_NAME "Source code" + DESCRIPTION "I2pd source code" + INSTALL_TYPES full + ) +cpack_add_component(Headers + DISPLAY_NAME "Header files" + DESCRIPTION "I2pd header files for development" + INSTALL_TYPES full dev3rd + ) +cpack_add_install_type(recommended DISPLAY_NAME Recommended) +cpack_add_install_type(dev3rd DISPLAY_NAME "Third party development") +cpack_add_install_type(full DISPLAY_NAME Full) +cpack_add_install_type(minimal DISPLAY_NAME Minimal) +if((WIN32 OR MSYS) AND NOT UNIX) + # There is a bug in NSI that does not handle full unix paths properly. Make + # sure there is at least one set of four (4) backlasshes. + set(CPACK_NSIS_DEFINES "RequestExecutionLevel user") + set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/../Win32\\\\ictoopie.bmp") + set(CPACK_NSIS_INSTALLED_ICON_NAME "bin/i2pd.exe") + SET(CPACK_NSIS_DISPLAY_NAME "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") + set(CPACK_NSIS_HELP_LINK "https:\\\\\\\\github.com\\\\PurpleI2P\\\\i2pd\\\\issues") + set(CPACK_NSIS_URL_INFO_ABOUT "https:\\\\\\\\github.com\\\\PurpleI2P\\\\i2pd") + set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Install i2pd as windows service.lnk' '$INSTDIR\\\\bin\\\\i2pd.exe' '--service=install' +CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Remove i2pd windows service.lnk' '$INSTDIR\\\\bin\\\\i2pd.exe' '--service=remove'") + set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\Install i2pd as windows service.lnk' +Delete '$SMPROGRAMS\\\\$START_MENU\\\\Remove i2pd windows service.lnk'") +else() + set(CPACK_STRIP_FILES "bin/i2pd") + set(CPACK_SOURCE_STRIP_FILES "") +endif() +set(CPACK_PACKAGE_EXECUTABLES "i2pd" "C++ I2P daemon") +set(CPACK_SOURCE_GENERATOR "TGZ") +include(CPack) From 7fc9a161b1b836bdb2e5dcd557b8c8c760b48477 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Fri, 11 Dec 2015 20:17:42 -0600 Subject: [PATCH 0662/6300] Default NSIS template from CMake --- build/cmake_modules/NSIS.template.in | 977 +++++++++++++++++++++++++++ 1 file changed, 977 insertions(+) create mode 100644 build/cmake_modules/NSIS.template.in diff --git a/build/cmake_modules/NSIS.template.in b/build/cmake_modules/NSIS.template.in new file mode 100644 index 00000000..76310af3 --- /dev/null +++ b/build/cmake_modules/NSIS.template.in @@ -0,0 +1,977 @@ +; CPack install script designed for a nmake build + +;-------------------------------- +; You must define these values + + !define VERSION "@CPACK_PACKAGE_VERSION@" + !define PATCH "@CPACK_PACKAGE_VERSION_PATCH@" + !define INST_DIR "@CPACK_TEMPORARY_DIRECTORY@" + +;-------------------------------- +;Variables + + Var MUI_TEMP + Var STARTMENU_FOLDER + Var SV_ALLUSERS + Var START_MENU + Var DO_NOT_ADD_TO_PATH + Var ADD_TO_PATH_ALL_USERS + Var ADD_TO_PATH_CURRENT_USER + Var INSTALL_DESKTOP + Var IS_DEFAULT_INSTALLDIR +;-------------------------------- +;Include Modern UI + + !include "MUI.nsh" + + ;Default installation folder + InstallDir "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + +;-------------------------------- +;General + + ;Name and file + Name "@CPACK_NSIS_PACKAGE_NAME@" + OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@" + + ;Set compression + SetCompressor @CPACK_NSIS_COMPRESSOR@ + + ;Require administrator access + RequestExecutionLevel admin + +@CPACK_NSIS_DEFINES@ + + !include Sections.nsh + +;--- Component support macros: --- +; The code for the add/remove functionality is from: +; http://nsis.sourceforge.net/Add/Remove_Functionality +; It has been modified slightly and extended to provide +; inter-component dependencies. +Var AR_SecFlags +Var AR_RegFlags +@CPACK_NSIS_SECTION_SELECTED_VARS@ + +; Loads the "selected" flag for the section named SecName into the +; variable VarName. +!macro LoadSectionSelectedIntoVar SecName VarName + SectionGetFlags ${${SecName}} $${VarName} + IntOp $${VarName} $${VarName} & ${SF_SELECTED} ;Turn off all other bits +!macroend + +; Loads the value of a variable... can we get around this? +!macro LoadVar VarName + IntOp $R0 0 + $${VarName} +!macroend + +; Sets the value of a variable +!macro StoreVar VarName IntValue + IntOp $${VarName} 0 + ${IntValue} +!macroend + +!macro InitSection SecName + ; This macro reads component installed flag from the registry and + ;changes checked state of the section on the components page. + ;Input: section index constant name specified in Section command. + + ClearErrors + ;Reading component status from registry + ReadRegDWORD $AR_RegFlags HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" "Installed" + IfErrors "default_${SecName}" + ;Status will stay default if registry value not found + ;(component was never installed) + IntOp $AR_RegFlags $AR_RegFlags & ${SF_SELECTED} ;Turn off all other bits + SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading default section flags + IntOp $AR_SecFlags $AR_SecFlags & 0xFFFE ;Turn lowest (enabled) bit off + IntOp $AR_SecFlags $AR_RegFlags | $AR_SecFlags ;Change lowest bit + + ; Note whether this component was installed before + !insertmacro StoreVar ${SecName}_was_installed $AR_RegFlags + IntOp $R0 $AR_RegFlags & $AR_RegFlags + + ;Writing modified flags + SectionSetFlags ${${SecName}} $AR_SecFlags + + "default_${SecName}:" + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected +!macroend + +!macro FinishSection SecName + ; This macro reads section flag set by user and removes the section + ;if it is not selected. + ;Then it writes component installed flag to registry + ;Input: section index constant name specified in Section command. + + SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading section flags + ;Checking lowest bit: + IntOp $AR_SecFlags $AR_SecFlags & ${SF_SELECTED} + IntCmp $AR_SecFlags 1 "leave_${SecName}" + ;Section is not selected: + ;Calling Section uninstall macro and writing zero installed flag + !insertmacro "Remove_${${SecName}}" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" \ + "Installed" 0 + Goto "exit_${SecName}" + + "leave_${SecName}:" + ;Section is selected: + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" \ + "Installed" 1 + + "exit_${SecName}:" +!macroend + +!macro RemoveSection_CPack SecName + ; This macro is used to call section's Remove_... macro + ;from the uninstaller. + ;Input: section index constant name specified in Section command. + + !insertmacro "Remove_${${SecName}}" +!macroend + +; Determine whether the selection of SecName changed +!macro MaybeSelectionChanged SecName + !insertmacro LoadVar ${SecName}_selected + SectionGetFlags ${${SecName}} $R1 + IntOp $R1 $R1 & ${SF_SELECTED} ;Turn off all other bits + + ; See if the status has changed: + IntCmp $R0 $R1 "${SecName}_unchanged" + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected + + IntCmp $R1 ${SF_SELECTED} "${SecName}_was_selected" + !insertmacro "Deselect_required_by_${SecName}" + goto "${SecName}_unchanged" + + "${SecName}_was_selected:" + !insertmacro "Select_${SecName}_depends" + + "${SecName}_unchanged:" +!macroend +;--- End of Add/Remove macros --- + +;-------------------------------- +;Interface Settings + + !define MUI_HEADERIMAGE + !define MUI_ABORTWARNING + +;-------------------------------- +; path functions + +!verbose 3 +!include "WinMessages.NSH" +!verbose 4 + +;---------------------------------------- +; based upon a script of "Written by KiCHiK 2003-01-18 05:57:02" +;---------------------------------------- +!verbose 3 +!include "WinMessages.NSH" +!verbose 4 +;==================================================== +; get_NT_environment +; Returns: the selected environment +; Output : head of the stack +;==================================================== +!macro select_NT_profile UN +Function ${UN}select_NT_profile + StrCmp $ADD_TO_PATH_ALL_USERS "1" 0 environment_single + DetailPrint "Selected environment for all users" + Push "all" + Return + environment_single: + DetailPrint "Selected environment for current user only." + Push "current" + Return +FunctionEnd +!macroend +!insertmacro select_NT_profile "" +!insertmacro select_NT_profile "un." +;---------------------------------------------------- +!define NT_current_env 'HKCU "Environment"' +!define NT_all_env 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + +!ifndef WriteEnvStr_RegKey + !ifdef ALL_USERS + !define WriteEnvStr_RegKey \ + 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + !else + !define WriteEnvStr_RegKey 'HKCU "Environment"' + !endif +!endif + +; AddToPath - Adds the given dir to the search path. +; Input - head of the stack +; Note - Win9x systems requires reboot + +Function AddToPath + Exch $0 + Push $1 + Push $2 + Push $3 + + # don't add if the path doesn't exist + IfFileExists "$0\*.*" "" AddToPath_done + + ReadEnvStr $1 PATH + ; if the path is too long for a NSIS variable NSIS will return a 0 + ; length string. If we find that, then warn and skip any path + ; modification as it will trash the existing path. + StrLen $2 $1 + IntCmp $2 0 CheckPathLength_ShowPathWarning CheckPathLength_Done CheckPathLength_Done + CheckPathLength_ShowPathWarning: + Messagebox MB_OK|MB_ICONEXCLAMATION "Warning! PATH too long installer unable to modify PATH!" + Goto AddToPath_done + CheckPathLength_Done: + Push "$1;" + Push "$0;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + Push "$1;" + Push "$0\;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + GetFullPathName /SHORT $3 $0 + Push "$1;" + Push "$3;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + Push "$1;" + Push "$3\;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + + Call IsNT + Pop $1 + StrCmp $1 1 AddToPath_NT + ; Not on NT + StrCpy $1 $WINDIR 2 + FileOpen $1 "$1\autoexec.bat" a + FileSeek $1 -1 END + FileReadByte $1 $2 + IntCmp $2 26 0 +2 +2 # DOS EOF + FileSeek $1 -1 END # write over EOF + FileWrite $1 "$\r$\nSET PATH=%PATH%;$3$\r$\n" + FileClose $1 + SetRebootFlag true + Goto AddToPath_done + + AddToPath_NT: + StrCmp $ADD_TO_PATH_ALL_USERS "1" ReadAllKey + ReadRegStr $1 ${NT_current_env} "PATH" + Goto DoTrim + ReadAllKey: + ReadRegStr $1 ${NT_all_env} "PATH" + DoTrim: + StrCmp $1 "" AddToPath_NTdoIt + Push $1 + Call Trim + Pop $1 + StrCpy $0 "$1;$0" + AddToPath_NTdoIt: + StrCmp $ADD_TO_PATH_ALL_USERS "1" WriteAllKey + WriteRegExpandStr ${NT_current_env} "PATH" $0 + Goto DoSend + WriteAllKey: + WriteRegExpandStr ${NT_all_env} "PATH" $0 + DoSend: + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + AddToPath_done: + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + + +; RemoveFromPath - Remove a given dir from the path +; Input: head of the stack + +Function un.RemoveFromPath + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + + IntFmt $6 "%c" 26 # DOS EOF + + Call un.IsNT + Pop $1 + StrCmp $1 1 unRemoveFromPath_NT + ; Not on NT + StrCpy $1 $WINDIR 2 + FileOpen $1 "$1\autoexec.bat" r + GetTempFileName $4 + FileOpen $2 $4 w + GetFullPathName /SHORT $0 $0 + StrCpy $0 "SET PATH=%PATH%;$0" + Goto unRemoveFromPath_dosLoop + + unRemoveFromPath_dosLoop: + FileRead $1 $3 + StrCpy $5 $3 1 -1 # read last char + StrCmp $5 $6 0 +2 # if DOS EOF + StrCpy $3 $3 -1 # remove DOS EOF so we can compare + StrCmp $3 "$0$\r$\n" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "$0$\n" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "$0" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "" unRemoveFromPath_dosLoopEnd + FileWrite $2 $3 + Goto unRemoveFromPath_dosLoop + unRemoveFromPath_dosLoopRemoveLine: + SetRebootFlag true + Goto unRemoveFromPath_dosLoop + + unRemoveFromPath_dosLoopEnd: + FileClose $2 + FileClose $1 + StrCpy $1 $WINDIR 2 + Delete "$1\autoexec.bat" + CopyFiles /SILENT $4 "$1\autoexec.bat" + Delete $4 + Goto unRemoveFromPath_done + + unRemoveFromPath_NT: + StrCmp $ADD_TO_PATH_ALL_USERS "1" unReadAllKey + ReadRegStr $1 ${NT_current_env} "PATH" + Goto unDoTrim + unReadAllKey: + ReadRegStr $1 ${NT_all_env} "PATH" + unDoTrim: + StrCpy $5 $1 1 -1 # copy last char + StrCmp $5 ";" +2 # if last char != ; + StrCpy $1 "$1;" # append ; + Push $1 + Push "$0;" + Call un.StrStr ; Find `$0;` in $1 + Pop $2 ; pos of our dir + StrCmp $2 "" unRemoveFromPath_done + ; else, it is in path + # $0 - path to add + # $1 - path var + StrLen $3 "$0;" + StrLen $4 $2 + StrCpy $5 $1 -$4 # $5 is now the part before the path to remove + StrCpy $6 $2 "" $3 # $6 is now the part after the path to remove + StrCpy $3 $5$6 + + StrCpy $5 $3 1 -1 # copy last char + StrCmp $5 ";" 0 +2 # if last char == ; + StrCpy $3 $3 -1 # remove last char + + StrCmp $ADD_TO_PATH_ALL_USERS "1" unWriteAllKey + WriteRegExpandStr ${NT_current_env} "PATH" $3 + Goto unDoSend + unWriteAllKey: + WriteRegExpandStr ${NT_all_env} "PATH" $3 + unDoSend: + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + unRemoveFromPath_done: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Uninstall sutff +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +########################################### +# Utility Functions # +########################################### + +;==================================================== +; IsNT - Returns 1 if the current system is NT, 0 +; otherwise. +; Output: head of the stack +;==================================================== +; IsNT +; no input +; output, top of the stack = 1 if NT or 0 if not +; +; Usage: +; Call IsNT +; Pop $R0 +; ($R0 at this point is 1 or 0) + +!macro IsNT un +Function ${un}IsNT + Push $0 + ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion + StrCmp $0 "" 0 IsNT_yes + ; we are not NT. + Pop $0 + Push 0 + Return + + IsNT_yes: + ; NT!!! + Pop $0 + Push 1 +FunctionEnd +!macroend +!insertmacro IsNT "" +!insertmacro IsNT "un." + +; StrStr +; input, top of stack = string to search for +; top of stack-1 = string to search in +; output, top of stack (replaces with the portion of the string remaining) +; modifies no other variables. +; +; Usage: +; Push "this is a long ass string" +; Push "ass" +; Call StrStr +; Pop $R0 +; ($R0 at this point is "ass string") + +!macro StrStr un +Function ${un}StrStr +Exch $R1 ; st=haystack,old$R1, $R1=needle + Exch ; st=old$R1,haystack + Exch $R2 ; st=old$R1,old$R2, $R2=haystack + Push $R3 + Push $R4 + Push $R5 + StrLen $R3 $R1 + StrCpy $R4 0 + ; $R1=needle + ; $R2=haystack + ; $R3=len(needle) + ; $R4=cnt + ; $R5=tmp + loop: + StrCpy $R5 $R2 $R3 $R4 + StrCmp $R5 $R1 done + StrCmp $R5 "" done + IntOp $R4 $R4 + 1 + Goto loop +done: + StrCpy $R1 $R2 "" $R4 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Exch $R1 +FunctionEnd +!macroend +!insertmacro StrStr "" +!insertmacro StrStr "un." + +Function Trim ; Added by Pelaca + Exch $R1 + Push $R2 +Loop: + StrCpy $R2 "$R1" 1 -1 + StrCmp "$R2" " " RTrim + StrCmp "$R2" "$\n" RTrim + StrCmp "$R2" "$\r" RTrim + StrCmp "$R2" ";" RTrim + GoTo Done +RTrim: + StrCpy $R1 "$R1" -1 + Goto Loop +Done: + Pop $R2 + Exch $R1 +FunctionEnd + +Function ConditionalAddToRegisty + Pop $0 + Pop $1 + StrCmp "$0" "" ConditionalAddToRegisty_EmptyString + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" \ + "$1" "$0" + ;MessageBox MB_OK "Set Registry: '$1' to '$0'" + DetailPrint "Set install registry entry: '$1' to '$0'" + ConditionalAddToRegisty_EmptyString: +FunctionEnd + +;-------------------------------- + +!ifdef CPACK_USES_DOWNLOAD +Function DownloadFile + IfFileExists $INSTDIR\* +2 + CreateDirectory $INSTDIR + Pop $0 + + ; Skip if already downloaded + IfFileExists $INSTDIR\$0 0 +2 + Return + + StrCpy $1 "@CPACK_DOWNLOAD_SITE@" + + try_again: + NSISdl::download "$1/$0" "$INSTDIR\$0" + + Pop $1 + StrCmp $1 "success" success + StrCmp $1 "Cancelled" cancel + MessageBox MB_OK "Download failed: $1" + cancel: + Return + success: +FunctionEnd +!endif + +;-------------------------------- +; Installation types +@CPACK_NSIS_INSTALLATION_TYPES@ + +;-------------------------------- +; Component sections +@CPACK_NSIS_COMPONENT_SECTIONS@ + +;-------------------------------- +; Define some macro setting for the gui +@CPACK_NSIS_INSTALLER_MUI_ICON_CODE@ +@CPACK_NSIS_INSTALLER_ICON_CODE@ +@CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC@ +@CPACK_NSIS_INSTALLER_MUI_FINISHPAGE_RUN_CODE@ + +;-------------------------------- +;Pages + !insertmacro MUI_PAGE_WELCOME + + !insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@" + Page custom InstallOptionsPage + !insertmacro MUI_PAGE_DIRECTORY + + ;Start Menu Folder Page Configuration + !define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX" + !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" + !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER + + @CPACK_NSIS_PAGE_COMPONENTS@ + + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_PAGE_FINISH + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" ;first language is the default language + !insertmacro MUI_LANGUAGE "Albanian" + !insertmacro MUI_LANGUAGE "Arabic" + !insertmacro MUI_LANGUAGE "Basque" + !insertmacro MUI_LANGUAGE "Belarusian" + !insertmacro MUI_LANGUAGE "Bosnian" + !insertmacro MUI_LANGUAGE "Breton" + !insertmacro MUI_LANGUAGE "Bulgarian" + !insertmacro MUI_LANGUAGE "Croatian" + !insertmacro MUI_LANGUAGE "Czech" + !insertmacro MUI_LANGUAGE "Danish" + !insertmacro MUI_LANGUAGE "Dutch" + !insertmacro MUI_LANGUAGE "Estonian" + !insertmacro MUI_LANGUAGE "Farsi" + !insertmacro MUI_LANGUAGE "Finnish" + !insertmacro MUI_LANGUAGE "French" + !insertmacro MUI_LANGUAGE "German" + !insertmacro MUI_LANGUAGE "Greek" + !insertmacro MUI_LANGUAGE "Hebrew" + !insertmacro MUI_LANGUAGE "Hungarian" + !insertmacro MUI_LANGUAGE "Icelandic" + !insertmacro MUI_LANGUAGE "Indonesian" + !insertmacro MUI_LANGUAGE "Irish" + !insertmacro MUI_LANGUAGE "Italian" + !insertmacro MUI_LANGUAGE "Japanese" + !insertmacro MUI_LANGUAGE "Korean" + !insertmacro MUI_LANGUAGE "Kurdish" + !insertmacro MUI_LANGUAGE "Latvian" + !insertmacro MUI_LANGUAGE "Lithuanian" + !insertmacro MUI_LANGUAGE "Luxembourgish" + !insertmacro MUI_LANGUAGE "Macedonian" + !insertmacro MUI_LANGUAGE "Malay" + !insertmacro MUI_LANGUAGE "Mongolian" + !insertmacro MUI_LANGUAGE "Norwegian" + !insertmacro MUI_LANGUAGE "Polish" + !insertmacro MUI_LANGUAGE "Portuguese" + !insertmacro MUI_LANGUAGE "PortugueseBR" + !insertmacro MUI_LANGUAGE "Romanian" + !insertmacro MUI_LANGUAGE "Russian" + !insertmacro MUI_LANGUAGE "Serbian" + !insertmacro MUI_LANGUAGE "SerbianLatin" + !insertmacro MUI_LANGUAGE "SimpChinese" + !insertmacro MUI_LANGUAGE "Slovak" + !insertmacro MUI_LANGUAGE "Slovenian" + !insertmacro MUI_LANGUAGE "Spanish" + !insertmacro MUI_LANGUAGE "Swedish" + !insertmacro MUI_LANGUAGE "Thai" + !insertmacro MUI_LANGUAGE "TradChinese" + !insertmacro MUI_LANGUAGE "Turkish" + !insertmacro MUI_LANGUAGE "Ukrainian" + !insertmacro MUI_LANGUAGE "Welsh" + + +;-------------------------------- +;Reserve Files + + ;These files should be inserted before other files in the data block + ;Keep these lines before any File command + ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA) + + ReserveFile "NSIS.InstallOptions.ini" + !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS + +;-------------------------------- +;Installer Sections + +Section "-Core installation" + ;Use the entire tree produced by the INSTALL target. Keep the + ;list of directories here in sync with the RMDir commands below. + SetOutPath "$INSTDIR" + @CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS@ + @CPACK_NSIS_FULL_INSTALL@ + + ;Store installation folder + WriteRegStr SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR + + ;Create uninstaller + WriteUninstaller "$INSTDIR\Uninstall.exe" + Push "DisplayName" + Push "@CPACK_NSIS_DISPLAY_NAME@" + Call ConditionalAddToRegisty + Push "DisplayVersion" + Push "@CPACK_PACKAGE_VERSION@" + Call ConditionalAddToRegisty + Push "Publisher" + Push "@CPACK_PACKAGE_VENDOR@" + Call ConditionalAddToRegisty + Push "UninstallString" + Push "$INSTDIR\Uninstall.exe" + Call ConditionalAddToRegisty + Push "NoRepair" + Push "1" + Call ConditionalAddToRegisty + + !ifdef CPACK_NSIS_ADD_REMOVE + ;Create add/remove functionality + Push "ModifyPath" + Push "$INSTDIR\AddRemove.exe" + Call ConditionalAddToRegisty + !else + Push "NoModify" + Push "1" + Call ConditionalAddToRegisty + !endif + + ; Optional registration + Push "DisplayIcon" + Push "$INSTDIR\@CPACK_NSIS_INSTALLED_ICON_NAME@" + Call ConditionalAddToRegisty + Push "HelpLink" + Push "@CPACK_NSIS_HELP_LINK@" + Call ConditionalAddToRegisty + Push "URLInfoAbout" + Push "@CPACK_NSIS_URL_INFO_ABOUT@" + Call ConditionalAddToRegisty + Push "Contact" + Push "@CPACK_NSIS_CONTACT@" + Call ConditionalAddToRegisty + !insertmacro MUI_INSTALLOPTIONS_READ $INSTALL_DESKTOP "NSIS.InstallOptions.ini" "Field 5" "State" + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + + ;Create shortcuts + CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER" +@CPACK_NSIS_CREATE_ICONS@ +@CPACK_NSIS_CREATE_ICONS_EXTRA@ + CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\Uninstall.exe" + + ;Read a value from an InstallOptions INI file + !insertmacro MUI_INSTALLOPTIONS_READ $DO_NOT_ADD_TO_PATH "NSIS.InstallOptions.ini" "Field 2" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $ADD_TO_PATH_ALL_USERS "NSIS.InstallOptions.ini" "Field 3" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $ADD_TO_PATH_CURRENT_USER "NSIS.InstallOptions.ini" "Field 4" "State" + + ; Write special uninstall registry entries + Push "StartMenu" + Push "$STARTMENU_FOLDER" + Call ConditionalAddToRegisty + Push "DoNotAddToPath" + Push "$DO_NOT_ADD_TO_PATH" + Call ConditionalAddToRegisty + Push "AddToPathAllUsers" + Push "$ADD_TO_PATH_ALL_USERS" + Call ConditionalAddToRegisty + Push "AddToPathCurrentUser" + Push "$ADD_TO_PATH_CURRENT_USER" + Call ConditionalAddToRegisty + Push "InstallToDesktop" + Push "$INSTALL_DESKTOP" + Call ConditionalAddToRegisty + + !insertmacro MUI_STARTMENU_WRITE_END + +@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@ + +SectionEnd + +Section "-Add to path" + Push $INSTDIR\bin + StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 doNotAddToPath + StrCmp $DO_NOT_ADD_TO_PATH "1" doNotAddToPath 0 + Call AddToPath + doNotAddToPath: +SectionEnd + +;-------------------------------- +; Create custom pages +Function InstallOptionsPage + !insertmacro MUI_HEADER_TEXT "Install Options" "Choose options for installing @CPACK_NSIS_PACKAGE_NAME@" + !insertmacro MUI_INSTALLOPTIONS_DISPLAY "NSIS.InstallOptions.ini" + +FunctionEnd + +;-------------------------------- +; determine admin versus local install +Function un.onInit + + ClearErrors + UserInfo::GetName + IfErrors noLM + Pop $0 + UserInfo::GetAccountType + Pop $1 + StrCmp $1 "Admin" 0 +3 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Admin group' + Goto done + StrCmp $1 "Power" 0 +3 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Power Users group' + Goto done + + noLM: + ;Get installation folder from registry if available + + done: + +FunctionEnd + +;--- Add/Remove callback functions: --- +!macro SectionList MacroName + ;This macro used to perform operation on multiple sections. + ;List all of your components in following manner here. +@CPACK_NSIS_COMPONENT_SECTION_LIST@ +!macroend + +Section -FinishComponents + ;Removes unselected components and writes component status to registry + !insertmacro SectionList "FinishSection" + +!ifdef CPACK_NSIS_ADD_REMOVE + ; Get the name of the installer executable + System::Call 'kernel32::GetModuleFileNameA(i 0, t .R0, i 1024) i r1' + StrCpy $R3 $R0 + + ; Strip off the last 13 characters, to see if we have AddRemove.exe + StrLen $R1 $R0 + IntOp $R1 $R0 - 13 + StrCpy $R2 $R0 13 $R1 + StrCmp $R2 "AddRemove.exe" addremove_installed + + ; We're not running AddRemove.exe, so install it + CopyFiles $R3 $INSTDIR\AddRemove.exe + + addremove_installed: +!endif +SectionEnd +;--- End of Add/Remove callback functions --- + +;-------------------------------- +; Component dependencies +Function .onSelChange + !insertmacro SectionList MaybeSelectionChanged +FunctionEnd + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + ReadRegStr $START_MENU SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "StartMenu" + ;MessageBox MB_OK "Start menu is in: $START_MENU" + ReadRegStr $DO_NOT_ADD_TO_PATH SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "DoNotAddToPath" + ReadRegStr $ADD_TO_PATH_ALL_USERS SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathAllUsers" + ReadRegStr $ADD_TO_PATH_CURRENT_USER SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathCurrentUser" + ;MessageBox MB_OK "Add to path: $DO_NOT_ADD_TO_PATH all users: $ADD_TO_PATH_ALL_USERS" + ReadRegStr $INSTALL_DESKTOP SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "InstallToDesktop" + ;MessageBox MB_OK "Install to desktop: $INSTALL_DESKTOP " + +@CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS@ + + ;Remove files we installed. + ;Keep the list of directories here in sync with the File commands above. +@CPACK_NSIS_DELETE_FILES@ +@CPACK_NSIS_DELETE_DIRECTORIES@ + +!ifdef CPACK_NSIS_ADD_REMOVE + ;Remove the add/remove program + Delete "$INSTDIR\AddRemove.exe" +!endif + + ;Remove the uninstaller itself. + Delete "$INSTDIR\Uninstall.exe" + DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + ;Remove the installation directory if it is empty. + RMDir "$INSTDIR" + + ; Remove the registry entries. + DeleteRegKey SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + ; Removes all optional components + !insertmacro SectionList "RemoveSection_CPack" + + !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP + + Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" +@CPACK_NSIS_DELETE_ICONS@ +@CPACK_NSIS_DELETE_ICONS_EXTRA@ + + ;Delete empty start menu parent diretories + StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP" + + startMenuDeleteLoop: + ClearErrors + RMDir $MUI_TEMP + GetFullPathName $MUI_TEMP "$MUI_TEMP\.." + + IfErrors startMenuDeleteLoopDone + + StrCmp "$MUI_TEMP" "$SMPROGRAMS" startMenuDeleteLoopDone startMenuDeleteLoop + startMenuDeleteLoopDone: + + ; If the user changed the shortcut, then untinstall may not work. This should + ; try to fix it. + StrCpy $MUI_TEMP "$START_MENU" + Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" +@CPACK_NSIS_DELETE_ICONS_EXTRA@ + + ;Delete empty start menu parent diretories + StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP" + + secondStartMenuDeleteLoop: + ClearErrors + RMDir $MUI_TEMP + GetFullPathName $MUI_TEMP "$MUI_TEMP\.." + + IfErrors secondStartMenuDeleteLoopDone + + StrCmp "$MUI_TEMP" "$SMPROGRAMS" secondStartMenuDeleteLoopDone secondStartMenuDeleteLoop + secondStartMenuDeleteLoopDone: + + DeleteRegKey /ifempty SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + Push $INSTDIR\bin + StrCmp $DO_NOT_ADD_TO_PATH_ "1" doNotRemoveFromPath 0 + Call un.RemoveFromPath + doNotRemoveFromPath: +SectionEnd + +;-------------------------------- +; determine admin versus local install +; Is install for "AllUsers" or "JustMe"? +; Default to "JustMe" - set to "AllUsers" if admin or on Win9x +; This function is used for the very first "custom page" of the installer. +; This custom page does not show up visibly, but it executes prior to the +; first visible page and sets up $INSTDIR properly... +; Choose different default installation folder based on SV_ALLUSERS... +; "Program Files" for AllUsers, "My Documents" for JustMe... + +Function .onInit + StrCmp "@CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL@" "ON" 0 inst + + ReadRegStr $0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "UninstallString" + StrCmp $0 "" inst + + MessageBox MB_YESNOCANCEL|MB_ICONEXCLAMATION \ + "@CPACK_NSIS_PACKAGE_NAME@ is already installed. $\n$\nDo you want to uninstall the old version before installing the new one?" \ + IDYES uninst IDNO inst + Abort + +;Run the uninstaller +uninst: + ClearErrors + StrLen $2 "\Uninstall.exe" + StrCpy $3 $0 -$2 # remove "\Uninstall.exe" from UninstallString to get path + ExecWait '$0 _?=$3' ;Do not copy the uninstaller to a temp file + + IfErrors uninst_failed inst +uninst_failed: + MessageBox MB_OK|MB_ICONSTOP "Uninstall failed." + Abort + + +inst: + ; Reads components status for registry + !insertmacro SectionList "InitSection" + + ; check to see if /D has been used to change + ; the install directory by comparing it to the + ; install directory that is expected to be the + ; default + StrCpy $IS_DEFAULT_INSTALLDIR 0 + StrCmp "$INSTDIR" "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" 0 +2 + StrCpy $IS_DEFAULT_INSTALLDIR 1 + + StrCpy $SV_ALLUSERS "JustMe" + ; if default install dir then change the default + ; if it is installed for JustMe + StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2 + StrCpy $INSTDIR "$DOCUMENTS\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + + ClearErrors + UserInfo::GetName + IfErrors noLM + Pop $0 + UserInfo::GetAccountType + Pop $1 + StrCmp $1 "Admin" 0 +4 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Admin group' + StrCpy $SV_ALLUSERS "AllUsers" + Goto done + StrCmp $1 "Power" 0 +4 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Power Users group' + StrCpy $SV_ALLUSERS "AllUsers" + Goto done + + noLM: + StrCpy $SV_ALLUSERS "AllUsers" + ;Get installation folder from registry if available + + done: + StrCmp $SV_ALLUSERS "AllUsers" 0 +3 + StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2 + StrCpy $INSTDIR "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + + StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 noOptionsPage + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "NSIS.InstallOptions.ini" + + noOptionsPage: +FunctionEnd From 23cb45454b1787d0ab7fc5cb969c320210f32ff6 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Fri, 11 Dec 2015 20:18:28 -0600 Subject: [PATCH 0663/6300] Set default install folder for CMake based NSIS --- build/cmake_modules/NSIS.template.in | 1 + 1 file changed, 1 insertion(+) diff --git a/build/cmake_modules/NSIS.template.in b/build/cmake_modules/NSIS.template.in index 76310af3..f7d5a7f7 100644 --- a/build/cmake_modules/NSIS.template.in +++ b/build/cmake_modules/NSIS.template.in @@ -557,6 +557,7 @@ FunctionEnd !define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX" !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" + !define MUI_STARTMENUPAGE_DEFAULTFOLDER "Purple I2P" !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER @CPACK_NSIS_PAGE_COMPONENTS@ From 8e04218c95ebc97f8bb8e0ca4155a5a3c99b51f7 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sun, 13 Dec 2015 15:35:56 -0600 Subject: [PATCH 0664/6300] Install optional miniupnpc.dll if exist --- build/CMakeLists.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 452019e9..748d8264 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -417,6 +417,22 @@ cpack_add_component(Headers DESCRIPTION "I2pd header files for development" INSTALL_TYPES full dev3rd ) +install(FILES ${MINIUPNPC_INCLUDE_DIR}/miniupnpc/miniupnpc.dll + DESTINATION bin + COMPONENT MiniUPnPc + OPTIONAL + ) +install(FILES ${MINIUPNPC_INCLUDE_DIR}/miniupnpc/LICENSE + DESTINATION . + COMPONENT MiniUPnPc + RENAME LICENSE_MINIUPNPC + OPTIONAL + ) +cpack_add_component(MiniUPnPc + INSTALL_TYPES full recommended + # DOWNLOADED + # ARCHIVE_FILE miniupnpc-win32.zip + ) cpack_add_install_type(recommended DISPLAY_NAME Recommended) cpack_add_install_type(dev3rd DISPLAY_NAME "Third party development") cpack_add_install_type(full DISPLAY_NAME Full) From f68481527253165ef5f7a101d9474ddff6340416 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Mon, 14 Dec 2015 00:49:14 -0600 Subject: [PATCH 0665/6300] Build miniupnpc.dll on Appveyor --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 67200f07..df6264bc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -83,6 +83,7 @@ install: && set "openssl_target=VC-WIN32" && set "zlib_asm=-DASM686=ON "-DCMAKE_ASM_MASM_FLAGS=/W0 /safeseh"" ) +- C:\msys64\usr\bin\bash -lc "export PATH=/mingw%bitness%/bin:/usr/bin:. && cd /c/projects/miniupnpc && CC=gcc make -f Makefile.mingw init miniupnpc.dll > c:\projects\instdir\build_miniupnpc.log 2>&1 || cat c:\projects\instdir\build_miniupnpc.log" - set /a generator=%msvc%+2001 - if defined msvc ( ( From 6d3dac0ec1ac2cd365948dd4e9a9e11168371bf0 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sat, 19 Dec 2015 00:00:51 -0600 Subject: [PATCH 0666/6300] Windows build status badge update in README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index b890029b..221ec48c 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,9 @@ http://download.i2p.io/purplei2p/i2pd/releases/ Supported OS ------------ -[![Build status](https://ci.appveyor.com/api/projects/status/458fhbki14gaplyj/branch/openssl?svg=true)](https://ci.appveyor.com/project/mlt/i2pd/branch/openssl) - * Linux x86/x64 - Proved working. * Mac OS X - Not well tested. (Only works with clang, not GCC) -* Windows - At least builds and runs. +* Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) More documentation ------------------ From 4899e0d2d56bb3ccd8727b7f87c67fce86e6cae4 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 17 Dec 2015 07:11:36 +0000 Subject: [PATCH 0667/6300] * sane log messages: I2NPProtocol.cpp --- I2NPProtocol.cpp | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 6f7c98f5..63c8ea9c 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -60,7 +60,7 @@ namespace i2p msg->len += len; } else - LogPrint (eLogError, "I2NP message length ", len, " exceeds max length"); + LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length"); msg->FillI2NPMessageHeader (msgType, replyMsgID); return msg; } @@ -75,7 +75,7 @@ namespace i2p msg->from = from; } else - LogPrint (eLogError, "I2NP message length ", len, " exceeds max length"); + LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length"); return msg; } @@ -276,7 +276,7 @@ namespace i2p uint8_t * record = records + i*TUNNEL_BUILD_RECORD_SIZE; if (!memcmp (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) { - LogPrint ("Record ",i," is ours"); + LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours"); i2p::crypto::ElGamalDecrypt (i2p::context.GetEncryptionPrivateKey (), record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText); // replace record to reply @@ -320,22 +320,22 @@ namespace i2p void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) { int num = buf[0]; - LogPrint ("VariableTunnelBuild ", num, " records"); + LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); if (tunnel) { // endpoint of inbound tunnel - LogPrint ("VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); + LogPrint (eLogDebug, "I2NP: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); if (tunnel->HandleTunnelBuildResponse (buf, len)) { - LogPrint ("Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); + LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); i2p::tunnel::tunnels.AddInboundTunnel (tunnel); } else { - LogPrint ("Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); } } @@ -382,25 +382,25 @@ namespace i2p void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) { - LogPrint ("VariableTunnelBuildReplyMsg replyMsgID=", replyMsgID); + LogPrint (eLogDebug, "I2NP: VariableTunnelBuildReplyMsg replyMsgID=", replyMsgID); auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID); if (tunnel) { // reply for outbound tunnel if (tunnel->HandleTunnelBuildResponse (buf, len)) { - LogPrint ("Outbound tunnel ", tunnel->GetTunnelID (), " has been created"); + LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been created"); tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); i2p::tunnel::tunnels.AddOutboundTunnel (tunnel); } else { - LogPrint ("Outbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + LogPrint (eLogInfo, "I2NP: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined"); tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); } } else - LogPrint ("Pending tunnel for message ", replyMsgID, " not found"); + LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found"); } @@ -488,30 +488,26 @@ namespace i2p { uint8_t typeID = msg[I2NP_HEADER_TYPEID_OFFSET]; uint32_t msgID = bufbe32toh (msg + I2NP_HEADER_MSGID_OFFSET); - LogPrint ("I2NP msg received len=", len,", type=", (int)typeID, ", msgID=", (unsigned int)msgID); + LogPrint (eLogDebug, "I2NP: msg received len=", len,", type=", (int)typeID, ", msgID=", (unsigned int)msgID); uint8_t * buf = msg + I2NP_HEADER_SIZE; int size = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET); switch (typeID) { case eI2NPVariableTunnelBuild: - LogPrint ("VariableTunnelBuild"); HandleVariableTunnelBuildMsg (msgID, buf, size); break; case eI2NPVariableTunnelBuildReply: - LogPrint ("VariableTunnelBuildReply"); HandleVariableTunnelBuildReplyMsg (msgID, buf, size); break; case eI2NPTunnelBuild: - LogPrint ("TunnelBuild"); HandleTunnelBuildMsg (buf, size); break; case eI2NPTunnelBuildReply: - LogPrint ("TunnelBuildReply"); // TODO: break; default: - LogPrint ("Unexpected message ", (int)typeID); + LogPrint (eLogWarning, "I2NP: Unexpected message ", (int)typeID); } } @@ -519,25 +515,24 @@ namespace i2p { if (msg) { - switch (msg->GetTypeID ()) + uint8_t typeID = msg->GetTypeID (); + LogPrint (eLogDebug, "I2NP: Got message with type ", (int)typeID); + switch (typeID) { case eI2NPTunnelData: - LogPrint ("TunnelData"); i2p::tunnel::tunnels.PostTunnelData (msg); break; case eI2NPTunnelGateway: - LogPrint ("TunnelGateway"); i2p::tunnel::tunnels.PostTunnelData (msg); break; case eI2NPGarlic: { - LogPrint ("Garlic"); if (msg->from) { if (msg->from->GetTunnelPool ()) msg->from->GetTunnelPool ()->ProcessGarlicMessage (msg); else - LogPrint (eLogInfo, "Local destination for garlic doesn't exist anymore"); + LogPrint (eLogInfo, "I2NP: Local destination for garlic doesn't exist anymore"); } else i2p::context.ProcessGarlicMessage (msg); @@ -551,7 +546,6 @@ namespace i2p break; case eI2NPDeliveryStatus: { - LogPrint ("DeliveryStatus"); if (msg->from && msg->from->GetTunnelPool ()) msg->from->GetTunnelPool ()->ProcessDeliveryStatus (msg); else From 489e37b2a16150fe5f8e007d79c1a41c24649d56 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 04:03:07 +0000 Subject: [PATCH 0668/6300] * sane log messages: NetDb.cpp --- NetDb.cpp | 85 +++++++++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index d1f8ef14..e6269959 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -80,22 +80,20 @@ namespace data int numMsgs = 0; while (msg) { + LogPrint(eLogDebug, "NetDb: got request with type ", (int) msg->GetTypeID ()); switch (msg->GetTypeID ()) { case eI2NPDatabaseStore: - LogPrint ("DatabaseStore"); HandleDatabaseStoreMsg (msg); break; case eI2NPDatabaseSearchReply: - LogPrint ("DatabaseSearchReply"); HandleDatabaseSearchReplyMsg (msg); break; case eI2NPDatabaseLookup: - LogPrint ("DatabaseLookup"); HandleDatabaseLookupMsg (msg); break; default: // WTF? - LogPrint (eLogError, "NetDb: unexpected message type ", msg->GetTypeID ()); + LogPrint (eLogError, "NetDb: unexpected message type ", (int) msg->GetTypeID ()); //i2p::HandleI2NPMessage (msg); } if (numMsgs > 100) break; @@ -141,7 +139,7 @@ namespace data } catch (std::exception& ex) { - LogPrint ("NetDb: ", ex.what ()); + LogPrint (eLogError, "NetDb: runtime exception: ", ex.what ()); } } } @@ -161,11 +159,11 @@ namespace data auto ts = r->GetTimestamp (); r->Update (buf, len); if (r->GetTimestamp () > ts) - LogPrint ("RouterInfo updated"); + LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase32()); } else { - LogPrint ("New RouterInfo added"); + LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase32()); r = std::make_shared (buf, len); { std::unique_lock l(m_RouterInfosMutex); @@ -191,10 +189,10 @@ namespace data { it->second->Update (buf, len); if (it->second->IsValid ()) - LogPrint (eLogInfo, "LeaseSet updated"); + LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32()); else { - LogPrint (eLogInfo, "LeaseSet update failed"); + LogPrint (eLogWarning, "NetDb: LeaseSet update failed: ", ident.ToBase32()); m_LeaseSets.erase (it); } } @@ -203,11 +201,11 @@ namespace data auto leaseSet = std::make_shared (buf, len); if (leaseSet->IsValid ()) { - LogPrint (eLogInfo, "New LeaseSet added"); + LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase32()); m_LeaseSets[ident] = leaseSet; } else - LogPrint (eLogError, "New LeaseSet validation failed"); + LogPrint (eLogError, "NetDb: new LeaseSet validation failed: ", ident.ToBase32()); } } } @@ -247,10 +245,10 @@ namespace data // TODO: Move to reseed and/or scheduled tasks. (In java version, scheduler fix this as well as sort RIs.) bool NetDb::CreateNetDb(boost::filesystem::path directory) { - LogPrint (directory.string(), " doesn't exist, trying to create it."); + LogPrint (eLogInfo, "NetDb: storage directory doesn't exist, trying to create it."); if (!boost::filesystem::create_directory (directory)) { - LogPrint (eLogError, "Failed to create directory ", directory); + LogPrint (eLogError, "NetDb: failed to create directory ", directory); return false; } @@ -261,7 +259,7 @@ namespace data auto p = directory / (std::string ("r") + chars[i]); if (!boost::filesystem::exists (p) && !boost::filesystem::create_directory (p)) { - LogPrint (eLogError, "Failed to create directory ", p); + LogPrint (eLogError, "NetDb: failed to create directory ", p); return false; } } @@ -279,7 +277,7 @@ namespace data while (reseedRetries < 10 && !m_Reseeder->ReseedNowSU3 ()) reseedRetries++; if (reseedRetries >= 10) - LogPrint (eLogWarning, "Failed to reseed after 10 attempts"); + LogPrint (eLogWarning, "NetDb: failed to reseed after 10 attempts"); } void NetDb::Load () @@ -327,8 +325,7 @@ namespace data } } } - LogPrint (numRouters, " routers loaded"); - LogPrint (m_Floodfills.size (), " floodfills loaded"); + LogPrint (eLogInfo, "NetDb: ", numRouters, " routers loaded (", m_Floodfills.size (), " floodfils)"); } void NetDb::SaveUpdated () @@ -406,10 +403,10 @@ namespace data } } if (count > 0) - LogPrint (count," new/updated routers saved"); + LogPrint (eLogInfo, "NetDb: ", count, " new/updated routers saved"); if (deletedCount > 0) { - LogPrint (deletedCount," routers deleted"); + LogPrint (eLogDebug, "NetDb: ", deletedCount, " routers deleted"); // clean up RouterInfos table std::unique_lock l(m_RouterInfosMutex); for (auto it = m_RouterInfos.begin (); it != m_RouterInfos.end ();) @@ -430,7 +427,7 @@ namespace data auto dest = m_Requests.CreateRequest (destination, false, requestComplete); // non-exploratory if (!dest) { - LogPrint (eLogWarning, "Destination ", destination.ToBase64(), " is requested already"); + LogPrint (eLogWarning, "NetDb: destination ", destination.ToBase32(), " is requested already"); return; } @@ -439,7 +436,7 @@ namespace data transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); else { - LogPrint (eLogError, "No floodfills found"); + LogPrint (eLogError, "NetDb: ", destination.ToBase32(), " destination requested, but no floodfills found"); m_Requests.RequestComplete (destination, nullptr); } } @@ -451,7 +448,7 @@ namespace data IdentHash ident (buf + DATABASE_STORE_KEY_OFFSET); if (ident.IsZero ()) { - LogPrint (eLogError, "Database store with zero ident. Dropped"); + LogPrint (eLogError, "NetDb: database store with zero ident, dropped"); return; } uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET); @@ -470,7 +467,7 @@ namespace data if (outbound) outbound->SendTunnelDataMsg (buf + offset, tunnelID, deliveryStatus); else - LogPrint (eLogError, "No outbound tunnels for DatabaseStore reply found"); + LogPrint (eLogError, "NetDb: no outbound tunnels for DatabaseStore reply found"); } offset += 32; @@ -496,17 +493,17 @@ namespace data if (buf[DATABASE_STORE_TYPE_OFFSET]) // type { - LogPrint ("LeaseSet"); + LogPrint (eLogDebug, "NetDb: store request: LeaseSet"); AddLeaseSet (ident, buf + offset, len - offset, m->from); } else { - LogPrint ("RouterInfo"); + LogPrint (eLogDebug, "NetDb: store request: RouterInfo"); size_t size = bufbe16toh (buf + offset); offset += 2; if (size > 2048 || size > len - offset) { - LogPrint ("Invalid RouterInfo length ", (int)size); + LogPrint (eLogError, "NetDb: invalid RouterInfo length ", (int)size); return; } uint8_t uncompressed[2048]; @@ -523,7 +520,7 @@ namespace data int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48); key[l] = 0; int num = buf[32]; // num - LogPrint ("DatabaseSearchReply for ", key, " num=", num); + LogPrint (eLogDebug, "NetDb: DatabaseSearchReply for ", key, " num=", num); IdentHash ident (buf); auto dest = m_Requests.FindRequest (ident); if (dest) @@ -555,7 +552,7 @@ namespace data }); // request destination - LogPrint ("Try ", key, " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 ()); + LogPrint (eLogDebug, "NetDb: Try ", key, " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 ()); auto msg = dest->CreateRequestMessage (nextFloodfill, inbound); msgs.push_back (i2p::tunnel::TunnelMessageBlock { @@ -566,7 +563,7 @@ namespace data } } else - LogPrint (key, " was not found on 7 floodfills"); + LogPrint (eLogWarning, "NetDb: ", key, " was not found on ", count, " floodfills"); if (msgs.size () > 0) outbound->SendTunnelDataMsg (msgs); @@ -582,7 +579,7 @@ namespace data m_Requests.RequestComplete (ident, nullptr); } else - LogPrint ("Requested destination for ", key, " not found"); + LogPrint (eLogWarning, "NetDb: requested destination for ", key, " not found"); // try responses for (int i = 0; i < num; i++) @@ -591,17 +588,17 @@ namespace data char peerHash[48]; int l1 = i2p::data::ByteStreamToBase64 (router, 32, peerHash, 48); peerHash[l1] = 0; - LogPrint (i,": ", peerHash); + LogPrint (eLogDebug, "NetDb: ", i, ": ", peerHash); auto r = FindRouter (router); if (!r || i2p::util::GetMillisecondsSinceEpoch () > r->GetTimestamp () + 3600*1000LL) { // router with ident not found or too old (1 hour) - LogPrint ("Found new/outdated router. Requesting RouterInfo ..."); + LogPrint (eLogDebug, "NetDb: found new/outdated router. Requesting RouterInfo ..."); RequestDestination (router); } else - LogPrint ("Bayan"); + LogPrint (eLogDebug, "NetDb: [:|||:]"); } } @@ -611,14 +608,14 @@ namespace data IdentHash ident (buf); if (ident.IsZero ()) { - LogPrint (eLogError, "DatabaseLookup for zero ident. Ignored"); + LogPrint (eLogError, "NetDb: DatabaseLookup for zero ident. Ignored"); return; } char key[48]; int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48); key[l] = 0; uint8_t flag = buf[64]; - LogPrint ("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; const uint8_t * excluded = buf + 65; uint32_t replyTunnelID = 0; @@ -631,14 +628,14 @@ namespace data excluded += 2; if (numExcluded > 512) { - LogPrint ("Number of excluded peers", numExcluded, " exceeds 512"); + LogPrint (eLogWarning, "NetDb: number of excluded peers", numExcluded, " exceeds 512"); numExcluded = 0; // TODO: } std::shared_ptr replyMsg; if (lookupType == DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP) { - LogPrint ("Exploratory close to ", key, " ", numExcluded, " excluded"); + LogPrint (eLogInfo, "NetDb: exploratory close to ", key, " ", numExcluded, " excluded"); std::set excludedRouters; for (int i = 0; i < numExcluded; i++) { @@ -665,7 +662,7 @@ namespace data auto router = FindRouter (ident); if (router) { - LogPrint ("Requested RouterInfo ", key, " found"); + LogPrint (eLogDebug, "NetDb: requested RouterInfo ", key, " found"); router->LoadBuffer (); if (router->GetBuffer ()) replyMsg = CreateDatabaseStoreMsg (router); @@ -678,14 +675,14 @@ namespace data auto leaseSet = FindLeaseSet (ident); if (leaseSet) // we don't send back our LeaseSets { - LogPrint ("Requested LeaseSet ", key, " found"); + LogPrint (eLogDebug, "NetDb: requested LeaseSet ", key, " found"); replyMsg = CreateDatabaseStoreMsg (leaseSet); } } if (!replyMsg) { - LogPrint ("Requested ", key, " not found. ", numExcluded, " excluded"); + LogPrint (eLogWarning, "NetDb: Requested ", key, " not found. ", numExcluded, " excluded"); std::set excludedRouters; for (int i = 0; i < numExcluded; i++) { @@ -735,14 +732,14 @@ namespace data uint8_t randomHash[32]; std::vector msgs; std::set floodfills; - LogPrint ("Exploring new ", numDestinations, " routers ..."); + LogPrint (eLogInfo, "NetDb: exploring new ", numDestinations, " routers ..."); for (int i = 0; i < numDestinations; i++) { RAND_bytes (randomHash, 32); auto dest = m_Requests.CreateRequest (randomHash, true); // exploratory if (!dest) { - LogPrint (eLogWarning, "Exploratory destination is requested already"); + LogPrint (eLogWarning, "NetDb: exploratory destination is requested already"); return; } auto floodfill = GetClosestFloodfill (randomHash, dest->GetExcludedPeers ()); @@ -786,7 +783,7 @@ namespace data { uint32_t replyToken; RAND_bytes ((uint8_t *)&replyToken, 4); - LogPrint ("Publishing our RouterInfo to ", i2p::data::GetIdentHashAbbreviation(floodfill->GetIdentHash ()), ". reply token=", replyToken); + LogPrint (eLogInfo, "NetDb: Publishing our RouterInfo to ", i2p::data::GetIdentHashAbbreviation(floodfill->GetIdentHash ()), ". reply token=", replyToken); transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken)); excluded.insert (floodfill->GetIdentHash ()); } @@ -972,7 +969,7 @@ namespace data { if (!it->second->HasNonExpiredLeases ()) // all leases expired { - LogPrint ("LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); + LogPrint (eLogWarning, "NetDb: LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); it = m_LeaseSets.erase (it); } else From 8170257c2602a6851eea1a302c59875e9513e46e Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 04:05:52 +0000 Subject: [PATCH 0669/6300] * sane log messages: AddressBook.cpp --- AddressBook.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 49177e8e..6be1c0eb 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -152,10 +152,10 @@ namespace client num++; } } - LogPrint (eLogInfo, num, " addresses loaded"); + LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded"); } else - LogPrint (eLogWarning, filename, " not found"); + LogPrint (eLogWarning, "Addressbook: ", filename, " not found"); return num; } @@ -171,7 +171,7 @@ namespace client f << it.first << "," << it.second.ToBase32 () << std::endl; num++; } - LogPrint (eLogInfo, num, " addresses saved"); + LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); } else LogPrint (eLogError, "Can't open file ", filename); @@ -290,7 +290,7 @@ namespace client m_Storage = CreateStorage (); m_Storage->AddAddress (ident); m_Addresses[address] = ident->GetIdentHash (); - LogPrint (address,"->", ToAddress(ident->GetIdentHash ()), " added"); + LogPrint (eLogInfo, "Addressbook: ", address,"->", ToAddress(ident->GetIdentHash ()), " added"); } void AddressBook::InsertAddress (std::shared_ptr address) @@ -476,7 +476,7 @@ namespace client void AddressBookSubscription::Request () { // must be run in separate thread - LogPrint (eLogInfo, "Downloading hosts from ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); + LogPrint (eLogInfo, "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; @@ -591,7 +591,10 @@ namespace client } else LogPrint (eLogError, "Can't resolve ", u.host_); - LogPrint (eLogInfo, "Download complete ", success ? "Success" : "Failed"); + + if (!success) + LogPrint (eLogError, "Addressbook download failed"); + m_Book.DownloadComplete (success); } } From facc5f8aa732522f25514520de77304bd05280f5 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 17 Dec 2015 08:09:54 +0000 Subject: [PATCH 0670/6300] * sane log messages: SSUSession.cpp --- SSUSession.cpp | 96 +++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index 1516b6c2..a91ecc2d 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -70,7 +70,7 @@ namespace transport nonZero++; if (nonZero - sharedKey > 32) { - LogPrint ("First 32 bytes of shared key is all zeros. Ignored"); + LogPrint (eLogWarning, "SSU: first 32 bytes of shared key is all zeros. Ignored"); return; } } @@ -90,7 +90,7 @@ namespace transport if (m_State == eSessionStateIntroduced) { // HolePunch received - LogPrint ("SSU HolePunch of ", len, " bytes received"); + LogPrint (eLogDebug, "SSU: HolePunch of ", len, " bytes received"); m_State = eSessionStateUnknown; Connect (); } @@ -120,7 +120,7 @@ namespace transport Decrypt (buf, len, address->key); else { - LogPrint (eLogError, "MAC verification failed ", len, " bytes from ", senderEndpoint); + LogPrint (eLogError, "SSU: MAC verification failed ", len, " bytes from ", senderEndpoint); m_Server.DeleteSession (shared_from_this ()); return; } @@ -166,12 +166,12 @@ namespace transport ProcessSessionConfirmed (buf, len); // buf with header break; case PAYLOAD_TYPE_PEER_TEST: - LogPrint (eLogDebug, "SSU peer test received"); + LogPrint (eLogDebug, "SSU: peer test received"); ProcessPeerTest (buf + headerSize, len - headerSize, senderEndpoint); break; case PAYLOAD_TYPE_SESSION_DESTROYED: { - LogPrint (eLogDebug, "SSU session destroy received"); + LogPrint (eLogDebug, "SSU: session destroy received"); m_Server.DeleteSession (shared_from_this ()); break; } @@ -181,21 +181,21 @@ namespace transport m_Server.DeleteSession (shared_from_this ()); break; case PAYLOAD_TYPE_RELAY_REQUEST: - LogPrint (eLogDebug, "SSU relay request received"); + LogPrint (eLogDebug, "SSU: relay request received"); ProcessRelayRequest (buf + headerSize, len - headerSize, senderEndpoint); break; case PAYLOAD_TYPE_RELAY_INTRO: - LogPrint (eLogDebug, "SSU relay intro received"); + LogPrint (eLogDebug, "SSU: relay intro received"); ProcessRelayIntro (buf + headerSize, len - headerSize); break; default: - LogPrint (eLogWarning, "Unexpected SSU payload type ", (int)header->GetPayloadType ()); + LogPrint (eLogWarning, "SSU: Unexpected payload type ", (int)header->GetPayloadType ()); } } void SSUSession::ProcessSessionRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { - LogPrint (eLogDebug, "Session request received"); + LogPrint (eLogDebug, "SSU message: session request"); m_RemoteEndpoint = senderEndpoint; if (!m_DHKeysPair) m_DHKeysPair = transports.GetNextDHKeysPair (); @@ -207,11 +207,11 @@ namespace transport { if (!IsOutgoing () || !m_DHKeysPair) { - LogPrint (eLogWarning, "Unsolicited session created message"); + LogPrint (eLogWarning, "SSU: Unsolicited session created message"); return; } - LogPrint (eLogDebug, "Session created received"); + LogPrint (eLogDebug, "SSU message: session created"); m_Timer.cancel (); // connect timer SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, signed on time auto headerSize = GetSSUHeaderSize (buf); @@ -247,7 +247,7 @@ namespace transport uint16_t ourPort = bufbe16toh (payload); s.Insert (payload, 2); // our port payload += 2; // port - LogPrint ("Our external address is ", ourIP.to_string (), ":", ourPort); + LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); i2p::context.UpdateAddress (ourIP); if (m_RemoteEndpoint.address ().is_v4 ()) s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 @@ -267,18 +267,18 @@ namespace transport m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); // TODO: non-const payload // verify if (!s.Verify (m_RemoteIdentity, payload)) - LogPrint (eLogError, "Session created SSU signature verification failed"); + LogPrint (eLogError, "SSU: message 'created' signature verification failed"); SendSessionConfirmed (y, ourAddress, addressSize + 2); } void SSUSession::ProcessSessionConfirmed (const uint8_t * buf, size_t len) { - LogPrint (eLogDebug, "Session confirmed received"); + LogPrint (eLogDebug, "SSU: Session confirmed received"); auto headerSize = GetSSUHeaderSize (buf); if (headerSize >= len) { - LogPrint (eLogError, "Session confirmed header size ", len, " exceeds packet length ", len); + LogPrint (eLogError, "SSU: Session confirmed header size ", len, " exceeds packet length ", len); return; } const uint8_t * payload = buf + headerSize; @@ -297,7 +297,7 @@ namespace transport payload += paddingSize; // verify if (m_SignedData && !m_SignedData->Verify (m_RemoteIdentity, payload)) - LogPrint (eLogError, "Session confirmed SSU signature verification failed"); + LogPrint (eLogError, "SSU message 'confirmed' signature verification failed"); m_Data.Send (CreateDeliveryStatusMsg (0)); Established (); } @@ -508,7 +508,7 @@ namespace transport // Charlie's address always v4 if (!to.address ().is_v4 ()) { - LogPrint (eLogError, "Charlie's IP must be v4"); + LogPrint (eLogError, "SSU: Charlie's IP must be v4"); return; } *payload = 4; @@ -551,7 +551,7 @@ namespace transport FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80, introKey, iv, introKey); m_Server.Send (buf, isV4 ? 64 : 80, from); } - LogPrint (eLogDebug, "SSU relay response sent"); + LogPrint (eLogDebug, "SSU: relay response sent"); } void SSUSession::SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from) @@ -560,7 +560,7 @@ namespace transport // Alice's address always v4 if (!from.address ().is_v4 ()) { - LogPrint (eLogError, "Alice's IP must be v4"); + LogPrint (eLogError, "SSU: Alice's IP must be v4"); return; } uint8_t buf[48 + 18]; @@ -576,12 +576,12 @@ namespace transport RAND_bytes (iv, 16); // random iv FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, 48, session->m_SessionKey, iv, session->m_MacKey); m_Server.Send (buf, 48, session->m_RemoteEndpoint); - LogPrint (eLogDebug, "SSU relay intro sent"); + LogPrint (eLogDebug, "SSU: relay intro sent"); } void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len) { - LogPrint (eLogDebug, "Relay response received"); + LogPrint (eLogDebug, "SSU message: Relay response received"); uint8_t remoteSize = *buf; buf++; // remote size boost::asio::ip::address_v4 remoteIP (bufbe32toh (buf)); @@ -606,7 +606,7 @@ namespace transport buf += ourSize; // our address uint16_t ourPort = bufbe16toh (buf); buf += 2; // our port - LogPrint ("Our external address is ", ourIP.to_string (), ":", ourPort); + LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); i2p::context.UpdateAddress (ourIP); uint32_t nonce = bufbe32toh (buf); buf += 4; // nonce @@ -619,7 +619,7 @@ namespace transport { // we didn't have correct endpoint when sent relay request // now we do - LogPrint (eLogInfo, "RelayReponse connecting to endpoint ", remoteEndpoint); + LogPrint (eLogInfo, "SSU: RelayReponse connecting to endpoint ", remoteEndpoint); if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch m_Server.CreateDirectSession (it->second, remoteEndpoint, false); @@ -628,7 +628,7 @@ namespace transport m_RelayRequests.erase (it); } else - LogPrint (eLogError, "Unsolicited RelayResponse, nonce=", nonce); + LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce); } void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) @@ -644,7 +644,7 @@ namespace transport m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (address, port)); } else - LogPrint (eLogWarning, "Address size ", size, " is not supported"); + LogPrint (eLogWarning, "SSU: Address size ", size, " is not supported"); } void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, @@ -652,7 +652,7 @@ namespace transport { if (len < sizeof (SSUHeader)) { - LogPrint (eLogError, "Unexpected SSU packet length ", len); + LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } //TODO: we are using a dirty solution here but should work for now @@ -676,7 +676,7 @@ namespace transport { if (len < sizeof (SSUHeader)) { - LogPrint (eLogError, "Unexpected SSU packet length ", len); + LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } //TODO: we are using a dirty solution here but should work for now @@ -698,7 +698,7 @@ namespace transport { if (len < sizeof (SSUHeader)) { - LogPrint (eLogError, "Unexpected SSU packet length ", len); + LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved @@ -715,7 +715,7 @@ namespace transport { if (len < sizeof (SSUHeader)) { - LogPrint (eLogError, "Unexpected SSU packet length ", len); + LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved @@ -733,7 +733,7 @@ namespace transport { if (len < sizeof (SSUHeader)) { - LogPrint (eLogError, "Unexpected SSU packet length ", len); + LogPrint (eLogError, "SSU: Unexpected packet length ", len); return false; } //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved @@ -764,7 +764,7 @@ namespace transport if (!IsOutgoing ()) // incoming session ScheduleConnectTimer (); else - LogPrint (eLogError, "SSU wait for connect for outgoing session"); + LogPrint (eLogError, "SSU: wait for connect for outgoing session"); } void SSUSession::ScheduleConnectTimer () @@ -780,7 +780,7 @@ namespace transport if (!ecode) { // timeout expired - LogPrint ("SSU session was not established after ", SSU_CONNECT_TIMEOUT, " second"); + LogPrint (eLogWarning, "SSU: session was not established after ", SSU_CONNECT_TIMEOUT, " seconds"); Failed (); } } @@ -858,7 +858,7 @@ namespace transport { if (ecode != boost::asio::error::operation_aborted) { - LogPrint ("SSU no activity fo ", SSU_TERMINATION_TIMEOUT, " seconds"); + LogPrint (eLogInfo, "SSU: no activity for", SSU_TERMINATION_TIMEOUT, " seconds"); Failed (); } } @@ -902,7 +902,7 @@ namespace transport const uint8_t * introKey = buf + size + 7; if (port && !address) { - LogPrint (eLogWarning, "Address of ", size, " bytes not supported"); + LogPrint (eLogWarning, "SSU: Address of ", size, " bytes not supported"); return; } switch (m_Server.GetPeerTestParticipant (nonce)) @@ -912,13 +912,13 @@ namespace transport { if (m_State == eSessionStateEstablished) { - LogPrint (eLogDebug, "SSU peer test from Bob. We are Alice"); + LogPrint (eLogDebug, "SSU: peer test from Bob. We are Alice"); if (i2p::context.GetStatus () == eRouterStatusTesting) // still not OK i2p::context.SetStatus (eRouterStatusFirewalled); } else { - LogPrint (eLogDebug, "SSU first peer test from Charlie. We are Alice"); + LogPrint (eLogDebug, "SSU: first peer test from Charlie. We are Alice"); i2p::context.SetStatus (eRouterStatusOK); m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2); SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), @@ -929,11 +929,11 @@ namespace transport case ePeerTestParticipantAlice2: { if (m_State == eSessionStateEstablished) - LogPrint (eLogDebug, "SSU peer test from Bob. We are Alice"); + LogPrint (eLogDebug, "SSU: peer test from Bob. We are Alice"); else { // peer test successive - LogPrint (eLogDebug, "SSU second peer test from Charlie. We are Alice"); + LogPrint (eLogDebug, "SSU: second peer test from Charlie. We are Alice"); i2p::context.SetStatus (eRouterStatusOK); m_Server.RemovePeerTest (nonce); } @@ -941,7 +941,7 @@ namespace transport } case ePeerTestParticipantBob: { - LogPrint (eLogDebug, "SSU peer test from Charlie. We are Bob"); + LogPrint (eLogDebug, "SSU: peer test from Charlie. We are Bob"); auto session = m_Server.GetPeerTestSession (nonce); // session with Alice from PeerTest if (session && session->m_State == eSessionStateEstablished) session->Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Alice @@ -950,7 +950,7 @@ namespace transport } case ePeerTestParticipantCharlie: { - LogPrint (eLogDebug, "SSU peer test from Alice. We are Charlie"); + LogPrint (eLogDebug, "SSU: peer test from Alice. We are Charlie"); SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), senderEndpoint.port (), introKey); // to Alice with her actual address m_Server.RemovePeerTest (nonce); // nonce has been used @@ -964,14 +964,14 @@ namespace transport // new test if (port) { - LogPrint (eLogDebug, "SSU peer test from Bob. We are Charlie"); + LogPrint (eLogDebug, "SSU: peer test from Bob. We are Charlie"); m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob SendPeerTest (nonce, be32toh (address), be16toh (port), introKey); // to Alice with her address received from Bob } else { - LogPrint (eLogDebug, "SSU peer test from Alice. We are Bob"); + LogPrint (eLogDebug, "SSU: peer test from Alice. We are Bob"); auto session = m_Server.GetRandomEstablishedV4Session (shared_from_this ()); // Charlie, TODO: implement v6 support if (session) { @@ -982,7 +982,7 @@ namespace transport } } else - LogPrint (eLogError, "SSU unexpected peer test"); + LogPrint (eLogError, "SSU: unexpected peer test"); } } } @@ -1045,7 +1045,7 @@ namespace transport void SSUSession::SendPeerTest () { // we are Alice - LogPrint (eLogDebug, "SSU sending peer test"); + LogPrint (eLogDebug, "SSU: sending peer test"); auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); if (!address) { @@ -1072,7 +1072,7 @@ namespace transport // encrypt message with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); Send (buf, 48); - LogPrint (eLogDebug, "SSU keep-alive sent"); + LogPrint (eLogDebug, "SSU: keep-alive sent"); ScheduleTermination (); } } @@ -1090,9 +1090,9 @@ namespace transport } catch (std::exception& ex) { - LogPrint (eLogError, "SSU send session destoriyed exception ", ex.what ()); + LogPrint (eLogError, "SSU: exception while send session destoriyed: ", ex.what ()); } - LogPrint (eLogDebug, "SSU session destroyed sent"); + LogPrint (eLogDebug, "SSU: session destroyed sent"); } } @@ -1104,7 +1104,7 @@ namespace transport if (paddingSize > 0) msgSize += (16 - paddingSize); if (msgSize > SSU_MTU_V4) { - LogPrint (eLogWarning, "SSU payload size ", msgSize, " exceeds MTU"); + LogPrint (eLogWarning, "SSU: payload size ", msgSize, " exceeds MTU"); return; } memcpy (buf + sizeof (SSUHeader), payload, len); From c3958bf042e0509dec24f464bd87302931d657c4 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 04:15:05 +0000 Subject: [PATCH 0671/6300] * sane log messages: NTCPSession.cpp --- NTCPSession.cpp | 82 ++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 90733d73..f7d3b3d7 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -56,7 +56,7 @@ namespace transport nonZero++; if (nonZero - sharedKey > 32) { - LogPrint (eLogWarning, "First 32 bytes of shared key is all zeros. Ignored"); + LogPrint (eLogWarning, "NTCP: First 32 bytes of shared key is all zeros, ignored"); return; } } @@ -81,7 +81,7 @@ namespace transport m_SendQueue.clear (); m_NextMessage = nullptr; m_TerminationTimer.cancel (); - LogPrint (eLogInfo, "NTCP session terminated"); + LogPrint (eLogDebug, "NTCP: session terminated"); } } @@ -136,7 +136,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "Couldn't send Phase 1 message: ", ecode.message ()); + LogPrint (eLogError, "NTCP: couldn't send Phase 1 message: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -152,7 +152,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "Phase 1 read error: ", ecode.message ()); + LogPrint (eLogError, "NTCP: phase 1 read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -166,7 +166,7 @@ namespace transport { if ((m_Establisher->phase1.HXxorHI[i] ^ ident[i]) != digest[i]) { - LogPrint (eLogError, "Wrong ident"); + LogPrint (eLogError, "NTCP: phase 1 error: ident mismatch"); Terminate (); return; } @@ -207,7 +207,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "Couldn't send Phase 2 message: ", ecode.message ()); + LogPrint (eLogError, "NTCP: Couldn't send Phase 2 message: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -223,7 +223,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "Phase 2 read error: ", ecode.message (), ". Wrong ident assumed"); + LogPrint (eLogError, "NTCP: Phase 2 read error: ", ecode.message (), ". Wrong ident assumed"); if (ecode != boost::asio::error::operation_aborted) { // this RI is not valid @@ -251,7 +251,7 @@ namespace transport SHA256 (xy, 512, digest); if (memcmp(m_Establisher->phase2.encrypted.hxy, digest, 32)) { - LogPrint (eLogError, "Incorrect hash"); + LogPrint (eLogError, "NTCP: Phase 2 process error: incorrect hash"); transports.ReuseDHKeysPair (m_DHKeysPair); m_DHKeysPair = nullptr; Terminate (); @@ -299,7 +299,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "Couldn't send Phase 3 message: ", ecode.message ()); + LogPrint (eLogError, "NTCP: Couldn't send Phase 3 message: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -319,7 +319,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "Phase 3 read error: ", ecode.message ()); + LogPrint (eLogError, "NTCP: Phase 3 read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -331,7 +331,7 @@ namespace transport SetRemoteIdentity (std::make_shared (buf + 2, size)); if (m_Server.FindNTCPSession (m_RemoteIdentity->GetIdentHash ())) { - LogPrint (eLogError, "NTCP session already exists"); + LogPrint (eLogError, "NTCP: session already exists"); Terminate (); } size_t expectedSize = size + 2/*size*/ + 4/*timestamp*/ + m_RemoteIdentity->GetSignatureLen (); @@ -354,7 +354,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "Phase 3 extra read error: ", ecode.message ()); + LogPrint (eLogError, "NTCP: Phase 3 extra read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -380,7 +380,7 @@ namespace transport s.Insert (tsB); // tsB if (!s.Verify (m_RemoteIdentity, buf)) { - LogPrint (eLogError, "signature verification failed"); + LogPrint (eLogError, "NTCP: signature verification failed"); Terminate (); return; } @@ -411,13 +411,13 @@ namespace transport { if (ecode) { - LogPrint (eLogWarning, "Couldn't send Phase 4 message: ", ecode.message ()); + LogPrint (eLogWarning, "NTCP: Couldn't send Phase 4 message: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else { - LogPrint (eLogInfo, "NTCP server session from ", m_Socket.remote_endpoint (), " connected"); + LogPrint (eLogInfo, "NTCP: Server session from ", m_Socket.remote_endpoint (), " connected"); m_Server.AddNTCPSession (shared_from_this ()); Connected (); @@ -431,7 +431,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "Phase 4 read error: ", ecode.message (), ". Check your clock"); + LogPrint (eLogError, "NTCP: Phase 4 read error: ", ecode.message (), ". Check your clock"); if (ecode != boost::asio::error::operation_aborted) { // this router doesn't like us @@ -453,11 +453,11 @@ namespace transport if (!s.Verify (m_RemoteIdentity, m_ReceiveBuffer)) { - LogPrint (eLogError, "signature verification failed"); + LogPrint (eLogError, "NTCP: Phase 4 process error: signature verification failed"); Terminate (); return; } - LogPrint (eLogInfo, "NTCP session to ", m_Socket.remote_endpoint (), " connected"); + LogPrint (eLogDebug, "NTCP: session to ", m_Socket.remote_endpoint (), " connected"); Connected (); m_ReceiveBufferOffset = 0; @@ -477,7 +477,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "Read error: ", ecode.message ()); + LogPrint (eLogError, "NTCP: Read error: ", ecode.message ()); if (!m_NumReceivedBytes) m_Server.Ban (m_ConnectedFrom); //if (ecode != boost::asio::error::operation_aborted) Terminate (); @@ -519,7 +519,7 @@ namespace transport moreBytes = m_Socket.read_some (boost::asio::buffer (m_ReceiveBuffer + m_ReceiveBufferOffset, moreBytes)); if (ec) { - LogPrint (eLogError, "Read more bytes error: ", ec.message ()); + LogPrint (eLogError, "NTCP: Read more bytes error: ", ec.message ()); Terminate (); return; } @@ -542,7 +542,7 @@ namespace transport { if (!m_NextMessage) // new message, header expected { - // descrypt header and extract length + // decrypt header and extract length uint8_t buf[16]; m_Decryption.Decrypt (encrypted, buf); uint16_t dataSize = bufbe16toh (buf); @@ -551,7 +551,7 @@ namespace transport // new message if (dataSize > NTCP_MAX_MESSAGE_SIZE) { - LogPrint (eLogError, "NTCP data size ", dataSize, " exceeds max size"); + LogPrint (eLogError, "NTCP: data size ", dataSize, " exceeds max size"); return false; } auto msg = dataSize <= I2NP_MAX_SHORT_MESSAGE_SIZE - 2 ? NewI2NPShortMessage () : NewI2NPMessage (); @@ -564,7 +564,7 @@ namespace transport else { // timestamp - LogPrint ("Timestamp"); + LogPrint (eLogDebug, "NTCP: Timestamp"); return true; } } @@ -582,7 +582,7 @@ namespace transport if (!memcmp (m_NextMessage->buf + m_NextMessageOffset - 4, checksum, 4)) m_Handler.PutNextMessage (m_NextMessage); else - LogPrint (eLogWarning, "Incorrect adler checksum of NTCP message. Dropped"); + LogPrint (eLogWarning, "NTCP: Incorrect adler checksum of message, dropped"); m_NextMessage = nullptr; } return true; @@ -604,7 +604,7 @@ namespace transport { // regular I2NP if (msg->offset < 2) - LogPrint (eLogError, "Malformed I2NP message"); // TODO: + LogPrint (eLogError, "NTCP: Malformed I2NP message"); // TODO: sendBuffer = msg->GetBuffer () - 2; len = msg->GetLength (); htobe16buf (sendBuffer, len); @@ -644,7 +644,7 @@ namespace transport m_IsSending = false; if (ecode) { - LogPrint (eLogWarning, "Couldn't send msgs: ", ecode.message ()); + LogPrint (eLogWarning, "NTCP: Couldn't send msgs: ", ecode.message ()); // we shouldn't call Terminate () here, because HandleReceive takes care // TODO: 'delete this' statement in Terminate () must be eliminated later // Terminate (); @@ -699,7 +699,7 @@ namespace transport { if (ecode != boost::asio::error::operation_aborted) { - LogPrint ("No activity fo ", NTCP_TERMINATION_TIMEOUT, " seconds"); + LogPrint (eLogWarning, "NTCP: No activity fo ", NTCP_TERMINATION_TIMEOUT, " seconds"); //Terminate (); m_Socket.close ();// invoke Terminate () from HandleReceive } @@ -732,7 +732,7 @@ namespace transport m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address.port)); - LogPrint (eLogInfo, "Start listening TCP port ", address.port); + LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address.port); auto conn = std::make_shared(*this); m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, conn, std::placeholders::_1)); @@ -745,7 +745,7 @@ namespace transport m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address.port)); m_NTCPV6Acceptor->listen (); - LogPrint (eLogInfo, "Start listening V6 TCP port ", address.port); + LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address.port); auto conn = std::make_shared (*this); m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this, conn, std::placeholders::_1)); @@ -788,7 +788,7 @@ namespace transport } catch (std::exception& ex) { - LogPrint ("NTCP server: ", ex.what ()); + LogPrint (eLogError, "NTCP: runtime exception: ", ex.what ()); } } } @@ -800,7 +800,7 @@ namespace transport auto it = m_NTCPSessions.find (ident); if (it != m_NTCPSessions.end ()) { - LogPrint (eLogWarning, "NTCP session to ", ident.ToBase64 (), " already exists"); + LogPrint (eLogWarning, "NTCP: session to ", ident.ToBase64 (), " already exists"); return false; } m_NTCPSessions.insert (std::pair >(ident, session)); @@ -829,14 +829,14 @@ namespace transport auto ep = conn->GetSocket ().remote_endpoint(ec); if (!ec) { - LogPrint (eLogInfo, "Connected from ", ep); + LogPrint (eLogDebug, "NTCP: Connected from ", ep); auto it = m_BanList.find (ep.address ()); if (it != m_BanList.end ()) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); if (ts < it->second) { - LogPrint (eLogInfo, ep.address (), " is banned for ", it->second - ts, " more seconds"); + LogPrint (eLogWarning, "NTCP: ", ep.address (), " is banned for ", it->second - ts, " more seconds"); conn = nullptr; } else @@ -846,7 +846,7 @@ namespace transport conn->ServerLogin (); } else - LogPrint (eLogError, "Connected from error ", ec.message ()); + LogPrint (eLogError, "NTCP: Connected from error ", ec.message ()); } @@ -866,14 +866,14 @@ namespace transport auto ep = conn->GetSocket ().remote_endpoint(ec); if (!ec) { - LogPrint (eLogInfo, "Connected from ", ep); + LogPrint (eLogDebug, "NTCP: Connected from ", ep); auto it = m_BanList.find (ep.address ()); if (it != m_BanList.end ()) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); if (ts < it->second) { - LogPrint (eLogInfo, ep.address (), " is banned for ", it->second - ts, " more seconds"); + LogPrint (eLogWarning, "NTCP: ", ep.address (), " is banned for ", it->second - ts, " more seconds"); conn = nullptr; } else @@ -883,7 +883,7 @@ namespace transport conn->ServerLogin (); } else - LogPrint (eLogError, "Connected from error ", ec.message ()); + LogPrint (eLogError, "NTCP: Connected from error ", ec.message ()); } if (error != boost::asio::error::operation_aborted) @@ -896,7 +896,7 @@ namespace transport void NTCPServer::Connect (const boost::asio::ip::address& address, int port, std::shared_ptr conn) { - LogPrint (eLogInfo, "Connecting to ", address ,":", port); + LogPrint (eLogDebug, "NTCP: Connecting to ", address ,":", port); m_Service.post([=]() { if (this->AddNTCPSession (conn)) @@ -909,14 +909,14 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "Connect error: ", ecode.message ()); + LogPrint (eLogError, "NTCP: Connect error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); conn->Terminate (); } else { - LogPrint (eLogInfo, "Connected to ", conn->GetSocket ().remote_endpoint ()); + LogPrint (eLogDebug, "NTCP: Connected to ", conn->GetSocket ().remote_endpoint ()); if (conn->GetSocket ().local_endpoint ().protocol () == boost::asio::ip::tcp::v6()) // ipv6 context.UpdateNTCPV6Address (conn->GetSocket ().local_endpoint ().address ()); conn->ClientLogin (); @@ -927,7 +927,7 @@ namespace transport { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); m_BanList[addr] = ts + NTCP_BAN_EXPIRATION_TIMEOUT; - LogPrint (eLogInfo, addr, " has been banned for ", NTCP_BAN_EXPIRATION_TIMEOUT, " seconds"); + LogPrint (eLogWarning, "NTCP: ", addr, " has been banned for ", NTCP_BAN_EXPIRATION_TIMEOUT, " seconds"); } } } From 3156f7dacd6839d1aedcae239ac7d68e43ad7623 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 06:20:06 +0000 Subject: [PATCH 0672/6300] * sane log messages: Tunnel.cpp --- Tunnel.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 8e5510f1..c7d6573e 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -92,7 +92,7 @@ namespace tunnel bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) { - LogPrint ("TunnelBuildResponse ", (int)msg[0], " records."); + LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); i2p::crypto::CBCDecryption decryption; TunnelHopConfig * hop = m_Config->GetLastHop (); @@ -111,7 +111,7 @@ namespace tunnel decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); } else - LogPrint ("Tunnel hop index ", idx, " is out of range"); + LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); hop1 = hop1->prev; } hop = hop->prev; @@ -123,7 +123,7 @@ namespace tunnel { const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE; uint8_t ret = record[BUILD_RESPONSE_RECORD_RET_OFFSET]; - LogPrint ("Ret code=", (int)ret); + LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); if (profile) profile->TunnelBuildResponse (ret); @@ -163,7 +163,7 @@ namespace tunnel void Tunnel::SendTunnelDataMsg (std::shared_ptr msg) { - LogPrint (eLogInfo, "Can't send I2NP messages without delivery instructions"); + LogPrint (eLogInfo, "Tunnel: Can't send I2NP messages without delivery instructions"); } std::vector > Tunnel::GetPeers () const @@ -442,7 +442,7 @@ namespace tunnel HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); break; default: - LogPrint (eLogError, "Unexpected messsage type ", (int)typeID); + LogPrint (eLogError, "Tunnel: Unexpected messsage type ", (int)typeID); } msg = m_Queue.Get (); @@ -466,7 +466,7 @@ namespace tunnel } catch (std::exception& ex) { - LogPrint ("Tunnels: ", ex.what ()); + LogPrint (eLogError, "Tunnel: runtime exception: ", ex.what ()); } } } @@ -521,7 +521,7 @@ namespace tunnel case eTunnelStatePending: if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT) { - LogPrint ("Pending tunnel build request ", it->first, " timeout. Deleted"); + LogPrint (eLogError, "Tunnel: Pending build request ", it->first, " timeout, deleted"); // update stats auto config = tunnel->GetTunnelConfig (); if (config) @@ -546,7 +546,7 @@ namespace tunnel it++; break; case eTunnelStateBuildFailed: - LogPrint ("Pending tunnel build request ", it->first, " failed. Deleted"); + LogPrint (eLogError, "Tunnel: Pending build request ", it->first, " failed, deleted"); it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; break; @@ -571,7 +571,7 @@ namespace tunnel auto tunnel = *it; if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - LogPrint ("Tunnel ", tunnel->GetTunnelID (), " expired"); + LogPrint (eLogDebug, "Tunnel: ", tunnel->GetTunnelID (), " expired"); auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); @@ -602,7 +602,7 @@ namespace tunnel auto inboundTunnel = GetNextInboundTunnel (); auto router = i2p::data::netdb.GetRandomRouter (); if (!inboundTunnel || !router) return; - LogPrint ("Creating one hop outbound tunnel..."); + LogPrint (eLogDebug, "Creating one hop outbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) @@ -619,7 +619,7 @@ namespace tunnel auto tunnel = it->second; if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - LogPrint ("Tunnel ", tunnel->GetTunnelID (), " expired"); + LogPrint (eLogDebug, "Tunnel: ", tunnel->GetTunnelID (), " expired"); auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); @@ -647,7 +647,7 @@ namespace tunnel if (m_InboundTunnels.empty ()) { - LogPrint ("Creating zero hops inbound tunnel..."); + LogPrint (eLogDebug, "Creating zero hops inbound tunnel..."); CreateZeroHopsInboundTunnel (); if (!m_ExploratoryPool) { @@ -661,7 +661,7 @@ namespace tunnel { // trying to create one more inbound tunnel auto router = i2p::data::netdb.GetRandomRouter (); - LogPrint ("Creating one hop inbound tunnel..."); + LogPrint (eLogDebug, "Creating one hop inbound tunnel..."); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }) ); @@ -676,7 +676,7 @@ namespace tunnel if (ts > it->second->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { auto tmp = it->second; - LogPrint ("Transit tunnel ", tmp->GetTunnelID (), " expired"); + LogPrint (eLogDebug, "Transit tunnel ", tmp->GetTunnelID (), " expired"); { std::unique_lock l(m_TransitTunnelsMutex); it = m_TransitTunnels.erase (it); From a0fe02a5605e48880f6bdc99b47d4659b93242da Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 06:27:35 +0000 Subject: [PATCH 0673/6300] * sane log messages: BOB.cpp --- BOB.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index 38777092..af1fb19a 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -61,7 +61,7 @@ namespace client std::shared_ptr receiver) { if (ecode) - LogPrint ("BOB inbound tunnel read error: ", ecode.message ()); + LogPrint (eLogError, "BOB: inbound tunnel read error: ", ecode.message ()); else { receiver->bufferOffset += bytes_transferred; @@ -76,7 +76,7 @@ namespace client i2p::data::IdentHash ident; if (!context.GetAddressBook ().GetIdentHash (receiver->buffer, ident)) { - LogPrint (eLogError, "BOB address ", receiver->buffer, " not found"); + LogPrint (eLogError, "BOB: address ", receiver->buffer, " not found"); return; } auto leaseSet = GetLocalDestination ()->FindLeaseSet (ident); @@ -92,7 +92,7 @@ namespace client if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE) ReceiveAddress (receiver); else - LogPrint ("BOB missing inbound address "); + LogPrint (eLogError, "BOB: missing inbound address"); } } } @@ -102,12 +102,12 @@ namespace client if (leaseSet) CreateConnection (receiver, leaseSet); else - LogPrint ("LeaseSet for BOB inbound destination not found"); + LogPrint (eLogError, "BOB: LeaseSet for inbound destination not found"); } void BOBI2PInboundTunnel::CreateConnection (std::shared_ptr receiver, std::shared_ptr leaseSet) { - LogPrint ("New BOB inbound connection"); + LogPrint (eLogDebug, "BOB: New inbound connection"); auto connection = std::make_shared(this, receiver->socket, leaseSet); AddHandler (connection); connection->I2PConnect (receiver->data, receiver->dataLen); @@ -135,7 +135,7 @@ namespace client if (localDestination) localDestination->AcceptStreams (std::bind (&BOBI2POutboundTunnel::HandleAccept, this, std::placeholders::_1)); else - LogPrint ("Local destination not set for server tunnel"); + LogPrint (eLogError, "BOB: Local destination not set for server tunnel"); } void BOBI2POutboundTunnel::HandleAccept (std::shared_ptr stream) @@ -229,7 +229,7 @@ namespace client { if (ecode) { - LogPrint ("BOB command channel read error: ", ecode.message ()); + LogPrint (eLogError, "BOB: command channel read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -256,7 +256,7 @@ namespace client (this->*(it->second))(operand, eol - operand); else { - LogPrint (eLogError, "BOB unknown command ", m_ReceiveBuffer); + LogPrint (eLogError, "BOB: unknown command ", m_ReceiveBuffer); SendReplyError ("unknown command"); } @@ -269,7 +269,7 @@ namespace client m_ReceiveBufferOffset = size; else { - LogPrint (eLogError, "Malformed input of the BOB command channel"); + LogPrint (eLogError, "BOB: Malformed input of the command channel"); Terminate (); } } @@ -288,7 +288,7 @@ namespace client { if (ecode) { - LogPrint ("BOB command channel send error: ", ecode.message ()); + LogPrint (eLogError, "BOB: command channel send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -589,7 +589,7 @@ namespace client } catch (std::exception& ex) { - LogPrint (eLogError, "BOB: ", ex.what ()); + LogPrint (eLogError, "BOB: runtime exception: ", ex.what ()); } } } @@ -632,11 +632,11 @@ namespace client if (!ecode) { - LogPrint (eLogInfo, "New BOB command connection from ", session->GetSocket ().remote_endpoint ()); + LogPrint (eLogInfo, "BOB: New command connection from ", session->GetSocket ().remote_endpoint ()); session->SendVersion (); } else - LogPrint (eLogError, "BOB accept error: ", ecode.message ()); + LogPrint (eLogError, "BOB: accept error: ", ecode.message ()); } } } From 0859cf30f818a5e4b6681d8bd07878b367514876 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 06:35:39 +0000 Subject: [PATCH 0674/6300] * sane log messages: UPnP.cpp --- UPnP.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/UPnP.cpp b/UPnP.cpp index 6773b514..f8c038ff 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -46,7 +46,7 @@ template F GetKnownProcAddressImpl(M hmod, const char *name, F) { auto proc = reinterpret_cast(dlsym(hmod, name)); if (!proc) { - LogPrint("Error resolving ", name, " from UPNP library. This often happens if there is version mismatch!"); + LogPrint(eLogError, "UPnP: Error resolving ", name, " from library, version mismatch?"); } return proc; } @@ -83,7 +83,7 @@ namespace transport #endif if (m_Module == NULL) { - LogPrint ("Error loading UPNP library. This often happens if there is version mismatch!"); + LogPrint (eLogError, "UPnP: Error loading UPNP library, version mismatch?"); return; } else @@ -144,20 +144,20 @@ namespace transport r = UPNP_GetExternalIPAddressFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); if(r != UPNPCOMMAND_SUCCESS) { - LogPrint ("UPnP: UPNP_GetExternalIPAddress () returned ", r); + LogPrint (eLogError, "UPnP: UPNP_GetExternalIPAddress () returned ", r); return; } else { if (m_externalIPAddress[0]) { - LogPrint ("UPnP: ExternalIPAddress = ", m_externalIPAddress); + LogPrint (eLogInfo, "UPnP: ExternalIPAddress = ", m_externalIPAddress); i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress)); return; } else { - LogPrint ("UPnP: GetExternalIPAddress failed."); + LogPrint (eLogError, "UPnP: GetExternalIPAddress failed."); return; } } @@ -189,12 +189,12 @@ namespace transport #endif if (r!=UPNPCOMMAND_SUCCESS) { - LogPrint ("AddPortMapping (", strPort.c_str () ,", ", strPort.c_str () ,", ", m_NetworkAddr, ") failed with code ", r); + LogPrint (eLogError, "UPnP: AddPortMapping (", strPort.c_str () ,", ", strPort.c_str () ,", ", m_NetworkAddr, ") failed with code ", r); return; } else { - LogPrint ("UPnP Port Mapping successful. (", m_NetworkAddr ,":", strPort.c_str(), " type ", strType.c_str () ," -> ", m_externalIPAddress ,":", strPort.c_str() ,")"); + 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 @@ -224,7 +224,7 @@ namespace transport } int r = 0; r = UPNP_DeletePortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), 0); - LogPrint ("UPNP_DeletePortMapping() returned : ", r, "\n"); + LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", r, "\n"); } void UPnP::Close () From 89a0a94f3ec262d405445ca652879faf50bebd0f Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 06:50:12 +0000 Subject: [PATCH 0675/6300] * sane log messages: SAM.cpp --- SAM.cpp | 78 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index 0c0cb714..a20de13d 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -78,7 +78,7 @@ namespace client { if (ecode) { - LogPrint ("SAM handshake read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: handshake read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -88,7 +88,7 @@ namespace client char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred); if (eol) *eol = 0; - LogPrint ("SAM handshake ", m_Buffer); + LogPrint (eLogDebug, "SAM: handshake ", m_Buffer); char * separator = strchr (m_Buffer, ' '); if (separator) { @@ -127,7 +127,7 @@ namespace client } else { - LogPrint ("SAM handshake mismatch"); + LogPrint (eLogError, "SAM: handshake mismatch"); Terminate (); } } @@ -137,7 +137,7 @@ namespace client { if (ecode) { - LogPrint ("SAM handshake reply send error: ", ecode.message ()); + LogPrint (eLogError, "SAM: handshake reply send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -168,7 +168,7 @@ namespace client { if (ecode) { - LogPrint ("SAM reply send error: ", ecode.message ()); + LogPrint (eLogError, "SAM: reply send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -185,7 +185,7 @@ namespace client { if (ecode) { - LogPrint ("SAM read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -240,19 +240,19 @@ namespace client } else { - LogPrint (eLogError, "SAM unexpected message ", m_Buffer); + LogPrint (eLogError, "SAM: unexpected message ", m_Buffer); Terminate (); } } else { - LogPrint (eLogError, "SAM malformed message ", m_Buffer); + LogPrint (eLogError, "SAM: malformed message ", m_Buffer); Terminate (); } } else { - LogPrint (eLogWarning, "SAM incomplete message ", bytes_transferred); + LogPrint (eLogWarning, "SAM: incomplete message ", bytes_transferred); m_BufferOffset = bytes_transferred; // try to receive remaining message Receive (); @@ -262,7 +262,7 @@ namespace client void SAMSocket::ProcessSessionCreate (char * buf, size_t len) { - LogPrint ("SAM session create: ", buf); + LogPrint (eLogDebug, "SAM: session create: ", buf); std::map params; ExtractParams (buf, params); std::string& style = params[SAM_PARAM_STYLE]; @@ -333,7 +333,7 @@ namespace client void SAMSocket::ProcessStreamConnect (char * buf, size_t len) { - LogPrint (eLogDebug, "SAM stream connect: ", buf); + LogPrint (eLogDebug, "SAM: stream connect: ", buf); std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; @@ -382,14 +382,14 @@ namespace client Connect (leaseSet); else { - LogPrint ("SAM destination to connect not found"); + LogPrint (eLogError, "SAM: destination to connect not found"); SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true); } } void SAMSocket::ProcessStreamAccept (char * buf, size_t len) { - LogPrint (eLogDebug, "SAM stream accept: ", buf); + LogPrint (eLogDebug, "SAM: stream accept: ", buf); std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; @@ -415,7 +415,7 @@ namespace client size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data) { - LogPrint (eLogDebug, "SAM datagram send: ", buf, " ", len); + LogPrint (eLogDebug, "SAM: datagram send: ", buf, " ", len); std::map params; ExtractParams (buf, params); size_t size = boost::lexical_cast(params[SAM_PARAM_SIZE]), offset = data - buf; @@ -431,14 +431,14 @@ namespace client d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); } else - LogPrint (eLogError, "SAM missing datagram destination"); + LogPrint (eLogError, "SAM: missing datagram destination"); } else - LogPrint (eLogError, "SAM session is not created from DATAGRAM SEND"); + LogPrint (eLogError, "SAM: session is not created from DATAGRAM SEND"); } else { - LogPrint (eLogWarning, "SAM sent datagram size ", size, " exceeds buffer ", len - offset); + LogPrint (eLogWarning, "SAM: sent datagram size ", size, " exceeds buffer ", len - offset); return 0; // try to receive more } return offset + size; @@ -446,7 +446,7 @@ namespace client void SAMSocket::ProcessDestGenerate () { - LogPrint (eLogDebug, "SAM dest generate"); + LogPrint (eLogDebug, "SAM: dest generate"); auto keys = i2p::data::PrivateKeys::CreateRandomKeys (); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, @@ -460,7 +460,7 @@ namespace client void SAMSocket::ProcessNamingLookup (char * buf, size_t len) { - LogPrint (eLogDebug, "SAM naming lookup: ", buf); + LogPrint (eLogDebug, "SAM: naming lookup: ", buf); std::map params; ExtractParams (buf, params); std::string& name = params[SAM_PARAM_NAME]; @@ -483,7 +483,7 @@ namespace client } else { - LogPrint ("SAM naming failed. Unknown address ", name); + LogPrint (eLogError, "SAM: naming failed, unknown address ", name); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #else @@ -502,7 +502,7 @@ namespace client } else { - LogPrint (eLogInfo, "SAM naming lookup failed. LeaseSet for ", ident.ToBase32 (), " not found"); + LogPrint (eLogError, "SAM: naming lookup failed. LeaseSet for ", ident.ToBase32 (), " not found"); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, context.GetAddressBook ().ToAddress (ident).c_str()); @@ -548,7 +548,7 @@ namespace client { if (m_BufferOffset >= SAM_SOCKET_BUFFER_SIZE) { - LogPrint (eLogError, "Buffer is full. Terminate"); + LogPrint (eLogError, "SAM: Buffer is full, terminate"); Terminate (); return; } @@ -561,7 +561,7 @@ namespace client { if (ecode) { - LogPrint ("SAM read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -595,7 +595,7 @@ namespace client { if (ecode) { - LogPrint ("SAM stream read error: ", ecode.message ()); + LogPrint (eLogError, "SAM: stream read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -610,7 +610,7 @@ namespace client { if (ecode) { - LogPrint ("SAM socket write error: ", ecode.message ()); + LogPrint (eLogError, "SAM: socket write error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -622,7 +622,7 @@ namespace client { if (stream) { - LogPrint ("SAM incoming I2P connection for session ", m_ID); + LogPrint (eLogDebug, "SAM: incoming I2P connection for session ", m_ID); m_Stream = stream; context.GetAddressBook ().InsertAddress (stream->GetRemoteIdentity ()); auto session = m_Owner.FindSession (m_ID); @@ -642,12 +642,12 @@ namespace client I2PReceive (); } else - LogPrint (eLogInfo, "SAM I2P acceptor has been reset"); + LogPrint (eLogWarning, "SAM: I2P acceptor has been reset"); } void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { - LogPrint (eLogDebug, "SAM datagram received ", len); + LogPrint (eLogDebug, "SAM: datagram received ", len); auto base64 = from.ToBase64 (); #ifdef _MSC_VER size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), len); @@ -661,7 +661,7 @@ namespace client std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1)); } else - LogPrint (eLogWarning, "SAM received datagram size ", len," exceeds buffer"); + LogPrint (eLogWarning, "SAM: received datagram size ", len," exceeds buffer"); } SAMSession::SAMSession (std::shared_ptr dest): @@ -733,7 +733,7 @@ namespace client } catch (std::exception& ex) { - LogPrint ("SAM: ", ex.what ()); + LogPrint (eLogError, "SAM: runtime exception: ", ex.what ()); } } } @@ -753,14 +753,14 @@ namespace client auto ep = socket->GetSocket ().remote_endpoint (ec); if (!ec) { - LogPrint ("New SAM connection from ", ep); + LogPrint (eLogDebug, "SAM: new connection from ", ep); socket->ReceiveHandshake (); } else - LogPrint (eLogError, "SAM connection from error ", ec.message ()); + LogPrint (eLogError, "SAM: incoming connection error ", ec.message ()); } else - LogPrint ("SAM accept error: ", ecode.message ()); + LogPrint (eLogError, "SAM: accept error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Accept (); @@ -794,7 +794,7 @@ namespace client std::unique_lock l(m_SessionsMutex); auto ret = m_Sessions.insert (std::pair(id, new SAMSession (localDestination))); if (!ret.second) - LogPrint ("Session ", id, " already exists"); + LogPrint (eLogWarning, "SAM: Session ", id, " already exists"); return ret.first->second; } return nullptr; @@ -839,7 +839,7 @@ namespace client char * eol = strchr ((char *)m_DatagramReceiveBuffer, '\n'); *eol = 0; eol++; size_t payloadLen = bytes_transferred - ((uint8_t *)eol - m_DatagramReceiveBuffer); - LogPrint ("SAM datagram received ", m_DatagramReceiveBuffer," size=", payloadLen); + LogPrint (eLogDebug, "SAM: datagram received ", m_DatagramReceiveBuffer," size=", payloadLen); char * sessionID = strchr ((char *)m_DatagramReceiveBuffer, ' '); if (sessionID) { @@ -857,17 +857,17 @@ namespace client SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); } else - LogPrint ("Session ", sessionID, " not found"); + LogPrint (eLogError, "SAM: Session ", sessionID, " not found"); } else - LogPrint ("Missing destination key"); + LogPrint (eLogError, "SAM: Missing destination key"); } else - LogPrint ("Missing sessionID"); + LogPrint (eLogError, "SAM: Missing sessionID"); ReceiveDatagram (); } else - LogPrint ("SAM datagram receive error: ", ecode.message ()); + LogPrint (eLogError, "SAM: datagram receive error: ", ecode.message ()); } } } From 18fad9c9d9c34ba90b60d6137184d1b2ac6a0b7d Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 06:59:43 +0000 Subject: [PATCH 0676/6300] * sane log messages: Garlic.cpp --- Garlic.cpp | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index c94139e0..0008e25e 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -61,7 +61,7 @@ namespace garlic if (msgID == m_LeaseSetUpdateMsgID) { m_LeaseSetUpdateStatus = eLeaseSetUpToDate; - LogPrint (eLogInfo, "LeaseSet update confirmed"); + LogPrint (eLogInfo, "Garlic: LeaseSet update confirmed"); } else CleanupExpiredTags (); @@ -139,10 +139,10 @@ namespace garlic // create message if (!tagFound) // new session { - LogPrint ("No garlic tags available. Use ElGamal"); + LogPrint (eLogWarning, "Garlic: No tags available. Use ElGamal"); if (!m_Destination) { - LogPrint ("Can't use ElGamal for unknown destination"); + LogPrint (eLogError, "Garlic: Can't use ElGamal for unknown destination"); return nullptr; } // create ElGamal block @@ -237,7 +237,7 @@ namespace garlic m_Owner->DeliveryStatusSent (shared_from_this (), msgID); } else - LogPrint ("DeliveryStatus clove was not created"); + LogPrint (eLogWarning, "Garlic: DeliveryStatus clove was not created"); } // attach LeaseSet if (m_LeaseSetUpdateStatus == eLeaseSetUpdated) @@ -337,10 +337,10 @@ namespace garlic size += 3; } else - LogPrint (eLogError, "No inbound tunnels in the pool for DeliveryStatus"); + LogPrint (eLogError, "Garlic: No inbound tunnels in the pool for DeliveryStatus"); } else - LogPrint ("Missing local LeaseSet"); + LogPrint (eLogWarning, "Garlic: Missing local LeaseSet"); return size; } @@ -372,7 +372,7 @@ namespace garlic uint32_t length = bufbe32toh (buf); if (length > msg->GetLength ()) { - LogPrint (eLogError, "Garlic message length ", length, " exceeds I2NP message length ", msg->GetLength ()); + LogPrint (eLogWarning, "Garlic: message length ", length, " exceeds I2NP message length ", msg->GetLength ()); return; } buf += 4; // length @@ -389,7 +389,7 @@ namespace garlic HandleAESBlock (buf + 32, length - 32, it->second, msg->from); } else - LogPrint (eLogError, "Garlic message length ", length, " is less than 32 bytes"); + LogPrint (eLogWarning, "Garlic: message length ", length, " is less than 32 bytes"); m_Tags.erase (it); // tag might be used only once } else @@ -407,7 +407,7 @@ namespace garlic HandleAESBlock (buf + 514, length - 514, decryption, msg->from); } else - LogPrint (eLogError, "Failed to decrypt garlic"); + LogPrint (eLogError, "Garlic: Failed to decrypt message"); } // cleanup expired tags @@ -427,7 +427,7 @@ namespace garlic else it++; } - LogPrint (numExpiredTags, " tags expired for ", GetIdentHash().ToBase64 ()); + LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " tags expired for ", GetIdentHash().ToBase64 ()); } m_LastTagsCleanupTime = ts; } @@ -442,7 +442,7 @@ namespace garlic { if (tagCount*32 > len) { - LogPrint (eLogError, "Tag count ", tagCount, " exceeds length ", len); + LogPrint (eLogError, "Garlic: Tag count ", tagCount, " exceeds length ", len); return ; } uint32_t ts = i2p::util::GetSecondsSinceEpoch (); @@ -454,7 +454,7 @@ namespace garlic uint32_t payloadSize = bufbe32toh (buf); if (payloadSize > len) { - LogPrint (eLogError, "Unexpected payload size ", payloadSize); + LogPrint (eLogError, "Garlic: Unexpected payload size ", payloadSize); return; } buf += 4; @@ -469,7 +469,7 @@ namespace garlic SHA256 (buf, payloadSize, digest); if (memcmp (payloadHash, digest, 32)) // payload hash doesn't match { - LogPrint ("Wrong payload hash"); + LogPrint (eLogError, "Garlic: wrong payload hash"); return; } HandleGarlicPayload (buf, payloadSize, from); @@ -479,7 +479,7 @@ namespace garlic { const uint8_t * buf1 = buf; int numCloves = buf[0]; - LogPrint (numCloves," cloves"); + LogPrint (eLogDebug, "Garlic: ", numCloves," cloves"); buf++; for (int i = 0; i < numCloves; i++) { @@ -489,24 +489,24 @@ namespace garlic if (flag & 0x80) // encrypted? { // TODO: implement - LogPrint ("Clove encrypted"); + LogPrint (eLogWarning, "Garlic: clove encrypted"); buf += 32; } GarlicDeliveryType deliveryType = (GarlicDeliveryType)((flag >> 5) & 0x03); switch (deliveryType) { case eGarlicDeliveryTypeLocal: - LogPrint ("Garlic type local"); + LogPrint (eLogDebug, "Garlic: type local"); HandleI2NPMessage (buf, len, from); break; case eGarlicDeliveryTypeDestination: - LogPrint ("Garlic type destination"); + LogPrint (eLogDebug, "Garlic: type destination"); buf += 32; // destination. check it later or for multiple destinations HandleI2NPMessage (buf, len, from); break; case eGarlicDeliveryTypeTunnel: { - LogPrint ("Garlic type tunnel"); + LogPrint (eLogDebug, "Garlic: type tunnel"); // gwHash and gwTunnel sequence is reverted uint8_t * gwHash = buf; buf += 32; @@ -521,15 +521,15 @@ namespace garlic tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg); } else - LogPrint ("No outbound tunnels available for garlic clove"); + LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); break; } case eGarlicDeliveryTypeRouter: - LogPrint ("Garlic type router not supported"); + LogPrint (eLogWarning, "Garlic: type router not supported"); buf += 32; break; default: - LogPrint ("Unknow garlic delivery type ", (int)deliveryType); + LogPrint (eLogWarning, "Garlic: unknown delivery type ", (int)deliveryType); } buf += GetI2NPMessageLength (buf); // I2NP buf += 4; // CloveID @@ -537,7 +537,7 @@ namespace garlic buf += 3; // Certificate if (buf - buf1 > (int)len) { - LogPrint (eLogError, "Garlic clove is too long"); + LogPrint (eLogError, "Garlic: clove is too long"); break; } } @@ -601,7 +601,7 @@ namespace garlic { it->second->MessageConfirmed (msgID); m_CreatedSessions.erase (it); - LogPrint (eLogInfo, "Garlic message ", msgID, " acknowledged"); + LogPrint (eLogDebug, "Garlic: message ", msgID, " acknowledged"); } } } From e8952d7e0290a56d5ada7fed1f24e22c5ea69c0e Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 11:48:22 +0000 Subject: [PATCH 0677/6300] * sane log messages: TunnelPool.cpp --- TunnelPool.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index fcce1c22..3f92116d 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -34,12 +34,12 @@ namespace tunnel if (m_NumInboundHops > size) { m_NumInboundHops = size; - LogPrint (eLogInfo, "Inbound tunnel length has beed adjusted to ", size, " for explicit peers"); + LogPrint (eLogInfo, "Tunnels: Inbound tunnel length has beed adjusted to ", size, " for explicit peers"); } if (m_NumOutboundHops > size) { m_NumOutboundHops = size; - LogPrint (eLogInfo, "Outbound tunnel length has beed adjusted to ", size, " for explicit peers"); + LogPrint (eLogInfo, "Tunnels: Outbound tunnel length has beed adjusted to ", size, " for explicit peers"); } m_NumInboundTunnels = 1; m_NumOutboundTunnels = 1; @@ -203,7 +203,7 @@ namespace tunnel { for (auto it: m_Tests) { - LogPrint ("Tunnel test ", (int)it.first, " failed"); + LogPrint (eLogWarning, "Tunnels: test of ", (int)it.first, " failed"); // if test failed again with another tunnel we consider it failed if (it.second.first) { @@ -266,7 +266,7 @@ namespace tunnel if (m_LocalDestination) m_LocalDestination->ProcessGarlicMessage (msg); else - LogPrint (eLogWarning, "Local destination doesn't exist. Dropped"); + LogPrint (eLogWarning, "Tunnels: local destination doesn't exist, dropped"); } void TunnelPool::ProcessDeliveryStatus (std::shared_ptr msg) @@ -284,7 +284,7 @@ namespace tunnel it->second.first->SetState (eTunnelStateEstablished); if (it->second.second->GetState () == eTunnelStateTestFailed) it->second.second->SetState (eTunnelStateEstablished); - LogPrint ("Tunnel test ", it->first, " successive. ", i2p::util::GetMillisecondsSinceEpoch () - timestamp, " milliseconds"); + LogPrint (eLogDebug, "Tunnels: test of ", it->first, " successful. ", i2p::util::GetMillisecondsSinceEpoch () - timestamp, " milliseconds"); m_Tests.erase (it); } else @@ -292,7 +292,7 @@ namespace tunnel if (m_LocalDestination) m_LocalDestination->ProcessDeliveryStatusMessage (msg); else - LogPrint (eLogWarning, "Local destination doesn't exist. Dropped"); + LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped"); } } @@ -328,7 +328,7 @@ namespace tunnel auto hop = SelectNextHop (prevHop); if (!hop) { - LogPrint (eLogError, "Can't select next hop"); + LogPrint (eLogError, "Tunnels: Can't select next hop for ", prevHop->GetIdentHashBase64 ()); return false; } prevHop = hop; @@ -353,7 +353,7 @@ namespace tunnel peers.push_back (r->GetRouterIdentity ()); else { - LogPrint (eLogInfo, "Can't find router for ", ident.ToBase64 ()); + LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ()); i2p::data::netdb.RequestDestination (ident); return false; } @@ -366,7 +366,7 @@ namespace tunnel auto outboundTunnel = GetNextOutboundTunnel (); if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); - LogPrint ("Creating destination inbound tunnel..."); + LogPrint (eLogDebug, "Tunnels: Creating destination inbound tunnel..."); std::vector > peers; if (SelectPeers (peers, true)) { @@ -375,7 +375,7 @@ namespace tunnel tunnel->SetTunnelPool (shared_from_this ()); } else - LogPrint (eLogError, "Can't create inbound tunnel. No peers available"); + LogPrint (eLogError, "Tunnels: Can't create inbound tunnel, no peers available"); } void TunnelPool::RecreateInboundTunnel (std::shared_ptr tunnel) @@ -383,7 +383,7 @@ namespace tunnel auto outboundTunnel = GetNextOutboundTunnel (); if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); - LogPrint ("Re-creating destination inbound tunnel..."); + LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel..."); auto newTunnel = tunnels.CreateTunnel (std::make_shared(tunnel->GetPeers ()), outboundTunnel); newTunnel->SetTunnelPool (shared_from_this()); } @@ -395,7 +395,7 @@ namespace tunnel inboundTunnel = tunnels.GetNextInboundTunnel (); if (inboundTunnel) { - LogPrint ("Creating destination outbound tunnel..."); + LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); std::vector > peers; if (SelectPeers (peers, false)) { @@ -404,10 +404,10 @@ namespace tunnel tunnel->SetTunnelPool (shared_from_this ()); } else - LogPrint (eLogError, "Can't create outbound tunnel. No peers available"); + LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); } else - LogPrint (eLogError, "Can't create outbound tunnel. No inbound tunnels found"); + LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no inbound tunnels found"); } void TunnelPool::RecreateOutboundTunnel (std::shared_ptr tunnel) @@ -417,19 +417,19 @@ namespace tunnel inboundTunnel = tunnels.GetNextInboundTunnel (); if (inboundTunnel) { - LogPrint ("Re-creating destination outbound tunnel..."); + LogPrint (eLogDebug, "Tunnels: Re-creating destination outbound tunnel..."); auto newTunnel = tunnels.CreateTunnel ( std::make_shared (tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())); newTunnel->SetTunnelPool (shared_from_this ()); } else - LogPrint ("Can't re-create outbound tunnel. No inbound tunnels found"); + LogPrint (eLogDebug, "Tunnels: Can't re-create outbound tunnel, no inbound tunnels found"); } void TunnelPool::CreatePairedInboundTunnel (std::shared_ptr outboundTunnel) { - LogPrint (eLogInfo, "Creating paired inbound tunnel..."); + LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); auto tunnel = tunnels.CreateTunnel (std::make_shared(outboundTunnel->GetInvertedPeers ()), outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); } From 1cb08fdecc3bdfe277830b40b1c6af990411d520 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 12:07:13 +0000 Subject: [PATCH 0678/6300] * sane log messages: util.cpp --- util.cpp | 46 ++++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/util.cpp b/util.cpp index 6d2ee500..722fa011 100644 --- a/util.cpp +++ b/util.cpp @@ -166,7 +166,7 @@ namespace filesystem // Create data directory if (!boost::filesystem::create_directory( path )) { - LogPrint("Failed to create data directory!"); + LogPrint(eLogError, "FS: Failed to create data directory!"); path = ""; return path; } @@ -291,13 +291,13 @@ namespace http } else { - LogPrint ("Can't connect to ", address); + LogPrint (eLogError, "HTTPClient: Can't connect to ", address); return ""; } } catch (std::exception& ex) { - LogPrint ("Failed to download ", address, " : ", ex.what ()); + LogPrint (eLogError, "HTTPClient: runtime exception: ", ex.what ()); return ""; } } @@ -334,7 +334,7 @@ namespace http } else { - LogPrint ("HTTP response ", status); + LogPrint (eLogError, "HTTPClient: error, server responds ", status); return ""; } } @@ -399,19 +399,19 @@ namespace http } else { - LogPrint("HTTP response ", status); + LogPrint (eLogError, "HTTPClient: error, server responds ", status); return status; } } else { - LogPrint("Can't connect to proxy"); + LogPrint(eLogError, "HTTPClient: Can't connect to proxy"); return 408; } } catch (std::exception& ex) { - LogPrint("Failed to download ", address, " : ", ex.what()); + LogPrint(eLogError, "HTTPClient: runtime exception: ", ex.what()); return 408; } } @@ -515,7 +515,7 @@ namespace net ifaddrs* ifaddr, *ifa = nullptr; if(getifaddrs(&ifaddr) == -1) { - LogPrint(eLogError, "Can't excute getifaddrs"); + LogPrint(eLogError, "NetIface: Can't call getifaddrs(): ", strerror(errno)); return fallback; } @@ -551,14 +551,14 @@ namespace net if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) mtu = ifr.ifr_mtu; // MTU else - LogPrint (eLogError, "Failed to run ioctl"); + LogPrint (eLogError, "NetIface: Failed to run ioctl: ", strerror(errno)); close(fd); } else - LogPrint(eLogError, "Failed to create datagram socket"); + LogPrint(eLogError, "NetIface: Failed to create datagram socket"); } else - LogPrint(eLogWarning, "Interface for local address", localAddress.to_string(), " not found"); + LogPrint(eLogWarning, "NetIface: interface for local address", localAddress.to_string(), " not found"); freeifaddrs(ifaddr); return mtu; @@ -584,9 +584,7 @@ namespace net ); if(dwRetVal != NO_ERROR) { - LogPrint( - eLogError, "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed" - ); + LogPrint(eLogError, "NetIface: GetMTU(): enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return fallback; } @@ -597,9 +595,7 @@ namespace net pUnicast = pCurrAddresses->FirstUnicastAddress; if(pUnicast == nullptr) { - LogPrint( - eLogError, "GetMTU() has failed: not a unicast ipv4 address, this is not supported" - ); + LogPrint(eLogError, "NetIface: GetMTU(): not a unicast ipv4 address, this is not supported"); } for(int i = 0; pUnicast != nullptr; ++i) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; @@ -614,7 +610,7 @@ namespace net pCurrAddresses = pCurrAddresses->Next; } - LogPrint(eLogError, "GetMTU() error: no usable unicast ipv4 addresses found"); + LogPrint(eLogError, "NetIface: GetMTU(): no usable unicast ipv4 addresses found"); FREE(pAddresses); return fallback; } @@ -637,10 +633,7 @@ namespace net ); if(dwRetVal != NO_ERROR) { - LogPrint( - eLogError, - "GetMTU() has failed: enclosed GetAdaptersAddresses() call has failed" - ); + LogPrint(eLogError, "NetIface: GetMTU(): enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return fallback; } @@ -651,10 +644,7 @@ namespace net PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; if(pUnicast == nullptr) { - LogPrint( - eLogError, - "GetMTU() has failed: not a unicast ipv6 address, this is not supported" - ); + LogPrint(eLogError, "NetIface: GetMTU(): not a unicast ipv6 address, this is not supported"); } for(int i = 0; pUnicast != nullptr; ++i) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; @@ -678,7 +668,7 @@ namespace net pCurrAddresses = pCurrAddresses->Next; } - LogPrint(eLogError, "GetMTU() error: no usable unicast ipv6 addresses found"); + LogPrint(eLogError, "NetIface: GetMTU(): no usable unicast ipv6 addresses found"); FREE(pAddresses); return fallback; } @@ -701,7 +691,7 @@ namespace net inet_pton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); return GetMTUWindowsIpv6(inputAddress, fallback); } else { - LogPrint(eLogError, "GetMTU() has failed: address family is not supported"); + LogPrint(eLogError, "NetIface: GetMTU(): address family is not supported"); return fallback; } From 8d998088216c228c6c4a126a16df056ea0dbcc78 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 12:12:46 +0000 Subject: [PATCH 0679/6300] * sane log messages: I2PTunnel.cpp --- I2PTunnel.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 09edafc5..db98c7e4 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -78,7 +78,7 @@ namespace client { if (ecode) { - LogPrint ("I2PTunnel read error: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -103,7 +103,7 @@ namespace client { if (ecode) { - LogPrint ("I2PTunnel write error: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: write error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -124,7 +124,7 @@ namespace client { if (ecode) { - LogPrint ("I2PTunnel stream read error: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: stream read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -142,12 +142,12 @@ namespace client { if (ecode) { - LogPrint ("I2PTunnel connect error: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: connect error: ", ecode.message ()); Terminate (); } else { - LogPrint ("I2PTunnel connected"); + LogPrint (eLogDebug, "I2PTunnel: connected"); if (m_IsQuiet) StreamReceive (); else @@ -232,7 +232,7 @@ namespace client if (stream) { if (Kill()) return; - LogPrint (eLogInfo,"New I2PTunnel connection"); + LogPrint (eLogDebug, "I2PTunnel: new connection"); auto connection = std::make_shared(GetOwner(), m_Socket, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (); @@ -240,7 +240,7 @@ namespace client } else { - LogPrint (eLogError,"I2P Client Tunnel Issue when creating the stream, check the previous warnings for more info."); + LogPrint (eLogError, "I2PTunnel: Client Tunnel Issue when creating the stream, check the previous warnings for more info."); Terminate(); } } @@ -282,7 +282,7 @@ namespace client if (i2p::client::context.GetAddressBook ().GetIdentHash (m_Destination, identHash)) m_DestinationIdentHash = new i2p::data::IdentHash (identHash); else - LogPrint (eLogWarning,"Remote destination ", m_Destination, " not found"); + LogPrint (eLogWarning, "I2PTunnel: Remote destination ", m_Destination, " not found"); } return m_DestinationIdentHash; } @@ -333,12 +333,12 @@ namespace client if (!ecode) { auto addr = (*it).endpoint ().address (); - LogPrint (eLogInfo, "server tunnel ", (*it).host_name (), " has been resolved to ", addr); + LogPrint (eLogInfo, "I2PTunnel: server tunnel ", (*it).host_name (), " has been resolved to ", addr); m_Endpoint.address (addr); Accept (); } else - LogPrint (eLogError, "Unable to resolve server tunnel address: ", ecode.message ()); + LogPrint (eLogError, "I2PTunnel: Unable to resolve server tunnel address: ", ecode.message ()); } void I2PServerTunnel::SetAccessList (const std::set& accessList) @@ -359,7 +359,7 @@ namespace client localDestination->AcceptStreams (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1)); } else - LogPrint ("Local destination not set for server tunnel"); + LogPrint (eLogError, "I2PTunnel: Local destination not set for server tunnel"); } void I2PServerTunnel::HandleAccept (std::shared_ptr stream) @@ -370,7 +370,7 @@ namespace client { if (!m_AccessList.count (stream->GetRemoteIdentity ()->GetIdentHash ())) { - LogPrint (eLogWarning, "Address ", stream->GetRemoteIdentity ()->GetIdentHash ().ToBase32 (), " is not in white list. Incoming connection dropped"); + LogPrint (eLogWarning, "I2PTunnel: Address ", stream->GetRemoteIdentity ()->GetIdentHash ().ToBase32 (), " is not in white list. Incoming connection dropped"); stream->Close (); return; } From 56ef0dad9c0e84e67d2e5a1fc288209102780ca7 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 12:21:37 +0000 Subject: [PATCH 0680/6300] * sane log messages: Daemon.cpp --- Daemon.cpp | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index f698e90b..4991ab07 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -64,9 +64,8 @@ namespace i2p i2p::util::config::OptionParser(argc, argv); i2p::context.Init (); - LogPrint("\n\n\n\ni2pd starting\n"); - LogPrint("Version ", VERSION); - LogPrint("data directory: ", i2p::util::filesystem::GetDataDir().string()); + LogPrint(eLogInfo, "\n\n\n\ni2pd v", VERSION, " starting\n"); + LogPrint(eLogDebug, "data directory: ", i2p::util::filesystem::GetDataDir().string()); i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs); isDaemon = i2p::util::config::GetArg("-daemon", 0); @@ -90,9 +89,9 @@ namespace i2p i2p::context.SetLowBandwidth (); } - LogPrint("CMD parameters:"); + LogPrint(eLogDebug, "Daemon: CMD parameters:"); for (int i = 0; i < argc; ++i) - LogPrint(i, " ", argv[i]); + LogPrint(eLogDebug, i, ": ", argv[i]); return true; } @@ -116,55 +115,60 @@ namespace i2p StartLog (""); // write to stdout } + LogPrint(eLogInfo, "Daemon: staring HTTP Server"); d.httpServer = std::unique_ptr(new i2p::util::HTTPServer(i2p::util::config::GetArg("-httpaddress", "127.0.0.1"), i2p::util::config::GetArg("-httpport", 7070))); d.httpServer->Start(); - LogPrint("HTTP Server started"); + + LogPrint(eLogInfo, "Daemon: starting NetDB"); i2p::data::netdb.Start(); - LogPrint("NetDB started"); + #ifdef USE_UPNP + LogPrint(eLogInfo, "Daemon: starting UPnP"); d.m_UPnP.Start (); - LogPrint(eLogInfo, "UPnP started"); #endif + LogPrint(eLogInfo, "Daemon: starting Transports"); i2p::transport::transports.Start(); - LogPrint("Transports started"); + + LogPrint(eLogInfo, "Daemon: starting Tunnels"); i2p::tunnel::tunnels.Start(); - LogPrint("Tunnels started"); + + LogPrint(eLogInfo, "Daemon: starting Client"); i2p::client::context.Start (); - LogPrint("Client started"); + // I2P Control int i2pcontrolPort = i2p::util::config::GetArg("-i2pcontrolport", 0); if (i2pcontrolPort) { + LogPrint(eLogInfo, "Daemon: starting I2PControl"); d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2p::util::config::GetArg("-i2pcontroladdress", "127.0.0.1"), i2pcontrolPort)); d.m_I2PControlService->Start (); - LogPrint("I2PControl started"); } return true; } bool Daemon_Singleton::stop() { - LogPrint("Shutdown started."); + LogPrint(eLogInfo, "Daemon: shutting down"); + LogPrint(eLogInfo, "Daemon: stopping Client"); i2p::client::context.Stop(); - LogPrint("Client stopped"); + LogPrint(eLogInfo, "Daemon: stopping Tunnels"); i2p::tunnel::tunnels.Stop(); - LogPrint("Tunnels stopped"); #ifdef USE_UPNP + LogPrint(eLogInfo, "Daemon: stopping UPnP"); d.m_UPnP.Stop (); - LogPrint(eLogInfo, "UPnP stopped"); #endif + LogPrint(eLogInfo, "Daemon: stopping Transports"); i2p::transport::transports.Stop(); - LogPrint("Transports stopped"); + LogPrint(eLogInfo, "Daemon: stopping NetDB"); i2p::data::netdb.Stop(); - LogPrint("NetDB stopped"); + LogPrint(eLogInfo, "Daemon: stopping HTTP Server"); d.httpServer->Stop(); d.httpServer = nullptr; - LogPrint("HTTP Server stopped"); if (d.m_I2PControlService) { + LogPrint(eLogInfo, "Daemon: stopping I2PControl"); d.m_I2PControlService->Stop (); d.m_I2PControlService = nullptr; - LogPrint("I2PControl stopped"); } StopLog (); From 19c74ce9fa1dc912eaccdf0e02b835a93faa0d0a Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 12:25:48 +0000 Subject: [PATCH 0681/6300] * sane log messages: DaemonLinux.cpp --- DaemonLinux.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index ac7a3402..d4b72de0 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -26,7 +26,7 @@ void handle_signal(int sig) return; } } - LogPrint("Reloading config."); + LogPrint(eLogInfo, "Daemon: Got SIGHUP, reloading config."); i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs); break; case SIGABRT: @@ -59,7 +59,7 @@ namespace i2p int sid = setsid(); if (sid < 0) { - LogPrint("Error, could not create process group."); + LogPrint(eLogError, "Daemon: could not create process group."); return false; } std::string d(i2p::util::filesystem::GetDataDir().string ()); // make a copy @@ -80,12 +80,12 @@ namespace i2p pidFilehandle = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600); if (pidFilehandle == -1) { - LogPrint("Error, could not create pid file (", pidfile, ")\nIs an instance already running?"); + LogPrint(eLogError, "Daemon: could not create pid file ", pidfile, ": ", strerror(errno)); return false; } if (lockf(pidFilehandle, F_TLOCK, 0) == -1) { - LogPrint("Error, could not lock pid file (", pidfile, ")\nIs an instance already running?"); + LogPrint(eLogError, "Daemon: could not lock pid file ", pidfile, ": ", strerror(errno)); return false; } char pid[10]; From 16880074fa572e7135c6ea916e5c296b0bed6869 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 12:27:47 +0000 Subject: [PATCH 0682/6300] * sane log messages: DaemonWin32.cpp --- DaemonWin32.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DaemonWin32.cpp b/DaemonWin32.cpp index 96b28076..6e0c46b8 100644 --- a/DaemonWin32.cpp +++ b/DaemonWin32.cpp @@ -49,17 +49,17 @@ namespace i2p if (isDaemon == 1) { - LogPrint("Service session"); + LogPrint(eLogDebug, "Daemon: running as service"); I2PService service(SERVICE_NAME); if (!I2PService::Run(service)) { - LogPrint("Service failed to run w/err 0x%08lx\n", GetLastError()); + LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } else - LogPrint("User session"); + LogPrint(eLogDebug, "Daemon: running as user"); return true; } @@ -80,4 +80,4 @@ namespace i2p } } -#endif \ No newline at end of file +#endif From 3e8c247c0582113906811e6949a7fcb952db7e79 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 12:44:03 +0000 Subject: [PATCH 0683/6300] * sane log messages: ClientContext.cpp --- ClientContext.cpp | 63 ++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 557deb38..6b49b53a 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -41,17 +41,18 @@ namespace client std::string proxyKeys = i2p::util::config::GetArg("-proxykeys", ""); if (proxyKeys.length () > 0) localDestination = LoadLocalDestination (proxyKeys, false); + LogPrint(eLogInfo, "Clients: starting HTTP Proxy"); m_HttpProxy = new i2p::proxy::HTTPProxy(i2p::util::config::GetArg("-httpproxyaddress", "127.0.0.1"), i2p::util::config::GetArg("-httpproxyport", 4446), localDestination); m_HttpProxy->Start(); - LogPrint("HTTP Proxy started"); + LogPrint(eLogInfo, "Clients: starting SOCKS Proxy"); m_SocksProxy = new i2p::proxy::SOCKSProxy(i2p::util::config::GetArg("-socksproxyaddress", "127.0.0.1"), i2p::util::config::GetArg("-socksproxyport", 4447), localDestination); m_SocksProxy->Start(); - LogPrint("SOCKS Proxy Started"); - // I2P tunnels + // I2P tunnels: IRC std::string ircDestination = i2p::util::config::GetArg("-ircdest", ""); if (ircDestination.length () > 0) // ircdest is presented { + LogPrint(eLogInfo, "Clients: starting IRC tunnel"); localDestination = nullptr; std::string ircKeys = i2p::util::config::GetArg("-irckeys", ""); if (ircKeys.length () > 0) @@ -60,17 +61,18 @@ namespace client auto ircTunnel = new I2PClientTunnel (ircDestination, i2p::util::config::GetArg("-ircaddress", "127.0.0.1"), ircPort, localDestination); ircTunnel->Start (); m_ClientTunnels.insert (std::make_pair(ircPort, std::unique_ptr(ircTunnel))); - LogPrint("IRC tunnel started"); } + + // I2P tunnels: local site std::string eepKeys = i2p::util::config::GetArg("-eepkeys", ""); if (eepKeys.length () > 0) // eepkeys file is presented { + LogPrint(eLogInfo, "Clients: starting server tunnel for eepsite"); localDestination = LoadLocalDestination (eepKeys, true); auto serverTunnel = new I2PServerTunnel (i2p::util::config::GetArg("-eephost", "127.0.0.1"), i2p::util::config::GetArg("-eepport", 80), localDestination); serverTunnel->Start (); m_ServerTunnels.insert (std::make_pair(localDestination->GetIdentHash (), std::unique_ptr(serverTunnel))); - LogPrint("Server tunnel started"); } ReadTunnels (); @@ -78,18 +80,18 @@ namespace client int samPort = i2p::util::config::GetArg("-samport", 0); if (samPort) { + LogPrint(eLogInfo, "Clients: starting SAM bridge"); m_SamBridge = new SAMBridge (i2p::util::config::GetArg("-samaddress", "127.0.0.1"), samPort); m_SamBridge->Start (); - LogPrint("SAM bridge started"); } // BOB int bobPort = i2p::util::config::GetArg("-bobport", 0); if (bobPort) { + LogPrint(eLogInfo, "Clients: starting BOB command channel"); m_BOBCommandChannel = new BOBCommandChannel (i2p::util::config::GetArg("-bobaddress", "127.0.0.1"), bobPort); m_BOBCommandChannel->Start (); - LogPrint("BOB command channel started"); } m_AddressBook.Start (); @@ -97,40 +99,47 @@ namespace client void ClientContext::Stop () { + LogPrint(eLogInfo, "Clients: stopping HTTP Proxy"); m_HttpProxy->Stop(); delete m_HttpProxy; m_HttpProxy = nullptr; - LogPrint("HTTP Proxy stopped"); + + LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy"); m_SocksProxy->Stop(); delete m_SocksProxy; m_SocksProxy = nullptr; - LogPrint("SOCKS Proxy stopped"); + for (auto& it: m_ClientTunnels) { + LogPrint(eLogInfo, "Clients: stopping I2P client tunnel on port ", it.first); it.second->Stop (); - LogPrint("I2P client tunnel on port ", it.first, " stopped"); } m_ClientTunnels.clear (); + for (auto& it: m_ServerTunnels) { + LogPrint(eLogInfo, "Clients: stopping I2P server tunnel"); it.second->Stop (); - LogPrint("I2P server tunnel stopped"); } m_ServerTunnels.clear (); + if (m_SamBridge) { + LogPrint(eLogInfo, "Clients: stopping SAM bridge"); m_SamBridge->Stop (); delete m_SamBridge; m_SamBridge = nullptr; - LogPrint("SAM brdige stopped"); } + if (m_BOBCommandChannel) { + LogPrint(eLogInfo, "Clients: stopping BOB command channel"); m_BOBCommandChannel->Stop (); delete m_BOBCommandChannel; m_BOBCommandChannel = nullptr; - LogPrint("BOB command channel stopped"); - } + } + + LogPrint(eLogInfo, "Clients: stopping AddressBook"); m_AddressBook.Stop (); for (auto it: m_Destinations) it.second->Stop (); @@ -138,6 +147,7 @@ namespace client m_SharedLocalDestination = nullptr; } + // should be moved in i2p::utils::fs std::shared_ptr ClientContext::LoadLocalDestination (const std::string& filename, bool isPublic) { i2p::data::PrivateKeys keys; @@ -152,11 +162,11 @@ namespace client s.read ((char *)buf, len); keys.FromBuffer (buf, len); delete[] buf; - LogPrint ("Local address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " loaded"); + LogPrint (eLogInfo, "Clients: Local address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " loaded"); } else { - LogPrint ("Can't open file ", fullPath, " Creating new one"); + LogPrint (eLogError, "Clients: can't open file ", fullPath, " Creating new one"); keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); size_t len = keys.GetFullLen (); @@ -165,7 +175,7 @@ namespace client f.write ((char *)buf, len); delete[] buf; - LogPrint ("New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); + LogPrint (eLogInfo, "Clients: New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); } std::shared_ptr localDestination = nullptr; @@ -173,7 +183,7 @@ namespace client auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ()); if (it != m_Destinations.end ()) { - LogPrint (eLogWarning, "Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " alreday exists"); + LogPrint (eLogWarning, "Clients: Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " already exists"); localDestination = it->second; } else @@ -217,7 +227,7 @@ namespace client auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ()); if (it != m_Destinations.end ()) { - LogPrint ("Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " exists"); + LogPrint (eLogWarning, "Clients: Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " exists"); if (!it->second->IsRunning ()) { it->second->Start (); @@ -240,6 +250,7 @@ namespace client return nullptr; } + // should be moved in i2p::utils::fs void ClientContext::ReadTunnels () { boost::property_tree::ptree pt; @@ -250,7 +261,7 @@ namespace client } catch (std::exception& ex) { - LogPrint (eLogWarning, "Can't read ", pathTunnelsConfigFile, ": ", ex.what ()); + LogPrint (eLogWarning, "Clients: Can't read ", pathTunnelsConfigFile, ": ", ex.what ()); return; } @@ -278,7 +289,7 @@ namespace client if (m_ClientTunnels.insert (std::make_pair (port, std::unique_ptr(clientTunnel))).second) clientTunnel->Start (); else - LogPrint (eLogError, "I2P client tunnel with port ", port, " already exists"); + LogPrint (eLogError, "Clients: I2P client tunnel with port ", port, " already exists"); numClientTunnels++; } else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP) @@ -311,20 +322,20 @@ namespace client if (m_ServerTunnels.insert (std::make_pair (localDestination->GetIdentHash (), std::unique_ptr(serverTunnel))).second) serverTunnel->Start (); else - LogPrint (eLogError, "I2P server tunnel for destination ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), " already exists"); + LogPrint (eLogError, "Clients: I2P server tunnel for destination ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), " already exists"); numServerTunnels++; } else - LogPrint (eLogWarning, "Unknown section type=", type, " of ", name, " in ", pathTunnelsConfigFile); + LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", pathTunnelsConfigFile); } catch (std::exception& ex) { - LogPrint (eLogError, "Can't read tunnel ", name, " params: ", ex.what ()); + LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ()); } } - LogPrint (eLogInfo, numClientTunnels, " I2P client tunnels created"); - LogPrint (eLogInfo, numServerTunnels, " I2P server tunnels created"); + LogPrint (eLogInfo, "Clients: ", numClientTunnels, " I2P client tunnels created"); + LogPrint (eLogInfo, "Clients: ", numServerTunnels, " I2P server tunnels created"); } } } From 830fe7f9b84c3b2ce407613903fd2f6fa7bfc536 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 12:57:22 +0000 Subject: [PATCH 0684/6300] * sane log messages: Transports.cpp --- Transports.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index cdd35a42..72f95fd8 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -126,12 +126,12 @@ namespace transport if (!m_SSUServer) { m_SSUServer = new SSUServer (address.port); - LogPrint ("Start listening UDP port ", address.port); + LogPrint (eLogInfo, "Transports: Start listening UDP port ", address.port); m_SSUServer->Start (); DetectExternalIP (); } else - LogPrint ("SSU server already exists"); + LogPrint (eLogError, "Transports: SSU server already exists"); } } m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); @@ -176,7 +176,7 @@ namespace transport } catch (std::exception& ex) { - LogPrint ("Transports: ", ex.what ()); + LogPrint (eLogError, "Transports: runtime exception: ", ex.what ()); } } } @@ -236,7 +236,7 @@ namespace transport } catch (std::exception& ex) { - LogPrint (eLogError, "Transports::PostMessages ", ex.what ()); + LogPrint (eLogError, "Transports: PostMessages exception:", ex.what ()); } if (!connected) return; } @@ -278,14 +278,14 @@ namespace transport { if (address->addressString.length () > 0) // trying to resolve { - LogPrint (eLogInfo, "Resolving NTCP ", address->addressString); + LogPrint (eLogDebug, "Transports: Resolving NTCP ", address->addressString); NTCPResolve (address->addressString, ident); return true; } } } else - LogPrint (eLogInfo, "NTCP address is not presented. Trying SSU"); + LogPrint (eLogWarning, "Transports: NTCP address is not present for ", i2p::data::GetIdentHashAbbreviation (ident), ", trying SSU"); } if (peer.numAttempts == 1)// SSU { @@ -308,21 +308,21 @@ namespace transport { if (address->addressString.length () > 0) // trying to resolve { - LogPrint (eLogInfo, "Resolving SSU ", address->addressString); + LogPrint (eLogDebug, "Transports: Resolving SSU ", address->addressString); SSUResolve (address->addressString, ident); return true; } } } } - LogPrint (eLogError, "No NTCP and SSU addresses available"); + LogPrint (eLogError, "Transports: No NTCP or SSU addresses available"); peer.Done (); m_Peers.erase (ident); return false; } else // otherwise request RI { - LogPrint ("Router not found. Requested"); + LogPrint (eLogInfo, "Transports: RouterInfo for ", ident.ToBase64 (), " not found, requested"); i2p::data::netdb.RequestDestination (ident, std::bind ( &Transports::RequestComplete, this, std::placeholders::_1, ident)); } @@ -341,13 +341,13 @@ namespace transport { if (r) { - LogPrint ("Router found. Trying to connect"); + LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, Trying to connect"); it->second.router = r; ConnectToPeer (ident, it->second); } else { - LogPrint ("Router not found. Failed to send messages"); + LogPrint (eLogError, "Transports: RouterInfo not found, Failed to send messages"); m_Peers.erase (it); } } @@ -371,7 +371,7 @@ namespace transport if (!ecode && peer.router) { auto address = (*it).endpoint ().address (); - LogPrint (eLogInfo, (*it).host_name (), " has been resolved to ", address); + LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); auto addr = peer.router->GetNTCPAddress (); if (addr) { @@ -380,7 +380,7 @@ namespace transport return; } } - LogPrint (eLogError, "Unable to resolve NTCP address: ", ecode.message ()); + LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ()); m_Peers.erase (it1); } } @@ -403,7 +403,7 @@ namespace transport if (!ecode && peer.router) { auto address = (*it).endpoint ().address (); - LogPrint (eLogInfo, (*it).host_name (), " has been resolved to ", address); + LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); auto addr = peer.router->GetSSUAddress (!context.SupportsV6 ());; if (addr) { @@ -411,7 +411,7 @@ namespace transport return; } } - LogPrint (eLogError, "Unable to resolve SSU address: ", ecode.message ()); + LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ()); m_Peers.erase (it1); } } @@ -428,7 +428,7 @@ namespace transport if (ssuSession) // try SSU first { m_SSUServer->DeleteSession (ssuSession); - LogPrint ("SSU session closed"); + LogPrint (eLogDebug, "Transports: SSU session closed"); } // TODO: delete NTCP } @@ -453,7 +453,7 @@ namespace transport } } else - LogPrint (eLogError, "Can't detect external IP. SSU is not available"); + LogPrint (eLogError, "Transports: Can't detect external IP. SSU is not available"); } void Transports::PeerTest () @@ -541,7 +541,7 @@ namespace transport { if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) { - LogPrint (eLogError, "Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); + LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); it = m_Peers.erase (it); } else From d9e659deb0abcec9c3a1de420b4c92b0610cc644 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 13:11:56 +0000 Subject: [PATCH 0685/6300] * sane log messages: Destination.cpp --- Destination.cpp | 64 ++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 67042276..ec2c1741 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -34,7 +34,7 @@ namespace client if (len > 0) { inboundTunnelLen = len; - LogPrint (eLogInfo, "Inbound tunnel length set to ", len); + LogPrint (eLogInfo, "Destination: Inbound tunnel length set to ", len); } } it = params->find (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH); @@ -44,7 +44,7 @@ namespace client if (len > 0) { outboundTunnelLen = len; - LogPrint (eLogInfo, "Outbound tunnel length set to ", len); + LogPrint (eLogInfo, "Destination: Outbound tunnel length set to ", len); } } it = params->find (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY); @@ -54,7 +54,7 @@ namespace client if (quantity > 0) { inboundTunnelsQuantity = quantity; - LogPrint (eLogInfo, "Inbound tunnels quantity set to ", quantity); + LogPrint (eLogInfo, "Destination: Inbound tunnels quantity set to ", quantity); } } it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY); @@ -64,7 +64,7 @@ namespace client if (quantity > 0) { outboundTunnelsQuantity = quantity; - LogPrint (eLogInfo, "Outbound tunnels quantity set to ", quantity); + LogPrint (eLogInfo, "Destination: Outbound tunnels quantity set to ", quantity); } } it = params->find (I2CP_PARAM_EXPLICIT_PEERS); @@ -79,14 +79,14 @@ namespace client ident.FromBase64 (b64); explicitPeers->push_back (ident); } - LogPrint (eLogInfo, "Explicit peers set to ", it->second); + LogPrint (eLogInfo, "Destination: Explicit peers set to ", it->second); } } m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inboundTunnelLen, outboundTunnelLen, inboundTunnelsQuantity, outboundTunnelsQuantity); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); if (m_IsPublic) - LogPrint (eLogInfo, "Local address ", GetIdentHash().ToBase32 (), " created"); + LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created"); } ClientDestination::~ClientDestination () @@ -112,7 +112,7 @@ namespace client } catch (std::exception& ex) { - LogPrint ("Destination: ", ex.what ()); + LogPrint (eLogError, "Destination: runtime exception: ", ex.what ()); } } } @@ -175,7 +175,7 @@ namespace client if (it->second->HasNonExpiredLeases ()) return it->second; else - LogPrint ("All leases of remote LeaseSet expired"); + LogPrint (eLogWarning, "Destination: All leases of remote LeaseSet expired"); } else { @@ -256,7 +256,7 @@ namespace client size_t offset = DATABASE_STORE_HEADER_SIZE; if (replyToken) { - LogPrint (eLogInfo, "Reply token is ignored for DatabaseStore"); + LogPrint (eLogInfo, "Destination: Reply token is ignored for DatabaseStore"); offset += 36; } std::shared_ptr leaseSet; @@ -293,7 +293,7 @@ namespace client } } else - LogPrint (eLogError, "Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ". Dropped"); + LogPrint (eLogError, "Destination: Unexpected client's DatabaseStore type ", buf[DATABASE_STORE_TYPE_OFFSET], ", dropped"); auto it1 = m_LeaseSetRequests.find (buf + DATABASE_STORE_KEY_OFFSET); if (it1 != m_LeaseSetRequests.end ()) @@ -308,7 +308,7 @@ namespace client { i2p::data::IdentHash key (buf); int num = buf[32]; // num - LogPrint ("DatabaseSearchReply for ", key.ToBase64 (), " num=", num); + LogPrint (eLogDebug, "Destination: DatabaseSearchReply for ", key.ToBase64 (), " num=", num); auto it = m_LeaseSetRequests.find (key); if (it != m_LeaseSetRequests.end ()) { @@ -322,21 +322,21 @@ namespace client auto floodfill = i2p::data::netdb.FindRouter (peerHash); if (floodfill) { - LogPrint (eLogInfo, "Requesting ", key.ToBase64 (), " at ", peerHash.ToBase64 ()); + LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", peerHash.ToBase64 ()); if (SendLeaseSetRequest (key, floodfill, request)) found = true; } else { - LogPrint (eLogInfo, "Found new floodfill. Request it"); + LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); // TODO: recheck this message i2p::data::netdb.RequestDestination (peerHash); } } if (!found) - LogPrint (eLogError, "Suggested floodfills are not presented in netDb"); + LogPrint (eLogError, "Destination: Suggested floodfills are not presented in netDb"); } else - LogPrint (eLogInfo, key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST," floodfills"); + LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills"); if (!found) { if (request->requestComplete) request->requestComplete (nullptr); @@ -344,7 +344,7 @@ namespace client } } else - LogPrint ("Request for ", key.ToBase64 (), " not found"); + LogPrint (eLogWarning, "Destination: Request for ", key.ToBase64 (), " not found"); } void ClientDestination::HandleDeliveryStatusMessage (std::shared_ptr msg) @@ -352,7 +352,7 @@ namespace client uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET); if (msgID == m_PublishReplyToken) { - LogPrint (eLogDebug, "Publishing confirmed"); + LogPrint (eLogDebug, "Destination: Publishing LeaseSet confirmed"); m_ExcludedFloodfills.clear (); m_PublishReplyToken = 0; } @@ -372,30 +372,30 @@ namespace client { if (!m_LeaseSet || !m_Pool) { - LogPrint (eLogError, "Can't publish non-existing LeaseSet"); + LogPrint (eLogError, "Destination: Can't publish non-existing LeaseSet"); return; } if (m_PublishReplyToken) { - LogPrint (eLogInfo, "Publishing is pending"); + LogPrint (eLogDebug, "Destination: Publishing LeaseSet is pending"); return; } auto outbound = m_Pool->GetNextOutboundTunnel (); if (!outbound) { - LogPrint ("Can't publish LeaseSet. No outbound tunnels"); + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); return; } std::set excluded; auto floodfill = i2p::data::netdb.GetClosestFloodfill (m_LeaseSet->GetIdentHash (), m_ExcludedFloodfills); if (!floodfill) { - LogPrint ("Can't publish LeaseSet. No more floodfills found"); + LogPrint (eLogError, "Destination: Can't publish LeaseSet, no more floodfills found"); m_ExcludedFloodfills.clear (); return; } m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); - LogPrint (eLogDebug, "Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); + LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); @@ -410,7 +410,7 @@ namespace client { if (m_PublishReplyToken) { - LogPrint (eLogWarning, "Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, "seconds. Try again"); + LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again"); m_PublishReplyToken = 0; Publish (); } @@ -433,7 +433,7 @@ namespace client if (dest) dest->HandleDataMessagePayload (buf, length); else - LogPrint ("Missing streaming destination"); + LogPrint (eLogError, "Destination: Missing streaming destination"); } break; case PROTOCOL_TYPE_DATAGRAM: @@ -441,10 +441,10 @@ namespace client if (m_DatagramDestination) m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length); else - LogPrint ("Missing streaming destination"); + LogPrint (eLogError, "Destination: Missing datagram destination"); break; default: - LogPrint ("Data: unexpected protocol ", buf[9]); + LogPrint (eLogError, "Destination: Data: unexpected protocol ", buf[9]); } } @@ -564,23 +564,23 @@ namespace client } else // duplicate { - LogPrint (eLogError, "Request of ", dest.ToBase64 (), " is pending already"); + LogPrint (eLogWarning, "Destination: Request of LeaseSet ", dest.ToBase64 (), " is pending already"); // TODO: queue up requests if (request->requestComplete) request->requestComplete (nullptr); } } else - LogPrint (eLogError, "No floodfills found"); + LogPrint (eLogError, "Destination: Can't request LeaseSet, no floodfills found"); } bool ClientDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, std::shared_ptr request) { auto replyTunnel = m_Pool->GetNextInboundTunnel (); - if (!replyTunnel) LogPrint (eLogError, "No inbound tunnels found"); + if (!replyTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no inbound tunnels found"); auto outboundTunnel = m_Pool->GetNextOutboundTunnel (); - if (!outboundTunnel) LogPrint (eLogError, "No outbound tunnels found"); + if (!outboundTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found"); if (replyTunnel && outboundTunnel) { @@ -632,7 +632,7 @@ namespace client } else { - LogPrint (eLogInfo, dest.ToBase64 (), " was not found within ", MAX_LEASESET_REQUEST_TIMEOUT, " seconds"); + LogPrint (eLogWarning, "Destination: ", dest.ToBase64 (), " was not found within ", MAX_LEASESET_REQUEST_TIMEOUT, " seconds"); done = true; } @@ -663,7 +663,7 @@ namespace client { if (!it->second->HasNonExpiredLeases ()) // all leases expired { - LogPrint ("Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); + LogPrint (eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); it = m_RemoteLeaseSets.erase (it); } else From 642d0e6f74b42bee042ea4121493dc333a791ae6 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 13:33:58 +0000 Subject: [PATCH 0686/6300] * sane log messages: Streaming.cpp --- Streaming.cpp | 66 ++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 99223512..3af8b184 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -53,7 +53,7 @@ namespace stream delete it; m_SavedPackets.clear (); - LogPrint (eLogDebug, "Stream deleted"); + LogPrint (eLogDebug, "Streaming: Stream deleted"); } void Stream::Terminate () @@ -83,12 +83,12 @@ namespace stream if (!receivedSeqn && !isSyn) { // plain ack - LogPrint (eLogDebug, "Plain ACK received"); + LogPrint (eLogDebug, "Streaming: Plain ACK received"); delete packet; return; } - LogPrint (eLogDebug, "Received seqn=", receivedSeqn); + LogPrint (eLogDebug, "Streaming: Received seqn=", receivedSeqn); if (isSyn || receivedSeqn == m_LastReceivedSequenceNumber + 1) { // we have received next in sequence message @@ -128,13 +128,13 @@ namespace stream if (receivedSeqn <= m_LastReceivedSequenceNumber) { // we have received duplicate - LogPrint (eLogWarning, "Duplicate message ", receivedSeqn, " received"); + LogPrint (eLogWarning, "Streaming: Duplicate message ", receivedSeqn, " received"); SendQuickAck (); // resend ack for previous message again delete packet; // packet dropped } else { - LogPrint (eLogWarning, "Missing messages from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1); + LogPrint (eLogWarning, "Streaming: Missing messages from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1); // save message and wait for missing message again SavePacket (packet); if (m_LastReceivedSequenceNumber >= 0) @@ -169,44 +169,37 @@ namespace stream // process flags uint32_t receivedSeqn = packet->GetSeqn (); uint16_t flags = packet->GetFlags (); - LogPrint (eLogDebug, "Process seqn=", receivedSeqn, ", flags=", flags); + LogPrint (eLogDebug, "Streaming: Process seqn=", receivedSeqn, ", flags=", flags); const uint8_t * optionData = packet->GetOptionData (); - if (flags & PACKET_FLAG_SYNCHRONIZE) - LogPrint (eLogDebug, "Synchronize"); if (flags & PACKET_FLAG_DELAY_REQUESTED) - { optionData += 2; - } if (flags & PACKET_FLAG_FROM_INCLUDED) { m_RemoteIdentity = std::make_shared(optionData, packet->GetOptionSize ()); optionData += m_RemoteIdentity->GetFullLen (); - LogPrint (eLogInfo, "From identity ", m_RemoteIdentity->GetIdentHash ().ToBase64 ()); if (!m_RemoteLeaseSet) - LogPrint (eLogDebug, "Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 ()); + LogPrint (eLogDebug, "Streaming: Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 ()); } if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED) { uint16_t maxPacketSize = bufbe16toh (optionData); - LogPrint (eLogDebug, "Max packet size ", maxPacketSize); optionData += 2; } if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) { - LogPrint (eLogDebug, "Signature"); uint8_t signature[256]; auto signatureLen = m_RemoteIdentity->GetSignatureLen (); memcpy (signature, optionData, signatureLen); memset (const_cast(optionData), 0, signatureLen); if (!m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature)) { - LogPrint (eLogError, "Signature verification failed"); - Close (); + LogPrint (eLogError, "Streaming: Signature verification failed"); + Close (); flags |= PACKET_FLAG_CLOSE; } memcpy (const_cast(optionData), signature, signatureLen); @@ -226,7 +219,6 @@ namespace stream if (flags & (PACKET_FLAG_CLOSE | PACKET_FLAG_RESET)) { - LogPrint (eLogInfo, (flags & PACKET_FLAG_RESET) ? "Reset" : "Closed"); m_Status = eStreamStatusReset; Close (); } @@ -254,7 +246,7 @@ namespace stream } if (nacked) { - LogPrint (eLogDebug, "Packet ", seqn, " NACK"); + LogPrint (eLogDebug, "Streaming: Packet ", seqn, " NACK"); it++; continue; } @@ -418,7 +410,7 @@ namespace stream } if (lastReceivedSeqn < 0) { - LogPrint (eLogError, "No packets have been received yet"); + LogPrint (eLogError, "Streaming: No packets have been received yet"); return; } @@ -474,7 +466,7 @@ namespace stream p.len = size; SendPackets (std::vector { &p }); - LogPrint ("Quick Ack sent. ", (int)numNacks, " NACKs"); + LogPrint (eLogDebug, "Streaming: Quick Ack sent. ", (int)numNacks, " NACKs"); } void Stream::Close () @@ -485,7 +477,7 @@ namespace stream m_Status = eStreamStatusClosing; Close (); // recursion if (m_Status == eStreamStatusClosing) //still closing - LogPrint (eLogInfo, "Trying to send stream data before closing"); + LogPrint (eLogInfo, "Streaming: Trying to send stream data before closing"); break; case eStreamStatusReset: SendClose (); @@ -507,7 +499,7 @@ namespace stream m_LocalDestination.DeleteStream (shared_from_this ()); break; default: - LogPrint (eLogWarning, "Unexpected stream status ", (int)m_Status); + LogPrint (eLogWarning, "Streaming: Unexpected stream status ", (int)m_Status); }; } @@ -539,7 +531,7 @@ namespace stream p->len = size; m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p)); - LogPrint ("FIN sent"); + LogPrint (eLogDebug, "Streaming: FIN sent"); } size_t Stream::ConcatenatePackets (uint8_t * buf, size_t len) @@ -593,7 +585,7 @@ namespace stream UpdateCurrentRemoteLease (); if (!m_RemoteLeaseSet) { - LogPrint (eLogError, "Can't send packets. Missing remote LeaseSet"); + LogPrint (eLogError, "Streaming: Can't send packets, missing remote LeaseSet"); return; } } @@ -601,7 +593,7 @@ namespace stream m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); if (!m_CurrentOutboundTunnel) { - LogPrint (eLogError, "No outbound tunnels in the pool"); + LogPrint (eLogError, "Streaming: No outbound tunnels in the pool"); return; } @@ -625,7 +617,7 @@ namespace stream m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs); } else - LogPrint (eLogWarning, "All leases are expired"); + LogPrint (eLogWarning, "Streaming: All leases are expired"); } @@ -644,7 +636,7 @@ namespace stream // check for resend attempts if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS) { - LogPrint (eLogWarning, "Stream packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts. Terminate"); + LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate"); m_Status = eStreamStatusReset; Close (); return; @@ -678,12 +670,12 @@ namespace stream // no break here case 4: UpdateCurrentRemoteLease (); // pick another lease - LogPrint (eLogWarning, "Another remote lease has been selected for stream"); + LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream"); break; case 3: // pick another outbound tunnel m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); - LogPrint (eLogWarning, "Another outbound tunnel has been selected for stream"); + LogPrint (eLogWarning, "Streaming: Another outbound tunnel has been selected for stream"); break; default: ; } @@ -699,7 +691,7 @@ namespace stream { if (m_LastReceivedSequenceNumber < 0) { - LogPrint (eLogWarning, "SYN has not been recived after ", ACK_SEND_TIMEOUT, " milliseconds after follow on. Terminate"); + LogPrint (eLogWarning, "Streaming: SYN has not been recived after ", ACK_SEND_TIMEOUT, " milliseconds after follow on, terminate"); m_Status = eStreamStatusReset; Close (); return; @@ -716,7 +708,7 @@ namespace stream { m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); if (!m_RemoteLeaseSet) - LogPrint ("LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); + LogPrint (eLogError, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); } if (m_RemoteLeaseSet) { @@ -819,7 +811,7 @@ namespace stream it->second->HandleNextPacket (packet); else { - LogPrint ("Unknown stream sendStreamID=", sendStreamID); + LogPrint (eLogError, "Streaming: Unknown stream sendStreamID=", sendStreamID); delete packet; } } @@ -833,7 +825,7 @@ namespace stream m_Acceptor (incomingStream); else { - LogPrint (eLogInfo, "Acceptor for incoming stream is not set"); + LogPrint (eLogWarning, "Streaming: Acceptor for incoming stream is not set"); if (m_PendingIncomingStreams.size () < MAX_PENDING_INCOMING_BACKLOG) { m_PendingIncomingStreams.push_back (incomingStream); @@ -841,11 +833,11 @@ namespace stream m_PendingIncomingTimer.expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT)); m_PendingIncomingTimer.async_wait (std::bind (&StreamingDestination::HandlePendingIncomingTimer, this, std::placeholders::_1)); - LogPrint (eLogInfo, "Pending incoming stream added"); + LogPrint (eLogDebug, "Streaming: Pending incoming stream added"); } else { - LogPrint (eLogError, "Pending incoming streams backlog exceeds ", MAX_PENDING_INCOMING_BACKLOG); + LogPrint (eLogWarning, "Streaming: Pending incoming streams backlog exceeds ", MAX_PENDING_INCOMING_BACKLOG); incomingStream->Close (); } } @@ -861,7 +853,7 @@ namespace stream return; } // TODO: should queue it up - LogPrint ("Unknown stream receiveStreamID=", receiveStreamID); + LogPrint (eLogError, "Streaming: Unknown stream receiveStreamID=", receiveStreamID); delete packet; } } @@ -917,7 +909,7 @@ namespace stream { if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogInfo, "Pending incoming timeout expired"); + LogPrint (eLogWarning, "Streaming: Pending incoming timeout expired"); for (auto it: m_PendingIncomingStreams) it->Close (); m_PendingIncomingStreams.clear (); From 01a502339c56792df2d2547f2d6900737045b20d Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 13:37:31 +0000 Subject: [PATCH 0687/6300] * sane log messages: api.cpp --- api.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/api.cpp b/api.cpp index eb64309a..5858dc6d 100644 --- a/api.cpp +++ b/api.cpp @@ -27,23 +27,23 @@ namespace api StartLog (logStream); else StartLog (i2p::util::filesystem::GetFullPath (i2p::util::filesystem::GetAppName () + ".log")); + LogPrint(eLogInfo, "API: starting NetDB"); i2p::data::netdb.Start(); - LogPrint("NetDB started"); + LogPrint(eLogInfo, "API: starting Transports"); i2p::transport::transports.Start(); - LogPrint("Transports started"); + LogPrint(eLogInfo, "API: starting Tunnels"); i2p::tunnel::tunnels.Start(); - LogPrint("Tunnels started"); } void StopI2P () { - LogPrint("Shutdown started."); + LogPrint(eLogInfo, "API: shutting down"); + LogPrint(eLogInfo, "API: stopping Tunnels"); i2p::tunnel::tunnels.Stop(); - LogPrint("Tunnels stopped"); + LogPrint(eLogInfo, "API: stopping Transports"); i2p::transport::transports.Stop(); - LogPrint("Transports stopped"); + LogPrint(eLogInfo, "API: stopping NetDB"); i2p::data::netdb.Stop(); - LogPrint("NetDB stopped"); StopLog (); } From ce4ed190292a94585561f7b4f175c226344df78a Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 13:46:56 +0000 Subject: [PATCH 0688/6300] * sane log messages: SSU.cpp --- SSU.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 64045ae4..699f3e94 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -86,7 +86,7 @@ namespace transport } catch (std::exception& ex) { - LogPrint (eLogError, "SSU server: ", ex.what ()); + LogPrint (eLogError, "SSU: server runtime exception: ", ex.what ()); } } } @@ -101,7 +101,7 @@ namespace transport } catch (std::exception& ex) { - LogPrint (eLogError, "SSU V6 server: ", ex.what ()); + LogPrint (eLogError, "SSU: v6 server runtime exception: ", ex.what ()); } } } @@ -116,7 +116,7 @@ namespace transport } catch (std::exception& ex) { - LogPrint (eLogError, "SSU receivers: ", ex.what ()); + LogPrint (eLogError, "SSU: receivers runtime exception: ", ex.what ()); } } } @@ -179,7 +179,7 @@ namespace transport } else { - LogPrint ("SSU receive error: ", ecode.message ()); + LogPrint (eLogError, "SSU: receive error: ", ecode.message ()); delete packet; } } @@ -206,7 +206,7 @@ namespace transport } else { - LogPrint ("SSU V6 receive error: ", ecode.message ()); + LogPrint (eLogError, "SSU: v6 receive error: ", ecode.message ()); delete packet; } } @@ -277,7 +277,7 @@ namespace transport if (address) CreateSession (router, address->host, address->port, peerTest); else - LogPrint (eLogWarning, "Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); + LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); } void SSUServer::CreateSession (std::shared_ptr router, @@ -312,7 +312,7 @@ namespace transport auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); sessions[remoteEndpoint] = session; // connect - LogPrint ("Creating new SSU session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", + LogPrint (eLogInfo, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); session->Connect (); } @@ -359,15 +359,15 @@ namespace transport } if (!introducer) { - LogPrint (eLogWarning, "Can't connect to unreachable router. No ipv4 introducers presented"); + LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no ipv4 introducers present"); return; } if (introducerSession) // session found - LogPrint (eLogInfo, "Session to introducer already exists"); + LogPrint (eLogInfo, "SSU: Session to introducer already exists"); else // create new { - LogPrint (eLogInfo, "Creating new session to introducer"); + LogPrint (eLogInfo, "SSU: Creating new session to introducer"); boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); introducerSession = std::make_shared (*this, introducerEndpoint, router); m_Sessions[introducerEndpoint] = introducerSession; @@ -376,7 +376,7 @@ namespace transport auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); m_Sessions[remoteEndpoint] = session; // introduce - LogPrint ("Introduce new SSU session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), + LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] through introducer ", introducer->iHost, ":", introducer->iPort); session->WaitForIntroduction (); if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable @@ -387,10 +387,10 @@ namespace transport introducerSession->Introduce (*introducer, router); } else - LogPrint (eLogWarning, "Can't connect to unreachable router. No introducers presented"); + LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present"); } else - LogPrint (eLogWarning, "Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); + LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); } } @@ -595,7 +595,7 @@ namespace transport it++; } if (numDeleted > 0) - LogPrint (eLogInfo, numDeleted, " peer tests have been expired"); + LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired"); SchedulePeerTestsCleanupTimer (); } } From 3b5d9d6cee3b00d02124e7babd3716fe1ec62ed3 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 13:49:57 +0000 Subject: [PATCH 0689/6300] * sane log messages: RouterContext.cpp --- RouterContext.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index 6629f774..4cdb9e63 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -255,8 +255,11 @@ namespace i2p auto mtu = i2p::util::net::GetMTU (host); if (mtu) { - LogPrint ("Our v6 MTU=", mtu); - if (mtu > 1472) mtu = 1472; + LogPrint (eLogDebug, "Router: Our v6 MTU=", mtu); + if (mtu > 1472) { // TODO: magic constant + mtu = 1472; + LogPrint(eLogWarning, "Router: MTU dropped to upper limit of 1472 bytes"); + } } m_RouterInfo.AddSSUAddress (host.to_string ().c_str (), port, GetIdentHash (), mtu ? mtu : 1472); // TODO updated = true; From 89e3178ea3dcc0cbce67c5b31c6eae82cbd312a7 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 13:53:28 +0000 Subject: [PATCH 0690/6300] * sane log messages: HTTPServer.cpp --- HTTPServer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index f4cd7b82..82589765 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -597,7 +597,7 @@ namespace util { end = str.find ('&', pos); std::string param = str.substr (pos, end - pos); - LogPrint (param); + LogPrint (eLogDebug, "HTTPServer: extracted parameters: ", param); size_t e = param.find ('='); if (e != std::string::npos) params[param.substr(0, e)] = param.substr(e+1); @@ -968,7 +968,7 @@ namespace util void HTTPConnection::HandleDestinationRequest (const std::string& address, const std::string& uri) { std::string request = "GET " + uri + " HTTP/1.1\r\nHost:" + address + "\r\n\r\n"; - LogPrint("HTTP Client Request: ", request); + LogPrint(eLogDebug, "HTTPServer: client request: ", request); SendToAddress (address, 80, request.c_str (), request.size ()); } @@ -977,7 +977,7 @@ namespace util i2p::data::IdentHash destination; if (!i2p::client::context.GetAddressBook ().GetIdentHash (address, destination)) { - LogPrint ("Unknown address ", address); + LogPrint (eLogWarning, "HTTPServer: Unknown address ", address); SendReply ("" + itoopieImage + "
Unknown address " + address + "", 404); return; } From 1cb0826de06cf030b7b61adb476b1a5577298c06 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 14:06:18 +0000 Subject: [PATCH 0691/6300] * sane log messages: SSUData.cpp --- SSUData.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/SSUData.cpp b/SSUData.cpp index 1106cf89..33c85d71 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -14,7 +14,7 @@ namespace transport { if (msg->len + fragmentSize > msg->maxLen) { - LogPrint (eLogInfo, "SSU I2NP message size ", msg->maxLen, " is not enough"); + LogPrint (eLogWarning, "SSU: I2NP message size ", msg->maxLen, " is not enough"); auto newMsg = NewI2NPMessage (); *newMsg = *msg; msg = newMsg; @@ -64,11 +64,11 @@ namespace transport m_PacketSize >>= 4; m_PacketSize <<= 4; if (m_PacketSize > m_MaxPacketSize) m_PacketSize = m_MaxPacketSize; - LogPrint ("MTU=", ssuAddress->mtu, " packet size=", m_PacketSize); + LogPrint (eLogDebug, "SSU: MTU=", ssuAddress->mtu, " packet size=", m_PacketSize); } else { - LogPrint (eLogWarning, "Unexpected MTU ", ssuAddress->mtu); + LogPrint (eLogWarning, "SSU: Unexpected MTU ", ssuAddress->mtu); m_PacketSize = m_MaxPacketSize; } } @@ -162,7 +162,7 @@ namespace transport uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17 if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE) { - LogPrint (eLogError, "Fragment size ", fragmentSize, "exceeds max SSU packet size"); + LogPrint (eLogError, "SSU: Fragment size ", fragmentSize, " exceeds max SSU packet size"); return; } @@ -199,23 +199,23 @@ namespace transport break; } if (isLast) - LogPrint (eLogDebug, "Message ", msgID, " complete"); + LogPrint (eLogDebug, "SSU: Message ", msgID, " complete"); } } else { if (fragmentNum < incompleteMessage->nextFragmentNum) // duplicate fragment - LogPrint (eLogWarning, "Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ". Ignored"); + LogPrint (eLogWarning, "SSU: Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ", ignored"); else { // missing fragment - LogPrint (eLogWarning, "Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); + LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast); if (incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); else - LogPrint (eLogWarning, "Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); + LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); } isLast = false; } @@ -241,18 +241,18 @@ namespace transport m_Handler.PutNextMessage (msg); } else - LogPrint (eLogWarning, "SSU message ", msgID, " already received"); + LogPrint (eLogWarning, "SSU: Message ", msgID, " already received"); } else { // we expect DeliveryStatus if (msg->GetTypeID () == eI2NPDeliveryStatus) { - LogPrint ("SSU session established"); + LogPrint (eLogDebug, "SSU: session established"); m_Session.Established (); } else - LogPrint (eLogError, "SSU unexpected message ", (int)msg->GetTypeID ()); + LogPrint (eLogError, "SSU: unexpected message ", (int)msg->GetTypeID ()); } } else @@ -271,7 +271,7 @@ namespace transport //uint8_t * start = buf; uint8_t flag = *buf; buf++; - LogPrint (eLogDebug, "Process SSU data flags=", (int)flag, " len=", len); + LogPrint (eLogDebug, "SSU: Process data, flags=", (int)flag, ", len=", len); // process acks if presented if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED)) ProcessAcks (buf, flag); @@ -280,7 +280,7 @@ namespace transport { uint8_t extendedDataSize = *buf; buf++; // size - LogPrint (eLogDebug, "SSU extended data of ", extendedDataSize, " bytes presented"); + LogPrint (eLogDebug, "SSU: extended data of ", extendedDataSize, " bytes present"); buf += extendedDataSize; } // process data @@ -292,7 +292,7 @@ namespace transport uint32_t msgID = msg->ToSSU (); if (m_SentMessages.count (msgID) > 0) { - LogPrint (eLogWarning, "SSU message ", msgID, " already sent"); + LogPrint (eLogWarning, "SSU: message ", msgID, " already sent"); return; } if (m_SentMessages.empty ()) // schedule resend at first message only @@ -349,7 +349,7 @@ namespace transport } catch (boost::system::system_error& ec) { - LogPrint (eLogError, "Can't send SSU fragment ", ec.what ()); + LogPrint (eLogError, "SSU: Can't send data fragment ", ec.what ()); } if (!isLast) { @@ -383,7 +383,7 @@ namespace transport { if (fragmentNum > 64) { - LogPrint (eLogWarning, "Fragment number ", fragmentNum, " exceeds 64"); + LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); return; } uint8_t buf[64 + 18]; @@ -437,7 +437,7 @@ namespace transport } catch (boost::system::system_error& ec) { - LogPrint (eLogError, "Can't resend SSU fragment ", ec.what ()); + LogPrint (eLogError, "SSU: Can't resend data fragment ", ec.what ()); } } @@ -447,7 +447,7 @@ namespace transport } else { - LogPrint (eLogError, "SSU message has not been ACKed after ", MAX_NUM_RESENDS, " attempts. Deleted"); + LogPrint (eLogError, "SSU: message has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted"); it = m_SentMessages.erase (it); } } @@ -491,7 +491,7 @@ namespace transport { if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) { - LogPrint (eLogError, "SSU message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds. Deleted"); + LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); it = m_IncompleteMessages.erase (it); } else From 5266d4d79c30bdc9eca221c5ab02c26379860ded Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 14:07:50 +0000 Subject: [PATCH 0692/6300] * sane log messages: RouterInfo.cpp --- RouterInfo.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 82b44f23..e071320c 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -62,7 +62,7 @@ namespace data } else { - LogPrint (eLogError, "RouterInfo signature verification failed"); + LogPrint (eLogError, "RouterInfo: signature verification failed"); m_IsUnreachable = true; } m_RouterIdentity->DropVerifier (); @@ -83,7 +83,7 @@ namespace data m_BufferLen = s.tellg (); if (m_BufferLen < 40) { - LogPrint(eLogError, "File", m_FullPath, " is malformed"); + LogPrint(eLogError, "RouterInfo: File", m_FullPath, " is malformed"); return false; } s.seekg(0, std::ios::beg); @@ -93,7 +93,7 @@ namespace data } else { - LogPrint (eLogError, "Can't open file ", m_FullPath); + LogPrint (eLogError, "RouterInfo: Can't open file ", m_FullPath); return false; } return true; @@ -117,7 +117,7 @@ namespace data int l = m_BufferLen - m_RouterIdentity->GetSignatureLen (); if (!m_RouterIdentity->Verify ((uint8_t *)m_Buffer, l, (uint8_t *)m_Buffer + l)) { - LogPrint (eLogError, "RouterInfo signature verification failed"); + LogPrint (eLogError, "RouterInfo: signature verification failed"); m_IsUnreachable = true; } m_RouterIdentity->DropVerifier (); @@ -432,7 +432,7 @@ namespace data if (!m_Buffer) { if (LoadFile ()) - LogPrint ("Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file"); + LogPrint (eLogDebug, "RouterInfo: Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file"); } return m_Buffer; } @@ -463,10 +463,10 @@ namespace data if (f.is_open ()) f.write ((char *)m_Buffer, m_BufferLen); else - LogPrint(eLogError, "Can't save RouterInfo to ", fullPath); + LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath); } else - LogPrint (eLogError, "Can't save RouterInfo m_Buffer==NULL"); + LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL"); } size_t RouterInfo::ReadString (char * str, std::istream& s) From ca375314f01f3acaa2490074a4ce85e996c5b030 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 14:08:12 +0000 Subject: [PATCH 0693/6300] * sane log messages: Identity.cpp --- Identity.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index 78aeedd3..4b518b5b 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -101,7 +101,7 @@ namespace data break; } default: - LogPrint ("Signing key type ", (int)type, " is not supported"); + LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported"); } m_ExtendedLen = 4 + excessLen; // 4 bytes extra + excess length // fill certificate @@ -194,7 +194,7 @@ namespace data { if (len < DEFAULT_IDENTITY_SIZE) { - LogPrint (eLogError, "Identity buffer length ", len, " is too small"); + LogPrint (eLogError, "Identity: buffer length ", len, " is too small"); return 0; } memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE); @@ -210,7 +210,7 @@ namespace data } else { - LogPrint (eLogError, "Certificate length ", m_ExtendedLen, " exceeds buffer length ", len - DEFAULT_IDENTITY_SIZE); + LogPrint (eLogError, "Identity: Certificate length ", m_ExtendedLen, " exceeds buffer length ", len - DEFAULT_IDENTITY_SIZE); return 0; } } @@ -359,7 +359,7 @@ namespace data break; } default: - LogPrint ("Signing key type ", (int)keyType, " is not supported"); + LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported"); } } @@ -470,7 +470,7 @@ namespace data m_Signer.reset (new i2p::crypto::EDDSA25519Signer (m_SigningPrivateKey)); break; default: - LogPrint ("Signing key type ", (int)m_Public->GetSigningKeyType (), " is not supported"); + LogPrint (eLogError, "Identity: Signing key type ", (int)m_Public->GetSigningKeyType (), " is not supported"); } } @@ -505,7 +505,7 @@ namespace data i2p::crypto::CreateEDDSA25519RandomKeys (keys.m_SigningPrivateKey, signingPublicKey); break; default: - LogPrint ("Signing key type ", (int)type, " is not supported. Create DSA-SHA1"); + LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1"); return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1 } // encryption From d9af8c31a21939e328a8b2e7180e3a41512b8471 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 14:08:23 +0000 Subject: [PATCH 0694/6300] * sane log messages: LeaseSet.cpp --- LeaseSet.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index caeec262..fb6ac60b 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -32,7 +32,7 @@ namespace data m_Buffer = nullptr; m_BufferLen = 0; m_IsValid = false; - LogPrint (eLogError, "Destination for local LeaseSet doesn't exist"); + LogPrint (eLogError, "LeaseSet: Destination for local LeaseSet doesn't exist"); return; } m_Buffer = new uint8_t[MAX_LS_BUFFER_SIZE]; @@ -61,7 +61,7 @@ namespace data // signature localDestination->Sign (m_Buffer, m_BufferLen, m_Buffer + m_BufferLen); m_BufferLen += localDestination->GetIdentity ()->GetSignatureLen (); - LogPrint ("Local LeaseSet of ", tunnels.size (), " leases created"); + LogPrint (eLogDebug, "LeaseSet: Local LeaseSet of ", tunnels.size (), " leases created"); ReadFromBuffer (); } @@ -90,7 +90,7 @@ namespace data size += m_Identity->GetSigningPublicKeyLen (); // unused signing key uint8_t num = m_Buffer[size]; size++; // num - LogPrint ("LeaseSet num=", (int)num); + LogPrint (eLogDebug, "LeaseSet: read num=", (int)num); if (!num) m_IsValid = false; // process leases @@ -110,7 +110,7 @@ namespace data if (!netdb.FindRouter (lease.tunnelGateway)) { // if not found request it - LogPrint (eLogInfo, "Lease's tunnel gateway not found. Requested"); + LogPrint (eLogInfo, "LeaseSet: Lease's tunnel gateway not found, requesting"); netdb.RequestDestination (lease.tunnelGateway); } } @@ -118,7 +118,7 @@ namespace data // verify if (!m_Identity->Verify (m_Buffer, leases - m_Buffer, leases)) { - LogPrint (eLogWarning, "LeaseSet verification failed"); + LogPrint (eLogWarning, "LeaseSet: verification failed"); m_IsValid = false; } } From 6c0dfc435678ecbb151aabb38e61c606a0d62f81 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 14:08:35 +0000 Subject: [PATCH 0695/6300] * sane log messages: Log.cpp --- Log.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Log.cpp b/Log.cpp index a25b25e8..c20e0761 100644 --- a/Log.cpp +++ b/Log.cpp @@ -49,7 +49,7 @@ void Log::SetLogFile (const std::string& fullFilePath) if (logFile->is_open ()) { SetLogStream (logFile); - LogPrint("Logging to file ", fullFilePath, " enabled."); + LogPrint(eLogInfo, "Log: will send messages to ", fullFilePath); } else delete logFile; From 7936f8730fcb2be06befb6bd6b1695f36a96c74f Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 21 Dec 2015 02:20:16 +0000 Subject: [PATCH 0696/6300] * sane log messages: Reseed.cpp --- Reseed.cpp | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 0aac168f..36c16e18 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -54,7 +54,7 @@ namespace data int Reseeder::ReseedFromSU3 (const std::string& host, bool https) { std::string url = host + "i2pseeds.su3"; - LogPrint (eLogInfo, "Dowloading SU3 from ", host); + LogPrint (eLogInfo, "Reseed: Downloading SU3 from ", host); std::string su3 = https ? HttpsRequest (url) : i2p::util::http::httpRequest (url); if (su3.length () > 0) { @@ -63,7 +63,7 @@ namespace data } else { - LogPrint (eLogWarning, "SU3 download failed"); + LogPrint (eLogWarning, "Reseed: SU3 download failed"); return 0; } } @@ -75,7 +75,7 @@ namespace data return ProcessSU3Stream (s); else { - LogPrint (eLogError, "Can't open file ", filename); + LogPrint (eLogError, "Reseed: Can't open file ", filename); return 0; } } @@ -90,7 +90,7 @@ namespace data s.read (magicNumber, 7); // magic number and zero byte 6 if (strcmp (magicNumber, SU3_MAGIC_NUMBER)) { - LogPrint (eLogError, "Unexpected SU3 magic number"); + LogPrint (eLogError, "Reseed: Unexpected SU3 magic number"); return 0; } s.seekg (1, std::ios::cur); // su3 file format version @@ -114,7 +114,7 @@ namespace data s.read ((char *)&fileType, 1); // file type if (fileType != 0x00) // zip file { - LogPrint (eLogError, "Can't handle file type ", (int)fileType); + LogPrint (eLogError, "Reseed: Can't handle file type ", (int)fileType); return 0; } s.seekg (1, std::ios::cur); // unused @@ -122,7 +122,7 @@ namespace data s.read ((char *)&contentType, 1); // content type if (contentType != 0x03) // reseed data { - LogPrint (eLogError, "Unexpected content type ", (int)contentType); + LogPrint (eLogError, "Reseed: Unexpected content type ", (int)contentType); return 0; } s.seekg (12, std::ios::cur); // unused @@ -162,7 +162,7 @@ namespace data // digest is right aligned // we can't use RSA_verify due wrong padding in SU3 if (memcmp (enSigBuf + (signatureLength - 64), digest, 64)) - LogPrint (eLogWarning, "SU3 signature verification failed"); + LogPrint (eLogWarning, "Reseed: SU3 signature verification failed"); delete[] enSigBuf; BN_free (s); BN_free (n); BN_CTX_free (bnctx); @@ -173,10 +173,10 @@ namespace data s.seekg (pos, std::ios::beg); } else - LogPrint (eLogWarning, "Signature type ", signatureType, " is not supported"); + LogPrint (eLogWarning, "Reseed: Signature type ", signatureType, " is not supported"); } else - LogPrint (eLogWarning, "Certificate for ", signerID, " not loaded"); + LogPrint (eLogWarning, "Reseed: Certificate for ", signerID, " not loaded"); // handle content int numFiles = 0; @@ -220,7 +220,7 @@ namespace data size_t pos = s.tellg (); if (!FindZipDataDescriptor (s)) { - LogPrint (eLogError, "SU3 archive data descriptor not found"); + LogPrint (eLogError, "Reseed: SU3 archive data descriptor not found"); return numFiles; } s.read ((char *)&crc_32, 4); @@ -234,10 +234,10 @@ namespace data s.seekg (pos, std::ios::beg); // back to compressed data } - LogPrint (eLogDebug, "Proccessing file ", localFileName, " ", compressedSize, " bytes"); + LogPrint (eLogDebug, "Reseed: Proccessing file ", localFileName, " ", compressedSize, " bytes"); if (!compressedSize) { - LogPrint (eLogWarning, "Unexpected size 0. Skipped"); + LogPrint (eLogWarning, "Reseed: Unexpected size 0. Skipped"); continue; } @@ -263,10 +263,10 @@ namespace data numFiles++; } else - LogPrint (eLogError, "CRC32 verification failed"); + LogPrint (eLogError, "Reseed: CRC32 verification failed"); } else - LogPrint (eLogError, "decompression error ", err); + LogPrint (eLogError, "Reseed: SU3 decompression error ", err); delete[] uncompressed; inflateEnd (&inflator); } @@ -282,7 +282,7 @@ namespace data else { if (signature != ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE) - LogPrint (eLogWarning, "Missing zip central directory header"); + LogPrint (eLogWarning, "Reseed: Missing zip central directory header"); break; // no more files } size_t end = s.tellg (); @@ -335,7 +335,7 @@ namespace data SSL_free (ssl); } else - LogPrint (eLogError, "Can't open certificate file ", filename); + LogPrint (eLogError, "Reseed: Can't open certificate file ", filename); SSL_CTX_free (ctx); } @@ -345,7 +345,7 @@ namespace data if (!boost::filesystem::exists (reseedDir)) { - LogPrint (eLogWarning, "Reseed certificates not loaded. ", reseedDir, " doesn't exist"); + LogPrint (eLogWarning, "Reseed: certificates not loaded, ", reseedDir, " doesn't exist"); return; } @@ -359,7 +359,7 @@ namespace data numCertificates++; } } - LogPrint (eLogInfo, numCertificates, " certificates loaded"); + LogPrint (eLogInfo, "Reseed: ", numCertificates, " certificates loaded"); } std::string Reseeder::HttpsRequest (const std::string& address) @@ -382,7 +382,7 @@ namespace data s.handshake (boost::asio::ssl::stream_base::client, ecode); if (!ecode) { - LogPrint (eLogInfo, "Connected to ", u.host_, ":", u.port_); + 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_ @@ -401,13 +401,13 @@ namespace data return i2p::util::http::GetHttpContent (rs); } else - LogPrint (eLogError, "SSL handshake failed: ", ecode.message ()); + LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ()); } else - LogPrint (eLogError, "Couldn't connect to ", u.host_, ": ", ecode.message ()); + LogPrint (eLogError, "Reseed: Couldn't connect to ", u.host_, ": ", ecode.message ()); } else - LogPrint (eLogError, "Couldn't resolve address ", u.host_, ": ", ecode.message ()); + LogPrint (eLogError, "Reseed: Couldn't resolve address ", u.host_, ": ", ecode.message ()); return ""; } } From d09fedf2084563c3825b329e3babf633535ef411 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 21 Dec 2015 02:20:36 +0000 Subject: [PATCH 0697/6300] * sane log messages: TransitTunnel.cpp --- TransitTunnel.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index beb09da7..81773bb5 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -45,7 +45,7 @@ namespace tunnel { auto num = m_TunnelDataMsgs.size (); if (num > 1) - LogPrint (eLogDebug, "TransitTunnel: ",GetTunnelID (),"->", GetNextTunnelID (), " ", num); + LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num); i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); m_TunnelDataMsgs.clear (); } @@ -53,12 +53,12 @@ namespace tunnel void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) { - LogPrint (eLogError, "We are not a gateway for transit tunnel ", GetTunnelID ()); + LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); } void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { - LogPrint (eLogError, "Incoming tunnel message is not supported ", GetTunnelID ()); + LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); } void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr msg) @@ -81,7 +81,7 @@ namespace tunnel auto newMsg = CreateEmptyTunnelDataMsg (); EncryptTunnelMsg (tunnelMsg, newMsg); - LogPrint (eLogDebug, "TransitTunnel endpoint for ", GetTunnelID ()); + LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } @@ -92,12 +92,12 @@ namespace tunnel { if (isEndpoint) { - LogPrint (eLogInfo, "TransitTunnel endpoint: ", receiveTunnelID, " created"); + LogPrint (eLogInfo, "TransitTunnel: endpoint ", receiveTunnelID, " created"); return new TransitTunnelEndpoint (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else if (isGateway) { - LogPrint (eLogInfo, "TransitTunnel gateway: ", receiveTunnelID, " created"); + LogPrint (eLogInfo, "TransitTunnel: gateway ", receiveTunnelID, " created"); return new TransitTunnelGateway (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else From 364ccc05d5267dd5552da907b7faf13cfe79aea9 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 18 Dec 2015 14:30:54 +0000 Subject: [PATCH 0698/6300] * Log.h: drop unused template --- Log.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Log.h b/Log.h index 8728149f..09e2156a 100644 --- a/Log.h +++ b/Log.h @@ -120,10 +120,4 @@ void LogPrint (LogLevel level, TArgs... args) } } -template -void LogPrint (TArgs... args) -{ - LogPrint (eLogInfo, args...); -} - #endif From 3badda95c1c4d333b0a631b395e0fe632f13f566 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 21 Dec 2015 03:46:35 +0000 Subject: [PATCH 0699/6300] * reseed now https only --- Reseed.cpp | 2 +- util.cpp | 96 ------------------------------------------------------ util.h | 2 -- 3 files changed, 1 insertion(+), 99 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 0aac168f..e787ba71 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -55,7 +55,7 @@ namespace data { std::string url = host + "i2pseeds.su3"; LogPrint (eLogInfo, "Dowloading SU3 from ", host); - std::string su3 = https ? HttpsRequest (url) : i2p::util::http::httpRequest (url); + std::string su3 = HttpsRequest (url); if (su3.length () > 0) { std::stringstream s(su3); diff --git a/util.cpp b/util.cpp index 6d2ee500..7c433cff 100644 --- a/util.cpp +++ b/util.cpp @@ -265,43 +265,6 @@ namespace filesystem namespace http { - std::string httpRequest(const std::string& address) - { - try - { - i2p::util::http::url u(address); - boost::asio::ip::tcp::iostream site; - // please don't uncomment following line because it's not compatible with boost 1.46 - // 1.46 is default boost for Ubuntu 12.04 LTS - //site.expires_from_now (boost::posix_time::seconds(30)); - if (u.port_ == 80) - site.connect(u.host_, "http"); - else - { - std::stringstream ss; ss << u.port_; - site.connect(u.host_, ss.str()); - } - if (site) - { - // User-Agent is needed to get the server list routerInfo files. - site << "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"; - // read response and extract content - return GetHttpContent (site); - } - else - { - LogPrint ("Can't connect to ", address); - return ""; - } - } - catch (std::exception& ex) - { - LogPrint ("Failed to download ", address, " : ", ex.what ()); - return ""; - } - } - std::string GetHttpContent (std::istream& response) { std::string version, statusMessage; @@ -357,65 +320,6 @@ namespace http } } - int httpRequestViaI2pProxy(const std::string& address, std::string &content) - { - content = ""; - try - { - boost::asio::ip::tcp::iostream site; - // please don't uncomment following line because it's not compatible with boost 1.46 - // 1.46 is default boost for Ubuntu 12.04 LTS - //site.expires_from_now (boost::posix_time::seconds(30)); - { - std::stringstream ss; ss << i2p::util::config::GetArg("-httpproxyport", 4446); - site.connect("127.0.0.1", ss.str()); - } - if (site) - { - i2p::util::http::url u(address); - std::stringstream ss; - ss << "GET " << address << " HTTP/1.0" << std::endl; - ss << "Host: " << u.host_ << std::endl; - ss << "Accept: */*" << std::endl; - ss << "User - Agent: Wget / 1.11.4" << std::endl; - ss << "Connection: close" << std::endl; - ss << std::endl; - site << ss.str(); - - // read response - std::string version, statusMessage; - site >> version; // HTTP version - int status; - site >> status; // status - std::getline(site, statusMessage); - if (status == 200) // OK - { - std::string header; - while (std::getline(site, header) && header != "\r"){} - std::stringstream ss; - ss << site.rdbuf(); - content = ss.str(); - return status; - } - else - { - LogPrint("HTTP response ", status); - return status; - } - } - else - { - LogPrint("Can't connect to proxy"); - return 408; - } - } - catch (std::exception& ex) - { - LogPrint("Failed to download ", address, " : ", ex.what()); - return 408; - } - } - url::url(const std::string& url_s) { portstr_ = "80"; diff --git a/util.h b/util.h index 1a579283..8c72a97b 100644 --- a/util.h +++ b/util.h @@ -47,10 +47,8 @@ namespace util const char LAST_MODIFIED[] = "Last-Modified"; const char TRANSFER_ENCODING[] = "Transfer-Encoding"; - std::string httpRequest(const std::string& address); std::string GetHttpContent (std::istream& response); void MergeChunkedResponse (std::istream& response, std::ostream& merged); - int httpRequestViaI2pProxy(const std::string& address, std::string &content); // return http code std::string urlDecode(const std::string& data); struct url { From f48a98f691588b82866faf6abb6f17d9f70655af Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 19 Dec 2015 04:33:57 +0000 Subject: [PATCH 0700/6300] * disable AESNI by default for .deb package (#312) --- debian/patches/0001-disable-aesni-by-default.patch | 13 +++++++++++++ debian/patches/series | 1 + 2 files changed, 14 insertions(+) create mode 100644 debian/patches/0001-disable-aesni-by-default.patch create mode 100644 debian/patches/series diff --git a/debian/patches/0001-disable-aesni-by-default.patch b/debian/patches/0001-disable-aesni-by-default.patch new file mode 100644 index 00000000..eae44c8b --- /dev/null +++ b/debian/patches/0001-disable-aesni-by-default.patch @@ -0,0 +1,13 @@ +diff --git a/Makefile b/Makefile +index 2e86fd8..c1037af 100644 +--- a/Makefile ++++ b/Makefile +@@ -9,7 +9,7 @@ DEPS := obj/make.dep + + include filelist.mk + +-USE_AESNI := yes ++USE_AESNI := no + USE_STATIC := no + + ifeq ($(UNAME),Darwin) diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 00000000..1c9d0fbf --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +0001-disable-aesni-by-default.patch From 2635a658d084800880716a1b13a47cf7bfabb37b Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Sun, 20 Dec 2015 22:57:55 -0600 Subject: [PATCH 0701/6300] Fix missing cached openssl in appveyor --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index df6264bc..e190b949 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -135,9 +135,9 @@ install: && perl Configure %openssl_target% no-rc2 no-rc4 no-rc5 no-idea no-bf no-cast no-whirlpool no-md2 no-md4 no-ripemd no-mdc2 no-camellia no-seed no-comp no-krb5 no-gmp no-rfc3779 no-ec2m no-ssl2 no-jpake no-srp no-sctp no-srtp --prefix=c:\stage\OpenSSL-Win%bitness%-vc%msvc%-%type% > c:\projects\instdir\build_openssl.log && ( if "%x64%" == "1" ( ms\do_win64a >> c:\projects\instdir\build_openssl.log ) else ( ms\do_nasm >> c:\projects\instdir\build_openssl.log ) ) && nmake -f ms\nt%dll%.mak install >> c:\projects\instdir\build_openssl.log 2>&1 - && mklink /J \OpenSSL \stage\OpenSSL-Win%bitness%-vc%msvc%-%type% || type c:\projects\instdir\build_openssl.log ) +- mklink /J \OpenSSL \stage\OpenSSL-Win%bitness%-vc%msvc%-%type% - rem already there: mingw-w64-i686-openssl mingw-w64-i686-gcc cmake - if not defined msvc ( C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -Sy bash pacman pacman-mirrors msys2-runtime msys2-runtime-devel" From 06e45bff24e25e9f904daa2142592927b18a165f Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 21 Dec 2015 09:33:09 -0500 Subject: [PATCH 0702/6300] removed unused parameter --- Reseed.cpp | 4 ++-- Reseed.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index e787ba71..4d363460 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -48,10 +48,10 @@ namespace data { auto ind = rand () % httpsReseedHostList.size (); std::string& reseedHost = httpsReseedHostList[ind]; - return ReseedFromSU3 (reseedHost, true); + return ReseedFromSU3 (reseedHost); } - int Reseeder::ReseedFromSU3 (const std::string& host, bool https) + int Reseeder::ReseedFromSU3 (const std::string& host) { std::string url = host + "i2pseeds.su3"; LogPrint (eLogInfo, "Dowloading SU3 from ", host); diff --git a/Reseed.h b/Reseed.h index aec8389c..560a31d3 100644 --- a/Reseed.h +++ b/Reseed.h @@ -29,7 +29,7 @@ namespace data void LoadCertificate (const std::string& filename); - int ReseedFromSU3 (const std::string& host, bool https = false); + int ReseedFromSU3 (const std::string& host); int ProcessSU3File (const char * filename); int ProcessSU3Stream (std::istream& s); From 45c8858140460d4eea32ea88575837367d33b65e Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 21 Dec 2015 10:17:00 -0500 Subject: [PATCH 0703/6300] persist temporary keys --- Destination.cpp | 33 ++++++++++++++++++++++++++++++++- Destination.h | 3 ++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 67042276..a29f3a78 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -19,7 +19,10 @@ namespace client m_Keys (keys), m_IsPublic (isPublic), m_PublishReplyToken (0), m_DatagramDestination (nullptr), m_PublishConfirmationTimer (m_Service), m_CleanupTimer (m_Service) { - i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); + if (m_IsPublic) + PersistTemporaryKeys (); + else + i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); int inboundTunnelLen = DEFAULT_INBOUND_TUNNEL_LENGTH; int outboundTunnelLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH; int inboundTunnelsQuantity = DEFAULT_INBOUND_TUNNELS_QUANTITY; @@ -670,5 +673,33 @@ namespace client it++; } } + + void ClientDestination::PersistTemporaryKeys () + { + auto path = i2p::util::filesystem::GetDefaultDataDir() / "destinations"; + auto filename = path / (GetIdentHash ().ToBase32 () + ".dat"); + std::ifstream f(filename.string (), std::ifstream::binary); + if (f) + { + f.read ((char *)m_EncryptionPublicKey, 256); + f.read ((char *)m_EncryptionPrivateKey, 256); + } + if (!f) + { + LogPrint (eLogInfo, "Creating new temporary keys for address ", GetIdentHash ().ToBase32 ()); + i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); + if (!boost::filesystem::exists (path)) + { + if (!boost::filesystem::create_directory (path)) + LogPrint (eLogError, "Failed to create destinations directory"); + } + std::ofstream f1 (filename.string (), std::ofstream::binary | std::ofstream::out); + if (f1) + { + f1.write ((char *)m_EncryptionPublicKey, 256); + f1.write ((char *)m_EncryptionPrivateKey, 256); + } + } + } } } diff --git a/Destination.h b/Destination.h index f75e3fc0..3de47a81 100644 --- a/Destination.h +++ b/Destination.h @@ -123,7 +123,8 @@ namespace client void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleCleanupTimer (const boost::system::error_code& ecode); void CleanupRemoteLeaseSets (); - + void PersistTemporaryKeys (); + private: volatile bool m_IsRunning; From ca315c51a0de0160db69df5bb89db8e9cb770c35 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 21 Dec 2015 20:49:27 -0500 Subject: [PATCH 0704/6300] version 2.2.0 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index eedde45c..c597c65b 100644 --- a/version.h +++ b/version.h @@ -7,7 +7,7 @@ #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 1 +#define I2PD_VERSION_MINOR 2 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) From aecac0ef852ef8451911eec33896c80ea1282a17 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 23 Dec 2015 01:32:01 +0000 Subject: [PATCH 0705/6300] * bump version in debian/changelog --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 0b1e1871..52bca793 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.2.0-2) unstable; urgency=low + + * updated to version 2.2.0 + + -- hagen Wed, 23 Dec 2015 01:29:40 +0000 + i2pd (2.1.0-1) unstable; urgency=low * updated to version 2.1.0/0.9.23 From f9c592ca2277cd45ef74507c53be5a6a13bb0f26 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 23 Dec 2015 10:24:10 -0500 Subject: [PATCH 0706/6300] static link against libgcc, libstdc++ and libwinpthread --- Makefile.mingw | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.mingw b/Makefile.mingw index 33cefe5e..c2fd35a9 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,7 +1,7 @@ CXX = g++ CXXFLAGS = -O2 -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN -NEEDED_CXXFLAGS = -std=c++11 +NEEDED_CXXFLAGS = -std=c++11 BOOST_SUFFIX = -mgw48-mt-1_59 INCFLAGS = -I/usr/include/ -I/usr/local/include/ -I/c/dev/openssl/include -I/c/dev/boost/include/boost-1_59 LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/c/dev/openssl -L/c/dev/boost/lib -LDLIBS = -lboost_system$(BOOST_SUFFIX) -lboost_date_time$(BOOST_SUFFIX) -lboost_filesystem$(BOOST_SUFFIX) -lboost_regex$(BOOST_SUFFIX) -lboost_program_options$(BOOST_SUFFIX) -lssl -lcrypto -lz -lpthread -lwsock32 -lws2_32 -lgdi32 -liphlpapi +LDLIBS = -lboost_system$(BOOST_SUFFIX) -lboost_date_time$(BOOST_SUFFIX) -lboost_filesystem$(BOOST_SUFFIX) -lboost_regex$(BOOST_SUFFIX) -lboost_program_options$(BOOST_SUFFIX) -lssl -lcrypto -lz -lwsock32 -lws2_32 -lgdi32 -liphlpapi -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -lpthread From 7ea3a87bfc66fc4024d8a4c9ed6361fd0ec6658e Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 24 Dec 2015 00:55:53 +0000 Subject: [PATCH 0707/6300] =?UTF-8?q?*=20missing=20initializer=20for=20mem?= =?UTF-8?q?ber=20=E2=80=98i2p::transport::Peer::delayedMessages=E2=80=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Transports.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 72f95fd8..25f4f85a 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -231,7 +231,7 @@ namespace transport { auto r = netdb.FindRouter (ident); it = m_Peers.insert (std::pair(ident, { 0, r, {}, - i2p::util::GetSecondsSinceEpoch () })).first; + i2p::util::GetSecondsSinceEpoch (), {} })).first; connected = ConnectToPeer (ident, it->second); } catch (std::exception& ex) @@ -501,7 +501,7 @@ namespace transport it->second.delayedMessages.clear (); } else // incoming connection - m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch () })); + m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} })); }); } From 16596c18fbe28c92c5973bc1a6ceb7b5f2cfac34 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 23 Dec 2015 20:47:44 -0500 Subject: [PATCH 0708/6300] log max packet size --- Streaming.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Streaming.cpp b/Streaming.cpp index 3af8b184..65148127 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -187,6 +187,7 @@ namespace stream if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED) { uint16_t maxPacketSize = bufbe16toh (optionData); + LogPrint (eLogDebug, "Streaming: Max packet size ", maxPacketSize); optionData += 2; } From 196d7e8f7248e7d0fd0e881dfe6c1fe84b5175a7 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 26 Dec 2015 09:41:12 -0500 Subject: [PATCH 0709/6300] send correct RouterInfo statistics --- I2PControl.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 4760caec..a706cd79 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -398,11 +398,13 @@ namespace client LogPrint (eLogDebug, "I2PControl RouterInfo"); for (auto it = params.begin (); it != params.end (); it++) { - if (it != params.begin ()) results << ","; LogPrint (eLogDebug, it->first); auto it1 = m_RouterInfoHandlers.find (it->first); if (it1 != m_RouterInfoHandlers.end ()) + { + if (it != params.begin ()) results << ","; (this->*(it1->second))(results); + } else LogPrint (eLogError, "I2PControl RouterInfo unknown request ", it->first); } From f3bee5ff3f2049ef1d44ef674ecdf765a191ff6b Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 28 Dec 2015 00:00:00 +0000 Subject: [PATCH 0710/6300] * log message fix --- I2NPProtocol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 63c8ea9c..cfa1bd15 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -516,7 +516,7 @@ namespace i2p if (msg) { uint8_t typeID = msg->GetTypeID (); - LogPrint (eLogDebug, "I2NP: Got message with type ", (int)typeID); + LogPrint (eLogDebug, "I2NP: Handling message with type ", (int)typeID); switch (typeID) { case eI2NPTunnelData: From 1b2c88fe380157b617a3caea65674da31eaa7ee6 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 28 Dec 2015 00:00:00 +0000 Subject: [PATCH 0711/6300] * drop i2p::util::config::GetCharArg --- Daemon.cpp | 4 ++-- RouterContext.cpp | 4 ++-- util.cpp | 7 ------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 4991ab07..3fdc8a48 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -74,8 +74,8 @@ namespace i2p int port = i2p::util::config::GetArg("-port", 0); if (port) i2p::context.UpdatePort (port); - const char * host = i2p::util::config::GetCharArg("-host", ""); - if (host && host[0]) + std::string host = i2p::util::config::GetArg("-host", ""); + if (host != "") i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); i2p::context.SetSupportsV6 (i2p::util::config::GetArg("-v6", 0)); diff --git a/RouterContext.cpp b/RouterContext.cpp index 4cdb9e63..31038820 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -46,8 +46,8 @@ namespace i2p int port = i2p::util::config::GetArg("-port", 0); if (!port) port = rand () % (30777 - 9111) + 9111; // I2P network ports range - routerInfo.AddSSUAddress (i2p::util::config::GetCharArg("-host", "127.0.0.1"), port, routerInfo.GetIdentHash ()); - routerInfo.AddNTCPAddress (i2p::util::config::GetCharArg("-host", "127.0.0.1"), port); + routerInfo.AddSSUAddress (i2p::util::config::GetArg("-host", "127.0.0.1").c_str (), port, routerInfo.GetIdentHash ()); + routerInfo.AddNTCPAddress (i2p::util::config::GetArg("-host", "127.0.0.1").c_str (), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC routerInfo.SetProperty ("coreVersion", I2P_VERSION); diff --git a/util.cpp b/util.cpp index 20f376da..53762580 100644 --- a/util.cpp +++ b/util.cpp @@ -114,13 +114,6 @@ namespace config } } - const char* GetCharArg(const std::string& strArg, const std::string& nDefault) - { - if (mapArgs.count(strArg)) - return mapArgs[strArg].c_str(); - return nDefault.c_str(); - } - std::string GetArg(const std::string& strArg, const std::string& strDefault) { if (mapArgs.count(strArg)) From 8e867ab0c0a41914251b31c13b1a5ea4be1c871a Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 28 Dec 2015 00:00:00 +0000 Subject: [PATCH 0712/6300] * util.cpp : reorder defines --- util.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util.cpp b/util.cpp index 53762580..c8cdca1c 100644 --- a/util.cpp +++ b/util.cpp @@ -599,10 +599,10 @@ namespace net { const int fallback = 576; // fallback MTU -#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) - return GetMTUUnix(localAddress, fallback); -#elif defined(WIN32) +#ifdef WIN32 return GetMTUWindows(localAddress, fallback); +#else + return GetMTUUnix(localAddress, fallback); #endif return fallback; } From 2dae5bccb22a457374cbc51d34878369b1e28e0b Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 28 Dec 2015 00:00:00 +0000 Subject: [PATCH 0713/6300] * util.cpp : reorder defines --- util.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/util.cpp b/util.cpp index c8cdca1c..00616c55 100644 --- a/util.cpp +++ b/util.cpp @@ -16,10 +16,7 @@ #include "util.h" #include "Log.h" -#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) -#include -#include -#elif defined(WIN32) +#ifdef WIN32 #include #include #include @@ -30,7 +27,7 @@ #ifdef _MSC_VER #pragma comment(lib, "IPHLPAPI.lib") -#endif +#endif // _MSC_VER #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) #define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) @@ -60,6 +57,9 @@ http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found */ } return 0; } +#else /* !WIN32 => UNIX */ +#include +#include #endif namespace i2p @@ -229,7 +229,7 @@ namespace filesystem char localAppData[MAX_PATH]; SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); return boost::filesystem::path(std::string(localAppData) + "\\" + appName); -#else +#else /* UNIX */ if (i2p::util::config::GetArg("-service", 0)) // use system folder return boost::filesystem::path(std::string ("/var/lib/") + appName); boost::filesystem::path pathRet; @@ -243,11 +243,11 @@ namespace filesystem pathRet /= "Library/Application Support"; boost::filesystem::create_directory(pathRet); return pathRet / appName; -#else +#else /* Other Unix */ // Unix return pathRet / (std::string (".") + appName); #endif -#endif +#endif /* UNIX */ } boost::filesystem::path GetCertificatesDir() From a0e8fe5848a009d8d6775ab9a1fe503628934bba Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 28 Dec 2015 00:00:00 +0000 Subject: [PATCH 0714/6300] * implement --loglevel option --- Daemon.cpp | 5 +++-- Log.cpp | 13 +++++++++++++ Log.h | 10 +++++++--- docs/configuration.md | 1 + 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 3fdc8a48..e2d7d895 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -110,9 +110,10 @@ namespace i2p logfile_path.append("\\i2pd.log"); #endif StartLog (logfile_path); - } - else + } else { StartLog (""); // write to stdout + } + g_Log->SetLogLevel(i2p::util::config::GetArg("-loglevel", "info")); } LogPrint(eLogInfo, "Daemon: staring HTTP Server"); diff --git a/Log.cpp b/Log.cpp index c20e0761..4f31fb31 100644 --- a/Log.cpp +++ b/Log.cpp @@ -55,6 +55,19 @@ void Log::SetLogFile (const std::string& fullFilePath) delete logFile; } +void Log::SetLogLevel (const std::string& level) +{ + if (level == "error") { m_MinLevel = eLogError; } + else if (level == "warn") { m_MinLevel = eLogWarning; } + else if (level == "info") { m_MinLevel = eLogInfo; } + else if (level == "debug") { m_MinLevel = eLogDebug; } + else { + LogPrint(eLogError, "Log: Unknown loglevel: ", level); + return; + } + LogPrint(eLogInfo, "Log: min msg level set to ", level); +} + void Log::SetLogStream (std::ostream * logStream) { if (m_LogStream) delete m_LogStream; diff --git a/Log.h b/Log.h index 09e2156a..c4b0f276 100644 --- a/Log.h +++ b/Log.h @@ -38,9 +38,11 @@ class Log: public i2p::util::MsgQueue ~Log () { delete m_LogStream; }; void SetLogFile (const std::string& fullFilePath); + void SetLogLevel (const std::string& level); void SetLogStream (std::ostream * logStream); std::ostream * GetLogStream () const { return m_LogStream; }; const std::string& GetTimestamp (); + LogLevel GetLogLevel () { return m_MinLevel; }; private: @@ -49,6 +51,7 @@ class Log: public i2p::util::MsgQueue private: std::ostream * m_LogStream; + enum LogLevel m_MinLevel; std::string m_Timestamp; #if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) && !defined(__clang__) // gcc 4.6 std::chrono::monotonic_clock::time_point m_LastTimestampUpdate; @@ -108,13 +111,14 @@ void LogPrint (std::stringstream& s, TValue arg, TArgs... args) template void LogPrint (LogLevel level, TArgs... args) { + if (g_Log && level > g_Log->GetLogLevel ()) + return; LogMsg * msg = new LogMsg (g_Log, level); LogPrint (msg->s, args...); msg->s << std::endl; - if (g_Log) + if (g_Log) { g_Log->Put (msg); - else - { + } else { msg->Process (); delete msg; } diff --git a/docs/configuration.md b/docs/configuration.md index 44ec9d8d..3755153a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -6,6 +6,7 @@ i2pd cmdline options * --httpaddress= - The address to listen on (HTTP server) * --httpport= - The port to listen on (HTTP server) * --log= - Enable or disable logging to file. 1 for yes, 0 for no. +* --loglevel= - Log messages above this level (debug, *info, warn, error) * --daemon= - Enable or disable daemon mode. 1 for yes, 0 for no. * --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). * --v6= - 1 if supports communication through ipv6, off by default From 79cfa52bf976e2cd83492699612d5626fbdb5426 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 28 Dec 2015 08:30:40 -0500 Subject: [PATCH 0715/6300] fixed windows build --- Win32/Win32Service.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Win32/Win32Service.cpp b/Win32/Win32Service.cpp index bf7794e8..392d940d 100644 --- a/Win32/Win32Service.cpp +++ b/Win32/Win32Service.cpp @@ -132,7 +132,7 @@ void I2PService::Start(DWORD dwArgc, PSTR *pszArgv) } catch (DWORD dwError) { - LogPrint("Win32Service Start", dwError); + LogPrint(eLogError, "Win32Service Start", dwError); SetServiceStatus(SERVICE_STOPPED, dwError); } @@ -147,7 +147,7 @@ void I2PService::Start(DWORD dwArgc, PSTR *pszArgv) void I2PService::OnStart(DWORD dwArgc, PSTR *pszArgv) { - LogPrint("Win32Service in OnStart", + LogPrint(eLogInfo, "Win32Service in OnStart", EVENTLOG_INFORMATION_TYPE); Daemon.start(); @@ -186,7 +186,7 @@ void I2PService::Stop() } catch (DWORD dwError) { - LogPrint("Win32Service Stop", dwError); + LogPrint(eLogInfo, "Win32Service Stop", dwError); SetServiceStatus(dwOriginalState); } From 3a35b84b03e0b9edf3b78d002e8bf1c08dccb0c7 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 28 Dec 2015 10:52:02 -0500 Subject: [PATCH 0716/6300] fixed FreeBSD build --- Makefile.bsd | 2 +- util.cpp | 112 +++++++++++++++++++++++++-------------------------- 2 files changed, 56 insertions(+), 58 deletions(-) diff --git a/Makefile.bsd b/Makefile.bsd index 08994a24..255233f1 100644 --- a/Makefile.bsd +++ b/Makefile.bsd @@ -1,4 +1,4 @@ -CXX = g++ +CXX = clang++ CXXFLAGS = -O2 ## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. diff --git a/util.cpp b/util.cpp index 00616c55..ddbfad23 100644 --- a/util.cpp +++ b/util.cpp @@ -405,63 +405,7 @@ namespace http namespace net { -#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__OpenBSD__) - - int GetMTUUnix(const boost::asio::ip::address& localAddress, int fallback) - { - ifaddrs* ifaddr, *ifa = nullptr; - if(getifaddrs(&ifaddr) == -1) - { - LogPrint(eLogError, "NetIface: Can't call getifaddrs(): ", strerror(errno)); - return fallback; - } - - int family = 0; - // look for interface matching local address - for(ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) - { - if(!ifa->ifa_addr) - continue; - - family = ifa->ifa_addr->sa_family; - if(family == AF_INET && localAddress.is_v4()) - { - sockaddr_in* sa = (sockaddr_in*) ifa->ifa_addr; - if(!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4)) - break; // address matches - } - else if(family == AF_INET6 && localAddress.is_v6()) - { - sockaddr_in6* sa = (sockaddr_in6*) ifa->ifa_addr; - if(!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16)) - break; // address matches - } - } - int mtu = fallback; - if(ifa && family) - { // interface found? - int fd = socket(family, SOCK_DGRAM, 0); - if(fd > 0) - { - ifreq ifr; - strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query - if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) - mtu = ifr.ifr_mtu; // MTU - else - LogPrint (eLogError, "NetIface: Failed to run ioctl: ", strerror(errno)); - close(fd); - } - else - LogPrint(eLogError, "NetIface: Failed to create datagram socket"); - } - else - LogPrint(eLogWarning, "NetIface: interface for local address", localAddress.to_string(), " not found"); - freeifaddrs(ifaddr); - - return mtu; - } - -#elif defined(_WIN32) +#ifdef WIN32 int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback) { ULONG outBufLen = 0; @@ -593,6 +537,60 @@ namespace net } } +#else // assume unix + int GetMTUUnix(const boost::asio::ip::address& localAddress, int fallback) + { + ifaddrs* ifaddr, *ifa = nullptr; + if(getifaddrs(&ifaddr) == -1) + { + LogPrint(eLogError, "NetIface: Can't call getifaddrs(): ", strerror(errno)); + return fallback; + } + + int family = 0; + // look for interface matching local address + for(ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if(!ifa->ifa_addr) + continue; + + family = ifa->ifa_addr->sa_family; + if(family == AF_INET && localAddress.is_v4()) + { + sockaddr_in* sa = (sockaddr_in*) ifa->ifa_addr; + if(!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4)) + break; // address matches + } + else if(family == AF_INET6 && localAddress.is_v6()) + { + sockaddr_in6* sa = (sockaddr_in6*) ifa->ifa_addr; + if(!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16)) + break; // address matches + } + } + int mtu = fallback; + if(ifa && family) + { // interface found? + int fd = socket(family, SOCK_DGRAM, 0); + if(fd > 0) + { + ifreq ifr; + strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query + if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) + mtu = ifr.ifr_mtu; // MTU + else + LogPrint (eLogError, "NetIface: Failed to run ioctl: ", strerror(errno)); + close(fd); + } + else + LogPrint(eLogError, "NetIface: Failed to create datagram socket"); + } + else + LogPrint(eLogWarning, "NetIface: interface for local address", localAddress.to_string(), " not found"); + freeifaddrs(ifaddr); + + return mtu; + } #endif // WIN32 int GetMTU(const boost::asio::ip::address& localAddress) From 459800568a0bf50b7b566df646cda0422a3fced4 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 28 Dec 2015 11:55:55 -0500 Subject: [PATCH 0717/6300] fixed windows build --- Win32/Win32Service.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Win32/Win32Service.cpp b/Win32/Win32Service.cpp index 392d940d..cfeba0fe 100644 --- a/Win32/Win32Service.cpp +++ b/Win32/Win32Service.cpp @@ -192,7 +192,7 @@ void I2PService::Stop() } catch (...) { - LogPrint("Win32Service failed to stop.", EVENTLOG_ERROR_TYPE); + LogPrint(eLogError, "Win32Service failed to stop.", EVENTLOG_ERROR_TYPE); SetServiceStatus(dwOriginalState); } @@ -202,7 +202,7 @@ void I2PService::Stop() void I2PService::OnStop() { // Log a service stop message to the Application log. - LogPrint("Win32Service in OnStop", EVENTLOG_INFORMATION_TYPE); + LogPrint(eLogInfo, "Win32Service in OnStop", EVENTLOG_INFORMATION_TYPE); Daemon.stop(); @@ -228,13 +228,13 @@ void I2PService::Pause() } catch (DWORD dwError) { - LogPrint("Win32Service Pause", dwError); + LogPrint(eLogError, "Win32Service Pause", dwError); SetServiceStatus(SERVICE_RUNNING); } catch (...) { - LogPrint("Win32Service failed to pause.", EVENTLOG_ERROR_TYPE); + LogPrint(eLogError, "Win32Service failed to pause.", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_RUNNING); } @@ -258,13 +258,13 @@ void I2PService::Continue() } catch (DWORD dwError) { - LogPrint("Win32Service Continue", dwError); + LogPrint(eLogError, "Win32Service Continue", dwError); SetServiceStatus(SERVICE_PAUSED); } catch (...) { - LogPrint("Win32Service failed to resume.", EVENTLOG_ERROR_TYPE); + LogPrint(eLogError, "Win32Service failed to resume.", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_PAUSED); } @@ -286,11 +286,11 @@ void I2PService::Shutdown() } catch (DWORD dwError) { - LogPrint("Win32Service Shutdown", dwError); + LogPrint(eLogError, "Win32Service Shutdown", dwError); } catch (...) { - LogPrint("Win32Service failed to shut down.", EVENTLOG_ERROR_TYPE); + LogPrint(eLogError, "Win32Service failed to shut down.", EVENTLOG_ERROR_TYPE); } } From 2cc3dfc2ce3fa423b56054fba61c0a421b8bdb38 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 28 Dec 2015 12:26:10 -0500 Subject: [PATCH 0718/6300] fixed windows build --- Win32/Win32Service.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Win32/Win32Service.cpp b/Win32/Win32Service.cpp index cfeba0fe..5a9a8789 100644 --- a/Win32/Win32Service.cpp +++ b/Win32/Win32Service.cpp @@ -138,7 +138,7 @@ void I2PService::Start(DWORD dwArgc, PSTR *pszArgv) } catch (...) { - LogPrint("Win32Service failed to start.", EVENTLOG_ERROR_TYPE); + LogPrint(eLogError, "Win32Service failed to start.", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_STOPPED); } From 8daa7561fa8e879b7349570cb27674bec13e57c6 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 31 Dec 2015 11:21:01 -0500 Subject: [PATCH 0719/6300] pass ident hash by values to RequestComplete --- Transports.cpp | 2 +- Transports.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 25f4f85a..ab59f04d 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -334,7 +334,7 @@ namespace transport m_Service.post (std::bind (&Transports::HandleRequestComplete, this, r, ident)); } - void Transports::HandleRequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident) + void Transports::HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident) { auto it = m_Peers.find (ident); if (it != m_Peers.end ()) diff --git a/Transports.h b/Transports.h index c94ae650..cedbc1d3 100644 --- a/Transports.h +++ b/Transports.h @@ -105,7 +105,7 @@ namespace transport void Run (); void RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); - void HandleRequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); + void HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident); void PostMessages (i2p::data::IdentHash ident, std::vector > msgs); void PostCloseSession (std::shared_ptr router); bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); From ef4dc3cbc9db0cff615590d0668244eca7da8445 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 31 Dec 2015 16:02:10 -0500 Subject: [PATCH 0720/6300] fixed race condition of openssl calls --- Crypto.cpp | 31 +++++++++++++++++++++++++++++++ Crypto.h | 3 +++ Daemon.cpp | 6 +++--- api.cpp | 7 +++++++ api.h | 1 + 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Crypto.cpp b/Crypto.cpp index 75ece1b7..f6bb2f29 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -1,9 +1,13 @@ #include #include +#include +#include #include #include #include #include +#include +#include #include "Log.h" //#include "TunnelBase.h" #include "Crypto.h" @@ -677,6 +681,33 @@ namespace crypto m_IVDecryption.Decrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv #endif } + + std::vector > m_OpenSSLMutexes; + static void OpensslLockingCallback(int mode, int type, const char * file, int line) + { + if (type > 0 && (size_t)type < m_OpenSSLMutexes.size ()) + { + if (mode & CRYPTO_LOCK) + m_OpenSSLMutexes[type]->lock (); + else + m_OpenSSLMutexes[type]->unlock (); + } + } + + void InitCrypto () + { + SSL_library_init (); + auto numLocks = CRYPTO_num_locks(); + for (int i = 0; i < numLocks; i++) + m_OpenSSLMutexes.emplace_back (new std::mutex); + CRYPTO_set_locking_callback (OpensslLockingCallback); + } + + void TerminateCrypto () + { + CRYPTO_set_locking_callback (nullptr); + m_OpenSSLMutexes.clear (); + } } } diff --git a/Crypto.h b/Crypto.h index fd49ebc5..dfe0da0e 100644 --- a/Crypto.h +++ b/Crypto.h @@ -272,6 +272,9 @@ namespace crypto CBCDecryption m_LayerDecryption; #endif }; + + void InitCrypto (); + void TerminateCrypto (); } } diff --git a/Daemon.cpp b/Daemon.cpp index e2d7d895..eef58c78 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -19,8 +19,7 @@ #include "HTTPServer.h" #include "I2PControl.h" #include "ClientContext.h" -// ssl.h somehow pulls Windows.h stuff that has to go after asio -#include +#include "Crypto.h" #ifdef USE_UPNP #include "UPnP.h" @@ -60,7 +59,7 @@ namespace i2p bool Daemon_Singleton::init(int argc, char* argv[]) { - SSL_library_init (); + i2p::crypto::InitCrypto (); i2p::util::config::OptionParser(argc, argv); i2p::context.Init (); @@ -171,6 +170,7 @@ namespace i2p d.m_I2PControlService->Stop (); d.m_I2PControlService = nullptr; } + i2p::crypto::TerminateCrypto (); StopLog (); return true; diff --git a/api.cpp b/api.cpp index 5858dc6d..9a9c5c46 100644 --- a/api.cpp +++ b/api.cpp @@ -7,6 +7,7 @@ #include "RouterContext.h" #include "Identity.h" #include "Destination.h" +#include "Crypto.h" #include "util.h" #include "api.h" @@ -18,9 +19,15 @@ namespace api { i2p::util::filesystem::SetAppName (appName); i2p::util::config::OptionParser(argc, argv); + i2p::crypto::InitCrypto (); i2p::context.Init (); } + void TerminateI2P () + { + i2p::crypto::TerminateCrypto (); + } + void StartI2P (std::ostream * logStream) { if (logStream) diff --git a/api.h b/api.h index d34f8ae4..05552249 100644 --- a/api.h +++ b/api.h @@ -13,6 +13,7 @@ namespace api { // initialization start and stop void InitI2P (int argc, char* argv[], const char * appName); + void TerminateI2P (); void StartI2P (std::ostream * logStream = nullptr); // write system log to logStream, if not specified to .log in application's folder void StopI2P (); From 4242c86d40b9bb790f3a990c8c924afc3dc73765 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 31 Dec 2015 17:09:04 -0500 Subject: [PATCH 0721/6300] check for buffer overflow during flood --- NetDb.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index e6269959..961450e1 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -478,16 +478,22 @@ namespace data uint8_t * payload = floodMsg->GetPayload (); memcpy (payload, buf, 33); // key + type htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); // zero reply token - memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + offset, len - offset); - floodMsg->len += DATABASE_STORE_HEADER_SIZE + len -offset; - floodMsg->FillI2NPMessageHeader (eI2NPDatabaseStore); - std::set excluded; - for (int i = 0; i < 3; i++) - { - auto floodfill = GetClosestFloodfill (ident, excluded); - if (floodfill) - transports.SendMessage (floodfill->GetIdentHash (), floodMsg); + auto msgLen = len - offset; + floodMsg->len += DATABASE_STORE_HEADER_SIZE + msgLen; + if (floodMsg->len < floodMsg->maxLen) + { + memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + offset, msgLen); + floodMsg->FillI2NPMessageHeader (eI2NPDatabaseStore); + std::set excluded; + for (int i = 0; i < 3; i++) + { + auto floodfill = GetClosestFloodfill (ident, excluded); + if (floodfill) + transports.SendMessage (floodfill->GetIdentHash (), floodMsg); + } } + else + LogPrint (eLogError, "Database store message is too long ", floodMsg->len); } } From 68b1fe8631f5807ff219ea7743219a8ba8e1f948 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 31 Dec 2015 19:46:14 -0500 Subject: [PATCH 0722/6300] use TUNNEL_DATA_ENCRYPTED_SIZE for tunnel encryption --- Crypto.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Crypto.cpp b/Crypto.cpp index f6bb2f29..d6afaac4 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -9,7 +10,7 @@ #include #include #include "Log.h" -//#include "TunnelBase.h" +#include "TunnelBase.h" #include "Crypto.h" namespace i2p @@ -640,7 +641,7 @@ namespace crypto #else m_IVEncryption.Encrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv m_LayerEncryption.SetIV (out); - m_LayerEncryption.Encrypt (in + 16, /*i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE*/1008, out + 16); // data + m_LayerEncryption.Encrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data m_IVEncryption.Encrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv #endif } @@ -677,7 +678,7 @@ namespace crypto #else m_IVDecryption.Decrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv m_LayerDecryption.SetIV (out); - m_LayerDecryption.Decrypt (in + 16, /*i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE*/1008, out + 16); // data + m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data m_IVDecryption.Decrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv #endif } From f35660c8e2235d9f546468aac719acc7a926ce67 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 1 Jan 2016 08:30:38 -0500 Subject: [PATCH 0723/6300] fixed windows build --- Crypto.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Crypto.cpp b/Crypto.cpp index d6afaac4..1a868567 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -7,10 +7,10 @@ #include #include #include -#include #include -#include "Log.h" #include "TunnelBase.h" +#include +#include "Log.h" #include "Crypto.h" namespace i2p From 2ba314d9d94c8d316ebfef55fa32e55ab1e3a19d Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 1 Jan 2016 15:41:18 -0500 Subject: [PATCH 0724/6300] count checksum and padding for buffer size --- NTCPSession.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index f7d3b3d7..ededc765 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -549,12 +549,12 @@ namespace transport if (dataSize) { // new message - if (dataSize > NTCP_MAX_MESSAGE_SIZE) + if (dataSize + 16U > NTCP_MAX_MESSAGE_SIZE - 2) // + 6 + padding { LogPrint (eLogError, "NTCP: data size ", dataSize, " exceeds max size"); return false; } - auto msg = dataSize <= I2NP_MAX_SHORT_MESSAGE_SIZE - 2 ? NewI2NPShortMessage () : NewI2NPMessage (); + auto msg = (dataSize + 16U) <= I2NP_MAX_SHORT_MESSAGE_SIZE - 2 ? NewI2NPShortMessage () : NewI2NPMessage (); m_NextMessage = msg; memcpy (m_NextMessage->buf, buf, 16); m_NextMessageOffset = 16; From 9fc69db9ebd46d691ed5515008c6dbef4f33e81e Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 1 Jan 2016 17:39:12 -0500 Subject: [PATCH 0725/6300] reserve extra 16 bytes for padding --- I2NPProtocol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2NPProtocol.h b/I2NPProtocol.h index a38ff23b..340aec7f 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -193,7 +193,7 @@ namespace tunnel struct I2NPMessageBuffer: public I2NPMessage { I2NPMessageBuffer () { buf = m_Buffer; maxLen = sz; }; - uint8_t m_Buffer[sz + 16]; + uint8_t m_Buffer[sz + 32]; // 16 alignment + 16 padding }; std::shared_ptr NewI2NPMessage (); From 45e7111ddaab568cd3e7eb883e90399a741bad55 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 2 Jan 2016 17:23:20 -0500 Subject: [PATCH 0726/6300] publish stats for floodfill --- NetDb.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/NetDb.cpp b/NetDb.cpp index 961450e1..cc50bbc2 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -781,6 +781,7 @@ namespace data void NetDb::Publish () { + i2p::context.UpdateStats (); // for floodfill std::set excluded; // TODO: fill up later for (int i = 0; i < 2; i++) { From 7149b509d75ad7121b64025d83896025c3ad21a7 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 2 Jan 2016 22:17:04 -0500 Subject: [PATCH 0727/6300] extra bandwidth caps --- Daemon.cpp | 10 +++++++--- RouterContext.cpp | 13 +++++++++++-- RouterContext.h | 1 + RouterInfo.cpp | 12 ++++++++++-- RouterInfo.h | 18 +++++++++++------- Transports.cpp | 5 +++-- Transports.h | 1 + 7 files changed, 44 insertions(+), 16 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index eef58c78..ad61a130 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -78,16 +78,20 @@ namespace i2p i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); i2p::context.SetSupportsV6 (i2p::util::config::GetArg("-v6", 0)); - i2p::context.SetFloodfill (i2p::util::config::GetArg("-floodfill", 0)); + bool isFloodfill = i2p::util::config::GetArg("-floodfill", 0); + i2p::context.SetFloodfill (isFloodfill); auto bandwidth = i2p::util::config::GetArg("-bandwidth", ""); if (bandwidth.length () > 0) { - if (bandwidth[0] > 'L') + if (bandwidth[0] > 'O') + i2p::context.SetExtraBandwidth (); + else if (bandwidth[0] > 'L') i2p::context.SetHighBandwidth (); else i2p::context.SetLowBandwidth (); } - + else if (isFloodfill) + i2p::context.SetExtraBandwidth (); LogPrint(eLogDebug, "Daemon: CMD parameters:"); for (int i = 0; i < argc; ++i) LogPrint(eLogDebug, i, ": ", argv[i]); diff --git a/RouterContext.cpp b/RouterContext.cpp index 31038820..715d2ecc 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -149,7 +149,7 @@ namespace i2p { if (!m_RouterInfo.IsHighBandwidth ()) { - m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eHighBandwidth); + m_RouterInfo.SetCaps ((m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eHighBandwidth) & ~i2p::data::RouterInfo::eExtraBandwidth); UpdateRouterInfo (); } } @@ -158,11 +158,20 @@ namespace i2p { if (m_RouterInfo.IsHighBandwidth ()) { - m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eHighBandwidth); + m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eHighBandwidth & ~i2p::data::RouterInfo::eExtraBandwidth); UpdateRouterInfo (); } } + void RouterContext::SetExtraBandwidth () + { + if (!m_RouterInfo.IsExtraBandwidth ()) + { + m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eExtraBandwidth | i2p::data::RouterInfo::eHighBandwidth); + UpdateRouterInfo (); + } + } + bool RouterContext::IsUnreachable () const { return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable; diff --git a/RouterContext.h b/RouterContext.h index 13027b44..8c07b17f 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -62,6 +62,7 @@ namespace i2p void SetFloodfill (bool floodfill); void SetHighBandwidth (); void SetLowBandwidth (); + void SetExtraBandwidth (); bool AcceptsTunnels () const { return m_AcceptsTunnels; }; void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; }; bool SupportsV6 () const { return m_RouterInfo.IsV6 (); }; diff --git a/RouterInfo.cpp b/RouterInfo.cpp index e071320c..a0aad8d9 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -265,6 +265,10 @@ namespace data case CAPS_FLAG_HIGH_BANDWIDTH3: m_Caps |= Caps::eHighBandwidth; break; + case CAPS_FLAG_EXTRA_BANDWIDTH1: + case CAPS_FLAG_EXTRA_BANDWIDTH2: + m_Caps |= Caps::eExtraBandwidth; + break; case CAPS_FLAG_HIDDEN: m_Caps |= Caps::eHidden; break; @@ -291,11 +295,15 @@ namespace data std::string caps; if (m_Caps & eFloodfill) { - caps += CAPS_FLAG_HIGH_BANDWIDTH3; // highest bandwidth + if (m_Caps & eExtraBandwidth) caps += CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' + caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' caps += CAPS_FLAG_FLOODFILL; // floodfill } else - caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 : CAPS_FLAG_LOW_BANDWIDTH2; // bandwidth + { + if (m_Caps & eExtraBandwidth) caps += CAPS_FLAG_EXTRA_BANDWIDTH1; + caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 : CAPS_FLAG_LOW_BANDWIDTH2; // bandwidth + } if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable if (m_Caps & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable diff --git a/RouterInfo.h b/RouterInfo.h index a7ab2102..a750cfea 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -23,7 +23,9 @@ namespace data const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'M'; const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'N'; const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O'; - + const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; + const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; + const char CAPS_FLAG_SSU_TESTING = 'B'; const char CAPS_FLAG_SSU_INTRODUCER = 'C'; @@ -44,11 +46,12 @@ namespace data { eFloodfill = 0x01, eHighBandwidth = 0x02, - eReachable = 0x04, - eSSUTesting = 0x08, - eSSUIntroducer = 0x10, - eHidden = 0x20, - eUnreachable = 0x40 + eExtraBandwidth = 0x04, + eReachable = 0x08, + eSSUTesting = 0x10, + eSSUIntroducer = 0x20, + eHidden = 0x40, + eUnreachable = 0x80 }; enum TransportStyle @@ -131,7 +134,8 @@ namespace data bool IsPeerTesting () const { return m_Caps & eSSUTesting; }; bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; - + bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; + uint8_t GetCaps () const { return m_Caps; }; void SetCaps (uint8_t caps); void SetCaps (const char * caps); diff --git a/Transports.cpp b/Transports.cpp index ab59f04d..e66ad864 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -200,8 +200,9 @@ namespace transport bool Transports::IsBandwidthExceeded () const { - if (i2p::context.GetRouterInfo ().IsHighBandwidth ()) return false; - return std::max (m_InBandwidth, m_OutBandwidth) > LOW_BANDWIDTH_LIMIT; + if (i2p::context.GetRouterInfo ().IsExtraBandwidth ()) return false; + auto bw = std::max (m_InBandwidth, m_OutBandwidth); + return bw > (i2p::context.GetRouterInfo ().IsHighBandwidth () ? HIGH_BANDWIDTH_LIMIT : LOW_BANDWIDTH_LIMIT); } void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) diff --git a/Transports.h b/Transports.h index cedbc1d3..98e32679 100644 --- a/Transports.h +++ b/Transports.h @@ -67,6 +67,7 @@ namespace transport const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds const uint32_t LOW_BANDWIDTH_LIMIT = 32*1024; // 32KBs + const uint32_t HIGH_BANDWIDTH_LIMIT = 256*1024; // 256KBs class Transports { public: From b7c021af8cf1028bd6081ff0199ecfb4b566f9f8 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 3 Jan 2016 09:54:03 -0500 Subject: [PATCH 0728/6300] clear extra bandwidth bit --- RouterContext.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index 715d2ecc..3117c85a 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -147,7 +147,7 @@ namespace i2p void RouterContext::SetHighBandwidth () { - if (!m_RouterInfo.IsHighBandwidth ()) + if (!m_RouterInfo.IsHighBandwidth () || m_RouterInfo.IsExtraBandwidth ()) { m_RouterInfo.SetCaps ((m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eHighBandwidth) & ~i2p::data::RouterInfo::eExtraBandwidth); UpdateRouterInfo (); @@ -156,7 +156,7 @@ namespace i2p void RouterContext::SetLowBandwidth () { - if (m_RouterInfo.IsHighBandwidth ()) + if (m_RouterInfo.IsHighBandwidth () || m_RouterInfo.IsExtraBandwidth ()) { m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eHighBandwidth & ~i2p::data::RouterInfo::eExtraBandwidth); UpdateRouterInfo (); @@ -165,7 +165,7 @@ namespace i2p void RouterContext::SetExtraBandwidth () { - if (!m_RouterInfo.IsExtraBandwidth ()) + if (!m_RouterInfo.IsExtraBandwidth () || !m_RouterInfo.IsHighBandwidth ()) { m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eExtraBandwidth | i2p::data::RouterInfo::eHighBandwidth); UpdateRouterInfo (); From d1c57a18720aaf3834360b82e90906ce36cdaa37 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 3 Jan 2016 19:15:12 -0500 Subject: [PATCH 0729/6300] New bandwidth values --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 3755153a..5a420b23 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -11,7 +11,7 @@ i2pd cmdline options * --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). * --v6= - 1 if supports communication through ipv6, off by default * --floodfill= - 1 if router is floodfill, off by default -* --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O if not. Always O if floodfill, otherwise L by default. +* --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited * --httpproxyaddress= - The address to listen on (HTTP Proxy) * --httpproxyport= - The port to listen on (HTTP Proxy) 4446 by default * --socksproxyaddress= - The address to listen on (SOCKS Proxy) From 727436e1cfed05e1d094ebe57ceac2aa48fea25c Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 3 Jan 2016 21:43:43 -0500 Subject: [PATCH 0730/6300] specify signature type for I2P tunnels --- ClientContext.cpp | 16 +++++++++------- ClientContext.h | 5 ++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 6b49b53a..c9de0b86 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -147,8 +147,8 @@ namespace client m_SharedLocalDestination = nullptr; } - // should be moved in i2p::utils::fs - std::shared_ptr ClientContext::LoadLocalDestination (const std::string& filename, bool isPublic) + std::shared_ptr ClientContext::LoadLocalDestination (const std::string& filename, + bool isPublic, i2p::data::SigningKeyType sigType) { i2p::data::PrivateKeys keys; std::string fullPath = i2p::util::filesystem::GetFullPath (filename); @@ -166,8 +166,8 @@ namespace client } else { - LogPrint (eLogError, "Clients: can't open file ", fullPath, " Creating new one"); - keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); + LogPrint (eLogError, "Clients: can't open file ", fullPath, " Creating new one with signature type ", sigType); + keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType); std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); size_t len = keys.GetFullLen (); uint8_t * buf = new uint8_t[len]; @@ -281,10 +281,11 @@ namespace client std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); + i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); std::shared_ptr localDestination = nullptr; if (keys.length () > 0) - localDestination = LoadLocalDestination (keys, false); + localDestination = LoadLocalDestination (keys, false, sigType); auto clientTunnel = new I2PClientTunnel (dest, address, port, localDestination, destinationPort); if (m_ClientTunnels.insert (std::make_pair (port, std::unique_ptr(clientTunnel))).second) clientTunnel->Start (); @@ -301,8 +302,9 @@ namespace client // optional params int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); - - auto localDestination = LoadLocalDestination (keys, true); + i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); + + auto localDestination = LoadLocalDestination (keys, true, sigType); I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ? new I2PServerTunnelHTTP (host, port, localDestination, inPort) : new I2PServerTunnel (host, port, localDestination, inPort); if (accessList.length () > 0) { diff --git a/ClientContext.h b/ClientContext.h index 6684ea0e..44e3f5b9 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -24,10 +24,12 @@ namespace client const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination"; const char I2P_CLIENT_TUNNEL_KEYS[] = "keys"; + const char I2P_CLIENT_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport"; const char I2P_SERVER_TUNNEL_HOST[] = "host"; const char I2P_SERVER_TUNNEL_PORT[] = "port"; const char I2P_SERVER_TUNNEL_KEYS[] = "keys"; + const char I2P_SERVER_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_SERVER_TUNNEL_INPORT[] = "inport"; const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist"; @@ -48,7 +50,8 @@ namespace client const std::map * params = nullptr); void DeleteLocalDestination (std::shared_ptr destination); std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; - std::shared_ptr LoadLocalDestination (const std::string& filename, bool isPublic); + std::shared_ptr LoadLocalDestination (const std::string& filename, bool isPublic, + i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); AddressBook& GetAddressBook () { return m_AddressBook; }; const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; From a26c5f85c37898890ea3003e9d649df51e19472d Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 4 Jan 2016 19:56:46 -0500 Subject: [PATCH 0731/6300] ignore LeaseSets coming from transit tunnels --- I2NPProtocol.cpp | 6 ++++++ I2NPProtocol.h | 3 ++- Tunnel.cpp | 2 +- TunnelEndpoint.cpp | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index cfa1bd15..698475fe 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -268,6 +268,12 @@ namespace i2p m->FillI2NPMessageHeader (eI2NPDatabaseStore); return m; } + + bool IsRouterInfoMsg (std::shared_ptr msg) + { + if (!msg || msg->GetTypeID () != eI2NPDatabaseStore) return false; + return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo + } bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) { diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 340aec7f..e6f20558 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -213,7 +213,8 @@ namespace tunnel std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0); std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0); - + bool IsRouterInfoMsg (std::shared_ptr msg); + bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); diff --git a/Tunnel.cpp b/Tunnel.cpp index c7d6573e..7a91adca 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -486,7 +486,7 @@ namespace tunnel auto typeID = msg->GetTypeID (); LogPrint (eLogDebug, "TunnelGateway of ", (int)len, " bytes for tunnel ", tunnel->GetTunnelID (), ". Msg type ", (int)typeID); - if (typeID == eI2NPDatabaseStore || typeID == eI2NPDatabaseSearchReply) + if (IsRouterInfoMsg (msg) || typeID == eI2NPDatabaseSearchReply) // transit DatabaseStore my contain new/updated RI // or DatabaseSearchReply with new routers i2p::data::netdb.PostI2NPMsg (msg); diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 73530557..271c115d 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -248,7 +248,7 @@ namespace tunnel LogPrint (eLogError, "TunnelMessage: Unknown delivery type ", (int)msg.deliveryType); }; // catch RI or reply with new list of routers - if ((typeID == eI2NPDatabaseStore || typeID == eI2NPDatabaseSearchReply) && + if ((IsRouterInfoMsg (msg.data) || typeID == eI2NPDatabaseSearchReply) && !m_IsInbound && msg.deliveryType != eDeliveryTypeLocal) i2p::data::netdb.PostI2NPMsg (msg.data); } From ff48422ec03d953269cde88b8e60388f8f5d59ea Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 Jan 2016 14:29:18 -0500 Subject: [PATCH 0732/6300] check I2NP message buffer size --- I2NPProtocol.cpp | 29 ++++++++++++----------------- I2NPProtocol.h | 13 +++++++++++-- SSUData.cpp | 4 ++-- TunnelEndpoint.cpp | 8 ++++---- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 698475fe..16d5e245 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -51,21 +51,16 @@ namespace i2p SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); } - std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID) + std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID) { auto msg = NewI2NPMessage (len); - if (msg->len + len < msg->maxLen) - { - memcpy (msg->GetPayload (), buf, len); - msg->len += len; - } - else - LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length"); + if (msg->Concat (buf, len) < len) + LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length ", msg->maxLen); msg->FillI2NPMessageHeader (msgType, replyMsgID); return msg; } - std::shared_ptr CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from) + std::shared_ptr CreateI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { auto msg = NewI2NPMessage (); if (msg->offset + len < msg->maxLen) @@ -413,8 +408,7 @@ namespace i2p std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf) { auto msg = NewI2NPShortMessage (); - memcpy (msg->GetPayload (), buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE); - msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; + msg->Concat (buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE); msg->FillI2NPMessageHeader (eI2NPTunnelData); return msg; } @@ -422,9 +416,9 @@ namespace i2p std::shared_ptr CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload) { auto msg = NewI2NPShortMessage (); - memcpy (msg->GetPayload () + 4, payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4); htobe32buf (msg->GetPayload (), tunnelID); - msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; + msg->len += 4; // tunnelID + msg->Concat (payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4); msg->FillI2NPMessageHeader (eI2NPTunnelData); return msg; } @@ -442,8 +436,9 @@ namespace i2p uint8_t * payload = msg->GetPayload (); htobe32buf (payload + TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET, tunnelID); htobe16buf (payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET, len); - memcpy (payload + TUNNEL_GATEWAY_HEADER_SIZE, buf, len); - msg->len += TUNNEL_GATEWAY_HEADER_SIZE + len; + msg->len += TUNNEL_GATEWAY_HEADER_SIZE; + if (msg->Concat (buf, len) < len) + LogPrint (eLogError, "I2NP: tunnel gateway buffer overflow ", msg->maxLen); msg->FillI2NPMessageHeader (eI2NPTunnelGateway); return msg; } @@ -473,8 +468,8 @@ namespace i2p size_t gatewayMsgOffset = I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE; msg->offset += gatewayMsgOffset; msg->len += gatewayMsgOffset; - memcpy (msg->GetPayload (), buf, len); - msg->len += len; + if (msg->Concat (buf, len) < len) + LogPrint (eLogError, "I2NP: tunnel gateway buffer overflow ", msg->maxLen); msg->FillI2NPMessageHeader (msgType, replyMsgID); // create content message len = msg->GetLength (); msg->offset -= gatewayMsgOffset; diff --git a/I2NPProtocol.h b/I2NPProtocol.h index e6f20558..9c41a06b 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -155,6 +155,15 @@ namespace tunnel } } + size_t Concat (const uint8_t * buf1, size_t len1) + { + // make sure with don't write beyond maxLen + if (len + len1 > maxLen) len1 = maxLen - len; + memcpy (buf + len, buf1, len1); + len += len1; + return len1; + } + I2NPMessage& operator=(const I2NPMessage& other) { memcpy (buf + offset, other.buf + other.offset, other.GetLength ()); @@ -200,8 +209,8 @@ namespace tunnel std::shared_ptr NewI2NPShortMessage (); std::shared_ptr NewI2NPMessage (size_t len); - std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, int len, uint32_t replyMsgID = 0); - std::shared_ptr CreateI2NPMessage (const uint8_t * buf, int len, std::shared_ptr from = nullptr); + std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0); + std::shared_ptr CreateI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from = nullptr); std::shared_ptr CreateDeliveryStatusMsg (uint32_t msgID); std::shared_ptr CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, diff --git a/SSUData.cpp b/SSUData.cpp index 33c85d71..cb9d1015 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -19,8 +19,8 @@ namespace transport *newMsg = *msg; msg = newMsg; } - memcpy (msg->buf + msg->len, fragment, fragmentSize); - msg->len += fragmentSize; + if (msg->Concat (fragment, fragmentSize) < fragmentSize) + LogPrint (eLogError, "SSU: I2NP buffer overflow ", msg->maxLen); nextFragmentNum++; } diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 271c115d..5225c8d8 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -152,8 +152,8 @@ namespace tunnel *newMsg = *(msg.data); msg.data = newMsg; } - memcpy (msg.data->buf + msg.data->len, fragment, size); // concatenate fragment - msg.data->len += size; + if (msg.data->Concat (fragment, size) < size) // concatenate fragment + LogPrint (eLogError, "Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); if (isLastFragment) { // message complete @@ -208,8 +208,8 @@ namespace tunnel *newMsg = *(msg.data); msg.data = newMsg; } - memcpy (msg.data->buf + msg.data->len, it->second.data->GetBuffer (), size); // concatenate out-of-sync fragment - msg.data->len += size; + if (msg.data->Concat (it->second.data->GetBuffer (), size) < size) // concatenate out-of-sync fragment + LogPrint (eLogError, "Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); if (it->second.isLastFragment) { // message complete From 9cc592b564cf21ee9658de38ef2ec5048d4e5aae Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 Jan 2016 14:50:45 -0500 Subject: [PATCH 0733/6300] correct buffer size for deflate --- Streaming.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index 65148127..6834d4bb 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -764,6 +764,7 @@ namespace stream m_LocalDestination.m_Deflator.SetCompressionLevel (Z_DEFAULT_COMPRESSION); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for lengthlength + msg->len += 4; size_t size = m_LocalDestination.m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); if (size) { @@ -771,7 +772,7 @@ namespace stream htobe16buf (buf + 4, m_LocalDestination.GetLocalPort ()); // source port htobe16buf (buf + 6, m_Port); // destination port buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol - msg->len += size + 4; + msg->len += size; msg->FillI2NPMessageHeader (eI2NPData); } else From c9d95ff161e66b2b87d6ee33ea3aa17646007e3f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 8 Jan 2016 10:21:43 -0500 Subject: [PATCH 0734/6300] eliminate one extra multipilication --- Signature.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Signature.cpp b/Signature.cpp index 40f8ce6c..13612327 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -378,7 +378,9 @@ namespace crypto auto x = RecoverX (y, ctx); if (BN_is_bit_set (x, 0) != isHighestBitSet) BN_sub (x, q, x); // x = q - x - EDDSAPoint p {x, y}; + BIGNUM * z = BN_new (), * t = BN_new (); + BN_one (z); BN_mod_mul (t, x, y, q, ctx); // pre-calculate t + EDDSAPoint p {x, y, z, t}; if (!IsOnCurve (p, ctx)) LogPrint (eLogError, "Decoded point is not on 25519"); return p; From 26d305d8666a0617050fa17efae1b906d7b1e87b Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 9 Jan 2016 17:26:17 -0500 Subject: [PATCH 0735/6300] fixed misalignment of certificate length --- Identity.cpp | 18 +++++++++--------- Identity.h | 9 ++------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index 4b518b5b..ff3946b8 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -16,7 +16,7 @@ namespace data { // copy public and signing keys together memcpy (publicKey, keys.publicKey, sizeof (publicKey) + sizeof (signingKey)); - memset (&certificate, 0, sizeof (certificate)); + memset (certificate, 0, sizeof (certificate)); return *this; } @@ -105,8 +105,8 @@ namespace data } m_ExtendedLen = 4 + excessLen; // 4 bytes extra + excess length // fill certificate - m_StandardIdentity.certificate.type = CERTIFICATE_TYPE_KEY; - m_StandardIdentity.certificate.length = htobe16 (m_ExtendedLen); + m_StandardIdentity.certificate[0] = CERTIFICATE_TYPE_KEY; + htobe16buf (m_StandardIdentity.certificate + 1, m_ExtendedLen); // fill extended buffer m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; htobe16buf (m_ExtendedBuffer, type); @@ -125,7 +125,7 @@ namespace data else // DSA-SHA1 { memcpy (m_StandardIdentity.signingKey, signingKey, sizeof (m_StandardIdentity.signingKey)); - memset (&m_StandardIdentity.certificate, 0, sizeof (m_StandardIdentity.certificate)); + memset (m_StandardIdentity.certificate, 0, sizeof (m_StandardIdentity.certificate)); m_IdentHash = m_StandardIdentity.Hash (); m_ExtendedLen = 0; m_ExtendedBuffer = nullptr; @@ -200,9 +200,9 @@ namespace data memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE); delete[] m_ExtendedBuffer; - if (m_StandardIdentity.certificate.length) + m_ExtendedLen = bufbe16toh (m_StandardIdentity.certificate + 1); + if (m_ExtendedLen) { - m_ExtendedLen = be16toh (m_StandardIdentity.certificate.length); if (m_ExtendedLen + DEFAULT_IDENTITY_SIZE <= len) { m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; @@ -272,7 +272,7 @@ namespace data if (!m_Verifier) CreateVerifier (); if (m_Verifier) return m_Verifier->GetSignatureLen (); - return 40; + return i2p::crypto::DSA_SIGNATURE_LENGTH; } bool IdentityEx::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { @@ -284,14 +284,14 @@ namespace data SigningKeyType IdentityEx::GetSigningKeyType () const { - if (m_StandardIdentity.certificate.type == CERTIFICATE_TYPE_KEY && m_ExtendedBuffer) + if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedBuffer) return bufbe16toh (m_ExtendedBuffer); // signing key return SIGNING_KEY_TYPE_DSA_SHA1; } CryptoKeyType IdentityEx::GetCryptoKeyType () const { - if (m_StandardIdentity.certificate.type == CERTIFICATE_TYPE_KEY && m_ExtendedBuffer) + if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedBuffer) return bufbe16toh (m_ExtendedBuffer + 2); // crypto key return CRYPTO_KEY_TYPE_ELGAMAL; } diff --git a/Identity.h b/Identity.h index 1e7316af..27da190d 100644 --- a/Identity.h +++ b/Identity.h @@ -18,7 +18,6 @@ namespace data return ident.ToBase64 ().substr (0, 4); } -#pragma pack(1) struct Keys { uint8_t privateKey[256]; @@ -38,11 +37,7 @@ namespace data { uint8_t publicKey[256]; uint8_t signingKey[128]; - struct - { - uint8_t type; - uint16_t length; - } certificate; + uint8_t certificate[3]; // byte 1 - type, bytes 2-3 - length Identity () = default; Identity (const Keys& keys) { *this = keys; }; @@ -50,7 +45,7 @@ namespace data size_t FromBuffer (const uint8_t * buf, size_t len); IdentHash Hash () const; }; -#pragma pack() + Keys CreateRandomKeys (); const size_t DEFAULT_IDENTITY_SIZE = sizeof (Identity); // 387 bytes From 595b2619fd2aa81be39c46b803c045c4ded3e7a4 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 9 Jan 2016 19:24:52 -0500 Subject: [PATCH 0736/6300] fixed misalignment for timestamp --- SSUSession.cpp | 15 +++++---------- SSUSession.h | 4 +--- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index a91ecc2d..f5e1f6e6 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -655,11 +655,10 @@ namespace transport LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } - //TODO: we are using a dirty solution here but should work for now SSUHeader * header = (SSUHeader *)buf; memcpy (header->iv, iv, 16); header->flag = payloadType << 4; // MSB is 0 - htobe32buf (&(header->time), i2p::util::GetSecondsSinceEpoch ()); + htobe32buf (header->time, i2p::util::GetSecondsSinceEpoch ()); uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); i2p::crypto::CBCEncryption encryption; @@ -679,12 +678,11 @@ namespace transport LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } - //TODO: we are using a dirty solution here but should work for now SSUHeader * header = (SSUHeader *)buf; RAND_bytes (header->iv, 16); // random iv m_SessionKeyEncryption.SetIV (header->iv); header->flag = payloadType << 4; // MSB is 0 - htobe32buf (&(header->time), i2p::util::GetSecondsSinceEpoch ()); + htobe32buf (header->time, i2p::util::GetSecondsSinceEpoch ()); uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); m_SessionKeyEncryption.Encrypt (encrypted, encryptedLen, encrypted); @@ -700,8 +698,7 @@ namespace transport { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; - } - //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved + } SSUHeader * header = (SSUHeader *)buf; uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); @@ -717,8 +714,7 @@ namespace transport { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; - } - //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved + } SSUHeader * header = (SSUHeader *)buf; uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); @@ -735,8 +731,7 @@ namespace transport { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return false; - } - //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved + } SSUHeader * header = (SSUHeader *)buf; uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); diff --git a/SSUSession.h b/SSUSession.h index 59e834a0..be1a455f 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -14,18 +14,16 @@ namespace i2p namespace transport { const uint8_t SSU_HEADER_EXTENDED_OPTIONS_INCLUDED = 0x04; -#pragma pack(1) struct SSUHeader { uint8_t mac[16]; uint8_t iv[16]; uint8_t flag; - uint32_t time; + uint8_t time[4]; uint8_t GetPayloadType () const { return flag >> 4; }; bool IsExtendedOptions () const { return flag & SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; }; }; -#pragma pack() const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes From d41f930f6934f5cf4ad8a1cf510a3613aa102021 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 10 Jan 2016 16:40:28 -0500 Subject: [PATCH 0737/6300] fixed unintialized reply key --- TunnelConfig.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TunnelConfig.h b/TunnelConfig.h index 1628b4b4..826979c8 100644 --- a/TunnelConfig.h +++ b/TunnelConfig.h @@ -33,6 +33,7 @@ namespace tunnel { RAND_bytes (layerKey, 32); RAND_bytes (ivKey, 32); + RAND_bytes (replyKey, 32); RAND_bytes (replyIV, 16); RAND_bytes ((uint8_t *)&tunnelID, 4); isGateway = true; @@ -100,7 +101,7 @@ namespace tunnel clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); - // TODO: fill padding + RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); i2p::crypto::ElGamalEncryption elGamalEncryption (ident->GetEncryptionPublicKey ()); elGamalEncryption.Encrypt (clearText, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET); memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); From 5487fad2ce99c1f8f61a9f95c1d304353b9b07b2 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 10 Jan 2016 18:55:00 -0500 Subject: [PATCH 0738/6300] fixed race conditin --- Identity.cpp | 1 + RouterInfo.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Identity.cpp b/Identity.cpp index ff3946b8..7c8e38be 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -365,6 +365,7 @@ namespace data void IdentityEx::DropVerifier () const { + // TODO: potential race condition with Verify m_Verifier = nullptr; } diff --git a/RouterInfo.cpp b/RouterInfo.cpp index a0aad8d9..2f73bf92 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -65,7 +65,6 @@ namespace data LogPrint (eLogError, "RouterInfo: signature verification failed"); m_IsUnreachable = true; } - m_RouterIdentity->DropVerifier (); } void RouterInfo::SetRouterIdentity (std::shared_ptr identity) From 0c290e65ef14d9a672319d01ab7bb1ce1955b316 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 10 Jan 2016 21:39:29 -0500 Subject: [PATCH 0739/6300] removed deprecated parameters --- ClientContext.cpp | 27 +-------------------------- docs/configuration.md | 8 -------- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index c9de0b86..95d2d0a1 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -48,32 +48,7 @@ namespace client m_SocksProxy = new i2p::proxy::SOCKSProxy(i2p::util::config::GetArg("-socksproxyaddress", "127.0.0.1"), i2p::util::config::GetArg("-socksproxyport", 4447), localDestination); m_SocksProxy->Start(); - // I2P tunnels: IRC - std::string ircDestination = i2p::util::config::GetArg("-ircdest", ""); - if (ircDestination.length () > 0) // ircdest is presented - { - LogPrint(eLogInfo, "Clients: starting IRC tunnel"); - localDestination = nullptr; - std::string ircKeys = i2p::util::config::GetArg("-irckeys", ""); - if (ircKeys.length () > 0) - localDestination = LoadLocalDestination (ircKeys, false); - auto ircPort = i2p::util::config::GetArg("-ircport", 6668); - auto ircTunnel = new I2PClientTunnel (ircDestination, i2p::util::config::GetArg("-ircaddress", "127.0.0.1"), ircPort, localDestination); - ircTunnel->Start (); - m_ClientTunnels.insert (std::make_pair(ircPort, std::unique_ptr(ircTunnel))); - } - - // I2P tunnels: local site - std::string eepKeys = i2p::util::config::GetArg("-eepkeys", ""); - if (eepKeys.length () > 0) // eepkeys file is presented - { - LogPrint(eLogInfo, "Clients: starting server tunnel for eepsite"); - localDestination = LoadLocalDestination (eepKeys, true); - auto serverTunnel = new I2PServerTunnel (i2p::util::config::GetArg("-eephost", "127.0.0.1"), - i2p::util::config::GetArg("-eepport", 80), localDestination); - serverTunnel->Start (); - m_ServerTunnels.insert (std::make_pair(localDestination->GetIdentHash (), std::unique_ptr(serverTunnel))); - } + // I2P tunnels ReadTunnels (); // SAM diff --git a/docs/configuration.md b/docs/configuration.md index 5a420b23..04d68779 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -17,14 +17,6 @@ i2pd cmdline options * --socksproxyaddress= - The address to listen on (SOCKS Proxy) * --socksproxyport= - The port to listen on (SOCKS Proxy). 4447 by default * --proxykeys= - optional keys file for proxy's local destination -* --ircaddress= - The address to listen on (IRC tunnel) -* --ircport= - The port listen on (IRC tunnel). 6668 by default -* --ircdest= - I2P destination address of IRC server. For example irc.postman.i2p -* --irckeys= - optional keys file for tunnel's local destination -* --eepkeys= - File name containing destination keys, for example privKeys.dat. - The file will be created if it does not already exist (issue #110). -* --eephost= - Address incoming trafic forward to. 127.0.0.1 by default -* --eepport= - Port incoming trafic forward to. 80 by default * --samaddress= - The address to listen on (SAM bridge) * --samport= - Port of SAM bridge. Usually 7656. SAM is off if not specified * --bobaddress= - The address to listen on (BOB command channel) From fb1d2abbfaa14a5a80b1314f77630648f0ded2db Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 11 Jan 2016 02:51:35 +0000 Subject: [PATCH 0740/6300] * cleanup manpage --- debian/i2pd.1 | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/debian/i2pd.1 b/debian/i2pd.1 index b2e83c83..c6f0e9ba 100644 --- a/debian/i2pd.1 +++ b/debian/i2pd.1 @@ -56,25 +56,7 @@ The local port for the HTTP Proxy to listen on (default: \fI4446\fR) The local port for the SOCKS proxy to listen on (default: \fI4447\fR) .TP \fB\-\-proxykeys=\fR -An optional keys file for tunnel's local destination -.TP -\fB\-\-ircport=\fR -The local port of IRC tunnel to listen on. (default: \fI6668\fR) -.TP -\fB\-\-ircdest=\fR -I2P destination address of an IRC server to connect to, e.g. \fIirc.postman.i2p\fR -.TP -\fB\-\-irckeys=\fR -optional keys file for local destination -.TP -\fB\-\-eepkeys=\fR -File name containing destination keys. For example \fIprivKeys.dat\fR -.TP -\fB\-\-eephost=\fR -Address incoming trafic is forwarded to, \fI127.0.0.1\fR by default -.TP -\fB\-\-eepport=\fR -Port incoming trafic forward to. \fI80\fR by default +An optional keys file for tunnel local destination (both HTTP and SOCKS) .TP \fB\-\-samport=\fR Port of SAM bridge. Usually \fI7656\fR. SAM will not be enabled if this is not set. (default: unset) From 4b7e5864d42316abd129715508e193a157079771 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 11 Jan 2016 02:52:00 +0000 Subject: [PATCH 0741/6300] * cleanup default config file --- debian/i2pd.conf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/debian/i2pd.conf b/debian/i2pd.conf index 69b02ba0..a6e9c23f 100644 --- a/debian/i2pd.conf +++ b/debian/i2pd.conf @@ -4,10 +4,6 @@ v6=0 httpproxyaddress=127.0.0.1 httpproxyport=4444 -ircaddress=127.0.0.1 -ircport=6668 -ircdest=irc.postman.i2p - # other services (disabled by default) # # samaddress=127.0.0.1 From 108c1bcac47e02f70ee0d6b0d23b5fd10992aaaf Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 11 Jan 2016 02:52:16 +0000 Subject: [PATCH 0742/6300] * update docs/configuration.md --- docs/configuration.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 04d68779..a5a69b48 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -16,7 +16,7 @@ i2pd cmdline options * --httpproxyport= - The port to listen on (HTTP Proxy) 4446 by default * --socksproxyaddress= - The address to listen on (SOCKS Proxy) * --socksproxyport= - The port to listen on (SOCKS Proxy). 4447 by default -* --proxykeys= - optional keys file for proxy's local destination +* --proxykeys= - optional keys file for proxy local destination (both HTTP and SOCKS) * --samaddress= - The address to listen on (SAM bridge) * --samport= - Port of SAM bridge. Usually 7656. SAM is off if not specified * --bobaddress= - The address to listen on (BOB command channel) @@ -39,7 +39,6 @@ i2p.conf: log = 1 v6 = 0 - ircdest = irc.postman.i2p tunnels.cfg (filename of this config is subject of change): @@ -54,7 +53,7 @@ tunnels.cfg (filename of this config is subject of change): [IRC] type = client port = 6668 - destination = irc.echelon.i2p + destination = irc.postman.i2p keys = irc-keys.dat # # incoming tunnel sample, for local service From 43eecdbb3fb685bc900d88e74cae24ba3cd05a5c Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 11 Jan 2016 02:55:21 +0000 Subject: [PATCH 0743/6300] * update default tunnels.cfg --- debian/tunnels.conf | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/debian/tunnels.conf b/debian/tunnels.conf index 3cf35552..f710b70c 100644 --- a/debian/tunnels.conf +++ b/debian/tunnels.conf @@ -1 +1,7 @@ -# see examples in /usr/share/doc/i2pd/configuration.md.gz +[IRC] +type = client +port = 6668 +destination = irc.postman.i2p +keys = irc-keys.dat + +# see more examples in /usr/share/doc/i2pd/configuration.md.gz From 1819bd910a9f2f7107db144c34b039f99516634b Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 11 Jan 2016 10:55:18 +0000 Subject: [PATCH 0744/6300] * add log message when fork failed --- DaemonLinux.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index d4b72de0..440c5b96 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -52,7 +52,10 @@ namespace i2p ::exit (EXIT_SUCCESS); if (pid < 0) // error + { + LogPrint(eLogError, "Daemon: could not fork: ", strerror(errno)); return false; + } // child umask(0); From 5d510f1cf421160e8dee50cfc5be822964cc0d78 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 11 Jan 2016 10:55:50 +0000 Subject: [PATCH 0745/6300] * DaemonLinux : set umask to 0027 instead 0000 --- DaemonLinux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 440c5b96..7215eca7 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -58,7 +58,7 @@ namespace i2p } // child - umask(0); + umask(S_IWGRP | S_IRWXO); // 0027 int sid = setsid(); if (sid < 0) { From e4255ed712393db716f2e7e14a1052340ebab35d Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 11 Jan 2016 10:58:39 +0000 Subject: [PATCH 0746/6300] + add --pidfile cmdline option --- Daemon.h | 2 +- DaemonLinux.cpp | 34 ++++++++++++++++++---------------- docs/configuration.md | 1 + 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Daemon.h b/Daemon.h index bfbdcaa0..9af8c1bb 100644 --- a/Daemon.h +++ b/Daemon.h @@ -63,7 +63,7 @@ namespace i2p virtual bool stop(); private: std::string pidfile; - int pidFilehandle; + int pidFH; }; #endif diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 7215eca7..31ec209c 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -78,22 +78,25 @@ namespace i2p } // Pidfile - pidfile = IsService () ? "/var/run" : i2p::util::filesystem::GetDataDir().string(); - pidfile.append("/i2pd.pid"); - pidFilehandle = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600); - if (pidFilehandle == -1) - { - LogPrint(eLogError, "Daemon: could not create pid file ", pidfile, ": ", strerror(errno)); - return false; + // this code is c-styled and a bit ugly, but we need fd for locking pidfile + pidfile = i2p::util::config::GetArg("pidfile", ""); + if (pidfile != "") { + pidFH = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600); + if (pidFH < 0) + { + LogPrint(eLogError, "Daemon: could not create pid file ", pidfile, ": ", strerror(errno)); + return false; + } + if (lockf(pidFH, F_TLOCK, 0) != 0) + { + LogPrint(eLogError, "Daemon: could not lock pid file ", pidfile, ": ", strerror(errno)); + return false; + } + char pid[10]; + sprintf(pid, "%d\n", getpid()); + ftruncate(pidFH, 0); + write(pidFH, pid, strlen(pid)); } - if (lockf(pidFilehandle, F_TLOCK, 0) == -1) - { - LogPrint(eLogError, "Daemon: could not lock pid file ", pidfile, ": ", strerror(errno)); - return false; - } - char pid[10]; - sprintf(pid, "%d\n", getpid()); - write(pidFilehandle, pid, strlen(pid)); // Signal handler struct sigaction sa; @@ -110,7 +113,6 @@ namespace i2p bool DaemonLinux::stop() { - close(pidFilehandle); unlink(pidfile.c_str()); return Daemon_Singleton::stop(); diff --git a/docs/configuration.md b/docs/configuration.md index a5a69b48..57c038de 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -7,6 +7,7 @@ i2pd cmdline options * --httpport= - The port to listen on (HTTP server) * --log= - Enable or disable logging to file. 1 for yes, 0 for no. * --loglevel= - Log messages above this level (debug, *info, warn, error) +* --pidfile= - Where to write pidfile (dont write by default) * --daemon= - Enable or disable daemon mode. 1 for yes, 0 for no. * --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). * --v6= - 1 if supports communication through ipv6, off by default From a3c6ed4dd2ac9b8f3f29d70cfd9473359e07f389 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 11 Jan 2016 11:30:19 +0000 Subject: [PATCH 0747/6300] * fix warnings from -Wunused-result --- DaemonLinux.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 31ec209c..d94ede7f 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -66,7 +66,11 @@ namespace i2p return false; } std::string d(i2p::util::filesystem::GetDataDir().string ()); // make a copy - chdir(d.c_str()); + if (chdir(d.c_str()) != 0) + { + LogPrint(eLogError, "Daemon: could not chdir: ", strerror(errno)); + return false; + } // close stdin/stdout/stderr descriptors ::close (0); @@ -95,7 +99,11 @@ namespace i2p char pid[10]; sprintf(pid, "%d\n", getpid()); ftruncate(pidFH, 0); - write(pidFH, pid, strlen(pid)); + if (write(pidFH, pid, strlen(pid)) < 0) + { + LogPrint(eLogError, "Daemon: could not write pidfile: ", strerror(errno)); + return false; + } } // Signal handler From daaba1dbc00eb8ee8a12726a311d5681d0b2b829 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 11 Jan 2016 11:00:51 +0000 Subject: [PATCH 0748/6300] * prevent zero-division exception when running offline --- NetDb.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NetDb.cpp b/NetDb.cpp index cc50bbc2..d0b6da26 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -126,6 +126,11 @@ namespace data if (ts - lastExploratory >= 30) // exploratory every 30 seconds { auto numRouters = m_RouterInfos.size (); + if (numRouters == 0) + { + LogPrint(eLogError, "NetDb: no known routers, reseed seems to be totally failed"); + break; + } if (numRouters < 2500 || ts - lastExploratory >= 90) { numRouters = 800/numRouters; From b2ae30eba1b101ff09fdb739f3e74850aa97d501 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 11 Jan 2016 11:31:55 +0000 Subject: [PATCH 0749/6300] * fix cmake output library name (#315) --- build/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 748d8264..cbcfc275 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -53,6 +53,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR MSYS) endif () add_library(libi2pd ${LIBI2PD_SRC}) +set_target_properties(libi2pd PROPERTIES OUTPUT_NAME "i2pd") install(TARGETS libi2pd EXPORT libi2pd ARCHIVE DESTINATION lib From 258be402851a96feb019296ab8fa3050b8a8cac1 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 11 Jan 2016 11:08:06 -0500 Subject: [PATCH 0750/6300] notransit parameter added --- Daemon.cpp | 1 + docs/configuration.md | 1 + 2 files changed, 2 insertions(+) diff --git a/Daemon.cpp b/Daemon.cpp index ad61a130..0d2a52c4 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -78,6 +78,7 @@ namespace i2p i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); i2p::context.SetSupportsV6 (i2p::util::config::GetArg("-v6", 0)); + i2p::context.SetAcceptsTunnels (!i2p::util::config::GetArg("-notransit", 0)); bool isFloodfill = i2p::util::config::GetArg("-floodfill", 0); i2p::context.SetFloodfill (isFloodfill); auto bandwidth = i2p::util::config::GetArg("-bandwidth", ""); diff --git a/docs/configuration.md b/docs/configuration.md index 57c038de..a6de600f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -13,6 +13,7 @@ i2pd cmdline options * --v6= - 1 if supports communication through ipv6, off by default * --floodfill= - 1 if router is floodfill, off by default * --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited +* --notransit= - 1 if router doesn't accept transit tunnels at startup. 0 by default * --httpproxyaddress= - The address to listen on (HTTP Proxy) * --httpproxyport= - The port to listen on (HTTP Proxy) 4446 by default * --socksproxyaddress= - The address to listen on (SOCKS Proxy) From 7968279bc2499401ee7acd87e6dfeca2948a8741 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 11 Jan 2016 13:48:18 -0500 Subject: [PATCH 0751/6300] send X-I2P-DestHash --- I2PTunnel.cpp | 8 +++++++- I2PTunnel.h | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index db98c7e4..f6f5806f 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -165,7 +165,7 @@ namespace client I2PTunnelConnectionHTTP::I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& host): - I2PTunnelConnection (owner, stream, socket, target), m_Host (host), m_HeaderSent (false) + I2PTunnelConnection (owner, stream, socket, target), m_Host (host), m_HeaderSent (false), m_From (stream->GetRemoteIdentity ()) { } @@ -193,6 +193,12 @@ namespace client else break; } + // add X-I2P fields + if (m_From) + { + m_OutHeader << X_I2P_DEST_HASH << ": " << m_From->GetIdentHash ().ToBase64 () << "\r\n"; + // m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n"; + } if (endOfHeader) { diff --git a/I2PTunnel.h b/I2PTunnel.h index 0ca3d674..a0716897 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -19,7 +19,10 @@ namespace client const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 8192; const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds - + // for HTTP tunnels + const char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64 + //const char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64 + //const char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // full address in base32 class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this { @@ -74,6 +77,7 @@ namespace client std::string m_Host; std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent; + std::shared_ptr m_From; }; class I2PClientTunnel: public TCPIPAcceptor From 7a84daf3f7183704f8f64a67a8feadee45097885 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 11 Jan 2016 17:37:20 -0500 Subject: [PATCH 0752/6300] temporary disable openssl mutexes --- Crypto.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Crypto.cpp b/Crypto.cpp index 1a868567..8416a337 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -683,7 +683,7 @@ namespace crypto #endif } - std::vector > m_OpenSSLMutexes; +/* std::vector > m_OpenSSLMutexes; static void OpensslLockingCallback(int mode, int type, const char * file, int line) { if (type > 0 && (size_t)type < m_OpenSSLMutexes.size ()) @@ -693,21 +693,21 @@ namespace crypto else m_OpenSSLMutexes[type]->unlock (); } - } + }*/ void InitCrypto () { SSL_library_init (); - auto numLocks = CRYPTO_num_locks(); +/* auto numLocks = CRYPTO_num_locks(); for (int i = 0; i < numLocks; i++) m_OpenSSLMutexes.emplace_back (new std::mutex); - CRYPTO_set_locking_callback (OpensslLockingCallback); + CRYPTO_set_locking_callback (OpensslLockingCallback);*/ } void TerminateCrypto () { - CRYPTO_set_locking_callback (nullptr); - m_OpenSSLMutexes.clear (); +/* CRYPTO_set_locking_callback (nullptr); + m_OpenSSLMutexes.clear ();*/ } } } From 8d4fae24eae60a2a17da6c3d1dc8ed56a28d6139 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 11 Jan 2016 19:03:04 -0500 Subject: [PATCH 0753/6300] fixed misalignment --- NTCPSession.cpp | 8 ++++---- NTCPSession.h | 6 +----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index ededc765..24bc21e5 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -187,8 +187,8 @@ namespace transport memcpy (xy + 256, y, 256); SHA256(xy, 512, m_Establisher->phase2.encrypted.hxy); uint32_t tsB = htobe32 (i2p::util::GetSecondsSinceEpoch ()); - m_Establisher->phase2.encrypted.timestamp = tsB; - // TODO: fill filler + memcpy (m_Establisher->phase2.encrypted.timestamp, &tsB, 4); + RAND_bytes (m_Establisher->phase2.encrypted.filler, 12); i2p::crypto::AESKey aesKey; CreateAESKey (m_Establisher->phase1.pubKey, aesKey); @@ -287,7 +287,7 @@ namespace transport s.Insert (m_Establisher->phase2.pubKey, 256); // y s.Insert (m_RemoteIdentity->GetIdentHash (), 32); // ident s.Insert (tsA); // tsA - s.Insert (m_Establisher->phase2.encrypted.timestamp); // tsB + s.Insert (m_Establisher->phase2.encrypted.timestamp, 4); // tsB s.Sign (keys, buf); m_Encryption.Encrypt(m_ReceiveBuffer, len, m_ReceiveBuffer); @@ -449,7 +449,7 @@ namespace transport s.Insert (m_Establisher->phase2.pubKey, 256); // y s.Insert (i2p::context.GetIdentHash (), 32); // ident s.Insert (tsA); // tsA - s.Insert (m_Establisher->phase2.encrypted.timestamp); // tsB + s.Insert (m_Establisher->phase2.encrypted.timestamp, 4); // tsB if (!s.Verify (m_RemoteIdentity, m_ReceiveBuffer)) { diff --git a/NTCPSession.h b/NTCPSession.h index b8efa532..7e45137e 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -17,8 +17,6 @@ namespace i2p { namespace transport { - -#pragma pack(1) struct NTCPPhase1 { uint8_t pubKey[256]; @@ -31,12 +29,10 @@ namespace transport struct { uint8_t hxy[32]; - uint32_t timestamp; + uint8_t timestamp[4]; uint8_t filler[12]; } encrypted; }; - -#pragma pack() const size_t NTCP_MAX_MESSAGE_SIZE = 16384; const size_t NTCP_BUFFER_SIZE = 4160; // fits 4 tunnel messages (4*1028) From 870e84a700042795baa2c9946162069281129ceb Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 11 Jan 2016 22:05:10 -0500 Subject: [PATCH 0754/6300] new webconsole layout (by nda) --- HTTPServer.cpp | 50 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 82589765..4229acac 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -635,7 +635,20 @@ namespace util s << ""; // TODO: Find something to parse html/template system. This is horrible. s << "Purple I2P " << VERSION " Webconsole"; + s << "' />Purple I2P " << VERSION " Webconsole"; + s << ""; + s << ""; // Head end if (address.length () > 1) HandleCommand (address.substr (2), s); @@ -647,7 +660,11 @@ namespace util void HTTPConnection::FillContent (std::stringstream& s) { - s << "

Welcome to the Webconsole!


"; + s << "
"; + s << "

i2pd webconsole

"; + s << "
"; + s << "
"; + s << "

"; s << "Uptime: " << boost::posix_time::to_simple_string ( boost::posix_time::time_duration (boost::posix_time::seconds ( i2p::context.GetUptime ()))) << "
"; @@ -688,25 +705,22 @@ namespace util } s << address.host.to_string() << ":" << address.port << "
"; } - s << "
Routers: " << i2p::data::netdb.GetNumRouters () << " "; - s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; - s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
"; - - s << "
Local destinations"; - s << "
Tunnels"; - s << "
Transit tunnels"; - s << "
Transports"; + s << "
Routers: " << i2p::data::netdb.GetNumRouters () << " "; + s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; + s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
"; + s << "

"; + s << "

[ Local destinations ]
"; + s << "[ Tunnels ]
"; + s << "[ Transit tunnels ]
"; + s << "[ Transports ]
"; if (i2p::client::context.GetSAMBridge ()) - s << "
SAM sessions"; - s << "
"; - + s << "[ SAM sessions ]

"; if (i2p::context.AcceptsTunnels ()) - s << "
Stop accepting tunnels
"; + s << "[ Stop accepting tunnels ]

"; else - s << "
Start accepting tunnels
"; - s << "
Run peer test
"; - - s << "

Flibusta

"; + s << "[ Start accepting tunnels ]

"; + s << "[ Run peer test ]

"; + s << "
"; } void HTTPConnection::HandleCommand (const std::string& command, std::stringstream& s) From bd4a22405161f6dee14c70224067ad542e13ab36 Mon Sep 17 00:00:00 2001 From: libre-net-society Date: Tue, 12 Jan 2016 07:03:29 +0300 Subject: [PATCH 0755/6300] Added Sphinx documentation files --- .gitignore | 3 + docs/conf.py | 300 +++++++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 28 +++++ 3 files changed, 331 insertions(+) create mode 100644 docs/conf.py create mode 100644 docs/index.rst diff --git a/.gitignore b/.gitignore index 2da26ecd..d680e7ad 100644 --- a/.gitignore +++ b/.gitignore @@ -230,3 +230,6 @@ pip-log.txt #Mr Developer .mr.developer.cfg + +# Sphinx +docs/_build diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..a39b138e --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,300 @@ +# -*- coding: utf-8 -*- +# +# i2pd documentation build configuration file, created by +# sphinx-quickstart on Tue Jan 12 06:26:12 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex +from recommonmark.parser import CommonMarkParser + +source_parsers = { + '.md': CommonMarkParser, +} + +# Check if on RTD +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +#templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = ['.rst', '.md'] + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'i2pd' +copyright = u'2016, PurpleI2P team' +author = u'PurpleI2P team' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'2.2.0' +# The full version, including alpha/beta/rc tags. +release = u'2.2.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +if not on_rtd: + try: + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + except ImportError: + pass + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'i2pddoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'i2pd.tex', u'i2pd Documentation', + u'PurpleI2P team', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'i2pd', u'i2pd Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'i2pd', u'i2pd Documentation', + author, 'i2pd', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..9d041212 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,28 @@ +i2pd +==== + +`Website `_ | +`Github `_ | +`Issues `_ + +i2pd is C++ implementation of `I2P `_. + +Supports: +--------- + +* Complete I2P router functionality +* Floodfill +* HTTP and SOCKS proxy +* I2P client and server tunnels +* SAM and BOB interfaces + +Contents: +--------- + +.. toctree:: + :maxdepth: 2 + + build_notes_unix + build_notes_windows + configuration + From 623edf3bc907ecb0ee33e6c7289bb4e2969c7485 Mon Sep 17 00:00:00 2001 From: libre-net-society Date: Tue, 12 Jan 2016 07:18:17 +0300 Subject: [PATCH 0756/6300] Tuning docs for Sphinx --- docs/build_notes_unix.md | 19 ++----------------- docs/build_notes_windows.md | 2 +- docs/build_requirements.md | 15 +++++++++++++++ docs/configuration.md | 5 ++++- docs/index.rst | 1 + 5 files changed, 23 insertions(+), 19 deletions(-) create mode 100644 docs/build_requirements.md diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index dc652437..145e1a70 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -1,20 +1,5 @@ -Requirements -============ - -Linux/FreeBSD/OSX ------------------ - -GCC 4.6 or newer, Boost 1.46 or newer, openssl, zlib. Clang can be used instead of GCC. - -Windows -------- - -VS2013 (known to work with 12.0.21005.1 or newer), Boost 1.46 or newer, -crypto++ 5.62. See Win32/README-Build.txt for instructions on how to build i2pd -and its dependencies. - -Build notes -=========== +Building on Unix systems +============================= Common build/install process from sources: diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index d1b2f81f..3779eca0 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -1,4 +1,4 @@ -Building i2pd on Windows +Building on Windows ========================= There are two approaches available to build i2pd on Windows. The best diff --git a/docs/build_requirements.md b/docs/build_requirements.md new file mode 100644 index 00000000..3f523a80 --- /dev/null +++ b/docs/build_requirements.md @@ -0,0 +1,15 @@ +Build requirements +============ + +Linux/FreeBSD/OSX +----------------- + +GCC 4.6 or newer, Boost 1.46 or newer, openssl, zlib. Clang can be used instead of GCC. + +Windows +------- + +VS2013 (known to work with 12.0.21005.1 or newer), Boost 1.46 or newer, +crypto++ 5.62. See Win32/README-Build.txt for instructions on how to build i2pd +and its dependencies. + diff --git a/docs/configuration.md b/docs/configuration.md index a6de600f..8eb46ce2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,4 +1,7 @@ -i2pd cmdline options +i2pd configuration +================== + +Command line options -------------------- * --host= - The external IP (deprecated). diff --git a/docs/index.rst b/docs/index.rst index 9d041212..9635d4e4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,6 +22,7 @@ Contents: .. toctree:: :maxdepth: 2 + build_requirements build_notes_unix build_notes_windows configuration From 5eee430be3b41a512d915b0c5bccca9c707e6e12 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 Jan 2016 09:34:46 -0500 Subject: [PATCH 0757/6300] fixed typo --- RouterInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 2f73bf92..53ab7aa3 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -520,7 +520,7 @@ namespace data for (auto it: m_Addresses) // don't insert same address twice if (it == addr) return; m_Addresses.push_back(addr); - m_SupportedTransports |= addr.host.is_v6 () ? eNTCPV6 : eSSUV4; + m_SupportedTransports |= addr.host.is_v6 () ? eSSUV6 : eSSUV4; m_Caps |= eSSUTesting; m_Caps |= eSSUIntroducer; } From 81b2f2114d3ef7a1e066523682526812fe95b634 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 Jan 2016 09:36:01 -0500 Subject: [PATCH 0758/6300] purplei2p webconsole style(by sha-db) --- HTTPServer.cpp | 326 +++---------------------------------------------- 1 file changed, 18 insertions(+), 308 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 4229acac..64501660 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -176,293 +176,7 @@ namespace util const std::string HTTPConnection::itoopieFavicon = "data:image/vnd.microsoft.icon;base64," - "AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAABsAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgQAAAIMAAACEAAAAg" - "wAAAIAAAACCAAAASQAAACUAAAAmAAAAOwAAAFIAAABgAAAAVAAAACcAAAAVAAAADQAAAAcAAAADAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAAAA/wAAAP4AAAD+AAAA/gAAAP4AAAD+A" - "AAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/" - "AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAACbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAP8AA" - "AD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4A" - "AAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/wAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAKQAAAAAAAAAAAAAAAADbAAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP8AAAD/AAAA/gAA" - "AP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAIA/gAAAPwAAAD/AAAA/gAAAP4AAAD+AAAA/gA" - "AAP4AAAD+AAAA/wAAAFkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApAAAAAAAAAAAAHAAAAP8AAAD+AAAA/gAAAPwACwD9ABEA" - "/QASAPgAFgD4ABUA+AATAP0ADQD8AAIA/AAAAP8AAAD+AAAA/gAAAP8ADQD7ACMA/QAlAP8AJwD/ACI" - "A/QATAPwAAAD8AAAA/gAAAP4AAAD+AAAA/gAAAKIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADe" - "AAAA/gAAAPwAJAD+AAwA+wAAAP8AAAD/AAAA/wAEAPwAEgD8ACQA/gAmAP8AHAD8AAEA/gAAAP4ACwD" - "8ACUA/wAkAP8AFwD8AAkA/AAQAP0AIwD+ACQA/wANAPsAAAD+AAAA/gAAAP8AAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAEAAABNAAAA/wAAAP8AGAD8AAAA/QAYAPMARQDyAFEA9wBOAPIAPAD0ABEA8QAAAP8" - "AGgD7ACMA/wAVAP0AAAD9ACMA/QAkAP8ADwD9AAgA+ABBAPUALADyAAAA/AAUAPwAJgD/ABgA/AAAAP" - "0AAAD/AAAAdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAP4ADQD8AAgA/wAqAPAAcAD/AGoA/wB" - "qAP8AagD/AGoA/wBvAP8ATwDvAAAA/QAbAP0AEwD8AAYA/QAkAP8AGAD8AAsA9wBuAPgAagD/AGsA/w" - "BjAPkADAD3AAQA/QAiAP4AGgD9AAAA/gAAAP8AAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZgAAAP8AAAD+ABY" - "A+wAQAPQAcQD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBTAPEAAAD/AAcA/AAMAPwAIQD9AA" - "AA+gBoAPgAagD/AGoA/wBqAP8AagD/AHAA/gAuAO0AAAD/AB8A/QAPAPwAAAD+AAAA+AAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAANIAAAD+AAwA+QAAAP0AZQD1AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8Abg" - "D/ABcA8QAAAP4ACAD9AAAA+QBeAPkAagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AbQD/ADkA7wAAAP8AJ" - "AD9AAAA/gAAAP8AAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAALi8AAAAAAAAAAAAAAAD/AAAA/QAAAP8AKwD1AGsA/wBqAP8AagD/AGoA/wBqAP" - "8AagD/AGoA/wBqAP8AagD/AGoA/wA9APUAAAD/AAAA/wBKAPMAagD/AGoA/wBqAP8AagD/AGoA/wBqA" - "P8AagD/AGoA/wBxAP8ADgD1ABMA/AAHAPsAAAD/AAAARQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzPAIAAAAgAAAA/wAAAPwAAAD/AGQA9A" - "BqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8ASQD1AAAA/wASAPMAcAD/AGoA/" - "wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AawD/ACsA+QAOAP4ADQD8AAAA/wAAAEUAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7gAAAAAAAADgwAQAYLwAA//8AAA" - "ICIwAAAP8AAAD+AAYA8wBwAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/A" - "EIA9QAAAP8AMADvAGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGsA/wApAP0AEQD8" - "AAIA/QAAAP8AAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAVgEAAA" - "AAACJ5AQA2ygAAND8BAHV+AgAAAAAAAAH/AAAA/gAcAPQAbAD/AGoA/wBqAP8AagD/AGoA/wBqAP8Aa" - "gD/AGoA/wBqAP8AagD/AGsA/wApAPYAAAD+AB4A+gBsAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8A" - "agD/AGoA/wBrAP8ALADxAAAA/AAAAP4AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAABYAhQAfAE8ABgBWAQAAAAAAIoABAAAAAAA5UgEAAAAAAAAA+AAAAP4AJQD2AGsA/wBqA" - "P8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBsAPgAAAD6AAAA/gANAPAAbgD/AGoA/wBq" - "AP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/ADsA+QAAAP8AAAD+AAAA4AAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtQAAAAAAAABYABgAZgEGAIMBAAAAAAA+fgEAAAAAAAAAA" - "AACA8wAAAH+ACkA9wBgAPgARQDwADoA9gA5APYASgD0AHAA9wBrAP8AagD/AGoA/wBxAP0AFQD0AAAA" - "/gAAAP4AAAD5AHMA/QBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBvAP0ATgDxAEwA9AA1AOkAAAD/AAA" - "A/gAAAP8AAAB3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAvAAzAKcBAADXAAAAAAAcAFYBF" - "gB4ASAAZwEAXT8BADpdAgAAAAAAAQG+AAEB/gAAAf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAPsAJAD1" - "AEQA9ABEAPMACQD3AAAA/wAAAP4AAAD+AAAA/wAiAPMASAD4AE8A9gBuAPgAbQD/AGoA/wBvAP8AFQD" - "yAAAA/wAAAP8AAAD/AAAA/gAAAP4AAAD+AAAA/wAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "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" - "f7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf8/wCq5v4AERj+AAEA/gAAAP4AAAD+A" - "AAA/gAAAP4AAAD/AAAAdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOgAAAD+AAAA/gAAAP4Aeqf9ALz//wC3+/8At/" - "v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At" - "/r/ALz//gAcJvwAAQH+AAAA/gAAAP4AAAD/AAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI0AAAD/AAAA/gAAAP" - "4Ac579ALn+/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+" - "/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Avf/+ABcg/QAAAf4AAAD+AAAA/wAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAABIAAAD/AAAA/gAAAP4AP1X9AL7//wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/" - "wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wCy8v4AAAD+AAAA" - "/gAAAP8AAACKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAA/QAAAP4ABAb9AL7//wC3+/8At/v/ALf7/wC3+/8At/v/A" - "Lf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/" - "ALf7/wC3+/8Atvr/AHmm/QAAAP4AAAD+AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAA/wAAAP4AAAD+AHWh/QC2+v8At" - "/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8A" - "t/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wDD//8AGSP9AAAA/gAAAP8AAABuAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbQAAA" - "P8AAAD+AAUH/QDE//8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3" - "+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Atvv/AH+s/gA" - "AAP4AAAD+AAAA2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAALMAAAD+AAAA/gBZd/0At/z/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7" - "/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf" - "7/wC3+/8At/v/ALf7/wDF//8AAAD9AAAA/gAAAP8AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA/gAAAP4Al9D9ALf7/wC3+/8At/v/" - "ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v" - "/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Auf7/AERe/QAAAP4AAAD/AAAAUQAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP4A" - "AAD9AMb//gC3+/8At/v/ALf7/wC3+/8Auf7/AMb//gC2+f4AwP/+AMT//wC4/f8At/v/ALf7/wC3+/8" - "At/v/ALf7/wC3+/8At/v/ALf7/wC8//8Avf//ALn+/wC3+/8At/v/ALf7/wC3+/8At/v/ALb7/wBvlf" - "4AAAD+AAAA/gAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAACgAAAP8AAAD+ABgg/QDA//8At/v/ALf7/wC2+v8AuPz+AEVe/QAAAP0AAAD+AAAA/gA" - "EBv0AU2/9ALz//wC3+/8At/v/ALf7/wC3+/8Atvr/AL7//wBmi/0ALj/9ACQx/QBJZP0Ai739AMD//w" - "C3+/8At/v/ALf7/wC3+/8Aksj+AAAA/gAAAP4AAACZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcAAAD/AAAA/gAtPf4AvP//ALf7/wC3+/8As/T+AAo" - "O/QAAAP0SEhLtAAAA/gAAAP4AAAD+AAAA/gAWH/0Av///ALf7/wC3+/8At/v/ALHx/gANEv0AAAD+AA" - "AA/gAAAP4AAAD/AAAA/wAuP/0Avv//ALf7/wC3+/8At/v/AKjm/QAAAP4AAAD+AAAApwAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAAAA/wAAAP4AL0H" - "+ALz//wC3+/8AvP//ADJE/QAAAPfc3Nz1TExM6wAAAP4AAAD+AAAA/gAAAP8AAAD+AGeN/gC3+/8At/" - "v/AML//wAeKf0AAAH/AAAA/gAAAP4AAAD+cXFx3YSEhOoAAAD9AEVe/QC6//8At/v/ALf7/wCs7P8AA" - "AD+AAAA/gAAAKEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAP8AAAD+ACAt/QC+//8At/v/ALP1/gAAAP95eXns/////2FhYeoBAQH/AAAA/gEBAf" - "9eXl7lDQ0N9gAgLP0Av///ALf7/wCNwf4AAAD/NDQ03AAAAP8AAAD+AAAA/6Ojo+b/////eXl55AAAA" - "P8AtPf+ALf7/wC3+/8AoNv9AAAA/gAAAP4AAACHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/gAAAP0Ax///ALf7/wCIu/0AAAD/ysrK6/" - "/////8/Pz6Hx8f6wAAAP8kJCTm/f39/WVlZe4ABAX8AMT//wC3/P8AV3X9ERER7/////9MTEzrAAAA6" - "ElJSeb///////////X19e8AAAD/AHuo/QC3+/8At/v/AIW2/gAAAP4AAAD/AAAAZAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0wAAAP4AAAD+AK" - "fl/gC3+/8AjcH+AAAA/83Nze/////////////////////w//////////9gYGDsAA8V/QDC//8Auv//A" - "EJa/UFBQeb////////////////////////////////////vAAAA/wBvlfwAt/v/ALf7/wBdff0AAAD+" - "AAAA/wAAAC0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAI4AAAD+AAAA/gBnjP0At/v/ALz//gAAAP+CgoLx/////////////////////////////" - "///CAkJ8QBCXP4Auf7/ALj8/wBScf0DAwPw////////////////////////////////tra26AAAAP8A" - "hrf+ALf7/wDA//8AHCf9AAAA/gAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AAAA/wAAAP4AEBb9AMT//wC6//8AQVn+AAAA9+Pj4" - "/L/////////////////////cnJy1gAAAP8AlMr9ALf7/wC3+/8AhLT+AAAA/5mZmeL/////////////" - "/////////////R0dHesAAAD9AL///gC3+/8ApuX+AAAA/gAAAP4AAAC1AAAAAAAAAAAAAAAAAAAAAAA" - "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" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIMAAAD/AAAA/gAAAP4AAAD9AFl3/QCt7/4AwP//ALj9/w" - "C2+/8At/v/ALf7/wC3+/8At/v/ALz//wDB//4AeKP+ABki/QAAAP4AAAD+AAAA/wAAAOEAAAASAAAAA" - "QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAOUAAAD/AA" - "AA/gAAAP4AAAD+ABcg/QBQbv0AbJD9AH2s/gCBsP0Ac5v+AFt6/QAtPv0AAAD9AAAA/gAAAP4AAAD+A" - "AAA/wAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAWgAAAOIAAAD/AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AA" - "AD+AAAA/gAAAP8AAAD/AAAAmQAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAIgAAADRAAAA/wAAA" - "P8AAAD/AAAA/wAAAP8AAAD/AAAA5QAAAJoAAABWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAIAAAACQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///gAf///////4AAAAD/////wAAAAP////" - "/AAAAB/////+AAAAH/////4AAAA//////gAAAD/////8AAAAH/////wAAAAP////+AAAAA/////4AAA" - "AD/////gAAAAP////+AAAAA/////4AAAAD/////gAAAAP////+AAAAA/////4AAAAB/////gAAAAD//" - "//+AAAAAP////wAAAAA////+AAAAAD////wAAAAAP///8AAAAAA////gAAAAAB///8AAAAAAH///gAA" - "AAAAf//+AAAAAAA///4AAAAAAD///4AAAAAAP///wAAAAAAf///wAAAAAD////gAAAAAP////gAAAAB" - "/////AAAAAP////+AAAAD/////wAAAAf////+AAAAB/////4AAAAD/////AAAAAP////8AAAAA/////" - "wAAAAB////+AAAAAH////4AAAAAf////gAAAAA////+AAAAAD////4AAAAAP////gAAAAA////+AAAA" - "AD////4AAAAAf////gAAAAB////+AAAAAH////8AAAAAf////wAAAAD/////gAAAAP////+AAAAB///" - "//8AAAAH/////wAAAA//////gAAAH//////AAAA//////+AAAH//////+AAA///////+AAP///////+" - "AH//////////////8="; + ""; const char HTTP_COMMAND_TUNNELS[] = "tunnels"; const char HTTP_COMMAND_TRANSIT_TUNNELS[] = "transit_tunnels"; @@ -632,21 +346,18 @@ namespace util std::stringstream s; // Html5 head start s << "\n"; // TODO: Add support for locale. - s << ""; // TODO: Find something to parse html/template system. This is horrible. - s << "Purple I2P " << VERSION " Webconsole"; + s << ""; // TODO: Find something to parse html/template system. This is horrible. + // s << ""; + s << "Purple I2P " << VERSION " Webconsole"; s << ""; s << ""; // Head end @@ -660,11 +371,9 @@ namespace util void HTTPConnection::FillContent (std::stringstream& s) { - s << "
"; - s << "

i2pd webconsole

"; - s << "
"; + s << "
i2pd webconsole
"; s << "
"; - s << "

"; + s << "

"; s << "Uptime: " << boost::posix_time::to_simple_string ( boost::posix_time::time_duration (boost::posix_time::seconds ( i2p::context.GetUptime ()))) << "
"; @@ -708,8 +417,8 @@ namespace util s << "
Routers: " << i2p::data::netdb.GetNumRouters () << " "; s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
"; - s << "
"; - s << "

[ Local destinations ]
"; + s << "

"; + s << "[ Local destinations ]
"; s << "[ Tunnels ]
"; s << "[ Transit tunnels ]
"; s << "[ Transports ]
"; @@ -719,7 +428,7 @@ namespace util s << "[ Stop accepting tunnels ]

"; else s << "[ Start accepting tunnels ]

"; - s << "[ Run peer test ]

"; + s << "[ Run peer test ]

"; s << "
"; } @@ -1140,3 +849,4 @@ namespace util } } + From 7f08bbe938265a2f55da59877b28a70dfb1771dc Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 Jan 2016 10:00:17 -0500 Subject: [PATCH 0759/6300] handle -pidfile parameter correctly --- DaemonLinux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index d94ede7f..b4fd7815 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -83,7 +83,7 @@ namespace i2p // Pidfile // this code is c-styled and a bit ugly, but we need fd for locking pidfile - pidfile = i2p::util::config::GetArg("pidfile", ""); + pidfile = i2p::util::config::GetArg("-pidfile", ""); if (pidfile != "") { pidFH = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600); if (pidFH < 0) From 248ae7d4d5a8a81a12d286448c6ec7f1956c3e31 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 Jan 2016 10:12:55 -0500 Subject: [PATCH 0760/6300] do nothing upon SIGHUP for now --- DaemonLinux.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index b4fd7815..20cad65e 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -17,17 +17,8 @@ void handle_signal(int sig) switch (sig) { case SIGHUP: - if (i2p::util::config::GetArg("daemon", 0) == 1) - { - static bool first=true; - if (first) - { - first=false; - return; - } - } - LogPrint(eLogInfo, "Daemon: Got SIGHUP, reloading config."); - i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs); + LogPrint(eLogInfo, "Daemon: Got SIGHUP, doing nothing"); + // TODO: break; case SIGABRT: case SIGTERM: From cae9ccfda169b090246eb18ffefecec99f8d3ca6 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 Jan 2016 10:14:22 -0500 Subject: [PATCH 0761/6300] version 2.3.0 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index c597c65b..8b93f1af 100644 --- a/version.h +++ b/version.h @@ -7,7 +7,7 @@ #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 2 +#define I2PD_VERSION_MINOR 3 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) From e2ff49825f969c02e75d60af4167b3a5989a3ff0 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 Jan 2016 13:18:01 -0500 Subject: [PATCH 0762/6300] favicon added --- HTTPServer.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 64501660..f1c91793 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -175,8 +175,19 @@ namespace util "+mHGvzKrRS0R/yqsZq++6BRshpPMUDQcfzHFrIsqZHhWqasAtHc6b/D3cbSAuGcmWdAAAAAElFTkSuQmCC\" />"; const std::string HTTPConnection::itoopieFavicon = - "data:image/vnd.microsoft.icon;base64," - ""; + "data:image/png;base64," + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv" + "8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4wOGVynO" + "EAAAIzSURBVDhPjZNdSFNhGMf3nm3n7OzMs+8JtfJGzdlgoPtoWBrkqc1OsLTMKEY3eZOQbbS6aBVYO" + "oO8CKSLXEulQtZNahAM9Cq6lS533UUaeDEEKcN/79x7kbQT/eDhfPB7/u/7Poej08JqtXoEQbhoMpmG" + "ZFn2stf/h8nEZ4aHue1SiWBlhSCV4n41NBifBINBjina8DyfzOUIVlcJtrYINjcJ3rw1oFAg4HnjHaZ" + "p4/Ppv8zPH0G5XKZNPZibO4lKpYJ8vgOqqv+uKMq/d9Hfz/0sFr3w+/3IZt2YnbWhszOAxUUv0mkCs9" + "ncyNT6hEL6dYBgY4Ngd5eger+zU7sODHA/mpubzUytj9FofLa0VGv4s9bWCCTJUGSaNvSzXT3stuHDM" + "rc3xEqF4N2CERciURyyHfgqSZKPqfuxUMyC+OKcL4YHyl28nDFAPdqDZMcQ7tPnSfURUt0jMBgMH1nL" + "fkRRDPvcLds3otfhbRTwasaE8b6He43VSrT3QW3tBT3iPdbyN3T7Ibsor988H8OxtiaMx2sB1aBbCRW" + "R1hbQhbqYXh+6QkaJn8DZyzF09x6HeiaOTC6NK9cSsFqkb3aH3cLU+tCAx9l8FoXPBUy9n8LgyCCmS9" + "MYez0Gm9P2iWna0GOcDp8KY2JhAsnbSQS6Ahh9OgrlklINeM40bWhAkBd4SLIEh8cBURLhOeiBIArVA" + "U4yTRvJItk5PRehQVFaYfpbt9PBtTmdziaXyyUzjaHT/QZBQuKHAA0UxAAAAABJRU5ErkJggg=="; const char HTTP_COMMAND_TUNNELS[] = "tunnels"; const char HTTP_COMMAND_TRANSIT_TUNNELS[] = "transit_tunnels"; @@ -347,12 +358,12 @@ namespace util // Html5 head start s << "\n"; // TODO: Add support for locale. s << ""; // TODO: Find something to parse html/template system. This is horrible. - // s << ""; + s << ""; s << "Purple I2P " << VERSION " Webconsole"; s << ""; - s << ""; - // Head end + s << ""; + s << "
"; + s << "

i2pd webconsole

"; + s << "
"; + s << "
"; + s << "
"; + s << "Main page

"; + s << "Local destinations
"; + s << "Tunnels
"; + s << "Transit tunnels
"; + s << "Transports

"; + if (i2p::client::context.GetSAMBridge ()) + s << "SAM sessions

"; + if (i2p::context.AcceptsTunnels ()) + s << "Stop accepting tunnels

"; + else + s << "Start accepting tunnels

"; + s << "Run peer test

"; + s << "
"; if (address.length () > 1) HandleCommand (address.substr (2), s); else FillContent (s); - s << ""; + s << "
"; SendReply (s.str ()); } void HTTPConnection::FillContent (std::stringstream& s) { - s << "
i2pd webconsole
"; - s << "
"; - s << "
"; s << "Uptime: " << boost::posix_time::to_simple_string ( boost::posix_time::time_duration (boost::posix_time::seconds ( i2p::context.GetUptime ()))) << "
"; @@ -428,19 +446,6 @@ namespace util s << "
Routers: " << i2p::data::netdb.GetNumRouters () << " "; s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
"; - s << "
"; - s << "[ Local destinations ]
"; - s << "[ Tunnels ]
"; - s << "[ Transit tunnels ]
"; - s << "[ Transports ]
"; - if (i2p::client::context.GetSAMBridge ()) - s << "[ SAM sessions ]

"; - if (i2p::context.AcceptsTunnels ()) - s << "[ Stop accepting tunnels ]

"; - else - s << "[ Start accepting tunnels ]

"; - s << "[ Run peer test ]

"; - s << "
"; } void HTTPConnection::HandleCommand (const std::string& command, std::stringstream& s) @@ -479,100 +484,9 @@ namespace util } } - void HTTPConnection::ShowTransports (std::stringstream& s) - { - auto ntcpServer = i2p::transport::transports.GetNTCPServer (); - if (ntcpServer) - { - s << "NTCP
"; - for (auto it: ntcpServer->GetNTCPSessions ()) - { - if (it.second && it.second->IsEstablished ()) - { - // incoming connection doesn't have remote RI - if (it.second->IsOutgoing ()) s << "-->"; - s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " - << it.second->GetSocket ().remote_endpoint().address ().to_string (); - if (!it.second->IsOutgoing ()) s << "-->"; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - s << "
"; - } - s << std::endl; - } - } - auto ssuServer = i2p::transport::transports.GetSSUServer (); - if (ssuServer) - { - s << "
SSU
"; - for (auto it: ssuServer->GetSessions ()) - { - auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second->IsOutgoing ()) s << "-->"; - s << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it.second->IsOutgoing ()) s << "-->"; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - if (it.second->GetRelayTag ()) - s << " [itag:" << it.second->GetRelayTag () << "]"; - s << "
" << std::endl; - } - s << "
SSU6
"; - for (auto it: ssuServer->GetSessionsV6 ()) - { - auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second->IsOutgoing ()) s << "-->"; - s << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it.second->IsOutgoing ()) s << "-->"; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - s << "
" << std::endl; - } - } - } - - void HTTPConnection::ShowTunnels (std::stringstream& s) - { - s << "Queue size:" << i2p::tunnel::tunnels.GetQueueSize () << "
"; - - for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << " " << (int)it->GetNumSentBytes () << "
"; - s << std::endl; - } - - for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ()) - { - it.second->Print (s); - auto state = it.second->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << " " << (int)it.second->GetNumReceivedBytes () << "
"; - s << std::endl; - } - } - - void HTTPConnection::ShowTransitTunnels (std::stringstream& s) - { - for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ()) - { - if (dynamic_cast(it.second)) - s << it.second->GetTunnelID () << "-->"; - else if (dynamic_cast(it.second)) - s << "-->" << it.second->GetTunnelID (); - else - s << "-->" << it.second->GetTunnelID () << "-->"; - s << " " << it.second->GetNumTransmittedBytes () << "
"; - } - } - void HTTPConnection::ShowLocalDestinations (std::stringstream& s) { + s << "Local Destinations:

"; for (auto& it: i2p::client::context.GetDestinations ()) { auto ident = it.second->GetIdentHash ();; @@ -580,10 +494,11 @@ namespace util s << "&" << HTTP_PARAM_BASE32_ADDRESS << "=" << ident.ToBase32 () << ">"; s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
" << std::endl; } - } + } void HTTPConnection::ShowLocalDestination (const std::string& b32, std::stringstream& s) { + s << "Local Destination:

"; i2p::data::IdentHash ident; ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); @@ -629,10 +544,104 @@ namespace util s << "
"<< std::endl; } } + } + + void HTTPConnection::ShowTunnels (std::stringstream& s) + { + s << "Tunnels:

"; + s << "Queue size:" << i2p::tunnel::tunnels.GetQueueSize () << "
"; + for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) + { + it->Print (s); + auto state = it->GetState (); + if (state == i2p::tunnel::eTunnelStateFailed) + s << " " << "Failed"; + else if (state == i2p::tunnel::eTunnelStateExpiring) + s << " " << "Exp"; + s << " " << (int)it->GetNumSentBytes () << "
"; + s << std::endl; + } + + for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ()) + { + it.second->Print (s); + auto state = it.second->GetState (); + if (state == i2p::tunnel::eTunnelStateFailed) + s << " " << "Failed"; + else if (state == i2p::tunnel::eTunnelStateExpiring) + s << " " << "Exp"; + s << " " << (int)it.second->GetNumReceivedBytes () << "
"; + s << std::endl; + } } + void HTTPConnection::ShowTransitTunnels (std::stringstream& s) + { + s << "Transit tunnels:

"; + for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ()) + { + if (dynamic_cast(it.second)) + s << it.second->GetTunnelID () << "-->"; + else if (dynamic_cast(it.second)) + s << "-->" << it.second->GetTunnelID (); + else + s << "-->" << it.second->GetTunnelID () << "-->"; + s << " " << it.second->GetNumTransmittedBytes () << "
"; + } + } + + void HTTPConnection::ShowTransports (std::stringstream& s) + { + s << "Transports:

"; + auto ntcpServer = i2p::transport::transports.GetNTCPServer (); + if (ntcpServer) + { + s << "NTCP
"; + for (auto it: ntcpServer->GetNTCPSessions ()) + { + if (it.second && it.second->IsEstablished ()) + { + // incoming connection doesn't have remote RI + if (it.second->IsOutgoing ()) s << "-->"; + s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " + << it.second->GetSocket ().remote_endpoint().address ().to_string (); + if (!it.second->IsOutgoing ()) s << "-->"; + s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + } + s << "
" << std::endl; + } + } + auto ssuServer = i2p::transport::transports.GetSSUServer (); + if (ssuServer) + { + s << "
SSU
"; + for (auto it: ssuServer->GetSessions ()) + { + auto endpoint = it.second->GetRemoteEndpoint (); + if (it.second->IsOutgoing ()) s << "-->"; + s << endpoint.address ().to_string () << ":" << endpoint.port (); + if (!it.second->IsOutgoing ()) s << "-->"; + s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + if (it.second->GetRelayTag ()) + s << " [itag:" << it.second->GetRelayTag () << "]"; + s << "
" << std::endl; + } + s << "
SSU6
"; + for (auto it: ssuServer->GetSessionsV6 ()) + { + auto endpoint = it.second->GetRemoteEndpoint (); + if (it.second->IsOutgoing ()) s << "-->"; + s << endpoint.address ().to_string () << ":" << endpoint.port (); + if (!it.second->IsOutgoing ()) s << "-->"; + s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + s << "
" << std::endl; + } + } + } + void HTTPConnection::ShowSAMSessions (std::stringstream& s) { + s << "SAM Sessions:

"; auto sam = i2p::client::context.GetSAMBridge (); if (sam) { @@ -647,6 +656,7 @@ namespace util void HTTPConnection::ShowSAMSession (const std::string& id, std::stringstream& s) { + s << "SAM Session:

"; auto sam = i2p::client::context.GetSAMBridge (); if (sam) { @@ -680,23 +690,26 @@ namespace util } } } - - void HTTPConnection::StartAcceptingTunnels (std::stringstream& s) - { - i2p::context.SetAcceptsTunnels (true); - s << "Accepting tunnels started" << std::endl; - } void HTTPConnection::StopAcceptingTunnels (std::stringstream& s) { + s << "Stop Accepting Tunnels:

"; i2p::context.SetAcceptsTunnels (false); s << "Accepting tunnels stopped" << std::endl; } + void HTTPConnection::StartAcceptingTunnels (std::stringstream& s) + { + s << "Start Accepting Tunnels:

"; + i2p::context.SetAcceptsTunnels (true); + s << "Accepting tunnels started" << std::endl; + } + void HTTPConnection::RunPeerTest (std::stringstream& s) { + s << "Run Peer Test:

"; i2p::transport::transports.PeerTest (); - s << "Peer test" << std::endl; + s << "Peer test is running" << std::endl; } void HTTPConnection::HandleDestinationRequest (const std::string& address, const std::string& uri) From b152bb26e38147ba3be74da5d137a803d6eac4b0 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 13 Jan 2016 09:42:06 -0500 Subject: [PATCH 0767/6300] more parameters --- docs/configuration.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 8eb46ce2..150101d4 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -55,8 +55,11 @@ tunnels.cfg (filename of this config is subject of change): # optional parameters (may be omitted) # * keys -- our identity, if unset, will be generated on every startup, # if set and file missing, keys will be generated and placed to this file + # * address -- local interface to bind + # * signaturetype -- signature type for new destination. 0,1 or 7 [IRC] type = client + address = 127.0.0.1 port = 6668 destination = irc.postman.i2p keys = irc-keys.dat From 8fa053f7c7170b52eea45724fd44ffc7686a76e5 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 13 Jan 2016 20:21:53 -0500 Subject: [PATCH 0768/6300] show I2P tunnels at web console --- ClientContext.cpp | 6 ++++-- ClientContext.h | 2 ++ HTTPServer.cpp | 28 ++++++++++++++++++++++++++++ HTTPServer.h | 1 + I2PTunnel.cpp | 19 ++++++++++++------- I2PTunnel.h | 18 ++++++++++++------ 6 files changed, 59 insertions(+), 15 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 95d2d0a1..dacba5cd 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -261,7 +261,7 @@ namespace client std::shared_ptr localDestination = nullptr; if (keys.length () > 0) localDestination = LoadLocalDestination (keys, false, sigType); - auto clientTunnel = new I2PClientTunnel (dest, address, port, localDestination, destinationPort); + auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); if (m_ClientTunnels.insert (std::make_pair (port, std::unique_ptr(clientTunnel))).second) clientTunnel->Start (); else @@ -280,7 +280,9 @@ namespace client i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); auto localDestination = LoadLocalDestination (keys, true, sigType); - I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ? new I2PServerTunnelHTTP (host, port, localDestination, inPort) : new I2PServerTunnel (host, port, localDestination, inPort); + I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ? + new I2PServerTunnelHTTP (name, host, port, localDestination, inPort) : + new I2PServerTunnel (name, host, port, localDestination, inPort); if (accessList.length () > 0) { std::set idents; diff --git a/ClientContext.h b/ClientContext.h index 44e3f5b9..4495d505 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -78,6 +78,8 @@ namespace client public: // for HTTP const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; }; + const decltype(m_ClientTunnels)& GetClientTunnels () const { return m_ClientTunnels; }; + const decltype(m_ServerTunnels)& GetServerTunnels () const { return m_ServerTunnels; }; }; extern ClientContext context; diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 40edd5c7..e6099053 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -201,6 +201,7 @@ namespace util const char HTTP_COMMAND_SAM_SESSIONS[] = "sam_sessions"; const char HTTP_COMMAND_SAM_SESSION[] = "sam_session"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; + const char HTTP_COMMAND_I2P_TUNNELS[] = "i2p_tunnels"; namespace misc_strings { @@ -385,6 +386,7 @@ namespace util s << "Tunnels
"; s << "Transit tunnels
"; s << "Transports

"; + s << "I2P tunnels
"; if (i2p::client::context.GetSAMBridge ()) s << "SAM sessions

"; if (i2p::context.AcceptsTunnels ()) @@ -482,6 +484,8 @@ namespace util auto id = params[HTTP_PARAM_SAM_SESSION_ID]; ShowSAMSession (id, s); } + else if (cmd == HTTP_COMMAND_I2P_TUNNELS) + ShowI2PTunnels (s); } void HTTPConnection::ShowLocalDestinations (std::stringstream& s) @@ -691,6 +695,30 @@ namespace util } } + void HTTPConnection::ShowI2PTunnels (std::stringstream& s) + { + s << "Client Tunnels:

"; + for (auto& it: i2p::client::context.GetClientTunnels ()) + { + s << it.second->GetName () << "<--"; + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << ""; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
"<< std::endl; + } + s << "
Server Tunnels:

"; + for (auto& it: i2p::client::context.GetServerTunnels ()) + { + s << it.second->GetName () << "-->"; + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << ""; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
"<< std::endl; + } + } + void HTTPConnection::StopAcceptingTunnels (std::stringstream& s) { s << "Stop Accepting Tunnels:

"; diff --git a/HTTPServer.h b/HTTPServer.h index b4349c5b..702e3191 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -69,6 +69,7 @@ namespace util void ShowLocalDestination (const std::string& b32, std::stringstream& s); void ShowSAMSessions (std::stringstream& s); void ShowSAMSession (const std::string& id, std::stringstream& s); + void ShowI2PTunnels (std::stringstream& s); void StartAcceptingTunnels (std::stringstream& s); void StopAcceptingTunnels (std::stringstream& s); void RunPeerTest (std::stringstream& s); diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index f6f5806f..a281334f 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -262,8 +262,12 @@ namespace client Done(shared_from_this()); } - I2PClientTunnel::I2PClientTunnel (const std::string& destination, const std::string& address, int port, std::shared_ptr localDestination, int destinationPort): - TCPIPAcceptor (address, port, localDestination), m_Destination (destination), m_DestinationIdentHash (nullptr), m_DestinationPort (destinationPort) {} + I2PClientTunnel::I2PClientTunnel (const std::string& name, const std::string& destination, + const std::string& address, int port, std::shared_ptr localDestination, int destinationPort): + TCPIPAcceptor (address, port, localDestination), m_Name (name), m_Destination (destination), + m_DestinationIdentHash (nullptr), m_DestinationPort (destinationPort) + { + } void I2PClientTunnel::Start () { @@ -302,9 +306,9 @@ namespace client return nullptr; } - I2PServerTunnel::I2PServerTunnel (const std::string& address, int port, - std::shared_ptr localDestination, int inport): - I2PService (localDestination), m_Address (address), m_Port (port), m_IsAccessList (false) + I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address, + int port, std::shared_ptr localDestination, int inport): + I2PService (localDestination), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false) { m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port); } @@ -392,8 +396,9 @@ namespace client conn->Connect (); } - I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& address, int port, std::shared_ptr localDestination, int inport): - I2PServerTunnel (address, port, localDestination, inport) + I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& name, const std::string& address, + int port, std::shared_ptr localDestination, int inport): + I2PServerTunnel (name, address, port, localDestination, inport) { } diff --git a/I2PTunnel.h b/I2PTunnel.h index a0716897..146466c5 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -86,21 +86,25 @@ namespace client // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); - const char* GetName() { return "I2P Client Tunnel"; } public: - I2PClientTunnel (const std::string& destination, const std::string& address, int port, std::shared_ptr localDestination, int destinationPort = 0); + I2PClientTunnel (const std::string& name, const std::string& destination, + const std::string& address, int port, std::shared_ptr localDestination, int destinationPort = 0); ~I2PClientTunnel () {} void Start (); void Stop (); + const char* GetName() { return m_Name.c_str (); } + private: const i2p::data::IdentHash * GetIdentHash (); - std::string m_Destination; + private: + + std::string m_Name, m_Destination; const i2p::data::IdentHash * m_DestinationIdentHash; int m_DestinationPort; }; @@ -109,7 +113,7 @@ namespace client { public: - I2PServerTunnel (const std::string& address, int port, + I2PServerTunnel (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, int inport = 0); void Start (); @@ -121,6 +125,8 @@ namespace client int GetPort () const { return m_Port; }; const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; } + const char* GetName() { return m_Name.c_str (); } + private: void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, @@ -132,7 +138,7 @@ namespace client private: - std::string m_Address; + std::string m_Name, m_Address; int m_Port; boost::asio::ip::tcp::endpoint m_Endpoint; std::shared_ptr m_PortDestination; @@ -144,7 +150,7 @@ namespace client { public: - I2PServerTunnelHTTP (const std::string& address, int port, + I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, int inport = 0); private: From c533bfc83dfc9dff95fdb254995004df1ef67a6f Mon Sep 17 00:00:00 2001 From: scrrrapy Date: Thu, 14 Jan 2016 01:16:18 +0000 Subject: [PATCH 0769/6300] integration with travis-ci --- .travis.yml | 31 +++++++++++++++++++++++++++++++ README.md | 8 ++++---- 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..ff2d4958 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,31 @@ +language: cpp +cache: + apt: true +os: + - linux +sudo: required +dist: trusty +addons: + apt: + packages: + - build-essential + - cmake + - g++ + - clang + - libboost-chrono-dev + - libboost-date-time-dev + - libboost-filesystem-dev + - libboost-program-options-dev + - libboost-regex-dev + - libboost-system-dev + - libboost-thread-dev + - libminiupnpc-dev + - libssl-dev +compiler: + - gcc +env: + matrix: + - BUILD_TYPE=Release UPNP=ON + - BUILD_TYPE=Release UPNP=OFF +script: + - cd build && cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DWITH_UPNP=${UPNP} && make diff --git a/README.md b/README.md index 221ec48c..818e34cc 100644 --- a/README.md +++ b/README.md @@ -13,22 +13,22 @@ LICENSE in the root of the project source code. Donations --------- -BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY -LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 +BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY +LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z Downloads ------------ Official binary releases could be found at: -http://i2pd.website/releases/ +http://i2pd.website/releases/ older releases http://download.i2p.io/purplei2p/i2pd/releases/ Supported OS ------------ -* Linux x86/x64 - Proved working. +* Linux x86/x64 - [![Build Status](https://travis-ci.org/purplei2p/i2pd.svg?branch=master)](https://travis-ci.org/purplei2p/i2pd) * Mac OS X - Not well tested. (Only works with clang, not GCC) * Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) From 80b0a3cdecbf215422a661a42aeb6cd8c8fda9f9 Mon Sep 17 00:00:00 2001 From: scrrrapy Date: Thu, 14 Jan 2016 10:16:42 +0000 Subject: [PATCH 0770/6300] fixed travis badge branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 818e34cc..982feca2 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ http://download.i2p.io/purplei2p/i2pd/releases/ Supported OS ------------ -* Linux x86/x64 - [![Build Status](https://travis-ci.org/purplei2p/i2pd.svg?branch=master)](https://travis-ci.org/purplei2p/i2pd) +* Linux x86/x64 - [![Build Status](https://travis-ci.org/purplei2p/i2pd.svg?branch=openssl)](https://travis-ci.org/purplei2p/i2pd) * Mac OS X - Not well tested. (Only works with clang, not GCC) * Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) From 2d4d2374e358bff17214ec4f2e6fec87da6374cb Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 14 Jan 2016 07:47:04 -0500 Subject: [PATCH 0771/6300] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 982feca2..0253663f 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ LICENSE in the root of the project source code. Donations --------- -BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY -LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 -ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z +BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY +LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 +ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z Downloads ------------ From ffb8c3e53ccab61f81e8620700af693bb4c188ce Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 14 Jan 2016 07:47:58 -0500 Subject: [PATCH 0772/6300] Update README.md --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0253663f..bb974f21 100644 --- a/README.md +++ b/README.md @@ -20,22 +20,22 @@ ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z Downloads ------------ -Official binary releases could be found at: -http://i2pd.website/releases/ -older releases -http://download.i2p.io/purplei2p/i2pd/releases/ +Official binary releases could be found at: +http://i2pd.website/releases/ +older releases +http://download.i2p.io/purplei2p/i2pd/releases/ Supported OS ------------ -* Linux x86/x64 - [![Build Status](https://travis-ci.org/purplei2p/i2pd.svg?branch=openssl)](https://travis-ci.org/purplei2p/i2pd) -* Mac OS X - Not well tested. (Only works with clang, not GCC) -* Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) +* Linux x86/x64 - [![Build Status](https://travis-ci.org/purplei2p/i2pd.svg?branch=openssl)](https://travis-ci.org/purplei2p/i2pd) +* Mac OS X - Not well tested. (Only works with clang, not GCC) +* Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) More documentation ------------------ -* [Building from source / unix](docs/build_notes_unix.md) -* [Building from source / windows](docs/build_notes_windows.md) -* [Configuring your i2pd](docs/configuration.md) -* [Github wiki](https://github.com/PurpleI2P/i2pd/wiki/) +* [Building from source / unix](docs/build_notes_unix.md) +* [Building from source / windows](docs/build_notes_windows.md) +* [Configuring your i2pd](docs/configuration.md) +* [Github wiki](https://github.com/PurpleI2P/i2pd/wiki/) From 81d3ad2d354a97f0483f2881ef43dff0b26c2f05 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 14 Jan 2016 11:06:05 -0500 Subject: [PATCH 0773/6300] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb974f21..7f199b39 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ http://download.i2p.io/purplei2p/i2pd/releases/ Supported OS ------------ -* Linux x86/x64 - [![Build Status](https://travis-ci.org/purplei2p/i2pd.svg?branch=openssl)](https://travis-ci.org/purplei2p/i2pd) +* Linux x86/x64 - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) * Mac OS X - Not well tested. (Only works with clang, not GCC) * Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) From fe4362f459ffa54accf71e1f70ffb16e1bad9ee7 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 14 Jan 2016 15:57:55 -0500 Subject: [PATCH 0774/6300] tunnel parameters --- ClientContext.cpp | 22 +++++++++++++++++----- ClientContext.h | 3 ++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index dacba5cd..41226130 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -123,7 +123,7 @@ namespace client } std::shared_ptr ClientContext::LoadLocalDestination (const std::string& filename, - bool isPublic, i2p::data::SigningKeyType sigType) + bool isPublic, i2p::data::SigningKeyType sigType, const std::map * params) { i2p::data::PrivateKeys keys; std::string fullPath = i2p::util::filesystem::GetFullPath (filename); @@ -163,7 +163,7 @@ namespace client } else { - localDestination = std::make_shared (keys, isPublic); + localDestination = std::make_shared (keys, isPublic, params); m_Destinations[localDestination->GetIdentHash ()] = localDestination; localDestination->Start (); } @@ -257,10 +257,16 @@ namespace client std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); + // I2CP + std::map options; + options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = section.second.get (I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); + options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = section.second.get (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); + options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = section.second.get (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); + options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = section.second.get (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); std::shared_ptr localDestination = nullptr; if (keys.length () > 0) - localDestination = LoadLocalDestination (keys, false, sigType); + localDestination = LoadLocalDestination (keys, false, sigType, &options); auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); if (m_ClientTunnels.insert (std::make_pair (port, std::unique_ptr(clientTunnel))).second) clientTunnel->Start (); @@ -278,8 +284,14 @@ namespace client int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); - - auto localDestination = LoadLocalDestination (keys, true, sigType); + // I2CP + std::map options; + options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = section.second.get (I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); + options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = section.second.get (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); + options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = section.second.get (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); + options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = section.second.get (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); + + auto localDestination = LoadLocalDestination (keys, true, sigType, &options); I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ? new I2PServerTunnelHTTP (name, host, port, localDestination, inPort) : new I2PServerTunnel (name, host, port, localDestination, inPort); diff --git a/ClientContext.h b/ClientContext.h index 4495d505..24d2910c 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -51,7 +51,8 @@ namespace client void DeleteLocalDestination (std::shared_ptr destination); std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; std::shared_ptr LoadLocalDestination (const std::string& filename, bool isPublic, - i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); + i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, + const std::map * params = nullptr); AddressBook& GetAddressBook () { return m_AddressBook; }; const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; From 3d4890a28b43e6837bbeb8716b45a80cf118adab Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 14 Jan 2016 18:45:47 -0500 Subject: [PATCH 0775/6300] handle I2CP keys correctly --- ClientContext.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 41226130..b89f54ba 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -259,10 +259,10 @@ namespace client i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); // I2CP std::map options; - options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = section.second.get (I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); - options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = section.second.get (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); - options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = section.second.get (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); - options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = section.second.get (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); + options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_INBOUND_TUNNEL_LENGTH, '/'), std::to_string (DEFAULT_INBOUND_TUNNEL_LENGTH)); + options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, '/'), std::to_string (DEFAULT_OUTBOUND_TUNNEL_LENGTH)); + options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, '/'), std::to_string (DEFAULT_INBOUND_TUNNELS_QUANTITY)); + options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, '/'), std::to_string (DEFAULT_OUTBOUND_TUNNELS_QUANTITY)); std::shared_ptr localDestination = nullptr; if (keys.length () > 0) @@ -285,11 +285,11 @@ namespace client std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); // I2CP - std::map options; - options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = section.second.get (I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); - options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = section.second.get (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); - options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = section.second.get (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); - options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = section.second.get (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); + std::map options; + options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_INBOUND_TUNNEL_LENGTH, '/'), std::to_string (DEFAULT_INBOUND_TUNNEL_LENGTH)); + options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, '/'), std::to_string (DEFAULT_OUTBOUND_TUNNEL_LENGTH)); + options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, '/'), std::to_string (DEFAULT_INBOUND_TUNNELS_QUANTITY)); + options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, '/'), std::to_string (DEFAULT_OUTBOUND_TUNNELS_QUANTITY)); auto localDestination = LoadLocalDestination (keys, true, sigType, &options); I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ? From 208e8f8247996e7444f454cd1fd948c81567e670 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 14 Jan 2016 19:05:46 -0500 Subject: [PATCH 0776/6300] new webconsole style by sha-db --- HTTPServer.cpp | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index e6099053..f19abff4 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -358,27 +358,23 @@ namespace util std::stringstream s; // Html5 head start s << "\n"; // TODO: Add support for locale. - s << ""; // TODO: Find something to parse html/template system. This is horrible. - s << "Purple I2P " << VERSION " Webconsole"; + s << ""; // TODO: Find something to parse html/template system. This is horrible. + s << ""; + s << "Purple I2P " << VERSION " Webconsole"; s << ""; - s << ""; - s << "
"; - s << "

i2pd webconsole

"; - s << "
"; + s << ""; + s << "
i2pd webconsole
"; s << "
"; s << "
"; s << "Main page

"; @@ -611,8 +607,8 @@ namespace util << it.second->GetSocket ().remote_endpoint().address ().to_string (); if (!it.second->IsOutgoing ()) s << "-->"; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + s << "
" << std::endl; } - s << "
" << std::endl; } } auto ssuServer = i2p::transport::transports.GetSSUServer (); @@ -901,3 +897,4 @@ namespace util } } + From ec958697e23c48717286bae064eee0e7741f084f Mon Sep 17 00:00:00 2001 From: 0niichan Date: Fri, 15 Jan 2016 07:44:26 +0700 Subject: [PATCH 0777/6300] Update util.cpp change i2pd home data path for Windows --- util.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/util.cpp b/util.cpp index ddbfad23..afb1d92b 100644 --- a/util.cpp +++ b/util.cpp @@ -220,15 +220,15 @@ namespace filesystem boost::filesystem::path GetDefaultDataDir() { - // Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd - // Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd + // Windows < Vista: C:\Documents and Settings\Username\.i2pd + // Windows >= Vista: C:\Users\Username\.i2pd // Mac: ~/Library/Application Support/i2pd // Unix: ~/.i2pd or /var/lib/i2pd is system=1 #ifdef WIN32 // Windows char localAppData[MAX_PATH]; - SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); - return boost::filesystem::path(std::string(localAppData) + "\\" + appName); + SHGetFolderPath(NULL, CSIDL_PROFILE, 0, 0, localAppData); + return boost::filesystem::path(std::string(localAppData) + "\\" + "." + appName); #else /* UNIX */ if (i2p::util::config::GetArg("-service", 0)) // use system folder return boost::filesystem::path(std::string ("/var/lib/") + appName); From 3acc244692b1a973be85cc95c8529ca9c12a7b48 Mon Sep 17 00:00:00 2001 From: scrrrapy Date: Thu, 14 Jan 2016 00:49:45 +0000 Subject: [PATCH 0778/6300] reordered unix targeted documentation to be more user-friendly Also fixed wrong build root. --- docs/build_notes_unix.md | 112 +++++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 40 deletions(-) diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index 145e1a70..aa43b2c5 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -1,46 +1,60 @@ Building on Unix systems ============================= -Common build/install process from sources: +First of all we need to make sure that all dependencies are satisfied. -* git clone https://github.com/PurpleI2P/i2pd.git -* mkdir -p 'i2pd/build/tmp' && cd 'i2pd/build/tmp' -* cmake -DCMAKE_BUILD_TYPE=Release .. -* make -* make install +This doc is trying to cover: +* [Debian/Ubuntu](#debianubuntu) (contains packaging instructions) +* [FreeBSD](#freebsd) -Available cmake options: +Make sure you have all required dependencies for your system successfully installed. -* CMAKE_BUILD_TYPE -- build profile (Debug/Release) -* WITH_AESNI -- AES-NI support (ON/OFF) -* WITH_HARDENING -- enable hardening features (ON/OFF) (gcc only) -* WITH_BINARY -- build i2pd itself -* WITH_LIBRARY -- build libi2pd -* WITH_STATIC -- build static versions of library and i2pd binary -* WITH_UPNP -- build with UPnP support (requires libupnp) -* WITH_PCH -- use pre-compiled header (experimental, speeds up build) +If so then we are ready to go! +Let's clone the repository and start building the i2pd: +```bash +git clone https://github.com/PurpleI2P/i2pd.git +cd i2pd/build +cmake -DCMAKE_BUILD_TYPE=Release # more options could be passed, see "CMake Options" +make +``` + +After successfull build i2pd could be installed with: +```bash +make install +``` Debian/Ubuntu ------------- -For building from source on debian system you will need the following "-dev" packages: +You will need a compiler and other tools that could be installed with `build-essential` package: +```bash +sudo apt-get install build-essential +``` -* libboost-chrono-dev -* libboost-date-time-dev -* libboost-filesystem-dev -* libboost-program-options-dev -* libboost-regex-dev -* libboost-system-dev -* libboost-thread-dev -* libssl-dev (e.g. openssl) -* zlib1g-dev (libssl-dev already depends on it) -* libminiupnpc-dev (optional, if WITH_UPNP=ON) +Also you will need a bunch of development libraries: +```bash +sudo apt-get install \ + libboost-chrono-dev \ + libboost-date-time-dev \ + libboost-filesystem-dev \ + libboost-program-options-dev \ + libboost-regex-dev \ + libboost-system-dev \ + libboost-thread-dev \ + libssl-dev +``` + +If you need UPnP support (don't forget to run CMake with `WITH_UPNP=ON`) miniupnpc development library should be installed: +```bash +sudo apt-get install libminiupnpc-dev +``` You may also build deb-package with the following: - - apt-get install build-essential fakeroot devscripts - cd i2pd - debuild --no-tgz-check # building from git repo +```bash +sudo apt-get install fakeroot devscripts +cd i2pd +debuild --no-tgz-check +``` FreeBSD ------- @@ -49,17 +63,35 @@ Branch 9.X has gcc v4.2, that knows nothing about required c++11 standart. Required ports: -* devel/cmake -* devel/boost-libs -* lang/gcc47 # or later version +* `devel/cmake` +* `devel/boost-libs` +* `lang/gcc47`(or later version) -To use newer compiler you should set these variables: - - export CC=/usr/local/bin/gcc47 - export CXX=/usr/local/bin/g++47 - -Replace "47" with your actual gcc version +To use newer compiler you should set these variables(replace "47" with your actual gcc version): +```bash +export CC=/usr/local/bin/gcc47 +export CXX=/usr/local/bin/g++47 +``` Branch 10.X has more reliable clang version, that can finally build i2pd, -but i still recommend to use gcc, otherwise you will fight it's bugs by +but I still recommend to use gcc, otherwise you will fight it's bugs by your own. + +CMake Options +------------- + +Available CMake options(each option has a for of `=`, for more information see `man 1 cmake`): + +* `CMAKE_BUILD_TYPE` build profile (Debug/Release) +* `WITH_BINARY` build i2pd itself +* `WITH_LIBRARY` build libi2pd +* `WITH_STATIC` build static versions of library and i2pd binary +* `WITH_UPNP` build with UPnP support (requires libupnp) +* `WITH_AESNI` build with AES-NI support (ON/OFF) +* `WITH_HARDENING` enable hardening features (ON/OFF) (gcc only) +* `WITH_PCH` use pre-compiled header (experimental, speeds up build) + +Also there is `-L` flag for CMake that could be used to list current cached options: +```bash +cmake -L +``` From 01f7343781c58d95eb727d9db9383df66fd19951 Mon Sep 17 00:00:00 2001 From: scrrrapy Date: Thu, 14 Jan 2016 00:51:18 +0000 Subject: [PATCH 0779/6300] added instructions to build on Fedora/Centos --- docs/build_notes_unix.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index aa43b2c5..882a8ece 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -5,6 +5,7 @@ First of all we need to make sure that all dependencies are satisfied. This doc is trying to cover: * [Debian/Ubuntu](#debianubuntu) (contains packaging instructions) +* [Fedora/Centos](#fedoracentos) * [FreeBSD](#freebsd) Make sure you have all required dependencies for your system successfully installed. @@ -56,6 +57,35 @@ cd i2pd debuild --no-tgz-check ``` +Fedora/Centos +------------- + +You will need a compiler and other tools to perform a build: +```bash +sudo yum install make cmake gcc gcc-c++ +``` + +*Latest Fedora system using [DNF](https://en.wikipedia.org/wiki/DNF_(software)) instead of YUM by default, you may prefer to use DNF, but YUM should be ok* + +> *Centos 7 has CMake 2.8.11 in the official repositories that too old to build i2pd, CMake >=2.8.12 is required* +> You could build CMake for Centos manualy(WARNING there are a lot of build dependencies!): +> ```bash +> wget https://kojipkgs.fedoraproject.org/packages/cmake/2.8.12/3.fc21/src/cmake-2.8.12-3.fc21.src.rpm +> yum-builddep cmake-2.8.12-3.fc21.src.rpm +> rpmbuild --rebuild cmake-2.8.12-3.fc21.src.rpm +> yum install ~/rpmbuild/RPMS/x86_64/cmake-2.8.12-3.el7.centos.x86_64.rpm +> ``` + +Also you will need a bunch of development libraries +```bash +sudo yum install boost-devel openssl-devel +``` + +If you need UPnP support (don't forget to run CMake with `WITH_UPNP=ON`) miniupnpc development library should be installed: +```bash +miniupnpc-devel +``` + FreeBSD ------- From 13ffdc6dd273ee84050f35908bee2bb4c7546a62 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 15 Jan 2016 12:24:40 -0500 Subject: [PATCH 0780/6300] common ReadI2CPOptions --- ClientContext.cpp | 28 ++++++++++++++++++---------- ClientContext.h | 6 +++++- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index b89f54ba..c4ac9a77 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -225,7 +225,21 @@ namespace client return nullptr; } - // should be moved in i2p::utils::fs + template + std::string ClientContext::GetI2CPOption (const Section& section, const std::string& name, const Type& value) const + { + return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value)); + } + + template + void ClientContext::ReadI2CPOptions (const Section& section, std::map& options) const + { + options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); + options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); + options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); + options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); + } + void ClientContext::ReadTunnels () { boost::property_tree::ptree pt; @@ -258,11 +272,8 @@ namespace client int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); // I2CP - std::map options; - options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_INBOUND_TUNNEL_LENGTH, '/'), std::to_string (DEFAULT_INBOUND_TUNNEL_LENGTH)); - options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, '/'), std::to_string (DEFAULT_OUTBOUND_TUNNEL_LENGTH)); - options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, '/'), std::to_string (DEFAULT_INBOUND_TUNNELS_QUANTITY)); - options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, '/'), std::to_string (DEFAULT_OUTBOUND_TUNNELS_QUANTITY)); + std::map options; + ReadI2CPOptions (section, options); std::shared_ptr localDestination = nullptr; if (keys.length () > 0) @@ -286,10 +297,7 @@ namespace client i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); // I2CP std::map options; - options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_INBOUND_TUNNEL_LENGTH, '/'), std::to_string (DEFAULT_INBOUND_TUNNEL_LENGTH)); - options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, '/'), std::to_string (DEFAULT_OUTBOUND_TUNNEL_LENGTH)); - options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, '/'), std::to_string (DEFAULT_INBOUND_TUNNELS_QUANTITY)); - options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = section.second.get (decltype (pt)::path_type (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, '/'), std::to_string (DEFAULT_OUTBOUND_TUNNELS_QUANTITY)); + ReadI2CPOptions (section, options); auto localDestination = LoadLocalDestination (keys, true, sigType, &options); I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ? diff --git a/ClientContext.h b/ClientContext.h index 24d2910c..aff39d37 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -60,7 +60,11 @@ namespace client private: void ReadTunnels (); - + template + std::string GetI2CPOption (const Section& section, const std::string& name, const Type& value) const; + template + void ReadI2CPOptions (const Section& section, std::map& options) const; + private: std::mutex m_DestinationsMutex; From 02bbb46d2e2c87379823e8613cefff6036ea0e46 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 15 Jan 2016 14:46:29 -0500 Subject: [PATCH 0781/6300] separate keys and destination creation --- ClientContext.cpp | 36 ++++++++++++++---------------------- ClientContext.h | 4 +--- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index c4ac9a77..791ab563 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -40,7 +40,11 @@ namespace client // proxies std::string proxyKeys = i2p::util::config::GetArg("-proxykeys", ""); if (proxyKeys.length () > 0) - localDestination = LoadLocalDestination (proxyKeys, false); + { + i2p::data::PrivateKeys keys; + LoadPrivateKeys (keys, proxyKeys); + localDestination = CreateNewLocalDestination (keys, false); + } LogPrint(eLogInfo, "Clients: starting HTTP Proxy"); m_HttpProxy = new i2p::proxy::HTTPProxy(i2p::util::config::GetArg("-httpproxyaddress", "127.0.0.1"), i2p::util::config::GetArg("-httpproxyport", 4446), localDestination); m_HttpProxy->Start(); @@ -122,10 +126,8 @@ namespace client m_SharedLocalDestination = nullptr; } - std::shared_ptr ClientContext::LoadLocalDestination (const std::string& filename, - bool isPublic, i2p::data::SigningKeyType sigType, const std::map * params) + void ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType) { - i2p::data::PrivateKeys keys; std::string fullPath = i2p::util::filesystem::GetFullPath (filename); std::ifstream s(fullPath.c_str (), std::ifstream::binary); if (s.is_open ()) @@ -152,22 +154,6 @@ namespace client LogPrint (eLogInfo, "Clients: New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); } - - std::shared_ptr localDestination = nullptr; - std::unique_lock l(m_DestinationsMutex); - auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ()); - if (it != m_Destinations.end ()) - { - LogPrint (eLogWarning, "Clients: Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " already exists"); - localDestination = it->second; - } - else - { - localDestination = std::make_shared (keys, isPublic, params); - m_Destinations[localDestination->GetIdentHash ()] = localDestination; - localDestination->Start (); - } - return localDestination; } std::shared_ptr ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, @@ -277,7 +263,11 @@ namespace client std::shared_ptr localDestination = nullptr; if (keys.length () > 0) - localDestination = LoadLocalDestination (keys, false, sigType, &options); + { + i2p::data::PrivateKeys k; + LoadPrivateKeys (k, keys, sigType); + localDestination = CreateNewLocalDestination (k, false, &options); + } auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); if (m_ClientTunnels.insert (std::make_pair (port, std::unique_ptr(clientTunnel))).second) clientTunnel->Start (); @@ -299,7 +289,9 @@ namespace client std::map options; ReadI2CPOptions (section, options); - auto localDestination = LoadLocalDestination (keys, true, sigType, &options); + i2p::data::PrivateKeys k; + LoadPrivateKeys (k, keys, sigType); + auto localDestination = CreateNewLocalDestination (k, true, &options); I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ? new I2PServerTunnelHTTP (name, host, port, localDestination, inPort) : new I2PServerTunnel (name, host, port, localDestination, inPort); diff --git a/ClientContext.h b/ClientContext.h index aff39d37..1750f2b5 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -50,9 +50,7 @@ namespace client const std::map * params = nullptr); void DeleteLocalDestination (std::shared_ptr destination); std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; - std::shared_ptr LoadLocalDestination (const std::string& filename, bool isPublic, - i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, - const std::map * params = nullptr); + void LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); AddressBook& GetAddressBook () { return m_AddressBook; }; const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; From d9e199092deaec73a1d447185313ceed9202a8c6 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 15 Jan 2016 16:23:03 -0500 Subject: [PATCH 0782/6300] fixed race condition --- Transports.cpp | 22 +++++++++++++++++++--- Transports.h | 1 + 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index e66ad864..e2578456 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -231,8 +231,11 @@ namespace transport try { auto r = netdb.FindRouter (ident); - it = m_Peers.insert (std::pair(ident, { 0, r, {}, - i2p::util::GetSecondsSinceEpoch (), {} })).first; + { + std::unique_lock l(m_PeersMutex); + it = m_Peers.insert (std::pair(ident, { 0, r, {}, + i2p::util::GetSecondsSinceEpoch (), {} })).first; + } connected = ConnectToPeer (ident, it->second); } catch (std::exception& ex) @@ -318,6 +321,7 @@ namespace transport } LogPrint (eLogError, "Transports: No NTCP or SSU addresses available"); peer.Done (); + std::unique_lock l(m_PeersMutex); m_Peers.erase (ident); return false; } @@ -349,6 +353,7 @@ namespace transport else { LogPrint (eLogError, "Transports: RouterInfo not found, Failed to send messages"); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } @@ -382,6 +387,7 @@ namespace transport } } LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ()); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it1); } } @@ -413,6 +419,7 @@ namespace transport } } LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ()); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it1); } } @@ -502,7 +509,10 @@ namespace transport it->second.delayedMessages.clear (); } else // incoming connection + { + std::unique_lock l(m_PeersMutex); m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} })); + } }); } @@ -521,14 +531,18 @@ namespace transport if (it->second.delayedMessages.size () > 0) ConnectToPeer (ident, it->second); else + { + std::unique_lock l(m_PeersMutex); m_Peers.erase (it); + } } } }); } bool Transports::IsConnected (const i2p::data::IdentHash& ident) const - { + { + std::unique_lock l(m_PeersMutex); auto it = m_Peers.find (ident); return it != m_Peers.end (); } @@ -543,6 +557,7 @@ namespace transport if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) { LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); + std::unique_lock l(m_PeersMutex); it = m_Peers.erase (it); } else @@ -559,6 +574,7 @@ namespace transport std::shared_ptr Transports::GetRandomPeer () const { if (!m_Peers.size ()) return nullptr; + std::unique_lock l(m_PeersMutex); auto it = m_Peers.begin (); std::advance (it, rand () % m_Peers.size ()); return it != m_Peers.end () ? it->second.router : nullptr; diff --git a/Transports.h b/Transports.h index 98e32679..9b603963 100644 --- a/Transports.h +++ b/Transports.h @@ -132,6 +132,7 @@ namespace transport NTCPServer * m_NTCPServer; SSUServer * m_SSUServer; + mutable std::mutex m_PeersMutex; std::map m_Peers; DHKeysPairSupplier m_DHKeysPairSupplier; From 2a4d78d9bfbded34ec3384541ec6f4d28a78ed08 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 16 Jan 2016 15:36:26 -0500 Subject: [PATCH 0783/6300] wordwrapping (by sha-db) --- HTTPServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index f19abff4..3a16ff6f 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -368,7 +368,7 @@ namespace util s << ".header {font-size: 2.5em; text-align: center; margin: 1.5em 0; color: #894C84;}"; s << ".wrapper {margin: 0 auto; padding: 1em; max-width: 48em;}"; s << ".left {float: left; position: absolute;}"; - s << ".right {font-size: 1em; margin-left: 13em; float: left; max-width: 34em; overflow: auto;}"; + s << ".right {font-size: 1em; margin-left: 13em; float: left; max-width: 34em; word-break: break-all;}"; s << ".established_tunnel {color: #56b734;}"; s << ".expiring_tunnel {color: #d3ae3f;}"; s << ".failed_tunnel {color: #d33f3f;}"; From 0ca3fb5af05ebac35068111a0df37991b8eff9ea Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 16 Jan 2016 15:36:30 -0500 Subject: [PATCH 0784/6300] specify and check netId --- NetDb.cpp | 19 +++++++++++-------- RouterContext.cpp | 2 +- RouterInfo.cpp | 7 +++++++ version.h | 1 + 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index d0b6da26..b9d5a130 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -168,16 +168,19 @@ namespace data } else { - LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase32()); r = std::make_shared (buf, len); + if (!r->IsUnreachable ()) { - std::unique_lock l(m_RouterInfosMutex); - m_RouterInfos[r->GetIdentHash ()] = r; - } - if (r->IsFloodfill ()) - { - std::unique_lock l(m_FloodfillsMutex); - m_Floodfills.push_back (r); + LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase32()); + { + std::unique_lock l(m_RouterInfosMutex); + m_RouterInfos[r->GetIdentHash ()] = r; + } + if (r->IsFloodfill ()) + { + std::unique_lock l(m_FloodfillsMutex); + m_Floodfills.push_back (r); + } } } // take care about requested destination diff --git a/RouterContext.cpp b/RouterContext.cpp index 3117c85a..7d35ccf1 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -51,7 +51,7 @@ namespace i2p routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC routerInfo.SetProperty ("coreVersion", I2P_VERSION); - routerInfo.SetProperty ("netId", "2"); + routerInfo.SetProperty ("netId", std::to_string (I2PD_NET_ID)); routerInfo.SetProperty ("router.version", I2P_VERSION); routerInfo.SetProperty ("stat_uptime", "90m"); routerInfo.CreateBuffer (m_Keys); diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 53ab7aa3..8f2b7bfe 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -3,6 +3,7 @@ #include "I2PEndian.h" #include #include +#include "version.h" #include "Crypto.h" #include "Base.h" #include "Timestamp.h" @@ -243,6 +244,12 @@ namespace data // extract caps if (!strcmp (key, "caps")) ExtractCaps (value); + // check netId + if (!strcmp (key, "netId") && atoi (value) != I2PD_NET_ID) + { + LogPrint (eLogError, "Unexpected netid=", value); + m_IsUnreachable = true; + } } if (!m_SupportedTransports || !m_Addresses.size() || (UsesIntroducer () && !introducers)) diff --git a/version.h b/version.h index 8b93f1af..8fea069c 100644 --- a/version.h +++ b/version.h @@ -12,6 +12,7 @@ #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) #define VERSION I2PD_VERSION +#define I2PD_NET_ID 2 #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 From b6dcb2f4c0f846ee5754627a5b6a2eddac964956 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 17 Jan 2016 11:10:56 -0500 Subject: [PATCH 0785/6300] show streams as table (byt sha-db) --- HTTPServer.cpp | 208 ++++++++++++++++++++++++++++--------------------- Tunnel.cpp | 7 +- 2 files changed, 124 insertions(+), 91 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 3a16ff6f..bb3e2819 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -357,45 +357,47 @@ namespace util { std::stringstream s; // Html5 head start - s << "\n"; // TODO: Add support for locale. - s << ""; // TODO: Find something to parse html/template system. This is horrible. - s << ""; - s << "Purple I2P " << VERSION " Webconsole"; - s << ""; + s << "caption {font-size: 1.5em; text-align: center; color: #894C84;}"; + s << "table {width: 100%; border-collapse: collapse; text-align: center;}"; + s << "\r\n\r\n\r\n"; s << "
i2pd webconsole
"; s << "
"; - s << "
"; - s << "Main page

"; - s << "Local destinations
"; - s << "Tunnels
"; - s << "Transit tunnels
"; - s << "Transports

"; - s << "I2P tunnels
"; + s << "
\r\n"; + s << "Main page
\r\n
\r\n"; + s << "Local destinations
\r\n"; + s << "Tunnels
\r\n"; + s << "Transit tunnels
\r\n"; + s << "Transports
\r\n
\r\n"; + s << "I2P tunnels
\r\n"; if (i2p::client::context.GetSAMBridge ()) - s << "SAM sessions

"; + s << "SAM sessions
\r\n
\r\n"; if (i2p::context.AcceptsTunnels ()) - s << "Stop accepting tunnels

"; + s << "Stop accepting tunnels
\r\n
\r\n"; else - s << "Start accepting tunnels

"; - s << "Run peer test

"; + s << "Start accepting tunnels
\r\n
\r\n"; + s << "Run peer test
\r\n
\r\n"; s << "
"; if (address.length () > 1) HandleCommand (address.substr (2), s); else FillContent (s); - s << "
"; + s << "
\r\n\r\n"; SendReply (s.str ()); } @@ -403,7 +405,7 @@ namespace util { s << "Uptime: " << boost::posix_time::to_simple_string ( boost::posix_time::time_duration (boost::posix_time::seconds ( - i2p::context.GetUptime ()))) << "
"; + i2p::context.GetUptime ()))) << "
\r\n"; s << "Status: "; switch (i2p::context.GetStatus ()) { @@ -412,14 +414,14 @@ namespace util case eRouterStatusFirewalled: s << "Firewalled"; break; default: s << "Unknown"; } - s << "
"; - s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
"; + s << "
\r\n"; + s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; s << "Received: " << i2p::transport::transports.GetTotalReceivedBytes ()/1000 << "K"; - s << " (" << i2p::transport::transports.GetInBandwidth () <<" Bps)
"; + s << " (" << i2p::transport::transports.GetInBandwidth () <<" Bps)
\r\n"; s << "Sent: " << i2p::transport::transports.GetTotalSentBytes ()/1000 << "K"; - s << " (" << i2p::transport::transports.GetOutBandwidth () <<" Bps)
"; - s << "Data path: " << i2p::util::filesystem::GetDataDir().string() << "

"; - s << "Our external address:" << "
" ; + s << " (" << i2p::transport::transports.GetOutBandwidth () <<" Bps)
\r\n"; + s << "Data path: " << i2p::util::filesystem::GetDataDir().string() << "
\r\n
\r\n"; + s << "Our external address:" << "
\r\n" ; for (auto& address : i2p::context.GetRouterInfo().GetAddresses()) { switch (address.transportStyle) @@ -439,11 +441,11 @@ namespace util default: s << "Unknown  "; } - s << address.host.to_string() << ":" << address.port << "
"; + s << address.host.to_string() << ":" << address.port << "
\r\n"; } - s << "
Routers: " << i2p::data::netdb.GetNumRouters () << " "; + s << "
\r\nRouters: " << i2p::data::netdb.GetNumRouters () << " "; s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; - s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
"; + s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
\r\n"; } void HTTPConnection::HandleCommand (const std::string& command, std::stringstream& s) @@ -486,30 +488,31 @@ namespace util void HTTPConnection::ShowLocalDestinations (std::stringstream& s) { - s << "Local Destinations:

"; + s << "Local Destinations:
\r\n
\r\n"; for (auto& it: i2p::client::context.GetDestinations ()) { auto ident = it.second->GetIdentHash ();; s << ""; - s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
" << std::endl; + s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; } } void HTTPConnection::ShowLocalDestination (const std::string& b32, std::stringstream& s) { - s << "Local Destination:

"; + s << "Local Destination:
\r\n
\r\n"; i2p::data::IdentHash ident; ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); if (dest) { - s << "Base64:
" << dest->GetIdentity ()->ToBase64 () << "

"; - s << "LeaseSets: " << dest->GetNumRemoteLeaseSets () << "
"; + s << "Base64:
\r\n
\r\n
\r\n"; + s << "LeaseSets: " << dest->GetNumRemoteLeaseSets () << "
\r\n"; auto pool = dest->GetTunnelPool (); if (pool) { - s << "Tunnels:
"; + s << "Tunnels:
\r\n"; for (auto it: pool->GetOutboundTunnels ()) { it->Print (s); @@ -518,7 +521,7 @@ namespace util s << " " << "Failed"; else if (state == i2p::tunnel::eTunnelStateExpiring) s << " " << "Exp"; - s << "
" << std::endl; + s << "
\r\n" << std::endl; } for (auto it: pool->GetInboundTunnels ()) { @@ -528,28 +531,56 @@ namespace util s << " " << "Failed"; else if (state == i2p::tunnel::eTunnelStateExpiring) s << " " << "Exp"; - s << "
" << std::endl; + s << "
\r\n" << std::endl; } } - s << "
Streams:
"; + // s << "
\r\nStreams:
\r\n"; + // for (auto it: dest->GetStreamingDestination ()->GetStreams ()) + // { + // s << it.first << "->" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << " "; + // s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + // s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]"; + // s << "[buf:" << it.second->GetSendBufferSize () << "]"; + // s << "[RTT:" << it.second->GetRTT () << "]"; + // s << "[Window:" << it.second->GetWindowSize () << "]"; + // s << "[Status:" << (int)it.second->GetStatus () << "]"; + // s << "
\r\n"<< std::endl; + // } + s << "
\r\n"; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + for (auto it: dest->GetStreamingDestination ()->GetStreams ()) { - s << it.first << "->" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << " "; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]"; - s << "[buf:" << it.second->GetSendBufferSize () << "]"; - s << "[RTT:" << it.second->GetRTT () << "]"; - s << "[Window:" << it.second->GetWindowSize () << "]"; - s << "[Status:" << (int)it.second->GetStatus () << "]"; - s << "
"<< std::endl; - } + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << "
\r\n" << std::endl; + } } } void HTTPConnection::ShowTunnels (std::stringstream& s) { - s << "Tunnels:

"; - s << "Queue size:" << i2p::tunnel::tunnels.GetQueueSize () << "
"; + s << "Tunnels:
\r\n
\r\n"; + s << "Queue size:" << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n"; for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) { it->Print (s); @@ -558,7 +589,7 @@ namespace util s << " " << "Failed"; else if (state == i2p::tunnel::eTunnelStateExpiring) s << " " << "Exp"; - s << " " << (int)it->GetNumSentBytes () << "
"; + s << " " << (int)it->GetNumSentBytes () << "
\r\n"; s << std::endl; } @@ -570,78 +601,78 @@ namespace util s << " " << "Failed"; else if (state == i2p::tunnel::eTunnelStateExpiring) s << " " << "Exp"; - s << " " << (int)it.second->GetNumReceivedBytes () << "
"; + s << " " << (int)it.second->GetNumReceivedBytes () << "
\r\n"; s << std::endl; } } void HTTPConnection::ShowTransitTunnels (std::stringstream& s) { - s << "Transit tunnels:

"; + s << "Transit tunnels:
\r\n
\r\n"; for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ()) { if (dynamic_cast(it.second)) - s << it.second->GetTunnelID () << "-->"; + s << it.second->GetTunnelID () << " ⇒ "; else if (dynamic_cast(it.second)) - s << "-->" << it.second->GetTunnelID (); + s << " ⇒ " << it.second->GetTunnelID (); else - s << "-->" << it.second->GetTunnelID () << "-->"; - s << " " << it.second->GetNumTransmittedBytes () << "
"; + s << " ⇒ " << it.second->GetTunnelID () << " ⇒ "; + s << " " << it.second->GetNumTransmittedBytes () << "
\r\n"; } } void HTTPConnection::ShowTransports (std::stringstream& s) { - s << "Transports:

"; + s << "Transports:
\r\n
\r\n"; auto ntcpServer = i2p::transport::transports.GetNTCPServer (); if (ntcpServer) { - s << "NTCP
"; + s << "NTCP
\r\n"; for (auto it: ntcpServer->GetNTCPSessions ()) { if (it.second && it.second->IsEstablished ()) { // incoming connection doesn't have remote RI - if (it.second->IsOutgoing ()) s << "-->"; + if (it.second->IsOutgoing ()) s << " ⇒ "; s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " << it.second->GetSocket ().remote_endpoint().address ().to_string (); - if (!it.second->IsOutgoing ()) s << "-->"; + if (!it.second->IsOutgoing ()) s << " ⇒ "; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - s << "
" << std::endl; + s << "
\r\n" << std::endl; } } } auto ssuServer = i2p::transport::transports.GetSSUServer (); if (ssuServer) { - s << "
SSU
"; + s << "
\r\nSSU
\r\n"; for (auto it: ssuServer->GetSessions ()) { auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second->IsOutgoing ()) s << "-->"; + if (it.second->IsOutgoing ()) s << " ⇒ "; s << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it.second->IsOutgoing ()) s << "-->"; + if (!it.second->IsOutgoing ()) s << " ⇒ "; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; if (it.second->GetRelayTag ()) s << " [itag:" << it.second->GetRelayTag () << "]"; - s << "
" << std::endl; + s << "
\r\n" << std::endl; } - s << "
SSU6
"; + s << "
\r\nSSU6
\r\n"; for (auto it: ssuServer->GetSessionsV6 ()) { auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second->IsOutgoing ()) s << "-->"; + if (it.second->IsOutgoing ()) s << " ⇒ "; s << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it.second->IsOutgoing ()) s << "-->"; + if (!it.second->IsOutgoing ()) s << " ⇒ "; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - s << "
" << std::endl; + s << "
\r\n" << std::endl; } } } void HTTPConnection::ShowSAMSessions (std::stringstream& s) { - s << "SAM Sessions:

"; + s << "SAM Sessions:
\r\n
\r\n"; auto sam = i2p::client::context.GetSAMBridge (); if (sam) { @@ -649,14 +680,14 @@ namespace util { s << ""; - s << it.first << "
" << std::endl; + s << it.first << "
\r\n" << std::endl; } } } void HTTPConnection::ShowSAMSession (const std::string& id, std::stringstream& s) { - s << "SAM Session:

"; + s << "SAM Session:
\r\n
\r\n"; auto sam = i2p::client::context.GetSAMBridge (); if (sam) { @@ -666,8 +697,8 @@ namespace util auto& ident = session->localDestination->GetIdentHash(); s << ""; - s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
" << std::endl; - s << "Streams:
"; + s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; + s << "Streams:
\r\n"; for (auto it: session->sockets) { switch (it->GetSocketType ()) @@ -685,7 +716,7 @@ namespace util s << "unknown"; } s << " [" << it->GetSocket ().remote_endpoint() << "]"; - s << "
" << std::endl; + s << "
\r\n" << std::endl; } } } @@ -693,45 +724,45 @@ namespace util void HTTPConnection::ShowI2PTunnels (std::stringstream& s) { - s << "Client Tunnels:

"; + s << "Client Tunnels:
\r\n
\r\n"; for (auto& it: i2p::client::context.GetClientTunnels ()) { - s << it.second->GetName () << "<--"; + s << it.second->GetName () << " ⇠"; auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << ""; s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
"<< std::endl; + s << "
\r\n"<< std::endl; } - s << "
Server Tunnels:

"; + s << "
\r\nServer Tunnels:
\r\n
\r\n"; for (auto& it: i2p::client::context.GetServerTunnels ()) { - s << it.second->GetName () << "-->"; + s << it.second->GetName () << " ⇒ "; auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << ""; s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
"<< std::endl; + s << "
\r\n"<< std::endl; } } void HTTPConnection::StopAcceptingTunnels (std::stringstream& s) { - s << "Stop Accepting Tunnels:

"; + s << "Stop Accepting Tunnels:
\r\n
\r\n"; i2p::context.SetAcceptsTunnels (false); s << "Accepting tunnels stopped" << std::endl; } void HTTPConnection::StartAcceptingTunnels (std::stringstream& s) { - s << "Start Accepting Tunnels:

"; + s << "Start Accepting Tunnels:
\r\n
\r\n"; i2p::context.SetAcceptsTunnels (true); s << "Accepting tunnels started" << std::endl; } void HTTPConnection::RunPeerTest (std::stringstream& s) { - s << "Run Peer Test:

"; + s << "Run Peer Test:
\r\n
\r\n"; i2p::transport::transports.PeerTest (); s << "Peer test is running" << std::endl; } @@ -749,7 +780,7 @@ namespace util if (!i2p::client::context.GetAddressBook ().GetIdentHash (address, destination)) { LogPrint (eLogWarning, "HTTPServer: Unknown address ", address); - SendReply ("" + itoopieImage + "
Unknown address " + address + "", 404); + SendReply ("" + itoopieImage + "
\r\nUnknown address " + address + "", 404); return; } @@ -777,7 +808,7 @@ namespace util SendToDestination (leaseSet, port, buf, len); else // still no LeaseSet - SendReply (leaseSet ? "" + itoopieImage + "
Leases expired" : "" + itoopieImage + "LeaseSet not found", 504); + SendReply (leaseSet ? "" + itoopieImage + "
\r\nLeases expired" : "" + itoopieImage + "LeaseSet not found", 504); } } @@ -811,7 +842,7 @@ namespace util else { if (ecode == boost::asio::error::timed_out) - SendReply ("" + itoopieImage + "
Not responding", 504); + SendReply ("" + itoopieImage + "
\r\nNot responding", 504); else if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -898,3 +929,4 @@ namespace util } + diff --git a/Tunnel.cpp b/Tunnel.cpp index 7a91adca..3391fbc8 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -186,7 +186,7 @@ namespace tunnel { for (auto& it: m_Hops) { - s << "-->"; + s << " ⇒ "; s << i2p::data::GetIdentHashAbbreviation (it->ident->GetIdentHash ()); } } @@ -203,7 +203,7 @@ namespace tunnel void InboundTunnel::Print (std::stringstream& s) const { PrintHops (s); - s << "-->" << GetTunnelID () << ":me"; + s << " ⇒ " << GetTunnelID () << ":me"; } void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) @@ -245,7 +245,7 @@ namespace tunnel { s << GetTunnelID () << ":me"; PrintHops (s); - s << "-->"; + s << " ⇒ "; } Tunnels tunnels; @@ -787,3 +787,4 @@ namespace tunnel } } } + From c312dbaac1b88a2b2741226a64952210a89d52c6 Mon Sep 17 00:00:00 2001 From: zenjy Date: Sun, 17 Jan 2016 18:13:36 +0000 Subject: [PATCH 0786/6300] * Daemon.h: replace "#pragma once" with "#define" --- Daemon.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Daemon.h b/Daemon.h index 9af8c1bb..6252ffe4 100644 --- a/Daemon.h +++ b/Daemon.h @@ -1,4 +1,6 @@ -#pragma once +#ifndef DAEMON_H__ +#define DAEMON_H__ + #include #ifdef _WIN32 @@ -69,3 +71,5 @@ namespace i2p #endif } } + +#endif // DAEMON_H__ From 02b566055ebc8d2a45158cd0d2ed44b4805eed3a Mon Sep 17 00:00:00 2001 From: zenjy Date: Sun, 17 Jan 2016 18:18:21 +0000 Subject: [PATCH 0787/6300] * HTTPServer.cpp: add space after "Queue size:" --- HTTPServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index bb3e2819..699fbde4 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -580,7 +580,7 @@ namespace util void HTTPConnection::ShowTunnels (std::stringstream& s) { s << "Tunnels:
\r\n
\r\n"; - s << "Queue size:" << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n"; + s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n"; for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) { it->Print (s); From efdea07b7bf367003e90ac85ddcebe170a24cf04 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 17 Jan 2016 18:03:40 -0500 Subject: [PATCH 0788/6300] change message expiration timeout to 8 secs (RTT) --- I2NPProtocol.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 16d5e245..854f258c 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -38,7 +38,7 @@ namespace i2p SetTypeID (msgType); if (!replyMsgID) RAND_bytes ((uint8_t *)&replyMsgID, 4); SetMsgID (replyMsgID); - SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); // TODO: 5 secs is a magic number + SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 8000); // 8 secs means initial RTT UpdateSize (); UpdateChks (); } @@ -48,7 +48,7 @@ namespace i2p uint32_t msgID; RAND_bytes ((uint8_t *)&msgID, 4); SetMsgID (msgID); - SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 5000); + SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 8000); } std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID) From cd549937c5b337f49cfba56c2e7f4b54d0917bbd Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 17 Jan 2016 18:55:09 -0500 Subject: [PATCH 0789/6300] support multiple server tunnels with same destination and different ports --- ClientContext.cpp | 4 +++- ClientContext.h | 3 ++- HTTPServer.cpp | 1 + I2PTunnel.h | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 791ab563..e93f9a4a 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -310,7 +310,9 @@ namespace client while (comma != std::string::npos); serverTunnel->SetAccessList (idents); } - if (m_ServerTunnels.insert (std::make_pair (localDestination->GetIdentHash (), std::unique_ptr(serverTunnel))).second) + if (m_ServerTunnels.insert (std::make_pair ( + std::make_tuple (localDestination->GetIdentHash (), inPort), + std::unique_ptr(serverTunnel))).second) serverTunnel->Start (); else LogPrint (eLogError, "Clients: I2P server tunnel for destination ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), " already exists"); diff --git a/ClientContext.h b/ClientContext.h index 1750f2b5..52f1ab5b 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -2,6 +2,7 @@ #define CLIENT_CONTEXT_H__ #include +#include #include #include #include "Destination.h" @@ -74,7 +75,7 @@ namespace client i2p::proxy::HTTPProxy * m_HttpProxy; i2p::proxy::SOCKSProxy * m_SocksProxy; std::map > m_ClientTunnels; // port->tunnel - std::map > m_ServerTunnels; // destination->tunnel + std::map, std::unique_ptr > m_ServerTunnels; // ->tunnel SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 699fbde4..ab6e2631 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -742,6 +742,7 @@ namespace util s << ""; s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << ":" << it.second->GetLocalPort (); s << "
\r\n"<< std::endl; } } diff --git a/I2PTunnel.h b/I2PTunnel.h index 146466c5..7a172468 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -123,6 +123,7 @@ namespace client const std::string& GetAddress() const { return m_Address; } int GetPort () const { return m_Port; }; + uint16_t GetLocalPort () const { return m_PortDestination->GetLocalPort (); }; const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; } const char* GetName() { return m_Name.c_str (); } From 05043f30dc18359ddc3abdd0140c54b3ccf87246 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 18 Jan 2016 00:00:00 +0000 Subject: [PATCH 0790/6300] * tune logs --- AddressBook.cpp | 2 +- Daemon.cpp | 4 +-- HTTPServer.cpp | 2 +- Log.cpp | 2 +- NetDb.cpp | 16 +++++----- NetDbRequests.cpp | 8 ++--- SOCKS.cpp | 73 ++++++++++++++++++++++------------------------ SSUSession.cpp | 2 +- Tunnel.cpp | 30 +++++++++---------- TunnelEndpoint.cpp | 2 +- TunnelPool.cpp | 2 +- 11 files changed, 70 insertions(+), 73 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 64f910e8..00b735ae 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -393,7 +393,7 @@ namespace client if (!s.length()) continue; // skip empty line m_Subscriptions.push_back (new AddressBookSubscription (*this, s)); } - LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions loaded"); + LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); } else LogPrint (eLogWarning, "Addresbook: subscriptions.txt not found"); diff --git a/Daemon.cpp b/Daemon.cpp index 0d2a52c4..48ef1e82 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -63,8 +63,8 @@ namespace i2p i2p::util::config::OptionParser(argc, argv); i2p::context.Init (); - LogPrint(eLogInfo, "\n\n\n\ni2pd v", VERSION, " starting\n"); - LogPrint(eLogDebug, "data directory: ", i2p::util::filesystem::GetDataDir().string()); + LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); + LogPrint(eLogDebug, "FS: data directory: ", i2p::util::filesystem::GetDataDir().string()); i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs); isDaemon = i2p::util::config::GetArg("-daemon", 0); diff --git a/HTTPServer.cpp b/HTTPServer.cpp index ab6e2631..e4a75e1a 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -771,7 +771,7 @@ namespace util void HTTPConnection::HandleDestinationRequest (const std::string& address, const std::string& uri) { std::string request = "GET " + uri + " HTTP/1.1\r\nHost:" + address + "\r\n\r\n"; - LogPrint(eLogDebug, "HTTPServer: client request: ", request); + LogPrint(eLogInfo, "HTTPServer: client request: ", request); SendToAddress (address, 80, request.c_str (), request.size ()); } diff --git a/Log.cpp b/Log.cpp index 4f31fb31..837abac8 100644 --- a/Log.cpp +++ b/Log.cpp @@ -65,7 +65,7 @@ void Log::SetLogLevel (const std::string& level) LogPrint(eLogError, "Log: Unknown loglevel: ", level); return; } - LogPrint(eLogInfo, "Log: min msg level set to ", level); + LogPrint(eLogInfo, "Log: min messages level set to ", level); } void Log::SetLogStream (std::ostream * logStream) diff --git a/NetDb.cpp b/NetDb.cpp index b9d5a130..0cbdce30 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -164,14 +164,14 @@ namespace data auto ts = r->GetTimestamp (); r->Update (buf, len); if (r->GetTimestamp () > ts) - LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase32()); + LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64()); } else { r = std::make_shared (buf, len); if (!r->IsUnreachable ()) { - LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase32()); + LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase64()); { std::unique_lock l(m_RouterInfosMutex); m_RouterInfos[r->GetIdentHash ()] = r; @@ -197,10 +197,10 @@ namespace data { it->second->Update (buf, len); if (it->second->IsValid ()) - LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32()); + LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase64()); else { - LogPrint (eLogWarning, "NetDb: LeaseSet update failed: ", ident.ToBase32()); + LogPrint (eLogWarning, "NetDb: LeaseSet update failed: ", ident.ToBase64()); m_LeaseSets.erase (it); } } @@ -209,11 +209,11 @@ namespace data auto leaseSet = std::make_shared (buf, len); if (leaseSet->IsValid ()) { - LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase32()); + LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase64()); m_LeaseSets[ident] = leaseSet; } else - LogPrint (eLogError, "NetDb: new LeaseSet validation failed: ", ident.ToBase32()); + LogPrint (eLogError, "NetDb: new LeaseSet validation failed: ", ident.ToBase64()); } } } @@ -435,7 +435,7 @@ namespace data auto dest = m_Requests.CreateRequest (destination, false, requestComplete); // non-exploratory if (!dest) { - LogPrint (eLogWarning, "NetDb: destination ", destination.ToBase32(), " is requested already"); + LogPrint (eLogWarning, "NetDb: destination ", destination.ToBase64(), " is requested already"); return; } @@ -444,7 +444,7 @@ namespace data transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); else { - LogPrint (eLogError, "NetDb: ", destination.ToBase32(), " destination requested, but no floodfills found"); + LogPrint (eLogError, "NetDb: ", destination.ToBase64(), " destination requested, but no floodfills found"); m_Requests.RequestComplete (destination, nullptr); } } diff --git a/NetDbRequests.cpp b/NetDbRequests.cpp index a8ccf3f9..e1ea2872 100644 --- a/NetDbRequests.cpp +++ b/NetDbRequests.cpp @@ -122,15 +122,15 @@ namespace data else { done = true; - if (!inbound) LogPrint (eLogWarning, "No inbound tunnels"); - if (!outbound) LogPrint (eLogWarning, "No outbound tunnels"); - if (!nextFloodfill) LogPrint (eLogWarning, "No more floodfills"); + if (!inbound) LogPrint (eLogWarning, "NetDbReq: No inbound tunnels"); + if (!outbound) LogPrint (eLogWarning, "NetDbReq: No outbound tunnels"); + if (!nextFloodfill) LogPrint (eLogWarning, "NetDbReq: No more floodfills"); } } else { if (!dest->IsExploratory ()) - LogPrint (eLogWarning, dest->GetDestination ().ToBase64 (), " not found after 7 attempts"); + LogPrint (eLogWarning, "NetDbReq: ", dest->GetDestination ().ToBase64 (), " not found after 7 attempts"); done = true; } } diff --git a/SOCKS.cpp b/SOCKS.cpp index d82cd0f4..3a971a60 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -145,13 +145,14 @@ namespace proxy void SOCKSHandler::AsyncSockRead() { - LogPrint(eLogDebug,"--- SOCKS async sock read"); - if(m_sock) + LogPrint(eLogDebug, "SOCKS: async sock read"); + if (m_sock) { m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size), std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); - else - LogPrint(eLogError,"--- SOCKS no socket for read"); + } else { + LogPrint(eLogError,"SOCKS: no socket for read"); + } } void SOCKSHandler::Terminate() @@ -159,13 +160,13 @@ namespace proxy if (Kill()) return; if (m_sock) { - LogPrint(eLogDebug,"--- SOCKS close sock"); + LogPrint(eLogDebug, "SOCKS: closing socket"); m_sock->close(); m_sock = nullptr; } if (m_stream) { - LogPrint(eLogDebug,"--- SOCKS close stream"); + LogPrint(eLogDebug, "SOCKS: closing stream"); m_stream.reset (); } Done(shared_from_this()); @@ -216,14 +217,14 @@ namespace proxy boost::asio::const_buffers_1 response(m_response,2); if (m_authchosen == AUTH_UNACCEPTABLE) { - LogPrint(eLogWarning,"--- SOCKS5 authentication negotiation failed"); + LogPrint(eLogWarning, "SOCKS: v5 authentication negotiation failed"); boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, shared_from_this(), std::placeholders::_1)); return false; } else { - LogPrint(eLogDebug,"--- SOCKS5 choosing authentication method: ", m_authchosen); + LogPrint(eLogDebug, "SOCKS: v5 choosing authentication method: ", m_authchosen); boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse, shared_from_this(), std::placeholders::_1)); return true; @@ -238,12 +239,12 @@ namespace proxy switch (m_socksv) { case SOCKS4: - LogPrint(eLogWarning,"--- SOCKS4 failed: ", error); + LogPrint(eLogWarning, "SOCKS: v4 request failed: ", error); if (error < SOCKS4_OK) error = SOCKS4_FAIL; //Transparently map SOCKS5 errors response = GenerateSOCKS4Response(error, m_4aip, m_port); break; case SOCKS5: - LogPrint(eLogWarning,"--- SOCKS5 failed: ", error); + LogPrint(eLogWarning, "SOCKS: v5 request failed: ", error); response = GenerateSOCKS5Response(error, m_addrtype, m_address, m_port); break; } @@ -258,11 +259,11 @@ namespace proxy switch (m_socksv) { case SOCKS4: - LogPrint(eLogInfo,"--- SOCKS4 connection success"); + LogPrint(eLogInfo, "SOCKS: v4 connection success"); response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port); break; case SOCKS5: - LogPrint(eLogInfo,"--- SOCKS5 connection success"); + LogPrint(eLogInfo, "SOCKS: v5 connection success"); auto s = i2p::client::context.GetAddressBook().ToAddress(GetOwner()->GetLocalDestination()->GetIdentHash()); address ad; ad.dns.FromString(s); //HACK only 16 bits passed in port as SOCKS5 doesn't allow for more @@ -293,7 +294,7 @@ namespace proxy if ( m_cmd != CMD_CONNECT ) { //TODO: we need to support binds and other shit! - LogPrint(eLogError,"--- SOCKS unsupported command: ", m_cmd); + LogPrint(eLogError, "SOCKS: unsupported command: ", m_cmd); SocksRequestFailed(SOCKS5_CMD_UNSUP); return false; } @@ -303,10 +304,10 @@ namespace proxy switch (m_socksv) { case SOCKS5: - LogPrint(eLogError,"--- SOCKS5 unsupported address type: ", m_addrtype); + LogPrint(eLogError, "SOCKS: v5 unsupported address type: ", m_addrtype); break; case SOCKS4: - LogPrint(eLogError,"--- SOCKS4a rejected because it's actually SOCKS4"); + LogPrint(eLogError, "SOCKS: request with v4a rejected because it's actually SOCKS4"); break; } SocksRequestFailed(SOCKS5_ADDR_UNSUP); @@ -315,7 +316,7 @@ namespace proxy //TODO: we may want to support other domains if(m_addrtype == ADDR_DNS && m_address.dns.ToString().find(".i2p") == std::string::npos) { - LogPrint(eLogError,"--- SOCKS invalid hostname: ", m_address.dns.ToString()); + LogPrint(eLogError, "SOCKS: invalid hostname: ", m_address.dns.ToString()); SocksRequestFailed(SOCKS5_ADDR_UNSUP); return false; } @@ -340,7 +341,7 @@ namespace proxy EnterState(GET5_AUTHNUM); //Initialize the parser at the right position break; default: - LogPrint(eLogError,"--- SOCKS rejected invalid version: ", ((int)*sock_buff)); + LogPrint(eLogError, "SOCKS: rejected invalid version: ", ((int)*sock_buff)); Terminate(); return false; } @@ -367,7 +368,7 @@ namespace proxy case CMD_UDP: if (m_socksv == SOCKS5) break; default: - LogPrint(eLogError,"--- SOCKS invalid command: ", ((int)*sock_buff)); + LogPrint(eLogError, "SOCKS: invalid command: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } @@ -419,7 +420,7 @@ namespace proxy } if (m_address.dns.size >= max_socks_hostname_size) { - LogPrint(eLogError,"--- SOCKS4a destination is too large"); + LogPrint(eLogError, "SOCKS: v4a req failed: destination is too large"); SocksRequestFailed(SOCKS4_FAIL); return false; } @@ -428,7 +429,7 @@ namespace proxy case GET5_REQUESTV: if (*sock_buff != SOCKS5) { - LogPrint(eLogError,"--- SOCKS5 rejected unknown request version: ", ((int)*sock_buff)); + LogPrint(eLogError,"SOCKS: v5 rejected unknown request version: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } @@ -437,7 +438,7 @@ namespace proxy case GET5_GETRSV: if ( *sock_buff != 0 ) { - LogPrint(eLogError,"--- SOCKS5 unknown reserved field: ", ((int)*sock_buff)); + LogPrint(eLogError, "SOCKS: v5 unknown reserved field: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } @@ -450,7 +451,7 @@ namespace proxy case ADDR_IPV6: EnterState(GET5_IPV6); break; case ADDR_DNS : EnterState(GET5_HOST_SIZE); break; default: - LogPrint(eLogError,"--- SOCKS5 unknown address type: ", ((int)*sock_buff)); + LogPrint(eLogError, "SOCKS: v5 unknown address type: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } @@ -469,7 +470,7 @@ namespace proxy if (m_parseleft == 0) EnterState(GET_PORT); break; default: - LogPrint(eLogError,"--- SOCKS parse state?? ", m_state); + LogPrint(eLogError, "SOCKS: parse state?? ", m_state); Terminate(); return false; } @@ -487,11 +488,11 @@ namespace proxy void SOCKSHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { - LogPrint(eLogDebug,"--- SOCKS sock recv: ", len); + LogPrint(eLogDebug, "SOCKS: recieved ", len, " bytes"); if(ecode) { - LogPrint(eLogWarning," --- SOCKS sock recv got error: ", ecode); - Terminate(); + LogPrint(eLogWarning, "SOCKS: recv got error: ", ecode); + Terminate(); return; } @@ -499,7 +500,7 @@ namespace proxy { if (m_state == DONE) { - LogPrint(eLogInfo,"--- SOCKS requested ", m_address.dns.ToString(), ":" , m_port); + LogPrint(eLogInfo, "SOCKS: requested ", m_address.dns.ToString(), ":" , m_port); GetOwner()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_address.dns.ToString(), m_port); } @@ -510,13 +511,9 @@ namespace proxy void SOCKSHandler::SentSocksFailed(const boost::system::error_code & ecode) { - if (!ecode) - Terminate(); - else - { - LogPrint (eLogError,"--- SOCKS Closing socket after sending failure because: ", ecode.message ()); - Terminate(); - } + if (ecode) + LogPrint (eLogError, "SOCKS: closing socket after sending failure because: ", ecode.message ()); + Terminate(); } void SOCKSHandler::SentSocksDone(const boost::system::error_code & ecode) @@ -524,7 +521,7 @@ namespace proxy if (!ecode) { if (Kill()) return; - LogPrint (eLogInfo,"--- SOCKS New I2PTunnel connection"); + LogPrint (eLogInfo, "SOCKS: new I2PTunnel connection"); auto connection = std::make_shared(GetOwner(), m_sock, m_stream); GetOwner()->AddHandler (connection); connection->I2PConnect (m_remaining_data,m_remaining_data_len); @@ -532,7 +529,7 @@ namespace proxy } else { - LogPrint (eLogError,"--- SOCKS Closing socket after completion reply because: ", ecode.message ()); + LogPrint (eLogError, "SOCKS: closing socket after completion reply because: ", ecode.message ()); Terminate(); } } @@ -541,7 +538,7 @@ namespace proxy { if (ecode) { - LogPrint (eLogError,"--- SOCKS Closing socket after sending reply because: ", ecode.message ()); + LogPrint (eLogError, "SOCKS: closing socket after sending reply because: ", ecode.message ()); Terminate(); } } @@ -555,7 +552,7 @@ namespace proxy } else { - LogPrint (eLogError,"--- SOCKS Issue when creating the stream, check the previous warnings for more info."); + LogPrint (eLogError, "SOCKS: error when creating the stream, check the previous warnings for more info"); SocksRequestFailed(SOCKS5_HOST_UNREACH); } } diff --git a/SSUSession.cpp b/SSUSession.cpp index f5e1f6e6..68af81bb 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -853,7 +853,7 @@ namespace transport { if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogInfo, "SSU: no activity for", SSU_TERMINATION_TIMEOUT, " seconds"); + LogPrint (eLogWarning, "SSU: no activity for ", SSU_TERMINATION_TIMEOUT, " seconds"); Failed (); } } diff --git a/Tunnel.cpp b/Tunnel.cpp index 3391fbc8..267485c8 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -163,7 +163,7 @@ namespace tunnel void Tunnel::SendTunnelDataMsg (std::shared_ptr msg) { - LogPrint (eLogInfo, "Tunnel: Can't send I2NP messages without delivery instructions"); + LogPrint (eLogWarning, "Tunnel: Can't send I2NP messages without delivery instructions"); } std::vector > Tunnel::GetPeers () const @@ -238,7 +238,7 @@ namespace tunnel void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { - LogPrint (eLogError, "Incoming message for outbound tunnel ", GetTunnelID ()); + LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ()); } void OutboundTunnel::Print (std::stringstream& s) const @@ -368,7 +368,7 @@ namespace tunnel std::unique_lock l(m_TransitTunnelsMutex); if (!m_TransitTunnels.insert (std::make_pair (tunnel->GetTunnelID (), tunnel)).second) { - LogPrint (eLogError, "Transit tunnel ", tunnel->GetTunnelID (), " already exists"); + LogPrint (eLogError, "Tunnel: transit tunnel with id ", tunnel->GetTunnelID (), " already exists"); delete tunnel; } } @@ -432,7 +432,7 @@ namespace tunnel HandleTunnelGatewayMsg (tunnel, msg); } else - LogPrint (eLogWarning, "Tunnel ", tunnelID, " not found"); + LogPrint (eLogWarning, "Tunnel: tunnel with id ", tunnelID, " not found"); break; } case eI2NPVariableTunnelBuild: @@ -442,7 +442,7 @@ namespace tunnel HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); break; default: - LogPrint (eLogError, "Tunnel: Unexpected messsage type ", (int)typeID); + LogPrint (eLogError, "Tunnel: unexpected messsage type ", (int) typeID); } msg = m_Queue.Get (); @@ -475,7 +475,7 @@ namespace tunnel { if (!tunnel) { - LogPrint (eLogError, "Missing tunnel for TunnelGateway"); + LogPrint (eLogError, "Tunnel: missing tunnel for gateway"); return; } const uint8_t * payload = msg->GetPayload (); @@ -484,7 +484,7 @@ namespace tunnel msg->offset += I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE; msg->len = msg->offset + len; auto typeID = msg->GetTypeID (); - LogPrint (eLogDebug, "TunnelGateway of ", (int)len, " bytes for tunnel ", tunnel->GetTunnelID (), ". Msg type ", (int)typeID); + LogPrint (eLogDebug, "Tunnel: gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID); if (IsRouterInfoMsg (msg) || typeID == eI2NPDatabaseSearchReply) // transit DatabaseStore my contain new/updated RI @@ -521,7 +521,7 @@ namespace tunnel case eTunnelStatePending: if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT) { - LogPrint (eLogError, "Tunnel: Pending build request ", it->first, " timeout, deleted"); + LogPrint (eLogWarning, "Tunnel: pending build request ", it->first, " timeout, deleted"); // update stats auto config = tunnel->GetTunnelConfig (); if (config) @@ -546,7 +546,7 @@ namespace tunnel it++; break; case eTunnelStateBuildFailed: - LogPrint (eLogError, "Tunnel: Pending build request ", it->first, " failed, deleted"); + LogPrint (eLogError, "Tunnel: pending build request ", it->first, " failed, deleted"); it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; break; @@ -571,7 +571,7 @@ namespace tunnel auto tunnel = *it; if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - LogPrint (eLogDebug, "Tunnel: ", tunnel->GetTunnelID (), " expired"); + LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired"); auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); @@ -602,7 +602,7 @@ namespace tunnel auto inboundTunnel = GetNextInboundTunnel (); auto router = i2p::data::netdb.GetRandomRouter (); if (!inboundTunnel || !router) return; - LogPrint (eLogDebug, "Creating one hop outbound tunnel"); + LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) @@ -619,7 +619,7 @@ namespace tunnel auto tunnel = it->second; if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - LogPrint (eLogDebug, "Tunnel: ", tunnel->GetTunnelID (), " expired"); + LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired"); auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); @@ -647,7 +647,7 @@ namespace tunnel if (m_InboundTunnels.empty ()) { - LogPrint (eLogDebug, "Creating zero hops inbound tunnel..."); + LogPrint (eLogDebug, "Tunnel: Creating zero hops inbound tunnel"); CreateZeroHopsInboundTunnel (); if (!m_ExploratoryPool) { @@ -661,7 +661,7 @@ namespace tunnel { // trying to create one more inbound tunnel auto router = i2p::data::netdb.GetRandomRouter (); - LogPrint (eLogDebug, "Creating one hop inbound tunnel..."); + LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }) ); @@ -676,7 +676,7 @@ namespace tunnel if (ts > it->second->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { auto tmp = it->second; - LogPrint (eLogDebug, "Transit tunnel ", tmp->GetTunnelID (), " expired"); + LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tmp->GetTunnelID (), " expired"); { std::unique_lock l(m_TransitTunnelsMutex); it = m_TransitTunnels.erase (it); diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 5225c8d8..024bb36d 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -226,7 +226,7 @@ namespace tunnel void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg) { auto typeID = msg.data->GetTypeID (); - LogPrint (eLogInfo, "TunnelMessage: handle fragment of ", msg.data->GetLength ()," bytes. Msg type ", (int)typeID); + LogPrint (eLogDebug, "TunnelMessage: handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID); switch (msg.deliveryType) { case eDeliveryTypeLocal: diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 3f92116d..72d6735a 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -203,7 +203,7 @@ namespace tunnel { for (auto it: m_Tests) { - LogPrint (eLogWarning, "Tunnels: test of ", (int)it.first, " failed"); + LogPrint (eLogWarning, "Tunnels: test of tunnel ", it.first, " failed"); // if test failed again with another tunnel we consider it failed if (it.second.first) { From 1d5194a13888d76e4bfa859e4b7ea5994a40d400 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 18 Jan 2016 00:00:00 +0000 Subject: [PATCH 0791/6300] * drop mapMultiArgs : it's not used anywhere --- Daemon.cpp | 2 +- util.cpp | 7 +------ util.h | 4 +--- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 48ef1e82..d947bbb0 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -65,7 +65,7 @@ namespace i2p LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); LogPrint(eLogDebug, "FS: data directory: ", i2p::util::filesystem::GetDataDir().string()); - i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs); + i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs); isDaemon = i2p::util::config::GetArg("-daemon", 0); isLogging = i2p::util::config::GetArg("-log", 1); diff --git a/util.cpp b/util.cpp index afb1d92b..850dfa89 100644 --- a/util.cpp +++ b/util.cpp @@ -70,12 +70,10 @@ namespace util namespace config { std::map mapArgs; - std::map > mapMultiArgs; void OptionParser(int argc, const char* const argv[]) { mapArgs.clear(); - mapMultiArgs.clear(); for (int i = 1; i < argc; i++) { std::string strKey (argv[i]); @@ -96,7 +94,6 @@ namespace config break; mapArgs[strKey] = strValue; - mapMultiArgs[strKey].push_back(strValue); } BOOST_FOREACH(PAIRTYPE(const std::string,std::string)& entry, mapArgs) @@ -196,8 +193,7 @@ namespace filesystem return pathTunnelsConfigFile; } - void ReadConfigFile(std::map& mapSettingsRet, - std::map >& mapMultiSettingsRet) + void ReadConfigFile(std::map& mapSettingsRet) { boost::filesystem::ifstream streamConfig(GetConfigFile()); if (!streamConfig.good()) @@ -214,7 +210,6 @@ namespace filesystem { mapSettingsRet[strKey] = it->value[0]; } - mapMultiSettingsRet[strKey].push_back(it->value[0]); } } diff --git a/util.h b/util.h index 8c72a97b..6478f894 100644 --- a/util.h +++ b/util.h @@ -17,7 +17,6 @@ namespace util namespace config { extern std::map mapArgs; - extern std::map > mapMultiArgs; void OptionParser(int argc, const char* const argv[]); int GetArg(const std::string& strArg, int nDefault); std::string GetArg(const std::string& strArg, const std::string& strDefault); @@ -34,8 +33,7 @@ namespace util boost::filesystem::path GetDefaultDataDir(); boost::filesystem::path GetConfigFile(); boost::filesystem::path GetTunnelsConfigFile(); - void ReadConfigFile(std::map& mapSettingsRet, - std::map >& mapMultiSettingsRet); + void ReadConfigFile(std::map& mapSettingsRet); boost::filesystem::path GetCertificatesDir(); } From 45d68d89a9e96e2353a2cf618097b4dc014f2f30 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 18 Jan 2016 00:00:00 +0000 Subject: [PATCH 0792/6300] * clean outdated declaration --- util.h | 1 - 1 file changed, 1 deletion(-) diff --git a/util.h b/util.h index 6478f894..040b3a34 100644 --- a/util.h +++ b/util.h @@ -20,7 +20,6 @@ namespace util void OptionParser(int argc, const char* const argv[]); int GetArg(const std::string& strArg, int nDefault); std::string GetArg(const std::string& strArg, const std::string& strDefault); - const char* GetCharArg(const std::string& strArg, const std::string& nDefault); } namespace filesystem From 314e1e4bfedf3ad959650d1053bbcac8f72c634e Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 18 Jan 2016 00:00:00 +0000 Subject: [PATCH 0793/6300] * unwrap i2p::util::config::* calls in ClientContext.cpp --- ClientContext.cpp | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index e93f9a4a..4e189f26 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -45,31 +45,38 @@ namespace client LoadPrivateKeys (keys, proxyKeys); localDestination = CreateNewLocalDestination (keys, false); } - LogPrint(eLogInfo, "Clients: starting HTTP Proxy"); - m_HttpProxy = new i2p::proxy::HTTPProxy(i2p::util::config::GetArg("-httpproxyaddress", "127.0.0.1"), i2p::util::config::GetArg("-httpproxyport", 4446), localDestination); + std::string httpProxyAddr = i2p::util::config::GetArg("-httpproxyaddress", "127.0.0.1"); + uint16_t httpProxyPort = i2p::util::config::GetArg("-httpproxyport", 4446); + LogPrint(eLogInfo, "Clients: starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); + m_HttpProxy = new i2p::proxy::HTTPProxy(httpProxyAddr, httpProxyPort, localDestination); m_HttpProxy->Start(); - LogPrint(eLogInfo, "Clients: starting SOCKS Proxy"); - m_SocksProxy = new i2p::proxy::SOCKSProxy(i2p::util::config::GetArg("-socksproxyaddress", "127.0.0.1"), i2p::util::config::GetArg("-socksproxyport", 4447), localDestination); + + std::string socksProxyAddr = i2p::util::config::GetArg("-socksproxyaddress", "127.0.0.1"); + uint16_t socksProxyPort = i2p::util::config::GetArg("-socksproxyport", 4447); + LogPrint(eLogInfo, "Clients: starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); + m_SocksProxy = new i2p::proxy::SOCKSProxy(socksProxyAddr, socksProxyPort, localDestination); m_SocksProxy->Start(); // I2P tunnels ReadTunnels (); // SAM - int samPort = i2p::util::config::GetArg("-samport", 0); + std::string samAddr = i2p::util::config::GetArg("-samaddress", "127.0.0.1"); + uint16_t samPort = i2p::util::config::GetArg("-samport", 0); if (samPort) { - LogPrint(eLogInfo, "Clients: starting SAM bridge"); - m_SamBridge = new SAMBridge (i2p::util::config::GetArg("-samaddress", "127.0.0.1"), samPort); + LogPrint(eLogInfo, "Clients: starting SAM bridge at", samAddr, ":", samPort); + m_SamBridge = new SAMBridge (samAddr, samPort); m_SamBridge->Start (); } // BOB - int bobPort = i2p::util::config::GetArg("-bobport", 0); + std::string bobAddr = i2p::util::config::GetArg("-bobaddress", "127.0.0.1"); + uint16_t bobPort = i2p::util::config::GetArg("-bobport", 0); if (bobPort) { - LogPrint(eLogInfo, "Clients: starting BOB command channel"); - m_BOBCommandChannel = new BOBCommandChannel (i2p::util::config::GetArg("-bobaddress", "127.0.0.1"), bobPort); + LogPrint(eLogInfo, "Clients: starting BOB command channel at ", bobAddr, ":", bobPort); + m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); m_BOBCommandChannel->Start (); } From 6740ec464c1be90edbe942f34fa07e3e5d4b1068 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 18 Jan 2016 00:00:00 +0000 Subject: [PATCH 0794/6300] * unwrap i2p::util::config::* calls in Daemon.cpp --- Daemon.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index d947bbb0..ca6681b4 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -120,8 +120,10 @@ namespace i2p g_Log->SetLogLevel(i2p::util::config::GetArg("-loglevel", "info")); } - LogPrint(eLogInfo, "Daemon: staring HTTP Server"); - d.httpServer = std::unique_ptr(new i2p::util::HTTPServer(i2p::util::config::GetArg("-httpaddress", "127.0.0.1"), i2p::util::config::GetArg("-httpport", 7070))); + std::string httpAddr = i2p::util::config::GetArg("-httpaddress", "127.0.0.1"); + uint16_t httpPort = i2p::util::config::GetArg("-httpport", 7070); + LogPrint(eLogInfo, "Daemon: staring HTTP Server at ", httpAddr, ":", httpPort); + d.httpServer = std::unique_ptr(new i2p::util::HTTPServer(httpAddr, httpPort)); d.httpServer->Start(); LogPrint(eLogInfo, "Daemon: starting NetDB"); @@ -140,12 +142,13 @@ namespace i2p LogPrint(eLogInfo, "Daemon: starting Client"); i2p::client::context.Start (); - // I2P Control - int i2pcontrolPort = i2p::util::config::GetArg("-i2pcontrolport", 0); - if (i2pcontrolPort) + // I2P Control Protocol + std::string i2pcpAddr = i2p::util::config::GetArg("-i2pcontroladdress", "127.0.0.1"); + uint16_t i2pcpPort = i2p::util::config::GetArg("-i2pcontrolport", 0); + if (i2pcpPort) { - LogPrint(eLogInfo, "Daemon: starting I2PControl"); - d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2p::util::config::GetArg("-i2pcontroladdress", "127.0.0.1"), i2pcontrolPort)); + LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort); + d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); d.m_I2PControlService->Start (); } return true; From 7565843fbe746d8948d6e996b717e3f69fa53dfd Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 18 Jan 2016 00:00:00 +0000 Subject: [PATCH 0795/6300] * move ReadConfigFile() : i2p::filesystem -> i2p::config * don't export i2p::config::mapArgs outside namespace --- Daemon.cpp | 2 +- util.cpp | 40 ++++++++++++++++++++-------------------- util.h | 3 +-- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index ca6681b4..1415435a 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -65,7 +65,7 @@ namespace i2p LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); LogPrint(eLogDebug, "FS: data directory: ", i2p::util::filesystem::GetDataDir().string()); - i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs); + i2p::util::config::ReadConfigFile(i2p::util::filesystem::GetConfigFile()); isDaemon = i2p::util::config::GetArg("-daemon", 0); isLogging = i2p::util::config::GetArg("-log", 1); diff --git a/util.cpp b/util.cpp index 850dfa89..cff2aa75 100644 --- a/util.cpp +++ b/util.cpp @@ -124,6 +124,26 @@ namespace config return atoi(mapArgs[strArg].c_str()); return nDefault; } + + void ReadConfigFile(boost::filesystem::path path) + { + boost::filesystem::ifstream streamConfig(path); + if (!streamConfig.good()) + return; // No i2pd.conf file is OK + + std::set setOptions; + setOptions.insert("*"); + + for (boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it) + { + // Don't overwrite existing settings so command line settings override i2pd.conf + std::string strKey = std::string("-") + it->string_key; + if (mapArgs.count(strKey) == 0) + { + mapArgs[strKey] = it->value[0]; + } + } + } } namespace filesystem @@ -193,26 +213,6 @@ namespace filesystem return pathTunnelsConfigFile; } - void ReadConfigFile(std::map& mapSettingsRet) - { - boost::filesystem::ifstream streamConfig(GetConfigFile()); - if (!streamConfig.good()) - return; // No i2pd.conf file is OK - - std::set setOptions; - setOptions.insert("*"); - - for (boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it) - { - // Don't overwrite existing settings so command line settings override i2pd.conf - std::string strKey = std::string("-") + it->string_key; - if (mapSettingsRet.count(strKey) == 0) - { - mapSettingsRet[strKey] = it->value[0]; - } - } - } - boost::filesystem::path GetDefaultDataDir() { // Windows < Vista: C:\Documents and Settings\Username\.i2pd diff --git a/util.h b/util.h index 040b3a34..905c2a8d 100644 --- a/util.h +++ b/util.h @@ -16,10 +16,10 @@ namespace util { namespace config { - extern std::map mapArgs; void OptionParser(int argc, const char* const argv[]); int GetArg(const std::string& strArg, int nDefault); std::string GetArg(const std::string& strArg, const std::string& strDefault); + void ReadConfigFile(boost::filesystem::path path); } namespace filesystem @@ -32,7 +32,6 @@ namespace util boost::filesystem::path GetDefaultDataDir(); boost::filesystem::path GetConfigFile(); boost::filesystem::path GetTunnelsConfigFile(); - void ReadConfigFile(std::map& mapSettingsRet); boost::filesystem::path GetCertificatesDir(); } From 937d346676067daf1d7d8a07722ba62cc3724b09 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 Jan 2016 10:29:07 -0500 Subject: [PATCH 0796/6300] set clove expiration time interval to 8 seconds --- Garlic.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index 0008e25e..71688305 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -208,7 +208,7 @@ namespace garlic size_t GarlicRoutingSession::CreateGarlicPayload (uint8_t * payload, std::shared_ptr msg, UnconfirmedTags * newTags) { - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 sec + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec uint32_t msgID; RAND_bytes ((uint8_t *)&msgID, 4); size_t size = 0; @@ -268,7 +268,7 @@ namespace garlic size_t GarlicRoutingSession::CreateGarlicClove (uint8_t * buf, std::shared_ptr msg, bool isDestination) { - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 sec + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec size_t size = 0; if (isDestination && m_Destination) { @@ -326,7 +326,7 @@ namespace garlic memcpy (buf + size, msg->GetBuffer (), msg->GetLength ()); size += msg->GetLength (); // fill clove - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 5000; // 5 sec + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec uint32_t cloveID; RAND_bytes ((uint8_t *)&cloveID, 4); htobe32buf (buf + size, cloveID); // CloveID From 9611f80a3935836866790594c658d39116674dae Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 Jan 2016 21:13:43 -0500 Subject: [PATCH 0797/6300] check I2NP messages fro expiration --- I2NPProtocol.cpp | 5 +++++ I2NPProtocol.h | 1 + NTCPSession.cpp | 9 +++++++-- SSUData.cpp | 5 ++++- TunnelEndpoint.cpp | 5 +++++ 5 files changed, 22 insertions(+), 3 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 854f258c..862e9ec7 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -51,6 +51,11 @@ namespace i2p SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 8000); } + bool I2NPMessage::IsExpired () const + { + return i2p::util::GetMillisecondsSinceEpoch () > GetExpiration (); + } + std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID) { auto msg = NewI2NPMessage (len); diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 9c41a06b..83c72463 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -196,6 +196,7 @@ namespace tunnel void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0); void RenewI2NPMessageHeader (); + bool IsExpired () const; }; template diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 24bc21e5..8515d1a9 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -579,8 +579,13 @@ namespace transport // we have a complete I2NP message uint8_t checksum[4]; htobe32buf (checksum, adler32 (adler32 (0, Z_NULL, 0), m_NextMessage->buf, m_NextMessageOffset - 4)); - if (!memcmp (m_NextMessage->buf + m_NextMessageOffset - 4, checksum, 4)) - m_Handler.PutNextMessage (m_NextMessage); + if (!memcmp (m_NextMessage->buf + m_NextMessageOffset - 4, checksum, 4)) + { + if (!m_NextMessage->IsExpired ()) + m_Handler.PutNextMessage (m_NextMessage); + else + LogPrint (eLogInfo, "NTCP: message expired"); + } else LogPrint (eLogWarning, "NTCP: Incorrect adler checksum of message, dropped"); m_NextMessage = nullptr; diff --git a/SSUData.cpp b/SSUData.cpp index cb9d1015..f57b25e2 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -238,7 +238,10 @@ namespace transport else ScheduleDecay (); m_ReceivedMessages.insert (msgID); - m_Handler.PutNextMessage (msg); + if (!msg->IsExpired ()) + m_Handler.PutNextMessage (msg); + else + LogPrint (eLogInfo, "SSU: message expired"); } else LogPrint (eLogWarning, "SSU: Message ", msgID, " already received"); diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 024bb36d..326036a6 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -225,6 +225,11 @@ namespace tunnel void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg) { + if (msg.data->IsExpired ()) + { + LogPrint (eLogInfo, "TunnelMessage: message expired"); + return; + } auto typeID = msg.data->GetTypeID (); LogPrint (eLogDebug, "TunnelMessage: handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID); switch (msg.deliveryType) From c5f6a690de879769992a58d151bd517b61263958 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 19 Jan 2016 00:00:00 +0000 Subject: [PATCH 0798/6300] * Daemon.h : use boolean variables for flags --- Daemon.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Daemon.h b/Daemon.h index 6252ffe4..e755e3e9 100644 --- a/Daemon.h +++ b/Daemon.h @@ -21,10 +21,10 @@ namespace i2p virtual bool start(); virtual bool stop(); - int isLogging; - int isDaemon; + bool isLogging; + bool isDaemon; - int running; + bool running; protected: Daemon_Singleton(); From 36750ab900c4dadc21b670c3a47b74b92c97c9e7 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 19 Jan 2016 00:00:00 +0000 Subject: [PATCH 0799/6300] * DaemonWin32 : separate --service (boolean) from --svcctl (string) option --- DaemonWin32.cpp | 9 +++------ docs/configuration.md | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/DaemonWin32.cpp b/DaemonWin32.cpp index 6e0c46b8..e09bf077 100644 --- a/DaemonWin32.cpp +++ b/DaemonWin32.cpp @@ -23,9 +23,10 @@ namespace i2p else isDaemon = 0; - std::string serviceControl = i2p::util::config::GetArg("-service", "none"); + std::string serviceControl = i2p::util::config::GetArg("-svcctl", ""); if (serviceControl == "install") { + LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service"); InstallService( SERVICE_NAME, // Name of service SERVICE_DISPLAY_NAME, // Name to display @@ -38,14 +39,10 @@ namespace i2p } else if (serviceControl == "remove") { + LogPrint(eLogInfo, "WinSVC: uninstalling ", SERVICE_NAME, " service"); UninstallService(SERVICE_NAME); exit(0); } - else if (serviceControl != "none") - { - printf(" --service=install to install the service.\n"); - printf(" --service=remove to remove the service.\n"); - } if (isDaemon == 1) { diff --git a/docs/configuration.md b/docs/configuration.md index 150101d4..7181cc4c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,6 +12,7 @@ Command line options * --loglevel= - Log messages above this level (debug, *info, warn, error) * --pidfile= - Where to write pidfile (dont write by default) * --daemon= - Enable or disable daemon mode. 1 for yes, 0 for no. +* --svcctl= - Windows service management (--svcctl="install" or --svcctl="remove") * --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). * --v6= - 1 if supports communication through ipv6, off by default * --floodfill= - 1 if router is floodfill, off by default From 18914978d5869edf1fd802d7ced051e27894224c Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 19 Jan 2016 09:36:56 -0500 Subject: [PATCH 0800/6300] pass X-I2P_DestB32 and X-I2P-DestB64 --- I2PTunnel.cpp | 3 ++- I2PTunnel.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index a281334f..9eccc1bd 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -196,8 +196,9 @@ namespace client // add X-I2P fields if (m_From) { + m_OutHeader << X_I2P_DEST_B32 << ": " << context.GetAddressBook ().ToAddress(m_From->GetIdentHash ()) << "\r\n"; m_OutHeader << X_I2P_DEST_HASH << ": " << m_From->GetIdentHash ().ToBase64 () << "\r\n"; - // m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n"; + m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n"; } if (endOfHeader) diff --git a/I2PTunnel.h b/I2PTunnel.h index 7a172468..4530a141 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -21,8 +21,8 @@ namespace client const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds // for HTTP tunnels const char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64 - //const char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64 - //const char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // full address in base32 + const char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64 + const char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // .b32.i2p address class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this { From 5482a57c458b4a6532909f64c432dfa39fe9ff2e Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 19 Jan 2016 11:16:50 -0500 Subject: [PATCH 0801/6300] add clock skew to expiration --- I2NPProtocol.cpp | 8 +++++--- I2NPProtocol.h | 3 +++ TunnelEndpoint.cpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 862e9ec7..8f528f3c 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -38,7 +38,7 @@ namespace i2p SetTypeID (msgType); if (!replyMsgID) RAND_bytes ((uint8_t *)&replyMsgID, 4); SetMsgID (replyMsgID); - SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 8000); // 8 secs means initial RTT + SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT); UpdateSize (); UpdateChks (); } @@ -48,12 +48,14 @@ namespace i2p uint32_t msgID; RAND_bytes ((uint8_t *)&msgID, 4); SetMsgID (msgID); - SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + 8000); + SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT); } bool I2NPMessage::IsExpired () const { - return i2p::util::GetMillisecondsSinceEpoch () > GetExpiration (); + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + auto exp = GetExpiration (); + return (ts > exp + I2NP_MESSAGE_CLOCK_SKEW) || (ts < exp - 3*I2NP_MESSAGE_CLOCK_SKEW); // check if expired or too far in future } std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID) diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 83c72463..898396d0 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -107,6 +107,9 @@ namespace tunnel const size_t I2NP_MAX_MESSAGE_SIZE = 32768; const size_t I2NP_MAX_SHORT_MESSAGE_SIZE = 4096; + const unsigned int I2NP_MESSAGE_EXPIRATION_TIMEOUT = 8000; // in milliseconds (as initial RTT) + const unsigned int I2NP_MESSAGE_CLOCK_SKEW = 60*1000; // 1 minute in milliseconds + struct I2NPMessage { uint8_t * buf; diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 326036a6..e63ea948 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -225,7 +225,7 @@ namespace tunnel void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg) { - if (msg.data->IsExpired ()) + if (!m_IsInbound && msg.data->IsExpired ()) { LogPrint (eLogInfo, "TunnelMessage: message expired"); return; From 6c4977ee78d5ab2962d5d46cdcd9735c2242ac1f Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 20 Jan 2016 00:00:00 +0000 Subject: [PATCH 0802/6300] * tune log messages --- SSU.cpp | 2 +- TunnelEndpoint.cpp | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 699f3e94..ddfd1501 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -231,7 +231,7 @@ namespace transport session = std::make_shared (*this, packet->from); session->WaitForConnect (); (*sessions)[packet->from] = session; - LogPrint (eLogInfo, "New SSU session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); + LogPrint (eLogInfo, "SSU: new session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); } } session->ProcessNextMessage (packet->buf, packet->len, packet->from); diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index e63ea948..151a4828 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -114,7 +114,7 @@ namespace tunnel if (ret.second) HandleOutOfSequenceFragment (msgID, ret.first->second); else - LogPrint (eLogError, "Incomplete message ", msgID, "already exists"); + LogPrint (eLogError, "TunnelMessage: Incomplete message ", msgID, "already exists"); } else { @@ -123,7 +123,7 @@ namespace tunnel } } else - LogPrint (eLogError, "Message is fragmented, but msgID is not presented"); + LogPrint (eLogError, "TunnelMessage: Message is fragmented, but msgID is not presented"); } fragment += size; @@ -147,13 +147,13 @@ namespace tunnel { if (msg.data->len + size > msg.data->maxLen) { - LogPrint (eLogInfo, "Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); + LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough"); auto newMsg = NewI2NPMessage (); *newMsg = *(msg.data); msg.data = newMsg; } if (msg.data->Concat (fragment, size) < size) // concatenate fragment - LogPrint (eLogError, "Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); + LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen); if (isLastFragment) { // message complete @@ -168,19 +168,19 @@ namespace tunnel } else { - LogPrint (eLogError, "Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size. Message dropped"); + LogPrint (eLogError, "TunnelMessage: Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped"); m_IncompleteMessages.erase (it); } } else { - LogPrint (eLogInfo, "Unexpected fragment ", (int)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ". Saved"); + LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); } } else { - LogPrint (eLogInfo, "First fragment of message ", msgID, " not found. Saved"); + LogPrint (eLogWarning, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); } } @@ -199,11 +199,11 @@ namespace tunnel { if (it->second.fragmentNum == msg.nextFragmentNum) { - LogPrint (eLogInfo, "Out-of-sequence fragment ", (int)it->second.fragmentNum, " of message ", msgID, " found"); + LogPrint (eLogWarning, "TunnelMessage: Out-of-sequence fragment ", (int)it->second.fragmentNum, " of message ", msgID, " found"); auto size = it->second.data->GetLength (); if (msg.data->len + size > msg.data->maxLen) { - LogPrint (eLogInfo, "Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); + LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); auto newMsg = NewI2NPMessage (); *newMsg = *(msg.data); msg.data = newMsg; @@ -241,13 +241,13 @@ namespace tunnel if (!m_IsInbound) // outbound transit tunnel i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); else - LogPrint (eLogError, "Delivery type tunnel arrived from an inbound tunnel. Dropped"); + LogPrint (eLogError, "TunnelMessage: Delivery type 'tunnel' arrived from an inbound tunnel, dropped"); break; case eDeliveryTypeRouter: if (!m_IsInbound) // outbound transit tunnel i2p::transport::transports.SendMessage (msg.hash, msg.data); else // we shouldn't send this message. possible leakage - LogPrint (eLogError, "Delivery type router arrived from an inbound tunnel. Dropped"); + LogPrint (eLogError, "TunnelMessage: Delivery type 'router' arrived from an inbound tunnel, dropped"); break; default: LogPrint (eLogError, "TunnelMessage: Unknown delivery type ", (int)msg.deliveryType); From 35200a1ee52aae5ac349ce2c0299b05edfa46fb3 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 20 Jan 2016 00:00:00 +0000 Subject: [PATCH 0803/6300] + new cmdline & config impl --- Config.cpp | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Config.h | 100 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 Config.cpp create mode 100644 Config.h diff --git a/Config.cpp b/Config.cpp new file mode 100644 index 00000000..4e6f52d3 --- /dev/null +++ b/Config.cpp @@ -0,0 +1,137 @@ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Config.h" +#include "version.h" + +using namespace boost::program_options; + +namespace i2p { +namespace config { + options_description m_OptionsDesc; + variables_map m_Options; + + void Init() { + options_description general("General options"); + general.add_options() + ("help,h", "Show this message") + ("conf,c", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf)") + ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg)") + ("pidfile", value()->default_value(""), "Write pidfile to given path") + ("log", value()->zero_tokens(), "Write logs to file instead stdout") + ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") + ("host", value()->default_value(""), "External IP (deprecated)") + ("port,p", value()->default_value(4567), "Port to listen for incoming connections") + ("ipv6,6", value()->zero_tokens(), "Enable communication through ipv6") + ("daemon", value()->zero_tokens(), "Router will go to background after start") + ("service", value()->zero_tokens(), "Router will use system folders like '/var/lib/i2pd'") + ("notransit", value()->zero_tokens(), "Router will not forward transit traffic") + ("floodfill", value()->zero_tokens(), "Router will try to become floodfill") + ("bandwidth", value()->default_value('O'), "Bandwidth limiting: L - 32kbps, O - 256Kbps, P - unlimited") + ; + + options_description httpserver("HTTP Server options"); + httpserver.add_options() + ("http.enabled", value()->default_value(true), "Enable or disable webconsole") + ("http.address", value()->default_value("127.0.0.1"), "Webconsole listen address") + ("http.port", value()->default_value(7070), "Webconsole listen port") + ; + + options_description httpproxy("HTTP Proxy options"); + httpproxy.add_options() + ("httpproxy.enabled", value()->default_value(true), "Enable or disable HTTP Proxy") + ("httpproxy.address", value()->default_value("127.0.0.1"), "HTTP Proxy listen address") + ("httpproxy.port", value()->default_value(4446), "HTTP Proxy listen port") + ("httpproxy.keys", value()->default_value("httpproxy-keys.dat"), "HTTP Proxy encryption keys") + ; + + options_description socksproxy("SOCKS Proxy options"); + socksproxy.add_options() + ("socksproxy.enabled", value()->default_value(true), "Enable or disable SOCKS Proxy") + ("socksproxy.address", value()->default_value("127.0.0.1"), "SOCKS Proxy listen address") + ("socksproxy.port", value()->default_value(4447), "SOCKS Proxy listen port") + ("socksproxy.keys", value()->default_value("socksproxy-keys.dat"), "SOCKS Proxy encryption keys") + ; + + options_description sam("SAM bridge options"); + sam.add_options() + ("sam.enabled", value()->default_value(false), "Enable or disable SAM Application bridge") + ("sam.address", value()->default_value("127.0.0.1"), "SAM listen address") + ("sam.port", value()->default_value(7656), "SAM listen port") + ; + + options_description bob("BOB options"); + bob.add_options() + ("bob.enabled", value()->default_value(false), "Enable or disable BOB command channel") + ("bob.address", value()->default_value("127.0.0.1"), "BOB listen address") + ("bob.port", value()->default_value(2827), "BOB listen port") + ; + + options_description i2pcontrol("I2PControl options"); + i2pcontrol.add_options() + ("i2pcontrol.enabled", value()->default_value(false), "Enable or disable I2P Control Protocol") + ("i2pcontrol.address", value()->default_value("127.0.0.1"), "I2PCP listen address") + ("i2pcontrol.port", value()->default_value(7650), "I2PCP listen port") + ; + + m_OptionsDesc + .add(general) + .add(httpserver) + .add(httpproxy) + .add(socksproxy) + .add(sam) + .add(bob) + .add(i2pcontrol) + ; + } + + void ParseCmdline(int argc, char* argv[]) { + try { + store(parse_command_line(argc, argv, m_OptionsDesc), m_Options); + } catch (boost::program_options::error e) { + std::cerr << "args: " << e.what() << std::endl; + exit(EXIT_FAILURE); + } + + if (m_Options.count("help")) { + std::cout << "i2pd version " << I2PD_VERSION << " (" << I2P_VERSION << ")" << std::endl; + std::cout << m_OptionsDesc; + exit(EXIT_SUCCESS); + } + } + + void ParseConfig(const std::string& path) { + std::ifstream config(path, std::ios::in); + + if (!config.is_open()) { + std::cerr << "missing/unreadable config file: " << path << std::endl; + exit(EXIT_FAILURE); + } + + try { + store(boost::program_options::parse_config_file(config, m_OptionsDesc), m_Options); + } catch (boost::program_options::error e) { + std::cerr << e.what() << std::endl; + exit(EXIT_FAILURE); + }; + } + + void Finalize() { + notify(m_Options); + }; +} // namespace config +} // namespace i2p diff --git a/Config.h b/Config.h new file mode 100644 index 00000000..5418022a --- /dev/null +++ b/Config.h @@ -0,0 +1,100 @@ +#ifndef CONFIG_H__ +#define CONFIG_H__ + +#include +#include +#include + +/** + * Functions to parse and store i2pd parameters + * + * General usage flow: + * Init() -- early as possible + * ParseCmdline() -- somewhere close to main() + * ParseConfig() -- after detecting path to config + * Finalize() -- right after all Parse*() functions called + * GetOption() -- may be called after Finalize() + */ + +namespace i2p { +namespace config { + extern boost::program_options::variables_map m_Options; + + /** + * @brief Initialize list of acceptable parameters + * + * Should be called before any Parse* functions. + */ + void Init(); + + /** + * @brief Parse cmdline parameters, and show help if requested + * @param argc Cmdline arguments count, should be passed from main(). + * @param argv Cmdline parameters array, should be passed from main() + * + * If --help is given in parameters, shows it's list with description + * terminates the program with exitcode 0. + * + * In case of parameter misuse boost throws an exception. + * We internally handle type boost::program_options::unknown_option, + * and then terminate program with exitcode 1. + * + * Other exceptions will be passed to higher level. + */ + void ParseCmdline(int argc, char* argv[]); + + /** + * @brief Load and parse given config file + * @param path Path to config file + * + * If error occured when opening file path is points to, + * we show the error message and terminate program. + * + * In case of parameter misuse boost throws an exception. + * We internally handle type boost::program_options::unknown_option, + * and then terminate program with exitcode 1. + * + * Other exceptions will be passed to higher level. + */ + void ParseConfig(const std::string& path); + + /** + * @brief Used to combine options from cmdline, config and default values + */ + void Finalize(); + + /* @brief Accessor to parameters by name + * @param name Name of the requested parameter + * @param value Variable where to store option + * @return this function returns false if parameter not found + * + * @example uint16_t port; GetOption("sam.port", port); + */ + template + bool GetOption(const char *name, T& value) { + if (!m_Options.count(name)) + return false; + value = m_Options[name].as(); + return true; + } + + /** + * @brief Set value of given parameter + * @param name Name of settable parameter + * @param value New parameter value + * @return true if value set up successful, false otherwise + * + * @example uint16_t port = 2827; SetOption("bob.port", port); + */ + template + bool SetOption(const char *name, const T& value) { + if (!m_Options.count(name)) + return false; + m_Options[name] = value; + notify(m_Options); + return true; + } +} +} + +#endif // CONFIG_H__ From 209934ad675357fa3078a7c9ba62b995266ca763 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 20 Jan 2016 00:00:00 +0000 Subject: [PATCH 0804/6300] * update buildsystems to include Config.cpp --- build/CMakeLists.txt | 1 + filelist.mk | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index cbcfc275..71ce57ad 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -17,6 +17,7 @@ set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) set ( CMAKE_SOURCE_DIR ".." ) set (LIBI2PD_SRC + "${CMAKE_SOURCE_DIR}/Config.cpp" "${CMAKE_SOURCE_DIR}/Crypto.cpp" "${CMAKE_SOURCE_DIR}/Garlic.cpp" "${CMAKE_SOURCE_DIR}/I2NPProtocol.cpp" diff --git a/filelist.mk b/filelist.mk index 147ef259..ce3a12f5 100644 --- a/filelist.mk +++ b/filelist.mk @@ -8,9 +8,9 @@ LIB_SRC = \ LIB_CLIENT_SRC = \ AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \ - SAM.cpp SOCKS.cpp HTTPProxy.cpp + SAM.cpp SOCKS.cpp HTTPProxy.cpp Config.cpp # also: Daemon{Linux,Win32}.cpp will be added later DAEMON_SRC = \ - HTTPServer.cpp I2PControl.cpp UPnP.cpp Daemon.cpp i2pd.cpp + HTTPServer.cpp I2PControl.cpp UPnP.cpp Daemon.cpp Config.cpp i2pd.cpp From 2335d3879e009613bfe13f432c5502de207258a9 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 20 Jan 2016 00:00:00 +0000 Subject: [PATCH 0805/6300] * migrate to new settings --- ClientContext.cpp | 65 ++++++++++++++++++++++++----------------- Config.h | 6 ++-- Daemon.cpp | 74 ++++++++++++++++++++++++++--------------------- DaemonLinux.cpp | 6 ++-- DaemonWin32.cpp | 3 +- RouterContext.cpp | 8 +++-- api.cpp | 5 +++- util.cpp | 19 ++++++++---- 8 files changed, 109 insertions(+), 77 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 4e189f26..57ccc32e 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -2,6 +2,7 @@ #include #include #include +#include "Config.h" #include "util.h" #include "Log.h" #include "Identity.h" @@ -37,44 +38,56 @@ namespace client } std::shared_ptr localDestination; - // proxies - std::string proxyKeys = i2p::util::config::GetArg("-proxykeys", ""); - if (proxyKeys.length () > 0) - { - i2p::data::PrivateKeys keys; - LoadPrivateKeys (keys, proxyKeys); - localDestination = CreateNewLocalDestination (keys, false); + bool httproxy; i2p::config::GetOption("httpproxy.enabled", httproxy); + if (httproxy) { + std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys); + std::string httpProxyAddr; i2p::config::GetOption("httpproxy.address", httpProxyAddr); + uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort); + LogPrint(eLogInfo, "Clients: starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); + if (httpProxyKeys.length () > 0) + { + i2p::data::PrivateKeys keys; + LoadPrivateKeys (keys, httpProxyKeys); + localDestination = CreateNewLocalDestination (keys, false); + } + m_HttpProxy = new i2p::proxy::HTTPProxy(httpProxyAddr, httpProxyPort, localDestination); + m_HttpProxy->Start(); } - std::string httpProxyAddr = i2p::util::config::GetArg("-httpproxyaddress", "127.0.0.1"); - uint16_t httpProxyPort = i2p::util::config::GetArg("-httpproxyport", 4446); - LogPrint(eLogInfo, "Clients: starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); - m_HttpProxy = new i2p::proxy::HTTPProxy(httpProxyAddr, httpProxyPort, localDestination); - m_HttpProxy->Start(); - std::string socksProxyAddr = i2p::util::config::GetArg("-socksproxyaddress", "127.0.0.1"); - uint16_t socksProxyPort = i2p::util::config::GetArg("-socksproxyport", 4447); - LogPrint(eLogInfo, "Clients: starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); - m_SocksProxy = new i2p::proxy::SOCKSProxy(socksProxyAddr, socksProxyPort, localDestination); - m_SocksProxy->Start(); + bool socksproxy; i2p::config::GetOption("socksproxy.enabled", socksproxy); + if (socksproxy) { + std::string socksProxyKeys; i2p::config::GetOption("socksproxy.keys", socksProxyKeys); + std::string socksProxyAddr; i2p::config::GetOption("socksproxy.address", socksProxyAddr); + uint16_t socksProxyPort; i2p::config::GetOption("socksproxy.port", socksProxyPort); + LogPrint(eLogInfo, "Clients: starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); + if (socksProxyKeys.length () > 0) + { + i2p::data::PrivateKeys keys; + LoadPrivateKeys (keys, socksProxyKeys); + localDestination = CreateNewLocalDestination (keys, false); + } + m_SocksProxy = new i2p::proxy::SOCKSProxy(socksProxyAddr, socksProxyPort, localDestination); + m_SocksProxy->Start(); + } // I2P tunnels ReadTunnels (); // SAM - std::string samAddr = i2p::util::config::GetArg("-samaddress", "127.0.0.1"); - uint16_t samPort = i2p::util::config::GetArg("-samport", 0); - if (samPort) - { - LogPrint(eLogInfo, "Clients: starting SAM bridge at", samAddr, ":", samPort); + bool sam; i2p::config::GetOption("sam.enabled", sam); + if (sam) { + std::string samAddr; i2p::config::GetOption("sam.address", samAddr); + uint16_t samPort; i2p::config::GetOption("sam.port", samPort); + LogPrint(eLogInfo, "Clients: starting SAM bridge at ", samAddr, ":", samPort); m_SamBridge = new SAMBridge (samAddr, samPort); m_SamBridge->Start (); } // BOB - std::string bobAddr = i2p::util::config::GetArg("-bobaddress", "127.0.0.1"); - uint16_t bobPort = i2p::util::config::GetArg("-bobport", 0); - if (bobPort) - { + bool bob; i2p::config::GetOption("bob.enabled", bob); + if (bob) { + std::string bobAddr; i2p::config::GetOption("bob.address", bobAddr); + uint16_t bobPort; i2p::config::GetOption("bob.port", bobPort); LogPrint(eLogInfo, "Clients: starting BOB command channel at ", bobAddr, ":", bobPort); m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); m_BOBCommandChannel->Start (); diff --git a/Config.h b/Config.h index 5418022a..51a12d70 100644 --- a/Config.h +++ b/Config.h @@ -1,5 +1,5 @@ -#ifndef CONFIG_H__ -#define CONFIG_H__ +#ifndef CONFIG_H +#define CONFIG_H #include #include @@ -97,4 +97,4 @@ namespace config { } } -#endif // CONFIG_H__ +#endif // CONFIG_H diff --git a/Daemon.cpp b/Daemon.cpp index 1415435a..8530b93e 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -3,6 +3,7 @@ #include "Daemon.h" +#include "Config.h" #include "Log.h" #include "Base.h" #include "version.h" @@ -50,52 +51,55 @@ namespace i2p bool Daemon_Singleton::IsService () const { + bool service = false; #ifndef _WIN32 - return i2p::util::config::GetArg("-service", 0); -#else - return false; + i2p::config::GetOption("service", service); #endif + return service; } bool Daemon_Singleton::init(int argc, char* argv[]) { + i2p::config::Init(); + i2p::config::ParseCmdline(argc, argv); + i2p::config::ParseConfig(i2p::util::filesystem::GetConfigFile().string()); + i2p::config::Finalize(); + i2p::crypto::InitCrypto (); - i2p::util::config::OptionParser(argc, argv); i2p::context.Init (); LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); LogPrint(eLogDebug, "FS: data directory: ", i2p::util::filesystem::GetDataDir().string()); - i2p::util::config::ReadConfigFile(i2p::util::filesystem::GetConfigFile()); - isDaemon = i2p::util::config::GetArg("-daemon", 0); - isLogging = i2p::util::config::GetArg("-log", 1); + i2p::config::GetOption("daemon", isDaemon); + i2p::config::GetOption("log", isLogging); - int port = i2p::util::config::GetArg("-port", 0); + uint16_t port; i2p::config::GetOption("port", port); if (port) i2p::context.UpdatePort (port); - std::string host = i2p::util::config::GetArg("-host", ""); + std::string host; i2p::config::GetOption("host", host); if (host != "") i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); - i2p::context.SetSupportsV6 (i2p::util::config::GetArg("-v6", 0)); - i2p::context.SetAcceptsTunnels (!i2p::util::config::GetArg("-notransit", 0)); - bool isFloodfill = i2p::util::config::GetArg("-floodfill", 0); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool transit; i2p::config::GetOption("notransit", transit); + i2p::context.SetSupportsV6 (ipv6); + i2p::context.SetAcceptsTunnels (!transit); + + bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); i2p::context.SetFloodfill (isFloodfill); - auto bandwidth = i2p::util::config::GetArg("-bandwidth", ""); - if (bandwidth.length () > 0) + + char bandwidth; i2p::config::GetOption("bandwidth", bandwidth); + if (bandwidth != '-') { - if (bandwidth[0] > 'O') - i2p::context.SetExtraBandwidth (); - else if (bandwidth[0] > 'L') - i2p::context.SetHighBandwidth (); - else - i2p::context.SetLowBandwidth (); + switch (bandwidth) { + case 'P' : i2p::context.SetExtraBandwidth (); break; + case 'L' : i2p::context.SetHighBandwidth (); break; + default : i2p::context.SetLowBandwidth (); break; + } } else if (isFloodfill) i2p::context.SetExtraBandwidth (); - LogPrint(eLogDebug, "Daemon: CMD parameters:"); - for (int i = 0; i < argc; ++i) - LogPrint(eLogDebug, i, ": ", argv[i]); return true; } @@ -117,14 +121,18 @@ namespace i2p } else { StartLog (""); // write to stdout } - g_Log->SetLogLevel(i2p::util::config::GetArg("-loglevel", "info")); + std::string loglevel; i2p::config::GetOption("loglevel", loglevel); + g_Log->SetLogLevel(loglevel); } - std::string httpAddr = i2p::util::config::GetArg("-httpaddress", "127.0.0.1"); - uint16_t httpPort = i2p::util::config::GetArg("-httpport", 7070); - LogPrint(eLogInfo, "Daemon: staring HTTP Server at ", httpAddr, ":", httpPort); - d.httpServer = std::unique_ptr(new i2p::util::HTTPServer(httpAddr, httpPort)); - d.httpServer->Start(); + bool http; i2p::config::GetOption("http.enabled", http); + if (http) { + std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); + uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); + LogPrint(eLogInfo, "Daemon: staring HTTP Server at ", httpAddr, ":", httpPort); + d.httpServer = std::unique_ptr(new i2p::util::HTTPServer(httpAddr, httpPort)); + d.httpServer->Start(); + } LogPrint(eLogInfo, "Daemon: starting NetDB"); i2p::data::netdb.Start(); @@ -143,10 +151,10 @@ namespace i2p i2p::client::context.Start (); // I2P Control Protocol - std::string i2pcpAddr = i2p::util::config::GetArg("-i2pcontroladdress", "127.0.0.1"); - uint16_t i2pcpPort = i2p::util::config::GetArg("-i2pcontrolport", 0); - if (i2pcpPort) - { + bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); + if (i2pcontrol) { + std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr); + uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort); LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort); d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); d.m_I2PControlService->Start (); diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 306fc7ca..89df5306 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -8,10 +8,10 @@ #include #include +#include "Config.h" #include "Log.h" #include "util.h" - void handle_signal(int sig) { switch (sig) @@ -28,7 +28,6 @@ void handle_signal(int sig) } } - namespace i2p { namespace util @@ -74,7 +73,7 @@ namespace i2p // Pidfile // this code is c-styled and a bit ugly, but we need fd for locking pidfile - pidfile = i2p::util::config::GetArg("-pidfile", ""); + std::string pidfile; i2p::config::GetOption("pidfile", pidfile); if (pidfile == "") { pidfile = IsService () ? "/var/run" : i2p::util::filesystem::GetDataDir().string(); pidfile.append("/i2pd.pid"); @@ -120,7 +119,6 @@ namespace i2p return Daemon_Singleton::stop(); } - } } diff --git a/DaemonWin32.cpp b/DaemonWin32.cpp index e09bf077..4ab65040 100644 --- a/DaemonWin32.cpp +++ b/DaemonWin32.cpp @@ -1,3 +1,4 @@ +#include "Config.h" #include "Daemon.h" #include "util.h" #include "Log.h" @@ -23,7 +24,7 @@ namespace i2p else isDaemon = 0; - std::string serviceControl = i2p::util::config::GetArg("-svcctl", ""); + std::string serviceControl; i2p::config::GetOption("svcctl", serviceControl); if (serviceControl == "install") { LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service"); diff --git a/RouterContext.cpp b/RouterContext.cpp index 7d35ccf1..db6f1c71 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -1,5 +1,6 @@ #include #include +#include "Config.h" #include "Crypto.h" #include "Timestamp.h" #include "I2NPProtocol.h" @@ -43,11 +44,12 @@ namespace i2p { i2p::data::RouterInfo routerInfo; routerInfo.SetRouterIdentity (GetIdentity ()); - int port = i2p::util::config::GetArg("-port", 0); + uint16_t port; i2p::config::GetOption("port", port); if (!port) port = rand () % (30777 - 9111) + 9111; // I2P network ports range - routerInfo.AddSSUAddress (i2p::util::config::GetArg("-host", "127.0.0.1").c_str (), port, routerInfo.GetIdentHash ()); - routerInfo.AddNTCPAddress (i2p::util::config::GetArg("-host", "127.0.0.1").c_str (), port); + std::string host; i2p::config::GetOption("host", host); + routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + routerInfo.AddNTCPAddress (host.c_str(), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC routerInfo.SetProperty ("coreVersion", I2P_VERSION); diff --git a/api.cpp b/api.cpp index 9a9c5c46..cd527550 100644 --- a/api.cpp +++ b/api.cpp @@ -1,5 +1,6 @@ #include #include +#include "Config.h" #include "Log.h" #include "NetDb.h" #include "Transports.h" @@ -18,7 +19,9 @@ namespace api void InitI2P (int argc, char* argv[], const char * appName) { i2p::util::filesystem::SetAppName (appName); - i2p::util::config::OptionParser(argc, argv); + i2p::config::Init (); + i2p::config::ParseCmdline (argc, argv); + i2p::config::Finalize (); i2p::crypto::InitCrypto (); i2p::context.Init (); } diff --git a/util.cpp b/util.cpp index cff2aa75..4d6b203d 100644 --- a/util.cpp +++ b/util.cpp @@ -13,6 +13,7 @@ #include #include #include +#include "Config.h" #include "util.h" #include "Log.h" @@ -166,8 +167,10 @@ namespace filesystem // TODO: datadir parameter is useless because GetDataDir is called before OptionParser // and mapArgs is not initialized yet - /*if (i2p::util::config::mapArgs.count("-datadir")) - path = boost::filesystem::system_complete(i2p::util::config::mapArgs["-datadir"]); + /* + std::string datadir; i2p::config::GetOption("datadir", datadir); + if (datadir != "") + path = boost::filesystem::system_complete(datadir); else */ path = GetDefaultDataDir(); @@ -200,14 +203,17 @@ namespace filesystem boost::filesystem::path GetConfigFile() { - boost::filesystem::path pathConfigFile(i2p::util::config::GetArg("-conf", "i2p.conf")); - if (!pathConfigFile.is_complete()) pathConfigFile = GetDataDir() / pathConfigFile; + std::string config; i2p::config::GetOption("conf", config); + boost::filesystem::path pathConfigFile(config); + if (!pathConfigFile.is_complete()) + pathConfigFile = GetDataDir() / pathConfigFile; return pathConfigFile; } boost::filesystem::path GetTunnelsConfigFile() { - boost::filesystem::path pathTunnelsConfigFile(i2p::util::config::GetArg("-tunnelscfg", "tunnels.cfg")); + std::string tunconf; i2p::config::GetOption("tunconf", tunconf); + boost::filesystem::path pathTunnelsConfigFile(tunconf); if (!pathTunnelsConfigFile.is_complete()) pathTunnelsConfigFile = GetDataDir() / pathTunnelsConfigFile; return pathTunnelsConfigFile; @@ -225,7 +231,8 @@ namespace filesystem SHGetFolderPath(NULL, CSIDL_PROFILE, 0, 0, localAppData); return boost::filesystem::path(std::string(localAppData) + "\\" + "." + appName); #else /* UNIX */ - if (i2p::util::config::GetArg("-service", 0)) // use system folder + bool service; i2p::config::GetOption("service", service); + if (service) // use system folder return boost::filesystem::path(std::string ("/var/lib/") + appName); boost::filesystem::path pathRet; char* pszHome = getenv("HOME"); From 1bcc31173896f4b50aeae411ae7b3d1e138a126b Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 20 Jan 2016 00:00:00 +0000 Subject: [PATCH 0806/6300] - drop i2p::util::config namespace : not used anymore --- util.cpp | 80 -------------------------------------------------------- util.h | 8 ------ 2 files changed, 88 deletions(-) diff --git a/util.cpp b/util.cpp index 4d6b203d..e559d7f9 100644 --- a/util.cpp +++ b/util.cpp @@ -67,86 +67,6 @@ namespace i2p { namespace util { - -namespace config -{ - std::map mapArgs; - - void OptionParser(int argc, const char* const argv[]) - { - mapArgs.clear(); - for (int i = 1; i < argc; i++) - { - std::string strKey (argv[i]); - std::string strValue; - size_t has_data = strKey.find('='); - if (has_data != std::string::npos) - { - strValue = strKey.substr(has_data+1); - strKey = strKey.substr(0, has_data); - } - -#ifdef WIN32 - boost::to_lower(strKey); - if (boost::algorithm::starts_with(strKey, "/")) - strKey = "-" + strKey.substr(1); -#endif - if (strKey[0] != '-') - break; - - mapArgs[strKey] = strValue; - } - - BOOST_FOREACH(PAIRTYPE(const std::string,std::string)& entry, mapArgs) - { - std::string name = entry.first; - - // interpret --foo as -foo (as long as both are not set) - if (name.find("--") == 0) - { - std::string singleDash(name.begin()+1, name.end()); - if (mapArgs.count(singleDash) == 0) - mapArgs[singleDash] = entry.second; - name = singleDash; - } - } - } - - std::string GetArg(const std::string& strArg, const std::string& strDefault) - { - if (mapArgs.count(strArg)) - return mapArgs[strArg]; - return strDefault; - } - - int GetArg(const std::string& strArg, int nDefault) - { - if (mapArgs.count(strArg)) - return atoi(mapArgs[strArg].c_str()); - return nDefault; - } - - void ReadConfigFile(boost::filesystem::path path) - { - boost::filesystem::ifstream streamConfig(path); - if (!streamConfig.good()) - return; // No i2pd.conf file is OK - - std::set setOptions; - setOptions.insert("*"); - - for (boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it) - { - // Don't overwrite existing settings so command line settings override i2pd.conf - std::string strKey = std::string("-") + it->string_key; - if (mapArgs.count(strKey) == 0) - { - mapArgs[strKey] = it->value[0]; - } - } - } -} - namespace filesystem { std::string appName ("i2pd"); diff --git a/util.h b/util.h index 905c2a8d..0377ef8d 100644 --- a/util.h +++ b/util.h @@ -14,14 +14,6 @@ namespace i2p { namespace util { - namespace config - { - void OptionParser(int argc, const char* const argv[]); - int GetArg(const std::string& strArg, int nDefault); - std::string GetArg(const std::string& strArg, const std::string& strDefault); - void ReadConfigFile(boost::filesystem::path path); - } - namespace filesystem { void SetAppName (const std::string& name); From 3686a27c19d0b9c9c36b6dbbd71c1bbcff972cb0 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 20 Jan 2016 00:00:00 +0000 Subject: [PATCH 0807/6300] * update docs/configuration.md --- docs/configuration.md | 75 ++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 7181cc4c..f3b84952 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,47 +4,64 @@ i2pd configuration Command line options -------------------- -* --host= - The external IP (deprecated). -* --port= - The port to listen on -* --httpaddress= - The address to listen on (HTTP server) -* --httpport= - The port to listen on (HTTP server) -* --log= - Enable or disable logging to file. 1 for yes, 0 for no. -* --loglevel= - Log messages above this level (debug, *info, warn, error) -* --pidfile= - Where to write pidfile (dont write by default) -* --daemon= - Enable or disable daemon mode. 1 for yes, 0 for no. -* --svcctl= - Windows service management (--svcctl="install" or --svcctl="remove") -* --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). -* --v6= - 1 if supports communication through ipv6, off by default -* --floodfill= - 1 if router is floodfill, off by default -* --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited -* --notransit= - 1 if router doesn't accept transit tunnels at startup. 0 by default -* --httpproxyaddress= - The address to listen on (HTTP Proxy) -* --httpproxyport= - The port to listen on (HTTP Proxy) 4446 by default -* --socksproxyaddress= - The address to listen on (SOCKS Proxy) -* --socksproxyport= - The port to listen on (SOCKS Proxy). 4447 by default -* --proxykeys= - optional keys file for proxy local destination (both HTTP and SOCKS) -* --samaddress= - The address to listen on (SAM bridge) -* --samport= - Port of SAM bridge. Usually 7656. SAM is off if not specified -* --bobaddress= - The address to listen on (BOB command channel) -* --bobport= - Port of BOB command channel. Usually 2827. BOB is off if not specified -* --i2pcontroladdress= - The address to listen on (I2P control service) -* --i2pcontrolport= - Port of I2P control service. Usually 7650. I2PControl is off if not specified -* --tunnelscfg= - Tunnels Config file (default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg) * --conf= - Config file (default: ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf) This parameter will be silently ignored if the specified config file does not exist. Options specified on the command line take precedence over those in the config file. +* --tunconf= - Tunnels Config file (default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg) +* --pidfile= - Where to write pidfile (dont write by default) +* --log - Enable or disable logging to file. 1 for yes, 0 for no. +* --loglevel= - Log messages above this level (debug, *info, warn, error) +* --host= - The external IP (deprecated) +* --port= - The port to listen on +* --daemon - Enable or disable daemon mode. 1 for yes, 0 for no. +* --svcctl= - Windows service management (--svcctl="install" or --svcctl="remove") +* --service - Use system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). +* --ipv6 - Enable communication through ipv6, off by default +* --notransit - Router will not accept transit tunnels at startup. 0 by default +* --floodfill - Router will be floodfill, off by default +* --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited + +* --http.address= - The address to listen on (HTTP server) +* --http.port= - The port to listen on (HTTP server) + +* --httpproxy.address= - The address to listen on (HTTP Proxy) +* --httpproxy.port= - The port to listen on (HTTP Proxy) 4446 by default +* --httpproxy.keys= - optional keys file for proxy local destination (both HTTP and SOCKS) + +* --socksproxy.address= - The address to listen on (SOCKS Proxy) +* --socksproxy.port= - The port to listen on (SOCKS Proxy). 4447 by default +* --socksproxy.keys= - optional keys file for proxy local destination (both HTTP and SOCKS) + +* --sam.address= - The address to listen on (SAM bridge) +* --sam.port= - Port of SAM bridge. Usually 7656. SAM is off if not specified + +* --bob.address= - The address to listen on (BOB command channel) +* --bob.port= - Port of BOB command channel. Usually 2827. BOB is off if not specified + +* --i2pcontrol.address= - The address to listen on (I2P control service) +* --i2pcontrol.port= - Port of I2P control service. Usually 7650. I2PControl is off if not specified Config files ------------ INI-like, syntax is the following : = . Comments are "#", not ";" as you may expect. See [boost ticket](https://svn.boost.org/trac/boost/ticket/808) -All command-line parameters are allowed as keys, for example: +All command-line parameters are allowed as keys, but note for those which contains dot (.). + +For example: i2p.conf: - log = 1 - v6 = 0 + # comment + log = yes + ipv6 = yes + # settings for specific module + [httpproxy] + port = 4444 + # ^^ this will be --httproxy.port= in cmdline + # another one + [sam] + enabled = yes tunnels.cfg (filename of this config is subject of change): From 1f6f4d9c493598635bd4eae40076f3032c1fa3df Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 20 Jan 2016 00:00:00 +0000 Subject: [PATCH 0808/6300] + docs/config_opts_after_2.3.0.md --- docs/config_opts_after_2.3.0.md | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 docs/config_opts_after_2.3.0.md diff --git a/docs/config_opts_after_2.3.0.md b/docs/config_opts_after_2.3.0.md new file mode 100644 index 00000000..1b2adcdb --- /dev/null +++ b/docs/config_opts_after_2.3.0.md @@ -0,0 +1,43 @@ +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ параметров в релизах > 2.3.0 +------------------------------------------------ + +СиÑтема параметров отличаетÑÑ Ð¾Ñ‚ того, что было ранее и доÑтаточно Ñильно: + +* Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ð¼Ñ‘Ð½ и ÑÑ‚Ð¸Ð»Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð² + +Ð’Ñе параметры теперь в виде --help (gnu-style), у некоторых еÑть шорткаты в виде -h (unix-style). +Это каÑаетÑÑ Ð²Ñех ÑиÑтем, в том чиÑле винды. + +--daemon=1 и подобное -> проÑто --daemon, без параметра. Ðет опции - false, еÑть - true +--notransit=1 -> --notransit, то же что и выше: еÑть Ð¾Ð¿Ñ†Ð¸Ñ - false, нет - true +--v6 -> --ipv6 (первое было похоже на верÑию какого-то Ñвоего протокола, типа socksproxy --v5) +--tunnelscfg -> --tunconf (Ð¸Ð¼Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð° было Ñлишком длинным, cfg переделан на conf - единообразно Ñ --conf) +--sockskeys -> разделён на два, Ð´Ð»Ñ socks и httpproxy по-отдельноÑти + +* поддержка Ñекций в оÑновном конфиге + +ВыглÑдит Ñто так: + + # оÑновные опции + pidfile = /var/run/i2pd.pid + # + # наÑтройки конкретного Ð¼Ð¾Ð´ÑƒÐ»Ñ + [httproxy] + address = 1.2.3.4 + port = 4446 + keys = httproxy-keys.dat + # и так далее + [sam] + enabled = no + addresss = 127.0.0.2 + # ^^ переопределÑетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ адреÑ, оÑтальное берётÑÑ Ð¸Ð· дефолта + +Точно так же ÑÐµÐ¹Ñ‡Ð°Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ð°ÐµÑ‚ конфиг туннелей: ÑÐµÐºÑ†Ð¸Ñ Ð´Ð¾ точки - имÑ, поÑле - параметр + +* поддержка Ð²Ñ‹ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð´ÐµÐ»ÑŒÐ½Ñ‹Ñ… ÑервиÑов "на корню" Ñм sam.enabled и подобное + +Это позволило задать дефолт Ð´Ð»Ñ Ð½Ð¾Ð¼ÐµÑ€Ð° порта и не пиÑать его руками Ð´Ð»Ñ Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ. + +* добавлен --help (Ñм #110) + +* приÑутÑтвует Ð½ÐµÐºÐ°Ñ Ð²Ð°Ð»Ð¸Ð´Ð°Ñ†Ð¸Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð², --port=abcd - не прокатит, --port=100500 - тоже From 8aa158c1e0b61ee5156e61a01c1e0755ae8b80b5 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 20 Jan 2016 00:00:00 +0000 Subject: [PATCH 0809/6300] * update debian/ --- debian/i2pd.conf | 23 +++++++++++++---------- debian/i2pd.init | 2 +- debian/i2pd.upstart | 6 +++--- debian/postinst | 6 ++++-- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/debian/i2pd.conf b/debian/i2pd.conf index a6e9c23f..4a518916 100644 --- a/debian/i2pd.conf +++ b/debian/i2pd.conf @@ -1,16 +1,19 @@ -floodfill=0 -v6=0 +ipv6 -httpproxyaddress=127.0.0.1 -httpproxyport=4444 +[httpproxy] +address = 127.0.0.1 +port = 4444 # other services (disabled by default) # -# samaddress=127.0.0.1 -# samport=7656 +#[sam] +#address = 127.0.0.1 +#port = 7656 # -# bobaddress=127.0.0.1 -# bobport=2827 +#[bob] +#address = 127.0.0.1 +#port = 2827 # -# i2pcontroladdress=127.0.0.1 -# i2pcontrolport=7650 +#[i2pcontrol] +#address = 127.0.0.1 +#port = 7650 diff --git a/debian/i2pd.init b/debian/i2pd.init index 609a3407..d87aa000 100644 --- a/debian/i2pd.init +++ b/debian/i2pd.init @@ -46,7 +46,7 @@ do_start() start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" -- \ - --service=1 --daemon=1 --log=1 --conf=$I2PCONF --tunnelscfg=$TUNCONF \ + --service --daemon --log --conf=$I2PCONF --tunconf=$TUNCONF \ --port=$I2PD_PORT $DAEMON_OPTS > /dev/null 2>&1 \ || return 2 return $? diff --git a/debian/i2pd.upstart b/debian/i2pd.upstart index 0527935d..d1536ea3 100644 --- a/debian/i2pd.upstart +++ b/debian/i2pd.upstart @@ -4,7 +4,7 @@ start on runlevel [2345] stop on runlevel [016] or unmounting-filesystem # these can be overridden in /etc/init/i2pd.override -env I2P_HOST="1.2.3.4" -env I2P_PORT="4567" +env I2PD_HOST="1.2.3.4" +env I2PD_PORT="4567" -exec /usr/sbin/i2pd --daemon=0 --log=1 --host=$I2P_HOST --port=$I2P_PORT +exec /usr/sbin/i2pd --daemon --log --host=$I2PD_HOST --port=$I2PD_PORT diff --git a/debian/postinst b/debian/postinst index 4be6bf3f..1de58c1e 100755 --- a/debian/postinst +++ b/debian/postinst @@ -1,6 +1,7 @@ #!/bin/sh set -e +LOGFILE='/var/log/i2pd.log' I2PDHOME='/var/lib/i2pd' I2PDUSER='i2pd' @@ -16,8 +17,9 @@ case "$1" in adduser --system --quiet --group --home $I2PDHOME $I2PDUSER fi - touch /var/log/i2pd.log - chown -f ${I2PDUSER}:adm /var/log/i2pd.log + touch $LOGFILE + chmod 640 $LOGFILE + chown -f ${I2PDUSER}:adm $LOGFILE mkdir -p -m0750 $I2PDHOME chown -f -R -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} ;; From bc41a15ebaf97fccd3a8543656b4e198dee8ca3f Mon Sep 17 00:00:00 2001 From: 0niichan Date: Thu, 21 Jan 2016 01:33:28 +0700 Subject: [PATCH 0810/6300] Update Makefile.mingw new default boost' suffix; new paths --- Makefile.mingw | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile.mingw b/Makefile.mingw index c2fd35a9..ad5a8ade 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,7 +1,7 @@ CXX = g++ CXXFLAGS = -O2 -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN NEEDED_CXXFLAGS = -std=c++11 -BOOST_SUFFIX = -mgw48-mt-1_59 -INCFLAGS = -I/usr/include/ -I/usr/local/include/ -I/c/dev/openssl/include -I/c/dev/boost/include/boost-1_59 -LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/c/dev/openssl -L/c/dev/boost/lib -LDLIBS = -lboost_system$(BOOST_SUFFIX) -lboost_date_time$(BOOST_SUFFIX) -lboost_filesystem$(BOOST_SUFFIX) -lboost_regex$(BOOST_SUFFIX) -lboost_program_options$(BOOST_SUFFIX) -lssl -lcrypto -lz -lwsock32 -lws2_32 -lgdi32 -liphlpapi -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -lpthread +BOOST_SUFFIX = -mt // Suffix of your boost libraries +INCFLAGS = -I/usr/include/ -I/usr/lib -I/usr/local/include/ -I/usr/include/boost -I/c/dev/openssl/include +LDFLAGS = -Wl,-rpath,/usr/lib -L/usr/lib -L/usr/include -L/usr/include/boost +LDLIBS = -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) -Wl,-Bstatic -lssl -Wl,-Bstatic -lcrypto -Wl,-Bstatic -lz -Wl,-Bstatic -lwsock32 -Wl,-Bstatic -lws2_32 -Wl,-Bstatic -lgdi32 -Wl,-Bstatic -liphlpapi -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -Wl,-Bstatic -lpthread From e5037fc9f9ea899b8a6b15940486f1a2edbb9bfc Mon Sep 17 00:00:00 2001 From: 0niichan Date: Thu, 21 Jan 2016 02:05:16 +0700 Subject: [PATCH 0811/6300] new default boost' suffix; new paths --- Makefile.mingw | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile.mingw b/Makefile.mingw index ad5a8ade..19a71e9b 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,7 +1,7 @@ CXX = g++ CXXFLAGS = -O2 -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN NEEDED_CXXFLAGS = -std=c++11 -BOOST_SUFFIX = -mt // Suffix of your boost libraries -INCFLAGS = -I/usr/include/ -I/usr/lib -I/usr/local/include/ -I/usr/include/boost -I/c/dev/openssl/include -LDFLAGS = -Wl,-rpath,/usr/lib -L/usr/lib -L/usr/include -L/usr/include/boost +BOOST_SUFFIX = -mt # Suffix of your boost libraries +INCFLAGS = -I/usr/include -I/usr/lib -I/usr/include/boost -I/usr/include/openssl +LDFLAGS = -L/usr/include -L/usr/lib -L/usr/include/boost -L/usr/include/openssl -Wl,-rpath,/usr/lib LDLIBS = -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) -Wl,-Bstatic -lssl -Wl,-Bstatic -lcrypto -Wl,-Bstatic -lz -Wl,-Bstatic -lwsock32 -Wl,-Bstatic -lws2_32 -Wl,-Bstatic -lgdi32 -Wl,-Bstatic -liphlpapi -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -Wl,-Bstatic -lpthread From bd0eb81c1bac55361acbdbce791050b61e5d108d Mon Sep 17 00:00:00 2001 From: 0niichan Date: Thu, 21 Jan 2016 03:52:13 +0700 Subject: [PATCH 0812/6300] add "mkdir obj/Win32" in Windows --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 2e86fd88..b11305ce 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ else ifeq ($(UNAME),Linux) include Makefile.linux else # win32 mingw DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp + WINDIR := True include Makefile.mingw endif @@ -30,6 +31,9 @@ all: mk_build_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) mk_build_dir: mkdir -p obj + ifeq ($(WINDIR),True) + mkdir -p obj/Win32 + endif api: mk_build_dir $(SHLIB) $(ARLIB) api_client: mk_build_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) From 55c279cc7e97b11033bdbd3f6b3655d98cf59906 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 20 Jan 2016 21:51:18 -0500 Subject: [PATCH 0813/6300] Rolled back to working Makefile.mingw --- Makefile.mingw | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile.mingw b/Makefile.mingw index 19a71e9b..57fe094b 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,7 +1,7 @@ CXX = g++ CXXFLAGS = -O2 -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN NEEDED_CXXFLAGS = -std=c++11 -BOOST_SUFFIX = -mt # Suffix of your boost libraries -INCFLAGS = -I/usr/include -I/usr/lib -I/usr/include/boost -I/usr/include/openssl -LDFLAGS = -L/usr/include -L/usr/lib -L/usr/include/boost -L/usr/include/openssl -Wl,-rpath,/usr/lib +BOOST_SUFFIX = -mgw48-mt-1_59 +INCFLAGS = -I/usr/include/ -I/usr/local/include/ -I/c/dev/openssl/include -I/c/dev/boost/include/boost-1_59 +LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/c/dev/openssl -L/c/dev/boost/lib LDLIBS = -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) -Wl,-Bstatic -lssl -Wl,-Bstatic -lcrypto -Wl,-Bstatic -lz -Wl,-Bstatic -lwsock32 -Wl,-Bstatic -lws2_32 -Wl,-Bstatic -lgdi32 -Wl,-Bstatic -liphlpapi -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -Wl,-Bstatic -lpthread From 23cf6ebc89d5340372eda266bd906aa28118647d Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 21 Jan 2016 07:35:26 +0000 Subject: [PATCH 0814/6300] * add new option 'i2pcontrol.password' --- Config.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Config.cpp b/Config.cpp index 4e6f52d3..b2121b4e 100644 --- a/Config.cpp +++ b/Config.cpp @@ -86,6 +86,7 @@ namespace config { ("i2pcontrol.enabled", value()->default_value(false), "Enable or disable I2P Control Protocol") ("i2pcontrol.address", value()->default_value("127.0.0.1"), "I2PCP listen address") ("i2pcontrol.port", value()->default_value(7650), "I2PCP listen port") + ("i2pcontrol.password", value()->default_value("itoopie"), "I2PCP access password") ; m_OptionsDesc From e1a1aef990849ae654a350c6c1a496204bb48412 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 21 Jan 2016 07:37:38 +0000 Subject: [PATCH 0815/6300] * I2PControl : use password option from main config --- I2PControl.cpp | 6 ++++-- I2PControl.h | 6 ------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index a706cd79..534a85d7 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -12,6 +12,7 @@ #include #endif #include "Log.h" +#include "Config.h" #include "NetDb.h" #include "RouterContext.h" #include "Daemon.h" @@ -26,11 +27,12 @@ namespace i2p namespace client { I2PControlService::I2PControlService (const std::string& address, int port): - m_Password (I2P_CONTROL_DEFAULT_PASSWORD), m_IsRunning (false), m_Thread (nullptr), + m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), m_SSLContext (m_Service, boost::asio::ssl::context::sslv23), m_ShutdownTimer (m_Service) { + GetOption("i2pcontrol.password", m_Password); LoadConfig (); // certificate auto path = GetPath (); @@ -385,7 +387,7 @@ namespace client void I2PControlService::PasswordHandler (const std::string& value) { - LogPrint (eLogDebug, "I2PControl new password=", value); + LogPrint (eLogDebug, "I2PControl: new password=", value, ", to make it persistent you should update your config!"); m_Password = value; m_Tokens.clear (); SaveConfig (); diff --git a/I2PControl.h b/I2PControl.h index 10b6b651..bed84666 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -26,7 +26,6 @@ namespace client const char I2P_CONTROL_KEY_FILE[] = "key.pem"; const char I2P_CONTROL_CERT_FILE[] = "cert.pem"; const char I2P_CONTROL_CONFIG_FILE[] = "i2pcontrol.conf"; - const char I2P_CONTROL_DEFAULT_PASSWORD[] = "itoopie"; const char I2P_CONTROL_PROPERTY_ID[] = "id"; const char I2P_CONTROL_PROPERTY_METHOD[] = "method"; @@ -48,11 +47,6 @@ namespace client const char I2P_CONTROL_PARAM_ECHO[] = "Echo"; const char I2P_CONTROL_PARAM_RESULT[] = "Result"; - // I2PControl - const char I2P_CONTROL_I2PCONTROL_ADDRESS[] = "i2pcontrol.address"; - const char I2P_CONTROL_I2PCONTROL_PASSWORD[] = "i2pcontrol.password"; - const char I2P_CONTROL_I2PCONTROL_PORT[] = "i2pcontrol.port"; - // RouterInfo requests const char I2P_CONTROL_ROUTER_INFO_UPTIME[] = "i2p.router.uptime"; const char I2P_CONTROL_ROUTER_INFO_VERSION[] = "i2p.router.version"; From db9c20f3dd0c77c8a1396fb92461caa790fa9a32 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 21 Jan 2016 07:38:11 +0000 Subject: [PATCH 0816/6300] * I2PControl : move boost1.49+gcc4.7 hack --- I2PControl.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 534a85d7..8c3380b8 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -1,5 +1,3 @@ -// There is bug in boost 1.49 with gcc 4.7 coming with Debian Wheezy -#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) #include #include #include @@ -8,9 +6,13 @@ #include #include #include + +// There is bug in boost 1.49 with gcc 4.7 coming with Debian Wheezy +#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) #if !GCC47_BOOST149 #include #endif + #include "Log.h" #include "Config.h" #include "NetDb.h" From 2cace0008e3534fbf88c3e74aca9bd2c405e240a Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 21 Jan 2016 07:41:09 +0000 Subject: [PATCH 0817/6300] - I2PControlService::SaveConfig : not used anymore --- I2PControl.cpp | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 8c3380b8..39817e92 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -110,24 +110,7 @@ namespace client LogPrint (eLogError, "Can't read ", filename, ": ", ex.what ()); } } - m_Password = pt.get (I2P_CONTROL_I2PCONTROL_PASSWORD, I2P_CONTROL_DEFAULT_PASSWORD); - if (isNew) SaveConfig (); - } - - void I2PControlService::SaveConfig () - { - boost::property_tree::ptree pt; - pt.put (I2P_CONTROL_I2PCONTROL_PASSWORD, m_Password); - auto filename = GetPath () / I2P_CONTROL_CONFIG_FILE; - // we take care about directory in LoadConfig - try - { - boost::property_tree::write_ini (filename.string (), pt); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "Can't write ", filename, ": ", ex.what ()); - } + GetOption("i2pcontrol.password", m_Password); } void I2PControlService::Start () @@ -392,7 +375,6 @@ namespace client LogPrint (eLogDebug, "I2PControl: new password=", value, ", to make it persistent you should update your config!"); m_Password = value; m_Tokens.clear (); - SaveConfig (); } // RouterInfo From 928abf7094753030c931e122f0ea0110abc0793a Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 21 Jan 2016 07:46:17 +0000 Subject: [PATCH 0818/6300] - I2PControlService::LoadConfig : not used anymore --- I2PControl.cpp | 28 +--------------------------- I2PControl.h | 4 ---- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 39817e92..062f02ff 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -35,7 +35,7 @@ namespace client m_ShutdownTimer (m_Service) { GetOption("i2pcontrol.password", m_Password); - LoadConfig (); + // certificate auto path = GetPath (); if (!boost::filesystem::exists (path)) @@ -87,32 +87,6 @@ namespace client Stop (); } - void I2PControlService::LoadConfig () - { - auto path = GetPath (); - if (!boost::filesystem::exists (path)) - { - if (!boost::filesystem::create_directory (path)) - LogPrint (eLogError, "Failed to create i2pcontrol directory"); - } - boost::property_tree::ptree pt; - auto filename = path / I2P_CONTROL_CONFIG_FILE; - bool isNew = true; - if (boost::filesystem::exists (filename)) - { - try - { - boost::property_tree::read_ini (filename.string (), pt); - isNew = false; - } - catch (std::exception& ex) - { - LogPrint (eLogError, "Can't read ", filename, ": ", ex.what ()); - } - } - GetOption("i2pcontrol.password", m_Password); - } - void I2PControlService::Start () { if (!m_IsRunning) diff --git a/I2PControl.h b/I2PControl.h index bed84666..9d97b267 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -81,9 +81,6 @@ namespace client private: - void LoadConfig (); - void SaveConfig (); - void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); @@ -99,7 +96,6 @@ namespace client boost::filesystem::path GetPath () const { return i2p::util::filesystem::GetDefaultDataDir() / I2P_CONTROL_PATH; }; void CreateCertificate (); - private: From f3a7c233b386ebccb1bd7413b55a0877f712bfa9 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 21 Jan 2016 12:40:07 +0000 Subject: [PATCH 0819/6300] * I2PControl.cpp : #329 --- I2PControl.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index a706cd79..0c7b63ca 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -347,8 +347,10 @@ namespace client int api = params.get (I2P_CONTROL_PARAM_API); auto password = params.get (I2P_CONTROL_PARAM_PASSWORD); LogPrint (eLogDebug, "I2PControl Authenticate API=", api, " Password=", password); - if (password != m_Password) - LogPrint (eLogError, "I2PControl Authenticate Invalid password ", password, " expected ", m_Password); + if (password != m_Password) { + LogPrint (eLogError, "I2PControl: Authenticate - Invalid password: ", password); + return; + } InsertParam (results, I2P_CONTROL_PARAM_API, api); results << ","; std::string token = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); From 97ca8b7ada70418f4987d83b276d614f4199668d Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 21 Jan 2016 12:56:53 +0000 Subject: [PATCH 0820/6300] * fix build --- I2PControl.cpp | 2 +- I2PControl.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 062f02ff..eabea408 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -34,7 +34,7 @@ namespace client m_SSLContext (m_Service, boost::asio::ssl::context::sslv23), m_ShutdownTimer (m_Service) { - GetOption("i2pcontrol.password", m_Password); + i2p::config::GetOption("i2pcontrol.password", m_Password); // certificate auto path = GetPath (); diff --git a/I2PControl.h b/I2PControl.h index 9d97b267..7f8e7c30 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -47,6 +47,8 @@ namespace client const char I2P_CONTROL_PARAM_ECHO[] = "Echo"; const char I2P_CONTROL_PARAM_RESULT[] = "Result"; + const char I2P_CONTROL_I2PCONTROL_PASSWORD[] = "i2pcontrol.password"; + // RouterInfo requests const char I2P_CONTROL_ROUTER_INFO_UPTIME[] = "i2p.router.uptime"; const char I2P_CONTROL_ROUTER_INFO_VERSION[] = "i2p.router.version"; From 431af2c0ddfa8c3b1385b420be24c7099a125f22 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 21 Jan 2016 15:51:08 -0500 Subject: [PATCH 0821/6300] fixed issue #331. reuse existing local detination for tunnels --- ClientContext.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 4e189f26..c1a1a274 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -273,7 +273,9 @@ namespace client { i2p::data::PrivateKeys k; LoadPrivateKeys (k, keys, sigType); - localDestination = CreateNewLocalDestination (k, false, &options); + localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); + if (!localDestination) + localDestination = CreateNewLocalDestination (k, false, &options); } auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); if (m_ClientTunnels.insert (std::make_pair (port, std::unique_ptr(clientTunnel))).second) @@ -296,9 +298,12 @@ namespace client std::map options; ReadI2CPOptions (section, options); + std::shared_ptr localDestination = nullptr; i2p::data::PrivateKeys k; LoadPrivateKeys (k, keys, sigType); - auto localDestination = CreateNewLocalDestination (k, true, &options); + localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); + if (!localDestination) + localDestination = CreateNewLocalDestination (k, true, &options); I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ? new I2PServerTunnelHTTP (name, host, port, localDestination, inPort) : new I2PServerTunnel (name, host, port, localDestination, inPort); From 92830172f94f450ae1c0c6eecadf0a60dd88a753 Mon Sep 17 00:00:00 2001 From: o Date: Fri, 22 Jan 2016 16:08:54 +0500 Subject: [PATCH 0822/6300] asdf --- HTTPProxy.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 12d2c765..c5466d4f 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -183,6 +183,19 @@ namespace proxy bool HTTPProxyHandler::HandleData(uint8_t *http_buff, std::size_t len) { assert(len); // This should always be called with a least a byte left to parse + + + // remove "Referer" from http requst + http_buff[len] = '\0'; + char *start = strstr((char *)http_buff, "\nReferer:"); + if (start!=0) + { + char *end = strstr(start+1, "\n"); + strncpy(start, end, (char*)(http_buff + len) - end); + len = len - (end - start); + } + + while (len > 0) { //TODO: fallback to finding HOst: header if needed From 939c28b74be424cb089d6d40dc4bd1b36c492cdf Mon Sep 17 00:00:00 2001 From: xcps Date: Fri, 22 Jan 2016 16:30:24 +0500 Subject: [PATCH 0823/6300] removed extra lines --- HTTPProxy.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index c5466d4f..17b8e816 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -184,7 +184,6 @@ namespace proxy { assert(len); // This should always be called with a least a byte left to parse - // remove "Referer" from http requst http_buff[len] = '\0'; char *start = strstr((char *)http_buff, "\nReferer:"); @@ -194,7 +193,6 @@ namespace proxy strncpy(start, end, (char*)(http_buff + len) - end); len = len - (end - start); } - while (len > 0) { From de0658eaab3e688a8cd04a18299fe47f20b8c5f0 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 22 Jan 2016 00:00:00 +0000 Subject: [PATCH 0824/6300] * I2PControlService::CreateCertificate : use function parameters instead direct GetPath calls --- I2PControl.cpp | 43 ++++++++++++++++++++----------------------- I2PControl.h | 2 +- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index eabea408..3a8f8919 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -483,8 +483,9 @@ namespace client } // certificate - void I2PControlService::CreateCertificate () + void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path) { + FILE *f = NULL; EVP_PKEY * pkey = EVP_PKEY_new (); RSA * rsa = RSA_new (); BIGNUM * e = BN_dup (i2p::crypto::GetRSAE ()); @@ -504,34 +505,30 @@ namespace client X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name X509_set_issuer_name (x509, name); // set issuer to ourselves X509_sign (x509, pkey, EVP_sha1 ()); // sign - // save key and certificate - // keys - auto filename = GetPath () / I2P_CONTROL_KEY_FILE; - FILE * f= fopen (filename.string ().c_str (), "wb"); - if (f) - { - PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); - fclose (f); - } - else - LogPrint (eLogError, "Can't open file ", filename); - // certificate - filename = GetPath () / I2P_CONTROL_CERT_FILE; - f= fopen (filename.string ().c_str (), "wb"); - if (f) - { + + // save cert + if ((f = fopen (crt_path, "wb")) != NULL) { + LogPrint (eLogInfo, "I2PControl: saving new cert to ", crt_path); PEM_write_X509 (f, x509); fclose (f); + } else { + LogPrint (eLogError, "I2PControl: can't write cert: ", strerror(errno)); } - else - LogPrint (eLogError, "Can't open file ", filename); - X509_free (x509); + // save key + if ((f = fopen (key_path, "wb")) != NULL) { + LogPrint (eLogInfo, "I2PControl: saving cert key to : ", key_path); + PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); + fclose (f); + } else { + LogPrint (eLogError, "I2PControl: can't write key: ", strerror(errno)); + } + + X509_free (x509); + } else { + LogPrint (eLogError, "I2PControl: can't create RSA key for certificate"); } - else - LogPrint (eLogError, "Couldn't create RSA key for certificate"); EVP_PKEY_free (pkey); } - } } diff --git a/I2PControl.h b/I2PControl.h index 7f8e7c30..850bab10 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -97,7 +97,7 @@ namespace client std::shared_ptr socket, std::shared_ptr buf); boost::filesystem::path GetPath () const { return i2p::util::filesystem::GetDefaultDataDir() / I2P_CONTROL_PATH; }; - void CreateCertificate (); + void CreateCertificate (const char *crt_path, const char *key_path); private: From d5aa1a4880a7c329122e6f5200a488752f216b5e Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 22 Jan 2016 00:00:00 +0000 Subject: [PATCH 0825/6300] * use GetOption instead hardcoded values in header * move cert/key from $DATADIR/i2pcontrol/ to $DATADIR/ --- Config.cpp | 2 ++ I2PControl.cpp | 23 ++++++++++------------- I2PControl.h | 7 +------ 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/Config.cpp b/Config.cpp index b2121b4e..81a0bdea 100644 --- a/Config.cpp +++ b/Config.cpp @@ -87,6 +87,8 @@ namespace config { ("i2pcontrol.address", value()->default_value("127.0.0.1"), "I2PCP listen address") ("i2pcontrol.port", value()->default_value(7650), "I2PCP listen port") ("i2pcontrol.password", value()->default_value("itoopie"), "I2PCP access password") + ("i2pcontrol.cert", value()->default_value("i2pcontrol.crt.pem"), "I2PCP connection cerificate") + ("i2pcontrol.key", value()->default_value("i2pcontrol.key.pem"), "I2PCP connection cerificate key") ; m_OptionsDesc diff --git a/I2PControl.cpp b/I2PControl.cpp index 3a8f8919..2efba45f 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -36,23 +36,20 @@ namespace client { i2p::config::GetOption("i2pcontrol.password", m_Password); - // certificate + // certificate / keys + std::string i2pcp_crt; i2p::config::GetOption("i2pcontrol.cert", i2pcp_crt); + std::string i2pcp_key; i2p::config::GetOption("i2pcontrol.key", i2pcp_key); + // TODO: properly handle absolute paths auto path = GetPath (); - if (!boost::filesystem::exists (path)) + if (!boost::filesystem::exists (path / i2pcp_crt) || + !boost::filesystem::exists (path / i2pcp_key)) { - if (!boost::filesystem::create_directory (path)) - LogPrint (eLogError, "Failed to create i2pcontrol directory"); - } - if (!boost::filesystem::exists (path / I2P_CONTROL_KEY_FILE) || - !boost::filesystem::exists (path / I2P_CONTROL_CERT_FILE)) - { - // create new certificate - CreateCertificate (); - LogPrint (eLogInfo, "I2PControl certificates created"); + LogPrint (eLogInfo, "I2PControl: creating new certificate for control connection"); + CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str()); } m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); - m_SSLContext.use_certificate_file ((path / I2P_CONTROL_CERT_FILE).string (), boost::asio::ssl::context::pem); - m_SSLContext.use_private_key_file ((path / I2P_CONTROL_KEY_FILE).string (), boost::asio::ssl::context::pem); + m_SSLContext.use_certificate_file ((path / i2pcp_crt).string (), boost::asio::ssl::context::pem); + m_SSLContext.use_private_key_file ((path / i2pcp_crt).string (), boost::asio::ssl::context::pem); // handlers m_MethodHandlers[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlService::AuthenticateHandler; diff --git a/I2PControl.h b/I2PControl.h index 850bab10..38e3db55 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -22,11 +22,6 @@ namespace client const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024; typedef std::array I2PControlBuffer; - const char I2P_CONTROL_PATH[] = "ipcontrol"; - const char I2P_CONTROL_KEY_FILE[] = "key.pem"; - const char I2P_CONTROL_CERT_FILE[] = "cert.pem"; - const char I2P_CONTROL_CONFIG_FILE[] = "i2pcontrol.conf"; - const char I2P_CONTROL_PROPERTY_ID[] = "id"; const char I2P_CONTROL_PROPERTY_METHOD[] = "method"; const char I2P_CONTROL_PROPERTY_PARAMS[] = "params"; @@ -96,7 +91,7 @@ namespace client void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); - boost::filesystem::path GetPath () const { return i2p::util::filesystem::GetDefaultDataDir() / I2P_CONTROL_PATH; }; + boost::filesystem::path GetPath () const { return i2p::util::filesystem::GetDefaultDataDir(); }; void CreateCertificate (const char *crt_path, const char *key_path); private: From b70b3ec85b932865e1c3f531113a3c20bc2b0cb7 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 22 Jan 2016 00:00:00 +0000 Subject: [PATCH 0826/6300] * I2PControl : drop I2P_CONTROL_ID* vars : ugly --- I2PControl.cpp | 85 ++++++++++++++++++++++++++------------------------ I2PControl.h | 38 ---------------------- 2 files changed, 44 insertions(+), 79 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 2efba45f..128bd0f8 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -52,31 +52,31 @@ namespace client m_SSLContext.use_private_key_file ((path / i2pcp_crt).string (), boost::asio::ssl::context::pem); // handlers - m_MethodHandlers[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlService::AuthenticateHandler; - m_MethodHandlers[I2P_CONTROL_METHOD_ECHO] = &I2PControlService::EchoHandler; - m_MethodHandlers[I2P_CONTROL_METHOD_I2PCONTROL] = &I2PControlService::I2PControlHandler; - m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlService::RouterInfoHandler; - m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlService::RouterManagerHandler; - m_MethodHandlers[I2P_CONTROL_METHOD_NETWORK_SETTING] = &I2PControlService::NetworkSettingHandler; + m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; + m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; + m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; + m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler; + m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; + m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler; // I2PControl - m_I2PControlHandlers[I2P_CONTROL_I2PCONTROL_PASSWORD] = &I2PControlService::PasswordHandler; + m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler; // RouterInfo - m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_UPTIME] = &I2PControlService::UptimeHandler; - m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_VERSION] = &I2PControlService::VersionHandler; - m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_STATUS] = &I2PControlService::StatusHandler; - m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS] = &I2PControlService::NetDbKnownPeersHandler; - m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlService::NetDbActivePeersHandler; - m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NET_STATUS] = &I2PControlService::NetStatusHandler; - m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlService::TunnelsParticipatingHandler; - m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_IB_1S] = &I2PControlService::InboundBandwidth1S ; - m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_OB_1S] = &I2PControlService::OutboundBandwidth1S ; + m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlService::UptimeHandler; + m_RouterInfoHandlers["i2p.router.version"] = &I2PControlService::VersionHandler; + m_RouterInfoHandlers["i2p.router.status"] = &I2PControlService::StatusHandler; + m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlService::NetDbKnownPeersHandler; + m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlService::NetDbActivePeersHandler; + m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlService::InboundBandwidth1S; + m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlService::OutboundBandwidth1S; + m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlService::NetStatusHandler; + m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlService::TunnelsParticipatingHandler; // RouterManager - m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = &I2PControlService::ShutdownHandler; - m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = &I2PControlService::ShutdownGracefulHandler; - m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_RESEED] = &I2PControlService::ReseedHandler; + m_RouterManagerHandlers["Reseed"] = &I2PControlService::ReseedHandler; + m_RouterManagerHandlers["Shutdown"] = &I2PControlService::ShutdownHandler; + m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler; } I2PControlService::~I2PControlService () @@ -220,14 +220,14 @@ namespace client boost::property_tree::ptree pt; boost::property_tree::read_json (ss, pt); - std::string method = pt.get(I2P_CONTROL_PROPERTY_METHOD); + std::string id = pt.get("id"); + std::string method = pt.get("method"); auto it = m_MethodHandlers.find (method); if (it != m_MethodHandlers.end ()) { std::ostringstream response; - response << "{\"id\":" << pt.get(I2P_CONTROL_PROPERTY_ID) << ",\"result\":{"; - - (this->*(it->second))(pt.get_child (I2P_CONTROL_PROPERTY_PARAMS), response); + response << "{\"id\":" << id << ",\"result\":{"; + (this->*(it->second))(pt.get_child ("params"), response); response << "},\"jsonrpc\":\"2.0\"}"; SendResponse (socket, buf, response, isHtml); } @@ -302,23 +302,23 @@ namespace client void I2PControlService::AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { - int api = params.get (I2P_CONTROL_PARAM_API); - auto password = params.get (I2P_CONTROL_PARAM_PASSWORD); + int api = params.get ("API"); + auto password = params.get ("Password"); LogPrint (eLogDebug, "I2PControl Authenticate API=", api, " Password=", password); if (password != m_Password) LogPrint (eLogError, "I2PControl Authenticate Invalid password ", password, " expected ", m_Password); - InsertParam (results, I2P_CONTROL_PARAM_API, api); + InsertParam (results, "API", api); results << ","; std::string token = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); m_Tokens.insert (token); - InsertParam (results, I2P_CONTROL_PARAM_TOKEN, token); + InsertParam (results, "Token", token); } void I2PControlService::EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { - auto echo = params.get (I2P_CONTROL_PARAM_ECHO); + auto echo = params.get ("Echo"); LogPrint (eLogDebug, "I2PControl Echo Echo=", echo); - InsertParam (results, I2P_CONTROL_PARAM_RESULT, echo); + InsertParam (results, "Result", echo); } @@ -369,47 +369,50 @@ namespace client void I2PControlService::UptimeHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_UPTIME, (int)i2p::context.GetUptime ()*1000); + InsertParam (results, "i2p.router.uptime", (int)i2p::context.GetUptime ()*1000); } void I2PControlService::VersionHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_VERSION, VERSION); + InsertParam (results, "i2p.router.version", VERSION); } void I2PControlService::StatusHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_STATUS, "???"); // TODO: + InsertParam (results, "i2p.router.status", "???"); // TODO: } void I2PControlService::NetDbKnownPeersHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS, i2p::data::netdb.GetNumRouters ()); + InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ()); } void I2PControlService::NetDbActivePeersHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS, (int)i2p::transport::transports.GetPeers ().size ()); + InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ()); } void I2PControlService::NetStatusHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_NET_STATUS, (int)i2p::context.GetStatus ()); + InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ()); } void I2PControlService::TunnelsParticipatingHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING, (int)i2p::tunnel::tunnels.GetTransitTunnels ().size ()); + int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size (); + InsertParam (results, "i2p.router.net.tunnels.participating", transit); } void I2PControlService::InboundBandwidth1S (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_BW_IB_1S, (double)i2p::transport::transports.GetInBandwidth ()); + double bw = i2p::transport::transports.GetInBandwidth (); + InsertParam (results, "i2p.router.net.bw.inbound.1s", bw); } void I2PControlService::OutboundBandwidth1S (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_BW_OB_1S, (double)i2p::transport::transports.GetOutBandwidth ()); + double bw = i2p::transport::transports.GetOutBandwidth (); + InsertParam (results, "i2p.router.net.bw.outbound.1s", bw); } // RouterManager @@ -433,7 +436,7 @@ namespace client void I2PControlService::ShutdownHandler (std::ostringstream& results) { LogPrint (eLogInfo, "Shutdown requested"); - InsertParam (results, I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, ""); + InsertParam (results, "Shutdown", ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) @@ -447,7 +450,7 @@ namespace client i2p::context.SetAcceptsTunnels (false); int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout (); LogPrint (eLogInfo, "Graceful shutdown requested. Will shutdown after ", timeout, " seconds"); - InsertParam (results, I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL, ""); + InsertParam (results, "ShutdownGraceful", ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) @@ -459,7 +462,7 @@ namespace client void I2PControlService::ReseedHandler (std::ostringstream& results) { LogPrint (eLogInfo, "Reseed requested"); - InsertParam (results, I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, ""); + InsertParam (results, "Reseed", ""); i2p::data::netdb.Reseed (); } diff --git a/I2PControl.h b/I2PControl.h index 38e3db55..f3ddc3fe 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -22,44 +22,6 @@ namespace client const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024; typedef std::array I2PControlBuffer; - const char I2P_CONTROL_PROPERTY_ID[] = "id"; - const char I2P_CONTROL_PROPERTY_METHOD[] = "method"; - const char I2P_CONTROL_PROPERTY_PARAMS[] = "params"; - const char I2P_CONTROL_PROPERTY_RESULT[] = "result"; - - // methods - const char I2P_CONTROL_METHOD_AUTHENTICATE[] = "Authenticate"; - const char I2P_CONTROL_METHOD_ECHO[] = "Echo"; - const char I2P_CONTROL_METHOD_I2PCONTROL[] = "I2PControl"; - const char I2P_CONTROL_METHOD_ROUTER_INFO[] = "RouterInfo"; - const char I2P_CONTROL_METHOD_ROUTER_MANAGER[] = "RouterManager"; - const char I2P_CONTROL_METHOD_NETWORK_SETTING[] = "NetworkSetting"; - - // params - const char I2P_CONTROL_PARAM_API[] = "API"; - const char I2P_CONTROL_PARAM_PASSWORD[] = "Password"; - const char I2P_CONTROL_PARAM_TOKEN[] = "Token"; - const char I2P_CONTROL_PARAM_ECHO[] = "Echo"; - const char I2P_CONTROL_PARAM_RESULT[] = "Result"; - - const char I2P_CONTROL_I2PCONTROL_PASSWORD[] = "i2pcontrol.password"; - - // RouterInfo requests - const char I2P_CONTROL_ROUTER_INFO_UPTIME[] = "i2p.router.uptime"; - const char I2P_CONTROL_ROUTER_INFO_VERSION[] = "i2p.router.version"; - const char I2P_CONTROL_ROUTER_INFO_STATUS[] = "i2p.router.status"; - const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; - const char I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers"; - const char I2P_CONTROL_ROUTER_INFO_NET_STATUS[] = "i2p.router.net.status"; - const char I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating"; - const char I2P_CONTROL_ROUTER_INFO_BW_IB_1S[] = "i2p.router.net.bw.inbound.1s"; - const char I2P_CONTROL_ROUTER_INFO_BW_OB_1S[] = "i2p.router.net.bw.outbound.1s"; - - // RouterManager requests - const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN[] = "Shutdown"; - const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful"; - const char I2P_CONTROL_ROUTER_MANAGER_RESEED[] = "Reseed"; - // Certificate const long I2P_CONTROL_CERTIFICATE_VALIDITY = 365*10; // 10 years const char I2P_CONTROL_CERTIFICATE_COMMON_NAME[] = "i2pd.i2pcontrol"; From ac2cb773df6bc07a4cf11ac84dface9c41b71781 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 22 Jan 2016 00:00:00 +0000 Subject: [PATCH 0827/6300] * I2PControl.cpp : tune logs --- I2PControl.cpp | 90 +++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 128bd0f8..35f3d0c6 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -114,13 +114,10 @@ namespace client { while (m_IsRunning) { - try - { + try { m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "I2PControl: ", ex.what ()); + } catch (std::exception& ex) { + LogPrint (eLogError, "I2PControl: runtime exception: ", ex.what ()); } } } @@ -137,13 +134,12 @@ namespace client if (ecode != boost::asio::error::operation_aborted) Accept (); - if (!ecode) - { - LogPrint (eLogInfo, "New I2PControl request from ", socket->lowest_layer ().remote_endpoint ()); - Handshake (socket); + if (ecode) { + LogPrint (eLogError, "I2PControl: accept error: ", ecode.message ()); + return; } - else - LogPrint (eLogError, "I2PControl accept error: ", ecode.message ()); + LogPrint (eLogDebug, "I2PControl: new request from ", socket->lowest_layer ().remote_endpoint ()); + Handshake (socket); } void I2PControlService::Handshake (std::shared_ptr socket) @@ -154,13 +150,12 @@ namespace client void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket) { - if (!ecode) - { - //std::this_thread::sleep_for (std::chrono::milliseconds(5)); - ReadRequest (socket); + if (ecode) { + LogPrint (eLogError, "I2PControl: handshake error: ", ecode.message ()); + return; } - else - LogPrint (eLogError, "I2PControl handshake error: ", ecode.message ()); + //std::this_thread::sleep_for (std::chrono::milliseconds(5)); + ReadRequest (socket); } void I2PControlService::ReadRequest (std::shared_ptr socket) @@ -180,12 +175,10 @@ namespace client size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf) { - if (ecode) - { - LogPrint (eLogError, "I2PControl read error: ", ecode.message ()); - } - else - { + if (ecode) { + LogPrint (eLogError, "I2PControl: read error: ", ecode.message ()); + return; + } else { try { bool isHtml = !memcmp (buf->data (), "POST", 4); @@ -294,8 +287,9 @@ namespace client void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf) { - if (ecode) - LogPrint (eLogError, "I2PControl write error: ", ecode.message ()); + if (ecode) { + LogPrint (eLogError, "I2PControl: write error: ", ecode.message ()); + } } // handlers @@ -304,9 +298,11 @@ namespace client { int api = params.get ("API"); auto password = params.get ("Password"); - LogPrint (eLogDebug, "I2PControl Authenticate API=", api, " Password=", password); - if (password != m_Password) - LogPrint (eLogError, "I2PControl Authenticate Invalid password ", password, " expected ", m_Password); + LogPrint (eLogDebug, "I2PControl: Authenticate API=", api, " Password=", password); + if (password != m_Password) { + LogPrint (eLogError, "I2PControl: Authenticate Invalid password: ", password); + return; + } InsertParam (results, "API", api); results << ","; std::string token = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); @@ -326,10 +322,9 @@ namespace client void I2PControlService::I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { - LogPrint (eLogDebug, "I2PControl I2PControl"); for (auto& it: params) { - LogPrint (eLogDebug, it.first); + LogPrint (eLogDebug, "I2PControl: I2PControl request: ", it.first); auto it1 = m_I2PControlHandlers.find (it.first); if (it1 != m_I2PControlHandlers.end ()) { @@ -337,13 +332,13 @@ namespace client InsertParam (results, it.first, ""); } else - LogPrint (eLogError, "I2PControl I2PControl unknown request ", it.first); + LogPrint (eLogError, "I2PControl: I2PControl unknown request: ", it.first); } } void I2PControlService::PasswordHandler (const std::string& value) { - LogPrint (eLogDebug, "I2PControl: new password=", value, ", to make it persistent you should update your config!"); + LogPrint (eLogWarning, "I2PControl: new password=", value, ", to make it persistent you should update your config!"); m_Password = value; m_Tokens.clear (); } @@ -352,10 +347,9 @@ namespace client void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { - LogPrint (eLogDebug, "I2PControl RouterInfo"); for (auto it = params.begin (); it != params.end (); it++) { - LogPrint (eLogDebug, it->first); + LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); auto it1 = m_RouterInfoHandlers.find (it->first); if (it1 != m_RouterInfoHandlers.end ()) { @@ -363,7 +357,7 @@ namespace client (this->*(it1->second))(results); } else - LogPrint (eLogError, "I2PControl RouterInfo unknown request ", it->first); + LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first); } } @@ -419,23 +413,22 @@ namespace client void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { - LogPrint (eLogDebug, "I2PControl RouterManager"); for (auto it = params.begin (); it != params.end (); it++) { if (it != params.begin ()) results << ","; - LogPrint (eLogDebug, it->first); + LogPrint (eLogDebug, "I2PControl: RouterManager request: ", it->first); auto it1 = m_RouterManagerHandlers.find (it->first); - if (it1 != m_RouterManagerHandlers.end ()) + if (it1 != m_RouterManagerHandlers.end ()) { (this->*(it1->second))(results); - else - LogPrint (eLogError, "I2PControl RouterManager unknown request ", it->first); + } else + LogPrint (eLogError, "I2PControl: RouterManager unknown request: ", it->first); } } void I2PControlService::ShutdownHandler (std::ostringstream& results) { - LogPrint (eLogInfo, "Shutdown requested"); + LogPrint (eLogInfo, "I2PControl: Shutdown requested"); InsertParam (results, "Shutdown", ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent m_ShutdownTimer.async_wait ( @@ -449,7 +442,7 @@ namespace client { i2p::context.SetAcceptsTunnels (false); int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout (); - LogPrint (eLogInfo, "Graceful shutdown requested. Will shutdown after ", timeout, " seconds"); + LogPrint (eLogInfo, "I2PControl: Graceful shutdown requested, ", timeout, " seconds remains"); InsertParam (results, "ShutdownGraceful", ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second m_ShutdownTimer.async_wait ( @@ -461,7 +454,7 @@ namespace client void I2PControlService::ReseedHandler (std::ostringstream& results) { - LogPrint (eLogInfo, "Reseed requested"); + LogPrint (eLogInfo, "I2PControl: Reseed requested"); InsertParam (results, "Reseed", ""); i2p::data::netdb.Reseed (); } @@ -469,16 +462,15 @@ namespace client // network setting void I2PControlService::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { - LogPrint (eLogDebug, "I2PControl NetworkSetting"); for (auto it = params.begin (); it != params.end (); it++) { if (it != params.begin ()) results << ","; - LogPrint (eLogDebug, it->first); + LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first); auto it1 = m_NetworkSettingHandlers.find (it->first); - if (it1 != m_NetworkSettingHandlers.end ()) + if (it1 != m_NetworkSettingHandlers.end ()) { (this->*(it1->second))(it->second.data (), results); - else - LogPrint (eLogError, "I2PControl NetworkSetting unknown request ", it->first); + } else + LogPrint (eLogError, "I2PControl NetworkSetting unknown request: ", it->first); } } From 66637886123d27aa6e636d703a9d3755598a3520 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 22 Jan 2016 07:08:21 -0500 Subject: [PATCH 0828/6300] fixed some coding style --- HTTPProxy.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 17b8e816..fcb8674a 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -185,13 +185,16 @@ namespace proxy assert(len); // This should always be called with a least a byte left to parse // remove "Referer" from http requst - http_buff[len] = '\0'; - char *start = strstr((char *)http_buff, "\nReferer:"); - if (start!=0) + http_buff[len] = 0; + auto start = strstr((char *)http_buff, "\nReferer:"); + if (start) { - char *end = strstr(start+1, "\n"); - strncpy(start, end, (char*)(http_buff + len) - end); - len = len - (end - start); + auto end = strchr (start + 1, '\n'); + if (end) + { + strncpy(start, end, (char *)(http_buff + len) - end); + len -= (end - start); + } } while (len > 0) From 03587d703583af7626e511eb70d774b39c6efffb Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 22 Jan 2016 10:56:25 -0500 Subject: [PATCH 0829/6300] changed data path back to AppData/Roaming --- util.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/util.cpp b/util.cpp index cff2aa75..e894c44f 100644 --- a/util.cpp +++ b/util.cpp @@ -215,15 +215,15 @@ namespace filesystem boost::filesystem::path GetDefaultDataDir() { - // Windows < Vista: C:\Documents and Settings\Username\.i2pd - // Windows >= Vista: C:\Users\Username\.i2pd + // Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd + // Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd // Mac: ~/Library/Application Support/i2pd // Unix: ~/.i2pd or /var/lib/i2pd is system=1 #ifdef WIN32 // Windows char localAppData[MAX_PATH]; - SHGetFolderPath(NULL, CSIDL_PROFILE, 0, 0, localAppData); - return boost::filesystem::path(std::string(localAppData) + "\\" + "." + appName); + SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); + return boost::filesystem::path(std::string(localAppData) + "\\" + appName); #else /* UNIX */ if (i2p::util::config::GetArg("-service", 0)) // use system folder return boost::filesystem::path(std::string ("/var/lib/") + appName); From 1778d82bc3d7957b3eca8afcc88e59212d731f97 Mon Sep 17 00:00:00 2001 From: Andrey Alekseenko Date: Sat, 23 Jan 2016 17:34:58 +0300 Subject: [PATCH 0830/6300] Better checking if boost::asio::buffer works with std::array Otherwise, had troubles with clang 3.4 and boost 1.54 --- I2PControl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 0c7b63ca..ad81ec93 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -209,7 +209,7 @@ namespace client { auto request = std::make_shared(); socket->async_read_some ( -#if BOOST_VERSION >= 104900 +#if defined(BOOST_ASIO_HAS_STD_ARRAY) boost::asio::buffer (*request), #else boost::asio::buffer (request->data (), request->size ()), From f593802a51f5480e6331e8ebd379ec880b8429ff Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 23 Jan 2016 20:52:21 -0500 Subject: [PATCH 0831/6300] I2CP option crypto.tagsToSend added for I2P tunnels --- ClientContext.cpp | 1 + Destination.cpp | 12 ++++++++++++ Destination.h | 4 +++- Garlic.cpp | 4 ++-- Garlic.h | 4 +++- 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index c1a1a274..ef0d58c5 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -231,6 +231,7 @@ namespace client options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); + options[I2CP_PARAM_TAGS_TO_SEND] = GetI2CPOption (section, I2CP_PARAM_TAGS_TO_SEND, DEFAULT_TAGS_TO_SEND); } void ClientContext::ReadTunnels () diff --git a/Destination.cpp b/Destination.cpp index 765fbdae..233946ad 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -27,6 +27,7 @@ namespace client int outboundTunnelLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH; int inboundTunnelsQuantity = DEFAULT_INBOUND_TUNNELS_QUANTITY; int outboundTunnelsQuantity = DEFAULT_OUTBOUND_TUNNELS_QUANTITY; + int numTags = DEFAULT_TAGS_TO_SEND; std::shared_ptr > explicitPeers; if (params) { @@ -70,6 +71,16 @@ namespace client LogPrint (eLogInfo, "Destination: Outbound tunnels quantity set to ", quantity); } } + it = params->find (I2CP_PARAM_TAGS_TO_SEND); + if (it != params->end ()) + { + int tagsToSend = boost::lexical_cast(it->second); + if (tagsToSend > 0) + { + numTags = tagsToSend; + LogPrint (eLogInfo, "Destination: Tags to send set to ", tagsToSend); + } + } it = params->find (I2CP_PARAM_EXPLICIT_PEERS); if (it != params->end ()) { @@ -85,6 +96,7 @@ namespace client LogPrint (eLogInfo, "Destination: Explicit peers set to ", it->second); } } + SetNumTags (numTags); m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inboundTunnelLen, outboundTunnelLen, inboundTunnelsQuantity, outboundTunnelsQuantity); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); diff --git a/Destination.h b/Destination.h index 3de47a81..8ff2dbc8 100644 --- a/Destination.h +++ b/Destination.h @@ -42,7 +42,9 @@ namespace client const int DEFAULT_OUTBOUND_TUNNELS_QUANTITY = 5; const char I2CP_PARAM_EXPLICIT_PEERS[] = "explicitPeers"; const int STREAM_REQUEST_TIMEOUT = 60; //in seconds - + const char I2CP_PARAM_TAGS_TO_SEND[] = "crypto.tagsToSend"; + const int DEFAULT_TAGS_TO_SEND = 40; + typedef std::function stream)> StreamRequestComplete; class ClientDestination: public i2p::garlic::GarlicDestination, diff --git a/Garlic.cpp b/Garlic.cpp index 71688305..09fd27b3 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -546,7 +546,7 @@ namespace garlic std::shared_ptr GarlicDestination::WrapMessage (std::shared_ptr destination, std::shared_ptr msg, bool attachLeaseSet) { - auto session = GetRoutingSession (destination, attachLeaseSet); // 32 tags by default + auto session = GetRoutingSession (destination, attachLeaseSet); return session->WrapSingleMessage (msg); } @@ -560,7 +560,7 @@ namespace garlic if (!session) { session = std::make_shared (this, destination, - attachLeaseSet ? 40 : 4, attachLeaseSet); // 40 tags for connections and 4 for LS requests + attachLeaseSet ? m_NumTags : 4, attachLeaseSet); // specified num tags for connections and 4 for LS requests std::unique_lock l(m_SessionsMutex); m_Sessions[destination->GetIdentHash ()] = session; } diff --git a/Garlic.h b/Garlic.h index 716bc466..585d4f27 100644 --- a/Garlic.h +++ b/Garlic.h @@ -119,9 +119,10 @@ namespace garlic { public: - GarlicDestination (): m_LastTagsCleanupTime (0) {}; + GarlicDestination (): m_NumTags (32), m_LastTagsCleanupTime (0) {}; // 32 tags by default ~GarlicDestination (); + void SetNumTags (int numTags) { m_NumTags = numTags; }; std::shared_ptr GetRoutingSession (std::shared_ptr destination, bool attachLeaseSet); void CleanupRoutingSessions (); void RemoveCreatedSession (uint32_t msgID); @@ -154,6 +155,7 @@ namespace garlic private: // outgoing sessions + int m_NumTags; std::mutex m_SessionsMutex; std::map > m_Sessions; // incoming From f7e21dbe5cf927017453f938ae3b1bc6fc82db61 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 23 Jan 2016 22:53:19 -0500 Subject: [PATCH 0832/6300] show tags for local destinations --- Garlic.h | 10 ++++++++++ HTTPServer.cpp | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/Garlic.h b/Garlic.h index 585d4f27..45903f3f 100644 --- a/Garlic.h +++ b/Garlic.h @@ -113,6 +113,10 @@ namespace garlic i2p::crypto::CBCEncryption m_Encryption; std::unique_ptr m_ElGamalEncryption; + + public: + // for HTTP only + size_t GetNumOutgoingTags () const { return m_SessionTags.size (); }; }; class GarlicDestination: public i2p::data::LocalDestination @@ -163,6 +167,12 @@ namespace garlic uint32_t m_LastTagsCleanupTime; // DeliveryStatus std::map > m_CreatedSessions; // msgID -> session + + public: + + // for HTTP only + size_t GetNumIncomingTags () const { return m_Tags.size (); } + const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; }; } } diff --git a/HTTPServer.cpp b/HTTPServer.cpp index e4a75e1a..2fef6e66 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -534,6 +534,13 @@ namespace util s << "
\r\n" << std::endl; } } + s << "Tags
Incoming: " << dest->GetNumIncomingTags () << "
Outgoing:
" << std::endl; + for (auto it: dest->GetSessions ()) + { + s << i2p::client::context.GetAddressBook ().ToAddress(it.first) << " "; + s << it.second->GetNumOutgoingTags () << "
" << std::endl; + } + s << "
" << std::endl; // s << "
\r\nStreams:
\r\n"; // for (auto it: dest->GetStreamingDestination ()->GetStreams ()) // { From e6e2f04a10aa7217da46e6bd6489d4d1be3181a4 Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 24 Jan 2016 11:04:15 +0000 Subject: [PATCH 0833/6300] * Config.cpp : set default value for boolean options --- Config.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Config.cpp b/Config.cpp index 81a0bdea..4704816d 100644 --- a/Config.cpp +++ b/Config.cpp @@ -36,12 +36,12 @@ namespace config { ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") ("host", value()->default_value(""), "External IP (deprecated)") ("port,p", value()->default_value(4567), "Port to listen for incoming connections") - ("ipv6,6", value()->zero_tokens(), "Enable communication through ipv6") - ("daemon", value()->zero_tokens(), "Router will go to background after start") - ("service", value()->zero_tokens(), "Router will use system folders like '/var/lib/i2pd'") - ("notransit", value()->zero_tokens(), "Router will not forward transit traffic") - ("floodfill", value()->zero_tokens(), "Router will try to become floodfill") - ("bandwidth", value()->default_value('O'), "Bandwidth limiting: L - 32kbps, O - 256Kbps, P - unlimited") + ("ipv6,6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") + ("daemon", value()->zero_tokens()->default_value(false), "Router will go to background after start") + ("service", value()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'") + ("notransit", value()->zero_tokens()->default_value(false), "Router will not forward transit traffic") + ("floodfill", value()->zero_tokens()->default_value(false), "Router will try to become floodfill") + ("bandwidth", value()->default_value('O'), "Bandwidth limiting: L - 32kbps, O - 256Kbps, P - unlimited (ignored if floodfill)") ; options_description httpserver("HTTP Server options"); From 022642f4d5a8b2a1259d4290f87c86dbe7599d1e Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 24 Jan 2016 11:04:41 +0000 Subject: [PATCH 0834/6300] * Config.cpp : don't try to parse config, if path is empty --- Config.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Config.cpp b/Config.cpp index 4704816d..1b0c3072 100644 --- a/Config.cpp +++ b/Config.cpp @@ -118,6 +118,9 @@ namespace config { } void ParseConfig(const std::string& path) { + if (path == "") + return; + std::ifstream config(path, std::ios::in); if (!config.is_open()) { From efa48a7e397d226ba5c70a542f7d2153afe4e8b7 Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 24 Jan 2016 11:05:16 +0000 Subject: [PATCH 0835/6300] * tune logs --- AddressBook.cpp | 2 +- Daemon.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 00b735ae..754d314a 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -396,7 +396,7 @@ namespace client LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); } else - LogPrint (eLogWarning, "Addresbook: subscriptions.txt not found"); + LogPrint (eLogWarning, "Addresbook: subscriptions.txt not found in datadir"); } else LogPrint (eLogError, "Addressbook: subscriptions already loaded"); diff --git a/Daemon.cpp b/Daemon.cpp index 8530b93e..4b0f7b64 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -129,7 +129,7 @@ namespace i2p if (http) { std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); - LogPrint(eLogInfo, "Daemon: staring HTTP Server at ", httpAddr, ":", httpPort); + LogPrint(eLogInfo, "Daemon: starting HTTP Server at ", httpAddr, ":", httpPort); d.httpServer = std::unique_ptr(new i2p::util::HTTPServer(httpAddr, httpPort)); d.httpServer->Start(); } From 26d232c567ef808f313ec5086830a6d3c350b078 Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 24 Jan 2016 11:06:17 +0000 Subject: [PATCH 0836/6300] * Daemon_Singleton::init : unwrap spagetti-code --- Daemon.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 4b0f7b64..34a90f9a 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -60,17 +60,23 @@ namespace i2p bool Daemon_Singleton::init(int argc, char* argv[]) { + std::string config = i2p::util::filesystem::GetConfigFile().string(); + std::string tunconf = i2p::util::filesystem::GetTunnelsConfigFile().string(); + std::string datadir = i2p::util::filesystem::GetDataDir().string(); + + LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); + LogPrint(eLogDebug, "FS: main config file: ", config); + LogPrint(eLogDebug, "FS: tunnels config: ", tunconf); + LogPrint(eLogDebug, "FS: data directory: ", datadir); + i2p::config::Init(); i2p::config::ParseCmdline(argc, argv); - i2p::config::ParseConfig(i2p::util::filesystem::GetConfigFile().string()); + i2p::config::ParseConfig(config); i2p::config::Finalize(); i2p::crypto::InitCrypto (); i2p::context.Init (); - LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); - LogPrint(eLogDebug, "FS: data directory: ", i2p::util::filesystem::GetDataDir().string()); - i2p::config::GetOption("daemon", isDaemon); i2p::config::GetOption("log", isLogging); From 0f7e2ad11ac09ef69679f3f8f0e37d40eccb1dd2 Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 24 Jan 2016 11:06:54 +0000 Subject: [PATCH 0837/6300] * Daemon_Singleton::init : rewrite setting bandwidth limit and floodfill mode --- Daemon.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 34a90f9a..22ca2ac1 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -93,19 +93,20 @@ namespace i2p i2p::context.SetAcceptsTunnels (!transit); bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); - i2p::context.SetFloodfill (isFloodfill); + char bandwidth; i2p::config::GetOption("bandwidth", bandwidth); - char bandwidth; i2p::config::GetOption("bandwidth", bandwidth); - if (bandwidth != '-') - { + if (isFloodfill) { + LogPrint(eLogInfo, "Daemon: router will be floodfill, bandwidth set to 'extra'"); + i2p::context.SetFloodfill (true); + i2p::context.SetExtraBandwidth (); + } else if (bandwidth != '-') { + LogPrint(eLogInfo, "Daemon: bandwidth set to ", bandwidth); switch (bandwidth) { case 'P' : i2p::context.SetExtraBandwidth (); break; case 'L' : i2p::context.SetHighBandwidth (); break; default : i2p::context.SetLowBandwidth (); break; } } - else if (isFloodfill) - i2p::context.SetExtraBandwidth (); return true; } From 415314a90d21ac60d9ef9f2eec18a8c8f0340e51 Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 24 Jan 2016 11:07:05 +0000 Subject: [PATCH 0838/6300] * update docs --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index f3b84952..64925d4b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -20,6 +20,7 @@ Command line options * --notransit - Router will not accept transit tunnels at startup. 0 by default * --floodfill - Router will be floodfill, off by default * --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited + This option will be ignored if --floodfill given * --http.address= - The address to listen on (HTTP server) * --http.port= - The port to listen on (HTTP server) From 7b23d79dc2a5761298d6f997390886d174c581b9 Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 24 Jan 2016 11:13:38 +0000 Subject: [PATCH 0839/6300] * util.cpp : update Get*ConfigFile() : autodetect configs --- util.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/util.cpp b/util.cpp index e559d7f9..5f36f409 100644 --- a/util.cpp +++ b/util.cpp @@ -124,19 +124,33 @@ namespace filesystem boost::filesystem::path GetConfigFile() { std::string config; i2p::config::GetOption("conf", config); - boost::filesystem::path pathConfigFile(config); - if (!pathConfigFile.is_complete()) - pathConfigFile = GetDataDir() / pathConfigFile; - return pathConfigFile; + if (config != "") { + /* config file set with cmdline */ + boost::filesystem::path path(config); + return path; + } + /* else - try autodetect */ + boost::filesystem::path path("i2p.conf"); + path = GetDataDir() / path; + if (!boost::filesystem::exists(path)) + path = ""; /* reset */ + return path; } boost::filesystem::path GetTunnelsConfigFile() { std::string tunconf; i2p::config::GetOption("tunconf", tunconf); - boost::filesystem::path pathTunnelsConfigFile(tunconf); - if (!pathTunnelsConfigFile.is_complete()) - pathTunnelsConfigFile = GetDataDir() / pathTunnelsConfigFile; - return pathTunnelsConfigFile; + if (tunconf != "") { + /* config file set with cmdline */ + boost::filesystem::path path(tunconf); + return path; + } + /* else - try autodetect */ + boost::filesystem::path path("tunnels.cfg"); + path = GetDataDir() / path; + if (!boost::filesystem::exists(path)) + path = ""; /* reset */ + return path; } boost::filesystem::path GetDefaultDataDir() From 7da17ba21e3a2f9d04a6d0ebd23b59e350da49e7 Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 24 Jan 2016 12:41:08 +0000 Subject: [PATCH 0840/6300] * tune logs --- Daemon.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 22ca2ac1..a2d33720 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -81,11 +81,14 @@ namespace i2p i2p::config::GetOption("log", isLogging); uint16_t port; i2p::config::GetOption("port", port); - if (port) - i2p::context.UpdatePort (port); + LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); + i2p::context.UpdatePort (port); + std::string host; i2p::config::GetOption("host", host); - if (host != "") + if (host != "") { + LogPrint(eLogInfo, "Daemon: address for incoming connections is ", host); i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); + } bool ipv6; i2p::config::GetOption("ipv6", ipv6); bool transit; i2p::config::GetOption("notransit", transit); From 0c9ce6258c1ed9a42190dd98372d30614d44eef4 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 24 Jan 2016 09:59:02 -0500 Subject: [PATCH 0841/6300] sockoutproxy params added --- ClientContext.cpp | 4 +++- HTTPProxy.cpp | 5 +++-- SOCKS.cpp | 3 ++- SOCKS.h | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index ef0d58c5..00c98451 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -53,8 +53,10 @@ namespace client std::string socksProxyAddr = i2p::util::config::GetArg("-socksproxyaddress", "127.0.0.1"); uint16_t socksProxyPort = i2p::util::config::GetArg("-socksproxyport", 4447); + std::string socksOutProxyAddr = i2p::util::config::GetArg("-socksoutproxyaddress", ""); + uint16_t socksOutProxyPort = i2p::util::config::GetArg("-socksoutproxyport", 0); LogPrint(eLogInfo, "Clients: starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); - m_SocksProxy = new i2p::proxy::SOCKSProxy(socksProxyAddr, socksProxyPort, localDestination); + m_SocksProxy = new i2p::proxy::SOCKSProxy(socksProxyAddr, socksProxyPort, socksOutProxyAddr, socksOutProxyPort, localDestination); m_SocksProxy->Start(); // I2P tunnels diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index fcb8674a..b4010a37 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -182,7 +182,8 @@ namespace proxy bool HTTPProxyHandler::HandleData(uint8_t *http_buff, std::size_t len) { - assert(len); // This should always be called with a least a byte left to parse + // TODO: we should srtrip 'Referer' better, because it might be inside message body + /*assert(len); // This should always be called with a least a byte left to parse // remove "Referer" from http requst http_buff[len] = 0; @@ -195,7 +196,7 @@ namespace proxy strncpy(start, end, (char *)(http_buff + len) - end); len -= (end - start); } - } + }*/ while (len > 0) { diff --git a/SOCKS.cpp b/SOCKS.cpp index 3a971a60..27b23df1 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -557,7 +557,8 @@ namespace proxy } } - SOCKSServer::SOCKSServer(const std::string& address, int port, std::shared_ptr localDestination) : + SOCKSServer::SOCKSServer(const std::string& address, int port, const std::string& outAddress, int outPort, + std::shared_ptr localDestination) : TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()) { } diff --git a/SOCKS.h b/SOCKS.h index cc2cfa24..82f068fb 100644 --- a/SOCKS.h +++ b/SOCKS.h @@ -15,7 +15,8 @@ namespace proxy { public: - SOCKSServer(const std::string& address, int port, std::shared_ptr localDestination = nullptr); + SOCKSServer(const std::string& address, int port, const std::string& outAddress, int outPort, + std::shared_ptr localDestination = nullptr); ~SOCKSServer() {}; protected: From 3f0b595085d6f6d8e7c624f5eace1f0edb814de2 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 24 Jan 2016 22:24:39 -0500 Subject: [PATCH 0842/6300] fixed typo --- Streaming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index 6834d4bb..ea90c1f0 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -714,7 +714,7 @@ namespace stream if (m_RemoteLeaseSet) { if (!m_RoutingSession) - m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, 32); + m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (false); // try without threshold first if (leases.empty ()) { From 30f68759ff5cbfc4103f579aeda5221ee7eabc9e Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 25 Jan 2016 13:34:04 -0500 Subject: [PATCH 0843/6300] fixed race condition --- Garlic.cpp | 27 +++++++++++++++------------ Garlic.h | 11 ++++++----- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index 09fd27b3..1bd5cc3f 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -100,7 +100,7 @@ namespace garlic if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) { if (m_Owner) - m_Owner->RemoveCreatedSession (it->first); + m_Owner->RemoveDeliveryStatusSession (it->first); delete it->second; it = m_UnconfirmedTagsMsgs.erase (it); } @@ -553,10 +553,13 @@ namespace garlic std::shared_ptr GarlicDestination::GetRoutingSession ( std::shared_ptr destination, bool attachLeaseSet) { - auto it = m_Sessions.find (destination->GetIdentHash ()); - std::shared_ptr session; - if (it != m_Sessions.end ()) - session = it->second; + GarlicRoutingSessionPtr session; + { + std::unique_lock l(m_SessionsMutex); + auto it = m_Sessions.find (destination->GetIdentHash ()); + if (it != m_Sessions.end ()) + session = it->second; + } if (!session) { session = std::make_shared (this, destination, @@ -582,25 +585,25 @@ namespace garlic } } - void GarlicDestination::RemoveCreatedSession (uint32_t msgID) + void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID) { - m_CreatedSessions.erase (msgID); + m_DeliveryStatusSessions.erase (msgID); } - void GarlicDestination::DeliveryStatusSent (std::shared_ptr session, uint32_t msgID) + void GarlicDestination::DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID) { - m_CreatedSessions[msgID] = session; + m_DeliveryStatusSessions[msgID] = session; } void GarlicDestination::HandleDeliveryStatusMessage (std::shared_ptr msg) { uint32_t msgID = bufbe32toh (msg->GetPayload ()); { - auto it = m_CreatedSessions.find (msgID); - if (it != m_CreatedSessions.end ()) + auto it = m_DeliveryStatusSessions.find (msgID); + if (it != m_DeliveryStatusSessions.end ()) { it->second->MessageConfirmed (msgID); - m_CreatedSessions.erase (it); + m_DeliveryStatusSessions.erase (it); LogPrint (eLogDebug, "Garlic: message ", msgID, " acknowledged"); } } diff --git a/Garlic.h b/Garlic.h index 45903f3f..1bfcab7c 100644 --- a/Garlic.h +++ b/Garlic.h @@ -118,7 +118,8 @@ namespace garlic // for HTTP only size_t GetNumOutgoingTags () const { return m_SessionTags.size (); }; }; - + using GarlicRoutingSessionPtr = std::shared_ptr; + class GarlicDestination: public i2p::data::LocalDestination { public: @@ -129,13 +130,13 @@ namespace garlic void SetNumTags (int numTags) { m_NumTags = numTags; }; std::shared_ptr GetRoutingSession (std::shared_ptr destination, bool attachLeaseSet); void CleanupRoutingSessions (); - void RemoveCreatedSession (uint32_t msgID); + void RemoveDeliveryStatusSession (uint32_t msgID); std::shared_ptr WrapMessage (std::shared_ptr destination, std::shared_ptr msg, bool attachLeaseSet = false); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread - void DeliveryStatusSent (std::shared_ptr session, uint32_t msgID); + void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); virtual void ProcessGarlicMessage (std::shared_ptr msg); virtual void ProcessDeliveryStatusMessage (std::shared_ptr msg); @@ -161,12 +162,12 @@ namespace garlic // outgoing sessions int m_NumTags; std::mutex m_SessionsMutex; - std::map > m_Sessions; + std::map m_Sessions; // incoming std::map> m_Tags; uint32_t m_LastTagsCleanupTime; // DeliveryStatus - std::map > m_CreatedSessions; // msgID -> session + std::map m_DeliveryStatusSessions; // msgID -> session public: From 8061d306ddb4a870221c31204a3b6f028bd80008 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 25 Jan 2016 14:31:51 -0500 Subject: [PATCH 0844/6300] check tunnel payload size --- Tunnel.cpp | 5 +++++ TunnelEndpoint.cpp | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Tunnel.cpp b/Tunnel.cpp index 267485c8..47b3246c 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -482,6 +482,11 @@ namespace tunnel uint16_t len = bufbe16toh(payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET); // we make payload as new I2NP message to send msg->offset += I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE; + if (msg->offset + len > msg->len) + { + LogPrint (eLogError, "Tunnel: gateway payload ", (int)len, " exceeds message length ", (int)msg->len); + return; + } msg->len = msg->offset + len; auto typeID = msg->GetTypeID (); LogPrint (eLogDebug, "Tunnel: gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID); diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 151a4828..0b730d35 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -90,6 +90,11 @@ namespace tunnel msg->offset = fragment - msg->buf; msg->len = msg->offset + size; + if (msg->len > msg->maxLen) + { + LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size); + return; + } if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) { // this is not last message. we have to copy it From 5215bdc035939a614fa9cbcc7f4c42e86a9eefb1 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 25 Jan 2016 22:10:06 -0500 Subject: [PATCH 0845/6300] clean up remote destinations without outgoing and unconfirmed tags --- Destination.h | 2 +- Garlic.cpp | 4 ++-- Garlic.h | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Destination.h b/Destination.h index 8ff2dbc8..ece125e3 100644 --- a/Destination.h +++ b/Destination.h @@ -28,7 +28,7 @@ namespace client const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds - const int DESTINATION_CLEANUP_TIMEOUT = 20; // in minutes + const int DESTINATION_CLEANUP_TIMEOUT = 3; // in minutes const unsigned int MAX_NUM_FLOODFILLS_PER_REQUEST = 7; // I2CP diff --git a/Garlic.cpp b/Garlic.cpp index 1bd5cc3f..d62b6fe3 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -97,7 +97,7 @@ namespace garlic // delete expired unconfirmed tags for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();) { - if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) + if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_CONFIRMATION_TIMEOUT) { if (m_Owner) m_Owner->RemoveDeliveryStatusSession (it->first); @@ -107,7 +107,7 @@ namespace garlic else it++; } - return !m_SessionTags.empty () || m_UnconfirmedTagsMsgs.empty (); + return !m_SessionTags.empty () || !m_UnconfirmedTagsMsgs.empty (); } std::shared_ptr GarlicRoutingSession::WrapSingleMessage (std::shared_ptr msg) diff --git a/Garlic.h b/Garlic.h index 1bfcab7c..67e9b027 100644 --- a/Garlic.h +++ b/Garlic.h @@ -38,6 +38,7 @@ namespace garlic const int INCOMING_TAGS_EXPIRATION_TIMEOUT = 960; // 16 minutes const int OUTGOING_TAGS_EXPIRATION_TIMEOUT = 720; // 12 minutes + const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds const int LEASET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds struct SessionTag: public i2p::data::Tag<32> From 9774865d4a18acca901d502bde972108be8e9a1d Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 26 Jan 2016 00:00:00 +0000 Subject: [PATCH 0846/6300] * docs/configuration.md --- docs/configuration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 64925d4b..a48f0f66 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -32,6 +32,8 @@ Command line options * --socksproxy.address= - The address to listen on (SOCKS Proxy) * --socksproxy.port= - The port to listen on (SOCKS Proxy). 4447 by default * --socksproxy.keys= - optional keys file for proxy local destination (both HTTP and SOCKS) +* --socksproxy.outproxy= - Address of outproxy. requests outside i2p will go there +* --socksproxy.outproxyport= - Outproxy remote port * --sam.address= - The address to listen on (SAM bridge) * --sam.port= - Port of SAM bridge. Usually 7656. SAM is off if not specified From ab0d66c2efd5a4d2f484f9c02c63f047bbec368d Mon Sep 17 00:00:00 2001 From: Evgenii Terechkov Date: Tue, 26 Jan 2016 20:03:43 +0700 Subject: [PATCH 0847/6300] Write service log to separate directory --- Daemon.cpp | 2 +- docs/configuration.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 1415435a..c7867242 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -107,7 +107,7 @@ namespace i2p { if (isDaemon) { - std::string logfile_path = IsService () ? "/var/log" : i2p::util::filesystem::GetDataDir().string(); + std::string logfile_path = IsService () ? "/var/log/i2pd" : i2p::util::filesystem::GetDataDir().string(); #ifndef _WIN32 logfile_path.append("/i2pd.log"); #else diff --git a/docs/configuration.md b/docs/configuration.md index 7181cc4c..3cd21b8d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -13,7 +13,7 @@ Command line options * --pidfile= - Where to write pidfile (dont write by default) * --daemon= - Enable or disable daemon mode. 1 for yes, 0 for no. * --svcctl= - Windows service management (--svcctl="install" or --svcctl="remove") -* --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). +* --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd/i2pd.log, /var/lib/i2pd). * --v6= - 1 if supports communication through ipv6, off by default * --floodfill= - 1 if router is floodfill, off by default * --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited From 89b58ec3af1c52b5cf5a666b5183f20c65abc575 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 26 Jan 2016 09:45:29 -0500 Subject: [PATCH 0848/6300] Removed confusing accesslist --- docs/configuration.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 7181cc4c..284bdfff 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -67,7 +67,7 @@ tunnels.cfg (filename of this config is subject of change): # # incoming tunnel sample, for local service # mandatory parameters: - # * type -- always "server" + # * type -- "server" or "http" # * host -- ip address of our service # * port -- port of our service # * keys -- file with LeaseSet of address in i2p @@ -76,11 +76,14 @@ tunnels.cfg (filename of this config is subject of change): # * accesslist -- comma-separated list of i2p addresses, allowed to connect # every address is b32 without '.b32.i2p' part [LOCALSITE] - type = server + type = http host = 127.0.0.1 port = 80 keys = site-keys.dat - inport = 81 - accesslist = [,] + [IRC-SERVER] + type = server + host = 127.0.0.1 + port = 6667 + keys = irc.dat Also see [this page](https://github.com/PurpleI2P/i2pd/wiki/tunnels.cfg) for more tunnel examples. From cfd7f1571bbcd4b141ad7d0dd4a4aaee77572df5 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 26 Jan 2016 19:02:06 -0500 Subject: [PATCH 0849/6300] check clock skew --- NTCPSession.cpp | 21 +++++++++++++++++++++ NTCPSession.h | 1 + 2 files changed, 22 insertions(+) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 8515d1a9..3589c9d2 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -372,6 +372,17 @@ namespace transport buf += 4; buf += paddingLen; + // check timestamp + auto ts = i2p::util::GetSecondsSinceEpoch (); + uint32_t tsA1 = be32toh (tsA); + if (tsA1 < ts - NTCP_CLOCK_SKEW || tsA1 > ts + NTCP_CLOCK_SKEW) + { + LogPrint (eLogError, "NTCP: Phase3 time difference ", ts - tsA1, " exceeds clock skew"); + Terminate (); + return; + } + + // check signature SignedData s; s.Insert (m_Establisher->phase1.pubKey, 256); // x s.Insert (m_Establisher->phase2.pubKey, 256); // y @@ -443,6 +454,16 @@ namespace transport { m_Decryption.Decrypt(m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer); + // check timestamp + uint32_t tsB = bufbe32toh (m_Establisher->phase2.encrypted.timestamp); + auto ts = i2p::util::GetSecondsSinceEpoch (); + if (tsB < ts - NTCP_CLOCK_SKEW || tsB > ts + NTCP_CLOCK_SKEW) + { + LogPrint (eLogError, "NTCP: Phase4 time difference ", ts - tsB, " exceeds clock skew"); + Terminate (); + return; + } + // verify signature SignedData s; s.Insert (m_Establisher->phase1.pubKey, 256); // x diff --git a/NTCPSession.h b/NTCPSession.h index 7e45137e..12d8b54e 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -39,6 +39,7 @@ namespace transport const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes const size_t NTCP_DEFAULT_PHASE3_SIZE = 2/*size*/ + i2p::data::DEFAULT_IDENTITY_SIZE/*387*/ + 4/*ts*/ + 15/*padding*/ + 40/*signature*/; // 448 const int NTCP_BAN_EXPIRATION_TIMEOUT = 70; // in second + const int NTCP_CLOCK_SKEW = 60; // in seconds class NTCPServer; class NTCPSession: public TransportSession, public std::enable_shared_from_this From 6d8b0e3a5d0df673f85c1f1a389eaea03513d24d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 26 Jan 2016 22:30:00 -0500 Subject: [PATCH 0850/6300] control logs destination through -log parameter --- Daemon.cpp | 23 ++++++++++------------- docs/configuration.md | 5 ++--- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index c7867242..ee221080 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -68,7 +68,7 @@ namespace i2p i2p::util::config::ReadConfigFile(i2p::util::filesystem::GetConfigFile()); isDaemon = i2p::util::config::GetArg("-daemon", 0); - isLogging = i2p::util::config::GetArg("-log", 1); + isLogging = i2p::util::config::GetArg("-log", (int)isDaemon); int port = i2p::util::config::GetArg("-port", 0); if (port) @@ -104,22 +104,19 @@ namespace i2p { // initialize log if (isLogging) - { - if (isDaemon) - { - std::string logfile_path = IsService () ? "/var/log/i2pd" : i2p::util::filesystem::GetDataDir().string(); + { + std::string logfile_path = IsService () ? "/var/log/i2pd" : i2p::util::filesystem::GetDataDir().string(); #ifndef _WIN32 - logfile_path.append("/i2pd.log"); + logfile_path.append("/i2pd.log"); #else - logfile_path.append("\\i2pd.log"); + logfile_path.append("\\i2pd.log"); #endif - StartLog (logfile_path); - } else { - StartLog (""); // write to stdout - } - g_Log->SetLogLevel(i2p::util::config::GetArg("-loglevel", "info")); + StartLog (logfile_path); } - + else + StartLog (""); // write to stdout + g_Log->SetLogLevel(i2p::util::config::GetArg("-loglevel", "info")); + std::string httpAddr = i2p::util::config::GetArg("-httpaddress", "127.0.0.1"); uint16_t httpPort = i2p::util::config::GetArg("-httpport", 7070); LogPrint(eLogInfo, "Daemon: staring HTTP Server at ", httpAddr, ":", httpPort); diff --git a/docs/configuration.md b/docs/configuration.md index dcb3dbfb..4fbdd559 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,14 +4,13 @@ i2pd configuration Command line options -------------------- -* --host= - The external IP (deprecated). * --port= - The port to listen on * --httpaddress= - The address to listen on (HTTP server) * --httpport= - The port to listen on (HTTP server) -* --log= - Enable or disable logging to file. 1 for yes, 0 for no. * --loglevel= - Log messages above this level (debug, *info, warn, error) * --pidfile= - Where to write pidfile (dont write by default) -* --daemon= - Enable or disable daemon mode. 1 for yes, 0 for no. +* --daemon= - Enable or disable daemon mode. 1 for yes, 0 for no. 0 by default +* --log= - Enable or disable logging to the file. 1 for daemon, 0 for non-daemon by default * --svcctl= - Windows service management (--svcctl="install" or --svcctl="remove") * --service= - 1 if uses system folders (/var/run/i2pd.pid, /var/log/i2pd/i2pd.log, /var/lib/i2pd). * --v6= - 1 if supports communication through ipv6, off by default From d6d6ae8af2c8423eb02dc6ae319321959c9a5bd2 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Jan 2016 00:00:00 +0000 Subject: [PATCH 0851/6300] * Config.cpp : add old options remapping (revert this after 2.6.0) --- Config.cpp | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/Config.cpp b/Config.cpp index 1b0c3072..27753c87 100644 --- a/Config.cpp +++ b/Config.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,83 @@ namespace config { options_description m_OptionsDesc; variables_map m_Options; + /* list of renamed options */ + std::map remapped_options = { + { "tunnelscfg", "tunconf" }, + { "v6", "ipv6" }, + { "httpaddress", "http.address" }, + { "httpport", "http.port" }, + { "httpproxyaddress", "httpproxy.address" }, + { "httpproxyport", "httpproxy.port" }, + { "socksproxyaddress", "socksproxy.address" }, + { "socksproxyport", "socksproxy.port" }, + { "samaddress", "sam.address" }, + { "samport", "sam.port" }, + { "bobaddress", "bob.address" }, + { "bobport", "bob.port" }, + { "i2pcontroladdress", "i2pcontrol.address" }, + { "i2pcontroladdress", "i2pcontrol.port" }, + { "proxykeys", "httpproxy.keys" }, + }; + /* list of options, that loose their argument and become simple switch */ + std::set boolean_options = { + "daemon", "log", "floodfill", "notransit", "service", "ipv6" + }; + + /* this function is a solid piece of shit, remove it after 2.6.0 */ + std::pair old_syntax_parser(const std::string& s) { + std::string name = ""; + std::string value = ""; + std::size_t pos = 0; + /* shortcuts -- -h */ + if (s.length() == 2 && s.at(0) == '-' && s.at(1) != '-') + return make_pair(s.substr(1), ""); + /* old-style -- -log, /log, etc */ + if (s.at(0) == '/' || (s.at(0) == '-' && s.at(1) != '-')) { + if ((pos = s.find_first_of("= ")) != std::string::npos) { + name = s.substr(1, pos - 1); + value = s.substr(pos + 1); + } else { + name = s.substr(1, pos); + value = ""; + } + if (boolean_options.count(name) > 0 && value != "") + std::cerr << "args: don't give an argument to switch option: " << s << std::endl; + if (m_OptionsDesc.find_nothrow(name, false)) { + std::cerr << "args: option " << s << " style is DEPRECATED, use --" << name << " instead" << std::endl; + return std::make_pair(name, value); + } + if (remapped_options.count(name) > 0) { + name = remapped_options[name]; + std::cerr << "args: option " << s << " is DEPRECATED, use --" << name << " instead" << std::endl; + return std::make_pair(name, value); + } /* else */ + } + /* long options -- --help */ + if (s.substr(0, 2) == "--") { + if ((pos = s.find_first_of("= ")) != std::string::npos) { + name = s.substr(2, pos - 2); + value = s.substr(pos + 1); + } else { + name = s.substr(2, pos); + value = ""; + } + if (boolean_options.count(name) > 0 && value != "") { + std::cerr << "args: don't give an argument to switch option: " << s << std::endl; + value = ""; + } + if (m_OptionsDesc.find_nothrow(name, false)) + return std::make_pair(name, value); + if (remapped_options.count(name) > 0) { + name = remapped_options[name]; + std::cerr << "args: option " << s << " is DEPRECATED, use --" << name << " instead" << std::endl; + return std::make_pair(name, value); + } /* else */ + } + std::cerr << "args: unknown option -- " << s << std::endl; + return std::make_pair("", ""); + } + void Init() { options_description general("General options"); general.add_options() @@ -104,13 +182,16 @@ namespace config { void ParseCmdline(int argc, char* argv[]) { try { - store(parse_command_line(argc, argv, m_OptionsDesc), m_Options); + auto style = boost::program_options::command_line_style::unix_style + | boost::program_options::command_line_style::allow_long_disguise; + style &= ~ boost::program_options::command_line_style::allow_guessing; + store(parse_command_line(argc, argv, m_OptionsDesc, style, old_syntax_parser), m_Options); } catch (boost::program_options::error e) { std::cerr << "args: " << e.what() << std::endl; exit(EXIT_FAILURE); } - if (m_Options.count("help")) { + if (m_Options.count("help") || m_Options.count("h")) { std::cout << "i2pd version " << I2PD_VERSION << " (" << I2P_VERSION << ")" << std::endl; std::cout << m_OptionsDesc; exit(EXIT_SUCCESS); From c053bebccd1ec05e389893189de550c9fd2f3b55 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 27 Jan 2016 21:54:42 -0500 Subject: [PATCH 0852/6300] reduced numeber of error messages --- NTCPSession.cpp | 22 +++++++++++----------- NTCPSession.h | 2 +- SSUData.cpp | 6 +++--- SSUSession.cpp | 18 +++++++++--------- Transports.cpp | 2 +- Tunnel.cpp | 4 ++-- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 3589c9d2..2b745059 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -136,7 +136,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "NTCP: couldn't send Phase 1 message: ", ecode.message ()); + LogPrint (eLogInfo, "NTCP: couldn't send Phase 1 message: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -152,7 +152,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "NTCP: phase 1 read error: ", ecode.message ()); + LogPrint (eLogInfo, "NTCP: phase 1 read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -207,7 +207,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "NTCP: Couldn't send Phase 2 message: ", ecode.message ()); + LogPrint (eLogInfo, "NTCP: Couldn't send Phase 2 message: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -223,7 +223,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "NTCP: Phase 2 read error: ", ecode.message (), ". Wrong ident assumed"); + LogPrint (eLogInfo, "NTCP: Phase 2 read error: ", ecode.message (), ". Wrong ident assumed"); if (ecode != boost::asio::error::operation_aborted) { // this RI is not valid @@ -299,7 +299,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "NTCP: Couldn't send Phase 3 message: ", ecode.message ()); + LogPrint (eLogInfo, "NTCP: Couldn't send Phase 3 message: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -319,7 +319,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "NTCP: Phase 3 read error: ", ecode.message ()); + LogPrint (eLogInfo, "NTCP: Phase 3 read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -331,7 +331,7 @@ namespace transport SetRemoteIdentity (std::make_shared (buf + 2, size)); if (m_Server.FindNTCPSession (m_RemoteIdentity->GetIdentHash ())) { - LogPrint (eLogError, "NTCP: session already exists"); + LogPrint (eLogInfo, "NTCP: session already exists"); Terminate (); } size_t expectedSize = size + 2/*size*/ + 4/*timestamp*/ + m_RemoteIdentity->GetSignatureLen (); @@ -354,7 +354,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "NTCP: Phase 3 extra read error: ", ecode.message ()); + LogPrint (eLogInfo, "NTCP: Phase 3 extra read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -498,7 +498,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "NTCP: Read error: ", ecode.message ()); + LogPrint (eLogInfo, "NTCP: Read error: ", ecode.message ()); if (!m_NumReceivedBytes) m_Server.Ban (m_ConnectedFrom); //if (ecode != boost::asio::error::operation_aborted) Terminate (); @@ -540,7 +540,7 @@ namespace transport moreBytes = m_Socket.read_some (boost::asio::buffer (m_ReceiveBuffer + m_ReceiveBufferOffset, moreBytes)); if (ec) { - LogPrint (eLogError, "NTCP: Read more bytes error: ", ec.message ()); + LogPrint (eLogInfo, "NTCP: Read more bytes error: ", ec.message ()); Terminate (); return; } @@ -732,7 +732,7 @@ namespace transport } //----------------------------------------- - NTCPServer::NTCPServer (int port): + NTCPServer::NTCPServer (): m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_NTCPAcceptor (nullptr), m_NTCPV6Acceptor (nullptr) { diff --git a/NTCPSession.h b/NTCPSession.h index 12d8b54e..f4ce18a6 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -134,7 +134,7 @@ namespace transport { public: - NTCPServer (int port); + NTCPServer (); ~NTCPServer (); void Start (); diff --git a/SSUData.cpp b/SSUData.cpp index f57b25e2..f1282c3d 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -352,7 +352,7 @@ namespace transport } catch (boost::system::system_error& ec) { - LogPrint (eLogError, "SSU: Can't send data fragment ", ec.what ()); + LogPrint (eLogWarning, "SSU: Can't send data fragment ", ec.what ()); } if (!isLast) { @@ -440,7 +440,7 @@ namespace transport } catch (boost::system::system_error& ec) { - LogPrint (eLogError, "SSU: Can't resend data fragment ", ec.what ()); + LogPrint (eLogWarning, "SSU: Can't resend data fragment ", ec.what ()); } } @@ -450,7 +450,7 @@ namespace transport } else { - LogPrint (eLogError, "SSU: message has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted"); + LogPrint (eLogInfo, "SSU: message has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted"); it = m_SentMessages.erase (it); } } diff --git a/SSUSession.cpp b/SSUSession.cpp index 68af81bb..1eef67e1 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -113,14 +113,14 @@ namespace transport auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); if (!address) { - LogPrint (eLogError, "SSU is not supported"); + LogPrint (eLogInfo, "SSU is not supported"); return; } if (Validate (buf, len, address->key)) Decrypt (buf, len, address->key); else { - LogPrint (eLogError, "SSU: MAC verification failed ", len, " bytes from ", senderEndpoint); + LogPrint (eLogWarning, "SSU: MAC verification failed ", len, " bytes from ", senderEndpoint); m_Server.DeleteSession (shared_from_this ()); return; } @@ -330,7 +330,7 @@ namespace transport auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); if (!address) { - LogPrint (eLogError, "SSU is not supported"); + LogPrint (eLogInfo, "SSU is not supported"); return; } @@ -363,7 +363,7 @@ namespace transport i2p::context.GetRouterInfo ().GetSSUAddress (true); //v4 only if (!address) { - LogPrint (eLogError, "SSU is not supported"); + LogPrint (eLogInfo, "SSU is not supported"); return; } SignedData s; // x,y, remote IP, remote port, our IP, our port, relayTag, signed on time @@ -508,7 +508,7 @@ namespace transport // Charlie's address always v4 if (!to.address ().is_v4 ()) { - LogPrint (eLogError, "SSU: Charlie's IP must be v4"); + LogPrint (eLogWarning, "SSU: Charlie's IP must be v4"); return; } *payload = 4; @@ -560,7 +560,7 @@ namespace transport // Alice's address always v4 if (!from.address ().is_v4 ()) { - LogPrint (eLogError, "SSU: Alice's IP must be v4"); + LogPrint (eLogWarning, "SSU: Alice's IP must be v4"); return; } uint8_t buf[48 + 18]; @@ -1015,7 +1015,7 @@ namespace transport if (addr) memcpy (payload, addr->key, 32); // intro key else - LogPrint (eLogError, "SSU is not supported. Can't send peer test"); + LogPrint (eLogInfo, "SSU is not supported. Can't send peer test"); } else memcpy (payload, introKey, 32); // intro key @@ -1044,7 +1044,7 @@ namespace transport auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); if (!address) { - LogPrint (eLogError, "SSU is not supported. Can't send peer test"); + LogPrint (eLogInfo, "SSU is not supported. Can't send peer test"); return; } uint32_t nonce; @@ -1085,7 +1085,7 @@ namespace transport } catch (std::exception& ex) { - LogPrint (eLogError, "SSU: exception while send session destoriyed: ", ex.what ()); + LogPrint (eLogWarning, "SSU: exception while sending session destoroyed: ", ex.what ()); } LogPrint (eLogDebug, "SSU: session destroyed sent"); } diff --git a/Transports.cpp b/Transports.cpp index e2578456..98bd7bda 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -117,7 +117,7 @@ namespace transport { if (!m_NTCPServer) { - m_NTCPServer = new NTCPServer (address.port); + m_NTCPServer = new NTCPServer (); m_NTCPServer->Start (); } diff --git a/Tunnel.cpp b/Tunnel.cpp index 47b3246c..60030916 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -442,7 +442,7 @@ namespace tunnel HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); break; default: - LogPrint (eLogError, "Tunnel: unexpected messsage type ", (int) typeID); + LogPrint (eLogWarning, "Tunnel: unexpected messsage type ", (int) typeID); } msg = m_Queue.Get (); @@ -551,7 +551,7 @@ namespace tunnel it++; break; case eTunnelStateBuildFailed: - LogPrint (eLogError, "Tunnel: pending build request ", it->first, " failed, deleted"); + LogPrint (eLogWarning, "Tunnel: pending build request ", it->first, " failed, deleted"); it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; break; From 85830d50763542f96b1692428a935ab0b26b1bd6 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 27 Jan 2016 22:09:35 -0500 Subject: [PATCH 0853/6300] fixed race condtion #350 --- Transports.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 98bd7bda..8d0ad4a6 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -69,20 +69,20 @@ namespace transport std::shared_ptr DHKeysPairSupplier::Acquire () { - if (!m_Queue.empty ()) { std::unique_lock l(m_AcquiredMutex); - auto pair = m_Queue.front (); - m_Queue.pop (); - m_Acquired.notify_one (); - return pair; + if (!m_Queue.empty ()) + { + auto pair = m_Queue.front (); + m_Queue.pop (); + m_Acquired.notify_one (); + return pair; + } } - else // queue is empty, create new - { - auto pair = std::make_shared (); - pair->GenerateKeys (); - return pair; - } + // queue is empty, create new + auto pair = std::make_shared (); + pair->GenerateKeys (); + return pair; } void DHKeysPairSupplier::Return (std::shared_ptr pair) From 1042e19845ed56640e872c9c4dfa774e04ec2c80 Mon Sep 17 00:00:00 2001 From: 0niichan Date: Fri, 29 Jan 2016 07:18:49 +0700 Subject: [PATCH 0854/6300] httpProxyPort 4444 --- ClientContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 00c98451..0d112894 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -46,7 +46,7 @@ namespace client localDestination = CreateNewLocalDestination (keys, false); } std::string httpProxyAddr = i2p::util::config::GetArg("-httpproxyaddress", "127.0.0.1"); - uint16_t httpProxyPort = i2p::util::config::GetArg("-httpproxyport", 4446); + uint16_t httpProxyPort = i2p::util::config::GetArg("-httpproxyport", 4444); LogPrint(eLogInfo, "Clients: starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); m_HttpProxy = new i2p::proxy::HTTPProxy(httpProxyAddr, httpProxyPort, localDestination); m_HttpProxy->Start(); From bf15ad3bba3a3ed68f2ec70ee2ff296c8847a69d Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 29 Jan 2016 21:53:57 -0500 Subject: [PATCH 0855/6300] 0.9.24 --- RouterContext.cpp | 2 -- version.h | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index 7d35ccf1..d569066d 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -50,10 +50,8 @@ namespace i2p routerInfo.AddNTCPAddress (i2p::util::config::GetArg("-host", "127.0.0.1").c_str (), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC - routerInfo.SetProperty ("coreVersion", I2P_VERSION); routerInfo.SetProperty ("netId", std::to_string (I2PD_NET_ID)); routerInfo.SetProperty ("router.version", I2P_VERSION); - routerInfo.SetProperty ("stat_uptime", "90m"); routerInfo.CreateBuffer (m_Keys); m_RouterInfo.SetRouterIdentity (GetIdentity ()); m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); diff --git a/version.h b/version.h index 8fea069c..a0fff346 100644 --- a/version.h +++ b/version.h @@ -16,7 +16,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 23 +#define I2P_VERSION_MICRO 24 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) From a69cee03e59bf2d178af3442c2c749d98ce073c1 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 29 Jan 2016 22:35:51 -0500 Subject: [PATCH 0856/6300] remove coreVersion and stat_update --- RouterContext.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RouterContext.cpp b/RouterContext.cpp index d569066d..75406daf 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -314,6 +314,10 @@ namespace i2p m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION); m_RouterInfo.SetProperty ("router.version", I2P_VERSION); + // Migration to 0.9.24. TODO: remove later + m_RouterInfo.DeleteProperty ("coreVersion"); + m_RouterInfo.DeleteProperty ("stat_uptime"); + if (IsUnreachable ()) SetReachable (); // we assume reachable until we discover firewall through peer tests From 1dc9e74df4b12b6996b94a595efe21c4724243af Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 30 Jan 2016 10:35:32 -0500 Subject: [PATCH 0857/6300] check TunnelBuild message size --- I2NPProtocol.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 8f528f3c..2cd2b0b2 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -329,6 +329,11 @@ namespace i2p { int num = buf[0]; LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); + if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1) + { + LogPrint (eLogError, "VaribleTunnelBuild message of ", num, " records is too short ", len); + return; + } auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); if (tunnel) @@ -370,6 +375,11 @@ namespace i2p void HandleTunnelBuildMsg (uint8_t * buf, size_t len) { + if (len < NUM_TUNNEL_BUILD_RECORDS*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE) + { + LogPrint (eLogError, "TunnelBuild message is too short ", len); + return; + } uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, buf, clearText)) { @@ -390,7 +400,14 @@ namespace i2p void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) { - LogPrint (eLogDebug, "I2NP: VariableTunnelBuildReplyMsg replyMsgID=", replyMsgID); + int num = buf[0]; + LogPrint (eLogDebug, "I2NP: VariableTunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); + if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1) + { + LogPrint (eLogError, "VaribleTunnelBuildReply message of ", num, " records is too short ", len); + return; + } + auto tunnel = i2p::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID); if (tunnel) { From 68bc78d00b2c439c88de161c505a957e075f00fb Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 30 Jan 2016 21:04:02 -0500 Subject: [PATCH 0858/6300] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f199b39..4f167754 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ i2pd ==== -I2P router written in C++ -Contains all ongoing changes from https://bitbucket.org/orignal/i2pd/src +Independent C++ implementation of I2P router License ------- @@ -29,8 +28,9 @@ Supported OS ------------ * Linux x86/x64 - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) -* Mac OS X - Not well tested. (Only works with clang, not GCC) * Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) +* Mac OS X +* FreeBSD More documentation ------------------ From a8e4301f23fbb402746f9f85dbbf1da78cb5cae3 Mon Sep 17 00:00:00 2001 From: 0niichan Date: Sun, 31 Jan 2016 23:59:37 +0600 Subject: [PATCH 0859/6300] correct BOOST_SUFFIX --- Makefile.mingw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.mingw b/Makefile.mingw index 57fe094b..18f04c3f 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,7 +1,7 @@ CXX = g++ CXXFLAGS = -O2 -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN NEEDED_CXXFLAGS = -std=c++11 -BOOST_SUFFIX = -mgw48-mt-1_59 +BOOST_SUFFIX = -mt INCFLAGS = -I/usr/include/ -I/usr/local/include/ -I/c/dev/openssl/include -I/c/dev/boost/include/boost-1_59 LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/c/dev/openssl -L/c/dev/boost/lib LDLIBS = -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) -Wl,-Bstatic -lssl -Wl,-Bstatic -lcrypto -Wl,-Bstatic -lz -Wl,-Bstatic -lwsock32 -Wl,-Bstatic -lws2_32 -Wl,-Bstatic -lgdi32 -Wl,-Bstatic -liphlpapi -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -Wl,-Bstatic -lpthread From 8805f1e4d64cea5bc2b94a1d6e3ab63b5c83fdfd Mon Sep 17 00:00:00 2001 From: h0bbyte Date: Sun, 31 Jan 2016 22:52:20 +0300 Subject: [PATCH 0860/6300] I2PControl add total.sent|received.bytes --- I2PControl.cpp | 178 ++++++++++++++++++++++++++----------------------- I2PControl.h | 62 +++++++++-------- 2 files changed, 128 insertions(+), 112 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index ad81ec93..94dd5c80 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -32,13 +32,13 @@ namespace client m_ShutdownTimer (m_Service) { LoadConfig (); - // certificate + // certificate auto path = GetPath (); if (!boost::filesystem::exists (path)) { if (!boost::filesystem::create_directory (path)) LogPrint (eLogError, "Failed to create i2pcontrol directory"); - } + } if (!boost::filesystem::exists (path / I2P_CONTROL_KEY_FILE) || !boost::filesystem::exists (path / I2P_CONTROL_CERT_FILE)) { @@ -51,15 +51,15 @@ namespace client m_SSLContext.use_private_key_file ((path / I2P_CONTROL_KEY_FILE).string (), boost::asio::ssl::context::pem); // handlers - m_MethodHandlers[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlService::AuthenticateHandler; - m_MethodHandlers[I2P_CONTROL_METHOD_ECHO] = &I2PControlService::EchoHandler; - m_MethodHandlers[I2P_CONTROL_METHOD_I2PCONTROL] = &I2PControlService::I2PControlHandler; - m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlService::RouterInfoHandler; - m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlService::RouterManagerHandler; - m_MethodHandlers[I2P_CONTROL_METHOD_NETWORK_SETTING] = &I2PControlService::NetworkSettingHandler; + m_MethodHandlers[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlService::AuthenticateHandler; + m_MethodHandlers[I2P_CONTROL_METHOD_ECHO] = &I2PControlService::EchoHandler; + m_MethodHandlers[I2P_CONTROL_METHOD_I2PCONTROL] = &I2PControlService::I2PControlHandler; + m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlService::RouterInfoHandler; + m_MethodHandlers[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlService::RouterManagerHandler; + m_MethodHandlers[I2P_CONTROL_METHOD_NETWORK_SETTING] = &I2PControlService::NetworkSettingHandler; // I2PControl - m_I2PControlHandlers[I2P_CONTROL_I2PCONTROL_PASSWORD] = &I2PControlService::PasswordHandler; + m_I2PControlHandlers[I2P_CONTROL_I2PCONTROL_PASSWORD] = &I2PControlService::PasswordHandler; // RouterInfo m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_UPTIME] = &I2PControlService::UptimeHandler; @@ -71,9 +71,11 @@ namespace client m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlService::TunnelsParticipatingHandler; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_IB_1S] = &I2PControlService::InboundBandwidth1S ; m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_OB_1S] = &I2PControlService::OutboundBandwidth1S ; + m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NET_TOTAL_RB] = &I2PControlService::NetTotalReceivedBytes; + m_RouterInfoHandlers[I2P_CONTROL_ROUTER_INFO_NET_TOTAL_SB] = &I2PControlService::NetTotalSentBytes; - // RouterManager - m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = &I2PControlService::ShutdownHandler; + // RouterManager + m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = &I2PControlService::ShutdownHandler; m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = &I2PControlService::ShutdownGracefulHandler; m_RouterManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_RESEED] = &I2PControlService::ReseedHandler; } @@ -90,12 +92,12 @@ namespace client { if (!boost::filesystem::create_directory (path)) LogPrint (eLogError, "Failed to create i2pcontrol directory"); - } + } boost::property_tree::ptree pt; auto filename = path / I2P_CONTROL_CONFIG_FILE; bool isNew = true; if (boost::filesystem::exists (filename)) - { + { try { boost::property_tree::read_ini (filename.string (), pt); @@ -104,10 +106,10 @@ namespace client catch (std::exception& ex) { LogPrint (eLogError, "Can't read ", filename, ": ", ex.what ()); - } + } } m_Password = pt.get (I2P_CONTROL_I2PCONTROL_PASSWORD, I2P_CONTROL_DEFAULT_PASSWORD); - if (isNew) SaveConfig (); + if (isNew) SaveConfig (); } void I2PControlService::SaveConfig () @@ -115,15 +117,15 @@ namespace client boost::property_tree::ptree pt; pt.put (I2P_CONTROL_I2PCONTROL_PASSWORD, m_Password); auto filename = GetPath () / I2P_CONTROL_CONFIG_FILE; - // we take care about directory in LoadConfig + // we take care about directory in LoadConfig try { boost::property_tree::write_ini (filename.string (), pt); - } + } catch (std::exception& ex) { LogPrint (eLogError, "Can't write ", filename, ": ", ex.what ()); - } + } } void I2PControlService::Start () @@ -141,30 +143,30 @@ namespace client if (m_IsRunning) { m_IsRunning = false; - m_Acceptor.cancel (); + m_Acceptor.cancel (); m_Service.stop (); if (m_Thread) - { - m_Thread->join (); + { + m_Thread->join (); delete m_Thread; m_Thread = nullptr; - } + } } } - void I2PControlService::Run () - { + void I2PControlService::Run () + { while (m_IsRunning) { try - { + { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, "I2PControl: ", ex.what ()); - } - } + } + } } void I2PControlService::Accept () @@ -182,7 +184,7 @@ namespace client if (!ecode) { LogPrint (eLogInfo, "New I2PControl request from ", socket->lowest_layer ().remote_endpoint ()); - Handshake (socket); + Handshake (socket); } else LogPrint (eLogError, "I2PControl accept error: ", ecode.message ()); @@ -200,7 +202,7 @@ namespace client { //std::this_thread::sleep_for (std::chrono::milliseconds(5)); ReadRequest (socket); - } + } else LogPrint (eLogError, "I2PControl handshake error: ", ecode.message ()); } @@ -210,16 +212,16 @@ namespace client auto request = std::make_shared(); socket->async_read_some ( #if defined(BOOST_ASIO_HAS_STD_ARRAY) - boost::asio::buffer (*request), + boost::asio::buffer (*request), #else - boost::asio::buffer (request->data (), request->size ()), -#endif - std::bind(&I2PControlService::HandleRequestReceived, this, + boost::asio::buffer (request->data (), request->size ()), +#endif + std::bind(&I2PControlService::HandleRequestReceived, this, std::placeholders::_1, std::placeholders::_2, socket, request)); } void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode, - size_t bytes_transferred, std::shared_ptr socket, + size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf) { if (ecode) @@ -238,12 +240,12 @@ namespace client std::string header; size_t contentLength = 0; while (!ss.eof () && header != "\r") - { + { std::getline(ss, header); auto colon = header.find (':'); if (colon != std::string::npos && header.substr (0, colon) == "Content-Length") contentLength = std::stoi (header.substr (colon + 1)); - } + } if (ss.eof ()) { LogPrint (eLogError, "Malformed I2PControl request. HTTP header expected"); @@ -251,10 +253,10 @@ namespace client } std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read if (rem > 0) - { + { bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), rem)); ss.write (buf->data (), bytes_transferred); - } + } } #if GCC47_BOOST149 LogPrint (eLogError, "json_read is not supported due bug in boost 1.49 with gcc 4.7"); @@ -267,12 +269,12 @@ namespace client if (it != m_MethodHandlers.end ()) { std::ostringstream response; - response << "{\"id\":" << pt.get(I2P_CONTROL_PROPERTY_ID) << ",\"result\":{"; + response << "{\"id\":" << pt.get(I2P_CONTROL_PROPERTY_ID) << ",\"result\":{"; (this->*(it->second))(pt.get_child (I2P_CONTROL_PROPERTY_PARAMS), response); response << "},\"jsonrpc\":\"2.0\"}"; SendResponse (socket, buf, response, isHtml); - } + } else LogPrint (eLogWarning, "Unknown I2PControl method ", method); #endif @@ -291,21 +293,21 @@ namespace client void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, int value) const { ss << "\"" << name << "\":" << value; - } + } void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const { - ss << "\"" << name << "\":"; + ss << "\"" << name << "\":"; if (value.length () > 0) ss << "\"" << value << "\""; else ss << "null"; - } - + } + void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, double value) const { ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; - } + } void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) @@ -321,15 +323,15 @@ namespace client header << "Date: "; auto facet = new boost::local_time::local_time_facet ("%a, %d %b %Y %H:%M:%S GMT"); header.imbue(std::locale (header.getloc(), facet)); - header << boost::posix_time::second_clock::local_time() << "\r\n"; + header << boost::posix_time::second_clock::local_time() << "\r\n"; header << "\r\n"; offset = header.str ().size (); memcpy (buf->data (), header.str ().c_str (), offset); - } + } memcpy (buf->data () + offset, response.str ().c_str (), len); - boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), + boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), boost::asio::transfer_all (), - std::bind(&I2PControlService::HandleResponseSent, this, + std::bind(&I2PControlService::HandleResponseSent, this, std::placeholders::_1, std::placeholders::_2, socket, buf)); } @@ -354,15 +356,15 @@ namespace client InsertParam (results, I2P_CONTROL_PARAM_API, api); results << ","; std::string token = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); - m_Tokens.insert (token); + m_Tokens.insert (token); InsertParam (results, I2P_CONTROL_PARAM_TOKEN, token); - } + } void I2PControlService::EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { auto echo = params.get (I2P_CONTROL_PARAM_ECHO); LogPrint (eLogDebug, "I2PControl Echo Echo=", echo); - InsertParam (results, I2P_CONTROL_PARAM_RESULT, echo); + InsertParam (results, I2P_CONTROL_PARAM_RESULT, echo); } @@ -377,12 +379,12 @@ namespace client auto it1 = m_I2PControlHandlers.find (it.first); if (it1 != m_I2PControlHandlers.end ()) { - (this->*(it1->second))(it.second.data ()); - InsertParam (results, it.first, ""); + (this->*(it1->second))(it.second.data ()); + InsertParam (results, it.first, ""); } else - LogPrint (eLogError, "I2PControl I2PControl unknown request ", it.first); - } + LogPrint (eLogError, "I2PControl I2PControl unknown request ", it.first); + } } void I2PControlService::PasswordHandler (const std::string& value) @@ -404,9 +406,9 @@ namespace client auto it1 = m_RouterInfoHandlers.find (it->first); if (it1 != m_RouterInfoHandlers.end ()) { - if (it != params.begin ()) results << ","; - (this->*(it1->second))(results); - } + if (it != params.begin ()) results << ","; + (this->*(it1->second))(results); + } else LogPrint (eLogError, "I2PControl RouterInfo unknown request ", it->first); } @@ -414,27 +416,27 @@ namespace client void I2PControlService::UptimeHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_UPTIME, (int)i2p::context.GetUptime ()*1000); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_UPTIME, (int)i2p::context.GetUptime ()*1000); } void I2PControlService::VersionHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_VERSION, VERSION); - } + InsertParam (results, I2P_CONTROL_ROUTER_INFO_VERSION, VERSION); + } void I2PControlService::StatusHandler (std::ostringstream& results) { InsertParam (results, I2P_CONTROL_ROUTER_INFO_STATUS, "???"); // TODO: } - + void I2PControlService::NetDbKnownPeersHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS, i2p::data::netdb.GetNumRouters ()); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS, i2p::data::netdb.GetNumRouters ()); } void I2PControlService::NetDbActivePeersHandler (std::ostringstream& results) { - InsertParam (results, I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS, (int)i2p::transport::transports.GetPeers ().size ()); + InsertParam (results, I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS, (int)i2p::transport::transports.GetPeers ().size ()); } void I2PControlService::NetStatusHandler (std::ostringstream& results) @@ -457,25 +459,35 @@ namespace client InsertParam (results, I2P_CONTROL_ROUTER_INFO_BW_OB_1S, (double)i2p::transport::transports.GetOutBandwidth ()); } + void I2PControlService::NetTotalReceivedBytes (std::ostringstream& results) + { + InsertParam (results, I2P_CONTROL_ROUTER_INFO_NET_TOTAL_RB, (double)i2p::transport::transports.GetTotalReceivedBytes ()); + } + + void I2PControlService::NetTotalSentBytes (std::ostringstream& results) + { + InsertParam (results, I2P_CONTROL_ROUTER_INFO_NET_TOTAL_SB, (double)i2p::transport::transports.GetTotalSentBytes ()); + } + // RouterManager - + void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { LogPrint (eLogDebug, "I2PControl RouterManager"); for (auto it = params.begin (); it != params.end (); it++) { - if (it != params.begin ()) results << ","; + if (it != params.begin ()) results << ","; LogPrint (eLogDebug, it->first); auto it1 = m_RouterManagerHandlers.find (it->first); if (it1 != m_RouterManagerHandlers.end ()) - (this->*(it1->second))(results); + (this->*(it1->second))(results); else - LogPrint (eLogError, "I2PControl RouterManager unknown request ", it->first); + LogPrint (eLogError, "I2PControl RouterManager unknown request ", it->first); } - } + } - void I2PControlService::ShutdownHandler (std::ostringstream& results) + void I2PControlService::ShutdownHandler (std::ostringstream& results) { LogPrint (eLogInfo, "Shutdown requested"); InsertParam (results, I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, ""); @@ -483,7 +495,7 @@ namespace client m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) { - Daemon.running = 0; + Daemon.running = 0; }); } @@ -497,14 +509,14 @@ namespace client m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) { - Daemon.running = 0; + Daemon.running = 0; }); } void I2PControlService::ReseedHandler (std::ostringstream& results) { LogPrint (eLogInfo, "Reseed requested"); - InsertParam (results, I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, ""); + InsertParam (results, I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, ""); i2p::data::netdb.Reseed (); } @@ -514,17 +526,17 @@ namespace client LogPrint (eLogDebug, "I2PControl NetworkSetting"); for (auto it = params.begin (); it != params.end (); it++) { - if (it != params.begin ()) results << ","; + if (it != params.begin ()) results << ","; LogPrint (eLogDebug, it->first); auto it1 = m_NetworkSettingHandlers.find (it->first); if (it1 != m_NetworkSettingHandlers.end ()) - (this->*(it1->second))(it->second.data (), results); + (this->*(it1->second))(it->second.data (), results); else - LogPrint (eLogError, "I2PControl NetworkSetting unknown request ", it->first); + LogPrint (eLogError, "I2PControl NetworkSetting unknown request ", it->first); } } - // certificate + // certificate void I2PControlService::CreateCertificate () { EVP_PKEY * pkey = EVP_PKEY_new (); @@ -538,8 +550,8 @@ namespace client X509 * x509 = X509_new (); ASN1_INTEGER_set (X509_get_serialNumber (x509), 1); X509_gmtime_adj (X509_get_notBefore (x509), 0); - X509_gmtime_adj (X509_get_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration - X509_set_pubkey (x509, pkey); // public key + X509_gmtime_adj (X509_get_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration + X509_set_pubkey (x509, pkey); // public key X509_NAME * name = X509_get_subject_name (x509); X509_NAME_add_entry_by_txt (name, "C", MBSTRING_ASC, (unsigned char *)"RU", -1, -1, 0); // country (Russia by default) X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization @@ -548,7 +560,7 @@ namespace client X509_sign (x509, pkey, EVP_sha1 ()); // sign // save key and certificate // keys - auto filename = GetPath () / I2P_CONTROL_KEY_FILE; + auto filename = GetPath () / I2P_CONTROL_KEY_FILE; FILE * f= fopen (filename.string ().c_str (), "wb"); if (f) { @@ -557,7 +569,7 @@ namespace client } else LogPrint (eLogError, "Can't open file ", filename); - // certificate + // certificate filename = GetPath () / I2P_CONTROL_CERT_FILE; f= fopen (filename.string ().c_str (), "wb"); if (f) @@ -568,7 +580,7 @@ namespace client else LogPrint (eLogError, "Can't open file ", filename); - X509_free (x509); + X509_free (x509); } else LogPrint (eLogError, "Couldn't create RSA key for certificate"); diff --git a/I2PControl.h b/I2PControl.h index 10b6b651..e6114968 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -20,56 +20,58 @@ namespace i2p namespace client { const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024; - typedef std::array I2PControlBuffer; + typedef std::array I2PControlBuffer; const char I2P_CONTROL_PATH[] = "ipcontrol"; const char I2P_CONTROL_KEY_FILE[] = "key.pem"; const char I2P_CONTROL_CERT_FILE[] = "cert.pem"; const char I2P_CONTROL_CONFIG_FILE[] = "i2pcontrol.conf"; - const char I2P_CONTROL_DEFAULT_PASSWORD[] = "itoopie"; + const char I2P_CONTROL_DEFAULT_PASSWORD[] = "itoopie"; const char I2P_CONTROL_PROPERTY_ID[] = "id"; const char I2P_CONTROL_PROPERTY_METHOD[] = "method"; const char I2P_CONTROL_PROPERTY_PARAMS[] = "params"; const char I2P_CONTROL_PROPERTY_RESULT[] = "result"; - // methods + // methods const char I2P_CONTROL_METHOD_AUTHENTICATE[] = "Authenticate"; const char I2P_CONTROL_METHOD_ECHO[] = "Echo"; - const char I2P_CONTROL_METHOD_I2PCONTROL[] = "I2PControl"; - const char I2P_CONTROL_METHOD_ROUTER_INFO[] = "RouterInfo"; - const char I2P_CONTROL_METHOD_ROUTER_MANAGER[] = "RouterManager"; - const char I2P_CONTROL_METHOD_NETWORK_SETTING[] = "NetworkSetting"; + const char I2P_CONTROL_METHOD_I2PCONTROL[] = "I2PControl"; + const char I2P_CONTROL_METHOD_ROUTER_INFO[] = "RouterInfo"; + const char I2P_CONTROL_METHOD_ROUTER_MANAGER[] = "RouterManager"; + const char I2P_CONTROL_METHOD_NETWORK_SETTING[] = "NetworkSetting"; // params - const char I2P_CONTROL_PARAM_API[] = "API"; - const char I2P_CONTROL_PARAM_PASSWORD[] = "Password"; - const char I2P_CONTROL_PARAM_TOKEN[] = "Token"; - const char I2P_CONTROL_PARAM_ECHO[] = "Echo"; - const char I2P_CONTROL_PARAM_RESULT[] = "Result"; + const char I2P_CONTROL_PARAM_API[] = "API"; + const char I2P_CONTROL_PARAM_PASSWORD[] = "Password"; + const char I2P_CONTROL_PARAM_TOKEN[] = "Token"; + const char I2P_CONTROL_PARAM_ECHO[] = "Echo"; + const char I2P_CONTROL_PARAM_RESULT[] = "Result"; // I2PControl - const char I2P_CONTROL_I2PCONTROL_ADDRESS[] = "i2pcontrol.address"; + const char I2P_CONTROL_I2PCONTROL_ADDRESS[] = "i2pcontrol.address"; const char I2P_CONTROL_I2PCONTROL_PASSWORD[] = "i2pcontrol.password"; - const char I2P_CONTROL_I2PCONTROL_PORT[] = "i2pcontrol.port"; + const char I2P_CONTROL_I2PCONTROL_PORT[] = "i2pcontrol.port"; // RouterInfo requests const char I2P_CONTROL_ROUTER_INFO_UPTIME[] = "i2p.router.uptime"; const char I2P_CONTROL_ROUTER_INFO_VERSION[] = "i2p.router.version"; - const char I2P_CONTROL_ROUTER_INFO_STATUS[] = "i2p.router.status"; + const char I2P_CONTROL_ROUTER_INFO_STATUS[] = "i2p.router.status"; const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers"; const char I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers"; - const char I2P_CONTROL_ROUTER_INFO_NET_STATUS[] = "i2p.router.net.status"; + const char I2P_CONTROL_ROUTER_INFO_NET_STATUS[] = "i2p.router.net.status"; const char I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating"; - const char I2P_CONTROL_ROUTER_INFO_BW_IB_1S[] = "i2p.router.net.bw.inbound.1s"; - const char I2P_CONTROL_ROUTER_INFO_BW_OB_1S[] = "i2p.router.net.bw.outbound.1s"; + const char I2P_CONTROL_ROUTER_INFO_BW_IB_1S[] = "i2p.router.net.bw.inbound.1s"; + const char I2P_CONTROL_ROUTER_INFO_BW_OB_1S[] = "i2p.router.net.bw.outbound.1s"; + const char I2P_CONTROL_ROUTER_INFO_NET_TOTAL_RB[] = "i2p.router.net.total.received.bytes"; + const char I2P_CONTROL_ROUTER_INFO_NET_TOTAL_SB[] = "i2p.router.net.total.sent.bytes"; // RouterManager requests const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN[] = "Shutdown"; const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful"; - const char I2P_CONTROL_ROUTER_MANAGER_RESEED[] = "Reseed"; + const char I2P_CONTROL_ROUTER_MANAGER_RESEED[] = "Reseed"; - // Certificate + // Certificate const long I2P_CONTROL_CERTIFICATE_VALIDITY = 365*10; // 10 years const char I2P_CONTROL_CERTIFICATE_COMMON_NAME[] = "i2pd.i2pcontrol"; const char I2P_CONTROL_CERTIFICATE_ORGANIZATION[] = "Purple I2P"; @@ -92,11 +94,11 @@ namespace client void Run (); void Accept (); - void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); + void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); void Handshake (std::shared_ptr socket); void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket); void ReadRequest (std::shared_ptr socket); - void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, + void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); void SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml); @@ -105,7 +107,7 @@ namespace client boost::filesystem::path GetPath () const { return i2p::util::filesystem::GetDefaultDataDir() / I2P_CONTROL_PATH; }; void CreateCertificate (); - + private: @@ -121,7 +123,7 @@ namespace client void I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results); - void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results); + void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results); // I2PControl typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value); @@ -133,11 +135,13 @@ namespace client void VersionHandler (std::ostringstream& results); void StatusHandler (std::ostringstream& results); void NetDbKnownPeersHandler (std::ostringstream& results); - void NetDbActivePeersHandler (std::ostringstream& results); - void NetStatusHandler (std::ostringstream& results); + void NetDbActivePeersHandler (std::ostringstream& results); + void NetStatusHandler (std::ostringstream& results); void TunnelsParticipatingHandler (std::ostringstream& results); void InboundBandwidth1S (std::ostringstream& results); void OutboundBandwidth1S (std::ostringstream& results); + void NetTotalReceivedBytes (std::ostringstream& results); + void NetTotalSentBytes (std::ostringstream& results); // RouterManager typedef void (I2PControlService::*RouterManagerRequestHandler)(std::ostringstream& results); @@ -146,20 +150,20 @@ namespace client void ReseedHandler (std::ostringstream& results); // NetworkSetting - typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); + typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); private: std::string m_Password; bool m_IsRunning; - std::thread * m_Thread; + std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::ssl::context m_SSLContext; boost::asio::deadline_timer m_ShutdownTimer; std::set m_Tokens; - + std::map m_MethodHandlers; std::map m_I2PControlHandlers; std::map m_RouterInfoHandlers; From 97f8ab5c5149ea6eb97098d53f18fb16dc4d3ed6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 31 Jan 2016 14:58:49 -0500 Subject: [PATCH 0861/6300] Update build_notes_windows.md --- docs/build_notes_windows.md | 89 ++----------------------------------- 1 file changed, 4 insertions(+), 85 deletions(-) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index 3779eca0..0d3a39bb 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -23,33 +23,18 @@ paths like /c/dev/ for C:\dev\. msys2 ----- -Get it from https://msys2.github.io and update it as described -there. Use the installer appropriate for the bitness of your Windows -OS. You will be able to build 32-bit applications if you install -64-bit version of msys2. For 64-bit, use *mingw-w64-x86_64* prefix -instead of *mingw-w64-i686* for the packages mentioned below, and use -*/mingw64* as CMake find root. - +Get install file msys2-i686-20150916.exe from https://msys2.github.io. +open MSys2Shell (from Start menu). Install all prerequisites and download i2pd source: ```bash -pacman -S mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-gcc mingw-w64-i686-miniupnpc cmake git +pacman -S mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-gcc git make mkdir -p /c/dev/i2pd cd /c/dev/i2pd git clone https://github.com/PurpleI2P/i2pd.git cd i2pd -``` - -Check with `git status` that you are on *openssl* branch. If it is not -the case, do `git checkout openssl`. - -```sh -git pull origin openssl --ff-only # to update sources if you are rebuilding after a while -mkdir -p mingw32.build # CMake build folder -cd mingw32.build export PATH=/mingw32/bin:/usr/bin # we need compiler on PATH which is usually heavily cluttered on Windows -cmake ../build -G "Unix Makefiles" -DWITH_UPNP=ON -DWITH_PCH=ON \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=../mingw32.stage -DCMAKE_FIND_ROOT_PATH=/mingw32 +make ``` If your processor has @@ -57,72 +42,6 @@ If your processor has you may try adding `-DWITH_AESNI=ON`. No check is done however, it will compile but will crash with `Illegal instruction` if not supported. -Make sure CMake found proper libraries and compiler. This might be the -case if you have Strawberry Perl installed as it alters PATH and you -failed to override it like mentioned above. You should see something -like - -``` --- The C compiler identification is GNU 5.2.0 --- The CXX compiler identification is GNU 5.2.0 --- Check for working C compiler: /mingw32/bin/gcc.exe --- Check for working C compiler: /mingw32/bin/gcc.exe -- works --- Detecting C compiler ABI info --- Detecting C compiler ABI info - done --- Detecting C compile features --- Detecting C compile features - done --- Check for working CXX compiler: /mingw32/bin/c++.exe --- Check for working CXX compiler: /mingw32/bin/c++.exe -- works --- Detecting CXX compiler ABI info --- Detecting CXX compiler ABI info - done --- Detecting CXX compile features --- Detecting CXX compile features - done --- Performing Test CXX11_SUPPORTED --- Performing Test CXX11_SUPPORTED - Success --- Performing Test CXX0X_SUPPORTED --- Performing Test CXX0X_SUPPORTED - Success --- Looking for include file pthread.h --- Looking for include file pthread.h - found --- Looking for pthread_create --- Looking for pthread_create - found --- Found Threads: TRUE --- Boost version: 1.59.0 --- Found the following Boost libraries: --- system --- filesystem --- regex --- program_options --- date_time --- thread --- chrono --- Found OpenSSL: /mingw32/lib/libssl.dll.a;/mingw32/lib/libcrypto.dll.a (found version "1.0.2d") --- Found MiniUPnP headers: /mingw32/include --- Found ZLIB: /mingw32/lib/libz.dll.a (found version "1.2.8") --- --------------------------------------- --- Build type : RelWithDebInfo --- Compiler vendor : GNU --- Compiler version : 5.2.0 --- Compiler path : /mingw32/bin/c++.exe --- Install prefix: : ../mingw32.stage --- Options: --- AESNI : OFF --- HARDENING : OFF --- LIBRARY : ON --- BINARY : ON --- STATIC BUILD : OFF --- UPnP : ON --- PCH : ON --- --------------------------------------- --- Configuring done --- Generating done --- Build files have been written to: /c/dev/i2pd/i2pd/mingw32.build -``` - -Now it is time to compile everything. If you have a multicore processor -you can add `-j` flag. - - make -j4 install - You should be able to run ./i2pd . If you need to start from the new shell, consider starting *MinGW-w64 Win32 Shell* instead of *MSYS2 Shell* as it adds`/minw32/bin` to the PATH. From 096636972359209177db06d2f8a419fefb247c59 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 31 Jan 2016 18:27:47 -0500 Subject: [PATCH 0862/6300] copy transit message for nedb --- I2NPProtocol.cpp | 9 +++++++++ I2NPProtocol.h | 5 +++-- Tunnel.cpp | 2 +- TunnelEndpoint.cpp | 9 +++++---- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 2cd2b0b2..c31a4479 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -80,6 +80,15 @@ namespace i2p LogPrint (eLogError, "I2NP: message length ", len, " exceeds max length"); return msg; } + + std::shared_ptr CopyI2NPMessage (std::shared_ptr msg) + { + if (!msg) return nullptr; + auto newMsg = NewI2NPMessage (msg->len); + newMsg->offset = msg->offset; + *newMsg = *msg; + return newMsg; + } std::shared_ptr CreateDeliveryStatusMsg (uint32_t msgID) { diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 898396d0..173b57fb 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -174,7 +174,7 @@ namespace tunnel from = other.from; return *this; } - + // for SSU only uint8_t * GetSSUHeader () { return buf + offset + I2NP_HEADER_SIZE - I2NP_SHORT_HEADER_SIZE; }; void FromSSU (uint32_t msgID) // we have received SSU message and convert it to regular @@ -215,7 +215,8 @@ namespace tunnel std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0); std::shared_ptr CreateI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from = nullptr); - + std::shared_ptr CopyI2NPMessage (std::shared_ptr msg); + std::shared_ptr CreateDeliveryStatusMsg (uint32_t msgID); std::shared_ptr CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, uint32_t replyTunnelID, bool exploratory = false, std::set * excludedPeers = nullptr); diff --git a/Tunnel.cpp b/Tunnel.cpp index 60030916..dbe87a2f 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -494,7 +494,7 @@ namespace tunnel if (IsRouterInfoMsg (msg) || typeID == eI2NPDatabaseSearchReply) // transit DatabaseStore my contain new/updated RI // or DatabaseSearchReply with new routers - i2p::data::netdb.PostI2NPMsg (msg); + i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg)); tunnel->SendTunnelDataMsg (msg); } diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 0b730d35..a3907ce5 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -237,6 +237,11 @@ namespace tunnel } auto typeID = msg.data->GetTypeID (); LogPrint (eLogDebug, "TunnelMessage: handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID); + // catch RI or reply with new list of routers + if ((IsRouterInfoMsg (msg.data) || typeID == eI2NPDatabaseSearchReply) && + !m_IsInbound && msg.deliveryType != eDeliveryTypeLocal) + i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg.data)); + switch (msg.deliveryType) { case eDeliveryTypeLocal: @@ -257,10 +262,6 @@ namespace tunnel default: LogPrint (eLogError, "TunnelMessage: Unknown delivery type ", (int)msg.deliveryType); }; - // catch RI or reply with new list of routers - if ((IsRouterInfoMsg (msg.data) || typeID == eI2NPDatabaseSearchReply) && - !m_IsInbound && msg.deliveryType != eDeliveryTypeLocal) - i2p::data::netdb.PostI2NPMsg (msg.data); } } } From c9cf84f2f498a7b92d2191678942abc59e7afc18 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 31 Jan 2016 22:37:38 -0500 Subject: [PATCH 0863/6300] correct SAM datagram size for Windows --- SAM.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SAM.h b/SAM.h index e82e8b7d..07142c8c 100644 --- a/SAM.h +++ b/SAM.h @@ -41,7 +41,7 @@ namespace client const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n"; const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP"; const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n"; - const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%zu\n"; + const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n"; const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n"; const char SAM_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=INVALID_KEY_NOT_FOUND NAME=%s\n"; const char SAM_PARAM_MIN[] = "MIN"; From 0c56cd63bdd7f8b8c8cf60843a0ed7ed86660b49 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 1 Feb 2016 00:00:00 +0000 Subject: [PATCH 0864/6300] * chg default port for http proxy --- Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config.cpp b/Config.cpp index 27753c87..ab99f293 100644 --- a/Config.cpp +++ b/Config.cpp @@ -133,7 +133,7 @@ namespace config { httpproxy.add_options() ("httpproxy.enabled", value()->default_value(true), "Enable or disable HTTP Proxy") ("httpproxy.address", value()->default_value("127.0.0.1"), "HTTP Proxy listen address") - ("httpproxy.port", value()->default_value(4446), "HTTP Proxy listen port") + ("httpproxy.port", value()->default_value(4444), "HTTP Proxy listen port") ("httpproxy.keys", value()->default_value("httpproxy-keys.dat"), "HTTP Proxy encryption keys") ; From d2d4fa29e4fac1fee522d41910a6b486a381fa48 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 1 Feb 2016 00:00:00 +0000 Subject: [PATCH 0865/6300] * add --logfile option --- Config.cpp | 1 + Daemon.cpp | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Config.cpp b/Config.cpp index ab99f293..a29e4b02 100644 --- a/Config.cpp +++ b/Config.cpp @@ -111,6 +111,7 @@ namespace config { ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg)") ("pidfile", value()->default_value(""), "Write pidfile to given path") ("log", value()->zero_tokens(), "Write logs to file instead stdout") + ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") ("host", value()->default_value(""), "External IP (deprecated)") ("port,p", value()->default_value(4567), "Port to listen for incoming connections") diff --git a/Daemon.cpp b/Daemon.cpp index a2d33720..a0c3f5bb 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -116,22 +116,25 @@ namespace i2p bool Daemon_Singleton::start() { - // initialize log if (isLogging) { - if (isDaemon) - { - std::string logfile_path = IsService () ? "/var/log" : i2p::util::filesystem::GetDataDir().string(); + // set default to stdout + std::string logfile = ""; i2p::config::GetOption("logfile", logfile); + std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); + if (isDaemon && logfile == "") { + // can't log to stdout, use autodetect of logfile + if (IsService ()) { + logfile = "/var/log"; + } else { + logfile = i2p::util::filesystem::GetDataDir().string(); + } #ifndef _WIN32 - logfile_path.append("/i2pd.log"); + logfile.append("/i2pd.log"); #else - logfile_path.append("\\i2pd.log"); + logfile.append("\\i2pd.log"); #endif - StartLog (logfile_path); - } else { - StartLog (""); // write to stdout } - std::string loglevel; i2p::config::GetOption("loglevel", loglevel); + StartLog (logfile); g_Log->SetLogLevel(loglevel); } From 8baf7f3f6abe98016be0af89601f25f846e6c9b9 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 1 Feb 2016 00:00:00 +0000 Subject: [PATCH 0866/6300] * temporary remove short options : conflicts with remapping --- Config.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Config.cpp b/Config.cpp index a29e4b02..536277b4 100644 --- a/Config.cpp +++ b/Config.cpp @@ -106,16 +106,16 @@ namespace config { void Init() { options_description general("General options"); general.add_options() - ("help,h", "Show this message") - ("conf,c", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf)") + ("help", "Show this message") + ("conf", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf)") ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg)") ("pidfile", value()->default_value(""), "Write pidfile to given path") ("log", value()->zero_tokens(), "Write logs to file instead stdout") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") ("host", value()->default_value(""), "External IP (deprecated)") - ("port,p", value()->default_value(4567), "Port to listen for incoming connections") - ("ipv6,6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") + ("port", value()->default_value(4567), "Port to listen for incoming connections") + ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") ("daemon", value()->zero_tokens()->default_value(false), "Router will go to background after start") ("service", value()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'") ("notransit", value()->zero_tokens()->default_value(false), "Router will not forward transit traffic") From ed44d23afb6f04744a63212e7e27b8ab83d5a8e0 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 1 Feb 2016 00:00:00 +0000 Subject: [PATCH 0867/6300] * update docs/ --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index a48f0f66..e16240ba 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -10,6 +10,7 @@ Command line options * --tunconf= - Tunnels Config file (default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg) * --pidfile= - Where to write pidfile (dont write by default) * --log - Enable or disable logging to file. 1 for yes, 0 for no. +* --logfile= - Path to logfile (stdout if not set, autodetect if daemon) * --loglevel= - Log messages above this level (debug, *info, warn, error) * --host= - The external IP (deprecated) * --port= - The port to listen on From deb87f1d4cbb974969f2bea91ccfb24b57972132 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 1 Feb 2016 00:00:00 +0000 Subject: [PATCH 0868/6300] * for compatibility - leave --log option with arg --- Config.cpp | 4 ++-- Daemon.cpp | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Config.cpp b/Config.cpp index 536277b4..60af0319 100644 --- a/Config.cpp +++ b/Config.cpp @@ -46,7 +46,7 @@ namespace config { }; /* list of options, that loose their argument and become simple switch */ std::set boolean_options = { - "daemon", "log", "floodfill", "notransit", "service", "ipv6" + "daemon", "floodfill", "notransit", "service", "ipv6" }; /* this function is a solid piece of shit, remove it after 2.6.0 */ @@ -110,7 +110,7 @@ namespace config { ("conf", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf)") ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg)") ("pidfile", value()->default_value(""), "Write pidfile to given path") - ("log", value()->zero_tokens(), "Write logs to file instead stdout") + ("log", value()->default_value(""), "Write logs to file instead stdout") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") ("host", value()->default_value(""), "External IP (deprecated)") diff --git a/Daemon.cpp b/Daemon.cpp index a0c3f5bb..ce8ad35c 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -78,7 +78,11 @@ namespace i2p i2p::context.Init (); i2p::config::GetOption("daemon", isDaemon); - i2p::config::GetOption("log", isLogging); + // temporary hack + std::string logs = ""; + i2p::config::GetOption("log", logs); + if (logs != "") + isLogging = true; uint16_t port; i2p::config::GetOption("port", port); LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); From 3da6b3930b4d4e3cd70506b21c237f49f4efe17d Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 1 Feb 2016 00:00:00 +0000 Subject: [PATCH 0869/6300] * I2PControl.cpp : fix handling relative paths for cert/key --- I2PControl.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index f6522339..776cfe86 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -39,17 +39,23 @@ namespace client // certificate / keys std::string i2pcp_crt; i2p::config::GetOption("i2pcontrol.cert", i2pcp_crt); std::string i2pcp_key; i2p::config::GetOption("i2pcontrol.key", i2pcp_key); - // TODO: properly handle absolute paths auto path = GetPath (); - if (!boost::filesystem::exists (path / i2pcp_crt) || - !boost::filesystem::exists (path / i2pcp_key)) + // TODO: move this to i2p::fs::expand + if (i2pcp_crt.at(0) != '/') + i2pcp_crt.insert(0, (path / "/").string()); + if (i2pcp_key.at(0) != '/') + i2pcp_key.insert(0, (path / "/").string()); + if (!boost::filesystem::exists (i2pcp_crt) || + !boost::filesystem::exists (i2pcp_key)) { LogPrint (eLogInfo, "I2PControl: creating new certificate for control connection"); CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str()); + } else { + LogPrint(eLogDebug, "I2PControl: using cert from ", i2pcp_crt); } m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); - m_SSLContext.use_certificate_file ((path / i2pcp_crt).string (), boost::asio::ssl::context::pem); - m_SSLContext.use_private_key_file ((path / i2pcp_crt).string (), boost::asio::ssl::context::pem); + m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem); + m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem); // handlers m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; From 31d716bd0cdb16ae97c4e59988907c3153367539 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 1 Feb 2016 14:19:54 -0500 Subject: [PATCH 0870/6300] fixed race condition --- Transports.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 8d0ad4a6..acffd865 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -499,8 +499,9 @@ namespace transport { m_Service.post([session, this]() { - if (!session->GetRemoteIdentity ()) return; - auto ident = session->GetRemoteIdentity ()->GetIdentHash (); + auto remoteIdentity = session->GetRemoteIdentity (); + if (!remoteIdentity) return; + auto ident = remoteIdentity->GetIdentHash (); auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { @@ -520,8 +521,9 @@ namespace transport { m_Service.post([session, this]() { - if (!session->GetRemoteIdentity ()) return; - auto ident = session->GetRemoteIdentity ()->GetIdentHash (); + auto remoteIdentity = session->GetRemoteIdentity (); + if (!remoteIdentity) return; + auto ident = remoteIdentity->GetIdentHash (); auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { From 8de15c9d0df59f872fa3d63fbf68028c3143ecfa Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 1 Feb 2016 18:10:45 -0500 Subject: [PATCH 0871/6300] fixed bandwidth logic --- Config.cpp | 4 ++-- Daemon.cpp | 40 +++++++++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/Config.cpp b/Config.cpp index 60af0319..60f6b51d 100644 --- a/Config.cpp +++ b/Config.cpp @@ -114,13 +114,13 @@ namespace config { ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") ("host", value()->default_value(""), "External IP (deprecated)") - ("port", value()->default_value(4567), "Port to listen for incoming connections") + ("port", value()->default_value(0), "Port to listen for incoming connections") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") ("daemon", value()->zero_tokens()->default_value(false), "Router will go to background after start") ("service", value()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'") ("notransit", value()->zero_tokens()->default_value(false), "Router will not forward transit traffic") ("floodfill", value()->zero_tokens()->default_value(false), "Router will try to become floodfill") - ("bandwidth", value()->default_value('O'), "Bandwidth limiting: L - 32kbps, O - 256Kbps, P - unlimited (ignored if floodfill)") + ("bandwidth", value()->default_value('-'), "Bandwidth limiting: L - 32kbps, O - 256Kbps, P - unlimited") ; options_description httpserver("HTTP Server options"); diff --git a/Daemon.cpp b/Daemon.cpp index ef74dff3..2fbac28a 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -86,11 +86,15 @@ namespace i2p isLogging = true; uint16_t port; i2p::config::GetOption("port", port); - LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); - i2p::context.UpdatePort (port); + if (port) + { + LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); + i2p::context.UpdatePort (port); + } std::string host; i2p::config::GetOption("host", host); - if (host != "") { + if (host != "") + { LogPrint(eLogInfo, "Daemon: address for incoming connections is ", host); i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); } @@ -103,18 +107,28 @@ namespace i2p bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); char bandwidth; i2p::config::GetOption("bandwidth", bandwidth); - if (isFloodfill) { - LogPrint(eLogInfo, "Daemon: router will be floodfill, bandwidth set to 'extra'"); + if (isFloodfill) + { + LogPrint(eLogInfo, "Daemon: router will be floodfill"); i2p::context.SetFloodfill (true); - i2p::context.SetExtraBandwidth (); - } else if (bandwidth != '-') { - LogPrint(eLogInfo, "Daemon: bandwidth set to ", bandwidth); - switch (bandwidth) { - case 'P' : i2p::context.SetExtraBandwidth (); break; - case 'L' : i2p::context.SetHighBandwidth (); break; - default : i2p::context.SetLowBandwidth (); break; - } } + if (bandwidth != '-') + { + LogPrint(eLogInfo, "Daemon: bandwidth set to ", bandwidth); + if (bandwidth > 'O') + i2p::context.SetExtraBandwidth (); + else if (bandwidth > 'L') + i2p::context.SetHighBandwidth (); + else + i2p::context.SetLowBandwidth (); + } + else if (isFloodfill) + { + LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'"); + i2p::context.SetExtraBandwidth (); + } + else + i2p::context.SetLowBandwidth (); return true; } From 4ced1e5075bf0b984366a176c99f150236ce9c21 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 2 Feb 2016 07:24:14 -0500 Subject: [PATCH 0872/6300] proccess loglevel and logfile correctly --- Daemon.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 2fbac28a..08f93572 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -81,9 +81,8 @@ namespace i2p // temporary hack std::string logs = ""; - i2p::config::GetOption("log", logs); - if (logs != "") - isLogging = true; + i2p::config::GetOption("log", logs); + if (isDaemon || logs != "") isLogging = true; uint16_t port; i2p::config::GetOption("port", port); if (port) @@ -135,18 +134,17 @@ namespace i2p bool Daemon_Singleton::start() { + std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); + if (isLogging) { // set default to stdout std::string logfile = ""; i2p::config::GetOption("logfile", logfile); - std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); - if (isDaemon && logfile == "") { + + if (logfile == "") + { // can't log to stdout, use autodetect of logfile - if (IsService ()) { - logfile = "/var/log"; - } else { - logfile = i2p::util::filesystem::GetDataDir().string(); - } + logfile = IsService () ? "/var/log" : i2p::util::filesystem::GetDataDir().string(); #ifndef _WIN32 logfile.append("/i2pd.log"); #else @@ -154,9 +152,9 @@ namespace i2p #endif } StartLog (logfile); - g_Log->SetLogLevel(loglevel); } - + g_Log->SetLogLevel(loglevel); + bool http; i2p::config::GetOption("http.enabled", http); if (http) { std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); From 64b2a32c9af74ea016dc76294a35647d4066d33d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 2 Feb 2016 11:55:38 -0500 Subject: [PATCH 0873/6300] #343. check for malformed messages --- Identity.cpp | 3 ++- LeaseSet.cpp | 13 ++++++++++++- LeaseSet.h | 3 ++- RouterInfo.cpp | 28 +++++++++++++++++++++------- RouterInfo.h | 2 +- 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index 7c8e38be..ac49b711 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -199,7 +199,7 @@ namespace data } memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE); - delete[] m_ExtendedBuffer; + delete[] m_ExtendedBuffer; m_ExtendedBuffer = nullptr; m_ExtendedLen = bufbe16toh (m_StandardIdentity.certificate + 1); if (m_ExtendedLen) { @@ -211,6 +211,7 @@ namespace data else { LogPrint (eLogError, "Identity: Certificate length ", m_ExtendedLen, " exceeds buffer length ", len - DEFAULT_IDENTITY_SIZE); + m_ExtendedLen = 0; return 0; } } diff --git a/LeaseSet.cpp b/LeaseSet.cpp index fb6ac60b..716ee6d2 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -85,13 +85,24 @@ namespace data if (readIdentity || !m_Identity) m_Identity = std::make_shared(m_Buffer, m_BufferLen); size_t size = m_Identity->GetFullLen (); + if (size > m_BufferLen) + { + LogPrint (eLogError, "LeaseSet: identity length ", size, " exceeds buffer size ", m_BufferLen); + m_IsValid = false; + return; + } memcpy (m_EncryptionKey, m_Buffer + size, 256); size += 256; // encryption key size += m_Identity->GetSigningPublicKeyLen (); // unused signing key uint8_t num = m_Buffer[size]; size++; // num LogPrint (eLogDebug, "LeaseSet: read num=", (int)num); - if (!num) m_IsValid = false; + if (!num || num > MAX_NUM_LEASES) + { + LogPrint (eLogError, "LeaseSet: incorrect number of leases", (int)num); + m_IsValid = false; + return; + } // process leases const uint8_t * leases = m_Buffer + size; diff --git a/LeaseSet.h b/LeaseSet.h index 8135a7a1..aa9b73ad 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -31,7 +31,8 @@ namespace data } }; - const int MAX_LS_BUFFER_SIZE = 3072; + const int MAX_LS_BUFFER_SIZE = 3072; + const uint8_t MAX_NUM_LEASES = 16; class LeaseSet: public RoutingDestination { public: diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 8f2b7bfe..64b675a7 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -81,7 +81,7 @@ namespace data { s.seekg (0,std::ios::end); m_BufferLen = s.tellg (); - if (m_BufferLen < 40) + if (m_BufferLen < 40 || m_BufferLen > MAX_RI_BUFFER_SIZE) { LogPrint(eLogError, "RouterInfo: File", m_FullPath, " is malformed"); return false; @@ -109,13 +109,25 @@ namespace data { m_RouterIdentity = std::make_shared(m_Buffer, m_BufferLen); size_t identityLen = m_RouterIdentity->GetFullLen (); + if (identityLen >= m_BufferLen) + { + LogPrint (eLogError, "RouterInfo: identity length ", identityLen, " exceeds buffer size ", m_BufferLen); + m_IsUnreachable = true; + return; + } std::stringstream str (std::string ((char *)m_Buffer + identityLen, m_BufferLen - identityLen)); ReadFromStream (str); + if (!str) + { + LogPrint (eLogError, "RouterInfo: malformed message"); + m_IsUnreachable = true; + return; + } if (verifySignature) { // verify signature - int l = m_BufferLen - m_RouterIdentity->GetSignatureLen (); - if (!m_RouterIdentity->Verify ((uint8_t *)m_Buffer, l, (uint8_t *)m_Buffer + l)) + int l = m_BufferLen - m_RouterIdentity->GetSignatureLen (); + if (l < 0 || !m_RouterIdentity->Verify ((uint8_t *)m_Buffer, l, (uint8_t *)m_Buffer + l)) { LogPrint (eLogError, "RouterInfo: signature verification failed"); m_IsUnreachable = true; @@ -130,7 +142,7 @@ namespace data m_Timestamp = be64toh (m_Timestamp); // read addresses uint8_t numAddresses; - s.read ((char *)&numAddresses, sizeof (numAddresses)); + s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return; bool introducers = false; for (int i = 0; i < numAddresses; i++) { @@ -149,7 +161,7 @@ namespace data address.port = 0; address.mtu = 0; uint16_t size, r = 0; - s.read ((char *)&size, sizeof (size)); + s.read ((char *)&size, sizeof (size)); if (!s) return; size = be16toh (size); while (r < size) { @@ -214,17 +226,18 @@ namespace data else if (!strcmp (key, "ikey")) Base64ToByteStream (value, strlen (value), introducer.iKey, 32); } + if (!s) return; } if (isValidAddress) m_Addresses.push_back(address); } // read peers uint8_t numPeers; - s.read ((char *)&numPeers, sizeof (numPeers)); + s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return; s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers // read properties uint16_t size, r = 0; - s.read ((char *)&size, sizeof (size)); + s.read ((char *)&size, sizeof (size)); if (!s) return; size = be16toh (size); while (r < size) { @@ -250,6 +263,7 @@ namespace data LogPrint (eLogError, "Unexpected netid=", value); m_IsUnreachable = true; } + if (!s) return; } if (!m_SupportedTransports || !m_Addresses.size() || (UsesIntroducer () && !introducers)) diff --git a/RouterInfo.h b/RouterInfo.h index a750cfea..2d32edef 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -182,7 +182,7 @@ namespace data std::string m_FullPath; std::shared_ptr m_RouterIdentity; uint8_t * m_Buffer; - int m_BufferLen; + size_t m_BufferLen; uint64_t m_Timestamp; std::vector
m_Addresses; std::map m_Properties; From 3eeee1b08d31f3f3c2064d89f9b2f46628644a5a Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 2 Feb 2016 12:16:29 -0500 Subject: [PATCH 0874/6300] set correct log level for console --- Daemon.cpp | 4 +++- Log.h | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Daemon.cpp b/Daemon.cpp index 08f93572..7da64883 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -153,7 +153,9 @@ namespace i2p } StartLog (logfile); } - g_Log->SetLogLevel(loglevel); + else + StartLog (""); + SetLogLevel(loglevel); bool http; i2p::config::GetOption("http.enabled", http); if (http) { diff --git a/Log.h b/Log.h index c4b0f276..f941e6a7 100644 --- a/Log.h +++ b/Log.h @@ -95,6 +95,12 @@ inline void StopLog () } } +inline void SetLogLevel (const std::string& level) +{ + if (g_Log) + g_Log->SetLogLevel(level); +} + template void LogPrint (std::stringstream& s, TValue arg) { From 7274d436451f60d4be6925bdcf1ba7ab5cd803bd Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 2 Feb 2016 18:27:52 -0500 Subject: [PATCH 0875/6300] fixed incorrect long fragment size --- SSUData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SSUData.cpp b/SSUData.cpp index f1282c3d..0ccf25a8 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -157,7 +157,7 @@ namespace transport memcpy (frag + 1, buf, 3); buf += 3; uint32_t fragmentInfo = bufbe32toh (frag); // fragment info - uint16_t fragmentSize = fragmentInfo & 0x1FFF; // bits 0 - 13 + uint16_t fragmentSize = fragmentInfo & 0x3FFF; // bits 0 - 13 bool isLast = fragmentInfo & 0x010000; // bit 16 uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17 if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE) From 77d8bae2c237ecc6c2a963df63e2f8f110a2bae3 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 2 Feb 2016 19:24:49 -0500 Subject: [PATCH 0876/6300] fixed server http tunnel header --- I2PTunnel.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 9eccc1bd..88c84987 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -134,7 +134,7 @@ namespace client void I2PTunnelConnection::Write (const uint8_t * buf, size_t len) { - m_Socket->async_send (boost::asio::buffer (buf, len), + boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (), std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); } @@ -184,11 +184,14 @@ namespace client std::getline(m_InHeader, line); if (!m_InHeader.fail ()) { - if (line.find ("Host:") != std::string::npos) - m_OutHeader << "Host: " << m_Host << "\r\n"; - else - m_OutHeader << line << "\n"; if (line == "\r") endOfHeader = true; + else + { + if (line.find ("Host:") != std::string::npos) + m_OutHeader << "Host: " << m_Host << "\r\n"; + else + m_OutHeader << line << "\n"; + } } else break; @@ -203,6 +206,7 @@ namespace client if (endOfHeader) { + m_OutHeader << "\r\n"; // end of header m_OutHeader << m_InHeader.str (); // data right after header m_HeaderSent = true; I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); From b1cc1db967fb4da5c68cca890d219e662fcb0767 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 2 Feb 2016 22:00:51 -0500 Subject: [PATCH 0877/6300] fixed POST for server http tunnel --- I2PTunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 88c84987..e936834d 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -207,7 +207,7 @@ namespace client if (endOfHeader) { m_OutHeader << "\r\n"; // end of header - m_OutHeader << m_InHeader.str (); // data right after header + m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header m_HeaderSent = true; I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); } From 10fd8eb7093eb7927a0753a35b8405908d7827cd Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 3 Feb 2016 11:22:00 +0000 Subject: [PATCH 0878/6300] * Daemon.cpp : move ParseCmdline() before use of i2p::fs -- allow redefined paths --- Daemon.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 7da64883..54ebbea8 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -60,6 +60,9 @@ namespace i2p bool Daemon_Singleton::init(int argc, char* argv[]) { + i2p::config::Init(); + i2p::config::ParseCmdline(argc, argv); + std::string config = i2p::util::filesystem::GetConfigFile().string(); std::string tunconf = i2p::util::filesystem::GetTunnelsConfigFile().string(); std::string datadir = i2p::util::filesystem::GetDataDir().string(); @@ -69,8 +72,6 @@ namespace i2p LogPrint(eLogDebug, "FS: tunnels config: ", tunconf); LogPrint(eLogDebug, "FS: data directory: ", datadir); - i2p::config::Init(); - i2p::config::ParseCmdline(argc, argv); i2p::config::ParseConfig(config); i2p::config::Finalize(); From 4bb4012d87cbd6a1a48ba18be37965d95455f803 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 3 Feb 2016 11:22:00 +0000 Subject: [PATCH 0879/6300] * Daemon.cpp : move logs init to single place --- Daemon.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 54ebbea8..3d0f67c4 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -67,11 +67,6 @@ namespace i2p std::string tunconf = i2p::util::filesystem::GetTunnelsConfigFile().string(); std::string datadir = i2p::util::filesystem::GetDataDir().string(); - LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); - LogPrint(eLogDebug, "FS: main config file: ", config); - LogPrint(eLogDebug, "FS: tunnels config: ", tunconf); - LogPrint(eLogDebug, "FS: data directory: ", datadir); - i2p::config::ParseConfig(config); i2p::config::Finalize(); @@ -80,10 +75,12 @@ namespace i2p i2p::config::GetOption("daemon", isDaemon); - // temporary hack - std::string logs = ""; - i2p::config::GetOption("log", logs); - if (isDaemon || logs != "") isLogging = true; + // TODO: move log init here + + LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); + LogPrint(eLogDebug, "FS: main config file: ", config); + LogPrint(eLogDebug, "FS: tunnels config: ", tunconf); + LogPrint(eLogDebug, "FS: data directory: ", datadir); uint16_t port; i2p::config::GetOption("port", port); if (port) @@ -135,16 +132,18 @@ namespace i2p bool Daemon_Singleton::start() { + std::string logs = ""; i2p::config::GetOption("log", logs); + std::string logfile = ""; i2p::config::GetOption("logfile", logfile); std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); + // temporary hack + if (isDaemon || logs != "") isLogging = true; + if (isLogging) { - // set default to stdout - std::string logfile = ""; i2p::config::GetOption("logfile", logfile); - if (logfile == "") { - // can't log to stdout, use autodetect of logfile + // use autodetect of logfile logfile = IsService () ? "/var/log" : i2p::util::filesystem::GetDataDir().string(); #ifndef _WIN32 logfile.append("/i2pd.log"); @@ -153,9 +152,10 @@ namespace i2p #endif } StartLog (logfile); - } - else + } else { + // use stdout StartLog (""); + } SetLogLevel(loglevel); bool http; i2p::config::GetOption("http.enabled", http); From 21ecf309bba81e0f537b8abed3d6e992c591f3f3 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 3 Feb 2016 11:22:00 +0000 Subject: [PATCH 0880/6300] * Daemon.cpp : --log option now uses descriptive values: file, stdout (#356) --- Daemon.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 3d0f67c4..3a44b187 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -135,11 +135,11 @@ namespace i2p std::string logs = ""; i2p::config::GetOption("log", logs); std::string logfile = ""; i2p::config::GetOption("logfile", logfile); std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); - - // temporary hack - if (isDaemon || logs != "") isLogging = true; - if (isLogging) + if (isDaemon && (logs == "" || logs == "stdout")) + logs = "file"; + + if (logs == "file") { if (logfile == "") { From d98dd83369fed02c55cd82ab8127cfb8bc63ad6a Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 3 Feb 2016 11:22:00 +0000 Subject: [PATCH 0881/6300] * sync actial options and docs (#356) --- Config.cpp | 15 +++++++++------ docs/configuration.md | 21 ++++++++++----------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Config.cpp b/Config.cpp index 60f6b51d..337477a0 100644 --- a/Config.cpp +++ b/Config.cpp @@ -109,18 +109,21 @@ namespace config { ("help", "Show this message") ("conf", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf)") ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg)") - ("pidfile", value()->default_value(""), "Write pidfile to given path") - ("log", value()->default_value(""), "Write logs to file instead stdout") + ("pidfile", value()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)") + ("log", value()->default_value(""), "Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") - ("host", value()->default_value(""), "External IP (deprecated)") - ("port", value()->default_value(0), "Port to listen for incoming connections") + ("host", value()->default_value(""), "External IP") + ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") ("daemon", value()->zero_tokens()->default_value(false), "Router will go to background after start") ("service", value()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'") - ("notransit", value()->zero_tokens()->default_value(false), "Router will not forward transit traffic") - ("floodfill", value()->zero_tokens()->default_value(false), "Router will try to become floodfill") + ("notransit", value()->zero_tokens()->default_value(false), "Router will not accept transit tunnels at startup") + ("floodfill", value()->zero_tokens()->default_value(false), "Router will be floodfill") ("bandwidth", value()->default_value('-'), "Bandwidth limiting: L - 32kbps, O - 256Kbps, P - unlimited") +#ifdef _WIN32 + ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") +#endif ; options_description httpserver("HTTP Server options"); diff --git a/docs/configuration.md b/docs/configuration.md index 56d57bc1..3f3c5386 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -7,21 +7,20 @@ Command line options * --conf= - Config file (default: ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf) This parameter will be silently ignored if the specified config file does not exist. Options specified on the command line take precedence over those in the config file. -* --tunconf= - Tunnels Config file (default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg) +* --tunconf= - Tunnels config file (default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg) * --pidfile= - Where to write pidfile (dont write by default) -* --log - Enable or disable logging to file. 1 for yes, 0 for no. -* --logfile= - Path to logfile (stdout if not set, autodetect if daemon) +* --log= - Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility) +* --logfile= - Path to logfile (default - autodetect) * --loglevel= - Log messages above this level (debug, *info, warn, error) -* --host= - The external IP (deprecated) +* --host= - The external IP * --port= - The port to listen on -* --daemon - Enable or disable daemon mode. 1 for yes, 0 for no. -* --svcctl= - Windows service management (--svcctl="install" or --svcctl="remove") -* --service - Use system folders (/var/run/i2pd.pid, /var/log/i2pd.log, /var/lib/i2pd). -* --ipv6 - Enable communication through ipv6, off by default -* --notransit - Router will not accept transit tunnels at startup. 0 by default -* --floodfill - Router will be floodfill, off by default +* --daemon - Router will go to background after start +* --service - Router will use system folders like '/var/lib/i2pd' +* --ipv6 - Enable communication through ipv6 +* --notransit - Router will not accept transit tunnels at startup +* --floodfill - Router will be floodfill * --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited - This option will be ignored if --floodfill given +* --svcctl= - Windows service management (--svcctl="install" or --svcctl="remove") * --http.address= - The address to listen on (HTTP server) * --http.port= - The port to listen on (HTTP server) From bf3c4bc588e2dac0d072b59179d36c6f3d5ea4e5 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 3 Feb 2016 11:22:00 +0000 Subject: [PATCH 0882/6300] * bump version --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index a0fff346..49b52b96 100644 --- a/version.h +++ b/version.h @@ -7,7 +7,7 @@ #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 3 +#define I2PD_VERSION_MINOR 4 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) From 0c442622afdcb786f23470f1f54d5d1eb52761e7 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 3 Feb 2016 11:25:00 +0000 Subject: [PATCH 0883/6300] * chg default for --host= option : was broken in 900fc1c --- Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config.cpp b/Config.cpp index 337477a0..67a61a98 100644 --- a/Config.cpp +++ b/Config.cpp @@ -113,7 +113,7 @@ namespace config { ("log", value()->default_value(""), "Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") - ("host", value()->default_value(""), "External IP") + ("host", value()->default_value("0.0.0.0"), "External IP") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") ("daemon", value()->zero_tokens()->default_value(false), "Router will go to background after start") From 62cec2a31c43ade361bb55e63fcb594c572cc95c Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 3 Feb 2016 11:30:00 +0000 Subject: [PATCH 0884/6300] * correct shutdown of httpserver & socksproxy --- ClientContext.cpp | 20 ++++++++++++-------- Daemon.cpp | 8 +++++--- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 53c442f0..6d05cde1 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -100,15 +100,19 @@ namespace client void ClientContext::Stop () { - LogPrint(eLogInfo, "Clients: stopping HTTP Proxy"); - m_HttpProxy->Stop(); - delete m_HttpProxy; - m_HttpProxy = nullptr; + if (m_HttpProxy) { + LogPrint(eLogInfo, "Clients: stopping HTTP Proxy"); + m_HttpProxy->Stop(); + delete m_HttpProxy; + m_HttpProxy = nullptr; + } - LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy"); - m_SocksProxy->Stop(); - delete m_SocksProxy; - m_SocksProxy = nullptr; + if (m_SocksProxy) { + LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy"); + m_SocksProxy->Stop(); + delete m_SocksProxy; + m_SocksProxy = nullptr; + } for (auto& it: m_ClientTunnels) { diff --git a/Daemon.cpp b/Daemon.cpp index 3a44b187..9887ee30 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -210,9 +210,11 @@ namespace i2p i2p::transport::transports.Stop(); LogPrint(eLogInfo, "Daemon: stopping NetDB"); i2p::data::netdb.Stop(); - LogPrint(eLogInfo, "Daemon: stopping HTTP Server"); - d.httpServer->Stop(); - d.httpServer = nullptr; + if (d.httpServer) { + LogPrint(eLogInfo, "Daemon: stopping HTTP Server"); + d.httpServer->Stop(); + d.httpServer = nullptr; + } if (d.m_I2PControlService) { LogPrint(eLogInfo, "Daemon: stopping I2PControl"); From 72b3c10ebdb10872554c201dea6775db36f23fdd Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 3 Feb 2016 14:21:22 +0000 Subject: [PATCH 0885/6300] * fix updating address in RI --- Daemon.cpp | 4 ++-- RouterContext.cpp | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 9887ee30..b9d8bd44 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -90,9 +90,9 @@ namespace i2p } std::string host; i2p::config::GetOption("host", host); - if (host != "") + if (host != "0.0.0.0") { - LogPrint(eLogInfo, "Daemon: address for incoming connections is ", host); + LogPrint(eLogInfo, "Daemon: setting address for incoming connections to ", host); i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); } diff --git a/RouterContext.cpp b/RouterContext.cpp index 7b793d64..962cf3ee 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -48,6 +48,8 @@ namespace i2p if (!port) port = rand () % (30777 - 9111) + 9111; // I2P network ports range std::string host; i2p::config::GetOption("host", host); + if (host == "0.0.0.0") + host = "127.0.0.1"; // replace default address with safe value routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); routerInfo.AddNTCPAddress (host.c_str(), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | From 4386bd93c301cfff49cce37f980c359ca1cd111b Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 3 Feb 2016 12:48:59 -0500 Subject: [PATCH 0886/6300] handle USE_AESNI for mingw --- Makefile.mingw | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile.mingw b/Makefile.mingw index 18f04c3f..4c1d5d80 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -2,6 +2,10 @@ CXX = g++ CXXFLAGS = -O2 -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN NEEDED_CXXFLAGS = -std=c++11 BOOST_SUFFIX = -mt -INCFLAGS = -I/usr/include/ -I/usr/local/include/ -I/c/dev/openssl/include -I/c/dev/boost/include/boost-1_59 +INCFLAGS = -I/usr/include/ -I/usr/local/include/ LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/c/dev/openssl -L/c/dev/boost/lib LDLIBS = -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) -Wl,-Bstatic -lssl -Wl,-Bstatic -lcrypto -Wl,-Bstatic -lz -Wl,-Bstatic -lwsock32 -Wl,-Bstatic -lws2_32 -Wl,-Bstatic -lgdi32 -Wl,-Bstatic -liphlpapi -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -Wl,-Bstatic -lpthread + +ifeq ($(USE_AESNI),yes) + CPU_FLAGS = -maes -DAESNI +endif From 6782e6a532aebaf96358b1b5424d5610c8a493f8 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 3 Feb 2016 13:46:26 -0500 Subject: [PATCH 0887/6300] AES-NI --- docs/build_notes_windows.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index 0d3a39bb..1384f268 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -39,8 +39,8 @@ make If your processor has [AES instruction set](https://en.wikipedia.org/wiki/AES_instruction_set), -you may try adding `-DWITH_AESNI=ON`. No check is done however, it -will compile but will crash with `Illegal instruction` if not supported. +you use `make USE_AESNI=yes`. No check is done however, it +will compile, but it might crash with `Illegal instruction` if not supported. You should be able to run ./i2pd . If you need to start from the new shell, consider starting *MinGW-w64 Win32 Shell* instead of *MSYS2 Shell* as From 8c401cf01b233a0fae9504adf02e8576b6eedf8d Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 3 Feb 2016 15:00:55 -0500 Subject: [PATCH 0888/6300] check for USE_AESNI=1 --- Makefile.mingw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.mingw b/Makefile.mingw index 4c1d5d80..591a1256 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -6,6 +6,6 @@ INCFLAGS = -I/usr/include/ -I/usr/local/include/ LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/c/dev/openssl -L/c/dev/boost/lib LDLIBS = -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) -Wl,-Bstatic -lssl -Wl,-Bstatic -lcrypto -Wl,-Bstatic -lz -Wl,-Bstatic -lwsock32 -Wl,-Bstatic -lws2_32 -Wl,-Bstatic -lgdi32 -Wl,-Bstatic -liphlpapi -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -Wl,-Bstatic -lpthread -ifeq ($(USE_AESNI),yes) +ifeq ($(USE_AESNI),1) CPU_FLAGS = -maes -DAESNI endif From 0e7596a2055cdc8cb6a2fe9a7232aba682bc3c79 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 3 Feb 2016 15:01:28 -0500 Subject: [PATCH 0889/6300] Update build_notes_windows.md --- docs/build_notes_windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index 1384f268..c72b8989 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -39,7 +39,7 @@ make If your processor has [AES instruction set](https://en.wikipedia.org/wiki/AES_instruction_set), -you use `make USE_AESNI=yes`. No check is done however, it +you use `make USE_AESNI=1`. No check is done however, it will compile, but it might crash with `Illegal instruction` if not supported. You should be able to run ./i2pd . If you need to start from the new From b9b143e4e79d55f222529fc378326d115f56403e Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 3 Feb 2016 15:29:15 -0500 Subject: [PATCH 0890/6300] don't persist proxy keys by defualt --- Config.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Config.cpp b/Config.cpp index 67a61a98..8393375f 100644 --- a/Config.cpp +++ b/Config.cpp @@ -113,7 +113,7 @@ namespace config { ("log", value()->default_value(""), "Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") - ("host", value()->default_value("0.0.0.0"), "External IP") + ("host", value()->default_value(""), "External IP") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") ("daemon", value()->zero_tokens()->default_value(false), "Router will go to background after start") @@ -138,7 +138,7 @@ namespace config { ("httpproxy.enabled", value()->default_value(true), "Enable or disable HTTP Proxy") ("httpproxy.address", value()->default_value("127.0.0.1"), "HTTP Proxy listen address") ("httpproxy.port", value()->default_value(4444), "HTTP Proxy listen port") - ("httpproxy.keys", value()->default_value("httpproxy-keys.dat"), "HTTP Proxy encryption keys") + ("httpproxy.keys", value()->default_value(""), "File to persist HTTP Proxy keys") ; options_description socksproxy("SOCKS Proxy options"); @@ -146,7 +146,7 @@ namespace config { ("socksproxy.enabled", value()->default_value(true), "Enable or disable SOCKS Proxy") ("socksproxy.address", value()->default_value("127.0.0.1"), "SOCKS Proxy listen address") ("socksproxy.port", value()->default_value(4447), "SOCKS Proxy listen port") - ("socksproxy.keys", value()->default_value("socksproxy-keys.dat"), "SOCKS Proxy encryption keys") + ("socksproxy.keys", value()->default_value(""), "File to persist SOCKS Proxy keys") ; options_description sam("SAM bridge options"); From 51f7aba807691b0e487d088f38c4ccf959357331 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 3 Feb 2016 16:18:49 -0500 Subject: [PATCH 0891/6300] fixed crash --- Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config.cpp b/Config.cpp index 8393375f..d1bf7b64 100644 --- a/Config.cpp +++ b/Config.cpp @@ -113,7 +113,7 @@ namespace config { ("log", value()->default_value(""), "Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") - ("host", value()->default_value(""), "External IP") + ("host", value()->default_value("0.0.0.0"), "External IP") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") ("daemon", value()->zero_tokens()->default_value(false), "Router will go to background after start") From 4978edb8be736515e3b0fcc15e3e259982d4e79f Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Thu, 4 Feb 2016 16:49:07 +0300 Subject: [PATCH 0892/6300] Always place local include directories before all others --- build/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 71ce57ad..8e3f8ed1 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -210,7 +210,7 @@ else() endif () if (WITH_PCH) - include_directories(${CMAKE_BINARY_DIR}) + include_directories(BEFORE ${CMAKE_BINARY_DIR}) add_library(stdafx STATIC "${CMAKE_SOURCE_DIR}/stdafx.cpp") if(MSVC) target_compile_options(stdafx PRIVATE /Ycstdafx.h /Zm155) From 61d1b733f795cda35f3ace908d6d8c9c51a77ec5 Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Thu, 4 Feb 2016 16:51:47 +0300 Subject: [PATCH 0893/6300] Include system directories as SYSTEM --- build/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 71ce57ad..2ae1ef00 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -290,7 +290,7 @@ endif () link_directories(${CMAKE_CURRENT_BINARY_DIR}/zlib/lib ${ZLIB_ROOT}/lib) # load includes -include_directories( ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ) +include_directories( SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ) # show summary message(STATUS "---------------------------------------") From 2115ce66064acc6a45ecae5184156fbf064bfafc Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Thu, 4 Feb 2016 16:50:33 +0300 Subject: [PATCH 0894/6300] Do not try to use miniupnp if upnp support is disabled --- build/CMakeLists.txt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 71ce57ad..09d678e6 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -247,11 +247,9 @@ if(NOT DEFINED OPENSSL_INCLUDE_DIR) message(SEND_ERROR "Could not find OpenSSL. Please download and install it first!") endif() -find_package ( MiniUPnPc ) -if (MINIUPNPC_FOUND) - include_directories( ${MINIUPNPC_INCLUDE_DIR} ) -else () - set(WITH_UPNP OFF) +if (WITH_UPNP) + find_package ( MiniUPnPc REQUIRED ) + include_directories( SYSTEM ${MINIUPNPC_INCLUDE_DIR} ) endif() find_package ( ZLIB ) From 4ef183fee6f56ec76f891188c740e0bfc3888ac3 Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Thu, 4 Feb 2016 16:53:24 +0300 Subject: [PATCH 0895/6300] Do not force build type --- build/CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 71ce57ad..955029e7 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -91,11 +91,6 @@ if (WITH_UPNP) endif () endif () -# Default build is Debug -if (NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug) -endif () - # compiler flags customization (by vendor) if (MSVC) add_definitions( -D_WIN32_WINNT=_WIN32_WINNT_WINXP -DWIN32_LEAN_AND_MEAN -DNOMINMAX ) #-DOPENSSL_NO_SSL2 -DOPENSSL_USE_DEPRECATED From 2e7ce385525cae267693d4b0c6efca989b8aaeee Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 4 Feb 2016 12:36:54 -0500 Subject: [PATCH 0896/6300] compatibility with gcc 4.6 --- Garlic.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Garlic.h b/Garlic.h index 67e9b027..73231d10 100644 --- a/Garlic.h +++ b/Garlic.h @@ -119,7 +119,8 @@ namespace garlic // for HTTP only size_t GetNumOutgoingTags () const { return m_SessionTags.size (); }; }; - using GarlicRoutingSessionPtr = std::shared_ptr; + //using GarlicRoutingSessionPtr = std::shared_ptr; + typedef std::shared_ptr GarlicRoutingSessionPtr; // TODO: replace to using after switch to 4.8 class GarlicDestination: public i2p::data::LocalDestination { From 7ca1cfab1a3b1731dcda9c10c2e1aba941bdff96 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 4 Feb 2016 12:36:58 -0500 Subject: [PATCH 0897/6300] use shared_ptr for log's stream --- Log.cpp | 12 +++++------- Log.h | 13 +++++++------ api.cpp | 2 +- api.h | 2 +- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Log.cpp b/Log.cpp index 837abac8..d507b7dc 100644 --- a/Log.cpp +++ b/Log.cpp @@ -13,7 +13,8 @@ static const char * g_LogLevelStr[eNumLogLevels] = void LogMsg::Process() { - auto& output = (log && log->GetLogStream ()) ? *log->GetLogStream () : std::cerr; + auto stream = log ? log->GetLogStream () : nullptr; + auto& output = stream ? *stream : std::cout; if (log) output << log->GetTimestamp (); else @@ -45,14 +46,12 @@ void Log::Flush () void Log::SetLogFile (const std::string& fullFilePath) { - auto logFile = new std::ofstream (fullFilePath, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); + auto logFile = std::make_shared (fullFilePath, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); if (logFile->is_open ()) { SetLogStream (logFile); LogPrint(eLogInfo, "Log: will send messages to ", fullFilePath); } - else - delete logFile; } void Log::SetLogLevel (const std::string& level) @@ -65,11 +64,10 @@ void Log::SetLogLevel (const std::string& level) LogPrint(eLogError, "Log: Unknown loglevel: ", level); return; } - LogPrint(eLogInfo, "Log: min messages level set to ", level); + LogPrint(eLogInfo, "Log: min msg level set to ", level); } -void Log::SetLogStream (std::ostream * logStream) +void Log::SetLogStream (std::shared_ptr logStream) { - if (m_LogStream) delete m_LogStream; m_LogStream = logStream; } diff --git a/Log.h b/Log.h index f941e6a7..ec1dc015 100644 --- a/Log.h +++ b/Log.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "Queue.h" enum LogLevel @@ -34,13 +35,13 @@ class Log: public i2p::util::MsgQueue { public: - Log (): m_LogStream (nullptr) { SetOnEmpty (std::bind (&Log::Flush, this)); }; - ~Log () { delete m_LogStream; }; + Log () { SetOnEmpty (std::bind (&Log::Flush, this)); }; + ~Log () {}; void SetLogFile (const std::string& fullFilePath); void SetLogLevel (const std::string& level); - void SetLogStream (std::ostream * logStream); - std::ostream * GetLogStream () const { return m_LogStream; }; + void SetLogStream (std::shared_ptr logStream); + std::shared_ptr GetLogStream () const { return m_LogStream; }; const std::string& GetTimestamp (); LogLevel GetLogLevel () { return m_MinLevel; }; @@ -50,7 +51,7 @@ class Log: public i2p::util::MsgQueue private: - std::ostream * m_LogStream; + std::shared_ptr m_LogStream; enum LogLevel m_MinLevel; std::string m_Timestamp; #if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) && !defined(__clang__) // gcc 4.6 @@ -73,7 +74,7 @@ inline void StartLog (const std::string& fullFilePath) } } -inline void StartLog (std::ostream * s) +inline void StartLog (std::shared_ptr s) { if (!g_Log) { diff --git a/api.cpp b/api.cpp index cd527550..0f4e1799 100644 --- a/api.cpp +++ b/api.cpp @@ -31,7 +31,7 @@ namespace api i2p::crypto::TerminateCrypto (); } - void StartI2P (std::ostream * logStream) + void StartI2P (std::shared_ptr logStream) { if (logStream) StartLog (logStream); diff --git a/api.h b/api.h index 05552249..c010d82f 100644 --- a/api.h +++ b/api.h @@ -14,7 +14,7 @@ namespace api // initialization start and stop void InitI2P (int argc, char* argv[], const char * appName); void TerminateI2P (); - void StartI2P (std::ostream * logStream = nullptr); + void StartI2P (std::shared_ptr logStream = nullptr); // write system log to logStream, if not specified to .log in application's folder void StopI2P (); void RunPeerTest (); // should be called after UPnP From 98d5e0b56d321b72340212564a662aa2e6d6a06e Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 4 Feb 2016 13:53:38 -0500 Subject: [PATCH 0898/6300] #355. reopen log file by SIGHUP --- DaemonLinux.cpp | 6 +++--- Log.cpp | 11 +++++++++++ Log.h | 8 ++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 89df5306..9382a878 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -17,9 +17,9 @@ void handle_signal(int sig) switch (sig) { case SIGHUP: - LogPrint(eLogInfo, "Daemon: Got SIGHUP, doing nothing"); - // TODO: - break; + LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening log..."); + ReopenLogFile (); + break; case SIGABRT: case SIGTERM: case SIGINT: diff --git a/Log.cpp b/Log.cpp index d507b7dc..6391bcae 100644 --- a/Log.cpp +++ b/Log.cpp @@ -46,6 +46,7 @@ void Log::Flush () void Log::SetLogFile (const std::string& fullFilePath) { + m_FullFilePath = fullFilePath; auto logFile = std::make_shared (fullFilePath, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); if (logFile->is_open ()) { @@ -54,6 +55,16 @@ void Log::SetLogFile (const std::string& fullFilePath) } } +void Log::ReopenLogFile () +{ + if (m_FullFilePath.length () > 0) + { + SetLogFile (m_FullFilePath); + LogPrint(eLogInfo, "Log: file ", m_FullFilePath, " reopen"); + } +} + + void Log::SetLogLevel (const std::string& level) { if (level == "error") { m_MinLevel = eLogError; } diff --git a/Log.h b/Log.h index ec1dc015..05bae7d9 100644 --- a/Log.h +++ b/Log.h @@ -39,6 +39,7 @@ class Log: public i2p::util::MsgQueue ~Log () {}; void SetLogFile (const std::string& fullFilePath); + void ReopenLogFile (); void SetLogLevel (const std::string& level); void SetLogStream (std::shared_ptr logStream); std::shared_ptr GetLogStream () const { return m_LogStream; }; @@ -51,6 +52,7 @@ class Log: public i2p::util::MsgQueue private: + std::string m_FullFilePath; // empty if stream std::shared_ptr m_LogStream; enum LogLevel m_MinLevel; std::string m_Timestamp; @@ -102,6 +104,12 @@ inline void SetLogLevel (const std::string& level) g_Log->SetLogLevel(level); } +inline void ReopenLogFile () +{ + if (g_Log) + g_Log->ReopenLogFile (); +} + template void LogPrint (std::stringstream& s, TValue arg) { From a292bc77ba3bd3d649c4faf3a03fa7902555627a Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 5 Feb 2016 07:55:28 -0500 Subject: [PATCH 0899/6300] fix issue #362 , add bounds check to su3 fileNameLength --- Reseed.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Reseed.cpp b/Reseed.cpp index 6d7c7901..80cc6a45 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -208,6 +208,11 @@ namespace data uint16_t fileNameLength, extraFieldLength; s.read ((char *)&fileNameLength, 2); fileNameLength = le16toh (fileNameLength); + if ( fileNameLength > 255 ) { + // too big + LogPrint(eLogError, "Reseed: SU3 fileNameLength too large: ", fileNameLength); + return numFiles; + } s.read ((char *)&extraFieldLength, 2); extraFieldLength = le16toh (extraFieldLength); char localFileName[255]; From d0ea59c5682e9d3bb0d78015998ed732d9ce7a35 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 5 Feb 2016 08:44:09 -0500 Subject: [PATCH 0900/6300] add base64 buffer encoding bounds checking --- Base.h | 5 +++++ Identity.cpp | 25 +++++++++++++++++-------- SAM.cpp | 11 ++++++++--- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Base.h b/Base.h index 0c59ef03..d598542b 100644 --- a/Base.h +++ b/Base.h @@ -17,6 +17,11 @@ namespace data size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen); size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen); + /** + Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes + */ + size_t Base64EncodingBufferSize(const size_t input_size); + template class Tag { diff --git a/Identity.cpp b/Identity.cpp index ac49b711..648c996d 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -228,26 +228,35 @@ namespace data } size_t IdentityEx::ToBuffer (uint8_t * buf, size_t len) const - { + { + size_t fullLen = GetFullLen(); + if (fullLen > len) { + // buffer is too small and may overflow somewhere else + return 0; + } memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE); if (m_ExtendedLen > 0 && m_ExtendedBuffer) memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen); - return GetFullLen (); + return fullLen; } size_t IdentityEx::FromBase64(const std::string& s) { - uint8_t buf[1024]; - auto len = Base64ToByteStream (s.c_str(), s.length(), buf, 1024); + const size_t slen = s.length(); + const size_t bufLen = Base64EncodingBufferSize(slen); + uint8_t buf[bufLen]; + auto len = Base64ToByteStream (s.c_str(), slen, buf, 1024); return FromBuffer (buf, len); } std::string IdentityEx::ToBase64 () const { - uint8_t buf[1024]; - char str[1536]; - size_t l = ToBuffer (buf, 1024); - size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, str, 1536); + const size_t bufLen = GetFullLen(); + const size_t strLen = Base64EncodingBufferSize(bufLen); + uint8_t buf[bufLen]; + char str[strLen]; + size_t l = ToBuffer (buf, bufLen); + size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, str, strLen); str[l1] = 0; return std::string (str); } diff --git a/SAM.cpp b/SAM.cpp index a20de13d..d331a035 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -631,10 +631,15 @@ namespace client m_SocketType = eSAMSocketTypeStream; if (!m_IsSilent) { - // send remote peer address - uint8_t ident[1024]; - size_t l = stream->GetRemoteIdentity ()->ToBuffer (ident, 1024); + // get remote peer address + auto ident_ptr = stream->GetRemoteIdentity(); + size_t ident_len = ident_ptr->GetFullLen(); + uint8_t* ident = new uint8_t[ident_len]; + + // send remote peer address as base64 + size_t l = ident_ptr->ToBuffer (ident, ident_len); size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE); + delete[] ident; m_StreamBuffer[l1] = '\n'; HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream } From 21090eaa39aefebc7214b39b2aa19d69dca54e39 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 5 Feb 2016 08:46:08 -0500 Subject: [PATCH 0901/6300] forgot to commit Base.cpp changes --- Base.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Base.cpp b/Base.cpp index c9ba407a..f5144eda 100644 --- a/Base.cpp +++ b/Base.cpp @@ -327,7 +327,14 @@ namespace data LogPrint (eLogError, "Compression error ", err); return 0; } - } + } + + // from https://stackoverflow.com/questions/1533113/calculate-the-size-to-a-base-64-encoded-message + size_t Base64EncodingBufferSize(const size_t input_size) { + const size_t code_size = ((input_size * 4) / 3); + const size_t padding_size = (input_size % 3) ? (3 - (input_size % 3)) : 0; + return code_size + padding_size; + } } } From d4febb4e84fd1c45fa5f7dc95ddeaa566eb893ce Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 5 Feb 2016 08:52:07 -0500 Subject: [PATCH 0902/6300] * bounds check on Identity::FromBuffer * properly indet last commits --- Identity.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index 648c996d..932d215f 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -22,6 +22,10 @@ namespace data size_t Identity::FromBuffer (const uint8_t * buf, size_t len) { + if ( len < DEFAULT_IDENTITY_SIZE ) { + // buffer too small, don't overflow + return 0; + } memcpy (publicKey, buf, DEFAULT_IDENTITY_SIZE); return DEFAULT_IDENTITY_SIZE; } @@ -242,17 +246,17 @@ namespace data size_t IdentityEx::FromBase64(const std::string& s) { - const size_t slen = s.length(); - const size_t bufLen = Base64EncodingBufferSize(slen); + const size_t slen = s.length(); + const size_t bufLen = Base64EncodingBufferSize(slen); uint8_t buf[bufLen]; - auto len = Base64ToByteStream (s.c_str(), slen, buf, 1024); + const size_t len = Base64ToByteStream (s.c_str(), slen, buf, bufLen); return FromBuffer (buf, len); } std::string IdentityEx::ToBase64 () const { - const size_t bufLen = GetFullLen(); - const size_t strLen = Base64EncodingBufferSize(bufLen); + const size_t bufLen = GetFullLen(); + const size_t strLen = Base64EncodingBufferSize(bufLen); uint8_t buf[bufLen]; char str[strLen]; size_t l = ToBuffer (buf, bufLen); From bf38bd5a1d25766c16cddd6d9ed17ba9b552ca4a Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 5 Feb 2016 10:40:23 -0500 Subject: [PATCH 0903/6300] * Fill padding with random in NTCP phase3 * Fill padding with random in NTCPSession::CreateMsgBuffer * Silence unused variable warnings in NTCPSession.cpp --- NTCPSession.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 2b745059..ed987bb9 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -134,6 +134,7 @@ namespace transport void NTCPSession::HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { + (void) bytes_transferred; if (ecode) { LogPrint (eLogInfo, "NTCP: couldn't send Phase 1 message: ", ecode.message ()); @@ -150,6 +151,7 @@ namespace transport void NTCPSession::HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred) { + (void) bytes_transferred; if (ecode) { LogPrint (eLogInfo, "NTCP: phase 1 read error: ", ecode.message ()); @@ -205,6 +207,7 @@ namespace transport void NTCPSession::HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB) { + (void) bytes_transferred; if (ecode) { LogPrint (eLogInfo, "NTCP: Couldn't send Phase 2 message: ", ecode.message ()); @@ -221,6 +224,7 @@ namespace transport void NTCPSession::HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred) { + (void) bytes_transferred; if (ecode) { LogPrint (eLogInfo, "NTCP: Phase 2 read error: ", ecode.message (), ". Wrong ident assumed"); @@ -277,7 +281,8 @@ namespace transport if (paddingSize > 0) { paddingSize = 16 - paddingSize; - // TODO: fill padding with random data + // fill padding with random data + RAND_bytes(buf, paddingSize); buf += paddingSize; len += paddingSize; } @@ -297,6 +302,7 @@ namespace transport void NTCPSession::HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA) { + (void) bytes_transferred; if (ecode) { LogPrint (eLogInfo, "NTCP: Couldn't send Phase 3 message: ", ecode.message ()); @@ -420,6 +426,7 @@ namespace transport void NTCPSession::HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { + (void) bytes_transferred; if (ecode) { LogPrint (eLogWarning, "NTCP: Couldn't send Phase 4 message: ", ecode.message ()); @@ -645,8 +652,11 @@ namespace transport } int rem = (len + 6) & 0x0F; // %16 int padding = 0; - if (rem > 0) padding = 16 - rem; - // TODO: fill padding + if (rem > 0) { + padding = 16 - rem; + // fill with random padding + RAND_bytes(sendBuffer + len + 2, padding); + } htobe32buf (sendBuffer + len + 2 + padding, adler32 (adler32 (0, Z_NULL, 0), sendBuffer, len + 2+ padding)); int l = len + padding + 6; @@ -667,6 +677,7 @@ namespace transport void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector > msgs) { + (void) msgs; m_IsSending = false; if (ecode) { From f034aef2ae456fa13bf2b61926f017a33c819446 Mon Sep 17 00:00:00 2001 From: 0niichan Date: Fri, 5 Feb 2016 22:58:04 +0700 Subject: [PATCH 0904/6300] Added instructions for a 64-bit OS --- docs/build_notes_windows.md | 59 ++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index c72b8989..81e0dfc2 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -20,11 +20,15 @@ development location for the sake of convenience. Adjust paths accordingly if it is not the case. Note that msys uses unix-alike paths like /c/dev/ for C:\dev\. + + msys2 ----- +### x86 (32-bit architecture) + Get install file msys2-i686-20150916.exe from https://msys2.github.io. -open MSys2Shell (from Start menu). +open MSYS2 Shell (from Start menu). Install all prerequisites and download i2pd source: ```bash @@ -37,14 +41,23 @@ export PATH=/mingw32/bin:/usr/bin # we need compiler on PATH which is usually he make ``` -If your processor has -[AES instruction set](https://en.wikipedia.org/wiki/AES_instruction_set), -you use `make USE_AESNI=1`. No check is done however, it -will compile, but it might crash with `Illegal instruction` if not supported. -You should be able to run ./i2pd . If you need to start from the new -shell, consider starting *MinGW-w64 Win32 Shell* instead of *MSYS2 Shell* as -it adds`/minw32/bin` to the PATH. +### x64 (64-bit architecture) + +Get install file msys2-x86_64-20150916.exe from https://msys2.github.io. +open MSYS2 Shell (from Start menu). +Install all prerequisites and download i2pd source: + +```bash +pacman -S mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-gcc git make +mkdir -p /c/dev/i2pd +cd /c/dev/i2pd +git clone https://github.com/PurpleI2P/i2pd.git +cd i2pd +export PATH=/mingw64/bin:/usr/bin # we need compiler on PATH which is usually heavily cluttered on Windows +make +``` + ### Caveats @@ -63,6 +76,19 @@ home page, otherwise you might end up with DLLs incompatibility problems. +### AES-NI + +If your processor has +[AES instruction set](https://en.wikipedia.org/wiki/AES_instruction_set), +you use `make USE_AESNI=1`. No check is done however, it +will compile, but it might crash with `Illegal instruction` if not supported. + +You should be able to run ./i2pd . If you need to start from the new +shell, consider starting *MinGW-w64 Win32 Shell* instead of *MSYS2 Shell* as +it adds`/minw32/bin` to the PATH. + + + Using Visual Studio ------------------- @@ -77,7 +103,7 @@ Requirements for building: * Strawberry Perl or ActiveState Perl, do NOT try msys2 perl, it won't work -## Building Boost +### Building Boost Open a Command Prompt (there is no need to start Visual Studio command prompt to build Boost) and run the following: @@ -98,8 +124,8 @@ only and static linking, and/or you are out of space/time, you might consider `--build-type=minimal`. Take a look at [appveyor.yml](../appveyor.yml) for details on how test builds are done. -Building OpenSSL ------------------ + +### Building OpenSSL Download OpenSSL, e.g. with git @@ -123,8 +149,8 @@ maintaining multiple versions, e.g. 64 bit and/or static/shared. Consult `C:\Program Files (x86)\CMake\share\cmake-3.3\Modules\FindOpenSSL.cmake` for details. -Get miniupnpc -------------- + +### Get miniupnpc If you are behind a UPnP enabled router and don't feel like manually configuring port forwarding, you should consider using @@ -139,8 +165,7 @@ Note that you might need to build DLL yourself for 64-bit systems using msys2 as 64-bit DLLs are not provided by the project. -Creating Visual Studio project ------------------------------- +### Creating Visual Studio project Start CMake GUI, navigate to i2pd directory, choose building directory, e.g. ./out, and configure options. @@ -155,8 +180,8 @@ cmake ..\build -G "Visual Studio 12 2013" -DWITH_UPNP=ON -DWITH_PCH=ON -DCMAKE_I WITH_UPNP will stay off, if necessary files are not found. -Building i2pd -------------- + +### Building i2pd You can open generated solution/project with Visual Studio and build from there, alternatively you can use `cmake --build . --config Release --target install` or From babcbcbceacb4e284402d27be43ff8f872f218af Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 5 Feb 2016 12:32:50 -0500 Subject: [PATCH 0905/6300] use const size_t instead of size_t --- Identity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Identity.cpp b/Identity.cpp index 932d215f..e32c3a59 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -233,7 +233,7 @@ namespace data size_t IdentityEx::ToBuffer (uint8_t * buf, size_t len) const { - size_t fullLen = GetFullLen(); + const size_t fullLen = GetFullLen(); if (fullLen > len) { // buffer is too small and may overflow somewhere else return 0; From 9f1b84d6f21f37c99f68740a95df989243ac6d56 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 5 Feb 2016 12:39:17 -0500 Subject: [PATCH 0906/6300] use const size_t instead of size_t --- SAM.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index d331a035..7b493eb3 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -633,12 +633,12 @@ namespace client { // get remote peer address auto ident_ptr = stream->GetRemoteIdentity(); - size_t ident_len = ident_ptr->GetFullLen(); + const size_t ident_len = ident_ptr->GetFullLen(); uint8_t* ident = new uint8_t[ident_len]; // send remote peer address as base64 - size_t l = ident_ptr->ToBuffer (ident, ident_len); - size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE); + const size_t l = ident_ptr->ToBuffer (ident, ident_len); + const size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE); delete[] ident; m_StreamBuffer[l1] = '\n'; HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream From 4c72d43a8af73705005b57f5671c4ee1721f50f3 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 5 Feb 2016 15:58:14 -0500 Subject: [PATCH 0907/6300] use more efficient XOR over ChipherBlocks for win32 --- Crypto.h | 2 +- Makefile.mingw | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Crypto.h b/Crypto.h index dfe0da0e..e633f8bf 100644 --- a/Crypto.h +++ b/Crypto.h @@ -70,7 +70,7 @@ namespace crypto void operator^=(const ChipherBlock& other) // XOR { -#if defined(__x86_64__) // for Intel x64 +#if defined(__x86_64__) || defined(__SSE__) // for Intel x84 or with SSE __asm__ ( "movups (%[buf]), %%xmm0 \n" diff --git a/Makefile.mingw b/Makefile.mingw index 591a1256..24f33a11 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -8,4 +8,6 @@ LDLIBS = -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_date_ti ifeq ($(USE_AESNI),1) CPU_FLAGS = -maes -DAESNI +else + CPU_FLAGS = -msse endif From 3b268fe3ccd30469ad9c94fc1245fe94f1e06bf1 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 5 Feb 2016 16:17:53 -0500 Subject: [PATCH 0908/6300] allow resolving of .b32.i2p addresses in SAM name lookup --- SAM.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SAM.cpp b/SAM.cpp index 7b493eb3..3c3364b4 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -468,6 +468,8 @@ namespace client i2p::data::IdentHash ident; if (name == "ME") SendNamingLookupReply (m_Session->localDestination->GetIdentity ()); + else if (name.rfind(".b32.i2p") == 52) + ident.FromBase32(name.substr(0, 52)); else if ((identity = context.GetAddressBook ().GetAddress (name)) != nullptr) SendNamingLookupReply (identity); else if (m_Session && m_Session->localDestination && From a8e12e624d89457e375f69c314f5ec524cd1aca5 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 5 Feb 2016 21:38:03 -0500 Subject: [PATCH 0909/6300] fixed hanging connection --- SAM.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index 3c3364b4..7b493eb3 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -468,8 +468,6 @@ namespace client i2p::data::IdentHash ident; if (name == "ME") SendNamingLookupReply (m_Session->localDestination->GetIdentity ()); - else if (name.rfind(".b32.i2p") == 52) - ident.FromBase32(name.substr(0, 52)); else if ((identity = context.GetAddressBook ().GetAddress (name)) != nullptr) SendNamingLookupReply (identity); else if (m_Session && m_Session->localDestination && From 4c6ef32d72cec5673d2cbf7b67ea25fd5528717b Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 6 Feb 2016 08:52:02 -0500 Subject: [PATCH 0910/6300] fixed #369 --- NetDb.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 0cbdce30..70b33f7f 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -316,7 +316,8 @@ namespace data const std::string& fullPath = it1->path(); #endif auto r = std::make_shared(fullPath); - if (!r->IsUnreachable () && (!r->UsesIntroducer () || ts < r->GetTimestamp () + 3600*1000LL)) // 1 hour + if (r->GetRouterIdentity () && !r->IsUnreachable () && + (!r->UsesIntroducer () || ts < r->GetTimestamp () + 3600*1000LL)) // 1 hour { r->DeleteBuffer (); r->ClearProperties (); // properties are not used for regular routers From 76096747b68011b2b5c82088a5be7230a8e3eb0b Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 7 Feb 2016 17:45:11 -0500 Subject: [PATCH 0911/6300] cleanup incoming and outgoing tags together --- Destination.cpp | 2 +- Garlic.cpp | 41 ++++++++++++++++++----------------------- Garlic.h | 5 ++--- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 233946ad..f2dac631 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -664,7 +664,7 @@ namespace client { if (ecode != boost::asio::error::operation_aborted) { - CleanupRoutingSessions (); + CleanupExpiredTags (); CleanupRemoteLeaseSets (); m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer, diff --git a/Garlic.cpp b/Garlic.cpp index d62b6fe3..88e255ff 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -409,28 +409,6 @@ namespace garlic else LogPrint (eLogError, "Garlic: Failed to decrypt message"); } - - // cleanup expired tags - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - if (ts > m_LastTagsCleanupTime + INCOMING_TAGS_EXPIRATION_TIMEOUT) - { - if (m_LastTagsCleanupTime) - { - int numExpiredTags = 0; - for (auto it = m_Tags.begin (); it != m_Tags.end ();) - { - if (ts > it->first.creationTime + INCOMING_TAGS_EXPIRATION_TIMEOUT) - { - numExpiredTags++; - it = m_Tags.erase (it); - } - else - it++; - } - LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " tags expired for ", GetIdentHash().ToBase64 ()); - } - m_LastTagsCleanupTime = ts; - } } void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr decryption, @@ -570,8 +548,25 @@ namespace garlic return session; } - void GarlicDestination::CleanupRoutingSessions () + void GarlicDestination::CleanupExpiredTags () { + // incoming + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + int numExpiredTags = 0; + for (auto it = m_Tags.begin (); it != m_Tags.end ();) + { + if (ts > it->first.creationTime + INCOMING_TAGS_EXPIRATION_TIMEOUT) + { + numExpiredTags++; + it = m_Tags.erase (it); + } + else + it++; + } + if (numExpiredTags > 0) + LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " tags expired for ", GetIdentHash().ToBase64 ()); + + // outgoing std::unique_lock l(m_SessionsMutex); for (auto it = m_Sessions.begin (); it != m_Sessions.end ();) { diff --git a/Garlic.h b/Garlic.h index 73231d10..b42f92cf 100644 --- a/Garlic.h +++ b/Garlic.h @@ -126,12 +126,12 @@ namespace garlic { public: - GarlicDestination (): m_NumTags (32), m_LastTagsCleanupTime (0) {}; // 32 tags by default + GarlicDestination (): m_NumTags (32) {}; // 32 tags by default ~GarlicDestination (); void SetNumTags (int numTags) { m_NumTags = numTags; }; std::shared_ptr GetRoutingSession (std::shared_ptr destination, bool attachLeaseSet); - void CleanupRoutingSessions (); + void CleanupExpiredTags (); void RemoveDeliveryStatusSession (uint32_t msgID); std::shared_ptr WrapMessage (std::shared_ptr destination, std::shared_ptr msg, bool attachLeaseSet = false); @@ -167,7 +167,6 @@ namespace garlic std::map m_Sessions; // incoming std::map> m_Tags; - uint32_t m_LastTagsCleanupTime; // DeliveryStatus std::map m_DeliveryStatusSessions; // msgID -> session From f3b277aeef67c3fb6c31915968acaf6284334aad Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 7 Feb 2016 19:45:06 -0500 Subject: [PATCH 0912/6300] doesn't store leases in netdb --- Destination.cpp | 8 +++++--- Destination.h | 2 +- HTTPServer.cpp | 4 ++-- LeaseSet.cpp | 49 ++++++++++++++++++++++++++++++------------------- LeaseSet.h | 9 ++++++--- NetDb.cpp | 5 +++-- 6 files changed, 47 insertions(+), 30 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index f2dac631..c07718cb 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -187,16 +187,17 @@ namespace client auto it = m_RemoteLeaseSets.find (ident); if (it != m_RemoteLeaseSets.end ()) { - if (it->second->HasNonExpiredLeases ()) + if (!it->second->IsExpired ()) return it->second; else - LogPrint (eLogWarning, "Destination: All leases of remote LeaseSet expired"); + LogPrint (eLogWarning, "Destination: remote LeaseSet expired"); } else { auto ls = i2p::data::netdb.FindLeaseSet (ident); if (ls) { + ls->PopulateLeases (); // since we don't store them in netdb m_RemoteLeaseSets[ident] = ls; return ls; } @@ -674,9 +675,10 @@ namespace client void ClientDestination::CleanupRemoteLeaseSets () { + auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it = m_RemoteLeaseSets.begin (); it != m_RemoteLeaseSets.end ();) { - if (!it->second->HasNonExpiredLeases ()) // all leases expired + if (ts > it->second->GetExpirationTime ()) // leaseset expired { LogPrint (eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); it = m_RemoteLeaseSets.erase (it); diff --git a/Destination.h b/Destination.h index ece125e3..c45a20ec 100644 --- a/Destination.h +++ b/Destination.h @@ -72,7 +72,7 @@ namespace client bool IsRunning () const { return m_IsRunning; }; boost::asio::io_service& GetService () { return m_Service; }; std::shared_ptr GetTunnelPool () { return m_Pool; }; - bool IsReady () const { return m_LeaseSet && m_LeaseSet->HasNonExpiredLeases () && m_Pool->GetOutboundTunnels ().size () > 0; }; + bool IsReady () const { return m_LeaseSet && !m_LeaseSet->IsExpired () && m_Pool->GetOutboundTunnels ().size () > 0; }; std::shared_ptr FindLeaseSet (const i2p::data::IdentHash& ident); bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); void CancelDestinationRequest (const i2p::data::IdentHash& dest); diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 2fef6e66..f04b0b1c 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -793,7 +793,7 @@ namespace util } auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (destination); - if (leaseSet && leaseSet->HasNonExpiredLeases ()) + if (leaseSet && !leaseSet->IsExpired ()) SendToDestination (leaseSet, port, buf, len); else { @@ -812,7 +812,7 @@ namespace util if (ecode != boost::asio::error::operation_aborted) { auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (destination); - if (leaseSet && leaseSet->HasNonExpiredLeases ()) + if (leaseSet && !leaseSet->IsExpired ()) SendToDestination (leaseSet, port, buf, len); else // still no LeaseSet diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 716ee6d2..adcbc388 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -12,8 +12,8 @@ namespace i2p namespace data { - LeaseSet::LeaseSet (const uint8_t * buf, size_t len): - m_IsValid (true) + LeaseSet::LeaseSet (const uint8_t * buf, size_t len, bool storeLeases): + m_IsValid (true), m_StoreLeases (storeLeases), m_ExpirationTime (0) { m_Buffer = new uint8_t[len]; memcpy (m_Buffer, buf, len); @@ -22,7 +22,7 @@ namespace data } LeaseSet::LeaseSet (std::shared_ptr pool): - m_IsValid (true) + m_IsValid (true), m_StoreLeases (true), m_ExpirationTime (0) { if (!pool) return; // header @@ -55,6 +55,7 @@ namespace data uint64_t ts = it->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration ts *= 1000; // in milliseconds ts += rand () % 6; // + random milliseconds 0-5 + if (ts > m_ExpirationTime) m_ExpirationTime = ts; htobe64buf (m_Buffer + m_BufferLen, ts); m_BufferLen += 8; // end date } @@ -79,7 +80,14 @@ namespace data m_BufferLen = len; ReadFromBuffer (false); } - + + void LeaseSet::PopulateLeases () + { + m_StoreLeases = true; + m_Leases.clear (); + ReadFromBuffer (false); + } + void LeaseSet::ReadFromBuffer (bool readIdentity) { if (readIdentity || !m_Identity) @@ -105,6 +113,7 @@ namespace data } // process leases + m_ExpirationTime = 0; const uint8_t * leases = m_Buffer + size; for (int i = 0; i < num; i++) { @@ -113,16 +122,20 @@ namespace data leases += 32; // gateway lease.tunnelID = bufbe32toh (leases); leases += 4; // tunnel ID - lease.endDate = bufbe64toh (leases); + lease.endDate = bufbe64toh (leases); leases += 8; // end date - m_Leases.push_back (lease); - - // check if lease's gateway is in our netDb - if (!netdb.FindRouter (lease.tunnelGateway)) - { - // if not found request it - LogPrint (eLogInfo, "LeaseSet: Lease's tunnel gateway not found, requesting"); - netdb.RequestDestination (lease.tunnelGateway); + if (lease.endDate > m_ExpirationTime) + m_ExpirationTime = lease.endDate; + if (m_StoreLeases) + { + m_Leases.push_back (lease); + // check if lease's gateway is in our netDb + if (!netdb.FindRouter (lease.tunnelGateway)) + { + // if not found request it + LogPrint (eLogInfo, "LeaseSet: Lease's tunnel gateway not found, requesting"); + netdb.RequestDestination (lease.tunnelGateway); + } } } @@ -150,19 +163,17 @@ namespace data } bool LeaseSet::HasExpiredLeases () const - { + { auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto& it: m_Leases) if (ts >= it.endDate) return true; return false; - } + } - bool LeaseSet::HasNonExpiredLeases () const + bool LeaseSet::IsExpired () const { auto ts = i2p::util::GetMillisecondsSinceEpoch (); - for (auto& it: m_Leases) - if (ts < it.endDate) return true; - return false; + return ts > m_ExpirationTime; } } } diff --git a/LeaseSet.h b/LeaseSet.h index aa9b73ad..0f437170 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -37,10 +37,11 @@ namespace data { public: - LeaseSet (const uint8_t * buf, size_t len); + LeaseSet (const uint8_t * buf, size_t len, bool storeLeases = true); LeaseSet (std::shared_ptr pool); ~LeaseSet () { delete[] m_Buffer; }; void Update (const uint8_t * buf, size_t len); + void PopulateLeases (); /// from buffer std::shared_ptr GetIdentity () const { return m_Identity; }; const uint8_t * GetBuffer () const { return m_Buffer; }; @@ -52,7 +53,8 @@ namespace data const std::vector& GetLeases () const { return m_Leases; }; const std::vector GetNonExpiredLeases (bool withThreshold = true) const; bool HasExpiredLeases () const; - bool HasNonExpiredLeases () const; + bool IsExpired () const; + uint64_t GetExpirationTime () const { return m_ExpirationTime; }; const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionKey; }; bool IsDestination () const { return true; }; @@ -62,8 +64,9 @@ namespace data private: - bool m_IsValid; + bool m_IsValid, m_StoreLeases; // we don't need to store leases for floodfill std::vector m_Leases; + uint64_t m_ExpirationTime; // in milliseconds std::shared_ptr m_Identity; uint8_t m_EncryptionKey[256]; uint8_t * m_Buffer; diff --git a/NetDb.cpp b/NetDb.cpp index 70b33f7f..fe1d6a45 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -206,7 +206,7 @@ namespace data } else { - auto leaseSet = std::make_shared (buf, len); + auto leaseSet = std::make_shared (buf, len, false); // we don't need leases in netdb if (leaseSet->IsValid ()) { LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase64()); @@ -981,9 +981,10 @@ namespace data void NetDb::ManageLeaseSets () { + auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it = m_LeaseSets.begin (); it != m_LeaseSets.end ();) { - if (!it->second->HasNonExpiredLeases ()) // all leases expired + if (ts > it->second->GetExpirationTime ()) { LogPrint (eLogWarning, "NetDb: LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); it = m_LeaseSets.erase (it); From e90baf3ca6ee30d21861c1541ffb0b57b97b3894 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 7 Feb 2016 21:35:06 -0500 Subject: [PATCH 0913/6300] correct required base64 buffer size --- Base.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Base.cpp b/Base.cpp index f5144eda..1479d62f 100644 --- a/Base.cpp +++ b/Base.cpp @@ -190,6 +190,13 @@ namespace data return outCount; } + size_t Base64EncodingBufferSize (const size_t input_size) + { + auto d = div (input_size, 3); + if (d.rem) d.quot++; + return 4*d.quot; + } + /* * * iT64 @@ -328,13 +335,6 @@ namespace data return 0; } } - - // from https://stackoverflow.com/questions/1533113/calculate-the-size-to-a-base-64-encoded-message - size_t Base64EncodingBufferSize(const size_t input_size) { - const size_t code_size = ((input_size * 4) / 3); - const size_t padding_size = (input_size % 3) ? (3 - (input_size % 3)) : 0; - return code_size + padding_size; - } } } From 3d19e920599fb15145e1281fdc300f2a6e174553 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 8 Feb 2016 14:42:20 -0500 Subject: [PATCH 0914/6300] queue up out of sequence packets --- Streaming.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- Streaming.h | 5 +++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index ea90c1f0..7c27c5ce 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -787,6 +787,12 @@ namespace stream StreamingDestination::~StreamingDestination () { + for (auto it: m_SavedPackets) + { + for (auto it1: it.second) delete it1; + it.second.clear (); + } + m_SavedPackets.clear (); } void StreamingDestination::Start () @@ -822,7 +828,20 @@ namespace stream if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream { auto incomingStream = CreateNewIncomingStream (); - incomingStream->HandleNextPacket (packet); + uint32_t receiveStreamID = packet->GetReceiveStreamID (); + incomingStream->HandleNextPacket (packet); // SYN + // handle saved packets if any + { + auto it = m_SavedPackets.find (receiveStreamID); + if (it != m_SavedPackets.end ()) + { + LogPrint (eLogDebug, "Streaming: Processing ", it->second.size (), " saved packets for receiveStreamID=", receiveStreamID); + for (auto it1: it->second) + incomingStream->HandleNextPacket (it1); + m_SavedPackets.erase (it); + } + } + // accept if (m_Acceptor != nullptr) m_Acceptor (incomingStream); else @@ -834,7 +853,7 @@ namespace stream m_PendingIncomingTimer.cancel (); m_PendingIncomingTimer.expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT)); m_PendingIncomingTimer.async_wait (std::bind (&StreamingDestination::HandlePendingIncomingTimer, - this, std::placeholders::_1)); + shared_from_this (), std::placeholders::_1)); LogPrint (eLogDebug, "Streaming: Pending incoming stream added"); } else @@ -854,6 +873,31 @@ namespace stream it.second->HandleNextPacket (packet); return; } + // save follow on packet + auto it = m_SavedPackets.find (receiveStreamID); + if (it != m_SavedPackets.end ()) + it->second.push_back (packet); + else + { + m_SavedPackets.emplace (receiveStreamID, std::list{ packet }); + auto timer = std::make_shared (m_Owner->GetService ()); + timer->expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT)); + auto s = shared_from_this (); + timer->async_wait ([s,timer,receiveStreamID](const boost::system::error_code& ecode) + { + if (ecode == boost::asio::error::operation_aborted) + { + auto it = s->m_SavedPackets.find (receiveStreamID); + if (it != s->m_SavedPackets.end ()) + { + for (auto it1: it->second) delete it1; + it->second.clear (); + s->m_SavedPackets.erase (it); + } + } + }); + } + // TODO: should queue it up LogPrint (eLogError, "Streaming: Unknown stream receiveStreamID=", receiveStreamID); delete packet; diff --git a/Streaming.h b/Streaming.h index 519bda31..0b240c0b 100644 --- a/Streaming.h +++ b/Streaming.h @@ -189,7 +189,7 @@ namespace stream SendHandler m_SendHandler; }; - class StreamingDestination + class StreamingDestination: public std::enable_shared_from_this { public: @@ -222,10 +222,11 @@ namespace stream std::shared_ptr m_Owner; uint16_t m_LocalPort; std::mutex m_StreamsMutex; - std::map > m_Streams; + std::map > m_Streams; // sendStreamID->stream Acceptor m_Acceptor; std::list > m_PendingIncomingStreams; boost::asio::deadline_timer m_PendingIncomingTimer; + std::map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN public: From 74f03202b787a4405ce76f994c9afa02711b7376 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 8 Feb 2016 15:02:17 -0500 Subject: [PATCH 0915/6300] queue up out of sequence packets --- Streaming.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 7c27c5ce..ee5576c7 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -897,10 +897,6 @@ namespace stream } }); } - - // TODO: should queue it up - LogPrint (eLogError, "Streaming: Unknown stream receiveStreamID=", receiveStreamID); - delete packet; } } } From e2e101e4fb74d6c5ed54e06cedce5ca578b9180b Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 8 Feb 2016 15:47:39 -0500 Subject: [PATCH 0916/6300] queue up out of sequence packets --- Streaming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index ee5576c7..8e0b223d 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -885,7 +885,7 @@ namespace stream auto s = shared_from_this (); timer->async_wait ([s,timer,receiveStreamID](const boost::system::error_code& ecode) { - if (ecode == boost::asio::error::operation_aborted) + if (ecode != boost::asio::error::operation_aborted) { auto it = s->m_SavedPackets.find (receiveStreamID); if (it != s->m_SavedPackets.end ()) From 6f0a1367271bc81930a4d257cfc1a7f5ff58bf81 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 8 Feb 2016 20:29:34 -0500 Subject: [PATCH 0917/6300] some cleanup --- Identity.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index e32c3a59..9d4162bf 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -234,10 +234,7 @@ namespace data size_t IdentityEx::ToBuffer (uint8_t * buf, size_t len) const { const size_t fullLen = GetFullLen(); - if (fullLen > len) { - // buffer is too small and may overflow somewhere else - return 0; - } + if (fullLen > len) return 0; // buffer is too small and may overflow somewhere else memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE); if (m_ExtendedLen > 0 && m_ExtendedBuffer) memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen); @@ -247,9 +244,8 @@ namespace data size_t IdentityEx::FromBase64(const std::string& s) { const size_t slen = s.length(); - const size_t bufLen = Base64EncodingBufferSize(slen); - uint8_t buf[bufLen]; - const size_t len = Base64ToByteStream (s.c_str(), slen, buf, bufLen); + uint8_t buf[slen]; // binary data can't exceed base64 + const size_t len = Base64ToByteStream (s.c_str(), slen, buf, slen); return FromBuffer (buf, len); } From d19eda7e0861862a24874a5b84181e6e56d1b07f Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 8 Feb 2016 20:29:56 -0500 Subject: [PATCH 0918/6300] moved Config.cpp to libi2pd --- filelist.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/filelist.mk b/filelist.mk index ce3a12f5..1d46b8fc 100644 --- a/filelist.mk +++ b/filelist.mk @@ -4,11 +4,11 @@ LIB_SRC = \ Reseed.cpp RouterContext.cpp RouterInfo.cpp Signature.cpp SSU.cpp \ SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp TransitTunnel.cpp \ Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp TunnelGateway.cpp \ - Destination.cpp Base.cpp I2PEndian.cpp util.cpp api.cpp + Destination.cpp Base.cpp I2PEndian.cpp Config.cpp util.cpp api.cpp LIB_CLIENT_SRC = \ AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \ - SAM.cpp SOCKS.cpp HTTPProxy.cpp Config.cpp + SAM.cpp SOCKS.cpp HTTPProxy.cpp # also: Daemon{Linux,Win32}.cpp will be added later DAEMON_SRC = \ From 7d927b0e282f8735ce34f1656c5b3ab3b5a699e6 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Feb 2016 10:46:27 -0500 Subject: [PATCH 0919/6300] shared_ptr for Lease --- Datagram.cpp | 2 +- I2NPProtocol.cpp | 4 ++-- LeaseSet.cpp | 12 +++++++----- LeaseSet.h | 20 +++++++------------- Streaming.cpp | 17 ++++++++--------- Streaming.h | 2 +- 6 files changed, 26 insertions(+), 31 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index b944cf11..9221824d 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -66,7 +66,7 @@ namespace datagram msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, - leases[i].tunnelGateway, leases[i].tunnelID, + leases[i]->tunnelGateway, leases[i]->tunnelID, garlic }); outboundTunnel->SendTunnelDataMsg (msgs); diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index c31a4479..db3330e2 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -265,9 +265,9 @@ namespace i2p auto leases = leaseSet->GetNonExpiredLeases (); if (leases.size () > 0) { - htobe32buf (payload + size, leases[0].tunnelID); + htobe32buf (payload + size, leases[0]->tunnelID); size += 4; // reply tunnelID - memcpy (payload + size, leases[0].tunnelGateway, 32); + memcpy (payload + size, leases[0]->tunnelGateway, 32); size += 32; // reply tunnel gateway } else diff --git a/LeaseSet.cpp b/LeaseSet.cpp index adcbc388..6beec5ab 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -128,7 +128,9 @@ namespace data m_ExpirationTime = lease.endDate; if (m_StoreLeases) { - m_Leases.push_back (lease); + auto l = std::make_shared(); + *l = lease; + m_Leases.push_back (l); // check if lease's gateway is in our netDb if (!netdb.FindRouter (lease.tunnelGateway)) { @@ -147,13 +149,13 @@ namespace data } } - const std::vector LeaseSet::GetNonExpiredLeases (bool withThreshold) const + const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const { auto ts = i2p::util::GetMillisecondsSinceEpoch (); - std::vector leases; + std::vector > leases; for (auto& it: m_Leases) { - auto endDate = it.endDate; + auto endDate = it->endDate; if (!withThreshold) endDate -= i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000; if (ts < endDate) @@ -166,7 +168,7 @@ namespace data { auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto& it: m_Leases) - if (ts >= it.endDate) return true; + if (ts >= it->endDate) return true; return false; } diff --git a/LeaseSet.h b/LeaseSet.h index 0f437170..027fb948 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "Identity.h" namespace i2p @@ -21,14 +22,6 @@ namespace data IdentHash tunnelGateway; uint32_t tunnelID; uint64_t endDate; - - bool operator< (const Lease& other) const - { - if (endDate != other.endDate) - return endDate > other.endDate; - else - return tunnelID < other.tunnelID; - } }; const int MAX_LS_BUFFER_SIZE = 3072; @@ -48,13 +41,14 @@ namespace data size_t GetBufferLen () const { return m_BufferLen; }; bool IsValid () const { return m_IsValid; }; - // implements RoutingDestination - const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); }; - const std::vector& GetLeases () const { return m_Leases; }; - const std::vector GetNonExpiredLeases (bool withThreshold = true) const; + const std::vector >& GetLeases () const { return m_Leases; }; + const std::vector > GetNonExpiredLeases (bool withThreshold = true) const; bool HasExpiredLeases () const; bool IsExpired () const; uint64_t GetExpirationTime () const { return m_ExpirationTime; }; + + // implements RoutingDestination + const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); }; const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionKey; }; bool IsDestination () const { return true; }; @@ -65,7 +59,7 @@ namespace data private: bool m_IsValid, m_StoreLeases; // we don't need to store leases for floodfill - std::vector m_Leases; + std::vector > m_Leases; uint64_t m_ExpirationTime; // in milliseconds std::shared_ptr m_Identity; uint8_t m_EncryptionKey[256]; diff --git a/Streaming.cpp b/Streaming.cpp index 8e0b223d..7e812167 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -22,7 +22,6 @@ namespace stream { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); m_RemoteIdentity = remote->GetIdentity (); - m_CurrentRemoteLease.endDate = 0; } Stream::Stream (boost::asio::io_service& service, StreamingDestination& local): @@ -599,9 +598,9 @@ namespace stream } auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (!m_CurrentRemoteLease.endDate || ts >= m_CurrentRemoteLease.endDate - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000) + if (m_CurrentRemoteLease || ts >= m_CurrentRemoteLease->endDate - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000) UpdateCurrentRemoteLease (true); - if (ts < m_CurrentRemoteLease.endDate) + if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate) { std::vector msgs; for (auto it: packets) @@ -610,7 +609,7 @@ namespace stream msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, - m_CurrentRemoteLease.tunnelGateway, m_CurrentRemoteLease.tunnelID, + m_CurrentRemoteLease->tunnelGateway, m_CurrentRemoteLease->tunnelID, msg }); m_NumSentBytes += it->GetLength (); @@ -725,10 +724,10 @@ namespace stream if (!leases.empty ()) { bool updated = false; - if (expired) + if (expired && m_CurrentRemoteLease) { for (auto it: leases) - if ((it.tunnelGateway == m_CurrentRemoteLease.tunnelGateway) && (it.tunnelID != m_CurrentRemoteLease.tunnelID)) + if ((it->tunnelGateway == m_CurrentRemoteLease->tunnelGateway) && (it->tunnelID != m_CurrentRemoteLease->tunnelID)) { m_CurrentRemoteLease = it; updated = true; @@ -738,7 +737,7 @@ namespace stream if (!updated) { uint32_t i = rand () % leases.size (); - if (m_CurrentRemoteLease.endDate && leases[i].tunnelID == m_CurrentRemoteLease.tunnelID) + if (m_CurrentRemoteLease && leases[i]->tunnelID == m_CurrentRemoteLease->tunnelID) // make sure we don't select previous i = (i + 1) % leases.size (); // if so, pick next m_CurrentRemoteLease = leases[i]; @@ -747,12 +746,12 @@ namespace stream else { m_RemoteLeaseSet = nullptr; - m_CurrentRemoteLease.endDate = 0; + m_CurrentRemoteLease = nullptr; // re-request expired } } else - m_CurrentRemoteLease.endDate = 0; + m_CurrentRemoteLease = nullptr; } std::shared_ptr Stream::CreateDataMessage (const uint8_t * payload, size_t len) diff --git a/Streaming.h b/Streaming.h index 0b240c0b..2f85397a 100644 --- a/Streaming.h +++ b/Streaming.h @@ -172,7 +172,7 @@ namespace stream std::shared_ptr m_RemoteIdentity; std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_RoutingSession; - i2p::data::Lease m_CurrentRemoteLease; + std::shared_ptr m_CurrentRemoteLease; std::shared_ptr m_CurrentOutboundTunnel; std::queue m_ReceiveQueue; std::set m_SavedPackets; From 481fafc11d7b105a7a91274bd617ae4380a6c734 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Feb 2016 15:27:23 -0500 Subject: [PATCH 0920/6300] invalidate excluded leases --- LeaseSet.cpp | 35 +++++++++++++++++++++++++++-------- LeaseSet.h | 20 +++++++++++++++----- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 6beec5ab..921941b1 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -69,7 +69,6 @@ namespace data void LeaseSet::Update (const uint8_t * buf, size_t len) { - m_Leases.clear (); if (len > m_BufferLen) { auto oldBuffer = m_Buffer; @@ -84,7 +83,6 @@ namespace data void LeaseSet::PopulateLeases () { m_StoreLeases = true; - m_Leases.clear (); ReadFromBuffer (false); } @@ -112,6 +110,13 @@ namespace data return; } + // reset existing leases + if (m_StoreLeases) + for (auto it: m_Leases) + it->isUpdated = false; + else + m_Leases.clear (); + // process leases m_ExpirationTime = 0; const uint8_t * leases = m_Buffer + size; @@ -128,9 +133,9 @@ namespace data m_ExpirationTime = lease.endDate; if (m_StoreLeases) { - auto l = std::make_shared(); - *l = lease; - m_Leases.push_back (l); + auto ret = m_Leases.insert (std::make_shared(lease)); + if (!ret.second) *(*ret.first) = lease; // update existing + (*ret.first)->isUpdated = true; // check if lease's gateway is in our netDb if (!netdb.FindRouter (lease.tunnelGateway)) { @@ -140,7 +145,21 @@ namespace data } } } - + // delete old leases + if (m_StoreLeases) + { + for (auto it = m_Leases.begin (); it != m_Leases.end ();) + { + if (!(*it)->isUpdated) + { + (*it)->endDate = 0; // somebody might still hold it + m_Leases.erase (it++); + } + else + it++; + } + } + // verify if (!m_Identity->Verify (m_Buffer, leases - m_Buffer, leases)) { @@ -153,7 +172,7 @@ namespace data { auto ts = i2p::util::GetMillisecondsSinceEpoch (); std::vector > leases; - for (auto& it: m_Leases) + for (auto it: m_Leases) { auto endDate = it->endDate; if (!withThreshold) @@ -167,7 +186,7 @@ namespace data bool LeaseSet::HasExpiredLeases () const { auto ts = i2p::util::GetMillisecondsSinceEpoch (); - for (auto& it: m_Leases) + for (auto it: m_Leases) if (ts >= it->endDate) return true; return false; } diff --git a/LeaseSet.h b/LeaseSet.h index 027fb948..b9607494 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -21,7 +21,19 @@ namespace data { IdentHash tunnelGateway; uint32_t tunnelID; - uint64_t endDate; + uint64_t endDate; // 0 means invalid + bool isUpdated; // trasient + }; + + struct LeaseCmp + { + bool operator() (std::shared_ptr l1, std::shared_ptr l2) const + { + if (l1->tunnelID != l2->tunnelID) + return l1->tunnelID < l2->tunnelID; + else + return l1->tunnelGateway < l2->tunnelGateway; + }; }; const int MAX_LS_BUFFER_SIZE = 3072; @@ -34,14 +46,12 @@ namespace data LeaseSet (std::shared_ptr pool); ~LeaseSet () { delete[] m_Buffer; }; void Update (const uint8_t * buf, size_t len); - void PopulateLeases (); /// from buffer + void PopulateLeases (); // from buffer std::shared_ptr GetIdentity () const { return m_Identity; }; const uint8_t * GetBuffer () const { return m_Buffer; }; size_t GetBufferLen () const { return m_BufferLen; }; bool IsValid () const { return m_IsValid; }; - - const std::vector >& GetLeases () const { return m_Leases; }; const std::vector > GetNonExpiredLeases (bool withThreshold = true) const; bool HasExpiredLeases () const; bool IsExpired () const; @@ -59,7 +69,7 @@ namespace data private: bool m_IsValid, m_StoreLeases; // we don't need to store leases for floodfill - std::vector > m_Leases; + std::set, LeaseCmp> m_Leases; uint64_t m_ExpirationTime; // in milliseconds std::shared_ptr m_Identity; uint8_t m_EncryptionKey[256]; From c754b5ae18d74b7f1bb146aaa4e19559a720a48d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Feb 2016 17:54:22 -0500 Subject: [PATCH 0921/6300] fixed crash --- Streaming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index 7e812167..5de9edfb 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -598,7 +598,7 @@ namespace stream } auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (m_CurrentRemoteLease || ts >= m_CurrentRemoteLease->endDate - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000) + if (!m_CurrentRemoteLease || ts >= m_CurrentRemoteLease->endDate - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000) UpdateCurrentRemoteLease (true); if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate) { From e056c9c135a06e994c0b01a8292e1a506d832890 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Feb 2016 22:42:01 -0500 Subject: [PATCH 0922/6300] drop expired leasesand renew leaseset --- Destination.cpp | 4 ++-- LeaseSet.cpp | 41 +++++++++++++++++++++++++++-------------- LeaseSet.h | 1 + Streaming.cpp | 6 +++--- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index c07718cb..e7e9b93c 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -303,7 +303,7 @@ namespace client } else { - LogPrint (eLogError, "New remote LeaseSet verification failed"); + LogPrint (eLogError, "New remote LeaseSet failed"); leaseSet = nullptr; } } @@ -678,7 +678,7 @@ namespace client auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it = m_RemoteLeaseSets.begin (); it != m_RemoteLeaseSets.end ();) { - if (ts > it->second->GetExpirationTime ()) // leaseset expired + if (it->second->IsEmpty () || ts > it->second->GetExpirationTime ()) // leaseset expired { LogPrint (eLogWarning, "Destination: Remote LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); it = m_RemoteLeaseSets.erase (it); diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 921941b1..ebbdbd97 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -119,6 +119,7 @@ namespace data // process leases m_ExpirationTime = 0; + auto ts = i2p::util::GetMillisecondsSinceEpoch (); const uint8_t * leases = m_Buffer + size; for (int i = 0; i < num; i++) { @@ -129,21 +130,32 @@ namespace data leases += 4; // tunnel ID lease.endDate = bufbe64toh (leases); leases += 8; // end date - if (lease.endDate > m_ExpirationTime) - m_ExpirationTime = lease.endDate; - if (m_StoreLeases) + if (ts < lease.endDate) { - auto ret = m_Leases.insert (std::make_shared(lease)); - if (!ret.second) *(*ret.first) = lease; // update existing - (*ret.first)->isUpdated = true; - // check if lease's gateway is in our netDb - if (!netdb.FindRouter (lease.tunnelGateway)) - { - // if not found request it - LogPrint (eLogInfo, "LeaseSet: Lease's tunnel gateway not found, requesting"); - netdb.RequestDestination (lease.tunnelGateway); - } - } + if (lease.endDate > m_ExpirationTime) + m_ExpirationTime = lease.endDate; + if (m_StoreLeases) + { + auto ret = m_Leases.insert (std::make_shared(lease)); + if (!ret.second) *(*ret.first) = lease; // update existing + (*ret.first)->isUpdated = true; + // check if lease's gateway is in our netDb + if (!netdb.FindRouter (lease.tunnelGateway)) + { + // if not found request it + LogPrint (eLogInfo, "LeaseSet: Lease's tunnel gateway not found, requesting"); + netdb.RequestDestination (lease.tunnelGateway); + } + } + } + else + LogPrint (eLogWarning, "LeaseSet: Lease is expired already "); + } + if (!m_ExpirationTime) + { + LogPrint (eLogWarning, "LeaseSet: all leases are expired. Dropped"); + m_IsValid = false; + return; } // delete old leases if (m_StoreLeases) @@ -193,6 +205,7 @@ namespace data bool LeaseSet::IsExpired () const { + if (IsEmpty ()) return true; auto ts = i2p::util::GetMillisecondsSinceEpoch (); return ts > m_ExpirationTime; } diff --git a/LeaseSet.h b/LeaseSet.h index b9607494..dd94bfaf 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -55,6 +55,7 @@ namespace data const std::vector > GetNonExpiredLeases (bool withThreshold = true) const; bool HasExpiredLeases () const; bool IsExpired () const; + bool IsEmpty () const { return m_Leases.empty (); }; uint64_t GetExpirationTime () const { return m_ExpirationTime; }; // implements RoutingDestination diff --git a/Streaming.cpp b/Streaming.cpp index 5de9edfb..9c05c9cb 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -704,11 +704,11 @@ namespace stream void Stream::UpdateCurrentRemoteLease (bool expired) { - if (!m_RemoteLeaseSet) + if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) { m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); - if (!m_RemoteLeaseSet) - LogPrint (eLogError, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); + if (!m_RemoteLeaseSet) + LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); } if (m_RemoteLeaseSet) { From 70f72a78f65989ac89ae32631b619b35d0369c3a Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 10 Feb 2016 00:00:00 +0000 Subject: [PATCH 0923/6300] + i2p::config::IsDefault --- Config.cpp | 11 ++++++++++- Config.h | 7 +++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Config.cpp b/Config.cpp index d1bf7b64..00350608 100644 --- a/Config.cpp +++ b/Config.cpp @@ -223,6 +223,15 @@ namespace config { void Finalize() { notify(m_Options); - }; + } + + bool IsDefault(const char *name) { + if (!m_Options.count(name)) + throw "try to check non-existent option"; + + if (m_Options[name].defaulted()) + return true; + return false; + } } // namespace config } // namespace i2p diff --git a/Config.h b/Config.h index 51a12d70..07d7ccb7 100644 --- a/Config.h +++ b/Config.h @@ -94,6 +94,13 @@ namespace config { notify(m_Options); return true; } + + /** + * @brief Check is value explicitly given or default + * @param name Name of checked parameter + * @return true if value set to default, false othervise + */ + bool IsDefault(const char *name); } } From 7a0a45e9d203792c52862bd79e4d4b884e1d154b Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 10 Feb 2016 00:00:00 +0000 Subject: [PATCH 0924/6300] * use IsDefault() to check explicitly set values --- Daemon.cpp | 4 ++-- RouterContext.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index b9d8bd44..7421cba6 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -83,14 +83,14 @@ namespace i2p LogPrint(eLogDebug, "FS: data directory: ", datadir); uint16_t port; i2p::config::GetOption("port", port); - if (port) + if (!i2p::config::IsDefault("port")) { LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); i2p::context.UpdatePort (port); } std::string host; i2p::config::GetOption("host", host); - if (host != "0.0.0.0") + 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)); diff --git a/RouterContext.cpp b/RouterContext.cpp index 962cf3ee..3dc79027 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -48,7 +48,7 @@ namespace i2p if (!port) port = rand () % (30777 - 9111) + 9111; // I2P network ports range std::string host; i2p::config::GetOption("host", host); - if (host == "0.0.0.0") + if (i2p::config::IsDefault("host")) host = "127.0.0.1"; // replace default address with safe value routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); routerInfo.AddNTCPAddress (host.c_str(), port); From 5c1b5816d483e4baa9005b75c1dbc140fe19c304 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 10 Feb 2016 00:00:00 +0000 Subject: [PATCH 0925/6300] * fix segfault when offline (#330) --- Tunnel.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tunnel.cpp b/Tunnel.cpp index dbe87a2f..f7102327 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -666,6 +666,10 @@ namespace tunnel { // trying to create one more inbound tunnel auto router = i2p::data::netdb.GetRandomRouter (); + if (!router) { + LogPrint (eLogWarning, "Tunnel: can't find any router, skip creating tunnel"); + return; + } LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }) From 60b2da3671d9b2494c988dc15c77379f8828ccf5 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 10 Feb 2016 00:00:00 +0000 Subject: [PATCH 0926/6300] * add --datadir option (not actually works yet) (#290) --- Config.cpp | 1 + docs/configuration.md | 1 + 2 files changed, 2 insertions(+) diff --git a/Config.cpp b/Config.cpp index 00350608..9e9582ea 100644 --- a/Config.cpp +++ b/Config.cpp @@ -113,6 +113,7 @@ namespace config { ("log", value()->default_value(""), "Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") + ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") ("host", value()->default_value("0.0.0.0"), "External IP") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") diff --git a/docs/configuration.md b/docs/configuration.md index 3f3c5386..d5a3a5b0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,6 +12,7 @@ Command line options * --log= - Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility) * --logfile= - Path to logfile (default - autodetect) * --loglevel= - Log messages above this level (debug, *info, warn, error) +* --datadir= - Path to storage of i2pd data (RI, keys, peer profiles, ...) * --host= - The external IP * --port= - The port to listen on * --daemon - Router will go to background after start From dcab37a1486fcf2d7e2515b4dcce2d6e4ee6fab8 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 10 Feb 2016 00:00:00 +0000 Subject: [PATCH 0927/6300] * update debian/i2pd.{init,upstart} : logging options --- debian/i2pd.init | 6 +++++- debian/i2pd.upstart | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/debian/i2pd.init b/debian/i2pd.init index d87aa000..8cfee8d4 100644 --- a/debian/i2pd.init +++ b/debian/i2pd.init @@ -18,6 +18,7 @@ DAEMON_OPTS="" # Arguments to run the daemon with PIDFILE=/var/run/$NAME.pid I2PCONF=/etc/$NAME/i2pd.conf TUNCONF=/etc/$NAME/tunnels.conf +LOGFILE=/var/log/$NAME.log USER="i2pd" # Exit if the package is not installed @@ -43,10 +44,13 @@ do_start() touch "$PIDFILE" chown -f $USER:adm "$PIDFILE" + touch "$LOGFILE" + chown -f $USER:adm "$LOGFILE" + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" -- \ - --service --daemon --log --conf=$I2PCONF --tunconf=$TUNCONF \ + --service --daemon --log=file --logfile=$LOGFILE --conf=$I2PCONF --tunconf=$TUNCONF \ --port=$I2PD_PORT $DAEMON_OPTS > /dev/null 2>&1 \ || return 2 return $? diff --git a/debian/i2pd.upstart b/debian/i2pd.upstart index d1536ea3..29c6cbdb 100644 --- a/debian/i2pd.upstart +++ b/debian/i2pd.upstart @@ -6,5 +6,6 @@ stop on runlevel [016] or unmounting-filesystem # these can be overridden in /etc/init/i2pd.override env I2PD_HOST="1.2.3.4" env I2PD_PORT="4567" +env LOGFILE="/var/log/i2pd.log" -exec /usr/sbin/i2pd --daemon --log --host=$I2PD_HOST --port=$I2PD_PORT +exec /usr/sbin/i2pd --daemon --log=file --logfile=$LOGFILE --service --host=$I2PD_HOST --port=$I2PD_PORT From d5f831301fa07956e3f4ce88cbede647b6fbb0cb Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 10 Feb 2016 00:00:00 +0000 Subject: [PATCH 0928/6300] * explicit log message when bandwidth set to 'low' --- Daemon.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Daemon.cpp b/Daemon.cpp index 7421cba6..e972cee3 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -125,7 +125,10 @@ namespace i2p i2p::context.SetExtraBandwidth (); } else + { + LogPrint(eLogInfo, "Daemon: bandwidth set to 'low'"); i2p::context.SetLowBandwidth (); + } return true; } From 22c388ab184284d0e6d07b890f61059fdd369b53 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 10 Feb 2016 00:00:00 +0000 Subject: [PATCH 0929/6300] * fix compilation with gcc 4.7/4.8 --- Streaming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index 9c05c9cb..0c1a5e5d 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -878,7 +878,7 @@ namespace stream it->second.push_back (packet); else { - m_SavedPackets.emplace (receiveStreamID, std::list{ packet }); + m_SavedPackets[receiveStreamID] = std::list{ packet }; auto timer = std::make_shared (m_Owner->GetService ()); timer->expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT)); auto s = shared_from_this (); From d51bf735c49f825f01d3d53ecb17e39c9be95e90 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 10 Feb 2016 00:00:00 +0000 Subject: [PATCH 0930/6300] * fix mistype --- NTCPSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index ed987bb9..81b6bdc2 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -736,7 +736,7 @@ namespace transport { if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogWarning, "NTCP: No activity fo ", NTCP_TERMINATION_TIMEOUT, " seconds"); + LogPrint (eLogWarning, "NTCP: No activity for ", NTCP_TERMINATION_TIMEOUT, " seconds"); //Terminate (); m_Socket.close ();// invoke Terminate () from HandleReceive } From 61ad6a2b8816436fb47733f985f9c3f274734bb7 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 10 Feb 2016 16:09:34 -0500 Subject: [PATCH 0931/6300] set supported transports flag after actual address insertion --- RouterInfo.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 64b675a7..56482234 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -146,6 +146,7 @@ namespace data bool introducers = false; for (int i = 0; i < numAddresses; i++) { + uint8_t supportedTransports = 0; bool isValidAddress = true; Address address; s.read ((char *)&address.cost, sizeof (address.cost)); @@ -178,12 +179,12 @@ namespace data { if (address.transportStyle == eTransportNTCP) { - m_SupportedTransports |= eNTCPV4; // TODO: + supportedTransports |= eNTCPV4; // TODO: address.addressString = value; } else { - m_SupportedTransports |= eSSUV4; // TODO: + supportedTransports |= eSSUV4; // TODO: address.addressString = value; } } @@ -191,9 +192,9 @@ namespace data { // add supported protocol if (address.host.is_v4 ()) - m_SupportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4; + supportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4; else - m_SupportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV6 : eSSUV6; + supportedTransports |= (address.transportStyle == eTransportNTCP) ? eNTCPV6 : eSSUV6; } } else if (!strcmp (key, "port")) @@ -229,7 +230,10 @@ namespace data if (!s) return; } if (isValidAddress) + { m_Addresses.push_back(address); + m_SupportedTransports |= supportedTransports; + } } // read peers uint8_t numPeers; From 93720fffd453f403d809bf89bf1a19521371c1b5 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 10 Feb 2016 22:51:08 -0500 Subject: [PATCH 0932/6300] shared path between streams --- Garlic.cpp | 23 ++++++++++++++++++++++- Garlic.h | 24 ++++++++++++++++++++---- LeaseSet.cpp | 4 ++-- LeaseSet.h | 2 +- Streaming.cpp | 23 ++++++++++++++++++++++- Streaming.h | 2 +- 6 files changed, 68 insertions(+), 10 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index 88e255ff..b26077eb 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -42,7 +42,27 @@ namespace garlic delete it.second; m_UnconfirmedTagsMsgs.clear (); } - + + std::shared_ptr GarlicRoutingSession::GetSharedRoutingPath () + { + if (!m_SharedRoutingPath) return nullptr; + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + if (!m_SharedRoutingPath->outboundTunnel->IsEstablished () || + ts*1000LL > m_SharedRoutingPath->remoteLease->endDate || + ts > m_SharedRoutingPath->updateTime + ROUTING_PATH_EXPIRATION_TIMEOUT) + m_SharedRoutingPath = nullptr; + return m_SharedRoutingPath; + } + + void GarlicRoutingSession::SetSharedRoutingPath (std::shared_ptr path) + { + if (path && path->outboundTunnel && path->remoteLease) + path->updateTime = i2p::util::GetSecondsSinceEpoch (); + else + path = nullptr; + m_SharedRoutingPath = path; + } + GarlicRoutingSession::UnconfirmedTags * GarlicRoutingSession::GenerateSessionTags () { auto tags = new UnconfirmedTags (m_NumTags); @@ -570,6 +590,7 @@ namespace garlic std::unique_lock l(m_SessionsMutex); for (auto it = m_Sessions.begin (); it != m_Sessions.end ();) { + it->second->GetSharedRoutingPath (); // delete shared path if necessary if (!it->second->CleanupExpiredTags ()) { LogPrint (eLogInfo, "Routing session to ", it->first.ToBase32 (), " deleted"); diff --git a/Garlic.h b/Garlic.h index b42f92cf..95a59e72 100644 --- a/Garlic.h +++ b/Garlic.h @@ -15,7 +15,12 @@ #include "Identity.h" namespace i2p -{ +{ +namespace tunnel +{ + class OutboundTunnel; +} + namespace garlic { @@ -27,19 +32,18 @@ namespace garlic eGarlicDeliveryTypeTunnel = 3 }; -#pragma pack(1) struct ElGamalBlock { uint8_t sessionKey[32]; uint8_t preIV[32]; uint8_t padding[158]; }; -#pragma pack() const int INCOMING_TAGS_EXPIRATION_TIMEOUT = 960; // 16 minutes const int OUTGOING_TAGS_EXPIRATION_TIMEOUT = 720; // 12 minutes const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds const int LEASET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds + const int ROUTING_PATH_EXPIRATION_TIMEOUT = 30; // 30 seconds struct SessionTag: public i2p::data::Tag<32> { @@ -53,7 +57,14 @@ namespace garlic #endif uint32_t creationTime; // seconds since epoch }; - + + struct GarlicRoutingPath + { + std::shared_ptr outboundTunnel; + std::shared_ptr remoteLease; + uint32_t updateTime; // seconds since epoch + }; + class GarlicDestination; class GarlicRoutingSession: public std::enable_shared_from_this { @@ -88,6 +99,9 @@ namespace garlic { if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated; }; + + std::shared_ptr GetSharedRoutingPath (); + void SetSharedRoutingPath (std::shared_ptr path); private: @@ -115,6 +129,8 @@ namespace garlic i2p::crypto::CBCEncryption m_Encryption; std::unique_ptr m_ElGamalEncryption; + std::shared_ptr m_SharedRoutingPath; + public: // for HTTP only size_t GetNumOutgoingTags () const { return m_SessionTags.size (); }; diff --git a/LeaseSet.cpp b/LeaseSet.cpp index ebbdbd97..677f19ca 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -180,10 +180,10 @@ namespace data } } - const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const + const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const { auto ts = i2p::util::GetMillisecondsSinceEpoch (); - std::vector > leases; + std::vector > leases; for (auto it: m_Leases) { auto endDate = it->endDate; diff --git a/LeaseSet.h b/LeaseSet.h index dd94bfaf..aed27683 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -52,7 +52,7 @@ namespace data const uint8_t * GetBuffer () const { return m_Buffer; }; size_t GetBufferLen () const { return m_BufferLen; }; bool IsValid () const { return m_IsValid; }; - const std::vector > GetNonExpiredLeases (bool withThreshold = true) const; + const std::vector > GetNonExpiredLeases (bool withThreshold = true) const; bool HasExpiredLeases () const; bool IsExpired () const; bool IsEmpty () const { return m_Leases.empty (); }; diff --git a/Streaming.cpp b/Streaming.cpp index 0c1a5e5d..1e9cdd84 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -271,6 +271,10 @@ namespace stream m_LastWindowSizeIncreaseTime = ts; } } + if (!seqn && m_RoutingSession) // first message confirmed + m_RoutingSession->SetSharedRoutingPath ( + std::make_shared ( + i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, 0})); } else break; @@ -589,6 +593,21 @@ namespace stream return; } } + if (!m_CurrentOutboundTunnel) // first message to send + { + // try to get shared path first + if (!m_RoutingSession) + m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); + if (m_RoutingSession) + { + auto routingPath = m_RoutingSession->GetSharedRoutingPath (); + if (routingPath) + { + m_CurrentOutboundTunnel = routingPath->outboundTunnel; + m_CurrentRemoteLease = routingPath->remoteLease; + } + } + } if (!m_CurrentOutboundTunnel || !m_CurrentOutboundTunnel->IsEstablished ()) m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); if (!m_CurrentOutboundTunnel) @@ -668,12 +687,14 @@ namespace stream case 2: m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change first time // no break here - case 4: + case 4: + if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); UpdateCurrentRemoteLease (); // pick another lease LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream"); break; case 3: // pick another outbound tunnel + if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); LogPrint (eLogWarning, "Streaming: Another outbound tunnel has been selected for stream"); break; diff --git a/Streaming.h b/Streaming.h index 2f85397a..c908c6ec 100644 --- a/Streaming.h +++ b/Streaming.h @@ -172,7 +172,7 @@ namespace stream std::shared_ptr m_RemoteIdentity; std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_RoutingSession; - std::shared_ptr m_CurrentRemoteLease; + std::shared_ptr m_CurrentRemoteLease; std::shared_ptr m_CurrentOutboundTunnel; std::queue m_ReceiveQueue; std::set m_SavedPackets; From 45c3b3987b1a23eee5289f88d327e7a989961570 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 11 Feb 2016 07:50:29 -0500 Subject: [PATCH 0933/6300] reset floodfill --- Daemon.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Daemon.cpp b/Daemon.cpp index e972cee3..242f4bbf 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -109,6 +109,8 @@ namespace i2p LogPrint(eLogInfo, "Daemon: router will be floodfill"); i2p::context.SetFloodfill (true); } + else + i2p::context.SetFloodfill (false); if (bandwidth != '-') { LogPrint(eLogInfo, "Daemon: bandwidth set to ", bandwidth); From d8ea3a9035ca8e188040b8096ac8781224995c69 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0934/6300] * make target 'strip' --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index b11305ce..09cf2ace 100644 --- a/Makefile +++ b/Makefile @@ -78,6 +78,9 @@ clean: rm -rf obj $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) +strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) + strip $^ + LATEST_TAG=$(shell git describe --tags --abbrev=0 master) dist: git archive --format=tar.gz -9 --worktree-attributes \ From 2f2b12811f1305d382c52727f85d88dae5e3774e Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0935/6300] * Addressbook: don't save to disk if address map is empty --- AddressBook.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AddressBook.cpp b/AddressBook.cpp index 754d314a..43612506 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -161,6 +161,11 @@ namespace client int AddressBookFilesystemStorage::Save (const std::map& addresses) { + if (addresses.size() == 0) { + LogPrint(eLogWarning, "Addressbook: not saving empty addressbook"); + return 0; + } + int num = 0; auto filename = GetPath () / "addresses.csv"; std::ofstream f (filename.string (), std::ofstream::out); // in text mode From 576801cd3278f0e8a7276ae96e847b350fb78937 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0936/6300] * Addressbook: load addresses at start, not on first request --- AddressBook.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 43612506..62cf77dd 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -196,6 +196,7 @@ namespace client void AddressBook::Start () { + LoadHosts (); /* try storage, then hosts.txt, then download */ StartSubscriptions (); } @@ -276,14 +277,9 @@ namespace client const i2p::data::IdentHash * AddressBook::FindAddress (const std::string& address) { - if (!m_IsLoaded) - LoadHosts (); - if (m_IsLoaded) - { - auto it = m_Addresses.find (address); - if (it != m_Addresses.end ()) - return &it->second; - } + auto it = m_Addresses.find (address); + if (it != m_Addresses.end ()) + return &it->second; return nullptr; } @@ -457,8 +453,6 @@ namespace client } else { - if (!m_IsLoaded) - LoadHosts (); // try it again later m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_RETRY_TIMEOUT)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, From 8949ebf041b783dd346a7742c245d6042f6aa624 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0937/6300] * tune logging --- Garlic.cpp | 2 +- HTTPProxy.cpp | 40 ++++++++++++++++++---------------------- I2PControl.cpp | 12 ++++++------ I2PService.cpp | 6 +++--- NTCPSession.cpp | 13 +++++++------ 5 files changed, 35 insertions(+), 38 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index b26077eb..f967a1e2 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -159,7 +159,7 @@ namespace garlic // create message if (!tagFound) // new session { - LogPrint (eLogWarning, "Garlic: No tags available. Use ElGamal"); + LogPrint (eLogWarning, "Garlic: No tags available, will use ElGamal"); if (!m_Destination) { LogPrint (eLogError, "Garlic: Can't use ElGamal for unknown destination"); diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index b4010a37..c417d904 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -65,13 +65,13 @@ namespace proxy void HTTPProxyHandler::AsyncSockRead() { - LogPrint(eLogDebug,"--- HTTP Proxy async sock read"); + LogPrint(eLogDebug, "HTTPProxy: async sock read"); if(m_sock) { m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), std::bind(&HTTPProxyHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { - LogPrint(eLogError,"--- HTTP Proxy no socket for read"); + LogPrint(eLogError, "HTTPProxy: no socket for read"); } } @@ -79,7 +79,7 @@ namespace proxy if (Kill()) return; if (m_sock) { - LogPrint(eLogDebug,"--- HTTP Proxy close sock"); + LogPrint(eLogDebug, "HTTPProxy: close sock"); m_sock->close(); m_sock = nullptr; } @@ -102,7 +102,7 @@ namespace proxy void HTTPProxyHandler::ExtractRequest() { - LogPrint(eLogDebug,"--- HTTP Proxy method is: ", m_method, "\nRequest is: ", m_url); + LogPrint(eLogDebug, "HTTPProxy: request: ", m_method, " ", m_url); std::string server=""; std::string port="80"; boost::regex rHTTP("http://(.*?)(:(\\d+))?(/.*)"); @@ -114,7 +114,7 @@ namespace proxy if (m[2].str() != "") port=m[3].str(); path=m[4].str(); } - LogPrint(eLogDebug,"--- HTTP Proxy server is: ",server, " port is: ", port, "\n path is: ",path); + LogPrint(eLogDebug, "HTTPProxy: server: ", server, ", port: ", port, ", path: ", path); m_address = server; m_port = boost::lexical_cast(port); m_path = path; @@ -124,7 +124,7 @@ namespace proxy { if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) { - LogPrint(eLogError,"--- HTTP Proxy unsupported version: ", m_version); + LogPrint(eLogError, "HTTPProxy: unsupported version: ", m_version); HTTPRequestFailed(); //TODO: send right stuff return false; } @@ -156,7 +156,7 @@ namespace proxy } auto base64 = m_path.substr (addressHelperPos + strlen(helpermark1)); base64 = i2p::util::http::urlDecode(base64); //Some of the symbols may be urlencoded - LogPrint (eLogDebug,"Jump service for ", m_address, " found at ", base64, ". Inserting to address book"); + 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); @@ -229,13 +229,13 @@ namespace proxy { case '\n': EnterState(DONE); break; default: - LogPrint(eLogError,"--- HTTP Proxy rejected invalid request ending with: ", ((int)*http_buff)); + LogPrint(eLogError, "HTTPProxy: rejected invalid request ending with: ", ((int)*http_buff)); HTTPRequestFailed(); //TODO: add correct code return false; } break; default: - LogPrint(eLogError,"--- HTTP Proxy invalid state: ", m_state); + LogPrint(eLogError, "HTTPProxy: invalid state: ", m_state); HTTPRequestFailed(); //TODO: add correct code 500 return false; } @@ -249,11 +249,11 @@ namespace proxy void HTTPProxyHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { - LogPrint(eLogDebug,"--- HTTP Proxy sock recv: ", len); + LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes"); if(ecode) { - LogPrint(eLogWarning," --- HTTP Proxy sock recv got error: ", ecode); - Terminate(); + LogPrint(eLogWarning, "HTTPProxy: sock recv got error: ", ecode); + Terminate(); return; } @@ -261,7 +261,7 @@ namespace proxy { if (m_state == DONE) { - LogPrint(eLogInfo,"--- HTTP Proxy requested: ", m_url); + LogPrint(eLogDebug, "HTTPProxy: requested: ", m_url); GetOwner()->CreateStream (std::bind (&HTTPProxyHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_address, m_port); } @@ -273,13 +273,9 @@ namespace proxy void HTTPProxyHandler::SentHTTPFailed(const boost::system::error_code & ecode) { - if (!ecode) - Terminate(); - else - { - LogPrint (eLogError,"--- HTTP Proxy Closing socket after sending failure because: ", ecode.message ()); - Terminate(); - } + if (ecode) + LogPrint (eLogError, "HTTPProxy: Closing socket after sending failure because: ", ecode.message ()); + Terminate(); } void HTTPProxyHandler::HandleStreamRequestComplete (std::shared_ptr stream) @@ -287,7 +283,7 @@ namespace proxy if (stream) { if (Kill()) return; - LogPrint (eLogInfo,"--- HTTP Proxy New I2PTunnel connection"); + LogPrint (eLogInfo, "HTTPProxy: New I2PTunnel connection"); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); @@ -295,7 +291,7 @@ namespace proxy } else { - LogPrint (eLogError,"--- HTTP Proxy Issue 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(); // TODO: Send correct error message host unreachable } } diff --git a/I2PControl.cpp b/I2PControl.cpp index 776cfe86..23485c58 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -205,7 +205,7 @@ namespace client } if (ss.eof ()) { - LogPrint (eLogError, "Malformed I2PControl request. HTTP header expected"); + LogPrint (eLogError, "I2PControl: malformed request, HTTP header expected"); return; // TODO: } std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read @@ -216,7 +216,7 @@ namespace client } } #if GCC47_BOOST149 - LogPrint (eLogError, "json_read is not supported due bug in boost 1.49 with gcc 4.7"); + LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7"); #else boost::property_tree::ptree pt; boost::property_tree::read_json (ss, pt); @@ -233,16 +233,16 @@ namespace client SendResponse (socket, buf, response, isHtml); } else - LogPrint (eLogWarning, "Unknown I2PControl method ", method); + LogPrint (eLogWarning, "I2PControl: unknown method ", method); #endif } catch (std::exception& ex) { - LogPrint (eLogError, "I2PControl handle request: ", ex.what ()); + LogPrint (eLogError, "I2PControl: exception when handle request: ", ex.what ()); } catch (...) { - LogPrint (eLogError, "I2PControl handle request unknown exception"); + LogPrint (eLogError, "I2PControl: handle request unknown exception"); } } } @@ -527,7 +527,7 @@ namespace client // save key if ((f = fopen (key_path, "wb")) != NULL) { - LogPrint (eLogInfo, "I2PControl: saving cert key to : ", key_path); + LogPrint (eLogInfo, "I2PControl: saving cert key to ", key_path); PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); fclose (f); } else { diff --git a/I2PService.cpp b/I2PService.cpp index 0715a39f..85582c0e 100644 --- a/I2PService.cpp +++ b/I2PService.cpp @@ -28,7 +28,7 @@ namespace client m_LocalDestination->CreateStream (streamRequestComplete, identHash, port); else { - LogPrint (eLogWarning, "Remote destination ", dest, " not found"); + LogPrint (eLogWarning, "I2PService: Remote destination ", dest, " not found"); streamRequestComplete (nullptr); } } @@ -57,7 +57,7 @@ namespace client { if (!ecode) { - LogPrint(eLogDebug,"--- ",GetName()," accepted"); + LogPrint(eLogDebug, "I2PService: ", GetName(), " accepted"); auto handler = CreateHandler(socket); if (handler) { @@ -71,7 +71,7 @@ namespace client else { if (ecode != boost::asio::error::operation_aborted) - LogPrint (eLogError,"--- ",GetName()," Closing socket on accept because: ", ecode.message ()); + LogPrint (eLogError, "I2PService: ", GetName(), " closing socket on accept because: ", ecode.message ()); } } diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 81b6bdc2..0ac2e74b 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -503,12 +503,13 @@ namespace transport void NTCPSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { - if (ecode) - { - LogPrint (eLogInfo, "NTCP: Read error: ", ecode.message ()); - if (!m_NumReceivedBytes) m_Server.Ban (m_ConnectedFrom); + if (ecode) { + if (ecode != boost::asio::error::operation_aborted) + LogPrint (eLogDebug, "NTCP: Read error: ", ecode.message ()); + if (!m_NumReceivedBytes) + m_Server.Ban (m_ConnectedFrom); //if (ecode != boost::asio::error::operation_aborted) - Terminate (); + Terminate (); } else { @@ -736,7 +737,7 @@ namespace transport { if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogWarning, "NTCP: No activity for ", NTCP_TERMINATION_TIMEOUT, " seconds"); + LogPrint (eLogDebug, "NTCP: No activity for ", NTCP_TERMINATION_TIMEOUT, " seconds"); //Terminate (); m_Socket.close ();// invoke Terminate () from HandleReceive } From b2108ff2d0c7203c10a9bfce1c1837ec8c71e74a Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0938/6300] * fix flags on std::ifstream --- AddressBook.cpp | 6 +++--- RouterContext.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 62cf77dd..1c2f3e24 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -129,7 +129,7 @@ namespace client { int num = 0; auto filename = GetPath () / "addresses.csv"; - std::ifstream f (filename.string (), std::ofstream::in); // in text mode + std::ifstream f (filename.string (), std::ifstream::in); // in text mode if (f.is_open ()) { addresses.clear (); @@ -321,7 +321,7 @@ namespace client } // try hosts.txt first - std::ifstream f (i2p::util::filesystem::GetFullPath ("hosts.txt").c_str (), std::ofstream::in); // in text mode + std::ifstream f (i2p::util::filesystem::GetFullPath ("hosts.txt").c_str (), std::ifstream::in); // in text mode if (f.is_open ()) { LoadHostsFromStream (f); @@ -384,7 +384,7 @@ namespace client { if (!m_Subscriptions.size ()) { - std::ifstream f (i2p::util::filesystem::GetFullPath ("subscriptions.txt").c_str (), std::ofstream::in); // in text mode + std::ifstream f (i2p::util::filesystem::GetFullPath ("subscriptions.txt").c_str (), std::ifstream::in); // in text mode if (f.is_open ()) { std::string s; diff --git a/RouterContext.cpp b/RouterContext.cpp index 3dc79027..90961948 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -292,7 +292,7 @@ namespace i2p bool RouterContext::Load () { - std::ifstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ifstream::binary | std::ofstream::in); + std::ifstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ifstream::binary | std::ifstream::in); if (!fk.is_open ()) return false; fk.seekg (0, std::ios::end); size_t len = fk.tellg(); From 6e986496071f3f7655628e0515fc653b18fdb68f Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0939/6300] * I2PControl: send valid error response, instead closing connection --- I2PControl.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 23485c58..1ea6c8aa 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -215,8 +215,12 @@ namespace client ss.write (buf->data (), bytes_transferred); } } + std::ostringstream response; #if GCC47_BOOST149 LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7"); + response << "{\"id\":null,\"error\":"; + response << "{\"code\":-32603,\"message\":\"JSON requests is not supported with this version of boost\"},"; + response << "\"jsonrpc\":\"2.0\"}"; #else boost::property_tree::ptree pt; boost::property_tree::read_json (ss, pt); @@ -226,15 +230,17 @@ namespace client auto it = m_MethodHandlers.find (method); if (it != m_MethodHandlers.end ()) { - std::ostringstream response; response << "{\"id\":" << id << ",\"result\":{"; (this->*(it->second))(pt.get_child ("params"), response); response << "},\"jsonrpc\":\"2.0\"}"; - SendResponse (socket, buf, response, isHtml); - } - else + } else { LogPrint (eLogWarning, "I2PControl: unknown method ", method); + response << "{\"id\":null,\"error\":"; + response << "{\"code\":-32601,\"message\":\"Method not found\"},"; + response << "\"jsonrpc\":\"2.0\"}"; + } #endif + SendResponse (socket, buf, response, isHtml); } catch (std::exception& ex) { From f24054100ef3d5d0bab4d73c17ef72d3e61ae5fc Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0940/6300] * new i2p::fs implementation --- FS.cpp | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ FS.h | 143 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 332 insertions(+) create mode 100644 FS.cpp create mode 100644 FS.h diff --git a/FS.cpp b/FS.cpp new file mode 100644 index 00000000..758b81c8 --- /dev/null +++ b/FS.cpp @@ -0,0 +1,189 @@ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include + +#include "Base.h" +#include "FS.h" +#include "Log.h" + +namespace i2p { +namespace fs { + std::string appName = "i2pd"; + std::string dataDir = ""; +#ifdef _WIN32 + std::string dirSep = "\\"; +#else + std::string dirSep = "/"; +#endif + HashedStorage NetDB("netDb", "r", "routerInfo-", "dat"); + HashedStorage Peers("peerProfiles", "p", "profile-", "txt"); + ABookStorage ABook("addressbook", "b", "", "b32"); + + static const char T32[32] = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'k', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 't', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '2', '3', '4', '5', '6', '7', + }; + static const char T64[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '-', '~' + }; + + const std::string & GetAppName () { + return appName; + } + + void SetAppName (const std::string& name) { + appName = name; + } + + const std::string & GetDataDir () { + return dataDir; + } + + void DetectDataDir(const std::string & cmdline_param, bool isService) { + if (cmdline_param != "") { + dataDir = cmdline_param; + return; + } +#if defined(WIN32) || defined(_WIN32) + char localAppData[MAX_PATH]; + SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); + dataDir = std::string(localAppData) + "\\" + appName; + return; +#elif defined(MAC_OSX) + char *home = getenv("HOME"); + dataDir = (home != NULL && strlen(home) > 0) ? home : ""; + dataDir += "/Library/Application Support/" + appName; + return; +#else /* other unix */ + char *home = getenv("HOME"); + if (isService) { + dataDir = "/var/lib/" + appName; + } else if (home != NULL && strlen(home) > 0) { + dataDir = std::string(home) + "/." + appName; + } else { + dataDir = "/tmp/" + appName; + } + return; +#endif + } + + bool Init() { + if (boost::filesystem::exists(dataDir)) + boost::filesystem::create_directory(dataDir); + std::string destinations = DataDirPath("destinations"); + if (boost::filesystem::exists(destinations)) + boost::filesystem::create_directory(destinations); + + NetDB.SetRoot(dataDir); + NetDB.Init(T64, 64); + Peers.SetRoot(dataDir); + Peers.Init(T64, 64); + ABook.SetRoot(dataDir); + ABook.Init(T32, 32); + return true; + } + + bool ReadDir(const std::string & path, std::vector & files) { + if (!boost::filesystem::exists(path)) + return false; + boost::filesystem::directory_iterator it(path); + boost::filesystem::directory_iterator end; + + for ( ; it != end; it++) { + if (!boost::filesystem::is_regular_file(it->status())) + continue; + files.push_back(it->path().string()); + } + + return true; + } + + bool Exists(const std::string & path) { + return boost::filesystem::exists(path); + } + + bool Remove(const std::string & path) { + if (!boost::filesystem::exists(path)) + return false; + return boost::filesystem::remove(path); + } + + void HashedStorage::SetRoot(const std::string &path) { + root = path + i2p::fs::dirSep + name; + } + + bool HashedStorage::Init(const char * chars, size_t count) { + if (!boost::filesystem::exists(root)) { + boost::filesystem::create_directory(root); + } + + for (size_t i = 0; i < count; i++) { + auto p = root + i2p::fs::dirSep + prefix1 + chars[i]; + if (boost::filesystem::exists(p)) + continue; + if (boost::filesystem::create_directory(p)) + continue; /* ^ throws exception on failure */ + return false; + } + return true; + } + + std::string HashedStorage::Path(const std::string & ident) { + std::string safe_ident = ident; + std::replace(safe_ident.begin(), safe_ident.end(), '/', '-'); + std::replace(safe_ident.begin(), safe_ident.end(), '\\', '-'); + + std::stringstream t(""); + t << this->root << i2p::fs::dirSep; + t << prefix1 << safe_ident[0] << i2p::fs::dirSep; + t << prefix2 << safe_ident << "." << suffix; + + return t.str(); + } + + void HashedStorage::Remove(const std::string & ident) { + std::string path = Path(ident); + if (!boost::filesystem::exists(path)) + return; + boost::filesystem::remove(path); + } + + void HashedStorage::Traverse(std::vector & files) { + boost::filesystem::path p(root); + boost::filesystem::recursive_directory_iterator it(p); + boost::filesystem::recursive_directory_iterator end; + + for ( ; it != end; it++) { + if (!boost::filesystem::is_regular_file( it->status() )) + continue; + const std::string & t = it->path().string(); + files.push_back(t); + } + } + + std::string ABookStorage::IndexPath() { + std::string path = root + i2p::fs::dirSep + "addresses.csv"; + return path; + } + + HashedStorage & GetNetDB() { return NetDB; } + HashedStorage & GetPeerProfiles() { return Peers; } + ABookStorage & GetAddressBook() { return ABook; } +} // fs +} // i2p diff --git a/FS.h b/FS.h new file mode 100644 index 00000000..80b07353 --- /dev/null +++ b/FS.h @@ -0,0 +1,143 @@ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include + +namespace i2p { +namespace fs { + extern std::string dirSep; + + /** + * @brief Class to work with NetDb & Router profiles + * + * Usage: + * + * const char alphabet[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; + * auto h = HashedStorage("name", "y", "z-", ".txt"); + * h.SetRoot("/tmp/hs-test"); + * h.Init(alphabet, 8); <- creates needed dirs, 8 is size of alphabet + * h.Path("abcd"); <- returns /tmp/hs-test/name/ya/z-abcd.txt + * h.Remove("abcd"); <- removes /tmp/hs-test/name/ya/z-abcd.txt, if it exists + * std::vector files; + * h.Traverse(files); <- finds all files in storage and saves in given vector + */ + class HashedStorage { + protected: + std::string root; + std::string name; + std::string prefix1; + std::string prefix2; + std::string suffix; + + public: + HashedStorage(const char *n, const char *p1, const char *p2, const char *s): + name(n), prefix1(p1), prefix2(p2), suffix(s) {}; + + bool Init(const char* chars, size_t cnt); + const std::string & GetRoot() { return this->root; } + void SetRoot(const std::string & path); + std::string Path(const std::string & ident); + void Remove(const std::string & ident); + void Traverse(std::vector & files); + }; + + /** @brief Slightly extended HashedStorage */ + class ABookStorage : public HashedStorage { + public: + ABookStorage(const char *n, const char *p1, const char *p2, const char *s): + HashedStorage(n, p1, p2, s) {}; + + std::string IndexPath(); + }; + + /** @brief Returns current application name, default 'i2pd' */ + const std::string & GetAppName (); + /** @brief Set applicaton name, affects autodetection of datadir */ + void SetAppName (const std::string& name); + + /** @brief Returns datadir path */ + const std::string & GetDataDir(); + + /** + * @brief Set datadir either from cmdline option or using autodetection + * @param cmdline_param Value of cmdline parameter --datadir= + * @param isService Value of cmdline parameter --service + * + * Examples of autodetected paths: + * + * Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\ + * Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\ + * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/ + * Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/ + */ + void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); + + /** + * @brief Create subdirectories inside datadir + */ + bool Init(); + + /** + * @brief Get list of files in directory + * @param path Path to directory + * @param files Vector to store found files + * @return true on success and false if directory not exists + */ + bool ReadDir(const std::string & path, std::vector & files); + + /** + * @brief Remove file with given path + * @param path Absolute path to file + * @return true on success, false if file not exists, throws exception on error + */ + bool Remove(const std::string & path); + + /** + * @brief Check existence of file + * @param path Absolute path to file + * @return true if file exists, false otherwise + */ + bool Exists(const std::string & path); + + template + void _ExpandPath(std::stringstream & path, T c) { + path << i2p::fs::dirSep << c; + } + + template + void _ExpandPath(std::stringstream & path, T c, Other ... other) { + _ExpandPath(path, c); + _ExpandPath(path, other ...); + } + + /** + * @brief Get path relative to datadir + * + * Examples (with datadir = "/tmp/i2pd"): + * + * i2p::fs::Path("test") -> '/tmp/i2pd/test' + * i2p::fs::Path("test", "file.txt") -> '/tmp/i2pd/test/file.txt' + */ + template + std::string DataDirPath(Other ... components) { + std::stringstream s(""); + s << i2p::fs::GetDataDir(); + _ExpandPath(s, components ...); + + return s.str(); + } + + /* accessors */ + HashedStorage & GetNetDB(); + HashedStorage & GetPeerProfiles(); + ABookStorage & GetAddressBook(); +} // fs +} // i2p From 6f4271c054c033785c42d745db8140fad0cf710c Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0941/6300] * update buildsystems --- build/CMakeLists.txt | 1 + filelist.mk | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 646e8a6e..7227f4ea 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -23,6 +23,7 @@ set (LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/I2NPProtocol.cpp" "${CMAKE_SOURCE_DIR}/Identity.cpp" "${CMAKE_SOURCE_DIR}/LeaseSet.cpp" + "${CMAKE_SOURCE_DIR}/FS.cpp" "${CMAKE_SOURCE_DIR}/Log.cpp" "${CMAKE_SOURCE_DIR}/NTCPSession.cpp" "${CMAKE_SOURCE_DIR}/NetDbRequests.cpp" diff --git a/filelist.mk b/filelist.mk index 1d46b8fc..4417ba9b 100644 --- a/filelist.mk +++ b/filelist.mk @@ -4,7 +4,7 @@ LIB_SRC = \ Reseed.cpp RouterContext.cpp RouterInfo.cpp Signature.cpp SSU.cpp \ SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp TransitTunnel.cpp \ Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp TunnelGateway.cpp \ - Destination.cpp Base.cpp I2PEndian.cpp Config.cpp util.cpp api.cpp + Destination.cpp Base.cpp I2PEndian.cpp FS.cpp Config.cpp util.cpp api.cpp LIB_CLIENT_SRC = \ AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \ From 6d7449349159061e8254a6ac8c1d69e5ee5990f0 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0942/6300] * i2p::fs migration: NetDb.* --- NetDb.cpp | 205 +++++++++++++++++++----------------------------------- NetDb.h | 5 +- 2 files changed, 74 insertions(+), 136 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index fe1d6a45..9253dcdc 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -1,12 +1,13 @@ #include -#include "I2PEndian.h" #include #include #include #include #include +#include "I2PEndian.h" #include "Base.h" #include "Log.h" +#include "FS.h" #include "Timestamp.h" #include "I2NPProtocol.h" #include "Tunnel.h" @@ -22,7 +23,6 @@ namespace i2p { namespace data { - const char NetDb::m_NetDbPath[] = "netDb"; NetDb netdb; NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr) @@ -250,30 +250,6 @@ namespace data return it->second->SetUnreachable (unreachable); } - // TODO: Move to reseed and/or scheduled tasks. (In java version, scheduler fix this as well as sort RIs.) - bool NetDb::CreateNetDb(boost::filesystem::path directory) - { - LogPrint (eLogInfo, "NetDb: storage directory doesn't exist, trying to create it."); - if (!boost::filesystem::create_directory (directory)) - { - LogPrint (eLogError, "NetDb: failed to create directory ", directory); - return false; - } - - // list of chars might appear in base64 string - const char * chars = GetBase64SubstitutionTable (); // 64 bytes - for (int i = 0; i < 64; i++) - { - auto p = directory / (std::string ("r") + chars[i]); - if (!boost::filesystem::exists (p) && !boost::filesystem::create_directory (p)) - { - LogPrint (eLogError, "NetDb: failed to create directory ", p); - return false; - } - } - return true; - } - void NetDb::Reseed () { if (!m_Reseeder) @@ -288,145 +264,108 @@ namespace data LogPrint (eLogWarning, "NetDb: failed to reseed after 10 attempts"); } + bool NetDb::LoadRouterInfo (const std::string & path) + { + auto r = std::make_shared(path); + if (r->GetRouterIdentity () && !r->IsUnreachable () && + (!r->UsesIntroducer () || m_LastLoad < r->GetTimestamp () + 3600*1000LL)) // 1 hour + { + r->DeleteBuffer (); + r->ClearProperties (); // properties are not used for regular routers + m_RouterInfos[r->GetIdentHash ()] = r; + if (r->IsFloodfill ()) + m_Floodfills.push_back (r); + } else { + LogPrint(eLogWarning, "NetDb: Can't load RI from ", path, ", delete"); + i2p::fs::Remove(path); + } + return true; + } + void NetDb::Load () { - boost::filesystem::path p(i2p::util::filesystem::GetDataDir() / m_NetDbPath); - if (!boost::filesystem::exists (p)) - { - // seems netDb doesn't exist yet - if (!CreateNetDb(p)) return; - } // make sure we cleanup netDb from previous attempts m_RouterInfos.clear (); m_Floodfills.clear (); - // load routers now - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); - int numRouters = 0; - boost::filesystem::directory_iterator end; - for (boost::filesystem::directory_iterator it (p); it != end; ++it) - { - if (boost::filesystem::is_directory (it->status())) - { - for (boost::filesystem::directory_iterator it1 (it->path ()); it1 != end; ++it1) - { -#if BOOST_VERSION > 10500 - const std::string& fullPath = it1->path().string(); -#else - const std::string& fullPath = it1->path(); -#endif - auto r = std::make_shared(fullPath); - if (r->GetRouterIdentity () && !r->IsUnreachable () && - (!r->UsesIntroducer () || ts < r->GetTimestamp () + 3600*1000LL)) // 1 hour - { - r->DeleteBuffer (); - r->ClearProperties (); // properties are not used for regular routers - m_RouterInfos[r->GetIdentHash ()] = r; - if (r->IsFloodfill ()) - m_Floodfills.push_back (r); - numRouters++; - } - else - { - if (boost::filesystem::exists (fullPath)) - boost::filesystem::remove (fullPath); - } - } - } - } - LogPrint (eLogInfo, "NetDb: ", numRouters, " routers loaded (", m_Floodfills.size (), " floodfils)"); + m_LastLoad = i2p::util::GetSecondsSinceEpoch(); + std::vector files; + i2p::fs::GetNetDB().Traverse(files); + for (auto path : files) + LoadRouterInfo(path); + + LogPrint (eLogInfo, "NetDb: ", m_RouterInfos.size(), " routers loaded (", m_Floodfills.size (), " floodfils)"); } void NetDb::SaveUpdated () { - auto GetFilePath = [](const boost::filesystem::path& directory, const RouterInfo * routerInfo) - { - std::string s(routerInfo->GetIdentHashBase64()); - return directory / (std::string("r") + s[0]) / ("routerInfo-" + s + ".dat"); - }; - - boost::filesystem::path fullDirectory (i2p::util::filesystem::GetDataDir() / m_NetDbPath); - int count = 0, deletedCount = 0; + int updatedCount = 0, deletedCount = 0; auto total = m_RouterInfos.size (); - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); + for (auto it: m_RouterInfos) { - if (it.second->IsUpdated ()) - { - std::string f = GetFilePath(fullDirectory, it.second.get()).string(); - it.second->SaveToFile (f); + std::string ident = it.second->GetIdentHashBase64(); + std::string path = i2p::fs::GetNetDB().Path(ident); + if (it.second->IsUpdated ()) { + it.second->SaveToFile (path); it.second->SetUpdated (false); it.second->SetUnreachable (false); it.second->DeleteBuffer (); - count++; + updatedCount++; + continue; } - else - { + // find & mark unreachable routers + if (it.second->UsesIntroducer () && ts > it.second->GetTimestamp () + 3600*1000LL) { // RouterInfo expires after 1 hour if uses introducer - if (it.second->UsesIntroducer () && ts > it.second->GetTimestamp () + 3600*1000LL) // 1 hour - it.second->SetUnreachable (true); - else if (total > 75 && ts > (i2p::context.GetStartupTime () + 600)*1000LL) // routers don't expire if less than 25 or uptime is less than 10 minutes - { - if (i2p::context.IsFloodfill ()) - { - if (ts > it.second->GetTimestamp () + 3600*1000LL) // 1 hours - { - it.second->SetUnreachable (true); - total--; - } + it.second->SetUnreachable (true); + } else if (total > 75 && ts > (i2p::context.GetStartupTime () + 600)*1000LL) { + // routers don't expire if less than 25 or uptime is less than 10 minutes + if (i2p::context.IsFloodfill ()) { + if (ts > it.second->GetTimestamp () + 3600*1000LL) { // 1 hour + it.second->SetUnreachable (true); + total--; } - else if (total > 300) - { - if (ts > it.second->GetTimestamp () + 30*3600*1000LL) // 30 hours - { - it.second->SetUnreachable (true); - total--; - } + } else if (total > 300) { + if (ts > it.second->GetTimestamp () + 30*3600*1000LL) { // 30 hours + it.second->SetUnreachable (true); + total--; } - else if (total > 120) - { - if (ts > it.second->GetTimestamp () + 72*3600*1000LL) // 72 hours - { - it.second->SetUnreachable (true); - total--; - } + } else if (total > 120) { + if (ts > it.second->GetTimestamp () + 72*3600*1000LL) { // 72 hours + it.second->SetUnreachable (true); + total--; } } - - if (it.second->IsUnreachable ()) - { - total--; - // delete RI file - if (boost::filesystem::exists (GetFilePath (fullDirectory, it.second.get ()))) - { - boost::filesystem::remove (GetFilePath (fullDirectory, it.second.get ())); - deletedCount++; - } - // delete from floodfills list - if (it.second->IsFloodfill ()) - { - std::unique_lock l(m_FloodfillsMutex); - m_Floodfills.remove (it.second); - } + } + + if (it.second->IsUnreachable ()) { + total--; + // delete RI file + i2p::fs::GetNetDB().Remove(ident); + deletedCount++; + // delete from floodfills list + if (it.second->IsFloodfill ()) { + std::unique_lock l(m_FloodfillsMutex); + m_Floodfills.remove (it.second); } - } - } - if (count > 0) - LogPrint (eLogInfo, "NetDb: ", count, " new/updated routers saved"); + } + } // m_RouterInfos iteration + if (updatedCount > 0) + LogPrint (eLogInfo, "NetDb: saved ", updatedCount, " new/updated routers"); if (deletedCount > 0) { - LogPrint (eLogDebug, "NetDb: ", deletedCount, " routers deleted"); + LogPrint (eLogInfo, "NetDb: deleting ", deletedCount, " unreachable routers"); // clean up RouterInfos table std::unique_lock l(m_RouterInfosMutex); for (auto it = m_RouterInfos.begin (); it != m_RouterInfos.end ();) { - if (it->second->IsUnreachable ()) - { + if (it->second->IsUnreachable ()) { it->second->SaveProfile (); it = m_RouterInfos.erase (it); + continue; } - else - it++; + it++; } } } diff --git a/NetDb.h b/NetDb.h index 237e5be1..855faa3c 100644 --- a/NetDb.h +++ b/NetDb.h @@ -8,7 +8,6 @@ #include #include #include -#include #include "Base.h" #include "Queue.h" #include "I2NPProtocol.h" @@ -23,7 +22,6 @@ namespace i2p { namespace data { - class NetDb { public: @@ -69,8 +67,8 @@ namespace data private: - bool CreateNetDb(boost::filesystem::path directory); void Load (); + bool LoadRouterInfo (const std::string & path); void SaveUpdated (); void Run (); // exploratory thread void Explore (int numDestinations); @@ -90,6 +88,7 @@ namespace data std::list > m_Floodfills; bool m_IsRunning; + uint64_t m_LastLoad; std::thread * m_Thread; i2p::util::Queue > m_Queue; // of I2NPDatabaseStoreMsg From 2b137b43e6d305cdb9d440fbeb65e5abe49d86b4 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0943/6300] * i2p::fs migration: I2PControl.* --- I2PControl.cpp | 12 +++++------- I2PControl.h | 3 --- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 1ea6c8aa..907fa2fd 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -13,6 +13,7 @@ #include #endif +#include "FS.h" #include "Log.h" #include "Config.h" #include "NetDb.h" @@ -39,15 +40,12 @@ namespace client // certificate / keys std::string i2pcp_crt; i2p::config::GetOption("i2pcontrol.cert", i2pcp_crt); std::string i2pcp_key; i2p::config::GetOption("i2pcontrol.key", i2pcp_key); - auto path = GetPath (); - // TODO: move this to i2p::fs::expand + if (i2pcp_crt.at(0) != '/') - i2pcp_crt.insert(0, (path / "/").string()); + i2pcp_crt = i2p::fs::DataDirPath(i2pcp_crt); if (i2pcp_key.at(0) != '/') - i2pcp_key.insert(0, (path / "/").string()); - if (!boost::filesystem::exists (i2pcp_crt) || - !boost::filesystem::exists (i2pcp_key)) - { + i2pcp_key = i2p::fs::DataDirPath(i2pcp_key); + if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) { LogPrint (eLogInfo, "I2PControl: creating new certificate for control connection"); CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str()); } else { diff --git a/I2PControl.h b/I2PControl.h index 18592068..714d3aa5 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -12,8 +12,6 @@ #include #include #include -#include -#include "util.h" namespace i2p { @@ -52,7 +50,6 @@ namespace client void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); - boost::filesystem::path GetPath () const { return i2p::util::filesystem::GetDefaultDataDir(); }; void CreateCertificate (const char *crt_path, const char *key_path); private: From bfcb6f577f4a6d63e778f1c46a6d7461a6a57033 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0944/6300] * i2p::fs migration: Profiling.* --- Profiling.cpp | 186 ++++++++++++++++++++------------------------------ Profiling.h | 2 - 2 files changed, 73 insertions(+), 115 deletions(-) diff --git a/Profiling.cpp b/Profiling.cpp index 9bfd4de9..d868298f 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -1,8 +1,8 @@ -#include +#include #include #include #include "Base.h" -#include "util.h" +#include "FS.h" #include "Log.h" #include "Profiling.h" @@ -41,101 +41,69 @@ namespace data boost::property_tree::ptree pt; pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime)); pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation); - pt.put_child (PEER_PROFILE_SECTION_USAGE, usage); + pt.put_child (PEER_PROFILE_SECTION_USAGE, usage); // save to file - auto path = i2p::util::filesystem::GetDefaultDataDir() / PEER_PROFILES_DIRECTORY; - if (!boost::filesystem::exists (path)) - { - // Create directory is necessary - if (!boost::filesystem::create_directory (path)) - { - LogPrint (eLogError, "Failed to create directory ", path); - return; - } - const char * chars = GetBase64SubstitutionTable (); // 64 bytes - for (int i = 0; i < 64; i++) - { - auto path1 = path / (std::string ("p") + chars[i]); - if (!boost::filesystem::exists (path1) && !boost::filesystem::create_directory (path1)) - { - LogPrint (eLogError, "Failed to create directory ", path1); - return; - } - } + std::string ident = m_IdentHash.ToBase64 (); + std::string path = i2p::fs::GetPeerProfiles().Path(ident); + + try { + boost::property_tree::write_ini (path, pt); + } catch (std::exception& ex) { + /* boost exception verbose enough */ + LogPrint (eLogError, "Profiling: ", ex.what ()); } - std::string base64 = m_IdentHash.ToBase64 (); - path = path / (std::string ("p") + base64[0]); - auto filename = path / (std::string (PEER_PROFILE_PREFIX) + base64 + ".txt"); - try - { - boost::property_tree::write_ini (filename.string (), pt); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "Can't write ", filename, ": ", ex.what ()); - } - } + } void RouterProfile::Load () { - std::string base64 = m_IdentHash.ToBase64 (); - auto path = i2p::util::filesystem::GetDefaultDataDir() / PEER_PROFILES_DIRECTORY; - path /= std::string ("p") + base64[0]; - auto filename = path / (std::string (PEER_PROFILE_PREFIX) + base64 + ".txt"); - if (boost::filesystem::exists (filename)) - { - boost::property_tree::ptree pt; - try - { - boost::property_tree::read_ini (filename.string (), pt); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "Can't read ", filename, ": ", ex.what ()); - return; - } - try - { - auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, ""); - if (t.length () > 0) - m_LastUpdateTime = boost::posix_time::time_from_string (t); - if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT) - { - try - { - // read participations - auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION); - m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0); - m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0); - m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0); - } - catch (boost::property_tree::ptree_bad_path& ex) - { - LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_PARTICIPATION); - } - try - { - // read usage - auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE); - m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0); - m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0); - } - catch (boost::property_tree::ptree_bad_path& ex) - { - LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_USAGE); - } + std::string ident = m_IdentHash.ToBase64 (); + std::string path = i2p::fs::GetPeerProfiles().Path(ident); + boost::property_tree::ptree pt; + + if (!i2p::fs::Exists(path)) { + LogPrint(eLogWarning, "Profiling: no profile yet for ", ident); + return; + } + + try { + boost::property_tree::read_ini (path, pt); + } catch (std::exception& ex) { + /* boost exception verbose enough */ + LogPrint (eLogError, "Profiling: ", ex.what ()); + return; + } + + try { + auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, ""); + if (t.length () > 0) + m_LastUpdateTime = boost::posix_time::time_from_string (t); + if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT) { + try { + // read participations + auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION); + m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0); + m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0); + m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0); + } catch (boost::property_tree::ptree_bad_path& ex) { + LogPrint (eLogWarning, "Profiling: Missing section ", PEER_PROFILE_SECTION_PARTICIPATION, " in profile for ", ident); } - else - *this = RouterProfile (m_IdentHash); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "Can't read profile ", base64, " :", ex.what ()); - } - } - } - + try { + // read usage + auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE); + m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0); + m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0); + } catch (boost::property_tree::ptree_bad_path& ex) { + LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_USAGE, " in profile for ", ident); + } + } else { + *this = RouterProfile (m_IdentHash); + } + } catch (std::exception& ex) { + LogPrint (eLogError, "Profiling: Can't read profile ", ident, " :", ex.what ()); + } + } + void RouterProfile::TunnelBuildResponse (uint8_t ret) { UpdateTime (); @@ -186,29 +154,21 @@ namespace data void DeleteObsoleteProfiles () { - int num = 0; - auto ts = boost::posix_time::second_clock::local_time(); - boost::filesystem::path p (i2p::util::filesystem::GetDataDir()/PEER_PROFILES_DIRECTORY); - if (boost::filesystem::exists (p)) - { - boost::filesystem::directory_iterator end; - for (boost::filesystem::directory_iterator it (p); it != end; ++it) - { - if (boost::filesystem::is_directory (it->status())) - { - for (boost::filesystem::directory_iterator it1 (it->path ()); it1 != end; ++it1) - { - auto lastModified = boost::posix_time::from_time_t (boost::filesystem::last_write_time (it1->path ())); - if ((ts - lastModified).hours () >= PEER_PROFILE_EXPIRATION_TIMEOUT) - { - boost::filesystem::remove (it1->path ()); - num++; - } - } - } - } + struct stat st; + std::time_t now = std::time(nullptr); + + std::vector files; + i2p::fs::GetPeerProfiles().Traverse(files); + for (auto path: files) { + if (stat(path.c_str(), &st) != 0) { + LogPrint(eLogWarning, "Profiling: Can't stat(): ", path); + continue; + } + if (((now - st.st_mtime) / 3600) >= PEER_PROFILE_EXPIRATION_TIMEOUT) { + LogPrint(eLogDebug, "Profiling: removing expired peer profile: ", path); + i2p::fs::Remove(path); + } } - LogPrint (eLogInfo, num, " obsolete profiles deleted"); - } + } } } diff --git a/Profiling.h b/Profiling.h index 0690d6cb..3a65714d 100644 --- a/Profiling.h +++ b/Profiling.h @@ -9,8 +9,6 @@ namespace i2p { namespace data { - const char PEER_PROFILES_DIRECTORY[] = "peerProfiles"; - const char PEER_PROFILE_PREFIX[] = "profile-"; // sections const char PEER_PROFILE_SECTION_PARTICIPATION[] = "participation"; const char PEER_PROFILE_SECTION_USAGE[] = "usage"; From ddd8d4aeb2549a3ceb5d916d348a873613225030 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0945/6300] * i2p::fs migration: AddressBook.* --- AddressBook.cpp | 223 ++++++++++++++++++------------------------------ AddressBook.h | 2 - 2 files changed, 83 insertions(+), 142 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 1c2f3e24..540fd74c 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -5,11 +5,11 @@ #include #include #include -#include #include #include "Base.h" #include "util.h" #include "Identity.h" +#include "FS.h" #include "Log.h" #include "NetDb.h" #include "ClientContext.h" @@ -19,143 +19,96 @@ namespace i2p { namespace client { - + // TODO: this is actually proxy class class AddressBookFilesystemStorage: public AddressBookStorage { public: - - AddressBookFilesystemStorage (); + AddressBookFilesystemStorage () {}; std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const; void AddAddress (std::shared_ptr address); void RemoveAddress (const i2p::data::IdentHash& ident); int Load (std::map& addresses); int Save (const std::map& addresses); - - private: - - boost::filesystem::path GetPath () const - { - return i2p::util::filesystem::GetDefaultDataDir() / "addressbook"; - } - boost::filesystem::path GetAddressPath (const i2p::data::IdentHash& ident) const - { - auto b32 = ident.ToBase32(); - return GetPath () / (std::string ("b") + b32[0]) / (b32 + ".b32"); - } }; - AddressBookFilesystemStorage::AddressBookFilesystemStorage () - { - auto path = GetPath (); - if (!boost::filesystem::exists (path)) - { - // Create directory is necessary - if (!boost::filesystem::create_directory (path)) - LogPrint (eLogError, "Addressbook: failed to create addressbook directory"); - } - - } - std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const { - auto filename = GetAddressPath (ident); - if (!boost::filesystem::exists (filename)) - { - boost::filesystem::create_directory (filename.parent_path ()); - // try to find in main folder - auto filename1 = GetPath () / (ident.ToBase32 () + ".b32"); - if (!boost::filesystem::exists (filename1)) - { - boost::system::error_code ec; - boost::filesystem::rename (filename1, filename, ec); - if (ec) - LogPrint (eLogError, "Addresbook: couldn't move file ", ec.message ()); - } - else - return nullptr; // address doesn't exist - } - std::ifstream f(filename.string (), std::ifstream::binary); - if (f.is_open ()) - { - f.seekg (0,std::ios::end); - size_t len = f.tellg (); - if (len < i2p::data::DEFAULT_IDENTITY_SIZE) - { - LogPrint (eLogError, "Addresbook: File ", filename, " is too short. ", len); - return nullptr; - } - f.seekg(0, std::ios::beg); - uint8_t * buf = new uint8_t[len]; - f.read((char *)buf, len); - auto address = std::make_shared(buf, len); - delete[] buf; - return address; - } - else + std::string filename = i2p::fs::GetAddressBook().Path(ident.ToBase32()); + std::ifstream f(filename, std::ifstream::binary); + if (!f.is_open ()) { + LogPrint(eLogDebug, "Addressbook: Requested, but not found: ", filename); return nullptr; + } + + f.seekg (0,std::ios::end); + size_t len = f.tellg (); + if (len < i2p::data::DEFAULT_IDENTITY_SIZE) { + LogPrint (eLogError, "Addresbook: File ", filename, " is too short: ", len); + return nullptr; + } + f.seekg(0, std::ios::beg); + uint8_t * buf = new uint8_t[len]; + f.read((char *)buf, len); + auto address = std::make_shared(buf, len); + delete[] buf; + return address; } void AddressBookFilesystemStorage::AddAddress (std::shared_ptr address) { - auto filename = GetAddressPath (address->GetIdentHash ()); - std::ofstream f (filename.string (), std::ofstream::binary | std::ofstream::out); - if (!f.is_open ()) - { - // create subdirectory - if (boost::filesystem::create_directory (filename.parent_path ())) - f.open (filename.string (), std::ofstream::binary | std::ofstream::out); // and try to open again - } - if (f.is_open ()) - { - size_t len = address->GetFullLen (); - uint8_t * buf = new uint8_t[len]; - address->ToBuffer (buf, len); - f.write ((char *)buf, len); - delete[] buf; + std::string path = i2p::fs::GetAddressBook().Path( address->GetIdentHash().ToBase32() ); + std::ofstream f (path, std::ofstream::binary | std::ofstream::out); + if (!f.is_open ()) { + LogPrint (eLogError, "Addresbook: can't open file ", path); + return; } - else - LogPrint (eLogError, "Addresbook: can't open file ", filename); + size_t len = address->GetFullLen (); + uint8_t * buf = new uint8_t[len]; + address->ToBuffer (buf, len); + f.write ((char *)buf, len); + delete[] buf; } void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident) { - auto filename = GetAddressPath (ident); - if (boost::filesystem::exists (filename)) - boost::filesystem::remove (filename); + i2p::fs::GetAddressBook().Remove( ident.ToBase32() ); } int AddressBookFilesystemStorage::Load (std::map& addresses) { int num = 0; - auto filename = GetPath () / "addresses.csv"; - std::ifstream f (filename.string (), std::ifstream::in); // in text mode - if (f.is_open ()) - { - addresses.clear (); - while (!f.eof ()) - { - std::string s; - getline(f, s); - if (!s.length()) - continue; // skip empty line + std::string s; + std::string index = i2p::fs::GetAddressBook().IndexPath(); + std::ifstream f (index, std::ifstream::in); // in text mode - size_t pos = s.find(','); - if (pos != std::string::npos) - { - std::string name = s.substr(0, pos++); - std::string addr = s.substr(pos); - - i2p::data::IdentHash ident; - ident.FromBase32 (addr); - addresses[name] = ident; - num++; - } - } - LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded"); + if (f.is_open ()) { + LogPrint(eLogInfo, "Addressbook: using index file ", index); + } else { + LogPrint(eLogWarning, "Addressbook: Can't open ", index); + return 0; } - else - LogPrint (eLogWarning, "Addressbook: ", filename, " not found"); + + addresses.clear (); + while (!f.eof ()) { + getline(f, s); + if (!s.length()) + continue; // skip empty line + + std::size_t pos = s.find(','); + if (pos != std::string::npos) + { + std::string name = s.substr(0, pos++); + std::string addr = s.substr(pos); + + i2p::data::IdentHash ident; + ident.FromBase32 (addr); + addresses[name] = ident; + num++; + } + } + + LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded from storage"); return num; } @@ -167,24 +120,24 @@ namespace client } int num = 0; - auto filename = GetPath () / "addresses.csv"; - std::ofstream f (filename.string (), std::ofstream::out); // in text mode - if (f.is_open ()) - { - for (auto it: addresses) - { - f << it.first << "," << it.second.ToBase32 () << std::endl; - num++; - } - LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); + std::string index = i2p::fs::GetAddressBook().IndexPath(); + std::ofstream f (index, std::ofstream::out); // in text mode + + if (!f.is_open ()) { + LogPrint (eLogWarning, "Addressbook: Can't open ", index); + return 0; } - else - LogPrint (eLogError, "Addresbook: can't open file ", filename); + + for (auto it: addresses) { + f << it.first << "," << it.second.ToBase32 () << std::endl; + num++; + } + LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); return num; } //--------------------------------------------------------------------- - AddressBook::AddressBook (): m_Storage (nullptr), m_IsLoaded (false), m_IsDownloading (false), + AddressBook::AddressBook (): m_Storage(new AddressBookFilesystemStorage), m_IsLoaded (false), m_IsDownloading (false), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr) { } @@ -239,11 +192,6 @@ namespace client m_Subscriptions.clear (); } - AddressBookStorage * AddressBook::CreateStorage () - { - return new AddressBookFilesystemStorage (); - } - bool AddressBook::GetIdentHash (const std::string& address, i2p::data::IdentHash& ident) { auto pos = address.find(".b32.i2p"); @@ -287,8 +235,6 @@ namespace client { auto ident = std::make_shared(); ident->FromBase64 (base64); - if (!m_Storage) - m_Storage = CreateStorage (); m_Storage->AddAddress (ident); m_Addresses[address] = ident->GetIdentHash (); LogPrint (eLogInfo, "Addressbook: added ", address," -> ", ToAddress(ident->GetIdentHash ())); @@ -296,15 +242,11 @@ namespace client void AddressBook::InsertAddress (std::shared_ptr address) { - if (!m_Storage) - m_Storage = CreateStorage (); m_Storage->AddAddress (address); } std::shared_ptr AddressBook::GetAddress (const std::string& address) { - if (!m_Storage) - m_Storage = CreateStorage (); i2p::data::IdentHash ident; if (!GetIdentHash (address, ident)) return nullptr; return m_Storage->GetAddress (ident); @@ -312,16 +254,14 @@ namespace client void AddressBook::LoadHosts () { - if (!m_Storage) - m_Storage = CreateStorage (); if (m_Storage->Load (m_Addresses) > 0) { m_IsLoaded = true; return; } - // try hosts.txt first - std::ifstream f (i2p::util::filesystem::GetFullPath ("hosts.txt").c_str (), std::ifstream::in); // in text mode + // then try hosts.txt + std::ifstream f (i2p::fs::DataDirPath("hosts.txt"), std::ifstream::in); // in text mode if (f.is_open ()) { LoadHostsFromStream (f); @@ -384,7 +324,7 @@ namespace client { if (!m_Subscriptions.size ()) { - std::ifstream f (i2p::util::filesystem::GetFullPath ("subscriptions.txt").c_str (), std::ifstream::in); // in text mode + std::ifstream f (i2p::fs::DataDirPath ("subscriptions.txt"), std::ifstream::in); // in text mode if (f.is_open ()) { std::string s; @@ -443,7 +383,10 @@ namespace client if (ecode != boost::asio::error::operation_aborted) { auto dest = i2p::client::context.GetSharedLocalDestination (); - if (!dest) return; + if (!dest) { + LogPrint(eLogWarning, "Addressbook: missing local destination, skip subscription update"); + return; + } if (m_IsLoaded && !m_IsDownloading && dest->IsReady () && !m_Subscriptions.empty ()) { // pick random subscription @@ -495,7 +438,7 @@ namespace client }); if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) { - LogPrint (eLogError, "Subscription LeaseSet request timeout expired"); + LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired"); i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (ident); } } @@ -595,7 +538,7 @@ namespace client LogPrint (eLogError, "Addressbook: Can't resolve ", u.host_); if (!success) - LogPrint (eLogError, "Addressbook: download failed"); + LogPrint (eLogError, "Addressbook: download hosts.txt from ", m_Link, " failed"); m_Book.DownloadComplete (success); } diff --git a/AddressBook.h b/AddressBook.h index b50b9d9b..9ddce82a 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -10,7 +10,6 @@ #include #include #include "Base.h" -#include "util.h" #include "Identity.h" #include "Log.h" @@ -65,7 +64,6 @@ namespace client void StartSubscriptions (); void StopSubscriptions (); - AddressBookStorage * CreateStorage (); void LoadHosts (); void LoadSubscriptions (); From 79bf44b3f52bf26bd994ab226196439fff2e4180 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0946/6300] * i2p::fs migration: ClientContext, Destination, HTTPServer, Reseed, RouterContext --- ClientContext.cpp | 26 +++++++++++++------------- Destination.cpp | 43 ++++++++++++++++++++----------------------- HTTPServer.cpp | 3 ++- Reseed.cpp | 30 +++++++++++++++--------------- RouterContext.cpp | 9 +++++---- 5 files changed, 55 insertions(+), 56 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 6d05cde1..072e6e24 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -3,7 +3,7 @@ #include #include #include "Config.h" -#include "util.h" +#include "FS.h" #include "Log.h" #include "Identity.h" #include "ClientContext.h" @@ -152,10 +152,10 @@ namespace client m_SharedLocalDestination = nullptr; } - void ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType) + void ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType) { - std::string fullPath = i2p::util::filesystem::GetFullPath (filename); - std::ifstream s(fullPath.c_str (), std::ifstream::binary); + std::string fullPath = i2p::fs::DataDirPath (filename); + std::ifstream s(fullPath, std::ifstream::binary); if (s.is_open ()) { s.seekg (0, std::ios::end); @@ -256,14 +256,14 @@ namespace client void ClientContext::ReadTunnels () { boost::property_tree::ptree pt; - std::string pathTunnelsConfigFile = i2p::util::filesystem::GetTunnelsConfigFile().string(); - try - { - boost::property_tree::read_ini (pathTunnelsConfigFile, pt); - } - catch (std::exception& ex) - { - LogPrint (eLogWarning, "Clients: Can't read ", pathTunnelsConfigFile, ": ", ex.what ()); + std::string pathTunConf; + i2p::config::GetOption("tunconf", pathTunConf); + if (pathTunConf == "") + return; + try { + boost::property_tree::read_ini (pathTunConf, pt); + } catch (std::exception& ex) { + LogPrint (eLogWarning, "Clients: Can't read ", pathTunConf, ": ", ex.what ()); return; } @@ -351,7 +351,7 @@ namespace client numServerTunnels++; } else - LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", pathTunnelsConfigFile); + LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", pathTunConf); } catch (std::exception& ex) diff --git a/Destination.cpp b/Destination.cpp index e7e9b93c..3509fe80 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -2,8 +2,9 @@ #include #include #include + #include "Log.h" -#include "util.h" +#include "FS.h" #include "Crypto.h" #include "Timestamp.h" #include "NetDb.h" @@ -690,30 +691,26 @@ namespace client void ClientDestination::PersistTemporaryKeys () { - auto path = i2p::util::filesystem::GetDefaultDataDir() / "destinations"; - auto filename = path / (GetIdentHash ().ToBase32 () + ".dat"); - std::ifstream f(filename.string (), std::ifstream::binary); - if (f) - { - f.read ((char *)m_EncryptionPublicKey, 256); + std::string ident = GetIdentHash().ToBase32(); + std::string path = i2p::fs::DataDirPath("destinations", (ident + ".dat")); + std::ifstream f(path, std::ifstream::binary); + + if (f) { + f.read ((char *)m_EncryptionPublicKey, 256); f.read ((char *)m_EncryptionPrivateKey, 256); + return; } - if (!f) - { - LogPrint (eLogInfo, "Creating new temporary keys for address ", GetIdentHash ().ToBase32 ()); - i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); - if (!boost::filesystem::exists (path)) - { - if (!boost::filesystem::create_directory (path)) - LogPrint (eLogError, "Failed to create destinations directory"); - } - std::ofstream f1 (filename.string (), std::ofstream::binary | std::ofstream::out); - if (f1) - { - f1.write ((char *)m_EncryptionPublicKey, 256); - f1.write ((char *)m_EncryptionPrivateKey, 256); - } - } + + LogPrint (eLogInfo, "Destination: Creating new temporary keys for address ", ident, ".b32.i2p"); + i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); + + std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out); + if (f1) { + f1.write ((char *)m_EncryptionPublicKey, 256); + f1.write ((char *)m_EncryptionPrivateKey, 256); + return; + } + LogPrint(eLogError, "Destinations: Can't save keys to ", path); } } } diff --git a/HTTPServer.cpp b/HTTPServer.cpp index f04b0b1c..85dbec42 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -4,6 +4,7 @@ #include #include #include "Base.h" +#include "FS.h" #include "Log.h" #include "Tunnel.h" #include "TransitTunnel.h" @@ -420,7 +421,7 @@ namespace util s << " (" << i2p::transport::transports.GetInBandwidth () <<" Bps)
\r\n"; s << "Sent: " << i2p::transport::transports.GetTotalSentBytes ()/1000 << "K"; s << " (" << i2p::transport::transports.GetOutBandwidth () <<" Bps)
\r\n"; - s << "Data path: " << i2p::util::filesystem::GetDataDir().string() << "
\r\n
\r\n"; + s << "Data path: " << i2p::fs::GetDataDir() << "
\r\n
\r\n"; s << "Our external address:" << "
\r\n" ; for (auto& address : i2p::context.GetRouterInfo().GetAddresses()) { diff --git a/Reseed.cpp b/Reseed.cpp index 80cc6a45..506b0a0f 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -2,15 +2,16 @@ #include #include #include -#include #include #include #include #include #include #include + #include "I2PEndian.h" #include "Reseed.h" +#include "FS.h" #include "Log.h" #include "Identity.h" #include "Crypto.h" @@ -346,23 +347,22 @@ namespace data void Reseeder::LoadCertificates () { - boost::filesystem::path reseedDir = i2p::util::filesystem::GetCertificatesDir() / "reseed"; - - if (!boost::filesystem::exists (reseedDir)) - { - LogPrint (eLogWarning, "Reseed: certificates not loaded, ", reseedDir, " doesn't exist"); + std::string certDir = i2p::fs::DataDirPath("certificates", "reseed"); + std::vector files; + int numCertificates = 0; + + if (!i2p::fs::ReadDir(certDir, files)) { + LogPrint(eLogWarning, "Reseed: Can't load reseed certificates from ", certDir); return; } - int numCertificates = 0; - boost::filesystem::directory_iterator end; // empty - for (boost::filesystem::directory_iterator it (reseedDir); it != end; ++it) - { - if (boost::filesystem::is_regular_file (it->status()) && it->path ().extension () == ".crt") - { - LoadCertificate (it->path ().string ()); - numCertificates++; - } + for (const std::string & file : files) { + if (file.compare(file.size() - 4, 4, ".crt") != 0) { + LogPrint(eLogWarning, "Reseed: ignoring file ", file); + continue; + } + LoadCertificate (file); + numCertificates++; } LogPrint (eLogInfo, "Reseed: ", numCertificates, " certificates loaded"); } diff --git a/RouterContext.cpp b/RouterContext.cpp index 90961948..67f99d29 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -5,6 +5,7 @@ #include "Timestamp.h" #include "I2NPProtocol.h" #include "NetDb.h" +#include "FS.h" #include "util.h" #include "version.h" #include "Log.h" @@ -64,7 +65,7 @@ namespace i2p void RouterContext::UpdateRouterInfo () { m_RouterInfo.CreateBuffer (m_Keys); - m_RouterInfo.SaveToFile (i2p::util::filesystem::GetFullPath (ROUTER_INFO)); + m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO)); m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch (); } @@ -292,7 +293,7 @@ namespace i2p bool RouterContext::Load () { - std::ifstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ifstream::binary | std::ifstream::in); + std::ifstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ifstream::in | std::ifstream::binary); if (!fk.is_open ()) return false; fk.seekg (0, std::ios::end); size_t len = fk.tellg(); @@ -312,7 +313,7 @@ namespace i2p delete[] buf; } - i2p::data::RouterInfo routerInfo(i2p::util::filesystem::GetFullPath (ROUTER_INFO)); // TODO + i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO)); // TODO m_RouterInfo.SetRouterIdentity (GetIdentity ()); m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION); @@ -331,7 +332,7 @@ namespace i2p void RouterContext::SaveKeys () { // save in the same format as .dat files - std::ofstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ofstream::binary | std::ofstream::out); + std::ofstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ofstream::binary | std::ofstream::out); size_t len = m_Keys.GetFullLen (); uint8_t * buf = new uint8_t[len]; m_Keys.ToBuffer (buf, len); From 97c136d04391e203cfe0a4404a126ce9ee28c455 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0947/6300] * i2p::fs migration: Daemon, DaemonLinux, api (#290) --- Daemon.cpp | 29 ++++++++++++++--------------- DaemonLinux.cpp | 9 ++++----- api.cpp | 12 +++++++++--- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 242f4bbf..4b1d44b3 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -5,6 +5,7 @@ #include "Config.h" #include "Log.h" +#include "FS.h" #include "Base.h" #include "version.h" #include "Transports.h" @@ -14,7 +15,6 @@ #include "Tunnel.h" #include "NetDb.h" #include "Garlic.h" -#include "util.h" #include "Streaming.h" #include "Destination.h" #include "HTTPServer.h" @@ -63,9 +63,17 @@ namespace i2p i2p::config::Init(); i2p::config::ParseCmdline(argc, argv); - std::string config = i2p::util::filesystem::GetConfigFile().string(); - std::string tunconf = i2p::util::filesystem::GetTunnelsConfigFile().string(); - std::string datadir = i2p::util::filesystem::GetDataDir().string(); + std::string config; i2p::config::GetOption("conf", config); + std::string tunconf; i2p::config::GetOption("tunconf", tunconf); + std::string datadir; i2p::config::GetOption("datadir", datadir); + i2p::fs::DetectDataDir(datadir, IsService()); + i2p::fs::Init(); + + datadir = i2p::fs::GetDataDir(); + if (config == "") + config = i2p::fs::DataDirPath("i2p.conf"); + if (tunconf == "") + tunconf = i2p::fs::DataDirPath("tunnels.cfg"); i2p::config::ParseConfig(config); i2p::config::Finalize(); @@ -144,18 +152,9 @@ namespace i2p if (isDaemon && (logs == "" || logs == "stdout")) logs = "file"; - if (logs == "file") - { + if (logs == "file") { if (logfile == "") - { - // use autodetect of logfile - logfile = IsService () ? "/var/log" : i2p::util::filesystem::GetDataDir().string(); -#ifndef _WIN32 - logfile.append("/i2pd.log"); -#else - logfile.append("\\i2pd.log"); -#endif - } + logfile = i2p::fs::DataDirPath("i2pd.log"); StartLog (logfile); } else { // use stdout diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 9382a878..2ccbfe38 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -9,8 +9,8 @@ #include #include "Config.h" +#include "FS.h" #include "Log.h" -#include "util.h" void handle_signal(int sig) { @@ -55,7 +55,7 @@ namespace i2p LogPrint(eLogError, "Daemon: could not create process group."); return false; } - std::string d(i2p::util::filesystem::GetDataDir().string ()); // make a copy + std::string d = i2p::fs::GetDataDir(); if (chdir(d.c_str()) != 0) { LogPrint(eLogError, "Daemon: could not chdir: ", strerror(errno)); @@ -75,8 +75,7 @@ namespace i2p // this code is c-styled and a bit ugly, but we need fd for locking pidfile std::string pidfile; i2p::config::GetOption("pidfile", pidfile); if (pidfile == "") { - pidfile = IsService () ? "/var/run" : i2p::util::filesystem::GetDataDir().string(); - pidfile.append("/i2pd.pid"); + pidfile = i2p::fs::DataDirPath("i2pd.pid"); } if (pidfile != "") { pidFH = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600); @@ -115,7 +114,7 @@ namespace i2p bool DaemonLinux::stop() { - unlink(pidfile.c_str()); + i2p::fs::Remove(pidfile); return Daemon_Singleton::stop(); } diff --git a/api.cpp b/api.cpp index 0f4e1799..3e037c02 100644 --- a/api.cpp +++ b/api.cpp @@ -9,7 +9,7 @@ #include "Identity.h" #include "Destination.h" #include "Crypto.h" -#include "util.h" +#include "FS.h" #include "api.h" namespace i2p @@ -18,10 +18,16 @@ namespace api { void InitI2P (int argc, char* argv[], const char * appName) { - i2p::util::filesystem::SetAppName (appName); i2p::config::Init (); i2p::config::ParseCmdline (argc, argv); i2p::config::Finalize (); + + std::string datadir; i2p::config::GetOption("datadir", datadir); + + i2p::fs::SetAppName (appName); + i2p::fs::DetectDataDir(datadir, false); + i2p::fs::Init(); + i2p::crypto::InitCrypto (); i2p::context.Init (); } @@ -36,7 +42,7 @@ namespace api if (logStream) StartLog (logStream); else - StartLog (i2p::util::filesystem::GetFullPath (i2p::util::filesystem::GetAppName () + ".log")); + StartLog (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log")); LogPrint(eLogInfo, "API: starting NetDB"); i2p::data::netdb.Start(); LogPrint(eLogInfo, "API: starting Transports"); From 4b8465613399a2e948f94f8c77227e6df12c76ca Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 11 Feb 2016 00:00:00 +0000 Subject: [PATCH 0948/6300] * i2p::fs migration: drop unused code from util.* (#314) --- util.cpp | 131 ------------------------------------------------------- util.h | 17 -------- 2 files changed, 148 deletions(-) diff --git a/util.cpp b/util.cpp index 1ea5fcd7..7c11d280 100644 --- a/util.cpp +++ b/util.cpp @@ -6,13 +6,7 @@ #include #include #include -#include -#include -#include #include -#include -#include -#include #include "Config.h" #include "util.h" #include "Log.h" @@ -67,131 +61,6 @@ namespace i2p { namespace util { -namespace filesystem -{ - std::string appName ("i2pd"); - - void SetAppName (const std::string& name) - { - appName = name; - } - - std::string GetAppName () - { - return appName; - } - - const boost::filesystem::path &GetDataDir() - { - static boost::filesystem::path path; - - // TODO: datadir parameter is useless because GetDataDir is called before OptionParser - // and mapArgs is not initialized yet - /* - std::string datadir; i2p::config::GetOption("datadir", datadir); - if (datadir != "") - path = boost::filesystem::system_complete(datadir); - else */ - path = GetDefaultDataDir(); - - if (!boost::filesystem::exists( path )) - { - // Create data directory - if (!boost::filesystem::create_directory( path )) - { - LogPrint(eLogError, "FS: Failed to create data directory!"); - path = ""; - return path; - } - } - if (!boost::filesystem::is_directory(path)) - path = GetDefaultDataDir(); - return path; - } - - std::string GetFullPath (const std::string& filename) - { - std::string fullPath = GetDataDir ().string (); -#ifndef _WIN32 - fullPath.append ("/"); -#else - fullPath.append ("\\"); -#endif - fullPath.append (filename); - return fullPath; - } - - boost::filesystem::path GetConfigFile() - { - std::string config; i2p::config::GetOption("conf", config); - if (config != "") { - /* config file set with cmdline */ - boost::filesystem::path path(config); - return path; - } - /* else - try autodetect */ - boost::filesystem::path path("i2p.conf"); - path = GetDataDir() / path; - if (!boost::filesystem::exists(path)) - path = ""; /* reset */ - return path; - } - - boost::filesystem::path GetTunnelsConfigFile() - { - std::string tunconf; i2p::config::GetOption("tunconf", tunconf); - if (tunconf != "") { - /* config file set with cmdline */ - boost::filesystem::path path(tunconf); - return path; - } - /* else - try autodetect */ - boost::filesystem::path path("tunnels.cfg"); - path = GetDataDir() / path; - if (!boost::filesystem::exists(path)) - path = ""; /* reset */ - return path; - } - - boost::filesystem::path GetDefaultDataDir() - { - // Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd - // Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd - // Mac: ~/Library/Application Support/i2pd - // Unix: ~/.i2pd or /var/lib/i2pd is system=1 -#ifdef WIN32 - // Windows - char localAppData[MAX_PATH]; - SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); - return boost::filesystem::path(std::string(localAppData) + "\\" + appName); -#else /* UNIX */ - bool service; i2p::config::GetOption("service", service); - if (service) // use system folder - return boost::filesystem::path(std::string ("/var/lib/") + appName); - boost::filesystem::path pathRet; - char* pszHome = getenv("HOME"); - if (pszHome == NULL || strlen(pszHome) == 0) - pathRet = boost::filesystem::path("/"); - else - pathRet = boost::filesystem::path(pszHome); -#ifdef MAC_OSX - // Mac - pathRet /= "Library/Application Support"; - boost::filesystem::create_directory(pathRet); - return pathRet / appName; -#else /* Other Unix */ - // Unix - return pathRet / (std::string (".") + appName); -#endif -#endif /* UNIX */ - } - - boost::filesystem::path GetCertificatesDir() - { - return GetDataDir () / "certificates"; - } -} - namespace http { std::string GetHttpContent (std::istream& response) diff --git a/util.h b/util.h index 0377ef8d..d1d7f41a 100644 --- a/util.h +++ b/util.h @@ -5,28 +5,11 @@ #include #include #include -#include -#include - -#define PAIRTYPE(t1, t2) std::pair namespace i2p { namespace util { - namespace filesystem - { - void SetAppName (const std::string& name); - std::string GetAppName (); - - const boost::filesystem::path &GetDataDir(); - std::string GetFullPath (const std::string& filename); - boost::filesystem::path GetDefaultDataDir(); - boost::filesystem::path GetConfigFile(); - boost::filesystem::path GetTunnelsConfigFile(); - boost::filesystem::path GetCertificatesDir(); - } - namespace http { const char ETAG[] = "ETag"; From 2fdf92770435bf8f0094ce04c2576be66d64982c Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 11 Feb 2016 10:54:36 -0500 Subject: [PATCH 0949/6300] show actual name of an invalid parameter --- Config.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Config.cpp b/Config.cpp index 9e9582ea..46816e45 100644 --- a/Config.cpp +++ b/Config.cpp @@ -204,19 +204,22 @@ namespace config { } void ParseConfig(const std::string& path) { - if (path == "") - return; + if (path == "") return; std::ifstream config(path, std::ios::in); - if (!config.is_open()) { + if (!config.is_open()) + { std::cerr << "missing/unreadable config file: " << path << std::endl; exit(EXIT_FAILURE); } - try { - store(boost::program_options::parse_config_file(config, m_OptionsDesc), m_Options); - } catch (boost::program_options::error e) { + try + { + store(boost::program_options::parse_config_file(config, m_OptionsDesc), m_Options); + } + catch (boost::program_options::error& e) + { std::cerr << e.what() << std::endl; exit(EXIT_FAILURE); }; From fbb98e1aec5705b23167f7056a47213dd6f41620 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 11 Feb 2016 11:18:15 -0500 Subject: [PATCH 0950/6300] show actual name of an invalid parameter --- Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config.cpp b/Config.cpp index 46816e45..8e212360 100644 --- a/Config.cpp +++ b/Config.cpp @@ -191,7 +191,7 @@ namespace config { | boost::program_options::command_line_style::allow_long_disguise; style &= ~ boost::program_options::command_line_style::allow_guessing; store(parse_command_line(argc, argv, m_OptionsDesc, style, old_syntax_parser), m_Options); - } catch (boost::program_options::error e) { + } catch (boost::program_options::error& e) { std::cerr << "args: " << e.what() << std::endl; exit(EXIT_FAILURE); } From d81ca5f919e1f0f7290c19e9fa379c2c1a3e7f2e Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 11 Feb 2016 14:45:33 -0500 Subject: [PATCH 0951/6300] local destination leaseset storage verification --- Destination.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++------ Destination.h | 5 ++++- LeaseSet.h | 2 ++ 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index e7e9b93c..b1866969 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -17,7 +17,8 @@ namespace client const std::map * params): m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_Keys (keys), m_IsPublic (isPublic), m_PublishReplyToken (0), - m_DatagramDestination (nullptr), m_PublishConfirmationTimer (m_Service), m_CleanupTimer (m_Service) + m_DatagramDestination (nullptr), m_PublishConfirmationTimer (m_Service), + m_PublishVerificationTimer (m_Service), m_CleanupTimer (m_Service) { if (m_IsPublic) PersistTemporaryKeys (); @@ -156,6 +157,8 @@ namespace client if (m_IsRunning) { m_CleanupTimer.cancel (); + m_PublishConfirmationTimer.cancel (); + m_PublishVerificationTimer.cancel (); m_IsRunning = false; m_StreamingDestination->Stop (); m_StreamingDestination = nullptr; @@ -298,8 +301,13 @@ namespace client leaseSet = std::make_shared (buf + offset, len - offset); if (leaseSet->IsValid ()) { - LogPrint (eLogDebug, "New remote LeaseSet added"); - m_RemoteLeaseSets[buf + DATABASE_STORE_KEY_OFFSET] = leaseSet; + if (leaseSet->GetIdentHash () != GetIdentHash ()) + { + LogPrint (eLogDebug, "New remote LeaseSet added"); + m_RemoteLeaseSets[buf + DATABASE_STORE_KEY_OFFSET] = leaseSet; + } + else + LogPrint (eLogDebug, "Own remote LeaseSet dropped"); } else { @@ -371,6 +379,10 @@ namespace client LogPrint (eLogDebug, "Destination: Publishing LeaseSet confirmed"); m_ExcludedFloodfills.clear (); m_PublishReplyToken = 0; + // schedule verification + m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); + m_PublishVerificationTimer.async_wait (std::bind (&ClientDestination::HandlePublishVerificationTimer, + shared_from_this (), std::placeholders::_1)); } else i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msg); @@ -381,7 +393,10 @@ namespace client i2p::garlic::GarlicDestination::SetLeaseSetUpdated (); UpdateLeaseSet (); if (m_IsPublic) + { + m_PublishVerificationTimer.cancel (); Publish (); + } } void ClientDestination::Publish () @@ -402,7 +417,6 @@ namespace client LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); return; } - std::set excluded; auto floodfill = i2p::data::netdb.GetClosestFloodfill (m_LeaseSet->GetIdentHash (), m_ExcludedFloodfills); if (!floodfill) { @@ -416,7 +430,7 @@ namespace client auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&ClientDestination::HandlePublishConfirmationTimer, - this, std::placeholders::_1)); + shared_from_this (), std::placeholders::_1)); outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, msg); } @@ -433,6 +447,34 @@ namespace client } } + void ClientDestination::HandlePublishVerificationTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto s = shared_from_this (); + RequestLeaseSet (GetIdentHash (), + [s](std::shared_ptr leaseSet) + { + if (leaseSet) + { + if (s->m_LeaseSet && *s->m_LeaseSet == *leaseSet) + { + // we got latest LeasetSet + LogPrint (eLogDebug, "Destination: published LeaseSet verified"); + s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL)); + s->m_PublishVerificationTimer.async_wait (std::bind (&ClientDestination::HandlePublishVerificationTimer, + s, std::placeholders::_1)); + return; + } + } + else + LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet"); + // we have to publish again + s->Publish (); + }); + } + } + void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); @@ -622,7 +664,7 @@ namespace client }); request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT)); request->requestTimeoutTimer.async_wait (std::bind (&ClientDestination::HandleRequestTimoutTimer, - this, std::placeholders::_1, dest)); + shared_from_this (), std::placeholders::_1, dest)); } else return false; diff --git a/Destination.h b/Destination.h index c45a20ec..34e7fa38 100644 --- a/Destination.h +++ b/Destination.h @@ -26,6 +26,8 @@ namespace client const uint8_t PROTOCOL_TYPE_DATAGRAM = 17; const uint8_t PROTOCOL_TYPE_RAW = 18; const int PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds + const int PUBLISH_VERIFICATION_TIMEOUT = 10; // in seconds after successfull publish + const int PUBLISH_REGULAR_VERIFICATION_INTERNAL = 100; // in seconds periodically const int LEASESET_REQUEST_TIMEOUT = 5; // in seconds const int MAX_LEASESET_REQUEST_TIMEOUT = 40; // in seconds const int DESTINATION_CLEANUP_TIMEOUT = 3; // in minutes @@ -116,6 +118,7 @@ namespace client void UpdateLeaseSet (); void Publish (); void HandlePublishConfirmationTimer (const boost::system::error_code& ecode); + void HandlePublishVerificationTimer (const boost::system::error_code& ecode); void HandleDatabaseStoreMessage (const uint8_t * buf, size_t len); void HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len); void HandleDeliveryStatusMessage (std::shared_ptr msg); @@ -148,7 +151,7 @@ namespace client std::map > m_StreamingDestinationsByPorts; i2p::datagram::DatagramDestination * m_DatagramDestination; - boost::asio::deadline_timer m_PublishConfirmationTimer, m_CleanupTimer; + boost::asio::deadline_timer m_PublishConfirmationTimer, m_PublishVerificationTimer, m_CleanupTimer; public: diff --git a/LeaseSet.h b/LeaseSet.h index aed27683..6a0da922 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -57,6 +57,8 @@ namespace data bool IsExpired () const; bool IsEmpty () const { return m_Leases.empty (); }; uint64_t GetExpirationTime () const { return m_ExpirationTime; }; + bool operator== (const LeaseSet& other) const + { return m_BufferLen == other.m_BufferLen && !memcmp (m_Buffer, other.m_Buffer, m_BufferLen); }; // implements RoutingDestination const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); }; From b967acda5856c93671c791020db9acc7443bf8a2 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 11 Feb 2016 15:05:46 -0500 Subject: [PATCH 0952/6300] flood to floodfills that are close than us only --- NetDb.cpp | 11 ++++++++--- NetDb.h | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index fe1d6a45..6d019316 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -496,9 +496,11 @@ namespace data std::set excluded; for (int i = 0; i < 3; i++) { - auto floodfill = GetClosestFloodfill (ident, excluded); + auto floodfill = GetClosestFloodfill (ident, excluded, true); // we need a floodfill close than us only if (floodfill) transports.SendMessage (floodfill->GetIdentHash (), floodMsg); + else + break; } } else @@ -885,12 +887,15 @@ namespace data } std::shared_ptr NetDb::GetClosestFloodfill (const IdentHash& destination, - const std::set& excluded) const + const std::set& excluded, bool closeThanUsOnly) const { std::shared_ptr r; XORMetric minMetric; IdentHash destKey = CreateRoutingKey (destination); - minMetric.SetMax (); + if (closeThanUsOnly) + minMetric = destKey ^ i2p::context.GetIdentHash (); + else + minMetric.SetMax (); std::unique_lock l(m_FloodfillsMutex); for (auto it: m_Floodfills) { diff --git a/NetDb.h b/NetDb.h index 237e5be1..f1b97728 100644 --- a/NetDb.h +++ b/NetDb.h @@ -52,7 +52,7 @@ namespace data std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith) const; std::shared_ptr GetRandomPeerTestRouter () const; std::shared_ptr GetRandomIntroducer () const; - std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded) const; + std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded, bool closeThanUsOnly = false) const; std::vector GetClosestFloodfills (const IdentHash& destination, size_t num, std::set& excluded) const; std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; From ee8ab58d64ced0c83214866289fa9c25bd3850cd Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 11 Feb 2016 22:17:33 -0500 Subject: [PATCH 0953/6300] don't reply to lookup with expired LeaseSet --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 6d019316..6376f44e 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -690,7 +690,7 @@ namespace data lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP)) { auto leaseSet = FindLeaseSet (ident); - if (leaseSet) // we don't send back our LeaseSets + if (leaseSet && !leaseSet->IsExpired ()) // we don't send back our LeaseSets { LogPrint (eLogDebug, "NetDb: requested LeaseSet ", key, " found"); replyMsg = CreateDatabaseStoreMsg (leaseSet); From 517385fb63e0358690796544cc4416828fec60ff Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 11 Feb 2016 22:18:24 -0500 Subject: [PATCH 0954/6300] Lease enddate threshold --- LeaseSet.cpp | 10 ++++++++-- LeaseSet.h | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 677f19ca..561dc28c 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -130,7 +130,7 @@ namespace data leases += 4; // tunnel ID lease.endDate = bufbe64toh (leases); leases += 8; // end date - if (ts < lease.endDate) + if (ts < lease.endDate + LEASE_ENDDATE_THRESHOLD) { if (lease.endDate > m_ExpirationTime) m_ExpirationTime = lease.endDate; @@ -151,7 +151,7 @@ namespace data else LogPrint (eLogWarning, "LeaseSet: Lease is expired already "); } - if (!m_ExpirationTime) + if (!m_ExpirationTime && m_Leases.empty ()) { LogPrint (eLogWarning, "LeaseSet: all leases are expired. Dropped"); m_IsValid = false; @@ -192,6 +192,12 @@ namespace data if (ts < endDate) leases.push_back (it); } + if (leases.empty () && withThreshold) + { + for (auto it: m_Leases) + if (ts < it->endDate + LEASE_ENDDATE_THRESHOLD) + leases.push_back (it); + } return leases; } diff --git a/LeaseSet.h b/LeaseSet.h index 6a0da922..b9ec2d0b 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -17,6 +17,7 @@ namespace tunnel namespace data { + const int LEASE_ENDDATE_THRESHOLD = 9500; // in milliseconds struct Lease { IdentHash tunnelGateway; From 333103f50e44fee638a0058baa6af6ab478d547b Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 11 Feb 2016 22:18:44 -0500 Subject: [PATCH 0955/6300] shared RTT --- Destination.cpp | 3 +-- Garlic.h | 1 + Streaming.cpp | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index b1866969..5e76f770 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -462,8 +462,7 @@ namespace client // we got latest LeasetSet LogPrint (eLogDebug, "Destination: published LeaseSet verified"); s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL)); - s->m_PublishVerificationTimer.async_wait (std::bind (&ClientDestination::HandlePublishVerificationTimer, - s, std::placeholders::_1)); + s->m_PublishVerificationTimer.async_wait (std::bind (&ClientDestination::HandlePublishVerificationTimer, s, std::placeholders::_1)); return; } } diff --git a/Garlic.h b/Garlic.h index 95a59e72..1d8aa8cf 100644 --- a/Garlic.h +++ b/Garlic.h @@ -62,6 +62,7 @@ namespace garlic { std::shared_ptr outboundTunnel; std::shared_ptr remoteLease; + int rtt; // RTT uint32_t updateTime; // seconds since epoch }; diff --git a/Streaming.cpp b/Streaming.cpp index 1e9cdd84..7047c1fb 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -274,7 +274,7 @@ namespace stream if (!seqn && m_RoutingSession) // first message confirmed m_RoutingSession->SetSharedRoutingPath ( std::make_shared ( - i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, 0})); + i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, m_RTT, 0})); } else break; @@ -605,6 +605,8 @@ namespace stream { m_CurrentOutboundTunnel = routingPath->outboundTunnel; m_CurrentRemoteLease = routingPath->remoteLease; + m_RTT = routingPath->rtt; + m_RTO = m_RTT*1.5; // TODO: implement it better } } } @@ -727,7 +729,8 @@ namespace stream { if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) { - m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); + auto remoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); + if (remoteLeaseSet) m_RemoteLeaseSet = remoteLeaseSet; // renew if possible if (!m_RemoteLeaseSet) LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); } From 2cfb697867a6093f16615239bcb082b57b783d3d Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Feb 2016 15:42:13 -0500 Subject: [PATCH 0956/6300] strip our Referer and replace User-Agent --- HTTPProxy.cpp | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index c417d904..f0166a4a 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -176,28 +176,39 @@ namespace proxy m_request.push_back('\r'); m_request.push_back('\n'); m_request.append("Connection: close\r\n"); - m_request.append(reinterpret_cast(http_buff),len); + // 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)) // strip out referer + { + if (!strncmp ((const char *)http_buff, "User-Agent", 10)) // replace UserAgent + m_request.append("User-Agent: MYOB/6.66 (AN/ON)"); + else + m_request.append ((const char *)http_buff); + m_request.append ("\r\n"); + } + isEndOfHeader = !http_buff[0]; + auto l = eol - http_buff; + http_buff = eol; + len -= l; + if (len > 0) // \r + { + http_buff++; + len--; + } + } + } + m_request.append(reinterpret_cast(http_buff),len); return true; } bool HTTPProxyHandler::HandleData(uint8_t *http_buff, std::size_t len) { - // TODO: we should srtrip 'Referer' better, because it might be inside message body - /*assert(len); // This should always be called with a least a byte left to parse - - // remove "Referer" from http requst - http_buff[len] = 0; - auto start = strstr((char *)http_buff, "\nReferer:"); - if (start) - { - auto end = strchr (start + 1, '\n'); - if (end) - { - strncpy(start, end, (char *)(http_buff + len) - end); - len -= (end - start); - } - }*/ - while (len > 0) { //TODO: fallback to finding HOst: header if needed From c561d71dc0e4c628a9fade29490fbacda473bbc4 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Feb 2016 20:56:29 -0500 Subject: [PATCH 0957/6300] count lease expiration threshold --- Destination.cpp | 2 +- Streaming.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 5e76f770..8e13db42 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -198,7 +198,7 @@ namespace client else { auto ls = i2p::data::netdb.FindLeaseSet (ident); - if (ls) + if (ls && !ls->IsExpired ()) { ls->PopulateLeases (); // since we don't store them in netdb m_RemoteLeaseSets[ident] = ls; diff --git a/Streaming.cpp b/Streaming.cpp index 7047c1fb..79f202be 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -621,7 +621,7 @@ namespace stream auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (!m_CurrentRemoteLease || ts >= m_CurrentRemoteLease->endDate - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000) UpdateCurrentRemoteLease (true); - if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate) + if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) { std::vector msgs; for (auto it: packets) From b5feb3fd66173073122eeaa3dd72bdec1aa94056 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 13 Feb 2016 17:03:25 -0500 Subject: [PATCH 0958/6300] update reseeds list --- Reseed.cpp | 7 ++-- .../reseed/j_at_torontocrypto.org.crt | 34 +++++++++++++++++++ .../reseed/matt_at_drollette.com.crt | 32 ----------------- 3 files changed, 38 insertions(+), 35 deletions(-) create mode 100644 contrib/certificates/reseed/j_at_torontocrypto.org.crt delete mode 100644 contrib/certificates/reseed/matt_at_drollette.com.crt diff --git a/Reseed.cpp b/Reseed.cpp index 80cc6a45..768cd7c6 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -25,15 +25,16 @@ namespace data static std::vector httpsReseedHostList = { "https://reseed.i2p-projekt.de/", // Only HTTPS - "https://i2pseed.zarrenspry.info/", // Only HTTPS and SU3 (v3) support + //"https://i2pseed.zarrenspry.info/", // Only HTTPS and SU3 (v3) support "https://i2p.mooo.com/netDb/", "https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required "https://us.reseed.i2p2.no:444/", "https://uk.reseed.i2p2.no:444/", - "https://www.torontocrypto.org:8443/" + "https://www.torontocrypto.org:8443/", + "https://i2p-0.manas.ca:8443/" "https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support "https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support - "https://ieb9oopo.mooo.com/" // Only HTTPS and SU3 (v3) support + "https://download.xxlspeed.com/" // Only HTTPS and SU3 (v3) support }; Reseeder::Reseeder() diff --git a/contrib/certificates/reseed/j_at_torontocrypto.org.crt b/contrib/certificates/reseed/j_at_torontocrypto.org.crt new file mode 100644 index 00000000..4a2789ec --- /dev/null +++ b/contrib/certificates/reseed/j_at_torontocrypto.org.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- diff --git a/contrib/certificates/reseed/matt_at_drollette.com.crt b/contrib/certificates/reseed/matt_at_drollette.com.crt deleted file mode 100644 index e490a152..00000000 --- a/contrib/certificates/reseed/matt_at_drollette.com.crt +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFgzCCA2ugAwIBAgIEB52rdjANBgkqhkiG9w0BAQ0FADByMQswCQYDVQQGEwJY -WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt -b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEbMBkGA1UEAwwSbWF0dEBkcm9sbGV0 -dGUuY29tMB4XDTE0MDcyMTEzMjYxM1oXDTI0MDcyMDEzMjYxM1owcjELMAkGA1UE -BhMCWFgxCzAJBgNVBAgTAlhYMQswCQYDVQQHEwJYWDEeMBwGA1UEChMVSTJQIEFu -b255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGzAZBgNVBAMMEm1hdHRAZHJv -bGxldHRlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL5M9wKT -csNLg4EA3fW7LleTQdrm3stPnoUvFmsNZHGgsKt1Nc1qCNis3kr2QEY+4Z398U7r -7xGEQFa7D/9SPHf6n1uVXc9DIcmwBtEB0FPB1XPFp2h00ZXIv24yiLN3GQT1woAM -yEbBWsUgn8K/iMBeA5dU2vPwAbGO/0ibD62frgGdYqU2EeiJ/U6vBmKxvC+q2noL -gnyfQJEJANXgf+Cw/gBaS6yn5ZsYcenLNenID2TQKQ6Q/NxYrDYRdWdId29iwldt -dmNSmASv8C7g9d/isZkpmtYNkE4J4m0W9wKziOoyvLSMo8ec67QmCKaPaYKTHTjx -aUuja02+mnlV4DSdZo6nPkSdokRY0+5e6q7+dIPefu8ealGEAE5oedEfl5iM5Fnz -phTR+ePodBK3sB+bMi1NMppbWugpFpdqs1hg2KNKSSG8C4/eTqf2nnlDiVvvFANc -imt6tk0pZcKqveRiDSgI8mTzTcrNgVClsCLoInY5Vab7onZjY9bGijPQ2i1P6+qu -5G6LiLFW7xFq2BcX1DnTztcJ8Yu9NYHhR21J6u7Dr8YHntes3mnth1F0BX3FVA1s -9SaE9/pNhdqap9owpEhNoE1Ke3LorVLL8jyQsqgRHx8VdhWdi9Ao0mzzeI9HYX0j -nZ7uXK5DqGG74K6eWoS9jZSDJLj3IBkIr3B/AgMBAAGjITAfMB0GA1UdDgQWBBTK -YjH+9Jv82Zqi86r95/1sXUCOnDANBgkqhkiG9w0BAQ0FAAOCAgEAsDyl3dS/5pR1 -iDN0zE70HN1Sjv55c5um6N39rgz8JSObbAMihhpjRXPR6yl0PdfVcswdCuEaaykp -ppPNY5ObqZIdqI92XOaOhSA3AkZwZffbwaoXFYiawq1aQG1HP7oxXzWwbnbPOxgz -6ThNP5DJan53Mk8TAhxoJkEJxVlMwIiC+QEgqDNYrP8oNOR2J1EXgzsHheEKObyP -xTwRYFqZU/7BQlFeB0LG1LIy9zXAHlb/XIor10w6ChPDW7DiDwGq3zDJw1d8eiUn -RoPRmFjTqn+3rGaEkk+vUFHoWo7cLCEIC3+P9wlY4Kel+ldXMmuJ+BZ1glFXeO3L -VO85n7iVIyBbwo7RLtNaBvrRQIEG3ld5UOKklLlWwhrX/FXksEhdFvmuF9sbiYNr -cg81sbwZlX7Gi7VicXkykFFXwRRr3UblDtfeevouxk4nMVzcDsmzGeAZKQBvcxHa -Pzc70YwnVRqTc87c0bEwPoxK1Vb26+DILyDjKb/AkTw/rwj6vcJZP2ad+hpiz5Ka -nlbY2cI3JJb0TQiDiOIk+xFqC5oHUTSEmfqA6sA5o/RqdwDpkfpgI5mCwhYzDSLD -jfS+263ylhanl7oz0sM+GtH63owVbYJAFT2EozT9siTIErvJESL4Z80yUQG63d/7 -fss8T6gOo19esb/KEMZGZE4pAApakWM= ------END CERTIFICATE----- From 59b2e31adddd10313d48a31c15ba5246ceb194f4 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 13 Feb 2016 17:10:54 -0500 Subject: [PATCH 0959/6300] ssl certificates updated --- contrib/certificates/ssl/193.150.121.66.crt | 21 ---------- .../ssl/download.xxlspeed.com.crt | 19 +++++++++ contrib/certificates/ssl/i2p.mooo.com.crt | 42 +++++++++---------- contrib/certificates/ssl/i2p.mooo.com2.crt | 23 ---------- .../certificates/ssl/ieb9oopo.mooo.com.crt | 25 ----------- contrib/certificates/ssl/link.mx24.eu.crt | 24 ----------- contrib/certificates/ssl/netdb.rows.io.crt | 33 --------------- .../certificates/ssl/reseed.i2p.vzaws.com.crt | 33 +++++++++++++++ contrib/certificates/ssl/user.mx24.eu.crt | 17 ++++++++ 9 files changed, 90 insertions(+), 147 deletions(-) delete mode 100644 contrib/certificates/ssl/193.150.121.66.crt create mode 100644 contrib/certificates/ssl/download.xxlspeed.com.crt delete mode 100644 contrib/certificates/ssl/i2p.mooo.com2.crt delete mode 100644 contrib/certificates/ssl/ieb9oopo.mooo.com.crt delete mode 100644 contrib/certificates/ssl/link.mx24.eu.crt delete mode 100644 contrib/certificates/ssl/netdb.rows.io.crt create mode 100644 contrib/certificates/ssl/reseed.i2p.vzaws.com.crt create mode 100644 contrib/certificates/ssl/user.mx24.eu.crt diff --git a/contrib/certificates/ssl/193.150.121.66.crt b/contrib/certificates/ssl/193.150.121.66.crt deleted file mode 100644 index 450581d2..00000000 --- a/contrib/certificates/ssl/193.150.121.66.crt +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDgDCCAmgCCQCAKEkFUJcEezANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UEBhMC -Tk8xDTALBgNVBAgMBE9zbG8xDTALBgNVBAcMBE9zbG8xDDAKBgNVBAoMA0kyUDEM -MAoGA1UECwwDSTJQMRcwFQYDVQQDDA4xOTMuMTUwLjEyMS42NjEfMB0GCSqGSIb3 -DQEJARYQbWVlaEBpMnBtYWlsLm9yZzAeFw0xMzA2MjcxODM2MjhaFw0yMDA2MjUx -ODM2MjhaMIGBMQswCQYDVQQGEwJOTzENMAsGA1UECAwET3NsbzENMAsGA1UEBwwE -T3NsbzEMMAoGA1UECgwDSTJQMQwwCgYDVQQLDANJMlAxFzAVBgNVBAMMDjE5My4x -NTAuMTIxLjY2MR8wHQYJKoZIhvcNAQkBFhBtZWVoQGkycG1haWwub3JnMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuBuFY4ZFvsbr5l1/s/GeUBLIWQLB -nqrRkonrwCyxgjSnnG1uz/Z5nf6QDUjiVnFKMXenLaDn4KCmEi4LjWQllhK9r6pj -BRkR7C0DTHq7WqfyvWnGSZZsOJDiH2vLlivV8N9oGdjxvv0N9No3AJcsmLYrxSLi -6/JF8xZ2HGuT/oWW6aWvpIOKpIqti865BJw5P5KgYAS24J8vHRFM3FA4dfLNTBA2 -IGqPqYLQA+2zfOC4z01aArmcYnT1iJLT7krgKnr/BXdJfGQ2GjxkRSt8IwB6WmXA -byz6QdNYM/0eubi102/zpD/DrySTU2kc8xKjknGUqBJvVdsL+iLK98uJrQIDAQAB -MA0GCSqGSIb3DQEBBQUAA4IBAQCTimMu3X7+ztXxlIFhwGh42GfMjeBYT0NHOLAy -ZtQNRqhNvkl3jZ4ERPLxP99+bcAfCX0wgVpgD32OWEZopwveRyMImP8HfFr4NnZ+ -edbM37fRYiVJv57kbi6O0rhEC7J5JF+fnCaZVLCuvYIrIXTdxTjvxuLhyan6Ej7V -7iGDJ8t16tpLVJgcXfRg+dvAa6aDOK6x3w78j0bvh6rhvpOd9sW/Nk3LBKP4Xgkx -PHkqm3hNfDIu8Hubeav9SA1kLVMS/uce52VyYMEDauObfC65ds0GRmCtYhZqMvj+ -FFCbssLraVJE9Hi/ZKGu33jNngDCG+wG+nmleksMYE1yTSRt ------END CERTIFICATE----- diff --git a/contrib/certificates/ssl/download.xxlspeed.com.crt b/contrib/certificates/ssl/download.xxlspeed.com.crt new file mode 100644 index 00000000..253c0509 --- /dev/null +++ b/contrib/certificates/ssl/download.xxlspeed.com.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDLDCCArKgAwIBAgIJAMOgj4vE9qpcMAoGCCqGSM49BAMEMIHTMQswCQYDVQQG +EwJERTEeMBwGA1UECAwVZG93bmxvYWQueHhsc3BlZWQuY29tMR4wHAYDVQQHDBVk +b3dubG9hZC54eGxzcGVlZC5jb20xHjAcBgNVBAoMFWRvd25sb2FkLnh4bHNwZWVk +LmNvbTEeMBwGA1UECwwVZG93bmxvYWQueHhsc3BlZWQuY29tMR4wHAYDVQQDDBVk +b3dubG9hZC54eGxzcGVlZC5jb20xJDAiBgkqhkiG9w0BCQEWFWRvd25sb2FkLnh4 +bHNwZWVkLmNvbTAeFw0xNTEyMzAxMTI4NDJaFw0yMTA2MjExMTI4NDJaMIHTMQsw +CQYDVQQGEwJERTEeMBwGA1UECAwVZG93bmxvYWQueHhsc3BlZWQuY29tMR4wHAYD +VQQHDBVkb3dubG9hZC54eGxzcGVlZC5jb20xHjAcBgNVBAoMFWRvd25sb2FkLnh4 +bHNwZWVkLmNvbTEeMBwGA1UECwwVZG93bmxvYWQueHhsc3BlZWQuY29tMR4wHAYD +VQQDDBVkb3dubG9hZC54eGxzcGVlZC5jb20xJDAiBgkqhkiG9w0BCQEWFWRvd25s +b2FkLnh4bHNwZWVkLmNvbTB2MBAGByqGSM49AgEGBSuBBAAiA2IABFObW+pRshVD +gvMPvGdPGji2DAfdvkl3gvpyiQ0PUqxuTxwtBlwBo6cz2cMnkKdActuvE/VOTRG5 +/z7CcvG7b0+qgrHDffg7C2wWlAN0dSjuoV2Av7VoN1vEU96TCtheSqNQME4wHQYD +VR0OBBYEFPbEZH9oidjadUfvsnsh23b1jZnVMB8GA1UdIwQYMBaAFPbEZH9oidja +dUfvsnsh23b1jZnVMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwQDaAAwZQIwT1py +AV2hLFL/5ZgwmybdaCBBUsj3cGYroXb/Z2BHLDYmH8enK0DhhWyPdN1a7eCsAjEA +oQRU7lhXrisckjA2911Q5mA8y2sFAN/PDPrUeU9PI5vDF/ezTi20zULMOqbU1uRz +-----END CERTIFICATE----- diff --git a/contrib/certificates/ssl/i2p.mooo.com.crt b/contrib/certificates/ssl/i2p.mooo.com.crt index ac10e1ff..839767b3 100644 --- a/contrib/certificates/ssl/i2p.mooo.com.crt +++ b/contrib/certificates/ssl/i2p.mooo.com.crt @@ -1,23 +1,23 @@ -----BEGIN CERTIFICATE----- -MIIDvjCCAyegAwIBAgICZhcwDQYJKoZIhvcNAQEFBQAwdTELMAkGA1UEBhMCVVMx -DTALBgNVBAgMBG5vbmUxDTALBgNVBAcMBG5vbmUxDTALBgNVBAoMBG5vbmUxDTAL -BgNVBAsMBG5vbmUxFTATBgNVBAMMDGkycC5tb29vLmNvbTETMBEGCSqGSIb3DQEJ -ARYEbm9uZTAeFw0xMTEwMjMyMTM2NDFaFw0xOTEwMjMyMTM2NDFaMGYxCzAJBgNV -BAYTAlVTMQ0wCwYDVQQIDARub25lMQ0wCwYDVQQKDARub25lMQ0wCwYDVQQLDARu -b25lMRUwEwYDVQQDDAxpMnAubW9vby5jb20xEzARBgkqhkiG9w0BCQEWBG5vbmUw -ggGPMA0GCSqGSIb3DQEBAQUAA4IBfAAwggF3AoIBbgMG1O7HRVa7UoiKbQTmKy5m -x79Na8vjD3etcOwfc4TSenQFvn+GbAWkJwKpM8uvOcgj1CxNeHWdSaeTFH1OwJsw -vl3leJ7clMdo3hpQDhPeGzBLyOiWwFHVn15YKa9xcM7S9Op5Q6rKBHUyyx1vGSz+ -/NBmkktpI6rcGFfP3ISRL0auR+db+adWv4TS6W8YiwQIVZNbSlKP6FNO9Mv1kxQZ -KoHPn8vT/LtAh1fcI6ryBuy3F5oHfbGumIwsS5dpowryFxQzwg5vtMA7AMCMKyXv -hP/W6OuaaEP5MCIxkWjQs35gOYa8eF1dLoy3AD9yVVhoNrA8Bc5FnVFJ32Qv7agy -qRY85cXBA6hT/Qzs/wWwp7WrrnZuifaSv/u/Ayi5vX42/bf86PSM2IRNIESoA98A -NFz4U2KGq9s1K2JbkQmnFy8IU0w7CMq6PvNEm/uNjSk6OE1rcCXML+EuX0zmXy8d -PjRbLzC9csSg2CqMtQIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQf -Fh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUdjuOczdG -hUpYzH0UXqKrOleT8GkwHwYDVR0jBBgwFoAU+SKWC49cM5sCodv89AFin3pkS0Yw -DQYJKoZIhvcNAQEFBQADgYEAKYyWlDIStjjbn/ZzVScKR174I8whTbdqrX/vp9dr -2hMv5m4F+aswX4Jr58WneKg2LvRaL6xEhoL7OAQ6aB/7xVSpDjIrrBLZd513NAam -X6bOPYJ6IH7Vw9ClFY3AlfzsNlgRMXno7rySKKzhg24kusNwKDH2yCphZy4BgjMn -y6A= +MIIDvTCCAqWgAwIBAgIJAOeW0ejPrHimMA0GCSqGSIb3DQEBCwUAMHUxCzAJBgNV +BAYTAlVTMQ0wCwYDVQQIDARub25lMQ0wCwYDVQQHDARub25lMQ0wCwYDVQQKDARu +b25lMQ0wCwYDVQQLDARub25lMRUwEwYDVQQDDAxpMnAubW9vby5jb20xEzARBgkq +hkiG9w0BCQEWBG5vbmUwHhcNMTUwMjA4MTczMzA5WhcNMTkwMzE5MTczMzA5WjB1 +MQswCQYDVQQGEwJVUzENMAsGA1UECAwEbm9uZTENMAsGA1UEBwwEbm9uZTENMAsG +A1UECgwEbm9uZTENMAsGA1UECwwEbm9uZTEVMBMGA1UEAwwMaTJwLm1vb28uY29t +MRMwEQYJKoZIhvcNAQkBFgRub25lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAqxej7oRl9GOb8benIBCENrJXoow1iWhI9M+2nU0SaonrCDql5M2YMlwd +HzYUWtFbRjz2NinjB0fgFq9cfzHfr1Sc8k/OeGg1jvNfqt8wWo9tryQNjiHtDQUZ +6lQ5T13I+lj0CBasowgbApKQfrYjvaeuTaVYTfP8IVA60hoUQ+sy9JN+Unsx3/0Y +PLLd98+bT27qYuBNRB1g/ifUTd9Wosj2PevGBlCxYDaUjmCG4Q8kcQr87KvM6RTu +3AV61s/Wyy1j2YemlGG/ZhJ44YnlVMSu1vTjt9HInVf3lRRx/+RzbQO3lqeVC8LC +Bq3KbSlfJVx4vHslfHwBFw9A4rmD1QIDAQABo1AwTjAdBgNVHQ4EFgQUsSUvX0ED +yivB67iksVwZ+b8vLtQwHwYDVR0jBBgwFoAUsSUvX0EDyivB67iksVwZ+b8vLtQw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAde4wts7Q8TylFEc38ftJ +2f285fFIR7P1SSbBcHPK2eBwLEg0zJyFrCeiHuEpPrn+d5GqL2zOskjfcESGmDBT +aFajj8jPBJj/AmpkdWJG6a1YKro5tu9wrlenGwHOHu2/Cl0IJvafxrOs2x4G+2Nl +5Hcw/FIy8mK7eIch4pACfi0zNMZ6KMCKfX9bxPrQo78WdBfVjbrIBlgyOQJ5NJEF +JlWvS7Butv7eERi4I2huN5VRJSCFzjbuO+tjP3I8IB6WgdBmTeqq8ObtXRgahBuD +ZmkvqVSfIzK5JN4GjO8FOdCBomuwm9A92kgmAptwQwAHM9qCDJpH8L07/7poxlGb +iA== -----END CERTIFICATE----- diff --git a/contrib/certificates/ssl/i2p.mooo.com2.crt b/contrib/certificates/ssl/i2p.mooo.com2.crt deleted file mode 100644 index 839767b3..00000000 --- a/contrib/certificates/ssl/i2p.mooo.com2.crt +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDvTCCAqWgAwIBAgIJAOeW0ejPrHimMA0GCSqGSIb3DQEBCwUAMHUxCzAJBgNV -BAYTAlVTMQ0wCwYDVQQIDARub25lMQ0wCwYDVQQHDARub25lMQ0wCwYDVQQKDARu -b25lMQ0wCwYDVQQLDARub25lMRUwEwYDVQQDDAxpMnAubW9vby5jb20xEzARBgkq -hkiG9w0BCQEWBG5vbmUwHhcNMTUwMjA4MTczMzA5WhcNMTkwMzE5MTczMzA5WjB1 -MQswCQYDVQQGEwJVUzENMAsGA1UECAwEbm9uZTENMAsGA1UEBwwEbm9uZTENMAsG -A1UECgwEbm9uZTENMAsGA1UECwwEbm9uZTEVMBMGA1UEAwwMaTJwLm1vb28uY29t -MRMwEQYJKoZIhvcNAQkBFgRub25lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAqxej7oRl9GOb8benIBCENrJXoow1iWhI9M+2nU0SaonrCDql5M2YMlwd -HzYUWtFbRjz2NinjB0fgFq9cfzHfr1Sc8k/OeGg1jvNfqt8wWo9tryQNjiHtDQUZ -6lQ5T13I+lj0CBasowgbApKQfrYjvaeuTaVYTfP8IVA60hoUQ+sy9JN+Unsx3/0Y -PLLd98+bT27qYuBNRB1g/ifUTd9Wosj2PevGBlCxYDaUjmCG4Q8kcQr87KvM6RTu -3AV61s/Wyy1j2YemlGG/ZhJ44YnlVMSu1vTjt9HInVf3lRRx/+RzbQO3lqeVC8LC -Bq3KbSlfJVx4vHslfHwBFw9A4rmD1QIDAQABo1AwTjAdBgNVHQ4EFgQUsSUvX0ED -yivB67iksVwZ+b8vLtQwHwYDVR0jBBgwFoAUsSUvX0EDyivB67iksVwZ+b8vLtQw -DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAde4wts7Q8TylFEc38ftJ -2f285fFIR7P1SSbBcHPK2eBwLEg0zJyFrCeiHuEpPrn+d5GqL2zOskjfcESGmDBT -aFajj8jPBJj/AmpkdWJG6a1YKro5tu9wrlenGwHOHu2/Cl0IJvafxrOs2x4G+2Nl -5Hcw/FIy8mK7eIch4pACfi0zNMZ6KMCKfX9bxPrQo78WdBfVjbrIBlgyOQJ5NJEF -JlWvS7Butv7eERi4I2huN5VRJSCFzjbuO+tjP3I8IB6WgdBmTeqq8ObtXRgahBuD -ZmkvqVSfIzK5JN4GjO8FOdCBomuwm9A92kgmAptwQwAHM9qCDJpH8L07/7poxlGb -iA== ------END CERTIFICATE----- diff --git a/contrib/certificates/ssl/ieb9oopo.mooo.com.crt b/contrib/certificates/ssl/ieb9oopo.mooo.com.crt deleted file mode 100644 index 8be9eef8..00000000 --- a/contrib/certificates/ssl/ieb9oopo.mooo.com.crt +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIESzCCAzOgAwIBAgIJAKII1waVnWddMA0GCSqGSIb3DQEBCwUAMIG7MQswCQYD -VQQGEwJERTEaMBgGA1UECAwRaWViOW9vcG8ubW9vby5jb20xGjAYBgNVBAcMEWll -Yjlvb3BvLm1vb28uY29tMRowGAYDVQQKDBFpZWI5b29wby5tb29vLmNvbTEaMBgG -A1UECwwRaWViOW9vcG8ubW9vby5jb20xGjAYBgNVBAMMEWllYjlvb3BvLm1vb28u -Y29tMSAwHgYJKoZIhvcNAQkBFhFpZWI5b29wby5tb29vLmNvbTAeFw0xNDExMjIx -MzQzNThaFw0yMDA1MTQxMzQzNThaMIG7MQswCQYDVQQGEwJERTEaMBgGA1UECAwR -aWViOW9vcG8ubW9vby5jb20xGjAYBgNVBAcMEWllYjlvb3BvLm1vb28uY29tMRow -GAYDVQQKDBFpZWI5b29wby5tb29vLmNvbTEaMBgGA1UECwwRaWViOW9vcG8ubW9v -by5jb20xGjAYBgNVBAMMEWllYjlvb3BvLm1vb28uY29tMSAwHgYJKoZIhvcNAQkB -FhFpZWI5b29wby5tb29vLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAMhcnkSifOMw5bd66UlvYVsc42H22Nuy64qhtJHtggofrwBooF38kRCBVFL8 -9Xjzr0xsSshvO6p7E+CEUtA8v55l5vNbUTAvGP9WmzeZyZuCFg9Heo3orNMbIK7m -ppwKhwh6tFEIEpUTz/+xF5NRt0+CqcS4aNHuH3JPwNugfTBuSa86GeSaqL7K4eEZ -bZXqQ16Onvi0yyMqRJDp/ijRFxr2eKGPWb55kuRSET9PxVhlgRKULZkr39Dh9q1c -wb9lAMLMRZIzPVnyvC9jWkIqSDl5bkAAto0n1Jkw92rRp6EVKgSLA/4vl9wTb6xf -WfT5cs7pykAE0WXBr9TqpS3okncCAwEAAaNQME4wHQYDVR0OBBYEFGeEOHhWiKwZ -TGbc7uuK3DD7YjYZMB8GA1UdIwQYMBaAFGeEOHhWiKwZTGbc7uuK3DD7YjYZMAwG -A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAzRA/0OpJtCO4kQkTn/hux9 -dRi9T6B54Xav5jG53iAPLTeMxsaLkvweh2pZ3kvEUrQhvW0JF8QBrHTsgxzb4Wd6 -FNDHSgJbZv3uCjFtWeuUh+GTG1k9uwgNIEnx7J9Vp0JCi4ezi/HMNI7c+LjinM9f -hrAzclkeRPLYg645DkxckLyDUbrc9v1qWFoTpezXSBPO7n3Wk4sCytdoA1FkTdXh -RF4BWCl/3uOxcrn0TqoC9vCh8RcxnllOiOO5j4+PQ1Z6NkQ/5oRCK/jjaWc3Lr6/ -FicOZJe29BVnrPGynqe0Ky1o+kTdXFflKowfr7g8dwn8k9YavjtGbl1ZSHeuMF8= ------END CERTIFICATE----- diff --git a/contrib/certificates/ssl/link.mx24.eu.crt b/contrib/certificates/ssl/link.mx24.eu.crt deleted file mode 100644 index 8e0d910f..00000000 --- a/contrib/certificates/ssl/link.mx24.eu.crt +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEDzCCAvegAwIBAgIJAMsPNG1k0yV4MA0GCSqGSIb3DQEBCwUAMIGdMQswCQYD -VQQGEwJERTEVMBMGA1UECAwMbGluay5teDI0LmV1MRUwEwYDVQQHDAxsaW5rLm14 -MjQuZXUxFTATBgNVBAoMDGxpbmsubXgyNC5ldTEVMBMGA1UECwwMbGluay5teDI0 -LmV1MRUwEwYDVQQDDAxsaW5rLm14MjQuZXUxGzAZBgkqhkiG9w0BCQEWDGxpbmsu -bXgyNC5ldTAeFw0xNDExMTkxOTE4NTRaFw0yMDA1MTExOTE4NTRaMIGdMQswCQYD -VQQGEwJERTEVMBMGA1UECAwMbGluay5teDI0LmV1MRUwEwYDVQQHDAxsaW5rLm14 -MjQuZXUxFTATBgNVBAoMDGxpbmsubXgyNC5ldTEVMBMGA1UECwwMbGluay5teDI0 -LmV1MRUwEwYDVQQDDAxsaW5rLm14MjQuZXUxGzAZBgkqhkiG9w0BCQEWDGxpbmsu -bXgyNC5ldTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL8modDBRkyh -SHSm92pTfguO3F6n5ocsBJ4vaVoosYq3ILCsapjqmynMHZUef6gEB7+Gn5cKXsH2 -JaKOeb8DHrOFCaxfj187x1QfZj1UNMQblx2T9q4th12tqp+k4JuLwgemr+2uAUpM -xx/uHRJXD0hf67+fHQFYNVfa+WvT46xlKGsWDQ0LBsA/z4YGnyeaV4PrS5nj3euA -IbdfDj7rJea3bfhSqYA1ZH1cquKlsXOOYO5cIcXsa5dxDWX51QS+i7+ocph+JN1X -dRh6ZirE9OXZVXwXXVRnJSYjgBlP/DQBdE7YkE1R3LyCVZsgxJaaLV/ujijOIK61 -SqEhHvFNRe0CAwEAAaNQME4wHQYDVR0OBBYEFB6XRz6VZlrAE+3xL6AyKrkq+y2X -MB8GA1UdIwQYMBaAFB6XRz6VZlrAE+3xL6AyKrkq+y2XMAwGA1UdEwQFMAMBAf8w -DQYJKoZIhvcNAQELBQADggEBADhxBA5GHisDVf5a+1hIi7FBGBjJJLqzlaKh+bFB -gTCYfk3F4wYzndr1HpdCZSSYDtY3mXFNMWQCpwvwvy1DM+9AMRY68wKNXHa/WypW -zQSqTfEH8cdaIXUALB7pdWFVr3rx0f7/8I0Gj/ByUbJ94rzd22vduX5riY0Rag6B -dPtW0M9bJrC1AIjexzDcStupj9v/ceGYZQYC4zb2tZ7Ek/6q+vei8TxWZjku7Dl4 -YRPXXufyB24uQ1hJVy2fSyIJ63tIRJoEFLBNaKDOB53i10xLWBcsJpXKY57AOQMn -flqW4HG8uGJ/o1WjhiOB9eI7T9toy08zNzt+kSI/blFIoek= ------END CERTIFICATE----- diff --git a/contrib/certificates/ssl/netdb.rows.io.crt b/contrib/certificates/ssl/netdb.rows.io.crt deleted file mode 100644 index 5d99233c..00000000 --- a/contrib/certificates/ssl/netdb.rows.io.crt +++ /dev/null @@ -1,33 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFszCCA52gAwIBAgIRALWZzF745GPT8GVUcZ0RMg0wCwYJKoZIhvcNAQELMG0x -CzAJBgNVBAYTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAK -BgNVBAsTA0kyUDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMRYwFAYDVQQDEw1u -ZXRkYi5yb3dzLmlvMB4XDTE0MTIyMDE2NDIwNVoXDTE2MTIxOTE2NDIwNVowbTEL -MAkGA1UEBhMCWFgxHjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoG -A1UECxMDSTJQMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxFjAUBgNVBAMTDW5l -dGRiLnJvd3MuaW8wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCTZyJF -Im9pnc7OO5DfQy4SuotUztO5BJX7xniTqD4fKLQQXzZFeT4XHrkDste8TsTzUfxt -CWDEBH3af5cpnwWMT28rlRw2DlPr+LnAgt7VjFXdhFZr1N5VfNlTI1K3OiZ/DRlB -92CoTypyx4ebNfLtZfh+TPLOdg5UqROpHIrybsUj2IaG3IpGHJK8FuH79b/X5oVI -FlDZJs5QsJEARzq2QMJd6fnNqkCBSSjNpeL7TtDar9EKa6+O7s351kH8MVFNSogB -F0Hqu8LYaRC1L1JCz5lsOYKepp3MMIOdDOhy+FTd8NuNZXYkUTdTNI4dB6w4Z6o+ -xlnHEPpezIAAlPXLiupvlEi0om69/TMS+pLDBLAOlCZ2YaXS18UrSbmYYlekg40J -nEeALt8ZdsU/is7Q6SJZ3UltFIPCuMD+ixvaIvakkhNiqEWPxdg0XxAK1ZJYFup+ -2aVtPLQIzWePkG/VbdA5cxQKNtRwOgvCoKIE29nUbxuq2PCmMhLAfXHeieSzP5c7 -Q8A23qX94hwCIePj1YA9uNtStjECfVS1wjyXV4M1tTFUdSJv4aVtFjtya7PY+6SG -Srz11SqBWSqyJ/C14Su0QY/HquglzMRnkJ49Scwb+79hl7kPslO1iIgPLE5S2fIW -ZwJ/4AgGb6BZT8XPEYYANEA5y7KGanYNo8KdYwIDAQABo1IwUDAOBgNVHQ8BAf8E -BAMCAKQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAYBgNV -HREEETAPgg1uZXRkYi5yb3dzLmlvMAsGCSqGSIb3DQEBCwOCAgEAMjQSHPR/p9If -mJA1y489D1NB2CxfwO+CgAIs9HA7OsdneQBZTldMgBHoQGifkpD1uSl8DHoZqnJ8 -wo5YWcT1rYkP+V1jGfZj92VvfQL0/R4G4hWdQwYY0CcXN8ixS36UDQVSFKb4zvNG -j9iIN57WToEmxp5noHguKrpViwhXCMCpAXr3ZIv/Fd+QACNEXuvdZgbtwfOTPLKh -ZlkUPgVHiQopeQnZhZCT3aLZ5lndrUtWlQYiGN/OolVyRie+ysuxjRR4L5brt4Rz -hrwFBswbQZlgxJ3Nod9/wEdEJWP4+X69ggzOkBB+PgpOFpuDlJxNTcPA/WFIlsm0 -CzCv/o8Vg+MMWFPMwEZrk6UQXXACr1AEF+MUnZq3o5JaLvHoUcikewbZPcTCNvDp -nqT1RN9vq/MGdlRfPJkF028IXPz7T9DXXPXhJvv+FAfnOkREeUYpzBIftyYf92ol -l63z0FooVUTKWYPvFFgl5ShNnINTMVXPCZp8j7myLGSLOAFFwiaL1OtvftgxXfzC -B7Qj42SNhFUrHmO9fH3H2ptm/iW/Xe5eqgeb6MVGQ/eQJpdp0AvpDa50/AYNt1Iq -CcMKmBgzUezrIN24XXW/LZwazlc7I8e5RzgbEgXEDBZu21TApTKlmOqEYle8294W -fWThMdwk1kTrWxLooiVrS5A1hXqADqE= ------END CERTIFICATE----- diff --git a/contrib/certificates/ssl/reseed.i2p.vzaws.com.crt b/contrib/certificates/ssl/reseed.i2p.vzaws.com.crt new file mode 100644 index 00000000..25f9e369 --- /dev/null +++ b/contrib/certificates/ssl/reseed.i2p.vzaws.com.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- diff --git a/contrib/certificates/ssl/user.mx24.eu.crt b/contrib/certificates/ssl/user.mx24.eu.crt new file mode 100644 index 00000000..38c68ab5 --- /dev/null +++ b/contrib/certificates/ssl/user.mx24.eu.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICwDCCAkagAwIBAgIJAKXCoCBjd/C0MAoGCCqGSM49BAMEMIGdMQswCQYDVQQG +EwJERTEVMBMGA1UECAwMdXNlci5teDI0LmV1MRUwEwYDVQQHDAx1c2VyLm14MjQu +ZXUxFTATBgNVBAoMDHVzZXIubXgyNC5ldTEVMBMGA1UECwwMdXNlci5teDI0LmV1 +MRUwEwYDVQQDDAx1c2VyLm14MjQuZXUxGzAZBgkqhkiG9w0BCQEWDHVzZXIubXgy +NC5ldTAeFw0xNTA5MDMxNjMyNDVaFw0yMTAyMjMxNjMyNDVaMIGdMQswCQYDVQQG +EwJERTEVMBMGA1UECAwMdXNlci5teDI0LmV1MRUwEwYDVQQHDAx1c2VyLm14MjQu +ZXUxFTATBgNVBAoMDHVzZXIubXgyNC5ldTEVMBMGA1UECwwMdXNlci5teDI0LmV1 +MRUwEwYDVQQDDAx1c2VyLm14MjQuZXUxGzAZBgkqhkiG9w0BCQEWDHVzZXIubXgy +NC5ldTB2MBAGByqGSM49AgEGBSuBBAAiA2IABPlKs5fYTqVhIOMiR6U9U4TimxS3 +P5NBDVzeeIAgbw5KBC8UImScZVt9g4V1wQe5kPs7TxA2BfanAPZ+ekQiRRvMVQxD +bSlRYupEWhq5BrJI6Lq/HDc7VJe9UUWffWKUoKNQME4wHQYDVR0OBBYEFBGJ0Yr+ +PZXnrk5RafQEALUpAU6ZMB8GA1UdIwQYMBaAFBGJ0Yr+PZXnrk5RafQEALUpAU6Z +MAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwQDaAAwZQIxAPcovePHMCosrAQNzS5i +VDUiyPNLOxHyRBm79yKXGl13LxysB6OK+2M7t8j8E/udBwIwXVVjxN6aSgXYTJ7d +p+Hg/2CuBMwf41/ENRcYQA+oGS9bU6A+7U9KJ1xTWWoqsUEs +-----END CERTIFICATE----- From 1521d08285f501fca37f7cbc678ec7c0b822afdf Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 13 Feb 2016 17:13:07 -0500 Subject: [PATCH 0960/6300] family cetificates added --- contrib/certificates/family/i2p-dev.crt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 contrib/certificates/family/i2p-dev.crt diff --git a/contrib/certificates/family/i2p-dev.crt b/contrib/certificates/family/i2p-dev.crt new file mode 100644 index 00000000..a5796514 --- /dev/null +++ b/contrib/certificates/family/i2p-dev.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICCjCCAa2gAwIBAgIEfT9YJTAMBggqhkjOPQQDAgUAMHkxCzAJBgNVBAYTAlhY +MQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBBbm9ueW1v +dXMgTmV0d29yazEPMA0GA1UECxMGZmFtaWx5MR8wHQYDVQQDExZpMnAtZGV2LmZh +bWlseS5pMnAubmV0MB4XDTE1MTIwOTIxNDIzM1oXDTI1MTIwODIxNDIzM1oweTEL +MAkGA1UEBhMCWFgxCzAJBgNVBAgTAlhYMQswCQYDVQQHEwJYWDEeMBwGA1UEChMV +STJQIEFub255bW91cyBOZXR3b3JrMQ8wDQYDVQQLEwZmYW1pbHkxHzAdBgNVBAMT +FmkycC1kZXYuZmFtaWx5LmkycC5uZXQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC +AAR7FPSglYrxeSPzv74A1fTwjajZWV0TljqEMBS/56juZQB/7xOwrsHFHA0eEEF9 +dTH64wx3lhV/9sh/stwPU2MToyEwHzAdBgNVHQ4EFgQUQh4uRP1aaX8TJX5dljrS +CeFNjcAwDAYIKoZIzj0EAwIFAANJADBGAiEAhXlEKGCjJ4urpi2db3OIMl9pB+9t +M+oVtAqBamWvVBICIQDBaIqfwLzFameO5ULgGRMysKQkL0O5mH6xo910YQV8jQ== +-----END CERTIFICATE----- From 21dead31250843a54f880de2f6be704eb8270b8e Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 13 Feb 2016 17:56:42 -0500 Subject: [PATCH 0961/6300] increase lease expiration threshold --- LeaseSet.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LeaseSet.h b/LeaseSet.h index b9ec2d0b..cffa60dd 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -17,7 +17,7 @@ namespace tunnel namespace data { - const int LEASE_ENDDATE_THRESHOLD = 9500; // in milliseconds + const int LEASE_ENDDATE_THRESHOLD = 31000; // in milliseconds struct Lease { IdentHash tunnelGateway; From 80f81685d10146350ef47557859fb38ab3c206fe Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 13 Feb 2016 23:02:58 -0500 Subject: [PATCH 0962/6300] use rtt for ack timeout --- Destination.cpp | 28 +++++++++++++++++++++------- Garlic.cpp | 7 ++++++- Garlic.h | 2 ++ Streaming.cpp | 4 ++-- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 8e13db42..26330c29 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -505,18 +505,24 @@ namespace client } } - void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) { - assert(streamRequestComplete); + void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) + { + if (!streamRequestComplete) + { + LogPrint (eLogError, "Destination: request callback is not specified in CreateStream"); + return; + } auto leaseSet = FindLeaseSet (dest); if (leaseSet) streamRequestComplete(CreateStream (leaseSet, port)); else { + auto s = shared_from_this (); RequestDestination (dest, - [this, streamRequestComplete, port](std::shared_ptr ls) + [s, streamRequestComplete, port](std::shared_ptr ls) { if (ls) - streamRequestComplete(CreateStream (ls, port)); + streamRequestComplete(s->CreateStream (ls, port)); else streamRequestComplete (nullptr); }); @@ -597,7 +603,11 @@ namespace client { auto it = s->m_LeaseSetRequests.find (dest); if (it != s->m_LeaseSetRequests.end ()) - s->m_LeaseSetRequests.erase (it); + { + auto requestComplete = it->second->requestComplete; + s->m_LeaseSetRequests.erase (it); + if (requestComplete) requestComplete (nullptr); + } }); } @@ -615,8 +625,8 @@ namespace client if (!SendLeaseSetRequest (dest, floodfill, request)) { // request failed - if (request->requestComplete) request->requestComplete (nullptr); m_LeaseSetRequests.erase (dest); + if (request->requestComplete) request->requestComplete (nullptr); } } else // duplicate @@ -627,7 +637,10 @@ namespace client } } else + { LogPrint (eLogError, "Destination: Can't request LeaseSet, no floodfills found"); + if (requestComplete) requestComplete (nullptr); + } } bool ClientDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest, @@ -695,8 +708,9 @@ namespace client if (done) { - if (it->second->requestComplete) it->second->requestComplete (nullptr); + auto requestComplete = it->second->requestComplete; m_LeaseSetRequests.erase (it); + if (requestComplete) requestComplete (nullptr); } } } diff --git a/Garlic.cpp b/Garlic.cpp index f967a1e2..e11f8ec8 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -47,17 +47,22 @@ namespace garlic { if (!m_SharedRoutingPath) return nullptr; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - if (!m_SharedRoutingPath->outboundTunnel->IsEstablished () || + if (m_SharedRoutingPath->numTimesUsed >= ROUTING_PATH_MAX_NUM_TIMES_USED || + !m_SharedRoutingPath->outboundTunnel->IsEstablished () || ts*1000LL > m_SharedRoutingPath->remoteLease->endDate || ts > m_SharedRoutingPath->updateTime + ROUTING_PATH_EXPIRATION_TIMEOUT) m_SharedRoutingPath = nullptr; + if (m_SharedRoutingPath) m_SharedRoutingPath->numTimesUsed++; return m_SharedRoutingPath; } void GarlicRoutingSession::SetSharedRoutingPath (std::shared_ptr path) { if (path && path->outboundTunnel && path->remoteLease) + { path->updateTime = i2p::util::GetSecondsSinceEpoch (); + path->numTimesUsed = 0; + } else path = nullptr; m_SharedRoutingPath = path; diff --git a/Garlic.h b/Garlic.h index 1d8aa8cf..68041e1d 100644 --- a/Garlic.h +++ b/Garlic.h @@ -44,6 +44,7 @@ namespace garlic const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds const int LEASET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds const int ROUTING_PATH_EXPIRATION_TIMEOUT = 30; // 30 seconds + const int ROUTING_PATH_MAX_NUM_TIMES_USED = 10; // how many times might be used struct SessionTag: public i2p::data::Tag<32> { @@ -64,6 +65,7 @@ namespace garlic std::shared_ptr remoteLease; int rtt; // RTT uint32_t updateTime; // seconds since epoch + int numTimesUsed; }; class GarlicDestination; diff --git a/Streaming.cpp b/Streaming.cpp index 79f202be..ab6c6879 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -113,7 +113,7 @@ namespace stream if (!m_IsAckSendScheduled) { m_IsAckSendScheduled = true; - m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ACK_SEND_TIMEOUT)); + m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTT/10)); m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, shared_from_this (), std::placeholders::_1)); } @@ -274,7 +274,7 @@ namespace stream if (!seqn && m_RoutingSession) // first message confirmed m_RoutingSession->SetSharedRoutingPath ( std::make_shared ( - i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, m_RTT, 0})); + i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, m_RTT, 0, 0})); } else break; From 49b1e76585393ce542ba2179a241dd87ba577484 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 13 Feb 2016 23:10:51 -0500 Subject: [PATCH 0963/6300] use rtt for ack timeout --- Streaming.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index ab6c6879..a007fc01 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -113,7 +113,9 @@ namespace stream if (!m_IsAckSendScheduled) { m_IsAckSendScheduled = true; - m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTT/10)); + auto ackTimeout = m_RTT/10; + if (ackTimeout > ACK_SEND_TIMEOUT) ackTimeout = ACK_SEND_TIMEOUT; + m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ackTimeout)); m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, shared_from_this (), std::placeholders::_1)); } From ca56d3fc23889d6b419d77c18ca490e28c0d181f Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 14 Feb 2016 18:30:07 -0500 Subject: [PATCH 0964/6300] handle LeaseSet expiration correctly --- LeaseSet.cpp | 19 +++++++++---------- Streaming.cpp | 4 ++-- TunnelPool.h | 3 +++ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 561dc28c..891b73d6 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -42,7 +42,9 @@ namespace data auto signingKeyLen = localDestination->GetIdentity ()->GetSigningPublicKeyLen (); memset (m_Buffer + m_BufferLen, 0, signingKeyLen); m_BufferLen += signingKeyLen; - auto tunnels = pool->GetInboundTunnels (5); // 5 tunnels maximum + int numTunnels = pool->GetNumInboundTunnels () + 2; // 2 backup tunnels + if (numTunnels > 16) numTunnels = 16; // 16 tunnels maximum + auto tunnels = pool->GetInboundTunnels (numTunnels); m_Buffer[m_BufferLen] = tunnels.size (); // num leases m_BufferLen++; // leases @@ -151,12 +153,13 @@ namespace data else LogPrint (eLogWarning, "LeaseSet: Lease is expired already "); } - if (!m_ExpirationTime && m_Leases.empty ()) + if (!m_ExpirationTime) { LogPrint (eLogWarning, "LeaseSet: all leases are expired. Dropped"); m_IsValid = false; return; } + m_ExpirationTime += LEASE_ENDDATE_THRESHOLD; // delete old leases if (m_StoreLeases) { @@ -187,17 +190,13 @@ namespace data for (auto it: m_Leases) { auto endDate = it->endDate; - if (!withThreshold) - endDate -= i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000; + if (withThreshold) + endDate += LEASE_ENDDATE_THRESHOLD; + else + endDate -= LEASE_ENDDATE_THRESHOLD; if (ts < endDate) leases.push_back (it); } - if (leases.empty () && withThreshold) - { - for (auto it: m_Leases) - if (ts < it->endDate + LEASE_ENDDATE_THRESHOLD) - leases.push_back (it); - } return leases; } diff --git a/Streaming.cpp b/Streaming.cpp index a007fc01..ba41ab71 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -731,8 +731,7 @@ namespace stream { if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) { - auto remoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); - if (remoteLeaseSet) m_RemoteLeaseSet = remoteLeaseSet; // renew if possible + m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); if (!m_RemoteLeaseSet) LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); } @@ -774,6 +773,7 @@ namespace stream m_RemoteLeaseSet = nullptr; m_CurrentRemoteLease = nullptr; // re-request expired + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); } } else diff --git a/TunnelPool.h b/TunnelPool.h index 83b163d4..97a018b7 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -53,6 +53,9 @@ namespace tunnel bool IsActive () const { return m_IsActive; }; void SetActive (bool isActive) { m_IsActive = isActive; }; void DetachTunnels (); + + int GetNumInboundTunnels () const { return m_NumInboundTunnels; }; + int GetNumOutboundTunnels () const { return m_NumOutboundTunnels; }; private: From 882e7a845ed611901e8deda02b6e47987cab3209 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 14 Feb 2016 22:10:56 -0500 Subject: [PATCH 0965/6300] process remaining data from stream --- Garlic.h | 2 +- I2PTunnel.cpp | 30 +++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Garlic.h b/Garlic.h index 68041e1d..ea53dd03 100644 --- a/Garlic.h +++ b/Garlic.h @@ -44,7 +44,7 @@ namespace garlic const int OUTGOING_TAGS_CONFIRMATION_TIMEOUT = 10; // 10 seconds const int LEASET_CONFIRMATION_TIMEOUT = 4000; // in milliseconds const int ROUTING_PATH_EXPIRATION_TIMEOUT = 30; // 30 seconds - const int ROUTING_PATH_MAX_NUM_TIMES_USED = 10; // how many times might be used + const int ROUTING_PATH_MAX_NUM_TIMES_USED = 100; // how many times might be used struct SessionTag: public i2p::data::Tag<32> { diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index e936834d..07b9bd83 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -114,10 +114,25 @@ namespace client void I2PTunnelConnection::StreamReceive () { if (m_Stream) - m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), - std::bind (&I2PTunnelConnection::HandleStreamReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2), - I2P_TUNNEL_CONNECTION_MAX_IDLE); + { + if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || + m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular + { + m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), + std::bind (&I2PTunnelConnection::HandleStreamReceive, shared_from_this (), + std::placeholders::_1, std::placeholders::_2), + I2P_TUNNEL_CONNECTION_MAX_IDLE); + } + else // closed by peer + { + // get remaning data + auto len = m_Stream->ReadSome (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE); + if (len > 0) // still some data + Write (m_StreamBuffer, len); + else // no more data + Terminate (); + } + } } void I2PTunnelConnection::HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) @@ -126,7 +141,12 @@ namespace client { LogPrint (eLogError, "I2PTunnel: stream read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) - Terminate (); + { + if (bytes_transferred > 0) + Write (m_StreamBuffer, bytes_transferred); // postpone termination + else + Terminate (); + } } else Write (m_StreamBuffer, bytes_transferred); From ba6c0d0423fa4c271c0a17ab2857e29c40d948eb Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 15 Feb 2016 15:16:53 -0500 Subject: [PATCH 0966/6300] fixed messy http pages --- Streaming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index ba41ab71..085129bb 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -88,7 +88,7 @@ namespace stream } LogPrint (eLogDebug, "Streaming: Received seqn=", receivedSeqn); - if (isSyn || receivedSeqn == m_LastReceivedSequenceNumber + 1) + if (receivedSeqn == m_LastReceivedSequenceNumber + 1) { // we have received next in sequence message ProcessPacket (packet); From e1995b5c70aa3710f4eec2358909985109bb99df Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 15 Feb 2016 18:20:01 -0500 Subject: [PATCH 0967/6300] try to download default hosts.txt until success --- AddressBook.cpp | 50 ++++++++++++++++++++++++------------------------- AddressBook.h | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 1c2f3e24..6570e172 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -229,11 +229,7 @@ namespace client delete m_Storage; m_Storage = nullptr; } - if (m_DefaultSubscription) - { - delete m_DefaultSubscription; - m_DefaultSubscription = nullptr; - } + m_DefaultSubscription = nullptr; for (auto it: m_Subscriptions) delete it; m_Subscriptions.clear (); @@ -327,19 +323,6 @@ namespace client LoadHostsFromStream (f); m_IsLoaded = true; } - else - { - // if not found download it from http://i2p-projekt.i2p/hosts.txt - LogPrint (eLogInfo, "Addressbook: hosts.txt not found, trying to download it from default subscription."); - if (!m_IsDownloading) - { - m_IsDownloading = true; - if (!m_DefaultSubscription) - m_DefaultSubscription = new AddressBookSubscription (*this, DEFAULT_SUBSCRIPTION_ADDRESS); - m_DefaultSubscription->CheckSubscription (); - } - } - } void AddressBook::LoadHostsFromStream (std::istream& f) @@ -406,6 +389,11 @@ namespace client void AddressBook::DownloadComplete (bool success) { m_IsDownloading = false; + if (success && m_DefaultSubscription) + { + m_DefaultSubscription.reset (nullptr); + m_IsLoaded = true; + } if (m_SubscriptionsUpdateTimer) { m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes( @@ -418,8 +406,8 @@ namespace client void AddressBook::StartSubscriptions () { LoadSubscriptions (); - if (!m_Subscriptions.size ()) return; - + if (m_IsLoaded && m_Subscriptions.empty ()) return; + auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { @@ -444,12 +432,24 @@ namespace client { auto dest = i2p::client::context.GetSharedLocalDestination (); if (!dest) return; - if (m_IsLoaded && !m_IsDownloading && dest->IsReady () && !m_Subscriptions.empty ()) + if (!m_IsDownloading && dest->IsReady ()) { - // pick random subscription - auto ind = rand () % m_Subscriptions.size(); - m_IsDownloading = true; - m_Subscriptions[ind]->CheckSubscription (); + if (!m_IsLoaded) + { + // download it from http://i2p-projekt.i2p/hosts.txt + LogPrint (eLogInfo, "Addressbook: trying to download it from default subscription."); + if (!m_DefaultSubscription) + m_DefaultSubscription.reset (new AddressBookSubscription (*this, DEFAULT_SUBSCRIPTION_ADDRESS)); + m_IsDownloading = true; + m_DefaultSubscription->CheckSubscription (); + } + else if (!m_Subscriptions.empty ()) + { + // pick random subscription + auto ind = rand () % m_Subscriptions.size(); + m_IsDownloading = true; + m_Subscriptions[ind]->CheckSubscription (); + } } else { diff --git a/AddressBook.h b/AddressBook.h index b50b9d9b..f5f5270e 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -78,7 +78,7 @@ namespace client AddressBookStorage * m_Storage; volatile bool m_IsLoaded, m_IsDownloading; std::vector m_Subscriptions; - AddressBookSubscription * m_DefaultSubscription; // in case if we don't know any addresses yet + std::unique_ptr m_DefaultSubscription; // in case if we don't know any addresses yet boost::asio::deadline_timer * m_SubscriptionsUpdateTimer; }; From 2003b340364443296c9a5d0463b089577d84aee7 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 15 Feb 2016 21:40:49 -0500 Subject: [PATCH 0968/6300] 12 hours expiration if more than 2500 routers --- NetDb.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NetDb.cpp b/NetDb.cpp index 6376f44e..9f0e4312 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -375,6 +375,14 @@ namespace data total--; } } + else if (total > 2500) + { + if (ts > it.second->GetTimestamp () + 12*3600*1000LL) // 12 hours + { + it.second->SetUnreachable (true); + total--; + } + } else if (total > 300) { if (ts > it.second->GetTimestamp () + 30*3600*1000LL) // 30 hours From 2e9689886bb45dcb96ad69352b71c130b091bff5 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 16 Feb 2016 15:07:56 -0500 Subject: [PATCH 0969/6300] build with make added --- docs/build_notes_unix.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index 882a8ece..746aac2b 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -23,6 +23,13 @@ After successfull build i2pd could be installed with: ```bash make install ``` +or you can just use 'make' once you have all dependacies (boost and opensll) installed + +```bash +git clone https://github.com/PurpleI2P/i2pd.git +cd i2pd +make +``` Debian/Ubuntu ------------- From bf7982cc2e8d9c2cee00433cb1762298604f8023 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 16 Feb 2016 15:08:35 -0500 Subject: [PATCH 0970/6300] build with make added --- docs/build_notes_unix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index 746aac2b..292849a7 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -23,7 +23,7 @@ After successfull build i2pd could be installed with: ```bash make install ``` -or you can just use 'make' once you have all dependacies (boost and opensll) installed +or you can just use 'make' once you have all dependacies (boost and openssl) installed ```bash git clone https://github.com/PurpleI2P/i2pd.git From febc00d3577f795b9fb5c5707031f42e3a699066 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 16 Feb 2016 16:10:22 -0500 Subject: [PATCH 0971/6300] fixed race condition of DeliveryStatus message --- TunnelPool.cpp | 42 +++++++++++++++++++++++++++++++----------- TunnelPool.h | 1 + 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 72d6735a..c74ff475 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -201,7 +201,14 @@ namespace tunnel void TunnelPool::TestTunnels () { - for (auto it: m_Tests) + decltype(m_Tests) tests; + { + std::unique_lock l(m_TestsMutex); + tests = m_Tests; + m_Tests.clear (); + } + + for (auto it: tests) { LogPrint (eLogWarning, "Tunnels: test of tunnel ", it.first, " failed"); // if test failed again with another tunnel we consider it failed @@ -232,7 +239,7 @@ namespace tunnel it.second.second->SetState (eTunnelStateTestFailed); } } - m_Tests.clear (); + // new tests auto it1 = m_OutboundTunnels.begin (); auto it2 = m_InboundTunnels.begin (); @@ -253,7 +260,10 @@ namespace tunnel { uint32_t msgID; RAND_bytes ((uint8_t *)&msgID, 4); - m_Tests[msgID] = std::make_pair (*it1, *it2); + { + std::unique_lock l(m_TestsMutex); + m_Tests[msgID] = std::make_pair (*it1, *it2); + } (*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), CreateDeliveryStatusMsg (msgID)); it1++; it2++; @@ -276,16 +286,26 @@ namespace tunnel buf += 4; uint64_t timestamp = bufbe64toh (buf); - auto it = m_Tests.find (msgID); - if (it != m_Tests.end ()) + decltype(m_Tests)::mapped_type test; + bool found = false; + { + std::unique_lock l(m_TestsMutex); + auto it = m_Tests.find (msgID); + if (it != m_Tests.end ()) + { + found = true; + test = it->second; + m_Tests.erase (it); + } + } + if (found) { // restore from test failed state if any - if (it->second.first->GetState () == eTunnelStateTestFailed) - it->second.first->SetState (eTunnelStateEstablished); - if (it->second.second->GetState () == eTunnelStateTestFailed) - it->second.second->SetState (eTunnelStateEstablished); - LogPrint (eLogDebug, "Tunnels: test of ", it->first, " successful. ", i2p::util::GetMillisecondsSinceEpoch () - timestamp, " milliseconds"); - m_Tests.erase (it); + if (test.first->GetState () == eTunnelStateTestFailed) + test.first->SetState (eTunnelStateEstablished); + if (test.second->GetState () == eTunnelStateTestFailed) + test.second->SetState (eTunnelStateEstablished); + LogPrint (eLogDebug, "Tunnels: test of ", msgID, " successful. ", i2p::util::GetMillisecondsSinceEpoch () - timestamp, " milliseconds"); } else { diff --git a/TunnelPool.h b/TunnelPool.h index 97a018b7..0cd2057d 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -77,6 +77,7 @@ namespace tunnel std::set, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first mutable std::mutex m_OutboundTunnelsMutex; std::set, TunnelCreationTimeCmp> m_OutboundTunnels; + mutable std::mutex m_TestsMutex; std::map, std::shared_ptr > > m_Tests; bool m_IsActive; From f2168774a56bdbee549055836345a6afc6ad9b85 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 16 Feb 2016 22:57:38 -0500 Subject: [PATCH 0972/6300] check leaseset timestamp --- Destination.cpp | 34 ++++++++++++++++++++-------------- LeaseSet.cpp | 30 +++++++++++++++++++++++++++++- LeaseSet.h | 2 ++ 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 26330c29..e6380ffd 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -140,7 +140,7 @@ namespace client m_IsRunning = true; m_Pool->SetLocalDestination (shared_from_this ()); m_Pool->SetActive (true); - m_Thread = new std::thread (std::bind (&ClientDestination::Run, this)); + m_Thread = new std::thread (std::bind (&ClientDestination::Run, shared_from_this ())); m_StreamingDestination = std::make_shared (shared_from_this ()); // TODO: m_StreamingDestination->Start (); for (auto it: m_StreamingDestinationsByPorts) @@ -148,7 +148,7 @@ namespace client m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer, - this, std::placeholders::_1)); + shared_from_this (), std::placeholders::_1)); } } @@ -229,21 +229,22 @@ namespace client } data; memcpy (data.k, key, 32); memcpy (data.t, tag, 32); - m_Service.post ([this,data](void) + auto s = shared_from_this (); + m_Service.post ([s,data](void) { - this->AddSessionKey (data.k, data.t); + s->AddSessionKey (data.k, data.t); }); return true; } void ClientDestination::ProcessGarlicMessage (std::shared_ptr msg) { - m_Service.post (std::bind (&ClientDestination::HandleGarlicMessage, this, msg)); + m_Service.post (std::bind (&ClientDestination::HandleGarlicMessage, shared_from_this (), msg)); } void ClientDestination::ProcessDeliveryStatusMessage (std::shared_ptr msg) { - m_Service.post (std::bind (&ClientDestination::HandleDeliveryStatusMessage, this, msg)); + m_Service.post (std::bind (&ClientDestination::HandleDeliveryStatusMessage, shared_from_this (), msg)); } void ClientDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) @@ -286,15 +287,20 @@ namespace client if (it != m_RemoteLeaseSets.end ()) { leaseSet = it->second; - leaseSet->Update (buf + offset, len - offset); - if (leaseSet->IsValid ()) - LogPrint (eLogDebug, "Remote LeaseSet updated"); - else - { - LogPrint (eLogDebug, "Remote LeaseSet update failed"); - m_RemoteLeaseSets.erase (it); - leaseSet = nullptr; + if (leaseSet->IsNewer (buf + offset, len - offset)) + { + leaseSet->Update (buf + offset, len - offset); + if (leaseSet->IsValid ()) + LogPrint (eLogDebug, "Remote LeaseSet updated"); + else + { + LogPrint (eLogDebug, "Remote LeaseSet update failed"); + m_RemoteLeaseSets.erase (it); + leaseSet = nullptr; + } } + else + LogPrint (eLogDebug, "Remote LeaseSet is older. Not updated"); } else { diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 891b73d6..620c78db 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -182,7 +182,35 @@ namespace data m_IsValid = false; } } - + + uint64_t LeaseSet::ExtractTimestamp (const uint8_t * buf, size_t len) const + { + if (!m_Identity) return 0; + size_t size = m_Identity->GetFullLen (); + if (size > len) return 0; + size += 256; // encryption key + size += m_Identity->GetSigningPublicKeyLen (); // unused signing key + if (size > len) return 0; + uint8_t num = buf[size]; + size++; // num + if (size + num*44 > len) return 0; + uint64_t timestamp= 0 ; + for (int i = 0; i < num; i++) + { + size += 36; // gateway (32) + tunnelId(4) + auto endDate = bufbe64toh (buf + size); + size += 8; // end date + if (!timestamp || endDate < timestamp) + timestamp = endDate; + } + return timestamp; + } + + bool LeaseSet::IsNewer (const uint8_t * buf, size_t len) const + { + return true; //ExtractTimestamp (buf, len) > ExtractTimestamp (m_Buffer, m_BufferLen); + } + const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const { auto ts = i2p::util::GetMillisecondsSinceEpoch (); diff --git a/LeaseSet.h b/LeaseSet.h index cffa60dd..0afca269 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -47,6 +47,7 @@ namespace data LeaseSet (std::shared_ptr pool); ~LeaseSet () { delete[] m_Buffer; }; void Update (const uint8_t * buf, size_t len); + bool IsNewer (const uint8_t * buf, size_t len) const; void PopulateLeases (); // from buffer std::shared_ptr GetIdentity () const { return m_Identity; }; @@ -69,6 +70,7 @@ namespace data private: void ReadFromBuffer (bool readIdentity = true); + uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const; // min expiration time private: From b4ffca56a34a28d8fb866c19f42c584b9d5663fb Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Feb 2016 13:10:29 -0500 Subject: [PATCH 0973/6300] update lease's expiration time continiously --- LeaseSet.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 620c78db..c40968f3 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -48,6 +48,7 @@ namespace data m_Buffer[m_BufferLen] = tunnels.size (); // num leases m_BufferLen++; // leases + auto currentTime = i2p::util::GetMillisecondsSinceEpoch (); for (auto it: tunnels) { memcpy (m_Buffer + m_BufferLen, it->GetNextIdentHash (), 32); @@ -56,8 +57,9 @@ namespace data m_BufferLen += 4; // tunnel id uint64_t ts = it->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration ts *= 1000; // in milliseconds - ts += rand () % 6; // + random milliseconds 0-5 if (ts > m_ExpirationTime) m_ExpirationTime = ts; + // make sure leaseset is newer than previous, but adding some time to expiration date + ts += (currentTime - it->GetCreationTime ())*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2000 millisecond htobe64buf (m_Buffer + m_BufferLen, ts); m_BufferLen += 8; // end date } @@ -208,7 +210,7 @@ namespace data bool LeaseSet::IsNewer (const uint8_t * buf, size_t len) const { - return true; //ExtractTimestamp (buf, len) > ExtractTimestamp (m_Buffer, m_BufferLen); + return ExtractTimestamp (buf, len) > ExtractTimestamp (m_Buffer, m_BufferLen); } const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const From 713513aacca2ef15889f2efe7cf7cb29f413c5eb Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Feb 2016 15:36:55 -0500 Subject: [PATCH 0974/6300] flood newer RI/LS only --- NetDb.cpp | 112 ++++++++++++++++++++++++++++++------------------- NetDb.h | 6 +-- RouterInfo.cpp | 8 ++++ RouterInfo.h | 3 +- 4 files changed, 81 insertions(+), 48 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 9f0e4312..8516cbfe 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -149,22 +149,30 @@ namespace data } } - void NetDb::AddRouterInfo (const uint8_t * buf, int len) + bool NetDb::AddRouterInfo (const uint8_t * buf, int len) { IdentityEx identity; if (identity.FromBuffer (buf, len)) - AddRouterInfo (identity.GetIdentHash (), buf, len); + return AddRouterInfo (identity.GetIdentHash (), buf, len); + return false; } - void NetDb::AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len) + bool NetDb::AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len) { + bool updated = true; auto r = FindRouter (ident); if (r) { - auto ts = r->GetTimestamp (); - r->Update (buf, len); - if (r->GetTimestamp () > ts) + if (r->IsNewer (buf, len)) + { + r->Update (buf, len); LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64()); + } + else + { + LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64()); + updated = false; + } } else { @@ -182,27 +190,39 @@ namespace data m_Floodfills.push_back (r); } } + else + updated = false; } // take care about requested destination m_Requests.RequestComplete (ident, r); + return updated; } - void 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 from) { + bool updated = false; if (!from) // unsolicited LS must be received directly { auto it = m_LeaseSets.find(ident); if (it != m_LeaseSets.end ()) { - it->second->Update (buf, len); - if (it->second->IsValid ()) - LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase64()); - else + if (it->second->IsNewer (buf, len)) { - LogPrint (eLogWarning, "NetDb: LeaseSet update failed: ", ident.ToBase64()); - m_LeaseSets.erase (it); - } + it->second->Update (buf, len); + if (it->second->IsValid ()) + { + LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase64()); + updated = true; + } + else + { + LogPrint (eLogWarning, "NetDb: LeaseSet update failed: ", ident.ToBase64()); + m_LeaseSets.erase (it); + } + } + else + LogPrint (eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase64()); } else { @@ -211,11 +231,13 @@ namespace data { LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase64()); m_LeaseSets[ident] = leaseSet; + updated = true; } else LogPrint (eLogError, "NetDb: new LeaseSet validation failed: ", ident.ToBase64()); } } + return updated; } std::shared_ptr NetDb::FindRouter (const IdentHash& ident) const @@ -487,39 +509,14 @@ namespace data LogPrint (eLogError, "NetDb: no outbound tunnels for DatabaseStore reply found"); } offset += 32; - - if (context.IsFloodfill ()) - { - // flood it - auto floodMsg = NewI2NPShortMessage (); - uint8_t * payload = floodMsg->GetPayload (); - memcpy (payload, buf, 33); // key + type - htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); // zero reply token - auto msgLen = len - offset; - floodMsg->len += DATABASE_STORE_HEADER_SIZE + msgLen; - if (floodMsg->len < floodMsg->maxLen) - { - memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + offset, msgLen); - floodMsg->FillI2NPMessageHeader (eI2NPDatabaseStore); - std::set excluded; - for (int i = 0; i < 3; i++) - { - auto floodfill = GetClosestFloodfill (ident, excluded, true); // we need a floodfill close than us only - if (floodfill) - transports.SendMessage (floodfill->GetIdentHash (), floodMsg); - else - break; - } - } - else - LogPrint (eLogError, "Database store message is too long ", floodMsg->len); - } } - + size_t payloadOffset = offset; + + bool updated = false; if (buf[DATABASE_STORE_TYPE_OFFSET]) // type { LogPrint (eLogDebug, "NetDb: store request: LeaseSet"); - AddLeaseSet (ident, buf + offset, len - offset, m->from); + updated = AddLeaseSet (ident, buf + offset, len - offset, m->from); } else { @@ -534,7 +531,34 @@ namespace data uint8_t uncompressed[2048]; size_t uncompressedSize = m_Inflator.Inflate (buf + offset, size, uncompressed, 2048); if (uncompressedSize) - AddRouterInfo (ident, uncompressed, uncompressedSize); + updated = AddRouterInfo (ident, uncompressed, uncompressedSize); + } + + if (replyToken && context.IsFloodfill () && updated) + { + // flood updated + auto floodMsg = NewI2NPShortMessage (); + uint8_t * payload = floodMsg->GetPayload (); + memcpy (payload, buf, 33); // key + type + htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); // zero reply token + auto msgLen = len - payloadOffset; + floodMsg->len += DATABASE_STORE_HEADER_SIZE + msgLen; + if (floodMsg->len < floodMsg->maxLen) + { + memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + payloadOffset, msgLen); + floodMsg->FillI2NPMessageHeader (eI2NPDatabaseStore); + std::set excluded; + for (int i = 0; i < 3; i++) + { + auto floodfill = GetClosestFloodfill (ident, excluded, true); // we need a floodfill close than us only + if (floodfill) + transports.SendMessage (floodfill->GetIdentHash (), floodMsg); + else + break; + } + } + else + LogPrint (eLogError, "Database store message is too long ", floodMsg->len); } } diff --git a/NetDb.h b/NetDb.h index f1b97728..cad00aa7 100644 --- a/NetDb.h +++ b/NetDb.h @@ -34,9 +34,9 @@ namespace data void Start (); void Stop (); - void AddRouterInfo (const uint8_t * buf, int len); - void AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len); - void AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr from); + bool AddRouterInfo (const uint8_t * buf, int len); + bool AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len); + bool AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr from); std::shared_ptr FindRouter (const IdentHash& ident) const; std::shared_ptr FindLeaseSet (const IdentHash& destination) const; std::shared_ptr FindRouterProfile (const IdentHash& ident) const; diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 56482234..3267392b 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -459,6 +459,14 @@ namespace data s.write (properties.str ().c_str (), properties.str ().size ()); } + bool RouterInfo::IsNewer (const uint8_t * buf, size_t len) const + { + if (!m_RouterIdentity) return false; + size_t size = m_RouterIdentity->GetFullLen (); + if (size + 8 > len) return false; + return bufbe64toh (buf + size) > m_Timestamp; + } + const uint8_t * RouterInfo::LoadBuffer () { if (!m_Buffer) diff --git a/RouterInfo.h b/RouterInfo.h index 2d32edef..4870eb50 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -157,7 +157,8 @@ namespace data void Update (const uint8_t * buf, int len); void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; }; - + bool IsNewer (const uint8_t * buf, size_t len) const; + // implements RoutingDestination const IdentHash& GetIdentHash () const { return m_RouterIdentity->GetIdentHash (); }; const uint8_t * GetEncryptionPublicKey () const { return m_RouterIdentity->GetStandardIdentity ().publicKey; }; From 32fe2e7974f9ac52a39fa9965cb81a102252e899 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Feb 2016 19:36:07 -0500 Subject: [PATCH 0975/6300] correct monotonic expiration time calculation --- LeaseSet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index c40968f3..5d259b33 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -59,7 +59,7 @@ namespace data ts *= 1000; // in milliseconds if (ts > m_ExpirationTime) m_ExpirationTime = ts; // make sure leaseset is newer than previous, but adding some time to expiration date - ts += (currentTime - it->GetCreationTime ())*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2000 millisecond + ts += (currentTime - it->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs htobe64buf (m_Buffer + m_BufferLen, ts); m_BufferLen += 8; // end date } From b4e324ec0ecd7a94c77b31572fbeb7f1275ef382 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Feb 2016 21:24:21 -0500 Subject: [PATCH 0976/6300] flood to 3 closest floodfills --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 8516cbfe..b4b4ef08 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -550,7 +550,7 @@ namespace data std::set excluded; for (int i = 0; i < 3; i++) { - auto floodfill = GetClosestFloodfill (ident, excluded, true); // we need a floodfill close than us only + auto floodfill = GetClosestFloodfill (ident, excluded); if (floodfill) transports.SendMessage (floodfill->GetIdentHash (), floodMsg); else From 68cc75cada8bb5b6aacba5136b650a0bfb0235ee Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 18 Feb 2016 00:00:00 +0000 Subject: [PATCH 0977/6300] * Base.cpp : add T32 character set + accessor --- Base.cpp | 14 +++++++++++++- Base.h | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Base.cpp b/Base.cpp index 1479d62f..dbf887cf 100644 --- a/Base.cpp +++ b/Base.cpp @@ -6,6 +6,18 @@ namespace i2p { namespace data { + static const char T32[32] = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'k', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 't', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '2', '3', '4', '5', '6', '7', + }; + + const char * GetBase32SubstitutionTable () + { + return T32; + } + static void iT64Build(void); /* @@ -16,7 +28,7 @@ namespace data * Direct Substitution Table */ - static char T64[64] = { + static const char T64[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', diff --git a/Base.h b/Base.h index d598542b..0f48a78a 100644 --- a/Base.h +++ b/Base.h @@ -12,6 +12,7 @@ namespace data { size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len); size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len ); + const char * GetBase32SubstitutionTable (); const char * GetBase64SubstitutionTable (); size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen); From f190ee951c96a991d687eb569315856b1ed59a7f Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 18 Feb 2016 00:00:00 +0000 Subject: [PATCH 0978/6300] * use characters sets from Base.cpp - remove ABook class --- FS.cpp | 34 ++++------------------------------ FS.h | 10 ---------- 2 files changed, 4 insertions(+), 40 deletions(-) diff --git a/FS.cpp b/FS.cpp index 758b81c8..4a1b670a 100644 --- a/FS.cpp +++ b/FS.cpp @@ -24,24 +24,6 @@ namespace fs { #endif HashedStorage NetDB("netDb", "r", "routerInfo-", "dat"); HashedStorage Peers("peerProfiles", "p", "profile-", "txt"); - ABookStorage ABook("addressbook", "b", "", "b32"); - - static const char T32[32] = { - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - 'i', 'k', 'k', 'l', 'm', 'n', 'o', 'p', - 'q', 'r', 't', 't', 'u', 'v', 'w', 'x', - 'y', 'z', '2', '3', '4', '5', '6', '7', - }; - static const char T64[64] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '-', '~' - }; const std::string & GetAppName () { return appName; @@ -90,12 +72,10 @@ namespace fs { if (boost::filesystem::exists(destinations)) boost::filesystem::create_directory(destinations); - NetDB.SetRoot(dataDir); - NetDB.Init(T64, 64); - Peers.SetRoot(dataDir); - Peers.Init(T64, 64); - ABook.SetRoot(dataDir); - ABook.Init(T32, 32); + NetDB.SetPlace(dataDir); + NetDB.Init(i2p::data::GetBase64SubstitutionTable(), 64); + Peers.SetPlace(dataDir); + Peers.Init(i2p::data::GetBase64SubstitutionTable(), 64); return true; } @@ -177,13 +157,7 @@ namespace fs { } } - std::string ABookStorage::IndexPath() { - std::string path = root + i2p::fs::dirSep + "addresses.csv"; - return path; - } - HashedStorage & GetNetDB() { return NetDB; } HashedStorage & GetPeerProfiles() { return Peers; } - ABookStorage & GetAddressBook() { return ABook; } } // fs } // i2p diff --git a/FS.h b/FS.h index 80b07353..bb5f44b8 100644 --- a/FS.h +++ b/FS.h @@ -49,15 +49,6 @@ namespace fs { void Traverse(std::vector & files); }; - /** @brief Slightly extended HashedStorage */ - class ABookStorage : public HashedStorage { - public: - ABookStorage(const char *n, const char *p1, const char *p2, const char *s): - HashedStorage(n, p1, p2, s) {}; - - std::string IndexPath(); - }; - /** @brief Returns current application name, default 'i2pd' */ const std::string & GetAppName (); /** @brief Set applicaton name, affects autodetection of datadir */ @@ -138,6 +129,5 @@ namespace fs { /* accessors */ HashedStorage & GetNetDB(); HashedStorage & GetPeerProfiles(); - ABookStorage & GetAddressBook(); } // fs } // i2p From 2b92a039bb81e32eb5d22e971f9cb3ae57b7e78b Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 18 Feb 2016 00:00:00 +0000 Subject: [PATCH 0979/6300] * FS.h : more comments --- FS.h | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/FS.h b/FS.h index bb5f44b8..9d7f0e9b 100644 --- a/FS.h +++ b/FS.h @@ -23,6 +23,8 @@ namespace fs { * const char alphabet[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; * auto h = HashedStorage("name", "y", "z-", ".txt"); * h.SetRoot("/tmp/hs-test"); + * h.GetName() -> gives "name" + * h.GetRoot() -> gives "/tmp/hs-test/name" * h.Init(alphabet, 8); <- creates needed dirs, 8 is size of alphabet * h.Path("abcd"); <- returns /tmp/hs-test/name/ya/z-abcd.txt * h.Remove("abcd"); <- removes /tmp/hs-test/name/ya/z-abcd.txt, if it exists @@ -31,21 +33,27 @@ namespace fs { */ class HashedStorage { protected: - std::string root; - std::string name; - std::string prefix1; - std::string prefix2; - std::string suffix; + std::string root; /**< path to storage with it's name included */ + std::string name; /**< name of the storage */ + std::string prefix1; /**< hashed directory prefix */ + std::string prefix2; /**< prefix of file in storage */ + std::string suffix; /**< suffix of file in storage (extension) */ public: HashedStorage(const char *n, const char *p1, const char *p2, const char *s): name(n), prefix1(p1), prefix2(p2), suffix(s) {}; + /** create subdirs in storage */ bool Init(const char* chars, size_t cnt); const std::string & GetRoot() { return this->root; } + const std::string & GetName() { return this->name; } + /** set directory where to place storage directory */ void SetRoot(const std::string & path); + /** path to file with given ident */ std::string Path(const std::string & ident); + /** remove file by ident */ void Remove(const std::string & ident); + /** find all files in storage and store list in provided vector */ void Traverse(std::vector & files); }; From 464a2281062d0f3dd24bb31042049069ad7933a2 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 18 Feb 2016 00:00:00 +0000 Subject: [PATCH 0980/6300] * FS.cpp : rename method --- FS.cpp | 2 +- FS.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FS.cpp b/FS.cpp index 4a1b670a..c77cbe6d 100644 --- a/FS.cpp +++ b/FS.cpp @@ -104,7 +104,7 @@ namespace fs { return boost::filesystem::remove(path); } - void HashedStorage::SetRoot(const std::string &path) { + void HashedStorage::SetPlace(const std::string &path) { root = path + i2p::fs::dirSep + name; } diff --git a/FS.h b/FS.h index 9d7f0e9b..64b50940 100644 --- a/FS.h +++ b/FS.h @@ -22,7 +22,7 @@ namespace fs { * * const char alphabet[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; * auto h = HashedStorage("name", "y", "z-", ".txt"); - * h.SetRoot("/tmp/hs-test"); + * h.SetPlace("/tmp/hs-test"); * h.GetName() -> gives "name" * h.GetRoot() -> gives "/tmp/hs-test/name" * h.Init(alphabet, 8); <- creates needed dirs, 8 is size of alphabet @@ -48,7 +48,7 @@ namespace fs { const std::string & GetRoot() { return this->root; } const std::string & GetName() { return this->name; } /** set directory where to place storage directory */ - void SetRoot(const std::string & path); + void SetPlace(const std::string & path); /** path to file with given ident */ std::string Path(const std::string & ident); /** remove file by ident */ From 138d57143afc19df2e12ca72f61d3a0d25dcf3b1 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 18 Feb 2016 00:00:00 +0000 Subject: [PATCH 0981/6300] * FS.cpp : add const to accessors --- FS.cpp | 2 +- FS.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FS.cpp b/FS.cpp index c77cbe6d..f6125954 100644 --- a/FS.cpp +++ b/FS.cpp @@ -124,7 +124,7 @@ namespace fs { return true; } - std::string HashedStorage::Path(const std::string & ident) { + std::string HashedStorage::Path(const std::string & ident) const { std::string safe_ident = ident; std::replace(safe_ident.begin(), safe_ident.end(), '/', '-'); std::replace(safe_ident.begin(), safe_ident.end(), '\\', '-'); diff --git a/FS.h b/FS.h index 64b50940..0c4e246a 100644 --- a/FS.h +++ b/FS.h @@ -45,12 +45,12 @@ namespace fs { /** create subdirs in storage */ bool Init(const char* chars, size_t cnt); - const std::string & GetRoot() { return this->root; } - const std::string & GetName() { return this->name; } + const std::string & GetRoot() const { return this->root; } + const std::string & GetName() const { return this->name; } /** set directory where to place storage directory */ void SetPlace(const std::string & path); /** path to file with given ident */ - std::string Path(const std::string & ident); + std::string Path(const std::string & ident) const; /** remove file by ident */ void Remove(const std::string & ident); /** find all files in storage and store list in provided vector */ From 85bd7a63c6695d60d86d0c35584b0cbd171135f3 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 18 Feb 2016 00:00:00 +0000 Subject: [PATCH 0982/6300] * AddressBook : embed HashedStorage instance into AddressBookFilesystemStorage class --- AddressBook.cpp | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 540fd74c..fe5070f7 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -22,8 +22,12 @@ namespace client // TODO: this is actually proxy class class AddressBookFilesystemStorage: public AddressBookStorage { + private: + i2p::fs::HashedStorage storage; + std::string indexPath; + public: - AddressBookFilesystemStorage () {}; + AddressBookFilesystemStorage (); std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const; void AddAddress (std::shared_ptr address); void RemoveAddress (const i2p::data::IdentHash& ident); @@ -32,9 +36,17 @@ namespace client int Save (const std::map& addresses); }; + AddressBookFilesystemStorage::AddressBookFilesystemStorage(): + storage("addressbook", "b", "", "b32") + { + storage.SetPlace(i2p::fs::GetDataDir()); + storage.Init(i2p::data::GetBase32SubstitutionTable(), 32); + indexPath = storage.GetRoot() + i2p::fs::dirSep + "addresses.csv"; + } + std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const { - std::string filename = i2p::fs::GetAddressBook().Path(ident.ToBase32()); + std::string filename = storage.Path(ident.ToBase32()); std::ifstream f(filename, std::ifstream::binary); if (!f.is_open ()) { LogPrint(eLogDebug, "Addressbook: Requested, but not found: ", filename); @@ -57,7 +69,7 @@ namespace client void AddressBookFilesystemStorage::AddAddress (std::shared_ptr address) { - std::string path = i2p::fs::GetAddressBook().Path( address->GetIdentHash().ToBase32() ); + std::string path = storage.Path( address->GetIdentHash().ToBase32() ); std::ofstream f (path, std::ofstream::binary | std::ofstream::out); if (!f.is_open ()) { LogPrint (eLogError, "Addresbook: can't open file ", path); @@ -72,20 +84,19 @@ namespace client void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident) { - i2p::fs::GetAddressBook().Remove( ident.ToBase32() ); + storage.Remove( ident.ToBase32() ); } int AddressBookFilesystemStorage::Load (std::map& addresses) { int num = 0; std::string s; - std::string index = i2p::fs::GetAddressBook().IndexPath(); - std::ifstream f (index, std::ifstream::in); // in text mode + std::ifstream f (indexPath, std::ifstream::in); // in text mode if (f.is_open ()) { - LogPrint(eLogInfo, "Addressbook: using index file ", index); + LogPrint(eLogInfo, "Addressbook: using index file ", indexPath); } else { - LogPrint(eLogWarning, "Addressbook: Can't open ", index); + LogPrint(eLogWarning, "Addressbook: Can't open ", indexPath); return 0; } @@ -120,11 +131,10 @@ namespace client } int num = 0; - std::string index = i2p::fs::GetAddressBook().IndexPath(); - std::ofstream f (index, std::ofstream::out); // in text mode + std::ofstream f (indexPath, std::ofstream::out); // in text mode if (!f.is_open ()) { - LogPrint (eLogWarning, "Addressbook: Can't open ", index); + LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath); return 0; } From 2a4ba8d349a4de8f5a23f01807778568e2b2815e Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 18 Feb 2016 00:00:00 +0000 Subject: [PATCH 0983/6300] * Addressbook : move storage init code from constructor to Init() : was too early --- AddressBook.cpp | 9 +++++---- AddressBook.h | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index fe5070f7..3b630a65 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -27,21 +27,21 @@ namespace client std::string indexPath; public: - AddressBookFilesystemStorage (); + AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") {}; std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const; void AddAddress (std::shared_ptr address); void RemoveAddress (const i2p::data::IdentHash& ident); + bool Init (); int Load (std::map& addresses); int Save (const std::map& addresses); }; - AddressBookFilesystemStorage::AddressBookFilesystemStorage(): - storage("addressbook", "b", "", "b32") + bool AddressBookFilesystemStorage::Init() { storage.SetPlace(i2p::fs::GetDataDir()); - storage.Init(i2p::data::GetBase32SubstitutionTable(), 32); indexPath = storage.GetRoot() + i2p::fs::dirSep + "addresses.csv"; + return storage.Init(i2p::data::GetBase32SubstitutionTable(), 32); } std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const @@ -159,6 +159,7 @@ namespace client void AddressBook::Start () { + m_Storage->Init(); LoadHosts (); /* try storage, then hosts.txt, then download */ StartSubscriptions (); } diff --git a/AddressBook.h b/AddressBook.h index 9ddce82a..16403e1e 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -35,6 +35,7 @@ namespace client virtual void AddAddress (std::shared_ptr address) = 0; virtual void RemoveAddress (const i2p::data::IdentHash& ident) = 0; + virtual bool Init () = 0; virtual int Load (std::map& addresses) = 0; virtual int Save (const std::map& addresses) = 0; }; From 9a6d478eb1e264646a5593f6d6fa9993759dab09 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 18 Feb 2016 13:19:31 -0500 Subject: [PATCH 0984/6300] handle compressed addressbook --- AddressBook.cpp | 26 +++++++++++++++++++++++--- AddressBook.h | 1 + Base.cpp | 32 ++++++++++++++++++++++++++++++++ Base.h | 8 +++++++- util.h | 1 + 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 6570e172..ca96882e 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -507,6 +507,7 @@ namespace client << "Host: " << u.host_ << "\r\n" << "Accept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n" + //<< "Accept-Encoding: gzip\r\n" << "Connection: close\r\n"; if (m_Etag.length () > 0) // etag request << i2p::util::http::IF_NONE_MATCH << ": \"" << m_Etag << "\"\r\n"; @@ -545,7 +546,7 @@ namespace client response >> status; // status if (status == 200) // OK { - bool isChunked = false; + bool isChunked = false, isGzip = false; std::string header, statusMessage; std::getline (response, statusMessage); // read until new line meaning end of header @@ -563,6 +564,8 @@ namespace client 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"); } } LogPrint (eLogInfo, "Addressbook: ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); @@ -570,13 +573,13 @@ namespace client { success = true; if (!isChunked) - m_Book.LoadHostsFromStream (response); + success = ProcessResponse (response, isGzip); else { // merge chunks std::stringstream merged; i2p::util::http::MergeChunkedResponse (response, merged); - m_Book.LoadHostsFromStream (merged); + success = ProcessResponse (merged, isGzip); } } } @@ -599,6 +602,23 @@ namespace client m_Book.DownloadComplete (success); } + + bool AddressBookSubscription::ProcessResponse (std::stringstream& s, bool isGzip) + { + if (isGzip) + { + std::stringstream uncompressed; + i2p::data::GzipInflator inflator; + inflator.Inflate (s, uncompressed); + if (!uncompressed.fail ()) + m_Book.LoadHostsFromStream (uncompressed); + else + return false; + } + else + m_Book.LoadHostsFromStream (s); + return true; + } } } diff --git a/AddressBook.h b/AddressBook.h index f5f5270e..19d8744c 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -92,6 +92,7 @@ namespace client private: void Request (); + bool ProcessResponse (std::stringstream& s, bool isGzip = false); private: diff --git a/Base.cpp b/Base.cpp index 1479d62f..813b8dfa 100644 --- a/Base.cpp +++ b/Base.cpp @@ -302,6 +302,38 @@ namespace data } } + bool GzipInflator::Inflate (const uint8_t * in, size_t inLen, std::ostream& s) + { + m_IsDirty = true; + uint8_t * out = new uint8_t[GZIP_CHUNK_SIZE]; + m_Inflator.next_in = const_cast(in); + m_Inflator.avail_in = inLen; + int ret; + do + { + m_Inflator.next_out = out; + m_Inflator.avail_out = GZIP_CHUNK_SIZE; + ret = inflate (&m_Inflator, Z_NO_FLUSH); + if (ret < 0) + { + LogPrint (eLogError, "Decompression error ", ret); + inflateEnd (&m_Inflator); + s.setstate(std::ios_base::failbit); + break; + } + else + s.write ((char *)out, GZIP_CHUNK_SIZE - m_Inflator.avail_out); + } + while (!m_Inflator.avail_out); // more data to read + delete[] out; + return ret == Z_STREAM_END || ret < 0; + } + + void GzipInflator::Inflate (const std::stringstream& in, std::ostream& out) + { + Inflate ((const uint8_t *)in.str ().c_str (), in.str ().length (), out); + } + GzipDeflator::GzipDeflator (): m_IsDirty (false) { memset (&m_Deflator, 0, sizeof (m_Deflator)); diff --git a/Base.h b/Base.h index d598542b..b5ac04c3 100644 --- a/Base.h +++ b/Base.h @@ -5,6 +5,8 @@ #include #include #include +#include +#include namespace i2p { @@ -92,6 +94,7 @@ namespace data }; }; + const size_t GZIP_CHUNK_SIZE = 16384; class GzipInflator { public: @@ -100,7 +103,10 @@ namespace data ~GzipInflator (); size_t Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen); - + bool Inflate (const uint8_t * in, size_t inLen, std::ostream& s); + // return true when finshed or error, s failbit will be set in case of error + void Inflate (const std::stringstream& in, std::ostream& out); + private: z_stream m_Inflator; diff --git a/util.h b/util.h index 0377ef8d..15765f37 100644 --- a/util.h +++ b/util.h @@ -34,6 +34,7 @@ namespace util const char IF_MODIFIED_SINCE[] = "If-Modified-Since"; const char LAST_MODIFIED[] = "Last-Modified"; const char TRANSFER_ENCODING[] = "Transfer-Encoding"; + const char CONTENT_ENCODING[] = "Content-Encoding"; std::string GetHttpContent (std::istream& response); void MergeChunkedResponse (std::istream& response, std::ostream& merged); From e2aa2709acedcc58aa9ddeba62078e6b7caf93e5 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 18 Feb 2016 15:57:43 -0500 Subject: [PATCH 0985/6300] family added --- Family.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Family.h | 32 ++++++++++++++++++++++ NetDb.cpp | 1 + NetDb.h | 2 ++ filelist.mk | 3 ++- 5 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 Family.cpp create mode 100644 Family.h diff --git a/Family.cpp b/Family.cpp new file mode 100644 index 00000000..211d2f54 --- /dev/null +++ b/Family.cpp @@ -0,0 +1,76 @@ +#include +#include +#include "util.h" +#include "Log.h" +#include "Family.h" + +namespace i2p +{ +namespace data +{ + Families::Families () + { + } + + Families::~Families () + { + } + + void Families::LoadCertificate (const std::string& filename) + { + SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ()); + int ret = SSL_CTX_use_certificate_file (ctx, filename.c_str (), SSL_FILETYPE_PEM); + if (ret) + { + SSL * ssl = SSL_new (ctx); + X509 * cert = SSL_get_certificate (ssl); + // verify + if (cert) + { + // extract issuer name + char name[100]; + X509_NAME_oneline (X509_get_issuer_name(cert), name, 100); + auto pkey = X509_get_pubkey (cert); + int keyType = EVP_PKEY_type(pkey->type); + switch (keyType) + { + case EVP_PKEY_DSA: + // TODO: + break; + case EVP_PKEY_EC: + { + //EC_KEY * ecKey = EVP_PKEY_get0_EC_KEY (pkey); + break; + } + default: + LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported"); + } + } + SSL_free (ssl); + } + else + LogPrint (eLogError, "Family: Can't open certificate file ", filename); + SSL_CTX_free (ctx); + } + + void Families::LoadCertificates () + { + boost::filesystem::path familyDir = i2p::util::filesystem::GetCertificatesDir() / "family"; + + if (!boost::filesystem::exists (familyDir)) return; + int numCertificates = 0; + boost::filesystem::directory_iterator end; // empty + for (boost::filesystem::directory_iterator it (familyDir); it != end; ++it) + { + if (boost::filesystem::is_regular_file (it->status()) && it->path ().extension () == ".crt") + { + LoadCertificate (it->path ().string ()); + numCertificates++; + } + } + if (numCertificates > 0) + LogPrint (eLogInfo, "Family: ", numCertificates, " certificates loaded"); + } +} +} + diff --git a/Family.h b/Family.h new file mode 100644 index 00000000..ca8dac3f --- /dev/null +++ b/Family.h @@ -0,0 +1,32 @@ +#ifndef FAMILY_H__ +#define FAMILY_H_ + +#include +#include +#include +#include "Signature.h" + +namespace i2p +{ +namespace data +{ + class Families + { + public: + + Families (); + ~Families (); + void LoadCertificates (); + + private: + + void LoadCertificate (const std::string& filename); + + private: + + std::map > m_SigningKeys; + }; +} +} + +#endif diff --git a/NetDb.cpp b/NetDb.cpp index b4b4ef08..df464664 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -37,6 +37,7 @@ namespace data void NetDb::Start () { + m_Families.LoadCertificates (); Load (); if (m_RouterInfos.size () < 25) // reseed if # of router less than 50 Reseed (); diff --git a/NetDb.h b/NetDb.h index cad00aa7..33fd6e27 100644 --- a/NetDb.h +++ b/NetDb.h @@ -18,6 +18,7 @@ #include "TunnelPool.h" #include "Reseed.h" #include "NetDbRequests.h" +#include "Family.h" namespace i2p { @@ -95,6 +96,7 @@ namespace data GzipInflator m_Inflator; Reseeder * m_Reseeder; + Families m_Families; friend class NetDbRequests; NetDbRequests m_Requests; diff --git a/filelist.mk b/filelist.mk index 1d46b8fc..166be50e 100644 --- a/filelist.mk +++ b/filelist.mk @@ -4,7 +4,8 @@ LIB_SRC = \ Reseed.cpp RouterContext.cpp RouterInfo.cpp Signature.cpp SSU.cpp \ SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp TransitTunnel.cpp \ Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp TunnelGateway.cpp \ - Destination.cpp Base.cpp I2PEndian.cpp Config.cpp util.cpp api.cpp + Destination.cpp Base.cpp I2PEndian.cpp Config.cpp Family.cpp util.cpp \ + api.cpp LIB_CLIENT_SRC = \ AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \ From 47bf0ef591eb2b4b32be1e1c194b8d1227986f97 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 18 Feb 2016 16:28:43 -0500 Subject: [PATCH 0986/6300] free pkey after usage --- Family.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Family.cpp b/Family.cpp index 211d2f54..6c96812c 100644 --- a/Family.cpp +++ b/Family.cpp @@ -45,6 +45,7 @@ namespace data default: LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported"); } + EVP_PKEY_free (pkey); } SSL_free (ssl); } From 3053a9b6a0bc417202b1720d4e73b6ef22d7f969 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 18 Feb 2016 20:35:14 -0500 Subject: [PATCH 0987/6300] enable i2p gzip compression --- AddressBook.cpp | 4 ++++ Base.cpp | 5 +++-- Base.h | 2 +- util.h | 3 ++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index ca96882e..006d503c 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -508,6 +508,7 @@ namespace client << "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"; @@ -557,6 +558,7 @@ namespace client if (colon != std::string::npos) { std::string field = header.substr (0, colon); + colon++; header.resize (header.length () - 1); // delete \r if (field == i2p::util::http::ETAG) m_Etag = header.substr (colon + 1); @@ -566,6 +568,8 @@ namespace client 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"); + else if (field == i2p::util::http::CONTENT_ENCODING1) // Content-encoding + isGzip = !header.compare (colon + 1, std::string::npos, "x-i2p-gzip"); } } LogPrint (eLogInfo, "Addressbook: ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); diff --git a/Base.cpp b/Base.cpp index 813b8dfa..cb6faf2d 100644 --- a/Base.cpp +++ b/Base.cpp @@ -329,9 +329,10 @@ namespace data return ret == Z_STREAM_END || ret < 0; } - void GzipInflator::Inflate (const std::stringstream& in, std::ostream& out) + void GzipInflator::Inflate (std::stringstream& in, std::ostream& out) { - Inflate ((const uint8_t *)in.str ().c_str (), in.str ().length (), out); + auto str = in.str ().substr (in.tellg ()); + Inflate ((const uint8_t *)str.c_str (), str.length (), out); } GzipDeflator::GzipDeflator (): m_IsDirty (false) diff --git a/Base.h b/Base.h index b5ac04c3..19dc31da 100644 --- a/Base.h +++ b/Base.h @@ -105,7 +105,7 @@ namespace data size_t Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen); bool Inflate (const uint8_t * in, size_t inLen, std::ostream& s); // return true when finshed or error, s failbit will be set in case of error - void Inflate (const std::stringstream& in, std::ostream& out); + void Inflate (std::stringstream& in, std::ostream& out); private: diff --git a/util.h b/util.h index 15765f37..3b05c170 100644 --- a/util.h +++ b/util.h @@ -35,7 +35,8 @@ namespace util const char LAST_MODIFIED[] = "Last-Modified"; const char TRANSFER_ENCODING[] = "Transfer-Encoding"; const char CONTENT_ENCODING[] = "Content-Encoding"; - + const char CONTENT_ENCODING1[] = "Content-encoding"; + std::string GetHttpContent (std::istream& response); void MergeChunkedResponse (std::istream& response, std::ostream& merged); std::string urlDecode(const std::string& data); From 094d9193b9b0662863578495cafed599dab65237 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 18 Feb 2016 22:34:14 -0500 Subject: [PATCH 0988/6300] start addressbook first --- ClientContext.cpp | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 6d05cde1..9967a33a 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -37,6 +37,8 @@ namespace client m_SharedLocalDestination->Start (); } + m_AddressBook.Start (); + std::shared_ptr localDestination; bool httproxy; i2p::config::GetOption("httpproxy.enabled", httproxy); if (httproxy) { @@ -94,25 +96,19 @@ namespace client m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); m_BOBCommandChannel->Start (); } - - m_AddressBook.Start (); } void ClientContext::Stop () { - if (m_HttpProxy) { - LogPrint(eLogInfo, "Clients: stopping HTTP Proxy"); - m_HttpProxy->Stop(); - delete m_HttpProxy; - m_HttpProxy = nullptr; - } + LogPrint(eLogInfo, "Clients: stopping HTTP Proxy"); + m_HttpProxy->Stop(); + delete m_HttpProxy; + m_HttpProxy = nullptr; - if (m_SocksProxy) { - LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy"); - m_SocksProxy->Stop(); - delete m_SocksProxy; - m_SocksProxy = nullptr; - } + LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy"); + m_SocksProxy->Stop(); + delete m_SocksProxy; + m_SocksProxy = nullptr; for (auto& it: m_ClientTunnels) { From 76b49f6985bf4c7b80c667c94c301c64dec10576 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 18 Feb 2016 22:34:55 -0500 Subject: [PATCH 0989/6300] uncompress stream by chunks --- Base.cpp | 11 ++++++++--- Base.h | 3 +-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Base.cpp b/Base.cpp index cb6faf2d..3017d910 100644 --- a/Base.cpp +++ b/Base.cpp @@ -329,10 +329,15 @@ namespace data return ret == Z_STREAM_END || ret < 0; } - void GzipInflator::Inflate (std::stringstream& in, std::ostream& out) + void GzipInflator::Inflate (std::istream& in, std::ostream& out) { - auto str = in.str ().substr (in.tellg ()); - Inflate ((const uint8_t *)str.c_str (), str.length (), out); + uint8_t * buf = new uint8_t[GZIP_CHUNK_SIZE]; + while (!in.eof ()) + { + in.read ((char *)buf, GZIP_CHUNK_SIZE); + Inflate (buf, in.gcount (), out); + } + delete[] buf; } GzipDeflator::GzipDeflator (): m_IsDirty (false) diff --git a/Base.h b/Base.h index 19dc31da..e6f9567d 100644 --- a/Base.h +++ b/Base.h @@ -6,7 +6,6 @@ #include #include #include -#include namespace i2p { @@ -105,7 +104,7 @@ namespace data size_t Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen); bool Inflate (const uint8_t * in, size_t inLen, std::ostream& s); // return true when finshed or error, s failbit will be set in case of error - void Inflate (std::stringstream& in, std::ostream& out); + void Inflate (std::istream& in, std::ostream& out); private: From f1d4818045320816ce4220abb229bb1e536d5236 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 18 Feb 2016 22:39:09 -0500 Subject: [PATCH 0990/6300] Family.cpp added --- build/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 646e8a6e..f7e884f9 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -45,6 +45,7 @@ set (LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/Base.cpp" "${CMAKE_SOURCE_DIR}/util.cpp" "${CMAKE_SOURCE_DIR}/Datagram.cpp" + "${CMAKE_SOURCE_DIR}/Family.cpp" "${CMAKE_SOURCE_DIR}/Signature.cpp" "${CMAKE_SOURCE_DIR}/api.cpp" ) From c5b6da7201717039ce8cdac30bb4ea6fc69e7e0d Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 19 Feb 2016 10:04:52 -0500 Subject: [PATCH 0991/6300] case-insensitive http responses --- AddressBook.cpp | 6 +++--- util.cpp | 1 + util.h | 15 ++++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 006d503c..f08a4041 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -558,6 +558,7 @@ namespace client 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) @@ -567,9 +568,8 @@ namespace client 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"); - else if (field == i2p::util::http::CONTENT_ENCODING1) // Content-encoding - isGzip = !header.compare (colon + 1, std::string::npos, "x-i2p-gzip"); + isGzip = !header.compare (colon + 1, std::string::npos, "gzip") || + !header.compare (colon + 1, std::string::npos, "x-i2p-gzip"); } } LogPrint (eLogInfo, "Addressbook: ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); diff --git a/util.cpp b/util.cpp index 1ea5fcd7..e25ddb3f 100644 --- a/util.cpp +++ b/util.cpp @@ -212,6 +212,7 @@ namespace http if (colon != std::string::npos) { std::string field = header.substr (0, colon); + boost::to_lower (field); if (field == i2p::util::http::TRANSFER_ENCODING) isChunked = (header.find ("chunked", colon + 1) != std::string::npos); } diff --git a/util.h b/util.h index 3b05c170..81f178f8 100644 --- a/util.h +++ b/util.h @@ -29,14 +29,15 @@ namespace util namespace http { - const char ETAG[] = "ETag"; + // in (lower case) + const char ETAG[] = "etag"; // ETag + const char LAST_MODIFIED[] = "last-modified"; // Last-Modified + const char TRANSFER_ENCODING[] = "transfer-encoding"; // Transfer-Encoding + const char CONTENT_ENCODING[] = "content-encoding"; // Content-Encoding + // out const char IF_NONE_MATCH[] = "If-None-Match"; - const char IF_MODIFIED_SINCE[] = "If-Modified-Since"; - const char LAST_MODIFIED[] = "Last-Modified"; - const char TRANSFER_ENCODING[] = "Transfer-Encoding"; - const char CONTENT_ENCODING[] = "Content-Encoding"; - const char CONTENT_ENCODING1[] = "Content-encoding"; - + const char IF_MODIFIED_SINCE[] = "If-Modified-Since"; + std::string GetHttpContent (std::istream& response); void MergeChunkedResponse (std::istream& response, std::ostream& merged); std::string urlDecode(const std::string& data); From cb64072f7b2e48155b83f9067480c9e38c48a28a Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 19 Feb 2016 11:18:01 -0500 Subject: [PATCH 0992/6300] fixed windows build --- Family.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Family.cpp b/Family.cpp index 6c96812c..5470a92c 100644 --- a/Family.cpp +++ b/Family.cpp @@ -1,6 +1,6 @@ -#include -#include #include "util.h" +#include +#include #include "Log.h" #include "Family.h" From 7bfc3562af5befe514cd76693b6989aa8b40dcc4 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 19 Feb 2016 16:13:46 -0500 Subject: [PATCH 0993/6300] extract EcDSA key from family certificate --- Family.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- Family.h | 3 +++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Family.cpp b/Family.cpp index 5470a92c..7e2d6e6c 100644 --- a/Family.cpp +++ b/Family.cpp @@ -1,7 +1,9 @@ +#include #include "util.h" #include #include #include "Log.h" +#include "Crypto.h" #include "Family.h" namespace i2p @@ -24,12 +26,14 @@ namespace data { SSL * ssl = SSL_new (ctx); X509 * cert = SSL_get_certificate (ssl); - // verify if (cert) { + std::shared_ptr verifier; // extract issuer name char name[100]; X509_NAME_oneline (X509_get_issuer_name(cert), name, 100); + char * family = strstr (name, ".family"); + if (family) family[0] = 0; auto pkey = X509_get_pubkey (cert); int keyType = EVP_PKEY_type(pkey->type); switch (keyType) @@ -39,13 +43,37 @@ namespace data break; case EVP_PKEY_EC: { - //EC_KEY * ecKey = EVP_PKEY_get0_EC_KEY (pkey); + EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey); + if (ecKey) + { + auto group = EC_KEY_get0_group (ecKey); + if (group) + { + int curve = EC_GROUP_get_curve_name (group); + if (curve == NID_X9_62_prime256v1) + { + uint8_t signingKey[64]; + BIGNUM * x = BN_new(), * y = BN_new(); + EC_POINT_get_affine_coordinates_GFp (group, + EC_KEY_get0_public_key (ecKey), x, y, NULL); + i2p::crypto::bn2buf (x, signingKey, 32); + i2p::crypto::bn2buf (y, signingKey + 32, 32); + BN_free (x); BN_free (y); + verifier = std::make_shared(signingKey); + } + else + LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); + } + EC_KEY_free (ecKey); + } break; } default: LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported"); } EVP_PKEY_free (pkey); + if (verifier) + m_SigningKeys[name] = verifier; } SSL_free (ssl); } @@ -72,6 +100,23 @@ namespace data if (numCertificates > 0) LogPrint (eLogInfo, "Family: ", numCertificates, " certificates loaded"); } + + bool Families::VerifyFamily (const char * family, const IdentHash& ident, + const char * signature, const char * key) + { + uint8_t buf[50], signatureBuf[64]; + size_t len = strlen (family), signatureLen = strlen (signature); + memcpy (buf, family, len); + memcpy (buf + len, (const uint8_t *)ident, 32); + len += 32; + Base64ToByteStream (signature, signatureLen, signatureBuf, 64); + auto it = m_SigningKeys.find (family); + if (it != m_SigningKeys.end ()) + return it->second->Verify (buf, len, signatureBuf); + // TODO: process key + return true; + } + } } diff --git a/Family.h b/Family.h index ca8dac3f..78abd5b6 100644 --- a/Family.h +++ b/Family.h @@ -5,6 +5,7 @@ #include #include #include "Signature.h" +#include "Identity.h" namespace i2p { @@ -17,6 +18,8 @@ namespace data Families (); ~Families (); void LoadCertificates (); + bool VerifyFamily (const char * family, const IdentHash& ident, + const char * signature, const char * key); private: From 4eef9e780f1e226f1c1d20ee9f3d8b973ee833a6 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 19 Feb 2016 16:37:41 -0500 Subject: [PATCH 0994/6300] extract and verify family from RouterInfo --- Family.cpp | 6 +++--- Family.h | 4 ++-- NetDb.h | 1 + RouterInfo.cpp | 18 +++++++++++++++++- RouterInfo.h | 2 +- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Family.cpp b/Family.cpp index 7e2d6e6c..a93e31ef 100644 --- a/Family.cpp +++ b/Family.cpp @@ -101,12 +101,12 @@ namespace data LogPrint (eLogInfo, "Family: ", numCertificates, " certificates loaded"); } - bool Families::VerifyFamily (const char * family, const IdentHash& ident, + bool Families::VerifyFamily (const std::string& family, const IdentHash& ident, const char * signature, const char * key) { uint8_t buf[50], signatureBuf[64]; - size_t len = strlen (family), signatureLen = strlen (signature); - memcpy (buf, family, len); + size_t len = family.length (), signatureLen = strlen (signature); + memcpy (buf, family.c_str (), len); memcpy (buf + len, (const uint8_t *)ident, 32); len += 32; Base64ToByteStream (signature, signatureLen, signatureBuf, 64); diff --git a/Family.h b/Family.h index 78abd5b6..51cf6bf5 100644 --- a/Family.h +++ b/Family.h @@ -18,8 +18,8 @@ namespace data Families (); ~Families (); void LoadCertificates (); - bool VerifyFamily (const char * family, const IdentHash& ident, - const char * signature, const char * key); + bool VerifyFamily (const std::string& family, const IdentHash& ident, + const char * signature, const char * key = nullptr); private: diff --git a/NetDb.h b/NetDb.h index 33fd6e27..1ec2ade5 100644 --- a/NetDb.h +++ b/NetDb.h @@ -62,6 +62,7 @@ namespace data void PostI2NPMsg (std::shared_ptr msg); void Reseed (); + Families& GetFamilies () { return m_Families; }; // for web interface int GetNumRouters () const { return m_RouterInfos.size (); }; diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 3267392b..ec9a4acd 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -8,6 +8,7 @@ #include "Base.h" #include "Timestamp.h" #include "Log.h" +#include "NetDb.h" #include "RouterInfo.h" namespace i2p @@ -262,11 +263,26 @@ namespace data if (!strcmp (key, "caps")) ExtractCaps (value); // check netId - if (!strcmp (key, "netId") && atoi (value) != I2PD_NET_ID) + else if (!strcmp (key, "netId") && atoi (value) != I2PD_NET_ID) { LogPrint (eLogError, "Unexpected netid=", value); m_IsUnreachable = true; } + // family + else if (!strcmp (key, "family")) + { + m_Family = value; + boost::to_lower (m_Family); + } + else if (!strcmp (key, "family.sig")) + { + if (!netdb.GetFamilies ().VerifyFamily (m_Family, GetIdentHash (), value)) + { + LogPrint (eLogWarning, "RouterInfo: family signature verification failed"); + m_Family.clear (); + } + } + if (!s) return; } diff --git a/RouterInfo.h b/RouterInfo.h index 4870eb50..57212be9 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -180,7 +180,7 @@ namespace data private: - std::string m_FullPath; + std::string m_FullPath, m_Family; std::shared_ptr m_RouterIdentity; uint8_t * m_Buffer; size_t m_BufferLen; From 0e6d8c4e25c7b948cfa80a35308788c0ef2b4843 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 19 Feb 2016 20:09:48 -0500 Subject: [PATCH 0995/6300] i2pd-dev family certificate added --- contrib/certificates/family/i2pd-dev.crt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 contrib/certificates/family/i2pd-dev.crt diff --git a/contrib/certificates/family/i2pd-dev.crt b/contrib/certificates/family/i2pd-dev.crt new file mode 100644 index 00000000..35cc3c47 --- /dev/null +++ b/contrib/certificates/family/i2pd-dev.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC3TCCAoOgAwIBAgIJANRKmecaImBfMAoGCCqGSM49BAMCMHoxCzAJBgNVBAYT +AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u +eW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYDVQQDDBdpMnBkLWRl +di5mYW1pbHkuaTJwLm5ldDAeFw0xNjAyMjAwMTAyNDRaFw0yNjAyMTcwMTAyNDRa +MHoxCzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNV +BAoMFUkyUCBBbm9ueW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYD +VQQDDBdpMnBkLWRldi5mYW1pbHkuaTJwLm5ldDCCAUswggEDBgcqhkjOPQIBMIH3 +AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP////////////// +/zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY12Ko6k+ez +671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3gZ9+kARB +BGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO5+tKfA+e +FivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racXnoTzucrC +/GMlUQIBAQNCAAS2ZXQ1HnyCyYclXZWMs7D+yotVfOxl4XFGr2hQDQrlA+S3z/A9 +dt9NWC6/nOd3fDiroJMdMjawxZzZwFJRblBhMAoGCCqGSM49BAMCA0gAMEUCID2m +noL+4w3rA/d+G/dUs7/07h8VZXZkN0UTuvsSL57+AiEA5WTJmeMKJ0nsCBJCzT7q +kBuE/o2l2fSZ50oPJ3CqMAY= +-----END CERTIFICATE----- From 02310d4af67ebac7129117bc2920fd7e62d24917 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 20 Feb 2016 01:00:00 +0000 Subject: [PATCH 0996/6300] * Family : use i2p::fs::ReadDir instead direct boost::filesystem call --- Family.cpp | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Family.cpp b/Family.cpp index a93e31ef..442f096d 100644 --- a/Family.cpp +++ b/Family.cpp @@ -1,7 +1,7 @@ #include -#include "util.h" #include #include +#include "FS.h" #include "Log.h" #include "Crypto.h" #include "Family.h" @@ -84,21 +84,24 @@ namespace data void Families::LoadCertificates () { - boost::filesystem::path familyDir = i2p::util::filesystem::GetCertificatesDir() / "family"; - - if (!boost::filesystem::exists (familyDir)) return; + std::string certDir = i2p::fs::DataDirPath("certificates", "family"); + std::vector files; int numCertificates = 0; - boost::filesystem::directory_iterator end; // empty - for (boost::filesystem::directory_iterator it (familyDir); it != end; ++it) - { - if (boost::filesystem::is_regular_file (it->status()) && it->path ().extension () == ".crt") - { - LoadCertificate (it->path ().string ()); - numCertificates++; - } + + if (!i2p::fs::ReadDir(certDir, files)) { + LogPrint(eLogWarning, "Reseed: Can't load reseed certificates from ", certDir); + return; + } + + for (const std::string & file : files) { + if (file.compare(file.size() - 4, 4, ".crt") != 0) { + LogPrint(eLogWarning, "Family: ignoring file ", file); + continue; + } + LoadCertificate (file); + numCertificates++; } - if (numCertificates > 0) - LogPrint (eLogInfo, "Family: ", numCertificates, " certificates loaded"); + LogPrint (eLogInfo, "Family: ", numCertificates, " certificates loaded"); } bool Families::VerifyFamily (const std::string& family, const IdentHash& ident, @@ -116,7 +119,6 @@ namespace data // TODO: process key return true; } - } } From d312d753e960fd7473ffe274f7917c6c3f15973f Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 20 Feb 2016 01:00:00 +0000 Subject: [PATCH 0997/6300] * Destination.cpp : fix lambda with 4.7 --- Destination.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Destination.cpp b/Destination.cpp index 5163f99d..3d7f34c1 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -460,7 +460,7 @@ namespace client { auto s = shared_from_this (); RequestLeaseSet (GetIdentHash (), - [s](std::shared_ptr leaseSet) + [s,this](std::shared_ptr leaseSet) { if (leaseSet) { From 33a33e3c712cb631c2babb137819475e31b0850c Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 20 Feb 2016 01:00:00 +0000 Subject: [PATCH 0998/6300] * i2p::util::http::GetHttpContent() : use std::transform instead boost --- util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util.cpp b/util.cpp index e871dac1..bd77e682 100644 --- a/util.cpp +++ b/util.cpp @@ -81,7 +81,7 @@ namespace http if (colon != std::string::npos) { std::string field = header.substr (0, colon); - boost::to_lower (field); + std::transform(field.begin(), field.end(), field.begin(), ::tolower); if (field == i2p::util::http::TRANSFER_ENCODING) isChunked = (header.find ("chunked", colon + 1) != std::string::npos); } From 008583396d0beb3e856a5f73c3bfedf0fdc437e8 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 20 Feb 2016 08:33:13 -0500 Subject: [PATCH 0999/6300] extract CN --- Family.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Family.cpp b/Family.cpp index a93e31ef..8db0723c 100644 --- a/Family.cpp +++ b/Family.cpp @@ -32,8 +32,13 @@ namespace data // extract issuer name char name[100]; X509_NAME_oneline (X509_get_issuer_name(cert), name, 100); - char * family = strstr (name, ".family"); - if (family) family[0] = 0; + char * cn = strstr (name, "CN="); + if (cn) + { + cn += 3; + char * family = strstr (cn, ".family"); + if (family) family[0] = 0; + } auto pkey = X509_get_pubkey (cert); int keyType = EVP_PKEY_type(pkey->type); switch (keyType) @@ -72,8 +77,8 @@ namespace data LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported"); } EVP_PKEY_free (pkey); - if (verifier) - m_SigningKeys[name] = verifier; + if (verifier && cn) + m_SigningKeys[cn] = verifier; } SSL_free (ssl); } From 4db63d113c311583c09ded2864a2f64e065b8394 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 20 Feb 2016 09:22:09 -0500 Subject: [PATCH 1000/6300] i2pd-dev certificate updated --- contrib/certificates/family/i2pd-dev.crt | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/contrib/certificates/family/i2pd-dev.crt b/contrib/certificates/family/i2pd-dev.crt index 35cc3c47..3bb6f429 100644 --- a/contrib/certificates/family/i2pd-dev.crt +++ b/contrib/certificates/family/i2pd-dev.crt @@ -1,18 +1,13 @@ -----BEGIN CERTIFICATE----- -MIIC3TCCAoOgAwIBAgIJANRKmecaImBfMAoGCCqGSM49BAMCMHoxCzAJBgNVBAYT +MIIB6TCCAY+gAwIBAgIJAI7G9MXxh7OjMAoGCCqGSM49BAMCMHoxCzAJBgNVBAYT AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u eW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYDVQQDDBdpMnBkLWRl -di5mYW1pbHkuaTJwLm5ldDAeFw0xNjAyMjAwMTAyNDRaFw0yNjAyMTcwMTAyNDRa +di5mYW1pbHkuaTJwLm5ldDAeFw0xNjAyMjAxNDE2MzhaFw0yNjAyMTcxNDE2Mzha MHoxCzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNV BAoMFUkyUCBBbm9ueW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYD -VQQDDBdpMnBkLWRldi5mYW1pbHkuaTJwLm5ldDCCAUswggEDBgcqhkjOPQIBMIH3 -AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP////////////// -/zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY12Ko6k+ez -671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3gZ9+kARB -BGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO5+tKfA+e -FivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racXnoTzucrC -/GMlUQIBAQNCAAS2ZXQ1HnyCyYclXZWMs7D+yotVfOxl4XFGr2hQDQrlA+S3z/A9 -dt9NWC6/nOd3fDiroJMdMjawxZzZwFJRblBhMAoGCCqGSM49BAMCA0gAMEUCID2m -noL+4w3rA/d+G/dUs7/07h8VZXZkN0UTuvsSL57+AiEA5WTJmeMKJ0nsCBJCzT7q -kBuE/o2l2fSZ50oPJ3CqMAY= +VQQDDBdpMnBkLWRldi5mYW1pbHkuaTJwLm5ldDBZMBMGByqGSM49AgEGCCqGSM49 +AwEHA0IABMlWL3loKVOfsA8Rm91QR53Il69mQiaB7n3rUhfPkJb9MYc1S4198azE +iSnNZSXicKDPIifaCgvONmbACzElHc8wCgYIKoZIzj0EAwIDSAAwRQIgYWmSFuai +TJvVrlB5RlbiiNFCEootjWP8BFM3t/yFeaQCIQDkg4xcQIRGTHhjrCsxmlz9KcRF +G+eIF+ATfI93nPseLw== -----END CERTIFICATE----- From 230af9cafab7fd8b7a5a46b877389d0860449bac Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 20 Feb 2016 20:20:19 -0500 Subject: [PATCH 1001/6300] set router's family --- Config.cpp | 3 ++- Daemon.cpp | 7 ++++++- Family.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ Family.h | 5 ++++- RouterContext.cpp | 26 ++++++++++++++++++++++---- RouterContext.h | 4 +--- RouterInfo.cpp | 8 ++++---- RouterInfo.h | 6 ++++++ 8 files changed, 90 insertions(+), 14 deletions(-) diff --git a/Config.cpp b/Config.cpp index 8e212360..81c2a2ce 100644 --- a/Config.cpp +++ b/Config.cpp @@ -113,7 +113,8 @@ namespace config { ("log", value()->default_value(""), "Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") - ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") + ("family", value()->default_value(""), "Specify a family, router belongs to") + ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") ("host", value()->default_value("0.0.0.0"), "External IP") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") diff --git a/Daemon.cpp b/Daemon.cpp index 242f4bbf..0687d0b9 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -120,7 +120,7 @@ namespace i2p i2p::context.SetHighBandwidth (); else i2p::context.SetLowBandwidth (); - } + } else if (isFloodfill) { LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'"); @@ -132,6 +132,11 @@ namespace i2p i2p::context.SetLowBandwidth (); } + std::string family; i2p::config::GetOption("family", family); + i2p::context.SetFamily (family); + if (family.length () > 0) + LogPrint(eLogInfo, "Daemon: family set to ", family); + return true; } diff --git a/Family.cpp b/Family.cpp index 8db0723c..d83adf2a 100644 --- a/Family.cpp +++ b/Family.cpp @@ -122,6 +122,51 @@ namespace data return true; } + std::string CreateFamilySignature (const std::string& family, const IdentHash& ident) + { + std::string sig; + auto filename = i2p::util::filesystem::GetDefaultDataDir() / "family" / (family + ".key"); + SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ()); + int ret = SSL_CTX_use_PrivateKey_file (ctx, filename.string ().c_str (), SSL_FILETYPE_PEM); + if (ret) + { + SSL * ssl = SSL_new (ctx); + EVP_PKEY * pkey = SSL_get_privatekey (ssl); + EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey); + if (ecKey) + { + auto group = EC_KEY_get0_group (ecKey); + if (group) + { + int curve = EC_GROUP_get_curve_name (group); + if (curve == NID_X9_62_prime256v1) + { + uint8_t signingPrivateKey[32], buf[50], signature[64]; + i2p::crypto::bn2buf (EC_KEY_get0_private_key (ecKey), signingPrivateKey, 32); + i2p::crypto::ECDSAP256Signer signer (signingPrivateKey); + size_t len = family.length (); + memcpy (buf, family.c_str (), len); + memcpy (buf + len, (const uint8_t *)ident, 32); + len += 32; + signer.Sign (buf, len, signature); + len = Base64EncodingBufferSize (64); + char * b64 = new char[len+1]; + len = ByteStreamToBase64 (signature, 64, b64, len); + b64[len] = 0; + sig = b64; + delete[] b64; + } + else + LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); + } + } + SSL_free (ssl); + } + else + LogPrint (eLogError, "Family: Can't open keys file ", filename.string ()); + SSL_CTX_free (ctx); + return sig; + } } } diff --git a/Family.h b/Family.h index 51cf6bf5..42a37292 100644 --- a/Family.h +++ b/Family.h @@ -1,5 +1,5 @@ #ifndef FAMILY_H__ -#define FAMILY_H_ +#define FAMILY_H__ #include #include @@ -29,6 +29,9 @@ namespace data std::map > m_SigningKeys; }; + + std::string CreateFamilySignature (const std::string& family, const IdentHash& ident); + // return base64 signature of empty string in case of failure } } diff --git a/RouterContext.cpp b/RouterContext.cpp index 90961948..070e52ad 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -8,6 +8,7 @@ #include "util.h" #include "version.h" #include "Log.h" +#include "Family.h" #include "RouterContext.h" namespace i2p @@ -141,12 +142,29 @@ namespace i2p { m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill); // we don't publish number of routers and leaseset for non-floodfill - m_RouterInfo.DeleteProperty (ROUTER_INFO_PROPERTY_LEASESETS); - m_RouterInfo.DeleteProperty (ROUTER_INFO_PROPERTY_ROUTERS); + m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS); + m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS); } UpdateRouterInfo (); } + void RouterContext::SetFamily (const std::string& family) + { + std::string signature; + if (family.length () > 0) + signature = i2p::data::CreateFamilySignature (family, GetIdentHash ()); + if (signature.length () > 0) + { + m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY, family); + m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY_SIG, signature); + } + else + { + m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY); + m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY_SIG); + } + } + void RouterContext::SetHighBandwidth () { if (!m_RouterInfo.IsHighBandwidth () || m_RouterInfo.IsExtraBandwidth ()) @@ -284,8 +302,8 @@ namespace i2p if (m_IsFloodfill) { // update routers and leasesets - m_RouterInfo.SetProperty (ROUTER_INFO_PROPERTY_LEASESETS, boost::lexical_cast(i2p::data::netdb.GetNumLeaseSets ())); - m_RouterInfo.SetProperty (ROUTER_INFO_PROPERTY_ROUTERS, boost::lexical_cast(i2p::data::netdb.GetNumRouters ())); + m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS, boost::lexical_cast(i2p::data::netdb.GetNumLeaseSets ())); + m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS, boost::lexical_cast(i2p::data::netdb.GetNumRouters ())); UpdateRouterInfo (); } } diff --git a/RouterContext.h b/RouterContext.h index 8c07b17f..bc1ee836 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -15,9 +15,6 @@ namespace i2p const char ROUTER_INFO[] = "router.info"; const char ROUTER_KEYS[] = "router.keys"; const int ROUTER_INFO_UPDATE_INTERVAL = 1800; // 30 minutes - - const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets"; - const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters"; enum RouterStatus { @@ -60,6 +57,7 @@ namespace i2p void SetReachable (); bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); + void SetFamily (const std::string& family); void SetHighBandwidth (); void SetLowBandwidth (); void SetExtraBandwidth (); diff --git a/RouterInfo.cpp b/RouterInfo.cpp index ec9a4acd..1bf05a99 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -263,18 +263,18 @@ namespace data if (!strcmp (key, "caps")) ExtractCaps (value); // check netId - else if (!strcmp (key, "netId") && atoi (value) != I2PD_NET_ID) + else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != I2PD_NET_ID) { - LogPrint (eLogError, "Unexpected netid=", value); + LogPrint (eLogError, "Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); m_IsUnreachable = true; } // family - else if (!strcmp (key, "family")) + else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY)) { m_Family = value; boost::to_lower (m_Family); } - else if (!strcmp (key, "family.sig")) + else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG)) { if (!netdb.GetFamilies ().VerifyFamily (m_Family, GetIdentHash (), value)) { diff --git a/RouterInfo.h b/RouterInfo.h index 57212be9..5b77e170 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -14,6 +14,12 @@ namespace i2p { namespace data { + const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets"; + const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters"; + const char ROUTER_INFO_PROPERTY_NETID[] = "netId"; + const char ROUTER_INFO_PROPERTY_FAMILY[] = "family"; + const char ROUTER_INFO_PROPERTY_FAMILY_SIG[] = "family.sig"; + const char CAPS_FLAG_FLOODFILL = 'f'; const char CAPS_FLAG_HIDDEN = 'H'; const char CAPS_FLAG_REACHABLE = 'R'; From d3746e0119175f0d23c77fde7ac1d5013323a891 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 20 Feb 2016 01:00:00 +0000 Subject: [PATCH 1002/6300] * FS.h : add include guards --- FS.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/FS.h b/FS.h index 0c4e246a..f80840bc 100644 --- a/FS.h +++ b/FS.h @@ -6,6 +6,9 @@ * See full license text in LICENSE file at top of project tree */ +#ifndef FS_H__ +#define FS_H__ + #include #include #include @@ -139,3 +142,5 @@ namespace fs { HashedStorage & GetPeerProfiles(); } // fs } // i2p + +#endif // /* FS_H__ */ From b69fbdda9a6f8dc0891a6505789279342f917359 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 20 Feb 2016 01:00:00 +0000 Subject: [PATCH 1003/6300] * NetDb : move storage from FS.cpp to NetDb.cpp --- FS.cpp | 4 ---- FS.h | 1 - NetDb.cpp | 11 ++++++----- NetDb.h | 4 ++-- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/FS.cpp b/FS.cpp index f6125954..ac3d0935 100644 --- a/FS.cpp +++ b/FS.cpp @@ -22,7 +22,6 @@ namespace fs { #else std::string dirSep = "/"; #endif - HashedStorage NetDB("netDb", "r", "routerInfo-", "dat"); HashedStorage Peers("peerProfiles", "p", "profile-", "txt"); const std::string & GetAppName () { @@ -72,8 +71,6 @@ namespace fs { if (boost::filesystem::exists(destinations)) boost::filesystem::create_directory(destinations); - NetDB.SetPlace(dataDir); - NetDB.Init(i2p::data::GetBase64SubstitutionTable(), 64); Peers.SetPlace(dataDir); Peers.Init(i2p::data::GetBase64SubstitutionTable(), 64); return true; @@ -157,7 +154,6 @@ namespace fs { } } - HashedStorage & GetNetDB() { return NetDB; } HashedStorage & GetPeerProfiles() { return Peers; } } // fs } // i2p diff --git a/FS.h b/FS.h index f80840bc..a3f071e5 100644 --- a/FS.h +++ b/FS.h @@ -138,7 +138,6 @@ namespace fs { } /* accessors */ - HashedStorage & GetNetDB(); HashedStorage & GetPeerProfiles(); } // fs } // i2p diff --git a/NetDb.cpp b/NetDb.cpp index 428f1c36..1678850b 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -7,7 +7,6 @@ #include "I2PEndian.h" #include "Base.h" #include "Log.h" -#include "FS.h" #include "Timestamp.h" #include "I2NPProtocol.h" #include "Tunnel.h" @@ -25,7 +24,7 @@ namespace data { NetDb netdb; - NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr) + NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat") { } @@ -37,6 +36,8 @@ namespace data void NetDb::Start () { + m_Storage.SetPlace(i2p::fs::GetDataDir()); + m_Storage.Init(i2p::data::GetBase64SubstitutionTable(), 64); m_Families.LoadCertificates (); Load (); if (m_RouterInfos.size () < 25) // reseed if # of router less than 50 @@ -313,7 +314,7 @@ namespace data m_LastLoad = i2p::util::GetSecondsSinceEpoch(); std::vector files; - i2p::fs::GetNetDB().Traverse(files); + m_Storage.Traverse(files); for (auto path : files) LoadRouterInfo(path); @@ -329,7 +330,7 @@ namespace data for (auto it: m_RouterInfos) { std::string ident = it.second->GetIdentHashBase64(); - std::string path = i2p::fs::GetNetDB().Path(ident); + std::string path = m_Storage.Path(ident); if (it.second->IsUpdated ()) { it.second->SaveToFile (path); it.second->SetUpdated (false); @@ -376,7 +377,7 @@ namespace data if (it.second->IsUnreachable ()) { total--; // delete RI file - i2p::fs::GetNetDB().Remove(ident); + m_Storage.Remove(ident); deletedCount++; // delete from floodfills list if (it.second->IsFloodfill ()) { diff --git a/NetDb.h b/NetDb.h index 0c59ff0f..7efbfcf2 100644 --- a/NetDb.h +++ b/NetDb.h @@ -9,6 +9,7 @@ #include #include #include "Base.h" +#include "FS.h" #include "Queue.h" #include "I2NPProtocol.h" #include "RouterInfo.h" @@ -97,11 +98,10 @@ namespace data GzipInflator m_Inflator; Reseeder * m_Reseeder; Families m_Families; + i2p::fs::HashedStorage m_Storage; friend class NetDbRequests; NetDbRequests m_Requests; - - static const char m_NetDbPath[]; }; extern NetDb netdb; From 0d15eceacb3eee65327eb7a221491b6faa6c1357 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 20 Feb 2016 01:00:00 +0000 Subject: [PATCH 1004/6300] * Profiling : move storage from FS.cpp to Profiling.cpp --- FS.cpp | 5 ----- FS.h | 3 --- NetDb.cpp | 1 + Profiling.cpp | 14 +++++++++++--- Profiling.h | 1 + 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/FS.cpp b/FS.cpp index ac3d0935..9416d6b4 100644 --- a/FS.cpp +++ b/FS.cpp @@ -22,7 +22,6 @@ namespace fs { #else std::string dirSep = "/"; #endif - HashedStorage Peers("peerProfiles", "p", "profile-", "txt"); const std::string & GetAppName () { return appName; @@ -71,8 +70,6 @@ namespace fs { if (boost::filesystem::exists(destinations)) boost::filesystem::create_directory(destinations); - Peers.SetPlace(dataDir); - Peers.Init(i2p::data::GetBase64SubstitutionTable(), 64); return true; } @@ -153,7 +150,5 @@ namespace fs { files.push_back(t); } } - - HashedStorage & GetPeerProfiles() { return Peers; } } // fs } // i2p diff --git a/FS.h b/FS.h index a3f071e5..833258b9 100644 --- a/FS.h +++ b/FS.h @@ -136,9 +136,6 @@ namespace fs { return s.str(); } - - /* accessors */ - HashedStorage & GetPeerProfiles(); } // fs } // i2p diff --git a/NetDb.cpp b/NetDb.cpp index 1678850b..6401fcc8 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -38,6 +38,7 @@ namespace data { m_Storage.SetPlace(i2p::fs::GetDataDir()); m_Storage.Init(i2p::data::GetBase64SubstitutionTable(), 64); + InitProfilesStorage (); m_Families.LoadCertificates (); Load (); if (m_RouterInfos.size () < 25) // reseed if # of router less than 50 diff --git a/Profiling.cpp b/Profiling.cpp index d868298f..be675502 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -10,6 +10,8 @@ namespace i2p { namespace data { + i2p::fs::HashedStorage m_ProfilesStorage("peerProfiles", "p", "profile-", "txt"); + RouterProfile::RouterProfile (const IdentHash& identHash): m_IdentHash (identHash), m_LastUpdateTime (boost::posix_time::second_clock::local_time()), m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0), @@ -45,7 +47,7 @@ namespace data // save to file std::string ident = m_IdentHash.ToBase64 (); - std::string path = i2p::fs::GetPeerProfiles().Path(ident); + std::string path = m_ProfilesStorage.Path(ident); try { boost::property_tree::write_ini (path, pt); @@ -58,7 +60,7 @@ namespace data void RouterProfile::Load () { std::string ident = m_IdentHash.ToBase64 (); - std::string path = i2p::fs::GetPeerProfiles().Path(ident); + std::string path = m_ProfilesStorage.Path(ident); boost::property_tree::ptree pt; if (!i2p::fs::Exists(path)) { @@ -152,13 +154,19 @@ namespace data return profile; } + void InitProfilesStorage () + { + m_ProfilesStorage.SetPlace(i2p::fs::GetDataDir()); + m_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64); + } + void DeleteObsoleteProfiles () { struct stat st; std::time_t now = std::time(nullptr); std::vector files; - i2p::fs::GetPeerProfiles().Traverse(files); + m_ProfilesStorage.Traverse(files); for (auto path: files) { if (stat(path.c_str(), &st) != 0) { LogPrint(eLogWarning, "Profiling: Can't stat(): ", path); diff --git a/Profiling.h b/Profiling.h index 3a65714d..26d5c2f7 100644 --- a/Profiling.h +++ b/Profiling.h @@ -60,6 +60,7 @@ namespace data }; std::shared_ptr GetRouterProfile (const IdentHash& identHash); + void InitProfilesStorage (); void DeleteObsoleteProfiles (); } } From 389ee974f3a09e4d090994ec03a0d76ee0359272 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 20 Feb 2016 21:20:21 -0500 Subject: [PATCH 1005/6300] family --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index d5a3a5b0..79f95f10 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -21,6 +21,7 @@ Command line options * --notransit - Router will not accept transit tunnels at startup * --floodfill - Router will be floodfill * --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited +* --family= - Name of a family, router belongs to * --svcctl= - Windows service management (--svcctl="install" or --svcctl="remove") * --http.address= - The address to listen on (HTTP server) From 476dffff13c751a9bf896239352ea316a1c18a3f Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 21 Feb 2016 15:26:14 -0500 Subject: [PATCH 1006/6300] Create family.md --- docs/family.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 docs/family.md diff --git a/docs/family.md b/docs/family.md new file mode 100644 index 00000000..0f307143 --- /dev/null +++ b/docs/family.md @@ -0,0 +1,32 @@ +Family configuration +==================== + +Your might want to specify a family, your router belongs to. +There are two possibilities: create new family or joing to existing. + +New family +----------- +You must create family self-signed certificate and key. +The only key type supposted is prime256v1. +Use the following list of commands: +openssl ecparam -name prime256v1 -genkey -out .key +openssl req -new -key .key -out .csr +touch v3.ext +openssl x509 -req -days 3650 -in .csr -signkey .key -out .crt -extfile v3.ext + +specify .family.i2p.net for CN. + +Once you are done with it place .key and .crt to /family folder (for exmple ~/.i2pd/family). +You should provide these two files to other members joining your family. +If you want to register you family and let I2P network recorgnize it, create pull request for you .crt file into contrib/certificate/family. +It will appear in i2pd and I2P next releases packages. Don't place .key file, it must be shared betwwen you family members only. + +Join existing family +-------------------- +Once you and that family agree to do it, they must give you .key and .crt file and you must place to /family folder. + +Publish your family +------------------ +Run i2pd with parameter 'family=', make sure you have .key and .crt in your 'family' folder. +If everything is set properly, you router.info will contain two new fields: 'family' and 'family.sig'. + From dc344d465835f4adb06a0b2e2a0b4aeb525386c1 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 22 Feb 2016 12:57:25 +0000 Subject: [PATCH 1007/6300] * add comment --- Destination.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Destination.cpp b/Destination.cpp index 3d7f34c1..484c0679 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -460,6 +460,7 @@ namespace client { auto s = shared_from_this (); RequestLeaseSet (GetIdentHash (), + // "this" added due to bug in gcc 4.7-4.8 [s,this](std::shared_ptr leaseSet) { if (leaseSet) From 88798b1a9e309288abe257fa3b2261157d61a871 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 22 Feb 2016 09:53:26 -0500 Subject: [PATCH 1008/6300] fixed windows build --- FS.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FS.cpp b/FS.cpp index 9416d6b4..5cb64dae 100644 --- a/FS.cpp +++ b/FS.cpp @@ -9,6 +9,10 @@ #include #include +#ifdef WIN32 +#include +#endif + #include "Base.h" #include "FS.h" #include "Log.h" From ca6f656e1b3a0cf6f570d16690ca75adea465498 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 22 Feb 2016 10:27:43 -0500 Subject: [PATCH 1009/6300] ignore non-reachable floodfills --- NetDb.cpp | 9 ++++++--- RouterInfo.cpp | 5 ----- RouterInfo.h | 3 ++- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 6401fcc8..137e7695 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -170,6 +170,7 @@ namespace data { r->Update (buf, len); LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64()); + // TODO: check if floodfill has been changed } else { @@ -187,7 +188,7 @@ namespace data std::unique_lock l(m_RouterInfosMutex); m_RouterInfos[r->GetIdentHash ()] = r; } - if (r->IsFloodfill ()) + if (r->IsFloodfill () && r->IsReachable ()) // floodfill must be reachable { std::unique_lock l(m_FloodfillsMutex); m_Floodfills.push_back (r); @@ -298,9 +299,11 @@ namespace data r->DeleteBuffer (); r->ClearProperties (); // properties are not used for regular routers m_RouterInfos[r->GetIdentHash ()] = r; - if (r->IsFloodfill ()) + if (r->IsFloodfill () && r->IsReachable ()) // floodfill must be reachable m_Floodfills.push_back (r); - } else { + } + else + { LogPrint(eLogWarning, "NetDb: Can't load RI from ", path, ", delete"); i2p::fs::Remove(path); } diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 1bf05a99..84768201 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -629,11 +629,6 @@ namespace data m_Properties.erase (key); } - bool RouterInfo::IsFloodfill () const - { - return m_Caps & Caps::eFloodfill; - } - bool RouterInfo::IsNTCP (bool v4only) const { if (v4only) diff --git a/RouterInfo.h b/RouterInfo.h index 5b77e170..c0ef2131 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -128,7 +128,8 @@ namespace data void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only void DeleteProperty (const std::string& key); // called from RouterContext only void ClearProperties () { m_Properties.clear (); }; - bool IsFloodfill () const; + bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; + bool IsReachable () const { return m_Caps & Caps::eReachable; }; bool IsNTCP (bool v4only = true) const; bool IsSSU (bool v4only = true) const; bool IsV6 () const; From ab6f3fcf8e8d54385598824b37991f898b26db01 Mon Sep 17 00:00:00 2001 From: libre-net-society Date: Mon, 22 Feb 2016 20:49:17 +0300 Subject: [PATCH 1010/6300] added family documentation --- docs/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.rst b/docs/index.rst index 9635d4e4..3963dc3c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,4 +26,5 @@ Contents: build_notes_unix build_notes_windows configuration + family From 2b4c3b8d1ff17c28b8e81e150c6523e4b6518ad2 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 22 Feb 2016 15:17:58 -0500 Subject: [PATCH 1011/6300] start up if i2p.conf is not presented --- Daemon.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Daemon.cpp b/Daemon.cpp index 776e0701..4a61b81b 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -71,7 +71,11 @@ namespace i2p datadir = i2p::fs::GetDataDir(); if (config == "") - config = i2p::fs::DataDirPath("i2p.conf"); + { + config = i2p::fs::DataDirPath("i2p.conf"); + // use i2p.cong only if exists + if (!i2p::fs::Exists (config)) config = ""; /* reset */ + } if (tunconf == "") tunconf = i2p::fs::DataDirPath("tunnels.cfg"); From 6eec353c2b5afdab11be0ebd23ee0e0ca0c211b5 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 22 Feb 2016 15:27:40 -0500 Subject: [PATCH 1012/6300] moved tunnel config file inialization to ClientContext --- ClientContext.cpp | 21 ++++++++++++--------- Daemon.cpp | 4 ---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index bfa94ad1..9f5756e6 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -252,14 +252,17 @@ namespace client void ClientContext::ReadTunnels () { boost::property_tree::ptree pt; - std::string pathTunConf; - i2p::config::GetOption("tunconf", pathTunConf); - if (pathTunConf == "") - return; - try { - boost::property_tree::read_ini (pathTunConf, pt); - } catch (std::exception& ex) { - LogPrint (eLogWarning, "Clients: Can't read ", pathTunConf, ": ", ex.what ()); + std::string tunConf; i2p::config::GetOption("tunconf", tunConf); + if (tunConf == "") + tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); + LogPrint(eLogDebug, "FS: tunnels config file: ", tunConf); + try + { + boost::property_tree::read_ini (tunConf, pt); + } + catch (std::exception& ex) + { + LogPrint (eLogWarning, "Clients: Can't read ", tunConf, ": ", ex.what ()); return; } @@ -347,7 +350,7 @@ namespace client numServerTunnels++; } else - LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", pathTunConf); + LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", tunConf); } catch (std::exception& ex) diff --git a/Daemon.cpp b/Daemon.cpp index 4a61b81b..f301ee3a 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -64,7 +64,6 @@ namespace i2p i2p::config::ParseCmdline(argc, argv); std::string config; i2p::config::GetOption("conf", config); - std::string tunconf; i2p::config::GetOption("tunconf", tunconf); std::string datadir; i2p::config::GetOption("datadir", datadir); i2p::fs::DetectDataDir(datadir, IsService()); i2p::fs::Init(); @@ -76,8 +75,6 @@ namespace i2p // use i2p.cong only if exists if (!i2p::fs::Exists (config)) config = ""; /* reset */ } - if (tunconf == "") - tunconf = i2p::fs::DataDirPath("tunnels.cfg"); i2p::config::ParseConfig(config); i2p::config::Finalize(); @@ -91,7 +88,6 @@ namespace i2p LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); LogPrint(eLogDebug, "FS: main config file: ", config); - LogPrint(eLogDebug, "FS: tunnels config: ", tunconf); LogPrint(eLogDebug, "FS: data directory: ", datadir); uint16_t port; i2p::config::GetOption("port", port); From fe97f0929bbfe4a7417e8ffbbebde75d9a19039b Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 22 Feb 2016 20:51:32 -0500 Subject: [PATCH 1013/6300] delete expired floodfills thorugh a separate loop --- NetDb.cpp | 95 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 38 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 137e7695..aa7eb433 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -304,7 +304,7 @@ namespace data } else { - LogPrint(eLogWarning, "NetDb: Can't load RI from ", path, ", delete"); + LogPrint(eLogWarning, "NetDb: RI from ", path, " is invalid. Delete"); i2p::fs::Remove(path); } return true; @@ -335,7 +335,8 @@ namespace data { std::string ident = it.second->GetIdentHashBase64(); std::string path = m_Storage.Path(ident); - if (it.second->IsUpdated ()) { + if (it.second->IsUpdated ()) + { it.second->SaveToFile (path); it.second->SetUpdated (false); it.second->SetUnreachable (false); @@ -344,68 +345,86 @@ namespace data continue; } // find & mark unreachable routers - if (it.second->UsesIntroducer () && ts > it.second->GetTimestamp () + 3600*1000LL) { + if (it.second->UsesIntroducer () && ts > it.second->GetTimestamp () + 3600*1000LL) + { // RouterInfo expires after 1 hour if uses introducer it.second->SetUnreachable (true); - } else if (total > 75 && ts > (i2p::context.GetStartupTime () + 600)*1000LL) { + } + else if (total > 75 && ts > (i2p::context.GetStartupTime () + 600)*1000LL) + { // routers don't expire if less than 25 or uptime is less than 10 minutes - if (i2p::context.IsFloodfill ()) { - if (ts > it.second->GetTimestamp () + 3600*1000LL) { // 1 hour + if (i2p::context.IsFloodfill ()) + { + if (ts > it.second->GetTimestamp () + 3600*1000LL) + { // 1 hour it.second->SetUnreachable (true); total--; } - else if (total > 2500) - { - if (ts > it.second->GetTimestamp () + 12*3600*1000LL) // 12 hours - { - it.second->SetUnreachable (true); - total--; - } + } + else if (total > 2500) + { + if (ts > it.second->GetTimestamp () + 12*3600*1000LL) // 12 hours + { + it.second->SetUnreachable (true); + total--; } - else if (total > 300) - { - if (ts > it.second->GetTimestamp () + 30*3600*1000LL) // 30 hours - { - it.second->SetUnreachable (true); - total--; - } - } - } else if (total > 120) { - if (ts > it.second->GetTimestamp () + 72*3600*1000LL) { // 72 hours + } + else if (total > 300) + { + if (ts > it.second->GetTimestamp () + 30*3600*1000LL) // 30 hours + { + it.second->SetUnreachable (true); + total--; + } + } + else if (total > 120) + { + if (ts > it.second->GetTimestamp () + 72*3600*1000LL) + { + // 72 hours it.second->SetUnreachable (true); total--; } } } - if (it.second->IsUnreachable ()) { + if (it.second->IsUnreachable ()) + { total--; // delete RI file m_Storage.Remove(ident); deletedCount++; - // delete from floodfills list - if (it.second->IsFloodfill ()) { - std::unique_lock l(m_FloodfillsMutex); - m_Floodfills.remove (it.second); - } } } // m_RouterInfos iteration + if (updatedCount > 0) LogPrint (eLogInfo, "NetDb: saved ", updatedCount, " new/updated routers"); if (deletedCount > 0) { LogPrint (eLogInfo, "NetDb: deleting ", deletedCount, " unreachable routers"); // clean up RouterInfos table - std::unique_lock l(m_RouterInfosMutex); - for (auto it = m_RouterInfos.begin (); it != m_RouterInfos.end ();) { - if (it->second->IsUnreachable ()) { - it->second->SaveProfile (); - it = m_RouterInfos.erase (it); - continue; - } - it++; - } + std::unique_lock l(m_RouterInfosMutex); + for (auto it = m_RouterInfos.begin (); it != m_RouterInfos.end ();) + { + if (it->second->IsUnreachable ()) + { + it->second->SaveProfile (); + it = m_RouterInfos.erase (it); + continue; + } + it++; + } + } + // clean up expired floodfiils + { + std::unique_lock l(m_FloodfillsMutex); + for (auto it = m_Floodfills.begin (); it != m_Floodfills.end ();) + if ((*it)->IsUnreachable ()) + it = m_Floodfills.erase (it); + else + it++; + } } } From 7726705b5c1259f6b1277c34fdd90a504cc30a96 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 23 Feb 2016 12:16:53 -0500 Subject: [PATCH 1014/6300] process request relay tag extended SSU option --- SSUSession.cpp | 28 +++++++++++++++++++++++----- SSUSession.h | 5 ++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index 1eef67e1..a6676cca 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -157,7 +157,7 @@ namespace transport ProcessData (buf + headerSize, len - headerSize); break; case PAYLOAD_TYPE_SESSION_REQUEST: - ProcessSessionRequest (buf + headerSize, len - headerSize, senderEndpoint); + ProcessSessionRequest (buf, len, senderEndpoint); // buf with header break; case PAYLOAD_TYPE_SESSION_CREATED: ProcessSessionCreated (buf, len); // buf with header @@ -196,11 +196,29 @@ namespace transport void SSUSession::ProcessSessionRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { LogPrint (eLogDebug, "SSU message: session request"); + bool sendRelayTag = true; + auto headerSize = sizeof (SSUHeader); + if (((SSUHeader *)buf)->IsExtendedOptions ()) + { + uint8_t extendedOptionsLen = buf[headerSize]; + headerSize++; + if (extendedOptionsLen >= 3) // options are presented + { + uint16_t flags = bufbe16toh (buf + headerSize); + sendRelayTag = flags & EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG; + } + headerSize += extendedOptionsLen; + } + if (headerSize >= len) + { + LogPrint (eLogError, "Session reaquest header size ", headerSize, " exceeds packet length ", len); + return; + } m_RemoteEndpoint = senderEndpoint; if (!m_DHKeysPair) m_DHKeysPair = transports.GetNextDHKeysPair (); - CreateAESandMacKey (buf); - SendSessionCreated (buf); + CreateAESandMacKey (buf + headerSize); + SendSessionCreated (buf + headerSize, sendRelayTag); } void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) @@ -357,7 +375,7 @@ namespace transport m_Server.Send (buf, 96, m_RemoteEndpoint); } - void SSUSession::SendSessionCreated (const uint8_t * x) + void SSUSession::SendSessionCreated (const uint8_t * x, bool sendRelayTag) { auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : i2p::context.GetRouterInfo ().GetSSUAddress (true); //v4 only @@ -401,7 +419,7 @@ namespace transport s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6 s.Insert (htobe16 (address->port)); // our port uint32_t relayTag = 0; - if (i2p::context.GetRouterInfo ().IsIntroducer () && !IsV6 ()) + if (sendRelayTag && i2p::context.GetRouterInfo ().IsIntroducer () && !IsV6 ()) { RAND_bytes((uint8_t *)&relayTag, 4); if (!relayTag) relayTag = 1; diff --git a/SSUSession.h b/SSUSession.h index be1a455f..2fbc0f67 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -39,6 +39,9 @@ namespace transport const uint8_t PAYLOAD_TYPE_PEER_TEST = 7; const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8; + // extended options + const uint16_t EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG = 0x0001; + enum SessionState { eSessionStateUnknown, @@ -101,7 +104,7 @@ namespace transport void SendSessionRequest (); void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce); void ProcessSessionCreated (uint8_t * buf, size_t len); - void SendSessionCreated (const uint8_t * x); + void SendSessionCreated (const uint8_t * x, bool sendRelayTag = true); void ProcessSessionConfirmed (const uint8_t * buf, size_t len); void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen); void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); From f98a6fb6656abf2adaf7880be1a7d30f92e11c9c Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 24 Feb 2016 11:31:14 -0500 Subject: [PATCH 1015/6300] tighten RouterInfo expiration --- NetDb.cpp | 55 +++++++++++++------------------------------------------ NetDb.h | 6 ++++++ 2 files changed, 19 insertions(+), 42 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index aa7eb433..223d3085 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -294,7 +294,7 @@ namespace data { auto r = std::make_shared(path); if (r->GetRouterIdentity () && !r->IsUnreachable () && - (!r->UsesIntroducer () || m_LastLoad < r->GetTimestamp () + 3600*1000LL)) // 1 hour + (!r->UsesIntroducer () || m_LastLoad < r->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL)) // 1 hour { r->DeleteBuffer (); r->ClearProperties (); // properties are not used for regular routers @@ -329,7 +329,13 @@ namespace data { int updatedCount = 0, deletedCount = 0; auto total = m_RouterInfos.size (); + uint64_t expirationTimeout = NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); + // routers don't expire if less than 90 or uptime is less than 1 hour + bool checkForExpiration = total > NETDB_MIN_ROUTERS && ts > (i2p::context.GetStartupTime () + 3600)*1000LL; + if (checkForExpiration) + expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL : + NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; for (auto it: m_RouterInfos) { @@ -344,53 +350,18 @@ namespace data updatedCount++; continue; } - // find & mark unreachable routers - if (it.second->UsesIntroducer () && ts > it.second->GetTimestamp () + 3600*1000LL) + // find & mark expired routers + if (it.second->UsesIntroducer ()) { + if (ts > it.second->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL) // RouterInfo expires after 1 hour if uses introducer - it.second->SetUnreachable (true); - } - else if (total > 75 && ts > (i2p::context.GetStartupTime () + 600)*1000LL) - { - // routers don't expire if less than 25 or uptime is less than 10 minutes - if (i2p::context.IsFloodfill ()) - { - if (ts > it.second->GetTimestamp () + 3600*1000LL) - { // 1 hour - it.second->SetUnreachable (true); - total--; - } - } - else if (total > 2500) - { - if (ts > it.second->GetTimestamp () + 12*3600*1000LL) // 12 hours - { - it.second->SetUnreachable (true); - total--; - } - } - else if (total > 300) - { - if (ts > it.second->GetTimestamp () + 30*3600*1000LL) // 30 hours - { - it.second->SetUnreachable (true); - total--; - } - } - else if (total > 120) - { - if (ts > it.second->GetTimestamp () + 72*3600*1000LL) - { - // 72 hours - it.second->SetUnreachable (true); - total--; - } - } + it.second->SetUnreachable (true); } + else if (checkForExpiration && ts > it.second->GetTimestamp () + expirationTimeout) + it.second->SetUnreachable (true); if (it.second->IsUnreachable ()) { - total--; // delete RI file m_Storage.Remove(ident); deletedCount++; diff --git a/NetDb.h b/NetDb.h index 7efbfcf2..7b196330 100644 --- a/NetDb.h +++ b/NetDb.h @@ -24,6 +24,12 @@ namespace i2p { namespace data { + const int NETDB_MIN_ROUTERS = 90; + const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60*60; // 1 hour, in seconds + const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60; + const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours + const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 hours + class NetDb { public: From 7d660192201b122400d5047ec8cd1cd92501feb2 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 24 Feb 2016 11:50:56 -0500 Subject: [PATCH 1016/6300] start checking for expiration after 10 minutes --- NetDb.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 223d3085..501852bb 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -329,13 +329,13 @@ namespace data { int updatedCount = 0, deletedCount = 0; auto total = m_RouterInfos.size (); - uint64_t expirationTimeout = NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; + uint64_t expirationTimeout = NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); // routers don't expire if less than 90 or uptime is less than 1 hour - bool checkForExpiration = total > NETDB_MIN_ROUTERS && ts > (i2p::context.GetStartupTime () + 3600)*1000LL; - if (checkForExpiration) + bool checkForExpiration = total > NETDB_MIN_ROUTERS && ts > (i2p::context.GetStartupTime () + 600)*1000LL; // 10 minutes + if (checkForExpiration && ts > (i2p::context.GetStartupTime () + 3600)*1000LL) // 1 hour expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL : - NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; + NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; for (auto it: m_RouterInfos) { @@ -365,6 +365,7 @@ namespace data // delete RI file m_Storage.Remove(ident); deletedCount++; + if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false; } } // m_RouterInfos iteration From 1a4923cdce4be1983b030109833c441a90a70db1 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 25 Feb 2016 15:57:58 -0500 Subject: [PATCH 1017/6300] don't request relayTag if we are reachable --- SSUSession.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index a6676cca..520250d9 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -324,6 +324,17 @@ namespace transport { uint8_t buf[320 + 18]; // 304 bytes for ipv4, 320 for ipv6 uint8_t * payload = buf + sizeof (SSUHeader); + // fill extended options, 3 bytes extended options don't change message size + if (i2p::context.GetStatus () == eRouterStatusOK) // we don't need relays + { + // tell out peer to now assign relay tag + ((SSUHeader *)buf)->flag |= SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; + *payload = 2; payload++; // 1 byte length + uint16_t flags = 0; // clear EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG + htobe16buf (payload, flags); + payload += 2; + } + // fill payload memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); // x bool isV4 = m_RemoteEndpoint.address ().is_v4 (); if (isV4) @@ -336,7 +347,7 @@ namespace transport payload[256] = 16; memcpy (payload + 257, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); } - + // encrypt and send uint8_t iv[16]; RAND_bytes (iv, 16); // random iv FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, isV4 ? 304 : 320, m_IntroKey, iv, m_IntroKey); From 9d6d1825c77d69d9bac0565d52892c133304912d Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 25 Feb 2016 18:40:40 -0500 Subject: [PATCH 1018/6300] pass flag to SSU header --- SSUSession.cpp | 11 ++++++----- SSUSession.h | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index 520250d9..1fa29475 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -324,16 +324,17 @@ namespace transport { uint8_t buf[320 + 18]; // 304 bytes for ipv4, 320 for ipv6 uint8_t * payload = buf + sizeof (SSUHeader); + uint8_t flag = 0; // fill extended options, 3 bytes extended options don't change message size if (i2p::context.GetStatus () == eRouterStatusOK) // we don't need relays { // tell out peer to now assign relay tag - ((SSUHeader *)buf)->flag |= SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; + flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; *payload = 2; payload++; // 1 byte length uint16_t flags = 0; // clear EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG htobe16buf (payload, flags); payload += 2; - } + } // fill payload memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); // x bool isV4 = m_RemoteEndpoint.address ().is_v4 (); @@ -350,7 +351,7 @@ namespace transport // encrypt and send uint8_t iv[16]; RAND_bytes (iv, 16); // random iv - FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, isV4 ? 304 : 320, m_IntroKey, iv, m_IntroKey); + FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, isV4 ? 304 : 320, m_IntroKey, iv, m_IntroKey, flag); m_Server.Send (buf, isV4 ? 304 : 320, m_RemoteEndpoint); } @@ -677,7 +678,7 @@ namespace transport } void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, - const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey) + const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag) { if (len < sizeof (SSUHeader)) { @@ -686,7 +687,7 @@ namespace transport } SSUHeader * header = (SSUHeader *)buf; memcpy (header->iv, iv, 16); - header->flag = payloadType << 4; // MSB is 0 + header->flag = flag | (payloadType << 4); // MSB is 0 htobe32buf (header->time, i2p::util::GetSecondsSinceEpoch ()); uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); diff --git a/SSUSession.h b/SSUSession.h index 2fbc0f67..cfe24f6c 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -124,7 +124,8 @@ namespace transport void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key void Send (const uint8_t * buf, size_t size); - void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey); + void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey, + const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag = 0); void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey); void DecryptSessionKey (uint8_t * buf, size_t len); From 8dcf70408dd50bfa556bb6a92530c3c149a45068 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 25 Feb 2016 20:32:05 -0500 Subject: [PATCH 1019/6300] hostoverride added --- ClientContext.cpp | 5 +++-- ClientContext.h | 1 + I2PTunnel.cpp | 9 ++++++--- I2PTunnel.h | 9 +++++++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 9f5756e6..19d841d3 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -311,7 +311,8 @@ namespace client std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); // optional params int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); - std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); + std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); + std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); // I2CP std::map options; @@ -324,7 +325,7 @@ namespace client if (!localDestination) localDestination = CreateNewLocalDestination (k, true, &options); I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ? - new I2PServerTunnelHTTP (name, host, port, localDestination, inPort) : + new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort) : new I2PServerTunnel (name, host, port, localDestination, inPort); if (accessList.length () > 0) { diff --git a/ClientContext.h b/ClientContext.h index 52f1ab5b..30e495da 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -28,6 +28,7 @@ namespace client const char I2P_CLIENT_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport"; const char I2P_SERVER_TUNNEL_HOST[] = "host"; + const char I2P_SERVER_TUNNEL_HOST_OVERRIDE[] = "hostoverride"; const char I2P_SERVER_TUNNEL_PORT[] = "port"; const char I2P_SERVER_TUNNEL_KEYS[] = "keys"; const char I2P_SERVER_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 07b9bd83..4eb1c573 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -422,14 +422,17 @@ namespace client } I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& name, const std::string& address, - int port, std::shared_ptr localDestination, int inport): - I2PServerTunnel (name, address, port, localDestination, inport) + int port, std::shared_ptr localDestination, + const std::string& host, int inport): + I2PServerTunnel (name, address, port, localDestination, inport), + m_Host (host.length () > 0 ? host : address) { } void I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr stream) { - auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), GetAddress ()); + auto conn = std::make_shared (this, stream, + std::make_shared (GetService ()), GetEndpoint (), m_Host); AddHandler (conn); conn->Connect (); } diff --git a/I2PTunnel.h b/I2PTunnel.h index 4530a141..834897f7 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -152,11 +152,16 @@ namespace client public: I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, - std::shared_ptr localDestination, int inport = 0); + std::shared_ptr localDestination, const std::string& host, + int inport = 0); private: - void CreateI2PConnection (std::shared_ptr stream); + void CreateI2PConnection (std::shared_ptr stream); + + private: + + std::string m_Host; }; } } From bb33760e877425837a76a19ba1f386fc1a120583 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 26 Feb 2016 16:16:59 -0500 Subject: [PATCH 1020/6300] don't re-request twice --- Streaming.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 085129bb..46644cb0 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -621,7 +621,7 @@ namespace stream } auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (!m_CurrentRemoteLease || ts >= m_CurrentRemoteLease->endDate - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD*1000) + if (!m_CurrentRemoteLease || ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD) UpdateCurrentRemoteLease (true); if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) { @@ -743,8 +743,8 @@ namespace stream if (leases.empty ()) { expired = false; - m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // time to re-request - leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // time to request + leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold } if (!leases.empty ()) { @@ -772,8 +772,7 @@ namespace stream { m_RemoteLeaseSet = nullptr; m_CurrentRemoteLease = nullptr; - // re-request expired - m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); + // we have requested expired before, no need to do it twice } } else From 190e26276ac8b18bf39e4bae0038c65c69b39336 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 26 Feb 2016 16:17:29 -0500 Subject: [PATCH 1021/6300] reuse tunnel pair for LS request --- Destination.cpp | 24 +++++++++++++++--------- Destination.h | 2 ++ I2NPProtocol.cpp | 2 +- I2NPProtocol.h | 2 +- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 484c0679..eefd9700 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -654,13 +654,14 @@ namespace client bool ClientDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, std::shared_ptr request) { - auto replyTunnel = m_Pool->GetNextInboundTunnel (); - if (!replyTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no inbound tunnels found"); - - auto outboundTunnel = m_Pool->GetNextOutboundTunnel (); - if (!outboundTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found"); + if (!request->replyTunnel || !request->replyTunnel->IsEstablished ()) + request->replyTunnel = m_Pool->GetNextInboundTunnel (); + if (!request->replyTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no inbound tunnels found"); + if (!request->outboundTunnel || !request->outboundTunnel->IsEstablished ()) + request->outboundTunnel = m_Pool->GetNextOutboundTunnel (); + if (!request->outboundTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found"); - if (replyTunnel && outboundTunnel) + if (request->replyTunnel && request->outboundTunnel) { request->excluded.insert (nextFloodfill->GetIdentHash ()); request->requestTime = i2p::util::GetSecondsSinceEpoch (); @@ -673,8 +674,8 @@ namespace client auto msg = WrapMessage (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, - replyTunnel.get (), replyKey, replyTag)); - outboundTunnel->SendTunnelDataMsg ( + request->replyTunnel, replyKey, replyTag)); + request->outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock { @@ -704,7 +705,12 @@ namespace client { auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, it->second->excluded); if (floodfill) - done = !SendLeaseSetRequest (dest, floodfill, it->second); + { + // reset tunnels, because one them might fail + it->second->outboundTunnel = nullptr; + it->second->replyTunnel = nullptr; + done = !SendLeaseSetRequest (dest, floodfill, it->second); + } else done = true; } diff --git a/Destination.h b/Destination.h index 34e7fa38..2da4c8e5 100644 --- a/Destination.h +++ b/Destination.h @@ -61,6 +61,8 @@ namespace client uint64_t requestTime; boost::asio::deadline_timer requestTimeoutTimer; RequestComplete requestComplete; + std::shared_ptr outboundTunnel; + std::shared_ptr replyTunnel; }; diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index db3330e2..dff66951 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -157,7 +157,7 @@ namespace i2p std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, const std::set& excludedFloodfills, - const i2p::tunnel::InboundTunnel * replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag) + std::shared_ptr replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag) { int cnt = excludedFloodfills.size (); auto m = cnt > 0 ? NewI2NPMessage () : NewI2NPShortMessage (); diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 173b57fb..6450e958 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -222,7 +222,7 @@ namespace tunnel uint32_t replyTunnelID, bool exploratory = false, std::set * excludedPeers = nullptr); std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, const std::set& excludedFloodfills, - const i2p::tunnel::InboundTunnel * replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag); + std::shared_ptr replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag); std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0); From 1a05bcb2957b2ab46d66275d4da516fd1386e6cc Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 26 Feb 2016 17:06:11 -0500 Subject: [PATCH 1022/6300] initial support for out proxy via local upstream socks proxy --- Config.cpp | 2 + I2PService.cpp | 132 +++++++++++++++++++++++ I2PService.h | 24 +++++ SOCKS.cpp | 281 +++++++++++++++++++++++++++++++++++++++++++------ SOCKS.h | 9 +- 5 files changed, 417 insertions(+), 31 deletions(-) diff --git a/Config.cpp b/Config.cpp index 81c2a2ce..0de4d0aa 100644 --- a/Config.cpp +++ b/Config.cpp @@ -149,6 +149,8 @@ namespace config { ("socksproxy.address", value()->default_value("127.0.0.1"), "SOCKS Proxy listen address") ("socksproxy.port", value()->default_value(4447), "SOCKS Proxy listen port") ("socksproxy.keys", value()->default_value(""), "File to persist SOCKS Proxy keys") + ("socksproxy.outproxy", value()->default_value("127.0.0.1"), "Upstream outproxy address for SOCKS Proxy") + ("socksproxy.outproxyport", value()->default_value(9050), "Upstream outproxy port for SOCKS Proxy") ; options_description sam("SAM bridge options"); diff --git a/I2PService.cpp b/I2PService.cpp index 85582c0e..0aefc172 100644 --- a/I2PService.cpp +++ b/I2PService.cpp @@ -33,6 +33,138 @@ namespace client } } + TCPIPPipe::TCPIPPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream) : I2PServiceHandler(owner), m_up(upstream), m_down(downstream) {} + + TCPIPPipe::~TCPIPPipe() + { + Terminate(); + } + + void TCPIPPipe::Start() + { + AsyncReceiveUpstream(); + AsyncReceiveDownstream(); + } + + void TCPIPPipe::Terminate() + { + if(Kill()) return; + Done(shared_from_this()); + if (m_up) { + if (m_up->is_open()) { + m_up->close(); + } + m_up = nullptr; + } + if (m_down) { + if (m_down->is_open()) { + m_down->close(); + } + m_down = nullptr; + } + } + + void TCPIPPipe::AsyncReceiveUpstream() + { + if (m_up) { + m_up->async_read_some(boost::asio::buffer(m_upstream_to_down_buf, TCP_IP_PIPE_BUFFER_SIZE), + std::bind(&TCPIPPipe::HandleUpstreamReceived, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); + } else { + LogPrint(eLogError, "TCPIPPipe: no upstream socket for read"); + } + } + + void TCPIPPipe::AsyncReceiveDownstream() + { + if (m_down) { + m_down->async_read_some(boost::asio::buffer(m_downstream_to_up_buf, TCP_IP_PIPE_BUFFER_SIZE), + std::bind(&TCPIPPipe::HandleDownstreamReceived, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); + } else { + LogPrint(eLogError, "TCPIPPipe: no downstream socket for read"); + } + } + + void TCPIPPipe::UpstreamWrite(const uint8_t * buf, size_t len) + { + if (m_up) { + LogPrint(eLogDebug, "TCPIPPipe: write upstream ", (int)len); + boost::asio::async_write(*m_up, boost::asio::buffer(buf, len), + boost::asio::transfer_all(), + std::bind(&TCPIPPipe::HandleUpstreamWrite, + shared_from_this(), + std::placeholders::_1) + ); + } else { + LogPrint(eLogError, "tcpip pipe upstream socket null"); + } + } + + void TCPIPPipe::DownstreamWrite(const uint8_t * buf, size_t len) + { + if (m_down) { + LogPrint(eLogDebug, "TCPIPPipe: write downstream ", (int)len); + boost::asio::async_write(*m_down, boost::asio::buffer(buf, len), + boost::asio::transfer_all(), + std::bind(&TCPIPPipe::HandleDownstreamWrite, + shared_from_this(), + std::placeholders::_1) + ); + } else { + LogPrint(eLogError, "tcpip pipe downstream socket null"); + } + } + + + void TCPIPPipe::HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) + { + LogPrint(eLogDebug, "TCPIPPipe downstream got ", (int) bytes_transfered); + if (ecode) { + LogPrint(eLogError, "TCPIPPipe Downstream read error:" , ecode.message()); + if (ecode != boost::asio::error::operation_aborted) + Terminate(); + } else { + if (bytes_transfered > 0 ) { + memcpy(m_upstream_buf, m_downstream_to_up_buf, bytes_transfered); + UpstreamWrite(m_upstream_buf, bytes_transfered); + } + AsyncReceiveDownstream(); + } + } + + void TCPIPPipe::HandleDownstreamWrite(const boost::system::error_code & ecode) { + if (ecode) { + LogPrint(eLogError, "TCPIPPipe Downstream write error:" , ecode.message()); + if (ecode != boost::asio::error::operation_aborted) + Terminate(); + } + } + + void TCPIPPipe::HandleUpstreamWrite(const boost::system::error_code & ecode) { + if (ecode) { + LogPrint(eLogError, "TCPIPPipe Upstream write error:" , ecode.message()); + if (ecode != boost::asio::error::operation_aborted) + Terminate(); + } + } + + void TCPIPPipe::HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) + { + LogPrint(eLogDebug, "TCPIPPipe upstream got ", (int) bytes_transfered); + if (ecode) { + LogPrint(eLogError, "TCPIPPipe Upstream read error:" , ecode.message()); + if (ecode != boost::asio::error::operation_aborted) + Terminate(); + } else { + if (bytes_transfered > 0 ) { + memcpy(m_upstream_buf, m_upstream_to_down_buf, bytes_transfered); + DownstreamWrite(m_upstream_buf, bytes_transfered); + } + AsyncReceiveUpstream(); + } + } + void TCPIPAcceptor::Start () { m_Acceptor.listen (); diff --git a/I2PService.h b/I2PService.h index 9d91b086..251a379a 100644 --- a/I2PService.h +++ b/I2PService.h @@ -76,6 +76,30 @@ namespace client std::atomic m_Dead; //To avoid cleaning up multiple times }; + const size_t TCP_IP_PIPE_BUFFER_SIZE = 8192; + + // bidirectional pipe for 2 tcp/ip sockets + class TCPIPPipe: public I2PServiceHandler, public std::enable_shared_from_this { + public: + TCPIPPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream); + ~TCPIPPipe(); + void Start(); + protected: + void Terminate(); + void AsyncReceiveUpstream(); + void AsyncReceiveDownstream(); + void HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred); + void HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred); + void HandleUpstreamWrite(const boost::system::error_code & ecode); + void HandleDownstreamWrite(const boost::system::error_code & ecode); + void UpstreamWrite(const uint8_t * buf, size_t len); + void DownstreamWrite(const uint8_t * buf, size_t len); + private: + uint8_t m_upstream_to_down_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_to_up_buf[TCP_IP_PIPE_BUFFER_SIZE]; + uint8_t m_upstream_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_buf[TCP_IP_PIPE_BUFFER_SIZE]; + std::shared_ptr m_up, m_down; + }; + /* TODO: support IPv6 too */ //This is a service that listens for connections on the IP network and interacts with I2P class TCPIPAcceptor: public I2PService diff --git a/SOCKS.cpp b/SOCKS.cpp index 27b23df1..e95eccbe 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -9,6 +9,7 @@ #include "ClientContext.h" #include "I2PEndian.h" #include "I2PTunnel.h" +#include "I2PService.h" namespace i2p { @@ -17,6 +18,10 @@ namespace proxy static const size_t socks_buffer_size = 8192; static const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse + static const size_t SOCKS_FORWARDER_BUFFER_SIZE = 8192; + + static const size_t SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE = 8; + struct SOCKSDnsAddress { uint8_t size; @@ -51,7 +56,10 @@ namespace proxy GET5_IPV6, GET5_HOST_SIZE, GET5_HOST, - DONE + READY, + UPSTREAM_RESOLVE, + UPSTREAM_CONNECT, + UPSTREAM_HANDSHAKE }; enum authMethods { @@ -109,6 +117,7 @@ namespace proxy boost::asio::const_buffers_1 GenerateSOCKS5SelectAuth(authMethods method); boost::asio::const_buffers_1 GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port); boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port); + boost::asio::const_buffers_1 GenerateUpstreamRequest(); bool Socks5ChooseAuth(); void SocksRequestFailed(errTypes error); void SocksRequestSuccess(); @@ -116,12 +125,29 @@ namespace proxy void SentSocksDone(const boost::system::error_code & ecode); void SentSocksResponse(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); + void ForwardSOCKS(); - uint8_t m_sock_buff[socks_buffer_size]; - std::shared_ptr m_sock; + void SocksUpstreamSuccess(); + void AsyncUpstreamSockRead(); + void SendUpstreamRequest(); + void HandleUpstreamData(uint8_t * buff, std::size_t len); + void HandleUpstreamSockSend(const boost::system::error_code & ecode, std::size_t bytes_transfered); + void HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); + void HandleUpstreamConnected(const boost::system::error_code & ecode, + boost::asio::ip::tcp::resolver::iterator itr); + void HandleUpstreamResolved(const boost::system::error_code & ecode, + boost::asio::ip::tcp::resolver::iterator itr); + + boost::asio::ip::tcp::resolver m_proxy_resolver; + uint8_t m_sock_buff[socks_buffer_size]; + std::shared_ptr m_sock, m_upstreamSock; std::shared_ptr m_stream; uint8_t *m_remaining_data; //Data left to be sent + uint8_t *m_remaining_upstream_data; //upstream data left to be forwarded uint8_t m_response[7+max_socks_hostname_size]; + uint8_t m_upstream_response[SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE]; + uint8_t m_upstream_request[14+max_socks_hostname_size]; + std::size_t m_upstream_response_len; address m_address; //Address std::size_t m_remaining_data_len; //Size of the data left to be sent uint32_t m_4aip; //Used in 4a requests @@ -133,16 +159,25 @@ namespace proxy socksVersions m_socksv; //Socks version cmdTypes m_cmd; // Command requested state m_state; - + const bool m_UseUpstreamProxy; // do we want to use the upstream proxy for non i2p addresses? + const std::string m_UpstreamProxyAddress; + const uint16_t m_UpstreamProxyPort; + public: - SOCKSHandler(SOCKSServer * parent, std::shared_ptr sock) : - I2PServiceHandler(parent), m_sock(sock), m_stream(nullptr), - m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4) + SOCKSHandler(SOCKSServer * parent, std::shared_ptr sock, const std::string & upstreamAddr, const uint16_t upstreamPort, const bool useUpstream) : + I2PServiceHandler(parent), + m_proxy_resolver(parent->GetService()), + m_sock(sock), m_stream(nullptr), + m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4), + m_UseUpstreamProxy(useUpstream), + m_UpstreamProxyAddress(upstreamAddr), + m_UpstreamProxyPort(upstreamPort) { m_address.ip = 0; EnterState(GET_SOCKSV); } + ~SOCKSHandler() { Terminate(); } void Handle() { AsyncSockRead(); } }; - + void SOCKSHandler::AsyncSockRead() { LogPrint(eLogDebug, "SOCKS: async sock read"); @@ -164,6 +199,12 @@ namespace proxy m_sock->close(); m_sock = nullptr; } + if (m_upstreamSock) + { + LogPrint(eLogDebug, "SOCKS: closing upstream socket"); + m_upstreamSock->close(); + m_upstreamSock = nullptr; + } if (m_stream) { LogPrint(eLogDebug, "SOCKS: closing stream"); @@ -210,6 +251,37 @@ namespace proxy return boost::asio::const_buffers_1(m_response,size); } + boost::asio::const_buffers_1 SOCKSHandler::GenerateUpstreamRequest() + { + size_t upstreamRequestSize = 0; + // TODO: negotiate with upstream + // SOCKS 4a + m_upstream_request[0] = '\x04'; //version + m_upstream_request[1] = m_cmd; + htobe16buf(m_upstream_request+2, m_port); + m_upstream_request[4] = 0; + m_upstream_request[5] = 0; + m_upstream_request[6] = 0; + m_upstream_request[7] = 1; + // user id + m_upstream_request[8] = 'i'; + m_upstream_request[9] = '2'; + m_upstream_request[10] = 'p'; + m_upstream_request[11] = 'd'; + m_upstream_request[12] = 0; + upstreamRequestSize += 13; + if (m_address.dns.size <= max_socks_hostname_size - ( upstreamRequestSize + 1) ) { + // bounds check okay + memcpy(m_upstream_request + upstreamRequestSize, m_address.dns.value, m_address.dns.size); + upstreamRequestSize += m_address.dns.size; + // null terminate + m_upstream_request[++upstreamRequestSize] = 0; + } else { + LogPrint(eLogError, "SOCKS: BUG!!! m_addr.dns.sizs > max_socks_hostname - ( upstreamRequestSize + 1 ) )"); + } + return boost::asio::const_buffers_1(m_upstream_request, upstreamRequestSize); + } + bool SOCKSHandler::Socks5ChooseAuth() { m_response[0] = '\x05'; //Version @@ -219,14 +291,14 @@ namespace proxy { LogPrint(eLogWarning, "SOCKS: v5 authentication negotiation failed"); boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, - shared_from_this(), std::placeholders::_1)); + shared_from_this(), std::placeholders::_1)); return false; } else { LogPrint(eLogDebug, "SOCKS: v5 choosing authentication method: ", m_authchosen); boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse, - shared_from_this(), std::placeholders::_1)); + shared_from_this(), std::placeholders::_1)); return true; } } @@ -249,7 +321,7 @@ namespace proxy break; } boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, - shared_from_this(), std::placeholders::_1)); + shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::SocksRequestSuccess() @@ -271,7 +343,7 @@ namespace proxy break; } boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone, - shared_from_this(), std::placeholders::_1)); + shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::EnterState(SOCKSHandler::state nstate, uint8_t parseleft) { @@ -313,13 +385,6 @@ namespace proxy SocksRequestFailed(SOCKS5_ADDR_UNSUP); return false; } - //TODO: we may want to support other domains - if(m_addrtype == ADDR_DNS && m_address.dns.ToString().find(".i2p") == std::string::npos) - { - LogPrint(eLogError, "SOCKS: invalid hostname: ", m_address.dns.ToString()); - SocksRequestFailed(SOCKS5_ADDR_UNSUP); - return false; - } return true; } @@ -386,7 +451,7 @@ namespace proxy { switch (m_socksv) { - case SOCKS5: EnterState(DONE); break; + case SOCKS5: EnterState(READY); break; case SOCKS4: EnterState(GET_IPV4); break; } } @@ -407,7 +472,7 @@ namespace proxy if (!*sock_buff) { if( m_4aip == 0 || m_4aip > 255 ) - EnterState(DONE); + EnterState(READY); else EnterState(GET4A_HOST); } @@ -415,7 +480,7 @@ namespace proxy case GET4A_HOST: if (!*sock_buff) { - EnterState(DONE); + EnterState(READY); break; } if (m_address.dns.size >= max_socks_hostname_size) @@ -476,7 +541,7 @@ namespace proxy } sock_buff++; len--; - if (m_state == DONE) + if (m_state == READY) { m_remaining_data_len = len; m_remaining_data = sock_buff; @@ -498,11 +563,23 @@ namespace proxy if (HandleData(m_sock_buff, len)) { - if (m_state == DONE) + if (m_state == READY) { - LogPrint(eLogInfo, "SOCKS: requested ", m_address.dns.ToString(), ":" , m_port); - GetOwner()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete, - shared_from_this(), std::placeholders::_1), m_address.dns.ToString(), m_port); + const std::string addr = m_address.dns.ToString(); + LogPrint(eLogInfo, "SOCKS: requested ", addr, ":" , m_port); + const size_t addrlen = addr.size(); + // does it end with .i2p? + if ( addr.rfind(".i2p") == addrlen - 4) { + // yes it does, make an i2p session + GetOwner()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete, + shared_from_this(), std::placeholders::_1), m_address.dns.ToString(), m_port); + } else if (m_UseUpstreamProxy) { + // forward it to upstream proxy + ForwardSOCKS(); + } else { + // no upstream proxy + SocksRequestFailed(SOCKS5_ADDR_UNSUP); + } } else AsyncSockRead(); @@ -556,17 +633,161 @@ namespace proxy SocksRequestFailed(SOCKS5_HOST_UNREACH); } } + + void SOCKSHandler::ForwardSOCKS() + { + LogPrint(eLogInfo, "SOCKS: forwarding to upstream"); + EnterState(UPSTREAM_RESOLVE); + auto & service = GetOwner()->GetService(); + boost::asio::ip::tcp::resolver::query q(m_UpstreamProxyAddress,boost::lexical_cast(m_UpstreamProxyPort) ); + m_proxy_resolver.async_resolve(q, std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); - SOCKSServer::SOCKSServer(const std::string& address, int port, const std::string& outAddress, int outPort, - std::shared_ptr localDestination) : + } + + void SOCKSHandler::AsyncUpstreamSockRead() + { + LogPrint(eLogDebug, "SOCKS: async upstream sock read"); + if (m_upstreamSock) { + m_upstreamSock->async_read_some(boost::asio::buffer(m_upstream_response, SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE), + std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); + } else { + LogPrint(eLogError, "SOCKS: no upstream socket for read"); + SocksRequestFailed(SOCKS5_GEN_FAIL); + } + } + + void SOCKSHandler::HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered) + { + if (ecode) { + if (m_state == UPSTREAM_HANDSHAKE ) { + // we are trying to handshake but it failed + SocksRequestFailed(SOCKS5_NET_UNREACH); + } else { + LogPrint(eLogError, "SOCKS: bad state when reading from upstream: ", (int) m_state); + } + return; + } + HandleUpstreamData(m_upstream_response, bytes_transfered); + } + + void SOCKSHandler::SocksUpstreamSuccess() + { + LogPrint(eLogInfo, "SOCKS: upstream success"); + boost::asio::const_buffers_1 response(nullptr, 0); + switch (m_socksv) + { + case SOCKS4: + LogPrint(eLogInfo, "SOCKS: v4 connection success"); + response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port); + break; + case SOCKS5: + LogPrint(eLogInfo, "SOCKS: v5 connection success"); + //HACK only 16 bits passed in port as SOCKS5 doesn't allow for more + response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, m_address, m_port); + break; + } + m_sock->send(response); + auto forwarder = std::make_shared(GetOwner(), m_sock, m_upstreamSock); + m_upstreamSock = nullptr; + m_sock = nullptr; + GetOwner()->AddHandler(forwarder); + forwarder->Start(); + Terminate(); + + } + + void SOCKSHandler::HandleUpstreamData(uint8_t * dataptr, std::size_t len) + { + if (m_state == UPSTREAM_HANDSHAKE) { + m_upstream_response_len += len; + // handle handshake data + if (m_upstream_response_len < SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) { + // too small, continue reading + AsyncUpstreamSockRead(); + } else if (len == SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) { + // just right + uint8_t resp = m_upstream_response[1]; + if (resp == SOCKS4_OK) { + // we have connected ! + SocksUpstreamSuccess(); + } else { + // upstream failure + LogPrint(eLogError, "SOCKS: upstream proxy failure: ", (int) resp); + // TODO: runtime error? + SocksRequestFailed(SOCKS5_GEN_FAIL); + } + } else { + // too big + SocksRequestFailed(SOCKS5_GEN_FAIL); + } + } else { + // invalid state + LogPrint(eLogError, "SOCKS: invalid state reading from upstream: ", (int) m_state); + } + } + + void SOCKSHandler::SendUpstreamRequest() + { + LogPrint(eLogInfo, "SOCKS: negotiating with upstream proxy"); + EnterState(UPSTREAM_HANDSHAKE); + if (m_upstreamSock) { + boost::asio::write(*m_upstreamSock, + GenerateUpstreamRequest()); + AsyncUpstreamSockRead(); + } else { + LogPrint(eLogError, "SOCKS: no upstream socket to send handshake to"); + } + } + + void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) + { + if (ecode) { + LogPrint(eLogWarning, "SOCKS: could not connect to upstream proxy: ", ecode.message()); + SocksRequestFailed(SOCKS5_NET_UNREACH); + return; + } + LogPrint(eLogInfo, "SOCKS: connected to upstream proxy"); + SendUpstreamRequest(); + } + + void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) + { + if (ecode) { + // error resolving + LogPrint(eLogWarning, "SOCKS: upstream proxy", m_UpstreamProxyAddress, " not resolved: ", ecode.message()); + SocksRequestFailed(SOCKS5_NET_UNREACH); + return; + } + LogPrint(eLogInfo, "SOCKS: upstream proxy resolved"); + EnterState(UPSTREAM_CONNECT); + auto & service = GetOwner()->GetService(); + m_upstreamSock = std::make_shared(service); + boost::asio::async_connect(*m_upstreamSock, itr, + std::bind(&SOCKSHandler::HandleUpstreamConnected, + shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + } + + SOCKSServer::SOCKSServer(const std::string& address, int port, const std::string& outAddress, uint16_t outPort, + std::shared_ptr localDestination) : TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()) { + m_UseUpstreamProxy = false; + if (outAddress.length() > 0) + SetUpstreamProxy(outAddress, outPort); } std::shared_ptr SOCKSServer::CreateHandler(std::shared_ptr socket) { - return std::make_shared (this, socket); + return std::make_shared (this, socket, m_UpstreamProxyAddress, m_UpstreamProxyPort, m_UseUpstreamProxy); } + void SOCKSServer::SetUpstreamProxy(const std::string & addr, const uint16_t port) + { + m_UpstreamProxyAddress = addr; + m_UpstreamProxyPort = port; + m_UseUpstreamProxy = true; + } } } diff --git a/SOCKS.h b/SOCKS.h index 82f068fb..d417c4a0 100644 --- a/SOCKS.h +++ b/SOCKS.h @@ -15,14 +15,21 @@ namespace proxy { public: - SOCKSServer(const std::string& address, int port, const std::string& outAddress, int outPort, + SOCKSServer(const std::string& address, int port, const std::string& outAddress, uint16_t outPort, std::shared_ptr localDestination = nullptr); ~SOCKSServer() {}; + void SetUpstreamProxy(const std::string & addr, const uint16_t port); + protected: // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return "SOCKS"; } + + private: + std::string m_UpstreamProxyAddress; + uint16_t m_UpstreamProxyPort; + bool m_UseUpstreamProxy; }; typedef SOCKSServer SOCKSProxy; From cc13db9b1f1290061b2c6803695901ce00baef60 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 27 Feb 2016 15:44:36 -0500 Subject: [PATCH 1023/6300] updated FreeBSD instructions --- docs/build_notes_unix.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index 292849a7..d02eb5df 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -96,6 +96,9 @@ miniupnpc-devel FreeBSD ------- +For 10.X use clang. You would also need boost and openssl ports. +Type gmake, it invokes Makefile.bsd, make necessary changes there is required. + Branch 9.X has gcc v4.2, that knows nothing about required c++11 standart. Required ports: @@ -110,10 +113,6 @@ export CC=/usr/local/bin/gcc47 export CXX=/usr/local/bin/g++47 ``` -Branch 10.X has more reliable clang version, that can finally build i2pd, -but I still recommend to use gcc, otherwise you will fight it's bugs by -your own. - CMake Options ------------- From e45cfe7d0cc444058228f584d353608988bbfdf9 Mon Sep 17 00:00:00 2001 From: xcps Date: Tue, 23 Feb 2016 00:33:21 +0500 Subject: [PATCH 1024/6300] init --- ClientContext.cpp | 15 ++++++++++---- ClientContext.h | 1 + I2PTunnel.cpp | 51 +++++++++++++++++++++++++++++++++++++++++++++++ I2PTunnel.h | 33 ++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 4 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 19d841d3..3e4e0c05 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -303,7 +303,7 @@ namespace client LogPrint (eLogError, "Clients: I2P client tunnel with port ", port, " already exists"); numClientTunnels++; } - else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP) + else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC) { // mandatory params std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); @@ -324,9 +324,16 @@ namespace client localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); if (!localDestination) localDestination = CreateNewLocalDestination (k, true, &options); - I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ? - new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort) : - new I2PServerTunnel (name, host, port, localDestination, inPort); + + I2PServerTunnel * serverTunnel; + if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) { + serverTunnel = new I2PServerTunnelHTTP (name, host, port, localDestination, inPort); + } else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER) { + serverTunnel = new I2PServerTunnel (name, host, port, localDestination, inPort); + } else if (type == I2P_TUNNELS_SECTION_TYPE_IRC) { + serverTunnel = new I2PServerTunnelIRC (name, host, port, localDestination, inPort); + } + if (accessList.length () > 0) { std::set idents; diff --git a/ClientContext.h b/ClientContext.h index 30e495da..1be2960b 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -21,6 +21,7 @@ namespace client const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; + const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc"; const char I2P_CLIENT_TUNNEL_PORT[] = "port"; const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination"; diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 4eb1c573..51266eb8 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -234,6 +234,44 @@ namespace client } } + I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, + std::shared_ptr socket, + const boost::asio::ip::tcp::endpoint& target, const std::string& host): + I2PTunnelConnection (owner, stream, socket, target), m_Host (host), m_From (stream->GetRemoteIdentity ()) + { + } + + void I2PTunnelConnectionIRC::Write (const uint8_t * buf, size_t len) + { + std::string line; + m_OutPacket.str (""); + m_InPacket.write ((const char *)buf, len); + + while (!m_InPacket.eof () && !m_InPacket.fail ()) + { + std::getline (m_InPacket, line); + auto pos = line.find ("USER"); + if (pos != std::string::npos && pos == 0) + { + pos = line.find (" "); + pos++; + pos = line.find (" ", pos); + pos++; + pos = line.find (" ", pos); + pos++; + auto nextpos = line.find (" ", pos); + + m_OutPacket << line.substr (0, pos); + m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()); + m_OutPacket << line.substr (nextpos) << '\n'; + } else { + m_OutPacket << line << '\n'; + } + } + I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ()); + } + + /* This handler tries to stablish a connection with the desired server and dies if it fails to do so */ class I2PClientTunnelHandler: public I2PServiceHandler, public std::enable_shared_from_this { @@ -436,5 +474,18 @@ namespace client AddHandler (conn); conn->Connect (); } + + I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address, + int port, std::shared_ptr localDestination, int inport): + I2PServerTunnel (name, address, port, localDestination, inport) + { + } + + void I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr stream) + { + auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), GetAddress ()); + AddHandler (conn); + conn->Connect (); + } } } diff --git a/I2PTunnel.h b/I2PTunnel.h index 834897f7..dc791506 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -80,6 +80,26 @@ namespace client std::shared_ptr m_From; }; + class I2PTunnelConnectionIRC: public I2PTunnelConnection + { + public: + + I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, + std::shared_ptr socket, + const boost::asio::ip::tcp::endpoint& target, const std::string& host); + + protected: + + void Write (const uint8_t * buf, size_t len); + + private: + + std::string m_Host; + std::shared_ptr m_From; + std::stringstream m_OutPacket, m_InPacket; + }; + + class I2PClientTunnel: public TCPIPAcceptor { protected: @@ -163,6 +183,19 @@ namespace client std::string m_Host; }; + + class I2PServerTunnelIRC: public I2PServerTunnel + { + public: + + I2PServerTunnelIRC (const std::string& name, const std::string& address, int port, + std::shared_ptr localDestination, int inport = 0); + + private: + + void CreateI2PConnection (std::shared_ptr stream); + }; + } } From a26dc39a6d481c642c3d182e1e4fea8d1248c47e Mon Sep 17 00:00:00 2001 From: xcps Date: Tue, 23 Feb 2016 09:48:46 +0500 Subject: [PATCH 1025/6300] ident fix --- I2PTunnel.cpp | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 51266eb8..014e7028 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -249,25 +249,26 @@ namespace client while (!m_InPacket.eof () && !m_InPacket.fail ()) { - std::getline (m_InPacket, line); - auto pos = line.find ("USER"); - if (pos != std::string::npos && pos == 0) - { - pos = line.find (" "); - pos++; - pos = line.find (" ", pos); - pos++; - pos = line.find (" ", pos); - pos++; - auto nextpos = line.find (" ", pos); + std::getline (m_InPacket, line); + auto pos = line.find ("USER"); + if (pos != std::string::npos && pos == 0) + { + pos = line.find (" "); + pos++; + pos = line.find (" ", pos); + pos++; + pos = line.find (" ", pos); + pos++; + auto nextpos = line.find (" ", pos); - m_OutPacket << line.substr (0, pos); - m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()); - m_OutPacket << line.substr (nextpos) << '\n'; - } else { - m_OutPacket << line << '\n'; - } + m_OutPacket << line.substr (0, pos); + m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()); + m_OutPacket << line.substr (nextpos) << '\n'; + } else { + m_OutPacket << line << '\n'; + } } + LogPrint (eLogError, m_OutPacket.str ().substr (0, m_OutPacket.str ().length ())); I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ()); } @@ -477,15 +478,15 @@ namespace client I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, int inport): - I2PServerTunnel (name, address, port, localDestination, inport) + I2PServerTunnel (name, address, port, localDestination, inport) { } void I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr stream) { - auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), GetAddress ()); - AddHandler (conn); - conn->Connect (); + auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), GetAddress ()); + AddHandler (conn); + conn->Connect (); } } } From 0fe7bdf849cff5cd572b7c1a263d3449e0ed15f9 Mon Sep 17 00:00:00 2001 From: xcps Date: Sun, 28 Feb 2016 18:03:12 +0500 Subject: [PATCH 1026/6300] init --- I2PTunnel.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 014e7028..eec57928 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -243,9 +243,14 @@ namespace client void I2PTunnelConnectionIRC::Write (const uint8_t * buf, size_t len) { + char *p = (char*)(buf + len); + *p = '\0'; + LogPrint (eLogError, "======= packet received =====\n", buf, "==============\n"); std::string line; m_OutPacket.str (""); + m_InPacket.clear (); m_InPacket.write ((const char *)buf, len); + LogPrint (eLogError, "======= inpacket =====\n", m_InPacket.str ().c_str (), "==============\n"); while (!m_InPacket.eof () && !m_InPacket.fail ()) { @@ -260,7 +265,6 @@ namespace client pos = line.find (" ", pos); pos++; auto nextpos = line.find (" ", pos); - m_OutPacket << line.substr (0, pos); m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()); m_OutPacket << line.substr (nextpos) << '\n'; @@ -268,7 +272,7 @@ namespace client m_OutPacket << line << '\n'; } } - LogPrint (eLogError, m_OutPacket.str ().substr (0, m_OutPacket.str ().length ())); + LogPrint (eLogError, "======= outpacket =====\n", m_OutPacket.str ().substr (0, m_OutPacket.str ().length ()), "============\n"); I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ()); } From f6d0b3368f747ccfa7543808e9f9364f778b5ed8 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 28 Feb 2016 09:56:17 -0500 Subject: [PATCH 1027/6300] znx cert added --- .../certificates/reseed/zmx_at_mail.i2p.crt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 contrib/certificates/reseed/zmx_at_mail.i2p.crt diff --git a/contrib/certificates/reseed/zmx_at_mail.i2p.crt b/contrib/certificates/reseed/zmx_at_mail.i2p.crt new file mode 100644 index 00000000..41f4cc75 --- /dev/null +++ b/contrib/certificates/reseed/zmx_at_mail.i2p.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +fNr8P7NIRyZ8 +-----END CERTIFICATE----- From 57a53b4b6c8b8a1f64ccfac8991bc7903962ff77 Mon Sep 17 00:00:00 2001 From: xcps Date: Sun, 28 Feb 2016 21:13:01 +0500 Subject: [PATCH 1028/6300] fixed I2PServerTunnelHTTP call --- ClientContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 3e4e0c05..304b53c6 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -327,7 +327,7 @@ namespace client I2PServerTunnel * serverTunnel; if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) { - serverTunnel = new I2PServerTunnelHTTP (name, host, port, localDestination, inPort); + serverTunnel = new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort); } else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER) { serverTunnel = new I2PServerTunnel (name, host, port, localDestination, inPort); } else if (type == I2P_TUNNELS_SECTION_TYPE_IRC) { From 7b39a12396a0650c2286bbbfaeba0451b33e425c Mon Sep 17 00:00:00 2001 From: xcps Date: Sun, 28 Feb 2016 22:32:34 +0500 Subject: [PATCH 1029/6300] ready --- I2PTunnel.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index eec57928..0904ed35 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -245,12 +245,11 @@ namespace client { char *p = (char*)(buf + len); *p = '\0'; - LogPrint (eLogError, "======= packet received =====\n", buf, "==============\n"); - std::string line; + std::string line; m_OutPacket.str (""); + m_InPacket.str (""); m_InPacket.clear (); m_InPacket.write ((const char *)buf, len); - LogPrint (eLogError, "======= inpacket =====\n", m_InPacket.str ().c_str (), "==============\n"); while (!m_InPacket.eof () && !m_InPacket.fail ()) { @@ -272,7 +271,6 @@ namespace client m_OutPacket << line << '\n'; } } - LogPrint (eLogError, "======= outpacket =====\n", m_OutPacket.str ().substr (0, m_OutPacket.str ().length ()), "============\n"); I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ()); } From 8799f9079b9d0368cf226c4060e5706a786354e5 Mon Sep 17 00:00:00 2001 From: xcps Date: Mon, 29 Feb 2016 02:15:29 +0500 Subject: [PATCH 1030/6300] change part for replace --- I2PTunnel.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 0904ed35..f1d4a488 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -243,17 +243,17 @@ namespace client void I2PTunnelConnectionIRC::Write (const uint8_t * buf, size_t len) { - char *p = (char*)(buf + len); - *p = '\0'; std::string line; m_OutPacket.str (""); - m_InPacket.str (""); m_InPacket.clear (); m_InPacket.write ((const char *)buf, len); while (!m_InPacket.eof () && !m_InPacket.fail ()) { std::getline (m_InPacket, line); + if (line.length () == 0 && m_InPacket.eof ()) { + m_InPacket.str (""); + } auto pos = line.find ("USER"); if (pos != std::string::npos && pos == 0) { @@ -261,8 +261,6 @@ namespace client pos++; pos = line.find (" ", pos); pos++; - pos = line.find (" ", pos); - pos++; auto nextpos = line.find (" ", pos); m_OutPacket << line.substr (0, pos); m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()); From 4aae878db850a9eb9cf6dd43c3dd39cd2da704c6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 28 Feb 2016 21:43:18 -0500 Subject: [PATCH 1031/6300] increase LeaseSet expiration threshold --- LeaseSet.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LeaseSet.h b/LeaseSet.h index 0afca269..f5685210 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -17,7 +17,7 @@ namespace tunnel namespace data { - const int LEASE_ENDDATE_THRESHOLD = 31000; // in milliseconds + const int LEASE_ENDDATE_THRESHOLD = 51000; // in milliseconds struct Lease { IdentHash tunnelGateway; From 61675c20d81a318e214a0e1a2cfc90c6c11527eb Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 29 Feb 2016 11:02:55 -0500 Subject: [PATCH 1032/6300] don't delete log file upon HUP --- Log.cpp | 8 +++++--- Log.h | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Log.cpp b/Log.cpp index 6391bcae..6bba1b62 100644 --- a/Log.cpp +++ b/Log.cpp @@ -44,10 +44,12 @@ void Log::Flush () m_LogStream->flush(); } -void Log::SetLogFile (const std::string& fullFilePath) +void Log::SetLogFile (const std::string& fullFilePath, bool truncate) { m_FullFilePath = fullFilePath; - auto logFile = std::make_shared (fullFilePath, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); + auto mode = std::ofstream::out | std::ofstream::binary; + mode |= truncate ? std::ofstream::trunc : std::ofstream::app; + auto logFile = std::make_shared (fullFilePath, mode); if (logFile->is_open ()) { SetLogStream (logFile); @@ -59,7 +61,7 @@ void Log::ReopenLogFile () { if (m_FullFilePath.length () > 0) { - SetLogFile (m_FullFilePath); + SetLogFile (m_FullFilePath, false); // don't truncate LogPrint(eLogInfo, "Log: file ", m_FullFilePath, " reopen"); } } diff --git a/Log.h b/Log.h index 05bae7d9..5b07e2c9 100644 --- a/Log.h +++ b/Log.h @@ -38,7 +38,7 @@ class Log: public i2p::util::MsgQueue Log () { SetOnEmpty (std::bind (&Log::Flush, this)); }; ~Log () {}; - void SetLogFile (const std::string& fullFilePath); + void SetLogFile (const std::string& fullFilePath, bool truncate = true); void ReopenLogFile (); void SetLogLevel (const std::string& level); void SetLogStream (std::shared_ptr logStream); From 6d892179c89cca378b5f185b62c470adaadabb81 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 29 Feb 2016 14:44:15 -0500 Subject: [PATCH 1033/6300] added gzip parameter for server tunnels --- ClientContext.cpp | 14 ++++++------ ClientContext.h | 1 + Destination.cpp | 4 ++-- Destination.h | 4 ++-- I2PTunnel.cpp | 12 +++++----- I2PTunnel.h | 6 ++--- Streaming.cpp | 57 ++++++++++++++++++++++++----------------------- Streaming.h | 6 ++--- 8 files changed, 53 insertions(+), 51 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 304b53c6..ea459a13 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -313,6 +313,7 @@ namespace client int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); + bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); // I2CP std::map options; @@ -326,13 +327,12 @@ namespace client localDestination = CreateNewLocalDestination (k, true, &options); I2PServerTunnel * serverTunnel; - if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) { - serverTunnel = new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort); - } else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER) { - serverTunnel = new I2PServerTunnel (name, host, port, localDestination, inPort); - } else if (type == I2P_TUNNELS_SECTION_TYPE_IRC) { - serverTunnel = new I2PServerTunnelIRC (name, host, port, localDestination, inPort); - } + if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) + serverTunnel = new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort, gzip); + else if (type == I2P_TUNNELS_SECTION_TYPE_IRC) + serverTunnel = new I2PServerTunnelIRC (name, host, port, localDestination, inPort, gzip); + else // regular server tunnel by default + serverTunnel = new I2PServerTunnel (name, host, port, localDestination, inPort, gzip); if (accessList.length () > 0) { diff --git a/ClientContext.h b/ClientContext.h index 1be2960b..8126a17a 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -35,6 +35,7 @@ namespace client const char I2P_SERVER_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_SERVER_TUNNEL_INPORT[] = "inport"; const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist"; + const char I2P_SERVER_TUNNEL_GZIP[] = "gzip"; class ClientContext { diff --git a/Destination.cpp b/Destination.cpp index eefd9700..c53c4ee6 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -576,9 +576,9 @@ namespace client return false; } - std::shared_ptr ClientDestination::CreateStreamingDestination (int port) + std::shared_ptr ClientDestination::CreateStreamingDestination (int port, bool gzip) { - auto dest = std::make_shared (shared_from_this (), port); + auto dest = std::make_shared (shared_from_this (), port, gzip); if (port) m_StreamingDestinationsByPorts[port] = dest; else // update default diff --git a/Destination.h b/Destination.h index 2da4c8e5..44cb7424 100644 --- a/Destination.h +++ b/Destination.h @@ -46,7 +46,7 @@ namespace client const int STREAM_REQUEST_TIMEOUT = 60; //in seconds const char I2CP_PARAM_TAGS_TO_SEND[] = "crypto.tagsToSend"; const int DEFAULT_TAGS_TO_SEND = 40; - + typedef std::function stream)> StreamRequestComplete; class ClientDestination: public i2p::garlic::GarlicDestination, @@ -82,7 +82,7 @@ namespace client void CancelDestinationRequest (const i2p::data::IdentHash& dest); // streaming - std::shared_ptr CreateStreamingDestination (int port); // additional + std::shared_ptr CreateStreamingDestination (int port, bool gzip = true); // additional std::shared_ptr GetStreamingDestination (int port = 0) const; // following methods operate with default streaming destination void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index f1d4a488..e2b75232 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -371,10 +371,10 @@ namespace client } I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address, - int port, std::shared_ptr localDestination, int inport): + int port, std::shared_ptr localDestination, int inport, bool gzip): I2PService (localDestination), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false) { - m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port); + m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port, gzip); } void I2PServerTunnel::Start () @@ -462,8 +462,8 @@ namespace client I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, - const std::string& host, int inport): - I2PServerTunnel (name, address, port, localDestination, inport), + const std::string& host, int inport, bool gzip): + I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_Host (host.length () > 0 ? host : address) { } @@ -477,8 +477,8 @@ namespace client } I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address, - int port, std::shared_ptr localDestination, int inport): - I2PServerTunnel (name, address, port, localDestination, inport) + int port, std::shared_ptr localDestination, int inport, bool gzip): + I2PServerTunnel (name, address, port, localDestination, inport, gzip) { } diff --git a/I2PTunnel.h b/I2PTunnel.h index dc791506..9b704033 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -134,7 +134,7 @@ namespace client public: I2PServerTunnel (const std::string& name, const std::string& address, int port, - std::shared_ptr localDestination, int inport = 0); + std::shared_ptr localDestination, int inport = 0, bool gzip = true); void Start (); void Stop (); @@ -173,7 +173,7 @@ namespace client I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& host, - int inport = 0); + int inport = 0, bool gzip = true); private: @@ -189,7 +189,7 @@ namespace client public: I2PServerTunnelIRC (const std::string& name, const std::string& address, int port, - std::shared_ptr localDestination, int inport = 0); + std::shared_ptr localDestination, int inport = 0, bool gzip = true); private: diff --git a/Streaming.cpp b/Streaming.cpp index 46644cb0..33be58b5 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -628,7 +628,7 @@ namespace stream std::vector msgs; for (auto it: packets) { - auto msg = m_RoutingSession->WrapSingleMessage (CreateDataMessage (it->GetBuffer (), it->GetLength ())); + auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage (it->GetBuffer (), it->GetLength (), m_Port)); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, @@ -779,33 +779,9 @@ namespace stream m_CurrentRemoteLease = nullptr; } - std::shared_ptr Stream::CreateDataMessage (const uint8_t * payload, size_t len) - { - auto msg = NewI2NPShortMessage (); - if (len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE) - m_LocalDestination.m_Deflator.SetCompressionLevel (Z_NO_COMPRESSION); - else - m_LocalDestination.m_Deflator.SetCompressionLevel (Z_DEFAULT_COMPRESSION); - uint8_t * buf = msg->GetPayload (); - buf += 4; // reserve for lengthlength - msg->len += 4; - size_t size = m_LocalDestination.m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); - if (size) - { - htobe32buf (msg->GetPayload (), size); // length - htobe16buf (buf + 4, m_LocalDestination.GetLocalPort ()); // source port - htobe16buf (buf + 6, m_Port); // destination port - buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol - msg->len += size; - msg->FillI2NPMessageHeader (eI2NPData); - } - else - msg = nullptr; - return msg; - } - - StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort): - m_Owner (owner), m_LocalPort (localPort), m_PendingIncomingTimer (m_Owner->GetService ()) + StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): + m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), + m_PendingIncomingTimer (m_Owner->GetService ()) { } @@ -993,5 +969,30 @@ namespace stream else delete uncompressed; } + + std::shared_ptr StreamingDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort) + { + auto msg = NewI2NPShortMessage (); + if (!m_Gzip || len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE) + m_Deflator.SetCompressionLevel (Z_NO_COMPRESSION); + else + m_Deflator.SetCompressionLevel (Z_DEFAULT_COMPRESSION); + uint8_t * buf = msg->GetPayload (); + buf += 4; // reserve for lengthlength + msg->len += 4; + size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); + if (size) + { + htobe32buf (msg->GetPayload (), size); // length + htobe16buf (buf + 4, m_LocalPort); // source port + htobe16buf (buf + 6, toPort); // destination port + buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol + msg->len += size; + msg->FillI2NPMessageHeader (eI2NPData); + } + else + msg = nullptr; + return msg; + } } } diff --git a/Streaming.h b/Streaming.h index c908c6ec..c29b62f9 100644 --- a/Streaming.h +++ b/Streaming.h @@ -158,8 +158,6 @@ namespace stream void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); void HandleAckSendTimer (const boost::system::error_code& ecode); - - std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len); private: @@ -195,7 +193,7 @@ namespace stream typedef std::function)> Acceptor; - StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0); + StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = true); ~StreamingDestination (); void Start (); @@ -210,6 +208,7 @@ namespace stream uint16_t GetLocalPort () const { return m_LocalPort; }; void HandleDataMessagePayload (const uint8_t * buf, size_t len); + std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort); private: @@ -221,6 +220,7 @@ namespace stream std::shared_ptr m_Owner; uint16_t m_LocalPort; + bool m_Gzip; // gzip compression of data messages std::mutex m_StreamsMutex; std::map > m_Streams; // sendStreamID->stream Acceptor m_Acceptor; From 79190f313d6014e512ddb665fee1519192b4630f Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 1 Mar 2016 15:22:36 -0500 Subject: [PATCH 1034/6300] use shared_ptr for transit tunnels --- HTTPServer.cpp | 4 ++-- I2NPProtocol.cpp | 3 +-- TransitTunnel.cpp | 8 ++++---- TransitTunnel.h | 2 +- Tunnel.cpp | 19 ++++++------------- Tunnel.h | 8 ++++---- 6 files changed, 18 insertions(+), 26 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 85dbec42..af9498c3 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -619,9 +619,9 @@ namespace util s << "Transit tunnels:
\r\n
\r\n"; for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ()) { - if (dynamic_cast(it.second)) + if (std::dynamic_pointer_cast(it.second)) s << it.second->GetTunnelID () << " ⇒ "; - else if (dynamic_cast(it.second)) + else if (std::dynamic_pointer_cast(it.second)) s << " ⇒ " << it.second->GetTunnelID (); else s << " ⇒ " << it.second->GetTunnelID () << " ⇒ "; diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index dff66951..7cebf96a 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -301,8 +301,7 @@ namespace i2p i2p::tunnel::tunnels.GetTransitTunnels ().size () <= MAX_NUM_TRANSIT_TUNNELS && !i2p::transport::transports.IsBandwidthExceeded ()) { - i2p::tunnel::TransitTunnel * transitTunnel = - i2p::tunnel::CreateTransitTunnel ( + auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index 81773bb5..0d54fc11 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -85,7 +85,7 @@ namespace tunnel m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } - TransitTunnel * CreateTransitTunnel (uint32_t receiveTunnelID, + std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey, bool isGateway, bool isEndpoint) @@ -93,17 +93,17 @@ namespace tunnel if (isEndpoint) { LogPrint (eLogInfo, "TransitTunnel: endpoint ", receiveTunnelID, " created"); - return new TransitTunnelEndpoint (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); + return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else if (isGateway) { LogPrint (eLogInfo, "TransitTunnel: gateway ", receiveTunnelID, " created"); - return new TransitTunnelGateway (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); + return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else { LogPrint (eLogInfo, "TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created"); - return new TransitTunnelParticipant (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); + return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } } } diff --git a/TransitTunnel.h b/TransitTunnel.h index e611d685..2a1908df 100644 --- a/TransitTunnel.h +++ b/TransitTunnel.h @@ -93,7 +93,7 @@ namespace tunnel TunnelEndpoint m_Endpoint; }; - TransitTunnel * CreateTransitTunnel (uint32_t receiveTunnelID, + std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey, bool isGateway, bool isEndpoint); diff --git a/Tunnel.cpp b/Tunnel.cpp index f7102327..715a10f9 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -257,9 +257,6 @@ namespace tunnel Tunnels::~Tunnels () { - for (auto& it : m_TransitTunnels) - delete it.second; - m_TransitTunnels.clear (); } std::shared_ptr Tunnels::GetInboundTunnel (uint32_t tunnelID) @@ -270,7 +267,7 @@ namespace tunnel return nullptr; } - TransitTunnel * Tunnels::GetTransitTunnel (uint32_t tunnelID) + std::shared_ptr Tunnels::GetTransitTunnel (uint32_t tunnelID) { auto it = m_TransitTunnels.find(tunnelID); if (it != m_TransitTunnels.end ()) @@ -363,14 +360,11 @@ namespace tunnel } } - void Tunnels::AddTransitTunnel (TransitTunnel * tunnel) + void Tunnels::AddTransitTunnel (std::shared_ptr tunnel) { std::unique_lock l(m_TransitTunnelsMutex); if (!m_TransitTunnels.insert (std::make_pair (tunnel->GetTunnelID (), tunnel)).second) - { LogPrint (eLogError, "Tunnel: transit tunnel with id ", tunnel->GetTunnelID (), " already exists"); - delete tunnel; - } } void Tunnels::Start () @@ -404,10 +398,10 @@ namespace tunnel if (msg) { uint32_t prevTunnelID = 0, tunnelID = 0; - TunnelBase * prevTunnel = nullptr; + std::shared_ptr prevTunnel; do { - TunnelBase * tunnel = nullptr; + std::shared_ptr tunnel; uint8_t typeID = msg->GetTypeID (); switch (typeID) { @@ -421,7 +415,7 @@ namespace tunnel prevTunnel->FlushTunnelDataMsgs (); if (!tunnel && typeID == eI2NPTunnelData) - tunnel = GetInboundTunnel (tunnelID).get (); + tunnel = GetInboundTunnel (tunnelID); if (!tunnel) tunnel = GetTransitTunnel (tunnelID); if (tunnel) @@ -471,7 +465,7 @@ namespace tunnel } } - void Tunnels::HandleTunnelGatewayMsg (TunnelBase * tunnel, std::shared_ptr msg) + void Tunnels::HandleTunnelGatewayMsg (std::shared_ptr tunnel, std::shared_ptr msg) { if (!tunnel) { @@ -690,7 +684,6 @@ namespace tunnel std::unique_lock l(m_TransitTunnelsMutex); it = m_TransitTunnels.erase (it); } - delete tmp; } else it++; diff --git a/Tunnel.h b/Tunnel.h index 138a48fc..d37646d1 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -142,9 +142,9 @@ namespace tunnel std::shared_ptr GetNextInboundTunnel (); std::shared_ptr GetNextOutboundTunnel (); std::shared_ptr GetExploratoryPool () const { return m_ExploratoryPool; }; - TransitTunnel * GetTransitTunnel (uint32_t tunnelID); + std::shared_ptr GetTransitTunnel (uint32_t tunnelID); int GetTransitTunnelsExpirationTimeout (); - void AddTransitTunnel (TransitTunnel * tunnel); + void AddTransitTunnel (std::shared_ptr tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); void PostTunnelData (std::shared_ptr msg); @@ -163,7 +163,7 @@ namespace tunnel template std::shared_ptr GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels); - void HandleTunnelGatewayMsg (TunnelBase * tunnel, std::shared_ptr msg); + void HandleTunnelGatewayMsg (std::shared_ptr tunnel, std::shared_ptr msg); void Run (); void ManageTunnels (); @@ -186,7 +186,7 @@ namespace tunnel std::map > m_InboundTunnels; std::list > m_OutboundTunnels; std::mutex m_TransitTunnelsMutex; - std::map m_TransitTunnels; + std::map > m_TransitTunnels; std::mutex m_PoolsMutex; std::list> m_Pools; std::shared_ptr m_ExploratoryPool; From 9403fbaf818dffb14768a512e7c29cdfd9e2e351 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 1 Mar 2016 20:48:56 -0500 Subject: [PATCH 1035/6300] common tunnels' hash table --- HTTPServer.cpp | 12 ++++----- Tunnel.cpp | 73 +++++++++++++++++++++++++++----------------------- Tunnel.h | 7 ++--- 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index af9498c3..87982044 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -619,13 +619,13 @@ namespace util s << "Transit tunnels:
\r\n
\r\n"; for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ()) { - if (std::dynamic_pointer_cast(it.second)) - s << it.second->GetTunnelID () << " ⇒ "; - else if (std::dynamic_pointer_cast(it.second)) - s << " ⇒ " << it.second->GetTunnelID (); + if (std::dynamic_pointer_cast(it)) + s << it->GetTunnelID () << " ⇒ "; + else if (std::dynamic_pointer_cast(it)) + s << " ⇒ " << it->GetTunnelID (); else - s << " ⇒ " << it.second->GetTunnelID () << " ⇒ "; - s << " " << it.second->GetNumTransmittedBytes () << "
\r\n"; + s << " ⇒ " << it->GetTunnelID () << " ⇒ "; + s << " " << it->GetNumTransmittedBytes () << "
\r\n"; } } diff --git a/Tunnel.cpp b/Tunnel.cpp index 715a10f9..95d2f698 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -266,15 +266,15 @@ namespace tunnel return it->second; return nullptr; } - - std::shared_ptr Tunnels::GetTransitTunnel (uint32_t tunnelID) + + std::shared_ptr Tunnels::GetTunnel (uint32_t tunnelID) { - auto it = m_TransitTunnels.find(tunnelID); - if (it != m_TransitTunnels.end ()) + auto it = m_Tunnels.find(tunnelID); + if (it != m_Tunnels.end ()) return it->second; return nullptr; } - + std::shared_ptr Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID) { return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels); @@ -362,9 +362,10 @@ namespace tunnel void Tunnels::AddTransitTunnel (std::shared_ptr tunnel) { - std::unique_lock l(m_TransitTunnelsMutex); - if (!m_TransitTunnels.insert (std::make_pair (tunnel->GetTunnelID (), tunnel)).second) - LogPrint (eLogError, "Tunnel: transit tunnel with id ", tunnel->GetTunnelID (), " already exists"); + if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) + m_TransitTunnels.push_back (tunnel); + else + LogPrint (eLogError, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " already exists"); } void Tunnels::Start () @@ -414,10 +415,8 @@ namespace tunnel else if (prevTunnel) prevTunnel->FlushTunnelDataMsgs (); - if (!tunnel && typeID == eI2NPTunnelData) - tunnel = GetInboundTunnel (tunnelID); if (!tunnel) - tunnel = GetTransitTunnel (tunnelID); + tunnel = GetTunnel (tunnelID); if (tunnel) { if (typeID == eI2NPTunnelData) @@ -574,6 +573,7 @@ namespace tunnel auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); + // we don't have outbound tunnels in m_Tunnels it = m_OutboundTunnels.erase (it); } else @@ -622,6 +622,7 @@ namespace tunnel auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); + m_Tunnels.erase (tunnel->GetTunnelID ()); it = m_InboundTunnels.erase (it); } else @@ -676,14 +677,12 @@ namespace tunnel uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();) { - if (ts > it->second->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) + auto tunnel = *it; + if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - auto tmp = it->second; - LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tmp->GetTunnelID (), " expired"); - { - std::unique_lock l(m_TransitTunnelsMutex); - it = m_TransitTunnels.erase (it); - } + LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired"); + m_Tunnels.erase (tunnel->GetTunnelID ()); + it = m_TransitTunnels.erase (it); } else it++; @@ -737,6 +736,7 @@ namespace tunnel void Tunnels::AddOutboundTunnel (std::shared_ptr newTunnel) { + // we don't need to insert it to m_Tunnels m_OutboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (pool && pool->IsActive ()) @@ -747,22 +747,27 @@ namespace tunnel void Tunnels::AddInboundTunnel (std::shared_ptr newTunnel) { - m_InboundTunnels[newTunnel->GetTunnelID ()] = newTunnel; - auto pool = newTunnel->GetTunnelPool (); - if (!pool) - { - // build symmetric outbound tunnel - CreateTunnel (std::make_shared(newTunnel->GetInvertedPeers (), - newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), - GetNextOutboundTunnel ()); + if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second) + { + m_InboundTunnels[newTunnel->GetTunnelID ()] = newTunnel; + auto pool = newTunnel->GetTunnelPool (); + if (!pool) + { + // build symmetric outbound tunnel + CreateTunnel (std::make_shared(newTunnel->GetInvertedPeers (), + newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), + GetNextOutboundTunnel ()); + } + else + { + if (pool->IsActive ()) + pool->TunnelCreated (newTunnel); + else + newTunnel->SetTunnelPool (nullptr); + } } else - { - if (pool->IsActive ()) - pool->TunnelCreated (newTunnel); - else - newTunnel->SetTunnelPool (nullptr); - } + LogPrint (eLogError, "Tunnel: tunnel with id ", newTunnel->GetTunnelID (), " already exists"); } @@ -779,10 +784,10 @@ namespace tunnel { int timeout = 0; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - std::unique_lock l(m_TransitTunnelsMutex); + // TODO: possible race condition with I2PControl for (auto it: m_TransitTunnels) { - int t = it.second->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; + int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; if (t > timeout) timeout = t; } return timeout; diff --git a/Tunnel.h b/Tunnel.h index d37646d1..ca74c707 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -142,7 +143,7 @@ namespace tunnel std::shared_ptr GetNextInboundTunnel (); std::shared_ptr GetNextOutboundTunnel (); std::shared_ptr GetExploratoryPool () const { return m_ExploratoryPool; }; - std::shared_ptr GetTransitTunnel (uint32_t tunnelID); + std::shared_ptr GetTunnel (uint32_t tunnelID); int GetTransitTunnelsExpirationTimeout (); void AddTransitTunnel (std::shared_ptr tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); @@ -185,8 +186,8 @@ namespace tunnel std::map > m_PendingOutboundTunnels; // by replyMsgID std::map > m_InboundTunnels; std::list > m_OutboundTunnels; - std::mutex m_TransitTunnelsMutex; - std::map > m_TransitTunnels; + std::list > m_TransitTunnels; + std::unordered_map > m_Tunnels; // tunnelID->tunnel known by this id std::mutex m_PoolsMutex; std::list> m_Pools; std::shared_ptr m_ExploratoryPool; From eb96ead80e871320885dd916847b0ca97f9399da Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 2 Mar 2016 09:41:37 -0500 Subject: [PATCH 1036/6300] add tunnel counts to front page of web ui --- HTTPServer.cpp | 7 +++++++ Tunnel.cpp | 17 ++++++++++++++++- Tunnel.h | 5 +++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 85dbec42..e218117a 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -447,6 +447,13 @@ namespace util s << "
\r\nRouters: " << i2p::data::netdb.GetNumRouters () << " "; s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
\r\n"; + + size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); + clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); + size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); + + s << "Client Tunnels " << std::to_string(clientTunnelCount) << " "; + s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n"; } void HTTPConnection::HandleCommand (const std::string& command, std::stringstream& s) diff --git a/Tunnel.cpp b/Tunnel.cpp index f7102327..ee3b7119 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -793,7 +793,22 @@ namespace tunnel if (t > timeout) timeout = t; } return timeout; - } + } + + size_t Tunnels::CountTransitTunnels() { + // TODO: locking + return m_TransitTunnels.size(); + } + + size_t Tunnels::CountInboundTunnels() { + // TODO: locking + return m_InboundTunnels.size(); + } + + size_t Tunnels::CountOutboundTunnels() { + // TODO: locking + return m_OutboundTunnels.size(); + } } } diff --git a/Tunnel.h b/Tunnel.h index 138a48fc..f3bcc127 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -201,6 +201,11 @@ namespace tunnel const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; }; const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; }; + + size_t CountTransitTunnels(); + size_t CountInboundTunnels(); + size_t CountOutboundTunnels(); + int GetQueueSize () { return m_Queue.GetSize (); }; int GetTunnelCreationSuccessRate () const // in percents { From 9378668e52d1e02e304a02db29b96cb49428d912 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 2 Mar 2016 09:58:17 -0500 Subject: [PATCH 1037/6300] add colin --- HTTPServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index e218117a..39109dba 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -452,7 +452,7 @@ namespace util clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); - s << "Client Tunnels " << std::to_string(clientTunnelCount) << " "; + s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n"; } From 1dc6cec1aa72ac85eeeaaee8189ff68126d17193 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 2 Mar 2016 10:05:26 -0500 Subject: [PATCH 1038/6300] add client/transit tunnel count in webui --- HTTPServer.cpp | 12 ++++++------ Tunnel.cpp | 24 ++++++++++++------------ Tunnel.h | 8 ++++---- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 39109dba..128b3ad6 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -448,12 +448,12 @@ namespace util s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
\r\n"; - size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); - clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); - size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); - - s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; - s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n"; + size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); + clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); + size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); + + s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; + s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n"; } void HTTPConnection::HandleCommand (const std::string& command, std::stringstream& s) diff --git a/Tunnel.cpp b/Tunnel.cpp index ee3b7119..f235d321 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -795,20 +795,20 @@ namespace tunnel return timeout; } - size_t Tunnels::CountTransitTunnels() { - // TODO: locking - return m_TransitTunnels.size(); - } + size_t Tunnels::CountTransitTunnels() { + // TODO: locking + return m_TransitTunnels.size(); + } - size_t Tunnels::CountInboundTunnels() { - // TODO: locking - return m_InboundTunnels.size(); - } + size_t Tunnels::CountInboundTunnels() { + // TODO: locking + return m_InboundTunnels.size(); + } - size_t Tunnels::CountOutboundTunnels() { - // TODO: locking - return m_OutboundTunnels.size(); - } + size_t Tunnels::CountOutboundTunnels() { + // TODO: locking + return m_OutboundTunnels.size(); + } } } diff --git a/Tunnel.h b/Tunnel.h index f3bcc127..ea68f1c3 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -202,10 +202,10 @@ namespace tunnel const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; }; - size_t CountTransitTunnels(); - size_t CountInboundTunnels(); - size_t CountOutboundTunnels(); - + size_t CountTransitTunnels(); + size_t CountInboundTunnels(); + size_t CountOutboundTunnels(); + int GetQueueSize () { return m_Queue.GetSize (); }; int GetTunnelCreationSuccessRate () const // in percents { From ef6028e933df2ec2ca32f9dec91a0ac6b69b251c Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 2 Mar 2016 11:58:52 -0500 Subject: [PATCH 1039/6300] replace std::map to std::list for inbound tunnels --- HTTPServer.cpp | 6 +++--- Tunnel.cpp | 29 ++++++++++++----------------- Tunnel.h | 9 ++++----- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 2124ea87..3b92d4bc 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -610,13 +610,13 @@ namespace util for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ()) { - it.second->Print (s); - auto state = it.second->GetState (); + it->Print (s); + auto state = it->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) s << " " << "Failed"; else if (state == i2p::tunnel::eTunnelStateExpiring) s << " " << "Exp"; - s << " " << (int)it.second->GetNumReceivedBytes () << "
\r\n"; + s << " " << (int)it->GetNumReceivedBytes () << "
\r\n"; s << std::endl; } } diff --git a/Tunnel.cpp b/Tunnel.cpp index 91b8d077..f2521217 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -258,14 +258,6 @@ namespace tunnel Tunnels::~Tunnels () { } - - std::shared_ptr Tunnels::GetInboundTunnel (uint32_t tunnelID) - { - auto it = m_InboundTunnels.find(tunnelID); - if (it != m_InboundTunnels.end ()) - return it->second; - return nullptr; - } std::shared_ptr Tunnels::GetTunnel (uint32_t tunnelID) { @@ -303,11 +295,11 @@ namespace tunnel size_t minReceived = 0; for (auto it : m_InboundTunnels) { - if (!it.second->IsEstablished ()) continue; - if (!tunnel || it.second->GetNumReceivedBytes () < minReceived) + if (!it->IsEstablished ()) continue; + if (!tunnel || it->GetNumReceivedBytes () < minReceived) { - tunnel = it.second; - minReceived = it.second->GetNumReceivedBytes (); + tunnel = it; + minReceived = it->GetNumReceivedBytes (); } } return tunnel; @@ -615,7 +607,7 @@ namespace tunnel { for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) { - auto tunnel = it->second; + auto tunnel = *it; if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired"); @@ -749,7 +741,7 @@ namespace tunnel { if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second) { - m_InboundTunnels[newTunnel->GetTunnelID ()] = newTunnel; + m_InboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (!pool) { @@ -793,17 +785,20 @@ namespace tunnel return timeout; } - size_t Tunnels::CountTransitTunnels() { + size_t Tunnels::CountTransitTunnels() const + { // TODO: locking return m_TransitTunnels.size(); } - size_t Tunnels::CountInboundTunnels() { + size_t Tunnels::CountInboundTunnels() const + { // TODO: locking return m_InboundTunnels.size(); } - size_t Tunnels::CountOutboundTunnels() { + size_t Tunnels::CountOutboundTunnels() const + { // TODO: locking return m_OutboundTunnels.size(); } diff --git a/Tunnel.h b/Tunnel.h index 11b50eab..c02eb966 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -137,7 +137,6 @@ namespace tunnel void Start (); void Stop (); - std::shared_ptr GetInboundTunnel (uint32_t tunnelID); std::shared_ptr GetPendingInboundTunnel (uint32_t replyMsgID); std::shared_ptr GetPendingOutboundTunnel (uint32_t replyMsgID); std::shared_ptr GetNextInboundTunnel (); @@ -184,7 +183,7 @@ namespace tunnel std::thread * m_Thread; std::map > m_PendingInboundTunnels; // by replyMsgID std::map > m_PendingOutboundTunnels; // by replyMsgID - std::map > m_InboundTunnels; + std::list > m_InboundTunnels; std::list > m_OutboundTunnels; std::list > m_TransitTunnels; std::unordered_map > m_Tunnels; // tunnelID->tunnel known by this id @@ -203,9 +202,9 @@ namespace tunnel const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; }; - size_t CountTransitTunnels(); - size_t CountInboundTunnels(); - size_t CountOutboundTunnels(); + size_t CountTransitTunnels() const; + size_t CountInboundTunnels() const; + size_t CountOutboundTunnels() const; int GetQueueSize () { return m_Queue.GetSize (); }; int GetTunnelCreationSuccessRate () const // in percents From 81b72d54818814e47446889f0a4fb34adfa6df4b Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 2 Mar 2016 12:04:02 -0500 Subject: [PATCH 1040/6300] fixed crash on termination if proxies were excluded --- ClientContext.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index ea459a13..2cdd3ae9 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -100,15 +100,21 @@ namespace client void ClientContext::Stop () { - LogPrint(eLogInfo, "Clients: stopping HTTP Proxy"); - m_HttpProxy->Stop(); - delete m_HttpProxy; - m_HttpProxy = nullptr; + if (m_HttpProxy) + { + LogPrint(eLogInfo, "Clients: stopping HTTP Proxy"); + m_HttpProxy->Stop(); + delete m_HttpProxy; + m_HttpProxy = nullptr; + } - LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy"); - m_SocksProxy->Stop(); - delete m_SocksProxy; - m_SocksProxy = nullptr; + if (m_SocksProxy) + { + LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy"); + m_SocksProxy->Stop(); + delete m_SocksProxy; + m_SocksProxy = nullptr; + } for (auto& it: m_ClientTunnels) { From fa67e907673c12b23a97290bb9db8432e6770f68 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 2 Mar 2016 16:12:02 -0500 Subject: [PATCH 1041/6300] inbound zero-hops tunnel --- Tunnel.cpp | 6 ++++++ Tunnel.h | 1 + TunnelConfig.h | 35 ++++++++++++++++++++++++++++------- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index f2521217..92b20cdd 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -200,6 +200,12 @@ namespace tunnel m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } + void InboundTunnel::SendTunnelDataMsg (std::shared_ptr msg) + { + // assume zero-hops tunnel + m_Endpoint.HandleDecryptedTunnelDataMsg (msg); + } + void InboundTunnel::Print (std::stringstream& s) const { PrintHops (s); diff --git a/Tunnel.h b/Tunnel.h index c02eb966..c41d53b2 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -119,6 +119,7 @@ namespace tunnel InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; void HandleTunnelDataMsg (std::shared_ptr msg); + void SendTunnelDataMsg (std::shared_ptr msg); // for zero-hops only size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; void Print (std::stringstream& s) const; diff --git a/TunnelConfig.h b/TunnelConfig.h index 826979c8..ac493f24 100644 --- a/TunnelConfig.h +++ b/TunnelConfig.h @@ -108,7 +108,7 @@ namespace tunnel } }; - class TunnelConfig: public std::enable_shared_from_this + class TunnelConfig { public: @@ -160,26 +160,26 @@ namespace tunnel return num; } - bool IsInbound () const { return m_FirstHop->isGateway; } + virtual bool IsInbound () const { return m_FirstHop->isGateway; } - uint32_t GetTunnelID () const + virtual uint32_t GetTunnelID () const { if (!m_FirstHop) return 0; return IsInbound () ? m_LastHop->nextTunnelID : m_FirstHop->tunnelID; } - uint32_t GetNextTunnelID () const + virtual uint32_t GetNextTunnelID () const { if (!m_FirstHop) return 0; return m_FirstHop->tunnelID; } - const i2p::data::IdentHash& GetNextIdentHash () const + virtual const i2p::data::IdentHash& GetNextIdentHash () const { return m_FirstHop->ident->GetIdentHash (); } - const i2p::data::IdentHash& GetLastIdentHash () const + virtual const i2p::data::IdentHash& GetLastIdentHash () const { return m_LastHop->ident->GetIdentHash (); } @@ -196,12 +196,14 @@ namespace tunnel return peers; } - private: + protected: // this constructor can't be called from outside TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr) { } + + private: template void CreatePeers (const Peers& peers) @@ -222,6 +224,25 @@ namespace tunnel private: TunnelHopConfig * m_FirstHop, * m_LastHop; + }; + + class ZeroHopTunnelConfig: public TunnelConfig + { + public: + + ZeroHopTunnelConfig (uint32_t tunnelID = 0): // 0 means outbound + m_TunnelID (tunnelID) {}; + + bool IsInbound () const { return m_TunnelID; }; + uint32_t GetTunnelID () const { return m_TunnelID; }; + uint32_t GetNextTunnelID () const { return m_TunnelID; }; + const i2p::data::IdentHash& GetNextIdentHash () const { return i2p::context.GetIdentHash (); }; + const i2p::data::IdentHash& GetLastIdentHash () const { return i2p::context.GetIdentHash (); }; + + + private: + + uint32_t m_TunnelID; }; } } From ecfdc377ec8b1ad2ff5bf7f5a3c6989ad3af2c09 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 2 Mar 2016 19:46:32 -0500 Subject: [PATCH 1042/6300] send close floodfills only in DatabaseSearchReply --- NetDb.cpp | 11 +++++++---- NetDb.h | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 501852bb..eb1380de 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -677,7 +677,7 @@ namespace data excludedRouters.insert (excluded); excluded += 32; } - replyMsg = CreateDatabaseSearchReply (ident, GetClosestFloodfills (ident, 3, excludedRouters)); + replyMsg = CreateDatabaseSearchReply (ident, GetClosestFloodfills (ident, 3, excludedRouters, true)); } } @@ -884,7 +884,7 @@ namespace data } std::vector NetDb::GetClosestFloodfills (const IdentHash& destination, size_t num, - std::set& excluded) const + std::set& excluded, bool closeThanUsOnly) const { struct Sorted { @@ -895,6 +895,8 @@ namespace data std::set sorted; IdentHash destKey = CreateRoutingKey (destination); + XORMetric ourMetric; + if (closeThanUsOnly) ourMetric = destKey ^ i2p::context.GetIdentHash (); { std::unique_lock l(m_FloodfillsMutex); for (auto it: m_Floodfills) @@ -902,6 +904,7 @@ namespace data if (!it->IsUnreachable ()) { XORMetric m = destKey ^ it->GetIdentHash (); + if (closeThanUsOnly && ourMetric < m) continue; if (sorted.size () < num) sorted.insert ({it, m}); else if (m < sorted.rbegin ()->metric) @@ -960,9 +963,9 @@ namespace data auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it = m_LeaseSets.begin (); it != m_LeaseSets.end ();) { - if (ts > it->second->GetExpirationTime ()) + if (ts > it->second->GetExpirationTime () - LEASE_ENDDATE_THRESHOLD) { - LogPrint (eLogWarning, "NetDb: LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); + LogPrint (eLogInfo, "NetDb: LeaseSet ", it->second->GetIdentHash ().ToBase64 (), " expired"); it = m_LeaseSets.erase (it); } else diff --git a/NetDb.h b/NetDb.h index 7b196330..536ff644 100644 --- a/NetDb.h +++ b/NetDb.h @@ -60,7 +60,7 @@ namespace data std::shared_ptr GetRandomIntroducer () const; std::shared_ptr GetClosestFloodfill (const IdentHash& destination, const std::set& excluded, bool closeThanUsOnly = false) const; std::vector GetClosestFloodfills (const IdentHash& destination, size_t num, - std::set& excluded) const; + std::set& excluded, bool closeThanUsOnly = false) const; std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; void SetUnreachable (const IdentHash& ident, bool unreachable); From d541572882664b5be416bb3e1d37be16619d69b8 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 2 Mar 2016 22:41:53 -0500 Subject: [PATCH 1043/6300] enable zero-hops inbound tunnel --- Tunnel.cpp | 36 ++++++++++++++++++++++++++++-------- Tunnel.h | 15 ++++++++++++--- TunnelConfig.h | 7 +++---- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 92b20cdd..feaaddc4 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -200,17 +200,27 @@ namespace tunnel m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } - void InboundTunnel::SendTunnelDataMsg (std::shared_ptr msg) - { - // assume zero-hops tunnel - m_Endpoint.HandleDecryptedTunnelDataMsg (msg); - } - void InboundTunnel::Print (std::stringstream& s) const { PrintHops (s); s << " ⇒ " << GetTunnelID () << ":me"; } + + ZeroHopsInboundTunnel::ZeroHopsInboundTunnel (): + InboundTunnel (std::make_shared ()) + { + } + + void ZeroHopsInboundTunnel::SendTunnelDataMsg (std::shared_ptr msg) + { + msg->from = shared_from_this (); + m_Endpoint.HandleDecryptedTunnelDataMsg (msg); + } + + void ZeroHopsInboundTunnel::Print (std::stringstream& s) const + { + s << " ⇒ " << GetTunnelID () << ":me"; + } void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) { @@ -771,11 +781,21 @@ namespace tunnel void Tunnels::CreateZeroHopsInboundTunnel () { - CreateTunnel ( + /*CreateTunnel ( std::make_shared (std::vector > { i2p::context.GetIdentity () - })); + }));*/ + auto inboundTunnel = std::make_shared (); + m_InboundTunnels.push_back (inboundTunnel); + m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; + + // create paired outbound tunnel, TODO: move to separate function + CreateTunnel ( + std::make_shared (std::vector > + { + i2p::context.GetIdentity () + }, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())); } int Tunnels::GetTransitTunnelsExpirationTimeout () diff --git a/Tunnel.h b/Tunnel.h index c41d53b2..7fa22a63 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -72,6 +72,8 @@ namespace tunnel void SetTunnelPool (std::shared_ptr pool) { m_Pool = pool; }; bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); + + virtual void Print (std::stringstream& s) const {}; // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); @@ -112,22 +114,29 @@ namespace tunnel TunnelGateway m_Gateway; i2p::data::IdentHash m_EndpointIdentHash; }; - + class InboundTunnel: public Tunnel, public std::enable_shared_from_this { public: InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; void HandleTunnelDataMsg (std::shared_ptr msg); - void SendTunnelDataMsg (std::shared_ptr msg); // for zero-hops only size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; void Print (std::stringstream& s) const; - private: + protected: TunnelEndpoint m_Endpoint; }; + + class ZeroHopsInboundTunnel: public InboundTunnel + { + public: + ZeroHopsInboundTunnel (); + void SendTunnelDataMsg (std::shared_ptr msg); + void Print (std::stringstream& s) const; + }; class Tunnels { diff --git a/TunnelConfig.h b/TunnelConfig.h index ac493f24..c089b13c 100644 --- a/TunnelConfig.h +++ b/TunnelConfig.h @@ -226,14 +226,13 @@ namespace tunnel TunnelHopConfig * m_FirstHop, * m_LastHop; }; - class ZeroHopTunnelConfig: public TunnelConfig + class ZeroHopsTunnelConfig: public TunnelConfig { public: - ZeroHopTunnelConfig (uint32_t tunnelID = 0): // 0 means outbound - m_TunnelID (tunnelID) {}; + ZeroHopsTunnelConfig () { RAND_bytes ((uint8_t *)&m_TunnelID, 4);}; - bool IsInbound () const { return m_TunnelID; }; + bool IsInbound () const { return true; }; // TODO: uint32_t GetTunnelID () const { return m_TunnelID; }; uint32_t GetNextTunnelID () const { return m_TunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return i2p::context.GetIdentHash (); }; From 0f56b1c9438175c73959388e1dbe366c00fec30d Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 3 Mar 2016 07:30:38 -0500 Subject: [PATCH 1044/6300] show number of received bytes for zero-hops inbound tunnel --- Tunnel.cpp | 10 +++++++--- Tunnel.h | 9 +++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index feaaddc4..37dd6d67 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -207,14 +207,18 @@ namespace tunnel } ZeroHopsInboundTunnel::ZeroHopsInboundTunnel (): - InboundTunnel (std::make_shared ()) + InboundTunnel (std::make_shared ()), + m_NumReceivedBytes (0) { } void ZeroHopsInboundTunnel::SendTunnelDataMsg (std::shared_ptr msg) { - msg->from = shared_from_this (); - m_Endpoint.HandleDecryptedTunnelDataMsg (msg); + if (msg) + { + m_NumReceivedBytes += msg->GetLength (); + HandleI2NPMessage (msg); + } } void ZeroHopsInboundTunnel::Print (std::stringstream& s) const diff --git a/Tunnel.h b/Tunnel.h index 7fa22a63..34d0a0ab 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -121,10 +121,10 @@ namespace tunnel InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; void HandleTunnelDataMsg (std::shared_ptr msg); - size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; + virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; void Print (std::stringstream& s) const; - protected: + private: TunnelEndpoint m_Endpoint; }; @@ -136,6 +136,11 @@ namespace tunnel ZeroHopsInboundTunnel (); void SendTunnelDataMsg (std::shared_ptr msg); void Print (std::stringstream& s) const; + size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; + + private: + + size_t m_NumReceivedBytes; }; class Tunnels From 96a713afeb843e6c36b143799d60b7e77cfdcc1a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 3 Mar 2016 16:24:13 -0500 Subject: [PATCH 1045/6300] zero-hops outbound tunnels --- Tunnel.cpp | 54 ++++++++++++++++++++++++++++++++++++++++-------------- Tunnel.h | 21 ++++++++++++++++++--- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 37dd6d67..4e3d2275 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -244,8 +244,7 @@ namespace tunnel block.deliveryType = eDeliveryTypeLocal; block.data = msg; - std::unique_lock l(m_SendMutex); - m_Gateway.SendTunnelDataMsg (block); + SendTunnelDataMsg ({block}); } void OutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) @@ -268,6 +267,38 @@ namespace tunnel s << " ⇒ "; } + ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel (): + OutboundTunnel (std::make_shared ()), + m_NumSentBytes (0) + { + } + + void ZeroHopsOutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) + { + for (auto& msg : msgs) + { + switch (msg.deliveryType) + { + case eDeliveryTypeLocal: + i2p::HandleI2NPMessage (msg.data); + break; + case eDeliveryTypeTunnel: + i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); + break; + case eDeliveryTypeRouter: + i2p::transport::transports.SendMessage (msg.hash, msg.data); + break; + default: + LogPrint (eLogError, "Tunnel: Unknown delivery type ", (int)msg.deliveryType); + } + } + } + + void ZeroHopsOutboundTunnel::Print (std::stringstream& s) const + { + s << GetTunnelID () << ":me ⇒ "; + } + Tunnels tunnels; Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), @@ -661,6 +692,7 @@ namespace tunnel { LogPrint (eLogDebug, "Tunnel: Creating zero hops inbound tunnel"); CreateZeroHopsInboundTunnel (); + CreateZeroHopsOutboundTunnel (); if (!m_ExploratoryPool) { m_ExploratoryPool = CreateTunnelPool (2, 2, 5, 5); // 2-hop exploratory, 5 tunnels @@ -785,23 +817,17 @@ namespace tunnel void Tunnels::CreateZeroHopsInboundTunnel () { - /*CreateTunnel ( - std::make_shared (std::vector > - { - i2p::context.GetIdentity () - }));*/ auto inboundTunnel = std::make_shared (); m_InboundTunnels.push_back (inboundTunnel); m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; - - // create paired outbound tunnel, TODO: move to separate function - CreateTunnel ( - std::make_shared (std::vector > - { - i2p::context.GetIdentity () - }, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())); } + void Tunnels::CreateZeroHopsOutboundTunnel () + { + m_OutboundTunnels.push_back (std::make_shared ()); + // we don't insert into m_Tunnels + } + int Tunnels::GetTransitTunnelsExpirationTimeout () { int timeout = 0; diff --git a/Tunnel.h b/Tunnel.h index 34d0a0ab..fe6022ac 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -100,9 +100,9 @@ namespace tunnel Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {}; void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg); - void SendTunnelDataMsg (const std::vector& msgs); // multiple messages + virtual void SendTunnelDataMsg (const std::vector& msgs); // multiple messages const i2p::data::IdentHash& GetEndpointIdentHash () const { return m_EndpointIdentHash; }; - size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; + virtual size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; void Print (std::stringstream& s) const; // implements TunnelBase @@ -143,6 +143,20 @@ namespace tunnel size_t m_NumReceivedBytes; }; + class ZeroHopsOutboundTunnel: public OutboundTunnel + { + public: + + ZeroHopsOutboundTunnel (); + void SendTunnelDataMsg (const std::vector& msgs); + void Print (std::stringstream& s) const; + size_t GetNumSentBytes () const { return m_NumSentBytes; }; + + private: + + size_t m_NumSentBytes; + }; + class Tunnels { public: @@ -191,7 +205,8 @@ namespace tunnel void ManageTunnelPools (); void CreateZeroHopsInboundTunnel (); - + void CreateZeroHopsOutboundTunnel (); + private: bool m_IsRunning; From 70bd16adf6ac61cecbeef2e1e78c3b8fda25d146 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 3 Mar 2016 17:57:15 -0500 Subject: [PATCH 1046/6300] set established state for zero-hops tunnles --- Tunnel.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 4e3d2275..5e237d87 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -818,13 +818,16 @@ namespace tunnel void Tunnels::CreateZeroHopsInboundTunnel () { auto inboundTunnel = std::make_shared (); + inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; } void Tunnels::CreateZeroHopsOutboundTunnel () { - m_OutboundTunnels.push_back (std::make_shared ()); + auto outboundTunnel = std::make_shared (); + outboundTunnel->SetState (eTunnelStateEstablished); + m_OutboundTunnels.push_back (outboundTunnel); // we don't insert into m_Tunnels } From ef0bab0c6e39b701ecdf5882cf8096dbf7cc4110 Mon Sep 17 00:00:00 2001 From: xcps Date: Fri, 4 Mar 2016 11:37:38 +0500 Subject: [PATCH 1047/6300] webirc support --- ClientContext.cpp | 3 ++- ClientContext.h | 3 ++- I2PTunnel.cpp | 21 ++++++++++++++++----- I2PTunnel.h | 17 ++++++++++++----- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 2cdd3ae9..8d675286 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -319,6 +319,7 @@ namespace client int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); + std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); // I2CP @@ -336,7 +337,7 @@ namespace client if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) serverTunnel = new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort, gzip); else if (type == I2P_TUNNELS_SECTION_TYPE_IRC) - serverTunnel = new I2PServerTunnelIRC (name, host, port, localDestination, inPort, gzip); + serverTunnel = new I2PServerTunnelIRC (name, host, port, localDestination, webircpass, inPort, gzip); else // regular server tunnel by default serverTunnel = new I2PServerTunnel (name, host, port, localDestination, inPort, gzip); diff --git a/ClientContext.h b/ClientContext.h index 8126a17a..a05c2161 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -35,7 +35,8 @@ namespace client const char I2P_SERVER_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_SERVER_TUNNEL_INPORT[] = "inport"; const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist"; - const char I2P_SERVER_TUNNEL_GZIP[] = "gzip"; + const char I2P_SERVER_TUNNEL_GZIP[] = "gzip"; + const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword"; class ClientContext { diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index e2b75232..b5aa8e57 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -236,13 +236,21 @@ namespace client I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, - const boost::asio::ip::tcp::endpoint& target, const std::string& host): - I2PTunnelConnection (owner, stream, socket, target), m_Host (host), m_From (stream->GetRemoteIdentity ()) + const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass): + I2PTunnelConnection (owner, stream, socket, target), m_From (stream->GetRemoteIdentity ()), + m_isWebIrced (webircpass.length() ? false : true), m_WebircPass (webircpass) { } void I2PTunnelConnectionIRC::Write (const uint8_t * buf, size_t len) { + if (!m_isWebIrced) { + m_isWebIrced = true; + m_OutPacket.str (""); + m_OutPacket << "WEBIRC " << this->m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) << " 127.0.0.1\n"; + I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ()); + } + std::string line; m_OutPacket.str (""); m_InPacket.clear (); @@ -477,16 +485,19 @@ namespace client } I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address, - int port, std::shared_ptr localDestination, int inport, bool gzip): - I2PServerTunnel (name, address, port, localDestination, inport, gzip) + int port, std::shared_ptr localDestination, + const std::string& webircpass, int inport, bool gzip): + I2PServerTunnel (name, address, port, localDestination, inport, gzip), + m_WebircPass (webircpass) { } void I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr stream) { - auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), GetAddress ()); + auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), this->m_WebircPass); AddHandler (conn); conn->Connect (); } + } } diff --git a/I2PTunnel.h b/I2PTunnel.h index 9b704033..f29ad8da 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -86,17 +86,19 @@ namespace client I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, - const boost::asio::ip::tcp::endpoint& target, const std::string& host); + const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass); protected: void Write (const uint8_t * buf, size_t len); private: - - std::string m_Host; + + std::string m_WebircPass; std::shared_ptr m_From; std::stringstream m_OutPacket, m_InPacket; + bool m_isWebIrced; + }; @@ -189,11 +191,16 @@ namespace client public: I2PServerTunnelIRC (const std::string& name, const std::string& address, int port, - std::shared_ptr localDestination, int inport = 0, bool gzip = true); + std::shared_ptr localDestination, const std::string& webircpass, + int inport = 0, bool gzip = true); private: - void CreateI2PConnection (std::shared_ptr stream); + void CreateI2PConnection (std::shared_ptr stream); + + private: + + std::string m_WebircPass; }; } From 9aeb773169086562753160eaa0a5173eb278cd13 Mon Sep 17 00:00:00 2001 From: xcps Date: Fri, 4 Mar 2016 19:26:28 +0500 Subject: [PATCH 1048/6300] variable name --- I2PTunnel.cpp | 6 +++--- I2PTunnel.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index b5aa8e57..ad06328b 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -238,14 +238,14 @@ namespace client std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass): I2PTunnelConnection (owner, stream, socket, target), m_From (stream->GetRemoteIdentity ()), - m_isWebIrced (webircpass.length() ? false : true), m_WebircPass (webircpass) + m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass) { } void I2PTunnelConnectionIRC::Write (const uint8_t * buf, size_t len) { - if (!m_isWebIrced) { - m_isWebIrced = true; + if (m_NeedsWebIrc) { + m_NeedsWebIrc = false; m_OutPacket.str (""); m_OutPacket << "WEBIRC " << this->m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) << " 127.0.0.1\n"; I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ()); diff --git a/I2PTunnel.h b/I2PTunnel.h index f29ad8da..3cbd618f 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -97,7 +97,7 @@ namespace client std::string m_WebircPass; std::shared_ptr m_From; std::stringstream m_OutPacket, m_InPacket; - bool m_isWebIrced; + bool m_NeedsWebIrc; }; From 8e09f3478fff708a3c764166ed1c60bb6b4a920f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 4 Mar 2016 20:35:53 -0500 Subject: [PATCH 1049/6300] fixed warnings --- I2PTunnel.h | 7 +++---- SOCKS.cpp | 4 +--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/I2PTunnel.h b/I2PTunnel.h index 3cbd618f..bec0f9a4 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -93,12 +93,11 @@ namespace client void Write (const uint8_t * buf, size_t len); private: - - std::string m_WebircPass; + std::shared_ptr m_From; std::stringstream m_OutPacket, m_InPacket; - bool m_NeedsWebIrc; - + bool m_NeedsWebIrc; + std::string m_WebircPass; }; diff --git a/SOCKS.cpp b/SOCKS.cpp index e95eccbe..73833191 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -638,11 +638,9 @@ namespace proxy { LogPrint(eLogInfo, "SOCKS: forwarding to upstream"); EnterState(UPSTREAM_RESOLVE); - auto & service = GetOwner()->GetService(); boost::asio::ip::tcp::resolver::query q(m_UpstreamProxyAddress,boost::lexical_cast(m_UpstreamProxyPort) ); m_proxy_resolver.async_resolve(q, std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); - + std::placeholders::_1, std::placeholders::_2)); } void SOCKSHandler::AsyncUpstreamSockRead() From 380b56a89dc29cf3d4388798486875c9d650004b Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 4 Mar 2016 21:34:23 -0500 Subject: [PATCH 1050/6300] 2.5.0 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 49b52b96..0550dcc9 100644 --- a/version.h +++ b/version.h @@ -7,7 +7,7 @@ #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 4 +#define I2PD_VERSION_MINOR 5 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) From e2a70873b826085ac8db2fc751a2d41389b49392 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 5 Mar 2016 21:46:01 -0500 Subject: [PATCH 1051/6300] fixed garbage in console for windows --- DaemonWin32.cpp | 9 ++++++++- Log.h | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/DaemonWin32.cpp b/DaemonWin32.cpp index 4ab65040..bf5f938c 100644 --- a/DaemonWin32.cpp +++ b/DaemonWin32.cpp @@ -68,7 +68,14 @@ namespace i2p SetConsoleOutputCP(1251); setlocale(LC_ALL, "Russian"); - return Daemon_Singleton::start(); + bool ret = Daemon_Singleton::start(); + if (ret && IsLogToFile ()) + { + // TODO: find out where this garbage to console comes from + SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE); + SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); + } + return ret; } bool DaemonWin32::stop() diff --git a/Log.h b/Log.h index 5b07e2c9..363a23b5 100644 --- a/Log.h +++ b/Log.h @@ -45,6 +45,7 @@ class Log: public i2p::util::MsgQueue std::shared_ptr GetLogStream () const { return m_LogStream; }; const std::string& GetTimestamp (); LogLevel GetLogLevel () { return m_MinLevel; }; + const std::string& GetFullFilePath () const { return m_FullFilePath; }; private: @@ -110,6 +111,11 @@ inline void ReopenLogFile () g_Log->ReopenLogFile (); } +inline bool IsLogToFile () +{ + return g_Log ? !g_Log->GetFullFilePath ().empty () : false; +} + template void LogPrint (std::stringstream& s, TValue arg) { From a5576ddbf3e2b8948dd2fdada7c8799abb724f08 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 6 Mar 2016 09:57:38 -0500 Subject: [PATCH 1052/6300] don't acquire DH keys pair until connection is established --- NTCPSession.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 0ac2e74b..2584f584 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -25,7 +25,6 @@ namespace transport m_TerminationTimer (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false), m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_IsSending (false) { - m_DHKeysPair = transports.GetNextDHKeysPair (); m_Establisher = new Establisher; } From 6383fc357556b83e3b94d45cce541ed84a352398 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 7 Mar 2016 14:54:57 -0500 Subject: [PATCH 1053/6300] initial commit of Win32App --- Makefile | 2 +- Win32/Win32App.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++++++ Win32/Win32App.h | 8 +++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 Win32/Win32App.cpp create mode 100644 Win32/Win32App.h diff --git a/Makefile b/Makefile index 09cf2ace..9bfae351 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ else ifeq ($(UNAME),Linux) DAEMON_SRC += DaemonLinux.cpp include Makefile.linux else # win32 mingw - DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp + DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp WINDIR := True include Makefile.mingw endif diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp new file mode 100644 index 00000000..42d7bef8 --- /dev/null +++ b/Win32/Win32App.cpp @@ -0,0 +1,56 @@ +#include +#include "Win32App.h" + + +static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + } + return DefWindowProc( hWnd, uMsg, wParam, lParam); +} + + int WINAPI WinMain (HINSTANCE hInst, HINSTANCE prev, LPSTR cmdline, int show) + { + // check if tunning already + if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("Title"))) + { + MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); + return 0; + } + // register main window + WNDCLASSEX wclx; + memset (&wclx, 0, sizeof(wclx)); + wclx.cbSize = sizeof(wclx); + wclx.style = 0; + wclx.lpfnWndProc = &WndProc; + wclx.cbClsExtra = 0; + wclx.cbWndExtra = 0; + wclx.hInstance = hInst; + //wclx.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_TRAYICON ) ); + //wclx.hIconSm = LoadSmallIcon( hInstance, IDI_TRAYICON ); + wclx.hCursor = LoadCursor (NULL, IDC_ARROW); + wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wclx.lpszMenuName = NULL; + wclx.lpszClassName = I2PD_WIN32_CLASSNAME; + RegisterClassEx (&wclx); + // create new window + if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("Title"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 250, 150, NULL, NULL, hInst, NULL)) + { + MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); + return 1; + } + + // start + // main loop + MSG msg; + while (GetMessage (&msg, NULL, 0, 0 )) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + // atop + // terminate + UnregisterClass (I2PD_WIN32_CLASSNAME, hInst); + return msg.wParam; + } diff --git a/Win32/Win32App.h b/Win32/Win32App.h new file mode 100644 index 00000000..e7c384a9 --- /dev/null +++ b/Win32/Win32App.h @@ -0,0 +1,8 @@ +#ifndef WIN32APP_H__ +#define WIN32APP_H__ + +#include + +#define I2PD_WIN32_CLASSNAME "i2pd main window" + +#endif // WIN32APP_H__ From 607336d3ce5994e8fc51eba7424b81e7d3536ccc Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 7 Mar 2016 15:57:32 -0500 Subject: [PATCH 1054/6300] tray icon added --- Win32/Win32App.cpp | 96 ++++++++++++++++++++++++++++++++++++++++++++-- Win32/Win32App.h | 2 - 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 42d7bef8..a7bda173 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -1,11 +1,99 @@ #include +#include +#include #include "Win32App.h" +#define ID_ABOUT 2000 +#define ID_EXIT 2001 + +void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) +{ + HMENU hPopup = CreatePopupMenu(); + InsertMenu (hPopup, 0, MF_BYPOSITION | MF_STRING, ID_ABOUT, "About..."); + InsertMenu (hPopup, 1, MF_BYPOSITION | MF_STRING, ID_EXIT , "Exit"); + SetMenuDefaultItem (hPopup, ID_ABOUT, FALSE); + SetFocus (hWnd); + SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); + + POINT p; + if (!curpos) + { + GetCursorPos (&p); + curpos = &p; + } + + WORD cmd = TrackPopupMenu (hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, curpos->x, curpos->y, 0, hWnd, NULL); + SendMessage (hWnd, WM_COMMAND, cmd, 0); + + DestroyMenu(hPopup); +} + +void AddTrayIcon (HWND hWnd, UINT uID, UINT uCallbackMsg, UINT uIcon) +{ + NOTIFYICONDATA nid; + nid.hWnd = hWnd; + nid.uID = uID; + nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + nid.uCallbackMessage = uCallbackMsg; + nid.hIcon = LoadIcon (GetModuleHandle(NULL), IDI_APPLICATION); + strcpy (nid.szTip, "i2pd"); + Shell_NotifyIcon(NIM_ADD, &nid ); +} + +void RemoveTrayIcon (HWND hWnd, UINT uID) +{ + NOTIFYICONDATA nid; + nid.hWnd = hWnd; + nid.uID = uID; + Shell_NotifyIcon (NIM_DELETE, &nid); +} static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { + case WM_CREATE: + { + AddTrayIcon (hWnd, 1, WM_APP, 0); + return 0; + } + case WM_CLOSE: + { + RemoveTrayIcon (hWnd, 1); + PostQuitMessage (0); + return DefWindowProc (hWnd, uMsg, wParam, lParam); + } + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case ID_ABOUT: + { + MessageBox( hWnd, TEXT("i2pd"), TEXT("About"), MB_ICONINFORMATION | MB_OK ); + return 0; + } + case ID_EXIT: + { + PostMessage (hWnd, WM_CLOSE, 0, 0); + return 0; + } + } + break; + } + case WM_APP: + { + switch (lParam) + { + case WM_RBUTTONUP: + { + SetForegroundWindow (hWnd); + ShowPopupMenu(hWnd, NULL, -1); + PostMessage (hWnd, WM_APP + 1, 0, 0); + return 0; + } + } + break; + } } return DefWindowProc( hWnd, uMsg, wParam, lParam); } @@ -13,7 +101,7 @@ static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa int WINAPI WinMain (HINSTANCE hInst, HINSTANCE prev, LPSTR cmdline, int show) { // check if tunning already - if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("Title"))) + if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) { MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); return 0; @@ -27,15 +115,15 @@ static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa wclx.cbClsExtra = 0; wclx.cbWndExtra = 0; wclx.hInstance = hInst; - //wclx.hIcon = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_TRAYICON ) ); - //wclx.hIconSm = LoadSmallIcon( hInstance, IDI_TRAYICON ); + wclx.hIcon = LoadIcon (hInst, IDI_APPLICATION); + wclx.hIconSm = LoadIcon (hInst, IDI_APPLICATION); wclx.hCursor = LoadCursor (NULL, IDC_ARROW); wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wclx.lpszMenuName = NULL; wclx.lpszClassName = I2PD_WIN32_CLASSNAME; RegisterClassEx (&wclx); // create new window - if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("Title"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 250, 150, NULL, NULL, hInst, NULL)) + if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 250, 150, NULL, NULL, hInst, NULL)) { MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); return 1; diff --git a/Win32/Win32App.h b/Win32/Win32App.h index e7c384a9..8b14ae1b 100644 --- a/Win32/Win32App.h +++ b/Win32/Win32App.h @@ -1,8 +1,6 @@ #ifndef WIN32APP_H__ #define WIN32APP_H__ -#include - #define I2PD_WIN32_CLASSNAME "i2pd main window" #endif // WIN32APP_H__ From 9096cacba8b48f747344b2f580bcda1eca9363f8 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 7 Mar 2016 16:06:34 -0500 Subject: [PATCH 1055/6300] tray icon added --- Win32/Win32App.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index a7bda173..f69045d9 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -129,7 +129,13 @@ static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa return 1; } + // init + int argc; + auto argv = CommandLineToArgvW (cmdline, &argc) + Daemon.init(argc, argv); + LocalFree (argv); // start + Daemon.start (); // main loop MSG msg; while (GetMessage (&msg, NULL, 0, 0 )) @@ -138,6 +144,7 @@ static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa DispatchMessage (&msg); } // atop + Daemon.stop (); // terminate UnregisterClass (I2PD_WIN32_CLASSNAME, hInst); return msg.wParam; From 4cfdc770158cf8f45106ee538abd444109b9ded2 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 7 Mar 2016 16:17:06 -0500 Subject: [PATCH 1056/6300] invoke daemon --- Win32/Win32App.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index f69045d9..ca97fe59 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -1,6 +1,7 @@ #include #include #include +#include "../Daemon.h" #include "Win32App.h" #define ID_ABOUT 2000 @@ -130,10 +131,8 @@ static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa } // init - int argc; - auto argv = CommandLineToArgvW (cmdline, &argc) - Daemon.init(argc, argv); - LocalFree (argv); + char * argv[] = { (char *)"i2pd" }; + Daemon.init(sizeof (argv)/sizeof (argv[0]), argv); // start Daemon.start (); // main loop From 507093dbad65f748611606b7f65913b2a7a24c81 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 7 Mar 2016 21:36:11 -0500 Subject: [PATCH 1057/6300] compile with resources --- Makefile | 196 +++++++++++++++++++++++++------------------------ Makefile.mingw | 3 +- 2 files changed, 103 insertions(+), 96 deletions(-) diff --git a/Makefile b/Makefile index 9bfae351..ef0dec46 100644 --- a/Makefile +++ b/Makefile @@ -1,95 +1,101 @@ -UNAME := $(shell uname -s) -SHLIB := libi2pd.so -ARLIB := libi2pd.a -SHLIB_CLIENT := libi2pdclient.so -ARLIB_CLIENT := libi2pdclient.a -I2PD := i2pd -GREP := fgrep -DEPS := obj/make.dep - -include filelist.mk - -USE_AESNI := yes -USE_STATIC := no - -ifeq ($(UNAME),Darwin) - DAEMON_SRC += DaemonLinux.cpp - include Makefile.osx -else ifeq ($(shell echo $(UNAME) | $(GREP) -c FreeBSD),1) - DAEMON_SRC += DaemonLinux.cpp - include Makefile.bsd -else ifeq ($(UNAME),Linux) - DAEMON_SRC += DaemonLinux.cpp - include Makefile.linux -else # win32 mingw - DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp - WINDIR := True - include Makefile.mingw -endif - -all: mk_build_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) - -mk_build_dir: - mkdir -p obj - ifeq ($(WINDIR),True) - mkdir -p obj/Win32 - endif - -api: mk_build_dir $(SHLIB) $(ARLIB) -api_client: mk_build_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) - -## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time -## **without** overwriting the CXXFLAGS which we need in order to build. -## For example, when adding 'hardening flags' to the build -## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove -## -std=c++11. If you want to remove this variable please do so in a way that allows setting -## custom FLAGS to work at build-time. - -deps: - @mkdir -p obj - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) -MM *.cpp > $(DEPS) - @sed -i -e '/\.o:/ s/^/obj\//' $(DEPS) - -obj/%.o : %.cpp - @mkdir -p obj - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS) -c -o $@ $< - -# '-' is 'ignore if missing' on first run --include $(DEPS) - -$(I2PD): $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) $(ARLIB) $(ARLIB_CLIENT) - $(CXX) -o $@ $^ $(LDLIBS) $(LDFLAGS) - -$(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) -ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ -endif - -$(SHLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) - $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ - -$(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) - ar -r $@ $^ - -$(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) - ar -r $@ $^ - -clean: - rm -rf obj - $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) - -strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) - strip $^ - -LATEST_TAG=$(shell git describe --tags --abbrev=0 master) -dist: - git archive --format=tar.gz -9 --worktree-attributes \ - --prefix=i2pd_$(LATEST_TAG)/ $(LATEST_TAG) -o i2pd_$(LATEST_TAG).tar.gz - -.PHONY: all -.PHONY: clean -.PHONY: deps -.PHONY: dist -.PHONY: api -.PHONY: api_client -.PHONY: mk_build_dir +UNAME := $(shell uname -s) +SHLIB := libi2pd.so +ARLIB := libi2pd.a +SHLIB_CLIENT := libi2pdclient.so +ARLIB_CLIENT := libi2pdclient.a +I2PD := i2pd +GREP := fgrep +DEPS := obj/make.dep + +include filelist.mk + +USE_AESNI := yes +USE_STATIC := no + +ifeq ($(UNAME),Darwin) + DAEMON_SRC += DaemonLinux.cpp + include Makefile.osx +else ifeq ($(shell echo $(UNAME) | $(GREP) -c FreeBSD),1) + DAEMON_SRC += DaemonLinux.cpp + include Makefile.bsd +else ifeq ($(UNAME),Linux) + DAEMON_SRC += DaemonLinux.cpp + include Makefile.linux +else # win32 mingw + DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp + DAEMON_RC += Win32/Resource.rc + WINDIR := True + include Makefile.mingw +endif + +all: mk_build_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) + +mk_build_dir: + mkdir -p obj + ifeq ($(WINDIR),True) + mkdir -p obj/Win32 + endif + +api: mk_build_dir $(SHLIB) $(ARLIB) +api_client: mk_build_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) + +## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time +## **without** overwriting the CXXFLAGS which we need in order to build. +## For example, when adding 'hardening flags' to the build +## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove +## -std=c++11. If you want to remove this variable please do so in a way that allows setting +## custom FLAGS to work at build-time. + +deps: + @mkdir -p obj + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) -MM *.cpp > $(DEPS) + @sed -i -e '/\.o:/ s/^/obj\//' $(DEPS) + +obj/%.o : %.cpp + @mkdir -p obj + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS) -c -o $@ $< + +obj/%.o : %.rc + $(WINDRES) -i $< -o $@ + +# '-' is 'ignore if missing' on first run +-include $(DEPS) + +DAEMON_OBJS = $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) +DAEMON_RES_OBJS = $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) +$(I2PD): $(DAEMON_OBJS) $(DAEMON_RES_OBJS) $(ARLIB) $(ARLIB_CLIENT) + $(CXX) -o $@ $^ $(LDLIBS) $(LDFLAGS) + +$(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) +ifneq ($(USE_STATIC),yes) + $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ +endif + +$(SHLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) + $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ + +$(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) + ar -r $@ $^ + +$(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) + ar -r $@ $^ + +clean: + rm -rf obj + $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) + +strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) + strip $^ + +LATEST_TAG=$(shell git describe --tags --abbrev=0 master) +dist: + git archive --format=tar.gz -9 --worktree-attributes \ + --prefix=i2pd_$(LATEST_TAG)/ $(LATEST_TAG) -o i2pd_$(LATEST_TAG).tar.gz + +.PHONY: all +.PHONY: clean +.PHONY: deps +.PHONY: dist +.PHONY: api +.PHONY: api_client +.PHONY: mk_build_dir diff --git a/Makefile.mingw b/Makefile.mingw index 24f33a11..632eca0c 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,9 +1,10 @@ CXX = g++ +WINDRES = windres CXXFLAGS = -O2 -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN NEEDED_CXXFLAGS = -std=c++11 BOOST_SUFFIX = -mt INCFLAGS = -I/usr/include/ -I/usr/local/include/ -LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/c/dev/openssl -L/c/dev/boost/lib +LDFLAGS = -mwindows -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/c/dev/openssl -L/c/dev/boost/lib LDLIBS = -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) -Wl,-Bstatic -lssl -Wl,-Bstatic -lcrypto -Wl,-Bstatic -lz -Wl,-Bstatic -lwsock32 -Wl,-Bstatic -lws2_32 -Wl,-Bstatic -lgdi32 -Wl,-Bstatic -liphlpapi -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -Wl,-Bstatic -lpthread ifeq ($(USE_AESNI),1) From ebd356c7bdca430daba1d728ffcf766211d22b00 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 8 Mar 2016 11:24:29 -0500 Subject: [PATCH 1058/6300] set correct icons --- Win32/Win32App.cpp | 58 ++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index ca97fe59..b039c9b3 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -1,12 +1,16 @@ #include #include #include -#include "../Daemon.h" +//#include "../Daemon.h" +#include "resource.h" #include "Win32App.h" #define ID_ABOUT 2000 #define ID_EXIT 2001 +#define ID_TRAY_ICON 2050 +#define WM_TRAYICON (WM_USER + 1) + void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) { HMENU hPopup = CreatePopupMenu(); @@ -29,23 +33,36 @@ void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) DestroyMenu(hPopup); } -void AddTrayIcon (HWND hWnd, UINT uID, UINT uCallbackMsg, UINT uIcon) +void AddTrayIcon (HWND hWnd) { NOTIFYICONDATA nid; + memset(&nid, 0, sizeof(nid)); + nid.cbSize = sizeof(nid); nid.hWnd = hWnd; - nid.uID = uID; + nid.uID = ID_TRAY_ICON; nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; - nid.uCallbackMessage = uCallbackMsg; - nid.hIcon = LoadIcon (GetModuleHandle(NULL), IDI_APPLICATION); + nid.uCallbackMessage = WM_TRAYICON; + // TODO: must set correct icon + // nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (IDI_ICON1)); + { + char szIconFile[512]; + + GetSystemDirectory( szIconFile, sizeof( szIconFile ) ); + if ( szIconFile[ strlen( szIconFile ) - 1 ] != '\\' ) + strcat( szIconFile, "\\" ); + strcat( szIconFile, "shell32.dll" ); + // Icon #23 (0-indexed) in shell32.dll is a "help" icon. + ExtractIconEx( szIconFile, 23, NULL, &(nid.hIcon), 1 ); + } strcpy (nid.szTip, "i2pd"); Shell_NotifyIcon(NIM_ADD, &nid ); } -void RemoveTrayIcon (HWND hWnd, UINT uID) +void RemoveTrayIcon (HWND hWnd) { NOTIFYICONDATA nid; nid.hWnd = hWnd; - nid.uID = uID; + nid.uID = ID_TRAY_ICON; Shell_NotifyIcon (NIM_DELETE, &nid); } @@ -55,14 +72,14 @@ static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa { case WM_CREATE: { - AddTrayIcon (hWnd, 1, WM_APP, 0); - return 0; + AddTrayIcon (hWnd); + break; } case WM_CLOSE: { - RemoveTrayIcon (hWnd, 1); + RemoveTrayIcon (hWnd); PostQuitMessage (0); - return DefWindowProc (hWnd, uMsg, wParam, lParam); + break; } case WM_COMMAND: { @@ -81,8 +98,9 @@ static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa } break; } - case WM_APP: + case WM_TRAYICON: { + SetForegroundWindow (hWnd); switch (lParam) { case WM_RBUTTONUP: @@ -90,7 +108,7 @@ static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa SetForegroundWindow (hWnd); ShowPopupMenu(hWnd, NULL, -1); PostMessage (hWnd, WM_APP + 1, 0, 0); - return 0; + break; } } break; @@ -112,12 +130,12 @@ static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa memset (&wclx, 0, sizeof(wclx)); wclx.cbSize = sizeof(wclx); wclx.style = 0; - wclx.lpfnWndProc = &WndProc; + wclx.lpfnWndProc = WndProc; wclx.cbClsExtra = 0; wclx.cbWndExtra = 0; wclx.hInstance = hInst; - wclx.hIcon = LoadIcon (hInst, IDI_APPLICATION); - wclx.hIconSm = LoadIcon (hInst, IDI_APPLICATION); + wclx.hIcon = LoadIcon (hInst, MAKEINTRESOURCE (IDI_ICON1)); + wclx.hIconSm = LoadIcon (hInst, MAKEINTRESOURCE (IDI_ICON1)); wclx.hCursor = LoadCursor (NULL, IDC_ARROW); wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wclx.lpszMenuName = NULL; @@ -130,11 +148,11 @@ static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa return 1; } - // init + /* // init char * argv[] = { (char *)"i2pd" }; Daemon.init(sizeof (argv)/sizeof (argv[0]), argv); // start - Daemon.start (); + Daemon.start ();*/ // main loop MSG msg; while (GetMessage (&msg, NULL, 0, 0 )) @@ -142,8 +160,8 @@ static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa TranslateMessage (&msg); DispatchMessage (&msg); } - // atop - Daemon.stop (); + /* // atop + Daemon.stop ();*/ // terminate UnregisterClass (I2PD_WIN32_CLASSNAME, hInst); return msg.wParam; From 4b0d587fe108be85b2d3d37b45d5a53f0e51fb1a Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 8 Mar 2016 15:02:32 -0500 Subject: [PATCH 1059/6300] Daemon::run --- Daemon.h | 7 +++++-- DaemonLinux.cpp | 9 +++++++++ i2pd.cpp | 8 +------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Daemon.h b/Daemon.h index e755e3e9..4f31b2dc 100644 --- a/Daemon.h +++ b/Daemon.h @@ -20,6 +20,7 @@ namespace i2p virtual bool init(int argc, char* argv[]); virtual bool start(); virtual bool stop(); + virtual void run () {}; bool isLogging; bool isDaemon; @@ -61,8 +62,10 @@ namespace i2p return instance; } - virtual bool start(); - virtual bool stop(); + bool start(); + bool stop(); +; void run (); + private: std::string pidfile; int pidFH; diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 2ccbfe38..53d9f61e 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -118,6 +119,14 @@ namespace i2p return Daemon_Singleton::stop(); } + + void DaemonLinux::run () + { + while (running) + { + std::this_thread::sleep_for (std::chrono::seconds(1)); + } + } } } diff --git a/i2pd.cpp b/i2pd.cpp index 32749d16..6167f10e 100644 --- a/i2pd.cpp +++ b/i2pd.cpp @@ -1,4 +1,3 @@ -#include #include #include "Daemon.h" @@ -6,12 +5,7 @@ int main( int argc, char* argv[] ) { Daemon.init(argc, argv); if (Daemon.start()) - { - while (Daemon.running) - { - std::this_thread::sleep_for (std::chrono::seconds(1)); - } - } + Daemon.run (); Daemon.stop(); return EXIT_SUCCESS; } From e403c419e588bf117d0177aa582e82a3c235e3e1 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 8 Mar 2016 17:40:43 -0500 Subject: [PATCH 1060/6300] 16x16 icon added --- Win32/ictoopie_16.ico | Bin 0 -> 1150 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Win32/ictoopie_16.ico diff --git a/Win32/ictoopie_16.ico b/Win32/ictoopie_16.ico new file mode 100644 index 0000000000000000000000000000000000000000..525646c351db354f366a1a912c0471c2b9c71193 GIT binary patch literal 1150 zcmai!T`b#C7{*T#TEzN=i>1*qHY-|esLu|G>U>ngQj*z(8P;ve>U-2k%|^x$#V*u0 zTV$gm3o}eI*&^I-S+b8vwq&u%E)W-<{h5n@Y~ek5&ikDAyuXuka}FU4YOvXanuR3C ziV!D4$Z1MciK_gqQ;Eo^y`w^-(PVMC+)YQm<4>VbxF!~h*Zj}=uSuoS4TVC{d=y_@ zT^+|4@S#YLg?F$woL!s|c`*{sZqA_i-lKT2MBGziEQR^285La>2u}z{$6N>Wy?TiJ zM5|$;VaE?)db}Sw*ccFIpE(C7Jzhz}!eD)3fVjzw4tR?s`KCR$kTeuB?tm|fhf}@@Ees?2SZ4G&yg(7tfMk+^ zs5A2ADnq4`&QDKIGafYD=Lh*p+tXPZ6j@b)b>ktVK02rb<J~%pN+|sxR z?puKKlG4l$IFz>>`3ON!$jV?(w>7<=?kja#FZ-n2ZM)Zi?a(UNQ`=y_+JW8kU$B1z zuyXF)~LtmRZjRF^0+!AS=19M^n%*ic~=0!M!#lJ7eySDqRkBrKa zA_ti)A~!HO{he=O+ER*Itt+^myZbHt6YJD#+LH~H_ccr1P`|HrD(}`m)3nz|aa%5g F`~i^{J6iw% literal 0 HcmV?d00001 From ec8550d58766f62777e8cfbab9d0eb3954c82d1b Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 8 Mar 2016 21:18:48 -0500 Subject: [PATCH 1061/6300] use ictoopie_16 in tray --- Win32/Resource.rc | 2 +- Win32/Win32App.cpp | 17 +++-------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/Win32/Resource.rc b/Win32/Resource.rc index c8643e8d..56868181 100644 --- a/Win32/Resource.rc +++ b/Win32/Resource.rc @@ -53,7 +53,7 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. MAINICON ICON "ictoopie.ico" - +IDI_ICON1 ICON "ictoopie_16.ico" #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index b039c9b3..c356aec5 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -42,18 +42,7 @@ void AddTrayIcon (HWND hWnd) nid.uID = ID_TRAY_ICON; nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; nid.uCallbackMessage = WM_TRAYICON; - // TODO: must set correct icon - // nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (IDI_ICON1)); - { - char szIconFile[512]; - - GetSystemDirectory( szIconFile, sizeof( szIconFile ) ); - if ( szIconFile[ strlen( szIconFile ) - 1 ] != '\\' ) - strcat( szIconFile, "\\" ); - strcat( szIconFile, "shell32.dll" ); - // Icon #23 (0-indexed) in shell32.dll is a "help" icon. - ExtractIconEx( szIconFile, 23, NULL, &(nid.hIcon), 1 ); - } + nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (IDI_ICON1)); strcpy (nid.szTip, "i2pd"); Shell_NotifyIcon(NIM_ADD, &nid ); } @@ -134,8 +123,8 @@ static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPa wclx.cbClsExtra = 0; wclx.cbWndExtra = 0; wclx.hInstance = hInst; - wclx.hIcon = LoadIcon (hInst, MAKEINTRESOURCE (IDI_ICON1)); - wclx.hIconSm = LoadIcon (hInst, MAKEINTRESOURCE (IDI_ICON1)); + wclx.hIcon = LoadIcon (hInst, IDI_APPLICATION); + wclx.hIconSm = LoadIcon (hInst, IDI_APPLICATION); wclx.hCursor = LoadCursor (NULL, IDC_ARROW); wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wclx.lpszMenuName = NULL; From 121ac4f1de2513ba04a75255a9dc4b2f7473b616 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 9 Mar 2016 13:36:39 +0000 Subject: [PATCH 1062/6300] * move mingw-specific rules to Makefile.mingw --- Makefile | 32 +++++++++++--------------------- Makefile.mingw | 27 +++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index ef0dec46..2a0baaba 100644 --- a/Makefile +++ b/Makefile @@ -23,21 +23,17 @@ else ifeq ($(UNAME),Linux) include Makefile.linux else # win32 mingw DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp - DAEMON_RC += Win32/Resource.rc - WINDIR := True include Makefile.mingw endif -all: mk_build_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) +all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) -mk_build_dir: - mkdir -p obj - ifeq ($(WINDIR),True) - mkdir -p obj/Win32 - endif +mk_obj_dir: + @mkdir -p obj + @mkdir -p obj/Win32 -api: mk_build_dir $(SHLIB) $(ARLIB) -api_client: mk_build_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) +api: mk_obj_dir $(SHLIB) $(ARLIB) +api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. @@ -46,24 +42,18 @@ api_client: mk_build_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. -deps: - @mkdir -p obj +deps: mk_obj_dir $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) -MM *.cpp > $(DEPS) @sed -i -e '/\.o:/ s/^/obj\//' $(DEPS) -obj/%.o : %.cpp - @mkdir -p obj +obj/%.o: %.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS) -c -o $@ $< -obj/%.o : %.rc - $(WINDRES) -i $< -o $@ - # '-' is 'ignore if missing' on first run -include $(DEPS) -DAEMON_OBJS = $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) -DAEMON_RES_OBJS = $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) -$(I2PD): $(DAEMON_OBJS) $(DAEMON_RES_OBJS) $(ARLIB) $(ARLIB_CLIENT) +DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) +$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(CXX) -o $@ $^ $(LDLIBS) $(LDFLAGS) $(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) @@ -98,4 +88,4 @@ dist: .PHONY: dist .PHONY: api .PHONY: api_client -.PHONY: mk_build_dir +.PHONY: mk_obj_dir diff --git a/Makefile.mingw b/Makefile.mingw index 632eca0c..5de1f49d 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -4,8 +4,31 @@ CXXFLAGS = -O2 -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN NEEDED_CXXFLAGS = -std=c++11 BOOST_SUFFIX = -mt INCFLAGS = -I/usr/include/ -I/usr/local/include/ -LDFLAGS = -mwindows -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/c/dev/openssl -L/c/dev/boost/lib -LDLIBS = -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) -Wl,-Bstatic -lssl -Wl,-Bstatic -lcrypto -Wl,-Bstatic -lz -Wl,-Bstatic -lwsock32 -Wl,-Bstatic -lws2_32 -Wl,-Bstatic -lgdi32 -Wl,-Bstatic -liphlpapi -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -Wl,-Bstatic -lpthread +LDFLAGS = -mwindows -Wl,-rpath,/usr/local/lib \ + -L/usr/local/lib \ + -L/c/dev/openssl \ + -L/c/dev/boost/lib +LDLIBS = \ + -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lssl \ + -Wl,-Bstatic -lcrypto \ + -Wl,-Bstatic -lz \ + -Wl,-Bstatic -lwsock32 \ + -Wl,-Bstatic -lws2_32 \ + -Wl,-Bstatic -lgdi32 \ + -Wl,-Bstatic -liphlpapi \ + -static-libgcc -static-libstdc++ \ + -Wl,-Bstatic -lstdc++ \ + -Wl,-Bstatic -lpthread +DAEMON_RC += Win32/Resource.rc +DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) + +obj/%.o : %.rc + $(WINDRES) -i $< -o $@ ifeq ($(USE_AESNI),1) CPU_FLAGS = -maes -DAESNI From 95b2bf3645452407f09e707d7061945b6dc61fe4 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 9 Mar 2016 09:38:19 -0500 Subject: [PATCH 1063/6300] fixed windows build --- Makefile.mingw | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile.mingw b/Makefile.mingw index 5de1f49d..ba741918 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -27,11 +27,12 @@ LDLIBS = \ DAEMON_RC += Win32/Resource.rc DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) -obj/%.o : %.rc - $(WINDRES) -i $< -o $@ - ifeq ($(USE_AESNI),1) CPU_FLAGS = -maes -DAESNI else CPU_FLAGS = -msse endif + +obj/%.o : %.rc + $(WINDRES) -i $< -o $@ + From 74d4b8e0b92cd9dc2f7055cb7296ce441c15269f Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 9 Mar 2016 14:41:14 -0500 Subject: [PATCH 1064/6300] invoke win32app functions from main --- Daemon.h | 11 +- DaemonWin32.cpp | 62 +++-------- Makefile | 4 +- Win32/Win32App.cpp | 267 +++++++++++++++++++++++---------------------- Win32/Win32App.h | 9 ++ 5 files changed, 167 insertions(+), 186 deletions(-) diff --git a/Daemon.h b/Daemon.h index 4f31b2dc..efbd5df4 100644 --- a/Daemon.h +++ b/Daemon.h @@ -24,14 +24,14 @@ namespace i2p bool isLogging; bool isDaemon; - + bool running; protected: Daemon_Singleton(); virtual ~Daemon_Singleton(); - bool IsService () const; + bool IsService () const; // d-pointer for httpServer, httpProxy, etc. class Daemon_Singleton_Private; @@ -48,9 +48,10 @@ namespace i2p return instance; } - virtual bool init(int argc, char* argv[]); - virtual bool start(); - virtual bool stop(); + bool init(int argc, char* argv[]); + bool start(); + bool stop(); + void run (); }; #else class DaemonLinux : public Daemon_Singleton diff --git a/DaemonWin32.cpp b/DaemonWin32.cpp index bf5f938c..e5de9c9a 100644 --- a/DaemonWin32.cpp +++ b/DaemonWin32.cpp @@ -5,7 +5,7 @@ #ifdef _WIN32 -#include "./Win32/Win32Service.h" +#include "Win32/Win32App.h" namespace i2p { @@ -16,61 +16,21 @@ namespace i2p setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); - setlocale(LC_ALL, "Russian"); + setlocale(LC_ALL, "Russian"); + return Daemon_Singleton::init(argc, argv); + } - if (!Daemon_Singleton::init(argc, argv)) return false; - if (I2PService::isService()) - isDaemon = 1; - else - isDaemon = 0; - - std::string serviceControl; i2p::config::GetOption("svcctl", serviceControl); - if (serviceControl == "install") - { - LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service"); - InstallService( - SERVICE_NAME, // Name of service - SERVICE_DISPLAY_NAME, // Name to display - SERVICE_START_TYPE, // Service start type - SERVICE_DEPENDENCIES, // Dependencies - SERVICE_ACCOUNT, // Service running account - SERVICE_PASSWORD // Password of the account - ); - exit(0); - } - else if (serviceControl == "remove") - { - LogPrint(eLogInfo, "WinSVC: uninstalling ", SERVICE_NAME, " service"); - UninstallService(SERVICE_NAME); - exit(0); - } - - if (isDaemon == 1) - { - LogPrint(eLogDebug, "Daemon: running as service"); - I2PService service(SERVICE_NAME); - if (!I2PService::Run(service)) - { - LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); - exit(EXIT_FAILURE); - } - exit(EXIT_SUCCESS); - } - else - LogPrint(eLogDebug, "Daemon: running as user"); - - return true; - } bool DaemonWin32::start() { setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); setlocale(LC_ALL, "Russian"); + if (!i2p::win32::StartWin32App ()) return false; bool ret = Daemon_Singleton::start(); if (ret && IsLogToFile ()) - { + { // TODO: find out where this garbage to console comes from SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE); SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); @@ -79,9 +39,15 @@ namespace i2p } bool DaemonWin32::stop() - { + { + i2p::win32::StopWin32App (); return Daemon_Singleton::stop(); - } + } + + void DaemonWin32::run () + { + i2p::win32::RunWin32App (); + } } } diff --git a/Makefile b/Makefile index 2a0baaba..8f1847f3 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ else ifeq ($(UNAME),Linux) DAEMON_SRC += DaemonLinux.cpp include Makefile.linux else # win32 mingw - DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp + DAEMON_SRC += DaemonWin32.cpp Win32/Win32App.cpp include Makefile.mingw endif @@ -52,7 +52,7 @@ obj/%.o: %.cpp # '-' is 'ignore if missing' on first run -include $(DEPS) -DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) +DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(CXX) -o $@ $^ $(LDLIBS) $(LDFLAGS) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index c356aec5..c997b266 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -11,147 +11,152 @@ #define ID_TRAY_ICON 2050 #define WM_TRAYICON (WM_USER + 1) -void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) +namespace i2p { - HMENU hPopup = CreatePopupMenu(); - InsertMenu (hPopup, 0, MF_BYPOSITION | MF_STRING, ID_ABOUT, "About..."); - InsertMenu (hPopup, 1, MF_BYPOSITION | MF_STRING, ID_EXIT , "Exit"); - SetMenuDefaultItem (hPopup, ID_ABOUT, FALSE); - SetFocus (hWnd); - SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); - - POINT p; - if (!curpos) +namespace win32 +{ + static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) { - GetCursorPos (&p); - curpos = &p; + HMENU hPopup = CreatePopupMenu(); + InsertMenu (hPopup, 0, MF_BYPOSITION | MF_STRING, ID_ABOUT, "About..."); + InsertMenu (hPopup, 1, MF_BYPOSITION | MF_STRING, ID_EXIT , "Exit"); + SetMenuDefaultItem (hPopup, ID_ABOUT, FALSE); + SetFocus (hWnd); + SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); + + POINT p; + if (!curpos) + { + GetCursorPos (&p); + curpos = &p; + } + + WORD cmd = TrackPopupMenu (hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, curpos->x, curpos->y, 0, hWnd, NULL); + SendMessage (hWnd, WM_COMMAND, cmd, 0); + + DestroyMenu(hPopup); } - WORD cmd = TrackPopupMenu (hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, curpos->x, curpos->y, 0, hWnd, NULL); - SendMessage (hWnd, WM_COMMAND, cmd, 0); - - DestroyMenu(hPopup); -} - -void AddTrayIcon (HWND hWnd) -{ - NOTIFYICONDATA nid; - memset(&nid, 0, sizeof(nid)); - nid.cbSize = sizeof(nid); - nid.hWnd = hWnd; - nid.uID = ID_TRAY_ICON; - nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; - nid.uCallbackMessage = WM_TRAYICON; - nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (IDI_ICON1)); - strcpy (nid.szTip, "i2pd"); - Shell_NotifyIcon(NIM_ADD, &nid ); -} - -void RemoveTrayIcon (HWND hWnd) -{ - NOTIFYICONDATA nid; - nid.hWnd = hWnd; - nid.uID = ID_TRAY_ICON; - Shell_NotifyIcon (NIM_DELETE, &nid); -} - -static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) + static void AddTrayIcon (HWND hWnd) { - case WM_CREATE: + NOTIFYICONDATA nid; + memset(&nid, 0, sizeof(nid)); + nid.cbSize = sizeof(nid); + nid.hWnd = hWnd; + nid.uID = ID_TRAY_ICON; + nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + nid.uCallbackMessage = WM_TRAYICON; + nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (IDI_ICON1)); + strcpy (nid.szTip, "i2pd"); + Shell_NotifyIcon(NIM_ADD, &nid ); + } + + static void RemoveTrayIcon (HWND hWnd) + { + NOTIFYICONDATA nid; + nid.hWnd = hWnd; + nid.uID = ID_TRAY_ICON; + Shell_NotifyIcon (NIM_DELETE, &nid); + } + + static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + switch (uMsg) { - AddTrayIcon (hWnd); - break; - } - case WM_CLOSE: - { - RemoveTrayIcon (hWnd); - PostQuitMessage (0); - break; - } - case WM_COMMAND: - { - switch (LOWORD(wParam)) + case WM_CREATE: { - case ID_ABOUT: - { - MessageBox( hWnd, TEXT("i2pd"), TEXT("About"), MB_ICONINFORMATION | MB_OK ); - return 0; - } - case ID_EXIT: - { - PostMessage (hWnd, WM_CLOSE, 0, 0); - return 0; - } + AddTrayIcon (hWnd); + break; } - break; - } - case WM_TRAYICON: - { - SetForegroundWindow (hWnd); - switch (lParam) + case WM_CLOSE: { - case WM_RBUTTONUP: - { - SetForegroundWindow (hWnd); - ShowPopupMenu(hWnd, NULL, -1); - PostMessage (hWnd, WM_APP + 1, 0, 0); - break; - } + RemoveTrayIcon (hWnd); + PostQuitMessage (0); + break; + } + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case ID_ABOUT: + { + MessageBox( hWnd, TEXT("i2pd"), TEXT("About"), MB_ICONINFORMATION | MB_OK ); + return 0; + } + case ID_EXIT: + { + PostMessage (hWnd, WM_CLOSE, 0, 0); + return 0; + } + } + break; + } + case WM_TRAYICON: + { + SetForegroundWindow (hWnd); + switch (lParam) + { + case WM_RBUTTONUP: + { + SetForegroundWindow (hWnd); + ShowPopupMenu(hWnd, NULL, -1); + PostMessage (hWnd, WM_APP + 1, 0, 0); + break; + } + } + break; } - break; } + return DefWindowProc( hWnd, uMsg, wParam, lParam); + } + + bool StartWin32App () + { + if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) + { + MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); + return false; + } + // register main window + auto hInst = GetModuleHandle(NULL); + WNDCLASSEX wclx; + memset (&wclx, 0, sizeof(wclx)); + wclx.cbSize = sizeof(wclx); + wclx.style = 0; + wclx.lpfnWndProc = WndProc; + wclx.cbClsExtra = 0; + wclx.cbWndExtra = 0; + wclx.hInstance = hInst; + wclx.hIcon = LoadIcon (hInst, IDI_APPLICATION); + wclx.hIconSm = LoadIcon (hInst, IDI_APPLICATION); + wclx.hCursor = LoadCursor (NULL, IDC_ARROW); + wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wclx.lpszMenuName = NULL; + wclx.lpszClassName = I2PD_WIN32_CLASSNAME; + RegisterClassEx (&wclx); + // create new window + if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 250, 150, NULL, NULL, hInst, NULL)) + { + MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); + return false; + } + return true; + } + + int RunWin32App () + { + MSG msg; + while (GetMessage (&msg, NULL, 0, 0 )) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + return msg.wParam; + } + + void StopWin32App () + { + UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); } - return DefWindowProc( hWnd, uMsg, wParam, lParam); } - - int WINAPI WinMain (HINSTANCE hInst, HINSTANCE prev, LPSTR cmdline, int show) - { - // check if tunning already - if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) - { - MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); - return 0; - } - // register main window - WNDCLASSEX wclx; - memset (&wclx, 0, sizeof(wclx)); - wclx.cbSize = sizeof(wclx); - wclx.style = 0; - wclx.lpfnWndProc = WndProc; - wclx.cbClsExtra = 0; - wclx.cbWndExtra = 0; - wclx.hInstance = hInst; - wclx.hIcon = LoadIcon (hInst, IDI_APPLICATION); - wclx.hIconSm = LoadIcon (hInst, IDI_APPLICATION); - wclx.hCursor = LoadCursor (NULL, IDC_ARROW); - wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); - wclx.lpszMenuName = NULL; - wclx.lpszClassName = I2PD_WIN32_CLASSNAME; - RegisterClassEx (&wclx); - // create new window - if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 250, 150, NULL, NULL, hInst, NULL)) - { - MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); - return 1; - } - - /* // init - char * argv[] = { (char *)"i2pd" }; - Daemon.init(sizeof (argv)/sizeof (argv[0]), argv); - // start - Daemon.start ();*/ - // main loop - MSG msg; - while (GetMessage (&msg, NULL, 0, 0 )) - { - TranslateMessage (&msg); - DispatchMessage (&msg); - } - /* // atop - Daemon.stop ();*/ - // terminate - UnregisterClass (I2PD_WIN32_CLASSNAME, hInst); - return msg.wParam; - } +} diff --git a/Win32/Win32App.h b/Win32/Win32App.h index 8b14ae1b..7d35ec1e 100644 --- a/Win32/Win32App.h +++ b/Win32/Win32App.h @@ -3,4 +3,13 @@ #define I2PD_WIN32_CLASSNAME "i2pd main window" +namespace i2p +{ +namespace win32 +{ + bool StartWin32App (); + void StopWin32App (); + int RunWin32App (); +} +} #endif // WIN32APP_H__ From 38b6c12153d9473dbf4ba87eed48f19d2075dbf9 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 10 Mar 2016 12:05:28 -0500 Subject: [PATCH 1065/6300] fixed bug with missed data directory --- FS.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FS.cpp b/FS.cpp index 5cb64dae..e84ff1d4 100644 --- a/FS.cpp +++ b/FS.cpp @@ -68,10 +68,10 @@ namespace fs { } bool Init() { - if (boost::filesystem::exists(dataDir)) + if (!boost::filesystem::exists(dataDir)) boost::filesystem::create_directory(dataDir); std::string destinations = DataDirPath("destinations"); - if (boost::filesystem::exists(destinations)) + if (!boost::filesystem::exists(destinations)) boost::filesystem::create_directory(destinations); return true; From 0493a321d294a3225e680a782dbf6b5f15d70417 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 10 Mar 2016 12:23:17 -0500 Subject: [PATCH 1066/6300] oveeride --log for windows --- Config.h | 6 +++--- DaemonWin32.cpp | 4 +++- Makefile.mingw | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Config.h b/Config.h index 07d7ccb7..d79a9c47 100644 --- a/Config.h +++ b/Config.h @@ -30,7 +30,7 @@ namespace config { /** * @brief Parse cmdline parameters, and show help if requested * @param argc Cmdline arguments count, should be passed from main(). - * @param argv Cmdline parameters array, should be passed from main() + * @param argv Cmdline parameters array, should be passed from main() * * If --help is given in parameters, shows it's list with description * terminates the program with exitcode 0. @@ -90,10 +90,10 @@ namespace config { bool SetOption(const char *name, const T& value) { if (!m_Options.count(name)) return false; - m_Options[name] = value; + m_Options.at(name).value() = value; notify(m_Options); return true; - } + } /** * @brief Check is value explicitly given or default diff --git a/DaemonWin32.cpp b/DaemonWin32.cpp index e5de9c9a..02c602f0 100644 --- a/DaemonWin32.cpp +++ b/DaemonWin32.cpp @@ -27,7 +27,9 @@ namespace i2p SetConsoleOutputCP(1251); setlocale(LC_ALL, "Russian"); if (!i2p::win32::StartWin32App ()) return false; - + + // override log + i2p::config::SetOption("log", std::string ("file")); bool ret = Daemon_Singleton::start(); if (ret && IsLogToFile ()) { diff --git a/Makefile.mingw b/Makefile.mingw index ba741918..d752e48c 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,6 +1,6 @@ CXX = g++ WINDRES = windres -CXXFLAGS = -O2 -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN +CXXFLAGS = -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN NEEDED_CXXFLAGS = -std=c++11 BOOST_SUFFIX = -mt INCFLAGS = -I/usr/include/ -I/usr/local/include/ From 0e5b32ef13f4678de4d3246aa3dee807e7a840f7 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 10 Mar 2016 13:34:16 -0500 Subject: [PATCH 1067/6300] 2.5.1 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 0550dcc9..244096b7 100644 --- a/version.h +++ b/version.h @@ -8,7 +8,7 @@ #define I2PD_VERSION_MAJOR 2 #define I2PD_VERSION_MINOR 5 -#define I2PD_VERSION_MICRO 0 +#define I2PD_VERSION_MICRO 1 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) #define VERSION I2PD_VERSION From 5ffe1893cde1b218ef9d216f1f747f44fa8f3792 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 10 Mar 2016 14:46:45 -0500 Subject: [PATCH 1068/6300] reduce windows binary size --- Makefile.mingw | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.mingw b/Makefile.mingw index d752e48c..93867531 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,10 +1,10 @@ CXX = g++ WINDRES = windres -CXXFLAGS = -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN +CXXFLAGS = -Os -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN NEEDED_CXXFLAGS = -std=c++11 BOOST_SUFFIX = -mt INCFLAGS = -I/usr/include/ -I/usr/local/include/ -LDFLAGS = -mwindows -Wl,-rpath,/usr/local/lib \ +LDFLAGS = -mwindows -s -Wl,-rpath,/usr/local/lib \ -L/usr/local/lib \ -L/c/dev/openssl \ -L/c/dev/boost/lib From 74827cd8cfb9281c51b5886dbc4ab29233eda85c Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Mon, 7 Mar 2016 02:08:08 -0600 Subject: [PATCH 1069/6300] Workaround c++11 dynamic array for MSVC --- Identity.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index 9d4162bf..0ca9567a 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -244,21 +244,20 @@ namespace data size_t IdentityEx::FromBase64(const std::string& s) { const size_t slen = s.length(); - uint8_t buf[slen]; // binary data can't exceed base64 - const size_t len = Base64ToByteStream (s.c_str(), slen, buf, slen); - return FromBuffer (buf, len); + std::vector buf(slen); // binary data can't exceed base64 + const size_t len = Base64ToByteStream (s.c_str(), slen, buf.data(), slen); + return FromBuffer (buf.data(), len); } std::string IdentityEx::ToBase64 () const { const size_t bufLen = GetFullLen(); const size_t strLen = Base64EncodingBufferSize(bufLen); - uint8_t buf[bufLen]; - char str[strLen]; - size_t l = ToBuffer (buf, bufLen); - size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, str, strLen); - str[l1] = 0; - return std::string (str); + std::vector buf(bufLen); + std::vector str(strLen); + size_t l = ToBuffer (buf.data(), bufLen); + size_t l1 = i2p::data::ByteStreamToBase64 (buf.data(), l, str.data(), strLen); + return std::string (str.data(), l1); } size_t IdentityEx::GetSigningPublicKeyLen () const From e7f46b4fbe575562f20d67a144fa9130a9628083 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Tue, 8 Mar 2016 16:57:53 -0600 Subject: [PATCH 1070/6300] Create missing directories on the way --- FS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS.cpp b/FS.cpp index e84ff1d4..e2799d81 100644 --- a/FS.cpp +++ b/FS.cpp @@ -108,7 +108,7 @@ namespace fs { bool HashedStorage::Init(const char * chars, size_t count) { if (!boost::filesystem::exists(root)) { - boost::filesystem::create_directory(root); + boost::filesystem::create_directories(root); } for (size_t i = 0; i < count; i++) { From 18c00f0a4bbf23c12ed2a48155c73f948c5ec9d0 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Tue, 8 Mar 2016 16:58:51 -0600 Subject: [PATCH 1071/6300] Avoid debug symbol files (PDB) collision with MSVC --- build/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 577dcc66..ef5be84c 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -56,7 +56,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR MSYS) endif () add_library(libi2pd ${LIBI2PD_SRC}) -set_target_properties(libi2pd PROPERTIES OUTPUT_NAME "i2pd") +set_target_properties(libi2pd PROPERTIES PREFIX "") install(TARGETS libi2pd EXPORT libi2pd ARCHIVE DESTINATION lib From daad975f5d46438450c6465fff44132e5ef8e3ae Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Thu, 10 Mar 2016 00:12:46 -0600 Subject: [PATCH 1072/6300] fixup! invoke win32app functions from main --- DaemonWin32.cpp | 43 +++++++++++++++++++++++++++++++++++++++++-- build/CMakeLists.txt | 14 +++++++++----- i2pd.cpp | 23 +++++++++++++++++++---- 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/DaemonWin32.cpp b/DaemonWin32.cpp index 02c602f0..64b5b511 100644 --- a/DaemonWin32.cpp +++ b/DaemonWin32.cpp @@ -5,6 +5,7 @@ #ifdef _WIN32 +#include "Win32/Win32Service.h" #include "Win32/Win32App.h" namespace i2p @@ -16,8 +17,46 @@ namespace i2p setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); - setlocale(LC_ALL, "Russian"); - return Daemon_Singleton::init(argc, argv); + + if (!Daemon_Singleton::init(argc, argv)) + return false; + + std::string serviceControl; i2p::config::GetOption("svcctl", serviceControl); + if (serviceControl == "install") + { + LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service"); + InstallService( + SERVICE_NAME, // Name of service + SERVICE_DISPLAY_NAME, // Name to display + SERVICE_START_TYPE, // Service start type + SERVICE_DEPENDENCIES, // Dependencies + SERVICE_ACCOUNT, // Service running account + SERVICE_PASSWORD // Password of the account + ); + return false; + } + else if (serviceControl == "remove") + { + LogPrint(eLogInfo, "WinSVC: uninstalling ", SERVICE_NAME, " service"); + UninstallService(SERVICE_NAME); + return false; + } + + if (isDaemon == 1) + { + LogPrint(eLogDebug, "Daemon: running as service"); + I2PService service(SERVICE_NAME); + if (!I2PService::Run(service)) + { + LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); + return false; + } + return false; + } + else + LogPrint(eLogDebug, "Daemon: running as user"); + + return true; } bool DaemonWin32::start() diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index ef5be84c..c9c590f6 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -157,6 +157,7 @@ elseif (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonLinux.cpp") elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR MSYS) list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonWin32.cpp") + list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32App.cpp") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32Service.cpp") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Resource.rc") endif () @@ -309,11 +310,14 @@ include(GNUInstallDirs) if (WITH_BINARY) add_executable ( "${PROJECT_NAME}" ${DAEMON_SRC} ) - if(NOT MSVC) # FIXME: incremental linker file name (.ilk) collision for dll & exe - if (WITH_STATIC) - set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static" ) - endif () - endif() + if (WIN32) + set_target_properties("${PROJECT_NAME}" PROPERTIES WIN32_EXECUTABLE TRUE ) + endif() + if(NOT MSVC) + if (WITH_STATIC) + set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static" ) + endif () + endif() if (WITH_PCH) if (MSVC) diff --git a/i2pd.cpp b/i2pd.cpp index 6167f10e..f3ac6b3f 100644 --- a/i2pd.cpp +++ b/i2pd.cpp @@ -3,10 +3,25 @@ int main( int argc, char* argv[] ) { - Daemon.init(argc, argv); - if (Daemon.start()) - Daemon.run (); - Daemon.stop(); + if (Daemon.init(argc, argv)) + { + if (Daemon.start()) + Daemon.run (); + Daemon.stop(); + } return EXIT_SUCCESS; } +#ifdef _WIN32 +#include + +int CALLBACK WinMain( + _In_ HINSTANCE hInstance, + _In_ HINSTANCE hPrevInstance, + _In_ LPSTR lpCmdLine, + _In_ int nCmdShow + ) +{ + return main(__argc, __argv); +} +#endif From 33494c4f4b9c745f12b4aea34b8d972a9f6c91e7 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 9 Mar 2016 00:29:32 -0600 Subject: [PATCH 1073/6300] Catch up for miniupnpc API 15 --- UPnP.cpp | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/UPnP.cpp b/UPnP.cpp index f8c038ff..f2dddae1 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -24,22 +24,13 @@ #include // These are per-process and are safe to reuse for all threads -#ifndef UPNPDISCOVER_SUCCESS -/* miniupnpc 1.5 */ -UPNPDev* (*upnpDiscoverFunc) (int, const char *, const char *, int); -int (*UPNP_AddPortMappingFunc) (const char *, const char *, const char *, const char *, - const char *, const char *, const char *, const char *); -#else -/* miniupnpc 1.6 */ -UPNPDev* (*upnpDiscoverFunc) (int, const char *, const char *, int, int, int *); -int (*UPNP_AddPortMappingFunc) (const char *, const char *, const char *, const char *, - const char *, const char *, const char *, const char *, const char *); -#endif -int (*UPNP_GetValidIGDFunc) (struct UPNPDev *, struct UPNPUrls *, struct IGDdatas *, char *, int); -int (*UPNP_GetExternalIPAddressFunc) (const char *, const char *, char *); -int (*UPNP_DeletePortMappingFunc) (const char *, const char *, const char *, const char *, const char *); -void (*freeUPNPDevlistFunc) (struct UPNPDev *); -void (*FreeUPNPUrlsFunc) (struct UPNPUrls *); +decltype(upnpDiscover) *upnpDiscoverFunc; +decltype(UPNP_AddPortMapping) *UPNP_AddPortMappingFunc; +decltype(UPNP_GetValidIGD) *UPNP_GetValidIGDFunc; +decltype(UPNP_GetExternalIPAddress) *UPNP_GetExternalIPAddressFunc; +decltype(UPNP_DeletePortMapping) *UPNP_DeletePortMappingFunc; +decltype(freeUPNPDevlist) *freeUPNPDevlistFunc; +decltype(FreeUPNPUrls) *FreeUPNPUrlsFunc; // Nice approach http://stackoverflow.com/a/21517513/673826 template @@ -134,7 +125,11 @@ namespace transport #else /* miniupnpc 1.6 */ int nerror = 0; - m_Devlist = upnpDiscoverFunc (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, &nerror); +#if MINIUPNPC_API_VERSION >= 15 + m_Devlist = upnpDiscoverFunc(2000, m_MulticastIf, m_Minissdpdpath, 0, 0, 0, &nerror); +#else + m_Devlist = upnpDiscoverFunc(2000, m_MulticastIf, m_Minissdpdpath, 0, 0, &nerror); +#endif #endif int r; From abdef67ccc2d715fc3a39ab2a4747a9abb3367dc Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 9 Mar 2016 00:30:53 -0600 Subject: [PATCH 1074/6300] _WIN32_WINNT drove nuts 64 bit MSVC builds TODO: figure out why --- build/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index c9c590f6..f863bd69 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -95,7 +95,7 @@ endif () # compiler flags customization (by vendor) if (MSVC) - add_definitions( -D_WIN32_WINNT=_WIN32_WINNT_WINXP -DWIN32_LEAN_AND_MEAN -DNOMINMAX ) #-DOPENSSL_NO_SSL2 -DOPENSSL_USE_DEPRECATED + add_definitions( -DWIN32_LEAN_AND_MEAN -DNOMINMAX ) # TODO Check & report to Boost dev, there should be no need for these two add_definitions( -DBOOST_THREAD_NO_LIB -DBOOST_CHRONO_NO_LIB ) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL" ) From 8791f382b399f9727dfc8b3854a26ce29ee4c187 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 9 Mar 2016 00:42:58 -0600 Subject: [PATCH 1075/6300] Make a deep copy of our addresses for UPnP Somehow "Expression: vector iterators incompatible" gets thrown especially on fresh start TODO: figure out details --- UPnP.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/UPnP.cpp b/UPnP.cpp index f2dddae1..0466d9e7 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -19,6 +19,7 @@ #include "UPnP.h" #include "NetDb.h" #include "util.h" +#include "RouterInfo.h" #include #include @@ -100,7 +101,8 @@ namespace transport void UPnP::Run () { - for (auto& address : context.GetRouterInfo ().GetAddresses ()) + std::vector a = context.GetRouterInfo().GetAddresses(); + for (auto& address : a) { if (!address.host.is_v6 ()) { From 5a9ef57f78fe423e27013efd7792ab582d740515 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 9 Mar 2016 02:47:35 -0600 Subject: [PATCH 1076/6300] Make mingw via cmake happy with _WIN32 in FS.CPP --- FS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS.cpp b/FS.cpp index e2799d81..19963195 100644 --- a/FS.cpp +++ b/FS.cpp @@ -9,7 +9,7 @@ #include #include -#ifdef WIN32 +#ifdef _WIN32 #include #endif From 4532ca97faf1ba84ba827b3609a37c86e29aca24 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Wed, 9 Mar 2016 20:20:27 -0600 Subject: [PATCH 1077/6300] caffeine insomnia for win32 --- Config.cpp | 1 + DaemonWin32.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Config.cpp b/Config.cpp index 0de4d0aa..15c8ad67 100644 --- a/Config.cpp +++ b/Config.cpp @@ -125,6 +125,7 @@ namespace config { ("bandwidth", value()->default_value('-'), "Bandwidth limiting: L - 32kbps, O - 256Kbps, P - unlimited") #ifdef _WIN32 ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") + ("insomnia", value()->zero_tokens()->default_value(false), "Prevent system from sleeping") #endif ; diff --git a/DaemonWin32.cpp b/DaemonWin32.cpp index 64b5b511..222d2395 100644 --- a/DaemonWin32.cpp +++ b/DaemonWin32.cpp @@ -76,6 +76,9 @@ namespace i2p SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE); SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); } + bool insomnia; i2p::config::GetOption("insomnia", insomnia); + if (insomnia) + SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); return ret; } From ab5f1e712bba5a093cf7c8a1fdc7084e861b36f5 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Thu, 10 Mar 2016 14:40:35 -0600 Subject: [PATCH 1078/6300] AppVeyor msys fix attempt --- appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index e190b949..6600714d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -142,9 +142,9 @@ install: - if not defined msvc ( C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -Sy bash pacman pacman-mirrors msys2-runtime msys2-runtime-devel" && if "%x64%" == "1" ( - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-x86_64-boost mingw-w64-x86_64-miniupnpc" + C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-x86_64-openssl mingw-w64-x86_64-boost mingw-w64-x86_64-miniupnpc" ) else ( - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-i686-boost mingw-w64-i686-miniupnpc" + C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-i686-openssl mingw-w64-i686-boost mingw-w64-i686-miniupnpc" ) ) cache: @@ -167,7 +167,7 @@ build_script: echo "bitness=%bitness%; static=%static%; dll=%dll%; type=%type%; generator=%generator%; variant=%variant%; cmake=%cmake%; cmake_extra=%cmake_extra%" - if not defined msvc ( - C:\msys64\usr\bin\bash -lc "export PATH=/mingw%bitness%/bin:/usr/bin && cd /c/projects/build && cmake /c/projects/i2pd/build -G 'Unix Makefiles' -DWITH_AESNI=ON -DWITH_UPNP=ON %cmake% %cmake_extra% -DWITH_STATIC=%static% -DWITH_HARDENING=ON -DCMAKE_INSTALL_PREFIX:PATH=/c/projects/instdir -DCMAKE_FIND_ROOT_PATH=/mingw%bitness% && make install" + C:\msys64\usr\bin\bash -lc "export PATH=/mingw%bitness%/bin:/usr/bin && cd /c/projects/build && CC=/mingw%bitness%/bin/gcc.exe CXX=/mingw%bitness%/bin/g++.exe /usr/bin/cmake /c/projects/i2pd/build -G 'Unix Makefiles' -DWITH_AESNI=ON -DWITH_UPNP=ON %cmake% %cmake_extra% -DWITH_STATIC=%static% -DWITH_HARDENING=ON -DCMAKE_INSTALL_PREFIX:PATH=/c/projects/instdir -DCMAKE_FIND_ROOT_PATH=/mingw%bitness% && make install" && 7z a -tzip -mx9 -mmt C:\projects\i2pd\i2pd-mingw-win%bitness%-%type%.zip C:\projects\instdir\* C:\msys64\mingw%bitness%\bin\zlib1.dll C:\msys64\mingw%bitness%\bin\*eay32.dll ) - rem We are fine with multiple generated configurations in MS solution. Will use later From f8f2ab9cbaa0e62f62fdee7c89abb20043d60a62 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 10 Mar 2016 19:34:32 -0500 Subject: [PATCH 1079/6300] fixed windows build --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8f1847f3..d9d62b4c 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ else ifeq ($(UNAME),Linux) DAEMON_SRC += DaemonLinux.cpp include Makefile.linux else # win32 mingw - DAEMON_SRC += DaemonWin32.cpp Win32/Win32App.cpp + DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp include Makefile.mingw endif From b0395933dea8268d3381b263504b758cb20899e8 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 11 Mar 2016 00:46:52 +0000 Subject: [PATCH 1080/6300] * Addressbook: fix module name --- AddressBook.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index f66387db..aade32ee 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -56,7 +56,7 @@ namespace client f.seekg (0,std::ios::end); size_t len = f.tellg (); if (len < i2p::data::DEFAULT_IDENTITY_SIZE) { - LogPrint (eLogError, "Addresbook: File ", filename, " is too short: ", len); + LogPrint (eLogError, "Addressbook: File ", filename, " is too short: ", len); return nullptr; } f.seekg(0, std::ios::beg); @@ -72,7 +72,7 @@ namespace client std::string path = storage.Path( address->GetIdentHash().ToBase32() ); std::ofstream f (path, std::ofstream::binary | std::ofstream::out); if (!f.is_open ()) { - LogPrint (eLogError, "Addresbook: can't open file ", path); + LogPrint (eLogError, "Addressbook: can't open file ", path); return; } size_t len = address->GetFullLen (); @@ -174,17 +174,17 @@ namespace client } if (m_IsDownloading) { - LogPrint (eLogInfo, "Addresbook: subscriptions is downloading, abort"); + LogPrint (eLogInfo, "Addressbook: subscriptions is downloading, abort"); for (int i = 0; i < 30; i++) { if (!m_IsDownloading) { - LogPrint (eLogInfo, "Addresbook: subscriptions download complete"); + LogPrint (eLogInfo, "Addressbook: subscriptions download complete"); break; } std::this_thread::sleep_for (std::chrono::seconds (1)); // wait for 1 seconds } - LogPrint (eLogError, "Addresbook: subscription download timeout"); + LogPrint (eLogError, "Addressbook: subscription download timeout"); m_IsDownloading = false; } if (m_Storage) @@ -303,10 +303,10 @@ namespace client numAddresses++; } else - LogPrint (eLogError, "Addresbook: malformed address ", addr, " for ", name); + LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name); } } - LogPrint (eLogInfo, "Addresbook: ", numAddresses, " addresses processed"); + LogPrint (eLogInfo, "Addressbook: ", numAddresses, " addresses processed"); if (numAddresses > 0) { m_IsLoaded = true; @@ -331,7 +331,7 @@ namespace client LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); } else - LogPrint (eLogWarning, "Addresbook: subscriptions.txt not found in datadir"); + LogPrint (eLogWarning, "Addressbook: subscriptions.txt not found in datadir"); } else LogPrint (eLogError, "Addressbook: subscriptions already loaded"); @@ -368,7 +368,7 @@ namespace client this, std::placeholders::_1)); } else - LogPrint (eLogError, "Addresbook: can't start subscriptions: missing shared local destination"); + LogPrint (eLogError, "Addressbook: can't start subscriptions: missing shared local destination"); } void AddressBook::StopSubscriptions () @@ -429,7 +429,7 @@ namespace client void AddressBookSubscription::Request () { // must be run in separate thread - LogPrint (eLogInfo, "Addresbook: Downloading hosts database from ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); + 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; @@ -488,7 +488,7 @@ namespace client 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, "Addresbook: subscriptions request timeout expired"); + LogPrint (eLogError, "Addressbook: subscriptions request timeout expired"); } // process remaining buffer while (size_t len = stream->ReadSome (buf, 4096)) From 8f3daad5022b0aa56a6f4814f79232c1b9b39556 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Fri, 11 Mar 2016 02:35:49 -0600 Subject: [PATCH 1081/6300] Sane TTL for UPnP API>=14 and remove old miniupnpc support --- UPnP.cpp | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/UPnP.cpp b/UPnP.cpp index 0466d9e7..c4e5e04c 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -121,17 +121,11 @@ namespace transport void UPnP::Discover () { -#ifndef UPNPDISCOVER_SUCCESS - /* miniupnpc 1.5 */ - m_Devlist = upnpDiscoverFunc (2000, m_MulticastIf, m_Minissdpdpath, 0); -#else - /* miniupnpc 1.6 */ int nerror = 0; -#if MINIUPNPC_API_VERSION >= 15 - m_Devlist = upnpDiscoverFunc(2000, m_MulticastIf, m_Minissdpdpath, 0, 0, 0, &nerror); +#if MINIUPNPC_API_VERSION >= 14 + m_Devlist = upnpDiscoverFunc (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, 2, &nerror); #else - m_Devlist = upnpDiscoverFunc(2000, m_MulticastIf, m_Minissdpdpath, 0, 0, &nerror); -#endif + m_Devlist = upnpDiscoverFunc (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, &nerror); #endif int r; @@ -177,13 +171,7 @@ namespace transport std::string strDesc = "I2Pd"; try { for (;;) { -#ifndef UPNPDISCOVER_SUCCESS - /* miniupnpc 1.5 */ - r = UPNP_AddPortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0); -#else - /* miniupnpc 1.6 */ r = UPNP_AddPortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0"); -#endif if (r!=UPNPCOMMAND_SUCCESS) { LogPrint (eLogError, "UPnP: AddPortMapping (", strPort.c_str () ,", ", strPort.c_str () ,", ", m_NetworkAddr, ") failed with code ", r); From 400e3d21f9d1efa3985eac9685e784d87b62ed05 Mon Sep 17 00:00:00 2001 From: xcps Date: Fri, 11 Mar 2016 15:30:50 +0500 Subject: [PATCH 1082/6300] jump services --- HTTPProxy.cpp | 20 ++++++++++++++++++++ HTTPServer.cpp | 22 +++++++++++++++++++++- HTTPServer.h | 1 + 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index f0166a4a..160c27f6 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -12,6 +12,7 @@ #include "ClientContext.h" #include "I2PEndian.h" #include "I2PTunnel.h" +#include "Config.h" namespace i2p { @@ -36,6 +37,7 @@ namespace proxy void Terminate(); void AsyncSockRead(); void HTTPRequestFailed(/*std::string message*/); + void RedirectToJumpService(); void ExtractRequest(); bool ValidateHTTPRequest(); void HandleJumpServices(); @@ -95,6 +97,17 @@ namespace proxy std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } + void HTTPProxyHandler::RedirectToJumpService(/*HTTPProxyHandler::errTypes error*/) + { + std::stringstream response; + std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); + uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); + + response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?jumpservices=&address=" << m_address << "\r\n\r\n"; + boost::asio::async_write(*m_sock, boost::asio::buffer(response.str (),response.str ().length ()), + std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + } + void HTTPProxyHandler::EnterState(HTTPProxyHandler::state nstate) { m_state = nstate; @@ -168,6 +181,13 @@ namespace proxy ExtractRequest(); //TODO: parse earlier if (!ValidateHTTPRequest()) return false; HandleJumpServices(); + + i2p::data::IdentHash identHash; + if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ + RedirectToJumpService(); + return false; + } + m_request = m_method; m_request.push_back(' '); m_request += m_path; diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 3b92d4bc..b0281692 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -203,6 +203,9 @@ namespace util const char HTTP_COMMAND_SAM_SESSION[] = "sam_session"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_COMMAND_I2P_TUNNELS[] = "i2p_tunnels"; + const char HTTP_COMMAND_JUMPSERVICES[] = "jumpservices="; + const char HTTP_PARAM_ADDRESS[] = "address"; + namespace misc_strings { @@ -393,6 +396,7 @@ namespace util else s << "Start accepting tunnels
\r\n
\r\n"; s << "Run peer test
\r\n
\r\n"; + s << "Jump services
\r\n
\r\n"; s << "
"; if (address.length () > 1) HandleCommand (address.substr (2), s); @@ -464,7 +468,13 @@ namespace util ShowTransports (s); else if (cmd == HTTP_COMMAND_TUNNELS) ShowTunnels (s); - else if (cmd == HTTP_COMMAND_TRANSIT_TUNNELS) + else if (cmd == HTTP_COMMAND_JUMPSERVICES) + { + std::map params; + ExtractParams (command.substr (paramsPos), params); + auto address = params[HTTP_PARAM_ADDRESS]; + ShowJumpServices (address, s); + } else if (cmd == HTTP_COMMAND_TRANSIT_TUNNELS) ShowTransitTunnels (s); else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS) StartAcceptingTunnels (s); @@ -494,6 +504,16 @@ namespace util ShowI2PTunnels (s); } + void HTTPConnection::ShowJumpServices (const std::string& address, std::stringstream& s) + { + s << "
"; + s << ""; + s << "
\r\n"; + s << "Jump services for " << address << ""; + s << ""; + } + void HTTPConnection::ShowLocalDestinations (std::stringstream& s) { s << "Local Destinations:
\r\n
\r\n"; diff --git a/HTTPServer.h b/HTTPServer.h index 702e3191..f70e27dc 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -62,6 +62,7 @@ namespace util void HandleRequest (const std::string& address); void HandleCommand (const std::string& command, std::stringstream& s); + void ShowJumpServices (const std::string& address, std::stringstream& s); void ShowTransports (std::stringstream& s); void ShowTunnels (std::stringstream& s); void ShowTransitTunnels (std::stringstream& s); From a1fc48f2a6688a3ef7255d3e41d1f3e4432d2fac Mon Sep 17 00:00:00 2001 From: xcps Date: Fri, 11 Mar 2016 16:16:11 +0500 Subject: [PATCH 1083/6300] Update HTTPServer.cpp --- HTTPServer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index b0281692..ddfa6478 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -510,8 +510,8 @@ namespace util s << ""; s << "
\r\n"; s << "Jump services for " << address << ""; - s << ""; + s << ""; } void HTTPConnection::ShowLocalDestinations (std::stringstream& s) From 94806ad0b317b56a42c527cba27add0842c8e480 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 11 Mar 2016 16:29:49 -0500 Subject: [PATCH 1084/6300] try subscriptions right after initial download --- AddressBook.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index aade32ee..3b3f85b4 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -340,15 +340,18 @@ namespace client void AddressBook::DownloadComplete (bool success) { m_IsDownloading = false; - if (success && m_DefaultSubscription) + int nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT; + if (success) { - m_DefaultSubscription.reset (nullptr); - m_IsLoaded = true; + if (m_DefaultSubscription) m_DefaultSubscription.reset (nullptr); + if (m_IsLoaded) + nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT; + else + m_IsLoaded = true; } if (m_SubscriptionsUpdateTimer) { - m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes( - success ? CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT : CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT)); + m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(nextUpdateTimeout)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, this, std::placeholders::_1)); } From 3c55c2d7771721273e013644e98f2484c5947c7a Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 11 Mar 2016 19:27:43 -0500 Subject: [PATCH 1085/6300] fixed race condition at startup --- Transports.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Transports.cpp b/Transports.cpp index acffd865..5fcce0fc 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -261,7 +261,7 @@ namespace transport { peer.numAttempts++; auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ()); - if (address) + if (address && m_NTCPServer) { #if BOOST_VERSION >= 104900 if (!address->host.is_unspecified ()) // we have address now From 355c7437ed202d3b6b08f1456eca939268263cd8 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 11 Mar 2016 22:24:23 -0500 Subject: [PATCH 1086/6300] supoort win32 console application --- DaemonWin32.cpp | 189 ++++++++++++++++++++++++++---------------------- Makefile.mingw | 12 ++- 2 files changed, 111 insertions(+), 90 deletions(-) diff --git a/DaemonWin32.cpp b/DaemonWin32.cpp index 222d2395..b28cf2cd 100644 --- a/DaemonWin32.cpp +++ b/DaemonWin32.cpp @@ -1,98 +1,113 @@ -#include "Config.h" -#include "Daemon.h" -#include "util.h" -#include "Log.h" - -#ifdef _WIN32 - -#include "Win32/Win32Service.h" -#include "Win32/Win32App.h" - -namespace i2p -{ - namespace util - { - bool DaemonWin32::init(int argc, char* argv[]) - { - setlocale(LC_CTYPE, ""); - SetConsoleCP(1251); - SetConsoleOutputCP(1251); +#include +#include "Config.h" +#include "Daemon.h" +#include "util.h" +#include "Log.h" - if (!Daemon_Singleton::init(argc, argv)) - return false; - - std::string serviceControl; i2p::config::GetOption("svcctl", serviceControl); - if (serviceControl == "install") - { - LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service"); - InstallService( - SERVICE_NAME, // Name of service - SERVICE_DISPLAY_NAME, // Name to display - SERVICE_START_TYPE, // Service start type - SERVICE_DEPENDENCIES, // Dependencies - SERVICE_ACCOUNT, // Service running account - SERVICE_PASSWORD // Password of the account - ); - return false; - } - else if (serviceControl == "remove") - { - LogPrint(eLogInfo, "WinSVC: uninstalling ", SERVICE_NAME, " service"); - UninstallService(SERVICE_NAME); - return false; - } - - if (isDaemon == 1) - { - LogPrint(eLogDebug, "Daemon: running as service"); - I2PService service(SERVICE_NAME); - if (!I2PService::Run(service)) - { - LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); - return false; - } - return false; - } - else - LogPrint(eLogDebug, "Daemon: running as user"); - - return true; +#ifdef _WIN32 + +#include "Win32/Win32Service.h" +#ifdef WIN32_APP +#include "Win32/Win32App.h" +#endif + +namespace i2p +{ + namespace util + { + bool DaemonWin32::init(int argc, char* argv[]) + { + setlocale(LC_CTYPE, ""); + SetConsoleCP(1251); + SetConsoleOutputCP(1251); + + if (!Daemon_Singleton::init(argc, argv)) + return false; + + std::string serviceControl; i2p::config::GetOption("svcctl", serviceControl); + if (serviceControl == "install") + { + LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service"); + InstallService( + SERVICE_NAME, // Name of service + SERVICE_DISPLAY_NAME, // Name to display + SERVICE_START_TYPE, // Service start type + SERVICE_DEPENDENCIES, // Dependencies + SERVICE_ACCOUNT, // Service running account + SERVICE_PASSWORD // Password of the account + ); + return false; + } + else if (serviceControl == "remove") + { + LogPrint(eLogInfo, "WinSVC: uninstalling ", SERVICE_NAME, " service"); + UninstallService(SERVICE_NAME); + return false; + } + + if (isDaemon == 1) + { + LogPrint(eLogDebug, "Daemon: running as service"); + I2PService service(SERVICE_NAME); + if (!I2PService::Run(service)) + { + LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); + return false; + } + return false; + } + else + LogPrint(eLogDebug, "Daemon: running as user"); + + return true; } - - bool DaemonWin32::start() - { - setlocale(LC_CTYPE, ""); - SetConsoleCP(1251); - SetConsoleOutputCP(1251); - setlocale(LC_ALL, "Russian"); + + bool DaemonWin32::start() + { + setlocale(LC_CTYPE, ""); + SetConsoleCP(1251); + SetConsoleOutputCP(1251); + setlocale(LC_ALL, "Russian"); +#ifdef WIN32_APP if (!i2p::win32::StartWin32App ()) return false; // override log - i2p::config::SetOption("log", std::string ("file")); - bool ret = Daemon_Singleton::start(); - if (ret && IsLogToFile ()) - { - // TODO: find out where this garbage to console comes from - SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE); - SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); - } - bool insomnia; i2p::config::GetOption("insomnia", insomnia); - if (insomnia) - SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); - return ret; - } - - bool DaemonWin32::stop() + i2p::config::SetOption("log", std::string ("file")); +#endif + bool ret = Daemon_Singleton::start(); + if (ret && IsLogToFile ()) + { + // TODO: find out where this garbage to console comes from + SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE); + SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); + } + bool insomnia; i2p::config::GetOption("insomnia", insomnia); + if (insomnia) + SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); + return ret; + } + + bool DaemonWin32::stop() { - i2p::win32::StopWin32App (); - return Daemon_Singleton::stop(); +#ifdef WIN32_APP + i2p::win32::StopWin32App (); +#endif + return Daemon_Singleton::stop(); } void DaemonWin32::run () { +#ifdef WIN32_APP i2p::win32::RunWin32App (); - } - } -} - -#endif +#else + while (running) + { + std::this_thread::sleep_for (std::chrono::seconds(1)); + } + +#endif + } + } +} + +#endif diff --git a/Makefile.mingw b/Makefile.mingw index 93867531..1b500757 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,10 +1,11 @@ +USE_WIN32_APP=yes CXX = g++ WINDRES = windres CXXFLAGS = -Os -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN NEEDED_CXXFLAGS = -std=c++11 BOOST_SUFFIX = -mt INCFLAGS = -I/usr/include/ -I/usr/local/include/ -LDFLAGS = -mwindows -s -Wl,-rpath,/usr/local/lib \ +LDFLAGS = -Wl,-rpath,/usr/local/lib \ -L/usr/local/lib \ -L/c/dev/openssl \ -L/c/dev/boost/lib @@ -24,8 +25,13 @@ LDLIBS = \ -static-libgcc -static-libstdc++ \ -Wl,-Bstatic -lstdc++ \ -Wl,-Bstatic -lpthread -DAEMON_RC += Win32/Resource.rc -DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) + +ifeq ($(USE_WIN32_APP), yes) + CXXFLAGS += -DWIN32_APP + LDFLAGS += -mwindows -s + DAEMON_RC += Win32/Resource.rc + DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) +endif ifeq ($(USE_AESNI),1) CPU_FLAGS = -maes -DAESNI From 0a08765d73660db671dff815b3b2ca20bb4a9667 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Mon, 14 Mar 2016 02:35:15 -0500 Subject: [PATCH 1087/6300] Win32: hide to tray, webconsole menu item Standard icon works for me on Windows 8 --- Win32/Resource.rc | 1 - Win32/Win32App.cpp | 51 +++++++++++++++++++++++++++++++++--------- Win32/ictoopie_16.ico | Bin 1150 -> 0 bytes Win32/resource.h | 2 +- build/CMakeLists.txt | 9 ++++++-- 5 files changed, 48 insertions(+), 15 deletions(-) delete mode 100644 Win32/ictoopie_16.ico diff --git a/Win32/Resource.rc b/Win32/Resource.rc index 56868181..e10ec496 100644 --- a/Win32/Resource.rc +++ b/Win32/Resource.rc @@ -53,7 +53,6 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. MAINICON ICON "ictoopie.ico" -IDI_ICON1 ICON "ictoopie_16.ico" #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index c997b266..cd9a8f34 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -1,12 +1,14 @@ #include #include #include -//#include "../Daemon.h" +#include "../Config.h" #include "resource.h" #include "Win32App.h" #define ID_ABOUT 2000 #define ID_EXIT 2001 +#define ID_CONSOLE 2002 +#define ID_APP 2003 #define ID_TRAY_ICON 2050 #define WM_TRAYICON (WM_USER + 1) @@ -18,10 +20,12 @@ namespace win32 static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) { HMENU hPopup = CreatePopupMenu(); - InsertMenu (hPopup, 0, MF_BYPOSITION | MF_STRING, ID_ABOUT, "About..."); - InsertMenu (hPopup, 1, MF_BYPOSITION | MF_STRING, ID_EXIT , "Exit"); - SetMenuDefaultItem (hPopup, ID_ABOUT, FALSE); - SetFocus (hWnd); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_CONSOLE, "Open &console"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "Show app"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, NULL, NULL); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_EXIT, "E&xit"); + SetMenuDefaultItem (hPopup, ID_CONSOLE, FALSE); SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); POINT p; @@ -44,10 +48,11 @@ namespace win32 nid.cbSize = sizeof(nid); nid.hWnd = hWnd; nid.uID = ID_TRAY_ICON; - nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; nid.uCallbackMessage = WM_TRAYICON; - nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (IDI_ICON1)); + nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (MAINICON)); strcpy (nid.szTip, "i2pd"); + strcpy (nid.szInfo, "i2pd is running"); Shell_NotifyIcon(NIM_ADD, &nid ); } @@ -88,14 +93,39 @@ namespace win32 PostMessage (hWnd, WM_CLOSE, 0, 0); return 0; } + case ID_CONSOLE: + { + char buf[30]; + std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); + uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); + std::snprintf(buf, 30, "http://%s:%d", httpAddr.c_str(), httpPort); + ShellExecute(NULL, "open", buf, NULL, NULL, SW_SHOWNORMAL); + return 0; + } + case ID_APP: + { + ShowWindow(hWnd, SW_SHOW); + return 0; + } } break; } + case WM_SYSCOMMAND: + { + switch (wParam) + { + case SC_MINIMIZE: + { + ShowWindow(hWnd, SW_HIDE); + return 0; + } + } + } case WM_TRAYICON: { - SetForegroundWindow (hWnd); switch (lParam) { + case WM_LBUTTONUP: case WM_RBUTTONUP: { SetForegroundWindow (hWnd); @@ -127,15 +157,14 @@ namespace win32 wclx.cbClsExtra = 0; wclx.cbWndExtra = 0; wclx.hInstance = hInst; - wclx.hIcon = LoadIcon (hInst, IDI_APPLICATION); - wclx.hIconSm = LoadIcon (hInst, IDI_APPLICATION); + wclx.hIcon = LoadIcon (hInst, MAKEINTRESOURCE(MAINICON)); wclx.hCursor = LoadCursor (NULL, IDC_ARROW); wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wclx.lpszMenuName = NULL; wclx.lpszClassName = I2PD_WIN32_CLASSNAME; RegisterClassEx (&wclx); // create new window - if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 250, 150, NULL, NULL, hInst, NULL)) + if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPEDWINDOW, 100, 100, 250, 150, NULL, NULL, hInst, NULL)) { MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); return false; diff --git a/Win32/ictoopie_16.ico b/Win32/ictoopie_16.ico deleted file mode 100644 index 525646c351db354f366a1a912c0471c2b9c71193..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmai!T`b#C7{*T#TEzN=i>1*qHY-|esLu|G>U>ngQj*z(8P;ve>U-2k%|^x$#V*u0 zTV$gm3o}eI*&^I-S+b8vwq&u%E)W-<{h5n@Y~ek5&ikDAyuXuka}FU4YOvXanuR3C ziV!D4$Z1MciK_gqQ;Eo^y`w^-(PVMC+)YQm<4>VbxF!~h*Zj}=uSuoS4TVC{d=y_@ zT^+|4@S#YLg?F$woL!s|c`*{sZqA_i-lKT2MBGziEQR^285La>2u}z{$6N>Wy?TiJ zM5|$;VaE?)db}Sw*ccFIpE(C7Jzhz}!eD)3fVjzw4tR?s`KCR$kTeuB?tm|fhf}@@Ees?2SZ4G&yg(7tfMk+^ zs5A2ADnq4`&QDKIGafYD=Lh*p+tXPZ6j@b)b>ktVK02rb<J~%pN+|sxR z?puKKlG4l$IFz>>`3ON!$jV?(w>7<=?kja#FZ-n2ZM)Zi?a(UNQ`=y_+JW8kU$B1z zuyXF)~LtmRZjRF^0+!AS=19M^n%*ic~=0!M!#lJ7eySDqRkBrKa zA_ti)A~!HO{he=O+ER*Itt+^myZbHt6YJD#+LH~H_ccr1P`|HrD(}`m)3nz|aa%5g F`~i^{J6iw% diff --git a/Win32/resource.h b/Win32/resource.h index 7bb73d38..a8309c8b 100644 --- a/Win32/resource.h +++ b/Win32/resource.h @@ -2,7 +2,7 @@ // Microsoft Visual C++ generated include file. // Used by Resource.rc // -#define IDI_ICON1 101 +#define MAINICON 101 // Next default values for new objects // diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index f863bd69..76963026 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -11,6 +11,7 @@ option(WITH_BINARY "Build binary" ON) option(WITH_STATIC "Static build" OFF) option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_PCH "Use precompiled header" OFF) +option(WITH_GUI "Include GUI (currently MS Windows only)" ON) # paths set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) @@ -157,7 +158,11 @@ elseif (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonLinux.cpp") elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR MSYS) list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/DaemonWin32.cpp") - list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32App.cpp") + if (WITH_GUI) + list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32App.cpp") + set_source_files_properties("${CMAKE_SOURCE_DIR}/DaemonWin32.cpp" + PROPERTIES COMPILE_DEFINITIONS WIN32_APP) + endif () list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32Service.cpp") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Resource.rc") endif () @@ -310,7 +315,7 @@ include(GNUInstallDirs) if (WITH_BINARY) add_executable ( "${PROJECT_NAME}" ${DAEMON_SRC} ) - if (WIN32) + if (WIN32 AND WITH_GUI) set_target_properties("${PROJECT_NAME}" PROPERTIES WIN32_EXECUTABLE TRUE ) endif() if(NOT MSVC) From 4934fc8809124c0f223e93f565e5ba4a78ec1f1d Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 14 Mar 2016 13:33:51 -0400 Subject: [PATCH 1088/6300] fixed typo --- Base.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Base.cpp b/Base.cpp index e894b694..e0b6af07 100644 --- a/Base.cpp +++ b/Base.cpp @@ -8,8 +8,8 @@ namespace data { static const char T32[32] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - 'i', 'k', 'k', 'l', 'm', 'n', 'o', 'p', - 'q', 'r', 't', 't', 'u', 'v', 'w', 'x', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '2', '3', '4', '5', '6', '7', }; From 1a894abcff34e4828f14f274329bf9a2f828b20f Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 14 Mar 2016 16:05:57 -0400 Subject: [PATCH 1089/6300] persist etag for addressbook subscription --- AddressBook.cpp | 23 +++++++++++++++++++---- AddressBook.h | 4 +++- FS.cpp | 7 +++++++ FS.h | 2 ++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 3b3f85b4..4248ae32 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -24,7 +24,7 @@ namespace client { private: i2p::fs::HashedStorage storage; - std::string indexPath; + std::string etagsPath, indexPath; public: AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") {}; @@ -35,11 +35,18 @@ namespace client bool Init (); int Load (std::map& addresses); int Save (const std::map& addresses); + + void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified); }; bool AddressBookFilesystemStorage::Init() - { + { storage.SetPlace(i2p::fs::GetDataDir()); + // init ETags + etagsPath = storage.GetRoot() + i2p::fs::dirSep + "etags"; + if (!i2p::fs::Exists (etagsPath)) + i2p::fs::CreateDirectory (etagsPath); + // init storage indexPath = storage.GetRoot() + i2p::fs::dirSep + "addresses.csv"; return storage.Init(i2p::data::GetBase32SubstitutionTable(), 32); } @@ -146,6 +153,14 @@ namespace client return num; } + void AddressBookFilesystemStorage::SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) + { + std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt"; + std::ofstream f (fname, std::ofstream::out | std::ofstream::trunc); + if (f) + f << etag << lastModified; + } + //--------------------------------------------------------------------- AddressBook::AddressBook (): m_Storage(new AddressBookFilesystemStorage), m_IsLoaded (false), m_IsDownloading (false), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr) @@ -337,7 +352,7 @@ namespace client LogPrint (eLogError, "Addressbook: subscriptions already loaded"); } - void AddressBook::DownloadComplete (bool success) + void AddressBook::DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) { m_IsDownloading = false; int nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT; @@ -561,7 +576,7 @@ namespace client if (!success) LogPrint (eLogError, "Addressbook: download hosts.txt from ", m_Link, " failed"); - m_Book.DownloadComplete (success); + m_Book.DownloadComplete (success, ident, m_Etag, m_LastModified); } bool AddressBookSubscription::ProcessResponse (std::stringstream& s, bool isGzip) diff --git a/AddressBook.h b/AddressBook.h index 46df12d0..27034aad 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -38,6 +38,8 @@ namespace client virtual bool Init () = 0; virtual int Load (std::map& addresses) = 0; virtual int Save (const std::map& addresses) = 0; + + virtual void SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) = 0; }; class AddressBookSubscription; @@ -56,7 +58,7 @@ namespace client void InsertAddress (std::shared_ptr address); void LoadHostsFromStream (std::istream& f); - void DownloadComplete (bool success); + 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); } std::string ToAddress(std::shared_ptr ident) { return ToAddress(ident->GetIdentHash ()); } diff --git a/FS.cpp b/FS.cpp index 19963195..380ab2e5 100644 --- a/FS.cpp +++ b/FS.cpp @@ -102,6 +102,13 @@ namespace fs { return boost::filesystem::remove(path); } + bool CreateDirectory (const std::string& path) + { + if (boost::filesystem::exists(path) && + boost::filesystem::is_directory (boost::filesystem::status (path))) return true; + return boost::filesystem::create_directory(path); + } + void HashedStorage::SetPlace(const std::string &path) { root = path + i2p::fs::dirSep + name; } diff --git a/FS.h b/FS.h index 833258b9..d7f246dc 100644 --- a/FS.h +++ b/FS.h @@ -108,6 +108,8 @@ namespace fs { * @return true if file exists, false otherwise */ bool Exists(const std::string & path); + + bool CreateDirectory (const std::string& path); template void _ExpandPath(std::stringstream & path, T c) { From 59f99ea9bb0af9429f2922b13e2d9f3b0fc83eb5 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Mon, 14 Mar 2016 14:02:32 -0500 Subject: [PATCH 1090/6300] Ask to minimize on Win32app close This closes #413 --- Config.cpp | 1 + Win32/Win32App.cpp | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/Config.cpp b/Config.cpp index 15c8ad67..caec7e20 100644 --- a/Config.cpp +++ b/Config.cpp @@ -126,6 +126,7 @@ namespace config { #ifdef _WIN32 ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") ("insomnia", value()->zero_tokens()->default_value(false), "Prevent system from sleeping") + ("close", value()->default_value("ask"), "On close action") // minimize, exit, ask TODO: add custom validator or something #endif ; diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index cd9a8f34..269cc1ce 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -119,6 +119,29 @@ namespace win32 ShowWindow(hWnd, SW_HIDE); return 0; } + case SC_CLOSE: + { + std::string close; i2p::config::GetOption("close", close); + if (0 == close.compare("ask")) + switch(::MessageBox(hWnd, "Would you like to minimize instead of exiting?" + " You can add 'close' configuration option. Valid values are: ask, minimize, exit.", + "Minimize instead of exiting?", MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON1)) + { + case IDYES: close = "minimize"; break; + case IDNO: close = "exit"; break; + default: return 0; + } + if (0 == close.compare("minimize")) + { + ShowWindow(hWnd, SW_HIDE); + return 0; + } + if (0 != close.compare("exit")) + { + ::MessageBox(hWnd, close.c_str(), "Unknown close action in config", MB_OK | MB_ICONWARNING); + return 0; + } + } } } case WM_TRAYICON: From 60befdb36e70ff5d831c2bfe7d4d19d932f250b3 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Mon, 14 Mar 2016 15:12:56 -0500 Subject: [PATCH 1091/6300] VS2013 snprintf compatibility --- Win32/Win32App.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 269cc1ce..985d9eec 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -4,6 +4,11 @@ #include "../Config.h" #include "resource.h" #include "Win32App.h" +#include + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define snprintf _snprintf +#endif #define ID_ABOUT 2000 #define ID_EXIT 2001 @@ -98,7 +103,7 @@ namespace win32 char buf[30]; std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); - std::snprintf(buf, 30, "http://%s:%d", httpAddr.c_str(), httpPort); + snprintf(buf, 30, "http://%s:%d", httpAddr.c_str(), httpPort); ShellExecute(NULL, "open", buf, NULL, NULL, SW_SHOWNORMAL); return 0; } From 74efdb95e8c1dcf2b53c6c5cdea4c818594fee70 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 14 Mar 2016 22:00:05 -0400 Subject: [PATCH 1092/6300] persist etag --- AddressBook.cpp | 8 ++++++-- AddressBook.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 4248ae32..86d90a4c 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -158,7 +158,10 @@ namespace client std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt"; std::ofstream f (fname, std::ofstream::out | std::ofstream::trunc); if (f) - f << etag << lastModified; + { + f << etag << std::endl; + f<< lastModified << std::endl; + } } //--------------------------------------------------------------------- @@ -363,6 +366,7 @@ namespace client nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT; else m_IsLoaded = true; + if (m_Storage) m_Storage->SaveEtag (subscription, etag, lastModified); } if (m_SubscriptionsUpdateTimer) { @@ -483,7 +487,7 @@ namespace client << "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"; + 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 diff --git a/AddressBook.h b/AddressBook.h index 27034aad..a22a8b3b 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -99,6 +99,7 @@ namespace client AddressBook& m_Book; std::string m_Link, m_Etag, m_LastModified; + // m_Etag must be surrounded by "" }; } } From 84ccca0e985de516a14972ba6dde9479d95c1d6c Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 15 Mar 2016 14:37:07 -0400 Subject: [PATCH 1093/6300] read persistent ETags --- AddressBook.cpp | 29 ++++++++++++++++++++++++++++- AddressBook.h | 4 ++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 86d90a4c..2ffebe01 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -37,6 +37,8 @@ namespace client int Save (const std::map& addresses); void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified); + bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified); + }; bool AddressBookFilesystemStorage::Init() @@ -164,6 +166,17 @@ namespace client } } + bool AddressBookFilesystemStorage::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) + { + std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt"; + std::ifstream f (fname, std::ofstream::in); + if (!f || f.eof ()) return false; + std::getline (f, etag); + if (f.eof ()) return false; + std::getline (f, lastModified); + return true; + } + //--------------------------------------------------------------------- AddressBook::AddressBook (): m_Storage(new AddressBookFilesystemStorage), m_IsLoaded (false), m_IsDownloading (false), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr) @@ -355,6 +368,14 @@ namespace client LogPrint (eLogError, "Addressbook: subscriptions already loaded"); } + bool AddressBook::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) + { + if (m_Storage) + return m_Storage->GetEtag (subscription, etag, lastModified); + else + return false; + } + void AddressBook::DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) { m_IsDownloading = false; @@ -457,6 +478,12 @@ namespace client i2p::data::IdentHash ident; if (m_Book.GetIdentHash (u.host_, ident)) { + 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); @@ -548,7 +575,7 @@ namespace client !header.compare (colon + 1, std::string::npos, "x-i2p-gzip"); } } - LogPrint (eLogInfo, "Addressbook: ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); + LogPrint (eLogInfo, "Addressbook: received ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); if (!response.eof ()) { success = true; diff --git a/AddressBook.h b/AddressBook.h index a22a8b3b..2a1e0737 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -40,6 +40,7 @@ namespace client virtual int Save (const std::map& addresses) = 0; virtual void SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) = 0; + virtual bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) = 0; }; class AddressBookSubscription; @@ -62,6 +63,9 @@ namespace client //This method returns the ".b32.i2p" address std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); } std::string ToAddress(std::shared_ptr ident) { return ToAddress(ident->GetIdentHash ()); } + + bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified); + private: void StartSubscriptions (); From 7c8036807a0267f317b2bf0b0b8608ff1ef06ede Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Tue, 15 Mar 2016 19:04:57 -0500 Subject: [PATCH 1094/6300] Cross compiling notes for Win32 target --- docs/build_notes_cross.md | 75 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 docs/build_notes_cross.md diff --git a/docs/build_notes_cross.md b/docs/build_notes_cross.md new file mode 100644 index 00000000..d819ba34 --- /dev/null +++ b/docs/build_notes_cross.md @@ -0,0 +1,75 @@ +Cross compilation notes +======================= + +Static 64 bit windows binary on Ubuntu 15.10 (Wily Werewolf) +--------------------------------------------------------------------- + +Install cross compiler and friends +```sh +sudo apt-get install g++-mingw-w64-x86-64 +``` +Default is to use Win32 threading model which lacks std::mutex and such. So we change defaults +```sh +sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix +``` +From now on we assume we have everything in `~/dev/`. Get Boost sources unpacked into `~/dev/boost_1_60_0/` +and change directory to it. +Now add out cross compiler configuration. Warning: the following will wipe out whatever you had in there. +```sh +echo "using gcc : mingw : x86_64-w64-mingw32-g++ ;" > ~/user-config.jam +``` +Proceed with building Boost normal way, but let's define dedicated staging directory +```sh +./bootstrap.sh +./b2 toolset=gcc-mingw target-os=windows variant=release link=static runtime-link=static address-model=64 \ + --build-type=minimal --with-filesystem --with-program_options --with-regex --with-date_time \ + --stagedir=stage-mingw-64 +cd .. +``` +Now we get & build OpenSSL +```sh +git clone https://github.com/openssl/openssl +cd openssl +git checkout OpenSSL_1_0_2g +./Configure mingw64 no-rc2 no-rc4 no-rc5 no-idea no-bf no-cast no-whirlpool no-md2 no-md4 no-ripemd no-mdc2 \ + no-camellia no-seed no-comp no-krb5 no-gmp no-rfc3779 no-ec2m no-ssl2 no-jpake no-srp no-sctp no-srtp \ + --prefix=~/dev/stage --cross-compile-prefix=x86_64-w64-mingw32- +make depend +make +make install +cd .. +``` +and Zlib +```sh +git clone https://github.com/madler/zlib +cd zlib +git checkout v1.2.8 +CC=x86_64-w64-mingw32-gcc CFLAGS=-O3 ./configure --static --64 --prefix=~/dev/stage +make +make install +cd .. +``` +Now we prepare cross toolchain hint file for CMake, let's name it `~/dev/toolchain-mingw.cmake` +```cmake +SET(CMAKE_SYSTEM_NAME Windows) +SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) +SET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) +SET(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) +SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +``` +Download miniupnpc, unpack, and symlink it into `~/dev/miniupnpc/`. +Finally, we can build i2pd with all that goodness +```sh +git clone https://github.com/PurpleI2P/i2pd +mkdir i2pd-mingw-64-build +cd i2pd-mingw-64-build +BOOST_ROOT=~/dev/boost_1_60_0 cmake -G 'Unix Makefiles' ~/dev/i2pd/build -DBUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=~/dev/toolchain-mingw.cmake -DWITH_AESNI=ON -DWITH_UPNP=ON -DWITH_STATIC=ON \ + -DWITH_HARDENING=ON -DCMAKE_INSTALL_PREFIX:PATH=~/dev/i2pd-mingw-64-static \ + -DZLIB_ROOT=~/dev/stage -DBOOST_LIBRARYDIR:PATH=~/dev/boost_1_60_0/stage-mingw-64/lib \ + -DOPENSSL_ROOT_DIR:PATH=~/dev/stage +make +x86_64-w64-mingw32-strip i2pd.exe +``` +By now, you should have a release build with stripped symbols. From 803f11bebb904008628fdf51d2a2a94c2850ab8e Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 16 Mar 2016 15:40:29 -0400 Subject: [PATCH 1095/6300] local addresses --- AddressBook.cpp | 55 +++++++++++++++++++++++++++++++++++-------------- AddressBook.h | 3 ++- FS.h | 15 ++++++++++++-- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 2ffebe01..2a6fbaf0 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -24,7 +24,7 @@ namespace client { private: i2p::fs::HashedStorage storage; - std::string etagsPath, indexPath; + std::string etagsPath, indexPath, localPath; public: AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") {}; @@ -34,22 +34,29 @@ namespace client bool Init (); int Load (std::map& addresses); + int LoadLocal (std::map& addresses); int Save (const std::map& addresses); void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified); bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified); + private: + + int LoadFromFile (const std::string& filename, std::map& addresses); // returns -1 if can't open file, otherwise number of records + }; bool AddressBookFilesystemStorage::Init() { storage.SetPlace(i2p::fs::GetDataDir()); // init ETags - etagsPath = storage.GetRoot() + i2p::fs::dirSep + "etags"; + etagsPath = i2p::fs::StorageRootPath (storage, "etags"); if (!i2p::fs::Exists (etagsPath)) i2p::fs::CreateDirectory (etagsPath); + // init address files + indexPath = i2p::fs::StorageRootPath (storage, "addresses.csv"); + localPath = i2p::fs::StorageRootPath (storage, "local.csv"); // init storage - indexPath = storage.GetRoot() + i2p::fs::dirSep + "addresses.csv"; return storage.Init(i2p::data::GetBase32SubstitutionTable(), 32); } @@ -96,24 +103,18 @@ namespace client storage.Remove( ident.ToBase32() ); } - int AddressBookFilesystemStorage::Load (std::map& addresses) + int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, std::map& addresses) { int num = 0; - std::string s; - std::ifstream f (indexPath, std::ifstream::in); // in text mode - - if (f.is_open ()) { - LogPrint(eLogInfo, "Addressbook: using index file ", indexPath); - } else { - LogPrint(eLogWarning, "Addressbook: Can't open ", indexPath); - return 0; - } + std::ifstream f (filename, std::ifstream::in); // in text mode + if (!f) return -1; addresses.clear (); - while (!f.eof ()) { + while (!f.eof ()) + { + std::string s; getline(f, s); - if (!s.length()) - continue; // skip empty line + if (!s.length()) continue; // skip empty line std::size_t pos = s.find(','); if (pos != std::string::npos) @@ -127,8 +128,28 @@ namespace client num++; } } + return num; + } + int AddressBookFilesystemStorage::Load (std::map& addresses) + { + int num = LoadFromFile (indexPath, addresses); + if (num < 0) + { + LogPrint(eLogWarning, "Addressbook: Can't open ", indexPath); + return 0; + } + LogPrint(eLogInfo, "Addressbook: using index file ", indexPath); LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded from storage"); + + return num; + } + + int AddressBookFilesystemStorage::LoadLocal (std::map& addresses) + { + int num = LoadFromFile (localPath, addresses); + if (num < 0) return 0; + LogPrint (eLogInfo, "Addressbook: ", num, " local addresses loaded"); return num; } @@ -305,6 +326,8 @@ namespace client LoadHostsFromStream (f); m_IsLoaded = true; } + // load local + m_Storage->LoadLocal (m_Addresses); } void AddressBook::LoadHostsFromStream (std::istream& f) diff --git a/AddressBook.h b/AddressBook.h index 2a1e0737..98422fe1 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -37,6 +37,7 @@ namespace client virtual bool Init () = 0; virtual int Load (std::map& addresses) = 0; + virtual int LoadLocal (std::map& addresses) = 0; virtual int Save (const std::map& addresses) = 0; virtual void SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) = 0; @@ -79,7 +80,7 @@ namespace client private: std::mutex m_AddressBookMutex; - std::map m_Addresses; + std::map m_Addresses, m_LocalAddresses; AddressBookStorage * m_Storage; volatile bool m_IsLoaded, m_IsDownloading; std::vector m_Subscriptions; diff --git a/FS.h b/FS.h index d7f246dc..0437ccf9 100644 --- a/FS.h +++ b/FS.h @@ -48,8 +48,8 @@ namespace fs { /** create subdirs in storage */ bool Init(const char* chars, size_t cnt); - const std::string & GetRoot() const { return this->root; } - const std::string & GetName() const { return this->name; } + const std::string & GetRoot() const { return root; } + const std::string & GetName() const { return name; } /** set directory where to place storage directory */ void SetPlace(const std::string & path); /** path to file with given ident */ @@ -138,6 +138,17 @@ namespace fs { return s.str(); } + + template + std::string StorageRootPath (const Storage& storage, Filename... filenames) + { + std::stringstream s(""); + s << storage.GetRoot (); + _ExpandPath(s, filenames...); + + return s.str(); + } + } // fs } // i2p From 136b663cef9aae7759b1319652e1e88017ecf8fc Mon Sep 17 00:00:00 2001 From: xcps Date: Fri, 18 Mar 2016 10:00:10 -0400 Subject: [PATCH 1096/6300] strip connection http header --- HTTPProxy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 160c27f6..fc2e0c22 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -204,14 +204,14 @@ namespace proxy if (eol) { *eol = 0; eol++; - if (strncmp ((const char *)http_buff, "Referer", 7)) // strip out referer + 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; From 364136213b6e3febdce3c122287ebd37ac60e298 Mon Sep 17 00:00:00 2001 From: xcps Date: Fri, 18 Mar 2016 10:06:53 -0400 Subject: [PATCH 1097/6300] extra space --- HTTPProxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index fc2e0c22..6e835d21 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -211,7 +211,7 @@ namespace proxy 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; From 5896cebeaa12497053262a4f68380e0b18779275 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 18 Mar 2016 13:35:33 -0400 Subject: [PATCH 1098/6300] list 'enabled' options --- docs/configuration.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 79f95f10..931169fd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -30,21 +30,26 @@ Command line options * --httpproxy.address= - The address to listen on (HTTP Proxy) * --httpproxy.port= - The port to listen on (HTTP Proxy) 4446 by default * --httpproxy.keys= - optional keys file for proxy local destination (both HTTP and SOCKS) +* --httpproxy.enabled= - If HTTP proxy is enabled. true by default * --socksproxy.address= - The address to listen on (SOCKS Proxy) * --socksproxy.port= - The port to listen on (SOCKS Proxy). 4447 by default * --socksproxy.keys= - optional keys file for proxy local destination (both HTTP and SOCKS) -* --socksproxy.outproxy= - Address of outproxy. requests outside i2p will go there +* --socksproxy.enabled= - If SOCKS proxy is enabled. true by default +* --socksproxy.outproxy= - Address of outproxy. requests outside i2p will go there * --socksproxy.outproxyport= - Outproxy remote port * --sam.address= - The address to listen on (SAM bridge) * --sam.port= - Port of SAM bridge. Usually 7656. SAM is off if not specified +* --sam.enabled= - If SAM is enabled. false by default * --bob.address= - The address to listen on (BOB command channel) * --bob.port= - Port of BOB command channel. Usually 2827. BOB is off if not specified +* --sam.enabled= - If BOB is enabled. false by default * --i2pcontrol.address= - The address to listen on (I2P control service) * --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 Config files ------------ From 3dbab68f170cb3b0bb2d489a8e0ef43b6bfea833 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 18 Mar 2016 22:53:03 -0400 Subject: [PATCH 1099/6300] don't send own RouterInfo twice --- NTCPSession.cpp | 4 +--- SSUSession.cpp | 1 - Transports.cpp | 12 ++++++++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 2584f584..b0ea2e74 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -93,9 +93,7 @@ namespace transport m_DHKeysPair = nullptr; - SendTimeSyncMessage (); - m_SendQueue.push_back (CreateDatabaseStoreMsg ()); // we tell immediately who we are - + SendTimeSyncMessage (); transports.PeerConnected (shared_from_this ()); } diff --git a/SSUSession.cpp b/SSUSession.cpp index 1fa29475..aa534c56 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -855,7 +855,6 @@ namespace transport m_DHKeysPair = nullptr; m_SignedData = nullptr; m_Data.Start (); - m_Data.Send (CreateDatabaseStoreMsg ()); transports.PeerConnected (shared_from_this ()); if (m_IsPeerTest) SendPeerTest (); diff --git a/Transports.cpp b/Transports.cpp index 5fcce0fc..9b327133 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -505,12 +505,24 @@ namespace transport auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { + bool sendDatabaseStore = true; + if (it->second.delayedMessages.size () > 0) + { + // check if first message is our DatabaseStore (publishing) + auto firstMsg = it->second.delayedMessages[0]; + if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore && + i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ()) + sendDatabaseStore = false; // we have it in the list already + } + if (sendDatabaseStore) + session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); it->second.sessions.push_back (session); session->SendI2NPMessages (it->second.delayedMessages); it->second.delayedMessages.clear (); } else // incoming connection { + session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore std::unique_lock l(m_PeersMutex); m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} })); } From 1036ce0fa592fa6dc661251adc46116a26887585 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 19 Mar 2016 08:07:09 -0400 Subject: [PATCH 1100/6300] create addressbook before etags --- AddressBook.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 2a6fbaf0..22008294 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -49,15 +49,19 @@ namespace client bool AddressBookFilesystemStorage::Init() { storage.SetPlace(i2p::fs::GetDataDir()); - // init ETags - etagsPath = i2p::fs::StorageRootPath (storage, "etags"); - if (!i2p::fs::Exists (etagsPath)) - i2p::fs::CreateDirectory (etagsPath); - // init address files - indexPath = i2p::fs::StorageRootPath (storage, "addresses.csv"); - localPath = i2p::fs::StorageRootPath (storage, "local.csv"); // init storage - return storage.Init(i2p::data::GetBase32SubstitutionTable(), 32); + if (storage.Init(i2p::data::GetBase32SubstitutionTable(), 32)) + { + // init ETags + etagsPath = i2p::fs::StorageRootPath (storage, "etags"); + if (!i2p::fs::Exists (etagsPath)) + i2p::fs::CreateDirectory (etagsPath); + // init address files + indexPath = i2p::fs::StorageRootPath (storage, "addresses.csv"); + localPath = i2p::fs::StorageRootPath (storage, "local.csv"); + return true; + } + return false; } std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const From 1c76d43e44af1898ea20aa95f8b646404192d256 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 19 Mar 2016 08:17:30 -0400 Subject: [PATCH 1101/6300] mention true/false values for bool params --- docs/configuration.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 931169fd..46e5092c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -17,9 +17,9 @@ Command line options * --port= - The port to listen on * --daemon - Router will go to background after start * --service - Router will use system folders like '/var/lib/i2pd' -* --ipv6 - Enable communication through ipv6 -* --notransit - Router will not accept transit tunnels at startup -* --floodfill - Router will be floodfill +* --ipv6 - Enable communication through ipv6. false by default +* --notransit - Router will not accept transit tunnels at startup. false by default +* --floodfill - Router will be floodfill. false by default * --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited * --family= - Name of a family, router belongs to * --svcctl= - Windows service management (--svcctl="install" or --svcctl="remove") @@ -63,15 +63,15 @@ For example: i2p.conf: # comment - log = yes - ipv6 = yes + log = true + ipv6 = true # settings for specific module [httpproxy] port = 4444 # ^^ this will be --httproxy.port= in cmdline # another one [sam] - enabled = yes + enabled = true tunnels.cfg (filename of this config is subject of change): From 83d9513c4a97fb82adbdc1aff202873353944c33 Mon Sep 17 00:00:00 2001 From: libre-net-society Date: Sun, 20 Mar 2016 01:39:35 +0300 Subject: [PATCH 1102/6300] Added example configuration file --- docs/i2pd.conf | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 docs/i2pd.conf diff --git a/docs/i2pd.conf b/docs/i2pd.conf new file mode 100644 index 00000000..32c524ca --- /dev/null +++ b/docs/i2pd.conf @@ -0,0 +1,111 @@ +## Configuration file for a typical i2pd user +## See https://i2pd.readthedocs.org/en/latest/configuration.html +## for more options you can use in this file. + +## Lines that begin with "## " try to explain what's going on. Lines +## that begin with just "#" are disabled commands: you can enable them +## by removing the "#" symbol. + +## Tunnels config file +## Default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg +#tunconf = /var/lib/i2pd/tunnels.cfg + +## Where to write pidfile (don't write by default) +#pidfile = /var/run/i2pd.pid + +## Logging configuration section +## By default logs go to stdout with level info +## +## Logs destination (stdout, file) +#log = file +## Path to logfile (default - autodetect) +#logfile = /var/log/i2pd.log +## Log messages above this level (debug, *info, warn, error) +#loglevel = info + +## Path to storage of i2pd data (RI, keys, peer profiles, ...) +## Default: ~/.i2pd or /var/lib/i2pd +#datadir = /var/lib/i2pd + +## Daemon mode. Router will go to background after start +#daemon +## Run as a service. Router will use system folders like ‘/var/lib/i2pd’ +#service + +## External IP address to listen for connections +## By default i2pd sets IP automatically +#host = 1.2.3.4 +## Port to listen for connections +## By default i2pd picks random port. You MUST pick a random number too, +## don't just uncomment this +#port = 4321 +##Enable communication through ipv6 +ipv6 +## Bandwidth configuration +## L limit bandwidth to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited +## Default is P for floodfill, L for regular node +#bandwidth = L + +## Router will not accept transit tunnels at startup +#notransit + +## Router will be floodfill +#floodfill + +## Section for Web Console +## By default it's available at 127.0.0.1:7070 even if it's not configured +[http] +## The address to listen on +address = 127.0.0.1 +## The port to listen on +port = 7070 + +## Section for HTTP proxy +## By default it's available at 127.0.0.1:4444 even if it's not configured +[httpproxy] +## The address to listen on +address = 127.0.0.1 +## The port to listen on +port = 4444 +## Optional keys file for proxy local destination +#keys = http-proxy-keys.dat +## Uncomment if you want to disable HTTP proxy +#enabled=false + +## Section for Socks proxy +## By default it's available at 127.0.0.1:4447 even if it's not configured +#[socksproxy] +## The address to listen on +#address = 127.0.0.1 +## The port to listen on +#port = 4447 +## Optional keys file for proxy local destination +#keys = socks-proxy-keys.dat +## Uncomment if you want to disable Socks proxy +#enabled=false +## Socks outproxy. Example below is set to use Tor for all connections except i2p +## Address of outproxy +#outproxy = 127.0.0.1 +## Outproxy remote port +#outproxyport = 9050 + +## Section for SAM bridge +#[sam] +## The address to listen on +#address = 127.0.0.1 +## Port of SAM bridge +#port = 7656 + +## Section for BOB command channel +#[bob] +## The address to listen on +#address = 127.0.0.1 +## Port of BOB command channel. Usually 2827. BOB is off if not specified +#port = 2827 + +## Section for I2PControl protocol +#[i2pcontrol] +## The address to listen on +#address = 127.0.0.1 +## Port of I2P control service +#port = 7650 From 91f55a637b2a5b95b7e639b57038726f8e2e8eec Mon Sep 17 00:00:00 2001 From: 0niichan Date: Sun, 20 Mar 2016 20:02:04 +0700 Subject: [PATCH 1103/6300] New .ico by MilkHater --- Win32/ictoopie.ico | Bin 180626 -> 65592 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Win32/ictoopie.ico b/Win32/ictoopie.ico index 077479a2ab8c7304f6dca1ac1770176394134cc6..d4b422081269b0a939faf2e49dc3632770728b5f 100644 GIT binary patch literal 65592 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ℜ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 Pl$ZGBw7hbPAJP8@ziKnK literal 180626 zcmZtNWmH?iwgBMZ?p`dodvOa^in~+Xic4`PI23n>;uQDdR;*Z|xVyXS3-{iiZ@$HX zLl)sM+2@eidk+8r1wa724=4Z-5X}h%aD#ju5b)n)dPFDyG#3;A1p42xH~_#X2n}Fo z|L<`r4FI6(3k?7Q|96ZD06Z2z0|*KKdmN4g02I_i127@qe}8@}Sjf+JKm#I_6{XRT zKOjSX6`IUvN!9m9-#;+nA)in;zd@ch03ahNrtYzLoNZ}@t1%alD_GxlKpiJ8j?{6t z3U?qSsa(kTSp^d_x!4E_D;{075F8Xh`4_g({h*_HQU($BZ~ekb`qRt&O2CPC2y6l0 zM6QgRh2`mTddJ?fixL#f|2x2znDGl}?Ck9KYxFP4ud@Dmf-4871awcMuXSB-My=vi zaZK9?K0{$XrIvSTryMJ$UIVz^P!NHU43GG}QJqo~taTB#luKKNhQEghiKuug1ZPMO zV+zr=YfZj9)Tbzomh8wKf}A4N^!qD3|Fv5i47$122xyb~lw>h8$c-fE)^)@~s94+; z1ilY3+C(aTY3CTTAugebbD=&Bhqcqr72me7oFDMqrSU%+2Ie+xF6_wY3gg7lqbNfO zO+=8e&Z6!Ho+_RtTM!90n*V&JzWl^W!sn=;OWBFF7Vfb5BI&7Okd%r|O8P~XA{cY? zqWSBdr(8SDsxx&&Z$S+`iO#&|%TfIJ(Hb8ePdJ)MUu$GR{XVlmY)pR)=~E5%_|?Z) zQ{J|P>>mwUYSqOC3KA3MY^fdkx6bA6>y6zufpeTsWrY73W|-CtjY+!5Glu^lUN-wN zIV4esFNnMsMpt+7G!m^<6-;9@NNB@`ovJ9cT6&X-Sq46Hs76QM;S5MQsp!j=tev^9cVB2dZN0RNemPyWDqB@`C~F?qP+!%8Ag@>U zG=S*z_b$r+tNc|P)-_8&k(3}~lxh}2^QK8smy#tu{pBMX>ih5uQ|oSR_QT6wqGU^y zWh{GG{4Le%#?B*B(sN);vlrjbRCd}p72-c^()sf^H@N46hl5#2GJ;C`zp z=5KC($<~v`{_tVZaANuGXz62ANQJ+j!&hLA$L7{clC3Nc&C*!x7=HbLt&VH=^v1#Y zx9g+L#z~v}xnAg{Y$chmr$P=ET`chL<4vIVHuMB-31!2x?<#apqr>;^;q+>S0DBak zr)DE0!&<4aWYOgYokRK$mW%z9pk-Z5;UE0MiASC>PUr77`iZJ#dFslHRuz)uQ_|P1 z_gV9Fw-LUkmYIX^c-JJ!F5?F0rj-%@GtIBK(}eGoElas(h-6(7|Cw>?prrjteHW_v z(T*2FbZ+`Vj z_ko0CRf%UJ#PnpOf$=*y`5rE?CH#bX20{ax74alb{hA^SxCCsB%XP1`{*rU&y?Pe- zQ2g$%5Cg}%H31fRl<8i9#f1sK+pgWX!5t@0p*4ZMKPq2Oap{3V!C~h^$8Gn-h?EsMr-YXtToT zqGbfrmTd`J#%}N|i{fIP;fRGuaagNZZOx@O_JNe6cBI~k;Dq64if{Bd; z)EdGAwD$?B!c;kZr=R10bjkYwUD`a82~aQQ1YGpP z>?dE#0%Eds?Ezu2!pNYMX&V@$EoO^3*T|Np(>IOo=yc^gI^hsxR8ztLMBG4(H6;rw zHS=Ccm|gpaefS8}(?2X6Q<7 zov4uvmN6xv*lWFG?#Z(%=uBaa7|>aL-w1y$aOO$|VqVt+D7t*;=K1?d12PHhe+ES0 z;Lt)-^T?tO5U3i|&!wQP|J|4uzr}3Pq57-D6R0;pb><9v8mrV<Fm|I7YTv?rEod^K>O`S=1hRY zedtn^X%+ZD&P9z?Y#r*2krMM#0Y#K%GrzrZ62+RE9v4q{P#rFEhS?)wLAgQp+}!2k zeX#c|HgONvVg>EgdZl)GxUG>cosgs^;DKcrgpC5Fy@h>Gyz>cgJP)PpxF{SMwFzj* zhXJ%6Z2yX{N&~HKkqA=o&$!8-Au~w*q~n8@f%*B}45vka@*+VPmQ|PaOT!dYcf>`NMM89`Mr~EX#0T)DiWu0`A8I^U8-d$788;caXwNPW z$M{G_NGi~hn!suy5Xruv)gZr70123M+v*c#0Pl{tiCER9NNqrdSPI%4vzA$3F!D3d z0EpXn;nx2QR6xvG5lvQ0b5*Z>4akV6Hig=D#0m1WEZvYhVp*0l{feN)k3;8 zI^3<0ze$)I^!P;QbH+8;`(;bWjB=>@Mok^|P%U@4JNB7g{d0xwc`ysSy;Oi%f(*Qh z#0)0)HXb}zTF;RoHwzC$&c}hyzYxy8#C@Db=MKHf0Mu6vJ_PN!A3Ik30@Rw>1&S&q z^5s99r+mkB==sE3Rmu$_RG zE;-uW!I#zd{|*&%h|HYPZ~q`%KiQ8vr_`(PCV<~@J{c1dKJ&kY=J&6Q%4c+^s>(84Msk=$ z6Hf1YcW%pd9xv$`q|WW!d=g!cpv$4GRt{mDp_{@??QWK30ys>{Sa-WK?{a#4^5pKp z8PTH8P_jc}vE`0T>&WW~OHNd%Rc5AWh%H0%XyR7F<;ashICI3^<`Hj!sq500fkgI; z(laAz)qbQVEHgFlw z&@?9C*!-ofoDFb96C6lJ?d}A<-FAtv2|%IUn!BTGVL{*@3PRrGuZ>;KQ`h>Wf~8t- z)ocdLV)S<@F(rM*ZgOv;0{A_<9Ut&d4EAZAu)~UUe68SfF*kVfjQm(kpqpe z9|#}P&-f#GYGhv~`R3`-_r>q(qAvHC?&;wpPEN0yhe#lN%iQ_gNTVvuhre-5>l!0W zY-GwEUt77XB|KaVqs~~cl>IFZrgjmBG^gq-7csA6ywHWjSQ7?XP%n9gt8DisgAi8xx5X-A>VHlawIy4I?2K9^U%-be1&f{)psMEP)P zC+Yj(x`rQ4N8_D?X96!#jd{0a|I~v1fLS}o40)c!?zTfka@>J?%?p>MMWCvGf?Ft$ zY>V)eVzfZCz+9vjX2whSS}UNdXKoZwK-OP<$dzX)_AqMF!LP#bpYx=V%TBK~QksU* zN#KmOcHOs*VGeSWKX+r7ABU2i`9s9onRP9zLk%j%-iR5EP)H%#cyztYB%%Sb8d}-#{`SxJ8~IAf4O(T2rd$QL)HHUZBnyYx zk7XA3(UsF+W+F%9W}G!)PYftnOrUA^rd^pBXZyX{*uw6>xxsgyOko}njdcD&aY9~B zB5}W8+|3T$cu;hYMcVZnoR#8q=a4iaGW{77R~b^1&!SBoy{)3~ZO01K55I>Ef+O+y zmz(_DMy5RTe~dQ)f~Pn{M<-ZHn{&O_9_LN5l@v;S zPuLUBnH6!AMN?d<(iMSg(#09<(9S5NU}7Jr`_w!RUYMx-T(<=DT3zAO!Qkr_i3})ZEJsO>z9Vwv9Bk#l zg#wpEaTF2>sVsB7`hYLdMtH-k2)bJC2L{-5*Cu0+25+|1K3dw}#C@+K!XiqETC>|c zqw)Ss>Y;dSBCMXP%psl*n;Z5`@unQMF(}fN8)k%CNAf4EZ~UJLjcx*^t+@--O+~Bi zC`q_lgJ3mS5;${B9gL=HL4z@Su(4fNrCm0{(STy2KDhr@#86xqs`?D!>ZN z^I`StN9Ii#EVSlbuTMld(=`kYDdLH4LW8#fCuQO~@;@PLK2vSM)#oD;3gJcPkFQ4< ziI_paF0aG$BCAg)-k1q{zVLN`SH;eh^K?I<_?5|#LLml62M2IM;k3Z|PBguZaDI4X zwR()78^p}>bkO`AxGHnj>lGLA`P)#TDhU1!cUc8aOj}vZQ!HIxD!j||W^yg?;E0z^ zNe5v|a6;6M4S5R1qhVI==VuAk6k#%0_lUJO~=8(jgp#MGt+DaeC-&Um}v( zKncLX_e6HqNA*)7f}uLS`*mDPxVT*T5M|Ecag1M)#F2NGx{(F#bI!i%2kxRySEr`` zH=ubLL`E4if`5MXDonmWb8W-dp*F%1uI=|y{z~H*fl>e)-dkYxX`A3P(~1(T*UG|H|a zw#zWveqx9A602Sb55(A6UdcE_Y%wDG`$V(ez97-(?=`xz{%M*ID_92EdDGE*x}LKz zQ9A0HBCszo>1&wJ@czUOd)@`)acC{j1)ZG+%)$76|NLrfgVT55w=WM7%y;qLJ|AG< z=H&>%Id?5bt&6!14EB@0Q1T9;Il~l@8uE8K`}|wZvQqp=mywzIC8$e2qu0*poDi}w z5&Lz1y0tRchH*c8QMsizxL2qu&rpn;IWwBRU+9|VA~zTPE2m+^40b&vRtWAPV~?@w zI;<@JjMfV+h`_g;rS=*@_|sb)4A5r8Qxb~oEuQ2Fo+xVJVRgvQc9(4b;Tj`6;8(%6 zk(_9nU+o%pwU}~|?*n_0sB+-fOWYw+XPZ!ZXfnvO1RPTf4l9b#nUgO_-=@#VHDIzx zM$P8;=mj;yI^4U+nE#R2I#u+AJ(HpN&3UiWE%? zZ@E(yg#ws8M%3ag9b+CL6I=I|{a^sjTc}PpsK18kND>LR0TR^15a)e3YY~HA_ln)- z88?RU&CdbbDDUWZvZna4zYIOqx5JB_jQrDLcnS2fjV}S&Cued>Vx40f^rD1Q#k9nI zFK8-du~zbAX#M2;efxs~Y0Bce^c5NSH=gL8sr6b+2}n8)c1FmWj9Y2Cj@bk&iqoo%_5}-3A zjvx25&t zo$=U|{3*k2SLCi0;~?juZR;MAkT?@_+_7!zcR#;rKFhv|3Dsnq<+EHs^y2U(!~%9% zrc&IaEu{OL*8c7 zSO}#nH&`5bNBEyNZE$ad{9mjgZ+}N%zI|PN&e}2ccnS#>GcX68&_6|L0NY0K`i&qj zu|H&Gw~jqL59%uF`psQx!Nk7&7vp!xb;|)a=H)lkrQ+zGgHddxG?_K=^e1=Afk4rLY134BB*csEDb$@qUUWXY#m; z4qEG&RU+g!r^aPScPPK)MqN37oXC*_J}JIoS<_ot-Z=0FQ{3ljEC;P1kmB{DPoA4yID$%iFi(Dw5}yGFd>lL9aMrIPplVq}X39Z=4R^ z{_Q9m3gLD2wo*cG^cIiPI&zkc@yoO?bu^M*OKtNGV$TnSd?=^ydaB5_rfw{+!3uj*82S3ULlTMrnnA`S%VRZa{+4u3cw4FFdc%}X?$Y~IwyYH zQLFH3qDvSCe#(ys*a>o6LvvKrq1(R#)zv_go^c(_Lz$Pz(2}fJuYAZ6 zZIQ*%$+eubmZkd)UXeM%yWYr38w|HMHHPoPbL`p7Ad(i?0>vy$=>hOvJzSZVJQKD+ z#t7MhgbC%NFAytaTV{&sYP+cEv2309#cu+af~~UFdA7>Mc|F7(nK#YyL7K!B#oGAb z)z4&m5^-nF7{ox%dEK4QIOhw9fDJmub&P`e7BRx`ssd8DZl}nMOET)5T0iQuz>_aa zV(mRxhb>S*usM6cJdg+AVU-fW*Zrh*Ry+e;EjuD&j{bQYwmis3adnSxXu1w*PV7@+ z5d4G>5@p*oJ*z+hfxq%0(~nX>Q^X?$5n}xGY12QOzTX{#K_@H5Ta#O~OB{TMzL&wM zL8TdY-#YlIJ!$AJNcSnwQ49e4xK3aCS~Em|CmC>n(yBG+W0P=SY!BWG#XRWX0XQD{ zFGv%<>bEhEP#;ey}#hG*O!2hevb&-#0cj5c5 z1yrwjGnjp40A0()&loMuocW>G;BbNh(nYfQ)SH>MMX7Ma*69f+ELNWOY`G=WJBtBa zAO)<7@&2{4U#n0x6?B#Nu`a&z+;)J zUBTQe&1|!~J;|S%MW(x8m$a8;?Ayk75Vonv7Xpn@aTq6aZUJvxOTmJ!*GQhw0c6*4 zA8Hb_tFFSFiP|YUMls=&k5Z0*b^;vS{1045b*w}>eUwJ6&q<9VRPlpW)%-JWfM=%C z{?B{V&T)8fNp2(9vOUk$O)HJ3;MKKQHPu@&=e5GhBxomTeLUpYFM1%_Vd`{PlQM0_ z6oW;o|0E`U_>Fg@-tJRfV?BnKTz(>ahIw$u;Djd%ooBm^dRhO&zRRWVmbhZpJg}A4 zgKF_Hb})2|8^>SBbRW#w@tM?T?!IFTDb3UP+_u_z^42R^A!k4=x9mtVs9oCn*K+={ zxdDO-jK6XTlZraP#&ytMhcITl?eAbMxhCvsX7VGyEvdgzvl|NKtpPF33-I7S%YTDaM2aXV6P2}!|!JCHl8Ist5KW!%59p;%%y1g&EN^r>Ln zE53s=whfN+3{%j;x7W1Vx@k%!o$5@WiqcfEfnMKwzn;MVVXUt$bS3*!*2=h?gQ#_9 zXvj1QmQTQ=hxX|qFmtELP|YD@tliEQ>Dk9K6MhuCQPI?;x)0FsUgRqHK{?VB!(MTBHAR-cANrk88VKma;F5etyo39y2U&R< zzXxyp!{=#i3S`;26RKVTJPABu{p=P)j|5?ANNG^V9>cNyFiVPtW_6B_f|M14Hya4I zVe*l0RB(|j(M0l)z89kF7n2_4_s$iOj_?r{eG@#q;Se%x+^$aX0~${9I-_mt#)za) z#s#4v9V(b3C~T;Y4I$rhPX2y9*xAN$6nNbP`DPu7h|9Pqi_xXt@-<^HLxn=0ul0vR z-^V8MTp2Q^L>7riEDU|(ahs6(I3-zPcwXYk9hRu|8?;)vq0#TRU(`TyQVZo{nGQ~- z0lEz5OJ43nkF$8l;snDb3CXfEE*s#!l{KM>_!`GEw9>Z=JWLPEuytp31igc3&cK-sUtBOdJ^s*lcQHQP*nG5_4R3Dh=+5-?U7zs#x#9 zpwp1$hj!=^d}xLI5zF7M>N=R|7m`oR5LA*Kna$z=L!jlCwIEq-5VPh79CVvG64cu7 zVEg1w+YC;q*|k-yycg(Oi(B4vNF_I9bZ%P-u4~iBWm}(LYaH%tJpNZtb~ObY?GOfe zVb<;y?ta*rYr!c` z=iKycV4a7r?y?D&y_u2B{Un~w?75pM8+6-lkbENa%OzRw{9_R_J|=OpTX5g13D%A6 zT91w0Z%rlS5RSGk9aC>K$_Z(LR0n|fgX9ccJhQ44J|>N1glOg3DY8S-dl8f{OEJzL z7xZ;$Z`7d1k)E6NMIE$m-Y-38|&>% z=Pnk5O*BNPNF-U1f8hrH$*yi?Vci*uf$P^VqTi?)zcX@J;@iI&=+9OA)U%Kl4)A5h zR7fg?-Xy)KQhxa1lBs8ekp!~?ip!WOgfE797x%+A=9Yg^0$d0$gO-pt(JzAYBp*Bx z>+kn`wZ(3K)iSol(>LVs$&c%j*n@4OBlr$GPbb~NJR>=9d84(@B@E2ai)$R|IDTCO zvyBXC!-4%7>*SXWgxX`zzs9&oQ#oE_{HBv;Z&=0tXb%Oy1>MvyhwVTPm-$tJ#N z7*N5JZabC5!$OKZmmhP%dyAU-Rhj?`jzAY6_gA@8yJahx-%W7bb6Vz-++&h5Xtj>jrdwh|Kb zZ+BA4w=;TAMOnC&E(hU7unKeU@%LKm_!(@Mv-*KF-}B|~d~lP~d8fJ8Lz0SP(?C)3 zBU?A+;|7!oc%QjF@Yu}%{+nv*ubDuR>wFA$NHlxa7-r{>D_aI=QB{93_}=}u2LdVy zUlyeZ&WgW*L>IjVzYzk?1`i?`0s7CE_K@8Bh+NP|{O?kxS&;#sGJv>h1K;ukgjixJ z)*UYr#YN#95afpv{*u$4d6Y12h~scL?s^h zclMHUVc4quJzqwT^8J)w@*c(F*c>|BA1;HG^m3a-?8K0?dXpX3u~S>FCtXf}ftIC}Lq$)7-7}&YiEp9eV3@Yd*r1dGB~uoE!&=ucl=0Mu zR@9!{ryJGLzNd-C#iIoxbQTdO3QzL>NLWLnz30YoW}&))p30iYXGnI;FfYU7y*ujg zzG7AHtn!YG7P=bF0HqzCW8<2v*rT+HQ80s4LbYkxC#I|K210UJ6P_d^x z_<8V>eAFMc+_tQ)Xat|WxpKM5c?p=SB$DeRg6Y9`?znEu?rZ%xbInkQE6b3iifAvr z9MeJyMUY~k?PbhBa(~dRgy0th^ABBJP#LWNU79pT0QHxI`#$)0l=0eU_*{f5j^b=J1#(-t*~eE^W#1R}iGZN_^pyIdQTb ztOL2%)8tZEo>T}vp00X894UpMxZ66T+tuW)Kd=*LE#j;jf!rrx6SssK*2we%GOxhgdcVOx-VS)_Jj}*}m z-r;rffZD?=^1g~3E#YdfxH^7wmk|q0rheqwO*Mx1)|CzAzoff&-Ajgv1o()S6NvQE<87ZJaPPORm4L%ebpUQO*vb2HYkemi1s2hdrM%1!7WXG$a-&WQ zEz4CV;Mo(!w~7s!O;J)yTKu)}MP1I3Uh3Pp58r1%C#B<*hyuqI{*%sV z&fmrtrR@;wQrid3Upwn_1^wRIOC_+vkNx}`-C#sUc&mdOzssFtz@1{=lmz*AEwkN$ zxk5O$+tSZ0Iy2cB_e!&VIQQNWcdgyOeA?G$CSKWxU$yL* z@X8IT;~iP0_l6N!xj*==#M|q9wzE3gu~9kSY_b*!Cz_c%HgRh-yw2N604>Rp8r#eM zTMdc_ckLp>4To0ai^jYF28r+XXC1iyFrlzF93LlrbeA{}NTmw@imlc58@i@n6`zcCzklT}{uf|U5|z#BmQg}`zmoR* z9Z2phWtFAF_%ZkZr&uY#WcD4x+4lyS%m(3Wspi-KMqBZ~{s|oPO&G&WzsnlDkR_~( z!A3OTuSlltttq&M5|;xIQ%Q+4V!11$Temt<#&Y!sBkN6pUD(f=^1eTb-I+M?nYDdF z%ZX>u4nt2{>wMC8MwtyCQE&cJVwjlrMu$%azrS~zEVt5VV9Yz2akCa=8cSW;acdQ} z+oTMM`Yjo3<` zM!nci`<0d|uZ;EzVe}d^yO~pzp(Nenw|Im66PKxZp$so+48_NGxKro!(NSF3{&cYO zwomV6i=15t=6Q?V1)T~%_-R=)H6|}AQYo;Or`ii#N{;N`OM|GCqE+6vYS8f!%&zbxdA5G}d&JmWxD#Mvk zQF!b|(gnz>*joRory#I88^DTKIV_^Obm+6q!JJ)*ob^>a8Lqq?`VsQ`Y@f&XQ4~6S zUW@C51o<1QtI1*iOhjO<`CtmE9=@~Np9K!fL4=7Nr7XDYj+KzkZSYH~;-5L5?n5L5 zznU;`X|?<=Yq)J1xpYqo%QpRHw_bAt9>=7$>&j)Z_WA13bQXIa?msiH6uMFqii#G!|cg5uREd4nqf-X%T$$%lnDI_u1Fu_ko*1A-xm>vUwNFlih-YvB`D+e#De=`=^T!12&LGlDA>s zUy+QB$PRd`j3go>Zz`{`m*Bgj)oSl9bWZuuVfMfB2ap!T5~M}s8vC2o-!U&W4!>JD zV_x!DRVhnBp&HFNomGcE`~$K20d{*a*N*qSH#;iTLqH?Stzc1q_J=k|7W}#A0esar zctJksPl5qgfFel{K7MTQMtpbTOnJOq=#n_DkSB9QTbbDa-&7OA0^BmoiuT?|!3c{F z?b$d}Vw3UcqEEBm$bymgcR4-7)*rzb!DH+l&$cXCP%|2=d-45f`O1kS1=7}E zov;twT7*Z#_(1lNL}c_%r*q;`ES<@`DycPL;pL?ZO9~QhumY^dEj3%>R@^oJx;0?} zZS=Q33?ULxP;XkG{BZ9rk@I=`M#H+lFT_jP@@rBJv4wP`eMab!wUw^k^%!n-J&zwrG`u5+* zWycM$B$+URSP$bY`EDt+WkVylwZ_$by(ZPk#9fnhKJE*dHJXFzk zctZiJKMaRVLl61S+S{+3n3{)6B2KWN-auGOdmp9;vDG<1LbAYbv0v(246_psnsDQlkC1^fUpuK#(fyi5h{`f+GbfunA2Y9MKD?$58) zXm{?^tdpWJ##o{vv#J4dzOxmhR*Il}SFt?>g}m3;o!IB!43m$JHe^uEG&Rt8rI?Ee zTO@lPQM)EpX-vBoZfC?o45)DHM1vy35J>Vx3J?-{k!%C0o1^$+wWf5!>95ON-}&XY zdA6$)u7u4oXQG%~{|Vt--*^d%i_sQ{$lfP%Dp0GSvNrx&8?MFWF%)hexQMc`WWZeQ zed0BwmmnGe#bCJjq-{xfy!<}mHRyucU4ECo@)WD|(7S_@%7!`?%1gXzdk>z-Hr5lW zf&H^>vw_jvB;CaTHX3{y#0_H$?=>MM0Nv*m(fyLZ#Wz$zWDWW4*0dL}pY1gD*jAOD zkI~4a`Fms@;f;4>ieCB$iL%Mph$5O43pa;i@`)cg(!#OgqY5m%jmxkG678WLXcS{} z)ZfEWp@TMTn?^%!A{~$HH?8gH!?QLK{$3c2yk636c1vWeV!G!Wq(@%KN31_|?uj5n zvFLNtkzu)SQn@#TnifEqoMTm{Y05KM6e9a+a=b_8OPZg&DEuP2U04&uM+q|X* zF{(!B)wAd&Kd+QY*+y%j;1Gbwl3kf!N4kx{i4y<(B9DWvRgipCAPQ&nW?AAlWiMt{ zvcLS7V83svH@6G5r2InRyMwh449zO*g&IBJc__ zaE=0mSH!6+EUrJ#4#mfu0A*`Lg(qO5>V=v*wa9xpN;MnL*&!{v8Z=K8BXr#Yejd0{ zOJ`1kaFLi;l;XM{32vl(RYV)ii`{c$ z%m~`>#iG))E{mBC{8$OWGUNB-mbh^ZP(5o<*`aIcR$@tlSFTlRkS~C%t8e9$KOhvo zrg#({_R7_FF+e9cjE^bGD-vu29Cu;<@<4hSJ^+{^|HO;eX(XrE5r}-9x91Qe*iV5j zmPYdj+aeJ83)8pCbJQwQ%d?CQo7*boPVEPiohadkt-DTuAqO7PaB{-Sd1ysq^)9Ao zzyl>M)gnbR_7Eu|p3WIPLjLS;PNYNSx$z?*uhqZ?oVL#N5N5MsPVLVWA%8g2*zSYa za{T;>g>>Gp)qyABv%B^+7##)8#DAcwWSgDEb*jq-Ar>Xk&q$B+J`=VDK+CYWx)JJ9 zl`<>$E3!$Yp;$inG;$WIY(9o!EWpJYZEnilM*j)ZQo~qPw(&WFx0Hj|H1V_d!` zoVk0-ijJG5#hur091r?)<#h`BJD1kn(U)gYbo78WIFl$D!Q~-5cZnr*N&|WMxo8|~ z80|Ujxt3Q^RA6Lefxnw$AOl=6ZW?r8J+vj<_{j0@mc9IFu&mD<2KmhUypAMt>GSunJP`myRp@WQ3>XA1@SKOY5&+~m7dno<# zXwV5kK9evQTh_`-uEfPIYIi0d-IcR?4J`*1k}QulL8%Ry7w^`ehTv)_ny2T9{7zJJ z`{vm0gq&of?Ho`eejfg_A_UK@j!Wb%o@mhQ+h?745)enr>+gRjEtc8>{`^0Ny%>&) zex?F_H1B_&TPm-ob+Y>!KSD=#s2(SqR0DF)k#Yl;X`6@bFi7w5SEK;_T8f@}t6(*P zs>*M+@KfrNSSeMse7SdIt$Vz=uOZEzZfX!X9x<0BJN%6G-e<${dsjladk-W+(jGDA zB!LwfyAmruL>fSwP%*O3?aZhoK;k1YSm1Q>#bhHcFl@wFKUg+7GFt^jyh&fjJ{+{J z;3aaLc;POSNtgw>p@W(aHCGNv8JI>Q>r5=}eu;g86qzUJV_4rAy{*1>ym;R*`L>AY zlb_-wufp?pJ~V1k5K0|Wl7Ea$kjr{*$5WXmk@4lB1&*x z&>ecJ%7*fP=^%-6VIHp~^kM15U;B3d@p*b!INy&`T~F6acmO%lcgj9^h6B9y-Y3KHcHT`V3r*mi zQV9B*l*XSgQDEO5d zxX zzl@cCGh%SG@%-&-S$5O?3F5`nI$jZk9(? ztlRgr!JXm5KA}N;zc!rhYVmh^+cZ+-C`nTbu(LH?0>41^X+ED{%!zu4>-zZ^<3Mql;gJl(NI zuE@c0lsF3{!r$zn$#-*gHVci%P+wf5?5A>#rJlM%RnYH=q^>4~K4MHtAN{zQ+NqDr zJ~q1}l2!709rrzoS5m+=J4@wGy<|yB!+k8{N7%35HW~*6NyNI&w&MaEnQQJSFt5`& zdC5ThuZ9~msl(>ZL_^ivT9*}clkq&Z^Xs=-&0W7<@P3}w{Te-Ft+9vf20HrGJA6eq zkt>98ak8~E*o-hxHIFA4jE+u9B0$J!SJ}|`SJNYZ3x(8hAJJ8J_Nz__&3)DRmyNc~ zGhT`RwCS~0@V@Fu3(d#{#yM+}{1pMb(Bl=N7qDb#BV1A+{y-w*hXo#?=)eX1n*H_x z^fYy`l}qY-vsFQE_(GWQzL!x!j{B{Pe^h5&yjKwEjJh2LvZLyW>^5X21_C`?o3NJM zgre}z=k$0^n4m-0e@Nvpcx>wJ$`{Zx!a8?mD~WL~D&tc81W42NkUfNy*O56_pFItlpPCvpF7$bw-3RweS6!*>2D)q8;GaLiwf-li3odprzpMd zjjS*ivT>v+cjnbv4}^~~_9QEOAK<@}ee{~NqBm^wIk{{MWK`dHW9Uq{R2`9HU<*ts zD6(QG)F|6RcW=3B1LO20fQd#-l)vdlR^)y8!5OQ@TNQzdFhq*~+SHD0K-%%C4h|cE zx;>4ypA6eIbxGger4>zo#npB0-xo}6(HcLam$$u6>yjD8GPI+rpKW!g$bVjpP7z#0 z1B4AvADkyr9p83eGVdIMu2WwxH~xge5DkIOOHWl+eh4I?u@M{akce2_iq&Ieh+Qp= zS@PzhS@Sk#F0#O^p%6@oO1m+U0 zMd6y%_Isr__V1GB*%l%IUc1&0kf7xdC!LNQBg{Q9Q8Gzu#{5J@I-_Y%JvaD$vn7!0 zDstqLw{&b@-!>Ln$)RW3^gF<=3E~#qPw(IAnyLJSYv~?*fv|=jD63P5ha3C;5X&A$1W16qv9-FAUvdekK#yx;%?*9A>9gi7L)1e6Z{R zcy>t{ube0L*-%Y*sBZj;D;}*yY1tv`&n|lyKc*U8v7$Wbe5cRT<;m#VWBVQ3T9?lo zr;qA$;$|*x7%y^{%Ki7=n%xs_hiXsa)3Uo6Z^j|;WuDMI$VuTyE0xQ}I${R9jK*j! z!B_5Tu*Pboz%^<%5VGZF*pKMWpruv$N_-53yT`sR^O$zsRAhH641HgY z9M&WGqO;EqaUyZgrj;y>5zJL^d`q4)O{SBir~D_rBJP0uyy9qwtv7)5*o)Z`bm!p| zZAx-QR9>RI8OlSwoh>lC=*G`sMj@vX^ot}rU(2?MyL%OB^y@K)T7SdQzsp#) z@v3IfZ}Hu@4f#e5a&kG&Ha#QpMu3kY=Cfbt3rCEBfIoG~eFD-(4R4;|N;};&4^h^I zGXoXF|Bt7$;EJP*vS?$CySoN=cPChI3-0dj?gV!W8Z0;j3+@CD!QCYUx^ahTzL_;^ z|ADSmRb8*@owG0YF03t5|F0uuQSVCuAMgH7{>TTw%sjuw-Y3-A z2wH9lJpzkW*T!DbLoFGWQ_h>Mk9&ToRbA0dwX0_ zBut~@jTHZxARaRm!;>DRN>XLZ$U)S7{<(K;Ly~o|9o1u&ebyF~aH@ZNUzI(?L+%g@ z&7`GzS|93NezmCVBu#%}J!-gb+uKSECQh^x%007b9qkPmZ%MajSTYIdyf_aP`cWXxB9~ZMDQpBdR)1(64`vERJN;-Uo8y>jj!GbS;P71@i z!yybx5gtztFi$4U&h%;^v`;xY4IXS{c^hw{8uL7V-_b#(EYnqJPDh~+^n9MY;2NFj z`>m1`gGLdZ2W0>}`2jyiLyL;jlMbljwv5gs)dSk!)o!$TZAmRLP%6Y=3HLzs6A3?I zwJ)}QS~_O8o9;%%HVAmQA0I|z-8qQ0y{zB5yIk)7N~2SOhGUTH7qgvN>tf*ne?4!` z)>E~S>(Mo%-lnwlD@Za>;BGXrlSSP@Zt>1SxE1f^&t?$#)oGY#8 zwd3P)>&{w8V7Gg|E0MyU0B_PX3DTrr1mic$o?)35~Dfu zqrg9s9s3u7_6nDJ)*+DKIqLqf{EgS{e|%ot8SSDq<6qh9G-hwx+oq`Ms9kk#hlYS~ zcgBn*?l$I|0qrPErGm(f4J)Mbxy-iVJ4*RM=`1+ z=c_160c#Yr3HUA+x_;mu%Q%`*ds)SHFQ!df06a_x2a)>JH{5{4}jNk`G@JBs8O-A!>+t5r)gwG z>dcSK2|krCk%my&PCP>K`J8x78CT>?Wx-=U1X8?uI$l$8U zJfj|UT~i51R`}3UP8JOMqk#la3cD_UF%@<}3r(N#x9ySCaZU$Qu?J`6GkZIhapA*9==o z$jj@WL$-Y`gs|ns##(cIvE@S3%#5wOQxG#V{1w@xKRX+Z-#gDQR|cQjvt2l#qU;A- zc8Jz%{oBqreu^_zeM>EOky8={YsN+>G>_M4dWq){KU7p2iYum@3&`e5X6(dM=hB1u zs{%?Uqh{s)_%sW7YWU&w{Xp}~DE1OCEf>mY?-zsK!xS~I^q&{`OQt$PZ=TMcp8 zLLj;GATb(V6_X}R8Y4Mcct3POOGn4h5EpOBz`)&AX%6e|880c}WL;EFb#=@$Aa!zLE=7z}uP7`ul2#97O1Qb9pd?-W3cs)kZB4=Pfhfk35$>5>L zw)cp64kuzI6`h!})rc53R~!v@G9;%<_hVhnyjB2O!I{ZJ+7aRU8+8`c!pYvbw!^-r zfR-|xEap4O?)xMHM@exiQCFQu-nO7JNN`qiAR3XJHsHRumn?;vN>&=pN9^Wh{!YVG zfkmijQQ;CyOMzpa9&{%(RzG6x{8J8 zrXbd~3-wFxBs7KkFAO&Sjh1Jj{!@Yygus&YRRzbg+q6z{h%2QSAZ)(uz?(M(T-<$U zGsSi|7*ZYBWgw2CUwr!(ukQ@tG+I+hlyY=xpcT)t0-^a`nbPU44dsobdY~%n25F#zjxxwcy5} zEgKjV1x(>%S}&Zoj-&ETzR~2rgrUsgl5bE0bqh zUnQnPGR55~LN4PjVF0eSJOcw<`@m1iYNc=Ala4Ub|K^;^{Fwl+CX^pc^?)O@1AiaF zJ*qsiR)roj{Y1!spSJi#lZK+xRqtbb5w)4-uW~LzII{9i(aB5sUEU@NP_(F6q)$)GI@y4D~|u=`DEZ zp|viZ$?#1F?zP?HmJf4tK;I{gw_usnD|%yx^n^$S*6>&|tHR9~r~-Ij`paS8+mc)S z%PsX=%|X(3z)gV4{?ggwJ38~u^ZFENZo7CoRea-iN-R?xmT z#6yl4IzrcnLgF6DcnA5i4iYmOh%7>uzz^G=y64;5ijNLLeq=dr1N;nHzuBuJms-5l zU9)yX_x}Hwk6b(yW621Eh@f8H23uI(!Lf!TvyvWD+htY?s?c z>1Kn~`AU{?;#tg9ANp_t+8`=L5YoWWQs0(8UdM`JR=EZJXH zU`f>U)q!rUzX$3G_>UN%sLiI^ZNXEM-{dwv&fr43{i!bp;DX`?fr{nTFd|MBbDSKu z_lLmId{5g8o*O_ksN)nj$x`)1Jv3{IPrSNlYa7z(JYYZ!;v)laW=%eTqwBJZMn^>w zE2sHpzk>fV5WAsp5||_QZKt>}x3Iw_Yur=;U@`OSuwQYlDz;O34s@p_mUnB;77V~A z74_)12GAWPO}#|{sM!s2Sw&D}pG74jxG)uv7$BYQk2CE?14o3hV9bfRoYyZmVf2QrPsM^pF#*^yv))jI^{ARdXhH8*= zStxoWFyk9TNXjK>#eH2n+a|e%#J*dNX`FD6q|7bwdtVycCEpW5V3Bx5x0q8uNWE)4 zY|Fm5E!KY4J4h(n5~WM8%c{weV@!y9pLgub`uMy-N2>?!%d6sC{u`%{nd$p$Oh`Yq zsfdjTLQ%`SF?10gi52ONQ6?t$wFxWSn-k~TfO{Q?s7YQ2sQozj2wL4{@OBr1!C%8?EFNK+(w(6~St z3S}}tJS6u#cBQFr0%jY6cxcEFPgze{0ks{sQ~()4$7znGRs4qdVo(fl9JIPSeo-j}1Ci#fQP-M|98 zku*v=_|wn!d|eVVbu%B(lXY+c7{7!#NoZ&*Q(_6%Gel=_%pT>9fPA{q3ms24Mw>93 zoRNcM1K_hcCaNj!#x`v2v!?xB^nlM0OCC(4gaZb#xO6N%CLC4P*ax?Z>j6M;lK-Ku z)?ykjMxL}Yccp&KDp?E1C!(#2uS@W%`@=<=R{^utr#HIj_uDyDw7Td=so_DAL6Z#N znu^z=>nPC;VlQxFlzEv5Sg9zjHs}P0yX>_R$+S#ZyR?P(0;AO1Y`@GeJqAy7 zrqcmxHc0*)1h#jJoY=Ea;{v?r!zbo4DSc&7I7tT)!Guy(b(%WgVQOt;n3CVaV=u** z1?3*?FmU2_-!e4{BHPJU-B+!~NC#~{z=M%pUN~l+=-aJ6KRP|IR`gM;pOjrk8NHnL zo^3!Qu}(jJC%v}-w<(P0x$xh~2M6NH8QT`~x`b8_kVBGWq=-4^gZl-p%BgAJqMFBM zEWt?Nztd1Q#>JsioQP2}o_@ctSR*WdF%iqQSUqF|G5vyW zF7Y#)!3x86zRVL8Uskx3ZBhK_0@;biXmk|cpazaqT!mZ{_X+OAXUhFCkY$*nxFJ<1 zj*U%ztGMXJBl|^{f6<_z<=qTHvLJzm`g(IjY3-y7~d^{S$)|X%orahKp+BpDI6huj61MPnj`gwoZnAbR5U!E{vSrdkwN=4S8%H8cu4B4gVoii%xwXvY}0ju0|BUR+>?Tz9bxBmjQY_y z)mrINC;50P@QKlAVrFb2#461*kTpZ*#U1OIQSi$*&$d4!Jp~H~YIMTMf{g6`wj)?$ z)q(!-4z{Q-1<|6@fE2x~iadmhX;WbNu@?clvX-Q9$==F6CUufKg#*lXT9wIqMRg}Q znzkCp$X7fFwi%u^4Iv=72_;MJW72D4*zjjm6S z@=C)_Q03ML5bQC*w2ckmByB5t zkmMdFpVy^|LP~b~eUZtGGP>Dm=UNj!^|!wY&3M`Oj@QNpazAn~`tYZ#Y>eJO?_8~{5T2l)rWuH3vqXE^SxND%|3fbp zaq&=sYT5%p_(;FJ`WJBc{NP{NZFJ31V4;pEU`0_(24k#;Gj`%VD(j3Pe&VCNIyfV* z%h<&LG>bk~foce5s2}rBo~0^_>4N`Nmezd%bc}6<4PoXlj24;^$E?u!e&Beu2H0E+ zq>=}2J(KZB^Qu<-{&XK|q)T>yFs473Ww9+FkhVsl!Twgt!W10(Q(spOdwJo}j_>po ztX>zirp`FfO?@Xym1l1t=Pg;C*YS0Y(SQ9-k)e!x5%AjbJYnP>{x+PZ4wze1_pF+D z0rS>7aU6xjLQ1{h6LER48t=kbA6H@a| z3@L1lQb%}VG8z1ToVgrGPogJICC>|RsP99uXrjYm1fC$9Jk0GWhA?Y2)uiiCdP|Bc zL$*GC&U(nz!jt_GxpYAcSbGTi$O7&${aG>zw;~0Sl}PU1_hb!^&rs?>MNG`*gP1qoJ~y=_textzlCr#I}XoLUP;~p)Jf;fT+jQXeHvbCC|jxxxhE(2sSL- z`Ll9Li|`py!_O3DQ~^JOZp}_lAw80k*yhEdy?kT6GA%s`hWd1!@vX^P8hS(p$qW&< zbdnFU8E=CtvfvDH$X^Kr-wBRe{*IxAL#1g#p1%C353r%~Z!aNI{@HVJ zQdlr*$k}eaN>>`(+-tRJ+zGIGLw6_Ry7z9D9|Ehhf4`43Gh{;cC zzI?-I9Ku5W8~sl79yA-A5mFb95Z%qEzd7?urf+MESXzByT6yfocA)` z)Y$?8EKRaF^cbRBSbj22t|?S!^fIHS+;X03exS>9Li5AE&9+h*+LQRbfKn<%HmpKs ziieC4c?x4@Z?{J2_V`u67SRuy?5)cdPfAC??n7s);V-mtXv@$hie}I%Md^EZ6UP+a z%M|{=7s3NPqVQZEp=vrlu0b$wF#*Yj$Jvi=b{f3qmiZ@Wdl?}~F3A5PN`dRYy5rcL zT}PyW{*5CfJN%u^XhbGU6fiEfw7B3jNa32AH&)3^V@|bx-KeA6jXaUB*D`40S=(x> zOX8#Hdl;sC^&OK$cG+xdw9zXv(1p>QD#Z6(wl{=MH+BdMg=XD$ z#)`MI3D|*Jw2TZY`7uG;uzVmz?P!L-E@W{sC>ok#6BAT6x=Z&Q*$ak;l)@|||K;x= zzcYBj`(CkZ;gw8kc7(vT^MO-RTxZ``a_3`)kl}St zgK@XVx)CZ)^ISS-$l#f^vx!Oa@0ht=;eHHK+@o2hfpaH1MLQ`&A$O?}yi<{XNdFS1 zCNDq#?}vzwuJ~<`OgW@A>M!eo_f_WTn<_-wXeE7q?^uJy_zg!+bWCR1QmCWP6w1G= zixKJq(*A7*+Cn7CbnQ6yGHOH`Ks3A2r9lnj++F;;*G0n=KU_$TvLSfpZU*^%f z2XMX;Msh@UG_Ss`|D8t52F}lfD1J3$)uSI-tXb@JJKa3>y01K_nh@T81YZtJ|CD93 zF{k|LX5ijU6_dJ)(x0<|3LISsF#Gm&B~2Z5IX(Qv6fDt5y%+X!oa(k@g+e(@8+ z$3^cJ`-F@tCFPOvr42Gqu|Rmg4mgP*UtB)^#63Xto5xLorqc{G~VAM zKfMm&Flpv0y!>|}@yGhz8c0Z5@}Y5x`oHB-(8B9Jg4 zrS5?6sI3J`QeUub&5$7!nOyD-j_f<83o_Etl#mo5u-Ac@XQ~aje}T|bsSG2M*Av!E zAON{#$Y40setbY)-uuC3A#}Xr?Ix$rmQCLE_~FU3@8kDMD2V^cYF!KdClY&7Ea&2D zp%MymUoh-L?6Y(bR*OMFiuHa@Mm5CGoKZ#SYPJ+_PNOk~hRL=*R-P2b(}BL<*o#V~ zyibiO&0g>^i~=THcIfO{N7dsSbiSTjbGf;g!*N4zl=;`wY;_7@E}GU+nR&M(uY+JPSz)3u4tO5Mydc^fGh@9dhB`Vb&th0)EJFr-e3jt@a#L84uJJ|30!bHGyA? z5If;}d5ePin!lE3&-wI z%CEWo3C0n|p~Hi5xPUxeF%E_a0sUXphrYh;;O#o?hmJ87g7tAM^M%6j49_;@#Y5-? zOVqd!AEWsRgx?@EbT23GNB*{t%UjKfkl4R0J1#E-;FT?elRW$Mq<7Ff#w#28xNjRI zDwF3pABeO+|FHd|s~^<<*3?qm3KaqQFa8XTg7O+Ht=v=;b}u8Hicnp0yV=@{YAwYk zb6gs-^NB{NcWUy1zuhh(Cbi@nJz}LYpEgnnyeFqDGglZ* zPki50p+%LEkjOb02Wxp(x}S1!cQ0YmS~r^JXdTj$I|wy8x3xm?l8QA>6p$MUXfqhG zAPngLQIjYyN={sbh1|0^tf8!jaryhibGe_&lDZ&H2-o8X--cF?{XOOpyz%;37Kx{IBezzs(tGfm$Y5y!O%8mtusLjRq zX))j;3`s`eGf_7;){JlxZ-nkLTcHSsxtBn+hFrlp^w^eEF&ew+{BVbZ?+67B2{tO% zC=z{Vna8t3=IZ1_Dd2`&4&+jXWp(O0p^HZL^({$y(%pYzq z9}~nQ4w?LZ&(6;^nDj$SV@M2@iIl`uQhk3;S9u>e5jS~`5?&GcUQ26;Tp4gMML&Qi zn0AR`t5HVb7&2H*`<)4Jl_0@mq(PT!HHox!({<7pjWHlyQxk&SrNflNjNg?2z9|`i zx(FmZq5C%tg7vkd1U@V&DLEok7J%pK9i4ry6k@qqC2;X zHg(FWHC=otLdSQv#KovjhIzX-9PisUjwWPcq}Q7F`qp>uc+}HKH&8OOgfuEo(Ke~% zh~X){fpT%{4H0j*X8~Bxb`WIwn;L%lOKvMuI)S(PAQiCE_Cf!7)4%MV1U|v1FSU1V zLf!w(!0?lv-0Igc`9jng1q)v;hOku0+)SZEs^vUDPki6J_`CWW*6*(?@LO-mCpG$YDy{i1!()w-hX{f1^hr-cQ+vBb0Y zz46OGrQOnw{7$V*q!AJHdz#~CwGRko5EN1gf|&Q5of0^TUdgaH+o zJXn0q39v@zUcPsXaRfB)*1Wbw^o5aPm&RIufn?URW1s{i>H&p8wj56n6&@^7Rc9Au zErZ7>?s0KXriX(f_ObzIuOH*H_p|N}_S{v6pT&d_I|x+pP>4e}{OeosVbD9xPM?Kz zA$&mfRDlS_^B^C-p}=&s;<&h;rk>WdA&UO%(^7;{LT^R(eVs1<{tEf25G)4u6EC*9 z2vPRrsbLL~6^ONxO!G50wLgZa4pFW{rN6(~8@McN55B3!>c=0Olk#!kU09?xo)`ST&!r zhtOG+yZ9cEC5(d%WB3sU#wkeB^qF@YzaB&n$=`>?2&E!GbgRi%hFebe*Ga~MD+NUv zH@8Oaq3K^x7Zc%6o;X=*iTYP#7XO%^i77Pz0fq+s1v5q++ z{-0LDvZ_f__t8`3wlOKt{-{)J6C=aQcua~6fFgF3naPw)rs_&gAw)>yJa= zPIOo2>dI;;rw(LTMa*IPXjqEG>^6AX(l$Kf!0Qx2^2ktCPD7B0K88?ESAp#YygD z2u$dxn;gGUzW4uN4&Wd}DsCPUzk^I-S430^B>Mj!Y8=q?WO4Tk4NBr2{krOR>3`(H zmyTf;rxy@=|BoG7HeaarUrO8cU-Q>tN)n|a`V+uwr#DdSAvp&c9|t#)FvXTfLgx$w zr1)c?eN6i+S#6R#Xi~#qTQZswu`opFojM@#n=>wZ`|s!^1isu^qL85w^(FiX;Q|6$my z@iX%)y=tHlgje;{E^3H`G5B)0H}_%dkQZ!n_=b;=)<6+TY=HY=+v5+i@v-^NhH-_U z`p5pAoTcW~k;<#m7(~Z|1`DS?^8*fce;Q1r`^#uOf^CH znf?hw0yYqbzC5nV%ufB+mMo(1<>cF4J1%59TMUbTNI?zymQBP>jvP6#{u8P)A>63! z$G@yHec{6By0GzOP~mj3Yx8eSO;K1bZGK%zqIkd{-qX``wFY`ObHg?MWGmftaf}UV z;^Jgi`m2E-Hv zW)hM?Tt4gUtrf+4Y|R&>vc$>f59sVU(@OPdsTUn2Kb`<-#;d@Zp6m;uIfG3WWpW?L2RKT0_025RpT- z+o1PR1ZQrucxV}G7`vBKao)3;ABO>xBK>K1@*kqF-?bPKlhu+~@m>sJa~1P~PpMSXpdmBG#o%b<8~Io}TBm@$7M zDs{l@_HUQyt4Q+^Xa(~Y260VE2VEAV$2agz{UUF}kh_NWn%u0|!WS$wvupwyW(xz! z!cl3++>#t>nB$W};5*L$7IaRkueI!ZT{T@GK$NDuW6bgWv)hd>{@7pYN~({E zM83XWThGorMqq?d`w6-Z$qSA75TbuGhKQ*2VGHnYlKZTE;&vO}u2VDvQLnSbZe7N? zKNlX28LSbg>M`h7!cr;3Q{Xh1@i{;P->|)*3y&D$;L?}U^CAY-e7Tk(N{thNDl>Hq zX3^fpo-?7@-r0$_G}vGwhzcKSz67sD($A(*uLkc)f){kTdC)zvLdh$3%oDdc$x}{ZMUmtX{U3q-eAdIrlu~g_3cLj7zREcX8?d30%b<@t0ZX34M zcZW+{s(oG_Qx1e1ZFlbfs*u%w>Kor6A2n|lehZa&OM}M#X%h?E#uv{}NU?WY`2Cs( z&R74{;^DL?otinc#@68OP=^E{^tN0MF^f*I?L+i2PmzXyAZO7|<5K*74vb&zK<0aQa&U4+X ziSM6FNJvGZg|zLRtaGjK1rwfZ0fsyc-U^55_hW%HSo9rgsz3$Toc9$P0EM)xz~~ zW0IL~cbr@jn{ba~(m-hK-5i*ZSc)Qy&;aB~JjNDrh@T9up!G!@QVkgock7>a9Q)WP z`w!ESh>IVN40&Gb7G1-cx51akKo&vA2tAnvUIBYvTz7ed)0}7?>Kv> zwLy}~)zb{A^0X`7-g?<{e?~|}T-1!D0S{wz)csH2k|ODCnyX*6uh_t^>pE9{fDg>3 zV%bZf%+!JM&;o{0?t~Udj6@@BM_XalDD|HiHz_dE<%S_AOK4Z$UdKc))N^Oim)(6u z#d*~c<3{7!y@?Er9Y_zotMyyEQrP9MyNI_he1-aNN!oGmP{Va|s%&@H0=@000)7w= z`8?+CcOBbg`+Kdna29dnoLS;jH#f;V1V6mXVi+TfQfzj`drmTGIt&S6k>NfqslHl_ z_hqz(2h7p*W?k62qI*XoFHWd6tT_Rf4~+q>#OFyfJf^7wdI5#R@iyh@bgqNR#4leV zT^6mJVP>ny!_B9bU*Uyn;lo;(Oq?x15x6nb%CKP&rSI#=5E~R!Q|bJHR}qnH6&w`PkX1daDytkJ z4iR!$Yh{+gr-%XaoC2mF=)y+~Uj}=ibH3oUa=EAo225{6eQCKVz56&&CaO1kV&%eE zE&A9BXQn)X^{YOdnyrH$7B)HQyw7cCLK*{A{2q!(h2ylW7(uxDJ-xCesfHtiPz3v7 zKd{gW(qf=u?Da{w${%%)NO*6mLjvKnj&tSQqw$1SFdBPecx{&3mBMiV1vQ0_VF&Jt zZ2!Rd#+LItFPW)S{6BDY{s~dg?wu5(oL#k_D~_t+58IG}}d@Dir*5Kqg~R$U1|k zFI43S>Sg#0WzD!>WqICodSVn1Z6bN~1LYg1rTgZpN!54F+YW#m!H${em~m`sBowmM zco>0HP#J4XmOwE%?3z_zusUH(RSkZJ5vFqHAE41iRI~XFrOr(M#S97g*A$FBN_#qT zAWNp11=J=*{6Zgdaf~1ZbKR?t75=NeFm?#&7OOlyJ145)UGk<3=5$x2H4ujlw}x8Jq;>0Pp#P{hPfq7fpn5!uBghs zZs{iT@R#a92}BaC5U`ha{55>#Sc^lmsf-9a{#f)GnJV-jG=}2X2P3#A65S0VG_|u9 z%&fz{6r@C{BZYy`m&CPE+JWH&Kc9UBIipYCH(?3nSkb}==y24AJrhs@$SomjWW=H$ z>Us>d*`<@JR`gt%hpE;OJxXgEqbPP^>`VzU|1E_0lEF8kf=J3W3YZJRL#|0{g0Xzg zib=xU3DPp0#i3+<$4=z!mQ&XXDU*DYawchkI;h_R8jz; zi%GaXX$sX`@k6NUNCk6@2MHf=FTCs>mPCHM3?RrNrJ`tiS2#Sqr(wTumSppx`1wS5 zVhUeEzczTNxMBrnhW{KT!1TU2tda>Wg*Y8;T%A#J9BYl85Mi|3uydOqfQ;-+mh(tk zbMNXOIEvbSvv5XkDU&>LLog0=B*6A8frC99gxz|CHGRn`X(!&^brt;Yt9Jm-!cuf0 zS7)9PA}nPB=r>aOUAJvRxs_iC3Z!c4LJBs&yAnm zG|VS^b`7rr%N&!;{;f>>&VyD3aA@+P+M(%EY+InET~hGKxlJmqEtZ_3EdxuAw5;Hq zs~7XA!1RR?Z=8C13F6d`ire`DzLU+1i|7kNz}wz)Ksk96lZDP$-60p(7i+v3<_iZl zk-E_vpDW#f=_5VWxW*`+h|s#xicZ|g|nnzyMWr{rH*T$&Ai={uTWmC~nniO3qO>%CF=pATA@3))}L=Tv* z9G!aykpRVTJeOim5ms6d*`GoKsG{cg?yTiH8llN zhP(1Ms^7%j{(uG=7Z;Wkyf4G^EqNqNP?FO$0i?A0%Lu;{CMU0y8)2}O&|thsGlh`3BZ>S>00Dv%x7n}JUvAO;BSfv=5X=v zSQMav2fuR#c<+Ilet|odZrJL!pTwgr03C(_hy)zyDhL1Cu=51}sM8IV8s>vd#s-gg z2t<8E2@Pfxh1m{{7~4r1q>h#{iS5G*{ScptFWL)!h@AP7==Px<%K*`N-v#2m>jHfv zTiWxZYKMd9aG)>qh3U{taH|0xc}_4)-`{eT>F*-*_g=c2bALi-kB~VO!$t0%BMuLH#C3GLWtor`p2X^_XsSq z7CLOLP3;zH*u+3ETQVJA9C~3_HSCgM)K@yW!NkX=E!-#y>I%J4JH;wNST5vU>8m>j zeRbPjofU>@P|14-zaPrh`(_{C(X%v9r)0l=xk38)qbg8(08!CgQpwPFdyzC~fN^SP9Uzte`^9gGqNOE2}gJd{-~rng zP~NWik)gViSj2J>(mpe%OVL^DCOuVlf60nqM(N=nR>vax$hZ3=Z({@>iY~rDv9)0# z#HAdX^7JuJ`_15s6Vb-5-{-uxTt_2Dm?fjfldhZDqZ6Wf`u>&7ww~y*p7w9pW?J$m zUXonY8OjEQ=3_()PkUY=7>JT8n%Ja5S^#6z&E}30trYsIY@J ztfAzw{v-nBRMuEkgAaB?_C!t9-}+ zcgO_01DimbLSO=O@`$(iW%Gh*bVA}6u#_>uv2Y+Z{G7|bAgW)*QiP*U*g&x2Yx5>N zCSuA;Z{VukAA}omo8j3k0Yu|PVqQTPzV*(I%aV|j?M(;T%MD|jB#$OFc0YL}8A|J- zhXn0t_awuBp&EEs>=_47-0-0m{0dvH*=apPNeT~18!{27GIJ#EM!v}7qQ2bL@@=s> zF?3mqN(EJ7B1tesZ_5ZxGJ)P7j1W@o{LX3UZRkZUa#M1b4g|DNh=?H*2>|0heio0e!-U^00C+w&3czXXDOZSP%FTU2$Bc zedUI&SlLynTK#yX$T+ELq35=?b>sv8wNfTepd&qq-zt3ZsKyHBb;(xg4Me;4a|=?% zju+kjTjYAOXVRaxQhTTwrWYq@AQ7WtAK9GF^LWm+n zheJUoz+LqXEvuFUYFBqpZRqay}XLoCpFAMW8;u^{tM9>2l ze0~xMQzN00-s0}j<_9cE5)aI>TYee+Z%(ED$9X1rrg8q2CvMUTl;Eu6vNhqC`_lyL=OpE&|GgU zQ5&?fOyT`GWZgL_Y5oK!?1ye^y_SB!DzTUDm4U`N)4aFTMJi*HNpzV6P$qJ=PDAixR>Gx^8#I zIw_2OsB`2B79s${1g7Y9xwz><9B24eB_gr;=??&b7y{ZC@>}I9kPrfgnAV^=I~MW0OA_ z6yC9vWgSko5m{f*l`?;E3Y22$TBIzT*`JjL{+Nkx-HesbcedR=+~tA@8=r-<+1lk@ z(za=*tgUb-7&Ep0tr!U^TA$P$Hp|g{^R;Q(o3Q|wQw4L~6TMlJ(ya+pP-J%2 zzo&^HnAHdeQYgLDKYn=NyPx;zkRsg*-yHgNfu`&kfXRbN_5_xeM7} zwh|$0wJdWXdkDxQ%;uRKOzn~WKOlHaaHLWorh=VFyA?rmoh50WiPOIYD1vsqb-7`K z1g_o+Kv2g@&VY>){p~yJSG$vgL73b+Ho#0{Y+I2D)ZS|hTk!*M)ILE`suVCf?sq3) zLAobCZ#5fq-%S34rGf{}Wj!0e$wEUsjMB42Kv;m-$T_#eOOu-nnaxp@%@2i*o0C3vD12E7; z))|AIzirh1;XhA6x2>R^2)P;h1GPLV$|`a1vqHFx!ibW(`CCoZyw}`gs!X-J$NW=5 zD8@~PF!lrGm}1j9NQT|I;ZHnp+Q0M;^)(OX)w!5pA8ya+*#h%pY1$O46@K$XrzdmlW@hxH;UTNLjteJc1P1gjM)Y%>{ncge8`mWYAX)$= z*kpUgvzt{*108yRLqSr`h!EQa!AZc(UVn$H{f?!#sjWVg>gtSZTnTWdn8^y`&KCO< z_ZZA{qRF>Ayp9EbLs`MU;Z7EouC3{E(D^?kodrWwUAu;-7@8raVL-aO8$>`#lu+qL zIwYiq?hxrlN~ODD5Co(J1SALP?yfWMclPxU_S$Q&XFYM>e#~7CX@wuLfYCa^j^igQ z(A~Wc`3q^+HS`#Qc`d2ih~v%L$Kuf8Vm>DPLA=Z1@TiWB+s*nDg1hUEq+c`cs4hR# zA-NVa@MYdwZF~>gcpVb1vB?aNh*1Y>y5eDmIx#Nk0ivCR|D6UxbZIXHbfe||XkI)Y z(hUFdA@+H6Y6!x*50e2FM5!R38KSpgVGQ+8^O(e70%6}B;@`DIHh7d^9NCb(Zk{); zcB7cub!@BpQCGd@c-gsMlmAWcRz+;)4_2Z!c8WG1$uLB)z_MC5-rFGQk^j-+p}%!s zq>sWPmY*^uds(u}DTG^)427~CARi_&=p>mWZ!-R9XQe3!;9r5H}=XVkj0EzhUsm@(A|JDCOqbZ~z*O@~5z-iLo)ss^ELrQry9xa5LhRFan46Nr z#J}%V-@cc~btd^hf9YIp{5w+XCRs_H5~zuXyOd_wl>=yIAI4v;H3}^47sk$eV?9ZZ z85#(HO2|mtYU*JX8L9Zje}Sv?X{lG1P0j!$-j{S@I3V+gk=`}G@ruY))|SU}yJi+z zz_)nLy6wOnGg_BurdE)t8xVO@Wx}cXn^FPuS8jfoHdpUf-lYhe%`DGr^)?}Kt ztL-VCm*oe_M`%&_`-D9^coZ>Xp7CRk36IFsyN@ppW4kFRDE4o4(0BQ_gq34EbvJt> zPDPKjesqXHv3T3(^-rlhS~X|3G0eHcDoW#h{z6)ny+~!XI7~*(Y-yYiX(=pysy!i4qXtXC! ziLWimTn3um|Muxan~f=OmJK2hPd~zI8rax!`a?glGnro68PH$MKZ5wTH0Epc=zWxk z)lm4oIo46z_0{ht$ApmnaVb0un)V}`VViArY(}JRlC4I>I&S0Q&P7OlZ+%!cn8)>{ z8o(#=Mpf48-9GcD%*xO3=1#n$N1#K<8TVL!V&}88Hr}A;$CuDoWak0rO}YEp5&#t} zEptkWu8Us_X980pDUB(X+&C-81Sr}5ed!%+fUnt$uPnA4-x@FBN3RM$Y*DebW~U=` zw96}weSQaPZ{g41&8lfzmpfYienUuS)k}Uu6I@F5NAVINqxzwc#FjB)qyUxitX-T) zWmD)F3CRC)i8pF;ph({pM(cv`Tx|{j{LB>=A>{_F(+l7z>xXEZOTqFSeyndkWRgJw zm7QMlm07DCncDw2QA$65c*{SVxn+rWfr0rTjlO>bQpmzK+?D=o%|^!%ftmMq7o}qt z69R9QhlFI~=>x9TKY|{_-hao2Tm8gYJgGcCCFTVV^q&wRBt8uZ-eY!OrPBD&es8zi zixS7H7`w(?x@3P{KOe$T1-;h0xbg>W(76q7%W>B}J}ItWwjYaH`y`x5Y3`)*2)dv@ zS(~4vMMnQkOVTIP0eX~ZK9@VF@t30!^8Y&bG+vi*i6y`Fn9N(m_aq5Ybl0NI&Tk1` zarq{n`AX4j@i#sBM^9iL_=EfxrbDeR)Z)gZ$3BQ5N`N7spDu6&{i-CF12g%=$3?AW+>Ge#K+* zxdxOZ22a%XK6w_rVlb~a8ght0QRE&~VanfHX=?|dFCc zTX`c{$M4vEJKy4>D#adYxdj-$MAb0uK+=nkYzVe+bcUO1tO>9e?#S!Cpqv=&Ow20_ zpt_r1&sWA(-IdHa;7SuqH+X`TH^$U+?*)_s2{V|6@{gmxzaIno>vk2AZV^iuX)4~4 z>~l-^s&df;92%O9@rXU~Ji&Ivmy`WbwUf3`W#v83o9M3|I(Iogmxhk{^4z$-?F0LL zbq1E{)wnDLD}p$Z`6U(O=d<&ch-!H!kp7};Ocs;ZrkTCkxam%wD(Rq6yWET8!tOW8 z6#hUD>BNnFS=QP9M6~c(9>e}!W{5?+(F+Ys<4wx`7!(%ED{Dlcg_HonT~^JE9-#wm z?5zS0kXNUN0%PY(BLVP(s{#zjP1#3$ALEmn0czR@@{c2L zeZb$C8vs`b7+_1$84o3NxFcl0!P--BM`@uIG?u@}^@!B_Q*ff}tG!MxH#kJs_1-j> zp+6g(&5{K*bftWFh1COxdPm`U1k?I1P{X8vzf88f%WIHy*bJCoL#+UJ!{;WBde^Yn z3hk5At$;>yq@Q*jB~GA-kk$zb3DwbO6vr!bbgf(TVPJo1RcsB|eZxtFtT4{VUC0Qm zFWFrsoYq{zQ3;lOe=}m64v8<(>2)SfkNNvA12_4k(Vsx1F{q>Cw*(WF!cuQItu3w; zzEWW?I4(}W=K%V~RQ6>6Ks#&o3Yd4d*URfub^iRly~cP*_PDSd0dF0>0b~K@hm{TX zhrq^yZ5Y1I;n?=NX~lY(kRUXc9s^13+1W_004AgpBfS9s(9=vzM)O!~3|z%Eo;QT; zFrD%Ga>Jf0<%>`A?-18Cn_@HHnU_zcmlJ%DH>)izqHR$RyH|8wT^WcyG!8E9o8Du` z#x5~uxZC6ty!A9aJyOFxhtmb39ow`y4wMl13Ng>h%rT_XngHPQNB9$QH}`2(w6&%cjLXeB)6-xb>Y%U@CL@zXdBRJ`%xX$h?74rP8`8ppLba zsr$msPdtU}SljM7pGV}!8ElKVr3pHHuOj?%D~+`@*?Dl6Z7ZplN+kb;qTM=&rrE&K zO&IZLjksA7%g|-OI81fMOtdY<|8D+MuF?>l%ZO-~S{@F4gr%Oh>=QjbzA^fFd`2Au zClp%&`S}-PNa5$-zqXR!aQ%M|3~g%5yZ;#B%=w?*Q*YLy+N05fs(a%jQ^4_m5WZ&k zs-TTgXf>Li%`a{deK>~q3r)sViLT7)XT`VbF?KaumPQq2AI{IIuY~){8XqA>e1O31 z3SYUh?HH`yM*baTx)8d&o4`w?+LmEY;9U(=lZH~d_H+-|1?30)T`2lx`$k}p1EACGB% znA%%zb6&+3SL7G_`8NJlC?ly)VG5-sG(-N`b0ST#73HC>ow>)r z>gilXrdb`!&x3PR4!7sb*EsL11Vo-hM|i%vLw{Dxq-7`yV=E3@GnV(he6ozc%OPT) z0(&zb=p8SOLkCO;>?-2w`nyY}4|p%NLicD0w)BxoQDk6`FPUtSiMyC2{|byd-oq%$ z^7jzwdW`|5jpH^HAguj|DIf2cbrnUQxSndyJDvzhUZ!D99DT$mDJ~*pjNZ}u`d(y8 z_KGK)?1Y#eAL$^={HgwX+zq;mOx^0(s=chmL74}^=5hmoofiI6?ATR-QGR^Hds4^; zAkIHj-&M@h3MI>iZyJuJr&jf&wmH8)z6x9{Zu6KKams#_L8rX^6jm749TmRU$-r60vO?7)BV!EJwR zrBd!ugZqj6fZFP<)G|UGW4I{@Vi;=5K7Wr3y&LidkPpl!r=_^GCvS!*TABH3jfHjm z3F5$mQz&W6U`DI)|By48Tq+7#P6)7I2V5R}|GX^U?km2QteJiohM z%}?JqtKhTkIloTe!qI(2^VFUOV5CJ@fEeP;U+d|QGnGsvc&G|d2Ns52E^E0c;%RF9 zd98tgmOEWQi4HlZE|>T21U$Qoz9IvTZRU3zBUH^?JC0JHD(pA z6-v({c8AepziLs4+>RTy%F8`-@8UR&GY#6lfJ2!^g}U*NLyku96180GzW3u=XKGY) zdE2R2cn#kFl7dg5XUA2vIiJgCC6BhB@CvlHn>|DK8lpFgx9AuPFyS)T2$0b8%R0_r9i#w zs8<6qmx+<|Ob!~enbpFCE)pmYHZj`o!fC7pzt}VZz4RAmBi|wd9u%>)5(F1-&r7uZL*7l1XzC`i zo8PJ)nYn6|k|;rc^#}PEOq+LUIcMMrsF~mOA@mp*7s8#7E?q6YVTU3FLG_~PEkPwe zQ6FEg-eGJPeI`(b6-NBFc(IAP=-v;R&-0NfwbC5QPSw|g`YjmvRi!L5v^#k(9<`b- z{|8}Cb|bgrySYX!0wQ(3?mU`_0*TiV%B;Jt=(@Et3YoY2FHluu5y!2mvYUR8j!#Wl z8seXek9C-h`L=_caH&!jqLHSeIs-j4SVQYpA}9aORrrf^t_S5a2u4xJSeCFpOAnohWk` zIQN4idQ(*l=9(njbWOmb)FaULO&MFAgzpxv3RIZ39O8G$m&UG!J?KSoFc#qWSSW^p z3>A4tk856Syfq0bKq`b!MRuF@UQST>mEa7-#O*(9@HFoQ&WB#-fl$kRfwpYToolT( zA=4NYxNk8jd2QOWkKGbnY~;~&N5Bl8aR)1uJ41i)RpO z`!V9)uc3Rq$^fZoBop@jQ@obF6X<5hrQdrzsn=ctm9RjF`;d@S#!iojqevuE8dBwZ z^75}3Mx*M2(>qvS$AVK9%#AuFZUml#^(n2{uzihu4SFwJ`n5iyZg)gdOBy%gP(Zd- z8-j451Cm~=Rxi5WWPU{QkGN)nj*v*G^c{=bY^&U)HxbqhU7GOvBP0nH=m5X}nb338 zb4N?N5Rgdd@%tNK$l!!z=F#?iOV34!u_`wBElf;#h6@YmAG^cwCcn!49;)j za7Tp{&~ITl6hid-WcQDaq~Peta}2*jvMKZLKxv2iozdC-0A-KDE#=>FrXxwhFQ&N) z?0*b~reJf@zdw*dp&75sxNiOrUA^=0CwdSX9Ly<-r=Yy;tcJ>pGJPNOcZ73&XMmLzYr%g z>xmHGotB2O)3w?#2VcE#lv&*|mE1KB%$gB{G21`cwra=nk~ZvUu>fs7JEg@zuvk2# zR9m?IB0W)bbKB5#XXCo}e$J_H0q=_~2N%h8sM;Y zQrU&|4OxCBk3`CUsN|UTa8%(`pv0?8s8^c4cd@JfyD}^XI5D1#4|IiBojD8qVhTA< zMKhf`8e{?Lhs2B5jr0CM^Urw?mmV?Ja)moVr|!$QFK;VTbe*`NsCLl6H6{8SJz0kr zQ_1VYl(5zk#EOdhG2^#0p-MOx=3PkZ(Xn`}-g8;-QXAQRI}{xG8vg|I)M+}M9-Oh1 z@i5|coVAo^Kr;n;DytGWFzfiF%K%5j$W6Mvg{?gEU-T>usQ=YjO3^Jt-FLmg6U4qh z3Z&%6NhJSL+b*lZw`IOyaQ8yhs#Na!;Np~QUVaPGhcSiGWfR3O6Shd1`{8!CXD2v! z>_Q4peS+&Pn24C$6W&kdb{zdfYshDJc8NJ(uiq+cq4PBy{h*G*c= zcPGA^xQ`yNS>qJIUjpv=*>^L6bW|PGMBdE6||i-M7$cZbJ;xOX=Lr0C<&F76b1PZ{`q*f{`H1B|Lo=&2U) zeEt<3lCv0bQ~}1Sl^GFG1I{wqKZRxRYCQHnz}M-irTjhqYW>eiz_#vFk?q_S13DV| z$AATjHQ*{P;~Hu2ZCmbQ2a!zMAIj?wTn^X04qn!>>GDz~H=AZd^B=FK z_7gwG)SaKj9NjV5k<`GOkGq)j9Ob9TAy$VN-}S?U zlBn}-Nqc_PH7Ri-2EqEvbpPL61+R|!v^3T75(26jPmck4ZgfKi8UeWVhnf| z_c5*T2F!%|2<^FJ;a2@ZLyG^o)h9QsMEd@6@f9&%n-EqfmNmU+=4lP|fx3Y8=(%fw zWr&|9yB*jX(Zcu?YkJ=`?p+qhq_?e=R}z5jJ{-m_jnMz~oMs?^zX<9=dPT9dy|{5a zTHKZLHW_^3lFBw9Uhr#!P>kGuw9|t#`xS_n8K6P}_+FEF43B8mm=3uq^ltv=HcUHA z78xOt_d3U>Je-4<>~?$_LyGkSToHgdc9-J8>N?9V}28$O5c!MQa z{%(*`L&IMYNK7D}742@x24)Hbe^I7v_DGOCHuW6{n90s-#64n-ZIL55LN$o?Yfa{N z@#JcV{H6KpQrET}NNa+J91{~iT^s%^nB(KX{2v6BpHAJ3M@@(-uc(gzueY>DP-cXG zbM--L!Ib?zBP$!QyOwS*3pO?gWz-_0;&GUR*?AKa>#Uy=c*-BRz7mbEIN_Y68zOe$(Bj4HG(iU4f$Dy#w1#z9k4 z;N)LS^8u|dmkp%^+OR9#%U!hi07^TAJAedU?vCxfua$fZ$^b{l<%W{)Mu9vypLZB!s(%d1tvd;N10|Pd@i)o&)r)1+4qY{@yGTu!t2 z`oV*JZZ{r#3U1g3%4$d`qqs|4q2WJdRKINQE5{9~7vso$E_cUNwFkgH4PUvxV~{<# zzvKu79kss(nC6tgkw8N%mB_27$bc#bEy+2e@Dw(O{y|pgRE1M-TNtN&ApH@`X2DeY z7wTB@0-vn+8Z;zwu&gR=fY8rf{;SW1p7)|j`!wMx7+&R`x7}eizi7!!IkT$bZ#Iq3 zt7(E|@+0|iIe42y*#rSaO?ziu)M6^Fo&KM1XZjnSQmkRVLByFIt=?DG?8+i^|5cLC zP8k(}VFtfOp13|nopP<}j<$bg zu2n=C8f(o=`gVt;*uyV_l#G4d*9gwg+7o1BTx%9eG0wC_gR=HtQW@v%F)b!H4p z6^14v8ORRSfztf4q?$~>P)l}5(x_!GOhQkHRNBjqK8E0@xI7tppWM~7TnT@PT6%7V=uMLz%fp7;*t?b%lM^wH-9_DVGlpI=_;)U4 z3R4k;rWr(kg!h+CF~h|OO9f?&x|5%dd{r%VeZg;C_zm+0=kRpYe^|-dvP=){ zZ}KK`9g4h>#u`^#TH_*op<0XJ6`};58&q&u6$t^#48lN*Tj7AJQ;A#ov7I*)azB{x zfIn)?BgyM15K?P@EC&Rc7r0fAkm(Qfcmfmv7$>quZT0C3b889P|Q>X?`?6azByxz|e(n;-7ZlidAG`fC;3l6; zlHk|D*Jpo|Tn_9zgdOPs4D~GpBcE;qxHMg(ZSX{hVq*PIlh z0Am=GW#|FS%6+38MTYIdwFDuY*|DX(=7+Yj=Q9d&g3%Pu@`Fc~^3Ook(rl98aImE6 z3{jw>>`ilx5;5Z#kgPuT6f@Fyq2of%LIvH5qDAbYjd#$(^IQip42$}Gj*4PMxS}-` z&h7$XP0VaeRjz81Jr7z7Ww4y^DgL)CWe`3Mg{w7*>~&@ySM1o&eX6T#8DSUh|M8Bl zF3|dM_~9@9Hjq9Lm;}t>&PV=qm)h|@UT)KQ0S2hwGQkb|2 zrpOhke0RgZne$KZ+vhhB_?86WQy`IJ3yEPP#4ORcwj*k@4&C+Ds9XcDNv*?gk`TAnv@3)b2pzV{bF=! z5Kb#{4hQ=zKD!LS%$-O+f4I+_esE7yDQ-GTU4n-dSCi&8A;o{9SzlN{t_Jmk6P6?C zj*j(@C;@?lbrFAi_JFym|FQfxqnC;0{6?APAK`O_I@yk%<+qPRaXf*_I4aNhc2&|F zgTiu*YN&h6MqD?)UONX*P03WsAooLuZVie4wT!CZ{-D6C^x#fjgeHxpbB~pS+Pf8I zD^@oM?vloQ9mCq?1IVzMA!{<<O(2c+? z`dvMPtZBB8$d>0OBUa0Cqeh*kHPnLTkAPiI*eKRvPX8X!sKF>80r8M~OWqb#MF# z34Xaup{_gYhMw)yx0ppc5EHwzB}RN%+l-!mOd+o&$v1Qsc`g)wB<(=(9_w|_FUgs% zF24n)KGRKt0WMYgO`w=`n)jx=+=1kU#Pinq+mPArt%MYjbR>NiR(5Y*fXlYgj-_XW zwDebZc9MaS{+8*}vnSV|p1LW;TjCLTGoqpptkVz-TLhG?XiwvBR_*0YV^~7he|={m zcU(qRNevma_Kv4creFv2rFXG{b8u)nCBKNRq|YS6u= zca@{yR}iJ;3XpCMFDvNS{3&g9)Ov80q9fAn9DMaAUVX7V3s{68m6OXZU7D(jF)y)n4*r87%U!n$lUw-W5ks7d+N!5onNM-UP==|V|uS`%; z(niwSAwoZ@_Q~JM8d^qX9ma}HebFk4&&RL(UQudQQ0xk2hfj*m-K`#d_?5Soi63OX z3p5Y&>ENy|cI`50M&vA>Zt>ZlfLZ-*eGc}ReTBU$cBtuH z2P}ux;6~8A$Lb2oPJ81?zt^7y)FnuB8*&qw*$N$6&%-!V6vuB@^5I8(ha?fSzIx@s zx#bCg4LAxx;B(Xy%`}`Wv%3>%E;Ye7B?4rKH^ZQDNP{JULzNlui}jjtyrVVi-W0F; z9!X^@QI$DagY!cN#26&fvSB*4$Ds^dkQb6gkxKb1gSri)&TBcIHo%B0m3o zC_Ldo7g5b7P9a9XaaJwL;ZE!!5oYxSE>rRNNGw3#CI#5ke}5TkjG`I#9FvrzuX%a; z_VVF{LtYRUrZ}F^kB@nGvUZ4x3_>3=H)Y$^y<4+Q`vVP3dPFu%{F@h8@h8i9Wlz-h zdcDcB8OY|w>8jKhD?3a{P6bo{(De=$MOJetlMT`hJoN?4-M2*xD4nK zO2)_{a@+%E)X}C%oI)<-QsI%Pi5=#3;x>l1E-lK?$3FB@jUZT-{O;}Jh>i$em(a=v zATljzc^6sHF(XiW`gT8*?wW1GpsE(lps9QB!T(Z znpOo+Ttv?#e2gb6f#un7j<-AmM(8Z;;xBd3fjz`lLi4g-aMkpVzB6@nRtmXorn@qh zpBZqb1Z5UbX8s;xIUMjkF;)Jv=#=OmoSH#0EKoBbB|4Qrs1xz;{0qx`F6dsa zP^{Zz%w97!VF326$@=dw5nt8mjh?tx@H@jYam_1^Z%x9}m%JvOXa>2eT!*b`PVC+3dF_d+4vlU7rOxBu>S(ZAGDbCRst zQLZ)n&Q78iaG6Tj|B9cuittEy@CJ>`o;!^j&CVTkmEz~uXCJ&XY241ltb4D-K7U! zs{OJ+obr~yZr(kM7}kH|%o8r9^;bXb21_Dv`_b{1Jl~SK&@op&|I+rm6Y$A5Txu2A zHWZGz#s#`*CxGK_X$6i3(@pnaskpMogV2K9=tv_Y8LXqy4b@RlBlIlp=&dFuIFHS3 zk#hfw+Z6CJ;QK10QYVqrA_c+w$>$GAyRw*~IE_#GK!ZOno(d+u~;dn$!+sH#oI2#l9CaNGzI#^C8#}IcehS zfp*()R%x89)kVpF2jDU4*AUJC)JL`lqsoq&AbbY{l5*DbqTFGM@(=a#(=D!x|5oSF zp~y@|9u?^0f>kR^GYi-Eq0e)MSzdIb#^`_N1`X2W2XnS+`q$QGF4IEkdQ8OZBg$Tp zYo-yR7K>%rs)1>(8x*BE(^GcSEy|n?8>K_58VU2<6H*j@b)e}ZCLOG}y0JNA0w;DBl{`m8> zn@yR)AYA?lUxH6I@V#~j94%PNhLu?;{{G`+oH~k$JvlgWZJc<5_`Q_gil7CzOYV&n z$a1BZZE5RJnBfn;s6O7^ zLME;A%OwMh=c3JZ7q7#>%jr#dIk?G;vN2ba(V>sBn;UQEeQ+O2mX2NH(1!qZtY!@! zNZj01vGO1$&6!^qcZ_%ouR>%6S{0F^6ZfSFeO%ESxG+Y zIg=CD_Oj48b8F~p6ltj#_#{+{!AgE~@C`e-nN5jtmTtz;4~P6?k+VRI{6w|-xfEuf z+C0P@dG$708>a}%r}}s(!1aZoz>=zzI#0I zy5>u1r8){i*6@W7j-?>|(D8DEQtq4@ zvmQ_DmE}2onFoQHNs;qoOsoL{G|+zE_)D5Yj3$*abutpj z*~q88t8B=NiCm+?N?LC7$KQ%1LwHHr`l7_gS*MCkf``FxKz}wUmY+!BIij0U7fkR5 z6F%To={>1uBX5wfmDaQep%PRX^<#r2#F{Z;TYFXT_J&+L$VCY{xenTGtQq%sYNtQUDmJv)|LQ(wz*@c#I0Zs$?)B4nxp<-e!;A=BIVwsCFjF{16k=b#^3t;% z@L5glorWyLW;#Qb#TmfPH5N&0(8oCiwdMm;PiLBPpn9VKJrqzwo|8h{DL3-D@&=8G za)|0;S*oE5f%tpS=L{}0B;%7$_~?3nE$pm07iruE%gxLdF6e5{CZFNy85T(?0iUIdPOJ=B>0UI#on%GK%Otju#fNG z`2%94K9!s=@~Mb=B2bQE9CHAMV$nLwCaAe@n>rgIWn1s!rqJ|C?B5BWCtIR(%Tr$w5P3=p}hu$GfucL{cIi*kPmrO?531N|u` zF)hj4?f3v@V3lQ%AV1K&XlmL6zWctp_t$`gcw;*u<9X0NaWF~Q4_l;g_VY^6d)@Ck z!WPwzacD*^IzK9CZDCh7$$d|t4jltDtKLcJAv=2O`s%D-8WC)#-K<{}>%4fY5(6U` zz!WOIRvh|M;q+mWjI3=|ZU+HDxI(>2hS3H+`9LH?Ir(QmleyhR9Fd|#aI!)(88J&n z6#F}~e+H$S)L_q1_mp`7%(rBhRnt28YdII~Wc`Shq@NG8X1AP`n@wb)t|K`Wo$83t zgyDU5UwOvj7Oe50PY{6BIvo{|x*lh!Yhoh)%uq!eGix^O<=SUs#c z0Fy+T+RU#C1ExY;AK6jd*g@by;X2R#pqT&mT&K`s-&AipGisFBrRYbL85PXKq97Cu zy<|j6!EuGOrEe%9c?>_BG2VMLin86(3^=fFo6vfQZs$r|S>Hc;xP`e%C8vR_ZOUx! zf}B^{Mh;TnWd^avN=X6C$s-*4PDt@YMN~R9c@KwfMVmPN?_AO8T-h+wA^6eZ`eW+jJM0mU zB+aR)vEEF@k%EnJ>Cy$e13a&fmy@%?^1P&xDbq@Wh4~df&z=WWj zqba@%aV#!dRgh2KTFNHU$56vvs(jH#PfC~ig4L=jb1z78V|Gq=A7?#lPKM`9NWEkE zkG&O$sol;=%LiOkAe0Qqr{NaH73-Bh1~2^{L#Nt$He>97!ffr5=(9 zq@W^~eHJe-5Nm+<&5Ez(huersI_px*Vh7+2@YlgpoGe%RtMJCI5Fo6fy`h=Z6I8-$e;>NtlLr$(o zRC)g|u^(=elxa=l;JET|jJ!wMlm7D|l*LZeTN*QgB|IZmWeSp>&vhJI%1t_4^1Iw< z6ZO%#Sp4f{vy885^?8sbrQ>hM&g$jSPr#ib5fM?#9{UzYhh=2@&A44*y_947eNY7G zrks+#V`_MseG1^nN+3PFp3#L@-403qA%K+${(yiXUYK^`4DF-VeL?=N3X+IZMmoNC?O9{dwG2Yzt*F>{p@-euDC}(@t}60b?nM=@MEl%{a5AIF7PeRE zK1wqoR68qt1EAwHJ^19$?+zyMeVNcqecTsfd}-U3K(7gisGf{0+OG{q4nl;q!jScU zWAai|MZ=D%0BO7Q#~uEsxc)WxUamiB%V<{%jGZXAHW*7^nzF?D&I%V)tI$Y{BcT(K zh>`h)+~pn5e4dbPvOOIVYkqyond9iW${^}s0gxE|?@@w;O&){5tjX>%-Ixh)vo!OaWBewy=V zgAQ|2>wpD+&G?1y#dJt|7_FtsEBnKeW|HeUc!(j>@vQcE$6HiTXP z`+bRdL$JUu{1xY4zd#1A;g4U>n(GDpJ#Mn9OL`ADZdsANbjz&VCb&aohlcgL1OrQn zpLcd}Ek7$Wsw7@Tg_wMVKIlPxo6tA-<#2RTwdoU2Fs8~&*yvWVj(RZDKQQd27`FG8 z5U(8vgNIdI=r(0y*mTT`EMP{yV2xpT6PN*Rw^P8jHAAezG++?@ioQPTz(U}th9VP7 zM{ZD!?FhiT0x+uFRmHrQTkgludA9?XQ>^NHbg=ftk8+=tx7ool0(q23!vJrxSuwn$ z=}JM*f! z-BMfkJ17zP43Oy>uAL83cv4O05;EG8dG1qo`)h3aq#^F(877vYLM=bl%>#vt@9jVB z(h!qh^Y`7STtiaNs-MjJ*&!aK)JPcpL`f5p7@#CJ*HXo1lz#(&Y7h!4Y^wwz0@6c46u751!?I)=DbS&s$%~RiXaGDwsJz zWJ1ZX>L5~WSV|Cy&SxdQ11ss^Vnh=zg$99BzEBPza)m)b{?Gxrhlu{D#EvpiJtyAggocJ#d&8m{`;n}B*=5h_$}p`5AB!R zW&v$2va(2R;qSn-7=m?zyVenor{%|bBcP0MZNdQ{NPg3lJI*`IFo6GP)7xYrjY&a5 zP7kMh!*5oqufz2*4HZ4!mh(c6j@hu!r|T@i_3zEVGh^1|--%?!%(cehg2f{QrNO{< zfj3p}8$5w;l2yIxGHCsCCeuYCg!DdJ5sDHyzJ|19@8HLpD`eFot{HzM&5G zjj;LT13FO(EeUJR_xf`yaG(~F5bV3Yf5V@AFLf~=yoz8`z=n1dQD$~8%M=f3V79l= zVbKSYnGmA7NcX=-Zzy5L~^WJOTfq3=M9b(lE})d0CEGEB56Z z+H5VlJ4K-5_Jb^rrJ76}*teu2-H}G>d_p?wfDPyVrt$iyb6tB&dx_2`Iz+lxWI7dg z6)blVoH{N)M_$Xa#x?d0X!oHm-tsbHGLI!*6(@|Fm&VZBn}92)P1;W1cQ(;^xj5wK zisAJO_as?>aD8}Ye!p;73f$f*P6+(|B`)kp821mvjuVRG7pHa1 z`DeT!I@FQ(scnYTr^*=zbC*elT;he71UW5sPz~z4iIwq!v_zoS2*3-~-~F~23rCgDRVS_1!)c){#t$fN^2-A3pcl=se3(J}03B{9iLZW(3p+MViC+_TrQQ}b@ z?iZ&bnzhSurlAtWDjv1NB(m?pw?zU!<)@R>#gFzTW+-s+((Zb}9Td+f5><(?G3O?f zUjeDZv8vOV%>jo;1b}`qC}Po9>zOUivjVqH^E8}Ng{~-=zuU49n}qDmY5Q0Z>+ibX z!+@swy3snyA+jBi>+kpnRN!FE_-_J`%m=>>3asB&J(r;sA(e+tf3k3f&7FB=KI^9%*cYxA% zSxrLO@}NhXkVmVPVnd@-L$3#x1xt{P{8I3mi$Bw?hC%gI%k6xpws+9}=odwXt>hZX zgy2-vSIusm=`(KZ1wTfBq%GstCF&aLu|R*7TN{~?$ST6cSA?JJ%kTNFTg{Yi`uhFm zpqyXDnl*Xsl=v{_X|ssHYW`nb;T?S*km9uTiRyg9%65dThdKxNm*51hO=$4!TIgP{ zPJ4TNF000*kOo+sb#sm8D&>N6UtdD7FWXd;kJW&`b4JH+s((4g85wkXIjQ`M6eS`Hk6L92;-aV?j>(h28kVPmCo%iP=-YS1ob{ zd5Hms??C&%^5W$UehA%iWw{;ih#!B4!^@_p9nIU`_BGGVCCUHXReUHUN{Ao_XxRzs z;)Vo_NQjtx0Jc<)TVy{4o)OW0I}8!mrKJMr23hOY344!vr)F$2u>I<)el-2sP~y>) zRD~RGqVK41)V*R^w&%Tg5ixUcz~;y!x9u4QG9L^YPLwRhPuH?Fii5_Db6p@tpQ~U_ z{LCTtuzt+3MraI9c z)2*Mr=;Uob*f?4zFx0m9K(rz+K?Ccuj~AKzAF&vI%NzS@`&tvOc~EyNiLObtqehL{ z2=8|-ePb@gx{6skwfslr-(a%^MfI|q-&kNC{$3}noU08(p zt$@xfpS42iE=4O!^gJ^_%P5M@y8pS{ae?Nz;IG)e|KrA+A7+Nbe;49Qxh~kdneu1t z3_-QRG1_!y?>t|yY1$4&_)bdlLGLDWT##tNY^d&&nAw6BB?fl=@Z}uZf>N47$>l1V zS1wnSdt89qiNr-1S8nzhDbL70#V_w41E>{6()F_b*;%1)i4pkef|RPOI@RysbpCs3 zG5u22R2}cGhVaHYr7F0~gsLC`jWHVN)R4gU@)W4|g-$+$v8 z*kD0#!-21B{MBAt_At5f_TbgWp8BColXPG zbpp{`AEGauf#V{zU&RSFL-MyVzSVu5zy6u_dK0`{;w^!zx4N%XH9g^OU7Wt9fe8Ph z+yQVYTCV4vCFpx<|T6_cL`J|YasJbSvHd-(p_Y>^kfZZE}s1z=UVH?xtVny-Op zl}Gqsrg9oEg?+lww)e_B|{?k?+}I;e{S1eAF{5_hlkYy7u+_n*89(3N6eyXlj9 zXDQ|PThK6KlsDS@a{`skqTuo-7Gt+@+C+k{l%lXT{c19bV&;oPG`lQglLM_YAzwt| zl*&)oM)?LCU&NIIuup1(+yfpND+`9cqWPmYU%zR5PztZMTfY3dK>^>)`0xYr1*8zW z)fkcH8S&|ntJ$dpYhlFqSjsJ3^a!o9{ZjQ(&)q1nBI4IIZwoP4o@pu3SZl zANXS={=KNB5+!wsExi~E^1KsIby8@}#qy81y2;_sqav5hMz*>7y1cE6RVYX5DzsCtB~V9NN0e)2h+0!SgS|t@ zYO3~~s5_$QcW4$KC*HmPP8i6>gf??-$lPT35~{OA$57k@B)!UB)I05p^cV};Mn%s2 z7&{c)pt}hWe2U7p)@PP~7tsc_G)Wp4beFD~`h|TOPE`D(230E0TO|-|T+eEM{8t`v zTRA%(2%cxBSATf0D3QwIXRF0)qVX9&wToC%(cpOcFqcZ{?60lmPEki;QFrx0(!5fh zUG|hw_PNQ-k~8@Bw7IztYFo&E?AGQ=JBRYTManAr+B}P4^C+{^_R&&3`*w4N)!rbR z+R4n(%u#A$`0c&*J@O7?bfk5(wSDrR{fjjoQwIj^1ZRqHXGCuu%8g;VJMQk!+F+FD zf4A3C5FcK3&PHxbQ~Bq>KZ3ifHoC>U=|3wok`^_kar<6vz-n+ezp1C=!Kre^YQk~H zPv)e7vaF)f$XH5Jf5XhAtSUKe=BK-d&L~UfBef1EtOVtKiBF$#POlONFrQB2=nG@B ziyX~y4ezmxqhT*khcE}eDJ#@c2e+AF&{>u}<9) zS+uJ%eBd@HH;B0j%o22Hlk?MH0Vg9H3*J>B;VJM-^WvKF&9x0i>+7?8uoi74PzUEFT2wy#j~|IYvlYp8 zqx-pg59metYwCfAx)SMQbo;77y6S+4`}FF_3gio1FOp2;KOt1JI**(e*uUb^g6oVO z2h60(eI1ybRVAxWwS@=NeQAcsQ6wzFCUEH7hS3A|d2?foj1_T*_6112r6PPJn3hUz zrr&in1mu6e+wQtC+}`%`eb2@y2zHY5f4xUINkye%b0vwf6rYn0^^uyw6_dwpe4Tav zq^9L_sH=i6f*C%-T5telOvmh#hAOyhnBon#df)VpTRRo!uI$UevUtUg?gFK`sJ8-K zkG>+>QvtQ81DZ9%55|4_Pqd$P5vqlpCybbfab*S#f9JI8EJGPrIVR{drU8`&z?}HS z@3ov-!y!vJef9!~-iDn;;w2IyC@?WtV)SOWD$shsvaTq@uZ0;Pm2oKU3RW z=NqoIrzT0)W8p`UA1Fb~`|XW^MaV{v95WX$WK;z^Z*d}O(HE{aT^)lBeIl671r)3w zimj>mXp+s4yL957s0r^pl||q>#tnm>eBXy(FM#^I2$=S)%hOqONfN-i=5Z0DLiY*k zd>x_;AI=ay5?{3vkqe8-EA7uFlO2V7d>OLCJ>4Ly5B>DJK~GYlBU69cKyOJ?1(LdcL>>N=Jc<#WwUZX=;0YFzDC_@vW^Egj>t-Z{PHHRNO3)X< z^XpLDD!f9WoG!=b z28?A&_O}T7-M{+2iUgKaU~NZ6uINLTC^j|hyv}9s{rkJ#5`)E_#Tqc$F)T4c{CDuj z=Wx*jf2iP~KH(YfT6-IWPLR3~B4$a}(#oDp13Mrp@9zn}RR?#3z#xuoW3^@x>^c`!sOCEjS zYlLDl$DN$4eiBC$ZlHiv1@b1ssf?3kOsjQ<>xNwH5Jfe@l zotkzcGp=FJpe3r}T(U*!U6$$mJD>{ZJNt>;3$dZ&;PloUP_mY(40>ieR)_0_!TsKH zcR}z038}7A7))6U!I*uZ4H0&5e&5@s*>FKYo*9j}n1vH35;tccZC&2eR8g0E@azqH zEEo=Hs3G|Rd!-6lB72VWsjqO^_&NLR7`MWKg|W(jk^Nfe^Q6_|VMO!Ui`k0}Xn13> zqp_ohf+2M~h@IS7m#lYvIZE!<%~=*uiM{W1tXq+h@Q==RYpByM64eogvo_h75PTTg zmzXw24Jg8~4UcMqyADX*gSre1G~YSKu8vQ-Awn+}q^4g}>Ff(-rMuRJwuND#SCCxm zuTj0XB;gl#7K6OK!@Kl?JrsHT7QtPT^&bmbLNsvgXO(v#mc$?KMvUm{Um0eNq#*+M zgPg`}5k4GW1I8#O@O)}%E=0nCo5egD@NoT40>{+dpH<_zZT^e+VBi<%BA7TT`pZ|sesabxGg8wjbvxk8&C{0bJq^Xvb%3Ltz`T1eu;XQRqh9UsQx~2ckFuDXA~O- z>^l!=+O=YKmC?yIGsfLS5(HYQkMs!xgJ*60YmK^g9k_&LfZXb=|A0No8Egnqe{&kt z1xCUEv~OA#rKl!;;$OE@!_|hT>`OY)T3M-+#4h;n-N%!5E`uTQtBWF>t{exI?)Q&+ zt1rh;uhb54paS+{n8`}=Oqu&K$5xtWl3f^@2@>{ufb*n}q?;%K>v50olfp><`vj|O z_MP{g4oN&8|Auyj#B1W-za4A;7H9vwto5*vu^AI%?(&1Icy z7`8XX@E-VXA{`gWdj3^Z*@c~v`T7p^a$mvK+df8yHcm=MQ5;S#2zg+5|^x%m|p{4oJb3h>DJ?``R?H7mKk8#ma* zirsG>xdA6@Gb|v?147je{SFJpBI*`EUs}5bcoTk+1?NlBx$yXlK>sR-ODS}DDYoti zMJ9;5ZfNU>=_Oi{m(h0s?{Z`4of5@meVD#9zRCFt_W!MiTs&&-4Uj!2@ug9Vy9%7{ zx*nVTeU1Aw|3fbAB^MF7;dUBBcj5KV(>HOvkqnb>3`t_6K{0Vu-0tY%voUqprg(2d zy?$iTvUCarcW)Y5Uv=kz{A8Wk&Eq~;sqSxMo;6iiPiA@s9KN5&2r-nIDDG8oxO{if zV!-mU2a8@770cf=b6NUf?N4^yBl!_>p;V=YG^YF0-59QvD9xp_Sm>HQ^?Ro<=gn9t z_ww;(LJUdzt*nGQ!FX_5Q_fqYafcnQvTT4pP2YFwwe=P+lUq^CKg={#Xw=WT>)$oc zEAvvy^9uR%%*q)Y4Mh*WHwZ?BC0X^dy-wwO2ZyL2h`4Hd)%o>Cnhd^r7aVTr!_hXs zQ56f#AJ#4_y*VzwEZ+{Az{g<$qtk<+94bU0T?Cj2lIzL2Bd4@$3>jDho&ryr#If2o zeJb+%Fw@W8ojDyMb3d76gHLnoKHkzrJ8y(cC{Vo8TVrA!1o@a%buYaOecCrqx6en( zhMeo+?ZZ+sa8U3_Lr>GpW1jsbWwS?@{80S)Mi3$LnrKsy&lg|;bwvr6(*vTkqmbbp znieu6+p(|FrC!gjE_j*vmVWbd>TDauidecC)%wN=%O+2x5EQ}Bbs;^oMJq;Qg{Y?8t^=gH~7l6~V6 zFy6456!z-YOs*J_uN+3)mgIa5GG7Fr<+y8xkNZ;@^1zGW@ zJ$q}uWD;%PVmLmmgrrm~WaCV!nyQpBQr_$Sr)=3Hj7vyRc#^X_uh+9$Pj=Qf`qqMk zZ?6r$#Cw0_fzq7fo9q%zXZ96oTrgYy5!cyG%}v%@0&X*(5vW0j9UKLKgfBG^GP(*J zu+8ciL%N`Zsaw>{=@H%<;C@|lPxyI;_PdC;3=*V5%TvqazgCQrve}eh5312v0op6CaO^b_PjpB&-7AoY z?f@#u*|P!aXnxoaHhrg;q&Nfackk>?T*uduw(5WXSNqRr%umcR;hu>UN$FnDlQd#e zQY(xXp#f(K*YGen^3@XvDMbBKXhM1U^T$HBcYe>foo&#d(Jfj%s=jTh&Vn&!Op5QQ zWG-ZbvrQx@7)5zC^9Mr+?p@+ zO~re{rR-J8ZqRnunf36`;5Ow}*XnP?f8h}yB<#6_NGP!G`u*>vA8Eo0_MiUa5g(tF z%}Xpi>pvzlzLc?=1BO=F-sd-k*fVHmCD~`AH)Mt1;$W%C6p_F@M>RP_OpoUz+GG}} zu%bTi5S%{&{sRzE!^f0B(XTjdwQ;X+=(sS&V0tADRmxW7Mp)l`#DjiyWYK_X{l(A# zgHA}%-qZJKw3HD;J7;5Wg^z{CkA>XRxyC?g6~>@DnY-4@5Nq0ltEdpZfkscoj;P$A z+(Gdxm^~usvpqiI*;z1RgIWvC9t2M%P;UQy7@ltvT;*=DYy>oVp*4cjzG*P?<^OOI z&Ij4nne!=v+lzVI;Y2*ipdaQ^Kd25i9xmtmUrxXfh(aLZ8S|xZV=A{ES{zMsh^7#i zDsJ@!o#^Jxlp9YbW@(D~yucHRDml6T)r!tK-=?Ch~Gk2Y5a zJN525w)xL81Dxgj$GUPO#L?U?O>UbEgSTKI_Z<1WQda&Q05h-|_+vs>AGi;!zF3lP zl5ZlfHdD+)#H46513CP~b%G|&tt5_>%Bq_J7r8B2!WGcK8#F5{PzJ~qT7k&v8VYdl z{~C(IAS&pX>aSt10K&$q*qOZ}%cjpwPMBSU6i<>0@fx;0dhJ`DFQ!fnqCNNndFvGu zRo*BQX`0?BxG7_=tMeMGs`FxwWdby#ui)Y1L`huN*hI~Z=cf!;@^GU#8LKs7bV*`y zu>&Hq&n`g;lkPPaHy=pNx?lK;N`R>5TWV+E74Gy}_n)u=klsN{C6r=6T@zucAHVBH z?^_;TXf<{>N~m?Be6Fn-+r_S7RSh8y0uBB9zRuN%RdhHMCh(W1TI(vPONRPOvlQ1T zeA(GfbUu3i-uWe+^Xr*|>L~nQEzp{8v-nctbxOx}=>EdHgD|76Zw_X!^&!$_7w25h z(I-XBNbF;5Q7}xn$pnmj^=OwY#}X!iXJs{cd5Z~|#Zkp7$7+;v_q*rDLw>685XN(f zO86#ZrbuL6OT_Zzfw~~tf-=>Dfgnuk#U&8&ZBfkLrEr+%@jI>bSLYp`u zSF|bw;!U7$S~e#E3UTXuP}_R$?0^BvbToFHKrz4t?y&;<5IJ2u*N@6mfO*-y>LH-f zNOa$FjbGxJtQrn>26R`4IFaFd@O-!I5oZ%GohnaGDGaZ;{D~>T=2h~}AxjWpZ~gcp z0977&y|xbA=<@CS=-f(s?$ffjkL1@CLrw09Pln(`H?mDyi<$d#gx*JewKhAzOLC5n zs;@jsEccPSU)lI{5+oP%j+YwqG&`8B>ZB3x6~$M|b0q|dr%FeJDPwkdxiHAIQ2WvtcL(>)oy%|mT-815PHPHbokjv8gg<)lrPj+0gEyMTY( zwx7QdKspbPr=}A*ISYh5E_d7Fif>vQTpZ-{jmeBZ%WWxQ+rg_&T*9{c&ho0=)oNk& z3A=+CK>Nf?z|Bt(EsSij)MG&JBEtsn$|>2x)1%XqUNpuZE>y=Y#(5*SIL2?KK{1%RBCt@L9u`P7%1X{oZ`lNfieh8G?G< zW%~0vw^J_?+NM?puqcY3O+jNUv`(HPhqep36pG z6y;_eQN}bPfT7)S8^id1G(&?l4D7_o)mGgAvimo3E#t&$DDev5j@H`@W>Zv&v#9T^ zrP|@Dj`(~4(6sgpw@Qzo`H3G5Yom7?poC$~tK}>+An4l(mK2KfEp+R~Z zm8aX;`zNRS92yncFvhMcv`&h94!hu#lfb@xYa#@snNJ{}@6Wf@D%c4B*dB0#*Bg&! zjou5^203N_)DzyUbASz5PB8FVe%&sb?d~U{`}6k()cZ<7!K`yd z3a0ZFN#&0Wh2abu`aJK8zQIt4dlToeij0U9h>Fb-EYEW_psAp$Pf?MP_*27mwH>r6 zfY7-zcZJFQwsEig(a5Zj%LT9P^7=W;H#wo@M*J7wwq7~~4aeZuiQ5@ZynX-FF*R^~ z^y%#r3+ubf7}KC^@A7)IKY|?0efG~?h3hRS>zcefe0vWx3Jx(t+gpUbh$1>U3b%~I z+sc@W>kS&6DdZPM`Y$UBSjH43gxe~RAHVDl*a~w%$6C~q>a3RDTuxi&IO4(2neXZA zWUXvg-GzkcbwZq^%}|^ScBfr-jr@co)9WRZ9Dfl%*WiQCWa#=^`=2tt&NI;27k|%1 zvDfNyQT4^i$>0OU%2~D?oHIjuU+4OQxXe0zg&+U^`NALxs-C( z!||TUw&;wPU#AJQaMFW)3gLT zv4XtWlVB1%w6{ws)acDlwXy>iSW!uv#Y8e7*T%BcMVxKr+FEgf`;L0I(>oGS2i80M zW9mrMSC4{1099!cKc~`uy35|kk{Vw5=BQuMsb=W>Q5cN2nDh>=0}pauasnT=lQ9A( zg0=9%ai=J~@ezY20yh`!+XP)<)z@r#!I31iohA50Sl$baz6;=dQYn>uQtRcxUe?*X zNR#3I+01$>Ppgp@<|_sKviTlNe|M|V3V1THwWCp#l4|Sv`uK{}{jvXh_4m?LCaC9i z;+9%geZlv?*lN(F*l7n+=y2?wISWedMXwVB5f<#9J$#u55;Xnn6eG@l6)EqOL2++3 zVJ?v&4K5IfB1-2AG8A|y{b$evGVBeKgO23WoHSkA8Cgtf#M1~eXD;s|)8q-1x{P2c%i`%wZ zFgYXKv|0mfNBRDHo)X;LqXuoH!`}%7o9z&(OBF!F_&O47KS+;tZqcsih9Wp~KY@)l zj(?SuPY-4Ipn~!g1F}3h!hP%p(Vo+yt`{hV&_1&3=NfU={EDUhF$Y^s~u2C9c~CTksNP;zk10w?{zbpGvPza0!#R+;azR0 zSywfsFplJ|MT^!EsNNx{9@bHzb1SloF-{;26hb~Ssm0bL^u+P#&Y4Ovam2 z@uzLBZt{m!>s_nz3gE>vI3m=u>jy;RXkTtsR2` z(vV=_y;kyILb`8`>3cm=J_#8WrT4T}5gp)Q3odnt_QkhG^-^lli5x>u@GjSb+?@Os z9%|_91N|=fh4$<5hBc4YoFFPAzUt)mQ`(&{qfaK#m zd^#|98W}3`%!clAHiU(KqTM4!Y8uuM)}cMS@dm_L2E__7emR6UvJH<{eBsSL$01Ty zP(dluE;{}`KU)y;wNTj$VMaM8<^ZdOO!aoLW@m$yN-hmq%hutNYSrr&T8V^f+GFrtBY zMX_U+KCgB4;&LWLaI(>aX%ae94Q>kf74o;_7oFU0#s5Nctnn-Q<+ zZ-@_+0TS>!sy#jyWR9|qpmwTU8h5H_zcY5O*X@RVcHUxJ`?*edZPcX)xBD~g)Ia_Bw+uRs(P({lQ4|;!x za)m#DH|B_-y$>o7Dw~(~asV}}GRCIE&@ccm)k1;f^K$E~Ye`LqF-^#3>2vYS8cI-P>~cM|0<@F$zXya9EK z5q_F8F&R}K08>ma_q!M_;U?paxeSOy8;)uI{;qRP+UiF}SmNhg@LP{a6C@tw7(DUb zRS0y*klP9~P5nfIuK>`w&ktV>3h@P@gBae^QTP`GHG`Wp&xG+PaNmX0y_WqZ(jJ=h zP7r34*~1AV`D)nUAH?Apme!v8n%5YUYnU4aAD`sh8cKFdJ$-ZN*F4sVHo6NHbjR~< zKcqnq(DT&X*0`Y_x9^S~6^LFrFY(jn@F`VWc9O=BsU-gI$k%#LNh>PjD)pT5D&OT! zQL3n@&7q0u0bAm3RF&kLN}yMCER^<90eqO#nt_niXf#IDCGRriv@@id+h%^LJX-Gaxu=6u{wtz1D#8h1#*4U)v z2BCiYdxYKkQeZq#2I7@`lbaa6=DBVV?gWju6fJO>By{Gz)~IU-+|qYnbU)chsjPIf zxn;e6&QLCg@m!h|E*e)XR{p$rv~c8=cEGF5p(3x$DJ;}qnGe_P><&;pR13R5N!nj; zuWIpO&fZ-;zdJk0=p#j0+hDa59d*rKCJL9qxn5A3c2Q)}ZWY z@-w*b`6orCvEiA9?U3;}!IXd20LM?LFte*}Hla0{#k=j{NpXtsoA*2AH|S(u<=h{{ zk(PPL6;3jTSmYSH%nvbD5^W0BV)&4L$tm-Z5m#@SXOieg9Oji<%YyK3=Pf?%$PWy3 zInj1tYX3}JXz8ENJ@VR~=G+JK7)_fOG?&$7{xXa(zb3s_*mjQFp7jIj$e z=da|`X=!VJ=hxSFF1!$i1<9G^EdIh!KDqtGJf}nY>{}X(*mk7357K-rTDgHMw0G$Z z3(o6dePPik-hP9vetc=$orp8=2Bo<$udc)wMBT?mjoS3>*-53qR18yQc)wYsVp&rJ zB_K(L(Rv&+R>QWq0QVR@v<_y$k?Zu>?(1E>zXbnflpnavS4X7(@(e_+YnYvsr%N}M zhjHHzkQT~q3p`+6Lq!IFe7F>!xys}yhVT0Pppp_3gltR&<`eG%Ez9KQr;879f+*g( zx-6A!&&Ge9Q`eXq;iUCd4(#Ek-?LUbKWk(Lt?sF>k|5pIT*=3l5q?5J&MP|Z8NMj3 zYTyb7m9ns~R+haN8X)eOVD8PKGj1{WHNXPT;+vqx$_82&+25fF`|G15-p3-ZkmJ1%MQ@+aJ7F1d1{aK`vE-a-Gvh%D|eq ziya#E0F;I4Gfbq#P*f`sY4=s=TQ-2PHW!p)Q5n3lExdx-kkajopg)CS18*|twruGD zPk*ihYyFFlEcQ$X`!ExBo=a#cCS7)Psrd5n9kMPjD9(31F#bpxLFLs^7NEWAEH3_6 z`6ikne2Iy>8EsQ@Pi5|pM=M-psV9W)h@4-M5}{1aZUr!KP}gfCznVIUMzQ=G)u~8Y zjml+FLCkIO>Gm+1uf zT;A<_@Q%1=Za%qDLkcdmcL|a^D4S7O2Aiq$$$W0+UpY41V8bcY=zc@#Z%0bXBY^aXG5JbSt8?36V>YmWoRx$_T>@3OiT z^DvU>_fGL9W`E~$7cP2J4z4grkq&)+{29mOWZG9!?zon{lX;Yl=8X~i1q)UQj{X1+2*d3@uhT)KnO~q49^wXGocBZPtv3sH_RZ}4^mUGe@r#h4k|3%aGsuN6!bzK zAe4)x28Q*Z@!FZx=sfg#T?54&ok&|5h_A@D4og z1lPn5|9fwN9oqiS_8~Ar{1+EryABPx(fZ}x-DG%<128cL>#HOUs#n&|TQBls6rXF5 zkWsA=DHGJ00Px1p=)2)t;Z2RfTNXD=I}K^lG~u-P)r~iYF$h&o+1y`VnW{&Fw~^OX z;!BGB?N)FoiES22!`Fw7YH6Iwm|^Af*K4*}!~IAJirPd@QH47IG<#Af8~#~;T~&$w zc;x(5&|A=C?<6&IY02Fg#{+$XQMhyiX=a7CKS?3$pCava%W-YjP3ViCH z=Qzz=_o6T7KNls7{k`K8v<*E#@I52|YPrUSuMCJSjPSvyFTDEOuAM6}12`e<IV2Okv(@hO=lg{&wQ! zw!@j6bw{Ks7{PyINHyzqn4=bK z_gDZvxenm!D97DC5kEs-)<0M-&f!l=23oyx16-)aFRH9xmWi%(QVV~<5Jn2Gp2RxI zSXyPLLunQ^f#hEhK>cEJ1Ne6)ZQ?&hh7$Ol1e!R~OS?1L%C7F^a-I?K^%On$8>f1b zXz4wB*ZNJJL9p<(M#I<(4CYtpjb7wi+ z+_C=y>oQI1f||J&au28vBC?RITz?3Wc$A^$=Pj~7fr$WH|8V}oBt9!z;CDhbA8UL) zGDsv*%87RR%X^`Yb_Nz)yazas&r>(p z7aoKd5Du-Nr7Kl@Ojm;YaM>(Z6&PaGYh!h>Ii(5@p%$mJ;hvB`t79~`6X$1Z{+AP0eygjqMj# zr9tW2wI_!)Ukd?N1n+~j-cPS%2XZW5sVc$)Hdo2DNj?{1?Z~L2p&Q_D9*k}<983M1AZsJ6hT-mSq4F% zMt_|xFdieov}d%~=@03oTz6gAmj?o2>=y@oGInG{3ZxBOanYM$RDP^a;CC|fV$ItT zP+J1W5rvfcB+iX*huduP<)PEghJ3h`3VpBnB(qiCD$(T+^?-T#KV~4!cLK)E(bOkn zvOt%Dk0_Ud`1hd>uE%$Xg6uQnP>#;ODfDlO%TrMrTYgo@m5S$6X(jdxG+ujo@j83l zTo?3R5#L>FIKa1^fDUmq(?onNI&{YYVTB7-$fLmeh#tT^9J02}yoyZp`=hFC9;=Y){1kEHNwfb`0QByfd;u$4h4TnSe~wHkBETNljvi61s)8?oXb(vD$s| z$VC+NVDi?K=En#`oafD!7($Jt`RT@X%3MO#YXb9az9HFtvJO5n6ueK2W#4Z}V zNK{>gYwT);b6cD^#oOF;6~_PTa0~Okpz_$oxCW?~)h9MVSzl}j5a-a_Qkr-Z*tr61 zG2@&9e)%!kmx(K2H8dDXm*;K@ z9HmLS=&KlSbdN32(?pX;IZi*7sQRGVyVk$a)3QBz`ia$Eu7KwF3Tgk}{WsxXpRcW? zaf|7*!q3y5Nftr|DZ<3hHhi|1$V|IZYjqGtvIqgBWjNTx58hX%v>_)uO?n|nm#Qr< z_g4+86i>05zXu(4YLJB*_iFVG%^}`bR$8yTd~DC88jV9BriadeLf2CnxYPQ_Ic9L@ zAF*IGdlj^Xnv^_bfJil=gND!r+&CN^lEp%T0p9pBIc^O2@oFpzjP@@a`LtyDXfW>^qx|pTAo>q-!sE*mCpc zD%?c|)E9UKn}xEw`gwJ@UKdj@7gLMb6A62d0nWaC?F=CL2KC{9ZGWr?fKtjkfcxCl z3~a@;m3g8H9~_Gwx)S(g1sEUqC$Fz_{`~LK&?(fZ-zkKyj4eihXp&aKm!%K;@?Pg9 zgvsi=nvA=ymIpyyX@F)6d?-?#{1kKDzApVzz znUvp%2ZKJe1*@*@>mvry{)ipMIp=XD0Px|fx97)8O_!t--iH{mAXYT@T zu{h#$f_HrL+Z{zxON~v_$*NWYgf`jpK~lXQ*U$0YK%!(N+^~mYL_wpuU}-KBvFCR? z&7o3IKK~*1I9Z`^qjWYN9fv{+Y^BU%ErAE9fJfyIC^gdoY zg-nJ4ziTIPSmz70W__hQ>sbJ1u|mxpVGW?aQ^_q*fqoU#4aDS%7$j8hpGv6SGjnNi8o(nF|UGjo@w6rnnXG<4>{zMTcx>~3aKf)HM_N=mBLOYXGP#_ zA^wSbl7Kfu52w0^)D! znRMO;LHTuN$?*+Ws9*lfDdeNfBgZw-^Fi1pffQMl05}WYbIiZ>r%CzuL%Ct6KVao@ zUfM0f^|`xG&ZJ+&Ok@X>VgAyK@GVT`nm^UTxj$Ik>Vx z-{6HlwW>z`@sY+kw=MX*Z*VPOGG#LWqiKZ`IX@Qsh*EBcIS!EJ%cwz)ddfBebBz1_ z5wuKD)d>FnriD`~WtW>*+rYGMFTu!&)~0XcPbJ8wumQb;(U12i@tgFfv zK7*8%Ge310=Ht0B2SsVaIB(t>pRRNgWVE5(bbDTH;(-TK`jTVj*iYXXYTTC*szN@| z>Ah)Gk9rVn==)ay{MsSS^|b@Vazn;)qv&X8SZZt-pA7$p6dypoH9e4{juY`yN0%=rRAZd>Z*1>+-sQ*3lxf&_TxEjMh z8%_EuRn0VSUzGH>g+9uw>cr{Np)#PZDJk8<=9uE=V2$V%Db9_%6;pL?*VQVmMDj2OAkwpFe%=G!Lw^FP;VSUh~F}W z#r?6gR%u)orHpj+s3!1**pK)_Kf8zoM3o)HAv=FYXw5A7hU8D9eq8)22S^;3MSz0i{I{ao8zC={pYzY2gT@P4#lr}x)*!k6a@+Qt?Tz&U zyEdFuBP7Suwq&qQKjeN4feHtnvbi%-`%^inGyVn`miHQ5{uul-PMLV6q;o*TKBy~~ zyndbgfJajR77r2N77u~p1;pW16?Xn%(6}v0oDqHQNyJUhyaZTFA&mHXkGXJ5SABr+)bB6tAl@--CJqN8u39yX{yr!f^ z)P^6)6)-po_?s5!g5KG`l;m~yr=R&0SBCT8KIhNh`6-u*Ee!n}m8%d-((Ry3g>BKd zFO-EH*?#fZnv1VEkPK@+YwX{`i>g;`1ShJqk%cUB8sXXI`zmKAO8?tArQD8D-2ghT zUI>TL-_7@T%rrBz1Ru|q5K2bbpw+}$ILl6t0~(*QnC{6Owv^ytD*{ZZOd;w^8&eC*rH zG-LzfLf5jln+An(E|ZpjI6fJ>w{IS71!DMTHu_(mwodzbzwZElN*~DjM1sCVxkT^I z>mcho8H(f>tH#7M+!E$CMv2eNtwR^ftaBOaV!{t=wz!8+=@$w;-K`qM_HOu+kNhi) zKGX#_2F*5~>Sf2#JM18vCO^h@OrS~oMAEoE z_Vt;DekAh`;GRRg+i$+pO3esLX--*2E>66T>8$+@tnhH$=28^VaITNjKOk033t>Mu zG~Df$xU9%TyR%YQX~nzFk^U|IpVQ(4DZ;n6aZHPSIz(^7sYc zl8HXW`QB;N0n|3g)+VltF`TAB{5YN8Vdwl*7_<@{euzY85%|+d?Jc+GB<&(o`M+oE zc+qHGmIyfSyPnUxN9F&@sCU2VO6i5apnfYYv5W?!8%`;h!tK*p=LNBnK}kI6NnePVCMX2GvM(tJ`hcaGVQ%u+gEfG?z$Hj$IJ`nsHgT|= zm^Hc*CE!RbnxV2`qq@djZjHqxDmyFDEIu-LG8hshvHQny>d!FZYlBpJ+~h_aJUaihqm>qJy>B^Ok17 z|4_XmVW;q&_d~70=TGNO#04A=6vq&0e~^EMcpKK(Uj^H|U#}Oot73Q;u?Iz|D6@mX zWL1=def#FdCy)QwMZ_s-$#FLr#XL^6cVknkPb#-8L|0H!=@SL$>c5oEfG)nNNf+|& z@;!%ANAXM%o2#eY7igX(Ec67UjwD?Kp4p;T5H*X}5eP}li*!uNu`PePkDj82Rb>tt zAx*byNpR5)gV)^YxORoRItocQS?tEfGytVGQalC#(XmduI%w?LDtG7)N+~R9jQptT zt>(0y1}QYb4VnLA?@It`+P?qm-s(0_G|w`m(mY6m1}Zb9i4aN=%_7aCS)oCLic*GB zqEebni6}(L(4YtrN=nN9uYKw!8Qy!}_kQpH9`7E@IrrStIcKlWn)cdjpR*@H0N&kq z&x8|Yc0(OycCsCpx0kdUnIn9#sF-aW{6YO%uG zni>{~bNYrUFPY1v^Ky1nzRx@>{>*V8+E!UMyLV7Cv1Nk4wgQ zMH+dHO-;PBf6I2E1JyoHtB*-V9m!c-;xmV4z$@-kldPMpC-3=NG4CIGzMBvs{(OSo z!uF48S943YHuYvpoPVq9t?$5*@K_VIi&%C{}dXA7mb5oEIGbLCyx+dp9 zX;zHaDc!v(+>?zC*_b=M3bM++qqwo#gpwLQyFku&tV^Ow)rx)BKDaM$c4sV9yu<4u z**oEZ=En)aep#oxKO&!Ln<8W`i_lHGQZ%zIz`SN|Cc}BFmAivZCx1HBj;s*pj1tiY zYMmQx_>1`&7N;oPwXtss_x5DCaQMT1Be^(^nRYDKc%ta*$BWj}opnXWcmlaE*Cpxp z>xru7T}@fI*fL_pV#dbO>FxtPuP@48xu1W%Nis1%zCbE6B8Y7zKA@kaK9?k|YTy13K3cOo z1s+ZgcbmlTzMF2fT&nY&k_o;-*LAwqzOJ{oc{|Uv#L8z(Yfq&>WFyC=abcDLw%oHA zrY%fj=gd32c*#9xhleM87qu+sw8(n6)U`X{ZJ$(2V46qrIcWXT&)ho zu1Sk!67>%+z2g6b(qG2WpWW$uYWu6%30G53zl*AB+T&`m@k&{3Kb6Q!VAIsHlP z)*bS78CGxYq*_k$o0hX_&0CSkptDL&EU@d)VbiLuHm5dTd$lvu<=I=2B+)IxyF`_0 zp3#PI8dl^cW;X8SKHq4@aFZvMC*N4+Ufz~@mp7bP>6$XRi%*?YH?~dY)fb*~D!Y@=W9v$8gh^1d;)a9i(zkMOWix8aGXE@hcy=evL!xiZ(1+=llD5O^MQ+94S}kcPCl9jJEjF>d2qm?0?bN zSxBX`PE*=1+vzcDym)(S!5FJa%{MzlIK=L=v1K_b7dCHPxLUWxzm}gSRxD{l!Ux6H z+#Bpwjx19rUtoIfH~G_ZKZTDc7Yx`p(v#D7-_v#xywp9VeS160Q{^>hKCqS+rVqp% zvd#Kn7CSDBG{$S!_Ux{g;VGVpq0jR;RwOwEzl_+$P%k0QdukiK$KjGAW6qmAdf|MG zU6onz!Md@!7mi)qFlIh&Wh+gkcz>{zMK8sse#1fMdbY&wj?0PNF$twPJyJ^&r=N7t z*c?_^=@k}~8@58ykxkcw28K~6TLYda*i2ij;+T8XLEp2|UCcf|1Ty5O@4KW zscll)<-{aLM-KV^EB7yEJ6+uOx^_cAQI6G`U!c7 z=~|k(h^ZRS3&Xt4g-+w?UNe2v+05~5%ze`&8aIuZa@!1+>>w*O%sLqPmi?u#driD| zBVF6FJy{8x0@p)TS4<}Vv852}Y>zbFeqC&q)ism%T{XQe;r@OHL~luMX$>#j?%Q8a zBq%M88i>5G{6GuuZ9mmHP`P`$TyxN@Eqo{L(NEgP;paC_VEzQ*=bqBE<4#->-JL;-Wr%oG6 zN=5NmoLnrrFn;s8mK%2tkdJxK=Sl4dED*U;s-7>Ix?$=Dm=|4sv3yO-O1bgl`W`(k zIO{)o!$Zj<=Y^YRDH@fPB;0_Ki?W#FhG(62@|GnwhMk@C5q0u1T3KSrn-_}jGno-o ze*D-e8-K+ntB*SuYM%27zh8e-D(zK-p(1C$*}b5qEZz8{OXF#*Quov6EY-JFZ=G`0 zPsy3-rBmUjI~{2enhIS$9lADCyfU72()*t}oum@;^tN#736H3gGL12zISYAD_UjUl zYnk4g`2KDBMHr?%J8rfQ$rit_?1QA$rE$EzesK@Z?tTDH&~xgdS&JeAJNL2D!9z5g zPcl$6orGMoC0n%)C@Qq+9qpZcV+YTQz}|@29GAAzq>f@;s`M_xw)W>hrag&IdC2 z-bgIyl6a=xx80BYi6>a}T^8ec`K>x){YnafuMfCfJ?oSvuO94}yZWf3YHLIE2j7kj zWpjMa&*?vBBVFZ?o_RMdQ^I!h=2aCgM;{e_cyT{dLR`tgMtA%h&R1UBSfYbdn!P*2 z!!8y%CHX663E8U6LlaL=HlDuv&^z)=x>arY&TNr0!abr(JQ!MdB(H9!cbk|fpQOyi zyD7G_`tmZHPT$wU@jhb0CkkJ`Uu~w>b6~*>QJ9%)QA-OWvUFvkYUqkcZ{Ugjo| zZS=8Hj<)0O>~LI?ZY~_MY1bC*BFTkG3NM0L_qEw_9$?W@bEnNXuC%HCxb&MFZ7pkj zyEIy-Dm7NS2AsO>;E+LgGUlXpbuNdP>|5sWG?iQ8(~YD1X$(W`GPr1~%=hqAnP>Y} z-anV;9Js)ul)v}+I?FRYw{Op^lqi)-*Z1SvW+uCeK1XAL+RYHUINf(uO4T!8hxA!x z+TW=TDti#LiPFGuf9C2lEPVNfvyb2C&dEFTE~v~%zn!8(8p|s-;Y`Y;S7pUDs`bTV zs{AAKp7a|fE5;pF)E#@|LxJ{M%FL;{-fJ~p(YvmF@}m5RUbpJ*#1kb9vdy*b(JnUA zM`vq9 zB$m&8&eWG+Wu6pl@Mu$s$5G9>K94h}j7bW3;#+#Mph&)T%^Kh6c&5!Rk7=*%)3v(E zztNHP-kOg*^=k~GE)~9)-hR{V@s0MS^Fw;8rRBpBYwF%zk%nb8_jjikB=cYORJ7F` z%OZ-vTvc-bbN zJ`Bs_RVU6Jf~8rt=(TVEu{R%}P$SF5s8I6JUh#3`_N}-gvvB!(J+7H!Rd4ikXqy{m ztct2UTRDF2;qj~XS$fNU zj;7Q)OPA!M5)}`W7Bw*RD9hChu=c-s^f2GAm%XZIdix>zmZS5=a zqx+EVX8t58XmxRqpGSuJ`Zr6}oPxz(3YT_#IM{Ni>EOc$EDf~0rxPxV=U-i3!1gY? zqxPcirnnlB!wgZMEbfRq@ZWo19u~;ixBODX%^uwcCzA5oR`D-kx@wZ z0}S`3krnzTH7?i|GNtEz+zd|a`;!XHr zFPEjqK2|+4XTFx7*^utD)F$R^J-uMcR#gVe6D(FNGq~rBui*4r78Wpm_O9_kdACgO zGgK9r?5HZRF^I97r0x;e-6J2H^U1H`WDHmAGAIUX>E)}Kk`T32H|epbFY|c)#7M?% zXD6rWUz)dYz}mpO+nuwtn=(|1pg?ngFqqW0xM_9bw(&pDapEeMyi65}& zUTgUJbtDtt;VnMB0f?zbN~|v8F@;5MQqX+5gB4mEYLy&gbH|n3w7zloq31KDWoebV z4H4?CyDOhLhso{}u<1~ViPMy*i;X?d9#Ro?s%4yo{(6&o(^Io-G9tWp@wmvfT~BXw zEAzSC74czwg=1%E5yMmd(|H;UCiY1lN2h zw^SjfH|;O&d)iscSEcMY#h;pX%XD3kkHo=N7MnBI?Np2du1C^D7+Xp26~AkjzL?TN zd(1|Q#^nB4i`l{N6a|gNAhmU4_xUD`XM#^P7(d+7=;~bEw76RG+=7ik^Vp&nG#(%| z7Eau_^xb5i6$6F)?h^G?^3S>jlk3PcTNkw+e4I3QGs(=Drr7A>J5l3${|vor zeiaFkIuqP)Uw+*~N_xwk6j=Epp*ctY`r+OOt9kX_#AcsuQr2aEP|@7Tb97%_yVdnA z**XpbG2MEMjbk5aLk~{0(nq$3zm=^@FhQMv^R8ws29G1O%bFinDZ5v{3b`oO(bw^2 zYm>k;(Ix@LL#}(z>9>?Qb!FM-2Oinc9dd-(MSrO)N%#>I(ia*OO2&Fn)*D;icQzo& zT6da#44q?Rf9<}TrfIUny7OH#O#_QTFI<9r6KFhLlr_c61n#JmG2LCG-h24|`lI=& zW|>>&h!;Bd=RGU2882{f*Ld-*uUcv363zw~ui8eZA-}s-tJU)=bHyu?=J8C?>=eNS zQCE-0&$*K8o_B2#G1h!8_v-9R1>+kvS~pe)ZSu3v%6@+umUHI}-tB!RdCN%c`l&Tb zlrv+e6+1ZJYB*e6*SEI0rMmpCWlQgyK-HUq4s3KSzOW1&&^tLFFDq2r44qob<$7K$ zf(N7Pu`0GIsU_YyHuDADi7ui&G9(XDnG3m(^FYb4{5^pN&u9m*VzsuBcddt|5ddcZ*rwu~e9UGQ5Z#nvA z_3i;4zmNloOm?fb=-=x#skvBguyDz<7QMAi)2F?WLAPu+tZLt|f#hcC*2;K5NbAP+ z-3zm)I_sX#kC?7%@BV3V#iQtM+YYsgv)bM9yB%I3lbvZc%WNL3Uq-{T{RL z-M2?4X&e&QIEpGn#`80q)x5|&5y!yBkw0tgX8zQqQ-*!C-t`soOv2}Nb=8=2xN<1A zTJhR6c~Pi&V8fHD8Q%!u#5Vvy!YSIn{P|s&pu8?5nv?D`$t^T~B*> zyM&;_ta(dBXmzBJKfPPDSblrmhAGfP* zk9QOch&V8??#t(=Yl}~h)@c;>OtLIp*TZD|QTqArGg$%CRF1G4YpSZnZ z+AZ8W2bNBYy64_~i6wl>#`+T$>&vI4JmyJpf)^ms?lLoUT+MT5j?>-bCCd(O~Si*DLs&rvbeYFx|NXOVgHES{?+i{-cQEMEJP zzGS6NW59-j#^n#(vU9WjS2@zwhG%eCO((Tz%u#L8Q0P!AQSP8EiMp|i#_0~j7Rj@X zp1#diOdU%f4Ln*_rhV(faqn?w=WFCB{q~qv2`d>6y&1FTc5fSd^!=iN`nx(oAr;aqCd>#@eYECO8a$@e?SAMA zcnXm9-9^$@7apTKRs}DBpSJSil{s5B^zsX*?0l+e7|gmi->#zCM}FYU-uJ=DPb8e_ zrfjX<;FMWg$Tq|FNC_! z^{jaLCa3V)IatDr4(xm9#uNCMlA*R}r&?gsqLp6fQdE=Ro{ntCVBk^+lm zg)J}X!PJXt;wimS5@*j&@p4!isF2KbN`>~mtIVqaO|KA5z2kEvkA12+Mp_sH@4V}c zmjA%JIYLj1QK>4sVk+bA?Un+9CeoYga)=b2Wa?| z3x7IYHib(8Dnu9$-=06VGppKH8Ev`egL(qvUvE9rk^VkyBc()Que0v@$35=b6ID!W zRL9)AAe^Ur*R}hFxd?^rMgH@eWF7v!PUlH^to<&)=`Oj< z^xc!m$zskoIf71KoK0_UaJh9!migQ;W+TUUSuSIP(zaHy#<3SJPJd?7#IpR!&N+SZ zi?Vl28Iv<_MqXe`?%~OM>ckb@UhlnRJauf5Z{r~wmxs$Ot6ep-x1U~5Vu}!FYRaKU zpumGw)_O;u)^va74A9}v3E7Sos%UaF2w#p|Hlcvyri?;PgM z%)2bm%Ie0DIsf+LX*1I=b)UV>8pL-zjqMPoSFjr-> z+Lm>j)%363TpeW+b|LU%{=8`}({24c@|HZklAP|buYWyI9G^Mndh{)^j7e5dF)UfnsyT-yA5q zvRYhTasKk4hjCUZZjKi^Vrs)54sa)Q_;}oUUX`ru5mH4ndD7h@4==ZW46o~sW4~Ef zRwCa-`KaR{x-q3*i}~8itT#Tf1MtM2ZPCs8DrH$F53{cCZ&BiS*mzd`TxsB)l1Wvm z??Y2|_C!xB;g)|DND)rBgP&S!a74W_;>^`!8ntyNKB^ISXcvI4}_>C?lef5-T zi>>6>&R%cIK1L{g2#Hk^xNf1XdBZ|aC&IZk_{4zB9J%%l@y=3SuiQJkTqod?m{OMM zk2Loz=*){>(*LeI%EMCDrKHyX1#5QSgH;){H=Fv};9F~5(cUHVd4&uOpb6W*Jj?i+t3KTnOQDrM>pUfzfHKj`&$F{++w;m#@0XyRMG6Z0#PmzJ0FI*9~0x zQ)Odht=RXJT~OMiJ2kie%^TCoW|vP@7cPE?cpy=^?*Z-RXui$7shRI0@~m@LnZcB< zIF6hJ@2{5h?|t8m7vDsc^w^r~YgsNP#N);9qnA&=_8UBW%I9~S#$(ESf&T7~4QdYK zUt~%gUGtWlHzV20@f{;8f4}2&5#!=C+9*Alb%)JEE-)0FW)afud!f83JuCE`SGsyyb!f(6_hVL} z3*W5kh^NmA^3HCb*JGnl9sB6Id$L}kMR0&!A6sR>5x0FuY^?2z)>>2ctbq@ueD3P=h+7jf4Gj5Q($7^D)C7LWSgB*YYrHWl+887<@9zFM=BbS3 zv`^yAovlkItN0Y(NC?XcMhD6YKCRYuT$fhcT9;2J_gs)h#?La4R;!vO!fc<+i85hZ z!KdOb)oyH?s!6`}yY9y5u|A)|U@4lt@st)#wD_FE6Bzj~uHHrKYpMC-V%bVhmjiR? zOM3c*ZbyfSQ&J;^X{s0ZxK%GU){ZzM(M=;BgwMG z<6W!GPTILhN>{< z7H*6$JTGNeWEV+lAOAqJc9xCiD*qZ!y8cRrgm>~Rc{QK1?#&O*EXqvE)Uqsp(2%o> zB5ksLM;YS)MN+p-X?`22ZR$mhh!>lE>F>J;Z3vkEaMq;cggYt12U4`{uit7CY%8i! zW$$!sJ5!a#CUC+u-##PkUEI~xw;lBjOOc0y*&B}b8s3=A(-=?~we(KLQUQ`VgDSHnlVEDRDr3cCVXOWg zFTQK0yE}C?x7=diwig~6xnA2yjgMR8)PB{Mt&NpzF_s>MZ)qOMO_SUb=Do%%3^e=< zbDKd^?}Y)oaQTp$C~m&QlXO)LGsUKPlgGqfnGun9d)a-r`sn?Qv$v1al?WX>(J4ZK zX@7Qnm{$rTw>mcIF~LNXcB<6gzxtCXzsT-AhF4D3?@`6eic;tI>L23=f(^O#vqK=9 zwP&J_yS-bfY}!P(u~X;Mo+>lsb>nr3u;?$7o7H-vX<2iCdIl%$!J{eNGB(mD4lvi6 z=544owQ+3fNzJ6~ZKH(swHcH(CwI_pJum;zKWxR+Ui#kBc0L+ErcZ1An40QtOmD~% zx))Dry%!&>ebGNxoBZ-M1M8Vmmb|O)yYjBuWUijFA(Px0)*P*_%RXzDFsWWHZ9SzU znPU^pQmb-v8_CN0iqOTy#B7M0udmm=(^QCgG4Ryn4XX>=8`glsYV^Bz+}FLe^@GSx zosgn%_lByE{BEYoE2`&(K1^5QYty0E(999NUQb)a5L%qTR(4&jRN-_WK%- zLpQ~g=DV{mdAM3P6n|Xfaaf>Yj*{MTQr{b4-?ELh9}?STZ5PklU2&8&>qhvyx$D>` zf4FkmwDXY5l25%C*6MCra$AJ|0%Mr9$}2ErTWNQLj&b0WNzv$(&=QR=LK8!~5PCmD@{Yn%ZyScRFrAsZpq>{w!>#*$c*T zi~&BK3!UHY8 zH@UGce=&IueY@)%dIdFp^VQ^qas0DaxQRSZ5Er>_`c$`T>{*$@L=OwP9@;v=9$FXn zO)odHQ>q>6HYMI_dj8DoMh%x%%fkm+ExH`f^S3Z=J8#IU5V4$+x?p86IBjMFsn$_v z+Ud`%{hIVA>M0pd2=#Q~T6PjAuhytZCd3JblX}hf%(@+rEXDT#* z!_?g7Wy;SN`1kqWs#z*5JHaf%}D9 z$GgO>PGH#M9??>^^7d5L)I+JRid#P+3x!Y9FE_89F;?u!DHGA73k+V6H#*E0NoJUL zlvd>sd7kd&}fjQh^C)A ze1*v=raj~VJ^fv`B&nkevD!8HcJD*nozw4V_NdwJnkAB`$k55rW`1=#>l%^j@u%6} z&`N%w*&NWendZ_0(xqzeo!0Hg`X}5uHh&|j-gdul-hONOy2|cwOHvG-9Fw(@%%SY1 zEAuZn%~jYGd+z$Ox-DmPKFRh^7A?{FRLC!nmxV?7OA12sYCP{d)jPaOJ$T0S39p4< zM_Y@30(Yp*ez}@?x*SevlnSYfk&Bx6)`_+8G3%upgi{WNaXR*nkuYc6to&>Rt#goF z%#^D+EU9A#QpX(d?9#4bugcp~UzN9Ts-}FZi(2cA8;SeX`F61LUY|UT@#M@s50>pL z6L!v1y+YPrtGl4dtD(&+Vxn8H%4E^z6$iLy>NZ5GKe=(bMV2xJ7RvfNbn|Y8T;8vh zf|rxsQog-!VDA2syRh)A-}Im(?~%wE3#AFBN~I^CPGLwrSG0)ccu8&Dr&W&IB*LU; z2~V6AmG2dhZ+0x`p!5X8b{Pk@vV}(k7O9v%nJ3yb&+PrFSl{Ez2U-@mJSw$V>gMap zJ<0WWXSS9shkV}-<%H(B*Tr9dJlS1y@k$bXV`fv!4tToRO^Iaku1C{6+wW!G5wqNA zb2rfJb=>nutNRb9JKfDJRCRd$>#)kH53bhVhox@`N9~o|3&iNGY|;{)TpwnbAAaHi>gtl*G&?r|RLT)cENxO_U* z6@Jh^!;PhQ!Rs1my_B!DOh#!ji#&>-WxOo=$T?5t)}3>; zEnU!Rf5ZOEEBXUD^%;*n3_7*TZhdu2LW^*l&+b8}hM(bXqhFsAj91vse-FIpBl!@mUOQ@QiPiBYL2q7umBt!a7-|<}Bw0?c zPi!%x>oi(-^69#cT+xFZVx?@V#i41cW~Qn@v(l<%t1(M~sS*pP|HkXFP7{H<`;8&!gaO>vbL;06E4OkVkR1{%E>G~4a8%1VK`f(12C$1Y8q0c@{JYW5$r&;G8F6=CH38DtX%@*SIhEM z%i38du8nzwW-mJ?qgq=H?xss+^1HcjVOeX^>Q&Q~);3SsoO`?;w_kHt=#rzMeHmP9 zLIhT>tbV>2%8m`(&g}HOB`2<(y7e8Tvjdq54hg0x3hp0>wkWH7u*JOkg_XIb;}Te& z_NW@gN#5Ao#TSLg8D3SA_V)R=|k#>yS4usjls$d_|o)=-#9 z+mI>OlGFC&xUWC^#-rra-Dmcy-B!t-Z4*@{-EmifqR$>8Z0!7O*1PdrxV7n@Tj9n3 z-7}y5tR|S-WX*MCe$+Ix7h^5YS>c7`{`AT9%2*5OBT}ibv=**eXBvNBP&7y63@qz~ z*VWs=ax=B!+(w)7Pj!>>R$pPiZaY!={M|BGXtbQY8{QYOnHXL(j&;=5tV>uG7}IDG z{)|gIr^&T+JS?eaE+;%yN$1mQX5AE~d9iDM>X9>%(HbK6%GL6oggxKAC8c5Whep8==Kad|# z09^A3?9T<-gZIJr)vz`KBK`*M*@%IGK^ne`0EvE&;JiW<3WX6^L=VT%!}(}|{zs1e zTz>{UqyZWS{HOxnpudqm!L|BE5#INsd*~l|Jlu01&@VYEfbxLyfwll~ z0R3EG{tvAI-p~LU055uge#HBq>9+xBE0zHoOuzC6`+Xo`kb$~i2!AZg*cM=a@GHYl z!^Ffyv>$w=H}C@M#m}VYKWa-9$UHRn*Tx@%k8J?N06f5pl7AZhaC>Mp{RQ_38CZHH z9Q*wDfu8A*9|V2SgY6f8n7=f%Utu&7vp;-3utE6mK`s$12P@(g;za`D0*Ft94{?s= zL=+|pq1X7B|KZ%Pp`8n{%){UQ2mcTUhHL<^h7|H6o?@O;`Ht~npfDgNRwl&8$wpwq z&Bu)}aq)5?c5ZgW%*u@D8R&=cqW{;K(Ck^Le9t2ry?AcMsQ#4(bx zl89pr2cg&BlLHc&geJ&OApX|U#S&RMTOtc53u0~MVuc8D9b99%&|Rd9#3qRmGWNZ5 zj%Z6Nw2$pSyH7>@%QfWqh#kPM@DV&CbeE2v4k?1pEp}XtjFuRo$tsi4Si!M~g^h)< z#}q~iVq|6{>;S(gKay6JMtUZC$kf3USqU8NSsa--m>@+RMZ%`T-x2$Nw8k=neK)ob*dAa^2?D)dv}O^)SW~Zq z&x3tCw)a%*@fu%KUQ?ck1Mso`W7a%ERsqfi>R%jQtZE%YJ zKBGB6?FWK>ECfEk8Ho!kCo7t1F%!wC%OElxnZN+YWm1Y#X!`u=XwLFEXrb*wG$`!O0ix1n0{?NA3&Hv@UW1awS z_+Wj7ialOqKC9}hA|+iVVjF+Q`s^L+jSiF?KuK4U(4nh`(4lKUu*Tox&c~shnLCj~ zkONZIRVMrb#+Zej1!>OFBTP215*fptZ%B1c!dXb zRA-(Jq4Ph&eY6}TRe*f}+pz8UHo`C9^J1K4Se~bHfGHNme?8{q3=pMv`d^H$9x`W5*4xbH9$@9%A6`3Oo4LYlKRhjR_sH{gCK_64}V zifzfr_Tl#8c2nDrw|~VC@Bw4b3%nT~{{=n}u@>eh10w@s=VC{jqBaqFJQ~+O@*T^7 z$6k-&SRVUrj0ukCv7X|1KwL_kzzX*rv9G7v{O@5;-H&YnKHg-F36YP&ZNRw-+$X?( z2KNo|z7aBj>kznb`+wyR@B`a^Qy@GRPw)rwBS=A80gd4wL+}ji(I3J2N5>w#bP#FJ z)&2qth{=$Op2~245O43y+WEV&AC>`#4N|2gV!Q8=O1DwhYS|wT-ZT1*8o0 z%fb(1el!9C0=O0?j>;FzGaRE)^#$9}-KTb=Kjia&;1943aNXld*cIwE;rhdUgo*MK ziMS5yGZoiAv@VJwWMJOPc|@KO$E@mx>VzG@aR+YS&~*nu?ks<0{Ba%>h#UBS9rFa^ zKqdSg>*ex|%faU#LWzKBVtFFa*RlSO^nLViSdMTUfphmGupqW^UvP8G=I_RPzgG^h z&)5YIft##6nK&=*L*Q`(YV0wDKW?kvfB4f&gI#=vai_`v&c$GCurJ0wm8$y~`#~8< z{8Rn__X%ayWxv25#{$bem;Y((sWO6d6Iga}yYRV*F(c?xg14jUKVJL)%J>6saO{U` zDe<@%q3?K&E^l!iQJ@IlP(lF8(#vjX?f|ddbKNJ3keLfZM z(QBL^zR8ja)s`h7J1c$^TASwdS!(tr4m!XNVo<07phJ)GaA z;`XC8#`O2f0qz6fyfD5FCfvXDiTC--Y(JJ6jQ@u18;E=W#JPn2Pg0yTEOXG#Pq5z> z)^H2I<_7}*Vf#N)FMu0pruj^Sb9X<&{9C{MmL0&cMD)4n;aCh`3y%wIj{fs;-$+|A z5`UZ%RMt}_>QV6ZhT4kjooD>Y_=By-_{01OqK2Nz7eYsH?iBaczJ>9h*@xqa(6mq@ zw@k$X>!+Q+-Cu71-6Px)6EhRx^YQo+)wd1d-wJEVUm1Vg{|6ERzQFv5&v75llh_B? zqr=w@|0Vcin=nX&V*xf^HeXH1T{zSWJNl*t(BKyF3-KtjAQFY24krk2CtOS*`iB@-YL4i8vVe~XP7Ovv5<(+maN?R6 zADpYhxu*X`*Qt1qv>$kTZ{A*n$1T5u|69O*9oPU!5B$2dz(gN_Otu9XfVr20F#zt5 z<6PXv{hr9$%^Kkv067ggA|HtJwwQ2kP)SD#ncABo?>KM5b`y0#5Z{fI1-wmQpA!6C zEHFMe-$0z_e~1CFuH#&V**Y_%GhYYcItAk;#>irW1u>5K9sIHE;kf_{F#fQH!+yOF zwtql_IUvIS4~_xg`Z)%u>4o12{Q=H=drb@dH>d%|`2x)EfI|T&rYL4OPmFbXrPoR% zC@%OtJsiFU&d+Vn*iP6PY+tZm2?oI0o+@L6s2i zi2kZJfELe#!Q)^g(%{@bECWC=D`+H-ez;AQ4a`%V%fa>gTC=r?9G}Pp5h9+z$Bf|l zSKIJ-45GeFOiGOCtE<7h0$eMI`vo`_#q~o}c^H-j80*71W1Kr4d4B5sjNJF5YXkQF zaP070NWA?ke5mzrz?)EvIYC%AhPSDG!f)&Tw|EMCBd{2`9(f$b`AZ@rI$8$AK+6Ke zf#_-25giQ+BGJH{p`m$Y^o(>!aGU^9H;Cg$T>rVhU_Xj2i$xpvZA71IeQ6LqJq2-( z6(Wd2k&X*eT>`&8v*a;qUZh9`hxV(|DmT)SV&}Y5y6jP+y`X<90Oqo7#Ug6 z7#?9HA|`{xB;*jk;CRH&!Gq}N7{1WEkvtu_O~soGIJ1#>&^S6-q(VOv&7mwq=8W#h zk=Y-)GH*j}%;7+LVS5*HW)4IvnY_?k$||HxHxmicPCyJa%!o`+M!cefa|(!gAJA_Y zycP{&U|>X+Hlb+m!TTsY{vHb0S%z%aMHBRJ()AI$?k0uM1ikLL9c(EvJr_7h6a?L#Z< z!x1wJ2iWQ_{X52S zJf83+RQwm1Zz3>H&;1DZ_9bCC*d2QZ@d=FkrmVs_vVrKZ4E)R=1_la-9?$t7Po|?Q zCNL-L{*cWF9A)J7P;A=M@A4As*TLg2k?9g2#K<%>e+S16Lxi!HppyYxzKy^gWA{h# z{?YOHdtIW8E!0m7KXra5 znM|h(I)8(T*B5J$1=|fV-{50EArD7R_agrtrASF_4%Af)&hHxe_ZW+bbn>9{v4m~^ z5srW6H!K6&SP!DHWXLBC;cw@3;2ZZnXa}(UiagYagutf(z{9dS;@+EKTNLQ0#Gi+U zmuMHnxY8iwQ16HR=BSu|NPkzZ+lNjRB7&D#pRm3kI{6j_?mCZV%w9vpe&6CD#(|S0 zfIL~k30wVV@cpg79gRP}A1jFcu&<)(`|uk47T&g49yI9ZM%bzm_`d?)DF2Lo04&oi zEUb8}Pao`l?O>ZR{y675LI!{@n2$mtlOS%6hPdx6a@}$anOO%Qd1XVy$~HJxi>hxU zct_|H1e9nV#fI?FzlGnQ{T<`Koi!QplEnx6lW^Y}I%W{-^#Im`cw@|uf!?Q``G6c; z;t?DBU_JZj`yTp^YoKr*jL?&x&4H(8^nBQP4w~b_E7AHf5$R_ zV?YK96XawDpRa-U;bRB?erOH*fLlY@(?H*j8~L#8`#;xxs_g7%JC3C2)Q0ir;N(R< zVTFXf!gdSe9v)wZ<}LO>EG(Sgv-{LG{dD*b)_2pffxY(tJYNsm{4enrAWuZ1WH~Ai zetV7OWC(W>I~O7gkAuH~d?DEWF(iHz#+m^7|JTNQRJ=#8abI8|7lv&-x8vhryRpp&{RXU44CKCNKX*!i*J`Nr`Lf47@!QkKG-$=Ydh++O}#JaB(s0fk9@Deh?K+6mctEF#ejb4So9&I1+kF3qDf>*)eTF2iP+SUB&(Ctsn=g^s^9|7Uxw){>_MOCT2ug z;)>W4Zz8U99}rh5LY(J%5yLzi0)Na?ZHBpU-ap>IAFbQJ_dB)$?krm&7Xb50zjxn5 z`*j&iQ6$?*w2-m}j{PjpBief&WPlaE|0}VFd&ja%2kQ~O{xg0U9})5twDBA9c^u?s zEE%0aM_|kk=6{Uf$>27&pDb*Mb$==1Dnp2?=;I)jenPB!3K5BwozOW>5+Cw|9AEt3 zbbO-ynLm8AGuk8Cfy90RE@`(wxt4*#j(ZF<{T8(5ruL!_a->Ht~_A&e$z*?l3(m#vCF1C5L;S#Yag=SO z$^zH`IyD2tnfC_p@Bb_V;0Ku9A_vFX;99CMwu5>6TbTXTzvFAFfDap%;Su-?fZf}{ zdKB;<9CujD?2G8JtsBX!!R=z`EV2Gu#)7E%3b6Th(3UT;hxQE0z9jMj+lT%9Xr5z! z>}ER(enD;o-$vn&`GREt@F#JNL2L)EAg+=THlSnxv8U7`lHlMt5bl$2gTDFS)eoSy z3EP}`l$9geF{lTa2mEAdz(0++CwyP{{&>j}UmXj4>;fOEPvz@B*$52jJ&yIXVY}sP zSssjeZD1Vr$M!f2&0w(liWgs>5BLc(z_>a9@E`cB{}2msp6fvj#!HD9fF9!PbufPW zEqSA2^?$L(*K=gv3VO`~dOetzz-<`BUuFpZLA!))g&Mu_H`@#{upQ(6PnQAw%r?mB zj|Kd{`P^pQZeG$vkoD9b>LS*MVCYAQkYP^l2)+&DKQbP~K0rnlab!P3T;TgZ$AJj3 z_@yFRs2#=U&}CQ*w&w5215jlN+p=)5WfSS9e$ftmKa=S+(0;azVf%;m!HUu8YhO3$ z1Mss!xS{?3Wc4Td_)x#P5bpKEFjj;2Pxv+5ZXS{(^z9CTF8&w`90Phn?oNUZ`+-57 z!8(M`F-SO$!1fgMio%FkLQW9O%|*ZHIF~7K~0HM}KcF z==Zh<^FWTCn)mpcU%<~eBO@bPYTXQ#-~Yrlyd#mgEtu0O0x}Q+_~9#{q_BxcCd zFM_x*`8WN+NM2%n3Wa_GuIGe{4aR|CnP0|%*aw7Vz;A=k+#C-v!yd33|G#X&$on6? z{Tup^xiI9Kup0K|Hf?Fo*f*f4@mK_+3>q$hc|Ho_evXBA95>y|+FmKi1 zSP*qQ0Q-OwO^7TZMc4qG3s?*F-BcTZ<$!9Jv40t)!F_+Hj9~vi=+C~>=LP!-{Cf%d zq(Ge~@lA&R19t-E#B)t?`~q?i0pGvM3u3vz7~&dTJchmpa^t@h8)E&z$KX0_M$+K3 zwXv_~IMxV0U_?F$_XB1xC-BGDP@va>{>O0?1@?9)+hMRfagdKZ0R6B1K#?#ma{zJ_ z3Gmwy;+R-ME^z(#=lEl^?*7^DSk~}Z2p+E@WceHZ5_nVz{2K#u@DD`!w{HpVod)7V zdawrzpe^5w3vk;>&_|SpoXJL(okXnvBR#~wg|nrB?SQx$`v3+8#1eGuD<8n@m4IMg z8Q4(B7lOZ0f-wPE$iWCe&Gs1JISa@gg_fHjHrO6Z7C?e9Mk52q;M~y?MmMyHC4#ut zpgdq%`0K_La4kBnAt(K4yc?bmhPnK2#UI~0?jPU};4cCc1N0sU`?fELY76ExO}n$ev;U#L_E*`2NcQ7|T^hXBm%JF_O)cOq`ESOa0DkB{Yzv?*Ot5x<_B|er zKmI<*3lb6sJ+xukNaRwn-yW@Zc;6zT?rIP}5;qTGiybv4RQwS!t_>q{0aTocHO?hN zet?#d3DL4b?u`xR=rKdSo&m<<@bh_xWa3M?pamPkiYCx0Aq&P0AfGs&`8k*OM|}al z|J5)iLVp*ZG z-JZ#BSPrPVhv!}xQY=6FO~8zCg%{)kMvMmx)detbiX(D@4705f<1#PAv@sg7hUFmE zT?J4}avrfpT|lf6#fWv=8N}q9h?s1H5QC8wB2SP7`2g9#*bK?UpnTGSe9A!mk}vdO z2kV#q2>!SY8(DV4*q_+v`~MREe&CHiTo;e0|18iCw}aXyhz;@kJ_A4)zLPT=v4_yJHxgU@S)e3gSZNMzRU;X@gZWIxDWP2y%^qx{^2m; z@1ytO-=U_ANk1~XFOmy-v$CH`$3pt=bZdxDe7k5BB`Fm^uJ_hH7*TbA4elpAf z0~v?*kEVXu7X|SWJIKH=&wj=Y+)o7C29J87|6$Gu^JtJ)B*>+6z_f53z^#vIG5$kbl)CVLT5MJ}`zT0(JoBjGkk>zfXg4+A#LmE`#5u z*I$e{(jI-;2mG`6kNkTq3t$&GA;!cu0m}g`3-|`OK9i=H<-(f#e4)8X%2L;`TB{UO}WmE`R1-xKQf_|k4QPVLSViQIB8Q`PDiDye-8TpRA z$A3>e2MXK5Uk&$>?KNGri0J2n{7Zlg)O~^VNSlCp4e>jJ;S$7t_|BmJ#`-xLr~lLM z*dBZ$#+oRmFdhMQ=~P=l#hSYQZabh&&`$g=uyw$PpEA}PiSKW1lcQCg5i~RlnA}>&bhHU|}M>HahN6P9CwxOLZ5c@2Hxe|2$ zw*2mIVfjaYr;cv{?s(q7=i2d+{KMGu3Ls|hWYBep^Zs_7|BjsD`v79ukps0y6MnA@ zyaqdGh51Bn`yb`m&vZQ2ec*=_V1E~g@Tp&{G4_0dh^~3N0BR2GSCawk_kqR%FTRfXu+N7)E9^4D`#u zkLQdU7#Kil9vMFu=xf~<6dTh0e_71;Jvkt3z;i^FoBAyqfagr%cc1?<_~Up72+wWB z^`KwjPcd~I%pJXw*L|H%?~bWo-Utp4CQ5tc8g?P5E4 z12Ov~A*NMPV?f+K zpl|m5C{|9u%OBWn%ww3B!5o;5$dlxUp9e_fRl$CIa}BlObgBl34QjT3za79nwg4I9 zEbK;1u6rRK8LH>}&UJ|EpE)?~|Fw4=@Ksb<|MmV}3M7;ON$9>L{h)NSIsNk->tn2FPeyc0!s=Mm0vQ}2b^ZoyKW*+ao zBqV_Vt}wrx%*>madr!OloO7`rWQ)X|^MJp(y&oc#`I7P!SPZK`WS53E&E#J}U! zOQ#R{`<6Yb`JaZTePhcX^`U#h>YU{ddHJ`#gt;@c^DJ-RTbnj*g+PyWAG6^P4qmCTGu{6(0Nc?ZX~m zIj*e-b%JG8<%K-BPlSKRE7{8^>-{5NsQzSLZ3|_cl$0bNe)yqWxNt!pe)wT^uXF(V z;ToQ)`VTpdKXaG=zd=V`^5?gF=<+WId3>Br{zKum(|Y~q^i`CxrU#g1iK$R{~DSzw>0e-qvY5RbI9 zH2M1LuY-6z@W2DXcF3AoK?3)`pK~2>3Twej4x;_fW*7J&XyCZ`w=906|Ci(V-_dha z_HqM$c`~tf4mpzVEn2h)<2%b>_3DV9glCaDz~3rWswD5d_nyp}HA{O3zcD_j@*gqk zeVp01q^%ymq1^ZH-!Hv-A;we==`x=cD^>($&x`Zt&r9FFeYN*t>%;M4j(yov=>Jb+ zoceH`LYlOJD+WN8zuklM0dk!Gvo~%p(T{%@KK>`ahGhlo{%O;uf!pB+Wrf&tiud4L z@Eji>ujI(LPnCbY9ee%t*Ciz-C77O(cI(!y z<Qam@I6Pb+q3I3^$qomYsef$5BLzOvnMbwLfq{@kmmb>E!ie;1_={rT+*ACwvE8_y#kB zHf`Dz%JQ)5Z@>Lk^$S1x=p$7hhdzAel~*J-R@*z$-l#)(`9Fm*+4AFROlHL%6YSTA z9>4tZi+un6_rmnq-^=ZIoe2kWR=y z`})5c14i`CPNDuEi}5)01H4h~cy9c^)bEE6A6ETDrs3>1GFuyCjL9vilXvXcajB$o zeRk>;_M+4KjP?fYBANc$P^A?ZeuKgWOo+J3(pDSs8y9X=DmIXcvtQ4fDC){2Pv z4Zd=&<(X%mDUf{F9@MCz#fnsM;rKClpaFB3+feuG`;>;#4cul>?!Wc5|4`l5<=+Zt?sIan zJfLi-y?qXa?Cpc{8TA16b*WTG$%nEWIB=kR{PD*I`*S@1@WT(Xe*JoZpQS37l#6fG zc?o`yxkUj8}k|j&ZlTTs;`g|N`&YY1(jT!~ZzP({io$y9lv}L+dZ5jo4(?BrP6!sJ=t)$1s2C0sQ7=9!em1pxOY?KRuKm z>KeSa)oG6T_|MgtDOiReE6>aw(1Ez1jJWnwyLN3=AF$sxYSbt-e`AdGwep63Wm;Fn zbNm$P<+3hAJ)n*)$NG=1W7L6S#fr()sZ-_UmtU51=gtY+6VCOq?%BP2x0)Zih(G#$ z-AAf+KFlr%^?-ZiqKkFh#rp0il!KuLFGBfqBV4o21C$5-?_K1f-WI?Xl=GxvH=kCt%5N$+5yTaH&C+2+C1lbqYi$Ev@RigqaHw9Ana6B2*v8J6+hkO*SW6&w?Rc%Q~%Bk*u3G~NN{x{-5`SSq%nc3tIxmwFsk-)w0 zM(BW6PNN?DP_zGnPZi^VsW|WWM#+MGy+E|sQd=m$yXX_J(YjQ*56Yf>0qWR~S}Z5} zYjN!`7adc1F;B!NabSItIECw&*5`M0)b=5WppkK28TUZdgEyl5Ni+KanDZO_Sba))QJyHz(MP{fd%=3<--CIgmEs<~RNS+-iEqpAVH^6TraPhiurOIu z-njtEZPX(li)Z>4u{Ul9+k*te3wJ>uu-42N;aDf6y|TS|7wkj$AnN>Yvg;Vq@XX$s zqb&*OeG#g1HC;+IOCz;=m+aS zl!qulcApUFfCc`*{*4ji=}3W)ka+G@2Nf@ zWWNXfR=42`byNP}5)Q<}p1>gD2`9bpZDU3AYWSK0pWT_1j{9-w%GhI-q3#53zR~sM)4R=rL$x%+IdS zbJgz&W5CTDwO<(4hZzTQJ#>Km<81YSy&>YL?|=W-rvt3>(f_yK(kn;VgO=|>*LdvZ zYc>j7IzbMZ&6;as;iKY`S=93xg)0$i*$G#r$ zSNpk8rvZ_q-n`F&1MC+=_G}}|0iMcD2RO&$95@Ygdzf>>I4%GFU00|F`k0e8zKl(( z_K4zG^j3q=vEusWBE~EF8T@3Q z&<5b%5bpqt`ZfGad0;M{F@xz#7TEnJ=I*}L#tN(#*j`_>wy z8o2=TwMUhIE@N@h4oizOz`f!C>_cMTz~~eIQR{^M?Jp}|AZOo+VpHo&(VCv+E_XiX zK0a$PzAsHY6A}`5x_J~b2WTe&UEmx+cfd>Fd-$3`Xx%`0DVgEybSiyd+yHz1*5YW< zQyiU#h@;yuakL*Oj%Hoed@aWnDsI2N?x()LwA|^pdjfjd9%H*3GL~~8i#X5ZN|Y#} z-a%R8+WAPp%a_yxLnjP<$j{mMCinAq(7zXf!w5itdI=oz#Qo+Ag~tK%J1#Dc>&J0` zp^(R;`0YzTcwE8UWf`?{-ea0HX<~xUPvH@DGo=D^fOjWjkADneUi%HOy3kM;HJC_RT?)F`?Ya^HtpwNg854# zue9T=hNRiJKdmF3*L|k`g8scJ^4|C!`SA#*A9;UXdu}z}|5>|t8u$A1T<;^(i@ZOp zrDrnIi@ZOrKhM-(h`i5=c>Zg@H}Fr3peOP^G2(gTeU$#(pf|UBojoR9K)*!X>*9nj zxR#zS&fvW+uHe0azVW`1y>V|Ouipo=GieGGOkTeaCa>S04SugFWaRzni04_sdrg5Z zai2*7L!PVq(-*&2_h&EOr{Q(Eh_6ZA2iaxP?rH9>AL@BX2ATL=nFMt;bR*yq6 z(C&i_GYX{fy-@&9>pvI;?yP=q$lt&?QA>~Yi8|6E?)BRsg2w%6?R6u2iM-F!o(Ico zk42kF!CiM$UoSbrY8Pt*`B=)J)Xlg^+2+55|+KayXO{Lbq>sNk6z|Fhjk z%0Ke{w59;L>eE^6xuM>Mz8dPC?Oqq4q3)s+sIx~zCn!xvS9F9kgZEh~!{Du^;6e0M z101aF_?F5rn4O+M+QsixkTboRi?Z#(x3l z035e+uH#GuaJI&LD82>_JO_Udp)Kt95IS8Rm&C-yNK}z1ak`x9+g#rL27DikXUw0c z>vE9y)B`{xTz_WJ>W}tILfHfPv$9~+FR#cx1^ zdZ*pMcG4rGhjbm$Ra*CJt=Mo5tG*-{~O>)&_={2^O^d8$=>b0t;zO9m4 zMZMc;XeX&qtAg0R!x#| z%f?Bcaebsl!x|D3A0zc|sW1H|^^=;7Yl_qDls5g_sOS8ay3%8G4@vKqE+vvmsCTFj z%uvUQzbsSJl+2vgqDLa`&mbWz)$`GIi}#^;?O=64JVFYpLJ5zStcq z#^kr4f3TkabtwYKU)BG>VF(cTiDf>yO0v{Vt1FvNZk9bS?UCIt?hXU~Mm}UL%20Ix z%SKY=BuVRc+vduar?)8DNk6aeeB@4P+rO<8DON<9cWtipi)9A+{vJ5czv#Nq z&w3Z&03L?`5?wS}ikB%ackH-B(HKd4@cyOUvi*14C8fC*gD@&KN=lb6EfZEw2&0|! z6E;7!SvEYeK|0^oS&9@dB279p5s%Nqa=;i9b&&VsGx=p_}vg?I#nFr|( z(FK-?{uBGFcAok?aLPd0{PgCa+zr}|GkLJ)=o%&a%5^KNG6tgAPvTtfb)lbnr}Llv zH&xF^%AUN)i65kY^n%ff{%ZBB$z6}$rQ~eTo70&*Xx6EjDldr@6ZtLa=Y02dp`ZBz zsDEDq0V!X-yrN+@^gB}KyI$B8_8aLRGINNczg4eRIqH2R4@iI0j!lJiA=?n-c@F%q zPdQwVLzjOtY*jxb{q^gj<^zw3I4A9cUYWJQO^Y_T39{q-RKdE4j7sH{<{FT?)M z*rj7t`$hdHU)Ud5cYK}F6@EjAFBLCK_AgO=1JW5<50ZAae@ti0;xS78D0Ql?1>N5P z#$)Xor)$px3)(-7_aT1T?OD(_bBWuFnEi->Y|v(fUp?t-*14J7Hs>}OJbkcKs$Efg zhjG6E)a3?k8_3{UgJjgAQ8Ited|CI{I=TCqyJg*pb<(J9Bj`N!9kC;DX4;_FfKN~c z>RyjJfb;_bp#M4AtD`amem04YEiNSzDoD||(%8em2RtKiHd(}HiIFna3R277SeiQ8 zOLKcSY3}SKsqS>CQM{fsY}Q!%Oz0y6rVId2T~a2ghIH#QOVU~oky2%}(J){4ig#pxox zq`ALqjNIW{FKhhw%I3gBvNdo>wgnEWGw)aV_R1935^3e=AyJMZNEJX zF)3RC@oaQC)wj2hWxk!VJ#YkY7&IRO&5vXQ{;oYgEG-;e#E$qB^H)4A2OihxCm(Jd zz9O_7Fvdp0wVGj!#2eB8Zd0REL;DIjGuY0G-}=_D0k{cqP3E& z@b6M|h3>Da(S)q%=`gg0HLcs9%dZ_fQ_nk zGV=U4>GE4*74X^)9q{_11#N;R8@AIi@nW)?rLD7L~by~@^ z`G;i4=)0s$GW=6ehd>O5ZqY{hs`Z0FtEyX&M+sbS1#q5HD%pWO+W9-n!Cj~$QtZt@s}=J_828ft8~jK^ zxk$EERe1uJ3&8EFjD?Z5F~+Te>yxCLd~aZHDJ%WEWd~%$GM#9xY~Z5kcihqsJ_qne zJ0;?}ZNB)-Q8L%Nwm|wH%7d1UZbAOWT8hh3}YU^j3>nB&5$1DTc%6>(-? z3pkjiu00KM*{5isjt+25!0$mkH2Ie<51;t7p*9GB6Z)Dw!1q5A=D>sz~!wgMJ_CyQwJi)ca7JDIeDHF_vPe7wSp?tE6_s zO;dh4C+mcdGTR*FUFi3DUmub|?nzSAk|6Dz{p9Yz!z!INPVDWW$)7rK5b|ek8{^<~xwKif78aN?4O+tY^NW-)O^mUgf_>7WuX+xwLm+&k*cg>EHCc#GDqy zihC3L3YNU=GC=;2=4HNJvflrI%9}D^Un+$DPoe)+3$qM>cFt`(aPAEr`~mO_^YdB) z(%Cf#^}u~Wd6EYNmWesuyTlJ2a1NZV{Ij_KhqYN}sc5bt>-_ht_q2DlZ+M-z4rpzH z@gC_P>>f|K8$dhmk(52+`W1!>s5Hm}9ZKW8nYut8Sj;x`-y6w1)a&fOhSI=(Y^t@H znBiL%^~gVz4!9?65Qo_%8J;<+?@L{o@7*GcQMOn4_bR<1@3g*O-uz(Q+s%bJl#BY$ zc*t`Ln|_0lEDtDKjAzYt+jq!!=3NBsQD4_+S*vv6h~k5ijccM!B$iCgYC7`5VP-` z=A(J?M=gdj_vxX}CjBybM!bb+#D?~-|DCu7V;>#(YdI_~Nwaqhj+v-aq82}Vccu6ohAhz0wX>!-o5wXNT zZ%%&6646jNG|M;#Jy-so6AZclrN42S~FPYDzLhRrn)06q+J8~_Io;wOkG)>#e@lJNFP)cqk1?3AFStFmRVWWWM!xhJ zzBOh)1Q(fl~)YBmY7h^W}$2jN!sOk+-(j6L7IrsEs%pm)IXS zl#VO8##qujZ?6`wL8}XCcjY8L*Xp3`!2@Jz-U(2$a1EM$_4GcJUuaJl7s;Aj)zE=! zUJsIffDg1kr}Pl9Iee?1P;))m>U*|-Z2$9&(NU28!H?oZ-&rpMKLVGoz~x$rU+F&b zqYdhN#KD?S4*Qs#(fYE6tVlQdx={~)gm@)?lEA*ds6FC>eO}|e(6hlSj1$xzV{Aim zihqSN;F*lQMzy{#$9&VTCH=bmS@(TP`fatEYJGYmRx%=e_!uQqCELGzbF=8lu-tDkbdHIEe>7&w9EcCab=7R z##v#kY|iu8Tlbfe*hl@)AUi~Yc>AS=}$(!A2EOSOQRn{oOt4e zISx~FQJFV)j_{Z{bEcx7^bmUY?k#m{)f7uT>?On)M$l#N(jyWTS4>*9Y#FqPMW0^n zfx|R1@3;f=CdDPNQ{O)gb%KAxvtsf14f^K-munWUfO!Fm0Ny11*0R+!{lq+{cj;kq zVXwr;urDtU?xWMadv~>eYo9)S)E=wjZpWNu*_tZe8tJ$9pC-+kG?u^q^{+Bv!UW+y zZQS#P`?BzO_0?A;7W)8u=I+zl7<}W+I-u?0pxNCIBu>}rpq>F7$nz-aSNno-o)>dm z-WA8hh5b$+f9x@ZJO6QCL+(|_y+(Otj2bDX%JtBf`%1ML);2?=PtP7|KO*i!^W~Rc z%8x((sNUtjx8HtS;^X76zu^{*e(0GSd#W>U;P4+nqiadOF~5#H=K~ZT?u_NyJonyr z#2l9(S@Y)y@ejTD;)^e&UfmS2br`CR5mEQqdry?qI<(HPHqhMSMEMoU)_cDpR_Y+NYTNRv8EPEnB!?fjF=( z;9GHAtN%_xXBL1yuTR#kTPLCK^Sgij>tE8YZ5uI_EUU#wMj7KCbZT!DP5!$f4^*jz zx9*jna-`RR>;yjxR;aevoB4p!0mgB*)@qJ60DCM=nJfn$ctCdV+9kc9<8F@!u{BYb zt36cF{;~cC-)v|T*3)6%TB%^++yLT2jzP6pzE6XqZpo9C7q9e1mj-YffUH*&+u0&w&1D%Hnz) zdOa8i`ss(G_UhrD?rQD_G_fpD7kq1<0p3r6C+}4V0$;77{F zTC5D@c1)*3@8d?&n%{fSPrYCnP3t~e#^d6p8UL)+cjQA(W#6EgXT7{|p36IXk66o9H_AJ8|4#tb*CGACwnNtg+97gHEa(eo zO(-j_TW5)X%S-w?SV)~<)D7ef#*k6>y{Go6Q*+%8jBm9zo<5<+z~gG9`C5&0FNeRn z9)J}V_Kh9~oY!ek?H22AXV3BCnXndnf$mlDI6YJER%?D7M_NlJ1@~sdx0LxAq%|Gs z#^Cx_rSrM*$G{Uf({_ly%;bY=9~3VrQv!WvRJp!5=GS?}^-RufFF_hrkq>V1SvV>?p3e{ zoK=tq#$@1gq^lB6!#NB0nK<)zoQ$)2AHRp4)AaNT;jGd{I$>wzr}8(>xK;S+XG1P3 zR>WC74OU?{+BO2U5}<*zJG1j{VSFMV@I;QC>} z5nLaEp63LI@cW&B+W~Ah7Xj7+<^XQTceLy6sY4H(Zvm8JzZYi>-$MS6JUO0>M*1%S z*aqbX82>@VfB-s_7W#qeo^?6nJkVyE=kEX~09-#QWX!eXjIk-`AWxY;+8fxV05ANe z{3$@6qKUr|)H^zz9=}KVvMOI5{1);27reU$-&@J2t93AKKmd82Wd8ImE?uFt@)@I# zEq%mmHK`?)Q!1-?2?kvi;ak*ka7Sr7psl2JOOray>nK`6=;N3;=!NfZ4VdyeTT{Ec+cFO)vvt$MeTvQ^6} z*&)3hXhUpB`>LUDlr7{n2y~n$Zp3HS=2^;bnLdv6&7!Y1eHZDMPkE%aOjSH*?1P2- z7RvI2%cW-q?7$GaplPS3QlVxA>e)AdF+~1q$pGmyZA7{;J>v~{C9zVXOuTcV^1loE zU&2R~{&;JStP#dppg%QZQ;-JYPQP>d-Vvs(oub|$twl?~-rQvRBl3u6K3C%)UjXbc zeMR5smOWa^&gXXK=vPXA!U2;9s4~iU8&?xEaLND8Iyi{;v5lan(d2 z|M?#9{sPc}n6s`c>;vOExWibI;g0hm>wcGqaeTCP*6DJndY|zuTJ&fkfg&32u(Fqg zQe~u5|1Q#ZTtAtzZi=irx=LnknWbWd__1c-#u^B4CH@5NF&|G^7v|wt`xdM6+Z}oQ z;Ba~*rG7{0H84|p56Y5iwOXkBiH9Gy+x2W|(%Urx{yB4HvU`b4axIdPu9?y!cBr(h z*|G^PssGl!_qjdukw*a+UyT?0u&nh@FPaKW!-6Q2>!oG9NTvtFZUh$ zK@L22L6*Wdk@)lX^6(v-2|pIikK~B*XEAK=m7THwu&ng#mP)2t(xhd7^*#L$mv4Mc z{DC5d9A*%I*kE2U4R-jH@&NAXNb?$R-aE7T z;6&QUlb+_b_EIgSrR;v_E7|_QXVR_<_BG?YgQi1kndg=ECG`3RHn@mKWj1}}vqVX{ zqq8L0D#2D)>pLS8WfNg**;9%;%gRdMZe`0IiF4>Z@gM1)EG zU2leu)&SS-;)Gp1SS_s`J>^!|?bDVSdA@k3KVQ_(V8i2gCVe@7kxOw7)j| z#P{T>LzKzg0Q!tM%pR2{?`zr{>bxt2lWa>C@zav`QxmIf0N<6G8cYO zlRT^wr;8`PqOt|!+_t+$x{P(s$(Ih1AM~x!{9^X2v|Bm58u)(z9$uE;qfu{xj5&fBhnlZVU2$f>9)~(KbgneCwNSjJzR@3rXTEFD9$FqqgYi4h z?cg6meL}uJA^+Yu7g+y|4|F-uN3Ac?d7J5$guae|{%i_7sPZLV105M+M_ou;9j-|R zyb039)?Yf>`p7NT?$XshK!!Uf!zXJU^zc9s-%vVuy#>BqDfXrY{#=Lba3%PY4x{{_ zUr2qrpJmGly=Tk-`dDv)@84w4LUDSFsB=ve^Y8N_7J_(59^w%4Nw`wp8+v4%(WaQ9V`E82U8MakX}~kFBvlsg9rsBL?Y4|HtH|{%-s?7- z{+)lszR~@qm?KGQz)!cBwUqJ`LwcMi+Kcqn81i!J9s}2$&c^x>(q|v;97-1akeb1M z?2j65hMlHCLvClTy~5VUIb@!gU~5SpAU(of;2cAF8+w})9+&$Y@FwreAfGoB?ywtG zYl!4)Zn)<5JLLh}MAyJs###&aY#vP9XvbdIhapSgo=My-fmqo3oY3?=Hyrc&9c!Yr zk+C=Gp!y<6`y}}8zskISm-s7x7M9JtaLnuZDcCKc&LuyPF30cf0qlDfnh02KnI7cZ z7peY4I~o7x7cSajdl6TfFt=s~4`eWKV zkAkf~=3$GL5NlEuarK=7yI0U*$O7j8VqG}9j1oKS-)yjHc1=c%3iS8XJ9tiCaBEp* z(?Gw?riF<=bsyjdyhK|W)y|@?#=3)R>+RdLmXtbmw0a!!fzVLC#{~Ht*!G%O%2ZPN z&1Y3ARgzkG-_dy_+5yc5pS0MjHsCv4%V^0w3+vF^6OLz|L`*gK`P?h9zeDVZg%3&9 zTD9f<_uiAig9j_T@vgK;gI{s^s!E?&KY6;2lIm3|3vH)~e^yqO{N*q2O7)af@hm>1 zWEp(~wuWd=fc00%hc^6$g+6tk^#PJJ??tNO9=SwffWIB*ez1M!@!D&zN&o)+Wy+K( z@`u-7m#TH^i+96w5_RY^Dc-Q9JoD5^S+Zn_j2}N<&cF_m2koWp1L0o;St{E~*byTA zzagK}g^9n>Z$O%}h`*y*H`G1wwSj#`yY}sc_NzR8{`qHh{>!`XO6gju;@|zY1P*>I zB~zQppWk{*Ju~cBc^+}wP_YjH{;-estvsRjbVmA5BOgy;;!i;`JwS7$|Fxw^2~}SN z_Whs4R;w!q_TQ&$tcf6LnLT5=IMaG6JAB&WJJ63_m^nZA4&QqV^pvZN^#S%-!G9NQ zmB}0Av6%T4X8ASJLz-OoI>mfk17@oF-ZN*f#8;{z!-o!$MOm5BwNpp&R;rEoB6|CV zy3xDju*8%pD|2Sfkl#Q1tUU72L(;5qL*-jR`@z7TKZ~_QGVy2JSY=1WJPY$M+F;;6 z82S8{b)|pfbE@Cz1FrV|)5Y4dFZ5xC)*m(MPPSk0mm&?ehV8{)v4*(IS4W*YQq{d| zJDj-Id4CVmryWIMq8?0(GDZDD860AM_LLTy&l2Pxw9RBYrP?as9oqIn4jf+w4xCZ; zs%j77XMPsX#Pyoqn@zN5QuWT&^4sO!ndA-fse$v`0Qgk()Mz*b8ba~S@fv*xw%2?Y zeD_S*Y}lKBjWmanQRLCpJcz$e1KXJQNP|5kUB!n`_=C^Eb~lm+$^~PEs0Yr9tIuTk z$@r)@Tz8xTxiX&5)kHm*wo#uVzxv4gS>W)CCAOHj;LF6gK$`yt^oQdbHD*LVAN3H+ zsb}nJv6ZiddPdFFeuOlJ0rzVj@0`5Sp*Uz*2pT>DD1ETiXabw$5$Na5Q~oGU`1`TX zO50a8$Ba4{-yFdAso({=B!$1$5@dEB7o-9CAMb8U#<<2Wyl>q0|{_E(JJ$ulGH z(=N+6tKV@Sb~fHCz!}j!RQl?C7IR{}KdqlZ2`y1*7R58ow~RA;vdWxGf6jaSsej6x zi}z=>_ldts*T5h5VRYd=MOVl<43E9f?lvJ*8!Xc(1ziS2wr~ePp%Ti-Yz*uyJ?~IB;H$ zbQI_S%?95Vd;MNzr()!5*ip>fG*iX(pS%6y9uTxWVEk+Dm4JN`7-yb!UV-xF%jB8< z-BQY%c0II-pdEqAAJ^P}fpjt!zhMKx_trnQUfG(#7U%Css@&z|PaL>5jbn5&Y)WR| zJzJ)&n<|UCr7RdI`=YuI0a7O-Rf$Osst2C0ucfBZcmpma!;6xrQdMPt@-A>-UnD?kc>$?BsK;yZZYqK zyjh3knSbydv#Gt!Y=5UY;(U+yz&{Q#MDa0VBhC`{xHZz#J0i!mCgL#7yHrZLk|o++ zRIRBy%+B+mr6bcWQ>IL=IB;(^9?<(%c>fcwS8**Y)3-sbsp6698~YD@Ak~xm$_C&5 zAngVZc;;GND@Rwv9!)?@$2C&iTAJ%d8yYldpz&7alKmg-*wv57e;D5XzA9pZE`@xV zW`<|BbaUK_nC5N7zyCcc(sjDbb!#z2NpqwexE49gJr!{;@0S{OwWduUsFEzxmpb?y zs_H~k+r?#vwjkQkEFk8Uny3t zi^Qe$k}?%KOT*|cG6psVn-JrWYj%u*JKwh!ah72NVm7^iyz^~0P@+T$RrdjBFTTyd zw`YnXPHUpQqPW@)Rckz)ABcjFX%yCb0*`zu9;~tZx{Q}*F}-Dqdm(HV#v#6|vfq3i z-=~uHy!ao<7QPRNLdki|1bfj~gP(~(y$$R5{;GF@dk{{g|vuN?zkYD8ZG4vkeG9UCq_qpAJx)W7t!N)N=?ZrU* z>-JjBu+I6JHpULk>k`+le$zOH{Ri-`Mcx&-^&fCuGapkO$tu zSUa$j@oP&Ehbc_U;AejDh>3|&`iF0)G6pWkx13vGtdPKc@2hcom@F`UXUrEhKIYm7 z^n&AJjxCYK{T$mEO#f{Mps zOT9(hbr8D|aSfu6V*VaD>3-+5A4d%MOV>T2cp3Tv+?U!a!X6!S=>7@oWC7-zBmbu2EFo9cUj<%wrn~@te1FnjuZ^m6<8PZQ-G+_E-eN-}dema* z*mn|zIHg6P7o)~akW9#@V!NK=+x~{QGgfd;;{$w8`;3B+uHI$-IuE*=?T)WK87r0k zdw0DhglMjH>2)A+aIbt!T=T#u(CuG)O04mvE+E&rBbKi$aJ_3Ft9XDpB&4$hv2%X% zOx%Dv5czUmQ`NmF>#9ybnZsOt%;^i_U-hI~>%un=;(Pk{8p&POnct)A`jFm2#0LJ4 zv(2sIUw2w-2e{4*V651{w%5glxnj(haZZL~Qq|sFY5qi-X#ilaXzGBpkAoLqVomE8 zM|w|jwCIDiyJnoH`xbe=i08xct`Czb1=jgXF^D)IZ9^y1aXkmH1F##g0MH5V#8s-_ zut0S!I+mP1$W&fl#&!5PjZ+Pnc1XjN1R>D?PDSX`5GoB^XX>E7;j@t6w0t;4!KsSV za6W@a8RsdA18M`zrX>8wfvsZlv+pp=2WajMKwLHR-|*%80G|JZXQy#~1@J83QQRK| zEC<6`U|xa9sNDlhCKdj{_(>3H$mTo2HRXNr+~yWG}A_Pw@G+1GP^fc_iJJ2wyR zQN}$ihtC_%J!=1nkMcDZVtWmGk%MJTw-Md2N8(&r@z4sj=T+B{U8QzfZCP`CjT-l^ zI=l+D{9QTs^D!h=EnoP<_FEBPv1~}fx>2LF?ozgLWvNxWw$zDgD6M0<%IKz(W$e&N z(xOv)X^na7VkOFQ9H!RsE|mxSnuh>UcZ+lznHO8JDm`4L|X`4v=vK=a4vxznbvufDueZ{eX%kCC?;|@~v zmNBxxyFNG$F~(&B17jtrbWN%5ZXhnR`%frn%8o2D@!%YR*)%cMTI?r|qo#NlBi=e< zl%(|+|D8|B;3BgzwzwE)onxIfu=lNQtq1$b8B)rc{5{fdM?4YlH7tu@`W911r^WqF zTiA52#2!CO5YxV0{3!9PdQ9r3Op$f&J+jG*Jq!HWo(-gfdjWB8mnw)Aj`Z(h!bU$S zG?QRI&ul7hHCykgVyk_gG)#11>~I_UX5+U=^@@X}R`m>NQEIe|_1+8P3qEJ*!e3v+a{JD+05M{WwpMTV)p{bw8(Ut4jYtjZ z&Qs96D5ZbdlMMTX`UMRu0=tP9+#_Ijkki_v(Z@&M+Xs6Hi!Yk-EAus1~YjXUZYoM@OvNvcCA6V0E{MMKc2tzvLhVS4Q?r5sT5)5F?*fTU! z+|#ye{co%d`_K>aOx-H}U4K&fa14j9R`bK4p717zLr;H1?W>voItnhg@@r(Jr{C3{+WJS|@<(OcJ^SP} z#I1Yi!2MEw#zsk;zef%pctHM)SZ@0td`JSTo`C(38q=xql>WVuT(&cx1D9m%C-uH3 z>!7rlu|f_XIU>`Rtdtno%f_sJQWmVbOWt_(73n>Bu6XY{CAKQ{8FQjZUTHHk(&xmW z1Me?lKi|LmEs4Uu4YV(aW}giG(`fWbi%;7mfepV$JQKv|x7zmMeOG?cXC68Y@fZUf zc6J!1tw*z;sdNcvTt~q^$Bh_Sh;7NfS4Duyn9E`smpV8m<@~KAf>vj08J<6e%?_6pR zlAle9ShKna=k;rz&zYi3XG4JZ=K*}5&vS#HO=(yk%f#9j!V4n*Xh0Ictp4Ky(-yh) z!;JC+8m0rjK->En+Ut)2@4<)juc({fLJB+aEJwd}>JNO~s7J%#3w(j|#`L!vDR`w_q@LM^?QZu0d4xWQTzUKz8w9YT>CTn_jdGyj!xe& zUF|12W=V$fr`~XKgUsJOpL?o*iu|+pS7Pxi1!gkE#g|C@UF%LmrB=0i(zQfC$w-(k zQ`7H|p1p6E2F>I346yIGe8kSYvU4vz`bDAVp?JbGJw#jnecG=-Oq~@k)c~(e& z?4MQER_S%%P*kT^)qASuH=8Pyu$JD_&o%OgQBm`xNXABKS}#+2RzO=>^iI_#F7|Cf zd!EEKoMdE(Q^=v+x*R6knz)L$Nc5(cRh#F3=hF`%qf>%EIlVXq&OuYRe2-`Le-{MS*K`w85CM$P%K?g>2d znMC32$ymYoVqH=1WJ`eG<5@@7=oLTKTK<4kYSK#Lrfrs@%MMGG_PwOm>cir>W4~CO zu95IT$nkspSkKmZ^jEj8+b1WF9+LEhTP1DrPI>X^CnaO^esK(6!dONfP=Dq2y$|j7 zvG{p=q~ZFbQtZCJO7XpKOPjk6NU6-dVyoNg1$>yJ->mB2gD#|B$6l}PE1qS?f1#~t z6lnKgUc{c>`yJG2>9C&;>P{pQiU;tW#Zeyf1$Ww0Z+R7S9e;q$*CwP})77%OuI3lN iJmHh&v-Y;wb From a880c733c8b81d1fb4ffe4ff16a4a036004ef8c7 Mon Sep 17 00:00:00 2001 From: 0niichan Date: Sun, 20 Mar 2016 20:47:09 +0700 Subject: [PATCH 1104/6300] "Anke" by MilkHater, the I2Pd mascot --- Win32/Anke.jpg | Bin 0 -> 57221 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Win32/Anke.jpg diff --git a/Win32/Anke.jpg b/Win32/Anke.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8f1079fb25bbd50fc418923812096f5572b01f13 GIT binary patch literal 57221 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 I0sJ-fKNDy7t^fc4 literal 0 HcmV?d00001 From d35b14f4cc0a6674272b02446461c657e3858566 Mon Sep 17 00:00:00 2001 From: 0niichan Date: Sun, 20 Mar 2016 23:00:20 +0700 Subject: [PATCH 1105/6300] "Anke" by MilkHater, the I2Pd mascot 700px .bmp and 2200px .jpg --- Win32/Anke_2200px.jpg | Bin 0 -> 149209 bytes Win32/Anke_700px.bmp | Bin 0 -> 747656 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Win32/Anke_2200px.jpg create mode 100644 Win32/Anke_700px.bmp diff --git a/Win32/Anke_2200px.jpg b/Win32/Anke_2200px.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1c5b0f31681ecf31d9eae38a6590b0bf2267b503 GIT binary patch literal 149209 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
>&&84UGHbJUq@^SZ1}>42R~4S)wtjZJ zrZ2!9rY!Q3N3!rF#d@1DCp#aEep#t#=Z|lI6(_65W6OGnuik6A4^;eo74=0Emy`+v zOqERt+9<65+QyC0P;x=8f@KvIa-bd5*t=+%_l6PwCSR1i=0@Yx7dLCGjRI{T!<@$L z+ANjYY6GrqX8MYQsd`=OCh5NhRT#gsn+_763%+d z(MG;``;X=B{xryGyU~wGzA1~{d@@n782r-3S+!}Hs@%nmFbbSx<2cQk)m5KO*-fkj za+#{pw`oDhIn2dgB-X2cqMOw$`yts>!b z-J<-l=9i7iq$khZwVv$Wy3Tu9hu;{m_4|{~c%ZM8)}lwN&^D~^<|1ZIvTzD)Zc8Ms z?0rl$qUfE@^ZvM-(ShMQ3gU3N{QA{Zs6`J#?r9j~h{jabPQv|Mdc z3r#erwLuV}(2^vG(Ka(DSD$Lq7T=jP`o!)ZBffs)mpzcWuHcfq?UZlr*Xig4I!PZa zdCAVJ1m-9|P5_5~@!PC-6-L61XvQQk2I&g(x*4u9Ydr;|7iukIs8DY(g;6_!*sYc~ zA1iYXUnXCT|PHd?y+2uF1w&;(7BeaVYv8^ z&sP8U-rLv_xO=P#X(tNIGN`7SN81=G4xkFmnXf-h9JP#-8}j^;RF+c6pXm+|vg5u- z=#wTeDvOH!Z)`0yJwN{=HvAVJ5w#B&#w}{AD3yaX<65z+Dt&+egP1?$=2^QFo8I&B zJypwXLUO~~jFF*}BZldz^OaCN(zqd&c>aO5Po&en{}QvLDA8-BX$4=$7!tWHrDt*G z!;Y;w*D4uzG#!kx00>~0l=vkXf{2FEf>X(YtXl*XbWK^mX|V`YfpzSwEugc;qMWeu z29OjXtrysqq#P;o#V{w8Oe1jlok)USw?xbAesGV{2Tf17%=>`d zlO?+@V3gC$6jlo|7dtn23IDM~&R-wm$LD20Sq zJa@sdHH18heorTA$XgPfrTO@^-YMRbq&rHi>|Lk8x|mZfTBq4pSe*!;1CMDh=}n>1 zr}cQi+7>_oD#phyir@wcQP$%^^k1YufRKC4odHxlZG%}D{tXJNh*AU#juya+fbkfZP5-%~e-FgBf1NID;PMrCx@MN`1xNE@t{&>*$ zSe%3%fbW4|{R_TlbxH&#hA#d6aP=3Mf6}us$CPm#3L4e`8Uue6#-amNG@jtG>WdX~ zay#KLZj4mcV6EueogDvriD_d2t~JZfhO!$B0O2k}#;ongC>R}~4EjiP-T>oyk&K{z zE7`_DmxLLA&2a3ur=vbUNxSd;&6i)Vj*SvfU+a6@OY-bQ8744HtRu5tyu6mqvWs~x z0DDuomz{~0Ohl0qG*M46Lhgsnom_~bE59%FIM*a@Wa|U|Xg`|#8700UjApAAN{K(S z28n>8LF~W{s#c#+A-XuZ-R+9hljcBpA5mDz$C#Y48c2S~Vcz;VX2ja4v-XN884EV@ z@T;}I-fCIz!y4C%s`tpr!EO1SpL52P4|_4*=Jh$R&<`CW#9VGpD^{oo3IN$VFeLiB zefepVYO3EggxvG&`v@s{9p0vx3H&;2i=R@AekZ@8*erpj2= z1qE@w?k*@&6R>&%-Z%C-8qgtcebDczxZsghU(hXQ)7Vn$v*k?xB5QvfdfvDDe(I-b z+kJ;2cS0;=n0b4dn(UN2qIdSRoQg>4&CYE}ygA;#klU%BYqv!IyK)6JHHdM`C=mx4=pX{!1gn_H24%yXlL`2I_S(2b*P@@`_HZG=Z5X3ar@!TPujlFA2#<FjN+B za)5{2MMo!Q_}*c!ZOIVpvJXx25(YXlNUcd_URY(qE_lF4{aH#$kcyH4k#%JwYid&K z+d=P=KO@#VbsszUjpD2-o?Vr%O|x{+?Y{RE69cOz3Tc_~ZqMXpP|QYbAGXym;5=7@ zlVyJZv*F>MpnUPSR#?JTl%T24_O-U|{^xhDuUf)orU+QpMmL!Ly~J$lGfGUsd-dzbL{-oc>uAQo^uyO>Q0T_VgZ(3ij++^0n!@8yyP|^NFgsa_YaeG;! zeoG}wMI|YLk9*vx7AYQp+<8zL3u1kaIyTMjCMVrNFLK3l;%iu(w^1b}t`&u>drZ!o zEqbW(HUKW0`VJ)wG}~Z8b#?CxE2Xrp_`9M?6rO?{&2A=o!t+*Cu3+7!oPE?iAI;RW z0E4Nu2bD&(NnG4N526P6VGW&4hi{hj9`72WeFtP8wR+=x^dEiO1GHKl@;R<$TFrIu zU$!d9KD+3xd}|QpDnT zC)Y<0&5rDoa-J4Zi}v`Un#BGVXEjPxqBe4Nym+VsCaMz!^K<*LUV`%Pd zEv3FG#41$bue28Lu-9k*sJP)J%slOKTg=7mS)}1&j9kh?onf(&9!IR+fFpbg!!xnF zskpOs69^zK+f=Bcr|X{mm2cpvbZ7rF`&Ab(VkVtZI`|B|mGPQ{7=nCTKeVRJm`Yui zSO7)7#fP_7o1h`OlJy4UGB5L%K(lN6NCUnuaC+% zy7vD4JI|>cBfnEYGW&Xlx7&_%4uT1s=+BjgqZtaif>aEJ>oiqxJ za^$}Noq)yLwl3?vJe4}zX(~SEPao87y4qm6N&=2&%?Fy&!xnbc1>?lI&$Q9Hj}2Ai zAAIY4NAH@Qj{-fy=gXgOx+m+OEaGmDsi@mZ=(M0Iwt=eLeyjFaDKoC@9efBGI{>6*^*O3!| zZ-D?>`M&444bIrJ&pjJ+$!5HBHi-5oU87ch`<$;JukO#YDb}*dray}Cfuw!gVfn6( zyB_Edl#xE!QhPuMJKAAa6+bz=45Q#ZuK}_oRDw_w!a1utK_*tDkvjG>bayc!CTA2Q zp0w8H9>9vyZng~3=__-P#ptAL`@2aoPgsgBgHliLYLQdfHIb1=DpBL~r?c=!gk zh19F*5gly+&`s{Ip+y1IRzdYN1GWImw{S6$!#75^){)(_6!%?yJoNi%;Z`}mJVHKd zu~l>l$!1!|rR>`7f^ABKNaIwEic$+5=BCUWiR~Pw44P^Ntt4c&*mV4PP`W{+v+GD} zQ)FZav@NZZ7_$`27G&rIIL5GHx`9pggLU zLtDlvRJz}!_-ZsZxA)PMEjlM72@xKLMsv{fb#EeLu6El$N9-*(T^4OZWB|lr?}1)q zp|&NdJ^+r28OauO8~?u73DIC!Wri(->Tl-Cm z`UvN?p|D_~HV~ouXrRjdPQOb)a(_kDxH;4yId4O6JTqqpmz&37;h7wqHPy^=nG=$; zvH)9;PGtrc8ap_}nJc=xZ!tkBSnbQguI$En3rh>ie7Wfk3&S{OsBU*$%JzOnLHV*!W9em^v5yxzMC{34H;F{n2`Oasio5?Lu<^OXA9cK_Qa4J1 zkxWrgiI9o$Z{eSO9Myd+8NJMQ69-~QJ+bo`=ZWM#_tCwoAs1h-WCWqpQ^8NA`LVqy z9WWsG-}iM^?p}YjCCZD}t+fs?+AZDVnbfHZt=E(a!Ysgy*h`zDQKCOUqLgj9R&nUE z$_w5I`=aDdElRi0vORQhY=x{RnXSwFyYFEV*fl@ER5!=+?U6Z2(=6NhYbPxzc<3n$ z)y9fK#*O%nCH%DUGn1Gl=_b|kn>OUPG)1oztss^>Rli0LFD85>z^4j$GOOd6RBgat z>93lXL?G0KIyO(7Jt@Xp0q87>@mPrcU2TI-|i z?Q#Q=2oLpq&5xOBVor*xDp$$_%b@Ee=aYQdwDegfSYj}y$(jLi zTOc;u=rRxcMld+twGD_T>cmGDV$oV-|Es~5#uqAWz`m;tPlUnF(MVVK2Nj2@7 z;0l{7@x6ZFzVz7`MOm@h*|kqJ^blfokzxY|Fd~JSAPiJrA z?lL)bD5eU(!94x#*;Jmb?RaVZXoJMh0n3x}mKC25;jjEjrn55Q%`{cRILmxTO7S{C;i0#JATZ+zQ#Z0-~FAvH1b?$l#L{T*lEdjR3j+vT}8L)Z5bX} zP+aOOI%%?M6eKvwjFc=-1~Jrmicpz;VpD6wOF8ek>U*AFM{g7*sn76Rngbelv0G!0 zcjA5EsQ`hPDf}YN)iByj@<4Ke_`y^o;|J9&*oXSALF!;6V37ad(e7Y}_bXEmNq?#Q zk$n6)I}+S;CWC*t9Tg)!2<2u+Cpl5Lie4l{SF%{I5m~a0&sxj^OSw7I+3E zhyw2cA-D(6i#+E@3>8rVieO5^%ji}%HOb3C`9B>f#MUGMJx{B5t}mK5I4UJ=d7swx zD6YrbL}WmJ>c;7%vGhtZyo%vA(FwFP5Q^3%Jir>gQqMm=U>^T!2>s;@bL=Eza3)mx z*hA)TE+-BcefoPa3OMq9jX(jMgGzv*p!O+mCa-#ER!#Ma&&1PSHg%#Y?bHj51yNp0 zPPDaQ24X8vncy#&9x^;9#bz*Y#R%)OX8r|RlT}tA8eO#q=N&}2{p9Jk&Gf;m4}xK1 z0HjvGEN&?2wrSNUHtUB|j33#Sh%Qwfxq?~XEF5z00zucN5JTO3TlQ*uWxJTx0iU1Z z!-}#n(V5oomVV+dFV*Fv9haUE9F{EKuZ0T|8O#5y^kSZ7g`KCAl_oT_ZbW<+pnHqJ3{f zYe`gz^kOQb&)`ghY`=s+)&t30=@!AH3s^>2X4{-=hQU{~+Quu;dnQ0r;kbMG3qSq4 z!Fk;f05TJftIA`|2GJARf4!Msh1;(t;&CrAkIE_hvP~Tub~XA%A?=K{kgKT9ut?}1 z1WD)UTt>rl3kyE8xmeAB3kPA zaz-TKS&fx-NkEet)|YbzK89(Oml3FILSKpa?l}7mviZD7@9V^oMyVB4tOVnuqK8@9 z6S5dASf=cvFYewe{|so{=gXD{9xs!MGknP23Sql&-o#FhD%xKr2Ux&EZ{fO(g{ zxHPt~$s(hq;JhZLV^(uE;q#I8zMCj7a%SCO8%YV=v+nq?Di(0r2Klufz7IMi2pUzs zk^ub^7x+KvrOxoOwi;?m?_}AX=xU+OzWX?ya?8&FYbidx&3x;8j9Ln+-= zcPB8d0ICK~v|DlLg!OpNs3F(EQP0x!x`JdveQ&bufri|jEBBb|L%!BJi*C%I!k5No z!)x{hxR~}-9WPcEzPyZxk?YOUdRO1xSKdJ(JgqFchm+^~lMdwFSv%j!_wsYhvg01+ z2BvgJkxwvg!&u^B{mb6?;{2vIyjQy1vu_7;XL%3!-m1SUm>y1LHsi*3wHra&?f#^* zi_4Fjd(!x*P}3rnm$ux!2+?EM*5z~f!?c%$VGLmOV;IDEyZCPWJ`C&(SNV^K-ylGE z@JoMSa3Kw7Yga`$UFZOGI#`9H1uNhpr4Z& zEhu5)y;3ZaaRR39oW!937ge8L#+)7Rad)*l(W4W)@gA(poO|tBl8}i2hf+%$;Q9c( z`bhKz^6$X7fbQ47x34t7iTee!Jej(mzQe!}tR{NZiI86TOR@|&^#8?Pg}WVar8JQ= z9>XIf44WQkW6EYu8bru&Cq(I7oaSz688+l+JLSsffJH<|f6f>C3V)cmpb>7+p71;> z6rWL!LaiV*Wf~p5uC&~?7!;}Sq!I){QE#I15HRV3kYGMzA5p1c&h*cjFR$$;x*RA6 zbsvEE*Hk<)hs}K(&X*pLi&yVu)on~x3dj3iiC0TB#f?|hNM=^Ea=A3MHMN%)*+PPr z^xaolHGK`7*F$w`HFRp{tS6F#*P}U`2Z8a`{^9xh4ktVZ`(T|VS4erKXV=8h?!g(O zn|BCk``}27XTH08}R9)#(8cbM%U^g{Jko%b} zWc2ezpXG+1+05QS@L9$YJ0D@aWJ{T)k+nD8F)e@6jpb@@e}HRbcz}FeA!U)(dpK>v zi{ToZLfdZnkI}k^$>PR<8&Ys%?dh!yE=iS*iY|cK%+HhDn`!Zdr z0Wdbbq8EBTOH(r4`IWmJh>g89w&^}gmJ9UmY)J>c`K7hHIp9)iEGf zn7jQpUxJIR+T$d1roQHy!a}okclWsZ(7w6Xqa^m{QD|LPr-f-Nv$VZ-pWlk+>y4W= zYko*&>nd&*@NDk^k}73lD!xAYnGc>5fEB?juPdl-;GwIMn-z|Bcsk>T`5wEPH3QkD}yW4#l^iHwB-IL;gWzZwK%I18RQ_T^E_!#y8Xg`D(vykUBmzN zSA$X)eBf|$jf+_dRZvYqtkq4u=eO>eeSR$>9+*`~ePrLwzLj6oJ<_Fal~$qc@nRTf zCl+ivK*WUkcEiuVuNT@2uzZ&fICpVfUN+qkXikw{im9{vfP$PI(FNCv>jCp|Ax(IX zz7WHZSxDr>Tb7wU;Ya#u)cq^!wRlc53G5ej@OqI2!Q8h?%yHwu2oKZ@LyvrTLC9t8 zueCEIaC6L=RX95Tn+bIaf)1Lvt<%?srY&`vWz6r`l1MLNN;NqoACDUh##7|cq0<%`Ab(H8tn9VkB z9YCwu-au+X)2|1+-NFL7PUqEVtz$Crzhvrwlw-&YkmdAT*SzXf_qgzJdOUX}dQ3R- zA3{&ZI+Vbno+JGLk#}61VBJ-|MNgC5xKqo|XX#2tB0jP-J)%y%!Du;UiIj?^-N4ke zF&IZdteJ!!CkQ|4VOGPK6NQZo!+jP7bP}Fc6e1#TO(P{Fl&Z(*0 zu*147m&UsC0ssXKsyZI<8lD}Rr*}z#*Y1^gAqO?=kg5_X`v~kd+7j$+EnjdfsM(`cM6_$EiufdUQZ^xrhgHd z8(|qDC1NQ5lz>Mpuz+HHRHJw0LeYVABQ z^uF+0lD!;rW-|L&e0($D*>>&R{?4b`2U}sh=Ji6J~r$6ofvzKR1m zUZJP8J7!g4=7^W}E2wBMm22EzOT=oW;Fs=r$B4 z+Y2~iVpAhfoZ#E2wcBf%wR30r`&6<0e5<1sH{So`w;zu8_|luIbLuM;W+hP)N#%#G z5T4d^g$$B3#;Og9Gkt5dwHgMNl@v?|sv4$7_CN{3Q7VUq{TvJ3P%xR4sL zqV)Y8wz7+2##xTu7Isl51Y)W%Zg;27`~%p``s>c;rdI+-RIl2vLhNtxyuja&%d_Jc z3-D!N*PMhdF|{JAe*m)^CMS#8sqWuDpTND`4YJ1^$(laxoNeQYKdBkRsFPq1sB(vt zNIJ*x>E4yu$k0xr@bXRQc;kjcWKIb^iFz|FQzt4z_HXV_cs)?Aqf-ZW)*A~QpP0LH zDz$=(UN!!(U;sj<4?V9)lr+0leZA6X?6te^A1D5=!w~+nMIj;3gm|7{7#efKa-jcP z#~;d-MGKndv#oth)^#sns|y<-%C==!$PL!E3S+1NOiR>&gj>{H(b-pM8&bI!mg`UB ziinGC@3mr54I|MowQOa_Y^YoDJdXSv{1 zdNLPEL_#1}=tS_3%s8q8tjq{HdQx%i9&$FD^n{=E0ZRmami-; zi1q4S`?w21ErNLn8UC{@>hKr}zN5MU7AZ)g1Zq={raqu?tWJGZ=s8iO~j zZn~;GrWHU#TSz{ki&=+q8@fek-4f(OO*9SPk5klCEsjd02}YS|)vPVN@S;|lxmqOp zBVRl8{*T&&f9+4@BmBbqiRew~0o+VG3E4Cc@w3=3TO#k$QvSFosr|0Yg;($a;yK}L zyS%2Mf&V2omACj$<^$hn$Dv)WOrjH6O?6mO(>?y*n#nrz>0`VrGaA!`#mqU z#4|~8aBy*pCL_QpkiMTj%Khu?JVw-h*>(UYsv+=aL(TAaBOcRU_XzMuCh13s+zrLg zT27*eyeo0D8P2XQBkq3~9-*bHN()SHkCwuJgim1zA0lp2n@4F~|^wyD=ekmyM(SOG6&yFP-O!1Tl<6eEh$=-uvG#7PA0S656mN z-~u%t9s4{o&zTlWr!0$4o9LaJ1K}vLdLeGE1^8+?E>@cM7%Dtd?<(Ouh`=PEDI-vE ztk0ngnBMC@PHbt`vv8esDV{vLmwe?_B0EORmbMY zPw-XyNFYn4XLC52+qNs5H;`U)Ceb#+f^D?ye zX#K?!n7pR+P*ZO25HuT7*Vl5l2tS{YB%>?Ylb0%tHvN?->0T?PKrK?yCJu}u_o(r z-Rjna3D46Q?MLQz5ob)FII!^n#)|Z1bXu3gb4tZN1U*ib9PQsbSAq`<@DzWrRXMGM zG(_u#NiQZi?GI=E)OGA3$T6e9w-CQB$T{NAWiL2-pP_qxiw-c> z_@DXpo3?qs=wh`$S)tBc6J?P)kmT6}>DZ%+skKq&Hz0Wai^cOvRw9`4{Z~`|R>3#? zA(kPTsM}pflT{AJm|&Xf%Y+*{~e_w1t_#6I6aLI z8$!JfYg?Op_gcR;FIn=KjP^hBi_>a&k-2zXiYJuDr#OJg$u9}lz>k_whiUZ@t4~5HLlkY>4 z26+~B;UdBe!Vz{MGuO^MYrkv!jE=6uE8|o~O^lI-xheoI7)8=FZIhZ1~_$ljaU0x$z1N*9Y>byZIIg9$W4rG#(hxh3DC1V zKzTTsx5XmFc%4?_MA}=njBej)89F2uzpHb*VY_spMMu1_D4VvaN?hpfYYlL}IO7xl z8%6gddNb$^Q0wuZA?EqFg8i4RD^f?F%l-JYTgzR&!2SkVMRdC?NdMuJ28)xWGpJ+z zlgrcAx(ba!4N3*a;$kWlC`m_P=A2eg>7mU*enCx-Zg;OcLe5LO^X={xclxr!VZ!rw zjm7k}u4_}%u2y1h@|7)3H#c@*LKs3@kcA>m=v|KlSF7F43(!~i78YIBE56OM&Gf1Y z!k@*sX}>CX@Dj*decJq`$Y;7ssh%)Ds=@yK@Pdi(9mmfGaL{4rHYc6wXSijG&-FEc$GIUjjK8%rIxcFGBu9v)rkeI$r>q64>~1D69cc!qGyR%aj-$N}FztVK=I(zd zf&cycz8~QCp7NZ0LG8$aH|86xegf#Yio1iT9V3C+TAC+Z%7i9f(*rH}qNm$EPz$$~ zUX6FBdxn)Dv^rgopCG2H{IyY1l6zAItY#Q?mI~ypX5K%#L=*tfk#HpPCT@KfZ4Y1r z$x4{C_9U)1Hb+4onC2!nh3;I%_AmkQC&v0e0VZ8xV3pvwFp9F)^)VEPq8DSTUlyi>{8F?nz3S^6gl={ zpCs2}@1Yu)l??n;nPeercaa92ZbMQ8l)oV<(x*0MTFi!{Vuv@WuC^kYkfg7mAlr#eS|0%$C~_5LSY zS8fGfp?J7cIm@!S5j&YL#ft!Rz4d2&otitg_z7sW74L2jIRxe?j})uTd_+x5?R&(* zb<5MhvPnZDw^P6epoxi6H$tJ?Zf9zi%NZqOVKb*e`p;m);XE*K5L@UfuolXEiYW zGfYrzKTx$zhr{|0{bNkjj_+UfHm^Uf!0lha3yh?M`X~FDUSyQ!epIh`wi~to+XPNu#?a(%* zaB>gU;WPKc#yPFk!x~+P)U+`2Or|;E+0~QO`P(J+&~|1{h9SUZ5g|kj9rZk1k|3$~ z&ZB_tMbYK`>0jj?b;4ZNtw~Ow=*o>t=jp(u~5`+V_59=Oz$HK2m8Td7V+=I^w+S{V}39Z{qIh!k1O=|0YVl|taE&SCG2x!k3J-jVu`eUo( zv)av_Qfynb^{f}Is!x2-*GhG>N31JQlCymXPxG}`!?!GCZ_u2ejQJ!FJCD2Ct%N=2X+*8-ZVc@{V zkRgbx;rew&)=99>Qe1IPWad}up6Pec+l-(hRb~dEN#=q^&NaP|bnF|@EJxx?kXl=;! zsuDS-lh^7+tyETa4!COME&W(W_j_&Y)np*@_%4z`{Bn#aqaN6U~G_0~CXnOQt6qI253-^5Q1U z@^+GI1wRBn`r@~CyESN4`h}~3z`&IDWk049O<{sNuL+;?4FP%0s+vLzomi`LeCh)< z$@K}6!idC&-jPHmH;4eZfpr2(`wE3q*u0UDzPa#(XA=L>EBe?l;>2A^>vA*c1}}%D>hklLe=T@ywvK_ zd_O4=+k3pEOSLS~bzoOu@>30W;I-Du66ld7O(4=)Z~qA%QmzP(%C5x4mSYMQ1283A zq^37*O&aiJeBO>|8T@#gq0xO_1Rv>XC&Lw;8a zesbziR!uH5_4vCljGis$SqB@wu453a?>f(yRz{(+%a8iSnz$$mXHXZto?ATob0}Ef;U@_ckSPDF-`eIRjwjO5!m2*CLh-;l8)495I!$oQ z5c$#1ZysavU_OmN;vMH}p-75=99S*__t-Z@aE)J65<1pQ(LE;6nthK`h$!7}cm+S>5k)rH*c<%)eONCz$t5^o-P!u_6O zbl<4`(`!hLmt`7G;9|dmkxw94FZCu$6<<+7d0jLFYc5i)*H6;Hj98Ql{QBi zmYYXYG0n0kKu_5KR8Tn4uM9K1%IWVycY`L3l?K)sg z(k?yRO;2IqSQS&O4A`m=ObKyZGPUNkd^Y-j#|BmQD|XRyNzCS$TANe}pANQM#OnS8 z4C4-W!~ZZh;^7`3?BZf2lGD2n1NtF&5ow|y>;RN zr~k=k=Tm4U3n1a>2xJ@;dY8tX+lBKosZCMAU#^caTJ&4CELSom zz%^R->{^VLFaf?JGhRp>=Z2x7cSHiwT-9aSSm`I=b=wJ*r|8-D04?FYQDpFqTTRY3 z5-!C*0Y6HD@te$ZEv_M1m2u43&c_O|qp=|uLJZeT>5CQ34iU2qe;KV_deJ%4#t+3$ zs9FJP7nilp(kBSTaXU80L(lsD`ZpZ%W0L7*4pNZn8TckvDe=qAs+Y?)1Big86OJ|# z>bL%(tx-2NRd5SZIC-d6Kp*|Q-d|L5xX@2YVsZC_o11>OAB$URZtX|(;MPgL5<0-w z$^m0v=!J;2RI+!UC3W}Xbd!c*N~4L?x$I^vwrWDF<`VpSK>Bv!I3dybQL<-j)_8LE z#N(;{j|f}alE7ZcW9OE`K8_Zm%h@lDsrAdZ4@$PWQ~Rb7oCB7G@T2l?STFUri^hY?0KD^M+Z=3fvUFJsnoKW4a;W ziLesd+TuU=n88L#Ox_GH>2%{ZDc2rfi{nXU68;I8lUSyMYr149yAO-1c)EpH*5kH7 z#q0hqIj?iDi;{FD8UJdYO*fCL3#J4}&n0=e&n(kwB z>Se$^`Gnj$7os%{<6u~>+?G^$&s^%@S7QUKz84FMKLLkLH|Xt5&5fbapUaGjB+^(@ zWG>b3Dv${w+^Amt@I~q61()fP_ZrQjxX#^W`GYG*G_~sa%uS!NxjX6u%@7jP4Hl{a%P5 z-oY_FBnQ50j~I{DmOB88UKZ>%u`ct2)VYpUjfn7xMlT!e|N3ipPJ`*&T!NY3=4@vK8fJ!#J^cj|nxd2)c?G-)mWphyj2IppL_fDUxHXojrCHg61zW zZ9C}2;{CE`xH#_gKHDd)M~V>>lE?1QAKI!t_2e_kmmqZ^^`&g!|Dw9asOnF6z3TznBk&?$$%y;9^=PQs2sob~K}dQX?9P*0ch ztV!3kAt-Nmg-FTAKOXN?@+szOv{U?~<||{(Y0Hwu#mV^XvxQob;a**VkTI+oZodM< zx5j>F7EHMTnrbU((jD-^Sr$zHu<(QHW+Sac*R==(P4Tp%^upNeo2wIL%uN>18}+*! z;PAYK9p%(Zpm7v4zSG7{-~Ifzt&Cg=6{Ro@-6XsrZS5z(kSDoe-ur(NsbPeV_X?^N z!EAco%EfiTy=J2)1J=AFDU_M+1bVri{`I{`0uO!ZuP}ZZi`LzuM-PIwXQk}!>7VqT54Ap zyzl;pzRVC4THrJ>Qt@OSS+g?(JZ}h!mfz5+2Y>p@&6qTcsaH0ab7N-x8(GBQAy(Tz zIu8I#FG@Tj{pEMoNPA)$+J5U2Nf^Wx);Ybt*W;y%HfQK=DOUp@-42kdQwq_?ba1=T zXr?gLB9UPkpWNo5`h340Sde9*Vd~?9y9*3PQi7g?VifZdAVuk25d@5ZvYUir(24n) zJL-g01#JZb(eM+uyWXeTnujD;m&WA12$q4Kaj%3}G*h4ES_5qXLf!30vZCa-)+mse zB`()>q$*!$2-WRrt(8c5n(FUQz#B#1c)4xCmxM% zo$9AQ?4U)S(olLzmzn~IW`I0WK3uYzxC{ReBLByJdItYa?UB0wGPJ{g4EEGB^T?_a zrEzMMfSf2s5`1-7zo+IZNXP01!qSb*P8=4W!cDvvl$W|3l7kZBCN>N!R*jdG!33(6 zsU^X(F17l4iNucza$sZJl8e|Ycc#@3OECuXkPyE0;i+$JmKnI}5L%e;3Te6{oNhsXYDty=-b)5EXj{5zzr(Vs@n3>IVsj05LMuS2p#&dLCP(42W#cNL`Dr! zRPC~@H+@n$arpv=NE9}vHddp0{YzTt;siZa#{P!|UZysv$ne+|lJ@KWFuRN4zGdf; zSW*ZCsPLs=aNQFMJ#i56C)bz(haq{lHO3j&l|0-Zz46 zfzr6T4y=wYy+?Oc#D$66NV*v!>?D&;=NH_&rm6@8K4K?1V6_mPH-psO1`2boHOf6%e|}Gw{z8_#UK3ybZjc|9d0nBaIQqA%P`*VU_I6E=pEV}ts>^x!;dmNgdusv2Lz# zLvYkQRoi*#{d@fuw@yDS_GZjzLyq@_lWVK+49|Xt9&&UDcJ8?Sa+IDCAJDA*i$U0r zWuUnM4Pq!=>e^U;!EL;!L8W=<)|w-EPZWz+A1hvbtn{w4Cw9Z8PTwW`aMmb_9`J%p zVtN`4;VeHi*vjHB0gDb^cPk(6J=$cY(||bE9wFUFeWX`3TR*%l8!yVv`M%q-#R<&V z+T8RBIx_R9PA#Z;3hZp?z8tmU`_!VSwKl8V*gqd6rxsc(S~WGK#bZXS*c7dk4|?wv z3!WWHq0kwbjqyIr&QQD`(booqX=CSMW4Ukbg zp@N9ttbL-Oh&8@*ti_lTUPHoLh_2LV3#o^96Ixd zwECC766-?nU$-p&Ky1is2YC5dxva~H&Wu&9%}$WmK!d^Z2BPBOkLCExbN3$(!QA90 zgK?dMlvK{*hy8sc`7h}x>_uv!26OST@IWJPYY>8Zd+TM0$wQk9S}tCEiSQdLrp`Bb zb8j${EsDn2rBCN$-Q{f8rfmmrLJIYC^ss4ffm&KbzCwq()cV9*`z{Zyj}CE2Z2sApIT+UrMktsEp7JuK68!MgkNVf3c&prLN=u;DId)aK2$ z)tDt|Hov`A!7yUqE#Oz>wVc^}l&rgVD#x0a%GLP>;Yy%_*!!bOmGc}2q5Cs-)=j5V zOItdGVETAZljtNbIsMQ>bQC{1QRKRTHl4+BbQnc3S6ijGp?&$sp2<@@64<#QhsPo2D%H$P>&f>&Jz_6~0AI*U|R&KseYOU32E(Cd98YVv_b6IdjLP={E3=|Tnn@Flb&6DP z9Ao(O59{{oxU?FYfat{5ht6%Ef_h=O##Aw6KJj+-Pk=XRNKC2r^r~*qvZg*R=TmT+-GNYQ>CMVIi^&Qt+z-uwDrr zBcxX>)QW$guyE;gJv}ee5W|CH_yz!MPQ877u0Tp$=NdsU^|$kD@hPEKaen3_C0ZLd zo;Yy%;7aQ|d>UaHMOyk%HbqF1c1^3E>C2L<3j%DNVv>+33`dX=1N4$A`yT2|YgU{d zvI`+HFahGI6)X)@>&3^hH6JJO&oB>UkF_-PaD>E{h&hjzI$Y399}|DyvUV&r^s%nC z*SW*n1j@5w@O+pkg<^Q=CBQ$=@&HKaSiY~@-qF#FN5`?XQd+vRPY+N~Ogp>TiPQ_a za%$IgUXB6#kg39z6C7!yt#=|`LHL+stuCD6>v3oOEsiQID;6YoE9-DSM#rD-xt|zC z?l>IVifm14bX{-CwHZTpr+yf-&=!wxZ9@7JY7R6KH)NE0o-FK$=J*_XeeP`|E|>P) zqb`ooS_>)%6U1x?D3iv<&7)MF}%xYLja3nBEowMfrI3198ArnPUmM>m4?p z(P>ji_k8JW*r-~A#hCqvszWD_WTe+l43c)7Hb0Sx+u?S-ld>WZa#yJ#Qz`$!1r+b)`XKwK8-Mozz8&esjN?OIdU|LAnPk_z4%jC7MsMslg25$_ka&4I zs(*^f6xOs97ruq4z7!-t|JwdVu`cTYY_aCP*vNy_3E|9lnw zp#-~m>*kkL7#mS{@M1@qU0o4%v^MZXs`7G}Df}V^GG?)koq@PnXEt$+4bCv{l*tUb zIOYQ6o@}epF01;py*u116mZi&R4-o|?u2=` zt$(GE*Z{m_lsv^&U5BPfD<7pkuu4l6PC^m{dXQWsp)ZaC1LriBlG9n*MNL_tI5`dE z)Rpn%kio(Xj7ZQWf{82eyw|-t`{Q`|fbI$ zUvu!&(~{|VWsdxxUf!Z-z|!Y#PsITkW7V~JL9(Q`54rpbHN&Q0J>aX!<2iBqRePs_ zT>)~cC2>1atI7NmJvhzJyS%%TA71cg^y=iQGKcw{>r#kVDsH<#tz>#P#(Ks8Z9i&$z zy5_HDJWBKKlR#9;cQ)vMp`~2=<{fTH<5u=h9|sEX5cLzuu!(Z#?Ibe-^NKdIW_65w zj%N_-@6T?LMx3r8*6i>X(>NMG_}%g0X@O2)oPBdiU>mf&aVAtYSD#aZsX4#=rOKg)p?bHAXyJ}2W_OF&{*)A-{6`yNuF zqz`9tVqcuWQZX`aC#C;u!l&5fBUD>8?Qv>eiq5A@O_9a6=r9S@H1wk56Weu7+v;-X+DyOL z_yKj-f0zyu#p9Q|^uH!pdO5)f^r9|_4&_k?!(k+oH7KUI+z?OC;%^js&SjZmZt@%2 zi3UwB+8d3fn@aU_jMj#5b7A#4rueb_J;UItvSCEd7(HqGCjditH0qq2@_F;K?_HY!N*~Vl zVwPmr#xW_y$z&pb5z*9>%LB@m0Ev3K35~XhhECywQW*Lc2)6yiLy94wh*!hvvjdc>!K~?}8dB${FWuUX>a?~Bip>6#@-WmDB3^{=0DH>;4Y7PfsaNbWfA7~c~p2H!G|i*^8Ym)OLp5Dd%5@vzBo^Gdgtf-xL7^oem)z|}kF(!Z(AThPs-;~#4D z$agb6(DL-;esN6xj~N>R+IfoZ1p5h?D-vlc(YX3v=d*&|e2fMD=*SX?1?fQa6gZbI z{iYMj$)Z-C8s*NR(LOku#EKomM50RzwGs@9p-LtL^8c#(Hlcbr4m|ES@6!*vF(Zh)%3}+7JLc0XhrHL;q#|0I@29R2YIL6yh_rH^3N`Q zN(akC6^r$%Gp}@FB8w)5tDlz@l0zr>cq!Gn7sli0%&EyTDnfJ&f7F`5ruqW((^q_? zi2Uy*{(`AoIXfzc&_a=i${tGAU}*(WVkZXSeyI1{vGK-epW4D&m~i#fcHccBguN^* z-|l;12{gne35;M)bFG_3ku%--s? z+XocMl{M;`^425lAz{GIx5NKMF*$v=cW%)V9H>c{qb4uAMKO+de(j=(n%8mG7Yc1E z=Hyxn+kNhr5?xeaXJhZ{+Axs{EUFG1o*{(i+u50}S7RCx;l%30S&|gydD#2d4jgCv zXv*jSH8z{U=r0N&hW}p_z)-cP581r9D?_VEQJw}Z5EC~2o^267(TN2(d3Uh79(3Y@ z5&j@cdgk-y+`L+DiPj;39JAtuG6QPm0$Bc0EApQ||JMd*;-Mif0OU*A&{z%9^RVL6 z`%0J_{g|zK?T>nMUijxjqyM>7=boQDp5OaeHQHrw*j~qTsD#+<3knJxR@m&_=5}gb zx@5E|ioGa%#HnOu4r&v)ra=c-0DX0Qfljvt8W=X(b)Tk!OnV(tFDXPRun^vR{Q_6F ze4}s``3qPbdS@)+l~W~)gb~Z_j%T)jvw-%qkJ#1usZ_mMt*Vj;9_tfJlJBt;ya_y; z(}1>-*yJx-2*wY*kGV29YGS<$vvkkyhEtRXFT8#JoQd_P#d@H4;TUhDxK)TMOUNwN zhJXZDq^71U!Sl(Ru)6oVuu|Hq>=k3x8)&#m8JJwHbIsGe)PuM8!B8jKO5EmUX%pOniga2kTT^{|?AVUocG7l(xNh^nlOLg>OuCPDI{v^!Z%5J>2h+!Lvr3jrCet6z zi*^bb@IU;Y%!&QyKK}o8o8i7i{}Wt^kiAoj^bU!j&n&Qw-slOu-`y`YA0Edcx--A! z#C=(6RZGtL+Z`V=FfzVSz|@UueOouz$2+s4k;6$xH+*oUL7`y5U#!=dvwDd#0d)`< zNb0(ksHSs2$@?n9rTAj*N|_jK0Av1-MQ-j`0FU~Sil94p>_^4}U=MeBZ|!-2lWIsd z*Cy{-#tKs4*fVE|_%zD2z zMpKpbIHJYw*R`vnB118Bx>E_5&dS(w)U6Z*fzBy3XwxNsKT+H&Xkd!#K|{t?Y)pGY ze$`-Aw45%EV(ZZS)q0m@I&nO%BqLDvb9%c4&aJ0ryz)E=f9AnGq1!n%{Nd031lU!7 zzsVRShDD~>o9+8%2Mpnx-HLJEQmyVhoy)odm8i=zXRLf9f#}obhSMN&ht@>rE z=!SI0$F?729-P!lMw=oD`$0L^xNp!@G-N$eO~_9(Gy7$}ns&%O(?qKqA71Q#-92v<4)QJnauDZDLpxm-03R3D6oRU&V9@zcH zR}{4MrpU3>70H8ezFsir>J2byX9g#o4l!pC$Ry4T=aWDWVwkjh22FQ|hBF9c(!9@q zJ?q(Pt^HB1@)x#ivxVzx*RH+S-s`vj&&PUJ|I7dS|2_8u{QH0Ce-HitzyHr4{NQ8y z|9||T9dG}jNT0tztKh%$wGf@PqZd_wl~R zEfBZBYPCSjiq&fA_;=zKSl=xWvtoUpzj*KC7Fewoh*_~(Egk>P(pq4CVrKkTW7$f_ z=!+xY44o~%U3$AzEn@kwJU7%a`f6pW8hg-|*2eX}9kZf-`^29<-4^)UM0xCN>94tb z`fm4Z=Y@`u_Tl#aHd%5BTJ)=t_6r@eU7L2C%G}SrUVL%n*VU)H=Ns?WV^(aubGlKE z=-1U(3O&h_Tl?F#C;G9p{j6=I&Y}oH>uo23^tYYt=t*AhJ6kG`%{LtB+bAu!GRG0K zVrAMj-s;j?V1A-lxY$X3K2eVj^*;Mq8&l)0t%)7?b8|yevCdc`GheumfI@lB2QB)2bGPed_zsbCt%= zo_-7bzIHV2XbNG&))BS5MStI_v+!CTQ9{}AGvdX?j%xAgAMN_ugP0ZT>wInOy`3Ra zXm2JMVRJ#4;XMAL{bX?2dQvS+6xs&95nx7tTdpmij;)bquBVt4&9qj$?n<`6%=rEk zX$nON^mOX;M1o&9bWop$yc5Cu|07=x6;SB{N$6MwooEr2+P zQ4_Nrvz@-BV>W~uj)$XZVz|m;dt&q8>KyBO{gs#%>-EfSto{CO=q=Co@KM4~4fWb^ z#b=-Rh*8ff(RPSd7X68QdSkWeavx31isf$6czcU!ftlLfZtC@<9UM}0&0^_P|HGJJ zw1(}`aClLl7RG1M-?nK-T-&$axr=YFGfSW zfX`w?`z-p~u64$B_UfJQm=$*Gbc|FB9V7NT@$=PcfwLu8Ar>o0O+?Xx-?tUWLc9o9 z7j`tHzwJUtTmh%v*^XIZw@!AnW2BgW6pvGGaO9!gc1wM(tPstLyIsj0$sLL9^;v;W zLwFIPF2ZO5R*uQRwl-DGj@G>Dit=vooA^cfG&`Cel;_fN z!EfLbUW-4IZ}48qzG5x;yj;Pa$a*v5d=yV6?0u9^<#$`_@%7@#t*k2&JMt+k$>hSc zAYORH(CVT=--RNDXNB~)&33xyxwTq<;iGMw71hEOZ|v@)A@rxY@?4pJVZK((rw5C9 z^!fB4t9V`qpV!9l8Grs{B0E~l^S5Tk+xu~LI!4lirbk~Xwf7hE)xy~#wl3>DnJDJ* zda9Zp#8S-jcik*kaZjSBO_wNA?cAS&o3W&HLbf1Fv{{vVX-q z-oxJE=y_l9{p=|AliLS-V%Ebk;@ItdyTkY}pl#%sMTS+N&Bz9C%w4^KPe5w&z zSiBH#r%@LXo(oM1`jq{f7(o`#BvS91Yn&D6(Q@A_=F$HQD{5oy{p=SiSJH#%1L!YI z0Q5bm`uW<~;#sOIB7IgkFf(rVP4qqVHf$%171##u+H>U~E6guF8k!l$K0%+z_T_hj ztjLbe*Jj40f1~&E5lFj*eM64JWrgrmwk50pF))K3+7L{ErGu@TePp|*wOR3G;=5t- zZ|0Lk3dM`Yw1sVth`R8yFC1DJMtIMZ71Ezb?2t1aKZdnH$I4iKHOau8X zvyXiAta=6-F2doS#dY9XjAq@T*Rd|xM#-Y`+ioyjuA`B&x$7#S4yxz=E|%q zWZ&=_t%wD>EM~=%iN6jAE!b<3+%emgAWvr_v=GllQ_6yvf?V(ZIB%2sL%pRLR)`hC z7Kq&Wv32RCxbK%Mh&&ea9V4tGaJOgsBurPptKs)3)yA@;;jCC#SMV=kpUgeGFD( z)rGKPwo}of5oN*a!0>EKE8DqVZ)xq%nqdX*x$x$*1p-#U)4<+Iw#V5DK#zvxd=q^b zEfB>D6VdV8ARrMR6@DB39xSl2N=OT|rw=OOO!qjwlbMD;^DR%(h&vqiT*7utNC$j^F1jg4!7T`>Y@&uORpcoh>~S zk5g5){P`a0-brDDn6>d4vJqq*&Y%#hN7_L?#hNBp0=WyE^}+iKnwTx)J?2%pBJav} zaCBzd?fo2;0ddfm+19`s!aK1ZpcdE<%ilKpiQnKoGr9unV2{Mlf$u_X$S=3@k9vGi*M8ae*D^QfvNH9@Lc#azv~df zQxTb)3w;J0dH@Quj#MOW}|l`CRJK<5O1UvyXSllUFC1-=<#bpf3~`XbsQu?fPY zpsF|vSRi1BUT2u9XG|^X3Uc>%Bfn~v70cTei|gOK4@>#3eH2UG4u9WREcUkJ=Syz^ zqVeEGnmdH83i+$Jq3GehBd^q>!wMb`5|&*7UJSS2>5k7~{j>FSSy6xA;!ooi;1(#4 z-OtT-VTMzO1)?9K9z$3`GYj|^wbpQZoQbv2)fTg2p%#iiUmGp(>+0L3(;1o@K=o~l z8J!ntxz>fhv{>8_`-15p<4gmrQ0y3KzteL+_e$YIiiI=S+V?W8gFss zT3~8?Zs>k4k-R|h(2G>zVpCEtJMMPh>7MOU%wS%ys_btQ}KZQI*UZq28w#n|6zu7;<{irANA&O`jH`4+gI+n;h-ai<%s zxI_6_LWpB0+Vg{wwZcl9g;SYXzD)W0UFK@B? zx)Hv&7*@F10q_Kwciae~yl>ZjGOd;!HPv(QuGESWy&>OrYe(_-kp+bXzs=9^7vD7B zl3&7h@vdwa@5`&;Bm1_*zlE$y$(7(7OL;Y4GufWlBG!~|$uGx`E7<~@^ZV0BeO8Dl z3uTE&hD9A^;c`P%1xSH~)J0-@;s-z6pNeU*I9*{^oZ!(R7$=3RLNcT{GfApM$+!%c z3>cp!6(;aqtj)EeB(F)b8^gYj@tatXg~iYC7u%C>$yLgE>DadYYqH1Y5uaiouy7@v znQR+uVLGWK+#RyKOT)fG|SH|wL`9{MjY=6$@jM~*he3Vcg4 zxAAr*n>fq4Q*3shLwZ*U^xEkXdqP}m|hp#{? z@X9D}STpdP${zV`lW<*us+;KbY=iLlFjzJ?5}O7f1p8&>nf+&mzc*VXAo3FSO9CK>jyKu|3g~{F}ZXE&s8shb=CS6=H?>D`ZK} zIG=T71*e zGsiT;3ik5jZ+L|)u2T&QAH7woL`lsjdE{sW|20>Z_#gP&7-0^t0cz^X_vBH&W8gJr66xi$zYRLzW|4lkl4S4yZ23J(Rg&&ZhQ5{r%i(@f(pN|8c3Wepk$N*?MR3Lg=N^cbpDT0ka(x%BuE3q%REu)IFV~+ip*F^m45$DQO5O+C zm^H|+z*;sd5bZF(!O8(!#(Vx>HhYSU3VFr(6|A*lT_I;>H6Ou>>wU@!;mE1-Z)#f|oFe@%nNc?B?`SzBc7oc%gpg*9#8!LjS3R!%u2rGhD4tnrh8R~?+ z8DIoUPf|wmvLh_gKOH z%>-5Z_UBpXv8O&e#OK#!zXj32@38XTtUwjN3mwBM`=UQl^;yp=eUxiw`^~VT@xA6=+xYtKnoIk(yDjr7 z?#-qxHpa7WhVBpC&wUVKg^15WTOo!O(HY;074TT-oGelsVuM`lBqc)c_a|C6gL7qA zu`#a2_$bzP3(O4@D+btOVX`c&Y;Wb^^jKkN5tWz4tS}x6%?j8cvz;e96f4?hyFR8! zU@I;$D_ZfaZ;WI9j##mI5LU=(&a&`oS-3t6s)-}jve*u(!wT|PP*ykF8}Mm}zhbuY zf?~x;+gt6en!g#dVq^51Ry>MF!_N^J$G#5Zft*n55lXS(;;N7Gw z&3^WF8dh|8R!H0HE$Xp|S<#BUY-1eri=)hnFe}7X7T3D2(To7(-K53N%8FzKc_sc1 zBS36j(Q(c5SX}Sh812{WQN*lhw#DN0*Gda?5G#7~%40!Rh@Fw;DvMdg3P)KeDqAPYGgrIICH1hVFm0DkV2Lb#R|*2nYh~}`FPFl@%mmr))njf{I&AlzY10q zHV@txR(K;$5espx5RVmXh16w*@>qDRfU=26;Z|QujbW7IAidoTmeQcu~F?2I70kN2gS7GPHNTC51o^j55(+|33fA=(PD zJQl<4iWMf$J8`$W6{DsxE1J1F<8{|k3j|oZR zSiyPT{cYDexo*i?YK@iOmzWhR-^TGTtV|17tRU|uMuphIfwH5;tP$`c&CIF^>u}+yt!$G~ziq<*T8|_Fx1}ln6S(ve=$lIiOLS#Hh9bHj}6~7=>@YoR6 z6+u?CVjaDh6&tMwwa)Q?75^iQ6)7_&o#!8$r7UmE6QXU9dKnhVTY=fpNn7Y_3ydan zZII#ipSIwr^q3W`)3-L-kN(Rnzaq#An%Puzh0Thf zcav&&Q+*qwD~J_s7aZT_TU&n>=WVuJ-s*bj6Y)NVx4=ADQHo*(k1~}xkneP17UadV z;-tk2nyIj(6=y1pbw&7f7{9yrTYy=itPmP&s%9%RGp0+-)Yu@+vO;+`*=Iquu`E{f zlgentikmSj)_$Le_b|K#=4-vAE657rJa5cwYFI%PH#rxJYFBh!pjpFs{8w;9NTdx? zhZVD3@i^1)a}vKBx4>F#0kGnKmHt-=u@LcYqJl*-)kCDX)6I-fUP|SGK;{0%SwY{^ zaS&9raCAkYC%G2eW2N^g))gz=!twT3t_4nI_ot6$PGyf~j%N0!ds3T{ce@cCy+Bb> zW`v9*RdX2oSrM9Hp*)-9+jLp+)>bv9UCk$yOPtQE+%c`)Eyt`_yXPj}vxQpVXqsc0 z=*OtZ57nS%7E?9-(d%tiTv!4tMl4orPxQvMdE+QZ+yd*W1uB`@&bwW_XkJ9n0=l9p zRv;V8vO$;?7gYU<{URJ0`2Ujep2mB8Nk8F@E(RQfQn;zGwTz4O-A zatn?H!4(vRyeMsfj9`)>JHi<)c-p$%ZDtDv4S~)zckX6azEKy@wXOL;4v#& zp-*kpV?1TEqRWYzk}`lkc#HG_QKFM(H0^4f73!PtV|oj!-HgyX+Y?{6f?`%| z)PB?o$8{=;>bn^CLDg(j?Oe{sqP$H~5g^2D7gfemYussy6(hu$VUHB3qDQ$E6-c8RQb z3l<1+E2`_EZB}9*V^*w0tH#@0jTU&daw>bBXu-<|*-P2e8H_Z==rPpXl={3<`9ItS z;W$a~?^~$koe(PoIs@K{{Vh2|I-kv$6{~So$G@=>E%0HPS@A*N2UI_gNJ09-p3zk5 z^D0)5wz!~TB<$T(zD*VH2vS17Lo!z4W)h{KWm-2TD^A zD~J@Rpl4X2YI$4Af_OpWPya(IV@1zsDq}#XlDE+n!)?j}Nwl&B68kp&S77{ly|lpG z@TrWpK9s63tjMd1oA6l}D+JmiY&IdEst{6;FH^HZSs^1^-|F}o#XwGH*GucH#D2!Q zVkKHN-sVcTz)TH(&2U!mY(fE6kgo8}CZyu34lQg}c(Wf<1ho@$A&<0wH@wovwU)OZ zvtliuop|4(THx&xsfwJ<3V18v**vPN{s&kAT_GL|&jyJg1?dWzDSf#8LdRPjut$=~ z`C8Qew8Hx_D_Y?!Z?t2as+tIh?b+lg2tzWmB0QT$RTx%K^}LJZ*F-#| zRmXwI)f2NqS|Wa2KP~WjkyzoW3LOQ}p3S`06``!)9Pfoyg|LD~e^8~Zj^Xz0iJlf$ z6|rw~{q&D`Po)KF)%QJ8T&H+xBrB-yzfV_0v%=OCc-{`HV}z)3k^Gv-+kDj0z6!K` ztSjUSj33ua3vAAlUsJ0JumbT?D+*$C#UfY%TSL{(BVXn))!q9i8cj01b$jBk1M8)A zR&GCIR;*mx##>&A7ML4)KYKqH!3uabJyzJN;x5eqz+*-1>b;g{QxO8BU{;W4lSlzo zak683TVlsdOM5l>JjSe8iR(Gu=1R3dt@;5pMSv9&1z~SRGU<wRBatZ39*QI{3kSK*POjdHJ02S=%j z;jPu;T5XTj-LIGxtJ~i3uPvnoUMXH8`$MrJA`i=of;ip^j}=}F#LOKcwf${YXjMUe z&CP>L+2f7$-Ix^{>1=N7W2u%-5h;|f!spp!3k2iF3@co3h03oW8^qMZ@_ZGJs!-kv z%J0^yV(Y1FYv*6tS5(XjyEWqHYqtd`Q&W+`=hx&BrcQ1J<}-E2f}k1>`8H)<5O;j3 z<<-e-rUlN@_;YBAFjWzZmxi;#XbQjm!PbXF zOU3>eX@5QTR9Lk|V^%EY%8kFXx-DQx5$3DV76`>ljRj(AS3pysir%%Zkj!pJQ`q(g z){Rw#dzNEXxa|?YT6-;^tdD_cRe@?)@Kqe8%nENTh?TJkTEJgH{ofr~8cGUQ6=20H zEnL?o>?(*^5!Nd4duyi!UN2Jal_rJHSE2GX{eDf(tqAIhJKbP~=h37(yI92f=vQ-r zQ0*&~RRL0L+A-C#l{Lf9e$0ySc8T9z3oS5TOBXKnZ5|X>7z;%5D!9gGB~!61kf65$ zHVCnz!y|>_gv$#0j7Z_JqWyl$*7lD&^D!%;S|)z~X}7=(WnO7g7z@OgS7B5Ic`J+s z;#9<98^mHoeNw30?vqhbXt0{g>Q5CR2{N4(lPl#p%vREOc7^e9LEmCMz@m5E+vKHE5E$u_hinVk$ z;(g&3cyaVn-~9oQ!uTo-DP$Bn+aJsdL`x&;Y?7yfmM+p27ArhbP^~;6g;EuY6>n|r z`eEzNMHqDz#;k~HnfU#u)dFYB@KW&dy!LB`*&ompo~lqI(StD%<^<*)LxzQ-g4Py4 zqesmNWqoK;C{|D%tRK#H#@XHO6%ez+ZIAfX(`|wI8jTP0ND-zgY)=J9!J|NI3&b7? z0!1OqY&Yf(^A_hg{>+=Ef7yt-0314gmndHQb1vNr0{eFu|lZ|(iJf) z+-o6bh1(wStEbfh9i!KCQANx|dAcx)+ZR=By{;?cz8EX|QV(*BDON`MwNDYONGdjIXQZ_}J3Jd0W3 zwnzMGwOfF_6upWRHY*w>1xG;q`I@e(xX>9GW2(8~aYDsPX)X|0AQ(}WIN1@89doaR zm=$h&#IIJj1+o=*DL@K`7CNhf?GL|4QzE4pg|1bF(iCnjETbzf>e1-T1+hCUJ)t;( zxj?9DH)cF0R{ULJ(~i|`iM9W0F)P;oIoh~;I5$KiK8O;sXe%Uw6r9n`q`x+AaRpQd?lY_ImMp z4tl~~$Y0?jg{LaCPZQZ!LJHUZz$g%zlTb1%{Gxk=D#4%G}3&`~wKdw#-knK@IT?!&Y1Sup_GtBycmjZqYSRXbi{Ho$E z>57Y#sVTaGJr$O=(44^BVz53gcFcCtEXN)zPHwHmqd|N z;$FOZE%40{)ur%AAwJFJB?YqroS2Pbg|R(6O)=Yfjl31?*F?qML^2+c?!PW#UEyz$ z`1Q)Pz|?rJs z!bx1pu1qVe=dH%9SkGr~{p|ZsDd&ntn{x#l<*8s&!1fU9L;Mui`#e@CpQb-bDt-#K zK5S2gs=`a8xS&V@R;c;T?L2N1`OumIo(fuMO;Hul6>pc;Pus56p2n_BIu`(?5hANXwD!eg|R`PD?DF?QWfOSbV)(9Fc!$q+D>-V z;<-TrwtCEpR_((pf7~EtU3W|PMwg-_yYUG7z3Xc?$d4|l4&stRQNTKWxD^FATDI8J=D}G@#g|Rd=?ICCDpC+DxIUg9c^;alDH#=-#JEm{-%o+D zVi#5PL!za#v>!6xv9>~l6+#O3YLcozcDJyiKT(M-kPuZ7>xyRjM7*xs0yDK&D<77@ zh@K?zLFGeX^j3mq^SRr--?BXnCzSO;kq_gii1Jieq|kGJz@HgT3d*U_aZ-?iX#r(J z(TzG5h^Pvm1=2P*B%GR0R}8Px^Y_^E#74q8@fOg>8R)Z9Ema zN}gMFH;NSg+^Y+oo`9bMcF^7K#15i>j}?JfX;)&0iI>tS)AqP}chr>$_K3|1TRU~#KlV(oj{zN*iS`q52z_k#w!t{vvnZE_5#ydu-B26Ex z4OkcCEl|}W)T3qvNC7VemY&q5tdA6hUWXKa#f%?1`VsA^h$e;W)2vGhvW@Dn0?`l3 z*3738J0xSXKXEiOAK6pUwcJFWO3eo4S)}Lk8k&MQ zp)>_4iv47J2r0Dfp-Dk8QWYgt9?b1!%p+Mk&1jI%20U+*z1>bFm8d<_B(d$*fr4KXq0Zp-=d?{QNOL;7a6`r?(ERe*G(;29W*{=S! z{i$Eaqe>g_>Bg+scqE8ABFe?CJ{o?#cz=M%KnvHum3XBlg-=x|T_Jh`j}|GE?Lnjv zf2PjPG+qj_Jxt7lSs|RjuZpISdNzoDSk_N~6l{MmDRi7vu|kuAbIY}+04ownu3q7( z3KcJf1(MiNNuSQ-Q^?rtNlnGo@){sT95apTR~za585+|yH&i4>TxUYiJ^ouS2MUV9 z(-a;l_G=5oVFgqM#(5}BaffV=(^*75V0&2pOi~nVdxTgX`d1quMYN}a{1l25uBPyj z0<1uULum@v0_nI*kbw#5swcgg7pO~t4jDA?|Eub1MX$a2> zkb67b*A*uy0>RdY%Br^Q4@C+n3AHFvs0b-We;iF;@NP*}Fc{FY7Lz6;$n%K(zl$%{476?d@ClV1U zI+CfG+Il;$Pj7!?Ry@77UzuP2JygW7QV}_2XLyXz(FZf8(^C}6S72!hM^$*FkhsSk zihEQtiWGi-CXoVO3b8zbl&}@WC9g(>;)EIbp*2N_Pg9Ws<4M_5;rc0r6wnm=X|HL{ zLDm&k41`I6yl#-r2|{%upA+xz7R z`@4y9=2RAu4=a}fq_Auc z^_yOdMHIycUep6#iqmzhk9wX8)bfX>s85RhsXUEexqw-Ol%l}ji>xg=E14_hSWhhO z3XWN^xORIw-$K;0WAvq9gJ`!m&;53)T%ycAkf zY|hhI54Ln1PJj^sPB@yvijtC_!Xkw=`a|NRL<;58tfwj1{vclkbAq&ms^d*+qCFqy zW-jHrjajjj)?2Ob&WyiWVM~M5Jx4T{5LVW-u{o3lVA&n2uWC++p0G*b_fiNa*h_&3 zDOI$gxQFefup=Li?cphyASGNy5vXP3BLzohVPl}`l|Awtq~P~$KLuMKsJU}Avp)?! zjINBt3HAPpMg+5FpPk`@jT}%L~z1W6P^dtRTJQZ89}O|qefAvOp0(tL8MT& zN0M@`XmpwElUN^mytx%26%`X8g`X29M#|QQ{{85&AF4vPM+zMyHJais(QNbJtCh8I z7B~7n#H@IlXLx?1R{d`H6^zs%U!+(0ni#>08c|_5;V25NCOj?Sr-YvqiWFM;*Yjo~ z>x#XZDw9IxaVr~$NufF6_h*u(Sb!6-enLqR68rEpMfY7qK1fq=lvL$-z)zu7PcoG* z)Z*OBCUP&Aa<#{-SW4?H{<|NK|FnYG=eMDZFdob>ABCqTJWeR}@A)P;-Vm-Ql;shw zDD)UopY7p|4O4y!Q4~7siuS&qqEKT!V0W%?dAHJ5o(pQHOB51HMGj6Sn1H zq8{YWr2a~|3_bgOw#R15wW7sG3fh-YQn1~@Bh4*RD1WBUqpAHALJGqP_%!d*Y^b6s z3@Ipn+I=cJHx$P}mvS90=DRT~7Sn2r`ObVTTX~yi*SJpO!KEgCG$TA^;ZY)}CSY|S zYtwiU{dNasZ!7-<_fXEvM6OcOT6AtEMu<@+w`5YBR@s?Wtt-t5Gd8R_QgGxWoD@n? zII#~Vh0ICH_6O7kWma@w>WjUYi@E-m{+*Z=OK-!4wxB4XvLvL|1Cc;gtMyoc*rSR+ z$~#JTc$|n-6PAy_RTIiP;c_Bic`z}+2v!t2?m-rVH`<)`(^eGZrO?(#eNu>;5bL8J zDZ(@bY!H_e7&EqM$NgNKdAZQlwsN1xtXR3WeKH}GU?rh?8WVv@!F(`(jYrd?M5LN< zDA6bhiv8(gu@+alTO}`Mc%O(_5#BCKf44?+ej>i%*&D(HRYi*K&$PTm z3qNB$;i`!sC$taK&k3)0sz`%Y6I$= zJ@H84k9>d=^-1B?vZ<3#0b&?ZfD=d4AC~LoSWQ*4qf0+u@fH?pftVEwwNUe)&(vNV zeT8x*Xxz48fiOYwLFsy>?hPqiPWY{kutG z+k@#Av^_{uklqnZ;m_f2iWIPWXi=UDEAruLii@2WJ5Oc4TQJ9JPySe1&Qh&oM~{vcriUc zrbh{z6G~5L_0PQ(wnv__xC5lHZ4a_OTuov7Q!laYv4HhKQ5fS@HKb5JO|d@s5z&u} zq$f^iJ4Szxdcvv5%QqI}&1zin zB4)+%FyhI?%=mZ1y#?i&2&f6A=GjZA|0>&qIN{FksqWhP&Moms_x??HZyzNTC+v8n zSBnCD)8&Mp6o`8uiyPxTK2YOVl`VvQ)TB`R$)vC~g~csRipH&v5c|Vs#YLL+<4*Ve z+)v4_n5q^Fm;0u((|tE{?Sp4abLIcO^-#e*WABVVo4nV(k5t5qqX&o*$J56%^q-bp zsSo<<*)HyO>c2XZ-I;9lC~eG&`qxNfpVq4En+U22ixR&3wDJ;KnV5d>#6rFa*Xqb) z#HM6Wc#1;#5LJA%2ji=>@>lwwBczl`;j=wR580$(rje$IB!yx{)1(NqKga?hPV7&= zUgY^YrpEs|a6H300e>emf9yF%uRBJs3~kA8$)7ECjD9?RYv@dN%ixy5`Pv74M2ZZ5 z<5>C_J>QwC7I6lzmfu-YUH_T!U8z5IT`4toHEh(iVpc5e%+62zw1OOMiiY~sgiDE^ zgA~*Y#qMye4$TQG57W(?2vZYAPdJ=VTEedA#VqMUJ{${4e9}fI;$E~5Ioad|M$;{QV@ro>ETkH3`F)J2-?qIH zA?+y%`ZSSGu_@VM#WVgk0pA09{*Aul89c()AbTYHS=T|u1K1h|)1P-ANM9MEHM7e4 zfbJN%TE;i=odfBesp;%->gn~*^L%Yj{)EyXvfeyZGJSFMyW#JK%VR_e(iFQ=9V6tA zDz)d^^XbCbQmf5t7_&mIjpiO})wfHCh;Am$Zj-`K3Fd@ahrAQYKWNzacy7 zh=>|BA)GK{%l&Es7DyN=xE8k_Yp%Q$JT^?zO;JwvxSpn{PYT{Y$7d2j3d$-+<*RNx z;&HCDB~LnmSa2pgGrnaI+TtGR3Gm@w_p?dR1H{0)2UEu~iKP0BUQcJAOk_u~KGyxL z>sV&KcDZjMR_v=hn|wBDXz}Q^_QB~s&I!5mwNH4V2=B}x= zzIM!t=Fd=h?DZ0QxodUUY64oquP2)0M18A6IHAUOc;1PCp0NEBqA1Ar=%L!RelNvl zMF_2iSX1b{t0?P3ks>nov5=;~7%}l^V&uncSK9Jf&esm6Kka&khyX9eeC=xa09hkM z1*Io;rSNPrbD8u8KR=#%CIx8? z<;uNo*eZXUsFvUt;e~#K^vr?u+49`*r$*6`opU_%a_0Njn!kS5>o;OnGpevfsw z3{Gc>89fKnd-6AP4+p>scslUef%K!-=4;0@k9C&DgWS8Fm-~X}{7~U0G3M6Lz3y`5 z3V9}=X!hjGmFes;+72|!f%FG`&uaB@JTsX&Lzd0)%*~wfuLQTcvEF%lteC2u~hLtt419xmQ3e4$`I^tpIU_VPLZ{~jL z%bZC8;wdkMT|v`R6ztLbR@cz+lR~i~(xa&~g{GK}#MstHAfp1LaD58%wPP9F+3$6u zpFSFzszO1a$FdjV+2oll`u5(!XI;BekB1))u)Y8*whTU*crP-`+M<&9LHi07oJH@D$mD3Wnr8viE=qew#Y7}UciX#=(bTj9?t}~ z<=^&3dA*vfn5j)wYh$%4st(k~?sf0Uua{Pwr`Ua%7ZZ12w({BLSOh1mOa(V{!i|YK zR)?o1T&n}RA$kee9(E?Trrk?WOT;%X_7SQn>XU-^)L~eF6vqBQob+z@&eWULbEV6D z*-EttzhSYMOifb+^k(ix-)CKi3iGw;EIgaS3huve&^N@-39dkU>`H+ZSId{_+w8$$ zTVz-2KzdJMR|+=9%^Y8Owd$?mD?^WlJ4UmWW7_*UGyV+uE%2RPDXf)M3gu>wlm>AD zYURk-4dwkfGRAh!`31bD>*X3>ct2N*6|Yj&hC{_0bnYJv6pL3VzYcA`9+bu3G$&BO zT6&UtCVGORp+7IYLW^s6sHmu?C+d4K{Z>aLCoD>!H|o1~T|I%?wN$@`{vL&wf-B+? zC#+gmiWDz-M`vefUdOn1i)VOFd%FZpVS6f=6thHwClhl+pLXHSy(_gly(|4pa+>^# zm-|i>!GPM>Wzzp*gTM;;6ImQOZc4A12mAa*s%=w^Kf7~3$H3ND-NV?^nn$ViWRqp_T+Kh-Wqx#k1ZT_ zq9+UYxb?fn>agN$8JTxyvS-N3J3~>gW0X}>8#|U>`}&RS4vLC8oX}&xl;xo~Vf>iV z-&{_(*%SU~N5h=3e3*Vt)Ke5m;sn*=R$dB>H4nEv6#Z;PQP28Vz@ur65c6u;=-exx zrvjQ{SL(B_zYd%&A0Yq4B=v5$=i?Q57RmY$RzN*m?t3A>C;xEZW)A*MeCGgdg}(_u z=5f{E@yX>rSP{oF@L<3u!4(JZ2Xx1=^p-(93TZ%y^Yq}x9=jr*N=owyE zy_9Ji+0iY7Q`MWfgK2*r>H1~CLVLa%tTHBg$PN=)Ib)Qz8@J5NUng~WkO*Jc4_KoTZ zwnwz>p}iDeTV4u()I-L6NaQ0zQ$&&?+WNqr1am47Cq1A%hdWbs4BicN!b~@QkVPoyNR+KCG^cnb=XaOtEkk9&A@*nLa4T-ZbnJpGSUQ!0`O0;9P;`t|-6R_h!1IO}E zRtM!{I-F2GO!zSuvODb2U!K(w94T7I?g-1DFdhrctpnR5&k<7cJz%dm>&oMuIwtl} zu51@Qp@(9T_$Ysd*YI*)?t7zjWbB=BCI$bUqETH_#Em0k zKd#&wI#GN$0DA?V1=;Sd6t1~vc1#jG&SdvhV4*N8uF_~!JbRE69)K9EVQ%KYkaHyy z`(XWnzwn*h>;9|@md<3RnC}=_$eMm<{K!~sZ288rt;99I&^A||6o=;_C&bVL3%>R8!bO( zT}}iokA%K|GcRo0gGYv`x?FS|%6?Mn2*(sg3S}t;{S@^zMXH|lVOQOSpW>M$M>@^9}mNiD19A|yXd~Mx3I4QDnuG#eN z>!Q|M>+i2LE9PtPVuBQt6la1Z3F4nCiN3%iXt7pmD1JO%Eq#UB)z~`h)40$#&Q_%K9OS6y|+iZ`UyL-qpiP+~u%EW}~;7m}^1lJ>&a;|i)biVx2(E0K?vW*TE;LF4} zu?{G7CR-c(q&7*9+pOGle~+K9y+OXzNy@}{ki)t+bLYs1>R)R;yk2Qm%+yXRO)*Jl z2`##}aF4vvN5-a!5V%)>6!1_L*MK*9zBV`Xt;*ZhbTFDik;B}LqpS|?#ngU5?scZl z+;Vs)B=RAA;76eZ9=U%yagR{j<0TxEIzq8M#QIn=DFPl%))a;m3F?`w=+W=c^Uh>X z6ydR`jU6h$>$p>8J+VDh{%Ag1?SIfT}c$s?qqanmb9u6Q+w7w>WZv%YVMl-A4=)?66eIa>g zTL#yUzhk9Y@q5x0tSZ>A^kx-T!DZEE&n6EQwhTVVAtE;2_lRuGuu3MYMO^k!1SrA0 z5Jf?{UG*{5%S?vVeX4<_y%QcMRF8A~n0Ce5ij3`8Aj``2V=7Wu_v~;|i0y%#E3rNN zUJ64B<%=ahhT>dZO<`wgV*kZv%3}nZvX(T-ygdxp6bZ5>aF3TPC-l~1@B=O`r)?X!@df#tm-eY zVvr58EA@HzLQ#+xoCb zp=S|eb))u8&kw|^0#VXEg&$WQ55q!$Ckvx0SU+(5f|UlMqHmPGf33D?P8ePs8TJXh)%c{T|jO7u1scQvonw^ouB^R+{TTjexGvyPKL`N-Ju46<056?pH{ zPI!{hTKBqxXAv`Esw6CbGtT zSfq%wKE(dG+x=hw8XtZ1@$hY0t}1W9;o{!HUh)X+B@ZM%MO3tTyF>WkKEm61WUQ=M z0PBK(y=*ZeyMU4qO333ICD83S`QV~G6<5oM!(OI4B(nl{L0BO|i!<5h$S-4$Yg_2L z-!@u%s(#UxV8xcf6U9$RVa-r=Gx8cG|431>ErVCfxQEP@MOQqV{HLvmgghW`#liF& z)RM<&w9Z2974|u@y3Wvj!hAM0o~0|XWAxE$ua5n`ZiHpN21P-Zhqm2)HVABbS#-ro zD;jE5yW@W5r$mqwp4;i=gN%tKs|2egcbQvoY2QA)`yTH%ufN=(W?Ye2nmH4p&Vq zN>2zGtVhrKXYYjk_4=dsXZmdqMG8I3hgcsGq|k~Yn9UtdiU{jtK}~Uo@+Qxfu9gv7 z;C^TKN<5bwE9vFS^~gl<6GCVq43K@Y_sabYuMj20J@62DEpGG$)$F!WP~t=pBVLb; z-5#dgav~(v<4_(7_~$sg{If3j__vM1vwx!a0`-)=g%iaSMfg^0V}EdZ0xEDEOP?rC zK2_hwO0eSLz=`7H;aw?I3OJa4H2i4z0O^O%x^@yJU~ho4h+}~gJ1GVQ`|Wt<-Oj_s zU@XbsyO|}Zs(-db<4Vfe%FH;t9#1CF;*_O74F5E|SgaCZ>$DHPTB(r*^qVf%=ltDp z&`05rLNUYmCj8z@OG{XkP*#V<3HD+(n~mwQB9s(1Cla)VZF_v++8#<%unnb1VU9_S zRiR^AW>R?jrlX`XN9i4^I>Qmj^F#~okEU0yBq@xp5IWe8E)Ae6%wD;l@vGazGB)Kn z9go|mpG=%5^^7~>G+p6{1mEaG{Po%7<-P|wXl+DHvot~#86LQoNi6p$QphylYXAfDtxulK6oQHoy}9$C;Su0%+pSeM=f8Dq51oxfqc3?8o8w!Fg{r%+C<)C8t9PCD)#4XUn97(|Hm2jB2%|)p z7n6x0eUTsW9&8U(uhCU0Y=0)aFyfV6fE4~Y#Fm>BmCO-}GMp!01z2&ocuQF!atD7r z3{S;MEy4<;CeZuw%BBE6$Cxqpri6crD;fUEd)+VOgYgSQQqN@1m*1?Opt{0{tbzjI zz%d#b3ySb-kV8HZR`7bzA)p3$F`YeBcs#t5YWeV0yD?ZXUn>$R?6x?VKAnBLG)X+Y z*Zm;(W|heb?*dUD>hG8PFy>Dxoe3$>zo8GFO`a=#Jl?J%)o9a6 zBF&)!;!&KzgN&GcRLhw&Y#xxM2+f8HDd307+RuKSOQtv?`{Jl;dk7~KA5fP@*QODl zAFFh}`zr6H&*BKHJE3e2Hv<6`Y8*=Jw={$yhF?XP*Dfa#KHEc$Aq|j1sR@r0#_u9A zQcXXLdOQLh?DOYSMEfbE4wu?P&!1_mk3@3Y=sV-E^g#-+;%eFSQ)rBp;slQ`b3GO2 zm+{Q#i^s!z>8^(S&W8h>ljW|7nmV}hZsyQyU3-?VSY&8U(_IX<^q~U0GfV{Z3@8!i zfvqaU2EiwY0My1X9-B!4>*Y^9CWGSB>5%K zLObb7c|6P+_Ghwt3%BVma%2oG4-N8p_u=A}LA2C^kV=TCHGomXr02+!#GC*>V6)UH z`xB#rd5j=(=y)8`)$*YN=#m|UWbo4wA_c~OMRa2QrjJVN+@;i#X&_) zxOMo|o!6PyN3?|Pn-De8NY*y-!pcrCjKH%XC!i>l?V%LK2dsM#`JmVb*QW4uLNn3I zyYl$wV?|@65Z1|_GAaDl#~rHA{b(5Dy^f6CqI_v)#d*aF=nDL=>2aegK@7jD@b`b@ z4c{7qiukYn-~{Xd+})AIxwpXj2j96(*XwSIf#SXf5};O}f5DXxWdTneJa$3~u!8FO z>DTa#@+bI<5px*(fag#uuyOF5RC?xf(jTUx=N$P$@QZ&4{H}jLR_68cu;MIfFL;H) zQ;euXg%S{$Mw60PXHOnAhM>D1Qf!?$4iC{}2K>gT9_WCZInyfUSgF=P3U*0tjQt+) zYOsftf8iLhfYk(O!#v{AJ$Qc?|Qvp>0O#xoWVzXi&jZDRL`D_yQDJzOwLr}PQ4r1Vn zh4y%(^s>5!kZZb|#-T%57<+kN1zt>i2YHz{Dbn$90QWBF1JL6?=^eZWMa*|JJewpY zfCsn8i-YSERdcq{{SEKq6WHu$$PSU-@CK=rbEU&7QUWC=@2}S5(pWJw&eUu3>cQf}5eRO_0Khd`On&o$jle6zI3xMlli#Hpn^U-2^FC(*9t-Cg))>D{NA* z{ee5ydCCXGRRuQ8Q>91HB0GA5u4-rt*iP`rv#v!H58g?M(c?^H^7Nt#R=ChA&dxG99*TwAp|&4ml9!|04dn^U~i^Y6hw-z znH2-JeZ|j>?O`Gx*VS0kU`7R7AI4K*lS1(?L{rFq`ZWdRUUi?P*#X6}04r`QWr2Gm zC;|Ip<>DW#Dv-flrmK#7tIZ024;3olTgCN-eh&XPW+XXRI;7@OeK>$@^n2aNFGU~b zksakq$B5j|kYjZ`b486#fQN#4fqNNxHKLzbWIWn!mEDfJ9rOd%#WQefzJ@HX;~9*L z!oJ`N$9UH#6K_(*zsr5k?7;O7t%6U$kr(n)tEeuP#)|pc4I=CnnoR+^ALDvK095tZ zN&O!!fcHTV@gafH7&%1qSkoxsErZBY!Lql2NE)snLyL~lY3lO_Q^%==(CTP8@PcW7 zPK^f?Pr6(Kpf}D6a);lbvkgvjG^CPMmGJbP*0ViA?T%12;mgzxR}u|VVv(ExDH2K4 z;-)Na8W|?mhmgXK_*$e;N9prtLNodN6s((EQUphcdD=*kF2Tyj(G*%$bXQA;6d(r9 z%~ho<-YDHBR-lgxNmk+!l!zMbfh#hre49|Kh-czjK_1Y9+!Xm%rj;h(agMMDu9o3P z$9>*hp@tS*9sKd|o;>DOMZe~#AI8%270KD7}r0%b;#<(wOX#=9t6IRzp1kiLNZ z2pYi#6+%QmJ{~?-f}L1#tPhtIst>J7 z{F}HdfE6q2)xd(E^_@FA<`*wiH&(*RGWRImDRO29pA{MNnO2+vp#t^-+2GCJ`QyYUn`rfnlt`6y2d}M2al6cw-A_ zKz=??Q-t>+=yE&Op*Rs#6JGTyu{ivZP!J-V647}RqT|CUfp@j-krLa((-fAcg8eU| zC!&wCIa09ig-3{a$E71SYJZ(5DQFQ+iZ`jA1bPlw@eia5(O+GbEbl8J1xHNfO5war zxgM`lk7e%zvjKj|n-n+0z3Jh=p~7>i+ccIOHUX6Fp+cBP5p}PiNnpY7{T_WAec@aw zj1fYmHxtpQvd0OWAH+y7Z{)U7)Fpz?_;683fx8WA5MiX@lZnF=OF@JhJ|oml10n1M zcMVn+emg|23VFO*CQ^7_KAmHrb10MrukN?1|9Gl=AvMAMCyWyMuInfXdQaVj{rUl~ zK>_@&w_pu;De{)>VKjyDYJwFYggbL;(DsP7KE(b|`bkkSoD|q2Z?6OP2WA}fYYLn@ zK3^XVtp+P_eei-SP_6}j%xYD!m%IR2Sl{z=iI{R#3tWMmKCI5w~XkFeD^?t%C@pF2dU;3>j!!?(el5DVm7=^s`69hq9NK%PxP zWxN^Sgkc8s1zOy_7VKBw4{yoa%G`~8NWPU#icn5?Y9gEx5riOO)U!D1@xgqCK6%Gy zd)TDVz6$sxKtNeSJTW**b)*s2N01apy*f5lq*rmG9x2)>zGcpld3J=nn_PEWYDbvQ z>ODgph-PyzgcNc|v9Af-wU94?9?c6>04Rawa^LSr$3p$!x;&miz2I=m176VclvBY( znW`c)0QY_@P{Io@*emD@>jL-ts2NteMf?TVc zR9zYQe)jjnpZo6{_H}W%D77g-46V!c96t^zbpIhzcy@>OPH0YqQ6h>F^$6khvbtJA zSmD|p?9cQ_LH36i0bw>UDU2@@*&-^oBb;bRQ@CWT6Z`P{DK_nxrP)5F90iDT#_upI z&^H=o1>a%dqlA~Tab6fw@Ne2z#6H^AZSrJJD^j4^(hK<;bYGIOUdY+x1sOkFCDZLS z@(Otuuwig5^H`>b1HYjw8xe}z!-xnsvhcp~-4PxP!v}eWN7(MjKje&1<`Mt(kD`*j zTkgkZS#f0SK{)D1E%tL4=#6giQfgWs=Vv6RWL&g@=8tNe< z&*c`4ClXrV3VK1!UW=LOK%ASD9p2dDr-j%hw}$RxCLmh8`>i(@n9=6ae{4k&$_d?T ztb45A)~I7`SgO4tN`Mw(d#LI)L^JO3-22sEl{uz7n?_gIq;O)ui_jEaR=T31mshGu z7VuQec70vvoYj=BfX9NfyzQ$ac%H!t_BG&Ig8kZPd{qd8Z}_hakYd~D?`U2zD2i#i z|IduiQ7vHfM!fgmbVTlr(nmv|ke59>`dQbFklB(ysofev>=&6rpaXO??)&iA-^?M0 zy%A3EI_7GHw*)p3wr3W$?cw9cNE480TB{f6i^bK4YMvy`vI4(wgYNe?a?enkz<-HY z(v7|{#hjom_!v#rm3=3`D7-V-TSKUscZ@g#?SZ@rjNRufiC>aOh-NXQJQHvN(V|8d zwngl_2S49KvlV+QMaWKyvG!98!9`X|P6h^D{@FkJ0_m_Yuezh@m8 z!<`N5{x^zJpvU4W1~c#+)&CkN1=i&Cam|9H$SN?Tz*cepmW+Co92mZ^e)2J$`N(XLTd}9(e4-}W-TGTNl0-?$3}yC!uDW>*c>ZB z2`~kU;!Y1{AvFBgNxv zS2A55d#5hnnc%o1Bl9jgXVWDG^l9UysMR3_vcY~_;rJ-~3n7z_3ltk^aR{eu32Z0NNa z2brm%(k^HWrG=h*=}F3pBq9ju*aWySGnOkF5?gP}YNnys%+efaC+B;hB$b8WckRn^DPYU!W zoFVizW<{7%fJU?zkix!_qOSnCYeZ=Z)b~U*hOLhqG*XE(v%w1F_(}giTnrnV#P{Z! zrzm9Y|Jn~tf^A|u&3G!pcwrOCid;jz-_GiHH}j7OC;STCs0wCYgKPRqxmIuD+*At2qmKWE_&qO^Q+oRb4DfZJ$)(iWg`3#|oH?tzh z0GAYCOK=@?RRy0#Ss#eMa7^vCnz7?XpX5g$8RNMBf9>ZiG5B4>dkwO%jiL}%{DZ0y zjywJVss;BE{Yn@Ente~yF}V6ms>25bZ~HxAkzhP4#;=?qFJSl{FXy|>u!3sfW-cpl z=DE^b8C7YwWYUZPhzEGKQJzu^>8yBS)qhYV9H48(xZs z12>2wJSWu69P+d|W*WRA!rw7=2=B*zniZtv-HWVqMHHGV` zm?ev}5mHEog<*yNUJ$fBnj?kSAJ8RoE%GDoJ5wqm^M=ZRn5Nl_ko$)Uj0Z?1O2k}o$2`11PDSIyu&epqR@uksX;?rBaDsiW7=w+f56ek~ z@NG52iYsMe1$idsC>sO|DuzN$VE>V;`JXxe**T|TPmf9W@mYdy04Y#?@Bp!5Z(&yo zGuy+zi}{)0wKzl-O406?j6_q_y#>Bz#d`?5_n4}+#`#y_q<}}%igF)MwK}M1&@pacwgTchrJ%&yO;OfW>`U9O{FW4 zYY8UL5oIxA49W;r)75hD41v459nalv91CI}GSqNwaWnUZs!fh7y4hH1J(XctA=U_HP!n1p zu7KY|1y59_UX7(WR#5*|HUxV$vA6Kh{&UBtozJC^QDL7Uett`3PoRyF?LDPtf54dZ z$HN$9i@7U73RoZTO@l&a8}L`KvOt~|(?;$e(T`c>J;PSO3fLfDOAHaVz3x@$eR98b zNdXJQuPLOzG)M`rH@RF0zViAXDH3e^ZcjpaDOBXcQ4}9IG?1eRdt|Q2UpYqe=-M7^ zb4A)+YOma*Hb((gT^62YIp=z zz;7lC;+#9_e#MqC6fb=9ZqGCEQ9mnSc^p@|`j?c0&D9|r&%vrk3egpY6WlL;r{c26 zsn|ApgL1%RmJxT4SLRhStY8}iRs{S)@Cac+O%B9Bqw=h|VgYAlBJPZ{HC4rU8~8(^ zB47m`rZM|afOgy9EVHuUZ)2uxxh|p~%NDV;l8`uJT~gS-3a~=oSDoA{yKe`c!|y3f>QlN5Hj<%63q;cm__~ zphyOfLXxo`f1v8wLEFPHgEJvsCRP1z=K&gV4L!U(b_kDgV?xMZjF5o80;59yKqK0a z(HN{IzA~hEGSLhxYGW0$8^DU`>>Q2pLbXhIH9-p0#*-uRKi*b3yW+=BXoe3e z@?m*0d-S%DNpYtq%=QQ&f=9fN&m6+YG0Gz;iY9Ci8SQ41LP&@z{xp`ftDUOe^SN8- ztnlZ2Zoq=`>N=1s#ij-BIg23$tBSJY)BOImFj9ajn9m2-FGv1yRSPLldkQ4M%u^Wc zQI8a`k1=lP1}(V9@1b1rhXeKW0{jm3J~y8UNu`al)GCM_!w$i|BeHGtPglAcGBNpW^sPysN6?HPf-)= z6TSEIHNIl}G4m;l6=*SV0(t^=2wNgo>C7}TYqt4)npG^o3Y!$hQz5ECSs=s;zp8M1 zjP#lM%EA8BjdftmD39E}Lvb+NAQv_@e9r<8IA$C_iLdx+lC+>D1 z3e=}qh#2M?!Mx<=JmKoWf;%#vH|x>F?~1Chw_49~MQv!QoP@k;jOySK(mb9VaS_yG zGaABvhVjrGD}9c<`cNgvDtxySIpfi~g8RSWLA_`8aY*~1&wT&dFDa6MnmjxL9{!3$ z#pg1A>HSOZw?Fw&=8K+x>He4Qo$0xVxbNcm(Htx0YiXjzB(=;gs#@_3*=k@gYFo*< zwvP}k@%?K@#=`Q-=4&{+=GwvczJCq5GWZT&{g|#DSh45I#o`HK#8sjOEE0@do2siW zWUKos#lj_;)l)|lJx_(Hpy~D>&P5U}&bq9<<2qN$ zR26mEa8EKZPh@jnEhAGC#ge6*8FW84_{QReTnSpMAmdCy;Vf3^F!J@0j=3*ix+#nPfV zR^V4KyVx_SovFOqOW1oD&5Pqey;ZZoXC+o&y}g^S`4@Zb`P!@Wd&janGY7JheK!VP zC>$;wCO@gzEpipwk8(xstDGoZ>boCgg*T$qXmV}8s~*JnSo;o3b~-JJ6oH7LkRbYz z`&aniJ6*Gsb%nWrPV13jQ7owczD)|TIn1NW#DG83$)#XA`U%NL$H%Z~$0X$mE}VB| zpA&vAX9goe{2H4{vA9ZEY=4-oqpxA~R&WpGohgdZKTEUpY#Bt&SEzZ6M8ZgN%tkEZ zm7w;e${pJ&|EUxR@+fyvhB9Iw$m~X5;+8?W-YD7+8{6~(_nG)0+;`0@u*2m^^PxuV$V%&kD?ywkw4(>1g8{ zeW+Oa?=*&f+vq{6g$^Z%xvrN^w5jnc<(mU9XQ8dIeAWBq?k{$KPRp*$v21&uBKi@d z)y*|CHw0E-QQAT&3-VQHZw2)r&wh7V;V5$QDO~8na;uY9A&Q1h$;)uCf}ecSi2}5SdSHQ)P@iCGx<~=S*^=ojJd_Qe4Z}@ zehTCnP##0ynZ7oqD?V48c)#a;#fmR`{*7qS3=Qh9+dL~~#&=O|FdCblewS)>qpHtt z%A|iX@rbINFP#v)w^QSL3eRP}-2GMWw`7Cq1<%#;1qiv*x;35Sn>U9 zCL2p=fqunzT>EZovjRQ~n--gP+)f2b9Ur+9^%m%hi^k4CP(xaad@;aHK+)UaQ zkwWKEV6UQCQ2!Glg)q?Q2|P1AtZ?i@TOaN*DpolAt4Zf0#cbEz?k)MLx;9|&jPN_` zr{aEqyRW^#7Fa$~z*>#k3-kWWIN}zp7pAjY26>K5j7h@i1F(V>19U#P0nZ#_5R(~< zfqFpWpfO?{W2@n)aGuLN zPpoK5|EBxBp6AjpXP-;I*ZptZ&=&vNeWhHV0F8gzJS$L*7p*znH?90ZlT-%}^Qs_c zw(;Msx8~eXTju@U9wkUoC|w0+s^u$U$XC6;-Tg1!2eO!_X3BTv8dmJ9wC7Ehw`5ls zR=8Ddj5cRlm{FFLO-T_#Sv)>iRM0(3zavZteI&mrvbdQPQa!Ui4eEW$q=@2#j3H$f znj;J&g+xAj%yBth3Mioqoja-eX)p?DdSLW3b6$9ChUpuKjxUxJLHh$<@y5KPd-cqk zi0q-)VHOVLAj7JLwnwZ1V>w^QBgzVYB6J03DZ^%g)q%B;Pq=Lqb*E90CY%)=BSHHD zEx@cWZNbk(O9&}gIkBCF%CqOG!YB5S;tYMeGJp9A`6@E+_1xQiwLCNa``Xm_qt`Cy zenY(YEwN*{?%>U{0(HY`G-Bn+#EYZVV!ALlggM_aBAw?Isz-r^KbxP}QuwWK!cc-# z6)(yX6Jo%F({nl3LA6Q^FKT01?~bKC7QX5>k*x6ieVP?I*I8#f@~RRilvfb0Zw>%+%74=30pVmJ@oe8<6b~hTIf zR=EAzA-7J5;+V$L~65jD1d$o57A1Ec^eP82bh&oqs0LRA^`L|7i^k2B*Z zinu?*k1Gukv;In(!V-S2fr)&E<#H@a&qh86$s*ajBJmgsXmWNRk+P-pP zq`spG#y+g9GS*Yqx-NFj5-Xmetmel0qDzgM<*h&;F$>4k-L)Hi#WR2y3L*EZ++ZaG zWN->8zzU2+g1W%i#luC!_l}Wujmj+ZHPnkh+=1hiyNMcL1!kdus*s8mI40ZwU_Xa9 zX5qzfVxP7-5G6fK)$@2ykx`B~AeNc&e=PrqtPftk-2IH2&C~67#o}n!a*bDSo)t6W zsm#mSzxguIZw=Y=FuD8N*sB?uNBeS)Y!8k$siTd8xNaqtn^T>Cr-Ld1c9`&77TnTKI{n zGAbJ74Aw&pZt#JX1)f7qz^x)Kqc8+ z@ISw;yfJ1k!=L#%QC9gT=gU&_27VLl5oXijQN90vj#=^d#EbX3j|F-qdfo@QbIath zXr2{&i(l>jb8neygf7=oCY|!jzCZW=rU#k=@eZ>P3sn5$3;Io!ZNk1zdj98LW(V`) z{oVgYme~pNx%ydgmg1%_>A67+E#z)2cVD7K6f2ZsXN3V~&?4{n9x0~ajU?IcSsX4q z;OnqSL4F2OIc5oR!MyW7j5q(FiGwJr%S*RK}P?3Z4bLnf}P@;>r(OpxPIs zQ?OO3$0^&bA14jpXPzO1DS<2rRHuVAA@ke70y$S=JptbZGL)exP?ZyZL; zo8B4zHIGo9=MvJi@RS96HK#=D_wxwiUjAWDKl8 z6cl-e@K~T8KQE~M_&`-+Iz%%fLp|_u_;rN+8vR}JD&(l$N5ct>_durdRh4-iX~_ke8CPNVE?V@6m$1)u;u$5Mum^)+Ju_zE6)g2Ih$UKh}3K_&2ZT zdh?_$l)3;dI3j{x#(hh%!q@M)|4Z+;AB7CjkG!_@OL;_mfpkTZJ(`LWf&T8Q3_}Ng z7S0Gh4wn*O1&+$hr>IDwS>cg_{H(HsYYLkbce)a4`~foW(8HQp;Bhv+TKWp^osD`I zg1-h;A=}K75@wNqfK7vpcgA zqd<%rXqFX+O26qLR{Z48y;JUAFy)pY4P6|;Jcob5YQE?R2Mdq1D3cNZ_ zS17DyLRN)Q70Bpb4pJcF7+y`}Z{l|_pE+u)V8-3sS{vM= zoI{NIsLu*`Jb27Yn5V+dv%*o_B2MrqNhk?-*;{y-S{ySa&exDjRHIownHIu|A5qrg zdp+>xU0o*6L^w;DWyRqV_6oi7%iW9juihGdF8vi9n~>r?j}*w5_%m^$9wov^!MtFL zgvQShE5M=MnLULLmH$>Pemn4Uj}}qN!t}0Yo*7OEFH}!eeHFbFUIOGfZAv2gp}7E^ z5a|2OyrcAlGlx5C3f>2^|GWnr`8BqO$-DA6;f!<BsKnk`zg31Dl z0;<3qZPxSZay!Uh3z6Ayq7P}yUnuVLkWc!XxsZ6^Rzm7VNi(qJ=*iV)|UzGh0ufkDgNh@s}6=j$#FQ z1~w%%DI)EO##o`$MCOz)-r@gM{I-yawao&g@K}LNF(?Y7Dqw|Nql`@`U+9WvS%K)3 zpB2qUH|;(SRUr{C+?DH%60^V8*mE;(0Cjg@6WF9+0vJNbGx1rl?ZNhnp@lzZo3kg_ z@<1&QS%erE9dk?31HMhnrhyUjs1uG%3${U?P4m4G-o8JP7x71w%dlKInqhQlmKEp9 zUy^;mJ^HKOn*(9ntoPod;e**Pi8VrsFNhSsRn`aMAh5q0(-iPl2u}<#@NM=C;~9-K zQ@r>s`7hw_JX^Z2{1)2BsdFuGe;2=?>fx{_7PLlq4O>x&r@%a7YluO>SCLH7Xj0Y` z#uL$`u3(FU_BE0cw%rj)3eTI#Hi+N)P^8eTU{8hoMU6OQFE-DLvLc18E6_I@>$zcT zffc+pjz)y7v()$CVL{!LV6QiHFv`M=Hy7K3If1#qZHoiMgqpzqW4<7GK|lpwVCU>p z2d^jX*ElyeSJOlJ?IwW=Wf3{}Ajqhkf@c{j_7ZKH^TfEL)( zzw{QH7z5HVdU@bWQWS`zDC>hP4pB1nwh$Dt=A%nI2IFrNW;IEwF#^ks@E~ zeNrrEa_Iu;3Fd|0!>Rip>j9+~>hglu&});Ph*~Ncj#E;VytGmcL<;hgu&*+N6~Y7i z5iH{+NQqNQ!I)@=s2{B;{BaM56Pgl^PZQ&2k*Dcu3Z}(QVg)bF`73Iksu0^9x_c@9 z1yL2y_P15W^HRy+ejl7bpAS1Q3h}8fXnBzBfrYcg)QH+pQp|R-4T8H`b8!@qqRtOj7qPc`Ws zHT_3a1(RYa%rVfa?Z2P|j}(Ou;j$24nV z)KS3K%AQPkC@^~E4CSEziSm?jg(I&5s>0-CeX;vc6L;@mPp$V(v#fYBadZEd$~HnC z6nODo&&`2PBCGpR4c?*4IgBrtyT0~l5+mMIo(hcTfY$K!e=8!|$S2?aM3F*i2+sSa z%na3@7*&J17s%|wCtp$q=rid}DOd#3cOz(_ayc~@lwD$;(=+Xp2v-!aYL1eo(5k}u zHPAvU3!mo#Wbm|u$1d+3tzP7pm4&0M9G4REt8DvdR#-V#mN!#t3XlS}hf)-w_6PhG zGQjKFTrlgO+$o+(5yn;)6?D4?&^t#WQ|~06j-c;7V>Idgg+8e~7!FSOl0uyaJ)Zm&s?REKMQgj}k|+x% z2Io((Pt(>CzV-~R3)iG94*na`ngX9jWa8$ibVZmjY!8R!0ahqY;U>lVcC7SQYKhSx z$lk;+9P*8ewX%M?-D{SuD$x6w6{V_Rx{vmHjsCWTyZ|_(*ZZ|>W37VNC1!Bv>vYq> z?=`MunqsQOl;}3J1ned-4!IMEr11SHcPFQE7|(__7TK)+wvqM0q(J_(_EoSI0=xU$ z%RY+FW<4xnM)j=tG(Vj(X@TAfg>kmCH}Y(3i)y`*hVVh60@bQ9`a5NCgA{gr6rKvI z4d_x7X5Tm0ow~z($Gr}H!TJGz6)$W}gWOK_F70uSJQ**Iugb1)LVB5pa7%k$3vPKL|QTS{*vn&gw zaClRg6Y_bS`K9y(FhT#Z7ZZPrNa3i=!F~!SC)ih^XwiI{X7U>gf0e4QSKc2*Rh&?) zfWNRhZ-@Jnz#fnSI;C7sb06#X8fRoqKvCe8v$-{d^IgO%iWP-ZO@I@eM}e#o?CocD zIIj`o=9v{|I?r^@qfAZqXo~%TekYWW3{n@wi1`#@ zVs(M*JJj}ItSyhW+HP(6dDcto9~}AS(42v{6Hi9J6;UI>)Ts2HcF=>BJ}6R5iSI&} z*m}bLZ7=v06XezWDOdqg*zKH}JyE@apHs0y>53ARz~{QXm_iAY69wDDV1zd(*!JKt z=i<>6QtV&Gnxepp=C_)6kR37}*;Z9{MM|l1SmpTLMJ{7yy~NpP^s7O;KFQ@w?b+e_ zY{)1@&L{K)6or!l`js~=@b7{D)2%4PFTq-ZIRSqzd?27iHH^b(6q;8cvMxLiF;eot zabtxpUQOAGCENCGG{=+fv9qyOi>|=u?jc$nA}u6MTMSq;)dcS$wug}7CoOL)o69)9 z^ViBg01q(c^hL_vgg=B@YoW;&wLGKffiswQiq~mtNRb7c;6~@2A8H6eUBD;QlNrnR z=Ev;TOT=g+l`$`W=7|rVIPk=ao1R?%E#k)CW=`MpcJEITPsY9(X{DS?XntXVs;Fz* z1=Nel0$8rbSJ)l`d!#8;g(5|P6@@ug3uELtFU#$h_-;YJfD+u?HV3mpQO%jb?NSrv z6$PvicTR<-#SX=Z6`>UrF+EX5)+ST{@{JIA#P2nF)>IX$#O&2rXH@}BF+27L$0NXr z$pI)I|5i*3|b)h>$5*tM%5t>>27S87% zc?d7l=&IFJ=W8|?A?K)-6{zREC3>*!*9I@7-@BLicd_kpkh~PIJbv85Jj9+v7GlKw z@I%qxCW!3m$f!kiJT7Gp$9J|qx$GtSSK}CGL+%!D*+~lJfTGGl+c$x87I;C#Fk>X- zyC62hV!A%uW{u^y7>%g`UYvR2z+*c%z36KH`_NqjpJb>47meGqviFZR4PT<)Bh0Ej zNj~ijG~afA-=d!1#9oQo9!gRfq!*$EAF5Imzfl!;5V^G*w%W>~*ai_k6f6%S5}H;M zSkt5sKc==heDnm_BJgH98`jV*D2s!hVS3s*Z^7gQKl=x1%;`%sW_zadaMk%w!UeBo?X1ArE@fUt zB$WIC#Y%#SA)I)*^=Yd9hcV;1fz?!-o2>;AJ^r=mpZ;&ssOlR{&Nh|Abo^3y&GwOiwK;Csf``p#q@O`PUcr zM?JAp*bC@;X!XTEcr3}Znj4cxVz|G#Qm?@Y)AI191Yav+KH!8O`$}&|#PmSRzck^V zqmJK}FlS)lX)g9uaF1)GsNYUv1^U>*wym=m6RD3C=-ZLeRmpLxIS!w!9c6$mLD>xW z?SG00@l?x`Eoa)cMEeI)+1rP=#5h8Ve*Cle|3l;XoP=<`dOWwPZ*KdyzzeM{{K;Wc z1H|A)5m`kJTpeAXYVdu&6#oq|;(Y#sYUgQfPTw6(cQj?_|9@)v+w_M|h_Vn?43D4f zeJ_8Jl!fyO@qPL|S>2<*iG5chv&0I66ov8eg%R?~Yaz4{Md9{Oh^mn630{iTW9o@w zFD7rnjAz_TRZ(nzu%F^bGzx?Wp|UlhE7s9m)uo?r^Ih&Q$Vuei*Lq{6LW&4goisnE znsw#`6b0tbg%1I=kJy?5OIbI+)dLX2l`({%lsIs-G3yb7)?L;SB>95=Y{Pb z-$SI{4z)cU`&g0U-}BtYbN{p~)BI1%&@#>ct>vlpZe=k%ez5oF`A4k9!U_Gj-+|iH7%c<=UW`#itTT8I*Ar=T*9m~l>A(X(u6aj z8UcUg+9XH;Rv^QnxJ3+N@VjhFY$IC*eDGrgbK=Sna;z}tUh%)8nk^g!LA~G)Ti&J_ zHZU6J3_ZWzqyRNwe;-i20a;Lppw3<<=WMaU>^pwzIU08c{ofI}XW;RwN1ZhdAC8}; zO8*pxb=SYa7E)#G6=6>XSOFCwgy1D@QAhu}^X1t2{3rSUng63=#ed{GMuilL7EPz` z7iA&3;#=eG1Gi1APd^h~5o)7RI$NT`xO04UYKCdo%u_IEup>?IT6-)}d zk_NVFRv_Yv23lx8rjUaD6haDv6NQL}pPFD!=n>~GQrKQiCoA4scB^_iF&<%z=EOi- z9h#(BSt@(p(WB7Apu5<(Aux* zSgOt$J)i&oNalE&D<~G?Wqs(LK)I31UKdu( zbS|QCXS30+6)P6@JViFdYcc!^*V}$UGo`FbVqP|kFyXvRxmTDeL7JIu(GY3~w}r=| zTVfygt{K>!xocq81X%IE6f3}sLup|JmQRh7t}y(YFK+tg(A#b2NEaaj%YG03%Qn$L zicq0ahCI($Ue0`%A|Vnl)vQ?ABdp*WUCfGK+Z8P+8lrTC>8U8_i9*gs8Jojk#g{<| zJ3CW2VOk(W3PV+hvY;$Ym3PIgfW6G5s64;htt#rvz=9^=ds~aD;G-qdf!N0+MLw32 zEm4XUY;`avx{G2Q%!k<|gPSB%xhq2|V^d|z(wyToD*Y$q`IBeedr3KlU(UvSFa5#7WI5<*@Mgerh-vs=Czeuf1V<%{mET1WQihA3)xx+Rl{0Cn5=~OuK2>_ znt^2Mjm+ZgZMjFRNAk~6dozD=yd!(1pex|BxV{gJ$TVk~{%P6id*?+b%ig7%JDV{NBG7tVkw7ihrBcSg-l-Ib~V76h;we zrqg>U+hIC=JQJZD08}+TWi8IWncuDm@n{~6cws%7zi;Aj=7R05fU0;UMWkrXH2*5` zVEUc$HkD)HWW|4p9U@jBD;=zWB_fpI7~QTr1n-6RE6Q$T=0#Lk!4}~ zD@wA$&a+Ase3?QDSQrK=RJ|JJfUPI&QKUi(FIHfGtt~kIv47b*WpRKICMz~p=&gWS z=X`FE0zGc3LN7;gZ|7>A%!+F57)w>!lW!$L-V zig-4JW=98^*IwEB)Wo|ZpaU48+jIHf4sDs>nCZXg+eek6Xud8qmfuVnoXFlZb;XOD z{>#t-*dLvUz`_bag+tWbIIF0^J`8Nbj05~sFCG3kt}`|rtWe|OZB;?6_>J;y8mu5I zL_C{zE`>AVVS6&c3A1755sVd8lu-T&Wqs&<44;LmD-mN1=UpHX0l>STtVm^dBFAFcrdiAh zsf-bNuNNkcjeKoFGzIhG(fn-_zs_*nbUgd7tz>(U1#)m?OyznrD>%afe$DS}`tY8H z5TdOZ*C!qe)Gm96;@rp@mo{csZN=01Ya_RUSV0;Be9%p0ZmOIM#6g^__^urV(OH?| z&6L<^F(sH2HYuPdG$nYASmCWJL{%V9liPlZ&`DcCky5rmDjQ+Szr(C5FrIRn;*ymR z#;?z$3id~JUQKWSxRn)*-_{1>t#NH!LGH`%x8W`#c~zI@ADG&?HP zgftII6tTjLnX;}hIRSqKa)8j*5i?d$tQ6~&Ss?st%@u9ACzkFLrQd}%;QLvPs$gX! zN8Fb@Xcah(X`IxC<42=faOn0C2yM+wy zC1Rx%0kK)3Es(z!T``$@a@otRPqws&o!Lv(_VwA=AGXNiiRWyO1=PiJ`QMFvkobF2 z6lkY=$JDsfdCCHLr{LM72uS98=|{=qaqq;M)KBAcDMJ+=3-MVvX>qP|HANNq*+$;B zWOuU-^087D_FT-iufq0MC}m;R{zP^~1*$?30^SHFg)b+HXkl6*&=t@V;;X=NwIT)c zf@y(D_fuuP4(O%uRp2+8RYfI7`Smbue_)1izrEbsaShNE{5>!!Fy|5bULP<>A@Ne?$;@AYzv7g&I*eS)CtJ?8{j%nqirL@qDVqE%&QdRZ6XMbUN;f@#96P@YbviklUng|Gs3ZA4cvE#S4N zECzyphS6fkNPq`mZyhyw&0NjOy_z6})Am4I1DpUQSVxGp0UaTV0-Vqu3Z?*4180+F zazxhurtfF5XJcd0v1o6kH*#lqj4IPmiy~j|NPJPxA{tdl|5ICKv>WrZYV zkLq5!b6rX3oYkRKlp}k};`u-*zk=fa9R)Txb%jj|m8pzzW9GXJ;dGSe)i4`9zRq^Ed#86*)adx|k zpFokQ4dt&8E2IKeD8HsB z6NCn?C71v@*vAwUh=Uc%vnk4g#+6dL9^*h9?L-B!4DLNp6^MPnS5Z%1w^kLE=Rx8M zOhtkIe`QGfDL@8Xndk^n6(%S6983qSBYye9#4jng|CIHIqWqxCL+{Wp#vzqC{4#mx z$mUk#pmp6y9@Qm(;Uo8J;*o5pDp+qX`)c3hz%MPXxcMqouxJQ*c`W8rR=2V~xVN>; z6r!ad#<{L7u~S9YTuV>7nyd)*@rbX3q%7LPzjTbDsO4H`=a^F#)WyAIfpBaD%ZuaR z%dE@1JaTaTJDc8F|I+%G#vdHIGyT2H#gXlli}46qA79HY&fPWvyXDRN$Gr!sI)K?6 zw4kigg+1NFONVnwVueWxn+ql*cwMrw+^kSmh>Dkrzrv|2T1)13<6ZbA9M*{kD|mz{ zG=&<0zU*|%oaM{gDZfIEK^Ifl0%sE|qEfG>TTw(*ejG@_@eNRdIl+wJ z*D@w#sZtO4tQRJD-k$Q$+$pO!LL;qJPB7}lzewIWwE5JpYig*|(0Qf}5fH>b9?wN+ zTsrbLy;y;2vbLYXCIycf)4Jkp=cS?Y|B6cXxSFh39eLUw9SS>SEb_u9HuVqM$1@C(JddhgS7J zZ)Hx{w1DD(rJ}47(G{8$pv4YVsbXCiO(?<3y%kir0Vz;t3cf-_7%*DQZO2#s+SCV? z90j&$MFC2111F&AnF~S+e#I6Ddov%;RmummXB|%?K8eu-h?2e7f~fjgs-{#mC+NAE zA0mAM(hzYzY=M^oIrWd{ILDh=(P3C29rU@V#=zx_MpLf&A3X={r^N3Lc%Sj68 z3bQFY#K8*5=>{i$wCsJVrn1hCe>h1|N!1GURVV@NtEeZh+cNrnC91+QyqTI4snKcj zQS7zt4$TSpDNwBjV?kd~Gym249F0d+{TBM}57H9>RWVZyDQrR@V~0jWs|s*f?rd2R zdKI;J!yA&PEaYv%UjYI^G0;c`wMMlSRNUhrYYJ7fLMsZ)cd=>~X8^c*toSq!&n6QO zBdzG)Lv<_;v~G$1?+Fb#k|n|3&csD%QyDzUv}D)6ikgB&6o`iW6>j{5n=)~#teD5 z(BPk9Wig$4C5rK3I{%8-ylBC?!uD9m_%T&4&p8I2JrOsZb&QyBgV?H0-A#B+@dA~A|Un5LTFkOR@qpo-dpSrEK{WHm=w4kX2o%( zBxcWdb~%GS%Jv7Viqh5xG^dLXUbLWR>1^9vQh3aZ&EeH5yF#%7oS?Wp{R*pJVx=jl zpNZyxuE1;n7|U4h*{|;XtEnrFXP$Ji;-%IVpMgw^;yYyJ-X(iPqN&?SQ|u_g3D9Ck z(?`bmmOW|c3ZjLwSr4=|gc@ySK_fowInHcesCrlnSY2rQq@XL96@{^;iWQV);mq-- zSp;k~Q5YYh^o3!Ih{6y)h<8GkCMQrQPgTDn4+Z!jS(+HL#1_f6l%5aRsV3wVs|vP1 zxL!p)W6*In_G-fGD4*fC6jGR+04b1trL7P6B&_W0QG%bMY4TQxs({}>c{IIr1t|%7 zI#@|K^@P2Re`zT8&NhZ3!da^Fk9x>+L-RtgI+UKETAP{^Y<(c}N^wN}N}cW85}i!V z_P_M%>WWYETcS>7@lxyTs-ogvU;CC6@!~7FM=0mw;y7uFGHrizBBVcSR}6U;coyKZ zKrKXMexIk2WP4Kfe26`T@uj>*x!y8z%(OvVtnjcxTx!Ck1=bZW^o$^4ZJcgFM3hN^ z%q!6oygW^#KVWgJrV4o&eZa4f3xuteL_nY^xLO59WuZ3A3G&I++NAe`7v;{8@|ay1 z)ELad+F(6#Whgazb;cvD9RMpxE0D+FMT%C!Mvz|3eY-_EzVY{C6Iw=xVP+>)(6ia7Ycu~)i3hT}3=eM%8tEnqK%|D-85yHO) z4ffnv^!J~U$f@_;i4ZStn;40B`zf6Kc)vxwv^2*n`LE4}r-!0mp-W^HFYF;3#7zpR zwrS*9unl5*H}&{2JtEyC1#1U$i@k2>3(X3-decWC#JJP;XRZ$Q_hHQTt|*fOe=Et> z1Sv45_wh7W&WEp}5`UyO2jHb-Re}04-uv)YOL-}rdIGOfL<;0~fE1w3)$@?`C&3Cl z+b@yVO7(k#7VT~=!IYri6ZUGsZ(;f(i#THw1C*fni0#3IhXR~{7Z83`Jb4XNsqK8? zcu_T2@8`U>tyh;7pXROXiqKP(yNP@GVOz>o1-rIuzM4m!vVNF;tBj`LUbmxJsscQ& z{97g~pf2EB!q|}G8B!KKsBgo~<_75srUljz{-*dj6Qm*L*dB`IMI+Xs zF7Tfo_3;5bnJuSDTj=aeA;swyiI1)h9f@sAeYKC~u2HlAD>hW<)m$>nW3UkaK`$-u zW$>l|><^p+-qF&k0{fYsOr}Iik%DTS(lvBn6*XbyZp{AM$mYy9!|%7Arm=+Z$YHk4 zcUo0n7gpGuz>`lEqTA+$dc#{Ni}OhQeBwd^Ih=NNTB@;vZM$OQ(Q(T9Yzwzj6$+U- z>nv&1os{48Th@`|U47O0r8nYR5~ynLT$ z`vcz0jRWZ8U`A;@;7tkMDx?4_N_}=QDWE2#9ZwH>%pxIAeeKKpN3-c~4t;0+i;w+p z(b$cD-~6|k#K=?ex7(hk{yRf)2x(*yD6IkCK{9zPbN}csCVp?BqKaSpGu2x8y@mN_ zrl=arrOevo4MdX*iStzNn%jlMsy@mBr|K-1hf>+8iG9~tqhEbi_}!b&>h`S_vSNSp zucQ2L_&sIrffK9-m8O8MP|9MGtoQEG{v>E&Q-YUjY`V-4V#iDkRusIO1y<;BWUMX3 zd#RTsBurM2?m*6_?ad^uwDvVuP|m zd5!-b>f9CUre9jBk<|xFq|odt7N!;A)D^-CYbZ6BSK?ZQSNAzjb-#SryO{81sbQ3y)%|hB>yJs)C~ye)>aGf_)RB zCa`=wx0F8J{jJvjdf&wN(mU6K6D#uH9LhAun*Pry#tuyVZ=eoALmU0oDFvi`4E1*vFP)pIrRj_ zk8`~}_jg+V>wOdB=>v~peBpuh-^%=JTde6{ThDA-p+*`CFCHA9OSMc6ktbnxYSz_% z)w6qsfEDihc`CoWy)tF7qxoH`E`YYn!3yNz5-+;o>pn#F%h*$g`kR!+NqJd1kCWEy z919@@$4p;QGp@U70bQY+u;PQ3Ik%XkP!z?7Tdz9s}!?zKdIOHb-;?^JO0zCn-v%bqMN9W z9nE`eR;=nf#ACg>rl&$x(Ni-OR>F#e(iL-- zf6#)a34!rp5-Fv~N9*$T7371swf%!uCWmBQK|gTj75@bzutiu=dHxl=nuw7?RbULd z^jhiPl`i=V@M$6+!s{$TiegScQJ}%UQv8k9yXCggo#U(XWBGrH$8JOowSQVhtQbIT zB(7wjZIK`MUjB?!U#R!rTzq`MisE~+rq7Io(CCkrk?_jC-Ra%wyGa=&LZ@5kSDM(d zqxpveeDv+34QlR1Gp|M>=m*+XtBbWF>r!3g(ihUSyqDbb@fW%Q%n(*!#=DIJM`F+xqAv8_U+pW`!=VDucUe_{ z5KtBVwnHW7g93w>6kZ7F{rb3aq?pP(3t;?(+QnXF{w-!`o<#{Y`38UiT zY6mSod}4U~p;Wz}_gOtYU`6pab19=)vA_A{=*96zscOZ0c6&2_TXuJPW#75@TP>l& zD85B~JZ_A0^;Xi1CCKvzFV1x$a%#4%1E|{j3XLk|W>P{aiUKD<3h{0_SpibOrwLZz zFVli!r0l0~l7d+Q7AOmZ)B`LI)O0u>V{fMB1Trb# z3M9Z?f(rA}CPE5)uGutGkPTM+ZPvd9s$wj^CEsSudHeUxEuk&M4<{{@k8{Yn3d*Mb ze?|Rg2v|{k52p&=&Hc^i5*VS5(PTfj+ew=lC-ZL%r3z2QFDAA`4{=t$%C$3VBcAP? zR{l+s7rB9-#=aYVg@{4_7aB)D*iXTA^t?!cEKVYY@@#??G@~&^M^Cr>iu4ZoCuj}U zhUf=SK`}!4F7axd#w?<(4(+1Z75AnwSLUt8SoGIom5&s|ynlPsAO*&BdiU~5Xo2Iw z3a+$YtSVBrm%^k3)-@?$fBf>QWE{MfSz&<{ujX6AsJ94@#m!C2`wr9m5bKJR#dp?U zyeh;heon9TmVgyrcl;TZ<-NaoBmz!6N_Bj<7g(X0vE91eh|N8p8zWjkSFDV*Q{J7< z-NlTO{F`ag;_?vjB056Rj%N%{rf@>(iK5w$;n@^YNTlQX<#&cqnT9P4uYTq6nJ5mjX5JkmKp~UDmtpl=Vzz9Vv({`NR2!(SL1?HKF}WWS4cAwn15l&q7sS zeB#B&e)c)_SOl!_dX|0@gB9|A%f)eGh2_nPpXX2JKN&YG8Sa}npEyf#)0GkY)vP$z z<+MV0JQFeqE@w6nFA|hN!PZ8B5QR9YSRU-B;4Cb$KTfyaT8Lvv|6kGGM%C~3x4gBC zqaDbsS~86JtAq#ZLL3tnZNV{8A<34=iA<$qO3||sAw?Bi=>G`W4z*bzY+s1|;XH>Q zDNIH2_*L>$;Mw@a#LGjhDh}t*=bxQ;I<>rSCUc(F50la;X2si2j0ddn%}({|ZGYX{ z7WV8Q-(_PTRplj8pcU&1u;R9y<*Ex98C}$KhGtW3bjbo;T1HUU#Z#_(x4&UjDV5QWda5Xr%hji`vQjb5!TI`1oBhswYrj z1y{c5Pa=Plo5>2M#ggG<>es!AaO?6!yO6=TG!%vMQ@r28x`A{=Ai;A*8@^vuYSoTAmc9Pji~)?SeIe-VRR%SmFG&uZ$I}DxgxJOmIGF{7$OK z`fz{Bm7%LM;vC=Y^SOh{vk6vUo;qoVi5^Z}ft;)lpLqKTV#Vk3cQs&z_xGAihElgt zg)F5ih!t-Zkpk>cR*362WNMrJ)%H&9d>ev*< zjS3;ym#O`kr(3SKqoLpe{1KuK(D#eAMt>jX{AFdasz+GCjL@VgZ+)1gK;0|p+33?C zh0}VF9$$LNJYf3+nV))ZKT_yDcnwyN_qRA3TdnL!nr)EZ=Km+nmTj^^{1uoff^#fB zB!9)T0V_V2tn`1Htci>3*;@f!;mwNoJgkrn12o={Je&o81+6!PSExBd{B_0U%o`)m zk$Sn$`a$N`@kGe1kf*aURdkdhq*9v(T!25Kzt2<(V1mgGL`O02H&u6~9E)`Y#i3aN zk7oH%VnPaJV6*+issh_V3L%HPFPD04*?vpZwu3g&iou%{`h7X;V0P(M$f+=&i`4_G zRTSg|^??6XmK8x9#JqQ3jPP_TY=!(e|7=XDiioyC8Y3&C4dD%Hyf$dz zWrHjk-kPELR#mOfH}m&q@20s$THBE?!Q(rG5|l|n<2-ac6uJUY%kz{|rR@r>ESL<4 zj!shE6_5GQ`;FPw#|5h3HC0gl(hJ>7kb(UaniS}}@N9w0q?>-U)IN)cL~ zlbL-Xe~#+2LRK4^B2(=>O4X2sm)M<_>vv$bU_`z%(6UL|kkkr-yM z0xzI3u&(z)h9<^*p?UpgOOLeZeOMXL6DO#l;uMbI_wgRymDh4q`6Z4*W;j(tH|!5@ zQh1#WpXvAg)wX?4%3V(y)#?tkemAq3$gnGaEPsX6#eXPLyh^!P$gXgzimT~6sqNo1 zTSo<~@V}EZ_ZYmJl(Xp-1!0BZwnC1j&vt?pP!)GY3(A6Mff++sSvYMF(1R(mH6ybo zih4ng=%4a$S{H{F^k5tc6N5aPcJCL(@YTL;Da??oy%bOq%mi(Zu%ZxKWlQ8joJV?b zkHs%JGsC0?=T4wUGviEB2pei;1*;0T{ei3DE5X%B^CHFItVc>8%c_caL_$(jN&kSEvg{+#+X(MtkW__N}s?7d_aJXewxZYyM4>Y%V9 zdKWdO1=umu1&_s*A@K|fF|Y-!SUS8leJ`;IGY<$WghziOW;{BvJN-)Z%eNpW^Lna7 zgHaq~;Uj7^$7ZUEc_MQmE*y}|E4DpM9l`5IV*Py_?-Wje24;g&Fy8|ypeNAqYmO#N zVOFFJzob{sc5kzkj|28cq+owoO1u=iDlkhR zJ`LPqdbl~^ciyGs-SGeJYI$eF0L^e-ObVK_=T4f{=JwI2hgRqRyG@J#*B&)CPgTZ( zrr5bDOEbRK^0|L4#{{hK|E8?mS122+94nkw$i4~qHrWCJDQsHMmd5Zd+hZY$LY8cU zETM5zh?^F(0-Rua5SOyL<~Dql@LXaJQurc31KmV<@H?Qy+9Z$Rbd#XeYy9T3^}$z( zp6mRr7jpRZX+LH_Js?L6)(wsoO~Gf^f4z?fY5c#r>fbqK?MZ#dnE47bPl~=c(8^V% zBcmgkdDb5KHvW75A$t^vkUgRu|$-3HT4W$CsjmelJ2EM;rrQTp5C=0$JFoL}PLRecVY8@80d)%9>`cA_d|f z)oBV;-<`ckY4_-&z7?VGP$h1m1Li3gRy^4v6^$^1#aQ~u@wwKD{H@BPiORfhlb2$1 z=JwIgv-QD!Bw&Rh$E1XtHcNSI%^aQF`tW?SZmI#eLjbLoLm&XEIDP~9> zi`!J()a^^vtRP(x=_OV`O$Z@iZD>ZogSjOFLR^SrwvQ9UC-in_(?lq}22Fu#yVEJI zqiIrtd%q|E%Z`dTnc(*dtU&%1J{|s3EQL98#^TSCvvrToPNeh~6MIr)(VaB^24)Qt zI$*YPn(v%+#Y?SEwj>KRO4O5LEgs#H86A3h~2uR2BH07FJBBk^kj20-kLU zFykZT!Mw8_nG(`Kh8rk%`>TEZeQT4G1E^00Hb~^f$qAy8!;k6pN*ICqnK&1&a>_FY zLwa^Ktgy)AfmzC+S>Pk}CWYS_@rk9rwMyRGH*qX|I6g0g8Gy(~(WdC2d=((Zi!Ipx zWXplpoL%|MdCq+kR`z~sk7Z|vSW^j+){#SXp3gK@a?!&=us* z3@@Y^NW~AqEWlr(14ei;D}*7!4=4sSeEMZt@}8n2^qF`KJ=?4QLm_bUde?g!ju9^_ z`z?yC4?j}O#ymm4p!sJHso96;hrn`J8z2W7RK=4m8$8uSsVC)&@Ls@*Qr~ilqNe3p zf&S~XLO!KYA@FNrHnREQcW6dz&|+nDHN`k}KbHQ?tN<-&UiHk^$YW8O727S)f;^X@ z1wD`C?wdHS>W|q;WT>m2UE27We&HSTU$7bEZgB;D0Ya zg+D2vLrQ&PmAsdv`HC^0;P+xbi@zTKKFx8j7{PzZM=>{at!MwM|1g6|g^0L#ujH%ub|qYYM6&8~s`Q`|;Nk zzf0T|FjEui zI!RZksHxeDv94J`^9^N?>0O>q^7$w8i?hXb!nIeV=>d2nzzOy%Anw3TUSo;(BSp5y z67shSIbesN`mU1|a*j$_VHNBTd}1|z3Xr1yOie2{7rqMEhM|XIzfJr$F+ZeOVf!nd zY`OF~9??3oM=+Zc9ZEp9eb))+$);<~Mb8#F5xpf6^NX&1;PDVNTHXqU6=tnfE?U}73kTR2gI8d z&aM8h2D7j~{4~X&>+I%v>bF1soS#g6*!ChZd#3FW*&H2_-zFZ4D*MB>K%Q!OHCp<4 z_#(d>u%h(0-;_nRLpfHU@1D%xl$%mHIaaPca(C?Y#KZAM(iP{rFeZIIc`bIfu8Zhe zF~!<~S%C^!$m%e8!0RVHw(&dfj!2J|CG!Zccm@ys`_GKvUMRf4yRuz027dR^BDS)? zI)2%m&8jL2V$mETsOF1vr94!H^W4r?_0_aFWgW|mMPDLblc$*)v4Z~256!?+VS6iH zYJI!)^EwYu>8HH1cLP?GzE7##Z4?P9#|rdf`hA~>C?7po@o?;qYhF(@MrLUCbYg|d zu{hMZA`Dh|=?ahoieGswC~jID3Be~Y8Qgzig}7rOqAT!O+ftJ>+o;e0Opsp4d~p87 zd)%9u6|69@jw_TSWy!42XO!*UOL#TG2xbMY$!y-o@K(%OQu>=J_3ll>tI20X+$JwG z7{P?#zbY4tED&UF2KGm(yBM&d)ZL*G0J+;Lh=fQlW-DahL?787&=oWioh)PMipI$6 ziSNf=Zrh^9gt#>YzrLcPrrpDnX~a$4MDTwNN|{y&a{_wX5T@VYB{+dgY?wvwnRdNO|BVTJrTzblThW5~WztRPjfGV=ZS z?|N58zSFX($14&7TG*_h`H>Mft=zkb&whhABh6$5Jdzs*CI|5Q?f&&Q8Nut)v+>HQ zEueScX>gxIj;Le_AFCfJN}U(}&{{`|ow5$c55m?UX3uoKL)M1LinHXsK&*6U%ZhNM zW*X_I<@3E3u%h($dx~ZNDbI?NN(G$EKQa+fO1Xc4;~=P7K~^!1AbUOWgZOUx|6toQ zGzXxUu7I_mJQmdF-=Mgu7c2O4zy@bSc89h?G%MI!;l~Q}ZznIL$MaSvEkGT26h!u~ zWZ6B6{L+-C{ZWb(h=0^R>LNFQIpCNSGo2s>u>#yss^T50j@K3%i{u7^?5ooK0PhB@ zD0h!;%9a}uqWkTeb}S^7g%tu_flRD(ohze1h~FK1m}t=$SrJA?Wic&;74TUgZtCUT z^z&JOQcf#`%wP15taLn{NA^6^C*Y=oYEA`ivlZ%76;otZ0jEV#YC-|#qU!;uiFDwl>26zvX4@~ z_4<|9jF2^0A^Zz_Zzlw(TsmMcYoFQKaRbh%f5<0V~RV^C=o}0cG)KiN1T%j!{tE?TP!96>>Z? z)77b}Ry2mm^G?bFI)nV3vH4;2y3(xRnT5PQ@ubgHoXlSw(N+j30q=*3m!?*!(PiaX z;iLd)p{)>{iFsoZ#d(DPtHCT+zkm$_ouFH}vn?5}r`psOnziwUAF!g_ow6owQjrj^o~wWHr4MhXOiw=J`2=Q6N7V{sY|d9X-w(&` zqLJz7H^r=gmqBy|)#)Abt70Laq(2*Lpwu4HE7Dk{podS_#0s~rK+YyIKe64P34ZTj zY^#^5D0eP=V!7|f@BLJES8OL`)~m6;Y=_X;oQGobLm10P*)G9o(vne+;`+a^mHRtyzyoQ*NMXKH^!TK+dcA0 zPFDP!bkA0@VRVB<1LY4^98;RWuP1wLcd|k(5U|2)@6y|r3~wC3zsapC%5ewr59fDL z>8qyUEzvU+^>~LeDxfL2&9prf`+n?f=NXEN9Bf-n^>!;gU$7tUK){M}&%r60Q>b$9 zCdQDZ?5v?www-1&rJ0b)XMracu@ThL^Vb!c6&yEp^MGG_)8b_Q-4XCX8rDxJRxF~* z-PQUUgcT{($JyJ!mP2VS6u)bc{lWGNRDy2hNKtF0isy4j;%7-WV9sgsR?xDYSn+*Q zbo0Zn#~+Hl(}s7?v~j)Na^HUNzP*ICJ8QQz54)WgDf z5Int4zskg8)T@20bF`n@tMSx_{@j+`QPO4>J!Di$Xw;^EcUAIWN+ z>hx0F)SFKyef*o-9af0JiXN;FR$)CjS+Qi8J(Em}(qwSI+daB*fK`P_it?JGHv8ij z6RU|5H18Yn0hw3lx;hjq*uVLGs%vwGVjwTIuIelQtp|I!??Au`_Z=yIoysj%^Mv@( z0o!%|rMPMCdF5X}mZ33ZU`3SXK&B_SfhuI3>%5D`ntId1^ll<%pW z0yog;czPS@ijbOhxi(h72Em9DZ9lkaQ2O=W^u__UtTZdilcHAp<9q^|qC?d=h5r(& z0^9&AP~D~@a)7Gv9*!4($HA8JcOYOz`Fq6kgp{)ggccZ`fl3y?x3ShG0ybH74-xBU0w-owhyQ}a$!T$JpPb6r9TumT>7hsm$` zV#}+sz^CcHbCtagSW*5yVV)4Gf$3urc=cSwO{dfIBM-+Pj*+q;WdvNH)K)P zvKK9!tf07Q=02b7O|Q?yHp<8&jz+na7kJLfmKwPkQE8xOD~1Z<>Ma^Jd|^=BIhDQ%06C zE`+lyHVpLFzzS9tsIq-!2);?J3fvmN|MlgJx2o_zg5Oy({L7l6nXkqa9f%Lm6zod| zHJ~f#o$!mT&qM=F;rEP}-yX1{{5|?Ke^VCofmP}~KweZ+HXC1~YFIo><3NE~Wn>kU1!q^Rt&0_~K#(0FtSHyd_4|X_ zjif4$tIQ7ZRX|nfJ-pXyvp+t~Pvv?k9t~ANQ76(A?5$u@AZC5A?aoLrLadUf-EW_O z6@GWd+Arf>`2EM|jJsmYiq{iId+#Po1mnmW>1l>VvLbxRv*HaC1e%@e zQ6YQNuabrNdHO2M(djlTk{d`DRBMAkSBM3oy%p8?M~|o1CZ|*IYnr4e$BMz4$A8>D zsv;oD0s$);Xzq_jTUDS&@~z=|s^yqZ!xwcuU_~Y0{PPnR;ju7iVD`@9_4o3-hdN?+ zv#y}J74TXpp9SS*L0Qnurk-{PSiyNyiWkGk-t}oH={!r8 z2iqVsopVD$?o}mEdZ~Q^R+PFs^4>n{sPb->_8puoumtmv^^)cByWZdRf(`V4v=jbK z(iTt_^CMp+A40Kz(_}?tc5J)kWPwv9!C zI-1qq&*J?9Rutb8uPsO6-7FIc(LMJh&G2)04JzFHQE3XWLPbtVS3p^GM2>iREKtJ& zbt2dbLH|bfX62p|5JTsBD^{d2%9L4APXC*|mvseVAk}*|r$`+@RUijT?2kdxDdj#N z@ma5XU)i?vi8Gz^LJhW(=pbrL(fr0^Wbd6%1mj67yN9Ls3RqG4{>ZzZjKjNGx~7C? z1$yicGkwvADmRK0l4Sv(#mdMoPgX!z>@`@?O;*TlWkgjmD}j6|>ATfwnX z_G?zo3acjj%zyOOQ(uuws_)Krt7*ww%hdchfuDL3m-ZV!IW| zeKmQmOQ{O-W|CK4MNL^*w1r_AxRnL7Lb9SGVwQVK zpeww{;I-|O@%yUC-9!(*DSOJgK-GBHP>dAuJ609+WK;9b-h^*+FGVldyUDD$oOySI zS>eYiO(@MQ)Up68rc+5Z8iX~9zun+`2dbib^hD;K0gM_G|73ZO!{fOWjVs+qM8n^` z&cYWg(4v8=uGpj?e+9)(i4`+l$h>yz3Sq?wBNBpfA#8(`n*Geli<9|#sjl}4s$55` zP#NCLit@ddnZXvwUgfQT=R`6joJ1*pg*gaWRiI|Kv~r(Xi~Vu`=sY6DjmhuRJdh8e zG9hV+yND3*RKR!K5eamK*Hc{2wm?@@diSh}i|b3}Ixr`6pH;Jk&}hk2_KYS4v4Zox z&vh-L-+M1sKn-waG`yQmE5uJ(fK2+6aV4mzcbr%;N%>zzTVj#)Y6Uk;?ylkLrF-5_7sqyGG zD~J~>qYcV$KNf8&(iMnUu$KXPf^`LY`PK~bvC!TuK?_q^z`vPGogh|h7|^V!%)1Fy z!5N$IP~tjRBe>b%d<{#YdR2i>s{9+^J)4FbLNvE@@9XhKWpyC#aaWYp1XKkz@?mTk zsP??U{-y6gz>3oM$9(su?7igOEbXyy_SgF+a%rU|l&)Z}W@og6Xt6-eKL-DVvk5EM zyUA(*vms*!W^Uy*1@Cc^tdMCU#bipeqLMKoY=LmRbZIyqRZ;Gk zTI`QcX|`C*Bn_{ou!2;DV#P|OFYY4!HWm$Xu*_$7Hr4}HRDKtwmR_mfcC#m+Bo*+} zxVAv>BtuuMp$O{y$m)o9)Raejv7bO}2da@Z^kxOh0$N6{0JVBI5G(fB8Qy%Z^1T5Zms)zIy#~6sqUSynL!A}ntB5RMR?yh7yQ62j#-jeL;FzPfLC6Y8!&8D* zo>Y7V_`5aJKQL`)crz;o%X&B2TY;(-&YD(Ei^KiOszFuMVt;&^-;<_#crY)w}tzY)#qwd{uF$1_Y7E3`FC&E(xd(=zuo1Qf68|z4Ow598n1?B(I`!UqvnU$EW#IsCZ1?2}DH|d&vTsA8w$(4dJoKbTI43 zEXO{~-Vw0Eyc^}#B_Hd}l9qt{uA{eZw|XNVb+NMORIGr<0<2gWT}Rdby=Z~z+mgGf z`}v0SeU$56nH2(6fvb2ge{{&HE0zqG^E<%vfXdzXP>mbSiqc1649FzqRZJUMo^BQ4 zd|jLUL6!0*dewYPh&$tdcq4w(=thhVY8@WWk`Nhft*_M)zrCBkZmMn|%9t&p0N&56htx1Q!qFg=}bOkIB z_EuEN3K;`}QRu8fO0xy^uWIYrH1DIbH(+x>U4Rwj%M5ZUuA-ih9ulxZ?nEU^J0DAV z7)xsl^j5U@@)w6X@I;fxf<}>%epakd%Az9*zhf~gkYQ2m-Mlh{TyItua`y7elT`V$ z8Y=|e3XDmCw}Ox3-tSZe&Q~(B6gG>n!nue0)so@r;~)I}9M3dR_7#&tSkV|hlBnd{ z3-+n+4g{>I?*4K0e^lb<=G|LQ+N6N4xHmV>)$^DY%45M+NJnHVjpB6YSa9a1mn@+j~?~d@^ljSS&4>yNP(|2^BA8Z$-qeLE-N0=4-B3u`R`MUg(O_ zs-jl=1K-rK%zVWPjNY|bK@=X3R`)Fid)IOY0#;Oa*D$v!YTlG)g(e937s(q(9t%ARUqZWVeUMf^`LZEKF8V-5bjFhPEihDgD`4gB|h))eJY!Ggu;STI{VT ztt()&pw1qA6_v(JYPCP`Or(v{ zJyiPa$^7|Vm5-%Z0V@Pe*(1>l2`^S~Hr6!#PDh9&W|I-8{MnWHEl?+TI%6t}l&`XY z1tQ*x6DoefV@&;Y0=9E)O;{|F4Q^_V;`JrNFR0mGtGn;A_vN7(nkNXHP!;n?Qy?DF z8;m~>{1w;W4#-nj*>c~+C#tfxu)?puYF7Ly|HBmJW7+-+#6J`*;Jb*vM&rkdtq`tZ z0q-XA8#F7B>-{y#_0|@U-=_bx_YHQZPAFodewiVxjKvxvslhRv0Q8R^T zOTfFoYF50LUpz|H^AsyEf*P?9Ru-Mn@mP~$f!n{yk&wOVD?=tL;0Jv})x8m2QA*or zUeFcfjybX{{B=T#;-y@#pBdGy;u_w)A7{aMv2AJ$NTsUc4>j_C-^7N2UdoN9`zNFE z$Of@l5$K8~L`{&7?7tw7!=S~*rA7aYC{?1_E-D8#hs z9<9~>Xc``ip6luaX@wNb31nGB1Aj&N=eGVm0#;Oe4^z3@m3I^URrgtw61*;~fEB>r z%}yFM){b9B@ma+Bq8x2-(*nQkh=de-ET+?+P+f2Rxs}m^ltp1SGA4kxQh>LDM}lx3 z7PG>;|HG?^Xem?$d>f^$mHOX&f*1i+LD7(CkY7>lQ(Vjb0V}G#Yf}?9 z(d^|In{M{mVpg!H0NekRU)Tqa1tO&wW7}XKw{71@=~yX2s(<)Wcy_!L0B)tEN)*l`8nV=^vOMhN4iU5I==_ zMR$>w9gzz`ennt|T!TAMEh|o0H)X*JwnC)G>V9diom9~f@@}SQzzREG6R|%0CN7A4 zKQ1)zVg=_|M3gNcwCJW0r^vG4#;T$cZGk#j#|>I|#X`ihsq?U4tqgibSXos-Q?ROF zs%S10uXT^s=6{?{&u}et%<@QHAGDRxhhw`F4^iCt`!rKiV`NcZwclc}fAx1D&=u9+ zv!?8$1&@W+7A7N{>lp7T-c8Q#W?C$WJRBDr#A$_`P}RI)hd3i4OVk{vm|f`2Drwb)mZ1MgOUr!oGc!PXRt6e|C9CFwG- z;_etK-hdVTK@F^H@T(fIqWUK|lD%JfEW{RIZQQ(15%q&Oc7`*KA+p@pUu^o4wG@shI5Nm(GD0}T{#|Mpwi$S`s?F*7>v=A0QqiSyH#>lZuZD$XTs^$&^tf=O0 z@xIo??aE^zR*3Z0;{J&(Cslhjj@(VLLJ$jqw{B&mt4|^wO%Ir~*sJRGBOgoN=iWE1TLNn*vtA;k(qS==4Nh)MAbfCHKp=(A#lNZ&Szfh-Hn2)$P7 zDxh#E8j=<(1onlg70{=7gefc^n@c{d5MPB>6~!t=&OKPO{XrJU0F9y{R%lhxNVTtQ zOHSD#jWiy;HlJxN-}@K)n1B_veE$*hZa!kfLdq!%%!J%)|29HVbL*h!=~7Y+ng*H{ z5;fhXYEzkPyA$3pi}cmG6vs>SpxNzXcsq~^%!(KTNg*xlO^`5TIwnwmjqaon~+WImq= z*$v!-`R~>tH>up@@YWVIe|vVCqAT1|-pc{KwfJ_O(~Agn4d11l^V(?a3( z+H)%88@Z6cXuEmDi8T~ALRJd1;#}9(lBM-^Xk5nJ$VI5hrN;4CPY@D;5M% z(rd0M@T~-_(9dQyYx}GStPq#S0{zvS6`;kgIDQw;b)D-%)bDq_7fD%MT+%HoPjZzolZ^C7!aqbDApCAOs)5ok2{`eqr9~>W z9t8aEd$r?~WdS=#80EKQb~(MAk6Kz;ur|P_BCA8QLNo)9AA|OgSgCs-=ltuq)>`i> zABVA}on(86qA2i!JU%pU8`b@o3TozEb6NzfsP(Bvjs-@hd-YZK?>M-D>i?84Kp9yx zoun){Zc2YUBG1OOV!#?(1en)!L$W`)uqW9U>iIbSYIJjCbL2PCh2L1!)8DsY0KFPn z7T#Cj<_7aZ^XsGkS=2_0?bgSM zIm?%~kA{C5IUEc1<%mlg1}0OO(@?W3V#HoJN`h)#*hBci%arZiw6aU<^l9!PO@X+F zixkSksaQdlNk_D{5$?5q*THeP0|6^)e-18Kntl9MUa17&hxE&x>2IPRe;zzfdQsC7iCd<#v(4PlT1YJxO{ZG~V9Z$s@0=!)k9 zT@lzI*ZdC1Q(FD<_UwJCj)iwWb*p~lW5Hw5Ngj$rZRa{WVTBMc*2K93mghX^Zn+_=AafAi^O z+^{a(=m~us?@wa%8Jg1yq3n!Iz0r?kazS)WJjATfq=+k3VOu3wZx4K%*PIlA4I=%% zmgPQ+=N0m^0L*^LUd1;l$AWSZmB&JjwpJd_e{RzKwd%F&ItR3}U|#U`c+qRSEBoWU z{MMAH3h0ZQue)Q;0}ap>niawfEcZ9R)x4jy2AHv7;Fz5&4rWkpSgPKUT#scjg0!G2 zlodj+*wH}>7+ipFdzqP&ak;DpEA>_5Vala2OwAPq&%kmKUHDdkA zOQbW7r9Y{=j@=D?P!*l5DpdS~>=7mfDGT;sfE^2>L4D17p4wWE4Omg@ox^dF>}|Hk z0{ztMmmJzcM=IaTzdi(y#knqcEZAFdrVeifdifm-zv8e$+{9o`JYwCJTbw;MbZlrk zGo3!3p{D*`8rqZI9R8q%dErzR-rB;{8BiKKn&&KkCX%bWI_}g&JNa(3s(@#+KnhZ0 zXm~$Kncwv)n0mhRFYd7cE9&{~tqIPC;{NH!3X>Lp+=L2}$j6$ob1Z(cjQeTre-|X* z6kXv`7mv_b*n7!RnoMs@O;fCM>F}Uk>1ry6hOrw{xfdobXTH5qeSSe%_}d{)R`9DI zHPzpHn}(@gp3)D@1tJ4!iVpRc2@)c9BvsA4dPkbn^PPWjj}2H+&v$>y@K``sl%vHX z`EdT#1V)e%E0lM$Bl-aM(%Mn+rPFPSRj@43h!6MW@6CUW+8;OlaA*T%WG|&$DPe<* z?J}1yOl*$u1r(G;Sv$mJ#{0|auZ}yNrV;3d1p;d$8J|yCnn~CpN>PMjjgeJ-0V(Qv zW@|q-U`6fs4o8|Ei*k0zqxr?ue>^~W$LQ+5o5{T0go zNXA!E^@=1hBT3Pb#t6;nn6B^p3XZP*4g{>I{odioR1PD<*#==&6l)8%K|l)h;oaly z+PfJ$pyQ>;&~#^MdTsL{%SUS{7DDwjKnZ-t#m9a)w2j86`e=#l8|if;PpyAy{WBw< z42iy2Zu>Y{S-`_tDJ}LdTi(8}rkeidy=PV+%Aq_H;?s;bkg{l`Iu@_R_oVCp6!Q~< z_3G|Gz>4bbmwWH22^}{TR=8>La~j>nHo@1ZAAco(VGTx`B6styD7+Quox0h)Ft$)s zvrIPU`>o`cusK1BVw@^p5hsKK&gE3@c;>k;<+4SHDs?j}8K8s{>nNm3F zO6DMTTA+oUX<=F&=KB8TS0e8FQvK^=Y3K^&u^@W`_6Ml3hOCfX@yS#${-gRkI@?DC ztf=>nelo6A1+Q`BfiouGBu+d+d^o!4-SyPQ-yJzNGM#>iayMrv64DX{H?oI83dIVd#rkI{g9>dh`{m_IvM5U3;iN?&Gs|Rz ztnY9BC|2)Xq7lI!HR-1uTX6eS>AgByW=mAc~$(42COK)C)I6f%HHRSKCqV) z>f$+yLR?(GXJnfCFKXjmq0tq{knZXuR?ukE`QbZg1c*>V z0{_332rAon-LY%EEnr2xcRZCdCbkYmn& zp7ZsFf4=dbZ+w9M{=>qrH{3ku<~f;#7_ISb8e?_`HFFf|kDC`;N7t{V83O!fD=rWu zyJcu&{=Zr;Zh8_{2>C25#g?gDWVBT&3!7b}EF6}IueR9V9O|j}E`FNdH^Hm`A7;7^ zv@NisA|V=8T7Q(K{_6|Q5U`@&X8@Vr+6sZ+fIJ7*yW`VCmxsEE5-Aram>R!LKv$gU zL==R_chVTG@W=5`4~+~@u1)s$_4i?{*5350o?k8dI+0=7b>F@bITDyV{dneB`dFIA zuMZ@Xp`P>cRXuBys|GgEJNUHHw3y0W8ah0_Eq`FsPd33{acpSH)*&xUY#g`)*HhqC zWwhAeyr!?-yNKhDB*2Nr$bq)E6)D(bK~*cF8|u#VnvLuGqMsvRMg31eB#TUM$!S;NSOM^Wv{uWcMaExm^D{$)L(P|qIpa#!suu2XfKSRpy!>q3KB%wtY=j5_cCK~#{o zWVkCSqsPn9;_=)kLz{=58F^*=@W`d1#|w4+r^sjV_A-+dl<{4Gw%FhN!&&Lelc{rE zP!y~w5GPeONHi3T{|NjQpNTtAKPv_))0;hu7auz|4l4k?&W{qj{c9s1Ij$d5ptg#Nx?T9|Vj)vMh5qc*3x4QJK0stWc;!ISZKS&#;~7v_6Y zxv%#9gPj}ppKN_$(86a-*;~zfW~B!Ig~W@kGhOd=!usHx3T8!;vN!9`->Cn4{-U2D z&=vJR0WVB^vL4aUqw9|k4chggO0!}KRq+4!#JSF{r2l?k0(1mMjbkk6f1a~(0Ci?k zq$!+S03$FHsmTi1KBZ}4GD6m;h(sF)K8_{YcQpUsHzOs3xHgh#Vc(cmF@^6eRF=ol|X8_mj z?;c&7%tp1eAIa|cdj;S2#=2-PhW2W)(GiK2!zogNoWKDD(Q}>yYo?7-GP7=vvDWU z)11H1?!IRBUU)1q$CtTHucHPoc!0itIrC$xnI+x|u}k#6=324G!hDzQ5%zX6Bf3X% z4QMCm(_r<$w!!DZ3Q4(GQG6~$Zddi8rtoOEo_qLwu1(GQagOFAOvZN;DVP;U*C3+plWtbP-uG`{1vuch2m0%DYHgBc_DLh(0WpvrHzJ0L|NV~Y z9z`_O|6R}mJR8hj8Jecq%%)RZyPx%f{1q>0>3;!#3eHHgpAB@@m5FsE@lh941?6cH z72Y6LzyevEdUuVAf~b)p^_?SNMSb^=kFK5-)2^t6vyX!&h^B5xs%%bT z#r3zmFhO2}6n_2vYz=rboafQ6xL+SnZydOX<`_gY#Zoo}wxgveZo&=y|9JY!5SEp+ z!R($??G^Z&hG!Hh=7(kMc`mU&@x$Iv?%CZ-te^(Z=Ehlx$506ed;Y~ zozcX<-KBaNJQDuBnvYD4B4P^9CjU1Ueqn-hE8yGoK08?Ar7Tj^=P|qB+9Z?*)CF@w zGeWO{7a#}bN5pZJG*~0G-o;(A#G+?uPD0M9K>UN&5@dl;TSGR;v++Px)Ovqs z^SFQ&_1?`Xnyc`)BTiP7?q%>uc=c)Nm#|PUv*^;%TY2Os;o?Y)EeK?J`E#b&3SpJ7 zmge?=PO%{#Yz<|5*<{nTjgvL zCql9DSTG*F-e+`njt^K-?|uBc?1Z(&$drahpj01&b>XKBxIaQ2aIS>R-f{DFt9mY_ znHJsjoA1|eF)Nt)UW&4|x{{LvP!rsk7Ff3m-dJqImXyr}UNU7k_scw|_xj~bEx-L6 z2F`UYpjgNJFvg>ELyb+?BUD|Jbj7prVBA=}Pv~qNAF!g{JNbCF(;9QJf`6O6dsS(_ zCVT~$Nf;RxOGc50h5x?ZuyNoRaUexG9Hm$ROM@u`T3ybprBP@&8%O{;=*F*|u^YBc zm=w5ntRW&(N(0Hi5vry7JNcTjKrpV97NS15f+FJXb`SMD{CgnKa6z)bxo@@nG0{Y~3a8wVyS$2&#+ z*I!q3kHXgAb55te+IN|o@|RBMq@sIjd|dUS7`pq3V1gcq=f+ zF`}i^$oiPC;-`&~j_A)4_0Kp`**6&MRoNW~SW(%1^55(6>_?lP8DaZCdktAx;IF$^ z;g{OIO~1x_++(M57vf9`<;>==u;OrZ-vlx$!I{#>W9-+G z;kC)Vq)wO=LIq6$))sQ5yj}JhET{_*%Z!_r;{~Fu)!xes31&qH)!seZMSe~q1zRJp z#e-T{)!xmky??-pdhhTrt*>NXULR4TLAd`RLz}sPS;d(ZGQzxcb|v~FW*bKwty8N3N6RG%I! zSpVp+Y?FW@r7RGPHfQyKyv`4nF)MDqZaM{xzTJn%ooL^#WWDr9X4@~^7)|5SVbtJ^K44BDYQQ}czt+;zw3bJY^6ulj6=O8ErJzCz^9E8B1ZSmM{q(ogv3L*TI;IMw z5HEzjB5zh`J4Et0g(Cbf5UWLhI&_coYG5CE-yQ$0OGbCL&8JK(kV3J-wm_2cRq@w? zxzGbve8%p8|C3zJJMc*`C%Bh!??cbD>|gJc@^ZR+nC^YDhWa*E7ITbB?L3SH!AP&t ziUh|a#v*!#sUV*&eO_20y#6Qv% zD_AM8Qx<(gJe!-tI5)d(d+H~l`p79z;?;Q{AsoObc+&#^3g(5Tg;e6e zzpe6nIW^Hv^|=jFkY5vi3l%d3E2xHd;N7hJeqM_`16I_3mo+Jv2t9XnaNTSKejiVcGi#XP_7=p@onL zo*HPTYT_U_^kE!`sV6Xo46jie3(6uCzad%wcN3gp_U=HyihA!Q@*eoNKBeNOQ0CG% zr7ucvEX@k`j)D|8$E{!m<#;1vx^LnHc@9sIZQ`#hShHvf@EWc~Ur#w!2x%&%1ivF$k@uQW(9g0dKvV) z^h&)%k1ZVq0Sgc-#b06tjYsF|d5D675hv*Pn?K3xFO-KU15FHJg?*_j&ha=_GYfYYf(8XRwZph5FJqc3h`1@;$@peI{#lvLtg>qg_9}k| z0#=m2N2T^)RROC(`WWj9ci%*hDi<+@6@l?1Q@Ix=x<@hp7j(sso6vvDu>$ss^mEMt z&Lw3I2qmxtA!IM!&iml6yu*6IpB4JkL|Ifi()4(yF|tOnVkMCRIo^%Ps~{gmC-DPq z4b2a-GFtzvBc;B%;Js?^K){M>@0htiaw++D-Rxo9zs$bL>*ZL1GqD|lI=f$|*^a@A zUo~U?(!H)MNl_EB#QSm$LIEsI2H+}$5_(^`PQ51|Zz;N!vO@-K-iXiQc)GHBHycu< zClo7CqnmTF$nHoa)!fJ61zwHE_F3k=4%YwDcOYOzJ>LLZ3u1quk8!`@-)HHCvMkRE zs1)ws{e5@L!F=Uk`qBr>kQ0hAXMPq8BC6gNh!-n_7D5!cI9IcB>32 zQ&r$M8$BCM2%(qdSiyC;(f?jxR?N9$&a&$`!y7r?oJVd_0_!RIAO2A3-@*VUms|~B z5w5RzKYfK(;p&AoIJ>{D7%ZF(G~1i5WCX`4Yn)VrvNKf16?Db9E>)XXc`QJSnXVvb zv-aomY8@G{qSiZm%0grls>0d(bni6#AlBiHK;QFgG7U{SV8d*HTqog zv1YnDqL}X(q&U~rA7o*D21i&1tf-Y0=tZy$+`UxyCbI{6vm!-jNEO(DD%>1bv1n!x zkm8Ppo3C3(qd_8+^$pdrw6L#c1}qTNq|$`YYZkFW>jF&)=7hXse3}nfQOVO?yw4x<@It!1 z73g2+pVFMY(5)+&6q*)@WnlI|)XLcvy`$j)YB$f>OtT2#oX68kRYs_sn;C<@@N4?9 zg7@NMxL1p|@FN923r+8hbMQ8=zjB_+S@A6SD{NIk3PV`2GS(jLPn_#o)fZIHD}LTS z3tIwK)b=fa6XjWf9;cg|72PhCz?Ji$5A7#O<;Dh4)R(sDeyNGn@=@* zCxdEOpGi%DUJ|gPHde4Nk#&W;pGsfj{;B&RypvE7=zD$*Io`;{TGjKw!f#W1paHr9 ztdMA`d3KzOGcTlHJC}Z};NHy&Ltme)$tUQ47c30fTD;`_@UN<@I`6+xP9|#cHmZtP z#0d~X`6({+*7j`#M_r3M5U`?_yL`%8s`4~zVTGoIv;Xlc_$#Jz`zF>V|6$>`Z~XR+ z4=h|48dSaryn{8%wh_>0Q^Xw3x^OZ=Ub$JJd%tkOdnr7S^T=!OC7;!*m=#-M?{v;| zA!=&Z&Ld6`FX+{-pl;r0R#gPLq83tonm=W6FY;!EW%TAj+Xg`|Gh?RMEXW1J$pVP;8|?`QxS?s@>X-?a&lriOBv~9w&dO*A?(S!ir*#g}h%G zE1YV^{MYe1pACws^xa%CIu`wP7qYFyPk|Ap@LphE^pzAX4g3|Q@8DnZ-GCLf-Thx$ zgY*lJXr@QSuVPqFA- z^B6=&us5#7s|hREOEO3*!o64JtZ?q>cS6JL;ou>qUZ$%)VZzn;B%@4o0RBuFh*(Y61BMlo>s?;!$qQcl z3!A0|ul&-OJyG~7=P~sJEf9J!QJc4stoW$<AmntswJtoW#!+1;FDT4;kfthgr^Gu794Ij6k)*mQ;d z6hZ<0X!v_KS5_!el4$=~lppW`=9Qwdjj}x&K?-S_6^=!sSuwVJuE~9C=IDv^fMLZ% zdd+>znam&CXg#nMkE{&#YE{AeQu@F-YAdenqzR9D(h^quw))u^b%u;hH)}G;+0^HD zRDi<`@mFXogrCoOmGhnx-Qwrr=aj~?(26SdOjfRhrvmX%>IY2<-ixkKtWY&?$ch`` zulQh0H89-YfZ3j&9x$w!ihdUBfup8)>f7P{q{c-N$I(rsay7x<7@CV@Fq%`h4#5jc?mc3m#dT6^mnxlS*xy(O5CtdMYp# z--kKJmFNM(imB;UMlWP!>hb1W!?{1MJQi_yBuaTHKaw`&fv67s z4iCcvDj6FVJSKW?CUtvmdvsPfpHd$`o_puh@~8}SMRB#8ml53{DI`kj(1N*|vNpO} zJxF;lhMLxx+TBG~+|>L+Cw~^hiplpO&%j&<8JQf>968SDmHk>< z_jEiPBPqy=2Yr&kK^37#r9K0F7JO&7vS2Jlo_Di?pGR}dIeK!e(2)~;7ILt#M^V`Z z^GR6;OEW_5rB+_KkDd%&&BA1D7WUrN^w{W%sroj4gNTTxg4}CcAcHZ=Cam~lTWKqV z3SP8_RLeV+xc+a16|ADS6~y6ebv??Rdp#@Ntt|f7CZkv0SNs*ltl;O;que>)c(>eV zVGk7c=f;w((3#!?-r36QNzZ1vb#jcW5f#3Nc|09GU|2DAy)3yAGCp~$M=VDZTU_Z1 zW^a}rkK)s0B|Z8|Ur*dwCsxSyfAbvn-G*Gw3^Ff@kJYrGYLRDoJYT8b*YR$;*&v^* z_}ddvzdMv8?zoZhNtk+C+G6E(eg5gEaAkqD(mat1R_NfM#O zRzyhnI(c7hgA`gJQ_l*%e_;=P7UE10n-$3Jrbn|Jq#z+UYpmQF#4L5x*)+AguWC{l zRuulSC;QmRwBSgj)xeR>Z5grD9m888LIGMYWCbE2D$Dy|)vw}GJ95Y%v2vQhNMPa{PeUFX5 zVyeHBtyt4sMj=OzSRol(RdC$$Ip%tARn|%Ih@={MDE3^O)&K2CNcEklqjxIt9QM2x z*9xQfNLnefcA2ObW_Ix%@46vLmt)HD24`aH!IEKVlCiePJ?%vTRmsA{}93q)Aa z7}y;&Yl2Mu?{KQmXjn1TJ^EGlKeLV?`q9NR$+;04MrF-Iy_Qvx$OS z^?p6WiqfNfvU@qMMdi!<4~G@3H^pZ-pLA=1&w{f`NDEpZR3y*EwRiKrMp_|MBUi;q8@T1T?wda82miB!d_pg`mSxEg}smw{g#~pX*+|5%7>IEmZGBPXV zS$awNPHg$C$S7WEZj9wzBfW-ykx+<#tGgGsrdRpBsC7dnp-Iu8aw{Y@@=ADaY+h_% zxZK*BcbxkiX8Y>s0i!Ff)c5eP9S^x} z=3w>WC?C@|AWEh9$l^Vj)N5EtSu~Bq3KBv;q86BZ6IPH%{O|PWnapn37p%{%S%G`U4(?9x$x9a^C>$5RM+X)vDqi*aIA)LXX!IrA90Byq`sm#pUGd ziErE?vrX@;BSY9m%jC-E2-VAUv4XU?J;#bKljR^qDf@%upbgRoR*)8#BIVX)GaKY8 z_gNTLTsc;Jm1WJl47`}s2@Webq9kuAj$AF)hsht^8PJ7USEmwR!pEuWZhln#P>hic zHXc9r=0tFdl|?C1js48}mD&d3Uv}F{iJEThsZ%^CttwQ^6gJ3-$cb<_vNy|pMps

8t24BawSkRj1s~{;Pb~@OE*&xiu!YfD$Zy;>eCcMfm5EC=KvZUbI z8ToWFD1{EyLplGPxmPPv@1mmjhp45;l|poNywM5aeubu0At@LUaldZj z+hT?Aud7_S93G34$krqY81-;srL6r=N(^FVDak=jh^km@YVsOZT=hM0rD;JACuvaZ zvEaz$wU}&>nWcKH(u+Ya!)4eG^m2Y&BWtG5Uv#_ji|W^n{r+CMd~L4gH=J>Ukiy(_E%uc!>_3U8p!D$Mr0(%*_X|M+{r*dXKYv+*858-SLG zj8jn-+6vM8PA0!-zYHI!jN*)nZ)U&=S`V+qAN0Lhb+GEi%KI?ilvKH0#Z|{n3Mv@w zvml+WBr9%P`2`{xPk|GnDnwVnKY>h5nPtjn2E5c4qAbcSQ|W)azp0scyUG6E9j_*S-O~&nwEDFLLQ*Jp_*vzKehRzJrqv1$M%aJA)Va9^^tWNfbg+V} zp7Od*DoE2F3yGa_-)qTrnf2Lq*=0FmFng083Pwu5@i#A49<2J|ob$m0DtnWaDzr8k zi@wODZ|gW^M??5-T3K8fRxIzRQ#n|g6`~--0;zz$pu#vyD+JsaZ2Hn7lvGh6#`a(7i`oRr;@jW6SP9^>DXxfa1PpyAJ?#MuS8Zy zs(Pc&t*oBg;9ITis7x(S-%3W{eHp8mdRFj^ib5T)#=lHzR?x2rRY8?O3&aO+g<=Jj zMHxI7te!Vr-+_6b$@PHI6_e|~u^-KNh+A1WRtPg7*j`I4>>(w<3o4Avm+)T*Q&yzb z#E%7j<^QSg&bnt~&)clH9p_{GflZHnc?a+T@dzQr`kpPB{nHWs5c}-{v{>}|6#y;97_e!p(?q-E!g*dU( zSCctbfEHi2e@oTuroFK~a~k9QjqH}p-JClHZE@SmFWc`%A?P5k9%zHAi3h9x$x9 z@_nq|fmidmQV=q#94kaCi`NtLQk!Xquw|F^Tt+=_W@Fvju|F$~BYr*hGml}5%@6K7 zUC_9%xM^h}(b%ch6**QU-%I>1gf&bRD`0a_V_-#7#{wZMB7w8so11mtnER{02MjBw z?>l-peLK7bGNO3vW<^H!uY5MKOxYi##pdj-DNrE=KL)n9s77MEf&bJH#VioQnzb2GdY(m)E<+C=P>-U_WMa9{3wz+_-uGZu)kL8gxsd?!XL9agxl z5Go4`y5n;4r1Efz*J5*aVfxn8I*?+0b~74z;pmIe$x!?Xtt?no<4UpOww15O*Tk@@ zv0{Z6D}rDanNk&umqJtcrBzrr8C_vmaSiprbkc%eMK>)o^Gix;3#(su?p%8S9Yf zDeVv2%W2c17wZ7LYgl1eaSiprbh6^BY`%Xp-wLsP7O+F+rPk}%sf!iV7!EB)CqwZo z#M@B`-v}%5(PNTNtMTw6lgkg0qcS*`rr?^`*7VKI6{z1`?!)Xbu|Jqu;iwA#zEEG# ztYJF6zrM-6o6!}M>%Z|H&0K3|Oy&DGwX#4)cygJOY2l)U!wT_S6w_d&J=)W;1$h>% zz{7d(lVL?ZEgV*?fDMv?|7F0-TK~+m7;0h;7W|sh=&=Y~jO-1qiI2Dc%jqcPcw|5vI#46OoV>Uk=T&`oRC74#od^FMn5XYcm9YUpR#Zt z>taPJ4OW~=4xrY*WMH{j(Hra!w+4Dqm3MXE)x>o7zInfi_kdx=#QSbyN3!Z}JQoeg zw?Z6caXERb+YT94Ta1+#4k@^`CHo~jo8qx>kN5GAf-_n2NvHMFiqy05XXAA$uY&Q9 za^H}iRSI3PFLVN|*cZBp8k;B0Opt5HS~ski*odF_QOw_TSW#$&Xl3EHLv(IdX>Bn! zTI93hjqKgX`=m9*{P6J+!g+O}D$(9eWTeM1GfYKCS+8QqE36O;qz@~Nb-@DJ7b^3` zUF!o(yhqKE)6)Zn71Ptt?sw7lhur61n;m5VJ0yvC2xf=aGemOQqRQDGP@ci&1M+JQa+8kZ!;5+8CVmM{N3ORb_ zX(27dzq!6=VR~-rV*91`1*zqjPs({A4k4T^H!DbrEy`zc3YGfC#)`sEsM$s<#CgtP z#r$Tn0`tb0RZ+wWSRmovVA#wsy{1TESTQrK$j{#_^lv&o3uTAE_HfxD8OB8RBctj> zduMV{GSqf&`*+apZM!d-O7^!cOP8WWF)L`bEK5HZ9}6ou*F)1G|6W*;v7dEV@orG& zij|jS#a?(f%dOW;48*YF8tZ|Xpv7-cCrd{SXl)dZMVTAIXa1Nx*``?`#Mlf@^d;_3 ze6Rh-YqxF4YiqyPxhUD2+|rZJi&CTzb@41J@(picI9zaB8qQvcggDP>R>V7YgB2Aj ztAbVtZIF5=y8@Ar-r%v|O!T{X&&l+F@mEZy_a=TU^EaJV$O=bU&DUDpxWAPY#UZ~-k(^O{=Lf<8IBdKXTcnc(P=UKD`2#qn1JI{iO4tD;|X*HpubiI4Zm9LW`f+{!}hczxA&l*K)Whb*)0 z5R#%k_T8#Ssvqn8^G$X`83)kdaxj47na-u|mj%VzoiNK|VPfvj%M5Nq`hc*VEL+^l-*-Jq4jssZObJ@J zSrLb~mg@=3YTQ{nsYC zDT@^~i&=Z^WqR>nj>u2K7gqr|XcCRy=(UY)Gbl*N;Y z2eTWq;q-rbl2vT~Vs-ZH!k#A;FYdCv7eb5e8{XRRo%W0Er(LYrU*xZ##{#p+kYiC= zH8{)=RuuC@SP{qC1mdlTciiuLqUNY8?m>?QtdJ&;WoC-aEy`97S?8K^9% z-Pz(rIa$t3vU@sCCU&+z2{N=nRXkG77O4CO#{(ERjPhj=Fur%I-}`=2>WE z;b;l>mc&g*Qx>DKLh`V%{%T@d4J{CvWhx$vGFTyZnK`A`u%eJ(xM9U)M*8HBJXtyt$fK8t7K1Kqo_Whqbs`4w!*s{isF%bd?111&b%+Jdyu%Hqds?@!1KRH22N z5bkZ(vS2G#4TZX*_%&Kh$bBZYJMehTQ4i|!>dY(U(LCfanqu<5m6<++VZ}^;1G09Z zLyN)@tFyA6iO)*!${uM;+N4mdsC^l%5L!H`yqs=jv2DW*v1Mvy5k^nR3pXM3HshvT z?QkSoWYn{zUgKuP3e@mCRJ*fw(Cc_B)Xa3Ogg(=~Y~FwJJz!Wd`97T78U8c-KIVpN zZBaa8Gj_#WVMRFoiyBRfK;6QdPi{ne%Lt6eb0e4#iUukJmC33=<$u+tQe*{bK|6#Ik-JoOmRKR%)*cFRR%&jRYOV^n z))e|4R*15Hib++>ihQ0ptXPpcmDmndK~|`>&}oGzQgDT_XHBgN7Z{N%Z(q48!R(4-N=bYMN@!Auu5f!d&#GBvf17NLFYK9{T997Y!`Wx7y+NHN3SgVmIC9@at{=$YpZFA8lCvf^RX!rESY#A90^DxX`c3TAqnRfDGPmoigl zGOU=Xo-cl1u1Y4>h#r^DsJy4+TOHl;yLzZB-i5ZH<>55(S1^lHY!I^IlOOyj^uy{$ ztI(?dqvq>%_xiqXy%cPS^(8KKvf7r-3|nKI!piHq71AQ(@@&c{)A12LLsG0reVIhI zrU$H$d13{u@cN$$nB4B-Z~WTYV_0!@eP6%HaHd!#ECOE7*|ZhFIU&DFKB<%iy_rG^ z-t!T2ESzj_`YX1teWdDZU;FF7`s=^?hwJ~j?1wkLScz8kV%5Q_k819y`-#6HdZK+@ zMu@RF`(?X3hty4rVp34?$mi0eusXulQSTA3f>~EOyW7zft*BUGayGB7U)uOTjbX+3 z`)oW%Y#4V{`!ktL`rY6vYjLa+v%0ooKDabq^`o&mr7VsqZE+q}$OT0T$FnJ!-bz&j zzlMKqR)7-K(tZnKpjC$&Be8iY+9nI3X|%48d*MYXQt%nws41+DRetD+CR!jmUfLDt z!+NDoGc_y5^Q*Z!Uu#%#CBK(5neo+X_#iVsI!mp+!>jbH#*KkAi1Bdx`JU%{=;8ch zvNOI4(a~M7M2>+I{~tWdmsJepNi~m5TOenD@SV0mnP!EpEatpe>Ci%$L1KL4jvJd7 zqVm?4E-OS+LleP$uRWREM?Xd7N@@yJ_P1Ff6}SC>at_0E-LL|i0*SzMV0EQJ; zk`?jPr{h=&d}~kG+6jxFHb2|j*xKni>)Gg!Mo%T<>RJvvq>Y*0-(d!KO|l% zs^W(1W1Wvwk`(v{8wBn8e<=Is8-F;5Y|z@mxhF4n1r|p&A@c7fn>3#m`q9IwSL3r9 zsVPwX3ao$yqO-dLT}oB#4b6@1Rje>k(<}K)n~rlER$R$mjz&KnM-AHZp`FN0aJND4 z&e|txo~U`eW_#_d##Pp_;Ns{z!6)FgAT3@7E#8B|(6d2ur0D)Xs}(2IjbcU4XHkk3 zA60)?eX#1|nqz_Yl^t^1N-M7#DB0mAhE^7t)coe{wNlrHx`Gyn@>Y-%D#yDEYX;4Y z(Hb$j;!6I`rsv#-6<4mG&tyEl`D3mE6;E;Qc78kR*@3aBnj*&cs=rtLSoJS!ei4eyb7)aGKjh7$nK`5uvxJ$ zv>?XS2qS?y>tR#7;u`l@7*>pRERX+n>DXb<$uX~(vkD$UhZEd#sR~63vf^>OJyG+0 zD2%r}_gaU-pDg)!$!{ay34G-F?{kVcp>}9HgkFmm=Nv5ZaXPf%){9kl)Sbf0Ar2|@ z78S)k9gCwkG=8s|pXIRU9Oy=(4K|_5zp%o|?Z&#L^J4q3D!SLdJ23wKGe=xqJz!XI zrM{0dnT`HI@0ZE3oj;S7de?4R6sn3HH9K;wATy{j9 z-iiuqjhPQ}jjIY%7i(-Ic_Oc}I_i97jkk4-?bw@}chCZH*&qd~0v3qq3au@~8hJvE zTG_XKcvk3mh^7UUMQJam=EcFP+B>K%q|rxFxpHyzV^3Svt?&9xyh@mFejZ zGOMsopT{>+X0z3?J8;;u)AJH?Dp>2<%?d_Kou*kq9%xF)y)Z53s@1sys?dDF34ZR71glf%CO?gBwxi~MR9630LtVHq4KY<|LecPd@>b3Wt3EVDe~_>goYp^L{a>>`mvfFwe(RS z_EFHz;V<|lXiy6o@Xz-iarKNry*XllFgItMk?$vm? zuf<>Q+g+65y*$n+vZetLg1m#5fINTxx&G|7O*TmA}p71#OTYD_#gG$gu6T=bDb- z^HN#ltBa#x1+px#%2++@kna?|+Xy>wETf_PKaLRygFyrv+KTwqG58 zsr@JZ{3zc^udEP2M3Ql;Ah3BO-C&&ng z7J7S`;>D$$z9?n|nXyb!LTJHTt~RtU8ESNFksm=*?11%g)JuOR8fb91b~Z>yYlNI| zm`7IZ1S`rd=3Xr}xmSNN(GO>Q7*;raG2Sh!qtrZWW>!#DKvy(IqWYZko8hN`qF|%9 z!p#O@g|mnAymhlx1-zS&S0DC%8eW+Gz49{%C5l;*&x^U4QmR6$i**IG*aB^`tmo~Z z(Bj`1BSn-{kwW6T@Kzl5fE7wvkP{9oNRFMgkJk(~MbxZPvv!dF=Kpf;4J#)8UA`T7 zwZtsan-+-$LRRPsdX+27kayr_g;*Z?mh8}tUI9%C?b&?1=8)&p$g=dGia1f47NRv4 z_Vl&y?H)cZvH+F;#cSbK7jI-6qHiMSg4x_VP@e*t!p;QY95K%OKorM1jD2LDLkqHE zd(ByI1QAoz_%HD4PP~`RkylF(7*tc6iT6O6dtlTbx$5i1SzzuU@pnTd)6FzHTHmE#YDfA1DO|V zidpeqLSi5-sNyi-{X)%1e>HyC(_(2>XbZ%#Ie2x}q~Kmn3eGg$UV997$8dVWp#wdf zrHH|Om%6*!`;nnl%5Onh6tZGo`XDkYSR3!KXTZs&Q2q$cJ%93%CAXQob`T*x^b+eH|Q;7g`+J< zihNcKde}e;PdIQ1@ebxejMhq%LXP@WP z3z$pF42lN-Y-`Xv+bXw^rI}L}d>wo%$lcUgn`OSy=GacO=go05(*uST6Ycj?$zTyH zHl&c-ZQCGZ1$Fqz-`*f1g55`5^o=f^pwxdb~{ti_E%Zpqy zx3W;Rd2%+0@o!G_cXjn1XIL@O@8*2yAFE!?s|HTp~dNJcVZ{Js)zIEw;_(J zSOHQrfD%U#JEhM;J~O=)cpjNs;@cEEB<%l<$@2aSQWl046JH3jcg#szW~WYi5d?GCRWx1!L@7+G1!t4`bAi$ZHe z=VqPGMiWOohg4;5M^_-{1FC}A6=X$)b)=S6ZOY+^(X^mv^YNMi6*HAsNSV*n?w;tc z>uNmCuwr80%9+e8)UwX6=pT*h%nGF|tUG-Nu#y;RTzg3hHs)bT)3k7MIr&_f)t#7_ zqHt)Tw??`@jrB_zOU+joLW|Scn_}CQ{UKJ7j+eSw(O_|nF;YX6g^q$eR#WCfU8Q+YNYtI^dfWKEEJ#=Y|M#J&x4)O7TKVa3FH+3MJi90WHjINzKr^NFv5vrKPX z^lY4&j`eoVcS}xHXm#PJigI6Pka?M>RWw6x3pp^h$77`hc_Fb>cV_1@FyzxE%*oVE z(L!aNgB7S{Lp1^KX47G1h2)flV#T&RR+L*;CLikz;?NWO*340p=>fxviS^#e#A5}l zm=6nttPonXf)$O;=YuL@NtMOtB6C;v}qeBQ{96Z>+1MjO{x(&=XIc zOgxR1nw6R88LkfYXLa*HY0(-rs7<+@%HwaOeTFY`Aq!N(5}E;zC)fT{U*a}ynQ%TuQ#li zSWiX~-*B^n7Rc&AZ&2%s)qam}_M!o3cjehsx+2GloGl`xxH0xd0U0=cr;8ON5i_;+ z+4DvgWS1X4+!Gt;x z0xO|{5NN!|R=xkpzyVX|cZT!23@awaiu zjkH6UwJ8}{R2l=`ZLmT(JC&@U&$HD(tMNe3iqvp97FNgnXxO>|{>ro7a=gA2C6uZN zc#QQi!-_)2h+)Mqn(QWs%Q{ z3g7PV{>*6Oxp>4ghiVyC9qjAw?tGDf zdE8o8kQMOXf)->2;-;`oBvM>%9Trv~5>f$G(t!Am$M;qC*?6%cIForT(dp+J>jO>5 zNpV{nIV~ZSAV12j{7m|Zd|&36sptX2iiz}MJoQV(3aMAk3f-_kV*TM((GvTiz|tOfl(h6UzHrR%XKr?})Eo5J{fE7>{wjDAER+PgZ!#2QdEHp+! z$cmw+je*}}K66zvXHWk*`)Xnpe7=W0vwZc`5~DO!<10ABM7}R`%vAJ%VMTr~p2<|M z%%n!k3$!}^6}I`~HL?cbYl(8--jFZWhlr_C7J=1)X0T!atSE1UswiSbx%G70*fOtV z+{^gp_&vA$z0PHsEwDF!4oif#2-`AHV<9Y(SJpNp(`#{)w z5%Wt25e4BRj4hW}llDjc)qSLK?TmW*DfYX(5<&z%FUJczzE~S+Z*3D^>H(#Ge>1bsu%fu%pT%mVt?)0@Sz)Znxh7UzzkUO4 z5a{i@8b8RKVa6t6AQx$YC@yd$c&vY`;rN1QQ|)uS70A=HzCeA7(MPU+g=T@ULGLZR z7iOHsdADTe^~_FGB$oE@(b!u)DM+6E+3vP-#7s3SXj>c)?*T2SEa2HKbpcT{`SnCE7b#r z6~#Tvy4(Eqi(Ot0AKk{lGjVE+%9SS*ne>579J5ThcE*7BKu-qVioT#PM$aaXVFN3C zzpN>5%&{Wp*~FX>>)i;)Fh}Ot#<9bS(O)mb_&v_;O@u<1I!E`3xX-B-1vw#YUG~%E zLJC=-x!$^fED|aU=8Qy;09nDj=DU$4(BQuqiNzPhFGl*qdISh7 z>?lZ%6=cT7&=#dB+_V^t7Y+m5Dxi4#G?WVeODkTXOg~fVzL*r7QL}e5D);uUEdvp_ zJ~};}7s3}JdqR6dBq;g_6eUTx-5+l8Q(5eU5A>+_#zniJGosPEd{3aS4%LzY(xbu) zO$$*M@O>~ZL$a|zilHWBf0X)V&g4GBiqgG*b2B|~!U*0<pPmm&(Hj%-l6q5-PC+)5|uqjZ{PMyc_u_l=Derxhu9 zJCT;p3gLvXVnH_2HsIB)=npr=_F(3V%#)*E2bx1=S@Z?_!-weKgt9p5eJ5DfxV={V zI@=0J;iiSMH^5(39u-YNpXq?AfnNH%F?(mG2MjAp_xp;J2ex%9M!No8^Pa69zw3b< zD;|VRu{X2;tmqBG8;Upq>ChYa#B-LcfF-jDk7Sch2bg6um>2EAvYD;OP)#rMDl(X0?us-DX}$FCawhj0Rt(qsyE(U`fE0L) zYo5c3Bc9*ZHbZ;I;&V~$zud0apbuE58N6?MK%U`0x)3i<-;ts7$`g}!PRs*B-A z^JpYEt+a&P4u6*6p2;VL{&dXT*o^;V#ib4R2gC;H4X#ZbSKdvGcx8ofy%5yBflsnO zd^~(e*=7UY`w%q+D^Ssw9!;Tx`yS?2t8^B2uq_1ji<^wSGdBO2I+*s!R(<3}9BpK-yBb`}dGWv69l1Y{qZ@D_af^1*= zTgqS2>hDQhK+H$wZz@*M*C4Fu3(k#|shq4s@KDIy)3aXbKlY*1oZhraVdq@X!aM5K z^LK~)Zp{9f=mEou;d=dftYW-J8J`BiEeFCObcNJDM1bl#{&KQXiO;eyu9Z~q*#wg z2v;&(kZnLDnNiI-(Y1*^DxY0g(c<41>JM!S?8Ux*%%Cen_@z}j=Mt6@ZX_%X6kytuwwYWv^qNBeOMi!*PjZ5t&kOEzI~yK zVNgPmq8{pkK8lq8-)fa-6Dp%Yae}O94lL}U!e5X2xB8k*j{qA->&UDao(RJ+q!b(6 z`{eyb))f4lOS5xxte`jHc!*Yr_HEKT(;q@6no?m@SuOsd`-;i_|M#Yv2^TZjD_Q9VxO<3 zO4h7ErKs&#)wn<0OTQ*K!B*#MR;>6Kv&QOuQW1-qf^A>>X3Pi`QjidOL>AH_e?;@q zRI@@W3O6g{Q#hYWf4Y1d1gt>xcR_q@!WRQAkWoQBp#FBwR|gwuh0JPXmW9$4MXbmt zMTH$@R`v%nxJ<65^LxD7whSxW-$*9?BxXy7t@+W6nqhi($5XIE_P|%kE1zvq=UBmN zwau7ey4r8A7KCbfq{b%9NLQ?|{S@RxtG_ciuV)=du}-yl=}YbT+Cr}IGKxo{g^)wP z;`9Vmg(iigDKsZak-}kxSLq7U;$nDhVosEH86!m?1^#MTo3#Pn+_|x4KXO{>+k{Ui zUsup3b2J6#qKT@Y&-h%E$1*E_xPOmV*S29r{`Ya9XLpeED$9H~HD}U)R#pf+^qLha zV#*wEiI{GKm%ksiyY1Pe@J`TQ(F_mgC&*+ZH^_;UKZW(vKV3pLtjo?#N8-DJy92Kz z)}iJE*|FZ`v)E#LEsEo(Zf(JBp@p-h=`q^nQ1 zT!)daIDx-eSi$F8{EUSlr`2YKWP%GZsIzqJW2lLqQkk18Su01puNJHkV0A3-$p3!K zqnYag!-~Sb|8>ts5P=qTy>&Tx%(lm=E3{2bRvcCl)9tmZkx$W#m?%{TBZn9@7&&#; zCzy@SYzt}i7JQ)45&iA6qZH4ZKGbzYC=;!&T^i#JB`Rh+hY;y#Z) zH{Yw2n$W(9r8dP#3(bnIHuxbyhYIk(7n>6$7x~vxRbW4|OUR0PSY`d;BP!RMx}w1q z1!+++QLQW3%ji?|tMY1M2FRat)*ru>FOstt8J$u1`<%JQh84wq|MPShUI~l|SQ{Qo zWiu;M&{N5M6_&8#Fe*j0_%}g=3n>^MRU^lDm+uq!E7Zt=x?r2-I~U#+>I`gB{*RY~ zC)(GmT2><|i$ZO|6@Rv%MrVF=lVG%MA%$jADT`yB%DiHXlu=R=!-<3J?L<9(<(sJB z%-UGWU)HF20d{a+h++lE(HlhERCz46BO{IZoVw{8P0~VC1xdlI3f9qbuYUUHY&80f zMw7i+{Og>#J%$yf`rhg&!><<&3V|LAQ5CGIpjp8g4~LPPh-)g;gRErH8*Ii^l}h4< z#|-eW>P*iq}GJMd|Y@uSfBgoF(Gs#s2KR&h&%_73Pt=--Na6vC(G}d%<6CQs&DmE<=nAre^x!SGOErBaIX@K)$wYsN)A)H7_MDG=8a@_&N3~s{ zbCHI)H__LAaRYgAaqa!cLFHduuY494!@a>lFTFCNyIQcHtUw$Yf2FKj$hoOxMZTwk zs)9$+v&lX^;@N1cicES};E3mKuc=-!-0yPc_8V3V*Y^i{!qChW@Je#D%V@}F1+DRX zRy4zN!F;XXB1fwnr09g_6O2%t5K7bxQi&KFogR|o&y60wB>0AoM zi{9Xca2=F|W(A1=ods6x!8y*V^^i3y-1Y}mg{&$u9um^O;aX06xe-Hodm}jQD2MjAl>hZ-fT;*VeNFKAD zwF&(V8w6RK!ipA)tMm#f=+WRtjSf9f#T z5*+_j3N6Ui@?gv#R;-}KLMs(DfiN1x`B6Cb}O_YUgMd2^p8%$w^ zVmB)q{3l$>LP()35yVuRkpX+eb4b;^ARpYU5LKbP7}n0(8y96#VN|V9n$5Sv^IAbh z)=1xwd2af8z_4QEzFv;#DTsiI_6{rRm97|sZ&P9bT34uSXUvwU$0`9BS!%pFoZyvu zV_eq=s_nmJ+X zKl3uOM*e=xv(wiDh83gr_59{RFULM}0V?RPfQ>;Lgll;+n}Ty9XoJWcD%kUWR`uN* z`aFC~^g@K=45JS7RC$&DG`v||(={R7c1J!bw69`IQ8YxTAbtvG+s%r6RiQb-d(8^b z5Ta@L8RjWnLt+WDHfJM=rvmuY_}oG}Y9!iOeZe|-d&xs-4VWvo2UU1&|7MOAo1nrD z!%m~FaI=DTh?AYgy1uN4PA`R`2vo)8*kDb zJ@OqIIR4C1tkAF5_s*FLv${n|2sfNttuNjI15n>z{RNJ)ATMTNTsyR2G=y0u;{8?1 z0+AqQy&qDn(4^o!NCExevOu6Jwqt!ZO^T!5jcV1$(Y_P&{7m$KVa4dZ{!Co2gCkLp z28)*XpobAt#z3el7%3HB1*>$D3{5dT&QSeAJ>DH$YS+3U8-y03Ec7i$toSt@8$^$J zhZW+>)WqN;_j6J`Qy-;CK|RB>>vxj-rRuR(6Y}oDiuIV|wlUCeYa;c1y z(R-Vj76VOcM!L%UZm^I$09G(ssC^Z3ou#T^b>3T|w?su#P*q%rT!=gnyCr&Kl-@~> zT89-Jr=t5cCDyCg$QainUT0%zCTLFZesor7t>V0|FoL|e6^-}0>CdtZpP~zP$c2bz zp2G^PMu_zT9c8f^z71xfDP@7Wn`8kWvzOeg7-~9-m?$}+XVkGK4pu3%?T@3#z#9GA zFt3=Y9x$vJOV6)~9r7rDg_{+axlUH_I%|71p)Bg)*|-p%6TJWhFP8oVr74!gUk>eS zb6CNvH?QMtGKQ%n$Ohp7x5x|L>wWp8AR{y@NDQaRF>))+*=)auyc2IlJ-Bb3=k(UP z>^smCO|b{!qLAFQ@SrZgrUh$Oq+o%G6+*?(s^-vXR%nmKAan-TR3|BTTj5i_iktbl|NJ!~L3KS_m^V zIr!e12*ck?JY-Jvp}5cjtWfKMkd9r!vmm9mL+IJ$ycmZT^@xoOU{yoOt`c9x5%@3Y z*A!N8RgrBqM{0EhWC$K)vnu*nevLEr8pDdQ_54kXhMHvVbOlC;u!7Yb$O^40S`bf! zs))e;K-7cRVs4WY9BVf=lL~JCB9%pNvLRN{+!`GU{i5b0&#quoY)Sm{$b}u?Iwl1r%fZJCf3`Zf|_EzZSRmArRcFa>y7OREr~yrfS*>;kDOqW z=&7SDT2yT?n-wb0ovgszeK1vY#Zm80a6zmO-ZGlBz2=C@)MT`Dhv$LV*uD$%s+s8l z!-}!>dpt$Wt#!pLtWr3HSyN<%OI292Q0HZi@>I}AaXdT+oM2NHNb(IeN;NIkXE$Vy z2A{AhS8h!o=n46LSo8mOW^@1kXWJLaGnq3*q#zYW;sl@1X9XW^&YtM(g<^RcdZi&2 zvhKBB3Z~<05)UPxMe9rUx6ey2&4@+f))||#3wkyNpezy(C2m2@U9Mv4v{{(dAVYC3{dL+An3v=9sAD14t}$xzc-uc=lsw%_7Ry~?m+tUbRwcn(<6$+5A?{p-`(-; z%6CKA>_26s{e9;DR(9(d%oHp1R^)4l(vQdoH#49aGJHR_w}W51@eW@s{=fG8Q}*vO zf7kQh(m!i^ApX<9f7Ct^Xo$ANdy^805oT=3MzDJ6lK69pCGq27&T4U3;dnTiRf5=u zuE>o{6wCl=9CYc5Aurd8mKcc8f)>b5%+_T;nmHiy-CW(bh81J|eoiJiHzc1G)D?0? zZNPQ)te2{yKin5$tdThtb4UuPid&+ehG|bY?TkHg<=xMPzFp~y|5Mg!|1CW~THE+^ z?9UTo1v%kn#T(f_+wXTK^YywHYJQgd_qIQE>Cg z4yr<`A&>RjFt3~b9x$vJYoC9R@u;j#x2^yy>{;3^{xVW)1V|5-8?Pa!{pv}m?^61#%mt@fghLopM^w`YI$_pU2;BwAhhS$KaN$@H`2oM?*i z3(FVVoZOsTmwd72X%&ljCSDPx0wOIogK?2~FOSpe;HTTwqeD1zK>N{2S+@a!U~;V(0~!stSu0Z zC8g8Jif_hlj{YvR$T}K&!vCS?k*e>a{VMo=`{ni(>HI1U?*XR#zsZ;ab((dAMW%)xT80`+YeH6<%h^#;*Vx3^-uvOxG8pM5nxo^Qdtc4m6O zuwp#@{tPPik`K(VfOf_f|Kq9(3xt^!97$|q(eDZUE_^KXWZ+KUb&Y@UB&)t%`AF3x z)sIxC-u7DC>us0Yt<=LkWX74y>xqZcn^3XydsY8fIpAsbeFMitq6aWuC)?bA z;Y-Necp&zFLBsfBe79z6z=QY%teyq2xzYQrdwplJ5BI#*_Cigd^6QPQv4yDj$M^R_ zQBYaTQIXSrdq$beJLOC=DhsVD#0p_8EX4}UEQJMv8Db4NRZ$La1=g8Yd00bD9#h-K z=!$Ei2izlRv~A1`@e3UWU}sZds7J~I*^B50)ww1GZIC%pdN%ijB1x>Wk=hyfZWU?K z7`xnls*R1rIMx1Aptf-%Rs>jr6*roFk5zxK>M2+T-)Q_>-%Zim(vu~{8{m-hcg0hg zG#_tGKjnW4nb>uW->Fn=f}&}*ZfN|E+LQQ1r`qrEQ2~9o>K8TFHHNH^_4USswfFg- z3_R%fSYiK0way9sx5)0c>)((Tt?*W`DUXHC3eGJhE1W#6R(LCDfgDk}6-T`{nTmPW z#7Hr^!WlCxsHC#%)Je>A>)lB~e`Lp`UUUbgn+M`^jv z^O?+3{s9kJvAqVQfHiVg~XdtSgZ&&`oGZg*_ssS=nZ_+E>wnDmW@$irFC*c+K6pYX4*T2Fz<_ss{`! z#@F+eD+d)TP%R6@(5#@hLVSLXs*oBMOX7248-o|uE>%&G1*sQmlDJye`grXYZE?B1 z!~g9{59VKO!WB7%YyIznzYBVBHQyP0Ho0MvHpuDh^7Ly^FVruzcQVKkJq0x6fo& z!3sgt6iqyv^j4Ii(u;U1`a)f(Wx>@#dNHD!Vm{0S*%g|LdFRZ-`hB(`8mN4v`ny#f zekcn?i_7i5g1_-D=n@>GXwi)75BFP71kQyoBhos#b~|T$#cORJ;vT93q~M&C)A$VQu{sEt7vanh`Zkfnu2d8~GcAE! zEX3v8WbS5}iY;>;WY*mkUBTMD;;mq`^r$xq|E+1)LJt^LjQ3lbAEgx{tWXvRSiw1C zHY-31Ha?adZ&c2UEI@q-UYikDK;9(B;diTk5)fKkZoe`5?aGh6C!p&0fEJseEt;*4 zz|l}N{$AUbN$QF>vXv=e1yl^^bY0^QX>X|52knlqudFWYdx|TQf)&55eWdCpQf@ug5<0hpkyD9M1ldl{rfq?kl|aV( z3!JIf8&-_B=egD?6$NLG)hllWS3w6U6fLCQH(5bnCuf872EB3Cv!OmvHDN1-8g~va>Uel+#doVJK#Ki}6vB$WcFcE)F&5H?zmB;iniU?^4~}-$BGrp0xQi{s=)tuKi&x+?HgurqLkq|Wy-R0X}6LbC7sf3K_$nZrU|F*ma@xG($= zW|8ehw1-h2S|DAjUN0;%<*{(h2*I3?I%|+okeVH+e&u9W9KzfXVZ~AJd}D!JGZvU( z#dulqRW==?72>c$Je!IZus~XE8-!Qhrr5>EXW@o;YjSn+V*3~EC)&E=PxxP`!Ij${ zk&je==()qfe2|(KYG3fY>HWH|)@l#D6dDS9V<*~{O)3gW@OP{!PSpv$GU<(_&1S90%f6D z!TEFA3Lz_ORe^i5LPbGpjv_Bb{1vQQp>r!t3}pPjzL`71uwwjuzad4x{{S*Lsk-Ue zY_&a`ta~Hbi?l(=ivIA%0HYuapyfH{Si@>z`e=wYh>Xf)b+THKqV`SChfUX6_xjrd zJ40_P3xpny$+Y!7eXOMuJs0uD)+k~`<6rlEz43mlHy~|OfW&CFpedAPWPc`jHNRaM zsQg3i_YsY$h<3*p^lOhV7*eKvsCKgRThqcLw_s>w8X5iWB70!gOog8=oCJ7daY!DfAKu z^iH_e`djNd-CEPC6U{XWD(E=K6n)D}n=RwJ{$n+2>GK$T6EwVA73aTvAqL|2sg zex0kCF_CY<95cN=U|2Dc{{C}zV_*ntBGdPeI{%DhaRhV?DpKs|3(?`9?jBbG8oC6{Yrw4j1%jZ;&wkrbjU-lVR8vJhH4 zf_&0PtA|j%tC$m9IhAs!+LSDu&ER2rLh$am6-ag|1yu&vlr< zild&R-rZ_W>qPqB95WL=U|2D+UY{QwY~p%44IqV^6%ATfXjb?SdiuLL(iULE<l zkqgqO_m}p(9qNj&?^&2WAATB92+<_-pe`n7i_A;C9ftK`U8mZIO&>PB=>;h~k1Ab} zgumqbf&O+@t0?su83kcmklovP2Qo~TD33*VPFJ)Ba&;^mR$z;);Jh(i=Y~0(SPNFM z;)rLBsoQ-GSs-6!4J#(biprI!`;RyvGdSxp7ewibfXXpY{tC_xxfq!fYqtKaW_40n z;dnPojYrnc-Hc4FRsN>fjnQ)H-mqqHGN2TS|TexpG?BIGgdh zd8te7C)zKyf6>YOi7gq_$+L9>>hj_d_wRQxgREm)kbOMxVvWa|6I+t#hsQ#*f`4%oLwmTKJ zWPNub--5bgZtN~pb@@q9#u{@hN;i&6`YG0BUczcZO^Oo23ab6j!n0!+VaM<+bJ3Q< zQYh3E^pko~_m18M9XCNeE4+GN_rA`BsGms-M;}FMoX$PsF~SPcU>(kWK0H6>_$?d@ z7F>jRB0x^F!nqx(fb3KM(h03nbvq2aI5mxN1J#VV<8dh9mJup#LRCaJ(U$DXg zD}vo%1uI!AR-n2!S#bfZnB_ZEyT}qdf*uF=)jFRW^c7s}+=ba$qyk6m{#R@0Gn=8z7t->9( zCleFxd2`&%^nlS76Ycjinfu|nqppAr0#@kt$C%AYR`h`tb7D7Ow0z>-3vU25!Mb5t zA>*<&{yX?7IO8l&TYwsC6E|axk-d4=h&z&cI(w=8=IGOj(~1xB%znJzLOf=>w4o3B zVkztk&R}EPlGoPZHRJ(_Ft2AfNT*jRpgNKL7uVjeXuw>GIrxlwU93V+B@EcUaMQ$aB`ykceTt=_(fa zqf{R4hpJeYeH$w!D^irCg;*nus>tY_t7f`WRWRE69##))j4o3-qB^5oED~ombowH+ zvdI4wsPU(WKvwMSd@6V%ygRr|DGSE}ITw-2nsvxNV?5aLZc;c1yiT8Hp%C|6k5^)f#TUeni5RRLCRt$LF4(n@ksVD5R?3cAdw~6%SY+us;_GK{Y|-rsdfn$aJT!kccVg+O=R0qaetv zKsJ`~R!sbNH&aI%R!qF-4`l9CRX52B+U>gjW+5vEJrBaW$&omc6@qwa+f(#xI?Y83 zu0Bi;#zP5ZjYMEi*khY&MRdhCW6vdK$7IBFpI93XGc+f7pRX#&3hlALY*NfQ?+^Ef zcZZhshy}t=-w_y6F=E8V`dM^j4TE{}cb+&D4>G z6_e@tV?pL))j6#2)x(z8tmqG4jLd?+;+HiaH(lBwtQbCSx*03>{0>*^ISwn7C8E`Z zrUli-lK8PuYeM)#0xbX+F162({mRia}Zaai^!*tcaLCmq8gZcK7w@FHtH>%YNX@fAsg15MrOf1&x78~R&W`~d! zhdpt%zS(5@-W)sKJz!WdnI1oxI0TPH9aca_!>W9&OI^r{0ndQvx1OIw=-*^3yrRqF z^%&cqhCjz@$!sodA=@e@;?RQX;`8vQk&A0(d{bM1718ICOE9mL`?yCu8TpLRj|xA9 zX2k-`CEcY+aXh>a^XV_CcUX}9Rp_vb6;u&(qNtB$dpGN?A#a|p5LT#pVp3~USRtwc zxfQ+-T z7{uC2w!gyW1eL{EJ7QXa=NM7Os_9S_M?7XG$Yg$XGj*(C#pHVaSC~WA>gRZg1S(*) zi?TpfZ{%CR>K9f(Sm*4_%!vg`NuNH3;{GQh~jlG59r)6tM!U z!pDw>m98KwP&vy%iCxWr$TbB2*Si zU4RjK8}xA5CHd+ih8%D&@;Cas&xd1R$3w{4>O$m{mI>pjj@{vGkrj@%;HTJIvO?gR|oo*M5S(fwq*kKq?Rkc`7(ZMNK34+v~9^DtW*rUQOQCTU_Co zb#8(*>33LBVPUlovI74}i7w2I zX|P$rh-p3Ito2sdvO4bRSdmJ{Ze2OqUN^^2e-9W|Ot#O{vB9Q%R!P8z|4`H{{q?~ zexp4%mHC~$_Is^IHJ#H1jqi<&ip;{E6YaC(KaIQ;=}vNvD>F9w*FF{8r+gL6QttyT zQh|@*n*f4c4Iw!XG* zwqzvfO*AClUhw}9z=azq=d?%bcLRR#(W2I)SY}}{T{$N#(-XJXyvO=wE zF08=c=&>$f?IU3Y=AP$RfxkU)B1~3rHFU57l{H)Z7sJi|HL>T(iuC2A$*!2}uWlxe zH>{X!uYZua3v6J87x*?CNeUM$P_vf0f{mj~vK7QO5dY$Sb&aRD;6DGIzUv#m(YP@n zT0-4|6l^7E5xqt8Lfw-OPKNlB_%5u?zBC(Y|LEH<{%`Jf@Y^5#_eIb#-oz{I3z3z1 z+J1k=LAW}aH?c{vqF3pnURDteF@8Z(u=ci66)M`}@m)Z^7Fp36uvr01gC2`sta}O@ z1exH}6-U8})q#tVrvkTiY)wCm_G;pQ$*wTE;u`FM$+F_B?5_fYO``;0v5EE7zXPo~5|BB6uA>?C%6E-VYxr6=+L`=oAiPc{F!|#U3iHFlGQYH#A z`QO}3oWbad$@luJ@p@ZVV0|1#3ua>pE$UFef|fpYMZNW_ka#R)4u_0VWK4n;k@zNz z(a*60>Mc>54&Vb>!3JfKqeTHPTF{_gq6QW%-AMy#INg=v|s}RxxIgTtW zSwD>{mi7jjn~hdGh`&O!VwMk4Q`EbmFLO|{0)%MrtNd`~t05~|@I1~LN{a>3bIMpC z*XR}1uwwG8h^JmwRk5&^FIWLus0<5X1#%U^3N~6MsDZ6=D&1Dddc}$xqkDqKL&Az2 z9molcGVaguBaa!v4f0`*qQnEB1-BRv`CX{hRx!FR4&xyX8w$5LbG^TlSy&eoD>%mO z72mN^koqf>?}6E7Yy(YuLO!fn=vAy3q^?k;5LS>Evl_A9n5Pd4r9ZqUSh?~{=J}rI zd%9zR!~cP4pQt1{f@LykGRKH7dF&0fAa@G1fV$v1AgcPdA|5;z-~@9>m|?LXPF7&OMp!2{ zDG>1>CoYCx4l={L+(%YigawjG|0Z)Lb29OI;`WZ$63@hqe{=G`yy-oIVZ~JR_sz}l z^1-($te~=hGUqtM+CnZ?AcpW@fOY{{A-NBXU7~gbs@CnnEDW0!$_h}#04o$Na`X^h zC}IdVV4X1gg0_o83wke_VlM}E|7`r}_*S!)&^4qk3@fIB z6(^H>P@T8Jhgo81WQ9aVxMm?(p{x*ECRop~QO_BdoGkbWz={Q!3!+(ZAtH7M)dWZZ zg6vVsLez#<9GV%n)L`O ziq!jUYhsII&%~#q_suz`rw0rxrlOy>reB6Hk2Z*=g|LESO|7$~{1s%yp!d@yF{LY{ zzAjm@IXgGC5fO(y!3*JUA_~HMN#TWJ1!!T@L#d0>%y2xOv_zPf!7Pp4h@6rYGDCet zR^U@y>h474(BrUgs0GLhu|vp;I^=7Uc}HEWcsV#X)(R_&tbo-8zXh{1dlf5I2Xub- zkheGRa`1B7SJ_PZnK%^%HAOlWPpwFq{LQKO^-b3~3@fIlr$568=WI*O3eF!>b_n!2 z*V#h$qMA)n;k(~n-;`H-Mo!6!6YWn0HU;(sKgS6F9BM+mnebKwHw9l-io>Qwfx>We zLu?UkkEk3j_&eUoRqxwU;IY`B=jT*$+hhxJBN(r3ApvYwNDgX?U#%C0xnEQjLrv6B zy}?U}uyT$Fbww+VC1ZMF#q=X5L*-%NuO33Zz1_iQ;wKaFjy3VyJ5D7R$4(`gziD-t z{LQKP^-a%N3@fIlpI=MtL2k4|3%SnnT6@@|tPnMWs)AOC&#^+5Va}M0(v;s}#a`60 zfWCl-k*r{&-&1Ri;i;ju#W!tbL9fP>m?tXAVwvsV%=c5sBl#3JVx?xX;&_;IIixxb zZ4u;HAmd7}EjZNVu;OB55BPxm?Zy=AnOm@36f5=y%Mb(E!MF)@$m+oS=(keuC!dWs zHm4B45Yn5ZG~`72uaZjT_LQ1R}j8U(qwjA zTOm*uYPGSxWV1hoaY|hQt)QxTQ(u4;&>LWd@>S)%aZef1yV+#at|MLher{%sS~)^Xr?Qvlv!PRX<0g zm~#~>$CwfSrf7KPDY;moGMRpAuR}&FglhtB&UVMa3XDm`3XFYP67XzNQ;-$(WvGaV zzNN}2R2qsRd9SD8Ep@kX*wJAy?bG-1v3uusw zm@1PC=PtC6GEu0Uiqt(^=dhXyrcDlE?BtvoYuf;xNg5hCX;1C##Mm8LXk=K_EZYEX zITLD=EGF|j?|VPpE7=B@N(#mw-@*ESC4JKS)ARoM-j6nRQMyAR*9dKaJ^rxz`|C2E z6;^yD#VF!wpB3++_6nrHs;$$g(I^;Y5mFb18TF@(bLeUk(gsHXb$-_OLN!mT{d((J zp&rQ3vq`Yx0*)|%Rrb^sX1s*z^PlhIO0cl5AS*6_6YR@-5$}&N3%(1I2C4!c2v(zm z6f?2isnXzKL^2H}R8=fx5_a$7b(Ar+`=mFD6knW}^o}Bn(BCr~E5Kq{hd}+#b7TeA zP-(6aeQfvW-a@1Wl?A)0ah5S%1Z-CTputgA^R1EXWEoYE(uoF{n*6&Pi5$ zdf;oRC*%DtcM4ifS-}5*w&|2;1)saWhfFi1Km?+%E%mzto8o7|3a%Q}K~}ucSLnGV zv0Ji&`P2dM=9c&$@L1r1ETFqh!e28MX2s#c`_BBjvXsVsQ`YN|tgdL>C$}Yu-&N>} zft~>nf>{vKViUSJkQMyhl`1Bzy?V{xFNCzg{_^jv@R-kY91;jUYVl_HdlVQbaWQqB1IT)>&js2$2L@2_nC_7%UaWij^)QTCf ztZ3XUx96Tf+=Ot$23uU91IVY!e;` zUME>$#)W-tDOOS}X9eD;kb>_{bVb(y)~72eG+HqeTgohFMa6y2`XB2~RasUvjurFX z>pipRAtj@;fLI0g+JWtyh38IdqV1`&sLh2u>%9oCe`mZPtauN<))LXA2Let&tBA7D z?5UEXo*ZG0Fl%C}I_b-BywUf^n8}@*9-Ky5Xtd(a3qR|-16D9PDf*{Mip|JwkQHzA zosEpEU$KYTo){vTAO*5K7v#F#kG-&#(nEr zcO=V-M$YxIG%N=7O7b7xJEMP_!3zDQ-GwZq>WWSA7klG~RcOSb&D)>oCo6gw)g&u8 zB(4cgBr*Eog$O@HaVRyyf2l!21D*tFaT)Ona)Mf8XTn4{gL-eJg+?pRxR;Ru<@3;M z;;U3u!4A9ZN70tL(8u?oF^Gjdvz?l3LN zmK5twQK*erRy2|oiVgS2|C@mh3>+d|Hn|FTrBI=QjCkB~O&kem!7{ka1Bb$tU7!C5r*h?7`;S8@M(+ZDH)RtdCRFhTd z)wl|@#Y?Ef^jWb_x@StflG%-&iCuAe9;nQMeh^j&FQ}oI1pzS;)t$>ID-12l*85ne zq_BCAMv~%-6I*kL41f|G;05;AVI||)=sBqO^HJ6x&<;t9Tb+OM*M*Q3sDVI~khKY{ zsxYJ&fZYlyKnunEd$3l6$6_vp;|(Z_aD+3U1=qR$21a72ruMK2)Tt&`}x$DeOL`mKE!E z1{z6=rR?o}7ljl;3wR;Ogd{$S($nXfORO)$S_<0M!k%Y(Oke)r{cuMDUV^d$UIX$d zJ{Ob~^aki1{B;A_PgZjYDNf5{r=y$GR2I}S!RipH!y0^d-&5(d)0P@Q9ThU2J}aOp z$O=R~QIooF8t=jPK{_wSo{$U(F0yMzW%U zH4ireR$vEE{x)BXy;^0(H(`mYEN<>mFNC{=J_(BK0xQr*53B$ed@^tkP#uVGqRwLN zw4e^bv5-?SkAbp?|Dn_L<>DSC9J+RmkxjB9?VN$~AuH5V(iI*rVdd!WxXQeS6{0H0 z4vBP9VX#sQOr)wnv|=`PBi5bQx-066itAZYtYcDGAEc3__~Jyxy__jz#4F;LwZfwBw}>!k=0kHJh80v6%8PpwgOAcjl01eL zB*qmSc{=)^k~`(IY7M7mLC$2}LyxPcpevH5w-@uK7_24hhq{oC6jfH->XdH1$n7vH zgRBVav4j;>4+Or*rM^?e?hC_7)H4vkh=);YSj?op!h1RhG zu}!i9gj8KIj%dYnj4RJs**%Fao0Uu>=h+_X%a{Snibl@*Qf4l*w{WzFe!a#wX>FV1 zKaJ?FI&Xjz?C-!lCgPiWxT=et`S-(`3-C8k3l(AoE2ESYphbukh6VROr7v?CIap>g z`g_=~Di%NG*NjqG&;wz8_n(miiKmkZdJm`&fl`nt1Tv${aNLUV2yb9jI^P#p#-XZ$ zbUoBaCiH*lz?`5ols)9L?RH{8^SCuyMyVm++SynW1&f9Zy=*}?eoP&lS zEtm@-D@=5gzx3QYgto^R;;*I0-BZ|yoKeWl==4y_ip%?wqAE7kDU174!rY_K!#-Q5 zd*f+c8*-uVlg@#xMmYb;ui5;O_YQoFPY>McX@520TRF`Xg_v?seu(Ar)PP_M6K?aCsi1x!f+9fq* zARs&t^zRW}VOYU?;uid;GU0nanwZP5SND{#!YOP?e*VJe*}2Ryr#&}mJMyl}=!IoP zBU#}TCfvQ~>2fR`C<}Taw>p1{%m8BnTrWY5&u&r+dr*Pl(hFHQ_wm0xD0MCSS@v4?P`WEM;@FHxBfoX6I+kTcBWK!!|FIXkqC5XgT$P1-A)64fP}`zT zVCH~2f!6q9A6JU+ati7yJG=9Cm%>JV>soaz%Zf(M zw6bDv{z$Q2S-c%RC)P!UAivsbjeBk)zWMBlm$J{0708m11gvV{nosT_Y$`qZI4TVi zgiHx|pu`9+*Pp|qdfKq78R!H)vF@9>jm^@>k6sA&Qbh+p%|$nXvlhoyf?3dSFx6P8{hpA`|Re^&N>>&oBMSND6o@pau8e`0EF|Rb7GSp}b)Zm3uLNHY0%oK~Svci9TV1QI& zhFGDi(1k60hyIXQ#W>c!>`oaf1iUy>WyetAuI-+;F73qXibk?x-YceWqsQg3^prPL z+<~qyv?JCw-s*f?YB%v)jPq451ok)^`x7FKKZd4YPuoph^gr5CcMe{G&fs2u^jUOJ zS*&%iucr*G0psHsyJSuVEJA*k{swnT{Q8M+gB5?qbI>md>|S(*VTD$0gAZH*$9u8Q zmqufPN1&p@XT@V(-T8nK%g+rJCtSNXVI#kFtvZ%vMPp}r5t+jFTp7{-$-y}nRwb;U zvM><~<{@io@mN=z_v?d6)Lt+{2`>bh3wj@1S$^dJdu*9Un647_#Hf^BDo zvavY2+JY72imcwiDs`Nq9)h_P9jNO94R#qK$fYVLLTsqv1g@txVNWjhqUi^V7_pd% za<@c!A+PnW#0q`J1=MGe6*E;$$q)=6HK6<0`-l~F?YPP~F{@xDk@<uv_Oq&rMi1yK3`tl@;WJ z{$IuhR)_|JeuWNY1#jh1Hlr5?GzM6qtK!?dXV80`&%tLN7wq`#= z_p2EaiCK%PID)vZ?Vs29Z(M7RXj#$tdG?BP8D<8z!EfhEvKjdJ)CQV=#3i2=KIg0 z(+>Od@HLJLDKsW=tID(U@IAQ3oYfV)O{>g8tk7Bu#y_K<#5!_86f^93==r-@yMIvQ zzi+KNqGd(nXZlyDJkp9R>hawvT9^MzE!L=Bh!TUp^va8K(VIB0HppxU_6?9#Tg+yX zH1eMoFXLWZsHX+jc5sj+>{Wxm$i^_g!Kmt$A3WX#R)7N36oIZVM56~1u;M&ooK;Rp z+=7qLoa)KQmi-mvi|=NaQ-as9j`R0+J;*wZR@it&<4M6Ima!i=t2a3g7cTXY7HaRb ztS~F6EtD4A18~-7#ljw}UFR-6v3_*SqFv*6UbTsD_w_tj>9SNv_&fiY+j%4sgRka% z^gGzIePvb%DX4%z6td!?uPZ_xNR<>Cs~C>#h;vtCuP}$zw%yenX}}HCcH2Ec)-5S4 zE1JWKqWTYw_?&BpKc0(#gn9*O!&C=Ag7 zP$r-SHOM_^5vD`^d8GyU!x$%-K`-RW4=#NFCWxSlLjTV2xo{l^)d$zpYjp)reW@&VS^9xgBgx3QBPhd?3^Czc!lL_>R5X%)>gji9}gHT8LH3FE_0*L<>efMOlchpbFqRRCKUIUn>(MATg*c z>PB!)wK7{{AT@vWQFs=r6J7gpD)N)Q?`EhKX!%qPy28zniR2m0EoN9T23D}jg5>DPm$BN^=1H6LJ!|DrEi0Ne+w&fye1;Y!pb`> zSzR>@MWL+VZOch^u3^m~TGXh8I#y7LaOi!)=gp(26@(Q~Ipc^} z{J4+DHmX9E1$Bk8Nm+3Y(Fpn=d~LxHxd#A`TFLN~1Ut>>|hW101q zVOdc>BaOVwK3RJU9z1xC(%^)vI|9%St2)2xhtMOzYE+N`+>kY%YTsl9fBSWiFNOpX z)9~qFC~>b-K5kZG6;ur5!TIQa*O5Z2D9p3rnZW|GqLviG3HmINliM%#QBxGL4!tyJ zNWs&xJ83hdmKBfC3^bAr^~aj@xJKP41w8m})MnifPJ~#Y)DU9)8p=X#)u;fTGREPv zLWn_a5{w|`F^g;U5^v=>b6KzDWe&zFm@(yRI3Il#byi_jQOk-MJo9kmWJpz1DM3=; zwigq+0-gOdC#$T$eju5r?Oy1Ql$}^zQE%UkyN+hteaC?cCkiZ_}51G|P(S&i_)@$trH?0luznP= z2wyRf2%)$HNkLYeWV8a;*A9>(_K)dn*$>=n$o42LDsBbuwuC6|vGj-Nulbg{JGGQ; z?hMLtA|M4k28m;+u8?2+ zs@*F!Ox)ANBx^4-in*LFWDaYs7`MbwQq<6b450^t97r82m?s@|u4O;WkO`k*1&HxO z`0hxGw?K*y+z;Jgbjg&7SFY)GV@pwTG-}u&!zd#JIf=>VE|rmNlJ- zID!=t!I0=g4J*{jYq{{Cm(DX9aUWLHlVUhB9j&-WiX?>&u6v=n=*X4P>*r-hSwUWW znE7h*eW%&8Y>(gk8L+Hq_8iZ93B)NDGhe|P%uy%2Vtc6gt<*GBzNuvhe*3kex>gDB zH{SOZ57xfq7}oDPhu6sY!4Sc6J0@CaQ(e1i@8iH^(F|R zoOsjuYVuGjm9lY*W`EC`e|*b|=FjmLCp_%CwIx|b{`zw_Z>X@NVX4Ppy)m z`iBo;SRu6zRb64MH>5MdTA=>?y%>V~MoxuEQFH7d2XYFe*n%G2+0)t8qaOzqW=Iv_6g!YPOE0AMYYBbRfd%pVP~}l^e^h+O)C$ z2R!wU7g87gku|Q#y3I>{+}&K&#F(%m?1dOk=&4~ua9Vce?JBZ$?}b=aw2~EHoB%DV z*$`C~+pr(7VZ~B5kslk}ow^ZIUYwIXM9GO73K&-0`;=MG+Q+SYDRdN{7Qy|bzWZom z0`+7G>;W_dJz>UbUI=*+P=e<^%i73hYrkd9c{a<6=FIErN3FQsc~6x5;leiA7r-Y4 zYB|fW$tiEj>n?2Bue@LfyE;;YD4+g#nIn{x|mzBPC1`JRVS6^D^4At|_egf{7*CgVoz zWaQQDU_fBub=G|kKCE=l;5a-Ny2q(-d=s~zXGQct;!tWE;;!Zul5$~ItnP&bv^ZRt zLl>Op%%weQYiGdfiq_8e)|^H+>q#+Gd>`=ul7g(@05@d)$k86uT5LxjJB}Lr4%@mp ze=21E=DHP6t4C5w7*?DDC7wrZ);qmR=oY#c%4cttY4AeaDKF3!_wqta>1C|xdn%R|(%h)D=jq6EKLT8xv+`N#&utIrp9T5)nf=C@rOe2Rn3}zS_&^wV{ znDUqBbgW2*RS3dkDTT<2;qA|FKN)$f>-N68SrS2gBvx-5WkGKutSsnD)eo&ALH$*( z;+E0TVx@Vo*R97Hu&ik1r^bDDhNLT{u|7TR#_k!aDX1&P{8%Sce{p|m)M%OuIYsiF7v|Kf;eBhSQlC+D!njWpovGORGl;uUo3 zY0nvDF$sUEg!{0gXh*&y$NzV;bD0Tu!YyMgWt@V|ezfx2)$Hf8tZ4QOhmYT$OCam9 zOjQ(7e^4~4!q9@5No55e#dC~$jujL6QD@8kqlp{X3G8{;`19NU*(XPh^;h{}yo)e7 z=xGc`_&BZw`6TvCVs|Q$CzZ$v!>S`i#$Gv;XrxCOyPWH587s0&#i4Y?-I`moEX&s0nwb3Vv?kg<`=R(^xN%;&PKXyxp)x}u}{E3M36ckfZy{D`xZ z9V0LNs71hv3ar_XA~>BwUq0F?^oU2T;z4n+Kkle=D79t(mi@Q;F7>_JtK$;RU+U%M zOMQn@6K*Q?9O^juTo}G$;d%Dn){dFVi*8?67*S>z;>Rprq3x2LHC_=-FUTAnC=e%0eG+ zUMo+1{{xYVW9c^$$Gn=gS<%*ihrWzww5<3tW_=9)e%bBFIRyt%hZN$9;a4&5TX7;! zMKC$|afkuN;-?1Nb5G~5p9r&pdc{zIr^<>WV&CR*Je`8BXwM;6;Y_$e=iDXi);#Jg zX83GISLkV&6g>Y5{(E3hcfLKx#~e)A-Acos-=liovf|77WGAu0g1_ib=Z&VQeI=7# zF`Y`G9=xc%vBTC5dk1Yax}n!kLMPCFF;R|5VTG}BdMUxUgw;<=gCl9XV`=SY_h??Stax}o*9o@+`O#vU8PmYh^&FH5JP`F$$cHJ! z9u_5|VQ8T>5^CdQg$_O%x*|J@7;6V2m*r}|s;xQpra6Lj^Nge#R*XSI+-s0zi<$r1 zAkn>t_q)=HS7uq!idlY;=U~i}JBe_`R{fYnP32PNy0AitfjH#}vLc*yRU1D7TQ{WO zsn7z|;}yhHmoi;Rv##qol_aa4m?qm148vp4*-l=`e`j`RBiih=c zYDX2M*-^Nj6^vFS^7mr}xiExmMyaYQ3^VA%Fm8HXDk^jakb#vhf1{3OT?{M6mg$NT zu5r-oN^311*7>uqtI;!HS<&bjZPf89?p%icfGUAnS};1PnG&Nas4FDjx{M2k54EQ} z9*yU3nw%^feyThC)d&46V^qinm;$4RBiuGU6dIo9^vSkcBO z%&cvsQ_G5n^V9ZV$KftiVNy^LlwGdkTtrPa>n}}(Hg8$mV5Qdkbv@k z4C)GY$JW0pZmObyuga@B~U<_VOTKbbzn#3U>(N1*Pdgf!Q8%_6{N%%dSI}so3-34z3WBv zp13YG;~qNI;GU@N99!|8D?Qp?_o&Z+WyQn)3A6WRdyX}p%#^Y}1hqz2GT2WT8B8z$ z)fK1+4-d_ZP9omGF5H#O(}+v1{(ckiYRc}S)Ln2Ls*PZ-K35y@7R!qD^QljITz5{T zL9MZv3A~8*9IHFIk}Na^$E3%_Qg#s@ha){I?)P33d1OJB`6l;!l)Zgp%z$OZqwqOD zjdkLO5$EJeG>&$75UN9*g4G9)!YBMNya&sQhv8HB+g}3}2ZvL54#Z&3%s_Sg?MJsq zurpxy2eID)I|FtG);0rHSFCNG?7i#^Z1fqhtk~$^0Q>&!46JPiEGyPFPxfAR1~&Q( zSXONGZ-9OOb_Uiq1C|wQn($fn*qy;wat^gmz{x)J_D8& f8~qz#-@l!KwatKK#oFe{-pkIwMxTMd!@vIre_~JM literal 0 HcmV?d00001 From bdcb26edaef8e839a02f159bf5f194f859a1d819 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 20 Mar 2016 14:58:35 -0400 Subject: [PATCH 1106/6300] mascot bitmap added to resources --- Win32/Anke.jpg | Bin 57221 -> 0 bytes Win32/Resource.rc | 5 ++++- Win32/anke.ico | Bin 0 -> 65592 bytes Win32/ictoopie.ico | Bin 65592 -> 180626 bytes Win32/resource.h | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) delete mode 100644 Win32/Anke.jpg create mode 100644 Win32/anke.ico diff --git a/Win32/Anke.jpg b/Win32/Anke.jpg deleted file mode 100644 index 8f1079fb25bbd50fc418923812096f5572b01f13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57221 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 I0sJ-fKNDy7t^fc4 diff --git a/Win32/Resource.rc b/Win32/Resource.rc index e10ec496..90c12efb 100644 --- a/Win32/Resource.rc +++ b/Win32/Resource.rc @@ -52,7 +52,10 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -MAINICON ICON "ictoopie.ico" +MAINICON ICON "Anke.ico" + +MASCOT BITMAP "Anke_700px.bmp" + #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/Win32/anke.ico b/Win32/anke.ico new file mode 100644 index 0000000000000000000000000000000000000000..d4b422081269b0a939faf2e49dc3632770728b5f GIT binary patch literal 65592 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ℜ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 Pl$ZGBw7hbPAJP8@ziKnK literal 0 HcmV?d00001 diff --git a/Win32/ictoopie.ico b/Win32/ictoopie.ico index d4b422081269b0a939faf2e49dc3632770728b5f..077479a2ab8c7304f6dca1ac1770176394134cc6 100644 GIT binary patch literal 180626 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 iJmHh&v-Y;wb literal 65592 zcmdsg2Y6LSl5Xw3P2arTx3g(>XLh1JPJl5vXOSg@5JG?mA`3(Yk&MYgwye>&=ue+m|u<-zT z`}2p!Y}k8|>elK=C|@WeWiRwXRF?YL^@WVm7i^$m{ig=V3h}=Bmi-F^@uKg z_=t{Q`-)y)ahNJsNv4{$yU{BJ8_|FNXC?Zd|El6Qzy4JfqZ_~ak4n^`+XTAy*$;mF zm##ZZH7c|Si9O0M_`mXeWJnA|MhS5#0rf|Rpd!Me0{{eDk6 zFk;|zS~6!%fXuRkKhk6ee@T;5Z9lvCqx|KIG^94s$$nYaC>BeQrN|XDrc8aRTD~={ zJN#*2+vj$F9N6FSVK4Yy`|KfI`_$P;oQt==rdCO#vS5$!4g1bhbd3)FG6DOTN-d~n znWh1Ly8YKI5lcNgkMV^6wlzDcbGu=5;po`_9Wd?xV;%=!cszi7g0+9a5%Iz?NSBk? zKMnp3tG1y^#Tps8Hmcr+O6IQhbCu{i_e35up>`}%VjDKk!qwM-QxuFAt z`c9%nGt&d`hrb>FuK|Cjdg*!&=|A1bPWPu;2O?{9rQSnV`SIVE$ryd$o2$FE?@<2d z-~WoL*X&GF7i^`mGuPAp3-9}N0e!%pV^^qpnP!wwy)Ca%>IY!&RL)x5|Ns5p6sA2} zk7U76*A;f@0~*xr?zfAd9XBWO&y9Xxk0anO^{}a3FYT_tbAW^zfqiSam+Mk=XDKUuTR$VH_;{!tML?%pYpgZ+;a)fB0<;s$8`b4H&+NE`9Wk;R_u| zU$uv-mTE$g<(dWHuXR)G&1xmlKmJ`Y+PC9aNc=?yHZ0qkMP6Diu)E<8`Py+qxqnRj z-O4+E=4vyqMw`+mx^x^Bf2j`Dk4U8WCTV``vF-;p!LaobhW1~pRyS(fc@}lvevg{% zxlesJzD-36C-9nJIqf-hldkfbuu8=?RR8ffuzlGsQ%1pD)21<9>6HR;v~Tw*Pq^6a zL%TJOZusjsUhTP!Kl*}j`hbOV)-m>p0l4eFHL6s7DqT2^qU#MX_+#8Z`R>zW5B)g* z-hJL9Do=4Ew^QuS`xL+HJ~d#|GHnh0@}J9^_2H4X?ojca;1WfY3)}~ia$&w- zx_=bR`|7Fi*K+!~;qO%kHm=@H)p=}{K40XjX^rCb zDBlY)wC&Wl27m1FUFJO=Fa3L{I49rx+TdSp%pO1X(1C=#U(<|@rzt++HCnmx7)6w2 z{d+t%>9WC&J>SSuu~h2idbDEGQ8O+zX*Q5P_t^f?Zgsz}?b4>x7j6h>|KcAm&8znB z75~G#Pf+T>nMSs@T*ZwwA@=b%pYZMXAHBw7Kew?^*oF$n{9)9lO#0n#s?oDA)TgM| z&--U7_v4Q~V917x)N9}z+IQw%s#&>hP#vwqI#9iQ zbF+^OzGX@_r^2ig;oxM;25w1j%TfHpp`q|EKjQ5?*8wU2umLlu4$t$d6sboApNpbe zJg!0qurKudpCakyd~vk(^f!dFy|+Go`nHe00XUV2?oa>zujT3ag1qOn{CWWX{nF1; zn+~IC>4u|(v%?z2>IKaEYL|>PZOZ5Ir18UQ>FRv9^Q-xyjRD{|N6Izl+tIeIrBmL zzsh_1q55_&;lRIS%m6CYW*S8=y%vByWRWy+3+qNX`fr{e{wJ5AD(j~7OQ40Gj|#wF zhoK(N|FIUesGZ~)e~quk8Q8T>92gd#pBw&Fio{SE-s=$=0(-$9^Yf9DchWAN_n+c* zUrzM>(2uz*_EMc;8?xXI>|shr_cLPyKck1o)Lh5-1aZ1#;nuy+)EA2y%5M> z%AU@;G7Nn{^eh(Yt{t@|EatE_=m#PYxwBt8%_USWoQOP4BJc*!?&6>0P+LYg1e^Z z4=@(I^lWVtE@#6|{Xgncitn{0#dx0s+WK+);5pj7ZeOlqpI7}q@)6UM_wrz`$NgiO zm!Hh}a0j4d{%ERLsID;;m;#TqX}itd5Bh(v_y&uIwg1S8Ys|g^_J48r54hs4p!D?` zcWp=bZ+XoKzt(l!`+wk%vtaaLHm-T)Mj1us!Dw#J|Lwfz%sxmdJ?EPC0Xbp*SAysN zxIZKP0BCg{Z?Qg0owSoq+|0SLANZ$^T}9=~CsB>~A@pLthI}Wma!Bk&4-gl2JytpO z0XVzBowdBeTj%2ydtj5-_#@AS3y#r?&qh&Uo+o3yq_Ho>?m{m{(F-rcQu#{Bw0i%? zIgbCp@#!>p{$5&f?lYRW@+98_8E^KP&<6<4!Dtlk_t^N`I)Hs4FL#19ZaHQ5ia)T& z-cN3>|Lyh<_p0;@<~|k5iMEbC^akz5P_2doX!wdZY2KT+sZy=(6x(Ph;jYKkd%nK^ zJfGRWHgXA7snLb@pTC{$@nF>S^;9LY2lbz~o8}z-fEHi)jM9&OM3J>pOkd!J{qO(R zw-#s+5qLM$?QF1I!Dt#cHX~2_0I%5Vwx5^yuUvVK+P9lz+8N}IezHN*SjPNAg9H4G zf1^P(Zr%~vfB7NZ_|k{};*1m2X~<%#7Tt$lUvt6rEFjcA!N|$0sb*|nN*eSAC3K!l zZ*ZP4OOM>9DwR9YZ~i;Nu^+&GG34vi_MOg#aVH3O7;>szd*PE^{Bvs$*lz#PQN8`x z&)EMCMI{WT1*bm_z#h6#Evgso}>D#as8jr@9SaHF?h5f0Gh>&7`?U-={aue@2^5enusV zHg{YH*!WAk7k$w47qLf{tWU-ANC(2jUiyC7_sXq3V66ivBii@$eK$pFH#0QLw= zPF+g%5{4O!FFoG#pSS7|bsfFh#Eoe()T{{)^Le0d>t}9$&F8j5%zn{vz8j3N zF!&9c#^b~hocHpcKiX?z`_VM>;Jc6UA7=1B!gqjWYyj4$-~Eo_5>m~2@z{0WS#f~^ z4XF39MOp5Q;hUeNJ~RFKqc3XJcP=eBbB_{QjiNd8kI}Nt?@-FX6;zz{p>&zH^voZl zD52>H*D*lw7W_poa88W1E!OlJvvBRe{XX98Je0x~fLof0ge~ta1h5N{<-2?j> z``)2Kg`4ty&yNUgAGpq2xt~%-Ec0U()5M4W0iGLVKJFh7Z}!pyG;HE_gFns;&hcHL z7hjB}UPH6ufAJ3A^J$Ul=ZU-ry|JHK^qE6*Horw}lBWlJZ-9G;V|nemc+=Zdy;72+ z4!GejdQg?`2~^>`?ssn9c`7~7dIRj|%}5Ume?9NdtM)JWk4QgG`SLf>(#z7oNFG zQ&*p-n7Bc->)coVw%w`cWLk3awjV3OAK!kR=CK~w!^E>*>^SqC!5?A0Qq!sb9RAkk z)j72H?5%(?;2huoY~FqhExGu)pYQxrA5lV=DKvQcPD<)F+wgJv&UEj^AEBy!*_2$um{C=Bym+*QQ5-rgnO^oF5IBoH$Dto7w)}(m(IO;iQ2RnL_>$H^t8{T z#{#Fe4?W1uIiTPl*=Qhj8@tvobKnj$|HMbsy5}rv)p43R@0I!6ift!p=;XDu?2>P+ zM%>uu!%ZBl4W#|wVEh|3Pvg5{U;EpC1MbH$&q(SugEk$=48Y&q0h+Ugx{dM8DWn|n z$rGs0*bPDPhfd%wD*Bk!`#z@njfYW##zQHxPH$?}Y_MbBp!ERuSiVzS@@4+EiTA*3 z@Ht3>h*m}q?C%FYxbXoU+keJ{ojh`ZrcGE%!w1cx#O5Qig}YnZmwo`@T32$)F8CKJ z+Kgr&zU9aNjq{%x`=mAJD3QPKHL_*7%00jMgaNer!d-uS;8cqD?ncerPkQ{voUTdB z(X@^4ihu|13&gh`LsNELr8X%uXzP(HS@2JrxtY3;Ul+i;A>-e3_$o^3KF78FJM{@t z?)j_E8~p7#fQ`Sn6ZxJ1zALR!x{3MDq-@FN)S~HYl-zch*&nM@yA#!_+MYVLOC@|K zGi*pY&7E_Q_Vd1&7i@s17<;}im}{CF_zU)+6>4@1kayb3V-(qNAeAc9hDJ@_8-V}p zrMswXg?46K0Pb^-eL&5-O{b(Db4>fk{80M-;gdGf@X5RUn5{qh8MW#)n;N$tPaS*D zx4|!O-#6V~Q&P%wnydPXRw=V-@%CH(d711DKnKzo|5Uy!AefAN{U8-9(Siny z-bjlx-tm1W%sP{DL>ComTtS{=OsM7(A9fBq_V}I+-uhO zhGQ?wIuOBUBlsS&0^gB@45d$zc_#c|`u1Mnr~}!-AG(m!I-v2FaaQQ~b!X`p|5$>S zYpx7p_LEa5Y@m@dwo>i*0W@sckpO*c(q$@@D%~a^AI&3_ef{Cj zs732>d_MT(n|8q+-x7*F6h&N|iE6BIPfosx6MwiNU))D=8V{aU2XbmW5M34Ag-%%O z!++tX>!wfd!SmTJgO<_^-j|a&5p{Y|jp)9#c!qu0{Xsae|zRJmFg zii~-c_VDWtwMEc(iDEV^M??{8`0ltl|Rw^Mt2XI!3G=e{96z?&Ld~;FH3J;1sE^Eiy*INyjU8bdEW zQ=1Ar7ftz|jWqtaOBBa*ewcX1*-a1d4SH$bKNP$n$AZsAQboQaBYvPbYZMu~=|Fb- z0_Z_b;@^|^{o>k=ruuBA?z%=5`K&LxDYrG=8_-x7`}r@Z0k3r+Q^?t1fAk);>^hwa z^ID?QfW@@v*d6LIVI8H6T}Mq?jWux)Z|v+t)U4eE8n^0n7QS^DQyc$T2X0b{5{Z1a z_?~~QfIG){S7zIho7D5wdGs&8D9zt_7oh=Uv_jLC*}iEE?_!s z?{ylx;Vezqe2E%$oIu4(x2E#dIve-MH79ArsuP4UUv!~%Y=2s`I$rRga zB+Wbip;-?szTn%x!Jb0Og!L57?SIM6_YCd>r);CS2cN7p!4G@=pf>(XczWxas!jxO_F_drYV1J!Vkzo-^pz{EdDE){hn(FQL<9n#=bCkhaUH zbQ2HfcM;LOs5+nN;?CGV{-2`MjMs74&reL5>Epw^kWcG=^BnPCd*v${Hf{xF9J-tZ zd(i<<=?~;>siC7*P^nTaC?Rn;%}GCJ`h+E0Z&Ci|pY+*&{7d-SF!}+U3Bq3+H~is` zyRvfsRHuPl@vS`kDp+HJn+|yG19A%cQ23({z&cR$0d&HaOO(3&C=FZ6@3`?@4eSl; zaM;JJKSQZJf5*7dxYHzR$$R%>)}JNxA*m~lne^Zx2E4Gxi?q7^3 zTC4-Lt$T8~V}ST|AM1ed0fm1s9mtDzulpdB#l{fsVCO*y2d)?^oYIOPFmF3xLC`o7 z7c}be_z&#o9C(M?bs5j+O}@83G|r*y=6sUp2htymoV?z=If!`zFc&;D{^AE6sH(mz zz&uefv+;);_l~enDCvdbn>nNtx2D2R;{ZGQfMEDP?R?M94{j-o#sT)%=I%0c0Ic_C z9SR(;5#Ddsc1jw&I3%{f)Ty7a^Mf1nPtf^%|Ep`C*>wE+JjSF7JQz!hI&oSAgnPbZOwd=*TpYr$+^!d`PJ8{0pBfZ)^a#?NtsFqFMPoG zf6Mc_2U&Cg`^x?Kt*nwszS!0ypJ2{GA$4>~8qOAAN>SukA=HZmFlpUsEUibF=LuZ=749O!nT3;Os`RCy+F7 zQ2@4fT+Hp!FQ9*4p7~`Kym97_HXYD!vtKYb;%DA_kR{y}#vX0`5T8NdZGv@t51?Ly zL9}AWdyZ!Zu^eWP|AG;yV2*tr+!+pqKh6T}cXEZ_PZ@vcK#y*7_%2zf0 zwQtmi$`*>Js$~+XI-f!Gd@wu~xuZKbQ`*($-R0K4&Xjta(sOgW)Y+uf=?K9lA zF8B%^F?o$&mm0SiX82*AkGE&-K7RF%cc^pgRKguU^v}&=Q>YQ&Ye2t0Y}^*A7SSPK z4_9LayR?7U@%?LoKSu`OZ{IaX9<_PSh`2T{xCM8iB6pkWF(9|cemn1-yiYrC?jeec zeT~mL8&jvwV`(6-_0m?pNh4ODF!%kj&nq|!#hDQ9jl99%4NO~if|l>NVCLur`K~YO zEBHMv1+PWBGHLeOV}8CD`A+G6K10^yJM0+ack@|ON|y=zj&T!nmjin?iIK?^SG6tQ z*@z*$b6SnpdRX__m?5u1X+5xIFK&&0UbTJ6 zTPW&TmDj;U15xq=}TMRYtG~%tM zCHz(o_&Ckuv#ST-2Qy^MQlnG;w$E7G`1{?szc7B2IkU0fw!|4d&h()NxZ9yILq38( z{BZ{eem4HL8*dZaX9)~sB zfAg|28B8}He2dRu-lm-=Zv?b;@X9XMUeaOD_f7t;z>l?y9$;*kxBLt>ZP3s0eN)t} zMrq#9=RF_M0hC>z<;eK2-x;v6hZ}EC;$DdG(G)!4cEVl!ehT>G4s1(a)9U(lZ`z0I z#`L6V3%7a3^rwQqj0d~UKcGtG+64T*g{~v^a^*}1Wyc%xf*;la@T*ha`CDV*gE0p0 zQAs*YkzR0iqdzA8r~}GaoySY5kI;S{QmJliFTy>2FKsg~;-LMTxt~kBcj^~#A2dFu z7h$ZHx(US_Vc72jW{`u%PW*}&YGmG+l{8MYHos3U_eC|2pECX^C+dPSI@PI5n;|AF zFL3pm2gZN=#uV-lI$`}fV?AihrhqWa3sWa-rMgw0yceSDi+le#2Xex{L;G>Yzigqv z-$>HwVaIRN*zah1!5)3WkJ0``M^Q#M{Na!L7HD%`@yUyL*z3ifkKmud_X>BM^?h@+ za@YHm%HN;i`=x~&uKD9^Jn}g;O&sC>JsBH6ycL7`id=+t?=;rIRwvC16Bu9x7C?>o`oLf*KqpIexQ%NP6pIO9P- zfbWbJZMtsyef0l=w@pv-8%Cu{G^hCLfxmMibx-8^Z!wmM-WzBZ<@?SQ>xqv1PDON( z-z02|->FgeO}yd{jHM6AOZ;))4LXi;iVTF-vzd4^q8aS-%UAdgHD)-o27OAU?t;2k_Z=@I$J? z`+fSi@+6&5>={bhaPiNpy+4!-_nGB)zMSxns?^5duiFsX8u|h3>w0Og*!z9j{a-Kn zpf5m~PT%~V&ceL+h|V$I7x*j~20Wx3CvJR7jT-c!R*!#IPH+>7_u^hrYq|R68yYzP ze|+oKlz)>T&U$-D(t_&ypKg6O;z8zOr2Xeb+eco=7vDT;J=JxP@}*7A`a7@a2eur( z$#?Ue?`yyx{09twgU;RN@Bi6c;&H>wA3~h-dBIx8!CB%-K3j_5F}-Nv#{6bX0u|vq z-#xnf=4>Kgod)*QF-!cvD`(dm?nrLqdLIaUR_^+c@Y@I7d(Y&#zVG)4oYKl!5Yjl| zFa51hi39BO3V+doQ239UwkOM2ihgy$>Qj{5Z4xcZcr!~sfOdwu|1G^j>kfTt`eCfq z&~`9yx3LJ<4a|{de)@ju(R&J2tkBxr^TgYrF!DAa%5d=*pBLV>?v!x(j$XY_WmsRF z@Pi-rd&oCc z{`W&Mhu8JT8xds+H89@^NIUJHww{`{7-7c0y3suY;_I|Fwvt}suH(QTM(`Imc#7WS z21P#j-6UN;T@Qqhne_yJ=)ss-2dLlhH3a-Gf9(95uXr;6eT3)@DBS2fu$KVXy_ho%miKzD*Fh32n=Fl@Jc|);pimJB&N>68sw{4CQm6h6YEy zh7%d!?Rfn5jmRgC&yhz?T5s@=sT&-BltI#oT`<@F2$TE-e{oAaG|fpp0AH;CP#*lY zMrl6llDt6?K5B+T|Ic^=f2Y3u%)6hPc^K|s$^0Am<2Qnz27BQ4?w1egv(LVwk3ag9 z`gR^q*NubPAQ`?}MsVO{M{9i)b*9+Y#Rhdt1hKI_(AzxCO7=pmsU! z{GIT}8t^KQ@mD_ihNjKSpsLl9&AYw7{#6z8emCCdTe0<|X=mtbVMJH5OM(A)51-)g z*MlE;EwXlh0RDgaeJyI&W++{G>n8o>{x|gC+Xu9B>rwg>|8@Z07KW}o|7sML|F zS4n341K*L=GHA%U@e;q03_0~1w2;o;`i|o3_ab}~uH$LDv~!KW$XUk=X3q=kp-;#M z-@!^*b(s)0dd9whaUA`?+3R1?vXwjO&wq-dQiT$zQB+F6{0sQwE^l_Rc1nleoOqz{ z&&<3|pWOMB?%ldagSt-%z`tho4)p!^5Bzxr!0jcT*5S1lgcPfA>&g0;w0+!7v&B7SbPb>S)V48b4czLZs$4dv*o ze(xLDkDt!(zQ*>XfBvVEG^Fbk+Qx4;?^}Gxw10WGAHU^XFn=7a-E%%ibO3Gt7LWZ8 zANT#h^En=uC-k3Bdlv4c4*b25ya|dj4;?(qkNpon{NT5b8@q@)^0$R2_&1Eq+=S~h zeZdia=RC%>Ekho-Cj!1j^2PY~g|(b)JK~Bw#cs!Ob0faslp7S->pjM)Q+ArVNqKd- zP?o`?Hu~rCc+WQ}X*jJ;-^u%Dz3I%Ji*!EYJniClB=P%@h_B5w{!MqMwj8b>@V|Zk z5j|AxANjOy(4PwOyOMwWU9EuM4XDoVbba^T_kP@=FW29GkMA&zHSZDQcZ{IRSdVt< zG24_`*W1aC{36x4kW(D-LtMyHsQ8DXh@TsnYo5R#-x`6!e=_gyNjXtwT}Jp1pO8Vv zd9H^yKyTmYy}WZ*>A{01ZR+-=+tjT^n&E-?+KijNE(h`F_OI|CG2wNS7uvn_J3>*0 zh)QiKedTsKbnrCIp0>*T#(c42O{rCfiTpm{C~Dk%BsESPLxo>yWcUO-(}7HI4^Y~O zEhjBA*hL1R;^}lqqjAbDyNy5cLEdr>q{|HdvD3FvCa>q;;r{pA2fjJCjHiznk6UMM zQ5AkSLhAg|^D+EwNk2*(K9^eYZ|0r<;J%{{ggY<9+Hcx|bec40sox+^%sC}*Cu)8x zhUa_;!@J@C`p;#kazq!3sNR((&p%96%lpcXZw53Tw%xwhV~1;ZQNA~e{Ip*%yOcY( zFxPzD@W=P-qTfpsz&H+Vmo@9*Q?uu$W{@we~N%*8ut_LB8}?4bv}UYlU@)$LwnX;ZvM zf%l_dOEUwK*|ℜ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 Pl$ZGBw7hbPAJP8@ziKnK diff --git a/Win32/resource.h b/Win32/resource.h index a8309c8b..f37fce46 100644 --- a/Win32/resource.h +++ b/Win32/resource.h @@ -3,6 +3,7 @@ // Used by Resource.rc // #define MAINICON 101 +#define MASCOT 201 // Next default values for new objects // From 0c6befe8a5f3555f6a90e4f34f8c73735acb9a34 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 20 Mar 2016 16:00:29 -0400 Subject: [PATCH 1107/6300] fixed build --- Win32/Resource.rc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Win32/Resource.rc b/Win32/Resource.rc index 90c12efb..e6750ef3 100644 --- a/Win32/Resource.rc +++ b/Win32/Resource.rc @@ -52,7 +52,7 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -MAINICON ICON "Anke.ico" +MAINICON ICON "ictoopie.ico" MASCOT BITMAP "Anke_700px.bmp" From aeed2dbc3e87c1f69e7f5569b53e08e9f9d8303d Mon Sep 17 00:00:00 2001 From: 0niichan Date: Mon, 21 Mar 2016 03:36:17 +0700 Subject: [PATCH 1108/6300] Fixed anke.ico --- Win32/anke.ico | Bin 0 -> 121681 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Win32/anke.ico diff --git a/Win32/anke.ico b/Win32/anke.ico new file mode 100644 index 0000000000000000000000000000000000000000..509177bb378cd1e711f85ea0b284e7d49c0b179d GIT binary patch literal 121681 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
%?MsGrQ^B^7%MdM{hd5k-r- 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

ZAV6DTygQAu&!6< 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 S`XKTJ#gBVU9sIxk@&5rN32ux4 literal 0 HcmV?d00001 From acfaa0041ecaca89aeb435a308b7c0abd33e4bce Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 20 Mar 2016 17:01:20 -0400 Subject: [PATCH 1109/6300] fixed anke.ico --- Win32/anke.ico | Bin 65592 -> 121681 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Win32/anke.ico b/Win32/anke.ico index d4b422081269b0a939faf2e49dc3632770728b5f..509177bb378cd1e711f85ea0b284e7d49c0b179d 100644 GIT binary patch literal 121681 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
%?MsGrQ^B^7%MdM{hd5k-r- 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

ZAV6DTygQAu&!6< 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 S`XKTJ#gBVU9sIxk@&5rN32ux4 literal 65592 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ℜ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 Pl$ZGBw7hbPAJP8@ziKnK From 9470107bbaa5e25cae03a3c61efa73a509a0b0e9 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 20 Mar 2016 18:34:29 -0400 Subject: [PATCH 1110/6300] show mascot image --- Win32/Resource.rc | 3 ++- Win32/Win32App.cpp | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Win32/Resource.rc b/Win32/Resource.rc index e6750ef3..bdc532e9 100644 --- a/Win32/Resource.rc +++ b/Win32/Resource.rc @@ -52,7 +52,8 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -MAINICON ICON "ictoopie.ico" +//MAINICON ICON "ictoopie.ico" +MAINICON ICON "anke.ico" MASCOT BITMAP "Anke_700px.bmp" diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 985d9eec..f2915498 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -164,6 +164,19 @@ namespace win32 } break; } + case WM_PAINT: + { + PAINTSTRUCT ps; + auto hDC = BeginPaint (hWnd, &ps); + auto mascot = LoadBitmap (GetModuleHandle(NULL), MAKEINTRESOURCE (MASCOT)); + auto mascotDC = CreateCompatibleDC (hDC); + SelectObject (mascotDC, mascot); + BitBlt (hDC, 0,0, 533, 700, mascotDC, 0, 0, SRCCOPY); + DeleteDC (mascotDC); + DeleteObject (mascot); + EndPaint (hWnd, &ps); + break; + } } return DefWindowProc( hWnd, uMsg, wParam, lParam); } @@ -192,7 +205,7 @@ namespace win32 wclx.lpszClassName = I2PD_WIN32_CLASSNAME; RegisterClassEx (&wclx); // create new window - if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPEDWINDOW, 100, 100, 250, 150, NULL, NULL, hInst, NULL)) + if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPEDWINDOW, 100, 100, 533, 700, NULL, NULL, hInst, NULL)) { MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); return false; From 5947364846f6a1c5bc299013e35a92a98311cd34 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 21 Mar 2016 09:02:44 -0400 Subject: [PATCH 1111/6300] updated reseeds list --- Reseed.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 380babb5..caac8071 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -26,13 +26,12 @@ namespace data static std::vector httpsReseedHostList = { "https://reseed.i2p-projekt.de/", // Only HTTPS - //"https://i2pseed.zarrenspry.info/", // Only HTTPS and SU3 (v3) support "https://i2p.mooo.com/netDb/", "https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required "https://us.reseed.i2p2.no:444/", "https://uk.reseed.i2p2.no:444/", - "https://www.torontocrypto.org:8443/", - "https://i2p-0.manas.ca:8443/" + "https://i2p.manas.ca:8443/", + "https://i2p-0.manas.ca:8443/", "https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support "https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support "https://download.xxlspeed.com/" // Only HTTPS and SU3 (v3) support From 996f61efe1507b173bd57324b7860329f462e817 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 21 Mar 2016 13:02:51 -0400 Subject: [PATCH 1112/6300] use shared_ptr for Address --- HTTPServer.cpp | 10 +++--- NTCPSession.cpp | 14 ++++----- RouterContext.cpp | 36 ++++++++++----------- RouterInfo.cpp | 79 ++++++++++++++++++++++++----------------------- RouterInfo.h | 12 +++---- Transports.cpp | 10 +++--- 6 files changed, 81 insertions(+), 80 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index ddfa6478..9ffa0599 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -427,18 +427,18 @@ namespace util s << " (" << i2p::transport::transports.GetOutBandwidth () <<" Bps)
\r\n"; s << "Data path: " << i2p::fs::GetDataDir() << "
\r\n
\r\n"; s << "Our external address:" << "
\r\n" ; - for (auto& address : i2p::context.GetRouterInfo().GetAddresses()) + for (auto address : i2p::context.GetRouterInfo().GetAddresses()) { - switch (address.transportStyle) + switch (address->transportStyle) { case i2p::data::RouterInfo::eTransportNTCP: - if (address.host.is_v6 ()) + if (address->host.is_v6 ()) s << "NTCP6  "; else s << "NTCP  "; break; case i2p::data::RouterInfo::eTransportSSU: - if (address.host.is_v6 ()) + if (address->host.is_v6 ()) s << "SSU6     "; else s << "SSU     "; @@ -446,7 +446,7 @@ namespace util default: s << "Unknown  "; } - s << address.host.to_string() << ":" << address.port << "
\r\n"; + s << address->host.to_string() << ":" << address->port << "
\r\n"; } s << "
\r\nRouters: " << i2p::data::netdb.GetNumRouters () << " "; s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; diff --git a/NTCPSession.cpp b/NTCPSession.cpp index b0ea2e74..a7832335 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -759,15 +759,15 @@ namespace transport m_IsRunning = true; m_Thread = new std::thread (std::bind (&NTCPServer::Run, this)); // create acceptors - auto addresses = context.GetRouterInfo ().GetAddresses (); - for (auto& address : addresses) + auto& addresses = context.GetRouterInfo ().GetAddresses (); + for (auto address: addresses) { - if (address.transportStyle == i2p::data::RouterInfo::eTransportNTCP && address.host.is_v4 ()) + if (address->transportStyle == i2p::data::RouterInfo::eTransportNTCP && address->host.is_v4 ()) { m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, - boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address.port)); + boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port)); - LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address.port); + LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address->port); auto conn = std::make_shared(*this); m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, conn, std::placeholders::_1)); @@ -777,10 +777,10 @@ namespace transport m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service); m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6()); m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true)); - m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address.port)); + m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port)); m_NTCPV6Acceptor->listen (); - LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address.port); + LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address->port); auto conn = std::make_shared (*this); m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this, conn, std::placeholders::_1)); diff --git a/RouterContext.cpp b/RouterContext.cpp index b3774d89..dc08ede3 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -92,11 +92,11 @@ namespace i2p void RouterContext::UpdatePort (int port) { bool updated = false; - for (auto& address : m_RouterInfo.GetAddresses ()) + for (auto address : m_RouterInfo.GetAddresses ()) { - if (address.port != port) + if (address->port != port) { - address.port = port; + address->port = port; updated = true; } } @@ -107,11 +107,11 @@ namespace i2p void RouterContext::UpdateAddress (const boost::asio::ip::address& host) { bool updated = false; - for (auto& address : m_RouterInfo.GetAddresses ()) + for (auto address : m_RouterInfo.GetAddresses ()) { - if (address.host != host && address.IsCompatible (host)) + if (address->host != host && address->IsCompatible (host)) { - address.host = host; + address->host = host; updated = true; } } @@ -206,15 +206,15 @@ namespace i2p auto& addresses = m_RouterInfo.GetAddresses (); for (size_t i = 0; i < addresses.size (); i++) { - if (addresses[i].transportStyle == i2p::data::RouterInfo::eTransportNTCP) + if (addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP) { addresses.erase (addresses.begin () + i); break; } } // delete previous introducers - for (auto& addr : addresses) - addr.introducers.clear (); + for (auto addr : addresses) + addr->introducers.clear (); // update UpdateRouterInfo (); @@ -235,16 +235,16 @@ namespace i2p auto& addresses = m_RouterInfo.GetAddresses (); for (size_t i = 0; i < addresses.size (); i++) { - if (addresses[i].transportStyle == i2p::data::RouterInfo::eTransportSSU) + if (addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU) { // insert NTCP address with host/port from SSU - m_RouterInfo.AddNTCPAddress (addresses[i].host.to_string ().c_str (), addresses[i].port); + m_RouterInfo.AddNTCPAddress (addresses[i]->host.to_string ().c_str (), addresses[i]->port); break; } } // delete previous introducers - for (auto& addr : addresses) - addr.introducers.clear (); + for (auto addr : addresses) + addr->introducers.clear (); // update UpdateRouterInfo (); @@ -264,19 +264,19 @@ namespace i2p bool updated = false, found = false; int port = 0; auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr : addresses) + for (auto addr: addresses) { - if (addr.host.is_v6 () && addr.transportStyle == i2p::data::RouterInfo::eTransportNTCP) + if (addr->host.is_v6 () && addr->transportStyle == i2p::data::RouterInfo::eTransportNTCP) { - if (addr.host != host) + if (addr->host != host) { - addr.host = host; + addr->host = host; updated = true; } found = true; } else - port = addr.port; + port = addr->port; } if (!found) { diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 84768201..f1e1e0f4 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -232,7 +232,7 @@ namespace data } if (isValidAddress) { - m_Addresses.push_back(address); + m_Addresses.push_back(std::make_shared

(address)); m_SupportedTransports |= supportedTransports; } } @@ -359,8 +359,9 @@ namespace data // addresses uint8_t numAddresses = m_Addresses.size (); s.write ((char *)&numAddresses, sizeof (numAddresses)); - for (auto& address : m_Addresses) + for (auto addr : m_Addresses) { + Address& address = *addr; s.write ((char *)&address.cost, sizeof (address.cost)); s.write ((char *)&address.date, sizeof (address.date)); std::stringstream properties; @@ -543,46 +544,46 @@ namespace data void RouterInfo::AddNTCPAddress (const char * host, int port) { - Address addr; - addr.host = boost::asio::ip::address::from_string (host); - addr.port = port; - addr.transportStyle = eTransportNTCP; - addr.cost = 2; - addr.date = 0; - addr.mtu = 0; + auto addr = std::make_shared
(); + addr->host = boost::asio::ip::address::from_string (host); + addr->port = port; + addr->transportStyle = eTransportNTCP; + addr->cost = 2; + addr->date = 0; + addr->mtu = 0; for (auto it: m_Addresses) // don't insert same address twice if (it == addr) return; m_Addresses.push_back(addr); - m_SupportedTransports |= addr.host.is_v6 () ? eNTCPV6 : eNTCPV4; + m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4; } void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu) { - Address addr; - addr.host = boost::asio::ip::address::from_string (host); - addr.port = port; - addr.transportStyle = eTransportSSU; - addr.cost = 10; // NTCP should have priority over SSU - addr.date = 0; - addr.mtu = mtu; - memcpy (addr.key, key, 32); + auto addr = std::make_shared
(); + addr->host = boost::asio::ip::address::from_string (host); + addr->port = port; + addr->transportStyle = eTransportSSU; + addr->cost = 10; // NTCP should have priority over SSU + addr->date = 0; + addr->mtu = mtu; + memcpy (addr->key, key, 32); for (auto it: m_Addresses) // don't insert same address twice if (it == addr) return; 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 |= eSSUIntroducer; } 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 ()) { - for (auto intro: addr.introducers) + for (auto intro: addr->introducers) if (intro.iTag == introducer.iTag) return false; // already presented - addr.introducers.push_back (introducer); + addr->introducers.push_back (introducer); return true; } } @@ -591,14 +592,14 @@ namespace data bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) { - for (auto& addr : m_Addresses) + for (auto addr: m_Addresses) { - if (addr.transportStyle == eTransportSSU && addr.host.is_v4 ()) + if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) { - for (std::vector::iterator it = addr.introducers.begin (); it != addr.introducers.end (); it++) + for (std::vector::iterator it = addr->introducers.begin (); it != addr->introducers.end (); it++) if ( boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e) { - addr.introducers.erase (it); + addr->introducers.erase (it); return true; } } @@ -664,8 +665,8 @@ namespace data m_SupportedTransports &= ~eNTCPV6; for (size_t i = 0; i < m_Addresses.size (); i++) { - if (m_Addresses[i].transportStyle == i2p::data::RouterInfo::eTransportNTCP && - m_Addresses[i].host.is_v6 ()) + if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP && + m_Addresses[i]->host.is_v6 ()) { m_Addresses.erase (m_Addresses.begin () + i); break; @@ -676,8 +677,8 @@ namespace data 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 ()) + if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU && + m_Addresses[i]->host.is_v6 ()) { m_Addresses.erase (m_Addresses.begin () + i); break; @@ -691,29 +692,29 @@ namespace data return m_Caps & Caps::eUnreachable; // non-reachable } - const RouterInfo::Address * RouterInfo::GetNTCPAddress (bool v4only) const + std::shared_ptr RouterInfo::GetNTCPAddress (bool v4only) const { return GetAddress (eTransportNTCP, v4only); } - const RouterInfo::Address * RouterInfo::GetSSUAddress (bool v4only) const + std::shared_ptr RouterInfo::GetSSUAddress (bool v4only) const { return GetAddress (eTransportSSU, v4only); } - const RouterInfo::Address * RouterInfo::GetSSUV6Address () const + std::shared_ptr RouterInfo::GetSSUV6Address () const { return GetAddress (eTransportSSU, false, true); } - const RouterInfo::Address * RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const + std::shared_ptr RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const { - for (auto& address : m_Addresses) + for (auto address : m_Addresses) { - if (address.transportStyle == s) + if (address->transportStyle == s) { - if ((!v4only || address.host.is_v4 ()) && (!v6only || address.host.is_v6 ())) - return &address; + if ((!v4only || address->host.is_v4 ()) && (!v6only || address->host.is_v6 ())) + return address; } } return nullptr; diff --git a/RouterInfo.h b/RouterInfo.h index c0ef2131..69607d4b 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -116,10 +116,10 @@ namespace data void SetRouterIdentity (std::shared_ptr identity); std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; uint64_t GetTimestamp () const { return m_Timestamp; }; - std::vector
& GetAddresses () { return m_Addresses; }; - const Address * GetNTCPAddress (bool v4only = true) const; - const Address * GetSSUAddress (bool v4only = true) const; - const Address * GetSSUV6Address () const; + std::vector >& GetAddresses () { return m_Addresses; }; + std::shared_ptr GetNTCPAddress (bool v4only = true) const; + std::shared_ptr GetSSUAddress (bool v4only = true) const; + std::shared_ptr GetSSUV6Address () const; void AddNTCPAddress (const char * host, int port); void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); @@ -182,7 +182,7 @@ namespace data size_t ReadString (char * str, std::istream& s); void WriteString (const std::string& str, std::ostream& s); void ExtractCaps (const char * value); - const Address * GetAddress (TransportStyle s, bool v4only, bool v6only = false) const; + std::shared_ptr GetAddress (TransportStyle s, bool v4only, bool v6only = false) const; void UpdateCapsProperty (); private: @@ -192,7 +192,7 @@ namespace data uint8_t * m_Buffer; size_t m_BufferLen; uint64_t m_Timestamp; - std::vector
m_Addresses; + std::vector > m_Addresses; std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; uint8_t m_SupportedTransports, m_Caps; diff --git a/Transports.cpp b/Transports.cpp index 9b327133..c5d6c3cb 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -112,8 +112,8 @@ namespace transport m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); // create acceptors - auto addresses = context.GetRouterInfo ().GetAddresses (); - for (auto& address : addresses) + auto& addresses = context.GetRouterInfo ().GetAddresses (); + for (auto address : addresses) { if (!m_NTCPServer) { @@ -121,12 +121,12 @@ namespace transport m_NTCPServer->Start (); } - if (address.transportStyle == RouterInfo::eTransportSSU && address.host.is_v4 ()) + if (address->transportStyle == RouterInfo::eTransportSSU && address->host.is_v4 ()) { if (!m_SSUServer) { - m_SSUServer = new SSUServer (address.port); - LogPrint (eLogInfo, "Transports: Start listening UDP port ", address.port); + m_SSUServer = new SSUServer (address->port); + LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port); m_SSUServer->Start (); DetectExternalIP (); } From a003e396c5fe4a3bea40b876a9e031f5d4038768 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 21 Mar 2016 13:45:35 -0400 Subject: [PATCH 1113/6300] fixed UPnP build --- UPnP.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/UPnP.cpp b/UPnP.cpp index c4e5e04c..eb86d709 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -101,19 +101,19 @@ namespace transport void UPnP::Run () { - std::vector a = context.GetRouterInfo().GetAddresses(); - for (auto& address : a) + auto& a = context.GetRouterInfo().GetAddresses(); + for (auto address : a) { - if (!address.host.is_v6 ()) + if (!address->host.is_v6 ()) { Discover (); - if (address.transportStyle == data::RouterInfo::eTransportSSU ) + if (address->transportStyle == data::RouterInfo::eTransportSSU ) { - TryPortMapping (I2P_UPNP_UDP, address.port); + TryPortMapping (I2P_UPNP_UDP, address->port); } - else if (address.transportStyle == data::RouterInfo::eTransportNTCP ) + else if (address->transportStyle == data::RouterInfo::eTransportNTCP ) { - TryPortMapping (I2P_UPNP_TCP, address.port); + TryPortMapping (I2P_UPNP_TCP, address->port); } } } From 37c450f1e12f4eecd973c11fbecd983b6321a356 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 21 Mar 2016 15:13:07 -0400 Subject: [PATCH 1114/6300] fixed race condition --- UPnP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPnP.cpp b/UPnP.cpp index eb86d709..fea2ff9e 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -101,7 +101,7 @@ namespace transport void UPnP::Run () { - auto& a = context.GetRouterInfo().GetAddresses(); + const std::vector > a = context.GetRouterInfo().GetAddresses(); for (auto address : a) { if (!address->host.is_v6 ()) From 9692c34f6c5004ca97ca62ef90e5750e2b8f0641 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 22 Mar 2016 07:30:16 -0400 Subject: [PATCH 1115/6300] don't insert same address twice --- RouterInfo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index f1e1e0f4..e9285d54 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -552,7 +552,7 @@ namespace data addr->date = 0; addr->mtu = 0; 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_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4; } @@ -568,7 +568,7 @@ namespace data addr->mtu = mtu; memcpy (addr->key, key, 32); 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_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; m_Caps |= eSSUTesting; From 447566fe1475ef01f76a47bc66676084007e83c3 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 22 Mar 2016 09:50:24 -0400 Subject: [PATCH 1116/6300] gcc 4.8 --- docs/build_requirements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build_requirements.md b/docs/build_requirements.md index 3f523a80..ec4aa180 100644 --- a/docs/build_requirements.md +++ b/docs/build_requirements.md @@ -4,7 +4,7 @@ Build requirements Linux/FreeBSD/OSX ----------------- -GCC 4.6 or newer, Boost 1.46 or newer, openssl, zlib. Clang can be used instead of GCC. +GCC 4.8 or newer, Boost 1.49 or newer, openssl, zlib. Clang can be used instead of GCC. Windows ------- From c5644ee3f90cff3a567b71bdf16d9183eb6888de Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 22 Mar 2016 13:10:02 -0400 Subject: [PATCH 1117/6300] hold previous lookup response --- NetDb.cpp | 43 +++++++++++++++++++++++++++++++++++++------ NetDb.h | 3 +++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index eb1380de..857381d8 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -118,6 +118,7 @@ namespace data { SaveUpdated (); ManageLeaseSets (); + ManageLookupResponses (); } lastSave = ts; } @@ -671,13 +672,31 @@ namespace data if (!replyMsg) { LogPrint (eLogWarning, "NetDb: Requested ", key, " not found. ", numExcluded, " excluded"); - std::set excludedRouters; - for (int i = 0; i < numExcluded; i++) - { - excludedRouters.insert (excluded); - excluded += 32; + // find or cleate response + std::vector closestFloodfills; + bool found = false; + if (!numExcluded) + { + auto it = m_LookupResponses.find (ident); + if (it != m_LookupResponses.end ()) + { + closestFloodfills = it->second.first; + found = true; + } + } + if (!found) + { + std::set excludedRouters; + for (int i = 0; i < numExcluded; i++) + { + excludedRouters.insert (excluded); + excluded += 32; + } + closestFloodfills = GetClosestFloodfills (ident, 3, excludedRouters, true); + if (!numExcluded) // save if no excluded + m_LookupResponses[ident] = std::make_pair(closestFloodfills, i2p::util::GetSecondsSinceEpoch ()); } - replyMsg = CreateDatabaseSearchReply (ident, GetClosestFloodfills (ident, 3, excludedRouters, true)); + replyMsg = CreateDatabaseSearchReply (ident, closestFloodfills); } } @@ -972,5 +991,17 @@ namespace data it++; } } + + void NetDb::ManageLookupResponses () + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_LookupResponses.begin (); it != m_LookupResponses.end ();) + { + if (ts > it->second.second + 180) // 3 minutes + it = m_LookupResponses.erase (it); + else + it++; + } + } } } diff --git a/NetDb.h b/NetDb.h index 536ff644..1c062358 100644 --- a/NetDb.h +++ b/NetDb.h @@ -84,6 +84,7 @@ namespace data void Publish (); void ManageLeaseSets (); void ManageRequests (); + void ManageLookupResponses (); template std::shared_ptr GetRandomRouter (Filter filter) const; @@ -108,6 +109,8 @@ namespace data friend class NetDbRequests; NetDbRequests m_Requests; + + std::map, uint64_t> > m_LookupResponses; // ident->(closest FFs, timestamp) }; extern NetDb netdb; From 1833c0acbc4f775a4a929ff34bf7e44810333c9e Mon Sep 17 00:00:00 2001 From: 0niichan Date: Wed, 23 Mar 2016 02:37:22 +0700 Subject: [PATCH 1118/6300] Fix height and width of the main window --- Win32/Win32App.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index f2915498..3466db91 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -205,7 +205,7 @@ namespace win32 wclx.lpszClassName = I2PD_WIN32_CLASSNAME; RegisterClassEx (&wclx); // create new window - if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPEDWINDOW, 100, 100, 533, 700, NULL, NULL, hInst, NULL)) + if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPEDWINDOW, 100, 100, 549, 738, NULL, NULL, hInst, NULL)) { MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); return false; From 9bd97383bdddd43a189b0d136de56cf0ff8633eb Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 23 Mar 2016 16:04:42 -0400 Subject: [PATCH 1119/6300] don't connect to ipv6 address if not supported --- Transports.cpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index c5d6c3cb..2127b646 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -378,13 +378,18 @@ namespace transport { auto address = (*it).endpoint ().address (); LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); - auto addr = peer.router->GetNTCPAddress (); - if (addr) + if (address.is_v4 () || context.SupportsV6 ()) { - auto s = std::make_shared (*m_NTCPServer, peer.router); - m_NTCPServer->Connect (address, addr->port, s); - return; + auto addr = peer.router->GetNTCPAddress (); // TODO: take one we requested + if (addr) + { + auto s = std::make_shared (*m_NTCPServer, peer.router); + m_NTCPServer->Connect (address, addr->port, s); + return; + } } + else + LogPrint (eLogInfo, "Can't connect to NTCP ", address, " ipv6 is not supported"); } LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ()); std::unique_lock l(m_PeersMutex); @@ -411,12 +416,17 @@ namespace transport { auto address = (*it).endpoint ().address (); LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); - auto addr = peer.router->GetSSUAddress (!context.SupportsV6 ());; - if (addr) + if (address.is_v4 () || context.SupportsV6 ()) { - m_SSUServer->CreateSession (peer.router, address, addr->port); - return; + auto addr = peer.router->GetSSUAddress (); // TODO: take one we requested + if (addr) + { + m_SSUServer->CreateSession (peer.router, address, addr->port); + return; + } } + else + LogPrint (eLogInfo, "Can't connect to SSU ", address, " ipv6 is not supported"); } LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ()); std::unique_lock l(m_PeersMutex); From b28208d1bf6afdff97a5e7227c9f33a6a699ef61 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 23 Mar 2016 19:03:17 -0400 Subject: [PATCH 1120/6300] 0.9.25 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 244096b7..e7414de3 100644 --- a/version.h +++ b/version.h @@ -16,7 +16,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 24 +#define I2P_VERSION_MICRO 25 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) From aa877a73ba1f046765752331af5556dd2a55abb1 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 24 Mar 2016 00:00:00 +0000 Subject: [PATCH 1121/6300] * fix mistype --- Daemon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Daemon.cpp b/Daemon.cpp index f301ee3a..7f96b470 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -72,7 +72,7 @@ namespace i2p if (config == "") { config = i2p::fs::DataDirPath("i2p.conf"); - // use i2p.cong only if exists + // use i2p.conf only if exists if (!i2p::fs::Exists (config)) config = ""; /* reset */ } From 926b9458463d88940c851df96a5b1c3cea9e0b3c Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 24 Mar 2016 00:00:00 +0000 Subject: [PATCH 1122/6300] * UPnP.h : comments --- UPnP.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/UPnP.h b/UPnP.h index 1a7b55c5..32c42118 100644 --- a/UPnP.h +++ b/UPnP.h @@ -58,6 +58,5 @@ namespace transport } } -#endif - -#endif +#endif // USE_UPNP +#endif // __UPNP_H__ From 20341a381f3ec4b5ecc1f759915edbe8e31dae0a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 24 Mar 2016 11:05:47 -0400 Subject: [PATCH 1123/6300] show version in the 'About' window --- Win32/Win32App.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 3466db91..81a3ba48 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -2,6 +2,7 @@ #include #include #include "../Config.h" +#include "../version.h" #include "resource.h" #include "Win32App.h" #include @@ -90,7 +91,9 @@ namespace win32 { case ID_ABOUT: { - MessageBox( hWnd, TEXT("i2pd"), TEXT("About"), MB_ICONINFORMATION | MB_OK ); + std::stringstream text; + text << "Version: " << I2PD_VERSION << " " << CODENAME; + MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); return 0; } case ID_EXIT: @@ -171,12 +174,12 @@ namespace win32 auto mascot = LoadBitmap (GetModuleHandle(NULL), MAKEINTRESOURCE (MASCOT)); auto mascotDC = CreateCompatibleDC (hDC); SelectObject (mascotDC, mascot); - BitBlt (hDC, 0,0, 533, 700, mascotDC, 0, 0, SRCCOPY); + BitBlt (hDC, 0,0, 533, 700, mascotDC, 0, 0, SRCCOPY); DeleteDC (mascotDC); DeleteObject (mascot); EndPaint (hWnd, &ps); break; - } + } } return DefWindowProc( hWnd, uMsg, wParam, lParam); } From bc5ff37e375c0e8554d492c1d23d7a715b881ac6 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 24 Mar 2016 11:18:11 -0400 Subject: [PATCH 1124/6300] check for chunk size --- util.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/util.cpp b/util.cpp index bd77e682..5230f55f 100644 --- a/util.cpp +++ b/util.cpp @@ -106,11 +106,15 @@ namespace http while (!response.eof ()) { std::string hexLen; - int len; + size_t len; std::getline (response, hexLen); std::istringstream iss (hexLen); iss >> std::hex >> len; - if (!len) break; + if (!len || len > 10000000L) // 10M + { + LogPrint (eLogError, "Unexpected chunk length ", len); + break; + } char * buf = new char[len]; response.read (buf, len); merged.write (buf, len); From 2e5c56205c210dc7b6e0446f87d54c5e3fef5fba Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 24 Mar 2016 14:48:07 -0400 Subject: [PATCH 1125/6300] address resolver --- AddressBook.cpp | 81 +++++++++++++++++++++++++++++++++++++++++++++++-- AddressBook.h | 26 ++++++++++++++-- 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 22008294..f972d48f 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -330,8 +330,7 @@ namespace client LoadHostsFromStream (f); m_IsLoaded = true; } - // load local - m_Storage->LoadLocal (m_Addresses); + LoadLocal (); } void AddressBook::LoadHostsFromStream (std::istream& f) @@ -395,6 +394,40 @@ namespace client LogPrint (eLogError, "Addressbook: subscriptions already loaded"); } + void AddressBook::LoadLocal () + { + std::map localAddresses; + m_Storage->LoadLocal (localAddresses); + for (auto it: localAddresses) + { + auto dot = it.first.find ('.'); + if (dot != std::string::npos) + { + auto domain = it.first.substr (dot + 1); + auto it1 = m_Addresses.find (domain); // find domain in our addressbook + if (it1 != m_Addresses.end ()) + { + auto dest = context.FindLocalDestination (it1->second); + if (dest) + { + // address is ours + std::shared_ptr resolver; + auto it2 = m_Resolvers.find (it1->second); + if (it2 != m_Resolvers.end ()) + resolver = it2->second; // resolver exists + else + { + // create new resolver + resolver = std::make_shared(dest); + m_Resolvers.insert (std::make_pair(it1->second, resolver)); + } + resolver->AddAddress (it.first, it.second); + } + } + } + } + } + bool AddressBook::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) { if (m_Storage) @@ -653,6 +686,50 @@ namespace client m_Book.LoadHostsFromStream (s); return true; } + + AddressResolver::AddressResolver (std::shared_ptr destination): + m_LocalDestination (destination) + { + if (m_LocalDestination) + { + auto datagram = m_LocalDestination->GetDatagramDestination (); + if (!datagram) + datagram = m_LocalDestination->CreateDatagramDestination (); + datagram->SetReceiver (std::bind (&AddressResolver::HandleRequest, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), + ADDRESS_RESOLVER_DATAGRAM_PORT); + } + } + + void AddressResolver::HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + if (len < 9 || len < buf[8] + 9U) + { + LogPrint (eLogError, "Address request is too short ", len); + return; + } + // read requested address + uint8_t l = buf[8]; + char address[255]; + memcpy (address, buf + 9, l); + address[l] = 0; + // send response + uint8_t response[40]; + memset (response, 0, 4); // reserved + memcpy (response + 4, buf + 4, 4); // nonce + auto it = m_LocalAddresses.find (address); // address lookup + if (it != m_LocalAddresses.end ()) + memcpy (response + 8, it->second, 32); // ident + else + memset (response + 8, 0, 32); // not found + m_LocalDestination->GetDatagramDestination ()->SendDatagramTo (response, 40, from.GetIdentHash (), toPort, fromPort); + } + + void AddressResolver::AddAddress (const std::string& name, const i2p::data::IdentHash& ident) + { + m_LocalAddresses[name] = ident; + } + } } diff --git a/AddressBook.h b/AddressBook.h index 98422fe1..436e7c8b 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -12,6 +12,7 @@ #include "Base.h" #include "Identity.h" #include "Log.h" +#include "Destination.h" namespace i2p { @@ -23,7 +24,7 @@ namespace client const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours) const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes const int SUBSCRIPTION_REQUEST_TIMEOUT = 60; //in second - + inline std::string GetB32Address(const i2p::data::IdentHash& ident) { return ident.ToBase32().append(".b32.i2p"); } class AddressBookStorage // interface for storage @@ -45,6 +46,7 @@ namespace client }; class AddressBookSubscription; + class AddressResolver; class AddressBook { public: @@ -74,13 +76,15 @@ namespace client void LoadHosts (); void LoadSubscriptions (); + void LoadLocal (); void HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode); private: std::mutex m_AddressBookMutex; - std::map m_Addresses, m_LocalAddresses; + std::map m_Addresses; + std::map > m_Resolvers; // local destination->resolver AddressBookStorage * m_Storage; volatile bool m_IsLoaded, m_IsDownloading; std::vector m_Subscriptions; @@ -106,6 +110,24 @@ namespace client std::string m_Link, m_Etag, m_LastModified; // m_Etag must be surrounded by "" }; + + const uint16_t ADDRESS_RESOLVER_DATAGRAM_PORT = 53; + class AddressResolver + { + public: + + AddressResolver (std::shared_ptr destination); + void AddAddress (const std::string& name, const i2p::data::IdentHash& ident); + + private: + + void HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + + private: + + std::shared_ptr m_LocalDestination; + std::map m_LocalAddresses; + }; } } From 12c12a8ad1620e4125cbb8667add3cad8af017ab Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 24 Mar 2016 18:44:41 -0400 Subject: [PATCH 1126/6300] add no ipv4 option in config --- Config.cpp | 1 + Daemon.cpp | 6 ++++-- RouterContext.cpp | 12 +++++++++++- RouterContext.h | 3 +++ RouterInfo.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++-- RouterInfo.h | 3 +++ 6 files changed, 67 insertions(+), 5 deletions(-) diff --git a/Config.cpp b/Config.cpp index caec7e20..5e775e74 100644 --- a/Config.cpp +++ b/Config.cpp @@ -117,6 +117,7 @@ namespace config { ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") ("host", value()->default_value("0.0.0.0"), "External IP") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") + ("ipv4", value()->zero_tokens()->default_value(true), "Enable communication through ipv4") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") ("daemon", value()->zero_tokens()->default_value(false), "Router will go to background after start") ("service", value()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'") diff --git a/Daemon.cpp b/Daemon.cpp index 7f96b470..bd89250e 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -104,9 +104,11 @@ namespace i2p i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); } - bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); bool transit; i2p::config::GetOption("notransit", transit); - i2p::context.SetSupportsV6 (ipv6); + i2p::context.SetSupportsV6 (ipv6); + i2p::context.SetSupportsV4 (ipv4); i2p::context.SetAcceptsTunnels (!transit); bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); diff --git a/RouterContext.cpp b/RouterContext.cpp index dc08ede3..2157a81e 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -257,7 +257,17 @@ namespace i2p else m_RouterInfo.DisableV6 (); UpdateRouterInfo (); - } + } + + void RouterContext::SetSupportsV4 (bool supportsV4) + { + if (supportsV4) + m_RouterInfo.EnableV4 (); + else + m_RouterInfo.DisableV4 (); + UpdateRouterInfo (); + } + void RouterContext::UpdateNTCPV6Address (const boost::asio::ip::address& host) { diff --git a/RouterContext.h b/RouterContext.h index bc1ee836..0679a038 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -64,7 +64,10 @@ namespace i2p bool AcceptsTunnels () const { return m_AcceptsTunnels; }; void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; }; bool SupportsV6 () const { return m_RouterInfo.IsV6 (); }; + bool SupportsV4 () const { return m_RouterInfo.IsV4 (); }; void SetSupportsV6 (bool supportsV6); + void SetSupportsV4 (bool supportsV4); + void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session void UpdateStats (); diff --git a/RouterInfo.cpp b/RouterInfo.cpp index e9285d54..22c5f2f8 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -651,12 +651,24 @@ namespace data return m_SupportedTransports & (eNTCPV6 | eSSUV6); } + bool RouterInfo::IsV4 () const + { + return m_SupportedTransports & (eNTCPV4 | eSSUV4); + } + void RouterInfo::EnableV6 () { if (!IsV6 ()) m_SupportedTransports |= eNTCPV6 | eSSUV6; } - + + void RouterInfo::EnableV4 () + { + if (!IsV4 ()) + m_SupportedTransports |= eNTCPV4 | eSSUV4; + } + + void RouterInfo::DisableV6 () { if (IsV6 ()) @@ -686,7 +698,38 @@ namespace data } } } - + + void RouterInfo::DisableV4 () + { + if (IsV4 ()) + { + // NTCP + m_SupportedTransports &= ~eNTCPV4; + for (size_t i = 0; i < m_Addresses.size (); i++) + { + if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP && + m_Addresses[i]->host.is_v4 ()) + { + m_Addresses.erase (m_Addresses.begin () + i); + break; + } + } + + // SSU + m_SupportedTransports &= ~eSSUV4; + for (size_t i = 0; i < m_Addresses.size (); i++) + { + if (m_Addresses[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU && + m_Addresses[i]->host.is_v4 ()) + { + m_Addresses.erase (m_Addresses.begin () + i); + break; + } + } + } + } + + bool RouterInfo::UsesIntroducer () const { return m_Caps & Caps::eUnreachable; // non-reachable diff --git a/RouterInfo.h b/RouterInfo.h index 69607d4b..5fa865b0 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -133,8 +133,11 @@ namespace data bool IsNTCP (bool v4only = true) const; bool IsSSU (bool v4only = true) const; bool IsV6 () const; + bool IsV4 () const; void EnableV6 (); void DisableV6 (); + void EnableV4 (); + void DisableV4 (); bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; bool UsesIntroducer () const; bool IsIntroducer () const { return m_Caps & eSSUIntroducer; }; From 5befe1f019e1c3351492c99cc6f59686fbcf257f Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 24 Mar 2016 20:04:45 -0400 Subject: [PATCH 1127/6300] select appropritae address --- Transports.cpp | 52 +++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 2127b646..518d167b 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -376,20 +376,24 @@ namespace transport auto& peer = it1->second; if (!ecode && peer.router) { - auto address = (*it).endpoint ().address (); - LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); - if (address.is_v4 () || context.SupportsV6 ()) - { - auto addr = peer.router->GetNTCPAddress (); // TODO: take one we requested - if (addr) + while (it != boost::asio::ip::tcp::resolver::iterator()) + { + auto address = (*it).endpoint ().address (); + LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); + if (address.is_v4 () || context.SupportsV6 ()) { - auto s = std::make_shared (*m_NTCPServer, peer.router); - m_NTCPServer->Connect (address, addr->port, s); - return; - } + auto addr = peer.router->GetNTCPAddress (); // TODO: take one we requested + if (addr) + { + auto s = std::make_shared (*m_NTCPServer, peer.router); + m_NTCPServer->Connect (address, addr->port, s); + return; + } + break; + } + else + LogPrint (eLogInfo, "Transports: NTCP ", address, " is not supported"); } - else - LogPrint (eLogInfo, "Can't connect to NTCP ", address, " ipv6 is not supported"); } LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ()); std::unique_lock l(m_PeersMutex); @@ -414,19 +418,23 @@ namespace transport auto& peer = it1->second; if (!ecode && peer.router) { - auto address = (*it).endpoint ().address (); - LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); - if (address.is_v4 () || context.SupportsV6 ()) - { - auto addr = peer.router->GetSSUAddress (); // TODO: take one we requested - if (addr) + while (it != boost::asio::ip::tcp::resolver::iterator()) + { + auto address = (*it).endpoint ().address (); + LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); + if (address.is_v4 () || context.SupportsV6 ()) { - m_SSUServer->CreateSession (peer.router, address, addr->port); - return; + auto addr = peer.router->GetSSUAddress (); // TODO: take one we requested + if (addr) + { + m_SSUServer->CreateSession (peer.router, address, addr->port); + return; + } + break; } + else + LogPrint (eLogInfo, "Transports: SSU ", address, " is not supported"); } - else - LogPrint (eLogInfo, "Can't connect to SSU ", address, " ipv6 is not supported"); } LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ()); std::unique_lock l(m_PeersMutex); From 34223b8d4fb2b9e125fddf84293e9866e72b5a3f Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 24 Mar 2016 20:14:58 -0400 Subject: [PATCH 1128/6300] select appropritae address --- Transports.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Transports.cpp b/Transports.cpp index 518d167b..275c6f90 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -393,6 +393,7 @@ namespace transport } else LogPrint (eLogInfo, "Transports: NTCP ", address, " is not supported"); + it++; } } LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ()); @@ -434,6 +435,7 @@ namespace transport } else LogPrint (eLogInfo, "Transports: SSU ", address, " is not supported"); + it++; } } LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ()); From 6699bd47b58885d9f4ef2215788e967f9bf4fa28 Mon Sep 17 00:00:00 2001 From: 0niichan Date: Fri, 25 Mar 2016 22:48:58 +0700 Subject: [PATCH 1129/6300] Fixed b64 textarea in the webconsole --- HTTPServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 9ffa0599..67ddf362 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -534,7 +534,7 @@ namespace util auto dest = i2p::client::context.FindLocalDestination (ident); if (dest) { - s << "Base64:
\r\n
\r\n
\r\n"; s << "LeaseSets: " << dest->GetNumRemoteLeaseSets () << "
\r\n"; auto pool = dest->GetTunnelPool (); From 3afb1922bb5ecc790df4bca6167f8b4a947096b1 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 25 Mar 2016 16:04:44 -0400 Subject: [PATCH 1130/6300] Update family.md --- docs/family.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/family.md b/docs/family.md index 0f307143..8ef76b7a 100644 --- a/docs/family.md +++ b/docs/family.md @@ -8,11 +8,11 @@ New family ----------- You must create family self-signed certificate and key. The only key type supposted is prime256v1. -Use the following list of commands: -openssl ecparam -name prime256v1 -genkey -out .key -openssl req -new -key .key -out .csr -touch v3.ext -openssl x509 -req -days 3650 -in .csr -signkey .key -out .crt -extfile v3.ext +Use the following list of commands: +openssl ecparam -name prime256v1 -genkey -out .key +openssl req -new -key .key -out .csr +touch v3.ext +openssl x509 -req -days 3650 -in .csr -signkey .key -out .crt -extfile v3.ext specify .family.i2p.net for CN. From 56699a9f8974374ef72ccd3a94d478fd702e707e Mon Sep 17 00:00:00 2001 From: xcps Date: Sat, 26 Mar 2016 02:45:37 -0400 Subject: [PATCH 1131/6300] check if i2p address to call jump service --- HTTPProxy.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 6e835d21..c252614b 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -39,6 +39,7 @@ namespace proxy void HTTPRequestFailed(/*std::string message*/); void RedirectToJumpService(); void ExtractRequest(); + bool IsI2PAddress(); bool ValidateHTTPRequest(); void HandleJumpServices(); bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); @@ -176,6 +177,16 @@ namespace proxy m_path.erase(addressHelperPos); } + bool HTTPProxyHandler::IsI2PAddress() + { + auto pos = m_address.rfind (".i2p"); + if (pos != std::string::npos && (pos+4) == m_address.length ()) + { + return true; + } + return false; + } + bool HTTPProxyHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { ExtractRequest(); //TODO: parse earlier @@ -183,10 +194,14 @@ namespace proxy HandleJumpServices(); i2p::data::IdentHash identHash; - if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ - RedirectToJumpService(); - return false; + if (IsI2PAddress ()) + { + if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ + RedirectToJumpService(); + return false; + } } + m_request = m_method; m_request.push_back(' '); From 5261a3e8458a3ee1a811d861690795056b1c4b52 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 26 Mar 2016 09:40:19 -0400 Subject: [PATCH 1132/6300] add syslog logging option --- Daemon.cpp | 5 ++++- Log.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ Log.h | 27 +++++++++++++++++++++++---- docs/i2pd.conf | 7 +++++-- 4 files changed, 73 insertions(+), 7 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index bd89250e..8b8dfac1 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -159,7 +159,10 @@ namespace i2p if (isDaemon && (logs == "" || logs == "stdout")) logs = "file"; - if (logs == "file") { + if (logs == "syslog") { + // use syslog only no stdout + StartSyslog(); + } else if (logs == "file") { if (logfile == "") logfile = i2p::fs::DataDirPath("i2pd.log"); StartLog (logfile); diff --git a/Log.cpp b/Log.cpp index 6bba1b62..fbaac8c1 100644 --- a/Log.cpp +++ b/Log.cpp @@ -11,8 +11,32 @@ static const char * g_LogLevelStr[eNumLogLevels] = "debug" // eLogDebug }; +/** convert LogLevel enum to syslog priority level */ +static int ToSyslogLevel(LogLevel lvl) +{ + switch (lvl) { + case eLogError: + return LOG_ERR; + case eLogWarning: + return LOG_WARNING; + case eLogInfo: + return LOG_INFO; + case eLogDebug: + return LOG_DEBUG; + default: + // WTF? invalid log level? + return LOG_CRIT; + } +} + + void LogMsg::Process() { + if (log && log->SyslogEnabled()) { + // only log to syslog + syslog(ToSyslogLevel(level), "%s", s.str().c_str()); + return; + } auto stream = log ? log->GetLogStream () : nullptr; auto& output = stream ? *stream : std::cout; if (log) @@ -84,3 +108,20 @@ void Log::SetLogStream (std::shared_ptr logStream) { m_LogStream = logStream; } + +void Log::StartSyslog(const std::string & ident, const int facility) +{ + m_Ident = ident; + openlog(m_Ident.c_str(), LOG_PID, facility); +} + +void Log::StopSyslog() +{ + closelog(); + m_Ident.clear(); +} + +bool Log::SyslogEnabled() +{ + return m_Ident.size() > 0; +} diff --git a/Log.h b/Log.h index 363a23b5..701c450d 100644 --- a/Log.h +++ b/Log.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "Queue.h" enum LogLevel @@ -45,14 +46,19 @@ class Log: public i2p::util::MsgQueue std::shared_ptr GetLogStream () const { return m_LogStream; }; const std::string& GetTimestamp (); LogLevel GetLogLevel () { return m_MinLevel; }; - const std::string& GetFullFilePath () const { return m_FullFilePath; }; - + const std::string& GetFullFilePath () const { return m_FullFilePath; }; + /** start logging to syslog */ + void StartSyslog(const std::string & ident, const int facility = LOG_USER); + /** stop logging to syslog */ + void StopSyslog(); + /** are we logging to syslog right now? */ + bool SyslogEnabled(); private: void Flush (); private: - + std::string m_FullFilePath; // empty if stream std::shared_ptr m_LogStream; enum LogLevel m_MinLevel; @@ -61,7 +67,8 @@ class Log: public i2p::util::MsgQueue std::chrono::monotonic_clock::time_point m_LastTimestampUpdate; #else std::chrono::steady_clock::time_point m_LastTimestampUpdate; -#endif +#endif + std::string m_Ident; }; extern Log * g_Log; @@ -116,6 +123,18 @@ inline bool IsLogToFile () return g_Log ? !g_Log->GetFullFilePath ().empty () : false; } +inline void StartSyslog() +{ + StartLog(""); + g_Log->StartSyslog("i2pd"); +} + +inline void StopSyslog() +{ + if(g_Log) + g_Log->StopSyslog(); +} + template void LogPrint (std::stringstream& s, TValue arg) { diff --git a/docs/i2pd.conf b/docs/i2pd.conf index 32c524ca..92595311 100644 --- a/docs/i2pd.conf +++ b/docs/i2pd.conf @@ -16,7 +16,10 @@ ## Logging configuration section ## By default logs go to stdout with level info ## -## Logs destination (stdout, file) +## Logs destination (stdout, file, syslog) +## stdout - print log entries to stdout +## file - log entries to a file +## syslog - use syslog, see man 3 syslog #log = file ## Path to logfile (default - autodetect) #logfile = /var/log/i2pd.log @@ -40,7 +43,7 @@ ## don't just uncomment this #port = 4321 ##Enable communication through ipv6 -ipv6 +ipv6 = true ## Bandwidth configuration ## L limit bandwidth to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited ## Default is P for floodfill, L for regular node From c4e5a130ee2f045d9361fae33c5f9c6ecef270b8 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 26 Mar 2016 09:49:45 -0400 Subject: [PATCH 1133/6300] don't break win32 --- Log.cpp | 10 ++++++++-- Log.h | 11 ++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Log.cpp b/Log.cpp index fbaac8c1..bb4f8231 100644 --- a/Log.cpp +++ b/Log.cpp @@ -10,7 +10,7 @@ static const char * g_LogLevelStr[eNumLogLevels] = "info", // eLogInfo "debug" // eLogDebug }; - +#ifndef _WIN32 /** convert LogLevel enum to syslog priority level */ static int ToSyslogLevel(LogLevel lvl) { @@ -28,15 +28,17 @@ static int ToSyslogLevel(LogLevel lvl) return LOG_CRIT; } } - +#endif void LogMsg::Process() { +#ifndef _WIN32 if (log && log->SyslogEnabled()) { // only log to syslog syslog(ToSyslogLevel(level), "%s", s.str().c_str()); return; } +#endif auto stream = log ? log->GetLogStream () : nullptr; auto& output = stream ? *stream : std::cout; if (log) @@ -111,14 +113,18 @@ void Log::SetLogStream (std::shared_ptr logStream) void Log::StartSyslog(const std::string & ident, const int facility) { +#ifndef _WIN32 m_Ident = ident; openlog(m_Ident.c_str(), LOG_PID, facility); +#endif } void Log::StopSyslog() { +#ifndef _WIN32 closelog(); m_Ident.clear(); +#endif } bool Log::SyslogEnabled() diff --git a/Log.h b/Log.h index 701c450d..76866590 100644 --- a/Log.h +++ b/Log.h @@ -8,9 +8,12 @@ #include #include #include -#include #include "Queue.h" +#ifndef _WIN32 +#include +#endif + enum LogLevel { eLogError = 0, @@ -48,7 +51,7 @@ class Log: public i2p::util::MsgQueue LogLevel GetLogLevel () { return m_MinLevel; }; const std::string& GetFullFilePath () const { return m_FullFilePath; }; /** start logging to syslog */ - void StartSyslog(const std::string & ident, const int facility = LOG_USER); + void StartSyslog(const std::string & ident, const int facility); /** stop logging to syslog */ void StopSyslog(); /** are we logging to syslog right now? */ @@ -126,7 +129,9 @@ inline bool IsLogToFile () inline void StartSyslog() { StartLog(""); - g_Log->StartSyslog("i2pd"); +#ifndef _WIN32 + g_Log->StartSyslog("i2pd", LOG_USER); +#endif } inline void StopSyslog() From 215d39fc547e8159692e3a535967ad56091ec2b1 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 26 Mar 2016 10:31:47 -0400 Subject: [PATCH 1134/6300] address lookup --- AddressBook.cpp | 117 ++++++++++++++++++++++++++++++++++++++++++++-- AddressBook.h | 13 +++++- ClientContext.cpp | 2 + 3 files changed, 126 insertions(+), 6 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index f972d48f..e26ce024 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "Base.h" #include "util.h" #include "Identity.h" @@ -218,10 +219,17 @@ namespace client m_Storage->Init(); LoadHosts (); /* try storage, then hosts.txt, then download */ StartSubscriptions (); + StartLookups (); } + + void AddressBook::StartResolvers () + { + LoadLocal (); + } void AddressBook::Stop () { + StopLookups (); StopSubscriptions (); if (m_SubscriptionsUpdateTimer) { @@ -330,7 +338,6 @@ namespace client LoadHostsFromStream (f); m_IsLoaded = true; } - LoadLocal (); } void AddressBook::LoadHostsFromStream (std::istream& f) @@ -518,6 +525,94 @@ namespace client } } + void AddressBook::StartLookups () + { + auto dest = i2p::client::context.GetSharedLocalDestination (); + if (dest) + { + auto datagram = dest->GetDatagramDestination (); + if (!datagram) + datagram = dest->CreateDatagramDestination (); + datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), + ADDRESS_RESPONSE_DATAGRAM_PORT); + } + } + + void AddressBook::StopLookups () + { + auto dest = i2p::client::context.GetSharedLocalDestination (); + if (dest) + { + auto datagram = dest->GetDatagramDestination (); + if (datagram) + datagram->ResetReceiver (ADDRESS_RESPONSE_DATAGRAM_PORT); + } + } + + void AddressBook::LookupAddress (const std::string& address) + { + const i2p::data::IdentHash * ident = nullptr; + auto dot = address.find ('.'); + if (dot != std::string::npos) + ident = FindAddress (address.substr (dot + 1)); + if (!ident) + { + LogPrint (eLogError, "AddressBook: Can't find domain for ", address); + return; + } + + auto dest = i2p::client::context.GetSharedLocalDestination (); + if (dest) + { + auto datagram = dest->GetDatagramDestination (); + if (datagram) + { + uint32_t nonce; + RAND_bytes ((uint8_t *)&nonce, 4); + { + std::unique_lock l(m_LookupsMutex); + m_Lookups[nonce] = address; + } + 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); + htobe32buf (buf + 4, nonce); + buf[8] = address.length (); + memcpy (buf + 9, address.c_str (), address.length ()); + datagram->SendDatagramTo (buf, len, *ident, ADDRESS_RESPONSE_DATAGRAM_PORT, ADDRESS_RESOLVER_DATAGRAM_PORT); + delete[] buf; + } + } + } + + void AddressBook::HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + if (len < 44) + { + 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); + std::string address; + { + std::unique_lock l(m_LookupsMutex); + auto it = m_Lookups.find (nonce); + if (it != m_Lookups.end ()) + { + address = it->second; + m_Lookups.erase (it); + } + } + if (address.length () > 0) + { + // TODO: verify from + m_Addresses[address] = buf + 8; + } + } + AddressBookSubscription::AddressBookSubscription (AddressBook& book, const std::string& link): m_Book (book), m_Link (link) { @@ -701,20 +796,31 @@ namespace client } } + AddressResolver::~AddressResolver () + { + if (m_LocalDestination) + { + auto datagram = m_LocalDestination->GetDatagramDestination (); + if (datagram) + datagram->ResetReceiver (ADDRESS_RESOLVER_DATAGRAM_PORT); + } + } + void AddressResolver::HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if (len < 9 || len < buf[8] + 9U) { - LogPrint (eLogError, "Address request is too short ", len); + LogPrint (eLogError, "AddressBook: Address request is too short ", len); return; } // read requested address uint8_t l = buf[8]; char address[255]; memcpy (address, buf + 9, l); - address[l] = 0; + address[l] = 0; + LogPrint (eLogDebug, "AddressBook: Address request ", address); // send response - uint8_t response[40]; + uint8_t response[44]; memset (response, 0, 4); // reserved memcpy (response + 4, buf + 4, 4); // nonce auto it = m_LocalAddresses.find (address); // address lookup @@ -722,7 +828,8 @@ namespace client memcpy (response + 8, it->second, 32); // ident else memset (response + 8, 0, 32); // not found - m_LocalDestination->GetDatagramDestination ()->SendDatagramTo (response, 40, from.GetIdentHash (), toPort, fromPort); + memset (response + 40, 0, 4); // set expiration time to zero + m_LocalDestination->GetDatagramDestination ()->SendDatagramTo (response, 44, from.GetIdentHash (), toPort, fromPort); } void AddressResolver::AddAddress (const std::string& name, const i2p::data::IdentHash& ident) diff --git a/AddressBook.h b/AddressBook.h index 436e7c8b..24a7e151 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -25,6 +25,9 @@ namespace client const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes const int SUBSCRIPTION_REQUEST_TIMEOUT = 60; //in second + const uint16_t ADDRESS_RESOLVER_DATAGRAM_PORT = 53; + const uint16_t ADDRESS_RESPONSE_DATAGRAM_PORT = 54; + inline std::string GetB32Address(const i2p::data::IdentHash& ident) { return ident.ToBase32().append(".b32.i2p"); } class AddressBookStorage // interface for storage @@ -54,10 +57,12 @@ namespace client AddressBook (); ~AddressBook (); void Start (); + void StartResolvers (); void Stop (); bool GetIdentHash (const std::string& address, i2p::data::IdentHash& ident); std::shared_ptr GetAddress (const std::string& address); const i2p::data::IdentHash * FindAddress (const std::string& address); + void LookupAddress (const std::string& address); void InsertAddress (const std::string& address, const std::string& base64); // for jump service void InsertAddress (std::shared_ptr address); @@ -80,11 +85,17 @@ namespace client void HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode); + void StartLookups (); + void StopLookups (); + void HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + private: std::mutex m_AddressBookMutex; std::map m_Addresses; std::map > m_Resolvers; // local destination->resolver + std::mutex m_LookupsMutex; + std::map m_Lookups; // nonce -> address AddressBookStorage * m_Storage; volatile bool m_IsLoaded, m_IsDownloading; std::vector m_Subscriptions; @@ -111,12 +122,12 @@ namespace client // m_Etag must be surrounded by "" }; - const uint16_t ADDRESS_RESOLVER_DATAGRAM_PORT = 53; class AddressResolver { public: AddressResolver (std::shared_ptr destination); + ~AddressResolver (); void AddAddress (const std::string& name, const i2p::data::IdentHash& ident); private: diff --git a/ClientContext.cpp b/ClientContext.cpp index 8d675286..8c626b25 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -96,6 +96,8 @@ namespace client m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); m_BOBCommandChannel->Start (); } + + m_AddressBook.StartResolvers (); } void ClientContext::Stop () From 8614c4db736457dc39d0fc7c92cb2f07d05f1b06 Mon Sep 17 00:00:00 2001 From: xcps Date: Sat, 26 Mar 2016 10:32:19 -0400 Subject: [PATCH 1135/6300] Sent/received traffic amount humanize --- HTTPServer.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 67ddf362..1aa5d273 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -421,10 +421,25 @@ namespace util } s << "
\r\n"; s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; - s << "Received: " << i2p::transport::transports.GetTotalReceivedBytes ()/1000 << "K"; - s << " (" << i2p::transport::transports.GetInBandwidth () <<" Bps)
\r\n"; - s << "Sent: " << i2p::transport::transports.GetTotalSentBytes ()/1000 << "K"; - s << " (" << i2p::transport::transports.GetOutBandwidth () <<" Bps)
\r\n"; + s << "Received: "; + s << std::fixed << std::setprecision(2); + auto numKBytesReceived = (double) i2p::transport::transports.GetTotalReceivedBytes () / 1024; + if (numKBytesReceived < 1024) + s << numKBytesReceived << " KiB"; + else if (numKBytesReceived < 1024 * 1024) + s << numKBytesReceived / 1024 << " MiB"; + else + s << numKBytesReceived / 1024 / 1024 << " GiB"; + s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " KiB/s)
\r\n"; + s << "Sent: "; + auto numKBytesSent = (double) i2p::transport::transports.GetTotalSentBytes () / 1024; + if (numKBytesSent < 1024) + s << numKBytesSent << " KiB"; + else if (numKBytesSent < 1024 * 1024) + s << numKBytesSent / 1024 << " MiB"; + else + s << numKBytesSent / 1024 / 1024 << " GiB"; + s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)
\r\n"; s << "Data path: " << i2p::fs::GetDataDir() << "
\r\n
\r\n"; s << "Our external address:" << "
\r\n" ; for (auto address : i2p::context.GetRouterInfo().GetAddresses()) From d39229713fc717018d63511a7a1874f81825e0c4 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 26 Mar 2016 15:02:27 -0400 Subject: [PATCH 1136/6300] lookup address upon request --- AddressBook.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AddressBook.cpp b/AddressBook.cpp index e26ce024..ad2d0c32 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -283,7 +283,10 @@ namespace client return true; } else + { + LookupAddress (address); // TODO: return false; + } } } // if not .b32 we assume full base64 address From 437225b43eaf448fe7338dc33ac78c192e4ad371 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 24 Mar 2016 10:34:39 +0000 Subject: [PATCH 1137/6300] * convert makefiles back to unix linefeeds --- Makefile | 182 ++++++++++++++++++++++++------------------------- Makefile.mingw | 87 ++++++++++++----------- 2 files changed, 134 insertions(+), 135 deletions(-) diff --git a/Makefile b/Makefile index d9d62b4c..beb7b467 100644 --- a/Makefile +++ b/Makefile @@ -1,91 +1,91 @@ -UNAME := $(shell uname -s) -SHLIB := libi2pd.so -ARLIB := libi2pd.a -SHLIB_CLIENT := libi2pdclient.so -ARLIB_CLIENT := libi2pdclient.a -I2PD := i2pd -GREP := fgrep -DEPS := obj/make.dep - -include filelist.mk - -USE_AESNI := yes -USE_STATIC := no - -ifeq ($(UNAME),Darwin) - DAEMON_SRC += DaemonLinux.cpp - include Makefile.osx -else ifeq ($(shell echo $(UNAME) | $(GREP) -c FreeBSD),1) - DAEMON_SRC += DaemonLinux.cpp - include Makefile.bsd -else ifeq ($(UNAME),Linux) - DAEMON_SRC += DaemonLinux.cpp - include Makefile.linux -else # win32 mingw - DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp - include Makefile.mingw -endif - -all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) - -mk_obj_dir: - @mkdir -p obj - @mkdir -p obj/Win32 - -api: mk_obj_dir $(SHLIB) $(ARLIB) -api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) - -## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time -## **without** overwriting the CXXFLAGS which we need in order to build. -## For example, when adding 'hardening flags' to the build -## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove -## -std=c++11. If you want to remove this variable please do so in a way that allows setting -## custom FLAGS to work at build-time. - -deps: mk_obj_dir - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) -MM *.cpp > $(DEPS) - @sed -i -e '/\.o:/ s/^/obj\//' $(DEPS) - -obj/%.o: %.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS) -c -o $@ $< - -# '-' is 'ignore if missing' on first run --include $(DEPS) - -DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) -$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) - $(CXX) -o $@ $^ $(LDLIBS) $(LDFLAGS) - -$(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) -ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ -endif - -$(SHLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) - $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ - -$(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) - ar -r $@ $^ - -$(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) - ar -r $@ $^ - -clean: - rm -rf obj - $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) - -strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) - strip $^ - -LATEST_TAG=$(shell git describe --tags --abbrev=0 master) -dist: - git archive --format=tar.gz -9 --worktree-attributes \ - --prefix=i2pd_$(LATEST_TAG)/ $(LATEST_TAG) -o i2pd_$(LATEST_TAG).tar.gz - -.PHONY: all -.PHONY: clean -.PHONY: deps -.PHONY: dist -.PHONY: api -.PHONY: api_client -.PHONY: mk_obj_dir +UNAME := $(shell uname -s) +SHLIB := libi2pd.so +ARLIB := libi2pd.a +SHLIB_CLIENT := libi2pdclient.so +ARLIB_CLIENT := libi2pdclient.a +I2PD := i2pd +GREP := fgrep +DEPS := obj/make.dep + +include filelist.mk + +USE_AESNI := yes +USE_STATIC := no + +ifeq ($(UNAME),Darwin) + DAEMON_SRC += DaemonLinux.cpp + include Makefile.osx +else ifeq ($(shell echo $(UNAME) | $(GREP) -c FreeBSD),1) + DAEMON_SRC += DaemonLinux.cpp + include Makefile.bsd +else ifeq ($(UNAME),Linux) + DAEMON_SRC += DaemonLinux.cpp + include Makefile.linux +else # win32 mingw + DAEMON_SRC += DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp + include Makefile.mingw +endif + +all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) + +mk_obj_dir: + @mkdir -p obj + @mkdir -p obj/Win32 + +api: mk_obj_dir $(SHLIB) $(ARLIB) +api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) + +## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time +## **without** overwriting the CXXFLAGS which we need in order to build. +## For example, when adding 'hardening flags' to the build +## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove +## -std=c++11. If you want to remove this variable please do so in a way that allows setting +## custom FLAGS to work at build-time. + +deps: mk_obj_dir + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) -MM *.cpp > $(DEPS) + @sed -i -e '/\.o:/ s/^/obj\//' $(DEPS) + +obj/%.o: %.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS) -c -o $@ $< + +# '-' is 'ignore if missing' on first run +-include $(DEPS) + +DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) +$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) + $(CXX) -o $@ $^ $(LDLIBS) $(LDFLAGS) + +$(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) +ifneq ($(USE_STATIC),yes) + $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ +endif + +$(SHLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) + $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ + +$(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) + ar -r $@ $^ + +$(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) + ar -r $@ $^ + +clean: + rm -rf obj + $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) + +strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) + strip $^ + +LATEST_TAG=$(shell git describe --tags --abbrev=0 master) +dist: + git archive --format=tar.gz -9 --worktree-attributes \ + --prefix=i2pd_$(LATEST_TAG)/ $(LATEST_TAG) -o i2pd_$(LATEST_TAG).tar.gz + +.PHONY: all +.PHONY: clean +.PHONY: deps +.PHONY: dist +.PHONY: api +.PHONY: api_client +.PHONY: mk_obj_dir diff --git a/Makefile.mingw b/Makefile.mingw index 1b500757..5fe3c479 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,44 +1,43 @@ -USE_WIN32_APP=yes -CXX = g++ -WINDRES = windres -CXXFLAGS = -Os -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN -NEEDED_CXXFLAGS = -std=c++11 -BOOST_SUFFIX = -mt -INCFLAGS = -I/usr/include/ -I/usr/local/include/ -LDFLAGS = -Wl,-rpath,/usr/local/lib \ - -L/usr/local/lib \ - -L/c/dev/openssl \ - -L/c/dev/boost/lib -LDLIBS = \ - -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) \ - -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) \ - -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) \ - -Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) \ - -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) \ - -Wl,-Bstatic -lssl \ - -Wl,-Bstatic -lcrypto \ - -Wl,-Bstatic -lz \ - -Wl,-Bstatic -lwsock32 \ - -Wl,-Bstatic -lws2_32 \ - -Wl,-Bstatic -lgdi32 \ - -Wl,-Bstatic -liphlpapi \ - -static-libgcc -static-libstdc++ \ - -Wl,-Bstatic -lstdc++ \ - -Wl,-Bstatic -lpthread - -ifeq ($(USE_WIN32_APP), yes) - CXXFLAGS += -DWIN32_APP - LDFLAGS += -mwindows -s - DAEMON_RC += Win32/Resource.rc - DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) -endif - -ifeq ($(USE_AESNI),1) - CPU_FLAGS = -maes -DAESNI -else - CPU_FLAGS = -msse -endif - -obj/%.o : %.rc - $(WINDRES) -i $< -o $@ - +USE_WIN32_APP=yes +CXX = g++ +WINDRES = windres +CXXFLAGS = -Os -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN +NEEDED_CXXFLAGS = -std=c++11 +BOOST_SUFFIX = -mt +INCFLAGS = -I/usr/include/ -I/usr/local/include/ +LDFLAGS = -Wl,-rpath,/usr/local/lib \ + -L/usr/local/lib \ + -L/c/dev/openssl \ + -L/c/dev/boost/lib +LDLIBS = \ + -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lssl \ + -Wl,-Bstatic -lcrypto \ + -Wl,-Bstatic -lz \ + -Wl,-Bstatic -lwsock32 \ + -Wl,-Bstatic -lws2_32 \ + -Wl,-Bstatic -lgdi32 \ + -Wl,-Bstatic -liphlpapi \ + -static-libgcc -static-libstdc++ \ + -Wl,-Bstatic -lstdc++ \ + -Wl,-Bstatic -lpthread + +ifeq ($(USE_WIN32_APP), yes) + CXXFLAGS += -DWIN32_APP + LDFLAGS += -mwindows -s + DAEMON_RC += Win32/Resource.rc + DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) +endif + +ifeq ($(USE_AESNI),1) + CPU_FLAGS = -maes -DAESNI +else + CPU_FLAGS = -msse +endif + +obj/%.o : %.rc + $(WINDRES) -i $< -o $@ From 2e848a7c9a5fa711bd319cd7895042547ac819e2 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 24 Mar 2016 10:39:16 +0000 Subject: [PATCH 1138/6300] * chg default branch for 'dist' target --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index beb7b467..ae49ae4f 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ clean: strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) strip $^ -LATEST_TAG=$(shell git describe --tags --abbrev=0 master) +LATEST_TAG=$(shell git describe --tags --abbrev=0 openssl) dist: git archive --format=tar.gz -9 --worktree-attributes \ --prefix=i2pd_$(LATEST_TAG)/ $(LATEST_TAG) -o i2pd_$(LATEST_TAG).tar.gz From 7bbe926232c870b975170e22ef4546221b954ef0 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 25 Mar 2016 04:00:27 +0000 Subject: [PATCH 1139/6300] * use freopen() instead close()/open() : avoid potential fd leak --- DaemonLinux.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 53d9f61e..59c286be 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -64,12 +64,9 @@ namespace i2p } // close stdin/stdout/stderr descriptors - ::close (0); - ::open ("/dev/null", O_RDWR); - ::close (1); - ::open ("/dev/null", O_RDWR); - ::close (2); - ::open ("/dev/null", O_RDWR); + freopen("/dev/null", "r", stdin); + freopen("/dev/null", "w", stdout); + freopen("/dev/null", "w", stderr); } // Pidfile From 598d0e216a14d2efd2e693d2bcd6c98f9b311379 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 24 Mar 2016 14:06:44 +0000 Subject: [PATCH 1140/6300] * fix build requrements --- debian/control | 2 +- docs/build_requirements.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/control b/debian/control index e76f9c7b..ceca7a5f 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: net Priority: extra Maintainer: hagen Build-Depends: debhelper (>= 9.0.0), dpkg-dev (>= 1.16.1~), - gcc (>= 4.6) | clang (>= 3.3), + gcc (>= 4.7) | clang (>= 3.3), libboost-regex-dev, libboost-system-dev (>= 1.46), libboost-date-time-dev, diff --git a/docs/build_requirements.md b/docs/build_requirements.md index ec4aa180..3e0dfea9 100644 --- a/docs/build_requirements.md +++ b/docs/build_requirements.md @@ -4,7 +4,7 @@ Build requirements Linux/FreeBSD/OSX ----------------- -GCC 4.8 or newer, Boost 1.49 or newer, openssl, zlib. Clang can be used instead of GCC. +GCC 4.7 or newer, Boost 1.49 or newer, openssl, zlib. Clang can be used instead of GCC. Windows ------- From 17fb419fb1c116142dc4eeaab064cb0b22df097d Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 27 Mar 2016 00:00:00 +0000 Subject: [PATCH 1141/6300] * new logs: code --- Log.cpp | 268 +++++++++++++++++++++++++++++-------------------------- Log.h | 269 +++++++++++++++++++++++++++++--------------------------- 2 files changed, 284 insertions(+), 253 deletions(-) diff --git a/Log.cpp b/Log.cpp index bb4f8231..27637c77 100644 --- a/Log.cpp +++ b/Log.cpp @@ -1,133 +1,155 @@ -#include +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include "Log.h" -Log * g_Log = nullptr; +namespace i2p { +namespace log { + Log logger; + /** + * @enum Maps our loglevel to their symbolic name + */ + static const char * g_LogLevelStr[eNumLogLevels] = + { + "error", // eLogError + "warn", // eLogWarn + "info", // eLogInfo + "debug" // eLogDebug + }; -static const char * g_LogLevelStr[eNumLogLevels] = -{ - "error", // eLogError - "warn", // eLogWarning - "info", // eLogInfo - "debug" // eLogDebug -}; #ifndef _WIN32 -/** convert LogLevel enum to syslog priority level */ -static int ToSyslogLevel(LogLevel lvl) -{ - switch (lvl) { - case eLogError: - return LOG_ERR; - case eLogWarning: - return LOG_WARNING; - case eLogInfo: - return LOG_INFO; - case eLogDebug: - return LOG_DEBUG; - default: - // WTF? invalid log level? - return LOG_CRIT; - } -} -#endif - -void LogMsg::Process() -{ -#ifndef _WIN32 - if (log && log->SyslogEnabled()) { - // only log to syslog - syslog(ToSyslogLevel(level), "%s", s.str().c_str()); - return; - } -#endif - auto stream = log ? log->GetLogStream () : nullptr; - auto& output = stream ? *stream : std::cout; - if (log) - output << log->GetTimestamp (); - else - output << boost::posix_time::second_clock::local_time().time_of_day (); - output << "/" << g_LogLevelStr[level] << " - "; - output << s.str(); -} - -const std::string& Log::GetTimestamp () -{ -#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) && !defined(__clang__) - auto ts = std::chrono::monotonic_clock::now (); -#else - auto ts = std::chrono::steady_clock::now (); -#endif - if (ts > m_LastTimestampUpdate + std::chrono::milliseconds (500)) // 0.5 second - { - m_LastTimestampUpdate = ts; - m_Timestamp = boost::posix_time::to_simple_string (boost::posix_time::second_clock::local_time().time_of_day ()); - } - return m_Timestamp; -} - -void Log::Flush () -{ - if (m_LogStream) - m_LogStream->flush(); -} - -void Log::SetLogFile (const std::string& fullFilePath, bool truncate) -{ - m_FullFilePath = fullFilePath; - auto mode = std::ofstream::out | std::ofstream::binary; - mode |= truncate ? std::ofstream::trunc : std::ofstream::app; - auto logFile = std::make_shared (fullFilePath, mode); - if (logFile->is_open ()) - { - SetLogStream (logFile); - LogPrint(eLogInfo, "Log: will send messages to ", fullFilePath); - } -} - -void Log::ReopenLogFile () -{ - if (m_FullFilePath.length () > 0) - { - SetLogFile (m_FullFilePath, false); // don't truncate - LogPrint(eLogInfo, "Log: file ", m_FullFilePath, " reopen"); + /** + * @brief Maps our log levels to syslog one + * @return syslog priority LOG_*, as defined in syslog.h + */ + static inline int GetSyslogPrio (enum LogLevel l) { + int priority = LOG_DEBUG; + switch (l) { + case eLogError : priority = LOG_ERR; break; + case eLogWarning : priority = LOG_WARNING; break; + case eLogInfo : priority = LOG_INFO; break; + case eLogDebug : priority = LOG_DEBUG; break; + default : priority = LOG_DEBUG; break; + } + return priority; } -} - - -void Log::SetLogLevel (const std::string& level) -{ - if (level == "error") { m_MinLevel = eLogError; } - else if (level == "warn") { m_MinLevel = eLogWarning; } - else if (level == "info") { m_MinLevel = eLogInfo; } - else if (level == "debug") { m_MinLevel = eLogDebug; } - else { - LogPrint(eLogError, "Log: Unknown loglevel: ", level); - return; - } - LogPrint(eLogInfo, "Log: min msg level set to ", level); -} - -void Log::SetLogStream (std::shared_ptr logStream) -{ - m_LogStream = logStream; -} - -void Log::StartSyslog(const std::string & ident, const int facility) -{ -#ifndef _WIN32 - m_Ident = ident; - openlog(m_Ident.c_str(), LOG_PID, facility); #endif -} -void Log::StopSyslog() -{ + Log::Log(): + m_Destination(eLogStdout), m_MinLevel(eLogInfo), + m_LogStream (nullptr), m_Logfile(""), m_IsReady(false) + { + } + + Log::~Log () + { + switch (m_Destination) { #ifndef _WIN32 - closelog(); - m_Ident.clear(); + case eLogSyslog : + closelog(); + break; #endif -} + case eLogFile: + case eLogStream: + m_LogStream->flush(); + break; + default: + /* do nothing */ + break; + } + Process(); + } -bool Log::SyslogEnabled() -{ - return m_Ident.size() > 0; -} + void Log::SetLogLevel (const std::string& level) { + if (level == "error") { m_MinLevel = eLogError; } + else if (level == "warn") { m_MinLevel = eLogWarning; } + else if (level == "info") { m_MinLevel = eLogInfo; } + else if (level == "debug") { m_MinLevel = eLogDebug; } + else { + LogPrint(eLogError, "Log: unknown loglevel: ", level); + return; + } + LogPrint(eLogInfo, "Log: min messages level set to ", level); + } + + const char * Log::TimeAsString(std::time_t t) { + if (t != m_LastTimestamp) { + strftime(m_LastDateTime, sizeof(m_LastDateTime), "%H:%M:%S", localtime(&t)); + m_LastTimestamp = t; + } + return m_LastDateTime; + } + + void Log::Process() { + std::unique_lock l(m_OutputLock); + while (1) { + auto msg = m_Queue.GetNextWithTimeout (1); + if (!msg) + break; + switch (m_Destination) { +#ifndef _WIN32 + case eLogSyslog: + syslog(GetSyslogPrio(msg->level), "%s", msg->text.c_str()); + break; +#endif + case eLogFile: + case eLogStream: + *m_LogStream << TimeAsString(msg->timestamp) << "/" << g_LogLevelStr[msg->level] << " - " << msg->text << std::endl; + break; + default: + std::cout << TimeAsString(msg->timestamp) << "/" << g_LogLevelStr[msg->level] << " - " << msg->text << std::endl; + break; + } // switch + } // while + } + + void Log::Append(std::shared_ptr & msg) { + m_Queue.Put(msg); + if (!m_IsReady) + return; + Process(); + } + + void Log::SendTo (const std::string& path) { + auto flags = std::ofstream::out | std::ofstream::app; + auto os = std::make_shared (path, flags); + if (os->is_open ()) { + m_Logfile = path; + m_Destination = eLogFile; + m_LogStream = os; + m_IsReady = true; + return; + } + LogPrint(eLogError, "Log: can't open file ", path); + } + + void Log::SendTo (std::shared_ptr os) { + m_Destination = eLogStream; + m_IsReady = true; + m_LogStream = os; + } + +#ifndef _WIN32 + void Log::SendTo(const char *name, int facility) { + m_Destination = eLogSyslog; + m_LogStream = nullptr; + m_IsReady = true; + openlog(name, LOG_CONS | LOG_PID, facility); + } +#endif + + void Log::Reopen() { + if (m_Destination == eLogFile) + SendTo(m_Logfile); + } + + Log & Logger() { + return logger; + } +} // log +} // i2p diff --git a/Log.h b/Log.h index 76866590..ba755ae4 100644 --- a/Log.h +++ b/Log.h @@ -1,11 +1,19 @@ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef LOG_H__ #define LOG_H__ +#include #include #include -#include #include -#include +#include #include #include #include "Queue.h" @@ -23,150 +31,151 @@ enum LogLevel eNumLogLevels }; -class Log; -struct LogMsg -{ - std::stringstream s; - Log * log; - LogLevel level; - - LogMsg (Log * l = nullptr, LogLevel lv = eLogInfo): log (l), level (lv) {}; - - void Process(); -}; - -class Log: public i2p::util::MsgQueue -{ - public: - - Log () { SetOnEmpty (std::bind (&Log::Flush, this)); }; - ~Log () {}; - - void SetLogFile (const std::string& fullFilePath, bool truncate = true); - void ReopenLogFile (); - void SetLogLevel (const std::string& level); - void SetLogStream (std::shared_ptr logStream); - std::shared_ptr GetLogStream () const { return m_LogStream; }; - const std::string& GetTimestamp (); - LogLevel GetLogLevel () { return m_MinLevel; }; - const std::string& GetFullFilePath () const { return m_FullFilePath; }; - /** start logging to syslog */ - void StartSyslog(const std::string & ident, const int facility); - /** stop logging to syslog */ - void StopSyslog(); - /** are we logging to syslog right now? */ - bool SyslogEnabled(); - private: - - void Flush (); - - private: - - std::string m_FullFilePath; // empty if stream - std::shared_ptr m_LogStream; - enum LogLevel m_MinLevel; - std::string m_Timestamp; -#if (__GNUC__ == 4) && (__GNUC_MINOR__ <= 6) && !defined(__clang__) // gcc 4.6 - std::chrono::monotonic_clock::time_point m_LastTimestampUpdate; -#else - std::chrono::steady_clock::time_point m_LastTimestampUpdate; -#endif - std::string m_Ident; -}; - -extern Log * g_Log; - -inline void StartLog (const std::string& fullFilePath) -{ - if (!g_Log) - { - auto log = new Log (); - if (fullFilePath.length () > 0) - log->SetLogFile (fullFilePath); - g_Log = log; - } -} - -inline void StartLog (std::shared_ptr s) -{ - if (!g_Log) - { - auto log = new Log (); - if (s) - log->SetLogStream (s); - g_Log = log; - } -} - -inline void StopLog () -{ - if (g_Log) - { - auto log = g_Log; - g_Log = nullptr; - log->Stop (); - delete log; - } -} - -inline void SetLogLevel (const std::string& level) -{ - if (g_Log) - g_Log->SetLogLevel(level); -} - -inline void ReopenLogFile () -{ - if (g_Log) - g_Log->ReopenLogFile (); -} - -inline bool IsLogToFile () -{ - return g_Log ? !g_Log->GetFullFilePath ().empty () : false; -} - -inline void StartSyslog() -{ - StartLog(""); +enum LogType { + eLogStdout = 0, + eLogStream, + eLogFile, #ifndef _WIN32 - g_Log->StartSyslog("i2pd", LOG_USER); + eLogSyslog, #endif -} +}; -inline void StopSyslog() -{ - if(g_Log) - g_Log->StopSyslog(); -} +namespace i2p { +namespace log { + struct LogMsg; /* forward declaration */ + class Log + { + private: + + enum LogType m_Destination; + enum LogLevel m_MinLevel; + std::shared_ptr m_LogStream; + std::string m_Logfile; + std::time_t m_LastTimestamp; + char m_LastDateTime[64]; + i2p::util::Queue > m_Queue; + volatile bool m_IsReady; + mutable std::mutex m_OutputLock; + + private: + + /** prevent making copies */ + Log (const Log &); + const Log& operator=(const Log&); + + /** + * @brief process stored messages in queue + */ + void Process (); + + /** + * @brief Makes formatted string from unix timestamp + * @param ts Second since epoch + * + * This function internally caches the result for last provided value + */ + const char * TimeAsString(std::time_t ts); + + public: + + Log (); + ~Log (); + + LogType GetLogType () { return m_Destination; }; + LogLevel GetLogLevel () { return m_MinLevel; }; + + /** + * @brief Sets minimal alloed level for log messages + * @param level String with wanted minimal msg level + */ + void SetLogLevel (const std::string& level); + + /** + * @brief Sets log destination to logfile + * @param path Path to logfile + */ + void SendTo (const std::string &path); + + /** + * @brief Sets log destination to given output stream + * @param os Output stream + */ + void SendTo (std::shared_ptr s); + + #ifndef _WIN32 + /** + * @brief Sets log destination to syslog + * @param name Wanted program name + * @param facility Wanted log category + */ + void SendTo (const char *name, int facility); + #endif + + /** + * @brief Format log message and write to output stream/syslog + * @param msg Pointer to processed message + */ + void Append(std::shared_ptr &); + + /** @brief Flushes the output log stream */ + void Flush(); + + /** @brief Reopen log file */ + void Reopen(); + }; + + /** + * @struct Log message container + * + * We creating it somewhere with LogPrint(), + * then put in MsgQueue for later processing. + */ + struct LogMsg { + std::time_t timestamp; + std::string text; /**< message text as single string */ + LogLevel level; /**< message level */ + + LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {}; + }; + + Log & Logger(); +} // log +} // i2p + +/** internal usage only -- folding args array to single string */ template -void LogPrint (std::stringstream& s, TValue arg) +void LogPrint (std::stringstream& s, TValue arg) { s << arg; } - + +/** internal usage only -- folding args array to single string */ template -void LogPrint (std::stringstream& s, TValue arg, TArgs... args) +void LogPrint (std::stringstream& s, TValue arg, TArgs... args) { LogPrint (s, arg); LogPrint (s, args...); } +/** + * @brief Create log message and send it to queue + * @param level Message level (eLogError, eLogInfo, ...) + * @param args Array of message parts + */ template -void LogPrint (LogLevel level, TArgs... args) +void LogPrint (LogLevel level, TArgs... args) { - if (g_Log && level > g_Log->GetLogLevel ()) + i2p::log::Log &log = i2p::log::Logger(); + if (level > log.GetLogLevel ()) return; - LogMsg * msg = new LogMsg (g_Log, level); - LogPrint (msg->s, args...); - msg->s << std::endl; - if (g_Log) { - g_Log->Put (msg); - } else { - msg->Process (); - delete msg; - } + + // fold message to single string + std::stringstream ss(""); + LogPrint (ss, args ...); + + auto msg = std::make_shared(level, std::time(nullptr), ss.str()); + log.Append(msg); } -#endif +#endif // LOG_H__ From c57b13d9226ff5b52651f7548bf98b1dc5ca0715 Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 27 Mar 2016 00:17:29 +0000 Subject: [PATCH 1142/6300] * migration --- Config.cpp | 2 +- Daemon.cpp | 50 ++++++++++++++++++++++++------------------------- DaemonLinux.cpp | 2 +- DaemonWin32.cpp | 2 +- api.cpp | 5 ++--- 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/Config.cpp b/Config.cpp index 5e775e74..8671f38c 100644 --- a/Config.cpp +++ b/Config.cpp @@ -110,7 +110,7 @@ namespace config { ("conf", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf)") ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg)") ("pidfile", value()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)") - ("log", value()->default_value(""), "Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility)") + ("log", value()->default_value(""), "Logs destination: stdout, file, syslog (stdout if not set)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") ("family", value()->default_value(""), "Specify a family, router belongs to") diff --git a/Daemon.cpp b/Daemon.cpp index 8b8dfac1..29d52ecc 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -79,17 +79,38 @@ namespace i2p i2p::config::ParseConfig(config); i2p::config::Finalize(); - i2p::crypto::InitCrypto (); - i2p::context.Init (); - i2p::config::GetOption("daemon", isDaemon); - // TODO: move log init here + std::string logs = ""; i2p::config::GetOption("log", logs); + std::string logfile = ""; i2p::config::GetOption("logfile", logfile); + std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); + + /* setup logging */ + if (isDaemon && (logs == "" || logs == "stdout")) + logs = "file"; + + i2p::log::Logger().SetLogLevel(loglevel); + if (logs == "file") { + if (logfile == "") + logfile = i2p::fs::DataDirPath("i2pd.log"); + LogPrint(eLogInfo, "Log: will send messages to ", logfile); + i2p::log::Logger().SendTo (logfile); +#ifndef _WIN32 + } else if (logs == "syslog") { + LogPrint(eLogInfo, "Log: will send messages to syslog"); + i2p::log::Logger().SendTo("i2pd", LOG_DAEMON); +#endif + } else { + // use stdout -- default + } LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); LogPrint(eLogDebug, "FS: main config file: ", config); LogPrint(eLogDebug, "FS: data directory: ", datadir); + i2p::crypto::InitCrypto (); + i2p::context.Init (); + uint16_t port; i2p::config::GetOption("port", port); if (!i2p::config::IsDefault("port")) { @@ -152,26 +173,6 @@ namespace i2p bool Daemon_Singleton::start() { - std::string logs = ""; i2p::config::GetOption("log", logs); - std::string logfile = ""; i2p::config::GetOption("logfile", logfile); - std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); - - if (isDaemon && (logs == "" || logs == "stdout")) - logs = "file"; - - if (logs == "syslog") { - // use syslog only no stdout - StartSyslog(); - } else if (logs == "file") { - if (logfile == "") - logfile = i2p::fs::DataDirPath("i2pd.log"); - StartLog (logfile); - } else { - // use stdout - StartLog (""); - } - SetLogLevel(loglevel); - bool http; i2p::config::GetOption("http.enabled", http); if (http) { std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); @@ -236,7 +237,6 @@ namespace i2p d.m_I2PControlService = nullptr; } i2p::crypto::TerminateCrypto (); - StopLog (); return true; } diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 59c286be..e760b4f5 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -19,7 +19,7 @@ void handle_signal(int sig) { case SIGHUP: LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening log..."); - ReopenLogFile (); + i2p::log::Logger().Reopen (); break; case SIGABRT: case SIGTERM: diff --git a/DaemonWin32.cpp b/DaemonWin32.cpp index b28cf2cd..a6d91da4 100644 --- a/DaemonWin32.cpp +++ b/DaemonWin32.cpp @@ -75,7 +75,7 @@ namespace i2p i2p::config::SetOption("log", std::string ("file")); #endif bool ret = Daemon_Singleton::start(); - if (ret && IsLogToFile ()) + if (ret && i2p::log::Logger().GetLogType() == eLogFile) { // TODO: find out where this garbage to console comes from SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE); diff --git a/api.cpp b/api.cpp index 3e037c02..3bef3f8e 100644 --- a/api.cpp +++ b/api.cpp @@ -40,9 +40,9 @@ namespace api void StartI2P (std::shared_ptr logStream) { if (logStream) - StartLog (logStream); + i2p::log::Logger().SendTo (logStream); else - StartLog (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log")); + i2p::log::Logger().SendTo (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log")); LogPrint(eLogInfo, "API: starting NetDB"); i2p::data::netdb.Start(); LogPrint(eLogInfo, "API: starting Transports"); @@ -60,7 +60,6 @@ namespace api i2p::transport::transports.Stop(); LogPrint(eLogInfo, "API: stopping NetDB"); i2p::data::netdb.Stop(); - StopLog (); } void RunPeerTest () From 3eae716a2d19c4161e6d4f3f82831094f30e8874 Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 27 Mar 2016 00:00:00 +0000 Subject: [PATCH 1143/6300] * drop MsgQueue wrapper : not used anymore --- Queue.h | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/Queue.h b/Queue.h index 6f50e189..b47a19c8 100644 --- a/Queue.h +++ b/Queue.h @@ -117,52 +117,6 @@ namespace util std::mutex m_QueueMutex; std::condition_variable m_NonEmpty; }; - - template - class MsgQueue: public Queue - { - public: - - typedef std::function OnEmpty; - - MsgQueue (): m_IsRunning (true), m_Thread (std::bind (&MsgQueue::Run, this)) {}; - ~MsgQueue () { Stop (); }; - void Stop() - { - if (m_IsRunning) - { - m_IsRunning = false; - Queue::WakeUp (); - m_Thread.join(); - } - } - - void SetOnEmpty (OnEmpty const & e) { m_OnEmpty = e; }; - - private: - - void Run () - { - while (m_IsRunning) - { - while (auto msg = Queue::Get ()) - { - msg->Process (); - delete msg; - } - if (m_OnEmpty != nullptr) - m_OnEmpty (); - if (m_IsRunning) - Queue::Wait (); - } - } - - private: - - volatile bool m_IsRunning; - OnEmpty m_OnEmpty; - std::thread m_Thread; - }; } } From 5c9970c7867dd151ee7766cabe647a916663cd67 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 27 Mar 2016 09:16:30 -0400 Subject: [PATCH 1144/6300] delete packet if not saved --- Streaming.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index 33be58b5..8c11a889 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -162,7 +162,9 @@ namespace stream void Stream::SavePacket (Packet * packet) { - m_SavedPackets.insert (packet); + auto ins = m_SavedPackets.insert (packet); + // delete packed if not saved + if (!ins.second) delete packet; } void Stream::ProcessPacket (Packet * packet) From 23d6739580fc9725fb10188a9e0fb83c8b112a09 Mon Sep 17 00:00:00 2001 From: i2phttp Date: Sun, 27 Mar 2016 16:27:36 +0300 Subject: [PATCH 1145/6300] Renamed i2pd.conf->i2p.conf with some fixes --- docs/{i2pd.conf => i2p.conf} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename docs/{i2pd.conf => i2p.conf} (96%) diff --git a/docs/i2pd.conf b/docs/i2p.conf similarity index 96% rename from docs/i2pd.conf rename to docs/i2p.conf index 92595311..9bbc73fa 100644 --- a/docs/i2pd.conf +++ b/docs/i2p.conf @@ -31,9 +31,9 @@ #datadir = /var/lib/i2pd ## Daemon mode. Router will go to background after start -#daemon +#daemon = true ## Run as a service. Router will use system folders like ‘/var/lib/i2pd’ -#service +#service = true ## External IP address to listen for connections ## By default i2pd sets IP automatically @@ -50,10 +50,10 @@ ipv6 = true #bandwidth = L ## Router will not accept transit tunnels at startup -#notransit +#notransit = true ## Router will be floodfill -#floodfill +#floodfill = true ## Section for Web Console ## By default it's available at 127.0.0.1:7070 even if it's not configured @@ -73,7 +73,7 @@ port = 4444 ## Optional keys file for proxy local destination #keys = http-proxy-keys.dat ## Uncomment if you want to disable HTTP proxy -#enabled=false +#enabled = false ## Section for Socks proxy ## By default it's available at 127.0.0.1:4447 even if it's not configured @@ -85,7 +85,7 @@ port = 4444 ## Optional keys file for proxy local destination #keys = socks-proxy-keys.dat ## Uncomment if you want to disable Socks proxy -#enabled=false +#enabled = false ## Socks outproxy. Example below is set to use Tor for all connections except i2p ## Address of outproxy #outproxy = 127.0.0.1 From 266744f640dd66c73e8758eadcd787c9e84d8b1e Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 27 Mar 2016 12:06:00 -0400 Subject: [PATCH 1146/6300] fixe memory leak --- Streaming.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index 33be58b5..e425ce27 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -162,7 +162,8 @@ namespace stream void Stream::SavePacket (Packet * packet) { - m_SavedPackets.insert (packet); + if (!m_SavedPackets.insert (packet).second) + delete packet; } void Stream::ProcessPacket (Packet * packet) From 905cad56d8ed49ccaddf2848cb9e66a0e4e69e9d Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 28 Mar 2016 00:00:00 +0000 Subject: [PATCH 1147/6300] * add note --- Log.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Log.cpp b/Log.cpp index 27637c77..9dd75d6b 100644 --- a/Log.cpp +++ b/Log.cpp @@ -85,6 +85,11 @@ namespace log { return m_LastDateTime; } + /** + * @note This function better to be run in separate thread due to disk i/o. + * Unfortunately, with current startup process with late fork() this + * will give us nothing but pain. Maybe later. See in NetDb as example. + */ void Log::Process() { std::unique_lock l(m_OutputLock); while (1) { From 8437d458661f8c439719a024ea457699161ec70e Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 28 Mar 2016 00:00:00 +0000 Subject: [PATCH 1148/6300] * rename i2p.conf -> i2pd.conf, add detection of old config --- Config.cpp | 2 +- Daemon.cpp | 14 +++++++++++--- debian/i2pd.1 | 2 +- debian/i2pd.links | 2 +- docs/configuration.md | 4 ++-- docs/{i2p.conf => i2pd.conf} | 0 6 files changed, 16 insertions(+), 8 deletions(-) rename docs/{i2p.conf => i2pd.conf} (100%) diff --git a/Config.cpp b/Config.cpp index 8671f38c..f9cf2477 100644 --- a/Config.cpp +++ b/Config.cpp @@ -107,7 +107,7 @@ namespace config { options_description general("General options"); general.add_options() ("help", "Show this message") - ("conf", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf)") + ("conf", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf)") ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg)") ("pidfile", value()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)") ("log", value()->default_value(""), "Logs destination: stdout, file, syslog (stdout if not set)") diff --git a/Daemon.cpp b/Daemon.cpp index 29d52ecc..d4803195 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -69,11 +69,19 @@ namespace i2p i2p::fs::Init(); datadir = i2p::fs::GetDataDir(); - if (config == "") + // TODO: drop old name detection in v2.8.0 + if (config == "") { config = i2p::fs::DataDirPath("i2p.conf"); - // use i2p.conf only if exists - if (!i2p::fs::Exists (config)) config = ""; /* reset */ + if (i2p::fs::Exists (config)) { + LogPrint(eLogWarning, "Daemon: please rename i2p.conf to i2pd.conf here: ", config); + } else { + config = i2p::fs::DataDirPath("i2pd.conf"); + if (!i2p::fs::Exists (config)) { + // use i2pd.conf only if exists + config = ""; /* reset */ + } + } } i2p::config::ParseConfig(config); diff --git a/debian/i2pd.1 b/debian/i2pd.1 index c6f0e9ba..2a1e7f88 100644 --- a/debian/i2pd.1 +++ b/debian/i2pd.1 @@ -68,7 +68,7 @@ Port of BOB command channel. Usually \fI2827\fR. BOB will not be enabled if this Port of I2P control service. Usually \fI7650\fR. I2PControl will not be enabled if this is not set. (default: unset) .TP \fB\-\-conf=\fR -Config file (default: \fI~/.i2pd/i2p.conf\fR or \fI/var/lib/i2pd/i2p.conf\fR) +Config file (default: \fI~/.i2pd/i2pd.conf\fR or \fI/var/lib/i2pd/i2pd.conf\fR) This parameter will be silently ignored if the specified config file does not exist. Options specified on the command line take precedence over those in the config file. diff --git a/debian/i2pd.links b/debian/i2pd.links index 369cce5b..58a75552 100644 --- a/debian/i2pd.links +++ b/debian/i2pd.links @@ -1,4 +1,4 @@ -etc/i2pd/i2pd.conf var/lib/i2pd/i2p.conf +etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.cfg etc/i2pd/subscriptions.txt var/lib/i2pd/subscriptions.txt usr/share/i2pd/certificates var/lib/i2pd/certificates diff --git a/docs/configuration.md b/docs/configuration.md index 46e5092c..3d03b1c7 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,7 +4,7 @@ i2pd configuration Command line options -------------------- -* --conf= - Config file (default: ~/.i2pd/i2p.conf or /var/lib/i2pd/i2p.conf) +* --conf= - Config file (default: ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf) This parameter will be silently ignored if the specified config file does not exist. Options specified on the command line take precedence over those in the config file. * --tunconf= - Tunnels config file (default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg) @@ -60,7 +60,7 @@ All command-line parameters are allowed as keys, but note for those which contai For example: -i2p.conf: +i2pd.conf: # comment log = true diff --git a/docs/i2p.conf b/docs/i2pd.conf similarity index 100% rename from docs/i2p.conf rename to docs/i2pd.conf From e6dbeda18eef8b7747910b4c9af149f4c54e316a Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 28 Mar 2016 00:00:00 +0000 Subject: [PATCH 1149/6300] * rename tunnels.conf -> tunnels.conf, add detection of old config --- ClientContext.cpp | 9 ++++++++- Config.cpp | 2 +- debian/i2pd.links | 2 +- docs/configuration.md | 4 ++-- docs/i2pd.conf | 4 ++-- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 8c626b25..ccb8e0ad 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -261,8 +261,15 @@ namespace client { boost::property_tree::ptree pt; std::string tunConf; i2p::config::GetOption("tunconf", tunConf); - if (tunConf == "") + if (tunConf == "") { + // TODO: cleanup this in 2.8.0 tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); + if (i2p::fs::Exists(tunConf)) { + LogPrint(eLogWarning, "FS: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); + } else { + tunConf = i2p::fs::DataDirPath ("tunnels.conf"); + } + } LogPrint(eLogDebug, "FS: tunnels config file: ", tunConf); try { diff --git a/Config.cpp b/Config.cpp index f9cf2477..1dc1207f 100644 --- a/Config.cpp +++ b/Config.cpp @@ -108,7 +108,7 @@ namespace config { general.add_options() ("help", "Show this message") ("conf", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf)") - ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg)") + ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf)") ("pidfile", value()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)") ("log", value()->default_value(""), "Logs destination: stdout, file, syslog (stdout if not set)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") diff --git a/debian/i2pd.links b/debian/i2pd.links index 58a75552..e8e2473b 100644 --- a/debian/i2pd.links +++ b/debian/i2pd.links @@ -1,4 +1,4 @@ etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf -etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.cfg +etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.conf etc/i2pd/subscriptions.txt var/lib/i2pd/subscriptions.txt usr/share/i2pd/certificates var/lib/i2pd/certificates diff --git a/docs/configuration.md b/docs/configuration.md index 3d03b1c7..18d30017 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -7,7 +7,7 @@ Command line options * --conf= - Config file (default: ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf) This parameter will be silently ignored if the specified config file does not exist. Options specified on the command line take precedence over those in the config file. -* --tunconf= - Tunnels config file (default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg) +* --tunconf= - Tunnels config file (default: ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf) * --pidfile= - Where to write pidfile (dont write by default) * --log= - Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility) * --logfile= - Path to logfile (default - autodetect) @@ -73,7 +73,7 @@ i2pd.conf: [sam] enabled = true -tunnels.cfg (filename of this config is subject of change): +tunnels.conf # outgoing tunnel sample, to remote service # mandatory parameters: diff --git a/docs/i2pd.conf b/docs/i2pd.conf index 9bbc73fa..81bcc634 100644 --- a/docs/i2pd.conf +++ b/docs/i2pd.conf @@ -7,8 +7,8 @@ ## by removing the "#" symbol. ## Tunnels config file -## Default: ~/.i2pd/tunnels.cfg or /var/lib/i2pd/tunnels.cfg -#tunconf = /var/lib/i2pd/tunnels.cfg +## Default: ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf +#tunconf = /var/lib/i2pd/tunnels.conf ## Where to write pidfile (don't write by default) #pidfile = /var/run/i2pd.pid From cc25b22f118fd050f944a58c56789e6380e27b99 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 28 Mar 2016 00:00:00 +0000 Subject: [PATCH 1150/6300] * update docs --- docs/configuration.md | 14 ++++- docs/family.md | 30 +++++----- docs/i2pd.conf | 131 ++++++++++++++++++++---------------------- 3 files changed, 91 insertions(+), 84 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 18d30017..1a45716d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,9 +4,12 @@ i2pd configuration Command line options -------------------- +Options specified on the command line take precedence over those in the config file. +If you are upgrading your very old router (< 2.3.0) see also [this](config_opts_after_2.3.0.md) page. + +* --help - Show builtin help message (default value of option will be shown in braces) * --conf= - Config file (default: ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf) This parameter will be silently ignored if the specified config file does not exist. - Options specified on the command line take precedence over those in the config file. * --tunconf= - Tunnels config file (default: ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf) * --pidfile= - Where to write pidfile (dont write by default) * --log= - Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility) @@ -24,6 +27,8 @@ Command line options * --family= - Name of a family, router belongs to * --svcctl= - Windows service management (--svcctl="install" or --svcctl="remove") +All options below still possible in cmdline, but better write it in config file: + * --http.address= - The address to listen on (HTTP server) * --http.port= - The port to listen on (HTTP server) @@ -69,11 +74,13 @@ i2pd.conf: [httpproxy] port = 4444 # ^^ this will be --httproxy.port= in cmdline - # another one + # another comment [sam] enabled = true -tunnels.conf +See also commented config with examples of all options in ``docs/i2pd.conf``. + +tunnels.conf: # outgoing tunnel sample, to remote service # mandatory parameters: @@ -107,6 +114,7 @@ tunnels.conf host = 127.0.0.1 port = 80 keys = site-keys.dat + # [IRC-SERVER] type = server host = 127.0.0.1 diff --git a/docs/family.md b/docs/family.md index 8ef76b7a..bb925171 100644 --- a/docs/family.md +++ b/docs/family.md @@ -6,27 +6,31 @@ There are two possibilities: create new family or joing to existing. New family ----------- + You must create family self-signed certificate and key. The only key type supposted is prime256v1. Use the following list of commands: -openssl ecparam -name prime256v1 -genkey -out .key -openssl req -new -key .key -out .csr -touch v3.ext -openssl x509 -req -days 3650 -in .csr -signkey .key -out .crt -extfile v3.ext -specify .family.i2p.net for CN. + openssl ecparam -name prime256v1 -genkey -out .key + openssl req -new -key .key -out .csr + touch v3.ext + openssl x509 -req -days 3650 -in .csr -signkey .key -out .crt -extfile v3.ext -Once you are done with it place .key and .crt to /family folder (for exmple ~/.i2pd/family). +Specify .family.i2p.net for CN (Common Name) when requested. + +Once you are done with it place .key and .crt to /family folder (for exmple ~/.i2pd/family). You should provide these two files to other members joining your family. If you want to register you family and let I2P network recorgnize it, create pull request for you .crt file into contrib/certificate/family. -It will appear in i2pd and I2P next releases packages. Don't place .key file, it must be shared betwwen you family members only. +It will appear in i2pd and I2P next releases packages. Dont place .key file, it must be shared between you family members only. -Join existing family --------------------- -Once you and that family agree to do it, they must give you .key and .crt file and you must place to /family folder. +How to join existing family +--------------------------- + +Once you and that family agree to do it, they must give you .key and .crt file and you must place in /certificates/family/ folder. Publish your family ------------------- -Run i2pd with parameter 'family=', make sure you have .key and .crt in your 'family' folder. -If everything is set properly, you router.info will contain two new fields: 'family' and 'family.sig'. +------------------- +Run i2pd with parameter 'family=', make sure you have .key and .crt in your 'family' folder. +If everything is set properly, you router.info will contain two new fields: 'family' and 'family.sig'. +Otherwise your router will complain on startup with log messages starting with "Family:" prefix and severity 'warn' or 'error'. diff --git a/docs/i2pd.conf b/docs/i2pd.conf index 81bcc634..e85eaa17 100644 --- a/docs/i2pd.conf +++ b/docs/i2pd.conf @@ -6,109 +6,104 @@ ## that begin with just "#" are disabled commands: you can enable them ## by removing the "#" symbol. -## Tunnels config file +## Tunnels config file ## Default: ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf -#tunconf = /var/lib/i2pd/tunnels.conf +# tunconf = /var/lib/i2pd/tunnels.conf ## Where to write pidfile (don't write by default) -#pidfile = /var/run/i2pd.pid +# pidfile = /var/run/i2pd.pid ## Logging configuration section -## By default logs go to stdout with level info +## By default logs go to stdout with level 'info' and higher ## -## Logs destination (stdout, file, syslog) -## stdout - print log entries to stdout -## file - log entries to a file -## syslog - use syslog, see man 3 syslog -#log = file +## Logs destination (valid values: stdout, file, syslog) +## * stdout - print log entries to stdout +## * file - log entries to a file +## * syslog - use syslog, see man 3 syslog +# log = file ## Path to logfile (default - autodetect) -#logfile = /var/log/i2pd.log +# logfile = /var/log/i2pd.log ## Log messages above this level (debug, *info, warn, error) -#loglevel = info +# loglevel = info ## Path to storage of i2pd data (RI, keys, peer profiles, ...) ## Default: ~/.i2pd or /var/lib/i2pd -#datadir = /var/lib/i2pd +# datadir = /var/lib/i2pd ## Daemon mode. Router will go to background after start -#daemon = true +# daemon = true ## Run as a service. Router will use system folders like ‘/var/lib/i2pd’ -#service = true +# service = true ## External IP address to listen for connections ## By default i2pd sets IP automatically -#host = 1.2.3.4 +# host = 1.2.3.4 + ## Port to listen for connections -## By default i2pd picks random port. You MUST pick a random number too, +## By default i2pd picks random port. You MUST pick a random number too, ## don't just uncomment this -#port = 4321 -##Enable communication through ipv6 +# port = 4321 + +## Enable communication through ipv6 ipv6 = true + ## Bandwidth configuration ## L limit bandwidth to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited ## Default is P for floodfill, L for regular node -#bandwidth = L +# bandwidth = L ## Router will not accept transit tunnels at startup -#notransit = true +# notransit = true ## Router will be floodfill -#floodfill = true +# floodfill = true -## Section for Web Console -## By default it's available at 127.0.0.1:7070 even if it's not configured [http] -## The address to listen on -address = 127.0.0.1 -## The port to listen on -port = 7070 +## Uncomment and set to 'false' to disable Web Console +# enabled = true +## Address and port service will listen on +address = 127.0.0.1 +port = 7070 -## Section for HTTP proxy -## By default it's available at 127.0.0.1:4444 even if it's not configured [httpproxy] -## The address to listen on -address = 127.0.0.1 -## The port to listen on -port = 4444 +## Uncomment and set to 'false' to disable HTTP Proxy +# enabled = true +## Address and port service will listen on +# address = 127.0.0.1 +# port = 4444 ## Optional keys file for proxy local destination -#keys = http-proxy-keys.dat -## Uncomment if you want to disable HTTP proxy -#enabled = false +# keys = http-proxy-keys.dat -## Section for Socks proxy -## By default it's available at 127.0.0.1:4447 even if it's not configured -#[socksproxy] -## The address to listen on -#address = 127.0.0.1 -## The port to listen on -#port = 4447 +[socksproxy] +## Uncomment and set to 'false' to disable SOCKS Proxy +# enabled = true +## Address and port service will listen on +# address = 127.0.0.1 +# port = 4447 ## Optional keys file for proxy local destination -#keys = socks-proxy-keys.dat -## Uncomment if you want to disable Socks proxy -#enabled = false +# keys = socks-proxy-keys.dat ## Socks outproxy. Example below is set to use Tor for all connections except i2p -## Address of outproxy -#outproxy = 127.0.0.1 -## Outproxy remote port -#outproxyport = 9050 +## Address and port of outproxy +# outproxy = 127.0.0.1 +# outproxyport = 9050 -## Section for SAM bridge -#[sam] -## The address to listen on -#address = 127.0.0.1 -## Port of SAM bridge -#port = 7656 +[sam] +## Uncomment and set to 'true' to enable SAM Bridge +# enabled = false +## Address and port service will listen on +# address = 127.0.0.1 +# port = 7656 -## Section for BOB command channel -#[bob] -## The address to listen on -#address = 127.0.0.1 -## Port of BOB command channel. Usually 2827. BOB is off if not specified -#port = 2827 +[bob] +## Uncomment and set to 'true' to enable BOB command channel +# enabled = false +## Address and port service will listen on +# address = 127.0.0.1 +# port = 2827 -## Section for I2PControl protocol -#[i2pcontrol] -## The address to listen on -#address = 127.0.0.1 -## Port of I2P control service -#port = 7650 +[i2pcontrol] +## Uncomment and set to 'true' to enable I2PControl protocol +# enabled = false +## Address and port service will listen on +# address = 127.0.0.1 +# port = 7650 From 38103aaac5d77161858e021dbf94061aa82e00f4 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 28 Mar 2016 14:00:00 +0000 Subject: [PATCH 1151/6300] * logger: explicit allow log output --- Daemon.cpp | 1 + Log.cpp | 3 --- Log.h | 3 +++ api.cpp | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index d4803195..3adfdb71 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -111,6 +111,7 @@ namespace i2p } else { // use stdout -- default } + i2p::log::Logger().Ready(); LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); LogPrint(eLogDebug, "FS: main config file: ", config); diff --git a/Log.cpp b/Log.cpp index 9dd75d6b..06aabac5 100644 --- a/Log.cpp +++ b/Log.cpp @@ -127,7 +127,6 @@ namespace log { m_Logfile = path; m_Destination = eLogFile; m_LogStream = os; - m_IsReady = true; return; } LogPrint(eLogError, "Log: can't open file ", path); @@ -135,7 +134,6 @@ namespace log { void Log::SendTo (std::shared_ptr os) { m_Destination = eLogStream; - m_IsReady = true; m_LogStream = os; } @@ -143,7 +141,6 @@ namespace log { void Log::SendTo(const char *name, int facility) { m_Destination = eLogSyslog; m_LogStream = nullptr; - m_IsReady = true; openlog(name, LOG_CONS | LOG_PID, facility); } #endif diff --git a/Log.h b/Log.h index ba755ae4..6cda62f6 100644 --- a/Log.h +++ b/Log.h @@ -118,6 +118,9 @@ namespace log { */ void Append(std::shared_ptr &); + /** @brief Allow log output */ + void Ready() { m_IsReady = true; } + /** @brief Flushes the output log stream */ void Flush(); diff --git a/api.cpp b/api.cpp index 3bef3f8e..64648743 100644 --- a/api.cpp +++ b/api.cpp @@ -43,6 +43,7 @@ namespace api i2p::log::Logger().SendTo (logStream); else i2p::log::Logger().SendTo (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log")); + i2p::log::Logger().Ready(); LogPrint(eLogInfo, "API: starting NetDB"); i2p::data::netdb.Start(); LogPrint(eLogInfo, "API: starting Transports"); From 1fae3baaa3270081c68c645c1db03ecce9dc6d24 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 28 Mar 2016 14:00:00 +0000 Subject: [PATCH 1152/6300] * logger: print also thread id --- Log.cpp | 16 +++++++++++++--- Log.h | 4 +++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Log.cpp b/Log.cpp index 06aabac5..36fcebf3 100644 --- a/Log.cpp +++ b/Log.cpp @@ -92,22 +92,32 @@ namespace log { */ void Log::Process() { std::unique_lock l(m_OutputLock); + std::hash hasher; + unsigned short short_tid; while (1) { auto msg = m_Queue.GetNextWithTimeout (1); if (!msg) break; + short_tid = (short) (hasher(msg->tid) % 1000); switch (m_Destination) { #ifndef _WIN32 case eLogSyslog: - syslog(GetSyslogPrio(msg->level), "%s", msg->text.c_str()); + syslog(GetSyslogPrio(msg->level), "[%03u] %s", short_tid, msg->text.c_str()); break; #endif case eLogFile: case eLogStream: - *m_LogStream << TimeAsString(msg->timestamp) << "/" << g_LogLevelStr[msg->level] << " - " << msg->text << std::endl; + *m_LogStream << TimeAsString(msg->timestamp) + << "@" << short_tid + << "/" << g_LogLevelStr[msg->level] + << " - " << msg->text << std::endl; break; + case eLogStdout: default: - std::cout << TimeAsString(msg->timestamp) << "/" << g_LogLevelStr[msg->level] << " - " << msg->text << std::endl; + std::cout << TimeAsString(msg->timestamp) + << "@" << short_tid + << "/" << g_LogLevelStr[msg->level] + << " - " << msg->text << std::endl; break; } // switch } // while diff --git a/Log.h b/Log.h index 6cda62f6..ebb73a12 100644 --- a/Log.h +++ b/Log.h @@ -138,8 +138,9 @@ namespace log { std::time_t timestamp; std::string text; /**< message text as single string */ LogLevel level; /**< message level */ + std::thread::id tid; /**< id of thread that generated message */ - LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {}; + LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl), tid(0) {}; }; Log & Logger(); @@ -178,6 +179,7 @@ void LogPrint (LogLevel level, TArgs... args) LogPrint (ss, args ...); auto msg = std::make_shared(level, std::time(nullptr), ss.str()); + msg->tid = std::this_thread::get_id(); log.Append(msg); } From e3451617635493e65b4c34193c2393f07194e3ee Mon Sep 17 00:00:00 2001 From: xcps Date: Mon, 28 Mar 2016 14:33:55 -0400 Subject: [PATCH 1153/6300] default subscription address to inr, jump services b32 --- AddressBook.h | 2 +- HTTPServer.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AddressBook.h b/AddressBook.h index 24a7e151..56f8145c 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -18,7 +18,7 @@ namespace i2p { namespace client { - const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p/hosts.txt"; + const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt"; const int INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT = 3; // in minutes const int INITIAL_SUBSCRIPTION_RETRY_TIMEOUT = 1; // in minutes const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 1aa5d273..b6ec3522 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -525,8 +525,8 @@ namespace util s << ""; s << "
\r\n"; s << "Jump services for " << address << ""; - s << "
"; + s << ""; } void HTTPConnection::ShowLocalDestinations (std::stringstream& s) From f5e1077e200e1d6cd660d4deb69d8d622955baf9 Mon Sep 17 00:00:00 2001 From: Manas Bhatnagar Date: Mon, 28 Mar 2016 17:05:39 -0400 Subject: [PATCH 1154/6300] i2p family cert file --- contrib/certificates/family/i2p.crt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 contrib/certificates/family/i2p.crt diff --git a/contrib/certificates/family/i2p.crt b/contrib/certificates/family/i2p.crt new file mode 100644 index 00000000..59d160ce --- /dev/null +++ b/contrib/certificates/family/i2p.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBsTCCAVigAwIBAgIJAN5fDNQVePgoMAkGByqGSM49BAEwXzELMAkGA1UEBhMC +QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp +dHMgUHR5IEx0ZDEYMBYGA1UEAwwPLmZhbWlseS5pMnAubmV0MB4XDTE2MDMyODE5 +NDUyNFoXDTI2MDMyNjE5NDUyNFowXzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNv +bWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEYMBYG +A1UEAwwPLmZhbWlseS5pMnAubmV0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +nq+UQQ6Wm/HIbEx41jY+kT7oP0NXU5Nsxsku97KPMtHNwYQD4CJYLwsTICd2ZtrK +jn8adUWmmXd+NbX1KrtMtDAJBgcqhkjOPQQBA0gAMEUCIQCTSNPVfjH2vwXzFEMy +V/4Q8sQ56TIjANcZ3ubAUQbA6wIgNu/bekl9tB1pRqPIm38v6Y6sxxZ8kOL++Q24 +YotZ7o0= +-----END CERTIFICATE----- From 0a3c4f131e16ea2e44b826ddd3ab9fec13f7a680 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 28 Mar 2016 17:15:27 -0400 Subject: [PATCH 1155/6300] fix issue #449 failed build of freebsd 10.1 --- Log.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Log.h b/Log.h index ebb73a12..33bd5868 100644 --- a/Log.h +++ b/Log.h @@ -140,7 +140,7 @@ namespace log { LogLevel level; /**< message level */ std::thread::id tid; /**< id of thread that generated message */ - LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl), tid(0) {}; + LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {}; }; Log & Logger(); From 0828065a623565f55cf5cc5ba92bc9d132f8a2ae Mon Sep 17 00:00:00 2001 From: Manas Bhatnagar Date: Mon, 28 Mar 2016 17:51:24 -0400 Subject: [PATCH 1156/6300] Rename i2p.crt to mca2-i2p.crt --- contrib/certificates/family/{i2p.crt => mca2-i2p.crt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contrib/certificates/family/{i2p.crt => mca2-i2p.crt} (100%) diff --git a/contrib/certificates/family/i2p.crt b/contrib/certificates/family/mca2-i2p.crt similarity index 100% rename from contrib/certificates/family/i2p.crt rename to contrib/certificates/family/mca2-i2p.crt From ed6851863b8244af3fd1c758db578218dda5910c Mon Sep 17 00:00:00 2001 From: Manas Bhatnagar Date: Mon, 28 Mar 2016 18:08:11 -0400 Subject: [PATCH 1157/6300] update mca2-i2p.crt --- contrib/certificates/family/mca2-i2p.crt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/contrib/certificates/family/mca2-i2p.crt b/contrib/certificates/family/mca2-i2p.crt index 59d160ce..f5b57303 100644 --- a/contrib/certificates/family/mca2-i2p.crt +++ b/contrib/certificates/family/mca2-i2p.crt @@ -1,12 +1,12 @@ -----BEGIN CERTIFICATE----- -MIIBsTCCAVigAwIBAgIJAN5fDNQVePgoMAkGByqGSM49BAEwXzELMAkGA1UEBhMC +MIIBwTCCAWigAwIBAgIJAOZBC10+/38EMAkGByqGSM49BAEwZzELMAkGA1UEBhMC QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp -dHMgUHR5IEx0ZDEYMBYGA1UEAwwPLmZhbWlseS5pMnAubmV0MB4XDTE2MDMyODE5 -NDUyNFoXDTI2MDMyNjE5NDUyNFowXzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNv -bWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEYMBYG -A1UEAwwPLmZhbWlseS5pMnAubmV0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE -nq+UQQ6Wm/HIbEx41jY+kT7oP0NXU5Nsxsku97KPMtHNwYQD4CJYLwsTICd2ZtrK -jn8adUWmmXd+NbX1KrtMtDAJBgcqhkjOPQQBA0gAMEUCIQCTSNPVfjH2vwXzFEMy -V/4Q8sQ56TIjANcZ3ubAUQbA6wIgNu/bekl9tB1pRqPIm38v6Y6sxxZ8kOL++Q24 -YotZ7o0= +dHMgUHR5IEx0ZDEgMB4GA1UEAwwXbWNhMi1pMnAuZmFtaWx5LmkycC5uZXQwHhcN +MTYwMzI4MjIwMjMxWhcNMjYwMzI2MjIwMjMxWjBnMQswCQYDVQQGEwJBVTETMBEG +A1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkg +THRkMSAwHgYDVQQDDBdtY2EyLWkycC5mYW1pbHkuaTJwLm5ldDBZMBMGByqGSM49 +AgEGCCqGSM49AwEHA0IABNNyfzJr/rMSUeWliVBbJHRF2+qMypOlHEZ9m1nNATVX +64OhuyuVCmbF9R3oDkcZZJQQK1ovXd/EsbAIWDI8K/gwCQYHKoZIzj0EAQNIADBF +AiEApmv2tvMwzlvPjHJG1/5aXOSjYWw2s4ETeGt4abWPQkACIBbF3RuCHuzg+KN8 +N0n9hAJztAqhRCdG3hilxF4fbVLp -----END CERTIFICATE----- From bbba01da9274bead751a7748ca9279c3a704beba Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Tue, 29 Mar 2016 15:09:40 +0200 Subject: [PATCH 1158/6300] OSX travis update (Test) --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index ff2d4958..f5fa5cf7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ cache: apt: true os: - linux + - osx sudo: required dist: trusty addons: @@ -23,6 +24,10 @@ addons: - libssl-dev compiler: - gcc + - clang +before_install: + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install boost openssl miniupnpc cmake ; fi env: matrix: - BUILD_TYPE=Release UPNP=ON From 06a4e6c323aecfc6ac3a8ba12f7273abddec8e9d Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Tue, 29 Mar 2016 15:30:06 +0200 Subject: [PATCH 1159/6300] OSX worker already got boost and cmake installed --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f5fa5cf7..f7cfdd89 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ compiler: - clang before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install boost openssl miniupnpc cmake ; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install openssl miniupnpc ; fi env: matrix: - BUILD_TYPE=Release UPNP=ON From c7d55ad8588c87ebbb1f5b4736c53b6cba50f18b Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Tue, 29 Mar 2016 16:01:07 +0200 Subject: [PATCH 1160/6300] Trying to fix broken builds on 10.7. Works fine on local 10.11 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f7cfdd89..c791187d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ compiler: before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install openssl miniupnpc ; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew unlink boost openssl && brew link boost openssl -f ; fi env: matrix: - BUILD_TYPE=Release UPNP=ON From db88183a23ee277e63fe998cd2fae41b886121fa Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 29 Mar 2016 12:34:53 -0400 Subject: [PATCH 1161/6300] graceful shutdown by SIGINT --- Daemon.h | 31 ++++++++++++++++++------------- DaemonLinux.cpp | 29 +++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/Daemon.h b/Daemon.h index efbd5df4..031686f7 100644 --- a/Daemon.h +++ b/Daemon.h @@ -50,26 +50,31 @@ namespace i2p bool init(int argc, char* argv[]); bool start(); - bool stop(); + bool stop(); void run (); }; #else class DaemonLinux : public Daemon_Singleton { - public: - static DaemonLinux& Instance() - { - static DaemonLinux instance; - return instance; - } + public: + static DaemonLinux& Instance() + { + static DaemonLinux instance; + return instance; + } - bool start(); - bool stop(); -; void run (); + bool start(); + bool stop(); + void run (); - private: - std::string pidfile; - int pidFH; + private: + + std::string pidfile; + int pidFH; + + public: + + int gracefullShutdownInterval; // in seconds }; #endif diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index e760b4f5..d517ef79 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -17,14 +17,17 @@ void handle_signal(int sig) { switch (sig) { - case SIGHUP: - LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening log..."); - i2p::log::Logger().Reopen (); - break; - case SIGABRT: - case SIGTERM: - case SIGINT: - Daemon.running = 0; // Exit loop + case SIGHUP: + LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening log..."); + i2p::log::Logger().Reopen (); + break; + case SIGINT: + Daemon.gracefullShutdownInterval = 10*60; // 10 minutes + LogPrint(eLogInfo, "Graceful shutdown after ", Daemon.gracefullShutdownInterval, " seconds"); + break; + case SIGABRT: + case SIGTERM: + Daemon.running = 0; // Exit loop break; } } @@ -96,6 +99,7 @@ namespace i2p return false; } } + gracefullShutdownInterval = 0; // not specified // Signal handler struct sigaction sa; @@ -122,6 +126,15 @@ namespace i2p while (running) { std::this_thread::sleep_for (std::chrono::seconds(1)); + if (gracefullShutdownInterval) + { + gracefullShutdownInterval--; // - 1 second + if (gracefullShutdownInterval <= 0) + { + LogPrint(eLogInfo, "Graceful shutdown"); + return; + } + } } } } From ac2e1709f80f08c5936faf9502eeec388a85cdb1 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 29 Mar 2016 12:50:34 -0400 Subject: [PATCH 1162/6300] graceful shutdown by SIGINT --- DaemonLinux.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index d517ef79..f91f782a 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -12,6 +12,7 @@ #include "Config.h" #include "FS.h" #include "Log.h" +#include "RouterContext.h" void handle_signal(int sig) { @@ -22,6 +23,7 @@ void handle_signal(int sig) i2p::log::Logger().Reopen (); break; case SIGINT: + i2p::context.SetAcceptsTunnels (false); Daemon.gracefullShutdownInterval = 10*60; // 10 minutes LogPrint(eLogInfo, "Graceful shutdown after ", Daemon.gracefullShutdownInterval, " seconds"); break; From 6c628094ce9d57536859ef09f36221f701bc1dcc Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Tue, 29 Mar 2016 23:55:29 +0200 Subject: [PATCH 1163/6300] Adding exception handler on HTTP Proxy --- ClientContext.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index ccb8e0ad..88d90622 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -52,8 +52,12 @@ namespace client LoadPrivateKeys (keys, httpProxyKeys); localDestination = CreateNewLocalDestination (keys, false); } - m_HttpProxy = new i2p::proxy::HTTPProxy(httpProxyAddr, httpProxyPort, localDestination); - m_HttpProxy->Start(); + try { + m_HttpProxy = new i2p::proxy::HTTPProxy(httpProxyAddr, httpProxyPort, localDestination); + m_HttpProxy->Start(); + } catch (std::exception& e) { + LogPrint(eLogError, "Exception in HTTP Proxy: ", e.what()); + } } bool socksproxy; i2p::config::GetOption("socksproxy.enabled", socksproxy); From f1fb2651190bf02273489ab5ebf2e844556f3610 Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Wed, 30 Mar 2016 00:03:15 +0200 Subject: [PATCH 1164/6300] Adding exceptions for SOCKS, SAM and BOB proxy/briges --- ClientContext.cpp | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 88d90622..59f11f21 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -56,7 +56,7 @@ namespace client m_HttpProxy = new i2p::proxy::HTTPProxy(httpProxyAddr, httpProxyPort, localDestination); m_HttpProxy->Start(); } catch (std::exception& e) { - LogPrint(eLogError, "Exception in HTTP Proxy: ", e.what()); + LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); } } @@ -74,8 +74,12 @@ namespace client LoadPrivateKeys (keys, socksProxyKeys); localDestination = CreateNewLocalDestination (keys, false); } - m_SocksProxy = new i2p::proxy::SOCKSProxy(socksProxyAddr, socksProxyPort, socksOutProxyAddr, socksOutProxyPort, localDestination); - m_SocksProxy->Start(); + try { + m_SocksProxy = new i2p::proxy::SOCKSProxy(socksProxyAddr, socksProxyPort, socksOutProxyAddr, socksOutProxyPort, localDestination); + m_SocksProxy->Start(); + } catch (std::exception& e) { + LogPrint(eLogError, "Clients: Exception in SOCKS Proxy: ", e.what()); + } } // I2P tunnels @@ -87,8 +91,12 @@ namespace client std::string samAddr; i2p::config::GetOption("sam.address", samAddr); uint16_t samPort; i2p::config::GetOption("sam.port", samPort); LogPrint(eLogInfo, "Clients: starting SAM bridge at ", samAddr, ":", samPort); - m_SamBridge = new SAMBridge (samAddr, samPort); - m_SamBridge->Start (); + try { + m_SamBridge = new SAMBridge (samAddr, samPort); + m_SamBridge->Start (); + } catch (std::exception& e) { + LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what()); + } } // BOB @@ -97,8 +105,12 @@ namespace client std::string bobAddr; i2p::config::GetOption("bob.address", bobAddr); uint16_t bobPort; i2p::config::GetOption("bob.port", bobPort); LogPrint(eLogInfo, "Clients: starting BOB command channel at ", bobAddr, ":", bobPort); - m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); - m_BOBCommandChannel->Start (); + try { + m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); + m_BOBCommandChannel->Start (); + } catch (std::exception& e) { + LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what()); + } } m_AddressBook.StartResolvers (); From 8366c8d2a7cdf78e1abed05343fa413fa4adbdc5 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 29 Mar 2016 21:37:30 -0400 Subject: [PATCH 1165/6300] don't initiate graceful shutdown twice --- DaemonLinux.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index f91f782a..b408fc70 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -23,9 +23,14 @@ void handle_signal(int sig) i2p::log::Logger().Reopen (); break; case SIGINT: - i2p::context.SetAcceptsTunnels (false); - Daemon.gracefullShutdownInterval = 10*60; // 10 minutes - LogPrint(eLogInfo, "Graceful shutdown after ", Daemon.gracefullShutdownInterval, " seconds"); + if (i2p::context.AcceptsTunnels () && !Daemon.gracefullShutdownInterval) + { + i2p::context.SetAcceptsTunnels (false); + Daemon.gracefullShutdownInterval = 10*60; // 10 minutes + LogPrint(eLogInfo, "Graceful shutdown after ", Daemon.gracefullShutdownInterval, " seconds"); + } + else + Daemon.running = 0; break; case SIGABRT: case SIGTERM: From e2a76056b8a095178fc3bd8e2d14a26d0f1e1f8f Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 31 Mar 2016 00:00:00 +0000 Subject: [PATCH 1166/6300] * RouterInfo.h : add comments with bandwidth letter speed in KBps --- RouterInfo.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/RouterInfo.h b/RouterInfo.h index 5fa865b0..7648c6ba 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -24,13 +24,14 @@ namespace data const char CAPS_FLAG_HIDDEN = 'H'; const char CAPS_FLAG_REACHABLE = 'R'; const char CAPS_FLAG_UNREACHABLE = 'U'; - const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K'; - const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L'; - const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'M'; - const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'N'; - const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O'; - const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; - const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; + /* bandwidth flags */ + const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K'; /* < 12 KBps */ + const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L'; /* 12-48 KBps */ + const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'M'; /* 48-64 KBps */ + const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'N'; /* 64-128 KBps */ + const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O'; /* 128-256 KBps */ + const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2000 KBps */ + const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /* > 2000 KBps */ const char CAPS_FLAG_SSU_TESTING = 'B'; const char CAPS_FLAG_SSU_INTRODUCER = 'C'; @@ -174,7 +175,6 @@ namespace data const uint8_t * GetEncryptionPublicKey () const { return m_RouterIdentity->GetStandardIdentity ().publicKey; }; bool IsDestination () const { return false; }; - private: bool LoadFile (); From 5888ecbdcdee89136a3a4d5f1cda02a6f2ee79c9 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 31 Mar 2016 00:00:00 +0000 Subject: [PATCH 1167/6300] * RouterInfo::UpdateCapsProperty() : add only one bw letter --- RouterInfo.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 22c5f2f8..c34087e5 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -333,16 +333,19 @@ namespace data void RouterInfo::UpdateCapsProperty () { std::string caps; - if (m_Caps & eFloodfill) - { - if (m_Caps & eExtraBandwidth) caps += CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' - caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' + if (m_Caps & eFloodfill) { caps += CAPS_FLAG_FLOODFILL; // floodfill - } - else - { - if (m_Caps & eExtraBandwidth) caps += CAPS_FLAG_EXTRA_BANDWIDTH1; - caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 : CAPS_FLAG_LOW_BANDWIDTH2; // bandwidth + caps += (m_Caps & eExtraBandwidth) + ? CAPS_FLAG_EXTRA_BANDWIDTH1 // 'P' + : CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' + } else { + if (m_Caps & eExtraBandwidth) { + caps += CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' + } else if (m_Caps & eHighBandwidth) { + caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' + } else { + caps += CAPS_FLAG_LOW_BANDWIDTH2; // 'L' + } } if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable From e625d8aabc074e0e3e965485d3d176493c1048a0 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 31 Mar 2016 00:00:00 +0000 Subject: [PATCH 1168/6300] * RouterInfo.cpp : remove .c_str() --- RouterInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index c34087e5..dfd54059 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -77,7 +77,7 @@ namespace data bool RouterInfo::LoadFile () { - std::ifstream s(m_FullPath.c_str (), std::ifstream::binary); + std::ifstream s(m_FullPath, std::ifstream::binary); if (s.is_open ()) { s.seekg (0,std::ios::end); From 642bcfcdeab395d5e155124400b0d038a2b64cf0 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 31 Mar 2016 00:00:00 +0000 Subject: [PATCH 1169/6300] * RouterContext : replace Set(Low|High|Extra)Bandwidth with SetBandwidth() --- RouterContext.cpp | 62 +++++++++++++++++++++++++++++------------------ RouterContext.h | 7 +++--- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index 2157a81e..9a1749d0 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -165,34 +165,50 @@ namespace i2p m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY_SIG); } } - - void RouterContext::SetHighBandwidth () - { - if (!m_RouterInfo.IsHighBandwidth () || m_RouterInfo.IsExtraBandwidth ()) - { - m_RouterInfo.SetCaps ((m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eHighBandwidth) & ~i2p::data::RouterInfo::eExtraBandwidth); - UpdateRouterInfo (); + + void RouterContext::SetBandwidth (char L) { + uint16_t limit = 0; + enum { low, high, extra } type = high; + /* detect parameters */ + switch (L) { + case i2p::data::CAPS_FLAG_LOW_BANDWIDTH1 : limit = 12; type = low; break; + case i2p::data::CAPS_FLAG_LOW_BANDWIDTH2 : limit = 48; type = low; break; + case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH1 : limit = 64; type = high; break; + case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH2 : limit = 128; type = high; break; + default: + case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH3 : limit = 256; type = high; break; + case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = 2048; type = extra; break; + case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2 : limit = 9999; type = extra; break; } + /* floodfill requires 'extra' bandwidth */ + if (m_IsFloodfill && limit < 2048) { + limit = 2048, type = extra; + LogPrint(eLogInfo, "Daemon: bandwidth set to 'extra' due to floodfill"); + } + /* update caps & flags in RI */ + auto caps = m_RouterInfo.GetCaps (); + caps &= ~i2p::data::RouterInfo::eHighBandwidth; + caps &= ~i2p::data::RouterInfo::eExtraBandwidth; + switch (type) { + case low : /* not set */; break; + case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break; + case extra : caps |= i2p::data::RouterInfo::eExtraBandwidth; break; + } + m_RouterInfo.SetCaps (caps); + UpdateRouterInfo (); + m_BandwidthLimit = limit; } - void RouterContext::SetLowBandwidth () - { - if (m_RouterInfo.IsHighBandwidth () || m_RouterInfo.IsExtraBandwidth ()) - { - m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eHighBandwidth & ~i2p::data::RouterInfo::eExtraBandwidth); - UpdateRouterInfo (); - } + void RouterContext::SetBandwidth (int limit) { + if (limit > 2000) { SetBandwidth('X'); } + else if (limit > 256) { SetBandwidth('P'); } + else if (limit > 128) { SetBandwidth('O'); } + else if (limit > 64) { SetBandwidth('N'); } + else if (limit > 48) { SetBandwidth('M'); } + else if (limit > 12) { SetBandwidth('L'); } + else { SetBandwidth('K'); } } - void RouterContext::SetExtraBandwidth () - { - if (!m_RouterInfo.IsExtraBandwidth () || !m_RouterInfo.IsHighBandwidth ()) - { - m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eExtraBandwidth | i2p::data::RouterInfo::eHighBandwidth); - UpdateRouterInfo (); - } - } - bool RouterContext::IsUnreachable () const { return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable; diff --git a/RouterContext.h b/RouterContext.h index 0679a038..9766c66e 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -45,6 +45,7 @@ namespace i2p uint32_t GetUptime () const; uint32_t GetStartupTime () const { return m_StartupTime; }; uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; }; + uint64_t GetBandwidthLimit () const { return m_BandwidthLimit; }; RouterStatus GetStatus () const { return m_Status; }; void SetStatus (RouterStatus status); @@ -58,9 +59,8 @@ namespace i2p bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); void SetFamily (const std::string& family); - void SetHighBandwidth (); - void SetLowBandwidth (); - void SetExtraBandwidth (); + void SetBandwidth (int limit); /* in kilobytes */ + void SetBandwidth (char L); /* by letter */ bool AcceptsTunnels () const { return m_AcceptsTunnels; }; void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; }; bool SupportsV6 () const { return m_RouterInfo.IsV6 (); }; @@ -101,6 +101,7 @@ namespace i2p uint64_t m_LastUpdateTime; bool m_AcceptsTunnels, m_IsFloodfill; uint64_t m_StartupTime; // in seconds since epoch + uint32_t m_BandwidthLimit; // allowed bandwidth RouterStatus m_Status; std::mutex m_GarlicMutex; }; From aef6b7712c0f8682111dc1ec6d0639a60873bb98 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 31 Mar 2016 00:00:00 +0000 Subject: [PATCH 1170/6300] * Transports: update IsBandwidthExceeded() and comments in header --- Transports.cpp | 4 ++-- Transports.h | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 275c6f90..0fdfce3d 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -200,9 +200,9 @@ namespace transport bool Transports::IsBandwidthExceeded () const { - if (i2p::context.GetRouterInfo ().IsExtraBandwidth ()) return false; + auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes auto bw = std::max (m_InBandwidth, m_OutBandwidth); - return bw > (i2p::context.GetRouterInfo ().IsHighBandwidth () ? HIGH_BANDWIDTH_LIMIT : LOW_BANDWIDTH_LIMIT); + return bw > limit; } void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) diff --git a/Transports.h b/Transports.h index 9b603963..3bfe1f8b 100644 --- a/Transports.h +++ b/Transports.h @@ -66,8 +66,6 @@ namespace transport }; const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds - const uint32_t LOW_BANDWIDTH_LIMIT = 32*1024; // 32KBs - const uint32_t HIGH_BANDWIDTH_LIMIT = 256*1024; // 256KBs class Transports { public: @@ -94,8 +92,8 @@ namespace transport void UpdateReceivedBytes (uint64_t numBytes) { m_TotalReceivedBytes += numBytes; }; uint64_t GetTotalSentBytes () const { return m_TotalSentBytes; }; uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; }; - uint32_t GetInBandwidth () const { return m_InBandwidth; }; // bytes per second - uint32_t GetOutBandwidth () const { return m_OutBandwidth; }; // bytes per second + uint32_t GetInBandwidth () const { return m_InBandwidth; }; + uint32_t GetOutBandwidth () const { return m_OutBandwidth; }; bool IsBandwidthExceeded () const; size_t GetNumPeers () const { return m_Peers.size (); }; std::shared_ptr GetRandomPeer () const; @@ -138,7 +136,7 @@ namespace transport DHKeysPairSupplier m_DHKeysPairSupplier; std::atomic m_TotalSentBytes, m_TotalReceivedBytes; - uint32_t m_InBandwidth, m_OutBandwidth; + uint32_t m_InBandwidth, m_OutBandwidth; // bytes per second uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes; uint64_t m_LastBandwidthUpdateTime; From 350dea622835b3c4752e33bda935e3ae19b59da1 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 31 Mar 2016 00:00:00 +0000 Subject: [PATCH 1171/6300] * update --bandwidth option handling --- Config.cpp | 2 +- Daemon.cpp | 35 +++++++++++------------------------ 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/Config.cpp b/Config.cpp index 1dc1207f..fab986f5 100644 --- a/Config.cpp +++ b/Config.cpp @@ -123,7 +123,7 @@ namespace config { ("service", value()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'") ("notransit", value()->zero_tokens()->default_value(false), "Router will not accept transit tunnels at startup") ("floodfill", value()->zero_tokens()->default_value(false), "Router will be floodfill") - ("bandwidth", value()->default_value('-'), "Bandwidth limiting: L - 32kbps, O - 256Kbps, P - unlimited") + ("bandwidth", value()->default_value("256"), "Bandwidth limit: integer in kbps or letters: L (32), O (256), P (2048), X (>9000)") #ifdef _WIN32 ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") ("insomnia", value()->zero_tokens()->default_value(false), "Prevent system from sleeping") diff --git a/Daemon.cpp b/Daemon.cpp index 3adfdb71..13cdc075 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -142,34 +142,21 @@ namespace i2p i2p::context.SetAcceptsTunnels (!transit); bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); - char bandwidth; i2p::config::GetOption("bandwidth", bandwidth); - - if (isFloodfill) - { + if (isFloodfill) { LogPrint(eLogInfo, "Daemon: router will be floodfill"); i2p::context.SetFloodfill (true); - } - else + } else { i2p::context.SetFloodfill (false); - if (bandwidth != '-') - { - LogPrint(eLogInfo, "Daemon: bandwidth set to ", bandwidth); - if (bandwidth > 'O') - i2p::context.SetExtraBandwidth (); - else if (bandwidth > 'L') - i2p::context.SetHighBandwidth (); - else - i2p::context.SetLowBandwidth (); } - else if (isFloodfill) - { - LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'"); - i2p::context.SetExtraBandwidth (); - } - else - { - LogPrint(eLogInfo, "Daemon: bandwidth set to 'low'"); - i2p::context.SetLowBandwidth (); + + /* this section also honors 'floodfill' flag, if set above */ + std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); + if (bandwidth[0] > 'K' && bandwidth[0] < 'Z') { + i2p::context.SetBandwidth (bandwidth[0]); + LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps"); + } else if (bandwidth[0] >= '0' && bandwidth[0] <= '9') { + i2p::context.SetBandwidth (std::atoi(bandwidth.c_str())); + LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), " KBps"); } std::string family; i2p::config::GetOption("family", family); From 27649f7d4c54b7b47a357697ae1492cb613f2165 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 31 Mar 2016 00:00:00 +0000 Subject: [PATCH 1172/6300] * update docs --- Config.cpp | 2 +- docs/configuration.md | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Config.cpp b/Config.cpp index fab986f5..73124ae4 100644 --- a/Config.cpp +++ b/Config.cpp @@ -127,7 +127,7 @@ namespace config { #ifdef _WIN32 ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") ("insomnia", value()->zero_tokens()->default_value(false), "Prevent system from sleeping") - ("close", value()->default_value("ask"), "On close action") // minimize, exit, ask TODO: add custom validator or something + ("close", value()->default_value("ask"), "Action on close: minimize, exit, ask") // TODO: add custom validator or something #endif ; diff --git a/docs/configuration.md b/docs/configuration.md index 1a45716d..f3e9f98c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -23,9 +23,14 @@ If you are upgrading your very old router (< 2.3.0) see also [this](config_opts_ * --ipv6 - Enable communication through ipv6. false by default * --notransit - Router will not accept transit tunnels at startup. false by default * --floodfill - Router will be floodfill. false by default -* --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited +* --bandwidth= - Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000) * --family= - Name of a family, router belongs to + +Windows-specific options: + * --svcctl= - Windows service management (--svcctl="install" or --svcctl="remove") +* --insomnia - Prevent system from sleeping +* --close= - Action on close: minimize, exit, ask All options below still possible in cmdline, but better write it in config file: From df5b7c7d0d9ed26bb964ed5bb5e42c62e1ed2270 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 30 Mar 2016 21:31:17 -0400 Subject: [PATCH 1173/6300] specify bandwidth for floodfill --- Config.cpp | 2 +- Daemon.cpp | 39 ++++++++++++++++++++++++++++++++------- RouterContext.cpp | 17 ++++++++--------- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/Config.cpp b/Config.cpp index 73124ae4..8d42895a 100644 --- a/Config.cpp +++ b/Config.cpp @@ -123,7 +123,7 @@ namespace config { ("service", value()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'") ("notransit", value()->zero_tokens()->default_value(false), "Router will not accept transit tunnels at startup") ("floodfill", value()->zero_tokens()->default_value(false), "Router will be floodfill") - ("bandwidth", value()->default_value("256"), "Bandwidth limit: integer in kbps or letters: L (32), O (256), P (2048), X (>9000)") + ("bandwidth", value()->default_value(""), "Bandwidth limit: integer in kbps or letters: L (32), O (256), P (2048), X (>9000)") #ifdef _WIN32 ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") ("insomnia", value()->zero_tokens()->default_value(false), "Prevent system from sleeping") diff --git a/Daemon.cpp b/Daemon.cpp index 13cdc075..f15fe3e3 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -151,13 +151,38 @@ namespace i2p /* this section also honors 'floodfill' flag, if set above */ std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); - if (bandwidth[0] > 'K' && bandwidth[0] < 'Z') { - i2p::context.SetBandwidth (bandwidth[0]); - LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps"); - } else if (bandwidth[0] >= '0' && bandwidth[0] <= '9') { - i2p::context.SetBandwidth (std::atoi(bandwidth.c_str())); - LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), " KBps"); - } + if (bandwidth.length () > 0) + { + if (bandwidth[0] >= 'K' && bandwidth[0] <= 'X') + { + i2p::context.SetBandwidth (bandwidth[0]); + LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps"); + } + else + { + auto value = std::atoi(bandwidth.c_str()); + if (value > 0) + { + i2p::context.SetBandwidth (value); + LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), " KBps"); + } + else + { + LogPrint(eLogInfo, "Daemon: unexpected bandwidth ", bandwidth, ". Set to 'low'"); + i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); + } + } + } + else if (isFloodfill) + { + LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'"); + i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1); + } + else + { + LogPrint(eLogInfo, "Daemon: bandwidth set to 'low'"); + i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); + } std::string family; i2p::config::GetOption("family", family); i2p::context.SetFamily (family); diff --git a/RouterContext.cpp b/RouterContext.cpp index 9a1749d0..e50681d9 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -170,26 +170,24 @@ namespace i2p uint16_t limit = 0; enum { low, high, extra } type = high; /* detect parameters */ - switch (L) { + switch (L) + { case i2p::data::CAPS_FLAG_LOW_BANDWIDTH1 : limit = 12; type = low; break; case i2p::data::CAPS_FLAG_LOW_BANDWIDTH2 : limit = 48; type = low; break; case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH1 : limit = 64; type = high; break; case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH2 : limit = 128; type = high; break; - default: case i2p::data::CAPS_FLAG_HIGH_BANDWIDTH3 : limit = 256; type = high; break; case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = 2048; type = extra; break; case i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH2 : limit = 9999; type = extra; break; - } - /* floodfill requires 'extra' bandwidth */ - if (m_IsFloodfill && limit < 2048) { - limit = 2048, type = extra; - LogPrint(eLogInfo, "Daemon: bandwidth set to 'extra' due to floodfill"); + default: + limit = 48; type = low; } /* update caps & flags in RI */ auto caps = m_RouterInfo.GetCaps (); caps &= ~i2p::data::RouterInfo::eHighBandwidth; caps &= ~i2p::data::RouterInfo::eExtraBandwidth; - switch (type) { + switch (type) + { case low : /* not set */; break; case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break; case extra : caps |= i2p::data::RouterInfo::eExtraBandwidth; break; @@ -199,7 +197,8 @@ namespace i2p m_BandwidthLimit = limit; } - void RouterContext::SetBandwidth (int limit) { + void RouterContext::SetBandwidth (int limit) + { if (limit > 2000) { SetBandwidth('X'); } else if (limit > 256) { SetBandwidth('P'); } else if (limit > 128) { SetBandwidth('O'); } From e5fac08d1d509998aa84785ecdd05cdf9007c02c Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 31 Mar 2016 09:12:21 -0400 Subject: [PATCH 1174/6300] release 2.6.0 --- version.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/version.h b/version.h index e7414de3..c5177d30 100644 --- a/version.h +++ b/version.h @@ -1,14 +1,14 @@ #ifndef _VERSION_H_ #define _VERSION_H_ -#define CODENAME "Purple" +#define CODENAME "Bora Bora" #define STRINGIZE(x) #x #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 5 -#define I2PD_VERSION_MICRO 1 +#define I2PD_VERSION_MINOR 6 +#define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) #define VERSION I2PD_VERSION From 751b95d4af16f8b70033b365e13fc48df03f0947 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 1 Apr 2016 11:36:56 -0400 Subject: [PATCH 1175/6300] add locking to SAM when adding/removing sockets --- HTTPServer.cpp | 2 +- SAM.cpp | 32 ++++++++++++++++++-------------- SAM.h | 25 ++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index b6ec3522..f7efcba9 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -749,7 +749,7 @@ namespace util s << "&" << HTTP_PARAM_BASE32_ADDRESS << "=" << ident.ToBase32 () << ">"; s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; s << "Streams:
\r\n"; - for (auto it: session->sockets) + for (auto it: session->ListSockets()) { switch (it->GetSocketType ()) { diff --git a/SAM.cpp b/SAM.cpp index 7b493eb3..ecdd513d 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -47,16 +47,19 @@ namespace client break; case eSAMSocketTypeStream: { - if (m_Session) - m_Session->sockets.remove (shared_from_this ()); + if (m_Session) { + m_Session->DelSocket (shared_from_this ()); + m_Session = nullptr; + } break; } case eSAMSocketTypeAcceptor: { if (m_Session) { - m_Session->sockets.remove (shared_from_this ()); + m_Session->DelSocket (shared_from_this ()); m_Session->localDestination->StopAcceptingStreams (); + m_Session = nullptr; } break; } @@ -64,7 +67,7 @@ namespace client ; } m_SocketType = eSAMSocketTypeTerminated; - m_Socket.close (); + if (m_Socket.is_open()) m_Socket.close (); } void SAMSocket::ReceiveHandshake () @@ -369,7 +372,7 @@ namespace client void SAMSocket::Connect (std::shared_ptr remote) { m_SocketType = eSAMSocketTypeStream; - m_Session->sockets.push_back (shared_from_this ()); + m_Session->AddSocket (shared_from_this ()); m_Stream = m_Session->localDestination->CreateStream (remote); m_Stream->Send ((uint8_t *)m_Buffer, 0); // connect I2PReceive (); @@ -402,7 +405,7 @@ namespace client if (!m_Session->localDestination->IsAcceptingStreams ()) { m_SocketType = eSAMSocketTypeAcceptor; - m_Session->sockets.push_back (shared_from_this ()); + m_Session->AddSocket (shared_from_this ()); m_Session->localDestination->AcceptStreams (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1)); SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } @@ -676,19 +679,20 @@ namespace client SAMSession::~SAMSession () { - for (auto it: sockets) - it->SetSocketType (eSAMSocketTypeTerminated); + CloseStreams(); i2p::client::context.DeleteLocalDestination (localDestination); } void SAMSession::CloseStreams () { - for (auto it: sockets) - { - it->CloseStream (); - it->SetSocketType (eSAMSocketTypeTerminated); - } - sockets.clear (); + { + std::lock_guard lock(m_SocketsMutex); + for (auto sock : m_Sockets) { + sock->CloseStream(); + } + } + // XXX: should this be done inside locked parts? + m_Sockets.clear(); } SAMBridge::SAMBridge (const std::string& address, int port): diff --git a/SAM.h b/SAM.h index 07142c8c..4cdea686 100644 --- a/SAM.h +++ b/SAM.h @@ -134,7 +134,30 @@ namespace client struct SAMSession { std::shared_ptr localDestination; - std::list > sockets; + std::list > m_Sockets; + std::mutex m_SocketsMutex; + + /** safely add a socket to this session */ + void AddSocket(std::shared_ptr sock) { + std::lock_guard lock(m_SocketsMutex); + m_Sockets.push_back(sock); + } + + /** safely remove a socket from this session */ + void DelSocket(std::shared_ptr sock) { + std::lock_guard lock(m_SocketsMutex); + m_Sockets.remove(sock); + } + + /** get a list holding a copy of all sam sockets from this session */ + std::list > ListSockets() { + std::list > l; + { + std::lock_guard lock(m_SocketsMutex); + for( auto & sock : m_Sockets ) l.push_back(sock); + } + return l; + } SAMSession (std::shared_ptr dest); ~SAMSession (); From 5c877de2c2f954c27471092fff73443a1c9af9d4 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 1 Apr 2016 12:51:34 -0400 Subject: [PATCH 1176/6300] check if hosts is incomplete --- AddressBook.cpp | 18 ++++++++++++------ AddressBook.h | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index ad2d0c32..766d8c95 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -343,10 +343,11 @@ namespace client } } - void AddressBook::LoadHostsFromStream (std::istream& f) + bool AddressBook::LoadHostsFromStream (std::istream& f) { std::unique_lock l(m_AddressBookMutex); int numAddresses = 0; + bool incomplete = false; std::string s; while (!f.eof ()) { @@ -370,15 +371,21 @@ namespace client numAddresses++; } else + { LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name); - } + incomplete = f.eof (); + } + } + else + incomplete = f.eof (); } LogPrint (eLogInfo, "Addressbook: ", numAddresses, " addresses processed"); if (numAddresses > 0) { - m_IsLoaded = true; + if (!incomplete) m_IsLoaded = true; m_Storage->Save (m_Addresses); } + return !incomplete; } void AddressBook::LoadSubscriptions () @@ -776,13 +783,12 @@ namespace client i2p::data::GzipInflator inflator; inflator.Inflate (s, uncompressed); if (!uncompressed.fail ()) - m_Book.LoadHostsFromStream (uncompressed); + return m_Book.LoadHostsFromStream (uncompressed); else return false; } else - m_Book.LoadHostsFromStream (s); - return true; + return m_Book.LoadHostsFromStream (s); } AddressResolver::AddressResolver (std::shared_ptr destination): diff --git a/AddressBook.h b/AddressBook.h index 56f8145c..61b82f4b 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -66,7 +66,7 @@ namespace client void InsertAddress (const std::string& address, const std::string& base64); // for jump service void InsertAddress (std::shared_ptr address); - void LoadHostsFromStream (std::istream& f); + bool LoadHostsFromStream (std::istream& f); 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); } From 924f2815365ca02cd37c37d791582461e1a7238e Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 2 Apr 2016 08:05:14 -0400 Subject: [PATCH 1177/6300] * Don't set m_Session to nullptr in SAMSocket::Terminate * check for null localDestination in SAMSocket::Terminate --- SAM.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index ecdd513d..bcb4623a 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -49,7 +49,6 @@ namespace client { if (m_Session) { m_Session->DelSocket (shared_from_this ()); - m_Session = nullptr; } break; } @@ -58,8 +57,8 @@ namespace client if (m_Session) { m_Session->DelSocket (shared_from_this ()); - m_Session->localDestination->StopAcceptingStreams (); - m_Session = nullptr; + if (m_Session->localDestination) + m_Session->localDestination->StopAcceptingStreams (); } break; } From 0bf2abaa4c4c40775a9b8d3f4b852158cc8f840e Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 2 Apr 2016 08:57:35 -0400 Subject: [PATCH 1178/6300] fixed race condition at startup --- Signature.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Signature.cpp b/Signature.cpp index 13612327..11fc8600 100644 --- a/Signature.cpp +++ b/Signature.cpp @@ -435,8 +435,13 @@ namespace crypto std::unique_ptr& GetEd25519 () { if (!g_Ed25519) - g_Ed25519.reset (new Ed25519()); - + { + auto c = new Ed25519(); + if (!g_Ed25519) // make sure it was not created already + g_Ed25519.reset (c); + else + delete c; + } return g_Ed25519; } From 97afa502c52fde5c5b265884ae916050c2dd249d Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 2 Apr 2016 22:16:49 -0400 Subject: [PATCH 1179/6300] shard_ptr for SAMSession --- SAM.cpp | 36 ++++++++++++++++++++---------------- SAM.h | 8 ++++---- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index ecdd513d..dea3614e 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -47,19 +47,16 @@ namespace client break; case eSAMSocketTypeStream: { - if (m_Session) { + if (m_Session) m_Session->DelSocket (shared_from_this ()); - m_Session = nullptr; - } break; } case eSAMSocketTypeAcceptor: { if (m_Session) - { + { m_Session->DelSocket (shared_from_this ()); - m_Session->localDestination->StopAcceptingStreams (); - m_Session = nullptr; + m_Session->localDestination->StopAcceptingStreams (); } break; } @@ -68,6 +65,7 @@ namespace client } m_SocketType = eSAMSocketTypeTerminated; if (m_Socket.is_open()) m_Socket.close (); + m_Session = nullptr; } void SAMSocket::ReceiveHandshake () @@ -721,7 +719,7 @@ namespace client m_IsRunning = false; m_Acceptor.cancel (); for (auto it: m_Sessions) - delete it.second; + it.second->CloseStreams (); m_Sessions.clear (); m_Service.stop (); if (m_Thread) @@ -775,7 +773,7 @@ namespace client Accept (); } - SAMSession * SAMBridge::CreateSession (const std::string& id, const std::string& destination, + std::shared_ptr SAMBridge::CreateSession (const std::string& id, const std::string& destination, const std::map * params) { std::shared_ptr localDestination = nullptr; @@ -800,8 +798,9 @@ namespace client } if (localDestination) { + auto session = std::make_shared(localDestination); std::unique_lock l(m_SessionsMutex); - auto ret = m_Sessions.insert (std::pair(id, new SAMSession (localDestination))); + auto ret = m_Sessions.insert (std::make_pair(id, session)); if (!ret.second) LogPrint (eLogWarning, "SAM: Session ", id, " already exists"); return ret.first->second; @@ -811,19 +810,24 @@ namespace client void SAMBridge::CloseSession (const std::string& id) { - std::unique_lock l(m_SessionsMutex); - auto it = m_Sessions.find (id); - if (it != m_Sessions.end ()) + std::shared_ptr session; { - auto session = it->second; + std::unique_lock l(m_SessionsMutex); + auto it = m_Sessions.find (id); + if (it != m_Sessions.end ()) + { + session = it->second; + m_Sessions.erase (it); + } + } + if (session) + { session->localDestination->StopAcceptingStreams (); session->CloseStreams (); - m_Sessions.erase (it); - delete session; } } - SAMSession * SAMBridge::FindSession (const std::string& id) const + std::shared_ptr SAMBridge::FindSession (const std::string& id) const { std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (id); diff --git a/SAM.h b/SAM.h index 4cdea686..ef36f285 100644 --- a/SAM.h +++ b/SAM.h @@ -128,7 +128,7 @@ namespace client std::string m_ID; // nickname bool m_IsSilent; std::shared_ptr m_Stream; - SAMSession * m_Session; + std::shared_ptr m_Session; }; struct SAMSession @@ -176,10 +176,10 @@ namespace client void Stop (); boost::asio::io_service& GetService () { return m_Service; }; - SAMSession * CreateSession (const std::string& id, const std::string& destination, // empty string means transient + std::shared_ptr CreateSession (const std::string& id, const std::string& destination, // empty string means transient const std::map * params); void CloseSession (const std::string& id); - SAMSession * FindSession (const std::string& id) const; + std::shared_ptr FindSession (const std::string& id) const; private: @@ -200,7 +200,7 @@ namespace client boost::asio::ip::udp::endpoint m_DatagramEndpoint, m_SenderEndpoint; boost::asio::ip::udp::socket m_DatagramSocket; mutable std::mutex m_SessionsMutex; - std::map m_Sessions; + std::map > m_Sessions; uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1]; public: From 941f30d1ea17c8bf63b856ee8d91e949804d11d7 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 4 Apr 2016 22:17:04 -0400 Subject: [PATCH 1180/6300] show streams from all streaming destinations --- Destination.cpp | 14 ++++++++++++++ Destination.h | 1 + HTTPServer.cpp | 22 +++++++++++----------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index c53c4ee6..5893caff 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -780,5 +780,19 @@ namespace client } LogPrint(eLogError, "Destinations: Can't save keys to ", path); } + + std::vector > ClientDestination::GetAllStreams () const + { + std::vector > ret; + if (m_StreamingDestination) + { + for (auto& it: m_StreamingDestination->GetStreams ()) + ret.push_back (it.second); + } + for (auto& it: m_StreamingDestinationsByPorts) + for (auto& it1: it.second->GetStreams ()) + ret.push_back (it1.second); + return ret; + } } } diff --git a/Destination.h b/Destination.h index 44cb7424..3011b4cb 100644 --- a/Destination.h +++ b/Destination.h @@ -159,6 +159,7 @@ namespace client // for HTTP only int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); }; + std::vector > GetAllStreams () const; }; } } diff --git a/HTTPServer.cpp b/HTTPServer.cpp index f7efcba9..8db9d330 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -609,19 +609,19 @@ namespace util s << "
"; s << ""; - for (auto it: dest->GetStreamingDestination ()->GetStreams ()) + for (auto it: dest->GetAllStreams ()) { s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; s << "
\r\n" << std::endl; } } From f412f4ca883dd69839e3df8299b937774c4d6644 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 5 Apr 2016 00:00:00 +0000 Subject: [PATCH 1181/6300] * use commented i2pd.conf as default --- debian/i2pd.conf | 19 ------------------- debian/i2pd.install | 2 +- docs/i2pd.conf | 4 ++-- 3 files changed, 3 insertions(+), 22 deletions(-) delete mode 100644 debian/i2pd.conf diff --git a/debian/i2pd.conf b/debian/i2pd.conf deleted file mode 100644 index 4a518916..00000000 --- a/debian/i2pd.conf +++ /dev/null @@ -1,19 +0,0 @@ -ipv6 - -[httpproxy] -address = 127.0.0.1 -port = 4444 - -# other services (disabled by default) -# -#[sam] -#address = 127.0.0.1 -#port = 7656 -# -#[bob] -#address = 127.0.0.1 -#port = 2827 -# -#[i2pcontrol] -#address = 127.0.0.1 -#port = 7650 diff --git a/debian/i2pd.install b/debian/i2pd.install index f4e7e4f1..2e3c93cd 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,5 +1,5 @@ i2pd usr/sbin/ -debian/i2pd.conf etc/i2pd/ +docs/i2pd.conf etc/i2pd/ debian/tunnels.conf etc/i2pd/ debian/subscriptions.txt etc/i2pd/ contrib/certificates/ usr/share/i2pd/ diff --git a/docs/i2pd.conf b/docs/i2pd.conf index e85eaa17..d4ea226a 100644 --- a/docs/i2pd.conf +++ b/docs/i2pd.conf @@ -69,8 +69,8 @@ port = 7070 ## Uncomment and set to 'false' to disable HTTP Proxy # enabled = true ## Address and port service will listen on -# address = 127.0.0.1 -# port = 4444 +address = 127.0.0.1 +port = 4444 ## Optional keys file for proxy local destination # keys = http-proxy-keys.dat From cb8333a48f0441bf553ce70b8b8436d0a51659ff Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 5 Apr 2016 00:00:00 +0000 Subject: [PATCH 1182/6300] * update manpage --- debian/i2pd.1 | 107 +++++++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/debian/i2pd.1 b/debian/i2pd.1 index 2a1e7f88..f61e243e 100644 --- a/debian/i2pd.1 +++ b/debian/i2pd.1 @@ -5,7 +5,7 @@ i2pd \- Load-balanced unspoofable packet switching network .SH SYNOPSIS .B i2pd -[\fIOPTION1\fR) [\fIOPTION2\fR]... +[\fIOPTION1\fR] [\fIOPTION2\fR]... .SH DESCRIPTION i2pd @@ -18,59 +18,58 @@ network is both distributed and dynamic, with no trusted parties. Any of the configuration options below can be used in the \fBDAEMON_ARGS\fR variable in \fI/etc/default/i2pd\fR. .BR .TP -\fB\-\-host=\fR -The external IP (deprecated) -.TP -\fB\-\-port=\fR -The external port to listen on -.TP -\fB\-\-httpport=\fR -The HTTP port to listen on -.TP -\fB\-\-log=\fR[\fI1\fR|\fI0\fR] -.br -Enable of disable logging to a file. \fI1\fR for yes, \fI0\fR for no. (default: \fI0\fR, off) -.TP -\fB\-\-daemon=\fR[\fI1\fR|\fI0\fR] -Enable or disable daemon mode. Daemon mode is enabled with \fI1\fR and disabled with \fI0\fR. (default: \fI0\fR, off) -.TP -\fB\-\-service=\fR[\fI1\fR|\fI0\fR] -If enabled, system folders (\fB/var/run/i2pd.pid\fR, \fB/var/log/i2pd.log\fR, \fB/var/lib/i2pd\fR) will be used. If off, \fB$HOME/.i2pd\fR will be used instead. (default: \fI0\fR, off). -.TP -\fB\-\-unreachable=\fR[\fI1\fR|\fI0\fR] -\fI1\fR if router is declared as unreachable and works through introducers. (default: \fI0\fR, off) -.TP -\fB\-\-v6=\fR[\fI1\fR|\fI0\fR] -\fI1\fR if \fBi2pd\fR should communicate via IPv6. (default: \fI0\fR, off) -.TP -\fB\-\-floodfill=\fR[\fI1\fR|\fI0\fR] -\fI1\fR if \fBi2pd\fR should become a floodfill. (default: \fI0\fR, off) -.TP -\fB\-\-bandwidth=\fR[\fI1\fR|\fI0\fR] -\fIL\fR if \fBi2pd\fR should be limited to 32KiB/s. Enabling floodfill will automatically set this to \fI0\fR (default: \fI0\fR, no limit) -.TP -\fB\-\-httpproxyport=\fR -The local port for the HTTP Proxy to listen on (default: \fI4446\fR) -.TP -\fB\-\-socksproxyport=\fR -The local port for the SOCKS proxy to listen on (default: \fI4447\fR) -.TP -\fB\-\-proxykeys=\fR -An optional keys file for tunnel local destination (both HTTP and SOCKS) -.TP -\fB\-\-samport=\fR -Port of SAM bridge. Usually \fI7656\fR. SAM will not be enabled if this is not set. (default: unset) -.TP -\fB\-\-bobport=\fR -Port of BOB command channel. Usually \fI2827\fR. BOB will not be enabled if this is not set. (default: unset) -.TP -\fB\-\-i2pcontrolport=\fR -Port of I2P control service. Usually \fI7650\fR. I2PControl will not be enabled if this is not set. (default: unset) +\fB\-\-help\fR +Show available options. .TP \fB\-\-conf=\fR Config file (default: \fI~/.i2pd/i2pd.conf\fR or \fI/var/lib/i2pd/i2pd.conf\fR) +.BR This parameter will be silently ignored if the specified config file does not exist. Options specified on the command line take precedence over those in the config file. +.TP +\fB\-\-tunconf=\fR +Tunnels config file (default: \fI~/.i2pd/tunnels.conf\fR or \fI/var/lib/i2pd/tunnels.conf\fR) +.TP +\fB\-\-pidfile=\fR +Where to write pidfile (don\'t write by default) +.TP +\fB\-\-log=\fR +Logs destination: \fIstdout\fR, \fIfile\fR, \fIsyslog\fR (\fIstdout\fR if not set, \fIfile\fR - otherwise, for compatibility) +.TP +\fB\-\-loglevel=\fR +Log messages above this level (\fIdebug\fR, \fBinfo\fR, \fIwarn\fR, \fIerror\fR) +.TP +\fB\-\-datadir=\fR +Path to storage of i2pd data (RI, keys, peer profiles, ...) +.TP +\fB\-\-host=\fR +The external IP address +.TP +\fB\-\-port=\fR +The port to listen on for incoming connections +.TP +\fB\-\-daemon\fR +Router will go to background after start +.TP +\fB\-\-service\fR +Router will use system folders like \fI/var/lib/i2pd\fR +.TP +\fB\-\-ipv6\fR +Enable communication through ipv6. false by default +.TP +\fB\-\-notransit\fR +Router will not accept transit tunnels at startup +.TP +\fB\-\-floodfill\fR +Router will be floodfill +.TP +\fB\-\-bandwidth=\fR +Bandwidth limit: integer in KBps or letter aliases: \fIL (32KBps)\fR, O (256), P (2048), X (>9000) +.TP +\fB\-\-family=\fR +Name of a family, router belongs to. +.PP +See service-specific parameters in page \fIdocs/configuration.md\fR or in example config file \fIdocs/i2pd.conf\fR .SH FILES .PP @@ -82,10 +81,10 @@ i2pd configuration files (when running as a system service) .PP /var/lib/i2pd/ .RS 4 -i2pd profile directory (when running as a system service, see \fB\-\-service=\fR above) +i2pd profile directory (when running as a system service, see \fB\-\-service\fR above) .RE .PP -$HOME/.i2pd +$HOME/.i2pd/ .RS 4 i2pd profile directory (when running as a normal user) .RE @@ -95,7 +94,9 @@ i2pd profile directory (when running as a normal user) default I2P hosts file .SH AUTHOR This manual page was written by kytv for the Debian system (but may be used by others). -.BR +.PP +Updated by hagen in 2016. +.PP Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License, Version 2 or any later version published by the Free Software Foundation .BR -On Debian systems, the complete text of the GNU General Public License can be found in /usr/share/common-licenses/GPL +On Debian systems, the complete text of the GNU General Public License can be found in \fI/usr/share/common-licenses/GPL\fR From b5875f3a0a9ab0efe64e46e5f1e3b4ade2c89e2c Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 5 Apr 2016 00:00:00 +0000 Subject: [PATCH 1183/6300] * update year in copyrights --- debian/copyright | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/debian/copyright b/debian/copyright index 117fcffd..606d059b 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,9 +3,9 @@ Upstream-Name: i2pd Source: https://github.com/PurpleI2P Files: * -Copyright: 2013-2015 PurpleI2P +Copyright: 2013-2016 PurpleI2P License: BSD-3-clause - Copyright (c) 2013-2015, The PurpleI2P Project + Copyright (c) 2013-2016, The PurpleI2P Project . All rights reserved. . @@ -34,7 +34,7 @@ License: BSD-3-clause SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Files: debian/* -Copyright: 2014-2015 hagen +Copyright: 2014-2016 hagen 2013-2015 Kill Your TV License: GPL-2.0+ This package is free software; you can redistribute it and/or modify From cc55335a8dc982dad2f951bcf5e025f9b76a5de2 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 5 Apr 2016 00:00:00 +0000 Subject: [PATCH 1184/6300] * docs/configuration.md --- docs/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index f3e9f98c..ac5c4684 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -16,8 +16,8 @@ If you are upgrading your very old router (< 2.3.0) see also [this](config_opts_ * --logfile= - Path to logfile (default - autodetect) * --loglevel= - Log messages above this level (debug, *info, warn, error) * --datadir= - Path to storage of i2pd data (RI, keys, peer profiles, ...) -* --host= - The external IP -* --port= - The port to listen on +* --host= - Router external IP for incoming connections +* --port= - Port to listen for incoming connections (default: auto) * --daemon - Router will go to background after start * --service - Router will use system folders like '/var/lib/i2pd' * --ipv6 - Enable communication through ipv6. false by default From f63dd75f0876eb32c021041465108c31acda4748 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 5 Apr 2016 00:00:00 +0000 Subject: [PATCH 1185/6300] * set bw limits thru i2pcontrol (#461) (experimental) --- I2PControl.cpp | 20 ++++++++++++++++++++ I2PControl.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/I2PControl.cpp b/I2PControl.cpp index 907fa2fd..1ef56c2d 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -83,6 +83,10 @@ namespace client m_RouterManagerHandlers["Reseed"] = &I2PControlService::ReseedHandler; m_RouterManagerHandlers["Shutdown"] = &I2PControlService::ShutdownHandler; m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler; + + // NetworkSetting + m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlService::InboundBandwidthLimit; + m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlService::OutboundBandwidthLimit; } I2PControlService::~I2PControlService () @@ -496,6 +500,22 @@ namespace client } } + void I2PControlService::InboundBandwidthLimit (const std::string& value, std::ostringstream& results) + { + if (value != "null") + i2p::context.SetBandwidth (std::atoi(value.c_str())); + int bw = i2p::context.GetBandwidthLimit(); + InsertParam (results, "i2p.router.net.bw.in", bw); + } + + void I2PControlService::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results) + { + if (value != "null") + i2p::context.SetBandwidth (std::atoi(value.c_str())); + int bw = i2p::context.GetBandwidthLimit(); + InsertParam (results, "i2p.router.net.bw.out", bw); + } + // certificate void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path) { diff --git a/I2PControl.h b/I2PControl.h index 714d3aa5..728c9925 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -94,6 +94,8 @@ namespace client // NetworkSetting typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); + void InboundBandwidthLimit (const std::string& value, std::ostringstream& results); + void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results); private: From 5f73f09836e04d83fd126cbf4c6b74925b7e0a71 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 5 Apr 2016 00:00:00 +0000 Subject: [PATCH 1186/6300] * RouterInfo::SaveToFile() now returns bool --- RouterInfo.cpp | 21 +++++++++++---------- RouterInfo.h | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index dfd54059..8b12f591 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -514,19 +514,20 @@ namespace data m_BufferLen += privateKeys.GetPublic ()->GetSignatureLen (); } - void RouterInfo::SaveToFile (const std::string& fullPath) + bool RouterInfo::SaveToFile (const std::string& fullPath) { m_FullPath = fullPath; - if (m_Buffer) - { - std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); - if (f.is_open ()) - f.write ((char *)m_Buffer, m_BufferLen); - else - LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath); - } - else + if (!m_Buffer) { LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL"); + return false; + } + std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); + if (!f.is_open ()) { + LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath); + return false; + } + f.write ((char *)m_Buffer, m_BufferLen); + return true; } size_t RouterInfo::ReadString (char * str, std::istream& s) diff --git a/RouterInfo.h b/RouterInfo.h index 7648c6ba..c9881dd2 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -161,7 +161,7 @@ namespace data bool IsUpdated () const { return m_IsUpdated; }; void SetUpdated (bool updated) { m_IsUpdated = updated; }; - void SaveToFile (const std::string& fullPath); + bool SaveToFile (const std::string& fullPath); std::shared_ptr GetProfile () const; void SaveProfile () { if (m_Profile) m_Profile->Save (); }; From f48a7df80fc413e543822ddf20164488cd3d96e3 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 Apr 2016 10:22:32 -0400 Subject: [PATCH 1187/6300] recreate router.info if missing or malformed --- RouterContext.cpp | 24 ++++++++++++++++-------- RouterInfo.cpp | 2 ++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index e50681d9..f35d8426 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -356,16 +356,24 @@ namespace i2p delete[] buf; } - i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO)); // TODO m_RouterInfo.SetRouterIdentity (GetIdentity ()); - m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); - m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION); - m_RouterInfo.SetProperty ("router.version", I2P_VERSION); + i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO)); + if (!routerInfo.IsUnreachable ()) // router.info looks good + { + m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); + m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION); + m_RouterInfo.SetProperty ("router.version", I2P_VERSION); + + // Migration to 0.9.24. TODO: remove later + m_RouterInfo.DeleteProperty ("coreVersion"); + m_RouterInfo.DeleteProperty ("stat_uptime"); + } + else + { + LogPrint (eLogError, ROUTER_INFO, " is malformed. Creating new"); + NewRouterInfo (); + } - // Migration to 0.9.24. TODO: remove later - m_RouterInfo.DeleteProperty ("coreVersion"); - m_RouterInfo.DeleteProperty ("stat_uptime"); - if (IsUnreachable ()) SetReachable (); // we assume reachable until we discover firewall through peer tests diff --git a/RouterInfo.cpp b/RouterInfo.cpp index dfd54059..9f590d74 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -104,6 +104,8 @@ namespace data { if (LoadFile ()) ReadFromBuffer (false); + else + m_IsUnreachable = true; } void RouterInfo::ReadFromBuffer (bool verifySignature) From 405aa906c5cffb3d7aba8da58ae7a28473806d6c Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 Apr 2016 13:18:25 -0400 Subject: [PATCH 1188/6300] short exponent for non-x64 --- Crypto.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Crypto.cpp b/Crypto.cpp index 8416a337..b42dafa6 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -200,8 +200,11 @@ namespace crypto ctx = BN_CTX_new (); // select random k BIGNUM * k = BN_new (); - BN_rand_range (k, elgp); - if (BN_is_zero (k)) BN_one (k); +#if defined(__x86_64__) + BN_rand (k, 2048, -1, 1); // full exponent for x64 +#else + BN_rand (k, 226, -1, 1); // short exponent of 226 bits +#endif // caulculate a a = BN_new (); BN_mod_exp (a, elgg, k, elgp, ctx); From 86572265945b6ef2ea2d5bb8e60765d47cf663b6 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 Apr 2016 15:49:46 -0400 Subject: [PATCH 1189/6300] use 226 bits private keys for non-x64 --- Crypto.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Crypto.cpp b/Crypto.cpp index b42dafa6..1dc8a2e5 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -146,6 +146,10 @@ namespace crypto } // DH/ElGamal + + const int ELGAMAL_SHORT_EXPONENT_NUM_BITS = 226; + const int ELGAMAL_FULL_EXPONENT_NUM_BITS = 2048; + #define elgp GetCryptoConstants ().elgp #define elgg GetCryptoConstants ().elgg @@ -169,6 +173,10 @@ namespace crypto { if (m_DH->priv_key) { BN_free (m_DH->priv_key); m_DH->priv_key = NULL; }; if (m_DH->pub_key) { BN_free (m_DH->pub_key); m_DH->pub_key = NULL; }; +#if !defined(__x86_64__) // use short exponent for non x64 + m_DH->priv_key = BN_new (); + BN_rand (m_DH->priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1); +#endif DH_generate_key (m_DH); if (priv) bn2buf (m_DH->priv_key, priv, 256); if (pub) bn2buf (m_DH->pub_key, pub, 256); @@ -201,9 +209,9 @@ namespace crypto // select random k BIGNUM * k = BN_new (); #if defined(__x86_64__) - BN_rand (k, 2048, -1, 1); // full exponent for x64 + BN_rand (k, ELGAMAL_FULL_EXPONENT_NUM_BITS, -1, 1); // full exponent for x64 #else - BN_rand (k, 226, -1, 1); // short exponent of 226 bits + BN_rand (k, ELGAMAL_SHORT_EXPONENT_NUM_BITS, -1, 1); // short exponent of 226 bits #endif // caulculate a a = BN_new (); @@ -282,6 +290,14 @@ namespace crypto { #if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER) RAND_bytes (priv, 256); +#else + // lower 226 bits (28 bytes and 2 bits) only. short exponent + auto numBytes = (ELGAMAL_SHORT_EXPONENT_NUM_BITS)/8 + 1; // 29 + auto numZeroBytes = 256 - numBytes; + RAND_bytes (priv + numZeroBytes, numBytes); + memset (priv, 0, numZeroBytes); + priv[numZeroBytes] &= 0x04; +#endif BN_CTX * ctx = BN_CTX_new (); BIGNUM * p = BN_new (); BN_bin2bn (priv, 256, p); @@ -289,11 +305,6 @@ namespace crypto bn2buf (p, pub, 256); BN_free (p); BN_CTX_free (ctx); -#else - DHKeys dh; - dh.GenerateKeys (priv, pub); - -#endif } // HMAC From 380c7b7720bb00aaa6670541462660fb0323b568 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 Apr 2016 16:11:18 -0400 Subject: [PATCH 1190/6300] use 226 bits private keys for non-x64 --- Crypto.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Crypto.cpp b/Crypto.cpp index 1dc8a2e5..0ec0f020 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -296,7 +296,7 @@ namespace crypto auto numZeroBytes = 256 - numBytes; RAND_bytes (priv + numZeroBytes, numBytes); memset (priv, 0, numZeroBytes); - priv[numZeroBytes] &= 0x04; + priv[numZeroBytes] &= 0x03; #endif BN_CTX * ctx = BN_CTX_new (); BIGNUM * p = BN_new (); From afe2935c9db67d62b1dd6e2694db9ac8e1f7fa52 Mon Sep 17 00:00:00 2001 From: xcps Date: Wed, 6 Apr 2016 16:33:23 -0400 Subject: [PATCH 1191/6300] webconsole update --- HTTPServer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 8db9d330..06fabc31 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -777,20 +777,20 @@ namespace util s << "Client Tunnels:
\r\n
\r\n"; for (auto& it: i2p::client::context.GetClientTunnels ()) { - s << it.second->GetName () << " ⇠"; auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << ""; + s << it.second->GetName () << " ⇠"; s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; + s << "
\r\n"<< std::endl; } s << "
\r\nServer Tunnels:
\r\n
\r\n"; for (auto& it: i2p::client::context.GetServerTunnels ()) { - s << it.second->GetName () << " ⇒ "; auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << ""; + s << it.second->GetName () << " ⇒ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << ":" << it.second->GetLocalPort (); s << "
\r\n"<< std::endl; From 2ebb2d8f0e7ec783e188645a96c4d252c60b671e Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 Apr 2016 21:02:58 -0400 Subject: [PATCH 1192/6300] fixed race condition --- Identity.cpp | 26 +++++++++++++++++--------- Identity.h | 1 + 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index 0ca9567a..f221dd04 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -311,18 +311,18 @@ namespace data switch (keyType) { case SIGNING_KEY_TYPE_DSA_SHA1: - m_Verifier.reset (new i2p::crypto::DSAVerifier (m_StandardIdentity.signingKey)); + UpdateVerifier (new i2p::crypto::DSAVerifier (m_StandardIdentity.signingKey)); break; case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: { size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64 - m_Verifier.reset (new i2p::crypto::ECDSAP256Verifier (m_StandardIdentity.signingKey + padding)); + UpdateVerifier (new i2p::crypto::ECDSAP256Verifier (m_StandardIdentity.signingKey + padding)); break; } case SIGNING_KEY_TYPE_ECDSA_SHA384_P384: { size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96 - m_Verifier.reset (new i2p::crypto::ECDSAP384Verifier (m_StandardIdentity.signingKey + padding)); + UpdateVerifier (new i2p::crypto::ECDSAP384Verifier (m_StandardIdentity.signingKey + padding)); break; } case SIGNING_KEY_TYPE_ECDSA_SHA512_P521: @@ -331,7 +331,7 @@ namespace data memcpy (signingKey, m_StandardIdentity.signingKey, 128); size_t excessLen = i2p::crypto::ECDSAP521_KEY_LENGTH - 128; // 4 = 132- 128 memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - m_Verifier.reset (new i2p::crypto::ECDSAP521Verifier (signingKey)); + UpdateVerifier (new i2p::crypto::ECDSAP521Verifier (signingKey)); break; } case SIGNING_KEY_TYPE_RSA_SHA256_2048: @@ -340,7 +340,7 @@ namespace data memcpy (signingKey, m_StandardIdentity.signingKey, 128); size_t excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256- 128 memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - m_Verifier.reset (new i2p::crypto:: RSASHA2562048Verifier (signingKey)); + UpdateVerifier (new i2p::crypto:: RSASHA2562048Verifier (signingKey)); break; } case SIGNING_KEY_TYPE_RSA_SHA384_3072: @@ -349,7 +349,7 @@ namespace data memcpy (signingKey, m_StandardIdentity.signingKey, 128); size_t excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384- 128 memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - m_Verifier.reset (new i2p::crypto:: RSASHA3843072Verifier (signingKey)); + UpdateVerifier (new i2p::crypto:: RSASHA3843072Verifier (signingKey)); break; } case SIGNING_KEY_TYPE_RSA_SHA512_4096: @@ -358,20 +358,28 @@ namespace data memcpy (signingKey, m_StandardIdentity.signingKey, 128); size_t excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512- 128 memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - m_Verifier.reset (new i2p::crypto:: RSASHA5124096Verifier (signingKey)); + UpdateVerifier (new i2p::crypto:: RSASHA5124096Verifier (signingKey)); break; } case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: { size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 - m_Verifier.reset (new i2p::crypto::EDDSA25519Verifier (m_StandardIdentity.signingKey + padding)); + UpdateVerifier (new i2p::crypto::EDDSA25519Verifier (m_StandardIdentity.signingKey + padding)); break; } default: LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported"); } } - + + void IdentityEx::UpdateVerifier (i2p::crypto::Verifier * verifier) const + { + if (!m_Verifier || !verifier) + m_Verifier.reset (verifier); + else + delete verifier; + } + void IdentityEx::DropVerifier () const { // TODO: potential race condition with Verify diff --git a/Identity.h b/Identity.h index 27da190d..d8abd6f4 100644 --- a/Identity.h +++ b/Identity.h @@ -95,6 +95,7 @@ namespace data private: void CreateVerifier () const; + void UpdateVerifier (i2p::crypto::Verifier * verifier) const; private: From c45aab7cefb75ee5b7f3802bd0b3a0b9a317a791 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 8 Apr 2016 15:45:23 -0400 Subject: [PATCH 1193/6300] precalculate g^x mod p table --- Crypto.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Crypto.cpp b/Crypto.cpp index 0ec0f020..9858d6fd 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -153,6 +153,32 @@ namespace crypto #define elgp GetCryptoConstants ().elgp #define elgg GetCryptoConstants ().elgg + void PrecalculateElggTable (BIGNUM * table[][256], int len) // table is len's array of array of 256 bignums + { + if (len <= 0) return; + BN_CTX * ctx = BN_CTX_new (); + BN_MONT_CTX * montCtx = BN_MONT_CTX_new (); + BN_MONT_CTX_set (montCtx, elgp, ctx); + BIGNUM * elggMont = BN_new (); + BN_from_montgomery(elggMont, elgg, montCtx, ctx); + for (int i = 0; i < len; i++) + { + table[i][0] = BN_new (); + if (!i) + BN_from_montgomery (table[0][0], BN_value_one (), montCtx, ctx); // 2^0 = 1 + else + BN_mod_mul_montgomery (table[i][0], table[i-1][255], elggMont, montCtx, ctx); + for (int j = 1; j < 256; j++) + { + table[i][j] = BN_new (); + BN_mod_mul_montgomery (table[i][j], table[i][j-1], elggMont, montCtx, ctx); + } + } + BN_free (elggMont); + BN_MONT_CTX_free (montCtx); + BN_CTX_free (ctx); + } + // DH DHKeys::DHKeys (): m_IsUpdated (true) From ffc666eaaa5390019809f957412427b4d740f8e6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 9 Apr 2016 22:44:13 -0400 Subject: [PATCH 1194/6300] g^x mod p using precalculated table --- Crypto.cpp | 48 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/Crypto.cpp b/Crypto.cpp index 9858d6fd..6891e776 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -153,32 +153,62 @@ namespace crypto #define elgp GetCryptoConstants ().elgp #define elgg GetCryptoConstants ().elgg - void PrecalculateElggTable (BIGNUM * table[][256], int len) // table is len's array of array of 256 bignums + void PrecalculateElggTable (BIGNUM * table[][255], int len) // table is len's array of array of 255 bignums { if (len <= 0) return; BN_CTX * ctx = BN_CTX_new (); BN_MONT_CTX * montCtx = BN_MONT_CTX_new (); - BN_MONT_CTX_set (montCtx, elgp, ctx); - BIGNUM * elggMont = BN_new (); - BN_from_montgomery(elggMont, elgg, montCtx, ctx); + BN_MONT_CTX_set (montCtx, elgp, ctx); for (int i = 0; i < len; i++) { table[i][0] = BN_new (); if (!i) - BN_from_montgomery (table[0][0], BN_value_one (), montCtx, ctx); // 2^0 = 1 + BN_to_montgomery (table[0][0], elgg, montCtx, ctx); else - BN_mod_mul_montgomery (table[i][0], table[i-1][255], elggMont, montCtx, ctx); - for (int j = 1; j < 256; j++) + BN_mod_mul_montgomery (table[i][0], table[i-1][254], table[i-1][0], montCtx, ctx); + for (int j = 1; j < 255; j++) { table[i][j] = BN_new (); - BN_mod_mul_montgomery (table[i][j], table[i][j-1], elggMont, montCtx, ctx); + BN_mod_mul_montgomery (table[i][j], table[i][j-1], table[i][0], montCtx, ctx); } } - BN_free (elggMont); BN_MONT_CTX_free (montCtx); BN_CTX_free (ctx); } + BIGNUM * ElggPow (const uint8_t * exp, int len, BIGNUM * table[][255], BN_CTX * ctx) + // exp is in Big Endian + { + if (len <= 0) return nullptr; + BIGNUM * res = nullptr; + BN_MONT_CTX * montCtx = BN_MONT_CTX_new (); + BN_MONT_CTX_set (montCtx, elgp, ctx); + for (int i = 0; i < len; i++) + { + if (res) + { + if (exp[i]) + BN_mod_mul_montgomery (res, res, table[len-1-i][exp[i]-1], montCtx, ctx); + } + else if (exp[i]) + res = BN_dup (table[len-i-1][exp[i]-1]); + } + if (res) + BN_from_montgomery (res, res, montCtx, ctx); + BN_MONT_CTX_free (montCtx); + return res; + } + + BIGNUM * ElggPow (const BIGNUM * exp, BIGNUM * table[][255], BN_CTX * ctx) + { + auto len = BN_num_bytes (exp); + uint8_t * buf = new uint8_t[len]; + BN_bn2bin (exp, buf); + auto ret = ElggPow (buf, len, table, ctx); + delete[] buf; + return ret; + } + // DH DHKeys::DHKeys (): m_IsUpdated (true) From 34a8d4a57d8d6cccf0fc6f40999f74b57eb30f2f Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 10 Apr 2016 17:06:02 -0400 Subject: [PATCH 1195/6300] use precalculated table for ElGamal encryption --- Crypto.cpp | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/Crypto.cpp b/Crypto.cpp index 6891e776..0523f55f 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -148,12 +148,15 @@ namespace crypto // DH/ElGamal const int ELGAMAL_SHORT_EXPONENT_NUM_BITS = 226; + const int ELGAMAL_SHORT_EXPONENT_NUM_BYTES = ELGAMAL_SHORT_EXPONENT_NUM_BITS/8+1; const int ELGAMAL_FULL_EXPONENT_NUM_BITS = 2048; #define elgp GetCryptoConstants ().elgp #define elgg GetCryptoConstants ().elgg - void PrecalculateElggTable (BIGNUM * table[][255], int len) // table is len's array of array of 255 bignums +#if !defined(__x86_64__) // use precalculated table + + static void PrecalculateElggTable (BIGNUM * table[][255], int len) // table is len's array of array of 255 bignums { if (len <= 0) return; BN_CTX * ctx = BN_CTX_new (); @@ -176,7 +179,17 @@ namespace crypto BN_CTX_free (ctx); } - BIGNUM * ElggPow (const uint8_t * exp, int len, BIGNUM * table[][255], BN_CTX * ctx) + static void DestroyElggTable (BIGNUM * table[][255], int len) + { + for (int i = 0; i < len; i++) + for (int j = 0; j < 255; j++) + { + BN_free (table[i][j]); + table[i][j] = nullptr; + } + } + + static BIGNUM * ElggPow (const uint8_t * exp, int len, BIGNUM * table[][255], BN_CTX * ctx) // exp is in Big Endian { if (len <= 0) return nullptr; @@ -199,7 +212,7 @@ namespace crypto return res; } - BIGNUM * ElggPow (const BIGNUM * exp, BIGNUM * table[][255], BN_CTX * ctx) + static BIGNUM * ElggPow (const BIGNUM * exp, BIGNUM * table[][255], BN_CTX * ctx) { auto len = BN_num_bytes (exp); uint8_t * buf = new uint8_t[len]; @@ -208,6 +221,10 @@ namespace crypto delete[] buf; return ret; } + + BIGNUM * g_ElggTable[ELGAMAL_SHORT_EXPONENT_NUM_BYTES][255]; + +#endif // DH @@ -229,9 +246,9 @@ namespace crypto { if (m_DH->priv_key) { BN_free (m_DH->priv_key); m_DH->priv_key = NULL; }; if (m_DH->pub_key) { BN_free (m_DH->pub_key); m_DH->pub_key = NULL; }; -#if !defined(__x86_64__) // use short exponent for non x64 +#if !defined(__x86_64__) // use short exponent for non x64 m_DH->priv_key = BN_new (); - BN_rand (m_DH->priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1); + BN_rand (m_DH->priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1); #endif DH_generate_key (m_DH); if (priv) bn2buf (m_DH->priv_key, priv, 256); @@ -266,12 +283,14 @@ namespace crypto BIGNUM * k = BN_new (); #if defined(__x86_64__) BN_rand (k, ELGAMAL_FULL_EXPONENT_NUM_BITS, -1, 1); // full exponent for x64 -#else - BN_rand (k, ELGAMAL_SHORT_EXPONENT_NUM_BITS, -1, 1); // short exponent of 226 bits -#endif - // caulculate a + // calculate a a = BN_new (); BN_mod_exp (a, elgg, k, elgp, ctx); +#else + BN_rand (k, ELGAMAL_SHORT_EXPONENT_NUM_BITS, -1, 1); // short exponent of 226 bits + // calculate a + a = ElggPow (k, g_ElggTable, ctx); +#endif BIGNUM * y = BN_new (); BN_bin2bn (key, 256, y); // calculate b1 @@ -772,10 +791,16 @@ namespace crypto for (int i = 0; i < numLocks; i++) m_OpenSSLMutexes.emplace_back (new std::mutex); CRYPTO_set_locking_callback (OpensslLockingCallback);*/ +#if !defined(__x86_64__) + PrecalculateElggTable (g_ElggTable, ELGAMAL_SHORT_EXPONENT_NUM_BYTES); +#endif } void TerminateCrypto () { +#if !defined(__x86_64__) + DestroyElggTable (g_ElggTable, ELGAMAL_SHORT_EXPONENT_NUM_BYTES); +#endif /* CRYPTO_set_locking_callback (nullptr); m_OpenSSLMutexes.clear ();*/ } From 6a9d2ba653e1f99e4591908bed5f01c2baaeb4a0 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 10 Apr 2016 21:16:18 -0400 Subject: [PATCH 1196/6300] use precalculated table for DH --- Crypto.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Crypto.cpp b/Crypto.cpp index 0523f55f..fbd3e139 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -155,27 +155,27 @@ namespace crypto #define elgg GetCryptoConstants ().elgg #if !defined(__x86_64__) // use precalculated table - + + static BN_MONT_CTX * g_MontCtx = nullptr; static void PrecalculateElggTable (BIGNUM * table[][255], int len) // table is len's array of array of 255 bignums { if (len <= 0) return; BN_CTX * ctx = BN_CTX_new (); - BN_MONT_CTX * montCtx = BN_MONT_CTX_new (); - BN_MONT_CTX_set (montCtx, elgp, ctx); + g_MontCtx = BN_MONT_CTX_new (); + BN_MONT_CTX_set (g_MontCtx, elgp, ctx); for (int i = 0; i < len; i++) { table[i][0] = BN_new (); if (!i) - BN_to_montgomery (table[0][0], elgg, montCtx, ctx); + BN_to_montgomery (table[0][0], elgg, g_MontCtx, ctx); else - BN_mod_mul_montgomery (table[i][0], table[i-1][254], table[i-1][0], montCtx, ctx); + BN_mod_mul_montgomery (table[i][0], table[i-1][254], table[i-1][0], g_MontCtx, ctx); for (int j = 1; j < 255; j++) { table[i][j] = BN_new (); - BN_mod_mul_montgomery (table[i][j], table[i][j-1], table[i][0], montCtx, ctx); + BN_mod_mul_montgomery (table[i][j], table[i][j-1], table[i][0], g_MontCtx, ctx); } } - BN_MONT_CTX_free (montCtx); BN_CTX_free (ctx); } @@ -187,15 +187,16 @@ namespace crypto BN_free (table[i][j]); table[i][j] = nullptr; } + BN_MONT_CTX_free (g_MontCtx); } static BIGNUM * ElggPow (const uint8_t * exp, int len, BIGNUM * table[][255], BN_CTX * ctx) // exp is in Big Endian { if (len <= 0) return nullptr; + auto montCtx = BN_MONT_CTX_new (); + BN_MONT_CTX_copy (montCtx, g_MontCtx); BIGNUM * res = nullptr; - BN_MONT_CTX * montCtx = BN_MONT_CTX_new (); - BN_MONT_CTX_set (montCtx, elgp, ctx); for (int i = 0; i < len; i++) { if (res) @@ -249,8 +250,12 @@ namespace crypto #if !defined(__x86_64__) // use short exponent for non x64 m_DH->priv_key = BN_new (); BN_rand (m_DH->priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1); -#endif + auto ctx = BN_CTX_new (); + m_DH->pub_key = ElggPow (m_DH->priv_key, g_ElggTable, ctx); + BN_CTX_free (ctx); +#else DH_generate_key (m_DH); +#endif if (priv) bn2buf (m_DH->priv_key, priv, 256); if (pub) bn2buf (m_DH->pub_key, pub, 256); m_IsUpdated = true; From 6336d38a3ea14bafdb5d34f36b9dd2cd80838aa5 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 11 Apr 2016 12:04:15 -0400 Subject: [PATCH 1197/6300] Removed downloads. Added Docimentation --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4f167754..b985abf4 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,11 @@ Donations BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z +DOGE: DNXLQKziRPAsD9H3DFNjk4fLQrdaSX893Y -Downloads ------------- - -Official binary releases could be found at: -http://i2pd.website/releases/ -older releases -http://download.i2p.io/purplei2p/i2pd/releases/ +Documentation: +-------------- +http://i2pd.readthedocs.org Supported OS ------------ From d15cc7cc4766e766fe56b3f41b4ea9817a0cfeaa Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 11 Apr 2016 12:39:32 -0400 Subject: [PATCH 1198/6300] changed tray icon back to ictoopie --- Win32/Resource.rc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Win32/Resource.rc b/Win32/Resource.rc index bdc532e9..c885c044 100644 --- a/Win32/Resource.rc +++ b/Win32/Resource.rc @@ -52,8 +52,8 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -//MAINICON ICON "ictoopie.ico" -MAINICON ICON "anke.ico" +MAINICON ICON "ictoopie.ico" +//MAINICON ICON "anke.ico" MASCOT BITMAP "Anke_700px.bmp" From c0b0df34d2817034b4775b6626198a966f543432 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 Apr 2016 19:07:11 -0400 Subject: [PATCH 1199/6300] clean montgomery context --- Crypto.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Crypto.cpp b/Crypto.cpp index fbd3e139..fe6dfa8f 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -163,19 +163,22 @@ namespace crypto BN_CTX * ctx = BN_CTX_new (); g_MontCtx = BN_MONT_CTX_new (); BN_MONT_CTX_set (g_MontCtx, elgp, ctx); + auto montCtx = BN_MONT_CTX_new (); + BN_MONT_CTX_copy (montCtx, g_MontCtx); for (int i = 0; i < len; i++) { table[i][0] = BN_new (); if (!i) - BN_to_montgomery (table[0][0], elgg, g_MontCtx, ctx); + BN_to_montgomery (table[0][0], elgg, montCtx, ctx); else - BN_mod_mul_montgomery (table[i][0], table[i-1][254], table[i-1][0], g_MontCtx, ctx); + BN_mod_mul_montgomery (table[i][0], table[i-1][254], table[i-1][0], montCtx, ctx); for (int j = 1; j < 255; j++) { table[i][j] = BN_new (); - BN_mod_mul_montgomery (table[i][j], table[i][j-1], table[i][0], g_MontCtx, ctx); + BN_mod_mul_montgomery (table[i][j], table[i][j-1], table[i][0], montCtx, ctx); } } + BN_MONT_CTX_free (montCtx); BN_CTX_free (ctx); } From ef106f3232ed1bb99994c61e7b10edb1a3e0870a Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 13 Apr 2016 11:22:08 -0400 Subject: [PATCH 1200/6300] fixed typo --- TunnelEndpoint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index a3907ce5..842b624f 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -119,7 +119,7 @@ namespace tunnel if (ret.second) HandleOutOfSequenceFragment (msgID, ret.first->second); else - LogPrint (eLogError, "TunnelMessage: Incomplete message ", msgID, "already exists"); + LogPrint (eLogError, "TunnelMessage: Incomplete message ", msgID, " already exists"); } else { From a4773d259da5d61c39ea1539d61634e0a2a02b6f Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 14 Apr 2016 00:00:00 +0000 Subject: [PATCH 1201/6300] * use std::to_string() instead boost's function --- HTTPServer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 06fabc31..7a15eaa4 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include "Base.h" #include "FS.h" @@ -911,7 +910,7 @@ namespace util m_Reply.headers[0].name = "Date"; m_Reply.headers[0].value = std::string(time_buff); m_Reply.headers[1].name = "Content-Length"; - m_Reply.headers[1].value = boost::lexical_cast(m_Reply.content.size()); + m_Reply.headers[1].value = std::to_string(m_Reply.content.size()); m_Reply.headers[2].name = "Content-Type"; m_Reply.headers[2].value = "text/html"; } From 5d38693b4dd7456b0e88c63f4f907db75e3ec526 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 14 Apr 2016 00:00:00 +0000 Subject: [PATCH 1202/6300] * HTTPServer : fold namespace to two constants --- HTTPServer.cpp | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 7a15eaa4..86808e25 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -23,7 +23,6 @@ namespace i2p { namespace util { - const std::string HTTPConnection::itoopieImage = "\"ICToopie HTTPConnection::reply::to_buffers(int status) { std::vector buffers; @@ -236,17 +228,17 @@ namespace util default: status_string += "WTF"; } buffers.push_back(boost::asio::buffer(status_string, status_string.size())); - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); + buffers.push_back(boost::asio::buffer(HTTP_CRLF)); for (std::size_t i = 0; i < headers.size(); ++i) { header& h = headers[i]; buffers.push_back(boost::asio::buffer(h.name)); - buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator)); + buffers.push_back(boost::asio::buffer(HTTP_HEADER_KV_SEP)); buffers.push_back(boost::asio::buffer(h.value)); - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); + buffers.push_back(boost::asio::buffer(HTTP_CRLF)); } - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); + buffers.push_back(boost::asio::buffer(HTTP_CRLF)); } buffers.push_back(boost::asio::buffer(content)); return buffers; @@ -977,6 +969,3 @@ namespace util } } } - - - From a5c0b48b575f99a648885bd85b960092cf7d5877 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 14 Apr 2016 00:00:00 +0000 Subject: [PATCH 1203/6300] * HandleDestinationRequestTimeout() : readable code --- HTTPServer.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 86808e25..2d69ae7a 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -846,11 +846,13 @@ namespace util if (ecode != boost::asio::error::operation_aborted) { auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (destination); - if (leaseSet && !leaseSet->IsExpired ()) + if (leaseSet && !leaseSet->IsExpired ()) { SendToDestination (leaseSet, port, buf, len); - else - // still no LeaseSet - SendReply (leaseSet ? "" + itoopieImage + "
\r\nLeases expired" : "" + itoopieImage + "LeaseSet not found", 504); + } else if (leaseSet) { + SendReply ("" + itoopieImage + "
\r\nLeaseSet expired", 504); + } else { + SendReply ("" + itoopieImage + "
\r\nLeaseSet not found", 504); + } } } From 87dd890eb054aabee7e6082d9dc50c91180b95d0 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 14 Apr 2016 00:00:00 +0000 Subject: [PATCH 1204/6300] * HTTPConnection::reply : to_buffers() -> to_string() --- HTTPServer.cpp | 49 ++++++++++++++++++++----------------------------- HTTPServer.h | 2 +- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 2d69ae7a..4b426aa9 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -206,42 +206,33 @@ namespace util const char HTTP_HEADER_KV_SEP[] = ": "; const char HTTP_CRLF[] = "\r\n"; - std::vector HTTPConnection::reply::to_buffers(int status) + std::string HTTPConnection::reply::to_string(int code) { - std::vector buffers; + std::stringstream ss(""); if (headers.size () > 0) { - status_string = "HTTP/1.1 "; - status_string += std::to_string (status); - status_string += " "; - switch (status) + const char *status; + switch (code) { - case 105: status_string += "Name Not Resolved"; break; - case 200: status_string += "OK"; break; - case 400: status_string += "Bad Request"; break; - case 404: status_string += "Not Found"; break; - case 408: status_string += "Request Timeout"; break; - case 500: status_string += "Internal Server Error"; break; - case 502: status_string += "Bad Gateway"; break; - case 503: status_string += "Not Implemented"; break; - case 504: status_string += "Gateway Timeout"; break; - default: status_string += "WTF"; + case 105: status = "Name Not Resolved"; break; + case 200: status = "OK"; break; + case 400: status = "Bad Request"; break; + case 404: status = "Not Found"; break; + case 408: status = "Request Timeout"; break; + case 500: status = "Internal Server Error"; break; + case 502: status = "Bad Gateway"; break; + case 503: status = "Not Implemented"; break; + case 504: status = "Gateway Timeout"; break; + default: status = "WTF"; } - buffers.push_back(boost::asio::buffer(status_string, status_string.size())); - buffers.push_back(boost::asio::buffer(HTTP_CRLF)); - - for (std::size_t i = 0; i < headers.size(); ++i) - { - header& h = headers[i]; - buffers.push_back(boost::asio::buffer(h.name)); - buffers.push_back(boost::asio::buffer(HTTP_HEADER_KV_SEP)); - buffers.push_back(boost::asio::buffer(h.value)); - buffers.push_back(boost::asio::buffer(HTTP_CRLF)); + ss << "HTTP/1.1 " << code << "" << status << HTTP_CRLF; + for (header & h : headers) { + ss << h.name << HTTP_HEADER_KV_SEP << h.value << HTTP_CRLF; } - buffers.push_back(boost::asio::buffer(HTTP_CRLF)); + ss << HTTP_CRLF; /* end of headers */ } - buffers.push_back(boost::asio::buffer(content)); - return buffers; + ss << content; + return ss.str(); } void HTTPConnection::Terminate () diff --git a/HTTPServer.h b/HTTPServer.h index f70e27dc..8dd40dea 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -40,7 +40,7 @@ namespace util { std::vector
headers; std::string status_string, content; - std::vector to_buffers (int status); + std::string to_string (int status); }; public: From 04bfd52fba8107bec4c5438c2bfc649f96ced768 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 14 Apr 2016 00:00:00 +0000 Subject: [PATCH 1205/6300] * HTTPConnection::SendReply() : cleaner code --- HTTPServer.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 4b426aa9..b54f55e8 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -885,22 +885,23 @@ namespace util void HTTPConnection::SendReply (const std::string& content, int status) { - m_Reply.content = content; + // we need the date header to be complaint with http 1.1 + std::time_t time_now = std::time(nullptr); + char time_buff[128]; + std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now)); + /* fill reply with headers */ m_Reply.headers.resize(3); - // we need the date header to be complaint with http 1.1 - std::time_t time_now = std::time(nullptr); - char time_buff[128]; - if (std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now))) - { - m_Reply.headers[0].name = "Date"; - m_Reply.headers[0].value = std::string(time_buff); - m_Reply.headers[1].name = "Content-Length"; - m_Reply.headers[1].value = std::to_string(m_Reply.content.size()); - m_Reply.headers[2].name = "Content-Type"; - m_Reply.headers[2].value = "text/html"; - } - - boost::asio::async_write (*m_Socket, m_Reply.to_buffers(status), + m_Reply.headers[0].name = "Date"; + m_Reply.headers[0].value = std::string(time_buff); + m_Reply.headers[1].name = "Content-Length"; + m_Reply.headers[1].value = std::to_string(m_Reply.content.size()); + m_Reply.headers[2].name = "Content-Type"; + m_Reply.headers[2].value = "text/html"; + + std::vector buffers; + buffers.push_back(boost::asio::buffer(m_Reply.to_string(status))); + buffers.push_back(boost::asio::buffer(content)); + boost::asio::async_write (*m_Socket, buffers, std::bind (&HTTPConnection::HandleWriteReply, shared_from_this (), std::placeholders::_1)); } From 3f9d2601b41a95e4630e2e538602503a8e76a726 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 14 Apr 2016 00:00:00 +0000 Subject: [PATCH 1206/6300] + HTTPConnection::SendError() --- HTTPServer.cpp | 13 +++++++++---- HTTPServer.h | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index b54f55e8..66506908 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -813,7 +813,7 @@ namespace util if (!i2p::client::context.GetAddressBook ().GetIdentHash (address, destination)) { LogPrint (eLogWarning, "HTTPServer: Unknown address ", address); - SendReply ("" + itoopieImage + "
\r\nUnknown address " + address + "", 404); + SendError ("Unknown address " + address); return; } @@ -840,9 +840,9 @@ namespace util if (leaseSet && !leaseSet->IsExpired ()) { SendToDestination (leaseSet, port, buf, len); } else if (leaseSet) { - SendReply ("" + itoopieImage + "
\r\nLeaseSet expired", 504); + SendError ("LeaseSet expired"); } else { - SendReply ("" + itoopieImage + "
\r\nLeaseSet not found", 504); + SendError ("LeaseSet not found"); } } } @@ -877,7 +877,7 @@ namespace util else { if (ecode == boost::asio::error::timed_out) - SendReply ("" + itoopieImage + "
\r\nNot responding", 504); + SendError ("Host not responding"); else if (ecode != boost::asio::error::operation_aborted) Terminate (); } @@ -905,6 +905,11 @@ namespace util std::bind (&HTTPConnection::HandleWriteReply, shared_from_this (), std::placeholders::_1)); } + void HTTPConnection::SendError(const std::string& content) + { + SendReply ("" + itoopieImage + "
\r\n" + content + "", 504); + } + HTTPServer::HTTPServer (const std::string& address, int port): m_Thread (nullptr), m_Work (m_Service), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)) diff --git a/HTTPServer.h b/HTTPServer.h index 8dd40dea..d5cef8ad 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -59,6 +59,7 @@ namespace util void HandleWriteReply(const boost::system::error_code& ecode); void HandleWrite (const boost::system::error_code& ecode); void SendReply (const std::string& content, int status = 200); + void SendError (const std::string& message); void HandleRequest (const std::string& address); void HandleCommand (const std::string& command, std::stringstream& s); From bce2a63772532d15de617c059f24bbe86c333cbf Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 14 Apr 2016 14:05:25 -0400 Subject: [PATCH 1207/6300] rollback some changes --- HTTPServer.cpp | 90 +++++++++++++++++++++++++++++--------------------- HTTPServer.h | 2 +- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 66506908..6c2c6112 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -203,36 +203,51 @@ namespace util const char HTTP_COMMAND_I2P_TUNNELS[] = "i2p_tunnels"; const char HTTP_COMMAND_JUMPSERVICES[] = "jumpservices="; const char HTTP_PARAM_ADDRESS[] = "address"; - const char HTTP_HEADER_KV_SEP[] = ": "; - const char HTTP_CRLF[] = "\r\n"; - std::string HTTPConnection::reply::to_string(int code) + namespace misc_strings { - std::stringstream ss(""); + + const char name_value_separator[] = { ':', ' ' }; + const char crlf[] = { '\r', '\n' }; + + } // namespace misc_strings + + std::vector HTTPConnection::reply::to_buffers(int status) + { + std::vector buffers; if (headers.size () > 0) { - const char *status; - switch (code) + status_string = "HTTP/1.1 "; + status_string += std::to_string (status); + status_string += " "; + switch (status) { - case 105: status = "Name Not Resolved"; break; - case 200: status = "OK"; break; - case 400: status = "Bad Request"; break; - case 404: status = "Not Found"; break; - case 408: status = "Request Timeout"; break; - case 500: status = "Internal Server Error"; break; - case 502: status = "Bad Gateway"; break; - case 503: status = "Not Implemented"; break; - case 504: status = "Gateway Timeout"; break; - default: status = "WTF"; + case 105: status_string += "Name Not Resolved"; break; + case 200: status_string += "OK"; break; + case 400: status_string += "Bad Request"; break; + case 404: status_string += "Not Found"; break; + case 408: status_string += "Request Timeout"; break; + case 500: status_string += "Internal Server Error"; break; + case 502: status_string += "Bad Gateway"; break; + case 503: status_string += "Not Implemented"; break; + case 504: status_string += "Gateway Timeout"; break; + default: status_string += "WTF"; } - ss << "HTTP/1.1 " << code << "" << status << HTTP_CRLF; - for (header & h : headers) { - ss << h.name << HTTP_HEADER_KV_SEP << h.value << HTTP_CRLF; + buffers.push_back(boost::asio::buffer(status_string, status_string.size())); + buffers.push_back(boost::asio::buffer(misc_strings::crlf)); + + for (std::size_t i = 0; i < headers.size(); ++i) + { + header& h = headers[i]; + buffers.push_back(boost::asio::buffer(h.name)); + buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator)); + buffers.push_back(boost::asio::buffer(h.value)); + buffers.push_back(boost::asio::buffer(misc_strings::crlf)); } - ss << HTTP_CRLF; /* end of headers */ + buffers.push_back(boost::asio::buffer(misc_strings::crlf)); } - ss << content; - return ss.str(); + buffers.push_back(boost::asio::buffer(content)); + return buffers; } void HTTPConnection::Terminate () @@ -885,23 +900,22 @@ namespace util void HTTPConnection::SendReply (const std::string& content, int status) { - // we need the date header to be complaint with http 1.1 - std::time_t time_now = std::time(nullptr); - char time_buff[128]; - std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now)); - /* fill reply with headers */ + m_Reply.content = content; m_Reply.headers.resize(3); - m_Reply.headers[0].name = "Date"; - m_Reply.headers[0].value = std::string(time_buff); - m_Reply.headers[1].name = "Content-Length"; - m_Reply.headers[1].value = std::to_string(m_Reply.content.size()); - m_Reply.headers[2].name = "Content-Type"; - m_Reply.headers[2].value = "text/html"; - - std::vector buffers; - buffers.push_back(boost::asio::buffer(m_Reply.to_string(status))); - buffers.push_back(boost::asio::buffer(content)); - boost::asio::async_write (*m_Socket, buffers, + // we need the date header to be complaint with http 1.1 + std::time_t time_now = std::time(nullptr); + char time_buff[128]; + if (std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now))) + { + m_Reply.headers[0].name = "Date"; + m_Reply.headers[0].value = std::string(time_buff); + m_Reply.headers[1].name = "Content-Length"; + m_Reply.headers[1].value = std::to_string(m_Reply.content.size()); + m_Reply.headers[2].name = "Content-Type"; + m_Reply.headers[2].value = "text/html"; + } + + boost::asio::async_write (*m_Socket, m_Reply.to_buffers(status), std::bind (&HTTPConnection::HandleWriteReply, shared_from_this (), std::placeholders::_1)); } diff --git a/HTTPServer.h b/HTTPServer.h index d5cef8ad..66083d85 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -40,7 +40,7 @@ namespace util { std::vector
headers; std::string status_string, content; - std::string to_string (int status); + std::vector to_buffers (int status); }; public: From aff8cd478c6318dadf594acf9cfe408c869e108c Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 17 Apr 2016 16:57:58 -0400 Subject: [PATCH 1208/6300] optional elgamal precomputation for x64 --- Config.cpp | 12 ++++++++++ Crypto.cpp | 65 ++++++++++++++++++++++++++++++++++++------------------ Crypto.h | 2 +- Daemon.cpp | 3 ++- api.cpp | 6 ++++- 5 files changed, 63 insertions(+), 25 deletions(-) diff --git a/Config.cpp b/Config.cpp index 8d42895a..d5ff2a46 100644 --- a/Config.cpp +++ b/Config.cpp @@ -180,6 +180,17 @@ namespace config { ("i2pcontrol.key", value()->default_value("i2pcontrol.key.pem"), "I2PCP connection cerificate key") ; + options_description precomputation("Precomputation options"); + precomputation.add_options() + ("precomputation.elgamal", +#if defined(__x86_64__) + value()->default_value(false), +#else + value()->default_value(true), +#endif + "Enable or disable elgamal precomputation table") + ; + m_OptionsDesc .add(general) .add(httpserver) @@ -188,6 +199,7 @@ namespace config { .add(sam) .add(bob) .add(i2pcontrol) + .add(precomputation) ; } diff --git a/Crypto.cpp b/Crypto.cpp index fe6dfa8f..f7c00595 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -150,12 +150,11 @@ namespace crypto const int ELGAMAL_SHORT_EXPONENT_NUM_BITS = 226; const int ELGAMAL_SHORT_EXPONENT_NUM_BYTES = ELGAMAL_SHORT_EXPONENT_NUM_BITS/8+1; const int ELGAMAL_FULL_EXPONENT_NUM_BITS = 2048; - + const int ELGAMAL_FULL_EXPONENT_NUM_BYTES = ELGAMAL_FULL_EXPONENT_NUM_BITS/8; + #define elgp GetCryptoConstants ().elgp #define elgg GetCryptoConstants ().elgg -#if !defined(__x86_64__) // use precalculated table - static BN_MONT_CTX * g_MontCtx = nullptr; static void PrecalculateElggTable (BIGNUM * table[][255], int len) // table is len's array of array of 255 bignums { @@ -226,9 +225,7 @@ namespace crypto return ret; } - BIGNUM * g_ElggTable[ELGAMAL_SHORT_EXPONENT_NUM_BYTES][255]; - -#endif + static BIGNUM * (* g_ElggTable)[255] = nullptr; // DH @@ -253,12 +250,20 @@ namespace crypto #if !defined(__x86_64__) // use short exponent for non x64 m_DH->priv_key = BN_new (); BN_rand (m_DH->priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1); - auto ctx = BN_CTX_new (); - m_DH->pub_key = ElggPow (m_DH->priv_key, g_ElggTable, ctx); - BN_CTX_free (ctx); -#else - DH_generate_key (m_DH); #endif + if (g_ElggTable) + { +#if defined(__x86_64__) + m_DH->priv_key = BN_new (); + BN_rand (m_DH->priv_key, ELGAMAL_FULL_EXPONENT_NUM_BITS, 0, 1); +#endif + auto ctx = BN_CTX_new (); + m_DH->pub_key = ElggPow (m_DH->priv_key, g_ElggTable, ctx); + BN_CTX_free (ctx); + } + else + DH_generate_key (m_DH); + if (priv) bn2buf (m_DH->priv_key, priv, 256); if (pub) bn2buf (m_DH->pub_key, pub, 256); m_IsUpdated = true; @@ -291,14 +296,16 @@ namespace crypto BIGNUM * k = BN_new (); #if defined(__x86_64__) BN_rand (k, ELGAMAL_FULL_EXPONENT_NUM_BITS, -1, 1); // full exponent for x64 - // calculate a - a = BN_new (); - BN_mod_exp (a, elgg, k, elgp, ctx); #else BN_rand (k, ELGAMAL_SHORT_EXPONENT_NUM_BITS, -1, 1); // short exponent of 226 bits +#endif // calculate a - a = ElggPow (k, g_ElggTable, ctx); -#endif + a = BN_new (); + if (g_ElggTable) + a = ElggPow (k, g_ElggTable, ctx); + else + BN_mod_exp (a, elgg, k, elgp, ctx); + BIGNUM * y = BN_new (); BN_bin2bn (key, 256, y); // calculate b1 @@ -792,23 +799,37 @@ namespace crypto } }*/ - void InitCrypto () + void InitCrypto (bool precomputation) { SSL_library_init (); /* auto numLocks = CRYPTO_num_locks(); for (int i = 0; i < numLocks; i++) m_OpenSSLMutexes.emplace_back (new std::mutex); CRYPTO_set_locking_callback (OpensslLockingCallback);*/ -#if !defined(__x86_64__) - PrecalculateElggTable (g_ElggTable, ELGAMAL_SHORT_EXPONENT_NUM_BYTES); + if (precomputation) + { +#if defined(__x86_64__) + g_ElggTable = new BIGNUM * [ELGAMAL_FULL_EXPONENT_NUM_BYTES][255]; + PrecalculateElggTable (g_ElggTable, ELGAMAL_FULL_EXPONENT_NUM_BYTES); +#else + g_ElggTable = new BIGNUM * [ELGAMAL_SHORT_EXPONENT_NUM_BYTES][255]; + PrecalculateElggTable (g_ElggTable, ELGAMAL_SHORT_EXPONENT_NUM_BYTES); #endif + } } void TerminateCrypto () { -#if !defined(__x86_64__) - DestroyElggTable (g_ElggTable, ELGAMAL_SHORT_EXPONENT_NUM_BYTES); -#endif + if (g_ElggTable) + { + DestroyElggTable (g_ElggTable, +#if defined(__x86_64__) + ELGAMAL_FULL_EXPONENT_NUM_BYTES +#else + ELGAMAL_SHORT_EXPONENT_NUM_BYTES +#endif + ); + } /* CRYPTO_set_locking_callback (nullptr); m_OpenSSLMutexes.clear ();*/ } diff --git a/Crypto.h b/Crypto.h index e633f8bf..e333940e 100644 --- a/Crypto.h +++ b/Crypto.h @@ -273,7 +273,7 @@ namespace crypto #endif }; - void InitCrypto (); + void InitCrypto (bool precomputation); void TerminateCrypto (); } } diff --git a/Daemon.cpp b/Daemon.cpp index f15fe3e3..0924b236 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -117,7 +117,8 @@ namespace i2p LogPrint(eLogDebug, "FS: main config file: ", config); LogPrint(eLogDebug, "FS: data directory: ", datadir); - i2p::crypto::InitCrypto (); + bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); + i2p::crypto::InitCrypto (precomputation); i2p::context.Init (); uint16_t port; i2p::config::GetOption("port", port); diff --git a/api.cpp b/api.cpp index 64648743..1828901b 100644 --- a/api.cpp +++ b/api.cpp @@ -28,7 +28,11 @@ namespace api i2p::fs::DetectDataDir(datadir, false); i2p::fs::Init(); - i2p::crypto::InitCrypto (); +#if defined(__x86_64__) + i2p::crypto::InitCrypto (false); +#else + i2p::crypto::InitCrypto (true); +#endif i2p::context.Init (); } From aff65083cc779dec6847bc900c65d27dc1cf39cc Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 17 Apr 2016 17:03:56 -0400 Subject: [PATCH 1209/6300] precomputation.elgamal --- docs/configuration.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index ac5c4684..14db728a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -59,7 +59,9 @@ All options below still possible in cmdline, but better write it in config file: * --i2pcontrol.address= - The address to listen on (I2P control service) * --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 + +* --precomputation.elgamal= - Use ElGamal precomputated tables. false for x64 and true for other platforms by default Config files ------------ From c265bd6c4d455b89e443542571cbf3f9e5043dd5 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 Apr 2016 21:07:45 -0400 Subject: [PATCH 1210/6300] delete pre-calculated tablle upon termination --- Crypto.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Crypto.cpp b/Crypto.cpp index f7c00595..742296f5 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -828,7 +828,8 @@ namespace crypto #else ELGAMAL_SHORT_EXPONENT_NUM_BYTES #endif - ); + ); + delete[] g_ElggTable; g_ElggTable = nullptr; } /* CRYPTO_set_locking_callback (nullptr); m_OpenSSLMutexes.clear ();*/ From bb656ce44b638dd3e7822dacba4d840793329bb3 Mon Sep 17 00:00:00 2001 From: weekendi2p Date: Wed, 20 Apr 2016 19:12:14 +0200 Subject: [PATCH 1211/6300] added some limits options --- Config.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Config.cpp b/Config.cpp index d5ff2a46..41236014 100644 --- a/Config.cpp +++ b/Config.cpp @@ -130,6 +130,13 @@ namespace config { ("close", value()->default_value("ask"), "Action on close: minimize, exit, ask") // TODO: add custom validator or something #endif ; + options_description limits("Limits options"); + limits.add_options() + ("limits.transit", value()->default_value(2500), "Maximum active transit sessions (default:2500)") + ("limits.router", value()->default_value(4096), "Maximum active router sessions (default:4096)") + ("limits.client", value()->default_value(1024), "Maximum active client sessions (default:1024)") + ("limits.floodfill", value()->default_value(1024), "Maximum active floodfill sessions (default:1024)") + ; options_description httpserver("HTTP Server options"); httpserver.add_options() From 8456c8b47b56e37d655660c5d8c0e16a79aa19bc Mon Sep 17 00:00:00 2001 From: weekendi2p Date: Wed, 20 Apr 2016 19:22:04 +0200 Subject: [PATCH 1212/6300] limits options --- Config.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Config.cpp b/Config.cpp index 41236014..af399bd4 100644 --- a/Config.cpp +++ b/Config.cpp @@ -133,9 +133,6 @@ namespace config { options_description limits("Limits options"); limits.add_options() ("limits.transit", value()->default_value(2500), "Maximum active transit sessions (default:2500)") - ("limits.router", value()->default_value(4096), "Maximum active router sessions (default:4096)") - ("limits.client", value()->default_value(1024), "Maximum active client sessions (default:1024)") - ("limits.floodfill", value()->default_value(1024), "Maximum active floodfill sessions (default:1024)") ; options_description httpserver("HTTP Server options"); From 9a860341626462d5c725bb14e4da0f57ffc33777 Mon Sep 17 00:00:00 2001 From: weekendi2p Date: Wed, 20 Apr 2016 19:24:50 +0200 Subject: [PATCH 1213/6300] limits options --- Config.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Config.cpp b/Config.cpp index af399bd4..922ce236 100644 --- a/Config.cpp +++ b/Config.cpp @@ -130,9 +130,10 @@ namespace config { ("close", value()->default_value("ask"), "Action on close: minimize, exit, ask") // TODO: add custom validator or something #endif ; + options_description limits("Limits options"); limits.add_options() - ("limits.transit", value()->default_value(2500), "Maximum active transit sessions (default:2500)") + ("limits.transittunnels", value()->default_value(2500), "Maximum active transit sessions (default:2500)") ; options_description httpserver("HTTP Server options"); From e120e9a78eb9eba3914f4e28e90006705c82703e Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 20 Apr 2016 14:53:50 -0400 Subject: [PATCH 1214/6300] configurable transit tunnels limit --- Config.cpp | 1 + Daemon.cpp | 2 ++ I2NPProtocol.cpp | 12 +++++++++++- I2NPProtocol.h | 5 +++-- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Config.cpp b/Config.cpp index 922ce236..d7cef879 100644 --- a/Config.cpp +++ b/Config.cpp @@ -198,6 +198,7 @@ namespace config { m_OptionsDesc .add(general) + .add(limits) .add(httpserver) .add(httpproxy) .add(socksproxy) diff --git a/Daemon.cpp b/Daemon.cpp index 0924b236..81bbcdd5 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -141,6 +141,8 @@ namespace i2p i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); i2p::context.SetAcceptsTunnels (!transit); + uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); + SetMaxNumTransitTunnels (transitTunnels); bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); if (isFloodfill) { diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 7cebf96a..9674fdca 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -286,6 +286,16 @@ namespace i2p return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo } + static uint16_t g_MaxNumTransitTunnels = DEFAULT_MAX_NUM_TRANSIT_TUNNELS; // TODO: + void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels) + { + if (maxNumTransitTunnels > 0 && maxNumTransitTunnels <= 10000 && g_MaxNumTransitTunnels != maxNumTransitTunnels) + { + LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels); + g_MaxNumTransitTunnels = maxNumTransitTunnels; + } + } + bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) { for (int i = 0; i < num; i++) @@ -298,7 +308,7 @@ namespace i2p i2p::crypto::ElGamalDecrypt (i2p::context.GetEncryptionPrivateKey (), record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText); // replace record to reply if (i2p::context.AcceptsTunnels () && - i2p::tunnel::tunnels.GetTransitTunnels ().size () <= MAX_NUM_TRANSIT_TUNNELS && + i2p::tunnel::tunnels.GetTransitTunnels ().size () <= g_MaxNumTransitTunnels && !i2p::transport::transports.IsBandwidthExceeded ()) { auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 6450e958..cf8f4266 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -97,8 +97,6 @@ namespace i2p const uint8_t DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP = 0x08; // 1000 const uint8_t DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP = 0x0C; // 1100 - const unsigned int MAX_NUM_TRANSIT_TUNNELS = 2500; - namespace tunnel { class InboundTunnel; @@ -259,6 +257,9 @@ namespace tunnel std::vector > m_TunnelMsgs, m_TunnelGatewayMsgs; }; + + const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 2500; + void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels); } #endif From 4431d5063541451258cf19bd8886f6107efb74a4 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 20 Apr 2016 15:02:11 -0400 Subject: [PATCH 1215/6300] limits.transittunnels --- docs/configuration.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 14db728a..2a639be3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -61,7 +61,9 @@ 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 -* --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 Config files ------------ From d582c30f6e45f6a47bfcdc1e7608bb03ecd2dc6d Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 24 Apr 2016 17:32:24 -0400 Subject: [PATCH 1216/6300] allow same port at different interfaces --- ClientContext.cpp | 5 +++-- ClientContext.h | 6 +++--- I2PService.h | 3 +++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 59f11f21..2eb81af2 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -328,7 +328,8 @@ namespace client localDestination = CreateNewLocalDestination (k, false, &options); } auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); - if (m_ClientTunnels.insert (std::make_pair (port, std::unique_ptr(clientTunnel))).second) + if (m_ClientTunnels.insert (std::make_pair (clientTunnel->GetAcceptor ().local_endpoint (), + std::unique_ptr(clientTunnel))).second) clientTunnel->Start (); else LogPrint (eLogError, "Clients: I2P client tunnel with port ", port, " already exists"); @@ -382,7 +383,7 @@ namespace client serverTunnel->SetAccessList (idents); } if (m_ServerTunnels.insert (std::make_pair ( - std::make_tuple (localDestination->GetIdentHash (), inPort), + std::make_pair (localDestination->GetIdentHash (), inPort), std::unique_ptr(serverTunnel))).second) serverTunnel->Start (); else diff --git a/ClientContext.h b/ClientContext.h index a05c2161..3381228b 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -2,9 +2,9 @@ #define CLIENT_CONTEXT_H__ #include -#include #include #include +#include #include "Destination.h" #include "HTTPProxy.h" #include "SOCKS.h" @@ -78,8 +78,8 @@ namespace client i2p::proxy::HTTPProxy * m_HttpProxy; i2p::proxy::SOCKSProxy * m_SocksProxy; - std::map > m_ClientTunnels; // port->tunnel - std::map, std::unique_ptr > m_ServerTunnels; // ->tunnel + std::map > m_ClientTunnels; // local endpoint->tunnel + std::map, std::unique_ptr > m_ServerTunnels; // ->tunnel SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; diff --git a/I2PService.h b/I2PService.h index 251a379a..2df11909 100644 --- a/I2PService.h +++ b/I2PService.h @@ -118,6 +118,9 @@ namespace client void Start (); //If you override this make sure you call it from the children void Stop (); + + const boost::asio::ip::tcp::acceptor& GetAcceptor () const { return m_Acceptor; }; + protected: virtual std::shared_ptr CreateHandler(std::shared_ptr socket) = 0; virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; } From 85840872ab50fe7e1af312d51912a3ebba3362f9 Mon Sep 17 00:00:00 2001 From: weekendi2p Date: Tue, 26 Apr 2016 19:39:10 +0200 Subject: [PATCH 1217/6300] family: volatile.crt --- contrib/certificates/family/volatile.crt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 contrib/certificates/family/volatile.crt diff --git a/contrib/certificates/family/volatile.crt b/contrib/certificates/family/volatile.crt new file mode 100644 index 00000000..928c7f39 --- /dev/null +++ b/contrib/certificates/family/volatile.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBxDCCAWmgAwIBAgIJAJnJIdKHYwWcMAoGCCqGSM49BAMCMGcxCzAJBgNVBAYT +AkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRn +aXRzIFB0eSBMdGQxIDAeBgNVBAMMF3ZvbGF0aWxlLmZhbWlseS5pMnAubmV0MB4X +DTE2MDQyNjE1MjAyNloXDTI2MDQyNDE1MjAyNlowZzELMAkGA1UEBhMCQVUxEzAR +BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 +IEx0ZDEgMB4GA1UEAwwXdm9sYXRpbGUuZmFtaWx5LmkycC5uZXQwWTATBgcqhkjO +PQIBBggqhkjOPQMBBwNCAARf6LBfbbfL6HInvC/4wAGaN3rj0eeLE/OdBpA93R3L +s8EUp0YTEJHWPo9APiKMmAwQSsMJfjhNrbp+UWEnnx2LMAoGCCqGSM49BAMCA0kA +MEYCIQDpQu2KPV5G1JOFLoZvdj+rcvEnjxM/FxkaqikwkVx8FAIhANP7DkUal+GT +SuiCtcqM4QyIBsfsCJBWEMzovft164Bo +-----END CERTIFICATE----- From a78caa2976d3d96f8ac2defb7622c48e456e0630 Mon Sep 17 00:00:00 2001 From: weekendi2p Date: Wed, 27 Apr 2016 00:31:33 +0200 Subject: [PATCH 1218/6300] added SetFamilyString(); GetFamilyString() and shows family in webiface --- HTTPServer.cpp | 1 + RouterContext.cpp | 12 ++++++++++++ RouterContext.h | 2 ++ 3 files changed, 15 insertions(+) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 6c2c6112..08b67e22 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -417,6 +417,7 @@ namespace util default: s << "Unknown"; } s << "
\r\n"; + s << "Family: " << i2p::context.GetFamilyString() << "
\r\n"; s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; s << "Received: "; s << std::fixed << std::setprecision(2); diff --git a/RouterContext.cpp b/RouterContext.cpp index f35d8426..4cdf13fe 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -149,11 +149,23 @@ namespace i2p UpdateRouterInfo (); } + std::string RouterContext::GetFamilyString () const + { + return m_FamilyString; + } + void RouterContext::SetFamily (const std::string& family) { + + m_FamilyString = family; + if (m_FamilyString.length() == 0) + m_FamilyString = "<undefined>"; + std::string signature; if (family.length () > 0) + { signature = i2p::data::CreateFamilySignature (family, GetIdentHash ()); + } if (signature.length () > 0) { m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY, family); diff --git a/RouterContext.h b/RouterContext.h index 9766c66e..1b0f7ed2 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -59,6 +59,7 @@ namespace i2p bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); void SetFamily (const std::string& family); + std::string GetFamilyString () const; void SetBandwidth (int limit); /* in kilobytes */ void SetBandwidth (char L); /* by letter */ bool AcceptsTunnels () const { return m_AcceptsTunnels; }; @@ -100,6 +101,7 @@ namespace i2p i2p::data::PrivateKeys m_Keys; uint64_t m_LastUpdateTime; bool m_AcceptsTunnels, m_IsFloodfill; + std::string m_FamilyString; uint64_t m_StartupTime; // in seconds since epoch uint32_t m_BandwidthLimit; // allowed bandwidth RouterStatus m_Status; From 61e8becd38e2b1af4948fa56333f8ee82d66040b Mon Sep 17 00:00:00 2001 From: weekendi2p Date: Wed, 27 Apr 2016 00:48:23 +0200 Subject: [PATCH 1219/6300] wrong file version --- Daemon.cpp | 1 + RouterContext.cpp | 14 ++++++++------ RouterContext.h | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 81bbcdd5..2a32ff04 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -189,6 +189,7 @@ namespace i2p std::string family; i2p::config::GetOption("family", family); i2p::context.SetFamily (family); + i2p::context.SetFamilyString (family); if (family.length () > 0) LogPrint(eLogInfo, "Daemon: family set to ", family); diff --git a/RouterContext.cpp b/RouterContext.cpp index 4cdf13fe..bc1f3fc1 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -149,6 +149,14 @@ namespace i2p UpdateRouterInfo (); } + void RouterContext::SetFamilyString (const std::string& family) + { + if (family.length() > 0) + m_FamilyString = family; + else + m_FamilyString = "<undefined>"; + } + std::string RouterContext::GetFamilyString () const { return m_FamilyString; @@ -157,15 +165,9 @@ namespace i2p void RouterContext::SetFamily (const std::string& family) { - m_FamilyString = family; - if (m_FamilyString.length() == 0) - m_FamilyString = "<undefined>"; - std::string signature; if (family.length () > 0) - { signature = i2p::data::CreateFamilySignature (family, GetIdentHash ()); - } if (signature.length () > 0) { m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY, family); diff --git a/RouterContext.h b/RouterContext.h index 1b0f7ed2..def89383 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -59,6 +59,7 @@ namespace i2p bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); void SetFamily (const std::string& family); + void SetFamilyString (const std::string& family); std::string GetFamilyString () const; void SetBandwidth (int limit); /* in kilobytes */ void SetBandwidth (char L); /* by letter */ From ebee94fb11b436add295ae791cb1fe53c6e99d7d Mon Sep 17 00:00:00 2001 From: weekendi2p Date: Wed, 27 Apr 2016 01:19:27 +0200 Subject: [PATCH 1220/6300] removed 1 blank line.. --- RouterContext.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index bc1f3fc1..29891169 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -164,7 +164,6 @@ namespace i2p void RouterContext::SetFamily (const std::string& family) { - std::string signature; if (family.length () > 0) signature = i2p::data::CreateFamilySignature (family, GetIdentHash ()); From 7cf171671db8de9f7dfb34029d2ec7c1db94709c Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1221/6300] * HTTPConnection::reply : to_buffers() -> to_string() --- HTTPServer.cpp | 57 +++++++++++++++++++++----------------------------- HTTPServer.h | 4 ++-- 2 files changed, 26 insertions(+), 35 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 6c2c6112..6897a347 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -212,42 +212,32 @@ namespace util } // namespace misc_strings - std::vector HTTPConnection::reply::to_buffers(int status) + std::string HTTPConnection::reply::to_string(int code) { - std::vector buffers; + std::stringstream ss(""); if (headers.size () > 0) { - status_string = "HTTP/1.1 "; - status_string += std::to_string (status); - status_string += " "; - switch (status) + const char *status; + switch (code) { - case 105: status_string += "Name Not Resolved"; break; - case 200: status_string += "OK"; break; - case 400: status_string += "Bad Request"; break; - case 404: status_string += "Not Found"; break; - case 408: status_string += "Request Timeout"; break; - case 500: status_string += "Internal Server Error"; break; - case 502: status_string += "Bad Gateway"; break; - case 503: status_string += "Not Implemented"; break; - case 504: status_string += "Gateway Timeout"; break; - default: status_string += "WTF"; + case 105: status = "Name Not Resolved"; break; + case 200: status = "OK"; break; + case 400: status = "Bad Request"; break; + case 404: status = "Not Found"; break; + case 408: status = "Request Timeout"; break; + case 500: status = "Internal Server Error"; break; + case 502: status = "Bad Gateway"; break; + case 503: status = "Not Implemented"; break; + case 504: status = "Gateway Timeout"; break; + default: status = "WTF"; } - buffers.push_back(boost::asio::buffer(status_string, status_string.size())); - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); - - for (std::size_t i = 0; i < headers.size(); ++i) - { - header& h = headers[i]; - buffers.push_back(boost::asio::buffer(h.name)); - buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator)); - buffers.push_back(boost::asio::buffer(h.value)); - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); - } - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); - } - buffers.push_back(boost::asio::buffer(content)); - return buffers; + ss << "HTTP/1.1 " << code << "" << status << HTTP_CRLF; + for (header & h : headers) { + ss << h.name << HTTP_HEADER_KV_SEP << h.value << HTTP_CRLF; + } + ss << HTTP_CRLF; /* end of headers */ + } + return ss.str(); } void HTTPConnection::Terminate () @@ -914,8 +904,9 @@ namespace util m_Reply.headers[2].name = "Content-Type"; m_Reply.headers[2].value = "text/html"; } - - boost::asio::async_write (*m_Socket, m_Reply.to_buffers(status), + std::string res = m_Reply.to_string(status); + + boost::asio::async_write (*m_Socket, res, std::bind (&HTTPConnection::HandleWriteReply, shared_from_this (), std::placeholders::_1)); } diff --git a/HTTPServer.h b/HTTPServer.h index 66083d85..889afca9 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -39,8 +39,8 @@ namespace util struct reply { std::vector
headers; - std::string status_string, content; - std::vector to_buffers (int status); + std::string status; + std::string to_string (int code); }; public: From 7a461c168490a98450e716d2972c46672740c6f0 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1222/6300] * HTTPServer.{cpp,h}: move #include to one place --- HTTPServer.cpp | 7 +++++++ HTTPServer.h | 12 +----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 6897a347..bf895ced 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -1,7 +1,13 @@ #include #include +#include +#include +#include + +#include #include #include + #include "Base.h" #include "FS.h" #include "Log.h" @@ -9,6 +15,7 @@ #include "TransitTunnel.h" #include "Transports.h" #include "NetDb.h" +#include "LeaseSet.h" #include "I2PEndian.h" #include "Streaming.h" #include "Destination.h" diff --git a/HTTPServer.h b/HTTPServer.h index 889afca9..ead03cde 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -1,14 +1,6 @@ #ifndef HTTP_SERVER_H__ #define HTTP_SERVER_H__ -#include -#include -#include -#include -#include -#include "LeaseSet.h" -#include "Streaming.h" - namespace i2p { namespace util @@ -135,6 +127,4 @@ namespace util } } -#endif - - +#endif /* HTTP_SERVER_H__ */ From 6ed709d6e6b2af7516de4ceea69753a73f82428c Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1223/6300] * HTTPServer.{cpp,h}: extract itoopie{Image,Favicon} from HTTPConnection (!) class --- HTTPServer.cpp | 10 ++++++---- HTTPServer.h | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index bf895ced..2c0f3ea5 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -30,7 +30,7 @@ namespace i2p { namespace util { - const std::string HTTPConnection::itoopieImage = + const char *itoopieImage = "\"ICToopie"; - const std::string HTTPConnection::itoopieFavicon = + const char *itoopieFavicon = "data:image/png;base64," "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv" "8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4wOGVynO" @@ -357,7 +357,7 @@ namespace util // Html5 head start s << "\r\n"; // TODO: Add support for locale. s << "\r\n\r\n"; // TODO: Find something to parse html/template system. This is horrible. - s << "\r\n"; + s << "\r\n"; s << "Purple I2P " << VERSION " Webconsole\r\n"; s << "\r\n"; + + const char HTTP_PAGE_TUNNELS[] = "tunnels"; + const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels"; + const char HTTP_PAGE_TRANSPORTS[] = "transports"; + const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations"; + const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination"; + const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions"; + const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; + const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; + const char HTTP_PAGE_JUMPSERVICES[] = "jumpservices"; const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels"; const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; - const char HTTP_COMMAND_LOCAL_DESTINATIONS[] = "local_destinations"; - const char HTTP_COMMAND_LOCAL_DESTINATION[] = "local_destination"; const char HTTP_PARAM_BASE32_ADDRESS[] = "b32"; - const char HTTP_COMMAND_SAM_SESSIONS[] = "sam_sessions"; - const char HTTP_COMMAND_SAM_SESSION[] = "sam_session"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; - const char HTTP_COMMAND_I2P_TUNNELS[] = "i2p_tunnels"; - const char HTTP_COMMAND_JUMPSERVICES[] = "jumpservices="; const char HTTP_PARAM_ADDRESS[] = "address"; void HTTPConnection::Terminate () @@ -282,52 +299,53 @@ namespace http { { std::stringstream s; // Html5 head start - s << "\r\n"; // TODO: Add support for locale. - s << "\r\n\r\n"; // TODO: Find something to parse html/template system. This is horrible. - s << "\r\n"; - s << "Purple I2P " << VERSION " Webconsole\r\n"; - s << "\r\n\r\n\r\n"; - s << "
i2pd webconsole
"; - s << "
"; - s << "
\r\n"; - s << "Main page
\r\n
\r\n"; - s << "Local destinations
\r\n"; - s << "Tunnels
\r\n"; - s << "Transit tunnels
\r\n"; - s << "Transports
\r\n
\r\n"; - s << "I2P tunnels
\r\n"; + s << + "\r\n" + "\r\n" /* TODO: Add support for locale */ + " \r\n" + " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ + " \r\n" + " Purple I2P " VERSION " Webconsole\r\n" + << cssStyles << + "\r\n"; + s << + "\r\n" + "
i2pd webconsole
\r\n" + "
\r\n" + "
\r\n" + " Main page
\r\n
\r\n" + " Local destinations
\r\n" + " Tunnels
\r\n" + " Transit tunnels
\r\n" + " Transports
\r\n" + " I2P tunnels
\r\n" + " Jump services
\r\n" + ; if (i2p::client::context.GetSAMBridge ()) - s << "SAM sessions
\r\n
\r\n"; + s << " SAM sessions
\r\n"; + /* commands */ + s << "
\r\n"; + s << " Run peer test
\r\n"; if (i2p::context.AcceptsTunnels ()) - s << "Stop accepting tunnels
\r\n
\r\n"; + s << " Stop accepting tunnels
\r\n"; else - s << "Start accepting tunnels
\r\n
\r\n"; - s << "Run peer test
\r\n
\r\n"; - s << "Jump services
\r\n
\r\n"; - s << "
"; - if (request.uri.find("cmd=") != std::string::npos) + s << " Start accepting tunnels
\r\n"; + s << "
\r\n"; + s << "
"; + if (request.uri.find("page=") != std::string::npos) + HandlePage (s, request.uri); + else if (request.uri.find("cmd=") != std::string::npos) HandleCommand (s, request.uri); else - FillContent (s); - s << "
\r\n\r\n"; + ShowStatus (s); + s << + "
\r\n" + "\r\n" + "\r\n"; SendReply (s.str ()); } - void HTTPConnection::FillContent (std::stringstream& s) + void HTTPConnection::ShowStatus (std::stringstream& s) { s << "Uptime: " << boost::posix_time::to_simple_string ( boost::posix_time::time_duration (boost::posix_time::seconds ( @@ -396,6 +414,38 @@ namespace http { s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n"; } + void HTTPConnection::HandlePage (std::stringstream& s, const std::string & uri) + { + std::map params; + std::string page(""); + URL url; + + url.parse(uri); + url.parse_query(params); + page = params["page"]; + + if (page == HTTP_PAGE_TRANSPORTS) + ShowTransports (s); + else if (page == HTTP_PAGE_TUNNELS) + ShowTunnels (s); + else if (page == HTTP_PAGE_JUMPSERVICES) + ShowJumpServices (s, params["address"]); + else if (page == HTTP_PAGE_TRANSIT_TUNNELS) + ShowTransitTunnels (s); + else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) + ShowLocalDestinations (s); + else if (page == HTTP_PAGE_LOCAL_DESTINATION) + ShowLocalDestination (s, params["b32"]); + else if (page == HTTP_PAGE_SAM_SESSIONS) + ShowSAMSessions (s); + else if (page == HTTP_PAGE_SAM_SESSION) + ShowSAMSession (s, params["sam_id"]); + else if (page == HTTP_PAGE_I2P_TUNNELS) + ShowI2PTunnels (s); + else + SendError("Unknown page: " + page); + } + void HTTPConnection::HandleCommand (std::stringstream& s, const std::string & uri) { std::map params; @@ -406,30 +456,14 @@ namespace http { url.parse_query(params); cmd = params["cmd"]; - if (cmd == HTTP_COMMAND_TRANSPORTS) - ShowTransports (s); - else if (cmd == HTTP_COMMAND_TUNNELS) - ShowTunnels (s); - else if (cmd == HTTP_COMMAND_JUMPSERVICES) - ShowJumpServices (s, params["address"]); - else if (cmd == HTTP_COMMAND_TRANSIT_TUNNELS) - ShowTransitTunnels (s); - else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS) + if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS) StartAcceptingTunnels (s); else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS) StopAcceptingTunnels (s); else if (cmd == HTTP_COMMAND_RUN_PEER_TEST) RunPeerTest (s); - else if (cmd == HTTP_COMMAND_LOCAL_DESTINATIONS) - ShowLocalDestinations (s); - else if (cmd == HTTP_COMMAND_LOCAL_DESTINATION) - ShowLocalDestination (s, params["b32"]); - else if (cmd == HTTP_COMMAND_SAM_SESSIONS) - ShowSAMSessions (s); - else if (cmd == HTTP_COMMAND_SAM_SESSION) - ShowSAMSession (s, params["sam_id"]); - else if (cmd == HTTP_COMMAND_I2P_TUNNELS) - ShowI2PTunnels (s); + else + SendError("Unknown command: " + cmd); } void HTTPConnection::ShowJumpServices (std::stringstream& s, const std::string& address) @@ -448,7 +482,7 @@ namespace http { for (auto& it: i2p::client::context.GetDestinations ()) { auto ident = it.second->GetIdentHash ();; - s << ""; + s << ""; s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; } } @@ -548,9 +582,9 @@ namespace http { it->Print (s); auto state = it->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; + s << " " << "Failed"; else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; + s << " " << "Expiring"; s << " " << (int)it->GetNumSentBytes () << "
\r\n"; s << std::endl; } @@ -560,9 +594,9 @@ namespace http { it->Print (s); auto state = it->GetState (); if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; + s << " " << "Failed"; else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; + s << " " << "Expiring"; s << " " << (int)it->GetNumReceivedBytes () << "
\r\n"; s << std::endl; } @@ -640,7 +674,7 @@ namespace http { { for (auto& it: sam->GetSessions ()) { - s << ""; + s << ""; s << it.first << "
\r\n" << std::endl; } } @@ -656,7 +690,7 @@ namespace http { if (session) { auto& ident = session->localDestination->GetIdentHash(); - s << ""; + s << ""; s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; s << "Streams:
\r\n"; for (auto it: session->ListSockets()) @@ -688,7 +722,7 @@ namespace http { for (auto& it: i2p::client::context.GetClientTunnels ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << ""; s << it.second->GetName () << " ⇠"; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; @@ -697,7 +731,7 @@ namespace http { for (auto& it: i2p::client::context.GetServerTunnels ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << ""; s << it.second->GetName () << " ⇒ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << ":" << it.second->GetLocalPort (); diff --git a/HTTPServer.h b/HTTPServer.h index 89df8fb5..57d6fa59 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -29,21 +29,24 @@ namespace http { void SendError (const std::string& message); void HandleRequest (const HTTPReq & request); - void HandleCommand (std::stringstream& s, const std::string& request); + void HandlePage (std::stringstream& s, const std::string& request); + void HandleCommand (std::stringstream& s, const std::string& request); + /* pages */ void ShowJumpServices (std::stringstream& s, const std::string& address); void ShowTransports (std::stringstream& s); void ShowTunnels (std::stringstream& s); + void ShowStatus (std::stringstream& s); void ShowTransitTunnels (std::stringstream& s); void ShowLocalDestinations (std::stringstream& s); void ShowLocalDestination (std::stringstream& s, const std::string& b32); void ShowSAMSessions (std::stringstream& s); void ShowSAMSession (std::stringstream& s, const std::string& id); void ShowI2PTunnels (std::stringstream& s); + /* commands */ void StartAcceptingTunnels (std::stringstream& s); void StopAcceptingTunnels (std::stringstream& s); void RunPeerTest (std::stringstream& s); - void FillContent (std::stringstream& s); protected: From 9bbff744e991cfa60cb4fa21684b84a06413d712 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1232/6300] * HTTPServer.{cpp,h}: chg HandleRequest() signature --- HTTPServer.cpp | 12 ++++++------ HTTPServer.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 5673e468..80022897 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -271,7 +271,7 @@ namespace http { } if (ret == 0) return; /* need more data */ - HandleRequest (request); + HandleRequest (request.uri); } void HTTPConnection::HandleWriteReply (const boost::system::error_code& ecode) @@ -295,7 +295,7 @@ namespace http { AsyncStreamReceive (); } - void HTTPConnection::HandleRequest (const HTTPReq &request) + void HTTPConnection::HandleRequest (const std::string &uri) { std::stringstream s; // Html5 head start @@ -332,10 +332,10 @@ namespace http { s << " Start accepting tunnels
\r\n"; s << "\r\n"; s << "
"; - if (request.uri.find("page=") != std::string::npos) - HandlePage (s, request.uri); - else if (request.uri.find("cmd=") != std::string::npos) - HandleCommand (s, request.uri); + if (uri.find("page=") != std::string::npos) + HandlePage (s, uri); + else if (uri.find("cmd=") != std::string::npos) + HandleCommand (s, uri); else ShowStatus (s); s << diff --git a/HTTPServer.h b/HTTPServer.h index 57d6fa59..28962dde 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -28,7 +28,7 @@ namespace http { void SendReply (const std::string& content, int code = 200); void SendError (const std::string& message); - void HandleRequest (const HTTPReq & request); + void HandleRequest (const std::string& uri); void HandlePage (std::stringstream& s, const std::string& request); void HandleCommand (std::stringstream& s, const std::string& request); From 48b3959cfb9b71d5765c70439ab7a7b94a93dae6 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1233/6300] * HTTPServer.{cpp,h}: cleanup --- HTTPServer.cpp | 18 ++++++++---------- HTTPServer.h | 4 ---- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 80022897..accca463 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -836,11 +836,10 @@ namespace http { { m_Acceptor.close(); m_Service.stop (); - if (m_Thread) - { - m_Thread->join (); - m_Thread = nullptr; - } + if (m_Thread) { + m_Thread->join (); + m_Thread = nullptr; + } } void HTTPServer::Run () @@ -858,11 +857,10 @@ namespace http { void HTTPServer::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr newSocket) { - if (!ecode) - { - CreateConnection(newSocket); - Accept (); - } + if (ecode) + return; + CreateConnection(newSocket); + Accept (); } void HTTPServer::CreateConnection(std::shared_ptr newSocket) diff --git a/HTTPServer.h b/HTTPServer.h index 28962dde..a6ef52cc 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -6,7 +6,6 @@ namespace http { extern const char *itoopieImage; extern const char *itoopieFavicon; const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192; - const int HTTP_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds class HTTPConnection: public std::enable_shared_from_this { @@ -59,9 +58,6 @@ namespace http { protected: virtual void RunRequest (); - - public: - }; class HTTPServer From 4fa4ba6301d4aadd19b5155218a9efc48832b745 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1234/6300] * HTTPServer.cpp: move known jump services to std::map --- HTTPServer.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index accca463..73c9c791 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -226,7 +226,12 @@ namespace http { const char HTTP_PARAM_BASE32_ADDRESS[] = "b32"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; - + + std::map jumpservices = { + { "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" }, + { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, + }; + void HTTPConnection::Terminate () { if (!m_Stream) return; @@ -468,12 +473,16 @@ namespace http { void HTTPConnection::ShowJumpServices (std::stringstream& s, const std::string& address) { - s << "
"; - s << ""; - s << "
\r\n"; - s << "Jump services for " << address << ""; - s << ""; + s << "
"; + s << ""; + s << ""; + s << ""; + s << "
\r\n"; + s << "Jump services for " << address << "\r\n\r\n"; } void HTTPConnection::ShowLocalDestinations (std::stringstream& s) From 2a1fe99a2981dbfe6966d60379155346b1c723c2 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1235/6300] * HTTPServer.{cpp,h}: drop rest of streaming support --- HTTPServer.cpp | 52 +++----------------------------------------------- HTTPServer.h | 9 +++------ 2 files changed, 6 insertions(+), 55 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 73c9c791..5f195792 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -17,8 +17,6 @@ #include "NetDb.h" #include "HTTP.h" #include "LeaseSet.h" -#include "I2PEndian.h" -#include "Streaming.h" #include "Destination.h" #include "RouterContext.h" #include "ClientContext.h" @@ -234,9 +232,6 @@ namespace http { void HTTPConnection::Terminate () { - if (!m_Stream) return; - m_Stream->Close (); - m_Stream = nullptr; m_Socket->close (); } @@ -251,14 +246,9 @@ namespace http { { if (!ecode) { - if (!m_Stream) // new request - { - m_Buffer[bytes_transferred] = '\0'; - m_BufferLen = bytes_transferred; - RunRequest(); - } - else // follow-on - m_Stream->Send ((uint8_t *)m_Buffer, bytes_transferred); + m_Buffer[bytes_transferred] = '\0'; + m_BufferLen = bytes_transferred; + RunRequest(); Receive (); } else if (ecode != boost::asio::error::operation_aborted) @@ -289,17 +279,6 @@ namespace http { } } - void HTTPConnection::HandleWrite (const boost::system::error_code& ecode) - { - if (ecode || (m_Stream && !m_Stream->IsOpen ())) - { - if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - else // data keeps coming - AsyncStreamReceive (); - } - void HTTPConnection::HandleRequest (const std::string &uri) { std::stringstream s; @@ -769,31 +748,6 @@ namespace http { s << "Peer test is running" << std::endl; } - void HTTPConnection::AsyncStreamReceive () - { - if (m_Stream) - m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, 8192), - std::bind (&HTTPConnection::HandleStreamReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2), - 45); // 45 seconds timeout - } - - void HTTPConnection::HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (!ecode) - { - boost::asio::async_write (*m_Socket, boost::asio::buffer (m_StreamBuffer, bytes_transferred), - std::bind (&HTTPConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); - } - else - { - if (ecode == boost::asio::error::timed_out) - SendError ("Host not responding"); - else if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - } - void HTTPConnection::SendReply (const std::string& content, int code) { std::time_t time_now = std::time(nullptr); diff --git a/HTTPServer.h b/HTTPServer.h index a6ef52cc..cafa9b46 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -13,17 +13,15 @@ namespace http { HTTPConnection (std::shared_ptr socket): m_Socket (socket), m_Timer (socket->get_io_service ()), - m_Stream (nullptr), m_BufferLen (0) {}; + m_BufferLen (0) {}; void Receive (); private: void Terminate (); void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void AsyncStreamReceive (); - void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleWriteReply(const boost::system::error_code& ecode); - void HandleWrite (const boost::system::error_code& ecode); + void SendReply (const std::string& content, int code = 200); void SendError (const std::string& message); @@ -51,8 +49,7 @@ namespace http { std::shared_ptr m_Socket; boost::asio::deadline_timer m_Timer; - std::shared_ptr m_Stream; - char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1], m_StreamBuffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; + char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; size_t m_BufferLen; protected: From fd928e8d12167c87ff63f540d7a2d0c9ff9cd78f Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1236/6300] * HTTPServer.h: not virtual: not inherited anywhere --- HTTPServer.h | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/HTTPServer.h b/HTTPServer.h index cafa9b46..18c6bed8 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -25,6 +25,7 @@ namespace http { void SendReply (const std::string& content, int code = 200); void SendError (const std::string& message); + void RunRequest (); void HandleRequest (const std::string& uri); void HandlePage (std::stringstream& s, const std::string& request); void HandleCommand (std::stringstream& s, const std::string& request); @@ -45,16 +46,12 @@ namespace http { void StopAcceptingTunnels (std::stringstream& s); void RunPeerTest (std::stringstream& s); - protected: + private: std::shared_ptr m_Socket; boost::asio::deadline_timer m_Timer; char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; size_t m_BufferLen; - - protected: - - virtual void RunRequest (); }; class HTTPServer @@ -62,7 +59,7 @@ namespace http { public: HTTPServer (const std::string& address, int port); - virtual ~HTTPServer (); + ~HTTPServer (); void Start (); void Stop (); @@ -73,6 +70,7 @@ namespace http { void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr newSocket); + void CreateConnection(std::shared_ptr newSocket); private: @@ -80,9 +78,6 @@ namespace http { boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; boost::asio::ip::tcp::acceptor m_Acceptor; - - protected: - virtual void CreateConnection(std::shared_ptr newSocket); }; } // http } // i2p From 0c8fdfca7d9670a692891cebb25ef8848411a794 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1237/6300] * HTTPServer.{cpp,h}: merge HandleWriteReply & Terminate : the same purpose --- HTTPServer.cpp | 36 +++++++++++++++--------------------- HTTPServer.h | 3 +-- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 5f195792..c7b91fd0 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -230,11 +230,6 @@ namespace http { { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, }; - void HTTPConnection::Terminate () - { - m_Socket->close (); - } - void HTTPConnection::Receive () { m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), @@ -244,15 +239,15 @@ namespace http { void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { - if (!ecode) - { - m_Buffer[bytes_transferred] = '\0'; - m_BufferLen = bytes_transferred; - RunRequest(); - Receive (); + if (ecode) { + if (ecode != boost::asio::error::operation_aborted) + Terminate (ecode); + return; } - else if (ecode != boost::asio::error::operation_aborted) - Terminate (); + m_Buffer[bytes_transferred] = '\0'; + m_BufferLen = bytes_transferred; + RunRequest(); + Receive (); } void HTTPConnection::RunRequest () @@ -269,14 +264,13 @@ namespace http { HandleRequest (request.uri); } - void HTTPConnection::HandleWriteReply (const boost::system::error_code& ecode) + void HTTPConnection::Terminate (const boost::system::error_code& ecode) { - if (ecode != boost::asio::error::operation_aborted) - { - boost::system::error_code ignored_ec; - m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); - Terminate (); - } + if (ecode == boost::asio::error::operation_aborted) + return; + boost::system::error_code ignored_ec; + m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); + m_Socket->close (); } void HTTPConnection::HandleRequest (const std::string &uri) @@ -767,7 +761,7 @@ namespace http { buffers.push_back(boost::asio::buffer(content)); boost::asio::async_write (*m_Socket, buffers, - std::bind (&HTTPConnection::HandleWriteReply, shared_from_this (), std::placeholders::_1)); + std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); } void HTTPConnection::SendError(const std::string& content) diff --git a/HTTPServer.h b/HTTPServer.h index 18c6bed8..f3070502 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -18,9 +18,8 @@ namespace http { private: - void Terminate (); void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandleWriteReply(const boost::system::error_code& ecode); + void Terminate (const boost::system::error_code& ecode); void SendReply (const std::string& content, int code = 200); void SendError (const std::string& message); From 4d98a64000bf3ccce04f5165a59c3deb3f29beac Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1238/6300] * HTTPServer.{cpp,h}: extract html-rendering methods from class --- HTTPServer.cpp | 734 ++++++++++++++++++++++++------------------------- HTTPServer.h | 16 -- 2 files changed, 367 insertions(+), 383 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index c7b91fd0..98080808 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -230,6 +230,373 @@ namespace http { { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, }; + void ShowStatus (std::stringstream& s) + { + s << "Uptime: " << boost::posix_time::to_simple_string ( + boost::posix_time::time_duration (boost::posix_time::seconds ( + i2p::context.GetUptime ()))) << "
\r\n"; + s << "Status: "; + switch (i2p::context.GetStatus ()) + { + case eRouterStatusOK: s << "OK"; break; + case eRouterStatusTesting: s << "Testing"; break; + case eRouterStatusFirewalled: s << "Firewalled"; break; + default: s << "Unknown"; + } + s << "
\r\n"; + s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; + s << "Received: "; + s << std::fixed << std::setprecision(2); + auto numKBytesReceived = (double) i2p::transport::transports.GetTotalReceivedBytes () / 1024; + if (numKBytesReceived < 1024) + s << numKBytesReceived << " KiB"; + else if (numKBytesReceived < 1024 * 1024) + s << numKBytesReceived / 1024 << " MiB"; + else + s << numKBytesReceived / 1024 / 1024 << " GiB"; + s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " KiB/s)
\r\n"; + s << "Sent: "; + auto numKBytesSent = (double) i2p::transport::transports.GetTotalSentBytes () / 1024; + if (numKBytesSent < 1024) + s << numKBytesSent << " KiB"; + else if (numKBytesSent < 1024 * 1024) + s << numKBytesSent / 1024 << " MiB"; + else + s << numKBytesSent / 1024 / 1024 << " GiB"; + s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)
\r\n"; + s << "Data path: " << i2p::fs::GetDataDir() << "
\r\n
\r\n"; + s << "Our external address:" << "
\r\n" ; + for (auto address : i2p::context.GetRouterInfo().GetAddresses()) + { + switch (address->transportStyle) + { + case i2p::data::RouterInfo::eTransportNTCP: + if (address->host.is_v6 ()) + s << "NTCP6  "; + else + s << "NTCP  "; + break; + case i2p::data::RouterInfo::eTransportSSU: + if (address->host.is_v6 ()) + s << "SSU6     "; + else + s << "SSU     "; + break; + default: + s << "Unknown  "; + } + s << address->host.to_string() << ":" << address->port << "
\r\n"; + } + s << "
\r\nRouters: " << i2p::data::netdb.GetNumRouters () << " "; + s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; + s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
\r\n"; + + size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); + clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); + size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); + + s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; + s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n"; + } + + void ShowJumpServices (std::stringstream& s, const std::string& address) + { + s << "
"; + s << ""; + s << ""; + s << ""; + s << "
\r\n"; + s << "Jump services for " << address << "\r\n\r\n"; + } + + void ShowLocalDestinations (std::stringstream& s) + { + s << "Local Destinations:
\r\n
\r\n"; + for (auto& it: i2p::client::context.GetDestinations ()) + { + auto ident = it.second->GetIdentHash ();; + s << ""; + s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; + } + } + + void ShowLocalDestination (std::stringstream& s, const std::string& b32) + { + s << "Local Destination:
\r\n
\r\n"; + i2p::data::IdentHash ident; + ident.FromBase32 (b32); + auto dest = i2p::client::context.FindLocalDestination (ident); + if (dest) + { + s << "Base64:
\r\n
\r\n
\r\n"; + s << "LeaseSets: " << dest->GetNumRemoteLeaseSets () << "
\r\n"; + auto pool = dest->GetTunnelPool (); + if (pool) + { + s << "Tunnels:
\r\n"; + for (auto it: pool->GetOutboundTunnels ()) + { + it->Print (s); + auto state = it->GetState (); + if (state == i2p::tunnel::eTunnelStateFailed) + s << " " << "Failed"; + else if (state == i2p::tunnel::eTunnelStateExpiring) + s << " " << "Exp"; + s << "
\r\n" << std::endl; + } + for (auto it: pool->GetInboundTunnels ()) + { + it->Print (s); + auto state = it->GetState (); + if (state == i2p::tunnel::eTunnelStateFailed) + s << " " << "Failed"; + else if (state == i2p::tunnel::eTunnelStateExpiring) + s << " " << "Exp"; + s << "
\r\n" << std::endl; + } + } + s << "Tags
Incoming: " << dest->GetNumIncomingTags () << "
Outgoing:
" << std::endl; + for (auto it: dest->GetSessions ()) + { + s << i2p::client::context.GetAddressBook ().ToAddress(it.first) << " "; + s << it.second->GetNumOutgoingTags () << "
" << std::endl; + } + s << "
" << std::endl; + // s << "
\r\nStreams:
\r\n"; + // for (auto it: dest->GetStreamingDestination ()->GetStreams ()) + // { + // s << it.first << "->" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << " "; + // s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + // s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]"; + // s << "[buf:" << it.second->GetSendBufferSize () << "]"; + // s << "[RTT:" << it.second->GetRTT () << "]"; + // s << "[Window:" << it.second->GetWindowSize () << "]"; + // s << "[Status:" << (int)it.second->GetStatus () << "]"; + // s << "
\r\n"<< std::endl; + // } + s << "
\r\n
Streams
StreamIDDestinationSentReceivedOutInBufRTTWindowStatus
" << it.first << "" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << "" << it.second->GetNumSentBytes () << "" << it.second->GetNumReceivedBytes () << "" << it.second->GetSendQueueSize () << "" << it.second->GetReceiveQueueSize () << "" << it.second->GetSendBufferSize () << "" << it.second->GetRTT () << "" << it.second->GetWindowSize () << "" << (int)it.second->GetStatus () << "
Status
" << it.first << "" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << "" << it.second->GetNumSentBytes () << "" << it.second->GetNumReceivedBytes () << "" << it.second->GetSendQueueSize () << "" << it.second->GetReceiveQueueSize () << "" << it.second->GetSendBufferSize () << "" << it.second->GetRTT () << "" << it.second->GetWindowSize () << "" << (int)it.second->GetStatus () << "" << it->GetSendStreamID () << "" << i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()) << "" << it->GetNumSentBytes () << "" << it->GetNumReceivedBytes () << "" << it->GetSendQueueSize () << "" << it->GetReceiveQueueSize () << "" << it->GetSendBufferSize () << "" << it->GetRTT () << "" << it->GetWindowSize () << "" << (int)it->GetStatus () << "
"; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + + for (auto it: dest->GetAllStreams ()) + { + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << "
\r\n" << std::endl; + } + } + } + + void ShowTunnels (std::stringstream& s) + { + s << "Tunnels:
\r\n
\r\n"; + s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n"; + for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) + { + it->Print (s); + auto state = it->GetState (); + if (state == i2p::tunnel::eTunnelStateFailed) + s << " " << "Failed"; + else if (state == i2p::tunnel::eTunnelStateExpiring) + s << " " << "Expiring"; + s << " " << (int)it->GetNumSentBytes () << "
\r\n"; + s << std::endl; + } + + for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ()) + { + it->Print (s); + auto state = it->GetState (); + if (state == i2p::tunnel::eTunnelStateFailed) + s << " " << "Failed"; + else if (state == i2p::tunnel::eTunnelStateExpiring) + s << " " << "Expiring"; + s << " " << (int)it->GetNumReceivedBytes () << "
\r\n"; + s << std::endl; + } + } + + void ShowTransitTunnels (std::stringstream& s) + { + s << "Transit tunnels:
\r\n
\r\n"; + for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ()) + { + if (std::dynamic_pointer_cast(it)) + s << it->GetTunnelID () << " ⇒ "; + else if (std::dynamic_pointer_cast(it)) + s << " ⇒ " << it->GetTunnelID (); + else + s << " ⇒ " << it->GetTunnelID () << " ⇒ "; + s << " " << it->GetNumTransmittedBytes () << "
\r\n"; + } + } + + void ShowTransports (std::stringstream& s) + { + s << "Transports:
\r\n
\r\n"; + auto ntcpServer = i2p::transport::transports.GetNTCPServer (); + if (ntcpServer) + { + s << "NTCP
\r\n"; + for (auto it: ntcpServer->GetNTCPSessions ()) + { + if (it.second && it.second->IsEstablished ()) + { + // incoming connection doesn't have remote RI + if (it.second->IsOutgoing ()) s << " ⇒ "; + s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " + << it.second->GetSocket ().remote_endpoint().address ().to_string (); + if (!it.second->IsOutgoing ()) s << " ⇒ "; + s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + s << "
\r\n" << std::endl; + } + } + } + auto ssuServer = i2p::transport::transports.GetSSUServer (); + if (ssuServer) + { + s << "
\r\nSSU
\r\n"; + for (auto it: ssuServer->GetSessions ()) + { + auto endpoint = it.second->GetRemoteEndpoint (); + if (it.second->IsOutgoing ()) s << " ⇒ "; + s << endpoint.address ().to_string () << ":" << endpoint.port (); + if (!it.second->IsOutgoing ()) s << " ⇒ "; + s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + if (it.second->GetRelayTag ()) + s << " [itag:" << it.second->GetRelayTag () << "]"; + s << "
\r\n" << std::endl; + } + s << "
\r\nSSU6
\r\n"; + for (auto it: ssuServer->GetSessionsV6 ()) + { + auto endpoint = it.second->GetRemoteEndpoint (); + if (it.second->IsOutgoing ()) s << " ⇒ "; + s << endpoint.address ().to_string () << ":" << endpoint.port (); + if (!it.second->IsOutgoing ()) s << " ⇒ "; + s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; + s << "
\r\n" << std::endl; + } + } + } + + void ShowSAMSessions (std::stringstream& s) + { + s << "SAM Sessions:
\r\n
\r\n"; + auto sam = i2p::client::context.GetSAMBridge (); + if (sam) + { + for (auto& it: sam->GetSessions ()) + { + s << ""; + s << it.first << "
\r\n" << std::endl; + } + } + } + + void ShowSAMSession (std::stringstream& s, const std::string& id) + { + s << "SAM Session:
\r\n
\r\n"; + auto sam = i2p::client::context.GetSAMBridge (); + if (sam) + { + auto session = sam->FindSession (id); + if (session) + { + auto& ident = session->localDestination->GetIdentHash(); + s << ""; + s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; + s << "Streams:
\r\n"; + for (auto it: session->ListSockets()) + { + switch (it->GetSocketType ()) + { + case i2p::client::eSAMSocketTypeSession: + s << "session"; + break; + case i2p::client::eSAMSocketTypeStream: + s << "stream"; + break; + case i2p::client::eSAMSocketTypeAcceptor: + s << "acceptor"; + break; + default: + s << "unknown"; + } + s << " [" << it->GetSocket ().remote_endpoint() << "]"; + s << "
\r\n" << std::endl; + } + } + } + } + + void ShowI2PTunnels (std::stringstream& s) + { + s << "Client Tunnels:
\r\n
\r\n"; + for (auto& it: i2p::client::context.GetClientTunnels ()) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << ""; + s << it.second->GetName () << " ⇠"; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; + } + s << "
\r\nServer Tunnels:
\r\n
\r\n"; + for (auto& it: i2p::client::context.GetServerTunnels ()) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << ""; + s << it.second->GetName () << " ⇒ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << ":" << it.second->GetLocalPort (); + s << "
\r\n"<< std::endl; + } + } + + void StopAcceptingTunnels (std::stringstream& s) + { + s << "Stop Accepting Tunnels:
\r\n
\r\n"; + i2p::context.SetAcceptsTunnels (false); + s << "Accepting tunnels stopped" << std::endl; + } + + void StartAcceptingTunnels (std::stringstream& s) + { + s << "Start Accepting Tunnels:
\r\n
\r\n"; + i2p::context.SetAcceptsTunnels (true); + s << "Accepting tunnels started" << std::endl; + } + + void RunPeerTest (std::stringstream& s) + { + s << "Run Peer Test:
\r\n
\r\n"; + i2p::transport::transports.PeerTest (); + s << "Peer test is running" << std::endl; + } + void HTTPConnection::Receive () { m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), @@ -323,75 +690,6 @@ namespace http { SendReply (s.str ()); } - void HTTPConnection::ShowStatus (std::stringstream& s) - { - s << "Uptime: " << boost::posix_time::to_simple_string ( - boost::posix_time::time_duration (boost::posix_time::seconds ( - i2p::context.GetUptime ()))) << "
\r\n"; - s << "Status: "; - switch (i2p::context.GetStatus ()) - { - case eRouterStatusOK: s << "OK"; break; - case eRouterStatusTesting: s << "Testing"; break; - case eRouterStatusFirewalled: s << "Firewalled"; break; - default: s << "Unknown"; - } - s << "
\r\n"; - s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; - s << "Received: "; - s << std::fixed << std::setprecision(2); - auto numKBytesReceived = (double) i2p::transport::transports.GetTotalReceivedBytes () / 1024; - if (numKBytesReceived < 1024) - s << numKBytesReceived << " KiB"; - else if (numKBytesReceived < 1024 * 1024) - s << numKBytesReceived / 1024 << " MiB"; - else - s << numKBytesReceived / 1024 / 1024 << " GiB"; - s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " KiB/s)
\r\n"; - s << "Sent: "; - auto numKBytesSent = (double) i2p::transport::transports.GetTotalSentBytes () / 1024; - if (numKBytesSent < 1024) - s << numKBytesSent << " KiB"; - else if (numKBytesSent < 1024 * 1024) - s << numKBytesSent / 1024 << " MiB"; - else - s << numKBytesSent / 1024 / 1024 << " GiB"; - s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)
\r\n"; - s << "Data path: " << i2p::fs::GetDataDir() << "
\r\n
\r\n"; - s << "Our external address:" << "
\r\n" ; - for (auto address : i2p::context.GetRouterInfo().GetAddresses()) - { - switch (address->transportStyle) - { - case i2p::data::RouterInfo::eTransportNTCP: - if (address->host.is_v6 ()) - s << "NTCP6  "; - else - s << "NTCP  "; - break; - case i2p::data::RouterInfo::eTransportSSU: - if (address->host.is_v6 ()) - s << "SSU6     "; - else - s << "SSU     "; - break; - default: - s << "Unknown  "; - } - s << address->host.to_string() << ":" << address->port << "
\r\n"; - } - s << "
\r\nRouters: " << i2p::data::netdb.GetNumRouters () << " "; - s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; - s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
\r\n"; - - size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); - clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); - size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); - - s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; - s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n"; - } - void HTTPConnection::HandlePage (std::stringstream& s, const std::string & uri) { std::map params; @@ -444,304 +742,6 @@ namespace http { SendError("Unknown command: " + cmd); } - void HTTPConnection::ShowJumpServices (std::stringstream& s, const std::string& address) - { - s << "
"; - s << ""; - s << ""; - s << ""; - s << "
\r\n"; - s << "Jump services for " << address << "\r\n\r\n"; - } - - void HTTPConnection::ShowLocalDestinations (std::stringstream& s) - { - s << "Local Destinations:
\r\n
\r\n"; - for (auto& it: i2p::client::context.GetDestinations ()) - { - auto ident = it.second->GetIdentHash ();; - s << ""; - s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; - } - } - - void HTTPConnection::ShowLocalDestination (std::stringstream& s, const std::string& b32) - { - s << "Local Destination:
\r\n
\r\n"; - i2p::data::IdentHash ident; - ident.FromBase32 (b32); - auto dest = i2p::client::context.FindLocalDestination (ident); - if (dest) - { - s << "Base64:
\r\n
\r\n
\r\n"; - s << "LeaseSets: " << dest->GetNumRemoteLeaseSets () << "
\r\n"; - auto pool = dest->GetTunnelPool (); - if (pool) - { - s << "Tunnels:
\r\n"; - for (auto it: pool->GetOutboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << "
\r\n" << std::endl; - } - for (auto it: pool->GetInboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << "
\r\n" << std::endl; - } - } - s << "Tags
Incoming: " << dest->GetNumIncomingTags () << "
Outgoing:
" << std::endl; - for (auto it: dest->GetSessions ()) - { - s << i2p::client::context.GetAddressBook ().ToAddress(it.first) << " "; - s << it.second->GetNumOutgoingTags () << "
" << std::endl; - } - s << "
" << std::endl; - // s << "
\r\nStreams:
\r\n"; - // for (auto it: dest->GetStreamingDestination ()->GetStreams ()) - // { - // s << it.first << "->" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << " "; - // s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - // s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]"; - // s << "[buf:" << it.second->GetSendBufferSize () << "]"; - // s << "[RTT:" << it.second->GetRTT () << "]"; - // s << "[Window:" << it.second->GetWindowSize () << "]"; - // s << "[Status:" << (int)it.second->GetStatus () << "]"; - // s << "
\r\n"<< std::endl; - // } - s << "
\r\n
Streams
StreamIDDestinationSentReceivedOutInBufRTTWindowStatus
" << it->GetSendStreamID () << "" << i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()) << "" << it->GetNumSentBytes () << "" << it->GetNumReceivedBytes () << "" << it->GetSendQueueSize () << "" << it->GetReceiveQueueSize () << "" << it->GetSendBufferSize () << "" << it->GetRTT () << "" << it->GetWindowSize () << "" << (int)it->GetStatus () << "
"; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - - for (auto it: dest->GetAllStreams ()) - { - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << "
\r\n" << std::endl; - } - } - } - - void HTTPConnection::ShowTunnels (std::stringstream& s) - { - s << "Tunnels:
\r\n
\r\n"; - s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n"; - for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Expiring"; - s << " " << (int)it->GetNumSentBytes () << "
\r\n"; - s << std::endl; - } - - for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Expiring"; - s << " " << (int)it->GetNumReceivedBytes () << "
\r\n"; - s << std::endl; - } - } - - void HTTPConnection::ShowTransitTunnels (std::stringstream& s) - { - s << "Transit tunnels:
\r\n
\r\n"; - for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ()) - { - if (std::dynamic_pointer_cast(it)) - s << it->GetTunnelID () << " ⇒ "; - else if (std::dynamic_pointer_cast(it)) - s << " ⇒ " << it->GetTunnelID (); - else - s << " ⇒ " << it->GetTunnelID () << " ⇒ "; - s << " " << it->GetNumTransmittedBytes () << "
\r\n"; - } - } - - void HTTPConnection::ShowTransports (std::stringstream& s) - { - s << "Transports:
\r\n
\r\n"; - auto ntcpServer = i2p::transport::transports.GetNTCPServer (); - if (ntcpServer) - { - s << "NTCP
\r\n"; - for (auto it: ntcpServer->GetNTCPSessions ()) - { - if (it.second && it.second->IsEstablished ()) - { - // incoming connection doesn't have remote RI - if (it.second->IsOutgoing ()) s << " ⇒ "; - s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " - << it.second->GetSocket ().remote_endpoint().address ().to_string (); - if (!it.second->IsOutgoing ()) s << " ⇒ "; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - s << "
\r\n" << std::endl; - } - } - } - auto ssuServer = i2p::transport::transports.GetSSUServer (); - if (ssuServer) - { - s << "
\r\nSSU
\r\n"; - for (auto it: ssuServer->GetSessions ()) - { - auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second->IsOutgoing ()) s << " ⇒ "; - s << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it.second->IsOutgoing ()) s << " ⇒ "; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - if (it.second->GetRelayTag ()) - s << " [itag:" << it.second->GetRelayTag () << "]"; - s << "
\r\n" << std::endl; - } - s << "
\r\nSSU6
\r\n"; - for (auto it: ssuServer->GetSessionsV6 ()) - { - auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second->IsOutgoing ()) s << " ⇒ "; - s << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it.second->IsOutgoing ()) s << " ⇒ "; - s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - s << "
\r\n" << std::endl; - } - } - } - - void HTTPConnection::ShowSAMSessions (std::stringstream& s) - { - s << "SAM Sessions:
\r\n
\r\n"; - auto sam = i2p::client::context.GetSAMBridge (); - if (sam) - { - for (auto& it: sam->GetSessions ()) - { - s << ""; - s << it.first << "
\r\n" << std::endl; - } - } - } - - void HTTPConnection::ShowSAMSession (std::stringstream& s, const std::string& id) - { - s << "SAM Session:
\r\n
\r\n"; - auto sam = i2p::client::context.GetSAMBridge (); - if (sam) - { - auto session = sam->FindSession (id); - if (session) - { - auto& ident = session->localDestination->GetIdentHash(); - s << ""; - s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; - s << "Streams:
\r\n"; - for (auto it: session->ListSockets()) - { - switch (it->GetSocketType ()) - { - case i2p::client::eSAMSocketTypeSession: - s << "session"; - break; - case i2p::client::eSAMSocketTypeStream: - s << "stream"; - break; - case i2p::client::eSAMSocketTypeAcceptor: - s << "acceptor"; - break; - default: - s << "unknown"; - } - s << " [" << it->GetSocket ().remote_endpoint() << "]"; - s << "
\r\n" << std::endl; - } - } - } - } - - void HTTPConnection::ShowI2PTunnels (std::stringstream& s) - { - s << "Client Tunnels:
\r\n
\r\n"; - for (auto& it: i2p::client::context.GetClientTunnels ()) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; - s << it.second->GetName () << " ⇠"; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; - } - s << "
\r\nServer Tunnels:
\r\n
\r\n"; - for (auto& it: i2p::client::context.GetServerTunnels ()) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; - s << it.second->GetName () << " ⇒ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << ":" << it.second->GetLocalPort (); - s << "
\r\n"<< std::endl; - } - } - - void HTTPConnection::StopAcceptingTunnels (std::stringstream& s) - { - s << "Stop Accepting Tunnels:
\r\n
\r\n"; - i2p::context.SetAcceptsTunnels (false); - s << "Accepting tunnels stopped" << std::endl; - } - - void HTTPConnection::StartAcceptingTunnels (std::stringstream& s) - { - s << "Start Accepting Tunnels:
\r\n
\r\n"; - i2p::context.SetAcceptsTunnels (true); - s << "Accepting tunnels started" << std::endl; - } - - void HTTPConnection::RunPeerTest (std::stringstream& s) - { - s << "Run Peer Test:
\r\n
\r\n"; - i2p::transport::transports.PeerTest (); - s << "Peer test is running" << std::endl; - } - void HTTPConnection::SendReply (const std::string& content, int code) { std::time_t time_now = std::time(nullptr); diff --git a/HTTPServer.h b/HTTPServer.h index f3070502..06fa4457 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -29,22 +29,6 @@ namespace http { void HandlePage (std::stringstream& s, const std::string& request); void HandleCommand (std::stringstream& s, const std::string& request); - /* pages */ - void ShowJumpServices (std::stringstream& s, const std::string& address); - void ShowTransports (std::stringstream& s); - void ShowTunnels (std::stringstream& s); - void ShowStatus (std::stringstream& s); - void ShowTransitTunnels (std::stringstream& s); - void ShowLocalDestinations (std::stringstream& s); - void ShowLocalDestination (std::stringstream& s, const std::string& b32); - void ShowSAMSessions (std::stringstream& s); - void ShowSAMSession (std::stringstream& s, const std::string& id); - void ShowI2PTunnels (std::stringstream& s); - /* commands */ - void StartAcceptingTunnels (std::stringstream& s); - void StopAcceptingTunnels (std::stringstream& s); - void RunPeerTest (std::stringstream& s); - private: std::shared_ptr m_Socket; From 849308e28d3f0852bf86873399bea809d16719fe Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1239/6300] * HTTPServer.cpp: drop boost::date_time dep --- HTTPServer.cpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 98080808..d5a4622e 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -6,7 +6,6 @@ #include #include -#include #include "Base.h" #include "FS.h" @@ -230,11 +229,29 @@ namespace http { { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, }; + void ShowUptime (std::stringstream& s, int seconds) { + int num; + + if ((num = seconds / 86400) > 0) { + s << num << " days, "; + seconds -= num; + } + if ((num = seconds / 3600) > 0) { + s << num << " hours, "; + seconds -= num; + } + if ((num = seconds / 60) > 0) { + s << num << " min, "; + seconds -= num; + } + s << seconds << " seconds"; + } + void ShowStatus (std::stringstream& s) { - s << "Uptime: " << boost::posix_time::to_simple_string ( - boost::posix_time::time_duration (boost::posix_time::seconds ( - i2p::context.GetUptime ()))) << "
\r\n"; + s << "Uptime: "; + ShowUptime(s, i2p::context.GetUptime ()); + s << "
\r\n"; s << "Status: "; switch (i2p::context.GetStatus ()) { From 65395516b0b087d09fc28ae3df9dd012f38160e0 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1240/6300] * HTTPServer.cpp: drop separate function handlers for commands --- HTTPServer.cpp | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index d5a4622e..d3a57b50 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -593,27 +593,6 @@ namespace http { } } - void StopAcceptingTunnels (std::stringstream& s) - { - s << "Stop Accepting Tunnels:
\r\n
\r\n"; - i2p::context.SetAcceptsTunnels (false); - s << "Accepting tunnels stopped" << std::endl; - } - - void StartAcceptingTunnels (std::stringstream& s) - { - s << "Start Accepting Tunnels:
\r\n
\r\n"; - i2p::context.SetAcceptsTunnels (true); - s << "Accepting tunnels started" << std::endl; - } - - void RunPeerTest (std::stringstream& s) - { - s << "Run Peer Test:
\r\n
\r\n"; - i2p::transport::transports.PeerTest (); - s << "Peer test is running" << std::endl; - } - void HTTPConnection::Receive () { m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), @@ -749,14 +728,17 @@ namespace http { url.parse_query(params); cmd = params["cmd"]; - if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS) - StartAcceptingTunnels (s); + if (cmd == HTTP_COMMAND_RUN_PEER_TEST) + i2p::transport::transports.PeerTest (); + else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS) + i2p::context.SetAcceptsTunnels (true); else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS) - StopAcceptingTunnels (s); - else if (cmd == HTTP_COMMAND_RUN_PEER_TEST) - RunPeerTest (s); - else + i2p::context.SetAcceptsTunnels (false); + else { SendError("Unknown command: " + cmd); + return; + } + s << "Command accepted"; } void HTTPConnection::SendReply (const std::string& content, int code) From 23b8df1c36a420f367b12bfa5e897bb163813e2a Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1241/6300] * HTTPServer.cpp: move commands to separate page --- HTTPServer.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index d3a57b50..44841a91 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -217,6 +217,7 @@ namespace http { const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; const char HTTP_PAGE_JUMPSERVICES[] = "jumpservices"; + const char HTTP_PAGE_COMMANDS[] = "commands"; const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels"; const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; @@ -452,10 +453,20 @@ namespace http { else if (state == i2p::tunnel::eTunnelStateExpiring) s << " " << "Expiring"; s << " " << (int)it->GetNumReceivedBytes () << "
\r\n"; - s << std::endl; } } + void ShowCommands (std::stringstream& s) + { + /* commands */ + s << "Router Commands
\r\n"; + s << " Run peer test
\r\n"; + if (i2p::context.AcceptsTunnels ()) + s << " Stop accepting tunnels
\r\n"; + else + s << " Start accepting tunnels
\r\n"; + } + void ShowTransitTunnels (std::stringstream& s) { s << "Transit tunnels:
\r\n
\r\n"; @@ -655,22 +666,16 @@ namespace http { "
\r\n" "
\r\n" " Main page
\r\n
\r\n" + " Router commands
\r\n" " Local destinations
\r\n" " Tunnels
\r\n" " Transit tunnels
\r\n" " Transports
\r\n" " I2P tunnels
\r\n" - " Jump services
\r\n" + " Jump services
\r\n" ; if (i2p::client::context.GetSAMBridge ()) s << " SAM sessions
\r\n"; - /* commands */ - s << "
\r\n"; - s << " Run peer test
\r\n"; - if (i2p::context.AcceptsTunnels ()) - s << " Stop accepting tunnels
\r\n"; - else - s << " Start accepting tunnels
\r\n"; s << "
\r\n"; s << "
"; if (uri.find("page=") != std::string::npos) @@ -700,6 +705,8 @@ namespace http { ShowTransports (s); else if (page == HTTP_PAGE_TUNNELS) ShowTunnels (s); + else if (page == HTTP_PAGE_COMMANDS) + ShowCommands (s); else if (page == HTTP_PAGE_JUMPSERVICES) ShowJumpServices (s, params["address"]); else if (page == HTTP_PAGE_TRANSIT_TUNNELS) @@ -738,7 +745,8 @@ namespace http { SendError("Unknown command: " + cmd); return; } - s << "Command accepted"; + s << "Command accepted

\r\n"; + s << "Back to commands list"; } void HTTPConnection::SendReply (const std::string& content, int code) From 54078087e5713da4b692563b55a1b3420462a0cd Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1242/6300] * HTTPServer.cpp: move common code to function --- HTTPServer.cpp | 78 ++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 44841a91..142c392b 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -248,6 +248,23 @@ namespace http { s << seconds << " seconds"; } + void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, int bytes) + { + std::string state; + switch (eState) { + case i2p::tunnel::eTunnelStateBuildReplyReceived : + case i2p::tunnel::eTunnelStatePending : state = "building"; break; + case i2p::tunnel::eTunnelStateBuildFailed : + case i2p::tunnel::eTunnelStateTestFailed : + case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; + case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; + case i2p::tunnel::eTunnelStateEstablished : state = "established"; break; + default: state = "unknown"; break; + } + s << " " << state << ", "; + s << " " << (int) (bytes / 1024) << " KiB
\r\n"; + } + void ShowStatus (std::stringstream& s) { s << "Uptime: "; @@ -356,28 +373,19 @@ namespace http { auto pool = dest->GetTunnelPool (); if (pool) { - s << "Tunnels:
\r\n"; - for (auto it: pool->GetOutboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << "
\r\n" << std::endl; + s << "Inbound tunnels:
\r\n"; + for (auto & it : pool->GetInboundTunnels ()) { + it->Print(s); + ShowTunnelDetails(s, it->GetState (), it->GetNumReceivedBytes ()); } - for (auto it: pool->GetInboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << "
\r\n" << std::endl; + s << "
\r\n"; + s << "Outbound tunnels:
\r\n"; + for (auto & it : pool->GetOutboundTunnels ()) { + it->Print(s); + ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ()); } } + s << "
\r\n"; s << "Tags
Incoming: " << dest->GetNumIncomingTags () << "
Outgoing:
" << std::endl; for (auto it: dest->GetSessions ()) { @@ -430,30 +438,20 @@ namespace http { void ShowTunnels (std::stringstream& s) { - s << "Tunnels:
\r\n
\r\n"; s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n"; - for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Expiring"; - s << " " << (int)it->GetNumSentBytes () << "
\r\n"; - s << std::endl; - } - for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Expiring"; - s << " " << (int)it->GetNumReceivedBytes () << "
\r\n"; + s << "Inbound tunnels:
\r\n"; + for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { + it->Print(s); + ShowTunnelDetails(s, it->GetState (), it->GetNumReceivedBytes ()); } + s << "
\r\n"; + s << "Outbound tunnels:
\r\n"; + for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { + it->Print(s); + ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ()); + } + s << "
\r\n"; } void ShowCommands (std::stringstream& s) From 1f404bb62291542b7923bb3656b7e23c8e4d0bb0 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1243/6300] * HTTPServer.cpp: move html parts outside HTTPConnection class --- HTTPServer.cpp | 70 ++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 142c392b..864c9fd3 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -265,6 +265,43 @@ namespace http { s << " " << (int) (bytes / 1024) << " KiB
\r\n"; } + void ShowPageHead (std::stringstream& s) + { + s << + "\r\n" + "\r\n" /* TODO: Add support for locale */ + " \r\n" + " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ + " \r\n" + " Purple I2P " VERSION " Webconsole\r\n" + << cssStyles << + "\r\n"; + s << + "\r\n" + "
i2pd webconsole
\r\n" + "
\r\n" + "
\r\n" + " Main page
\r\n
\r\n" + " Router commands
\r\n" + " Local destinations
\r\n" + " Tunnels
\r\n" + " Transit tunnels
\r\n" + " Transports
\r\n" + " I2P tunnels
\r\n" + " Jump services
\r\n" + " SAM sessions
\r\n" + "
\r\n" + "
"; + } + + void ShowPageTail (std::stringstream& s) + { + s << + "
\r\n" + "\r\n" + "\r\n"; + } + void ShowStatus (std::stringstream& s) { s << "Uptime: "; @@ -649,43 +686,14 @@ namespace http { { std::stringstream s; // Html5 head start - s << - "\r\n" - "\r\n" /* TODO: Add support for locale */ - " \r\n" - " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ - " \r\n" - " Purple I2P " VERSION " Webconsole\r\n" - << cssStyles << - "\r\n"; - s << - "\r\n" - "
i2pd webconsole
\r\n" - "
\r\n" - "
\r\n" - " Main page
\r\n
\r\n" - " Router commands
\r\n" - " Local destinations
\r\n" - " Tunnels
\r\n" - " Transit tunnels
\r\n" - " Transports
\r\n" - " I2P tunnels
\r\n" - " Jump services
\r\n" - ; - if (i2p::client::context.GetSAMBridge ()) - s << " SAM sessions
\r\n"; - s << "
\r\n"; - s << "
"; + ShowPageHead (s); if (uri.find("page=") != std::string::npos) HandlePage (s, uri); else if (uri.find("cmd=") != std::string::npos) HandleCommand (s, uri); else ShowStatus (s); - s << - "
\r\n" - "\r\n" - "\r\n"; + ShowPageTail (s); SendReply (s.str ()); } From 80e37df0123067aedc784321ff2c7eb2f0074030 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1244/6300] * HTTPServer.{cpp,h}: change page/cmd processing flow --- Daemon.cpp | 1 + HTTPServer.cpp | 55 ++++++++++++++++++++++++++------------------------ HTTPServer.h | 10 ++++----- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index fdb9bf3f..c98bce05 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -13,6 +13,7 @@ #include "RouterInfo.h" #include "RouterContext.h" #include "Tunnel.h" +#include "HTTP.h" #include "NetDb.h" #include "Garlic.h" #include "Streaming.h" diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 864c9fd3..e5093bba 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -302,6 +302,11 @@ namespace http { "\r\n"; } + void ShowError(std::stringstream& s, const std::string& string) + { + s << "ERROR: " << string << "
\r\n"; + } + void ShowStatus (std::stringstream& s) { s << "Uptime: "; @@ -670,7 +675,7 @@ namespace http { } if (ret == 0) return; /* need more data */ - HandleRequest (request.uri); + HandleRequest (request); } void HTTPConnection::Terminate (const boost::system::error_code& ecode) @@ -682,28 +687,31 @@ namespace http { m_Socket->close (); } - void HTTPConnection::HandleRequest (const std::string &uri) + void HTTPConnection::HandleRequest (const HTTPReq & req) { std::stringstream s; + std::string content; + HTTPRes res; // Html5 head start ShowPageHead (s); - if (uri.find("page=") != std::string::npos) - HandlePage (s, uri); - else if (uri.find("cmd=") != std::string::npos) - HandleCommand (s, uri); + if (req.uri.find("page=") != std::string::npos) + HandlePage (req, res, s); + else if (req.uri.find("cmd=") != std::string::npos) + HandleCommand (req, res, s); else ShowStatus (s); ShowPageTail (s); - SendReply (s.str ()); + content = s.str (); + SendReply (res, content); } - void HTTPConnection::HandlePage (std::stringstream& s, const std::string & uri) + void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s) { std::map params; std::string page(""); URL url; - url.parse(uri); + url.parse(req.uri); url.parse_query(params); page = params["page"]; @@ -727,17 +735,20 @@ namespace http { ShowSAMSession (s, params["sam_id"]); else if (page == HTTP_PAGE_I2P_TUNNELS) ShowI2PTunnels (s); - else - SendError("Unknown page: " + page); + else { + res.code = 400; + ShowError(s, "Unknown page: " + page); + return; + } } - void HTTPConnection::HandleCommand (std::stringstream& s, const std::string & uri) + void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s) { std::map params; std::string cmd(""); URL url; - url.parse(uri); + url.parse(req.uri); url.parse_query(params); cmd = params["cmd"]; @@ -748,21 +759,20 @@ namespace http { else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS) i2p::context.SetAcceptsTunnels (false); else { - SendError("Unknown command: " + cmd); + res.code = 400; + ShowError(s, "Unknown command: " + cmd); return; } - s << "Command accepted

\r\n"; + s << "SUCCESS: Command accepted

\r\n"; s << "Back to commands list"; } - void HTTPConnection::SendReply (const std::string& content, int code) + void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) { std::time_t time_now = std::time(nullptr); char time_buff[128]; std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now)); - HTTPRes reply; - reply.code = code; - reply.status = HTTPCodeToStatus(code); + reply.status = HTTPCodeToStatus(reply.code); reply.headers.insert(std::pair("Date", time_buff)); reply.headers.insert(std::pair("Content-Type", "text/html")); reply.headers.insert(std::pair("Content-Length", std::to_string(content.size()))); @@ -777,13 +787,6 @@ namespace http { std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); } - void HTTPConnection::SendError(const std::string& content) - { - std::stringstream ss; - ss << "" << itoopieImage << "
\r\n" << content << ""; - SendReply (ss.str(), 504); - } - HTTPServer::HTTPServer (const std::string& address, int port): m_Thread (nullptr), m_Work (m_Service), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)) diff --git a/HTTPServer.h b/HTTPServer.h index 06fa4457..72a0c383 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -21,13 +21,11 @@ namespace http { void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void Terminate (const boost::system::error_code& ecode); - void SendReply (const std::string& content, int code = 200); - void SendError (const std::string& message); - void RunRequest (); - void HandleRequest (const std::string& uri); - void HandlePage (std::stringstream& s, const std::string& request); - void HandleCommand (std::stringstream& s, const std::string& request); + void HandleRequest (const HTTPReq & req); + void HandlePage (const HTTPReq & req, HTTPRes & res, std::stringstream& data); + void HandleCommand (const HTTPReq & req, HTTPRes & res, std::stringstream& data); + void SendReply (HTTPRes & res, std::string & content); private: From 75db2867dcdd77452414e4cbfec6c19ef3c44619 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1245/6300] * HTTPServer.cpp: protect SAM pages if disabled --- HTTPServer.cpp | 65 ++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index e5093bba..7dc8d910 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -573,15 +573,16 @@ namespace http { void ShowSAMSessions (std::stringstream& s) { - s << "SAM Sessions:
\r\n
\r\n"; auto sam = i2p::client::context.GetSAMBridge (); - if (sam) - { - for (auto& it: sam->GetSessions ()) - { - s << ""; - s << it.first << "
\r\n" << std::endl; - } + if (!sam) { + ShowError(s, "SAM disabled"); + return; + } + s << "SAM Sessions:
\r\n
\r\n"; + for (auto& it: sam->GetSessions ()) + { + s << ""; + s << it.first << "
\r\n" << std::endl; } } @@ -589,35 +590,31 @@ namespace http { { s << "SAM Session:
\r\n
\r\n"; auto sam = i2p::client::context.GetSAMBridge (); - if (sam) + if (!sam) { + ShowError(s, "SAM disabled"); + return; + } + auto session = sam->FindSession (id); + if (!session) { + ShowError(s, "SAM session not found"); + return; + } + auto& ident = session->localDestination->GetIdentHash(); + s << ""; + s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n"; + s << "
\r\n"; + s << "Streams:
\r\n"; + for (auto it: session->ListSockets()) { - auto session = sam->FindSession (id); - if (session) + switch (it->GetSocketType ()) { - auto& ident = session->localDestination->GetIdentHash(); - s << ""; - s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; - s << "Streams:
\r\n"; - for (auto it: session->ListSockets()) - { - switch (it->GetSocketType ()) - { - case i2p::client::eSAMSocketTypeSession: - s << "session"; - break; - case i2p::client::eSAMSocketTypeStream: - s << "stream"; - break; - case i2p::client::eSAMSocketTypeAcceptor: - s << "acceptor"; - break; - default: - s << "unknown"; - } - s << " [" << it->GetSocket ().remote_endpoint() << "]"; - s << "
\r\n" << std::endl; - } + case i2p::client::eSAMSocketTypeSession : s << "session"; break; + case i2p::client::eSAMSocketTypeStream : s << "stream"; break; + case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break; + default: s << "unknown"; break; } + s << " [" << it->GetSocket ().remote_endpoint() << "]"; + s << "
\r\n"; } } From e09386be445fde1a9e7fe63bbbabb6cff39ebff4 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1246/6300] * add http.auth, http.user & http.pass options --- Config.cpp | 3 +++ docs/configuration.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Config.cpp b/Config.cpp index d7cef879..44dec286 100644 --- a/Config.cpp +++ b/Config.cpp @@ -141,6 +141,9 @@ namespace config { ("http.enabled", value()->default_value(true), "Enable or disable webconsole") ("http.address", value()->default_value("127.0.0.1"), "Webconsole listen address") ("http.port", value()->default_value(7070), "Webconsole listen port") + ("http.auth", value()->default_value(false), "Enable Basic HTTP auth for webconsole") + ("http.user", value()->default_value("i2pd"), "Username for basic auth") + ("http.pass", value()->default_value(""), "Password for basic auth (default: random, see logs)") ; options_description httpproxy("HTTP Proxy options"); diff --git a/docs/configuration.md b/docs/configuration.md index 2a639be3..11e8b4a8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -36,6 +36,9 @@ All options below still possible in cmdline, but better write it in config file: * --http.address= - The address to listen on (HTTP server) * --http.port= - The port to listen on (HTTP server) +* --http.auth - Enable basic HTTP auth for webconsole +* --http.user= - Username for basic auth (default: i2pd) +* --http.pass= - Password for basic auth (default: random, see logs) * --httpproxy.address= - The address to listen on (HTTP Proxy) * --httpproxy.port= - The port to listen on (HTTP Proxy) 4446 by default From 678650beaff02b59491233b43aee2ffc5e660e6d Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1247/6300] * HTTPServer.{cpp,h}: basic auth --- HTTPServer.cpp | 61 +++++++++++++++++++++++++++++++++++++++++++++++++- HTTPServer.h | 8 ++++--- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 7dc8d910..8d9be41f 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -10,6 +10,7 @@ #include "Base.h" #include "FS.h" #include "Log.h" +#include "Config.h" #include "Tunnel.h" #include "TransitTunnel.h" #include "Transports.h" @@ -640,7 +641,16 @@ namespace http { s << "
\r\n"<< std::endl; } } - + + HTTPConnection::HTTPConnection (std::shared_ptr socket): + m_Socket (socket), m_Timer (socket->get_io_service ()), m_BufferLen (0) + { + /* cache options */ + i2p::config::GetOption("http.auth", needAuth); + i2p::config::GetOption("http.user", user); + i2p::config::GetOption("http.pass", pass); + }; + void HTTPConnection::Receive () { m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), @@ -672,6 +682,7 @@ namespace http { } if (ret == 0) return; /* need more data */ + HandleRequest (request); } @@ -684,11 +695,44 @@ namespace http { m_Socket->close (); } + bool HTTPConnection::CheckAuth (const HTTPReq & req) { + /* method #1: http://user:pass@127.0.0.1:7070/ */ + if (req.uri.find('@') != std::string::npos) { + URL url; + if (url.parse(req.uri) && url.user == user && url.pass == pass) + return true; + } + /* method #2: 'Authorization' header sent */ + if (req.headers.count("Authorization") > 0) { + std::string provided = req.headers.find("Authorization")->second; + std::string expected = user + ":" + pass; + char b64_creds[64]; + std::size_t len = 0; + len = i2p::data::ByteStreamToBase64((unsigned char *)expected.c_str(), expected.length(), b64_creds, sizeof(b64_creds)); + b64_creds[len] = '\0'; + expected = "Basic "; + expected += b64_creds; + if (provided == expected) + return true; + } + + LogPrint(eLogWarning, "HTTPServer: auth failure from ", m_Socket->remote_endpoint().address ()); + return false; + } + void HTTPConnection::HandleRequest (const HTTPReq & req) { std::stringstream s; std::string content; HTTPRes res; + + if (needAuth && !CheckAuth(req)) { + res.code = 401; + res.headers.insert(std::pair("WWW-Authenticate", "Basic realm=\"WebAdmin\"")); + SendReply(res, content); + return; + } + // Html5 head start ShowPageHead (s); if (req.uri.find("page=") != std::string::npos) @@ -797,6 +841,21 @@ namespace http { void HTTPServer::Start () { + bool needAuth; i2p::config::GetOption("http.auth", needAuth); + std::string user; i2p::config::GetOption("http.user", user); + std::string pass; i2p::config::GetOption("http.pass", pass); + /* generate pass if needed */ + if (needAuth && pass == "") { + char alnum[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + pass.resize(16); + for (size_t i = 0; i < pass.size(); i++) { + pass[i] = alnum[rand() % (sizeof(alnum) - 1)]; + } + i2p::config::SetOption("http.pass", pass); + LogPrint(eLogInfo, "HTTPServer: password set to ", pass); + } m_Thread = std::unique_ptr(new std::thread (std::bind (&HTTPServer::Run, this))); m_Acceptor.listen (); Accept (); diff --git a/HTTPServer.h b/HTTPServer.h index 72a0c383..2635c3be 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -11,9 +11,7 @@ namespace http { { public: - HTTPConnection (std::shared_ptr socket): - m_Socket (socket), m_Timer (socket->get_io_service ()), - m_BufferLen (0) {}; + HTTPConnection (std::shared_ptr socket); void Receive (); private: @@ -22,6 +20,7 @@ namespace http { void Terminate (const boost::system::error_code& ecode); void RunRequest (); + bool CheckAuth (const HTTPReq & req); void HandleRequest (const HTTPReq & req); void HandlePage (const HTTPReq & req, HTTPRes & res, std::stringstream& data); void HandleCommand (const HTTPReq & req, HTTPRes & res, std::stringstream& data); @@ -33,6 +32,9 @@ namespace http { boost::asio::deadline_timer m_Timer; char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; size_t m_BufferLen; + bool needAuth; + std::string user; + std::string pass; }; class HTTPServer From 8fd55a210a7613acc01f4467c1a91186028b0ab5 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1248/6300] * HTTPServer.cpp: add 'Shutdown' commands --- HTTPServer.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 8d9be41f..36ff2a37 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -21,6 +21,7 @@ #include "RouterContext.h" #include "ClientContext.h" #include "HTTPServer.h" +#include "Daemon.h" // For image and info #include "version.h" @@ -221,6 +222,9 @@ namespace http { const char HTTP_PAGE_COMMANDS[] = "commands"; const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels"; const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels"; + const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start"; + const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; + const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; const char HTTP_PARAM_BASE32_ADDRESS[] = "b32"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; @@ -506,6 +510,14 @@ namespace http { s << " Stop accepting tunnels
\r\n"; else s << " Start accepting tunnels
\r\n"; + if (Daemon.gracefullShutdownInterval) { + s << " Cancel gracefull shutdown ("; + s << Daemon.gracefullShutdownInterval; + s << " seconds remains)
\r\n"; + } else { + s << " Start gracefull shutdown
\r\n"; + } + s << " Force shutdown
\r\n"; } void ShowTransitTunnels (std::stringstream& s) @@ -799,7 +811,15 @@ namespace http { i2p::context.SetAcceptsTunnels (true); else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS) i2p::context.SetAcceptsTunnels (false); - else { + else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { + i2p::context.SetAcceptsTunnels (false); + Daemon.gracefullShutdownInterval = 10*60; + } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { + i2p::context.SetAcceptsTunnels (true); + Daemon.gracefullShutdownInterval = 0; + } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { + Daemon.running = false; + } else { res.code = 400; ShowError(s, "Unknown command: " + cmd); return; From f131e319496739cd3983964cf7e048bec8663423 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Apr 2016 00:00:00 +0000 Subject: [PATCH 1249/6300] * HTTPServer.cpp: add request logging --- HTTPServer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 36ff2a37..b3efc57d 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -738,6 +738,8 @@ namespace http { std::string content; HTTPRes res; + LogPrint(eLogDebug, "HTTPServer: request: ", req.uri); + if (needAuth && !CheckAuth(req)) { res.code = 401; res.headers.insert(std::pair("WWW-Authenticate", "Basic realm=\"WebAdmin\"")); From 2373b94d3e7794dc19a95322a0c407fddf6ae8d9 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 27 Apr 2016 12:08:08 -0400 Subject: [PATCH 1250/6300] try fixing issue #482 --- Destination.cpp | 25 ++++++++++++++----------- util.h | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 5893caff..1534cbf9 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -9,6 +9,7 @@ #include "Timestamp.h" #include "NetDb.h" #include "Destination.h" +#include "util.h" namespace i2p { @@ -35,28 +36,30 @@ namespace client { auto it = params->find (I2CP_PARAM_INBOUND_TUNNEL_LENGTH); if (it != params->end ()) - { - int len = boost::lexical_cast(it->second); + { + + int len = i2p::util::lexical_cast(it->second, inboundTunnelLen); if (len > 0) { - inboundTunnelLen = len; - LogPrint (eLogInfo, "Destination: Inbound tunnel length set to ", len); + inboundTunnelLen = len; } + LogPrint (eLogInfo, "Destination: Inbound tunnel length set to ", inboundTunnelLen); } it = params->find (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH); if (it != params->end ()) { - int len = boost::lexical_cast(it->second); + + int len = i2p::util::lexical_cast(it->second, outboundTunnelLen); if (len > 0) { - outboundTunnelLen = len; - LogPrint (eLogInfo, "Destination: Outbound tunnel length set to ", len); + outboundTunnelLen = len; } + LogPrint (eLogInfo, "Destination: Outbound tunnel length set to ", outboundTunnelLen); } it = params->find (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY); if (it != params->end ()) { - int quantity = boost::lexical_cast(it->second); + int quantity = i2p::util::lexical_cast(it->second, inboundTunnelsQuantity); if (quantity > 0) { inboundTunnelsQuantity = quantity; @@ -66,7 +69,7 @@ namespace client it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY); if (it != params->end ()) { - int quantity = boost::lexical_cast(it->second); + int quantity = i2p::util::lexical_cast(it->second, outboundTunnelsQuantity); if (quantity > 0) { outboundTunnelsQuantity = quantity; @@ -76,11 +79,11 @@ namespace client it = params->find (I2CP_PARAM_TAGS_TO_SEND); if (it != params->end ()) { - int tagsToSend = boost::lexical_cast(it->second); + int tagsToSend = i2p::util::lexical_cast(it->second, numTags); if (tagsToSend > 0) { numTags = tagsToSend; - LogPrint (eLogInfo, "Destination: Tags to send set to ", tagsToSend); + LogPrint (eLogInfo, "Destination: Tags to send set to ", tagsToSend); } } it = params->find (I2CP_PARAM_EXPLICIT_PEERS); diff --git a/util.h b/util.h index 13200591..f5dbc9aa 100644 --- a/util.h +++ b/util.h @@ -5,11 +5,25 @@ #include #include #include +#include namespace i2p { namespace util { + + /** + wrapper arround boost::lexical_cast that "never" fails + */ + template + T lexical_cast(const std::string & str, const T fallback) { + try { + return boost::lexical_cast(str); + } catch ( ... ) { + return fallback; + } + } + namespace http { // in (lower case) From 5e2dc14dd5564fda8f6458db0de38485edd9d6de Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 28 Apr 2016 18:16:11 -0400 Subject: [PATCH 1251/6300] get family string from local RouterInfo --- Daemon.cpp | 1 - HTTPServer.cpp | 4 +++- RouterContext.cpp | 12 ++---------- RouterContext.h | 4 +--- RouterInfo.cpp | 8 ++++++++ RouterInfo.h | 1 + 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 2a32ff04..81bbcdd5 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -189,7 +189,6 @@ namespace i2p std::string family; i2p::config::GetOption("family", family); i2p::context.SetFamily (family); - i2p::context.SetFamilyString (family); if (family.length () > 0) LogPrint(eLogInfo, "Daemon: family set to ", family); diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 08b67e22..458a717f 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -417,7 +417,9 @@ namespace util default: s << "Unknown"; } s << "
\r\n"; - s << "Family: " << i2p::context.GetFamilyString() << "
\r\n"; + auto family = i2p::context.GetFamily (); + if (family.length () > 0) + s << "Family: " << family << "
\r\n"; s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; s << "Received: "; s << std::fixed << std::setprecision(2); diff --git a/RouterContext.cpp b/RouterContext.cpp index 29891169..5fa32a13 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -149,17 +149,9 @@ namespace i2p UpdateRouterInfo (); } - void RouterContext::SetFamilyString (const std::string& family) + std::string RouterContext::GetFamily () const { - if (family.length() > 0) - m_FamilyString = family; - else - m_FamilyString = "<undefined>"; - } - - std::string RouterContext::GetFamilyString () const - { - return m_FamilyString; + return m_RouterInfo.GetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY); } void RouterContext::SetFamily (const std::string& family) diff --git a/RouterContext.h b/RouterContext.h index def89383..5a72ad58 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -59,8 +59,7 @@ namespace i2p bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); void SetFamily (const std::string& family); - void SetFamilyString (const std::string& family); - std::string GetFamilyString () const; + std::string GetFamily () const; void SetBandwidth (int limit); /* in kilobytes */ void SetBandwidth (char L); /* by letter */ bool AcceptsTunnels () const { return m_AcceptsTunnels; }; @@ -102,7 +101,6 @@ namespace i2p i2p::data::PrivateKeys m_Keys; uint64_t m_LastUpdateTime; bool m_AcceptsTunnels, m_IsFloodfill; - std::string m_FamilyString; uint64_t m_StartupTime; // in seconds since epoch uint32_t m_BandwidthLimit; // allowed bandwidth RouterStatus m_Status; diff --git a/RouterInfo.cpp b/RouterInfo.cpp index c27f8754..2e76127c 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -636,6 +636,14 @@ namespace data m_Properties.erase (key); } + std::string RouterInfo::GetProperty (const std::string& key) const + { + auto it = m_Properties.find (key); + if (it != m_Properties.end ()) + return it->second; + return ""; + } + bool RouterInfo::IsNTCP (bool v4only) const { if (v4only) diff --git a/RouterInfo.h b/RouterInfo.h index c9881dd2..a55924a8 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -128,6 +128,7 @@ namespace data bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only void DeleteProperty (const std::string& key); // called from RouterContext only + std::string GetProperty (const std::string& key) const; // called from RouterContext only void ClearProperties () { m_Properties.clear (); }; bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; bool IsReachable () const { return m_Caps & Caps::eReachable; }; From 00cfdc7d9257cfdb0ada151b77bf14b52c4da729 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 4 May 2016 12:12:24 -0400 Subject: [PATCH 1252/6300] fix mac brew, use libressl and homebrew --- Makefile | 2 +- Makefile.homebrew | 29 +++++++++++++++++++++++++++++ docs/build_notes_unix.md | 15 +++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 Makefile.homebrew diff --git a/Makefile b/Makefile index ae49ae4f..db6da2df 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ USE_STATIC := no ifeq ($(UNAME),Darwin) DAEMON_SRC += DaemonLinux.cpp - include Makefile.osx + include Makefile.homebrew else ifeq ($(shell echo $(UNAME) | $(GREP) -c FreeBSD),1) DAEMON_SRC += DaemonLinux.cpp include Makefile.bsd diff --git a/Makefile.homebrew b/Makefile.homebrew new file mode 100644 index 00000000..163b7950 --- /dev/null +++ b/Makefile.homebrew @@ -0,0 +1,29 @@ +# root directory holding homebrew +BREWROOT = /usr/local/ +BOOSTROOT = ${BREWROOT}/opt/boost +SSLROOT = ${BREWROOT}/opt/libressl +CXX = clang++ +CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX +INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include +LDFLAGS = -L${SSLROOT}/lib -L${BOOSTROOT}/lib +LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread + +ifeq ($(USE_UPNP),1) + LDFLAGS += -ldl + CXXFLAGS += -DUSE_UPNP +endif + +# OSX Notes +# http://www.hutsby.net/2011/08/macs-with-aes-ni.html +# Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2 +# Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic +# note from psi: 2009 macbook does not have aesni +#ifeq ($(USE_AESNI),yes) +# CXXFLAGS += -maes -DAESNI +#endif + +# Disabled, since it will be the default make rule. I think its better +# to define the default rule in Makefile and not Makefile. - torkel +#install: all +# test -d ${PREFIX} || mkdir -p ${PREFIX}/ +# cp -r i2p ${PREFIX}/ diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index d02eb5df..d173d4ae 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -93,6 +93,21 @@ If you need UPnP support (don't forget to run CMake with `WITH_UPNP=ON`) miniupn miniupnpc-devel ``` +MAC OS X +-------- + +Requires homebrew + +```bash +brew install libressl boost +``` + +Then build: +```bash +make +``` + + FreeBSD ------- From ca36a6fe41afffc1f9f8f2fd69ded564a71e69a8 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 May 2016 15:55:48 -0400 Subject: [PATCH 1253/6300] update our IP after signture verification --- SSUSession.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index aa534c56..a1bbf4d9 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -265,8 +265,6 @@ namespace transport uint16_t ourPort = bufbe16toh (payload); s.Insert (payload, 2); // our port payload += 2; // port - LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); - i2p::context.UpdateAddress (ourIP); if (m_RemoteEndpoint.address ().is_v4 ()) s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 else @@ -283,11 +281,18 @@ namespace transport //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved m_SessionKeyDecryption.SetIV (((SSUHeader *)buf)->iv); m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); // TODO: non-const payload - // verify - if (!s.Verify (m_RemoteIdentity, payload)) + // verify signature + if (s.Verify (m_RemoteIdentity, payload)) + { + LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); + i2p::context.UpdateAddress (ourIP); + SendSessionConfirmed (y, ourAddress, addressSize + 2); + } + else + { LogPrint (eLogError, "SSU: message 'created' signature verification failed"); - - SendSessionConfirmed (y, ourAddress, addressSize + 2); + Failed (); + } } void SSUSession::ProcessSessionConfirmed (const uint8_t * buf, size_t len) @@ -313,11 +318,17 @@ namespace transport paddingSize &= 0x0F; // %16 if (paddingSize > 0) paddingSize = 16 - paddingSize; payload += paddingSize; - // verify - if (m_SignedData && !m_SignedData->Verify (m_RemoteIdentity, payload)) + // verify signature + if (m_SignedData && m_SignedData->Verify (m_RemoteIdentity, payload)) + { + m_Data.Send (CreateDeliveryStatusMsg (0)); + Established (); + } + else + { LogPrint (eLogError, "SSU message 'confirmed' signature verification failed"); - m_Data.Send (CreateDeliveryStatusMsg (0)); - Established (); + Failed (); + } } void SSUSession::SendSessionRequest () From aa215f2a5a975742ad78410caf59be49c6759ce3 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 May 2016 07:08:02 -0400 Subject: [PATCH 1254/6300] regular/homebrew build selection for Mac OS X --- Makefile | 6 +++++- docs/build_notes_unix.md | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index db6da2df..e3807d93 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,11 @@ USE_STATIC := no ifeq ($(UNAME),Darwin) DAEMON_SRC += DaemonLinux.cpp - include Makefile.homebrew + ifeq ($(HOMEBREW),1) + include Makefile.homebrew + else + include Makefile.osx + endif else ifeq ($(shell echo $(UNAME) | $(GREP) -c FreeBSD),1) DAEMON_SRC += DaemonLinux.cpp include Makefile.bsd diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index d173d4ae..05605343 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -104,7 +104,7 @@ brew install libressl boost Then build: ```bash -make +make HOMEBREW=1 ``` From 47ce2398a4f5d7e4a50814eb8fa8990547f74999 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 11 May 2016 08:41:32 -0400 Subject: [PATCH 1255/6300] fix http unit test SIGBUS in os x --- HTTP.cpp | 27 +++++++++++++-------------- HTTP.h | 4 ++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/HTTP.cpp b/HTTP.cpp index 74acfb7a..02b58f99 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -10,23 +10,22 @@ namespace i2p { namespace http { - const char *HTTP_METHODS[] = { + const std::vector HTTP_METHODS = { "GET", "HEAD", "POST", "PUT", "PATCH", - "DELETE", "OPTIONS", "CONNECT", - NULL + "DELETE", "OPTIONS", "CONNECT" }; - const char *HTTP_VERSIONS[] = { - "HTTP/1.0", "HTTP/1.1", NULL + const std::vector HTTP_VERSIONS = { + "HTTP/1.0", "HTTP/1.1" }; - bool in_cstr_array(const char **haystack, const char *needle) { - for (const char *p = haystack[0]; p != NULL; p++) { - if (strcmp(p, needle) == 0) - return true; - } - return false; + inline bool is_http_version(const std::string & str) { + return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); } + inline bool is_http_method(const std::string & str) { + return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); + } + void strsplit(const std::string & line, std::vector &tokens, char delim, std::size_t limit = 0) { std::size_t count = 0; std::stringstream ss(line); @@ -205,9 +204,9 @@ namespace http { strsplit(line, tokens, ' '); if (tokens.size() != 3) return -1; - if (!in_cstr_array(HTTP_METHODS, tokens[0].c_str())) + if (!is_http_method(tokens[0])) return -1; - if (!in_cstr_array(HTTP_VERSIONS, tokens[2].c_str())) + if (!is_http_version(tokens[2])) return -1; if (!url.parse(tokens[1])) return -1; @@ -288,7 +287,7 @@ namespace http { strsplit(line, tokens, ' ', 3); if (tokens.size() != 3) return -1; - if (!in_cstr_array(HTTP_VERSIONS, tokens[0].c_str())) + if (!is_http_version(tokens[0])) return -1; code = atoi(tokens[1].c_str()); if (code < 100 || code >= 600) diff --git a/HTTP.h b/HTTP.h index 864ad88b..9bd31c75 100644 --- a/HTTP.h +++ b/HTTP.h @@ -19,8 +19,8 @@ namespace i2p { namespace http { const char CRLF[] = "\r\n"; /**< HTTP line terminator */ const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */ - extern const char *HTTP_METHODS[]; /**< list of valid HTTP methods */ - extern const char *HTTP_VERSIONS[]; /**< list of valid HTTP versions */ + extern const std::vector HTTP_METHODS; /**< list of valid HTTP methods */ + extern const std::vector HTTP_VERSIONS; /**< list of valid HTTP versions */ struct URL { std::string schema; From 28b5f39b840a4b10542d9f1a29a1d693e1376c94 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 11 May 2016 08:41:32 -0400 Subject: [PATCH 1256/6300] fix http unit test SIGBUS in os x --- HTTP.cpp | 27 +++++++++++++-------------- HTTP.h | 4 ++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/HTTP.cpp b/HTTP.cpp index 74acfb7a..02b58f99 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -10,23 +10,22 @@ namespace i2p { namespace http { - const char *HTTP_METHODS[] = { + const std::vector HTTP_METHODS = { "GET", "HEAD", "POST", "PUT", "PATCH", - "DELETE", "OPTIONS", "CONNECT", - NULL + "DELETE", "OPTIONS", "CONNECT" }; - const char *HTTP_VERSIONS[] = { - "HTTP/1.0", "HTTP/1.1", NULL + const std::vector HTTP_VERSIONS = { + "HTTP/1.0", "HTTP/1.1" }; - bool in_cstr_array(const char **haystack, const char *needle) { - for (const char *p = haystack[0]; p != NULL; p++) { - if (strcmp(p, needle) == 0) - return true; - } - return false; + inline bool is_http_version(const std::string & str) { + return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); } + inline bool is_http_method(const std::string & str) { + return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); + } + void strsplit(const std::string & line, std::vector &tokens, char delim, std::size_t limit = 0) { std::size_t count = 0; std::stringstream ss(line); @@ -205,9 +204,9 @@ namespace http { strsplit(line, tokens, ' '); if (tokens.size() != 3) return -1; - if (!in_cstr_array(HTTP_METHODS, tokens[0].c_str())) + if (!is_http_method(tokens[0])) return -1; - if (!in_cstr_array(HTTP_VERSIONS, tokens[2].c_str())) + if (!is_http_version(tokens[2])) return -1; if (!url.parse(tokens[1])) return -1; @@ -288,7 +287,7 @@ namespace http { strsplit(line, tokens, ' ', 3); if (tokens.size() != 3) return -1; - if (!in_cstr_array(HTTP_VERSIONS, tokens[0].c_str())) + if (!is_http_version(tokens[0])) return -1; code = atoi(tokens[1].c_str()); if (code < 100 || code >= 600) diff --git a/HTTP.h b/HTTP.h index 864ad88b..9bd31c75 100644 --- a/HTTP.h +++ b/HTTP.h @@ -19,8 +19,8 @@ namespace i2p { namespace http { const char CRLF[] = "\r\n"; /**< HTTP line terminator */ const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */ - extern const char *HTTP_METHODS[]; /**< list of valid HTTP methods */ - extern const char *HTTP_VERSIONS[]; /**< list of valid HTTP versions */ + extern const std::vector HTTP_METHODS; /**< list of valid HTTP methods */ + extern const std::vector HTTP_VERSIONS; /**< list of valid HTTP versions */ struct URL { std::string schema; From 8363b4fda78ced508e0f505239389195b7bfde79 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 11 May 2016 09:33:25 -0400 Subject: [PATCH 1257/6300] add missing header --- HTTP.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/HTTP.cpp b/HTTP.cpp index 02b58f99..ef43d55f 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -7,6 +7,7 @@ */ #include "HTTP.h" +#include namespace i2p { namespace http { From aa5ea0e3a17737d6b6046724734aa0893a1b5bae Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 May 2016 11:57:02 -0400 Subject: [PATCH 1258/6300] support gcc 6 --- Makefile.linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.linux b/Makefile.linux index 24816770..791382c6 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -19,7 +19,7 @@ else ifeq ($(shell expr match ${CXXVER} "4\.[7-9]"),3) # >= 4.7 NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 else ifeq ($(shell expr match ${CXXVER} "4\.6"),3) # = 4.6 NEEDED_CXXFLAGS += -std=c++0x -else ifeq ($(shell expr match ${CXXVER} "5\.[0-9]"),3) # gcc >= 5.0 +else ifeq ($(shell expr match ${CXXVER} "[5-6]\.[0-9]"),3) # gcc >= 5.0 NEEDED_CXXFLAGS += -std=c++11 else # not supported $(error Compiler too old) From 3907b4101a1794fa1d3cf2f3d659deb1c5f10921 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 May 2016 15:12:38 -0400 Subject: [PATCH 1259/6300] include openssl through OPENSSL macro --- Crypto.cpp | 16 +++++----- Crypto.h | 12 +++++--- CryptoConst.cpp | 73 ---------------------------------------------- CryptoConst.h | 38 ------------------------ Datagram.cpp | 3 +- Destination.cpp | 4 +-- Family.cpp | 6 ++-- Garlic.cpp | 5 ++-- I2NPProtocol.cpp | 2 -- I2NPProtocol.h | 2 +- I2PControl.cpp | 5 ++-- I2PControl.h | 2 +- Identity.cpp | 3 -- NTCPSession.cpp | 4 +-- NetDb.cpp | 2 +- Reseed.cpp | 10 +++---- SSUSession.cpp | 4 +-- Signature.h | 14 ++++----- Streaming.cpp | 2 +- Transports.cpp | 1 - Tunnel.cpp | 2 +- TunnelConfig.h | 1 - TunnelEndpoint.cpp | 2 +- TunnelGateway.cpp | 3 +- TunnelPool.cpp | 1 - 25 files changed, 48 insertions(+), 169 deletions(-) delete mode 100644 CryptoConst.cpp delete mode 100644 CryptoConst.h diff --git a/Crypto.cpp b/Crypto.cpp index 742296f5..ff68ff6a 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -3,15 +3,15 @@ #include #include #include -#include -#include -#include -#include -#include -#include "TunnelBase.h" -#include -#include "Log.h" #include "Crypto.h" +#include OPENSSL(sha.h) +#include OPENSSL(dh.h) +#include OPENSSL(md5.h) +#include OPENSSL(rand.h) +#include OPENSSL(crypto.h) +#include "TunnelBase.h" +#include OPENSSL(ssl.h) +#include "Log.h" namespace i2p { diff --git a/Crypto.h b/Crypto.h index e333940e..7b580c94 100644 --- a/Crypto.h +++ b/Crypto.h @@ -1,12 +1,16 @@ #ifndef CRYPTO_H__ #define CRYPTO_H__ +#define OPENSSL(file) + #include #include -#include -#include -#include -#include +#include OPENSSL(bn.h) +#include OPENSSL(dh.h) +#include OPENSSL(aes.h) +#include OPENSSL(dsa.h) +#include OPENSSL(sha.h) +#include OPENSSL(rand.h) #include "Base.h" namespace i2p diff --git a/CryptoConst.cpp b/CryptoConst.cpp deleted file mode 100644 index a8868988..00000000 --- a/CryptoConst.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include "CryptoConst.h" - -namespace i2p -{ -namespace crypto -{ - const uint8_t elgp_[256]= - { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, - 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, - 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, - 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, - 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, - 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, - 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, - 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, - 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, - 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, - 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, - 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, - 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, - 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, - 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, - 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF - }; - - const uint8_t dsap_[128]= - { - 0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c, - 0x30, 0x26, 0xe9, 0xb8, 0xed, 0x92, 0xfa, 0xd0, 0xa6, 0x9c, 0xc8, 0x86, 0xd5, 0xbf, 0x80, 0x15, - 0xfc, 0xad, 0xae, 0x31, 0xa0, 0xad, 0x18, 0xfa, 0xb3, 0xf0, 0x1b, 0x00, 0xa3, 0x58, 0xde, 0x23, - 0x76, 0x55, 0xc4, 0x96, 0x4a, 0xfa, 0xa2, 0xb3, 0x37, 0xe9, 0x6a, 0xd3, 0x16, 0xb9, 0xfb, 0x1c, - 0xc5, 0x64, 0xb5, 0xae, 0xc5, 0xb6, 0x9a, 0x9f, 0xf6, 0xc3, 0xe4, 0x54, 0x87, 0x07, 0xfe, 0xf8, - 0x50, 0x3d, 0x91, 0xdd, 0x86, 0x02, 0xe8, 0x67, 0xe6, 0xd3, 0x5d, 0x22, 0x35, 0xc1, 0x86, 0x9c, - 0xe2, 0x47, 0x9c, 0x3b, 0x9d, 0x54, 0x01, 0xde, 0x04, 0xe0, 0x72, 0x7f, 0xb3, 0x3d, 0x65, 0x11, - 0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93 - }; - - const uint8_t dsaq_[20]= - { - 0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d, - 0x68, 0x40, 0x46, 0xb7 - }; - - const uint8_t dsag_[128]= - { - 0x0c, 0x1f, 0x4d, 0x27, 0xd4, 0x00, 0x93, 0xb4, 0x29, 0xe9, 0x62, 0xd7, 0x22, 0x38, 0x24, 0xe0, - 0xbb, 0xc4, 0x7e, 0x7c, 0x83, 0x2a, 0x39, 0x23, 0x6f, 0xc6, 0x83, 0xaf, 0x84, 0x88, 0x95, 0x81, - 0x07, 0x5f, 0xf9, 0x08, 0x2e, 0xd3, 0x23, 0x53, 0xd4, 0x37, 0x4d, 0x73, 0x01, 0xcd, 0xa1, 0xd2, - 0x3c, 0x43, 0x1f, 0x46, 0x98, 0x59, 0x9d, 0xda, 0x02, 0x45, 0x18, 0x24, 0xff, 0x36, 0x97, 0x52, - 0x59, 0x36, 0x47, 0xcc, 0x3d, 0xdc, 0x19, 0x7d, 0xe9, 0x85, 0xe4, 0x3d, 0x13, 0x6c, 0xdc, 0xfc, - 0x6b, 0xd5, 0x40, 0x9c, 0xd2, 0xf4, 0x50, 0x82, 0x11, 0x42, 0xa5, 0xe6, 0xf8, 0xeb, 0x1c, 0x3a, - 0xb5, 0xd0, 0x48, 0x4b, 0x81, 0x29, 0xfc, 0xf1, 0x7b, 0xce, 0x4f, 0x7f, 0x33, 0x32, 0x1c, 0x3c, - 0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82 - }; - - const CryptoConstants& GetCryptoConstants () - { - static CryptoConstants cryptoConstants = - { - {elgp_, 256}, // elgp - {2}, // elgg - {dsap_, 128}, // dsap - {dsaq_, 20}, // dsaq - {dsag_, 128} // dsag - }; - return cryptoConstants; - } - -} -} - diff --git a/CryptoConst.h b/CryptoConst.h deleted file mode 100644 index ba48a35d..00000000 --- a/CryptoConst.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef CRYPTO_CONST_H__ -#define CRYPTO_CONST_H__ - -#include - -namespace i2p -{ -namespace crypto -{ - struct CryptoConstants - { - // DH/ElGamal - const CryptoPP::Integer elgp; - const CryptoPP::Integer elgg; - - // DSA - const CryptoPP::Integer dsap; - const CryptoPP::Integer dsaq; - const CryptoPP::Integer dsag; - }; - - const CryptoConstants& GetCryptoConstants (); - - // DH/ElGamal - #define elgp GetCryptoConstants ().elgp - #define elgg GetCryptoConstants ().elgg - - // DSA - #define dsap GetCryptoConstants ().dsap - #define dsaq GetCryptoConstants ().dsaq - #define dsag GetCryptoConstants ().dsag - - // RSA - const int rsae = 65537; -} -} - -#endif diff --git a/Datagram.cpp b/Datagram.cpp index 9221824d..2015622c 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -1,7 +1,6 @@ #include #include -#include -#include +#include "Crypto.h" #include "Log.h" #include "TunnelBase.h" #include "RouterContext.h" diff --git a/Destination.cpp b/Destination.cpp index 1534cbf9..f487b9ad 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -1,11 +1,9 @@ #include #include #include -#include - +#include "Crypto.h" #include "Log.h" #include "FS.h" -#include "Crypto.h" #include "Timestamp.h" #include "NetDb.h" #include "Destination.h" diff --git a/Family.cpp b/Family.cpp index 34406faa..a3dc5bd0 100644 --- a/Family.cpp +++ b/Family.cpp @@ -1,9 +1,9 @@ #include -#include -#include +#include "Crypto.h" +#include OPENSSL(evp.h) +#include OPENSSL(ssl.h) #include "FS.h" #include "Log.h" -#include "Crypto.h" #include "Family.h" namespace i2p diff --git a/Garlic.cpp b/Garlic.cpp index e11f8ec8..2188ade7 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -2,8 +2,9 @@ #include "I2PEndian.h" #include #include -#include -#include +#include "Crypto.h" +#include OPENSSL(rand.h) +#include OPENSSL(sha.h) #include "RouterContext.h" #include "I2NPProtocol.h" #include "Tunnel.h" diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 9674fdca..e2451f68 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -1,7 +1,5 @@ #include #include -#include -#include #include "Base.h" #include "Log.h" #include "Crypto.h" diff --git a/I2NPProtocol.h b/I2NPProtocol.h index cf8f4266..113e8eb8 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include "Crypto.h" #include "I2PEndian.h" #include "Identity.h" #include "RouterInfo.h" diff --git a/I2PControl.cpp b/I2PControl.cpp index 1ef56c2d..ef9b5081 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -1,7 +1,8 @@ #include #include -#include -#include +#include "Crypto.h" +#include OPENSSL(x509.h) +#include OPENSSL(pem.h) #include #include #include diff --git a/I2PControl.h b/I2PControl.h index 728c9925..0bd70956 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include // might include openssl #include namespace i2p diff --git a/Identity.cpp b/Identity.cpp index f221dd04..71ca007f 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -1,8 +1,5 @@ #include #include -#include -#include -#include #include "Crypto.h" #include "I2PEndian.h" #include "Log.h" diff --git a/NTCPSession.cpp b/NTCPSession.cpp index a7832335..ae020f5f 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -1,13 +1,11 @@ #include #include -#include -#include #include #include "I2PEndian.h" #include "Base.h" +#include "Crypto.h" #include "Log.h" #include "Timestamp.h" -#include "Crypto.h" #include "I2NPProtocol.h" #include "RouterContext.h" #include "Transports.h" diff --git a/NetDb.cpp b/NetDb.cpp index 857381d8..3aeff92f 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -2,10 +2,10 @@ #include #include #include -#include #include #include "I2PEndian.h" #include "Base.h" +#include "Crypto.h" #include "Log.h" #include "Timestamp.h" #include "I2NPProtocol.h" diff --git a/Reseed.cpp b/Reseed.cpp index caac8071..81811a5e 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -3,10 +3,11 @@ #include #include #include -#include -#include -#include -#include +#include // might include openssl +#include "Crypto.h" +#include OPENSSL(bn.h) +#include OPENSSL(ssl.h) +#include OPENSSL(err.h) #include #include "I2PEndian.h" @@ -14,7 +15,6 @@ #include "FS.h" #include "Log.h" #include "Identity.h" -#include "Crypto.h" #include "NetDb.h" #include "util.h" diff --git a/SSUSession.cpp b/SSUSession.cpp index a1bbf4d9..cf56ca15 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -1,7 +1,5 @@ #include -#include -#include -#include +#include "Crypto.h" #include "Log.h" #include "Timestamp.h" #include "RouterContext.h" diff --git a/Signature.h b/Signature.h index a0b54468..e3a39339 100644 --- a/Signature.h +++ b/Signature.h @@ -3,14 +3,14 @@ #include #include -#include -#include -#include -#include -#include -#include -#include #include "Crypto.h" +#include OPENSSL(sha.h) +#include OPENSSL(dsa.h) +#include OPENSSL(ec.h) +#include OPENSSL(ecdsa.h) +#include OPENSSL(rsa.h) +#include OPENSSL(rand.h) +#include OPENSSL(evp.h) namespace i2p { diff --git a/Streaming.cpp b/Streaming.cpp index e425ce27..ab0a6df0 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -1,4 +1,4 @@ -#include +#include "Crypto.h" #include "Log.h" #include "RouterInfo.h" #include "RouterContext.h" diff --git a/Transports.cpp b/Transports.cpp index 0fdfce3d..057e2472 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -1,4 +1,3 @@ -#include #include "Log.h" #include "Crypto.h" #include "RouterContext.h" diff --git a/Tunnel.cpp b/Tunnel.cpp index 5e237d87..bf81dc5e 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include "Crypto.h" #include "RouterContext.h" #include "Log.h" #include "Timestamp.h" diff --git a/TunnelConfig.h b/TunnelConfig.h index c089b13c..0340254e 100644 --- a/TunnelConfig.h +++ b/TunnelConfig.h @@ -5,7 +5,6 @@ #include #include #include -#include #include "Crypto.h" #include "Identity.h" #include "RouterContext.h" diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 842b624f..1bc8a937 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -1,6 +1,6 @@ #include "I2PEndian.h" #include -#include +#include "Crypto.h" #include "Log.h" #include "NetDb.h" #include "I2NPProtocol.h" diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index 4f517eb8..3383010b 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -1,6 +1,5 @@ #include -#include -#include +#include "Crypto.h" #include "I2PEndian.h" #include "Log.h" #include "RouterContext.h" diff --git a/TunnelPool.cpp b/TunnelPool.cpp index c74ff475..5e7e8ec4 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -1,5 +1,4 @@ #include -#include #include "I2PEndian.h" #include "Crypto.h" #include "Tunnel.h" From ae81cc2644a83dfdfebf4ef6009cfdba68f25308 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 May 2016 15:33:53 -0400 Subject: [PATCH 1260/6300] windows doesn't support graceful shutdown yet --- HTTPServer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 7ad8279d..63975722 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -513,6 +513,7 @@ namespace http { s << " Stop accepting tunnels
\r\n"; else s << " Start accepting tunnels
\r\n"; +#ifndef WIN32 if (Daemon.gracefullShutdownInterval) { s << " Cancel gracefull shutdown ("; s << Daemon.gracefullShutdownInterval; @@ -521,6 +522,7 @@ namespace http { s << " Start gracefull shutdown
\r\n"; } s << " Force shutdown
\r\n"; +#endif } void ShowTransitTunnels (std::stringstream& s) @@ -818,10 +820,14 @@ namespace http { i2p::context.SetAcceptsTunnels (false); else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { i2p::context.SetAcceptsTunnels (false); +#ifndef WIN32 Daemon.gracefullShutdownInterval = 10*60; +#endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); +#ifndef WIN32 Daemon.gracefullShutdownInterval = 0; +#endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { Daemon.running = false; } else { From 7c835bae20cd5d877b7a16351b851881577d6aac Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 May 2016 16:02:26 -0400 Subject: [PATCH 1261/6300] changed back to #include #include -#include "Crypto.h" -#include OPENSSL(sha.h) -#include OPENSSL(dh.h) -#include OPENSSL(md5.h) -#include OPENSSL(rand.h) -#include OPENSSL(crypto.h) +#include +#include +#include #include "TunnelBase.h" -#include OPENSSL(ssl.h) +#include #include "Log.h" +#include "Crypto.h" namespace i2p { diff --git a/Crypto.h b/Crypto.h index 7b580c94..7ce202ce 100644 --- a/Crypto.h +++ b/Crypto.h @@ -1,16 +1,14 @@ #ifndef CRYPTO_H__ #define CRYPTO_H__ -#define OPENSSL(file) - #include #include -#include OPENSSL(bn.h) -#include OPENSSL(dh.h) -#include OPENSSL(aes.h) -#include OPENSSL(dsa.h) -#include OPENSSL(sha.h) -#include OPENSSL(rand.h) +#include +#include +#include +#include +#include +#include #include "Base.h" namespace i2p diff --git a/Family.cpp b/Family.cpp index a3dc5bd0..90c5ccd0 100644 --- a/Family.cpp +++ b/Family.cpp @@ -1,7 +1,7 @@ #include +#include +#include #include "Crypto.h" -#include OPENSSL(evp.h) -#include OPENSSL(ssl.h) #include "FS.h" #include "Log.h" #include "Family.h" diff --git a/Garlic.cpp b/Garlic.cpp index 2188ade7..c1100f64 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -3,8 +3,6 @@ #include #include #include "Crypto.h" -#include OPENSSL(rand.h) -#include OPENSSL(sha.h) #include "RouterContext.h" #include "I2NPProtocol.h" #include "Tunnel.h" diff --git a/I2PControl.cpp b/I2PControl.cpp index ef9b5081..ab9feeed 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -1,8 +1,7 @@ #include #include -#include "Crypto.h" -#include OPENSSL(x509.h) -#include OPENSSL(pem.h) +#include +#include #include #include #include @@ -14,6 +13,7 @@ #include #endif +#include "Crypto.h" #include "FS.h" #include "Log.h" #include "Config.h" diff --git a/I2PControl.h b/I2PControl.h index 0bd70956..f2e82254 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -10,7 +10,7 @@ #include #include #include -#include // might include openssl +#include #include namespace i2p diff --git a/Reseed.cpp b/Reseed.cpp index 81811a5e..ddefc460 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -3,13 +3,12 @@ #include #include #include -#include // might include openssl -#include "Crypto.h" -#include OPENSSL(bn.h) -#include OPENSSL(ssl.h) -#include OPENSSL(err.h) +#include +#include +#include #include +#include "Crypto.h" #include "I2PEndian.h" #include "Reseed.h" #include "FS.h" diff --git a/Signature.h b/Signature.h index e3a39339..5934f8d2 100644 --- a/Signature.h +++ b/Signature.h @@ -3,14 +3,12 @@ #include #include +#include +#include +#include +#include +#include #include "Crypto.h" -#include OPENSSL(sha.h) -#include OPENSSL(dsa.h) -#include OPENSSL(ec.h) -#include OPENSSL(ecdsa.h) -#include OPENSSL(rsa.h) -#include OPENSSL(rand.h) -#include OPENSSL(evp.h) namespace i2p { From c49fdf12334c5fae3cb5545ecb14eba9bc0f0fb5 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 May 2016 11:38:18 -0400 Subject: [PATCH 1262/6300] initial commit for reload config command --- ClientContext.cpp | 18 ++++++++++++++---- ClientContext.h | 2 ++ HTTPServer.cpp | 6 +++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 2eb81af2..aad41ab2 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -171,6 +171,11 @@ namespace client m_Destinations.clear (); m_SharedLocalDestination = nullptr; } + + void ClientContext::ReloadConfig () + { + ReadTunnels (); // TODO: it reads new tunnels only, should be implemented better + } void ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType) { @@ -330,10 +335,12 @@ namespace client auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); if (m_ClientTunnels.insert (std::make_pair (clientTunnel->GetAcceptor ().local_endpoint (), std::unique_ptr(clientTunnel))).second) + { clientTunnel->Start (); + numClientTunnels++; + } else - LogPrint (eLogError, "Clients: I2P client tunnel with port ", port, " already exists"); - numClientTunnels++; + LogPrint (eLogError, "Clients: I2P client tunnel for endpoint ", clientTunnel->GetAcceptor ().local_endpoint (), " already exists"); } else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC) { @@ -385,10 +392,13 @@ namespace client if (m_ServerTunnels.insert (std::make_pair ( std::make_pair (localDestination->GetIdentHash (), inPort), std::unique_ptr(serverTunnel))).second) + { serverTunnel->Start (); + numServerTunnels++; + } else - LogPrint (eLogError, "Clients: I2P server tunnel for destination ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), " already exists"); - numServerTunnels++; + LogPrint (eLogError, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists"); + } else LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", tunConf); diff --git a/ClientContext.h b/ClientContext.h index 3381228b..15ae73c2 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -48,6 +48,8 @@ namespace client void Start (); void Stop (); + void ReloadConfig (); + std::shared_ptr GetSharedLocalDestination () const { return m_SharedLocalDestination; }; std::shared_ptr CreateNewLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1, const std::map * params = nullptr); // transient diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 63975722..19ccecb5 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -225,7 +225,8 @@ namespace http { const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start"; const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; - const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; + const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; + const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; const char HTTP_PARAM_BASE32_ADDRESS[] = "b32"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; @@ -509,6 +510,7 @@ namespace http { /* commands */ s << "Router Commands
\r\n"; s << " Run peer test
\r\n"; + s << " Reload config
\r\n"; if (i2p::context.AcceptsTunnels ()) s << " Stop accepting tunnels
\r\n"; else @@ -814,6 +816,8 @@ namespace http { if (cmd == HTTP_COMMAND_RUN_PEER_TEST) i2p::transport::transports.PeerTest (); + else if (cmd == HTTP_COMMAND_RELOAD_CONFIG) + i2p::client::context.ReloadConfig (); else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS) i2p::context.SetAcceptsTunnels (true); else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS) From 67f1e07508a2a4348d95d2d8024430f5d67a78c0 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 May 2016 15:37:46 -0400 Subject: [PATCH 1263/6300] I2CP added --- ClientContext.cpp | 3 ++- ClientContext.h | 2 ++ I2CP.cpp | 13 +++++++++++++ I2CP.h | 21 +++++++++++++++++++++ build/CMakeLists.txt | 1 + filelist.mk | 2 +- 6 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 I2CP.cpp create mode 100644 I2CP.h diff --git a/ClientContext.cpp b/ClientContext.cpp index aad41ab2..df0de4e5 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -16,7 +16,7 @@ namespace client ClientContext::ClientContext (): m_SharedLocalDestination (nullptr), m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr), - m_BOBCommandChannel (nullptr) + m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr) { } @@ -26,6 +26,7 @@ namespace client delete m_SocksProxy; delete m_SamBridge; delete m_BOBCommandChannel; + delete m_I2CPServer; } void ClientContext::Start () diff --git a/ClientContext.h b/ClientContext.h index 15ae73c2..cfbc039f 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -11,6 +11,7 @@ #include "I2PTunnel.h" #include "SAM.h" #include "BOB.h" +#include "I2CP.h" #include "AddressBook.h" namespace i2p @@ -84,6 +85,7 @@ namespace client std::map, std::unique_ptr > m_ServerTunnels; // ->tunnel SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; + I2CPServer * m_I2CPServer; public: // for HTTP diff --git a/I2CP.cpp b/I2CP.cpp new file mode 100644 index 00000000..77ff06e2 --- /dev/null +++ b/I2CP.cpp @@ -0,0 +1,13 @@ +#include "I2CP.h" + + +namespace i2p +{ +namespace client +{ + I2CPServer::I2CPServer (const std::string& interface, int port) + { + } +} +} + diff --git a/I2CP.h b/I2CP.h new file mode 100644 index 00000000..c5460c93 --- /dev/null +++ b/I2CP.h @@ -0,0 +1,21 @@ +#ifndef I2CP_H__ +#define I2CP_H__ + +#include +#include + +namespace i2p +{ +namespace client +{ + class I2CPServer + { + public: + + I2CPServer (const std::string& interface, int port); + }; +} +} + +#endif + diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 22029e85..7944e2ec 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -76,6 +76,7 @@ set (CLIENT_SRC "${CMAKE_SOURCE_DIR}/SOCKS.cpp" "${CMAKE_SOURCE_DIR}/HTTP.cpp" "${CMAKE_SOURCE_DIR}/HTTPProxy.cpp" + "${CMAKE_SOURCE_DIR}/I2CP.cpp" ) add_library(i2pdclient ${CLIENT_SRC}) diff --git a/filelist.mk b/filelist.mk index e2a3da36..8abf0b4b 100644 --- a/filelist.mk +++ b/filelist.mk @@ -9,7 +9,7 @@ LIB_SRC = \ LIB_CLIENT_SRC = \ AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \ - SAM.cpp SOCKS.cpp HTTP.cpp HTTPProxy.cpp + SAM.cpp SOCKS.cpp HTTP.cpp HTTPProxy.cpp I2CP.cpp # also: Daemon{Linux,Win32}.cpp will be added later DAEMON_SRC = \ From 4c2d4009dab26fc4daffb2349e426adeb3636cd6 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 May 2016 16:17:10 -0400 Subject: [PATCH 1264/6300] handle protocol byte --- I2CP.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ I2CP.h | 24 ++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/I2CP.cpp b/I2CP.cpp index 77ff06e2..d3e25e99 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -5,6 +5,46 @@ namespace i2p { namespace client { + I2CPSession::I2CPSession (std::shared_ptr socket): + m_Socket (socket) + { + ReadProtocolByte (); + } + + void I2CPSession::ReadProtocolByte () + { + if (m_Socket) + { + auto s = shared_from_this (); + m_Socket->async_read_some (boost::asio::buffer (m_Buffer, 1), + [s](const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (!ecode && bytes_transferred > 0 && s->m_Buffer[0] == I2CP_PRTOCOL_BYTE) + s->Receive (); + else + s->Terminate (); + }); + } + } + + void I2CPSession::Receive () + { + m_Socket->async_read_some (boost::asio::buffer (m_Buffer, I2CP_SESSION_BUFFER_SIZE), + std::bind (&I2CPSession::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + } + + void I2CPSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (ecode) + Terminate (); + else + Receive (); + } + + void I2CPSession::Terminate () + { + } + I2CPServer::I2CPServer (const std::string& interface, int port) { } diff --git a/I2CP.h b/I2CP.h index c5460c93..e4b94870 100644 --- a/I2CP.h +++ b/I2CP.h @@ -1,13 +1,37 @@ #ifndef I2CP_H__ #define I2CP_H__ +#include #include +#include #include namespace i2p { namespace client { + const uint8_t I2CP_PRTOCOL_BYTE = 0x2A; + const size_t I2CP_SESSION_BUFFER_SIZE = 8192; + + class I2CPSession: public std::enable_shared_from_this + { + public: + + I2CPSession (std::shared_ptr socket); + + private: + + void ReadProtocolByte (); + void Receive (); + void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void Terminate (); + + private: + + std::shared_ptr m_Socket; + uint8_t m_Buffer[I2CP_SESSION_BUFFER_SIZE]; + }; + class I2CPServer { public: From 448b25a8b2adf3fb1a03589111781ae94141ec78 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 13 May 2016 15:13:36 -0400 Subject: [PATCH 1265/6300] receive I2CP messages --- I2CP.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- I2CP.h | 31 ++++++++++++++++++++++++---- 2 files changed, 88 insertions(+), 6 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index d3e25e99..d6ba4e42 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -1,3 +1,6 @@ +#include +#include "I2PEndian.h" +#include "Log.h" #include "I2CP.h" @@ -5,12 +8,18 @@ namespace i2p { namespace client { - I2CPSession::I2CPSession (std::shared_ptr socket): - m_Socket (socket) + I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): + m_Owner (owner), m_Socket (socket), + m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0) { ReadProtocolByte (); } + I2CPSession::~I2CPSession () + { + delete[] m_NextMessage; + } + void I2CPSession::ReadProtocolByte () { if (m_Socket) @@ -38,15 +47,65 @@ namespace client if (ecode) Terminate (); else + { + size_t offset = 0; + if (m_NextMessage) + { + if (m_NextMessageOffset + bytes_transferred <= m_NextMessageLen) + { + memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, bytes_transferred); + m_NextMessageOffset += bytes_transferred; + } + else + { + offset = m_NextMessageLen - m_NextMessageOffset; + memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, offset); + HandleNextMessage (m_NextMessage); + delete[] m_NextMessage; + } + } + while (offset < bytes_transferred) + { + auto msgLen = bufbe32toh (m_Buffer + offset + I2CP_HEADER_LENGTH_OFFSET) + I2CP_HEADER_SIZE; + if (msgLen <= bytes_transferred - offset) + { + HandleNextMessage (m_Buffer + offset); + offset += msgLen; + } + else + { + m_NextMessageLen = msgLen; + m_NextMessageOffset = bytes_transferred - offset; + m_NextMessage = new uint8_t[m_NextMessageLen]; + memcpy (m_NextMessage, m_Buffer + offset, m_NextMessageOffset); + offset = bytes_transferred; + } + } Receive (); + } + } + + void I2CPSession::HandleNextMessage (const uint8_t * buf) + { + auto handler = m_Owner.GetMessagesHandlers ()[buf[I2CP_HEADER_TYPE_OFFSET]]; + if (handler) + (this->*handler)(buf + I2CP_HEADER_SIZE, bufbe32toh (buf + I2CP_HEADER_LENGTH_OFFSET)); + else + LogPrint (eLogError, "I2CP: Unknown I2CP messsage ", (int)buf[I2CP_HEADER_TYPE_OFFSET]); } void I2CPSession::Terminate () { } + void I2CPSession::GetDateMessageHandler (const uint8_t * buf, size_t len) + { + } + I2CPServer::I2CPServer (const std::string& interface, int port) { + memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); + m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; } } } diff --git a/I2CP.h b/I2CP.h index e4b94870..cb17626d 100644 --- a/I2CP.h +++ b/I2CP.h @@ -11,32 +11,55 @@ namespace i2p namespace client { const uint8_t I2CP_PRTOCOL_BYTE = 0x2A; - const size_t I2CP_SESSION_BUFFER_SIZE = 8192; + const size_t I2CP_SESSION_BUFFER_SIZE = 4096; + const size_t I2CP_HEADER_LENGTH_OFFSET = 0; + const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; + const size_t I2CP_HEADER_SIZE = I2CP_HEADER_TYPE_OFFSET + 1; + + const uint8_t I2CP_GET_DATE_MESSAGE = 32; + + class I2CPServer; class I2CPSession: public std::enable_shared_from_this { public: - I2CPSession (std::shared_ptr socket); + I2CPSession (I2CPServer& owner, std::shared_ptr socket); + ~I2CPSession (); + + // message handlers + void GetDateMessageHandler (const uint8_t * buf, size_t len); private: void ReadProtocolByte (); void Receive (); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void HandleNextMessage (const uint8_t * buf); void Terminate (); private: + I2CPServer& m_Owner; std::shared_ptr m_Socket; - uint8_t m_Buffer[I2CP_SESSION_BUFFER_SIZE]; + uint8_t m_Buffer[I2CP_SESSION_BUFFER_SIZE], * m_NextMessage; + size_t m_NextMessageLen, m_NextMessageOffset; }; - + typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); + class I2CPServer { public: I2CPServer (const std::string& interface, int port); + + private: + + I2CPMessageHandler m_MessagesHandlers[256]; + + public: + + const decltype(m_MessagesHandlers)& GetMessagesHandlers () const { return m_MessagesHandlers; }; }; } } From 8353f928a1b1f406be69a23367e9b304eb32c6eb Mon Sep 17 00:00:00 2001 From: weekendi2p Date: Tue, 17 May 2016 01:42:58 +0200 Subject: [PATCH 1266/6300] fix jumpservices --- HTTPProxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index c252614b..6104e15e 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -104,7 +104,7 @@ namespace proxy std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); - response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?jumpservices=&address=" << m_address << "\r\n\r\n"; + response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n\r\n"; boost::asio::async_write(*m_sock, boost::asio::buffer(response.str (),response.str ().length ()), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } From caf2e469a624d9aa63d83075c54484c4db162f0b Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 17 May 2016 12:35:08 -0400 Subject: [PATCH 1267/6300] remove mascot --- Win32/Anke_2200px.jpg | Bin 149209 -> 0 bytes Win32/Anke_700px.bmp | Bin 747656 -> 0 bytes Win32/Resource.rc | 2 -- Win32/Win32App.cpp | 13 ------------- Win32/anke.ico | Bin 121681 -> 0 bytes Win32/resource.h | 1 - 6 files changed, 16 deletions(-) delete mode 100644 Win32/Anke_2200px.jpg delete mode 100644 Win32/Anke_700px.bmp delete mode 100644 Win32/anke.ico diff --git a/Win32/Anke_2200px.jpg b/Win32/Anke_2200px.jpg deleted file mode 100644 index 1c5b0f31681ecf31d9eae38a6590b0bf2267b503..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 149209 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
>&&84UGHbJUq@^SZ1}>42R~4S)wtjZJ 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 diff --git a/Win32/Resource.rc b/Win32/Resource.rc index c885c044..dc0f82fa 100644 --- a/Win32/Resource.rc +++ b/Win32/Resource.rc @@ -55,8 +55,6 @@ END MAINICON ICON "ictoopie.ico" //MAINICON ICON "anke.ico" -MASCOT BITMAP "Anke_700px.bmp" - #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 81a3ba48..01432381 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -167,19 +167,6 @@ namespace win32 } break; } - case WM_PAINT: - { - PAINTSTRUCT ps; - auto hDC = BeginPaint (hWnd, &ps); - auto mascot = LoadBitmap (GetModuleHandle(NULL), MAKEINTRESOURCE (MASCOT)); - auto mascotDC = CreateCompatibleDC (hDC); - SelectObject (mascotDC, mascot); - BitBlt (hDC, 0,0, 533, 700, mascotDC, 0, 0, SRCCOPY); - DeleteDC (mascotDC); - DeleteObject (mascot); - EndPaint (hWnd, &ps); - break; - } } return DefWindowProc( hWnd, uMsg, wParam, lParam); } diff --git a/Win32/anke.ico b/Win32/anke.ico deleted file mode 100644 index 509177bb378cd1e711f85ea0b284e7d49c0b179d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121681 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
%?MsGrQ^B^7%MdM{hd5k-r- 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

ZAV6DTygQAu&!6< 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 S`XKTJ#gBVU9sIxk@&5rN32ux4 diff --git a/Win32/resource.h b/Win32/resource.h index f37fce46..a8309c8b 100644 --- a/Win32/resource.h +++ b/Win32/resource.h @@ -3,7 +3,6 @@ // Used by Resource.rc // #define MAINICON 101 -#define MASCOT 201 // Next default values for new objects // From f9e41826247d066c47567975076a138a688d212a Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 May 2016 09:22:11 -0400 Subject: [PATCH 1268/6300] temporary disable 'reload config' item --- HTTPServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 19ccecb5..81515105 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -510,7 +510,7 @@ namespace http { /* commands */ s << "Router Commands
\r\n"; s << "
Run peer test
\r\n"; - s << " Reload config
\r\n"; + //s << " Reload config
\r\n"; if (i2p::context.AcceptsTunnels ()) s << " Stop accepting tunnels
\r\n"; else From 90ea714e486f857d8b667f7f14bfc1ec08a1a7b6 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 May 2016 09:22:48 -0400 Subject: [PATCH 1269/6300] version 2.7.0 --- version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index c5177d30..bee17044 100644 --- a/version.h +++ b/version.h @@ -1,13 +1,13 @@ #ifndef _VERSION_H_ #define _VERSION_H_ -#define CODENAME "Bora Bora" +#define CODENAME "Purple" #define STRINGIZE(x) #x #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 6 +#define I2PD_VERSION_MINOR 7 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) From 9ddfc750e590bd28accadf268440581311ed3b87 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 19 May 2016 00:00:00 +0000 Subject: [PATCH 1270/6300] * update manpage: add --logfile description (#495) --- debian/i2pd.1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/debian/i2pd.1 b/debian/i2pd.1 index f61e243e..7409cd49 100644 --- a/debian/i2pd.1 +++ b/debian/i2pd.1 @@ -36,6 +36,9 @@ Where to write pidfile (don\'t write by default) \fB\-\-log=\fR Logs destination: \fIstdout\fR, \fIfile\fR, \fIsyslog\fR (\fIstdout\fR if not set, \fIfile\fR - otherwise, for compatibility) .TP +\fB\-\-logfile\fR +Path to logfile (default - autodetect) +.TP \fB\-\-loglevel=\fR Log messages above this level (\fIdebug\fR, \fBinfo\fR, \fIwarn\fR, \fIerror\fR) .TP From 89d2505a7cf363d69cb89725ca521c48837579c9 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 19 May 2016 00:00:00 +0000 Subject: [PATCH 1271/6300] * fix time in webconsole (#496) --- HTTPServer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 81515105..4e44e6f9 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -241,15 +241,15 @@ namespace http { if ((num = seconds / 86400) > 0) { s << num << " days, "; - seconds -= num; + seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { s << num << " hours, "; - seconds -= num; + seconds -= num * 3600; } if ((num = seconds / 60) > 0) { s << num << " min, "; - seconds -= num; + seconds -= num * 60; } s << seconds << " seconds"; } From cb68d19bed3a17b677f3b450da738b50025acd88 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 May 2016 10:33:01 -0400 Subject: [PATCH 1272/6300] ClientDestination/LeaseSetDestination split --- Datagram.cpp | 2 +- Datagram.h | 6 +-- Destination.cpp | 139 +++++++++++++++++++++++++----------------------- Destination.h | 39 +++++++++----- Streaming.cpp | 2 +- Streaming.h | 8 +-- 6 files changed, 107 insertions(+), 89 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 2015622c..f06d62da 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -11,7 +11,7 @@ namespace i2p { namespace datagram { - DatagramDestination::DatagramDestination (std::shared_ptr owner): + DatagramDestination::DatagramDestination (std::shared_ptr owner): m_Owner (owner), m_Receiver (nullptr) { } diff --git a/Datagram.h b/Datagram.h index c593fad2..404facf2 100644 --- a/Datagram.h +++ b/Datagram.h @@ -14,7 +14,7 @@ namespace i2p { namespace client { - class ClientDestination; + class LeaseSetDestination; } namespace datagram { @@ -25,7 +25,7 @@ namespace datagram public: - DatagramDestination (std::shared_ptr owner); + DatagramDestination (std::shared_ptr owner); ~DatagramDestination (); void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort = 0, uint16_t toPort = 0); @@ -47,7 +47,7 @@ namespace datagram private: - std::shared_ptr m_Owner; + std::shared_ptr m_Owner; Receiver m_Receiver; // default std::map m_ReceiversByPorts; diff --git a/Destination.cpp b/Destination.cpp index f487b9ad..4712ac09 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -13,17 +13,12 @@ namespace i2p { namespace client { - ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, - const std::map * params): + LeaseSetDestination::LeaseSetDestination (bool isPublic, const std::map * params): m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), - m_Keys (keys), m_IsPublic (isPublic), m_PublishReplyToken (0), + m_IsPublic (isPublic), m_PublishReplyToken (0), m_DatagramDestination (nullptr), m_PublishConfirmationTimer (m_Service), m_PublishVerificationTimer (m_Service), m_CleanupTimer (m_Service) { - if (m_IsPublic) - PersistTemporaryKeys (); - else - i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); int inboundTunnelLen = DEFAULT_INBOUND_TUNNEL_LENGTH; int outboundTunnelLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH; int inboundTunnelsQuantity = DEFAULT_INBOUND_TUNNELS_QUANTITY; @@ -103,11 +98,9 @@ namespace client m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inboundTunnelLen, outboundTunnelLen, inboundTunnelsQuantity, outboundTunnelsQuantity); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); - if (m_IsPublic) - LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created"); } - ClientDestination::~ClientDestination () + LeaseSetDestination::~LeaseSetDestination () { if (m_IsRunning) Stop (); @@ -120,7 +113,7 @@ namespace client delete m_DatagramDestination; } - void ClientDestination::Run () + void LeaseSetDestination::Run () { while (m_IsRunning) { @@ -135,26 +128,26 @@ namespace client } } - void ClientDestination::Start () + void LeaseSetDestination::Start () { if (!m_IsRunning) { m_IsRunning = true; m_Pool->SetLocalDestination (shared_from_this ()); m_Pool->SetActive (true); - m_Thread = new std::thread (std::bind (&ClientDestination::Run, shared_from_this ())); + m_Thread = new std::thread (std::bind (&LeaseSetDestination::Run, shared_from_this ())); m_StreamingDestination = std::make_shared (shared_from_this ()); // TODO: m_StreamingDestination->Start (); for (auto it: m_StreamingDestinationsByPorts) it.second->Start (); m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); - m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer, + m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, shared_from_this (), std::placeholders::_1)); } } - void ClientDestination::Stop () + void LeaseSetDestination::Stop () { if (m_IsRunning) { @@ -187,7 +180,7 @@ namespace client } } - std::shared_ptr ClientDestination::FindLeaseSet (const i2p::data::IdentHash& ident) + std::shared_ptr LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident) { auto it = m_RemoteLeaseSets.find (ident); if (it != m_RemoteLeaseSets.end ()) @@ -210,7 +203,7 @@ namespace client return nullptr; } - std::shared_ptr ClientDestination::GetLeaseSet () + std::shared_ptr LeaseSetDestination::GetLeaseSet () { if (!m_Pool) return nullptr; if (!m_LeaseSet) @@ -218,12 +211,12 @@ namespace client return m_LeaseSet; } - void ClientDestination::UpdateLeaseSet () + void LeaseSetDestination::UpdateLeaseSet () { m_LeaseSet.reset (new i2p::data::LeaseSet (m_Pool)); } - bool ClientDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) + bool LeaseSetDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) { struct { @@ -239,17 +232,17 @@ namespace client return true; } - void ClientDestination::ProcessGarlicMessage (std::shared_ptr msg) + void LeaseSetDestination::ProcessGarlicMessage (std::shared_ptr msg) { - m_Service.post (std::bind (&ClientDestination::HandleGarlicMessage, shared_from_this (), msg)); + m_Service.post (std::bind (&LeaseSetDestination::HandleGarlicMessage, shared_from_this (), msg)); } - void ClientDestination::ProcessDeliveryStatusMessage (std::shared_ptr msg) + void LeaseSetDestination::ProcessDeliveryStatusMessage (std::shared_ptr msg) { - m_Service.post (std::bind (&ClientDestination::HandleDeliveryStatusMessage, shared_from_this (), msg)); + m_Service.post (std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msg)); } - void ClientDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) + void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { uint8_t typeID = buf[I2NP_HEADER_TYPEID_OFFSET]; switch (typeID) @@ -272,7 +265,7 @@ namespace client } } - void ClientDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len) + void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len) { uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET); size_t offset = DATABASE_STORE_HEADER_SIZE; @@ -336,7 +329,7 @@ namespace client } } - void ClientDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len) + void LeaseSetDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len) { i2p::data::IdentHash key (buf); int num = buf[32]; // num @@ -379,7 +372,7 @@ namespace client LogPrint (eLogWarning, "Destination: Request for ", key.ToBase64 (), " not found"); } - void ClientDestination::HandleDeliveryStatusMessage (std::shared_ptr msg) + void LeaseSetDestination::HandleDeliveryStatusMessage (std::shared_ptr msg) { uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET); if (msgID == m_PublishReplyToken) @@ -389,14 +382,14 @@ namespace client m_PublishReplyToken = 0; // schedule verification m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); - m_PublishVerificationTimer.async_wait (std::bind (&ClientDestination::HandlePublishVerificationTimer, + m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, shared_from_this (), std::placeholders::_1)); } else i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msg); } - void ClientDestination::SetLeaseSetUpdated () + void LeaseSetDestination::SetLeaseSetUpdated () { i2p::garlic::GarlicDestination::SetLeaseSetUpdated (); UpdateLeaseSet (); @@ -407,7 +400,7 @@ namespace client } } - void ClientDestination::Publish () + void LeaseSetDestination::Publish () { if (!m_LeaseSet || !m_Pool) { @@ -437,12 +430,12 @@ namespace client RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); - m_PublishConfirmationTimer.async_wait (std::bind (&ClientDestination::HandlePublishConfirmationTimer, + m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, msg); } - void ClientDestination::HandlePublishConfirmationTimer (const boost::system::error_code& ecode) + void LeaseSetDestination::HandlePublishConfirmationTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { @@ -455,7 +448,7 @@ namespace client } } - void ClientDestination::HandlePublishVerificationTimer (const boost::system::error_code& ecode) + void LeaseSetDestination::HandlePublishVerificationTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { @@ -471,7 +464,7 @@ namespace client // we got latest LeasetSet LogPrint (eLogDebug, "Destination: published LeaseSet verified"); s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL)); - s->m_PublishVerificationTimer.async_wait (std::bind (&ClientDestination::HandlePublishVerificationTimer, s, std::placeholders::_1)); + s->m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, s, std::placeholders::_1)); return; } } @@ -483,7 +476,7 @@ namespace client } } - void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) + void LeaseSetDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); buf += 4; @@ -514,7 +507,7 @@ namespace client } } - void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) + void LeaseSetDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) { if (!streamRequestComplete) { @@ -538,7 +531,7 @@ namespace client } } - std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, int port) + std::shared_ptr LeaseSetDestination::CreateStream (std::shared_ptr remote, int port) { if (m_StreamingDestination) return m_StreamingDestination->CreateNewOutgoingStream (remote, port); @@ -546,7 +539,7 @@ namespace client return nullptr; } - std::shared_ptr ClientDestination::GetStreamingDestination (int port) const + std::shared_ptr LeaseSetDestination::GetStreamingDestination (int port) const { if (port) { @@ -558,26 +551,26 @@ namespace client return m_StreamingDestination; } - void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor) + void LeaseSetDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor) { if (m_StreamingDestination) m_StreamingDestination->SetAcceptor (acceptor); } - void ClientDestination::StopAcceptingStreams () + void LeaseSetDestination::StopAcceptingStreams () { if (m_StreamingDestination) m_StreamingDestination->ResetAcceptor (); } - bool ClientDestination::IsAcceptingStreams () const + bool LeaseSetDestination::IsAcceptingStreams () const { if (m_StreamingDestination) return m_StreamingDestination->IsAcceptorSet (); return false; } - std::shared_ptr ClientDestination::CreateStreamingDestination (int port, bool gzip) + std::shared_ptr LeaseSetDestination::CreateStreamingDestination (int port, bool gzip) { auto dest = std::make_shared (shared_from_this (), port, gzip); if (port) @@ -587,25 +580,25 @@ namespace client return dest; } - i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination () + i2p::datagram::DatagramDestination * LeaseSetDestination::CreateDatagramDestination () { if (!m_DatagramDestination) m_DatagramDestination = new i2p::datagram::DatagramDestination (shared_from_this ()); return m_DatagramDestination; } - bool ClientDestination::RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete) + bool LeaseSetDestination::RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete) { if (!m_Pool || !IsReady ()) { if (requestComplete) requestComplete (nullptr); return false; } - m_Service.post (std::bind (&ClientDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete)); + m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete)); return true; } - void ClientDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest) + void LeaseSetDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest) { auto s = shared_from_this (); m_Service.post ([dest, s](void) @@ -620,7 +613,7 @@ namespace client }); } - void ClientDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete) + void LeaseSetDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete) { std::set excluded; auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, excluded); @@ -652,7 +645,7 @@ namespace client } } - bool ClientDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest, + bool LeaseSetDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, std::shared_ptr request) { if (!request->replyTunnel || !request->replyTunnel->IsEstablished ()) @@ -685,7 +678,7 @@ namespace client } }); request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT)); - request->requestTimeoutTimer.async_wait (std::bind (&ClientDestination::HandleRequestTimoutTimer, + request->requestTimeoutTimer.async_wait (std::bind (&LeaseSetDestination::HandleRequestTimoutTimer, shared_from_this (), std::placeholders::_1, dest)); } else @@ -693,7 +686,7 @@ namespace client return true; } - void ClientDestination::HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest) + void LeaseSetDestination::HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest) { if (ecode != boost::asio::error::operation_aborted) { @@ -731,19 +724,19 @@ namespace client } } - void ClientDestination::HandleCleanupTimer (const boost::system::error_code& ecode) + void LeaseSetDestination::HandleCleanupTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { CleanupExpiredTags (); CleanupRemoteLeaseSets (); m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); - m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer, + m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, shared_from_this (), std::placeholders::_1)); } } - void ClientDestination::CleanupRemoteLeaseSets () + void LeaseSetDestination::CleanupRemoteLeaseSets () { auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it = m_RemoteLeaseSets.begin (); it != m_RemoteLeaseSets.end ();) @@ -758,6 +751,32 @@ namespace client } } + std::vector > LeaseSetDestination::GetAllStreams () const + { + std::vector > ret; + if (m_StreamingDestination) + { + for (auto& it: m_StreamingDestination->GetStreams ()) + ret.push_back (it.second); + } + for (auto& it: m_StreamingDestinationsByPorts) + for (auto& it1: it.second->GetStreams ()) + ret.push_back (it1.second); + return ret; + } + + ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): + LeaseSetDestination (isPublic, params), + m_Keys (keys) + { + if (isPublic) + PersistTemporaryKeys (); + else + i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); + if (isPublic) + LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created"); + } + void ClientDestination::PersistTemporaryKeys () { std::string ident = GetIdentHash().ToBase32(); @@ -780,20 +799,6 @@ namespace client return; } LogPrint(eLogError, "Destinations: Can't save keys to ", path); - } - - std::vector > ClientDestination::GetAllStreams () const - { - std::vector > ret; - if (m_StreamingDestination) - { - for (auto& it: m_StreamingDestination->GetStreams ()) - ret.push_back (it.second); - } - for (auto& it: m_StreamingDestinationsByPorts) - for (auto& it1: it.second->GetStreams ()) - ret.push_back (it1.second); - return ret; } } } diff --git a/Destination.h b/Destination.h index 3011b4cb..3cf4ff48 100644 --- a/Destination.h +++ b/Destination.h @@ -49,8 +49,8 @@ namespace client typedef std::function stream)> StreamRequestComplete; - class ClientDestination: public i2p::garlic::GarlicDestination, - public std::enable_shared_from_this + class LeaseSetDestination: public i2p::garlic::GarlicDestination, + public std::enable_shared_from_this { typedef std::function leaseSet)> RequestComplete; // leaseSet = nullptr means not found @@ -68,8 +68,8 @@ namespace client public: - ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); - ~ClientDestination (); + LeaseSetDestination (bool isPublic, const std::map * params = nullptr); + ~LeaseSetDestination (); virtual void Start (); virtual void Stop (); @@ -94,11 +94,6 @@ namespace client // datagram i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; i2p::datagram::DatagramDestination * CreateDatagramDestination (); - - // implements LocalDestination - const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; - const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; - const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; // implements GarlicDestination std::shared_ptr GetLeaseSet (); @@ -129,8 +124,7 @@ namespace client bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, std::shared_ptr request); void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleCleanupTimer (const boost::system::error_code& ecode); - void CleanupRemoteLeaseSets (); - void PersistTemporaryKeys (); + void CleanupRemoteLeaseSets (); private: @@ -138,8 +132,6 @@ namespace client std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; - i2p::data::PrivateKeys m_Keys; - uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; std::map > m_RemoteLeaseSets; std::map > m_LeaseSetRequests; @@ -161,6 +153,27 @@ namespace client int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); }; std::vector > GetAllStreams () const; }; + + class ClientDestination: public LeaseSetDestination + { + public: + + ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); + + // implements LocalDestination + const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; + const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; + const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; + + private: + + void PersistTemporaryKeys (); + + private: + + i2p::data::PrivateKeys m_Keys; + uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; + }; } } diff --git a/Streaming.cpp b/Streaming.cpp index ab0a6df0..bf29caec 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -780,7 +780,7 @@ namespace stream m_CurrentRemoteLease = nullptr; } - StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): + StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), m_PendingIncomingTimer (m_Owner->GetService ()) { diff --git a/Streaming.h b/Streaming.h index c29b62f9..43061b94 100644 --- a/Streaming.h +++ b/Streaming.h @@ -23,7 +23,7 @@ namespace i2p { namespace client { - class ClientDestination; + class LeaseSetDestination; } namespace stream { @@ -193,7 +193,7 @@ namespace stream typedef std::function)> Acceptor; - StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = true); + StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = true); ~StreamingDestination (); void Start (); @@ -204,7 +204,7 @@ namespace stream void SetAcceptor (const Acceptor& acceptor); void ResetAcceptor (); bool IsAcceptorSet () const { return m_Acceptor != nullptr; }; - std::shared_ptr GetOwner () const { return m_Owner; }; + std::shared_ptr GetOwner () const { return m_Owner; }; uint16_t GetLocalPort () const { return m_LocalPort; }; void HandleDataMessagePayload (const uint8_t * buf, size_t len); @@ -218,7 +218,7 @@ namespace stream private: - std::shared_ptr m_Owner; + std::shared_ptr m_Owner; uint16_t m_LocalPort; bool m_Gzip; // gzip compression of data messages std::mutex m_StreamsMutex; From f6103d3841fe220090e38fecc74e65fdafdce9e9 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 May 2016 14:31:22 -0400 Subject: [PATCH 1273/6300] moved streaming and datagram destination from LeaseSetDestination to ClientDestination --- Destination.cpp | 323 ++++++++++++++++++++++++++---------------------- Destination.h | 63 ++++++---- 2 files changed, 215 insertions(+), 171 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 4712ac09..c5a3beff 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -14,9 +14,8 @@ namespace i2p namespace client { LeaseSetDestination::LeaseSetDestination (bool isPublic, const std::map * params): - m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), - m_IsPublic (isPublic), m_PublishReplyToken (0), - m_DatagramDestination (nullptr), m_PublishConfirmationTimer (m_Service), + m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_IsPublic (isPublic), + m_PublishReplyToken (0), m_PublishConfirmationTimer (m_Service), m_PublishVerificationTimer (m_Service), m_CleanupTimer (m_Service) { int inboundTunnelLen = DEFAULT_INBOUND_TUNNEL_LENGTH; @@ -109,8 +108,6 @@ namespace client m_LeaseSetRequests.clear (); if (m_Pool) i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool); - if (m_DatagramDestination) - delete m_DatagramDestination; } void LeaseSetDestination::Run () @@ -128,7 +125,7 @@ namespace client } } - void LeaseSetDestination::Start () + bool LeaseSetDestination::Start () { if (!m_IsRunning) { @@ -136,18 +133,17 @@ namespace client m_Pool->SetLocalDestination (shared_from_this ()); m_Pool->SetActive (true); m_Thread = new std::thread (std::bind (&LeaseSetDestination::Run, shared_from_this ())); - m_StreamingDestination = std::make_shared (shared_from_this ()); // TODO: - m_StreamingDestination->Start (); - for (auto it: m_StreamingDestinationsByPorts) - it.second->Start (); m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, shared_from_this (), std::placeholders::_1)); + return true; } + else + return false; } - void LeaseSetDestination::Stop () + bool LeaseSetDestination::Stop () { if (m_IsRunning) { @@ -155,16 +151,6 @@ namespace client m_PublishConfirmationTimer.cancel (); m_PublishVerificationTimer.cancel (); m_IsRunning = false; - m_StreamingDestination->Stop (); - m_StreamingDestination = nullptr; - for (auto it: m_StreamingDestinationsByPorts) - it.second->Stop (); - if (m_DatagramDestination) - { - auto d = m_DatagramDestination; - m_DatagramDestination = nullptr; - delete d; - } if (m_Pool) { m_Pool->SetLocalDestination (nullptr); @@ -177,7 +163,10 @@ namespace client delete m_Thread; m_Thread = 0; } + return true; } + else + return false; } std::shared_ptr LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident) @@ -476,117 +465,6 @@ namespace client } } - void LeaseSetDestination::HandleDataMessage (const uint8_t * buf, size_t len) - { - uint32_t length = bufbe32toh (buf); - buf += 4; - // we assume I2CP payload - uint16_t fromPort = bufbe16toh (buf + 4), // source - toPort = bufbe16toh (buf + 6); // destination - switch (buf[9]) - { - case PROTOCOL_TYPE_STREAMING: - { - // streaming protocol - auto dest = GetStreamingDestination (toPort); - if (dest) - dest->HandleDataMessagePayload (buf, length); - else - LogPrint (eLogError, "Destination: Missing streaming destination"); - } - break; - case PROTOCOL_TYPE_DATAGRAM: - // datagram protocol - if (m_DatagramDestination) - m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length); - else - LogPrint (eLogError, "Destination: Missing datagram destination"); - break; - default: - LogPrint (eLogError, "Destination: Data: unexpected protocol ", buf[9]); - } - } - - void LeaseSetDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) - { - if (!streamRequestComplete) - { - LogPrint (eLogError, "Destination: request callback is not specified in CreateStream"); - return; - } - auto leaseSet = FindLeaseSet (dest); - if (leaseSet) - streamRequestComplete(CreateStream (leaseSet, port)); - else - { - auto s = shared_from_this (); - RequestDestination (dest, - [s, streamRequestComplete, port](std::shared_ptr ls) - { - if (ls) - streamRequestComplete(s->CreateStream (ls, port)); - else - streamRequestComplete (nullptr); - }); - } - } - - std::shared_ptr LeaseSetDestination::CreateStream (std::shared_ptr remote, int port) - { - if (m_StreamingDestination) - return m_StreamingDestination->CreateNewOutgoingStream (remote, port); - else - return nullptr; - } - - std::shared_ptr LeaseSetDestination::GetStreamingDestination (int port) const - { - if (port) - { - auto it = m_StreamingDestinationsByPorts.find (port); - if (it != m_StreamingDestinationsByPorts.end ()) - return it->second; - } - // if port is zero or not found, use default destination - return m_StreamingDestination; - } - - void LeaseSetDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor) - { - if (m_StreamingDestination) - m_StreamingDestination->SetAcceptor (acceptor); - } - - void LeaseSetDestination::StopAcceptingStreams () - { - if (m_StreamingDestination) - m_StreamingDestination->ResetAcceptor (); - } - - bool LeaseSetDestination::IsAcceptingStreams () const - { - if (m_StreamingDestination) - return m_StreamingDestination->IsAcceptorSet (); - return false; - } - - std::shared_ptr LeaseSetDestination::CreateStreamingDestination (int port, bool gzip) - { - auto dest = std::make_shared (shared_from_this (), port, gzip); - if (port) - m_StreamingDestinationsByPorts[port] = dest; - else // update default - m_StreamingDestination = dest; - return dest; - } - - i2p::datagram::DatagramDestination * LeaseSetDestination::CreateDatagramDestination () - { - if (!m_DatagramDestination) - m_DatagramDestination = new i2p::datagram::DatagramDestination (shared_from_this ()); - return m_DatagramDestination; - } - bool LeaseSetDestination::RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete) { if (!m_Pool || !IsReady ()) @@ -751,23 +629,9 @@ namespace client } } - std::vector > LeaseSetDestination::GetAllStreams () const - { - std::vector > ret; - if (m_StreamingDestination) - { - for (auto& it: m_StreamingDestination->GetStreams ()) - ret.push_back (it.second); - } - for (auto& it: m_StreamingDestinationsByPorts) - for (auto& it1: it.second->GetStreams ()) - ret.push_back (it1.second); - return ret; - } - ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): LeaseSetDestination (isPublic, params), - m_Keys (keys) + m_Keys (keys), m_DatagramDestination (nullptr) { if (isPublic) PersistTemporaryKeys (); @@ -777,6 +641,157 @@ namespace client LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created"); } + ClientDestination::~ClientDestination () + { + if (m_DatagramDestination) + delete m_DatagramDestination; + } + + bool ClientDestination::Start () + { + if (LeaseSetDestination::Start ()) + { + m_StreamingDestination = std::make_shared (shared_from_this ()); // TODO: + m_StreamingDestination->Start (); + for (auto it: m_StreamingDestinationsByPorts) + it.second->Start (); + return true; + } + else + return false; + } + + bool ClientDestination::Stop () + { + if (LeaseSetDestination::Stop ()) + { + m_StreamingDestination->Stop (); + m_StreamingDestination = nullptr; + for (auto it: m_StreamingDestinationsByPorts) + it.second->Stop (); + if (m_DatagramDestination) + { + auto d = m_DatagramDestination; + m_DatagramDestination = nullptr; + delete d; + } + return true; + } + else + return false; + } + + void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) + { + uint32_t length = bufbe32toh (buf); + buf += 4; + // we assume I2CP payload + uint16_t fromPort = bufbe16toh (buf + 4), // source + toPort = bufbe16toh (buf + 6); // destination + switch (buf[9]) + { + case PROTOCOL_TYPE_STREAMING: + { + // streaming protocol + auto dest = GetStreamingDestination (toPort); + if (dest) + dest->HandleDataMessagePayload (buf, length); + else + LogPrint (eLogError, "Destination: Missing streaming destination"); + } + break; + case PROTOCOL_TYPE_DATAGRAM: + // datagram protocol + if (m_DatagramDestination) + m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length); + else + LogPrint (eLogError, "Destination: Missing datagram destination"); + break; + default: + LogPrint (eLogError, "Destination: Data: unexpected protocol ", buf[9]); + } + } + + void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) + { + if (!streamRequestComplete) + { + LogPrint (eLogError, "Destination: request callback is not specified in CreateStream"); + return; + } + auto leaseSet = FindLeaseSet (dest); + if (leaseSet) + streamRequestComplete(CreateStream (leaseSet, port)); + else + { + auto s = std::static_pointer_cast(shared_from_this ()); + RequestDestination (dest, + [s, streamRequestComplete, port](std::shared_ptr ls) + { + if (ls) + streamRequestComplete(s->CreateStream (ls, port)); + else + streamRequestComplete (nullptr); + }); + } + } + + std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, int port) + { + if (m_StreamingDestination) + return m_StreamingDestination->CreateNewOutgoingStream (remote, port); + else + return nullptr; + } + + std::shared_ptr ClientDestination::GetStreamingDestination (int port) const + { + if (port) + { + auto it = m_StreamingDestinationsByPorts.find (port); + if (it != m_StreamingDestinationsByPorts.end ()) + return it->second; + } + // if port is zero or not found, use default destination + return m_StreamingDestination; + } + + void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor) + { + if (m_StreamingDestination) + m_StreamingDestination->SetAcceptor (acceptor); + } + + void ClientDestination::StopAcceptingStreams () + { + if (m_StreamingDestination) + m_StreamingDestination->ResetAcceptor (); + } + + bool ClientDestination::IsAcceptingStreams () const + { + if (m_StreamingDestination) + return m_StreamingDestination->IsAcceptorSet (); + return false; + } + + std::shared_ptr ClientDestination::CreateStreamingDestination (int port, bool gzip) + { + auto dest = std::make_shared (shared_from_this (), port, gzip); + if (port) + m_StreamingDestinationsByPorts[port] = dest; + else // update default + m_StreamingDestination = dest; + return dest; + } + + i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination () + { + if (!m_DatagramDestination) + m_DatagramDestination = new i2p::datagram::DatagramDestination (shared_from_this ()); + return m_DatagramDestination; + } + void ClientDestination::PersistTemporaryKeys () { std::string ident = GetIdentHash().ToBase32(); @@ -800,5 +815,19 @@ namespace client } LogPrint(eLogError, "Destinations: Can't save keys to ", path); } + + std::vector > ClientDestination::GetAllStreams () const + { + std::vector > ret; + if (m_StreamingDestination) + { + for (auto& it: m_StreamingDestination->GetStreams ()) + ret.push_back (it.second); + } + for (auto& it: m_StreamingDestinationsByPorts) + for (auto& it1: it.second->GetStreams ()) + ret.push_back (it1.second); + return ret; + } } } diff --git a/Destination.h b/Destination.h index 3cf4ff48..c2292cfe 100644 --- a/Destination.h +++ b/Destination.h @@ -71,8 +71,8 @@ namespace client LeaseSetDestination (bool isPublic, const std::map * params = nullptr); ~LeaseSetDestination (); - virtual void Start (); - virtual void Stop (); + virtual bool Start (); + virtual bool Stop (); bool IsRunning () const { return m_IsRunning; }; boost::asio::io_service& GetService () { return m_Service; }; std::shared_ptr GetTunnelPool () { return m_Pool; }; @@ -81,20 +81,6 @@ namespace client bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); void CancelDestinationRequest (const i2p::data::IdentHash& dest); - // streaming - std::shared_ptr CreateStreamingDestination (int port, bool gzip = true); // additional - std::shared_ptr GetStreamingDestination (int port = 0) const; - // following methods operate with default streaming destination - void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); - std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); - void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); - void StopAcceptingStreams (); - bool IsAcceptingStreams () const; - - // datagram - i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; - i2p::datagram::DatagramDestination * CreateDatagramDestination (); - // implements GarlicDestination std::shared_ptr GetLeaseSet (); std::shared_ptr GetTunnelPool () const { return m_Pool; } @@ -106,9 +92,11 @@ namespace client void ProcessDeliveryStatusMessage (std::shared_ptr msg); void SetLeaseSetUpdated (); - // I2CP - void HandleDataMessage (const uint8_t * buf, size_t len); + protected: + // I2CP + virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0; + private: void Run (); @@ -125,7 +113,7 @@ namespace client void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleCleanupTimer (const boost::system::error_code& ecode); void CleanupRemoteLeaseSets (); - + private: volatile bool m_IsRunning; @@ -140,10 +128,6 @@ namespace client bool m_IsPublic; uint32_t m_PublishReplyToken; std::set m_ExcludedFloodfills; // for publishing - - std::shared_ptr m_StreamingDestination; // default - std::map > m_StreamingDestinationsByPorts; - i2p::datagram::DatagramDestination * m_DatagramDestination; boost::asio::deadline_timer m_PublishConfirmationTimer, m_PublishVerificationTimer, m_CleanupTimer; @@ -151,7 +135,6 @@ namespace client // for HTTP only int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); }; - std::vector > GetAllStreams () const; }; class ClientDestination: public LeaseSetDestination @@ -159,12 +142,35 @@ namespace client public: ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); + ~ClientDestination (); + + bool Start (); + bool Stop (); + // streaming + std::shared_ptr CreateStreamingDestination (int port, bool gzip = true); // additional + std::shared_ptr GetStreamingDestination (int port = 0) const; + // following methods operate with default streaming destination + void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); + std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); + void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); + void StopAcceptingStreams (); + bool IsAcceptingStreams () const; + + // datagram + i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; + i2p::datagram::DatagramDestination * CreateDatagramDestination (); + // implements LocalDestination const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; + protected: + + // I2CP + void HandleDataMessage (const uint8_t * buf, size_t len); + private: void PersistTemporaryKeys (); @@ -173,6 +179,15 @@ namespace client i2p::data::PrivateKeys m_Keys; uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; + + std::shared_ptr m_StreamingDestination; // default + std::map > m_StreamingDestinationsByPorts; + i2p::datagram::DatagramDestination * m_DatagramDestination; + + public: + + // for HTTP only + std::vector > GetAllStreams () const; }; } } From 50ff0d251ae6b75121143fa4fdf81af809fe1f21 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 24 May 2016 00:00:00 +0000 Subject: [PATCH 1274/6300] * HTTP.h : add base class HTTPMsg --- HTTP.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/HTTP.h b/HTTP.h index 9bd31c75..82594874 100644 --- a/HTTP.h +++ b/HTTP.h @@ -54,8 +54,11 @@ namespace http { std::string to_string (); }; - struct HTTPReq { + struct HTTPMsg { std::map headers; + }; + + struct HTTPReq : HTTPMsg { std::string version; std::string method; std::string uri; @@ -75,8 +78,7 @@ namespace http { std::string to_string(); }; - struct HTTPRes { - std::map headers; + struct HTTPRes : HTTPMsg { std::string version; std::string status; unsigned short int code; From a461f462d223d1004423aff3fce5276800fca7e0 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 24 May 2016 00:00:00 +0000 Subject: [PATCH 1275/6300] * HTTP.{cpp,h} : add HTTPMsg::{add,del}_header() helpers --- HTTP.cpp | 15 +++++++++++++++ HTTP.h | 3 +++ 2 files changed, 18 insertions(+) diff --git a/HTTP.cpp b/HTTP.cpp index ef43d55f..f74e9429 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -184,6 +184,21 @@ namespace http { return out; } + void HTTPMsg::add_header(const char *name, const char *value, bool replace) { + std::size_t count = headers.count(name); + if (count && !replace) + return; + if (count) { + headers[name] = value; + return; + } + headers.insert(std::pair(name, value)); + } + + void HTTPMsg::del_header(const char *name) { + headers.erase(name); + } + int HTTPReq::parse(const char *buf, size_t len) { std::string str(buf, len); return parse(str); diff --git a/HTTP.h b/HTTP.h index 82594874..e13fe547 100644 --- a/HTTP.h +++ b/HTTP.h @@ -56,6 +56,9 @@ namespace http { struct HTTPMsg { std::map headers; + + void add_header(const char *name, const char *value, bool replace = false); + void del_header(const char *name); }; struct HTTPReq : HTTPMsg { From 70e9d85a756357a2f018b58d3ac1d3a4e4f6bd57 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 24 May 2016 00:00:00 +0000 Subject: [PATCH 1276/6300] * HTTP.cpp : add internal function gen_rfc1123_date() --- HTTP.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/HTTP.cpp b/HTTP.cpp index f74e9429..6747c35b 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -8,6 +8,7 @@ #include "HTTP.h" #include +#include namespace i2p { namespace http { @@ -54,6 +55,13 @@ namespace http { return true; } + void gen_rfc1123_date(std::string & out) { + std::time_t now = std::time(nullptr); + char buf[128]; + std::strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&now)); + out = buf; + } + bool URL::parse(const char *str, std::size_t len) { std::string url(str, len ? len : strlen(str)); return parse(url); From 2ce61402bb4a300f6b79a21efb924d1a0306bb0e Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 24 May 2016 00:00:00 +0000 Subject: [PATCH 1277/6300] * HTTP.{cpp,h} * add 'body' member ot HTTPRes * change HTTPRes::to_string() to add 'Date', 'Content-Length' headers and body --- HTTP.cpp | 12 ++++++++++++ HTTP.h | 14 +++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/HTTP.cpp b/HTTP.cpp index 6747c35b..254cab95 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -334,12 +334,24 @@ namespace http { } std::string HTTPRes::to_string() { + if (version == "HTTP/1.1" && headers.count("Date") == 0) { + std::string date; + gen_rfc1123_date(date); + add_header("Date", date.c_str()); + } + if (status == "OK" && code != 200) + status = HTTPCodeToStatus(code); // update + if (body.length() > 0 && headers.count("Content-Length") == 0) + add_header("Content-Length", std::to_string(body.length()).c_str()); + /* build response */ std::stringstream ss; ss << version << " " << code << " " << status << CRLF; for (auto & h : headers) { ss << h.first << ": " << h.second << CRLF; } ss << CRLF; + if (body.length() > 0) + ss << body; return ss.str(); } diff --git a/HTTP.h b/HTTP.h index e13fe547..7f73285f 100644 --- a/HTTP.h +++ b/HTTP.h @@ -85,6 +85,12 @@ namespace http { std::string version; std::string status; unsigned short int code; + /** simplifies response generation + * If this variable is set: + * a) Content-Length header will be added if missing + * b) contents of body will be included in response + */ + std::string body; HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {} @@ -96,7 +102,13 @@ namespace http { int parse(const char *buf, size_t len); int parse(const std::string& buf); - /** @brief Serialize HTTP response to string */ + /** + * @brief Serialize HTTP response to string + * @note If version is set to HTTP/1.1, and Date header is missing, + * it will be generated based on current time and added to headers + * @note If body member is set and Content-Length header is missing, + * this header will be added, based on body's length + */ std::string to_string(); /** @brief Checks that response declared as chunked data */ From b68f06ca83c6521998715513ae7d80287e3bc56d Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 24 May 2016 00:00:00 +0000 Subject: [PATCH 1278/6300] * update tests --- tests/test-http-req.cpp | 7 ++++++- tests/test-http-res.cpp | 15 ++++++++++++++- tests/test-http-url.cpp | 2 +- tests/test-http-url_decode.cpp | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/tests/test-http-req.cpp b/tests/test-http-req.cpp index 484a7ad6..10ea621f 100644 --- a/tests/test-http-req.cpp +++ b/tests/test-http-req.cpp @@ -3,11 +3,12 @@ using namespace i2p::http; -int main(int argc, char *argv[]) { +int main() { HTTPReq *req; int ret = 0, len = 0; const char *buf; + /* test: parsing request with body */ buf = "GET / HTTP/1.0\r\n" "User-Agent: curl/7.26.0\r\n" @@ -31,6 +32,7 @@ int main(int argc, char *argv[]) { assert(req->headers.find("User-Agent")->second == "curl/7.26.0"); delete req; + /* test: parsing request without body */ buf = "GET / HTTP/1.0\r\n" "\r\n"; @@ -44,6 +46,7 @@ int main(int argc, char *argv[]) { assert(req->headers.size() == 0); delete req; + /* test: parsing request without body */ buf = "GET / HTTP/1.1\r\n" "\r\n"; @@ -52,6 +55,7 @@ int main(int argc, char *argv[]) { assert((ret = req->parse(buf, len)) == -1); /* no host header */ delete req; + /* test: parsing incomplete request */ buf = "GET / HTTP/1.0\r\n" ""; @@ -60,6 +64,7 @@ int main(int argc, char *argv[]) { assert((ret = req->parse(buf, len)) == 0); /* request not completed */ delete req; + /* test: parsing slightly malformed request */ buf = "GET http://inr.i2p HTTP/1.1\r\n" "Host: stats.i2p\r\n" diff --git a/tests/test-http-res.cpp b/tests/test-http-res.cpp index 6188a68d..7dd74e1e 100644 --- a/tests/test-http-res.cpp +++ b/tests/test-http-res.cpp @@ -3,11 +3,12 @@ using namespace i2p::http; -int main(int argc, char *argv[]) { +int main() { HTTPRes *res; int ret = 0, len = 0; const char *buf; + /* test: parsing valid response without body */ buf = "HTTP/1.1 304 Not Modified\r\n" "Date: Thu, 14 Apr 2016 00:00:00 GMT\r\n" @@ -31,6 +32,18 @@ int main(int argc, char *argv[]) { assert(res->length() == 536); delete res; + /* test: building request */ + buf = + "HTTP/1.0 304 Not Modified\r\n" + "Content-Length: 0\r\n" + "\r\n"; + res = new HTTPRes; + res->version = "HTTP/1.0"; + res->code = 304; + res->status = "Not Modified"; + res->add_header("Content-Length", "0"); + assert(res->to_string() == buf); + return 0; } diff --git a/tests/test-http-url.cpp b/tests/test-http-url.cpp index 71b2f703..d574f1e2 100644 --- a/tests/test-http-url.cpp +++ b/tests/test-http-url.cpp @@ -3,7 +3,7 @@ using namespace i2p::http; -int main(int argc, char *argv[]) { +int main() { std::map params; URL *url; diff --git a/tests/test-http-url_decode.cpp b/tests/test-http-url_decode.cpp index 1c548e6f..f72b2c50 100644 --- a/tests/test-http-url_decode.cpp +++ b/tests/test-http-url_decode.cpp @@ -3,7 +3,7 @@ using namespace i2p::http; -int main(int argc, char *argv[]) { +int main() { std::string in("/%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0/"); std::string out = UrlDecode(in); From f10064ce39a8deea81d6c5157a5d3177fdfd71bc Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 24 May 2016 00:00:00 +0000 Subject: [PATCH 1279/6300] * HTTPServer.cpp : update response building --- HTTPServer.cpp | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 4e44e6f9..5d54711d 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -749,7 +748,7 @@ namespace http { if (needAuth && !CheckAuth(req)) { res.code = 401; - res.headers.insert(std::pair("WWW-Authenticate", "Basic realm=\"WebAdmin\"")); + res.add_header("WWW-Authenticate", "Basic realm=\"WebAdmin\""); SendReply(res, content); return; } @@ -763,6 +762,8 @@ namespace http { else ShowStatus (s); ShowPageTail (s); + + res.code = 200; content = s.str (); SendReply (res, content); } @@ -845,21 +846,11 @@ namespace http { void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) { - std::time_t time_now = std::time(nullptr); - char time_buff[128]; - std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now)); - reply.status = HTTPCodeToStatus(reply.code); - reply.headers.insert(std::pair("Date", time_buff)); - reply.headers.insert(std::pair("Content-Type", "text/html")); - reply.headers.insert(std::pair("Content-Length", std::to_string(content.size()))); + reply.add_header("Content-Type", "text/html"); + reply.body = content; std::string res = reply.to_string(); - std::vector buffers; - - buffers.push_back(boost::asio::buffer(res)); - buffers.push_back(boost::asio::buffer(content)); - - boost::asio::async_write (*m_Socket, buffers, + boost::asio::async_write (*m_Socket, boost::asio::buffer(res), std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); } From f01f6e94d1487f2036ecee5daaf146d0d75bc409 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 24 May 2016 16:27:34 -0400 Subject: [PATCH 1280/6300] fix #500. check result of readline --- AddressBook.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 766d8c95..40e4c383 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -716,12 +716,14 @@ namespace client if (status == 200) // OK { 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) { @@ -741,7 +743,7 @@ namespace client } } LogPrint (eLogInfo, "Addressbook: received ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); - if (!response.eof ()) + if (!response.eof () && !response.fail ()) { success = true; if (!isChunked) From 7035ead9e774406c45ff7c286a3a71ff1e3b8e5f Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 May 2016 12:55:58 -0400 Subject: [PATCH 1281/6300] provide reply tunnel expcilitly for LeaseSet --- Destination.cpp | 8 +++++++- I2NPProtocol.cpp | 11 +++++------ I2NPProtocol.h | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index c5a3beff..f64fe208 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -407,6 +407,12 @@ namespace client LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); return; } + auto inbound = m_Pool->GetNextInboundTunnel (); + if (!inbound) + { + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); + return; + } auto floodfill = i2p::data::netdb.GetClosestFloodfill (m_LeaseSet->GetIdentHash (), m_ExcludedFloodfills); if (!floodfill) { @@ -417,7 +423,7 @@ namespace client m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); - auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken)); + auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken, inbound)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index e2451f68..1f1caab7 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -249,7 +249,7 @@ namespace i2p return m; } - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken) + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken, std::shared_ptr replyTunnel) { if (!leaseSet) return nullptr; auto m = NewI2NPShortMessage (); @@ -258,14 +258,13 @@ namespace i2p payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, replyToken); size_t size = DATABASE_STORE_HEADER_SIZE; - if (replyToken) + if (replyToken && replyTunnel) { - auto leases = leaseSet->GetNonExpiredLeases (); - if (leases.size () > 0) + if (replyTunnel) { - htobe32buf (payload + size, leases[0]->tunnelID); + htobe32buf (payload + size, replyTunnel->GetNextTunnelID ()); size += 4; // reply tunnelID - memcpy (payload + size, leases[0]->tunnelGateway, 32); + memcpy (payload + size, replyTunnel->GetNextIdentHash (), 32); size += 32; // reply tunnel gateway } else diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 113e8eb8..1eff8c16 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -224,7 +224,7 @@ namespace tunnel std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0); - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0); + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0, std::shared_ptr replyTunnel = nullptr); bool IsRouterInfoMsg (std::shared_ptr msg); bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); From f2292fd618e90758916fd1367607db1476502360 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 May 2016 14:17:34 -0400 Subject: [PATCH 1282/6300] LocalLeaseSet added --- LeaseSet.cpp | 44 +++++++++++++++++++++++++++++++++++++++++++- LeaseSet.h | 27 +++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 5d259b33..0bc380f6 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -4,6 +4,7 @@ #include "Log.h" #include "Timestamp.h" #include "NetDb.h" +#include "Tunnel.h" #include "TunnelPool.h" #include "LeaseSet.h" @@ -195,7 +196,7 @@ namespace data if (size > len) return 0; uint8_t num = buf[size]; size++; // num - if (size + num*44 > len) return 0; + if (size + num*LEASE_SIZE > len) return 0; uint64_t timestamp= 0 ; for (int i = 0; i < num; i++) { @@ -243,6 +244,47 @@ namespace data if (IsEmpty ()) return true; auto ts = i2p::util::GetMillisecondsSinceEpoch (); return ts > m_ExpirationTime; + } + + LocalLeaseSet::LocalLeaseSet (std::shared_ptr identity, const uint8_t * encryptionPublicKey, std::vector > tunnels): + m_Identity (identity) + { + int num = tunnels.size (); + if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES; + // identity + m_BufferLen = m_Identity->GetFullLen () + 256 + num*LEASE_SIZE + m_Identity->GetSignatureLen (); + m_Buffer = new uint8_t[m_BufferLen]; + auto offset = m_Identity->ToBuffer (m_Buffer, m_BufferLen); + memcpy (m_Buffer + offset, encryptionPublicKey, 256); + offset += 256; + auto signingKeyLen = m_Identity->GetSigningPublicKeyLen (); + memset (m_Buffer + offset, 0, signingKeyLen); + offset += signingKeyLen; + // num leases + m_Buffer[offset] = num; + offset++; + // leases + auto currentTime = i2p::util::GetMillisecondsSinceEpoch (); + for (int i = 0; i < num; i++) + { + memcpy (m_Buffer + offset, tunnels[i]->GetNextIdentHash (), 32); + offset += 32; // gateway id + htobe32buf (m_Buffer + offset, tunnels[i]->GetNextTunnelID ()); + offset += 4; // tunnel id + uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration + ts *= 1000; // in milliseconds + // make sure leaseset is newer than previous, but adding some time to expiration date + ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs + htobe64buf (m_Buffer + offset, ts); + offset += 8; // end date + } + // we don't sign it yet. must be signed later on } + + void LocalLeaseSet::SetSignature (const uint8_t * signature) + { + auto signatureLen = GetSignatureLen (); + memcpy (m_Buffer + m_BufferLen - signatureLen, signature, signatureLen); + } } } diff --git a/LeaseSet.h b/LeaseSet.h index f5685210..644e0ef4 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -12,6 +12,7 @@ namespace i2p namespace tunnel { + class InboundTunnel; class TunnelPool; } @@ -37,14 +38,15 @@ namespace data }; }; - const int MAX_LS_BUFFER_SIZE = 3072; + const size_t MAX_LS_BUFFER_SIZE = 3072; + const size_t LEASE_SIZE = 44; // 32 + 4 + 8 const uint8_t MAX_NUM_LEASES = 16; class LeaseSet: public RoutingDestination { public: LeaseSet (const uint8_t * buf, size_t len, bool storeLeases = true); - LeaseSet (std::shared_ptr pool); + LeaseSet (std::shared_ptr pool); // deprecated ~LeaseSet () { delete[] m_Buffer; }; void Update (const uint8_t * buf, size_t len); bool IsNewer (const uint8_t * buf, size_t len) const; @@ -82,6 +84,27 @@ namespace data uint8_t * m_Buffer; size_t m_BufferLen; }; + + class LocalLeaseSet + { + public: + + LocalLeaseSet (std::shared_ptr identity, const uint8_t * encryptionPublicKey, std::vector > tunnels); + ~LocalLeaseSet () { delete[] m_Buffer; }; + + void SetSignature (const uint8_t * signature); + + const uint8_t * GetBuffer () const { return m_Buffer; }; + size_t GetBufferLen () const { return m_BufferLen; }; + size_t GetSignatureLen () const { return m_Identity->GetSignatureLen (); }; + const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); }; + + private: + + std::shared_ptr m_Identity; + uint8_t * m_Buffer; + size_t m_BufferLen; + }; } } From 4e4f9b6f8beab91e44e3bdbf39132408dd3fbc09 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 May 2016 15:10:28 -0400 Subject: [PATCH 1283/6300] use LocalLeaseSet for own LeaseSets --- Destination.cpp | 9 +++++++-- Destination.h | 4 ++-- Garlic.h | 2 +- I2NPProtocol.cpp | 18 +++++++++++++++++- I2NPProtocol.h | 3 ++- LeaseSet.cpp | 15 ++++++++------- LeaseSet.h | 8 ++++++-- RouterContext.h | 2 +- 8 files changed, 44 insertions(+), 17 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index f64fe208..a7d33454 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -192,7 +192,7 @@ namespace client return nullptr; } - std::shared_ptr LeaseSetDestination::GetLeaseSet () + std::shared_ptr LeaseSetDestination::GetLeaseSet () { if (!m_Pool) return nullptr; if (!m_LeaseSet) @@ -202,7 +202,12 @@ namespace client void LeaseSetDestination::UpdateLeaseSet () { - m_LeaseSet.reset (new i2p::data::LeaseSet (m_Pool)); + int numTunnels = m_Pool->GetNumInboundTunnels () + 2; // 2 backup tunnels + if (numTunnels > i2p::data::MAX_NUM_LEASES) numTunnels = i2p::data::MAX_NUM_LEASES; // 16 tunnels maximum + auto leaseSet = new i2p::data::LocalLeaseSet (GetIdentity (), GetEncryptionPublicKey (), + m_Pool->GetInboundTunnels (numTunnels)); + Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); // TODO + m_LeaseSet.reset (leaseSet); } bool LeaseSetDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) diff --git a/Destination.h b/Destination.h index c2292cfe..85b4b5ab 100644 --- a/Destination.h +++ b/Destination.h @@ -82,7 +82,7 @@ namespace client void CancelDestinationRequest (const i2p::data::IdentHash& dest); // implements GarlicDestination - std::shared_ptr GetLeaseSet (); + std::shared_ptr GetLeaseSet (); std::shared_ptr GetTunnelPool () const { return m_Pool; } void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); @@ -124,7 +124,7 @@ namespace client std::map > m_LeaseSetRequests; std::shared_ptr m_Pool; - std::shared_ptr m_LeaseSet; + std::shared_ptr m_LeaseSet; bool m_IsPublic; uint32_t m_PublishReplyToken; std::set m_ExcludedFloodfills; // for publishing diff --git a/Garlic.h b/Garlic.h index ea53dd03..6d25fd39 100644 --- a/Garlic.h +++ b/Garlic.h @@ -163,7 +163,7 @@ namespace garlic virtual void ProcessDeliveryStatusMessage (std::shared_ptr msg); virtual void SetLeaseSetUpdated (); - virtual std::shared_ptr GetLeaseSet () = 0; // TODO + virtual std::shared_ptr GetLeaseSet () = 0; // TODO virtual std::shared_ptr GetTunnelPool () const = 0; virtual void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) = 0; diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 1f1caab7..c47a1657 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -249,7 +249,23 @@ namespace i2p return m; } - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken, std::shared_ptr replyTunnel) + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet) + { + if (!leaseSet) return nullptr; + auto m = NewI2NPShortMessage (); + uint8_t * payload = m->GetPayload (); + memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32); + payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet + htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); + size_t size = DATABASE_STORE_HEADER_SIZE; + memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ()); + size += leaseSet->GetBufferLen (); + m->len += size; + m->FillI2NPMessageHeader (eI2NPDatabaseStore); + return m; + } + + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken, std::shared_ptr replyTunnel) { if (!leaseSet) return nullptr; auto m = NewI2NPShortMessage (); diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 1eff8c16..1ade55ed 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -224,7 +224,8 @@ namespace tunnel std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0); - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0, std::shared_ptr replyTunnel = nullptr); + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet); // for floodfill only + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0, std::shared_ptr replyTunnel = nullptr); bool IsRouterInfoMsg (std::shared_ptr msg); bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 0bc380f6..e78cc9ce 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -247,7 +247,7 @@ namespace data } LocalLeaseSet::LocalLeaseSet (std::shared_ptr identity, const uint8_t * encryptionPublicKey, std::vector > tunnels): - m_Identity (identity) + m_ExpirationTime (0), m_Identity (identity) { int num = tunnels.size (); if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES; @@ -273,18 +273,19 @@ namespace data offset += 4; // tunnel id uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration ts *= 1000; // in milliseconds + if (ts > m_ExpirationTime) m_ExpirationTime = ts; // make sure leaseset is newer than previous, but adding some time to expiration date ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs htobe64buf (m_Buffer + offset, ts); offset += 8; // end date } // we don't sign it yet. must be signed later on - } - - void LocalLeaseSet::SetSignature (const uint8_t * signature) - { - auto signatureLen = GetSignatureLen (); - memcpy (m_Buffer + m_BufferLen - signatureLen, signature, signatureLen); } + + bool LocalLeaseSet::IsExpired () const + { + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + return ts > m_ExpirationTime; + } } } diff --git a/LeaseSet.h b/LeaseSet.h index 644e0ef4..faeda67f 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -92,15 +92,19 @@ namespace data LocalLeaseSet (std::shared_ptr identity, const uint8_t * encryptionPublicKey, std::vector > tunnels); ~LocalLeaseSet () { delete[] m_Buffer; }; - void SetSignature (const uint8_t * signature); - const uint8_t * GetBuffer () const { return m_Buffer; }; + uint8_t * GetSignature () { return m_Buffer + m_BufferLen - GetSignatureLen (); }; size_t GetBufferLen () const { return m_BufferLen; }; size_t GetSignatureLen () const { return m_Identity->GetSignatureLen (); }; const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); }; + bool IsExpired () const; + bool operator== (const LeaseSet& other) const + { return m_BufferLen == other.GetBufferLen () && !memcmp (other.GetBuffer (), other.GetBuffer (), m_BufferLen); }; + private: + uint64_t m_ExpirationTime; // in milliseconds std::shared_ptr m_Identity; uint8_t * m_Buffer; size_t m_BufferLen; diff --git a/RouterContext.h b/RouterContext.h index 5a72ad58..29164f4b 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -79,7 +79,7 @@ namespace i2p void SetLeaseSetUpdated () {}; // implements GarlicDestination - std::shared_ptr GetLeaseSet () { return nullptr; }; + std::shared_ptr GetLeaseSet () { return nullptr; }; std::shared_ptr GetTunnelPool () const; void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); From e686fad5468b62b9dc4bf68e3ff6760a3ae4da02 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 May 2016 15:18:21 -0400 Subject: [PATCH 1284/6300] rmoved deprecated constructor --- LeaseSet.cpp | 50 -------------------------------------------------- LeaseSet.h | 1 - 2 files changed, 51 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index e78cc9ce..0c1d5b37 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -22,56 +22,6 @@ namespace data ReadFromBuffer (); } - LeaseSet::LeaseSet (std::shared_ptr pool): - m_IsValid (true), m_StoreLeases (true), m_ExpirationTime (0) - { - if (!pool) return; - // header - auto localDestination = pool->GetLocalDestination (); - if (!localDestination) - { - m_Buffer = nullptr; - m_BufferLen = 0; - m_IsValid = false; - LogPrint (eLogError, "LeaseSet: Destination for local LeaseSet doesn't exist"); - return; - } - m_Buffer = new uint8_t[MAX_LS_BUFFER_SIZE]; - m_BufferLen = localDestination->GetIdentity ()->ToBuffer (m_Buffer, MAX_LS_BUFFER_SIZE); - memcpy (m_Buffer + m_BufferLen, localDestination->GetEncryptionPublicKey (), 256); - m_BufferLen += 256; - auto signingKeyLen = localDestination->GetIdentity ()->GetSigningPublicKeyLen (); - memset (m_Buffer + m_BufferLen, 0, signingKeyLen); - m_BufferLen += signingKeyLen; - int numTunnels = pool->GetNumInboundTunnels () + 2; // 2 backup tunnels - if (numTunnels > 16) numTunnels = 16; // 16 tunnels maximum - auto tunnels = pool->GetInboundTunnels (numTunnels); - m_Buffer[m_BufferLen] = tunnels.size (); // num leases - m_BufferLen++; - // leases - auto currentTime = i2p::util::GetMillisecondsSinceEpoch (); - for (auto it: tunnels) - { - memcpy (m_Buffer + m_BufferLen, it->GetNextIdentHash (), 32); - m_BufferLen += 32; // gateway id - htobe32buf (m_Buffer + m_BufferLen, it->GetNextTunnelID ()); - m_BufferLen += 4; // tunnel id - uint64_t ts = it->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration - ts *= 1000; // in milliseconds - if (ts > m_ExpirationTime) m_ExpirationTime = ts; - // make sure leaseset is newer than previous, but adding some time to expiration date - ts += (currentTime - it->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs - htobe64buf (m_Buffer + m_BufferLen, ts); - m_BufferLen += 8; // end date - } - // signature - localDestination->Sign (m_Buffer, m_BufferLen, m_Buffer + m_BufferLen); - m_BufferLen += localDestination->GetIdentity ()->GetSignatureLen (); - LogPrint (eLogDebug, "LeaseSet: Local LeaseSet of ", tunnels.size (), " leases created"); - - ReadFromBuffer (); - } - void LeaseSet::Update (const uint8_t * buf, size_t len) { if (len > m_BufferLen) diff --git a/LeaseSet.h b/LeaseSet.h index faeda67f..c8604412 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -46,7 +46,6 @@ namespace data public: LeaseSet (const uint8_t * buf, size_t len, bool storeLeases = true); - LeaseSet (std::shared_ptr pool); // deprecated ~LeaseSet () { delete[] m_Buffer; }; void Update (const uint8_t * buf, size_t len); bool IsNewer (const uint8_t * buf, size_t len) const; From 789eb48698bfb8617ed2b05b3a8acbd41d2c872c Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 May 2016 15:30:04 -0400 Subject: [PATCH 1285/6300] removed deprecated constructor --- LeaseSet.cpp | 1 - LeaseSet.h | 1 - 2 files changed, 2 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 0c1d5b37..42b961fa 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -5,7 +5,6 @@ #include "Timestamp.h" #include "NetDb.h" #include "Tunnel.h" -#include "TunnelPool.h" #include "LeaseSet.h" namespace i2p diff --git a/LeaseSet.h b/LeaseSet.h index c8604412..289ae25c 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -13,7 +13,6 @@ namespace i2p namespace tunnel { class InboundTunnel; - class TunnelPool; } namespace data From c7173d5e1c54e896d39d5ff2b91b7eb09f1bab77 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 May 2016 16:18:02 -0400 Subject: [PATCH 1286/6300] use shared ClientDestination --- Datagram.cpp | 2 +- Datagram.h | 6 +++--- Destination.cpp | 8 ++++---- Destination.h | 4 +++- Streaming.cpp | 2 +- Streaming.h | 8 ++++---- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index f06d62da..2015622c 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -11,7 +11,7 @@ namespace i2p { namespace datagram { - DatagramDestination::DatagramDestination (std::shared_ptr owner): + DatagramDestination::DatagramDestination (std::shared_ptr owner): m_Owner (owner), m_Receiver (nullptr) { } diff --git a/Datagram.h b/Datagram.h index 404facf2..c593fad2 100644 --- a/Datagram.h +++ b/Datagram.h @@ -14,7 +14,7 @@ namespace i2p { namespace client { - class LeaseSetDestination; + class ClientDestination; } namespace datagram { @@ -25,7 +25,7 @@ namespace datagram public: - DatagramDestination (std::shared_ptr owner); + DatagramDestination (std::shared_ptr owner); ~DatagramDestination (); void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort = 0, uint16_t toPort = 0); @@ -47,7 +47,7 @@ namespace datagram private: - std::shared_ptr m_Owner; + std::shared_ptr m_Owner; Receiver m_Receiver; // default std::map m_ReceiversByPorts; diff --git a/Destination.cpp b/Destination.cpp index a7d33454..46ff688f 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -662,7 +662,7 @@ namespace client { if (LeaseSetDestination::Start ()) { - m_StreamingDestination = std::make_shared (shared_from_this ()); // TODO: + m_StreamingDestination = std::make_shared (GetSharedFromThis ()); // TODO: m_StreamingDestination->Start (); for (auto it: m_StreamingDestinationsByPorts) it.second->Start (); @@ -735,7 +735,7 @@ namespace client streamRequestComplete(CreateStream (leaseSet, port)); else { - auto s = std::static_pointer_cast(shared_from_this ()); + auto s = GetSharedFromThis (); RequestDestination (dest, [s, streamRequestComplete, port](std::shared_ptr ls) { @@ -788,7 +788,7 @@ namespace client std::shared_ptr ClientDestination::CreateStreamingDestination (int port, bool gzip) { - auto dest = std::make_shared (shared_from_this (), port, gzip); + auto dest = std::make_shared (GetSharedFromThis (), port, gzip); if (port) m_StreamingDestinationsByPorts[port] = dest; else // update default @@ -799,7 +799,7 @@ namespace client i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination () { if (!m_DatagramDestination) - m_DatagramDestination = new i2p::datagram::DatagramDestination (shared_from_this ()); + m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis ()); return m_DatagramDestination; } diff --git a/Destination.h b/Destination.h index 85b4b5ab..4c8bfc80 100644 --- a/Destination.h +++ b/Destination.h @@ -174,7 +174,9 @@ namespace client private: void PersistTemporaryKeys (); - + std::shared_ptr GetSharedFromThis () + { return std::static_pointer_cast(shared_from_this ()); } + private: i2p::data::PrivateKeys m_Keys; diff --git a/Streaming.cpp b/Streaming.cpp index bf29caec..ab0a6df0 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -780,7 +780,7 @@ namespace stream m_CurrentRemoteLease = nullptr; } - StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): + StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), m_PendingIncomingTimer (m_Owner->GetService ()) { diff --git a/Streaming.h b/Streaming.h index 43061b94..c29b62f9 100644 --- a/Streaming.h +++ b/Streaming.h @@ -23,7 +23,7 @@ namespace i2p { namespace client { - class LeaseSetDestination; + class ClientDestination; } namespace stream { @@ -193,7 +193,7 @@ namespace stream typedef std::function)> Acceptor; - StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = true); + StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = true); ~StreamingDestination (); void Start (); @@ -204,7 +204,7 @@ namespace stream void SetAcceptor (const Acceptor& acceptor); void ResetAcceptor (); bool IsAcceptorSet () const { return m_Acceptor != nullptr; }; - std::shared_ptr GetOwner () const { return m_Owner; }; + std::shared_ptr GetOwner () const { return m_Owner; }; uint16_t GetLocalPort () const { return m_LocalPort; }; void HandleDataMessagePayload (const uint8_t * buf, size_t len); @@ -218,7 +218,7 @@ namespace stream private: - std::shared_ptr m_Owner; + std::shared_ptr m_Owner; uint16_t m_LocalPort; bool m_Gzip; // gzip compression of data messages std::mutex m_StreamsMutex; From 0d2df220741654c43f1815132eaf27a70be29445 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 May 2016 17:41:24 -0400 Subject: [PATCH 1287/6300] fixed crash --- LeaseSet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 42b961fa..75d8a1ae 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -201,12 +201,12 @@ namespace data int num = tunnels.size (); if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES; // identity - m_BufferLen = m_Identity->GetFullLen () + 256 + num*LEASE_SIZE + m_Identity->GetSignatureLen (); + auto signingKeyLen = m_Identity->GetSigningPublicKeyLen (); + m_BufferLen = m_Identity->GetFullLen () + 256 + signingKeyLen + num*LEASE_SIZE + m_Identity->GetSignatureLen (); m_Buffer = new uint8_t[m_BufferLen]; auto offset = m_Identity->ToBuffer (m_Buffer, m_BufferLen); memcpy (m_Buffer + offset, encryptionPublicKey, 256); offset += 256; - auto signingKeyLen = m_Identity->GetSigningPublicKeyLen (); memset (m_Buffer + offset, 0, signingKeyLen); offset += signingKeyLen; // num leases From 57bb0da1d6895444c5e740fbe32a41dfd62989b5 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 25 May 2016 18:47:16 -0400 Subject: [PATCH 1288/6300] correct LeaseSet message size --- LeaseSet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 75d8a1ae..5efe2b16 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -202,7 +202,7 @@ namespace data if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES; // identity auto signingKeyLen = m_Identity->GetSigningPublicKeyLen (); - m_BufferLen = m_Identity->GetFullLen () + 256 + signingKeyLen + num*LEASE_SIZE + m_Identity->GetSignatureLen (); + m_BufferLen = m_Identity->GetFullLen () + 256 + signingKeyLen + 1 + num*LEASE_SIZE + m_Identity->GetSignatureLen (); m_Buffer = new uint8_t[m_BufferLen]; auto offset = m_Identity->ToBuffer (m_Buffer, m_BufferLen); memcpy (m_Buffer + offset, encryptionPublicKey, 256); From 2e1a9a8df92abb5e564e3f91db7deabe6d0f2725 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1289/6300] * HTTP.{cpp,h} : move length() method to base class --- HTTP.cpp | 2 +- HTTP.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/HTTP.cpp b/HTTP.cpp index 254cab95..83f1ac3f 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -279,7 +279,7 @@ namespace http { return false; } - long int HTTPRes::length() { + long int HTTPMsg::length() { unsigned long int length = 0; auto it = headers.find("Content-Length"); if (it == headers.end()) diff --git a/HTTP.h b/HTTP.h index 7f73285f..15efc989 100644 --- a/HTTP.h +++ b/HTTP.h @@ -59,6 +59,9 @@ namespace http { void add_header(const char *name, const char *value, bool replace = false); void del_header(const char *name); + + /** @brief Returns declared message length or -1 if unknown */ + long int length(); }; struct HTTPReq : HTTPMsg { @@ -113,9 +116,6 @@ namespace http { /** @brief Checks that response declared as chunked data */ bool is_chunked(); - - /** @brief Returns declared response length or -1 if unknown */ - long int length(); }; /** From 43a90d7b98b1b317022f468bcf23027e039142fe Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1290/6300] * HTTP.cpp : fix parse_header_line (#501) --- HTTP.cpp | 3 ++- tests/test-http-req.cpp | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/HTTP.cpp b/HTTP.cpp index 83f1ac3f..66dbc763 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -45,9 +45,10 @@ namespace http { bool parse_header_line(const std::string & line, std::map & headers) { std::size_t pos = 0; std::size_t len = 2; /* strlen(": ") */ + std::size_t max = line.length(); if ((pos = line.find(": ", pos)) == std::string::npos) return false; - while (isspace(line.at(pos + len))) + while ((pos + len) < max && isspace(line.at(pos + len))) len++; std::string name = line.substr(0, pos); std::string value = line.substr(pos + len); diff --git a/tests/test-http-req.cpp b/tests/test-http-req.cpp index 10ea621f..d5362622 100644 --- a/tests/test-http-req.cpp +++ b/tests/test-http-req.cpp @@ -68,6 +68,7 @@ int main() { buf = "GET http://inr.i2p HTTP/1.1\r\n" "Host: stats.i2p\r\n" + "Accept-Encoding: \r\n" "Accept: */*\r\n" "\r\n"; len = strlen(buf); @@ -76,9 +77,13 @@ int main() { assert(req->method == "GET"); assert(req->uri == "http://inr.i2p"); assert(req->host == "stats.i2p"); - assert(req->headers.size() == 2); + assert(req->headers.size() == 3); assert(req->headers.count("Host") == 1); assert(req->headers.count("Accept") == 1); + assert(req->headers.count("Accept-Encoding") == 1); + assert(req->headers["Host"] == "stats.i2p"); + assert(req->headers["Accept"] == "*/*"); + assert(req->headers["Accept-Encoding"] == ""); delete req; return 0; From f245feb0b0c003c3aba37e464dd0d415094bbd77 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1291/6300] * HTTP.h : export MergeChunkedResponse() --- HTTP.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/HTTP.h b/HTTP.h index 15efc989..0aa31b1e 100644 --- a/HTTP.h +++ b/HTTP.h @@ -132,6 +132,14 @@ namespace http { * @return Decoded string */ std::string UrlDecode(const std::string& data, bool null = false); + + /** + * @brief Merge HTTP response content with Transfer-Encoding: chunked + * @param in Input stream + * @param out Output stream + * @return true on success, false otherwise + */ + bool MergeChunkedResponse (std::istream& in, std::ostream& out); } // http } // i2p From a76d8f0f9f7c63c3468ba93a5a13efd31c8d73aa Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1292/6300] * HTTP.{cpp,h} : add add_header() variant with std::string --- HTTP.cpp | 4 ++++ HTTP.h | 1 + 2 files changed, 5 insertions(+) diff --git a/HTTP.cpp b/HTTP.cpp index 66dbc763..3f6fd937 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -193,6 +193,10 @@ namespace http { return out; } + void HTTPMsg::add_header(const char *name, std::string & value, bool replace) { + add_header(name, value.c_str(), replace); + } + void HTTPMsg::add_header(const char *name, const char *value, bool replace) { std::size_t count = headers.count(name); if (count && !replace) diff --git a/HTTP.h b/HTTP.h index 0aa31b1e..f227271f 100644 --- a/HTTP.h +++ b/HTTP.h @@ -57,6 +57,7 @@ namespace http { struct HTTPMsg { std::map headers; + void add_header(const char *name, std::string & value, bool replace = false); void add_header(const char *name, const char *value, bool replace = false); void del_header(const char *name); From ebc411bbbd465bb930235dc546911199a1b1b342 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1293/6300] * I2PControl.cpp : * use new http classes for building HTTP response * drop boost::lexical_cast & boost::local_time deps --- I2PControl.cpp | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index ab9feeed..0f92a5db 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -2,8 +2,6 @@ #include #include #include -#include -#include #include #include @@ -16,6 +14,7 @@ #include "Crypto.h" #include "FS.h" #include "Log.h" +#include "HTTP.h" #include "Config.h" #include "NetDb.h" #include "RouterContext.h" @@ -278,24 +277,21 @@ namespace client void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) { - size_t len = response.str ().length (), offset = 0; - if (isHtml) - { - std::ostringstream header; - header << "HTTP/1.1 200 OK\r\n"; - header << "Connection: close\r\n"; - header << "Content-Length: " << boost::lexical_cast(len) << "\r\n"; - header << "Content-Type: application/json\r\n"; - header << "Date: "; - auto facet = new boost::local_time::local_time_facet ("%a, %d %b %Y %H:%M:%S GMT"); - header.imbue(std::locale (header.getloc(), facet)); - header << boost::posix_time::second_clock::local_time() << "\r\n"; - header << "\r\n"; - offset = header.str ().size (); - memcpy (buf->data (), header.str ().c_str (), offset); + std::string out; + std::size_t len; + if (isHtml) { + i2p::http::HTTPRes res; + res.code = 200; + res.add_header("Content-Type", "application/json"); + res.add_header("Connection", "close"); + res.body = response.str(); + out = res.to_string(); + } else { + out = response.str(); } - memcpy (buf->data () + offset, response.str ().c_str (), len); - boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), + std::copy(out.begin(), out.end(), buf->begin()); + len = out.length(); + boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), len), boost::asio::transfer_all (), std::bind(&I2PControlService::HandleResponseSent, this, std::placeholders::_1, std::placeholders::_2, socket, buf)); @@ -322,7 +318,7 @@ namespace client } InsertParam (results, "API", api); results << ","; - std::string token = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); + std::string token = std::to_string(i2p::util::GetSecondsSinceEpoch ()); m_Tokens.insert (token); InsertParam (results, "Token", token); } From 0e1765e04563f526cbc066d31cde4ae3d28c87fc Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1294/6300] * I2PControl.cpp : SendResponse() third arg now std::string & --- I2PControl.cpp | 20 +++++++++----------- I2PControl.h | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 0f92a5db..7a5014ec 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -192,6 +192,7 @@ namespace client try { bool isHtml = !memcmp (buf->data (), "POST", 4); + std::string content; std::stringstream ss; ss.write (buf->data (), bytes_transferred); if (isHtml) @@ -242,7 +243,8 @@ namespace client response << "\"jsonrpc\":\"2.0\"}"; } #endif - SendResponse (socket, buf, response, isHtml); + content = response.str(); + SendResponse (socket, buf, content, isHtml); } catch (std::exception& ex) { @@ -275,23 +277,19 @@ namespace client } void I2PControlService::SendResponse (std::shared_ptr socket, - std::shared_ptr buf, std::ostringstream& response, bool isHtml) + std::shared_ptr buf, std::string& content, bool isHtml) { - std::string out; - std::size_t len; if (isHtml) { i2p::http::HTTPRes res; res.code = 200; res.add_header("Content-Type", "application/json"); res.add_header("Connection", "close"); - res.body = response.str(); - out = res.to_string(); - } else { - out = response.str(); + res.body = content; + std::string tmp = res.to_string(); + content = tmp; } - std::copy(out.begin(), out.end(), buf->begin()); - len = out.length(); - boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), len), + std::copy(content.begin(), content.end(), buf->begin()); + boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), content.length()), boost::asio::transfer_all (), std::bind(&I2PControlService::HandleResponseSent, this, std::placeholders::_1, std::placeholders::_2, socket, buf)); diff --git a/I2PControl.h b/I2PControl.h index f2e82254..ed572c42 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -46,7 +46,7 @@ namespace client void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); void SendResponse (std::shared_ptr socket, - std::shared_ptr buf, std::ostringstream& response, bool isHtml); + std::shared_ptr buf, std::string& response, bool isHtml); void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); From 4f8db487e77675ebd691f309ac4143c05fefd158 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1295/6300] * I2PControl.{cpp,h} : add BuildErrorResponse() --- I2PControl.cpp | 19 ++++++++++++------- I2PControl.h | 1 + 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 7a5014ec..047f211e 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -221,9 +221,7 @@ namespace client std::ostringstream response; #if GCC47_BOOST149 LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7"); - response << "{\"id\":null,\"error\":"; - response << "{\"code\":-32603,\"message\":\"JSON requests is not supported with this version of boost\"},"; - response << "\"jsonrpc\":\"2.0\"}"; + BuildErrorResponse(content, 32603, "JSON requests is not supported with this version of boost"); #else boost::property_tree::ptree pt; boost::property_tree::read_json (ss, pt); @@ -236,19 +234,18 @@ namespace client response << "{\"id\":" << id << ",\"result\":{"; (this->*(it->second))(pt.get_child ("params"), response); response << "},\"jsonrpc\":\"2.0\"}"; + content = response.str(); } else { LogPrint (eLogWarning, "I2PControl: unknown method ", method); - response << "{\"id\":null,\"error\":"; - response << "{\"code\":-32601,\"message\":\"Method not found\"},"; - response << "\"jsonrpc\":\"2.0\"}"; + BuildErrorResponse(content, 32601, "Method not found"); } #endif - content = response.str(); SendResponse (socket, buf, content, isHtml); } catch (std::exception& ex) { LogPrint (eLogError, "I2PControl: exception when handle request: ", ex.what ()); + /* TODO: also send error, code 32603 */ } catch (...) { @@ -276,6 +273,14 @@ namespace client ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; } + void I2PControlService::BuildErrorResponse (std::string & content, int code, const char *message) { + std::stringstream ss; + ss << "{\"id\":null,\"error\":"; + ss << "{\"code\":" << -code << ",\"message\":\"" << message << "\"},"; + ss << "\"jsonrpc\":\"2.0\"}"; + content = ss.str(); + } + void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::string& content, bool isHtml) { diff --git a/I2PControl.h b/I2PControl.h index ed572c42..bd5b9bad 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -45,6 +45,7 @@ namespace client void ReadRequest (std::shared_ptr socket); void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); + void BuildErrorResponse (std::string & content, int code, const char *message); void SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::string& response, bool isHtml); void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, From 0ab5f993c7d576e055de3cb22b2cf29b92445bc3 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1296/6300] * I2PControl.cpp : * use new http classes for parsing request * implement correct reading rest of json data if HTTP/Content-length is used * general cleanup --- I2PControl.cpp | 69 +++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index 047f211e..d24c0b31 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -191,56 +191,55 @@ namespace client } else { try { - bool isHtml = !memcmp (buf->data (), "POST", 4); - std::string content; - std::stringstream ss; - ss.write (buf->data (), bytes_transferred); - if (isHtml) - { - std::string header; - size_t contentLength = 0; - while (!ss.eof () && header != "\r") - { - std::getline(ss, header); - auto colon = header.find (':'); - if (colon != std::string::npos && header.substr (0, colon) == "Content-Length") - contentLength = std::stoi (header.substr (colon + 1)); + std::stringstream json; + std::string response; + bool isHTTP = false; + if (memcmp (buf->data (), "POST", 4) == 0) { + long int remains = 0; + isHTTP = true; + i2p::http::HTTPReq req; + std::size_t len = req.parse(buf->data(), bytes_transferred); + if (len <= 0) { + LogPrint(eLogError, "I2PControl: incomplete/malformed POST request"); + return; } - if (ss.eof ()) - { - LogPrint (eLogError, "I2PControl: malformed request, HTTP header expected"); - return; // TODO: - } - std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read - if (rem > 0) - { - bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), rem)); - ss.write (buf->data (), bytes_transferred); + /* append to json chunk of data from 1st request */ + json.write(buf->begin() + len, bytes_transferred - len); + remains = req.length() - len; + /* if request has Content-Length header, fetch rest of data and store to json buffer */ + while (remains > 0) { + len = ((long int) buf->size() < remains) ? buf->size() : remains; + bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), len)); + json.write(buf->begin(), bytes_transferred); + remains -= bytes_transferred; } + } else { + json.write(buf->begin(), bytes_transferred); } - std::ostringstream response; + LogPrint(eLogDebug, "I2PControl: json from request: ", json.str()); #if GCC47_BOOST149 LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7"); - BuildErrorResponse(content, 32603, "JSON requests is not supported with this version of boost"); + BuildErrorResponse(response, 32603, "JSON requests is not supported with this version of boost"); #else boost::property_tree::ptree pt; - boost::property_tree::read_json (ss, pt); + boost::property_tree::read_json (json, pt); std::string id = pt.get("id"); std::string method = pt.get("method"); auto it = m_MethodHandlers.find (method); if (it != m_MethodHandlers.end ()) { - response << "{\"id\":" << id << ",\"result\":{"; - (this->*(it->second))(pt.get_child ("params"), response); - response << "},\"jsonrpc\":\"2.0\"}"; - content = response.str(); + std::ostringstream ss; + ss << "{\"id\":" << id << ",\"result\":{"; + (this->*(it->second))(pt.get_child ("params"), ss); + ss << "},\"jsonrpc\":\"2.0\"}"; + response = ss.str(); } else { LogPrint (eLogWarning, "I2PControl: unknown method ", method); - BuildErrorResponse(content, 32601, "Method not found"); + BuildErrorResponse(response, 32601, "Method not found"); } #endif - SendResponse (socket, buf, content, isHtml); + SendResponse (socket, buf, response, isHTTP); } catch (std::exception& ex) { @@ -282,9 +281,9 @@ namespace client } void I2PControlService::SendResponse (std::shared_ptr socket, - std::shared_ptr buf, std::string& content, bool isHtml) + std::shared_ptr buf, std::string& content, bool isHTTP) { - if (isHtml) { + if (isHTTP) { i2p::http::HTTPRes res; res.code = 200; res.add_header("Content-Type", "application/json"); From 9291f5c9c6ea4c794361a9dfd6f94b3979c72a9e Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1297/6300] * I2PControl.cpp : * unwrap big else {} block * smaller try {} block, only for json parsing & request handling * respond with valid error message on exception --- I2PControl.cpp | 119 +++++++++++++++++++++++-------------------------- 1 file changed, 57 insertions(+), 62 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index d24c0b31..c87db150 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -188,69 +188,64 @@ namespace client if (ecode) { LogPrint (eLogError, "I2PControl: read error: ", ecode.message ()); return; - } else { - try - { - std::stringstream json; - std::string response; - bool isHTTP = false; - if (memcmp (buf->data (), "POST", 4) == 0) { - long int remains = 0; - isHTTP = true; - i2p::http::HTTPReq req; - std::size_t len = req.parse(buf->data(), bytes_transferred); - if (len <= 0) { - LogPrint(eLogError, "I2PControl: incomplete/malformed POST request"); - return; - } - /* append to json chunk of data from 1st request */ - json.write(buf->begin() + len, bytes_transferred - len); - remains = req.length() - len; - /* if request has Content-Length header, fetch rest of data and store to json buffer */ - while (remains > 0) { - len = ((long int) buf->size() < remains) ? buf->size() : remains; - bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), len)); - json.write(buf->begin(), bytes_transferred); - remains -= bytes_transferred; - } - } else { - json.write(buf->begin(), bytes_transferred); - } - LogPrint(eLogDebug, "I2PControl: json from request: ", json.str()); -#if GCC47_BOOST149 - LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7"); - BuildErrorResponse(response, 32603, "JSON requests is not supported with this version of boost"); -#else - boost::property_tree::ptree pt; - boost::property_tree::read_json (json, pt); - - std::string id = pt.get("id"); - std::string method = pt.get("method"); - auto it = m_MethodHandlers.find (method); - if (it != m_MethodHandlers.end ()) - { - std::ostringstream ss; - ss << "{\"id\":" << id << ",\"result\":{"; - (this->*(it->second))(pt.get_child ("params"), ss); - ss << "},\"jsonrpc\":\"2.0\"}"; - response = ss.str(); - } else { - LogPrint (eLogWarning, "I2PControl: unknown method ", method); - BuildErrorResponse(response, 32601, "Method not found"); - } -#endif - SendResponse (socket, buf, response, isHTTP); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "I2PControl: exception when handle request: ", ex.what ()); - /* TODO: also send error, code 32603 */ - } - catch (...) - { - LogPrint (eLogError, "I2PControl: handle request unknown exception"); - } } + /* try to parse received data */ + std::stringstream json; + std::string response; + bool isHTTP = false; + if (memcmp (buf->data (), "POST", 4) == 0) { + long int remains = 0; + isHTTP = true; + i2p::http::HTTPReq req; + std::size_t len = req.parse(buf->data(), bytes_transferred); + if (len <= 0) { + LogPrint(eLogError, "I2PControl: incomplete/malformed POST request"); + return; + } + /* append to json chunk of data from 1st request */ + json.write(buf->begin() + len, bytes_transferred - len); + remains = req.length() - len; + /* if request has Content-Length header, fetch rest of data and store to json buffer */ + while (remains > 0) { + len = ((long int) buf->size() < remains) ? buf->size() : remains; + bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), len)); + json.write(buf->begin(), bytes_transferred); + remains -= bytes_transferred; + } + } else { + json.write(buf->begin(), bytes_transferred); + } + LogPrint(eLogDebug, "I2PControl: json from request: ", json.str()); +#if GCC47_BOOST149 + LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7"); + BuildErrorResponse(response, 32603, "JSON requests is not supported with this version of boost"); +#else + /* now try to parse json itself */ + try { + boost::property_tree::ptree pt; + boost::property_tree::read_json (json, pt); + + std::string id = pt.get("id"); + std::string method = pt.get("method"); + auto it = m_MethodHandlers.find (method); + if (it != m_MethodHandlers.end ()) { + std::ostringstream ss; + ss << "{\"id\":" << id << ",\"result\":{"; + (this->*(it->second))(pt.get_child ("params"), ss); + ss << "},\"jsonrpc\":\"2.0\"}"; + response = ss.str(); + } else { + LogPrint (eLogWarning, "I2PControl: unknown method ", method); + BuildErrorResponse(response, 32601, "Method not found"); + } + } catch (std::exception& ex) { + LogPrint (eLogError, "I2PControl: exception when handle request: ", ex.what ()); + BuildErrorResponse(response, 32603, ex.what()); + } catch (...) { + LogPrint (eLogError, "I2PControl: handle request unknown exception"); + } +#endif + SendResponse (socket, buf, response, isHTTP); } void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, int value) const From 3c9459e489486fa77dc8964496dbf6593e711ba5 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1298/6300] * fix mistype in log message --- Family.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Family.cpp b/Family.cpp index 90c5ccd0..ff09f2f5 100644 --- a/Family.cpp +++ b/Family.cpp @@ -94,7 +94,7 @@ namespace data int numCertificates = 0; if (!i2p::fs::ReadDir(certDir, files)) { - LogPrint(eLogWarning, "Reseed: Can't load reseed certificates from ", certDir); + LogPrint(eLogWarning, "Family: Can't load family certificates from ", certDir); return; } From 827a54435d6425340e6ceb825d67784722783bd0 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1299/6300] * Tunnel.cpp : tune log messages --- Tunnel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index bf81dc5e..ebaf98c8 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -562,7 +562,7 @@ namespace tunnel case eTunnelStatePending: if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT) { - LogPrint (eLogWarning, "Tunnel: pending build request ", it->first, " timeout, deleted"); + LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " timeout, deleted"); // update stats auto config = tunnel->GetTunnelConfig (); if (config) @@ -587,7 +587,7 @@ namespace tunnel it++; break; case eTunnelStateBuildFailed: - LogPrint (eLogWarning, "Tunnel: pending build request ", it->first, " failed, deleted"); + LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " failed, deleted"); it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; break; From 99398bf0daf572312f9f93315d4542f08cf5423c Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1300/6300] * HTTPProxy.{cpp,h} : move & sort headers --- AddressBook.cpp | 1 - ClientContext.h | 1 + HTTPProxy.cpp | 8 +++++++- HTTPProxy.h | 7 ------- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 40e4c383..5a07b3c8 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "Base.h" #include "util.h" diff --git a/ClientContext.h b/ClientContext.h index cfbc039f..f5696902 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -6,6 +6,7 @@ #include #include #include "Destination.h" +#include "I2PService.h" #include "HTTPProxy.h" #include "SOCKS.h" #include "I2PTunnel.h" diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 6104e15e..d7af51e5 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -4,6 +4,13 @@ #include #include #include +#include +#include +#include +#include + +#include "I2PService.h" +#include "Destination.h" #include "HTTPProxy.h" #include "util.h" #include "Identity.h" @@ -351,6 +358,5 @@ namespace proxy { return std::make_shared (this, socket); } - } } diff --git a/HTTPProxy.h b/HTTPProxy.h index b5ed77b9..0356adb5 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -1,13 +1,6 @@ #ifndef HTTP_PROXY_H__ #define HTTP_PROXY_H__ -#include -#include -#include -#include -#include "I2PService.h" -#include "Destination.h" - namespace i2p { namespace proxy From 896bb2187e1e54ba7938f7455dc33a99fdca18fc Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 26 May 2016 00:00:00 +0000 Subject: [PATCH 1301/6300] * HTTPProxy.cpp : HTTPRequestFailed() now responds with error message --- HTTPProxy.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index d7af51e5..cc63ed0f 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -43,7 +43,7 @@ namespace proxy void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); - void HTTPRequestFailed(/*std::string message*/); + void HTTPRequestFailed(const char *message); void RedirectToJumpService(); void ExtractRequest(); bool IsI2PAddress(); @@ -98,10 +98,17 @@ namespace proxy /* All hope is lost beyond this point */ //TODO: handle this apropriately - void HTTPProxyHandler::HTTPRequestFailed(/*HTTPProxyHandler::errTypes error*/) + void HTTPProxyHandler::HTTPRequestFailed(const char *message) { - static std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n"; - boost::asio::async_write(*m_sock, boost::asio::buffer(response,response.size()), + std::size_t size = std::strlen(message); + static std::stringstream ss; + ss << "HTTP/1.0 500 Internal Server Error\r\n" + << "Content-Type: text/plain\r\n"; + ss << "Content-Length: " << std::to_string(size + 2) << "\r\n" + << "\r\n"; /* end of headers */ + ss << message << "\r\n"; + std::string response = ss.str(); + boost::asio::async_write(*m_sock, boost::asio::buffer(response, response.size()), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } @@ -146,7 +153,7 @@ namespace proxy if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) { LogPrint(eLogError, "HTTPProxy: unsupported version: ", m_version); - HTTPRequestFailed(); //TODO: send right stuff + HTTPRequestFailed("unsupported HTTP version"); return false; } return true; @@ -283,13 +290,13 @@ namespace proxy case '\n': EnterState(DONE); break; default: LogPrint(eLogError, "HTTPProxy: rejected invalid request ending with: ", ((int)*http_buff)); - HTTPRequestFailed(); //TODO: add correct code + HTTPRequestFailed("rejected invalid request"); return false; } break; default: LogPrint(eLogError, "HTTPProxy: invalid state: ", m_state); - HTTPRequestFailed(); //TODO: add correct code 500 + HTTPRequestFailed("invalid parser state"); return false; } http_buff++; @@ -345,7 +352,7 @@ namespace proxy else { LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); - HTTPRequestFailed(); // TODO: Send correct error message host unreachable + HTTPRequestFailed("error when creating the stream, check logs"); } } From fc25da37c549c1ecc1bb2466ee3185858ede812d Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 May 2016 14:54:33 -0400 Subject: [PATCH 1302/6300] removed GetPrivateKeys from LocalDestination --- Destination.h | 5 ++++- Identity.h | 8 ++------ RouterContext.h | 4 +++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Destination.h b/Destination.h index 4c8bfc80..b42aba3d 100644 --- a/Destination.h +++ b/Destination.h @@ -147,6 +147,8 @@ namespace client bool Start (); bool Stop (); + const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; + // streaming std::shared_ptr CreateStreamingDestination (int port, bool gzip = true); // additional std::shared_ptr GetStreamingDestination (int port = 0) const; @@ -162,9 +164,10 @@ namespace client i2p::datagram::DatagramDestination * CreateDatagramDestination (); // implements LocalDestination - const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; + std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; + void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; protected: diff --git a/Identity.h b/Identity.h index d8abd6f4..541a7801 100644 --- a/Identity.h +++ b/Identity.h @@ -178,16 +178,12 @@ namespace data public: virtual ~LocalDestination() {}; - virtual const PrivateKeys& GetPrivateKeys () const = 0; virtual const uint8_t * GetEncryptionPrivateKey () const = 0; virtual const uint8_t * GetEncryptionPublicKey () const = 0; + virtual std::shared_ptr GetIdentity () const = 0; + virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0; - std::shared_ptr GetIdentity () const { return GetPrivateKeys ().GetPublic (); }; const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; - void Sign (const uint8_t * buf, int len, uint8_t * signature) const - { - GetPrivateKeys ().Sign (buf, len, signature); - }; }; } } diff --git a/RouterContext.h b/RouterContext.h index 29164f4b..05339847 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -30,6 +30,7 @@ namespace i2p RouterContext (); void Init (); + const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; }; std::shared_ptr GetSharedRouterInfo () const { @@ -73,9 +74,10 @@ namespace i2p void UpdateStats (); // implements LocalDestination - const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; + std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; const uint8_t * GetEncryptionPrivateKey () const { return m_Keys.GetPrivateKey (); }; const uint8_t * GetEncryptionPublicKey () const { return GetIdentity ()->GetStandardIdentity ().publicKey; }; + void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; void SetLeaseSetUpdated () {}; // implements GarlicDestination From 3d6c93cd6bd98e333ec600e39e3849e37063ae83 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 May 2016 15:53:32 -0400 Subject: [PATCH 1303/6300] moved transient encryption keys to LeaseSetDestination --- Destination.cpp | 56 ++++++++++++++++++++++++------------------------- Destination.h | 14 ++++++++----- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 46ff688f..40c8768e 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -130,6 +130,10 @@ namespace client if (!m_IsRunning) { m_IsRunning = true; + if (m_IsPublic) + PersistTemporaryKeys (); + else + i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); m_Pool->SetLocalDestination (shared_from_this ()); m_Pool->SetActive (true); m_Thread = new std::thread (std::bind (&LeaseSetDestination::Run, shared_from_this ())); @@ -640,14 +644,34 @@ namespace client } } + void LeaseSetDestination::PersistTemporaryKeys () + { + std::string ident = GetIdentHash().ToBase32(); + std::string path = i2p::fs::DataDirPath("destinations", (ident + ".dat")); + std::ifstream f(path, std::ifstream::binary); + + if (f) { + f.read ((char *)m_EncryptionPublicKey, 256); + f.read ((char *)m_EncryptionPrivateKey, 256); + return; + } + + LogPrint (eLogInfo, "Destination: Creating new temporary keys for address ", ident, ".b32.i2p"); + i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); + + std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out); + if (f1) { + f1.write ((char *)m_EncryptionPublicKey, 256); + f1.write ((char *)m_EncryptionPrivateKey, 256); + return; + } + LogPrint(eLogError, "Destinations: Can't save keys to ", path); + } + ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): LeaseSetDestination (isPublic, params), m_Keys (keys), m_DatagramDestination (nullptr) { - if (isPublic) - PersistTemporaryKeys (); - else - i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); if (isPublic) LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created"); } @@ -802,30 +826,6 @@ namespace client m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis ()); return m_DatagramDestination; } - - void ClientDestination::PersistTemporaryKeys () - { - std::string ident = GetIdentHash().ToBase32(); - std::string path = i2p::fs::DataDirPath("destinations", (ident + ".dat")); - std::ifstream f(path, std::ifstream::binary); - - if (f) { - f.read ((char *)m_EncryptionPublicKey, 256); - f.read ((char *)m_EncryptionPrivateKey, 256); - return; - } - - LogPrint (eLogInfo, "Destination: Creating new temporary keys for address ", ident, ".b32.i2p"); - i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); - - std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out); - if (f1) { - f1.write ((char *)m_EncryptionPublicKey, 256); - f1.write ((char *)m_EncryptionPrivateKey, 256); - return; - } - LogPrint(eLogError, "Destinations: Can't save keys to ", path); - } std::vector > ClientDestination::GetAllStreams () const { diff --git a/Destination.h b/Destination.h index b42aba3d..e2531699 100644 --- a/Destination.h +++ b/Destination.h @@ -80,7 +80,11 @@ namespace client std::shared_ptr FindLeaseSet (const i2p::data::IdentHash& ident); bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); void CancelDestinationRequest (const i2p::data::IdentHash& dest); - + + // implements LocalDestination + const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; + const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; + // implements GarlicDestination std::shared_ptr GetLeaseSet (); std::shared_ptr GetTunnelPool () const { return m_Pool; } @@ -113,9 +117,13 @@ namespace client void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleCleanupTimer (const boost::system::error_code& ecode); void CleanupRemoteLeaseSets (); + + void PersistTemporaryKeys (); private: + uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; + volatile bool m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; @@ -165,8 +173,6 @@ namespace client // implements LocalDestination std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; - const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; - const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; protected: @@ -176,14 +182,12 @@ namespace client private: - void PersistTemporaryKeys (); std::shared_ptr GetSharedFromThis () { return std::static_pointer_cast(shared_from_this ()); } private: i2p::data::PrivateKeys m_Keys; - uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; std::shared_ptr m_StreamingDestination; // default std::map > m_StreamingDestinationsByPorts; From 95f100f378ff1da4820f888b9e7ae1132a7eb95a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 May 2016 16:21:27 -0400 Subject: [PATCH 1304/6300] HTTP error message cleanup --- HTTPProxy.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index cc63ed0f..70c1dba3 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -101,14 +101,15 @@ namespace proxy void HTTPProxyHandler::HTTPRequestFailed(const char *message) { std::size_t size = std::strlen(message); - static std::stringstream ss; + std::stringstream ss; ss << "HTTP/1.0 500 Internal Server Error\r\n" << "Content-Type: text/plain\r\n"; ss << "Content-Length: " << std::to_string(size + 2) << "\r\n" << "\r\n"; /* end of headers */ ss << message << "\r\n"; - std::string response = ss.str(); - boost::asio::async_write(*m_sock, boost::asio::buffer(response, response.size()), + static std::string response; + response = ss.str(); + boost::asio::async_write(*m_sock, boost::asio::buffer(response), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } @@ -119,7 +120,9 @@ namespace proxy uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n\r\n"; - boost::asio::async_write(*m_sock, boost::asio::buffer(response.str (),response.str ().length ()), + static std::string s; + s = response.str (); + boost::asio::async_write(*m_sock, boost::asio::buffer(s), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } From 5ad10955be548691b667ad2b043c6a837f1a1556 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 May 2016 16:27:53 -0400 Subject: [PATCH 1305/6300] use m_Response field for HTTP proxy response --- HTTPProxy.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 70c1dba3..f681f365 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -56,6 +56,7 @@ namespace proxy uint8_t m_http_buff[http_buffer_size]; std::shared_ptr m_sock; std::string m_request; //Data left to be sent + std::string m_Response; std::string m_url; //URL std::string m_method; //Method std::string m_version; //HTTP version @@ -107,9 +108,8 @@ namespace proxy ss << "Content-Length: " << std::to_string(size + 2) << "\r\n" << "\r\n"; /* end of headers */ ss << message << "\r\n"; - static std::string response; - response = ss.str(); - boost::asio::async_write(*m_sock, boost::asio::buffer(response), + m_Response = ss.str(); + boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } @@ -120,9 +120,8 @@ namespace proxy uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n\r\n"; - static std::string s; - s = response.str (); - boost::asio::async_write(*m_sock, boost::asio::buffer(s), + m_Response = response.str (); + boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } From c994c11d8c82d3ee8809d28d48167215c0c3e8b6 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 May 2016 00:00:00 +0000 Subject: [PATCH 1306/6300] * HTTPProxy.{cpp,h} : rename classes, drop typedef --- HTTPProxy.cpp | 65 ++++++++++++++++++++++++--------------------------- HTTPProxy.h | 18 ++++++-------- 2 files changed, 38 insertions(+), 45 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index f681f365..6915274c 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -21,12 +21,10 @@ #include "I2PTunnel.h" #include "Config.h" -namespace i2p -{ -namespace proxy -{ +namespace i2p { +namespace proxy { static const size_t http_buffer_size = 8192; - class HTTPProxyHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this + class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: enum state @@ -67,26 +65,26 @@ namespace proxy public: - HTTPProxyHandler(HTTPProxyServer * parent, std::shared_ptr sock) : + HTTPReqHandler(HTTPProxy * parent, std::shared_ptr sock) : I2PServiceHandler(parent), m_sock(sock) { EnterState(GET_METHOD); } - ~HTTPProxyHandler() { Terminate(); } + ~HTTPReqHandler() { Terminate(); } void Handle () { AsyncSockRead(); } }; - void HTTPProxyHandler::AsyncSockRead() + void HTTPReqHandler::AsyncSockRead() { LogPrint(eLogDebug, "HTTPProxy: async sock read"); if(m_sock) { m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), - std::bind(&HTTPProxyHandler::HandleSockRecv, shared_from_this(), + std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { LogPrint(eLogError, "HTTPProxy: no socket for read"); } } - void HTTPProxyHandler::Terminate() { + void HTTPReqHandler::Terminate() { if (Kill()) return; if (m_sock) { @@ -99,7 +97,7 @@ namespace proxy /* All hope is lost beyond this point */ //TODO: handle this apropriately - void HTTPProxyHandler::HTTPRequestFailed(const char *message) + void HTTPReqHandler::HTTPRequestFailed(const char *message) { std::size_t size = std::strlen(message); std::stringstream ss; @@ -108,29 +106,28 @@ namespace proxy ss << "Content-Length: " << std::to_string(size + 2) << "\r\n" << "\r\n"; /* end of headers */ ss << message << "\r\n"; - m_Response = ss.str(); - boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), - std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + std::string response = ss.str(); + boost::asio::async_write(*m_sock, boost::asio::buffer(response, response.size()), + std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPProxyHandler::RedirectToJumpService(/*HTTPProxyHandler::errTypes error*/) + void HTTPReqHandler::RedirectToJumpService(/*HTTPReqHandler::errTypes error*/) { std::stringstream response; std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n\r\n"; - m_Response = response.str (); - boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), - std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + boost::asio::async_write(*m_sock, boost::asio::buffer(response.str (),response.str ().length ()), + std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPProxyHandler::EnterState(HTTPProxyHandler::state nstate) + void HTTPReqHandler::EnterState(HTTPReqHandler::state nstate) { m_state = nstate; } - void HTTPProxyHandler::ExtractRequest() + void HTTPReqHandler::ExtractRequest() { LogPrint(eLogDebug, "HTTPProxy: request: ", m_method, " ", m_url); std::string server=""; @@ -150,7 +147,7 @@ namespace proxy m_path = path; } - bool HTTPProxyHandler::ValidateHTTPRequest() + bool HTTPReqHandler::ValidateHTTPRequest() { if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) { @@ -161,7 +158,7 @@ namespace proxy return true; } - void HTTPProxyHandler::HandleJumpServices() + void HTTPReqHandler::HandleJumpServices() { static const char * helpermark1 = "?i2paddresshelper="; static const char * helpermark2 = "&i2paddresshelper="; @@ -193,7 +190,7 @@ namespace proxy m_path.erase(addressHelperPos); } - bool HTTPProxyHandler::IsI2PAddress() + bool HTTPReqHandler::IsI2PAddress() { auto pos = m_address.rfind (".i2p"); if (pos != std::string::npos && (pos+4) == m_address.length ()) @@ -203,7 +200,7 @@ namespace proxy return false; } - bool HTTPProxyHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) + bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { ExtractRequest(); //TODO: parse earlier if (!ValidateHTTPRequest()) return false; @@ -258,7 +255,7 @@ namespace proxy return true; } - bool HTTPProxyHandler::HandleData(uint8_t *http_buff, std::size_t len) + bool HTTPReqHandler::HandleData(uint8_t *http_buff, std::size_t len) { while (len > 0) { @@ -309,7 +306,7 @@ namespace proxy return true; } - void HTTPProxyHandler::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"); if(ecode) @@ -324,7 +321,7 @@ namespace proxy if (m_state == DONE) { LogPrint(eLogDebug, "HTTPProxy: requested: ", m_url); - GetOwner()->CreateStream (std::bind (&HTTPProxyHandler::HandleStreamRequestComplete, + GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_address, m_port); } else @@ -333,14 +330,14 @@ namespace proxy } - void HTTPProxyHandler::SentHTTPFailed(const boost::system::error_code & ecode) + void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) { if (ecode) LogPrint (eLogError, "HTTPProxy: Closing socket after sending failure because: ", ecode.message ()); Terminate(); } - void HTTPProxyHandler::HandleStreamRequestComplete (std::shared_ptr stream) + void HTTPReqHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (stream) { @@ -358,14 +355,14 @@ namespace proxy } } - HTTPProxyServer::HTTPProxyServer(const std::string& address, int port, std::shared_ptr localDestination): + HTTPProxy::HTTPProxy(const std::string& address, int port, std::shared_ptr localDestination): TCPIPAcceptor(address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()) { } - std::shared_ptr HTTPProxyServer::CreateHandler(std::shared_ptr socket) + std::shared_ptr HTTPProxy::CreateHandler(std::shared_ptr socket) { - return std::make_shared (this, socket); + return std::make_shared (this, socket); } -} -} +} // http +} // i2p diff --git a/HTTPProxy.h b/HTTPProxy.h index 0356adb5..29b997eb 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -1,25 +1,21 @@ #ifndef HTTP_PROXY_H__ #define HTTP_PROXY_H__ -namespace i2p -{ -namespace proxy -{ - class HTTPProxyServer: public i2p::client::TCPIPAcceptor +namespace i2p { +namespace proxy { + class HTTPProxy: public i2p::client::TCPIPAcceptor { public: - HTTPProxyServer(const std::string& address, int port, std::shared_ptr localDestination = nullptr); - ~HTTPProxyServer() {}; + HTTPProxy(const std::string& address, int port, std::shared_ptr localDestination = nullptr); + ~HTTPProxy() {}; protected: // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return "HTTP Proxy"; } }; - - typedef HTTPProxyServer HTTPProxy; -} -} +} // http +} // i2p #endif From 61868d97c4d3f3ab15aa1ff2b8f6a5f413cc6720 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 May 2016 00:00:00 +0000 Subject: [PATCH 1307/6300] * HTTPProxy.cpp : migrate HTTPRequestFailed(), RedirectToJumpService() to new http classes --- HTTPProxy.cpp | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 6915274c..06753b78 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -20,6 +20,7 @@ #include "I2PEndian.h" #include "I2PTunnel.h" #include "Config.h" +#include "HTTP.h" namespace i2p { namespace proxy { @@ -42,7 +43,7 @@ namespace proxy { void Terminate(); void AsyncSockRead(); void HTTPRequestFailed(const char *message); - void RedirectToJumpService(); + void RedirectToJumpService(std::string & host); void ExtractRequest(); bool IsI2PAddress(); bool ValidateHTTPRequest(); @@ -99,26 +100,33 @@ namespace proxy { //TODO: handle this apropriately void HTTPReqHandler::HTTPRequestFailed(const char *message) { - std::size_t size = std::strlen(message); - std::stringstream ss; - ss << "HTTP/1.0 500 Internal Server Error\r\n" - << "Content-Type: text/plain\r\n"; - ss << "Content-Length: " << std::to_string(size + 2) << "\r\n" - << "\r\n"; /* end of headers */ - ss << message << "\r\n"; - std::string response = ss.str(); + i2p::http::HTTPRes res; + res.code = 500; + res.add_header("Content-Type", "text/plain"); + res.add_header("Connection", "close"); + res.body = message; + res.body += "\r\n"; + std::string response = res.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(response, response.size()), std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPReqHandler::RedirectToJumpService(/*HTTPReqHandler::errTypes error*/) + void HTTPReqHandler::RedirectToJumpService(std::string & host) { - std::stringstream response; - std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); - uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); + i2p::http::HTTPRes res; + i2p::http::URL url; - response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n\r\n"; - boost::asio::async_write(*m_sock, boost::asio::buffer(response.str (),response.str ().length ()), + 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 */ + res.add_header("Location", url.to_string().c_str()); + + std::string response = res.to_string(); + boost::asio::async_write(*m_sock, boost::asio::buffer(response, response.length()), std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } @@ -210,7 +218,7 @@ namespace proxy { if (IsI2PAddress ()) { if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ - RedirectToJumpService(); + RedirectToJumpService(m_address); return false; } } From 0de1e2c6fc6b8592b9db4a3c27751ec28e5e242b Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 May 2016 00:00:00 +0000 Subject: [PATCH 1308/6300] * HTTPProxy.cpp : extract IsI2PAddress() from class and generalize --- HTTPProxy.cpp | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 06753b78..cbbd66c4 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -24,6 +24,15 @@ namespace i2p { namespace proxy { + bool str_rmatch(std::string & str, const char *suffix) { + auto pos = str.rfind (suffix); + if (pos == std::string::npos) + return false; /* not found */ + if (str.length() == (pos + std::strlen(suffix))) + return true; /* match */ + return false; + } + static const size_t http_buffer_size = 8192; class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { @@ -45,7 +54,6 @@ namespace proxy { void HTTPRequestFailed(const char *message); void RedirectToJumpService(std::string & host); void ExtractRequest(); - bool IsI2PAddress(); bool ValidateHTTPRequest(); void HandleJumpServices(); bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); @@ -198,16 +206,6 @@ namespace proxy { m_path.erase(addressHelperPos); } - bool HTTPReqHandler::IsI2PAddress() - { - auto pos = m_address.rfind (".i2p"); - if (pos != std::string::npos && (pos+4) == m_address.length ()) - { - return true; - } - return false; - } - bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { ExtractRequest(); //TODO: parse earlier @@ -215,14 +213,13 @@ namespace proxy { HandleJumpServices(); i2p::data::IdentHash identHash; - if (IsI2PAddress ()) + if (str_rmatch(m_address, ".i2p")) { if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ RedirectToJumpService(m_address); return false; } } - m_request = m_method; m_request.push_back(' '); @@ -335,7 +332,6 @@ namespace proxy { else AsyncSockRead(); } - } void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) From 2bf32fb3fadd82ccefaf4bdb7e664d42f51e797d Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 May 2016 00:00:00 +0000 Subject: [PATCH 1309/6300] * HTTPProxy.cpp : kill ExtractRequest(), drop boost::regex --- HTTPProxy.cpp | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index cbbd66c4..2001f620 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -1,7 +1,5 @@ #include #include -#include -#include #include #include #include @@ -53,7 +51,6 @@ namespace proxy { void AsyncSockRead(); void HTTPRequestFailed(const char *message); void RedirectToJumpService(std::string & host); - void ExtractRequest(); bool ValidateHTTPRequest(); void HandleJumpServices(); bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); @@ -143,26 +140,6 @@ namespace proxy { m_state = nstate; } - void HTTPReqHandler::ExtractRequest() - { - LogPrint(eLogDebug, "HTTPProxy: request: ", m_method, " ", m_url); - std::string server=""; - std::string port="80"; - boost::regex rHTTP("http://(.*?)(:(\\d+))?(/.*)"); - boost::smatch m; - std::string path; - if(boost::regex_search(m_url, m, rHTTP, boost::match_extra)) - { - server=m[1].str(); - if (m[2].str() != "") port=m[3].str(); - path=m[4].str(); - } - LogPrint(eLogDebug, "HTTPProxy: server: ", server, ", port: ", port, ", path: ", path); - m_address = server; - m_port = boost::lexical_cast(port); - m_path = path; - } - bool HTTPReqHandler::ValidateHTTPRequest() { if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) @@ -208,7 +185,11 @@ namespace proxy { bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { - ExtractRequest(); //TODO: parse earlier + i2p::http::URL url; + url.parse(m_url); + m_address = url.host; /* < compatibility */ + m_port = url.port; /* < compatibility */ + m_path = url.path; /* < compatibility */ if (!ValidateHTTPRequest()) return false; HandleJumpServices(); From 5c9a69e0e81fdb759d91bb42c0e2ab130f631099 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 May 2016 00:00:00 +0000 Subject: [PATCH 1310/6300] * drop boost_regex from build deps --- .travis.yml | 1 - Makefile.bsd | 2 +- Makefile.homebrew | 2 +- Makefile.linux | 3 +-- Makefile.mingw | 1 - Makefile.osx | 2 +- Reseed.cpp | 1 - appveyor.yml | 2 +- build/CMakeLists.txt | 2 +- build/Dockerfile | 2 +- debian/control | 1 - docs/build_notes_cross.md | 2 +- docs/build_notes_unix.md | 1 - docs/build_notes_windows.md | 4 ++-- stdafx.h | 1 - 15 files changed, 10 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index c791187d..d83cdbc0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,6 @@ addons: - libboost-date-time-dev - libboost-filesystem-dev - libboost-program-options-dev - - libboost-regex-dev - libboost-system-dev - libboost-thread-dev - libminiupnpc-dev diff --git a/Makefile.bsd b/Makefile.bsd index 255233f1..d6871f07 100644 --- a/Makefile.bsd +++ b/Makefile.bsd @@ -9,4 +9,4 @@ CXXFLAGS = -O2 NEEDED_CXXFLAGS = -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 INCFLAGS = -I/usr/include/ -I/usr/local/include/ LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread +LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread diff --git a/Makefile.homebrew b/Makefile.homebrew index 163b7950..6ce513fe 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -6,7 +6,7 @@ CXX = clang++ CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include LDFLAGS = -L${SSLROOT}/lib -L${BOOSTROOT}/lib -LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread +LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread ifeq ($(USE_UPNP),1) LDFLAGS += -ldl diff --git a/Makefile.linux b/Makefile.linux index 791382c6..70307267 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -32,7 +32,6 @@ ifeq ($(USE_STATIC),yes) LDLIBS = $(LIBDIR)/libboost_system.a LDLIBS += $(LIBDIR)/libboost_date_time.a LDLIBS += $(LIBDIR)/libboost_filesystem.a - LDLIBS += $(LIBDIR)/libboost_regex.a LDLIBS += $(LIBDIR)/libboost_program_options.a LDLIBS += $(LIBDIR)/libcrypto.a LDLIBS += $(LIBDIR)/libssl.a @@ -40,7 +39,7 @@ ifeq ($(USE_STATIC),yes) LDLIBS += -lpthread -static-libstdc++ -static-libgcc USE_AESNI := no else - LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread + LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread endif # UPNP Support (miniupnpc 1.5 or 1.6) diff --git a/Makefile.mingw b/Makefile.mingw index 5fe3c479..0390d66a 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -13,7 +13,6 @@ LDLIBS = \ -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) \ -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) \ -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) \ - -Wl,-Bstatic -lboost_regex$(BOOST_SUFFIX) \ -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) \ -Wl,-Bstatic -lssl \ -Wl,-Bstatic -lcrypto \ diff --git a/Makefile.osx b/Makefile.osx index 71f95a7f..ef236c9a 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -3,7 +3,7 @@ CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX #CXXFLAGS = -g -O2 -Wall -std=c++11 INCFLAGS = -I/usr/local/include -I/usr/local/ssl/include LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/usr/local/ssl/lib -LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread +LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread ifeq ($(USE_UPNP),1) LDFLAGS += -ldl diff --git a/Reseed.cpp b/Reseed.cpp index ddefc460..6f27891d 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include diff --git a/appveyor.yml b/appveyor.yml index 6600714d..6018bea0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -125,7 +125,7 @@ install: - cd %BOOST_ROOT% - if defined msvc if not exist "stage%bitness%\lib\%boostlib%boost_system-vc%msvc%0-mt%boostdbg%*" ( bootstrap > c:\projects\instdir\build_boost.log - && b2 toolset=msvc-%msvc%.0 %boost_variant% link=%type% runtime-link=%type% address-model=%bitness% --build-type=minimal --with-filesystem --with-program_options --with-regex --with-date_time --stagedir=stage%bitness% >> c:\projects\instdir\build_boost.log + && b2 toolset=msvc-%msvc%.0 %boost_variant% link=%type% runtime-link=%type% address-model=%bitness% --build-type=minimal --with-filesystem --with-program_options --with-date_time --stagedir=stage%bitness% >> c:\projects\instdir\build_boost.log || type c:\projects\instdir\build_boost.log ) - if defined msvc if not exist C:\stage\OpenSSL-Win%bitness%-vc%msvc%-%type%\ ( diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 7944e2ec..4a1bfe2b 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -242,7 +242,7 @@ endif() target_link_libraries(i2pdclient libi2pd) -find_package ( Boost COMPONENTS system filesystem regex program_options date_time REQUIRED ) +find_package ( Boost COMPONENTS system filesystem program_options date_time REQUIRED ) if(NOT DEFINED Boost_INCLUDE_DIRS) message(SEND_ERROR "Boost is not found, or your boost version was bellow 1.46. Please download Boost!") endif() diff --git a/build/Dockerfile b/build/Dockerfile index f570bd79..751fe956 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu RUN apt-get update && apt-get install -y libboost-dev libboost-filesystem-dev \ - libboost-program-options-dev libboost-regex-dev libboost-date-time-dev \ + libboost-program-options-dev libboost-date-time-dev \ libssl-dev git build-essential RUN git clone https://github.com/PurpleI2P/i2pd.git diff --git a/debian/control b/debian/control index ceca7a5f..78906ba4 100644 --- a/debian/control +++ b/debian/control @@ -4,7 +4,6 @@ Priority: extra Maintainer: hagen Build-Depends: debhelper (>= 9.0.0), dpkg-dev (>= 1.16.1~), gcc (>= 4.7) | clang (>= 3.3), - libboost-regex-dev, libboost-system-dev (>= 1.46), libboost-date-time-dev, libboost-filesystem-dev, diff --git a/docs/build_notes_cross.md b/docs/build_notes_cross.md index d819ba34..78d60e6a 100644 --- a/docs/build_notes_cross.md +++ b/docs/build_notes_cross.md @@ -22,7 +22,7 @@ Proceed with building Boost normal way, but let's define dedicated staging direc ```sh ./bootstrap.sh ./b2 toolset=gcc-mingw target-os=windows variant=release link=static runtime-link=static address-model=64 \ - --build-type=minimal --with-filesystem --with-program_options --with-regex --with-date_time \ + --build-type=minimal --with-filesystem --with-program_options --with-date_time \ --stagedir=stage-mingw-64 cd .. ``` diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index 05605343..cdde1ee7 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -46,7 +46,6 @@ sudo apt-get install \ libboost-date-time-dev \ libboost-filesystem-dev \ libboost-program-options-dev \ - libboost-regex-dev \ libboost-system-dev \ libboost-thread-dev \ libssl-dev diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index 81e0dfc2..921a6110 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -110,11 +110,11 @@ prompt to build Boost) and run the following: cd C:\dev\boost bootstrap - b2 toolset=msvc-12.0 --build-type=complete --with-filesystem --with-program_options --with-regex --with-date_time + b2 toolset=msvc-12.0 --build-type=complete --with-filesystem --with-program_options --with-date_time If you are on 64-bit Windows and you want to build 64-bit version as well - b2 toolset=msvc-12.0 --build-type=complete --stagedir=stage64 address-model=64 --with-filesystem --with-program_options --with-regex --with-date_time + b2 toolset=msvc-12.0 --build-type=complete --stagedir=stage64 address-model=64 --with-filesystem --with-program_options --with-date_time After Boost is compiled, set the environment variable `BOOST_ROOT` to the directory Boost was unpacked to, e.g., C:\dev\boost. diff --git a/stdafx.h b/stdafx.h index ed13bf8b..42490354 100644 --- a/stdafx.h +++ b/stdafx.h @@ -33,7 +33,6 @@ #include #include -#include #include #include #include From a5f49550b3c6a010b7cc933c5cab628f6fced3f9 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 May 2016 00:00:00 +0000 Subject: [PATCH 1311/6300] * HTTPProxy.cpp : unwrap AsyncSockRead() --- HTTPProxy.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 2001f620..42e4fc72 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -81,13 +81,13 @@ namespace proxy { void HTTPReqHandler::AsyncSockRead() { LogPrint(eLogDebug, "HTTPProxy: async sock read"); - if(m_sock) { - m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), - std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); - } else { + if (!m_sock) { LogPrint(eLogError, "HTTPProxy: no socket for read"); + return; } + m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), + std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); } void HTTPReqHandler::Terminate() { From dba7a2ee4f32ac6d2b0c4ed01a73150c0f42a132 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 May 2016 00:00:00 +0000 Subject: [PATCH 1312/6300] * HTTPProxy.cpp : HandleJumpServices() -> ExtractAddressHelper() --- HTTPProxy.cpp | 57 +++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 42e4fc72..884d2521 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -52,7 +52,7 @@ namespace proxy { void HTTPRequestFailed(const char *message); void RedirectToJumpService(std::string & host); bool ValidateHTTPRequest(); - void HandleJumpServices(); + bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64); bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); @@ -123,12 +123,14 @@ namespace proxy { i2p::config::GetOption("http.address", url.host); i2p::config::GetOption("http.port", url.port); + url.schema = "http"; url.path = "/"; url.query = "page=jumpservices&address="; url.query += host; res.code = 302; /* redirect */ res.add_header("Location", url.to_string().c_str()); + res.add_header("Connection", "close"); std::string response = res.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(response, response.length()), @@ -151,47 +153,40 @@ namespace proxy { return true; } - void HTTPReqHandler::HandleJumpServices() + bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64) { - static const char * helpermark1 = "?i2paddresshelper="; - static const char * helpermark2 = "&i2paddresshelper="; - size_t addressHelperPos1 = m_path.rfind (helpermark1); - size_t addressHelperPos2 = m_path.rfind (helpermark2); - size_t addressHelperPos; - if (addressHelperPos1 == std::string::npos) - { - if (addressHelperPos2 == std::string::npos) - return; //Not a jump service - else - addressHelperPos = addressHelperPos2; - } - else - { - if (addressHelperPos2 == std::string::npos) - addressHelperPos = addressHelperPos1; - else if ( addressHelperPos1 > addressHelperPos2 ) - addressHelperPos = addressHelperPos1; - else - addressHelperPos = addressHelperPos2; - } - 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); + const char *param = "i2paddresshelper="; + std::size_t pos = url.query.find(param); + std::size_t len = std::strlen(param); + std::map params; + + if (pos == std::string::npos) + return false; /* not found */ + if (!url.parse_query(params)) + return false; + + std::string value = params["i2paddresshelper"]; + len += value.length(); + b64 = i2p::http::UrlDecode(value); + url.query.replace(pos, len, ""); + return true; } bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { + std::string b64; i2p::http::URL url; url.parse(m_url); m_address = url.host; /* < compatibility */ m_port = url.port; /* < compatibility */ m_path = url.path; /* < compatibility */ if (!ValidateHTTPRequest()) return false; - HandleJumpServices(); + + /* TODO: notify user */ + if (ExtractAddressHelper(url, b64)) { + i2p::client::context.GetAddressBook ().InsertAddress (url.host, b64); + LogPrint (eLogInfo, "HTTPProxy: added b64 from addresshelper for ", url.host, " to address book"); + } i2p::data::IdentHash identHash; if (str_rmatch(m_address, ".i2p")) From 4098a5c08e636d786f68b22e94e8e7996f095380 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 May 2016 00:00:00 +0000 Subject: [PATCH 1313/6300] * HTTPProxy.cpp : rename variable --- HTTPProxy.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 884d2521..bd03a6a6 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -31,7 +31,6 @@ namespace proxy { return false; } - static const size_t http_buffer_size = 8192; class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: @@ -57,7 +56,7 @@ namespace proxy { void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); - uint8_t m_http_buff[http_buffer_size]; + uint8_t m_downstream_recv_buf[8192]; std::shared_ptr m_sock; std::string m_request; //Data left to be sent std::string m_Response; @@ -85,7 +84,7 @@ namespace proxy { LogPrint(eLogError, "HTTPProxy: no socket for read"); return; } - m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), + m_sock->async_receive(boost::asio::buffer(m_downstream_recv_buf, sizeof(m_downstream_recv_buf)), std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } @@ -297,7 +296,7 @@ namespace proxy { return; } - if (HandleData(m_http_buff, len)) + if (HandleData(m_downstream_recv_buf, len)) { if (m_state == DONE) { From a9f3235fd3f1163eeb97329c1749887aa3bd9e22 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 May 2016 00:00:00 +0000 Subject: [PATCH 1314/6300] * HTTPProxy.cpp : unwrap HandleStreamRequestComplete() --- HTTPProxy.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index bd03a6a6..7150e14f 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -318,20 +318,18 @@ namespace proxy { void HTTPReqHandler::HandleStreamRequestComplete (std::shared_ptr stream) { - if (stream) - { - if (Kill()) return; - LogPrint (eLogInfo, "HTTPProxy: New I2PTunnel connection"); - auto connection = std::make_shared(GetOwner(), m_sock, stream); - GetOwner()->AddHandler (connection); - connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); - Done(shared_from_this()); - } - else - { + if (!stream) { LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); HTTPRequestFailed("error when creating the stream, check logs"); + return; } + if (Kill()) + return; + LogPrint (eLogDebug, "HTTPProxy: New I2PTunnel connection"); + auto connection = std::make_shared(GetOwner(), m_sock, stream); + GetOwner()->AddHandler (connection); + connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); + Done (shared_from_this()); } HTTPProxy::HTTPProxy(const std::string& address, int port, std::shared_ptr localDestination): From 347157b9992a07290bb45e815398a27106e43333 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 May 2016 00:00:00 +0000 Subject: [PATCH 1315/6300] * HTTPProxy.cpp : direct use of parsed url parts in CreateHTTPRequest() --- HTTPProxy.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 7150e14f..43ee8631 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -188,10 +188,10 @@ namespace proxy { } i2p::data::IdentHash identHash; - if (str_rmatch(m_address, ".i2p")) + if (str_rmatch(url.host, ".i2p")) { - if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ - RedirectToJumpService(m_address); + if (!i2p::client::context.GetAddressBook ().GetIdentHash (url.host, identHash)){ + RedirectToJumpService(url.host); return false; } } From d0ffaab339b6aa21d5f4ec4be2b71c7e5e051ecb Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 27 May 2016 00:00:00 +0000 Subject: [PATCH 1316/6300] * HTTPProxy: * use new http classes instead homemade parser * proper error handling for "address not found", "addresshelper" and "not .i2p domain" cases * use std::vector instead uint8_t[] for buffers * general code cleanup --- HTTPProxy.cpp | 221 +++++++++++++++----------------------------------- 1 file changed, 66 insertions(+), 155 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 43ee8631..cc534470 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -34,45 +34,26 @@ namespace proxy { class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: - enum state - { - GET_METHOD, - GET_HOSTNAME, - GET_HTTPV, - GET_HTTPVNL, //TODO: fallback to finding HOst: header if needed - DONE - }; - void EnterState(state nstate); - bool HandleData(uint8_t *http_buff, std::size_t len); + bool HandleRequest(std::size_t len); void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); void HTTPRequestFailed(const char *message); void RedirectToJumpService(std::string & host); - bool ValidateHTTPRequest(); bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64); - bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); + void SanitizeHTTPRequest(i2p::http::HTTPReq & req); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); - uint8_t m_downstream_recv_buf[8192]; std::shared_ptr m_sock; - std::string m_request; //Data left to be sent - std::string m_Response; - 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 + std::vector m_recv_buf; /* as "downstream recieve buffer", from client to me */ + std::vector m_send_buf; /* as "upstream send buffer", from me to remote host */ public: HTTPReqHandler(HTTPProxy * parent, std::shared_ptr sock) : - I2PServiceHandler(parent), m_sock(sock) - { EnterState(GET_METHOD); } + I2PServiceHandler(parent), m_sock(sock), m_recv_buf(8192), m_send_buf(0) {}; ~HTTPReqHandler() { Terminate(); } void Handle () { AsyncSockRead(); } }; @@ -84,7 +65,7 @@ namespace proxy { LogPrint(eLogError, "HTTPProxy: no socket for read"); return; } - m_sock->async_receive(boost::asio::buffer(m_downstream_recv_buf, sizeof(m_downstream_recv_buf)), + m_sock->async_receive(boost::asio::buffer(m_recv_buf), std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } @@ -100,8 +81,6 @@ namespace proxy { Done(shared_from_this()); } - /* All hope is lost beyond this point */ - //TODO: handle this apropriately void HTTPReqHandler::HTTPRequestFailed(const char *message) { i2p::http::HTTPRes res; @@ -136,22 +115,6 @@ namespace proxy { std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPReqHandler::EnterState(HTTPReqHandler::state nstate) - { - m_state = nstate; - } - - bool HTTPReqHandler::ValidateHTTPRequest() - { - if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) - { - LogPrint(eLogError, "HTTPProxy: unsupported version: ", m_version); - HTTPRequestFailed("unsupported HTTP version"); - return false; - } - return true; - } - bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64) { const char *param = "i2paddresshelper="; @@ -171,118 +134,74 @@ namespace proxy { return true; } - bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) + void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req) { - std::string b64; - i2p::http::URL url; - url.parse(m_url); - m_address = url.host; /* < compatibility */ - m_port = url.port; /* < compatibility */ - m_path = url.path; /* < compatibility */ - if (!ValidateHTTPRequest()) return false; + req.del_header("Referer"); + req.add_header("Connection", "close", true); + req.add_header("User-Agent", "MYOB/6.66 (AN/ON)", true); + } + + /** + * @param len length of data in m_recv_buf + * @return true on processed request or false if more data needed + */ + bool HTTPReqHandler::HandleRequest(std::size_t len) + { + i2p::http::HTTPReq req; + i2p::http::URL url; + std::string b64; + + int req_len = 0; + + req_len = req.parse((const char *) m_recv_buf.data(), len); + if (req_len == 0) + return false; /* need more data */ + if (req_len < 0) { + LogPrint(eLogError, "HTTPProxy: unable to parse request"); + HTTPRequestFailed("invalid request"); + return true; /* parse error */ + } + + /* parsing success, now let's look inside request */ + LogPrint(eLogDebug, "HTTPProxy: requested: ", req.uri); + url.parse(req.uri); - /* TODO: notify user */ if (ExtractAddressHelper(url, b64)) { i2p::client::context.GetAddressBook ().InsertAddress (url.host, b64); - LogPrint (eLogInfo, "HTTPProxy: added b64 from addresshelper for ", url.host, " to address book"); + std::string message = "added b64 from addresshelper for " + url.host + " to address book"; + LogPrint (eLogInfo, "HTTPProxy: ", message); + message += ", please reload page"; + HTTPRequestFailed(message.c_str()); + return true; /* request processed */ } i2p::data::IdentHash identHash; - if (str_rmatch(url.host, ".i2p")) - { - if (!i2p::client::context.GetAddressBook ().GetIdentHash (url.host, identHash)){ + if (str_rmatch(url.host, ".i2p")) { + if (!i2p::client::context.GetAddressBook ().GetIdentHash (url.host, identHash)) { RedirectToJumpService(url.host); - return false; + return true; /* request processed */ } + /* TODO: outproxy handler here */ + } else { + std::string message = "Host " + url.host + " not inside i2p network, but outproxy support still missing"; + HTTPRequestFailed(message.c_str()); + LogPrint (eLogWarning, "HTTPProxy: ", message); + return true; } + SanitizeHTTPRequest(req); - m_request = m_method; - m_request.push_back(' '); - m_request += m_path; - m_request.push_back(' '); - m_request += m_version; - m_request.push_back('\r'); - m_request.push_back('\n'); - m_request.append("Connection: close\r\n"); - // TODO: temporary shortcut. Must be implemented properly - uint8_t * eol = nullptr; - bool isEndOfHeader = false; - while (!isEndOfHeader && len && (eol = (uint8_t *)memchr (http_buff, '\r', len))) - { - if (eol) - { - *eol = 0; eol++; - if (strncmp ((const char *)http_buff, "Referer", 7) && strncmp ((const char *)http_buff, "Connection", 10)) // strip out referer and connection - { - if (!strncmp ((const char *)http_buff, "User-Agent", 10)) // replace UserAgent - m_request.append("User-Agent: MYOB/6.66 (AN/ON)"); - else - m_request.append ((const char *)http_buff); - m_request.append ("\r\n"); - } - isEndOfHeader = !http_buff[0]; - auto l = eol - http_buff; - http_buff = eol; - len -= l; - if (len > 0) // \r - { - http_buff++; - len--; - } - } - } - m_request.append(reinterpret_cast(http_buff),len); - return true; - } + /* drop original request from input buffer */ + m_recv_buf.erase(m_recv_buf.begin(), m_recv_buf.begin() + req_len); + + /* build new buffer from modified request and data from original request */ + std::string request = req.to_string(); + m_send_buf.assign(request.begin(), request.end()); + m_send_buf.insert(m_send_buf.end(), m_recv_buf.begin(), m_recv_buf.end()); + + /* connect to destination */ + GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, + shared_from_this(), std::placeholders::_1), url.host, url.port); - bool HTTPReqHandler::HandleData(uint8_t *http_buff, std::size_t len) - { - while (len > 0) - { - //TODO: fallback to finding HOst: header if needed - switch (m_state) - { - case GET_METHOD: - switch (*http_buff) - { - case ' ': EnterState(GET_HOSTNAME); break; - default: m_method.push_back(*http_buff); break; - } - break; - case GET_HOSTNAME: - switch (*http_buff) - { - case ' ': EnterState(GET_HTTPV); break; - default: m_url.push_back(*http_buff); break; - } - break; - case GET_HTTPV: - switch (*http_buff) - { - case '\r': EnterState(GET_HTTPVNL); break; - default: m_version.push_back(*http_buff); break; - } - break; - case GET_HTTPVNL: - switch (*http_buff) - { - case '\n': EnterState(DONE); break; - default: - LogPrint(eLogError, "HTTPProxy: rejected invalid request ending with: ", ((int)*http_buff)); - HTTPRequestFailed("rejected invalid request"); - return false; - } - break; - default: - LogPrint(eLogError, "HTTPProxy: invalid state: ", m_state); - HTTPRequestFailed("invalid parser state"); - return false; - } - http_buff++; - len--; - if (m_state == DONE) - return CreateHTTPRequest(http_buff,len); - } return true; } @@ -296,17 +215,9 @@ namespace proxy { return; } - if (HandleData(m_downstream_recv_buf, len)) - { - if (m_state == DONE) - { - LogPrint(eLogDebug, "HTTPProxy: requested: ", m_url); - GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, - shared_from_this(), std::placeholders::_1), m_address, m_port); - } - else - AsyncSockRead(); - } + if (HandleRequest(len)) + return; /* request processed */ + AsyncSockRead(); } void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) @@ -328,7 +239,7 @@ namespace proxy { LogPrint (eLogDebug, "HTTPProxy: New I2PTunnel connection"); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler (connection); - connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); + connection->I2PConnect (m_send_buf.data(), m_send_buf.size()); Done (shared_from_this()); } From 8622385e884bbf4bb3f0a005242aca82ddb5e5b8 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 May 2016 13:46:28 -0400 Subject: [PATCH 1317/6300] I2CPDestination added --- I2CP.cpp | 6 ++++++ I2CP.h | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/I2CP.cpp b/I2CP.cpp index d6ba4e42..5c718a48 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -8,6 +8,12 @@ namespace i2p { namespace client { + + I2CPDestination::I2CPDestination (I2CPSession& owner, std::shared_ptr identity, bool isPublic): + LeaseSetDestination (isPublic), m_Owner (owner), m_Identity (identity) + { + } + I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0) diff --git a/I2CP.h b/I2CP.h index cb17626d..020fd22a 100644 --- a/I2CP.h +++ b/I2CP.h @@ -5,6 +5,7 @@ #include #include #include +#include "Destination.h" namespace i2p { @@ -19,6 +20,28 @@ namespace client const uint8_t I2CP_GET_DATE_MESSAGE = 32; + class I2CPSession; + class I2CPDestination: public LeaseSetDestination + { + public: + + I2CPDestination (I2CPSession& owner, std::shared_ptr identity, bool isPublic); + + protected: + + // implements LocalDestination + std::shared_ptr GetIdentity () const { return m_Identity; }; + void Sign (const uint8_t * buf, int len, uint8_t * signature) const { /* TODO */}; + + // I2CP + void HandleDataMessage (const uint8_t * buf, size_t len) {}; + + private: + + I2CPSession& m_Owner; + std::shared_ptr m_Identity; + }; + class I2CPServer; class I2CPSession: public std::enable_shared_from_this { @@ -44,6 +67,8 @@ namespace client std::shared_ptr m_Socket; uint8_t m_Buffer[I2CP_SESSION_BUFFER_SIZE], * m_NextMessage; size_t m_NextMessageLen, m_NextMessageOffset; + + std::shared_ptr m_Destination; }; typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); From 1a9422c3f9e57513c1aea05d557cefc79ff1a454 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 May 2016 16:22:42 -0400 Subject: [PATCH 1318/6300] send SetDateMessage --- I2CP.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- I2CP.h | 9 +++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 5c718a48..fbd3e51b 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -1,9 +1,9 @@ #include #include "I2PEndian.h" #include "Log.h" +#include "Timestamp.h" #include "I2CP.h" - namespace i2p { namespace client @@ -104,14 +104,67 @@ namespace client { } + void I2CPSession::SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len) + { + auto l = len + I2CP_HEADER_SIZE; + uint8_t * buf = new uint8_t[l]; + htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len); + buf[I2CP_HEADER_TYPE_OFFSET] = type; + memcpy (buf + I2CP_HEADER_SIZE, payload, len); + boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), + std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), + std::placeholders::_1, std::placeholders::_2, buf)); + } + + void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf) + { + delete[] buf; + if (ecode && ecode != boost::asio::error::operation_aborted) + Terminate (); + } + + std::string I2CPSession::ExtractString (const uint8_t * buf, size_t len) + { + uint8_t l = buf[0]; + if (l > len) l = len; + return std::string ((const char *)buf, l); + } + + size_t I2CPSession::PutString (uint8_t * buf, size_t len, const std::string& str) + { + auto l = str.length (); + if (l + 1 >= len) l = len - 1; + if (l > 255) l = 255; // 1 byte max + buf[0] = l; + memcpy (buf + 1, str.c_str (), l); + return l + 1; + } + void I2CPSession::GetDateMessageHandler (const uint8_t * buf, size_t len) { + // get version + auto version = ExtractString (buf, len); + auto l = version.length () + 1 + 8; + uint8_t * payload = new uint8_t[l]; + // set date + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + htobe64buf (payload, ts); + // echo vesrion back + PutString (payload + 8, l - 8, version); + SendI2CPMessage (I2CP_SET_DATE_MESSAGE, payload, l); + } + + void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len) + { + // TODO + m_Destination = std::make_shared(*this, nullptr, false); } I2CPServer::I2CPServer (const std::string& interface, int port) { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); - m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; + m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; + m_MessagesHandlers[I2CP_CREATE_SESSION_MESSAGE ] = &I2CPSession::CreateSessionMessageHandler; } } } diff --git a/I2CP.h b/I2CP.h index 020fd22a..c495d34e 100644 --- a/I2CP.h +++ b/I2CP.h @@ -19,6 +19,8 @@ namespace client const size_t I2CP_HEADER_SIZE = I2CP_HEADER_TYPE_OFFSET + 1; const uint8_t I2CP_GET_DATE_MESSAGE = 32; + const uint8_t I2CP_SET_DATE_MESSAGE = 33; + const uint8_t I2CP_CREATE_SESSION_MESSAGE = 1; class I2CPSession; class I2CPDestination: public LeaseSetDestination @@ -52,6 +54,7 @@ namespace client // message handlers void GetDateMessageHandler (const uint8_t * buf, size_t len); + void CreateSessionMessageHandler (const uint8_t * buf, size_t len); private: @@ -61,6 +64,12 @@ namespace client void HandleNextMessage (const uint8_t * buf); void Terminate (); + void SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len); + void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf); + + std::string ExtractString (const uint8_t * buf, size_t len); + size_t PutString (uint8_t * buf, size_t len, const std::string& str); + private: I2CPServer& m_Owner; From 44eccd85fd86c77a415a329d91115ffa49b31985 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 28 May 2016 00:00:00 +0000 Subject: [PATCH 1319/6300] * HTTPServer.cpp : * autorefresh for status page * autoreturn to commands list --- HTTPServer.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 5d54711d..2af92057 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -755,12 +755,14 @@ namespace http { // Html5 head start ShowPageHead (s); - if (req.uri.find("page=") != std::string::npos) + if (req.uri.find("page=") != std::string::npos) { HandlePage (req, res, s); - else if (req.uri.find("cmd=") != std::string::npos) + } else if (req.uri.find("cmd=") != std::string::npos) { HandleCommand (req, res, s); - else + } else { ShowStatus (s); + res.add_header("Refresh", "5"); + } ShowPageTail (s); res.code = 200; @@ -841,7 +843,9 @@ namespace http { return; } s << "SUCCESS: Command accepted

\r\n"; - s << "Back to commands list"; + s << "Back to commands list
\r\n"; + s << "

i2pd webconsole
\r\n" "
\r\n" "
\r\n" - " Main page
\r\n
\r\n" - " Router commands
\r\n" - " Local destinations
\r\n" - " Tunnels
\r\n" - " Transit tunnels
\r\n" - " Transports
\r\n" - " I2P tunnels
\r\n" - " Jump services
\r\n" - " SAM sessions
\r\n" + " Main page
\r\n
\r\n" + " Router commands
\r\n" + " Local destinations
\r\n" + " Tunnels
\r\n" + " Transit tunnels
\r\n" + " Transports
\r\n" + " I2P tunnels
\r\n" + " Jump services
\r\n" + " SAM sessions
\r\n" "
\r\n" "
"; } @@ -236,9 +236,9 @@ namespace http { void ShowJumpServices (std::stringstream& s, const std::string& address) { - s << "
"; + s << ""; s << ""; - s << ""; + s << ""; s << ""; s << "
\r\n"; s << "Jump services for " << address << "\r\n
    \r\n"; @@ -254,7 +254,7 @@ namespace http { for (auto& it: i2p::client::context.GetDestinations ()) { auto ident = it.second->GetIdentHash ();; - s << ""; + s << ""; s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
    \r\n" << std::endl; } } @@ -358,21 +358,21 @@ namespace http { { /* commands */ s << "Router Commands
    \r\n"; - s << " Run peer test
    \r\n"; - //s << " Reload config
    \r\n"; + s << " Run peer test
    \r\n"; + //s << " Reload config
    \r\n"; if (i2p::context.AcceptsTunnels ()) - s << " Stop accepting tunnels
    \r\n"; + s << " Stop accepting tunnels
    \r\n"; else - s << " Start accepting tunnels
    \r\n"; + s << " Start accepting tunnels
    \r\n"; #ifndef WIN32 if (Daemon.gracefullShutdownInterval) { - s << " Cancel gracefull shutdown ("; + s << " Cancel gracefull shutdown ("; s << Daemon.gracefullShutdownInterval; s << " seconds remains)
    \r\n"; } else { - s << " Start gracefull shutdown
    \r\n"; + s << " Start gracefull shutdown
    \r\n"; } - s << " Force shutdown
    \r\n"; + s << " Force shutdown
    \r\n"; #endif } @@ -450,7 +450,7 @@ namespace http { s << "SAM Sessions:
    \r\n
    \r\n"; for (auto& it: sam->GetSessions ()) { - s << ""; + s << ""; s << it.first << "
    \r\n" << std::endl; } } @@ -469,7 +469,7 @@ namespace http { return; } auto& ident = session->localDestination->GetIdentHash(); - s << ""; + s << ""; s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
    \r\n"; s << "
    \r\n"; s << "Streams:
    \r\n"; @@ -493,7 +493,7 @@ namespace http { for (auto& it: i2p::client::context.GetClientTunnels ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << ""; s << it.second->GetName () << " ⇠"; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
    \r\n"<< std::endl; @@ -502,7 +502,7 @@ namespace http { for (auto& it: i2p::client::context.GetServerTunnels ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << ""; s << it.second->GetName () << " ⇒ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << ":" << it.second->GetLocalPort (); From 28c2ca8bf8f6b7923130a11144e80a064816857c Mon Sep 17 00:00:00 2001 From: anon5 Date: Tue, 14 Jun 2016 04:47:22 +0800 Subject: [PATCH 1392/6300] gitignore improved - added various generated files --- .gitignore | 4 ++++ build/.gitignore | 10 ++++++++++ 2 files changed, 14 insertions(+) create mode 100644 build/.gitignore diff --git a/.gitignore b/.gitignore index d680e7ad..89a17a3c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,10 @@ router.keys i2p libi2pd.so netDb +/i2pd +/libi2pd.a +/libi2pdclient.a + # Autotools autom4te.cache diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 00000000..79c3f959 --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,10 @@ +# Various generated files +/CMakeFiles/ +/i2pd +/libi2pd.a +/libi2pdclient.a +/cmake_install.cmake +/CMakeCache.txt +/CPackConfig.cmake +/CPackSourceConfig.cmake +/install_manifest.txt From 58b058ab3a6ad9598c28684ac232ee111324461e Mon Sep 17 00:00:00 2001 From: anon5 Date: Tue, 14 Jun 2016 08:52:17 +0800 Subject: [PATCH 1393/6300] .apk builds. untested --- ClientContext.cpp | 8 +- Daemon.h | 2 +- DaemonLinux.cpp | 9 ++- HTTP.cpp | 19 ++++- HTTPServer.cpp | 10 ++- I2PControl.cpp | 8 +- Reseed.cpp | 8 +- RouterContext.cpp | 8 +- qt/.gitignore | 2 + qt/i2pd_qt/i2pd_qt.pro | 153 ++++++++++++++++++++++++++++++++++++++ qt/i2pd_qt/main.cpp | 21 ++++++ qt/i2pd_qt/mainwindow.cpp | 14 ++++ qt/i2pd_qt/mainwindow.h | 22 ++++++ qt/i2pd_qt/mainwindow.ui | 21 ++++++ qt/i2pd_qt/to_string.h | 19 +++++ 15 files changed, 314 insertions(+), 10 deletions(-) create mode 100644 qt/.gitignore create mode 100644 qt/i2pd_qt/i2pd_qt.pro create mode 100644 qt/i2pd_qt/main.cpp create mode 100644 qt/i2pd_qt/mainwindow.cpp create mode 100644 qt/i2pd_qt/mainwindow.h create mode 100644 qt/i2pd_qt/mainwindow.ui create mode 100644 qt/i2pd_qt/to_string.h diff --git a/ClientContext.cpp b/ClientContext.cpp index db1c8e6a..3e69510c 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -8,6 +8,12 @@ #include "Identity.h" #include "ClientContext.h" +#ifdef ANDROID +# include "to_string.h" +#else +# define to_string(x) std::to_string(x) +#endif + namespace i2p { namespace client @@ -292,7 +298,7 @@ namespace client template std::string ClientContext::GetI2CPOption (const Section& section, const std::string& name, const Type& value) const { - return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value)); + return section.second.get (boost::property_tree::ptree::path_type (name, '/'), to_string (value)); } template diff --git a/Daemon.h b/Daemon.h index 977d9258..6b154ee4 100644 --- a/Daemon.h +++ b/Daemon.h @@ -52,7 +52,7 @@ namespace i2p void run (); }; #else - class DaemonLinux : public Daemon_Singleton + class DaemonLinux : public Daemon_Singleton { public: static DaemonLinux& Instance() diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 1cc0fa85..4aceff07 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -74,9 +74,11 @@ namespace i2p } // point std{in,out,err} descriptors to /dev/null - stdin = freopen("/dev/null", "r", stdin); +#ifndef ANDROID + stdin = freopen("/dev/null", "r", stdin); stdout = freopen("/dev/null", "w", stdout); stderr = freopen("/dev/null", "w", stderr); +#endif } // Pidfile @@ -92,7 +94,12 @@ namespace i2p LogPrint(eLogError, "Daemon: could not create pid file ", pidfile, ": ", strerror(errno)); return false; } +#ifndef ANDROID if (lockf(pidFH, F_TLOCK, 0) != 0) +#else + //TODO ANDROID actually need to read man for this, blindly took a solution from . -anon5 + if (fcntl(pidFH, 1, 0) < 0) +#endif { LogPrint(eLogError, "Daemon: could not lock pid file ", pidfile, ": ", strerror(errno)); return false; diff --git a/HTTP.cpp b/HTTP.cpp index a23f5a72..dbb115af 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -10,6 +10,13 @@ #include #include +#ifdef ANDROID +# include "to_string.h" +# include +#else +# define to_string(x) std::to_string(x) +#endif + namespace i2p { namespace http { const std::vector HTTP_METHODS = { @@ -180,7 +187,11 @@ namespace http { out += user + "@"; } if (port) { - out += host + ":" + std::to_string(port); +#ifndef ANDROID + out += host + ":" + to_string(port); +#else + out += host + ":" + tostr::to_string(port); +#endif } else { out += host; } @@ -338,7 +349,11 @@ namespace http { if (status == "OK" && code != 200) status = HTTPCodeToStatus(code); // update if (body.length() > 0 && headers.count("Content-Length") == 0) - add_header("Content-Length", std::to_string(body.length()).c_str()); +#ifndef ANDROID + add_header("Content-Length", to_string(body.length()).c_str()); +#else + add_header("Content-Length", tostr::to_string(body.length()).c_str()); +#endif /* build response */ std::stringstream ss; ss << version << " " << code << " " << status << CRLF; diff --git a/HTTPServer.cpp b/HTTPServer.cpp index a48536a7..583c02da 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -25,6 +25,12 @@ // For image and info #include "version.h" +#ifdef ANDROID +# include "to_string.h" +#else +# define to_string(x) std::to_string(x) +#endif + namespace i2p { namespace http { const char *itoopieFavicon = @@ -230,8 +236,8 @@ namespace http { clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); - s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; - s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
    \r\n"; + s << "Client Tunnels: " << to_string(clientTunnelCount) << " "; + s << "Transit Tunnels: " << to_string(transitTunnelCount) << "
    \r\n"; } void ShowJumpServices (std::stringstream& s, const std::string& address) diff --git a/I2PControl.cpp b/I2PControl.cpp index c87db150..bad25f37 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -25,6 +25,12 @@ #include "version.h" #include "I2PControl.h" +#ifdef ANDROID +# include "to_string.h" +#else +# define to_string(x) std::to_string(x) +#endif + namespace i2p { namespace client @@ -315,7 +321,7 @@ namespace client } InsertParam (results, "API", api); results << ","; - std::string token = std::to_string(i2p::util::GetSecondsSinceEpoch ()); + std::string token = to_string(i2p::util::GetSecondsSinceEpoch ()); m_Tokens.insert (token); InsertParam (results, "Token", token); } diff --git a/Reseed.cpp b/Reseed.cpp index 6f27891d..b707429a 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -17,6 +17,12 @@ #include "util.h" +#ifdef ANDROID +# include "to_string.h" +#else +# define to_string(x) std::to_string(x) +#endif + namespace i2p { namespace data @@ -373,7 +379,7 @@ namespace data 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); + boost::asio::ip::tcp::resolver::query (u.host_, to_string (u.port_)), ecode); if (!ecode) { boost::asio::ssl::context ctx(service, boost::asio::ssl::context::sslv23); diff --git a/RouterContext.cpp b/RouterContext.cpp index 5fa32a13..34d49553 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -12,6 +12,12 @@ #include "Family.h" #include "RouterContext.h" +#ifdef ANDROID +# include "to_string.h" +#else +# define to_string(x) std::to_string(x) +#endif + namespace i2p { RouterContext context; @@ -56,7 +62,7 @@ namespace i2p routerInfo.AddNTCPAddress (host.c_str(), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC - routerInfo.SetProperty ("netId", std::to_string (I2PD_NET_ID)); + routerInfo.SetProperty ("netId", to_string (I2PD_NET_ID)); routerInfo.SetProperty ("router.version", I2P_VERSION); routerInfo.CreateBuffer (m_Keys); m_RouterInfo.SetRouterIdentity (GetIdentity ()); diff --git a/qt/.gitignore b/qt/.gitignore new file mode 100644 index 00000000..e3a93c87 --- /dev/null +++ b/qt/.gitignore @@ -0,0 +1,2 @@ +/build-i2pd_qt-Android_armeabi_v7a_GCC_4_9_Qt_5_6_0-Debug/ +/build-i2pd_qt-Desktop_Qt_5_6_0_GCC_64bit-Debug/ diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro new file mode 100644 index 00000000..f247cee4 --- /dev/null +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -0,0 +1,153 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2016-06-14T04:53:04 +# +#------------------------------------------------- + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = i2pd_qt +TEMPLATE = app + + +SOURCES += main.cpp\ + mainwindow.cpp \ + ../../HTTPServer.cpp ../../I2PControl.cpp ../../UPnP.cpp ../../Daemon.cpp ../../Config.cpp \ + ../../AddressBook.cpp \ + ../../api.cpp \ + ../../Base.cpp \ + ../../BOB.cpp \ + ../../ClientContext.cpp \ + ../../Crypto.cpp \ + ../../DaemonLinux.cpp \ + ../../DaemonWin32.cpp \ + ../../Datagram.cpp \ + ../../Destination.cpp \ + ../../Family.cpp \ + ../../FS.cpp \ + ../../Garlic.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 \ + ../../stdafx.cpp \ + ../../Streaming.cpp \ + ../../TransitTunnel.cpp \ + ../../Transports.cpp \ + ../../Tunnel.cpp \ + ../../TunnelEndpoint.cpp \ + ../../TunnelGateway.cpp \ + ../../TunnelPool.cpp \ + ../../util.cpp \ + ../../../android-ifaddrs/ifaddrs.c + +HEADERS += mainwindow.h \ + ../../HTTPServer.h ../../I2PControl.h ../../UPnP.h ../../Daemon.h ../../Config.h \ + to_string.h \ + ../../AddressBook.h \ + ../../api.h \ + ../../Base.h \ + ../../BOB.h \ + ../../ClientContext.h \ + ../../Crypto.h \ + ../../Datagram.h \ + ../../Destination.h \ + ../../Family.h \ + ../../FS.h \ + ../../Garlic.h \ + ../../HTTP.h \ + ../../HTTPProxy.h \ + ../../I2CP.h \ + ../../I2NPProtocol.h \ + ../../I2PEndian.h \ + ../../I2PService.h \ + ../../I2PTunnel.h \ + ../../Identity.h \ + ../../LeaseSet.h \ + ../../LittleBigEndian.h \ + ../../Log.h \ + ../../NetDb.h \ + ../../NetDbRequests.h \ + ../../NTCPSession.h \ + ../../Profiling.h \ + ../../Queue.h \ + ../../Reseed.h \ + ../../RouterContext.h \ + ../../RouterInfo.h \ + ../../SAM.h \ + ../../Signature.h \ + ../../SOCKS.h \ + ../../SSU.h \ + ../../SSUData.h \ + ../../SSUSession.h \ + ../../stdafx.h \ + ../../Streaming.h \ + ../../Timestamp.h \ + ../../TransitTunnel.h \ + ../../Transports.h \ + ../../TransportSession.h \ + ../../Tunnel.h \ + ../../TunnelBase.h \ + ../../TunnelConfig.h \ + ../../TunnelEndpoint.h \ + ../../TunnelGateway.h \ + ../../TunnelPool.h \ + ../../util.h \ + ../../version.h \ + ../../../android-ifaddrs/ifaddrs.h + +FORMS += mainwindow.ui + +CONFIG += mobility + +MOBILITY = + +LIBS += -lz + +android { +message("Using Android settings") +DEFINES += ANDROID=1 +# git clone https://github.com/emileb/Boost-for-Android-Prebuilt.git +# git clone https://github.com/anon5/OpenSSL-for-Android-Prebuilt.git +# git clone https://github.com/anon5/android-ifaddrs.git +INCLUDEPATH += /home/anon5/git/Boost-for-Android-Prebuilt/boost_1_53_0/include \ + /home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/include \ + ../../../android-ifaddrs/ +equals(ANDROID_TARGET_ARCH, armeabi-v7a){ +LIBS += -L/home/anon5/git/Boost-for-Android-Prebuilt/boost_1_53_0/armeabi-v7a/lib \ + -L/home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib \ +-lcrypto \ +-lssl \ +-lboost_system-gcc-mt-1_53 \ +-lboost_date_time-gcc-mt-1_53 \ +-lboost_filesystem-gcc-mt-1_53 \ +-lboost_program_options-gcc-mt-1_53 +} +} + +linux:!android { +message("Using Linux settings") +LIBS += -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread +} + diff --git a/qt/i2pd_qt/main.cpp b/qt/i2pd_qt/main.cpp new file mode 100644 index 00000000..db5c39e5 --- /dev/null +++ b/qt/i2pd_qt/main.cpp @@ -0,0 +1,21 @@ +#include "mainwindow.h" +#include +#include +#include "../../Daemon.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + + w.show(); + + if (Daemon.init(argc, argv)) + { + if (Daemon.start()) + Daemon.run (); + Daemon.stop(); + } + + return a.exec(); +} diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp new file mode 100644 index 00000000..49d64fce --- /dev/null +++ b/qt/i2pd_qt/mainwindow.cpp @@ -0,0 +1,14 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); +} + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h new file mode 100644 index 00000000..a3948a91 --- /dev/null +++ b/qt/i2pd_qt/mainwindow.h @@ -0,0 +1,22 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private: + Ui::MainWindow *ui; +}; + +#endif // MAINWINDOW_H diff --git a/qt/i2pd_qt/mainwindow.ui b/qt/i2pd_qt/mainwindow.ui new file mode 100644 index 00000000..7ebf8731 --- /dev/null +++ b/qt/i2pd_qt/mainwindow.ui @@ -0,0 +1,21 @@ + + MainWindow + + + + 0 + 0 + 800 + 480 + + + + MainWindow + + + + + + + + diff --git a/qt/i2pd_qt/to_string.h b/qt/i2pd_qt/to_string.h new file mode 100644 index 00000000..a4bcf480 --- /dev/null +++ b/qt/i2pd_qt/to_string.h @@ -0,0 +1,19 @@ +#ifndef TO_STRING_H +#define TO_STRING_H + +#include +#include + +namespace tostr { +template +std::string to_string(T value) +{ + std::ostringstream os ; + os << value ; + return os.str() ; +} +} + +using namespace tostr; + +#endif // TO_STRING_H From f672af9706b4d309f565431dc5fe72b45a51bdfd Mon Sep 17 00:00:00 2001 From: anon5 Date: Tue, 14 Jun 2016 13:20:34 +0800 Subject: [PATCH 1394/6300] .apk runs on emulator --- qt/i2pd_qt/docs/patch_openssl_so_libs.html | 59 ++++++++++++++++++++++ qt/i2pd_qt/i2pd_qt.pro | 22 ++++++-- 2 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 qt/i2pd_qt/docs/patch_openssl_so_libs.html diff --git a/qt/i2pd_qt/docs/patch_openssl_so_libs.html b/qt/i2pd_qt/docs/patch_openssl_so_libs.html new file mode 100644 index 00000000..0fb7d540 --- /dev/null +++ b/qt/i2pd_qt/docs/patch_openssl_so_libs.html @@ -0,0 +1,59 @@ + + + + +

    + OpenSSL под Android в Qt + +

    ЗапиÑÑŒ от Wyn размещена 18.01.2016 в 18:22
    Метки android, openssl, qt

    Мини-руководÑтво по тому, как быÑтро Ñкомпилировать OpenSSL Ð´Ð»Ñ Android и ÑвÑзать его Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð¾Ð¼ Qt.
    +Ð”Ð»Ñ Linux.

    +Вначале дейÑÑ‚Ð²Ð¸Ñ Ð¿Ð¾Ð»Ð½Ð¾Ñтью идентичны "раÑово-верному" руководÑтву по компилÑнию OpenSSL Ð´Ð»Ñ Android:
    +Качаем иÑходники openssl нужной верÑии Ñ Ð¸Ñ… Ñайта, качаем setenv-android.sh(вÑе ÑÑылки на закачку выше по ÑÑылке).
    +Ложим их в одну папку. ЗапуÑкаем конÑоль, переходим в ней в Ñту Ñамую папку.
    +Далее:

You will be redirected in 5 seconds"; + res.add_header("Refresh", "5; url=/?page=commands"); } void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) From ea8e1be294537d21e59b7d5d6d3a0fc61ec48c3e Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 29 May 2016 00:00:00 +0000 Subject: [PATCH 1320/6300] * update default init-script : make --port optional --- debian/i2pd.default | 1 + debian/i2pd.init | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/debian/i2pd.default b/debian/i2pd.default index bf6eb005..28b0e621 100644 --- a/debian/i2pd.default +++ b/debian/i2pd.default @@ -4,6 +4,7 @@ I2PD_ENABLED="yes" # port to listen for incoming connections +# comment this line if you want to use value from config I2PD_PORT="4567" # Additional options that are passed to the Daemon. diff --git a/debian/i2pd.init b/debian/i2pd.init index 8cfee8d4..02b37546 100644 --- a/debian/i2pd.init +++ b/debian/i2pd.init @@ -41,6 +41,10 @@ do_start() return 2 fi + if [ -n "$I2PD_PORT" ]; then + DAEMON_OPTS="--port $I2PD_PORT $DAEMON_OPTS" + fi + touch "$PIDFILE" chown -f $USER:adm "$PIDFILE" @@ -51,7 +55,7 @@ do_start() || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" -- \ --service --daemon --log=file --logfile=$LOGFILE --conf=$I2PCONF --tunconf=$TUNCONF \ - --port=$I2PD_PORT $DAEMON_OPTS > /dev/null 2>&1 \ + $DAEMON_OPTS > /dev/null 2>&1 \ || return 2 return $? } From d9babda1b8470bd76c998640f5f3ec9e4601b4f2 Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 29 May 2016 00:00:00 +0000 Subject: [PATCH 1321/6300] + debian/i2pd.openrc (experimental) --- debian/i2pd.openrc | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 debian/i2pd.openrc diff --git a/debian/i2pd.openrc b/debian/i2pd.openrc new file mode 100644 index 00000000..ddcb4003 --- /dev/null +++ b/debian/i2pd.openrc @@ -0,0 +1,27 @@ +#!/sbin/openrc-run + +pidfile="/var/run/i2pd.pid" +logfile="/var/log/i2pd.log" +mainconf="/etc/i2pd/i2pd.conf" +tunconf="/etc/i2pd/tunnels.conf" + +. /etc/default/i2pd + +name="i2pd" +command="/usr/sbin/i2pd" +command_args="--service --daemon --log=file --logfile=$logfile --conf=$mainconf --tunconf=$tunconf" +description="i2p router written in C++" +required_dirs="/var/lib/i2pd" +required_files="$mainconf" +start_stop_daemon_args="--chuid i2pd" + +depend() { + need mountall + use net + after bootmisc +} + +start_pre() { + checkpath -f -o i2pd:adm -w $pidfile + checkpath -f -o i2pd:adm -w $logfile +} From 6c9b4a8c5df8ff57a73d706601196f7ea256c68f Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 29 May 2016 09:33:50 -0400 Subject: [PATCH 1322/6300] moved LeaseSet creating away from LeaseSetDestination --- Destination.cpp | 84 +++++++++++++++++++++++++++---------------------- Destination.h | 27 ++++++++-------- I2CP.cpp | 1 + I2CP.h | 7 +++-- Identity.h | 1 - 5 files changed, 66 insertions(+), 54 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 40c8768e..bb67b601 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -130,10 +130,6 @@ namespace client if (!m_IsRunning) { m_IsRunning = true; - if (m_IsPublic) - PersistTemporaryKeys (); - else - i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); m_Pool->SetLocalDestination (shared_from_this ()); m_Pool->SetActive (true); m_Thread = new std::thread (std::bind (&LeaseSetDestination::Run, shared_from_this ())); @@ -204,14 +200,21 @@ namespace client return m_LeaseSet; } + void LeaseSetDestination::SetLeaseSet (i2p::data::LocalLeaseSet * newLeaseSet) + { + m_LeaseSet.reset (newLeaseSet); + if (m_IsPublic) + { + m_PublishVerificationTimer.cancel (); + Publish (); + } + } + void LeaseSetDestination::UpdateLeaseSet () { int numTunnels = m_Pool->GetNumInboundTunnels () + 2; // 2 backup tunnels if (numTunnels > i2p::data::MAX_NUM_LEASES) numTunnels = i2p::data::MAX_NUM_LEASES; // 16 tunnels maximum - auto leaseSet = new i2p::data::LocalLeaseSet (GetIdentity (), GetEncryptionPublicKey (), - m_Pool->GetInboundTunnels (numTunnels)); - Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); // TODO - m_LeaseSet.reset (leaseSet); + CreateNewLeaseSet (m_Pool->GetInboundTunnels (numTunnels)); } bool LeaseSetDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) @@ -391,11 +394,6 @@ namespace client { i2p::garlic::GarlicDestination::SetLeaseSetUpdated (); UpdateLeaseSet (); - if (m_IsPublic) - { - m_PublishVerificationTimer.cancel (); - Publish (); - } } void LeaseSetDestination::Publish () @@ -642,36 +640,16 @@ namespace client else it++; } - } - - void LeaseSetDestination::PersistTemporaryKeys () - { - std::string ident = GetIdentHash().ToBase32(); - std::string path = i2p::fs::DataDirPath("destinations", (ident + ".dat")); - std::ifstream f(path, std::ifstream::binary); - - if (f) { - f.read ((char *)m_EncryptionPublicKey, 256); - f.read ((char *)m_EncryptionPrivateKey, 256); - return; - } - - LogPrint (eLogInfo, "Destination: Creating new temporary keys for address ", ident, ".b32.i2p"); - i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); - - std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out); - if (f1) { - f1.write ((char *)m_EncryptionPublicKey, 256); - f1.write ((char *)m_EncryptionPrivateKey, 256); - return; - } - LogPrint(eLogError, "Destinations: Can't save keys to ", path); } ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): LeaseSetDestination (isPublic, params), m_Keys (keys), m_DatagramDestination (nullptr) { + if (isPublic) + PersistTemporaryKeys (); + else + i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); if (isPublic) LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created"); } @@ -840,5 +818,37 @@ namespace client ret.push_back (it1.second); return ret; } + + void ClientDestination::PersistTemporaryKeys () + { + std::string ident = GetIdentHash().ToBase32(); + std::string path = i2p::fs::DataDirPath("destinations", (ident + ".dat")); + std::ifstream f(path, std::ifstream::binary); + + if (f) { + f.read ((char *)m_EncryptionPublicKey, 256); + f.read ((char *)m_EncryptionPrivateKey, 256); + return; + } + + LogPrint (eLogInfo, "Destination: Creating new temporary keys for address ", ident, ".b32.i2p"); + i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); + + std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out); + if (f1) { + f1.write ((char *)m_EncryptionPublicKey, 256); + f1.write ((char *)m_EncryptionPrivateKey, 256); + return; + } + LogPrint(eLogError, "Destinations: Can't save keys to ", path); + } + + void ClientDestination::CreateNewLeaseSet (std::vector > tunnels) + { + auto leaseSet = new i2p::data::LocalLeaseSet (GetIdentity (), m_EncryptionPublicKey, tunnels); + // sign + Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); // TODO + SetLeaseSet (leaseSet); + } } } diff --git a/Destination.h b/Destination.h index e2531699..56c83fb4 100644 --- a/Destination.h +++ b/Destination.h @@ -81,10 +81,6 @@ namespace client bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); void CancelDestinationRequest (const i2p::data::IdentHash& dest); - // implements LocalDestination - const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; - const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; - // implements GarlicDestination std::shared_ptr GetLeaseSet (); std::shared_ptr GetTunnelPool () const { return m_Pool; } @@ -98,8 +94,10 @@ namespace client protected: + void SetLeaseSet (i2p::data::LocalLeaseSet * newLeaseSet); // I2CP virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0; + virtual void CreateNewLeaseSet (std::vector > tunnels) = 0; private: @@ -117,13 +115,9 @@ namespace client void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleCleanupTimer (const boost::system::error_code& ecode); void CleanupRemoteLeaseSets (); - - void PersistTemporaryKeys (); private: - uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; - volatile bool m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; @@ -156,7 +150,8 @@ namespace client bool Stop (); const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; - + void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; + // streaming std::shared_ptr CreateStreamingDestination (int port, bool gzip = true); // additional std::shared_ptr GetStreamingDestination (int port = 0) const; @@ -166,28 +161,32 @@ namespace client void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); void StopAcceptingStreams (); bool IsAcceptingStreams () const; - + // datagram i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; i2p::datagram::DatagramDestination * CreateDatagramDestination (); - // implements LocalDestination - std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; - void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; + // implements LocalDestination + const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; + const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; + std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; protected: // I2CP void HandleDataMessage (const uint8_t * buf, size_t len); + void CreateNewLeaseSet (std::vector > tunnels); private: std::shared_ptr GetSharedFromThis () - { return std::static_pointer_cast(shared_from_this ()); } + { return std::static_pointer_cast(shared_from_this ()); } + void PersistTemporaryKeys (); private: i2p::data::PrivateKeys m_Keys; + uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; std::shared_ptr m_StreamingDestination; // default std::map > m_StreamingDestinationsByPorts; diff --git a/I2CP.cpp b/I2CP.cpp index fbd3e51b..ee329d37 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -152,6 +152,7 @@ namespace client // echo vesrion back PutString (payload + 8, l - 8, version); SendI2CPMessage (I2CP_SET_DATE_MESSAGE, payload, l); + delete[] payload; } void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len) diff --git a/I2CP.h b/I2CP.h index c495d34e..f677b9ee 100644 --- a/I2CP.h +++ b/I2CP.h @@ -32,16 +32,19 @@ namespace client protected: // implements LocalDestination + const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; + const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; std::shared_ptr GetIdentity () const { return m_Identity; }; - void Sign (const uint8_t * buf, int len, uint8_t * signature) const { /* TODO */}; // I2CP - void HandleDataMessage (const uint8_t * buf, size_t len) {}; + void HandleDataMessage (const uint8_t * buf, size_t len) { /* TODO */ }; + void CreateNewLeaseSet (std::vector > tunnels) { /* TODO */ }; private: I2CPSession& m_Owner; std::shared_ptr m_Identity; + uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; }; class I2CPServer; diff --git a/Identity.h b/Identity.h index 541a7801..2a60ddd3 100644 --- a/Identity.h +++ b/Identity.h @@ -181,7 +181,6 @@ namespace data virtual const uint8_t * GetEncryptionPrivateKey () const = 0; virtual const uint8_t * GetEncryptionPublicKey () const = 0; virtual std::shared_ptr GetIdentity () const = 0; - virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0; const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; }; From 09a80ed6541ad69ad8a47c29cba14c056aea148d Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 29 May 2016 16:35:57 -0400 Subject: [PATCH 1323/6300] RequestVariableLeaseSetMessage --- I2CP.cpp | 17 +++++++++++++++-- I2CP.h | 13 ++++++++----- LeaseSet.cpp | 1 + LeaseSet.h | 5 ++++- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index ee329d37..bb7c956b 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -2,6 +2,7 @@ #include "I2PEndian.h" #include "Log.h" #include "Timestamp.h" +#include "LeaseSet.h" #include "I2CP.h" namespace i2p @@ -14,13 +15,25 @@ namespace client { } + void I2CPDestination::CreateNewLeaseSet (std::vector > tunnels) + { + i2p::data::LocalLeaseSet ls (m_Identity, m_EncryptionPublicKey, tunnels); + uint8_t * leases = ls.GetLeases (); + leases[-1] = tunnels.size (); + htobe16buf (leases - 3, m_Owner.GetSessionID ()); + size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); + m_Owner.SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); + + } + I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), - m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0) + m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0), + m_SessionID (0) { ReadProtocolByte (); } - + I2CPSession::~I2CPSession () { delete[] m_NextMessage; diff --git a/I2CP.h b/I2CP.h index f677b9ee..3de7d73e 100644 --- a/I2CP.h +++ b/I2CP.h @@ -21,7 +21,8 @@ namespace client const uint8_t I2CP_GET_DATE_MESSAGE = 32; const uint8_t I2CP_SET_DATE_MESSAGE = 33; const uint8_t I2CP_CREATE_SESSION_MESSAGE = 1; - + const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37; + class I2CPSession; class I2CPDestination: public LeaseSetDestination { @@ -38,7 +39,7 @@ namespace client // I2CP void HandleDataMessage (const uint8_t * buf, size_t len) { /* TODO */ }; - void CreateNewLeaseSet (std::vector > tunnels) { /* TODO */ }; + void CreateNewLeaseSet (std::vector > tunnels); private: @@ -55,6 +56,9 @@ namespace client I2CPSession (I2CPServer& owner, std::shared_ptr socket); ~I2CPSession (); + uint16_t GetSessionID () const { return m_SessionID; }; + void SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len); + // message handlers void GetDateMessageHandler (const uint8_t * buf, size_t len); void CreateSessionMessageHandler (const uint8_t * buf, size_t len); @@ -66,10 +70,8 @@ namespace client void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleNextMessage (const uint8_t * buf); void Terminate (); - - void SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len); + void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf); - std::string ExtractString (const uint8_t * buf, size_t len); size_t PutString (uint8_t * buf, size_t len, const std::string& str); @@ -81,6 +83,7 @@ namespace client size_t m_NextMessageLen, m_NextMessageOffset; std::shared_ptr m_Destination; + uint16_t m_SessionID; }; typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 5efe2b16..20861677 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -213,6 +213,7 @@ namespace data m_Buffer[offset] = num; offset++; // leases + m_Leases = m_Buffer + offset; auto currentTime = i2p::util::GetMillisecondsSinceEpoch (); for (int i = 0; i < num; i++) { diff --git a/LeaseSet.h b/LeaseSet.h index 289ae25c..6688fbf8 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "Identity.h" @@ -94,6 +95,8 @@ namespace data uint8_t * GetSignature () { return m_Buffer + m_BufferLen - GetSignatureLen (); }; size_t GetBufferLen () const { return m_BufferLen; }; size_t GetSignatureLen () const { return m_Identity->GetSignatureLen (); }; + uint8_t * GetLeases () { return m_Leases; }; + const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); }; bool IsExpired () const; bool operator== (const LeaseSet& other) const @@ -104,7 +107,7 @@ namespace data uint64_t m_ExpirationTime; // in milliseconds std::shared_ptr m_Identity; - uint8_t * m_Buffer; + uint8_t * m_Buffer, * m_Leases; size_t m_BufferLen; }; } From 5a2c4919c6c415bcb22aeae8cbd5f579aa90bcea Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 May 2016 09:41:45 -0400 Subject: [PATCH 1324/6300] close previous file first upon repon --- Log.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Log.cpp b/Log.cpp index 36fcebf3..0518fb2e 100644 --- a/Log.cpp +++ b/Log.cpp @@ -130,10 +130,13 @@ namespace log { Process(); } - void Log::SendTo (const std::string& path) { + void Log::SendTo (const std::string& path) + { + if (m_LogStream) m_LogStream = nullptr; // close previous auto flags = std::ofstream::out | std::ofstream::app; auto os = std::make_shared (path, flags); - if (os->is_open ()) { + if (os->is_open ()) + { m_Logfile = path; m_Destination = eLogFile; m_LogStream = os; From 6a453bcc8a51c740a6a23edbae683f01aa5ba54c Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 May 2016 12:08:20 -0400 Subject: [PATCH 1325/6300] check for null pointer --- Log.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Log.cpp b/Log.cpp index 0518fb2e..155efbe8 100644 --- a/Log.cpp +++ b/Log.cpp @@ -56,7 +56,7 @@ namespace log { #endif case eLogFile: case eLogStream: - m_LogStream->flush(); + if (m_LogStream) m_LogStream->flush(); break; default: /* do nothing */ @@ -107,10 +107,11 @@ namespace log { #endif case eLogFile: case eLogStream: - *m_LogStream << TimeAsString(msg->timestamp) - << "@" << short_tid - << "/" << g_LogLevelStr[msg->level] - << " - " << msg->text << std::endl; + if (m_LogStream) + *m_LogStream << TimeAsString(msg->timestamp) + << "@" << short_tid + << "/" << g_LogLevelStr[msg->level] + << " - " << msg->text << std::endl; break; case eLogStdout: default: From a062bca431e5ca9d36f79837b06956e78ea2ba27 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 May 2016 12:56:42 -0400 Subject: [PATCH 1326/6300] CreateLeaseSetMessage --- Destination.h | 1 - I2CP.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++--- I2CP.h | 21 +++++++++++++++++---- Identity.h | 1 - LeaseSet.cpp | 8 ++++++++ LeaseSet.h | 3 +++ 6 files changed, 70 insertions(+), 9 deletions(-) diff --git a/Destination.h b/Destination.h index 56c83fb4..e64508c9 100644 --- a/Destination.h +++ b/Destination.h @@ -168,7 +168,6 @@ namespace client // implements LocalDestination const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; - const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; protected: diff --git a/I2CP.cpp b/I2CP.cpp index bb7c956b..b0e889ac 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "I2PEndian.h" #include "Log.h" @@ -15,9 +23,15 @@ namespace client { } + void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) + { + memcpy (m_EncryptionPrivateKey, key, 256); + } + void I2CPDestination::CreateNewLeaseSet (std::vector > tunnels) { - i2p::data::LocalLeaseSet ls (m_Identity, m_EncryptionPublicKey, tunnels); + i2p::data::LocalLeaseSet ls (m_Identity, m_EncryptionPrivateKey, tunnels); // we don't care about encryption key + m_LeaseSetExpirationTime = ls.GetExpirationTime (); uint8_t * leases = ls.GetLeases (); leases[-1] = tunnels.size (); htobe16buf (leases - 3, m_Owner.GetSessionID ()); @@ -25,7 +39,14 @@ namespace client m_Owner.SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); } - + + void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) + { + auto ls = new i2p::data::LocalLeaseSet (m_Identity, buf, len); + ls->SetExpirationTime (m_LeaseSetExpirationTime); + SetLeaseSet (ls); + } + I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0), @@ -174,11 +195,29 @@ namespace client m_Destination = std::make_shared(*this, nullptr, false); } + void I2CPSession::CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len) + { + uint16_t sessionID = bufbe16toh (buf); + if (sessionID == m_SessionID) + { + size_t offset = 2; + if (m_Destination) + { + m_Destination->SetEncryptionPrivateKey (buf + offset); + offset += 256; + m_Destination->LeaseSetCreated (buf + offset, len - offset); + } + } + else + LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); + } + I2CPServer::I2CPServer (const std::string& interface, int port) { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; - m_MessagesHandlers[I2CP_CREATE_SESSION_MESSAGE ] = &I2CPSession::CreateSessionMessageHandler; + m_MessagesHandlers[I2CP_CREATE_SESSION_MESSAGE] = &I2CPSession::CreateSessionMessageHandler; + m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler; } } } diff --git a/I2CP.h b/I2CP.h index 3de7d73e..73f63432 100644 --- a/I2CP.h +++ b/I2CP.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef I2CP_H__ #define I2CP_H__ @@ -22,7 +30,8 @@ namespace client const uint8_t I2CP_SET_DATE_MESSAGE = 33; const uint8_t I2CP_CREATE_SESSION_MESSAGE = 1; const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37; - + const uint8_t I2CP_CREATE_LEASESET_MESSAGE = 4; + class I2CPSession; class I2CPDestination: public LeaseSetDestination { @@ -30,11 +39,13 @@ namespace client I2CPDestination (I2CPSession& owner, std::shared_ptr identity, bool isPublic); + void SetEncryptionPrivateKey (const uint8_t * key); + void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession + protected: // implements LocalDestination const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; - const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; std::shared_ptr GetIdentity () const { return m_Identity; }; // I2CP @@ -45,7 +56,8 @@ namespace client I2CPSession& m_Owner; std::shared_ptr m_Identity; - uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; + uint8_t m_EncryptionPrivateKey[256]; + uint64_t m_LeaseSetExpirationTime; }; class I2CPServer; @@ -62,6 +74,7 @@ namespace client // message handlers void GetDateMessageHandler (const uint8_t * buf, size_t len); void CreateSessionMessageHandler (const uint8_t * buf, size_t len); + void CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len); private: @@ -95,7 +108,7 @@ namespace client private: - I2CPMessageHandler m_MessagesHandlers[256]; + I2CPMessageHandler m_MessagesHandlers[256]; public: diff --git a/Identity.h b/Identity.h index 2a60ddd3..841acf65 100644 --- a/Identity.h +++ b/Identity.h @@ -179,7 +179,6 @@ namespace data virtual ~LocalDestination() {}; virtual const uint8_t * GetEncryptionPrivateKey () const = 0; - virtual const uint8_t * GetEncryptionPublicKey () const = 0; virtual std::shared_ptr GetIdentity () const = 0; const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 20861677..16a470e0 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -232,6 +232,14 @@ namespace data // we don't sign it yet. must be signed later on } + LocalLeaseSet::LocalLeaseSet (std::shared_ptr identity, const uint8_t * buf, size_t len): + m_ExpirationTime (0), m_Identity (identity) + { + m_BufferLen = len; + m_Buffer = new uint8_t[m_BufferLen]; + memcpy (m_Buffer, buf, len); + } + bool LocalLeaseSet::IsExpired () const { auto ts = i2p::util::GetMillisecondsSinceEpoch (); diff --git a/LeaseSet.h b/LeaseSet.h index 6688fbf8..c174ac39 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -89,6 +89,7 @@ namespace data public: LocalLeaseSet (std::shared_ptr identity, const uint8_t * encryptionPublicKey, std::vector > tunnels); + LocalLeaseSet (std::shared_ptr identity, const uint8_t * buf, size_t len); ~LocalLeaseSet () { delete[] m_Buffer; }; const uint8_t * GetBuffer () const { return m_Buffer; }; @@ -99,6 +100,8 @@ namespace data const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); }; bool IsExpired () const; + uint64_t GetExpirationTime () const { return m_ExpirationTime; }; + void SetExpirationTime (uint64_t expirationTime) { m_ExpirationTime = expirationTime; }; bool operator== (const LeaseSet& other) const { return m_BufferLen == other.GetBufferLen () && !memcmp (other.GetBuffer (), other.GetBuffer (), m_BufferLen); }; From ae10793d0f2f559727d38b8e3769ac0e8b019824 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 May 2016 14:31:56 -0400 Subject: [PATCH 1327/6300] SendMessageMessage --- I2CP.cpp | 31 ++++++++++++++++++++++++++++++- I2CP.h | 3 +++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/I2CP.cpp b/I2CP.cpp index b0e889ac..b74e9cc8 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -47,6 +47,17 @@ namespace client SetLeaseSet (ls); } + void I2CPDestination::SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident) + { + auto msg = NewI2NPMessage (); + uint8_t * buf = msg->GetPayload (); + htobe32buf (buf, len); + memcpy (buf + 4, payload, len); + msg->len += len + 4; + msg->FillI2NPMessageHeader (eI2NPData); + // TODO: send + } + I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0), @@ -212,12 +223,30 @@ namespace client LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } + void I2CPSession::SendMessageMessageHandler (const uint8_t * buf, size_t len) + { + uint16_t sessionID = bufbe16toh (buf); + if (sessionID == m_SessionID) + { + size_t offset = 2; + if (m_Destination) + { + i2p::data::IdentityEx identity; + offset += identity.FromBuffer (buf + offset, len - offset); + m_Destination->SendMsgTo (buf + offset, len - offset, identity.GetIdentHash ()); + } + } + else + LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); + } + I2CPServer::I2CPServer (const std::string& interface, int port) { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; m_MessagesHandlers[I2CP_CREATE_SESSION_MESSAGE] = &I2CPSession::CreateSessionMessageHandler; - m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler; + m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler; + m_MessagesHandlers[I2CP_SEND_MESSAGE_MESSAGE] = &I2CPSession::SendMessageMessageHandler; } } } diff --git a/I2CP.h b/I2CP.h index 73f63432..4e0f1caf 100644 --- a/I2CP.h +++ b/I2CP.h @@ -31,6 +31,7 @@ namespace client const uint8_t I2CP_CREATE_SESSION_MESSAGE = 1; const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37; const uint8_t I2CP_CREATE_LEASESET_MESSAGE = 4; + const uint8_t I2CP_SEND_MESSAGE_MESSAGE = 5; class I2CPSession; class I2CPDestination: public LeaseSetDestination @@ -41,6 +42,7 @@ namespace client void SetEncryptionPrivateKey (const uint8_t * key); void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession + void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident); // called from I2CPSession protected: @@ -75,6 +77,7 @@ namespace client void GetDateMessageHandler (const uint8_t * buf, size_t len); void CreateSessionMessageHandler (const uint8_t * buf, size_t len); void CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len); + void SendMessageMessageHandler (const uint8_t * buf, size_t len); private: From eeffcea69e6fdabcc530ac6eb7f773bd728b5678 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 May 2016 15:19:22 -0400 Subject: [PATCH 1328/6300] CreateSessionMessage --- I2CP.cpp | 28 ++++++++++++++++++++++++++-- I2CP.h | 3 +++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index b74e9cc8..8d0bedf1 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -7,6 +7,7 @@ */ #include +#include #include "I2PEndian.h" #include "Log.h" #include "Timestamp.h" @@ -202,8 +203,31 @@ namespace client void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len) { - // TODO - m_Destination = std::make_shared(*this, nullptr, false); + auto identity = std::make_shared(); + size_t offset = identity->FromBuffer (buf, len); + uint16_t optionsSize = bufbe16toh (buf + offset); + // TODO: extract options + offset += optionsSize; + offset += 8; // date + if (identity->Verify (buf, offset, buf + offset)) // signature + { + m_Destination = std::make_shared(*this, identity, false); + RAND_bytes ((uint8_t *)&m_SessionID, 2); + SendSessionStatusMessage (1); // created + } + else + { + LogPrint (eLogError, "I2CP: create session signature verification falied"); + SendSessionStatusMessage (3); // invalid + } + } + + void I2CPSession::SendSessionStatusMessage (uint8_t status) + { + uint8_t buf[3]; + htobe16buf (buf, m_SessionID); + buf[2] = status; + SendI2CPMessage (I2CP_SESSION_STATUS_MESSAGE, buf, 3); } void I2CPSession::CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len) diff --git a/I2CP.h b/I2CP.h index 4e0f1caf..fca3317d 100644 --- a/I2CP.h +++ b/I2CP.h @@ -29,6 +29,7 @@ namespace client const uint8_t I2CP_GET_DATE_MESSAGE = 32; const uint8_t I2CP_SET_DATE_MESSAGE = 33; const uint8_t I2CP_CREATE_SESSION_MESSAGE = 1; + const uint8_t I2CP_SESSION_STATUS_MESSAGE = 20; const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37; const uint8_t I2CP_CREATE_LEASESET_MESSAGE = 4; const uint8_t I2CP_SEND_MESSAGE_MESSAGE = 5; @@ -91,6 +92,8 @@ namespace client std::string ExtractString (const uint8_t * buf, size_t len); size_t PutString (uint8_t * buf, size_t len, const std::string& str); + void SendSessionStatusMessage (uint8_t status); + private: I2CPServer& m_Owner; From 23e019ec83db38b3f0b0f27aaa2eb02ba953469e Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 31 May 2016 00:00:00 +0000 Subject: [PATCH 1329/6300] * debian/i2pd.openrc (working version) --- debian/i2pd.openrc | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/debian/i2pd.openrc b/debian/i2pd.openrc index ddcb4003..6253cfa6 100644 --- a/debian/i2pd.openrc +++ b/debian/i2pd.openrc @@ -5,11 +5,9 @@ logfile="/var/log/i2pd.log" mainconf="/etc/i2pd/i2pd.conf" tunconf="/etc/i2pd/tunnels.conf" -. /etc/default/i2pd - name="i2pd" command="/usr/sbin/i2pd" -command_args="--service --daemon --log=file --logfile=$logfile --conf=$mainconf --tunconf=$tunconf" +command_args="--service --daemon --log=file --logfile=$logfile --conf=$mainconf --tunconf=$tunconf --pidfile=$pidfile" description="i2p router written in C++" required_dirs="/var/lib/i2pd" required_files="$mainconf" @@ -22,6 +20,22 @@ depend() { } start_pre() { - checkpath -f -o i2pd:adm -w $pidfile - checkpath -f -o i2pd:adm -w $logfile + if [ -r /etc/default/i2pd ]; then + . /etc/default/i2pd + fi + + if [ "x$I2PD_ENABLED" != "xyes" ]; then + ewarn "i2pd disabled in /etc/default/i2pd" + exit 1 + fi + + checkpath -f -o i2pd:adm $logfile + checkpath -f -o i2pd:adm $pidfile + + if [ -n "$I2PD_PORT" -a "$I2PD_PORT" -gt 0 ]; then + command_args="$command_args --port=$I2PD_PORT" + fi + if [ -n "$DAEMON_OPTS" ]; then + command_args="$command_args $DAEMON_OPTS" + fi } From 289b679e3c6153a70ae3193fba93dc7242f2c24e Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 31 May 2016 00:00:00 +0000 Subject: [PATCH 1330/6300] * add doxygen support --- Makefile | 5 + docs/Doxyfile | 259 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 docs/Doxyfile diff --git a/Makefile b/Makefile index e3807d93..4cc313a9 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,7 @@ $(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) clean: rm -rf obj + rm -rf docs/generated $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) @@ -86,9 +87,13 @@ dist: git archive --format=tar.gz -9 --worktree-attributes \ --prefix=i2pd_$(LATEST_TAG)/ $(LATEST_TAG) -o i2pd_$(LATEST_TAG).tar.gz +doxygen: + doxygen -s docs/Doxyfile + .PHONY: all .PHONY: clean .PHONY: deps +.PHONY: doxygen .PHONY: dist .PHONY: api .PHONY: api_client diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 00000000..f2e29995 --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,259 @@ +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "i2pd" +PROJECT_NUMBER = +PROJECT_BRIEF = "load-balanced unspoofable packet switching network" +PROJECT_LOGO = +OUTPUT_DIRECTORY = docs/generated +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = NO +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +INPUT = +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.cpp *.h +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = NO +CLANG_ASSISTED_PARSING = NO +CLANG_OPTIONS = +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = YES +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO +GENERATE_XML = NO +XML_OUTPUT = xml +XML_PROGRAMLISTING = YES +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +DOCBOOK_PROGRAMLISTING = NO +GENERATE_AUTOGEN_DEF = NO +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +PERL_PATH = /usr/bin/perl +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES From c9836cf0f787c7ee8a0b3553653b80148299becb Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 31 May 2016 00:00:00 +0000 Subject: [PATCH 1331/6300] * fix doxygen warnings --- Config.h | 4 ++-- HTTP.h | 16 +++++++++------- Log.cpp | 2 +- Log.h | 7 ++++--- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Config.h b/Config.h index d79a9c47..6b2af717 100644 --- a/Config.h +++ b/Config.h @@ -68,7 +68,7 @@ namespace config { * @param value Variable where to store option * @return this function returns false if parameter not found * - * @example uint16_t port; GetOption("sam.port", port); + * Example: uint16_t port; GetOption("sam.port", port); */ template bool GetOption(const char *name, T& value) { @@ -84,7 +84,7 @@ namespace config { * @param value New parameter value * @return true if value set up successful, false otherwise * - * @example uint16_t port = 2827; SetOption("bob.port", port); + * Example: uint16_t port = 2827; SetOption("bob.port", port); */ template bool SetOption(const char *name, const T& value) { diff --git a/HTTP.h b/HTTP.h index f227271f..8d10c231 100644 --- a/HTTP.h +++ b/HTTP.h @@ -38,7 +38,7 @@ namespace http { * @brief Tries to parse url from string * @return true on success, false on invalid url */ - bool parse (const char *str, size_t len = 0); + bool parse (const char *str, std::size_t len = 0); bool parse (const std::string& url); /** @@ -89,10 +89,12 @@ namespace http { std::string version; std::string status; unsigned short int code; - /** simplifies response generation - * If this variable is set: - * a) Content-Length header will be added if missing - * b) contents of body will be included in response + /** + * @brief Simplifies response generation + * + * If this variable is set, on @a to_string() call: + * * Content-Length header will be added if missing, + * * contents of @a body will be included in generated response */ std::string body; @@ -108,9 +110,9 @@ namespace http { /** * @brief Serialize HTTP response to string - * @note If version is set to HTTP/1.1, and Date header is missing, + * @note If @a version is set to HTTP/1.1, and Date header is missing, * it will be generated based on current time and added to headers - * @note If body member is set and Content-Length header is missing, + * @note If @a body is set and Content-Length header is missing, * this header will be added, based on body's length */ std::string to_string(); diff --git a/Log.cpp b/Log.cpp index 155efbe8..590f3d0f 100644 --- a/Log.cpp +++ b/Log.cpp @@ -12,7 +12,7 @@ namespace i2p { namespace log { Log logger; /** - * @enum Maps our loglevel to their symbolic name + * @brief Maps our loglevel to their symbolic name */ static const char * g_LogLevelStr[eNumLogLevels] = { diff --git a/Log.h b/Log.h index 33bd5868..6762899f 100644 --- a/Log.h +++ b/Log.h @@ -86,7 +86,7 @@ namespace log { LogLevel GetLogLevel () { return m_MinLevel; }; /** - * @brief Sets minimal alloed level for log messages + * @brief Sets minimal allowed level for log messages * @param level String with wanted minimal msg level */ void SetLogLevel (const std::string& level); @@ -101,7 +101,7 @@ namespace log { * @brief Sets log destination to given output stream * @param os Output stream */ - void SendTo (std::shared_ptr s); + void SendTo (std::shared_ptr os); #ifndef _WIN32 /** @@ -129,7 +129,8 @@ namespace log { }; /** - * @struct Log message container + * @struct LogMsg + * @brief Log message container * * We creating it somewhere with LogPrint(), * then put in MsgQueue for later processing. From a47417ff49f46c5a1f92819b54c43960607e206e Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 31 May 2016 00:00:00 +0000 Subject: [PATCH 1332/6300] * I2PService.cpp: tune logs --- I2PService.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/I2PService.cpp b/I2PService.cpp index 0aefc172..6a0cd291 100644 --- a/I2PService.cpp +++ b/I2PService.cpp @@ -3,7 +3,6 @@ #include "ClientContext.h" #include "I2PService.h" - namespace i2p { namespace client @@ -71,7 +70,7 @@ namespace client std::bind(&TCPIPPipe::HandleUpstreamReceived, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { - LogPrint(eLogError, "TCPIPPipe: no upstream socket for read"); + LogPrint(eLogError, "TCPIPPipe: upstream receive: no socket"); } } @@ -82,14 +81,14 @@ namespace client std::bind(&TCPIPPipe::HandleDownstreamReceived, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { - LogPrint(eLogError, "TCPIPPipe: no downstream socket for read"); + LogPrint(eLogError, "TCPIPPipe: downstream receive: no socket"); } } void TCPIPPipe::UpstreamWrite(const uint8_t * buf, size_t len) { if (m_up) { - LogPrint(eLogDebug, "TCPIPPipe: write upstream ", (int)len); + LogPrint(eLogDebug, "TCPIPPipe: upstream: ", (int) len, " bytes written"); boost::asio::async_write(*m_up, boost::asio::buffer(buf, len), boost::asio::transfer_all(), std::bind(&TCPIPPipe::HandleUpstreamWrite, @@ -97,14 +96,14 @@ namespace client std::placeholders::_1) ); } else { - LogPrint(eLogError, "tcpip pipe upstream socket null"); + LogPrint(eLogError, "TCPIPPipe: upstream write: no socket"); } } void TCPIPPipe::DownstreamWrite(const uint8_t * buf, size_t len) { if (m_down) { - LogPrint(eLogDebug, "TCPIPPipe: write downstream ", (int)len); + LogPrint(eLogDebug, "TCPIPPipe: downstream: ", (int) len, " bytes written"); boost::asio::async_write(*m_down, boost::asio::buffer(buf, len), boost::asio::transfer_all(), std::bind(&TCPIPPipe::HandleDownstreamWrite, @@ -112,16 +111,16 @@ namespace client std::placeholders::_1) ); } else { - LogPrint(eLogError, "tcpip pipe downstream socket null"); + LogPrint(eLogError, "TCPIPPipe: downstream write: no socket"); } } void TCPIPPipe::HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) { - LogPrint(eLogDebug, "TCPIPPipe downstream got ", (int) bytes_transfered); + LogPrint(eLogDebug, "TCPIPPipe: downstream: ", (int) bytes_transfered, " bytes received"); if (ecode) { - LogPrint(eLogError, "TCPIPPipe Downstream read error:" , ecode.message()); + LogPrint(eLogError, "TCPIPPipe: downstream read error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } else { @@ -135,7 +134,7 @@ namespace client void TCPIPPipe::HandleDownstreamWrite(const boost::system::error_code & ecode) { if (ecode) { - LogPrint(eLogError, "TCPIPPipe Downstream write error:" , ecode.message()); + LogPrint(eLogError, "TCPIPPipe: downstream write error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } @@ -143,7 +142,7 @@ namespace client void TCPIPPipe::HandleUpstreamWrite(const boost::system::error_code & ecode) { if (ecode) { - LogPrint(eLogError, "TCPIPPipe Upstream write error:" , ecode.message()); + LogPrint(eLogError, "TCPIPPipe: upstream write error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } @@ -151,9 +150,9 @@ namespace client void TCPIPPipe::HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) { - LogPrint(eLogDebug, "TCPIPPipe upstream got ", (int) bytes_transfered); + LogPrint(eLogDebug, "TCPIPPipe: upstream ", (int) bytes_transfered, , " bytes received"); if (ecode) { - LogPrint(eLogError, "TCPIPPipe Upstream read error:" , ecode.message()); + LogPrint(eLogError, "TCPIPPipe: upstream read error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } else { @@ -206,6 +205,5 @@ namespace client LogPrint (eLogError, "I2PService: ", GetName(), " closing socket on accept because: ", ecode.message ()); } } - } } From f66f4ffee6f845ecf4c3c6218051bec393e724bd Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 31 May 2016 00:00:00 +0000 Subject: [PATCH 1333/6300] * add generic changelog (#502) --- ChangeLog | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 ChangeLog diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 00000000..9a32e42f --- /dev/null +++ b/ChangeLog @@ -0,0 +1,86 @@ +# for this file format description, +# see https://github.com/olivierlacan/keep-a-changelog + +## [2.8.0] - UNRELEASED +### Changed +- Proxy refactoring & speedup +- I2PControl refactoring & fixes (proper jsonrpc responses on errors) +- boost::regex no more needed + +### Fixed +- initscripts: added openrc one, in sysv-ish make I2PD_PORT optional + +## [2.7.0] - 2016-05-18 +### Added +- Precomputed El-Gamal/DH tables +- Configurable limit of transit tunnels + +### Changed +- Speed-up of assymetric crypto for non-x64 platforms +- Refactoring of web-console + +## [2.6.0] - 2016-03-31 +### Added +- Gracefull shutdown on SIGINT +- Numeric bandwidth limits (was: by router class) +- Jumpservices in web-console +- Logging to syslog +- Tray icon for windows application + +### Changed +- Logs refactoring +- Improved statistics in web-console + +### Deprecated: +- Renamed main/tunnels config files (will use old, if found, but emits warning) + +## [2.5.1] - 2016-03-10 +### Fixed +- Doesn't create ~/.i2pd dir if missing + +## [2.5.0] - 2016-03-04 +### Added +- IRC server tunnels +- SOCKS outproxy support +- Support for gzipped addressbook updates +- Support for router families + +### Changed +- Shared RTT/RTO between streams +- Filesystem work refactoring + +## [2.4.0] - 2016-02-03 +### Added +- X-I2P-* headers for server http-tunnels +- I2CP options for I2P tunnels +- Show I2P tunnels in webconsole + +### Changed +- Refactoring of cmdline/config parsing + +## [2.3.0] - 2016-01-12 +### Added +- Support for new router bandwidth class codes (P and X) +- I2PControl supports external webui +- Added --pidfile and --notransit parameters +- Ability to specify signature type for i2p tunnel + +### Changed +- Fixed multiple floodfill-related bugs +- New webconsole layout + +## [2.2.0] - 2015-12-22 +### Added +- Ability to connect to router without ip via introducer + +### Changed +- Persist temporary encryption keys for local destinations +- Performance improvements for EdDSA +- New addressbook structure + +## [2.1.0] - 2015-11-12 +### Added +- Implementation of EdDSA + +### Changed +- EdDSA is default signature type for new RouterInfos From f9718bccb9c06e94ee412afdb5a378f8acfbeff6 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 31 May 2016 00:00:00 +0000 Subject: [PATCH 1334/6300] * update debian changelog (closes #502) --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 52bca793..5ac732e0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.7.0-1) unstable; urgency=low + + * updated to version 2.7.0/0.9.25 + + -- hagen Wed, 18 May 2016 01:11:04 +0000 + i2pd (2.2.0-2) unstable; urgency=low * updated to version 2.2.0 From 846ff46b2e2bf243411bfc2586f37b08e2e725ec Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 May 2016 21:42:25 -0400 Subject: [PATCH 1335/6300] fixed build error --- I2PService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PService.cpp b/I2PService.cpp index 6a0cd291..9bbe1521 100644 --- a/I2PService.cpp +++ b/I2PService.cpp @@ -150,7 +150,7 @@ namespace client void TCPIPPipe::HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) { - LogPrint(eLogDebug, "TCPIPPipe: upstream ", (int) bytes_transfered, , " bytes received"); + LogPrint(eLogDebug, "TCPIPPipe: upstream ", (int)bytes_transfered, " bytes received"); if (ecode) { LogPrint(eLogError, "TCPIPPipe: upstream read error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) From 025eec1782994d01c46a28ac9be973be5a8200dd Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 31 May 2016 11:54:45 -0400 Subject: [PATCH 1336/6300] I2CP configuration --- ClientContext.cpp | 26 +++++++++++++ Config.cpp | 8 ++++ I2CP.cpp | 98 +++++++++++++++++++++++++++++++++++++++++++++-- I2CP.h | 23 +++++++++++ 4 files changed, 151 insertions(+), 4 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index df0de4e5..db1c8e6a 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -114,6 +114,24 @@ namespace client } } + // I2CP + bool i2cp; i2p::config::GetOption("i2cp.enabled", i2cp); + if (i2cp) + { + std::string i2cpAddr; i2p::config::GetOption("i2cp.address", i2cpAddr); + uint16_t i2cpPort; i2p::config::GetOption("i2cp.port", i2cpPort); + LogPrint(eLogInfo, "Clients: starting I2CP at ", i2cpAddr, ":", i2cpPort); + try + { + m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort); + m_I2CPServer->Start (); + } + catch (std::exception& e) + { + LogPrint(eLogError, "Clients: Exception in I2CP: ", e.what()); + } + } + m_AddressBook.StartResolvers (); } @@ -165,6 +183,14 @@ namespace client m_BOBCommandChannel = nullptr; } + if (m_I2CPServer) + { + LogPrint(eLogInfo, "Clients: stopping I2CP"); + m_I2CPServer->Stop (); + delete m_I2CPServer; + m_I2CPServer = nullptr; + } + LogPrint(eLogInfo, "Clients: stopping AddressBook"); m_AddressBook.Stop (); for (auto it: m_Destinations) diff --git a/Config.cpp b/Config.cpp index 44dec286..d9b4091d 100644 --- a/Config.cpp +++ b/Config.cpp @@ -178,6 +178,13 @@ namespace config { ("bob.port", value()->default_value(2827), "BOB listen port") ; + options_description i2cp("I2CP options"); + bob.add_options() + ("i2cp.enabled", value()->default_value(false), "Enable or disable I2CP") + ("i2cp.address", value()->default_value("127.0.0.1"), "I2CP listen address") + ("i2cp.port", value()->default_value(7654), "I2CP listen port") + ; + options_description i2pcontrol("I2PControl options"); i2pcontrol.add_options() ("i2pcontrol.enabled", value()->default_value(false), "Enable or disable I2P Control Protocol") @@ -207,6 +214,7 @@ namespace config { .add(socksproxy) .add(sam) .add(bob) + .add(i2cp) .add(i2pcontrol) .add(precomputation) ; diff --git a/I2CP.cpp b/I2CP.cpp index 8d0bedf1..94d5db7f 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -61,9 +61,9 @@ namespace client I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), - m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0), - m_SessionID (0) + m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0) { + RAND_bytes ((uint8_t *)&m_SessionID, 2); ReadProtocolByte (); } @@ -72,6 +72,10 @@ namespace client delete[] m_NextMessage; } + void I2CPSession::Close () + { + } + void I2CPSession::ReadProtocolByte () { if (m_Socket) @@ -148,6 +152,12 @@ namespace client void I2CPSession::Terminate () { + if (m_Destination) + { + m_Destination->Stop (); + m_Destination = nullptr; + } + m_Owner.RemoveSession (GetSessionID ()); } void I2CPSession::SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len) @@ -212,7 +222,7 @@ namespace client if (identity->Verify (buf, offset, buf + offset)) // signature { m_Destination = std::make_shared(*this, identity, false); - RAND_bytes ((uint8_t *)&m_SessionID, 2); + m_Destination->Start (); SendSessionStatusMessage (1); // created } else @@ -264,7 +274,9 @@ namespace client LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } - I2CPServer::I2CPServer (const std::string& interface, int port) + I2CPServer::I2CPServer (const std::string& interface, int port): + m_IsRunning (false), m_Thread (nullptr), + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(interface), port)) { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; @@ -272,6 +284,84 @@ namespace client m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler; m_MessagesHandlers[I2CP_SEND_MESSAGE_MESSAGE] = &I2CPSession::SendMessageMessageHandler; } + + I2CPServer::~I2CPServer () + { + if (m_IsRunning) + Stop (); + } + + void I2CPServer::Start () + { + Accept (); + m_IsRunning = true; + m_Thread = new std::thread (std::bind (&I2CPServer::Run, this)); + } + + void I2CPServer::Stop () + { + m_IsRunning = false; + m_Acceptor.cancel (); + for (auto it: m_Sessions) + it.second->Close (); + m_Sessions.clear (); + m_Service.stop (); + if (m_Thread) + { + m_Thread->join (); + delete m_Thread; + m_Thread = nullptr; + } + } + + void I2CPServer::Run () + { + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "I2CP: runtime exception: ", ex.what ()); + } + } + } + + void I2CPServer::Accept () + { + auto newSocket = std::make_shared (m_Service); + m_Acceptor.async_accept (*newSocket, std::bind (&I2CPServer::HandleAccept, this, + std::placeholders::_1, newSocket)); + } + + void I2CPServer::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) + { + if (!ecode && socket) + { + boost::system::error_code ec; + auto ep = socket->remote_endpoint (ec); + if (!ec) + { + LogPrint (eLogDebug, "I2CP: new connection from ", ep); + auto session = std::make_shared(*this, socket); + m_Sessions[session->GetSessionID ()] = session; + } + else + LogPrint (eLogError, "I2CP: incoming connection error ", ec.message ()); + } + else + LogPrint (eLogError, "I2CP: accept error: ", ecode.message ()); + + if (ecode != boost::asio::error::operation_aborted) + Accept (); + } + + void I2CPServer::RemoveSession (uint16_t sessionID) + { + m_Sessions.erase (sessionID); + } } } diff --git a/I2CP.h b/I2CP.h index fca3317d..74ccb3f2 100644 --- a/I2CP.h +++ b/I2CP.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include "Destination.h" @@ -72,6 +74,7 @@ namespace client ~I2CPSession (); uint16_t GetSessionID () const { return m_SessionID; }; + void Close (); void SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len); // message handlers @@ -111,10 +114,30 @@ namespace client public: I2CPServer (const std::string& interface, int port); + ~I2CPServer (); + + void Start (); + void Stop (); + boost::asio::io_service& GetService () { return m_Service; }; + + void RemoveSession (uint16_t sessionID); + + private: + + void Run (); + + void Accept (); + void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); private: I2CPMessageHandler m_MessagesHandlers[256]; + std::map > m_Sessions; + + bool m_IsRunning; + std::thread * m_Thread; + boost::asio::io_service m_Service; + boost::asio::ip::tcp::acceptor m_Acceptor; public: From f62d25fa5f9fc36c9e048df43eaf88169f18339b Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 1 Jun 2016 00:00:00 +0000 Subject: [PATCH 1337/6300] * Config.cpp : fix wrong group for options & code style --- Config.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Config.cpp b/Config.cpp index d9b4091d..da4f160b 100644 --- a/Config.cpp +++ b/Config.cpp @@ -178,11 +178,11 @@ namespace config { ("bob.port", value()->default_value(2827), "BOB listen port") ; - options_description i2cp("I2CP options"); - bob.add_options() + options_description i2cp("I2CP options"); + i2cp.add_options() ("i2cp.enabled", value()->default_value(false), "Enable or disable I2CP") ("i2cp.address", value()->default_value("127.0.0.1"), "I2CP listen address") - ("i2cp.port", value()->default_value(7654), "I2CP listen port") + ("i2cp.port", value()->default_value(7654), "I2CP listen port") ; options_description i2pcontrol("I2PControl options"); @@ -208,15 +208,15 @@ namespace config { m_OptionsDesc .add(general) - .add(limits) .add(httpserver) .add(httpproxy) .add(socksproxy) .add(sam) .add(bob) - .add(i2cp) + .add(i2cp) .add(i2pcontrol) - .add(precomputation) + .add(precomputation) + .add(limits) ; } From 1b2ac38a507c16a47474145b53f865084f5229dd Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 1 Jun 2016 00:00:00 +0000 Subject: [PATCH 1338/6300] * fix compilation warnings --- AddressBook.cpp | 2 +- Daemon.cpp | 4 ++-- Daemon.h | 2 -- DaemonLinux.cpp | 2 +- DaemonWin32.cpp | 2 +- HTTP.cpp | 3 +-- HTTPServer.cpp | 2 +- NetDb.cpp | 3 ++- RouterInfo.cpp | 2 +- Transports.cpp | 2 +- Tunnel.cpp | 2 +- Tunnel.h | 2 +- 12 files changed, 13 insertions(+), 15 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 5a07b3c8..0dbb42d7 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -159,7 +159,7 @@ namespace client int AddressBookFilesystemStorage::Save (const std::map& addresses) { - if (addresses.size() == 0) { + if (addresses.empty()) { LogPrint(eLogWarning, "Addressbook: not saving empty addressbook"); return 0; } diff --git a/Daemon.cpp b/Daemon.cpp index c98bce05..7ca28a6f 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -45,10 +45,10 @@ namespace i2p #endif }; - Daemon_Singleton::Daemon_Singleton() : running(1), d(*new Daemon_Singleton_Private()) {}; + Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {} Daemon_Singleton::~Daemon_Singleton() { delete &d; - }; + } bool Daemon_Singleton::IsService () const { diff --git a/Daemon.h b/Daemon.h index 031686f7..977d9258 100644 --- a/Daemon.h +++ b/Daemon.h @@ -22,9 +22,7 @@ namespace i2p virtual bool stop(); virtual void run () {}; - bool isLogging; bool isDaemon; - bool running; protected: diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index b408fc70..a3848c8b 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -45,7 +45,7 @@ namespace i2p { bool DaemonLinux::start() { - if (isDaemon == 1) + if (isDaemon) { pid_t pid; pid = fork(); diff --git a/DaemonWin32.cpp b/DaemonWin32.cpp index a6d91da4..3afb70ce 100644 --- a/DaemonWin32.cpp +++ b/DaemonWin32.cpp @@ -45,7 +45,7 @@ namespace i2p return false; } - if (isDaemon == 1) + if (isDaemon) { LogPrint(eLogDebug, "Daemon: running as service"); I2PService service(SERVICE_NAME); diff --git a/HTTP.cpp b/HTTP.cpp index 3f6fd937..ee1010ec 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -406,11 +406,10 @@ namespace http { bool MergeChunkedResponse (std::istream& in, std::ostream& out) { std::string hexLen; - long int len; while (!in.eof ()) { std::getline (in, hexLen); errno = 0; - len = strtoul(hexLen.c_str(), (char **) NULL, 16); + long int len = strtoul(hexLen.c_str(), (char **) NULL, 16); if (errno != 0) return false; /* conversion error */ if (len == 0) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 2af92057..fba4a1be 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -667,7 +667,7 @@ namespace http { i2p::config::GetOption("http.auth", needAuth); i2p::config::GetOption("http.user", user); i2p::config::GetOption("http.pass", pass); - }; + } void HTTPConnection::Receive () { diff --git a/NetDb.cpp b/NetDb.cpp index 3aeff92f..d2afc50a 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -849,7 +849,8 @@ namespace data template std::shared_ptr NetDb::GetRandomRouter (Filter filter) const { - if (!m_RouterInfos.size ()) return 0; + if (m_RouterInfos.empty()) + return 0; uint32_t ind = rand () % m_RouterInfos.size (); for (int j = 0; j < 2; j++) { diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 2e76127c..0ef2f623 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -353,7 +353,7 @@ namespace data if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable if (m_Caps & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable - SetProperty ("caps", caps.c_str ()); + SetProperty ("caps", caps); } void RouterInfo::WriteToStream (std::ostream& s) diff --git a/Transports.cpp b/Transports.cpp index 057e2472..2d3d423f 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -606,7 +606,7 @@ namespace transport std::shared_ptr Transports::GetRandomPeer () const { - if (!m_Peers.size ()) return nullptr; + if (m_Peers.empty ()) return nullptr; std::unique_lock l(m_PeersMutex); auto it = m_Peers.begin (); std::advance (it, rand () % m_Peers.size ()); diff --git a/Tunnel.cpp b/Tunnel.cpp index ebaf98c8..1403330b 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -358,7 +358,7 @@ namespace tunnel std::shared_ptr Tunnels::GetNextOutboundTunnel () { - if (!m_OutboundTunnels.size ()) return nullptr; + if (m_OutboundTunnels.empty ()) return nullptr; uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0; std::shared_ptr tunnel; for (auto it: m_OutboundTunnels) diff --git a/Tunnel.h b/Tunnel.h index fe6022ac..43417e5d 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -73,7 +73,7 @@ namespace tunnel bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); - virtual void Print (std::stringstream& s) const {}; + virtual void Print (std::stringstream&) const {}; // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); From f7ca44cad833060de81008d06a4928be101ac440 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 1 Jun 2016 00:00:00 +0000 Subject: [PATCH 1339/6300] * fix compile warnings: reopen() usage --- DaemonLinux.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index a3848c8b..1cc0fa85 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -73,10 +73,10 @@ namespace i2p return false; } - // close stdin/stdout/stderr descriptors - freopen("/dev/null", "r", stdin); - freopen("/dev/null", "w", stdout); - freopen("/dev/null", "w", stderr); + // point std{in,out,err} descriptors to /dev/null + stdin = freopen("/dev/null", "r", stdin); + stdout = freopen("/dev/null", "w", stdout); + stderr = freopen("/dev/null", "w", stderr); } // Pidfile From ca2e148ad7388c68891b7ec7684bb7d240913002 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 1 Jun 2016 00:00:00 +0000 Subject: [PATCH 1340/6300] * enable -Wextra for linux builds --- Makefile.linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.linux b/Makefile.linux index 70307267..e00fd705 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -1,5 +1,5 @@ # set defaults instead redefine -CXXFLAGS ?= -g -Wall +CXXFLAGS ?= -g -Wall -Wextra -Wno-unused-parameter -pedantic INCFLAGS ?= ## NOTE: The NEEDED_CXXFLAGS are here so that custom CXXFLAGS can be specified at build time From 8589493581b761c5764a233b2fb246e7a58e1d94 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 1 Jun 2016 00:00:00 +0000 Subject: [PATCH 1341/6300] * add test for MergeChunkedResponse() (#432) --- tests/Makefile | 3 ++- tests/test-http-merge_chunked.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 tests/test-http-merge_chunked.cpp diff --git a/tests/Makefile b/tests/Makefile index 199b7353..957d4632 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,6 +1,7 @@ CXXFLAGS += -Wall -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -TESTS = test-http-url test-http-req test-http-res test-http-url_decode +TESTS = test-http-url test-http-req test-http-res test-http-url_decode \ + test-http-merge_chunked all: $(TESTS) run diff --git a/tests/test-http-merge_chunked.cpp b/tests/test-http-merge_chunked.cpp new file mode 100644 index 00000000..ba587a45 --- /dev/null +++ b/tests/test-http-merge_chunked.cpp @@ -0,0 +1,25 @@ +#include +#include "../HTTP.h" + +using namespace i2p::http; + +int main() { + const char *buf = + "4\r\n" + "HTTP\r\n" + "A\r\n" + " response \r\n" + "E\r\n" + "with \r\n" + "chunks.\r\n" + "0\r\n" + "\r\n" + ; + std::stringstream in(buf); + std::stringstream out; + + assert(MergeChunkedResponse(in, out) == true); + assert(out.str() == "HTTP response with \r\nchunks."); + + return 0; +} From cd237219e451b9bec8e13bc852c6726da92e02bc Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 1 Jun 2016 00:00:00 +0000 Subject: [PATCH 1342/6300] * extract unused image to separate file --- HTTPServer.cpp | 150 ------------------------------------------ HTTPServer.h | 1 - docs/itoopieImage.png | Bin 0 -> 8712 bytes 3 files changed, 151 deletions(-) create mode 100644 docs/itoopieImage.png diff --git a/HTTPServer.cpp b/HTTPServer.cpp index fba4a1be..7b7b8bcc 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -27,156 +27,6 @@ namespace i2p { namespace http { - const char *itoopieImage = - "\"ICToopie"; - const char *itoopieFavicon = "data:image/png;base64," "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv" diff --git a/HTTPServer.h b/HTTPServer.h index 2635c3be..bf7f5c65 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -3,7 +3,6 @@ namespace i2p { namespace http { - extern const char *itoopieImage; extern const char *itoopieFavicon; const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192; diff --git a/docs/itoopieImage.png b/docs/itoopieImage.png new file mode 100644 index 0000000000000000000000000000000000000000..a5dc7b680ba9dee30161ffb1c9fcd17bbd6ddd84 GIT binary patch literal 8712 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 literal 0 HcmV?d00001 From 689432f627125644c8589e98577f700880bf8e5f Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 31 May 2016 21:37:32 -0400 Subject: [PATCH 1343/6300] fixed typo --- Config.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Config.cpp b/Config.cpp index da4f160b..e6d44d59 100644 --- a/Config.cpp +++ b/Config.cpp @@ -178,11 +178,11 @@ namespace config { ("bob.port", value()->default_value(2827), "BOB listen port") ; - options_description i2cp("I2CP options"); + options_description i2cp("I2CP options"); i2cp.add_options() ("i2cp.enabled", value()->default_value(false), "Enable or disable I2CP") ("i2cp.address", value()->default_value("127.0.0.1"), "I2CP listen address") - ("i2cp.port", value()->default_value(7654), "I2CP listen port") + ("i2cp.port", value()->default_value(7654), "I2CP listen port") ; options_description i2pcontrol("I2PControl options"); @@ -208,15 +208,15 @@ namespace config { m_OptionsDesc .add(general) + .add(limits) .add(httpserver) .add(httpproxy) .add(socksproxy) .add(sam) .add(bob) - .add(i2cp) + .add(i2cp) .add(i2pcontrol) - .add(precomputation) - .add(limits) + .add(precomputation) ; } From 153d883aebffc2066def97802d7dc1f6571c3df1 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Jun 2016 10:05:40 -0400 Subject: [PATCH 1344/6300] SessionDestoryedMessage --- I2CP.cpp | 21 ++++++++++++++++++--- I2CP.h | 5 ++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 94d5db7f..fd98ce44 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -64,7 +64,6 @@ namespace client m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0) { RAND_bytes ((uint8_t *)&m_SessionID, 2); - ReadProtocolByte (); } I2CPSession::~I2CPSession () @@ -72,7 +71,12 @@ namespace client delete[] m_NextMessage; } - void I2CPSession::Close () + void I2CPSession::Start () + { + ReadProtocolByte (); + } + + void I2CPSession::Stop () { } @@ -216,6 +220,7 @@ namespace client auto identity = std::make_shared(); size_t offset = identity->FromBuffer (buf, len); uint16_t optionsSize = bufbe16toh (buf + offset); + offset += 2; // TODO: extract options offset += optionsSize; offset += 8; // date @@ -224,6 +229,7 @@ namespace client m_Destination = std::make_shared(*this, identity, false); m_Destination->Start (); SendSessionStatusMessage (1); // created + LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created"); } else { @@ -232,6 +238,13 @@ namespace client } } + void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len) + { + SendSessionStatusMessage (0); // destroy + LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " destroyed"); + Terminate (); + } + void I2CPSession::SendSessionStatusMessage (uint8_t status) { uint8_t buf[3]; @@ -281,6 +294,7 @@ namespace client memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; m_MessagesHandlers[I2CP_CREATE_SESSION_MESSAGE] = &I2CPSession::CreateSessionMessageHandler; + m_MessagesHandlers[I2CP_DESTROY_SESSION_MESSAGE] = &I2CPSession::DestroySessionMessageHandler; m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler; m_MessagesHandlers[I2CP_SEND_MESSAGE_MESSAGE] = &I2CPSession::SendMessageMessageHandler; } @@ -303,7 +317,7 @@ namespace client m_IsRunning = false; m_Acceptor.cancel (); for (auto it: m_Sessions) - it.second->Close (); + it.second->Stop (); m_Sessions.clear (); m_Service.stop (); if (m_Thread) @@ -347,6 +361,7 @@ namespace client LogPrint (eLogDebug, "I2CP: new connection from ", ep); auto session = std::make_shared(*this, socket); m_Sessions[session->GetSessionID ()] = session; + session->Start (); } else LogPrint (eLogError, "I2CP: incoming connection error ", ec.message ()); diff --git a/I2CP.h b/I2CP.h index 74ccb3f2..f68c4809 100644 --- a/I2CP.h +++ b/I2CP.h @@ -32,6 +32,7 @@ namespace client const uint8_t I2CP_SET_DATE_MESSAGE = 33; const uint8_t I2CP_CREATE_SESSION_MESSAGE = 1; const uint8_t I2CP_SESSION_STATUS_MESSAGE = 20; + const uint8_t I2CP_DESTROY_SESSION_MESSAGE = 3; const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37; const uint8_t I2CP_CREATE_LEASESET_MESSAGE = 4; const uint8_t I2CP_SEND_MESSAGE_MESSAGE = 5; @@ -73,13 +74,15 @@ namespace client I2CPSession (I2CPServer& owner, std::shared_ptr socket); ~I2CPSession (); + void Start (); + void Stop (); uint16_t GetSessionID () const { return m_SessionID; }; - void Close (); void SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len); // message handlers void GetDateMessageHandler (const uint8_t * buf, size_t len); void CreateSessionMessageHandler (const uint8_t * buf, size_t len); + void DestroySessionMessageHandler (const uint8_t * buf, size_t len); void CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len); void SendMessageMessageHandler (const uint8_t * buf, size_t len); From 6538a2e673bbf14a56e9fbf52679b039bd4eee20 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Jun 2016 11:11:18 -0400 Subject: [PATCH 1345/6300] HostLookupMessage --- I2CP.cpp | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- I2CP.h | 6 +++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index fd98ce44..7cef9013 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -287,6 +287,66 @@ namespace client LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } + void I2CPSession::HostLookupMessageHandler (const uint8_t * buf, size_t len) + { + uint16_t sessionID = bufbe16toh (buf); + if (sessionID == m_SessionID) + { + uint32_t requestID = bufbe32toh (buf + 2); + //uint32_t timeout = bufbe32toh (buf + 6); + if (!buf[10]) // request type = 0 (hash) + { + if (m_Destination) + { + auto ls = m_Destination->FindLeaseSet (buf + 11); + if (ls) + SendHostReplyMessage (requestID, ls->GetIdentity ()); + else + { + auto s = shared_from_this (); + m_Destination->RequestDestination (buf + 11, + [s, requestID](std::shared_ptr leaseSet) + { + s->SendHostReplyMessage (requestID, leaseSet ? leaseSet->GetIdentity () : nullptr); + }); + } + } + else + SendHostReplyMessage (requestID, nullptr); + } + else + { + LogPrint (eLogError, "I2CP: request type ", (int)buf[8], " is not supported"); + SendHostReplyMessage (requestID, nullptr); + } + } + else + LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); + } + + void I2CPSession::SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity) + { + if (identity) + { + size_t l = identity->GetFullLen () + 7; + uint8_t * buf = new uint8_t[l]; + htobe16buf (buf, m_SessionID); + htobe32buf (buf + 2, requestID); + buf[6] = 0; // result code + identity->ToBuffer (buf + 7, l - 7); + SendI2CPMessage (I2CP_HOST_REPLY_MESSAGE, buf, l); + delete[] buf; + } + else + { + uint8_t buf[7]; + htobe16buf (buf, m_SessionID); + htobe32buf (buf + 2, requestID); + buf[6] = 1; // result code + SendI2CPMessage (I2CP_HOST_REPLY_MESSAGE, buf, 7); + } + } + I2CPServer::I2CPServer (const std::string& interface, int port): m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(interface), port)) @@ -296,7 +356,8 @@ namespace client m_MessagesHandlers[I2CP_CREATE_SESSION_MESSAGE] = &I2CPSession::CreateSessionMessageHandler; m_MessagesHandlers[I2CP_DESTROY_SESSION_MESSAGE] = &I2CPSession::DestroySessionMessageHandler; m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler; - m_MessagesHandlers[I2CP_SEND_MESSAGE_MESSAGE] = &I2CPSession::SendMessageMessageHandler; + m_MessagesHandlers[I2CP_SEND_MESSAGE_MESSAGE] = &I2CPSession::SendMessageMessageHandler; + m_MessagesHandlers[I2CP_HOST_LOOKUP_MESSAGE] = &I2CPSession::HostLookupMessageHandler; } I2CPServer::~I2CPServer () diff --git a/I2CP.h b/I2CP.h index f68c4809..f3a88c9a 100644 --- a/I2CP.h +++ b/I2CP.h @@ -35,7 +35,9 @@ namespace client const uint8_t I2CP_DESTROY_SESSION_MESSAGE = 3; const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37; const uint8_t I2CP_CREATE_LEASESET_MESSAGE = 4; - const uint8_t I2CP_SEND_MESSAGE_MESSAGE = 5; + const uint8_t I2CP_SEND_MESSAGE_MESSAGE = 5; + const uint8_t I2CP_HOST_LOOKUP_MESSAGE = 38; + const uint8_t I2CP_HOST_REPLY_MESSAGE = 39; class I2CPSession; class I2CPDestination: public LeaseSetDestination @@ -85,6 +87,7 @@ namespace client void DestroySessionMessageHandler (const uint8_t * buf, size_t len); void CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len); void SendMessageMessageHandler (const uint8_t * buf, size_t len); + void HostLookupMessageHandler (const uint8_t * buf, size_t len); private: @@ -99,6 +102,7 @@ namespace client size_t PutString (uint8_t * buf, size_t len, const std::string& str); void SendSessionStatusMessage (uint8_t status); + void SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity); private: From d79c6b8f06d4435016ccee337a1de9a1ddf5b6da Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Jun 2016 14:38:13 -0400 Subject: [PATCH 1346/6300] MessagePayloadMessage --- I2CP.cpp | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- I2CP.h | 14 ++++++++++-- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 7cef9013..5d0f2865 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -7,6 +7,7 @@ */ #include +#include #include #include "I2PEndian.h" #include "Log.h" @@ -29,6 +30,13 @@ namespace client memcpy (m_EncryptionPrivateKey, key, 256); } + void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len) + { + uint32_t length = bufbe32toh (buf); + if (length > len - 4) length = len - 4; + m_Owner.SendMessagePayloadMessage (buf + 4, length); + } + void I2CPDestination::CreateNewLeaseSet (std::vector > tunnels) { i2p::data::LocalLeaseSet ls (m_Identity, m_EncryptionPrivateKey, tunnels); // we don't care about encryption key @@ -38,7 +46,6 @@ namespace client htobe16buf (leases - 3, m_Owner.GetSessionID ()); size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); m_Owner.SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); - } void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) @@ -56,12 +63,50 @@ namespace client memcpy (buf + 4, payload, len); msg->len += len + 4; msg->FillI2NPMessageHeader (eI2NPData); - // TODO: send + auto remote = FindLeaseSet (ident); + if (remote) + GetService ().post (std::bind (&I2CPDestination::SendMsg, GetSharedFromThis (), msg, remote)); + else + { + auto s = GetSharedFromThis (); + RequestDestination (ident, + [s, msg](std::shared_ptr ls) + { + if (ls) s->SendMsg (msg, ls); + }); + } + } + + void I2CPDestination::SendMsg (std::shared_ptr msg, std::shared_ptr remote) + { + auto outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); + auto leases = remote->GetNonExpiredLeases (); + if (!leases.empty () && outboundTunnel) + { + std::vector msgs; + uint32_t i = rand () % leases.size (); + auto garlic = WrapMessage (remote, msg, true); + msgs.push_back (i2p::tunnel::TunnelMessageBlock + { + i2p::tunnel::eDeliveryTypeTunnel, + leases[i]->tunnelGateway, leases[i]->tunnelID, + garlic + }); + outboundTunnel->SendTunnelDataMsg (msgs); + } + else + { + if (outboundTunnel) + LogPrint (eLogWarning, "I2CP: Failed to send message. All leases expired"); + else + LogPrint (eLogWarning, "I2CP: Failed to send message. No outbound tunnels"); + } } I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), - m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0) + m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0), + m_MessageID (0) { RAND_bytes ((uint8_t *)&m_SessionID, 2); } @@ -347,6 +392,21 @@ namespace client } } + void I2CPSession::SendMessagePayloadMessage (const uint8_t * payload, size_t len) + { + // we don't use SendI2CPMessage to eliminate additional copy + auto l = len + 6 + I2CP_HEADER_SIZE; + uint8_t * buf = new uint8_t[l]; + htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 6); + buf[I2CP_HEADER_TYPE_OFFSET] = I2CP_MESSAGE_PAYLOAD_MESSAGE; + htobe16buf (buf + I2CP_HEADER_SIZE, m_SessionID); + htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++); + memcpy (buf + I2CP_HEADER_SIZE + 6, payload, len); + boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), + std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), + std::placeholders::_1, std::placeholders::_2, buf)); + } + I2CPServer::I2CPServer (const std::string& interface, int port): m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(interface), port)) diff --git a/I2CP.h b/I2CP.h index f3a88c9a..b3693d3d 100644 --- a/I2CP.h +++ b/I2CP.h @@ -36,6 +36,7 @@ namespace client const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37; const uint8_t I2CP_CREATE_LEASESET_MESSAGE = 4; const uint8_t I2CP_SEND_MESSAGE_MESSAGE = 5; + const uint8_t I2CP_MESSAGE_PAYLOAD_MESSAGE = 31; const uint8_t I2CP_HOST_LOOKUP_MESSAGE = 38; const uint8_t I2CP_HOST_REPLY_MESSAGE = 39; @@ -57,9 +58,15 @@ namespace client std::shared_ptr GetIdentity () const { return m_Identity; }; // I2CP - void HandleDataMessage (const uint8_t * buf, size_t len) { /* TODO */ }; + void HandleDataMessage (const uint8_t * buf, size_t len); void CreateNewLeaseSet (std::vector > tunnels); + private: + + std::shared_ptr GetSharedFromThis () + { return std::static_pointer_cast(shared_from_this ()); } + void SendMsg (std::shared_ptr msg, std::shared_ptr remote); + private: I2CPSession& m_Owner; @@ -79,8 +86,10 @@ namespace client void Start (); void Stop (); uint16_t GetSessionID () const { return m_SessionID; }; + void SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len); - + void SendMessagePayloadMessage (const uint8_t * payload, size_t len); // called from I2CPDestination + // message handlers void GetDateMessageHandler (const uint8_t * buf, size_t len); void CreateSessionMessageHandler (const uint8_t * buf, size_t len); @@ -113,6 +122,7 @@ namespace client std::shared_ptr m_Destination; uint16_t m_SessionID; + uint32_t m_MessageID; }; typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); From ace3e86546efcca0f96ced93a02f31f93344901d Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Jun 2016 15:30:57 -0400 Subject: [PATCH 1347/6300] MessageStatusMessage --- I2CP.cpp | 44 ++++++++++++++++++++++++++++++++++---------- I2CP.h | 17 ++++++++++++++--- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 5d0f2865..77330af4 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -55,7 +55,7 @@ namespace client SetLeaseSet (ls); } - void I2CPDestination::SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident) + void I2CPDestination::SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce) { auto msg = NewI2NPMessage (); uint8_t * buf = msg->GetPayload (); @@ -70,14 +70,20 @@ namespace client { auto s = GetSharedFromThis (); RequestDestination (ident, - [s, msg](std::shared_ptr ls) + [s, msg, nonce](std::shared_ptr ls) { - if (ls) s->SendMsg (msg, ls); + if (ls) + { + bool sent = s->SendMsg (msg, ls); + s->m_Owner.SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); + } + else + s->m_Owner.SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLeaseSet); }); } } - void I2CPDestination::SendMsg (std::shared_ptr msg, std::shared_ptr remote) + bool I2CPDestination::SendMsg (std::shared_ptr msg, std::shared_ptr remote) { auto outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); auto leases = remote->GetNonExpiredLeases (); @@ -93,6 +99,7 @@ namespace client garlic }); outboundTunnel->SendTunnelDataMsg (msgs); + return true; } else { @@ -100,13 +107,14 @@ namespace client LogPrint (eLogWarning, "I2CP: Failed to send message. All leases expired"); else LogPrint (eLogWarning, "I2CP: Failed to send message. No outbound tunnels"); + return false; } } I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0), - m_MessageID (0) + m_MessageID (0) { RAND_bytes ((uint8_t *)&m_SessionID, 2); } @@ -298,6 +306,17 @@ namespace client SendI2CPMessage (I2CP_SESSION_STATUS_MESSAGE, buf, 3); } + void I2CPSession::SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status) + { + uint8_t buf[15]; + htobe16buf (buf, m_SessionID); + htobe32buf (buf + 2, m_MessageID++); + buf[6] = (uint8_t)status; + memset (buf + 7, 0, 4); // size + htobe32buf (buf + 11, nonce); + SendI2CPMessage (I2CP_MESSAGE_STATUS_MESSAGE, buf, 15); + } + void I2CPSession::CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); @@ -325,7 +344,11 @@ namespace client { i2p::data::IdentityEx identity; offset += identity.FromBuffer (buf + offset, len - offset); - m_Destination->SendMsgTo (buf + offset, len - offset, identity.GetIdentHash ()); + uint32_t payloadLen = bufbe32toh (buf + offset); + offset += 4; + uint32_t nonce = bufbe32toh (buf + offset + payloadLen); + SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted + m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce); } } else @@ -395,13 +418,14 @@ namespace client void I2CPSession::SendMessagePayloadMessage (const uint8_t * payload, size_t len) { // we don't use SendI2CPMessage to eliminate additional copy - auto l = len + 6 + I2CP_HEADER_SIZE; + auto l = len + 10 + I2CP_HEADER_SIZE; uint8_t * buf = new uint8_t[l]; - htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 6); + htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 10); buf[I2CP_HEADER_TYPE_OFFSET] = I2CP_MESSAGE_PAYLOAD_MESSAGE; htobe16buf (buf + I2CP_HEADER_SIZE, m_SessionID); - htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++); - memcpy (buf + I2CP_HEADER_SIZE + 6, payload, len); + htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++); + htobe32buf (buf + I2CP_HEADER_SIZE + 6, len); + memcpy (buf + I2CP_HEADER_SIZE + 10, payload, len); boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, buf)); diff --git a/I2CP.h b/I2CP.h index b3693d3d..4d50b5ef 100644 --- a/I2CP.h +++ b/I2CP.h @@ -37,9 +37,18 @@ namespace client const uint8_t I2CP_CREATE_LEASESET_MESSAGE = 4; const uint8_t I2CP_SEND_MESSAGE_MESSAGE = 5; const uint8_t I2CP_MESSAGE_PAYLOAD_MESSAGE = 31; + const uint8_t I2CP_MESSAGE_STATUS_MESSAGE = 22; const uint8_t I2CP_HOST_LOOKUP_MESSAGE = 38; const uint8_t I2CP_HOST_REPLY_MESSAGE = 39; + enum I2CPMessageStatus + { + eI2CPMessageStatusAccepted = 1, + eI2CPMessageStatusGuaranteedSuccess = 4, + eI2CPMessageStatusGuaranteedFailure = 5, + eI2CPMessageStatusNoLeaseSet = 21 + }; + class I2CPSession; class I2CPDestination: public LeaseSetDestination { @@ -49,7 +58,7 @@ namespace client void SetEncryptionPrivateKey (const uint8_t * key); void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession - void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident); // called from I2CPSession + void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession protected: @@ -65,7 +74,7 @@ namespace client std::shared_ptr GetSharedFromThis () { return std::static_pointer_cast(shared_from_this ()); } - void SendMsg (std::shared_ptr msg, std::shared_ptr remote); + bool SendMsg (std::shared_ptr msg, std::shared_ptr remote); private: @@ -87,8 +96,10 @@ namespace client void Stop (); uint16_t GetSessionID () const { return m_SessionID; }; + // called from I2CPDestination void SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len); - void SendMessagePayloadMessage (const uint8_t * payload, size_t len); // called from I2CPDestination + void SendMessagePayloadMessage (const uint8_t * payload, size_t len); + void SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status); // message handlers void GetDateMessageHandler (const uint8_t * buf, size_t len); From 26a6c9e932837a3c54e235bd8a9b6327e85e033c Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 2 Jun 2016 13:26:41 -0400 Subject: [PATCH 1348/6300] procee session options --- I2CP.cpp | 38 +++++++++++++++++++++++++++++++++----- I2CP.h | 8 ++++++-- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 77330af4..13a658a1 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -20,8 +20,8 @@ namespace i2p namespace client { - I2CPDestination::I2CPDestination (I2CPSession& owner, std::shared_ptr identity, bool isPublic): - LeaseSetDestination (isPublic), m_Owner (owner), m_Identity (identity) + I2CPDestination::I2CPDestination (I2CPSession& owner, std::shared_ptr identity, bool isPublic, const std::map& params): + LeaseSetDestination (isPublic, ¶ms), m_Owner (owner), m_Identity (identity) { } @@ -141,7 +141,7 @@ namespace client m_Socket->async_read_some (boost::asio::buffer (m_Buffer, 1), [s](const boost::system::error_code& ecode, std::size_t bytes_transferred) { - if (!ecode && bytes_transferred > 0 && s->m_Buffer[0] == I2CP_PRTOCOL_BYTE) + if (!ecode && bytes_transferred > 0 && s->m_Buffer[0] == I2CP_PROTOCOL_BYTE) s->Receive (); else s->Terminate (); @@ -253,6 +253,30 @@ namespace client return l + 1; } + void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping) + // TODO: move to Base.cpp + { + size_t offset = 0; + while (offset < len) + { + auto semicolon = (const uint8_t *)memchr (buf + offset, ';', len - offset); + if (semicolon) + { + auto l = semicolon - buf - offset + 1; + auto equal = (const uint8_t *)memchr (buf + offset, '=', l); + if (equal) + { + auto l1 = equal - buf - offset + 1; + mapping.insert (std::make_pair (std::string ((const char *)(buf + offset), l1 -1), + std::string ((const char *)(buf + offset + l1), l - l1 - 2))); + } + offset += l; + } + else + break; + } + } + void I2CPSession::GetDateMessageHandler (const uint8_t * buf, size_t len) { // get version @@ -274,12 +298,16 @@ namespace client size_t offset = identity->FromBuffer (buf, len); uint16_t optionsSize = bufbe16toh (buf + offset); offset += 2; - // TODO: extract options + + std::map params; + ExtractMapping (buf + offset, optionsSize, params); offset += optionsSize; offset += 8; // date if (identity->Verify (buf, offset, buf + offset)) // signature { - m_Destination = std::make_shared(*this, identity, false); + bool isPublic = true; + if (params[I2CP_PARAM_DONT_PUBLISH_LEASESET] == "false") isPublic = false; + m_Destination = std::make_shared(*this, identity, isPublic, params); m_Destination->Start (); SendSessionStatusMessage (1); // created LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created"); diff --git a/I2CP.h b/I2CP.h index 4d50b5ef..47e6f750 100644 --- a/I2CP.h +++ b/I2CP.h @@ -21,7 +21,7 @@ namespace i2p { namespace client { - const uint8_t I2CP_PRTOCOL_BYTE = 0x2A; + const uint8_t I2CP_PROTOCOL_BYTE = 0x2A; const size_t I2CP_SESSION_BUFFER_SIZE = 4096; const size_t I2CP_HEADER_LENGTH_OFFSET = 0; @@ -49,12 +49,15 @@ namespace client eI2CPMessageStatusNoLeaseSet = 21 }; + // params + const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet "; + class I2CPSession; class I2CPDestination: public LeaseSetDestination { public: - I2CPDestination (I2CPSession& owner, std::shared_ptr identity, bool isPublic); + I2CPDestination (I2CPSession& owner, std::shared_ptr identity, bool isPublic, const std::map& params); void SetEncryptionPrivateKey (const uint8_t * key); void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession @@ -120,6 +123,7 @@ namespace client void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf); std::string ExtractString (const uint8_t * buf, size_t len); size_t PutString (uint8_t * buf, size_t len, const std::string& str); + void ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping); void SendSessionStatusMessage (uint8_t status); void SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity); From aa6bc8042a5edc8beeaa20edfaec7ab2f6ab9bf0 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 2 Jun 2016 15:49:14 -0400 Subject: [PATCH 1349/6300] address lookup --- I2CP.cpp | 50 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 13a658a1..6cdc4ec9 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -13,6 +13,7 @@ #include "Log.h" #include "Timestamp.h" #include "LeaseSet.h" +#include "ClientContext.h" #include "I2CP.h" namespace i2p @@ -390,31 +391,46 @@ namespace client { uint32_t requestID = bufbe32toh (buf + 2); //uint32_t timeout = bufbe32toh (buf + 6); - if (!buf[10]) // request type = 0 (hash) + i2p::data::IdentHash ident; + switch (buf[10]) { - if (m_Destination) + case 0: // hash + ident = i2p::data::IdentHash (buf + 11); + break; + case 1: // address { - auto ls = m_Destination->FindLeaseSet (buf + 11); - if (ls) - SendHostReplyMessage (requestID, ls->GetIdentity ()); - else + auto name = ExtractString (buf + 11, len - 11); + if (!i2p::client::context.GetAddressBook ().GetIdentHash (name, ident)) { - auto s = shared_from_this (); - m_Destination->RequestDestination (buf + 11, - [s, requestID](std::shared_ptr leaseSet) - { - s->SendHostReplyMessage (requestID, leaseSet ? leaseSet->GetIdentity () : nullptr); - }); - } + LogPrint (eLogError, "I2CP: address ", name, " not found"); + SendHostReplyMessage (requestID, nullptr); + return; + } + break; } - else + default: + LogPrint (eLogError, "I2CP: request type ", (int)buf[10], " is not supported"); SendHostReplyMessage (requestID, nullptr); + return; + } + + if (m_Destination) + { + auto ls = m_Destination->FindLeaseSet (ident); + if (ls) + SendHostReplyMessage (requestID, ls->GetIdentity ()); + else + { + auto s = shared_from_this (); + m_Destination->RequestDestination (ident, + [s, requestID](std::shared_ptr leaseSet) + { + s->SendHostReplyMessage (requestID, leaseSet ? leaseSet->GetIdentity () : nullptr); + }); + } } else - { - LogPrint (eLogError, "I2CP: request type ", (int)buf[8], " is not supported"); SendHostReplyMessage (requestID, nullptr); - } } else LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); From 26284260840d6eaefbe6e33dc9442e907095fb2c Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 3 Jun 2016 00:00:00 +0000 Subject: [PATCH 1350/6300] * http proxy : fix converted request (#508) --- HTTPProxy.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index cc534470..265d36e5 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -189,6 +189,12 @@ namespace proxy { return true; } SanitizeHTTPRequest(req); + /* convert proxy-style http req to ordinary one: */ + /* 1) replace Host header, 2) make relative url */ + req.add_header("Host", url.host, true); + url.schema = ""; + url.host = ""; + req.uri = url.to_string(); /* drop original request from input buffer */ m_recv_buf.erase(m_recv_buf.begin(), m_recv_buf.begin() + req_len); From aa764fbd1ccb50ce8ec58c2345eed4f4f4849f5b Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 3 Jun 2016 00:00:00 +0000 Subject: [PATCH 1351/6300] * HTTPProxy: fix converted request (#508) * I2PService: reword log message, to avoid ambiguity --- HTTPProxy.cpp | 5 ++++- I2PService.cpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 265d36e5..6bf33664 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -189,6 +189,9 @@ namespace proxy { return true; } SanitizeHTTPRequest(req); + + std::string dest_host = url.host; + uint16_t dest_host = url.port; /* convert proxy-style http req to ordinary one: */ /* 1) replace Host header, 2) make relative url */ req.add_header("Host", url.host, true); @@ -206,7 +209,7 @@ namespace proxy { /* connect to destination */ GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, - shared_from_this(), std::placeholders::_1), url.host, url.port); + shared_from_this(), std::placeholders::_1), dest_host, dest_port); return true; } diff --git a/I2PService.cpp b/I2PService.cpp index 9bbe1521..4f907f18 100644 --- a/I2PService.cpp +++ b/I2PService.cpp @@ -27,7 +27,7 @@ namespace client m_LocalDestination->CreateStream (streamRequestComplete, identHash, port); else { - LogPrint (eLogWarning, "I2PService: Remote destination ", dest, " not found"); + LogPrint (eLogWarning, "I2PService: Remote destination not found: ", dest); streamRequestComplete (nullptr); } } From e50c35d38c81314be5d674a197334da3df5e86fa Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 3 Jun 2016 00:00:00 +0000 Subject: [PATCH 1352/6300] * fix mistype --- HTTPProxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 6bf33664..934dbbf5 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -191,7 +191,7 @@ namespace proxy { SanitizeHTTPRequest(req); std::string dest_host = url.host; - uint16_t dest_host = url.port; + uint16_t dest_port = url.port; /* convert proxy-style http req to ordinary one: */ /* 1) replace Host header, 2) make relative url */ req.add_header("Host", url.host, true); From c8d6425123e00083d6fb3d674348b9c298dde733 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 3 Jun 2016 11:49:39 -0400 Subject: [PATCH 1353/6300] DestLookupMessage --- I2CP.cpp | 42 ++++++++++++++++++++++++++++++++++++++++-- I2CP.h | 3 +++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 6cdc4ec9..afd10466 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -307,7 +307,7 @@ namespace client if (identity->Verify (buf, offset, buf + offset)) // signature { bool isPublic = true; - if (params[I2CP_PARAM_DONT_PUBLISH_LEASESET] == "false") isPublic = false; + if (params[I2CP_PARAM_DONT_PUBLISH_LEASESET] == "true") isPublic = false; m_Destination = std::make_shared(*this, identity, isPublic, params); m_Destination->Start (); SendSessionStatusMessage (1); // created @@ -459,6 +459,43 @@ namespace client } } + void I2CPSession::DestLookupMessageHandler (const uint8_t * buf, size_t len) + { + if (m_Destination) + { + auto ls = m_Destination->FindLeaseSet (buf); + if (ls) + { + auto l = ls->GetIdentity ()->GetFullLen (); + uint8_t * identBuf = new uint8_t[l]; + ls->GetIdentity ()->ToBuffer (identBuf, l); + SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, identBuf, l); + delete[] identBuf; + } + else + { + auto s = shared_from_this (); + i2p::data::IdentHash ident (buf); + m_Destination->RequestDestination (ident, + [s, ident](std::shared_ptr leaseSet) + { + if (leaseSet) // found + { + auto l = leaseSet->GetIdentity ()->GetFullLen (); + uint8_t * identBuf = new uint8_t[l]; + leaseSet->GetIdentity ()->ToBuffer (identBuf, l); + s->SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, identBuf, l); + delete[] identBuf; + } + else + s->SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, ident, 32); // not found + }); + } + } + else + SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, buf, 32); + } + void I2CPSession::SendMessagePayloadMessage (const uint8_t * payload, size_t len) { // we don't use SendI2CPMessage to eliminate additional copy @@ -485,7 +522,8 @@ namespace client m_MessagesHandlers[I2CP_DESTROY_SESSION_MESSAGE] = &I2CPSession::DestroySessionMessageHandler; m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler; m_MessagesHandlers[I2CP_SEND_MESSAGE_MESSAGE] = &I2CPSession::SendMessageMessageHandler; - m_MessagesHandlers[I2CP_HOST_LOOKUP_MESSAGE] = &I2CPSession::HostLookupMessageHandler; + m_MessagesHandlers[I2CP_HOST_LOOKUP_MESSAGE] = &I2CPSession::HostLookupMessageHandler; + m_MessagesHandlers[I2CP_DEST_LOOKUP_MESSAGE] = &I2CPSession::DestLookupMessageHandler; } I2CPServer::~I2CPServer () diff --git a/I2CP.h b/I2CP.h index 47e6f750..a3e0dde0 100644 --- a/I2CP.h +++ b/I2CP.h @@ -40,6 +40,8 @@ namespace client const uint8_t I2CP_MESSAGE_STATUS_MESSAGE = 22; const uint8_t I2CP_HOST_LOOKUP_MESSAGE = 38; const uint8_t I2CP_HOST_REPLY_MESSAGE = 39; + const uint8_t I2CP_DEST_LOOKUP_MESSAGE = 34; + const uint8_t I2CP_DEST_REPLY_MESSAGE = 35; enum I2CPMessageStatus { @@ -111,6 +113,7 @@ namespace client void CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len); void SendMessageMessageHandler (const uint8_t * buf, size_t len); void HostLookupMessageHandler (const uint8_t * buf, size_t len); + void DestLookupMessageHandler (const uint8_t * buf, size_t len); private: From 444539b8263d899927e26ecc8cf25915ec009eed Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 3 Jun 2016 12:03:36 -0400 Subject: [PATCH 1354/6300] SendMessageExpires --- I2CP.cpp | 6 ++++++ I2CP.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/I2CP.cpp b/I2CP.cpp index afd10466..b2685e80 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -384,6 +384,11 @@ namespace client LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } + void I2CPSession::SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len) + { + SendMessageMessageHandler (buf, len - 8); // ignore flags(2) and expiration(6) + } + void I2CPSession::HostLookupMessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); @@ -522,6 +527,7 @@ namespace client m_MessagesHandlers[I2CP_DESTROY_SESSION_MESSAGE] = &I2CPSession::DestroySessionMessageHandler; m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler; m_MessagesHandlers[I2CP_SEND_MESSAGE_MESSAGE] = &I2CPSession::SendMessageMessageHandler; + m_MessagesHandlers[I2CP_SEND_MESSAGE_EXPIRES_MESSAGE] = &I2CPSession::SendMessageExpiresMessageHandler; m_MessagesHandlers[I2CP_HOST_LOOKUP_MESSAGE] = &I2CPSession::HostLookupMessageHandler; m_MessagesHandlers[I2CP_DEST_LOOKUP_MESSAGE] = &I2CPSession::DestLookupMessageHandler; } diff --git a/I2CP.h b/I2CP.h index a3e0dde0..273dcb65 100644 --- a/I2CP.h +++ b/I2CP.h @@ -36,6 +36,7 @@ namespace client const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37; const uint8_t I2CP_CREATE_LEASESET_MESSAGE = 4; const uint8_t I2CP_SEND_MESSAGE_MESSAGE = 5; + const uint8_t I2CP_SEND_MESSAGE_EXPIRES_MESSAGE = 36; const uint8_t I2CP_MESSAGE_PAYLOAD_MESSAGE = 31; const uint8_t I2CP_MESSAGE_STATUS_MESSAGE = 22; const uint8_t I2CP_HOST_LOOKUP_MESSAGE = 38; @@ -112,6 +113,7 @@ namespace client void DestroySessionMessageHandler (const uint8_t * buf, size_t len); void CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len); void SendMessageMessageHandler (const uint8_t * buf, size_t len); + void SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len); void HostLookupMessageHandler (const uint8_t * buf, size_t len); void DestLookupMessageHandler (const uint8_t * buf, size_t len); From d6bfe7810a3e06fdedb5f1bfeac09db18ada6ae7 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 3 Jun 2016 13:01:12 -0400 Subject: [PATCH 1355/6300] skip SigningPrivateKey --- I2CP.cpp | 1 + I2CP.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index b2685e80..ba6825b8 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -354,6 +354,7 @@ namespace client size_t offset = 2; if (m_Destination) { + offset += m_Destination->GetIdentity ()->GetSigningPrivateKeyLen (); // skip signing private key m_Destination->SetEncryptionPrivateKey (buf + offset); offset += 256; m_Destination->LeaseSetCreated (buf + offset, len - offset); diff --git a/I2CP.h b/I2CP.h index 273dcb65..20bd33a5 100644 --- a/I2CP.h +++ b/I2CP.h @@ -66,12 +66,12 @@ namespace client void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession - protected: - // implements LocalDestination const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; std::shared_ptr GetIdentity () const { return m_Identity; }; + protected: + // I2CP void HandleDataMessage (const uint8_t * buf, size_t len); void CreateNewLeaseSet (std::vector > tunnels); From 667ea43b3ca9b4e711be7e17455b0974d51a6243 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 3 Jun 2016 13:48:21 -0400 Subject: [PATCH 1356/6300] GetBandwidthLimitMessage --- I2CP.cpp | 13 ++++++++++++- I2CP.h | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/I2CP.cpp b/I2CP.cpp index ba6825b8..8faa07e7 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -14,6 +14,7 @@ #include "Timestamp.h" #include "LeaseSet.h" #include "ClientContext.h" +#include "Transports.h" #include "I2CP.h" namespace i2p @@ -241,7 +242,7 @@ namespace client { uint8_t l = buf[0]; if (l > len) l = len; - return std::string ((const char *)buf, l); + return std::string ((const char *)(buf + 1), l); } size_t I2CPSession::PutString (uint8_t * buf, size_t len, const std::string& str) @@ -502,6 +503,15 @@ namespace client SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, buf, 32); } + void I2CPSession::GetBandwidthLimitsMessageHandler (const uint8_t * buf, size_t len) + { + uint8_t limits[64]; + memset (limits, 0, 64); + htobe32buf (limits, i2p::transport::transports.GetInBandwidth ()); // inbound + htobe32buf (limits + 4, i2p::transport::transports.GetOutBandwidth ()); // outbound + SendI2CPMessage (I2CP_BANDWIDTH_LIMITS_MESSAGE, limits, 64); + } + void I2CPSession::SendMessagePayloadMessage (const uint8_t * payload, size_t len) { // we don't use SendI2CPMessage to eliminate additional copy @@ -531,6 +541,7 @@ namespace client m_MessagesHandlers[I2CP_SEND_MESSAGE_EXPIRES_MESSAGE] = &I2CPSession::SendMessageExpiresMessageHandler; m_MessagesHandlers[I2CP_HOST_LOOKUP_MESSAGE] = &I2CPSession::HostLookupMessageHandler; m_MessagesHandlers[I2CP_DEST_LOOKUP_MESSAGE] = &I2CPSession::DestLookupMessageHandler; + m_MessagesHandlers[I2CP_GET_BANDWIDTH_LIMITS_MESSAGE] = &I2CPSession::GetBandwidthLimitsMessageHandler; } I2CPServer::~I2CPServer () diff --git a/I2CP.h b/I2CP.h index 20bd33a5..453c5d40 100644 --- a/I2CP.h +++ b/I2CP.h @@ -43,6 +43,8 @@ namespace client const uint8_t I2CP_HOST_REPLY_MESSAGE = 39; const uint8_t I2CP_DEST_LOOKUP_MESSAGE = 34; const uint8_t I2CP_DEST_REPLY_MESSAGE = 35; + const uint8_t I2CP_GET_BANDWIDTH_LIMITS_MESSAGE = 8; + const uint8_t I2CP_BANDWIDTH_LIMITS_MESSAGE = 23; enum I2CPMessageStatus { @@ -116,6 +118,7 @@ namespace client void SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len); void HostLookupMessageHandler (const uint8_t * buf, size_t len); void DestLookupMessageHandler (const uint8_t * buf, size_t len); + void GetBandwidthLimitsMessageHandler (const uint8_t * buf, size_t len); private: From e4edc59689cee2ad15c02d901952a04dd28e49c4 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 4 Jun 2016 00:00:00 +0000 Subject: [PATCH 1357/6300] * HTTPProxy.cpp : force clean recv buffer (#508) --- HTTPProxy.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 934dbbf5..0d305a47 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -224,8 +224,10 @@ namespace proxy { return; } - if (HandleRequest(len)) + if (HandleRequest(len)) { + m_recv_buf.clear(); return; /* request processed */ + } AsyncSockRead(); } From 66c301c03109088dc7b44f5708fa50b58440cdb6 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 4 Jun 2016 00:00:00 +0000 Subject: [PATCH 1358/6300] * HTTPProxy.cpp : allow "tranparent" proxy (#508) --- HTTPProxy.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 0d305a47..e8926dc6 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -192,9 +192,28 @@ namespace proxy { std::string dest_host = url.host; uint16_t dest_port = url.port; - /* convert proxy-style http req to ordinary one: */ - /* 1) replace Host header, 2) make relative url */ - req.add_header("Host", url.host, true); + /* 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 */ + std::string message = "Can't detect destination host from request"; + HTTPRequestFailed(message.c_str()); + return true; + } + /* make relative url */ url.schema = ""; url.host = ""; req.uri = url.to_string(); From 03973cc6d4fa6cf17681b5a7f514bc8a8d781368 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 4 Jun 2016 00:00:00 +0000 Subject: [PATCH 1359/6300] * HTTPProxy.cpp : drop X-Forwarded-*, Proxy-*, Via headers from request --- HTTPProxy.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index e8926dc6..a2bc4828 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -137,8 +137,24 @@ namespace proxy { void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req) { req.del_header("Referer"); - req.add_header("Connection", "close", true); - req.add_header("User-Agent", "MYOB/6.66 (AN/ON)", true); + req.del_header("Via"); + req.del_header("Forwarded"); + std::vector toErase; + for (auto it : req.headers) { + if (it.first.compare(0, 12, "X-Forwarded-")) { + toErase.push_back(it.first); + } else if (it.first.compare(0, 6, "Proxy-")) { + toErase.push_back(it.first); + } else { + /* allow this header */ + } + } + 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 */ } /** From a4dc67cba0b85e266b69778fa27a84618bca39bf Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 4 Jun 2016 00:00:00 +0000 Subject: [PATCH 1360/6300] * HTTP.{cpp,h} : drop HTTPReq.host --- HTTP.cpp | 9 --------- HTTP.h | 1 - 2 files changed, 10 deletions(-) diff --git a/HTTP.cpp b/HTTP.cpp index ee1010ec..a23f5a72 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -253,21 +253,12 @@ namespace http { if (pos >= eoh) break; } - auto it = headers.find("Host"); - if (it != headers.end ()) { - host = it->second; - } else if (version == "HTTP/1.1") { - return -1; /* 'Host' header required for HTTP/1.1 */ - } else if (url.host != "") { - host = url.host; - } return eoh + strlen(HTTP_EOH); } std::string HTTPReq::to_string() { std::stringstream ss; ss << method << " " << uri << " " << version << CRLF; - ss << "Host: " << host << CRLF; for (auto & h : headers) { ss << h.first << ": " << h.second << CRLF; } diff --git a/HTTP.h b/HTTP.h index 8d10c231..19d0612e 100644 --- a/HTTP.h +++ b/HTTP.h @@ -69,7 +69,6 @@ namespace http { std::string version; std::string method; std::string uri; - std::string host; HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {}; From 4d7c089b099fbc7b3a4db58b215fab136fd7de16 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 5 Jun 2016 10:31:55 -0400 Subject: [PATCH 1361/6300] I2CP config --- docs/configuration.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 11e8b4a8..9ab85b46 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -58,7 +58,11 @@ All options below still possible in cmdline, but better write it in config file: * --bob.address= - The address to listen on (BOB command channel) * --bob.port= - Port of BOB command channel. Usually 2827. BOB is off if not specified -* --sam.enabled= - If BOB is enabled. false by default +* --bob.enabled= - If BOB is enabled. false by default + +* --i2cp.address= - The address to listen on +* --i2cp.port= - Port of I2CP server. Usually 7654. IPCP is off if not specified +* --i2cp.enabled= - If I2CP is enabled. false by default. Other services don't requeire I2CP * --i2pcontrol.address= - The address to listen on (I2P control service) * --i2pcontrol.port= - Port of I2P control service. Usually 7650. I2PControl is off if not specified From e481ed37ce38ec46d8d2405362bb6d6a03430e26 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 6 Jun 2016 15:36:02 -0400 Subject: [PATCH 1362/6300] ReconfigureSessionMessage --- I2CP.cpp | 7 +++++++ I2CP.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/I2CP.cpp b/I2CP.cpp index 8faa07e7..44abdf8b 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -328,6 +328,12 @@ namespace client Terminate (); } + void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len) + { + // TODO: implement actual reconfiguration + SendSessionStatusMessage (2); // updated + } + void I2CPSession::SendSessionStatusMessage (uint8_t status) { uint8_t buf[3]; @@ -536,6 +542,7 @@ namespace client m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; m_MessagesHandlers[I2CP_CREATE_SESSION_MESSAGE] = &I2CPSession::CreateSessionMessageHandler; m_MessagesHandlers[I2CP_DESTROY_SESSION_MESSAGE] = &I2CPSession::DestroySessionMessageHandler; + m_MessagesHandlers[I2CP_RECONFIGURE_SESSION_MESSAGE] = &I2CPSession::ReconfigureSessionMessageHandler; m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler; m_MessagesHandlers[I2CP_SEND_MESSAGE_MESSAGE] = &I2CPSession::SendMessageMessageHandler; m_MessagesHandlers[I2CP_SEND_MESSAGE_EXPIRES_MESSAGE] = &I2CPSession::SendMessageExpiresMessageHandler; diff --git a/I2CP.h b/I2CP.h index 453c5d40..436b1ad6 100644 --- a/I2CP.h +++ b/I2CP.h @@ -31,6 +31,7 @@ namespace client const uint8_t I2CP_GET_DATE_MESSAGE = 32; const uint8_t I2CP_SET_DATE_MESSAGE = 33; const uint8_t I2CP_CREATE_SESSION_MESSAGE = 1; + const uint8_t I2CP_RECONFIGURE_SESSION_MESSAGE = 2; const uint8_t I2CP_SESSION_STATUS_MESSAGE = 20; const uint8_t I2CP_DESTROY_SESSION_MESSAGE = 3; const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37; @@ -113,6 +114,7 @@ namespace client void GetDateMessageHandler (const uint8_t * buf, size_t len); void CreateSessionMessageHandler (const uint8_t * buf, size_t len); void DestroySessionMessageHandler (const uint8_t * buf, size_t len); + void ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len); void CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len); void SendMessageMessageHandler (const uint8_t * buf, size_t len); void SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len); From 5e068c3af5a2764aa3a7e78463142e46656ddf18 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 7 Jun 2016 13:05:44 -0400 Subject: [PATCH 1363/6300] 0.9.26 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index bee17044..5d07c596 100644 --- a/version.h +++ b/version.h @@ -16,7 +16,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 25 +#define I2P_VERSION_MICRO 26 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) From 37fc21f3cf2dce4d1fb9f8ebcfc5fda8f90c423a Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Jun 2016 09:33:25 -0400 Subject: [PATCH 1364/6300] always assume 20 bytes for signing private key --- I2CP.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/I2CP.cpp b/I2CP.cpp index 44abdf8b..ec06895f 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -15,6 +15,7 @@ #include "LeaseSet.h" #include "ClientContext.h" #include "Transports.h" +#include "Signature.h" #include "I2CP.h" namespace i2p @@ -361,7 +362,10 @@ namespace client size_t offset = 2; if (m_Destination) { - offset += m_Destination->GetIdentity ()->GetSigningPrivateKeyLen (); // skip signing private key + offset += i2p::crypto::DSA_PRIVATE_KEY_LENGTH; // skip signing private key + // we always assume this field as 20 bytes (DSA) regardless actual size + // instead of + //offset += m_Destination->GetIdentity ()->GetSigningPrivateKeyLen (); m_Destination->SetEncryptionPrivateKey (buf + offset); offset += 256; m_Destination->LeaseSetCreated (buf + offset, len - offset); From 3cfbc05bf9bfb72fa6351daef31f47f4c279c054 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 8 Jun 2016 09:56:13 -0400 Subject: [PATCH 1365/6300] set pointer to null after delete --- I2CP.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/I2CP.cpp b/I2CP.cpp index ec06895f..9963cb33 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -178,6 +178,7 @@ namespace client memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, offset); HandleNextMessage (m_NextMessage); delete[] m_NextMessage; + m_NextMessage = nullptr } } while (offset < bytes_transferred) From 4d2b535b047ace4b219379dc3c632d3ac9f74ce3 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Jun 2016 10:29:48 -0400 Subject: [PATCH 1366/6300] correct concatenation of long I2CP messages --- I2CP.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/I2CP.cpp b/I2CP.cpp index ec06895f..f7ad7dfd 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -164,25 +164,34 @@ namespace client Terminate (); else { - size_t offset = 0; + size_t offset = 0; // from m_Buffer if (m_NextMessage) { if (m_NextMessageOffset + bytes_transferred <= m_NextMessageLen) { memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, bytes_transferred); m_NextMessageOffset += bytes_transferred; + offset = bytes_transferred; } else { + // m_NextMessage complete offset = m_NextMessageLen - m_NextMessageOffset; memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, offset); HandleNextMessage (m_NextMessage); delete[] m_NextMessage; + m_NextMessage = nullptr; } } while (offset < bytes_transferred) { auto msgLen = bufbe32toh (m_Buffer + offset + I2CP_HEADER_LENGTH_OFFSET) + I2CP_HEADER_SIZE; + if (msgLen > 0xFFFF) // 64K + { + LogPrint (eLogError, "I2CP: message length ", msgLen, " exceeds 64K. Terminated"); + Terminate (); + return; + } if (msgLen <= bytes_transferred - offset) { HandleNextMessage (m_Buffer + offset); @@ -217,6 +226,11 @@ namespace client m_Destination->Stop (); m_Destination = nullptr; } + if (m_Socket) + { + m_Socket->close (); + m_Socket = nullptr; + } m_Owner.RemoveSession (GetSessionID ()); } From d8f24b442bfa57d9d426560280e9896b05864882 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Jun 2016 14:05:20 -0400 Subject: [PATCH 1367/6300] fixed mapping --- I2CP.cpp | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index f7ad7dfd..5447a622 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -276,21 +276,24 @@ namespace client size_t offset = 0; while (offset < len) { - auto semicolon = (const uint8_t *)memchr (buf + offset, ';', len - offset); - if (semicolon) + std::string param = ExtractString (buf + offset, len - offset); + offset += param.length (); + if (buf[offset] != '=') { - auto l = semicolon - buf - offset + 1; - auto equal = (const uint8_t *)memchr (buf + offset, '=', l); - if (equal) - { - auto l1 = equal - buf - offset + 1; - mapping.insert (std::make_pair (std::string ((const char *)(buf + offset), l1 -1), - std::string ((const char *)(buf + offset + l1), l - l1 - 2))); - } - offset += l; - } - else - break; + LogPrint (eLogWarning, "I2CP: Unexpected character ", buf[offset], " instead '=' after ", param); + break; + } + offset++; + + std::string value = ExtractString (buf + offset, len - offset); + offset += value.length (); + if (buf[offset] != ';') + { + LogPrint (eLogWarning, "I2CP: Unexpected character ", buf[offset], " instead ';' after ", value); + break; + } + offset++; + mapping.insert (std::make_pair (param, value)); } } From 21b5f2c96ab80631c85f9f2e1097ad9f90bc3bd8 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Jun 2016 14:14:19 -0400 Subject: [PATCH 1368/6300] fixed crash upon I2CP session disconnect --- I2CP.cpp | 15 ++++++++------- I2CP.h | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 5447a622..e0ab180a 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -23,7 +23,7 @@ namespace i2p namespace client { - I2CPDestination::I2CPDestination (I2CPSession& owner, std::shared_ptr identity, bool isPublic, const std::map& params): + I2CPDestination::I2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params): LeaseSetDestination (isPublic, ¶ms), m_Owner (owner), m_Identity (identity) { } @@ -37,7 +37,7 @@ namespace client { uint32_t length = bufbe32toh (buf); if (length > len - 4) length = len - 4; - m_Owner.SendMessagePayloadMessage (buf + 4, length); + m_Owner->SendMessagePayloadMessage (buf + 4, length); } void I2CPDestination::CreateNewLeaseSet (std::vector > tunnels) @@ -46,9 +46,9 @@ namespace client m_LeaseSetExpirationTime = ls.GetExpirationTime (); uint8_t * leases = ls.GetLeases (); leases[-1] = tunnels.size (); - htobe16buf (leases - 3, m_Owner.GetSessionID ()); + htobe16buf (leases - 3, m_Owner->GetSessionID ()); size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); - m_Owner.SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); + m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); } void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) @@ -78,10 +78,10 @@ namespace client if (ls) { bool sent = s->SendMsg (msg, ls); - s->m_Owner.SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); + s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); } else - s->m_Owner.SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLeaseSet); + s->m_Owner->SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLeaseSet); }); } } @@ -134,6 +134,7 @@ namespace client void I2CPSession::Stop () { + Terminate (); } void I2CPSession::ReadProtocolByte () @@ -327,7 +328,7 @@ namespace client { bool isPublic = true; if (params[I2CP_PARAM_DONT_PUBLISH_LEASESET] == "true") isPublic = false; - m_Destination = std::make_shared(*this, identity, isPublic, params); + m_Destination = std::make_shared(shared_from_this (), identity, isPublic, params); m_Destination->Start (); SendSessionStatusMessage (1); // created LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created"); diff --git a/I2CP.h b/I2CP.h index 436b1ad6..62a9a729 100644 --- a/I2CP.h +++ b/I2CP.h @@ -63,7 +63,7 @@ namespace client { public: - I2CPDestination (I2CPSession& owner, std::shared_ptr identity, bool isPublic, const std::map& params); + I2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params); void SetEncryptionPrivateKey (const uint8_t * key); void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession @@ -87,7 +87,7 @@ namespace client private: - I2CPSession& m_Owner; + std::shared_ptr m_Owner; std::shared_ptr m_Identity; uint8_t m_EncryptionPrivateKey[256]; uint64_t m_LeaseSetExpirationTime; From b786576bcb6b8d4778429a3afbc351ba5426da38 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 9 Jun 2016 14:30:36 +0000 Subject: [PATCH 1369/6300] * HTTPProxy.cpp : always set dest_port --- HTTPProxy.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index a2bc4828..ec3dcffa 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -223,6 +223,8 @@ namespace proxy { u.parse(t); dest_host = u.host; dest_port = u.port; + if (dest_port == 0) + dest_port = 80; /* always set port for CreateStream() */ } else { /* relative url and missing 'Host' header */ std::string message = "Can't detect destination host from request"; From 88561c22d361ccaf3330fa5f73b24154294200c4 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 9 Jun 2016 14:34:38 -0400 Subject: [PATCH 1370/6300] make sure ackThrough is correct --- Streaming.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index ab0a6df0..a258eef1 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -232,6 +232,11 @@ namespace stream bool acknowledged = false; auto ts = i2p::util::GetMillisecondsSinceEpoch (); uint32_t ackThrough = packet->GetAckThrough (); + if (ackThrough > m_SequenceNumber) + { + LogPrint (eLogError, "Streaming: Unexpected ackThrough=", ackThrough, " > seqn=", m_SequenceNumber); + return; + } int nackCount = packet->GetNACKCount (); for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();) { @@ -521,7 +526,7 @@ namespace stream size += 4; // receiveStreamID htobe32buf (packet + size, m_SequenceNumber++); size += 4; // sequenceNum - htobe32buf (packet + size, m_LastReceivedSequenceNumber); + htobe32buf (packet + size, m_LastReceivedSequenceNumber >= 0 ? m_LastReceivedSequenceNumber : 0); size += 4; // ack Through packet[size] = 0; size++; // NACK count From d5d501875ea5ad08730279e45b09432cb5d7a119 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 9 Jun 2016 14:56:12 -0400 Subject: [PATCH 1371/6300] send correct ackThrough --- Streaming.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index a258eef1..1a6fdbca 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -341,9 +341,9 @@ namespace stream htobe32buf (packet + size, m_SequenceNumber++); size += 4; // sequenceNum if (isNoAck) - htobe32buf (packet + size, m_LastReceivedSequenceNumber); - else htobuf32 (packet + size, 0); + else + htobe32buf (packet + size, m_LastReceivedSequenceNumber); size += 4; // ack Through packet[size] = 0; size++; // NACK count From f6d7f7d984a5b4e1bb9c6471fd938193af90b94a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 9 Jun 2016 15:48:31 -0400 Subject: [PATCH 1372/6300] set port to 80 is not specified --- HTTPProxy.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index ec3dcffa..c1ebc4e8 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -223,14 +223,13 @@ namespace proxy { u.parse(t); dest_host = u.host; dest_port = u.port; - if (dest_port == 0) - dest_port = 80; /* always set port for CreateStream() */ } else { /* relative url and missing 'Host' header */ std::string message = "Can't detect destination host from request"; HTTPRequestFailed(message.c_str()); return true; } + if (!dest_port) dest_port = 80; /* always set port for CreateStream() */ //TODO: 443 for https /* make relative url */ url.schema = ""; url.host = ""; From 6de7cd5063fcd7c4ec40d902a7e3da609b6b1b0f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Jun 2016 11:39:20 -0400 Subject: [PATCH 1373/6300] don't send 'accepted' if not requested --- I2CP.cpp | 39 +++++++++++++++++++++++++++++++-------- I2CP.h | 4 +++- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index e0ab180a..20f9dcee 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -117,7 +117,7 @@ namespace client I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0), - m_MessageID (0) + m_MessageID (0), m_IsSendAccepted (true) { RAND_bytes ((uint8_t *)&m_SessionID, 2); } @@ -317,21 +317,42 @@ namespace client { auto identity = std::make_shared(); size_t offset = identity->FromBuffer (buf, len); + if (!offset) + { + LogPrint (eLogError, "I2CP: create session maformed identity"); + SendSessionStatusMessage (3); // invalid + return; + } uint16_t optionsSize = bufbe16toh (buf + offset); offset += 2; - + if (optionsSize > len - offset) + { + LogPrint (eLogError, "I2CP: options size ", optionsSize, "exceeds message size"); + SendSessionStatusMessage (3); // invalid + return; + } std::map params; ExtractMapping (buf + offset, optionsSize, params); - offset += optionsSize; + offset += optionsSize; // options + if (params[I2CP_PARAM_MESSAGE_RELIABILITY] == "none") m_IsSendAccepted = false; + offset += 8; // date if (identity->Verify (buf, offset, buf + offset)) // signature { bool isPublic = true; if (params[I2CP_PARAM_DONT_PUBLISH_LEASESET] == "true") isPublic = false; - m_Destination = std::make_shared(shared_from_this (), identity, isPublic, params); - m_Destination->Start (); - SendSessionStatusMessage (1); // created - LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created"); + if (!m_Destination) + { + m_Destination = std::make_shared(shared_from_this (), identity, isPublic, params); + SendSessionStatusMessage (1); // created + LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created"); + m_Destination->Start (); + } + else + { + LogPrint (eLogError, "I2CP: session already exists"); + SendSessionStatusMessage (4); // refused + } } else { @@ -363,6 +384,7 @@ namespace client void I2CPSession::SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status) { + if (!nonce) return; // don't send status with zero nonce uint8_t buf[15]; htobe16buf (buf, m_SessionID); htobe32buf (buf + 2, m_MessageID++); @@ -406,7 +428,8 @@ namespace client uint32_t payloadLen = bufbe32toh (buf + offset); offset += 4; uint32_t nonce = bufbe32toh (buf + offset + payloadLen); - SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted + if (m_IsSendAccepted) + SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce); } } diff --git a/I2CP.h b/I2CP.h index 62a9a729..f77607ba 100644 --- a/I2CP.h +++ b/I2CP.h @@ -56,7 +56,8 @@ namespace client }; // params - const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet "; + const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet"; + const char I2CP_PARAM_MESSAGE_RELIABILITY[] = "i2cp.messageReliability"; class I2CPSession; class I2CPDestination: public LeaseSetDestination @@ -148,6 +149,7 @@ namespace client std::shared_ptr m_Destination; uint16_t m_SessionID; uint32_t m_MessageID; + bool m_IsSendAccepted; }; typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); From ecd3a49d48378ac5156caafd43f98e421afc8f9a Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Jun 2016 11:47:22 -0400 Subject: [PATCH 1374/6300] handle DestroySession properly --- I2CP.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 20f9dcee..d3ad2350 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -117,9 +117,8 @@ namespace client I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0), - m_MessageID (0), m_IsSendAccepted (true) + m_SessionID (0), m_MessageID (0), m_IsSendAccepted (true) { - RAND_bytes ((uint8_t *)&m_SessionID, 2); } I2CPSession::~I2CPSession () @@ -315,6 +314,7 @@ namespace client void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len) { + RAND_bytes ((uint8_t *)&m_SessionID, 2); auto identity = std::make_shared(); size_t offset = identity->FromBuffer (buf, len); if (!offset) @@ -365,7 +365,11 @@ namespace client { SendSessionStatusMessage (0); // destroy LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " destroyed"); - Terminate (); + if (m_Destination) + { + m_Destination->Stop (); + m_Destination = 0; + } } void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len) From 8feca6874a1f76704273c6afe1d60ec8969fa098 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Jun 2016 12:18:19 -0400 Subject: [PATCH 1375/6300] process complete message --- I2CP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2CP.cpp b/I2CP.cpp index d3ad2350..71188ec8 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -167,7 +167,7 @@ namespace client size_t offset = 0; // from m_Buffer if (m_NextMessage) { - if (m_NextMessageOffset + bytes_transferred <= m_NextMessageLen) + if (m_NextMessageOffset + bytes_transferred < m_NextMessageLen) { memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, bytes_transferred); m_NextMessageOffset += bytes_transferred; From b15b38868d391892f8adcc3f565a7f8dab286066 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Jun 2016 14:01:39 -0400 Subject: [PATCH 1376/6300] rolled back to previous implementation --- HTTPProxy.cpp | 443 +++++++++++++++++++++++++++----------------------- HTTPProxy.h | 25 ++- 2 files changed, 262 insertions(+), 206 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index c1ebc4e8..056e3170 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -1,14 +1,8 @@ #include #include +#include #include #include -#include -#include -#include -#include - -#include "I2PService.h" -#include "Destination.h" #include "HTTPProxy.h" #include "util.h" #include "Identity.h" @@ -20,57 +14,71 @@ #include "Config.h" #include "HTTP.h" -namespace i2p { -namespace proxy { - bool str_rmatch(std::string & str, const char *suffix) { - auto pos = str.rfind (suffix); - if (pos == std::string::npos) - return false; /* not found */ - if (str.length() == (pos + std::strlen(suffix))) - return true; /* match */ - return false; - } - - class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this +namespace i2p +{ +namespace proxy +{ + static const size_t http_buffer_size = 8192; + class HTTPProxyHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: + enum state + { + GET_METHOD, + GET_HOSTNAME, + GET_HTTPV, + GET_HTTPVNL, //TODO: fallback to finding HOst: header if needed + DONE + }; - bool HandleRequest(std::size_t len); + 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 Terminate(); void AsyncSockRead(); - void HTTPRequestFailed(const char *message); - void RedirectToJumpService(std::string & host); - bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64); - void SanitizeHTTPRequest(i2p::http::HTTPReq & req); + void HTTPRequestFailed(/*std::string message*/); + void RedirectToJumpService(); + void ExtractRequest(); + bool IsI2PAddress(); + bool ValidateHTTPRequest(); + void HandleJumpServices(); + bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); + uint8_t m_http_buff[http_buffer_size]; std::shared_ptr m_sock; - std::vector m_recv_buf; /* as "downstream recieve buffer", from client to me */ - std::vector m_send_buf; /* as "upstream send buffer", from me to remote host */ + std::string m_request; //Data left to be sent + std::string m_url; //URL + std::string m_method; //Method + std::string m_version; //HTTP version + std::string m_address; //Address + std::string m_path; //Path + int m_port; //Port + state m_state;//Parsing state public: - HTTPReqHandler(HTTPProxy * parent, std::shared_ptr sock) : - I2PServiceHandler(parent), m_sock(sock), m_recv_buf(8192), m_send_buf(0) {}; - ~HTTPReqHandler() { Terminate(); } + HTTPProxyHandler(HTTPProxyServer * parent, std::shared_ptr sock) : + I2PServiceHandler(parent), m_sock(sock) + { EnterState(GET_METHOD); } + ~HTTPProxyHandler() { Terminate(); } void Handle () { AsyncSockRead(); } }; - void HTTPReqHandler::AsyncSockRead() + void HTTPProxyHandler::AsyncSockRead() { LogPrint(eLogDebug, "HTTPProxy: async sock read"); - if (!m_sock) { + if(m_sock) { + m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), + std::bind(&HTTPProxyHandler::HandleSockRecv, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); + } else { LogPrint(eLogError, "HTTPProxy: no socket for read"); - return; } - m_sock->async_receive(boost::asio::buffer(m_recv_buf), - std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); } - void HTTPReqHandler::Terminate() { + void HTTPProxyHandler::Terminate() { if (Kill()) return; if (m_sock) { @@ -81,176 +89,203 @@ namespace proxy { Done(shared_from_this()); } - void HTTPReqHandler::HTTPRequestFailed(const char *message) + /* All hope is lost beyond this point */ + //TODO: handle this apropriately + void HTTPProxyHandler::HTTPRequestFailed(/*HTTPProxyHandler::errTypes error*/) { - i2p::http::HTTPRes res; - res.code = 500; - res.add_header("Content-Type", "text/plain"); - res.add_header("Connection", "close"); - res.body = message; - res.body += "\r\n"; - std::string response = res.to_string(); - boost::asio::async_write(*m_sock, boost::asio::buffer(response, response.size()), - std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + static std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n"; + boost::asio::async_write(*m_sock, boost::asio::buffer(response,response.size()), + std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPReqHandler::RedirectToJumpService(std::string & host) + void HTTPProxyHandler::RedirectToJumpService(/*HTTPProxyHandler::errTypes error*/) { - i2p::http::HTTPRes res; + std::stringstream response; + std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); + uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); + + response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n\r\n"; + boost::asio::async_write(*m_sock, boost::asio::buffer(response.str (),response.str ().length ()), + std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + } + + void HTTPProxyHandler::EnterState(HTTPProxyHandler::state nstate) + { + m_state = nstate; + } + + void HTTPProxyHandler::ExtractRequest() + { + LogPrint(eLogDebug, "HTTPProxy: request: ", m_method, " ", m_url); i2p::http::URL url; - - i2p::config::GetOption("http.address", url.host); - i2p::config::GetOption("http.port", url.port); - url.schema = "http"; - url.path = "/"; - url.query = "page=jumpservices&address="; - url.query += host; - - res.code = 302; /* redirect */ - res.add_header("Location", url.to_string().c_str()); - res.add_header("Connection", "close"); - - std::string response = res.to_string(); - boost::asio::async_write(*m_sock, boost::asio::buffer(response, response.length()), - std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + url.parse (m_url); + m_address = url.host; + m_port = url.port; + m_path = url.path; + if (!m_port) m_port = 80; + LogPrint(eLogDebug, "HTTPProxy: server: ", m_address, ", port: ", m_port, ", path: ", m_path); } - bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64) + bool HTTPProxyHandler::ValidateHTTPRequest() { - const char *param = "i2paddresshelper="; - std::size_t pos = url.query.find(param); - std::size_t len = std::strlen(param); - std::map params; - - if (pos == std::string::npos) - return false; /* not found */ - if (!url.parse_query(params)) + if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) + { + LogPrint(eLogError, "HTTPProxy: unsupported version: ", m_version); + HTTPRequestFailed(); //TODO: send right stuff return false; - - std::string value = params["i2paddresshelper"]; - len += value.length(); - b64 = i2p::http::UrlDecode(value); - url.query.replace(pos, len, ""); + } return true; } - void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req) + void HTTPProxyHandler::HandleJumpServices() { - req.del_header("Referer"); - req.del_header("Via"); - req.del_header("Forwarded"); - std::vector toErase; - for (auto it : req.headers) { - if (it.first.compare(0, 12, "X-Forwarded-")) { - toErase.push_back(it.first); - } else if (it.first.compare(0, 6, "Proxy-")) { - toErase.push_back(it.first); - } else { - /* allow this header */ - } + static const char * helpermark1 = "?i2paddresshelper="; + static const char * helpermark2 = "&i2paddresshelper="; + size_t addressHelperPos1 = m_path.rfind (helpermark1); + size_t addressHelperPos2 = m_path.rfind (helpermark2); + size_t addressHelperPos; + if (addressHelperPos1 == std::string::npos) + { + if (addressHelperPos2 == std::string::npos) + return; //Not a jump service + else + addressHelperPos = addressHelperPos2; } - for (auto header : toErase) { - req.headers.erase(header); + else + { + if (addressHelperPos2 == std::string::npos) + addressHelperPos = addressHelperPos1; + else if ( addressHelperPos1 > addressHelperPos2 ) + addressHelperPos = addressHelperPos1; + else + addressHelperPos = addressHelperPos2; } - /* 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 */ + 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); } - /** - * @param len length of data in m_recv_buf - * @return true on processed request or false if more data needed - */ - bool HTTPReqHandler::HandleRequest(std::size_t len) + bool HTTPProxyHandler::IsI2PAddress() { - i2p::http::HTTPReq req; - i2p::http::URL url; - std::string b64; - - int req_len = 0; - - req_len = req.parse((const char *) m_recv_buf.data(), len); - if (req_len == 0) - return false; /* need more data */ - if (req_len < 0) { - LogPrint(eLogError, "HTTPProxy: unable to parse request"); - HTTPRequestFailed("invalid request"); - return true; /* parse error */ + auto pos = m_address.rfind (".i2p"); + if (pos != std::string::npos && (pos+4) == m_address.length ()) + { + return true; } + return false; + } - /* parsing success, now let's look inside request */ - LogPrint(eLogDebug, "HTTPProxy: requested: ", req.uri); - url.parse(req.uri); - - if (ExtractAddressHelper(url, b64)) { - i2p::client::context.GetAddressBook ().InsertAddress (url.host, b64); - std::string message = "added b64 from addresshelper for " + url.host + " to address book"; - LogPrint (eLogInfo, "HTTPProxy: ", message); - message += ", please reload page"; - HTTPRequestFailed(message.c_str()); - return true; /* request processed */ - } + bool HTTPProxyHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) + { + ExtractRequest(); //TODO: parse earlier + if (!ValidateHTTPRequest()) return false; + HandleJumpServices(); i2p::data::IdentHash identHash; - if (str_rmatch(url.host, ".i2p")) { - if (!i2p::client::context.GetAddressBook ().GetIdentHash (url.host, identHash)) { - RedirectToJumpService(url.host); - return true; /* request processed */ + if (IsI2PAddress ()) + { + if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ + RedirectToJumpService(); + return false; } - /* TODO: outproxy handler here */ - } else { - std::string message = "Host " + url.host + " not inside i2p network, but outproxy support still missing"; - HTTPRequestFailed(message.c_str()); - LogPrint (eLogWarning, "HTTPProxy: ", message); - return true; } - SanitizeHTTPRequest(req); - - std::string dest_host = url.host; - uint16_t dest_port = url.port; - /* 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 */ - std::string message = "Can't detect destination host from request"; - HTTPRequestFailed(message.c_str()); - return true; - } - if (!dest_port) dest_port = 80; /* always set port for CreateStream() */ //TODO: 443 for https - /* make relative url */ - url.schema = ""; - url.host = ""; - req.uri = url.to_string(); - - /* drop original request from input buffer */ - m_recv_buf.erase(m_recv_buf.begin(), m_recv_buf.begin() + req_len); - - /* build new buffer from modified request and data from original request */ - std::string request = req.to_string(); - m_send_buf.assign(request.begin(), request.end()); - m_send_buf.insert(m_send_buf.end(), m_recv_buf.begin(), m_recv_buf.end()); - - /* connect to destination */ - GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, - shared_from_this(), std::placeholders::_1), dest_host, dest_port); + + m_request = m_method; + m_request.push_back(' '); + m_request += m_path; + m_request.push_back(' '); + m_request += m_version; + m_request.push_back('\r'); + m_request.push_back('\n'); + m_request.append("Connection: close\r\n"); + // TODO: temporary shortcut. Must be implemented properly + uint8_t * eol = nullptr; + bool isEndOfHeader = false; + while (!isEndOfHeader && len && (eol = (uint8_t *)memchr (http_buff, '\r', len))) + { + if (eol) + { + *eol = 0; eol++; + if (strncmp ((const char *)http_buff, "Referer", 7) && strncmp ((const char *)http_buff, "Connection", 10)) // strip out referer and connection + { + if (!strncmp ((const char *)http_buff, "User-Agent", 10)) // replace UserAgent + m_request.append("User-Agent: MYOB/6.66 (AN/ON)"); + else + m_request.append ((const char *)http_buff); + m_request.append ("\r\n"); + } + isEndOfHeader = !http_buff[0]; + auto l = eol - http_buff; + http_buff = eol; + len -= l; + if (len > 0) // \r + { + http_buff++; + len--; + } + } + } + m_request.append(reinterpret_cast(http_buff),len); return true; } - void HTTPReqHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) + bool HTTPProxyHandler::HandleData(uint8_t *http_buff, std::size_t len) + { + while (len > 0) + { + //TODO: fallback to finding HOst: header if needed + switch (m_state) + { + case GET_METHOD: + switch (*http_buff) + { + case ' ': EnterState(GET_HOSTNAME); break; + default: m_method.push_back(*http_buff); break; + } + break; + case GET_HOSTNAME: + switch (*http_buff) + { + case ' ': EnterState(GET_HTTPV); break; + default: m_url.push_back(*http_buff); break; + } + break; + case GET_HTTPV: + switch (*http_buff) + { + case '\r': EnterState(GET_HTTPVNL); break; + default: m_version.push_back(*http_buff); break; + } + break; + case GET_HTTPVNL: + switch (*http_buff) + { + case '\n': EnterState(DONE); break; + default: + LogPrint(eLogError, "HTTPProxy: rejected invalid request ending with: ", ((int)*http_buff)); + HTTPRequestFailed(); //TODO: add correct code + return false; + } + break; + default: + LogPrint(eLogError, "HTTPProxy: invalid state: ", m_state); + HTTPRequestFailed(); //TODO: add correct code 500 + return false; + } + http_buff++; + len--; + if (m_state == DONE) + return CreateHTTPRequest(http_buff,len); + } + return true; + } + + void HTTPProxyHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes"); if(ecode) @@ -260,44 +295,54 @@ namespace proxy { return; } - if (HandleRequest(len)) { - m_recv_buf.clear(); - return; /* request processed */ + if (HandleData(m_http_buff, len)) + { + if (m_state == DONE) + { + LogPrint(eLogDebug, "HTTPProxy: requested: ", m_url); + GetOwner()->CreateStream (std::bind (&HTTPProxyHandler::HandleStreamRequestComplete, + shared_from_this(), std::placeholders::_1), m_address, m_port); + } + else + AsyncSockRead(); } - AsyncSockRead(); + } - void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) + void HTTPProxyHandler::SentHTTPFailed(const boost::system::error_code & ecode) { if (ecode) LogPrint (eLogError, "HTTPProxy: Closing socket after sending failure because: ", ecode.message ()); Terminate(); } - void HTTPReqHandler::HandleStreamRequestComplete (std::shared_ptr stream) + void HTTPProxyHandler::HandleStreamRequestComplete (std::shared_ptr stream) { - if (!stream) { + if (stream) + { + if (Kill()) return; + LogPrint (eLogInfo, "HTTPProxy: New I2PTunnel connection"); + auto connection = std::make_shared(GetOwner(), m_sock, stream); + GetOwner()->AddHandler (connection); + connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); + Done(shared_from_this()); + } + else + { LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); - HTTPRequestFailed("error when creating the stream, check logs"); - return; + HTTPRequestFailed(); // TODO: Send correct error message host unreachable } - if (Kill()) - return; - LogPrint (eLogDebug, "HTTPProxy: New I2PTunnel connection"); - auto connection = std::make_shared(GetOwner(), m_sock, stream); - GetOwner()->AddHandler (connection); - connection->I2PConnect (m_send_buf.data(), m_send_buf.size()); - Done (shared_from_this()); } - HTTPProxy::HTTPProxy(const std::string& address, int port, std::shared_ptr localDestination): + HTTPProxyServer::HTTPProxyServer(const std::string& address, int port, std::shared_ptr localDestination): TCPIPAcceptor(address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()) { } - std::shared_ptr HTTPProxy::CreateHandler(std::shared_ptr socket) + std::shared_ptr HTTPProxyServer::CreateHandler(std::shared_ptr socket) { - return std::make_shared (this, socket); + return std::make_shared (this, socket); } -} // http -} // i2p + +} +} diff --git a/HTTPProxy.h b/HTTPProxy.h index 29b997eb..b5ed77b9 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -1,21 +1,32 @@ #ifndef HTTP_PROXY_H__ #define HTTP_PROXY_H__ -namespace i2p { -namespace proxy { - class HTTPProxy: public i2p::client::TCPIPAcceptor +#include +#include +#include +#include +#include "I2PService.h" +#include "Destination.h" + +namespace i2p +{ +namespace proxy +{ + class HTTPProxyServer: public i2p::client::TCPIPAcceptor { public: - HTTPProxy(const std::string& address, int port, std::shared_ptr localDestination = nullptr); - ~HTTPProxy() {}; + HTTPProxyServer(const std::string& address, int port, std::shared_ptr localDestination = nullptr); + ~HTTPProxyServer() {}; protected: // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return "HTTP Proxy"; } }; -} // http -} // i2p + + typedef HTTPProxyServer HTTPProxy; +} +} #endif From 2e1e95d48368a210b97ee6c8c68c5e8eba15fd82 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Jun 2016 15:12:50 -0400 Subject: [PATCH 1377/6300] pass URL params --- HTTPProxy.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 056e3170..153e61f7 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -122,6 +122,7 @@ namespace proxy m_address = url.host; m_port = url.port; m_path = url.path; + if (url.query.length () > 0) m_path += "?" + url.query; if (!m_port) m_port = 80; LogPrint(eLogDebug, "HTTPProxy: server: ", m_address, ", port: ", m_port, ", path: ", m_path); } From 44556b7f5e8e626c21f8ac5076e55760798d10d4 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Jun 2016 15:25:30 -0400 Subject: [PATCH 1378/6300] correct string size for mapping --- I2CP.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 71188ec8..4842bf2d 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -277,7 +277,7 @@ namespace client while (offset < len) { std::string param = ExtractString (buf + offset, len - offset); - offset += param.length (); + offset += param.length () + 1; if (buf[offset] != '=') { LogPrint (eLogWarning, "I2CP: Unexpected character ", buf[offset], " instead '=' after ", param); @@ -286,7 +286,7 @@ namespace client offset++; std::string value = ExtractString (buf + offset, len - offset); - offset += value.length (); + offset += value.length () + 1; if (buf[offset] != ';') { LogPrint (eLogWarning, "I2CP: Unexpected character ", buf[offset], " instead ';' after ", value); From f4d8c3304abf47b851c706948337d75ab80b3747 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Jun 2016 18:43:35 -0400 Subject: [PATCH 1379/6300] execute lookup wothout session --- I2CP.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 4842bf2d..68809817 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -449,7 +449,7 @@ namespace client void I2CPSession::HostLookupMessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); - if (sessionID == m_SessionID) + if (sessionID == m_SessionID || sessionID == 0xFFFF) // -1 means without session { uint32_t requestID = bufbe32toh (buf + 2); //uint32_t timeout = bufbe32toh (buf + 6); @@ -476,15 +476,17 @@ namespace client return; } - if (m_Destination) + std::shared_ptr destination = m_Destination; + if(!destination) destination = i2p::client::context.GetSharedLocalDestination (); + if (destination) { - auto ls = m_Destination->FindLeaseSet (ident); + auto ls = destination->FindLeaseSet (ident); if (ls) SendHostReplyMessage (requestID, ls->GetIdentity ()); else { auto s = shared_from_this (); - m_Destination->RequestDestination (ident, + destination->RequestDestination (ident, [s, requestID](std::shared_ptr leaseSet) { s->SendHostReplyMessage (requestID, leaseSet ? leaseSet->GetIdentity () : nullptr); From bf8db7725f6e3c7e1ec337563ab948197f92ee28 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Jun 2016 22:13:20 -0400 Subject: [PATCH 1380/6300] set -1 as default session id --- I2CP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2CP.cpp b/I2CP.cpp index 68809817..044fd7a3 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -117,7 +117,7 @@ namespace client I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0), - m_SessionID (0), m_MessageID (0), m_IsSendAccepted (true) + m_SessionID (0xFFFF), m_MessageID (0), m_IsSendAccepted (true) { } From 88f52c490295c1ca703ce4b8bf6203ecf7b74e7c Mon Sep 17 00:00:00 2001 From: Osipov Kirill Date: Sat, 11 Jun 2016 11:20:20 +0300 Subject: [PATCH 1381/6300] Remove unused assigning (success is assinged anyway) --- AddressBook.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 0dbb42d7..5e7510a5 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -744,7 +744,6 @@ namespace client LogPrint (eLogInfo, "Addressbook: received ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); if (!response.eof () && !response.fail ()) { - success = true; if (!isChunked) success = ProcessResponse (response, isGzip); else From ae6877ce2fb7e496c2cc96deacc767c192b57eef Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 12 Jun 2016 08:22:55 -0400 Subject: [PATCH 1382/6300] handle incomplete message header --- I2CP.cpp | 83 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 044fd7a3..dab7f9b0 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -167,24 +167,59 @@ namespace client size_t offset = 0; // from m_Buffer if (m_NextMessage) { - if (m_NextMessageOffset + bytes_transferred < m_NextMessageLen) + if (!m_NextMessageLen) // we didn't receive header yet { - memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, bytes_transferred); - m_NextMessageOffset += bytes_transferred; - offset = bytes_transferred; + if (m_NextMessageOffset + bytes_transferred < I2CP_HEADER_SIZE) + { + // still no complete header + memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, bytes_transferred); + m_NextMessageOffset += bytes_transferred; + offset = bytes_transferred; + } + else + { + // we know message length now + offset = I2CP_HEADER_SIZE - m_NextMessageOffset; + memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, offset); + m_NextMessageLen = bufbe32toh (m_NextMessage + I2CP_HEADER_LENGTH_OFFSET) + I2CP_HEADER_SIZE; + m_NextMessageOffset = I2CP_HEADER_SIZE; + } + } + + if (offset < bytes_transferred) + { + auto msgRemainingLen = m_NextMessageLen - m_NextMessageOffset; + auto bufRemainingLen = bytes_transferred - offset; + if (bufRemainingLen < msgRemainingLen) + { + memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer + offset, bufRemainingLen); + m_NextMessageOffset += bufRemainingLen; + offset += bufRemainingLen; + } + else + { + // m_NextMessage complete + offset += msgRemainingLen; + memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer + offset, msgRemainingLen); + HandleNextMessage (m_NextMessage); + delete[] m_NextMessage; + m_NextMessage = nullptr; + } } - else - { - // m_NextMessage complete - offset = m_NextMessageLen - m_NextMessageOffset; - memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, offset); - HandleNextMessage (m_NextMessage); - delete[] m_NextMessage; - m_NextMessage = nullptr; - } } + // process the rest while (offset < bytes_transferred) { + if (bytes_transferred - offset < I2CP_HEADER_SIZE) + { + // we don't have message header yet + m_NextMessage = new uint8_t[0xFFFF]; // allocate 64K + m_NextMessageLen = 0; // we must set message length later + m_NextMessageOffset = bytes_transferred - offset; + memcpy (m_NextMessage, m_Buffer + offset, m_NextMessageOffset); // just copy it + break; + } + auto msgLen = bufbe32toh (m_Buffer + offset + I2CP_HEADER_LENGTH_OFFSET) + I2CP_HEADER_SIZE; if (msgLen > 0xFFFF) // 64K { @@ -236,14 +271,20 @@ namespace client void I2CPSession::SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len) { - auto l = len + I2CP_HEADER_SIZE; - uint8_t * buf = new uint8_t[l]; - htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len); - buf[I2CP_HEADER_TYPE_OFFSET] = type; - memcpy (buf + I2CP_HEADER_SIZE, payload, len); - boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), - std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, buf)); + auto socket = m_Socket; + if (socket) + { + auto l = len + I2CP_HEADER_SIZE; + uint8_t * buf = new uint8_t[l]; + htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len); + buf[I2CP_HEADER_TYPE_OFFSET] = type; + memcpy (buf + I2CP_HEADER_SIZE, payload, len); + boost::asio::async_write (*socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), + std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), + std::placeholders::_1, std::placeholders::_2, buf)); + } + else + LogPrint (eLogError, "I2CP: Can't write to the socket"); } void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf) From e795de5562309f7f2bd9dec4e6b0dee7d5ad9b07 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 13 Jun 2016 07:48:20 -0400 Subject: [PATCH 1383/6300] fix 500 response in http proxy --- HTTPProxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 153e61f7..48fa0ae6 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -93,7 +93,7 @@ namespace proxy //TODO: handle this apropriately void HTTPProxyHandler::HTTPRequestFailed(/*HTTPProxyHandler::errTypes error*/) { - static std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n"; + static std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n\r\n"; boost::asio::async_write(*m_sock, boost::asio::buffer(response,response.size()), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } From 05939a2bbc1947103b58b5e67fc0295bb8757e8c Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 13 Jun 2016 08:50:53 -0400 Subject: [PATCH 1384/6300] special case for i2p.rocks in proxy --- HTTP.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/HTTP.cpp b/HTTP.cpp index a23f5a72..b4ba9940 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -71,7 +71,11 @@ namespace http { bool URL::parse(const std::string& url) { std::size_t pos_p = 0; /* < current parse position */ std::size_t pos_c = 0; /* < work position */ - if (url.at(0) != '/') { + if (url.at(0) == "/" && url.find("/http://") == url.begin()) { + /* specical case */ + pos_p ++; + } + if(url.at(0) != "/" || pos_b > 0) { /* schema */ pos_c = url.find("://"); if (pos_c != std::string::npos) { From a183ca8661c4ffa7c863ef36aff018fb449a9035 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 13 Jun 2016 08:52:21 -0400 Subject: [PATCH 1385/6300] fix special case --- HTTP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTP.cpp b/HTTP.cpp index b4ba9940..1a79502c 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -71,7 +71,7 @@ namespace http { bool URL::parse(const std::string& url) { std::size_t pos_p = 0; /* < current parse position */ std::size_t pos_c = 0; /* < work position */ - if (url.at(0) == "/" && url.find("/http://") == url.begin()) { + if (url.at(0) == "/" && url.find("/http://") == 0) { /* specical case */ pos_p ++; } From ea7e6615f2406f050f2b8201deb43cc061b95a57 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 13 Jun 2016 08:52:54 -0400 Subject: [PATCH 1386/6300] fix typo --- HTTP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTP.cpp b/HTTP.cpp index 1a79502c..451c099f 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -75,7 +75,7 @@ namespace http { /* specical case */ pos_p ++; } - if(url.at(0) != "/" || pos_b > 0) { + if(url.at(0) != "/" || pos_p > 0) { /* schema */ pos_c = url.find("://"); if (pos_c != std::string::npos) { From 09fc767bb0af70265923ada3bc811fe12e7308a6 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 13 Jun 2016 08:53:35 -0400 Subject: [PATCH 1387/6300] fix another typo --- HTTP.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HTTP.cpp b/HTTP.cpp index 451c099f..53115bda 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -71,11 +71,11 @@ namespace http { bool URL::parse(const std::string& url) { std::size_t pos_p = 0; /* < current parse position */ std::size_t pos_c = 0; /* < work position */ - if (url.at(0) == "/" && url.find("/http://") == 0) { + if (url.at(0) == '/' && url.find("/http://") == 0) { /* specical case */ pos_p ++; } - if(url.at(0) != "/" || pos_p > 0) { + if(url.at(0) != '/' || pos_p > 0) { /* schema */ pos_c = url.find("://"); if (pos_c != std::string::npos) { From 9eaa51442f434e6a25aa75b198ab4246483778f6 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 13 Jun 2016 09:01:38 -0400 Subject: [PATCH 1388/6300] update comment --- HTTP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTP.cpp b/HTTP.cpp index 53115bda..bb389356 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -72,7 +72,7 @@ namespace http { std::size_t pos_p = 0; /* < current parse position */ std::size_t pos_c = 0; /* < work position */ if (url.at(0) == '/' && url.find("/http://") == 0) { - /* specical case */ + /* special case for i2p.rocks inproxy */ pos_p ++; } if(url.at(0) != '/' || pos_p > 0) { From fa68e392c81f929f4388b1c18043b30759461499 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 13 Jun 2016 11:34:44 -0400 Subject: [PATCH 1389/6300] don't abort when ntcp fails to bind --- Daemon.cpp | 30 ++++++++++++++------- I2CP.cpp | 20 +++++++++----- NTCPSession.cpp | 66 +++++++++++++++++++++++++++++----------------- NTCPSession.h | 5 +++- NetDb.cpp | 2 +- SSU.h | 2 +- Transports.cpp | 70 +++++++++++++++++++++++++++++++------------------ Transports.h | 3 +++ 8 files changed, 129 insertions(+), 69 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 7ca28a6f..b580f827 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -198,25 +198,34 @@ namespace i2p bool Daemon_Singleton::start() { - bool http; i2p::config::GetOption("http.enabled", http); - if (http) { - std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); - uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); - LogPrint(eLogInfo, "Daemon: starting HTTP Server at ", httpAddr, ":", httpPort); - d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); - d.httpServer->Start(); - } - LogPrint(eLogInfo, "Daemon: starting NetDB"); i2p::data::netdb.Start(); #ifdef USE_UPNP LogPrint(eLogInfo, "Daemon: starting UPnP"); d.m_UPnP.Start (); -#endif +#endif LogPrint(eLogInfo, "Daemon: starting Transports"); i2p::transport::transports.Start(); + if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU()) { + LogPrint(eLogInfo, "Daemon: Transports started"); + } else { + LogPrint(eLogError, "Daemon: failed to start Transports"); + /** shut down netdb right away */ + i2p::data::netdb.Stop(); + return false; + } + + bool http; i2p::config::GetOption("http.enabled", http); + if (http) { + std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); + uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); + LogPrint(eLogInfo, "Daemon: starting HTTP Server at ", httpAddr, ":", httpPort); + d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); + d.httpServer->Start(); + } + LogPrint(eLogInfo, "Daemon: starting Tunnels"); i2p::tunnel::tunnels.Start(); @@ -232,6 +241,7 @@ namespace i2p d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); d.m_I2PControlService->Start (); } + return true; } diff --git a/I2CP.cpp b/I2CP.cpp index dab7f9b0..b7f91cdd 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -469,13 +469,19 @@ namespace client if (m_Destination) { i2p::data::IdentityEx identity; - offset += identity.FromBuffer (buf + offset, len - offset); - uint32_t payloadLen = bufbe32toh (buf + offset); - offset += 4; - uint32_t nonce = bufbe32toh (buf + offset + payloadLen); - if (m_IsSendAccepted) - SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted - m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce); + size_t identsize = identity.FromBuffer (buf + offset, len - offset); + if (identsize) + { + offset += identsize; + uint32_t payloadLen = bufbe32toh (buf + offset); + offset += 4; + uint32_t nonce = bufbe32toh (buf + offset + payloadLen); + if (m_IsSendAccepted) + SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted + m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce); + } + else + LogPrint(eLogError, "I2CP: invalid identity"); } } else diff --git a/NTCPSession.cpp b/NTCPSession.cpp index ae020f5f..5cd31960 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -760,30 +760,46 @@ namespace transport auto& addresses = context.GetRouterInfo ().GetAddresses (); for (auto address: addresses) { - if (address->transportStyle == i2p::data::RouterInfo::eTransportNTCP && address->host.is_v4 ()) - { - m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, - boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port)); - - LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address->port); - auto conn = std::make_shared(*this); - m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, - conn, std::placeholders::_1)); - - if (context.SupportsV6 ()) + if (address->transportStyle == i2p::data::RouterInfo::eTransportNTCP) + { + if (address->host.is_v4()) { - m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service); - m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6()); - m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true)); - m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port)); - m_NTCPV6Acceptor->listen (); - - LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address->port); - auto conn = std::make_shared (*this); - m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, - this, conn, std::placeholders::_1)); + try + { + m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, + boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port)); + } catch ( std::exception & ex ) { + /** fail to bind ip4 */ + LogPrint(eLogError, "NTCP: Failed to bind to ip4 port ",address->port, ex.what()); + continue; + } + + LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address->port); + auto conn = std::make_shared(*this); + m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, + conn, std::placeholders::_1)); + } + else if (address->host.is_v6() && context.SupportsV6 ()) + { + m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service); + try + { + m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6()); + m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true)); + + m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port)); + m_NTCPV6Acceptor->listen (); + + LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address->port); + auto conn = std::make_shared (*this); + m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, + this, conn, std::placeholders::_1)); + } catch ( std::exception & ex ) { + LogPrint(eLogError, "NTCP: failed to bind to ip6 port ", address->port); + continue; + } } - } + } } } } @@ -795,9 +811,11 @@ namespace transport if (m_IsRunning) { m_IsRunning = false; - delete m_NTCPAcceptor; + if (m_NTCPAcceptor) + delete m_NTCPAcceptor; m_NTCPAcceptor = nullptr; - delete m_NTCPV6Acceptor; + if (m_NTCPV6Acceptor) + delete m_NTCPV6Acceptor; m_NTCPV6Acceptor = nullptr; m_Service.stop (); diff --git a/NTCPSession.h b/NTCPSession.h index f4ce18a6..2a60f1dc 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -144,7 +144,10 @@ namespace transport void RemoveNTCPSession (std::shared_ptr session); std::shared_ptr FindNTCPSession (const i2p::data::IdentHash& ident); void Connect (const boost::asio::ip::address& address, int port, std::shared_ptr conn); - + + bool IsBoundV4() const { return m_NTCPAcceptor != nullptr; }; + bool IsBoundV6() const { return m_NTCPV6Acceptor != nullptr; }; + boost::asio::io_service& GetService () { return m_Service; }; void Ban (const boost::asio::ip::address& addr); diff --git a/NetDb.cpp b/NetDb.cpp index d2afc50a..a4c685db 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -67,7 +67,7 @@ namespace data } m_LeaseSets.clear(); m_Requests.Stop (); - } + } } void NetDb::Run () diff --git a/SSU.h b/SSU.h index 8ee58ffa..fc79b981 100644 --- a/SSU.h +++ b/SSU.h @@ -62,7 +62,7 @@ namespace transport std::shared_ptr GetPeerTestSession (uint32_t nonce); void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role); void RemovePeerTest (uint32_t nonce); - + private: void Run (); diff --git a/Transports.cpp b/Transports.cpp index 2d3d423f..52b1d261 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -46,7 +46,7 @@ namespace transport int num; while ((num = m_QueueSize - m_Queue.size ()) > 0) CreateDHKeysPairs (num); - std::unique_lock l(m_AcquiredMutex); + std::unique_lock l(m_AcquiredMutex); m_Acquired.wait (l); // wait for element gets aquired } } @@ -60,7 +60,7 @@ namespace transport { auto pair = std::make_shared (); pair->GenerateKeys (); - std::unique_lock l(m_AcquiredMutex); + std::unique_lock l(m_AcquiredMutex); m_Queue.push (pair); } } @@ -69,7 +69,7 @@ namespace transport std::shared_ptr DHKeysPairSupplier::Acquire () { { - std::unique_lock l(m_AcquiredMutex); + std::unique_lock l(m_AcquiredMutex); if (!m_Queue.empty ()) { auto pair = m_Queue.front (); @@ -86,7 +86,7 @@ namespace transport void DHKeysPairSupplier::Return (std::shared_ptr pair) { - std::unique_lock l(m_AcquiredMutex); + std::unique_lock l(m_AcquiredMutex); m_Queue.push (pair); } @@ -115,9 +115,16 @@ namespace transport for (auto address : addresses) { if (!m_NTCPServer) - { + { m_NTCPServer = new NTCPServer (); m_NTCPServer->Start (); + if (!(m_NTCPServer->IsBoundV6() || m_NTCPServer->IsBoundV4())) { + /** failed to bind to NTCP */ + LogPrint(eLogError, "Transports: failed to bind to TCP"); + m_NTCPServer->Stop(); + delete m_NTCPServer; + m_NTCPServer = nullptr; + } } if (address->transportStyle == RouterInfo::eTransportSSU && address->host.is_v4 ()) @@ -126,7 +133,14 @@ namespace transport { m_SSUServer = new SSUServer (address->port); LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port); - m_SSUServer->Start (); + try { + m_SSUServer->Start (); + } catch ( std::exception & ex ) { + LogPrint(eLogError, "Transports: Failed to bind to UDP port", address->port); + delete m_SSUServer; + m_SSUServer = nullptr; + continue; + } DetectExternalIP (); } else @@ -206,7 +220,7 @@ namespace transport void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) { - SendMessages (ident, std::vector > {msg }); + SendMessages (ident, std::vector > {msg }); } void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs) @@ -231,7 +245,7 @@ namespace transport { auto r = netdb.FindRouter (ident); { - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); it = m_Peers.insert (std::pair(ident, { 0, r, {}, i2p::util::GetSecondsSinceEpoch (), {} })).first; } @@ -288,7 +302,7 @@ namespace transport } } else - LogPrint (eLogWarning, "Transports: NTCP address is not present for ", i2p::data::GetIdentHashAbbreviation (ident), ", trying SSU"); + LogPrint (eLogDebug, "Transports: NTCP address is not present for ", i2p::data::GetIdentHashAbbreviation (ident), ", trying SSU"); } if (peer.numAttempts == 1)// SSU { @@ -320,7 +334,7 @@ namespace transport } LogPrint (eLogError, "Transports: No NTCP or SSU addresses available"); peer.Done (); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (ident); return false; } @@ -352,7 +366,7 @@ namespace transport else { LogPrint (eLogError, "Transports: RouterInfo not found, Failed to send messages"); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } @@ -396,7 +410,7 @@ namespace transport } } LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ()); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it1); } } @@ -438,7 +452,7 @@ namespace transport } } LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ()); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it1); } } @@ -446,7 +460,7 @@ namespace transport void Transports::CloseSession (std::shared_ptr router) { if (!router) return; - m_Service.post (std::bind (&Transports::PostCloseSession, this, router)); + m_Service.post (std::bind (&Transports::PostCloseSession, this, router)); } void Transports::PostCloseSession (std::shared_ptr router) @@ -458,6 +472,12 @@ namespace transport LogPrint (eLogDebug, "Transports: SSU session closed"); } // TODO: delete NTCP + auto ntcpSession = m_NTCPServer ? m_NTCPServer->FindNTCPSession(router->GetIdentHash()) : nullptr; + if (ntcpSession) + { + m_NTCPServer->RemoveNTCPSession(ntcpSession); + LogPrint(eLogDebug, "Transports: NTCP session closed"); + } } void Transports::DetectExternalIP () @@ -468,14 +488,14 @@ namespace transport for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomPeerTestRouter (); - if (router && router->IsSSU (!context.SupportsV6 ())) - m_SSUServer->CreateSession (router, true); // peer test + if (router && router->IsSSU (!context.SupportsV6 ())) + m_SSUServer->CreateSession (router, true); // peer test else { // if not peer test capable routers found pick any router = i2p::data::netdb.GetRandomRouter (); if (router && router->IsSSU ()) - m_SSUServer->CreateSession (router); // no peer test + m_SSUServer->CreateSession (router); // no peer test } } } @@ -498,7 +518,7 @@ namespace transport statusChanged = true; i2p::context.SetStatus (eRouterStatusTesting); // first time only } - m_SSUServer->CreateSession (router, true); // peer test + m_SSUServer->CreateSession (router, true); // peer test } } } @@ -517,7 +537,7 @@ namespace transport void Transports::PeerConnected (std::shared_ptr session) { m_Service.post([session, this]() - { + { auto remoteIdentity = session->GetRemoteIdentity (); if (!remoteIdentity) return; auto ident = remoteIdentity->GetIdentHash (); @@ -530,7 +550,7 @@ namespace transport // check if first message is our DatabaseStore (publishing) auto firstMsg = it->second.delayedMessages[0]; if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore && - i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ()) + i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ()) sendDatabaseStore = false; // we have it in the list already } if (sendDatabaseStore) @@ -542,7 +562,7 @@ namespace transport else // incoming connection { session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} })); } }); @@ -551,7 +571,7 @@ namespace transport void Transports::PeerDisconnected (std::shared_ptr session) { m_Service.post([session, this]() - { + { auto remoteIdentity = session->GetRemoteIdentity (); if (!remoteIdentity) return; auto ident = remoteIdentity->GetIdentHash (); @@ -565,7 +585,7 @@ namespace transport ConnectToPeer (ident, it->second); else { - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } @@ -590,14 +610,14 @@ namespace transport if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) { LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); it = m_Peers.erase (it); } else it++; } UpdateBandwidth (); // TODO: use separate timer(s) for it - if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test + if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test DetectExternalIP (); m_PeerCleanupTimer.expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer.async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); diff --git a/Transports.h b/Transports.h index 3bfe1f8b..0e1b621b 100644 --- a/Transports.h +++ b/Transports.h @@ -75,6 +75,9 @@ namespace transport void Start (); void Stop (); + + bool IsBoundNTCP() const { return m_NTCPServer != nullptr; } + bool IsBoundSSU() const { return m_SSUServer != nullptr; } boost::asio::io_service& GetService () { return m_Service; }; std::shared_ptr GetNextDHKeysPair (); From fb94d6ae2b6419fe761728744ce1179409304d58 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 13 Jun 2016 13:20:21 -0400 Subject: [PATCH 1390/6300] read header and payload separately --- I2CP.cpp | 137 +++++++++++++++++++------------------------------------ I2CP.h | 12 +++-- 2 files changed, 53 insertions(+), 96 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index dab7f9b0..f506a312 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -115,15 +115,14 @@ namespace client } I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): - m_Owner (owner), m_Socket (socket), - m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0), + m_Owner (owner), m_Socket (socket), m_Payload (nullptr), m_SessionID (0xFFFF), m_MessageID (0), m_IsSendAccepted (true) { } I2CPSession::~I2CPSession () { - delete[] m_NextMessage; + delete[] m_Payload; } void I2CPSession::Start () @@ -141,117 +140,72 @@ namespace client if (m_Socket) { auto s = shared_from_this (); - m_Socket->async_read_some (boost::asio::buffer (m_Buffer, 1), + m_Socket->async_read_some (boost::asio::buffer (m_Header, 1), [s](const boost::system::error_code& ecode, std::size_t bytes_transferred) { - if (!ecode && bytes_transferred > 0 && s->m_Buffer[0] == I2CP_PROTOCOL_BYTE) - s->Receive (); + if (!ecode && bytes_transferred > 0 && s->m_Header[0] == I2CP_PROTOCOL_BYTE) + s->ReceiveHeader (); else s->Terminate (); }); } } - void I2CPSession::Receive () + void I2CPSession::ReceiveHeader () { - m_Socket->async_read_some (boost::asio::buffer (m_Buffer, I2CP_SESSION_BUFFER_SIZE), - std::bind (&I2CPSession::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Header, I2CP_HEADER_SIZE), + boost::asio::transfer_all (), + std::bind (&I2CPSession::HandleReceivedHeader, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } - void I2CPSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) + void I2CPSession::HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) Terminate (); else { - size_t offset = 0; // from m_Buffer - if (m_NextMessage) + m_PayloadLen = bufbe32toh (m_Header + I2CP_HEADER_LENGTH_OFFSET); + if (m_PayloadLen > 0) { - if (!m_NextMessageLen) // we didn't receive header yet - { - if (m_NextMessageOffset + bytes_transferred < I2CP_HEADER_SIZE) - { - // still no complete header - memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, bytes_transferred); - m_NextMessageOffset += bytes_transferred; - offset = bytes_transferred; - } - else - { - // we know message length now - offset = I2CP_HEADER_SIZE - m_NextMessageOffset; - memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, offset); - m_NextMessageLen = bufbe32toh (m_NextMessage + I2CP_HEADER_LENGTH_OFFSET) + I2CP_HEADER_SIZE; - m_NextMessageOffset = I2CP_HEADER_SIZE; - } - } - - if (offset < bytes_transferred) - { - auto msgRemainingLen = m_NextMessageLen - m_NextMessageOffset; - auto bufRemainingLen = bytes_transferred - offset; - if (bufRemainingLen < msgRemainingLen) - { - memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer + offset, bufRemainingLen); - m_NextMessageOffset += bufRemainingLen; - offset += bufRemainingLen; - } - else - { - // m_NextMessage complete - offset += msgRemainingLen; - memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer + offset, msgRemainingLen); - HandleNextMessage (m_NextMessage); - delete[] m_NextMessage; - m_NextMessage = nullptr; - } - } - } - // process the rest - while (offset < bytes_transferred) + m_Payload = new uint8_t[m_PayloadLen]; + ReceivePayload (); + } + else // no following payload { - if (bytes_transferred - offset < I2CP_HEADER_SIZE) - { - // we don't have message header yet - m_NextMessage = new uint8_t[0xFFFF]; // allocate 64K - m_NextMessageLen = 0; // we must set message length later - m_NextMessageOffset = bytes_transferred - offset; - memcpy (m_NextMessage, m_Buffer + offset, m_NextMessageOffset); // just copy it - break; - } - - auto msgLen = bufbe32toh (m_Buffer + offset + I2CP_HEADER_LENGTH_OFFSET) + I2CP_HEADER_SIZE; - if (msgLen > 0xFFFF) // 64K - { - LogPrint (eLogError, "I2CP: message length ", msgLen, " exceeds 64K. Terminated"); - Terminate (); - return; - } - if (msgLen <= bytes_transferred - offset) - { - HandleNextMessage (m_Buffer + offset); - offset += msgLen; - } - else - { - m_NextMessageLen = msgLen; - m_NextMessageOffset = bytes_transferred - offset; - m_NextMessage = new uint8_t[m_NextMessageLen]; - memcpy (m_NextMessage, m_Buffer + offset, m_NextMessageOffset); - offset = bytes_transferred; - } - } - Receive (); + HandleMessage (); + ReceiveHeader (); // next message + } } } - void I2CPSession::HandleNextMessage (const uint8_t * buf) + void I2CPSession::ReceivePayload () { - auto handler = m_Owner.GetMessagesHandlers ()[buf[I2CP_HEADER_TYPE_OFFSET]]; - if (handler) - (this->*handler)(buf + I2CP_HEADER_SIZE, bufbe32toh (buf + I2CP_HEADER_LENGTH_OFFSET)); + boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Payload, m_PayloadLen), + boost::asio::transfer_all (), + std::bind (&I2CPSession::HandleReceivedPayload, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + } + + void I2CPSession::HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (ecode) + Terminate (); else - LogPrint (eLogError, "I2CP: Unknown I2CP messsage ", (int)buf[I2CP_HEADER_TYPE_OFFSET]); + { + HandleMessage (); + delete[] m_Payload; + m_Payload = nullptr; + m_PayloadLen = 0; + ReceiveHeader (); // next message + } + } + + void I2CPSession::HandleMessage () + { + auto handler = m_Owner.GetMessagesHandlers ()[m_Header[I2CP_HEADER_TYPE_OFFSET]]; + if (handler) + (this->*handler)(m_Payload, m_PayloadLen); + else + LogPrint (eLogError, "I2CP: Unknown I2CP messsage ", (int)m_Header[I2CP_HEADER_TYPE_OFFSET]); } void I2CPSession::Terminate () @@ -267,6 +221,7 @@ namespace client m_Socket = nullptr; } m_Owner.RemoveSession (GetSessionID ()); + LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " terminated"); } void I2CPSession::SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len) diff --git a/I2CP.h b/I2CP.h index f77607ba..6fc0e846 100644 --- a/I2CP.h +++ b/I2CP.h @@ -126,9 +126,11 @@ namespace client private: void ReadProtocolByte (); - void Receive (); - void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandleNextMessage (const uint8_t * buf); + void ReceiveHeader (); + void HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void ReceivePayload (); + void HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void HandleMessage (); void Terminate (); void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf); @@ -143,8 +145,8 @@ namespace client I2CPServer& m_Owner; std::shared_ptr m_Socket; - uint8_t m_Buffer[I2CP_SESSION_BUFFER_SIZE], * m_NextMessage; - size_t m_NextMessageLen, m_NextMessageOffset; + uint8_t m_Header[I2CP_HEADER_SIZE], * m_Payload; + size_t m_PayloadLen; std::shared_ptr m_Destination; uint16_t m_SessionID; From b8eef181b93b9c7d2e266d47a7782b45b7aba442 Mon Sep 17 00:00:00 2001 From: Andrey Tikhomirov Date: Tue, 14 Jun 2016 11:25:51 +0300 Subject: [PATCH 1391/6300] Fix html attributes --- HTTPServer.cpp | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 7b7b8bcc..a48536a7 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -127,7 +127,7 @@ namespace http { "\r\n" /* TODO: Add support for locale */ " \r\n" " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ - " \r\n" + " \r\n" " Purple I2P " VERSION " Webconsole\r\n" << cssStyles << "\r\n"; @@ -136,15 +136,15 @@ namespace http { "

Streams
StreamIDDestinationSentReceivedOutInBufRTTWindowStatus
" << it->GetSendStreamID () << "" << i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()) << "" << it->GetNumSentBytes () << "" << it->GetNumReceivedBytes () << "" << it->GetSendQueueSize () << "" << it->GetReceiveQueueSize () << "" << it->GetSendBufferSize () << "" << it->GetRTT () << "" << it->GetWindowSize () << "" << (int)it->GetStatus () << "
BashВыделить код
1
+2
+3
+
$ rm -rf openssl-1.0.1g/   # удалÑем иÑходники(вмеÑто верÑии 1.0.1g - подÑтавлÑем Ñвою), еÑли они уже были раÑпакованы
+$ tar xzf openssl-1.0.1g.tar.gz    # раÑпаковываем иÑходники в подпапку
+$ chmod a+x setenv-android.sh    # разрешаем setenv-android.sh иÑполнÑтьÑÑ
Редактируем setenv-android.sh, наÑÑ‚Ñ€Ð°Ð¸Ð²Ð°Ñ Ñ‚Ð°Ð¼ _ANDROID_EABI, _ANDROID_ARCH, _ANDROID_API на нужные значениÑ.
+Дальше возвращаемÑÑ Ð² конÑоль:
BashВыделить код
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
$ export ANDROID_NDK_ROOT=путь_до_ANDROID_NDK # указываем путь до Android NDK Ð´Ð»Ñ setenv-android.sh
+$ . ./setenv-android.sh # запуÑкаем Ñкрипт, чтобы он нам в окружение проÑтавил необходимые далее переменные
+$ cd openssl-1.0.1g/
+$ perl -pi -e 's/install: all install_docs install_sw/install: install_docs install_sw/g' Makefile.org
+# конфигурируем
+$ ./config shared no-ssl2 no-ssl3 no-comp no-hw no-engine --openssldir=/usr/local/ssl/$ANDROID_API
+# Ñобираем
+$ make depend
+$ make all
+# уÑтанавливаем
+$ sudo -E make install CC=$ANDROID_TOOLCHAIN/arm-linux-androideabi-gcc RANLIB=$ANDROID_TOOLCHAIN/arm-linux-androideabi-ranlib
И тут начинаетÑÑ Ð¸Ð½Ñ‚ÐµÑ€ÐµÑное. Ðндроид не принимает versioned shared object (Ñто *.so.x и подобные). КазалоÑÑŒ бы 2016 год, коÑмичеÑкие корабли уже давно бороздÑÑ‚ проÑторы Большого театра, но вот те на.

+Однако, еÑть обходной приём - нужно заменить *.so.x.x.x на *_x_x_x.so. ПроÑтым переименованием файлов данную проблему здеÑÑŒ, разумеетÑÑ, не решить. Ðужно лезть внутрь и переименовывать soname и внутренние ÑÑылки на другие versioned shared object. Ð’ интернете еÑть много ÑпоÑобов по подобному переименованию. БольшинÑтво из них обещают райÑкую жизнь Ñ rpl, Ð·Ð°Ð±Ñ‹Ð²Ð°Ñ ÑƒÐ¿Ð¾Ð¼Ñнуть, что утилита уже давно отпета и закопана на большинÑтве диÑтрибутивов. Или хитро-хитро редактируют makefile, что в итоге на меÑто левой руки ÑобираетÑÑ Ð¿Ñ€Ð°Ð²Ð°Ñ Ð½Ð¾Ð³Ð°. Ð’ целом множеÑтво путей из разрÑда "как потратить много времени на полную фигню".

+В итоге предлагаю решить данную проблему методом топора:
+Качаем hex-редактор, еÑли ещё нет(в моём Ñлучае таковым оказалÑÑ Okteta). ЗапуÑкаем его из под рута(kdesu okteta), открываем в нём файлы openssldir/lib/libcrypto.so.1.0.0. ЗаменÑем(ctrl+r) в нём Ñимволы ".so.1.0.0" на char "_1_0_0.so". Проделываем тоже Ñамое Ñ libssl.so.1.0.0. Ð’ÑÑ‘, теперь оÑталоÑÑŒ только переименовать Ñами файлы(в libcrypto_1_0_0.so и libssl_1_0_0.so) и поправить ÑÑылки libssl.so и libcrypto.so, чтобы они вели на них.

+Чтобы подключить и иÑпользовать данную библиотеку в проекте нужно добавить в .pro:
BashВыделить код
1
+2
+3
+4
+5
+
android: {
+    INCLUDEPATH += /usr/local/ssl/android-21/include
+    LIBS += -L/usr/local/ssl/android-21/lib
+}
+LIBS += -lcrypto
Рзатем в наÑтройках проекта, в Buld/Build Steps/Bulild Android Apk добавить libcrypto_1_0_0.so и libssl_1_0_0.so в ÑпиÑок Additional Libraries.

+Ðа Ñтом вÑÑ‘. + +
+

Original: http://www.cyberforum.ru/blogs/748276/blog4086.html

+ + diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index f247cee4..8adfd140 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -135,14 +135,23 @@ INCLUDEPATH += /home/anon5/git/Boost-for-Android-Prebuilt/boost_1_53_0/include /home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/include \ ../../../android-ifaddrs/ equals(ANDROID_TARGET_ARCH, armeabi-v7a){ +# http://stackoverflow.com/a/30235934/529442 LIBS += -L/home/anon5/git/Boost-for-Android-Prebuilt/boost_1_53_0/armeabi-v7a/lib \ - -L/home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib \ --lcrypto \ --lssl \ +#/home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ +#/home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libssl.a \ -lboost_system-gcc-mt-1_53 \ -lboost_date_time-gcc-mt-1_53 \ -lboost_filesystem-gcc-mt-1_53 \ --lboost_program_options-gcc-mt-1_53 +-lboost_program_options-gcc-mt-1_53 \ +-L$$PWD/../../../OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/ -lcrypto -lssl + +PRE_TARGETDEPS += $$PWD/../../../OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ + $$PWD/../../../OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libssl.a + +DEPENDPATH += $$PWD/../../../OpenSSL-for-Android-Prebuilt/openssl-1.0.2/include + +ANDROID_EXTRA_LIBS += /home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libcrypto_1_0_0.so \ + /home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libssl_1_0_0.so } } @@ -151,3 +160,8 @@ message("Using Linux settings") LIBS += -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread } + +unix:!macx: + + + From a5be4c9d0ed0c2d0077ba64c48d14e94007497f7 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 14 Jun 2016 11:55:44 -0400 Subject: [PATCH 1395/6300] moved std::to_string to util.h from android --- ClientContext.cpp | 8 +------- HTTP.cpp | 19 ++----------------- HTTPServer.cpp | 10 ++-------- I2PControl.cpp | 8 +------- Reseed.cpp | 9 +-------- RouterContext.cpp | 8 +------- util.h | 11 +++++++++++ 7 files changed, 19 insertions(+), 54 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 3e69510c..d545ea78 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -8,12 +8,6 @@ #include "Identity.h" #include "ClientContext.h" -#ifdef ANDROID -# include "to_string.h" -#else -# define to_string(x) std::to_string(x) -#endif - namespace i2p { namespace client @@ -298,7 +292,7 @@ namespace client template std::string ClientContext::GetI2CPOption (const Section& section, const std::string& name, const Type& value) const { - return section.second.get (boost::property_tree::ptree::path_type (name, '/'), to_string (value)); + return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value)); } template diff --git a/HTTP.cpp b/HTTP.cpp index dbb115af..fa7809a5 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -10,13 +10,6 @@ #include #include -#ifdef ANDROID -# include "to_string.h" -# include -#else -# define to_string(x) std::to_string(x) -#endif - namespace i2p { namespace http { const std::vector HTTP_METHODS = { @@ -187,11 +180,7 @@ namespace http { out += user + "@"; } if (port) { -#ifndef ANDROID - out += host + ":" + to_string(port); -#else - out += host + ":" + tostr::to_string(port); -#endif + out += host + ":" + std::to_string(port); } else { out += host; } @@ -349,11 +338,7 @@ namespace http { if (status == "OK" && code != 200) status = HTTPCodeToStatus(code); // update if (body.length() > 0 && headers.count("Content-Length") == 0) -#ifndef ANDROID - add_header("Content-Length", to_string(body.length()).c_str()); -#else - add_header("Content-Length", tostr::to_string(body.length()).c_str()); -#endif + add_header("Content-Length", std::to_string(body.length()).c_str()); /* build response */ std::stringstream ss; ss << version << " " << code << " " << status << CRLF; diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 583c02da..ff693313 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -25,12 +25,6 @@ // For image and info #include "version.h" -#ifdef ANDROID -# include "to_string.h" -#else -# define to_string(x) std::to_string(x) -#endif - namespace i2p { namespace http { const char *itoopieFavicon = @@ -236,8 +230,8 @@ namespace http { clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); - s << "Client Tunnels: " << to_string(clientTunnelCount) << " "; - s << "Transit Tunnels: " << to_string(transitTunnelCount) << "
\r\n"; + s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; + s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n"; } void ShowJumpServices (std::stringstream& s, const std::string& address) diff --git a/I2PControl.cpp b/I2PControl.cpp index bad25f37..9eebb389 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -25,12 +25,6 @@ #include "version.h" #include "I2PControl.h" -#ifdef ANDROID -# include "to_string.h" -#else -# define to_string(x) std::to_string(x) -#endif - namespace i2p { namespace client @@ -321,7 +315,7 @@ namespace client } InsertParam (results, "API", api); results << ","; - std::string token = to_string(i2p::util::GetSecondsSinceEpoch ()); + std::string token = std::to_string(i2p::util::GetSecondsSinceEpoch ()); m_Tokens.insert (token); InsertParam (results, "Token", token); } diff --git a/Reseed.cpp b/Reseed.cpp index b707429a..722d7eff 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -16,13 +16,6 @@ #include "NetDb.h" #include "util.h" - -#ifdef ANDROID -# include "to_string.h" -#else -# define to_string(x) std::to_string(x) -#endif - namespace i2p { namespace data @@ -379,7 +372,7 @@ namespace data 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_, to_string (u.port_)), ecode); + boost::asio::ip::tcp::resolver::query (u.host_, std::to_string (u.port_)), ecode); if (!ecode) { boost::asio::ssl::context ctx(service, boost::asio::ssl::context::sslv23); diff --git a/RouterContext.cpp b/RouterContext.cpp index 34d49553..768750bb 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -12,12 +12,6 @@ #include "Family.h" #include "RouterContext.h" -#ifdef ANDROID -# include "to_string.h" -#else -# define to_string(x) std::to_string(x) -#endif - namespace i2p { RouterContext context; @@ -62,7 +56,7 @@ namespace i2p routerInfo.AddNTCPAddress (host.c_str(), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC - routerInfo.SetProperty ("netId", to_string (I2PD_NET_ID)); + routerInfo.SetProperty ("netId", std::to_string (I2PD_NET_ID)); routerInfo.SetProperty ("router.version", I2P_VERSION); routerInfo.CreateBuffer (m_Keys); m_RouterInfo.SetRouterIdentity (GetIdentity ()); diff --git a/util.h b/util.h index f5dbc9aa..9f797158 100644 --- a/util.h +++ b/util.h @@ -7,6 +7,17 @@ #include #include +#ifdef ANDROID +namespace std +{ +template +std::string to_string(T value) +{ + return boost::lexical_cast(value); +} +} +#endif + namespace i2p { namespace util From 27ca3b4b01bf512f11e1b1634d64677e53e57395 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 14 Jun 2016 13:21:22 -0400 Subject: [PATCH 1396/6300] Enable C++11 --- qt/i2pd_qt/i2pd_qt.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 8adfd140..789ba842 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -10,7 +10,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = i2pd_qt TEMPLATE = app - +QMAKE_CXXFLAGS *= -std=c++11 SOURCES += main.cpp\ mainwindow.cpp \ From 756e86662beab1fe526d52602faf0fef8d5b3936 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 14 Jun 2016 14:37:22 -0400 Subject: [PATCH 1397/6300] fixed android build --- ClientContext.cpp | 1 + HTTP.cpp | 1 + HTTPServer.cpp | 1 + I2PControl.cpp | 1 + 4 files changed, 4 insertions(+) diff --git a/ClientContext.cpp b/ClientContext.cpp index d545ea78..2bc13969 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -6,6 +6,7 @@ #include "FS.h" #include "Log.h" #include "Identity.h" +#include "util.h" #include "ClientContext.h" namespace i2p diff --git a/HTTP.cpp b/HTTP.cpp index fa7809a5..eca21fde 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -6,6 +6,7 @@ * See full license text in LICENSE file at top of project tree */ +#include "util.h" #include "HTTP.h" #include #include diff --git a/HTTPServer.cpp b/HTTPServer.cpp index ff693313..5263c53d 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -21,6 +21,7 @@ #include "ClientContext.h" #include "HTTPServer.h" #include "Daemon.h" +#include "util.h" // For image and info #include "version.h" diff --git a/I2PControl.cpp b/I2PControl.cpp index 9eebb389..c0c87fd4 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -23,6 +23,7 @@ #include "Timestamp.h" #include "Transports.h" #include "version.h" +#include "util.h" #include "I2PControl.h" namespace i2p From b5723a6c18f7a2a9488a7df23dcc700357a57dc3 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 15 Jun 2016 09:31:52 -0400 Subject: [PATCH 1398/6300] use QT's main loop --- qt/i2pd_qt/i2pd_qt.pro | 26 +++++++++++++------------- qt/i2pd_qt/main.cpp | 10 ++++++---- qt/i2pd_qt/to_string.h | 19 ------------------- 3 files changed, 19 insertions(+), 36 deletions(-) delete mode 100644 qt/i2pd_qt/to_string.h diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 789ba842..d1480522 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -12,6 +12,7 @@ TARGET = i2pd_qt TEMPLATE = app QMAKE_CXXFLAGS *= -std=c++11 + SOURCES += main.cpp\ mainwindow.cpp \ ../../HTTPServer.cpp ../../I2PControl.cpp ../../UPnP.cpp ../../Daemon.cpp ../../Config.cpp \ @@ -60,11 +61,10 @@ SOURCES += main.cpp\ ../../TunnelGateway.cpp \ ../../TunnelPool.cpp \ ../../util.cpp \ - ../../../android-ifaddrs/ifaddrs.c + /mnt/media/android/android-ifaddrs/ifaddrs.c HEADERS += mainwindow.h \ ../../HTTPServer.h ../../I2PControl.h ../../UPnP.h ../../Daemon.h ../../Config.h \ - to_string.h \ ../../AddressBook.h \ ../../api.h \ ../../Base.h \ @@ -115,7 +115,7 @@ HEADERS += mainwindow.h \ ../../TunnelPool.h \ ../../util.h \ ../../version.h \ - ../../../android-ifaddrs/ifaddrs.h + /mnt/media/android/android-ifaddrs/ifaddrs.h FORMS += mainwindow.ui @@ -131,27 +131,27 @@ DEFINES += ANDROID=1 # git clone https://github.com/emileb/Boost-for-Android-Prebuilt.git # git clone https://github.com/anon5/OpenSSL-for-Android-Prebuilt.git # git clone https://github.com/anon5/android-ifaddrs.git -INCLUDEPATH += /home/anon5/git/Boost-for-Android-Prebuilt/boost_1_53_0/include \ - /home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/include \ - ../../../android-ifaddrs/ +INCLUDEPATH += /mnt/media/android/Boost-for-Android-Prebuilt/boost_1_53_0/include \ + /mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/include \ + /mnt/media/android/android-ifaddrs/ equals(ANDROID_TARGET_ARCH, armeabi-v7a){ # http://stackoverflow.com/a/30235934/529442 -LIBS += -L/home/anon5/git/Boost-for-Android-Prebuilt/boost_1_53_0/armeabi-v7a/lib \ +LIBS += -L/mnt/media/android/Boost-for-Android-Prebuilt/boost_1_53_0/armeabi-v7a/lib \ #/home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ #/home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libssl.a \ -lboost_system-gcc-mt-1_53 \ -lboost_date_time-gcc-mt-1_53 \ -lboost_filesystem-gcc-mt-1_53 \ -lboost_program_options-gcc-mt-1_53 \ --L$$PWD/../../../OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/ -lcrypto -lssl +-L/mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/ -lcrypto -lssl -PRE_TARGETDEPS += $$PWD/../../../OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ - $$PWD/../../../OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libssl.a +PRE_TARGETDEPS += /mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ + /mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libssl.a -DEPENDPATH += $$PWD/../../../OpenSSL-for-Android-Prebuilt/openssl-1.0.2/include +DEPENDPATH += /mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/include -ANDROID_EXTRA_LIBS += /home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libcrypto_1_0_0.so \ - /home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libssl_1_0_0.so +ANDROID_EXTRA_LIBS += /mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libcrypto.so \ + /mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libssl.so } } diff --git a/qt/i2pd_qt/main.cpp b/qt/i2pd_qt/main.cpp index db5c39e5..d00a7dbf 100644 --- a/qt/i2pd_qt/main.cpp +++ b/qt/i2pd_qt/main.cpp @@ -8,14 +8,16 @@ int main(int argc, char *argv[]) QApplication a(argc, argv); MainWindow w; - w.show(); - + int ret = -1; if (Daemon.init(argc, argv)) { if (Daemon.start()) - Daemon.run (); + { + w.show(); + ret = a.exec(); + } Daemon.stop(); } - return a.exec(); + return ret; } diff --git a/qt/i2pd_qt/to_string.h b/qt/i2pd_qt/to_string.h deleted file mode 100644 index a4bcf480..00000000 --- a/qt/i2pd_qt/to_string.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef TO_STRING_H -#define TO_STRING_H - -#include -#include - -namespace tostr { -template -std::string to_string(T value) -{ - std::ostringstream os ; - os << value ; - return os.str() ; -} -} - -using namespace tostr; - -#endif // TO_STRING_H From ff38a3bbfea4e195edf27847c5a351b97ff4b2c2 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 15 Jun 2016 10:17:02 -0400 Subject: [PATCH 1399/6300] don't demonize --- qt/i2pd_qt/main.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/qt/i2pd_qt/main.cpp b/qt/i2pd_qt/main.cpp index d00a7dbf..e7978bfe 100644 --- a/qt/i2pd_qt/main.cpp +++ b/qt/i2pd_qt/main.cpp @@ -3,20 +3,31 @@ #include #include "../../Daemon.h" +class DaemonQT: public i2p::util::Daemon_Singleton +{ + public: + + static DaemonQT& Instance() + { + static DaemonQT instance; + return instance; + } +}; + int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; int ret = -1; - if (Daemon.init(argc, argv)) + if (DaemonQT::Instance ().init(argc, argv)) { - if (Daemon.start()) + if (DaemonQT::Instance ().start()) { w.show(); ret = a.exec(); } - Daemon.stop(); + DaemonQT::Instance ().stop(); } return ret; From 70e502e55dca205149cafa587820af4a701674bf Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 15 Jun 2016 11:28:59 -0400 Subject: [PATCH 1400/6300] QT doesn't depend on Linux daemon anymore --- Daemon.h | 25 +++++++++++++++++++++---- DaemonLinux.cpp | 7 ------- HTTPServer.cpp | 6 +++--- qt/i2pd_qt/i2pd_qt.pro | 2 -- qt/i2pd_qt/main.cpp | 17 +++-------------- 5 files changed, 27 insertions(+), 30 deletions(-) diff --git a/Daemon.h b/Daemon.h index 6b154ee4..9f040683 100644 --- a/Daemon.h +++ b/Daemon.h @@ -3,10 +3,12 @@ #include -#ifdef _WIN32 -#define Daemon i2p::util::DaemonWin32::Instance() +#if defined(QT_GUI) + +#elif defined(_WIN32) + #else -#define Daemon i2p::util::DaemonLinux::Instance() + #endif namespace i2p @@ -36,7 +38,21 @@ namespace i2p Daemon_Singleton_Private &d; }; -#ifdef _WIN32 +#if defined(QT_GUI_LIB) // check if QT +#define Daemon i2p::util::DaemonQT::Instance() + class DaemonQT: public i2p::util::Daemon_Singleton + { + public: + + static DaemonQT& Instance() + { + static DaemonQT instance; + return instance; + } + }; + +#elif defined(_WIN32) +#define Daemon i2p::util::DaemonWin32::Instance() class DaemonWin32 : public Daemon_Singleton { public: @@ -52,6 +68,7 @@ namespace i2p void run (); }; #else +#define Daemon i2p::util::DaemonLinux::Instance() class DaemonLinux : public Daemon_Singleton { public: diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 4aceff07..118fc5f5 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -74,11 +74,9 @@ namespace i2p } // point std{in,out,err} descriptors to /dev/null -#ifndef ANDROID stdin = freopen("/dev/null", "r", stdin); stdout = freopen("/dev/null", "w", stdout); stderr = freopen("/dev/null", "w", stderr); -#endif } // Pidfile @@ -94,12 +92,7 @@ namespace i2p LogPrint(eLogError, "Daemon: could not create pid file ", pidfile, ": ", strerror(errno)); return false; } -#ifndef ANDROID if (lockf(pidFH, F_TLOCK, 0) != 0) -#else - //TODO ANDROID actually need to read man for this, blindly took a solution from . -anon5 - if (fcntl(pidFH, 1, 0) < 0) -#endif { LogPrint(eLogError, "Daemon: could not lock pid file ", pidfile, ": ", strerror(errno)); return false; diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 5263c53d..dc504c58 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -365,7 +365,7 @@ namespace http { s << " Stop accepting tunnels
\r\n"; else s << " Start accepting tunnels
\r\n"; -#ifndef WIN32 +#if (!defined(WIN32) && !defined(QT_GUI_LIB)) if (Daemon.gracefullShutdownInterval) { s << " Cancel gracefull shutdown ("; s << Daemon.gracefullShutdownInterval; @@ -678,12 +678,12 @@ namespace http { i2p::context.SetAcceptsTunnels (false); else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { i2p::context.SetAcceptsTunnels (false); -#ifndef WIN32 +#if (!defined(WIN32) && !defined(QT_GUI_LIB)) Daemon.gracefullShutdownInterval = 10*60; #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); -#ifndef WIN32 +#if (!defined(WIN32) && !defined(QT_GUI_LIB)) Daemon.gracefullShutdownInterval = 0; #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index d1480522..cbde47b3 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -22,8 +22,6 @@ SOURCES += main.cpp\ ../../BOB.cpp \ ../../ClientContext.cpp \ ../../Crypto.cpp \ - ../../DaemonLinux.cpp \ - ../../DaemonWin32.cpp \ ../../Datagram.cpp \ ../../Destination.cpp \ ../../Family.cpp \ diff --git a/qt/i2pd_qt/main.cpp b/qt/i2pd_qt/main.cpp index e7978bfe..d00a7dbf 100644 --- a/qt/i2pd_qt/main.cpp +++ b/qt/i2pd_qt/main.cpp @@ -3,31 +3,20 @@ #include #include "../../Daemon.h" -class DaemonQT: public i2p::util::Daemon_Singleton -{ - public: - - static DaemonQT& Instance() - { - static DaemonQT instance; - return instance; - } -}; - int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; int ret = -1; - if (DaemonQT::Instance ().init(argc, argv)) + if (Daemon.init(argc, argv)) { - if (DaemonQT::Instance ().start()) + if (Daemon.start()) { w.show(); ret = a.exec(); } - DaemonQT::Instance ().stop(); + Daemon.stop(); } return ret; From b0e3339370e729b90078047b53bb584e2eab0b1f Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 15 Jun 2016 12:20:31 -0400 Subject: [PATCH 1401/6300] DaemonQT --- Daemon.h | 13 +++++-------- qt/i2pd_qt/DaemonQT.cpp | 39 +++++++++++++++++++++++++++++++++++++++ qt/i2pd_qt/i2pd_qt.pro | 3 ++- qt/i2pd_qt/main.cpp | 23 ----------------------- 4 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 qt/i2pd_qt/DaemonQT.cpp delete mode 100644 qt/i2pd_qt/main.cpp diff --git a/Daemon.h b/Daemon.h index 9f040683..2f7682eb 100644 --- a/Daemon.h +++ b/Daemon.h @@ -3,14 +3,6 @@ #include -#if defined(QT_GUI) - -#elif defined(_WIN32) - -#else - -#endif - namespace i2p { namespace util @@ -49,6 +41,11 @@ namespace i2p static DaemonQT instance; return instance; } + + bool init(int argc, char* argv[]); + bool start(); + bool stop(); + void run (); }; #elif defined(_WIN32) diff --git a/qt/i2pd_qt/DaemonQT.cpp b/qt/i2pd_qt/DaemonQT.cpp new file mode 100644 index 00000000..19a13c6e --- /dev/null +++ b/qt/i2pd_qt/DaemonQT.cpp @@ -0,0 +1,39 @@ +#include +#include "mainwindow.h" +#include +#include +#include "../../Daemon.h" + +namespace i2p +{ +namespace util +{ + std::unique_ptr app; + bool DaemonQT::init(int argc, char* argv[]) + { + app.reset (new QApplication (argc, argv)); + return Daemon_Singleton::init(argc, argv); + } + + bool DaemonQT::start() + { + return Daemon_Singleton::start(); + } + + bool DaemonQT::stop() + { + return Daemon_Singleton::stop(); + } + + void DaemonQT::run () + { + MainWindow w; + w.show (); + if (app) + { + app->exec(); + app.reset (nullptr); + } + } +} +} diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index cbde47b3..5dca2e51 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -13,7 +13,7 @@ TEMPLATE = app QMAKE_CXXFLAGS *= -std=c++11 -SOURCES += main.cpp\ +SOURCES += DaemonQT.cpp\ mainwindow.cpp \ ../../HTTPServer.cpp ../../I2PControl.cpp ../../UPnP.cpp ../../Daemon.cpp ../../Config.cpp \ ../../AddressBook.cpp \ @@ -59,6 +59,7 @@ SOURCES += main.cpp\ ../../TunnelGateway.cpp \ ../../TunnelPool.cpp \ ../../util.cpp \ + ../../i2pd.cpp \ /mnt/media/android/android-ifaddrs/ifaddrs.c HEADERS += mainwindow.h \ diff --git a/qt/i2pd_qt/main.cpp b/qt/i2pd_qt/main.cpp deleted file mode 100644 index d00a7dbf..00000000 --- a/qt/i2pd_qt/main.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "mainwindow.h" -#include -#include -#include "../../Daemon.h" - -int main(int argc, char *argv[]) -{ - QApplication a(argc, argv); - MainWindow w; - - int ret = -1; - if (Daemon.init(argc, argv)) - { - if (Daemon.start()) - { - w.show(); - ret = a.exec(); - } - Daemon.stop(); - } - - return ret; -} From 14c85fa975e3ca45421d603be8295b8ce509c173 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 15 Jun 2016 13:18:04 -0400 Subject: [PATCH 1402/6300] configurable pathes to dependancies --- qt/i2pd_qt/i2pd_qt.pro | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 5dca2e51..b8b5adbb 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -12,6 +12,13 @@ TARGET = i2pd_qt TEMPLATE = app QMAKE_CXXFLAGS *= -std=c++11 +# git clone https://github.com/emileb/Boost-for-Android-Prebuilt.git +# git clone https://github.com/anon5/OpenSSL-for-Android-Prebuilt.git +# git clone https://github.com/anon5/android-ifaddrs.git +# change to your own +BOOST_PATH = /mnt/media/android/Boost-for-Android-Prebuilt +OPENSSL_PATH = /mnt/media/android/OpenSSL-for-Android-Prebuilt +IFADDRS_PATH = /mnt/media/android/android-ifaddrs SOURCES += DaemonQT.cpp\ mainwindow.cpp \ @@ -60,7 +67,7 @@ SOURCES += DaemonQT.cpp\ ../../TunnelPool.cpp \ ../../util.cpp \ ../../i2pd.cpp \ - /mnt/media/android/android-ifaddrs/ifaddrs.c + $$IFADDRS_PATH/ifaddrs.c HEADERS += mainwindow.h \ ../../HTTPServer.h ../../I2PControl.h ../../UPnP.h ../../Daemon.h ../../Config.h \ @@ -114,7 +121,7 @@ HEADERS += mainwindow.h \ ../../TunnelPool.h \ ../../util.h \ ../../version.h \ - /mnt/media/android/android-ifaddrs/ifaddrs.h + $$IFADDRS_PATH/ifaddrs.h FORMS += mainwindow.ui @@ -127,30 +134,27 @@ LIBS += -lz android { message("Using Android settings") DEFINES += ANDROID=1 -# git clone https://github.com/emileb/Boost-for-Android-Prebuilt.git -# git clone https://github.com/anon5/OpenSSL-for-Android-Prebuilt.git -# git clone https://github.com/anon5/android-ifaddrs.git -INCLUDEPATH += /mnt/media/android/Boost-for-Android-Prebuilt/boost_1_53_0/include \ - /mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/include \ - /mnt/media/android/android-ifaddrs/ +INCLUDEPATH += $$BOOST_PATH/boost_1_53_0/include \ + $$OPENSSL_PATH/openssl-1.0.2/include \ + $$IFADDRS_PATH equals(ANDROID_TARGET_ARCH, armeabi-v7a){ # http://stackoverflow.com/a/30235934/529442 -LIBS += -L/mnt/media/android/Boost-for-Android-Prebuilt/boost_1_53_0/armeabi-v7a/lib \ +LIBS += -L$$BOOST_PATH/boost_1_53_0/armeabi-v7a/lib \ #/home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ #/home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libssl.a \ -lboost_system-gcc-mt-1_53 \ -lboost_date_time-gcc-mt-1_53 \ -lboost_filesystem-gcc-mt-1_53 \ -lboost_program_options-gcc-mt-1_53 \ --L/mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/ -lcrypto -lssl +-L$$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/ -lcrypto -lssl -PRE_TARGETDEPS += /mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ - /mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libssl.a +PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ + $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl.a -DEPENDPATH += /mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/include +DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include -ANDROID_EXTRA_LIBS += /mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libcrypto.so \ - /mnt/media/android/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libssl.so +ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto.so \ + $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl.so } } From eb96edbd311dca4713657f01d143b0c72ffdd407 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 15 Jun 2016 14:43:29 -0400 Subject: [PATCH 1403/6300] separate DaemonQT and DaemonQTImpl --- Daemon.h | 8 ++++-- qt/i2pd_qt/DaemonQT.cpp | 63 ++++++++++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/Daemon.h b/Daemon.h index 2f7682eb..7467a518 100644 --- a/Daemon.h +++ b/Daemon.h @@ -1,6 +1,7 @@ #ifndef DAEMON_H__ #define DAEMON_H__ +#include #include namespace i2p @@ -32,6 +33,7 @@ namespace i2p #if defined(QT_GUI_LIB) // check if QT #define Daemon i2p::util::DaemonQT::Instance() + class DaemonQTImpl; class DaemonQT: public i2p::util::Daemon_Singleton { public: @@ -43,9 +45,11 @@ namespace i2p } bool init(int argc, char* argv[]); - bool start(); - bool stop(); void run (); + + private: + + std::shared_ptr m_Impl; }; #elif defined(_WIN32) diff --git a/qt/i2pd_qt/DaemonQT.cpp b/qt/i2pd_qt/DaemonQT.cpp index 19a13c6e..41242e3b 100644 --- a/qt/i2pd_qt/DaemonQT.cpp +++ b/qt/i2pd_qt/DaemonQT.cpp @@ -8,32 +8,57 @@ namespace i2p { namespace util { - std::unique_ptr app; + class DaemonQTImpl: public std::enable_shared_from_this + { + public: + + DaemonQTImpl (int argc, char* argv[]): + m_App (argc, argv) + { + } + + void Run () + { + MainWindow w; + w.show (); + m_App.exec(); + } + + private: + + void StartDaemon () + { + Daemon.start (); + } + + void StopDaemon () + { + Daemon.stop (); + } + + bool IsRunning () const + { + return Daemon.running; + } + + private: + + QApplication m_App; + }; + bool DaemonQT::init(int argc, char* argv[]) { - app.reset (new QApplication (argc, argv)); + m_Impl = std::make_shared (argc, argv); return Daemon_Singleton::init(argc, argv); } - bool DaemonQT::start() - { - return Daemon_Singleton::start(); - } - - bool DaemonQT::stop() - { - return Daemon_Singleton::stop(); - } - void DaemonQT::run () { - MainWindow w; - w.show (); - if (app) - { - app->exec(); - app.reset (nullptr); - } + if (m_Impl) + { + m_Impl->Run (); + m_Impl = nullptr; + } } } } From 5be147e8ccb9d22edf2acc30dde25cc1d1d3b6ed Mon Sep 17 00:00:00 2001 From: libre-net-society Date: Thu, 16 Jun 2016 01:11:42 +0300 Subject: [PATCH 1404/6300] More informative README and docs index --- README.md | 62 +++++++++++++++++++++++++++++++------------------- docs/index.rst | 32 ++++++++++++++++---------- 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index b985abf4..0f4b2183 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,43 @@ i2pd ==== -Independent C++ implementation of I2P router +i2pd is a full-featured C++ implementation of +[I2P](https://geti2p.net/en/about/intro) client. -License -------- +I2P (Invisible Internet Project) is anonymous network which works on top of +public Internet. Privacy and anonymity are achieved by strong encryption and +bouncing your traffic through thousands of I2P nodes all around the world. -This project is licensed under the BSD 3-clause license, which can be found in the file -LICENSE in the root of the project source code. +We are building network which helps people to communicate and share information +without restrictions. + +* [Website](http://i2pd.website) +* [Documentation](https://i2pd.readthedocs.io/en/latest/) +* [Wiki](https://github.com/PurpleI2P/i2pd/wiki) +* [Tickets/Issues](https://github.com/PurpleI2P/i2pd/issues) +* [Twitter](https://twitter.com/i2porignal) + +Installing +---------- + +The easiest way to install i2pd is by using +[precompiled binaries](https://github.com/PurpleI2P/i2pd/releases/latest). +See [documentation](https://i2pd.readthedocs.io/en/latest/) for how to build +i2pd from source on your OS. + +**Supported systems:** + +* Linux x86/x64 - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) +* Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) +* Mac OS X +* FreeBSD +* Android *(coming soon)* + +Using i2pd +---------- + +See [documentation](https://i2pd.readthedocs.io/en/latest/) and +[example config file](https://github.com/PurpleI2P/i2pd/blob/openssl/docs/i2pd.conf). Donations --------- @@ -17,22 +47,8 @@ LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z DOGE: DNXLQKziRPAsD9H3DFNjk4fLQrdaSX893Y -Documentation: --------------- -http://i2pd.readthedocs.org +License +------- -Supported OS ------------- - -* Linux x86/x64 - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) -* Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) -* Mac OS X -* FreeBSD - -More documentation ------------------- - -* [Building from source / unix](docs/build_notes_unix.md) -* [Building from source / windows](docs/build_notes_windows.md) -* [Configuring your i2pd](docs/configuration.md) -* [Github wiki](https://github.com/PurpleI2P/i2pd/wiki/) +This project is licensed under the BSD 3-clause license, which can be found in the file +LICENSE in the root of the project source code. diff --git a/docs/index.rst b/docs/index.rst index 3963dc3c..d0471add 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,20 +1,27 @@ i2pd ==== -`Website `_ | -`Github `_ | -`Issues `_ +i2pd is a full-featured C++ implementation of +`I2P `_ client. -i2pd is C++ implementation of `I2P `_. +* `Website `_ +* `GitHub `_ +* `Wiki `_ +* `Tickets/Issues `_ +* `Twitter `_ -Supports: ---------- - -* Complete I2P router functionality -* Floodfill -* HTTP and SOCKS proxy -* I2P client and server tunnels -* SAM and BOB interfaces +Installing +---------- + +The easiest way to install i2pd is by using +`precompiled binaries `_. +See documentation for how to build i2pd from source on your OS. + +Using i2pd +---------- + +See documentation and +`example config file `_. Contents: --------- @@ -28,3 +35,4 @@ Contents: configuration family + From ba330a42d652b249c1cfc19390b1f2e0fae244d3 Mon Sep 17 00:00:00 2001 From: xcps Date: Thu, 16 Jun 2016 09:15:30 -0400 Subject: [PATCH 1405/6300] Auto webconsole page refresh removed --- HTTPServer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index dc504c58..db27dee5 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -612,7 +612,6 @@ namespace http { HandleCommand (req, res, s); } else { ShowStatus (s); - res.add_header("Refresh", "5"); } ShowPageTail (s); From 675861c32324c2c5180377dbf7ecf670dc625720 Mon Sep 17 00:00:00 2001 From: xcps Date: Thu, 16 Jun 2016 09:18:46 -0400 Subject: [PATCH 1406/6300] Auto webconsole page refresh commented --- HTTPServer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index db27dee5..b40cef27 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -612,6 +612,7 @@ namespace http { HandleCommand (req, res, s); } else { ShowStatus (s); + //res.add_header("Refresh", "5"); } ShowPageTail (s); From ed561ad86b76f42344dc2dfed454495299387e65 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 16 Jun 2016 11:42:34 -0400 Subject: [PATCH 1407/6300] x86 build added --- qt/i2pd_qt/i2pd_qt.pro | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index b8b5adbb..355e94c7 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -156,6 +156,25 @@ DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto.so \ $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl.so } +equals(ANDROID_TARGET_ARCH, x86){ +# http://stackoverflow.com/a/30235934/529442 +LIBS += -L$$BOOST_PATH/boost_1_53_0/x86/lib \ +#/home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ +#/home/anon5/git/OpenSSL-for-Android-Prebuilt/openssl-1.0.2/armeabi-v7a/lib/libssl.a \ +-lboost_system-gcc-mt-1_53 \ +-lboost_date_time-gcc-mt-1_53 \ +-lboost_filesystem-gcc-mt-1_53 \ +-lboost_program_options-gcc-mt-1_53 \ +-L$$OPENSSL_PATH/openssl-1.0.2/x86/lib/ -lcrypto -lssl + +PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto.a \ + $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl.a + +DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include + +ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto.so \ + $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl.so +} } linux:!android { From f2f760bda49086b23823ef5932b121aa28bde464 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 16 Jun 2016 14:29:32 -0400 Subject: [PATCH 1408/6300] link against correct openssl libs --- qt/i2pd_qt/i2pd_qt.pro | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 355e94c7..80f89a1d 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -13,7 +13,7 @@ TEMPLATE = app QMAKE_CXXFLAGS *= -std=c++11 # git clone https://github.com/emileb/Boost-for-Android-Prebuilt.git -# git clone https://github.com/anon5/OpenSSL-for-Android-Prebuilt.git +# git clone https://github.com/hypnosis-i2p/OpenSSL-for-Android-Prebuilt # git clone https://github.com/anon5/android-ifaddrs.git # change to your own BOOST_PATH = /mnt/media/android/Boost-for-Android-Prebuilt @@ -153,8 +153,8 @@ PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include -ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto.so \ - $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl.so +ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto_1_0_0.so \ + $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl_1_0_0.so } equals(ANDROID_TARGET_ARCH, x86){ # http://stackoverflow.com/a/30235934/529442 @@ -172,8 +172,8 @@ PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto.a \ DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include -ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto.so \ - $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl.so +ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto_1_0_0.so \ + $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl_1_0_0.so } } From 7ae563867c6bffd7de1ec5f47bbb3a09400585aa Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 16 Jun 2016 16:22:14 -0400 Subject: [PATCH 1409/6300] mainfest --- qt/i2pd_qt/android/AndroidManifest.xml | 60 ++++++++++++++++++++++++++ qt/i2pd_qt/i2pd_qt.pro | 5 +++ 2 files changed, 65 insertions(+) create mode 100644 qt/i2pd_qt/android/AndroidManifest.xml diff --git a/qt/i2pd_qt/android/AndroidManifest.xml b/qt/i2pd_qt/android/AndroidManifest.xml new file mode 100644 index 00000000..603776e7 --- /dev/null +++ b/qt/i2pd_qt/android/AndroidManifest.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 80f89a1d..e5494b0a 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -185,5 +185,10 @@ LIBS += -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboo unix:!macx: +DISTFILES += \ + android/AndroidManifest.xml + +ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + From b9cbdb2dc42a0bf915b6fd12981caac2472eb956 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 16 Jun 2016 18:20:07 -0400 Subject: [PATCH 1410/6300] Removed dependancy from stdafx --- qt/i2pd_qt/i2pd_qt.pro | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index e5494b0a..d820184f 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -12,9 +12,9 @@ TARGET = i2pd_qt TEMPLATE = app QMAKE_CXXFLAGS *= -std=c++11 -# git clone https://github.com/emileb/Boost-for-Android-Prebuilt.git -# git clone https://github.com/hypnosis-i2p/OpenSSL-for-Android-Prebuilt -# git clone https://github.com/anon5/android-ifaddrs.git +# git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt +# git clone https://github.com/PuerpleI2P/android-ifaddrs.git # change to your own BOOST_PATH = /mnt/media/android/Boost-for-Android-Prebuilt OPENSSL_PATH = /mnt/media/android/OpenSSL-for-Android-Prebuilt @@ -57,7 +57,6 @@ SOURCES += DaemonQT.cpp\ ../../SSU.cpp \ ../../SSUData.cpp \ ../../SSUSession.cpp \ - ../../stdafx.cpp \ ../../Streaming.cpp \ ../../TransitTunnel.cpp \ ../../Transports.cpp \ @@ -107,7 +106,6 @@ HEADERS += mainwindow.h \ ../../SSU.h \ ../../SSUData.h \ ../../SSUSession.h \ - ../../stdafx.h \ ../../Streaming.h \ ../../Timestamp.h \ ../../TransitTunnel.h \ From e868d427dd703eca51b075c8ec95c54c7c89efe6 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 17 Jun 2016 09:02:12 -0400 Subject: [PATCH 1411/6300] add options to not use ntcp or ssu --- Config.cpp | 2 ++ Daemon.cpp | 4 +++- I2CP.cpp | 15 ++++++++++----- Makefile.linux | 6 +++--- Transports.cpp | 6 +++--- Transports.h | 2 +- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Config.cpp b/Config.cpp index e6d44d59..3156ac66 100644 --- a/Config.cpp +++ b/Config.cpp @@ -124,6 +124,8 @@ namespace config { ("notransit", value()->zero_tokens()->default_value(false), "Router will not accept transit tunnels at startup") ("floodfill", value()->zero_tokens()->default_value(false), "Router will be floodfill") ("bandwidth", value()->default_value(""), "Bandwidth limit: integer in kbps or letters: L (32), O (256), P (2048), X (>9000)") + ("ntcp", value()->zero_tokens()->default_value(true), "enable ntcp transport") + ("ssu", value()->zero_tokens()->default_value(true), "enable ssu transport") #ifdef _WIN32 ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") ("insomnia", value()->zero_tokens()->default_value(false), "Prevent system from sleeping") diff --git a/Daemon.cpp b/Daemon.cpp index b580f827..0a82a5cb 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -205,8 +205,10 @@ namespace i2p LogPrint(eLogInfo, "Daemon: starting UPnP"); d.m_UPnP.Start (); #endif + bool ntcp; i2p::config::GetOption("ntcp", ntcp); + bool ssu; i2p::config::GetOption("ssu", ssu); LogPrint(eLogInfo, "Daemon: starting Transports"); - i2p::transport::transports.Start(); + i2p::transport::transports.Start(ntcp, ssu); if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU()) { LogPrint(eLogInfo, "Daemon: Transports started"); } else { diff --git a/I2CP.cpp b/I2CP.cpp index b7f91cdd..3005efd0 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -474,11 +474,16 @@ namespace client { offset += identsize; uint32_t payloadLen = bufbe32toh (buf + offset); - offset += 4; - uint32_t nonce = bufbe32toh (buf + offset + payloadLen); - if (m_IsSendAccepted) - SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted - m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce); + if (payloadLen + offset <= len) + { + offset += 4; + uint32_t nonce = bufbe32toh (buf + offset + payloadLen); + if (m_IsSendAccepted) + SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted + m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce); + } + else + LogPrint(eLogError, "I2CP: cannot send message, too big"); } else LogPrint(eLogError, "I2CP: invalid identity"); diff --git a/Makefile.linux b/Makefile.linux index e00fd705..70e9e7dd 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -28,15 +28,15 @@ endif NEEDED_CXXFLAGS += -fPIC ifeq ($(USE_STATIC),yes) - LIBDIR := /usr/lib + #LIBDIR = /usr/lib LDLIBS = $(LIBDIR)/libboost_system.a LDLIBS += $(LIBDIR)/libboost_date_time.a LDLIBS += $(LIBDIR)/libboost_filesystem.a LDLIBS += $(LIBDIR)/libboost_program_options.a - LDLIBS += $(LIBDIR)/libcrypto.a LDLIBS += $(LIBDIR)/libssl.a + LDLIBS += $(LIBDIR)/libcrypto.a LDLIBS += $(LIBDIR)/libz.a - LDLIBS += -lpthread -static-libstdc++ -static-libgcc + LDLIBS += -lpthread -static-libstdc++ -static-libgcc -lrt USE_AESNI := no else LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread diff --git a/Transports.cpp b/Transports.cpp index 52b1d261..be27ffd1 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -105,7 +105,7 @@ namespace transport Stop (); } - void Transports::Start () + void Transports::Start (bool enableNTCP, bool enableSSU) { m_DHKeysPairSupplier.Start (); m_IsRunning = true; @@ -114,7 +114,7 @@ namespace transport auto& addresses = context.GetRouterInfo ().GetAddresses (); for (auto address : addresses) { - if (!m_NTCPServer) + if (!m_NTCPServer && enableNTCP) { m_NTCPServer = new NTCPServer (); m_NTCPServer->Start (); @@ -129,7 +129,7 @@ namespace transport if (address->transportStyle == RouterInfo::eTransportSSU && address->host.is_v4 ()) { - if (!m_SSUServer) + if (!m_SSUServer && enableSSU) { m_SSUServer = new SSUServer (address->port); LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port); diff --git a/Transports.h b/Transports.h index 0e1b621b..a7e7dc1d 100644 --- a/Transports.h +++ b/Transports.h @@ -73,7 +73,7 @@ namespace transport Transports (); ~Transports (); - void Start (); + void Start (bool enableNTCP=true, bool enableSSU=true); void Stop (); bool IsBoundNTCP() const { return m_NTCPServer != nullptr; } From 6264569ca042a38a8579a5d04a982d1cf7f6432f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 17 Jun 2016 09:10:11 -0400 Subject: [PATCH 1412/6300] use /sdcard/i2pd at android --- FS.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FS.cpp b/FS.cpp index 380ab2e5..89cdb7c8 100644 --- a/FS.cpp +++ b/FS.cpp @@ -54,6 +54,9 @@ namespace fs { dataDir = (home != NULL && strlen(home) > 0) ? home : ""; dataDir += "/Library/Application Support/" + appName; return; +#elif defined(ANDROID) + dataDir = "/sdcard/" + appName; // TODO: might not work for some devices + return; #else /* other unix */ char *home = getenv("HOME"); if (isService) { From 3e912c6198edc640c80a7e99de3ed19615cb6c28 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Fri, 17 Jun 2016 21:47:17 +0800 Subject: [PATCH 1413/6300] qt: daemon now operates in the background thread; added Quit GUI button --- qt/i2pd_qt/DaemonQT.h | 4 ++++ qt/i2pd_qt/i2pd_qt_gui.cpp | 0 qt/i2pd_qt/i2pd_qt_gui.h | 4 ++++ 3 files changed, 8 insertions(+) create mode 100644 qt/i2pd_qt/DaemonQT.h create mode 100644 qt/i2pd_qt/i2pd_qt_gui.cpp create mode 100644 qt/i2pd_qt/i2pd_qt_gui.h diff --git a/qt/i2pd_qt/DaemonQT.h b/qt/i2pd_qt/DaemonQT.h new file mode 100644 index 00000000..5c0ec14e --- /dev/null +++ b/qt/i2pd_qt/DaemonQT.h @@ -0,0 +1,4 @@ +#ifndef DAEMONQT_H +#define DAEMONQT_H + +#endif // DAEMONQT_H diff --git a/qt/i2pd_qt/i2pd_qt_gui.cpp b/qt/i2pd_qt/i2pd_qt_gui.cpp new file mode 100644 index 00000000..e69de29b diff --git a/qt/i2pd_qt/i2pd_qt_gui.h b/qt/i2pd_qt/i2pd_qt_gui.h new file mode 100644 index 00000000..493fb1af --- /dev/null +++ b/qt/i2pd_qt/i2pd_qt_gui.h @@ -0,0 +1,4 @@ +#ifndef IQPD_QT_GUI_H +#define IQPD_QT_GUI_H + +#endif // IQPD_QT_GUI_H From 1b35f68de9263db71c9a8e504493afa721a05c8c Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Fri, 17 Jun 2016 21:49:49 +0800 Subject: [PATCH 1414/6300] qt: daemon now operates in the background thread; added Quit GUI button --- Daemon.cpp | 10 +++- Daemon.h | 14 +++-- i2pd.cpp | 45 +++++++++++++- qt/i2pd_qt/DaemonQT.cpp | 83 +++++++++++++++++++++++++- qt/i2pd_qt/DaemonQT.h | 62 +++++++++++++++++++ qt/i2pd_qt/android/AndroidManifest.xml | 4 +- qt/i2pd_qt/i2pd_qt.pro | 30 ++++------ qt/i2pd_qt/i2pd_qt_gui.cpp | 24 ++++++++ qt/i2pd_qt/i2pd_qt_gui.h | 2 + qt/i2pd_qt/mainwindow.cpp | 51 ++++++++++++++-- qt/i2pd_qt/mainwindow.h | 18 +++++- qt/i2pd_qt/mainwindow.ui | 60 ++++++++++++++++--- 12 files changed, 360 insertions(+), 43 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 7ca28a6f..a00d85a2 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -265,5 +265,13 @@ namespace i2p return true; } - } + + bool DaemonQT::init(int argc, char* argv[]) + { + #if 0 + m_Impl = std::make_shared (argc, argv); + #endif + return Daemon_Singleton::init(argc, argv); + } + } } diff --git a/Daemon.h b/Daemon.h index 7467a518..fd9afeec 100644 --- a/Daemon.h +++ b/Daemon.h @@ -33,8 +33,10 @@ namespace i2p #if defined(QT_GUI_LIB) // check if QT #define Daemon i2p::util::DaemonQT::Instance() - class DaemonQTImpl; - class DaemonQT: public i2p::util::Daemon_Singleton +#if 0 + class DaemonQTImpl; +#endif + class DaemonQT: public i2p::util::Daemon_Singleton { public: @@ -45,12 +47,14 @@ namespace i2p } bool init(int argc, char* argv[]); - void run (); +#if 0 + void run (); - private: + private: std::shared_ptr m_Impl; - }; +#endif + }; #elif defined(_WIN32) #define Daemon i2p::util::DaemonWin32::Instance() diff --git a/i2pd.cpp b/i2pd.cpp index f3ac6b3f..2e9d5a19 100644 --- a/i2pd.cpp +++ b/i2pd.cpp @@ -1,15 +1,54 @@ -#include -#include "Daemon.h" +#ifndef ANDROID +# include +# include "Daemon.h" +#else +# include "qt/i2pd_qt/i2pd_qt_gui.h" +# include +# include +# include "DaemonQT.h" +# include "mainwindow.h" +# include +#endif int main( int argc, char* argv[] ) { - if (Daemon.init(argc, argv)) +#ifdef ANDROID + //int result = runGUI(argc, argv); + //QMessageBox::information(0,"Debug","runGUI completed"); + QApplication app(argc, argv); + qDebug("Initialising the daemon..."); + bool daemonInitSuccess = i2p::util::DaemonQt::DaemonQTImpl::init(argc, argv); + if(!daemonInitSuccess) { + QMessageBox::critical(0, "Error", "Daemon init failed"); + return 1; + } + qDebug("Initialised, creating the main window..."); + MainWindow w; + qDebug("Before main window.show()..."); + w.show (); + int result; + { + i2p::util::DaemonQt::Controller daemonQtController; + qDebug("Starting the daemon..."); + emit daemonQtController.startDaemon(); + qDebug("Starting gui event loop..."); + result = app.exec(); + //QMessageBox::information(&w, "Debug", "exec finished"); + } + i2p::util::DaemonQt::DaemonQTImpl::deinit(); + //QMessageBox::information(&w, "Debug", "demon stopped"); + //exit(result); //return from main() causes intermittent sigsegv bugs in some Androids. exit() is a workaround for this + qDebug("Exiting the application"); + return result; +#else + if (Daemon.init(argc, argv)) { if (Daemon.start()) Daemon.run (); Daemon.stop(); } return EXIT_SUCCESS; +#endif } #ifdef _WIN32 diff --git a/qt/i2pd_qt/DaemonQT.cpp b/qt/i2pd_qt/DaemonQT.cpp index 41242e3b..121d547c 100644 --- a/qt/i2pd_qt/DaemonQT.cpp +++ b/qt/i2pd_qt/DaemonQT.cpp @@ -1,3 +1,83 @@ +#include "DaemonQT.h" +#include "../../Daemon.h" +#include +#include + +namespace i2p +{ +namespace util +{ +namespace DaemonQt +{ + +void Worker::startDaemon() { + qDebug("Performing daemon start..."); + DaemonQTImpl::start(); + qDebug("Daemon started."); + emit resultReady(); +} +void Worker::restartDaemon() { + qDebug("Performing daemon restart..."); + DaemonQTImpl::restart(); + qDebug("Daemon restarted."); + emit resultReady(); +} +void Worker::stopDaemon() { + qDebug("Performing daemon stop..."); + DaemonQTImpl::stop(); + qDebug("Daemon stopped."); + emit resultReady(); +} + +Controller::Controller() { + Worker *worker = new Worker; + 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() { + qDebug("Closing and waiting for daemon worker thread..."); + workerThread.quit(); + workerThread.wait(); + qDebug("Waiting for daemon worker thread finished."); + if(i2p::util::DaemonQt::DaemonQTImpl::isRunning()) { + qDebug("Stopping the daemon..."); + i2p::util::DaemonQt::DaemonQTImpl::stop(); + qDebug("Stopped the daemon."); + } +} + + + +static DaemonQTImpl::runningChangedCallback DaemonQTImpl_runningChanged; +static bool DaemonQTImpl_running; +static QMutex* mutex; + +bool DaemonQTImpl::init(int argc, char* argv[]){mutex=new QMutex(QMutex::Recursive);setRunningCallback(0);DaemonQTImpl_running=false;return Daemon.init(argc,argv);} +void DaemonQTImpl::deinit(){delete mutex;} +void DaemonQTImpl::start(){QMutexLocker locker(mutex);setRunning(true);Daemon.start();} +void DaemonQTImpl::stop(){QMutexLocker locker(mutex);Daemon.stop();setRunning(false);} +void DaemonQTImpl::restart(){QMutexLocker locker(mutex);stop();start();} + +void DaemonQTImpl::setRunningCallback(runningChangedCallback cb){DaemonQTImpl_runningChanged=cb;} +bool DaemonQTImpl::isRunning(){return DaemonQTImpl_running;} +void DaemonQTImpl::setRunning(bool newValue){ + bool oldValue = DaemonQTImpl_running; + if(oldValue!=newValue) { + DaemonQTImpl_running = newValue; + if(DaemonQTImpl_runningChanged!=0)DaemonQTImpl_runningChanged(); + } +} + +} +} +} + +#if 0 #include #include "mainwindow.h" #include @@ -19,7 +99,7 @@ namespace util void Run () { - MainWindow w; + MainWindow w(m_App); w.show (); m_App.exec(); } @@ -62,3 +142,4 @@ namespace util } } } +#endif diff --git a/qt/i2pd_qt/DaemonQT.h b/qt/i2pd_qt/DaemonQT.h index 5c0ec14e..1d57d7e8 100644 --- a/qt/i2pd_qt/DaemonQT.h +++ b/qt/i2pd_qt/DaemonQT.h @@ -1,4 +1,66 @@ #ifndef DAEMONQT_H #define DAEMONQT_H +#include +#include + +namespace i2p +{ +namespace util +{ +namespace DaemonQt +{ + class Worker : public QObject + { + Q_OBJECT + + public slots: + void startDaemon(); + void restartDaemon(); + void stopDaemon(); + + signals: + void resultReady(); + }; + + class DaemonQTImpl + { + public: + typedef void (*runningChangedCallback)(); + + /** + * @brief init + * @param argc + * @param argv + * @return success + */ + bool static init(int argc, char* argv[]); + void static deinit(); + void static start(); + void static stop(); + void static restart(); + void static setRunningCallback(runningChangedCallback cb); + bool static isRunning(); + private: + void static setRunning(bool running); + }; + + class Controller : public QObject + { + Q_OBJECT + QThread workerThread; + public: + Controller(); + ~Controller(); + public slots: + void handleResults(){} + signals: + void startDaemon(); + void stopDaemon(); + void restartDaemon(); + }; +} +} +} + #endif // DAEMONQT_H diff --git a/qt/i2pd_qt/android/AndroidManifest.xml b/qt/i2pd_qt/android/AndroidManifest.xml index 603776e7..73f6eb6d 100644 --- a/qt/i2pd_qt/android/AndroidManifest.xml +++ b/qt/i2pd_qt/android/AndroidManifest.xml @@ -1,6 +1,6 @@ - + @@ -46,7 +46,7 @@ - + + + + + + + + + + + + + + + + + + diff --git a/qt/i2pd_qt/android/res/values/strings.xml b/qt/i2pd_qt/android/res/values/strings.xml new file mode 100644 index 00000000..fcc3eb09 --- /dev/null +++ b/qt/i2pd_qt/android/res/values/strings.xml @@ -0,0 +1,7 @@ + + + + Can\'t find Ministro service.\nThe application can\'t start. + This application requires Ministro service. Would you like to install it? + Your application encountered a fatal error and cannot continue. + diff --git a/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl b/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl new file mode 100644 index 00000000..bbd8116d --- /dev/null +++ b/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl @@ -0,0 +1,60 @@ +/* + Copyright (c) 2011-2013, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +package org.kde.necessitas.ministro; + +import org.kde.necessitas.ministro.IMinistroCallback; + +interface IMinistro +{ +/** +* Check/download required libs to run the application +* +* param callback - interface used by Minsitro service to notify the client when the loader is ready +* param parameters +* parameters fields: +* * Key Name Key type Explanations +* "sources" StringArray Sources list from where Ministro will download the libs. Make sure you are using ONLY secure locations. +* "repository" String Overwrites the default Ministro repository. Possible values: default, stable, testing and unstable +* "required.modules" StringArray Required modules by your application +* "application.title" String Application name, used to show more informations to user +* "qt.provider" String Qt libs provider, currently only "necessitas" is supported. +* "minimum.ministro.api" Integer Minimum Ministro API level, used to check if Ministro service compatible with your application. Current API Level is 3 ! +* "minimum.qt.version" Integer Minimim Qt version (e.g. 0x040800, which means Qt 4.8.0, check http://qt-project.org/doc/qt-4.8/qtglobal.html#QT_VERSION)! +*/ + void requestLoader(in IMinistroCallback callback, in Bundle parameters); +} diff --git a/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl b/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl new file mode 100644 index 00000000..f19caa69 --- /dev/null +++ b/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl @@ -0,0 +1,65 @@ +/* + Copyright (c) 2011-2013, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.kde.necessitas.ministro; + +oneway interface IMinistroCallback { +/** +* This method is called by the Ministro service back into the application which +* implements this interface. +* +* param in - loaderParams +* loaderParams fields: +* * Key Name Key type Explanations +* * "error.code" Integer See below +* * "error.message" String Missing if no error, otherwise will contain the error message translated into phone language where available. +* * "dex.path" String The list of jar/apk files containing classes and resources, needed to be passed to application DexClassLoader +* * "lib.path" String The list of directories containing native libraries; may be missing, needed to be passed to application DexClassLoader +* * "loader.class.name" String Loader class name. +* +* "error.code" field possible errors: +* - 0 no error. +* - 1 incompatible Ministro version. Ministro needs to be upgraded. +* - 2 not all modules could be satisfy. +* - 3 invalid parameters +* - 4 invalid qt version +* - 5 download canceled +* +* The parameter contains additional fields which are used by the loader to start your application, so it must be passed to the loader. +*/ + + void loaderReady(in Bundle loaderParams); +} diff --git a/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java new file mode 100644 index 00000000..f370b4f6 --- /dev/null +++ b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java @@ -0,0 +1,1622 @@ +/* + Copyright (c) 2012-2013, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.qtproject.qt5.android.bindings; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.InputStream; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.DataOutputStream; +import java.io.DataInputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; + +import org.kde.necessitas.ministro.IMinistro; +import org.kde.necessitas.ministro.IMinistroCallback; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageInfo; +import android.content.res.Configuration; +import android.content.res.Resources.Theme; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.ColorDrawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager.LayoutParams; +import android.view.accessibility.AccessibilityEvent; +import dalvik.system.DexClassLoader; + +//@ANDROID-11 +import android.app.Fragment; +import android.view.ActionMode; +import android.view.ActionMode.Callback; +//@ANDROID-11 + + +public class QtActivity extends Activity +{ + private final static int MINISTRO_INSTALL_REQUEST_CODE = 0xf3ee; // request code used to know when Ministro instalation is finished + private static final int MINISTRO_API_LEVEL = 5; // Ministro api level (check IMinistro.aidl file) + private static final int NECESSITAS_API_LEVEL = 2; // Necessitas api level used by platform plugin + private static final int QT_VERSION = 0x050100; // This app requires at least Qt version 5.1.0 + + private static final String ERROR_CODE_KEY = "error.code"; + private static final String ERROR_MESSAGE_KEY = "error.message"; + private static final String DEX_PATH_KEY = "dex.path"; + private static final String LIB_PATH_KEY = "lib.path"; + private static final String LOADER_CLASS_NAME_KEY = "loader.class.name"; + private static final String NATIVE_LIBRARIES_KEY = "native.libraries"; + private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables"; + private static final String APPLICATION_PARAMETERS_KEY = "application.parameters"; + private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries"; + private static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id"; + private static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id"; + private static final String MAIN_LIBRARY_KEY = "main.library"; + private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes"; + private static final String NECESSITAS_API_LEVEL_KEY = "necessitas.api.level"; + private static final String EXTRACT_STYLE_KEY = "extract.android.style"; + + /// Ministro server parameter keys + private static final String REQUIRED_MODULES_KEY = "required.modules"; + private static final String APPLICATION_TITLE_KEY = "application.title"; + private static final String MINIMUM_MINISTRO_API_KEY = "minimum.ministro.api"; + private static final String MINIMUM_QT_VERSION_KEY = "minimum.qt.version"; + private static final String SOURCES_KEY = "sources"; // needs MINISTRO_API_LEVEL >=3 !!! + // Use this key to specify any 3rd party sources urls + // Ministro will download these repositories into their + // own folders, check http://community.kde.org/Necessitas/Ministro + // for more details. + + private static final String REPOSITORY_KEY = "repository"; // use this key to overwrite the default ministro repsitory + private static final String ANDROID_THEMES_KEY = "android.themes"; // themes that your application uses + + + public String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application, + // the parameters must not contain any white spaces + // and must be separated with "\t" + // e.g "-param1\t-param2=value2\t-param3\tvalue3" + + public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_STYLE=1\tQT_USE_ANDROID_NATIVE_DIALOGS=1\t"; + // use this variable to add any environment variables to your application. + // the env vars must be separated with "\t" + // e.g. "ENV_VAR1=1\tENV_VAR2=2\t" + // Currently the following vars are used by the android plugin: + // * QT_USE_ANDROID_NATIVE_STYLE - 1 to use the android widget style if available. + // * QT_USE_ANDROID_NATIVE_DIALOGS -1 to use the android native dialogs. + + public String[] QT_ANDROID_THEMES = null; // A list with all themes that your application want to use. + // The name of the theme must be the same with any theme from + // http://developer.android.com/reference/android/R.style.html + // The most used themes are: + // * "Theme" - (fallback) check http://developer.android.com/reference/android/R.style.html#Theme + // * "Theme_Black" - check http://developer.android.com/reference/android/R.style.html#Theme_Black + // * "Theme_Light" - (default for API <=10) check http://developer.android.com/reference/android/R.style.html#Theme_Light + // * "Theme_Holo" - check http://developer.android.com/reference/android/R.style.html#Theme_Holo + // * "Theme_Holo_Light" - (default for API 11-13) check http://developer.android.com/reference/android/R.style.html#Theme_Holo_Light + // * "Theme_DeviceDefault" - check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault + // * "Theme_DeviceDefault_Light" - (default for API 14+) check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault_Light + + public String QT_ANDROID_DEFAULT_THEME = null; // sets the default theme. + + private static final int INCOMPATIBLE_MINISTRO_VERSION = 1; // Incompatible Ministro version. Ministro needs to be upgraded. + private static final int BUFFER_SIZE = 1024; + + private ActivityInfo m_activityInfo = null; // activity info object, used to access the libs and the strings + private DexClassLoader m_classLoader = null; // loader object + private String[] m_sources = {"https://download.qt-project.org/ministro/android/qt5/qt-5.2"}; // Make sure you are using ONLY secure locations + private String m_repository = "default"; // Overwrites the default Ministro repository + // Possible values: + // * default - Ministro default repository set with "Ministro configuration tool". + // By default the stable version is used. Only this or stable repositories should + // be used in production. + // * stable - stable repository, only this and default repositories should be used + // in production. + // * testing - testing repository, DO NOT use this repository in production, + // this repository is used to push a new release, and should be used to test your application. + // * unstable - unstable repository, DO NOT use this repository in production, + // this repository is used to push Qt snapshots. + private String[] m_qtLibs = null; // required qt libs + private int m_displayDensity = -1; + + public QtActivity() + { + if (Build.VERSION.SDK_INT <= 10) { + QT_ANDROID_THEMES = new String[] {"Theme_Light"}; + QT_ANDROID_DEFAULT_THEME = "Theme_Light"; + } + else if ((Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 13) || Build.VERSION.SDK_INT >= 21){ + QT_ANDROID_THEMES = new String[] {"Theme_Holo_Light"}; + QT_ANDROID_DEFAULT_THEME = "Theme_Holo_Light"; + } else { + QT_ANDROID_THEMES = new String[] {"Theme_DeviceDefault_Light"}; + QT_ANDROID_DEFAULT_THEME = "Theme_DeviceDefault_Light"; + } + } + + // this function is used to load and start the loader + private void loadApplication(Bundle loaderParams) + { + try { + final int errorCode = loaderParams.getInt(ERROR_CODE_KEY); + if (errorCode != 0) { + if (errorCode == INCOMPATIBLE_MINISTRO_VERSION) { + downloadUpgradeMinistro(loaderParams.getString(ERROR_MESSAGE_KEY)); + return; + } + + // fatal error, show the error and quit + AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create(); + errorDialog.setMessage(loaderParams.getString(ERROR_MESSAGE_KEY)); + errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + errorDialog.show(); + return; + } + + // add all bundled Qt libs to loader params + ArrayList libs = new ArrayList(); + if ( m_activityInfo.metaData.containsKey("android.app.bundled_libs_resource_id") ) + libs.addAll(Arrays.asList(getResources().getStringArray(m_activityInfo.metaData.getInt("android.app.bundled_libs_resource_id")))); + + String libName = null; + if ( m_activityInfo.metaData.containsKey("android.app.lib_name") ) { + libName = m_activityInfo.metaData.getString("android.app.lib_name"); + loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function + } + + loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs); + loaderParams.putInt(NECESSITAS_API_LEVEL_KEY, NECESSITAS_API_LEVEL); + + // load and start QtLoader class + m_classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files + getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written. + loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists) + getClassLoader()); // parent loader + + @SuppressWarnings("rawtypes") + Class loaderClass = m_classLoader.loadClass(loaderParams.getString(LOADER_CLASS_NAME_KEY)); // load QtLoader class + Object qtLoader = loaderClass.newInstance(); // create an instance + Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication", + Activity.class, + ClassLoader.class, + Bundle.class); + if (!(Boolean)prepareAppMethod.invoke(qtLoader, this, m_classLoader, loaderParams)) + throw new Exception(""); + + QtApplication.setQtActivityDelegate(qtLoader); + + // now load the application library so it's accessible from this class loader + if (libName != null) + System.loadLibrary(libName); + + Method startAppMethod=qtLoader.getClass().getMethod("startApplication"); + if (!(Boolean)startAppMethod.invoke(qtLoader)) + throw new Exception(""); + + } catch (Exception e) { + e.printStackTrace(); + AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create(); + if (m_activityInfo.metaData.containsKey("android.app.fatal_error_msg")) + errorDialog.setMessage(m_activityInfo.metaData.getString("android.app.fatal_error_msg")); + else + errorDialog.setMessage("Fatal error, your application can't be started."); + + errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + errorDialog.show(); + } + } + + private ServiceConnection m_ministroConnection=new ServiceConnection() { + private IMinistro m_service = null; + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + m_service = IMinistro.Stub.asInterface(service); + try { + if (m_service != null) { + Bundle parameters = new Bundle(); + parameters.putStringArray(REQUIRED_MODULES_KEY, m_qtLibs); + parameters.putString(APPLICATION_TITLE_KEY, (String)QtActivity.this.getTitle()); + parameters.putInt(MINIMUM_MINISTRO_API_KEY, MINISTRO_API_LEVEL); + parameters.putInt(MINIMUM_QT_VERSION_KEY, QT_VERSION); + parameters.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES); + if (APPLICATION_PARAMETERS != null) + parameters.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS); + parameters.putStringArray(SOURCES_KEY, m_sources); + parameters.putString(REPOSITORY_KEY, m_repository); + if (QT_ANDROID_THEMES != null) + parameters.putStringArray(ANDROID_THEMES_KEY, QT_ANDROID_THEMES); + m_service.requestLoader(m_ministroCallback, parameters); + } + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + private IMinistroCallback m_ministroCallback = new IMinistroCallback.Stub() { + // this function is called back by Ministro. + @Override + public void loaderReady(final Bundle loaderParams) throws RemoteException { + runOnUiThread(new Runnable() { + @Override + public void run() { + unbindService(m_ministroConnection); + loadApplication(loaderParams); + } + }); + } + }; + + @Override + public void onServiceDisconnected(ComponentName name) { + m_service = null; + } + }; + + private void downloadUpgradeMinistro(String msg) + { + AlertDialog.Builder downloadDialog = new AlertDialog.Builder(this); + downloadDialog.setMessage(msg); + downloadDialog.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + try { + Uri uri = Uri.parse("market://search?q=pname:org.kde.necessitas.ministro"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivityForResult(intent, MINISTRO_INSTALL_REQUEST_CODE); + } catch (Exception e) { + e.printStackTrace(); + ministroNotFound(); + } + } + }); + + downloadDialog.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + QtActivity.this.finish(); + } + }); + downloadDialog.show(); + } + + private void ministroNotFound() + { + AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create(); + + if (m_activityInfo.metaData.containsKey("android.app.ministro_not_found_msg")) + errorDialog.setMessage(m_activityInfo.metaData.getString("android.app.ministro_not_found_msg")); + else + errorDialog.setMessage("Can't find Ministro service.\nThe application can't start."); + + errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + errorDialog.show(); + } + + static private void copyFile(InputStream inputStream, OutputStream outputStream) + throws IOException + { + byte[] buffer = new byte[BUFFER_SIZE]; + + int count; + while ((count = inputStream.read(buffer)) > 0) + outputStream.write(buffer, 0, count); + } + + + private void copyAsset(String source, String destination) + throws IOException + { + // Already exists, we don't have to do anything + File destinationFile = new File(destination); + if (destinationFile.exists()) + return; + + File parentDirectory = destinationFile.getParentFile(); + if (!parentDirectory.exists()) + parentDirectory.mkdirs(); + + destinationFile.createNewFile(); + + AssetManager assetsManager = getAssets(); + InputStream inputStream = assetsManager.open(source); + OutputStream outputStream = new FileOutputStream(destinationFile); + copyFile(inputStream, outputStream); + + inputStream.close(); + outputStream.close(); + } + + private static void createBundledBinary(String source, String destination) + throws IOException + { + // Already exists, we don't have to do anything + File destinationFile = new File(destination); + if (destinationFile.exists()) + return; + + File parentDirectory = destinationFile.getParentFile(); + if (!parentDirectory.exists()) + parentDirectory.mkdirs(); + + destinationFile.createNewFile(); + + InputStream inputStream = new FileInputStream(source); + OutputStream outputStream = new FileOutputStream(destinationFile); + copyFile(inputStream, outputStream); + + inputStream.close(); + outputStream.close(); + } + + private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) + { + File versionFile = new File(pluginsPrefix + "cache.version"); + + long cacheVersion = 0; + if (versionFile.exists() && versionFile.canRead()) { + try { + DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile)); + cacheVersion = inputStream.readLong(); + inputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (cacheVersion != packageVersion) { + deleteRecursively(new File(pluginsPrefix)); + return true; + } else { + return false; + } + } + + private void extractBundledPluginsAndImports(String pluginsPrefix) + throws IOException + { + ArrayList libs = new ArrayList(); + + String libsDir = getApplicationInfo().nativeLibraryDir + "/"; + + long packageVersion = -1; + try { + PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); + packageVersion = packageInfo.lastUpdateTime; + } catch (Exception e) { + e.printStackTrace(); + } + + if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) + return; + + { + File versionFile = new File(pluginsPrefix + "cache.version"); + + File parentDirectory = versionFile.getParentFile(); + if (!parentDirectory.exists()) + parentDirectory.mkdirs(); + + versionFile.createNewFile(); + + DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile)); + outputStream.writeLong(packageVersion); + outputStream.close(); + } + + { + String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY; + java.util.Set keys = m_activityInfo.metaData.keySet(); + if (m_activityInfo.metaData.containsKey(key)) { + String[] list = getResources().getStringArray(m_activityInfo.metaData.getInt(key)); + + for (String bundledImportBinary : list) { + String[] split = bundledImportBinary.split(":"); + String sourceFileName = libsDir + split[0]; + String destinationFileName = pluginsPrefix + split[1]; + createBundledBinary(sourceFileName, destinationFileName); + } + } + } + + { + String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY; + if (m_activityInfo.metaData.containsKey(key)) { + String[] list = getResources().getStringArray(m_activityInfo.metaData.getInt(key)); + + for (String fileName : list) { + String[] split = fileName.split(":"); + String sourceFileName = split[0]; + String destinationFileName = pluginsPrefix + split[1]; + copyAsset(sourceFileName, destinationFileName); + } + } + + } + } + + private void deleteRecursively(File directory) + { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) + deleteRecursively(file); + else + file.delete(); + } + + directory.delete(); + } + } + + private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix) + { + File newCache = new File(localPrefix); + if (!newCache.exists()) { + { + File oldPluginsCache = new File(oldLocalPrefix + "plugins/"); + if (oldPluginsCache.exists() && oldPluginsCache.isDirectory()) + deleteRecursively(oldPluginsCache); + } + + { + File oldImportsCache = new File(oldLocalPrefix + "imports/"); + if (oldImportsCache.exists() && oldImportsCache.isDirectory()) + deleteRecursively(oldImportsCache); + } + + { + File oldQmlCache = new File(oldLocalPrefix + "qml/"); + if (oldQmlCache.exists() && oldQmlCache.isDirectory()) + deleteRecursively(oldQmlCache); + } + } + } + + private void startApp(final boolean firstStart) + { + try { + if (m_activityInfo.metaData.containsKey("android.app.qt_sources_resource_id")) { + int resourceId = m_activityInfo.metaData.getInt("android.app.qt_sources_resource_id"); + m_sources = getResources().getStringArray(resourceId); + } + + if (m_activityInfo.metaData.containsKey("android.app.repository")) + m_repository = m_activityInfo.metaData.getString("android.app.repository"); + + if (m_activityInfo.metaData.containsKey("android.app.qt_libs_resource_id")) { + int resourceId = m_activityInfo.metaData.getInt("android.app.qt_libs_resource_id"); + m_qtLibs = getResources().getStringArray(resourceId); + } + + if (m_activityInfo.metaData.containsKey("android.app.use_local_qt_libs") + && m_activityInfo.metaData.getInt("android.app.use_local_qt_libs") == 1) { + ArrayList libraryList = new ArrayList(); + + + String localPrefix = "/data/local/tmp/qt/"; + if (m_activityInfo.metaData.containsKey("android.app.libs_prefix")) + localPrefix = m_activityInfo.metaData.getString("android.app.libs_prefix"); + + String pluginsPrefix = localPrefix; + + boolean bundlingQtLibs = false; + if (m_activityInfo.metaData.containsKey("android.app.bundle_local_qt_libs") + && m_activityInfo.metaData.getInt("android.app.bundle_local_qt_libs") == 1) { + localPrefix = getApplicationInfo().dataDir + "/"; + pluginsPrefix = localPrefix + "qt-reserved-files/"; + cleanOldCacheIfNecessary(localPrefix, pluginsPrefix); + extractBundledPluginsAndImports(pluginsPrefix); + bundlingQtLibs = true; + } + + if (m_qtLibs != null) { + for (int i=0;i 0) { + if (lib.startsWith("lib/")) + libraryList.add(localPrefix + lib); + else + libraryList.add(pluginsPrefix + lib); + } + } + } + + + String dexPaths = new String(); + String pathSeparator = System.getProperty("path.separator", ":"); + if (!bundlingQtLibs && m_activityInfo.metaData.containsKey("android.app.load_local_jars")) { + String[] jarFiles = m_activityInfo.metaData.getString("android.app.load_local_jars").split(":"); + for (String jar:jarFiles) { + if (jar.length() > 0) { + if (dexPaths.length() > 0) + dexPaths += pathSeparator; + dexPaths += localPrefix + jar; + } + } + } + + Bundle loaderParams = new Bundle(); + loaderParams.putInt(ERROR_CODE_KEY, 0); + loaderParams.putString(DEX_PATH_KEY, dexPaths); + loaderParams.putString(LOADER_CLASS_NAME_KEY, "org.qtproject.qt5.android.QtActivityDelegate"); + if (m_activityInfo.metaData.containsKey("android.app.static_init_classes")) { + loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY, + m_activityInfo.metaData.getString("android.app.static_init_classes").split(":")); + } + loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList); + + + String themePath = getApplicationInfo().dataDir + "/qt-reserved-files/android-style/"; + String stylePath = themePath + m_displayDensity + "/"; + if (!(new File(stylePath)).exists()) + loaderParams.putString(EXTRACT_STYLE_KEY, stylePath); + ENVIRONMENT_VARIABLES += "\tMINISTRO_ANDROID_STYLE_PATH=" + stylePath + + "\tQT_ANDROID_THEMES_ROOT_PATH=" + themePath; + + loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES + + "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml" + + "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports" + + "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins"); + + if (APPLICATION_PARAMETERS != null) { + loaderParams.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS); + } else { + Intent intent = getIntent(); + if (intent != null) { + String parameters = intent.getStringExtra("applicationArguments"); + if (parameters != null) + loaderParams.putString(APPLICATION_PARAMETERS_KEY, parameters.replace(' ', '\t')); + } + } + + loadApplication(loaderParams); + return; + } + + try { + if (!bindService(new Intent(org.kde.necessitas.ministro.IMinistro.class.getCanonicalName()), + m_ministroConnection, + Context.BIND_AUTO_CREATE)) { + throw new SecurityException(""); + } + } catch (Exception e) { + if (firstStart) { + String msg = "This application requires Ministro service. Would you like to install it?"; + if (m_activityInfo.metaData.containsKey("android.app.ministro_needed_msg")) + msg = m_activityInfo.metaData.getString("android.app.ministro_needed_msg"); + downloadUpgradeMinistro(msg); + } else { + ministroNotFound(); + } + } + } catch (Exception e) { + Log.e(QtApplication.QtTAG, "Can't create main activity", e); + } + } + + + + /////////////////////////// forward all notifications //////////////////////////// + /////////////////////////// Super class calls //////////////////////////////////// + /////////////// PLEASE DO NOT CHANGE THE FOLLOWING CODE ////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + + @Override + public boolean dispatchKeyEvent(KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyEvent, event); + else + return super.dispatchKeyEvent(event); + } + public boolean super_dispatchKeyEvent(KeyEvent event) + { + return super.dispatchKeyEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchPopulateAccessibilityEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchPopulateAccessibilityEvent, event); + else + return super.dispatchPopulateAccessibilityEvent(event); + } + public boolean super_dispatchPopulateAccessibilityEvent(AccessibilityEvent event) + { + return super_dispatchPopulateAccessibilityEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchTouchEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTouchEvent, ev); + else + return super.dispatchTouchEvent(ev); + } + public boolean super_dispatchTouchEvent(MotionEvent event) + { + return super.dispatchTouchEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchTrackballEvent(MotionEvent ev) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchTrackballEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTrackballEvent, ev); + else + return super.dispatchTrackballEvent(ev); + } + public boolean super_dispatchTrackballEvent(MotionEvent event) + { + return super.dispatchTrackballEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + + if (QtApplication.m_delegateObject != null && QtApplication.onActivityResult != null) { + QtApplication.invokeDelegateMethod(QtApplication.onActivityResult, requestCode, resultCode, data); + return; + } + if (requestCode == MINISTRO_INSTALL_REQUEST_CODE) + startApp(false); + super.onActivityResult(requestCode, resultCode, data); + } + public void super_onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + } + //--------------------------------------------------------------------------- + + @Override + protected void onApplyThemeResource(Theme theme, int resid, boolean first) + { + if (!QtApplication.invokeDelegate(theme, resid, first).invoked) + super.onApplyThemeResource(theme, resid, first); + } + public void super_onApplyThemeResource(Theme theme, int resid, boolean first) + { + super.onApplyThemeResource(theme, resid, first); + } + //--------------------------------------------------------------------------- + + + @Override + protected void onChildTitleChanged(Activity childActivity, CharSequence title) + { + if (!QtApplication.invokeDelegate(childActivity, title).invoked) + super.onChildTitleChanged(childActivity, title); + } + public void super_onChildTitleChanged(Activity childActivity, CharSequence title) + { + super.onChildTitleChanged(childActivity, title); + } + //--------------------------------------------------------------------------- + + @Override + public void onConfigurationChanged(Configuration newConfig) + { + if (!QtApplication.invokeDelegate(newConfig).invoked) + super.onConfigurationChanged(newConfig); + } + public void super_onConfigurationChanged(Configuration newConfig) + { + super.onConfigurationChanged(newConfig); + } + //--------------------------------------------------------------------------- + + @Override + public void onContentChanged() + { + if (!QtApplication.invokeDelegate().invoked) + super.onContentChanged(); + } + public void super_onContentChanged() + { + super.onContentChanged(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onContextItemSelected(MenuItem item) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(item); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onContextItemSelected(item); + } + public boolean super_onContextItemSelected(MenuItem item) + { + return super.onContextItemSelected(item); + } + //--------------------------------------------------------------------------- + + @Override + public void onContextMenuClosed(Menu menu) + { + if (!QtApplication.invokeDelegate(menu).invoked) + super.onContextMenuClosed(menu); + } + public void super_onContextMenuClosed(Menu menu) + { + super.onContextMenuClosed(menu); + } + //--------------------------------------------------------------------------- + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + try { + m_activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + for (Field f : Class.forName("android.R$style").getDeclaredFields()) { + if (f.getInt(null) == m_activityInfo.getThemeResource()) { + QT_ANDROID_THEMES = new String[] {f.getName()}; + QT_ANDROID_DEFAULT_THEME = f.getName(); + } + } + } catch (Exception e) { + e.printStackTrace(); + finish(); + return; + } + + try { + setTheme(Class.forName("android.R$style").getDeclaredField(QT_ANDROID_DEFAULT_THEME).getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + + if (Build.VERSION.SDK_INT > 10) { + try { + requestWindowFeature(Window.class.getField("FEATURE_ACTION_BAR").getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + requestWindowFeature(Window.FEATURE_NO_TITLE); + } + + if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) { + QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState); + return; + } + + m_displayDensity = getResources().getDisplayMetrics().densityDpi; + + ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME + + "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t"; + + if (null == getLastNonConfigurationInstance()) { + // if splash screen is defined, then show it + if (m_activityInfo.metaData.containsKey("android.app.splash_screen_drawable")) + getWindow().setBackgroundDrawableResource(m_activityInfo.metaData.getInt("android.app.splash_screen_drawable")); + else + getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000)); + + if (m_activityInfo.metaData.containsKey("android.app.background_running") + && m_activityInfo.metaData.getBoolean("android.app.background_running")) { + ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t"; + } else { + ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t"; + } + + if (m_activityInfo.metaData.containsKey("android.app.auto_screen_scale_factor") + && m_activityInfo.metaData.getBoolean("android.app.auto_screen_scale_factor")) { + ENVIRONMENT_VARIABLES += "QT_AUTO_SCREEN_SCALE_FACTOR=1\t"; + } + + startApp(true); + } + } + //--------------------------------------------------------------------------- + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) + { + if (!QtApplication.invokeDelegate(menu, v, menuInfo).invoked) + super.onCreateContextMenu(menu, v, menuInfo); + } + public void super_onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) + { + super.onCreateContextMenu(menu, v, menuInfo); + } + //--------------------------------------------------------------------------- + + @Override + public CharSequence onCreateDescription() + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) + return (CharSequence)res.methodReturns; + else + return super.onCreateDescription(); + } + public CharSequence super_onCreateDescription() + { + return super.onCreateDescription(); + } + //--------------------------------------------------------------------------- + + @Override + protected Dialog onCreateDialog(int id) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(id); + if (res.invoked) + return (Dialog)res.methodReturns; + else + return super.onCreateDialog(id); + } + public Dialog super_onCreateDialog(int id) + { + return super.onCreateDialog(id); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onCreateOptionsMenu(menu); + } + public boolean super_onCreateOptionsMenu(Menu menu) + { + return super.onCreateOptionsMenu(menu); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onCreatePanelMenu(featureId, menu); + } + public boolean super_onCreatePanelMenu(int featureId, Menu menu) + { + return super.onCreatePanelMenu(featureId, menu); + } + //--------------------------------------------------------------------------- + + + @Override + public View onCreatePanelView(int featureId) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId); + if (res.invoked) + return (View)res.methodReturns; + else + return super.onCreatePanelView(featureId); + } + public View super_onCreatePanelView(int featureId) + { + return super.onCreatePanelView(featureId); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(outBitmap, canvas); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onCreateThumbnail(outBitmap, canvas); + } + public boolean super_onCreateThumbnail(Bitmap outBitmap, Canvas canvas) + { + return super.onCreateThumbnail(outBitmap, canvas); + } + //--------------------------------------------------------------------------- + + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(name, context, attrs); + if (res.invoked) + return (View)res.methodReturns; + else + return super.onCreateView(name, context, attrs); + } + public View super_onCreateView(String name, Context context, AttributeSet attrs) + { + return super.onCreateView(name, context, attrs); + } + //--------------------------------------------------------------------------- + + @Override + protected void onDestroy() + { + super.onDestroy(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyDown, keyCode, event); + else + return super.onKeyDown(keyCode, event); + } + public boolean super_onKeyDown(int keyCode, KeyEvent event) + { + return super.onKeyDown(keyCode, event); + } + //--------------------------------------------------------------------------- + + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyMultiple != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyMultiple, keyCode, repeatCount, event); + else + return super.onKeyMultiple(keyCode, repeatCount, event); + } + public boolean super_onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) + { + return super.onKeyMultiple(keyCode, repeatCount, event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyUp, keyCode, event); + else + return super.onKeyUp(keyCode, event); + } + public boolean super_onKeyUp(int keyCode, KeyEvent event) + { + return super.onKeyUp(keyCode, event); + } + //--------------------------------------------------------------------------- + + @Override + public void onLowMemory() + { + if (!QtApplication.invokeDelegate().invoked) + super.onLowMemory(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, item); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onMenuItemSelected(featureId, item); + } + public boolean super_onMenuItemSelected(int featureId, MenuItem item) + { + return super.onMenuItemSelected(featureId, item); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onMenuOpened(int featureId, Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onMenuOpened(featureId, menu); + } + public boolean super_onMenuOpened(int featureId, Menu menu) + { + return super.onMenuOpened(featureId, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onNewIntent(Intent intent) + { + if (!QtApplication.invokeDelegate(intent).invoked) + super.onNewIntent(intent); + } + public void super_onNewIntent(Intent intent) + { + super.onNewIntent(intent); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(item); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onOptionsItemSelected(item); + } + public boolean super_onOptionsItemSelected(MenuItem item) + { + return super.onOptionsItemSelected(item); + } + //--------------------------------------------------------------------------- + + @Override + public void onOptionsMenuClosed(Menu menu) + { + if (!QtApplication.invokeDelegate(menu).invoked) + super.onOptionsMenuClosed(menu); + } + public void super_onOptionsMenuClosed(Menu menu) + { + super.onOptionsMenuClosed(menu); + } + //--------------------------------------------------------------------------- + + @Override + public void onPanelClosed(int featureId, Menu menu) + { + if (!QtApplication.invokeDelegate(featureId, menu).invoked) + super.onPanelClosed(featureId, menu); + } + public void super_onPanelClosed(int featureId, Menu menu) + { + super.onPanelClosed(featureId, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPause() + { + super.onPause(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPostCreate(Bundle savedInstanceState) + { + super.onPostCreate(savedInstanceState); + QtApplication.invokeDelegate(savedInstanceState); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPostResume() + { + super.onPostResume(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPrepareDialog(int id, Dialog dialog) + { + if (!QtApplication.invokeDelegate(id, dialog).invoked) + super.onPrepareDialog(id, dialog); + } + public void super_onPrepareDialog(int id, Dialog dialog) + { + super.onPrepareDialog(id, dialog); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onPrepareOptionsMenu(Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onPrepareOptionsMenu(menu); + } + public boolean super_onPrepareOptionsMenu(Menu menu) + { + return super.onPrepareOptionsMenu(menu); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, view, menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onPreparePanel(featureId, view, menu); + } + public boolean super_onPreparePanel(int featureId, View view, Menu menu) + { + return super.onPreparePanel(featureId, view, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onRestart() + { + super.onRestart(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) + { + if (!QtApplication.invokeDelegate(savedInstanceState).invoked) + super.onRestoreInstanceState(savedInstanceState); + } + public void super_onRestoreInstanceState(Bundle savedInstanceState) + { + super.onRestoreInstanceState(savedInstanceState); + } + //--------------------------------------------------------------------------- + + @Override + protected void onResume() + { + super.onResume(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + public Object onRetainNonConfigurationInstance() + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) + return res.methodReturns; + else + return super.onRetainNonConfigurationInstance(); + } + public Object super_onRetainNonConfigurationInstance() + { + return super.onRetainNonConfigurationInstance(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onSaveInstanceState(Bundle outState) + { + if (!QtApplication.invokeDelegate(outState).invoked) + super.onSaveInstanceState(outState); + } + public void super_onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + + } + //--------------------------------------------------------------------------- + + @Override + public boolean onSearchRequested() + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onSearchRequested(); + } + public boolean super_onSearchRequested() + { + return super.onSearchRequested(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onStart() + { + super.onStart(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onStop() + { + super.onStop(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onTitleChanged(CharSequence title, int color) + { + if (!QtApplication.invokeDelegate(title, color).invoked) + super.onTitleChanged(title, color); + } + public void super_onTitleChanged(CharSequence title, int color) + { + super.onTitleChanged(title, color); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onTouchEvent(MotionEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onTouchEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTouchEvent, event); + else + return super.onTouchEvent(event); + } + public boolean super_onTouchEvent(MotionEvent event) + { + return super.onTouchEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onTrackballEvent(MotionEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onTrackballEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTrackballEvent, event); + else + return super.onTrackballEvent(event); + } + public boolean super_onTrackballEvent(MotionEvent event) + { + return super.onTrackballEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public void onUserInteraction() + { + if (!QtApplication.invokeDelegate().invoked) + super.onUserInteraction(); + } + public void super_onUserInteraction() + { + super.onUserInteraction(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onUserLeaveHint() + { + if (!QtApplication.invokeDelegate().invoked) + super.onUserLeaveHint(); + } + public void super_onUserLeaveHint() + { + super.onUserLeaveHint(); + } + //--------------------------------------------------------------------------- + + @Override + public void onWindowAttributesChanged(LayoutParams params) + { + if (!QtApplication.invokeDelegate(params).invoked) + super.onWindowAttributesChanged(params); + } + public void super_onWindowAttributesChanged(LayoutParams params) + { + super.onWindowAttributesChanged(params); + } + //--------------------------------------------------------------------------- + + @Override + public void onWindowFocusChanged(boolean hasFocus) + { + if (!QtApplication.invokeDelegate(hasFocus).invoked) + super.onWindowFocusChanged(hasFocus); + } + public void super_onWindowFocusChanged(boolean hasFocus) + { + super.onWindowFocusChanged(hasFocus); + } + //--------------------------------------------------------------------------- + + //////////////// Activity API 5 ///////////// +//@ANDROID-5 + @Override + public void onAttachedToWindow() + { + if (!QtApplication.invokeDelegate().invoked) + super.onAttachedToWindow(); + } + public void super_onAttachedToWindow() + { + super.onAttachedToWindow(); + } + //--------------------------------------------------------------------------- + + @Override + public void onBackPressed() + { + if (!QtApplication.invokeDelegate().invoked) + super.onBackPressed(); + } + public void super_onBackPressed() + { + super.onBackPressed(); + } + //--------------------------------------------------------------------------- + + @Override + public void onDetachedFromWindow() + { + if (!QtApplication.invokeDelegate().invoked) + super.onDetachedFromWindow(); + } + public void super_onDetachedFromWindow() + { + super.onDetachedFromWindow(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyLongPress != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyLongPress, keyCode, event); + else + return super.onKeyLongPress(keyCode, event); + } + public boolean super_onKeyLongPress(int keyCode, KeyEvent event) + { + return super.onKeyLongPress(keyCode, event); + } + //--------------------------------------------------------------------------- +//@ANDROID-5 + +//////////////// Activity API 8 ///////////// +//@ANDROID-8 +@Override + protected Dialog onCreateDialog(int id, Bundle args) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(id, args); + if (res.invoked) + return (Dialog)res.methodReturns; + else + return super.onCreateDialog(id, args); + } + public Dialog super_onCreateDialog(int id, Bundle args) + { + return super.onCreateDialog(id, args); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPrepareDialog(int id, Dialog dialog, Bundle args) + { + if (!QtApplication.invokeDelegate(id, dialog, args).invoked) + super.onPrepareDialog(id, dialog, args); + } + public void super_onPrepareDialog(int id, Dialog dialog, Bundle args) + { + super.onPrepareDialog(id, dialog, args); + } + //--------------------------------------------------------------------------- +//@ANDROID-8 + //////////////// Activity API 11 ///////////// + +//@ANDROID-11 + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyShortcutEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyShortcutEvent, event); + else + return super.dispatchKeyShortcutEvent(event); + } + public boolean super_dispatchKeyShortcutEvent(KeyEvent event) + { + return super.dispatchKeyShortcutEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public void onActionModeFinished(ActionMode mode) + { + if (!QtApplication.invokeDelegate(mode).invoked) + super.onActionModeFinished(mode); + } + public void super_onActionModeFinished(ActionMode mode) + { + super.onActionModeFinished(mode); + } + //--------------------------------------------------------------------------- + + @Override + public void onActionModeStarted(ActionMode mode) + { + if (!QtApplication.invokeDelegate(mode).invoked) + super.onActionModeStarted(mode); + } + public void super_onActionModeStarted(ActionMode mode) + { + super.onActionModeStarted(mode); + } + //--------------------------------------------------------------------------- + + @Override + public void onAttachFragment(Fragment fragment) + { + if (!QtApplication.invokeDelegate(fragment).invoked) + super.onAttachFragment(fragment); + } + public void super_onAttachFragment(Fragment fragment) + { + super.onAttachFragment(fragment); + } + //--------------------------------------------------------------------------- + + @Override + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(parent, name, context, attrs); + if (res.invoked) + return (View)res.methodReturns; + else + return super.onCreateView(parent, name, context, attrs); + } + public View super_onCreateView(View parent, String name, Context context, + AttributeSet attrs) { + return super.onCreateView(parent, name, context, attrs); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyShortcut != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyShortcut, keyCode,event); + else + return super.onKeyShortcut(keyCode, event); + } + public boolean super_onKeyShortcut(int keyCode, KeyEvent event) + { + return super.onKeyShortcut(keyCode, event); + } + //--------------------------------------------------------------------------- + + @Override + public ActionMode onWindowStartingActionMode(Callback callback) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(callback); + if (res.invoked) + return (ActionMode)res.methodReturns; + else + return super.onWindowStartingActionMode(callback); + } + public ActionMode super_onWindowStartingActionMode(Callback callback) + { + return super.onWindowStartingActionMode(callback); + } + //--------------------------------------------------------------------------- +//@ANDROID-11 + //////////////// Activity API 12 ///////////// + +//@ANDROID-12 + @Override + public boolean dispatchGenericMotionEvent(MotionEvent ev) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchGenericMotionEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchGenericMotionEvent, ev); + else + return super.dispatchGenericMotionEvent(ev); + } + public boolean super_dispatchGenericMotionEvent(MotionEvent event) + { + return super.dispatchGenericMotionEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onGenericMotionEvent(MotionEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onGenericMotionEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onGenericMotionEvent, event); + else + return super.onGenericMotionEvent(event); + } + public boolean super_onGenericMotionEvent(MotionEvent event) + { + return super.onGenericMotionEvent(event); + } + //--------------------------------------------------------------------------- +//@ANDROID-12 + +} diff --git a/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java new file mode 100644 index 00000000..c78aeb7f --- /dev/null +++ b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java @@ -0,0 +1,159 @@ +/* + Copyright (c) 2012-2013, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.qtproject.qt5.android.bindings; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; + +import android.app.Application; + +public class QtApplication extends Application +{ + public final static String QtTAG = "Qt"; + public static Object m_delegateObject = null; + public static HashMap> m_delegateMethods= new HashMap>(); + public static Method dispatchKeyEvent = null; + public static Method dispatchPopulateAccessibilityEvent = null; + public static Method dispatchTouchEvent = null; + public static Method dispatchTrackballEvent = null; + public static Method onKeyDown = null; + public static Method onKeyMultiple = null; + public static Method onKeyUp = null; + public static Method onTouchEvent = null; + public static Method onTrackballEvent = null; + public static Method onActivityResult = null; + public static Method onCreate = null; + public static Method onKeyLongPress = null; + public static Method dispatchKeyShortcutEvent = null; + public static Method onKeyShortcut = null; + public static Method dispatchGenericMotionEvent = null; + public static Method onGenericMotionEvent = null; + + public static void setQtActivityDelegate(Object listener) + { + QtApplication.m_delegateObject = listener; + + ArrayList delegateMethods = new ArrayList(); + for (Method m : listener.getClass().getMethods()) { + if (m.getDeclaringClass().getName().startsWith("org.qtproject.qt5.android")) + delegateMethods.add(m); + } + + ArrayList applicationFields = new ArrayList(); + for (Field f : QtApplication.class.getFields()) { + if (f.getDeclaringClass().getName().equals(QtApplication.class.getName())) + applicationFields.add(f); + } + + for (Method delegateMethod : delegateMethods) { + try { + QtActivity.class.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes()); + if (QtApplication.m_delegateMethods.containsKey(delegateMethod.getName())) { + QtApplication.m_delegateMethods.get(delegateMethod.getName()).add(delegateMethod); + } else { + ArrayList delegateSet = new ArrayList(); + delegateSet.add(delegateMethod); + QtApplication.m_delegateMethods.put(delegateMethod.getName(), delegateSet); + } + for (Field applicationField:applicationFields) { + if (applicationField.getName().equals(delegateMethod.getName())) { + try { + applicationField.set(null, delegateMethod); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } catch (Exception e) { + } + } + } + + @Override + public void onTerminate() { + if (m_delegateObject != null && m_delegateMethods.containsKey("onTerminate")) + invokeDelegateMethod(m_delegateMethods.get("onTerminate").get(0)); + super.onTerminate(); + } + + public static class InvokeResult + { + public boolean invoked = false; + public Object methodReturns = null; + } + + private static int stackDeep=-1; + public static InvokeResult invokeDelegate(Object... args) + { + InvokeResult result = new InvokeResult(); + if (m_delegateObject == null) + return result; + StackTraceElement[] elements = Thread.currentThread().getStackTrace(); + if (-1 == stackDeep) { + String activityClassName = QtActivity.class.getCanonicalName(); + for (int it=0;it Date: Sun, 19 Jun 2016 20:35:17 +0800 Subject: [PATCH 1424/6300] setForeground works. --- qt/i2pd_qt/android/AndroidManifest.xml | 24 ++++- qt/i2pd_qt/android/build.gradle | 57 ++++++++++ .../android/libs/android-support-v4.jar | Bin 0 -> 1422188 bytes qt/i2pd_qt/android/project.properties | 2 +- qt/i2pd_qt/android/res/drawable/itoopie.png | Bin 0 -> 8712 bytes qt/i2pd_qt/android/res/values/strings.xml | 3 + .../org/purplei2p/i2pd/I2PDMainActivity.java | 97 ++++++++++++++++++ .../src/org/purplei2p/i2pd/LocalService.java | 92 +++++++++++++++++ .../qt5/android/bindings/QtActivity.java | 59 ++++++----- qt/i2pd_qt/i2pd_qt.pro | 5 + 10 files changed, 303 insertions(+), 36 deletions(-) create mode 100644 qt/i2pd_qt/android/build.gradle create mode 100644 qt/i2pd_qt/android/libs/android-support-v4.jar create mode 100644 qt/i2pd_qt/android/res/drawable/itoopie.png create mode 100644 qt/i2pd_qt/android/src/org/purplei2p/i2pd/I2PDMainActivity.java create mode 100644 qt/i2pd_qt/android/src/org/purplei2p/i2pd/LocalService.java diff --git a/qt/i2pd_qt/android/AndroidManifest.xml b/qt/i2pd_qt/android/AndroidManifest.xml index 73f6eb6d..cf426bf2 100644 --- a/qt/i2pd_qt/android/AndroidManifest.xml +++ b/qt/i2pd_qt/android/AndroidManifest.xml @@ -1,7 +1,22 @@ - - - + + + + + + + @@ -45,9 +60,8 @@ + - - diff --git a/qt/i2pd_qt/android/build.gradle b/qt/i2pd_qt/android/build.gradle new file mode 100644 index 00000000..ef416b0b --- /dev/null +++ b/qt/i2pd_qt/android/build.gradle @@ -0,0 +1,57 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:1.1.0' + } +} + +allprojects { + repositories { + jcenter() + } +} + +apply plugin: 'com.android.application' + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} + +android { + /******************************************************* + * The following variables: + * - androidBuildToolsVersion, + * - androidCompileSdkVersion + * - qt5AndroidDir - holds the path to qt android files + * needed to build any Qt application + * on Android. + * + * are defined in gradle.properties file. This file is + * updated by QtCreator and androiddeployqt tools. + * Changing them manually might break the compilation! + *******************************************************/ + + compileSdkVersion androidCompileSdkVersion.toInteger() + + buildToolsVersion androidBuildToolsVersion + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] + aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] + res.srcDirs = [qt5AndroidDir + '/res', 'res'] + resources.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + assets.srcDirs = ['assets'] + jniLibs.srcDirs = ['libs'] + } + } + + lintOptions { + abortOnError false + } +} diff --git a/qt/i2pd_qt/android/libs/android-support-v4.jar b/qt/i2pd_qt/android/libs/android-support-v4.jar new file mode 100644 index 0000000000000000000000000000000000000000..2ff47f4f3f4485d6c5e0ddde7869a09a41557bb1 GIT binary patch literal 1422188 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 z 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

  • j^?+O;cG=X1)pRX??pxCbh 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
    (5BE9wc4hwaBkSM)Th{+i)cJSr^H1R8PvlG4 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%0D^9%lu@tqK^*Isb;gply46~t-Fm0bujx*9=2d)t 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%0APx;5*cv*ndo^u%^yWnFK{EO&F!rM^PW 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 z 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�h79FDEA-qk3J1i?HE^M;muuA8 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 literal 0 HcmV?d00001 diff --git a/qt/i2pd_qt/android/project.properties b/qt/i2pd_qt/android/project.properties index c6998b3d..4d07452b 100644 --- a/qt/i2pd_qt/android/project.properties +++ b/qt/i2pd_qt/android/project.properties @@ -11,4 +11,4 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-9 +target=android-11 diff --git a/qt/i2pd_qt/android/res/drawable/itoopie.png b/qt/i2pd_qt/android/res/drawable/itoopie.png new file mode 100644 index 0000000000000000000000000000000000000000..a5dc7b680ba9dee30161ffb1c9fcd17bbd6ddd84 GIT binary patch literal 8712 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 literal 0 HcmV?d00001 diff --git a/qt/i2pd_qt/android/res/values/strings.xml b/qt/i2pd_qt/android/res/values/strings.xml index fcc3eb09..713c7aa0 100644 --- a/qt/i2pd_qt/android/res/values/strings.xml +++ b/qt/i2pd_qt/android/res/values/strings.xml @@ -4,4 +4,7 @@ Can\'t find Ministro service.\nThe application can\'t start. This application requires Ministro service. Would you like to install it? Your application encountered a fatal error and cannot continue. + i2pd started + i2pd stopped + i2pd diff --git a/qt/i2pd_qt/android/src/org/purplei2p/i2pd/I2PDMainActivity.java b/qt/i2pd_qt/android/src/org/purplei2p/i2pd/I2PDMainActivity.java new file mode 100644 index 00000000..23b32312 --- /dev/null +++ b/qt/i2pd_qt/android/src/org/purplei2p/i2pd/I2PDMainActivity.java @@ -0,0 +1,97 @@ +package org.purplei2p.i2pd; + +import org.qtproject.qt5.android.bindings.QtActivity; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; + +public class I2PDMainActivity extends QtActivity +{ + + private static I2PDMainActivity instance; + + public I2PDMainActivity() {} + + /* (non-Javadoc) + * @see org.qtproject.qt5.android.bindings.QtActivity#onCreate(android.os.Bundle) + */ + @Override + public void onCreate(Bundle savedInstanceState) { + I2PDMainActivity.setInstance(this); + super.onCreate(savedInstanceState); + + //set the app be foreground (do not unload when RAM needed) + doBindService(); + } + + /* (non-Javadoc) + * @see org.qtproject.qt5.android.bindings.QtActivity#onDestroy() + */ + @Override + protected void onDestroy() { + I2PDMainActivity.setInstance(null); + doUnbindService(); + super.onDestroy(); + } + + public static I2PDMainActivity getInstance() { + return instance; + } + + private static void setInstance(I2PDMainActivity instance) { + I2PDMainActivity.instance = instance; + } + + + +// 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, + LocalService.class), mConnection, Context.BIND_AUTO_CREATE); + mIsBound = true; + } + + void doUnbindService() { + if (mIsBound) { + // Detach our existing connection. + unbindService(mConnection); + mIsBound = false; + } + } +} diff --git a/qt/i2pd_qt/android/src/org/purplei2p/i2pd/LocalService.java b/qt/i2pd_qt/android/src/org/purplei2p/i2pd/LocalService.java new file mode 100644 index 00000000..0cbdb7d2 --- /dev/null +++ b/qt/i2pd_qt/android/src/org/purplei2p/i2pd/LocalService.java @@ -0,0 +1,92 @@ +package org.purplei2p.i2pd; + +import android.app.Notification; +import android.app.NotificationManager; +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; +import android.widget.Toast; + +public class LocalService 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.local_service_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 { + LocalService getService() { + return LocalService.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("LocalService", "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.local_service_started); + + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, I2PDMainActivity.class), 0); + + // Set the info for the views that show in the notification panel. + Notification notification = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.itoopie) // the status icon + .setTicker(text) // the status text + .setWhen(System.currentTimeMillis()) // the time stamp + .setContentTitle(getText(R.string.local_service_label)) // the label of the entry + .setContentText(text) // the contents of the entry + .setContentIntent(contentIntent) // The intent to send when the entry is clicked + .build(); + + // Send the notification. + //mNM.notify(NOTIFICATION, notification); + startForeground(NOTIFICATION, notification); + } +} \ No newline at end of file diff --git a/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java index f370b4f6..677e8f46 100644 --- a/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java +++ b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java @@ -62,7 +62,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageInfo; import android.content.res.Configuration; import android.content.res.Resources.Theme; @@ -1589,34 +1588,34 @@ public class QtActivity extends Activity //@ANDROID-11 //////////////// Activity API 12 ///////////// -//@ANDROID-12 - @Override - public boolean dispatchGenericMotionEvent(MotionEvent ev) - { - if (QtApplication.m_delegateObject != null && QtApplication.dispatchGenericMotionEvent != null) - return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchGenericMotionEvent, ev); - else - return super.dispatchGenericMotionEvent(ev); - } - public boolean super_dispatchGenericMotionEvent(MotionEvent event) - { - return super.dispatchGenericMotionEvent(event); - } - //--------------------------------------------------------------------------- - - @Override - public boolean onGenericMotionEvent(MotionEvent event) - { - if (QtApplication.m_delegateObject != null && QtApplication.onGenericMotionEvent != null) - return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onGenericMotionEvent, event); - else - return super.onGenericMotionEvent(event); - } - public boolean super_onGenericMotionEvent(MotionEvent event) - { - return super.onGenericMotionEvent(event); - } - //--------------------------------------------------------------------------- -//@ANDROID-12 +////@ANDROID-12 +// @Override +// public boolean dispatchGenericMotionEvent(MotionEvent ev) +// { +// if (QtApplication.m_delegateObject != null && QtApplication.dispatchGenericMotionEvent != null) +// return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchGenericMotionEvent, ev); +// else +// return super.dispatchGenericMotionEvent(ev); +// } +// public boolean super_dispatchGenericMotionEvent(MotionEvent event) +// { +// return super.dispatchGenericMotionEvent(event); +// } +// //--------------------------------------------------------------------------- +// +// @Override +// public boolean onGenericMotionEvent(MotionEvent event) +// { +// if (QtApplication.m_delegateObject != null && QtApplication.onGenericMotionEvent != null) +// return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onGenericMotionEvent, event); +// else +// return super.onGenericMotionEvent(event); +// } +// public boolean super_onGenericMotionEvent(MotionEvent event) +// { +// return super.onGenericMotionEvent(event); +// } +// //--------------------------------------------------------------------------- +////@ANDROID-12 } diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 6f02385a..7cf27b1d 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -20,6 +20,11 @@ BOOST_PATH = /mnt/media/android/Boost-for-Android-Prebuilt OPENSSL_PATH = /mnt/media/android/OpenSSL-for-Android-Prebuilt IFADDRS_PATH = /mnt/media/android/android-ifaddrs +# Steps in Android SDK manager: +# 1) Check Extras/Google Support Library https://developer.android.com/topic/libraries/support-library/setup.html +# 2) Check API 11 +# Finally, click Install. + SOURCES += DaemonQT.cpp\ mainwindow.cpp \ ../../HTTPServer.cpp ../../I2PControl.cpp ../../UPnP.cpp ../../Daemon.cpp ../../Config.cpp \ From 9d9793e1afa4b62deb5827429c04d02bad6035df Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Sun, 19 Jun 2016 20:35:37 +0800 Subject: [PATCH 1425/6300] fs fixed. need another solution --- FS.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FS.cpp b/FS.cpp index 89cdb7c8..7d6b8efe 100644 --- a/FS.cpp +++ b/FS.cpp @@ -54,9 +54,9 @@ namespace fs { dataDir = (home != NULL && strlen(home) > 0) ? home : ""; dataDir += "/Library/Application Support/" + appName; return; -#elif defined(ANDROID) - dataDir = "/sdcard/" + appName; // TODO: might not work for some devices - return; +//#elif defined(ANDROID) +// dataDir = "/sdcard/" + appName; // TODO: might not work for some devices //does throw & terminate on Android 6.0 (?) in i2p::fs::Init()+164 +// return; #else /* other unix */ char *home = getenv("HOME"); if (isService) { From 4b8466e5e5f7dbad544cb3405e5fa9e65d9d2e93 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Sun, 19 Jun 2016 20:44:23 +0800 Subject: [PATCH 1426/6300] restored backg. thread; removed deinit() --- qt/i2pd_qt/DaemonQT.cpp | 60 +++++++++++++++++++---------------------- qt/i2pd_qt/DaemonQT.h | 1 - 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/qt/i2pd_qt/DaemonQT.cpp b/qt/i2pd_qt/DaemonQT.cpp index 2f8a824f..5cd43858 100644 --- a/qt/i2pd_qt/DaemonQT.cpp +++ b/qt/i2pd_qt/DaemonQT.cpp @@ -78,11 +78,6 @@ namespace qt setRunningCallback(0); m_IsRunning=false; return Daemon.init(argc,argv); - - } - void DaemonQTImpl::deinit() - { - delete mutex; mutex = nullptr; } void DaemonQTImpl::start() @@ -129,34 +124,35 @@ namespace qt int RunQT (int argc, char* argv[]) { - //int result = runGUI(argc, argv); - //QMessageBox::information(0,"Debug","runGUI completed"); - QApplication app(argc, argv); - DaemonQTImpl daemon; - qDebug("Initialising the daemon..."); - bool daemonInitSuccess = daemon.init(argc, argv); - if(!daemonInitSuccess) - { - QMessageBox::critical(0, "Error", "Daemon init failed"); - return 1; - } - qDebug("Initialised, creating the main window..."); - MainWindow w; - qDebug("Before main window.show()..."); - w.show (); - int result; - { - /* i2p::qt::Controller daemonQtController(daemon); - qDebug("Starting the daemon..."); - emit daemonQtController.startDaemon(); - qDebug("Starting gui event loop...");*/ - daemon.start (); - result = app.exec(); - daemon.stop (); - } - daemon.deinit(); + QApplication app(argc, argv); + int result; + + { + DaemonQTImpl daemon; + qDebug("Initialising the daemon..."); + bool daemonInitSuccess = daemon.init(argc, argv); + if(!daemonInitSuccess) + { + QMessageBox::critical(0, "Error", "Daemon init failed"); + return 1; + } + qDebug("Initialised, creating the main window..."); + MainWindow w; + qDebug("Before main window.show()..."); + w.show (); + + { + i2p::qt::Controller daemonQtController(daemon); + qDebug("Starting the daemon..."); + emit daemonQtController.startDaemon(); + //daemon.start (); + qDebug("Starting GUI event loop..."); + result = app.exec(); + //daemon.stop (); + } + } + //QMessageBox::information(&w, "Debug", "demon stopped"); - //exit(result); //return from main() causes intermittent sigsegv bugs in some Androids. exit() is a workaround for this qDebug("Exiting the application"); return result; } diff --git a/qt/i2pd_qt/DaemonQT.h b/qt/i2pd_qt/DaemonQT.h index 85a9dccd..98e8b4e6 100644 --- a/qt/i2pd_qt/DaemonQT.h +++ b/qt/i2pd_qt/DaemonQT.h @@ -25,7 +25,6 @@ namespace qt * @return success */ bool init(int argc, char* argv[]); - void deinit(); void start(); void stop(); void restart(); From debd13d8c3e775f9ff0095525614e97ea4c72189 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Sun, 19 Jun 2016 21:13:21 +0800 Subject: [PATCH 1427/6300] fixes: icon + localservice.java --- qt/i2pd_qt/android/res/drawable/itoopie.png | Bin 8712 -> 0 bytes .../res/drawable/itoopie_notification_icon.png | Bin 0 -> 33199 bytes .../src/org/purplei2p/i2pd/LocalService.java | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 qt/i2pd_qt/android/res/drawable/itoopie.png create mode 100644 qt/i2pd_qt/android/res/drawable/itoopie_notification_icon.png diff --git a/qt/i2pd_qt/android/res/drawable/itoopie.png b/qt/i2pd_qt/android/res/drawable/itoopie.png deleted file mode 100644 index a5dc7b680ba9dee30161ffb1c9fcd17bbd6ddd84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8712 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 diff --git a/qt/i2pd_qt/android/res/drawable/itoopie_notification_icon.png b/qt/i2pd_qt/android/res/drawable/itoopie_notification_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8fbe24689b0af36100a6afa238829d7ce96a6529 GIT binary patch literal 33199 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 Date: Sun, 19 Jun 2016 09:58:29 -0400 Subject: [PATCH 1428/6300] use /sdcard for android only if available --- FS.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/FS.cpp b/FS.cpp index 7d6b8efe..663c1916 100644 --- a/FS.cpp +++ b/FS.cpp @@ -54,10 +54,15 @@ namespace fs { dataDir = (home != NULL && strlen(home) > 0) ? home : ""; dataDir += "/Library/Application Support/" + appName; return; -//#elif defined(ANDROID) -// dataDir = "/sdcard/" + appName; // TODO: might not work for some devices //does throw & terminate on Android 6.0 (?) in i2p::fs::Init()+164 -// return; #else /* other unix */ +#if defined(ANDROID) + if (boost::filesystem::exists("/sdcard")) + { + dataDir = "/sdcard/" + appName; + return; + } + // otherwise use /data/files +#endif char *home = getenv("HOME"); if (isService) { dataDir = "/var/lib/" + appName; From bd092295a4e2e83f1477247d459e7af593aaa689 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Mon, 20 Jun 2016 02:31:27 +0800 Subject: [PATCH 1429/6300] fixed #519 --- Tunnel.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tunnel.cpp b/Tunnel.cpp index 1403330b..961cc97e 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -861,6 +861,12 @@ namespace tunnel // TODO: locking return m_OutboundTunnels.size(); } + +#ifdef ANDROID_ARM7A + template std::shared_ptr Tunnels::CreateTunnel(std::shared_ptr, std::shared_ptr); + template std::shared_ptr Tunnels::CreateTunnel(std::shared_ptr, std::shared_ptr); +#endif + } } From 51519361e24cce40fff8f4fee9b17ef59a9adc12 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Mon, 20 Jun 2016 02:49:41 +0800 Subject: [PATCH 1430/6300] various + fixed #519 --- qt/.gitignore | 4 +--- qt/i2pd_qt/android/AndroidManifest.xml | 23 ++++++----------------- qt/i2pd_qt/i2pd_qt.pro | 4 ++++ 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/qt/.gitignore b/qt/.gitignore index 8fe8328a..a0155cb2 100644 --- a/qt/.gitignore +++ b/qt/.gitignore @@ -1,3 +1 @@ -/build-i2pd_qt-Android_armeabi_v7a_GCC_4_9_Qt_5_6_0-Debug/ -/build-i2pd_qt-Desktop_Qt_5_6_0_GCC_64bit-Debug/ -/build-i2pd_qt-Android_x86_GCC_4_9_Qt_5_6_0-Debug/ +/build*/ diff --git a/qt/i2pd_qt/android/AndroidManifest.xml b/qt/i2pd_qt/android/AndroidManifest.xml index cf426bf2..69f6fde3 100644 --- a/qt/i2pd_qt/android/AndroidManifest.xml +++ b/qt/i2pd_qt/android/AndroidManifest.xml @@ -1,22 +1,11 @@ - + - - - + + + - + @@ -60,7 +49,7 @@ - + + + + @@ -45,9 +49,8 @@ + - - diff --git a/qt/i2pd_qt/android/build.gradle b/qt/i2pd_qt/android/build.gradle new file mode 100644 index 00000000..ef416b0b --- /dev/null +++ b/qt/i2pd_qt/android/build.gradle @@ -0,0 +1,57 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:1.1.0' + } +} + +allprojects { + repositories { + jcenter() + } +} + +apply plugin: 'com.android.application' + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} + +android { + /******************************************************* + * The following variables: + * - androidBuildToolsVersion, + * - androidCompileSdkVersion + * - qt5AndroidDir - holds the path to qt android files + * needed to build any Qt application + * on Android. + * + * are defined in gradle.properties file. This file is + * updated by QtCreator and androiddeployqt tools. + * Changing them manually might break the compilation! + *******************************************************/ + + compileSdkVersion androidCompileSdkVersion.toInteger() + + buildToolsVersion androidBuildToolsVersion + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] + aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] + res.srcDirs = [qt5AndroidDir + '/res', 'res'] + resources.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + assets.srcDirs = ['assets'] + jniLibs.srcDirs = ['libs'] + } + } + + lintOptions { + abortOnError false + } +} diff --git a/qt/i2pd_qt/android/libs/android-support-v4.jar b/qt/i2pd_qt/android/libs/android-support-v4.jar new file mode 100644 index 0000000000000000000000000000000000000000..2ff47f4f3f4485d6c5e0ddde7869a09a41557bb1 GIT binary patch literal 1422188 zcmdSAWmMJe`YnufNq2X5H`3kR-Q6wH-Q5k+(ka~yBGR3Ll%yb@weYFE|9k5>ll1nKUvImuX$ZFZbca|a5NBTXlRh|d}~RNfBb_10SA#2Qx&3@k{4%u8Uq0V15uQL zf(E_<^pBS*{?~`m0I&G#VL2grDRD6sRR%fnuW}RPvNH4xvoB@nX{IKo8LA~$^*V>TD~fI)7!X0_T=Fot$h{wV z{rbH>$l>-}Z$^*{6ej3(#MFmyNE(WOeJ19{Vz%O{hD04|NA&#&C7aB0*-4YjL{VI; zWOzx2cP@b;47Osxmh`z zxhR@B+gZ7|06s`E4QDG?v!5SDiW0vJm=Ka*(yG^fw6`@e21z(L{Z)LjdLaxrs5HE` zrd=YjZR?7R`z`1bJDmR;LFw!_SiNl2`#~9;U)P!P49*BaGP%Ov;S)!^lYl;;q0Mmg zdp)aWZVJaQt;d~%u~0Ua-bY&tmvrN)ZasdQU{{lqH20GDHH|dyZu69B@0S0_N6|_2 zt+yIDwJxt0m`uk0QF2VnYtD#PR`~$7?Lz)u{bsygPfdLU`(P68H1E9V9OT?*vjku7rvys=o z9ClDzZ9FiJd9Pq>IUlj4h)%O_CBL>AceZ9g$})BD$p} zANTSieXG9JkYo-5dwdP!ASig%GwDkvovI%Kg+f1ph}{6GJ)w{(?-i7grOBk@;@;MA zvIN)@79_lf@T3a;!Ryr8Pdx1YKfW*FiP(Jd;L=gH#j1goSgLNA>`|=uKs*JU^#$09 z1$W!|2KSA9n_wOU$HP_3WSEz1Ie6Sw+}ux~KvncNv~cIEZ*(UO41tYA3&;x6(k}>7_dtPEBs6)zJ zoQ#vKTlY%=fsOSSu8ziAGkpmmCZXQR829PzEEJ*Ic~l*l5haFPFQY_9x%o#!@CJ9u zq$+|Ft?CV1v9RLsqfD)%HUjYz3kTlfhK?x@gb*w}4fDvFaO-D4dTfd!_b=^3GF(zq zf9Y7&y*17}j_^%M;Ls8o{=(})Ad(@7$j*+xf!Nyb6Y%5P*EvTp!R@W0@u2vEZ<`kn zo9e@dH=P0dE(x-is`4jd31(I`Pfupe12ftO(BiB(v}=vb%ieBwkfDiPI!tB3mz?T~ zAAPG+N^DkwaXwpO-3QVbL3Kdhz_YzEm^x`^q98ru@tubj&p=NH(O?gc@xxp5( z>rImyxWzTWN@{UMhUuKG%sHNnEM8&T>nPwFofm1l9n~GUq2VM@G@>GFk2SK5Dbt0#Dc@58%Z}SP4PsMH=V+f<-*Z2IHT(Wu|qu=y45)I`h6biy_MZR zXl7g7b2MJSPxTj7llRgGr284_TdZ;7|BnuJ;gDf_VGE1XrsYqXC+xZJ$s-1G? zZDW+IE5Wh@--E>0zFqIZ05i2eBg_R66aG44^n)gXDhW1@ZlxPp!<4iwM88_HP33}f zDGR#-*{5Yqqm)_fe!W91L1gcvsEyzaPN%Na<*`AqemiDVDH(9DVAW=lQ7dNv)V3(- zXSza+yGbzpZce?b#WGeA9gI^@5w6E_9=KA9*WBp>G2h^unB%zk1H&ocLxm*k%(*^a zoVyJb5&Oe~SyNw;uv>3g+?$&yKeU`p4>LF3m`Hq0iKg$D+oE{buw`XRJ|yVLK^fc7 z7D}-VD|omU*b;pYurVhjXaEu zZO#719>6sKiq8QseB?93b2*wK67Z{Ol1Tl*@Vvz2G961_jPGm+9$!3B?aT+^Cdt{( zaenY}V5$9jfBp0V%x$9W%LyGDs0&WYPm+&Bl%-NMC42Bs0*;5ENi*&RJ7!T;<^+mp$|SkB+Dq2 zW~bQU!Sg1ubk#T~%$kK|+B>WTBxjbg&XmcNzH+Av%7wyfs}MMQgA>bP#efMl#iqlz zdg`4!P7sQ{A}EPvEix8loSQejneHCno240_P1?gD9p7x9lWUtL0E1+2_t}(x`B`xB z4aJ~<8h_-&Knv2cBS;v7UjR=ZvMU}Q?;#J@JtPqRNe{+gAlIDy68_(8lX6B@_WzA( zKGG;ey~Ip$kNw(Vll~n?fUsJB%NY`(1uPeP^Z&Valjvx?5{w$V`I_WsBg(bG!a%88STXQUE8kKUF!*>(x0?lp{w-? zAY?b@xOTk&B5timON$La`{OSW{|&pVxO&-|{nrz}Ioum`vGa!WW^-00ggmh--~@$% zKvs*QxVSOv@1-W&8wmlh)HsxWUB8msKYrLj1tHj)3t~l_;%_yaiIz(s=Sqgfb~Wx$ zquPfho$#TpLx!&@6xL}Q%9tQW*0nJSNfAGQKNT*!NSTo_ctn2cAsfVL8XQCoT5*;O zem?Qd$Q48nr-E}4N-Rt5p|3}F(tF*!`c+M38jCv^55Pj~JC8WTP(B(29gTbTFd3c`U6HUlZyktbW=@>`=;-E${E~C)+0{3|(n^hQ z(K5#X28rGdwJ8r#(XAQ8Ge`h9@?is72fZuGCA`Z3=&Hxql*J>jJ#*CId-)vJ^eZ8|Sgcd@;*BVB^KiEDPl0x4h!$?53 z7MhqyVU|UI^;19%h|YLxGR1p|n6G!9Kkqj1j{94S{Sjd*f$kOKBnPC#+(H&_OBjUB zPBCozp{J6RvFmnG>o8PC%axazNE@|Lb;YK3WJ>o19?&0yacl58C@pQ%>FjKO5FTsF zBsl2xGZk7%J1VWk*BMX6Tnq z-E{$_)h(%B>MIA91u@teTFaWpzkhOUI zA!B0rV1*ZCwZ9FDk|5jLaVQ@rUcw4S@ylaXfTp+P<#wKPa(;vW63usUUZLNw@BcgJ{2$OikJVo*C#j|XGv$QN z%+{`b#8z05p21>@3Pd$!$ z6QY3ikv~e6LwK**4Y)q0{3qoECT8C`>Mn;-Apn(BW)xSzv5njkM^umhEr_TQu+yAe zp1X||y#VYqp}Ywb|KBMm%CA8uzlWaT*GrA6vyr`v70~DT7yO@X8i;~GgK&L5EI|ZO zMgYsBCZNbMk(YR1G&TZknv+C#nLdNBn~3KU*cSZZ6mqMk766-O$%BP3&*2Qj-sxm( zM&wg}!Yd!+F}{L=G0A+~17*xHnj%sf`+a9}2>C)~ou+(DwYOH{e$_E42-M&V;;|R3 z38?*t$tylT%rxJdz!I7ugeBs5jI}&*&W2CN3dL{}_4HPkUui zgf4%OtQpWCeLsod0}9IC<<_dwztCcrKl+r}K=5E!(*kXGV)!Yf_ATnoyR(S^)*OHh z1_e!5A2*5#Aeoh463uV+cXon)7_ky3f47^Yww6>GgV(1XA{53jDK4 zmId{7GDW~pPo_}K^_sAKagSIcZdvM8;evGqHOmmsQ8K$tKvtK9F#RN7a(Bo(n-9KH zL$fFPo9*dRBh0x2rDpZKl7R-8C+sC0(7Zlo*BlpqNz9NRhyBlWrEVv2*-7aPJ9yK}kI!8)_9q&pK; zWKg{0%l*sly(WY2X$UL&(yXzYeNOS#NVy@pg+R$ni2`dJv`t|?uMmrA0!B!jj9vdM1A+tk9MF16zXZ+ibT>g@l~s%RcVmaqTMPgL2KxOKaDrxW8OYe}Z3k>Mr$-c%2l5Hi=p^j2 zwE?_Gj)+K)ntaU5(cmqh&pubuIIxC!5G2}uUD^>t_Vyc|Y@WDZ=yJM5Hi3{RDr7%V zu_i0twdWnr&TPD3=!+&a?*yEt^9J7r=JM|=w+A~|+7ER__tHbDTcwUk-yNE6dF-sK^LFvx?iC!6&C8LF2Y>Gh{APDiv-($2 zDhJrn0j}?rO;eM$AOpbwBbBEUz!XSn-Y#f2<3t}&Y3H3!wyU>o2zg>hd;;-LPKVY; zgoC`CiuW>KXZkri9e0g3`UAp5Ld}b#_)2**P(*x4?rr2r+H#{(47m{C%yt8Fca`N; zK2w*(Fez7(_ur2&zowa=!0d+b&)+c?8dG#0cT}JH>3z*>kkD7T-4eQ1sbJUYiTKY) z0lcqEfEkI6J+7DhDU7=9?7~eP!**W{mQ~nzdM%>xRIbQd+1y@hu#fbCSv)O;gc8`Mie7<_k}%fs6q6@L=*EXTG<*8%JGTf&wo#k!$d{ zJU06RWFXfhHN*l@Gl+wpijg7`v_muzG>l2Hbi-0uoMT+H#>7H(sqKQrq#j2%@BkfMwuLR-VW zb*2^IGp_GTDn5LSG6-Z2nu-p~CeW9}r%;(L)0{>WU-i%X>Nu9?7iW$}L*FR7RzK#V zm;T-VHdUf>wFv=(Ru(>JW!cId7oW*+Cf^6-hAscTlHM{2UwNL`NAey)7&CTrvE5-t zlq(=NMTORn)2tT)MxQoJh;KzxcZ1 z0Yb@zP)E2;G!TUp1{#&;yeDZ`Z3n+6M_)~jyDdo+E`@;YNblDBrrNsNg}l~GPlmOE zl0n1#O5L!?NOr$iC4;*}M$3{CWGYcxA`Nuqqqe{Pav_@vp#i?B`CH%SdC(H;FP#H) zZ9US7==X1?lK0nnnf>Th<(nn$HMPdiKXtwBvDd{a8@f``ax1VBAA-eu{HXZ^GjHN- z+Bw8V^CRGjr5aLYx@F0VETU)qF<5n<)H#|ro6x}u$7Rr!hZvII(zQcrS#m|`R_0sV zClpQZ4e1TBGQK$TdAYee5!M*vQ||?Px5WqPOrzX#qcw~qsnibtwsnG-o^R2ju1;cL z>!VM)`VC$%*{kKF7iS$_E-iHaD(JlrdYcCt_~4*sRqvX{)JKhD@NXS|49!|VE1Wc& z;Nx<}y4X6GgjF#Z!mT`Zn|?tK8xO|k8>-QGfQD8$pO3Q8Kk>T2lZoCi_1%nXRu&)m z;au|g{Iz6PB&BQx91KRVUxkgo*|dc0|6}k4`nN3#NO?6$MsY@jTRoY~to}9l%C;sU z;HZ<@$e7~$kZ}VHzFuLb5lr!A`?MOqRgldca?VE?o!Rup9Ia|V(dl(W-&?L|$_8Y- zsH06B>Y8S*Lynlh+)Mu(F!z$nt}SVHzUXn(nGl^r5h5T?sg`uMUIk>lS)zly8Z&VB zrhSfjx=iRt%G%q2j8}+c&GW;WI0_*5Dpe>F5V4yel@B8-L|_2%=_YX(^?2FPH0x2&`sWwxgpi8z#AR%3yt%*0Uc(Z#^PF zlsY&Hsv#LhOS!ptE%E#l-yPVa(vX(PKht$!65!cW62uC1fWQF$If+bx^mAo8IF87i zmn2~3wzOtvAX?v(e0E^ynF4bEbM9jco+4$Q1g@EzkU0ww?>bow6>{y*VG( zGby46m;{u{uuK4E{`d3~?+f&hxb|0FbS(Dt{Ks zd>fLrmnD~&+3n{qAfMkv$eUS9#RAG1l6B(W0Mr#eAU@Jm{x%S5&G@lAwusyYr1MU} zp>76>6nI5edjrzrV?UDs$!>(8-_QKt#EmLe-hbriAqms+0IfV^5ui*=b7!Cm&W-eb zF-SszHW|7+IXQH|lL)TLRLTn1GQxBA;1a0cKHI7u1`O~|O!;m&cyKg44}3=ma&%Z4 zG&L^L{E2i_XV8nUQIG-jpWF3&K)2e|o<@Wz6}w(zl9+7K)Sfq(j^-w(g>-O;Yu_Pg zJVApVHDk(u>!iRtiMNijc+G9hDo$h(Vox;GNm^QXaBlXAI-rmkd(!As(F9JU!Lvs% zLl2eeM^Gc5#MJ>b4SG=X3ixFu;-W3f?hEIVk6owd%p(?;PS{**DlZCq{HW0__ zb@iRZcnbE2&=NM|X=ZF{4D5Prj-bMT!YICzHb!@N6DdqXQ>>&J2a8K(S}2U+urkC3 z$@?ya-iGu9B2r}Z-=Sx|G0VKDjD~adU z1fC#-V~f`yK*Hipy6^60&8R(_rhg-$E6x=+6XPgkNjbC`Cx2ONnJgHFV1~o-Fv-TZHB$P;c|}^L5>}b*HO};~VYTohvgQl2y1I9n!C@S7Jt0 z4>n77Av0?~xN{h}U4r%Pvc6Y*0~aP;1yd~x=-+6GP!E5v7$eLF`;ECM1~ps#2rAqu zbP5rOszJJw?GpgA#$VFWZ}I|RD|^%boirGXRK+R^3$`33C^&s}S5>#fJ^5E@FelgL z%!T9TR(t{W5lP&Ue(AtWTM|t{cJ~2U;&Fy1Z0OEAMe(U4F@{~Tl3e9wXwoKartCPp zoibrLzYC3XC?1CWVtDcvc#Az*$srVu21!5axxQtOA40=CDHmhxR}$~3jJc>CHc9dl z>sH0Q{L&L22n@_j3x;;$1;N2W)df8xJ?~ZzfFVuo4sn0Ub4bGo3~83K8`|hYZ+VFLvGs~mfU!WUgb&IXX>5cgt)PSCEWW>kdnV&d=GIYn7Qg~oU z)2MS+;n;YzaVc|H+9e$3%A$qyF|hLnukw{q-g<0MKVc>8o!WJYY})QNsknG6smm#A zGuDBo;qqM8BHzfK92DcrY26g2V<(sdvWnBlWO%DP{0I`rQXQ=H&F4K7P3ZBu=+QKX z;oh%_eE6Pxc@U!kXZ-ftJfR?h4;|w*8>=;rcDH8wgk7xdT^~br3j!r;1bp5V??jW>{U-&>(3?Ki7A2`WO`J zN|O{rdlz4gZ!(6U#Fk#4wi+M5Gg z(XB1AwpPnX8#FWU0wL!ilx7Q>o^n`zSziA2^A})LJqiby3m^WHb$%nj0WP1i zk-f#A16gStFp$ysNs}Q-pt~-RV<<%R7DE7i2#U>!q0{G8h5W zm}h?<7DqChemCY&ywxbz=ZFeI=ulgFRRYAYZTA6-?1IC=g3IB;9fj}qM5$FOp)MLG zRc|&ie%NT{Dw534@&_^R2+1~<_~*-E-@R`mpz3h19CU1doj+Mrpcvetx#0oIOfhuh zO9Gwj?YtZp11r*Q<-_udSOaJ+BEi1 z2`lottz{NJBG;Hp=OMX7mAsU_EQ#ovGx17=iAqK(^=*H#uy<2D@kyyTNfh21Cz_oS zFQZ})hr8=tba4;EP2ZJHN3s+;I!u>dXw0qUjNxK+q9W~K-s|Htv6v1nWJ>=K>Y$9p2ubA! zDm4dCq^I4}UR-aZsQX+eNh~tIf*jzjyWdNj^f7If*u8IEEWD^IU9-!VZFz{!4aosxh zD(0p0#q1F&B^Ge2d(o(3{4L8UY`A6WO>3TuLa0Top&c{KT`2!j6e9gcQAl`iz7uE( z(nS0ROAw6wPgw?2I{k+%i~CcSAp>Pu7C@He^}X2=Iux@`49VNeijn%V;eXo!kY&7a zf5#%8^BqFn~F79K8KsIWnx; z%q}2Do~Oz<)$Jt$=E&`xM951h^iH>X_pb_c7?2Pr z@fSyy&8_2T36pii=YAHjE-`3s!t%49b$Bu?zfUVmACxsGchkLO){`%M3$j}N>RQJ} zk#eQzt;9|I<;!3SJfZP6Mny5M(B>pmN_@&PgXgV&XBY1Gvg|i5gr?m;VgDrPp&YP8IdyU4-1XyrT&Ro)6E(xE}m$AIEKA zJqtdwj=CN#Oqn=n;j6S*`fV!E6ci0k7EHLpUzv{#6hVeI? zGY(nae$4H4<3=q2THw~W1+dnu-=T7_$Ujum9)jxn&_AU~B;_aDhEsXgSN?02hP&NJnTpN(p%$`PlB~5u74b zDFE~Zf9*s3W(12`*_r)Ier)qhHkxjg)eEojy{W!~#+~+>D3sTzE*4g8mxQ5wB1*6$ z>Co!!UJ&tr<*#~FoGfir5YWk*;YGnZ3an1NVfd^0{7g2l$P)`@HENY5SdOa(#F=zO z+nUjUE&snApy%fEpAOIme07FX?3$?EpfQFWXFJBgNOz19EdB>Fvf5_<|<@|037^N)5% z0914FTw2kgzI;atK>pXo=ieX?43dEjtv_|_A2bgLlMC;hE({-~fnjpobC`_wSD3s; z_czV6EAiPHCrtr5=NT;-anmgxAT3-7j!2tKJqfQ|Or!Wpa_7iqycrr(QRO&Y+7dx2 zrHrz*XQH%ero@LdDRb)0sU2r;0Y-R6$Yl$XyPY=O2v_DhOm-S83^xUradC5EyW!E4 z;NgsQN{wH?pCC>QxcoTo5KCTy$!UEjfz9OxZY`_v+b6U*LP))UAtq^(*8?!LR|Go( z2Q|eR`|5K2W(j5-9{$6Rbkh0n6R>6XlX(Ifl=miYctAnXyVUMfdKX%%3#I_6{R=>9 z&j~FDNbSRF-=b(Qel_37x_L(0h&6x!7$(mEe*U{<@Ee^4Y~32UI{Y(Z`+t-?2xvId zM>7c1g*|uS`lYekhT7=%NJMIqIr=24|Nj>~MEX|p{=WUm^qVabaF@v0(ZTlLK7zkU zKA(h!;F;w4-u-dOVlwf0uK~6v)=u1|JlPQi-T;QHHj>3yo973sj3jJE5Kk7A2mIO{RZS40M*NiHH!lH#!Eo)a3RZH zh3xGUAnYuB5j2!O3Ne z-)P(Q_4D@yJ=`4YZwrG%OoP9!oBELcjfFT=oT{0J6WQ!b$s%;3;Ni)A zo|9&S>U?Xvl_&27f@M!Yn*})*pmbTbz+Y09$!E$>{ZzVOi~|F}G5c$!`y1?k+6;eJ zT9n460e85nrfk@)2_m78l|%#eR{<-I5Gi!x%Z^GS?h!Jp{@_vD#R>@OE1c?ehInKbwob+AnQJLt1deF02M>2X~`bSiS;%7vcrXq#E?D} zuOuq}dD)U_VzK>Nhc9bEGY-L(_-fksZ2Jiv`a&V1-QC?i-nXx)@~QNs+I=;o5!M~n z`kGgV1IL6hv3M5;*FoKdqG2lpfg5Gd$wLv)T>J3L)6Vj1)={@|u>wq2aR+BrGZ$BY z$MeUWRp@{P6@t&c1>_a>+8sJApM}yxL zGG~#hY55A#1LB5=j;MynF3im&g?1@QL&2uOl4>)Eib~JjtnBc>>x34(Kr*X-dLI$j z2uQZ9TxAbsRbp#{*S~E;+`%z*Ym|Uqd5xa zApi!ieu>g=_HJb(kAD!tUzGem_wKnhpwa{|vW$#-2iBjxSv)cdKtudT#?&JPYtHli zAI~>3h*NxAx0``NtSn-wn~EhP`Q*DDJ)JGdxTBlWXv-Do@@h5a)PUM-iaIu+HVcqD zWy*}c_gUvq6x8Z(>8A*>S5sL*f7WJ48uN$N{AVJh)au_;Xx1&E^oC?_qR}El0Kn9UYV62WGJ}^GA`I8 zEeQsl-VEh8oblw+GuOe}OG7O&KCj)UFIrUsKFj}$+-ck#X{-H*+<88n+UCW9K)Lhl zt@}43U)2lQz%`@8GMKU)Ok^xAWyIA27QohEQq&tcE~c&cS2al^1OX>YUMW4W zF(&2dk*2zP_^GgFzW#lONF!8Mov}(n2i#6nb3$ypMWA1*Vv#fzooSdhlC4lKwaJj^ zJ_ehPSvaK`@HG&VDqeoj6yslygq$tfOoD_2N6@RT10SR3K+qJ>;9FQn4|t4^K!eMB zBDIr54q@ae*q$>_dgbLV+>^+l@s>9izXjsVXmGP+TFG>Exi=;(D9c7MkK~HXLdm$=U2mva|^h%lwr)qihG@j1Z zePK-t!G_VNr8yEpC(awgsiLFazPnfUpf}SbB;PTz_*MzRz|uwWmR=i^xLDin8ZcG zUARGPs1|I=W1l8&>9F|f46ZHGg4|Nw#YWC9mgURrH?D5fZhPaMUpm%CKFrWBo@qEC zA)=X-Q~|D!!*f#!Aafhzb#=SgS&=CJ<6pn|HVr&6y1|6K-@pvDEtYhs;lwEVQasc~3+ zrE^+PXl)ZZb26MlZx$;un86%4dRk;F8Ldn95~;WlRC6-CXi5@h+C@4O##cE#oEhGp zN&}zgcY^Eq&&^p{ncDaeG~lvUZ7z!z*H|TWi6M$# zYx+1QSc$*nSN{a|A$Mj6KS7b@uv6EAp}WATaqU9={KN^2iHtJ1tARXq>MXyj7xXQR&E z#mLXt-EIsYssRP>8%WAp7L%=kQJ-ezg{~c9DoY-o)}us9~Ks8tI$Da zHRly&JNq(dPW91>DfJyeOSGt%KwoTs2NgP;L=2CPbHbu{lH=!ya}Jj9JDImZYF5EZ zsT}W2CoNkMzjR`=Hs5T(Uz?C}D(@a7?cq++ZZs%pgW3+;aqI-odKLG+UNvT3h2^(j zi3r?4QVnyD&eqxjeOt~ zX|;g*m?@etD9(}`K0wcU(fux*PjqRSF*=($3#0~Qieq>f*LN=^3B^$ z6lWOLZ^NUWo+j_OJDDF0LqL|Bjfd!-D5B5&Et-()QzLnJ+?o^ZymroDAHoCXJ=K+M zrBNX58n-XoIe=+?9JaVw*4^Im_k8}fqj z^GO6tHKJS{M}HLRC0wd$MNmAaREMDSgYe(ioeZ26AtueJN@vXi^PY4BWrbo@dfm&> z^|m>k8Y4QXb?62xhdi}6z)gw{jXfz*7QV6{p4L;O-{4&1C38BC_B(MA(o`ERLOC4u ze64<;I_-XKZhpjZHo5^4bFu!7bB~ul-dOp3iWUl#A1qWQ!{N|Okv1o%`cb}R;HlED zguUL^jow+kCFm*9&x|0K&Wj# zSDE6ZmSo3bMJSH&)mV`g>@+1xm*yGbMOM+`du-}OPHed)c^gV>m$RZF^0SsGa;aG= zY?(Bi3MpG`JbFd2nWZzXG3N0GMbfbEKAG`rqjk`j4XR+28H${lCe6K#tbmx6jWJh> zXR-&$o3rHjXvr3~U!S)NgRFaWg-UHSmnhmND~sN3?301xAP{evf z7c+&?tA;3H68r)3R<1A5Lq`*7if>f3f7A}CgChARRlIVUqb{=nrMYFnm29F~zMAF7 zE@*;qQ?QjtnV?V64Wa&kx%iWYD?oJU(SCRNXx(^T9X|y z*>XXEXb2S5n;D9uI4kFdk+cc0SZThD-K$+9u+Y+Yz@>$%Y^pa>ScdFkl(uG4Nt9o; za~9@W6QoLP$E)6}X5;>WlU96XS&w)f%fA3qR-z@3-wR#z;WD*ln7>m9%ilE}=7=mZ zJLzqCm<|gpWn|qXG&7Dcx`xyDJvK$StjX!GWP*%X>N~1SKd{S@FmQ?OUDTKA(wa)I zsvf@|Qx2=8ekQ)z_=1i-!V`iudsXnlMO6A-0M=-94sWfCUT&NL2Su6kzV>XlkgbFi zR>2oy7^7VI6|iQlwh(;^d~j9?S@j(Mo_?WuTJ5>z!P!Fdz9aV~65xueNPxLd{dBrax5OL!Qd&!ga z86!u|!&tyRyT9F6irPovCFNE15?h1utL`xq0&JFVglxwXc}X%H%SLQA1R`t*ZQPMs z$5}~~ceGi~m7P1wnGsI`Qz!A}ocuPjv)+ih_9>PkUYH#0daIr1o#gawu`AtYG^1DD z@1>0AV3zfIbu;;8wGu4fjoKcx=ZGoiF;?eUmX*m@pgIsHl!(MFa(fvww1*<_&ys9K z2PMk|>EPhJcKvLqO(ZMe)H9%(9pv)DQ<2CIVK)pH>QpTCyaGhVYnK9=iav#5G`CHu z-r_XnBx8l#XJ0+CAnMM8et|jky3^VSub!1&@ncBfD>qD7FxHMu*gIW4N2swiJu29f zFqC`-&zoh1t~iMl3q}6Y8K0T44V)qqvc)%WME;|Gf(-5in>T~~KigA++PM;|wdG5U8Ev*X z9=bC1##wX@xXO$PoV)WEhja-L6UMm0iosX#I`yy(2qXH~oiy(>aA&8^#wx&&TTebx z*oNXN>Bj|Ep4fq-xiaLpq}PBR*|5%Sj2bvMgx)i9y}78CLmamz^bJ$W)hb~YQm6Ab z_*lSyUiFqo_4}rN*W120nl9bJ6ZPwN0z&gr8p0)&Orh?EEP63Vji?P)8nLC;EOamd zcL^^K_TkJp|ro)ITGh6%GxZ`K`Pc^&a)T6|1w3~ED)y22|JvY zeBq9wgNFj={&gQ(fM+>|x;l2I^m2DCPQ_;JlD=_79H$Jw=cq|cfKx?ZsnXR%yE2a6 z+8c-Urr+Rr6tqm~NcKhE2|E5OwQ3EbN7B$`>0MwW1XR1$_#T{iW8W$2_tAQ$bjNL4J*>?3 zm!wa4w+6l3Wk`L{=ZN)r7q8}qycRX-Du{B0#84)(C=_Qx$scHQsI)2@x8?4Q;xVJr zniXqE$R7iCMc00WjaPi&l&;@{EZ`qIZLs?_?d}m>z&~Z$VCO66-J_iM?1D}9_f4E+ zW7_6Au?cfE`(d*a8@Q!PfndZ2O8o)mFQj)=orQ}8KX@bKwwzZeVz+nhg1`7n_&lCb z2}CH=eUCMY6^xmFr~2p#nP&zt^SL<6S=?@c~8M8^?fkN;oL>NKp1LoKR73koJkTRfJ99V)ekXGk(pC`fxrIKpM z^pYCobUsDw4A@I4e;OQs#%bzx((Z6eVmL}-EJ9(Z2~hQ&qb|1hk@!O`$ykVIQ&l^hq{mM>OXfEX4Y2h+f>|p zaOz)dtNH2;bMoDBKZap$478|z&9nYyhyP=5kVIEw(!X+_{o`X??3s5R+Q!#HtRIy- z5BkZp>4ckM6<%vmKlxwslZ5K^+?OO?&2 zn@474AwWU%;N0+dRCKUV_;JxS^{@glrv-w>PwyxJlL?z61@ow9Zp(#?h{y^4QJL;z z{5nbBwxtdg79j$}kg`!B1=y03+RdV5A0F;YsHB(lTdMPT$4o)xp4%tOY-Dv@MS4zd zdAhk^B9tORQKAMmE*cLPr>}G#E@G!%UqT@#%j$gjtUR8XsOn(hqu}r2Z;^lCso|o4 z+lz3aALa515yJbyCyVz1W*z4c_G($P9gII<;X7ay1Tv0jwS=;zSsKhl2e*4^v0KGR zY{U2%i-JWk4H{P*L+kREm(1G>iG9ottP~(|Y}783Y4593oc463NWxN;F{Mgz zc_E=zw%wbQt<54{Dukx0=CM5NT=N-5J|?P-{mXX9#j4iDNVHvuAe36cV3QqkU|q%1 zK2O^BlU6Y7s>x&S)W_?jivj{xX?0x@D~3mx#9|Tf2K!H!JFGnqdC+Cbo#xti3m5jR z=CQ#jY&moxc#TJTx!0m1{YDLpkCKvUIPx8=Dnf2uNt4vlu9kf0(lwKTC^7xJ?S^}g z5~_=bG6_faE)y)`kQP~V=*xB}C(-$J6%8)U=iW!X_`E!o7H5kG;mf$Om&#y(>KK)o zvD{5qA8Ao}^u-XDHV>xtXNJsuqPWv~(d^U=)^@jeLmH!@L}XU(~(aan+-9`gxU<-<}0-gWU}*P!G)u#CjN{L zOVM=;Qw-ZC0`2kqXXWA5A{qW928fTZDX{8yniz1gxJaE`7Nsd*k}hnxc80Ij8fp?1 zzH8OeU?~J!87O^~HSpw`M~{r5;-w0YQ#y9e?4FG-o)@^6%$%*09BIBT+*)1jf1Iok zPJ`6rKv85)->UHE&}hlZ-Z#-7DW2v?ckC=+jZaBfj)G%DuX6!0Hrb)ZQ5%U?9?H_B znz6P5l;R>>@lL`iok@1rT+CilIWWfrNYc=mBtpT!*ZU@%mRz!R_wpQV=I)|4W~O-Vlro7j?OeXpMpsbMbNg@^xy|AY5$+jE6YO~2wgEc2pB9&)- zBh}rGn$^FWLTY&5X-jcberG%KX0>cQpZInhEEF%rBsw;fkLqe#%&U<0>mD8VJ9lXD z?NP-&7*@RU`MuTvi(O(p*&ri_#rSww*@h-#C_%R`$lA_Ni^JBoU7qGlV$31c-IM8M zmN{&@MiE#jDe1j@LUZgbwNBpWaXS9Hjy_Q&PMLYj*GL9*tbvFzXAA5RhF;<+H1<~m zD)<){i;l6%m~u*=3FQV9R?c#lc*e=D#k?*nu5Gy!U95-tqi&Tb{5djU#U6wsRJMqu zs>W5W_F{bC5N`2KVBmKJh}J~Y81&V+v#Iqk&52xc^T!WT+P=?^A^SRfq#tW4mwr-b z_b+`{l%wXhF7WO{1u;Zrg}qbgJHpO}E4K%S_fxz*yb4YDdA0BI$`ZQ<2fxDZzw)(| z=(03MheA8HTG6*G&u$a{K%v4nNEj5lY@sB3SP_c=p@H=Bg9M++MKONe2j1HTSaqB4 z86MG_-yKqD*4uAmud930rYBpwjGt6Vsa_5vL{qBVgq32Tvmeq=pwqbyH%tv14c%w^ zMpZaq<~mmS@R@}nlj0qZdvPagZ;({Y%-~87k(Q~x(M~cs_^>^MhIHM2i9r+{cTuNk ztF2g(`Tgnb)B_z+(M=YG3yVn$>*l3KuES{2kky_NWC#3+Ck>v}VI=GM*~Ee9*J{(9 zJA{(BHnjJtD168iuQkj!txnfIIs4-N08eqb$P_3w#VA%(E|4`|Ou5w=xDxp)m2Z6N zkyihS46&b+-BctS!FL7I}`PgG+&KLd{YwsA{iJq(rr#nf2RXtVpl;*Yhcj}R!Y1bBP>%BmswO9SmRVOTd zjgElB>j)f{jrq6r))S;;E`uxNMNNYh;G7(??jt2tkzx~*p1D#lw_~MC@*hte zJlJ)w4m>4^5#2MdIox*2@_+eBNuJ#3y2x*^3}D9SXXq`=$$<9DEsKo>grJm>VDFcm zo5`*_j9sS-v}W;ai}X=vg3Dnc_M}HCgBy&8F}64THxxeD2{>V||ZXSw0( zv95-yxn$0<8zKuEO~4qqY!Qj6KQsZ}vb ztcudgTecIL{>vqj8cSZV;vW67pxrO;G0IN9XPSa|+6iU{T_s zgRa$+n#!)jSIpyB3&-bAViFC*SI`~tPY_^l3a@M-wNN~f*?56S%l_Gg@>&DxK`uKR zj;A`?sraT-Eb}~eJ19=C@H1CVSxF=Ec{i~8`-Ed|!(qfGZ$L@O+2K|Wzc8mrYFZ`+ z1Cfx9^i^u?(@!~(4=EL`9e!mRRndkwVGi(=m6hszLJFA8B8#V-idN4 z;LB7uxR?9#j_#egOJ+3j4Aqgn3zmb~JsK-*hrH9wYOmp!$F#-U7`3$+M%nI`z?ztl z23l%5Y#EzgPm0qMLn3FwLYXoczydc; zf(ICE@Zm;8$m8gWklp^nl%a#O-wt-AtU`aGF7SMZk=M7d{XtKKa`MA(_!{VdR!}>^ zYz4kNz!?E#d+}Ni3uorKAgb8Zq}w8O_0kt@k24pu;Fp~I3o5s@gyIV(S(5FM@ZcV4 zl~?SSRDMq|q4*Yy8pjQzif;B$^0C^s;;fyvqN1dCB@WpS%oB7h%TtU@?%_S#mkdJ% z>L*O@BP2^xM>ce)?DAc*glEmDW|lV3wmr#w@At!Iujf5gRb79kr@N8h1u`@H_%cXl zH|-KJ1qHS}wiZ$Q{1dNdB1?I|lfm26TT;uLx<`-d#rfwA!;(=0f~zjwLX7SuKQhBh znvRo4O;h@_x4b*=*hZa+O&!k#8_h!4>7B=GNZ@No1OF49zR%?Ej3=5#^$n7;)-3u43^jrFUJWi|v{fihtJXtuPdFc55Xv6pj)`<~fwd?eBgeDC-20mD zp4f2U;W^hNf(`oda85mpRS;SET{i)?BN9Q^eE!(MLi_ltokq(`v04|!Vuqo*?mPCo z^t-!0P5^{CT}D^qUe$IMH88~K&95u%3I2L%u{y?z&Hci0%}Q_^8D`t*iM|ri>S;Ym zt6-MrrO9m+E$yRnANWbMuFP2``1VQG6dM=Cqs&lGSSV?ltsZTS#Tp(}<%FBOz0AAV z-`>?5;*a^<-D!@_mrW+E1b)gg#Wz6*uDU-pm?xMus^%}`DzAP2W<}6sI*@18$85!* z&T4)1%vS}%dU4>+vxKw%RG@^=qDt*{E%HRDw49AyDn2`b?w860gb=S-B>WD&9i4gy zPT7dn>Bx7_OIt!W-*3pIvTPt){9Nt|a7ZQ|=brC(KA zRjFC zi|SVg!T#|uE%&9Pr^7}CfNVTENj5WkNBxUYQ~3L>Nrxp<)2@&(G|A7!-RWx-MM;YA z$K>nepWr#)`_#6FOs-oU(H5qN5{d59R>Q_^HB;n7SHPLW4dmB>m5 zaKDw-)qk@VEp)~+;Hnj6GxkIK02I&ayJZ*v0I|<0$bYuW{kbT~{cnqs|Ew+& z%Kr0v1xF`+F)Q1@uf8cv(DIKnZq`U4#!FfgON)Z+HyaIg6jW980IagON{ZYWcadeaZ!N$cieUc|2Rtj)bkvJ;3DgSHlm}(Y0j$* z&1sUl6{=CT9HT+5myyY%Ulvm&KSxuJ&c#Y%D=|~J(|V<brqUOIj`jI2XJjtOlWuVqCKYd^Qxj^o+@*xDrqbI}R>uhH z9Xvo(V}{Q2dfb#;f4874hT}JKHK_Kpa8yXwBiA>xyIh_~pg6OFrx;=RK7ydcxh~iS zvGUqAb0vy)$X_3Y=cY{pDr9_LylSMf@wwfXz>|W8QBG$$l87J_z+w7 zae%vp3|KE6mdF?c9Vj{YCXmUYB*(d9%TJV7U0H zil66Ooaw2UL5IpmN9CXMw&zPZB0{uK%pwcD!f#0H#sLk0vhZtV6#NpV^kqwiq8E>*wq}Kj^<&+n+f);s4Ou{$9(MvHrvAgw3qY987-?yXxy1Sp1E- zi;R{6@1unc+IrP4h2Z)w3p*I-x6N(kFD#%Qcf6zN3IIdY)P~rrHi3bC zc}*96bPlZ>78Ttik`BA?j@Q^A!tcepiqNO!O;@TaN(A~=8M{gqNFJaj;c{qgzpHXV zfIrbXB}vU-Od}MFF7a_7bCCrPw#eko(vlt*j1#Kw({36JR}P7GecU9o_2YTN693Zcs+BY{>S8eUJ*-N=chP+_s{$_Jn{aA z0{d5Z`kV0n2~d?u8V+lMp8$nXo^;S+y*tZmYGLimJ|)y?7cO9CkhVB425t?wLp_cz zQc7t?SU88)1*r4I^*cxqnr;!}oO6-P4-n(+z?(&HgrJ=>an_+)_2w#*j@5ehBOh-M zHj9tX_ACv6sGWL7dsm5}pNv5@CAx?vu9uWHhQ z=$B(HxO;gKNiT?%$@rk zCW20E+N$<|PY(9V#<}LP;)sIwJIvC1!GZt-opUahefi#v-aFtEh(krlIiURYWEL7C z4pu~II#2jzEY9;S7$ZJR4L3?Pi$AxbnBXfj7hvIM( zz>%iPpVkB___ZfL+=#&A6KNaHRHIO!$)BCGUtBuI;*P>hT)jUM%S+ zK^Xs-)kM3T^R8OfPYZ8Ye|J}_uV^dM(O)K2WN8>rGdFWqs07}hrEDZwyJK|OnD=5r zB)@OY;9>}sN?^%&u3Lm}Zz7<`3~UpfYhB*}f+Xvl+&p@CIMhQmLrt2Q|GL*rhh_l@ z0b}QFQ0TJs^@P%_iZzAv`6gQiQmy_GD=NtJd zhXZg~hxw4k*VnrIBCSIt^xQ@%9*$`0t>>9qr8cPS8>ofB3mQzE*$+gBnBg8XIL4Bd z*_d~R448-vOjwCHsYWEKj?Cx6FS4N?kx@HGfqc}PeBnJ_XDX{+<(b8rK9+-TJ&Fi# zCEK)bP$M(Zx7yE#u3WP^z=2(gS&Vhr?h-M_Bsi#4*1kwK`leRq5TV(4Z-V{;th`pp zVCLO~!_~BY)pjuzwX~paoPZ&l^{!EbXw{z^SrC8O(iFNP&AwYKH&Ue{({%BHZ#j>U zS+M~lxXO*pOu~&~Au-47+Aqbtc2y=J?rnh;N**JS(Ru$I*dEzn<&j;ErelqVlcs|v zS%`FKB$-mAjGB#f$n3Ko*p?JwWw2xYX`P|LWoK+h81~`Ub6&#;eiQShcSCN*4VC z2%nEMXK`gijJ{@NWoqi{jCf-g&VqVnwEBD=6Su4c5e8|Jxl)Dd%av~!l7@xXZhjM7 zMT6_q*=L_sJz?(T<9Vmc1>0lCCEL;Ah{Pk-SKCrvGCx-on9IW*F3yhR;778Q=*Qa= zKzJPa?fzi5Cs3H?n=3is<{bm@v@5&NH3>Tg?p$3??$Nbu`QzKUSGV8+6GYP%gg zn1ZIbuU6_6#^Gq%l}|#Jq!1clw$f{rd~jMTA5G+Vs@}g@w!&0JC`MepVMb_i2r(b+i=M3^m~d7O z>{y}XVooDt z5T}-qJK#VuEH@ahM1^GOo>&$DfstKfsn@oAtq<0j6KvpGMz@-#tI!i1HZ@)^vhjro z&Q>cIIXgJZr;g+|3ASjnwByZCL12@R^OPd96!x_{H(8B^}wnQ2`W(5i1DK?DYsW$A>WWcav zT@?onTr~$}t{HsOW>Cckr>`N1ofQXZt|^eZhv<;Hdl3pMJyJ@asJuz83&M)8Jh*=` zqR9{9JY6wxzQXy)Uz@xN?N*b&2O*HZNBPJuA65ZMciUbDZvtc-MV@^i zCWjjvGdGo5aLaEf>c98RY_w=F)Zc2hO?rGi%S`%2_!4?bxFgvh5i}zA*4+rbnj9sm zgNM3wEiyGkdnpsQ@dQw+kw9rrQ$N?Ubk^-bDnE6XQv=>Oq$i>$L=gQZ<6u==PH)ym z)U0+N_UBSSv#hY$=l1Cw{G|gU#=}G1A`2N8lB;P(Cf2fAJ)fD` zBk!&Wb#t@}{dlXd9Q+7-tMmK_Vk8#e%!rb|=H5^QpP;LF4#0>VjohlPpcVOOVS{EV z23F=n&N)iWdA~uOmx4kr^w?w`pcjHev0#H#K5f=Xk<_>4?WFJaM+qf`R?|e$$S94$ zMPa%6kRT1_1=$8Ztrnf&C3$!GOx9i_hfw8SKt$hMiI+BiMq zG{2ygf5sgio@yjls4gb?*af@f?$uZO_D;~AV@~oYRE>O5Xo7=aPRF{N#f_)5msmVn zMJs`W@?0Btus1ERbNulQ+T5VnxKS={u2hzdKEYN%RI;#8q>Vt+xJ={*qzrDr6F?i$ z&H_|X(f0CqVxF6w!6{}+QmTMK!C#Do^G+jz=&nm!yj;m~Q^LV%BSSl+?O7XfVe(wa zPAjn(C5zohj7I$Ad3!^GRmjwlb#@Ok*cK|XtiEc9z5<|oS!H*Xa-*L#DEcr^QXV_N z#$baR@jiDxK8U}&j&VMw*;^X@z=hY{eVa_1^bNbaS-glYsuhW2oj5;WDA<(K&&(C* z-ZkOQV`8U^xgT#lQ)SW+Y^OrqRB69L*#K&0SBXOV$h12~x(RO3?8*R3TLyhWpx52< z`+yUWimW4gBRmF*0X39+IF*Un_^wqmR>hwv7S4oc8LoU5y-_{+)VI(8*+3-Nj zk`8~GGv#s99@QAk!x;J*w)33F990h^GhF`$~ z5IQddFZ2bk7S>3KSM2Gxl-k-S?fHg6x_S^LJfprdurJ+pIE@&u*zDro9FW=Fgpq9b z!MU?5&)5k81zY3zn~~$Gm+ROQP>HPuWo(k!{y%9vbZN+NhN)VR>TO{ja-$CcoZ~gV z8?Z`ay;G|5oOzmPJ|b%h?#W5!1@i;Q$hKe?*LcK1K0~sXoJQ{fIm@&7Ks<+@)VDxQ z??{`ky6h3U+k$&seYInk>X?FUMzP~X+~Jdx#FXB(^TQ!}qVvNcc~SuKN!um?eebwp z--+c#o2lqYx8-`mDMsFm0Bv;zV-(C)xU$UqZbhOdzETRPHyz^bkoM>0A}E(1ap9Cj2rulgI!hYtJglHj8D z0okB&+7@qp5D0A1_+dZD=$iLg-sRra2hQ)?lVg%CNZMQ)C0|)o(|908@kWA2 z@&n}g&H?|5Hc){;#s)-&pVYz7&TzXqac1iC`s)F!$G|e#_KV9zoqm2uyHP@ox!$Co z#SXVW!*=f4t{&>uVrn3J|4puYwrJsFL7Z7L9ewOkmpr~%0H>7DSk2siS*Bp!dGQ-- z6#O|%)>AFmUHugceuU7jG_Xgn?7UWL8w`~t?4Dqc@4SiT<>Ricaaie0kbbb}J$3)^ zGbiuue5+lbtf`puEhd)zGB&wMsB;@ns2Za2#TCj4Zv_OzBzPN zzOV{|jJ2RO=vz#Aln-h~SW;8X;FUe{54<0D-WskwSFazJ7K__H6BcZ@ooBd17<&4-m+%DzIHUm( zNB*l&^x`ELqDMg7aOA`-v2`rvHoSGd;JSD_l_T9b`Gq2}g6ZM+%b$_w!KqFA6Fktb{rG zy=YFqEIfQ0Z7MSQ)((4m4XX#UAGQ+~kbnAE(fsFBz=NSXuAqm)nr4AKSf8m$Vbvmk zV&4?F_W_=MU)*Uv)6IycR??92S(~mAU>yzT%E{whYuB#05ob5-5YNN4u!WvV7zarP z3k!2m3wN!Ra!3b0;fE>U*W5tjl06^(VHYaLEjHeLi+~`o8JOokh#MgZ_=BcTlY{-Y z#0}s7!YGxE?0+A)`zLc#DF1s34fQo_#1D5D=E-Ld0L9}+{5m1Ln5<;T`uHs6;H>p3 zzbEq_Bf+oEE1>91f*z?;o*Su|Mb$RbGyt~HB-o0athll@>-V0-zbdiqzy4Hj-xOhb zDyUjGIsY|Nl(afAcsv15wcB6;q!PuJiv-?_f`@~iyPjx^fb4E7nzOF-7&{LyzAP6n z#-{sDz}}p=TSS)HxDJoH#BRWBph%DOBdedrPdK#Db<)Wh`|NN*JK|Wk!{8YgfwBTiw29IrRd%bM6z0g zoPfNzz%ye-*Y|wr0CrgPbnal&#GJ`jkpV|~6ut1zmiZFsb|6?TcG-Shr1-)e07lCb z`?l&qZ;}sKzmZk|`(@ww^Fguy%lgniXUUBJ;iLXz6p8R3d9sR?0)h&nH#N{=0I)x} zcw!jiVoc7LS|x}u_}rW?rQfNRzN%om!>x3)B19%g8~YZ%};oQmdT z&Lw>We8h_9ezv9)3B!Zhf!80ccRpqvb*wu~ue|=cr0M>8+!t-#unk0FkP+cP-#aP> z)3hV>g~kn6ch+Bl^<0>uh}CVypTSkAr`$7oz$EYMZbtTfPdE^Xm^7kv5m$=u2_I~I zhN)-vlnB|EBhG@c?E%Ce{jEf}RJzhVZr^A_1k#8x^pxnORt!xu;uUoyXZ7pK#FO$Y z48-~46@x0_#KJM!;9-6mlWNxFuE8zr-{uG9sRdJw%jTJd8*n2^T^Ig{g9^`UL#Kh= z7(h`p)0WX7F=VJz*Ar99m$ww|;p`6*jYSH28@$7sG5D#L`eRRiEDlPCPOh$CRh+!2 zI$lo&dO)&RzD+Oi@L_)sYRp`mIf`sC!tj@~!zi|pLWMu1&(k;p72Gm>uSdjzQefUW zp+7~%uu)JWoKU>TY`xqHYH}X=WJUNfofsDx8n}-Ou@&^BiCkvEim z)RhU@pGRY-yU#I;B1MFdT$O`p&d)MOIZP|yQmEoC8rH;mqNN4!3<6$ZOcaHn3yI7v zT&LiLsOH0z{MbqIP3Mj?reP63q$o@Vkw5B|oh=d1E2e6!3ISkdkmZR7;^LBGMuDBf zZV1i&X&I@)X||by(6t=ud?l1I9wDWnm#^_67EleQVBC zRbIsF-3gshwY_L(#UxVh zMpkfAn1S!FOPt@sr`~mfYlnre#gYkM{?Z-7Ed6zvED!L7w0lf~Mu#NXL!c)epL!=O zq`ZZA*Q$eHTMKT<&Odj>S$albeI0HCef!s<4<2#x6BFW0cs{M#{8YJuh-iz)>dFZ_Q6D~KzU4GE<>axy zTD%hI%PhABs%}nrlz6EZI`U#6Po-AaFBAhp^wsH|tiJHjx+YI?>+{JmZpp)~`B|Qx z<_MDZduvO$MJpQ@1~_b-hR?6{oWPa3iq@uBrgc?8(;L@im^!1BV5^r3fYy6SQ&sJ7 zOPI&=ov}&B+VZZMYV9&Jy#Y>Y-HeAzjhXW#t_Q!fAX{7Yu{Q>c?sN(ZJ)?zbp>b~@1d96 zxVzPV2~p^-bxZFrJzdUVhdH&=-8}dls8LT#B6EX~l3Y z=xuohu31B&>M>&CPIz ztM*=2AkkC8gp1amA~kWC*`7S-BeCN+WA4rPYpjZb_D$cyX*CCmzxKYA?6H&aTljbH z^#Hn()HBa-4YR9!JN>y5!`DwcsXQxC9odZAV%@Lg7PDEtn$EE7ioxVLL9ZsV{58#$ zu?`u86k8XWs#tFPPI29a*W6p10f0*n zY%)c_rQ-~jaIJ-*Z0FBd$5{zIR1fa>_4~JccTP_4gY%Cd)-1{%uu*FNTcw(a4ec1? z8vua#XSMpD6z!kG{QqgI!vECY{x@p2P(?!pLkaO+8Wd722)3t5H(WV*&tvT zlc|+t8y}BsuPLzZ_m?>ufXyKkZo=3b16VVjZ#5Knd%-9;&!*S00bD$UoS?d>IJY|p z+@52>(@!x6{Q}%2o+Dx-63fXsbl>@+(1MJG!pf{HnEMN@Bv>+_DpeONq&C>(GgV79 z4N=WwiZjHcB#1nxY+LzaXDBsTBFn0Hb;Ix)PTO+VK;RlxCrW=21btqNLXS3XPG~fB z=1MZwr3>l`F+kW!7UG8KJWF$YoO1T6;~k0MU#PM$epA6c+s;GnuRumly(u^WeGBQkIT z^rR*~m03rER8|8F8;WZI6?p%So1gQd7FBdK?s|GPR#9QNuAC2NbW<+dj+O)po|n5A zmFX$_Wu-4M#5bQiJ8g>F^BSUK$X4m^H0mHeV->1y$x0=?CP~9dwuJcsa%c>+hLXB* zJ5z4cPH)KjwDt5u*TnsOZhBc-X_x~E*VLQwb{H;(ls7B%*_Br-THG3gUfiUR2AKo7 z^QVQ%_QC2|($}sckVmMeI|Hy@|{yQ2iyMqAdNM76?W4*2)oY>cL_;{j9GO zD+LBhGvO}=siyQoOKh1(&yd2ZEM^P9ha@KES}3~|&j&qxL&Z@$zu>fV3~WzKTpx>~XWjta zyM%bC3b~vkwAHzhU@>3m+ss}gJA6p4ctWnS1%J^51WM!XQjxiO}4W_^q- z6Xnl%&~cUyI5UY()y3Mp}!z^U$2zurIG}Eh1)J#JC0EcJ}v}xsqsrO9G!Png!I{%f(Ljypo8huIhUQXKL zYX^*6MLZKo_od$@TuU=F_Z&PY|F*?f&oc8@Ok3_X-iYjP;J07I)Q@1zq3fmeoV8Q> ziFXYUFd6C!Ryx>fWokmnb7N0w2}$`kp$*F@W&}; z*J8@pfPt3uvx!s=m#I#M$1I(0pI4xoP-|f7i|4Bv&0fljUsrZpBn`VezQIZ`P~Vm? z$17%TuTs81_EHoIuFAM3g--M%1YLYDXI-F&t{-)+P(aAC4%|~DN4QwKE)*TY_oA8v zu}1G48e-V>q)ud{L1j)Cf@y|E%jk*%7I4B%T+Z~w47;PkAvR3KISwHS=1g?X#K7|S z5~STLIwv@;e%~4<*e7eg`s3X6H31z>PFIduv2{PQN$Pt`6@oO|RA$NB^09U+0F=Q8W z-!it=g5D>KLY)o ze}pqF%-dv%O0+zwpz?@^=mrl3@AV~sf0GMQBaycUjSHl5GjTRK>`Yym(SH4PjnJcT zrfduL*-mDh7xAK`SYxf*|J`aE&!6eg_xu|7*!FUQH(u{v6nmJz3=mIf)<#Nd-;J~o zr}=9Zp?6=+(qSD&aNa3#J}J1tK~#2YEVSd?m5Vp*ui8%7Gb91Edm()u<{F_0hcB0b z?0YA+sKBXY$ifz3T=+^aklE>O-*4tyoyKI%1GTbq7m;T_SkawdpAasA9P4JGORtML zk==Q?D1Ua*e%Ip>nSDSg^C3%!cSH@7$_#abkJ6v!flVvnMrq;LHjpOkO4NoW=6~RR zD#LN6VqI>sMRS6XsG+3H*V`dRcVu$2C$N_~{-*h`oQw4}yM!W;f#wGG!l z{?T1s(fg6IWPb8XxCAd6_1ok_#TbH6(WYgRJ6-g1pTv5o%LSf8U2|0L)NTZmJHr@b z@Hg=Lwl1IVgiwH;6u{5Pwfu|a1)Vuwv4)c?+#n9lQ*yRlxlcq+9m3^7U%d4al_?f1 zTB0z17)b9i2D(YhuSp(JfN#m^r{JZK9oV>cP#?-pkcq=J%TKSh2-F2ow1ktJoffFn zfYoc})=TMI$;!n~_W*jm|FPpRA}R&>>n8z8_ICpEe_lO<@;_s`wb8%sk@<7{O!wD+ z^m#qf*x1ZK!BNlALBPh!_H&KQ-?cI_;&0~qVcMt;-0pDN7YX`p1|!4|!Y&sj#Hxaf zj!f+2Z(U}>nr(JLzp_F4J-RMn03hxR2I%1V?P(h)2ZYJ_P_e0avW=Xt*4qzacM=Y>#=MZKX0?aL6 zKOHNNLpkG#+?ZOaKZy9@lO~p_mQSC%5=u()smOT6A+_v&uEko|yAJ zSA4Q6#=9oAFk%Lul3Hb>i!|as&$IPm+wxq8Xih5@!TsZEkx@7OaF)$EKsr%yU%qEDvRs{b3I z^6NPm{Re`rRM4=2=Rl><_#_|Rw zV3CiQ+jSu^_mlaqjd{ct^*{lPXd3J21RKNAP3CxpPIvbQfNe-8kiKgQFS-VpVGL8P z!ES752JdU(`SPJnI4V+eK2VZHQ$BjXx5O{0m6L0u#=|C~OT}kr+iY(1=X@^M zWp65wvR(Ela|;i{)9^M&LzQ1h&6i?Y=N??XhKc&!v+@4Ed1Cd%nNgRkSQfN*oJ&iK z$E^pXR+EnL=@9mWHP$fN!1@ca&+(_(ONq)|C!qT`K3`07Fp)MP1XsO^7*rBd*QP*J zbZ^s65F5nha`I?67ey6;lcXCgvzH1{7AFS`d#@5R80XKDxucQhX^|h?ib+Ut`bD6Ky-M*dbDfiM^wZd-5Cv4L+A3qGrQjWMt!o3V`sgX2;= z!oVhnt3_-CV{2mmnmUi`rQ9Z1p#2`2LPxEG@1F7Qr)yMX+bbT}#Jh?rGCY)`$tbw0 zN4k9!n#-F_gnxn1%a0~AinR+s(?=SHP+*4mr8%tq4z(Y9(Kecu;;u0Y>bX7xGx)k-*T16+i+UO* ztw~kM;gUW!mth_d2QHIeXDQ}^k&<52FgpUBTgT&&X`saMBrWRd)wdR?$bVr$%b4|= zmlt$4$+h^Bg&N99Z;N?$SR3W%&(*i!a{`bHi`LflL+deb!vH_Gv0>$F^ak+#NnKF= zI9ycn!Y)+YYd@IT`vn;Gx`nn8n>|`6o!1k_9R+puKme&*6z25jS>HU6DnQkwpq9K9C>TKib$B9Vf=lZL7CmOg3lDS{uHe=)#-moZ+3 zZjpCR->AIKq1(JW)`H2p+7^-|OdRmfVtkhxVp#*{?`5o&+DcD-^xE>6bE|*ZTRNfv zn9?NNt|?o-lKu;q#X9YT@}fJ@}g*Ol)@1VmMw zWDLwCvmb_o?)R2*xnb7PWW385*CH{fOlT>uObCPAKv;)s2SJi#Abh}QHe+z9gG5lX zMz$O$Ey%PlHC(ZNo>^%iTX*EDws9$y3u5jhZOQb}h0wmrbL$$|mVug3$cj*cg{)NT zN31_c1lBwH#UHD@E77+Rm`CS`USu7Pq}S#^r01|yGZCT0tOYM&ChwrLIO}GYwHB06 z_OnryqbH&uO(AXgs$HJ@V!jwsj?m=ixF70y>6yTLh{sEHRiwCmPwC*ElU~r(yn$AN zp}I&fR1oOFraWW>TG39F!odZgf0TX6b$5D$zkz%0P_(?DPS#H0s&zkGx#g+;YE1l; z`$J8Iq7ceM`LS?xm;h@9eyFxtR4FU92pMDvY7%iIHgHa(8Zt#GTIt;@`YsFk;EMkQiT14V6c`Zw=vRq@Qp;=5I&W)YgpB zIs}qseMTAg5bwc{Yh-NVOgaO2?V)y5$2A6+VOyJ_?+KR4^DshY3j&EX z+FnY4w!um{g+Q|8#yN{0GbtyQ;P5BZVBDas_Cd%@GTIPoaj|46Rzs19w)nXDx)@*! zShl3r`HxwCL$z865eVz2L9&DVYg*;@r@eMv(nPn zl8lTN3;l4M+JS9N+kP1zkT;sWNa6bh2saj?Q+}l zyu9YxJYBnm_zqanK9aMok46EljD-lcVt^OOhF372h>WM8fJd@_%71fMvbFP^=7c|v zctWl#7j6c3lx;Y)9)g#vZbCmV9`J6%&yJvH`3#M`k&ToTx>`ya3RfTK}|t zuJinHImIo7Z?QKG1cs<@rkjL_a_H?fWVFg><)<2xokaSll(`#@8N9WZ0XY<Viy4Nv85tF$*&Mkv z(4mCptRgR=Y(W90`hAZT0?p5+P%F0doOhxGK+UiLUkdo{QvQfcP_kfoLS^*nvx&7- zxBAoU?k+EoE68@{S=D+wm{VqE+pRu8i1dJoteeD55>%q26x~HY8F=LMv1E`>=P4}m z>6@nI8ID6XYeN(}ve~7)^1cPAD?DicVoWJ&9YHS~KA{Pbo)HADg zQ|sU^RJ`)8VyaN<&NFip6>3T<2Nm|+*p`_LU}6|JOeDoBZi>^}ICSD)y0n)0V~#EN zBb?oC-N4htRo%posqUMuYe6;F47}}MAvV`ea9-5-d^sdKq^!`~rIV9%V%l+i`~v}) zUCTCp@6h)Cw{BVduhr_myJf=PdBlJJ_@9|sWy0ETk3H1Al0;3_co5}>qB!3Kj?`-2 zItcF=Pfo{3$PIlii@dgB>@u!%00Cy27ka(IGv;dSvazTg5X^n%eCWy0{Wg_4_v`*; z4F*7BISenXm^^DRN0&Q{k8&Vp&;co2PVkC@LSNH=T91kLCR3M}GDC1Cw4)ezwvZD#wEK|3+^3B+?^DOPsE?ce0nD1tDPtzBmG z3tXM_QDxkQ@oum6*;?83U!FySCujw?%;8HI(3Qo`e%2W(WRB8^@?sjG7Rx^zM4%8^ zaYYdZDRo5bA&ty?XRnb&K~0n`8!n%Nr0TV-JiLe$=Lr_BwQ{$jIMpTR6h6;2)nO2~ zy&IGcl50a0LY%kG_(HDo^%qv31Jyg~3H|gAoyi|TNOFPFvIG|v^R*-hll)a%_(}1UXucvF(~jar~V1Uajo9-Kyecx3wWGm%Rd# zXN-em?8K&5I3|}w?T(v-N0!-<#U=7A{KXP(YE8c zv-F^&tHf`#q6u;KLc>M1BA>2w0{j}V4Cf;1}`zef~&q z?in6~+de^9_rLheKL@gs|36vGKj@!IMJ<~-T12i3m9&^zdMn7GB~;)vWYOYtt2ylB zloG|JVC6(tM}%r8v3PR3$M)?n#J&T+?|oI(gO@=O5);Xtof>R8R9!~C?Hw&$0ytmw z34*~;p}HFC&hcYMQ}H)Ja?;5fk(S0vmPr9lqcUsX3f}2^yOVcT?UI`b#gnO=k231k ztm%>ND)+<-5<-e#VL#gudaNqatq`l z{qoamB@nRVOotR)-}C<-lPJA^oilAvI_yxBT)SMb1yHVt|;;tcY0n9L4(-!;4 zSAvZbC*OQ%NN^XNfT$AcG%?+RabOf{PqAXLzy)6|HwJ7UI%v#zP8KW4gPB&y?Zjku6XL4niE%L$X4lV zH96h*tr6r2FcWY7$e#-E3+UcHf!h7wHbnl#SnF>-^1mZ8;eY?=pCBC>^ZSG=qL7W9 zt}ZVyUr%Sa-X5jn(NKUXeJB`2$cy?ID{Di16Kwb7@1uWcTM>f!rur*+yW|wap^1mv7X#c>nmt7s1JQJSOO%m7s=uiw4#Z$_C$&4`?I#m=1hwg z2dEvi)D;V~)GM{^a8SeXrO457RgZmZL|a#)Vklmw$$?ZY-thD^`jydN1mf$uC3_XC zBV$<1h)WYwcfy6p$6jl-57sl0V!4ALRWv6frH^8@j#`Horhyfa$G5S}*ki&dy5wt$+?QSEaoBk2(KU#Q7nb&nF`KFCp5WkwN#b zk>M|+X+lvOYa=%U8!LS&dHTQnt$#jVqx`ru1|1?7OEs98YheukxPm@k$$Y6gGiboP zFu#zve5nv6KJ(PT*Zv4Bw+zKs1r{x&+HJsRIV6{$lCqe*7+{DO%`K0pXs>e~ADc@6 z&jEKZHp(Q~K?<00Y;6x$0a8rT%5^-vb}bH~{gWaJuzILMqjDUl0xr1B7>2|kFhN)H z3h~8$!zrzj6xkR3h7M@Sc{A@9Sr+y2o>E`?C8^xO)&?hp`v8t8cf4HB2_p>{Axw#+ zET(`9_53#P^~?JRr+3|@rP*Hu%`5J8E8MwdyE0;GO$9G{XG#j}W=6?1jc?Pg0c+ls zQX@ccdJdF{vd`Af%rPS5X5*_P2A%DQfO5iLvVs@rjp>f`B7b;y5yKwB%X|ij>mRGF zhWNQmwOgps3v~DXVo)F5I6H3z_LjKg-I*~*)!93}SVu~+1lh5uw;bJk-Lp4};-Ycc za=Wdsygd*TwDLES0@v6Ebz1M|Q1m=Fq0#bo_rA1k2FI{LgTZP%%VdhHJ2tF`9R>!i(Km` z^CMCa2u$@WomK`1a-XwWR(Ajzy9p^PjjrF;#4I!DK3C zzwPibot)XP@^Yo8*n~^6@H7AtVxC6qA^!-SyxlFk;Z%)D>Hb;6r2EN@79Da09omzx zv)fSqK@eR`>D>(SEQQC_iWL(UNPq1rr>wxy*h>CMxe7BUzh}0XN_|EPWdB-2%`{)Q zSmEPwg0}4Jm58VnsOMgT0{?*;J*ARUURIvj2-m!dW`H0H8XIhnGf~E-n)mT)!;Obb zAhD9hVe)PHh#_W5ZOJN9*j1{dv3N^+k?@qJbO1H>b@o#fyZ;o$tp60n-kw+vnZNxE zg^;Zc{9P2Qx=M`66;2I}LeD#!X>%4?ZHLe5+n({KMG>CnK-T|dH>TZ;J^cPmg_y5B#{@r8zDU`{poBt-1Njc3{m@W^fshgKc zR_zjeE5@Uv!vioS675d>{>c0*{HT6dYDt3NNjw%!n^NceFN znk9yULlfzk&;8!e?3&TrSsa0`;GK2=?XT#4KiG+rnIWD=@V1nVlB!|lu};Fi_HVi; zLS!uEuMer!P9W^mYKF?-ba}Y)c@(HB) zKd=wv7$OW1X5IXH03*C`NpMWdbp#l83d2#c&79cWggnyhXSnZ>bOu#TLwBIFhe(_o zeQ(zHu>Lz_n8~ZVI{P%Pi2pDym%kX--!xqR?~p-M-~JC{N{H>2TH*QJ@J=kn7Y+qX zHsvoPCVr9+k7nYYvyIpwmk$mb(rlehoJ`_mU@h~tG$|AFZVK#>Uok@y%S1w_E$W9RMeWl;LdAS=DD-ejM6yIi+YPAQ*cz#_$InN74X7hDkjL-!4r>qvA>85*<@ z;UPp|LE=GJ-BmSZ59|djS=|hz#`osUoT7J9QGP8~1pbcxH!XA*E>g@WAzreKw5CX! z{8@;ny@G>A;v`u@^vGt5)@g{|QVw{|Q;h)M2Zx&87wG=Z=p*bFGkOb`POQ+J%e^NN ze+@UE4e)_ZA}}ZUUs}*|u0oosZ6#qCB}dczZax%eU2jyy`r}rbH&~UPgDpt&>0z;M zSG+0RuaEsGgpL;RgIIwe>@S%njefR=q0*U{il+Ni$DyXW07|$)s7gAjD*dSegDT2f zApy0^mN|O5XrI;Q9~ex%wai?bUK?x|T2M(AmWAs(=Qza>et?=(cVn|rufAId zXV_{O6~C69tF7x(dI|c|r4nldrb9tggklza-G=jB!LIZrXp7uZ&Y4006r#JCOH46C z+{Eqc*>CQoaI$X0;~8rOTs_Q78GUT6DL^l}vtK@%YG5qP^cl~AZN$V1#uG-63}*c) z?&cVExs-D9AM$+-tSsDtNMI+8Tj^QV&2!U?eDg`%lGV zg5+pTS0g=urJTa{-7KF!yTN%G`HUqnONZLeWH_g+GtvW9z zMeqCM#!cgn46O&NNy89`>Gj`&Pca}fzw89EWTOfbnQ z+^15^gcI9z25o}~X?HRJPZ*S++bzlzPQe*Yo-Ijvy4kztSyUt>|D2`V=zJ(pECmN& zh;d^AR(=Y^*|8_Fp+LrP_Y(;e%iH4=h6=6$JCw#V@)D#9%wh}O0$g!ntcwV)~-;np1+J0t9bJF9US8}`qP$9>nwwkD71pGz0oEOu zZHR3$Ti;}YOd=^n>3*bFFu5M~*bl(pAn#j~{y@}cRS_#G{<*v>aN|hXEL;smy<%g=ufl}FO@;!u96TU5xc`M&lZi=E ztQO@X5e$LzcZh32;R4_7yIXw&d%;=9koFL@$nwQ~i8h9BS^%@3E)o@n3PXj&fRI>W z4zUT?44z)N@}g9q&#unn6YBa!5=k&=YG=Z^Szu7;YbSQX$pe9LfIrns(Elp={ z!0{P!pRRN&+)Jw*J>V8t8;o~h0{9>AgCDM?W`4g?A-0H8K#gJm$8p_1jzIQGb!y$_ zRT+*>!+gL3*$7oIV#5#!w+me;5ARvjBdA7yofOfccSVdRRozfgXwY zdX=e?Rt~FjCEiosjwdV1N%(~9KHwNBojv9cNflY1gM^d}V5c*(inbRmeJ05e&vP|9~NX zH3ZTBlOcTKkpF2O|IL3ZZYJKov&p-UtML>3$i2fpw(djl9hH_g(;H++DN zeaXHvAR7&R^`Z+P9RJ*Xqge{fca%r?`vH!co~=e+jpZ zDxy~gg9C?n_b@0|Jm)8+_}8-(sW=0Se2k;p{ZAlcKtwA@pO-EvQD%_fjWHHybZtj) zS;-Dm0)V)<#A&(-6#Mvy_T4d{$UWk6zL<3@PFuf$do8%&0A5!+Ced+594LRgpN&^7 zG}|Tx8A0R(Hc>&10e@6qS#{ot8i2fv$(hsGdn0h!B&;4!yl)-k! z-fRkYdQ)r;ufs9)jlY4(8@1><`1l6Aw5?fq>cuPo`sCJfRU|bc?jFL1j>HrY4*3~> zUt}^ip%?auimM5SbJ*bHcVXDh%-G!a6XPuYW5poW|8D2{)}Q-$|6}{yUoXhtY?b)e zA9?Lvt@IUi?ad{ubqx&d{(wap%I-D_pS$O9YKN_QBmuER0L=hoHJJN~brb+aR$U% zPTt;5C+CSg1p=c+E^Ao7Yx$XZ@S$cQPe>1^NDtZy!yMZbMPDF}7#lb_GnrkqM+3x)j8~jo-q@z)jo|?|C(xeoDaP^c6;Ok?4#f zz4Z5hBfc~M)UN1Wyu1U39ep6r;l>$cu;na^KCHRR0qAgv^L1)4#E>E>LJJd;&C$!g zp$IUTjCi){pe|BxTYlCYtaoxGn=`T~J3LNoQ=Lo*2aY6DaH?q0LwcHTgbaj*oPT0+ zkFXeSzzJ~ejE*(mU#@LTpk|BIZL!pcj#o)}uxxG?ZVJR+!8W5zgalc!U2&p`Bdol1 zMtx9K-9k!(IY_6Qb?t23ST#Jd8m2=|%nA%g0n)@Nn}`B0oiui!G~czmontBa{lb5=hP!g%VAGvU;@n!^kJOy0r9C}?sKH%UHKGj+Y+iwNsd{7Y05c8 z0E0Y!Z?YrC)43xDs zo99NWmy+4fGGlACA~e9TqLqb&#yF-vl0+A=W~P;EdtAh(n(SIH4D|fDaJnHqCyE(_ z4nG#mUwE4J0yGy%I?8G#;)q$>!YXl{NJ3ebX6U`|&fmN6O#$`y?X*`@^QvsP}UxoG(XRM@s>Z>c{v2BKZz z@bNNgrIpn2_liGO2AUySPL6a{|OX$wp;bmeoh25vw#ILbyJq{Q?JR!*U~ z;8PrzoZRWoU9dySckNmA4p48qM-t2rbAk114A&`QdAo z_4%>Ra1IWwv_*txj>6Q2soqIW-><6FQu$c??Di<|y;uQG$0B4}`dM{|`6MwhF@jX6 zV1u#bNP;APo1L!gDB1n3HKXzeH;Z&hzIy%yv`)(+=8u-uXtyMB(-&ks_tI>M;JVey z$g?24ojQX+a&hl+pQhxNG#dJ!GY*UO)ayD%HTKbv7UC{OhG!n@4y3phFp*Y_p#|91 z4}s*Hft%{l^Zu07zUH>p+{RC~8=Q{6Tw`LEthW8>s#Z)nT-VMh*nGO}<<{hLGr-f+ zj=%phvH;L-Lc1nU;bBPJ()&)n(vlh|-rR|t6mYU90?BFHspjo>JIeKJ_U@cmfe%hn zCEv|>AwIONB8|(3Gg#JgEDX}(bL=o`FEdDc29AbuxUaHf=@Ct`7XDzraLAAgcEV{p zuM(vddbFdlv$(OcSwfl(3P~jvCu~Q*W(SROq#7KNP0?Whd}S*s+Q+)?D|TA=xvdJR z$rbb9g_J*Hm?v-S8N=TcVQrYFspktbNGr|=3WVg5E1}3(*>@Xw$0IbEjyrTSAzDr7 zU_t0+KhV{Xqvo*6bsXCka>i_kG>p;WQAwX=>P&{x7+)81;Adw$VhQ`%!R!KI*|vb? zlSVs5ZeI+dG3+Q_!UUj)>psu$9e}Zcw`4ysP6iJ}dmx7A5i%@J(nj63DtV?^13EYB zXELL7?_TL^;EA|-Sw?JuyfF%)sjdr$lqfwVwdwTGUi?YbqWu!|{!uQ(tZPLB795=^-|PMKchQq1=}PvqDgV1 zR|>=C0VJ6{9Bzc+HprXG;@d#Hiye2Y9xkraHr~iz)nDGlO60tWhkGc=9@*%mGs5#k z6u!Eki$~>|-f>oz`saRifrs)ZMBRdA5uR?}6X6LdOQJPKwr>hgdJqf;zNDzT#H_ny zue$`TyCm`1r}iCP@elaQpw6AM#oZ6@H4*$l0Nq=l!lyO>C1omCFCJr}oSVBh<0kcZ z3EuN6G4&cYBQ9hc2%NJA;U#Qql)4T9PxjMe0@ECHW3vu|y^DtYTDiEt3IhCR&eqR< z)khrE%?DPrAHiSxa%rW*sz-ZyQbY$lywMMq;q5A5TO8~_*4(Y4`*b6X4*N!Zkj@Y{ z69<0{Q{V1;1}!P%B|Ar#z#qWoq>Mfx{4@gh>~5A1TUzY;dbFH%IHi3!wPCWnVGH}- zZSfk%bwvaC!GSQi%>Vm~lx+c*{r8t`xNC|ki}_eXa;&h9N);Ps(!sKGL!1MfmZUxlxg%HZ>wtPL2Ch0&G>B!$ZA{@F*FoR5PUl4Ta*@(s|Ii(syv9 zIgn$D6bo)cPs88NQZzm}+S?7R!Pdju{QC$Hy0*}_7Wn(RCAEXT$Sv%z924dROb3l5 zXv2qu1FjXrb4#Jc^r8I#`(WiqIt;1mCf)e@Rvu#PwjWhXWKYu>sAdBvc%btBtOO#W?iQmc7^9p2P8 zIMC)E+_ndLH2g0ilx-c+XM?yT`8{c8JL*KJ!#nSGr>cpa-r-(PYvFLN8*uvMyZF69 zm@6j)V~@W#JXHygR5gAMTslMk_j0elV#2@ao%k0f{1nZ81C^h<#H}ry{=IkNe}4NL zclxbv=MUW2@Hv7B>9clvVzE$MYy}xyoy;;PfXK2nVH4FKI40VcPn~HBiqv>9U!B?M z(#rRd^cA`pIRpq14fd6+@07%!pAR$NuG*N>i(!Wa_hoRBMjK#tFf)XaSz>hn2Ptid z-e!_#nkNMRW-3nWF&JN!3Wp)zYy<@w3Pw8gO{<9b7Ha)^IM$jh=w(pDecH`LB7jt@ zt6EK-9+{pK(dYVwZ&9IWwAZcZgnJ91+s=`E`1q4L0drf}9GUI4I8B{6CBUA&1Fc~A zSCa-Vr>AC*M0>3uJ#+OoZIj(=11brBA?JH3xG$~3ENY*$%o=T6gZ`X{J8wHGkuBP; zptIQQ;D2#5C0~2gK>ScvfH1d*Z;b%awlM26(??tuBy70Engp|^F6|C2u3wKnR;g6f z6EZ_jC9Ts%iw6F|Lck3{%MHl6aE51xrvKvh9o~7GsfK+GJ%ktNC!&|uOH{}yEe$84Uq#f1 zSyv5XvyH^__{=$A-5G6g0wd}<6L%CqG`i;>(?K-yV!|6vs)GgdE2cySmtkrR_0C9? zLgp)22^of6srPH8N_c&ccHPi!;_3No(}&-G*MpGwGd6xJUR?dh0tUH%rbqurix%Vi z!=C#Uf1iy14!SHwE|~4Dh5!I!*+LDOt0IuY16UFr9P8qm(iqoTy;f1D9-p;ML)&>2 zhA2tY`M7CKzx|7vQ7kD8B1Dqi-fchqLSsK|_Uq5@KR{>$1Hdt95q$!u!7Fz_2b0^P z>{r9_{1GD6v70dX{^l&j$?)}^Fs{F(`gs{XVx~{ zN4#(NnlRXB6OTk@UCAs*m*OcbQN(*xsS?8rR09ulO=BnE8>xp~*a*kkYj4|)lEjI{ z*_QX46U)AsbnQ%{6pW5EBG2~}m*D*Hp8Pp69D5>e ztlOXr%70OlFyon+FfsLc{rL0q%N$pZWv5TF~^F1&)8+gDJd?D z&_?Hx$7yY@K@pxy3GJ}p1crA~gy%W1$s@zC<*}XF3PUCphD#Nd*jlpN4B9uv;}BS$ z$Oue`ev+MUeIJxSw!|}Pd}3q|W!y&>daKhU7u_pR6II_Y^TNJ?E)uhJqbLGn5ogpt zWZ!RpL06mBVdWtWO!Pa|5lO!-{kYZ|Zq3g(m5Y|DIC?jACrDvCs=h-);|P1gT_rCVNh^cYaho8gRSxZ&EU@x1iRM(Cn+V!-fd5CpNbslKgzRzvy|dr0Y>!q`0{5@-oJnNZz(Q9W?HQGvvAF}F2nxV zV+IaBoo1i+YkUt6D4#Bc%8$+EfS3wNu~?MX2Chc{ulQuAco-->TsymqaW+y??Tr)Z zFD&SRtkAY&v}2~^nIfe@bHj42aDgw=@pEG)Y&#`NQe=aq>@@Y)MaCn74)dvlOajmc z5gG*aG3=I&wL8-eyu?@GbLpfLB1}=s8Qk@g6lco1I2emVoiUp9w@4lIB`)zPI;?B( zrKE9fmC$_Yw5v?5PKwCOtXk{uwM(?$oWfXX=s$$d7VwnBl0}6UAWy6{VH>BTLbKcaV-0^vL-ET$_rT2{7LJ2|EsRo=l}qF8cEz3fN=cM6s%W7oGr|Gw;m`|SKIGk#kB8I90IdTm6}}{oi|_asN4F+gN@UBLD#-e*p&v|MFLp|C@~OzfAt~zr26;|B6}} zS^wc^{s`#_GQa0{JZB4)^q+N61YUuTyXTK35s1_v`tGyu)tHA%)nnJ9tsvj2qH+Qx>5|=apAeQGGc$zd4|-e>mbxGvCytg@5*ePuM>T3_ zWuuW!vMJu+Z4gv<^x$X7C)Im&Wvk5rJIWxx=A}; z^gs>bfJ6xOc-Km(-ozrRg%NN=mCTyarJ({w8Xvd0&IvoK3GnlbZ(%Co2E_GmNp`FU z;^(?3=Kgsf$4JlfY~n3$dT}P-^!)Ts7E>EDoj0=;U4;{PSDYe_qXe5KW(Evv94|rL zD#%Y7TTx}R&U@-I9{Bmd*3{~i8D2Ji%eMqG^pv4~I+~b&bT(@Ld}{J0x^{*JGIoYW zhIWQl`iB4NMi(?Nb+GJ{?q!nf7~>Gvc;fx&G=#=PJLU9hHuhi=3n7jiyz0p&UL zg6q1^?lanfyGUPSZ%qC@HYNa+f3Kfsz-JxiHG0sXF#*wSaYvSjATZ$cM0_iNkNY6T zcoa=$_JZunid)eha=B&5JrJY;S?L&mKyX$tsO~?Oj!WK>CHL!S+toN)GsLK zVVW=QJSl+If^op2NN(4qA|I)62r12YQor~oZmG(1ZiNti>|DNQWebg7P8K;zA7`RZ zZs6QUGuzi6POU!2uv}G!x)}h_91)_=4m2HtTfL~gy~EcSr+zxcnl+?xx-=PxEv{q2 z?SN2wkmq`STbDm15^_>6S#gywGL+x+w`Nk$GLp!5vUQC^10(}U4}cpsX&EP48K76< zpo7GmfHsnBpLaS9n?Ekdpc{TFg&4ZAL96k_;l>g`4kr(%5{%-DzCh$XVFXC$BkRnX z7EWLm9+eva5Yc?D&SuldEhc&*R|SdPyQ3kaZ+}X#E_z2HP?MxXt!u_6oJO@xsjZ5( zB1X{wDb3d-uQDeMk~-EST};r$)RzESv?Y{mOBtZiPc(>oWoY(;5|AaR5M#Yj>Gfp` zFy9q!kSJzQVLJOgK`6~}dr$u9X3YMB8T_>ijp3gS_CL^vzP+T~pK}uuWJjg==#bL^ z*C~U&$>5y70pvwYC!izpe|D<3c2(0Pfrd@RuJdhcABpYsz+L8rpMhjG*ND1H71>XW ziE3|TYJb7jX9)mqlwacQ7M>Ok>Eb|=O9tg_mpelsN;T_(sI|m@+OwEBcuu_=L>+EY z>>A)I3}~b2_HRMJ2e4#dn-YUIVNH`tV0Q5010se=3T(`sQivmGsK7ft%wy+u?XhwV zg7~$kkBeR~2}HQxvx>k-afXn_z;0wG(k2AOR$fIgFFl;a-snv7x*#uV>{*bG_t0%d z-y0;RzNer(?$AZ!k5y5B{4&)bst=PHWrt~Mfr$NTZb$iIxL3+u3kn|kL)w?C@65Ud zrEaCvx2p-GA%->o9{d`ft7>I7tP3#qUV;W(M7MV!zpj<)dxOQ|MA;cPzSBopE4CsU z8ni)l#4gva#lAojf!F)*GfKNLop)t^^Ddu<_V>PB@ShLt|BHwD!_`;($=(9B64y{m zP!N!5OcLb$QONXoiDd8?_3-1PFAV~n{QJ+&jhw|lSH%r0^TNm?YD>P#9;a9W_#q;g z%w*W7JWV)%R;@n0?~E#c=}n%67tRU|I#j03#s}662o;6|WfC|UiS{iQ($|JgqRZW+ z|SQ(^t030{#FH9T&273dlxdcTvBF?+ zwKkc-0HRGw#7ig4`i8YKhrine8!4MwjqRfHqawjDyo9wf_stgw8#|P>GF!5nv3Hkk zYv)iX6dzwaFKu;s3T?4TAZ6lowLcvU#5M5N6pOzbnt!m{Vr90-JWy4%V&Yp8WSAg( zm9U)Y{?jO*ZGaC7@)*50e$w~R74$Q=hY3@|;dV`nsqo%oYAeB!Y`6F*Aiy$Px~K9>?cK zLPU*cT?6tNa4Q{TuAyo086XJ<|~3XEcK`S znk#T;pC#XzR9X=l5-IviRjROf$<>*_fL%_5iRD7K%`213KrQg?n|w<56%Xu zJ(0!A{;Bsx;&*Nz8}YVMGxt z3*&qR{E&N%fKAv+A&UcwIQ|C$%M6WFeE7U&#Q)fA#PiQL?XSe)H&Dc(#!`YY>_nYenKo|8Y18t}{sMMAnX{C7D|Bfyh8D3HmoX-OEZ^Ku1aE#IfE9e0Qxm#H>|4g-Ro3kTU}^pBa#w2XsTyn z1t>weE5a2Qpu5~dy}+eFBSAB+t#MmdnXPv58T3G_HWaq)hN34XtE`?~*o7TV9EpWQ z3azdDV7(|(`m}AD?$*a}a&muqi0dj{jdytn_IavGpPacO7HBH_!Ze}r2A%QxL)plp zB7&?>+x66U%Yqvi-ML!cvJmwM+Id!v)H3a5Ib)f-@%!fTr6@7=9@&ns9}kw(KAxS! zrmGeaIi3ey38uQYstJuFgcDOAyX(_3CDvfI7YbsKXd5cL=KtK6NSv=IJ zZ$yeGn0%zrj*iRHsxq0UU%W6Ph@4wY8N+2ioNqYtB+|}#neqyg3aILT z>^;n`uM(-Xu3mCxf99s0^wJA>3w;X5)meBdo`;`+mx`6TQ%xt#k8wd;wpL-Ecqo3; z2*%J5;66gGn9I@(HSoQq4gdKYbgc}8jrXZKkK zp|5N2@aNnhqW}6r&QRal&fw3;^5Y-T8kNWPxh?496pciLaIKR2rU(9i-<8MMRDPo~N7 zMl({ixt!^y49rz1ks~JlZGz2f@Yp($_7;0?!Y>4BrBRBNwqit6n z4$ckjrs#73O7mW1w zdRb?E9;qS%T5`ZP((b>Ns@Tc;@91F@gIS}0=W-B(X2PQ0EWFn&)XJ~0!7!^kTGq>%!dWN7P|1|)4{6b^h$V_8Yu0j8^=w8AZ z`y+RsW8{(FB`usU@G&Ewu!P}1ij3|5Qt$r%LOK5(NB&n2#h;WjR{ggj27{lQ0xB$T zW(3E5-R5wYi?w>d`GNr%$gVt{aLnd1V%B6Y)#v4>`M+kvp^R5z6hlpSjj`G9zrGt= zWoci0{UTMZPY@xAq)z6~Kg~1T#oRr^hNhtjPeeJ-%v<`6BOGsK)0ZhIv-cI%@tM7L zJ^I^OQ^{oSlQeBXrVEeFYIg39_q(sEQ{(8i-G%rB)ip@11oB~(7oF7+QFCG|fCI(|nxduzLYFMw)L4sq5{ z#Q5kiptm-fn+&=H@sMq78oDIL=%nhcpg)D!Vm_@PO<6H) zlU8y*Y?j||<70)=MljPx;nPO)uNpnZva@N*UL`DZcl2QJe`?mAz>VJG#popN<@dd` zRP_{A+JM>8J%!u!9*Y9L?10DZFSZx}WJT}F`2q`esq#hG|JTFbCYarmEzS--cg~I| zjoiQ}4u%qfCIKq66>R%46YX^;`ukAD z(Za25am#vY7zU`OS)*@a@KML|Xunb{jW~@BXE2R47k*MV5~4~D%=AwCq+1pQg&>K+ zBo9Nx?~9=`graxcSLapA_}n@1NfZ`o;4{@LCuFtDd)7FtN|5OD3H>52!Pgij!ysix zNg_&(Rrn$KysF~yQk^kegoL^8=pfZQyO<<8bsh1hw5*XNZOMEFQ!_3qUq9BTKir*c zIlKfLR0`OL+_B8HO`Om z5Lw>=w3iI6&R$P5maz5<{jZ+y@uF>X#xvV)B$5nijQ&5C#!7Zm6%kGPZ?8GlZN&%U z<@Xr1Xe7IS;bfr5N9RiSn4cAUlK{gYQVlPliYfY}Z%Oo|uF0mT$dl788c9wdN9(E9 z=jUp!J|jLUDD@6NQfUMygk)M1kIxq(HC0$dZmD`}3)HWYtc=icl^&i?*)ym#PoPZ2p3V>4G_V_*mm( ze3kB#pklcB#yZGDGr=@PS~kzBJSYwQip8K4Tt z?pOS^|LKa@OELug3(`7#sWqmGADoFv86q)g9{nAZbffOU1|~U((=w`|^gAd^b<(N}=-L`dqtM zh3@mw{8~D@vNj6q&JiXc^g$=WfI&LZJwO{xx?R9Ox>)jWgQVJxDI)rnF& zx5dBE+D@fM@60?!sM^iC84zu9k&=%yTc7L(=Jd?YNAzA_Ky!jd`V1$LH2kO{f&@E&9OfJ|18!LxAP0rcFTQJXgsz(KBM4H2` zfZZFVb`}VQMn!Crp`WOeqbi^=!X_kVrpOCL3OA`lag@zeTPDo-(ZBAO3Yp5mZAi37 z{BrQ$h1)>QL_H~1ggG!mu*GFCdWWa>m?2ArE#^e2F(N}HEbf&GtrxGcbhJiUVwT@c zeoUxmaj$=fYy4&=nS*nVY543IHS^=FQ`Sg_BW@17=|y;2X17hm@ZNa%0rPrIGIMbz zV{uw=aYtd*+iJDgpk-=LF4{1KG^~7k%n!3cY|5a)%#fbc!>l2on!N#&WK8;Sfv*qd zCUZ)KXq{6C!w|Q3*x{({T*hbdp3&hP6&Yu#wD>HCvx&~dYEX1Z*z|%5qDqi>OEGA9 zAW)6I0a7A~5(&_2MzguZBs5xX8&|8P$v=66uyA;ED37*J)kYmXYCI-HBrIMq^<)NY z${S3T8JEJ=RJ=Q!T8jv#Sbdoavb6*6P%+c7=k|~QvTdnh20MkbFtCk8yCld`aNmmH zyM$ZRRBzNwQJ#vg-=TpNN&-}pO6X}4e6qo6+i6~A#&Le+1x@mBmA9~1@f?aoyirec z5;1{Ud`wDiw;f^B%>)uL=oK3Dw(>Mlc-`EOQoI~H-bf|nzCd}=Xqgr~>6-}#Mc^16 z)B(0#N^m*tZ$tJdAiuiPEAd1;Tks}tCZIll)j%BJpj7K4L)+~S^*t@MK*-aGPI5We zqwux3qPm_AMK%5WX+G4~qr6rSa|FaS!da_|V5r&{;l1*Doh+hH$q^s>=-BE3r!V$= z2tCn>C5s?W;1XTxjt2*q%mF+@AKZN^7_5TEx0N%R4x(noj~SVFlv74MMsyA+gE`O{ zP0_w>63e5oec0*-Rs1x|mi@g4tXEqV;(cJ@!I@;5+lJ|V*m(G7{PSpsFH6olu7_L+ zO_}r+oqV2>@(x4k`l&7*H&|z(*7wcIlCJFD zDsEaBA03}ALq&*m4?y7J(3ilD#14`Dlc)XIp(&Hbmq*aFC+*)@>eAOIdD+igm?-)` z$)EqnY%cvD93w+t zb4VY$Cy^v_2-5ad1IhT*Xv$se2>QlI_VpKI^P_Lrvuv6wAZ>th6eMsPwh zhoaMh`3X6XTJWQ4K=ndXPq%*E|77BuwYdq}w!O_-%ItPc0~*r9B11!(3Me^y4$agZ z^l&VsL)J4%UX1;l`*EzON*FVB`Ja>4FAH2y7Z?1$-86VJazTykbW<#L#yrHE%Hsi{ zr@N8|WWul za6Jql-3=+z*URXcs9%-E3fB}WICaWsq3U9z_l`joumosD7FeLxCg&IQPQ>jISGrQC zFTrseQmD$--1i}E0AIYdA<46A_NLzgqCD5OlI1!o*3fQe1LeUkZ=s(z`f)UyZ)N(~ zt~Swb=^(;kOKj!%2`4+z`lPXIWwX#Iwgs^=Sb~hDEcDyGTm5{#d5Dy5rUg~c9MseQBv&6*4r^&s#H@> zdwUR7&GBr$gtZWiv0#_0i^IT=xSL>;u+E#dF19dl2rsn?p@uUw1vM+WDjyqB*ui=U zQa;qV-A9Tjf0)}55boV{Sh!|-d3M-sn(G@F02hsR@9AjJ3EoTLz>9^#uxs%V-E*Ll>-_UyXCvQ{w+#K`w=>YbB=59i!7KSo* z)=s9MMGizVx(+7t4tBZ@hQ_Xc+=~dQQ86H1`0_|wmdcPQX?WoAezs)wNIo8Lba1b( z=9O}!#FVKO)yqZB->OJh>v*TqPx;X_H5J!>=iKMEnR!EW_!4Fd#Zx%$4)IZ z8BE`j1;&cRPbeWO=`-gA_B<{`M@>QnF4&MIKMLVP9hXFhUkH;Rln~jKYgUO3BN=*5 zNek92n|S9O800~#PUl~pG^)xwD;3M_-h|MM}I_Ed2b_wJ5#(w)^e<+b;utjR2uip_<%AOc~L#dwdyWvPM~G zF|uIGenWeoc>@+JX7Brw`!}!Gg_L)&_o;=Hr4oh;dE@1jck(wZkJ~o!F323G)Ra~G ziegIU%ZS+;cq_|o1XJbYR+Y$ug8|b4CjLL?Sv6t@TN7kV04GGwa zO)KlUpj4IoIzm6D!|er4hah}>a}=r=LvPgK`&BzfRm4wmpRXn%NQV>~LnS$@Xdqg%z8W$iE^JpE>t4Il73o%77#4Y z(21vvQRW$dBa4b0S+Z7GV~jeyqtc0eSEeuxA8NRyZ>YeCl-(u3MmK;fxRET=jh`)< zMI8l$dZ?$~gw?tQgthIpou(NaMyNoSj>wMbi(5xzN7?&%$2P7$-yqAi0i@2UIxIU3IkMzAbPYRtyG~sEY|vehtT9~F zVJ{B(Z!DRk0Kl&g96E{=P z(LAHrrtZ8jbXm4HK2YbcSGj*V@R&qiiqlx9o*k_^)yu)+RaZF$15^688Kkv4Kz?LQ z6Yrs_!q}82#8?3&9wX;GLxoxpSBCTBgkGtEsAP#Tg~clB9qXNBq9UP%p1l`#S#kveh($X5|Zl`={qTiIu$~3qu!AI(qx61(YVK?>Db%^dCRfE63wLo1^5_8A z`M9@ud|!Fj1g7rX7s?9DJ$^l`uYmX#eNLRPKBUd1egjPIXp6%-9bp?k8tEpOu9ucs zzYizm`ZBIM;cN%-+>%Gec>fCNz>aVQ_^xvwx3FW%$xuZ#U_vFfUhq|x#p$%^E5h-< zHitb7{>(+TdHPwXc^V~nRO4AG-b@a0J$H84T2rTW*a?b&b#5nNI=DFtJXTwzqHUh< zJVb>rQ$7l#!qgIir~n@dXKXMdz7{W%r=^qtg$rZOv{+E*ixS^Q!a)>mR~WR>PqW5+ zv@0bZ{%#+)>7oCJwQmZ~1nau2RK*qBwr$(CDzT4jzDVoIq7EFk)K zz*bRc8zvZqJKB06_E!nmC8glb-xtN859q^gd{QTI-~T&_So{qD{}&+z|8GMB&h~$3 zt%Pi?T&(_se*d-t79l|T30)8d5lB{c2b^Igpt7%-R})s#cqu8zCss#HjP^>Yb-FJA zdW#=*)yG(yFFa1-GGq1jeysiYaPbAin?61^7(c6z3EXknrutMn3de@jJ*NsZcVEm8 zM=6d{D!Sx*)+VHzWF?mBEPX;mt9{C%w%F3832h;}E)UP?m=!6hnmx_gL&415aeC{_ z;@V=p;jYM{e%0h%MXa7G;ZS2s2dYV>`@wwj>h>ZZtilL4$oTZUSo>%u3S#UAcvKoZ zhEpxSj0))Zvx+s$qXP1aggM7S)3KjYk5y2Hp6ePkFkCDg0rs9u%NiNj&Gv#cWgOqv zNIzY0le41g$Zx)JUXsz;=Zmi$%lnln{GPnhLK;wvHY)FR^6@bAP6J%Bx2ayh-Lb>P zA$@(1)96ECqIfD$fQ`w*Gvy^AU&x94mL>HV0Xy+-I6#mMw;&e2I%A^CKlT*kD|y(z zh8}TfLL99L&=y!E_8V%qA|BcOuIXw@4k2~4^T7WjCH))_aWJ?4 zs18y#A!HPm5H4wiFx|
  • j^?+O;cG=X1)pRX??pxCbh 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
    (5BE9wc4hwaBkSM)Th{+i)cJSr^H1R8PvlG4 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%0D^9%lu@tqK^*Isb;gply46~t-Fm0bujx*9=2d)t zejl(}Ko&!L9qKTXEo1}Hgsg#DkS5ArGJ~YSQcxT1gczioQ0q9N&OTQQJLXG+lV-5M z7)2@UEbRA1h+RWDR(wj%D55Sb;u>?!p&i3@NbnP4TgIEy-Ls4c&1$@oNJGiG`Jwn> zf)W>9R^@avarW)!?_s5v~0a2FtCy4KcjQY)1RTg+XT0!6k>6)B+VgBYeSK9 zQ%We6(S3eWMz$%R*I1-jU0qYTW@C4T*23#6T|y^3?N*|&f4%5clhG>eKwF-LaZxaO zQ&>dJP(Sz<%*$OeLb+U?@Y1YEQ~@o;VD_U~(HN8P1*IC}V@|rB8@r_f zmUqokd;GaDn+bprS)Zov*48RAyW}yFLy0CjtSKTaF^V}D8;KM-)(UXIN-*RiEuQbs zKr%&lV%qAL-q3D|$ zqTP~s!kD(RZ$p0Gemz#;e?@TDC$gQm?=ke&aK5T3$QjnVGPd60X+OYO8$@sS{nNe< z^miWbJ}ObazahW>)QVyJU+n9D3fzCtqW@JKB>k~7OAxUV9n^!OTQG5u7{LYJb08Hn zU>Hye6&whWVBkh1oUFU1%kY+fAuopD>xXZ0q8rKR2SJv0mUeV@ls)F{+w~QQLxeDl zf=&4w5yptxXAhwhjnx%?LXq$ybV4mB8PRVnja76IUCoYm#;b?Bs5ac!XOX3n{*>4U`$OBV~w>)Mj5>tEC7kEP%9mnxB=t35F(rtPR!d01h0 zJ=&{iPx%)^G9_E7-;ks0RLDOW#lDP_lPy;iY5O|!8xvc*+LgjBRi>yz?X~ zEBRum@P

    CrJsfgBDeRP%2e~D3vDFl}}ZUU$`q=-6VgG8Cv}^R?sORyiAiO4=kZWa zIusK83(DPl1xg3}df{5+hyBNV19F#*V-4<8y^ZKkf{TG4M_tXHJ9WXsO^mr*!i>lc z14(^xziw9mE1LMV;C@fJ&bU__Vq9^zavTo4SW;!BlZ z{3`)o3vj|V?ZB!#2O%oz$+qeEuwY{};gR{xk6pehU9eYtBVFbshqsA*bS zmVAUR0jn$BqNWFI6hhCyvd?LV<%fAb0b_UtnF`mX8&G9wPtcSuIfi0ctu7HKC2`5V zq`k}C`alax`ruAsZ&=}|SSzC-#)++2uOmHdXnak_mTVC_d)X;lb1H} z5Ow{3H(LA~3P?V8LJ`Fr>B%5#Bk2OfV?)y#&O%#Ih}4G1Yt>oQnI)h~h6O)hIKo`$MwGZx$T_qd5!Gr%k}m& zkX|tM5a`k{V~|z8hc?SGBZa&84V#EEqO2lI5oWX@bNI2ldVdtAv9%P%NGzg*biZtH z!g2B_zPVagdmnx!Yulk(>*+yq*#ZuDU}Y*<%XRR_KxCYP0Y_aiR=MqSd5M}z$TLse z1>Qj6d3^2%M5V7mRcbofe519rY{NpcI?0C7Ae;3y-KJ(t`!@J!>QTTER&u`nxIFLc zaKxJGc=eH^P)U+UkkESh?16+JyN5-K-tJwp7OOd#W-ZHNX=yXv@Vas1Iv9gi)=}hl z)e`f?8+m6>W?_mditCo3Zf^F)3R+EGOM{@nD>-~4vY*Q?zto&)9YQ1c6cG>^Nj|2v znSBryF1Dg8R?d>EEbm>Mo*5Xk9A(_v#}?S!4iC%EW}wWu*Y2*IT>TXC$k@NMmar{W z=B2MLPag4BLzM_#W=i#nPa~Fj=tG^)Q`BIKCFxDt?-PW)%{!eYwHd$+FcXm?WmxIh zIc6B#^`trzOuaQH_;SN4J5tInXwJ$0)HTJFjt~L=iJK)y#%t&k;Y?!@91^OW-}Efn zw25X`X??na-BTRVXZ$_})1MfLlA6pIn81`h0>h*?hy~SRyfzHMG_@TNNrP_O35Z}q zchDTh(MZ0Ci|pW-jK#ks=6RXYz>9P2HR-cAWdRx(+z@2Y!M)rKSu<3BdsiK_Ke}Mb zj~P`LaQmbxKfNouSRnC`y6LWM$GM!O`?GXXb;&j{X%abb;WSB|{fw%JR%$f&GsYpr zHpQzeqLd26?JyD=sSxw@oH-Kk5pdmz{=63?=ml#O2#2Um>+KE>k9bvQO+SsCa%A|^ z6{MYEqA`g2-G+7$F>;QV+cmKr-sjs* z&Np6pHziWRD@7xlk^o>sY(00~7dvr|0QXbyolRd({ufes1{PjFD(2;yCt#VccmdQQ z??^~+#!S(nu|%7)mAbJzgtoY|CAB7vnBC;JPG|E~kE?gM7dX$< zzhK4ar@p}J2c)$9t&h|H^4j^cJRu z0$5JC^QUT>oH}7)0fCaE3nJpZk@~zo@RKsyt4|z=J2lY-vXmJfZ zkThU45H!eOD3xTgt_y)cf?^U`)vJ3Wo0SR`Kvc0KuI)JmRCgm&arG1A-!mwID-9!r z&<(!jKT{9+b1W0NAs)?-$gi=i=y}))7O8noX^m~hIiqjMZ}BXbTeHO(Bpg;=)pR5= z0*)m9Ff44S{DGh$y>jc~fF{MhS@K1*ya11>lp^GKkaF6+-Y~CgUvlVv5@7LX3?6rO z@3cVe$Le;w-`8jNFDfuBU%-XHYlxghf6wbvK!np9wZe{iJK0ODj>&6l!e2Bg<1! zD51U`YPO)!Lb2qvm*gqJrbnM)(& zInDEx0czB>0vNWAYPUG76QVm-4@Cz!zBe(%fp0Pl9@YQ=oeqVrNOz;o26b!NC89ev zzao!hcc^ch_ji^?0-EIYMRe7XsWH*gt)&)~6yzfbHN){~aU%`-6ImQtvCCt@<-`Nu z3M}}z#?hGx5u-^mNT|k?s{Kk_G09RFAvqtk^|=g{mazUs;1`oJaX+Qn7E+}XQ1%%r zQ}#f67iGe6WO&mJ9i_lsnlVjE4i;m}*;)){CM%f6YIKG|4aX8hYZT2u*DnBS$nOA6*v_l2 zsT7o%FvdzDNlYCDGP{G&i_(EBvrr3bhpkJ_{h3X{QQN4b!mJ z8-o!nv`ObiBCXGl+t%581|XRi2X6}jr;#;WiD!52lXuom?v*}*e=Bp2L;;u{9U+M} zXI)fDNcw^Ia27slMn!Y~6^Zzn^J`2Ow~#~|@z_^XW0#CZ3O>`ib81Vj@hh^QlHE(( zfcn)qCQFI?vmw_63#<;A4o3sjspPLkFDH?f`8A3H+J#~BkW!&;4Gu>{?ra@niJX%d ztC=_1Dl+Y?NxB>KO6V8PW0x&@hGiM&BARR0ixdrAyN)Adv;+!k`-}!U#N@v_;nB=B6`WVo)z3Qwq0Kc)SY-r5ow2L}`Ks0zbV%Omi8Gu2{ z;llAizt*;!u-pIBO)$!eWm$~A091_LE>oG5kxt01i-uP$CNBzITrq|rhyb7U$2Iek zlq^M?Fd+OPX#9~@2Ur)0W^6uZw{v2f#Ej^~f>$?NYofYDToE-u%^rUmH{pJI-zn6A z9EjfjcRh3Uo~l4p#Xub$vid?=!5UuGFLv^lG0F#3$|P@p=tFXpmlX7UTs)w^(IPDV za`~|Re~%+UTPqi1c;N$P%!paf_=2}3R z9<(iqlHyBgmkg`?D2#h9O2@<32IM%XFEwbS24Apw9+n^Q26Y7$Wp<3)QM&rz^+Im; zvBzPC);-KNUT4~T>F{Bq6)M{Q=;;v1M{9C^d?db)zRtg<!wLWSm^1JKi9hRlmxUdJ8v?zXC_o>6}M>=}Rk zWd|@fAnwc~J>BkpBW1Di5am;vvIu_269Xniax}dTZ9oBHB3f)KUA$`)t|mz)z4Fge zD%1gTIz-}%E`({c2Y22YB!%5K)~Nhrx9o!N25~xhJ-x2^`s)@A=+H57Z=-p!GQTH&;D6P|u)`}VD@B^`%VB`rdqcH;4Qyjy+Y``m>KcjK&^C;|Xvc|$1`!6_$?wcC*`uM`X>l*x3a-a8aj^Y1Ca{tfI-7dfR z%cuD5LQ%0r;)yVr1rEsogID0H8%hKYNlYh%?LlBU!BC?OKG8uFdChQ0(Oj*`4f%m%o7Nk*p3>s@l7`?W z03XC)k`}m^Utwh@+eH!Kf?czAh)%-+$`Z>)3FI9=ZzoIGce%v&qY-FwqFFm_t*&Kd zaZE|N;!07*d05{4N>v8mn@hR!F6^C?(J3DsQz}O$FXa+;irjemoVHRzaBA|cqtUv_m=qj<@0V-_ zEFH0zF$dFg(j{n`hkAsYM!W5wZ#z9@@=J6vRompNff533uH=qzWu`x6fE6)>B;8l< zpj1uT3v0(NXzTi|luw1p*^fA;(?p=HFs^l(qu`)zm~2KnOW_PlX++CA=RABePVODI z&Kj%$+X8k0?XKH_At^&a#Q>t=^=^(tK(=NV=i~eG!G*w2biyy@+rh|B0?)BELlO4C z{KO949g5XRrA!0>DJj;35;(HO^;OyyoFWJnW*EQ~Uj$bnUrzN1xU|$4Kr!d_4~K3e zR~U8c5A=HcTUjKLzlrbP5scu!Anc#N|Mv_a1xyLDiBL-B!$C^5jagIDVsCzaF z5U((9F|rA@0IT&Xfm-#@M#O@o^i!y)deaqX`|VsP;2#R-SaFHPp|)MIhvO3+4~`tC zTAf}mKvz(!22I6@GQFG_7pqrYy@f$RP#0n)`p=;`(pL7Up%UvTP+}G(eQz`u5I31l z_@(kIErWSqm}e?)bZK8n5CT^!6?ck@b}!g%=Fq_t9Q{S|6?Hg*QD3Ach>q1-zI)gc5e)z=~HRQQ2fuSMq zhQa6Ejp(vUMNg%r;CSl8t%-~ z;-ajU`|h8Kw`8mxK-)klDph3U(VX8<-Ylci6LDEy=+!EWV2&J@em5>>v3T?_)cdkP z|E95qQ!cHT+xvaCOT9-(`SB0_lcq&~=EMg`ZvNKi{Of4r?~GmlZB+Qrp~eT3{nw=M zEl_Q_g-nTu2evr@S!E9o3~T|QTEveZil`0x+?%hVR^Le#pmP{Si{`rf@hplib2*W$ z9jH!ZGKJG_f_anm=KSsD?wsA*#avLQH$)eCG9o2Rd*cyhfC8-+vs-LeDO@E&o6=Va z%EC-mq8oOu)Mjb_?S1h_lMYg`HOFp>36bSM29r{lK#H z%i(kcL`f-5E%tukB9i`;S}CIaeW_;2qI*R&*IM;+1CL3EP1laLtN4RTw}~l~GH&@3 z#aTH}sPdsH4Wqd35fLr@`ddVU0@565FAAl_4JG z8#yTmntPF>^VK&OSN>N@`38j@QP=VDCzy$`H`3f>KJe5oPT$7NLFZr^W2Z4mp zrfr7OZ8hg-i0X^s30okX^`{xg>TFl&4;8u6sXAp;*JikHPAPJb@Q(9jqRtcF7U-Z{ z$RuiF))G)0NFy1j*4l{tE32b0Z=Q!x?F-0^K zdWC7GIlpfi^*2B%xJh`^9~_vk^|ym9Slnv&4&a+ddz(q3n`NhLfY`8C#9zr#!2rN5 zp>~;J;0v#y3dQQ&H{7(NM@E3RRm1Ux^4paqg&oLLhy_lhb{M->U$v)J(r-LV1j(j^ zy#{q4TWQ(PJ1Z~3=eZ3atmROrKGn}=Q9<8oS)$ANllP2!yz6CFlRaP3*djeVj^}}QP+Tv)CZ>3R~!oeoB%tp|v3k@ez z0#~voT(2MD+mvm{!Z&Dv-UyKsu6vYp1)th0R6ZbP^994O=^=4vAgNJ`MRE#+B;&F` z$tnLr3>vI{S6O)e*{IXsJTI*Z5>2Bm@M8iIA` zW+BCtT6xIdrNyQ`p$cRLFw!eSl?nUIjRBNz{>nSl$#MOkKX6&&Z_7xTzrp4I8?F8y z+_3-Kqkqkt5+Z5E0C^Do5Br@_sHefut81%iA|OqTQi}b--~o`2nVevj^=sYEE;8Q7 zdwx{}m$D-TL*yG>W6U()I<+=GT}(&C^sx}7yM3wUEjV={WBi8JJeLkfWvQuD6VEGl zgS(MPF4?=Q`zXmG*YO#fVXGGyH)XY_;H8`ap|T+ZxcEX}iI4kOfz~ zU^rbN^gt)xY(%i2qYgz_Y4F z&*v5Hf}mhi(Cd4rsWOHV;U2ganZrLid!Ud&*P9>jF7X4j{*|}?wMhRvR_k9BW&eD! zf2MI2@iJC3(jUT8D5w@WV7X~*_L6}RM~X!^CSgQ+@TK9$IDP&6awFqs6qF{AhS-C| zo8sv(VN}vwU~-L}vFSeq~uR-n>jzS~{*K6NTn+9r{{{33MFYa7~4op@tpQx32`i z-@TI1X;Q{<>MeSIH03d&C9hChS}ChL_f|n&g7eX&mC2!PxcRM=iZ6a~jcCeA1C%X4 znXa{{;3Q#^t!Iy%2n)mx|B?rz%sSPQ8XhM6K|UCztNeKYf(%qXQYS$gs5A?Gl$B^Q zk|VXj7`R_z4F&$LL|$lkPRpJG=7!)2|ECfdk&2^YmE;4=hh}( z$4YAaF~)4CRH4vJvA6ueE?=+S4RL?qmk6{j#2;@FDKE6VT0%5dZm7-FT@nfmhIH^t!~bq|qd(wsCW)+1&Azc!(h?ye*5BUFH0#1(|ey zs80+Ay>rSbb9hgX`n!7wIx%n9*ju>yf6%bOk?^0SKj1*FT#QSP+vyKiC=?K3Uz3Lb;#s6;sTb;ascA%WVgc=v=(D`Jsb`@6#8u*Ze{dGB=i(5v6{ADlxcJ9@dQZ3$V}A? zN2Qc?uR2IUR%dCzGUpNa7nZLd-uZKUAkE9i2mGJW=I_#@-!@$SM41Q`aA$P|Bran5 zlq7MjRQnl|v3TdO_ap=7#mFu`4k%c9pWi9`g_<5^V+S%b7ESD#3pIc z<;+8R8Z!bbGf!)<`)2c#oZ=aYB!4^^w zPX*Ch;7|3@n_y2B(MA3ols%nxvzNy(=&#cfl()oaFDgr3(?{PeO_3(RUL}S$177&f zJOo`QN0zrr#D3Vu3rjq zYXxra00bK7XmR?*d>vVT#~V<{o?A#O+iOvKFp<*dt)i6;)VJG*E5(ME65P@Lz|dd8 zpf(Vcy~TVXWs36DZ=Kz@|7Bc!BQs`WLj(U+ttH46@OAxufNuCccY8}(o5o7ofC3gq zi1&j_aVE--AOeQOvN|@jw81c2qpU^1W$fR}Vrqn!A=#&o>&$b*?drS+=3`|C6m`AU zLRg($y{b7VZyAASGgf}WxU;Q~2XBcbhQ8~DpCezEYNDiTXzo4)#bds&PMc!fnBc%a z_<-I;*Fb1;b~((Shksp_5-zwy6x{)#J(hvWl;h}P4nl2I$&QQ~MjUH6h1%#Hl8-M~ zM;#MMu*05RZS=*dhysF$E~mYB8VI?WDoWcGU#^|n%7p|@D(%|dEU6B3=6pO)e4>&c zl}Yhl_u)<4yfLNEB)70h5l^3AC_r4rw)6!cI zvF}{-uSBPLGfLQ9(f*jN5VhlGRep6(`;t<^PlN`rQXoQ)1eO)aR_@cXuXmH#y>0g8 zQ_gbXWYrS1*pKa;CxAz}4LHV!tqV*Oo06IQ{q)Lp$1O^vo3LF=krwB@etY7nuVO@W z*Hw?5`}~K`2LrN8nHJ+AC94LQ9oP2$jFka{6HAj^FXH$ROV7x?3`L89NMr}#W<{4| z)zuW{QYw#=_+T$FpY6r*$oKKorTP@=9Ho+D1JEJvi&<)XRV5lmPvGr*OB{93)A1i4P~mgwZsCsxeib) zp4xK`V@;-|I*7sv2Ubp-_}-t|fAYuBcg6zC;&H-xbkH0(GHnw)tbH_(z;I|u(5#e` z@6+Yo;T;ITM2yWgW3Zfg!sJpi!p53DCX!hQ|N1ka4vbZbXhZbj#J%HogU9K}{WI2L zbHbu&o8%$nF$=(UU5)WB8|$e6+xJ5;`;Pd<%=;cl2TwSyLOb^8olzIR2@IDwNh^QG zTS?a>Nf^<~c3v)iA%=}|J5VRnwSpUD+Mghw%hTNKHz0Va5wtCVwkKZX8)H zs2@z$zTbF5f$W!4C+-s;>9N6t?6747u@>J1gM|YiyZV2fe-iy%oFOxnDtXFH?R+K7 zX1WrKRI6C2p54`Tj#7>(Zq#mlK`P5hZZ5wURoW6Bapo=`$WqlB3ug-s-ecxIZdZx} z!EL4qq_b32{3%l?)(5ZTy4a>_vX6&%tAc8#ERsd;lj${lKm`DN{amw8b^(ZfsnO8TpALSZJY?I~G5W|h+Q$$@&R#4r1 zi|*shH;3+P&5fCIXpcekrjJYIKqEJzO_{hJ2d=S6jF(mUdKzT1-~&|4`x%jNyhu)F zy8({Hh5@hSs~@e68oTbv!S!5^UZpw3<@OKPo>@JUZTx~s;md%*J}ATzSq^%A*LhwA zXi%TZFRhiV)0;WErGw~O!>2TkMfz!9w-W|i=!WwhOrdWQC!2I3Yzu2q+7P;Bz`LIr zsY&!*;?02(08M5+!N#@0KpKQb;A{o7+L6jHL*_fazEIwt=bRiRH<>7J8z4=JtACbZ zm*P~~#2e2;@60~}gJc7T@5e+_^`l;uanGp8Rb3HL+K#&+L`|5QN9~NS9FB6*akrRW z>T-6PX7RUaGB($3D>CK?yxz#Q0owJT-xJH{F7uqT)N{mWT=id%EL*^z{4}4tL+&|{ zDYUM1{<)RY_{OSgki?|4ecIAW_C}#<9~KM516VL^$OjE?e6we^4_ndqyZk!`&e><% zX`FV5cBDkq_EbwE*>(|E==k6oH3|dwSkd8-W}{ciLkYQ%cLG*N$vC@E(P@XAG6|U! zFSLx*T-#2)&^6w~$5)s*E&(KC(d)9InVCci1;dgP9o2>12j|p#mr|!qHi6OCuU3_v zJ(nuWbubwUo>CH#DyNHf>ujAxT#Hs$jp^C+HFd6`6SqHW>p(hduh=m<2Phc9FViV9 zPM!3JK_OE!RN(Vcr9P;r?+h791vj*9U4~lLv8n;>ic2c04=fY@^ew>bK|Pk(R2jyo zOBRI&b3Au*0!DJoPsKqKLHL?bN^4NWYhQ|*;Hgl3Aw%ggy??etTsX0>U68Du;WmjO zQ#rs}-l7@55~*(oSqB!`%zJe+e)9`UXuVb|KD<0o`DvN|pz4ZcY02KMmFZb^$z)v? zBBfI;m$ztk3ld?ORA%79#v|XR3siB{_STYu;&-E|d<7S^@-W|Ym$t1KSBKe(?B1W& zI4I^+3hNyA7|}GAHt*@Q|FCN4rjwxFFVOVTLQyd}(_g!O?O5uc5I|Y_Lrs2qoxZV2 z$l@BAP1kC=)IFg*4s-ICi7jfSHnGC_Vg=3)6~4gOtWC05_4<=Knnc>H zmeS^ygQT2VHw=aQMS*P#OkgM0@S$h@w5u4;#v}TeRvy#$oW>lbTvsch$sE^*yhC~B zL!3%~oXoWbMx|9|1r1PgMb1x3{C3TqmZ?-J7K5$D;TsZ2_E5W^Vcg{tt1=QWBAQ026wYo(`;kf;Q8c zayx@oiqQl5Li*02l8bqhY}yQT19QN+pM6dWZQF;UqA36Klp|@~-d=}qtCInkuO_$; zK_HM}589-P^OlM6a7jiV>~Ji3zx{H2l_5q5gsJ{8x~OHa&3+rAf;lNeZW?gJ0};R6tSwprW|%^<2s42G(Or zQ4>@6V%g_*-31_I@8_uInjoR6q|jR%CIq_VRNyk3r+hwNqXUgcGMBDW3RrghqYIRR z?rz{SzVtnf1siWcgaqe>q$juEfhaU&y_vA`UL~Zb zn-763c-#*5G#;iy6X4f1fEQLj%(48hknq+r3cfPbZx6R#3edVHH;hn=t)_2N07uHi zQQ8N9X^@YWg#}awMBg}Xkh@9~1$##7y#l11iL6apnGkXUH@c@%6>QFeJOhdjVaE`^ z`^wL!f_I=`UE{D5DDOkz3xa08MlXXBq4=~j%SvG7Ey78 zym`-ETmW#^_2{F5#Z^kJnwxDfmke6LA^7+=#C>PW|EjoyB|YB zKiBY&qB<_#s1@bP{#0A*Js^W5R^YLQ88qbOzXZ-!*$m~m3=EKf5AML&N<`$TJxq_9!P!g(LyJC|D#f$JBzf5 zhyL{GC&u3udxC#cssHMQ|2xf};IBV?@c#c*oEOA?c<)NX2ELcnPpe)&wEBj*j`KPp z$%OId6iXlX1X!nHUJ0l6sgrg1Kw|etkoO0`Z1IJ})dllIY67s@{g@nM?v>Kq%+&g1 zu1g!nKva|57axcQUF$#AP@_=AcQiz;1b&>WC(7%kviyUjd@xOtfFLjp4nzfK%=HIZyr*d50$$F0l{@jkyimq z;^Yz?b0Hkj>K8nFbd(V~@ZQa_y>j!5(sx@9Ts0fs$><=mA6^W{GXn+2!I*3@(==Ip z*w2g^hf|!J@DWso^2_CG%N3F1WmJMf1wspzlAX<93ofKJ)CRO~l|C?8Du{ z7PUT(_3co$JdH6zMhj32g^rmsVmD^5oUA+}$UcPJw>EhcsLYr~SpuSl-=GK5U|#0w z0Odi6=};Jk-$+|C^{+6vCZ`;*1XViys{F5S^e$39D*v8;3`Raw;65x6{@Q0G`kP?* zu(~#|vop~(F*mVyq5fC*z~e7O5bgi=E25h(XRm zfD6XSg`)mjDn-GhJ})gZ1mAWMovbEVOQtJ2?NX*Jo+Do-f$zduV-v)VFo@*LY@`?w;rLFQHM55wtVC> zXw}Z)su1qnTi1OjHIG5EfDI z-!@Tnk|!Jew+}WmNm}DI3+E<+-iy`CyK+1Ez8ScdY-X>D;s;USvoF5@-TQ!m7}f~5 z{vb3IIo}2t%Mo43z0XANZWOGVe}UM=C)zlCR1$KAgk;HB{^0$SPIgl5B@JB1xx7i> zYGDi}-Mp;wdZHPHy3kK{l5at$kKbZSMpw)X^{^d?K&#`Z{rrRBGOQspa`h3xQ@_!W z{yN(GyZP|%5&T>9N$6PqQa1cE_(b&V{y~FCT>Ulp8|Wmh600Ud5%^3E7HlD-n7u&~ zERAa-OARiJXEl2+4bs>;e$gPD8{!oW^XwZ!G+^{jvYTQdm!l{eFqKc=%3j;~+CgtL zIWejAVSt^?QwnLMeblrX09$B3X0L<(AaAF|2qS+Z%@}1rb|QR|tcR+_AhGR2M^1Ts zS^2z%QbK8YC?M$CQ5Dd-H=?dd!#~56Q9yCw(xdmukBQjg_?w||*uVnXikxKRLB^!8 z8V5B}O^?Q|N?7HsA!eM~=~}``n``y@W~{%;gf;si8=aAJf$p@8M_xM3i0m1vdneVd z_lVKjfy>MeV(6Mwnr5ud95VZOI*og9X5a2WByx>DMeX?LT9xsTbIdw&p%ibuIb-I6 ziD=HJYxPPOZ^bvDqhQ%sue5LrnxlXbnqK)){y@pH!s135*g*{$>yg~%teCc|B#Q(a zhl2pML_?aGZIjc&+4*|Prx`lL0RB63h?r5^oADJ(nQ~s^Y?TX^yp>i%al1)^ zHmW5Pp)W24zcn8tzVnB4Ss0wSc1X=W&>5}$V&@^$YLOByTKeoO(-i2eu?-rUW_@~t z#2t{!I*Nt-lxrR|)l8!&V6U_q^xf>S6su&b&xSS(FJaVkODs8G+izDl2N7}Pi^FtZ+g z?m6w<%K^nfyx&F$)CmQ^A}ixaOXuUa-g!;#iun=dzywXia}3K!D7orD+c##CCbl@pBScG`j_T-SPhP?fBagwt^lnl$bjvEeHp5=}pWZ%a-|?qJ^e z)yc!DuGFd;Wn&)Y17`~ILcae&&aj61dIR!tb;SOr9vbi$=r{P!pkK+vQs2tyU*J#h z+tK{j(|@HJ715)=Ja+=$osZ{SJ2#Q|>wH-t*PCZP>wH)WkTk_GAgm50Ly4#!mo;B~ zb|;~Y$a79HH5(2IF%8Ye!Dr#Vc!tkuCLtqp~h~P2+}TBdDNQOrjp!f4OIrE zq$*>wr??iLQ6QCnBFba4`yIg?r8I)yFnC$%5Sx7@M6Ob@v?eN zxXF*rXgs$tU&KD~V7C~VduL+}@*apBPS;YqmtE z&#XKNEUw$2AlsZ7=GmeAKqq; zCi?$``vr}J^Q%Us9y;^hQ+XFQg{Om3uhZAZkcbB?AatHMF)VwQ^Zd{@6MY( z9I<~`1zxB;MR|EXA$2*J2f+3F8ei~Fh4zEVK1wr)!Oy_?S1pAblz$lT_`AXJU&S;v zQ=8M}sw8}fd8hhEq2U=2Vk?dF%GjV@!{RSlmEo z9!^Nw9?Z8Q$<1D>7^axDFdhO((D|oJSPp&{e)QMLu6%-bN<<`9AUQz$FiU{t)~vQz zH(auWN_aGTM7A$#>dr6;T<=4BwvcWBT|@=2W0-)Iu;bGrMPjlyQ&=9F6?R*ZPk2fF z@buI|g939v?UH6`wcb@Cou(96A4yfz5p-xWp-Gp@msNl$%t)t1Q`OAE%r}^oJxG+p zyXYe-pshkY&X>=GBv-7IpMnnRIb!+jokKkeT@k1_GL(9MHc|RUNcj*Ja+@ft80*qM zL`WamAF-xyI)y>b%8?~*r$%uAR#bFA=B9tqX-J^}j4M1406`UYuL=(}zL98uOjoyH z3t?+Tc4kA4=0E2kf10u5vjG#y`EA}CwJ?Og4dj6)z7<2QVw=TnVvKq`2}tLQN0I+ZbPs$ z909j$E}L+6 zMzk3i=%gGaLX3|}m>Fwq)#03qr(rWoEVxxYeL8!~652ELtB6Jw;7k;{y}m?dlgJCW zP1_iAA7BDUnpZ(=S6SMU6MT(`0qczoJe zRuC784u#eZMeR?RmL-b|b!5g1;F5Kya2-Hf3OE@q`R=#3A8_w)_p{c0jQo+Tm+xAe$l41};)^I;M&aTl)By*7&SecY zjJ|L!Yw$q|&_IPeYW09!LxW4s8za+(j#@H=S*mmKW(dM71$%&6?vUL9?CHcu@8tJ6 z)SQ*7t{1DW6O_@Wp88x8`iQ}ABwT%sqmg_Mzk1I_lxPCa{1BsWQkcfoim9EQ*MXrN z#qY!)%8ZZ(;2-j2-g8v)$Lpyg!AV1;Y{tEJnN;jOE)PZ^5-B7#&k`#IKeOT|6#bG1 zuQ_?sEXUy`5xg0FXf=G}2JU-|VnnXc?72pMN6Gr3Z<@+V_-QYpkh@XvSp@%GK{ zqJ5Nb&K$z%K$GbC*_@TxbigaA3whUKO;?)t!xmaLcg#A>rXhMIft}2UEM}ntUGPwW zgGzg2)mVUKOLB&rU2LWxBO{dLP6&*FaqlfIfeFJYG>GG9tyB^}qb(1+>e9UwR0okE zZcmHF@`4V41I=y?XNQl}i#8R!6SpRVgeeo5K_OK6V%Q@aYntWl@Qq9qE8k(z zwF2sU0UjL(u@CK9Ldaqg?GRZOXdsfv;-LaNxd9#Gh%wI>TAu>$5nVHpbf)iEK+{uS z+wYJSM4a?FN7X>q%|^ien2i}};F3J}w2Zo`n|=7c{c+2L9`s_au`iq4*#Q82!fd`0F)q@}FJv|H|tALH_t3 zay~hG2iaxz8)S@e=>7U<%J?A?W@Mytqx~I zJB1oP?17zt3{-a9_Q%bn7faWv!hw1#bk*Ejs8*dbF=)~@E%nS*?aBBVW^*XN1SQ9w zI$)uO7(kW&%&rqZ7S?vX%wb#F|KcKGBzy?%r^c#LZy^bwDIF1O5E`qiM2<9T8^6*b zi}1oLqb4E>YB|C2y?DaBuS6|AUBzBrhbCMf1;WTwLcxBXgdDV=zLK@>Tatwqv_m)~ zK&t@Qa@^<|Yo$^#W}Z$H2OqWz;v8Ee`b1FSG0knTn2dRsCEHP+jST#)-T1?+B`jqWHs`1{C%VAZ1RZ?M8kZiR~Vto@7! zEscn+&9vN~4#zOBZQ z*PYCZx2WqlujAwJoeY($qHiL6q>xI_)UBk2G5!?0;C}bmnZSHR%1VJ!A4)m{IdMuC z7x(%1P|aVhX(Tp>S=Ot8JecUC*Dj{YqYNP;>|dju9|32a=m>6Sy=K(Zc|+f3p+VJq z9hy)aqG6%0bEy-T8oh1n1xV{jg539sB4mqgXXPRp9X$%npmzhER=@=PppMPFtC6}< zPkpPgx=}CjlGecn#;H($!iB48?AgM0CwJvfI*W8Bua<2ou-wDWMvN2rK7lQ^M6l;; zH-(jGb6;z7U*ZF#UiekRUeRwaXgP;kboFNa z-f}baTnBB%nXjOw3SercFmUI(hoAW*(j3(ZT|4J1!axh|dMlP`zI>xZ!@Ub6@HI~; zi)b7mV(2#Jkc=s|r1RIIJFq3~-}pFm6@R-?ZTO!Zy5G7Szo*szc=X>W^6he?k{BQE zre90~KUuiME2)*h3j|x90H4V!RLGJ=#FvK2YuGCo*`kGGy6L-tL~qGG#!x+e;wmjg z0I-1FP6$;Q%V6i?;yT{p1AdBV+PlIk{Do&;f*2uFhiqY89Q<&I7PErc-%<^Kb!}h8u>7!}h??Ns=9(HGd+Qyq zPI!`4$;!$8TtZI?(YLIsud~6C^qMhnF?YSgA@K zE0bwi`(egqULsDM-lEplh@buA?ecA-@S^>J!#xk|UxiN9M3wpgX8BpT%lgTv6cx^VRj!r)fzd6=cxf)-Ng1l^u(BvtH8z zHP{W%$8r2ndycGBaL|)eqq4dV= zpyQlzT92SXMO;CAFkQPIB5mr`cgS*;&jHLK%8-3_B+eJ6(`V}uNnu(SG9n6gp})rt zMSn0wcNjNUM#@C`5eM@cV5$D$L)=>eCYh%d+6r-i*W~{M7MPEks0;LfL_x!G;S6xA zy@H@E?3Dd1xfzd`&jxoFTa~1$X9U)G#+hY!kIFtCG52k-w`wUT{pe?)@yAwQx?d>5 z?Es!FMNZmnCUSeyt#O!VY)jyC;5~%N9EUv}w`w8-c7^HACykKmM(v-G?`W}{*)fA$ zy^|d{V;wsb%OI&6eOx48Zxu_5r z-)tVa+22};Js(-ztFYdC5x)77Z#eH8V7wj5xFgEXmQlUox{PKC(9k-5+VsSD&hz_f z>WaBQ8*C8j9_35z7S-#h6nnDS(F03^lLL zcPK8opVQy-kM$9gnx%@?5A@agjqCo`9lO6agJ4RkF3t9_yH=cY+v zMSC?c#pmf-T7CdIBYX#BMS_wKmC}60E>JyBXSB zg@8Q_oxFi|i`s)|A}t5X*=1VJoC6C3yd{MAuQYAVrb36LFu2<0 zIkKx@jEnG^u(A|&{Z(km-83!a`RfjTTC8ga*Z_ecx(=J>DJ^;?L7~!=)&rpSM<4Bcd#k)WK$CQKO@~nj z2);cQ-GQGDp0;O5VkIrsdsvqAB2$-&nN=CvtMp@fAkW+eXurOe4hxG;b)tCKYu&}k z3xRTQGAYELP_nnjvF9Y{uDPfTP$FU42UXkJXhGb0+ztUYjsSnybX4Z>WO>( z1T`Y>PiJqwEGRQWV>!zvcP?fIVTEPm8Z5Id5rw%eh-ox^fL}MQ*ZL(j5N<7Ep&pDm z&~3WoXMSGX^!)>+0rx%*$`d=CoxeUn)&wkt08oKm?-!oR^UBx(JODz@KIgq3{Qk5d zLNc*oh&6(;nf5BFcLcNY5O-qlf3S>4yYZ}hK2A~n-<~4r|37Y_|2jc`UOyG`8oylp zIMY}h^$|Nn0CKn=NosQ7fO>?)jWf7|{SZh#H;%8WsuGWq$O^`eUu8W{b>wGo**~vg z&mXVX!(-x(=VP5+C$za;EZbdj&TPKFToZe{Em7J-YtUaRV?V2R)$dTw$3H>sNPsN3A>`Sa;vj# zL1S4nyOu6}AJ+X?K7r$_9rj>`$~O~QUth-wKi}|ZCGH1JV~-5RTTaXj|F&}1?R9D| z-Fpcn);&QLr4(@Jp^muroi^NdQDY@Bb5y!I_41?g{rb>&rgZ+$6JiD$bc~>iXbMfK z0iOo{tOC*-JbTC<7b9NAi8i2d!HE!q@1FG2#;>ZpTbmzm1FR({FBIAjr*L7Yn6L!Q zl=q4X9=Ep{DkkAWNGm?wH`0ikIpMK-LkB1CrCI~d(bu}@qL1#XHf@@Pge3T^zw*+R zH%ntrB(}S^;v({D*SP9gN;^;_wi)@>AD|tqdGpSAs2WkGL4EzSx}vvhxI#U0>_XZs zjEDUo7Ex94fWLOY*gpS~3RE)5*~-v#6w|XP%SK#_83-oBV#n5iV01h1i|7e5CFJTESwUNxDvxT`{zfv%Prw-~&;iLQ^0! zk)?JoKd_a=*j7eJt8vQh%wu;SrYFO-aDWSNCFzyalD6`ztrQg+*SdRP$k}$o?ax4W z)vCHz;&KjrlyVOS7em59viEu${ZQ7^_kD@8RvZIB^==VY=CVnf=V|V4>%kM8~F6^3(*w5>T@!Buei+Qx#!usBiy=ws#KBblukm z(_zQ9I<{?F9ouHdMn`XK+qP{x9oy*G)?}@{_c?p5`OZw$o-?URs!~tY`_FqnKV0{P z{%oDyZq0?i!eYvxoWHgnW`chKN3lR7K%x1_iGnif_)v8Q(_7{`;mAUf4U(8T4#EpF1AqFuWgey(q(epzXZVM> z*C%1w8qPbe(XRYjeE8qw$IAkn+Om%0+XB6H(Ud1@sTKr9`9YBLd0LcAGRmJcu=MW} zhaOJU>t}}ny1ccjmTo#cndcrvbr*8>9qxoQRuMA`4p7?DI>xT04xRr9q4S=_S5N=E zHQ4{lcJp^g!Tvw541evfe_e6@{pR4m>c+oe3(DF*{)p+ATrq;eGz1wOdR&rZyC*;- zaK#o}q2w1)7Fc11yxJqyvD@cNh;v@S=7Y>+_3i(|*xn|V5g|_jUyx?tz9DLgr}Mhw z>3w&M4M^^CIxqqo&Zf26c5J|!nydL_3$O3l1|yU>##f1v$9yQp)p92%Jiv`6o^u&l zi_ODpV2h;ztuV(mHaOr5%so?y@pHPqiQ!Fj&P;Y6%neP6h{rT^0LPYZZo+^Hj=j@x z#Q5ni1CqCIA)-yqk75R^yd43kKq!&8k$eFdYAguBR%ot^LsytA=<3pI)J$~wh*tHN8VB`p}f%OuXY*!FPTzn$Yj$2;celEzAY{ z8kGFB3aXc_W@(OWme%XPk$(dQCj6G|2Sr(&{c|fKG?WcRq`l*}KkwsC81HX?y3R|t zNt4<9S@WVS&%Ny~Q`3XjgK>_!x-==($qa~}tJ^`}*1#eBY!Nbv+%osTVugrS+|X};xV02)}ym^kAh3V3nU;(P>AqSv8|_-AYOu!8V9#z+kuQ3U5qlV z3#n0)R+4rJda7scWwj#ZhJYx-(mU}asZ}aDTSLA)90K(zC36p869keBi$#UH^B~4$ z$M1G);t;-CtQ6$SvKg$wtS~K%FRE}MN$<(m(I5`ZM7Ww1P>9I|lsc|z7wtZyH{ge# zg#We^vQt1SbeQ}|)syrnGXDxU$U>IEzos+L9;-L`yp@eOK%^R;G}eVnpn( z@^W+8Aa|26+NwRZl z!EAP{td5!Cu=e0Z^wpO~SpWrnf)SQP3rqsNB48OIaa*T2cH29dObGN~tUB5VkvdVI zqJih@m{Bo_x@@hVCu(i(T$!LWctn0Av8;kzZX9!TIk7}Eb1a75XmmaYH4-)sW~#(O zw-6(@^n!aWYivV^5z~lDto2-tzrlt?Q=9mG^l3q(*IXNZCS3Oe(wxrf#TUp#-8@J_ zS<2K*{p#$l>r$fb0rBtN^BCm5c8>*8T{Y)H7p~EIz#0VYlCF1A+|ViZd4YWG%+gkoa95(D znlg-Gfj;l~g~U^HR-C8tEvn$%Z%#LGj-%NqcL(vOYOczG0?28q4>Hn)zr}6x$@9D( zb|zK2(Lt78cNcVQ2pRH^Wu7JD58ttOMT|X=>Td0OL0*<&g%82wP3^*+dV~G#%bowP@ z(Hd0wh1nwotT!b>Y8EIZrO~Y`c{@#PbRcYXbL9S4A%RL;0#MFV_Id!y~guw4itmx=3vFw z$@6WliZ!3+6cSr8OEd%KhIo!yy&FWU=g!2}3G!_oN*nw?P`P6Z&IONc2<{*+s9eeT zK#ZxK@D(1QOXS=!mj;R=4N4%Q-M@>kpUFR5mgK1)6XfHeDP&z2dpx<}_9;YtyDN_7 zTpv5(eaHJ+GiNByvk~Znam4fUWO|EW9c|L=Eafj|95|8WoXPZ;x)Mxv;S^6m^Q z5sXNr3|enpu0~Kyuz?zi11BKILCg$^av6))D_|((C&7OLzDLF$tCMAp%%oq$ChE1#Q zs@_E+Ht>u`A8gWY$C0T2ZpS`p^v0?UDDDL=n_%QaC3+JWaB3zi($%=QV3s&!z2$Pb zHtnH_!;{v2Vq(Q!C|E~xwV5*c968EqY-ZD_aT~SmNIjH8fq@OJf{un3bs}ZVzG2LM zx)+hbyoK-r`CynsqkJ+QXKDua{Q}u^u4rKz1C2WFriyZMXbJ-l&2%g2Fa=2`C*3;p zt-SA9Xllq~i-md+<*Xx5q9mpez&yXG3^#D$>muxOk>E~EiKD2L=^#>EO^q6N21nA| z4su~)zM3xxr3E);8Z#1dNd(cV#oc7l&^U}tP#@@hIl8)LM_no^PYS&ZmhQDO4M%$_ zt}<`_QJ(BT;_)fc?rd2L)}q2u^3N^}4uKgi6{O5cnIBHL28z2H>7Y#&UsDTNGYJz+ zlo_q^2g8*tSOOAem_@!^_|@&zFC4H~E{E<_^d^WK+^1&j!)_f-q1q>WN%cySSFF-) zkfeBdj@2Gs@mh}qhUK)ToMCeSNQTY8m zl-7lNX;5jKPO3JWM~7=vC8BQ%r{Y?`7ZKx3Ew()18kv~9(|gk!3zzS$66;x%v2h1y(6nXRPT1s|=6l>LSh0}qXQXz3Is%<@G9DP*&e{_Sc=Av6jfqC@DY2FB zgKNbScnVMLjOEBZ6qXf7Gga=xYrRJAsa|4hK}Iu`oxiW3@xB{%I9~l?APz_L2*i`S zZSQTogtxumeFTEFSqo|w6QK=9cz((tDf~rkVjJl?3_0XRF`DU{^rS4hK`tHYd}W-s zCq_(`aOuSi@aPP(RBlO0=oeuQmNG+1DsV4D?#MkCO9z+Pq$Z}XWR_Ead1y)y#d(LT zgt%{v8^hb}KiJ{QO!d+3Jw!u@Jl8 zi`fgB@8vVbFVL%M7h(zi8K+h`He2X3?uo-Fjo2R!l5ttK%50=jN)$4>T!b;nU`l); zdkJpDGxRK;x>&8uh+x23g0)x;yDrf=$Wgp@mB548ZGm7);<{Z(F!t1G7&`XUA$VMJ z%sZ&vRqh;O@&U%0^Yfbfy8f5?NxlPeLF$*4{=E7*#rSQvXI7QVuP&F+=Q}Q*fqZ5| zZ-GINpS!QGZXkSDh}WZHz*SUmD5;%fJrplY(q5|$saH10tG_``+PfI($hFC6>|6Kl z%bx{az6?4+;qzw7;8T?H-}(XHzmZ6#|2GP}fUP;>zi2N>FRI95pE)nkkZ#rFU;?1! zDkap9!3t%K-$DiKET}d1dq|!dR(`F4dv3q^_hmJQD^M_0&=1W2=lg zY&q7Fghb$!Ilgjm4-~v&1^qpZ%6q$)c~SXxBim(W!72y{18{A z>Za8jv4(V9G2ny8*MLy{yqRbjcd!k6T1bx&ukn6X@=9u25ye_AFybjY?i<|r7~xWTigfBq0X7Qlu+YUHTcn5kG1j+l~G}=>XyoL z1!^UolQdTv)){r#q6zKv16yQH+E9Gl2TsY%?5*``xBWNzIWy_2dSz|K|tN`2oks2ckl7BQYy zw3iEip&ud^kPAs&KsKFLV6tB!Ri$m8!%$J`qQVVKggQJ8JShd)$ABx#k0=z+VBf5F zFP-x@5|emN<)t2Lqk2#4O;T4SHEWvK=plD*?pta`zK5;wSufMXA!@2_W5$l>_l2g9 zI`iL!SzEOjk-sYvD2>y)!glrl__{o_;jM1UN#tUB-Z`04hpGdRG&9@iHUiMLscamx&iu< zi;S!P!Af6r5xmGO@N29KE;l9l>ENseqFKTX`^**>MxD#)H2~GwLaK2Sdg0s!rO~{@ z*Skz4j0fXEpkaI)H#Mt0q}XHm8B2*KEOq$H^91>a)2xq$$hXb8D2djNqZAw;s9kuZ z6?*BsSL?5_NRJpn5{!K!uE=7)Q%Kh|>ji-;XVF_4EnhiSSf_Q^pKwCR5U11f?S*}x z`v%wnyHN4{UnS;*69yquF#`)8Sf!Zk8z_DS$k=2PwGQn5Chq+(Qj9BLE*!?RFd}2Q zI(9ZVeju$8;^25jTA@f^sE4QEhCCYBn!emN73T|ymZg|+Z3Z`uR?KIfj5Q)Z!Xb^% zVBRMPa?Z^fYD?82kqoM*KJp6b++#AzMI((5JhH{5HG?LlwfOvvL2H-AVHUt zFA*DSe?*_LTs#n#HcC*D7ng&p_3N35vlkO}8`e=2nN`9_^>DD ze96OZp6^Z2Bqvw38n>L2kt~!pRmlOcP2(D`%a`YEmy%M2t%i~maFw#pC=KJAA2Xu@ z>NDP;M+$o4A5dkD zc>?WpA{>X)VP`=qCxuP%&7SwAdV@)+zs0%Pu!WjlbFEow?~Rd!#*EJu za+8}#v)k;LswJ9#W|Q^y!9Nr_LiPeHGhIW|Y%Fh|d@_eozik776@v;L8c3InBM*D9zf=xazF!?iB~koxhUVus#(!?eiFUapoLF8oTfP1S+PO zgH|;+I)acWUgu$JXiZeP-*|MR<*M!b^Y6mnPrn?;jLdmv86MbNA5J+NVTn0!f% zfk$ydZz{z7-#knp``GD{qL55n-w4=;>WNm{%y^BbOFu6}Tad9OJ^E3ViwGj&`qV@o z#~N3DTQeZr2&MXd>zf%dOo(z8UQbHQAvc=qE=1TzV)N1Tkw>ONtD06FxP~doEnixz zYBSi(RWL_bukw~Wh~YHD>CT)g zxnpe5(O}rAEWEsMbWUL#UZIr0CmiWxI$AS|h*;1mqs+7PIVTmy8A**p zq~Hx-lq3may>+yDz0GVJi+1`-MH}mleEye!W3(vvog<})!2#@&E_3jz1z%Ig!%uU& z)(y)7ldX{AWDJmyA4657!B091IYyrlpWxO@#7P+2#2frO62FtCZV7|J*)cKa9wWMK zhDtSgyuW_eg4qPUX1ip#V`BFC;&Rk+2XTGeD*VuQg<>7u_84=3Khu?~Rr?Pqu{Q6y zui59ljly3hx4#4D7XR6M|1WU-|3n1+*R%g(GygYy-k~)0V@(+4om9Olu3|Qxe-=pd zYe7Mqgr=f~cYHj5EkQ*eD`Y;b0=#(Ly%l4^qR#S$$XgcscgT?L9CUUdq$K+-U<@Ic4yQu!l;;U!jy#FM1+ZxISRScDI)PpYu}%l zRGMvSX`28kc`r&UFjED<6eU_|2IN>tsEWEQLiSv*yr7uW2LZ&~OKN#O%}4hv~P|@R-gw zdBkrnuk>|)f*A%CQ0WeZ4aoIGSFI$jdKU^fV&IOnUZWP_T(X9$t-?8($c${p$8{!T zyt_HUjjuX-Wrbt5nhG2x9q$$2L1Bg*5KfvfBV$wt9XW??8$+3Y@mUQI`)B}y+|d`M z5i*cL(|vNA9vVU_zpi*st4mTvCpEGRufk94XC5{alIk3k%(BBfyW?()Z1=E!7>?5m zDcL-F<%Cy)t(~+mzr-ljr6pNv&4*<40hqXi80@7iNnn@vzHrbh#ZHz>IxU*u+ac4E zlY_|VNe;Fn?kBuc z(Lg0L>}SmEyV>iRE8i!GG(qgb@N}PrkE}-6d6YNh?5N-vd*g;cIGMhh~ z%2-nrSqUnihB~8)Z>Ta{YU#HEDudT^nk)HAR8tsAFWKfG1RP)Yr85U;6AiqW7v-nT z53DVyieu@tP5*a^LgLM#>DiDTAPuRUl-3W5@jL$l+_4!?Aeon(>UL6{$T1Iwq&3&X zwJ5MXoe?tCk=U1z;WsVua|OxBjb;=l!|BxHCmx)UnU{iUnyP`_{QAW!(KR%8t{ZY} z_0cY*{Y@wey8KnioK;j-9@`v$qfF`q&NLVSorseLij(cEdRH_vIf;v=T%{pbwjc%mdX>-f{8#t z>Wt7>+Ay|NJrqWs=mtV0eu!78Eh$?#yJ;;8kVs_RZ51i9reoBWru@slY_mcu0OP4Zl1^k1So{!IT(G2@wpg32RyO>)jg|ipP|wn z9k}z z;IT-<`TknR_oMNa$Cc%dt4jVx-}L~Z9B|wFM9>Xf!mP=oCO*K zv;`*-77Ypxvzq8LnvHkgte@Xz*DVZ;>nvL1=rRr%`>H!TsWGW)>n*5_RP)mbN?PL$ z^%(oI)$EQdC1_1r^uZ`DqA&ohI15G@R(pODQ-!8CJHW7XRLkkvW9swCNz6Q1aWFFI z_UYK)&aZs7B`pBSUi~vBb8VNi93o;LRtuOZXt@j!V*q?GyPgB^4wCf)SnsY zv=v7HQi|B$GIR3D-!=rI$cimkt3qdxWCQ>wM(hiS2*UC+%x)?wMqca5W1(&nH$KAk z$aDpmxrC4$vL@V$rp}-RhoGaxA((zP)bc7%uY;bl-)};ri4wjQguf1Qcb&l)6L&Pk ztLVJ2o9=<(&}D5$N;2IrZdz}1Bz(p(r0teS+YUb7aOu0kxv>yUjFcv!%3_^~%v{&G z%f_+?GTb))1S8^>iIY>2HOeEuAX^ zw&rf3aA|84YBAu*8x2b$Cj69VCj?VO6(kGH5>>}jC+sY%r#dWWLpnN#^2CE^YV=z; z%G62{`X&49FehaiL_XqX_RTpH-_R}?^IeXl9+K26G-OLKfrY%9>bfMs8y8Osevx$<;M<&1-Ugl&o3fRP#@@M7y9%nP12Wu}BcDFq>G$xph5Fwae->wMnrX z*XNBy3?HlkWr0JFV7Ji=+g`!DK@!7*Z+<_t^9)b3TLWPAqT6`}+5ysD-W#980vo(k zf1snEywG-Nr_pPzr?CpfUYNH^XGw5sHk+cF|6ce|@23y=ePeYGC@f6Kn4Mi7K7GzT zLD~*7SY))HG6NL3j|5$^@Bmhy6WrY!^^>IH+G(>FBhZ$Q%u__($rVdoX6zE7c7$$` z;AZV|K}0(8F#6be2C8ljQHH+CJzio5M7><51(10?f^Ii?J`!(J;AfI@@Aega?K{}@NE#^(u@?vmHZ+IA`Je(x5W3uBW|3lOvp(6yxT zh1R)}!iz#ob_>-5+H6t==N`-A4J5BsyGOD4wy2QouL?`pgDU$L-dXe;-aw36Ne#C4 zXOUy`hz-S6CU{0Yp(9&mJGDf3s!?zF%Ks~ z;UnITl<@nXG>eQJ>~w?AEs^vuTjJlR#s42Ja{e_f{`ZhrpknTUB7$%(GL9?$4Lv9n zgCNFG89^RFMiAH^J9rs^*;U4bGYznuS>WLu_if|BYxAQY{vF6CK5Cxl!s29+hF{Py<>RA;PbfWb}~lCQCJlWG;Ob z&CA^hfv5eoW!JANDN<2cm=&1KwXXgizfxcCcqxCn%j~};V(y6>qq4+crJxRFa ze2$oKvCI<-DI@b`j`B+zE%EmIYOkAFEN8@Tta&tQX_+MV<0tlAXv@e3=ChySm zBydW5hGH4N-MJ&u30BVTlfKpMK|{0nu&6l7L)u7kKWmO{mh zP(1d;v#JJ@^jB;<_eM#5%;R4J_rI^GS+T02Lq%xqRmu&Bh(-{d(3m7~XpdId7Od-~ z&lX6sv`H0x^{`|aN+g^;cXw)qF`bS+bt_Ij!uqa&oi}uV$R|%X4VhZe; zn^Xir%5n;6P3*DVW2i*LEsuAiU|%u6BsmOJzCmTS80ST=F~>2#X)B zrH0gh-w2`y4mz2px@7#M1xV-$Gcc^m_XT9qLc|P1hLBs5H!P71u!zhc@;wv<)Nn+n zUWHVv+2`$&wyNA~0<`67OUDWnigT^@jb1mLEGz2L##z!i4^X5WgCDFI7xu+R5;|iS4M8UgX7x;jEB5MuNqCo1p#eAb`hG;VN$Y^-KctgEPT+VLE`SQVfgTGQgGHo4p zXMy>^dBS+&cmlt2KN3HpJc3<@ZNWW~Tpq?$;iNUXV!lE>!aPE6)or<5ayZZLaJQLU ziQLByfbBhTUcWP5zd+reDPJyj>q5Bnpr@#=<7cs(N~!2{w~z7ry4qByp@9sC@k7G+ zlKkETrYtY3*~EIm7tjH;!?=ODY5KlVURML!*8uOu-adTa z1eXLdjhCWM4-*^zp^EbHAy@kHN%_M<`ghdw@4i+4E2^k}`d0myHtL_W^w_CCe1wL& z>f&um!t@e!fk)|T&U#3CAz>iR6=uE@eBI_pfWj}>1Pi#4p^t`0q*0GY0CEciCJGbR7 zbPQX0_`X)t8_k2zItL?^!`%11(|uTT?e6Gjn=%(lrVd4X2|Ffwd|R88wxVCttKn)) zXb-9BLo-3^UfFa3mjKhPuzKp`;fKa0Rb0OSel6<(W}?s3wr*ON`H}z}9WD$uCIe$= zdjxRbeaBRyKK(LRU++gE9$%q-IdjvUM=6qG^^rhYRgyOrH3nYvVXQH!Mmu;L@sOWT zYNUVGwA}G4=g%NlVOAj!uQh;f)3)>#b~)9p7V%@8rYE=@J<95~jVI+umh+xi>s5!u zdpQwbXw9VOGk1^AdAFu3)(JcK$iZ+OL)e#EkR!{z)7I33esO3N$#tO8X<{fy*4|0vBglm@vE2e}sqJ)3Hr@wfzR+tL;#N-gYg z9i}h8e6F^2Y)osM0Ru8#)=k+Z;-e}?Rmw{5}S(ietU}EBZ{pfJ{12aRrH1!U7VNbDSZz*HjFq) zyp#ATbk8z&j968?6SBvJpt+P^r$ryjCJ=Zx6upT&TGN^&85qn7FmgQ0V`su405yO&sf-pJO^&kUCl-^8i7FY(kiJ=4 zo-7k`qcL%=EN4Qk728T1_ljk8XE9oUFxap~8CgLKbls0es3^k$?Fo9+WR>>jO3jn` zT#1R(aGOXMruBiBuu!ooDA*gTC}{}vwTz{awNYU|eQEPqWxdv~*ken|HE?4l6Xp14 zk|e9DF6Pe2s~IMRleXBX>mB3smORPlN6ZbuF?RzK zzdO@(OKU3jf;R7dMcb9^WlpJ?yz*aKGQLRqj!juf`@%uE^v%G;%(ZxBF67XNTIMs> zR4Hp{=~k-Wq>-s2HlXSwTgE<$L*1$8lb7|4kAaDxwrNwi>Bch;>n(}%oB%>_+oXB3 zb8Gbx`pT~mN!M0Ur3T6e-4lsfXOQgXEaL(5=zUmF*s@OOvo1Bli3;X7pPujW8ujw9 zMXVXp2G1CMhu941;M9J+Rd`oAq!lS>F!~vAznPKzY6{3Wi7cG}-L#?rL>j$^ZaS2X7cdP4La1Nk$dPNg_3?)m~qaS;NhM; zcDZCBJLAyQ%Z<8#TuBY;7lM|mJ{Y%btwNx^T7@83xx7A&1c?M{g>u|nbh*C<>cXrU zh(x#%Q8`RcHq>SxDeCc-1S*!98?*!wbIG)HZ9;xveu5FUm1+*`in1$KraS>$D^$2> z1~K^hl8B`QRtj^iSv|HD>J0N)fjQp@UTjBwBm8}tEzpXPt&neH+e4&s_8VV~Zs~?$){Z4k(s=?G03q@{G%M}44%DYcTEI8R zR#+9RYBxy>Vjl|5eoKFjTSG}n4PeM(_%o|KYFjyH> z(qjiChj16F_=P|+#JP@>LYfd+T{GJ#pU=E67QdX!Fh^@Kt!Y9h>K8#wdJd|1?OYFA`?D zqm>-y&zbO5N!XP?T+Q8xcYG_jf2>K}QM))SL632lkX%0=`m`9)?pCBDx(kg@y*3=U z@REn6-YZd!ahH*F>b@&Bw*$x9Dq)LuSCq8AVw$F!>?X{7CQ|T;Rd{)YxeY6Uzn;sA zc@B%`+){i9E*apyQ_bIqRmi$4@fdK^9#`}cI=)aicJS7@>f-$T;O!EhZ+CJOcW$md zd3Xq>*#SlGfpc92bN)Ku_FK`1Z-m^Ti-I@_y-n3Pe43+|?y%SYQ1$uNtUH>0PEiWz z|2{?i-G5R1|9jW_FZ;!U#5YZp&+axwUVF|7$lb9lG3Wf*T2M$5&`a1F@~MK5GOnuw z38!XacS%etum887erDo^>%;fr%*hm7{pO7@b#D00mWRnV?{kg^>-(y%i`OqeR9g=i z)PepC7|=n4mv}szWE|4=0ec{^@Dh=+I7Cmudxo*Q#AM>U1W)mM#3K}cDjYau>;a!` zi%w!PHl66tw)ICd>m+ze+#?(ziMok<(&>iDRHLHm0tk)4{A{Ne1i|BFma4os+<-0CLzVGAw#tOeoMr>=b^Kqa(?92y;ZwO* zlu57|&VXZT<}JK^Mx5#wmtTvx%4!kNNRRwuTTrOJD#c7!juFoycN&ls7_s#5YhjyF zM%i_w*z&iuaK-eK9()%3ucHJU_|#CcR`w6aBlXm+sC&H|6#93glJjBzK|u(bUO*%c zG;T|_n;7lMrAc_rcal+nTp_E5_h+18_|9SC6z~*^K#&%(P!aVG5BY#S(YzY-_3N+7 zYh#pPsIhOAy*JZU-L;5)+}CORmDrT!zPd`}L1~mq(d8uSV%7=7%EXY(1-E0%0APx;5*cv*ndo^u%^yWnFK{EO&F!rM^PW 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 z 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�h79FDEA-qk3J1i?HE^M;muuA8 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 literal 0 HcmV?d00001 diff --git a/qt/i2pd_qt/android/project.properties b/qt/i2pd_qt/android/project.properties new file mode 100644 index 00000000..4d07452b --- /dev/null +++ b/qt/i2pd_qt/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-11 diff --git a/qt/i2pd_qt/android/res/drawable/itoopie_notification_icon.png b/qt/i2pd_qt/android/res/drawable/itoopie_notification_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8fbe24689b0af36100a6afa238829d7ce96a6529 GIT binary patch literal 33199 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 + diff --git a/qt/i2pd_qt/android/res/values-de/strings.xml b/qt/i2pd_qt/android/res/values-de/strings.xml new file mode 100644 index 00000000..320d9ec3 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-de/strings.xml @@ -0,0 +1,6 @@ + + + Ministro-Dienst wurde nicht gefunden.\nAnwendung kann nicht gestartet werden + Diese Anwendung benötigt den Ministro-Dienst. Möchten Sie ihn installieren? + In Ihrer Anwendung ist ein schwerwiegender Fehler aufgetreten, sie kann nicht fortgesetzt werden + diff --git a/qt/i2pd_qt/android/res/values-el/strings.xml b/qt/i2pd_qt/android/res/values-el/strings.xml new file mode 100644 index 00000000..3cab212f --- /dev/null +++ b/qt/i2pd_qt/android/res/values-el/strings.xml @@ -0,0 +1,6 @@ + + + Δεν ήταν δυνατή η εÏÏεση της υπηÏεσίας Ministro. Δεν είναι δυνατή η εκκίνηση της εφαÏμογής. + Η εφαÏμογή απαιτεί την υπηÏεσία Ministro. Îα εγκατασταθεί η υπηÏεσία? + ΠαÏουσιάστηκε ένα κÏίσιμο σφάλμα και η εφαÏμογή δεν μποÏεί να συνεχίσει. + diff --git a/qt/i2pd_qt/android/res/values-es/strings.xml b/qt/i2pd_qt/android/res/values-es/strings.xml new file mode 100644 index 00000000..cf0b54d0 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-es/strings.xml @@ -0,0 +1,6 @@ + + + Servicio Ministro inesistente. Imposible ejecutar la aplicación. + Esta aplicación requiere el servicio Ministro. Instalarlo? + La aplicación ha causado un error grave y no es posible continuar. + diff --git a/qt/i2pd_qt/android/res/values-et/strings.xml b/qt/i2pd_qt/android/res/values-et/strings.xml new file mode 100644 index 00000000..d55a3c14 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-et/strings.xml @@ -0,0 +1,6 @@ + + + Ei suuda leida Ministro teenust.\nProgrammi ei saa käivitada. + See programm vajab Ministro teenust.\nKas soovite paigaldada? + Programmiga juhtus fataalne viga.\nKahjuks ei saa jätkata. + diff --git a/qt/i2pd_qt/android/res/values-fa/strings.xml b/qt/i2pd_qt/android/res/values-fa/strings.xml new file mode 100644 index 00000000..a8d1b874 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-fa/strings.xml @@ -0,0 +1,6 @@ + + + سرویس Ministro را پیدا نمی‌کند. برنامه نمی‌تواند آغاز شود. + این Ù†Ø±Ù…â€ŒØ§ÙØ²Ø§Ø± به سرویس Ministro احتیاج دارد. آیا دوست دارید آن را نصب کنید؟ + خطایی اساسی در برنامه‌تان رخ داد Ùˆ اجرای برنامه نمی‌تواند ادامه یابد. + diff --git a/qt/i2pd_qt/android/res/values-fr/strings.xml b/qt/i2pd_qt/android/res/values-fr/strings.xml new file mode 100644 index 00000000..efc0fb6e --- /dev/null +++ b/qt/i2pd_qt/android/res/values-fr/strings.xml @@ -0,0 +1,6 @@ + + + Le service Ministro est introuvable.\nL\'application ne peut pas démarrer. + Cette application requiert le service Ministro. Voulez-vous l\'installer? + Votre application a rencontré une erreur fatale et ne peut pas continuer. + diff --git a/qt/i2pd_qt/android/res/values-id/strings.xml b/qt/i2pd_qt/android/res/values-id/strings.xml new file mode 100644 index 00000000..aaa5bda0 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-id/strings.xml @@ -0,0 +1,6 @@ + + + Layanan Ministro tidak bisa ditemukan.\nAplikasi tidak bisa dimulai. + Aplikasi ini membutuhkan layanan Ministro. Apakah Anda ingin menginstalnya? + Aplikasi Anda mengalami kesalahan fatal dan tidak dapat melanjutkan. + diff --git a/qt/i2pd_qt/android/res/values-it/strings.xml b/qt/i2pd_qt/android/res/values-it/strings.xml new file mode 100644 index 00000000..4773419c --- /dev/null +++ b/qt/i2pd_qt/android/res/values-it/strings.xml @@ -0,0 +1,6 @@ + + + Servizio Ministro inesistente. Impossibile eseguire \nl\'applicazione. + Questa applicazione richiede il servizio Ministro.Installarlo? + L\'applicazione ha provocato un errore grave e non puo\' continuare. + diff --git a/qt/i2pd_qt/android/res/values-ja/strings.xml b/qt/i2pd_qt/android/res/values-ja/strings.xml new file mode 100644 index 00000000..ba1cfda9 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-ja/strings.xml @@ -0,0 +1,6 @@ + + + MinistroサービスãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。\nアプリケーションãŒèµ·å‹•ã§ãã¾ã›ã‚“。 + ã“ã®ã‚¢ãƒ—リケーションã«ã¯MinistroサービスãŒå¿…è¦ã§ã™ã€‚ インストールã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ + アプリケーションã§è‡´å‘½çš„ãªã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ãŸãŸã‚続行ã§ãã¾ã›ã‚“。 + diff --git a/qt/i2pd_qt/android/res/values-ms/strings.xml b/qt/i2pd_qt/android/res/values-ms/strings.xml new file mode 100644 index 00000000..6e3952ea --- /dev/null +++ b/qt/i2pd_qt/android/res/values-ms/strings.xml @@ -0,0 +1,6 @@ + + + Tidak jumpa servis Ministro.\nAplikasi tidak boleh dimulakan. + Aplikasi ini memerlukan servis Ministro. Adakah anda ingin pasang servis itu? + Aplikasi anda menemui ralat muat dan tidak boleh diteruskan. + diff --git a/qt/i2pd_qt/android/res/values-nb/strings.xml b/qt/i2pd_qt/android/res/values-nb/strings.xml new file mode 100644 index 00000000..8a550e99 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-nb/strings.xml @@ -0,0 +1,6 @@ + + + Kan ikke finne tjenesten Ministro. Applikasjonen kan ikke starte. + Denne applikasjonen krever tjenesten Ministro. Vil du installere denne? + Applikasjonen fikk en kritisk feil og kan ikke fortsette + diff --git a/qt/i2pd_qt/android/res/values-nl/strings.xml b/qt/i2pd_qt/android/res/values-nl/strings.xml new file mode 100644 index 00000000..8a45a724 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-nl/strings.xml @@ -0,0 +1,6 @@ + + + De Ministro service is niet gevonden.\nDe applicatie kan niet starten. + Deze applicatie maakt gebruik van de Ministro service. Wilt u deze installeren? + Er is een fatale fout in de applicatie opgetreden. De applicatie kan niet verder gaan. + diff --git a/qt/i2pd_qt/android/res/values-pl/strings.xml b/qt/i2pd_qt/android/res/values-pl/strings.xml new file mode 100644 index 00000000..9fefc92d --- /dev/null +++ b/qt/i2pd_qt/android/res/values-pl/strings.xml @@ -0,0 +1,6 @@ + + + UsÅ‚uga Ministro nie zostaÅ‚a znaleziona.\nAplikacja nie może zostać uruchomiona. + Aplikacja wymaga usÅ‚ugi Ministro. Czy chcesz jÄ… zainstalować? + WystÄ…piÅ‚ błąd krytyczny. Aplikacja zostanie zamkniÄ™ta. + diff --git a/qt/i2pd_qt/android/res/values-pt-rBR/strings.xml b/qt/i2pd_qt/android/res/values-pt-rBR/strings.xml new file mode 100644 index 00000000..67ac3f9f --- /dev/null +++ b/qt/i2pd_qt/android/res/values-pt-rBR/strings.xml @@ -0,0 +1,6 @@ + + + Não foi possível encontrar o serviço Ministro.\nA aplicação não pode iniciar. + Essa aplicação requer o serviço Ministro. Gostaria de instalá-lo? + Sua aplicação encontrou um erro fatal e não pode continuar. + diff --git a/qt/i2pd_qt/android/res/values-ro/strings.xml b/qt/i2pd_qt/android/res/values-ro/strings.xml new file mode 100644 index 00000000..f88a442b --- /dev/null +++ b/qt/i2pd_qt/android/res/values-ro/strings.xml @@ -0,0 +1,6 @@ + + + Serviciul Ministro nu poate fi găsit.\nAplicaÅ£ia nu poate porni. + Această aplicaÅ£ie necesită serviciul Ministro.\nDoriÅ£i să-l instalaÅ£i? + AplicaÅ£ia dumneavoastră a întâmpinat o eroare fatală ÅŸi nu poate continua. + diff --git a/qt/i2pd_qt/android/res/values-rs/strings.xml b/qt/i2pd_qt/android/res/values-rs/strings.xml new file mode 100644 index 00000000..3194ce90 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-rs/strings.xml @@ -0,0 +1,6 @@ + + + Ministro servise nije pronaÄ‘en. Aplikacija ne može biti pokrenuta. + Ova aplikacija zahteva Ministro servis. Želite li da ga instalirate? + VaÅ¡a aplikacija je naiÅ¡la na fatalnu greÅ¡ku i ne može nastaviti sa radom. + diff --git a/qt/i2pd_qt/android/res/values-ru/strings.xml b/qt/i2pd_qt/android/res/values-ru/strings.xml new file mode 100644 index 00000000..d3cee80f --- /dev/null +++ b/qt/i2pd_qt/android/res/values-ru/strings.xml @@ -0,0 +1,6 @@ + + + Ð¡ÐµÑ€Ð²Ð¸Ñ Ministro не найден.\nПриложение Ð½ÐµÐ»ÑŒÐ·Ñ Ð·Ð°Ð¿ÑƒÑтить. + Этому приложению необходим ÑÐµÑ€Ð²Ð¸Ñ Ministro. Ð’Ñ‹ хотите его уÑтановить? + Ваше приложение ÑтолкнулоÑÑŒ Ñ Ñ„Ð°Ñ‚Ð°Ð»ÑŒÐ½Ð¾Ð¹ ошибкой и не может более работать. + diff --git a/qt/i2pd_qt/android/res/values-zh-rCN/strings.xml b/qt/i2pd_qt/android/res/values-zh-rCN/strings.xml new file mode 100644 index 00000000..2eb12698 --- /dev/null +++ b/qt/i2pd_qt/android/res/values-zh-rCN/strings.xml @@ -0,0 +1,6 @@ + + + 无法找到MinistroæœåŠ¡ã€‚\nåº”ç”¨ç¨‹åºæ— æ³•å¯åŠ¨ã€‚ + 此应用程åºéœ€è¦MinistroæœåŠ¡ã€‚æ‚¨æƒ³å®‰è£…å®ƒå—? + 您的应用程åºé‡åˆ°ä¸€ä¸ªè‡´å‘½é”™è¯¯å¯¼è‡´å®ƒæ— æ³•继续。 + diff --git a/qt/i2pd_qt/android/res/values-zh-rTW/strings.xml b/qt/i2pd_qt/android/res/values-zh-rTW/strings.xml new file mode 100644 index 00000000..f6e68efa --- /dev/null +++ b/qt/i2pd_qt/android/res/values-zh-rTW/strings.xml @@ -0,0 +1,6 @@ + + + 無法找到Ministroæœå‹™ã€‚\n應用程åºç„¡æ³•啟動。 + 此應用程åºéœ€è¦Ministroæœå‹™ã€‚您想安è£å®ƒå—Žï¼Ÿ + 您的應用程åºé‡åˆ°ä¸€å€‹è‡´å‘½éŒ¯èª¤å°Žè‡´å®ƒç„¡æ³•繼續。 + diff --git a/qt/i2pd_qt/android/res/values/libs.xml b/qt/i2pd_qt/android/res/values/libs.xml new file mode 100644 index 00000000..4d68673c --- /dev/null +++ b/qt/i2pd_qt/android/res/values/libs.xml @@ -0,0 +1,25 @@ + + + + https://download.qt-project.org/ministro/android/qt5/qt-5.4 + + + + + + + + + + + + + + + + + + + + diff --git a/qt/i2pd_qt/android/res/values/strings.xml b/qt/i2pd_qt/android/res/values/strings.xml new file mode 100644 index 00000000..713c7aa0 --- /dev/null +++ b/qt/i2pd_qt/android/res/values/strings.xml @@ -0,0 +1,10 @@ + + + + Can\'t find Ministro service.\nThe application can\'t start. + This application requires Ministro service. Would you like to install it? + Your application encountered a fatal error and cannot continue. + i2pd started + i2pd stopped + i2pd + diff --git a/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl b/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl new file mode 100644 index 00000000..bbd8116d --- /dev/null +++ b/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl @@ -0,0 +1,60 @@ +/* + Copyright (c) 2011-2013, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +package org.kde.necessitas.ministro; + +import org.kde.necessitas.ministro.IMinistroCallback; + +interface IMinistro +{ +/** +* Check/download required libs to run the application +* +* param callback - interface used by Minsitro service to notify the client when the loader is ready +* param parameters +* parameters fields: +* * Key Name Key type Explanations +* "sources" StringArray Sources list from where Ministro will download the libs. Make sure you are using ONLY secure locations. +* "repository" String Overwrites the default Ministro repository. Possible values: default, stable, testing and unstable +* "required.modules" StringArray Required modules by your application +* "application.title" String Application name, used to show more informations to user +* "qt.provider" String Qt libs provider, currently only "necessitas" is supported. +* "minimum.ministro.api" Integer Minimum Ministro API level, used to check if Ministro service compatible with your application. Current API Level is 3 ! +* "minimum.qt.version" Integer Minimim Qt version (e.g. 0x040800, which means Qt 4.8.0, check http://qt-project.org/doc/qt-4.8/qtglobal.html#QT_VERSION)! +*/ + void requestLoader(in IMinistroCallback callback, in Bundle parameters); +} diff --git a/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl b/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl new file mode 100644 index 00000000..f19caa69 --- /dev/null +++ b/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl @@ -0,0 +1,65 @@ +/* + Copyright (c) 2011-2013, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.kde.necessitas.ministro; + +oneway interface IMinistroCallback { +/** +* This method is called by the Ministro service back into the application which +* implements this interface. +* +* param in - loaderParams +* loaderParams fields: +* * Key Name Key type Explanations +* * "error.code" Integer See below +* * "error.message" String Missing if no error, otherwise will contain the error message translated into phone language where available. +* * "dex.path" String The list of jar/apk files containing classes and resources, needed to be passed to application DexClassLoader +* * "lib.path" String The list of directories containing native libraries; may be missing, needed to be passed to application DexClassLoader +* * "loader.class.name" String Loader class name. +* +* "error.code" field possible errors: +* - 0 no error. +* - 1 incompatible Ministro version. Ministro needs to be upgraded. +* - 2 not all modules could be satisfy. +* - 3 invalid parameters +* - 4 invalid qt version +* - 5 download canceled +* +* The parameter contains additional fields which are used by the loader to start your application, so it must be passed to the loader. +*/ + + void loaderReady(in Bundle loaderParams); +} diff --git a/qt/i2pd_qt/android/src/org/purplei2p/i2pd/I2PDMainActivity.java b/qt/i2pd_qt/android/src/org/purplei2p/i2pd/I2PDMainActivity.java new file mode 100644 index 00000000..23b32312 --- /dev/null +++ b/qt/i2pd_qt/android/src/org/purplei2p/i2pd/I2PDMainActivity.java @@ -0,0 +1,97 @@ +package org.purplei2p.i2pd; + +import org.qtproject.qt5.android.bindings.QtActivity; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; + +public class I2PDMainActivity extends QtActivity +{ + + private static I2PDMainActivity instance; + + public I2PDMainActivity() {} + + /* (non-Javadoc) + * @see org.qtproject.qt5.android.bindings.QtActivity#onCreate(android.os.Bundle) + */ + @Override + public void onCreate(Bundle savedInstanceState) { + I2PDMainActivity.setInstance(this); + super.onCreate(savedInstanceState); + + //set the app be foreground (do not unload when RAM needed) + doBindService(); + } + + /* (non-Javadoc) + * @see org.qtproject.qt5.android.bindings.QtActivity#onDestroy() + */ + @Override + protected void onDestroy() { + I2PDMainActivity.setInstance(null); + doUnbindService(); + super.onDestroy(); + } + + public static I2PDMainActivity getInstance() { + return instance; + } + + private static void setInstance(I2PDMainActivity instance) { + I2PDMainActivity.instance = instance; + } + + + +// 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, + LocalService.class), mConnection, Context.BIND_AUTO_CREATE); + mIsBound = true; + } + + void doUnbindService() { + if (mIsBound) { + // Detach our existing connection. + unbindService(mConnection); + mIsBound = false; + } + } +} diff --git a/qt/i2pd_qt/android/src/org/purplei2p/i2pd/LocalService.java b/qt/i2pd_qt/android/src/org/purplei2p/i2pd/LocalService.java new file mode 100644 index 00000000..4c158c08 --- /dev/null +++ b/qt/i2pd_qt/android/src/org/purplei2p/i2pd/LocalService.java @@ -0,0 +1,92 @@ +package org.purplei2p.i2pd; + +import android.app.Notification; +import android.app.NotificationManager; +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; +import android.widget.Toast; + +public class LocalService 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.local_service_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 { + LocalService getService() { + return LocalService.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("LocalService", "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.local_service_started); + + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, I2PDMainActivity.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.local_service_label)) // the label of the entry + .setContentText(text) // the contents of the entry + .setContentIntent(contentIntent) // The intent to send when the entry is clicked + .build(); + + // Send the notification. + //mNM.notify(NOTIFICATION, notification); + startForeground(NOTIFICATION, notification); + } +} \ No newline at end of file diff --git a/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java new file mode 100644 index 00000000..677e8f46 --- /dev/null +++ b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java @@ -0,0 +1,1621 @@ +/* + Copyright (c) 2012-2013, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.qtproject.qt5.android.bindings; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.InputStream; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.DataOutputStream; +import java.io.DataInputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; + +import org.kde.necessitas.ministro.IMinistro; +import org.kde.necessitas.ministro.IMinistroCallback; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageInfo; +import android.content.res.Configuration; +import android.content.res.Resources.Theme; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.ColorDrawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager.LayoutParams; +import android.view.accessibility.AccessibilityEvent; +import dalvik.system.DexClassLoader; + +//@ANDROID-11 +import android.app.Fragment; +import android.view.ActionMode; +import android.view.ActionMode.Callback; +//@ANDROID-11 + + +public class QtActivity extends Activity +{ + private final static int MINISTRO_INSTALL_REQUEST_CODE = 0xf3ee; // request code used to know when Ministro instalation is finished + private static final int MINISTRO_API_LEVEL = 5; // Ministro api level (check IMinistro.aidl file) + private static final int NECESSITAS_API_LEVEL = 2; // Necessitas api level used by platform plugin + private static final int QT_VERSION = 0x050100; // This app requires at least Qt version 5.1.0 + + private static final String ERROR_CODE_KEY = "error.code"; + private static final String ERROR_MESSAGE_KEY = "error.message"; + private static final String DEX_PATH_KEY = "dex.path"; + private static final String LIB_PATH_KEY = "lib.path"; + private static final String LOADER_CLASS_NAME_KEY = "loader.class.name"; + private static final String NATIVE_LIBRARIES_KEY = "native.libraries"; + private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables"; + private static final String APPLICATION_PARAMETERS_KEY = "application.parameters"; + private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries"; + private static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id"; + private static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id"; + private static final String MAIN_LIBRARY_KEY = "main.library"; + private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes"; + private static final String NECESSITAS_API_LEVEL_KEY = "necessitas.api.level"; + private static final String EXTRACT_STYLE_KEY = "extract.android.style"; + + /// Ministro server parameter keys + private static final String REQUIRED_MODULES_KEY = "required.modules"; + private static final String APPLICATION_TITLE_KEY = "application.title"; + private static final String MINIMUM_MINISTRO_API_KEY = "minimum.ministro.api"; + private static final String MINIMUM_QT_VERSION_KEY = "minimum.qt.version"; + private static final String SOURCES_KEY = "sources"; // needs MINISTRO_API_LEVEL >=3 !!! + // Use this key to specify any 3rd party sources urls + // Ministro will download these repositories into their + // own folders, check http://community.kde.org/Necessitas/Ministro + // for more details. + + private static final String REPOSITORY_KEY = "repository"; // use this key to overwrite the default ministro repsitory + private static final String ANDROID_THEMES_KEY = "android.themes"; // themes that your application uses + + + public String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application, + // the parameters must not contain any white spaces + // and must be separated with "\t" + // e.g "-param1\t-param2=value2\t-param3\tvalue3" + + public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_STYLE=1\tQT_USE_ANDROID_NATIVE_DIALOGS=1\t"; + // use this variable to add any environment variables to your application. + // the env vars must be separated with "\t" + // e.g. "ENV_VAR1=1\tENV_VAR2=2\t" + // Currently the following vars are used by the android plugin: + // * QT_USE_ANDROID_NATIVE_STYLE - 1 to use the android widget style if available. + // * QT_USE_ANDROID_NATIVE_DIALOGS -1 to use the android native dialogs. + + public String[] QT_ANDROID_THEMES = null; // A list with all themes that your application want to use. + // The name of the theme must be the same with any theme from + // http://developer.android.com/reference/android/R.style.html + // The most used themes are: + // * "Theme" - (fallback) check http://developer.android.com/reference/android/R.style.html#Theme + // * "Theme_Black" - check http://developer.android.com/reference/android/R.style.html#Theme_Black + // * "Theme_Light" - (default for API <=10) check http://developer.android.com/reference/android/R.style.html#Theme_Light + // * "Theme_Holo" - check http://developer.android.com/reference/android/R.style.html#Theme_Holo + // * "Theme_Holo_Light" - (default for API 11-13) check http://developer.android.com/reference/android/R.style.html#Theme_Holo_Light + // * "Theme_DeviceDefault" - check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault + // * "Theme_DeviceDefault_Light" - (default for API 14+) check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault_Light + + public String QT_ANDROID_DEFAULT_THEME = null; // sets the default theme. + + private static final int INCOMPATIBLE_MINISTRO_VERSION = 1; // Incompatible Ministro version. Ministro needs to be upgraded. + private static final int BUFFER_SIZE = 1024; + + private ActivityInfo m_activityInfo = null; // activity info object, used to access the libs and the strings + private DexClassLoader m_classLoader = null; // loader object + private String[] m_sources = {"https://download.qt-project.org/ministro/android/qt5/qt-5.2"}; // Make sure you are using ONLY secure locations + private String m_repository = "default"; // Overwrites the default Ministro repository + // Possible values: + // * default - Ministro default repository set with "Ministro configuration tool". + // By default the stable version is used. Only this or stable repositories should + // be used in production. + // * stable - stable repository, only this and default repositories should be used + // in production. + // * testing - testing repository, DO NOT use this repository in production, + // this repository is used to push a new release, and should be used to test your application. + // * unstable - unstable repository, DO NOT use this repository in production, + // this repository is used to push Qt snapshots. + private String[] m_qtLibs = null; // required qt libs + private int m_displayDensity = -1; + + public QtActivity() + { + if (Build.VERSION.SDK_INT <= 10) { + QT_ANDROID_THEMES = new String[] {"Theme_Light"}; + QT_ANDROID_DEFAULT_THEME = "Theme_Light"; + } + else if ((Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 13) || Build.VERSION.SDK_INT >= 21){ + QT_ANDROID_THEMES = new String[] {"Theme_Holo_Light"}; + QT_ANDROID_DEFAULT_THEME = "Theme_Holo_Light"; + } else { + QT_ANDROID_THEMES = new String[] {"Theme_DeviceDefault_Light"}; + QT_ANDROID_DEFAULT_THEME = "Theme_DeviceDefault_Light"; + } + } + + // this function is used to load and start the loader + private void loadApplication(Bundle loaderParams) + { + try { + final int errorCode = loaderParams.getInt(ERROR_CODE_KEY); + if (errorCode != 0) { + if (errorCode == INCOMPATIBLE_MINISTRO_VERSION) { + downloadUpgradeMinistro(loaderParams.getString(ERROR_MESSAGE_KEY)); + return; + } + + // fatal error, show the error and quit + AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create(); + errorDialog.setMessage(loaderParams.getString(ERROR_MESSAGE_KEY)); + errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + errorDialog.show(); + return; + } + + // add all bundled Qt libs to loader params + ArrayList libs = new ArrayList(); + if ( m_activityInfo.metaData.containsKey("android.app.bundled_libs_resource_id") ) + libs.addAll(Arrays.asList(getResources().getStringArray(m_activityInfo.metaData.getInt("android.app.bundled_libs_resource_id")))); + + String libName = null; + if ( m_activityInfo.metaData.containsKey("android.app.lib_name") ) { + libName = m_activityInfo.metaData.getString("android.app.lib_name"); + loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function + } + + loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs); + loaderParams.putInt(NECESSITAS_API_LEVEL_KEY, NECESSITAS_API_LEVEL); + + // load and start QtLoader class + m_classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files + getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written. + loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists) + getClassLoader()); // parent loader + + @SuppressWarnings("rawtypes") + Class loaderClass = m_classLoader.loadClass(loaderParams.getString(LOADER_CLASS_NAME_KEY)); // load QtLoader class + Object qtLoader = loaderClass.newInstance(); // create an instance + Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication", + Activity.class, + ClassLoader.class, + Bundle.class); + if (!(Boolean)prepareAppMethod.invoke(qtLoader, this, m_classLoader, loaderParams)) + throw new Exception(""); + + QtApplication.setQtActivityDelegate(qtLoader); + + // now load the application library so it's accessible from this class loader + if (libName != null) + System.loadLibrary(libName); + + Method startAppMethod=qtLoader.getClass().getMethod("startApplication"); + if (!(Boolean)startAppMethod.invoke(qtLoader)) + throw new Exception(""); + + } catch (Exception e) { + e.printStackTrace(); + AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create(); + if (m_activityInfo.metaData.containsKey("android.app.fatal_error_msg")) + errorDialog.setMessage(m_activityInfo.metaData.getString("android.app.fatal_error_msg")); + else + errorDialog.setMessage("Fatal error, your application can't be started."); + + errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + errorDialog.show(); + } + } + + private ServiceConnection m_ministroConnection=new ServiceConnection() { + private IMinistro m_service = null; + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + m_service = IMinistro.Stub.asInterface(service); + try { + if (m_service != null) { + Bundle parameters = new Bundle(); + parameters.putStringArray(REQUIRED_MODULES_KEY, m_qtLibs); + parameters.putString(APPLICATION_TITLE_KEY, (String)QtActivity.this.getTitle()); + parameters.putInt(MINIMUM_MINISTRO_API_KEY, MINISTRO_API_LEVEL); + parameters.putInt(MINIMUM_QT_VERSION_KEY, QT_VERSION); + parameters.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES); + if (APPLICATION_PARAMETERS != null) + parameters.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS); + parameters.putStringArray(SOURCES_KEY, m_sources); + parameters.putString(REPOSITORY_KEY, m_repository); + if (QT_ANDROID_THEMES != null) + parameters.putStringArray(ANDROID_THEMES_KEY, QT_ANDROID_THEMES); + m_service.requestLoader(m_ministroCallback, parameters); + } + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + private IMinistroCallback m_ministroCallback = new IMinistroCallback.Stub() { + // this function is called back by Ministro. + @Override + public void loaderReady(final Bundle loaderParams) throws RemoteException { + runOnUiThread(new Runnable() { + @Override + public void run() { + unbindService(m_ministroConnection); + loadApplication(loaderParams); + } + }); + } + }; + + @Override + public void onServiceDisconnected(ComponentName name) { + m_service = null; + } + }; + + private void downloadUpgradeMinistro(String msg) + { + AlertDialog.Builder downloadDialog = new AlertDialog.Builder(this); + downloadDialog.setMessage(msg); + downloadDialog.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + try { + Uri uri = Uri.parse("market://search?q=pname:org.kde.necessitas.ministro"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivityForResult(intent, MINISTRO_INSTALL_REQUEST_CODE); + } catch (Exception e) { + e.printStackTrace(); + ministroNotFound(); + } + } + }); + + downloadDialog.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + QtActivity.this.finish(); + } + }); + downloadDialog.show(); + } + + private void ministroNotFound() + { + AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create(); + + if (m_activityInfo.metaData.containsKey("android.app.ministro_not_found_msg")) + errorDialog.setMessage(m_activityInfo.metaData.getString("android.app.ministro_not_found_msg")); + else + errorDialog.setMessage("Can't find Ministro service.\nThe application can't start."); + + errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + errorDialog.show(); + } + + static private void copyFile(InputStream inputStream, OutputStream outputStream) + throws IOException + { + byte[] buffer = new byte[BUFFER_SIZE]; + + int count; + while ((count = inputStream.read(buffer)) > 0) + outputStream.write(buffer, 0, count); + } + + + private void copyAsset(String source, String destination) + throws IOException + { + // Already exists, we don't have to do anything + File destinationFile = new File(destination); + if (destinationFile.exists()) + return; + + File parentDirectory = destinationFile.getParentFile(); + if (!parentDirectory.exists()) + parentDirectory.mkdirs(); + + destinationFile.createNewFile(); + + AssetManager assetsManager = getAssets(); + InputStream inputStream = assetsManager.open(source); + OutputStream outputStream = new FileOutputStream(destinationFile); + copyFile(inputStream, outputStream); + + inputStream.close(); + outputStream.close(); + } + + private static void createBundledBinary(String source, String destination) + throws IOException + { + // Already exists, we don't have to do anything + File destinationFile = new File(destination); + if (destinationFile.exists()) + return; + + File parentDirectory = destinationFile.getParentFile(); + if (!parentDirectory.exists()) + parentDirectory.mkdirs(); + + destinationFile.createNewFile(); + + InputStream inputStream = new FileInputStream(source); + OutputStream outputStream = new FileOutputStream(destinationFile); + copyFile(inputStream, outputStream); + + inputStream.close(); + outputStream.close(); + } + + private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) + { + File versionFile = new File(pluginsPrefix + "cache.version"); + + long cacheVersion = 0; + if (versionFile.exists() && versionFile.canRead()) { + try { + DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile)); + cacheVersion = inputStream.readLong(); + inputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (cacheVersion != packageVersion) { + deleteRecursively(new File(pluginsPrefix)); + return true; + } else { + return false; + } + } + + private void extractBundledPluginsAndImports(String pluginsPrefix) + throws IOException + { + ArrayList libs = new ArrayList(); + + String libsDir = getApplicationInfo().nativeLibraryDir + "/"; + + long packageVersion = -1; + try { + PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); + packageVersion = packageInfo.lastUpdateTime; + } catch (Exception e) { + e.printStackTrace(); + } + + if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) + return; + + { + File versionFile = new File(pluginsPrefix + "cache.version"); + + File parentDirectory = versionFile.getParentFile(); + if (!parentDirectory.exists()) + parentDirectory.mkdirs(); + + versionFile.createNewFile(); + + DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile)); + outputStream.writeLong(packageVersion); + outputStream.close(); + } + + { + String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY; + java.util.Set keys = m_activityInfo.metaData.keySet(); + if (m_activityInfo.metaData.containsKey(key)) { + String[] list = getResources().getStringArray(m_activityInfo.metaData.getInt(key)); + + for (String bundledImportBinary : list) { + String[] split = bundledImportBinary.split(":"); + String sourceFileName = libsDir + split[0]; + String destinationFileName = pluginsPrefix + split[1]; + createBundledBinary(sourceFileName, destinationFileName); + } + } + } + + { + String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY; + if (m_activityInfo.metaData.containsKey(key)) { + String[] list = getResources().getStringArray(m_activityInfo.metaData.getInt(key)); + + for (String fileName : list) { + String[] split = fileName.split(":"); + String sourceFileName = split[0]; + String destinationFileName = pluginsPrefix + split[1]; + copyAsset(sourceFileName, destinationFileName); + } + } + + } + } + + private void deleteRecursively(File directory) + { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) + deleteRecursively(file); + else + file.delete(); + } + + directory.delete(); + } + } + + private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix) + { + File newCache = new File(localPrefix); + if (!newCache.exists()) { + { + File oldPluginsCache = new File(oldLocalPrefix + "plugins/"); + if (oldPluginsCache.exists() && oldPluginsCache.isDirectory()) + deleteRecursively(oldPluginsCache); + } + + { + File oldImportsCache = new File(oldLocalPrefix + "imports/"); + if (oldImportsCache.exists() && oldImportsCache.isDirectory()) + deleteRecursively(oldImportsCache); + } + + { + File oldQmlCache = new File(oldLocalPrefix + "qml/"); + if (oldQmlCache.exists() && oldQmlCache.isDirectory()) + deleteRecursively(oldQmlCache); + } + } + } + + private void startApp(final boolean firstStart) + { + try { + if (m_activityInfo.metaData.containsKey("android.app.qt_sources_resource_id")) { + int resourceId = m_activityInfo.metaData.getInt("android.app.qt_sources_resource_id"); + m_sources = getResources().getStringArray(resourceId); + } + + if (m_activityInfo.metaData.containsKey("android.app.repository")) + m_repository = m_activityInfo.metaData.getString("android.app.repository"); + + if (m_activityInfo.metaData.containsKey("android.app.qt_libs_resource_id")) { + int resourceId = m_activityInfo.metaData.getInt("android.app.qt_libs_resource_id"); + m_qtLibs = getResources().getStringArray(resourceId); + } + + if (m_activityInfo.metaData.containsKey("android.app.use_local_qt_libs") + && m_activityInfo.metaData.getInt("android.app.use_local_qt_libs") == 1) { + ArrayList libraryList = new ArrayList(); + + + String localPrefix = "/data/local/tmp/qt/"; + if (m_activityInfo.metaData.containsKey("android.app.libs_prefix")) + localPrefix = m_activityInfo.metaData.getString("android.app.libs_prefix"); + + String pluginsPrefix = localPrefix; + + boolean bundlingQtLibs = false; + if (m_activityInfo.metaData.containsKey("android.app.bundle_local_qt_libs") + && m_activityInfo.metaData.getInt("android.app.bundle_local_qt_libs") == 1) { + localPrefix = getApplicationInfo().dataDir + "/"; + pluginsPrefix = localPrefix + "qt-reserved-files/"; + cleanOldCacheIfNecessary(localPrefix, pluginsPrefix); + extractBundledPluginsAndImports(pluginsPrefix); + bundlingQtLibs = true; + } + + if (m_qtLibs != null) { + for (int i=0;i 0) { + if (lib.startsWith("lib/")) + libraryList.add(localPrefix + lib); + else + libraryList.add(pluginsPrefix + lib); + } + } + } + + + String dexPaths = new String(); + String pathSeparator = System.getProperty("path.separator", ":"); + if (!bundlingQtLibs && m_activityInfo.metaData.containsKey("android.app.load_local_jars")) { + String[] jarFiles = m_activityInfo.metaData.getString("android.app.load_local_jars").split(":"); + for (String jar:jarFiles) { + if (jar.length() > 0) { + if (dexPaths.length() > 0) + dexPaths += pathSeparator; + dexPaths += localPrefix + jar; + } + } + } + + Bundle loaderParams = new Bundle(); + loaderParams.putInt(ERROR_CODE_KEY, 0); + loaderParams.putString(DEX_PATH_KEY, dexPaths); + loaderParams.putString(LOADER_CLASS_NAME_KEY, "org.qtproject.qt5.android.QtActivityDelegate"); + if (m_activityInfo.metaData.containsKey("android.app.static_init_classes")) { + loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY, + m_activityInfo.metaData.getString("android.app.static_init_classes").split(":")); + } + loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList); + + + String themePath = getApplicationInfo().dataDir + "/qt-reserved-files/android-style/"; + String stylePath = themePath + m_displayDensity + "/"; + if (!(new File(stylePath)).exists()) + loaderParams.putString(EXTRACT_STYLE_KEY, stylePath); + ENVIRONMENT_VARIABLES += "\tMINISTRO_ANDROID_STYLE_PATH=" + stylePath + + "\tQT_ANDROID_THEMES_ROOT_PATH=" + themePath; + + loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES + + "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml" + + "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports" + + "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins"); + + if (APPLICATION_PARAMETERS != null) { + loaderParams.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS); + } else { + Intent intent = getIntent(); + if (intent != null) { + String parameters = intent.getStringExtra("applicationArguments"); + if (parameters != null) + loaderParams.putString(APPLICATION_PARAMETERS_KEY, parameters.replace(' ', '\t')); + } + } + + loadApplication(loaderParams); + return; + } + + try { + if (!bindService(new Intent(org.kde.necessitas.ministro.IMinistro.class.getCanonicalName()), + m_ministroConnection, + Context.BIND_AUTO_CREATE)) { + throw new SecurityException(""); + } + } catch (Exception e) { + if (firstStart) { + String msg = "This application requires Ministro service. Would you like to install it?"; + if (m_activityInfo.metaData.containsKey("android.app.ministro_needed_msg")) + msg = m_activityInfo.metaData.getString("android.app.ministro_needed_msg"); + downloadUpgradeMinistro(msg); + } else { + ministroNotFound(); + } + } + } catch (Exception e) { + Log.e(QtApplication.QtTAG, "Can't create main activity", e); + } + } + + + + /////////////////////////// forward all notifications //////////////////////////// + /////////////////////////// Super class calls //////////////////////////////////// + /////////////// PLEASE DO NOT CHANGE THE FOLLOWING CODE ////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + + @Override + public boolean dispatchKeyEvent(KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyEvent, event); + else + return super.dispatchKeyEvent(event); + } + public boolean super_dispatchKeyEvent(KeyEvent event) + { + return super.dispatchKeyEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchPopulateAccessibilityEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchPopulateAccessibilityEvent, event); + else + return super.dispatchPopulateAccessibilityEvent(event); + } + public boolean super_dispatchPopulateAccessibilityEvent(AccessibilityEvent event) + { + return super_dispatchPopulateAccessibilityEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchTouchEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTouchEvent, ev); + else + return super.dispatchTouchEvent(ev); + } + public boolean super_dispatchTouchEvent(MotionEvent event) + { + return super.dispatchTouchEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchTrackballEvent(MotionEvent ev) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchTrackballEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTrackballEvent, ev); + else + return super.dispatchTrackballEvent(ev); + } + public boolean super_dispatchTrackballEvent(MotionEvent event) + { + return super.dispatchTrackballEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + + if (QtApplication.m_delegateObject != null && QtApplication.onActivityResult != null) { + QtApplication.invokeDelegateMethod(QtApplication.onActivityResult, requestCode, resultCode, data); + return; + } + if (requestCode == MINISTRO_INSTALL_REQUEST_CODE) + startApp(false); + super.onActivityResult(requestCode, resultCode, data); + } + public void super_onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + } + //--------------------------------------------------------------------------- + + @Override + protected void onApplyThemeResource(Theme theme, int resid, boolean first) + { + if (!QtApplication.invokeDelegate(theme, resid, first).invoked) + super.onApplyThemeResource(theme, resid, first); + } + public void super_onApplyThemeResource(Theme theme, int resid, boolean first) + { + super.onApplyThemeResource(theme, resid, first); + } + //--------------------------------------------------------------------------- + + + @Override + protected void onChildTitleChanged(Activity childActivity, CharSequence title) + { + if (!QtApplication.invokeDelegate(childActivity, title).invoked) + super.onChildTitleChanged(childActivity, title); + } + public void super_onChildTitleChanged(Activity childActivity, CharSequence title) + { + super.onChildTitleChanged(childActivity, title); + } + //--------------------------------------------------------------------------- + + @Override + public void onConfigurationChanged(Configuration newConfig) + { + if (!QtApplication.invokeDelegate(newConfig).invoked) + super.onConfigurationChanged(newConfig); + } + public void super_onConfigurationChanged(Configuration newConfig) + { + super.onConfigurationChanged(newConfig); + } + //--------------------------------------------------------------------------- + + @Override + public void onContentChanged() + { + if (!QtApplication.invokeDelegate().invoked) + super.onContentChanged(); + } + public void super_onContentChanged() + { + super.onContentChanged(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onContextItemSelected(MenuItem item) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(item); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onContextItemSelected(item); + } + public boolean super_onContextItemSelected(MenuItem item) + { + return super.onContextItemSelected(item); + } + //--------------------------------------------------------------------------- + + @Override + public void onContextMenuClosed(Menu menu) + { + if (!QtApplication.invokeDelegate(menu).invoked) + super.onContextMenuClosed(menu); + } + public void super_onContextMenuClosed(Menu menu) + { + super.onContextMenuClosed(menu); + } + //--------------------------------------------------------------------------- + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + try { + m_activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + for (Field f : Class.forName("android.R$style").getDeclaredFields()) { + if (f.getInt(null) == m_activityInfo.getThemeResource()) { + QT_ANDROID_THEMES = new String[] {f.getName()}; + QT_ANDROID_DEFAULT_THEME = f.getName(); + } + } + } catch (Exception e) { + e.printStackTrace(); + finish(); + return; + } + + try { + setTheme(Class.forName("android.R$style").getDeclaredField(QT_ANDROID_DEFAULT_THEME).getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + + if (Build.VERSION.SDK_INT > 10) { + try { + requestWindowFeature(Window.class.getField("FEATURE_ACTION_BAR").getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + requestWindowFeature(Window.FEATURE_NO_TITLE); + } + + if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) { + QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState); + return; + } + + m_displayDensity = getResources().getDisplayMetrics().densityDpi; + + ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME + + "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t"; + + if (null == getLastNonConfigurationInstance()) { + // if splash screen is defined, then show it + if (m_activityInfo.metaData.containsKey("android.app.splash_screen_drawable")) + getWindow().setBackgroundDrawableResource(m_activityInfo.metaData.getInt("android.app.splash_screen_drawable")); + else + getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000)); + + if (m_activityInfo.metaData.containsKey("android.app.background_running") + && m_activityInfo.metaData.getBoolean("android.app.background_running")) { + ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t"; + } else { + ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t"; + } + + if (m_activityInfo.metaData.containsKey("android.app.auto_screen_scale_factor") + && m_activityInfo.metaData.getBoolean("android.app.auto_screen_scale_factor")) { + ENVIRONMENT_VARIABLES += "QT_AUTO_SCREEN_SCALE_FACTOR=1\t"; + } + + startApp(true); + } + } + //--------------------------------------------------------------------------- + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) + { + if (!QtApplication.invokeDelegate(menu, v, menuInfo).invoked) + super.onCreateContextMenu(menu, v, menuInfo); + } + public void super_onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) + { + super.onCreateContextMenu(menu, v, menuInfo); + } + //--------------------------------------------------------------------------- + + @Override + public CharSequence onCreateDescription() + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) + return (CharSequence)res.methodReturns; + else + return super.onCreateDescription(); + } + public CharSequence super_onCreateDescription() + { + return super.onCreateDescription(); + } + //--------------------------------------------------------------------------- + + @Override + protected Dialog onCreateDialog(int id) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(id); + if (res.invoked) + return (Dialog)res.methodReturns; + else + return super.onCreateDialog(id); + } + public Dialog super_onCreateDialog(int id) + { + return super.onCreateDialog(id); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onCreateOptionsMenu(menu); + } + public boolean super_onCreateOptionsMenu(Menu menu) + { + return super.onCreateOptionsMenu(menu); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onCreatePanelMenu(featureId, menu); + } + public boolean super_onCreatePanelMenu(int featureId, Menu menu) + { + return super.onCreatePanelMenu(featureId, menu); + } + //--------------------------------------------------------------------------- + + + @Override + public View onCreatePanelView(int featureId) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId); + if (res.invoked) + return (View)res.methodReturns; + else + return super.onCreatePanelView(featureId); + } + public View super_onCreatePanelView(int featureId) + { + return super.onCreatePanelView(featureId); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(outBitmap, canvas); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onCreateThumbnail(outBitmap, canvas); + } + public boolean super_onCreateThumbnail(Bitmap outBitmap, Canvas canvas) + { + return super.onCreateThumbnail(outBitmap, canvas); + } + //--------------------------------------------------------------------------- + + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(name, context, attrs); + if (res.invoked) + return (View)res.methodReturns; + else + return super.onCreateView(name, context, attrs); + } + public View super_onCreateView(String name, Context context, AttributeSet attrs) + { + return super.onCreateView(name, context, attrs); + } + //--------------------------------------------------------------------------- + + @Override + protected void onDestroy() + { + super.onDestroy(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyDown, keyCode, event); + else + return super.onKeyDown(keyCode, event); + } + public boolean super_onKeyDown(int keyCode, KeyEvent event) + { + return super.onKeyDown(keyCode, event); + } + //--------------------------------------------------------------------------- + + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyMultiple != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyMultiple, keyCode, repeatCount, event); + else + return super.onKeyMultiple(keyCode, repeatCount, event); + } + public boolean super_onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) + { + return super.onKeyMultiple(keyCode, repeatCount, event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyUp, keyCode, event); + else + return super.onKeyUp(keyCode, event); + } + public boolean super_onKeyUp(int keyCode, KeyEvent event) + { + return super.onKeyUp(keyCode, event); + } + //--------------------------------------------------------------------------- + + @Override + public void onLowMemory() + { + if (!QtApplication.invokeDelegate().invoked) + super.onLowMemory(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, item); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onMenuItemSelected(featureId, item); + } + public boolean super_onMenuItemSelected(int featureId, MenuItem item) + { + return super.onMenuItemSelected(featureId, item); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onMenuOpened(int featureId, Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onMenuOpened(featureId, menu); + } + public boolean super_onMenuOpened(int featureId, Menu menu) + { + return super.onMenuOpened(featureId, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onNewIntent(Intent intent) + { + if (!QtApplication.invokeDelegate(intent).invoked) + super.onNewIntent(intent); + } + public void super_onNewIntent(Intent intent) + { + super.onNewIntent(intent); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(item); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onOptionsItemSelected(item); + } + public boolean super_onOptionsItemSelected(MenuItem item) + { + return super.onOptionsItemSelected(item); + } + //--------------------------------------------------------------------------- + + @Override + public void onOptionsMenuClosed(Menu menu) + { + if (!QtApplication.invokeDelegate(menu).invoked) + super.onOptionsMenuClosed(menu); + } + public void super_onOptionsMenuClosed(Menu menu) + { + super.onOptionsMenuClosed(menu); + } + //--------------------------------------------------------------------------- + + @Override + public void onPanelClosed(int featureId, Menu menu) + { + if (!QtApplication.invokeDelegate(featureId, menu).invoked) + super.onPanelClosed(featureId, menu); + } + public void super_onPanelClosed(int featureId, Menu menu) + { + super.onPanelClosed(featureId, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPause() + { + super.onPause(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPostCreate(Bundle savedInstanceState) + { + super.onPostCreate(savedInstanceState); + QtApplication.invokeDelegate(savedInstanceState); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPostResume() + { + super.onPostResume(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPrepareDialog(int id, Dialog dialog) + { + if (!QtApplication.invokeDelegate(id, dialog).invoked) + super.onPrepareDialog(id, dialog); + } + public void super_onPrepareDialog(int id, Dialog dialog) + { + super.onPrepareDialog(id, dialog); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onPrepareOptionsMenu(Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onPrepareOptionsMenu(menu); + } + public boolean super_onPrepareOptionsMenu(Menu menu) + { + return super.onPrepareOptionsMenu(menu); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, view, menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onPreparePanel(featureId, view, menu); + } + public boolean super_onPreparePanel(int featureId, View view, Menu menu) + { + return super.onPreparePanel(featureId, view, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onRestart() + { + super.onRestart(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) + { + if (!QtApplication.invokeDelegate(savedInstanceState).invoked) + super.onRestoreInstanceState(savedInstanceState); + } + public void super_onRestoreInstanceState(Bundle savedInstanceState) + { + super.onRestoreInstanceState(savedInstanceState); + } + //--------------------------------------------------------------------------- + + @Override + protected void onResume() + { + super.onResume(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + public Object onRetainNonConfigurationInstance() + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) + return res.methodReturns; + else + return super.onRetainNonConfigurationInstance(); + } + public Object super_onRetainNonConfigurationInstance() + { + return super.onRetainNonConfigurationInstance(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onSaveInstanceState(Bundle outState) + { + if (!QtApplication.invokeDelegate(outState).invoked) + super.onSaveInstanceState(outState); + } + public void super_onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + + } + //--------------------------------------------------------------------------- + + @Override + public boolean onSearchRequested() + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onSearchRequested(); + } + public boolean super_onSearchRequested() + { + return super.onSearchRequested(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onStart() + { + super.onStart(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onStop() + { + super.onStop(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onTitleChanged(CharSequence title, int color) + { + if (!QtApplication.invokeDelegate(title, color).invoked) + super.onTitleChanged(title, color); + } + public void super_onTitleChanged(CharSequence title, int color) + { + super.onTitleChanged(title, color); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onTouchEvent(MotionEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onTouchEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTouchEvent, event); + else + return super.onTouchEvent(event); + } + public boolean super_onTouchEvent(MotionEvent event) + { + return super.onTouchEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onTrackballEvent(MotionEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onTrackballEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTrackballEvent, event); + else + return super.onTrackballEvent(event); + } + public boolean super_onTrackballEvent(MotionEvent event) + { + return super.onTrackballEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public void onUserInteraction() + { + if (!QtApplication.invokeDelegate().invoked) + super.onUserInteraction(); + } + public void super_onUserInteraction() + { + super.onUserInteraction(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onUserLeaveHint() + { + if (!QtApplication.invokeDelegate().invoked) + super.onUserLeaveHint(); + } + public void super_onUserLeaveHint() + { + super.onUserLeaveHint(); + } + //--------------------------------------------------------------------------- + + @Override + public void onWindowAttributesChanged(LayoutParams params) + { + if (!QtApplication.invokeDelegate(params).invoked) + super.onWindowAttributesChanged(params); + } + public void super_onWindowAttributesChanged(LayoutParams params) + { + super.onWindowAttributesChanged(params); + } + //--------------------------------------------------------------------------- + + @Override + public void onWindowFocusChanged(boolean hasFocus) + { + if (!QtApplication.invokeDelegate(hasFocus).invoked) + super.onWindowFocusChanged(hasFocus); + } + public void super_onWindowFocusChanged(boolean hasFocus) + { + super.onWindowFocusChanged(hasFocus); + } + //--------------------------------------------------------------------------- + + //////////////// Activity API 5 ///////////// +//@ANDROID-5 + @Override + public void onAttachedToWindow() + { + if (!QtApplication.invokeDelegate().invoked) + super.onAttachedToWindow(); + } + public void super_onAttachedToWindow() + { + super.onAttachedToWindow(); + } + //--------------------------------------------------------------------------- + + @Override + public void onBackPressed() + { + if (!QtApplication.invokeDelegate().invoked) + super.onBackPressed(); + } + public void super_onBackPressed() + { + super.onBackPressed(); + } + //--------------------------------------------------------------------------- + + @Override + public void onDetachedFromWindow() + { + if (!QtApplication.invokeDelegate().invoked) + super.onDetachedFromWindow(); + } + public void super_onDetachedFromWindow() + { + super.onDetachedFromWindow(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyLongPress != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyLongPress, keyCode, event); + else + return super.onKeyLongPress(keyCode, event); + } + public boolean super_onKeyLongPress(int keyCode, KeyEvent event) + { + return super.onKeyLongPress(keyCode, event); + } + //--------------------------------------------------------------------------- +//@ANDROID-5 + +//////////////// Activity API 8 ///////////// +//@ANDROID-8 +@Override + protected Dialog onCreateDialog(int id, Bundle args) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(id, args); + if (res.invoked) + return (Dialog)res.methodReturns; + else + return super.onCreateDialog(id, args); + } + public Dialog super_onCreateDialog(int id, Bundle args) + { + return super.onCreateDialog(id, args); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPrepareDialog(int id, Dialog dialog, Bundle args) + { + if (!QtApplication.invokeDelegate(id, dialog, args).invoked) + super.onPrepareDialog(id, dialog, args); + } + public void super_onPrepareDialog(int id, Dialog dialog, Bundle args) + { + super.onPrepareDialog(id, dialog, args); + } + //--------------------------------------------------------------------------- +//@ANDROID-8 + //////////////// Activity API 11 ///////////// + +//@ANDROID-11 + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyShortcutEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyShortcutEvent, event); + else + return super.dispatchKeyShortcutEvent(event); + } + public boolean super_dispatchKeyShortcutEvent(KeyEvent event) + { + return super.dispatchKeyShortcutEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public void onActionModeFinished(ActionMode mode) + { + if (!QtApplication.invokeDelegate(mode).invoked) + super.onActionModeFinished(mode); + } + public void super_onActionModeFinished(ActionMode mode) + { + super.onActionModeFinished(mode); + } + //--------------------------------------------------------------------------- + + @Override + public void onActionModeStarted(ActionMode mode) + { + if (!QtApplication.invokeDelegate(mode).invoked) + super.onActionModeStarted(mode); + } + public void super_onActionModeStarted(ActionMode mode) + { + super.onActionModeStarted(mode); + } + //--------------------------------------------------------------------------- + + @Override + public void onAttachFragment(Fragment fragment) + { + if (!QtApplication.invokeDelegate(fragment).invoked) + super.onAttachFragment(fragment); + } + public void super_onAttachFragment(Fragment fragment) + { + super.onAttachFragment(fragment); + } + //--------------------------------------------------------------------------- + + @Override + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(parent, name, context, attrs); + if (res.invoked) + return (View)res.methodReturns; + else + return super.onCreateView(parent, name, context, attrs); + } + public View super_onCreateView(View parent, String name, Context context, + AttributeSet attrs) { + return super.onCreateView(parent, name, context, attrs); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyShortcut != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyShortcut, keyCode,event); + else + return super.onKeyShortcut(keyCode, event); + } + public boolean super_onKeyShortcut(int keyCode, KeyEvent event) + { + return super.onKeyShortcut(keyCode, event); + } + //--------------------------------------------------------------------------- + + @Override + public ActionMode onWindowStartingActionMode(Callback callback) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(callback); + if (res.invoked) + return (ActionMode)res.methodReturns; + else + return super.onWindowStartingActionMode(callback); + } + public ActionMode super_onWindowStartingActionMode(Callback callback) + { + return super.onWindowStartingActionMode(callback); + } + //--------------------------------------------------------------------------- +//@ANDROID-11 + //////////////// Activity API 12 ///////////// + +////@ANDROID-12 +// @Override +// public boolean dispatchGenericMotionEvent(MotionEvent ev) +// { +// if (QtApplication.m_delegateObject != null && QtApplication.dispatchGenericMotionEvent != null) +// return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchGenericMotionEvent, ev); +// else +// return super.dispatchGenericMotionEvent(ev); +// } +// public boolean super_dispatchGenericMotionEvent(MotionEvent event) +// { +// return super.dispatchGenericMotionEvent(event); +// } +// //--------------------------------------------------------------------------- +// +// @Override +// public boolean onGenericMotionEvent(MotionEvent event) +// { +// if (QtApplication.m_delegateObject != null && QtApplication.onGenericMotionEvent != null) +// return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onGenericMotionEvent, event); +// else +// return super.onGenericMotionEvent(event); +// } +// public boolean super_onGenericMotionEvent(MotionEvent event) +// { +// return super.onGenericMotionEvent(event); +// } +// //--------------------------------------------------------------------------- +////@ANDROID-12 + +} diff --git a/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java new file mode 100644 index 00000000..c78aeb7f --- /dev/null +++ b/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java @@ -0,0 +1,159 @@ +/* + Copyright (c) 2012-2013, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.qtproject.qt5.android.bindings; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; + +import android.app.Application; + +public class QtApplication extends Application +{ + public final static String QtTAG = "Qt"; + public static Object m_delegateObject = null; + public static HashMap> m_delegateMethods= new HashMap>(); + public static Method dispatchKeyEvent = null; + public static Method dispatchPopulateAccessibilityEvent = null; + public static Method dispatchTouchEvent = null; + public static Method dispatchTrackballEvent = null; + public static Method onKeyDown = null; + public static Method onKeyMultiple = null; + public static Method onKeyUp = null; + public static Method onTouchEvent = null; + public static Method onTrackballEvent = null; + public static Method onActivityResult = null; + public static Method onCreate = null; + public static Method onKeyLongPress = null; + public static Method dispatchKeyShortcutEvent = null; + public static Method onKeyShortcut = null; + public static Method dispatchGenericMotionEvent = null; + public static Method onGenericMotionEvent = null; + + public static void setQtActivityDelegate(Object listener) + { + QtApplication.m_delegateObject = listener; + + ArrayList delegateMethods = new ArrayList(); + for (Method m : listener.getClass().getMethods()) { + if (m.getDeclaringClass().getName().startsWith("org.qtproject.qt5.android")) + delegateMethods.add(m); + } + + ArrayList applicationFields = new ArrayList(); + for (Field f : QtApplication.class.getFields()) { + if (f.getDeclaringClass().getName().equals(QtApplication.class.getName())) + applicationFields.add(f); + } + + for (Method delegateMethod : delegateMethods) { + try { + QtActivity.class.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes()); + if (QtApplication.m_delegateMethods.containsKey(delegateMethod.getName())) { + QtApplication.m_delegateMethods.get(delegateMethod.getName()).add(delegateMethod); + } else { + ArrayList delegateSet = new ArrayList(); + delegateSet.add(delegateMethod); + QtApplication.m_delegateMethods.put(delegateMethod.getName(), delegateSet); + } + for (Field applicationField:applicationFields) { + if (applicationField.getName().equals(delegateMethod.getName())) { + try { + applicationField.set(null, delegateMethod); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } catch (Exception e) { + } + } + } + + @Override + public void onTerminate() { + if (m_delegateObject != null && m_delegateMethods.containsKey("onTerminate")) + invokeDelegateMethod(m_delegateMethods.get("onTerminate").get(0)); + super.onTerminate(); + } + + public static class InvokeResult + { + public boolean invoked = false; + public Object methodReturns = null; + } + + private static int stackDeep=-1; + public static InvokeResult invokeDelegate(Object... args) + { + InvokeResult result = new InvokeResult(); + if (m_delegateObject == null) + return result; + StackTraceElement[] elements = Thread.currentThread().getStackTrace(); + if (-1 == stackDeep) { + String activityClassName = QtActivity.class.getCanonicalName(); + for (int it=0;it -#include -#include "mainwindow.h" -#include "DaemonQT.h" - -int runGUI( int argc, char* argv[] ) { - QApplication app(argc, argv); - bool daemonInitSuccess = i2p::util::DaemonQTImpl::init(argc, argv); - if(!daemonInitSuccess) { - QMessageBox::critical(0, "Error", "Daemon init failed"); - return 1; - } - MainWindow w; - w.show (); - i2p::util::DaemonQTImpl::start(); - int result = app.exec(); - //QMessageBox::information(&w, "Debug", "exec finished"); - i2p::util::DaemonQTImpl::stop(); - //QMessageBox::information(&w, "Debug", "demon stopped"); - return result; -} -#endif diff --git a/qt/i2pd_qt/i2pd_qt_gui.h b/qt/i2pd_qt/i2pd_qt_gui.h deleted file mode 100644 index 73f01e7e..00000000 --- a/qt/i2pd_qt/i2pd_qt_gui.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef IQPD_QT_GUI_H -#define IQPD_QT_GUI_H - -int runGUI( int argc, char* argv[] ); - -#endif // IQPD_QT_GUI_H From 9fe4f3adeae093cd88bd20337ac046d18c4c96d4 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 19 Jun 2016 21:05:48 -0400 Subject: [PATCH 1433/6300] teminate NTCP session on close completely --- Transports.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Transports.cpp b/Transports.cpp index b6f27d7d..b15ec56f 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -460,7 +460,7 @@ namespace transport auto ntcpSession = m_NTCPServer ? m_NTCPServer->FindNTCPSession(router->GetIdentHash()) : nullptr; if (ntcpSession) // try deleting ntcp session too { - m_NTCPServer->RemoveNTCPSession(ntcpSession); + ntcpSession->Terminate (); LogPrint(eLogDebug, "Transports: NTCP session closed"); } } From 9b7f583b2bd237e4fd62faa8da6b58c12c84d15f Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 20 Jun 2016 11:31:27 -0400 Subject: [PATCH 1434/6300] icon and external storage permissions --- qt/i2pd_qt/android/AndroidManifest.xml | 5 +++-- qt/i2pd_qt/android/res/drawable-hdpi/icon.png | Bin 0 -> 8712 bytes 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 qt/i2pd_qt/android/res/drawable-hdpi/icon.png diff --git a/qt/i2pd_qt/android/AndroidManifest.xml b/qt/i2pd_qt/android/AndroidManifest.xml index 69f6fde3..12bac41c 100644 --- a/qt/i2pd_qt/android/AndroidManifest.xml +++ b/qt/i2pd_qt/android/AndroidManifest.xml @@ -1,9 +1,9 @@ - + - + @@ -60,4 +60,5 @@ Remove the comment if you do not require these default features. --> + diff --git a/qt/i2pd_qt/android/res/drawable-hdpi/icon.png b/qt/i2pd_qt/android/res/drawable-hdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a5dc7b680ba9dee30161ffb1c9fcd17bbd6ddd84 GIT binary patch literal 8712 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 literal 0 HcmV?d00001 From 8ffddf06e4237f2f495aa503d72a9e2a5030b570 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 20 Jun 2016 12:15:15 -0400 Subject: [PATCH 1435/6300] fixed memory leak --- Crypto.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Crypto.cpp b/Crypto.cpp index 90b74179..885c65f4 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -298,11 +298,13 @@ namespace crypto BN_rand (k, ELGAMAL_SHORT_EXPONENT_NUM_BITS, -1, 1); // short exponent of 226 bits #endif // calculate a - a = BN_new (); if (g_ElggTable) a = ElggPow (k, g_ElggTable, ctx); else + { + a = BN_new (); BN_mod_exp (a, elgg, k, elgp, ctx); + } BIGNUM * y = BN_new (); BN_bin2bn (key, 256, y); From be3fa6091d6f31b2ea6d9635e932caff8d15687c Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 20 Jun 2016 15:28:25 -0400 Subject: [PATCH 1436/6300] app appears as 'i2pd' --- qt/i2pd_qt/android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/i2pd_qt/android/AndroidManifest.xml b/qt/i2pd_qt/android/AndroidManifest.xml index 12bac41c..471b312c 100644 --- a/qt/i2pd_qt/android/AndroidManifest.xml +++ b/qt/i2pd_qt/android/AndroidManifest.xml @@ -5,7 +5,7 @@ - + From 13f33a9d198ab03b098fdaba7d8b691fc4d44194 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 20 Jun 2016 21:39:47 -0400 Subject: [PATCH 1437/6300] 2.8.0 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 5d07c596..46d170db 100644 --- a/version.h +++ b/version.h @@ -7,7 +7,7 @@ #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 7 +#define I2PD_VERSION_MINOR 8 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) From b07bff61f029e30eb9387522fab8b583b933a8c3 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 21 Jun 2016 09:09:31 -0400 Subject: [PATCH 1438/6300] set target SDK to 23 --- qt/i2pd_qt/android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/i2pd_qt/android/AndroidManifest.xml b/qt/i2pd_qt/android/AndroidManifest.xml index 471b312c..6ab763ff 100644 --- a/qt/i2pd_qt/android/AndroidManifest.xml +++ b/qt/i2pd_qt/android/AndroidManifest.xml @@ -1,6 +1,6 @@ - + From ed3e83df67d9d179d280934b995ac18372f420c3 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 21 Jun 2016 12:34:20 -0400 Subject: [PATCH 1439/6300] android is supported now --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f4b2183..f5ce9624 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ i2pd from source on your OS. * Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) * Mac OS X * FreeBSD -* Android *(coming soon)* +* Android Using i2pd ---------- @@ -43,6 +43,7 @@ Donations --------- BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY +DASH: Xw8YUrQpYzP9tZBmbjqxS3M97Q7v3vJKUF LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z DOGE: DNXLQKziRPAsD9H3DFNjk4fLQrdaSX893Y From b962a4c69bcb0a34bb720d241871f19480e98b20 Mon Sep 17 00:00:00 2001 From: MXPLRS|Kirill Date: Wed, 22 Jun 2016 11:46:37 +0300 Subject: [PATCH 1440/6300] updated config files --- debian/tunnels.conf | 26 ++++++++++++++++++++++++++ docs/i2pd.conf | 35 ++++++++++++++++++++++++++++------- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/debian/tunnels.conf b/debian/tunnels.conf index f710b70c..fa92a56b 100644 --- a/debian/tunnels.conf +++ b/debian/tunnels.conf @@ -1,7 +1,33 @@ [IRC] type = client +address = 127.0.0.1 port = 6668 destination = irc.postman.i2p +destinationport = 6667 keys = irc-keys.dat +#[SMTP] +#type = client +#address = 127.0.0.1 +#port = 7659 +#destination = smtp.postman.i2p +#destinationport = 25 +#keys = smtp-keys.dat + +#[POP3] +#type = client +#address = 127.0.0.1 +#port = 7660 +#destination = pop.postman.i2p +#destinationport = 110 +#keys = pop3-keys.dat + +#[MTN] +#type = client +#address = 127.0.0.1 +#port = 8998 +#destination = mtn.i2p-projekt.i2p +#destinationport = 4691 +#keys = mtn-keys.dat + # see more examples in /usr/share/doc/i2pd/configuration.md.gz diff --git a/docs/i2pd.conf b/docs/i2pd.conf index d4ea226a..aa112a4b 100644 --- a/docs/i2pd.conf +++ b/docs/i2pd.conf @@ -22,7 +22,7 @@ ## * syslog - use syslog, see man 3 syslog # log = file ## Path to logfile (default - autodetect) -# logfile = /var/log/i2pd.log +# logfile = /var/log/i2pd/i2pd.log ## Log messages above this level (debug, *info, warn, error) # loglevel = info @@ -42,14 +42,18 @@ ## Port to listen for connections ## By default i2pd picks random port. You MUST pick a random number too, ## don't just uncomment this -# port = 4321 +# port = 4567 + +## Enable communication through ipv4 +ipv4 = true ## Enable communication through ipv6 -ipv6 = true +ipv6 = false ## Bandwidth configuration -## L limit bandwidth to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited -## Default is P for floodfill, L for regular node +## L limit bandwidth to 32Kbs/sec, O - to 256Kbs/sec, P - to 2048Kbs/sec, +## X - unlimited +## Default is X for floodfill, L for regular node # bandwidth = L ## Router will not accept transit tunnels at startup @@ -58,6 +62,15 @@ ipv6 = true ## Router will be floodfill # floodfill = true +[limits] +## Maximum active transit sessions (default:2500) +# transittunnels = 2500 + +[precomputation] +## Enable or disable elgamal precomputation table +## By default, enabled on i386 hosts +# elgamal = true + [http] ## Uncomment and set to 'false' to disable Web Console # enabled = true @@ -78,10 +91,11 @@ port = 4444 ## Uncomment and set to 'false' to disable SOCKS Proxy # enabled = true ## Address and port service will listen on -# address = 127.0.0.1 -# port = 4447 +address = 127.0.0.1 +port = 4447 ## Optional keys file for proxy local destination # keys = socks-proxy-keys.dat + ## Socks outproxy. Example below is set to use Tor for all connections except i2p ## Address and port of outproxy # outproxy = 127.0.0.1 @@ -101,6 +115,13 @@ port = 4444 # address = 127.0.0.1 # port = 2827 +[i2cp] +## Uncomment and set to 'true' to enable I2CP protocol +# enabled = false +## Address and port service will listen on +# address = 127.0.0.1 +# port = 7654 + [i2pcontrol] ## Uncomment and set to 'true' to enable I2PControl protocol # enabled = false From 02e0b8cc3248daaced087be70d789d1955b6833b Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Wed, 22 Jun 2016 11:49:22 +0300 Subject: [PATCH 1441/6300] Update i2pd.conf --- docs/i2pd.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/i2pd.conf b/docs/i2pd.conf index aa112a4b..16314274 100644 --- a/docs/i2pd.conf +++ b/docs/i2pd.conf @@ -22,7 +22,7 @@ ## * syslog - use syslog, see man 3 syslog # log = file ## Path to logfile (default - autodetect) -# logfile = /var/log/i2pd/i2pd.log +# logfile = /var/log/i2pd.log ## Log messages above this level (debug, *info, warn, error) # loglevel = info From 7ea5af448efc18f5aef984bfda3bfe753e6686ff Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 22 Jun 2016 09:41:01 -0400 Subject: [PATCH 1442/6300] UPNP support from windows --- Makefile.mingw | 5 +++++ UPnP.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile.mingw b/Makefile.mingw index 0390d66a..b859ebb3 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -32,6 +32,11 @@ ifeq ($(USE_WIN32_APP), yes) DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif +# UPNP Support +ifeq ($(USE_UPNP),1) + CXXFLAGS += -DUSE_UPNP +endif + ifeq ($(USE_AESNI),1) CPU_FLAGS = -maes -DAESNI else diff --git a/UPnP.cpp b/UPnP.cpp index fea2ff9e..ea62998b 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -69,7 +69,7 @@ namespace transport #ifdef MAC_OSX m_Module = dlopen ("libminiupnpc.dylib", RTLD_LAZY); #elif _WIN32 - m_Module = LoadLibrary ("miniupnpc.dll"); // official prebuilt binary, e.g., in upnpc-exe-win32-20140422.zip + m_Module = LoadLibrary ("libminiupnpc.dll"); // from MSYS2 #else m_Module = dlopen ("libminiupnpc.so", RTLD_LAZY); #endif From ca55a9a8a68d9d69623b85a8f41c440d58dbe8ca Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 22 Jun 2016 10:53:29 -0400 Subject: [PATCH 1443/6300] build with UPnP --- docs/build_notes_windows.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index 921a6110..47ace84b 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -164,6 +164,11 @@ folder name included in downloaded archive. Note that you might need to build DLL yourself for 64-bit systems using msys2 as 64-bit DLLs are not provided by the project. +You can also install it through the MSYS2 +pacman -S mingw-w64-i686-miniupnpc +and build with USE_UPNP key +make USE_UPNP=1 +It requires libminiupnpc.dll from /mingw32/bin ### Creating Visual Studio project From c3dbbc9144af66ed09e451412cb2d5c4579626a9 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Wed, 22 Jun 2016 18:04:11 +0300 Subject: [PATCH 1444/6300] Update build_notes_windows.md --- docs/build_notes_windows.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index 47ace84b..72da830f 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -164,10 +164,14 @@ folder name included in downloaded archive. Note that you might need to build DLL yourself for 64-bit systems using msys2 as 64-bit DLLs are not provided by the project. -You can also install it through the MSYS2 +You can also install it through the MSYS2 + +```bash pacman -S mingw-w64-i686-miniupnpc and build with USE_UPNP key make USE_UPNP=1 +``` + It requires libminiupnpc.dll from /mingw32/bin ### Creating Visual Studio project From 2b1e40c6c6f3cb137efc1d63c5db91448efa35aa Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 22 Jun 2016 11:15:40 -0400 Subject: [PATCH 1445/6300] Update build_notes_windows.md --- docs/build_notes_windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index 72da830f..6290ccc2 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -165,10 +165,10 @@ Note that you might need to build DLL yourself for 64-bit systems using msys2 as 64-bit DLLs are not provided by the project. You can also install it through the MSYS2 +and build with USE_UPNP key. ```bash pacman -S mingw-w64-i686-miniupnpc -and build with USE_UPNP key make USE_UPNP=1 ``` From 49d28789381d541f2360273ac8fe474aa177a0fe Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 22 Jun 2016 15:48:36 -0400 Subject: [PATCH 1446/6300] use local sockets for android --- I2CP.cpp | 25 ++++++++++++++++++++++--- I2CP.h | 23 +++++++++++++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index f506a312..6e53451c 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -114,7 +114,12 @@ namespace client } } - I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): + I2CPSession::I2CPSession (I2CPServer& owner, +#ifdef ANDROID + std::shared_ptr socket): +#else + std::shared_ptr socket): +#endif m_Owner (owner), m_Socket (socket), m_Payload (nullptr), m_SessionID (0xFFFF), m_MessageID (0), m_IsSendAccepted (true) { @@ -583,7 +588,12 @@ namespace client I2CPServer::I2CPServer (const std::string& interface, int port): m_IsRunning (false), m_Thread (nullptr), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(interface), port)) + m_Acceptor (m_Service, +#ifdef ANDROID + boost::asio::local::stream_protocol::endpoint(std::string (1, '\0') + interface)) // leading 0 for abstract address +#else + boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(interface), port)) +#endif { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; @@ -644,12 +654,21 @@ namespace client void I2CPServer::Accept () { +#ifdef ANDROID + auto newSocket = std::make_shared (m_Service); +#else auto newSocket = std::make_shared (m_Service); +#endif m_Acceptor.async_accept (*newSocket, std::bind (&I2CPServer::HandleAccept, this, std::placeholders::_1, newSocket)); } - void I2CPServer::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) + void I2CPServer::HandleAccept(const boost::system::error_code& ecode, +#ifdef ANDROID + std::shared_ptr socket) +#else + std::shared_ptr socket) +#endif { if (!ecode && socket) { diff --git a/I2CP.h b/I2CP.h index 6fc0e846..20f20d63 100644 --- a/I2CP.h +++ b/I2CP.h @@ -99,7 +99,12 @@ namespace client { public: - I2CPSession (I2CPServer& owner, std::shared_ptr socket); + I2CPSession (I2CPServer& owner, +#ifdef ANDROID + std::shared_ptr socket); +#else + std::shared_ptr socket); +#endif ~I2CPSession (); void Start (); @@ -144,7 +149,11 @@ namespace client private: I2CPServer& m_Owner; +#ifdef ANDROID + std::shared_ptr m_Socket; +#else std::shared_ptr m_Socket; +#endif uint8_t m_Header[I2CP_HEADER_SIZE], * m_Payload; size_t m_PayloadLen; @@ -173,7 +182,13 @@ namespace client void Run (); void Accept (); - void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); + + void HandleAccept(const boost::system::error_code& ecode, +#ifdef ANDROID + std::shared_ptr socket); +#else + std::shared_ptr socket); +#endif private: @@ -183,7 +198,11 @@ namespace client bool m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; +#ifdef ANDROID + boost::asio::local::stream_protocol::acceptor m_Acceptor; +#else boost::asio::ip::tcp::acceptor m_Acceptor; +#endif public: From 118a7719801e0db2c3ebb62a90013b61953e41c8 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 23 Jun 2016 00:00:00 +0000 Subject: [PATCH 1447/6300] * update changelog --- ChangeLog | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 9a32e42f..520978df 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,14 +1,23 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog -## [2.8.0] - UNRELEASED +## [2.9.0] - UNRELEASED ### Changed - Proxy refactoring & speedup + +## [2.8.0] - 2016-06-20 +### Added +- Basic Android support +- I2CP implementation +- 'doxygen' target + +### Changed - I2PControl refactoring & fixes (proper jsonrpc responses on errors) - boost::regex no more needed ### Fixed - initscripts: added openrc one, in sysv-ish make I2PD_PORT optional +- properly close NTCP sessions (memleak) ## [2.7.0] - 2016-05-18 ### Added From 02857cf2b5de99b7ab244951000bae010a5297a6 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 23 Jun 2016 00:00:00 +0000 Subject: [PATCH 1448/6300] * Base.cpp : drop logger dependency --- Base.cpp | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/Base.cpp b/Base.cpp index e0b6af07..766eaab9 100644 --- a/Base.cpp +++ b/Base.cpp @@ -1,5 +1,4 @@ #include -#include "Log.h" #include "Base.h" namespace i2p @@ -305,13 +304,10 @@ namespace data m_Inflator.next_out = out; m_Inflator.avail_out = outLen; int err; - if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END) + if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END) { return outLen - m_Inflator.avail_out; - else - { - LogPrint (eLogError, "Decompression error ", err); - return 0; - } + } + return 0; } bool GzipInflator::Inflate (const uint8_t * in, size_t inLen, std::ostream& s) @@ -328,13 +324,11 @@ namespace data ret = inflate (&m_Inflator, Z_NO_FLUSH); if (ret < 0) { - LogPrint (eLogError, "Decompression error ", ret); inflateEnd (&m_Inflator); s.setstate(std::ios_base::failbit); break; } - else - s.write ((char *)out, GZIP_CHUNK_SIZE - m_Inflator.avail_out); + s.write ((char *)out, GZIP_CHUNK_SIZE - m_Inflator.avail_out); } while (!m_Inflator.avail_out); // more data to read delete[] out; @@ -377,13 +371,10 @@ namespace data m_Deflator.next_out = out; m_Deflator.avail_out = outLen; int err; - if ((err = deflate (&m_Deflator, Z_FINISH)) == Z_STREAM_END) + if ((err = deflate (&m_Deflator, Z_FINISH)) == Z_STREAM_END) { return outLen - m_Deflator.avail_out; - else - { - LogPrint (eLogError, "Compression error ", err); - return 0; - } + } /* else */ + return 0; } } } From 225ed5b6628b7b6f3e353e2c5b3005eeed278c1d Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 23 Jun 2016 00:00:00 +0000 Subject: [PATCH 1449/6300] * HTTPProxy.{cpp,h} : move & sort headers --- HTTPProxy.cpp | 8 +++++++- HTTPProxy.h | 7 ------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 48fa0ae6..4d037ebe 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -3,6 +3,13 @@ #include #include #include +#include +#include +#include +#include + +#include "I2PService.h" +#include "Destination.h" #include "HTTPProxy.h" #include "util.h" #include "Identity.h" @@ -344,6 +351,5 @@ namespace proxy { return std::make_shared (this, socket); } - } } diff --git a/HTTPProxy.h b/HTTPProxy.h index b5ed77b9..0356adb5 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -1,13 +1,6 @@ #ifndef HTTP_PROXY_H__ #define HTTP_PROXY_H__ -#include -#include -#include -#include -#include "I2PService.h" -#include "Destination.h" - namespace i2p { namespace proxy From dde53ea4ba0bf66d81445bb38f90647ab691ffa0 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 23 Jun 2016 00:00:00 +0000 Subject: [PATCH 1450/6300] * HTTPProxy.cpp : HTTPRequestFailed() now responds with error message --- HTTPProxy.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 4d037ebe..8bc617a7 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -43,7 +43,7 @@ namespace proxy void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); - void HTTPRequestFailed(/*std::string message*/); + void HTTPRequestFailed(const char *message); void RedirectToJumpService(); void ExtractRequest(); bool IsI2PAddress(); @@ -98,10 +98,17 @@ namespace proxy /* All hope is lost beyond this point */ //TODO: handle this apropriately - void HTTPProxyHandler::HTTPRequestFailed(/*HTTPProxyHandler::errTypes error*/) + void HTTPProxyHandler::HTTPRequestFailed(const char *message) { - static std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n\r\n"; - boost::asio::async_write(*m_sock, boost::asio::buffer(response,response.size()), + std::size_t size = std::strlen(message); + std::stringstream ss; + ss << "HTTP/1.0 500 Internal Server Error\r\n" + << "Content-Type: text/plain\r\n"; + ss << "Content-Length: " << std::to_string(size + 2) << "\r\n" + << "\r\n"; /* end of headers */ + ss << message << "\r\n"; + std::string response = ss.str(); + boost::asio::async_write(*m_sock, boost::asio::buffer(response), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } @@ -139,7 +146,7 @@ namespace proxy if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) { LogPrint(eLogError, "HTTPProxy: unsupported version: ", m_version); - HTTPRequestFailed(); //TODO: send right stuff + HTTPRequestFailed("unsupported HTTP version"); return false; } return true; @@ -276,13 +283,13 @@ namespace proxy case '\n': EnterState(DONE); break; default: LogPrint(eLogError, "HTTPProxy: rejected invalid request ending with: ", ((int)*http_buff)); - HTTPRequestFailed(); //TODO: add correct code + HTTPRequestFailed("rejected invalid request"); return false; } break; default: LogPrint(eLogError, "HTTPProxy: invalid state: ", m_state); - HTTPRequestFailed(); //TODO: add correct code 500 + HTTPRequestFailed("invalid parser state"); return false; } http_buff++; @@ -338,7 +345,7 @@ namespace proxy else { LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); - HTTPRequestFailed(); // TODO: Send correct error message host unreachable + HTTPRequestFailed("error when creating the stream, check logs"); } } From d8906f508c22fa6bf66cf3ff60e449bc862e2081 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 23 Jun 2016 00:00:00 +0000 Subject: [PATCH 1451/6300] * HTTPProxy.cpp : HTTP error message cleanup --- HTTPProxy.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 8bc617a7..8a430594 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -114,12 +114,16 @@ namespace proxy void HTTPProxyHandler::RedirectToJumpService(/*HTTPProxyHandler::errTypes error*/) { - std::stringstream response; + std::stringstream ss; std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); - response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n\r\n"; - boost::asio::async_write(*m_sock, boost::asio::buffer(response.str (),response.str ().length ()), + ss << "HTTP/1.1 302 Found\r\n" + << "Connection: close\r\n" + << "Location: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n" + << "\r\n"; + std::string response = ss.str(); + boost::asio::async_write(*m_sock, boost::asio::buffer(response), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } From 340686ba067340831b3159814f9747550cd7a8fa Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 23 Jun 2016 00:00:00 +0000 Subject: [PATCH 1452/6300] * HTTPProxy.{cpp,h} : rename classes, drop typedef --- HTTPProxy.cpp | 58 +++++++++++++++++++++++++-------------------------- HTTPProxy.h | 18 +++++++--------- 2 files changed, 35 insertions(+), 41 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 8a430594..81ed8da6 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -21,12 +21,10 @@ #include "Config.h" #include "HTTP.h" -namespace i2p -{ -namespace proxy -{ +namespace i2p { +namespace proxy { static const size_t http_buffer_size = 8192; - class HTTPProxyHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this + class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: enum state @@ -66,26 +64,26 @@ namespace proxy public: - HTTPProxyHandler(HTTPProxyServer * parent, std::shared_ptr sock) : + HTTPReqHandler(HTTPProxy * parent, std::shared_ptr sock) : I2PServiceHandler(parent), m_sock(sock) { EnterState(GET_METHOD); } - ~HTTPProxyHandler() { Terminate(); } + ~HTTPReqHandler() { Terminate(); } void Handle () { AsyncSockRead(); } }; - void HTTPProxyHandler::AsyncSockRead() + void HTTPReqHandler::AsyncSockRead() { LogPrint(eLogDebug, "HTTPProxy: async sock read"); if(m_sock) { m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), - std::bind(&HTTPProxyHandler::HandleSockRecv, shared_from_this(), + std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { LogPrint(eLogError, "HTTPProxy: no socket for read"); } } - void HTTPProxyHandler::Terminate() { + void HTTPReqHandler::Terminate() { if (Kill()) return; if (m_sock) { @@ -98,7 +96,7 @@ namespace proxy /* All hope is lost beyond this point */ //TODO: handle this apropriately - void HTTPProxyHandler::HTTPRequestFailed(const char *message) + void HTTPReqHandler::HTTPRequestFailed(const char *message) { std::size_t size = std::strlen(message); std::stringstream ss; @@ -109,10 +107,10 @@ namespace proxy ss << message << "\r\n"; std::string response = ss.str(); boost::asio::async_write(*m_sock, boost::asio::buffer(response), - std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPProxyHandler::RedirectToJumpService(/*HTTPProxyHandler::errTypes error*/) + void HTTPReqHandler::RedirectToJumpService(/*HTTPReqHandler::errTypes error*/) { std::stringstream ss; std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); @@ -124,15 +122,15 @@ namespace proxy << "\r\n"; std::string response = ss.str(); boost::asio::async_write(*m_sock, boost::asio::buffer(response), - std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPProxyHandler::EnterState(HTTPProxyHandler::state nstate) + void HTTPReqHandler::EnterState(HTTPReqHandler::state nstate) { m_state = nstate; } - void HTTPProxyHandler::ExtractRequest() + void HTTPReqHandler::ExtractRequest() { LogPrint(eLogDebug, "HTTPProxy: request: ", m_method, " ", m_url); i2p::http::URL url; @@ -145,7 +143,7 @@ namespace proxy LogPrint(eLogDebug, "HTTPProxy: server: ", m_address, ", port: ", m_port, ", path: ", m_path); } - bool HTTPProxyHandler::ValidateHTTPRequest() + bool HTTPReqHandler::ValidateHTTPRequest() { if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) { @@ -156,7 +154,7 @@ namespace proxy return true; } - void HTTPProxyHandler::HandleJumpServices() + void HTTPReqHandler::HandleJumpServices() { static const char * helpermark1 = "?i2paddresshelper="; static const char * helpermark2 = "&i2paddresshelper="; @@ -188,7 +186,7 @@ namespace proxy m_path.erase(addressHelperPos); } - bool HTTPProxyHandler::IsI2PAddress() + bool HTTPReqHandler::IsI2PAddress() { auto pos = m_address.rfind (".i2p"); if (pos != std::string::npos && (pos+4) == m_address.length ()) @@ -198,7 +196,7 @@ namespace proxy return false; } - bool HTTPProxyHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) + bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { ExtractRequest(); //TODO: parse earlier if (!ValidateHTTPRequest()) return false; @@ -253,7 +251,7 @@ namespace proxy return true; } - bool HTTPProxyHandler::HandleData(uint8_t *http_buff, std::size_t len) + bool HTTPReqHandler::HandleData(uint8_t *http_buff, std::size_t len) { while (len > 0) { @@ -304,7 +302,7 @@ namespace proxy return true; } - void HTTPProxyHandler::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"); if(ecode) @@ -319,7 +317,7 @@ namespace proxy if (m_state == DONE) { LogPrint(eLogDebug, "HTTPProxy: requested: ", m_url); - GetOwner()->CreateStream (std::bind (&HTTPProxyHandler::HandleStreamRequestComplete, + GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_address, m_port); } else @@ -328,14 +326,14 @@ namespace proxy } - void HTTPProxyHandler::SentHTTPFailed(const boost::system::error_code & ecode) + void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) { if (ecode) LogPrint (eLogError, "HTTPProxy: Closing socket after sending failure because: ", ecode.message ()); Terminate(); } - void HTTPProxyHandler::HandleStreamRequestComplete (std::shared_ptr stream) + void HTTPReqHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (stream) { @@ -353,14 +351,14 @@ namespace proxy } } - HTTPProxyServer::HTTPProxyServer(const std::string& address, int port, std::shared_ptr localDestination): + HTTPProxy::HTTPProxy(const std::string& address, int port, std::shared_ptr localDestination): TCPIPAcceptor(address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()) { } - std::shared_ptr HTTPProxyServer::CreateHandler(std::shared_ptr socket) + std::shared_ptr HTTPProxy::CreateHandler(std::shared_ptr socket) { - return std::make_shared (this, socket); + return std::make_shared (this, socket); } -} -} +} // http +} // i2p diff --git a/HTTPProxy.h b/HTTPProxy.h index 0356adb5..29b997eb 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -1,25 +1,21 @@ #ifndef HTTP_PROXY_H__ #define HTTP_PROXY_H__ -namespace i2p -{ -namespace proxy -{ - class HTTPProxyServer: public i2p::client::TCPIPAcceptor +namespace i2p { +namespace proxy { + class HTTPProxy: public i2p::client::TCPIPAcceptor { public: - HTTPProxyServer(const std::string& address, int port, std::shared_ptr localDestination = nullptr); - ~HTTPProxyServer() {}; + HTTPProxy(const std::string& address, int port, std::shared_ptr localDestination = nullptr); + ~HTTPProxy() {}; protected: // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return "HTTP Proxy"; } }; - - typedef HTTPProxyServer HTTPProxy; -} -} +} // http +} // i2p #endif From 92961bb7bf2d1c4c545bab71bf3c494901cc5d82 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 23 Jun 2016 11:23:06 -0400 Subject: [PATCH 1453/6300] i2cp for android --- docs/configuration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 9ab85b46..11c8d0ec 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -60,9 +60,9 @@ All options below still possible in cmdline, but better write it in config file: * --bob.port= - Port of BOB command channel. Usually 2827. BOB is off if not specified * --bob.enabled= - If BOB is enabled. false by default -* --i2cp.address= - The address to listen on -* --i2cp.port= - Port of I2CP server. Usually 7654. IPCP is off if not specified -* --i2cp.enabled= - If I2CP is enabled. false by default. Other services don't requeire I2CP +* --i2cp.address= - The address to listen on or an abstract address for Android LocalSocket +* --i2cp.port= - Port of I2CP server. Usually 7654. Ignored for Andorid +* --i2cp.enabled= - If I2CP is enabled. false by default. Other services don't require I2CP * --i2pcontrol.address= - The address to listen on (I2P control service) * --i2pcontrol.port= - Port of I2P control service. Usually 7650. I2PControl is off if not specified From 13e965096b31a4474fee4051a0a3302a5cec415f Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 23 Jun 2016 12:57:36 -0400 Subject: [PATCH 1454/6300] UPnP for android --- qt/i2pd_qt/i2pd_qt.pro | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index c3b38a8c..fb052641 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -10,14 +10,16 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = i2pd_qt TEMPLATE = app -QMAKE_CXXFLAGS *= -std=c++11 +QMAKE_CXXFLAGS *= -std=c++11 -DUSE_UPNP # 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 clone https://github.com/PurpleI2P/android-ifaddrs.git # change to your own BOOST_PATH = /mnt/media/android/Boost-for-Android-Prebuilt OPENSSL_PATH = /mnt/media/android/OpenSSL-for-Android-Prebuilt +MINIUPNP_PATH = /mnt/media/android/MiniUPnP-for-Android-Prebuilt IFADDRS_PATH = /mnt/media/android/android-ifaddrs # Steps in Android SDK manager: @@ -140,6 +142,7 @@ DEFINES += ANDROID=1 DEFINES += __ANDROID__ INCLUDEPATH += $$BOOST_PATH/boost_1_53_0/include \ $$OPENSSL_PATH/openssl-1.0.2/include \ + $$MINIUPNP_PATH/miniupnp-2.0/include \ $$IFADDRS_PATH DISTFILES += \ android/AndroidManifest.xml @@ -164,7 +167,8 @@ PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto_1_0_0.so \ - $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl_1_0_0.so + $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl_1_0_0.so \ + $$MINIUPNP_PATH/miniupnp-2.0/armeabi-v7a/lib/libminiupnpc.so } equals(ANDROID_TARGET_ARCH, x86){ # http://stackoverflow.com/a/30235934/529442 @@ -181,7 +185,8 @@ PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto.a \ DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto_1_0_0.so \ - $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl_1_0_0.so + $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl_1_0_0.so \ + $$MINIUPNP_PATH/miniupnp-2.0/x86/lib/libminiupnpc.so } } From 0f68bbac8ecd9bfb5e8cb8ee64d6927aaf588cf0 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 23 Jun 2016 14:01:41 -0400 Subject: [PATCH 1455/6300] single #ifdef for protocol type --- I2CP.cpp | 23 +++++------------------ I2CP.h | 29 +++++++++-------------------- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 6e53451c..4884583e 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -114,12 +114,7 @@ namespace client } } - I2CPSession::I2CPSession (I2CPServer& owner, -#ifdef ANDROID - std::shared_ptr socket): -#else - std::shared_ptr socket): -#endif + I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_Payload (nullptr), m_SessionID (0xFFFF), m_MessageID (0), m_IsSendAccepted (true) { @@ -590,9 +585,9 @@ namespace client m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, #ifdef ANDROID - boost::asio::local::stream_protocol::endpoint(std::string (1, '\0') + interface)) // leading 0 for abstract address + I2CPSession::proto::endpoint(std::string (1, '\0') + interface)) // leading 0 for abstract address #else - boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(interface), port)) + I2CPSession::proto::endpoint(boost::asio::ip::address::from_string(interface), port)) #endif { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); @@ -654,21 +649,13 @@ namespace client void I2CPServer::Accept () { -#ifdef ANDROID - auto newSocket = std::make_shared (m_Service); -#else - auto newSocket = std::make_shared (m_Service); -#endif + auto newSocket = std::make_shared (m_Service); m_Acceptor.async_accept (*newSocket, std::bind (&I2CPServer::HandleAccept, this, std::placeholders::_1, newSocket)); } void I2CPServer::HandleAccept(const boost::system::error_code& ecode, -#ifdef ANDROID - std::shared_ptr socket) -#else - std::shared_ptr socket) -#endif + std::shared_ptr socket) { if (!ecode && socket) { diff --git a/I2CP.h b/I2CP.h index 20f20d63..4964c575 100644 --- a/I2CP.h +++ b/I2CP.h @@ -99,12 +99,14 @@ namespace client { public: - I2CPSession (I2CPServer& owner, #ifdef ANDROID - std::shared_ptr socket); + typedef boost::asio::local::stream_protocol proto; #else - std::shared_ptr socket); -#endif + typedef boost::asio::ip::tcp proto; +#endif + + I2CPSession (I2CPServer& owner, std::shared_ptr socket); + ~I2CPSession (); void Start (); @@ -149,11 +151,7 @@ namespace client private: I2CPServer& m_Owner; -#ifdef ANDROID - std::shared_ptr m_Socket; -#else - std::shared_ptr m_Socket; -#endif + std::shared_ptr m_Socket; uint8_t m_Header[I2CP_HEADER_SIZE], * m_Payload; size_t m_PayloadLen; @@ -183,12 +181,7 @@ namespace client void Accept (); - void HandleAccept(const boost::system::error_code& ecode, -#ifdef ANDROID - std::shared_ptr socket); -#else - std::shared_ptr socket); -#endif + void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); private: @@ -198,11 +191,7 @@ namespace client bool m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; -#ifdef ANDROID - boost::asio::local::stream_protocol::acceptor m_Acceptor; -#else - boost::asio::ip::tcp::acceptor m_Acceptor; -#endif + I2CPSession::proto::acceptor m_Acceptor; public: From fedbf2cc44f8d868d4976db66bcac8b170c2781c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 24 Jun 2016 13:15:51 -0400 Subject: [PATCH 1456/6300] link UPnP with app if USE_UPNP is set --- Makefile.linux | 2 +- UPnP.cpp | 78 +++++------------------------------------- UPnP.h | 6 ---- qt/i2pd_qt/i2pd_qt.pro | 8 +++-- 4 files changed, 15 insertions(+), 79 deletions(-) diff --git a/Makefile.linux b/Makefile.linux index e00fd705..da72a41a 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -44,7 +44,7 @@ endif # UPNP Support (miniupnpc 1.5 or 1.6) ifeq ($(USE_UPNP),1) - LDFLAGS += -ldl + LDFLAGS += -lminiupnpc CXXFLAGS += -DUSE_UPNP endif diff --git a/UPnP.cpp b/UPnP.cpp index ea62998b..477342b3 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -6,13 +6,6 @@ #include #include -#ifdef _WIN32 -#include -#define dlsym GetProcAddress -#else -#include -#endif - #include "Log.h" #include "RouterContext.h" @@ -24,32 +17,11 @@ #include #include -// These are per-process and are safe to reuse for all threads -decltype(upnpDiscover) *upnpDiscoverFunc; -decltype(UPNP_AddPortMapping) *UPNP_AddPortMappingFunc; -decltype(UPNP_GetValidIGD) *UPNP_GetValidIGDFunc; -decltype(UPNP_GetExternalIPAddress) *UPNP_GetExternalIPAddressFunc; -decltype(UPNP_DeletePortMapping) *UPNP_DeletePortMappingFunc; -decltype(freeUPNPDevlist) *freeUPNPDevlistFunc; -decltype(FreeUPNPUrls) *FreeUPNPUrlsFunc; - -// Nice approach http://stackoverflow.com/a/21517513/673826 -template -F GetKnownProcAddressImpl(M hmod, const char *name, F) { - auto proc = reinterpret_cast(dlsym(hmod, name)); - if (!proc) { - LogPrint(eLogError, "UPnP: Error resolving ", name, " from library, version mismatch?"); - } - return proc; -} -#define GetKnownProcAddress(hmod, func) GetKnownProcAddressImpl(hmod, #func, func##Func); - - namespace i2p { namespace transport { - UPnP::UPnP () : m_Thread (nullptr) , m_IsModuleLoaded (false) + UPnP::UPnP () : m_Thread (nullptr) { } @@ -65,33 +37,6 @@ namespace transport void UPnP::Start() { - if (!m_IsModuleLoaded) { -#ifdef MAC_OSX - m_Module = dlopen ("libminiupnpc.dylib", RTLD_LAZY); -#elif _WIN32 - m_Module = LoadLibrary ("libminiupnpc.dll"); // from MSYS2 -#else - m_Module = dlopen ("libminiupnpc.so", RTLD_LAZY); -#endif - if (m_Module == NULL) - { - LogPrint (eLogError, "UPnP: Error loading UPNP library, version mismatch?"); - return; - } - else - { - upnpDiscoverFunc = GetKnownProcAddress (m_Module, upnpDiscover); - UPNP_GetValidIGDFunc = GetKnownProcAddress (m_Module, UPNP_GetValidIGD); - UPNP_GetExternalIPAddressFunc = GetKnownProcAddress (m_Module, UPNP_GetExternalIPAddress); - UPNP_AddPortMappingFunc = GetKnownProcAddress (m_Module, UPNP_AddPortMapping); - UPNP_DeletePortMappingFunc = GetKnownProcAddress (m_Module, UPNP_DeletePortMapping); - freeUPNPDevlistFunc = GetKnownProcAddress (m_Module, freeUPNPDevlist); - FreeUPNPUrlsFunc = GetKnownProcAddress (m_Module, FreeUPNPUrls); - if (upnpDiscoverFunc && UPNP_GetValidIGDFunc && UPNP_GetExternalIPAddressFunc && UPNP_AddPortMappingFunc && - UPNP_DeletePortMappingFunc && freeUPNPDevlistFunc && FreeUPNPUrlsFunc) - m_IsModuleLoaded = true; - } - } m_Thread = new std::thread (std::bind (&UPnP::Run, this)); } @@ -123,16 +68,16 @@ namespace transport { int nerror = 0; #if MINIUPNPC_API_VERSION >= 14 - m_Devlist = upnpDiscoverFunc (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, 2, &nerror); + m_Devlist = upnpDiscover (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, 2, &nerror); #else - m_Devlist = upnpDiscoverFunc (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, &nerror); + m_Devlist = upnpDiscover (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, &nerror); #endif int r; - r = UPNP_GetValidIGDFunc (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); + r = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); if (r == 1) { - r = UPNP_GetExternalIPAddressFunc (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) { LogPrint (eLogError, "UPnP: UPNP_GetExternalIPAddress () returned ", r); @@ -171,7 +116,7 @@ namespace transport std::string strDesc = "I2Pd"; try { for (;;) { - r = UPNP_AddPortMappingFunc (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0"); + r = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0"); if (r!=UPNPCOMMAND_SUCCESS) { LogPrint (eLogError, "UPnP: AddPortMapping (", strPort.c_str () ,", ", strPort.c_str () ,", ", m_NetworkAddr, ") failed with code ", r); @@ -208,20 +153,15 @@ namespace transport strType = "UDP"; } int r = 0; - r = UPNP_DeletePortMappingFunc (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"); } void UPnP::Close () { - freeUPNPDevlistFunc (m_Devlist); + freeUPNPDevlist (m_Devlist); m_Devlist = 0; - FreeUPNPUrlsFunc (&m_upnpUrls); -#ifndef _WIN32 - dlclose (m_Module); -#else - FreeLibrary (m_Module); -#endif + FreeUPNPUrls (&m_upnpUrls); } } diff --git a/UPnP.h b/UPnP.h index 32c42118..0a000177 100644 --- a/UPnP.h +++ b/UPnP.h @@ -48,12 +48,6 @@ namespace transport struct UPNPDev * m_Devlist = 0; char m_NetworkAddr[64]; char m_externalIPAddress[40]; - bool m_IsModuleLoaded; -#ifndef _WIN32 - void *m_Module; -#else - HINSTANCE m_Module; -#endif }; } } diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index fb052641..92ddb839 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -14,7 +14,7 @@ QMAKE_CXXFLAGS *= -std=c++11 -DUSE_UPNP # 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 clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/android-ifaddrs.git # change to your own BOOST_PATH = /mnt/media/android/Boost-for-Android-Prebuilt @@ -159,7 +159,8 @@ LIBS += -L$$BOOST_PATH/boost_1_53_0/armeabi-v7a/lib \ -lboost_date_time-gcc-mt-1_53 \ -lboost_filesystem-gcc-mt-1_53 \ -lboost_program_options-gcc-mt-1_53 \ --L$$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/ -lcrypto -lssl +-L$$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/ -lcrypto -lssl \ +-L$$MINIUPNP_PATH/miniupnp-2.0/armeabi-v7a/lib/ -lminiupnpc PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl.a @@ -177,7 +178,8 @@ LIBS += -L$$BOOST_PATH/boost_1_53_0/x86/lib \ -lboost_date_time-gcc-mt-1_53 \ -lboost_filesystem-gcc-mt-1_53 \ -lboost_program_options-gcc-mt-1_53 \ --L$$OPENSSL_PATH/openssl-1.0.2/x86/lib/ -lcrypto -lssl +-L$$OPENSSL_PATH/openssl-1.0.2/x86/lib/ -lcrypto -lssl \ +-L$$MINIUPNP_PATH/miniupnp-2.0/x86/lib/ -lminiupnpc PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto.a \ $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl.a From 0a4888a18ff1111c47b426b244cd91e709cbc768 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 24 Jun 2016 14:18:50 -0400 Subject: [PATCH 1457/6300] link with miniupnp --- Makefile.mingw | 94 +++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/Makefile.mingw b/Makefile.mingw index b859ebb3..682221d1 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,47 +1,47 @@ -USE_WIN32_APP=yes -CXX = g++ -WINDRES = windres -CXXFLAGS = -Os -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN -NEEDED_CXXFLAGS = -std=c++11 -BOOST_SUFFIX = -mt -INCFLAGS = -I/usr/include/ -I/usr/local/include/ -LDFLAGS = -Wl,-rpath,/usr/local/lib \ - -L/usr/local/lib \ - -L/c/dev/openssl \ - -L/c/dev/boost/lib -LDLIBS = \ - -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) \ - -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) \ - -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) \ - -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) \ - -Wl,-Bstatic -lssl \ - -Wl,-Bstatic -lcrypto \ - -Wl,-Bstatic -lz \ - -Wl,-Bstatic -lwsock32 \ - -Wl,-Bstatic -lws2_32 \ - -Wl,-Bstatic -lgdi32 \ - -Wl,-Bstatic -liphlpapi \ - -static-libgcc -static-libstdc++ \ - -Wl,-Bstatic -lstdc++ \ - -Wl,-Bstatic -lpthread - -ifeq ($(USE_WIN32_APP), yes) - CXXFLAGS += -DWIN32_APP - LDFLAGS += -mwindows -s - DAEMON_RC += Win32/Resource.rc - DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) -endif - -# UPNP Support -ifeq ($(USE_UPNP),1) - CXXFLAGS += -DUSE_UPNP -endif - -ifeq ($(USE_AESNI),1) - CPU_FLAGS = -maes -DAESNI -else - CPU_FLAGS = -msse -endif - -obj/%.o : %.rc - $(WINDRES) -i $< -o $@ +USE_WIN32_APP=yes +CXX = g++ +WINDRES = windres +CXXFLAGS = -Os -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN +NEEDED_CXXFLAGS = -std=c++11 +BOOST_SUFFIX = -mt +INCFLAGS = -I/usr/include/ -I/usr/local/include/ +LDFLAGS = -Wl,-rpath,/usr/local/lib \ + -L/usr/local/lib + +# UPNP Support +ifeq ($(USE_UPNP),1) + CXXFLAGS += -DUSE_UPNP -DMINIUPNP_STATICLIB + LDLIBS = -Wl,-Bstatic -lminiupnpc +endif + +LDLIBS += \ + -Wl,-Bstatic -lboost_system$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lboost_date_time$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lboost_filesystem$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lboost_program_options$(BOOST_SUFFIX) \ + -Wl,-Bstatic -lssl \ + -Wl,-Bstatic -lcrypto \ + -Wl,-Bstatic -lz \ + -Wl,-Bstatic -lwsock32 \ + -Wl,-Bstatic -lws2_32 \ + -Wl,-Bstatic -lgdi32 \ + -Wl,-Bstatic -liphlpapi \ + -static-libgcc -static-libstdc++ \ + -Wl,-Bstatic -lstdc++ \ + -Wl,-Bstatic -lpthread + +ifeq ($(USE_WIN32_APP), yes) + CXXFLAGS += -DWIN32_APP + LDFLAGS += -mwindows -s + DAEMON_RC += Win32/Resource.rc + DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) +endif + +ifeq ($(USE_AESNI),1) + CPU_FLAGS = -maes -DAESNI +else + CPU_FLAGS = -msse +endif + +obj/%.o : %.rc + $(WINDRES) -i $< -o $@ From ba772ab4811feb25faeaed523b8cb672cc3598b3 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 24 Jun 2016 14:20:35 -0400 Subject: [PATCH 1458/6300] static miniupnpc --- docs/build_notes_windows.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index 6290ccc2..8ba36131 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -172,8 +172,6 @@ pacman -S mingw-w64-i686-miniupnpc make USE_UPNP=1 ``` -It requires libminiupnpc.dll from /mingw32/bin - ### Creating Visual Studio project Start CMake GUI, navigate to i2pd directory, choose building directory, e.g. ./out, and configure options. From 5fbaf0bc7dbeb90cef2ce6cb14e36155be78cfee Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 24 Jun 2016 15:29:36 -0400 Subject: [PATCH 1459/6300] disabled UPNP=ON --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d83cdbc0..e52c53a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew unlink boost openssl && brew link boost openssl -f ; fi env: matrix: - - BUILD_TYPE=Release UPNP=ON +# - BUILD_TYPE=Release UPNP=ON - BUILD_TYPE=Release UPNP=OFF script: - cd build && cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DWITH_UPNP=${UPNP} && make From 814b174f25ff38c309b4df60998ac785eb2820e7 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Sat, 25 Jun 2016 02:47:46 +0800 Subject: [PATCH 1460/6300] android version code bump --- qt/i2pd_qt/android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/i2pd_qt/android/AndroidManifest.xml b/qt/i2pd_qt/android/AndroidManifest.xml index 6ab763ff..37a736fd 100644 --- a/qt/i2pd_qt/android/AndroidManifest.xml +++ b/qt/i2pd_qt/android/AndroidManifest.xml @@ -1,5 +1,5 @@ - + From 35f6c6cb982db628e250b271b5121baf8af2e979 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Sat, 25 Jun 2016 03:37:59 +0800 Subject: [PATCH 1461/6300] graceful quit button added --- qt/i2pd_qt/i2pd_qt.pro | 15 +++++++++------ qt/i2pd_qt/mainwindow.cpp | 35 +++++++++++++++++++++++++++++++---- qt/i2pd_qt/mainwindow.h | 3 +++ qt/i2pd_qt/mainwindow.ui | 24 ++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 92ddb839..38df4838 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -10,7 +10,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = i2pd_qt TEMPLATE = app -QMAKE_CXXFLAGS *= -std=c++11 -DUSE_UPNP +QMAKE_CXXFLAGS *= -std=c++11 # git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git @@ -29,7 +29,7 @@ IFADDRS_PATH = /mnt/media/android/android-ifaddrs SOURCES += DaemonQT.cpp\ mainwindow.cpp \ - ../../HTTPServer.cpp ../../I2PControl.cpp ../../UPnP.cpp ../../Daemon.cpp ../../Config.cpp \ + ../../HTTPServer.cpp ../../I2PControl.cpp ../../Daemon.cpp ../../Config.cpp \ ../../AddressBook.cpp \ ../../api.cpp \ ../../Base.cpp \ @@ -72,8 +72,7 @@ SOURCES += DaemonQT.cpp\ ../../TunnelGateway.cpp \ ../../TunnelPool.cpp \ ../../util.cpp \ - ../../i2pd.cpp \ - $$IFADDRS_PATH/ifaddrs.c + ../../i2pd.cpp HEADERS += DaemonQT.h mainwindow.h \ ../../HTTPServer.h ../../I2PControl.h ../../UPnP.h ../../Daemon.h ../../Config.h \ @@ -125,8 +124,7 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../TunnelGateway.h \ ../../TunnelPool.h \ ../../util.h \ - ../../version.h \ - $$IFADDRS_PATH/ifaddrs.h + ../../version.h FORMS += mainwindow.ui @@ -140,6 +138,8 @@ android { message("Using Android settings") DEFINES += ANDROID=1 DEFINES += __ANDROID__ +DEFINES += USE_UPNP + INCLUDEPATH += $$BOOST_PATH/boost_1_53_0/include \ $$OPENSSL_PATH/openssl-1.0.2/include \ $$MINIUPNP_PATH/miniupnp-2.0/include \ @@ -149,6 +149,9 @@ DISTFILES += \ ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android +SOURCES += $$IFADDRS_PATH/ifaddrs.c ../../UPnP.cpp +HEADERS += $$IFADDRS_PATH/ifaddrs.h + equals(ANDROID_TARGET_ARCH, armeabi-v7a){ DEFINES += ANDROID_ARM7A diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index c1654295..325c8fc5 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -1,6 +1,7 @@ #include "mainwindow.h" //#include "ui_mainwindow.h" #include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)/*, @@ -22,20 +23,29 @@ MainWindow::MainWindow(QWidget *parent) : verticalLayout1->setContentsMargins(0, 0, 0, 0); quitButton = new QPushButton(verticalLayoutWidget); quitButton->setObjectName(QStringLiteral("quitButton")); - QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); + QSizePolicy sizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + sizePolicy.setHorizontalStretch(1); + //sizePolicy.setVerticalStretch(1); sizePolicy.setHeightForWidth(quitButton->sizePolicy().hasHeightForWidth()); quitButton->setSizePolicy(sizePolicy); - verticalLayout1->addWidget(quitButton); + gracefulQuitButton = new QPushButton(verticalLayoutWidget); + gracefulQuitButton->setObjectName(QStringLiteral("gracefulQuitButton")); + QSizePolicy sizePolicy2(QSizePolicy::Maximum, QSizePolicy::Maximum); + sizePolicy2.setHorizontalStretch(1); + //sizePolicy2.setVerticalStretch(1); + sizePolicy2.setHeightForWidth(gracefulQuitButton->sizePolicy().hasHeightForWidth()); + gracefulQuitButton->setSizePolicy(sizePolicy2); + verticalLayout1->addWidget(gracefulQuitButton); setCentralWidget(centralWidget); setWindowTitle(QApplication::translate("MainWindow", "MainWindow", 0)); quitButton->setText(QApplication::translate("MainWindow", "Quit", 0)); + gracefulQuitButton->setText(QApplication::translate("MainWindow", "Graceful Quit", 0)); QObject::connect(quitButton, SIGNAL(released()), this, SLOT(handleQuitButton())); + QObject::connect(gracefulQuitButton, SIGNAL(released()), this, SLOT(handleGracefulQuitButton())); //QMetaObject::connectSlotsByName(this); } @@ -46,6 +56,23 @@ void MainWindow::handleQuitButton() { QApplication::instance()->quit(); } +void MainWindow::handleGracefulQuitButton() { + qDebug("Graceful Quit pressed."); + gracefulQuitButton->setText(QApplication::translate("MainWindow", "Graceful quit is in progress", 0)); + gracefulQuitButton->setEnabled(false); + gracefulQuitButton->adjustSize(); + verticalLayoutWidget->adjustSize(); + //here, the code to stop tunnels + QTimer::singleShot(10*60*1000/*millis*/, this, SLOT(handleGracefulQuitTimerEvent())); +} + +void MainWindow::handleGracefulQuitTimerEvent() { + qDebug("Hiding the main window"); + close(); + qDebug("Performing quit"); + QApplication::instance()->quit(); +} + MainWindow::~MainWindow() { qDebug("Destroying main window"); diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 3a172c25..94e3a7b3 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -27,12 +27,15 @@ public: private slots: void handleQuitButton(); + void handleGracefulQuitButton(); + void handleGracefulQuitTimerEvent(); private: QWidget *centralWidget; QWidget *verticalLayoutWidget; QVBoxLayout *verticalLayout1; QPushButton *quitButton; + QPushButton *gracefulQuitButton; }; #endif // MAINWINDOW_H diff --git a/qt/i2pd_qt/mainwindow.ui b/qt/i2pd_qt/mainwindow.ui index bdb57867..d73e7743 100644 --- a/qt/i2pd_qt/mainwindow.ui +++ b/qt/i2pd_qt/mainwindow.ui @@ -37,6 +37,13 @@ + + + + Graceful Quit + + + @@ -60,8 +67,25 @@ + + gracefulShutdownButton + released() + MainWindow + handleGracefulQuitButton() + + + 395 + 319 + + + 399 + 239 + + + handleQuitButton() + handleGracefulQuitButton() From f22e5c209c338c8f8bef9361ab3c6e1b646790d6 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 24 Jun 2016 16:05:03 -0400 Subject: [PATCH 1462/6300] fixed QT linux build --- qt/i2pd_qt/i2pd_qt.pro | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 38df4838..8a786e5d 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -11,6 +11,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = i2pd_qt TEMPLATE = app QMAKE_CXXFLAGS *= -std=c++11 +DEFINES += USE_UPNP # git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git @@ -71,6 +72,7 @@ SOURCES += DaemonQT.cpp\ ../../TunnelEndpoint.cpp \ ../../TunnelGateway.cpp \ ../../TunnelPool.cpp \ + ../../UPnP.cpp \ ../../util.cpp \ ../../i2pd.cpp @@ -123,6 +125,7 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../TunnelEndpoint.h \ ../../TunnelGateway.h \ ../../TunnelPool.h \ + ../../UPnP.h \ ../../util.h \ ../../version.h @@ -138,7 +141,6 @@ android { message("Using Android settings") DEFINES += ANDROID=1 DEFINES += __ANDROID__ -DEFINES += USE_UPNP INCLUDEPATH += $$BOOST_PATH/boost_1_53_0/include \ $$OPENSSL_PATH/openssl-1.0.2/include \ @@ -197,6 +199,6 @@ ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto_1_0_0.so \ linux:!android { message("Using Linux settings") -LIBS += -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread +LIBS += -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lminiupnpc } From 047c8eda2275bd5b4571c156cfd81ebed330c0a5 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 24 Jun 2016 16:26:13 -0400 Subject: [PATCH 1463/6300] stop accepting tunnels by graceful shutdown --- qt/i2pd_qt/mainwindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 325c8fc5..ab872ff4 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -2,6 +2,7 @@ //#include "ui_mainwindow.h" #include #include +#include "../../RouterContext.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)/*, @@ -62,7 +63,7 @@ void MainWindow::handleGracefulQuitButton() { gracefulQuitButton->setEnabled(false); gracefulQuitButton->adjustSize(); verticalLayoutWidget->adjustSize(); - //here, the code to stop tunnels + i2p::context.SetAcceptsTunnels (false); // stop accpting tunnels QTimer::singleShot(10*60*1000/*millis*/, this, SLOT(handleGracefulQuitTimerEvent())); } From 7e580e6a0bfe3eca72cdc662621f3f6a62e11405 Mon Sep 17 00:00:00 2001 From: xcps Date: Fri, 24 Jun 2016 17:58:46 -0400 Subject: [PATCH 1464/6300] Update HTTPServer.cpp --- HTTPServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index b40cef27..d0895cbc 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -612,7 +612,7 @@ namespace http { HandleCommand (req, res, s); } else { ShowStatus (s); - //res.add_header("Refresh", "5"); + res.add_header("Refresh", 10"); } ShowPageTail (s); From 6b3bd755b08fe3c3af3f1c9b88a06eb3000fcd7a Mon Sep 17 00:00:00 2001 From: xcps Date: Fri, 24 Jun 2016 19:07:47 -0400 Subject: [PATCH 1465/6300] fixtypo --- HTTPServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index d0895cbc..f35e02d3 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -612,7 +612,7 @@ namespace http { HandleCommand (req, res, s); } else { ShowStatus (s); - res.add_header("Refresh", 10"); + res.add_header("Refresh", "10"); } ShowPageTail (s); From 9f411511569916eb7da3e912f9a7448589fe8ca5 Mon Sep 17 00:00:00 2001 From: xcps Date: Fri, 24 Jun 2016 19:25:48 -0400 Subject: [PATCH 1466/6300] HTTP proxy redirects to 0.0.0.0:7070/?page=jumpservices --- HTTPProxy.cpp | 12 +++--------- HTTPServer.h | 2 ++ qt/i2pd_qt/i2pd_qt.pro | 8 ++++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 81ed8da6..e11d430d 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -20,6 +20,7 @@ #include "I2PTunnel.h" #include "Config.h" #include "HTTP.h" +#include "HTTPServer.h" namespace i2p { namespace proxy { @@ -113,15 +114,8 @@ namespace proxy { void HTTPReqHandler::RedirectToJumpService(/*HTTPReqHandler::errTypes error*/) { std::stringstream ss; - std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); - uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); - - ss << "HTTP/1.1 302 Found\r\n" - << "Connection: close\r\n" - << "Location: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n" - << "\r\n"; - std::string response = ss.str(); - boost::asio::async_write(*m_sock, boost::asio::buffer(response), + i2p::http::ShowJumpServices (ss, m_address); + boost::asio::async_write(*m_sock, boost::asio::buffer(ss.str ()), std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } diff --git a/HTTPServer.h b/HTTPServer.h index bf7f5c65..6aa0d792 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -61,6 +61,8 @@ namespace http { boost::asio::io_service::work m_Work; boost::asio::ip::tcp::acceptor m_Acceptor; }; + + void ShowJumpServices (std::stringstream& s, const std::string& address); } // http } // i2p diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 8a786e5d..b3829fcc 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -18,10 +18,10 @@ DEFINES += USE_UPNP # git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/android-ifaddrs.git # change to your own -BOOST_PATH = /mnt/media/android/Boost-for-Android-Prebuilt -OPENSSL_PATH = /mnt/media/android/OpenSSL-for-Android-Prebuilt -MINIUPNP_PATH = /mnt/media/android/MiniUPnP-for-Android-Prebuilt -IFADDRS_PATH = /mnt/media/android/android-ifaddrs +BOOST_PATH = /home/rebby/andp/Boost-for-Android-Prebuilt +OPENSSL_PATH = /home/rebby/andp/OpenSSL-for-Android-Prebuilt +MINIUPNP_PATH = /home/rebby/andp/MiniUPnP-for-Android-Prebuilt +IFADDRS_PATH = /home/rebby/andp/android-ifaddrs # Steps in Android SDK manager: # 1) Check Extras/Google Support Library https://developer.android.com/topic/libraries/support-library/setup.html From 4bc76995d1d14fc9e8b48def823d200e4157676f Mon Sep 17 00:00:00 2001 From: xcps Date: Fri, 24 Jun 2016 19:29:59 -0400 Subject: [PATCH 1467/6300] docs: default httpproxy.port changed to actual 4444 --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 11c8d0ec..1b3e899b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -41,7 +41,7 @@ All options below still possible in cmdline, but better write it in config file: * --http.pass= - Password for basic auth (default: random, see logs) * --httpproxy.address= - The address to listen on (HTTP Proxy) -* --httpproxy.port= - The port to listen on (HTTP Proxy) 4446 by default +* --httpproxy.port= - The port to listen on (HTTP Proxy) 4444 by default * --httpproxy.keys= - optional keys file for proxy local destination (both HTTP and SOCKS) * --httpproxy.enabled= - If HTTP proxy is enabled. true by default From 096927beeda11b5d39be591a62c23a222cb013d2 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 24 Jun 2016 21:54:58 -0400 Subject: [PATCH 1468/6300] don't sedn explicit Ack if no NACKs only --- Streaming.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 1a6fdbca..e5260556 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -395,8 +395,11 @@ namespace stream } if (packets.size () > 0) { - m_IsAckSendScheduled = false; - m_AckSendTimer.cancel (); + if (m_SavedPackets.empty ()) // no NACKS + { + m_IsAckSendScheduled = false; + m_AckSendTimer.cancel (); + } bool isEmpty = m_SentPackets.empty (); auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it: packets) From 134baad56db7531d7f70f1a81b40c3c9c9d654b0 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Sun, 26 Jun 2016 02:32:54 +0800 Subject: [PATCH 1469/6300] added tray icon to linux and windows versions --- qt/i2pd_qt/i2pd.qrc | 5 +++ qt/i2pd_qt/i2pd_qt.pro | 12 ++++++ qt/i2pd_qt/images/icon.png | Bin 0 -> 8712 bytes qt/i2pd_qt/mainwindow.cpp | 84 ++++++++++++++++++++++++++++++++++++- qt/i2pd_qt/mainwindow.h | 31 ++++++++++++++ 5 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 qt/i2pd_qt/i2pd.qrc create mode 100644 qt/i2pd_qt/images/icon.png diff --git a/qt/i2pd_qt/i2pd.qrc b/qt/i2pd_qt/i2pd.qrc new file mode 100644 index 00000000..2abdeb05 --- /dev/null +++ b/qt/i2pd_qt/i2pd.qrc @@ -0,0 +1,5 @@ + + + images/icon.png + + diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index b3829fcc..f9fb78e8 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -202,3 +202,15 @@ message("Using Linux settings") LIBS += -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lminiupnpc } + +!android:!symbian:!maemo5:!simulator { +message("Build with a system tray icon") +# see also http://doc.qt.io/qt-4.8/qt-desktop-systray-systray-pro.html for example on wince* +#sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS i2pd_qt.pro resources images +RESOURCES = i2pd.qrc +QT += xml +#INSTALLS += sources +} + +RESOURCES += \ + i2pd.qrc diff --git a/qt/i2pd_qt/images/icon.png b/qt/i2pd_qt/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a5dc7b680ba9dee30161ffb1c9fcd17bbd6ddd84 GIT binary patch literal 8712 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 literal 0 HcmV?d00001 diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index ab872ff4..3f220a8c 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -3,10 +3,14 @@ #include #include #include "../../RouterContext.h" +#ifndef ANDROID +#include +#endif MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)/*, - ui(new Ui::MainWindow)*/ + ui(new Ui::MainWindow)*/, + quitting(false) { //ui->setupUi(this); if (objectName().isEmpty()) @@ -41,18 +45,91 @@ MainWindow::MainWindow(QWidget *parent) : setCentralWidget(centralWidget); - setWindowTitle(QApplication::translate("MainWindow", "MainWindow", 0)); + setWindowTitle(QApplication::translate("MainWindow", "i2pd", 0)); quitButton->setText(QApplication::translate("MainWindow", "Quit", 0)); gracefulQuitButton->setText(QApplication::translate("MainWindow", "Graceful Quit", 0)); +#ifndef ANDROID + createActions(); + createTrayIcon(); +#endif + QObject::connect(quitButton, SIGNAL(released()), this, SLOT(handleQuitButton())); QObject::connect(gracefulQuitButton, SIGNAL(released()), this, SLOT(handleGracefulQuitButton())); +#ifndef ANDROID + QObject::connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); + + setIcon(); + trayIcon->show(); +#endif + //QMetaObject::connectSlotsByName(this); } +#ifndef ANDROID +void MainWindow::createActions() { + toggleWindowVisibleAction = new QAction(tr("&Toggle the window"), this); + connect(toggleWindowVisibleAction, SIGNAL(triggered()), this, SLOT(toggleVisibilitySlot())); + + //quitAction = new QAction(tr("&Quit"), this); + //connect(quitAction, SIGNAL(triggered()), QApplication::instance(), SLOT(quit())); +} + +void MainWindow::toggleVisibilitySlot() { + setVisible(!isVisible()); +} + +void MainWindow::createTrayIcon() { + trayIconMenu = new QMenu(this); + trayIconMenu->addAction(toggleWindowVisibleAction); + //trayIconMenu->addSeparator(); + //trayIconMenu->addAction(quitAction); + + trayIcon = new QSystemTrayIcon(this); + trayIcon->setContextMenu(trayIconMenu); +} + +void MainWindow::setIcon() { + QIcon icon(":/images/icon.png"); + trayIcon->setIcon(icon); + setWindowIcon(icon); + + trayIcon->setToolTip(QApplication::translate("MainWindow", "i2pd", 0)); +} + +void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) { + switch (reason) { + case QSystemTrayIcon::Trigger: + case QSystemTrayIcon::DoubleClick: + case QSystemTrayIcon::MiddleClick: + setVisible(!isVisible()); + break; + default: + qDebug() << "MainWindow::iconActivated(): unknown reason: " << reason << endl; + break; + } +} + +void MainWindow::closeEvent(QCloseEvent *event) { + if(quitting){ QMainWindow::closeEvent(event); return; } + if (trayIcon->isVisible()) { + QMessageBox::information(this, tr("i2pd"), + tr("The program will keep running in the " + "system tray. To gracefully terminate the program, " + "choose Graceful Quit at the main i2pd window.")); + hide(); + event->ignore(); + } +} +#endif + void MainWindow::handleQuitButton() { qDebug("Quit pressed. Hiding the main window"); +#ifndef ANDROID + quitting=true; +#endif close(); QApplication::instance()->quit(); } @@ -69,6 +146,9 @@ void MainWindow::handleGracefulQuitButton() { void MainWindow::handleGracefulQuitTimerEvent() { qDebug("Hiding the main window"); +#ifndef ANDROID + quitting=true; +#endif close(); qDebug("Performing quit"); QApplication::instance()->quit(); diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 94e3a7b3..349eadec 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -12,6 +12,11 @@ #include #include #include +#ifndef ANDROID +#include +#include +#include +#endif namespace Ui { class MainWindow; @@ -25,17 +30,43 @@ public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); +//#ifndef ANDROID +// void setVisible(bool visible); +//#endif + private slots: void handleQuitButton(); void handleGracefulQuitButton(); void handleGracefulQuitTimerEvent(); +#ifndef ANDROID + void setIcon(); + void iconActivated(QSystemTrayIcon::ActivationReason reason); + void toggleVisibilitySlot(); +#endif private: +#ifndef ANDROID + void createActions(); + void createTrayIcon(); +#endif + QWidget *centralWidget; QWidget *verticalLayoutWidget; QVBoxLayout *verticalLayout1; QPushButton *quitButton; QPushButton *gracefulQuitButton; + +#ifndef ANDROID + bool quitting; + QAction *toggleWindowVisibleAction; + QSystemTrayIcon *trayIcon; + QMenu *trayIconMenu; +#endif + +protected: +#ifndef ANDROID + void closeEvent(QCloseEvent *event); +#endif }; #endif // MAINWINDOW_H From 10638b6e40bbf519043ea50f6929e7a4547c20cb Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Sun, 26 Jun 2016 02:48:13 +0800 Subject: [PATCH 1470/6300] fixed unnecessary resources setting --- qt/i2pd_qt/.gitignore | 1 + qt/i2pd_qt/i2pd_qt.pro | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 qt/i2pd_qt/.gitignore diff --git a/qt/i2pd_qt/.gitignore b/qt/i2pd_qt/.gitignore new file mode 100644 index 00000000..35d7caf4 --- /dev/null +++ b/qt/i2pd_qt/.gitignore @@ -0,0 +1 @@ +i2pd_qt.pro.user* diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index f9fb78e8..9f579ffc 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -212,5 +212,3 @@ QT += xml #INSTALLS += sources } -RESOURCES += \ - i2pd.qrc From 9ba7120011f738e2e22ebf5f006d1b471afd4b63 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 26 Jun 2016 08:32:36 -0400 Subject: [PATCH 1471/6300] fixed build error --- qt/i2pd_qt/mainwindow.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 3f220a8c..0e2ca01c 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -9,8 +9,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)/*, - ui(new Ui::MainWindow)*/, + ui(new Ui::MainWindow)*/ +#ifndef ANDROID + , quitting(false) +#endif { //ui->setupUi(this); if (objectName().isEmpty()) From 2757ef94c93434cfb988aabffc5084d264c6b032 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 26 Jun 2016 08:37:40 -0400 Subject: [PATCH 1472/6300] don't include UPNP twice for android --- qt/i2pd_qt/i2pd_qt.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 9f579ffc..b2cafdeb 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -151,7 +151,7 @@ DISTFILES += \ ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android -SOURCES += $$IFADDRS_PATH/ifaddrs.c ../../UPnP.cpp +SOURCES += $$IFADDRS_PATH/ifaddrs.c HEADERS += $$IFADDRS_PATH/ifaddrs.h equals(ANDROID_TARGET_ARCH, armeabi-v7a){ From 5c6ec70126b3b949031d9bb1e9ae56eb4f2b12e4 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 26 Jun 2016 11:17:05 -0400 Subject: [PATCH 1473/6300] fix static build for rpi linux --- Makefile.linux | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.linux b/Makefile.linux index da72a41a..324d9467 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -33,10 +33,10 @@ ifeq ($(USE_STATIC),yes) LDLIBS += $(LIBDIR)/libboost_date_time.a LDLIBS += $(LIBDIR)/libboost_filesystem.a LDLIBS += $(LIBDIR)/libboost_program_options.a - LDLIBS += $(LIBDIR)/libcrypto.a LDLIBS += $(LIBDIR)/libssl.a + LDLIBS += $(LIBDIR)/libcrypto.a LDLIBS += $(LIBDIR)/libz.a - LDLIBS += -lpthread -static-libstdc++ -static-libgcc + LDLIBS += -lpthread -static-libstdc++ -static-libgcc -lrt USE_AESNI := no else LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread From 4b9e39ac64d7a876bfe562b2eec5d063ec7d9a41 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 26 Jun 2016 17:03:04 -0400 Subject: [PATCH 1474/6300] limit SSU outgoing windows --- SSUData.cpp | 10 +++++++++- SSUData.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/SSUData.cpp b/SSUData.cpp index 0ccf25a8..4ce7451d 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -425,6 +425,7 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + int numResent = 0; for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();) { if (ts >= it->second->nextResendTime) @@ -437,6 +438,7 @@ namespace transport try { m_Session.Send (f->buf, f->len); // resend + numResent++; } catch (boost::system::system_error& ec) { @@ -457,7 +459,13 @@ namespace transport else it++; } - ScheduleResend (); + if (numResent < MAX_OUTGOING_WINDOW_SIZE) + ScheduleResend (); + else + { + LogPrint (eLogError, "SSU: resend window exceeds max size. Session terminated"); + m_Session.Close (); + } } } diff --git a/SSUData.h b/SSUData.h index 392bfce6..02135350 100644 --- a/SSUData.h +++ b/SSUData.h @@ -29,6 +29,7 @@ namespace transport const int DECAY_INTERVAL = 20; // in seconds const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check + const int MAX_OUTGOING_WINDOW_SIZE = 200; // how many unacked message we can store // data flags const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02; const uint8_t DATA_FLAG_WANT_REPLY = 0x04; From c84468dbed5bdc35624b2cc2f35ae1453432e480 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 27 Jun 2016 01:12:20 +0000 Subject: [PATCH 1475/6300] * fix cmake build with upnp=on --- build/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 4a1bfe2b..61e05e83 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -338,6 +338,10 @@ if (WITH_BINARY) set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-z relro -z now" ) endif () + if (WITH_UPNP) + target_link_libraries("${PROJECT_NAME}" "miniupnpc") + endif () + # FindBoost pulls pthread for thread which is broken for static linking at least on Ubuntu 15.04 list(GET Boost_LIBRARIES -1 LAST_Boost_LIBRARIES) if(${LAST_Boost_LIBRARIES} MATCHES ".*pthread.*") From e28f910c882156039116ef45157afeb5869b5e66 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 27 Jun 2016 01:30:02 +0000 Subject: [PATCH 1476/6300] * enable travis for UPNP=ON back --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e52c53a7..d83cdbc0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew unlink boost openssl && brew link boost openssl -f ; fi env: matrix: -# - BUILD_TYPE=Release UPNP=ON + - BUILD_TYPE=Release UPNP=ON - BUILD_TYPE=Release UPNP=OFF script: - cd build && cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DWITH_UPNP=${UPNP} && make From c5e3e17eae62b2531e13ababd2264f332c7329b6 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 27 Jun 2016 01:30:00 +0000 Subject: [PATCH 1477/6300] * HTTPProxy.cpp : extract IsI2PAddress() from class --- HTTPProxy.cpp | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index e11d430d..b801bfc9 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -24,6 +24,15 @@ namespace i2p { namespace proxy { + bool str_rmatch(std::string & str, const char *suffix) { + auto pos = str.rfind (suffix); + if (pos == std::string::npos) + return false; /* not found */ + if (str.length() == (pos + std::strlen(suffix))) + return true; /* match */ + return false; + } + static const size_t http_buffer_size = 8192; class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { @@ -45,7 +54,6 @@ namespace proxy { void HTTPRequestFailed(const char *message); void RedirectToJumpService(); void ExtractRequest(); - bool IsI2PAddress(); bool ValidateHTTPRequest(); void HandleJumpServices(); bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); @@ -180,16 +188,6 @@ namespace proxy { m_path.erase(addressHelperPos); } - bool HTTPReqHandler::IsI2PAddress() - { - auto pos = m_address.rfind (".i2p"); - if (pos != std::string::npos && (pos+4) == m_address.length ()) - { - return true; - } - return false; - } - bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { ExtractRequest(); //TODO: parse earlier @@ -197,14 +195,13 @@ namespace proxy { HandleJumpServices(); i2p::data::IdentHash identHash; - if (IsI2PAddress ()) + if (str_rmatch(m_address, ".i2p")) { if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ RedirectToJumpService(); return false; } } - m_request = m_method; m_request.push_back(' '); @@ -317,7 +314,6 @@ namespace proxy { else AsyncSockRead(); } - } void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) From 6f77c6f3f49c3acb842c4f320e9a096a40253cdf Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 27 Jun 2016 01:30:00 +0000 Subject: [PATCH 1478/6300] * HTTPProxy.cpp : don't reuse part of httppserver, addresshelpers handling will be moved to proxy in future --- HTTPProxy.cpp | 25 ++++++++++++++++++------- HTTPServer.h | 2 -- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index b801bfc9..ed41049a 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -20,7 +20,6 @@ #include "I2PTunnel.h" #include "Config.h" #include "HTTP.h" -#include "HTTPServer.h" namespace i2p { namespace proxy { @@ -52,7 +51,7 @@ namespace proxy { void Terminate(); void AsyncSockRead(); void HTTPRequestFailed(const char *message); - void RedirectToJumpService(); + void RedirectToJumpService(std::string & host); void ExtractRequest(); bool ValidateHTTPRequest(); void HandleJumpServices(); @@ -119,11 +118,23 @@ namespace proxy { std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPReqHandler::RedirectToJumpService(/*HTTPReqHandler::errTypes error*/) + void HTTPReqHandler::RedirectToJumpService(std::string & host) { - std::stringstream ss; - i2p::http::ShowJumpServices (ss, m_address); - boost::asio::async_write(*m_sock, boost::asio::buffer(ss.str ()), + i2p::http::HTTPRes res; + i2p::http::URL url; + + /* TODO: don't redirect to webconsole, it's not always work, handle jumpservices here */ + i2p::config::GetOption("http.address", url.host); + i2p::config::GetOption("http.port", url.port); + url.path = "/"; + url.query = "page=jumpservices&address="; + url.query += host; + + res.code = 302; /* redirect */ + res.add_header("Location", url.to_string().c_str()); + + std::string response = res.to_string(); + boost::asio::async_write(*m_sock, boost::asio::buffer(response), std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } @@ -198,7 +209,7 @@ namespace proxy { if (str_rmatch(m_address, ".i2p")) { if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ - RedirectToJumpService(); + RedirectToJumpService(m_address); return false; } } diff --git a/HTTPServer.h b/HTTPServer.h index 6aa0d792..bf7f5c65 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -61,8 +61,6 @@ namespace http { boost::asio::io_service::work m_Work; boost::asio::ip::tcp::acceptor m_Acceptor; }; - - void ShowJumpServices (std::stringstream& s, const std::string& address); } // http } // i2p From 727068cc4bf9336945e5f611f750031c0d79af0e Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 27 Jun 2016 01:30:00 +0000 Subject: [PATCH 1479/6300] * HTTPProxy.cpp : migrate HTTPRequestFailed() to new http classes --- HTTPProxy.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index ed41049a..af914b47 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -106,14 +106,13 @@ namespace proxy { //TODO: handle this apropriately void HTTPReqHandler::HTTPRequestFailed(const char *message) { - std::size_t size = std::strlen(message); - std::stringstream ss; - ss << "HTTP/1.0 500 Internal Server Error\r\n" - << "Content-Type: text/plain\r\n"; - ss << "Content-Length: " << std::to_string(size + 2) << "\r\n" - << "\r\n"; /* end of headers */ - ss << message << "\r\n"; - std::string response = ss.str(); + i2p::http::HTTPRes res; + res.code = 500; + res.add_header("Content-Type", "text/plain"); + res.add_header("Connection", "close"); + res.body = message; + res.body += "\r\n"; + std::string response = res.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(response), std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } From 09b15f4940f10030019e3f02da06e611c5ded42c Mon Sep 17 00:00:00 2001 From: MXPLRS|Kirill Date: Mon, 27 Jun 2016 05:55:07 +0300 Subject: [PATCH 1480/6300] edited i2pd_qt.pro --- qt/i2pd_qt/i2pd_qt.pro | 252 ++++++++++++++--------------------------- 1 file changed, 83 insertions(+), 169 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index b2cafdeb..07e71839 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -1,10 +1,4 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2016-06-14T04:53:04 -# -#------------------------------------------------- - -QT += core gui +QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -13,202 +7,122 @@ TEMPLATE = app QMAKE_CXXFLAGS *= -std=c++11 DEFINES += USE_UPNP +# change to your own path, where you will store all needed libraries with 'git clone' commands below. +MAIN_PATH = /path/to/libraries + # 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 -BOOST_PATH = /home/rebby/andp/Boost-for-Android-Prebuilt -OPENSSL_PATH = /home/rebby/andp/OpenSSL-for-Android-Prebuilt -MINIUPNP_PATH = /home/rebby/andp/MiniUPnP-for-Android-Prebuilt -IFADDRS_PATH = /home/rebby/andp/android-ifaddrs + +BOOST_PATH = $$MAIN_PATH/Boost-for-Android-Prebuilt +OPENSSL_PATH = $$MAIN_PATH/OpenSSL-for-Android-Prebuilt +MINIUPNP_PATH = $$MAIN_PATH/MiniUPnP-for-Android-Prebuilt +IFADDRS_PATH = $$MAIN_PATH/android-ifaddrs # Steps in Android SDK manager: # 1) Check Extras/Google Support Library https://developer.android.com/topic/libraries/support-library/setup.html # 2) Check API 11 # Finally, click Install. -SOURCES += DaemonQT.cpp\ - mainwindow.cpp \ - ../../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 \ - ../../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 \ - ../../UPnP.cpp \ - ../../util.cpp \ - ../../i2pd.cpp +SOURCES += DaemonQT.cpp mainwindow.cpp \ + ../../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 ../../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 ../../UPnP.cpp ../../util.cpp ../../i2pd.cpp HEADERS += DaemonQT.h mainwindow.h \ - ../../HTTPServer.h ../../I2PControl.h ../../UPnP.h ../../Daemon.h ../../Config.h \ - ../../AddressBook.h \ - ../../api.h \ - ../../Base.h \ - ../../BOB.h \ - ../../ClientContext.h \ - ../../Crypto.h \ - ../../Datagram.h \ - ../../Destination.h \ - ../../Family.h \ - ../../FS.h \ - ../../Garlic.h \ - ../../HTTP.h \ - ../../HTTPProxy.h \ - ../../I2CP.h \ - ../../I2NPProtocol.h \ - ../../I2PEndian.h \ - ../../I2PService.h \ - ../../I2PTunnel.h \ - ../../Identity.h \ - ../../LeaseSet.h \ - ../../LittleBigEndian.h \ - ../../Log.h \ - ../../NetDb.h \ - ../../NetDbRequests.h \ - ../../NTCPSession.h \ - ../../Profiling.h \ - ../../Queue.h \ - ../../Reseed.h \ - ../../RouterContext.h \ - ../../RouterInfo.h \ - ../../SAM.h \ - ../../Signature.h \ - ../../SOCKS.h \ - ../../SSU.h \ - ../../SSUData.h \ - ../../SSUSession.h \ - ../../Streaming.h \ - ../../Timestamp.h \ - ../../TransitTunnel.h \ - ../../Transports.h \ - ../../TransportSession.h \ - ../../Tunnel.h \ - ../../TunnelBase.h \ - ../../TunnelConfig.h \ - ../../TunnelEndpoint.h \ - ../../TunnelGateway.h \ - ../../TunnelPool.h \ - ../../UPnP.h \ - ../../util.h \ - ../../version.h + ../../HTTPServer.h ../../I2PControl.h ../../UPnP.h ../../Daemon.h ../../Config.h \ + ../../AddressBook.h ../../api.h ../../Base.h ../../BOB.h ../../ClientContext.h \ + ../../Crypto.h ../../Datagram.h ../../Destination.h ../../Family.h ../../FS.h \ + ../../Garlic.h ../../HTTP.h ../../HTTPProxy.h ../../I2CP.h ../../I2NPProtocol.h \ + ../../I2PEndian.h ../../I2PService.h ../../I2PTunnel.h ../../Identity.h ../../LeaseSet.h \ + ../../LittleBigEndian.h ../../Log.h ../../NetDb.h ../../NetDbRequests.h ../../NTCPSession.h \ + ../../Profiling.h ../../Queue.h ../../Reseed.h ../../RouterContext.h ../../RouterInfo.h \ + ../../SAM.h ../../Signature.h ../../SOCKS.h ../../SSU.h ../../SSUData.h ../../SSUSession.h \ + ../../Streaming.h ../../Timestamp.h ../../TransitTunnel.h ../../Transports.h \ + ../../TransportSession.h ../../Tunnel.h ../../TunnelBase.h ../../TunnelConfig.h \ + ../../TunnelEndpoint.h ../../TunnelGateway.h ../../TunnelPool.h ../../UPnP.h \ + ../../util.h ../../version.h -FORMS += mainwindow.ui +FORMS += mainwindow.ui CONFIG += mobility -MOBILITY = +MOBILITY = LIBS += -lz android { -message("Using Android settings") -DEFINES += ANDROID=1 -DEFINES += __ANDROID__ + message("Using Android settings") + DEFINES += ANDROID=1 + DEFINES += __ANDROID__ -INCLUDEPATH += $$BOOST_PATH/boost_1_53_0/include \ - $$OPENSSL_PATH/openssl-1.0.2/include \ - $$MINIUPNP_PATH/miniupnp-2.0/include \ - $$IFADDRS_PATH -DISTFILES += \ - android/AndroidManifest.xml + INCLUDEPATH += $$BOOST_PATH/boost_1_53_0/include \ + $$OPENSSL_PATH/openssl-1.0.2/include \ + $$MINIUPNP_PATH/miniupnp-2.0/include \ + $$IFADDRS_PATH + DISTFILES += android/AndroidManifest.xml -ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android -SOURCES += $$IFADDRS_PATH/ifaddrs.c -HEADERS += $$IFADDRS_PATH/ifaddrs.h + SOURCES += $$IFADDRS_PATH/ifaddrs.c + HEADERS += $$IFADDRS_PATH/ifaddrs.h -equals(ANDROID_TARGET_ARCH, armeabi-v7a){ + equals(ANDROID_TARGET_ARCH, armeabi-v7a){ + DEFINES += ANDROID_ARM7A + # http://stackoverflow.com/a/30235934/529442 + LIBS += -L$$BOOST_PATH/boost_1_53_0/armeabi-v7a/lib \ + -lboost_system-gcc-mt-1_53 -lboost_date_time-gcc-mt-1_53 \ + -lboost_filesystem-gcc-mt-1_53 -lboost_program_options-gcc-mt-1_53 \ + -L$$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/ -lcrypto -lssl \ + -L$$MINIUPNP_PATH/miniupnp-2.0/armeabi-v7a/lib/ -lminiupnpc -DEFINES += ANDROID_ARM7A + PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ + $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl.a + DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include -# http://stackoverflow.com/a/30235934/529442 -LIBS += -L$$BOOST_PATH/boost_1_53_0/armeabi-v7a/lib \ --lboost_system-gcc-mt-1_53 \ --lboost_date_time-gcc-mt-1_53 \ --lboost_filesystem-gcc-mt-1_53 \ --lboost_program_options-gcc-mt-1_53 \ --L$$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/ -lcrypto -lssl \ --L$$MINIUPNP_PATH/miniupnp-2.0/armeabi-v7a/lib/ -lminiupnpc + ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto_1_0_0.so \ + $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl_1_0_0.so \ + $$MINIUPNP_PATH/miniupnp-2.0/armeabi-v7a/lib/libminiupnpc.so + } -PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ - $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl.a + equals(ANDROID_TARGET_ARCH, x86){ + # http://stackoverflow.com/a/30235934/529442 + LIBS += -L$$BOOST_PATH/boost_1_53_0/x86/lib \ + -lboost_system-gcc-mt-1_53 -lboost_date_time-gcc-mt-1_53 \ + -lboost_filesystem-gcc-mt-1_53 -lboost_program_options-gcc-mt-1_53 \ + -L$$OPENSSL_PATH/openssl-1.0.2/x86/lib/ -lcrypto -lssl \ + -L$$MINIUPNP_PATH/miniupnp-2.0/x86/lib/ -lminiupnpc -DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include + PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto.a \ + $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl.a -ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto_1_0_0.so \ - $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl_1_0_0.so \ - $$MINIUPNP_PATH/miniupnp-2.0/armeabi-v7a/lib/libminiupnpc.so -} -equals(ANDROID_TARGET_ARCH, x86){ -# http://stackoverflow.com/a/30235934/529442 -LIBS += -L$$BOOST_PATH/boost_1_53_0/x86/lib \ --lboost_system-gcc-mt-1_53 \ --lboost_date_time-gcc-mt-1_53 \ --lboost_filesystem-gcc-mt-1_53 \ --lboost_program_options-gcc-mt-1_53 \ --L$$OPENSSL_PATH/openssl-1.0.2/x86/lib/ -lcrypto -lssl \ --L$$MINIUPNP_PATH/miniupnp-2.0/x86/lib/ -lminiupnpc + DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include -PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto.a \ - $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl.a - -DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include - -ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto_1_0_0.so \ - $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl_1_0_0.so \ - $$MINIUPNP_PATH/miniupnp-2.0/x86/lib/libminiupnpc.so -} + ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto_1_0_0.so \ + $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl_1_0_0.so \ + $$MINIUPNP_PATH/miniupnp-2.0/x86/lib/libminiupnpc.so + } } linux:!android { -message("Using Linux settings") -LIBS += -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lminiupnpc + message("Using Linux settings") + LIBS += -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lminiupnpc } - !android:!symbian:!maemo5:!simulator { -message("Build with a system tray icon") -# see also http://doc.qt.io/qt-4.8/qt-desktop-systray-systray-pro.html for example on wince* -#sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS i2pd_qt.pro resources images -RESOURCES = i2pd.qrc -QT += xml -#INSTALLS += sources + message("Build with a system tray icon") + # see also http://doc.qt.io/qt-4.8/qt-desktop-systray-systray-pro.html for example on wince* + #sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS i2pd_qt.pro resources images + RESOURCES = i2pd.qrc + QT += xml + #INSTALLS += sources } - From 881d0652e71441dc3e0555ce95c5412d54b8f2dd Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 27 Jun 2016 13:00:00 +0000 Subject: [PATCH 1481/6300] * update debian package defaults --- debian/control | 1 + ...ble-aesni-by-default.patch => 01-tune-build-opts.patch} | 7 +++++-- debian/patches/series | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) rename debian/patches/{0001-disable-aesni-by-default.patch => 01-tune-build-opts.patch} (55%) diff --git a/debian/control b/debian/control index 78906ba4..ac6f5e28 100644 --- a/debian/control +++ b/debian/control @@ -8,6 +8,7 @@ Build-Depends: debhelper (>= 9.0.0), dpkg-dev (>= 1.16.1~), libboost-date-time-dev, libboost-filesystem-dev, libboost-program-options-dev, + libminiupnpc-dev, libssl-dev Standards-Version: 3.9.3 Homepage: https://github.com/PurpleI2P/i2pd diff --git a/debian/patches/0001-disable-aesni-by-default.patch b/debian/patches/01-tune-build-opts.patch similarity index 55% rename from debian/patches/0001-disable-aesni-by-default.patch rename to debian/patches/01-tune-build-opts.patch index eae44c8b..e0e24408 100644 --- a/debian/patches/0001-disable-aesni-by-default.patch +++ b/debian/patches/01-tune-build-opts.patch @@ -1,13 +1,16 @@ diff --git a/Makefile b/Makefile -index 2e86fd8..c1037af 100644 +index fe8ae7e..fc8abda 100644 --- a/Makefile +++ b/Makefile -@@ -9,7 +9,7 @@ DEPS := obj/make.dep +@@ -9,9 +9,9 @@ DEPS := obj/make.dep include filelist.mk -USE_AESNI := yes +USE_AESNI := no USE_STATIC := no +-USE_UPNP := no ++USE_UPNP := yes ifeq ($(UNAME),Darwin) + DAEMON_SRC += DaemonLinux.cpp diff --git a/debian/patches/series b/debian/patches/series index 1c9d0fbf..972d2a10 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1 +1 @@ -0001-disable-aesni-by-default.patch +01-tune-build-opts.patch From 4e7375c09c8fd2c644e54b78da6c82c1ad48564d Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 27 Jun 2016 13:00:00 +0000 Subject: [PATCH 1482/6300] * Addressbook.cpp : move storage creation to Start() --- AddressBook.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 5e7510a5..3cf2bc56 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -203,7 +203,7 @@ namespace client } //--------------------------------------------------------------------- - AddressBook::AddressBook (): m_Storage(new AddressBookFilesystemStorage), m_IsLoaded (false), m_IsDownloading (false), + AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), m_IsDownloading (false), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr) { } @@ -215,6 +215,8 @@ namespace client void AddressBook::Start () { + if (!m_Storage) + m_Storage = new AddressBookFilesystemStorage; m_Storage->Init(); LoadHosts (); /* try storage, then hosts.txt, then download */ StartSubscriptions (); From a973630cb4b8e9fc3e52d92ce0c4e628ace48a69 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 27 Jun 2016 13:00:00 +0000 Subject: [PATCH 1483/6300] * fix tests --- tests/test-http-req.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test-http-req.cpp b/tests/test-http-req.cpp index d5362622..c857ca24 100644 --- a/tests/test-http-req.cpp +++ b/tests/test-http-req.cpp @@ -22,7 +22,6 @@ int main() { assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); - assert(req->host == "inr.i2p"); assert(req->headers.size() == 3); assert(req->headers.count("Host") == 1); assert(req->headers.count("Accept") == 1); @@ -42,7 +41,6 @@ int main() { assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); - assert(req->host == ""); assert(req->headers.size() == 0); delete req; @@ -52,7 +50,7 @@ int main() { "\r\n"; len = strlen(buf); req = new HTTPReq; - assert((ret = req->parse(buf, len)) == -1); /* no host header */ + assert((ret = req->parse(buf, len)) > 0); delete req; /* test: parsing incomplete request */ @@ -76,7 +74,6 @@ int main() { assert((ret = req->parse(buf, len)) == len); /* no host header */ assert(req->method == "GET"); assert(req->uri == "http://inr.i2p"); - assert(req->host == "stats.i2p"); assert(req->headers.size() == 3); assert(req->headers.count("Host") == 1); assert(req->headers.count("Accept") == 1); From 646778227aff0beb9046359b4089e1660ea1cfa9 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 27 Jun 2016 13:00:00 +0000 Subject: [PATCH 1484/6300] * tune log messages --- NTCPSession.cpp | 4 ++-- NetDb.cpp | 2 +- SSU.cpp | 8 ++++---- SSUData.cpp | 2 +- SSUSession.cpp | 4 ++-- TransitTunnel.cpp | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index ae020f5f..8dc200b1 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -430,7 +430,7 @@ namespace transport } else { - LogPrint (eLogInfo, "NTCP: Server session from ", m_Socket.remote_endpoint (), " connected"); + LogPrint (eLogDebug, "NTCP: Server session from ", m_Socket.remote_endpoint (), " connected"); m_Server.AddNTCPSession (shared_from_this ()); Connected (); @@ -942,7 +942,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "NTCP: Connect error: ", ecode.message ()); + LogPrint (eLogError, "NTCP: Can't connect to ", conn->GetSocket ().remote_endpoint (), ": ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); conn->Terminate (); diff --git a/NetDb.cpp b/NetDb.cpp index d2afc50a..e94cc97d 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -671,7 +671,7 @@ namespace data if (!replyMsg) { - LogPrint (eLogWarning, "NetDb: Requested ", key, " not found. ", numExcluded, " excluded"); + LogPrint (eLogWarning, "NetDb: Requested ", key, " not found, ", numExcluded, " peers excluded"); // find or cleate response std::vector closestFloodfills; bool found = false; diff --git a/SSU.cpp b/SSU.cpp index ddfd1501..d635a7f9 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -231,7 +231,7 @@ namespace transport session = std::make_shared (*this, packet->from); session->WaitForConnect (); (*sessions)[packet->from] = session; - LogPrint (eLogInfo, "SSU: new session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); + LogPrint (eLogDebug, "SSU: new session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); } } session->ProcessNextMessage (packet->buf, packet->len, packet->from); @@ -312,7 +312,7 @@ namespace transport auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); sessions[remoteEndpoint] = session; // connect - LogPrint (eLogInfo, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", + LogPrint (eLogDebug, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); session->Connect (); } @@ -364,10 +364,10 @@ namespace transport } if (introducerSession) // session found - LogPrint (eLogInfo, "SSU: Session to introducer already exists"); + LogPrint (eLogWarning, "SSU: Session to introducer already exists"); else // create new { - LogPrint (eLogInfo, "SSU: Creating new session to introducer"); + LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost); boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); introducerSession = std::make_shared (*this, introducerEndpoint, router); m_Sessions[introducerEndpoint] = introducerSession; diff --git a/SSUData.cpp b/SSUData.cpp index 4ce7451d..2bd65682 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -241,7 +241,7 @@ namespace transport if (!msg->IsExpired ()) m_Handler.PutNextMessage (msg); else - LogPrint (eLogInfo, "SSU: message expired"); + LogPrint (eLogDebug, "SSU: message expired"); } else LogPrint (eLogWarning, "SSU: Message ", msgID, " already received"); diff --git a/SSUSession.cpp b/SSUSession.cpp index cf56ca15..9b480888 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -814,7 +814,7 @@ namespace transport if (!ecode) { // timeout expired - LogPrint (eLogWarning, "SSU: session was not established after ", SSU_CONNECT_TIMEOUT, " seconds"); + LogPrint (eLogWarning, "SSU: session with ", m_RemoteEndpoint, " was not established after ", SSU_CONNECT_TIMEOUT, " seconds"); Failed (); } } @@ -891,7 +891,7 @@ namespace transport { if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogWarning, "SSU: no activity for ", SSU_TERMINATION_TIMEOUT, " seconds"); + LogPrint (eLogWarning, "SSU: no activity with ", m_RemoteEndpoint, " for ", SSU_TERMINATION_TIMEOUT, " seconds"); Failed (); } } diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index 0d54fc11..dfe01a05 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -92,7 +92,7 @@ namespace tunnel { if (isEndpoint) { - LogPrint (eLogInfo, "TransitTunnel: endpoint ", receiveTunnelID, " created"); + LogPrint (eLogDebug, "TransitTunnel: endpoint ", receiveTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else if (isGateway) @@ -102,7 +102,7 @@ namespace tunnel } else { - LogPrint (eLogInfo, "TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created"); + LogPrint (eLogDebug, "TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } } From b668c4c302a8ba69234e067c061f9607353c5fc5 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 27 Jun 2016 13:00:00 +0000 Subject: [PATCH 1485/6300] * add global switch USE_UPNP to makefile --- Makefile | 1 + Makefile.homebrew | 2 +- Makefile.linux | 2 +- Makefile.mingw | 4 ++-- Makefile.osx | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 4cc313a9..fe8ae7e3 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ include filelist.mk USE_AESNI := yes USE_STATIC := no +USE_UPNP := no ifeq ($(UNAME),Darwin) DAEMON_SRC += DaemonLinux.cpp diff --git a/Makefile.homebrew b/Makefile.homebrew index 6ce513fe..f57f6495 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -8,7 +8,7 @@ INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include LDFLAGS = -L${SSLROOT}/lib -L${BOOSTROOT}/lib LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -ifeq ($(USE_UPNP),1) +ifeq ($(USE_UPNP),yes) LDFLAGS += -ldl CXXFLAGS += -DUSE_UPNP endif diff --git a/Makefile.linux b/Makefile.linux index 324d9467..1376260a 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -43,7 +43,7 @@ else endif # UPNP Support (miniupnpc 1.5 or 1.6) -ifeq ($(USE_UPNP),1) +ifeq ($(USE_UPNP),yes) LDFLAGS += -lminiupnpc CXXFLAGS += -DUSE_UPNP endif diff --git a/Makefile.mingw b/Makefile.mingw index 682221d1..5cf16bf5 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -9,7 +9,7 @@ LDFLAGS = -Wl,-rpath,/usr/local/lib \ -L/usr/local/lib # UPNP Support -ifeq ($(USE_UPNP),1) +ifeq ($(USE_UPNP),yes) CXXFLAGS += -DUSE_UPNP -DMINIUPNP_STATICLIB LDLIBS = -Wl,-Bstatic -lminiupnpc endif @@ -37,7 +37,7 @@ ifeq ($(USE_WIN32_APP), yes) DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif -ifeq ($(USE_AESNI),1) +ifeq ($(USE_AESNI),yes) CPU_FLAGS = -maes -DAESNI else CPU_FLAGS = -msse diff --git a/Makefile.osx b/Makefile.osx index ef236c9a..f40ce1af 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -5,7 +5,7 @@ INCFLAGS = -I/usr/local/include -I/usr/local/ssl/include LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib -L/usr/local/ssl/lib LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -ifeq ($(USE_UPNP),1) +ifeq ($(USE_UPNP),yes) LDFLAGS += -ldl CXXFLAGS += -DUSE_UPNP endif From 6b29d6b8dcb4834085e31d47654849321526d873 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 27 Jun 2016 13:00:00 +0000 Subject: [PATCH 1486/6300] * HTTPProxy.cpp : unwrap AsyncSockRead() --- HTTPProxy.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index af914b47..5b8aedde 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -82,13 +82,13 @@ namespace proxy { void HTTPReqHandler::AsyncSockRead() { LogPrint(eLogDebug, "HTTPProxy: async sock read"); - if(m_sock) { - m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), - std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); - } else { + if (!m_sock) { LogPrint(eLogError, "HTTPProxy: no socket for read"); + return; } + m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), + std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); } void HTTPReqHandler::Terminate() { From e2acc5581939eb61a44fb8bc78e7a3b7af3af5a2 Mon Sep 17 00:00:00 2001 From: hagen Date: Mon, 27 Jun 2016 13:00:00 +0000 Subject: [PATCH 1487/6300] * HTTPProxy.cpp : unwrap HandleStreamRequestComplete() --- HTTPProxy.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 5b8aedde..db58d1dc 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -335,20 +335,18 @@ namespace proxy { void HTTPReqHandler::HandleStreamRequestComplete (std::shared_ptr stream) { - if (stream) - { - if (Kill()) return; - LogPrint (eLogInfo, "HTTPProxy: New I2PTunnel connection"); - auto connection = std::make_shared(GetOwner(), m_sock, stream); - GetOwner()->AddHandler (connection); - connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); - Done(shared_from_this()); - } - else - { + if (!stream) { LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); HTTPRequestFailed("error when creating the stream, check logs"); + return; } + if (Kill()) + return; + LogPrint (eLogDebug, "HTTPProxy: New I2PTunnel connection"); + auto connection = std::make_shared(GetOwner(), m_sock, stream); + GetOwner()->AddHandler (connection); + connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); + Done (shared_from_this()); } HTTPProxy::HTTPProxy(const std::string& address, int port, std::shared_ptr localDestination): From 4cf44361697f4915f21a7b39e1537d70599cd7a3 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 09:47:53 -0400 Subject: [PATCH 1488/6300] initial meshnet mode --- Daemon.cpp | 5 +++++ Makefile | 5 +++++ Reseed.cpp | 22 ++++++++++++++-------- version.h | 5 +++++ 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index d34712a3..d6e37a8f 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -138,6 +138,11 @@ namespace i2p bool ipv6; i2p::config::GetOption("ipv6", ipv6); bool ipv4; i2p::config::GetOption("ipv4", ipv4); +#ifdef MESHNET + // manual override for meshnet + ipv4 = false; + ipv6 = true; +#endif bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); diff --git a/Makefile b/Makefile index 4cc313a9..025dbbcc 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ include filelist.mk USE_AESNI := yes USE_STATIC := no +USE_MESHNET := yes ifeq ($(UNAME),Darwin) DAEMON_SRC += DaemonLinux.cpp @@ -30,6 +31,10 @@ else # win32 mingw include Makefile.mingw endif +ifeq ($(USE_MESHNET),yes) + CXXFLAGS += -DMESHNET +endif + all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) mk_obj_dir: diff --git a/Reseed.cpp b/Reseed.cpp index 722d7eff..096035da 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -20,20 +20,26 @@ namespace i2p { namespace data { - static std::vector httpsReseedHostList = + static std::vector httpsReseedHostList = { +#ifdef MESHNET + // meshnet i2p reseeds + "https://reseed.i2p.rocks:8443/" +#else + // mainline i2p reseeds "https://reseed.i2p-projekt.de/", // Only HTTPS - "https://i2p.mooo.com/netDb/", - "https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required + "https://i2p.mooo.com/netDb/", + "https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required "https://us.reseed.i2p2.no:444/", - "https://uk.reseed.i2p2.no:444/", + "https://uk.reseed.i2p2.no:444/", "https://i2p.manas.ca:8443/", "https://i2p-0.manas.ca:8443/", - "https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support - "https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support - "https://download.xxlspeed.com/" // Only HTTPS and SU3 (v3) support + "https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support + "https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support + "https://download.xxlspeed.com/" // Only HTTPS and SU3 (v3) support +#endif }; - + Reseeder::Reseeder() { } diff --git a/version.h b/version.h index 46d170db..54fc295f 100644 --- a/version.h +++ b/version.h @@ -12,7 +12,12 @@ #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) #define VERSION I2PD_VERSION + +#ifdef MESHNET +#define I2PD_NET_ID 3 +#else #define I2PD_NET_ID 2 +#endif #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 From a3b08654b4631eae083051f10eacb91e3d72b9b7 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 10:24:37 -0400 Subject: [PATCH 1489/6300] try adding ipv6 only mode for ssu --- SSU.cpp | 32 +++++++++++++++++++++++++++----- SSU.h | 4 +++- Transports.cpp | 9 ++++++--- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index ddfd1501..edc5b898 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -10,12 +10,31 @@ namespace i2p { namespace transport { - SSUServer::SSUServer (int port): m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr), + + SSUServer::SSUServer (const boost::asio::ip::address & addr, int port): + m_OnlyV6(true), m_IsRunning(false), + m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr), + m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService), + m_EndpointV6 (addr, port), + m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversService), + m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service) + { + m_SocketV6.open (boost::asio::ip::udp::v6()); + m_SocketV6.set_option (boost::asio::ip::v6_only (true)); + m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (65535)); + m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (65535)); + m_SocketV6.bind (m_EndpointV6); + } + + SSUServer::SSUServer (int port): + m_OnlyV6(false), m_IsRunning(false), + m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr), m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService), m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversService), m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service) { + m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (65535)); m_Socket.set_option (boost::asio::socket_base::send_buffer_size (65535)); if (context.SupportsV6 ()) @@ -35,13 +54,16 @@ namespace transport void SSUServer::Start () { m_IsRunning = true; - m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); - m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); - m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); + m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); + if (!m_OnlyV6) + { + m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); + m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); + } if (context.SupportsV6 ()) { m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this)); - m_ReceiversService.post (std::bind (&SSUServer::ReceiveV6, this)); + m_ReceiversService.post (std::bind (&SSUServer::ReceiveV6, this)); } SchedulePeerTestsCleanupTimer (); ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers diff --git a/SSU.h b/SSU.h index 8ee58ffa..5ef60aca 100644 --- a/SSU.h +++ b/SSU.h @@ -37,6 +37,7 @@ namespace transport public: SSUServer (int port); + SSUServer (const boost::asio::ip::address & addr, int port); // ipv6 only constructor ~SSUServer (); void Start (); void Stop (); @@ -93,8 +94,9 @@ namespace transport uint64_t creationTime; PeerTestParticipant role; std::shared_ptr session; // for Bob to Alice - }; + }; + bool m_OnlyV6; bool m_IsRunning; std::thread * m_Thread, * m_ThreadV6, * m_ReceiversThread; boost::asio::io_service m_Service, m_ServiceV6, m_ReceiversService; diff --git a/Transports.cpp b/Transports.cpp index b15ec56f..d41690f8 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -120,11 +120,14 @@ namespace transport m_NTCPServer->Start (); } - if (address->transportStyle == RouterInfo::eTransportSSU && address->host.is_v4 ()) + if (address->transportStyle == RouterInfo::eTransportSSU) { if (!m_SSUServer) - { - m_SSUServer = new SSUServer (address->port); + { + if (address->host.is_v4()) + m_SSUServer = new SSUServer (address->port); + else + m_SSUServer = new SSUServer (address->host, address->port); LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port); m_SSUServer->Start (); DetectExternalIP (); From 556bfb752aee503ccc2feb1acc848d42ac7b51d8 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 10:33:14 -0400 Subject: [PATCH 1490/6300] disable meshnet by default, use `make USE_MESHNET=yes` to build for cjdns --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 025dbbcc..0f6cd8f0 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ include filelist.mk USE_AESNI := yes USE_STATIC := no -USE_MESHNET := yes +USE_MESHNET := no ifeq ($(UNAME),Darwin) DAEMON_SRC += DaemonLinux.cpp From 7868e1527e2b3ffed6d47e324a0766342655489c Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 11:08:23 -0400 Subject: [PATCH 1491/6300] try fixing ipv6 ssu --- SSUSession.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index cf56ca15..17e6c305 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -20,14 +20,14 @@ namespace transport if (router) { // we are client - auto address = router->GetSSUAddress (); + auto address = router->GetSSUAddress (false); if (address) m_IntroKey = address->key; m_Data.AdjustPacketSize (router); // mtu } else { // we are server - auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); + auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); if (address) m_IntroKey = address->key; } m_CreationTime = i2p::util::GetSecondsSinceEpoch (); @@ -108,7 +108,7 @@ namespace transport else { // try own intro key - auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); + auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); if (!address) { LogPrint (eLogInfo, "SSU is not supported"); @@ -366,7 +366,7 @@ namespace transport void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce) { - auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); + auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); if (!address) { LogPrint (eLogInfo, "SSU is not supported"); From 866cf940da076c183114ee37b089f7ed9f0e2a3c Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 13:15:05 -0400 Subject: [PATCH 1492/6300] make always reachable when in meshnet mode --- Daemon.cpp | 5 +++++ SSUSession.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Daemon.cpp b/Daemon.cpp index d6e37a8f..f8862de9 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -197,6 +197,11 @@ namespace i2p i2p::context.SetFamily (family); if (family.length () > 0) LogPrint(eLogInfo, "Daemon: family set to ", family); + +#ifdef MESHNET + // always reachable in meshnet mode, no NAT + i2p::context.SetStatus(eRouterStatusOK); +#endif return true; } diff --git a/SSUSession.cpp b/SSUSession.cpp index 17e6c305..ce70d637 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -1079,7 +1079,7 @@ namespace transport { // we are Alice LogPrint (eLogDebug, "SSU: sending peer test"); - auto address = i2p::context.GetRouterInfo ().GetSSUAddress (); + auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); if (!address) { LogPrint (eLogInfo, "SSU is not supported. Can't send peer test"); From fff3587d99bdaf648a0f3d6d2e2aa71512f819b5 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 13:20:21 -0400 Subject: [PATCH 1493/6300] only set as testing when not in meshnet mode --- Transports.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Transports.cpp b/Transports.cpp index d41690f8..5c0717b7 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -472,7 +472,10 @@ namespace transport { if (m_SSUServer) { +#ifndef MESHNET i2p::context.SetStatus (eRouterStatusTesting); +#endif + for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomPeerTestRouter (); From 926ffe25810c61fe38290d9435c65af261543165 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 13:46:14 -0400 Subject: [PATCH 1494/6300] change default addressbook for meshnet mode --- AddressBook.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AddressBook.h b/AddressBook.h index 61b82f4b..04b25021 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -18,7 +18,11 @@ namespace i2p { namespace client { +#ifdef MESHNET + const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://opo57rwxz27frnqxrwiyr6snkybuyetv25fyd25y6se6imj54xfq.b32.i2p/hosts.txt"; +#else const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt"; +#endif const int INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT = 3; // in minutes const int INITIAL_SUBSCRIPTION_RETRY_TIMEOUT = 1; // in minutes const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours) From cf3bab996ef87ed6cee6675d5bfa92e3128a7b2d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 14:00:04 -0400 Subject: [PATCH 1495/6300] when routers < 5 and in meshnet mode do not select random peers --- TunnelPool.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 5e7e8ec4..02adf8a1 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -331,6 +331,7 @@ namespace tunnel if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); auto prevHop = i2p::context.GetSharedRouterInfo (); int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; +#ifndef MESHNET if (i2p::transport::transports.GetNumPeers () > 25) { auto r = i2p::transport::transports.GetRandomPeer (); @@ -341,7 +342,7 @@ namespace tunnel numHops--; } } - +#endif for (int i = 0; i < numHops; i++) { auto hop = SelectNextHop (prevHop); From 32644ddada8b0ddb2e9dccb70a82cee3e33fc81f Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 14:16:29 -0400 Subject: [PATCH 1496/6300] try fixing duplicate Routers In tunnel path --- TunnelPool.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 02adf8a1..87c02682 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -322,7 +322,7 @@ namespace tunnel i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop); if (!hop || hop->GetProfile ()->IsBad ()) - hop = i2p::data::netdb.GetRandomRouter (); + hop = i2p::data::netdb.GetRandomRouter (prevHop); return hop; } @@ -331,7 +331,6 @@ namespace tunnel if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); auto prevHop = i2p::context.GetSharedRouterInfo (); int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; -#ifndef MESHNET if (i2p::transport::transports.GetNumPeers () > 25) { auto r = i2p::transport::transports.GetRandomPeer (); @@ -342,7 +341,6 @@ namespace tunnel numHops--; } } -#endif for (int i = 0; i < numHops; i++) { auto hop = SelectNextHop (prevHop); From 1ebcbd5b0ea7af3c4230c5d506da52fa28ed6c15 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 15:06:15 -0400 Subject: [PATCH 1497/6300] use smaller mtu for meshnet mode --- SSUData.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SSUData.h b/SSUData.h index 02135350..f5e52947 100644 --- a/SSUData.h +++ b/SSUData.h @@ -18,7 +18,11 @@ namespace transport { const size_t SSU_MTU_V4 = 1484; + #ifdef MESHNET + const size_t SSU_MTU_V6 = 1304; + #else const size_t SSU_MTU_V6 = 1472; + #endif const size_t IPV4_HEADER_SIZE = 20; const size_t IPV6_HEADER_SIZE = 40; const size_t UDP_HEADER_SIZE = 8; From 07dca9bd166a7351d2751fbefde55af7a730676f Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 15:28:26 -0400 Subject: [PATCH 1498/6300] tweak ssu mtu again for meshnet --- SSUData.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SSUData.h b/SSUData.h index f5e52947..bfc75128 100644 --- a/SSUData.h +++ b/SSUData.h @@ -19,7 +19,7 @@ namespace transport const size_t SSU_MTU_V4 = 1484; #ifdef MESHNET - const size_t SSU_MTU_V6 = 1304; + const size_t SSU_MTU_V6 = 1286; #else const size_t SSU_MTU_V6 = 1472; #endif From 614c1306f6dce78bddd9f34b2123eacdda7ab9a3 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 16:40:46 -0400 Subject: [PATCH 1499/6300] use correct netid when using separate test network --- I2NPProtocol.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index c47a1657..425668c3 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -11,6 +11,7 @@ #include "Transports.h" #include "Garlic.h" #include "I2NPProtocol.h" +#include "version.h" using namespace i2p::transport; @@ -101,7 +102,7 @@ namespace i2p { RAND_bytes ((uint8_t *)&msgID, 4); htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID); - htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, 2); // netID = 2 + htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, I2PD_NET_ID); // netID = 2 } m->len += DELIVERY_STATUS_SIZE; m->FillI2NPMessageHeader (eI2NPDeliveryStatus); From 9cfc61cd45e4b738f80086add09d851cebace3c6 Mon Sep 17 00:00:00 2001 From: MXPLRS|Kirill Date: Tue, 28 Jun 2016 00:00:54 +0300 Subject: [PATCH 1500/6300] fixed #546 --- DaemonWin32.cpp | 226 ++++++++++++++++++++++++------------------------ HTTPServer.cpp | 8 +- 2 files changed, 120 insertions(+), 114 deletions(-) diff --git a/DaemonWin32.cpp b/DaemonWin32.cpp index 3afb70ce..6eb43dc0 100644 --- a/DaemonWin32.cpp +++ b/DaemonWin32.cpp @@ -1,113 +1,115 @@ -#include -#include "Config.h" -#include "Daemon.h" -#include "util.h" -#include "Log.h" - -#ifdef _WIN32 - -#include "Win32/Win32Service.h" -#ifdef WIN32_APP -#include "Win32/Win32App.h" -#endif - -namespace i2p -{ - namespace util - { - bool DaemonWin32::init(int argc, char* argv[]) - { - setlocale(LC_CTYPE, ""); - SetConsoleCP(1251); - SetConsoleOutputCP(1251); - - if (!Daemon_Singleton::init(argc, argv)) - return false; - - std::string serviceControl; i2p::config::GetOption("svcctl", serviceControl); - if (serviceControl == "install") - { - LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service"); - InstallService( - SERVICE_NAME, // Name of service - SERVICE_DISPLAY_NAME, // Name to display - SERVICE_START_TYPE, // Service start type - SERVICE_DEPENDENCIES, // Dependencies - SERVICE_ACCOUNT, // Service running account - SERVICE_PASSWORD // Password of the account - ); - return false; - } - else if (serviceControl == "remove") - { - LogPrint(eLogInfo, "WinSVC: uninstalling ", SERVICE_NAME, " service"); - UninstallService(SERVICE_NAME); - return false; - } - +#include +#include +#include "Config.h" +#include "Daemon.h" +#include "util.h" +#include "Log.h" + +#ifdef _WIN32 + +#include "Win32/Win32Service.h" +#ifdef WIN32_APP +#include "Win32/Win32App.h" +#endif + +namespace i2p +{ + namespace util + { + bool DaemonWin32::init(int argc, char* argv[]) + { + setlocale(LC_CTYPE, ""); + SetConsoleCP(1251); + SetConsoleOutputCP(1251); + setlocale(LC_ALL, "Russian"); + + if (!Daemon_Singleton::init(argc, argv)) + return false; + + std::string serviceControl; i2p::config::GetOption("svcctl", serviceControl); + if (serviceControl == "install") + { + LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service"); + InstallService( + SERVICE_NAME, // Name of service + SERVICE_DISPLAY_NAME, // Name to display + SERVICE_START_TYPE, // Service start type + SERVICE_DEPENDENCIES, // Dependencies + SERVICE_ACCOUNT, // Service running account + SERVICE_PASSWORD // Password of the account + ); + return false; + } + else if (serviceControl == "remove") + { + LogPrint(eLogInfo, "WinSVC: uninstalling ", SERVICE_NAME, " service"); + UninstallService(SERVICE_NAME); + return false; + } + if (isDaemon) - { - LogPrint(eLogDebug, "Daemon: running as service"); - I2PService service(SERVICE_NAME); - if (!I2PService::Run(service)) - { - LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); - return false; - } - return false; - } - else - LogPrint(eLogDebug, "Daemon: running as user"); - - return true; - } - - bool DaemonWin32::start() - { - setlocale(LC_CTYPE, ""); - SetConsoleCP(1251); - SetConsoleOutputCP(1251); - setlocale(LC_ALL, "Russian"); -#ifdef WIN32_APP - if (!i2p::win32::StartWin32App ()) return false; - - // override log - i2p::config::SetOption("log", std::string ("file")); -#endif - bool ret = Daemon_Singleton::start(); - if (ret && i2p::log::Logger().GetLogType() == eLogFile) - { - // TODO: find out where this garbage to console comes from - SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE); - SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); - } - bool insomnia; i2p::config::GetOption("insomnia", insomnia); - if (insomnia) - SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); - return ret; - } - - bool DaemonWin32::stop() - { -#ifdef WIN32_APP - i2p::win32::StopWin32App (); -#endif - return Daemon_Singleton::stop(); - } - - void DaemonWin32::run () - { -#ifdef WIN32_APP - i2p::win32::RunWin32App (); -#else - while (running) - { - std::this_thread::sleep_for (std::chrono::seconds(1)); - } - -#endif - } - } -} - -#endif + { + LogPrint(eLogDebug, "Daemon: running as service"); + I2PService service(SERVICE_NAME); + if (!I2PService::Run(service)) + { + LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); + return false; + } + return false; + } + else + LogPrint(eLogDebug, "Daemon: running as user"); + + return true; + } + + bool DaemonWin32::start() + { + setlocale(LC_CTYPE, ""); + SetConsoleCP(1251); + SetConsoleOutputCP(1251); + setlocale(LC_ALL, "Russian"); +#ifdef WIN32_APP + if (!i2p::win32::StartWin32App ()) return false; + + // override log + i2p::config::SetOption("log", std::string ("file")); +#endif + bool ret = Daemon_Singleton::start(); + if (ret && i2p::log::Logger().GetLogType() == eLogFile) + { + // TODO: find out where this garbage to console comes from + SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE); + SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); + } + bool insomnia; i2p::config::GetOption("insomnia", insomnia); + if (insomnia) + SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); + return ret; + } + + bool DaemonWin32::stop() + { +#ifdef WIN32_APP + i2p::win32::StopWin32App (); +#endif + return Daemon_Singleton::stop(); + } + + void DaemonWin32::run () + { +#ifdef WIN32_APP + i2p::win32::RunWin32App (); +#else + while (running) + { + std::this_thread::sleep_for (std::chrono::seconds(1)); + } + +#endif + } + } +} + +#endif diff --git a/HTTPServer.cpp b/HTTPServer.cpp index f35e02d3..be3b9b58 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -126,8 +126,12 @@ namespace http { s << "\r\n" "\r\n" /* TODO: Add support for locale */ - " \r\n" - " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ + " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ +#if (!defined(WIN32)) + " \r\n" +#else + " \r\n" +#endif " \r\n" " Purple I2P " VERSION " Webconsole\r\n" << cssStyles << From 24d616672b374bc8913d1501509e08e46f1feae2 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 17:11:03 -0400 Subject: [PATCH 1501/6300] revert daemon.cpp change --- Daemon.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index f8862de9..7854398c 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -197,12 +197,6 @@ namespace i2p i2p::context.SetFamily (family); if (family.length () > 0) LogPrint(eLogInfo, "Daemon: family set to ", family); - -#ifdef MESHNET - // always reachable in meshnet mode, no NAT - i2p::context.SetStatus(eRouterStatusOK); -#endif - return true; } From afe81dcdbed504448cdbe7b7cc199851d0c9399d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 17:25:29 -0400 Subject: [PATCH 1502/6300] add logging --- Garlic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Garlic.cpp b/Garlic.cpp index c1100f64..3253cbad 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -523,7 +523,7 @@ namespace garlic tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg); } else - LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); + LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove given tunnelID=", gwTunnel); break; } case eGarlicDeliveryTypeRouter: From 34a90f442e0940947d7054a882779e921fdab127 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 17:37:31 -0400 Subject: [PATCH 1503/6300] try shooting in the dark for workarround --- Garlic.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index 3253cbad..3d61b4ca 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -517,13 +517,17 @@ namespace garlic std::shared_ptr tunnel; if (from && from->GetTunnelPool ()) tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel (); + if (!tunnel) + { + tunnel = i2p::context::GetExploratoryPool()->GetNextOutboundTunnel(); + } if (tunnel) // we have send it through an outbound tunnel { auto msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from); tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg); - } + } else - LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove given tunnelID=", gwTunnel); + LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove given tunnelID=", gwTunnel); break; } case eGarlicDeliveryTypeRouter: From 06daa8bb0effa576155c3eefdbc7b2e12066506b Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 27 Jun 2016 17:39:13 -0400 Subject: [PATCH 1504/6300] try shooting in the dark for workarround --- Garlic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Garlic.cpp b/Garlic.cpp index 3d61b4ca..b092c2fe 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -519,7 +519,7 @@ namespace garlic tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel (); if (!tunnel) { - tunnel = i2p::context::GetExploratoryPool()->GetNextOutboundTunnel(); + tunnel = GetTunnelPool()->GetNextOutboundTunnel(); } if (tunnel) // we have send it through an outbound tunnel { From 6350f5e6e80dbaa9ba8b0855b9580f1bc47190c4 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 28 Jun 2016 00:00:00 +0000 Subject: [PATCH 1505/6300] * Base.cpp : extract gzip classes to separate file --- Base.cpp | 93 ------------------------------------- Base.h | 36 --------------- Gzip.cpp | 108 +++++++++++++++++++++++++++++++++++++++++++ Gzip.h | 44 ++++++++++++++++++ NTCPSession.cpp | 2 +- NetDb.cpp | 2 +- NetDb.h | 2 + build/CMakeLists.txt | 1 + filelist.mk | 2 +- 9 files changed, 158 insertions(+), 132 deletions(-) create mode 100644 Gzip.cpp create mode 100644 Gzip.h diff --git a/Base.cpp b/Base.cpp index 766eaab9..600afebc 100644 --- a/Base.cpp +++ b/Base.cpp @@ -283,99 +283,6 @@ namespace data } return ret; } - - GzipInflator::GzipInflator (): m_IsDirty (false) - { - memset (&m_Inflator, 0, sizeof (m_Inflator)); - inflateInit2 (&m_Inflator, MAX_WBITS + 16); // gzip - } - - GzipInflator::~GzipInflator () - { - inflateEnd (&m_Inflator); - } - - size_t GzipInflator::Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen) - { - if (m_IsDirty) inflateReset (&m_Inflator); - m_IsDirty = true; - m_Inflator.next_in = const_cast(in); - m_Inflator.avail_in = inLen; - m_Inflator.next_out = out; - m_Inflator.avail_out = outLen; - int err; - if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END) { - return outLen - m_Inflator.avail_out; - } - return 0; - } - - bool GzipInflator::Inflate (const uint8_t * in, size_t inLen, std::ostream& s) - { - m_IsDirty = true; - uint8_t * out = new uint8_t[GZIP_CHUNK_SIZE]; - m_Inflator.next_in = const_cast(in); - m_Inflator.avail_in = inLen; - int ret; - do - { - m_Inflator.next_out = out; - m_Inflator.avail_out = GZIP_CHUNK_SIZE; - ret = inflate (&m_Inflator, Z_NO_FLUSH); - if (ret < 0) - { - inflateEnd (&m_Inflator); - s.setstate(std::ios_base::failbit); - break; - } - s.write ((char *)out, GZIP_CHUNK_SIZE - m_Inflator.avail_out); - } - while (!m_Inflator.avail_out); // more data to read - delete[] out; - return ret == Z_STREAM_END || ret < 0; - } - - void GzipInflator::Inflate (std::istream& in, std::ostream& out) - { - uint8_t * buf = new uint8_t[GZIP_CHUNK_SIZE]; - while (!in.eof ()) - { - in.read ((char *)buf, GZIP_CHUNK_SIZE); - Inflate (buf, in.gcount (), out); - } - delete[] buf; - } - - GzipDeflator::GzipDeflator (): m_IsDirty (false) - { - memset (&m_Deflator, 0, sizeof (m_Deflator)); - deflateInit2 (&m_Deflator, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); // 15 + 16 sets gzip - } - - GzipDeflator::~GzipDeflator () - { - deflateEnd (&m_Deflator); - } - - void GzipDeflator::SetCompressionLevel (int level) - { - deflateParams (&m_Deflator, level, Z_DEFAULT_STRATEGY); - } - - size_t GzipDeflator::Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen) - { - if (m_IsDirty) deflateReset (&m_Deflator); - m_IsDirty = true; - m_Deflator.next_in = const_cast(in); - m_Deflator.avail_in = inLen; - m_Deflator.next_out = out; - m_Deflator.avail_out = outLen; - int err; - if ((err = deflate (&m_Deflator, Z_FINISH)) == Z_STREAM_END) { - return outLen - m_Deflator.avail_out; - } /* else */ - return 0; - } } } diff --git a/Base.h b/Base.h index 0c38725e..5bcb8f16 100644 --- a/Base.h +++ b/Base.h @@ -4,7 +4,6 @@ #include #include #include -#include #include namespace i2p @@ -93,41 +92,6 @@ namespace data uint64_t ll[sz/8]; }; }; - - const size_t GZIP_CHUNK_SIZE = 16384; - class GzipInflator - { - public: - - GzipInflator (); - ~GzipInflator (); - - size_t Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen); - bool Inflate (const uint8_t * in, size_t inLen, std::ostream& s); - // return true when finshed or error, s failbit will be set in case of error - void Inflate (std::istream& in, std::ostream& out); - - private: - - z_stream m_Inflator; - bool m_IsDirty; - }; - - class GzipDeflator - { - public: - - GzipDeflator (); - ~GzipDeflator (); - - void SetCompressionLevel (int level); - size_t Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen); - - private: - - z_stream m_Deflator; - bool m_IsDirty; - }; } } diff --git a/Gzip.cpp b/Gzip.cpp new file mode 100644 index 00000000..da9f06b1 --- /dev/null +++ b/Gzip.cpp @@ -0,0 +1,108 @@ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include /* memset */ +#include + +#include "Gzip.h" + +namespace i2p { +namespace data { + const size_t GZIP_CHUNK_SIZE = 16384; + + GzipInflator::GzipInflator (): m_IsDirty (false) + { + memset (&m_Inflator, 0, sizeof (m_Inflator)); + inflateInit2 (&m_Inflator, MAX_WBITS + 16); // gzip + } + + GzipInflator::~GzipInflator () + { + inflateEnd (&m_Inflator); + } + + size_t GzipInflator::Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen) + { + if (m_IsDirty) inflateReset (&m_Inflator); + m_IsDirty = true; + m_Inflator.next_in = const_cast(in); + m_Inflator.avail_in = inLen; + m_Inflator.next_out = out; + m_Inflator.avail_out = outLen; + int err; + if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END) { + return outLen - m_Inflator.avail_out; + } + return 0; + } + + void GzipInflator::Inflate (const uint8_t * in, size_t inLen, std::ostream& os) + { + m_IsDirty = true; + uint8_t * out = new uint8_t[GZIP_CHUNK_SIZE]; + m_Inflator.next_in = const_cast(in); + m_Inflator.avail_in = inLen; + int ret; + do { + m_Inflator.next_out = out; + m_Inflator.avail_out = GZIP_CHUNK_SIZE; + ret = inflate (&m_Inflator, Z_NO_FLUSH); + if (ret < 0) { + inflateEnd (&m_Inflator); + os.setstate(std::ios_base::failbit); + break; + } + os.write ((char *)out, GZIP_CHUNK_SIZE - m_Inflator.avail_out); + } while (!m_Inflator.avail_out); // more data to read + delete[] out; + } + + void GzipInflator::Inflate (std::istream& in, std::ostream& out) + { + uint8_t * buf = new uint8_t[GZIP_CHUNK_SIZE]; + while (!in.eof ()) + { + in.read ((char *) buf, GZIP_CHUNK_SIZE); + Inflate (buf, in.gcount (), out); + } + delete[] buf; + } + + GzipDeflator::GzipDeflator (): m_IsDirty (false) + { + memset (&m_Deflator, 0, sizeof (m_Deflator)); + deflateInit2 (&m_Deflator, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); // 15 + 16 sets gzip + } + + GzipDeflator::~GzipDeflator () + { + deflateEnd (&m_Deflator); + } + + void GzipDeflator::SetCompressionLevel (int level) + { + deflateParams (&m_Deflator, level, Z_DEFAULT_STRATEGY); + } + + size_t GzipDeflator::Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen) + { + if (m_IsDirty) deflateReset (&m_Deflator); + m_IsDirty = true; + m_Deflator.next_in = const_cast(in); + m_Deflator.avail_in = inLen; + m_Deflator.next_out = out; + m_Deflator.avail_out = outLen; + int err; + if ((err = deflate (&m_Deflator, Z_FINISH)) == Z_STREAM_END) { + return outLen - m_Deflator.avail_out; + } /* else */ + return 0; + } +} // data +} // i2p diff --git a/Gzip.h b/Gzip.h new file mode 100644 index 00000000..35661abe --- /dev/null +++ b/Gzip.h @@ -0,0 +1,44 @@ +#ifndef GZIP_H__ +#define GZIP_H__ + +#include + +namespace i2p { +namespace data { + class GzipInflator + { + public: + + GzipInflator (); + ~GzipInflator (); + + size_t Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen); + /** @note @a os failbit will be set in case of error */ + void Inflate (const uint8_t * in, size_t inLen, std::ostream& os); + void Inflate (std::istream& in, std::ostream& out); + + private: + + z_stream m_Inflator; + bool m_IsDirty; + }; + + class GzipDeflator + { + public: + + GzipDeflator (); + ~GzipDeflator (); + + void SetCompressionLevel (int level); + size_t Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen); + + private: + + z_stream m_Deflator; + bool m_IsDirty; + }; +} // data +} // i2p + +#endif /* GZIP_H__ */ diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 8dc200b1..953c0707 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -1,6 +1,6 @@ #include #include -#include + #include "I2PEndian.h" #include "Base.h" #include "Crypto.h" diff --git a/NetDb.cpp b/NetDb.cpp index e94cc97d..937ea830 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -2,7 +2,7 @@ #include #include #include -#include + #include "I2PEndian.h" #include "Base.h" #include "Crypto.h" diff --git a/NetDb.h b/NetDb.h index 1c062358..823dbb54 100644 --- a/NetDb.h +++ b/NetDb.h @@ -8,7 +8,9 @@ #include #include #include + #include "Base.h" +#include "Gzip.h" #include "FS.h" #include "Queue.h" #include "I2NPProtocol.h" diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 61e05e83..3f5f599f 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -21,6 +21,7 @@ set (LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/Config.cpp" "${CMAKE_SOURCE_DIR}/Crypto.cpp" "${CMAKE_SOURCE_DIR}/Garlic.cpp" + "${CMAKE_SOURCE_DIR}/Gzip.cpp" "${CMAKE_SOURCE_DIR}/I2NPProtocol.cpp" "${CMAKE_SOURCE_DIR}/Identity.cpp" "${CMAKE_SOURCE_DIR}/LeaseSet.cpp" diff --git a/filelist.mk b/filelist.mk index 8abf0b4b..d8d74252 100644 --- a/filelist.mk +++ b/filelist.mk @@ -1,5 +1,5 @@ LIB_SRC = \ - Crypto.cpp Datagram.cpp Garlic.cpp I2NPProtocol.cpp LeaseSet.cpp \ + Gzip.cpp Crypto.cpp Datagram.cpp Garlic.cpp I2NPProtocol.cpp LeaseSet.cpp \ Log.cpp NTCPSession.cpp NetDb.cpp NetDbRequests.cpp Profiling.cpp \ Reseed.cpp RouterContext.cpp RouterInfo.cpp Signature.cpp SSU.cpp \ SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp TransitTunnel.cpp \ From d838ce85c3caa05bcc2fd57256de58bba0c50a8b Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 28 Jun 2016 00:00:00 +0000 Subject: [PATCH 1506/6300] * Base.h : extract Tag template class to separate header --- Base.cpp | 2 ++ Base.h | 81 +++---------------------------------------------------- Crypto.h | 2 ++ Tag.h | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 77 deletions(-) create mode 100644 Tag.h diff --git a/Base.cpp b/Base.cpp index 600afebc..35aae437 100644 --- a/Base.cpp +++ b/Base.cpp @@ -1,4 +1,6 @@ #include +#include + #include "Base.h" namespace i2p diff --git a/Base.h b/Base.h index 5bcb8f16..66192d1b 100644 --- a/Base.h +++ b/Base.h @@ -2,14 +2,11 @@ #define BASE_H__ #include -#include #include #include -namespace i2p -{ -namespace data -{ +namespace i2p { +namespace data { size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len); size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len ); const char * GetBase32SubstitutionTable (); @@ -22,77 +19,7 @@ namespace data Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes */ size_t Base64EncodingBufferSize(const size_t input_size); - - template - class Tag - { - public: - - Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); }; - Tag (const Tag& ) = default; -#ifndef _WIN32 // FIXME!!! msvs 2013 can't compile it - Tag (Tag&& ) = default; -#endif - Tag () = default; - - Tag& operator= (const Tag& ) = default; -#ifndef _WIN32 - Tag& operator= (Tag&& ) = default; -#endif - - uint8_t * operator()() { return m_Buf; }; - const uint8_t * operator()() const { return m_Buf; }; - - operator uint8_t * () { return m_Buf; }; - operator const uint8_t * () const { return m_Buf; }; - - const uint64_t * GetLL () const { return ll; }; - - bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); }; - bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; }; - - bool IsZero () const - { - for (int i = 0; i < sz/8; i++) - if (ll[i]) return false; - return true; - } - - std::string ToBase64 () const - { - char str[sz*2]; - int l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2); - str[l] = 0; - return std::string (str); - } - - std::string ToBase32 () const - { - char str[sz*2]; - int l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2); - str[l] = 0; - return std::string (str); - } - - void FromBase32 (const std::string& s) - { - i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); - } - - void FromBase64 (const std::string& s) - { - i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); - } - - private: - - union // 8 bytes alignment - { - uint8_t m_Buf[sz]; - uint64_t ll[sz/8]; - }; - }; -} -} +} // data +} // i2p #endif diff --git a/Crypto.h b/Crypto.h index 7ce202ce..a66f51b7 100644 --- a/Crypto.h +++ b/Crypto.h @@ -9,7 +9,9 @@ #include #include #include + #include "Base.h" +#include "Tag.h" namespace i2p { diff --git a/Tag.h b/Tag.h new file mode 100644 index 00000000..e3432501 --- /dev/null +++ b/Tag.h @@ -0,0 +1,82 @@ +#ifndef TAG_H__ +#define TAG_H__ + +#include /* memcpy */ + +#include "Base.h" + +namespace i2p { +namespace data { + template + class Tag + { + public: + + Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); }; + Tag (const Tag& ) = default; +#ifndef _WIN32 // FIXME!!! msvs 2013 can't compile it + Tag (Tag&& ) = default; +#endif + Tag () = default; + + Tag& operator= (const Tag& ) = default; +#ifndef _WIN32 + Tag& operator= (Tag&& ) = default; +#endif + + uint8_t * operator()() { return m_Buf; }; + const uint8_t * operator()() const { return m_Buf; }; + + operator uint8_t * () { return m_Buf; }; + operator const uint8_t * () const { return m_Buf; }; + + const uint64_t * GetLL () const { return ll; }; + + bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); }; + bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; }; + + bool IsZero () const + { + for (int i = 0; i < sz/8; i++) + if (ll[i]) return false; + return true; + } + + std::string ToBase64 () const + { + char str[sz*2]; + int l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2); + str[l] = 0; + return std::string (str); + } + + std::string ToBase32 () const + { + char str[sz*2]; + int l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2); + str[l] = 0; + return std::string (str); + } + + void FromBase32 (const std::string& s) + { + i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); + } + + void FromBase64 (const std::string& s) + { + i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); + } + + private: + + union // 8 bytes alignment + { + uint8_t m_Buf[sz]; + uint64_t ll[sz/8]; + }; + }; +} // data +} // i2p + +#endif /* TAG_H__ */ From 7f266701734940122e1e65398f56f386b3465caf Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 28 Jun 2016 00:00:00 +0000 Subject: [PATCH 1507/6300] + tests/test-base-64.cpp --- tests/Makefile | 7 +++++-- tests/test-base-64.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tests/test-base-64.cpp diff --git a/tests/Makefile b/tests/Makefile index 957d4632..ef30c631 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,11 +1,14 @@ CXXFLAGS += -Wall -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 TESTS = test-http-url test-http-req test-http-res test-http-url_decode \ - test-http-merge_chunked + test-http-merge_chunked test-base-64 all: $(TESTS) run -test-http-%: test-http-%.cpp ../HTTP.cpp +test-http-%: ../HTTP.cpp test-http-%.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ + +test-base-%: ../Base.cpp test-base-%.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ run: $(TESTS) diff --git a/tests/test-base-64.cpp b/tests/test-base-64.cpp new file mode 100644 index 00000000..d8d0ce2f --- /dev/null +++ b/tests/test-base-64.cpp @@ -0,0 +1,45 @@ +#include +#include + +#include "../Base.h" + +using namespace i2p::data; + +int main() { + const char *in = "test"; + size_t in_len = strlen(in); + char out[16]; + + /* bytes -> b64 */ + assert(ByteStreamToBase64(NULL, 0, NULL, 0) == 0); + assert(ByteStreamToBase64(NULL, 0, out, sizeof(out)) == 0); + + assert(Base64EncodingBufferSize(2) == 4); + assert(Base64EncodingBufferSize(4) == 8); + assert(Base64EncodingBufferSize(6) == 8); + assert(Base64EncodingBufferSize(7) == 12); + assert(Base64EncodingBufferSize(9) == 12); + assert(Base64EncodingBufferSize(10) == 16); + assert(Base64EncodingBufferSize(12) == 16); + assert(Base64EncodingBufferSize(13) == 20); + + assert(ByteStreamToBase64((uint8_t *) in, in_len, out, sizeof(out)) == 8); + assert(memcmp(out, "dGVzdA==", 8) == 0); + + /* b64 -> bytes */ + assert(Base64ToByteStream(NULL, 0, NULL, 0) == 0); + assert(Base64ToByteStream(NULL, 0, (uint8_t *) out, sizeof(out)) == 0); + + in = "dGVzdA=="; /* valid b64 */ + assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 4); + assert(memcmp(out, "test", 4) == 0); + + in = "dGVzdA="; /* invalid b64 : not padded */ + assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 0); + + in = "dG/z.A=="; /* invalid b64 : char not from alphabet */ +// assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 0); +// ^^^ fails, current implementation not checks acceptable symbols + + return 0; +} From 2be1c10522fb33c183bb157872c2cfcc8870259f Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 28 Jun 2016 07:11:55 -0400 Subject: [PATCH 1508/6300] Tag.h and Gzip.h/.cpp added --- qt/i2pd_qt/i2pd_qt.pro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 07e71839..db3f21c8 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -36,7 +36,7 @@ SOURCES += DaemonQT.cpp mainwindow.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 ../../UPnP.cpp ../../util.cpp ../../i2pd.cpp + ../../TunnelPool.cpp ../../UPnP.cpp ../../util.cpp ../../Gzip.cpp ../../i2pd.cpp HEADERS += DaemonQT.h mainwindow.h \ ../../HTTPServer.h ../../I2PControl.h ../../UPnP.h ../../Daemon.h ../../Config.h \ @@ -50,7 +50,7 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../Streaming.h ../../Timestamp.h ../../TransitTunnel.h ../../Transports.h \ ../../TransportSession.h ../../Tunnel.h ../../TunnelBase.h ../../TunnelConfig.h \ ../../TunnelEndpoint.h ../../TunnelGateway.h ../../TunnelPool.h ../../UPnP.h \ - ../../util.h ../../version.h + ../../util.h ../../version.h ..//../Gzip.h ../../Tag.h FORMS += mainwindow.ui From cd47ddd539582e8bc0cd143ff29fdfca5d300a5a Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 28 Jun 2016 07:57:48 -0400 Subject: [PATCH 1509/6300] default to USE_MESHNET=yes --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0f6cd8f0..025dbbcc 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ include filelist.mk USE_AESNI := yes USE_STATIC := no -USE_MESHNET := no +USE_MESHNET := yes ifeq ($(UNAME),Darwin) DAEMON_SRC += DaemonLinux.cpp From 8cb3e3418ad0bbab54284b802376a7e6d8832f28 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 28 Jun 2016 09:31:41 -0400 Subject: [PATCH 1510/6300] send garlic cloves directly if garlic was received derectly --- Garlic.cpp | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index c1100f64..17740fae 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -7,6 +7,7 @@ #include "I2NPProtocol.h" #include "Tunnel.h" #include "TunnelPool.h" +#include "Transports.h" #include "Timestamp.h" #include "Log.h" #include "Garlic.h" @@ -514,22 +515,34 @@ namespace garlic buf += 32; uint32_t gwTunnel = bufbe32toh (buf); buf += 4; - std::shared_ptr tunnel; - if (from && from->GetTunnelPool ()) - tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel (); - if (tunnel) // we have send it through an outbound tunnel - { - auto msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from); - tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg); - } - else - LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); + auto msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from); + if (from) // received through an inbound tunnel + { + std::shared_ptr tunnel; + if (from->GetTunnelPool ()) + tunnel = from->GetTunnelPool ()->GetNextOutboundTunnel (); + else + LogPrint (eLogError, "Garlic: Tunnel pool is not set for inbound tunnel"); + if (tunnel) // we have send it through an outbound tunnel + tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg); + else + LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); + } + else // received directly + i2p::transport::transports.SendMessage (gwHash, i2p::CreateTunnelGatewayMsg (gwTunnel, msg)); // send directly break; } case eGarlicDeliveryTypeRouter: - LogPrint (eLogWarning, "Garlic: type router not supported"); + { + uint8_t * ident = buf; buf += 32; - break; + if (!from) // received directly + i2p::transport::transports.SendMessage (ident, + CreateI2NPMessage (buf, GetI2NPMessageLength (buf))); + else + LogPrint (eLogWarning, "Garlic: type router for inbound tunnels not supported"); + break; + } default: LogPrint (eLogWarning, "Garlic: unknown delivery type ", (int)deliveryType); } From 2d252e6459fff5396b8925136ce6c40b4cd90a91 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 28 Jun 2016 13:00:00 +0000 Subject: [PATCH 1511/6300] * HTTP.cpp : rename method --- HTTP.cpp | 2 +- HTTP.h | 2 +- I2PControl.cpp | 2 +- tests/test-http-res.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/HTTP.cpp b/HTTP.cpp index eca21fde..31567bbd 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -276,7 +276,7 @@ namespace http { return false; } - long int HTTPMsg::length() { + long int HTTPMsg::content_length() { unsigned long int length = 0; auto it = headers.find("Content-Length"); if (it == headers.end()) diff --git a/HTTP.h b/HTTP.h index 19d0612e..bce55026 100644 --- a/HTTP.h +++ b/HTTP.h @@ -62,7 +62,7 @@ namespace http { void del_header(const char *name); /** @brief Returns declared message length or -1 if unknown */ - long int length(); + long int content_length(); }; struct HTTPReq : HTTPMsg { diff --git a/I2PControl.cpp b/I2PControl.cpp index c0c87fd4..aa3c55e8 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -205,7 +205,7 @@ namespace client } /* append to json chunk of data from 1st request */ json.write(buf->begin() + len, bytes_transferred - len); - remains = req.length() - len; + remains = req.content_length() - len; /* if request has Content-Length header, fetch rest of data and store to json buffer */ while (remains > 0) { len = ((long int) buf->size() < remains) ? buf->size() : remains; diff --git a/tests/test-http-res.cpp b/tests/test-http-res.cpp index 7dd74e1e..896a4403 100644 --- a/tests/test-http-res.cpp +++ b/tests/test-http-res.cpp @@ -29,7 +29,7 @@ int main() { assert(res->headers.find("Server")->second == "nginx/1.2.1"); assert(res->headers.find("Content-Length")->second == "536"); assert(res->is_chunked() == false); - assert(res->length() == 536); + assert(res->content_length() == 536); delete res; /* test: building request */ From 21b76d3d2b8b25af1438213bd7efa4696bb3f6ea Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 28 Jun 2016 13:00:00 +0000 Subject: [PATCH 1512/6300] * Config.cpp : drop compat parser --- Config.cpp | 79 +----------------------------------------------------- 1 file changed, 1 insertion(+), 78 deletions(-) diff --git a/Config.cpp b/Config.cpp index e6d44d59..2a7668c5 100644 --- a/Config.cpp +++ b/Config.cpp @@ -26,83 +26,6 @@ namespace config { options_description m_OptionsDesc; variables_map m_Options; - /* list of renamed options */ - std::map remapped_options = { - { "tunnelscfg", "tunconf" }, - { "v6", "ipv6" }, - { "httpaddress", "http.address" }, - { "httpport", "http.port" }, - { "httpproxyaddress", "httpproxy.address" }, - { "httpproxyport", "httpproxy.port" }, - { "socksproxyaddress", "socksproxy.address" }, - { "socksproxyport", "socksproxy.port" }, - { "samaddress", "sam.address" }, - { "samport", "sam.port" }, - { "bobaddress", "bob.address" }, - { "bobport", "bob.port" }, - { "i2pcontroladdress", "i2pcontrol.address" }, - { "i2pcontroladdress", "i2pcontrol.port" }, - { "proxykeys", "httpproxy.keys" }, - }; - /* list of options, that loose their argument and become simple switch */ - std::set boolean_options = { - "daemon", "floodfill", "notransit", "service", "ipv6" - }; - - /* this function is a solid piece of shit, remove it after 2.6.0 */ - std::pair old_syntax_parser(const std::string& s) { - std::string name = ""; - std::string value = ""; - std::size_t pos = 0; - /* shortcuts -- -h */ - if (s.length() == 2 && s.at(0) == '-' && s.at(1) != '-') - return make_pair(s.substr(1), ""); - /* old-style -- -log, /log, etc */ - if (s.at(0) == '/' || (s.at(0) == '-' && s.at(1) != '-')) { - if ((pos = s.find_first_of("= ")) != std::string::npos) { - name = s.substr(1, pos - 1); - value = s.substr(pos + 1); - } else { - name = s.substr(1, pos); - value = ""; - } - if (boolean_options.count(name) > 0 && value != "") - std::cerr << "args: don't give an argument to switch option: " << s << std::endl; - if (m_OptionsDesc.find_nothrow(name, false)) { - std::cerr << "args: option " << s << " style is DEPRECATED, use --" << name << " instead" << std::endl; - return std::make_pair(name, value); - } - if (remapped_options.count(name) > 0) { - name = remapped_options[name]; - std::cerr << "args: option " << s << " is DEPRECATED, use --" << name << " instead" << std::endl; - return std::make_pair(name, value); - } /* else */ - } - /* long options -- --help */ - if (s.substr(0, 2) == "--") { - if ((pos = s.find_first_of("= ")) != std::string::npos) { - name = s.substr(2, pos - 2); - value = s.substr(pos + 1); - } else { - name = s.substr(2, pos); - value = ""; - } - if (boolean_options.count(name) > 0 && value != "") { - std::cerr << "args: don't give an argument to switch option: " << s << std::endl; - value = ""; - } - if (m_OptionsDesc.find_nothrow(name, false)) - return std::make_pair(name, value); - if (remapped_options.count(name) > 0) { - name = remapped_options[name]; - std::cerr << "args: option " << s << " is DEPRECATED, use --" << name << " instead" << std::endl; - return std::make_pair(name, value); - } /* else */ - } - std::cerr << "args: unknown option -- " << s << std::endl; - return std::make_pair("", ""); - } - void Init() { options_description general("General options"); general.add_options() @@ -225,7 +148,7 @@ namespace config { auto style = boost::program_options::command_line_style::unix_style | boost::program_options::command_line_style::allow_long_disguise; style &= ~ boost::program_options::command_line_style::allow_guessing; - store(parse_command_line(argc, argv, m_OptionsDesc, style, old_syntax_parser), m_Options); + store(parse_command_line(argc, argv, m_OptionsDesc, style), m_Options); } catch (boost::program_options::error& e) { std::cerr << "args: " << e.what() << std::endl; exit(EXIT_FAILURE); From 2e5226356b492423b763d5d5e687a296a7c9fac8 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 28 Jun 2016 13:00:00 +0000 Subject: [PATCH 1513/6300] * Tag.h : add (c) header --- Tag.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Tag.h b/Tag.h index e3432501..b6f94de7 100644 --- a/Tag.h +++ b/Tag.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef TAG_H__ #define TAG_H__ From 21b3576b6625fb1117b17f6c04553640dbebda3a Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 28 Jun 2016 12:20:18 -0400 Subject: [PATCH 1514/6300] stop using auto --- TunnelEndpoint.cpp | 4 ++-- TunnelGateway.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 1bc8a937..e2a0843e 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -205,7 +205,7 @@ namespace tunnel if (it->second.fragmentNum == msg.nextFragmentNum) { LogPrint (eLogWarning, "TunnelMessage: Out-of-sequence fragment ", (int)it->second.fragmentNum, " of message ", msgID, " found"); - auto size = it->second.data->GetLength (); + size_t size = it->second.data->GetLength (); if (msg.data->len + size > msg.data->maxLen) { LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); @@ -235,7 +235,7 @@ namespace tunnel LogPrint (eLogInfo, "TunnelMessage: message expired"); return; } - auto typeID = msg.data->GetTypeID (); + uint8_t typeID = msg.data->GetTypeID (); LogPrint (eLogDebug, "TunnelMessage: handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID); // catch RI or reply with new list of routers if ((IsRouterInfoMsg (msg.data) || typeID == eI2NPDatabaseSearchReply) && diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index 3383010b..d701e24b 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -49,7 +49,7 @@ namespace tunnel // create fragments std::shared_ptr msg = block.data; - auto fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length + size_t fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length if (fullMsgLen <= m_RemainingSize) { // message fits. First and last fragment @@ -66,10 +66,10 @@ namespace tunnel { if (!messageCreated) // check if we should complete previous message { - auto numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE; + size_t numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE; // length of bytes don't fit full tunnel message // every follow-on fragment adds 7 bytes - auto nonFit = (fullMsgLen + numFollowOnFragments*7) % TUNNEL_DATA_MAX_PAYLOAD_SIZE; + size_t nonFit = (fullMsgLen + numFollowOnFragments*7) % TUNNEL_DATA_MAX_PAYLOAD_SIZE; if (!nonFit || nonFit > m_RemainingSize) { CompleteCurrentTunnelDataMessage (); From be6aab4c4079e022645f37f7dfd2a9b16dbd4a40 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 28 Jun 2016 14:24:18 -0400 Subject: [PATCH 1515/6300] revert --- Tunnel.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 9b052553..961cc97e 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -642,11 +642,7 @@ namespace tunnel { // trying to create one more oubound tunnel auto inboundTunnel = GetNextInboundTunnel (); - std::shared_ptr router(nullptr); - if (i2p::transport::transports.RoutesRestricted()) - router = i2p::transport::transports.GetRestrictedPeer(); - else - router = i2p::data::netdb.GetRandomRouter (); + auto router = i2p::data::netdb.GetRandomRouter (); if (!inboundTunnel || !router) return; LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel"); CreateTunnel ( @@ -707,13 +703,9 @@ namespace tunnel if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 5) { - // trying to create one more inbound tunnel - std::shared_ptr router(nullptr); - if (i2p::transport::transports.RoutesRestricted()) - router = i2p::transport::transports.GetRestrictedPeer(); - else - router = i2p::data::netdb.GetRandomRouter (); - if (!router) { + // trying to create one more inbound tunnel + auto router = i2p::data::netdb.GetRandomRouter (); + if (!router) { LogPrint (eLogWarning, "Tunnel: can't find any router, skip creating tunnel"); return; } From 2cd056cfb370cde564a180a91189f1d0aff69930 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 28 Jun 2016 14:43:55 -0400 Subject: [PATCH 1516/6300] try banning non responsive routers --- Transports.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Transports.cpp b/Transports.cpp index 9988acf1..b4130dfd 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -615,6 +615,12 @@ namespace transport if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) { LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); + auto profile = i2p::data::GetRouterProfile(it->first); + if (profile) + { + profile->TunnelNonReplied(); + profile->Save(); + } std::unique_lock l(m_PeersMutex); it = m_Peers.erase (it); } From 597b5e6cfba2077b173fb1960feb8272879cca12 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 28 Jun 2016 14:50:25 -0400 Subject: [PATCH 1517/6300] use different constants for now in meshnet mode --- NetDb.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/NetDb.h b/NetDb.h index 59e9b0a8..3bd3dcf7 100644 --- a/NetDb.h +++ b/NetDb.h @@ -27,11 +27,18 @@ namespace i2p namespace data { const int NETDB_MIN_ROUTERS = 90; +#ifdef MESHNET + const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60; // 1 hour, in seconds + const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65; + const int NETDB_MIN_EXPIRATION_TIMEOUT = 90; // 1.5 hours + const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60; // 27 hours +#else const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60*60; // 1 hour, in seconds const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60; const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 hours - +#endif + class NetDb { public: From 3a50320f79ab8ff3968389bd7150da813ca62465 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 28 Jun 2016 15:35:58 -0400 Subject: [PATCH 1518/6300] add more logging --- Tunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 961cc97e..7017d807 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -468,7 +468,7 @@ namespace tunnel HandleTunnelGatewayMsg (tunnel, msg); } else - LogPrint (eLogWarning, "Tunnel: tunnel with id ", tunnelID, " not found"); + LogPrint (eLogWarning, "Tunnel: tunnel with id ", tunnelID, " not found, type=", (int)typeID); break; } case eI2NPVariableTunnelBuild: From 14cdb531c8c6a00629769133898fafd23d22be93 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 29 Jun 2016 01:00:00 +0000 Subject: [PATCH 1519/6300] * Streaming.cpp : tune logs --- Streaming.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index e5260556..2d3c92ce 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -87,7 +87,7 @@ namespace stream return; } - LogPrint (eLogDebug, "Streaming: Received seqn=", receivedSeqn); + LogPrint (eLogDebug, "Streaming: Received seqn=", receivedSeqn, " on sSID=", m_SendStreamID); if (receivedSeqn == m_LastReceivedSequenceNumber + 1) { // we have received next in sequence message @@ -129,13 +129,13 @@ namespace stream if (receivedSeqn <= m_LastReceivedSequenceNumber) { // we have received duplicate - LogPrint (eLogWarning, "Streaming: Duplicate message ", receivedSeqn, " received"); + LogPrint (eLogWarning, "Streaming: Duplicate message ", receivedSeqn, " on sSID=", m_SendStreamID); SendQuickAck (); // resend ack for previous message again delete packet; // packet dropped } else { - LogPrint (eLogWarning, "Streaming: Missing messages from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1); + LogPrint (eLogWarning, "Streaming: Missing messages on sSID=", m_SendStreamID, ": from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1); // save message and wait for missing message again SavePacket (packet); if (m_LastReceivedSequenceNumber >= 0) @@ -183,7 +183,7 @@ namespace stream m_RemoteIdentity = std::make_shared(optionData, packet->GetOptionSize ()); optionData += m_RemoteIdentity->GetFullLen (); if (!m_RemoteLeaseSet) - LogPrint (eLogDebug, "Streaming: Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 ()); + LogPrint (eLogDebug, "Streaming: Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), ", sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); } if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED) @@ -263,7 +263,7 @@ namespace stream uint64_t rtt = ts - sentPacket->sendTime; m_RTT = (m_RTT*seqn + rtt)/(seqn + 1); m_RTO = m_RTT*1.5; // TODO: implement it better - LogPrint (eLogDebug, "Packet ", seqn, " acknowledged rtt=", rtt); + LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt); m_SentPackets.erase (it++); delete sentPacket; acknowledged = true; @@ -451,7 +451,7 @@ namespace stream auto seqn = it->GetSeqn (); if (numNacks + (seqn - nextSeqn) >= 256) { - LogPrint (eLogError, "Number of NACKs exceeds 256. seqn=", seqn, " nextSeqn=", nextSeqn); + LogPrint (eLogError, "Streaming: Number of NACKs exceeds 256. seqn=", seqn, " nextSeqn=", nextSeqn); htobe32buf (packet + 12, nextSeqn); // change ack Through break; } @@ -492,7 +492,7 @@ namespace stream m_Status = eStreamStatusClosing; Close (); // recursion if (m_Status == eStreamStatusClosing) //still closing - LogPrint (eLogInfo, "Streaming: Trying to send stream data before closing"); + LogPrint (eLogDebug, "Streaming: Trying to send stream data before closing, sSID=", m_SendStreamID); break; case eStreamStatusReset: SendClose (); @@ -514,7 +514,7 @@ namespace stream m_LocalDestination.DeleteStream (shared_from_this ()); break; default: - LogPrint (eLogWarning, "Streaming: Unexpected stream status ", (int)m_Status); + LogPrint (eLogWarning, "Streaming: Unexpected stream status ", (int)m_Status, "sSID=", m_SendStreamID); }; } @@ -546,7 +546,7 @@ namespace stream p->len = size; m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p)); - LogPrint (eLogDebug, "Streaming: FIN sent"); + LogPrint (eLogDebug, "Streaming: FIN sent, sSID=", m_SendStreamID); } size_t Stream::ConcatenatePackets (uint8_t * buf, size_t len) @@ -600,7 +600,7 @@ namespace stream UpdateCurrentRemoteLease (); if (!m_RemoteLeaseSet) { - LogPrint (eLogError, "Streaming: Can't send packets, missing remote LeaseSet"); + LogPrint (eLogError, "Streaming: Can't send packets, missing remote LeaseSet, sSID=", m_SendStreamID); return; } } @@ -625,7 +625,7 @@ namespace stream m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); if (!m_CurrentOutboundTunnel) { - LogPrint (eLogError, "Streaming: No outbound tunnels in the pool"); + LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); return; } @@ -649,7 +649,7 @@ namespace stream m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs); } else - LogPrint (eLogWarning, "Streaming: All leases are expired"); + LogPrint (eLogWarning, "Streaming: All leases are expired, sSID=", m_SendStreamID); } @@ -668,7 +668,7 @@ namespace stream // check for resend attempts if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS) { - LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate"); + LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, sSID=", m_SendStreamID); m_Status = eStreamStatusReset; Close (); return; @@ -703,13 +703,13 @@ namespace stream case 4: if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); UpdateCurrentRemoteLease (); // pick another lease - LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream"); + LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream with sSID=", m_SendStreamID); break; case 3: // pick another outbound tunnel if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); - LogPrint (eLogWarning, "Streaming: Another outbound tunnel has been selected for stream"); + LogPrint (eLogWarning, "Streaming: Another outbound tunnel has been selected for stream with sSID=", m_SendStreamID); break; default: ; } @@ -725,7 +725,7 @@ namespace stream { if (m_LastReceivedSequenceNumber < 0) { - LogPrint (eLogWarning, "Streaming: SYN has not been recived after ", ACK_SEND_TIMEOUT, " milliseconds after follow on, terminate"); + LogPrint (eLogWarning, "Streaming: SYN has not been recived after ", ACK_SEND_TIMEOUT, " milliseconds after follow on, terminate sSID=", m_SendStreamID); m_Status = eStreamStatusReset; Close (); return; @@ -828,7 +828,7 @@ namespace stream it->second->HandleNextPacket (packet); else { - LogPrint (eLogError, "Streaming: Unknown stream sendStreamID=", sendStreamID); + LogPrint (eLogError, "Streaming: Unknown stream sSID=", sendStreamID); delete packet; } } @@ -844,7 +844,7 @@ namespace stream auto it = m_SavedPackets.find (receiveStreamID); if (it != m_SavedPackets.end ()) { - LogPrint (eLogDebug, "Streaming: Processing ", it->second.size (), " saved packets for receiveStreamID=", receiveStreamID); + LogPrint (eLogDebug, "Streaming: Processing ", it->second.size (), " saved packets for rSID=", receiveStreamID); for (auto it1: it->second) incomingStream->HandleNextPacket (it1); m_SavedPackets.erase (it); @@ -863,7 +863,7 @@ namespace stream m_PendingIncomingTimer.expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT)); m_PendingIncomingTimer.async_wait (std::bind (&StreamingDestination::HandlePendingIncomingTimer, shared_from_this (), std::placeholders::_1)); - LogPrint (eLogDebug, "Streaming: Pending incoming stream added"); + LogPrint (eLogDebug, "Streaming: Pending incoming stream added, rSID=", receiveStreamID); } else { From 90d8ec0e81f7a7fe33e13de081fc500740316924 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 09:37:21 -0400 Subject: [PATCH 1520/6300] add more logging --- Tunnel.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 7017d807..8f1f4712 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -457,7 +457,8 @@ namespace tunnel tunnel = prevTunnel; else if (prevTunnel) prevTunnel->FlushTunnelDataMsgs (); - + else + LogPrint(eLogWarning, "Tunnel: no previous tunnel, prevTunnelID=", prevTunnelID, "tunnelID=", tunnelID, "type=", (int)typeID); if (!tunnel) tunnel = GetTunnel (tunnelID); if (tunnel) @@ -467,7 +468,7 @@ namespace tunnel else // tunnel gateway assumed HandleTunnelGatewayMsg (tunnel, msg); } - else + else LogPrint (eLogWarning, "Tunnel: tunnel with id ", tunnelID, " not found, type=", (int)typeID); break; } From f2dde98e2faf06f26fd242b43fb80afa5ba5f8c8 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 09:37:38 -0400 Subject: [PATCH 1521/6300] add meshnet option to cmake build files --- build/CMakeLists.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 3f5f599f..cc11b207 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -12,6 +12,7 @@ option(WITH_STATIC "Static build" OFF) option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_PCH "Use precompiled header" OFF) option(WITH_GUI "Include GUI (currently MS Windows only)" ON) +option(WITH_MESHNET "Build for cjdns test network" ON) # paths set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) @@ -90,6 +91,10 @@ set (DAEMON_SRC "${CMAKE_SOURCE_DIR}/UPnP.cpp" ) +if (WITH_MESHNET) + add_definitions(-DMESHNET) +endif () + if (WITH_UPNP) add_definitions(-DUSE_UPNP) if (NOT MSVC AND NOT MSYS) @@ -296,6 +301,14 @@ link_directories(${CMAKE_CURRENT_BINARY_DIR}/zlib/lib ${ZLIB_ROOT}/lib) # load includes include_directories( SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ) + +# warn if for meshnet +if (WITH_MESHNET) + message(STATUS "Building for testnet") + message(WARNING "This build will NOT work on mainline i2p") +endif() + + # show summary message(STATUS "---------------------------------------") message(STATUS "Build type : ${CMAKE_BUILD_TYPE}") @@ -311,6 +324,7 @@ message(STATUS " BINARY : ${WITH_BINARY}") message(STATUS " STATIC BUILD : ${WITH_STATIC}") message(STATUS " UPnP : ${WITH_UPNP}") message(STATUS " PCH : ${WITH_PCH}") +message(STATUS " MESHNET : ${WITH_MESHNET}") message(STATUS "---------------------------------------") #Handle paths nicely From 14f2b24b16fd6c1adc86d4b4ad601b309f8b42f0 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 10:11:14 -0400 Subject: [PATCH 1522/6300] update logging --- Tunnel.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 8f1f4712..0789e335 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -442,7 +442,7 @@ namespace tunnel if (msg) { uint32_t prevTunnelID = 0, tunnelID = 0; - std::shared_ptr prevTunnel; + std::shared_ptr prevTunnel; do { std::shared_ptr tunnel; @@ -457,8 +457,7 @@ namespace tunnel tunnel = prevTunnel; else if (prevTunnel) prevTunnel->FlushTunnelDataMsgs (); - else - LogPrint(eLogWarning, "Tunnel: no previous tunnel, prevTunnelID=", prevTunnelID, "tunnelID=", tunnelID, "type=", (int)typeID); + if (!tunnel) tunnel = GetTunnel (tunnelID); if (tunnel) @@ -469,7 +468,8 @@ namespace tunnel HandleTunnelGatewayMsg (tunnel, msg); } else - LogPrint (eLogWarning, "Tunnel: tunnel with id ", tunnelID, " not found, type=", (int)typeID); + LogPrint (eLogWarning, "Tunnel: tunnel not found, tunnelID=", tunnelID, " previousTunnelID=", prevTunnelID, " type=", (int)typeID); + break; } case eI2NPVariableTunnelBuild: From f88f68f248549267bf9657b24296ac422c4ea580 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 11:06:51 -0400 Subject: [PATCH 1523/6300] Add bind to network interface option --- Config.cpp | 6 ++++++ Daemon.cpp | 44 ++++++++++++++++++++++++++++++++------------ util.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++++++- util.h | 1 + 4 files changed, 86 insertions(+), 13 deletions(-) diff --git a/Config.cpp b/Config.cpp index ce0ffd04..27f0fe1d 100644 --- a/Config.cpp +++ b/Config.cpp @@ -27,6 +27,10 @@ namespace config { variables_map m_Options; void Init() { + bool nat = true; +#ifdef MESHNET + nat = false; +#endif options_description general("General options"); general.add_options() ("help", "Show this message") @@ -39,6 +43,8 @@ namespace config { ("family", value()->default_value(""), "Specify a family, router belongs to") ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") ("host", value()->default_value("0.0.0.0"), "External IP") + ("ifname", value()->default_value(""), "network interface to bind to") + ("nat", value()->zero_tokens()->default_value(nat), "should we assume we are behind NAT?") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv4", value()->zero_tokens()->default_value(true), "Enable communication through ipv4") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") diff --git a/Daemon.cpp b/Daemon.cpp index 5d0cce6f..a5628414 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -22,6 +22,7 @@ #include "I2PControl.h" #include "ClientContext.h" #include "Crypto.h" +#include "util.h" #ifdef USE_UPNP #include "UPnP.h" @@ -114,7 +115,7 @@ namespace i2p } i2p::log::Logger().Ready(); - LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); + LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); LogPrint(eLogDebug, "FS: main config file: ", config); LogPrint(eLogDebug, "FS: data directory: ", datadir); @@ -122,6 +123,14 @@ namespace i2p i2p::crypto::InitCrypto (precomputation); i2p::context.Init (); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); +#ifdef MESHNET + // manual override for meshnet + ipv4 = false; + ipv6 = true; +#endif + uint16_t port; i2p::config::GetOption("port", port); if (!i2p::config::IsDefault("port")) { @@ -129,20 +138,31 @@ namespace i2p i2p::context.UpdatePort (port); } - std::string host; i2p::config::GetOption("host", host); - if (!i2p::config::IsDefault("host")) + bool nat; i2p::config::GetOption("nat", nat); + if (nat) { - LogPrint(eLogInfo, "Daemon: setting address for incoming connections to ", host); - i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); + 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); + } } - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ipv4; i2p::config::GetOption("ipv4", ipv4); -#ifdef MESHNET - // manual override for meshnet - ipv4 = false; - ipv6 = true; -#endif bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); diff --git a/util.cpp b/util.cpp index 5230f55f..7fc0daca 100644 --- a/util.cpp +++ b/util.cpp @@ -413,7 +413,53 @@ namespace net return GetMTUUnix(localAddress, fallback); #endif return fallback; - } + } + + const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6) + { +#ifdef WIN32 + LogPrint(eLogError, "NetIface: cannot get address by interface name, not implemented on WIN32"); + return boost::asio::ip::from_string("127.0.0.1"); +#else + int af = (ipv6 ? AF_INET6 : AF_INET); + ifaddrs * addrs = nullptr; + if(getifaddrs(&addrs) == 0) + { + // got ifaddrs + ifaddrs * cur = addrs; + while(cur) + { + std::string cur_ifname(cur->ifa_name); + if (cur_ifname == ifname && cur->ifa_addr->sa_family == af) + { + // match + size_t sz = (ipv6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN); + char * addr = new char[sz]; + addr[sz-1] = 0; + socklen_t sl = (ipv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in)); + // this probably won't screw up (right?) + inet_ntop(af, cur->ifa_addr, addr, sl); + std::string cur_ifaddr(addr); + delete [] addr; + freeifaddrs(addrs); + return boost::asio::ip::address::from_string(cur_ifaddr); + } + cur = cur->ifa_next; + } + } + if(addrs) freeifaddrs(addrs); + std::string fallback; + if(ipv6) { + fallback = "::"; + LogPrint(eLogWarning, "NetIface: cannot find ipv6 address for interface ", ifname); + } else { + fallback = "127.0.0.1"; + LogPrint(eLogWarning, "NetIface: cannot find ipv4 address for interface ", ifname); + } + return boost::asio::ip::address::from_string(fallback); + +#endif + } } } // util diff --git a/util.h b/util.h index 9f797158..7c393e02 100644 --- a/util.h +++ b/util.h @@ -66,6 +66,7 @@ namespace util namespace net { int GetMTU (const boost::asio::ip::address& localAddress); + const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6=false); } } } From f6e988d6fdca673104b43461d92f723ea9072911 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 29 Jun 2016 11:26:46 -0400 Subject: [PATCH 1524/6300] support zero-hops tunnels for destinations --- Tunnel.cpp | 28 ++++++++++++++++++++-------- Tunnel.h | 11 +++++++---- TunnelConfig.h | 5 +++++ TunnelPool.cpp | 21 +++++++++++++++------ 4 files changed, 47 insertions(+), 18 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 961cc97e..478b57bc 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -768,6 +768,22 @@ namespace tunnel return newTunnel; } + std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) + { + if (config->IsEmpty ()) + return CreateZeroHopsInboundTunnel (); + else + return CreateTunnel(config, outboundTunnel); + } + + std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config) + { + if (config->IsEmpty ()) + return CreateZeroHopsOutboundTunnel (); + else + return CreateTunnel(config); + } + void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { m_PendingInboundTunnels[replyMsgID] = tunnel; @@ -815,20 +831,22 @@ namespace tunnel } - void Tunnels::CreateZeroHopsInboundTunnel () + std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel () { auto inboundTunnel = std::make_shared (); inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; + return inboundTunnel; } - void Tunnels::CreateZeroHopsOutboundTunnel () + std::shared_ptr Tunnels::CreateZeroHopsOutboundTunnel () { auto outboundTunnel = std::make_shared (); outboundTunnel->SetState (eTunnelStateEstablished); m_OutboundTunnels.push_back (outboundTunnel); // we don't insert into m_Tunnels + return outboundTunnel; } int Tunnels::GetTransitTunnelsExpirationTimeout () @@ -861,12 +879,6 @@ namespace tunnel // TODO: locking return m_OutboundTunnels.size(); } - -#ifdef ANDROID_ARM7A - template std::shared_ptr Tunnels::CreateTunnel(std::shared_ptr, std::shared_ptr); - template std::shared_ptr Tunnels::CreateTunnel(std::shared_ptr, std::shared_ptr); -#endif - } } diff --git a/Tunnel.h b/Tunnel.h index 43417e5d..5bc8b195 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -176,10 +176,10 @@ namespace tunnel void AddTransitTunnel (std::shared_ptr tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); + std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel); + std::shared_ptr CreateOutboundTunnel (std::shared_ptr config); void PostTunnelData (std::shared_ptr msg); void PostTunnelData (const std::vector >& msgs); - template - std::shared_ptr CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel = nullptr); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); std::shared_ptr CreateTunnelPool (int numInboundHops, @@ -189,6 +189,9 @@ namespace tunnel private: + template + std::shared_ptr CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel = nullptr); + template std::shared_ptr GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels); @@ -204,8 +207,8 @@ namespace tunnel void ManagePendingTunnels (PendingTunnels& pendingTunnels); void ManageTunnelPools (); - void CreateZeroHopsInboundTunnel (); - void CreateZeroHopsOutboundTunnel (); + std::shared_ptr CreateZeroHopsInboundTunnel (); + std::shared_ptr CreateZeroHopsOutboundTunnel (); private: diff --git a/TunnelConfig.h b/TunnelConfig.h index 0340254e..7546c9b2 100644 --- a/TunnelConfig.h +++ b/TunnelConfig.h @@ -159,6 +159,11 @@ namespace tunnel return num; } + bool IsEmpty () const + { + return !m_FirstHop; + } + virtual bool IsInbound () const { return m_FirstHop->isGateway; } virtual uint32_t GetTunnelID () const diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 5e7e8ec4..92e5f6ff 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -329,8 +329,9 @@ namespace tunnel bool TunnelPool::SelectPeers (std::vector >& peers, bool isInbound) { if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); - auto prevHop = i2p::context.GetSharedRouterInfo (); int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; + if (numHops <= 0) return true; // peers is empty + auto prevHop = i2p::context.GetSharedRouterInfo (); if (i2p::transport::transports.GetNumPeers () > 25) { auto r = i2p::transport::transports.GetRandomPeer (); @@ -390,8 +391,10 @@ namespace tunnel if (SelectPeers (peers, true)) { std::reverse (peers.begin (), peers.end ()); - auto tunnel = tunnels.CreateTunnel (std::make_shared (peers), outboundTunnel); + auto tunnel = tunnels.CreateInboundTunnel (std::make_shared (peers), outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); + if (tunnel->IsEstablished ()) // zero hops + TunnelCreated (tunnel); } else LogPrint (eLogError, "Tunnels: Can't create inbound tunnel, no peers available"); @@ -403,8 +406,10 @@ namespace tunnel if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel..."); - auto newTunnel = tunnels.CreateTunnel (std::make_shared(tunnel->GetPeers ()), outboundTunnel); + auto newTunnel = tunnels.CreateInboundTunnel (std::make_shared(tunnel->GetPeers ()), outboundTunnel); newTunnel->SetTunnelPool (shared_from_this()); + if (newTunnel->IsEstablished ()) // zero hops + TunnelCreated (newTunnel); } void TunnelPool::CreateOutboundTunnel () @@ -418,9 +423,11 @@ namespace tunnel std::vector > peers; if (SelectPeers (peers, false)) { - auto tunnel = tunnels.CreateTunnel ( + auto tunnel = tunnels.CreateOutboundTunnel ( std::make_shared (peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())); tunnel->SetTunnelPool (shared_from_this ()); + if (tunnel->IsEstablished ()) // zero hops + TunnelCreated (tunnel); } else LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); @@ -437,10 +444,12 @@ namespace tunnel if (inboundTunnel) { LogPrint (eLogDebug, "Tunnels: Re-creating destination outbound tunnel..."); - auto newTunnel = tunnels.CreateTunnel ( + auto newTunnel = tunnels.CreateOutboundTunnel ( std::make_shared (tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())); newTunnel->SetTunnelPool (shared_from_this ()); + if (newTunnel->IsEstablished ()) // zero hops + TunnelCreated (newTunnel); } else LogPrint (eLogDebug, "Tunnels: Can't re-create outbound tunnel, no inbound tunnels found"); @@ -449,7 +458,7 @@ namespace tunnel void TunnelPool::CreatePairedInboundTunnel (std::shared_ptr outboundTunnel) { LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); - auto tunnel = tunnels.CreateTunnel (std::make_shared(outboundTunnel->GetInvertedPeers ()), outboundTunnel); + auto tunnel = tunnels.CreateInboundTunnel (std::make_shared(outboundTunnel->GetInvertedPeers ()), outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); } } From 8b7b6cfbc591404dac57fb3b5d3ebf78aa67254e Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 11:57:44 -0400 Subject: [PATCH 1525/6300] try fixing segfault --- util.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/util.cpp b/util.cpp index 7fc0daca..ca21f3c3 100644 --- a/util.cpp +++ b/util.cpp @@ -430,7 +430,7 @@ namespace net while(cur) { std::string cur_ifname(cur->ifa_name); - if (cur_ifname == ifname && cur->ifa_addr->sa_family == af) + if (cur_ifname == ifname && cur->ifa_addr && cur->ifa_addr->sa_family == af) { // match size_t sz = (ipv6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN); @@ -440,7 +440,6 @@ namespace net // this probably won't screw up (right?) inet_ntop(af, cur->ifa_addr, addr, sl); std::string cur_ifaddr(addr); - delete [] addr; freeifaddrs(addrs); return boost::asio::ip::address::from_string(cur_ifaddr); } From ae5cea7f365f7cef51b9cd0faecfd1c0ff7433fd Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 11:59:43 -0400 Subject: [PATCH 1526/6300] change order of initialization --- Daemon.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index a5628414..a3b8ee09 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -130,13 +130,9 @@ namespace i2p ipv4 = false; ipv6 = true; #endif - - uint16_t port; i2p::config::GetOption("port", port); - if (!i2p::config::IsDefault("port")) - { - LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); - i2p::context.UpdatePort (port); - } + + i2p::context.SetSupportsV6 (ipv6); + i2p::context.SetSupportsV4 (ipv4); bool nat; i2p::config::GetOption("nat", nat); if (nat) @@ -163,6 +159,15 @@ namespace i2p } } + + uint16_t port; i2p::config::GetOption("port", port); + if (!i2p::config::IsDefault("port")) + { + LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); + i2p::context.UpdatePort (port); + } + + bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); From f405c62f1e4bc034983a17c3591c8813c71744b3 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 14:56:00 -0400 Subject: [PATCH 1527/6300] pedantic style and logging changes --- I2NPProtocol.cpp | 2 +- NetDb.cpp | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 425668c3..1b2d0317 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -102,7 +102,7 @@ namespace i2p { RAND_bytes ((uint8_t *)&msgID, 4); htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID); - htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, I2PD_NET_ID); // netID = 2 + htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, I2PD_NET_ID); } m->len += DELIVERY_STATUS_SIZE; m->FillI2NPMessageHeader (eI2NPDeliveryStatus); diff --git a/NetDb.cpp b/NetDb.cpp index 1c44c7f8..96d402b9 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -615,7 +615,7 @@ namespace data uint8_t flag = buf[64]; LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " recieved flags=", (int)flag); uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK; - const uint8_t * excluded = buf + 65; + uint8_t * excluded = buf + 65; uint32_t replyTunnelID = 0; if (flag & DATABASE_LOOKUP_DELIVERY_FLAG) //reply to tunnel { @@ -671,7 +671,12 @@ namespace data lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP)) { auto leaseSet = FindLeaseSet (ident); - if (leaseSet && !leaseSet->IsExpired ()) // we don't send back our LeaseSets + if (!leaseSet) + { + // no lease set found + LogPrint(eLogDebug, "NetDb: requested LeaseSet not found ident=", ident); + } + else if (leaseSet->IsExpired ()) // we don't send back our LeaseSets { LogPrint (eLogDebug, "NetDb: requested LeaseSet ", key, " found"); replyMsg = CreateDatabaseStoreMsg (leaseSet); From 766286b8bccf7aceee0dc63f5cbcb77134a7aa2b Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 14:57:42 -0400 Subject: [PATCH 1528/6300] undo pedantic change --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 96d402b9..5ca9a89d 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -615,7 +615,7 @@ namespace data uint8_t flag = buf[64]; LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " recieved flags=", (int)flag); uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK; - uint8_t * excluded = buf + 65; + const uint8_t * excluded = buf + 65; uint32_t replyTunnelID = 0; if (flag & DATABASE_LOOKUP_DELIVERY_FLAG) //reply to tunnel { From c282d95be160f35be4b922a6b6e454b96e83dfa9 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 15:10:33 -0400 Subject: [PATCH 1529/6300] undo change --- NetDb.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/NetDb.h b/NetDb.h index 3bd3dcf7..eae6cb92 100644 --- a/NetDb.h +++ b/NetDb.h @@ -27,17 +27,10 @@ namespace i2p namespace data { const int NETDB_MIN_ROUTERS = 90; -#ifdef MESHNET - const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60; // 1 hour, in seconds - const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65; - const int NETDB_MIN_EXPIRATION_TIMEOUT = 90; // 1.5 hours - const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60; // 27 hours -#else const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60*60; // 1 hour, in seconds const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60; const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 hours -#endif class NetDb { From 05655195095b411e0e7dfbedccd6baa34b08b281 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 15:15:33 -0400 Subject: [PATCH 1530/6300] don't print out raw ident --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 5ca9a89d..ffea99ab 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -674,7 +674,7 @@ namespace data if (!leaseSet) { // no lease set found - LogPrint(eLogDebug, "NetDb: requested LeaseSet not found ident=", ident); + LogPrint(eLogDebug, "NetDb: requested LeaseSet not found ident=", ident.ToBase64()); } else if (leaseSet->IsExpired ()) // we don't send back our LeaseSets { From 9dd0bd604cf9a19384f4c4e537784d629ff0e557 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 15:37:37 -0400 Subject: [PATCH 1531/6300] fix --- util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util.cpp b/util.cpp index ca21f3c3..bcbcbe23 100644 --- a/util.cpp +++ b/util.cpp @@ -438,7 +438,7 @@ namespace net addr[sz-1] = 0; socklen_t sl = (ipv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in)); // this probably won't screw up (right?) - inet_ntop(af, cur->ifa_addr, addr, sl); + inet_ntop(af, cur->ifa_addr->sa_data, addr, sl); std::string cur_ifaddr(addr); freeifaddrs(addrs); return boost::asio::ip::address::from_string(cur_ifaddr); From c70d2ad6fa231cf75df0b4c72060ceb47a62e6a1 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 15:42:03 -0400 Subject: [PATCH 1532/6300] try fixing --- util.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/util.cpp b/util.cpp index bcbcbe23..dc7b32d7 100644 --- a/util.cpp +++ b/util.cpp @@ -436,9 +436,8 @@ namespace net size_t sz = (ipv6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN); char * addr = new char[sz]; addr[sz-1] = 0; - socklen_t sl = (ipv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in)); // this probably won't screw up (right?) - inet_ntop(af, cur->ifa_addr->sa_data, addr, sl); + inet_ntop(af, cur->ifa_addr->sa_data, addr, sz); std::string cur_ifaddr(addr); freeifaddrs(addrs); return boost::asio::ip::address::from_string(cur_ifaddr); From 3f5077606220972e14c2f281e3fda870f23b0b79 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 15:48:02 -0400 Subject: [PATCH 1533/6300] ugh --- util.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/util.cpp b/util.cpp index dc7b32d7..18a1685e 100644 --- a/util.cpp +++ b/util.cpp @@ -437,8 +437,7 @@ namespace net char * addr = new char[sz]; addr[sz-1] = 0; // this probably won't screw up (right?) - inet_ntop(af, cur->ifa_addr->sa_data, addr, sz); - std::string cur_ifaddr(addr); + std::string cur_ifaddr = inet_ntop(af, cur->ifa_addr->sa_data, addr, sz); freeifaddrs(addrs); return boost::asio::ip::address::from_string(cur_ifaddr); } From 611b9c4fd1e16814a5bef4a56fe1ea140adbae23 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 15:50:39 -0400 Subject: [PATCH 1534/6300] ugh --- util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util.cpp b/util.cpp index 18a1685e..40d3a761 100644 --- a/util.cpp +++ b/util.cpp @@ -433,11 +433,11 @@ namespace net if (cur_ifname == ifname && cur->ifa_addr && cur->ifa_addr->sa_family == af) { // match - size_t sz = (ipv6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN); + size_t sz = 128; char * addr = new char[sz]; addr[sz-1] = 0; // this probably won't screw up (right?) - std::string cur_ifaddr = inet_ntop(af, cur->ifa_addr->sa_data, addr, sz); + std::string cur_ifaddr = inet_ntop(af, cur->ifa_addr->sa_data, addr, sizeof(in6_addr)); freeifaddrs(addrs); return boost::asio::ip::address::from_string(cur_ifaddr); } From 44f0bad2a63ffe0fda434a5c7d4719d46f65e6d8 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 15:52:32 -0400 Subject: [PATCH 1535/6300] fug --- util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util.cpp b/util.cpp index 40d3a761..09e953ee 100644 --- a/util.cpp +++ b/util.cpp @@ -435,10 +435,10 @@ namespace net // match size_t sz = 128; char * addr = new char[sz]; - addr[sz-1] = 0; // this probably won't screw up (right?) - std::string cur_ifaddr = inet_ntop(af, cur->ifa_addr->sa_data, addr, sizeof(in6_addr)); + inet_ntop(af, cur->ifa_addr->sa_data, addr, sizeof(in6_addr)); freeifaddrs(addrs); + std::string cur_ifaddr(addr); return boost::asio::ip::address::from_string(cur_ifaddr); } cur = cur->ifa_next; From 50286fd1730fa8246f5ef8ff4b0d72d7b07d858d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 16:10:43 -0400 Subject: [PATCH 1536/6300] use inet_ntop properly --- util.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/util.cpp b/util.cpp index 09e953ee..909040c8 100644 --- a/util.cpp +++ b/util.cpp @@ -433,11 +433,13 @@ namespace net if (cur_ifname == ifname && cur->ifa_addr && cur->ifa_addr->sa_family == af) { // match - size_t sz = 128; - char * addr = new char[sz]; - // this probably won't screw up (right?) - inet_ntop(af, cur->ifa_addr->sa_data, addr, sizeof(in6_addr)); - freeifaddrs(addrs); + char * addr = new char[INET6_ADDRSTRLEN]; + bzero(addr, INET6_ADDRSTRLEN); + if(af == AF_INET) + inet_ntop(af, &((sockaddr_in *)cur->ifa_addr)->sin_addr, addr, INET6_ADDRSTRLEN); + else + inet_ntop(af, &((sockaddr_in6 *)cur->ifa_addr)->sin6_addr, addr, INET6_ADDRSTRLEN); + freeifaddrs(addrs); std::string cur_ifaddr(addr); return boost::asio::ip::address::from_string(cur_ifaddr); } From 2a796051bf87f19b7402a15ce64e4c17c9c57a75 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 17:42:26 -0400 Subject: [PATCH 1537/6300] try fixing LS --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index ffea99ab..6e73629e 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -676,7 +676,7 @@ namespace data // no lease set found LogPrint(eLogDebug, "NetDb: requested LeaseSet not found ident=", ident.ToBase64()); } - else if (leaseSet->IsExpired ()) // we don't send back our LeaseSets + else if (!leaseSet->IsExpired ()) // we don't send back our LeaseSets { LogPrint (eLogDebug, "NetDb: requested LeaseSet ", key, " found"); replyMsg = CreateDatabaseStoreMsg (leaseSet); From 5841d0d87d5e46c85e0ab1982f8d12456770f14b Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 17:59:56 -0400 Subject: [PATCH 1538/6300] add more specific logging --- Destination.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Destination.cpp b/Destination.cpp index bb67b601..a28fcbe9 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -378,7 +378,7 @@ namespace client uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET); if (msgID == m_PublishReplyToken) { - LogPrint (eLogDebug, "Destination: Publishing LeaseSet confirmed"); + LogPrint (eLogDebug, "Destination: Publishing LeaseSet confirmed for ", GetIdentHash().ToBase32()); m_ExcludedFloodfills.clear (); m_PublishReplyToken = 0; // schedule verification From 2768a62f92b50dc79a8af484dc3e256f0630d008 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 29 Jun 2016 18:05:08 -0400 Subject: [PATCH 1539/6300] more logging --- Destination.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index a28fcbe9..27eeac22 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -464,14 +464,14 @@ namespace client if (s->m_LeaseSet && *s->m_LeaseSet == *leaseSet) { // we got latest LeasetSet - LogPrint (eLogDebug, "Destination: published LeaseSet verified"); + LogPrint (eLogDebug, "Destination: published LeaseSet verified for ", GetIdentHash().ToBase32()); s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL)); s->m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, s, std::placeholders::_1)); return; } } else - LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet"); + LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet for ", GetIdentHash().ToBase32()); // we have to publish again s->Publish (); }); From c50105493a7df6cff760e0b12ed7cd6c2be4b8b0 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 29 Jun 2016 21:37:17 -0400 Subject: [PATCH 1540/6300] fixed zero-hops tunnels --- Destination.cpp | 4 ++-- Tunnel.cpp | 1 + TunnelPool.cpp | 29 +++++++++++++++++++++-------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index bb67b601..6cc24537 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -31,7 +31,7 @@ namespace client { int len = i2p::util::lexical_cast(it->second, inboundTunnelLen); - if (len > 0) + if (len >= 0) { inboundTunnelLen = len; } @@ -42,7 +42,7 @@ namespace client { int len = i2p::util::lexical_cast(it->second, outboundTunnelLen); - if (len > 0) + if (len >= 0) { outboundTunnelLen = len; } diff --git a/Tunnel.cpp b/Tunnel.cpp index 478b57bc..7983635a 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -217,6 +217,7 @@ namespace tunnel if (msg) { m_NumReceivedBytes += msg->GetLength (); + msg->from = shared_from_this (); HandleI2NPMessage (msg); } } diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 92e5f6ff..f922853f 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -390,8 +390,15 @@ namespace tunnel std::vector > peers; if (SelectPeers (peers, true)) { - std::reverse (peers.begin (), peers.end ()); - auto tunnel = tunnels.CreateInboundTunnel (std::make_shared (peers), outboundTunnel); + std::shared_ptr config; + if (m_NumInboundHops > 0) + { + std::reverse (peers.begin (), peers.end ()); + config = std::make_shared (peers); + } + else + config = std::make_shared (); + auto tunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); @@ -406,7 +413,9 @@ namespace tunnel if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel..."); - auto newTunnel = tunnels.CreateInboundTunnel (std::make_shared(tunnel->GetPeers ()), outboundTunnel); + std::shared_ptr config = m_NumInboundHops > 0 ? + std::make_shared(tunnel->GetPeers ()) : std::make_shared (); + auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); newTunnel->SetTunnelPool (shared_from_this()); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); @@ -423,8 +432,11 @@ namespace tunnel std::vector > peers; if (SelectPeers (peers, false)) { - auto tunnel = tunnels.CreateOutboundTunnel ( - std::make_shared (peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())); + std::shared_ptr config = m_NumOutboundHops > 0 ? + std::make_shared(peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) : + std::make_shared (); + + auto tunnel = tunnels.CreateOutboundTunnel (config); tunnel->SetTunnelPool (shared_from_this ()); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); @@ -444,9 +456,10 @@ namespace tunnel if (inboundTunnel) { LogPrint (eLogDebug, "Tunnels: Re-creating destination outbound tunnel..."); - auto newTunnel = tunnels.CreateOutboundTunnel ( - std::make_shared (tunnel->GetPeers (), - inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())); + std::shared_ptr config = m_NumOutboundHops > 0 ? + std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) : + std::make_shared (); + auto newTunnel = tunnels.CreateOutboundTunnel (config); newTunnel->SetTunnelPool (shared_from_this ()); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); From d44245e1e9271d3acb02209dcf8b5d1def8f4400 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 07:35:30 -0400 Subject: [PATCH 1541/6300] add RouterInfo hash to web ui --- HTTPServer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index be3b9b58..9eed80d9 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -58,6 +58,7 @@ namespace http { " .tunnel.another { color: #434343; }\r\n" " caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n" " table { width: 100%; border-collapse: collapse; text-align: center; }\r\n" + " .private { background: black; color: black; } .private:hover { background: black; color: white } \r\n" "\r\n"; const char HTTP_PAGE_TUNNELS[] = "tunnels"; @@ -205,6 +206,7 @@ namespace http { s << numKBytesSent / 1024 / 1024 << " GiB"; s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)
    \r\n"; s << "Data path: " << i2p::fs::GetDataDir() << "
    \r\n
    \r\n"; + s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64()<< "
    \r\n"; s << "Our external address:" << "
    \r\n" ; for (auto address : i2p::context.GetRouterInfo().GetAddresses()) { From 466ad192b0476575de04f3b85164a7b9a64e94c5 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 07:35:30 -0400 Subject: [PATCH 1542/6300] add RouterInfo hash to web ui --- HTTPServer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index be3b9b58..9eed80d9 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -58,6 +58,7 @@ namespace http { " .tunnel.another { color: #434343; }\r\n" " caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n" " table { width: 100%; border-collapse: collapse; text-align: center; }\r\n" + " .private { background: black; color: black; } .private:hover { background: black; color: white } \r\n" "\r\n"; const char HTTP_PAGE_TUNNELS[] = "tunnels"; @@ -205,6 +206,7 @@ namespace http { s << numKBytesSent / 1024 / 1024 << " GiB"; s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)
    \r\n"; s << "Data path: " << i2p::fs::GetDataDir() << "
    \r\n
    \r\n"; + s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64()<< "
    \r\n"; s << "Our external address:" << "
    \r\n" ; for (auto address : i2p::context.GetRouterInfo().GetAddresses()) { From 79fbf9d47fe35afc03384ab85327e814416f5f02 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 07:35:30 -0400 Subject: [PATCH 1543/6300] add RouterInfo hash to web ui --- HTTPServer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index be3b9b58..9eed80d9 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -58,6 +58,7 @@ namespace http { " .tunnel.another { color: #434343; }\r\n" " caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n" " table { width: 100%; border-collapse: collapse; text-align: center; }\r\n" + " .private { background: black; color: black; } .private:hover { background: black; color: white } \r\n" "\r\n"; const char HTTP_PAGE_TUNNELS[] = "tunnels"; @@ -205,6 +206,7 @@ namespace http { s << numKBytesSent / 1024 / 1024 << " GiB"; s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)
    \r\n"; s << "Data path: " << i2p::fs::GetDataDir() << "
    \r\n
    \r\n"; + s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64()<< "
    \r\n"; s << "Our external address:" << "
    \r\n" ; for (auto address : i2p::context.GetRouterInfo().GetAddresses()) { From 28ab1230e2a97f9693eac1d9d7f32681b7a9f9fd Mon Sep 17 00:00:00 2001 From: xcps Date: Thu, 30 Jun 2016 07:59:58 -0400 Subject: [PATCH 1544/6300] '@' can exist in url path --- HTTP.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/HTTP.cpp b/HTTP.cpp index 31567bbd..7fd87c55 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -81,7 +81,8 @@ namespace http { } /* user[:pass] */ pos_c = url.find('@', pos_p); - if (pos_c != std::string::npos) { + std::size_t pos_slash = url.find('/', pos_p); + if (pos_c != std::string::npos && (pos_slash == std::string::npos || pos_slash > pos_c)) { std::size_t delim = url.find(':', pos_p); if (delim != std::string::npos && delim < pos_c) { user = url.substr(pos_p, delim - pos_p); @@ -90,7 +91,7 @@ namespace http { } else { user = url.substr(pos_p, pos_c - pos_p); } - pos_p = pos_c + 1; + pos_p = pos_c + 1; } /* hostname[:port][/path] */ pos_c = url.find_first_of(":/", pos_p); From be68906ae94e1949769b2763490f4f881cba2f3f Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 09:45:06 -0400 Subject: [PATCH 1545/6300] logging tweaks --- NetDb.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 6e73629e..4f57a434 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -490,7 +490,7 @@ namespace data uint8_t * payload = floodMsg->GetPayload (); memcpy (payload, buf, 33); // key + type htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); // zero reply token - auto msgLen = len - payloadOffset; + size_t msgLen = len - payloadOffset; floodMsg->len += DATABASE_STORE_HEADER_SIZE + msgLen; if (floodMsg->len < floodMsg->maxLen) { @@ -501,14 +501,18 @@ namespace data { auto floodfill = GetClosestFloodfill (ident, excluded); if (floodfill) - transports.SendMessage (floodfill->GetIdentHash (), floodMsg); + { + auto h = floodfill->GetIdentHash(); + LogPrint(eLogDebug, "NetDb: Flood lease set for ", ident.ToBase32(), " to ", h.ToBase32()); + transports.SendMessage (h, floodMsg); + } else - break; + LogPrint(eLogWarning, "NetDb: failed to flood, no close floodfill for ", ident.ToBase32()); } } else - LogPrint (eLogError, "Database store message is too long ", floodMsg->len); - } + LogPrint (eLogError, "NetDb: Database store message is too long ", floodMsg->len); + } } void NetDb::HandleDatabaseSearchReplyMsg (std::shared_ptr msg) @@ -674,7 +678,7 @@ namespace data if (!leaseSet) { // no lease set found - LogPrint(eLogDebug, "NetDb: requested LeaseSet not found ident=", ident.ToBase64()); + LogPrint(eLogDebug, "NetDb: requested LeaseSet not found for ", ident.ToBase32()); } else if (!leaseSet->IsExpired ()) // we don't send back our LeaseSets { From fb2602716e2297cd134db26eb0e086760c1a0dc9 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 09:48:08 -0400 Subject: [PATCH 1546/6300] add floodfill to exclude --- NetDb.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 4f57a434..0058f10c 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -503,7 +503,8 @@ namespace data if (floodfill) { auto h = floodfill->GetIdentHash(); - LogPrint(eLogDebug, "NetDb: Flood lease set for ", ident.ToBase32(), " to ", h.ToBase32()); + excluded.push_back(h); + LogPrint(eLogDebug, "NetDb: Flood lease set for ", ident.ToBase32(), " to ", h.ToBase64()); transports.SendMessage (h, floodMsg); } else From 0eab8e9322397d29ecfa3eae44b413c5c1bf1b91 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 09:49:28 -0400 Subject: [PATCH 1547/6300] more pedantic logging changes --- NetDb.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 0058f10c..e5b1cfa9 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -226,29 +226,29 @@ namespace data it->second->Update (buf, len); if (it->second->IsValid ()) { - LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase64()); + LogPrint (eLogInfo, "NetDb: LeaseSet updated: ", ident.ToBase32()); updated = true; } else { - LogPrint (eLogWarning, "NetDb: LeaseSet update failed: ", ident.ToBase64()); + LogPrint (eLogWarning, "NetDb: LeaseSet update failed: ", ident.ToBase32()); m_LeaseSets.erase (it); } } else - LogPrint (eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase64()); + LogPrint (eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase32()); } else { auto leaseSet = std::make_shared (buf, len, false); // we don't need leases in netdb if (leaseSet->IsValid ()) { - LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase64()); + LogPrint (eLogInfo, "NetDb: LeaseSet added: ", ident.ToBase32()); m_LeaseSets[ident] = leaseSet; updated = true; } else - LogPrint (eLogError, "NetDb: new LeaseSet validation failed: ", ident.ToBase64()); + LogPrint (eLogError, "NetDb: new LeaseSet validation failed: ", ident.ToBase32()); } } return updated; From 10911f5b64d04290549523b2c666a04ef9076d5d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 09:52:37 -0400 Subject: [PATCH 1548/6300] make it compile --- NetDb.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index e5b1cfa9..23b6c701 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -503,12 +503,12 @@ namespace data if (floodfill) { auto h = floodfill->GetIdentHash(); - excluded.push_back(h); + excluded.insert(h); LogPrint(eLogDebug, "NetDb: Flood lease set for ", ident.ToBase32(), " to ", h.ToBase64()); transports.SendMessage (h, floodMsg); } else - LogPrint(eLogWarning, "NetDb: failed to flood, no close floodfill for ", ident.ToBase32()); + break; } } else From ea1ba0f09b57eb1bf7ecae243b306ed6fe4e105a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 30 Jun 2016 10:21:53 -0400 Subject: [PATCH 1549/6300] don't flood to itself --- NetDb.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 937ea830..d146c7d7 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -488,11 +488,15 @@ namespace data memcpy (payload + DATABASE_STORE_HEADER_SIZE, buf + payloadOffset, msgLen); floodMsg->FillI2NPMessageHeader (eI2NPDatabaseStore); std::set excluded; - for (int i = 0; i < 3; i++) + excluded.insert (i2p::context.GetIdentHash ()); // don't flood to itself + for (int i = 0; i < 3; i++) { auto floodfill = GetClosestFloodfill (ident, excluded); if (floodfill) + { transports.SendMessage (floodfill->GetIdentHash (), floodMsg); + excluded.insert (floodfill->GetIdentHash ()); + } else break; } From c6e35876fa2df30738960f1d8c13a4b28ffa6583 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 10:51:39 -0400 Subject: [PATCH 1550/6300] if LS is older, assume updated so we reply --- NetDb.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NetDb.cpp b/NetDb.cpp index d455a7f4..c50c5fd8 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -236,7 +236,10 @@ namespace data } } else + { LogPrint (eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase32()); + updated = true; + } } else { From 6ab7e799872698614363685b7699552b3cd6bae3 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 30 Jun 2016 11:01:21 -0400 Subject: [PATCH 1551/6300] send own copy of a message during flood --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index d146c7d7..888e2e7f 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -494,7 +494,7 @@ namespace data auto floodfill = GetClosestFloodfill (ident, excluded); if (floodfill) { - transports.SendMessage (floodfill->GetIdentHash (), floodMsg); + transports.SendMessage (floodfill->GetIdentHash (), CopyI2NPMessage(floodMsg)); excluded.insert (floodfill->GetIdentHash ()); } else From c4c495948aecddc215240a73cc3ebfd2443ec065 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 11:10:01 -0400 Subject: [PATCH 1552/6300] don't flood if older --- NetDb.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 85fbf6ff..98a59423 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -236,10 +236,7 @@ namespace data } } else - { LogPrint (eLogDebug, "NetDb: LeaseSet is older: ", ident.ToBase32()); - updated = true; - } } else { From ff757ddc88ecd5d6121a2a970d957f84f3e46875 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 11:27:40 -0400 Subject: [PATCH 1553/6300] don't check for exact LS --- Destination.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 136035ea..abfe3227 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -459,16 +459,13 @@ namespace client // "this" added due to bug in gcc 4.7-4.8 [s,this](std::shared_ptr leaseSet) { - if (leaseSet) + if (leaseSet && s->m_LeaseSet) { - if (s->m_LeaseSet && *s->m_LeaseSet == *leaseSet) - { - // we got latest LeasetSet - LogPrint (eLogDebug, "Destination: published LeaseSet verified for ", GetIdentHash().ToBase32()); - s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL)); - s->m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, s, std::placeholders::_1)); - return; - } + // we got latest LeasetSet + LogPrint (eLogDebug, "Destination: published LeaseSet verified for ", GetIdentHash().ToBase32()); + s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL)); + s->m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, s, std::placeholders::_1)); + return; } else LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet for ", GetIdentHash().ToBase32()); From 0493f00a7abd595729058548c192dd26ef2d247d Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 30 Jun 2016 12:24:26 -0400 Subject: [PATCH 1554/6300] pass null tunnel config for zero hops tunnel --- Tunnel.cpp | 12 ++++++------ TunnelPool.cpp | 19 ++++++++----------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 7983635a..5da18542 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -771,18 +771,18 @@ namespace tunnel std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) { - if (config->IsEmpty ()) - return CreateZeroHopsInboundTunnel (); - else + if (config) return CreateTunnel(config, outboundTunnel); + else + return CreateZeroHopsInboundTunnel (); } std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config) { - if (config->IsEmpty ()) - return CreateZeroHopsOutboundTunnel (); - else + if (config) return CreateTunnel(config); + else + return CreateZeroHopsOutboundTunnel (); } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index f922853f..7024e1a1 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -396,8 +396,6 @@ namespace tunnel std::reverse (peers.begin (), peers.end ()); config = std::make_shared (peers); } - else - config = std::make_shared (); auto tunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); if (tunnel->IsEstablished ()) // zero hops @@ -413,8 +411,8 @@ namespace tunnel if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel..."); - std::shared_ptr config = m_NumInboundHops > 0 ? - std::make_shared(tunnel->GetPeers ()) : std::make_shared (); + std::shared_ptr config; + if (m_NumInboundHops > 0) config = std::make_shared(tunnel->GetPeers ()); auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); newTunnel->SetTunnelPool (shared_from_this()); if (newTunnel->IsEstablished ()) // zero hops @@ -432,10 +430,9 @@ namespace tunnel std::vector > peers; if (SelectPeers (peers, false)) { - std::shared_ptr config = m_NumOutboundHops > 0 ? - std::make_shared(peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) : - std::make_shared (); - + std::shared_ptr config; + if (m_NumOutboundHops > 0) + config = std::make_shared(peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); auto tunnel = tunnels.CreateOutboundTunnel (config); tunnel->SetTunnelPool (shared_from_this ()); if (tunnel->IsEstablished ()) // zero hops @@ -456,9 +453,9 @@ namespace tunnel if (inboundTunnel) { LogPrint (eLogDebug, "Tunnels: Re-creating destination outbound tunnel..."); - std::shared_ptr config = m_NumOutboundHops > 0 ? - std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) : - std::make_shared (); + std::shared_ptr config; + if (m_NumOutboundHops > 0) + config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); auto newTunnel = tunnels.CreateOutboundTunnel (config); newTunnel->SetTunnelPool (shared_from_this ()); if (newTunnel->IsEstablished ()) // zero hops From c72d9695da96d3bd18664f15e89f4e030789e75d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 12:27:23 -0400 Subject: [PATCH 1555/6300] testnet changes --- NetDb.cpp | 10 ++++------ NetDb.h | 8 +++++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 98a59423..28063cd3 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -124,9 +124,9 @@ namespace data } // if we're in hidden mode don't publish or explore - if (m_HiddenMode) continue; - - if (ts - lastPublish >= 2400) // publish every 40 minutes + // if (m_HiddenMode) continue; + + if (ts - lastPublish >= NETDB_PUBLISH_INTERVAL) // publish { Publish (); lastPublish = ts; @@ -183,10 +183,8 @@ namespace data // TODO: check if floodfill has been changed } else - { LogPrint (eLogDebug, "NetDb: RouterInfo is older: ", ident.ToBase64()); - updated = false; - } + } else { diff --git a/NetDb.h b/NetDb.h index eae6cb92..3b54ae4c 100644 --- a/NetDb.h +++ b/NetDb.h @@ -31,7 +31,13 @@ namespace data const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60; const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 hours - + +#ifdef MESHNET + const int NETDB_PUBLISH_INTERVAL = 60; +#else + const int NETDB_PUBLISH_INTERVAL = 60*40; +#endif + class NetDb { public: From d7653769b4212838c94a88184ca316f847812358 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 12:57:20 -0400 Subject: [PATCH 1556/6300] more logging --- NetDb.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NetDb.cpp b/NetDb.cpp index 28063cd3..c7c54176 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -685,6 +685,8 @@ namespace data LogPrint (eLogDebug, "NetDb: requested LeaseSet ", key, " found"); replyMsg = CreateDatabaseStoreMsg (leaseSet); } + else + LogPrint (eLogDebug, "NetDb: requested Expired LeaseSet ", key); } if (!replyMsg) From eab08ea78caffe01831d9322c53fb420036c6689 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 30 Jun 2016 13:15:36 -0400 Subject: [PATCH 1557/6300] don't accept our own RouterInfo --- NetDb.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NetDb.cpp b/NetDb.cpp index 888e2e7f..1ecf646e 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -450,6 +450,12 @@ namespace data } offset += 32; } + // we must send reply back before this check + if (ident == i2p::context.GetIdentHash ()) + { + LogPrint (eLogError, "NetDb: database store with own RouterInfo received, dropped"); + return; + } size_t payloadOffset = offset; bool updated = false; @@ -489,6 +495,7 @@ namespace data floodMsg->FillI2NPMessageHeader (eI2NPDatabaseStore); std::set excluded; excluded.insert (i2p::context.GetIdentHash ()); // don't flood to itself + excluded.insert (ident); // don't flood back for (int i = 0; i < 3; i++) { auto floodfill = GetClosestFloodfill (ident, excluded); From b8a205f755906666ecc2129e50d2d07a1bb1761e Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 13:25:20 -0400 Subject: [PATCH 1558/6300] netdb.cpp: * explicitly define replyIdent Daemon.cpp: * wait for 1 second before checking if transports are bound to wait for transports to bind --- Daemon.cpp | 1 + NetDb.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index a3b8ee09..86e11ff9 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -257,6 +257,7 @@ namespace i2p bool ssu; i2p::config::GetOption("ssu", ssu); LogPrint(eLogInfo, "Daemon: starting Transports"); i2p::transport::transports.Start(ntcp, ssu); + std::this_thread::sleep_for(std::chrono::seconds(1)); if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU()) { LogPrint(eLogInfo, "Daemon: Transports started"); } else { diff --git a/NetDb.cpp b/NetDb.cpp index c7c54176..def8086e 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -617,6 +617,9 @@ namespace data int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48); key[l] = 0; uint8_t flag = buf[64]; + + IdentHash replyIdent(buf + 32); + LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " recieved flags=", (int)flag); uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK; const uint8_t * excluded = buf + 65; @@ -739,12 +742,12 @@ namespace data auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; if (outbound) - outbound->SendTunnelDataMsg (buf+32, replyTunnelID, replyMsg); + outbound->SendTunnelDataMsg (replyIdent, replyTunnelID, replyMsg); else - transports.SendMessage (buf+32, i2p::CreateTunnelGatewayMsg (replyTunnelID, replyMsg)); + transports.SendMessage (replyIdent, i2p::CreateTunnelGatewayMsg (replyTunnelID, replyMsg)); } else - transports.SendMessage (buf+32, replyMsg); + transports.SendMessage (replyIdent, replyMsg); } } From 2412a0d50256eee1ff155aa410cf53c812c4dc26 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 13:35:36 -0400 Subject: [PATCH 1559/6300] off by one? --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index def8086e..ccf54290 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -626,7 +626,7 @@ namespace data uint32_t replyTunnelID = 0; if (flag & DATABASE_LOOKUP_DELIVERY_FLAG) //reply to tunnel { - replyTunnelID = bufbe32toh (buf + 64); + replyTunnelID = bufbe32toh (buf + 65); excluded += 4; } uint16_t numExcluded = bufbe16toh (excluded); From ff7cf503ae37b62fb0811385ad69917cdebb5e64 Mon Sep 17 00:00:00 2001 From: MXPLRS|Kirill Date: Thu, 30 Jun 2016 21:21:37 +0300 Subject: [PATCH 1560/6300] added hiding information in webconsole --- HTTPServer.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 9eed80d9..cfcf4261 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -59,6 +59,8 @@ namespace http { " caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n" " table { width: 100%; border-collapse: collapse; text-align: center; }\r\n" " .private { background: black; color: black; } .private:hover { background: black; color: white } \r\n" + " .slide p, .slide [type='checkbox']{ display:none; } \r\n" + " .slide [type='checkbox']:checked ~ p { display:block; } \r\n" "\r\n"; const char HTTP_PAGE_TUNNELS[] = "tunnels"; @@ -206,7 +208,10 @@ namespace http { s << numKBytesSent / 1024 / 1024 << " GiB"; s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)
    \r\n"; s << "Data path: " << i2p::fs::GetDataDir() << "
    \r\n
    \r\n"; - s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64()<< "
    \r\n"; + s << "
    \r\n\r\n

    \r\n"; + s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
    \r\n"; + s << "Router Family: " << i2p::context.GetRouterInfo().GetProperty("family") << "
    \r\n"; + s << "Router Caps: " << i2p::context.GetRouterInfo().GetProperty("caps") << "
    \r\n"; s << "Our external address:" << "
    \r\n" ; for (auto address : i2p::context.GetRouterInfo().GetAddresses()) { @@ -229,6 +234,7 @@ namespace http { } s << address->host.to_string() << ":" << address->port << "
    \r\n"; } + s << "

    \r\n
    \r\n"; s << "
    \r\nRouters: " << i2p::data::netdb.GetNumRouters () << " "; s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
    \r\n"; From 6b16a48568b6f3e61203b4e10f02e701efba5601 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 14:24:55 -0400 Subject: [PATCH 1561/6300] revert --- Daemon.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 86e11ff9..3c0f3f50 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -253,11 +253,12 @@ namespace i2p LogPrint(eLogInfo, "Daemon: starting UPnP"); d.m_UPnP.Start (); #endif - bool ntcp; i2p::config::GetOption("ntcp", ntcp); - bool ssu; i2p::config::GetOption("ssu", ssu); + bool ntcp; i2p::config::GetOption("ntcp", ntcp); + bool ssu; i2p::config::GetOption("ssu", ssu); LogPrint(eLogInfo, "Daemon: starting Transports"); + if(!ssu) LogPrint(eLogDebug, "Daemon: ssu disabled"); + if(!ntcp) LogPrint(eLogDebug, "Daemon: ntcp disabled"); i2p::transport::transports.Start(ntcp, ssu); - std::this_thread::sleep_for(std::chrono::seconds(1)); if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU()) { LogPrint(eLogInfo, "Daemon: Transports started"); } else { From f62ccc2d480e967d640c830ca7ea69658ee0abc6 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 13:35:36 -0400 Subject: [PATCH 1562/6300] off by one? --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 937ea830..9aa015f7 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -610,7 +610,7 @@ namespace data uint32_t replyTunnelID = 0; if (flag & DATABASE_LOOKUP_DELIVERY_FLAG) //reply to tunnel { - replyTunnelID = bufbe32toh (buf + 64); + replyTunnelID = bufbe32toh (buf + 65); excluded += 4; } uint16_t numExcluded = bufbe16toh (excluded); From 331065eec690e950f951d2611f91e987952f6b4f Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 17:01:00 -0400 Subject: [PATCH 1563/6300] remove expired LS --- NetDb.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index ccf54290..98dafd99 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -688,8 +688,14 @@ namespace data LogPrint (eLogDebug, "NetDb: requested LeaseSet ", key, " found"); replyMsg = CreateDatabaseStoreMsg (leaseSet); } - else + else if (!leaseSet->IsEmpty()) + { LogPrint (eLogDebug, "NetDb: requested Expired LeaseSet ", key); + // remove LS as it is expired + m_LeaseSets.erase(ident); + } + else + LogPrint(eLogWarning, "NetDb: LeaseSet is empty? ", ident.ToBase32()); } if (!replyMsg) From 1bad097a132db6197157b5f0e267a244f3c888bb Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 17:21:18 -0400 Subject: [PATCH 1564/6300] don't check for LS expired for FF --- LeaseSet.cpp | 2 +- NetDb.cpp | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 16a470e0..fafe14b7 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -190,7 +190,7 @@ namespace data bool LeaseSet::IsExpired () const { - if (IsEmpty ()) return true; + if (m_StoreLeases && IsEmpty ()) return true; auto ts = i2p::util::GetMillisecondsSinceEpoch (); return ts > m_ExpirationTime; } diff --git a/NetDb.cpp b/NetDb.cpp index 98dafd99..0bf21f6c 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -462,7 +462,7 @@ namespace data bool updated = false; if (buf[DATABASE_STORE_TYPE_OFFSET]) // type { - LogPrint (eLogDebug, "NetDb: store request: LeaseSet"); + LogPrint (eLogDebug, "NetDb: store request: LeaseSet for ", ident); updated = AddLeaseSet (ident, buf + offset, len - offset, m->from); } else @@ -688,14 +688,6 @@ namespace data LogPrint (eLogDebug, "NetDb: requested LeaseSet ", key, " found"); replyMsg = CreateDatabaseStoreMsg (leaseSet); } - else if (!leaseSet->IsEmpty()) - { - LogPrint (eLogDebug, "NetDb: requested Expired LeaseSet ", key); - // remove LS as it is expired - m_LeaseSets.erase(ident); - } - else - LogPrint(eLogWarning, "NetDb: LeaseSet is empty? ", ident.ToBase32()); } if (!replyMsg) From 4e0929e71aae1abf908adfd849832b16324a385b Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 17:24:33 -0400 Subject: [PATCH 1565/6300] don't print out junk when logging --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 0bf21f6c..80bff91f 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -462,7 +462,7 @@ namespace data bool updated = false; if (buf[DATABASE_STORE_TYPE_OFFSET]) // type { - LogPrint (eLogDebug, "NetDb: store request: LeaseSet for ", ident); + LogPrint (eLogDebug, "NetDb: store request: LeaseSet for ", ident.ToBase32()); updated = AddLeaseSet (ident, buf + offset, len - offset, m->from); } else From 4b903931bc68eab458849d91b694d645793a5c22 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 17:34:16 -0400 Subject: [PATCH 1566/6300] update i2pd testnet addressbook url --- AddressBook.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AddressBook.h b/AddressBook.h index 04b25021..d67089fa 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -19,7 +19,7 @@ namespace i2p namespace client { #ifdef MESHNET - const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://opo57rwxz27frnqxrwiyr6snkybuyetv25fyd25y6se6imj54xfq.b32.i2p/hosts.txt"; + const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://i42ofzetmgicvui5sshinfckpijix2udewbam4sjo6x5fbukltia.b32.i2p/hosts.txt"; #else const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt"; #endif From 346b0c9d68c0b398383bf816cb510f9c1b0f40ee Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 17:50:47 -0400 Subject: [PATCH 1567/6300] disable testnet by default --- Makefile | 2 +- build/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 87327e77..22f016ea 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ include filelist.mk USE_AESNI := yes USE_STATIC := no -USE_MESHNET := yes +USE_MESHNET := no USE_UPNP := no ifeq ($(UNAME),Darwin) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index cc11b207..edde2e06 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -12,7 +12,7 @@ option(WITH_STATIC "Static build" OFF) option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_PCH "Use precompiled header" OFF) option(WITH_GUI "Include GUI (currently MS Windows only)" ON) -option(WITH_MESHNET "Build for cjdns test network" ON) +option(WITH_MESHNET "Build for cjdns test network" OFF) # paths set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) From fab34d3dbbbae18c1c3dd1ae81fada4209b7a2bc Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 17:59:14 -0400 Subject: [PATCH 1568/6300] clean up identation --- Daemon.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 3c0f3f50..ab2c052f 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -141,10 +141,10 @@ namespace i2p // 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)); - } + { + LogPrint(eLogInfo, "Daemon: setting address for incoming connections to ", host); + i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); + } } else { From 9215a54c2300baa0e7d95f0eebed0d43827b992a Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 30 Jun 2016 18:05:41 -0400 Subject: [PATCH 1569/6300] revert --- Makefile.linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.linux b/Makefile.linux index 0db43153..1376260a 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -28,7 +28,7 @@ endif NEEDED_CXXFLAGS += -fPIC ifeq ($(USE_STATIC),yes) - #LIBDIR = /usr/lib + LIBDIR := /usr/lib LDLIBS = $(LIBDIR)/libboost_system.a LDLIBS += $(LIBDIR)/libboost_date_time.a LDLIBS += $(LIBDIR)/libboost_filesystem.a From 323f74c43aecf9d2c5d82c9cff6376f277ce3854 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 1570/6300] * HTTP.cpp : fuck the "special cases", use nginx rewriting frontend or some --- HTTP.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/HTTP.cpp b/HTTP.cpp index 71d277de..76ab54ac 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -72,10 +72,6 @@ namespace http { bool URL::parse(const std::string& url) { std::size_t pos_p = 0; /* < current parse position */ std::size_t pos_c = 0; /* < work position */ - if (url.at(0) == '/' && url.find("/http://") == 0) { - /* special case for i2p.rocks inproxy */ - pos_p ++; - } if(url.at(0) != '/' || pos_p > 0) { /* schema */ pos_c = url.find("://"); From 02ac638bd4fd1230e4aec5edb183cdc8537abcbe Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 1571/6300] * HTTP.cpp : add comments, update test case --- HTTP.cpp | 9 +++++---- tests/test-http-url.cpp | 12 ++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/HTTP.cpp b/HTTP.cpp index 76ab54ac..27421241 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -73,6 +73,7 @@ namespace http { std::size_t pos_p = 0; /* < current parse position */ std::size_t pos_c = 0; /* < work position */ if(url.at(0) != '/' || pos_p > 0) { + std::size_t pos_s = 0; /* schema */ pos_c = url.find("://"); if (pos_c != std::string::npos) { @@ -80,9 +81,9 @@ namespace http { pos_p = pos_c + 3; } /* user[:pass] */ - pos_c = url.find('@', pos_p); - std::size_t pos_slash = url.find('/', pos_p); - if (pos_c != std::string::npos && (pos_slash == std::string::npos || pos_slash > pos_c)) { + pos_s = url.find('/', pos_p); /* find first slash */ + pos_c = url.find('@', pos_p); /* find end of 'user' or 'user:pass' part */ + if (pos_c != std::string::npos && (pos_s == std::string::npos || pos_s > pos_c)) { std::size_t delim = url.find(':', pos_p); if (delim != std::string::npos && delim < pos_c) { user = url.substr(pos_p, delim - pos_p); @@ -91,7 +92,7 @@ namespace http { } else { user = url.substr(pos_p, pos_c - pos_p); } - pos_p = pos_c + 1; + pos_p = pos_c + 1; } /* hostname[:port][/path] */ pos_c = url.find_first_of(":/", pos_p); diff --git a/tests/test-http-url.cpp b/tests/test-http-url.cpp index d574f1e2..37e9c45e 100644 --- a/tests/test-http-url.cpp +++ b/tests/test-http-url.cpp @@ -104,6 +104,18 @@ int main() { assert(url->query == ""); delete url; + url = new URL; + assert(url->parse("http://user:password@site.com:84/asdasd/@17#frag") == true); + assert(url->schema == "http"); + assert(url->user == "user"); + assert(url->pass == "password"); + assert(url->host == "site.com"); + assert(url->port == 84); + assert(url->path == "/asdasd/@17"); + assert(url->query == ""); + assert(url->frag == "frag"); + delete url; + return 0; } From 2cb5e1a6c21d0d28eecb3c846bd15c457d97d8fa Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 1572/6300] * HTTPProxy.cpp : kill ExtractRequest() --- HTTPProxy.cpp | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index db58d1dc..9dd2cc87 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -52,7 +51,6 @@ namespace proxy { void AsyncSockRead(); void HTTPRequestFailed(const char *message); void RedirectToJumpService(std::string & host); - void ExtractRequest(); bool ValidateHTTPRequest(); void HandleJumpServices(); bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); @@ -142,19 +140,6 @@ namespace proxy { m_state = nstate; } - void HTTPReqHandler::ExtractRequest() - { - LogPrint(eLogDebug, "HTTPProxy: request: ", m_method, " ", m_url); - i2p::http::URL url; - url.parse (m_url); - m_address = url.host; - m_port = url.port; - m_path = url.path; - if (url.query.length () > 0) m_path += "?" + url.query; - if (!m_port) m_port = 80; - LogPrint(eLogDebug, "HTTPProxy: server: ", m_address, ", port: ", m_port, ", path: ", m_path); - } - bool HTTPReqHandler::ValidateHTTPRequest() { if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) @@ -200,7 +185,16 @@ namespace proxy { bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { - ExtractRequest(); //TODO: parse earlier + i2p::http::URL url; + url.parse(m_url); + m_address = url.host; /* < compatibility */ + m_port = url.port; /* < compatibility */ + if (!m_port) { + m_port = (url.schema == "https") ? 443 : 80; + } + url.schema = ""; + url.host = ""; + m_path = url.to_string(); /* < compatibility */ if (!ValidateHTTPRequest()) return false; HandleJumpServices(); From 66c09fc44c9526d4f8d9e347e6899f3b72413de7 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 1573/6300] * HTTPProxy.cpp : HandleJumpServices() -> ExtractAddressHelper() --- HTTPProxy.cpp | 57 +++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 9dd2cc87..a2ed965d 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -52,7 +52,7 @@ namespace proxy { void HTTPRequestFailed(const char *message); void RedirectToJumpService(std::string & host); bool ValidateHTTPRequest(); - void HandleJumpServices(); + bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64); bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); @@ -123,12 +123,14 @@ namespace proxy { /* TODO: don't redirect to webconsole, it's not always work, handle jumpservices here */ i2p::config::GetOption("http.address", url.host); i2p::config::GetOption("http.port", url.port); + url.schema = "http"; url.path = "/"; url.query = "page=jumpservices&address="; url.query += host; res.code = 302; /* redirect */ res.add_header("Location", url.to_string().c_str()); + res.add_header("Connection", "close"); std::string response = res.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(response), @@ -151,40 +153,28 @@ namespace proxy { return true; } - void HTTPReqHandler::HandleJumpServices() + bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64) { - static const char * helpermark1 = "?i2paddresshelper="; - static const char * helpermark2 = "&i2paddresshelper="; - size_t addressHelperPos1 = m_path.rfind (helpermark1); - size_t addressHelperPos2 = m_path.rfind (helpermark2); - size_t addressHelperPos; - if (addressHelperPos1 == std::string::npos) - { - if (addressHelperPos2 == std::string::npos) - return; //Not a jump service - else - addressHelperPos = addressHelperPos2; - } - else - { - if (addressHelperPos2 == std::string::npos) - addressHelperPos = addressHelperPos1; - else if ( addressHelperPos1 > addressHelperPos2 ) - addressHelperPos = addressHelperPos1; - else - addressHelperPos = addressHelperPos2; - } - 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); + const char *param = "i2paddresshelper="; + std::size_t pos = url.query.find(param); + std::size_t len = std::strlen(param); + std::map params; + + if (pos == std::string::npos) + return false; /* not found */ + if (!url.parse_query(params)) + return false; + + std::string value = params["i2paddresshelper"]; + len += value.length(); + b64 = i2p::http::UrlDecode(value); + url.query.replace(pos, len, ""); + return true; } bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { + std::string b64; i2p::http::URL url; url.parse(m_url); m_address = url.host; /* < compatibility */ @@ -196,7 +186,12 @@ namespace proxy { url.host = ""; m_path = url.to_string(); /* < compatibility */ if (!ValidateHTTPRequest()) return false; - HandleJumpServices(); + + /* TODO: notify user */ + if (ExtractAddressHelper(url, b64)) { + i2p::client::context.GetAddressBook ().InsertAddress (url.host, b64); + LogPrint (eLogInfo, "HTTPProxy: added b64 from addresshelper for ", url.host, " to address book"); + } i2p::data::IdentHash identHash; if (str_rmatch(m_address, ".i2p")) From 9fd78b1eb1bb01f8be9139abe2b4d82c5c0275c0 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 1574/6300] * HTTPProxy.cpp : rename variable --- HTTPProxy.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index a2ed965d..c5a355c7 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -31,7 +31,6 @@ namespace proxy { return false; } - static const size_t http_buffer_size = 8192; class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: @@ -57,9 +56,9 @@ namespace proxy { void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); - uint8_t m_http_buff[http_buffer_size]; + uint8_t m_recv_buf[8192]; + std::string m_request; //Data left to be sent. TODO: rename to m_send_buf std::shared_ptr m_sock; - std::string m_request; //Data left to be sent std::string m_url; //URL std::string m_method; //Method std::string m_version; //HTTP version @@ -84,7 +83,7 @@ namespace proxy { LogPrint(eLogError, "HTTPProxy: no socket for read"); return; } - m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), + m_sock->async_receive(boost::asio::buffer(m_recv_buf, sizeof(m_recv_buf)), std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } @@ -302,7 +301,7 @@ namespace proxy { return; } - if (HandleData(m_http_buff, len)) + if (HandleData(m_recv_buf, len)) { if (m_state == DONE) { From 642b01bf0d42159dde7fcc32a87f7ba393868edf Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 1575/6300] * HTTPProxy.cpp : add SanitizeHTTPRequest() --- HTTPProxy.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index c5a355c7..43806b86 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -52,6 +52,7 @@ namespace proxy { void RedirectToJumpService(std::string & host); bool ValidateHTTPRequest(); bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64); + void SanitizeHTTPRequest(i2p::http::HTTPReq & req); bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); @@ -171,6 +172,31 @@ namespace proxy { return true; } + void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req) + { + /* drop common headers */ + req.del_header("Referer"); + req.del_header("Via"); + req.del_header("Forwarded"); + /* drop proxy-disclosing headers */ + std::vector toErase; + for (auto it : req.headers) { + if (it.first.compare(0, 12, "X-Forwarded-") == 0) { + toErase.push_back(it.first); + } else if (it.first.compare(0, 6, "Proxy-") == 0) { + toErase.push_back(it.first); + } else { + /* allow */ + } + } + for (auto header : toErase) { + req.headers.erase(header); + } + /* replace headers */ + req.add_header("Connection", "close", true); /* keep-alive conns not supported yet */ + req.add_header("User-Agent", "MYOB/6.66 (AN/ON)", true); /* privacy */ + } + bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { std::string b64; From 8deb327b3b0a10d76f3cc55a21f52422d4061e1b Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 1576/6300] * HTTPProxy.cpp : * migrate to HTTPReq * change work with buffers * code cleanup --- HTTPProxy.cpp | 264 +++++++++++++++++++------------------------------- 1 file changed, 102 insertions(+), 162 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 43806b86..15e9f539 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -34,47 +34,29 @@ namespace proxy { class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: - enum state - { - GET_METHOD, - GET_HOSTNAME, - GET_HTTPV, - GET_HTTPVNL, //TODO: fallback to finding HOst: header if needed - DONE - }; - void EnterState(state nstate); - bool HandleData(uint8_t *http_buff, std::size_t len); + bool HandleRequest(); void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); void HTTPRequestFailed(const char *message); void RedirectToJumpService(std::string & host); - bool ValidateHTTPRequest(); bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64); void SanitizeHTTPRequest(i2p::http::HTTPReq & req); - bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); - uint8_t m_recv_buf[8192]; - std::string m_request; //Data left to be sent. TODO: rename to m_send_buf + uint8_t m_recv_chunk[8192]; + std::string m_recv_buf; // from client + std::string m_send_buf; // to upstream std::shared_ptr m_sock; - std::string m_url; //URL - std::string m_method; //Method - std::string m_version; //HTTP version - std::string m_address; //Address - std::string m_path; //Path - int m_port; //Port - state m_state;//Parsing state public: HTTPReqHandler(HTTPProxy * parent, std::shared_ptr sock) : - I2PServiceHandler(parent), m_sock(sock) - { EnterState(GET_METHOD); } + I2PServiceHandler(parent), m_sock(sock) {} ~HTTPReqHandler() { Terminate(); } - void Handle () { AsyncSockRead(); } + void Handle () { AsyncSockRead(); } /* overload */ }; void HTTPReqHandler::AsyncSockRead() @@ -84,7 +66,7 @@ namespace proxy { LogPrint(eLogError, "HTTPProxy: no socket for read"); return; } - m_sock->async_receive(boost::asio::buffer(m_recv_buf, sizeof(m_recv_buf)), + m_sock->async_read_some(boost::asio::buffer(m_recv_chunk, sizeof(m_recv_chunk)), std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } @@ -100,8 +82,6 @@ namespace proxy { Done(shared_from_this()); } - /* All hope is lost beyond this point */ - //TODO: handle this apropriately void HTTPReqHandler::HTTPRequestFailed(const char *message) { i2p::http::HTTPRes res; @@ -137,22 +117,6 @@ namespace proxy { std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPReqHandler::EnterState(HTTPReqHandler::state nstate) - { - m_state = nstate; - } - - bool HTTPReqHandler::ValidateHTTPRequest() - { - if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) - { - LogPrint(eLogError, "HTTPProxy: unsupported version: ", m_version); - HTTPRequestFailed("unsupported HTTP version"); - return false; - } - return true; - } - bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64) { const char *param = "i2paddresshelper="; @@ -197,129 +161,110 @@ namespace proxy { req.add_header("User-Agent", "MYOB/6.66 (AN/ON)", true); /* privacy */ } - bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) + /** + * @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() { - std::string b64; + i2p::http::HTTPReq req; i2p::http::URL url; - url.parse(m_url); - m_address = url.host; /* < compatibility */ - m_port = url.port; /* < compatibility */ - if (!m_port) { - m_port = (url.schema == "https") ? 443 : 80; - } - url.schema = ""; - url.host = ""; - m_path = url.to_string(); /* < compatibility */ - if (!ValidateHTTPRequest()) return false; + std::string b64; + int req_len = 0; - /* TODO: notify user */ + req_len = req.parse(m_recv_buf); + + if (req_len == 0) + return false; /* need more data */ + + if (req_len < 0) { + LogPrint(eLogError, "HTTPProxy: unable to parse request"); + HTTPRequestFailed("invalid request"); + return true; /* parse error */ + } + + /* parsing success, now let's look inside request */ + LogPrint(eLogDebug, "HTTPProxy: requested: ", req.uri); + url.parse(req.uri); + + /* TODO: show notice page with original link */ if (ExtractAddressHelper(url, b64)) { i2p::client::context.GetAddressBook ().InsertAddress (url.host, b64); - LogPrint (eLogInfo, "HTTPProxy: added b64 from addresshelper for ", url.host, " to address book"); + std::string message = "added b64 from addresshelper for " + url.host + " to address book"; + LogPrint (eLogInfo, "HTTPProxy: ", message); + message += ", please reload page"; + HTTPRequestFailed(message.c_str()); + return true; /* request processed */ } + SanitizeHTTPRequest(req); + + std::string dest_host = url.host; + uint16_t dest_port = url.port; + /* always set port, even if missing in request */ + if (!dest_port) { + dest_port = (url.schema == "https") ? 443 : 80; + } + /* detect dest_host, set proper 'Host' header in upstream request */ + auto h = req.headers.find("Host"); + if (dest_host != "") { + /* absolute url, replace 'Host' header */ + std::string h = dest_host; + if (dest_port != 0 && dest_port != 80) + h += ":" + std::to_string(dest_port); + req.add_header("Host", h, true); + } else if (h != req.headers.end()) { + /* relative url and 'Host' header provided. transparent proxy mode? */ + i2p::http::URL u; + std::string t = "http://" + h->second; + u.parse(t); + dest_host = u.host; + dest_port = u.port; + } else { + /* relative url and missing 'Host' header */ + const char *message = "Can't detect destination host from request"; + HTTPRequestFailed(message); + return true; + } + + /* check dest_host really exists and inside I2P network */ i2p::data::IdentHash identHash; - if (str_rmatch(m_address, ".i2p")) - { - if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ - RedirectToJumpService(m_address); - return false; + if (str_rmatch(dest_host, ".i2p")) { + if (!i2p::client::context.GetAddressBook ().GetIdentHash (dest_host, identHash)) { + RedirectToJumpService(dest_host); /* unknown host */ + return true; /* request processed */ } + /* TODO: outproxy handler here */ + } else { + std::string message = "Host " + url.host + " not inside i2p network, but outproxy support still missing"; + HTTPRequestFailed(message.c_str()); + LogPrint (eLogWarning, "HTTPProxy: ", message); + return true; } - m_request = m_method; - m_request.push_back(' '); - m_request += m_path; - m_request.push_back(' '); - m_request += m_version; - m_request.push_back('\r'); - m_request.push_back('\n'); - m_request.append("Connection: close\r\n"); - // TODO: temporary shortcut. Must be implemented properly - uint8_t * eol = nullptr; - bool isEndOfHeader = false; - while (!isEndOfHeader && len && (eol = (uint8_t *)memchr (http_buff, '\r', len))) - { - if (eol) - { - *eol = 0; eol++; - if (strncmp ((const char *)http_buff, "Referer", 7) && strncmp ((const char *)http_buff, "Connection", 10)) // strip out referer and connection - { - if (!strncmp ((const char *)http_buff, "User-Agent", 10)) // replace UserAgent - m_request.append("User-Agent: MYOB/6.66 (AN/ON)"); - else - m_request.append ((const char *)http_buff); - m_request.append ("\r\n"); - } - isEndOfHeader = !http_buff[0]; - auto l = eol - http_buff; - http_buff = eol; - len -= l; - if (len > 0) // \r - { - http_buff++; - len--; - } - } - } - m_request.append(reinterpret_cast(http_buff),len); - return true; - } - - bool HTTPReqHandler::HandleData(uint8_t *http_buff, std::size_t len) - { - while (len > 0) - { - //TODO: fallback to finding HOst: header if needed - switch (m_state) - { - case GET_METHOD: - switch (*http_buff) - { - case ' ': EnterState(GET_HOSTNAME); break; - default: m_method.push_back(*http_buff); break; - } - break; - case GET_HOSTNAME: - switch (*http_buff) - { - case ' ': EnterState(GET_HTTPV); break; - default: m_url.push_back(*http_buff); break; - } - break; - case GET_HTTPV: - switch (*http_buff) - { - case '\r': EnterState(GET_HTTPVNL); break; - default: m_version.push_back(*http_buff); break; - } - break; - case GET_HTTPVNL: - switch (*http_buff) - { - case '\n': EnterState(DONE); break; - default: - LogPrint(eLogError, "HTTPProxy: rejected invalid request ending with: ", ((int)*http_buff)); - HTTPRequestFailed("rejected invalid request"); - return false; - } - break; - default: - LogPrint(eLogError, "HTTPProxy: invalid state: ", m_state); - HTTPRequestFailed("invalid parser state"); - return false; - } - http_buff++; - len--; - if (m_state == DONE) - return CreateHTTPRequest(http_buff,len); - } + /* make relative url */ + url.schema = ""; + url.host = ""; + req.uri = url.to_string(); + + /* drop original request from recv buffer */ + m_recv_buf.erase(0, req_len); + /* build new buffer from modified request and data from original request */ + m_send_buf = req.to_string(); + m_send_buf.append(m_recv_buf); + /* connect to destination */ + LogPrint(eLogDebug, "HTTPProxy: connecting to host ", dest_host, ":", dest_port); + GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, + shared_from_this(), std::placeholders::_1), dest_host, dest_port); return true; } + /* will be called after some data received from client */ void HTTPReqHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { - LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes"); + LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes, recv buf: ", m_recv_buf.length(), ", send buf: ", m_send_buf.length()); if(ecode) { LogPrint(eLogWarning, "HTTPProxy: sock recv got error: ", ecode); @@ -327,17 +272,12 @@ namespace proxy { return; } - if (HandleData(m_recv_buf, len)) - { - if (m_state == DONE) - { - LogPrint(eLogDebug, "HTTPProxy: requested: ", m_url); - GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, - shared_from_this(), std::placeholders::_1), m_address, m_port); - } - else - AsyncSockRead(); + m_recv_buf.append(reinterpret_cast(m_recv_chunk), len); + if (HandleRequest()) { + m_recv_buf.clear(); + return; } + AsyncSockRead(); } void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) @@ -356,10 +296,10 @@ namespace proxy { } if (Kill()) return; - LogPrint (eLogDebug, "HTTPProxy: New I2PTunnel connection"); + LogPrint (eLogDebug, "HTTPProxy: Created new I2PTunnel stream"); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler (connection); - connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); + connection->I2PConnect (reinterpret_cast(m_send_buf.data()), m_send_buf.length()); Done (shared_from_this()); } From da2c04f6815e76eb8c74d8b3d5709374deb188c7 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 1577/6300] * HTTPProxy.cpp : show created stream IDs in log --- HTTPProxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 15e9f539..e70eb8ab 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -296,7 +296,7 @@ namespace proxy { } if (Kill()) return; - LogPrint (eLogDebug, "HTTPProxy: Created new I2PTunnel stream"); + LogPrint (eLogDebug, "HTTPProxy: Created new I2PTunnel stream, sSID=", stream->GetSendStreamID(), ", rSID=", stream->GetRecvStreamID()); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (reinterpret_cast(m_send_buf.data()), m_send_buf.length()); From 4d108489840b7cca1dc713fc15a311f944755c18 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 1 Jul 2016 13:07:24 +0300 Subject: [PATCH 1578/6300] Update i2pd_qt.pro --- qt/i2pd_qt/i2pd_qt.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index db3f21c8..567f3d66 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -50,7 +50,7 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../Streaming.h ../../Timestamp.h ../../TransitTunnel.h ../../Transports.h \ ../../TransportSession.h ../../Tunnel.h ../../TunnelBase.h ../../TunnelConfig.h \ ../../TunnelEndpoint.h ../../TunnelGateway.h ../../TunnelPool.h ../../UPnP.h \ - ../../util.h ../../version.h ..//../Gzip.h ../../Tag.h + ../../util.h ../../version.h ../../Gzip.h ../../Tag.h FORMS += mainwindow.ui From 217004e7a5384176a609a8c58d3df6d5c26f75de Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 1 Jul 2016 13:22:47 +0300 Subject: [PATCH 1579/6300] Update appveyor.yml --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 6018bea0..bb81757b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -138,9 +138,9 @@ install: || type c:\projects\instdir\build_openssl.log ) - mklink /J \OpenSSL \stage\OpenSSL-Win%bitness%-vc%msvc%-%type% -- rem already there: mingw-w64-i686-openssl mingw-w64-i686-gcc cmake +- rem already there: mingw-w64-i686-openssl mingw-w64-i686-gcc - if not defined msvc ( - C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -Sy bash pacman pacman-mirrors msys2-runtime msys2-runtime-devel" + C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -Sy bash pacman pacman-mirrors msys2-runtime msys2-runtime-devel cmake" && if "%x64%" == "1" ( C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-x86_64-openssl mingw-w64-x86_64-boost mingw-w64-x86_64-miniupnpc" ) else ( From a9a33c61793000f39f1ae293f3640bffc293aefa Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 1 Jul 2016 08:31:27 -0400 Subject: [PATCH 1580/6300] fixed build error --- util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util.cpp b/util.cpp index 909040c8..89bcda6c 100644 --- a/util.cpp +++ b/util.cpp @@ -419,7 +419,7 @@ namespace net { #ifdef WIN32 LogPrint(eLogError, "NetIface: cannot get address by interface name, not implemented on WIN32"); - return boost::asio::ip::from_string("127.0.0.1"); + return boost::asio::ip::address::from_string("127.0.0.1"); #else int af = (ipv6 ? AF_INET6 : AF_INET); ifaddrs * addrs = nullptr; From 725f939f35a8092a91a531455f4c9da01e2bab2a Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 2 Jul 2016 06:45:15 -0400 Subject: [PATCH 1581/6300] fixed VS build error --- I2PControl.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/I2PControl.cpp b/I2PControl.cpp index aa3c55e8..a55871a3 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -204,17 +204,17 @@ namespace client return; } /* append to json chunk of data from 1st request */ - json.write(buf->begin() + len, bytes_transferred - len); + json.write(buf->data() + len, bytes_transferred - len); remains = req.content_length() - len; /* if request has Content-Length header, fetch rest of data and store to json buffer */ while (remains > 0) { len = ((long int) buf->size() < remains) ? buf->size() : remains; bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), len)); - json.write(buf->begin(), bytes_transferred); + json.write(buf->data(), bytes_transferred); remains -= bytes_transferred; } } else { - json.write(buf->begin(), bytes_transferred); + json.write(buf->data(), bytes_transferred); } LogPrint(eLogDebug, "I2PControl: json from request: ", json.str()); #if GCC47_BOOST149 From 91ec08df4e2203b06882275b859883b519d3c7cd Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 Jul 2016 09:52:18 -0400 Subject: [PATCH 1582/6300] wait for close from other side --- Streaming.cpp | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 2d3c92ce..0951a6f2 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -66,6 +66,7 @@ namespace stream m_SendHandler = nullptr; handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted)); } + m_LocalDestination.DeleteStream (shared_from_this ()); } void Stream::HandleNextPacket (Packet * packet) @@ -220,11 +221,18 @@ namespace stream m_LastReceivedSequenceNumber = receivedSeqn; - if (flags & (PACKET_FLAG_CLOSE | PACKET_FLAG_RESET)) + if (flags & PACKET_FLAG_RESET) { m_Status = eStreamStatusReset; Close (); } + else if (flags & PACKET_FLAG_CLOSE) + { + if (m_Status != eStreamStatusClosed) + SendClose (); + m_Status = eStreamStatusClosed; + Terminate (); + } } void Stream::ProcessAck (Packet * packet) @@ -295,7 +303,7 @@ namespace stream SendBuffer (); } if (m_Status == eStreamStatusClosing) - Close (); // all outgoing messages have been sent + Close (); // check is all outgoing messages have been sent and we can send close } size_t Stream::Send (const uint8_t * buf, size_t len) @@ -495,23 +503,19 @@ namespace stream LogPrint (eLogDebug, "Streaming: Trying to send stream data before closing, sSID=", m_SendStreamID); break; case eStreamStatusReset: - SendClose (); - Terminate (); - m_LocalDestination.DeleteStream (shared_from_this ()); + // TODO: send reset + Terminate (); break; case eStreamStatusClosing: if (m_SentPackets.empty () && m_SendBuffer.eof ()) // nothing to send { m_Status = eStreamStatusClosed; SendClose (); - Terminate (); - m_LocalDestination.DeleteStream (shared_from_this ()); } break; case eStreamStatusClosed: // already closed Terminate (); - m_LocalDestination.DeleteStream (shared_from_this ()); break; default: LogPrint (eLogWarning, "Streaming: Unexpected stream status ", (int)m_Status, "sSID=", m_SendStreamID); @@ -578,15 +582,10 @@ namespace stream m_AckSendTimer.cancel (); } SendPackets (std::vector { packet }); - if (m_Status == eStreamStatusOpen) - { - bool isEmpty = m_SentPackets.empty (); - m_SentPackets.insert (packet); - if (isEmpty) - ScheduleResend (); - } - else - delete packet; + bool isEmpty = m_SentPackets.empty (); + m_SentPackets.insert (packet); + if (isEmpty) + ScheduleResend (); return true; } else From 4cf5ce871f32b91e66cdcd2dea71a0cd0c55690e Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 Jul 2016 17:52:11 -0400 Subject: [PATCH 1583/6300] destroy socket upon receive an ack for close --- Streaming.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 0951a6f2..74046ef7 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -36,7 +36,6 @@ namespace stream Stream::~Stream () { - Terminate (); while (!m_ReceiveQueue.empty ()) { auto packet = m_ReceiveQueue.front (); @@ -302,7 +301,9 @@ namespace stream m_NumResendAttempts = 0; SendBuffer (); } - if (m_Status == eStreamStatusClosing) + if (m_Status == eStreamStatusClosed) + Terminate (); + else if (m_Status == eStreamStatusClosing) Close (); // check is all outgoing messages have been sent and we can send close } From 43ed05d3c299b58781354983a59a66ecfb20e936 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 Jul 2016 11:29:00 -0400 Subject: [PATCH 1584/6300] Create build_notes_android.md --- docs/build_notes_android.md | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/build_notes_android.md diff --git a/docs/build_notes_android.md b/docs/build_notes_android.md new file mode 100644 index 00000000..65b1590c --- /dev/null +++ b/docs/build_notes_android.md @@ -0,0 +1,42 @@ +Pre-requesties +-------------- + +You need to install Android SDK, NDK and QT with android support. + +SDK (choose command line tools only) +https://developer.android.com/studio/index.html +NDK +https://developer.android.com/ndk/downloads/index.html + +QT +https://www.qt.io/download-open-source/ +Choose one for your platform for android +For example QT 5.6 under Linux would be +http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-linux-x64-android-5.6.1-1.run + +You also need Java JDK and Ant. + +QT-Creator +---------- +Open QT-creator that should be installed with QT. +Go to Settings/Anndroid and specify correct paths to SDK and NDK. +If everything is correct you will see two set avaiable: +Android for armeabi-v7a (gcc, qt) and Android for x86 (gcc, qt). + +Dependencies +-------------- +Take following pre-compiled binaries from PurpleI2P's repositories. +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 + + +Building the app +---------------- +Open qt/i2pd_qt/i2pd_qt.pro in the QT-creator. +Change line MAIN_PATH = /path/to/libraries to actual path where did you put the dependancies to. +Select appropriate project (usually armeabi-v7a) and build. +You will find an .apk file in android-build/bin folder. + + From cfc80b491f706425fc32f7e3911a32583be8e7db Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 Jul 2016 11:29:43 -0400 Subject: [PATCH 1585/6300] Update build_notes_android.md --- docs/build_notes_android.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/build_notes_android.md b/docs/build_notes_android.md index 65b1590c..7e3f91fa 100644 --- a/docs/build_notes_android.md +++ b/docs/build_notes_android.md @@ -8,7 +8,7 @@ https://developer.android.com/studio/index.html NDK https://developer.android.com/ndk/downloads/index.html -QT +QT https://www.qt.io/download-open-source/ Choose one for your platform for android For example QT 5.6 under Linux would be @@ -26,10 +26,10 @@ Android for armeabi-v7a (gcc, qt) and Android for x86 (gcc, qt). Dependencies -------------- Take following pre-compiled binaries from PurpleI2P's repositories. -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 +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 Building the app From 76e1114a1f81f89403c9ccee3f3428b763443331 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 Jul 2016 11:30:11 -0400 Subject: [PATCH 1586/6300] Update build_notes_android.md --- docs/build_notes_android.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/build_notes_android.md b/docs/build_notes_android.md index 7e3f91fa..dcce4a4d 100644 --- a/docs/build_notes_android.md +++ b/docs/build_notes_android.md @@ -5,7 +5,7 @@ You need to install Android SDK, NDK and QT with android support. SDK (choose command line tools only) https://developer.android.com/studio/index.html -NDK +NDK https://developer.android.com/ndk/downloads/index.html QT @@ -34,9 +34,9 @@ git clone https://github.com/PurpleI2P/android-ifaddrs.git Building the app ---------------- -Open qt/i2pd_qt/i2pd_qt.pro in the QT-creator. -Change line MAIN_PATH = /path/to/libraries to actual path where did you put the dependancies to. -Select appropriate project (usually armeabi-v7a) and build. +Open qt/i2pd_qt/i2pd_qt.pro in the QT-creator. +Change line MAIN_PATH = /path/to/libraries to actual path where did you put the dependancies to. +Select appropriate project (usually armeabi-v7a) and build. You will find an .apk file in android-build/bin folder. From ce9e0981a2111a624bcc95780f6041321ba8f542 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Wed, 6 Jul 2016 16:03:54 +0000 Subject: [PATCH 1587/6300] Update index.rst --- docs/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.rst b/docs/index.rst index d0471add..13cd9edb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,6 +32,7 @@ Contents: build_requirements build_notes_unix build_notes_windows + build_notes_android configuration family From 953d78da9ef10008cdb2291f2ffa6a4e0e097d63 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Wed, 6 Jul 2016 16:10:30 +0000 Subject: [PATCH 1588/6300] Update build_notes_android.md --- docs/build_notes_android.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/build_notes_android.md b/docs/build_notes_android.md index dcce4a4d..ef53b719 100644 --- a/docs/build_notes_android.md +++ b/docs/build_notes_android.md @@ -1,3 +1,6 @@ +Building on Android +=================== + Pre-requesties -------------- From b64b5d91030503a180197ab54642337bade075a6 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Wed, 6 Jul 2016 17:55:36 +0000 Subject: [PATCH 1589/6300] Update build_notes_android.md --- docs/build_notes_android.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/docs/build_notes_android.md b/docs/build_notes_android.md index ef53b719..71f0b2bc 100644 --- a/docs/build_notes_android.md +++ b/docs/build_notes_android.md @@ -6,16 +6,9 @@ Pre-requesties You need to install Android SDK, NDK and QT with android support. -SDK (choose command line tools only) -https://developer.android.com/studio/index.html -NDK -https://developer.android.com/ndk/downloads/index.html - -QT -https://www.qt.io/download-open-source/ -Choose one for your platform for android -For example QT 5.6 under Linux would be -http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-linux-x64-android-5.6.1-1.run +- [SDK](https://developer.android.com/studio/index.html) (choose command line tools only) +- [NDK](https://developer.android.com/ndk/downloads/index.html) +- [QT](https://www.qt.io/download-open-source/). Choose one for your platform for android. For example QT 5.6 under Linux would be [this file](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-linux-x64-android-5.6.1-1.run ) You also need Java JDK and Ant. @@ -29,17 +22,19 @@ Android for armeabi-v7a (gcc, qt) and Android for x86 (gcc, qt). Dependencies -------------- Take following pre-compiled binaries from PurpleI2P's repositories. +```bash 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 +``` Building the app ---------------- -Open qt/i2pd_qt/i2pd_qt.pro in the QT-creator. -Change line MAIN_PATH = /path/to/libraries to actual path where did you put the dependancies to. -Select appropriate project (usually armeabi-v7a) and build. -You will find an .apk file in android-build/bin folder. +- Open qt/i2pd_qt/i2pd_qt.pro in the QT-creator. +- Change line MAIN_PATH = /path/to/libraries to actual path where did you put the dependancies to. +- Select appropriate project (usually armeabi-v7a) and build. +- You will find an .apk file in android-build/bin folder. From 5be0b7a731ac97b088d4afda18d515e9a20198d6 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Mon, 27 Jun 2016 06:49:11 +0800 Subject: [PATCH 1590/6300] merged --- qt/i2pd_qt/i2pd_qt.pro | 4 ++-- qt/i2pd_qt/mainwindow.cpp | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 567f3d66..e38a4342 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -8,13 +8,13 @@ QMAKE_CXXFLAGS *= -std=c++11 DEFINES += USE_UPNP # change to your own path, where you will store all needed libraries with 'git clone' commands below. -MAIN_PATH = /path/to/libraries +#MAIN_PATH = /path/to/libraries +MAIN_PATH = /home/anon5/git # 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 - BOOST_PATH = $$MAIN_PATH/Boost-for-Android-Prebuilt OPENSSL_PATH = $$MAIN_PATH/OpenSSL-for-Android-Prebuilt MINIUPNP_PATH = $$MAIN_PATH/MiniUPnP-for-Android-Prebuilt diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 0e2ca01c..1b8af253 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -11,8 +11,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)/*, ui(new Ui::MainWindow)*/ #ifndef ANDROID - , - quitting(false) + ,quitting(false) #endif { //ui->setupUi(this); From 1f22b5b083ee5932677c7b42710ee37142cab0a6 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Thu, 7 Jul 2016 02:46:11 +0800 Subject: [PATCH 1591/6300] junk --- qt/i2pd_qt/i2pd_qt.pro | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index e38a4342..90ab6c10 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -8,8 +8,7 @@ QMAKE_CXXFLAGS *= -std=c++11 DEFINES += USE_UPNP # change to your own path, where you will store all needed libraries with 'git clone' commands below. -#MAIN_PATH = /path/to/libraries -MAIN_PATH = /home/anon5/git +MAIN_PATH = /path/to/libraries # git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git From 66dafca61adc04d560c8d9060ab16814ec4c67e1 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 Jul 2016 22:34:24 -0400 Subject: [PATCH 1592/6300] select existing connection for first hop of a tunnel --- TunnelPool.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 515e0f5d..4bd116cb 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -330,8 +330,8 @@ namespace tunnel { if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; - if (numHops <= 0) return true; - auto prevHop = i2p::context.GetSharedRouterInfo(); + if (numHops <= 0) return true; // peers is empty + auto prevHop = i2p::context.GetSharedRouterInfo (); if(i2p::transport::transports.RoutesRestricted()) { /** if routes are restricted prepend trusted first hop */ @@ -340,6 +340,17 @@ namespace tunnel peers.push_back(hop->GetRouterIdentity()); prevHop = hop; } + else if (i2p::transport::transports.GetNumPeers () > 25) + { + auto r = i2p::transport::transports.GetRandomPeer (); + if (r && !r->GetProfile ()->IsBad ()) + { + prevHop = r; + peers.push_back (r->GetRouterIdentity ()); + numHops--; + } + } + for(int i = 0; i < numHops; i++ ) { auto hop = SelectNextHop (prevHop); From 1da5be28717f55a1c7f99bf0cc18f0dcfb20b031 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 7 Jul 2016 22:39:20 -0400 Subject: [PATCH 1593/6300] clean up unconfirmed tags faster --- Garlic.cpp | 42 +++++++++++++++++++++++++----------------- Garlic.h | 3 ++- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index 17740fae..dc29cc5b 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -38,9 +38,6 @@ namespace garlic GarlicRoutingSession::~GarlicRoutingSession () { - for (auto it: m_UnconfirmedTagsMsgs) - delete it.second; - m_UnconfirmedTagsMsgs.clear (); } std::shared_ptr GarlicRoutingSession::GetSharedRoutingPath () @@ -94,18 +91,27 @@ namespace garlic void GarlicRoutingSession::TagsConfirmed (uint32_t msgID) { - auto it = m_UnconfirmedTagsMsgs.find (msgID); - if (it != m_UnconfirmedTagsMsgs.end ()) + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();) { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - UnconfirmedTags * tags = it->second; - if (ts < tags->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) - { - for (int i = 0; i < tags->numTags; i++) - m_SessionTags.push_back (tags->sessionTags[i]); + auto& tags = *it; + if (tags->msgID == msgID) + { + if (ts < tags->tagsCreationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) + { + for (int i = 0; i < tags->numTags; i++) + m_SessionTags.push_back (tags->sessionTags[i]); + } + it = m_UnconfirmedTagsMsgs.erase (it); } - m_UnconfirmedTagsMsgs.erase (it); - delete tags; + else if (ts >= tags->tagsCreationTime + OUTGOING_TAGS_CONFIRMATION_TIMEOUT) + { + if (m_Owner) + m_Owner->RemoveDeliveryStatusSession (tags->msgID); + it = m_UnconfirmedTagsMsgs.erase (it); + } + else + it++; } } @@ -122,11 +128,10 @@ namespace garlic // delete expired unconfirmed tags for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();) { - if (ts >= it->second->tagsCreationTime + OUTGOING_TAGS_CONFIRMATION_TIMEOUT) + if (ts >= (*it)->tagsCreationTime + OUTGOING_TAGS_CONFIRMATION_TIMEOUT) { if (m_Owner) - m_Owner->RemoveDeliveryStatusSession (it->first); - delete it->second; + m_Owner->RemoveDeliveryStatusSession ((*it)->msgID); it = m_UnconfirmedTagsMsgs.erase (it); } else @@ -258,7 +263,10 @@ namespace garlic size += cloveSize; (*numCloves)++; if (newTags) // new tags created - m_UnconfirmedTagsMsgs[msgID] = newTags; + { + newTags->msgID = msgID; + m_UnconfirmedTagsMsgs.emplace_back (newTags); + } m_Owner->DeliveryStatusSent (shared_from_this (), msgID); } else diff --git a/Garlic.h b/Garlic.h index 6d25fd39..21acf561 100644 --- a/Garlic.h +++ b/Garlic.h @@ -83,6 +83,7 @@ namespace garlic { UnconfirmedTags (int n): numTags (n), tagsCreationTime (0) { sessionTags = new SessionTag[numTags]; }; ~UnconfirmedTags () { delete[] sessionTags; }; + uint32_t msgID; int numTags; SessionTag * sessionTags; uint32_t tagsCreationTime; @@ -123,7 +124,7 @@ namespace garlic i2p::crypto::AESKey m_SessionKey; std::list m_SessionTags; int m_NumTags; - std::map m_UnconfirmedTagsMsgs; + std::list > m_UnconfirmedTagsMsgs; LeaseSetUpdateStatus m_LeaseSetUpdateStatus; uint32_t m_LeaseSetUpdateMsgID; From 9b6c229b7138bf5bc1a0bf9c25f721bf1333d311 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 8 Jul 2016 14:17:41 -0400 Subject: [PATCH 1594/6300] remember tunnels selection for following messages --- Garlic.cpp | 15 ++++++++++++--- Garlic.h | 1 + I2CP.cpp | 44 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index dc29cc5b..d48e9d94 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -125,6 +125,14 @@ namespace garlic else it++; } + CleanupUnconfirmedTags (); + return !m_SessionTags.empty () || !m_UnconfirmedTagsMsgs.empty (); + } + + bool GarlicRoutingSession::CleanupUnconfirmedTags () + { + bool ret = false; + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); // delete expired unconfirmed tags for (auto it = m_UnconfirmedTagsMsgs.begin (); it != m_UnconfirmedTagsMsgs.end ();) { @@ -133,12 +141,13 @@ namespace garlic if (m_Owner) m_Owner->RemoveDeliveryStatusSession ((*it)->msgID); it = m_UnconfirmedTagsMsgs.erase (it); + ret = true; } else it++; } - return !m_SessionTags.empty () || !m_UnconfirmedTagsMsgs.empty (); - } + return ret; + } std::shared_ptr GarlicRoutingSession::WrapSingleMessage (std::shared_ptr msg) { @@ -625,7 +634,7 @@ namespace garlic it++; } } - + void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID) { m_DeliveryStatusSessions.erase (msgID); diff --git a/Garlic.h b/Garlic.h index 21acf561..6a92b94a 100644 --- a/Garlic.h +++ b/Garlic.h @@ -98,6 +98,7 @@ namespace garlic std::shared_ptr WrapSingleMessage (std::shared_ptr msg); void MessageConfirmed (uint32_t msgID); bool CleanupExpiredTags (); // returns true if something left + bool CleanupUnconfirmedTags (); // returns true if something has been deleted void SetLeaseSetUpdated () { diff --git a/I2CP.cpp b/I2CP.cpp index 061f220c..0867d719 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -87,23 +87,51 @@ namespace client } bool I2CPDestination::SendMsg (std::shared_ptr msg, std::shared_ptr remote) - { - auto outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); - auto leases = remote->GetNonExpiredLeases (); - if (!leases.empty () && outboundTunnel) + { + auto remoteSession = GetRoutingSession (remote, true); + if (!remoteSession) + { + LogPrint (eLogError, "I2CP: Failed to create remote session"); + return false; + } + auto path = remoteSession->GetSharedRoutingPath (); + std::shared_ptr outboundTunnel; + std::shared_ptr remoteLease; + if (path) + { + if (!remoteSession->CleanupUnconfirmedTags ()) // no stuck tags + { + outboundTunnel = path->outboundTunnel; + remoteLease = path->remoteLease; + } + else + remoteSession->SetSharedRoutingPath (nullptr); + } + else + { + auto outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); + auto leases = remote->GetNonExpiredLeases (); + if (!leases.empty ()) + remoteLease = leases[rand () % leases.size ()]; + if (remoteLease && outboundTunnel) + remoteSession->SetSharedRoutingPath (std::make_shared ( + i2p::garlic::GarlicRoutingPath{outboundTunnel, remoteLease, 10000, 0, 0})); // 10 secs RTT + else + remoteSession->SetSharedRoutingPath (nullptr); + } + if (remoteLease && outboundTunnel) { std::vector msgs; - uint32_t i = rand () % leases.size (); - auto garlic = WrapMessage (remote, msg, true); + auto garlic = remoteSession->WrapSingleMessage (msg); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, - leases[i]->tunnelGateway, leases[i]->tunnelID, + remoteLease->tunnelGateway, remoteLease->tunnelID, garlic }); outboundTunnel->SendTunnelDataMsg (msgs); return true; - } + } else { if (outboundTunnel) From aacb9d9570d61057644d622c0bda2d3e5a76ac34 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Mon, 27 Jun 2016 06:49:11 +0800 Subject: [PATCH 1595/6300] merged --- qt/i2pd_qt/i2pd_qt.pro | 4 ++-- qt/i2pd_qt/mainwindow.cpp | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 567f3d66..e38a4342 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -8,13 +8,13 @@ QMAKE_CXXFLAGS *= -std=c++11 DEFINES += USE_UPNP # change to your own path, where you will store all needed libraries with 'git clone' commands below. -MAIN_PATH = /path/to/libraries +#MAIN_PATH = /path/to/libraries +MAIN_PATH = /home/anon5/git # 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 - BOOST_PATH = $$MAIN_PATH/Boost-for-Android-Prebuilt OPENSSL_PATH = $$MAIN_PATH/OpenSSL-for-Android-Prebuilt MINIUPNP_PATH = $$MAIN_PATH/MiniUPnP-for-Android-Prebuilt diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 0e2ca01c..1b8af253 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -11,8 +11,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)/*, ui(new Ui::MainWindow)*/ #ifndef ANDROID - , - quitting(false) + ,quitting(false) #endif { //ui->setupUi(this); From 40a4c3ccbd5d8e35a1906518147dcb243ebba475 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Thu, 7 Jul 2016 02:46:11 +0800 Subject: [PATCH 1596/6300] junk --- qt/i2pd_qt/i2pd_qt.pro | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index e38a4342..90ab6c10 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -8,8 +8,7 @@ QMAKE_CXXFLAGS *= -std=c++11 DEFINES += USE_UPNP # change to your own path, where you will store all needed libraries with 'git clone' commands below. -#MAIN_PATH = /path/to/libraries -MAIN_PATH = /home/anon5/git +MAIN_PATH = /path/to/libraries # git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git From 69c954760afccbcf7a4069bb973c993cc84ff7c2 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Sun, 10 Jul 2016 04:54:11 +0800 Subject: [PATCH 1597/6300] android without qt initial commit --- .gitignore | 1 + Daemon.h | 14 ++ HTTPServer.cpp | 6 +- android/.gitignore | 4 + android/AndroidManifest.xml | 16 ++ android/jni/Android.mk | 115 ++++++++++++ android/jni/Application.mk | 30 ++++ android/jni/DaemonAndroid.cpp | 170 ++++++++++++++++++ android/jni/DaemonAndroid.h | 86 +++++++++ android/jni/i2pd_android.cpp | 53 ++++++ android/jni/org_purplei2p_i2pd_I2PD_JNI.h | 21 +++ android/project.properties | 14 ++ android/res/drawable/icon.png | Bin 0 -> 8712 bytes .../drawable/itoopie_notification_icon.png | Bin 0 -> 33199 bytes android/res/values/strings.xml | 4 + android/src/org/purplei2p/i2pd/I2PD.java | 20 +++ android/src/org/purplei2p/i2pd/I2PD_JNI.java | 16 ++ 17 files changed, 567 insertions(+), 3 deletions(-) create mode 100644 android/.gitignore create mode 100755 android/AndroidManifest.xml create mode 100755 android/jni/Android.mk create mode 100755 android/jni/Application.mk create mode 100644 android/jni/DaemonAndroid.cpp create mode 100644 android/jni/DaemonAndroid.h create mode 100755 android/jni/i2pd_android.cpp create mode 100644 android/jni/org_purplei2p_i2pd_I2PD_JNI.h create mode 100644 android/project.properties create mode 100644 android/res/drawable/icon.png create mode 100644 android/res/drawable/itoopie_notification_icon.png create mode 100755 android/res/values/strings.xml create mode 100755 android/src/org/purplei2p/i2pd/I2PD.java create mode 100644 android/src/org/purplei2p/i2pd/I2PD_JNI.java diff --git a/.gitignore b/.gitignore index 89a17a3c..b6cffd15 100644 --- a/.gitignore +++ b/.gitignore @@ -237,3 +237,4 @@ pip-log.txt # Sphinx docs/_build +/androidIdea/ diff --git a/Daemon.h b/Daemon.h index fa4f47ec..85b31240 100644 --- a/Daemon.h +++ b/Daemon.h @@ -45,6 +45,20 @@ namespace i2p } }; +#elif defined(ANDROID) +#define Daemon i2p::util::DaemonAndroid::Instance() + // dummy, invoked from android/jni/DaemonAndroid.* + class DaemonAndroid: public i2p::util::Daemon_Singleton + { + public: + + static DaemonAndroid& Instance() + { + static DaemonAndroid instance; + return instance; + } + }; + #elif defined(_WIN32) #define Daemon i2p::util::DaemonWin32::Instance() class DaemonWin32 : public Daemon_Singleton diff --git a/HTTPServer.cpp b/HTTPServer.cpp index cfcf4261..39ced94a 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -377,7 +377,7 @@ namespace http { s << "
    Stop accepting tunnels
    \r\n"; else s << " Start accepting tunnels
    \r\n"; -#if (!defined(WIN32) && !defined(QT_GUI_LIB)) +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) if (Daemon.gracefullShutdownInterval) { s << " Cancel gracefull shutdown ("; s << Daemon.gracefullShutdownInterval; @@ -690,12 +690,12 @@ namespace http { i2p::context.SetAcceptsTunnels (false); else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { i2p::context.SetAcceptsTunnels (false); -#if (!defined(WIN32) && !defined(QT_GUI_LIB)) +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) Daemon.gracefullShutdownInterval = 10*60; #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); -#if (!defined(WIN32) && !defined(QT_GUI_LIB)) +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) Daemon.gracefullShutdownInterval = 0; #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 00000000..8364f857 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,4 @@ +/gen/ +/libs/ +/tests/ +.idea diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100755 index 00000000..58d46ed8 --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/android/jni/Android.mk b/android/jni/Android.mk new file mode 100755 index 00000000..bef6d5ec --- /dev/null +++ b/android/jni/Android.mk @@ -0,0 +1,115 @@ +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_CFLAGS := +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 \ + ../../UPnP.cpp \ + ../../util.cpp \ + ../../i2pd.cpp + +include $(BUILD_SHARED_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_system-gcc-mt-1_53 +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_53_0/$(TARGET_ARCH_ABI)/lib/libboost_system-gcc-mt-1_53.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_53_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_date_time-gcc-mt-1_53 +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_53_0/$(TARGET_ARCH_ABI)/lib/libboost_date_time-gcc-mt-1_53.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_53_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_filesystem-gcc-mt-1_53 +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_53_0/$(TARGET_ARCH_ABI)/lib/libboost_filesystem-gcc-mt-1_53.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_53_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := boost_program_options-gcc-mt-1_53 +LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_53_0/$(TARGET_ARCH_ABI)/lib/libboost_program_options-gcc-mt-1_53.a +LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_53_0/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := crypto +LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.0.2/$(TARGET_ARCH_ABI)/lib/libcrypto.a +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.0.2/include +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := ssl +LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.0.2/$(TARGET_ARCH_ABI)/lib/libssl.a +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.0.2/include +LOCAL_STATIC_LIBRARIES := crypto +include $(PREBUILT_STATIC_LIBRARY) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := miniupnpc +LOCAL_SRC_FILES := $(MINIUPNP_PATH)/miniupnp-2.0/$(TARGET_ARCH_ABI)/lib/libminiupnpc.a +LOCAL_EXPORT_C_INCLUDES := $(MINIUPNP_PATH)/miniupnp-2.0/include +include $(PREBUILT_STATIC_LIBRARY) diff --git a/android/jni/Application.mk b/android/jni/Application.mk new file mode 100755 index 00000000..34f9dd63 --- /dev/null +++ b/android/jni/Application.mk @@ -0,0 +1,30 @@ +#APP_ABI := all +APP_ABI := armeabi-v7a x86 +#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 += -DUSE_UPNP -DANDROID -D__ANDROID__ +ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +APP_CPPFLAGS += -DANDROID_ARM7A +endif + +APP_OPTIM := debug + +# git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git +# git clone https://github.com/PurpleI2P/android-ifaddrs.git +# change to your own +I2PD_LIBS_PATH=/path/to/libraries +BOOST_PATH = $(I2PD_LIBS_PATH)/Boost-for-Android-Prebuilt +OPENSSL_PATH = $(I2PD_LIBS_PATH)/OpenSSL-for-Android-Prebuilt +MINIUPNP_PATH = $(I2PD_LIBS_PATH)/MiniUPnP-for-Android-Prebuilt +IFADDRS_PATH = $(I2PD_LIBS_PATH)/android-ifaddrs diff --git a/android/jni/DaemonAndroid.cpp b/android/jni/DaemonAndroid.cpp new file mode 100644 index 00000000..02f6e3f7 --- /dev/null +++ b/android/jni/DaemonAndroid.cpp @@ -0,0 +1,170 @@ +#include "DaemonAndroid.h" +#include "../../Daemon.h" +//#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; + + /** + * returns 1 if daemon init failed + * returns 0 if daemon initialized and started okay + */ + int start(/*int argc, char* argv[]*/) + { + int result; + + { + //Log.d(TAG"Initialising the daemon..."); + bool daemonInitSuccess = daemon.init(0,0/*argc, argv*/); + if(!daemonInitSuccess) + { + //QMessageBox::critical(0, "Error", "Daemon init failed"); + return 1; + } + //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(); + return 0; + } + } + + //QMessageBox::information(&w, "Debug", "demon stopped"); + //Log.d(TAG"Exiting the application"); + //return result; + } + + void stop() + { + daemon.stop(); + } +} +} + diff --git a/android/jni/DaemonAndroid.h b/android/jni/DaemonAndroid.h new file mode 100644 index 00000000..f6ee618f --- /dev/null +++ b/android/jni/DaemonAndroid.h @@ -0,0 +1,86 @@ +#ifndef DAEMON_ANDROID_H +#define DAEMON_ANDROID_H + +namespace i2p +{ +namespace android +{ + //FIXME currently NOT threadsafe + 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 1 if daemon init failed + * returns 0 if daemon initialized and started okay + */ + int start(); + + // stops the daemon + void stop(); + + /* + class Worker : public QObject + { + Q_OBJECT + public: + + Worker (DaemonAndroidImpl& daemon); + + private: + + DaemonAndroidImpl& m_Daemon; + + public slots: + void startDaemon(); + void restartDaemon(); + void stopDaemon(); + + signals: + void resultReady(); + }; + + class Controller : public QObject + { + Q_OBJECT + QThread workerThread; + public: + Controller(DaemonAndroidImpl& daemon); + ~Controller(); + private: + DaemonAndroidImpl& m_Daemon; + + public slots: + void handleResults(){} + signals: + void startDaemon(); + void stopDaemon(); + void restartDaemon(); + }; + */ +} +} + +#endif // DAEMON_ANDROID_H diff --git a/android/jni/i2pd_android.cpp b/android/jni/i2pd_android.cpp new file mode 100755 index 00000000..b84ec1ac --- /dev/null +++ b/android/jni/i2pd_android.cpp @@ -0,0 +1,53 @@ + +//#include +#include +#include "org_purplei2p_i2pd_I2PD_JNI.h" +#include "DaemonAndroid.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 jint JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon + (JNIEnv * env, jclass clazz) { + return (jint)i2p::android::start(); +} + +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon + (JNIEnv * env, jclass clazz) { + i2p::android::stop(); +} + diff --git a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h new file mode 100644 index 00000000..ddbcace8 --- /dev/null +++ b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_purplei2p_i2pd_I2PD_JNI */ + +#ifndef _Included_org_purplei2p_i2pd_I2PD_JNI +#define _Included_org_purplei2p_i2pd_I2PD_JNI +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_purplei2p_i2pd_I2PD_JNI + * Method: stringFromJNI + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/android/project.properties b/android/project.properties new file mode 100644 index 00000000..c6998b3d --- /dev/null +++ b/android/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-9 diff --git a/android/res/drawable/icon.png b/android/res/drawable/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a5dc7b680ba9dee30161ffb1c9fcd17bbd6ddd84 GIT binary patch literal 8712 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 literal 0 HcmV?d00001 diff --git a/android/res/drawable/itoopie_notification_icon.png b/android/res/drawable/itoopie_notification_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8fbe24689b0af36100a6afa238829d7ce96a6529 GIT binary patch literal 33199 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 + + i2pd + diff --git a/android/src/org/purplei2p/i2pd/I2PD.java b/android/src/org/purplei2p/i2pd/I2PD.java new file mode 100755 index 00000000..6f77c53f --- /dev/null +++ b/android/src/org/purplei2p/i2pd/I2PD.java @@ -0,0 +1,20 @@ +package org.purplei2p.i2pd; + +import android.app.Activity; +import android.widget.TextView; +import android.os.Bundle; + +public class I2PD extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TextView tv = new TextView(this); + tv.setText( "libi2pd.so was compiled with ABI " + getABICompiledWith()); + setContentView(tv); + } + + public String getABICompiledWith() { + return I2PD_JNI.getABICompiledWith(); + } +} diff --git a/android/src/org/purplei2p/i2pd/I2PD_JNI.java b/android/src/org/purplei2p/i2pd/I2PD_JNI.java new file mode 100644 index 00000000..040cca1c --- /dev/null +++ b/android/src/org/purplei2p/i2pd/I2PD_JNI.java @@ -0,0 +1,16 @@ +package org.purplei2p.i2pd; + +public class I2PD_JNI { + public static native String getABICompiledWith(); + /** + * returns 1 if daemon init failed + * returns 0 if daemon initialized and started okay + */ + public static native int startDaemon(); + //should only be called after startDaemon() success + public static native void stopDaemon(); + + static { + System.loadLibrary("i2pd"); + } +} From 4d3a01a5fefc2dbe3246cf7e27bcea93df35c819 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Sun, 10 Jul 2016 09:42:42 +0800 Subject: [PATCH 1598/6300] android ported all + isConnected notif --- android/AndroidManifest.xml | 15 +- android/jni/Android.mk | 4 +- android/jni/Application.mk | 6 +- android/jni/DaemonAndroid.cpp | 102 ++++--- android/jni/DaemonAndroid.h | 21 +- android/jni/i2pd_android.cpp | 14 +- android/jni/org_purplei2p_i2pd_I2PD_JNI.h | 12 + android/project.properties | 2 +- android/res/menu/options_main.xml | 16 ++ android/res/values/strings.xml | 5 + .../org/purplei2p/i2pd/ForegroundService.java | 88 ++++++ android/src/org/purplei2p/i2pd/I2PD.java | 262 +++++++++++++++++- android/src/org/purplei2p/i2pd/I2PD_JNI.java | 13 +- .../i2pd/NetworkStateChangeReceiver.java | 30 ++ 14 files changed, 521 insertions(+), 69 deletions(-) create mode 100644 android/res/menu/options_main.xml create mode 100644 android/src/org/purplei2p/i2pd/ForegroundService.java create mode 100644 android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 58d46ed8..b6cc6f26 100755 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -3,14 +3,23 @@ package="org.purplei2p.i2pd" android:versionCode="1" android:versionName="1.0"> - - - + + + + + + + + + + + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index bef6d5ec..90a679b2 100755 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -12,7 +12,6 @@ LOCAL_STATIC_LIBRARIES := \ miniupnpc LOCAL_LDLIBS := -lz -#LOCAL_CFLAGS := LOCAL_SRC_FILES := DaemonAndroid.cpp i2pd_android.cpp \ $(IFADDRS_PATH)/ifaddrs.c \ ../../HTTPServer.cpp ../../I2PControl.cpp ../../Daemon.cpp ../../Config.cpp \ @@ -58,9 +57,8 @@ LOCAL_SRC_FILES := DaemonAndroid.cpp i2pd_android.cpp \ ../../TunnelEndpoint.cpp \ ../../TunnelGateway.cpp \ ../../TunnelPool.cpp \ - ../../UPnP.cpp \ ../../util.cpp \ - ../../i2pd.cpp + ../../i2pd.cpp ../../UPnP.cpp include $(BUILD_SHARED_LIBRARY) diff --git a/android/jni/Application.mk b/android/jni/Application.mk index 34f9dd63..e8a51add 100755 --- a/android/jni/Application.mk +++ b/android/jni/Application.mk @@ -1,5 +1,7 @@ #APP_ABI := all -APP_ABI := armeabi-v7a x86 +#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 @@ -11,7 +13,7 @@ APP_STL := gnustl_shared # Enable c++11 extentions in source code APP_CPPFLAGS += -std=c++11 -APP_CPPFLAGS += -DUSE_UPNP -DANDROID -D__ANDROID__ +APP_CPPFLAGS += -DANDROID -D__ANDROID__ -DUSE_UPNP ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) APP_CPPFLAGS += -DANDROID_ARM7A endif diff --git a/android/jni/DaemonAndroid.cpp b/android/jni/DaemonAndroid.cpp index 02f6e3f7..038a07fa 100644 --- a/android/jni/DaemonAndroid.cpp +++ b/android/jni/DaemonAndroid.cpp @@ -1,5 +1,9 @@ #include "DaemonAndroid.h" #include "../../Daemon.h" +#include +#include +#include +#include //#include "mainwindow.h" namespace i2p @@ -58,10 +62,11 @@ namespace android } } */ - DaemonAndroidImpl::DaemonAndroidImpl (): + DaemonAndroidImpl::DaemonAndroidImpl () + //: /*mutex(nullptr), */ - m_IsRunning(false), - m_RunningChangedCallback(nullptr) + //m_IsRunning(false), + //m_RunningChangedCallback(nullptr) { } @@ -73,15 +78,15 @@ namespace android bool DaemonAndroidImpl::init(int argc, char* argv[]) { //mutex=new QMutex(QMutex::Recursive); - setRunningCallback(0); - m_IsRunning=false; + //setRunningCallback(0); + //m_IsRunning=false; return Daemon.init(argc,argv); } void DaemonAndroidImpl::start() { //QMutexLocker locker(mutex); - setRunning(true); + //setRunning(true); Daemon.start(); } @@ -89,7 +94,7 @@ namespace android { //QMutexLocker locker(mutex); Daemon.stop(); - setRunning(false); + //setRunning(false); } void DaemonAndroidImpl::restart() @@ -98,7 +103,7 @@ namespace android stop(); start(); } - + /* void DaemonAndroidImpl::setRunningCallback(runningChangedCallback cb) { m_RunningChangedCallback = cb; @@ -119,46 +124,65 @@ namespace android m_RunningChangedCallback(); } } - +*/ static DaemonAndroidImpl daemon; - + static char* argv[1]={strdup("tmp")}; /** - * returns 1 if daemon init failed - * returns 0 if daemon initialized and started okay + * returns error details if failed + * returns "ok" if daemon initialized and started okay */ - int start(/*int argc, char* argv[]*/) + std::string start(/*int argc, char* argv[]*/) { - int result; - + try { - //Log.d(TAG"Initialising the daemon..."); - bool daemonInitSuccess = daemon.init(0,0/*argc, argv*/); - if(!daemonInitSuccess) - { - //QMessageBox::critical(0, "Error", "Daemon init failed"); - return 1; - } - //Log.d(TAG"Initialised, creating the main window..."); - //MainWindow w; - //Log.d(TAG"Before main window.show()..."); - //w.show (); + //int result; { - //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(); - return 0; + //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; } - - //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() diff --git a/android/jni/DaemonAndroid.h b/android/jni/DaemonAndroid.h index f6ee618f..9cc8219b 100644 --- a/android/jni/DaemonAndroid.h +++ b/android/jni/DaemonAndroid.h @@ -1,11 +1,12 @@ #ifndef DAEMON_ANDROID_H #define DAEMON_ANDROID_H +#include + namespace i2p { namespace android { - //FIXME currently NOT threadsafe class DaemonAndroidImpl { public: @@ -13,7 +14,7 @@ namespace android DaemonAndroidImpl (); ~DaemonAndroidImpl (); - typedef void (*runningChangedCallback)(); + //typedef void (*runningChangedCallback)(); /** * @return success @@ -22,21 +23,21 @@ namespace android void start(); void stop(); void restart(); - void setRunningCallback(runningChangedCallback cb); - bool isRunning(); + //void setRunningCallback(runningChangedCallback cb); + //bool isRunning(); private: - void setRunning(bool running); + //void setRunning(bool running); private: //QMutex* mutex; - bool m_IsRunning; - runningChangedCallback m_RunningChangedCallback; + //bool m_IsRunning; + //runningChangedCallback m_RunningChangedCallback; }; /** - * returns 1 if daemon init failed - * returns 0 if daemon initialized and started okay + * returns "ok" if daemon init failed + * returns errinfo if daemon initialized and started okay */ - int start(); + std::string start(); // stops the daemon void stop(); diff --git a/android/jni/i2pd_android.cpp b/android/jni/i2pd_android.cpp index b84ec1ac..40b50345 100755 --- a/android/jni/i2pd_android.cpp +++ b/android/jni/i2pd_android.cpp @@ -3,6 +3,7 @@ #include #include "org_purplei2p_i2pd_I2PD_JNI.h" #include "DaemonAndroid.h" +#include "../../RouterContext.h" JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith (JNIEnv * env, jclass clazz) { @@ -41,9 +42,9 @@ JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith return env->NewStringUTF(ABI); } -JNIEXPORT jint JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon +JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon (JNIEnv * env, jclass clazz) { - return (jint)i2p::android::start(); + return env->NewStringUTF(i2p::android::start().c_str()); } JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon @@ -51,3 +52,12 @@ JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon 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; +} diff --git a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h index ddbcace8..04923d22 100644 --- a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h +++ b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h @@ -15,6 +15,18 @@ extern "C" { 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 diff --git a/android/project.properties b/android/project.properties index c6998b3d..7ce68660 100644 --- a/android/project.properties +++ b/android/project.properties @@ -11,4 +11,4 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-9 +target=android-24 diff --git a/android/res/menu/options_main.xml b/android/res/menu/options_main.xml new file mode 100644 index 00000000..388dfd83 --- /dev/null +++ b/android/res/menu/options_main.xml @@ -0,0 +1,16 @@ + + + + diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml index b02181c8..8c78e88b 100755 --- a/android/res/values/strings.xml +++ b/android/res/values/strings.xml @@ -1,4 +1,9 @@ i2pd + i2pd started + Quit + Graceful Quit + Graceful quit is already in progress + Graceful quit is in progress diff --git a/android/src/org/purplei2p/i2pd/ForegroundService.java b/android/src/org/purplei2p/i2pd/ForegroundService.java new file mode 100644 index 00000000..6ff826c4 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/ForegroundService.java @@ -0,0 +1,88 @@ +package org.purplei2p.i2pd; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; +import android.util.Log; + +public class ForegroundService extends Service { +// private NotificationManager mNM; + + // Unique Identification Number for the Notification. + // We use it on Notification start, and to cancel it. + private int NOTIFICATION = R.string.i2pd_started; + + /** + * Class for clients to access. Because we know this service always + * runs in the same process as its clients, we don't need to deal with + * IPC. + */ + public class LocalBinder extends Binder { + ForegroundService getService() { + return ForegroundService.this; + } + } + + @Override + public void onCreate() { +// mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + + // Display a notification about us starting. We put an icon in the status bar. + showNotification(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.i("ForegroundService", "Received start id " + startId + ": " + intent); + return START_NOT_STICKY; + } + + @Override + public void onDestroy() { + // Cancel the persistent notification. + //mNM.cancel(NOTIFICATION); + stopForeground(true); + + // Tell the user we stopped. + //Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show(); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + // This is the object that receives interactions from clients. See + // RemoteService for a more complete example. + private final IBinder mBinder = new LocalBinder(); + + /** + * Show a notification while this service is running. + */ + private void showNotification() { + // In this sample, we'll use the same text for the ticker and the expanded notification + CharSequence text = getText(R.string.i2pd_started); + + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, I2PD.class), 0); + + // Set the info for the views that show in the notification panel. + Notification notification = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.itoopie_notification_icon) // the status icon + .setTicker(text) // the status text + .setWhen(System.currentTimeMillis()) // the time stamp + .setContentTitle(getText(R.string.app_name)) // the label of the entry + .setContentText(text) // the contents of the entry + .setContentIntent(contentIntent) // The intent to send when the entry is clicked + .build(); + + // Send the notification. + //mNM.notify(NOTIFICATION, notification); + startForeground(NOTIFICATION, notification); + } +} \ No newline at end of file diff --git a/android/src/org/purplei2p/i2pd/I2PD.java b/android/src/org/purplei2p/i2pd/I2PD.java index 6f77c53f..ef22f941 100755 --- a/android/src/org/purplei2p/i2pd/I2PD.java +++ b/android/src/org/purplei2p/i2pd/I2PD.java @@ -1,20 +1,272 @@ 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.widget.TextView; +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 static Throwable loadLibsThrowable; + static { + try { + I2PD_JNI.loadLibraries(); + } catch (Throwable tr) { + loadLibsThrowable = tr; + } + } + private String daemonStartResult="N/A"; + private boolean destroyed=false; + private TextView textView; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + destroyed=false; - TextView tv = new TextView(this); - tv.setText( "libi2pd.so was compiled with ABI " + getABICompiledWith()); - setContentView(tv); + //set the app be foreground (do not unload when RAM needed) + doBindService(); + + textView = new TextView(this); + setContentView(textView); + if (loadLibsThrowable != null) { + textView.setText(throwableToString(loadLibsThrowable)+"\r\n"); + return; + } + try { + textView.setText( + "libi2pd.so was compiled with ABI " + getABICompiledWith() + "\r\n"+ + "Starting daemon... "); + new Thread(new Runnable(){ + + @Override + public void run() { + try { + doStartDaemon(); + } catch (final Throwable tr) { + appendThrowable(tr); + } + } + + },"i2pdDaemonStarting").start(); + } catch (Throwable tr) { + textView.setText(textView.getText().toString()+throwableToString(tr)); + } } - public String getABICompiledWith() { + @Override + protected void onDestroy() { + super.onDestroy(); + localDestroy(); + } + + private synchronized void localDestroy() { + if(destroyed)return; + destroyed=true; + if(gracefulQuitTimer!=null) { + gracefulQuitTimer.cancel(); + gracefulQuitTimer = null; + } + try{ + doUnbindService(); + }catch(Throwable tr){ + Log.e(TAG, "", tr); + } + if("ok".equals(daemonStartResult)) { + try { + I2PD_JNI.stopDaemon(); + } catch (Throwable tr) { + Log.e(TAG, "error", tr); + } + } + } + + private CharSequence throwableToString(Throwable tr) { + StringWriter sw = new StringWriter(8192); + PrintWriter pw = new PrintWriter(sw); + tr.printStackTrace(pw); + pw.close(); + return sw.toString(); + } + + public String getABICompiledWith() { return I2PD_JNI.getABICompiledWith(); } + + private synchronized void doStartDaemon() { + if(destroyed)return; + daemonStartResult = I2PD_JNI.startDaemon(); + runOnUiThread(new Runnable(){ + + @Override + public void run() { + synchronized (I2PD.this) { + if(destroyed)return; + textView.setText( + textView.getText().toString()+ + "start result: "+daemonStartResult+"\r\n" + ); + } + } + + }); + } + + private void appendThrowable(final Throwable tr) { + runOnUiThread(new Runnable(){ + + @Override + public void run() { + synchronized (I2PD.this) { + if(destroyed)return; + textView.setText(textView.getText().toString()+throwableToString(tr)+"\r\n"); + } + } + }); + } + +// 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 { + localDestroy(); + 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); + } + } + + private Timer gracefulQuitTimer; + private synchronized void gracefulQuit() { + if(gracefulQuitTimer!=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{ + synchronized (I2PD.this) { + if("ok".equals(daemonStartResult)) { + I2PD_JNI.stopAcceptingTunnels(); + gracefulQuitTimer = new Timer(true); + gracefulQuitTimer.schedule(new TimerTask(){ + + @Override + public void run() { + quit(); + } + + }, 10*60*1000/*millis*/); + }else{ + quit(); + } + } + } catch(Throwable tr) { + Log.e(TAG,"",tr); + } + } + + },"gracQuitInit").start(); + } } diff --git a/android/src/org/purplei2p/i2pd/I2PD_JNI.java b/android/src/org/purplei2p/i2pd/I2PD_JNI.java index 040cca1c..4f5913e3 100644 --- a/android/src/org/purplei2p/i2pd/I2PD_JNI.java +++ b/android/src/org/purplei2p/i2pd/I2PD_JNI.java @@ -3,14 +3,19 @@ package org.purplei2p.i2pd; public class I2PD_JNI { public static native String getABICompiledWith(); /** - * returns 1 if daemon init failed - * returns 0 if daemon initialized and started okay + * returns error info if failed + * returns "ok" if daemon initialized and started okay */ - public static native int startDaemon(); + 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); - static { + public static void loadLibraries() { + //System.loadLibrary("gnustl_shared"); System.loadLibrary("i2pd"); } } diff --git a/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java b/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java new file mode 100644 index 00000000..e2f284b0 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java @@ -0,0 +1,30 @@ +package org.purplei2p.i2pd; + +import android.util.Log; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +public class NetworkStateChangeReceiver extends BroadcastReceiver { + + private static final String TAG = "i2pd"; + + //api level 1 + @Override + public void onReceive(final Context context, final Intent intent) { + Log.d(TAG,"Network state change"); + try { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); + boolean isConnected = activeNetworkInfo!=null && activeNetworkInfo.isConnected(); + // https://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html?hl=ru + // boolean isWiFi = activeNetworkInfo!=null && (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI); + + I2PD_JNI.onNetworkStateChanged(isConnected); + } catch (Throwable tr) { + Log.d(TAG,"",tr); + } + } +} From 7dbbe5a7d855cfd6b9c1949b01563f8b44876e8f Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 11 Jul 2016 14:35:59 -0400 Subject: [PATCH 1599/6300] wait until tunnels get created --- BOB.cpp | 30 ++++++++++++++++++++++++++---- BOB.h | 4 ++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index af1fb19a..c074b53b 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -202,9 +202,9 @@ namespace client } BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner): - m_Owner (owner), m_Socket (m_Owner.GetService ()), m_ReceiveBufferOffset (0), - m_IsOpen (true), m_IsQuiet (false), m_InPort (0), m_OutPort (0), - m_CurrentDestination (nullptr) + m_Owner (owner), m_Socket (m_Owner.GetService ()), m_Timer (m_Owner.GetService ()), + m_ReceiveBufferOffset (0), m_IsOpen (true), m_IsQuiet (false), + m_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr) { } @@ -364,9 +364,31 @@ namespace client if (m_OutPort && !m_Address.empty ()) m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet); m_CurrentDestination->Start (); - SendReplyOK ("tunnel starting"); + 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::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) { auto dest = m_Owner.FindDestination (m_Nickname); diff --git a/BOB.h b/BOB.h index b73e390e..a6a6fa11 100644 --- a/BOB.h +++ b/BOB.h @@ -42,6 +42,8 @@ namespace client 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: @@ -173,6 +175,7 @@ namespace client 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); @@ -184,6 +187,7 @@ 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; From 6b8469e9a30daf81a7d9412a1a74f613796fc777 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 12 Jul 2016 00:00:00 +0000 Subject: [PATCH 1600/6300] * docs/configuration.md : fix markdown --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 1b3e899b..67061ccd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -14,7 +14,7 @@ If you are upgrading your very old router (< 2.3.0) see also [this](config_opts_ * --pidfile= - Where to write pidfile (dont write by default) * --log= - Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility) * --logfile= - Path to logfile (default - autodetect) -* --loglevel= - Log messages above this level (debug, *info, warn, error) +* --loglevel= - Log messages above this level (debug, info, warn, error) * --datadir= - Path to storage of i2pd data (RI, keys, peer profiles, ...) * --host= - Router external IP for incoming connections * --port= - Port to listen for incoming connections (default: auto) From fda3cd5fe77bafb8fdcb1c2cdfe3059da0b805cd Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 12 Jul 2016 00:00:00 +0000 Subject: [PATCH 1601/6300] * Config.cpp : add --upnp.enabled option --- Config.cpp | 6 ++++++ docs/configuration.md | 2 ++ 2 files changed, 8 insertions(+) diff --git a/Config.cpp b/Config.cpp index 27f0fe1d..28cb5223 100644 --- a/Config.cpp +++ b/Config.cpp @@ -126,6 +126,11 @@ namespace config { ("i2pcontrol.key", value()->default_value("i2pcontrol.key.pem"), "I2PCP connection cerificate key") ; + options_description upnp("UPnP options"); + upnp.add_options() + ("upnp.enabled", value()->default_value(false), "Enable or disable UPnP: automatic port forwarding") + ; + options_description precomputation("Precomputation options"); precomputation.add_options() ("precomputation.elgamal", @@ -153,6 +158,7 @@ namespace config { .add(bob) .add(i2cp) .add(i2pcontrol) + .add(upnp) .add(precomputation) .add(trust) ; diff --git a/docs/configuration.md b/docs/configuration.md index 67061ccd..9a0f0197 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -68,6 +68,8 @@ All options below still possible in cmdline, but better write it in config file: * --i2pcontrol.port= - Port of I2P control service. Usually 7650. I2PControl is off if not specified * --i2pcontrol.enabled= - If I2P control is enabled. false by default +* --upnp.enabled= - Enable or disable UPnP, false by default + * --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 From 9dc5a4fce39d85e36121be085eae2278e83b1864 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 12 Jul 2016 00:00:00 +0000 Subject: [PATCH 1602/6300] * UPnP.{cpp,h} : cleanup & add class stub if opt-out --- UPnP.cpp | 13 ++++++++----- UPnP.h | 13 +++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/UPnP.cpp b/UPnP.cpp index 477342b3..3a6d0f5f 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -27,6 +27,7 @@ namespace transport void UPnP::Stop () { + LogPrint(eLogInfo, "UPnP: stopping"); if (m_Thread) { m_Thread->join (); @@ -37,6 +38,7 @@ namespace transport void UPnP::Start() { + LogPrint(eLogInfo, "UPnP: starting"); m_Thread = new std::thread (std::bind (&UPnP::Run, this)); } @@ -163,10 +165,11 @@ namespace transport m_Devlist = 0; FreeUPNPUrls (&m_upnpUrls); } - } } - - -#endif - +#else /* USE_UPNP */ +namespace i2p { +namespace transport { +} +} +#endif /* USE_UPNP */ diff --git a/UPnP.h b/UPnP.h index 0a000177..2831989a 100644 --- a/UPnP.h +++ b/UPnP.h @@ -52,5 +52,18 @@ namespace transport } } +#else // USE_UPNP +namespace i2p { +namespace transport { + /* class stub */ + class UPnP { + public: + UPnP () {}; + ~UPnP () {}; + void Start () { LogPrint(eLogWarning, "UPnP: this module was disabled at compile-time"); } + void Stop () {}; + }; +} +} #endif // USE_UPNP #endif // __UPNP_H__ From 9f5be52a9749b0f3f7583057a94a985862079f92 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 12 Jul 2016 00:00:00 +0000 Subject: [PATCH 1603/6300] * UPnP.cpp : tune log messages --- UPnP.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/UPnP.cpp b/UPnP.cpp index 3a6d0f5f..fbb8639b 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -82,20 +82,20 @@ namespace transport r = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); if(r != UPNPCOMMAND_SUCCESS) { - LogPrint (eLogError, "UPnP: UPNP_GetExternalIPAddress () returned ", r); + LogPrint (eLogError, "UPnP: UPNP_GetExternalIPAddress() returned ", r); return; } else { if (m_externalIPAddress[0]) { - LogPrint (eLogInfo, "UPnP: ExternalIPAddress = ", m_externalIPAddress); + LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress); i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress)); return; } else { - LogPrint (eLogError, "UPnP: GetExternalIPAddress failed."); + LogPrint (eLogError, "UPnP: GetExternalIPAddress() failed."); return; } } @@ -121,12 +121,12 @@ namespace transport r = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0"); if (r!=UPNPCOMMAND_SUCCESS) { - LogPrint (eLogError, "UPnP: AddPortMapping (", strPort.c_str () ,", ", strPort.c_str () ,", ", m_NetworkAddr, ") failed with code ", r); + LogPrint (eLogError, "UPnP: AddPortMapping (", m_NetworkAddr, ":", strPort, ") 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() ,")"); + 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 @@ -156,7 +156,7 @@ namespace transport } int r = 0; r = UPNP_DeletePortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), 0); - LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", r, "\n"); + LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", r); } void UPnP::Close () From 9340bf385e1cb3e0e1b03a1c6a44d4383d5561c1 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 12 Jul 2016 00:00:00 +0000 Subject: [PATCH 1604/6300] * Daemon.cpp : make upnp configurable via options --- Daemon.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index ab2c052f..00bfd817 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -22,11 +22,8 @@ #include "I2PControl.h" #include "ClientContext.h" #include "Crypto.h" -#include "util.h" - -#ifdef USE_UPNP #include "UPnP.h" -#endif +#include "util.h" namespace i2p { @@ -40,10 +37,7 @@ namespace i2p std::unique_ptr httpServer; std::unique_ptr m_I2PControlService; - -#ifdef USE_UPNP - i2p::transport::UPnP m_UPnP; -#endif + std::unique_ptr UPnP; }; Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {} @@ -249,10 +243,12 @@ namespace i2p LogPrint(eLogInfo, "Daemon: starting NetDB"); i2p::data::netdb.Start(); -#ifdef USE_UPNP - LogPrint(eLogInfo, "Daemon: starting UPnP"); - d.m_UPnP.Start (); -#endif + bool upnp; i2p::config::GetOption("upnp.enabled", upnp); + if (upnp) { + d.UPnP = std::unique_ptr(new i2p::transport::UPnP); + d.UPnP->Start (); + } + bool ntcp; i2p::config::GetOption("ntcp", ntcp); bool ssu; i2p::config::GetOption("ssu", ssu); LogPrint(eLogInfo, "Daemon: starting Transports"); @@ -304,10 +300,12 @@ namespace i2p i2p::client::context.Stop(); LogPrint(eLogInfo, "Daemon: stopping Tunnels"); i2p::tunnel::tunnels.Stop(); -#ifdef USE_UPNP - LogPrint(eLogInfo, "Daemon: stopping UPnP"); - d.m_UPnP.Stop (); -#endif + + if (d.UPnP) { + d.UPnP->Stop (); + d.UPnP = nullptr; + } + LogPrint(eLogInfo, "Daemon: stopping Transports"); i2p::transport::transports.Stop(); LogPrint(eLogInfo, "Daemon: stopping NetDB"); From 762b21f80981fe6f750d1951421ec2f347c6bd20 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 12 Jul 2016 00:00:00 +0000 Subject: [PATCH 1605/6300] * Streaming.cpp : tune log messages --- Streaming.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 74046ef7..3806d97d 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -201,7 +201,7 @@ namespace stream memset (const_cast(optionData), 0, signatureLen); if (!m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature)) { - LogPrint (eLogError, "Streaming: Signature verification failed"); + LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); Close (); flags |= PACKET_FLAG_CLOSE; } @@ -222,6 +222,7 @@ namespace stream if (flags & PACKET_FLAG_RESET) { + LogPrint (eLogDebug, "Streaming: closing stream sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ": reset flag received in packet #", receivedSeqn); m_Status = eStreamStatusReset; Close (); } @@ -495,6 +496,7 @@ namespace stream void Stream::Close () { + LogPrint(eLogDebug, "Streaming: closing stream with sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ", status=", m_Status); switch (m_Status) { case eStreamStatusOpen: @@ -668,7 +670,7 @@ namespace stream // check for resend attempts if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS) { - LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, sSID=", m_SendStreamID); + LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); m_Status = eStreamStatusReset; Close (); return; @@ -703,7 +705,7 @@ namespace stream case 4: if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); UpdateCurrentRemoteLease (); // pick another lease - LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream with sSID=", m_SendStreamID); + LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); break; case 3: // pick another outbound tunnel @@ -725,7 +727,7 @@ namespace stream { if (m_LastReceivedSequenceNumber < 0) { - LogPrint (eLogWarning, "Streaming: SYN has not been recived after ", ACK_SEND_TIMEOUT, " milliseconds after follow on, terminate sSID=", m_SendStreamID); + LogPrint (eLogWarning, "Streaming: SYN has not been received after ", ACK_SEND_TIMEOUT, " milliseconds after follow on, terminate rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); m_Status = eStreamStatusReset; Close (); return; From 174430e3b58e3833e091f43bb4715eab2adb32a0 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 12 Jul 2016 00:00:00 +0000 Subject: [PATCH 1606/6300] * HTTPServer.cpp : rename command --- HTTPServer.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 39ced94a..a98c1489 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -73,14 +73,13 @@ namespace http { const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; const char HTTP_PAGE_JUMPSERVICES[] = "jumpservices"; const char HTTP_PAGE_COMMANDS[] = "commands"; - const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels"; - const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels"; + const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit"; + const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit"; const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start"; const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; - const char HTTP_PARAM_BASE32_ADDRESS[] = "b32"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; @@ -374,9 +373,9 @@ namespace http { s << " Run peer test
    \r\n"; //s << " Reload config
    \r\n"; if (i2p::context.AcceptsTunnels ()) - s << " Stop accepting tunnels
    \r\n"; + s << " Decline transit tunnels
    \r\n"; else - s << " Start accepting tunnels
    \r\n"; + s << " Accept transit tunnels
    \r\n"; #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) if (Daemon.gracefullShutdownInterval) { s << " Cancel gracefull shutdown ("; @@ -684,9 +683,9 @@ namespace http { i2p::transport::transports.PeerTest (); else if (cmd == HTTP_COMMAND_RELOAD_CONFIG) i2p::client::context.ReloadConfig (); - else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS) + else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT) i2p::context.SetAcceptsTunnels (true); - else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS) + else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT) i2p::context.SetAcceptsTunnels (false); else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { i2p::context.SetAcceptsTunnels (false); From 4ac4f44ba795c0f3a2a8c4b0e042644ed3e78a41 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 Jul 2016 12:37:39 -0400 Subject: [PATCH 1607/6300] limit delayed messages queue size --- Transports.cpp | 23 ++++++++++++++++------- Transports.h | 1 + 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index b4130dfd..1efbfb2f 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -264,8 +264,17 @@ namespace transport it->second.sessions.front ()->SendI2NPMessages (msgs); else { - for (auto it1: msgs) - it->second.delayedMessages.push_back (it1); + if (it->second.delayedMessages.size () < MAX_NUM_DELAYED_MESSAGES) + { + for (auto it1: msgs) + it->second.delayedMessages.push_back (it1); + } + else + { + LogPrint (eLogWarning, "Transports: delayed messages queue size exceeds ", MAX_NUM_DELAYED_MESSAGES); + std::unique_lock l(m_PeersMutex); + m_Peers.erase (it); + } } } @@ -337,7 +346,7 @@ namespace transport } LogPrint (eLogError, "Transports: No NTCP or SSU addresses available"); peer.Done (); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (ident); return false; } @@ -369,7 +378,7 @@ namespace transport else { LogPrint (eLogError, "Transports: RouterInfo not found, Failed to send messages"); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } @@ -413,7 +422,7 @@ namespace transport } } LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ()); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it1); } } @@ -455,7 +464,7 @@ namespace transport } } LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ()); - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it1); } } @@ -590,7 +599,7 @@ namespace transport ConnectToPeer (ident, it->second); else { - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } diff --git a/Transports.h b/Transports.h index 9b231802..2902e052 100644 --- a/Transports.h +++ b/Transports.h @@ -66,6 +66,7 @@ namespace transport }; const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds + const int MAX_NUM_DELAYED_MESSAGES = 50; class Transports { public: From c664be52d79059425fb59314d691718ca704600f Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 Jul 2016 16:26:36 -0400 Subject: [PATCH 1608/6300] limit outgoing queue size --- NTCPSession.cpp | 12 ++++++++++-- NTCPSession.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 9a2b6687..d2c03857 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -713,8 +713,16 @@ namespace transport if (m_IsTerminated) return; if (m_IsSending) { - for (auto it: msgs) - m_SendQueue.push_back (it); + if (m_SendQueue.size () < NTCP_MAX_OUTGOING_QUEUE_SIZE) + { + for (auto it: msgs) + m_SendQueue.push_back (it); + } + else + { + LogPrint (eLogWarning, "NTCP: outgoing messages queue size exceeds ", NTCP_MAX_OUTGOING_QUEUE_SIZE); + Terminate (); + } } else Send (msgs); diff --git a/NTCPSession.h b/NTCPSession.h index 2a60f1dc..59b6bb58 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -40,6 +40,7 @@ namespace transport const size_t NTCP_DEFAULT_PHASE3_SIZE = 2/*size*/ + i2p::data::DEFAULT_IDENTITY_SIZE/*387*/ + 4/*ts*/ + 15/*padding*/ + 40/*signature*/; // 448 const int NTCP_BAN_EXPIRATION_TIMEOUT = 70; // in second const int NTCP_CLOCK_SKEW = 60; // in seconds + const int NTCP_MAX_OUTGOING_QUEUE_SIZE = 200; // how many messages we can queue up class NTCPServer; class NTCPSession: public TransportSession, public std::enable_shared_from_this From fac6229e43140f7e9b1da2fbd2cafec2788f7bac Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 13 Jul 2016 01:01:47 +0000 Subject: [PATCH 1609/6300] * cmake debug (closes #562) --- build/CMakeLists.txt | 3 +++ docs/build_notes_unix.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index edde2e06..7f9b8c66 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -3,6 +3,9 @@ cmake_minimum_required ( VERSION 2.8.12 ) cmake_policy( VERSION 2.8.12 ) project ( "i2pd" ) +# for debugging +#set(CMAKE_VERBOSE_MAKEFILE on) + # configurale options option(WITH_AESNI "Use AES-NI instructions set" OFF) option(WITH_HARDENING "Use hardening compiler flags" OFF) diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index cdde1ee7..795e408a 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -16,7 +16,7 @@ Let's clone the repository and start building the i2pd: git clone https://github.com/PurpleI2P/i2pd.git cd i2pd/build cmake -DCMAKE_BUILD_TYPE=Release # more options could be passed, see "CMake Options" -make +make # you may add VERBOSE=1 to cmdline for debugging ``` After successfull build i2pd could be installed with: From 814f60a51242fbe3f68cfb12d3cf53dc4c437463 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Wed, 13 Jul 2016 11:03:11 +0800 Subject: [PATCH 1610/6300] reworked android. added a build script. --- android/.gitignore | 9 +- android/build.xml | 97 ++++++++++ android/libs/.gitignore | 1 + android/libs/android-support-v4.jar | Bin 0 -> 1422188 bytes android/proguard-project.txt | 20 ++ .../org/purplei2p/i2pd/DaemonSingleton.java | 108 +++++++++++ android/src/org/purplei2p/i2pd/I2PD.java | 175 ++++++++---------- 7 files changed, 305 insertions(+), 105 deletions(-) create mode 100644 android/build.xml create mode 100644 android/libs/.gitignore create mode 100644 android/libs/android-support-v4.jar create mode 100644 android/proguard-project.txt create mode 100644 android/src/org/purplei2p/i2pd/DaemonSingleton.java diff --git a/android/.gitignore b/android/.gitignore index 8364f857..1ecaafbe 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,4 +1,7 @@ -/gen/ -/libs/ -/tests/ +gen +tests .idea +local.properties +build.sh +bin +log* \ No newline at end of file diff --git a/android/build.xml b/android/build.xml new file mode 100644 index 00000000..23e9f065 --- /dev/null +++ b/android/build.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/libs/.gitignore b/android/libs/.gitignore new file mode 100644 index 00000000..e4e4e6c1 --- /dev/null +++ b/android/libs/.gitignore @@ -0,0 +1 @@ +armeabi-v7a diff --git a/android/libs/android-support-v4.jar b/android/libs/android-support-v4.jar new file mode 100644 index 0000000000000000000000000000000000000000..2ff47f4f3f4485d6c5e0ddde7869a09a41557bb1 GIT binary patch literal 1422188 zcmdSAWmMJe`YnufNq2X5H`3kR-Q6wH-Q5k+(ka~yBGR3Ll%yb@weYFE|9k5>ll1nKUvImuX$ZFZbca|a5NBTXlRh|d}~RNfBb_10SA#2Qx&3@k{4%u8Uq0V15uQL zf(E_<^pBS*{?~`m0I&G#VL2grDRD6sRR%fnuW}RPvNH4xvoB@nX{IKo8LA~$^*V>TD~fI)7!X0_T=Fot$h{wV z{rbH>$l>-}Z$^*{6ej3(#MFmyNE(WOeJ19{Vz%O{hD04|NA&#&C7aB0*-4YjL{VI; zWOzx2cP@b;47Osxmh`z zxhR@B+gZ7|06s`E4QDG?v!5SDiW0vJm=Ka*(yG^fw6`@e21z(L{Z)LjdLaxrs5HE` zrd=YjZR?7R`z`1bJDmR;LFw!_SiNl2`#~9;U)P!P49*BaGP%Ov;S)!^lYl;;q0Mmg zdp)aWZVJaQt;d~%u~0Ua-bY&tmvrN)ZasdQU{{lqH20GDHH|dyZu69B@0S0_N6|_2 zt+yIDwJxt0m`uk0QF2VnYtD#PR`~$7?Lz)u{bsygPfdLU`(P68H1E9V9OT?*vjku7rvys=o z9ClDzZ9FiJd9Pq>IUlj4h)%O_CBL>AceZ9g$})BD$p} zANTSieXG9JkYo-5dwdP!ASig%GwDkvovI%Kg+f1ph}{6GJ)w{(?-i7grOBk@;@;MA zvIN)@79_lf@T3a;!Ryr8Pdx1YKfW*FiP(Jd;L=gH#j1goSgLNA>`|=uKs*JU^#$09 z1$W!|2KSA9n_wOU$HP_3WSEz1Ie6Sw+}ux~KvncNv~cIEZ*(UO41tYA3&;x6(k}>7_dtPEBs6)zJ zoQ#vKTlY%=fsOSSu8ziAGkpmmCZXQR829PzEEJ*Ic~l*l5haFPFQY_9x%o#!@CJ9u zq$+|Ft?CV1v9RLsqfD)%HUjYz3kTlfhK?x@gb*w}4fDvFaO-D4dTfd!_b=^3GF(zq zf9Y7&y*17}j_^%M;Ls8o{=(})Ad(@7$j*+xf!Nyb6Y%5P*EvTp!R@W0@u2vEZ<`kn zo9e@dH=P0dE(x-is`4jd31(I`Pfupe12ftO(BiB(v}=vb%ieBwkfDiPI!tB3mz?T~ zAAPG+N^DkwaXwpO-3QVbL3Kdhz_YzEm^x`^q98ru@tubj&p=NH(O?gc@xxp5( z>rImyxWzTWN@{UMhUuKG%sHNnEM8&T>nPwFofm1l9n~GUq2VM@G@>GFk2SK5Dbt0#Dc@58%Z}SP4PsMH=V+f<-*Z2IHT(Wu|qu=y45)I`h6biy_MZR zXl7g7b2MJSPxTj7llRgGr284_TdZ;7|BnuJ;gDf_VGE1XrsYqXC+xZJ$s-1G? zZDW+IE5Wh@--E>0zFqIZ05i2eBg_R66aG44^n)gXDhW1@ZlxPp!<4iwM88_HP33}f zDGR#-*{5Yqqm)_fe!W91L1gcvsEyzaPN%Na<*`AqemiDVDH(9DVAW=lQ7dNv)V3(- zXSza+yGbzpZce?b#WGeA9gI^@5w6E_9=KA9*WBp>G2h^unB%zk1H&ocLxm*k%(*^a zoVyJb5&Oe~SyNw;uv>3g+?$&yKeU`p4>LF3m`Hq0iKg$D+oE{buw`XRJ|yVLK^fc7 z7D}-VD|omU*b;pYurVhjXaEu zZO#719>6sKiq8QseB?93b2*wK67Z{Ol1Tl*@Vvz2G961_jPGm+9$!3B?aT+^Cdt{( zaenY}V5$9jfBp0V%x$9W%LyGDs0&WYPm+&Bl%-NMC42Bs0*;5ENi*&RJ7!T;<^+mp$|SkB+Dq2 zW~bQU!Sg1ubk#T~%$kK|+B>WTBxjbg&XmcNzH+Av%7wyfs}MMQgA>bP#efMl#iqlz zdg`4!P7sQ{A}EPvEix8loSQejneHCno240_P1?gD9p7x9lWUtL0E1+2_t}(x`B`xB z4aJ~<8h_-&Knv2cBS;v7UjR=ZvMU}Q?;#J@JtPqRNe{+gAlIDy68_(8lX6B@_WzA( zKGG;ey~Ip$kNw(Vll~n?fUsJB%NY`(1uPeP^Z&Valjvx?5{w$V`I_WsBg(bG!a%88STXQUE8kKUF!*>(x0?lp{w-? zAY?b@xOTk&B5timON$La`{OSW{|&pVxO&-|{nrz}Ioum`vGa!WW^-00ggmh--~@$% zKvs*QxVSOv@1-W&8wmlh)HsxWUB8msKYrLj1tHj)3t~l_;%_yaiIz(s=Sqgfb~Wx$ zquPfho$#TpLx!&@6xL}Q%9tQW*0nJSNfAGQKNT*!NSTo_ctn2cAsfVL8XQCoT5*;O zem?Qd$Q48nr-E}4N-Rt5p|3}F(tF*!`c+M38jCv^55Pj~JC8WTP(B(29gTbTFd3c`U6HUlZyktbW=@>`=;-E${E~C)+0{3|(n^hQ z(K5#X28rGdwJ8r#(XAQ8Ge`h9@?is72fZuGCA`Z3=&Hxql*J>jJ#*CId-)vJ^eZ8|Sgcd@;*BVB^KiEDPl0x4h!$?53 z7MhqyVU|UI^;19%h|YLxGR1p|n6G!9Kkqj1j{94S{Sjd*f$kOKBnPC#+(H&_OBjUB zPBCozp{J6RvFmnG>o8PC%axazNE@|Lb;YK3WJ>o19?&0yacl58C@pQ%>FjKO5FTsF zBsl2xGZk7%J1VWk*BMX6Tnq z-E{$_)h(%B>MIA91u@teTFaWpzkhOUI zA!B0rV1*ZCwZ9FDk|5jLaVQ@rUcw4S@ylaXfTp+P<#wKPa(;vW63usUUZLNw@BcgJ{2$OikJVo*C#j|XGv$QN z%+{`b#8z05p21>@3Pd$!$ z6QY3ikv~e6LwK**4Y)q0{3qoECT8C`>Mn;-Apn(BW)xSzv5njkM^umhEr_TQu+yAe zp1X||y#VYqp}Ywb|KBMm%CA8uzlWaT*GrA6vyr`v70~DT7yO@X8i;~GgK&L5EI|ZO zMgYsBCZNbMk(YR1G&TZknv+C#nLdNBn~3KU*cSZZ6mqMk766-O$%BP3&*2Qj-sxm( zM&wg}!Yd!+F}{L=G0A+~17*xHnj%sf`+a9}2>C)~ou+(DwYOH{e$_E42-M&V;;|R3 z38?*t$tylT%rxJdz!I7ugeBs5jI}&*&W2CN3dL{}_4HPkUui zgf4%OtQpWCeLsod0}9IC<<_dwztCcrKl+r}K=5E!(*kXGV)!Yf_ATnoyR(S^)*OHh z1_e!5A2*5#Aeoh463uV+cXon)7_ky3f47^Yww6>GgV(1XA{53jDK4 zmId{7GDW~pPo_}K^_sAKagSIcZdvM8;evGqHOmmsQ8K$tKvtK9F#RN7a(Bo(n-9KH zL$fFPo9*dRBh0x2rDpZKl7R-8C+sC0(7Zlo*BlpqNz9NRhyBlWrEVv2*-7aPJ9yK}kI!8)_9q&pK; zWKg{0%l*sly(WY2X$UL&(yXzYeNOS#NVy@pg+R$ni2`dJv`t|?uMmrA0!B!jj9vdM1A+tk9MF16zXZ+ibT>g@l~s%RcVmaqTMPgL2KxOKaDrxW8OYe}Z3k>Mr$-c%2l5Hi=p^j2 zwE?_Gj)+K)ntaU5(cmqh&pubuIIxC!5G2}uUD^>t_Vyc|Y@WDZ=yJM5Hi3{RDr7%V zu_i0twdWnr&TPD3=!+&a?*yEt^9J7r=JM|=w+A~|+7ER__tHbDTcwUk-yNE6dF-sK^LFvx?iC!6&C8LF2Y>Gh{APDiv-($2 zDhJrn0j}?rO;eM$AOpbwBbBEUz!XSn-Y#f2<3t}&Y3H3!wyU>o2zg>hd;;-LPKVY; zgoC`CiuW>KXZkri9e0g3`UAp5Ld}b#_)2**P(*x4?rr2r+H#{(47m{C%yt8Fca`N; zK2w*(Fez7(_ur2&zowa=!0d+b&)+c?8dG#0cT}JH>3z*>kkD7T-4eQ1sbJUYiTKY) z0lcqEfEkI6J+7DhDU7=9?7~eP!**W{mQ~nzdM%>xRIbQd+1y@hu#fbCSv)O;gc8`Mie7<_k}%fs6q6@L=*EXTG<*8%JGTf&wo#k!$d{ zJU06RWFXfhHN*l@Gl+wpijg7`v_muzG>l2Hbi-0uoMT+H#>7H(sqKQrq#j2%@BkfMwuLR-VW zb*2^IGp_GTDn5LSG6-Z2nu-p~CeW9}r%;(L)0{>WU-i%X>Nu9?7iW$}L*FR7RzK#V zm;T-VHdUf>wFv=(Ru(>JW!cId7oW*+Cf^6-hAscTlHM{2UwNL`NAey)7&CTrvE5-t zlq(=NMTORn)2tT)MxQoJh;KzxcZ1 z0Yb@zP)E2;G!TUp1{#&;yeDZ`Z3n+6M_)~jyDdo+E`@;YNblDBrrNsNg}l~GPlmOE zl0n1#O5L!?NOr$iC4;*}M$3{CWGYcxA`Nuqqqe{Pav_@vp#i?B`CH%SdC(H;FP#H) zZ9US7==X1?lK0nnnf>Th<(nn$HMPdiKXtwBvDd{a8@f``ax1VBAA-eu{HXZ^GjHN- z+Bw8V^CRGjr5aLYx@F0VETU)qF<5n<)H#|ro6x}u$7Rr!hZvII(zQcrS#m|`R_0sV zClpQZ4e1TBGQK$TdAYee5!M*vQ||?Px5WqPOrzX#qcw~qsnibtwsnG-o^R2ju1;cL z>!VM)`VC$%*{kKF7iS$_E-iHaD(JlrdYcCt_~4*sRqvX{)JKhD@NXS|49!|VE1Wc& z;Nx<}y4X6GgjF#Z!mT`Zn|?tK8xO|k8>-QGfQD8$pO3Q8Kk>T2lZoCi_1%nXRu&)m z;au|g{Iz6PB&BQx91KRVUxkgo*|dc0|6}k4`nN3#NO?6$MsY@jTRoY~to}9l%C;sU z;HZ<@$e7~$kZ}VHzFuLb5lr!A`?MOqRgldca?VE?o!Rup9Ia|V(dl(W-&?L|$_8Y- zsH06B>Y8S*Lynlh+)Mu(F!z$nt}SVHzUXn(nGl^r5h5T?sg`uMUIk>lS)zly8Z&VB zrhSfjx=iRt%G%q2j8}+c&GW;WI0_*5Dpe>F5V4yel@B8-L|_2%=_YX(^?2FPH0x2&`sWwxgpi8z#AR%3yt%*0Uc(Z#^PF zlsY&Hsv#LhOS!ptE%E#l-yPVa(vX(PKht$!65!cW62uC1fWQF$If+bx^mAo8IF87i zmn2~3wzOtvAX?v(e0E^ynF4bEbM9jco+4$Q1g@EzkU0ww?>bow6>{y*VG( zGby46m;{u{uuK4E{`d3~?+f&hxb|0FbS(Dt{Ks zd>fLrmnD~&+3n{qAfMkv$eUS9#RAG1l6B(W0Mr#eAU@Jm{x%S5&G@lAwusyYr1MU} zp>76>6nI5edjrzrV?UDs$!>(8-_QKt#EmLe-hbriAqms+0IfV^5ui*=b7!Cm&W-eb zF-SszHW|7+IXQH|lL)TLRLTn1GQxBA;1a0cKHI7u1`O~|O!;m&cyKg44}3=ma&%Z4 zG&L^L{E2i_XV8nUQIG-jpWF3&K)2e|o<@Wz6}w(zl9+7K)Sfq(j^-w(g>-O;Yu_Pg zJVApVHDk(u>!iRtiMNijc+G9hDo$h(Vox;GNm^QXaBlXAI-rmkd(!As(F9JU!Lvs% zLl2eeM^Gc5#MJ>b4SG=X3ixFu;-W3f?hEIVk6owd%p(?;PS{**DlZCq{HW0__ zb@iRZcnbE2&=NM|X=ZF{4D5Prj-bMT!YICzHb!@N6DdqXQ>>&J2a8K(S}2U+urkC3 z$@?ya-iGu9B2r}Z-=Sx|G0VKDjD~adU z1fC#-V~f`yK*Hipy6^60&8R(_rhg-$E6x=+6XPgkNjbC`Cx2ONnJgHFV1~o-Fv-TZHB$P;c|}^L5>}b*HO};~VYTohvgQl2y1I9n!C@S7Jt0 z4>n77Av0?~xN{h}U4r%Pvc6Y*0~aP;1yd~x=-+6GP!E5v7$eLF`;ECM1~ps#2rAqu zbP5rOszJJw?GpgA#$VFWZ}I|RD|^%boirGXRK+R^3$`33C^&s}S5>#fJ^5E@FelgL z%!T9TR(t{W5lP&Ue(AtWTM|t{cJ~2U;&Fy1Z0OEAMe(U4F@{~Tl3e9wXwoKartCPp zoibrLzYC3XC?1CWVtDcvc#Az*$srVu21!5axxQtOA40=CDHmhxR}$~3jJc>CHc9dl z>sH0Q{L&L22n@_j3x;;$1;N2W)df8xJ?~ZzfFVuo4sn0Ub4bGo3~83K8`|hYZ+VFLvGs~mfU!WUgb&IXX>5cgt)PSCEWW>kdnV&d=GIYn7Qg~oU z)2MS+;n;YzaVc|H+9e$3%A$qyF|hLnukw{q-g<0MKVc>8o!WJYY})QNsknG6smm#A zGuDBo;qqM8BHzfK92DcrY26g2V<(sdvWnBlWO%DP{0I`rQXQ=H&F4K7P3ZBu=+QKX z;oh%_eE6Pxc@U!kXZ-ftJfR?h4;|w*8>=;rcDH8wgk7xdT^~br3j!r;1bp5V??jW>{U-&>(3?Ki7A2`WO`J zN|O{rdlz4gZ!(6U#Fk#4wi+M5Gg z(XB1AwpPnX8#FWU0wL!ilx7Q>o^n`zSziA2^A})LJqiby3m^WHb$%nj0WP1i zk-f#A16gStFp$ysNs}Q-pt~-RV<<%R7DE7i2#U>!q0{G8h5W zm}h?<7DqChemCY&ywxbz=ZFeI=ulgFRRYAYZTA6-?1IC=g3IB;9fj}qM5$FOp)MLG zRc|&ie%NT{Dw534@&_^R2+1~<_~*-E-@R`mpz3h19CU1doj+Mrpcvetx#0oIOfhuh zO9Gwj?YtZp11r*Q<-_udSOaJ+BEi1 z2`lottz{NJBG;Hp=OMX7mAsU_EQ#ovGx17=iAqK(^=*H#uy<2D@kyyTNfh21Cz_oS zFQZ})hr8=tba4;EP2ZJHN3s+;I!u>dXw0qUjNxK+q9W~K-s|Htv6v1nWJ>=K>Y$9p2ubA! zDm4dCq^I4}UR-aZsQX+eNh~tIf*jzjyWdNj^f7If*u8IEEWD^IU9-!VZFz{!4aosxh zD(0p0#q1F&B^Ge2d(o(3{4L8UY`A6WO>3TuLa0Top&c{KT`2!j6e9gcQAl`iz7uE( z(nS0ROAw6wPgw?2I{k+%i~CcSAp>Pu7C@He^}X2=Iux@`49VNeijn%V;eXo!kY&7a zf5#%8^BqFn~F79K8KsIWnx; z%q}2Do~Oz<)$Jt$=E&`xM951h^iH>X_pb_c7?2Pr z@fSyy&8_2T36pii=YAHjE-`3s!t%49b$Bu?zfUVmACxsGchkLO){`%M3$j}N>RQJ} zk#eQzt;9|I<;!3SJfZP6Mny5M(B>pmN_@&PgXgV&XBY1Gvg|i5gr?m;VgDrPp&YP8IdyU4-1XyrT&Ro)6E(xE}m$AIEKA zJqtdwj=CN#Oqn=n;j6S*`fV!E6ci0k7EHLpUzv{#6hVeI? zGY(nae$4H4<3=q2THw~W1+dnu-=T7_$Ujum9)jxn&_AU~B;_aDhEsXgSN?02hP&NJnTpN(p%$`PlB~5u74b zDFE~Zf9*s3W(12`*_r)Ier)qhHkxjg)eEojy{W!~#+~+>D3sTzE*4g8mxQ5wB1*6$ z>Co!!UJ&tr<*#~FoGfir5YWk*;YGnZ3an1NVfd^0{7g2l$P)`@HENY5SdOa(#F=zO z+nUjUE&snApy%fEpAOIme07FX?3$?EpfQFWXFJBgNOz19EdB>Fvf5_<|<@|037^N)5% z0914FTw2kgzI;atK>pXo=ieX?43dEjtv_|_A2bgLlMC;hE({-~fnjpobC`_wSD3s; z_czV6EAiPHCrtr5=NT;-anmgxAT3-7j!2tKJqfQ|Or!Wpa_7iqycrr(QRO&Y+7dx2 zrHrz*XQH%ero@LdDRb)0sU2r;0Y-R6$Yl$XyPY=O2v_DhOm-S83^xUradC5EyW!E4 z;NgsQN{wH?pCC>QxcoTo5KCTy$!UEjfz9OxZY`_v+b6U*LP))UAtq^(*8?!LR|Go( z2Q|eR`|5K2W(j5-9{$6Rbkh0n6R>6XlX(Ifl=miYctAnXyVUMfdKX%%3#I_6{R=>9 z&j~FDNbSRF-=b(Qel_37x_L(0h&6x!7$(mEe*U{<@Ee^4Y~32UI{Y(Z`+t-?2xvId zM>7c1g*|uS`lYekhT7=%NJMIqIr=24|Nj>~MEX|p{=WUm^qVabaF@v0(ZTlLK7zkU zKA(h!;F;w4-u-dOVlwf0uK~6v)=u1|JlPQi-T;QHHj>3yo973sj3jJE5Kk7A2mIO{RZS40M*NiHH!lH#!Eo)a3RZH zh3xGUAnYuB5j2!O3Ne z-)P(Q_4D@yJ=`4YZwrG%OoP9!oBELcjfFT=oT{0J6WQ!b$s%;3;Ni)A zo|9&S>U?Xvl_&27f@M!Yn*})*pmbTbz+Y09$!E$>{ZzVOi~|F}G5c$!`y1?k+6;eJ zT9n460e85nrfk@)2_m78l|%#eR{<-I5Gi!x%Z^GS?h!Jp{@_vD#R>@OE1c?ehInKbwob+AnQJLt1deF02M>2X~`bSiS;%7vcrXq#E?D} zuOuq}dD)U_VzK>Nhc9bEGY-L(_-fksZ2Jiv`a&V1-QC?i-nXx)@~QNs+I=;o5!M~n z`kGgV1IL6hv3M5;*FoKdqG2lpfg5Gd$wLv)T>J3L)6Vj1)={@|u>wq2aR+BrGZ$BY z$MeUWRp@{P6@t&c1>_a>+8sJApM}yxL zGG~#hY55A#1LB5=j;MynF3im&g?1@QL&2uOl4>)Eib~JjtnBc>>x34(Kr*X-dLI$j z2uQZ9TxAbsRbp#{*S~E;+`%z*Ym|Uqd5xa zApi!ieu>g=_HJb(kAD!tUzGem_wKnhpwa{|vW$#-2iBjxSv)cdKtudT#?&JPYtHli zAI~>3h*NxAx0``NtSn-wn~EhP`Q*DDJ)JGdxTBlWXv-Do@@h5a)PUM-iaIu+HVcqD zWy*}c_gUvq6x8Z(>8A*>S5sL*f7WJ48uN$N{AVJh)au_;Xx1&E^oC?_qR}El0Kn9UYV62WGJ}^GA`I8 zEeQsl-VEh8oblw+GuOe}OG7O&KCj)UFIrUsKFj}$+-ck#X{-H*+<88n+UCW9K)Lhl zt@}43U)2lQz%`@8GMKU)Ok^xAWyIA27QohEQq&tcE~c&cS2al^1OX>YUMW4W zF(&2dk*2zP_^GgFzW#lONF!8Mov}(n2i#6nb3$ypMWA1*Vv#fzooSdhlC4lKwaJj^ zJ_ehPSvaK`@HG&VDqeoj6yslygq$tfOoD_2N6@RT10SR3K+qJ>;9FQn4|t4^K!eMB zBDIr54q@ae*q$>_dgbLV+>^+l@s>9izXjsVXmGP+TFG>Exi=;(D9c7MkK~HXLdm$=U2mva|^h%lwr)qihG@j1Z zePK-t!G_VNr8yEpC(awgsiLFazPnfUpf}SbB;PTz_*MzRz|uwWmR=i^xLDin8ZcG zUARGPs1|I=W1l8&>9F|f46ZHGg4|Nw#YWC9mgURrH?D5fZhPaMUpm%CKFrWBo@qEC zA)=X-Q~|D!!*f#!Aafhzb#=SgS&=CJ<6pn|HVr&6y1|6K-@pvDEtYhs;lwEVQasc~3+ zrE^+PXl)ZZb26MlZx$;un86%4dRk;F8Ldn95~;WlRC6-CXi5@h+C@4O##cE#oEhGp zN&}zgcY^Eq&&^p{ncDaeG~lvUZ7z!z*H|TWi6M$# zYx+1QSc$*nSN{a|A$Mj6KS7b@uv6EAp}WATaqU9={KN^2iHtJ1tARXq>MXyj7xXQR&E z#mLXt-EIsYssRP>8%WAp7L%=kQJ-ezg{~c9DoY-o)}us9~Ks8tI$Da zHRly&JNq(dPW91>DfJyeOSGt%KwoTs2NgP;L=2CPbHbu{lH=!ya}Jj9JDImZYF5EZ zsT}W2CoNkMzjR`=Hs5T(Uz?C}D(@a7?cq++ZZs%pgW3+;aqI-odKLG+UNvT3h2^(j zi3r?4QVnyD&eqxjeOt~ zX|;g*m?@etD9(}`K0wcU(fux*PjqRSF*=($3#0~Qieq>f*LN=^3B^$ z6lWOLZ^NUWo+j_OJDDF0LqL|Bjfd!-D5B5&Et-()QzLnJ+?o^ZymroDAHoCXJ=K+M zrBNX58n-XoIe=+?9JaVw*4^Im_k8}fqj z^GO6tHKJS{M}HLRC0wd$MNmAaREMDSgYe(ioeZ26AtueJN@vXi^PY4BWrbo@dfm&> z^|m>k8Y4QXb?62xhdi}6z)gw{jXfz*7QV6{p4L;O-{4&1C38BC_B(MA(o`ERLOC4u ze64<;I_-XKZhpjZHo5^4bFu!7bB~ul-dOp3iWUl#A1qWQ!{N|Okv1o%`cb}R;HlED zguUL^jow+kCFm*9&x|0K&Wj# zSDE6ZmSo3bMJSH&)mV`g>@+1xm*yGbMOM+`du-}OPHed)c^gV>m$RZF^0SsGa;aG= zY?(Bi3MpG`JbFd2nWZzXG3N0GMbfbEKAG`rqjk`j4XR+28H${lCe6K#tbmx6jWJh> zXR-&$o3rHjXvr3~U!S)NgRFaWg-UHSmnhmND~sN3?301xAP{evf z7c+&?tA;3H68r)3R<1A5Lq`*7if>f3f7A}CgChARRlIVUqb{=nrMYFnm29F~zMAF7 zE@*;qQ?QjtnV?V64Wa&kx%iWYD?oJU(SCRNXx(^T9X|y z*>XXEXb2S5n;D9uI4kFdk+cc0SZThD-K$+9u+Y+Yz@>$%Y^pa>ScdFkl(uG4Nt9o; za~9@W6QoLP$E)6}X5;>WlU96XS&w)f%fA3qR-z@3-wR#z;WD*ln7>m9%ilE}=7=mZ zJLzqCm<|gpWn|qXG&7Dcx`xyDJvK$StjX!GWP*%X>N~1SKd{S@FmQ?OUDTKA(wa)I zsvf@|Qx2=8ekQ)z_=1i-!V`iudsXnlMO6A-0M=-94sWfCUT&NL2Su6kzV>XlkgbFi zR>2oy7^7VI6|iQlwh(;^d~j9?S@j(Mo_?WuTJ5>z!P!Fdz9aV~65xueNPxLd{dBrax5OL!Qd&!ga z86!u|!&tyRyT9F6irPovCFNE15?h1utL`xq0&JFVglxwXc}X%H%SLQA1R`t*ZQPMs z$5}~~ceGi~m7P1wnGsI`Qz!A}ocuPjv)+ih_9>PkUYH#0daIr1o#gawu`AtYG^1DD z@1>0AV3zfIbu;;8wGu4fjoKcx=ZGoiF;?eUmX*m@pgIsHl!(MFa(fvww1*<_&ys9K z2PMk|>EPhJcKvLqO(ZMe)H9%(9pv)DQ<2CIVK)pH>QpTCyaGhVYnK9=iav#5G`CHu z-r_XnBx8l#XJ0+CAnMM8et|jky3^VSub!1&@ncBfD>qD7FxHMu*gIW4N2swiJu29f zFqC`-&zoh1t~iMl3q}6Y8K0T44V)qqvc)%WME;|Gf(-5in>T~~KigA++PM;|wdG5U8Ev*X z9=bC1##wX@xXO$PoV)WEhja-L6UMm0iosX#I`yy(2qXH~oiy(>aA&8^#wx&&TTebx z*oNXN>Bj|Ep4fq-xiaLpq}PBR*|5%Sj2bvMgx)i9y}78CLmamz^bJ$W)hb~YQm6Ab z_*lSyUiFqo_4}rN*W120nl9bJ6ZPwN0z&gr8p0)&Orh?EEP63Vji?P)8nLC;EOamd zcL^^K_TkJp|ro)ITGh6%GxZ`K`Pc^&a)T6|1w3~ED)y22|JvY zeBq9wgNFj={&gQ(fM+>|x;l2I^m2DCPQ_;JlD=_79H$Jw=cq|cfKx?ZsnXR%yE2a6 z+8c-Urr+Rr6tqm~NcKhE2|E5OwQ3EbN7B$`>0MwW1XR1$_#T{iW8W$2_tAQ$bjNL4J*>?3 zm!wa4w+6l3Wk`L{=ZN)r7q8}qycRX-Du{B0#84)(C=_Qx$scHQsI)2@x8?4Q;xVJr zniXqE$R7iCMc00WjaPi&l&;@{EZ`qIZLs?_?d}m>z&~Z$VCO66-J_iM?1D}9_f4E+ zW7_6Au?cfE`(d*a8@Q!PfndZ2O8o)mFQj)=orQ}8KX@bKwwzZeVz+nhg1`7n_&lCb z2}CH=eUCMY6^xmFr~2p#nP&zt^SL<6S=?@c~8M8^?fkN;oL>NKp1LoKR73koJkTRfJ99V)ekXGk(pC`fxrIKpM z^pYCobUsDw4A@I4e;OQs#%bzx((Z6eVmL}-EJ9(Z2~hQ&qb|1hk@!O`$ykVIQ&l^hq{mM>OXfEX4Y2h+f>|p zaOz)dtNH2;bMoDBKZap$478|z&9nYyhyP=5kVIEw(!X+_{o`X??3s5R+Q!#HtRIy- z5BkZp>4ckM6<%vmKlxwslZ5K^+?OO?&2 zn@474AwWU%;N0+dRCKUV_;JxS^{@glrv-w>PwyxJlL?z61@ow9Zp(#?h{y^4QJL;z z{5nbBwxtdg79j$}kg`!B1=y03+RdV5A0F;YsHB(lTdMPT$4o)xp4%tOY-Dv@MS4zd zdAhk^B9tORQKAMmE*cLPr>}G#E@G!%UqT@#%j$gjtUR8XsOn(hqu}r2Z;^lCso|o4 z+lz3aALa515yJbyCyVz1W*z4c_G($P9gII<;X7ay1Tv0jwS=;zSsKhl2e*4^v0KGR zY{U2%i-JWk4H{P*L+kREm(1G>iG9ottP~(|Y}783Y4593oc463NWxN;F{Mgz zc_E=zw%wbQt<54{Dukx0=CM5NT=N-5J|?P-{mXX9#j4iDNVHvuAe36cV3QqkU|q%1 zK2O^BlU6Y7s>x&S)W_?jivj{xX?0x@D~3mx#9|Tf2K!H!JFGnqdC+Cbo#xti3m5jR z=CQ#jY&moxc#TJTx!0m1{YDLpkCKvUIPx8=Dnf2uNt4vlu9kf0(lwKTC^7xJ?S^}g z5~_=bG6_faE)y)`kQP~V=*xB}C(-$J6%8)U=iW!X_`E!o7H5kG;mf$Om&#y(>KK)o zvD{5qA8Ao}^u-XDHV>xtXNJsuqPWv~(d^U=)^@jeLmH!@L}XU(~(aan+-9`gxU<-<}0-gWU}*P!G)u#CjN{L zOVM=;Qw-ZC0`2kqXXWA5A{qW928fTZDX{8yniz1gxJaE`7Nsd*k}hnxc80Ij8fp?1 zzH8OeU?~J!87O^~HSpw`M~{r5;-w0YQ#y9e?4FG-o)@^6%$%*09BIBT+*)1jf1Iok zPJ`6rKv85)->UHE&}hlZ-Z#-7DW2v?ckC=+jZaBfj)G%DuX6!0Hrb)ZQ5%U?9?H_B znz6P5l;R>>@lL`iok@1rT+CilIWWfrNYc=mBtpT!*ZU@%mRz!R_wpQV=I)|4W~O-Vlro7j?OeXpMpsbMbNg@^xy|AY5$+jE6YO~2wgEc2pB9&)- zBh}rGn$^FWLTY&5X-jcberG%KX0>cQpZInhEEF%rBsw;fkLqe#%&U<0>mD8VJ9lXD z?NP-&7*@RU`MuTvi(O(p*&ri_#rSww*@h-#C_%R`$lA_Ni^JBoU7qGlV$31c-IM8M zmN{&@MiE#jDe1j@LUZgbwNBpWaXS9Hjy_Q&PMLYj*GL9*tbvFzXAA5RhF;<+H1<~m zD)<){i;l6%m~u*=3FQV9R?c#lc*e=D#k?*nu5Gy!U95-tqi&Tb{5djU#U6wsRJMqu zs>W5W_F{bC5N`2KVBmKJh}J~Y81&V+v#Iqk&52xc^T!WT+P=?^A^SRfq#tW4mwr-b z_b+`{l%wXhF7WO{1u;Zrg}qbgJHpO}E4K%S_fxz*yb4YDdA0BI$`ZQ<2fxDZzw)(| z=(03MheA8HTG6*G&u$a{K%v4nNEj5lY@sB3SP_c=p@H=Bg9M++MKONe2j1HTSaqB4 z86MG_-yKqD*4uAmud930rYBpwjGt6Vsa_5vL{qBVgq32Tvmeq=pwqbyH%tv14c%w^ zMpZaq<~mmS@R@}nlj0qZdvPagZ;({Y%-~87k(Q~x(M~cs_^>^MhIHM2i9r+{cTuNk ztF2g(`Tgnb)B_z+(M=YG3yVn$>*l3KuES{2kky_NWC#3+Ck>v}VI=GM*~Ee9*J{(9 zJA{(BHnjJtD168iuQkj!txnfIIs4-N08eqb$P_3w#VA%(E|4`|Ou5w=xDxp)m2Z6N zkyihS46&b+-BctS!FL7I}`PgG+&KLd{YwsA{iJq(rr#nf2RXtVpl;*Yhcj}R!Y1bBP>%BmswO9SmRVOTd zjgElB>j)f{jrq6r))S;;E`uxNMNNYh;G7(??jt2tkzx~*p1D#lw_~MC@*hte zJlJ)w4m>4^5#2MdIox*2@_+eBNuJ#3y2x*^3}D9SXXq`=$$<9DEsKo>grJm>VDFcm zo5`*_j9sS-v}W;ai}X=vg3Dnc_M}HCgBy&8F}64THxxeD2{>V||ZXSw0( zv95-yxn$0<8zKuEO~4qqY!Qj6KQsZ}vb ztcudgTecIL{>vqj8cSZV;vW67pxrO;G0IN9XPSa|+6iU{T_s zgRa$+n#!)jSIpyB3&-bAViFC*SI`~tPY_^l3a@M-wNN~f*?56S%l_Gg@>&DxK`uKR zj;A`?sraT-Eb}~eJ19=C@H1CVSxF=Ec{i~8`-Ed|!(qfGZ$L@O+2K|Wzc8mrYFZ`+ z1Cfx9^i^u?(@!~(4=EL`9e!mRRndkwVGi(=m6hszLJFA8B8#V-idN4 z;LB7uxR?9#j_#egOJ+3j4Aqgn3zmb~JsK-*hrH9wYOmp!$F#-U7`3$+M%nI`z?ztl z23l%5Y#EzgPm0qMLn3FwLYXoczydc; zf(ICE@Zm;8$m8gWklp^nl%a#O-wt-AtU`aGF7SMZk=M7d{XtKKa`MA(_!{VdR!}>^ zYz4kNz!?E#d+}Ni3uorKAgb8Zq}w8O_0kt@k24pu;Fp~I3o5s@gyIV(S(5FM@ZcV4 zl~?SSRDMq|q4*Yy8pjQzif;B$^0C^s;;fyvqN1dCB@WpS%oB7h%TtU@?%_S#mkdJ% z>L*O@BP2^xM>ce)?DAc*glEmDW|lV3wmr#w@At!Iujf5gRb79kr@N8h1u`@H_%cXl zH|-KJ1qHS}wiZ$Q{1dNdB1?I|lfm26TT;uLx<`-d#rfwA!;(=0f~zjwLX7SuKQhBh znvRo4O;h@_x4b*=*hZa+O&!k#8_h!4>7B=GNZ@No1OF49zR%?Ej3=5#^$n7;)-3u43^jrFUJWi|v{fihtJXtuPdFc55Xv6pj)`<~fwd?eBgeDC-20mD zp4f2U;W^hNf(`oda85mpRS;SET{i)?BN9Q^eE!(MLi_ltokq(`v04|!Vuqo*?mPCo z^t-!0P5^{CT}D^qUe$IMH88~K&95u%3I2L%u{y?z&Hci0%}Q_^8D`t*iM|ri>S;Ym zt6-MrrO9m+E$yRnANWbMuFP2``1VQG6dM=Cqs&lGSSV?ltsZTS#Tp(}<%FBOz0AAV z-`>?5;*a^<-D!@_mrW+E1b)gg#Wz6*uDU-pm?xMus^%}`DzAP2W<}6sI*@18$85!* z&T4)1%vS}%dU4>+vxKw%RG@^=qDt*{E%HRDw49AyDn2`b?w860gb=S-B>WD&9i4gy zPT7dn>Bx7_OIt!W-*3pIvTPt){9Nt|a7ZQ|=brC(KA zRjFC zi|SVg!T#|uE%&9Pr^7}CfNVTENj5WkNBxUYQ~3L>Nrxp<)2@&(G|A7!-RWx-MM;YA z$K>nepWr#)`_#6FOs-oU(H5qN5{d59R>Q_^HB;n7SHPLW4dmB>m5 zaKDw-)qk@VEp)~+;Hnj6GxkIK02I&ayJZ*v0I|<0$bYuW{kbT~{cnqs|Ew+& z%Kr0v1xF`+F)Q1@uf8cv(DIKnZq`U4#!FfgON)Z+HyaIg6jW980IagON{ZYWcadeaZ!N$cieUc|2Rtj)bkvJ;3DgSHlm}(Y0j$* z&1sUl6{=CT9HT+5myyY%Ulvm&KSxuJ&c#Y%D=|~J(|V<brqUOIj`jI2XJjtOlWuVqCKYd^Qxj^o+@*xDrqbI}R>uhH z9Xvo(V}{Q2dfb#;f4874hT}JKHK_Kpa8yXwBiA>xyIh_~pg6OFrx;=RK7ydcxh~iS zvGUqAb0vy)$X_3Y=cY{pDr9_LylSMf@wwfXz>|W8QBG$$l87J_z+w7 zae%vp3|KE6mdF?c9Vj{YCXmUYB*(d9%TJV7U0H zil66Ooaw2UL5IpmN9CXMw&zPZB0{uK%pwcD!f#0H#sLk0vhZtV6#NpV^kqwiq8E>*wq}Kj^<&+n+f);s4Ou{$9(MvHrvAgw3qY987-?yXxy1Sp1E- zi;R{6@1unc+IrP4h2Z)w3p*I-x6N(kFD#%Qcf6zN3IIdY)P~rrHi3bC zc}*96bPlZ>78Ttik`BA?j@Q^A!tcepiqNO!O;@TaN(A~=8M{gqNFJaj;c{qgzpHXV zfIrbXB}vU-Od}MFF7a_7bCCrPw#eko(vlt*j1#Kw({36JR}P7GecU9o_2YTN693Zcs+BY{>S8eUJ*-N=chP+_s{$_Jn{aA z0{d5Z`kV0n2~d?u8V+lMp8$nXo^;S+y*tZmYGLimJ|)y?7cO9CkhVB425t?wLp_cz zQc7t?SU88)1*r4I^*cxqnr;!}oO6-P4-n(+z?(&HgrJ=>an_+)_2w#*j@5ehBOh-M zHj9tX_ACv6sGWL7dsm5}pNv5@CAx?vu9uWHhQ z=$B(HxO;gKNiT?%$@rk zCW20E+N$<|PY(9V#<}LP;)sIwJIvC1!GZt-opUahefi#v-aFtEh(krlIiURYWEL7C z4pu~II#2jzEY9;S7$ZJR4L3?Pi$AxbnBXfj7hvIM( zz>%iPpVkB___ZfL+=#&A6KNaHRHIO!$)BCGUtBuI;*P>hT)jUM%S+ zK^Xs-)kM3T^R8OfPYZ8Ye|J}_uV^dM(O)K2WN8>rGdFWqs07}hrEDZwyJK|OnD=5r zB)@OY;9>}sN?^%&u3Lm}Zz7<`3~UpfYhB*}f+Xvl+&p@CIMhQmLrt2Q|GL*rhh_l@ z0b}QFQ0TJs^@P%_iZzAv`6gQiQmy_GD=NtJd zhXZg~hxw4k*VnrIBCSIt^xQ@%9*$`0t>>9qr8cPS8>ofB3mQzE*$+gBnBg8XIL4Bd z*_d~R448-vOjwCHsYWEKj?Cx6FS4N?kx@HGfqc}PeBnJ_XDX{+<(b8rK9+-TJ&Fi# zCEK)bP$M(Zx7yE#u3WP^z=2(gS&Vhr?h-M_Bsi#4*1kwK`leRq5TV(4Z-V{;th`pp zVCLO~!_~BY)pjuzwX~paoPZ&l^{!EbXw{z^SrC8O(iFNP&AwYKH&Ue{({%BHZ#j>U zS+M~lxXO*pOu~&~Au-47+Aqbtc2y=J?rnh;N**JS(Ru$I*dEzn<&j;ErelqVlcs|v zS%`FKB$-mAjGB#f$n3Ko*p?JwWw2xYX`P|LWoK+h81~`Ub6&#;eiQShcSCN*4VC z2%nEMXK`gijJ{@NWoqi{jCf-g&VqVnwEBD=6Su4c5e8|Jxl)Dd%av~!l7@xXZhjM7 zMT6_q*=L_sJz?(T<9Vmc1>0lCCEL;Ah{Pk-SKCrvGCx-on9IW*F3yhR;778Q=*Qa= zKzJPa?fzi5Cs3H?n=3is<{bm@v@5&NH3>Tg?p$3??$Nbu`QzKUSGV8+6GYP%gg zn1ZIbuU6_6#^Gq%l}|#Jq!1clw$f{rd~jMTA5G+Vs@}g@w!&0JC`MepVMb_i2r(b+i=M3^m~d7O z>{y}XVooDt z5T}-qJK#VuEH@ahM1^GOo>&$DfstKfsn@oAtq<0j6KvpGMz@-#tI!i1HZ@)^vhjro z&Q>cIIXgJZr;g+|3ASjnwByZCL12@R^OPd96!x_{H(8B^}wnQ2`W(5i1DK?DYsW$A>WWcav zT@?onTr~$}t{HsOW>Cckr>`N1ofQXZt|^eZhv<;Hdl3pMJyJ@asJuz83&M)8Jh*=` zqR9{9JY6wxzQXy)Uz@xN?N*b&2O*HZNBPJuA65ZMciUbDZvtc-MV@^i zCWjjvGdGo5aLaEf>c98RY_w=F)Zc2hO?rGi%S`%2_!4?bxFgvh5i}zA*4+rbnj9sm zgNM3wEiyGkdnpsQ@dQw+kw9rrQ$N?Ubk^-bDnE6XQv=>Oq$i>$L=gQZ<6u==PH)ym z)U0+N_UBSSv#hY$=l1Cw{G|gU#=}G1A`2N8lB;P(Cf2fAJ)fD` zBk!&Wb#t@}{dlXd9Q+7-tMmK_Vk8#e%!rb|=H5^QpP;LF4#0>VjohlPpcVOOVS{EV z23F=n&N)iWdA~uOmx4kr^w?w`pcjHev0#H#K5f=Xk<_>4?WFJaM+qf`R?|e$$S94$ zMPa%6kRT1_1=$8Ztrnf&C3$!GOx9i_hfw8SKt$hMiI+BiMq zG{2ygf5sgio@yjls4gb?*af@f?$uZO_D;~AV@~oYRE>O5Xo7=aPRF{N#f_)5msmVn zMJs`W@?0Btus1ERbNulQ+T5VnxKS={u2hzdKEYN%RI;#8q>Vt+xJ={*qzrDr6F?i$ z&H_|X(f0CqVxF6w!6{}+QmTMK!C#Do^G+jz=&nm!yj;m~Q^LV%BSSl+?O7XfVe(wa zPAjn(C5zohj7I$Ad3!^GRmjwlb#@Ok*cK|XtiEc9z5<|oS!H*Xa-*L#DEcr^QXV_N z#$baR@jiDxK8U}&j&VMw*;^X@z=hY{eVa_1^bNbaS-glYsuhW2oj5;WDA<(K&&(C* z-ZkOQV`8U^xgT#lQ)SW+Y^OrqRB69L*#K&0SBXOV$h12~x(RO3?8*R3TLyhWpx52< z`+yUWimW4gBRmF*0X39+IF*Un_^wqmR>hwv7S4oc8LoU5y-_{+)VI(8*+3-Nj zk`8~GGv#s99@QAk!x;J*w)33F990h^GhF`$~ z5IQddFZ2bk7S>3KSM2Gxl-k-S?fHg6x_S^LJfprdurJ+pIE@&u*zDro9FW=Fgpq9b z!MU?5&)5k81zY3zn~~$Gm+ROQP>HPuWo(k!{y%9vbZN+NhN)VR>TO{ja-$CcoZ~gV z8?Z`ay;G|5oOzmPJ|b%h?#W5!1@i;Q$hKe?*LcK1K0~sXoJQ{fIm@&7Ks<+@)VDxQ z??{`ky6h3U+k$&seYInk>X?FUMzP~X+~Jdx#FXB(^TQ!}qVvNcc~SuKN!um?eebwp z--+c#o2lqYx8-`mDMsFm0Bv;zV-(C)xU$UqZbhOdzETRPHyz^bkoM>0A}E(1ap9Cj2rulgI!hYtJglHj8D z0okB&+7@qp5D0A1_+dZD=$iLg-sRra2hQ)?lVg%CNZMQ)C0|)o(|908@kWA2 z@&n}g&H?|5Hc){;#s)-&pVYz7&TzXqac1iC`s)F!$G|e#_KV9zoqm2uyHP@ox!$Co z#SXVW!*=f4t{&>uVrn3J|4puYwrJsFL7Z7L9ewOkmpr~%0H>7DSk2siS*Bp!dGQ-- z6#O|%)>AFmUHugceuU7jG_Xgn?7UWL8w`~t?4Dqc@4SiT<>Ricaaie0kbbb}J$3)^ zGbiuue5+lbtf`puEhd)zGB&wMsB;@ns2Za2#TCj4Zv_OzBzPN zzOV{|jJ2RO=vz#Aln-h~SW;8X;FUe{54<0D-WskwSFazJ7K__H6BcZ@ooBd17<&4-m+%DzIHUm( zNB*l&^x`ELqDMg7aOA`-v2`rvHoSGd;JSD_l_T9b`Gq2}g6ZM+%b$_w!KqFA6Fktb{rG zy=YFqEIfQ0Z7MSQ)((4m4XX#UAGQ+~kbnAE(fsFBz=NSXuAqm)nr4AKSf8m$Vbvmk zV&4?F_W_=MU)*Uv)6IycR??92S(~mAU>yzT%E{whYuB#05ob5-5YNN4u!WvV7zarP z3k!2m3wN!Ra!3b0;fE>U*W5tjl06^(VHYaLEjHeLi+~`o8JOokh#MgZ_=BcTlY{-Y z#0}s7!YGxE?0+A)`zLc#DF1s34fQo_#1D5D=E-Ld0L9}+{5m1Ln5<;T`uHs6;H>p3 zzbEq_Bf+oEE1>91f*z?;o*Su|Mb$RbGyt~HB-o0athll@>-V0-zbdiqzy4Hj-xOhb zDyUjGIsY|Nl(afAcsv15wcB6;q!PuJiv-?_f`@~iyPjx^fb4E7nzOF-7&{LyzAP6n z#-{sDz}}p=TSS)HxDJoH#BRWBph%DOBdedrPdK#Db<)Wh`|NN*JK|Wk!{8YgfwBTiw29IrRd%bM6z0g zoPfNzz%ye-*Y|wr0CrgPbnal&#GJ`jkpV|~6ut1zmiZFsb|6?TcG-Shr1-)e07lCb z`?l&qZ;}sKzmZk|`(@ww^Fguy%lgniXUUBJ;iLXz6p8R3d9sR?0)h&nH#N{=0I)x} zcw!jiVoc7LS|x}u_}rW?rQfNRzN%om!>x3)B19%g8~YZ%};oQmdT z&Lw>We8h_9ezv9)3B!Zhf!80ccRpqvb*wu~ue|=cr0M>8+!t-#unk0FkP+cP-#aP> z)3hV>g~kn6ch+Bl^<0>uh}CVypTSkAr`$7oz$EYMZbtTfPdE^Xm^7kv5m$=u2_I~I zhN)-vlnB|EBhG@c?E%Ce{jEf}RJzhVZr^A_1k#8x^pxnORt!xu;uUoyXZ7pK#FO$Y z48-~46@x0_#KJM!;9-6mlWNxFuE8zr-{uG9sRdJw%jTJd8*n2^T^Ig{g9^`UL#Kh= z7(h`p)0WX7F=VJz*Ar99m$ww|;p`6*jYSH28@$7sG5D#L`eRRiEDlPCPOh$CRh+!2 zI$lo&dO)&RzD+Oi@L_)sYRp`mIf`sC!tj@~!zi|pLWMu1&(k;p72Gm>uSdjzQefUW zp+7~%uu)JWoKU>TY`xqHYH}X=WJUNfofsDx8n}-Ou@&^BiCkvEim z)RhU@pGRY-yU#I;B1MFdT$O`p&d)MOIZP|yQmEoC8rH;mqNN4!3<6$ZOcaHn3yI7v zT&LiLsOH0z{MbqIP3Mj?reP63q$o@Vkw5B|oh=d1E2e6!3ISkdkmZR7;^LBGMuDBf zZV1i&X&I@)X||by(6t=ud?l1I9wDWnm#^_67EleQVBC zRbIsF-3gshwY_L(#UxVh zMpkfAn1S!FOPt@sr`~mfYlnre#gYkM{?Z-7Ed6zvED!L7w0lf~Mu#NXL!c)epL!=O zq`ZZA*Q$eHTMKT<&Odj>S$albeI0HCef!s<4<2#x6BFW0cs{M#{8YJuh-iz)>dFZ_Q6D~KzU4GE<>axy zTD%hI%PhABs%}nrlz6EZI`U#6Po-AaFBAhp^wsH|tiJHjx+YI?>+{JmZpp)~`B|Qx z<_MDZduvO$MJpQ@1~_b-hR?6{oWPa3iq@uBrgc?8(;L@im^!1BV5^r3fYy6SQ&sJ7 zOPI&=ov}&B+VZZMYV9&Jy#Y>Y-HeAzjhXW#t_Q!fAX{7Yu{Q>c?sN(ZJ)?zbp>b~@1d96 zxVzPV2~p^-bxZFrJzdUVhdH&=-8}dls8LT#B6EX~l3Y z=xuohu31B&>M>&CPIz ztM*=2AkkC8gp1amA~kWC*`7S-BeCN+WA4rPYpjZb_D$cyX*CCmzxKYA?6H&aTljbH z^#Hn()HBa-4YR9!JN>y5!`DwcsXQxC9odZAV%@Lg7PDEtn$EE7ioxVLL9ZsV{58#$ zu?`u86k8XWs#tFPPI29a*W6p10f0*n zY%)c_rQ-~jaIJ-*Z0FBd$5{zIR1fa>_4~JccTP_4gY%Cd)-1{%uu*FNTcw(a4ec1? z8vua#XSMpD6z!kG{QqgI!vECY{x@p2P(?!pLkaO+8Wd722)3t5H(WV*&tvT zlc|+t8y}BsuPLzZ_m?>ufXyKkZo=3b16VVjZ#5Knd%-9;&!*S00bD$UoS?d>IJY|p z+@52>(@!x6{Q}%2o+Dx-63fXsbl>@+(1MJG!pf{HnEMN@Bv>+_DpeONq&C>(GgV79 z4N=WwiZjHcB#1nxY+LzaXDBsTBFn0Hb;Ix)PTO+VK;RlxCrW=21btqNLXS3XPG~fB z=1MZwr3>l`F+kW!7UG8KJWF$YoO1T6;~k0MU#PM$epA6c+s;GnuRumly(u^WeGBQkIT z^rR*~m03rER8|8F8;WZI6?p%So1gQd7FBdK?s|GPR#9QNuAC2NbW<+dj+O)po|n5A zmFX$_Wu-4M#5bQiJ8g>F^BSUK$X4m^H0mHeV->1y$x0=?CP~9dwuJcsa%c>+hLXB* zJ5z4cPH)KjwDt5u*TnsOZhBc-X_x~E*VLQwb{H;(ls7B%*_Br-THG3gUfiUR2AKo7 z^QVQ%_QC2|($}sckVmMeI|Hy@|{yQ2iyMqAdNM76?W4*2)oY>cL_;{j9GO zD+LBhGvO}=siyQoOKh1(&yd2ZEM^P9ha@KES}3~|&j&qxL&Z@$zu>fV3~WzKTpx>~XWjta zyM%bC3b~vkwAHzhU@>3m+ss}gJA6p4ctWnS1%J^51WM!XQjxiO}4W_^q- z6Xnl%&~cUyI5UY()y3Mp}!z^U$2zurIG}Eh1)J#JC0EcJ}v}xsqsrO9G!Png!I{%f(Ljypo8huIhUQXKL zYX^*6MLZKo_od$@TuU=F_Z&PY|F*?f&oc8@Ok3_X-iYjP;J07I)Q@1zq3fmeoV8Q> ziFXYUFd6C!Ryx>fWokmnb7N0w2}$`kp$*F@W&}; z*J8@pfPt3uvx!s=m#I#M$1I(0pI4xoP-|f7i|4Bv&0fljUsrZpBn`VezQIZ`P~Vm? z$17%TuTs81_EHoIuFAM3g--M%1YLYDXI-F&t{-)+P(aAC4%|~DN4QwKE)*TY_oA8v zu}1G48e-V>q)ud{L1j)Cf@y|E%jk*%7I4B%T+Z~w47;PkAvR3KISwHS=1g?X#K7|S z5~STLIwv@;e%~4<*e7eg`s3X6H31z>PFIduv2{PQN$Pt`6@oO|RA$NB^09U+0F=Q8W z-!it=g5D>KLY)o ze}pqF%-dv%O0+zwpz?@^=mrl3@AV~sf0GMQBaycUjSHl5GjTRK>`Yym(SH4PjnJcT zrfduL*-mDh7xAK`SYxf*|J`aE&!6eg_xu|7*!FUQH(u{v6nmJz3=mIf)<#Nd-;J~o zr}=9Zp?6=+(qSD&aNa3#J}J1tK~#2YEVSd?m5Vp*ui8%7Gb91Edm()u<{F_0hcB0b z?0YA+sKBXY$ifz3T=+^aklE>O-*4tyoyKI%1GTbq7m;T_SkawdpAasA9P4JGORtML zk==Q?D1Ua*e%Ip>nSDSg^C3%!cSH@7$_#abkJ6v!flVvnMrq;LHjpOkO4NoW=6~RR zD#LN6VqI>sMRS6XsG+3H*V`dRcVu$2C$N_~{-*h`oQw4}yM!W;f#wGG!l z{?T1s(fg6IWPb8XxCAd6_1ok_#TbH6(WYgRJ6-g1pTv5o%LSf8U2|0L)NTZmJHr@b z@Hg=Lwl1IVgiwH;6u{5Pwfu|a1)Vuwv4)c?+#n9lQ*yRlxlcq+9m3^7U%d4al_?f1 zTB0z17)b9i2D(YhuSp(JfN#m^r{JZK9oV>cP#?-pkcq=J%TKSh2-F2ow1ktJoffFn zfYoc})=TMI$;!n~_W*jm|FPpRA}R&>>n8z8_ICpEe_lO<@;_s`wb8%sk@<7{O!wD+ z^m#qf*x1ZK!BNlALBPh!_H&KQ-?cI_;&0~qVcMt;-0pDN7YX`p1|!4|!Y&sj#Hxaf zj!f+2Z(U}>nr(JLzp_F4J-RMn03hxR2I%1V?P(h)2ZYJ_P_e0avW=Xt*4qzacM=Y>#=MZKX0?aL6 zKOHNNLpkG#+?ZOaKZy9@lO~p_mQSC%5=u()smOT6A+_v&uEko|yAJ zSA4Q6#=9oAFk%Lul3Hb>i!|as&$IPm+wxq8Xih5@!TsZEkx@7OaF)$EKsr%yU%qEDvRs{b3I z^6NPm{Re`rRM4=2=Rl><_#_|Rw zV3CiQ+jSu^_mlaqjd{ct^*{lPXd3J21RKNAP3CxpPIvbQfNe-8kiKgQFS-VpVGL8P z!ES752JdU(`SPJnI4V+eK2VZHQ$BjXx5O{0m6L0u#=|C~OT}kr+iY(1=X@^M zWp65wvR(Ela|;i{)9^M&LzQ1h&6i?Y=N??XhKc&!v+@4Ed1Cd%nNgRkSQfN*oJ&iK z$E^pXR+EnL=@9mWHP$fN!1@ca&+(_(ONq)|C!qT`K3`07Fp)MP1XsO^7*rBd*QP*J zbZ^s65F5nha`I?67ey6;lcXCgvzH1{7AFS`d#@5R80XKDxucQhX^|h?ib+Ut`bD6Ky-M*dbDfiM^wZd-5Cv4L+A3qGrQjWMt!o3V`sgX2;= z!oVhnt3_-CV{2mmnmUi`rQ9Z1p#2`2LPxEG@1F7Qr)yMX+bbT}#Jh?rGCY)`$tbw0 zN4k9!n#-F_gnxn1%a0~AinR+s(?=SHP+*4mr8%tq4z(Y9(Kecu;;u0Y>bX7xGx)k-*T16+i+UO* ztw~kM;gUW!mth_d2QHIeXDQ}^k&<52FgpUBTgT&&X`saMBrWRd)wdR?$bVr$%b4|= zmlt$4$+h^Bg&N99Z;N?$SR3W%&(*i!a{`bHi`LflL+deb!vH_Gv0>$F^ak+#NnKF= zI9ycn!Y)+YYd@IT`vn;Gx`nn8n>|`6o!1k_9R+puKme&*6z25jS>HU6DnQkwpq9K9C>TKib$B9Vf=lZL7CmOg3lDS{uHe=)#-moZ+3 zZjpCR->AIKq1(JW)`H2p+7^-|OdRmfVtkhxVp#*{?`5o&+DcD-^xE>6bE|*ZTRNfv zn9?NNt|?o-lKu;q#X9YT@}fJ@}g*Ol)@1VmMw zWDLwCvmb_o?)R2*xnb7PWW385*CH{fOlT>uObCPAKv;)s2SJi#Abh}QHe+z9gG5lX zMz$O$Ey%PlHC(ZNo>^%iTX*EDws9$y3u5jhZOQb}h0wmrbL$$|mVug3$cj*cg{)NT zN31_c1lBwH#UHD@E77+Rm`CS`USu7Pq}S#^r01|yGZCT0tOYM&ChwrLIO}GYwHB06 z_OnryqbH&uO(AXgs$HJ@V!jwsj?m=ixF70y>6yTLh{sEHRiwCmPwC*ElU~r(yn$AN zp}I&fR1oOFraWW>TG39F!odZgf0TX6b$5D$zkz%0P_(?DPS#H0s&zkGx#g+;YE1l; z`$J8Iq7ceM`LS?xm;h@9eyFxtR4FU92pMDvY7%iIHgHa(8Zt#GTIt;@`YsFk;EMkQiT14V6c`Zw=vRq@Qp;=5I&W)YgpB zIs}qseMTAg5bwc{Yh-NVOgaO2?V)y5$2A6+VOyJ_?+KR4^DshY3j&EX z+FnY4w!um{g+Q|8#yN{0GbtyQ;P5BZVBDas_Cd%@GTIPoaj|46Rzs19w)nXDx)@*! zShl3r`HxwCL$z865eVz2L9&DVYg*;@r@eMv(nPn zl8lTN3;l4M+JS9N+kP1zkT;sWNa6bh2saj?Q+}l zyu9YxJYBnm_zqanK9aMok46EljD-lcVt^OOhF372h>WM8fJd@_%71fMvbFP^=7c|v zctWl#7j6c3lx;Y)9)g#vZbCmV9`J6%&yJvH`3#M`k&ToTx>`ya3RfTK}|t zuJinHImIo7Z?QKG1cs<@rkjL_a_H?fWVFg><)<2xokaSll(`#@8N9WZ0XY<Viy4Nv85tF$*&Mkv z(4mCptRgR=Y(W90`hAZT0?p5+P%F0doOhxGK+UiLUkdo{QvQfcP_kfoLS^*nvx&7- zxBAoU?k+EoE68@{S=D+wm{VqE+pRu8i1dJoteeD55>%q26x~HY8F=LMv1E`>=P4}m z>6@nI8ID6XYeN(}ve~7)^1cPAD?DicVoWJ&9YHS~KA{Pbo)HADg zQ|sU^RJ`)8VyaN<&NFip6>3T<2Nm|+*p`_LU}6|JOeDoBZi>^}ICSD)y0n)0V~#EN zBb?oC-N4htRo%posqUMuYe6;F47}}MAvV`ea9-5-d^sdKq^!`~rIV9%V%l+i`~v}) zUCTCp@6h)Cw{BVduhr_myJf=PdBlJJ_@9|sWy0ETk3H1Al0;3_co5}>qB!3Kj?`-2 zItcF=Pfo{3$PIlii@dgB>@u!%00Cy27ka(IGv;dSvazTg5X^n%eCWy0{Wg_4_v`*; z4F*7BISenXm^^DRN0&Q{k8&Vp&;co2PVkC@LSNH=T91kLCR3M}GDC1Cw4)ezwvZD#wEK|3+^3B+?^DOPsE?ce0nD1tDPtzBmG z3tXM_QDxkQ@oum6*;?83U!FySCujw?%;8HI(3Qo`e%2W(WRB8^@?sjG7Rx^zM4%8^ zaYYdZDRo5bA&ty?XRnb&K~0n`8!n%Nr0TV-JiLe$=Lr_BwQ{$jIMpTR6h6;2)nO2~ zy&IGcl50a0LY%kG_(HDo^%qv31Jyg~3H|gAoyi|TNOFPFvIG|v^R*-hll)a%_(}1UXucvF(~jar~V1Uajo9-Kyecx3wWGm%Rd# zXN-em?8K&5I3|}w?T(v-N0!-<#U=7A{KXP(YE8c zv-F^&tHf`#q6u;KLc>M1BA>2w0{j}V4Cf;1}`zef~&q z?in6~+de^9_rLheKL@gs|36vGKj@!IMJ<~-T12i3m9&^zdMn7GB~;)vWYOYtt2ylB zloG|JVC6(tM}%r8v3PR3$M)?n#J&T+?|oI(gO@=O5);Xtof>R8R9!~C?Hw&$0ytmw z34*~;p}HFC&hcYMQ}H)Ja?;5fk(S0vmPr9lqcUsX3f}2^yOVcT?UI`b#gnO=k231k ztm%>ND)+<-5<-e#VL#gudaNqatq`l z{qoamB@nRVOotR)-}C<-lPJA^oilAvI_yxBT)SMb1yHVt|;;tcY0n9L4(-!;4 zSAvZbC*OQ%NN^XNfT$AcG%?+RabOf{PqAXLzy)6|HwJ7UI%v#zP8KW4gPB&y?Zjku6XL4niE%L$X4lV zH96h*tr6r2FcWY7$e#-E3+UcHf!h7wHbnl#SnF>-^1mZ8;eY?=pCBC>^ZSG=qL7W9 zt}ZVyUr%Sa-X5jn(NKUXeJB`2$cy?ID{Di16Kwb7@1uWcTM>f!rur*+yW|wap^1mv7X#c>nmt7s1JQJSOO%m7s=uiw4#Z$_C$&4`?I#m=1hwg z2dEvi)D;V~)GM{^a8SeXrO457RgZmZL|a#)Vklmw$$?ZY-thD^`jydN1mf$uC3_XC zBV$<1h)WYwcfy6p$6jl-57sl0V!4ALRWv6frH^8@j#`Horhyfa$G5S}*ki&dy5wt$+?QSEaoBk2(KU#Q7nb&nF`KFCp5WkwN#b zk>M|+X+lvOYa=%U8!LS&dHTQnt$#jVqx`ru1|1?7OEs98YheukxPm@k$$Y6gGiboP zFu#zve5nv6KJ(PT*Zv4Bw+zKs1r{x&+HJsRIV6{$lCqe*7+{DO%`K0pXs>e~ADc@6 z&jEKZHp(Q~K?<00Y;6x$0a8rT%5^-vb}bH~{gWaJuzILMqjDUl0xr1B7>2|kFhN)H z3h~8$!zrzj6xkR3h7M@Sc{A@9Sr+y2o>E`?C8^xO)&?hp`v8t8cf4HB2_p>{Axw#+ zET(`9_53#P^~?JRr+3|@rP*Hu%`5J8E8MwdyE0;GO$9G{XG#j}W=6?1jc?Pg0c+ls zQX@ccdJdF{vd`Af%rPS5X5*_P2A%DQfO5iLvVs@rjp>f`B7b;y5yKwB%X|ij>mRGF zhWNQmwOgps3v~DXVo)F5I6H3z_LjKg-I*~*)!93}SVu~+1lh5uw;bJk-Lp4};-Ycc za=Wdsygd*TwDLES0@v6Ebz1M|Q1m=Fq0#bo_rA1k2FI{LgTZP%%VdhHJ2tF`9R>!i(Km` z^CMCa2u$@WomK`1a-XwWR(Ajzy9p^PjjrF;#4I!DK3C zzwPibot)XP@^Yo8*n~^6@H7AtVxC6qA^!-SyxlFk;Z%)D>Hb;6r2EN@79Da09omzx zv)fSqK@eR`>D>(SEQQC_iWL(UNPq1rr>wxy*h>CMxe7BUzh}0XN_|EPWdB-2%`{)Q zSmEPwg0}4Jm58VnsOMgT0{?*;J*ARUURIvj2-m!dW`H0H8XIhnGf~E-n)mT)!;Obb zAhD9hVe)PHh#_W5ZOJN9*j1{dv3N^+k?@qJbO1H>b@o#fyZ;o$tp60n-kw+vnZNxE zg^;Zc{9P2Qx=M`66;2I}LeD#!X>%4?ZHLe5+n({KMG>CnK-T|dH>TZ;J^cPmg_y5B#{@r8zDU`{poBt-1Njc3{m@W^fshgKc zR_zjeE5@Uv!vioS675d>{>c0*{HT6dYDt3NNjw%!n^NceFN znk9yULlfzk&;8!e?3&TrSsa0`;GK2=?XT#4KiG+rnIWD=@V1nVlB!|lu};Fi_HVi; zLS!uEuMer!P9W^mYKF?-ba}Y)c@(HB) zKd=wv7$OW1X5IXH03*C`NpMWdbp#l83d2#c&79cWggnyhXSnZ>bOu#TLwBIFhe(_o zeQ(zHu>Lz_n8~ZVI{P%Pi2pDym%kX--!xqR?~p-M-~JC{N{H>2TH*QJ@J=kn7Y+qX zHsvoPCVr9+k7nYYvyIpwmk$mb(rlehoJ`_mU@h~tG$|AFZVK#>Uok@y%S1w_E$W9RMeWl;LdAS=DD-ejM6yIi+YPAQ*cz#_$InN74X7hDkjL-!4r>qvA>85*<@ z;UPp|LE=GJ-BmSZ59|djS=|hz#`osUoT7J9QGP8~1pbcxH!XA*E>g@WAzreKw5CX! z{8@;ny@G>A;v`u@^vGt5)@g{|QVw{|Q;h)M2Zx&87wG=Z=p*bFGkOb`POQ+J%e^NN ze+@UE4e)_ZA}}ZUUs}*|u0oosZ6#qCB}dczZax%eU2jyy`r}rbH&~UPgDpt&>0z;M zSG+0RuaEsGgpL;RgIIwe>@S%njefR=q0*U{il+Ni$DyXW07|$)s7gAjD*dSegDT2f zApy0^mN|O5XrI;Q9~ex%wai?bUK?x|T2M(AmWAs(=Qza>et?=(cVn|rufAId zXV_{O6~C69tF7x(dI|c|r4nldrb9tggklza-G=jB!LIZrXp7uZ&Y4006r#JCOH46C z+{Eqc*>CQoaI$X0;~8rOTs_Q78GUT6DL^l}vtK@%YG5qP^cl~AZN$V1#uG-63}*c) z?&cVExs-D9AM$+-tSsDtNMI+8Tj^QV&2!U?eDg`%lGV zg5+pTS0g=urJTa{-7KF!yTN%G`HUqnONZLeWH_g+GtvW9z zMeqCM#!cgn46O&NNy89`>Gj`&Pca}fzw89EWTOfbnQ z+^15^gcI9z25o}~X?HRJPZ*S++bzlzPQe*Yo-Ijvy4kztSyUt>|D2`V=zJ(pECmN& zh;d^AR(=Y^*|8_Fp+LrP_Y(;e%iH4=h6=6$JCw#V@)D#9%wh}O0$g!ntcwV)~-;np1+J0t9bJF9US8}`qP$9>nwwkD71pGz0oEOu zZHR3$Ti;}YOd=^n>3*bFFu5M~*bl(pAn#j~{y@}cRS_#G{<*v>aN|hXEL;smy<%g=ufl}FO@;!u96TU5xc`M&lZi=E ztQO@X5e$LzcZh32;R4_7yIXw&d%;=9koFL@$nwQ~i8h9BS^%@3E)o@n3PXj&fRI>W z4zUT?44z)N@}g9q&#unn6YBa!5=k&=YG=Z^Szu7;YbSQX$pe9LfIrns(Elp={ z!0{P!pRRN&+)Jw*J>V8t8;o~h0{9>AgCDM?W`4g?A-0H8K#gJm$8p_1jzIQGb!y$_ zRT+*>!+gL3*$7oIV#5#!w+me;5ARvjBdA7yofOfccSVdRRozfgXwY zdX=e?Rt~FjCEiosjwdV1N%(~9KHwNBojv9cNflY1gM^d}V5c*(inbRmeJ05e&vP|9~NX zH3ZTBlOcTKkpF2O|IL3ZZYJKov&p-UtML>3$i2fpw(djl9hH_g(;H++DN zeaXHvAR7&R^`Z+P9RJ*Xqge{fca%r?`vH!co~=e+jpZ zDxy~gg9C?n_b@0|Jm)8+_}8-(sW=0Se2k;p{ZAlcKtwA@pO-EvQD%_fjWHHybZtj) zS;-Dm0)V)<#A&(-6#Mvy_T4d{$UWk6zL<3@PFuf$do8%&0A5!+Ced+594LRgpN&^7 zG}|Tx8A0R(Hc>&10e@6qS#{ot8i2fv$(hsGdn0h!B&;4!yl)-k! z-fRkYdQ)r;ufs9)jlY4(8@1><`1l6Aw5?fq>cuPo`sCJfRU|bc?jFL1j>HrY4*3~> zUt}^ip%?auimM5SbJ*bHcVXDh%-G!a6XPuYW5poW|8D2{)}Q-$|6}{yUoXhtY?b)e zA9?Lvt@IUi?ad{ubqx&d{(wap%I-D_pS$O9YKN_QBmuER0L=hoHJJN~brb+aR$U% zPTt;5C+CSg1p=c+E^Ao7Yx$XZ@S$cQPe>1^NDtZy!yMZbMPDF}7#lb_GnrkqM+3x)j8~jo-q@z)jo|?|C(xeoDaP^c6;Ok?4#f zz4Z5hBfc~M)UN1Wyu1U39ep6r;l>$cu;na^KCHRR0qAgv^L1)4#E>E>LJJd;&C$!g zp$IUTjCi){pe|BxTYlCYtaoxGn=`T~J3LNoQ=Lo*2aY6DaH?q0LwcHTgbaj*oPT0+ zkFXeSzzJ~ejE*(mU#@LTpk|BIZL!pcj#o)}uxxG?ZVJR+!8W5zgalc!U2&p`Bdol1 zMtx9K-9k!(IY_6Qb?t23ST#Jd8m2=|%nA%g0n)@Nn}`B0oiui!G~czmontBa{lb5=hP!g%VAGvU;@n!^kJOy0r9C}?sKH%UHKGj+Y+iwNsd{7Y05c8 z0E0Y!Z?YrC)43xDs zo99NWmy+4fGGlACA~e9TqLqb&#yF-vl0+A=W~P;EdtAh(n(SIH4D|fDaJnHqCyE(_ z4nG#mUwE4J0yGy%I?8G#;)q$>!YXl{NJ3ebX6U`|&fmN6O#$`y?X*`@^QvsP}UxoG(XRM@s>Z>c{v2BKZz z@bNNgrIpn2_liGO2AUySPL6a{|OX$wp;bmeoh25vw#ILbyJq{Q?JR!*U~ z;8PrzoZRWoU9dySckNmA4p48qM-t2rbAk114A&`QdAo z_4%>Ra1IWwv_*txj>6Q2soqIW-><6FQu$c??Di<|y;uQG$0B4}`dM{|`6MwhF@jX6 zV1u#bNP;APo1L!gDB1n3HKXzeH;Z&hzIy%yv`)(+=8u-uXtyMB(-&ks_tI>M;JVey z$g?24ojQX+a&hl+pQhxNG#dJ!GY*UO)ayD%HTKbv7UC{OhG!n@4y3phFp*Y_p#|91 z4}s*Hft%{l^Zu07zUH>p+{RC~8=Q{6Tw`LEthW8>s#Z)nT-VMh*nGO}<<{hLGr-f+ zj=%phvH;L-Lc1nU;bBPJ()&)n(vlh|-rR|t6mYU90?BFHspjo>JIeKJ_U@cmfe%hn zCEv|>AwIONB8|(3Gg#JgEDX}(bL=o`FEdDc29AbuxUaHf=@Ct`7XDzraLAAgcEV{p zuM(vddbFdlv$(OcSwfl(3P~jvCu~Q*W(SROq#7KNP0?Whd}S*s+Q+)?D|TA=xvdJR z$rbb9g_J*Hm?v-S8N=TcVQrYFspktbNGr|=3WVg5E1}3(*>@Xw$0IbEjyrTSAzDr7 zU_t0+KhV{Xqvo*6bsXCka>i_kG>p;WQAwX=>P&{x7+)81;Adw$VhQ`%!R!KI*|vb? zlSVs5ZeI+dG3+Q_!UUj)>psu$9e}Zcw`4ysP6iJ}dmx7A5i%@J(nj63DtV?^13EYB zXELL7?_TL^;EA|-Sw?JuyfF%)sjdr$lqfwVwdwTGUi?YbqWu!|{!uQ(tZPLB795=^-|PMKchQq1=}PvqDgV1 zR|>=C0VJ6{9Bzc+HprXG;@d#Hiye2Y9xkraHr~iz)nDGlO60tWhkGc=9@*%mGs5#k z6u!Eki$~>|-f>oz`saRifrs)ZMBRdA5uR?}6X6LdOQJPKwr>hgdJqf;zNDzT#H_ny zue$`TyCm`1r}iCP@elaQpw6AM#oZ6@H4*$l0Nq=l!lyO>C1omCFCJr}oSVBh<0kcZ z3EuN6G4&cYBQ9hc2%NJA;U#Qql)4T9PxjMe0@ECHW3vu|y^DtYTDiEt3IhCR&eqR< z)khrE%?DPrAHiSxa%rW*sz-ZyQbY$lywMMq;q5A5TO8~_*4(Y4`*b6X4*N!Zkj@Y{ z69<0{Q{V1;1}!P%B|Ar#z#qWoq>Mfx{4@gh>~5A1TUzY;dbFH%IHi3!wPCWnVGH}- zZSfk%bwvaC!GSQi%>Vm~lx+c*{r8t`xNC|ki}_eXa;&h9N);Ps(!sKGL!1MfmZUxlxg%HZ>wtPL2Ch0&G>B!$ZA{@F*FoR5PUl4Ta*@(s|Ii(syv9 zIgn$D6bo)cPs88NQZzm}+S?7R!Pdju{QC$Hy0*}_7Wn(RCAEXT$Sv%z924dROb3l5 zXv2qu1FjXrb4#Jc^r8I#`(WiqIt;1mCf)e@Rvu#PwjWhXWKYu>sAdBvc%btBtOO#W?iQmc7^9p2P8 zIMC)E+_ndLH2g0ilx-c+XM?yT`8{c8JL*KJ!#nSGr>cpa-r-(PYvFLN8*uvMyZF69 zm@6j)V~@W#JXHygR5gAMTslMk_j0elV#2@ao%k0f{1nZ81C^h<#H}ry{=IkNe}4NL zclxbv=MUW2@Hv7B>9clvVzE$MYy}xyoy;;PfXK2nVH4FKI40VcPn~HBiqv>9U!B?M z(#rRd^cA`pIRpq14fd6+@07%!pAR$NuG*N>i(!Wa_hoRBMjK#tFf)XaSz>hn2Ptid z-e!_#nkNMRW-3nWF&JN!3Wp)zYy<@w3Pw8gO{<9b7Ha)^IM$jh=w(pDecH`LB7jt@ zt6EK-9+{pK(dYVwZ&9IWwAZcZgnJ91+s=`E`1q4L0drf}9GUI4I8B{6CBUA&1Fc~A zSCa-Vr>AC*M0>3uJ#+OoZIj(=11brBA?JH3xG$~3ENY*$%o=T6gZ`X{J8wHGkuBP; zptIQQ;D2#5C0~2gK>ScvfH1d*Z;b%awlM26(??tuBy70Engp|^F6|C2u3wKnR;g6f z6EZ_jC9Ts%iw6F|Lck3{%MHl6aE51xrvKvh9o~7GsfK+GJ%ktNC!&|uOH{}yEe$84Uq#f1 zSyv5XvyH^__{=$A-5G6g0wd}<6L%CqG`i;>(?K-yV!|6vs)GgdE2cySmtkrR_0C9? zLgp)22^of6srPH8N_c&ccHPi!;_3No(}&-G*MpGwGd6xJUR?dh0tUH%rbqurix%Vi z!=C#Uf1iy14!SHwE|~4Dh5!I!*+LDOt0IuY16UFr9P8qm(iqoTy;f1D9-p;ML)&>2 zhA2tY`M7CKzx|7vQ7kD8B1Dqi-fchqLSsK|_Uq5@KR{>$1Hdt95q$!u!7Fz_2b0^P z>{r9_{1GD6v70dX{^l&j$?)}^Fs{F(`gs{XVx~{ zN4#(NnlRXB6OTk@UCAs*m*OcbQN(*xsS?8rR09ulO=BnE8>xp~*a*kkYj4|)lEjI{ z*_QX46U)AsbnQ%{6pW5EBG2~}m*D*Hp8Pp69D5>e ztlOXr%70OlFyon+FfsLc{rL0q%N$pZWv5TF~^F1&)8+gDJd?D z&_?Hx$7yY@K@pxy3GJ}p1crA~gy%W1$s@zC<*}XF3PUCphD#Nd*jlpN4B9uv;}BS$ z$Oue`ev+MUeIJxSw!|}Pd}3q|W!y&>daKhU7u_pR6II_Y^TNJ?E)uhJqbLGn5ogpt zWZ!RpL06mBVdWtWO!Pa|5lO!-{kYZ|Zq3g(m5Y|DIC?jACrDvCs=h-);|P1gT_rCVNh^cYaho8gRSxZ&EU@x1iRM(Cn+V!-fd5CpNbslKgzRzvy|dr0Y>!q`0{5@-oJnNZz(Q9W?HQGvvAF}F2nxV zV+IaBoo1i+YkUt6D4#Bc%8$+EfS3wNu~?MX2Chc{ulQuAco-->TsymqaW+y??Tr)Z zFD&SRtkAY&v}2~^nIfe@bHj42aDgw=@pEG)Y&#`NQe=aq>@@Y)MaCn74)dvlOajmc z5gG*aG3=I&wL8-eyu?@GbLpfLB1}=s8Qk@g6lco1I2emVoiUp9w@4lIB`)zPI;?B( zrKE9fmC$_Yw5v?5PKwCOtXk{uwM(?$oWfXX=s$$d7VwnBl0}6UAWy6{VH>BTLbKcaV-0^vL-ET$_rT2{7LJ2|EsRo=l}qF8cEz3fN=cM6s%W7oGr|Gw;m`|SKIGk#kB8I90IdTm6}}{oi|_asN4F+gN@UBLD#-e*p&v|MFLp|C@~OzfAt~zr26;|B6}} zS^wc^{s`#_GQa0{JZB4)^q+N61YUuTyXTK35s1_v`tGyu)tHA%)nnJ9tsvj2qH+Qx>5|=apAeQGGc$zd4|-e>mbxGvCytg@5*ePuM>T3_ zWuuW!vMJu+Z4gv<^x$X7C)Im&Wvk5rJIWxx=A}; z^gs>bfJ6xOc-Km(-ozrRg%NN=mCTyarJ({w8Xvd0&IvoK3GnlbZ(%Co2E_GmNp`FU z;^(?3=Kgsf$4JlfY~n3$dT}P-^!)Ts7E>EDoj0=;U4;{PSDYe_qXe5KW(Evv94|rL zD#%Y7TTx}R&U@-I9{Bmd*3{~i8D2Ji%eMqG^pv4~I+~b&bT(@Ld}{J0x^{*JGIoYW zhIWQl`iB4NMi(?Nb+GJ{?q!nf7~>Gvc;fx&G=#=PJLU9hHuhi=3n7jiyz0p&UL zg6q1^?lanfyGUPSZ%qC@HYNa+f3Kfsz-JxiHG0sXF#*wSaYvSjATZ$cM0_iNkNY6T zcoa=$_JZunid)eha=B&5JrJY;S?L&mKyX$tsO~?Oj!WK>CHL!S+toN)GsLK zVVW=QJSl+If^op2NN(4qA|I)62r12YQor~oZmG(1ZiNti>|DNQWebg7P8K;zA7`RZ zZs6QUGuzi6POU!2uv}G!x)}h_91)_=4m2HtTfL~gy~EcSr+zxcnl+?xx-=PxEv{q2 z?SN2wkmq`STbDm15^_>6S#gywGL+x+w`Nk$GLp!5vUQC^10(}U4}cpsX&EP48K76< zpo7GmfHsnBpLaS9n?Ekdpc{TFg&4ZAL96k_;l>g`4kr(%5{%-DzCh$XVFXC$BkRnX z7EWLm9+eva5Yc?D&SuldEhc&*R|SdPyQ3kaZ+}X#E_z2HP?MxXt!u_6oJO@xsjZ5( zB1X{wDb3d-uQDeMk~-EST};r$)RzESv?Y{mOBtZiPc(>oWoY(;5|AaR5M#Yj>Gfp` zFy9q!kSJzQVLJOgK`6~}dr$u9X3YMB8T_>ijp3gS_CL^vzP+T~pK}uuWJjg==#bL^ z*C~U&$>5y70pvwYC!izpe|D<3c2(0Pfrd@RuJdhcABpYsz+L8rpMhjG*ND1H71>XW ziE3|TYJb7jX9)mqlwacQ7M>Ok>Eb|=O9tg_mpelsN;T_(sI|m@+OwEBcuu_=L>+EY z>>A)I3}~b2_HRMJ2e4#dn-YUIVNH`tV0Q5010se=3T(`sQivmGsK7ft%wy+u?XhwV zg7~$kkBeR~2}HQxvx>k-afXn_z;0wG(k2AOR$fIgFFl;a-snv7x*#uV>{*bG_t0%d z-y0;RzNer(?$AZ!k5y5B{4&)bst=PHWrt~Mfr$NTZb$iIxL3+u3kn|kL)w?C@65Ud zrEaCvx2p-GA%->o9{d`ft7>I7tP3#qUV;W(M7MV!zpj<)dxOQ|MA;cPzSBopE4CsU z8ni)l#4gva#lAojf!F)*GfKNLop)t^^Ddu<_V>PB@ShLt|BHwD!_`;($=(9B64y{m zP!N!5OcLb$QONXoiDd8?_3-1PFAV~n{QJ+&jhw|lSH%r0^TNm?YD>P#9;a9W_#q;g z%w*W7JWV)%R;@n0?~E#c=}n%67tRU|I#j03#s}662o;6|WfC|UiS{iQ($|JgqRZW+ z|SQ(^t030{#FH9T&273dlxdcTvBF?+ zwKkc-0HRGw#7ig4`i8YKhrine8!4MwjqRfHqawjDyo9wf_stgw8#|P>GF!5nv3Hkk zYv)iX6dzwaFKu;s3T?4TAZ6lowLcvU#5M5N6pOzbnt!m{Vr90-JWy4%V&Yp8WSAg( zm9U)Y{?jO*ZGaC7@)*50e$w~R74$Q=hY3@|;dV`nsqo%oYAeB!Y`6F*Aiy$Px~K9>?cK zLPU*cT?6tNa4Q{TuAyo086XJ<|~3XEcK`S znk#T;pC#XzR9X=l5-IviRjROf$<>*_fL%_5iRD7K%`213KrQg?n|w<56%Xu zJ(0!A{;Bsx;&*Nz8}YVMGxt z3*&qR{E&N%fKAv+A&UcwIQ|C$%M6WFeE7U&#Q)fA#PiQL?XSe)H&Dc(#!`YY>_nYenKo|8Y18t}{sMMAnX{C7D|Bfyh8D3HmoX-OEZ^Ku1aE#IfE9e0Qxm#H>|4g-Ro3kTU}^pBa#w2XsTyn z1t>weE5a2Qpu5~dy}+eFBSAB+t#MmdnXPv58T3G_HWaq)hN34XtE`?~*o7TV9EpWQ z3azdDV7(|(`m}AD?$*a}a&muqi0dj{jdytn_IavGpPacO7HBH_!Ze}r2A%QxL)plp zB7&?>+x66U%Yqvi-ML!cvJmwM+Id!v)H3a5Ib)f-@%!fTr6@7=9@&ns9}kw(KAxS! zrmGeaIi3ey38uQYstJuFgcDOAyX(_3CDvfI7YbsKXd5cL=KtK6NSv=IJ zZ$yeGn0%zrj*iRHsxq0UU%W6Ph@4wY8N+2ioNqYtB+|}#neqyg3aILT z>^;n`uM(-Xu3mCxf99s0^wJA>3w;X5)meBdo`;`+mx`6TQ%xt#k8wd;wpL-Ecqo3; z2*%J5;66gGn9I@(HSoQq4gdKYbgc}8jrXZKkK zp|5N2@aNnhqW}6r&QRal&fw3;^5Y-T8kNWPxh?496pciLaIKR2rU(9i-<8MMRDPo~N7 zMl({ixt!^y49rz1ks~JlZGz2f@Yp($_7;0?!Y>4BrBRBNwqit6n z4$ckjrs#73O7mW1w zdRb?E9;qS%T5`ZP((b>Ns@Tc;@91F@gIS}0=W-B(X2PQ0EWFn&)XJ~0!7!^kTGq>%!dWN7P|1|)4{6b^h$V_8Yu0j8^=w8AZ z`y+RsW8{(FB`usU@G&Ewu!P}1ij3|5Qt$r%LOK5(NB&n2#h;WjR{ggj27{lQ0xB$T zW(3E5-R5wYi?w>d`GNr%$gVt{aLnd1V%B6Y)#v4>`M+kvp^R5z6hlpSjj`G9zrGt= zWoci0{UTMZPY@xAq)z6~Kg~1T#oRr^hNhtjPeeJ-%v<`6BOGsK)0ZhIv-cI%@tM7L zJ^I^OQ^{oSlQeBXrVEeFYIg39_q(sEQ{(8i-G%rB)ip@11oB~(7oF7+QFCG|fCI(|nxduzLYFMw)L4sq5{ z#Q5kiptm-fn+&=H@sMq78oDIL=%nhcpg)D!Vm_@PO<6H) zlU8y*Y?j||<70)=MljPx;nPO)uNpnZva@N*UL`DZcl2QJe`?mAz>VJG#popN<@dd` zRP_{A+JM>8J%!u!9*Y9L?10DZFSZx}WJT}F`2q`esq#hG|JTFbCYarmEzS--cg~I| zjoiQ}4u%qfCIKq66>R%46YX^;`ukAD z(Za25am#vY7zU`OS)*@a@KML|Xunb{jW~@BXE2R47k*MV5~4~D%=AwCq+1pQg&>K+ zBo9Nx?~9=`graxcSLapA_}n@1NfZ`o;4{@LCuFtDd)7FtN|5OD3H>52!Pgij!ysix zNg_&(Rrn$KysF~yQk^kegoL^8=pfZQyO<<8bsh1hw5*XNZOMEFQ!_3qUq9BTKir*c zIlKfLR0`OL+_B8HO`Om z5Lw>=w3iI6&R$P5maz5<{jZ+y@uF>X#xvV)B$5nijQ&5C#!7Zm6%kGPZ?8GlZN&%U z<@Xr1Xe7IS;bfr5N9RiSn4cAUlK{gYQVlPliYfY}Z%Oo|uF0mT$dl788c9wdN9(E9 z=jUp!J|jLUDD@6NQfUMygk)M1kIxq(HC0$dZmD`}3)HWYtc=icl^&i?*)ym#PoPZ2p3V>4G_V_*mm( ze3kB#pklcB#yZGDGr=@PS~kzBJSYwQip8K4Tt z?pOS^|LKa@OELug3(`7#sWqmGADoFv86q)g9{nAZbffOU1|~U((=w`|^gAd^b<(N}=-L`dqtM zh3@mw{8~D@vNj6q&JiXc^g$=WfI&LZJwO{xx?R9Ox>)jWgQVJxDI)rnF& zx5dBE+D@fM@60?!sM^iC84zu9k&=%yTc7L(=Jd?YNAzA_Ky!jd`V1$LH2kO{f&@E&9OfJ|18!LxAP0rcFTQJXgsz(KBM4H2` zfZZFVb`}VQMn!Crp`WOeqbi^=!X_kVrpOCL3OA`lag@zeTPDo-(ZBAO3Yp5mZAi37 z{BrQ$h1)>QL_H~1ggG!mu*GFCdWWa>m?2ArE#^e2F(N}HEbf&GtrxGcbhJiUVwT@c zeoUxmaj$=fYy4&=nS*nVY543IHS^=FQ`Sg_BW@17=|y;2X17hm@ZNa%0rPrIGIMbz zV{uw=aYtd*+iJDgpk-=LF4{1KG^~7k%n!3cY|5a)%#fbc!>l2on!N#&WK8;Sfv*qd zCUZ)KXq{6C!w|Q3*x{({T*hbdp3&hP6&Yu#wD>HCvx&~dYEX1Z*z|%5qDqi>OEGA9 zAW)6I0a7A~5(&_2MzguZBs5xX8&|8P$v=66uyA;ED37*J)kYmXYCI-HBrIMq^<)NY z${S3T8JEJ=RJ=Q!T8jv#Sbdoavb6*6P%+c7=k|~QvTdnh20MkbFtCk8yCld`aNmmH zyM$ZRRBzNwQJ#vg-=TpNN&-}pO6X}4e6qo6+i6~A#&Le+1x@mBmA9~1@f?aoyirec z5;1{Ud`wDiw;f^B%>)uL=oK3Dw(>Mlc-`EOQoI~H-bf|nzCd}=Xqgr~>6-}#Mc^16 z)B(0#N^m*tZ$tJdAiuiPEAd1;Tks}tCZIll)j%BJpj7K4L)+~S^*t@MK*-aGPI5We zqwux3qPm_AMK%5WX+G4~qr6rSa|FaS!da_|V5r&{;l1*Doh+hH$q^s>=-BE3r!V$= z2tCn>C5s?W;1XTxjt2*q%mF+@AKZN^7_5TEx0N%R4x(noj~SVFlv74MMsyA+gE`O{ zP0_w>63e5oec0*-Rs1x|mi@g4tXEqV;(cJ@!I@;5+lJ|V*m(G7{PSpsFH6olu7_L+ zO_}r+oqV2>@(x4k`l&7*H&|z(*7wcIlCJFD zDsEaBA03}ALq&*m4?y7J(3ilD#14`Dlc)XIp(&Hbmq*aFC+*)@>eAOIdD+igm?-)` z$)EqnY%cvD93w+t zb4VY$Cy^v_2-5ad1IhT*Xv$se2>QlI_VpKI^P_Lrvuv6wAZ>th6eMsPwh zhoaMh`3X6XTJWQ4K=ndXPq%*E|77BuwYdq}w!O_-%ItPc0~*r9B11!(3Me^y4$agZ z^l&VsL)J4%UX1;l`*EzON*FVB`Ja>4FAH2y7Z?1$-86VJazTykbW<#L#yrHE%Hsi{ zr@N8|WWul za6Jql-3=+z*URXcs9%-E3fB}WICaWsq3U9z_l`joumosD7FeLxCg&IQPQ>jISGrQC zFTrseQmD$--1i}E0AIYdA<46A_NLzgqCD5OlI1!o*3fQe1LeUkZ=s(z`f)UyZ)N(~ zt~Swb=^(;kOKj!%2`4+z`lPXIWwX#Iwgs^=Sb~hDEcDyGTm5{#d5Dy5rUg~c9MseQBv&6*4r^&s#H@> zdwUR7&GBr$gtZWiv0#_0i^IT=xSL>;u+E#dF19dl2rsn?p@uUw1vM+WDjyqB*ui=U zQa;qV-A9Tjf0)}55boV{Sh!|-d3M-sn(G@F02hsR@9AjJ3EoTLz>9^#uxs%V-E*Ll>-_UyXCvQ{w+#K`w=>YbB=59i!7KSo* z)=s9MMGizVx(+7t4tBZ@hQ_Xc+=~dQQ86H1`0_|wmdcPQX?WoAezs)wNIo8Lba1b( z=9O}!#FVKO)yqZB->OJh>v*TqPx;X_H5J!>=iKMEnR!EW_!4Fd#Zx%$4)IZ z8BE`j1;&cRPbeWO=`-gA_B<{`M@>QnF4&MIKMLVP9hXFhUkH;Rln~jKYgUO3BN=*5 zNek92n|S9O800~#PUl~pG^)xwD;3M_-h|MM}I_Ed2b_wJ5#(w)^e<+b;utjR2uip_<%AOc~L#dwdyWvPM~G zF|uIGenWeoc>@+JX7Brw`!}!Gg_L)&_o;=Hr4oh;dE@1jck(wZkJ~o!F323G)Ra~G ziegIU%ZS+;cq_|o1XJbYR+Y$ug8|b4CjLL?Sv6t@TN7kV04GGwa zO)KlUpj4IoIzm6D!|er4hah}>a}=r=LvPgK`&BzfRm4wmpRXn%NQV>~LnS$@Xdqg%z8W$iE^JpE>t4Il73o%77#4Y z(21vvQRW$dBa4b0S+Z7GV~jeyqtc0eSEeuxA8NRyZ>YeCl-(u3MmK;fxRET=jh`)< zMI8l$dZ?$~gw?tQgthIpou(NaMyNoSj>wMbi(5xzN7?&%$2P7$-yqAi0i@2UIxIU3IkMzAbPYRtyG~sEY|vehtT9~F zVJ{B(Z!DRk0Kl&g96E{=P z(LAHrrtZ8jbXm4HK2YbcSGj*V@R&qiiqlx9o*k_^)yu)+RaZF$15^688Kkv4Kz?LQ z6Yrs_!q}82#8?3&9wX;GLxoxpSBCTBgkGtEsAP#Tg~clB9qXNBq9UP%p1l`#S#kveh($X5|Zl`={qTiIu$~3qu!AI(qx61(YVK?>Db%^dCRfE63wLo1^5_8A z`M9@ud|!Fj1g7rX7s?9DJ$^l`uYmX#eNLRPKBUd1egjPIXp6%-9bp?k8tEpOu9ucs zzYizm`ZBIM;cN%-+>%Gec>fCNz>aVQ_^xvwx3FW%$xuZ#U_vFfUhq|x#p$%^E5h-< zHitb7{>(+TdHPwXc^V~nRO4AG-b@a0J$H84T2rTW*a?b&b#5nNI=DFtJXTwzqHUh< zJVb>rQ$7l#!qgIir~n@dXKXMdz7{W%r=^qtg$rZOv{+E*ixS^Q!a)>mR~WR>PqW5+ zv@0bZ{%#+)>7oCJwQmZ~1nau2RK*qBwr$(CDzT4jzDVoIq7EFk)K zz*bRc8zvZqJKB06_E!nmC8glb-xtN859q^gd{QTI-~T&_So{qD{}&+z|8GMB&h~$3 zt%Pi?T&(_se*d-t79l|T30)8d5lB{c2b^Igpt7%-R})s#cqu8zCss#HjP^>Yb-FJA zdW#=*)yG(yFFa1-GGq1jeysiYaPbAin?61^7(c6z3EXknrutMn3de@jJ*NsZcVEm8 zM=6d{D!Sx*)+VHzWF?mBEPX;mt9{C%w%F3832h;}E)UP?m=!6hnmx_gL&415aeC{_ z;@V=p;jYM{e%0h%MXa7G;ZS2s2dYV>`@wwj>h>ZZtilL4$oTZUSo>%u3S#UAcvKoZ zhEpxSj0))Zvx+s$qXP1aggM7S)3KjYk5y2Hp6ePkFkCDg0rs9u%NiNj&Gv#cWgOqv zNIzY0le41g$Zx)JUXsz;=Zmi$%lnln{GPnhLK;wvHY)FR^6@bAP6J%Bx2ayh-Lb>P zA$@(1)96ECqIfD$fQ`w*Gvy^AU&x94mL>HV0Xy+-I6#mMw;&e2I%A^CKlT*kD|y(z zh8}TfLL99L&=y!E_8V%qA|BcOuIXw@4k2~4^T7WjCH))_aWJ?4 zs18y#A!HPm5H4wiFx|

    (xHG@T99yUIZSo(?VA!U4$?Mx!hFRJcbWt+qPhy@@Qfzdl zVNzWgw&ZsL)zo;K9MGwWTsL}#n?Gsygpu+*!dko8oFEu8N%!5?vpaWlXcjwsmQdU`Vun^i8%|_ezA+C$ zahb-CbU?AvW1$Y9Yy_6hk7l-fT6mH+ZUMR2nzA~wAIbos^7lvpr}j$M^Lby!{_(#2 zUrb8>L!|z{D0B$^Q2+gTcRn?le7p#pi*urNwN{}_KHt^n{7et=_+tHOM2)#SoXUuI z$MiNhayl1sv8SR!2N*)|$Wr_FGuaO_?N}$Nqolt?)(}>c$Pgmv#}U#k4ZIU|4FBNHEaKGtuD}Ih`R<0-Uo(p90x}NKSO-No$#^gSWoT z6Pe$|mUFLaZ-4VPAGd#eE^pmUwrvB7UBs3C;NoD8NK$g?TlVhFPh8Dg{K16lBiVDz z9Yt{Vb|lR5+$Ii-vq$3@Pdvu!%yAimvl;Im$NyA8)n!++OX^adEklvx%SfTKdP}B4 zmH6ZLQ#z}Uf1vn0C9^+Cum7{Y7Wl_Y#QhFs`Irs zIfxkFSyY04=eL;azSGZNGh$7pspY6oNyiW?8hu7H_s2g6s|uRgn>f*UG?7(7?mLbg zDhC?@+>S@aPH2)(BHe5v4uCK@*lbH`^G~P&ca!oYod;exC*5^;6mninBb`WCmYl6X z)jQL-$)~5jF(b6WA10n_iK@OvX62JS1QY|4^|T9d{xB;H&91nHU1)w+r~zqBcXzlD zp^n@yk+23C2So>ivj=1U*oD-oiHCzX8QAGPQZewsvBcbY@`3!u&iHh;;$Oh35{s6n z;f2>uU7^~k7(aFTmbZA6J5g2kYWi9SqRq?-k5MAf=;h<7ugOz@T(y6qmjyv4U%q zNtOY`g#k9Tq~F@dR+)8|g3p^o_>b?M;oonPf{V3*KCi8QAYecS_wY)ysb$xGb_9oan)0^?fO+^9Br%)`)z50AAtO>!Q#Z9Yvbza4feR{^YbsR6jSD|Rh5>x}y{GFPb9AE29cI9QK% zTllQiR(aInMUyHs0o9u6$b*GfzNm3toLQ6@^IHPl#V_;*LNFQZtsYj=n>oHUcHKLa zq{$vJw>uWigX8a>Q#(hqjMWmgCRJ^w;%`l#Hwi*rhmPg zR4bj&RLcBxg|WWould^BVOe?zQ3V!mK8mvx8}Y*|WYVJ4Vi|0#5Q43o2ZphZ`kT)& zF{Z^Ur{e63=cfW2kduH(c z+ZV+S-pvqwvqh`=c0=SYb^*MrJ8d0&hu$8q=!#}dsH2e>ImxtchAPE!?=fvw9b(gB zHF@n^t_F#+r3>=fU&`(-I z;(#t7d_P9jRm|GxO3OCc#F)#24>#Nnv?KjAq7qz=R*|yKQSV&Rj&r?dp)Q(P{1B1l zn@@M>I_zjI#wnCEHF`OHp5odCzV0_vpVM`IROV zZ;?Zhns$W93ZI`_rd&#@y=zLhk&QNS{~!vXs0;DYH|a-s4N|PLduSbP3f=$>)-Lij zsuTb9gh&)9BDXH1pUKpXe*c%QzQ zO`jqXGrw-#&tkZ^Qlb>n5;?KbIl_cp%tKY7Km+gkzD^Rhxj8uW@jIANJG81hMH07S znMUOt7hhLmIb#k*<5!k|LQ%rmpph%wgl#Rd?||)3unLuqgGPa61&j;gVbktg`vbSWM<7FLHE{j(N+-g!{qpV_P5lL&r6(ITdM*{T+uEygJ zMT9JvjgpHWzc2D~^OR?m{d}(Re;L5?`CGC6FNn|oA%62(o6!BKeXfZ6eKctBV-}<` z9#2M)6o^!MWg#dmx5`vPB1bgNR|xE~$|=pUX1(pqLbfxZqq`5;^9y&p%>_6lt|V?@ z(%Qt@*_f4~qvglz+dX<0R;GT;fDjU)xzK20fW>!q{}g69(c8pm3S}wKlGaPG9zJn0 z8-}B%04~QB&c;3Uj?+6S49dLA?|LKl1|rVuw(&(C$III|pzaoDb;^yWy zcy}Nxh&t<+AdxP}LrK)8{wSf_S*;c?>XCN&u@!C98ft3^HWG5of<>26E@MYr4n0iZ{tQ3K8-LB>lePTMm zDLL>cey|1xRy_oO7d#0atuOsjcxn7O+P5ritE*S*~{0nI=0LF3dY_Tmz6--_Yu$ySMj64qs~Xfu zF-&?g=FyY#CrVIKAl1;WW%_0hC&l0k(#0--aCvp6uDFk$GOH=*M^_`ls6<2auQQC{ z(>pndDku-3yRqQRZZxwHm9QIvsy$I{&O**UyipD_r{t!t2t*aGfiv#kas=P~dn)*6 zr2+RBah>n~mkUS#M*&z7_q$7d;ExAA9YQ_z!NC<;wIyH+M%a)ZMOe5}CRtK6xHQL0 zqJ@K)71#P^yua-kgGhHK~f3bP%$2X(8=LzH>{V(zReD=C-CejEl5R<;+%j+ zOe{9%J?$`^?E;0FpSmPYt%DE=IZ!~owbThL$x;J&tUJ5Gcn&{6IM@zC>NmOl5ben1 z1ChCub#e`k3T9arLaryW+1oQtnk3Ta_yW?tJ}=mJFg z5mO~j*-Q&0<$OR(8mQE;Q16`dxv_J>11SDf%*Fz?>Nw#^^Waq6o(G#fKImgQltXFF z*y#-Wp`f0RXq?B#JjG?;dfd#5?9CBHfvFV!+fJ#b1g62;f5T+M8*;xz2)j=pV9fW zYMXFv5+zLqSDpA0Nj9-06?l12<&Ragx!OhL>z{Q-3{(|Ua7Hg-$Kyz5Zs%0>`ncVp{&HK~X9IG}hIEw~ z`EczM&oy@q%e4?Qn5*$3ASN!?TGrcDgqFy-riZc)yB#{&d9WHeC8Le9vWDo;t*uf? z{_5N7#{&oog>roAy~Vbb7-VliuJZl_(u0M|rdIfK?9~aWHw5gb)f2@Ct@%C+ zCk%aWi`Y<^^rA~1rp%AGLNc_R^A?{##6bI&<3LAEm5WTwCH>m7R;{6g?r9R{#ny@F2Ou?O3Fq!Xv90(Bg6yC_dTCir9XL>6 zA*fd>F{!4(h9=CXOTe5gOruwcA0Lpkuqw9Z(KXm+$W3*|6W2Xzgt=e*FKr+nAXSp5 zu2OvLSgN*j(!+o&ar1OECkP?Yfj9acSUKpTdm&lfEw_j)X~!}l!*EHzr9nA4jYXUj z=Zc9h0vBWXEpL|Y1(<2gRyau0p6`b+LK*~yMB-D^Trye|B`&K`DwjvVIh^JwsJc8Z zky+4XcGb_THB>Zh@(>sJoY;mA?R5f85z|;dFVCbaC8M5*pv{EjL}p zwXgcJhR<}SzCz+sy};9IWVfN^+5?nucg2=$W*^QlL&Gdj7JLZUXCcUj&$1MT-=K8k z!Kw5Jwz`$w<7g(u?;=p?eU%VZz5>P*DLYjC8b{D4!Wh6Tibix?YWpQT4$CU8>nakh z0FP=?0{u!jRgXcs2|(EHcvu}I_a+z;3#J=<|a=gR%+ z?_BFZS%qf4T?GX4!KDbg!32ZPd(okoquVs!*^a5nZ}+8Ag0e(S&sC*UI`K~~OUAfg zKj@646I~8KaoW9~zeX0<4!FH+5r2J6`Qqh5>0u3u5_2gSOPDQMcgGs<2KDCjX_55u z<&$R+8vRnIPjTNg&m#N+-H@brXOomYk(KL$coSz02@Ugs{9F1U%CpE!`K(unKD*Ta z%}@pYEq(mU!1y29Oaf`k-+tlO`5Y`XH_(?bvHLV6`43GMA>D1-!vo`y$3#1=Cf$yl zuL8c#??xi@lO%wz%Wvj|36%iO(ZFeU@O}&AQMNxKa|O?l%4%cmrwjedWfi8k(H*}% z-yJAtg&gH=i2`a;I{ZaYi9*Y?AeDK;vhi-;FF}1ZRwb#qI`h~dXuf(9C9&2Kn(b@s z+-uuJiO7*{XC|>lTXI3td9g9>#q<{anc{^N7&?;m}kX!7_ zf`EHTCjV8th>Nxx7RrJKbFjBfVb}|*R$xHBZw@kHQv##6%Gm!wB#sZd?l*-%0@X4kw>Nvk!N+Y9SRE>8w_DU-mhlUs}IAlV-I z7CO2jr0Ua63sq_~*6T1+<_mNZDD6BqtI5o_m=x`mHw(x#&Gm3$h;F`yC`KJ2-Gydx zk;>vUnp%mjm$fKFD1f}*+eK@099s$y8AAba4n3oDd9A<)J`|0)Geob97HyvT`uPDjmV@wmde{f znspndK%~d?uwctxBTwUloR4K3#(t(l3n}$AIs)HiSBX@A7NFC%Z?q5oEW!K8bFh7a$&4p)EFQ8c{dD z91AWCjBZUv<2ei8+}sgjh&atjMrm3k=(+l91(VKJDm60-ViTX9DUBeVx*c;+w;qHQ z&K6{&8>SC=+gb^k>&zN`&e7KrO1i2qnjz0WrE2?f0la0`AC*lF?f`tq_WJ0@B8o_Z zzg2EotlUFYhy~*a*omQY`c7ry=f6c>^$ytyM8a?qNTc{M47_Qg>7w+v)yo}oWc0W- zi``CC*P0(e3_b>M=x`5I!3-Xeg<~Sr>)C(!A1!^i*~$u&1q^7mE+0M0y=H2? z22(AX@?(!5$1q5^)ibNFN~@kl$*Bo?XVgVB-R>=;)ga!(^}+=I6~*jGI~YFs>8!22 zW7TKeC#(;)4ZJPAW7zd>v@>xAlG4uP8jeDlI~dH(p9@1VsD<%7L|e*r#Jn|mZ!Q-+ zqk{7TR+J*HNTiwElZf$32bD<8{;DO)Kc? z+f4=hHsnn?{6*eJQg!x*6cCyQ?sqesoPen81;JQjv8u&M!#EYxIjTBA&k&|3Lf@mi z8q2O#273C2Ot(vVft#Rr%YZ!x+@aPe!;P|Ri3Lo!c}sDE2FlnKJiLD7X@*!6-NLxXEu^yqSUhfdtkBc!{fCxJt1Rp@Ze1>2?)5=+1bpWB zr^Th~PG#U*@lQ$at4<2CgFJb-$y)1#VhpFc#@g*3(&-Piex1MVBX&V8SP;ZC)%g3e zCQ>3$meb__GQe?&z`cm4Mul^jM}VM*L=B$RT7};RdQejsW{jUyPvVF_cvqD;LSHsm zGP%k|C0n*{YnKuZSUJCJ?EtR_tygEhmReM7>%Ff5?`yAG(Ms4jGQ~Ri_G7eTPA+;P z^OlgIuTm?*oi&7SKDqG~AT)F@ikxvtbVUlm6c8f2R3FC!AS9{$%#1p84%SY+? znZQ0*lfQAahI}Toe$T_NE+D z`A1~bnF_>9k7pBGIaTYT|FV*Vo(E+SXHZ=QoCIh~;>dEVT!)$ZsqcxMK{;Sz_InrQ zA|Om5JFKH%Ub~U>5EfzSw5?oL(DyVR=MQdnb%C4jnP`2y68t|0*!$G)Y}Odwr|=E( ze%B3di_(`if8KSte@yuQ@*4h5;>p|V*gO1*(`LxONzck4a7H>egvemS_J00&z}u}zMlrP|ej$V)~Pl81?sy^0wcA{PPwP~jDOWJSG&xEZyzXpmDk z3O?%CZc2qP4X_(kw+T0##bZ%se5^cNX;Z6na6WSFSjb$INP0nfXh6QG+`ryG0I{@0 z8-F;akHov~SSg+Vs%20iS9UZxT2`O3p;c*_;1Kx4{H;c4C2$rl7m04U8zmnxl%}tY zsmZiLX#IvMqLrWBmZ!aDusX8@3&n!p4x*##xWnl7P1iBoFr?HBZK82|IajzTrY|Cp zQ1>u(%AGK6^&PMG5eSWJ*`oB<<}!nqLem4Q!*wueJS;$gKHK_^t+Yi-6Iua4A9ZYM z@c-xRf-{QC zU4ri8F=UE!Imkf$wR?Mx*@eYKnJ-&i9ZYDE*q};VW2zg0tC`@V5Cnl=P(coyhr|Tf ztDp(IQ*L({dKBBnC0P6EMf8psmH&PL4C#Qs3eo0(#gHq*l>R%Zu)lW6u7yGZOHx$$ z=2sfvL|G|Sz_wr76Kw|W@R}`vVBI;;j&4JMU#dXmk#w?6uD*D$?m3Bx%0@hctk8QG z`vW2%XU>WcvJt-E#JWIW@Q`=5MJ5I!^;-TPFWsp6?B-dn7k?4& z3E^7eM5BMS7j3jBJX)naX)n@^mlo-Fi$ZKh47Nu+WMsTm!u1GLQiRof^*wVy0>X%P zH2|7~y`XM%l^W%^Kv|b9OW)qaV?>AQ;PzV@jq4}^2m|j+L>*Gc?@^wPzlA2{GlE(F zBh~aTQf4|&;7$RK|qLPM0vrw zrSM7Fg0vB+Ky_q%$qF<@d@+t%-I{^j>3VK~=M|`xdgmy3hvtjTNt>0b;$aXqKP}|P zb}G%9A^0u2!`2uP*7QUg0%!nlN6UZ$`FvItx04meD;7;7uj)T12)V^()e55;DN_wV zsgoOzISr3c#FI)yCrZ#D)`4c3LhsK*aTi?rb47Buj@HQRbVeNTQSLTU1k8V};mht$ zy|m&wqI!li)~)92r+rzys#oqA>mW71!K_qW+n5r|q&bf`J042msUULFpd7SVuco+2 ziPWTHHn%jGl{8Lp`^gt-($Z>;$*X~vOH0;!<+VJev=}fvWfzL%rv^V!m^0c4F^mL( zl3S^Ri4RxtMN_g1zGiy_aHj#U)V9^%GZcmY;4F;6TiP4W53E#M{c3?DRw;sT912W^*O9vp%c@F!=@^@ zGLs_RwabPExL+b!%8!FyFu0h@W|q^|Mrk|ZV0{EX;__8N!@T=;5!++(F6eBa=kPyfOn*F{1UcE? zT^s@vGbw`*MV)3dX0y3^Oub9;Gw0BC=g4o&WL2V=G=3tm+r$?;}~x?ocw7{e54qYe+o zL!IXu#;I9>Au~IT;J2ap*%ugtY>Y@SAY#yNHzdy2Q3?`EGYgtvPSo4MfH@uYXwYgQ zXkAY%q}O&urjX+yIaE>M^tn0`Gq{*sBDr-i`R5zTfPY1x%(uK=A7tXZ7^Vew&&}l(i)1>=cg$Z57K7cfVIakyi|2Z}89|~n%C+YM!c2M&yoib8 z>#Rck*{)vSb0Et72K@u>2M!0xK0mzwd6qlygX9Nlw$BZH$rZ!^}FEZmBkE?*+F&iN~zJ9e8u zM?Wy!pKR@x`Uox+T!~~PxRLtMoVy)eM~t#>=8RZ2X>42r*GvK_YJNsyj1oqw9H1)D z^eaK_)usv46<({rpAJV<4SBT$Q|d>RjR61~eHqJ6f3niIO;qhx(Xw0X^+53Y9W3&W zsg#@ljJgQ_7-~e{^PIizXks>@1nFI{a%nD4uGf--xyyBP9j6lEuHwHtd#@G!* zeOg$o#UK^nF(eH+Lok{+5TR#zYP&8MxaQQ_OCw9CsnaT0Hoa$AGR>f68|0V}YXaV8 zY&{68-ynZrINo@ddZNLQmUv#8WL9eEQfW%vvxC$8wb$7%D-3*bTyuP$+Tnq-*Rb{5 zicqX&qNrQ~vjFaF)1@E$TJeI}FwF-DNom5xF{)gruw;WD2Tb6U#^?obTO3u^wRk1k zT6q~rWkuROU*|z!@L)QY#Av+av0V{eLq+H(TQEv=V1hc=VXa}=cJ2(-VkgCE`l>Lw zseq`6RafAt%8QBdhj#a!AfeJOPBKqnrYTBoTkkJEmH9LBaI z6kIQ8)qHEjFr@AC8$fXEZD#kWecHiH0Xjs|I~^m%RByI7)i+K&+~<dABU+J|z`|P3{3XaT;{)0Yjn} zdLT5%*HqKJ@kEOdKKh(jK-k$pR0XJGaPHxF>Ae;=hRuF$lzdVPaKyM^Ng-9Ov2gCTi%c(>;1`tqR^xG-Y8{VuCwiZ-nH z`59#||3Y;B9a#OBeJX$Jv;GLL4XR-72>Xa1UTKnsPINGEI5;6N7IWjkqBzb0-hui) z0UCzzG*d`HKW)%y%(EfM4;yLw!;6<*Fg$)L1bHyfH&-ywKW=8O48WjoZ(+fH z#NckrX1Xg5=z5wB=y7;x^uW^{Y{`O2kC1dPI$x85{Ya7xw7S|e+3*ZZ@s#hWgX?zd3*H@%X0{w`z9BbI}z-h7|!#gds1Iv_YmvV7wC~t z{^wpl+fR+~AKQx@9x*|iS6AvpPZ|E3qs7dEAAbJaBgS1>&NoJ2Ue`NSk7?nv*G^hb zLH=IgJUn10tw>fOr6K-_FeD~YXN$d@rn<%I;`zCvU{#=F>queHCf677m1S+YXtF@} z`ACcNM?{L3QKmLvRp1B#v8Hr_9QlnC5$tZM%;512=d!}{t;MC#a{;l}!Ccis+EU^Y z(gcc}Es7C^4AAd_Unn!yeKIsNGA!8nyUzgtU@35<5y9gdnX{11FD)j`Zw3GWT*Psv z8tPgoo99(v`v|7#Q5NUkAx&9O!!D7FnWp83Me3wsGaUvvch>Wb}E6BX;fcZIFahF$P4^Y{%Jj>$8pIow8fz6=eRJWEi-*Q)K~;_XS<)yePoJV@YSj2>gw2GPApDgRai_K%G&Jk&;6G~r~ssUY*TpT z(cAm=V%}Q?O!Wznq!%J84YLXI>Ia{PyB%p1j9AmV4hhv7*2w}y)NpKHt5He5n%!)3 zvbF|0ywqe_o*c{Vo>=QjMxk~F`V?$KlFmvK?=_aRjGqO!IufN84*#6h*^G{wJHTbD zg1F@dr#*EeT@t2AT6IwjR$R4nSf@lLFNQ@tpwAq*xwq06*wf??5IX$-D( z$PaWfxyHIho5UsU;XgkKrY$lj+-7X82cu-|ZyD6^jfij#ptkp`5nz2yLo)E5t6a_dsIl^eiImZu{@(bn}eGW#xoL`eW$abhhmkByMH667^?r%ZIw{ zq74}NKXMoQlXWi;+JSni|EpfC#Tn^-8kz;_b5OHnH0Dhc(n6m zQDgJZ?J4eQyqwS*m!VX!Q`f5=;4gM0pe6$cnt0~XsMA9z)ALKqvzRLKHVogX?#}6% zs2?uTwWsRoX-=%cg%<4SSn&5*TPOY`fAseCTPo6~hQ90r0D8lb`#5V*ngS8?h#1@u zC38kMUh>fj=n(D!cl2ouNLBq@2nXZhU4_Sji|`ZGYWPz78@4^^Qphc$`&Vq)YV>bU zq?z{yg8dY$M&YtJU`=@7f-wXHg3e1!%D7ZuRld;lXTsOCNL!{@mNM4tTt%R&2;_XQ zb|n2qD!?U5kj1i8^QoHpfJ$;#Ws0^8ce-Gh8`pv*#odwN*Ssw#Ef~cvBn*T4j1@KBL_!d?jX$} zXnqHnGgMCS!MiY2;}dp?NAOUnjE@*zWgp${F`xf{EgfWakYi9bj+kABqh9^j)HW^t z7XKV?0d_5j*-WrC9_jAR=O^fHx2ACkrhL(!2!tAKU|?7w2u+|SXe7TdlY|GfSNP~R zJrXX6elI{fk(-vF?ZU-au9Z|Ll!800g~NwUzZtMG?rX1koZB39|ggCxyoPK2!z79~Vqn-fO!m&5bu|&TkFyBufL{mfxzw zGrJtFwp?``zKwI6d{Imhro{1FmDZg`#I2#~3g}}qNM13JjaX5*#35sHfbBCQ5r28Z zd(Oj}gggI|lKDj4j=XYh0o0HKhQS<3cs@EPE9*AJG^*kEY#92eK9tbJ zMxbFT-aHp;O^e(4{W6naTTZ>FAkn{20XZ)|+^uB4+mIdb!Y3AhnhxnQb?@E$1v&N0 zg}629hF+Z)HR^*cJI?|d0Kb=$QH|$Jx0-|wCB^dkTByn|_C@V1zxnULm*zO-ZmIDW|s~S&VaoCCGUo%jprD7TrTQh@jMXoHU z*I9XR{k9MM-GE&+EiQD7WK;@MSdnSMaS$lc0#koYWLOx*Dyv7bKCL?ua|vrj=PZr# zPCCxYDknc0s_^Tmx8Bu4pn`tF%> zq$E3?+C?efBdqaOqjyHD1X&RS`0_~RQs+M%unM(N3wSu7L^*s2cSwWi$;~mz3kh<; z)$2a1@N?Q8K8hOp*f6h%YJ%3W)_??xX-1M?@;Di;=n6i6RTo>mZx^51X(YjOw_h=ZXX3s zWRq~FX@67UZMX5+55RxSy>|a1d8Z+L6>;VyX9UZ+IFsUV$FVy8;qfEbuInRCEBO8p zLs(z^Jl`Gx|QPC%5{- z$jE@>9!umo3rh%Ct+_B5SP?%DS8VW7s2PwSm_=(W^90dNWvg0BGqTJPK>Km`GJZXb z+RTEC^%~&UdIUAsshKt)H4;kB?1%fiVR^PX^5I&FBB{*x<;x{2!wo0Hj+Axc54$}S z@B7ChWS|8{bpdR+Z{EH!~WH4DC&(6r|UCUYRj%R!k>eF53W~S)EiOI)mE?_I z41}|JHEdR8kN%N@`$8v%pW>A@)1w39N0&dyL+!*1N)PhW`WqHU7kk_*%UO54KebB@ zlCq1n|6tzF51dJ?n;jIa#H(O`VIVOl|3=V9*9S5^F`Aqs{O)1{(`S*Ot{y6WY1L3m zNV1T6vg&Mhj%sp*W2_~t3#6;7Quca$0-S4mH=`mR)=8!yH@Dmr7v*9zvnX%3yh5`D zBDzB>R0L}W)ZYB+TIJb!5m$!7RQ7lkJ_8IP4N(ZP!hzGV!qJty0>H2BpWX zGT#zSPsDtkVy`{NM+ZuAMj1WY(A#AJ#&rWkS4*9&b6c8|jj z4zMhT+tFwf_OK{3!J&)*{Zt__&FxgQVNK5I-~vTs>gTj54aSw7ekGI?`qDE-TS_Zc z@a%@v%R(u}jqbkWn(8`rnns$E61Od+E0~K$FsBf&b3`vtS|~uC>Zyt0x4wmzgqI@h zMOUKx&x&3js4D*$Zifw-KR(uHdIMCGir%P;VvVasH z0?m;hP!b?;7aZ6{iM|4?>Ll7e<=`USMyGS!fP)FcjB~gfN9#XI_}P0uTz~6fD_05SLFJbkZuX6MgVC#pH-e@az`ouc zg!9DX@6|%(?w=&!=nXR>qN1?F>4pA|l2N84#x*4CsWcgZx0ykRi+xbxIP1vaMg z6iK@GVD8~ZdiDouXSJRm2CtA_$XAn1DdWMLzm~KZEkz%Lx@J%nv!)oJe?bWJuHqxC zuSr0iwwgK;#w&!nWh0z#G?HU1^CsAK98fF4hg>I@9({CPFgn8>G@-S!VKEEo6)klQ zFQh)LtQ4xQE^Y3&9FSBKKzl(WlFm+L7*0noM&u^ykm0+(k+KA`O5xs4w(d9Bc^8H9 zmOhhmiVFINVmq{!-;E*7as9wclhj+Y9?{WZMml0C@zos4Z54q>D!xXBC0wi`Bk|~$ z9#2yyDamMwj7_D1C?;)Flas5uLcVlp!3S)+_6Gr+`25#Uvk;c5*1f!&@?&N^G{>py_o1ukA-7KN>vMcy_VR!#dn{pX$$51oN%mNN)Ny^X&Wc9#73` zM4dYOKETBcyMlT|eR0YvZEHe$l4=EPhQyfSJ4RshBG-&X@7DPIQBKk4G@hY(PEC3T zL=WmVRQ!w!4|shjX*>oQ4D{-bcshPF%IX)lg)lDH$xu90LFQOIXzM~GsHKyGF&fhBJN$fDm4B!hILlUm0bwS><6b;>7y;Gd{ymr9z(;gwnv~+qj?+IhXQuH;AhOGr4(M6b z$>(tz+l}xd|7cB6`=St{nHZ!aa{_ut?yD=7j;*0T0soNTw9!=$a3@T`pe!j8maw1y z=n;#Dxd91>D8drFhrqV4fkaMvh8k;4`H0;Yy2&y;kZoePOb)oPT7tket{?UyrUp!~ zL6R>NOq0aE`bUDj(_zd$1Xi@N~{RpXsx`gB1r3V7nu;SZr4p*VBwr2RIeYOmt zMCNd1p?ya3TJE3n%2`DQF3GW7;CG&hE9aER+VA{69Sb5aHFcT=MoIJW^UW7sE5A}} z=_j3%AeDic9h36b1a$1R`0%4a{_%}Gom3|&G1G=KiJtpY&evTqIxevRhnaW|pxu2DIaP|9 zh|661^pg7uY3*&ov&)T-5&kliii!i>t7OH(^5r;1i{zo)>wCSYDEprXTe#ZTuhlZs+~)+v=Y> zy(^^uE?fVOn*WOf?|+j@e^jj*O6zi~LJ03j=&#>d2m*N2Z{%Z9W>!SMOMdki0QFPU z-&N~@oIN&1Qs+l7AI2Y+a9eES60UMxEksUXp!e|Z;K$C-kSa0?um~TIj!)xsd+)IO zmCi~2c718p`Rx(H6+_P%qnBg>t~s1o*M$s;G|*uP*1Qk4dl%H!rVnsE93S^c3D6}_ zwEvgnYEp(!+>2PAs&c7Tcl%_tg88cbKHCGwtX}d_)95IK@^irH8a=)YirgDoV6_}c z3B)K_lUb$Se63d5vb_ekTYYi`n~zK5qFE)jZoqaU!Rd(9^bqH)-F|L9np{afSuKHo zDy|}dV8X&YMnPG}mN19<;V)jLTg_1}oJ-l|8qTa$&wxdBLxdkR`IA6vl#aN0ONPm~ zb+!6KE+x$gt2APZ5W+ywghS4T3Sq zLOYC(VjC@Fn4U7eX84feZjqu?W;Vvorz=32i+*q(3oB8s-YlZ<9Y291syUhx0Vz~(2FHck4w2@9bY*=k167we-=vro|IgjYMuS-c2G!| zEZ*KQ9xM?jqQ@2TK$9YX^bpIi8z#!p)eb5q8c!=+^z#VRA<*q9_lxd{GW7|C7Dyob z<&jhYx5?VX`*+i?y}e-rhAO6QhUGj7+q{1CRvuzluh^ZL$bN>Ule4c2PA-Md=}`I$ z;Y6C?p4w;*qj`SoY>SNs^DB;ZMk@|K5u-=KL zqi7cQtzg^NdpEmjDj0wXR6#pz@6g~dwVZ)|iEG;?EwIRT@t|N}&r-RV`%6dTqQTG3 z0;>>+=Y)0kP4$@h2VU7B8xTyrF8je0eRW5}nL28K&BQ?v5C`>dtAd4srq~HxN)5~{o{rG`Uh+u!+`a27Nb9oR-q(e&cTSoJJoS>)l__v}A!Blx4 zL1@6_fI$9F-EgTlsSu1l@z@s%c|p{Y7x*^Ly~K(q;#NT>K1cs!qdXPiygP-u09yE0 z*grI4=5_Ar4`1Wz-hVT={?#zT`}cM5KWz=dHeY~(KRqM=h_sc_cQCU5Z?c(!g$3f5 z3b_Mwf}f)m^-Zp; zDD$|spvmi3X1Q=Ke~YHOCQe|oK^;(I-v{cMnMBgI)rJNW=-W0YA;@v;+fjk7X42%} zk}{+hJE{-7eyENdI&K|q)eBqf(e5Ac2F7n_!Q)&W3*wC7$P_3Oai~;c(;+pQmIO~s_oz#UxVS`C_qsd6@mR$CGz;C zc$jAvB0(0V-Y}AGqx{hiZh}9_C+xfjQiVFl{x>_<2fNQkM}QwW18N9GD;8z3g3Ac0{G7i+$A`?xH{^ z(%lU13@-9y7IFb`V+}_=!RG%bL$oIPD|W&rVizQL++?+^sOQ;C;VG}!3v z5OQxBFtpIfLPPab8;cXN_E_6++^YJH_?37tdz%k1wL>Kbuv~&SA(W$lW7~x@9LDTA zqV*2u0j6Bod1Waui$wXv@4&hUV$9*CVk-EZOR0|XPrhc@J&3~B)ShZ5Iv`bJt!v{f9vBi1?5J2iGOZ^osX? zxz7~V3x*)HFIEGzHTi7Vz6Xl)(Yc+Y7I+YKWKOLnPL;_cEdiVPk2Kv&yO)0v>#}tX;U1+6Bc8)G zP|~G{Z+8+)=dMO_MBmM}7aE=TzEpqiakb-1dP5jxbXnOd5-FnfNSqp=rN z`WOH5+$l_uYHEtp3yrW#XdSj&DJ}6@)uBoQDwmBt)p%xpRuZAuDSdw@z>t3!gab&@y zd1UMq9bSr=gsty|;pXzCG45_DS(G;BX>C_4OtgqrNQ?_nhAOAW>)eX@%*dbwL!oqJ z>d=*)G0r;6TNCSQD%KE$2e7`FK0WMrlfTAoShjAaf`s_v{R4?Y+RKH6J%2{Rtf-6h zUa}~`9J0hDH+)P5!$-D+L724oD0Z>BpC^J_%loHjf?ZWUGFy3})g*H{t^ig|4m;>(Dw#$7|%73A^AI@40lQcUGe)XVH5L(%G9(OQ%^F18?{s%XLob~*4??~p>c?%O?w>?^^!M4z1 zXSfS8e6B)13@I`hiE*z=TDVTh$$}_w_xP3pz$8NWLBX53g)}qt^vWoqimQy2mw9O zXZd_LrK-a9!ZZ&1Vf22)G&XnY@-}~G=|KXs1CXt3QIa;LXKd;b5fdUr_{0NK#9GPs zAw^BTglOxi`|HTBCrV#SY}iG7D7ApPsBdi@{p|Q`_qhdyl#C~hV2oV)=Tw4?88>@k zC_YV@Qy=!aGf7@?2Pw4@&N_U=gOR=)LV3GCYRLY`0UQX&4ZZ`6QF6@g9{` zh?$i*Q>J9;Y3&-_Z|~K&o1_d*V#_cH8RPkzt5}AO#IN$RXk3Z5(&G{=I{12f<1N!e zD2}Tu5XNC3SIQmu)*76;@{bq$r^w|h`O7|+jKZ$%!juOVKrkj!wi&LBpzj<8cDDOa5=X|%mewUPW&vi^W7%~~R8qQ1MGmvQF z_W?D93t4bnm1t%V>%QStY*hC^h-K-}_udKFk=DHqM>0gQTEh^6%?a)-4uQC-DKx8R zvr;{@7%a$>K%_HHj{ON5)tdfTb&XR)hNQ_N9F(%%u7M`O+ZRDoqJ*R)R>vU@fiT0N zT$ONb$D&^)y4g0Ooz#$lmkpsn++pojYE%-IY$_n5q=_Nk-+U&aY$O6QhcURjj?nF_ zI~(*YDBL;~YQ={QNLXKesDUedpSi+CVn9v6wgmvk`$Z2LI4@?vogSGaO4}t?1Cm_$1JskM zP3{$|<`p}=3fOsI7tL<%<{y*NZm~?9n({}x1W6tz1Cz|LA-niV*4sf!gu7_1sm{1Y zFUq2e_gDrMn!*9 z=*z|_wIQZ43BQ=q=2^}rTZtrIF-M(C?chIJKVh#RZ+k1?(DSu4(ybKNGzjB zlEIx4#gPNLeseS_X-||cA!7Yg$z0lAtO@e)U=}Y%dSL4k%}#sLX59?_Q0+^^Vi!C{ z+hOUbB$P_S7?ifZwh|rpO3i8poBTLl9**}pa22uN#vE_0*WBMC!%;h=OQs~XeVV;B zF?EizNtm#(0i2Yia>z$Kjqy5X>O#ej-(^UU@eNo)`orjaUyB%}FF@i6d8Lm@GkhFR zb(#gW>}e|)y7e*Yx;#6`q1)WH#XCs5JN`-~%B?&7%F@>U3RjSg;CSRF%s$5p@@QU> zwurbMdvAVtx^ZPFVbk*!ZbMdQ^5{IMyz}{_(-v1~mCtpp9P}5LumAAQq55Q;h=zIe z0uI3*;Dlf(F#g1x42wIfv|ow=sf&T^1Ou-$%>eg0ZP?tSfcG zDtnyGgVMKlHXQ;0>cyz%9+QXYg>>>=$t6-=aDvh?pD%jwUEz(ffOa+pfLS}UAOSv56k(`?Mh9((d;zRM|dh^?Tv5Y_ayu z0j&e1cKfEyLU!c~sCLDAk;+es2;cq-WqSv*SFuv815G5XVR`m&KUmBbD==<1kb)c8eiXzlTNwSl7fjY=mGIav?GnD$=8 z>jmgMeU_IqK&IQs{#5h_^9CgEKnsZ(4}^~FUJ920+8qVFxZcd4=+|a236J=SzX8It zg6dfwUxQ!00x#LTZX?JrelczO-POP2L~~d$*C=1a{S?yOWO9OoJ)`?5^X85exu5^$ ziU^2xwO9H|4-TtK@%?VJ)iqX@D-fBp{kx8V`ZGJ?AR+LR9AHbx;kJYW55LQb%nImg zAa@{boHYRG4O&;}x9;$;UGs%GDRx#3fotqzmbpsq+*}=g?8~_sS|Uvdd0L8N?J;c5 zg(I8`V2aN3akk#B3dBg>D_eybj-yvMZN6^cpsQo7Tss%|NAf_m==?c9ox={0EBZui zg?twzx26@C+w7=?#>7@#aTK>aZ)po?9r5q<7I5|bI8_ti9&A(4sJ5A?KUkHM(^Eqd4`KR$cmT0ps93<{=-Xw%FF~u1{*Uu~T zR)N}5G^2V|_*Y>wBFk1x2%sSIO8w?o64_%`xfSJtT)8>o@P(}q2a(c6i~i2k0%`AI zodX^jYM31{ugKHyCxd+bI?SQJF~b3cbb89{I8)+HejeKeK4bkd!w$-tY|x`L1+hx< z@NY8V!T3Iti>8T9<}Qnmf%Em-y{ehIrMU3d2BLbPg+ztbZS!e$2Yy2>5vlbFa!31s z>>tdB&@7-);-u*8mi-?6iEqjkpgc^I3{zFgXE8uyeVQu`{h9ti!yP*Gkp9yNfHEmr zGfgluog0>eL#uIB!DC;Kv_TwAoj7CzwtU4Ny^6p+^rY^tenhD#dv#^4+~!8heYU5eHUQUv&AX4)E&Ah)^Zl`SB`Sv=LI-a%!WDw{*U{S>N_S8B6Yu98~V^1hd=*4nj4E0)a&%$uBbZ_fUYH#P2PmIEBD1T9yMhg}f2 zOymJDlUnwV4?1qiK#!6iEnw1j=(txJW-CH+=rmUc##b`F;H zLCai|g(v8(ec_db-`>f-Fh$GNqNULX{A`kn_fU@#iXz>E2?FedE%1rUXzeMb1GKdD zj-JVGy3udpg?kJ`hEW|Ofzz>l_Rrkh)13Od#^(8BAu@wUqSiZ+I>Gg=_<~*06&mE@ zzixzXZOLqi3&#+ab3Id&CKjA_Y#f_fv_4-oxIVCL;Ttt8ywe!B{8Wsh@iT|r8Jxhz z3rZZFzxf3396qza8%m@5qI7mBU9m(IVy36 z2{$T9KBL+=|5#@AmX3jShiRov)&9L;O;w%NRkB#>@LHn`I#0zsuFRoSuBg91*T&Z& zqmp;@qJ`DXMdyi`bX{9Vl}B(;vOFzhIwNzGO{tWfbTToATrtBaOTq{r3paLvr8q)0 zk+fQpz@|#!FiZBkMxCOTuPkg8YCh7Ge$@7qgB%-T*iBCc~P1Hx(?hck^XbcJev|fM1-h29Vv;`i1QB zT#+p1)97ecLT;zzlWX#EO@q;qDkcsO+8dGh5OlEys$B6>AVtjd(SGXRiseXO2;J(B zx1W!!j8rjFqVz5Tucoh?UU*Plir5*O&{E=ZbND?Ak4Z}T+6A)3lH(>9cmlkLOkmvH zjT3etq-X1AWboQ~owM(Bu#j-!V)>d>x4XRa+zsVI1=87 zt4dErum&(F4Q#1pO0c4odZC{Tl*>{jxE|U0(K)n;rtTqnioCq5?nN0OYW88M3F{B2 zo&6&odTln!h54&TM*p`?m;YlD(LXz%SxQ=8&{s5X7;q}8T0!3Pl10(n!vF*dipt!E zSbj`sQ!(-4`Enx%l8P!5##emT?Fi}aB3yw_|K%ywiw4H~E}pXaBdWmfpv-srj~yOc zk6v3xUQ@##@6)(G-%KbNM(kL~ z4A<`^$YVpd(bO8bd!?uJOVr<;ajDQkXW%oopcA&7~F8^!Dq&q^^r91Q$!@IPzl^ZgDo_$RgFF$G_Vwh%PIQ zjM}w#N)F4QnmdC1#32aLWPT9^H#!jpWt0@vgfsj7rd^IVguRB|5dZaq8@Xknuno}U zBS0rc@pvNvdulv_qCY%4=jI%(AZ~R_-vYfgq#!*lSVc|(sqVxh&fL&}ydu9SqXcrl zU5CXlj!-$$!Y9*U0O&5fk)ZhU8^}<-)&^Qw(S9`MBOp(z#0~m?1iE9Vy=djTt zcyW2*Iv9O%+46qywz0-)^tJl2gPFKUwx4EDv2WzKGLr1}e2A$FPSPbjOYEZ_XRX7VIF50xP3xB?`5Do=`B&R zb+5_gr+3NVMW7qC+&|!ReG$*gS#YLrQHzE-ak;enEh1;2HH$$!CtGY-yT>DKO8Z=h z+Quuc#D0SszlQ>P@A5dLr12QQbnAk54@uF=`fKNPhk-ZQA$%vf`64?HWDO9jgSeB% z6*3PuIS9_jCp{XON7RSNv0j^{74145cUtXSV16%JhC;HzNJS^QciA;+lN5LF@Gr+o zAYut&Y>@1O8f4%5lO>XpnlhgLHC(&;w_y0c4%hxqG&UP6TT3HHqrcMFYNh+>;Jj4C z`RC->okaA>B)EEfMHdT^5#$SCgw9E7%M^Q~EsJr7dqA({LLE5xg4AIjFP|^B+FxHj zo`7vb^dhw*zv<~ZS{t&lTuD`S5O791luB5X$+t9_Mc(Us&_d*Q&S?i~8`XOfHp?%_ zR}bD=qJs=@046k-Y!qN9ki5C>D}q%e#N67H9AVO^<7`d2cG3u!TZhdxh@cA=8nH*s)54S zd!g*b$(B-5CPF(l4_kGve@V;j&AaH3&cwkH2ROxm zZ(#VV)`Z8Ka_&61tSWe(PDR*}U-NM=X)%}3P}<(qGwn+;2vFh{59#Xc@h+hIKa^bu zK{sMrPoJPI3QiZY2^T|fa8OCE41z%=}Vo9(h+?rnH zWbwfpjCiP$=LMR{4vt#JQ>ZBXL83N?1( zjZ>d!betuT^ji@;kZl@7GF~BimtpAhPm8sJB?eN&*T%E`t;L%4?>GN{oPQAg6JC_1 zsP*N}N8*~VF1#R6HRluLt*F~1s0^3Ekg{ZoZRpT_;kG2bFi$gcF81vn0nL%5L&N=J zOud(7H%PJ+@xW)B&wvqP!v3JG-5HmYM7!n1DWqD`^ssXYwq?NCXz2RV+gI^4) zr)kB&Y3{HZp}1l(us16z)MnSV43@aSe=4j2;`ziN-+_9q>e90B11{*vVav0fefpu=)4H$k&Xekr>LSn4RjS5`=b=EsJc3_&%SKk-eDD^iA`w~ zBXc- zf0P&DW$`h8@R(`;ZEpQn!PS2#FaBavCQ`Anw=lAoce1wD)3-GGrvyvkAIGjWTX%@(^Jp9BZr^gg(V}0#E!|c;qDg_VtdzyZtDaoL0+E< zHo@a@!(*E3acXLMd)o)VHRv9Wc!B?^1jH3bm#qgvVCHp#`RV`)QlwJBdn!M%eD7G{ zNRf!dIF&1QI6Q+N@-}9TChhv9O>vG1O&pUJ2K~)=sW9?6S#j53_6%(EFe@?EoKnKo zeWbBrTVRHTG@gCRYxFK{<~2wjhPGQp{MdNxx27zowcX-k+PSdX?3Kxy;8Arxk%BN8 z;)R?*rSorzqy{(dChjpIh!kNCormhStIg~t??&H;u91j^fhg7<3LrG+A0)Y(VHZms z5h+mX35)cGPNcgRPhMuf;3hUJ54NcG)oKl+wKBR_Vuyy>TyIBoq8XZMu z>$i#UyVAbQ!7R<%Gy(^0qQPrCN(`1#b)3PIpE8drC!J4@^$L!@X26CfBfse+hU*a# z!5VhrW4EVSj}k+j zR6N4PkZ}u3HcJ0C;6z_3ZV_n`*9M=Ky%E^JrJMK%g<0ZfegEXwn~MD3PR?Ho)Bk`G z`8S8jUkcJB`LDD^dL%A03z6ZMvY0jH-0_zlJSP?cQ@#o$EwarP@nSxOp#sVIyqp8! z-XPo^kuVo|xf$nQJ^PZ2yEGyF63G+m`V(aa-!!=jVT+=l%0NASWdQ_;ud^8X>6ie?mr# z!C44^ZefYS3B^FnBSE_~Y8D@zvu+FeJQyAL2NMPaR-02dVant83wweyclQ<#K&D=G zG=uuLCO}Kj6?|W~E%ba%mL#{TTY&ZoP0}@u<;Da_cm8{R%nMrZgZ#a3xpR$S1WfL0 zC9fxjB^wD?MHLP1Kax*)*|qk0^ao=cr9<84h^9^j_vfkVry|sd^es9VwC_abO7~|= zO%4*nl)8>RrAr@I+4mmx95yD- zCSOe+X9!(T7xNbXBnnBU@<3%~Ek-+~1PhX?3{{hr#t@tQ1zgytg+l^a28eT_4CzAg z3$@{gLQ#RZH6ZjraZa%<@K0Y-(g(T&vt&A?XwBfUDqIl)aa?*NjSGSA1~JKuR1bvk z?yDJcB`{B9Br^H5UO2e+$Y$IwG_k2Z{NVeU2!(;kW;%!w_0TG#TV2YQcv0WQ;_9;f4auI6b!zcJ#N zut$JaKFED;h{K{!q*QZG-)GaGzv*-+@)5aaHv3+NWuLY7A8i)jJDK-DFcX!t+&7vE zz5!Cb(86W>6a_Q&0^xYqevRxSeMj!@p?l!zagCd2n3b32$>U|0P~`)cvYew%5&cC# zp4bTE5hK(GQpcVKvey_Ufp+Y-#C+R2js%OQT6FVWEx@zL_#T2Q+Q^by`~6SuX+UOl z#lhDh68X2Q0}O@Aym2kXOO*O<^R}T0nfRMvE-Xml)l1pKP5;M< zxliN0j84_1*Hsz^3nL=KOhwXHitACPjgQHd*TbsAug;?>fEE-Vs9g-t((hOOh#Q`1 z8D1ObM~9Srp2#|T)zuEaqtrEHY;KpE&FOKs538@9?C^X8_!jnL0Yctc5qf6v`dE<) zR!Li*X;@_JesfuOO;5tMxUIz)@HPBO!^zfP%khb@s_=<64_dRO$!S`j{ z$bsGo+4v;$#Ra`a{P3jrO#x&*8g}E+e%M3yJ6t^B))KoTJ84N*MD$W$Qk*ZthdY&0 z*iKNaQ)50G4(gNVN)jt8O4m6ZmH{E5U5;uomnjyrtil;78*=_$CVo!>ELhDeRR-W= zkP|PB)|PxcamawaNypKwvy9&S=*$ppuBoQI%@26G{Cz$=jZx7rg9g)dwyhAd9jua= zIT@P9&v14tC`GWs)FE3H&j{Tigq&{h+c@C}xrJJHDO%B(@)i}Gq!a9nKG!6mj(KIe z61XnEID3(as2J?6qJVi+?(m|evIOiJzj~LbgoSYmuf}UC1ji3BSC2bj>YSP4!bPRl z1z+bkfOiNReIMrUBKRdwB5X|NC z>4+%z;{(}BJ8d%Rv`!@F+&ENVEfw}|dBF!2Q@uy2KkWIGoc0Po||8FzH#&$a$@v? z#>41Ne||GvPm$|ybtGRNL~c3OSG4JRdj~O|zwH=m+e+!Hd-B$y7_U>wM z9%pCC>+Iy9 zN^WU^5k?q0pl!%|(3l_0y^Z5j%W5U^&0XCdGGlDh+zz2E&j?V;jw@+yVt3S7K!ybL zcnMiXlITz<4^C&t$-Uc5wZJ%~{fH*6GrB5^l%r?0(e>~!Gr8fnT!ho~xD|A2bX(Cw z9c4$DCIw+lSEu7#vZ?!#J#{IKQoWtPcxszmY;~^Dp|C)Hi>j-~i8liz>HbMFAFuqFVrU^Fcv*Au>_l!l` zrUv;4ZTk;MmLyiy@9bqeSw5C(&o+|jx18H_?;2c87{sWpkwK#~=jt;+<*Il1)Kt5C zM-9XrRMBlcskYckC7tWU^B%M>jg$FC)70x6O4(yEHDz=X3HyMslHUcGY~J-%i)H0A zRY*W%(o8Eoiwqge(_5ygMNSCV+M-Id4clrZz4K8i`U%~0E4b4R5p6D4e9`YB&M;nP zRRlg0b$fggJ$df|z1(Jms0;B-Hl(PH$Skl68On8NUNsDT%FjJ2z1c6c{9xtRMe;?! z`UT$4kHJGt7wI@R6ooqbuWwbDS-MOufj{(sqi4q`DhixQ02jQ0wKb(Q*H1!>Kr!G4 z4F~9w?8lWx^pPt~n`Uby_2xFsxJYc9FWE-Qwgp6- z7CwSX@KFzuQ=t=Q)B^w=Ap;#H3o-jrN3oilMXj(#r>wyh%c3r+`1^0}-Ku2l(Pp0! zq#3ZU>rhy2g!75rf}^uxOWt-6t?I@R@txyI^kwu#k2K@bJ&mq}39$$nT#Tls<%D*t6Cj$ek&53W9&H-gu~%dI5xEA~ zNO#{8A$UQY=bXLJd7ihhD{GG5C?PCoZbx>b>4LxUJMxl5ZccCihmKY4v$@2iiiTyE zO4y#ozzsCRc7|bB^N6QSj9C{kD!q;i`fvJGq&cJAbp@>LD@C+zceR!>{g!TJuoQC0 zBkEv}W`dUQ;x*5LjwzCYQkm(ML>G;W_h2N*b9GKkoiB%xuZL^`Mq%t@ve z@oOEL2;x=|mHjiHwX*|*NT`WqehAUnbw=lO{)Yq{p>D{lz2i=0foJiy+4`tqgEUk! z;K{?e$qv%36GD}RwsUwQ`Nu&j6(zA-4>!;*8bQJB!+z?-TsRFm2k??Ky6Q5~XaqbK zCGhK-E4>KkKLs3ypP1^A=Qh-WiP<}!t$ zs%^(G)h<|rr1_?q#0}v<$-rO`?gdIvLrzGrdkyW76b2J$-si>dWE`UFEZ*`tHGauC zbExQwlAIWiQ7uMT-eyGiFSmtAScES2L+C*0>08(ssZ51^}%SSPoYkxy=zXa)H% z8H=0^X97{Be7{3RAM2v7j!lNFN_Y~N-gTgRWKGQ=CP`81@&c>TDT{!hyuodKvZ{6w-OL%;^dWKiEX*4q*C}Xmgw4a+8vkiA&ky;~B5cFbK z-|k9t&iP>DT4`gA(|8(MhgyW0(A4DdLIh~|-TO8Vnd$dI6-?_CqyO=Stz>jroIb{*I7=WO&chB{crmwRzkn=s=e%IfT=V=9nFAGD2psmA;%@}mkAXXWxIHoanJosKc)CPmY!5D%IaxN_-JT%V zNS)s~D_CkHp>eb1ZC+Mx?LvAzNRk?+(NWxCVbRNgupxsF;uQ5Y3v%gfTvOrZa85!= zLpDb2eZDJSC>}zs6ISWx8)5AWC7T%ptUAM)83on#YExDI0Ln7n=t?{lO`QS5xuRQe zO(w?mbS#Z%MHR3pNu&!1JZk+p5#xST>T{SO*$RK-2Y(d=Pl4d(g43?hbcI|tA~Z3B zj{O@Q7NIKIf0M2`Sj1Upr8N3665qrg*@XBNmA1Ersg{#{^(f82cf|0xizYFt6s;_6 zf;bm+<62Qw&pBx>S)&v=YVJLX$lDrbNIfcoC>Jm)j7E}3dnA6lktg=C1*-}KH-cKT zkD&iWr`YNj&Ltz~^QVHo>T}Ywk}DO#+1@vStGq<`={RWxvrQ0>;hOttQOi8x%jQPMdqpz24dfYG zPFh>e_EGk}`X^U?S{1JMf9f#$C3BnJzpAO6f2*ecnj-O^j@ZA^a{n45@zojW<3-90 zg77B;dx{H{v)Sn-8Iv;LhwIO+1*W_aZZZlOoFiVRj`JzhmgIZ_dXpQn3JQ#%x305! znDqG4(7Zn1e|+14YF9HQGpq^I$0%eP11^PJb~zyCt5ZLNHVL~00_skf`W!73AR?r3VO?&wc$Hc-YqQy$ z?vlJp%=)y)$0{iACE0*rPC1MxTAol9D>!k3IU=E{twfKU;4xB?`fA3DVgbQOp_2ov zvJ3IEli;QzmTK~{yaoR3;2(MPykVXI3SS$C{kOdo-oGz{h0QFDWbJL7&Ay6ZA_cQQ zhLo~;j;0EZ_Ii#+CT{=itf*LcA}u0+PETG=88hOKLc|>$0KzA8Zpg!%{ZteUpo;Mg z6cLaZH*~2(-);rS$S03Rm=;QLdEL0}|x3LM~WaTYG$cZ|(NBC@(yk zuuMJ|7^}v$f821Ge)YL*P5AxE=7iDH_@n`FAv(T-?#tVTf$KjgK>~oOhTi_xLJWW& z@J(_sHd4cA1I5?B0k`@06?TVbdJ2cyIZe|odh$D!Lc4`6%VlZrku5E4PD*X@cl-_7 zJxHqGyZ};91hlX(waYQI^v_1xU48mGNwu`T@2;VfJQE8wks^ShHIE|Cj{vkWH}zupIP99 zX_(XHqYN9?n;Z#SRsC&r1I*UY!Btv{)ih*;RLasm8qL$A%iHe@a@W)(5^S|v2jcA$ zXhc}@Den@ST*=#2^Dx^w75fTATc0{65je4p{m+*xXQ3-8IcCb$Fc-HHhu|~pm>bm^ z9k-6saN2c>T>~~>c-adpRW7_43fI)*`2Wd;5zKg9(d*{*=Ue&Oub+35S_P;g0ft*c!`hXv=d= z))k5p$97&vK3UhP3y{gEMLHbz*5Grq=j>-k%2!% zcLqckloO_;*!W$Nr`jhgke4@Ze?(qIS*(QkAFzsp8nN*9%6kg-B6z9}xgd4;8Xy>J zNirsjn~58w#_jMLXO-4rX@wr>?7mwlT^W7QefjP&*u2|MFV z-^9Hs8hC>8a3YpY8Aiuqx^k&vWxQ5qZP*7ar`Wq*@lN=r+RJhNvx~{DPr6s;T0#2I zMDjheOaahKA(#DDh=K5TVSL>jZr{^bRUS3h%=s)WAzWiWR0X774VDCPc%x3^|mc+ z^-SR@|M5l@2CazZ-djFCfsTR?$5{eSSj^$4rXQvvEOT{XNM^$0?nMchIpIINYn<=O zm7ALw%hByHY&6KBF+i~mHqW)^Uqn{{u1ilEjEPFNDLR>rDm*2qXyRPX9L3zat=g#4 zQ{(LU;!VA`UHX1mo;OcuwB%$SqQoW2UbST4R2!hyMq>VvgA) ze$4}2pVkCM5Pz3N0HS?Ke!vSB#+oo3=e#FWHj2Jx?Ex9OC0+{tg6sm;64euV#b)f_ zrYYQGt{kUt>`DOnt~V7D?C$s4W$^kmz*K*;<39Ge0$GS}wEga+x<%G~-^GhFOnJ&M ztP^YPFtRNBj9K7oeJe(M7gpl?1;by<>RL25TG&`hBY>v$=ERuh^7p~rq<6)^`&CC5 z48ypw`7gF&ABl?SxR^uCF1nUtX+uu>Q&%6@zy8U$+F-NF38dY>VE6o`x~?{XcJ)rt zZ0_WCwYRw9LYLV;p|{W+Ojdx-Hj#IIwh?8*Ve{Kr)aJ(-b5_ryVI_BZf98_z6YybM zlNb%Z{crzCwgy@MEiCm>D-D=sEW5Jf!nH-&EVL}8g&%OQzmWT{BSK`UafB0GksJoT zRUpLVieL?3@7iq;XZKj&AYCHM?8Ul%7a6E$CC=_wyW!k|r`_4!O%2e;1vKo2&;XqhBt?cy2FRAw7^ zUXKjRon&LVyyjeYC~iecei^N-SY2@FimG5#a+NDBTpju3p?U&$%7gy_LcgcQG+Pwh zLHx57o9Kf0Y@wD_FbY9yT^*MgfV-w)k&!Fpne@8XS?0_U6ee%k{*{h*86kXwjM$f! z%(_`l@&epVL&R3AJqD9>=8_{a?ZVzH^qTG(CA74HVnh>+WFQ(?eNO^-nk>Z zI!tH|tpH0)Bf9kk)-ab$ozW@o0@c&vq{WS&r|7u?Uudponb?J3pI4(r~4PXKJ z%amk^ml@zvEo4`~z@L0cU)g+QW3NNn#1a9^_E>Av<~qE)xwtbi{1a>6zy*lf;=iU$ z(`+8b`#vtsKHn_&1i(S4l4>H6F}GH)w|dJ0nECZ|K-szRA{S4o`MdNyg~(W(eoBL; zSRrSPJI7GYOYMznO9-!JhYWgdEPDByj`hSuJ_z~11(Sq-b#1OC;mYw)pfh|1p&ii2 zp2!w7bRNJEv5|2_ZcQj0R6USJY96FdV^p$4gS9U1gLsI-K|xR)I}&+SF-nC-%yM{1 z1JS)d7d(@;V)&k<9u+@TMFL+;aqkaa($f&w* zJ(!g5^z~wbp3xN5{L)8JLj7HW!2P}q2A(t^Bl8{GZmCNa$EAy|kMozoMWaK-0z;NQ z_TJm;O43oxW`OK|){bQ6FpCYZk86TJm;pXHjlO?=1|X}9J&pbN0lr(xEL+5uW~=a} zOK@jq&5;5yg=+?g=em z2D!N73p8qT;2L?6OPFAWZCrb-5r71Z3Hn(gVr*$haI;VV@50tCEgE(e#G?;)V_1En zu)fAr*81{>)JKrCt?aJx!FpX14k1oi^#PDOql?diIn!alcKxaJiyr)%Cw{GE)7Ogs z?`HS^P@4R$(oa<2pQXt^BMS4E(yfe)AF{ZB)%Y+MB9Qseb9K2C@3^agq}uB1I3V83 z-6`CULGSYOzK*S;VgsGS<(9|P>bRcHcD4?{xh`=K{=R%_Z$;}N6_u*0AsTnA_^Cj! ztKNG$zTEAZzg`|i603~|GrZ=FdIEb|+12eKGwfGuj~VerIGCxNj5?9#Sv%q^eAzr% z0?Jv496m9f6AHu*rc84YNM|XlX4CxH&zZeF`b>o1M7H~7(J}}B;wG4NPaeBf-MOI4 zfDDD3>Xln~pR#PKmGoh`d!l{GkmV4D0sXdHm3|@>ghsH$?dL0p=1om+Y}?(sAEk7m zn4!JxYo*54uOGpBYN}22ehpFN1=RcK4SU>q#V{2k+`PosLw}A_X3%M6ANO$uY=Q*Q_m1li)F>M zu{MRx1H40oQ)?F16~ygJw(rpP)EM?k7GwTctszH8(V9c|Mo0kPczjD+!zNMZ?0ntw z`hwFqR&Q;S`g8$bgOBM!>ElNVQ3csyvpk(g&?VRQ{XdMoV{oMl*DczyR@kxCv2EK< zI_TK8JDqg0Vyk1@w$rg~+cs`CzH`pI@7eFYRjEoPRqM~Q<})$om}88l=o?iI1+unR z;fsOe=&uMRAT}6#QIe1w#u91Hq$Zy=HyhiS+pJz-#+8~|$n`1cs5@dA{H`k-wJM6E z-En2FSG!uaECJ9ABMoZg9~b2}q_Kq*3!Czcer1#$R^|;6 zA1UwvS6yB{Y&G6H1`47A1H!(HKD28MHPuKA8snfbV9hd^<>8n=(n!2k^|w^PjhUC{ z!cyu~AIW{yns*lrzf&2;4OG2ASc_LwutOHb(xqGwlI&tT8(2`hU~N~tpe-M2EZM+6 zqju{I3|!cST1&i41C6gZ+2rdv`5F?>RTrzSZ>vkKys6C(*R5h(Ag-GjviXAJpPweu z0lBf1|1_as&E00O46(ebB3mp$Za8ZduLsEu(PoWSC{XacLH3OHq@a2o68>akz@#lj z>M7Mzo+sZjxfuF>>;${6(~!#DnkDvm(P;e)FOzK|B;t05a-3++uVICSHi0!UnOwnB z6SXX9gE;yN5)`*0whmX!L54NPj_hiw9T1so*#a?Q*7!5q-XT4|gpWRqeJ_KI&o*+k zms`roB;7O5qM?opE(mr}wYjnO3f;}m0$0LYsD<;p_;Gaf56YJUD?a`k^w47R2zkQMnERKMc`z0=AS-!`U{X6oc{S27C&-CRF@+nj zELSB4FwYA=e3vW1_qBSdCCT|ZQT5< zJNaMeYn^2hUjNMGUAnN24QCs?Qb1lWQXm?7wP2Qr_p`e*6vxb@*rL90Goq*&)q|IX z3RsVHdLo~?HAQx+wy(!?DlMD%rO-pp64eO_9_ct`Nf~$mS3AX;B15Jr)6KKKzG0g3 z3d0(5iZy$AO~>@vn(4DF6C;%`0v1;LL3P;p6U=tczCkOH$XC9BKP_A?@a@l*0))Dd zIrxJkr~>ofiroK)q~HIEzx`d&zQTEF?K;0_l=R2%SaPcw7%cWGrgKDC2O!BPqy}z7 z*;iVTyMQ7fIa=%D`j%3cm#6x-PelmM^A64N{;2MzV6n~$5VGJNm0A)K9%h>>Tjipl z4y#TmLxen4n*%+2Hzn^RGCb_m>f&Y*`AOSgW`^v(9wT<>t z5JK>L5&LPyO?BjbIPe|)RUWJnpa0tN`kHuV3nSZIR_0y&_c7Jl#bzQ%+?G1X&9;yC z1OMbZ*AMo~_pJ?Ek!vk9TL`4?u5stmA8zxMAhf#K_dtq{p0pEL^J# z8r{!O!Te|_H0&R38&H_E3fAiNK~E?*YdCX zrdkwS*FQ{Ut!j=#w17h(`y~v2m2bvUtJyjX_lp_cV6J{0>5bEp#I@61`O?&C6aCz* zw1$`Le)!Wbe942sXz0(qu_Rj1!v@ygqhMvDE48jp_3$?*r)%qzOZBdq1x>aX8L;SF zDk}zL)Ai}K_s79uUDxe<)R1>BuJ?n={wRzXCmz=rn#o!&b)0a?8fGoc71p-uj^thI zEAtJmnI)WaY`kMJXm$0c=Vs=ytx3{qiH%t1k>r56o)nwOK!hsL&>C(m?)0FOsp&ui zkC2Afw#(>T8&jJ6MjJlS>6a4cqe1=QFhudGB$c7slYxc&fvBX~@LXEsJB>XJuZ2pr zeH`~;HX=)>zP<4CaQO8C%Ji`16EpSE=2vjnP|lI!M3=r;F?AECiOQ#&E`Op_1BAoi z*Jd;fN7G4bf{>c`d0G{Y;p?P9^kKb2r7aLSp4IxrY`j4nneMq{+>*m<+H8IU+oXnQ z>~NWDw7>@F+u1A{Rw5@gu~HXpSS|Z+3AiS0r7N;0ocn#hNq}wRAq^=BjTl+ixUl1{ zx1}1%Rhm3D%~cS zGPE`&?2%}781NG^_5pAsxj*ItqcgD$ll_6EdRhD^Mo%p;F=BDC&qXrC$XG2L4!366fq`C<5oumhkt5#m$xTxm zS7LKm(ke@X<_RdhHMV=zx5)L9N*F|ka+mlmV{^1@AX(;J>`Ct%w#ZoNs3GHZsm8eh zcb?(3N&1M!wuHEbj#Bm@xk{l@WhdyHg?!A%IakRwB0+>pSPc+J0F$lYO?>x&P)n0- zldOm2XQ>i%L!&q}BUx|&H0mf_E<}(q4G6Z$k=Z@`i`y)!-a=Km-tLy68GbOiGo+x; z6vv7=srbi19U#e{H!%@Rsm6UbYIvR{&MtjTprZC08|Tnf+9V-@1&3b_HeXm|B`2#z zj*F0Lx(lRkkZzdvhFt)wHy=;3~SW|6|4_%z25!eoGyvsnxqf% zg6_$m3h{W8I!l96(}e$<5q`akzbMy|MM4;xCYKc(J+tz#?NbP_z%C{at@CsDP!hKj zDN0{Z`bIwsi63vInPtn^NMKJrR>AcE3^2+5 zaW4r(wVC?^4UbjjVLLowT8!I_C%P2fC=Cma-#Yvi6-tFScK~h7!X?#kG2M9}9Ehca zxST!NRONP5-RpI@qP;}x-iG$j=}ZknY#NGeB7*Oo<|wu zk0q>LJN;5FaSD26e6M&q1XAml|FOjRe{ek^SPi=MaUe~o@AK5K)JfE6RB{HdC1*z9tR zwtO*dT+r(JF)u(@u-;#t0DsNW;m0?H1N+aYqS;TU5(qfr;6ARhfo>zOha-AR?(jqI zMNw1n(?Cuuz#3`;2_s#;C@meB$~N^q6P|iHu|ct=Nf>WNYLY6$>y1>4aYX&7%AqA3 zXH%5W@VK(W)u{-Y^a2Mt5*NBsS+fyQeSK6Y92#^TMC}<(6y6k6xeCzTmafy&cWTHe zi^nOJulr8M!7`XWJIQjFe2s>QuVt%M1eZ58(!^lo7ZiRtMERnk#@UJd^^|`Rq9d>$ zvt0|T4F+mI@$Pz-S3TNit(uLJF~qTy#InjHCelUZYqo+2=HUMjAko>K=QVOXomY; zuyKayLAJp=3U{SDJ@_bXRXd&;xd!_H=!-ni}d7&Hq;+IenAy97pqu!FI9_Y)ex~?e6-K=+3 z%y{~X;(4h{G<7=&oKL@Q!ZBZoJVo`VA1=gCpfD=l7~e>#1HX6eOZ_?uts_^ceIpG) z6KQjn7xo5xscI%~51A^YY8F+@D4Ep7fcFQLXF+emu5&1BJ@|eC4>431KDnj$wa^W> zt3?4zY529PdM&DA#it}gXw9DHj5lJo>q`xE!p`S7`7OUSEmkhRTZk&o;5pqOr-&Y$38_gSf1o-K@9Y3+B09VjBes)4C#aKDw73OG5DR>9!Sdv%nPFZ zld>7VKY1hAKk+EtAVplRS>HhO$wh9-c>oY6w%vl{B(&Col_K`i9yqirFf=-rWNbu}(Ij4~!+Xz~zxxLAJa`$98*Uhwd0;g30KzC+}+?^Kfs99DCqX>wnXnVTgchpnWD)3tD8l zzuneAcb3oQ+QIL?DkR_G1hXBuHe=w~4va5lJdi}4WLK0kManK%d|MatZy#;C0MO-_ z=2vZsT#I>yFR%uX|< zv@=4v0Sg`ggd8(S`AIq=*HDJUO=T}HTkDtCsE)#F|E3FXDzhuwtL}cK4~e{ovD_a& zd2vMp(NTw6mBT`QTG+9iZ2_=sdkFfY{TCe5u+}uOB_)m49-J*mS^F_)A59@Lk8_zC~?#nnNnYY7RHp_ zQV>URQY;jzK~%G5iroilYCGAyBc>go-=!xA#eIfzn6!ZvOz3b8CxiNT_)s-1?z}D3 zxV4|xDAgv*wqTN03sk+}QDwhDui2<0b|#cxv+aS@n&6ZhUbX@J3M-^2zDUu@pCwCo5mtCHLU;&4LyFl(*<}kM4+4>@%uU3+ zM_GzHUA4S3bjYSPTRS*|lCd@aMq)k`ili^|Ro0~_ksv~T0}!b~H)_*A&2x3xp>;_+ z_Nl{8j1Kynt6yiNIwy2HZPz)(SE|Ow-e-jLuJx+^oKzB@an9{%qA8eLJS&AuM5=Vv z%Sh#gz|Pv|G}f5dllA9Dr=s%IEwh`kJQPk|x@g(xLnNe*15S4hx*^oH7S07lYU?w^ zGaIgmtc`dB5B6HU{Tq+4`3iuGtbB|666#4(`q`$rx83 zxePIIz8T`bkC2g+j)l_=hP&*Ec{-4^7lTvVrIm14HVk(w(b8VW#l^J-NEx_#W2`$8 zYBvHcg{%t0%g&2^vL{b1ZsI(LR__hny_-x)3WDzeQgT@x9dtWsoxTfY_~jnohTuk} zK%9q;72?S?|hPWaj-Yp_M$B^RXf>BkaMR|y9{L}?T38u;QfnM$0~0gjB_<}w0(`zIo18* z1M*`pV?w$(CswxGCK2|YV`h%|8G5~=#Bm<4Eu->g9r#afUlIP`Lz)}Wkz;)<^L9!88|EVVv3k(A zF>fA(2)mWjIq<>Pk zXcy2ERqpVS52h)O>UUv>Y1LYHgI(LlkX-!fMk0F0!u4G7QgFCzo9I0Vr4%e*GtFnQ z7O0Qv^)>TbeqU&`G_LBh4B#{P-&qMW^p@m*Nv~U<^S_Rn$3~S@vrc12Xf%}SzyLJa zj-dC5bby!D8zN1H7{i!WbZOBF?sNa#D|4wa=eDU*#=f{XLKxuLMX!J}ee)7c*5N!m z#^%I7LU%d!p1+)tFkd`^IlnOYsdbx85bs`BzsNx9VzT2l&rhu4v~I)QOju@CytuBU z8MjEKq@l>@+ZX~)hL!)WpbRSATgTtejaH%&n=ez!{6>@F6nL(>A|loW20VRN2_|AR zx^8tLc1k2Fbe}I)-C^oWk<~}G6U3RdP-)NN+rRjJ2zM#{&IQYK)7N*n2_dN-ljs6d z#ITdtYg`TTq}&J!q+G&_Cdg$?=LQ?zl}8>xlS6(v0V!z9MOs8zR28Wd6CG}piVn*F zVrk!p^xOaZzIQq`!yw?<9Q`dN(mx@5cpZr=xi^qmyLd0wP=_M$aRmds1)5+lHoBeQ zLu+Q=75SMZGZrZ1g2!luR41$5gKlGpqd?>GT{%F{KEATuNYzQzU3q z&AE8`y#it?>2u#niFYlVqo#t#eot0SUpku}xXVWL1N6+|3YrSYB{7oZj{)NaJfyiv^O)&4o(Eg6C=2 zxX!?N4ANaY%%Oce9ALQlTHxj(R^|4t6?GfE?6|~cI(%~G@5-kS>0TjSne_bGYy$F7 z86L!)(U|=Sxu0QPx719!0|TA;vr>L&+SzqcWE0;ZE(fNO6x9IVCBKpJX*~l^bSu-q zEpxxhLnln-G74JJZgL6QMvWRirU*pK&_sq>ylm!WFC2=d?X-q-!<^W}bojY1m7rz_ zVpPul`A<-CjX`l$>9(#b#yaY{EKTUnp`GJ@e!|4e@kI~P|`nW75|La4{w|Q zTi&&G{BRNg14sP?4GsOtv;i!2c0zn@ zW|n0o$z}aQ+w&7<76V=+fV56g@3O%!=;?h)vFP}6IrsYUaHng@N`3yJ)J^xq65mM& zq`lu}x#94U_d;ob=S`BLdzn@E5sl&D8)_-)n!)zPfrBZCkskFD#6Zy^hvE466-(YrQ>L-W;{4BtEqA8Tn z{HTngDOe$jP-gqSLSXb;md&F*f_;0ybo&$x#rGppD2!cJr0YlJ&gx}qQjMue|9ZR6 zBkTr<&DUSQXNk)$sX@d(oZhL3RKa<1XlY~pVLiZ!$-siH12B8<|I zDj2nwMrn#FE7hDKso}~EEZ=j9!=*ruokg=pIneyM5EA$~^~Bo;$svt`*i)CFh2WJ= zzdIHrJec{1m`mszl}#~?b#fj3=iOlP@0#Yj^7A#Z_}bmLun<;pc3|w%3%%$)WA8f5 zJ>3;w7)p&)1#Da^mY2M}X3QJXa^{BBuA7$6m`Wp4qRjX!d9gf#m566thCdqqnX}*1 zf);_F$$O@c_@}B(s63-8eR*Cv4w;4NZ@~fk1J&lHwc|EEkk5J>*)S4mC6#8h;b3;@ zG#hwy8^oe{gB4NCPIGtSBP);oE1_{jrkFH8RR+E>)9l)l?p5g-X=wDKFw$pqspT(x z=^hdrH?CF-cix5iqia-z|qE??s);-!Y|~+USA;DeF(zo8bCE z0!vTgY+3uOHw-9avDvH-<8GwhOb^3UGm;pP)QGR_%T#NVOAIZxwcmUMNR2wvb{t(0 zR)Rt<)~zA#k?vsSI&SE4Z{`Go3=4myfETm z_u<4Rnm{Uhkt#f_L-pHjTGU8RFca3pkN|Mb<2!Pp&n_(Z9MgMI$(w_Gu)2`QsW0YR!d6LyKNfsaRb(Ziw#${p%9d}RdJpUXK{c&F~d42MzkhH97616~3< zds<_Rm+M3Wh!W^)C7dqj$RNn!Wx=b@0(Mraa*C_Bf7}+*z=%R1UP^Q~gThnw+!@_8 zMT4TzY}(Ig8)~G4{UBm4FY$*5?siy^=*Q|Gg6_DRai3}*@A`gQww!o;U!B%%{jO}u zf~65YHsEtQPd#4ekw4Gqg7{(7jSZ-&$ALbBO@W)LW_j%p#YuWK49M+w`???ybLMxD zB#6zVQtimXbeODXgu(A?JcB5K)}gm|gJbYv?i7?UC4+lCGlq;XKop>==}CZvO=(JP z4+CdJAL&*eH994JLdBU~seYJa{Y3&*g9OL-Gb;II6=Kj9>GRkX#2a||wwTYGNrz7U zi(oruo8%CAz}F#hNwEf95X7jrKgA1rtVa!*J}N2le-)Md7x99Rs;J_iZ}@jX{3{Ft zqW=s-;Ja)xjXfI+d~F=vmntTvKBuB>uVgo9SuscQ4@uj2K}!Tp=a8^91a z{2zfrBhBX6zTlpWHQd+Z?96w6wQ+u)XfzukKSj0c_L6@xl{>C%G!7Da&Cd5J3+5a4 zFm$Z(#q>{w=N`6vc+^g>H?JH1GVjYW_+@RG!yYu(MZS;+T4kI1dx9J{rSQx zp7%XyZ&$-ugXcr9CM7zeJASRjyP|GhehX|CFGu zK^4{eDxRg~InvyZ-*XdL>PGyOGTR^#lmyXBz}H6Llv< zB2i^9Ra>t)$fIU?r6yUBn69~ux0s4&?=MY0!AT)F<%xZ8Z^6zoW|82^ zUOgl3)|`m#$=JF6kaW9`!%b=ZDDHs{aE$@ng)fkQBq%B(?Kj&w2jEAm$@5 zB>!R{{?8ao`bTd>`f=Lh_V*D_yy~(Nj`#475F4c0C=t&Du%TS%!6X+EBtCFH>`JEl%qm`m)Iev1%r@?_^x3uhI*_=K9sXR}-bJ&U?69~PQX zCWzcC5(8&tolY(8=EXXsL1=Tn4rw>mE0nfl4M{AhmL;!lS8q4MFo}AM!3iOxzD4!? z`|}TJb(RBbT~Yh{Lu^~@ORJT-OZdrz^p@OyC4HIKXnW5zwo4^sv4_Q9q3J-6IS&nr=fum1sCtMnC{@&OK!S zVEtQb6}-shTy#erUojot`Eh#m5_5B}?az)P0O@rpS69IUcL4_yBS_;%J+hFjzj>L& zzElCcVtRfF<2m@Nq*}(aLZ{-tT^2qa_1Nqp{d0#OJ#|g{S1_*LE(>%cv zQar*JK)x~>nYI4l2p{&R$O@~;*i+Q3Wh(XZtP+jBrQRW3H_$6MLv|wGI z!S+xz-Rll6_a=2__N%+eD^LYA@+$Da9b`Wt4HZo=Z`9SeDf#_+mWFESy)T~+PSr&`K_kQTv=r@c%-rTmPJZZHL zwxb5*{|QQY|6|bk7nb?Qn=5MLV(wsTV{L5X^jGsLZSEwk|36$}g)X^2WTgV)x6*Qc1tCTe5vfXQQKU{tu zrvBl00(GGPSNT(iz?1I?kSCoN-(o<)ro{_cVF--L!ltCPHH~erz0b}-Q3@Rq$mP{n z{pRDBj#CuoD=vgsrYM{~ny+k#m!WGHduhq5K6em2Q*KD6yxBbD9AFvyoml523@tb; z&5qQ0C9KbYu94|QEU$W1Gofe#CNfQxizrzfn*=y?L?Minl7u(VYQXVgc=}Ziwh9;% zDHep30w{QA8mqjuJ}T!=0P{Q2MJeII&63DS%K+6`f`!cZmdZ379ZO!>FY1|QU=G5j zkdH|Rg{1e>fwSQ$^d+pj2l1*4w#kpmxVrV%(%T2!7z+9TUr>hOBb9>x*HlXPul=5| zzRkzvX{GPa@iXLR1W6VUqKtz3wSW-C zg569!9S4L|T~eeCwu`t=uMI7-e9?)YBK91MtQp`%hK ztHuB(D15Nhngs2LJkFj!Um7#19#PponKLu>dpU*9oK*tmX5yHy;6n{->4nmj61&ae z;jv^}p#(^#0ReP;xm;zb(q7 znyQj^=8?F38U41DRf)m6A2~)lnU#t3vN>9xJUZ~Q+b@YttU6{OP3yAdiy4yVBz`Z# z2_PHH78=*fp2|?PN2OggA84B&o~;62CC+s&WQo)=i7oe>BHQWh;xr6}yDLq$#@y-tN^k^sU92DskFQQcwLBG&}v_I;W<|L)fiiDUppQR^(i8X!&8MSyC#n+j49|Qf8N3Eg3#Ixc4u3 zstey%3*YzsPH$Y|S~uIn;ze4B0Hs2VCxx#Ur-WdCN2l~{r&4k;Jho9PNeqN6FIev9y?ld7_*=C0+CnIa zjK4EVX7Y+s^611@p+pj9@bx`UjJtA9xCB|)hnMr04qO7xY*H&yhtj;Mq@#p4ID7Q7 zgc85*vAS)j7@sG^2g++1I=0NQ&bmCnrB4reaksMW`5&$@WL0bvn6>f(f5ww}WGBbT zqNGdl?SX?ETa&DJeKzhQxzAd>ohO!$ry3%5SDtj3FV;Ck<7!zbLKdpEBPK2`5Rwx6 zWn`50!VnCyXPy>_A?mX6d_xiK6*@yRVHqvBDnf5V$>d70^+eS>m?oQ=TLN=tYNpQw zA3+qK6AHy!nvyo%5bt~P3gR3dzTQcDrYbi5jM~y2so+EKjhnthg|AvP*Q-zG{1kP>u?2G=|9~j4d?rcRZ+WY{5%+-Jg-fPDhZ(}-PXx+a(h=RTVUSU{=~I8 z1mm^_dp=HZu-3f1yxQBq8vA^^Bf87G)4TnnXbf6N!|LizOzU8Le-`>$OLFkd-}fb~ zsT%JIxhyYL@1=x_3vv0-HU`Jn2z zC-@Ki?5`&nucGybr1BdufsPh}D)D235iLzGuuF_BErG#K`mO&_xNOzGn0r z^b5Gp5B^Sa-B~EcKAs%=AE;cX~^NU)ntWBOyH?otnei2ddpT< zT=WCaFH6UaXn9QcB zfDh{nLhvZ+4VX_}ERDxwcK9rEZl>Na7}6>DI_0(I*sRJLM%FOHlcy^vRRIn1cEIxN z(b-kgR(lytIt$i8>weN_v#P_p3wcTMRz3ws5(2$J{-bx)Wu>Exz;|4h+Cs_EZwAn$ z%Es(46)doqBB4VL*zi5IHJ;a|8tP#lKU?%8QQ`YlejAR4%e-@XE%6fe44gae9lFdm zwp-1mHb>npwNqi&n@DIL=ksSdBt-$!3WNNeoOnYl2Pl4pY|)vZj))<5rTRTmoauK# z1Y-8lk;W6b@GSwr29+r(mh#Cgn^eAbc>}wYtGss1Je+G=>s3c=OPMKXsgo0xeo=EZ z&hd?-tH~JF!zX_b~s{M+ab)6_UJ!xedBor z$PQg1@-IxwR76=FAdH{{C>w|uDL8*dv8~RAVziyN*A}E#EpKPm@PWf2q>*#s23_Sn zZ9Ia(%grc<5GGjD*@DI6}O8tANemZ zWw=)8H1(s&Q~s}D3irRJjem^HvhM%+kH21mv_Ji0P*Y`PPB0UJxwR8QFi~-&w9Z+S zxSAn^(8wR6qM5{v)-h<9E6zKS5~+`jsnJr@zP-_4{IHjEi~$M6UwId}&)rSePL_4v zA0BsceyFaD_H?!e^OWx?_Oas4%bFGbDm3WI9g)Rj#e)ZGKlH&+Q3K%c=%Cv70Vjmm z2)z8Y;U_X_z<>PTjIV4_dsu_MXhTR$skeCmc#Vp)Q`?K4YGA z#vynW5c^2WSL>y00)d~!N^vINXQwX+- zN8>gUyI4Ij8lR-pA%2c@AHb3#uf!y4COYp}-s&;JGno&4X?NQy%ygRRxzgg>LYGOc z)6mT}G8|@k{FHqfSO%5k&kJ~r--@?S1RD_a0{oV=gs?nlWPWVoW|94-GGs&N|H+CF^@uv2l2n!n3@wXHtK%1Z+w&JQ&x?-*%WTyAo4o0H{d8QSSzL9~sT$zj?~;n2dkKvW ziP?|qjc01)98LSHS5MBY<{YX6V(gOP8%Uk>Phh#pdWhY;S15m#SF$(Cv7PlUFDV&M zG8}i0eUUSbDTOFqLhrF-Cvh^zBZbZcOa(^#ecsku+b zPj-w$@>T1ErI5f62fqDKfdsicub_Fs9-gWL$jX&iMkYy*Q?1d{?~oXs_POhW7g7Fz z!1TSIxuphcco26wXoDnI@6bpEX@LkeMtK5t{Z4cNoqae@KJAJ9n zvOd@qf6eYxyPUgL!()>WCb6wUJ7 zolj;(U(1MOHrSJewe$hq7h!8&zZ`M$`3xbfMG5uuBT>m+Z2a@%t#&?$-xAkwO-LB zPkWd|Z>{y1S|LxKM{+-_jIg#J#hJgJz$RzTp}z;F&q#yc`-mPOH%;G3vTZzK6|55@ z$;C$jp>^Ue!ed7%&%u6gFNy-xd3X%n;M4~#SA;YD5$wMS`(aAvdNG(UeNp(*w2nZ& zV^8PmpY@>6z?teqEQ_AUV$u8Tuq8gw5txpv*?deLZ@fgFZ=CNj(^k9UISmE94uIUb z;|2PN={`Xa!+y*5p}PSSvZ9N_g^372uoGLAmdh;=pV<#OtLqt8a1aEMMR}6;Ck0}K z>~NLUGzQ>MOlbDvw%Ks16d9rT!0>92y#lIH=K)`gfKw=>O_k$0&@Tt1&ZCx~k~#(N zu&9yAU#K<|*$>oir#OQ)a$SNjx&5;@_txXqU4v*__Rx#kj=vZpn6?!|6w>=)DtQjB zegY2OdNbXL7T*6Un6L_?oTK-VxqLph`~SO({O8X8UtAOZ|LyGmE~;_X^3w{a{qL?g z)LPUVQBV-Z6hrt?RD_Cwa3(PJgI<0Rx(6vO>w1CEq#y}-H3&BTkpvZ3!98dV#5450h``+UIe4I#zYQLDPp-G_w*oW8o^rJT1H;L$yDewop*ngRvRgB zmw8s8IIaTkiljVwg-bz~9l?3@%lDCw;wzkuWg;vJJ*csr;rvvxRRwKkJxXEk+` z3Sh=CVk}J5cT)}5eA?fS=!rZWbeMg4X0XdP^vX&!RE3o6bqh}7qwN?6NX=H8QS)%Y zceRu~P3w1ET};}|ZXqWEkqbzo)@oW%>kWgVHF@68UWIF@^k7j-sWC;Rx`BRJBh&*1 z(VLPx`ngzKKJYM+AHcIG8qiHHY|EtftI03eFOzLz2RY*Mq8(w4Ol9Uz*nNZ~g7tpX zMv;?}Kj$#Z4!h+mg)X5C*3B^bIbI_tVQCb^5>U|@1zQfkv6RQq@mNkNcd%`wzDlj z1e@0~NebN(|DLD9?I5r*MdtO-drnXdcaX@B%6a4Ycy%Eiow{aG}>h&1_Re#02Ygd;9g!p^=Pll1yhbm2X4r zwHkA^yYDhZLard=QPG&xOdhuV){Zy(9A~kr#Z{0m`+MO80v}_$UMW*P>3rJ$4jJ=Nue~%9j;92lo&w`BG2|6~B5!t79^!JT8e_!AFfIIhsJVD%R2$TBLlnrGCLXm2Sy#z`{Gkha zD>3$8z;cenq1UsFw>wl(5Ns!4S_Su;M-`|PtkcbSpdQ7mQz&Hh&HRkiv^aieRKZa8 z2!-jgV{o$U{SjOU*U*jtqnbhPU%p`@$ubE#AK#7rXl{k6~tFYCdS4l++>h zIXvkv^QfkPc*dZw4-@Iro@egqju{0_K5uVN@Ol`#s9Oti1_)o#ICpkSGWEm-6}t?9 zDh*@SEx(sDc!0b=x$!nN@m7!TJ<2a`V`I)dp0wze@<+Amzq+>Yp3MggdaL`Vp#gV% zopcdY>eQ+3Wv=RSVqym2KlV&RnG3z9o>vh*>1>N$Tj4`d;hGCu~f13 zta$E|fK>fV!WT!~##O=3J$KNo{>3|ChTprLQ@F*SQs$}_@A9nEmkX$RhT;%_bwvUq zqfvGv;}!lJ1-Y{I(a#L9iS;?_F@u>es0h_ zC)66>R{szUE*9+h3=cz!#xq+Pp#$GwFD@9I#S$7x`o3$9#$&kfa^4G=EUhi)Nx28q znYU|C&_3M5C%VcouHKiGFvdnGBl@*CasC!GnW`bZ&Ha@rIK(=l+%7;(8-g5C z^devBwFyPMen1@}>Orpn?t&y7jpQT*I`wn45qK;RRR(!*ShW;hnGYPXj4RvTi@N94 zA70gb<0gi10h*!e3Y&1Yv|5OVX$}euOdC zf2}c8{~F5wH+&*o%~~E+{UdZ+7)}^qiTw=9>Ip01nw9G6{RnN3)V|Rupjuc5u7Zdq zVx^{uS}*0-%f1WyfV@u7=R#OWF6EN39m-oI=a08BpU?%FurEG4oN9z3dUaP4#Xy7l{qZzs$KEPHH2)~wG;2B z*<jEW+)@)PT;W7I7R30GwoLn99Nd1Kn%JWkitD;I_H zBMVjA>J6X0L1potMX@rFgAkgqo=WlR@GN;ccx>IL1&5AQn@wcK6P^_?ZxgNd>dSaw zWxA&1l5;qR2PKG$l*oR%yO#zXq%$EL1gQuzSUu%;ZhDu3Bgj0OJazqQ%08%y#|%35 ze*o>G$U>jJ>xh+qe#cBX(1 zIxwwko_NDBR1jGcg4xh*ruxQmwwNqtW@ct)OH&8#boZIQedoP7f2+RQ zKWfLy6`2{4DV`GnmaK2pWViXT7A$$Mp8@DdIL)cmZ-s3OR`s8EtkW<{t1_NRY)2C>n#Z@{I?uI!7}2%QGGwK# z4TseRSY%z_+(^sN&!sk_&{a0-7rCM5&FZAdnX@W?+K_?CxX9=9sCeuyOJIY(9*F3= zO10)X&fISSv6*bL6$2k&Cq+(Fv9FY|79jbCT_WymOHf;xh>UXr3A?(>9*)s(kFaxC zP{2Shgg#DTR?jZg=siE=g%MzvFR1}f;w#VxZ@z=#;h*ogEb7ea&}Idllu^>s4!~f` zn4I@e{RML8$hm$8Qv)%NfU8HY&*nJ=kBcZm)rD#wGBp}MjiYIwLVKZLtH>my6S0h` z-S_RASGX&QVlEedg&hdvr=*2m5L7O=54T9eo$xE z_#ewsdI3IxWU#yMKVjXDOfgNwh`g(lYbVyH5IA)At8h;{FzI}X@pulxr^(5O)T`F& zFy7Tm3Yyn}kWt+VCt8f+1;gB}K#TL6D#**h=U01Ih&_|G?tIo=G#o4IBdYEDtVKQ5 z9JZ{dAhzCD&RX`^L6%i~+F+-?Y`4_8k>srH_fO$;e?--Z8F6)Dh&6FG)+^B@4t0`suV#Z zx`CM|GEiFnkIeJ0mpse=Wn?4)mU91POJ9-)m08@X!S*L$zA2JwgAD}jxS&-|?;4$r!KTo3wjP3~F>}0+2E1T$u=5L<@d)_!y>{)TSrMJj~y==;H#WCJ+$WR6^!@HwEa=y?j z^4x*=x75sKBAOX|~2R`gXspb|G`S$oH2AIxNDGZK}H; zCFvt|zqdMFv!~*qwqfw_p1}#^Y_4@wn$)JD%dikmL zN$B!JSM%?Ov_rI0UlLLb;_HlRWtMZAzYEbn&`F-JA^=)hgFLcD)g)ABQeg#GNg`=x zFXPZcCnlS3Wj>X%3S|~5dkK_-)wK0mkJqOxD~EFtlzps$(bwHn=t|hLRr2AY>`3T4 zcqAPyyhbgWR`(pg#pVIG{axbGE?z4+3gm>=K;6wh;NtZc&CP!^eCNMpl>eK^1FZyq z&_H0zP_7%eLkv3DvH}DZ;kH}mQXlyWaAmmNr7ROPDf8yJZ^OFPhNJA`LW5-94WL)a?57o!qUSyh2A8;r0FslXH@V8Iv~S-7V1in z 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

  • j^?+O;cG=X1)pRX??pxCbh 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
    (5BE9wc4hwaBkSM)Th{+i)cJSr^H1R8PvlG4 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%0D^9%lu@tqK^*Isb;gply46~t-Fm0bujx*9=2d)t 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%0APx;5*cv*ndo^u%^yWnFK{EO&F!rM^PW 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 z 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�h79FDEA-qk3J1i?HE^M;muuA8 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 literal 0 HcmV?d00001 diff --git a/android/proguard-project.txt b/android/proguard-project.txt new file mode 100644 index 00000000..f2fe1559 --- /dev/null +++ b/android/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/android/src/org/purplei2p/i2pd/DaemonSingleton.java b/android/src/org/purplei2p/i2pd/DaemonSingleton.java new file mode 100644 index 00000000..7f9aae75 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/DaemonSingleton.java @@ -0,0 +1,108 @@ +package org.purplei2p.i2pd; + +import java.util.HashSet; +import java.util.Set; + +import android.util.Log; + +public class DaemonSingleton { + private static final String TAG="i2pd"; + private static final DaemonSingleton instance = new DaemonSingleton(); + public static interface StateChangeListener { void daemonStateChanged(); } + private final Set stateChangeListeners = new HashSet(); + + public static DaemonSingleton getInstance() { + return instance; + } + + public synchronized void addStateChangeListener(StateChangeListener listener) { stateChangeListeners.add(listener); } + public synchronized void removeStateChangeListener(StateChangeListener listener) { stateChangeListeners.remove(listener); } + + public 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; } + + { + fireStateChange(); + new Thread(new Runnable(){ + + @Override + public void run() { + try { + I2PD_JNI.loadLibraries(); + state = State.jniLibraryLoaded; + fireStateChange(); + } catch (Throwable tr) { + lastThrowable=tr; + state = State.startFailed; + fireStateChange(); + return; + } + try { + daemonStartResult = I2PD_JNI.startDaemon(); + if("ok".equals(daemonStartResult)){state=State.startedOkay;setStartedOkay(true);} + else state=State.startFailed; + fireStateChange(); + } catch (Throwable tr) { + lastThrowable=tr; + 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; + } + + public synchronized boolean isStartedOkay() { + return startedOkay; + } + + private synchronized void setStartedOkay(boolean startedOkay) { + this.startedOkay = startedOkay; + } + + public void stopDaemon() { + if(isStartedOkay()){ + try {I2PD_JNI.stopDaemon();}catch(Throwable tr){Log.e(TAG, "", tr);} + setStartedOkay(false); + } + } +} diff --git a/android/src/org/purplei2p/i2pd/I2PD.java b/android/src/org/purplei2p/i2pd/I2PD.java index ef22f941..5d8322ed 100755 --- a/android/src/org/purplei2p/i2pd/I2PD.java +++ b/android/src/org/purplei2p/i2pd/I2PD.java @@ -5,6 +5,8 @@ import java.io.StringWriter; import java.util.Timer; import java.util.TimerTask; +import org.purplei2p.i2pd.DaemonSingleton.State; + import android.annotation.SuppressLint; import android.app.Activity; import android.content.ComponentName; @@ -22,52 +24,47 @@ import android.widget.Toast; public class I2PD extends Activity { private static final String TAG = "i2pd"; - - private static Throwable loadLibsThrowable; - static { - try { - I2PD_JNI.loadLibraries(); - } catch (Throwable tr) { - loadLibsThrowable = tr; + 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 String daemonStartResult="N/A"; - private boolean destroyed=false; + }; + private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - destroyed=false; //set the app be foreground (do not unload when RAM needed) doBindService(); textView = new TextView(this); setContentView(textView); - if (loadLibsThrowable != null) { - textView.setText(throwableToString(loadLibsThrowable)+"\r\n"); - return; - } - try { - textView.setText( - "libi2pd.so was compiled with ABI " + getABICompiledWith() + "\r\n"+ - "Starting daemon... "); - new Thread(new Runnable(){ - - @Override - public void run() { - try { - doStartDaemon(); - } catch (final Throwable tr) { - appendThrowable(tr); - } - } - - },"i2pdDaemonStarting").start(); - } catch (Throwable tr) { - textView.setText(textView.getText().toString()+throwableToString(tr)); - } + daemonStateChangeListener.daemonStateChanged(); + daemon.addStateChangeListener(daemonStateChangeListener); } @Override @@ -76,25 +73,18 @@ public class I2PD extends Activity { localDestroy(); } - private synchronized void localDestroy() { - if(destroyed)return; - destroyed=true; - if(gracefulQuitTimer!=null) { - gracefulQuitTimer.cancel(); - gracefulQuitTimer = null; + private void localDestroy() { + textView = null; + daemon.removeStateChangeListener(daemonStateChangeListener); + if(getGracefulQuitTimer()!=null) { + getGracefulQuitTimer().cancel(); + setGracefulQuitTimer(null); } try{ doUnbindService(); }catch(Throwable tr){ Log.e(TAG, "", tr); } - if("ok".equals(daemonStartResult)) { - try { - I2PD_JNI.stopDaemon(); - } catch (Throwable tr) { - Log.e(TAG, "error", tr); - } - } } private CharSequence throwableToString(Throwable tr) { @@ -105,42 +95,6 @@ public class I2PD extends Activity { return sw.toString(); } - public String getABICompiledWith() { - return I2PD_JNI.getABICompiledWith(); - } - - private synchronized void doStartDaemon() { - if(destroyed)return; - daemonStartResult = I2PD_JNI.startDaemon(); - runOnUiThread(new Runnable(){ - - @Override - public void run() { - synchronized (I2PD.this) { - if(destroyed)return; - textView.setText( - textView.getText().toString()+ - "start result: "+daemonStartResult+"\r\n" - ); - } - } - - }); - } - - private void appendThrowable(final Throwable tr) { - runOnUiThread(new Runnable(){ - - @Override - public void run() { - synchronized (I2PD.this) { - if(destroyed)return; - textView.setText(textView.getText().toString()+throwableToString(tr)+"\r\n"); - } - } - }); - } - // private LocalService mBoundService; private ServiceConnection mConnection = new ServiceConnection() { @@ -218,7 +172,6 @@ public class I2PD extends Activity { @SuppressLint("NewApi") private void quit() { try { - localDestroy(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { finishAndRemoveTask(); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { @@ -230,11 +183,18 @@ public class I2PD extends Activity { }catch (Throwable tr) { Log.e(TAG, "", tr); } + try{ + daemon.stopDaemon(); + }catch (Throwable tr) { + Log.e(TAG, "", tr); + } + System.exit(0); } - private Timer gracefulQuitTimer; - private synchronized void gracefulQuit() { - if(gracefulQuitTimer!=null){ + 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; @@ -246,22 +206,21 @@ public class I2PD extends Activity { @Override public void run() { try{ - synchronized (I2PD.this) { - if("ok".equals(daemonStartResult)) { - I2PD_JNI.stopAcceptingTunnels(); - gracefulQuitTimer = new Timer(true); - gracefulQuitTimer.schedule(new TimerTask(){ + Log.d(TAG, "grac stopping"); + if(daemon.isStartedOkay()) { + daemon.stopAcceptingTunnels(); + setGracefulQuitTimer(new Timer(true)); + getGracefulQuitTimer().schedule(new TimerTask(){ - @Override - public void run() { - quit(); - } - - }, 10*60*1000/*millis*/); - }else{ - quit(); - } - } + @Override + public void run() { + quit(); + } + + }, 10*60*1000/*milliseconds*/); + }else{ + quit(); + } } catch(Throwable tr) { Log.e(TAG,"",tr); } @@ -269,4 +228,16 @@ public class I2PD extends Activity { },"gracQuitInit").start(); } + + private Timer getGracefulQuitTimer() { + synchronized (gracefulQuitTimerLock) { + return gracefulQuitTimer; + } + } + + private void setGracefulQuitTimer(Timer gracefulQuitTimer) { + synchronized (gracefulQuitTimerLock) { + this.gracefulQuitTimer = gracefulQuitTimer; + } + } } From d240f3242cbc3c92c078f2f9671b730b7e132e6f Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Wed, 13 Jul 2016 11:04:44 +0800 Subject: [PATCH 1611/6300] gitignore --- android/libs/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/libs/.gitignore b/android/libs/.gitignore index e4e4e6c1..3d419698 100644 --- a/android/libs/.gitignore +++ b/android/libs/.gitignore @@ -1 +1 @@ -armeabi-v7a +/armeabi-v7a/ From 95ae23a32cecc8910c0fa870aceced2ccf093818 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Wed, 13 Jul 2016 11:06:03 +0800 Subject: [PATCH 1612/6300] gitignore --- android/libs/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/libs/.gitignore b/android/libs/.gitignore index 3d419698..e4e4e6c1 100644 --- a/android/libs/.gitignore +++ b/android/libs/.gitignore @@ -1 +1 @@ -/armeabi-v7a/ +armeabi-v7a From a3286ebac3932c83e7e93787e06cb51a3d79f300 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Wed, 13 Jul 2016 19:36:18 +0800 Subject: [PATCH 1613/6300] updated --- .../org/purplei2p/i2pd/DaemonSingleton.java | 84 +++++++++++-------- android/src/org/purplei2p/i2pd/I2PD.java | 7 +- 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/android/src/org/purplei2p/i2pd/DaemonSingleton.java b/android/src/org/purplei2p/i2pd/DaemonSingleton.java index 7f9aae75..5e0ac4d0 100644 --- a/android/src/org/purplei2p/i2pd/DaemonSingleton.java +++ b/android/src/org/purplei2p/i2pd/DaemonSingleton.java @@ -18,7 +18,7 @@ public class DaemonSingleton { public synchronized void addStateChangeListener(StateChangeListener listener) { stateChangeListeners.add(listener); } public synchronized void removeStateChangeListener(StateChangeListener listener) { stateChangeListeners.remove(listener); } - public void stopAcceptingTunnels() { + public synchronized void stopAcceptingTunnels() { if(isStartedOkay()){ state=State.gracefulShutdownInProgress; fireStateChange(); @@ -39,35 +39,45 @@ public class DaemonSingleton { public State getState() { return state; } { - fireStateChange(); - new Thread(new Runnable(){ - - @Override - public void run() { - try { - I2PD_JNI.loadLibraries(); - state = State.jniLibraryLoaded; - fireStateChange(); - } catch (Throwable tr) { - lastThrowable=tr; - state = State.startFailed; - fireStateChange(); - return; + 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; + } } - try { - daemonStartResult = I2PD_JNI.startDaemon(); - if("ok".equals(daemonStartResult)){state=State.startedOkay;setStartedOkay(true);} - else state=State.startFailed; - fireStateChange(); - } catch (Throwable tr) { - lastThrowable=tr; - state = State.startFailed; - fireStateChange(); - return; - } - } - - }, "i2pdDaemonStart").start(); + + }, "i2pdDaemonStart").start(); + } } private Throwable lastThrowable; private String daemonStartResult="N/A"; @@ -90,16 +100,22 @@ public class DaemonSingleton { public String getDaemonStartResult() { return daemonStartResult; } + + private final Object startedOkayLock = new Object(); - public synchronized boolean isStartedOkay() { - return startedOkay; + public boolean isStartedOkay() { + synchronized (startedOkayLock) { + return startedOkay; + } } - private synchronized void setStartedOkay(boolean startedOkay) { - this.startedOkay = startedOkay; + private void setStartedOkay(boolean startedOkay) { + synchronized (startedOkayLock) { + this.startedOkay = startedOkay; + } } - public void stopDaemon() { + public synchronized void stopDaemon() { if(isStartedOkay()){ try {I2PD_JNI.stopDaemon();}catch(Throwable tr){Log.e(TAG, "", tr);} setStartedOkay(false); diff --git a/android/src/org/purplei2p/i2pd/I2PD.java b/android/src/org/purplei2p/i2pd/I2PD.java index 5d8322ed..58912bf5 100755 --- a/android/src/org/purplei2p/i2pd/I2PD.java +++ b/android/src/org/purplei2p/i2pd/I2PD.java @@ -5,8 +5,6 @@ import java.io.StringWriter; import java.util.Timer; import java.util.TimerTask; -import org.purplei2p.i2pd.DaemonSingleton.State; - import android.annotation.SuppressLint; import android.app.Activity; import android.content.ComponentName; @@ -76,8 +74,9 @@ public class I2PD extends Activity { private void localDestroy() { textView = null; daemon.removeStateChangeListener(daemonStateChangeListener); - if(getGracefulQuitTimer()!=null) { - getGracefulQuitTimer().cancel(); + Timer gracefulQuitTimer = getGracefulQuitTimer(); + if(gracefulQuitTimer!=null) { + gracefulQuitTimer.cancel(); setGracefulQuitTimer(null); } try{ From e24eea313c28aec78c26ae5d15d3850ef224c610 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Wed, 13 Jul 2016 19:40:09 +0800 Subject: [PATCH 1614/6300] updated --- android/src/org/purplei2p/i2pd/I2PD.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/android/src/org/purplei2p/i2pd/I2PD.java b/android/src/org/purplei2p/i2pd/I2PD.java index 58912bf5..0397cf03 100755 --- a/android/src/org/purplei2p/i2pd/I2PD.java +++ b/android/src/org/purplei2p/i2pd/I2PD.java @@ -208,8 +208,9 @@ public class I2PD extends Activity { Log.d(TAG, "grac stopping"); if(daemon.isStartedOkay()) { daemon.stopAcceptingTunnels(); - setGracefulQuitTimer(new Timer(true)); - getGracefulQuitTimer().schedule(new TimerTask(){ + Timer gracefulQuitTimer = new Timer(true); + setGracefulQuitTimer(gracefulQuitTimer); + gracefulQuitTimer.schedule(new TimerTask(){ @Override public void run() { From 9a8e7b11e55c2e28bd5ce61eda1664149bc4a108 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 13 Jul 2016 10:09:22 -0400 Subject: [PATCH 1615/6300] detect network status at android --- Transports.cpp | 2 +- Transports.h | 5 ++++- android/jni/i2pd_android.cpp | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Transports.cpp b/Transports.cpp index 1efbfb2f..2d572aef 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -93,7 +93,7 @@ namespace transport Transports transports; Transports::Transports (): - m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_PeerCleanupTimer (m_Service), + m_IsOnline (true), m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_PeerCleanupTimer (m_Service), m_NTCPServer (nullptr), m_SSUServer (nullptr), m_DHKeysPairSupplier (5), // 5 pre-generated keys m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_InBandwidth (0), m_OutBandwidth (0), m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0) diff --git a/Transports.h b/Transports.h index 2902e052..708c55e2 100644 --- a/Transports.h +++ b/Transports.h @@ -80,6 +80,9 @@ namespace transport bool IsBoundNTCP() const { return m_NTCPServer != nullptr; } bool IsBoundSSU() const { return m_SSUServer != nullptr; } + bool IsOnline() const { return m_IsOnline; }; + void SetOnline (bool online) { m_IsOnline = online; }; + boost::asio::io_service& GetService () { return m_Service; }; std::shared_ptr GetNextDHKeysPair (); void ReuseDHKeysPair (std::shared_ptr pair); @@ -133,7 +136,7 @@ namespace transport private: - bool m_IsRunning; + bool m_IsOnline, m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; diff --git a/android/jni/i2pd_android.cpp b/android/jni/i2pd_android.cpp index 40b50345..201b668e 100755 --- a/android/jni/i2pd_android.cpp +++ b/android/jni/i2pd_android.cpp @@ -4,6 +4,7 @@ #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) { @@ -58,6 +59,8 @@ JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels } JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged - (JNIEnv * env, jclass clazz, jboolean isConnected) { + (JNIEnv * env, jclass clazz, jboolean isConnected) +{ bool isConnectedBool = (bool) isConnected; + i2p::transport::transports.SetOnline (isConnectedBool); } From 812f5045b06e667fa45a738d4b914e4d17d32c21 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 13 Jul 2016 11:12:51 -0400 Subject: [PATCH 1616/6300] enable UPnP for windows and android by default --- Config.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Config.cpp b/Config.cpp index 28cb5223..10330c34 100644 --- a/Config.cpp +++ b/Config.cpp @@ -31,6 +31,7 @@ namespace config { #ifdef MESHNET nat = false; #endif + options_description general("General options"); general.add_options() ("help", "Show this message") @@ -126,9 +127,13 @@ namespace config { ("i2pcontrol.key", value()->default_value("i2pcontrol.key.pem"), "I2PCP connection cerificate key") ; - options_description upnp("UPnP options"); - upnp.add_options() - ("upnp.enabled", value()->default_value(false), "Enable or disable UPnP: automatic port forwarding") + bool upnp_default = false; +#if (defined(USE_UPNP) && ((defined(WIN32) && defined(USE_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") ; options_description precomputation("Precomputation options"); From f2f0d69bceb519377cd959e6c07424ab877890e4 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 13 Jul 2016 11:14:06 -0400 Subject: [PATCH 1617/6300] Update configuration.md --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 9a0f0197..9a5d1826 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -68,7 +68,7 @@ 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 +* --upnp.enabled= - Enable or disable UPnP, false by default for CLI and true for GUI (Windows, Android) * --precomputation.elgamal= - Use ElGamal precomputated tables. false for x64 and true for other platforms by default From 2d40d69fa22997d61a56f6fc71a905a0838c2dde Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 13 Jul 2016 12:56:23 -0400 Subject: [PATCH 1618/6300] fixed race condition --- RouterInfo.cpp | 61 +++++++++++++++++++++++++++----------------------- RouterInfo.h | 7 +++--- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 3462d7d8..b3d7c989 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -19,6 +19,7 @@ namespace data m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0) { + m_Addresses = std::make_shared(); // create empty list m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; ReadFromFile (); } @@ -26,6 +27,7 @@ namespace data RouterInfo::RouterInfo (const uint8_t * buf, int len): m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0) { + m_Addresses = std::make_shared(); // create empty list m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; memcpy (m_Buffer, buf, len); m_BufferLen = len; @@ -48,7 +50,7 @@ namespace data m_IsUnreachable = false; m_SupportedTransports = 0; m_Caps = 0; - m_Addresses.clear (); + // don't clean up m_Addresses, it will be replaced in ReadFromStream m_Properties.clear (); // copy buffer if (!m_Buffer) @@ -144,6 +146,7 @@ namespace data s.read ((char *)&m_Timestamp, sizeof (m_Timestamp)); m_Timestamp = be64toh (m_Timestamp); // read addresses + auto addresses = std::make_shared(); uint8_t numAddresses; s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return; bool introducers = false; @@ -234,10 +237,11 @@ namespace data } if (isValidAddress) { - m_Addresses.push_back(std::make_shared
    (address)); + addresses->push_back(std::make_shared
    (address)); m_SupportedTransports |= supportedTransports; } } + m_Addresses = addresses; // read peers uint8_t numPeers; s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return; @@ -288,7 +292,7 @@ namespace data if (!s) return; } - if (!m_SupportedTransports || !m_Addresses.size() || (UsesIntroducer () && !introducers)) + if (!m_SupportedTransports || !m_Addresses->size() || (UsesIntroducer () && !introducers)) SetUnreachable (true); } @@ -366,9 +370,9 @@ namespace data s.write ((char *)&ts, sizeof (ts)); // addresses - uint8_t numAddresses = m_Addresses.size (); + uint8_t numAddresses = m_Addresses->size (); s.write ((char *)&numAddresses, sizeof (numAddresses)); - for (auto addr : m_Addresses) + for (auto addr : *m_Addresses) { Address& address = *addr; s.write ((char *)&address.cost, sizeof (address.cost)); @@ -561,9 +565,9 @@ namespace data addr->cost = 2; addr->date = 0; addr->mtu = 0; - for (auto it: m_Addresses) // don't insert same address twice + for (auto it: *m_Addresses) // don't insert same address twice if (*it == *addr) return; - m_Addresses.push_back(addr); + m_Addresses->push_back(addr); m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4; } @@ -577,9 +581,9 @@ namespace data addr->date = 0; addr->mtu = mtu; memcpy (addr->key, key, 32); - for (auto it: m_Addresses) // don't insert same address twice + for (auto it: *m_Addresses) // don't insert same address twice if (*it == *addr) return; - m_Addresses.push_back(addr); + m_Addresses->push_back(addr); m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; m_Caps |= eSSUTesting; m_Caps |= eSSUIntroducer; @@ -587,7 +591,7 @@ namespace data bool RouterInfo::AddIntroducer (const Introducer& introducer) { - for (auto addr : m_Addresses) + for (auto addr : *m_Addresses) { if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) { @@ -602,7 +606,7 @@ namespace data bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) { - for (auto addr: m_Addresses) + for (auto addr: *m_Addresses) { if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) { @@ -693,24 +697,24 @@ namespace data { // NTCP m_SupportedTransports &= ~eNTCPV6; - for (size_t i = 0; i < m_Addresses.size (); i++) + 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 ()) + if ((*m_Addresses)[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP && + (*m_Addresses)[i]->host.is_v6 ()) { - m_Addresses.erase (m_Addresses.begin () + i); + m_Addresses->erase (m_Addresses->begin () + i); break; } } // SSU m_SupportedTransports &= ~eSSUV6; - for (size_t i = 0; i < m_Addresses.size (); i++) + 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 ()) + if ((*m_Addresses)[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU && + (*m_Addresses)[i]->host.is_v6 ()) { - m_Addresses.erase (m_Addresses.begin () + i); + m_Addresses->erase (m_Addresses->begin () + i); break; } } @@ -723,24 +727,24 @@ namespace data { // NTCP m_SupportedTransports &= ~eNTCPV4; - for (size_t i = 0; i < m_Addresses.size (); i++) + 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 ()) + if ((*m_Addresses)[i]->transportStyle == i2p::data::RouterInfo::eTransportNTCP && + (*m_Addresses)[i]->host.is_v4 ()) { - m_Addresses.erase (m_Addresses.begin () + i); + m_Addresses->erase (m_Addresses->begin () + i); break; } } // SSU m_SupportedTransports &= ~eSSUV4; - for (size_t i = 0; i < m_Addresses.size (); i++) + 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 ()) + if ((*m_Addresses)[i]->transportStyle == i2p::data::RouterInfo::eTransportSSU && + (*m_Addresses)[i]->host.is_v4 ()) { - m_Addresses.erase (m_Addresses.begin () + i); + m_Addresses->erase (m_Addresses->begin () + i); break; } } @@ -770,7 +774,8 @@ namespace data std::shared_ptr RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const { - for (auto address : m_Addresses) + auto addresses = m_Addresses; + for (auto address : *addresses) { if (address->transportStyle == s) { diff --git a/RouterInfo.h b/RouterInfo.h index 8c8af691..78d2c3ab 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -105,7 +105,8 @@ namespace data return !(*this == other); } }; - + typedef std::vector > Addresses; + RouterInfo (const std::string& fullPath); RouterInfo (): m_Buffer (nullptr) { }; RouterInfo (const RouterInfo& ) = default; @@ -117,7 +118,7 @@ namespace data void SetRouterIdentity (std::shared_ptr identity); std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; uint64_t GetTimestamp () const { return m_Timestamp; }; - std::vector >& GetAddresses () { return m_Addresses; }; + Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr std::shared_ptr GetNTCPAddress (bool v4only = true) const; std::shared_ptr GetSSUAddress (bool v4only = true) const; std::shared_ptr GetSSUV6Address () const; @@ -199,7 +200,7 @@ namespace data uint8_t * m_Buffer; size_t m_BufferLen; uint64_t m_Timestamp; - std::vector > m_Addresses; + std::shared_ptr m_Addresses; std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; uint8_t m_SupportedTransports, m_Caps; From 8dd157d2ebaa2e346b9f8193c63d12d472324bdb Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 14 Jul 2016 00:00:00 +0000 Subject: [PATCH 1619/6300] * HTTPProxy.cpp : html error messages --- HTTPProxy.cpp | 102 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index e70eb8ab..b35c3b4a 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -22,6 +22,21 @@ namespace i2p { namespace proxy { + std::map jumpservices = { + { "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" }, + { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, + }; + + static const char *pageHead = + "\r\n" + " I2P HTTP proxy: error\r\n" + " \r\n" + "\r\n" + ; + bool str_rmatch(std::string & str, const char *suffix) { auto pos = str.rfind (suffix); if (pos == std::string::npos) @@ -39,12 +54,14 @@ namespace proxy { void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); - void HTTPRequestFailed(const char *message); - void RedirectToJumpService(std::string & host); bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64); void SanitizeHTTPRequest(i2p::http::HTTPReq & req); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); + /* error helpers */ + void GenericProxyError(const char *title, const char *description); + void HostNotFound(std::string & host); + void SendProxyError(std::string & content); uint8_t m_recv_chunk[8192]; std::string m_recv_buf; // from client @@ -82,36 +99,39 @@ namespace proxy { Done(shared_from_this()); } - void HTTPReqHandler::HTTPRequestFailed(const char *message) + void HTTPReqHandler::GenericProxyError(const char *title, const char *description) { + std::stringstream ss; + ss << "

    Proxy error: " << title << "

    \r\n"; + ss << "

    " << description << "

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

    Proxy error: Host not found

    \r\n" + << "

    Remote host not found in router's addressbook

    \r\n" + << "

    You may try to find this host on jumpservices below:

    \r\n" + << "
    \r\n"; + std::string content = ss.str(); + SendProxyError(content); + } + + void HTTPReqHandler::SendProxyError(std::string & content) { i2p::http::HTTPRes res; res.code = 500; - res.add_header("Content-Type", "text/plain"); + res.add_header("Content-Type", "text/html; charset=UTF-8"); res.add_header("Connection", "close"); - res.body = message; - res.body += "\r\n"; - std::string response = res.to_string(); - boost::asio::async_write(*m_sock, boost::asio::buffer(response), - std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); - } - - void HTTPReqHandler::RedirectToJumpService(std::string & host) - { - i2p::http::HTTPRes res; - i2p::http::URL url; - - /* TODO: don't redirect to webconsole, it's not always work, handle jumpservices here */ - i2p::config::GetOption("http.address", url.host); - i2p::config::GetOption("http.port", url.port); - url.schema = "http"; - url.path = "/"; - url.query = "page=jumpservices&address="; - url.query += host; - - res.code = 302; /* redirect */ - res.add_header("Location", url.to_string().c_str()); - res.add_header("Connection", "close"); - + std::stringstream ss; + ss << "\r\n" << pageHead + << "" << content << "\r\n" + << "\r\n"; + res.body = ss.str(); std::string response = res.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(response), std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); @@ -181,7 +201,7 @@ namespace proxy { if (req_len < 0) { LogPrint(eLogError, "HTTPProxy: unable to parse request"); - HTTPRequestFailed("invalid request"); + GenericProxyError("Invalid request", "Proxy unable to parse your request"); return true; /* parse error */ } @@ -189,13 +209,14 @@ namespace proxy { LogPrint(eLogDebug, "HTTPProxy: requested: ", req.uri); url.parse(req.uri); - /* TODO: show notice page with original link */ if (ExtractAddressHelper(url, b64)) { i2p::client::context.GetAddressBook ().InsertAddress (url.host, b64); - std::string message = "added b64 from addresshelper for " + url.host + " to address book"; - LogPrint (eLogInfo, "HTTPProxy: ", message); - message += ", please reload page"; - HTTPRequestFailed(message.c_str()); + LogPrint (eLogInfo, "HTTPProxy: added b64 from addresshelper for ", url.host); + std::string full_url = url.to_string(); + std::stringstream ss; + ss << "Host " << url.host << " added to router's addressbook from helper. " + << "Click here to proceed."; + GenericProxyError("Addresshelper found", ss.str().c_str()); return true; /* request processed */ } @@ -224,8 +245,7 @@ namespace proxy { dest_port = u.port; } else { /* relative url and missing 'Host' header */ - const char *message = "Can't detect destination host from request"; - HTTPRequestFailed(message); + GenericProxyError("Invalid request", "Can't detect destination host from request"); return true; } @@ -233,14 +253,14 @@ namespace proxy { i2p::data::IdentHash identHash; if (str_rmatch(dest_host, ".i2p")) { if (!i2p::client::context.GetAddressBook ().GetIdentHash (dest_host, identHash)) { - RedirectToJumpService(dest_host); /* unknown host */ + HostNotFound(dest_host); return true; /* request processed */ } /* TODO: outproxy handler here */ } else { - std::string message = "Host " + url.host + " not inside i2p network, but outproxy support still missing"; - HTTPRequestFailed(message.c_str()); - LogPrint (eLogWarning, "HTTPProxy: ", message); + 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; } @@ -291,7 +311,7 @@ namespace proxy { { if (!stream) { LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); - HTTPRequestFailed("error when creating the stream, check logs"); + GenericProxyError("Host is down", "Can't create connection to requested host, it may be down"); return; } if (Kill()) From 728f2670f3f4359aa0b89ad879863d30a3a47400 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 14 Jul 2016 00:00:00 +0000 Subject: [PATCH 1620/6300] * HTTPServer.cpp : drop jumpservices : now handled by HTTPProxy itself --- HTTPServer.cpp | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index a98c1489..2be70cec 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -71,7 +71,6 @@ namespace http { const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions"; const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; - const char HTTP_PAGE_JUMPSERVICES[] = "jumpservices"; const char HTTP_PAGE_COMMANDS[] = "commands"; const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit"; const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit"; @@ -83,11 +82,6 @@ namespace http { const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; - std::map jumpservices = { - { "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" }, - { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, - }; - void ShowUptime (std::stringstream& s, int seconds) { int num; @@ -150,7 +144,6 @@ namespace http { " Transit tunnels
    \r\n" " Transports
    \r\n" " I2P tunnels
    \r\n" - " Jump services
    \r\n" " SAM sessions
    \r\n" "
  • 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 z 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

  • \r\n" "
    "; @@ -246,20 +239,6 @@ namespace http { s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
    \r\n"; } - void ShowJumpServices (std::stringstream& s, const std::string& address) - { - s << "
    "; - s << ""; - s << ""; - s << ""; - s << "

    \r\n"; - s << "Jump services for " << address << "\r\n\r\n"; - } - void ShowLocalDestinations (std::stringstream& s) { s << "Local Destinations:
    \r\n
    \r\n"; @@ -648,8 +627,6 @@ namespace http { ShowTunnels (s); else if (page == HTTP_PAGE_COMMANDS) ShowCommands (s); - else if (page == HTTP_PAGE_JUMPSERVICES) - ShowJumpServices (s, params["address"]); else if (page == HTTP_PAGE_TRANSIT_TUNNELS) ShowTransitTunnels (s); else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) From c4721e10204b2de432d3f9ba88911c4230f8a735 Mon Sep 17 00:00:00 2001 From: hagen Date: Thu, 14 Jul 2016 00:00:00 +0000 Subject: [PATCH 1621/6300] + HTTP.{cpp,h} : add HTTPRes::is_gzipped() --- HTTP.cpp | 12 ++++++++++++ HTTP.h | 3 +++ 2 files changed, 15 insertions(+) diff --git a/HTTP.cpp b/HTTP.cpp index 27421241..20f07448 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -278,6 +278,18 @@ namespace http { return false; } + 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"); + if (it == headers.end()) + return false; /* no header */ + if (it->second.find("gzip") != std::string::npos) + return true; /* gotcha! */ + return false; + } + long int HTTPMsg::content_length() { unsigned long int length = 0; auto it = headers.find("Content-Length"); diff --git a/HTTP.h b/HTTP.h index bce55026..847cf347 100644 --- a/HTTP.h +++ b/HTTP.h @@ -118,6 +118,9 @@ namespace http { /** @brief Checks that response declared as chunked data */ bool is_chunked(); + + /** @brief Checks that response contains compressed data */ + bool is_gzipped(); }; /** From 3e5581e094408e0c89061969351178031c1ce728 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 13 Jul 2016 22:33:39 -0400 Subject: [PATCH 1622/6300] create addresses in defualt constructor --- RouterInfo.cpp | 5 +++++ RouterInfo.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index b3d7c989..8e9f8865 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -15,6 +15,11 @@ namespace i2p { namespace data { + RouterInfo::RouterInfo (): m_Buffer (nullptr) + { + m_Addresses = std::make_shared(); // create empty list + } + RouterInfo::RouterInfo (const std::string& fullPath): m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0) diff --git a/RouterInfo.h b/RouterInfo.h index 78d2c3ab..a23c32e3 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -107,8 +107,8 @@ namespace data }; typedef std::vector > Addresses; + RouterInfo (); RouterInfo (const std::string& fullPath); - RouterInfo (): m_Buffer (nullptr) { }; RouterInfo (const RouterInfo& ) = default; RouterInfo& operator=(const RouterInfo& ) = default; RouterInfo (const uint8_t * buf, int len); From 562f320198717a8499e511813f6b0c51d1092c07 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 13 Jul 2016 22:34:53 -0400 Subject: [PATCH 1623/6300] load libstd --- android/src/org/purplei2p/i2pd/I2PD_JNI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/org/purplei2p/i2pd/I2PD_JNI.java b/android/src/org/purplei2p/i2pd/I2PD_JNI.java index 4f5913e3..5a3addbf 100644 --- a/android/src/org/purplei2p/i2pd/I2PD_JNI.java +++ b/android/src/org/purplei2p/i2pd/I2PD_JNI.java @@ -15,7 +15,7 @@ public class I2PD_JNI { public static native void onNetworkStateChanged(boolean isConnected); public static void loadLibraries() { - //System.loadLibrary("gnustl_shared"); + System.loadLibrary("gnustl_shared"); System.loadLibrary("i2pd"); } } From 3ad196c4c7bb9b0344c2d3c801964846eb8d01d6 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 14 Jul 2016 09:23:33 -0400 Subject: [PATCH 1624/6300] fix meshnet mode: * don't default to ipv4 when creating router.info * add i2p::util::config::GetHost for getting host to use from config * proper check for no transports in Transports.cpp on startup --- Daemon.cpp | 44 +++++++------------------------------------- RouterContext.cpp | 4 +--- Transports.cpp | 4 ++-- util.cpp | 25 +++++++++++++++++++++++++ util.h | 6 ++++++ 5 files changed, 41 insertions(+), 42 deletions(-) diff --git a/Daemon.cpp b/Daemon.cpp index 00bfd817..7dec30a9 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -124,47 +124,16 @@ namespace i2p ipv4 = false; ipv6 = true; #endif - - i2p::context.SetSupportsV6 (ipv6); - i2p::context.SetSupportsV4 (ipv4); - - bool nat; i2p::config::GetOption("nat", nat); - if (nat) - { - LogPrint(eLogInfo, "Daemon: assuming be are behind NAT"); - // we are behind nat, try setting via host - std::string host; i2p::config::GetOption("host", host); - if (!i2p::config::IsDefault("host")) - { - LogPrint(eLogInfo, "Daemon: setting address for incoming connections to ", host); - i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host)); - } - } - else - { - // we are not behind nat - std::string ifname; i2p::config::GetOption("ifname", ifname); - if (ifname.size()) - { - // bind to interface, we have no NAT so set external address too - auto addr = i2p::util::net::GetInterfaceAddress(ifname, ipv6); - LogPrint(eLogInfo, "Daemon: bind to network interface ", ifname, " with public address ", addr); - i2p::context.UpdateAddress(addr); - } - } - - uint16_t port; i2p::config::GetOption("port", port); if (!i2p::config::IsDefault("port")) - { + { LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); i2p::context.UpdatePort (port); - } - - - bool transit; i2p::config::GetOption("notransit", transit); + } i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); + + bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetAcceptsTunnels (!transit); uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); SetMaxNumTransitTunnels (transitTunnels); @@ -252,14 +221,15 @@ namespace i2p bool ntcp; i2p::config::GetOption("ntcp", ntcp); bool ssu; i2p::config::GetOption("ssu", ssu); LogPrint(eLogInfo, "Daemon: starting Transports"); - if(!ssu) LogPrint(eLogDebug, "Daemon: ssu disabled"); - if(!ntcp) LogPrint(eLogDebug, "Daemon: ntcp disabled"); + if(!ssu) LogPrint(eLogInfo, "Daemon: ssu disabled"); + if(!ntcp) LogPrint(eLogInfo, "Daemon: ntcp disabled"); i2p::transport::transports.Start(ntcp, ssu); if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU()) { LogPrint(eLogInfo, "Daemon: Transports started"); } else { LogPrint(eLogError, "Daemon: failed to start Transports"); /** shut down netdb right away */ + i2p::transport::transports.Stop(); i2p::data::netdb.Stop(); return false; } diff --git a/RouterContext.cpp b/RouterContext.cpp index 768750bb..bb3d0110 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -49,9 +49,7 @@ namespace i2p uint16_t port; i2p::config::GetOption("port", port); if (!port) port = rand () % (30777 - 9111) + 9111; // I2P network ports range - std::string host; i2p::config::GetOption("host", host); - if (i2p::config::IsDefault("host")) - host = "127.0.0.1"; // replace default address with safe value + std::string host = i2p::util::config::GetHost(); routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); routerInfo.AddNTCPAddress (host.c_str(), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | diff --git a/Transports.cpp b/Transports.cpp index 2d572aef..a54455cc 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -114,7 +114,7 @@ namespace transport auto& addresses = context.GetRouterInfo ().GetAddresses (); for (auto address : addresses) { - if (!m_NTCPServer && enableNTCP) + if (m_NTCPServer == nullptr && enableNTCP) { m_NTCPServer = new NTCPServer (); m_NTCPServer->Start (); @@ -129,7 +129,7 @@ namespace transport if (address->transportStyle == RouterInfo::eTransportSSU) { - if (!m_SSUServer && enableSSU) + if (m_SSUServer == nullptr && enableSSU) { if (address->host.is_v4()) m_SSUServer = new SSUServer (address->port); diff --git a/util.cpp b/util.cpp index 89bcda6c..5913d9a7 100644 --- a/util.cpp +++ b/util.cpp @@ -461,5 +461,30 @@ namespace net } } +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 7c393e02..8995f12b 100644 --- a/util.h +++ b/util.h @@ -68,6 +68,12 @@ 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); + } } } From d98d091c43c7a7769a1b6168f6bd4f3455584097 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 14 Jul 2016 14:10:38 -0400 Subject: [PATCH 1625/6300] use list instead vector for addresses --- RouterContext.cpp | 12 +++++----- RouterInfo.cpp | 56 ++++++++++++----------------------------------- RouterInfo.h | 3 ++- 3 files changed, 22 insertions(+), 49 deletions(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index 768750bb..21e26f94 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -224,11 +224,11 @@ 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) { - addresses.erase (addresses.begin () + i); + addresses.erase (it); break; } } @@ -253,12 +253,12 @@ 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) { // 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); From 7d04ba0fc3b0d155ec851094da30345a333ee184 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 14 Jul 2016 14:25:20 -0400 Subject: [PATCH 1626/6300] CXXFLAGS -> NEEDED_CXXFLAGS --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 22f016ea..147bedd4 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ else # win32 mingw endif ifeq ($(USE_MESHNET),yes) - CXXFLAGS += -DMESHNET + NEEDED_CXXFLAGS += -DMESHNET endif all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) From caf7da10538ef85afa4413c56c0f3c152e317373 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 14 Jul 2016 14:29:45 -0400 Subject: [PATCH 1627/6300] set reachable/unreachable for v4 only --- RouterContext.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index 21e26f94..02a344de 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -226,7 +226,8 @@ namespace i2p auto& addresses = m_RouterInfo.GetAddresses (); for (auto it = addresses.begin (); it != addresses.end (); it++) { - if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP) + if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP && + (*it)->host.is_v4 ()) { addresses.erase (it); break; @@ -255,7 +256,8 @@ namespace i2p auto& addresses = m_RouterInfo.GetAddresses (); for (auto addr : addresses) { - if (addr->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 (addr->host.to_string ().c_str (), addr->port); From 4fdd9feddc9a9221b3d2788da1c143e4861b7bff Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 14 Jul 2016 16:59:26 -0400 Subject: [PATCH 1628/6300] don't try to republish forever --- Destination.cpp | 4 ++-- NetDb.h | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index abfe3227..6e6d880f 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -459,7 +459,7 @@ namespace client // "this" added due to bug in gcc 4.7-4.8 [s,this](std::shared_ptr leaseSet) { - if (leaseSet && s->m_LeaseSet) + if (leaseSet) { // we got latest LeasetSet LogPrint (eLogDebug, "Destination: published LeaseSet verified for ", GetIdentHash().ToBase32()); @@ -470,7 +470,7 @@ namespace client else LogPrint (eLogWarning, "Destination: couldn't find published LeaseSet for ", GetIdentHash().ToBase32()); // we have to publish again - s->Publish (); + s->Publish (); }); } } diff --git a/NetDb.h b/NetDb.h index 3b54ae4c..bc7e8e7b 100644 --- a/NetDb.h +++ b/NetDb.h @@ -31,12 +31,7 @@ namespace data const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60; const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 hours - -#ifdef MESHNET - const int NETDB_PUBLISH_INTERVAL = 60; -#else const int NETDB_PUBLISH_INTERVAL = 60*40; -#endif class NetDb { From 71cc4b5bf24c90f65b01a58efb66355a0f97fa72 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 15 Jul 2016 09:38:21 -0400 Subject: [PATCH 1629/6300] add lease set page in http server --- HTTPServer.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++++ NetDb.cpp | 9 ++++++++ NetDb.h | 11 ++++++++-- RouterContext.cpp | 4 +++- 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 2be70cec..7da308cc 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -72,6 +72,7 @@ namespace http { const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; const char HTTP_PAGE_COMMANDS[] = "commands"; + const char HTTP_PAGE_LEASESETS[] = "leasesets"; const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit"; const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit"; const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start"; @@ -140,6 +141,7 @@ namespace http { " Main page
    \r\n
    \r\n" " Router commands
    \r\n" " Local destinations
    \r\n" + " Lease Sets
    \r\n" " Tunnels
    \r\n" " Transit tunnels
    \r\n" " Transports
    \r\n" @@ -327,6 +329,57 @@ namespace http { } } + void ShowLeasesSets(std::stringstream& s) + { + s << "
    LeaseSets

    "; + // for each lease set + i2p::data::netdb.VisitLeaseSets( + [&s](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) + { + // create copy of lease set so we extract leases + i2p::data::LeaseSet ls(leaseSet->GetBuffer(), leaseSet->GetBufferLen()); + // begin lease set entry + s << "
    "; + // invalid ? + if (!ls.IsValid()) + s << "
    !! Invalid !!
    "; + // ident + s << "
    " << dest.ToBase32() << "
    "; + // LeaseSet time + s << "
    expires: " << ls.GetExpirationTime() << "
    "; + // get non expired leases + auto leases = ls.GetNonExpiredLeases(); + // show non expired leases + s << "
    Non Expired Leases: " << leases.size() << "
    "; + // for each lease + s << "
    "; + for ( auto & l : leases ) + { + // begin lease + s << "
    "; + // gateway + s << "
    Gateway: " << l->tunnelGateway.ToBase64() << "
    "; + // tunnel id + s << "
    TunnelID: " << l->tunnelID << "
    "; + // end date + s << "
    EndDate: " << l->endDate << "
    "; + // end lease + s << "
    "; + } + // end for each lease + s << "
    "; + // end lease set entry + s << "
    "; + // linebreak + s << "
    "; + } + ); + // end for each lease set + } + void ShowTunnels (std::stringstream& s) { s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
    \r\n"; @@ -639,6 +692,8 @@ namespace http { ShowSAMSession (s, params["sam_id"]); else if (page == HTTP_PAGE_I2P_TUNNELS) ShowI2PTunnels (s); + else if (page == HTTP_PAGE_LEASESETS) + ShowLeasesSets(s); else { res.code = 400; ShowError(s, "Unknown page: " + page); diff --git a/NetDb.cpp b/NetDb.cpp index f89617be..beafeaee 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -213,6 +213,7 @@ namespace data bool NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr from) { + std::unique_lock lock(m_LeaseSetsMutex); bool updated = false; if (!from) // unsolicited LS must be received directly { @@ -264,6 +265,7 @@ namespace data std::shared_ptr NetDb::FindLeaseSet (const IdentHash& destination) const { + std::unique_lock lock(m_LeaseSetsMutex); auto it = m_LeaseSets.find (destination); if (it != m_LeaseSets.end ()) return it->second; @@ -318,6 +320,13 @@ namespace data return true; } + void NetDb::VisitLeaseSets(LeaseSetVisitor v) + { + std::unique_lock lock(m_LeaseSetsMutex); + for ( auto & entry : m_LeaseSets) + v(entry.first, entry.second); + } + void NetDb::Load () { // make sure we cleanup netDb from previous attempts diff --git a/NetDb.h b/NetDb.h index bc7e8e7b..c7371407 100644 --- a/NetDb.h +++ b/NetDb.h @@ -32,7 +32,10 @@ namespace data 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_PUBLISH_INTERVAL = 60*40; - + + /** function for visiting a leaseset stored in a floodfill */ + typedef std::function)> LeaseSetVisitor; + class NetDb { public: @@ -80,7 +83,10 @@ namespace data int GetNumRouters () const { return m_RouterInfos.size (); }; int GetNumFloodfills () const { return m_Floodfills.size (); }; int GetNumLeaseSets () const { return m_LeaseSets.size (); }; - + + /** visit all lease sets we currently store */ + void VisitLeaseSets(LeaseSetVisitor v); + private: void Load (); @@ -98,6 +104,7 @@ namespace data private: + mutable std::mutex m_LeaseSetsMutex; std::map > m_LeaseSets; mutable std::mutex m_RouterInfosMutex; std::map > m_RouterInfos; diff --git a/RouterContext.cpp b/RouterContext.cpp index bb3d0110..44b967e8 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -49,7 +49,9 @@ namespace i2p uint16_t port; i2p::config::GetOption("port", port); if (!port) port = rand () % (30777 - 9111) + 9111; // I2P network ports range - std::string host = i2p::util::config::GetHost(); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + std::string host = i2p::util::config::GetHost(ipv4, ipv6); routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); routerInfo.AddNTCPAddress (host.c_str(), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | From e298987d9edf40b2ef092104b65a25b79a930b71 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 15 Jul 2016 09:45:54 -0400 Subject: [PATCH 1630/6300] fixed build error --- UPnP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPnP.cpp b/UPnP.cpp index fbb8639b..74f64467 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -48,7 +48,7 @@ namespace transport void UPnP::Run () { - const std::vector > a = context.GetRouterInfo().GetAddresses(); + auto a = context.GetRouterInfo().GetAddresses(); for (auto address : a) { if (!address->host.is_v6 ()) From 586f241074283efde86c68d05b4b574ef0d686d7 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 15 Jul 2016 10:44:08 -0400 Subject: [PATCH 1631/6300] OFF BY ONE --- NetDb.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index beafeaee..1a1b7cfd 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -748,7 +748,8 @@ namespace data uint8_t numTags = sessionKey[32]; if (numTags > 0) { - const uint8_t * sessionTag = sessionKey + 33; // take first tag + sessionKey ++; + const uint8_t * sessionTag = sessionKey + 32; // take first tag i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag); replyMsg = garlic.WrapSingleMessage (replyMsg); } From b9607b4b8ee5ebaff17b5ddee655eb1b52500635 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 15 Jul 2016 10:44:43 -0400 Subject: [PATCH 1632/6300] correct last commit --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 1a1b7cfd..6dc13a87 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -748,7 +748,7 @@ namespace data uint8_t numTags = sessionKey[32]; if (numTags > 0) { - sessionKey ++; + sessionKey += 32; const uint8_t * sessionTag = sessionKey + 32; // take first tag i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag); replyMsg = garlic.WrapSingleMessage (replyMsg); From 338b9928f05e366a4a7defb1785091eeffead2d6 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 15 Jul 2016 10:45:38 -0400 Subject: [PATCH 1633/6300] repeat correction --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 6dc13a87..32d71d7e 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -748,7 +748,7 @@ namespace data uint8_t numTags = sessionKey[32]; if (numTags > 0) { - sessionKey += 32; + sessionKey += 33; const uint8_t * sessionTag = sessionKey + 32; // take first tag i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag); replyMsg = garlic.WrapSingleMessage (replyMsg); From 24aff1575253872b8b2ac3e214b5b1158e9a7bca Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 15 Jul 2016 10:55:02 -0400 Subject: [PATCH 1634/6300] off by one --- NetDb.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 32d71d7e..beafeaee 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -748,8 +748,7 @@ namespace data uint8_t numTags = sessionKey[32]; if (numTags > 0) { - sessionKey += 33; - const uint8_t * sessionTag = sessionKey + 32; // take first tag + const uint8_t * sessionTag = sessionKey + 33; // take first tag i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag); replyMsg = garlic.WrapSingleMessage (replyMsg); } From 75fc8202ab4f1b0f73cb07dbb65884ea71af9c56 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 15 Jul 2016 12:49:45 -0400 Subject: [PATCH 1635/6300] fix off by ones --- I2NPProtocol.cpp | 4 ++-- I2NPProtocol.h | 2 +- NetDb.cpp | 22 ++++++++++++++-------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 1b2d0317..1a64bb9c 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -165,7 +165,7 @@ namespace i2p buf += 32; memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW buf += 32; - *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags + *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCRYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags htobe32buf (buf + 1, replyTunnel->GetNextTunnelID ()); // reply tunnel ID buf += 5; @@ -182,7 +182,7 @@ namespace i2p } // encryption memcpy (buf, replyKey, 32); - buf[32] = 1; // 1 tag + buf[32] = (uint8_t) 1; // 1 tag memcpy (buf + 33, replyTag, 32); buf += 65; diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 1ade55ed..76871114 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -90,7 +90,7 @@ namespace i2p // DatabaseLookup flags const uint8_t DATABASE_LOOKUP_DELIVERY_FLAG = 0x01; - const uint8_t DATABASE_LOOKUP_ENCYPTION_FLAG = 0x02; + const uint8_t DATABASE_LOOKUP_ENCRYPTION_FLAG = 0x02; const uint8_t DATABASE_LOOKUP_TYPE_FLAGS_MASK = 0x0C; const uint8_t DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP = 0; const uint8_t DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP = 0x04; // 0100 diff --git a/NetDb.cpp b/NetDb.cpp index beafeaee..c5b02615 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -632,17 +632,18 @@ namespace data char key[48]; int l = i2p::data::ByteStreamToBase64 (buf, 32, key, 48); key[l] = 0; - uint8_t flag = buf[64]; IdentHash replyIdent(buf + 32); - + uint8_t flag = buf[64]; + + LogPrint (eLogDebug, "NetDb: DatabaseLookup for ", key, " recieved flags=", (int)flag); uint8_t lookupType = flag & DATABASE_LOOKUP_TYPE_FLAGS_MASK; const uint8_t * excluded = buf + 65; uint32_t replyTunnelID = 0; if (flag & DATABASE_LOOKUP_DELIVERY_FLAG) //reply to tunnel { - replyTunnelID = bufbe32toh (buf + 65); + replyTunnelID = bufbe32toh (excluded); excluded += 4; } uint16_t numExcluded = bufbe16toh (excluded); @@ -650,7 +651,7 @@ namespace data if (numExcluded > 512) { LogPrint (eLogWarning, "NetDb: number of excluded peers", numExcluded, " exceeds 512"); - numExcluded = 0; // TODO: + return; } std::shared_ptr replyMsg; @@ -733,6 +734,8 @@ namespace data if (!numExcluded) // save if no excluded m_LookupResponses[ident] = std::make_pair(closestFloodfills, i2p::util::GetSecondsSinceEpoch ()); } + else + excluded += numExcluded * 32; replyMsg = CreateDatabaseSearchReply (ident, closestFloodfills); } } @@ -742,16 +745,19 @@ namespace data if (replyTunnelID) { // encryption might be used though tunnel only - if (flag & DATABASE_LOOKUP_ENCYPTION_FLAG) // encrypted reply requested + if (flag & DATABASE_LOOKUP_ENCRYPTION_FLAG) // encrypted reply requested { const uint8_t * sessionKey = excluded; - uint8_t numTags = sessionKey[32]; - if (numTags > 0) + const uint8_t numTags = excluded[32]; + if (numTags) { - const uint8_t * sessionTag = sessionKey + 33; // take first tag + const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag); replyMsg = garlic.WrapSingleMessage (replyMsg); + if(replyMsg == nullptr) LogPrint(eLogError, "NetDb: failed to wrap message"); } + else + LogPrint(eLogWarning, "NetDb: encrypted reply requested but no tags provided"); } auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; From d37482ada12bf8321b0d0c785bc3ce32d1671e26 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 15 Jul 2016 13:31:31 -0400 Subject: [PATCH 1636/6300] IT WORKS, floodfill confirmed working on test network --- I2NPProtocol.cpp | 9 +++++---- NetDb.cpp | 15 +++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 1a64bb9c..1ac178e1 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -165,9 +165,10 @@ namespace i2p buf += 32; memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW buf += 32; - *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCRYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags - htobe32buf (buf + 1, replyTunnel->GetNextTunnelID ()); // reply tunnel ID - buf += 5; + *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCRYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags + buf ++; + htobe32buf (buf, replyTunnel->GetNextTunnelID ()); // reply tunnel ID + buf += 4; // excluded htobe16buf (buf, cnt); @@ -182,7 +183,7 @@ namespace i2p } // encryption memcpy (buf, replyKey, 32); - buf[32] = (uint8_t) 1; // 1 tag + buf[32] = uint8_t( 1 ); // 1 tag memcpy (buf + 33, replyTag, 32); buf += 65; diff --git a/NetDb.cpp b/NetDb.cpp index c5b02615..645bc85f 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -724,22 +724,21 @@ namespace data } if (!found) { - std::set excludedRouters; + std::set excLudedrouters; + const uint8_t * exclude_ident = excluded; for (int i = 0; i < numExcluded; i++) { - excludedRouters.insert (excluded); - excluded += 32; + excludedRouters.insert (exclude_ident); + exclude_ident += 32; } closestFloodfills = GetClosestFloodfills (ident, 3, excludedRouters, true); if (!numExcluded) // save if no excluded m_LookupResponses[ident] = std::make_pair(closestFloodfills, i2p::util::GetSecondsSinceEpoch ()); } - else - excluded += numExcluded * 32; replyMsg = CreateDatabaseSearchReply (ident, closestFloodfills); - } + } } - + excluded += numExcluded * 32; if (replyMsg) { if (replyTunnelID) @@ -751,7 +750,7 @@ namespace data const uint8_t numTags = excluded[32]; if (numTags) { - const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag + const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag); replyMsg = garlic.WrapSingleMessage (replyMsg); if(replyMsg == nullptr) LogPrint(eLogError, "NetDb: failed to wrap message"); From 84bb740e6236eb14a75460c04d5878ef75cf9314 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 15 Jul 2016 13:52:55 -0400 Subject: [PATCH 1637/6300] clean up code --- Destination.cpp | 2 +- HTTPServer.cpp | 110 +++++++++++++++++++++++----------------------- NetDb.cpp | 20 ++++----- NetDb.h | 14 +++--- RouterContext.cpp | 6 +-- 5 files changed, 76 insertions(+), 76 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 6e6d880f..2df14a9f 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -459,7 +459,7 @@ namespace client // "this" added due to bug in gcc 4.7-4.8 [s,this](std::shared_ptr leaseSet) { - if (leaseSet) + if (leaseSet && s->m_LeaseSet) { // we got latest LeasetSet LogPrint (eLogDebug, "Destination: published LeaseSet verified for ", GetIdentHash().ToBase32()); diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 7da308cc..566ad53e 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -72,7 +72,7 @@ namespace http { const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; const char HTTP_PAGE_COMMANDS[] = "commands"; - const char HTTP_PAGE_LEASESETS[] = "leasesets"; + const char HTTP_PAGE_LEASESETS[] = "leasesets"; const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit"; const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit"; const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start"; @@ -141,7 +141,7 @@ namespace http { " Main page
    \r\n
    \r\n" " Router commands
    \r\n" " Local destinations
    \r\n" - " Lease Sets
    \r\n" + " Lease Sets
    \r\n" " Tunnels
    \r\n" " Transit tunnels
    \r\n" " Transports
    \r\n" @@ -329,57 +329,57 @@ namespace http { } } - void ShowLeasesSets(std::stringstream& s) - { - s << "
    LeaseSets

    "; - // for each lease set - i2p::data::netdb.VisitLeaseSets( - [&s](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) - { - // create copy of lease set so we extract leases - i2p::data::LeaseSet ls(leaseSet->GetBuffer(), leaseSet->GetBufferLen()); - // begin lease set entry - s << "
    "; - // invalid ? - if (!ls.IsValid()) - s << "
    !! Invalid !!
    "; - // ident - s << "
    " << dest.ToBase32() << "
    "; - // LeaseSet time - s << "
    expires: " << ls.GetExpirationTime() << "
    "; - // get non expired leases - auto leases = ls.GetNonExpiredLeases(); - // show non expired leases - s << "
    Non Expired Leases: " << leases.size() << "
    "; - // for each lease - s << "
    "; - for ( auto & l : leases ) - { - // begin lease - s << "
    "; - // gateway - s << "
    Gateway: " << l->tunnelGateway.ToBase64() << "
    "; - // tunnel id - s << "
    TunnelID: " << l->tunnelID << "
    "; - // end date - s << "
    EndDate: " << l->endDate << "
    "; - // end lease - s << "
    "; - } - // end for each lease - s << "
    "; - // end lease set entry - s << "
    "; - // linebreak - s << "
    "; - } - ); - // end for each lease set - } - + void ShowLeasesSets(std::stringstream& s) + { + s << "
    LeaseSets

    "; + // for each lease set + i2p::data::netdb.VisitLeaseSets( + [&s](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) + { + // create copy of lease set so we extract leases + i2p::data::LeaseSet ls(leaseSet->GetBuffer(), leaseSet->GetBufferLen()); + // begin lease set entry + s << "
    "; + // invalid ? + if (!ls.IsValid()) + s << "
    !! Invalid !!
    "; + // ident + s << "
    " << dest.ToBase32() << "
    "; + // LeaseSet time + s << "
    expires: " << ls.GetExpirationTime() << "
    "; + // get non expired leases + auto leases = ls.GetNonExpiredLeases(); + // show non expired leases + s << "
    Non Expired Leases: " << leases.size() << "
    "; + // for each lease + s << "
    "; + for ( auto & l : leases ) + { + // begin lease + s << "
    "; + // gateway + s << "
    Gateway: " << l->tunnelGateway.ToBase64() << "
    "; + // tunnel id + s << "
    TunnelID: " << l->tunnelID << "
    "; + // end date + s << "
    EndDate: " << l->endDate << "
    "; + // end lease + s << "
    "; + } + // end for each lease + s << "
    "; + // end lease set entry + s << "
    "; + // linebreak + s << "
    "; + } + ); + // end for each lease set + } + void ShowTunnels (std::stringstream& s) { s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
    \r\n"; @@ -692,8 +692,8 @@ namespace http { ShowSAMSession (s, params["sam_id"]); else if (page == HTTP_PAGE_I2P_TUNNELS) ShowI2PTunnels (s); - else if (page == HTTP_PAGE_LEASESETS) - ShowLeasesSets(s); + else if (page == HTTP_PAGE_LEASESETS) + ShowLeasesSets(s); else { res.code = 400; ShowError(s, "Unknown page: " + page); diff --git a/NetDb.cpp b/NetDb.cpp index 645bc85f..1710b73a 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -213,7 +213,7 @@ namespace data bool NetDb::AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr from) { - std::unique_lock lock(m_LeaseSetsMutex); + std::unique_lock lock(m_LeaseSetsMutex); bool updated = false; if (!from) // unsolicited LS must be received directly { @@ -265,7 +265,7 @@ namespace data std::shared_ptr NetDb::FindLeaseSet (const IdentHash& destination) const { - std::unique_lock lock(m_LeaseSetsMutex); + std::unique_lock lock(m_LeaseSetsMutex); auto it = m_LeaseSets.find (destination); if (it != m_LeaseSets.end ()) return it->second; @@ -320,13 +320,13 @@ namespace data return true; } - void NetDb::VisitLeaseSets(LeaseSetVisitor v) - { - std::unique_lock lock(m_LeaseSetsMutex); - for ( auto & entry : m_LeaseSets) - v(entry.first, entry.second); - } - + void NetDb::VisitLeaseSets(LeaseSetVisitor v) + { + std::unique_lock lock(m_LeaseSetsMutex); + for ( auto & entry : m_LeaseSets) + v(entry.first, entry.second); + } + void NetDb::Load () { // make sure we cleanup netDb from previous attempts @@ -750,7 +750,7 @@ namespace data const uint8_t numTags = excluded[32]; if (numTags) { - const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag + const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag i2p::garlic::GarlicRoutingSession garlic (sessionKey, sessionTag); replyMsg = garlic.WrapSingleMessage (replyMsg); if(replyMsg == nullptr) LogPrint(eLogError, "NetDb: failed to wrap message"); diff --git a/NetDb.h b/NetDb.h index c7371407..43b069b9 100644 --- a/NetDb.h +++ b/NetDb.h @@ -33,9 +33,9 @@ namespace data const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 hours const int NETDB_PUBLISH_INTERVAL = 60*40; - /** function for visiting a leaseset stored in a floodfill */ - typedef std::function)> LeaseSetVisitor; - + /** function for visiting a leaseset stored in a floodfill */ + typedef std::function)> LeaseSetVisitor; + class NetDb { public: @@ -84,9 +84,9 @@ namespace data int GetNumFloodfills () const { return m_Floodfills.size (); }; int GetNumLeaseSets () const { return m_LeaseSets.size (); }; - /** visit all lease sets we currently store */ - void VisitLeaseSets(LeaseSetVisitor v); - + /** visit all lease sets we currently store */ + void VisitLeaseSets(LeaseSetVisitor v); + private: void Load (); @@ -104,7 +104,7 @@ namespace data private: - mutable std::mutex m_LeaseSetsMutex; + mutable std::mutex m_LeaseSetsMutex; std::map > m_LeaseSets; mutable std::mutex m_RouterInfosMutex; std::map > m_RouterInfos; diff --git a/RouterContext.cpp b/RouterContext.cpp index 44b967e8..b2d4f073 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -49,10 +49,10 @@ namespace i2p uint16_t port; i2p::config::GetOption("port", port); if (!port) port = rand () % (30777 - 9111) + 9111; // I2P network ports range - bool ipv4; i2p::config::GetOption("ipv4", ipv4); - bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); std::string host = i2p::util::config::GetHost(ipv4, ipv6); - routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); routerInfo.AddNTCPAddress (host.c_str(), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC From a5d84bf8a9d4f947d97fa45ccf9d6fb54487594c Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 15 Jul 2016 13:54:34 -0400 Subject: [PATCH 1638/6300] pedantic whitespace fix --- I2NPProtocol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 1ac178e1..92ab8281 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -166,7 +166,7 @@ namespace i2p memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW buf += 32; *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCRYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags - buf ++; + buf ++; htobe32buf (buf, replyTunnel->GetNextTunnelID ()); // reply tunnel ID buf += 4; From a0144f093fdf8cdf9ed37daa2e88738d94601992 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 15 Jul 2016 13:59:56 -0400 Subject: [PATCH 1639/6300] fix typo --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 1710b73a..f5b51064 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -724,7 +724,7 @@ namespace data } if (!found) { - std::set excLudedrouters; + std::set excludedRouters; const uint8_t * exclude_ident = excluded; for (int i = 0; i < numExcluded; i++) { From 83db86854214ad7ddfbebb7f5f5f7c520245b06b Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 16 Jul 2016 00:00:00 +0000 Subject: [PATCH 1640/6300] * Addressbook.cpp : use new URL class, unwrap some conditions --- AddressBook.cpp | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 3cf2bc56..e3bf9745 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" @@ -637,13 +638,21 @@ namespace client void AddressBookSubscription::Request () { - // 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)) - { + i2p::http::URL url; + // must be run in separate thread + LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link); + if (!url.parse(m_Link)) { + LogPrint(eLogError, "Addressbook: failed to parse url: ", m_Link); + return; + } + if (!m_Book.GetIdentHash (url.host, ident)) { + LogPrint (eLogError, "Addressbook: Can't resolve ", url.host); + return; + } + + /* all code below till end of function is subject of refacroring */ + bool success = false; if (!m_Etag.length ()) { // load ETag @@ -671,9 +680,14 @@ namespace client if (leaseSet) { std::stringstream request, response; + std::string host = url.host; + int port = url.port ? url.port : 80; + /* make relative url */ + url.schema = ""; + url.host = ""; // standard header - request << "GET " << u.path_ << " HTTP/1.1\r\n" - << "Host: " << u.host_ << "\r\n" + request << "GET " << url.to_string() << " HTTP/1.1\r\n" + << "Host: " << url.host << "\r\n" << "Accept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n" //<< "Accept-Encoding: gzip\r\n" @@ -684,7 +698,7 @@ namespace client 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_); + auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, port); stream->Send ((uint8_t *)request.str ().c_str (), request.str ().length ()); uint8_t buf[4096]; @@ -766,10 +780,7 @@ namespace client LogPrint (eLogWarning, "Adressbook: HTTP response ", status); } else - LogPrint (eLogError, "Addressbook: address ", u.host_, " not found"); - } - else - LogPrint (eLogError, "Addressbook: Can't resolve ", u.host_); + LogPrint (eLogError, "Addressbook: address ", url.host, " not found"); if (!success) LogPrint (eLogError, "Addressbook: download hosts.txt from ", m_Link, " failed"); From 403e34506e7db11259b34b12fb05f0fd9860130a Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 16 Jul 2016 00:00:00 +0000 Subject: [PATCH 1641/6300] * Addressbook.cpp : use HTTPReq class for building request --- AddressBook.cpp | 100 ++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index e3bf9745..e260fc13 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -650,56 +650,57 @@ namespace client LogPrint (eLogError, "Addressbook: Can't resolve ", url.host); return; } - + /* this code block still needs some love */ + 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 (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); + return; + } + } + if (!leaseSet) { + /* still no leaseset found */ + LogPrint (eLogError, "Addressbook: LeaseSet for address ", url.host, " not found"); + return; + } + if (m_Etag.empty() && m_LastModified.empty()) { + m_Book.GetEtag (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()); /* all code below till end of function is subject of refacroring */ bool success = false; - 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 (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); - } - } - if (leaseSet) - { - std::stringstream request, response; - std::string host = url.host; - int port = url.port ? url.port : 80; - /* make relative url */ - url.schema = ""; - url.host = ""; - // standard header - request << "GET " << url.to_string() << " HTTP/1.1\r\n" - << "Host: " << url.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, port); - stream->Send ((uint8_t *)request.str ().c_str (), request.str ().length ()); + std::stringstream response; uint8_t buf[4096]; bool end = false; @@ -778,9 +779,6 @@ namespace client } else LogPrint (eLogWarning, "Adressbook: HTTP response ", status); - } - else - LogPrint (eLogError, "Addressbook: address ", url.host, " not found"); if (!success) LogPrint (eLogError, "Addressbook: download hosts.txt from ", m_Link, " failed"); From b6c336bf72141c0155711e7b60abb3a2d84be137 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 16 Jul 2016 00:00:00 +0000 Subject: [PATCH 1642/6300] * Addressbook.cpp : use HTTPRes class for response parsing --- AddressBook.cpp | 167 ++++++++++++++++++++---------------------------- AddressBook.h | 1 - 2 files changed, 68 insertions(+), 100 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index e260fc13..baba9b1a 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -698,108 +698,77 @@ namespace client 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()); - /* all code below till end of function is subject of refacroring */ - bool success = false; - std::stringstream response; - - uint8_t buf[4096]; - bool end = false; - while (!end) + /* 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) { - 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 - { - 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); - - 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; + 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"); + } + // 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; + } + if (res_head_len == 0) { + LogPrint(eLogError, "Addressbook: incomplete http response from ", dest_host, ", interrupted by timeout"); + return; + } + /* 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; + } + if (res.code != 200) { + LogPrint (eLogWarning, "Adressbook: can't get updates from ", dest_host, ", response code ", res.code); + return; + } + /* 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); + m_Book.DownloadComplete (true, ident, m_Etag, m_LastModified); } AddressResolver::AddressResolver (std::shared_ptr destination): diff --git a/AddressBook.h b/AddressBook.h index d67089fa..615c07ad 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -117,7 +117,6 @@ namespace client private: void Request (); - bool ProcessResponse (std::stringstream& s, bool isGzip = false); private: From cbfb1edb793d532d34d81007fbdf7742b20e9a2e Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 16 Jul 2016 00:00:00 +0000 Subject: [PATCH 1643/6300] * Addressbook.{cpp,h}: * Request() now renamed and returns value * move spawning download thread to Addressbook class * CheckSubscription() renamed and handles return value of Request() --- AddressBook.cpp | 51 +++++++++++++++++++++++++++++-------------------- AddressBook.h | 5 +++-- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index baba9b1a..5fe7d0f3 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -517,14 +517,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 @@ -630,34 +631,33 @@ 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::data::IdentHash ident; i2p::http::URL url; // must be run in separate thread LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link); if (!url.parse(m_Link)) { LogPrint(eLogError, "Addressbook: failed to parse url: ", m_Link); - return; + return false; } - if (!m_Book.GetIdentHash (url.host, ident)) { + if (!m_Book.GetIdentHash (url.host, m_Ident)) { LogPrint (eLogError, "Addressbook: Can't resolve ", url.host); - return; + return false; } /* this code block still needs some love */ std::condition_variable newDataReceived; std::mutex newDataReceivedMutex; - auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (ident); + auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (m_Ident); if (!leaseSet) { std::unique_lock l(newDataReceivedMutex); - i2p::client::context.GetSharedLocalDestination ()->RequestDestination (ident, + i2p::client::context.GetSharedLocalDestination ()->RequestDestination (m_Ident, [&newDataReceived, &leaseSet](std::shared_ptr ls) { leaseSet = ls; @@ -666,17 +666,17 @@ namespace client 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); - return; + i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (m_Ident); + return false; } } if (!leaseSet) { /* still no leaseset found */ LogPrint (eLogError, "Addressbook: LeaseSet for address ", url.host, " not found"); - return; + return false; } if (m_Etag.empty() && m_LastModified.empty()) { - m_Book.GetEtag (ident, m_Etag, m_LastModified); + 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 */ @@ -726,21 +726,30 @@ namespace client int res_head_len = res.parse(response); if (res_head_len < 0) { LogPrint(eLogError, "Addressbook: can't parse http response from ", dest_host); - return; + return false; } if (res_head_len == 0) { LogPrint(eLogError, "Addressbook: incomplete http response from ", dest_host, ", interrupted by timeout"); - return; + 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; + return false; } if (res.code != 200) { LogPrint (eLogWarning, "Adressbook: can't get updates from ", dest_host, ", response code ", res.code); - return; + 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"); @@ -768,7 +777,7 @@ namespace client std::stringstream ss(response); LogPrint (eLogInfo, "Addressbook: got update from ", dest_host); m_Book.LoadHostsFromStream (ss); - m_Book.DownloadComplete (true, ident, m_Etag, m_LastModified); + return true; } AddressResolver::AddressResolver (std::shared_ptr destination): diff --git a/AddressBook.h b/AddressBook.h index 615c07ad..514187fc 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -112,16 +112,17 @@ namespace client public: AddressBookSubscription (AddressBook& book, const std::string& link); - void CheckSubscription (); + void CheckUpdates (); private: - void Request (); + 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 "" }; From 9f3ce09e8812c7ea00c223d57ec71cad3c6b53d7 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 16 Jul 2016 00:00:00 +0000 Subject: [PATCH 1644/6300] * Addressbook.{cpp,h} : show new hosts --- AddressBook.cpp | 23 ++++++++++++----------- AddressBook.h | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 5fe7d0f3..ce0873fd 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -340,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; @@ -366,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 (); @@ -776,7 +777,7 @@ namespace client } std::stringstream ss(response); LogPrint (eLogInfo, "Addressbook: got update from ", dest_host); - m_Book.LoadHostsFromStream (ss); + m_Book.LoadHostsFromStream (ss, true); return true; } diff --git a/AddressBook.h b/AddressBook.h index 514187fc..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); } From d16afa96922d9064f18a57a5005dc83e3843757b Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 16 Jul 2016 00:00:00 +0000 Subject: [PATCH 1645/6300] * Addressbook.cpp : tune logs --- AddressBook.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index ce0873fd..cd15d68d 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -572,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; } @@ -588,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); @@ -605,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); @@ -809,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 @@ -817,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 From ecc82739d81202e05c7a32d23db9304ef9274e12 Mon Sep 17 00:00:00 2001 From: hagen Date: Sat, 16 Jul 2016 00:00:00 +0000 Subject: [PATCH 1646/6300] * HTTP.cpp : fix is_gzipped() --- HTTP.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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) From e1bf53d90a54732f1bf55c598a50a12b0257cad6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 16 Jul 2016 09:31:33 -0400 Subject: [PATCH 1647/6300] handle status command --- BOB.cpp | 10 ++++++++++ BOB.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/BOB.cpp b/BOB.cpp index c074b53b..b241e740 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -545,6 +545,15 @@ namespace client else SendReplyError ("malformed"); } + + void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len) + { + LogPrint (eLogDebug, "BOB: status ", operand); + if (m_Owner.FindDestination (operand)) + SendReplyOK ("") + else + SendReplyError ("no nickname has been set"); + } BOBCommandChannel::BOBCommandChannel (const std::string& address, int port): m_IsRunning (false), m_Thread (nullptr), @@ -570,6 +579,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..7e6696a7 100644 --- a/BOB.h +++ b/BOB.h @@ -36,6 +36,7 @@ namespace client const char BOB_COMMAND_CLEAR[] = "clear"; const char BOB_COMMAND_LIST[] = "list"; const char BOB_COMMAND_OPTION[] = "option"; + const char BOB_COMMAND_STATUS[] = "status"; const char BOB_VERSION[] = "BOB 00.00.10\nOK\n"; const char BOB_REPLY_OK[] = "OK %s\n"; @@ -170,6 +171,7 @@ namespace client void ClearCommandHandler (const char * operand, size_t len); void ListCommandHandler (const char * operand, size_t len); void OptionCommandHandler (const char * operand, size_t len); + void StatusCommandHandler (const char * operand, size_t len); private: From fba53117d8b2f4c30da30958dd4bf792d3bbab5e Mon Sep 17 00:00:00 2001 From: Ricardo Jesus Malagon Jerez Date: Sat, 16 Jul 2016 09:21:01 -0500 Subject: [PATCH 1648/6300] Update BOB.cpp Fix a little typo --- BOB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BOB.cpp b/BOB.cpp index b241e740..ab983866 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -550,7 +550,7 @@ namespace client { LogPrint (eLogDebug, "BOB: status ", operand); if (m_Owner.FindDestination (operand)) - SendReplyOK ("") + SendReplyOK (""); else SendReplyError ("no nickname has been set"); } From 2c099c7f0e28d7ceeaa32a3755d96fc83e1ef776 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 16 Jul 2016 10:21:07 -0400 Subject: [PATCH 1649/6300] Update BOB.cpp fixed typo --- BOB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BOB.cpp b/BOB.cpp index b241e740..ab983866 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -550,7 +550,7 @@ namespace client { LogPrint (eLogDebug, "BOB: status ", operand); if (m_Owner.FindDestination (operand)) - SendReplyOK ("") + SendReplyOK (""); else SendReplyError ("no nickname has been set"); } From b7c5e3b5d5d64f4372a11b42b5dfa6734655573e Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 Jul 2016 14:01:58 -0400 Subject: [PATCH 1650/6300] correct termination of UPnP --- UPnP.cpp | 169 +++++++++++++++++++++++++++++++------------------------ UPnP.h | 24 +++++--- 2 files changed, 110 insertions(+), 83 deletions(-) diff --git a/UPnP.cpp b/UPnP.cpp index 74f64467..d9a5c68d 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -21,49 +21,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 () { - auto 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 +92,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 - } - } - catch (boost::thread_interrupted) + 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) { - CloseMapping(type, port); - Close(); - throw; + 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; } } - 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 +171,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 Run (); - std::thread * m_Thread; + 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); + + 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; From 26be0c7c82cab1b92e3d99861702d5cfc7f16b3b Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 19 Jul 2016 00:00:00 +0000 Subject: [PATCH 1651/6300] * move i2p::util::config::GetHost to RouterContext.cpp --- RouterContext.cpp | 11 ++++++++++- util.cpp | 27 --------------------------- util.h | 7 ------- 3 files changed, 10 insertions(+), 35 deletions(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index f61aaed8..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 | 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 From 25ba08abcfe09f35b541062f667a366ec4c57ffd Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 19 Jul 2016 00:00:00 +0000 Subject: [PATCH 1652/6300] * Reseed.cpp : use new http classes --- Reseed.cpp | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index 096035da..e8969583 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,12 +397,12 @@ 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; @@ -412,10 +419,10 @@ namespace data 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 ""; } } From 19f3c75a8d5eeae3e7a531d050bf4067bd481242 Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 19 Jul 2016 00:00:00 +0000 Subject: [PATCH 1653/6300] * Reseed.cpp : use new response parsing --- Reseed.cpp | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index e8969583..1091ef0a 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -15,7 +15,6 @@ #include "Identity.h" #include "NetDb.h" #include "HTTP.h" -#include "util.h" namespace i2p { @@ -405,15 +404,31 @@ namespace data 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 ""; + } + 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 ()); From ea7f4447b2c632fc27918e70debf3365933a7abf Mon Sep 17 00:00:00 2001 From: hagen Date: Tue, 19 Jul 2016 00:00:00 +0000 Subject: [PATCH 1654/6300] * Reseed.cpp : check response code --- Reseed.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Reseed.cpp b/Reseed.cpp index 1091ef0a..66609414 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -417,6 +417,10 @@ namespace data 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()) { From 340c73cbdfcd15a2362b383ed3488ec5b051a5b1 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 19 Jul 2016 11:08:28 -0400 Subject: [PATCH 1655/6300] send actual status back --- BOB.cpp | 49 +++++++++++++++++++++++-------------------------- BOB.h | 4 ---- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index ab983866..f983c67a 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -202,7 +202,7 @@ namespace client } BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner): - m_Owner (owner), m_Socket (m_Owner.GetService ()), m_Timer (m_Owner.GetService ()), + m_Owner (owner), m_Socket (m_Owner.GetService ()), m_ReceiveBufferOffset (0), m_IsOpen (true), m_IsQuiet (false), m_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr) { @@ -364,30 +364,8 @@ 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"); } - - 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) { @@ -549,8 +527,27 @@ namespace client void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: status ", operand); - if (m_Owner.FindDestination (operand)) - SendReplyOK (""); + 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"); } diff --git a/BOB.h b/BOB.h index 7e6696a7..d2118c5c 100644 --- a/BOB.h +++ b/BOB.h @@ -43,8 +43,6 @@ namespace client 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: @@ -177,7 +175,6 @@ namespace client 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); @@ -189,7 +186,6 @@ 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; From 77493d0d097527657d478191e31c876ed98602ad Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 19 Jul 2016 12:03:03 -0400 Subject: [PATCH 1656/6300] configurable UPnP name --- Config.cpp | 1 + UPnP.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Config.cpp b/Config.cpp index 10330c34..9e18ff55 100644 --- a/Config.cpp +++ b/Config.cpp @@ -134,6 +134,7 @@ namespace config { 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/UPnP.cpp b/UPnP.cpp index d9a5c68d..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 @@ -143,7 +144,7 @@ namespace transport { std::string strType (GetProto (address)), strPort (std::to_string (address->port)); int r; - std::string strDesc = "I2Pd"; + 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) { From 3f2119556fabffd28e91087ad880e87bfe5c002a Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 19 Jul 2016 12:05:32 -0400 Subject: [PATCH 1657/6300] upnp.name --- docs/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 61e6a03d70c2e8af5834c148f82e8361591398e5 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 20 Jul 2016 09:33:50 -0400 Subject: [PATCH 1658/6300] check correct #ifdef for windows --- Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config.cpp b/Config.cpp index 9e18ff55..2a35ce7e 100644 --- a/Config.cpp +++ b/Config.cpp @@ -128,7 +128,7 @@ 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"); From 22a16da09ed649b2476271247382a1ac87810492 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 20 Jul 2016 21:44:17 -0400 Subject: [PATCH 1659/6300] fixed android build --- Reseed.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Reseed.cpp b/Reseed.cpp index 66609414..236531f9 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -15,6 +15,7 @@ #include "Identity.h" #include "NetDb.h" #include "HTTP.h" +#include "util.h" namespace i2p { From abcf030181acac231be8558423e2ed0b83b70649 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 21 Jul 2016 14:02:13 -0400 Subject: [PATCH 1660/6300] more BOB error messages --- BOB.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++++------------- BOB.h | 2 +- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index f983c67a..ae6e12ce 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -203,7 +203,7 @@ namespace client BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner): m_Owner (owner), m_Socket (m_Owner.GetService ()), - m_ReceiveBufferOffset (0), m_IsOpen (true), m_IsQuiet (false), + 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,19 +369,27 @@ namespace client if (m_OutPort && !m_Address.empty ()) m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet); m_CurrentDestination->Start (); - SendReplyOK ("tunnel starting"); + SendReplyOK ("Tunnel starting"); + m_IsActive = true; } 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) @@ -384,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 ()); } @@ -397,11 +410,11 @@ namespace client m_Keys = m_CurrentDestination->GetKeys (); 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) @@ -441,7 +454,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) @@ -455,14 +471,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) @@ -497,6 +526,7 @@ namespace client { LogPrint (eLogDebug, "BOB: clear"); m_Owner.DeleteDestination (m_Nickname); + m_Nickname = ""; SendReplyOK ("cleared"); } @@ -515,10 +545,14 @@ 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"); @@ -527,10 +561,10 @@ namespace client void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: status ", operand); - if (operand == m_Nickname) + if (m_Nickname == operand) { std::stringstream s; - s << "DATA"; s << " NICKNAME:"; s << operand; + s << "DATA"; s << " NICKNAME:"; s << m_Nickname; if (m_CurrentDestination->GetLocalDestination ()->IsReady ()) s << " STARTING:false RUNNING:true STOPPING:false"; else diff --git a/BOB.h b/BOB.h index d2118c5c..104073bd 100644 --- a/BOB.h +++ b/BOB.h @@ -188,7 +188,7 @@ namespace client boost::asio::ip::tcp::socket m_Socket; 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; From c908beade25cf53ae9ace75978e8f0a3d0a84814 Mon Sep 17 00:00:00 2001 From: /dev/null Date: Thu, 21 Jul 2016 17:57:43 -0600 Subject: [PATCH 1661/6300] Added client tunnel reload on SIGHUP for Linux --- ClientContext.cpp | 8 +++++++- DaemonLinux.cpp | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 2bc13969..788e6a46 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -202,7 +202,13 @@ namespace client void ClientContext::ReloadConfig () { - ReadTunnels (); // TODO: it reads new tunnels only, should be implemented better + /* + std::string config; i2p::config::GetOption("conf", config); + i2p::config::ParseConfig(config); + */ + //I don't think we can just reload the main config without making a mess of things, so holding off for now. + Stop(); + Start(); } void ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 118fc5f5..22d7dec8 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -13,14 +13,16 @@ #include "FS.h" #include "Log.h" #include "RouterContext.h" +#include "ClientContext.h" void handle_signal(int sig) { switch (sig) { case SIGHUP: - LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening log..."); + LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening logs and tunnel configuration..."); i2p::log::Logger().Reopen (); + i2p::client::context.ReloadConfig(); break; case SIGINT: if (i2p::context.AcceptsTunnels () && !Daemon.gracefullShutdownInterval) From 3b9b827ebfadd14ee50856b2ee804600e2a0fb70 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 21 Jul 2016 20:42:40 -0400 Subject: [PATCH 1662/6300] getnick doean't return error if was set before --- BOB.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/BOB.cpp b/BOB.cpp index ae6e12ce..aa11c7b8 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -409,10 +409,13 @@ namespace client { m_Keys = m_CurrentDestination->GetKeys (); m_Nickname = operand; + } + if (m_Nickname == operand) + { std::string msg ("Nickname set to "); msg += m_Nickname; SendReplyOK (msg.c_str ()); - } + } else SendReplyError ("no nickname has been set"); } From eaac21cda10019d888325190c6f17e00217b2732 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 22 Jul 2016 09:56:17 -0400 Subject: [PATCH 1663/6300] * check router info addresses for nullptr * Request LS before expiration for smoother handover --- Destination.cpp | 27 +++++++++++++++++++++++++-- Destination.h | 1 + LeaseSet.cpp | 9 ++++++++- LeaseSet.h | 1 + NTCPSession.cpp | 1 + Transports.cpp | 1 + 6 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 2df14a9f..d115f1fa 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -171,11 +171,29 @@ namespace client std::shared_ptr LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident) { + std::lock_guard lock(m_RemoteLeaseSetsMutex); auto it = m_RemoteLeaseSets.find (ident); if (it != m_RemoteLeaseSets.end ()) - { + { if (!it->second->IsExpired ()) + { + if (it->second->ExpiresSoon()) + { + LogPrint(eLogDebug, "Destination: Lease Set expires soon, updating before expire"); + // update now before expiration for smooth handover + RequestDestination(ident, [this, ident] (std::shared_ptr ls) { + if(ls && !ls->IsExpired()) + { + ls->PopulateLeases(); + { + std::lock_guard _lock(m_RemoteLeaseSetsMutex); + m_RemoteLeaseSets[ident] = ls; + } + } + }); + } return it->second; + } else LogPrint (eLogWarning, "Destination: remote LeaseSet expired"); } @@ -185,7 +203,10 @@ namespace client if (ls && !ls->IsExpired ()) { ls->PopulateLeases (); // since we don't store them in netdb - m_RemoteLeaseSets[ident] = ls; + { + std::lock_guard lock(m_RemoteLeaseSetsMutex); + m_RemoteLeaseSets[ident] = ls; + } return ls; } } @@ -279,6 +300,7 @@ namespace client if (buf[DATABASE_STORE_TYPE_OFFSET] == 1) // LeaseSet { LogPrint (eLogDebug, "Remote LeaseSet"); + std::lock_guard lock(m_RemoteLeaseSetsMutex); auto it = m_RemoteLeaseSets.find (buf + DATABASE_STORE_KEY_OFFSET); if (it != m_RemoteLeaseSets.end ()) { @@ -627,6 +649,7 @@ namespace client void LeaseSetDestination::CleanupRemoteLeaseSets () { auto ts = i2p::util::GetMillisecondsSinceEpoch (); + std::lock_guard lock(m_RemoteLeaseSetsMutex); for (auto it = m_RemoteLeaseSets.begin (); it != m_RemoteLeaseSets.end ();) { if (it->second->IsEmpty () || ts > it->second->GetExpirationTime ()) // leaseset expired diff --git a/Destination.h b/Destination.h index e64508c9..ad6318ef 100644 --- a/Destination.h +++ b/Destination.h @@ -122,6 +122,7 @@ namespace client std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; + mutable std::mutex m_RemoteLeaseSetsMutex; std::map > m_RemoteLeaseSets; std::map > m_LeaseSetRequests; diff --git a/LeaseSet.cpp b/LeaseSet.cpp index fafe14b7..a7fede01 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -162,7 +162,14 @@ namespace data { return ExtractTimestamp (buf, len) > ExtractTimestamp (m_Buffer, m_BufferLen); } - + + bool LeaseSet::ExpiresSoon(const uint64_t dlt) const + { + auto now = i2p::util::GetMillisecondsSinceEpoch (); + if (now >= m_ExpirationTime) return true; + return m_ExpirationTime - now <= dlt; + } + const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const { auto ts = i2p::util::GetMillisecondsSinceEpoch (); diff --git a/LeaseSet.h b/LeaseSet.h index c174ac39..d9e74bab 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -60,6 +60,7 @@ namespace data bool IsExpired () const; bool IsEmpty () const { return m_Leases.empty (); }; uint64_t GetExpirationTime () const { return m_ExpirationTime; }; + bool ExpiresSoon(const uint64_t dlt=1000 * 5) const ; bool operator== (const LeaseSet& other) const { return m_BufferLen == other.m_BufferLen && !memcmp (m_Buffer, other.m_Buffer, m_BufferLen); }; diff --git a/NTCPSession.cpp b/NTCPSession.cpp index d2c03857..71cb21b3 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -768,6 +768,7 @@ namespace transport auto& addresses = context.GetRouterInfo ().GetAddresses (); for (auto address: addresses) { + if (!address) continue; if (address->transportStyle == i2p::data::RouterInfo::eTransportNTCP) { if (address->host.is_v4()) diff --git a/Transports.cpp b/Transports.cpp index a54455cc..f8af379f 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -114,6 +114,7 @@ namespace transport auto& addresses = context.GetRouterInfo ().GetAddresses (); for (auto address : addresses) { + if (!address) continue; if (m_NTCPServer == nullptr && enableNTCP) { m_NTCPServer = new NTCPServer (); From a4112ebed26a2bd1879c35c3c8252ffb16017fb9 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 22 Jul 2016 10:16:57 -0400 Subject: [PATCH 1664/6300] add both v4 and v6 addresses --- RouterContext.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index 737a92fc..0140c298 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -53,16 +53,25 @@ namespace i2p bool ipv6; i2p::config::GetOption("ipv6", ipv6); bool nat; i2p::config::GetOption("nat", nat); std::string ifname; i2p::config::GetOption("ifname", ifname); - std::string host = ipv6 ? "::" : "127.0.0.1"; - if (nat) { + if (ipv4) + { + std::string host = "127.0.0.1"; 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(); + else if (!nat && !ifname.empty()) + /* bind to interface, we have no NAT so set external address too */ + host = i2p::util::net::GetInterfaceAddress(ifname, false).to_string(); // v4 + routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + routerInfo.AddNTCPAddress (host.c_str(), port); + } + if (ipv6) + { + std::string host = "::"; + if (!ifname.empty()) + host = i2p::util::net::GetInterfaceAddress(ifname, true).to_string(); // v6 + routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + routerInfo.AddNTCPAddress (host.c_str(), port); } - routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); - routerInfo.AddNTCPAddress (host.c_str(), port); routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC routerInfo.SetProperty ("netId", std::to_string (I2PD_NET_ID)); From 6a1049bfb7d26fe58c32a5f1e12b3c58b67cc85e Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 22 Jul 2016 10:34:56 -0400 Subject: [PATCH 1665/6300] override address if v6 only --- RouterContext.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/RouterContext.cpp b/RouterContext.cpp index 0140c298..f2c2bc48 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -67,7 +67,9 @@ namespace i2p if (ipv6) { std::string host = "::"; - if (!ifname.empty()) + if (!i2p::config::IsDefault("host") && !ipv4) // override if v6 only + i2p::config::GetOption("host", host); + else if (!ifname.empty()) host = i2p::util::net::GetInterfaceAddress(ifname, true).to_string(); // v6 routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); routerInfo.AddNTCPAddress (host.c_str(), port); From ba078f3ff571deacaa458f9ff54f1de711f0341f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 22 Jul 2016 12:50:03 -0400 Subject: [PATCH 1666/6300] support peer test for ipv6 --- SSUSession.cpp | 55 +++++++++++++++++++++++++++++++++----------------- SSUSession.h | 2 +- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index 9c90ff88..ef8fc467 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -930,10 +930,10 @@ namespace transport { uint32_t nonce = bufbe32toh (buf); // 4 bytes uint8_t size = buf[4]; // 1 byte - uint32_t address = (size == 4) ? buf32toh(buf + 5) : 0; // big endian, size bytes + const uint8_t * address = buf + 5; // big endian, size bytes uint16_t port = buf16toh(buf + size + 5); // big endian, 2 bytes const uint8_t * introKey = buf + size + 7; - if (port && !address) + if (port && (size != 4) && (size != 16)) { LogPrint (eLogWarning, "SSU: Address of ", size, " bytes not supported"); return; @@ -954,8 +954,7 @@ namespace transport LogPrint (eLogDebug, "SSU: first peer test from Charlie. We are Alice"); i2p::context.SetStatus (eRouterStatusOK); m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2); - SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), - senderEndpoint.port (), introKey, true, false); // to Charlie + SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, true, false); // to Charlie } break; } @@ -984,8 +983,7 @@ namespace transport case ePeerTestParticipantCharlie: { LogPrint (eLogDebug, "SSU: peer test from Alice. We are Charlie"); - SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), - senderEndpoint.port (), introKey); // to Alice with her actual address + SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey); // to Alice with her actual address m_Server.RemovePeerTest (nonce); // nonce has been used break; } @@ -1000,7 +998,20 @@ namespace transport LogPrint (eLogDebug, "SSU: peer test from Bob. We are Charlie"); m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob - SendPeerTest (nonce, be32toh (address), be16toh (port), introKey); // to Alice with her address received from Bob + boost::asio::ip::address addr; // Alice's address + if (size == 4) // v4 + { + boost::asio::ip::address_v4::bytes_type bytes; + memcpy (bytes.data (), address, 4); + addr = boost::asio::ip::address_v4 (bytes); + } + else // v6 + { + boost::asio::ip::address_v6::bytes_type bytes; + memcpy (bytes.data (), address, 6); + addr = boost::asio::ip::address_v6 (bytes); + } + SendPeerTest (nonce, addr, be16toh (port), introKey); // to Alice with her address received from Bob } else { @@ -1009,8 +1020,7 @@ namespace transport if (session) { m_Server.NewPeerTest (nonce, ePeerTestParticipantBob, shared_from_this ()); - session->SendPeerTest (nonce, senderEndpoint.address ().to_v4 ().to_ulong (), - senderEndpoint.port (), introKey, false); // to Charlie with Alice's actual address + session->SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, false); // to Charlie with Alice's actual address } } } @@ -1020,7 +1030,7 @@ namespace transport } } - void SSUSession::SendPeerTest (uint32_t nonce, uint32_t address, uint16_t port, + void SSUSession::SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, const uint8_t * introKey, bool toAddress, bool sendAddress) // toAddress is true for Alice<->Chalie communications only // sendAddress is false if message comes from Alice @@ -1031,12 +1041,21 @@ namespace transport htobe32buf (payload, nonce); payload += 4; // nonce // address and port - if (sendAddress && address) - { - *payload = 4; - payload++; // size - htobe32buf (payload, address); - payload += 4; // address + if (sendAddress) + { + if (address.is_v4 ()) + { + *payload = 4; + memcpy (payload + 1, address.to_v4 ().to_bytes ().data (), 4); // our IP V4 + } + else if (address.is_v6 ()) + { + *payload = 6; + memcpy (payload + 1, address.to_v6 ().to_bytes ().data (), 16); // our IP V6 + } + else + *payload = 0; + payload += (payload[0] + 1); } else { @@ -1064,7 +1083,7 @@ namespace transport { // encrypt message with specified intro key FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80, introKey, iv, introKey); - boost::asio::ip::udp::endpoint e (boost::asio::ip::address_v4 (address), port); + boost::asio::ip::udp::endpoint e (address, port); m_Server.Send (buf, 80, e); } else @@ -1090,7 +1109,7 @@ namespace transport if (!nonce) nonce = 1; m_IsPeerTest = false; m_Server.NewPeerTest (nonce, ePeerTestParticipantAlice1); - SendPeerTest (nonce, 0, 0, address->key, false, false); // address and port always zero for Alice + SendPeerTest (nonce, boost::asio::ip::address(), 0, address->key, false, false); // address and port always zero for Alice } void SSUSession::SendKeepAlive () diff --git a/SSUSession.h b/SSUSession.h index cfe24f6c..c2f24ce3 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -118,7 +118,7 @@ namespace transport void ScheduleConnectTimer (); void HandleConnectTimer (const boost::system::error_code& ecode); void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); - void SendPeerTest (uint32_t nonce, uint32_t address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true); + void SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true); void ProcessData (uint8_t * buf, size_t len); void SendSesionDestroyed (); void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key From 3d1a7f173c1dc136aa143debff41a909682d24ac Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 22 Jul 2016 13:08:41 -0400 Subject: [PATCH 1667/6300] select Charlie based on Alice's address type --- SSU.cpp | 27 +++++++++++++++++++++++++-- SSU.h | 5 ++++- SSUSession.cpp | 2 +- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/SSU.cpp b/SSU.cpp index 4693a4f6..f4277a5a 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -459,8 +459,31 @@ namespace transport return GetRandomV4Session ( [excluded](std::shared_ptr session)->bool { - return session->GetState () == eSessionStateEstablished && !session->IsV6 () && - session != excluded; + return session->GetState () == eSessionStateEstablished && session != excluded; + } + ); + } + + template + std::shared_ptr SSUServer::GetRandomV6Session (Filter filter) // v6 only + { + std::vector > filteredSessions; + for (auto s :m_SessionsV6) + if (filter (s.second)) filteredSessions.push_back (s.second); + if (filteredSessions.size () > 0) + { + auto ind = rand () % filteredSessions.size (); + return filteredSessions[ind]; + } + return nullptr; + } + + std::shared_ptr SSUServer::GetRandomEstablishedV6Session (std::shared_ptr excluded) // v6 only + { + return GetRandomV6Session ( + [excluded](std::shared_ptr session)->bool + { + return session->GetState () == eSessionStateEstablished && session != excluded; } ); } diff --git a/SSU.h b/SSU.h index 0fdf0621..d779c025 100644 --- a/SSU.h +++ b/SSU.h @@ -48,6 +48,7 @@ namespace transport std::shared_ptr FindSession (std::shared_ptr router) const; std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; std::shared_ptr GetRandomEstablishedV4Session (std::shared_ptr excluded); + std::shared_ptr GetRandomEstablishedV6Session (std::shared_ptr excluded); void DeleteSession (std::shared_ptr session); void DeleteAllSessions (); @@ -79,7 +80,9 @@ namespace transport void CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest = false); template std::shared_ptr GetRandomV4Session (Filter filter); - + template + std::shared_ptr GetRandomV6Session (Filter filter); + std::set FindIntroducers (int maxNumIntroducers); void ScheduleIntroducersUpdateTimer (); void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode); diff --git a/SSUSession.cpp b/SSUSession.cpp index ef8fc467..3aae06ba 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -1016,7 +1016,7 @@ namespace transport else { LogPrint (eLogDebug, "SSU: peer test from Alice. We are Bob"); - auto session = m_Server.GetRandomEstablishedV4Session (shared_from_this ()); // Charlie, TODO: implement v6 support + auto session = senderEndpoint.address ().is_v4 () ? m_Server.GetRandomEstablishedV4Session (shared_from_this ()) : m_Server.GetRandomEstablishedV6Session (shared_from_this ()); // Charlie if (session) { m_Server.NewPeerTest (nonce, ePeerTestParticipantBob, shared_from_this ()); From 8fb093b27265ab70d1fcc15da57ab319aa6a370e Mon Sep 17 00:00:00 2001 From: Mikal Villa Date: Sun, 24 Jul 2016 17:58:26 +0800 Subject: [PATCH 1668/6300] Reload client config on SIGHUP --- DaemonLinux.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 118fc5f5..f6664926 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -13,6 +13,7 @@ #include "FS.h" #include "Log.h" #include "RouterContext.h" +#include "ClientContext.h" void handle_signal(int sig) { @@ -21,6 +22,7 @@ void handle_signal(int sig) case SIGHUP: LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening log..."); i2p::log::Logger().Reopen (); + i2p::client::context.ReloadConfig(); break; case SIGINT: if (i2p::context.AcceptsTunnels () && !Daemon.gracefullShutdownInterval) From 11585327bf5e08eab1716803922f0d81725c74e7 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 24 Jul 2016 09:24:20 -0400 Subject: [PATCH 1669/6300] correct status response --- BOB.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index aa11c7b8..1ed03281 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -567,21 +567,21 @@ namespace client if (m_Nickname == operand) { std::stringstream s; - s << "DATA"; s << " NICKNAME:"; s << m_Nickname; + s << "DATA"; s << " NICKNAME: "; s << m_Nickname; if (m_CurrentDestination->GetLocalDestination ()->IsReady ()) - s << " STARTING:false RUNNING:true STOPPING:false"; + 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"); + 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"); + 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"); + s << " OUTPORT: " << m_OutPort; + s << " OUTHOST: " << (m_Address.length () > 0 ? m_Address : "127.0.0.1"); } SendReplyOK (s.str().c_str()); } From 061720bcf034ffee29d8f0492211223172fb2544 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 24 Jul 2016 10:20:37 -0400 Subject: [PATCH 1670/6300] handle'\r\n' terminated address from Transmission --- BOB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BOB.cpp b/BOB.cpp index 1ed03281..caf766af 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -70,7 +70,7 @@ namespace client if (eol) { *eol = 0; - + if (eol != receiver->buffer && eol[-1] == '\r') eol[-1] = 0; // workaround for Transmission, it sends '\r\n' terminated address receiver->data = (uint8_t *)eol + 1; receiver->dataLen = receiver->bufferOffset - (eol - receiver->buffer + 1); i2p::data::IdentHash ident; From 4dc9f6948dd4aeaff0c62374ddfffd5ef7866d89 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 25 Jul 2016 09:57:35 -0400 Subject: [PATCH 1671/6300] bounds checks --- Identity.cpp | 15 ++++++++++----- Identity.h | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index 71ca007f..6d37d34e 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -200,7 +200,9 @@ namespace data } memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE); - delete[] m_ExtendedBuffer; m_ExtendedBuffer = nullptr; + if(m_ExtendedBuffer) delete[] m_ExtendedBuffer; + m_ExtendedBuffer = nullptr; + m_ExtendedLen = bufbe16toh (m_StandardIdentity.certificate + 1); if (m_ExtendedLen) { @@ -410,6 +412,7 @@ namespace data memcpy (m_PrivateKey, buf + ret, 256); // private key always 256 ret += 256; size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen (); + if(signingPrivateKeySize + ret > len) return 0; // overflow memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize); ret += signingPrivateKeySize; m_Signer = nullptr; @@ -422,7 +425,8 @@ namespace data size_t ret = m_Public->ToBuffer (buf, len); memcpy (buf + ret, m_PrivateKey, 256); // private key always 256 ret += 256; - size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen (); + size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen (); + if(ret + signingPrivateKeySize > len) return 0; // overflow memcpy (buf + ret, m_SigningPrivateKey, signingPrivateKeySize); ret += signingPrivateKeySize; return ret; @@ -452,11 +456,12 @@ namespace data void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const { - if (m_Signer) - m_Signer->Sign (buf, len, signature); + if (!m_Signer) + CreateSigner(); + m_Signer->Sign (buf, len, signature); } - void PrivateKeys::CreateSigner () + void PrivateKeys::CreateSigner () const { switch (m_Public->GetSigningKeyType ()) { diff --git a/Identity.h b/Identity.h index 841acf65..d9fb7761 100644 --- a/Identity.h +++ b/Identity.h @@ -133,14 +133,14 @@ namespace data private: - void CreateSigner (); + void CreateSigner () const; private: std::shared_ptr m_Public; uint8_t m_PrivateKey[256]; uint8_t m_SigningPrivateKey[1024]; // assume private key doesn't exceed 1024 bytes - std::unique_ptr m_Signer; + mutable std::unique_ptr m_Signer; }; // kademlia From 1e1c4d159b068940544b6982cf377fc6d42dc4f2 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 25 Jul 2016 10:28:20 -0400 Subject: [PATCH 1672/6300] do reload --- ClientContext.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 788e6a46..be059ae3 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -202,11 +202,8 @@ namespace client void ClientContext::ReloadConfig () { - /* - std::string config; i2p::config::GetOption("conf", config); - i2p::config::ParseConfig(config); - */ - //I don't think we can just reload the main config without making a mess of things, so holding off for now. + std::string config; i2p::config::GetOption("conf", config); + i2p::config::ParseConfig(config); Stop(); Start(); } From eeeae126390ba150555902e85fc59a6d4ec0b47c Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 25 Jul 2016 11:13:54 -0400 Subject: [PATCH 1673/6300] check for correctly loaded privatekeys --- ClientContext.cpp | 34 +++++++++++++++++++++++----------- ClientContext.h | 2 +- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index be059ae3..bf0465e1 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -51,8 +51,10 @@ namespace client if (httpProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; - LoadPrivateKeys (keys, httpProxyKeys); - localDestination = CreateNewLocalDestination (keys, false); + if(LoadPrivateKeys (keys, httpProxyKeys)) + localDestination = CreateNewLocalDestination (keys, false); + else + LogPrint(eLogError, "Clients: failed to load HTTP Proxy key"); } try { m_HttpProxy = new i2p::proxy::HTTPProxy(httpProxyAddr, httpProxyPort, localDestination); @@ -208,8 +210,9 @@ namespace client Start(); } - void ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType) + bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType) { + bool success = true; std::string fullPath = i2p::fs::DataDirPath (filename); std::ifstream s(fullPath, std::ifstream::binary); if (s.is_open ()) @@ -219,9 +222,14 @@ namespace client s.seekg (0, std::ios::beg); uint8_t * buf = new uint8_t[len]; s.read ((char *)buf, len); - keys.FromBuffer (buf, len); + if(!keys.FromBuffer (buf, len)) + { + LogPrint (eLogError, "Clients: failed to load keyfile ", filename); + success = false; + } + else + LogPrint (eLogInfo, "Clients: Local address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " loaded"); delete[] buf; - LogPrint (eLogInfo, "Clients: Local address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " loaded"); } else { @@ -235,7 +243,8 @@ namespace client delete[] buf; LogPrint (eLogInfo, "Clients: New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); - } + } + return success; } std::shared_ptr ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, @@ -358,10 +367,12 @@ namespace client if (keys.length () > 0) { i2p::data::PrivateKeys k; - LoadPrivateKeys (k, keys, sigType); - localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); - if (!localDestination) - localDestination = CreateNewLocalDestination (k, false, &options); + if(LoadPrivateKeys (k, keys, sigType)) + { + localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); + if (!localDestination) + localDestination = CreateNewLocalDestination (k, false, &options); + } } auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); if (m_ClientTunnels.insert (std::make_pair (clientTunnel->GetAcceptor ().local_endpoint (), @@ -392,7 +403,8 @@ namespace client std::shared_ptr localDestination = nullptr; i2p::data::PrivateKeys k; - LoadPrivateKeys (k, keys, sigType); + if(!LoadPrivateKeys (k, keys, sigType)) + continue; localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); if (!localDestination) localDestination = CreateNewLocalDestination (k, true, &options); diff --git a/ClientContext.h b/ClientContext.h index f5696902..55176b91 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -59,7 +59,7 @@ namespace client const std::map * params = nullptr); void DeleteLocalDestination (std::shared_ptr destination); std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; - void LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); + bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); AddressBook& GetAddressBook () { return m_AddressBook; }; const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; From 63f4cf3d07d392f18a63ee2cfd008eae49a1e75e Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 26 Jul 2016 11:10:10 -0400 Subject: [PATCH 1674/6300] graceful shutdown for windows --- Win32/Win32App.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 01432381..cd9837ba 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -2,6 +2,7 @@ #include #include #include "../Config.h" +#include "../RouterContext.h" #include "../version.h" #include "resource.h" #include "Win32App.h" @@ -15,10 +16,13 @@ #define ID_EXIT 2001 #define ID_CONSOLE 2002 #define ID_APP 2003 +#define ID_GRACEFUL_SHUTDOWN 2004 #define ID_TRAY_ICON 2050 #define WM_TRAYICON (WM_USER + 1) +#define IDT_GRACEFUL_SHUTDOWN_TIMER 2100 + namespace i2p { namespace win32 @@ -30,6 +34,7 @@ namespace win32 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "Show app"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, NULL, NULL); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_GRACEFUL_SHUTDOWN, "&Graceful shutdown"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_EXIT, "E&xit"); SetMenuDefaultItem (hPopup, ID_CONSOLE, FALSE); SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); @@ -82,6 +87,7 @@ namespace win32 case WM_CLOSE: { RemoveTrayIcon (hWnd); + KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); PostQuitMessage (0); break; } @@ -101,6 +107,12 @@ namespace win32 PostMessage (hWnd, WM_CLOSE, 0, 0); return 0; } + case ID_GRACEFUL_SHUTDOWN: + { + i2p::context.SetAcceptsTunnels (false); + SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes + return 0; + } case ID_CONSOLE: { char buf[30]; @@ -167,6 +179,15 @@ namespace win32 } break; } + case WM_TIMER: + { + if (wParam == IDT_GRACEFUL_SHUTDOWN_TIMER) + { + PostMessage (hWnd, WM_CLOSE, 0, 0); // exit + return 0; + } + break; + } } return DefWindowProc( hWnd, uMsg, wParam, lParam); } From 183c22cc848ec8f08b1505a76aeeb6b8e7b7c863 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 26 Jul 2016 11:22:53 -0400 Subject: [PATCH 1675/6300] rollback --- Makefile.mingw | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.mingw b/Makefile.mingw index 5cf16bf5..85b6b455 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -9,7 +9,7 @@ LDFLAGS = -Wl,-rpath,/usr/local/lib \ -L/usr/local/lib # UPNP Support -ifeq ($(USE_UPNP),yes) +ifeq ($(USE_UPNP),yes) CXXFLAGS += -DUSE_UPNP -DMINIUPNP_STATICLIB LDLIBS = -Wl,-Bstatic -lminiupnpc endif @@ -37,7 +37,7 @@ ifeq ($(USE_WIN32_APP), yes) DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif -ifeq ($(USE_AESNI),yes) +ifeq ($(USE_AESNI),1) CPU_FLAGS = -maes -DAESNI else CPU_FLAGS = -msse From 36aa2485564678ba23c871e9666719cc169c62cf Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 26 Jul 2016 11:52:44 -0400 Subject: [PATCH 1676/6300] Graceful shutdown --- Win32/Win32App.cpp | 8 ++++++++ Win32/Win32App.h | 1 + 2 files changed, 9 insertions(+) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index cd9837ba..7da8f882 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -239,5 +239,13 @@ namespace win32 { UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); } + + bool GracefulShutdown () + { + HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); + if (hWnd) + PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_GRACEFUL_SHUTDOWN, 0), 0); + return hWnd; + } } } diff --git a/Win32/Win32App.h b/Win32/Win32App.h index 7d35ec1e..3babffa9 100644 --- a/Win32/Win32App.h +++ b/Win32/Win32App.h @@ -10,6 +10,7 @@ namespace win32 bool StartWin32App (); void StopWin32App (); int RunWin32App (); + bool GracefulShutdown (); } } #endif // WIN32APP_H__ From 10be15050344d31bfe997c6ba71e4934bb3e0fbc Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 26 Jul 2016 12:11:52 -0400 Subject: [PATCH 1677/6300] invoke GracefulShutdown for Win32 --- HTTPServer.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 566ad53e..b4f90466 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -22,6 +22,9 @@ #include "HTTPServer.h" #include "Daemon.h" #include "util.h" +#ifdef WIN32_APP +#include "Win32/Win32App.h" +#endif // For image and info #include "version.h" @@ -409,15 +412,21 @@ namespace http { else s << " Accept transit tunnels
    \r\n"; #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) - if (Daemon.gracefullShutdownInterval) { + if (Daemon.gracefullShutdownInterval) + { s << " Cancel gracefull shutdown ("; s << Daemon.gracefullShutdownInterval; s << " seconds remains)
    \r\n"; - } else { + } + else + { s << " Start gracefull shutdown
    \r\n"; } - s << " Force shutdown
    \r\n"; #endif +#ifdef WIN32_APP + s << " Gracefull shutdown
    \r\n"; +#endif + s << " Force shutdown
    \r\n"; } void ShowTransitTunnels (std::stringstream& s) @@ -723,6 +732,9 @@ namespace http { i2p::context.SetAcceptsTunnels (false); #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) Daemon.gracefullShutdownInterval = 10*60; +#endif +#ifdef WIN32_APP + i2p::win32::GracefulShutdown (); #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); From 97da8e2f2e72d4ed96cf6828aac77741ccebf7f2 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Jul 2016 02:00:00 +0000 Subject: [PATCH 1678/6300] * HTTPServer.cpp : true random password --- HTTPServer.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index b4f90466..a811ad45 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -782,12 +782,14 @@ namespace http { std::string pass; i2p::config::GetOption("http.pass", pass); /* generate pass if needed */ if (needAuth && pass == "") { + uint8_t random[16]; char alnum[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; - pass.resize(16); - for (size_t i = 0; i < pass.size(); i++) { - pass[i] = alnum[rand() % (sizeof(alnum) - 1)]; + pass.resize(sizeof(random)); + RAND_bytes(random, sizeof(random)); + for (size_t i = 0; i < sizeof(random); i++) { + pass[i] = alnum[random[i] % (sizeof(alnum) - 1)]; } i2p::config::SetOption("http.pass", pass); LogPrint(eLogInfo, "HTTPServer: password set to ", pass); From 5a6bd38d22a6df75c29835f44ed79c150990b693 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Jul 2016 02:00:00 +0000 Subject: [PATCH 1679/6300] * docs/configuration.md --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index e6ac74d2..8bd072b9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -109,7 +109,7 @@ tunnels.conf: # * keys -- our identity, if unset, will be generated on every startup, # if set and file missing, keys will be generated and placed to this file # * address -- local interface to bind - # * signaturetype -- signature type for new destination. 0,1 or 7 + # * signaturetype -- signature type for new destination. 0 (DSA/SHA1), 1 (EcDSA/SHA256) or 7 (EdDSA/SHA512) [IRC] type = client address = 127.0.0.1 From b1aeae6772f7440a913f4d1102bd5043468e2700 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 27 Jul 2016 02:00:00 +0000 Subject: [PATCH 1680/6300] * util.{cpp,h} : kill with fire i2p::util::http (#314, closes #432) --- NetDb.cpp | 1 - UPnP.h | 2 - util.cpp | 159 +----------------------------------------------------- util.h | 29 ---------- 4 files changed, 1 insertion(+), 190 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index f5b51064..1752f1a9 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -14,7 +14,6 @@ #include "RouterContext.h" #include "Garlic.h" #include "NetDb.h" -#include "util.h" using namespace i2p::transport; diff --git a/UPnP.h b/UPnP.h index 4013b3df..f62ce402 100644 --- a/UPnP.h +++ b/UPnP.h @@ -13,8 +13,6 @@ #include -#include "util.h" - namespace i2p { namespace transport diff --git a/util.cpp b/util.cpp index 08ee6672..ca2b61c2 100644 --- a/util.cpp +++ b/util.cpp @@ -1,12 +1,7 @@ #include #include -#include -#include -#include -#include -#include #include -#include + #include "util.h" #include "Log.h" @@ -60,158 +55,6 @@ namespace i2p { namespace util { -namespace http -{ - std::string GetHttpContent (std::istream& response) - { - std::string version, statusMessage; - response >> version; // HTTP version - int status; - response >> status; // status - std::getline (response, statusMessage); - if (status == 200) // OK - { - bool isChunked = false; - std::string header; - while (!response.eof () && header != "\r") - { - std::getline(response, header); - auto colon = header.find (':'); - if (colon != std::string::npos) - { - std::string field = header.substr (0, colon); - std::transform(field.begin(), field.end(), field.begin(), ::tolower); - if (field == i2p::util::http::TRANSFER_ENCODING) - isChunked = (header.find ("chunked", colon + 1) != std::string::npos); - } - } - - std::stringstream ss; - if (isChunked) - MergeChunkedResponse (response, ss); - else - ss << response.rdbuf(); - return ss.str(); - } - else - { - LogPrint (eLogError, "HTTPClient: error, server responds ", status); - return ""; - } - } - - void MergeChunkedResponse (std::istream& response, std::ostream& merged) - { - while (!response.eof ()) - { - std::string hexLen; - size_t len; - std::getline (response, hexLen); - std::istringstream iss (hexLen); - iss >> std::hex >> len; - if (!len || len > 10000000L) // 10M - { - LogPrint (eLogError, "Unexpected chunk length ", len); - break; - } - char * buf = new char[len]; - response.read (buf, len); - merged.write (buf, len); - delete[] buf; - std::getline (response, hexLen); // read \r\n after chunk - } - } - - url::url(const std::string& url_s) - { - portstr_ = "80"; - port_ = 80; - user_ = ""; - pass_ = ""; - - parse(url_s); - } - - - // code for parser tests - //{ - // i2p::util::http::url u_0("http://127.0.0.1:7070/asdasd?qqqqqqqqqqqq"); - // i2p::util::http::url u_1("http://user:password@site.com:8080/asdasd?qqqqqqqqqqqqq"); - // i2p::util::http::url u_2("http://user:password@site.com/asdasd?qqqqqqqqqqqqqq"); - // i2p::util::http::url u_3("http://user:@site.com/asdasd?qqqqqqqqqqqqq"); - // i2p::util::http::url u_4("http://user@site.com/asdasd?qqqqqqqqqqqq"); - // i2p::util::http::url u_5("http://@site.com:800/asdasd?qqqqqqqqqqqq"); - // i2p::util::http::url u_6("http://@site.com:err_port/asdasd?qqqqqqqqqqqq"); - // i2p::util::http::url u_7("http://user:password@site.com:err_port/asdasd?qqqqqqqqqqqq"); - //} - void url::parse(const std::string& url_s) - { - const std::string prot_end("://"); - std::string::const_iterator prot_i = search(url_s.begin(), url_s.end(), - prot_end.begin(), prot_end.end()); - protocol_.reserve(distance(url_s.begin(), prot_i)); - transform(url_s.begin(), prot_i, - back_inserter(protocol_), - std::ptr_fun(tolower)); // protocol is icase - if( prot_i == url_s.end() ) - return; - advance(prot_i, prot_end.length()); - std::string::const_iterator path_i = find(prot_i, url_s.end(), '/'); - host_.reserve(distance(prot_i, path_i)); - transform(prot_i, path_i, - back_inserter(host_), - std::ptr_fun(tolower)); // host is icase - - // parse user/password - auto user_pass_i = find(host_.begin(), host_.end(), '@'); - if (user_pass_i != host_.end()) - { - std::string user_pass = std::string(host_.begin(), user_pass_i); - auto pass_i = find(user_pass.begin(), user_pass.end(), ':'); - if (pass_i != user_pass.end()) - { - user_ = std::string(user_pass.begin(), pass_i); - pass_ = std::string(pass_i + 1, user_pass.end()); - } - else - user_ = user_pass; - - host_.assign(user_pass_i + 1, host_.end()); - } - - // parse port - auto port_i = find(host_.begin(), host_.end(), ':'); - if (port_i != host_.end()) - { - portstr_ = std::string(port_i + 1, host_.end()); - host_.assign(host_.begin(), port_i); - try{ - port_ = boost::lexical_cast(portstr_); - } - catch (std::exception e) { - port_ = 80; - } - } - - std::string::const_iterator query_i = find(path_i, url_s.end(), '?'); - path_.assign(path_i, query_i); - if( query_i != url_s.end() ) - ++query_i; - query_.assign(query_i, url_s.end()); - } - - std::string urlDecode(const std::string& data) - { - std::string res(data); - for (size_t pos = res.find('%'); pos != std::string::npos; pos = res.find('%',pos+1)) - { - char c = strtol(res.substr(pos+1,2).c_str(), NULL, 16); - res.replace(pos,3,1,c); - } - return res; - } -} - namespace net { #ifdef WIN32 diff --git a/util.h b/util.h index 642ecc9b..77b94995 100644 --- a/util.h +++ b/util.h @@ -22,7 +22,6 @@ namespace i2p { namespace util { - /** wrapper arround boost::lexical_cast that "never" fails */ @@ -34,34 +33,6 @@ namespace util return fallback; } } - - namespace http - { - // in (lower case) - const char ETAG[] = "etag"; // ETag - const char LAST_MODIFIED[] = "last-modified"; // Last-Modified - const char TRANSFER_ENCODING[] = "transfer-encoding"; // Transfer-Encoding - const char CONTENT_ENCODING[] = "content-encoding"; // Content-Encoding - // out - const char IF_NONE_MATCH[] = "If-None-Match"; - const char IF_MODIFIED_SINCE[] = "If-Modified-Since"; - - std::string GetHttpContent (std::istream& response); - void MergeChunkedResponse (std::istream& response, std::ostream& merged); - std::string urlDecode(const std::string& data); - - struct url { - url(const std::string& url_s); // omitted copy, ==, accessors, ... - private: - void parse(const std::string& url_s); - public: - std::string protocol_, host_, path_, query_; - std::string portstr_; - unsigned int port_; - std::string user_; - std::string pass_; - }; - } namespace net { From f32510e10ab895a68a7e2a62d4ddb49d59d75073 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 09:25:05 -0400 Subject: [PATCH 1681/6300] set socket options --- I2PService.cpp | 7 ++++++- I2PTunnel.cpp | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/I2PService.cpp b/I2PService.cpp index 4f907f18..f5ebcb0c 100644 --- a/I2PService.cpp +++ b/I2PService.cpp @@ -32,7 +32,12 @@ namespace client } } - TCPIPPipe::TCPIPPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream) : I2PServiceHandler(owner), m_up(upstream), m_down(downstream) {} + TCPIPPipe::TCPIPPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream) : I2PServiceHandler(owner), m_up(upstream), m_down(downstream) + { + boost::asio::socket_base::receive_buffer_size option(TCP_IP_PIPE_BUFFER_SIZE); + upstream->set_option(option); + downstream->set_option(option); + } TCPIPPipe::~TCPIPPipe() { diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index ad06328b..aadc15b6 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -9,11 +9,20 @@ namespace i2p { namespace client { + + /** set standard socket options */ + static void I2PTunnelSetSocketOptions(std::shared_ptr socket) + { + boost::asio::socket_base::receive_buffer_size option(I2P_TUNNEL_CONNECTION_BUFFER_SIZE); + socket->set_option(option); + } + I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr leaseSet, int port): I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { + I2PTunnelSetSocketOptions(m_Socket); m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); } @@ -22,6 +31,7 @@ namespace client I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { + I2PTunnelSetSocketOptions(m_Socket); } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, @@ -29,6 +39,7 @@ namespace client I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), m_RemoteEndpoint (target), m_IsQuiet (quiet) { + I2PTunnelSetSocketOptions(m_Socket); } I2PTunnelConnection::~I2PTunnelConnection () From f5684eba90a811559de12456b4db6263f52f1f89 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 09:49:43 -0400 Subject: [PATCH 1682/6300] color log messages for warn and error --- Log.h | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Log.h b/Log.h index 6762899f..bedc98f8 100644 --- a/Log.h +++ b/Log.h @@ -40,8 +40,20 @@ enum LogType { #endif }; +#ifdef _WIN32 + const char LOG_COLOR_ERROR[] = ""; + const char LOG_COLOR_WARNING[] = ""; + const char LOG_COLOR_RESET[] = ""; +#else + const char LOG_COLOR_ERROR[] = "\033[1;31m"; + const char LOG_COLOR_WARNING[] = "\033[1;33m"; + const char LOG_COLOR_RESET[] = "\033[0m"; +#endif + + namespace i2p { namespace log { + struct LogMsg; /* forward declaration */ class Log @@ -177,8 +189,16 @@ void LogPrint (LogLevel level, TArgs... args) // fold message to single string std::stringstream ss(""); - LogPrint (ss, args ...); + if(level == eLogError) // if log level is ERROR color log message red + ss << LOG_COLOR_ERROR; + else if (level == eLogWarning) // if log level is WARN color log message yellow + ss << LOG_COLOR_WARNING; + LogPrint (ss, args ...); + + // reset color + ss << LOG_COLOR_RESET; + auto msg = std::make_shared(level, std::time(nullptr), ss.str()); msg->tid = std::this_thread::get_id(); log.Append(msg); From 50b9eca34c98e09acffd132195c7341516e37f45 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 09:50:19 -0400 Subject: [PATCH 1683/6300] check for bogus times in packets --- Streaming.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index 3806d97d..316a3031 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -268,7 +268,12 @@ namespace stream } } auto sentPacket = *it; - uint64_t rtt = ts - sentPacket->sendTime; + uint64_t rtt = ts - sentPacket->sendTime; + if(ts < sentPacket->sendTime) + { + LogPrint(eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime); + rtt = 0; + } m_RTT = (m_RTT*seqn + rtt)/(seqn + 1); m_RTO = m_RTT*1.5; // TODO: implement it better LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt); From 61fe2923e4f3ae00efcde38212a9e4d7b447ea51 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 09:53:39 -0400 Subject: [PATCH 1684/6300] don't set socket option for closed sockets --- I2PTunnel.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index aadc15b6..d680fee9 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -13,8 +13,11 @@ namespace client /** set standard socket options */ static void I2PTunnelSetSocketOptions(std::shared_ptr socket) { - boost::asio::socket_base::receive_buffer_size option(I2P_TUNNEL_CONNECTION_BUFFER_SIZE); - socket->set_option(option); + if (socket && socket->is_open()) + { + boost::asio::socket_base::receive_buffer_size option(I2P_TUNNEL_CONNECTION_BUFFER_SIZE); + socket->set_option(option); + } } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, @@ -22,7 +25,6 @@ namespace client I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { - I2PTunnelSetSocketOptions(m_Socket); m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); } @@ -31,7 +33,6 @@ namespace client I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { - I2PTunnelSetSocketOptions(m_Socket); } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, @@ -39,7 +40,6 @@ namespace client I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), m_RemoteEndpoint (target), m_IsQuiet (quiet) { - I2PTunnelSetSocketOptions(m_Socket); } I2PTunnelConnection::~I2PTunnelConnection () @@ -61,6 +61,7 @@ namespace client void I2PTunnelConnection::Connect () { + I2PTunnelSetSocketOptions(m_Socket); if (m_Socket) m_Socket->async_connect (m_RemoteEndpoint, std::bind (&I2PTunnelConnection::HandleConnect, shared_from_this (), std::placeholders::_1)); From 59797a5c9a5d4d2683c84a53ead5af5bf4ef72d3 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 10:01:20 -0400 Subject: [PATCH 1685/6300] streaming log tweaks and dont set RTT to 0 --- Streaming.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 316a3031..9dfef0fe 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -272,11 +272,11 @@ namespace stream if(ts < sentPacket->sendTime) { LogPrint(eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime); - rtt = 0; + rtt = 1; } m_RTT = (m_RTT*seqn + rtt)/(seqn + 1); m_RTO = m_RTT*1.5; // TODO: implement it better - LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt); + LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt, " sentTime=", sentPacket->sendTime); m_SentPackets.erase (it++); delete sentPacket; acknowledged = true; From 17bfa35f7791ca6276d3d546139935969a8bb087 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 10:02:26 -0400 Subject: [PATCH 1686/6300] don't use warning for no tags --- Garlic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Garlic.cpp b/Garlic.cpp index d48e9d94..aab3e9ab 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -178,7 +178,7 @@ namespace garlic // create message if (!tagFound) // new session { - LogPrint (eLogWarning, "Garlic: No tags available, will use ElGamal"); + LogPrint (eLogInfo, "Garlic: No tags available, will use ElGamal"); if (!m_Destination) { LogPrint (eLogError, "Garlic: Can't use ElGamal for unknown destination"); From aa3a93b6a09943596a7e28d2d5976d0e8c8477ce Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 11:16:29 -0400 Subject: [PATCH 1687/6300] implement streaming limiting (initial) --- ClientContext.cpp | 2 ++ I2PTunnel.cpp | 4 +-- I2PTunnel.h | 6 ++-- Streaming.cpp | 90 ++++++++++++++++++++++++++++++++++++++++++++--- Streaming.h | 37 ++++++++++++++++++- 5 files changed, 129 insertions(+), 10 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index bf0465e1..623abab8 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -437,6 +437,8 @@ namespace client std::unique_ptr(serverTunnel))).second) { serverTunnel->Start (); + auto maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); + serverTunnel->SetMaxConnsPerMinute(maxConns); numServerTunnels++; } else diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index d680fee9..87764a84 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -45,7 +45,7 @@ namespace client I2PTunnelConnection::~I2PTunnelConnection () { } - + void I2PTunnelConnection::I2PConnect (const uint8_t * msg, size_t len) { if (m_Stream) @@ -396,7 +396,7 @@ namespace client { m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port, gzip); } - + void I2PServerTunnel::Start () { m_Endpoint.port (m_Port); diff --git a/I2PTunnel.h b/I2PTunnel.h index bec0f9a4..f0332722 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -148,8 +148,10 @@ namespace client const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; } const char* GetName() { return m_Name.c_str (); } - - private: + + void SetMaxConnsPerMinute(const uint32_t conns) { m_PortDestination->SetMaxConnsPerMinute(conns); } + + private: void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, std::shared_ptr resolver); diff --git a/Streaming.cpp b/Streaming.cpp index 9dfef0fe..887bdecc 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -797,7 +797,10 @@ namespace stream StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), - m_PendingIncomingTimer (m_Owner->GetService ()) + m_PendingIncomingTimer (m_Owner->GetService ()), + m_ConnTrackTimer(m_Owner->GetService()), + m_ConnsPerMinute(DEFAULT_MAX_CONNS_PER_MIN), + m_LastBanClear(i2p::util::GetMillisecondsSinceEpoch()) { } @@ -812,17 +815,23 @@ namespace stream } void StreamingDestination::Start () - { + { + ScheduleConnTrack(); } void StreamingDestination::Stop () { ResetAcceptor (); m_PendingIncomingTimer.cancel (); + m_ConnTrackTimer.cancel(); { std::unique_lock l(m_StreamsMutex); m_Streams.clear (); - } + } + { + std::unique_lock l(m_ConnsMutex); + m_Conns.clear (); + } } void StreamingDestination::HandleNextPacket (Packet * packet) @@ -856,7 +865,22 @@ namespace stream incomingStream->HandleNextPacket (it1); m_SavedPackets.erase (it); } - } + } + auto ident = incomingStream->GetRemoteIdentity(); + if(ident) + { + auto ih = ident->GetIdentHash(); + if(DropNewStream(ih)) + { + // drop + LogPrint(eLogWarning, "Streaming: Too many inbound streams from ", ih.ToBase32()); + DeleteStream(incomingStream); + incomingStream = nullptr; + delete packet; + return; + } + } else + LogPrint(eLogWarning, "Streaming: Inbound stream has no identity"); // accept if (m_Acceptor != nullptr) m_Acceptor (incomingStream); @@ -1009,6 +1033,62 @@ namespace stream else msg = nullptr; return msg; - } + } + + void StreamingDestination::SetMaxConnsPerMinute(const uint32_t conns) + { + m_ConnsPerMinute = conns; + } + + bool StreamingDestination::DropNewStream(const i2p::data::IdentHash & ih) + { + std::lock_guard lock(m_ConnsMutex); + if (m_Banned.size() > MAX_BANNED_CONNS) return true; // overload + auto end = m_Banned.end(); + if ( std::find(m_Banned.begin(), end, ih) != end) return true; // already banned + auto itr = m_Conns.find(ih); + if (itr == m_Conns.end()) + m_Conns[ih] = 0; + + m_Conns[ih] = m_Conns[ih] + 1; + + bool ban = m_Conns[ih] <= m_ConnsPerMinute; + if (ban) + { + m_Banned.push_back(ih); + m_Conns.erase(ih); + } + return ban; + } + + void StreamingDestination::HandleConnTrack(const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + { // acquire lock + std::lock_guard lock(m_ConnsMutex); + // clear conn tracking + m_Conns.clear(); + // check for ban clear + auto ts = i2p::util::GetMillisecondsSinceEpoch(); + if (ts - m_LastBanClear >= DEFAULT_BAN_INTERVAL) + { + // clear bans + m_Banned.clear(); + m_LastBanClear = ts; + } + } + // reschedule timer + ScheduleConnTrack(); + } + } + + void StreamingDestination::ScheduleConnTrack() + { + m_ConnTrackTimer.expires_from_now (boost::posix_time::seconds(60)); + m_ConnTrackTimer.async_wait ( + std::bind (&StreamingDestination::HandleConnTrack, + shared_from_this (), std::placeholders::_1)); + } } } diff --git a/Streaming.h b/Streaming.h index c29b62f9..d21dc16b 100644 --- a/Streaming.h +++ b/Streaming.h @@ -51,6 +51,22 @@ namespace stream const int INITIAL_RTO = 9000; // in milliseconds const size_t MAX_PENDING_INCOMING_BACKLOG = 128; const int PENDING_INCOMING_TIMEOUT = 10; // in seconds + + /** i2cp option for limiting inbound stremaing connections */ + const char I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN[] = "i2p.streaming.maxConnsPerMinute"; + /** default maximum connections attempts per minute per destination */ + const uint32_t DEFAULT_MAX_CONNS_PER_MIN = 600; + + /** + * max banned destinations per local destination + * TODO: make configurable + */ + const uint16_t MAX_BANNED_CONNS = 9999; + /** + * length of a ban in ms + * TODO: make configurable + */ + const uint64_t DEFAULT_BAN_INTERVAL = 60 * 60 * 1000; struct Packet { @@ -210,12 +226,22 @@ namespace stream void HandleDataMessagePayload (const uint8_t * buf, size_t len); std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort); + /** set max connections per minute per destination */ + void SetMaxConnsPerMinute(const uint32_t conns); + private: void HandleNextPacket (Packet * packet); std::shared_ptr CreateNewIncomingStream (); void HandlePendingIncomingTimer (const boost::system::error_code& ecode); + /** handle cleaning up connection tracking for ratelimits */ + void HandleConnTrack(const boost::system::error_code& ecode); + + bool DropNewStream(const i2p::data::IdentHash & ident); + + void ScheduleConnTrack(); + private: std::shared_ptr m_Owner; @@ -227,7 +253,16 @@ namespace stream std::list > m_PendingIncomingStreams; boost::asio::deadline_timer m_PendingIncomingTimer; std::map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN - + + std::mutex m_ConnsMutex; + /** how many connections per minute did each identity have */ + std::map m_Conns; + boost::asio::deadline_timer m_ConnTrackTimer; + uint32_t m_ConnsPerMinute; + /** banned identities */ + std::vector m_Banned; + uint64_t m_LastBanClear; + public: i2p::data::GzipInflator m_Inflator; From df8d73ae433c4b39fbd6985d38ec5f1d2c6106d3 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 11:20:24 -0400 Subject: [PATCH 1688/6300] typo --- Streaming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index 887bdecc..06f2a18a 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -1052,7 +1052,7 @@ namespace stream m_Conns[ih] = m_Conns[ih] + 1; - bool ban = m_Conns[ih] <= m_ConnsPerMinute; + bool ban = m_Conns[ih] >= m_ConnsPerMinute; if (ban) { m_Banned.push_back(ih); From 34da9a9655a09ec33079a809e2998b22241d79b5 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 11:37:33 -0400 Subject: [PATCH 1689/6300] streaming limiting tweaks --- Streaming.cpp | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 06f2a18a..160388cc 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -853,6 +853,23 @@ namespace stream if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream { auto incomingStream = CreateNewIncomingStream (); + auto ident = incomingStream->GetRemoteIdentity(); + if(ident) + { + auto ih = ident->GetIdentHash(); + if(DropNewStream(ih)) + { + // drop + LogPrint(eLogWarning, "Streaming: Too many inbound streams from ", ih.ToBase32()); + incomingStream->Close(); + DeleteStream(incomingStream); + incomingStream = nullptr; + delete packet; + return; + } + } else + LogPrint(eLogWarning, "Streaming: Inbound stream has no identity"); + uint32_t receiveStreamID = packet->GetReceiveStreamID (); incomingStream->HandleNextPacket (packet); // SYN // handle saved packets if any @@ -866,21 +883,6 @@ namespace stream m_SavedPackets.erase (it); } } - auto ident = incomingStream->GetRemoteIdentity(); - if(ident) - { - auto ih = ident->GetIdentHash(); - if(DropNewStream(ih)) - { - // drop - LogPrint(eLogWarning, "Streaming: Too many inbound streams from ", ih.ToBase32()); - DeleteStream(incomingStream); - incomingStream = nullptr; - delete packet; - return; - } - } else - LogPrint(eLogWarning, "Streaming: Inbound stream has no identity"); // accept if (m_Acceptor != nullptr) m_Acceptor (incomingStream); From a2e01f8a536c90749509de3a6ef8e377aba0d001 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 11:42:31 -0400 Subject: [PATCH 1690/6300] more tweaks --- Streaming.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 160388cc..5138498d 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -853,6 +853,8 @@ namespace stream if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream { auto incomingStream = CreateNewIncomingStream (); + uint32_t receiveStreamID = packet->GetReceiveStreamID (); + incomingStream->HandleNextPacket (packet); // SYN auto ident = incomingStream->GetRemoteIdentity(); if(ident) { @@ -864,14 +866,14 @@ namespace stream incomingStream->Close(); DeleteStream(incomingStream); incomingStream = nullptr; + if(m_SavedPackets.find(receiveStreamID) != m_SavedPackets.end()) + m_SavedPackets.erase(receiveStreamID); delete packet; return; } } else LogPrint(eLogWarning, "Streaming: Inbound stream has no identity"); - uint32_t receiveStreamID = packet->GetReceiveStreamID (); - incomingStream->HandleNextPacket (packet); // SYN // handle saved packets if any { auto it = m_SavedPackets.find (receiveStreamID); From 3b66bba92e25e68d0999f5777ba2186ca6c7157f Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 12:17:24 -0400 Subject: [PATCH 1691/6300] more fixes --- ClientContext.cpp | 3 ++- Streaming.cpp | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 623abab8..8fb8d927 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -436,9 +436,10 @@ namespace client std::make_pair (localDestination->GetIdentHash (), inPort), std::unique_ptr(serverTunnel))).second) { - serverTunnel->Start (); auto maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); + LogPrint(eLogInfo, "Clients: Set Max Conns To ", maxConns); serverTunnel->SetMaxConnsPerMinute(maxConns); + serverTunnel->Start (); numServerTunnels++; } else diff --git a/Streaming.cpp b/Streaming.cpp index 5138498d..a8d9bb71 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -1042,25 +1042,27 @@ namespace stream void StreamingDestination::SetMaxConnsPerMinute(const uint32_t conns) { m_ConnsPerMinute = conns; + LogPrint(eLogDebug, "Streaming: Set max conns per minute per destination to ", conns); } bool StreamingDestination::DropNewStream(const i2p::data::IdentHash & ih) { std::lock_guard lock(m_ConnsMutex); if (m_Banned.size() > MAX_BANNED_CONNS) return true; // overload - auto end = m_Banned.end(); - if ( std::find(m_Banned.begin(), end, ih) != end) return true; // already banned + auto end = std::end(m_Banned); + if ( std::find(std::begin(m_Banned), end, ih) != end) return true; // already banned auto itr = m_Conns.find(ih); if (itr == m_Conns.end()) m_Conns[ih] = 0; - m_Conns[ih] = m_Conns[ih] + 1; + m_Conns[ih] += 1; bool ban = m_Conns[ih] >= m_ConnsPerMinute; if (ban) { m_Banned.push_back(ih); m_Conns.erase(ih); + LogPrint(eLogWarning, "Streaming: ban ", ih.ToBase32()); } return ban; } From 6b3a783ce9bf98e724c446f403475877c170e33e Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 12:21:59 -0400 Subject: [PATCH 1692/6300] change type --- ClientContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 8fb8d927..0bf63ee1 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -436,7 +436,7 @@ namespace client std::make_pair (localDestination->GetIdentHash (), inPort), std::unique_ptr(serverTunnel))).second) { - auto maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); + auto maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); LogPrint(eLogInfo, "Clients: Set Max Conns To ", maxConns); serverTunnel->SetMaxConnsPerMinute(maxConns); serverTunnel->Start (); From 13735d04755009b62d8f007db812073fd6612b99 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 12:26:05 -0400 Subject: [PATCH 1693/6300] move setting --- ClientContext.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 0bf63ee1..0a5ce94f 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -417,6 +417,11 @@ namespace client else // regular server tunnel by default serverTunnel = new I2PServerTunnel (name, host, port, localDestination, inPort, gzip); + uint32_t maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); + LogPrint(eLogInfo, "Clients: Set Max Conns To ", maxConns); + serverTunnel->SetMaxConnsPerMinute(maxConns); + + if (accessList.length () > 0) { std::set idents; @@ -436,9 +441,6 @@ namespace client std::make_pair (localDestination->GetIdentHash (), inPort), std::unique_ptr(serverTunnel))).second) { - auto maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); - LogPrint(eLogInfo, "Clients: Set Max Conns To ", maxConns); - serverTunnel->SetMaxConnsPerMinute(maxConns); serverTunnel->Start (); numServerTunnels++; } From fc5b1ae3e2ed5b9075984a1cb4f0645122fad77f Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 12:28:18 -0400 Subject: [PATCH 1694/6300] fug --- ClientContext.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 0a5ce94f..25d3ce89 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -397,6 +397,8 @@ namespace client std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); + uint32_t maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); + // I2CP std::map options; ReadI2CPOptions (section, options); @@ -417,7 +419,6 @@ namespace client else // regular server tunnel by default serverTunnel = new I2PServerTunnel (name, host, port, localDestination, inPort, gzip); - uint32_t maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); LogPrint(eLogInfo, "Clients: Set Max Conns To ", maxConns); serverTunnel->SetMaxConnsPerMinute(maxConns); From 9447afe49ca17a2bcd046abceb719b947fe461cf Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 12:32:05 -0400 Subject: [PATCH 1695/6300] try changing i2cp option --- Streaming.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming.h b/Streaming.h index d21dc16b..4e76e435 100644 --- a/Streaming.h +++ b/Streaming.h @@ -53,7 +53,7 @@ namespace stream const int PENDING_INCOMING_TIMEOUT = 10; // in seconds /** i2cp option for limiting inbound stremaing connections */ - const char I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN[] = "i2p.streaming.maxConnsPerMinute"; + const char I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN[] = "maxConnsPerMinute"; /** default maximum connections attempts per minute per destination */ const uint32_t DEFAULT_MAX_CONNS_PER_MIN = 600; From 87e1c45c053a358f37a668dba09eb00dae213809 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 12:34:33 -0400 Subject: [PATCH 1696/6300] fug --- Streaming.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming.h b/Streaming.h index 4e76e435..5d995ec3 100644 --- a/Streaming.h +++ b/Streaming.h @@ -53,7 +53,7 @@ namespace stream const int PENDING_INCOMING_TIMEOUT = 10; // in seconds /** i2cp option for limiting inbound stremaing connections */ - const char I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN[] = "maxConnsPerMinute"; + const char I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN[] = "maxconns"; /** default maximum connections attempts per minute per destination */ const uint32_t DEFAULT_MAX_CONNS_PER_MIN = 600; From 584379b50284daf3a3a996664c282b2312d8ffaa Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 12:48:32 -0400 Subject: [PATCH 1697/6300] fix --- Streaming.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index a8d9bb71..3a1a972b 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -862,10 +862,8 @@ namespace stream if(DropNewStream(ih)) { // drop - LogPrint(eLogWarning, "Streaming: Too many inbound streams from ", ih.ToBase32()); + LogPrint(eLogWarning, "Streaming: Dropping connection, too many inbound streams from ", ih.ToBase32()); incomingStream->Close(); - DeleteStream(incomingStream); - incomingStream = nullptr; if(m_SavedPackets.find(receiveStreamID) != m_SavedPackets.end()) m_SavedPackets.erase(receiveStreamID); delete packet; From 1062776762636950d6a0d8a21c2ff1ff639e3357 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 28 Jul 2016 13:24:25 -0400 Subject: [PATCH 1698/6300] cleanup router's tags --- NetDb.cpp | 8 ++++++-- RouterContext.cpp | 6 ++++++ RouterContext.h | 3 ++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 1752f1a9..6ef5df7f 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -71,7 +71,7 @@ namespace data void NetDb::Run () { - uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0; + uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0; while (m_IsRunning) { try @@ -121,7 +121,11 @@ namespace data } lastSave = ts; } - + if (ts - lastDestinationCleanup >= i2p::garlic::INCOMING_TAGS_EXPIRATION_TIMEOUT) + { + i2p::context.CleanupDestination (); + lastDestinationCleanup = ts; + } // if we're in hidden mode don't publish or explore // if (m_HiddenMode) continue; diff --git a/RouterContext.cpp b/RouterContext.cpp index f2c2bc48..3b16d81a 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -439,6 +439,12 @@ namespace i2p std::unique_lock l(m_GarlicMutex); i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); } + + void RouterContext::CleanupDestination () + { + std::unique_lock l(m_GarlicMutex); + i2p::garlic::GarlicDestination::CleanupExpiredTags (); + } uint32_t RouterContext::GetUptime () const { diff --git a/RouterContext.h b/RouterContext.h index 05339847..27e0947d 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -71,7 +71,8 @@ namespace i2p void SetSupportsV4 (bool supportsV4); void UpdateNTCPV6Address (const boost::asio::ip::address& host); // called from NTCP session - void UpdateStats (); + void UpdateStats (); + void CleanupDestination (); // garlic destination // implements LocalDestination std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; From e8c3546433e4c3d372ed59b661441dff2504b2bd Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 13:27:51 -0400 Subject: [PATCH 1699/6300] try fixing --- Streaming.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 3a1a972b..a4cfb533 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -864,9 +864,6 @@ namespace stream // drop LogPrint(eLogWarning, "Streaming: Dropping connection, too many inbound streams from ", ih.ToBase32()); incomingStream->Close(); - if(m_SavedPackets.find(receiveStreamID) != m_SavedPackets.end()) - m_SavedPackets.erase(receiveStreamID); - delete packet; return; } } else From 570598f55624c50a9828d1e6be54ff66a6b8ab64 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 15:33:03 -0400 Subject: [PATCH 1700/6300] abruptly close --- Streaming.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index a4cfb533..87d669ca 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -863,12 +863,11 @@ namespace stream { // drop LogPrint(eLogWarning, "Streaming: Dropping connection, too many inbound streams from ", ih.ToBase32()); - incomingStream->Close(); + incomingStream->Terminate(); + delete incomingStream; return; } - } else - LogPrint(eLogWarning, "Streaming: Inbound stream has no identity"); - + } // handle saved packets if any { auto it = m_SavedPackets.find (receiveStreamID); From ee9dc789afce08b960c76f6b9b84da7dc517b7a4 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 15:34:32 -0400 Subject: [PATCH 1701/6300] change scope of Stream::Terminate --- Streaming.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Streaming.h b/Streaming.h index 5d995ec3..efa4d69e 100644 --- a/Streaming.h +++ b/Streaming.h @@ -150,10 +150,11 @@ namespace stream size_t GetSendBufferSize () const { return m_SendBuffer.rdbuf ()->in_avail (); }; int GetWindowSize () const { return m_WindowSize; }; int GetRTT () const { return m_RTT; }; - - private: + /** don't call me */ void Terminate (); + + private: void SendBuffer (); void SendQuickAck (); From cf8ff2cf861034a84e104c93e8c5679a0c8a1592 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 28 Jul 2016 15:35:13 -0400 Subject: [PATCH 1702/6300] make it compile --- Streaming.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index 87d669ca..8261a639 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -864,7 +864,6 @@ namespace stream // drop LogPrint(eLogWarning, "Streaming: Dropping connection, too many inbound streams from ", ih.ToBase32()); incomingStream->Terminate(); - delete incomingStream; return; } } From 0899eeddc0f881b78dd7d9240e0350d9dd107762 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 29 Jul 2016 10:59:44 -0400 Subject: [PATCH 1703/6300] UPnP for x86_64 --- docs/build_notes_windows.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index 8ba36131..827d0123 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -161,15 +161,17 @@ support for this. Unpack client source code in a sibling folder, e.g. C:\dev\miniupnpc . You may want to remove version number from folder name included in downloaded archive. -Note that you might need to build DLL yourself for 64-bit systems -using msys2 as 64-bit DLLs are not provided by the project. - You can also install it through the MSYS2 and build with USE_UPNP key. ```bash pacman -S mingw-w64-i686-miniupnpc -make USE_UPNP=1 +make USE_UPNP=yes +``` +or +```bash +pacman -S mingw-x86_64-miniupnpc +make USE_UPNP=yes ``` ### Creating Visual Studio project From ebc132ea6504da112c9aed09fb163d2a1a696ceb Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Sat, 30 Jul 2016 21:27:17 +0300 Subject: [PATCH 1704/6300] Update 01-tune-build-opts.patch --- debian/patches/01-tune-build-opts.patch | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/debian/patches/01-tune-build-opts.patch b/debian/patches/01-tune-build-opts.patch index e0e24408..2a09b07b 100644 --- a/debian/patches/01-tune-build-opts.patch +++ b/debian/patches/01-tune-build-opts.patch @@ -1,14 +1,15 @@ diff --git a/Makefile b/Makefile -index fe8ae7e..fc8abda 100644 +index 7d04ba0..33ee184 100644 --- a/Makefile +++ b/Makefile -@@ -9,9 +9,9 @@ DEPS := obj/make.dep +@@ -9,10 +9,10 @@ DEPS := obj/make.dep include filelist.mk -USE_AESNI := yes +USE_AESNI := no USE_STATIC := no + USE_MESHNET := no -USE_UPNP := no +USE_UPNP := yes From 47b562b032a362d1860f5c3b900bbc2f4392970a Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 30 Jul 2016 18:22:14 -0400 Subject: [PATCH 1705/6300] temporary disable OS X --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d83cdbc0..819c75f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ cache: apt: true os: - linux - - osx sudo: required dist: trusty addons: From 5698ff9c4cd7f7faf0512786f685b8fe1e4cdd3c Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 31 Jul 2016 10:22:41 -0400 Subject: [PATCH 1706/6300] wait for UPnP discovery during startup --- UPnP.cpp | 9 ++++++++- UPnP.h | 6 +++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/UPnP.cpp b/UPnP.cpp index 41b49e00..db0d3683 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -49,7 +49,9 @@ namespace transport m_IsRunning = true; LogPrint(eLogInfo, "UPnP: starting"); m_Service.post (std::bind (&UPnP::Discover, this)); + std::unique_lock l(m_StartedMutex); m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this))); + m_Started.wait_for (l, std::chrono::seconds (5)); // 5 seconds maximum } UPnP::~UPnP () @@ -80,7 +82,12 @@ namespace transport #else m_Devlist = upnpDiscover (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, &nerror); #endif - + { + // notify satrting thread + std::unique_lock l(m_StartedMutex); + m_Started.notify_all (); + } + int r; r = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); if (r == 1) diff --git a/UPnP.h b/UPnP.h index f62ce402..7d67fdbf 100644 --- a/UPnP.h +++ b/UPnP.h @@ -4,6 +4,8 @@ #ifdef USE_UPNP #include #include +#include +#include #include #include @@ -43,8 +45,10 @@ namespace transport bool m_IsRunning; std::unique_ptr m_Thread; + std::condition_variable m_Started; + std::mutex m_StartedMutex; boost::asio::io_service m_Service; - boost::asio::deadline_timer m_Timer; + boost::asio::deadline_timer m_Timer; struct UPNPUrls m_upnpUrls; struct IGDdatas m_upnpData; From c8f5fb4d039c03adbad186593514d4162f832395 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 3 Aug 2016 10:40:30 -0400 Subject: [PATCH 1707/6300] close duplicate ntcp sessions --- NTCPSession.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 71cb21b3..144b947e 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -861,6 +861,7 @@ namespace transport if (it != m_NTCPSessions.end ()) { LogPrint (eLogWarning, "NTCP: session to ", ident.ToBase64 (), " already exists"); + session->Terminate(); return false; } m_NTCPSessions.insert (std::pair >(ident, session)); From 94b3bb23914af2a6b37ccc440cdeec53fb4b9bc4 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 4 Aug 2016 10:26:50 -0400 Subject: [PATCH 1708/6300] adjust termination timeout --- NTCPSession.cpp | 7 ++++--- SSUSession.cpp | 7 ++++--- TransportSession.h | 8 ++++++-- Transports.cpp | 2 ++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index d2c03857..8475cd50 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -19,7 +19,8 @@ namespace i2p namespace transport { NTCPSession::NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter): - TransportSession (in_RemoteRouter), m_Server (server), m_Socket (m_Server.GetService ()), + TransportSession (in_RemoteRouter, NTCP_TERMINATION_TIMEOUT), + m_Server (server), m_Socket (m_Server.GetService ()), m_TerminationTimer (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false), m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_IsSending (false) { @@ -731,7 +732,7 @@ namespace transport void NTCPSession::ScheduleTermination () { m_TerminationTimer.cancel (); - m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP_TERMINATION_TIMEOUT)); + m_TerminationTimer.expires_from_now (boost::posix_time::seconds(GetTerminationTimeout ())); m_TerminationTimer.async_wait (std::bind (&NTCPSession::HandleTerminationTimer, shared_from_this (), std::placeholders::_1)); } @@ -740,7 +741,7 @@ namespace transport { if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogDebug, "NTCP: No activity for ", NTCP_TERMINATION_TIMEOUT, " seconds"); + LogPrint (eLogDebug, "NTCP: No activity for ", GetTerminationTimeout (), " seconds"); //Terminate (); m_Socket.close ();// invoke Terminate () from HandleReceive } diff --git a/SSUSession.cpp b/SSUSession.cpp index 3aae06ba..4e095deb 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -12,7 +12,8 @@ namespace i2p namespace transport { SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, - std::shared_ptr router, bool peerTest ): TransportSession (router), + std::shared_ptr router, bool peerTest ): + TransportSession (router, SSU_TERMINATION_TIMEOUT), m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_Timer (GetService ()), m_IsPeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), m_RelayTag (0),m_Data (*this), m_IsDataReceived (false) @@ -882,7 +883,7 @@ namespace transport void SSUSession::ScheduleTermination () { m_Timer.cancel (); - m_Timer.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_TIMEOUT)); + m_Timer.expires_from_now (boost::posix_time::seconds(GetTerminationTimeout ())); m_Timer.async_wait (std::bind (&SSUSession::HandleTerminationTimer, shared_from_this (), std::placeholders::_1)); } @@ -891,7 +892,7 @@ namespace transport { if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogWarning, "SSU: no activity with ", m_RemoteEndpoint, " for ", SSU_TERMINATION_TIMEOUT, " seconds"); + LogPrint (eLogWarning, "SSU: no activity with ", m_RemoteEndpoint, " for ", GetTerminationTimeout (), " seconds"); Failed (); } } diff --git a/TransportSession.h b/TransportSession.h index 608e72d1..1b057bc7 100644 --- a/TransportSession.h +++ b/TransportSession.h @@ -53,8 +53,8 @@ namespace transport { public: - TransportSession (std::shared_ptr router): - m_DHKeysPair (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router) + TransportSession (std::shared_ptr router, int terminationTimeout): + m_DHKeysPair (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout) { if (router) m_RemoteIdentity = router->GetRouterIdentity (); @@ -70,6 +70,9 @@ namespace transport size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; bool IsOutgoing () const { return m_IsOutgoing; }; + int GetTerminationTimeout () const { return m_TerminationTimeout; }; + void SetTerminationTimeout (int terminationTimeout) { m_TerminationTimeout = terminationTimeout; }; + virtual void SendI2NPMessages (const std::vector >& msgs) = 0; protected: @@ -78,6 +81,7 @@ namespace transport std::shared_ptr m_DHKeysPair; // X - for client and Y - for server size_t m_NumSentBytes, m_NumReceivedBytes; bool m_IsOutgoing; + int m_TerminationTimeout; }; } } diff --git a/Transports.cpp b/Transports.cpp index a54455cc..4ad53d7f 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -569,6 +569,8 @@ namespace transport } if (sendDatabaseStore) session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); + else + session->SetTerminationTimeout (10); // most likely it's publishing, no follow-up messages expected, set timeout to 10 seconds it->second.sessions.push_back (session); session->SendI2NPMessages (it->second.delayedMessages); it->second.delayedMessages.clear (); From 8f58886a21cd2d24c1b7a0256c04e30dd03acc64 Mon Sep 17 00:00:00 2001 From: brain5lug Date: Thu, 4 Aug 2016 23:27:07 +0300 Subject: [PATCH 1709/6300] tiny commit to check pulling --- SOCKS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SOCKS.cpp b/SOCKS.cpp index 73833191..9d85963b 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -26,7 +26,7 @@ namespace proxy { uint8_t size; char value[max_socks_hostname_size]; - void FromString (std::string str) + void FromString (const std::string& str) { size = str.length(); if (str.length() > max_socks_hostname_size) size = max_socks_hostname_size; From 4b9afdf53a733be28e66604a4f7c4865f53f73a4 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Fri, 5 Aug 2016 18:06:06 +0000 Subject: [PATCH 1710/6300] fix typo --- docs/build_notes_unix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index 795e408a..228333c8 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -23,7 +23,7 @@ After successfull build i2pd could be installed with: ```bash make install ``` -or you can just use 'make' once you have all dependacies (boost and openssl) installed +or you can just use 'make' once you have all dependencies (boost and openssl) installed ```bash git clone https://github.com/PurpleI2P/i2pd.git From b8ec63cf8c936d184f68da6ea0813d36676843d2 Mon Sep 17 00:00:00 2001 From: brain5lug Date: Fri, 5 Aug 2016 21:23:54 +0300 Subject: [PATCH 1711/6300] copy ellimination for ranges #part1 --- AddressBook.cpp | 8 ++++---- BOB.cpp | 8 ++++---- ClientContext.cpp | 2 +- Destination.cpp | 8 ++++---- Garlic.cpp | 20 ++++++++++---------- HTTP.cpp | 2 +- HTTPProxy.cpp | 10 +++++----- HTTPServer.cpp | 16 ++++++++-------- I2CP.cpp | 2 +- I2NPProtocol.cpp | 2 +- I2PControl.cpp | 6 +++--- LeaseSet.cpp | 12 ++++++------ NetDb.cpp | 26 +++++++++++++------------- NetDbRequests.cpp | 2 +- Profiling.cpp | 2 +- Queue.h | 5 +++-- Tunnel.h | 2 +- 17 files changed, 67 insertions(+), 66 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index cd15d68d..6fdde4eb 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -173,7 +173,7 @@ namespace client return 0; } - for (auto it: addresses) { + for (const auto& it: addresses) { f << it.first << "," << it.second.ToBase32 () << std::endl; num++; } @@ -252,7 +252,7 @@ namespace client } LogPrint (eLogError, "Addressbook: subscription download timeout"); m_IsDownloading = false; - } + } if (m_Storage) { m_Storage->Save (m_Addresses); @@ -260,7 +260,7 @@ namespace client m_Storage = nullptr; } m_DefaultSubscription = nullptr; - for (auto it: m_Subscriptions) + for (auto& it: m_Subscriptions) delete it; m_Subscriptions.clear (); } @@ -418,7 +418,7 @@ namespace client { std::map localAddresses; m_Storage->LoadLocal (localAddresses); - for (auto it: localAddresses) + for (const auto& it: localAddresses) { auto dot = it.first.find ('.'); if (dot != std::string::npos) diff --git a/BOB.cpp b/BOB.cpp index caf766af..8ffffba6 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -536,8 +536,8 @@ namespace client void BOBCommandSession::ListCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: list"); - auto& destinations = m_Owner.GetDestinations (); - for (auto it: destinations) + const auto& destinations = m_Owner.GetDestinations (); + for (const auto& it: destinations) SendData (it.first.c_str ()); SendReplyOK ("Listing done"); } @@ -619,7 +619,7 @@ namespace client BOBCommandChannel::~BOBCommandChannel () { Stop (); - for (auto it: m_Destinations) + for (const auto& it: m_Destinations) delete it.second; } @@ -633,7 +633,7 @@ namespace client void BOBCommandChannel::Stop () { m_IsRunning = false; - for (auto it: m_Destinations) + for (auto& it: m_Destinations) it.second->Stop (); m_Acceptor.cancel (); m_Service.stop (); diff --git a/ClientContext.cpp b/ClientContext.cpp index 2bc13969..aa18ecdf 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -194,7 +194,7 @@ namespace client LogPrint(eLogInfo, "Clients: stopping AddressBook"); m_AddressBook.Stop (); - for (auto it: m_Destinations) + for (auto& it: m_Destinations) it.second->Stop (); m_Destinations.clear (); m_SharedLocalDestination = nullptr; diff --git a/Destination.cpp b/Destination.cpp index 2df14a9f..b1ab2a6c 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -103,7 +103,7 @@ namespace client { if (m_IsRunning) Stop (); - for (auto it: m_LeaseSetRequests) + for (auto& it: m_LeaseSetRequests) if (it.second->requestComplete) it.second->requestComplete (nullptr); m_LeaseSetRequests.clear (); if (m_Pool) @@ -635,7 +635,7 @@ namespace client it = m_RemoteLeaseSets.erase (it); } else - it++; + ++it; } } @@ -663,7 +663,7 @@ namespace client { m_StreamingDestination = std::make_shared (GetSharedFromThis ()); // TODO: m_StreamingDestination->Start (); - for (auto it: m_StreamingDestinationsByPorts) + for (auto& it: m_StreamingDestinationsByPorts) it.second->Start (); return true; } @@ -677,7 +677,7 @@ namespace client { m_StreamingDestination->Stop (); m_StreamingDestination = nullptr; - for (auto it: m_StreamingDestinationsByPorts) + for (auto& it: m_StreamingDestinationsByPorts) it.second->Stop (); if (m_DatagramDestination) { diff --git a/Garlic.cpp b/Garlic.cpp index d48e9d94..c3c0e045 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -111,7 +111,7 @@ namespace garlic it = m_UnconfirmedTagsMsgs.erase (it); } else - it++; + ++it; } } @@ -123,7 +123,7 @@ namespace garlic if (ts >= it->creationTime + OUTGOING_TAGS_EXPIRATION_TIMEOUT) it = m_SessionTags.erase (it); else - it++; + ++it; } CleanupUnconfirmedTags (); return !m_SessionTags.empty () || !m_UnconfirmedTagsMsgs.empty (); @@ -144,7 +144,7 @@ namespace garlic ret = true; } else - it++; + ++it; } return ret; } @@ -615,7 +615,7 @@ namespace garlic it = m_Tags.erase (it); } else - it++; + ++it; } if (numExpiredTags > 0) LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " tags expired for ", GetIdentHash().ToBase64 ()); @@ -631,7 +631,7 @@ namespace garlic it = m_Sessions.erase (it); } else - it++; + ++it; } } @@ -643,26 +643,26 @@ namespace garlic void GarlicDestination::DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID) { m_DeliveryStatusSessions[msgID] = session; - } + } void GarlicDestination::HandleDeliveryStatusMessage (std::shared_ptr msg) { uint32_t msgID = bufbe32toh (msg->GetPayload ()); { auto it = m_DeliveryStatusSessions.find (msgID); - if (it != m_DeliveryStatusSessions.end ()) + if (it != m_DeliveryStatusSessions.end ()) { it->second->MessageConfirmed (msgID); m_DeliveryStatusSessions.erase (it); LogPrint (eLogDebug, "Garlic: message ", msgID, " acknowledged"); - } + } } } void GarlicDestination::SetLeaseSetUpdated () { - std::unique_lock l(m_SessionsMutex); - for (auto it: m_Sessions) + std::unique_lock l(m_SessionsMutex); + for (auto& it: m_Sessions) it.second->SetLeaseSetUpdated (); } diff --git a/HTTP.cpp b/HTTP.cpp index 4a0286a7..08615e5b 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -160,7 +160,7 @@ namespace http { strsplit(query, tokens, '&'); params.clear(); - for (auto it : tokens) { + for (const auto& it : tokens) { std::size_t eq = it.find ('='); if (eq != std::string::npos) { auto e = std::pair(it.substr(0, eq), it.substr(eq + 1)); diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index b35c3b4a..60b6bc33 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -105,7 +105,7 @@ namespace proxy { ss << "

    " << description << "

    \r\n"; std::string content = ss.str(); SendProxyError(content); - } + } void HTTPReqHandler::HostNotFound(std::string & host) { std::stringstream ss; @@ -113,13 +113,13 @@ namespace proxy { << "

    Remote host not found in router's addressbook

    \r\n" << "

    You may try to find this host on jumpservices below:

    \r\n" << "
      \r\n"; - for (auto & js : jumpservices) { + for (const auto& js : jumpservices) { ss << "
    • " << js.first << "
    • \r\n"; } ss << "
    \r\n"; std::string content = ss.str(); SendProxyError(content); - } + } void HTTPReqHandler::SendProxyError(std::string & content) { @@ -164,7 +164,7 @@ namespace proxy { req.del_header("Forwarded"); /* drop proxy-disclosing headers */ std::vector toErase; - for (auto it : req.headers) { + for (const 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) { @@ -173,7 +173,7 @@ namespace proxy { /* allow */ } } - for (auto header : toErase) { + for (const auto& header : toErase) { req.headers.erase(header); } /* replace headers */ diff --git a/HTTPServer.cpp b/HTTPServer.cpp index a811ad45..8ca35100 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -210,7 +210,7 @@ namespace http { s << "Router Family: " << i2p::context.GetRouterInfo().GetProperty("family") << "
    \r\n"; s << "Router Caps: " << i2p::context.GetRouterInfo().GetProperty("caps") << "
    \r\n"; s << "Our external address:" << "
    \r\n" ; - for (auto address : i2p::context.GetRouterInfo().GetAddresses()) + for (const auto& address : i2p::context.GetRouterInfo().GetAddresses()) { switch (address->transportStyle) { @@ -283,7 +283,7 @@ namespace http { } s << "
    \r\n"; s << "Tags
    Incoming: " << dest->GetNumIncomingTags () << "
    Outgoing:
    " << std::endl; - for (auto it: dest->GetSessions ()) + for (const auto& it: dest->GetSessions ()) { s << i2p::client::context.GetAddressBook ().ToAddress(it.first) << " "; s << it.second->GetNumOutgoingTags () << "
    " << std::endl; @@ -314,7 +314,7 @@ namespace http { s << "Status"; s << ""; - for (auto it: dest->GetAllStreams ()) + for (const auto& it: dest->GetAllStreams ()) { s << ""; s << "" << it->GetSendStreamID () << ""; @@ -432,7 +432,7 @@ namespace http { void ShowTransitTunnels (std::stringstream& s) { s << "Transit tunnels:
    \r\n
    \r\n"; - for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ()) + for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { if (std::dynamic_pointer_cast(it)) s << it->GetTunnelID () << " ⇒ "; @@ -451,7 +451,7 @@ namespace http { if (ntcpServer) { s << "NTCP
    \r\n"; - for (auto it: ntcpServer->GetNTCPSessions ()) + for (const auto& it: ntcpServer->GetNTCPSessions ()) { if (it.second && it.second->IsEstablished ()) { @@ -469,7 +469,7 @@ namespace http { if (ssuServer) { s << "
    \r\nSSU
    \r\n"; - for (auto it: ssuServer->GetSessions ()) + for (const auto& it: ssuServer->GetSessions ()) { auto endpoint = it.second->GetRemoteEndpoint (); if (it.second->IsOutgoing ()) s << " ⇒ "; @@ -481,7 +481,7 @@ namespace http { s << "
    \r\n" << std::endl; } s << "
    \r\nSSU6
    \r\n"; - for (auto it: ssuServer->GetSessionsV6 ()) + for (const auto& it: ssuServer->GetSessionsV6 ()) { auto endpoint = it.second->GetRemoteEndpoint (); if (it.second->IsOutgoing ()) s << " ⇒ "; @@ -526,7 +526,7 @@ namespace http { s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
    \r\n"; s << "
    \r\n"; s << "Streams:
    \r\n"; - for (auto it: session->ListSockets()) + for (const auto& it: session->ListSockets()) { switch (it->GetSocketType ()) { diff --git a/I2CP.cpp b/I2CP.cpp index 0867d719..0352cd15 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -659,7 +659,7 @@ namespace client { m_IsRunning = false; m_Acceptor.cancel (); - for (auto it: m_Sessions) + for (auto& it: m_Sessions) it.second->Stop (); m_Sessions.clear (); m_Service.stop (); diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 92ab8281..cbd4c3fc 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -202,7 +202,7 @@ namespace i2p len += 32; buf[len] = routers.size (); len++; - for (auto it: routers) + for (const auto& it: routers) { memcpy (buf + len, it, 32); len += 32; diff --git a/I2PControl.cpp b/I2PControl.cpp index a55871a3..3e2e3997 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -358,7 +358,7 @@ namespace client void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { - for (auto it = params.begin (); it != params.end (); it++) + for (auto it = params.begin (); it != params.end (); ++it) { LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); auto it1 = m_RouterInfoHandlers.find (it->first); @@ -434,7 +434,7 @@ namespace client void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { - for (auto it = params.begin (); it != params.end (); it++) + for (auto it = params.begin (); it != params.end (); ++it) { if (it != params.begin ()) results << ","; LogPrint (eLogDebug, "I2PControl: RouterManager request: ", it->first); @@ -483,7 +483,7 @@ namespace client // network setting void I2PControlService::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { - for (auto it = params.begin (); it != params.end (); it++) + for (auto it = params.begin (); it != params.end (); ++it) { if (it != params.begin ()) results << ","; LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first); diff --git a/LeaseSet.cpp b/LeaseSet.cpp index fafe14b7..89cad0df 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -64,10 +64,10 @@ namespace data return; } - // reset existing leases + // reset existing leases if (m_StoreLeases) - for (auto it: m_Leases) - it->isUpdated = false; + for (auto& it: m_Leases) + it->isUpdated = false; else m_Leases.clear (); @@ -123,7 +123,7 @@ namespace data m_Leases.erase (it++); } else - it++; + ++it; } } @@ -167,7 +167,7 @@ namespace data { auto ts = i2p::util::GetMillisecondsSinceEpoch (); std::vector > leases; - for (auto it: m_Leases) + for (const auto& it: m_Leases) { auto endDate = it->endDate; if (withThreshold) @@ -183,7 +183,7 @@ namespace data bool LeaseSet::HasExpiredLeases () const { auto ts = i2p::util::GetMillisecondsSinceEpoch (); - for (auto it: m_Leases) + for (const auto& it: m_Leases) if (ts >= it->endDate) return true; return false; } diff --git a/NetDb.cpp b/NetDb.cpp index 6ef5df7f..dc018326 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -51,7 +51,7 @@ namespace data { if (m_IsRunning) { - for (auto it: m_RouterInfos) + for (auto& it: m_RouterInfos) it.second->SaveProfile (); DeleteObsoleteProfiles (); m_RouterInfos.clear (); @@ -339,7 +339,7 @@ namespace data m_LastLoad = i2p::util::GetSecondsSinceEpoch(); std::vector files; m_Storage.Traverse(files); - for (auto path : files) + for (const auto& path : files) LoadRouterInfo(path); LogPrint (eLogInfo, "NetDb: ", m_RouterInfos.size(), " routers loaded (", m_Floodfills.size (), " floodfils)"); @@ -357,7 +357,7 @@ namespace data expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL : NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; - for (auto it: m_RouterInfos) + for (auto& it: m_RouterInfos) { std::string ident = it.second->GetIdentHashBase64(); std::string path = m_Storage.Path(ident); @@ -405,7 +405,7 @@ namespace data it = m_RouterInfos.erase (it); continue; } - it++; + ++it; } } // clean up expired floodfiils @@ -415,7 +415,7 @@ namespace data if ((*it)->IsUnreachable ()) it = m_Floodfills.erase (it); else - it++; + ++it; } } } @@ -901,7 +901,7 @@ namespace data { uint32_t i = 0; std::unique_lock l(m_RouterInfosMutex); - for (auto it: m_RouterInfos) + for (const auto& it: m_RouterInfos) { if (i >= ind) { @@ -933,7 +933,7 @@ namespace data else minMetric.SetMax (); std::unique_lock l(m_FloodfillsMutex); - for (auto it: m_Floodfills) + for (const auto& it: m_Floodfills) { if (!it->IsUnreachable ()) { @@ -964,7 +964,7 @@ namespace data if (closeThanUsOnly) ourMetric = destKey ^ i2p::context.GetIdentHash (); { std::unique_lock l(m_FloodfillsMutex); - for (auto it: m_Floodfills) + for (const auto& it: m_Floodfills) { if (!it->IsUnreachable ()) { @@ -983,11 +983,11 @@ namespace data std::vector res; size_t i = 0; - for (auto it: sorted) + for (const auto& it: sorted) { if (i < num) { - auto& ident = it.r->GetIdentHash (); + const auto& ident = it.r->GetIdentHash (); if (!excluded.count (ident)) { res.push_back (ident); @@ -1016,7 +1016,7 @@ namespace data IdentHash destKey = CreateRoutingKey (destination); minMetric.SetMax (); // must be called from NetDb thread only - for (auto it: m_RouterInfos) + for (const auto& it: m_RouterInfos) { if (!it.second->IsFloodfill ()) { @@ -1042,7 +1042,7 @@ namespace data it = m_LeaseSets.erase (it); } else - it++; + ++it; } } @@ -1054,7 +1054,7 @@ namespace data if (ts > it->second.second + 180) // 3 minutes it = m_LookupResponses.erase (it); else - it++; + ++it; } } } diff --git a/NetDbRequests.cpp b/NetDbRequests.cpp index e1ea2872..c3966597 100644 --- a/NetDbRequests.cpp +++ b/NetDbRequests.cpp @@ -141,7 +141,7 @@ namespace data if (done) it = m_RequestedDestinations.erase (it); else - it++; + ++it; } } } diff --git a/Profiling.cpp b/Profiling.cpp index be675502..890a2620 100644 --- a/Profiling.cpp +++ b/Profiling.cpp @@ -167,7 +167,7 @@ namespace data std::vector files; m_ProfilesStorage.Traverse(files); - for (auto path: files) { + for (const auto& path: files) { if (stat(path.c_str(), &st) != 0) { LogPrint(eLogWarning, "Profiling: Can't stat(): ", path); continue; diff --git a/Queue.h b/Queue.h index b47a19c8..39d139b9 100644 --- a/Queue.h +++ b/Queue.h @@ -7,6 +7,7 @@ #include #include #include +#include namespace i2p { @@ -20,7 +21,7 @@ namespace util void Put (Element e) { std::unique_lock l(m_QueueMutex); - m_Queue.push (e); + m_Queue.push (std::move(e)); m_NonEmpty.notify_one (); } @@ -29,7 +30,7 @@ namespace util if (!vec.empty ()) { std::unique_lock l(m_QueueMutex); - for (auto it: vec) + for (const auto& it: vec) m_Queue.push (it); m_NonEmpty.notify_one (); } diff --git a/Tunnel.h b/Tunnel.h index 0d35b682..5bc8b195 100644 --- a/Tunnel.h +++ b/Tunnel.h @@ -224,7 +224,7 @@ namespace tunnel std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; - + // some stats int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; From d5075d706c2888169ade77770f72239ac08ad58c Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 7 Aug 2016 16:27:36 -0400 Subject: [PATCH 1712/6300] eliminate decay timer --- SSUData.cpp | 30 ++++++++---------------------- SSUData.h | 10 ++++------ 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/SSUData.cpp b/SSUData.cpp index 2bd65682..e802791d 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -25,10 +25,10 @@ namespace transport } SSUData::SSUData (SSUSession& session): - m_Session (session), m_ResendTimer (session.GetService ()), m_DecayTimer (session.GetService ()), + m_Session (session), m_ResendTimer (session.GetService ()), m_IncompleteMessagesCleanupTimer (session.GetService ()), m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE), - m_PacketSize (m_MaxPacketSize) + m_PacketSize (m_MaxPacketSize), m_LastMessageReceivedTime (0) { } @@ -44,7 +44,6 @@ namespace transport void SSUData::Stop () { m_ResendTimer.cancel (); - m_DecayTimer.cancel (); m_IncompleteMessagesCleanupTimer.cancel (); } @@ -233,11 +232,8 @@ namespace transport { if (!m_ReceivedMessages.count (msgID)) { - if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES) - m_ReceivedMessages.clear (); - else - ScheduleDecay (); m_ReceivedMessages.insert (msgID); + m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch (); if (!msg->IsExpired ()) m_Handler.PutNextMessage (msg); else @@ -469,21 +465,6 @@ namespace transport } } - void SSUData::ScheduleDecay () - { - m_DecayTimer.cancel (); - m_DecayTimer.expires_from_now (boost::posix_time::seconds(DECAY_INTERVAL)); - auto s = m_Session.shared_from_this(); - m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode) - { s->m_Data.HandleDecayTimer (ecode); }); - } - - void SSUData::HandleDecayTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - m_ReceivedMessages.clear (); - } - void SSUData::ScheduleIncompleteMessagesCleanup () { m_IncompleteMessagesCleanupTimer.cancel (); @@ -508,6 +489,11 @@ namespace transport else it++; } + // decay + if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || + i2p::util::GetSecondsSinceEpoch () > m_LastMessageReceivedTime + DECAY_INTERVAL) + m_ReceivedMessages.clear (); + ScheduleIncompleteMessagesCleanup (); } } diff --git a/SSUData.h b/SSUData.h index bfc75128..f3ad9ea8 100644 --- a/SSUData.h +++ b/SSUData.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include #include "I2NPProtocol.h" @@ -109,9 +109,6 @@ namespace transport void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); - void ScheduleDecay (); - void HandleDecayTimer (const boost::system::error_code& ecode); - void ScheduleIncompleteMessagesCleanup (); void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode); @@ -121,10 +118,11 @@ namespace transport SSUSession& m_Session; std::map > m_IncompleteMessages; std::map > m_SentMessages; - std::set m_ReceivedMessages; - boost::asio::deadline_timer m_ResendTimer, m_DecayTimer, m_IncompleteMessagesCleanupTimer; + std::unordered_set m_ReceivedMessages; + boost::asio::deadline_timer m_ResendTimer, m_IncompleteMessagesCleanupTimer; int m_MaxPacketSize, m_PacketSize; i2p::I2NPMessagesHandler m_Handler; + uint32_t m_LastMessageReceivedTime; // in second }; } } From 8b53ded53a9f728d70d5f84c3160a34940a6b7e9 Mon Sep 17 00:00:00 2001 From: brain5lug Date: Mon, 8 Aug 2016 00:52:18 +0300 Subject: [PATCH 1713/6300] copy elimination for ranges #part2 --- NTCPSession.cpp | 6 +++--- RouterContext.cpp | 14 ++++++------- RouterInfo.cpp | 52 +++++++++++++++++++++++------------------------ RouterInfo.h | 6 +++--- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 8475cd50..b22b0f3c 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -666,7 +666,7 @@ namespace transport { m_IsSending = true; std::vector bufs; - for (auto it: msgs) + for (const auto& it: msgs) bufs.push_back (CreateMsgBuffer (it)); boost::asio::async_write (m_Socket, bufs, boost::asio::transfer_all (), std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msgs)); @@ -716,7 +716,7 @@ namespace transport { if (m_SendQueue.size () < NTCP_MAX_OUTGOING_QUEUE_SIZE) { - for (auto it: msgs) + for (const auto& it: msgs) m_SendQueue.push_back (it); } else @@ -767,7 +767,7 @@ namespace transport m_Thread = new std::thread (std::bind (&NTCPServer::Run, this)); // create acceptors auto& addresses = context.GetRouterInfo ().GetAddresses (); - for (auto address: addresses) + for (const auto& address: addresses) { if (address->transportStyle == i2p::data::RouterInfo::eTransportNTCP) { diff --git a/RouterContext.cpp b/RouterContext.cpp index 3b16d81a..c92b575b 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -112,7 +112,7 @@ namespace i2p void RouterContext::UpdatePort (int port) { bool updated = false; - for (auto address : m_RouterInfo.GetAddresses ()) + for (auto& address : m_RouterInfo.GetAddresses ()) { if (address->port != port) { @@ -127,7 +127,7 @@ namespace i2p void RouterContext::UpdateAddress (const boost::asio::ip::address& host) { bool updated = false; - for (auto address : m_RouterInfo.GetAddresses ()) + for (auto& address : m_RouterInfo.GetAddresses ()) { if (address->host != host && address->IsCompatible (host)) { @@ -244,7 +244,7 @@ namespace i2p m_RouterInfo.SetCaps (i2p::data::RouterInfo::eUnreachable | i2p::data::RouterInfo::eSSUTesting); // LU, B // remove NTCP address auto& addresses = m_RouterInfo.GetAddresses (); - for (auto it = addresses.begin (); it != addresses.end (); it++) + for (auto it = addresses.begin (); it != addresses.end (); ++it) { if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP && (*it)->host.is_v4 ()) @@ -254,7 +254,7 @@ namespace i2p } } // delete previous introducers - for (auto addr : addresses) + for (auto& addr : addresses) addr->introducers.clear (); // update @@ -274,7 +274,7 @@ namespace i2p // insert NTCP back auto& addresses = m_RouterInfo.GetAddresses (); - for (auto addr : addresses) + for (const auto& addr : addresses) { if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU && addr->host.is_v4 ()) @@ -285,7 +285,7 @@ namespace i2p } } // delete previous introducers - for (auto addr : addresses) + for (auto& addr : addresses) addr->introducers.clear (); // update @@ -316,7 +316,7 @@ namespace i2p bool updated = false, found = false; int port = 0; auto& addresses = m_RouterInfo.GetAddresses (); - for (auto addr: addresses) + for (auto& addr: addresses) { if (addr->host.is_v6 () && addr->transportStyle == i2p::data::RouterInfo::eTransportNTCP) { diff --git a/RouterInfo.cpp b/RouterInfo.cpp index f497c30e..b594cb7a 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -369,19 +369,19 @@ namespace data SetProperty ("caps", caps); } - void RouterInfo::WriteToStream (std::ostream& s) + void RouterInfo::WriteToStream (std::ostream& s) const { uint64_t ts = htobe64 (m_Timestamp); - s.write ((char *)&ts, sizeof (ts)); + s.write ((const char *)&ts, sizeof (ts)); // addresses uint8_t numAddresses = m_Addresses->size (); s.write ((char *)&numAddresses, sizeof (numAddresses)); - for (auto addr : *m_Addresses) + for (const auto& addr_ptr : *m_Addresses) { - Address& address = *addr; - s.write ((char *)&address.cost, sizeof (address.cost)); - s.write ((char *)&address.date, sizeof (address.date)); + const Address& address = *addr_ptr; + s.write ((const char *)&address.cost, sizeof (address.cost)); + s.write ((const char *)&address.date, sizeof (address.date)); std::stringstream properties; if (address.transportStyle == eTransportNTCP) WriteString ("NTCP", s); @@ -410,7 +410,7 @@ namespace data if (address.introducers.size () > 0) { int i = 0; - for (auto introducer: address.introducers) + for (const auto& introducer: address.introducers) { WriteString ("ihost" + boost::lexical_cast(i), properties); properties << '='; @@ -419,7 +419,7 @@ namespace data i++; } i = 0; - for (auto introducer: address.introducers) + for (const auto& introducer: address.introducers) { WriteString ("ikey" + boost::lexical_cast(i), properties); properties << '='; @@ -431,7 +431,7 @@ namespace data i++; } i = 0; - for (auto introducer: address.introducers) + for (const auto& introducer: address.introducers) { WriteString ("iport" + boost::lexical_cast(i), properties); properties << '='; @@ -440,7 +440,7 @@ namespace data i++; } i = 0; - for (auto introducer: address.introducers) + for (const auto& introducer: address.introducers) { WriteString ("itag" + boost::lexical_cast(i), properties); properties << '='; @@ -482,7 +482,7 @@ namespace data // properties std::stringstream properties; - for (auto& p : m_Properties) + for (const auto& p : m_Properties) { WriteString (p.first, properties); properties << '='; @@ -570,10 +570,10 @@ namespace data addr->cost = 2; addr->date = 0; addr->mtu = 0; - for (auto it: *m_Addresses) // don't insert same address twice + for (const auto& it: *m_Addresses) // don't insert same address twice if (*it == *addr) return; - m_Addresses->push_back(addr); m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4; + m_Addresses->push_back(std::move(addr)); } void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu) @@ -586,21 +586,22 @@ namespace data addr->date = 0; addr->mtu = mtu; memcpy (addr->key, key, 32); - for (auto it: *m_Addresses) // don't insert same address twice + for (const auto& it: *m_Addresses) // don't insert same address twice if (*it == *addr) return; - m_Addresses->push_back(addr); m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; - m_Caps |= eSSUTesting; - m_Caps |= eSSUIntroducer; + m_Addresses->push_back(std::move(addr)); + + m_Caps |= eSSUTesting; + m_Caps |= eSSUIntroducer; } bool RouterInfo::AddIntroducer (const Introducer& introducer) { - for (auto addr : *m_Addresses) + for (auto& addr : *m_Addresses) { if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) { - for (auto intro: addr->introducers) + for (auto& intro: addr->introducers) if (intro.iTag == introducer.iTag) return false; // already presented addr->introducers.push_back (introducer); return true; @@ -611,16 +612,16 @@ namespace data bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) { - for (auto addr: *m_Addresses) + for (auto& addr: *m_Addresses) { if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) { - for (std::vector::iterator it = addr->introducers.begin (); it != addr->introducers.end (); it++) + for (auto it = addr->introducers.begin (); it != addr->introducers.end (); ++it) if ( boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e) { addr->introducers.erase (it); return true; - } + } } } return false; @@ -707,7 +708,7 @@ namespace data if (addr->host.is_v6 ()) it = m_Addresses->erase (it); else - it++; + ++it; } } } @@ -723,7 +724,7 @@ namespace data if (addr->host.is_v4 ()) it = m_Addresses->erase (it); else - it++; + ++it; } } } @@ -751,8 +752,7 @@ namespace data std::shared_ptr RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const { - auto addresses = m_Addresses; - for (auto address : *addresses) + for (const auto& address : *m_Addresses) { if (address->transportStyle == s) { diff --git a/RouterInfo.h b/RouterInfo.h index 8c99d245..16937b5b 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -187,9 +187,9 @@ namespace data void ReadFromFile (); void ReadFromStream (std::istream& s); void ReadFromBuffer (bool verifySignature); - void WriteToStream (std::ostream& s); - size_t ReadString (char * str, std::istream& s); - void WriteString (const std::string& str, std::ostream& s); + void WriteToStream (std::ostream& s) const; + static size_t ReadString (char* str, std::istream& s); + static void WriteString (const std::string& str, std::ostream& s); void ExtractCaps (const char * value); std::shared_ptr GetAddress (TransportStyle s, bool v4only, bool v6only = false) const; void UpdateCapsProperty (); From 56a60772a412c4b350b8beffc6198a00db7694c3 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 8 Aug 2016 11:53:38 -0400 Subject: [PATCH 1714/6300] fixed potential deadlock --- AddressBook.cpp | 17 ++++++++++++----- Destination.cpp | 6 +++--- Destination.h | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 6fdde4eb..f778fff6 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -658,16 +658,23 @@ namespace client if (!leaseSet) { std::unique_lock l(newDataReceivedMutex); - i2p::client::context.GetSharedLocalDestination ()->RequestDestination (m_Ident, + if (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) + })) { - LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired"); - i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (m_Ident); + 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 (m_Ident, false); // don't notify, because we know it already + return false; + } + } + else + { + LogPrint (eLogError, "Addressbook: Destination is not ready"); return false; } } diff --git a/Destination.cpp b/Destination.cpp index b1ab2a6c..05216d18 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -486,17 +486,17 @@ namespace client return true; } - void LeaseSetDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest) + void LeaseSetDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest, bool notify) { auto s = shared_from_this (); - m_Service.post ([dest, s](void) + m_Service.post ([dest, notify, s](void) { auto it = s->m_LeaseSetRequests.find (dest); if (it != s->m_LeaseSetRequests.end ()) { auto requestComplete = it->second->requestComplete; s->m_LeaseSetRequests.erase (it); - if (requestComplete) requestComplete (nullptr); + if (notify && requestComplete) requestComplete (nullptr); } }); } diff --git a/Destination.h b/Destination.h index e64508c9..ac7ef7c9 100644 --- a/Destination.h +++ b/Destination.h @@ -79,7 +79,7 @@ namespace client bool IsReady () const { return m_LeaseSet && !m_LeaseSet->IsExpired () && m_Pool->GetOutboundTunnels ().size () > 0; }; std::shared_ptr FindLeaseSet (const i2p::data::IdentHash& ident); bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); - void CancelDestinationRequest (const i2p::data::IdentHash& dest); + void CancelDestinationRequest (const i2p::data::IdentHash& dest, bool notify = true); // implements GarlicDestination std::shared_ptr GetLeaseSet (); From ab1df3a1d08d6ce06a76b63a85867502cc990c9f Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Mon, 8 Aug 2016 23:37:16 +0300 Subject: [PATCH 1715/6300] Update HTTPServer.cpp --- HTTPServer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 8ca35100..228023d9 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -149,7 +149,8 @@ namespace http { " Transit tunnels
    \r\n" " Transports
    \r\n" " I2P tunnels
    \r\n" - " SAM sessions
    \r\n" + if (i2p::client::context.GetSAMBridge ()) + " SAM sessions
    \r\n" "
    \r\n" "
    "; } From 7ba4af7e2e9b6a75cddbb4c48da178cd1a6f5ec3 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 8 Aug 2016 17:31:32 -0400 Subject: [PATCH 1716/6300] fixed build error --- HTTPServer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 228023d9..8ca35100 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -149,8 +149,7 @@ namespace http { " Transit tunnels
    \r\n" " Transports
    \r\n" " I2P tunnels
    \r\n" - if (i2p::client::context.GetSAMBridge ()) - " SAM sessions
    \r\n" + " SAM sessions
    \r\n" "
    \r\n" "
    "; } From a530503c0c0ec1d7e01723abee074c5596c14bcb Mon Sep 17 00:00:00 2001 From: brain5lug Date: Tue, 9 Aug 2016 01:53:37 +0300 Subject: [PATCH 1717/6300] copy elimination for ranges #part3 --- SAM.cpp | 4 ++-- SAM.h | 2 +- SSU.cpp | 42 +++++++++++++++++++----------------------- SSUData.cpp | 14 +++++++------- SSUSession.cpp | 2 +- Streaming.cpp | 16 ++++++++-------- Transports.cpp | 10 +++++----- Transports.h | 2 +- TunnelConfig.h | 2 +- 9 files changed, 45 insertions(+), 49 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index 55eae222..2864cd6f 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -686,7 +686,7 @@ namespace client { { std::lock_guard lock(m_SocketsMutex); - for (auto sock : m_Sockets) { + for (auto& sock : m_Sockets) { sock->CloseStream(); } } @@ -719,7 +719,7 @@ namespace client { m_IsRunning = false; m_Acceptor.cancel (); - for (auto it: m_Sessions) + for (auto& it: m_Sessions) it.second->CloseStreams (); m_Sessions.clear (); m_Service.stop (); diff --git a/SAM.h b/SAM.h index ef36f285..db08c5a0 100644 --- a/SAM.h +++ b/SAM.h @@ -154,7 +154,7 @@ namespace client std::list > l; { std::lock_guard lock(m_SocketsMutex); - for( auto & sock : m_Sockets ) l.push_back(sock); + for(const auto& sock : m_Sockets ) l.push_back(sock); } return l; } diff --git a/SSU.cpp b/SSU.cpp index f4277a5a..0c3662d3 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -237,9 +237,8 @@ namespace transport std::map > * sessions) { std::shared_ptr session; - for (auto it1: packets) + for (auto& packet: packets) { - auto packet = it1; try { if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous @@ -431,11 +430,11 @@ namespace transport void SSUServer::DeleteAllSessions () { - for (auto it: m_Sessions) + for (auto& it: m_Sessions) it.second->Close (); m_Sessions.clear (); - for (auto it: m_SessionsV6) + for (auto& it: m_SessionsV6) it.second->Close (); m_SessionsV6.clear (); } @@ -444,7 +443,7 @@ namespace transport std::shared_ptr SSUServer::GetRandomV4Session (Filter filter) // v4 only { std::vector > filteredSessions; - for (auto s :m_Sessions) + for (const auto& s :m_Sessions) if (filter (s.second)) filteredSessions.push_back (s.second); if (filteredSessions.size () > 0) { @@ -468,7 +467,7 @@ namespace transport std::shared_ptr SSUServer::GetRandomV6Session (Filter filter) // v6 only { std::vector > filteredSessions; - for (auto s :m_SessionsV6) + for (const auto& s :m_SessionsV6) if (filter (s.second)) filteredSessions.push_back (s.second); if (filteredSessions.size () > 0) { @@ -535,7 +534,7 @@ namespace transport std::list newList; size_t numIntroducers = 0; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it :m_Introducers) + for (const auto& it : m_Introducers) { auto session = FindSession (it); if (session && ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION) @@ -552,23 +551,20 @@ namespace transport { // create new auto introducers = FindIntroducers (SSU_MAX_NUM_INTRODUCERS); - if (introducers.size () > 0) + for (const auto& it1: introducers) { - for (auto it1: introducers) + const auto& ep = it1->GetRemoteEndpoint (); + i2p::data::RouterInfo::Introducer introducer; + introducer.iHost = ep.address (); + introducer.iPort = ep.port (); + introducer.iTag = it1->GetRelayTag (); + introducer.iKey = it1->GetIntroKey (); + if (i2p::context.AddIntroducer (introducer)) { - auto& ep = it1->GetRemoteEndpoint (); - i2p::data::RouterInfo::Introducer introducer; - introducer.iHost = ep.address (); - introducer.iPort = ep.port (); - introducer.iTag = it1->GetRelayTag (); - introducer.iKey = it1->GetIntroKey (); - if (i2p::context.AddIntroducer (introducer)) - { - newList.push_back (ep); - if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break; - } - } - } + newList.push_back (ep); + if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break; + } + } } m_Introducers = newList; if (m_Introducers.size () < SSU_MAX_NUM_INTRODUCERS) @@ -637,7 +633,7 @@ namespace transport it = m_PeerTests.erase (it); } else - it++; + ++it; } if (numDeleted > 0) LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired"); diff --git a/SSUData.cpp b/SSUData.cpp index e802791d..e5abfd54 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -427,12 +427,12 @@ namespace transport if (ts >= it->second->nextResendTime) { if (it->second->numResends < MAX_NUM_RESENDS) - { + { for (auto& f: it->second->fragments) - if (f) + if (f) { try - { + { m_Session.Send (f->buf, f->len); // resend numResent++; } @@ -440,11 +440,11 @@ namespace transport { LogPrint (eLogWarning, "SSU: Can't resend data fragment ", ec.what ()); } - } + } it->second->numResends++; it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL; - it++; + ++it; } else { @@ -453,7 +453,7 @@ namespace transport } } else - it++; + ++it; } if (numResent < MAX_OUTGOING_WINDOW_SIZE) ScheduleResend (); @@ -487,7 +487,7 @@ namespace transport it = m_IncompleteMessages.erase (it); } else - it++; + ++it; } // decay if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || diff --git a/SSUSession.cpp b/SSUSession.cpp index 4e095deb..bf4058a5 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -907,7 +907,7 @@ namespace transport { if (m_State == eSessionStateEstablished) { - for (auto it: msgs) + for (const auto& it: msgs) if (it) m_Data.Send (it); } } diff --git a/Streaming.cpp b/Streaming.cpp index 3806d97d..90c363ea 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -247,7 +247,7 @@ namespace stream } int nackCount = packet->GetNACKCount (); for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();) - { + { auto seqn = (*it)->GetSeqn (); if (seqn <= ackThrough) { @@ -263,7 +263,7 @@ namespace stream if (nacked) { LogPrint (eLogDebug, "Streaming: Packet ", seqn, " NACK"); - it++; + ++it; continue; } } @@ -412,7 +412,7 @@ namespace stream } bool isEmpty = m_SentPackets.empty (); auto ts = i2p::util::GetMillisecondsSinceEpoch (); - for (auto it: packets) + for (auto& it: packets) { it->sendTime = ts; m_SentPackets.insert (it); @@ -762,7 +762,7 @@ namespace stream bool updated = false; if (expired && m_CurrentRemoteLease) { - for (auto it: leases) + for (const auto& it: leases) if ((it->tunnelGateway == m_CurrentRemoteLease->tunnelGateway) && (it->tunnelID != m_CurrentRemoteLease->tunnelID)) { m_CurrentRemoteLease = it; @@ -798,7 +798,7 @@ namespace stream StreamingDestination::~StreamingDestination () { - for (auto it: m_SavedPackets) + for (auto& it: m_SavedPackets) { for (auto it1: it.second) delete it1; it.second.clear (); @@ -877,7 +877,7 @@ namespace stream else // follow on packet without SYN { uint32_t receiveStreamID = packet->GetReceiveStreamID (); - for (auto it: m_Streams) + for (auto& it: m_Streams) if (it.second->GetSendStreamID () == receiveStreamID) { // found @@ -944,7 +944,7 @@ namespace stream m_Owner->GetService ().post([acceptor, this](void) { m_Acceptor = acceptor; - for (auto it: m_PendingIncomingStreams) + for (auto& it: m_PendingIncomingStreams) if (it->GetStatus () == eStreamStatusOpen) // still open? m_Acceptor (it); m_PendingIncomingStreams.clear (); @@ -963,7 +963,7 @@ namespace stream if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogWarning, "Streaming: Pending incoming timeout expired"); - for (auto it: m_PendingIncomingStreams) + for (auto& it: m_PendingIncomingStreams) it->Close (); m_PendingIncomingStreams.clear (); } diff --git a/Transports.cpp b/Transports.cpp index 4ad53d7f..b1c174f6 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -112,7 +112,7 @@ namespace transport m_Thread = new std::thread (std::bind (&Transports::Run, this)); // create acceptors auto& addresses = context.GetRouterInfo ().GetAddresses (); - for (auto address : addresses) + for (const auto& address : addresses) { if (m_NTCPServer == nullptr && enableNTCP) { @@ -236,7 +236,7 @@ namespace transport if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) { // we send it to ourself - for (auto it: msgs) + for (auto& it: msgs) i2p::HandleI2NPMessage (it); return; } @@ -266,7 +266,7 @@ namespace transport { if (it->second.delayedMessages.size () < MAX_NUM_DELAYED_MESSAGES) { - for (auto it1: msgs) + for (auto& it1: msgs) it->second.delayedMessages.push_back (it1); } else @@ -636,7 +636,7 @@ namespace transport it = m_Peers.erase (it); } else - it++; + ++it; } UpdateBandwidth (); // TODO: use separate timer(s) for it if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test @@ -658,7 +658,7 @@ namespace transport { std::lock_guard lock(m_FamilyMutex); m_TrustedFamilies.clear(); - for ( auto fam : families ) + for ( const auto& fam : families ) m_TrustedFamilies.push_back(fam); } diff --git a/Transports.h b/Transports.h index 708c55e2..81063916 100644 --- a/Transports.h +++ b/Transports.h @@ -60,7 +60,7 @@ namespace transport void Done () { - for (auto it: sessions) + for (auto& it: sessions) it->Done (); } }; diff --git a/TunnelConfig.h b/TunnelConfig.h index 7546c9b2..23417ed9 100644 --- a/TunnelConfig.h +++ b/TunnelConfig.h @@ -213,7 +213,7 @@ namespace tunnel void CreatePeers (const Peers& peers) { TunnelHopConfig * prev = nullptr; - for (auto it: peers) + for (const auto& it: peers) { auto hop = new TunnelHopConfig (it); if (prev) From 32466e3804ee52afeca9d644c31373e4d7165cec Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 8 Aug 2016 22:15:09 -0400 Subject: [PATCH 1718/6300] don't notify before wait --- AddressBook.cpp | 20 +++++++------------- Destination.cpp | 3 ++- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index f778fff6..e996b5f0 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -658,23 +658,17 @@ namespace client if (!leaseSet) { std::unique_lock l(newDataReceivedMutex); - if (i2p::client::context.GetSharedLocalDestination ()->RequestDestination (m_Ident, - [&newDataReceived, &leaseSet](std::shared_ptr ls) + i2p::client::context.GetSharedLocalDestination ()->RequestDestination (m_Ident, + [&newDataReceived, &leaseSet, &newDataReceivedMutex](std::shared_ptr ls) { leaseSet = ls; + std::unique_lock l1(newDataReceivedMutex); newDataReceived.notify_all (); - })) + }); + if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) { - 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 (m_Ident, false); // don't notify, because we know it already - return false; - } - } - else - { - LogPrint (eLogError, "Addressbook: Destination is not ready"); + LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired"); + i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (m_Ident, false); // don't notify, because we know it already return false; } } diff --git a/Destination.cpp b/Destination.cpp index 05216d18..42351787 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -479,7 +479,8 @@ namespace client { if (!m_Pool || !IsReady ()) { - if (requestComplete) requestComplete (nullptr); + if (requestComplete) + m_Service.post ([requestComplete](void){requestComplete (nullptr);}); return false; } m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete)); From 6a752a56ff33d9778444d6802b3177cfdf7901b5 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Tue, 9 Aug 2016 13:54:47 +0300 Subject: [PATCH 1719/6300] Update HTTPServer.cpp --- HTTPServer.cpp | 84 ++++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 8ca35100..f10289ef 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -68,7 +68,7 @@ namespace http { const char HTTP_PAGE_TUNNELS[] = "tunnels"; const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels"; - const char HTTP_PAGE_TRANSPORTS[] = "transports"; + const char HTTP_PAGE_TRANSPORTS[] = "transports"; const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations"; const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination"; const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions"; @@ -82,7 +82,7 @@ namespace http { const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; - const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; + const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; @@ -144,12 +144,14 @@ namespace http { " Main page
    \r\n
    \r\n" " Router commands
    \r\n" " Local destinations
    \r\n" - " Lease Sets
    \r\n" + " LeaseSets
    \r\n" " Tunnels
    \r\n" " Transit tunnels
    \r\n" " Transports
    \r\n" - " I2P tunnels
    \r\n" - " SAM sessions
    \r\n" + " I2P tunnels
    \r\n"; + if (i2p::client::context.GetSAMBridge ()) + s << " SAM sessions
    \r\n"; + s << "
    \r\n" "
    "; } @@ -239,9 +241,9 @@ namespace http { size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); - - s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; - s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
    \r\n"; + + s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; + s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
    \r\n"; } void ShowLocalDestinations (std::stringstream& s) @@ -280,18 +282,18 @@ namespace http { it->Print(s); ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ()); } - } + } s << "
    \r\n"; s << "Tags
    Incoming: " << dest->GetNumIncomingTags () << "
    Outgoing:
    " << std::endl; for (const auto& it: dest->GetSessions ()) { s << i2p::client::context.GetAddressBook ().ToAddress(it.first) << " "; s << it.second->GetNumOutgoingTags () << "
    " << std::endl; - } + } s << "
    " << std::endl; // s << "
    \r\nStreams:
    \r\n"; // for (auto it: dest->GetStreamingDestination ()->GetStreams ()) - // { + // { // s << it.first << "->" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << " "; // s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; // s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]"; @@ -300,7 +302,7 @@ namespace http { // s << "[Window:" << it.second->GetWindowSize () << "]"; // s << "[Status:" << (int)it.second->GetStatus () << "]"; // s << "
    \r\n"<< std::endl; - // } + // } s << "
    \r\n"; s << ""; s << ""; @@ -315,7 +317,7 @@ namespace http { s << ""; for (const auto& it: dest->GetAllStreams ()) - { + { s << ""; s << ""; s << ""; @@ -329,12 +331,12 @@ namespace http { s << ""; s << "
    \r\n" << std::endl; } - } + } } void ShowLeasesSets(std::stringstream& s) { - s << "
    LeaseSets

    "; + s << "
    LeaseSets:

    "; // for each lease set i2p::data::netdb.VisitLeaseSets( [&s](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) @@ -382,7 +384,7 @@ namespace http { ); // end for each lease set } - + void ShowTunnels (std::stringstream& s) { s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
    \r\n"; @@ -399,7 +401,7 @@ namespace http { ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ()); } s << "
    \r\n"; - } + } void ShowCommands (std::stringstream& s) { @@ -409,7 +411,7 @@ namespace http { //s << " Reload config
    \r\n"; if (i2p::context.AcceptsTunnels ()) s << " Decline transit tunnels
    \r\n"; - else + else s << " Accept transit tunnels
    \r\n"; #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) if (Daemon.gracefullShutdownInterval) @@ -435,11 +437,11 @@ namespace http { for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { if (std::dynamic_pointer_cast(it)) - s << it->GetTunnelID () << " ⇒ "; + s << it->GetTunnelID () << " ⇒ "; else if (std::dynamic_pointer_cast(it)) - s << " ⇒ " << it->GetTunnelID (); + s << " ⇒ " << it->GetTunnelID (); else - s << " ⇒ " << it->GetTunnelID () << " ⇒ "; + s << " ⇒ " << it->GetTunnelID () << " ⇒ "; s << " " << it->GetNumTransmittedBytes () << "
    \r\n"; } } @@ -449,22 +451,22 @@ namespace http { s << "Transports:
    \r\n
    \r\n"; auto ntcpServer = i2p::transport::transports.GetNTCPServer (); if (ntcpServer) - { + { s << "NTCP
    \r\n"; for (const auto& it: ntcpServer->GetNTCPSessions ()) { if (it.second && it.second->IsEstablished ()) { // incoming connection doesn't have remote RI - if (it.second->IsOutgoing ()) s << " ⇒ "; + if (it.second->IsOutgoing ()) s << " ⇒ "; s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " << it.second->GetSocket ().remote_endpoint().address ().to_string (); - if (!it.second->IsOutgoing ()) s << " ⇒ "; + if (!it.second->IsOutgoing ()) s << " ⇒ "; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; s << "
    \r\n" << std::endl; } } - } + } auto ssuServer = i2p::transport::transports.GetSSUServer (); if (ssuServer) { @@ -472,9 +474,9 @@ namespace http { for (const auto& it: ssuServer->GetSessions ()) { auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second->IsOutgoing ()) s << " ⇒ "; + if (it.second->IsOutgoing ()) s << " ⇒ "; s << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it.second->IsOutgoing ()) s << " ⇒ "; + if (!it.second->IsOutgoing ()) s << " ⇒ "; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; if (it.second->GetRelayTag ()) s << " [itag:" << it.second->GetRelayTag () << "]"; @@ -484,15 +486,15 @@ namespace http { for (const auto& it: ssuServer->GetSessionsV6 ()) { auto endpoint = it.second->GetRemoteEndpoint (); - if (it.second->IsOutgoing ()) s << " ⇒ "; + if (it.second->IsOutgoing ()) s << " ⇒ "; s << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it.second->IsOutgoing ()) s << " ⇒ "; + if (!it.second->IsOutgoing ()) s << " ⇒ "; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; s << "
    \r\n" << std::endl; } } } - + void ShowSAMSessions (std::stringstream& s) { auto sam = i2p::client::context.GetSAMBridge (); @@ -505,8 +507,8 @@ namespace http { { s << ""; s << it.first << "
    \r\n" << std::endl; - } - } + } + } void ShowSAMSession (std::stringstream& s, const std::string& id) { @@ -537,8 +539,8 @@ namespace http { } s << " [" << it->GetSocket ().remote_endpoint() << "]"; s << "
    \r\n"; - } - } + } + } void ShowI2PTunnels (std::stringstream& s) { @@ -547,21 +549,21 @@ namespace http { { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << ""; - s << it.second->GetName () << " ⇠"; + s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
    \r\n"<< std::endl; - } + } s << "
    \r\nServer Tunnels:
    \r\n
    \r\n"; for (auto& it: i2p::client::context.GetServerTunnels ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << ""; - s << it.second->GetName () << " ⇒ "; + s << it.second->GetName () << " ⇒ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << ":" << it.second->GetLocalPort (); s << "
    \r\n"<< std::endl; - } - } + } + } HTTPConnection::HTTPConnection (std::shared_ptr socket): m_Socket (socket), m_Timer (socket->get_io_service ()), m_BufferLen (0) @@ -734,7 +736,7 @@ namespace http { Daemon.gracefullShutdownInterval = 10*60; #endif #ifdef WIN32_APP - i2p::win32::GracefulShutdown (); + i2p::win32::GracefulShutdown (); #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); @@ -752,7 +754,7 @@ namespace http { s << "Back to commands list
    \r\n"; s << "

    You will be redirected in 5 seconds"; res.add_header("Refresh", "5; url=/?page=commands"); - } + } void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) { From 2dd5de4373b5303ad29ec7bfc2bddbfe28fd7c80 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Aug 2016 10:17:40 -0400 Subject: [PATCH 1720/6300] handle default subscription in separate thread --- AddressBook.cpp | 13 ++++++------- AddressBook.h | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index e996b5f0..b3f0f1ce 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -260,8 +260,6 @@ namespace client m_Storage = nullptr; } m_DefaultSubscription = nullptr; - for (auto& it: m_Subscriptions) - delete it; m_Subscriptions.clear (); } @@ -403,7 +401,7 @@ namespace client { getline(f, s); if (!s.length()) continue; // skip empty line - m_Subscriptions.push_back (new AddressBookSubscription (*this, s)); + m_Subscriptions.push_back (std::make_shared (*this, s)); } LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); } @@ -462,7 +460,7 @@ namespace client int nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT; if (success) { - if (m_DefaultSubscription) m_DefaultSubscription.reset (nullptr); + if (m_DefaultSubscription) m_DefaultSubscription = nullptr; if (m_IsLoaded) nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT; else @@ -516,16 +514,17 @@ namespace client // download it from http://i2p-projekt.i2p/hosts.txt LogPrint (eLogInfo, "Addressbook: trying to download it from default subscription."); if (!m_DefaultSubscription) - m_DefaultSubscription.reset (new AddressBookSubscription (*this, DEFAULT_SUBSCRIPTION_ADDRESS)); + m_DefaultSubscription = std::make_shared(*this, DEFAULT_SUBSCRIPTION_ADDRESS); m_IsDownloading = true; - m_DefaultSubscription->CheckUpdates (); + std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription)); + load_hosts.detach(); // TODO: use join } else if (!m_Subscriptions.empty ()) { // pick random subscription auto ind = rand () % m_Subscriptions.size(); m_IsDownloading = true; - std::thread load_hosts(&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind]); + std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind])); load_hosts.detach(); // TODO: use join } } diff --git a/AddressBook.h b/AddressBook.h index fd852907..7f559cd0 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -102,8 +102,8 @@ namespace client std::map m_Lookups; // nonce -> address AddressBookStorage * m_Storage; volatile bool m_IsLoaded, m_IsDownloading; - std::vector m_Subscriptions; - std::unique_ptr m_DefaultSubscription; // in case if we don't know any addresses yet + std::vector > m_Subscriptions; + std::shared_ptr m_DefaultSubscription; // in case if we don't know any addresses yet boost::asio::deadline_timer * m_SubscriptionsUpdateTimer; }; From 94642f90666feacb5d49bb5e423e40d2e1b7cb50 Mon Sep 17 00:00:00 2001 From: brain5lug Date: Wed, 10 Aug 2016 01:16:24 +0300 Subject: [PATCH 1721/6300] copy elimination for ranges #part4 --- Tunnel.cpp | 15 +++++++-------- TunnelGateway.cpp | 2 +- TunnelPool.cpp | 29 ++++++++++++++--------------- UPnP.cpp | 8 ++++---- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index fd971628..6a6accd6 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -345,7 +345,7 @@ namespace tunnel { std::shared_ptr tunnel; size_t minReceived = 0; - for (auto it : m_InboundTunnels) + for (const auto& it : m_InboundTunnels) { if (!it->IsEstablished ()) continue; if (!tunnel || it->GetNumReceivedBytes () < minReceived) @@ -362,7 +362,7 @@ namespace tunnel if (m_OutboundTunnels.empty ()) return nullptr; uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0; std::shared_ptr tunnel; - for (auto it: m_OutboundTunnels) + for (const auto& it: m_OutboundTunnels) { if (it->IsEstablished ()) { @@ -586,7 +586,7 @@ namespace tunnel m_NumFailedTunnelCreations++; } else - it++; + ++it; break; case eTunnelStateBuildFailed: LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " failed, deleted"); @@ -595,7 +595,7 @@ namespace tunnel break; case eTunnelStateBuildReplyReceived: // intermediate state, will be either established of build failed - it++; + ++it; break; default: // success @@ -635,7 +635,7 @@ namespace tunnel if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) tunnel->SetState (eTunnelStateExpiring); } - it++; + ++it; } } } @@ -738,9 +738,8 @@ namespace tunnel void Tunnels::ManageTunnelPools () { std::unique_lock l(m_PoolsMutex); - for (auto it: m_Pools) + for (auto& pool : m_Pools) { - auto pool = it; if (pool && pool->IsActive ()) { pool->CreateTunnels (); @@ -856,7 +855,7 @@ namespace tunnel int timeout = 0; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); // TODO: possible race condition with I2PControl - for (auto it: m_TransitTunnels) + for (const auto& it : m_TransitTunnels) { int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; if (t > timeout) timeout = t; diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index d701e24b..ad423fc0 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -197,7 +197,7 @@ namespace tunnel { m_Buffer.CompleteCurrentTunnelDataMessage (); auto tunnelMsgs = m_Buffer.GetTunnelDataMsgs (); - for (auto tunnelMsg : tunnelMsgs) + for (auto& tunnelMsg : tunnelMsgs) { m_Tunnel->EncryptTunnelMsg (tunnelMsg, tunnelMsg); tunnelMsg->FillI2NPMessageHeader (eI2NPTunnelData); diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 4bd116cb..119556aa 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -49,13 +49,13 @@ namespace tunnel { { std::unique_lock l(m_InboundTunnelsMutex); - for (auto it: m_InboundTunnels) + for (auto& it: m_InboundTunnels) it->SetTunnelPool (nullptr); m_InboundTunnels.clear (); } { std::unique_lock l(m_OutboundTunnelsMutex); - for (auto it: m_OutboundTunnels) + for (auto& it: m_OutboundTunnels) it->SetTunnelPool (nullptr); m_OutboundTunnels.clear (); } @@ -78,7 +78,7 @@ namespace tunnel if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); - for (auto it: m_Tests) + for (auto& it: m_Tests) if (it.second.second == expiredTunnel) it.second.second = nullptr; std::unique_lock l(m_InboundTunnelsMutex); @@ -101,7 +101,7 @@ namespace tunnel if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); - for (auto it: m_Tests) + for (auto& it: m_Tests) if (it.second.first == expiredTunnel) it.second.first = nullptr; std::unique_lock l(m_OutboundTunnelsMutex); @@ -114,7 +114,7 @@ namespace tunnel std::vector > v; int i = 0; std::unique_lock l(m_InboundTunnelsMutex); - for (auto it : m_InboundTunnels) + for (const auto& it : m_InboundTunnels) { if (i >= num) break; if (it->IsEstablished ()) @@ -144,7 +144,7 @@ namespace tunnel if (tunnels.empty ()) return nullptr; uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0; typename TTunnels::value_type tunnel = nullptr; - for (auto it: tunnels) + for (const auto& it: tunnels) { if (it->IsEstablished () && it != excluded) { @@ -164,7 +164,7 @@ namespace tunnel if (old) { std::unique_lock l(m_OutboundTunnelsMutex); - for (auto it: m_OutboundTunnels) + for (const auto& it: m_OutboundTunnels) if (it->IsEstablished () && old->GetEndpointIdentHash () == it->GetEndpointIdentHash ()) { tunnel = it; @@ -182,7 +182,7 @@ namespace tunnel int num = 0; { std::unique_lock l(m_InboundTunnelsMutex); - for (auto it : m_InboundTunnels) + for (const auto& it : m_InboundTunnels) if (it->IsEstablished ()) num++; } for (int i = num; i < m_NumInboundTunnels; i++) @@ -191,7 +191,7 @@ namespace tunnel num = 0; { std::unique_lock l(m_OutboundTunnelsMutex); - for (auto it : m_OutboundTunnels) + for (const auto& it : m_OutboundTunnels) if (it->IsEstablished ()) num++; } for (int i = num; i < m_NumOutboundTunnels; i++) @@ -203,11 +203,10 @@ namespace tunnel decltype(m_Tests) tests; { std::unique_lock l(m_TestsMutex); - tests = m_Tests; - m_Tests.clear (); + tests.swap(m_Tests); } - for (auto it: tests) + for (auto& it: tests) { LogPrint (eLogWarning, "Tunnels: test of tunnel ", it.first, " failed"); // if test failed again with another tunnel we consider it failed @@ -248,12 +247,12 @@ namespace tunnel if ((*it1)->IsFailed ()) { failed = true; - it1++; + ++it1; } if ((*it2)->IsFailed ()) { failed = true; - it2++; + ++it2; } if (!failed) { @@ -265,7 +264,7 @@ namespace tunnel } (*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), CreateDeliveryStatusMsg (msgID)); - it1++; it2++; + ++it1; ++it2; } } } diff --git a/UPnP.cpp b/UPnP.cpp index db0d3683..612b1441 100644 --- a/UPnP.cpp +++ b/UPnP.cpp @@ -122,8 +122,8 @@ namespace transport void UPnP::PortMapping () { - auto a = context.GetRouterInfo().GetAddresses(); - for (auto address : a) + const auto& a = context.GetRouterInfo().GetAddresses(); + for (const auto& address : a) { if (!address->host.is_v6 ()) TryPortMapping (address); @@ -139,8 +139,8 @@ namespace transport void UPnP::CloseMapping () { - auto a = context.GetRouterInfo().GetAddresses(); - for (auto address : a) + const auto& a = context.GetRouterInfo().GetAddresses(); + for (const auto& address : a) { if (!address->host.is_v6 ()) CloseMapping (address); From 8e835f2f6b03d6ab563a8575a964007283e3d5b4 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Aug 2016 20:51:54 -0400 Subject: [PATCH 1722/6300] fixed race condition --- RouterInfo.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index b594cb7a..90cf5e44 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -752,7 +752,8 @@ namespace data std::shared_ptr RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const { - for (const auto& address : *m_Addresses) + auto addresses = m_Addresses; + for (const auto& address : *addresses) { if (address->transportStyle == s) { From c27f8a5c1e9eba348e8b4b5e5dca74bbf7e6bafa Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Thu, 11 Aug 2016 12:25:26 +0300 Subject: [PATCH 1723/6300] Replaced arrows to HTML code. Deleted tab spaces. --- Tunnel.cpp | 317 ++++++++++++++++++++++++++--------------------------- 1 file changed, 158 insertions(+), 159 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 6a6accd6..7d2e6735 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -13,19 +13,19 @@ #include "Tunnel.h" namespace i2p -{ +{ namespace tunnel -{ - +{ + Tunnel::Tunnel (std::shared_ptr config): TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()), m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false) { - } + } Tunnel::~Tunnel () { - } + } void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { @@ -33,7 +33,7 @@ namespace tunnel int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops; auto msg = NewI2NPShortMessage (); *msg->GetPayload () = numRecords; - msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1; + msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1; // shuffle records std::vector recordIndicies; @@ -56,13 +56,13 @@ namespace tunnel hop->recordIndex = idx; i++; hop = hop->next; - } - // fill up fake records with random data + } + // fill up fake records with random data for (int i = numHops; i < numRecords; i++) { int idx = recordIndicies[i]; RAND_bytes (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE); - } + } // decrypt real records i2p::crypto::CBCDecryption decryption; @@ -73,31 +73,31 @@ namespace tunnel // decrypt records after current hop TunnelHopConfig * hop1 = hop->next; while (hop1) - { + { decryption.SetIV (hop->replyIV); uint8_t * record = records + hop1->recordIndex*TUNNEL_BUILD_RECORD_SIZE; decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); hop1 = hop1->next; - } + } hop = hop->prev; - } + } msg->FillI2NPMessageHeader (eI2NPVariableTunnelBuild); // send message if (outboundTunnel) - outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); + outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); else i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); - } - + } + bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) { LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); - + i2p::crypto::CBCDecryption decryption; TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) - { + { decryption.SetKey (hop->replyKey); // decrypt records before and including current hop TunnelHopConfig * hop1 = hop; @@ -105,22 +105,22 @@ namespace tunnel { auto idx = hop1->recordIndex; if (idx >= 0 && idx < msg[0]) - { + { uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE; decryption.SetIV (hop->replyIV); decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); - } + } else LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); hop1 = hop1->prev; - } + } hop = hop->prev; } bool established = true; hop = m_Config->GetFirstHop (); while (hop) - { + { const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE; uint8_t ret = record[BUILD_RESPONSE_RECORD_RET_OFFSET]; LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); @@ -143,12 +143,12 @@ namespace tunnel tunnelHop->decryption.SetKeys (hop->layerKey, hop->ivKey); m_Hops.push_back (std::unique_ptr(tunnelHop)); hop = hop->prev; - } + } m_Config = nullptr; - } + } if (established) m_State = eTunnelStateEstablished; return established; - } + } void Tunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { @@ -158,20 +158,20 @@ namespace tunnel { it->decryption.Decrypt (inPayload, outPayload); inPayload = outPayload; - } - } + } + } void Tunnel::SendTunnelDataMsg (std::shared_ptr msg) { LogPrint (eLogWarning, "Tunnel: Can't send I2NP messages without delivery instructions"); - } + } std::vector > Tunnel::GetPeers () const { auto peers = GetInvertedPeers (); - std::reverse (peers.begin (), peers.end ()); + std::reverse (peers.begin (), peers.end ()); return peers; - } + } std::vector > Tunnel::GetInvertedPeers () const { @@ -179,54 +179,54 @@ namespace tunnel std::vector > ret; for (auto& it: m_Hops) ret.push_back (it->ident); - return ret; - } + return ret; + } void Tunnel::PrintHops (std::stringstream& s) const { for (auto& it: m_Hops) - { - s << " ⇒ "; + { + s << " ⇒ "; s << i2p::data::GetIdentHashAbbreviation (it->ident->GetIdentHash ()); - } - } - + } + } + void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) { - if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive + if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive auto newMsg = CreateEmptyTunnelDataMsg (); EncryptTunnelMsg (msg, newMsg); newMsg->from = shared_from_this (); - m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); - } + m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); + } void InboundTunnel::Print (std::stringstream& s) const { PrintHops (s); - s << " ⇒ " << GetTunnelID () << ":me"; - } + s << " ⇒ " << GetTunnelID () << ":me"; + } ZeroHopsInboundTunnel::ZeroHopsInboundTunnel (): InboundTunnel (std::make_shared ()), m_NumReceivedBytes (0) { - } - + } + void ZeroHopsInboundTunnel::SendTunnelDataMsg (std::shared_ptr msg) { if (msg) - { + { m_NumReceivedBytes += msg->GetLength (); msg->from = shared_from_this (); HandleI2NPMessage (msg); - } - } + } + } void ZeroHopsInboundTunnel::Print (std::stringstream& s) const { - s << " ⇒ " << GetTunnelID () << ":me"; - } - + s << " ⇒ " << GetTunnelID () << ":me"; + } + void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) { TunnelMessageBlock block; @@ -234,28 +234,28 @@ namespace tunnel { block.hash = gwHash; if (gwTunnel) - { + { block.deliveryType = eDeliveryTypeTunnel; block.tunnelID = gwTunnel; - } + } else block.deliveryType = eDeliveryTypeRouter; - } - else + } + else block.deliveryType = eDeliveryTypeLocal; block.data = msg; - + SendTunnelDataMsg ({block}); } - + void OutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) { std::unique_lock l(m_SendMutex); for (auto& it : msgs) m_Gateway.PutTunnelDataMsg (it); m_Gateway.SendBuffer (); - } - + } + void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ()); @@ -265,9 +265,9 @@ namespace tunnel { s << GetTunnelID () << ":me"; PrintHops (s); - s << " ⇒ "; - } - + s << " ⇒ "; + } + ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel (): OutboundTunnel (std::make_shared ()), m_NumSentBytes (0) @@ -286,30 +286,30 @@ namespace tunnel case eDeliveryTypeTunnel: i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); break; - case eDeliveryTypeRouter: + case eDeliveryTypeRouter: i2p::transport::transports.SendMessage (msg.hash, msg.data); break; default: LogPrint (eLogError, "Tunnel: Unknown delivery type ", (int)msg.deliveryType); - } + } } } void ZeroHopsOutboundTunnel::Print (std::stringstream& s) const { - s << GetTunnelID () << ":me ⇒ "; + s << GetTunnelID () << ":me ⇒ "; } Tunnels tunnels; - + Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0) { } - - Tunnels::~Tunnels () + + Tunnels::~Tunnels () { - } + } std::shared_ptr Tunnels::GetTunnel (uint32_t tunnelID) { @@ -317,29 +317,29 @@ namespace tunnel if (it != m_Tunnels.end ()) return it->second; return nullptr; - } - + } + std::shared_ptr Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID) { - return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels); + return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels); } std::shared_ptr Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID) { - return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels); - } + return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels); + } - template + template std::shared_ptr Tunnels::GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels) { auto it = pendingTunnels.find(replyMsgID); if (it != pendingTunnels.end () && it->second->GetState () == eTunnelStatePending) - { - it->second->SetState (eTunnelStateBuildReplyReceived); + { + it->second->SetState (eTunnelStateBuildReplyReceived); return it->second; } return nullptr; - } + } std::shared_ptr Tunnels::GetNextInboundTunnel () { @@ -353,26 +353,26 @@ namespace tunnel tunnel = it; minReceived = it->GetNumReceivedBytes (); } - } + } return tunnel; } - + std::shared_ptr Tunnels::GetNextOutboundTunnel () { if (m_OutboundTunnels.empty ()) return nullptr; uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0; std::shared_ptr tunnel; for (const auto& it: m_OutboundTunnels) - { + { if (it->IsEstablished ()) { tunnel = it; i++; } if (i > ind && tunnel) break; - } + } return tunnel; - } + } std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels) @@ -381,19 +381,19 @@ namespace tunnel std::unique_lock l(m_PoolsMutex); m_Pools.push_back (pool); return pool; - } + } void Tunnels::DeleteTunnelPool (std::shared_ptr pool) { if (pool) - { + { StopTunnelPool (pool); { std::unique_lock l(m_PoolsMutex); m_Pools.remove (pool); - } - } - } + } + } + } void Tunnels::StopTunnelPool (std::shared_ptr pool) { @@ -401,47 +401,47 @@ namespace tunnel { pool->SetActive (false); pool->DetachTunnels (); - } - } - + } + } + void Tunnels::AddTransitTunnel (std::shared_ptr tunnel) { if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) m_TransitTunnels.push_back (tunnel); else LogPrint (eLogError, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " already exists"); - } + } void Tunnels::Start () { m_IsRunning = true; m_Thread = new std::thread (std::bind (&Tunnels::Run, this)); } - + void Tunnels::Stop () { m_IsRunning = false; m_Queue.WakeUp (); if (m_Thread) - { + { m_Thread->join (); delete m_Thread; m_Thread = 0; - } - } + } + } void Tunnels::Run () { std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready - + uint64_t lastTs = 0; while (m_IsRunning) { try - { + { auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec if (msg) - { + { uint32_t prevTunnelID = 0, tunnelID = 0; std::shared_ptr prevTunnel; do @@ -449,16 +449,16 @@ namespace tunnel std::shared_ptr tunnel; uint8_t typeID = msg->GetTypeID (); switch (typeID) - { + { case eI2NPTunnelData: case eI2NPTunnelGateway: - { + { tunnelID = bufbe32toh (msg->GetPayload ()); if (tunnelID == prevTunnelID) tunnel = prevTunnel; else if (prevTunnel) prevTunnel->FlushTunnelDataMsgs (); - + if (!tunnel) tunnel = GetTunnel (tunnelID); if (tunnel) @@ -472,17 +472,17 @@ namespace tunnel LogPrint (eLogWarning, "Tunnel: tunnel not found, tunnelID=", tunnelID, " previousTunnelID=", prevTunnelID, " type=", (int)typeID); break; - } - case eI2NPVariableTunnelBuild: + } + case eI2NPVariableTunnelBuild: case eI2NPVariableTunnelBuildReply: case eI2NPTunnelBuild: - case eI2NPTunnelBuildReply: + case eI2NPTunnelBuildReply: HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); - break; + break; default: LogPrint (eLogWarning, "Tunnel: unexpected messsage type ", (int) typeID); } - + msg = m_Queue.Get (); if (msg) { @@ -493,8 +493,8 @@ namespace tunnel tunnel->FlushTunnelDataMsgs (); } while (msg); - } - + } + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); if (ts - lastTs >= 15) // manage tunnels every 15 seconds { @@ -505,9 +505,9 @@ namespace tunnel catch (std::exception& ex) { LogPrint (eLogError, "Tunnel: runtime exception: ", ex.what ()); - } - } - } + } + } + } void Tunnels::HandleTunnelGatewayMsg (std::shared_ptr tunnel, std::shared_ptr msg) { @@ -528,7 +528,7 @@ namespace tunnel msg->len = msg->offset + len; auto typeID = msg->GetTypeID (); LogPrint (eLogDebug, "Tunnel: gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID); - + if (IsRouterInfoMsg (msg) || typeID == eI2NPDatabaseSearchReply) // transit DatabaseStore my contain new/updated RI // or DatabaseSearchReply with new routers @@ -543,7 +543,7 @@ namespace tunnel ManageOutboundTunnels (); ManageTransitTunnels (); ManageTunnelPools (); - } + } void Tunnels::ManagePendingTunnels () { @@ -557,7 +557,7 @@ namespace tunnel // check pending tunnel. delete failed or timeout uint64_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();) - { + { auto tunnel = it->second; switch (tunnel->GetState ()) { @@ -579,8 +579,8 @@ namespace tunnel profile->TunnelNonReplied (); } hop = hop->next; - } - } + } + } // delete it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; @@ -596,13 +596,13 @@ namespace tunnel case eTunnelStateBuildReplyReceived: // intermediate state, will be either established of build failed ++it; - break; + break; default: // success it = pendingTunnels.erase (it); m_NumSuccesiveTunnelCreations++; - } - } + } + } } void Tunnels::ManageOutboundTunnels () @@ -620,26 +620,26 @@ namespace tunnel pool->TunnelExpired (tunnel); // we don't have outbound tunnels in m_Tunnels it = m_OutboundTunnels.erase (it); - } + } else { if (tunnel->IsEstablished ()) - { + { if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - tunnel->SetIsRecreated (); + tunnel->SetIsRecreated (); auto pool = tunnel->GetTunnelPool (); if (pool) pool->RecreateOutboundTunnel (tunnel); } if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) tunnel->SetState (eTunnelStateExpiring); - } + } ++it; } } - } - + } + if (m_OutboundTunnels.size () < 5) { // trying to create one more oubound tunnel @@ -648,12 +648,12 @@ namespace tunnel if (!inboundTunnel || !router) return; LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel"); CreateTunnel ( - std::make_shared (std::vector > { router->GetRouterIdentity () }, - inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) - ); + std::make_shared (std::vector > { router->GetRouterIdentity () }, + inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) + ); } } - + void Tunnels::ManageInboundTunnels () { uint64_t ts = i2p::util::GetSecondsSinceEpoch (); @@ -669,26 +669,26 @@ namespace tunnel pool->TunnelExpired (tunnel); m_Tunnels.erase (tunnel->GetTunnelID ()); it = m_InboundTunnels.erase (it); - } + } else { if (tunnel->IsEstablished ()) - { + { if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { - tunnel->SetIsRecreated (); + tunnel->SetIsRecreated (); auto pool = tunnel->GetTunnelPool (); if (pool) pool->RecreateInboundTunnel (tunnel); } - + if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) tunnel->SetState (eTunnelStateExpiring); - } + } it++; } } - } + } if (m_InboundTunnels.empty ()) { @@ -702,10 +702,10 @@ namespace tunnel } return; } - + if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 5) { - // trying to create one more inbound tunnel + // trying to create one more inbound tunnel auto router = i2p::data::netdb.GetRandomRouter (); if (!router) { LogPrint (eLogWarning, "Tunnel: can't find any router, skip creating tunnel"); @@ -714,9 +714,9 @@ namespace tunnel LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }) - ); + ); } - } + } void Tunnels::ManageTransitTunnels () { @@ -729,35 +729,35 @@ namespace tunnel LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired"); m_Tunnels.erase (tunnel->GetTunnelID ()); it = m_TransitTunnels.erase (it); - } + } else it++; } - } + } void Tunnels::ManageTunnelPools () { std::unique_lock l(m_PoolsMutex); for (auto& pool : m_Pools) - { + { if (pool && pool->IsActive ()) - { + { pool->CreateTunnels (); pool->TestTunnels (); - } + } } - } - + } + void Tunnels::PostTunnelData (std::shared_ptr msg) { - if (msg) m_Queue.Put (msg); - } + if (msg) m_Queue.Put (msg); + } void Tunnels::PostTunnelData (const std::vector >& msgs) { m_Queue.Put (msgs); - } - + } + template std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) { @@ -767,7 +767,7 @@ namespace tunnel AddPendingTunnel (replyMsgID, newTunnel); newTunnel->Build (replyMsgID, outboundTunnel); return newTunnel; - } + } std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) { @@ -804,20 +804,20 @@ namespace tunnel pool->TunnelCreated (newTunnel); else newTunnel->SetTunnelPool (nullptr); - } + } void Tunnels::AddInboundTunnel (std::shared_ptr newTunnel) { if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second) - { + { m_InboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (!pool) - { + { // build symmetric outbound tunnel CreateTunnel (std::make_shared(newTunnel->GetInvertedPeers (), newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), - GetNextOutboundTunnel ()); + GetNextOutboundTunnel ()); } else { @@ -829,9 +829,9 @@ namespace tunnel } else LogPrint (eLogError, "Tunnel: tunnel with id ", newTunnel->GetTunnelID (), " already exists"); - } + } + - std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel () { auto inboundTunnel = std::make_shared (); @@ -839,7 +839,7 @@ namespace tunnel m_InboundTunnels.push_back (inboundTunnel); m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; return inboundTunnel; - } + } std::shared_ptr Tunnels::CreateZeroHopsOutboundTunnel () { @@ -859,7 +859,7 @@ namespace tunnel { int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; if (t > timeout) timeout = t; - } + } return timeout; } @@ -882,4 +882,3 @@ namespace tunnel } } } - From 702e6c8080e3f7a936a0e9c5f7e7f0bd4703c6cb Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Aug 2016 09:30:35 -0400 Subject: [PATCH 1724/6300] buld instruction without QT --- docs/build_notes_android.md | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/docs/build_notes_android.md b/docs/build_notes_android.md index 71f0b2bc..b6cbd978 100644 --- a/docs/build_notes_android.md +++ b/docs/build_notes_android.md @@ -1,6 +1,8 @@ Building on Android =================== +There are two versions: with QT and without QT. + Pre-requesties -------------- @@ -8,12 +10,12 @@ You need to install Android SDK, NDK and QT with android support. - [SDK](https://developer.android.com/studio/index.html) (choose command line tools only) - [NDK](https://developer.android.com/ndk/downloads/index.html) -- [QT](https://www.qt.io/download-open-source/). Choose one for your platform for android. For example QT 5.6 under Linux would be [this file](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-linux-x64-android-5.6.1-1.run ) +- [QT](https://www.qt.io/download-open-source/).(for QT only) Choose one for your platform for android. For example QT 5.6 under Linux would be [this file](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-linux-x64-android-5.6.1-1.run ) You also need Java JDK and Ant. -QT-Creator ----------- +QT-Creator (for QT only) +------------------------ Open QT-creator that should be installed with QT. Go to Settings/Anndroid and specify correct paths to SDK and NDK. If everything is correct you will see two set avaiable: @@ -30,11 +32,26 @@ git clone https://github.com/PurpleI2P/android-ifaddrs.git ``` -Building the app ----------------- -- Open qt/i2pd_qt/i2pd_qt.pro in the QT-creator. -- Change line MAIN_PATH = /path/to/libraries to actual path where did you put the dependancies to. -- Select appropriate project (usually armeabi-v7a) and build. -- You will find an .apk file in android-build/bin folder. +Building the app with QT +------------------------ +- Open qt/i2pd_qt/i2pd_qt.pro in the QT-creator +- Change line MAIN_PATH = /path/to/libraries to an actual path where you put the dependancies to +- Select appropriate project (usually armeabi-v7a) and build +- You will find an .apk file in android-build/bin folder +Building the app without QT +--------------------------- +- Change line I2PD_LIBS_PATH in android/jni/Application.mk to an actual path where you put the dependancies to +- Run 'ndk-build -j4' from andorid folder +- Create or edit file 'local.properties'. Place 'sdk.dir=' and 'ndk.dir=' +- Run 'ant clean debug' +Creating release .apk +---------------------- +In order to create release .apk you must obtain a Java keystore file(.jks). Either you have in already, or you can generate it yourself using keytool, or from one of you existing well-know ceritificates. For example, i2pd release are signed with this [certificate](https://github.com/PurpleI2P/i2pd/blob/openssl/contrib/certificates/router/orignal_at_mail.i2p.crt). +Create file 'ant.propeties' +key.store= +key.alias= +key.store.password= +key.alias.password= +Run 'ant clean release' From a5da55d0f7e46c6bf60b0e8b46e7dab144e35adb Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Aug 2016 09:38:31 -0400 Subject: [PATCH 1725/6300] Update build_notes_android.md --- docs/build_notes_android.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/build_notes_android.md b/docs/build_notes_android.md index b6cbd978..93b8b77e 100644 --- a/docs/build_notes_android.md +++ b/docs/build_notes_android.md @@ -10,7 +10,7 @@ You need to install Android SDK, NDK and QT with android support. - [SDK](https://developer.android.com/studio/index.html) (choose command line tools only) - [NDK](https://developer.android.com/ndk/downloads/index.html) -- [QT](https://www.qt.io/download-open-source/).(for QT only) Choose one for your platform for android. For example QT 5.6 under Linux would be [this file](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-linux-x64-android-5.6.1-1.run ) +- [QT](https://www.qt.io/download-open-source/)(for QT only). Choose one for your platform for android. For example QT 5.6 under Linux would be [this file](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-linux-x64-android-5.6.1-1.run ) You also need Java JDK and Ant. @@ -50,8 +50,8 @@ Creating release .apk ---------------------- In order to create release .apk you must obtain a Java keystore file(.jks). Either you have in already, or you can generate it yourself using keytool, or from one of you existing well-know ceritificates. For example, i2pd release are signed with this [certificate](https://github.com/PurpleI2P/i2pd/blob/openssl/contrib/certificates/router/orignal_at_mail.i2p.crt). Create file 'ant.propeties' -key.store= -key.alias= -key.store.password= -key.alias.password= +key.store='path to keystore file' +key.alias='alias name' +key.store.password='keystore password' +key.alias.password='alias password' Run 'ant clean release' From 82d80d2ead0e188ebc9215f6fcdf97b6a0d37b78 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Aug 2016 10:28:36 -0400 Subject: [PATCH 1726/6300] moved Config.cpp to libi2pd --- filelist.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/filelist.mk b/filelist.mk index d8d74252..f2f4937e 100644 --- a/filelist.mk +++ b/filelist.mk @@ -5,7 +5,7 @@ LIB_SRC = \ SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp TransitTunnel.cpp \ Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp TunnelGateway.cpp \ Destination.cpp Base.cpp I2PEndian.cpp FS.cpp Config.cpp Family.cpp \ - util.cpp api.cpp + Config.cpp util.cpp api.cpp LIB_CLIENT_SRC = \ AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \ @@ -13,5 +13,5 @@ LIB_CLIENT_SRC = \ # also: Daemon{Linux,Win32}.cpp will be added later DAEMON_SRC = \ - HTTPServer.cpp I2PControl.cpp UPnP.cpp Daemon.cpp Config.cpp i2pd.cpp + HTTPServer.cpp I2PControl.cpp UPnP.cpp Daemon.cpp i2pd.cpp From 8e849ea6f8c250a69d4c48970b9c4abd29ad0912 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Aug 2016 10:33:53 -0400 Subject: [PATCH 1727/6300] reseed from file --- Config.cpp | 18 ++++++++++++------ Reseed.cpp | 8 ++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Config.cpp b/Config.cpp index 2a35ce7e..a629f2d4 100644 --- a/Config.cpp +++ b/Config.cpp @@ -147,12 +147,17 @@ namespace config { #endif "Enable or disable elgamal precomputation table") ; + + options_description reseed("Reseed options"); + reseed.add_options() + ("reseed.file", value()->default_value(""), "Path to .su3 file") + ; - options_description trust("Trust options"); - trust.add_options() - ("trust.enabled", value()->default_value(false), "enable explicit trust options") - ("trust.family", value()->default_value(""), "Router Familiy to trust for first hops") - ("trust.hidden", value()->default_value(false), "should we hide our router from other routers?"); + options_description trust("Trust options"); + trust.add_options() + ("trust.enabled", value()->default_value(false), "enable explicit trust options") + ("trust.family", value()->default_value(""), "Router Familiy to trust for first hops") + ("trust.hidden", value()->default_value(false), "should we hide our router from other routers?"); m_OptionsDesc .add(general) @@ -166,7 +171,8 @@ namespace config { .add(i2pcontrol) .add(upnp) .add(precomputation) - .add(trust) + .add(reseed) + .add(trust) ; } diff --git a/Reseed.cpp b/Reseed.cpp index 236531f9..bac381b1 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -16,6 +16,7 @@ #include "NetDb.h" #include "HTTP.h" #include "util.h" +#include "Config.h" namespace i2p { @@ -51,6 +52,13 @@ namespace data int Reseeder::ReseedNowSU3 () { + std::string filename; i2p::config::GetOption("reseed.file", filename); + if (filename.length() > 0) // reseed file is specified + { + auto num = ProcessSU3File (filename.c_str ()); + if (num > 0) return num; // success + LogPrint (eLogWarning, "Can't reseed from ", filename, " . Trying from hosts"); + } auto ind = rand () % httpsReseedHostList.size (); std::string& reseedHost = httpsReseedHostList[ind]; return ReseedFromSU3 (reseedHost); From 4c96106666ddb8d86191ea9acfb3a06031e2421c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Aug 2016 10:37:03 -0400 Subject: [PATCH 1728/6300] reseed.file added --- docs/configuration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 8bd072b9..32cd7f49 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -70,7 +70,9 @@ All options below still possible in cmdline, but better write it in config file: * --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 +* --reseed.file - Full path to SU3 file to reseed from * --limits.transittunnels= - Override maximum number of transit tunnels. 2500 by default From db8d93d3085bf2e85c22b2368d2ff35abbdfd364 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Aug 2016 10:43:06 -0400 Subject: [PATCH 1729/6300] 2.9.0 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 54fc295f..00064c00 100644 --- a/version.h +++ b/version.h @@ -7,7 +7,7 @@ #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 8 +#define I2PD_VERSION_MINOR 9 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) From deca2175446bb7bf9ac64ece39e4ed64b6b1d596 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Aug 2016 11:07:00 -0400 Subject: [PATCH 1730/6300] don't always set port 4567 --- debian/i2pd.default | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/i2pd.default b/debian/i2pd.default index 28b0e621..495d7a69 100644 --- a/debian/i2pd.default +++ b/debian/i2pd.default @@ -4,8 +4,8 @@ I2PD_ENABLED="yes" # port to listen for incoming connections -# comment this line if you want to use value from config -I2PD_PORT="4567" +# uncomment following line if you want to specify it here +# I2PD_PORT="4567" # Additional options that are passed to the Daemon. # see possible switches in /usr/share/doc/i2pd/configuration.md.gz From 29593f0161c57b3722d84f2f198dbfa7fe8e70d0 Mon Sep 17 00:00:00 2001 From: Manas B Date: Fri, 12 Aug 2016 12:43:59 -0400 Subject: [PATCH 1731/6300] fix a typo --- docs/build_notes_unix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index 228333c8..18e51e62 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -130,7 +130,7 @@ export CXX=/usr/local/bin/g++47 CMake Options ------------- -Available CMake options(each option has a for of `=`, for more information see `man 1 cmake`): +Available CMake options(each option has a form of `=`, for more information see `man 1 cmake`): * `CMAKE_BUILD_TYPE` build profile (Debug/Release) * `WITH_BINARY` build i2pd itself From aaa52bd767949edb48fbd0feb5c20a483f8c1f82 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 12 Aug 2016 22:17:25 +0300 Subject: [PATCH 1732/6300] Update i2pd.default --- debian/i2pd.default | 4 ---- 1 file changed, 4 deletions(-) diff --git a/debian/i2pd.default b/debian/i2pd.default index 495d7a69..1e5d5c2e 100644 --- a/debian/i2pd.default +++ b/debian/i2pd.default @@ -3,10 +3,6 @@ # installed at /etc/default/i2pd by the maintainer scripts I2PD_ENABLED="yes" -# port to listen for incoming connections -# uncomment following line if you want to specify it here -# I2PD_PORT="4567" - # Additional options that are passed to the Daemon. # see possible switches in /usr/share/doc/i2pd/configuration.md.gz DAEMON_OPTS="" From d424e1e9ff910044f0cbf0bc6fb4f98221db72ac Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Aug 2016 15:19:45 -0400 Subject: [PATCH 1733/6300] Update ChangeLog --- ChangeLog | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 520978df..77635666 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,9 +1,13 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog -## [2.9.0] - UNRELEASED +## [2.9.0] - 2016-08-12 ### Changed - Proxy refactoring & speedup +- Transmission-I2P support +- Graceful shutdown for Windows +- Android without QT +- Reduced number of timers in SSU ## [2.8.0] - 2016-06-20 ### Added From b83ab85fd90df01810becff8d9c91410c8696780 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 12 Aug 2016 22:21:33 +0300 Subject: [PATCH 1734/6300] Update i2pd.init --- debian/i2pd.init | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/debian/i2pd.init b/debian/i2pd.init index 02b37546..32c181bb 100644 --- a/debian/i2pd.init +++ b/debian/i2pd.init @@ -41,10 +41,6 @@ do_start() return 2 fi - if [ -n "$I2PD_PORT" ]; then - DAEMON_OPTS="--port $I2PD_PORT $DAEMON_OPTS" - fi - touch "$PIDFILE" chown -f $USER:adm "$PIDFILE" @@ -103,7 +99,7 @@ case "$1" in status) status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? ;; - reload|force-reload) + reload) log_daemon_msg "Reloading $DESC" "$NAME" do_reload log_end_msg $? @@ -127,7 +123,7 @@ case "$1" in esac ;; *) - echo "Usage: $0 {start|stop|status|restart|force-reload}" >&2 + echo "Usage: $0 {start|stop|status|restart|reload}" >&2 exit 3 ;; esac From 37fef7e4f8707a26682432f3a7f33273e000a20f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Aug 2016 15:21:55 -0400 Subject: [PATCH 1735/6300] Update ChangeLog --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index 77635666..2fdc0082 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,6 +8,8 @@ - Graceful shutdown for Windows - Android without QT - Reduced number of timers in SSU +- ipv6 peer test support +- Reseed from SU3 file ## [2.8.0] - 2016-06-20 ### Added From 14a2c9d48f981ec9d8cb8673c4bb83223740ee65 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 12 Aug 2016 22:28:35 +0300 Subject: [PATCH 1736/6300] Update i2pd.install --- debian/i2pd.install | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/i2pd.install b/debian/i2pd.install index 2e3c93cd..57036a79 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,5 +1,5 @@ i2pd usr/sbin/ docs/i2pd.conf etc/i2pd/ -debian/tunnels.conf etc/i2pd/ -debian/subscriptions.txt etc/i2pd/ +docs/tunnels.conf etc/i2pd/ +docs/subscriptions.txt etc/i2pd/ contrib/certificates/ usr/share/i2pd/ From 8a542f2ce806380b72d6171c180bd53b16da2c34 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 12 Aug 2016 22:29:32 +0300 Subject: [PATCH 1737/6300] Update i2pd.upstart --- debian/i2pd.upstart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/debian/i2pd.upstart b/debian/i2pd.upstart index 29c6cbdb..fd2b24f0 100644 --- a/debian/i2pd.upstart +++ b/debian/i2pd.upstart @@ -4,8 +4,6 @@ start on runlevel [2345] stop on runlevel [016] or unmounting-filesystem # these can be overridden in /etc/init/i2pd.override -env I2PD_HOST="1.2.3.4" -env I2PD_PORT="4567" env LOGFILE="/var/log/i2pd.log" -exec /usr/sbin/i2pd --daemon --log=file --logfile=$LOGFILE --service --host=$I2PD_HOST --port=$I2PD_PORT +exec /usr/sbin/i2pd --daemon --service --log=file --logfile=$LOGFILE From a9b289626ea8ac271b05636c29c70cf03a5733a2 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 12 Aug 2016 22:30:26 +0300 Subject: [PATCH 1738/6300] Update logrotate --- debian/logrotate | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/debian/logrotate b/debian/logrotate index a3fed79a..3462c696 100644 --- a/debian/logrotate +++ b/debian/logrotate @@ -1,13 +1,9 @@ /var/log/i2pd.log { - rotate 4 - weekly - missingok - notifempty - compress - delaycompress - copytruncate - create 640 i2pd adm - postrotate - /etc/init.d/i2pd restart >/dev/null - endscript + rotate 6 + daily + missingok + notifempty + compress + delaycompress + copytruncate } From d51ad77ab494ed4a3b36b2af556106567c399833 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 12 Aug 2016 22:34:29 +0300 Subject: [PATCH 1739/6300] moved subscriptions.txt --- docs/subscriptions.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/subscriptions.txt diff --git a/docs/subscriptions.txt b/docs/subscriptions.txt new file mode 100644 index 00000000..8f4afb03 --- /dev/null +++ b/docs/subscriptions.txt @@ -0,0 +1,3 @@ +http://inr.i2p/export/alive-hosts.txt +http://stats.i2p/cgi-bin/newhosts.txt +http://i2p-projekt.i2p/hosts.txt From ce13de7d6cc911702c1c932a4f163478223038fa Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 12 Aug 2016 22:35:09 +0300 Subject: [PATCH 1740/6300] moved subscriptions.txt --- debian/subscriptions.txt | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 debian/subscriptions.txt diff --git a/debian/subscriptions.txt b/debian/subscriptions.txt deleted file mode 100644 index 8f4afb03..00000000 --- a/debian/subscriptions.txt +++ /dev/null @@ -1,3 +0,0 @@ -http://inr.i2p/export/alive-hosts.txt -http://stats.i2p/cgi-bin/newhosts.txt -http://i2p-projekt.i2p/hosts.txt From 2f1971ea8f91934a7ee7bb45d60d348431bb00d1 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 12 Aug 2016 22:36:17 +0300 Subject: [PATCH 1741/6300] moved tunnels.conf --- docs/tunnels.conf | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 docs/tunnels.conf diff --git a/docs/tunnels.conf b/docs/tunnels.conf new file mode 100644 index 00000000..fa92a56b --- /dev/null +++ b/docs/tunnels.conf @@ -0,0 +1,33 @@ +[IRC] +type = client +address = 127.0.0.1 +port = 6668 +destination = irc.postman.i2p +destinationport = 6667 +keys = irc-keys.dat + +#[SMTP] +#type = client +#address = 127.0.0.1 +#port = 7659 +#destination = smtp.postman.i2p +#destinationport = 25 +#keys = smtp-keys.dat + +#[POP3] +#type = client +#address = 127.0.0.1 +#port = 7660 +#destination = pop.postman.i2p +#destinationport = 110 +#keys = pop3-keys.dat + +#[MTN] +#type = client +#address = 127.0.0.1 +#port = 8998 +#destination = mtn.i2p-projekt.i2p +#destinationport = 4691 +#keys = mtn-keys.dat + +# see more examples in /usr/share/doc/i2pd/configuration.md.gz From 7a692898e4a509ad99aee5f44e380c05482a995f Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 12 Aug 2016 22:36:31 +0300 Subject: [PATCH 1742/6300] moved tunnels.conf --- debian/tunnels.conf | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 debian/tunnels.conf diff --git a/debian/tunnels.conf b/debian/tunnels.conf deleted file mode 100644 index fa92a56b..00000000 --- a/debian/tunnels.conf +++ /dev/null @@ -1,33 +0,0 @@ -[IRC] -type = client -address = 127.0.0.1 -port = 6668 -destination = irc.postman.i2p -destinationport = 6667 -keys = irc-keys.dat - -#[SMTP] -#type = client -#address = 127.0.0.1 -#port = 7659 -#destination = smtp.postman.i2p -#destinationport = 25 -#keys = smtp-keys.dat - -#[POP3] -#type = client -#address = 127.0.0.1 -#port = 7660 -#destination = pop.postman.i2p -#destinationport = 110 -#keys = pop3-keys.dat - -#[MTN] -#type = client -#address = 127.0.0.1 -#port = 8998 -#destination = mtn.i2p-projekt.i2p -#destinationport = 4691 -#keys = mtn-keys.dat - -# see more examples in /usr/share/doc/i2pd/configuration.md.gz From 774c11781d15ac96a3d2e3ae62e1f8255a1a820f Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 12 Aug 2016 22:47:20 +0300 Subject: [PATCH 1743/6300] Update changelog --- debian/changelog | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/debian/changelog b/debian/changelog index 5ac732e0..f1f9cb41 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,16 @@ +i2pd (2.9.0-1) unstable; urgency=low + + ** Changed + * Proxy refactoring & speedup + * Transmission-I2P support + * Graceful shutdown for Windows + * Android without QT + * Reduced number of timers in SSU + * ipv6 peer test support + * Reseed from SU3 file + + -- orignal Fri, 12 Aug 2016 14:25:40 +0000 + i2pd (2.7.0-1) unstable; urgency=low * updated to version 2.7.0/0.9.25 From ca7709a284f718776bfe89a3b5fdab9355ce74be Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Fri, 12 Aug 2016 23:23:10 +0300 Subject: [PATCH 1744/6300] Update i2pd.conf --- docs/i2pd.conf | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/i2pd.conf b/docs/i2pd.conf index 16314274..94287bd0 100644 --- a/docs/i2pd.conf +++ b/docs/i2pd.conf @@ -35,6 +35,9 @@ ## Run as a service. Router will use system folders like ‘/var/lib/i2pd’ # service = true +## Specify a family, router belongs to (default - none) +# family = + ## External IP address to listen for connections ## By default i2pd sets IP automatically # host = 1.2.3.4 @@ -46,10 +49,20 @@ ## Enable communication through ipv4 ipv4 = true - ## Enable communication through ipv6 ipv6 = false +## Network interface to bind to +# ifname = + +## Enable NTCP transport (default = true) +# ntcp = true +## Enable SSU transport (default = true) +# ssu = true + +## Should we assume we are behind NAT? (false only in MeshNet) +# nat = true + ## Bandwidth configuration ## L limit bandwidth to 32Kbs/sec, O - to 256Kbs/sec, P - to 2048Kbs/sec, ## X - unlimited @@ -71,6 +84,17 @@ ipv6 = false ## By default, enabled on i386 hosts # elgamal = true +[upnp] +## Enable or disable UPnP: automatic port forwarding (enabled by default in WINDOWS, ANDROID) +# enabled = false + +## Name i2pd appears in UPnP forwardings list (default = I2Pd) +# name = I2Pd + +[reseed] +## Path to reseed .su3 file (if ) +# file = + [http] ## Uncomment and set to 'false' to disable Web Console # enabled = true From c86bcb4dd6f571e0a1d58b8a13f488f2c313fdbb Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 13 Aug 2016 08:23:32 -0400 Subject: [PATCH 1745/6300] r4sas_at_mail.i2p.crt added --- .../certificates/reseed/r4sas_at_mail.i2p.crt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 contrib/certificates/reseed/r4sas_at_mail.i2p.crt diff --git a/contrib/certificates/reseed/r4sas_at_mail.i2p.crt b/contrib/certificates/reseed/r4sas_at_mail.i2p.crt new file mode 100644 index 00000000..b0c8d749 --- /dev/null +++ b/contrib/certificates/reseed/r4sas_at_mail.i2p.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFezCCA2OgAwIBAgIEb8xTzzANBgkqhkiG9w0BAQ0FADBuMQswCQYDVQQGEwJY +WDELMAkGA1UECBMCWFgxHjAcBgNVBAcTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEL +MAkGA1UEChMCWFgxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOcjRzYXNAbWFpbC5p +MnAwHhcNMTYwODExMjIyNDM2WhcNMjYwODExMjIyNDM2WjBuMQswCQYDVQQGEwJY +WDELMAkGA1UECBMCWFgxHjAcBgNVBAcTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEL +MAkGA1UEChMCWFgxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOcjRzYXNAbWFpbC5p +MnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDjUMy/aYd0i6Oc3rdc +24V/fM2vhviH+cNhAOXsMSrwDSVbFQkuDPIfq4fo1A25rsyULR57vy7XKA51OstX +GvREPDhth4cMZjthq0f8AVzPq2vIk8Po65uvKR190yupPQ4FhvGeRkHkqp+SqoIJ +lClD8xZEHrUHSYZotm5TLWIgSwa4DuO1q3bMRI8oIWzqhv99FtlmHlC8fjVUN4mR +2czhABr0u6RMPOtJwTVxWgT1PKXiLWfmeHb63TcPYGgpJ39iMDOjtgY9jYueoO8J +uGJJtkGRIRjOuhDFE9NUlNnljUxUDWvMU7zCO4ozaKMZgoxr1WoIO6ubI/003I53 +sZ0Q5h8yfz+QreEw3wzjxnQSkejG5c3NIvJSiu0ylOqDWmnj0v1Jv/P0qAMU4bt/ +ZWj0GOrYfPn9STg0VxMOQwQ2o15GAcbr6PFI56U2IJhZAeER3hIe2kOl6591jQ67 +zvOjPRRh2q05Ss8yo7nEpYUiB/FrE6RssJ5tVwX6e6Tq4Z1frINanIkUkToTkypP +Fn2T/KV2lak9rLuxzvhiDobu5iGCR323zFcFEpGq4Wsopx1uRT9+71G/ejw8pKTf +kQ7XiGaaxFyZuMuOz3bFkTuoTmAkUQTlRjGw2DmKZi/apcN+VQgpq9tQpS10pEUy +DCVdtw1AdlOnwb+Hf3X0Uz6OjwIDAQABoyEwHzAdBgNVHQ4EFgQUqLBlSlnqCo25 +sIduMPm4iROMqkAwDQYJKoZIhvcNAQENBQADggIBAGWv8rDTzqhHkjqDOT+Ba2bs +gVddpCNa94RQoOn2DUSu4c+yuWJLSctjpX7gswB6qvWk5Ojfafop8jJW8zuozJrO +76b9345S/VnnbHVSoVfIpF9Fve1Xc8nvU4ylRcAMwhf8N3Md5Yc1kb+P7NtTTwMZ +TBR3xY3fVxv3qTpKApWQKkUiqM7yJKOfS8xcK/pjO/3oRUwfA9DHugCUpgSidlN+ +JkZmgwAcA3/WMlDdNKmKnWLGB2Ea+W6kIx5TDFfjf11rbjuwXhDLyaOK88qlN0W2 +hYa31UDSEYYQd3gMG1gjVc+9vZA/Vr0+SF5ULN9QLjB18CVIdPv92mBjJQRmJSVW +b1qwZI0jf/V+1fu9H9r7sE4CId3+WGOek3UNRNZLOVZCSiFq/b9cswcQZGjw6aE+ +1FNjw1HW9CLoNcg74Kr98QouOoeRSofQYZiYqaM9Sz/MsinYMIRGRGw3Uq1uNRo0 +WgoOngmZSKGaW5PFR19uuuNIVB4fCShqBVyrguW4xIskta1JVFoggFeOeTwk6/kH +S5roMzyB/kzv83A2IB0VxqbiDj8khgdm1Us6HCCmU+iTRVyG28gFklCJ8dQfxgGH +W2gpIwvxYLyNP14/7E1oF7/NfHmyjAVzYnR5Xw2wE4tvSHuIrHhj6Q26VB3vze6j +E/w1AJEepnw/KfHqS3bw +-----END CERTIFICATE----- From 4631123231d57c26053e10626d13e223406416c6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 13 Aug 2016 09:05:35 -0400 Subject: [PATCH 1746/6300] reseed-ru.lngserv.ru added --- Reseed.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Reseed.cpp b/Reseed.cpp index bac381b1..48d7a53f 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -30,15 +30,16 @@ namespace data #else // mainline i2p reseeds "https://reseed.i2p-projekt.de/", // Only HTTPS - "https://i2p.mooo.com/netDb/", - "https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required + "https://i2p.mooo.com/netDb/", + "https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required "https://us.reseed.i2p2.no:444/", - "https://uk.reseed.i2p2.no:444/", + "https://uk.reseed.i2p2.no:444/", "https://i2p.manas.ca:8443/", "https://i2p-0.manas.ca:8443/", - "https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support - "https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support - "https://download.xxlspeed.com/" // Only HTTPS and SU3 (v3) support + "https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support + "https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support + "https://download.xxlspeed.com/", // Only HTTPS and SU3 (v3) support + "https://reseed-ru.lngserv.ru/" #endif }; From 049e1b26799a68cd8bf1ca054adbd44cd52e064c Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Sat, 13 Aug 2016 16:50:08 +0300 Subject: [PATCH 1747/6300] Update changelog --- debian/changelog | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/debian/changelog b/debian/changelog index f1f9cb41..c308c689 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,14 +1,11 @@ i2pd (2.9.0-1) unstable; urgency=low - ** Changed - * Proxy refactoring & speedup - * Transmission-I2P support - * Graceful shutdown for Windows - * Android without QT - * Reduced number of timers in SSU - * ipv6 peer test support - * Reseed from SU3 file - + * updated to version 2.9.0 + * removed I2PD_PORT in i2pd.default + * removed all port assigments in services files + * fixed logrotate + * subscriptions.txt and tunnels.conf taken from docs folder + -- orignal Fri, 12 Aug 2016 14:25:40 +0000 i2pd (2.7.0-1) unstable; urgency=low From 73452f758c1b480e4251b8c2c3c4d39831899747 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Sat, 13 Aug 2016 16:52:27 +0300 Subject: [PATCH 1748/6300] Update changelog --- debian/changelog | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/changelog b/debian/changelog index c308c689..fe04c1b4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,7 @@ i2pd (2.9.0-1) unstable; urgency=low * updated to version 2.9.0 + * updated tuner-patch * removed I2PD_PORT in i2pd.default * removed all port assigments in services files * fixed logrotate From 72ef621f9d32ab57df76ad7335a6629572a32993 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Sat, 13 Aug 2016 16:52:51 +0300 Subject: [PATCH 1749/6300] Update changelog --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index fe04c1b4..6600f8e4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,7 +1,7 @@ i2pd (2.9.0-1) unstable; urgency=low * updated to version 2.9.0 - * updated tuner-patch + * updated tune-patch * removed I2PD_PORT in i2pd.default * removed all port assigments in services files * fixed logrotate From bf4f22b203efe88df430720392e40c13972f66c6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 14 Aug 2016 17:52:11 -0400 Subject: [PATCH 1750/6300] add 'O' to extra bandwidth --- RouterInfo.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 90cf5e44..b4a10be2 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -348,19 +348,15 @@ namespace data void RouterInfo::UpdateCapsProperty () { std::string caps; - if (m_Caps & eFloodfill) { + if (m_Caps & eFloodfill) + { caps += CAPS_FLAG_FLOODFILL; // floodfill - caps += (m_Caps & eExtraBandwidth) - ? CAPS_FLAG_EXTRA_BANDWIDTH1 // 'P' - : CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' - } else { - if (m_Caps & eExtraBandwidth) { - caps += CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' - } else if (m_Caps & eHighBandwidth) { - caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' - } else { - caps += CAPS_FLAG_LOW_BANDWIDTH2; // 'L' - } + caps += (m_Caps & eExtraBandwidth)? CAPS_FLAG_EXTRA_BANDWIDTH1 /* 'P' */ : CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */; + } + else + { + if (m_Caps & eExtraBandwidth) caps += CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' + caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth } if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable From cb7efcb1884fe0a74e505b78651310f0e0dd5e9c Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 14 Aug 2016 17:58:50 -0400 Subject: [PATCH 1751/6300] add 'O' to extra bandwidth for flooadfill --- RouterInfo.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index b4a10be2..625db5cb 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -350,8 +350,9 @@ namespace data std::string caps; if (m_Caps & eFloodfill) { + if (m_Caps & eExtraBandwidth) caps += CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' + caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' caps += CAPS_FLAG_FLOODFILL; // floodfill - caps += (m_Caps & eExtraBandwidth)? CAPS_FLAG_EXTRA_BANDWIDTH1 /* 'P' */ : CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */; } else { From de29abb05cf1be0ad09d5c277e4aa701761fb16d Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 15 Aug 2016 13:12:56 -0400 Subject: [PATCH 1752/6300] check string buffer size --- RouterInfo.cpp | 43 +++++++++++++++++++++++-------------------- RouterInfo.h | 4 ++-- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 625db5cb..130355b8 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -163,7 +163,7 @@ namespace data s.read ((char *)&address.cost, sizeof (address.cost)); s.read ((char *)&address.date, sizeof (address.date)); char transportStyle[5]; - ReadString (transportStyle, s); + ReadString (transportStyle, 5, s); if (!strcmp (transportStyle, "NTCP")) address.transportStyle = eTransportNTCP; else if (!strcmp (transportStyle, "SSU")) @@ -177,10 +177,10 @@ namespace data size = be16toh (size); while (r < size) { - char key[500], value[500]; - r += ReadString (key, s); + char key[255], value[255]; + r += ReadString (key, 255, s); s.seekg (1, std::ios_base::cur); r++; // = - r += ReadString (value, s); + r += ReadString (value, 255, s); s.seekg (1, std::ios_base::cur); r++; // ; if (!strcmp (key, "host")) { @@ -257,16 +257,10 @@ namespace data size = be16toh (size); while (r < size) { -#ifdef _WIN32 - char key[500], value[500]; - // TODO: investigate why properties get read as one long string under Windows - // length should not be more than 44 -#else - char key[50], value[50]; -#endif - r += ReadString (key, s); + char key[255], value[255]; + r += ReadString (key, 255, s); s.seekg (1, std::ios_base::cur); r++; // = - r += ReadString (value, s); + r += ReadString (value, 255, s); s.seekg (1, std::ios_base::cur); r++; // ; m_Properties[key] = value; @@ -542,16 +536,25 @@ namespace data return true; } - size_t RouterInfo::ReadString (char * str, std::istream& s) + size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const { - uint8_t len; - s.read ((char *)&len, 1); - s.read (str, len); - str[len] = 0; - return len+1; + uint8_t l; + s.read ((char *)&l, 1); + if (l < len) + { + s.read (str, l); + str[l] = 0; + } + else + { + LogPrint (eLogWarning, "RouterInfo: string length ", (int)l, " exceeds buffer size ", len); + s.seekg (l, std::ios::cur); // skip + str[0] = 0; + } + return l+1; } - void RouterInfo::WriteString (const std::string& str, std::ostream& s) + void RouterInfo::WriteString (const std::string& str, std::ostream& s) const { uint8_t len = str.size (); s.write ((char *)&len, 1); diff --git a/RouterInfo.h b/RouterInfo.h index 16937b5b..f4c077ef 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -188,8 +188,8 @@ namespace data void ReadFromStream (std::istream& s); void ReadFromBuffer (bool verifySignature); void WriteToStream (std::ostream& s) const; - static size_t ReadString (char* str, std::istream& s); - static void WriteString (const std::string& str, std::ostream& s); + size_t ReadString (char* str, size_t len, std::istream& s) const; + void WriteString (const std::string& str, std::ostream& s) const; void ExtractCaps (const char * value); std::shared_ptr GetAddress (TransportStyle s, bool v4only, bool v6only = false) const; void UpdateCapsProperty (); From a527dcd95b9aa5e9cc9ba5c705611fdb984d36ac Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 15 Aug 2016 14:01:57 -0400 Subject: [PATCH 1753/6300] moved HTTP to libi2pd --- filelist.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/filelist.mk b/filelist.mk index f2f4937e..db243866 100644 --- a/filelist.mk +++ b/filelist.mk @@ -5,11 +5,11 @@ LIB_SRC = \ SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp TransitTunnel.cpp \ Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp TunnelGateway.cpp \ Destination.cpp Base.cpp I2PEndian.cpp FS.cpp Config.cpp Family.cpp \ - Config.cpp util.cpp api.cpp + Config.cpp HTTP.cpp util.cpp api.cpp LIB_CLIENT_SRC = \ AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \ - SAM.cpp SOCKS.cpp HTTP.cpp HTTPProxy.cpp I2CP.cpp + SAM.cpp SOCKS.cpp HTTPProxy.cpp I2CP.cpp # also: Daemon{Linux,Win32}.cpp will be added later DAEMON_SRC = \ From 03f0ca965ea804216bdc681f9093d1df2a0f76ed Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 15 Aug 2016 22:36:58 -0400 Subject: [PATCH 1754/6300] fixed race condition --- NetDbRequests.cpp | 27 +++++++++++++++++---------- NetDbRequests.h | 2 +- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/NetDbRequests.cpp b/NetDbRequests.cpp index c3966597..474d3693 100644 --- a/NetDbRequests.cpp +++ b/NetDbRequests.cpp @@ -68,8 +68,7 @@ namespace data dest->SetRequestComplete (requestComplete); { std::unique_lock l(m_RequestedDestinationsMutex); - if (!m_RequestedDestinations.insert (std::make_pair (destination, - std::shared_ptr (dest))).second) // not inserted + if (!m_RequestedDestinations.insert (std::make_pair (destination, dest)).second) // not inserted return nullptr; } return dest; @@ -77,20 +76,28 @@ namespace data void NetDbRequests::RequestComplete (const IdentHash& ident, std::shared_ptr r) { - auto it = m_RequestedDestinations.find (ident); - if (it != m_RequestedDestinations.end ()) - { - if (r) - it->second->Success (r); - else - it->second->Fail (); + std::shared_ptr request; + { std::unique_lock l(m_RequestedDestinationsMutex); - m_RequestedDestinations.erase (it); + auto it = m_RequestedDestinations.find (ident); + if (it != m_RequestedDestinations.end ()) + { + request = it->second; + m_RequestedDestinations.erase (it); + } + } + if (request) + { + if (r) + request->Success (r); + else + request->Fail (); } } std::shared_ptr NetDbRequests::FindRequest (const IdentHash& ident) const { + std::unique_lock l(m_RequestedDestinationsMutex); auto it = m_RequestedDestinations.find (ident); if (it != m_RequestedDestinations.end ()) return it->second; diff --git a/NetDbRequests.h b/NetDbRequests.h index 0bab7080..debf1273 100644 --- a/NetDbRequests.h +++ b/NetDbRequests.h @@ -59,7 +59,7 @@ namespace data private: - std::mutex m_RequestedDestinationsMutex; + mutable std::mutex m_RequestedDestinationsMutex; std::map > m_RequestedDestinations; }; } From 2d82c4ada44bef837228a48c20e31dac76cd6d90 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 16 Aug 2016 10:25:56 -0400 Subject: [PATCH 1755/6300] try fixing https://github.com/PurpleI2P/i2pd/issues/612 --- build/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 7f9b8c66..99d6a5ad 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -369,7 +369,10 @@ if (WITH_BINARY) if (MSYS OR MINGW) set (MINGW_EXTRA -lws2_32 -lmswsock -liphlpapi ) endif () - target_link_libraries( "${PROJECT_NAME}" libi2pd i2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ) + if (WITH_STATIC) + set(DL_LIB ${CMAKE_DL_LIBS}) + endif() + target_link_libraries( "${PROJECT_NAME}" libi2pd i2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB}) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) set (APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") From 2e74d91ddc1a776692dd7398e0126a6bd1f2a92a Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 16 Aug 2016 10:25:56 -0400 Subject: [PATCH 1756/6300] try fixing https://github.com/PurpleI2P/i2pd/issues/612 --- build/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 7f9b8c66..99d6a5ad 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -369,7 +369,10 @@ if (WITH_BINARY) if (MSYS OR MINGW) set (MINGW_EXTRA -lws2_32 -lmswsock -liphlpapi ) endif () - target_link_libraries( "${PROJECT_NAME}" libi2pd i2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ) + if (WITH_STATIC) + set(DL_LIB ${CMAKE_DL_LIBS}) + endif() + target_link_libraries( "${PROJECT_NAME}" libi2pd i2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB}) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) set (APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") From 3d066ea1b8d29706972b259f600f592df004255a Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Aug 2016 10:58:57 -0400 Subject: [PATCH 1757/6300] common termination timer for all NTCP sessions --- NTCPSession.cpp | 78 ++++++++++++++++++++++++++++--------------------- NTCPSession.h | 21 +++++++------ 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index b22b0f3c..a1b26aca 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -21,7 +21,8 @@ namespace transport NTCPSession::NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter): TransportSession (in_RemoteRouter, NTCP_TERMINATION_TIMEOUT), m_Server (server), m_Socket (m_Server.GetService ()), - m_TerminationTimer (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false), + m_IsEstablished (false), m_IsTerminated (false), + m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()), m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_IsSending (false) { m_Establisher = new Establisher; @@ -78,7 +79,6 @@ namespace transport m_Server.RemoveNTCPSession (shared_from_this ()); m_SendQueue.clear (); m_NextMessage = nullptr; - m_TerminationTimer.cancel (); LogPrint (eLogDebug, "NTCP: session terminated"); } } @@ -110,7 +110,6 @@ namespace transport boost::asio::async_write (m_Socket, boost::asio::buffer (&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (), std::bind(&NTCPSession::HandlePhase1Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); - ScheduleTermination (); } void NTCPSession::ServerLogin () @@ -124,7 +123,6 @@ namespace transport boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (), std::bind(&NTCPSession::HandlePhase1Received, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); - ScheduleTermination (); } } @@ -558,7 +556,7 @@ namespace transport m_Handler.Flush (); } - ScheduleTermination (); // reset termination timer + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); Receive (); } } @@ -685,6 +683,7 @@ namespace transport } else { + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); m_NumSentBytes += bytes_transferred; i2p::transport::transports.UpdateSentBytes (bytes_transferred); if (!m_SendQueue.empty()) @@ -692,8 +691,6 @@ namespace transport Send (m_SendQueue); m_SendQueue.clear (); } - else - ScheduleTermination (); // reset termination timer } } @@ -728,29 +725,11 @@ namespace transport else Send (msgs); } - - void NTCPSession::ScheduleTermination () - { - m_TerminationTimer.cancel (); - m_TerminationTimer.expires_from_now (boost::posix_time::seconds(GetTerminationTimeout ())); - m_TerminationTimer.async_wait (std::bind (&NTCPSession::HandleTerminationTimer, - shared_from_this (), std::placeholders::_1)); - } - - void NTCPSession::HandleTerminationTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogDebug, "NTCP: No activity for ", GetTerminationTimeout (), " seconds"); - //Terminate (); - m_Socket.close ();// invoke Terminate () from HandleReceive - } - } //----------------------------------------- NTCPServer::NTCPServer (): m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), - m_NTCPAcceptor (nullptr), m_NTCPV6Acceptor (nullptr) + m_TerminationTimer (m_Service), m_NTCPAcceptor (nullptr), m_NTCPV6Acceptor (nullptr) { } @@ -809,7 +788,8 @@ namespace transport } } } - } + } + ScheduleTermination (); } } @@ -820,13 +800,17 @@ namespace transport if (m_IsRunning) { m_IsRunning = false; - if (m_NTCPAcceptor) - delete m_NTCPAcceptor; - m_NTCPAcceptor = nullptr; - if (m_NTCPV6Acceptor) - delete m_NTCPV6Acceptor; - m_NTCPV6Acceptor = nullptr; - + m_TerminationTimer.cancel (); + if (m_NTCPAcceptor) + { + delete m_NTCPAcceptor; + m_NTCPAcceptor = nullptr; + } + if (m_NTCPV6Acceptor) + { + delete m_NTCPV6Acceptor; + m_NTCPV6Acceptor = nullptr; + } m_Service.stop (); if (m_Thread) { @@ -989,5 +973,31 @@ namespace transport m_BanList[addr] = ts + NTCP_BAN_EXPIRATION_TIMEOUT; LogPrint (eLogWarning, "NTCP: ", addr, " has been banned for ", NTCP_BAN_EXPIRATION_TIMEOUT, " seconds"); } + + void NTCPServer::ScheduleTermination () + { + m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP_TERMINATION_CHECK_TIMEOUT)); + m_TerminationTimer.async_wait (std::bind (&NTCPServer::HandleTerminationTimer, + this, std::placeholders::_1)); + } + + void NTCPServer::HandleTerminationTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto& it: m_NTCPSessions) + if (it.second->IsTerminationTimeoutExpired (ts)) + { + auto session = it.second; + m_Service.post ([session] + { + LogPrint (eLogDebug, "NTCP: No activity for ", session->GetTerminationTimeout (), " seconds"); + session->Terminate (); + }); + } + ScheduleTermination (); + } + } } } diff --git a/NTCPSession.h b/NTCPSession.h index 59b6bb58..adf33553 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -37,6 +37,7 @@ namespace transport const size_t NTCP_MAX_MESSAGE_SIZE = 16384; const size_t NTCP_BUFFER_SIZE = 4160; // fits 4 tunnel messages (4*1028) const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes + const int NTCP_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds const size_t NTCP_DEFAULT_PHASE3_SIZE = 2/*size*/ + i2p::data::DEFAULT_IDENTITY_SIZE/*387*/ + 4/*ts*/ + 15/*padding*/ + 40/*signature*/; // 448 const int NTCP_BAN_EXPIRATION_TIMEOUT = 70; // in second const int NTCP_CLOCK_SKEW = 60; // in seconds @@ -54,7 +55,9 @@ namespace transport boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; bool IsEstablished () const { return m_IsEstablished; }; - + bool IsTerminationTimeoutExpired (uint64_t ts) const + { return ts >= m_LastActivityTimestamp + GetTerminationTimeout (); }; + void ClientLogin (); void ServerLogin (); void SendI2NPMessages (const std::vector >& msgs); @@ -95,17 +98,12 @@ namespace transport void Send (const std::vector >& msgs); void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector > msgs); - - // timer - void ScheduleTermination (); - void HandleTerminationTimer (const boost::system::error_code& ecode); - private: NTCPServer& m_Server; boost::asio::ip::tcp::socket m_Socket; - boost::asio::deadline_timer m_TerminationTimer; bool m_IsEstablished, m_IsTerminated; + uint64_t m_LastActivityTimestamp; i2p::crypto::CBCDecryption m_Decryption; i2p::crypto::CBCEncryption m_Encryption; @@ -146,8 +144,8 @@ namespace transport std::shared_ptr FindNTCPSession (const i2p::data::IdentHash& ident); void Connect (const boost::asio::ip::address& address, int port, std::shared_ptr conn); - bool IsBoundV4() const { return m_NTCPAcceptor != nullptr; }; - bool IsBoundV6() const { return m_NTCPV6Acceptor != nullptr; }; + bool IsBoundV4() const { return m_NTCPAcceptor != nullptr; }; + bool IsBoundV6() const { return m_NTCPV6Acceptor != nullptr; }; boost::asio::io_service& GetService () { return m_Service; }; void Ban (const boost::asio::ip::address& addr); @@ -159,6 +157,10 @@ namespace transport void HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error); void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn); + + // timer + void ScheduleTermination (); + void HandleTerminationTimer (const boost::system::error_code& ecode); private: @@ -166,6 +168,7 @@ namespace transport std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; + boost::asio::deadline_timer m_TerminationTimer; boost::asio::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor; std::map > m_NTCPSessions; // access from m_Thread only std::map m_BanList; // IP -> ban expiration time in seconds From 959843ee9c1a63b7245ef8083480599a6ec92eb1 Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Fri, 19 Aug 2016 12:16:28 +0300 Subject: [PATCH 1758/6300] Find miniupnpc library as well as header --- build/CMakeLists.txt | 2 +- build/cmake_modules/FindMiniUPnPc.cmake | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 99d6a5ad..f9f21fd4 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -357,7 +357,7 @@ if (WITH_BINARY) endif () if (WITH_UPNP) - target_link_libraries("${PROJECT_NAME}" "miniupnpc") + target_link_libraries("${PROJECT_NAME}" "${MINIUPNPC_LIBRARY}") endif () # FindBoost pulls pthread for thread which is broken for static linking at least on Ubuntu 15.04 diff --git a/build/cmake_modules/FindMiniUPnPc.cmake b/build/cmake_modules/FindMiniUPnPc.cmake index 8d5d3860..6a5d4220 100644 --- a/build/cmake_modules/FindMiniUPnPc.cmake +++ b/build/cmake_modules/FindMiniUPnPc.cmake @@ -1,6 +1,6 @@ # - Find MINIUPNPC -if(MINIUPNPC_INCLUDE_DIR) +if(MINIUPNPC_INCLUDE_DIR AND MINIUPNPC_LIBRARY) set(MINIUPNPC_FOUND TRUE) else() @@ -11,15 +11,18 @@ else() $ENV{SystemDrive} ${PROJECT_SOURCE_DIR}/../.. ) - - if(MINIUPNPC_INCLUDE_DIR) + + find_library(MINIUPNPC_LIBRARY miniupnpc) + + if(MINIUPNPC_INCLUDE_DIR AND MINIUPNPC_LIBRARY) set(MINIUPNPC_FOUND TRUE) message(STATUS "Found MiniUPnP headers: ${MINIUPNPC_INCLUDE_DIR}") + message(STATUS "Found MiniUPnP library: ${MINIUPNPC_LIBRARY}") else() set(MINIUPNPC_FOUND FALSE) message(STATUS "MiniUPnP not found.") endif() - mark_as_advanced(MINIUPNPC_INCLUDE_DIR) + mark_as_advanced(MINIUPNPC_INCLUDE_DIR MINIUPNPC_LIBRARY) endif() From f99aea5cb14d6064a2a8517e8cb8f21cae94a045 Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 21 Aug 2016 00:00:00 +0000 Subject: [PATCH 1759/6300] * Makefile.linux : use linker flags instead full paths to obj files (#602) --- Makefile.linux | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Makefile.linux b/Makefile.linux index 1376260a..b0549f43 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -28,15 +28,13 @@ endif NEEDED_CXXFLAGS += -fPIC ifeq ($(USE_STATIC),yes) +# NOTE: on glibc you will get this warning: +# Using 'getaddrinfo' in statically linked applications requires at runtime +# the shared libraries from the glibc version used for linking LIBDIR := /usr/lib - LDLIBS = $(LIBDIR)/libboost_system.a - LDLIBS += $(LIBDIR)/libboost_date_time.a - LDLIBS += $(LIBDIR)/libboost_filesystem.a - LDLIBS += $(LIBDIR)/libboost_program_options.a - LDLIBS += $(LIBDIR)/libssl.a - LDLIBS += $(LIBDIR)/libcrypto.a - LDLIBS += $(LIBDIR)/libz.a - LDLIBS += -lpthread -static-libstdc++ -static-libgcc -lrt + LDLIBS = -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options + LDLIBS += -lssl -lcrypto -lz -ldl -lpthread -lrt + LDLIBS += -static-libstdc++ -static-libgcc -static USE_AESNI := no else LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread From bbbda442183e657590876e071fe3d3785fc0cf38 Mon Sep 17 00:00:00 2001 From: hagen Date: Sun, 21 Aug 2016 00:00:00 +0000 Subject: [PATCH 1760/6300] * HTTPServer : show termination time in main page (closes #506) --- HTTPServer.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index f10289ef..fdc5b38f 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -174,7 +174,7 @@ namespace http { s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); s << "
    \r\n"; - s << "Status: "; + s << "Network status: "; switch (i2p::context.GetStatus ()) { case eRouterStatusOK: s << "OK"; break; @@ -183,6 +183,13 @@ namespace http { default: s << "Unknown"; } s << "
    \r\n"; +#if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) + if (auto remains = Daemon.gracefullShutdownInterval) { + s << "Stopping in: "; + s << remains << " seconds"; + s << "
    \r\n"; + } +#endif auto family = i2p::context.GetFamily (); if (family.length () > 0) s << "Family: " << family << "
    \r\n"; @@ -415,15 +422,9 @@ namespace http { s << " Accept transit tunnels
    \r\n"; #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) if (Daemon.gracefullShutdownInterval) - { - s << " Cancel gracefull shutdown ("; - s << Daemon.gracefullShutdownInterval; - s << " seconds remains)
    \r\n"; - } + s << " Cancel gracefull shutdown
    "; else - { s << " Start gracefull shutdown
    \r\n"; - } #endif #ifdef WIN32_APP s << " Gracefull shutdown
    \r\n"; From aa3723d2bdeb637554c4f7cb9376c5c0a1141a7c Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 15:02:17 -0400 Subject: [PATCH 1761/6300] udp tunnels --- ClientContext.cpp | 105 +++++++++++++++++++++++++++----- ClientContext.h | 20 ++++++- I2PTunnel.cpp | 148 ++++++++++++++++++++++++++++++++++++++++++++++ I2PTunnel.h | 78 +++++++++++++++++++++++- Identity.h | 2 + Tag.h | 5 ++ 6 files changed, 338 insertions(+), 20 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 2c917025..68115402 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -17,7 +17,8 @@ namespace client ClientContext::ClientContext (): m_SharedLocalDestination (nullptr), m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr), - m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr) + m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr), + m_CleanupUDPTimer(m_Service, boost::posix_time::seconds(1)) { } @@ -39,6 +40,13 @@ namespace client m_SharedLocalDestination->Start (); } + if ( m_ServiceThread == nullptr ) { + m_ServiceThread = new std::thread([&] () { + m_Service.run(); + }); + ScheduleCleanupUDP(); + } + m_AddressBook.Start (); std::shared_ptr localDestination; @@ -195,11 +203,26 @@ namespace client } LogPrint(eLogInfo, "Clients: stopping AddressBook"); - m_AddressBook.Stop (); + m_AddressBook.Stop (); + + { + std::lock_guard lock(m_ForwardsMutex); + m_ServerForwards.clear(); + m_ClientForwards.clear(); + } + + for (auto& it: m_Destinations) it.second->Stop (); m_Destinations.clear (); - m_SharedLocalDestination = nullptr; + m_SharedLocalDestination = nullptr; + // stop io service thread + if(m_ServiceThread) { + m_Service.stop(); + m_ServiceThread->join(); + delete m_ServiceThread; + m_ServiceThread = nullptr; + } } void ClientContext::ReloadConfig () @@ -349,7 +372,7 @@ namespace client try { std::string type = section.second.get (I2P_TUNNELS_SECTION_TYPE); - if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT) + if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { // mandatory params std::string dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); @@ -374,17 +397,34 @@ namespace client localDestination = CreateNewLocalDestination (k, false, &options); } } - auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); - if (m_ClientTunnels.insert (std::make_pair (clientTunnel->GetAcceptor ().local_endpoint (), - std::unique_ptr(clientTunnel))).second) - { - clientTunnel->Start (); - numClientTunnels++; - } - else - LogPrint (eLogError, "Clients: I2P client tunnel for endpoint ", clientTunnel->GetAcceptor ().local_endpoint (), " already exists"); + if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { + // udp client + // TODO: ip6 and hostnames + boost::asio::ip::udp::endpoint end(boost::asio::ip::address::from_string(address), port); + if(destinationPort == 0) { + destinationPort = port; + } + auto clientTunnel = new I2PUDPClientTunnel(name, dest, end, localDestination, destinationPort, m_Service); + if(m_ClientForwards.insert(std::make_pair(end, std::unique_ptr(clientTunnel))).second) { + clientTunnel->Start(); + } else { + LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); + delete clientTunnel; + } + } else { + // tcp client + auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); + if (m_ClientTunnels.insert (std::make_pair (clientTunnel->GetAcceptor ().local_endpoint (), + std::unique_ptr(clientTunnel))).second) + { + clientTunnel->Start (); + numClientTunnels++; + } + else + LogPrint (eLogError, "Clients: I2P client tunnel for endpoint ", clientTunnel->GetAcceptor ().local_endpoint (), " already exists"); + } } - else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC) + else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC || type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // mandatory params std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); @@ -411,6 +451,25 @@ namespace client if (!localDestination) localDestination = CreateNewLocalDestination (k, true, &options); + if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { + // udp server tunnel + // TODO: ipv6 and hostnames + boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); + I2PUDPServerTunnel * serverTunnel = new I2PUDPServerTunnel(name, localDestination, endpoint, port, m_Service); + std::lock_guard lock(m_ForwardsMutex); + if(m_ServerForwards.insert( + std::make_pair( + std::make_pair( + localDestination->GetIdentHash(), port), + std::unique_ptr(serverTunnel))).second) { + LogPrint(eLogInfo, "Cleints: I2P Server Forward created for UDP Endpoint ", host, ":", port); + } else { + LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); + delete serverTunnel; + } + continue; + } + I2PServerTunnel * serverTunnel; if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) serverTunnel = new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort, gzip); @@ -460,6 +519,22 @@ namespace client } LogPrint (eLogInfo, "Clients: ", numClientTunnels, " I2P client tunnels created"); LogPrint (eLogInfo, "Clients: ", numServerTunnels, " I2P server tunnels created"); - } + } + void ClientContext::ScheduleCleanupUDP() + { + // schedule cleanup in 1 second + m_CleanupUDPTimer.expires_at(m_CleanupUDPTimer.expires_at() + boost::posix_time::seconds(1)); + m_CleanupUDPTimer.async_wait(std::bind(&ClientContext::CleanupUDP, this, std::placeholders::_1)); + } + + void ClientContext::CleanupUDP(const boost::system::error_code & ecode) + { + if(!ecode) { + std::lock_guard lock(m_ForwardsMutex); + for ( auto & s : m_ServerForwards ) { + s.second->ExpireStale(); + } + } + } } } diff --git a/ClientContext.h b/ClientContext.h index 55176b91..5a054f29 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -24,6 +24,8 @@ namespace client const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc"; + const char I2P_TUNNELS_SECTION_TYPE_UDPCLIENT[] = "udpclient"; + const char I2P_TUNNELS_SECTION_TYPE_UDPSERVER[] = "udpserver"; const char I2P_CLIENT_TUNNEL_PORT[] = "port"; const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination"; @@ -72,7 +74,10 @@ namespace client template void ReadI2CPOptions (const Section& section, std::map& options) const; - private: + void CleanupUDP(const boost::system::error_code & ecode); + void ScheduleCleanupUDP(); + + private: std::mutex m_DestinationsMutex; std::map > m_Destinations; @@ -84,10 +89,21 @@ namespace client i2p::proxy::SOCKSProxy * m_SocksProxy; std::map > m_ClientTunnels; // local endpoint->tunnel std::map, std::unique_ptr > m_ServerTunnels; // ->tunnel - SAMBridge * m_SamBridge; + + std::mutex m_ForwardsMutex; + + std::map > m_ClientForwards; // local endpoint -> udp tunnel + std::map, std::unique_ptr > m_ServerForwards; // -> udp tunnel + + SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; I2CPServer * m_I2CPServer; + boost::asio::io_service m_Service; + std::thread * m_ServiceThread; + + boost::asio::deadline_timer m_CleanupUDPTimer; + public: // for HTTP const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; }; diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 87764a84..e639c12a 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -511,5 +511,153 @@ namespace client conn->Connect (); } + void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + std::lock_guard lock(m_SessionsMutex); + auto & session = ObtainUDPSession(from, toPort, fromPort); + + session.IPSocket.send_to(boost::asio::buffer(buf, len), m_Endpoint); + session.LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + + } + + void I2PUDPServerTunnel::ExpireStale(const uint64_t delta) { + std::lock_guard lock(m_SessionsMutex); + uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); + std::remove_if(m_Sessions.begin(), m_Sessions.end(), [now, delta](const UDPSession & u) -> bool { + return now - u.LastActivity >= delta; + }); + } + + UDPSession & I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) + { + auto ih = from.GetIdentHash(); + for ( UDPSession & s : m_Sessions ) { + if ( s.Identity == ih) { + /** found existing */ + return s; + } + } + /** create new */ + m_Sessions.push_back(UDPSession(m_Service, m_Destination, m_Endpoint, ih, localPort, remotePort)); + return m_Sessions.back(); + } + + UDPSession::UDPSession(boost::asio::io_service & ios, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash from, uint16_t ourPort, uint16_t theirPort) : + Destination(localDestination), + IPSocket(ios, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)), + Identity(from), + ExpectedEndpoint(endpoint), + LocalPort(ourPort), + RemotePort(theirPort) + { + Receive(); + } + + + void UDPSession::Receive() { + IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); + } + + void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) + { + if(!ecode) { + i2p::datagram::DatagramDestination * dgram = Destination->GetDatagramDestination(); + if(dgram) { + LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + dgram->SendDatagramTo(m_Buffer, len, Identity, LocalPort, RemotePort); + } + Receive(); + } + } + + I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint forwardTo, uint16_t port, boost::asio::io_service & service) : + LocalPort(port), + m_Endpoint(forwardTo), + m_Service(service), + m_Destination(localDestination) + { + i2p::datagram::DatagramDestination * dgram = m_Destination->CreateDatagramDestination(); + if(dgram) + dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), LocalPort); + } + + I2PUDPServerTunnel::~I2PUDPServerTunnel() + { + i2p::datagram::DatagramDestination * dgram = m_Destination->GetDatagramDestination(); + if (dgram) { + dgram->ResetReceiver(LocalPort); + } + } + + I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort, boost::asio::io_service & service) : + m_Session(nullptr), + m_RemoteDest(remoteDest), + m_RemoteIdent(nullptr), + m_LocalDest(localDestination), + m_LocalEndpoint(localEndpoint), + m_ResolveThread(nullptr), + m_Service(service), + LocalPort(localEndpoint.port()), + RemotePort(remotePort), + m_cancel_resolve(false) + { + + } + + + + void I2PUDPClientTunnel::Start() { + if (m_ResolveThread == nullptr) + m_ResolveThread = new std::thread(std::bind(&I2PUDPClientTunnel::TryResolving, this)); + } + + void I2PUDPClientTunnel::TryResolving() { + LogPrint(eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); + m_RemoteIdent = new i2p::data::IdentHash; + m_RemoteIdent->Fill(0); + while(!context.GetAddressBook().GetIdentHash(m_RemoteDest, *m_RemoteIdent) && !m_cancel_resolve) { + LogPrint(eLogWarning, "UDP Tunnel: failed to lookup ", m_RemoteDest); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + if(m_cancel_resolve) { + LogPrint(eLogError, "UDP Tunnel: lookup of ", m_RemoteDest, " was cancelled"); + return; + } + // delete existing session + if(m_Session) delete m_Session; + m_Session = new UDPSession(m_Service, m_LocalDest, m_LocalEndpoint, *m_RemoteIdent, LocalPort, RemotePort); + auto dgram = m_LocalDest->CreateDatagramDestination(); + dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4, + std::placeholders::_5), LocalPort); + } + + void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + if(m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) { + // address match + if(m_Session) { + // tell session + m_Session->IPSocket.send_to(boost::asio::buffer(buf, len), m_LocalEndpoint); + } + } + } + + I2PUDPClientTunnel::~I2PUDPClientTunnel() { + auto dgram = m_LocalDest->GetDatagramDestination(); + if (dgram) { + dgram->ResetReceiver(LocalPort); + } + if (m_Session) delete m_Session; + m_cancel_resolve = true; + if(m_ResolveThread) { + m_ResolveThread->join(); + delete m_ResolveThread; + m_ResolveThread = nullptr; + } + if (m_RemoteIdent) delete m_RemoteIdent; + } } } diff --git a/I2PTunnel.h b/I2PTunnel.h index f0332722..232ed2ef 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -9,6 +9,7 @@ #include #include "Identity.h" #include "Destination.h" +#include "Datagram.h" #include "Streaming.h" #include "I2PService.h" @@ -128,8 +129,79 @@ namespace client std::string m_Name, m_Destination; const i2p::data::IdentHash * m_DestinationIdentHash; int m_DestinationPort; - }; + }; + + /** 2 minute timeout for udp sessions */ + const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; + + /** max size for i2p udp */ + const size_t I2P_UDP_MAX_MTU = i2p::datagram::MAX_DATAGRAM_SIZE; + + struct UDPSession + { + std::shared_ptr Destination; + boost::asio::ip::udp::socket IPSocket; + i2p::data::IdentHash Identity; + boost::asio::ip::udp::endpoint ExpectedEndpoint; + boost::asio::ip::udp::endpoint FromEndpoint; + uint64_t LastActivity; + + uint16_t LocalPort; + uint16_t RemotePort; + + uint8_t m_Buffer[I2P_UDP_MAX_MTU]; + + UDPSession(boost::asio::io_service & ios, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash ident, uint16_t ourPort, uint16_t theirPort); + + void HandleReceived(const boost::system::error_code & ecode, std::size_t len); + void Receive(); + + }; + + /** server side udp tunnel, many i2p inbound to 1 ip outbound */ + class I2PUDPServerTunnel + { + public: + I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint forwardTo, uint16_t port, boost::asio::io_service & service); + ~I2PUDPServerTunnel(); + /** expire stale udp conversations */ + void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); + + private: + void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + UDPSession & ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); + private: + const uint16_t LocalPort; + boost::asio::ip::udp::endpoint m_Endpoint; + std::mutex m_SessionsMutex; + std::vector m_Sessions; + boost::asio::io_service & m_Service; + std::shared_ptr m_Destination; + uint8_t m_Buffer[I2P_UDP_MAX_MTU]; + }; + + class I2PUDPClientTunnel + { + public: + I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort, boost::asio::io_service & service); + ~I2PUDPClientTunnel(); + void Start(); + private: + void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void TryResolving(); + UDPSession * m_Session; + const std::string m_RemoteDest; + std::shared_ptr m_LocalDest; + i2p::data::IdentHash * m_RemoteIdent; + const boost::asio::ip::udp::endpoint m_LocalEndpoint; + std::thread * m_ResolveThread; + boost::asio::io_service & m_Service; + uint16_t LocalPort; + uint16_t RemotePort; + bool m_cancel_resolve; + }; + class I2PServerTunnel: public I2PService { public: @@ -150,7 +222,7 @@ namespace client const char* GetName() { return m_Name.c_str (); } void SetMaxConnsPerMinute(const uint32_t conns) { m_PortDestination->SetMaxConnsPerMinute(conns); } - + private: void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, @@ -167,7 +239,7 @@ namespace client boost::asio::ip::tcp::endpoint m_Endpoint; std::shared_ptr m_PortDestination; std::set m_AccessList; - bool m_IsAccessList; + bool m_IsAccessList; }; class I2PServerTunnelHTTP: public I2PServerTunnel diff --git a/Identity.h b/Identity.h index d9fb7761..8f3e9e9d 100644 --- a/Identity.h +++ b/Identity.h @@ -92,6 +92,8 @@ namespace data CryptoKeyType GetCryptoKeyType () const; void DropVerifier () const; // to save memory + bool operator == (const IdentityEx & other) const { return GetIdentHash() == other.GetIdentHash(); } + private: void CreateVerifier () const; diff --git a/Tag.h b/Tag.h index b6f94de7..6e5638a4 100644 --- a/Tag.h +++ b/Tag.h @@ -50,6 +50,11 @@ namespace data { return true; } + /** fill with a value */ + void Fill(uint8_t c) { + memset(m_Buf, c, sz); + } + std::string ToBase64 () const { char str[sz*2]; From e8f9ecc7d902ecc05452649c40e1ef030faa9274 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 15:33:19 -0400 Subject: [PATCH 1762/6300] fixes --- ClientContext.cpp | 4 ++++ I2PTunnel.cpp | 12 +++++++----- I2PTunnel.h | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 68115402..4440cb44 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -404,6 +404,9 @@ namespace client if(destinationPort == 0) { destinationPort = port; } + if (!localDestination) { + localDestination = m_SharedLocalDestination; + } auto clientTunnel = new I2PUDPClientTunnel(name, dest, end, localDestination, destinationPort, m_Service); if(m_ClientForwards.insert(std::make_pair(end, std::unique_ptr(clientTunnel))).second) { clientTunnel->Start(); @@ -411,6 +414,7 @@ namespace client LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); delete clientTunnel; } + } else { // tcp client auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index e639c12a..55d40fad 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -539,19 +539,20 @@ namespace client } } /** create new */ - m_Sessions.push_back(UDPSession(m_Service, m_Destination, m_Endpoint, ih, localPort, remotePort)); + m_Sessions.push_back(UDPSession(m_Service, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0), m_Destination, m_Endpoint, ih, localPort, remotePort)); return m_Sessions.back(); } - UDPSession::UDPSession(boost::asio::io_service & ios, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash from, uint16_t ourPort, uint16_t theirPort) : + UDPSession::UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash from, uint16_t ourPort, uint16_t theirPort) : Destination(localDestination), - IPSocket(ios, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)), + IPSocket(ios, localEndpoint), Identity(from), ExpectedEndpoint(endpoint), LocalPort(ourPort), RemotePort(theirPort) { Receive(); + LogPrint(eLogDebug, "UDPSession: bound to", IPSocket.local_endpoint()); } @@ -624,10 +625,11 @@ namespace client LogPrint(eLogError, "UDP Tunnel: lookup of ", m_RemoteDest, " was cancelled"); return; } + LogPrint(eLogInfo, "UDP Tunnel: resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32()); + auto dgram = m_LocalDest->CreateDatagramDestination(); // delete existing session if(m_Session) delete m_Session; - m_Session = new UDPSession(m_Service, m_LocalDest, m_LocalEndpoint, *m_RemoteIdent, LocalPort, RemotePort); - auto dgram = m_LocalDest->CreateDatagramDestination(); + m_Session = new UDPSession(m_Service, m_LocalEndpoint, m_LocalDest, m_LocalEndpoint, *m_RemoteIdent, LocalPort, RemotePort); dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, diff --git a/I2PTunnel.h b/I2PTunnel.h index 232ed2ef..d2b895df 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -152,7 +152,7 @@ namespace client uint8_t m_Buffer[I2P_UDP_MAX_MTU]; - UDPSession(boost::asio::io_service & ios, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash ident, uint16_t ourPort, uint16_t theirPort); + UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash ident, uint16_t ourPort, uint16_t theirPort); void HandleReceived(const boost::system::error_code & ecode, std::size_t len); void Receive(); From e529d3ecc9ef11cf2c29634232bd559e312c4bae Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 15:39:11 -0400 Subject: [PATCH 1763/6300] fixes --- ClientContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 4440cb44..5b4d28d1 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -466,7 +466,7 @@ namespace client std::make_pair( localDestination->GetIdentHash(), port), std::unique_ptr(serverTunnel))).second) { - LogPrint(eLogInfo, "Cleints: I2P Server Forward created for UDP Endpoint ", host, ":", port); + LogPrint(eLogInfo, "Cleints: I2P Server Forward created for UDP Endpoint ", host, ":", port, " via ",localDestination->GetIdentHash().ToBase32()); } else { LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); delete serverTunnel; From 2a5af370754b607ef061d9c4c2d76b43dee58441 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 15:47:00 -0400 Subject: [PATCH 1764/6300] retrgiger expiration --- ClientContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ClientContext.cpp b/ClientContext.cpp index 5b4d28d1..dafb2789 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -538,6 +538,7 @@ namespace client for ( auto & s : m_ServerForwards ) { s.second->ExpireStale(); } + ScheduleCleanupUDP(); } } } From 2679c588921f3c9a47e3ed93285e47847c787243 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 15:51:39 -0400 Subject: [PATCH 1765/6300] logging and ip checks --- I2PTunnel.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 55d40fad..5e4c760b 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -552,7 +552,7 @@ namespace client RemotePort(theirPort) { Receive(); - LogPrint(eLogDebug, "UDPSession: bound to", IPSocket.local_endpoint()); + LogPrint(eLogDebug, "UDPSession: bound to ", IPSocket.local_endpoint()); } @@ -563,10 +563,12 @@ namespace client void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) { if(!ecode) { + LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); i2p::datagram::DatagramDestination * dgram = Destination->GetDatagramDestination(); - if(dgram) { + if(dgram && FromEndpoint == ExpectedEndpoint) { LastActivity = i2p::util::GetMillisecondsSinceEpoch(); dgram->SendDatagramTo(m_Buffer, len, Identity, LocalPort, RemotePort); + LogPrint(eLogDebug, "UDPSession: forward to ", Identity.ToBase32()); } Receive(); } From 51783a45e618290d223bdc65bb9bde86b08f4e21 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 15:56:52 -0400 Subject: [PATCH 1766/6300] set last activity --- I2PTunnel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 5e4c760b..2e4b4fed 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -548,6 +548,7 @@ namespace client IPSocket(ios, localEndpoint), Identity(from), ExpectedEndpoint(endpoint), + LastActivity(i2p::util::GetMillisecondsSinceEpoch()), LocalPort(ourPort), RemotePort(theirPort) { From 46d640cd86ef94a4d5020e17dcf60b412bea68b1 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 18:46:34 -0400 Subject: [PATCH 1767/6300] fixes --- ClientContext.cpp | 7 +++---- I2PTunnel.cpp | 41 +++++++++++++++++++++++++++-------------- I2PTunnel.h | 4 ++-- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index dafb2789..3bc6a2d3 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -42,7 +42,9 @@ namespace client if ( m_ServiceThread == nullptr ) { m_ServiceThread = new std::thread([&] () { + LogPrint(eLogInfo, "ClientContext: starting service"); m_Service.run(); + LogPrint(eLogError, "ClientContext: service died"); }); ScheduleCleanupUDP(); } @@ -394,7 +396,7 @@ namespace client { localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); if (!localDestination) - localDestination = CreateNewLocalDestination (k, false, &options); + localDestination = CreateNewLocalDestination (k, type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT, &options); } } if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { @@ -412,7 +414,6 @@ namespace client clientTunnel->Start(); } else { LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); - delete clientTunnel; } } else { @@ -454,7 +455,6 @@ namespace client localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); if (!localDestination) localDestination = CreateNewLocalDestination (k, true, &options); - if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // udp server tunnel // TODO: ipv6 and hostnames @@ -469,7 +469,6 @@ namespace client LogPrint(eLogInfo, "Cleints: I2P Server Forward created for UDP Endpoint ", host, ":", port, " via ",localDestination->GetIdentHash().ToBase32()); } else { LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); - delete serverTunnel; } continue; } diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 2e4b4fed..c864fbc1 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -515,7 +515,6 @@ namespace client { std::lock_guard lock(m_SessionsMutex); auto & session = ObtainUDPSession(from, toPort, fromPort); - session.IPSocket.send_to(boost::asio::buffer(buf, len), m_Endpoint); session.LastActivity = i2p::util::GetMillisecondsSinceEpoch(); @@ -535,19 +534,21 @@ namespace client for ( UDPSession & s : m_Sessions ) { if ( s.Identity == ih) { /** found existing */ + LogPrint(eLogDebug, "UDP Server: found ", s.SendEndpoint); return s; } } /** create new */ - m_Sessions.push_back(UDPSession(m_Service, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0), m_Destination, m_Endpoint, ih, localPort, remotePort)); + boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); + m_Sessions.push_back(UDPSession(m_Service, ep, m_Destination, ep, ih, localPort, remotePort)); return m_Sessions.back(); } - UDPSession::UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash from, uint16_t ourPort, uint16_t theirPort) : + UDPSession::UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash to, uint16_t ourPort, uint16_t theirPort) : Destination(localDestination), IPSocket(ios, localEndpoint), - Identity(from), - ExpectedEndpoint(endpoint), + Identity(to), + SendEndpoint(endpoint), LastActivity(i2p::util::GetMillisecondsSinceEpoch()), LocalPort(ourPort), RemotePort(theirPort) @@ -558,18 +559,24 @@ namespace client void UDPSession::Receive() { + LogPrint(eLogDebug, "UDPSesssion: Recveive"); IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); } void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) { + LogPrint(eLogDebug, "UDPSesssion: HandleRecveived"); if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); - i2p::datagram::DatagramDestination * dgram = Destination->GetDatagramDestination(); - if(dgram && FromEndpoint == ExpectedEndpoint) { - LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - dgram->SendDatagramTo(m_Buffer, len, Identity, LocalPort, RemotePort); - LogPrint(eLogDebug, "UDPSession: forward to ", Identity.ToBase32()); + if (Destination) { + auto dgram = Destination->CreateDatagramDestination(); + if(dgram) { + LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + dgram->SendDatagramTo(m_Buffer, len, Identity, RemotePort, LocalPort); + LogPrint(eLogDebug, "UDPSession: forward ", len, "B to ", Identity.ToBase32()); + } + } else { + LogPrint(eLogWarning, "UDPSession: no Local Destination"); } Receive(); } @@ -583,7 +590,7 @@ namespace client { i2p::datagram::DatagramDestination * dgram = m_Destination->CreateDatagramDestination(); if(dgram) - dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), LocalPort); + dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); } I2PUDPServerTunnel::~I2PUDPServerTunnel() @@ -632,11 +639,12 @@ namespace client auto dgram = m_LocalDest->CreateDatagramDestination(); // delete existing session if(m_Session) delete m_Session; - m_Session = new UDPSession(m_Service, m_LocalEndpoint, m_LocalDest, m_LocalEndpoint, *m_RemoteIdent, LocalPort, RemotePort); + boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); + m_Session = new UDPSession(m_Service, m_LocalEndpoint, m_LocalDest, ep, *m_RemoteIdent, LocalPort, RemotePort); dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, - std::placeholders::_5), LocalPort); + std::placeholders::_5)); } void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) @@ -645,8 +653,13 @@ namespace client // address match if(m_Session) { // tell session - m_Session->IPSocket.send_to(boost::asio::buffer(buf, len), m_LocalEndpoint); + LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", from.GetIdentHash().ToBase32(), " via ", m_Session->SendEndpoint); + m_Session->IPSocket.send_to(boost::asio::buffer(buf, len), m_Session->SendEndpoint); + } else { + LogPrint(eLogWarning, "UDP Client: no session"); } + } else { + LogPrint(eLogWarning, "UDP Client: unwarrented traffic from ", from.GetIdentHash().ToBase32()); } } diff --git a/I2PTunnel.h b/I2PTunnel.h index d2b895df..e50c47f7 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -143,8 +143,8 @@ namespace client std::shared_ptr Destination; boost::asio::ip::udp::socket IPSocket; i2p::data::IdentHash Identity; - boost::asio::ip::udp::endpoint ExpectedEndpoint; boost::asio::ip::udp::endpoint FromEndpoint; + boost::asio::ip::udp::endpoint SendEndpoint; uint64_t LastActivity; uint16_t LocalPort; @@ -193,8 +193,8 @@ namespace client UDPSession * m_Session; const std::string m_RemoteDest; std::shared_ptr m_LocalDest; - i2p::data::IdentHash * m_RemoteIdent; const boost::asio::ip::udp::endpoint m_LocalEndpoint; + i2p::data::IdentHash * m_RemoteIdent; std::thread * m_ResolveThread; boost::asio::io_service & m_Service; uint16_t LocalPort; From 194d63acd81a9af274ea1198470e1669320de6c5 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 19:17:08 -0400 Subject: [PATCH 1768/6300] fixes --- I2PTunnel.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index c864fbc1..641c5795 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -534,7 +534,6 @@ namespace client for ( UDPSession & s : m_Sessions ) { if ( s.Identity == ih) { /** found existing */ - LogPrint(eLogDebug, "UDP Server: found ", s.SendEndpoint); return s; } } @@ -555,6 +554,7 @@ namespace client { Receive(); LogPrint(eLogDebug, "UDPSession: bound to ", IPSocket.local_endpoint()); + } @@ -569,11 +569,13 @@ namespace client if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); if (Destination) { - auto dgram = Destination->CreateDatagramDestination(); + auto dgram = Destination->GetDatagramDestination(); if(dgram) { LastActivity = i2p::util::GetMillisecondsSinceEpoch(); dgram->SendDatagramTo(m_Buffer, len, Identity, RemotePort, LocalPort); LogPrint(eLogDebug, "UDPSession: forward ", len, "B to ", Identity.ToBase32()); + } else { + LogPrint(eLogWarning, "UDPSession: no datagram destination"); } } else { LogPrint(eLogWarning, "UDPSession: no Local Destination"); From aa11a5deb8b459325c0cae4d84c043e595129c2f Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 19:27:01 -0400 Subject: [PATCH 1769/6300] fix --- I2PTunnel.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 641c5795..5f17dd9c 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -539,7 +539,7 @@ namespace client } /** create new */ boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); - m_Sessions.push_back(UDPSession(m_Service, ep, m_Destination, ep, ih, localPort, remotePort)); + m_Sessions.push_back(UDPSession(m_Service, ep, m_Destination, m_Endpoint, ih, localPort, remotePort)); return m_Sessions.back(); } @@ -554,7 +554,9 @@ namespace client { Receive(); LogPrint(eLogDebug, "UDPSession: bound to ", IPSocket.local_endpoint()); - + if (localEndpoint == endpoint) { + SendEndpoint = IPSocket.local_endpoint(); + } } From 287e32aaed3963e167f14d88eadc9f77bbcc05e2 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 19:33:33 -0400 Subject: [PATCH 1770/6300] logging --- I2PTunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 5f17dd9c..749890bf 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -575,7 +575,7 @@ namespace client if(dgram) { LastActivity = i2p::util::GetMillisecondsSinceEpoch(); dgram->SendDatagramTo(m_Buffer, len, Identity, RemotePort, LocalPort); - LogPrint(eLogDebug, "UDPSession: forward ", len, "B to ", Identity.ToBase32()); + LogPrint(eLogDebug, "UDPSession: forward ", len, "B to ", Identity.ToBase32(), " from ", Destination->GetIdentHash().ToBase32()); } else { LogPrint(eLogWarning, "UDPSession: no datagram destination"); } From bf46c241d02b3306ccd5045f987a49c3516d3426 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 19:38:12 -0400 Subject: [PATCH 1771/6300] fixes --- I2PTunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 749890bf..7a2c9838 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -594,7 +594,7 @@ namespace client { i2p::datagram::DatagramDestination * dgram = m_Destination->CreateDatagramDestination(); if(dgram) - dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), LocalPort); } I2PUDPServerTunnel::~I2PUDPServerTunnel() From bce0e3ebf6c93a7ce8faaf9cfe3df66b944890f5 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 19:45:32 -0400 Subject: [PATCH 1772/6300] fix --- I2PTunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 7a2c9838..4a5897c4 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -574,7 +574,7 @@ namespace client auto dgram = Destination->GetDatagramDestination(); if(dgram) { LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - dgram->SendDatagramTo(m_Buffer, len, Identity, RemotePort, LocalPort); + dgram->SendDatagramTo(m_Buffer, len, Identity, LocalPort, RemotePort); LogPrint(eLogDebug, "UDPSession: forward ", len, "B to ", Identity.ToBase32(), " from ", Destination->GetIdentHash().ToBase32()); } else { LogPrint(eLogWarning, "UDPSession: no datagram destination"); From bbfe6b66ef81775c3591906280d1232dcf3434ad Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 19:48:47 -0400 Subject: [PATCH 1773/6300] fix --- I2PTunnel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 4a5897c4..534086c8 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -574,7 +574,7 @@ namespace client auto dgram = Destination->GetDatagramDestination(); if(dgram) { LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - dgram->SendDatagramTo(m_Buffer, len, Identity, LocalPort, RemotePort); + dgram->SendDatagramTo(m_Buffer, len, Identity, 0, 0); LogPrint(eLogDebug, "UDPSession: forward ", len, "B to ", Identity.ToBase32(), " from ", Destination->GetIdentHash().ToBase32()); } else { LogPrint(eLogWarning, "UDPSession: no datagram destination"); @@ -594,14 +594,14 @@ namespace client { i2p::datagram::DatagramDestination * dgram = m_Destination->CreateDatagramDestination(); if(dgram) - dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), LocalPort); + dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), 0); } I2PUDPServerTunnel::~I2PUDPServerTunnel() { i2p::datagram::DatagramDestination * dgram = m_Destination->GetDatagramDestination(); if (dgram) { - dgram->ResetReceiver(LocalPort); + dgram->ResetReceiver(0); } } From 28627a81dc0707e0fcd5b491c1ff49c382b3ff4d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 20:18:41 -0400 Subject: [PATCH 1774/6300] update --- I2PTunnel.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 534086c8..87370806 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -540,7 +540,9 @@ namespace client /** create new */ boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); m_Sessions.push_back(UDPSession(m_Service, ep, m_Destination, m_Endpoint, ih, localPort, remotePort)); - return m_Sessions.back(); + auto & s = m_Sessions.back(); + s.SendEndpoint = s.IPSocket.local_endpoint(); + return s; } UDPSession::UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash to, uint16_t ourPort, uint16_t theirPort) : @@ -552,11 +554,8 @@ namespace client LocalPort(ourPort), RemotePort(theirPort) { + IPSocket.local_endpoint(); Receive(); - LogPrint(eLogDebug, "UDPSession: bound to ", IPSocket.local_endpoint()); - if (localEndpoint == endpoint) { - SendEndpoint = IPSocket.local_endpoint(); - } } @@ -583,6 +582,8 @@ namespace client LogPrint(eLogWarning, "UDPSession: no Local Destination"); } Receive(); + } else { + LogPrint(eLogError, "UDPSession: ", ecode.message()); } } From 72974c85c8bfd9541ca38ab711cbcdc3f85e8953 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 20:23:39 -0400 Subject: [PATCH 1775/6300] try fix --- I2PTunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 87370806..d5b1a25b 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -570,7 +570,7 @@ namespace client if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); if (Destination) { - auto dgram = Destination->GetDatagramDestination(); + auto dgram = Destination->CreateDatagramDestination(); if(dgram) { LastActivity = i2p::util::GetMillisecondsSinceEpoch(); dgram->SendDatagramTo(m_Buffer, len, Identity, 0, 0); From 7b5e18d94b5bcf5b1483aa84159d4592262a9b48 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 21:17:09 -0400 Subject: [PATCH 1776/6300] changes --- ClientContext.cpp | 2 +- Destination.cpp | 17 +++++------------ Destination.h | 6 +++--- I2PTunnel.cpp | 32 ++++++++++++++------------------ I2PTunnel.h | 4 ++-- 5 files changed, 25 insertions(+), 36 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 3bc6a2d3..c031aae6 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -466,7 +466,7 @@ namespace client std::make_pair( localDestination->GetIdentHash(), port), std::unique_ptr(serverTunnel))).second) { - LogPrint(eLogInfo, "Cleints: I2P Server Forward created for UDP Endpoint ", host, ":", port, " via ",localDestination->GetIdentHash().ToBase32()); + LogPrint(eLogInfo, "Clients: I2P Server Forward created for UDP Endpoint ", host, ":", port, " via ",localDestination->GetIdentHash().ToBase32()); } else { LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); } diff --git a/Destination.cpp b/Destination.cpp index 6f26a84c..d48bee92 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -677,8 +677,6 @@ namespace client ClientDestination::~ClientDestination () { - if (m_DatagramDestination) - delete m_DatagramDestination; } bool ClientDestination::Start () @@ -703,13 +701,8 @@ namespace client m_StreamingDestination = nullptr; for (auto& it: m_StreamingDestinationsByPorts) it.second->Stop (); - if (m_DatagramDestination) - { - auto d = m_DatagramDestination; - m_DatagramDestination = nullptr; - delete d; - } - return true; + m_DatagramDestination = nullptr; + return true; } else return false; @@ -819,10 +812,10 @@ namespace client return dest; } - i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination () + std::shared_ptr ClientDestination::CreateDatagramDestination () { - if (!m_DatagramDestination) - m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis ()); + if (m_DatagramDestination == nullptr) + m_DatagramDestination = std::make_shared (GetSharedFromThis ()); return m_DatagramDestination; } diff --git a/Destination.h b/Destination.h index 75c94efc..6cd4c19c 100644 --- a/Destination.h +++ b/Destination.h @@ -164,8 +164,8 @@ namespace client bool IsAcceptingStreams () const; // datagram - i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; - i2p::datagram::DatagramDestination * CreateDatagramDestination (); + std::shared_ptr GetDatagramDestination () const { return m_DatagramDestination; }; + std::shared_ptr CreateDatagramDestination (); // implements LocalDestination const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; @@ -190,7 +190,7 @@ namespace client std::shared_ptr m_StreamingDestination; // default std::map > m_StreamingDestinationsByPorts; - i2p::datagram::DatagramDestination * m_DatagramDestination; + std::shared_ptr m_DatagramDestination; public: diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index d5b1a25b..068cbe37 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -539,14 +539,14 @@ namespace client } /** create new */ boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); - m_Sessions.push_back(UDPSession(m_Service, ep, m_Destination, m_Endpoint, ih, localPort, remotePort)); + m_Sessions.push_back(UDPSession(m_Service, ep, m_LocalDest, m_Endpoint, ih, localPort, remotePort)); auto & s = m_Sessions.back(); s.SendEndpoint = s.IPSocket.local_endpoint(); return s; } UDPSession::UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash to, uint16_t ourPort, uint16_t theirPort) : - Destination(localDestination), + m_Destination(localDestination), IPSocket(ios, localEndpoint), Identity(to), SendEndpoint(endpoint), @@ -569,17 +569,12 @@ namespace client LogPrint(eLogDebug, "UDPSesssion: HandleRecveived"); if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); - if (Destination) { - auto dgram = Destination->CreateDatagramDestination(); - if(dgram) { - LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - dgram->SendDatagramTo(m_Buffer, len, Identity, 0, 0); - LogPrint(eLogDebug, "UDPSession: forward ", len, "B to ", Identity.ToBase32(), " from ", Destination->GetIdentHash().ToBase32()); - } else { - LogPrint(eLogWarning, "UDPSession: no datagram destination"); - } + auto dgram = m_Destination->GetDatagramDestination(); + if(dgram) { + LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + dgram->SendDatagramTo(m_Buffer, len, Identity, 0, 0); } else { - LogPrint(eLogWarning, "UDPSession: no Local Destination"); + LogPrint(eLogWarning, "UDPSession: no datagram destination"); } Receive(); } else { @@ -590,20 +585,21 @@ namespace client I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint forwardTo, uint16_t port, boost::asio::io_service & service) : LocalPort(port), m_Endpoint(forwardTo), - m_Service(service), - m_Destination(localDestination) + m_Service(service) { - i2p::datagram::DatagramDestination * dgram = m_Destination->CreateDatagramDestination(); - if(dgram) - dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), 0); + m_LocalDest = localDestination; + m_LocalDest->Start(); + auto dgram = m_LocalDest->CreateDatagramDestination(); + dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), 0); } I2PUDPServerTunnel::~I2PUDPServerTunnel() { - i2p::datagram::DatagramDestination * dgram = m_Destination->GetDatagramDestination(); + auto dgram = m_LocalDest->GetDatagramDestination(); if (dgram) { dgram->ResetReceiver(0); } + LogPrint(eLogInfo, "UDPServer: done"); } I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort, boost::asio::io_service & service) : diff --git a/I2PTunnel.h b/I2PTunnel.h index e50c47f7..54ea09e9 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -140,7 +140,7 @@ namespace client struct UDPSession { - std::shared_ptr Destination; + std::shared_ptr m_Destination; boost::asio::ip::udp::socket IPSocket; i2p::data::IdentHash Identity; boost::asio::ip::udp::endpoint FromEndpoint; @@ -177,7 +177,7 @@ namespace client std::mutex m_SessionsMutex; std::vector m_Sessions; boost::asio::io_service & m_Service; - std::shared_ptr m_Destination; + std::shared_ptr m_LocalDest; uint8_t m_Buffer[I2P_UDP_MAX_MTU]; }; From da82b14307d3df84b265f413b8deca324f95ed2d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 21:28:24 -0400 Subject: [PATCH 1777/6300] changes --- I2PTunnel.cpp | 2 +- I2PTunnel.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 068cbe37..531eea87 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -569,7 +569,7 @@ namespace client LogPrint(eLogDebug, "UDPSesssion: HandleRecveived"); if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); - auto dgram = m_Destination->GetDatagramDestination(); + auto dgram = m_Destination.get()->GetDatagramDestination(); if(dgram) { LastActivity = i2p::util::GetMillisecondsSinceEpoch(); dgram->SendDatagramTo(m_Buffer, len, Identity, 0, 0); diff --git a/I2PTunnel.h b/I2PTunnel.h index 54ea09e9..cc78722c 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -136,7 +136,7 @@ namespace client const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; /** max size for i2p udp */ - const size_t I2P_UDP_MAX_MTU = i2p::datagram::MAX_DATAGRAM_SIZE; + const size_t I2P_UDP_MAX_MTU = 4000; struct UDPSession { From ff6d66b96ed3ba90c008a43b11e994e509b2a7ec Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 21:40:21 -0400 Subject: [PATCH 1778/6300] init addressbook first --- ClientContext.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index c031aae6..de06a465 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -40,6 +40,9 @@ namespace client m_SharedLocalDestination->Start (); } + + m_AddressBook.Start (); + if ( m_ServiceThread == nullptr ) { m_ServiceThread = new std::thread([&] () { LogPrint(eLogInfo, "ClientContext: starting service"); @@ -48,8 +51,6 @@ namespace client }); ScheduleCleanupUDP(); } - - m_AddressBook.Start (); std::shared_ptr localDestination; bool httproxy; i2p::config::GetOption("httpproxy.enabled", httproxy); From 808b758cd71d169966c6b9051bdde51cab7689ea Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 21:45:08 -0400 Subject: [PATCH 1779/6300] fix --- AddressBook.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index b3f0f1ce..bfb44be8 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -544,7 +544,7 @@ namespace client if (dest) { auto datagram = dest->GetDatagramDestination (); - if (!datagram) + if (datagram == nullptr) datagram = dest->CreateDatagramDestination (); datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), From 7ef7ef03dd62ca056ae6ee5d9c77ff6a19347cb1 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 21:47:56 -0400 Subject: [PATCH 1780/6300] fix --- AddressBook.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index bfb44be8..2c4fa1b5 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -543,9 +543,7 @@ namespace client auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { - auto datagram = dest->GetDatagramDestination (); - if (datagram == nullptr) - datagram = dest->CreateDatagramDestination (); + auto datagram = dest->CreateDatagramDestination (); datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), ADDRESS_RESPONSE_DATAGRAM_PORT); From d159d497003e584ac2bfc6818fd7b586ff940a74 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 21:51:32 -0400 Subject: [PATCH 1781/6300] os x fix --- Datagram.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 2015622c..b950d46b 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -22,29 +22,30 @@ namespace datagram void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort) { + auto owner = m_Owner.get(); uint8_t buf[MAX_DATAGRAM_SIZE]; - auto identityLen = m_Owner->GetIdentity ()->ToBuffer (buf, MAX_DATAGRAM_SIZE); + auto identityLen = owner->GetIdentity ()->ToBuffer (buf, MAX_DATAGRAM_SIZE); uint8_t * signature = buf + identityLen; - auto signatureLen = m_Owner->GetIdentity ()->GetSignatureLen (); + auto signatureLen = owner->GetIdentity ()->GetSignatureLen (); uint8_t * buf1 = signature + signatureLen; size_t headerLen = identityLen + signatureLen; memcpy (buf1, payload, len); - if (m_Owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) + if (owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) { uint8_t hash[32]; SHA256(buf1, len, hash); - m_Owner->Sign (hash, 32, signature); + owner->Sign (hash, 32, signature); } else - m_Owner->Sign (buf1, len, signature); + owner->Sign (buf1, len, signature); auto msg = CreateDataMessage (buf, len + headerLen, fromPort, toPort); - auto remote = m_Owner->FindLeaseSet (ident); + auto remote = owner->FindLeaseSet (ident); if (remote) - m_Owner->GetService ().post (std::bind (&DatagramDestination::SendMsg, this, msg, remote)); + owner->GetService ().post (std::bind (&DatagramDestination::SendMsg, this, msg, remote)); else - m_Owner->RequestDestination (ident, std::bind (&DatagramDestination::HandleLeaseSetRequestComplete, this, std::placeholders::_1, msg)); + owner->RequestDestination (ident, std::bind (&DatagramDestination::HandleLeaseSetRequestComplete, this, std::placeholders::_1, msg)); } void DatagramDestination::HandleLeaseSetRequestComplete (std::shared_ptr remote, std::shared_ptr msg) From 1bba5d5c9409998c45b45df797995a0d67db0078 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 21:55:00 -0400 Subject: [PATCH 1782/6300] osx fix --- Datagram.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index b950d46b..ebb5c993 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -23,15 +23,16 @@ namespace datagram void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort) { auto owner = m_Owner.get(); + auto i = owner->GetIdentity().get(); uint8_t buf[MAX_DATAGRAM_SIZE]; - auto identityLen = owner->GetIdentity ()->ToBuffer (buf, MAX_DATAGRAM_SIZE); + auto identityLen = i->ToBuffer (buf, MAX_DATAGRAM_SIZE); uint8_t * signature = buf + identityLen; - auto signatureLen = owner->GetIdentity ()->GetSignatureLen (); + auto signatureLen = i->GetSignatureLen (); uint8_t * buf1 = signature + signatureLen; size_t headerLen = identityLen + signatureLen; memcpy (buf1, payload, len); - if (owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) + if (i->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) { uint8_t hash[32]; SHA256(buf1, len, hash); From bc439cc47fef983f27aa932ba702793cf0774daf Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 21:57:36 -0400 Subject: [PATCH 1783/6300] osx fix --- Datagram.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Datagram.cpp b/Datagram.cpp index ebb5c993..c877902b 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -23,7 +23,7 @@ namespace datagram void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort) { auto owner = m_Owner.get(); - auto i = owner->GetIdentity().get(); + auto i = owner->GetIdentity(); uint8_t buf[MAX_DATAGRAM_SIZE]; auto identityLen = i->ToBuffer (buf, MAX_DATAGRAM_SIZE); uint8_t * signature = buf + identityLen; From b1e3f887047c08bef5b4b9dc73156796c8aec675 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 22:00:31 -0400 Subject: [PATCH 1784/6300] osx fix --- Datagram.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Datagram.cpp b/Datagram.cpp index c877902b..dbe4d978 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -22,7 +22,7 @@ namespace datagram void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort) { - auto owner = m_Owner.get(); + auto owner = m_Owner; auto i = owner->GetIdentity(); uint8_t buf[MAX_DATAGRAM_SIZE]; auto identityLen = i->ToBuffer (buf, MAX_DATAGRAM_SIZE); From 47ebb6ae6c1692bf8d4e21ca79c08e03aab9e3eb Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 22:11:41 -0400 Subject: [PATCH 1785/6300] osx fix --- ClientContext.cpp | 21 +++++++++++---------- I2PTunnel.cpp | 1 + 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index de06a465..0b9b1f60 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -43,15 +43,6 @@ namespace client m_AddressBook.Start (); - if ( m_ServiceThread == nullptr ) { - m_ServiceThread = new std::thread([&] () { - LogPrint(eLogInfo, "ClientContext: starting service"); - m_Service.run(); - LogPrint(eLogError, "ClientContext: service died"); - }); - ScheduleCleanupUDP(); - } - std::shared_ptr localDestination; bool httproxy; i2p::config::GetOption("httpproxy.enabled", httproxy); if (httproxy) { @@ -74,7 +65,7 @@ namespace client LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); } } - + bool socksproxy; i2p::config::GetOption("socksproxy.enabled", socksproxy); if (socksproxy) { std::string socksProxyKeys; i2p::config::GetOption("socksproxy.keys", socksProxyKeys); @@ -100,6 +91,16 @@ namespace client // I2P tunnels ReadTunnels (); + if ( m_ServiceThread == nullptr ) { + m_ServiceThread = new std::thread([&] () { + LogPrint(eLogInfo, "ClientContext: starting service"); + m_Service.run(); + LogPrint(eLogError, "ClientContext: service died"); + }); + ScheduleCleanupUDP(); + } + + // SAM bool sam; i2p::config::GetOption("sam.enabled", sam); if (sam) { diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 531eea87..a2811695 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -628,6 +628,7 @@ namespace client LogPrint(eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); m_RemoteIdent = new i2p::data::IdentHash; m_RemoteIdent->Fill(0); + while(!context.GetAddressBook().GetIdentHash(m_RemoteDest, *m_RemoteIdent) && !m_cancel_resolve) { LogPrint(eLogWarning, "UDP Tunnel: failed to lookup ", m_RemoteDest); std::this_thread::sleep_for(std::chrono::seconds(1)); From 9062bf14b69db802e379a9a8ce160377addb2540 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 22:16:35 -0400 Subject: [PATCH 1786/6300] osx fix --- I2PTunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index a2811695..0982c388 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -638,7 +638,7 @@ namespace client return; } LogPrint(eLogInfo, "UDP Tunnel: resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32()); - auto dgram = m_LocalDest->CreateDatagramDestination(); + auto dgram = m_LocalDest->CreateDatagramDestination().get(); // delete existing session if(m_Session) delete m_Session; boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); From 0c709f431f1e0fb20fbbdff1fbd731b1c35f5398 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 22:18:59 -0400 Subject: [PATCH 1787/6300] osx fix --- I2PTunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 0982c388..7f03311f 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -569,7 +569,7 @@ namespace client LogPrint(eLogDebug, "UDPSesssion: HandleRecveived"); if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); - auto dgram = m_Destination.get()->GetDatagramDestination(); + auto dgram = m_Destination.get()->GetDatagramDestination().get(); if(dgram) { LastActivity = i2p::util::GetMillisecondsSinceEpoch(); dgram->SendDatagramTo(m_Buffer, len, Identity, 0, 0); From 211660eb3d368e38fa4a4949a75130f4a679c4e0 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 22:23:27 -0400 Subject: [PATCH 1788/6300] osx fix --- I2PTunnel.cpp | 4 ++-- I2PTunnel.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 7f03311f..346baf29 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -545,7 +545,7 @@ namespace client return s; } - UDPSession::UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash to, uint16_t ourPort, uint16_t theirPort) : + UDPSession::UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash to, uint16_t ourPort, uint16_t theirPort) : m_Destination(localDestination), IPSocket(ios, localEndpoint), Identity(to), @@ -569,7 +569,7 @@ namespace client LogPrint(eLogDebug, "UDPSesssion: HandleRecveived"); if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); - auto dgram = m_Destination.get()->GetDatagramDestination().get(); + auto dgram = m_Destination->GetDatagramDestination(); if(dgram) { LastActivity = i2p::util::GetMillisecondsSinceEpoch(); dgram->SendDatagramTo(m_Buffer, len, Identity, 0, 0); diff --git a/I2PTunnel.h b/I2PTunnel.h index cc78722c..75eeaaef 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -152,7 +152,7 @@ namespace client uint8_t m_Buffer[I2P_UDP_MAX_MTU]; - UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash ident, uint16_t ourPort, uint16_t theirPort); + UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash ident, uint16_t ourPort, uint16_t theirPort); void HandleReceived(const boost::system::error_code & ecode, std::size_t len); void Receive(); From 3f63732c31bb42a9f7ebf2b26fcd879aded57341 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 22:26:30 -0400 Subject: [PATCH 1789/6300] osx fix --- I2PTunnel.cpp | 2 +- I2PTunnel.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 346baf29..4cae9cb7 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -546,7 +546,7 @@ namespace client } UDPSession::UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash to, uint16_t ourPort, uint16_t theirPort) : - m_Destination(localDestination), + m_Destination(localDestination.get()), IPSocket(ios, localEndpoint), Identity(to), SendEndpoint(endpoint), diff --git a/I2PTunnel.h b/I2PTunnel.h index 75eeaaef..ca5c8cc9 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -140,7 +140,7 @@ namespace client struct UDPSession { - std::shared_ptr m_Destination; + i2p::client::ClientDestination * m_Destination; boost::asio::ip::udp::socket IPSocket; i2p::data::IdentHash Identity; boost::asio::ip::udp::endpoint FromEndpoint; From b977050cafb324ebc2eabbcee3c2ecc78dd92e53 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 22:29:55 -0400 Subject: [PATCH 1790/6300] osx fix --- Destination.cpp | 4 ++-- Destination.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index d48bee92..56681884 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -812,10 +812,10 @@ namespace client return dest; } - std::shared_ptr ClientDestination::CreateDatagramDestination () + i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination () { if (m_DatagramDestination == nullptr) - m_DatagramDestination = std::make_shared (GetSharedFromThis ()); + m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis ()); return m_DatagramDestination; } diff --git a/Destination.h b/Destination.h index 6cd4c19c..3faab9cc 100644 --- a/Destination.h +++ b/Destination.h @@ -164,8 +164,8 @@ namespace client bool IsAcceptingStreams () const; // datagram - std::shared_ptr GetDatagramDestination () const { return m_DatagramDestination; }; - std::shared_ptr CreateDatagramDestination (); + i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; + i2p::datagram::DatagramDestination * CreateDatagramDestination (); // implements LocalDestination const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; @@ -190,7 +190,7 @@ namespace client std::shared_ptr m_StreamingDestination; // default std::map > m_StreamingDestinationsByPorts; - std::shared_ptr m_DatagramDestination; + i2p::datagram::DatagramDestination * m_DatagramDestination; public: From 6bb9de5a962ebfc706300ec58e948718b5462d4e Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 22:34:48 -0400 Subject: [PATCH 1791/6300] osx fix --- AddressBook.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 2c4fa1b5..78e9731f 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -543,7 +543,9 @@ namespace client auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { - auto datagram = dest->CreateDatagramDestination (); + + auto datagram = dest->GetDatagramDestination (); + if(datagram == nullptr) datagram = dest->CreateDatagramDestination(); datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), ADDRESS_RESPONSE_DATAGRAM_PORT); From 5b00cb1e644deecb3e1e751a36f54ca85858e339 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 22:38:41 -0400 Subject: [PATCH 1792/6300] osx fix --- I2PTunnel.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 4cae9cb7..ac64186c 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -614,7 +614,11 @@ namespace client RemotePort(remotePort), m_cancel_resolve(false) { - + auto dgram = m_LocalDest->CreateDatagramDestination(); + dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4, + std::placeholders::_5)); } @@ -638,15 +642,10 @@ namespace client return; } LogPrint(eLogInfo, "UDP Tunnel: resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32()); - auto dgram = m_LocalDest->CreateDatagramDestination().get(); // delete existing session if(m_Session) delete m_Session; boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); m_Session = new UDPSession(m_Service, m_LocalEndpoint, m_LocalDest, ep, *m_RemoteIdent, LocalPort, RemotePort); - dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, - std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4, - std::placeholders::_5)); } void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) From bee34a3222c8ced5c58eb140e35017d9f7315c7c Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 21 Aug 2016 22:54:06 -0400 Subject: [PATCH 1793/6300] fix --- Datagram.cpp | 2 +- Datagram.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index dbe4d978..aed831b7 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -12,7 +12,7 @@ namespace i2p namespace datagram { DatagramDestination::DatagramDestination (std::shared_ptr owner): - m_Owner (owner), m_Receiver (nullptr) + m_Owner (owner.get()), m_Receiver (nullptr) { } diff --git a/Datagram.h b/Datagram.h index c593fad2..51fd3553 100644 --- a/Datagram.h +++ b/Datagram.h @@ -47,7 +47,7 @@ namespace datagram private: - std::shared_ptr m_Owner; + i2p::client::ClientDestination * m_Owner; Receiver m_Receiver; // default std::map m_ReceiversByPorts; From f5e28992750a2d3497e8c761a3c4cf6d4ef1cd00 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 22 Aug 2016 13:04:36 -0400 Subject: [PATCH 1794/6300] post work to io service --- ClientContext.cpp | 10 ++++++---- I2PTunnel.cpp | 19 ++++++++++++------- I2PTunnel.h | 2 +- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 0b9b1f60..6b7e58bc 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -87,9 +87,6 @@ namespace client LogPrint(eLogError, "Clients: Exception in SOCKS Proxy: ", e.what()); } } - - // I2P tunnels - ReadTunnels (); if ( m_ServiceThread == nullptr ) { m_ServiceThread = new std::thread([&] () { @@ -99,7 +96,11 @@ namespace client }); ScheduleCleanupUDP(); } - + + + // I2P tunnels + ReadTunnels (); + // SAM bool sam; i2p::config::GetOption("sam.enabled", sam); @@ -468,6 +469,7 @@ namespace client std::make_pair( localDestination->GetIdentHash(), port), std::unique_ptr(serverTunnel))).second) { + serverTunnel->Start(); LogPrint(eLogInfo, "Clients: I2P Server Forward created for UDP Endpoint ", host, ":", port, " via ",localDestination->GetIdentHash().ToBase32()); } else { LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index ac64186c..ea0f6122 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -569,13 +569,13 @@ namespace client LogPrint(eLogDebug, "UDPSesssion: HandleRecveived"); if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); - auto dgram = m_Destination->GetDatagramDestination(); - if(dgram) { - LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - dgram->SendDatagramTo(m_Buffer, len, Identity, 0, 0); - } else { - LogPrint(eLogWarning, "UDPSession: no datagram destination"); - } + LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + m_Destination->GetService().post([&, len] () { + auto dgram = m_Destination->GetDatagramDestination(); + if (dgram) { + dgram->SendDatagramTo(m_Buffer, len, Identity, 0, 0); + } + }); Receive(); } else { LogPrint(eLogError, "UDPSession: ", ecode.message()); @@ -602,6 +602,10 @@ namespace client LogPrint(eLogInfo, "UDPServer: done"); } + void I2PUDPServerTunnel::Start() { + m_LocalDest->Start(); + } + I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort, boost::asio::io_service & service) : m_Session(nullptr), m_RemoteDest(remoteDest), @@ -624,6 +628,7 @@ namespace client void I2PUDPClientTunnel::Start() { + m_LocalDest->Start(); if (m_ResolveThread == nullptr) m_ResolveThread = new std::thread(std::bind(&I2PUDPClientTunnel::TryResolving, this)); } diff --git a/I2PTunnel.h b/I2PTunnel.h index ca5c8cc9..64e68640 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -167,7 +167,7 @@ namespace client ~I2PUDPServerTunnel(); /** expire stale udp conversations */ void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); - + void Start(); private: void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); UDPSession & ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); From 7f7acd8bde75b1b0e561e9c01ee75ce9c470ee11 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 22 Aug 2016 13:54:00 -0400 Subject: [PATCH 1795/6300] fixes --- ClientContext.cpp | 4 ++-- I2PTunnel.cpp | 53 +++++++++++++++++++++++------------------------ I2PTunnel.h | 17 +++++++-------- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 6b7e58bc..13c8690a 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -412,7 +412,7 @@ namespace client if (!localDestination) { localDestination = m_SharedLocalDestination; } - auto clientTunnel = new I2PUDPClientTunnel(name, dest, end, localDestination, destinationPort, m_Service); + auto clientTunnel = new I2PUDPClientTunnel(name, dest, end, localDestination, destinationPort); if(m_ClientForwards.insert(std::make_pair(end, std::unique_ptr(clientTunnel))).second) { clientTunnel->Start(); } else { @@ -462,7 +462,7 @@ namespace client // udp server tunnel // TODO: ipv6 and hostnames boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); - I2PUDPServerTunnel * serverTunnel = new I2PUDPServerTunnel(name, localDestination, endpoint, port, m_Service); + I2PUDPServerTunnel * serverTunnel = new I2PUDPServerTunnel(name, localDestination, endpoint, port); std::lock_guard lock(m_ForwardsMutex); if(m_ServerForwards.insert( std::make_pair( diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index ea0f6122..7dde2bdf 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -514,47 +514,45 @@ namespace client void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { std::lock_guard lock(m_SessionsMutex); - auto & session = ObtainUDPSession(from, toPort, fromPort); - session.IPSocket.send_to(boost::asio::buffer(buf, len), m_Endpoint); - session.LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + auto session = ObtainUDPSession(from, toPort, fromPort); + session->IPSocket.send_to(boost::asio::buffer(buf, len), m_Endpoint); + session->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); } void I2PUDPServerTunnel::ExpireStale(const uint64_t delta) { std::lock_guard lock(m_SessionsMutex); uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); - std::remove_if(m_Sessions.begin(), m_Sessions.end(), [now, delta](const UDPSession & u) -> bool { - return now - u.LastActivity >= delta; + std::remove_if(m_Sessions.begin(), m_Sessions.end(), [now, delta](const UDPSession * u) -> bool { + return now - u->LastActivity >= delta; }); } - UDPSession & I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) + UDPSession * I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) { auto ih = from.GetIdentHash(); - for ( UDPSession & s : m_Sessions ) { - if ( s.Identity == ih) { + for ( UDPSession * s : m_Sessions ) { + if ( s->Identity == ih) { /** found existing */ return s; } } /** create new */ boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); - m_Sessions.push_back(UDPSession(m_Service, ep, m_LocalDest, m_Endpoint, ih, localPort, remotePort)); - auto & s = m_Sessions.back(); - s.SendEndpoint = s.IPSocket.local_endpoint(); - return s; + m_Sessions.push_back(new UDPSession(ep, m_LocalDest, m_Endpoint, ih, localPort, remotePort)); + return m_Sessions.back(); } - UDPSession::UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash to, uint16_t ourPort, uint16_t theirPort) : - m_Destination(localDestination.get()), - IPSocket(ios, localEndpoint), + UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash to, uint16_t ourPort, uint16_t theirPort) : + m_Destination(localDestination->GetDatagramDestination()), + m_Service(localDestination->GetService()), + IPSocket(localDestination->GetService(), localEndpoint), Identity(to), SendEndpoint(endpoint), LastActivity(i2p::util::GetMillisecondsSinceEpoch()), LocalPort(ourPort), RemotePort(theirPort) { - IPSocket.local_endpoint(); Receive(); } @@ -570,22 +568,24 @@ namespace client if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - m_Destination->GetService().post([&, len] () { - auto dgram = m_Destination->GetDatagramDestination(); - if (dgram) { - dgram->SendDatagramTo(m_Buffer, len, Identity, 0, 0); - } + uint8_t * data = new uint8_t[len]; + memcpy(data, m_Buffer, len); + m_Service.post([&,len, data] () { + m_Destination->SendDatagramTo(data, len, Identity, 0, 0); + delete data; }); + Receive(); } else { LogPrint(eLogError, "UDPSession: ", ecode.message()); } } + - I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint forwardTo, uint16_t port, boost::asio::io_service & service) : + + I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint forwardTo, uint16_t port) : LocalPort(port), - m_Endpoint(forwardTo), - m_Service(service) + m_Endpoint(forwardTo) { m_LocalDest = localDestination; m_LocalDest->Start(); @@ -606,14 +606,13 @@ namespace client m_LocalDest->Start(); } - I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort, boost::asio::io_service & service) : + I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort) : m_Session(nullptr), m_RemoteDest(remoteDest), m_RemoteIdent(nullptr), m_LocalDest(localDestination), m_LocalEndpoint(localEndpoint), m_ResolveThread(nullptr), - m_Service(service), LocalPort(localEndpoint.port()), RemotePort(remotePort), m_cancel_resolve(false) @@ -650,7 +649,7 @@ namespace client // delete existing session if(m_Session) delete m_Session; boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); - m_Session = new UDPSession(m_Service, m_LocalEndpoint, m_LocalDest, ep, *m_RemoteIdent, LocalPort, RemotePort); + m_Session = new UDPSession(m_LocalEndpoint, m_LocalDest, ep, *m_RemoteIdent, LocalPort, RemotePort); } void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) diff --git a/I2PTunnel.h b/I2PTunnel.h index 64e68640..dfd364f0 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -140,7 +140,8 @@ namespace client struct UDPSession { - i2p::client::ClientDestination * m_Destination; + i2p::datagram::DatagramDestination * m_Destination; + boost::asio::io_service & m_Service; boost::asio::ip::udp::socket IPSocket; i2p::data::IdentHash Identity; boost::asio::ip::udp::endpoint FromEndpoint; @@ -151,32 +152,31 @@ namespace client uint16_t RemotePort; uint8_t m_Buffer[I2P_UDP_MAX_MTU]; + uint8_t * m_Forward; - UDPSession(boost::asio::io_service & ios, boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash ident, uint16_t ourPort, uint16_t theirPort); + UDPSession(boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash ident, uint16_t ourPort, uint16_t theirPort); void HandleReceived(const boost::system::error_code & ecode, std::size_t len); void Receive(); - }; /** server side udp tunnel, many i2p inbound to 1 ip outbound */ class I2PUDPServerTunnel { public: - I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint forwardTo, uint16_t port, boost::asio::io_service & service); + I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint forwardTo, uint16_t port); ~I2PUDPServerTunnel(); /** expire stale udp conversations */ void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); void Start(); private: void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - UDPSession & ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); + UDPSession * ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); private: const uint16_t LocalPort; boost::asio::ip::udp::endpoint m_Endpoint; std::mutex m_SessionsMutex; - std::vector m_Sessions; - boost::asio::io_service & m_Service; + std::vector m_Sessions; std::shared_ptr m_LocalDest; uint8_t m_Buffer[I2P_UDP_MAX_MTU]; }; @@ -184,7 +184,7 @@ namespace client class I2PUDPClientTunnel { public: - I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort, boost::asio::io_service & service); + I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort); ~I2PUDPClientTunnel(); void Start(); private: @@ -196,7 +196,6 @@ namespace client const boost::asio::ip::udp::endpoint m_LocalEndpoint; i2p::data::IdentHash * m_RemoteIdent; std::thread * m_ResolveThread; - boost::asio::io_service & m_Service; uint16_t LocalPort; uint16_t RemotePort; bool m_cancel_resolve; From be12739342ad0945166070e59247251a201faf84 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 22 Aug 2016 13:55:44 -0400 Subject: [PATCH 1796/6300] fix --- I2PTunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 7dde2bdf..93aa1cd6 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -572,7 +572,7 @@ namespace client memcpy(data, m_Buffer, len); m_Service.post([&,len, data] () { m_Destination->SendDatagramTo(data, len, Identity, 0, 0); - delete data; + delete [] data; }); Receive(); From 979575c311ea3391043a3e01a4fa11da15eceb4d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 22 Aug 2016 13:59:51 -0400 Subject: [PATCH 1797/6300] fix --- I2PTunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 93aa1cd6..7172b23b 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -659,7 +659,7 @@ namespace client if(m_Session) { // tell session LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", from.GetIdentHash().ToBase32(), " via ", m_Session->SendEndpoint); - m_Session->IPSocket.send_to(boost::asio::buffer(buf, len), m_Session->SendEndpoint); + m_Session->IPSocket.send_to(boost::asio::buffer(buf, len), m_Session->FromEndpoint); } else { LogPrint(eLogWarning, "UDP Client: no session"); } From 1d7d7cf9a02e1c889b912fb12cdd92af237f34af Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 22 Aug 2016 17:19:22 -0400 Subject: [PATCH 1798/6300] more changes --- ClientContext.cpp | 3 --- I2PTunnel.cpp | 12 ++++++------ I2PTunnel.h | 3 +-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 13c8690a..3b814260 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -406,9 +406,6 @@ namespace client // udp client // TODO: ip6 and hostnames boost::asio::ip::udp::endpoint end(boost::asio::ip::address::from_string(address), port); - if(destinationPort == 0) { - destinationPort = port; - } if (!localDestination) { localDestination = m_SharedLocalDestination; } diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 7172b23b..6cbb3668 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -534,6 +534,7 @@ namespace client for ( UDPSession * s : m_Sessions ) { if ( s->Identity == ih) { /** found existing */ + LogPrint(eLogDebug, "UDPServer: found session ", s->IPSocket.local_endpoint()); return s; } } @@ -558,20 +559,19 @@ namespace client void UDPSession::Receive() { - LogPrint(eLogDebug, "UDPSesssion: Recveive"); + LogPrint(eLogDebug, "UDPSession: Receive"); IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); } void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) { - LogPrint(eLogDebug, "UDPSesssion: HandleRecveived"); if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); LastActivity = i2p::util::GetMillisecondsSinceEpoch(); uint8_t * data = new uint8_t[len]; memcpy(data, m_Buffer, len); m_Service.post([&,len, data] () { - m_Destination->SendDatagramTo(data, len, Identity, 0, 0); + m_Destination->SendDatagramTo(data, len, Identity, LocalPort, RemotePort); delete [] data; }); @@ -590,14 +590,14 @@ namespace client m_LocalDest = localDestination; m_LocalDest->Start(); auto dgram = m_LocalDest->CreateDatagramDestination(); - dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), 0); + dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), LocalPort); } I2PUDPServerTunnel::~I2PUDPServerTunnel() { auto dgram = m_LocalDest->GetDatagramDestination(); if (dgram) { - dgram->ResetReceiver(0); + dgram->ResetReceiver(LocalPort); } LogPrint(eLogInfo, "UDPServer: done"); } @@ -658,7 +658,7 @@ namespace client // address match if(m_Session) { // tell session - LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", from.GetIdentHash().ToBase32(), " via ", m_Session->SendEndpoint); + LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", from.GetIdentHash().ToBase32()); m_Session->IPSocket.send_to(boost::asio::buffer(buf, len), m_Session->FromEndpoint); } else { LogPrint(eLogWarning, "UDP Client: no session"); diff --git a/I2PTunnel.h b/I2PTunnel.h index dfd364f0..63341679 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -136,7 +136,7 @@ namespace client const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; /** max size for i2p udp */ - const size_t I2P_UDP_MAX_MTU = 4000; + const size_t I2P_UDP_MAX_MTU = i2p::datagram::MAX_DATAGRAM_SIZE; struct UDPSession { @@ -152,7 +152,6 @@ namespace client uint16_t RemotePort; uint8_t m_Buffer[I2P_UDP_MAX_MTU]; - uint8_t * m_Forward; UDPSession(boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash ident, uint16_t ourPort, uint16_t theirPort); From e8195b78bad872411e73bcd598e1229eeaaa3c7e Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 22 Aug 2016 17:26:43 -0400 Subject: [PATCH 1799/6300] fix --- I2PTunnel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 6cbb3668..146c6d7f 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -571,7 +571,7 @@ namespace client uint8_t * data = new uint8_t[len]; memcpy(data, m_Buffer, len); m_Service.post([&,len, data] () { - m_Destination->SendDatagramTo(data, len, Identity, LocalPort, RemotePort); + m_Destination->SendDatagramTo(data, len, Identity, 0, 0); delete [] data; }); @@ -597,7 +597,7 @@ namespace client { auto dgram = m_LocalDest->GetDatagramDestination(); if (dgram) { - dgram->ResetReceiver(LocalPort); + dgram->ResetReceiver(0); } LogPrint(eLogInfo, "UDPServer: done"); } From 42b15e8bbe3381a363e98433e01c542cf3ddf148 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 22 Aug 2016 17:31:23 -0400 Subject: [PATCH 1800/6300] fix --- I2PTunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 146c6d7f..9c6ac89d 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -590,7 +590,7 @@ namespace client m_LocalDest = localDestination; m_LocalDest->Start(); auto dgram = m_LocalDest->CreateDatagramDestination(); - dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), LocalPort); + dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), 0); } I2PUDPServerTunnel::~I2PUDPServerTunnel() From 065d01bcf6398049f91ca2d3e00a06e70bf47d55 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 22 Aug 2016 18:29:12 -0400 Subject: [PATCH 1801/6300] logging update --- I2PTunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 9c6ac89d..69360c2c 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -534,7 +534,7 @@ namespace client for ( UDPSession * s : m_Sessions ) { if ( s->Identity == ih) { /** found existing */ - LogPrint(eLogDebug, "UDPServer: found session ", s->IPSocket.local_endpoint()); + LogPrint(eLogDebug, "UDPServer: found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32()); return s; } } From b02677ee2156797d5e23943e04f78b8e4b1d1125 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 24 Aug 2016 11:21:49 -0400 Subject: [PATCH 1802/6300] common termination timer for all SSU sessions --- NTCPSession.cpp | 1 - NTCPSession.h | 5 +--- SSU.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++++-- SSU.h | 10 +++++++- SSUSession.cpp | 44 ++++++++++------------------------ SSUSession.h | 7 ++---- TransportSession.h | 7 +++++- 7 files changed, 89 insertions(+), 45 deletions(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index a1b26aca..41252f97 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -22,7 +22,6 @@ namespace transport TransportSession (in_RemoteRouter, NTCP_TERMINATION_TIMEOUT), m_Server (server), m_Socket (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false), - m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()), m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_IsSending (false) { m_Establisher = new Establisher; diff --git a/NTCPSession.h b/NTCPSession.h index adf33553..a3d95c62 100644 --- a/NTCPSession.h +++ b/NTCPSession.h @@ -54,9 +54,7 @@ namespace transport void Done (); boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; - bool IsEstablished () const { return m_IsEstablished; }; - bool IsTerminationTimeoutExpired (uint64_t ts) const - { return ts >= m_LastActivityTimestamp + GetTerminationTimeout (); }; + bool IsEstablished () const { return m_IsEstablished; }; void ClientLogin (); void ServerLogin (); @@ -103,7 +101,6 @@ namespace transport NTCPServer& m_Server; boost::asio::ip::tcp::socket m_Socket; bool m_IsEstablished, m_IsTerminated; - uint64_t m_LastActivityTimestamp; i2p::crypto::CBCDecryption m_Decryption; i2p::crypto::CBCEncryption m_Encryption; diff --git a/SSU.cpp b/SSU.cpp index 0c3662d3..3d3fefdd 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -17,7 +17,8 @@ namespace transport m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService), m_EndpointV6 (addr, port), m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversService), - m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service) + m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service), + m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_ServiceV6) { m_SocketV6.open (boost::asio::ip::udp::v6()); m_SocketV6.set_option (boost::asio::ip::v6_only (true)); @@ -32,7 +33,8 @@ namespace transport m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService), m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversService), - m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service) + m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service), + m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_ServiceV6) { m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (65535)); @@ -59,11 +61,13 @@ namespace transport { m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); + ScheduleTermination (); } if (context.SupportsV6 ()) { m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this)); m_ReceiversService.post (std::bind (&SSUServer::ReceiveV6, this)); + ScheduleTerminationV6 (); } SchedulePeerTestsCleanupTimer (); ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers @@ -640,6 +644,58 @@ namespace transport SchedulePeerTestsCleanupTimer (); } } + + void SSUServer::ScheduleTermination () + { + m_TerminationTimer.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT)); + m_TerminationTimer.async_wait (std::bind (&SSUServer::HandleTerminationTimer, + this, std::placeholders::_1)); + } + + void SSUServer::HandleTerminationTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto& it: m_Sessions) + if (it.second->IsTerminationTimeoutExpired (ts)) + { + auto session = it.second; + m_Service.post ([session] + { + LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); + session->Failed (); + }); + } + ScheduleTermination (); + } + } + + void SSUServer::ScheduleTerminationV6 () + { + m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT)); + m_TerminationTimerV6.async_wait (std::bind (&SSUServer::HandleTerminationTimerV6, + this, std::placeholders::_1)); + } + + void SSUServer::HandleTerminationTimerV6 (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto& it: m_SessionsV6) + if (it.second->IsTerminationTimeoutExpired (ts)) + { + auto session = it.second; + m_ServiceV6.post ([session] + { + LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); + session->Failed (); + }); + } + ScheduleTerminationV6 (); + } + } } } diff --git a/SSU.h b/SSU.h index d779c025..182fa0eb 100644 --- a/SSU.h +++ b/SSU.h @@ -23,6 +23,7 @@ namespace transport const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour + const int SSU_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds const size_t SSU_MAX_NUM_INTRODUCERS = 3; struct SSUPacket @@ -90,6 +91,12 @@ namespace transport void SchedulePeerTestsCleanupTimer (); void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode); + // timer + void ScheduleTermination (); + void HandleTerminationTimer (const boost::system::error_code& ecode); + void ScheduleTerminationV6 (); + void HandleTerminationTimerV6 (const boost::system::error_code& ecode); + private: struct PeerTest @@ -106,7 +113,8 @@ namespace transport boost::asio::io_service::work m_Work, m_WorkV6, m_ReceiversWork; boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6; boost::asio::ip::udp::socket m_Socket, m_SocketV6; - boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer; + boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer, + m_TerminationTimer, m_TerminationTimerV6; std::list m_Introducers; // introducers we are connected to std::map > m_Sessions, m_SessionsV6; std::map m_Relays; // we are introducer diff --git a/SSUSession.cpp b/SSUSession.cpp index bf4058a5..9245f652 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -14,7 +14,7 @@ namespace transport SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, std::shared_ptr router, bool peerTest ): TransportSession (router, SSU_TERMINATION_TIMEOUT), - m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_Timer (GetService ()), + m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_ConnectTimer (GetService ()), m_IsPeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), m_RelayTag (0),m_Data (*this), m_IsDataReceived (false) { @@ -97,7 +97,7 @@ namespace transport { if (!len) return; // ignore zero-length packets if (m_State == eSessionStateEstablished) - ScheduleTermination (); + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); if (m_IsSessionKey && Validate (buf, len, m_MacKey)) // try session key first DecryptSessionKey (buf, len); @@ -229,7 +229,7 @@ namespace transport } LogPrint (eLogDebug, "SSU message: session created"); - m_Timer.cancel (); // connect timer + m_ConnectTimer.cancel (); // connect timer SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, signed on time auto headerSize = GetSSUHeaderSize (buf); if (headerSize >= len) @@ -804,9 +804,9 @@ namespace transport void SSUSession::ScheduleConnectTimer () { - m_Timer.cancel (); - m_Timer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); - m_Timer.async_wait (std::bind (&SSUSession::HandleConnectTimer, + m_ConnectTimer.cancel (); + m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); + m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, shared_from_this (), std::placeholders::_1)); } @@ -826,8 +826,8 @@ namespace transport if (m_State == eSessionStateUnknown) { // set connect timer - m_Timer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); - m_Timer.async_wait (std::bind (&SSUSession::HandleConnectTimer, + m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); + m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, shared_from_this (), std::placeholders::_1)); } uint32_t nonce; @@ -840,8 +840,8 @@ namespace transport { m_State = eSessionStateIntroduced; // set connect timer - m_Timer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); - m_Timer.async_wait (std::bind (&SSUSession::HandleConnectTimer, + m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); + m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, shared_from_this (), std::placeholders::_1)); } @@ -851,7 +851,7 @@ namespace transport SendSesionDestroyed (); transports.PeerDisconnected (shared_from_this ()); m_Data.Stop (); - m_Timer.cancel (); + m_ConnectTimer.cancel (); } void SSUSession::Done () @@ -868,7 +868,7 @@ namespace transport transports.PeerConnected (shared_from_this ()); if (m_IsPeerTest) SendPeerTest (); - ScheduleTermination (); + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); } void SSUSession::Failed () @@ -880,24 +880,6 @@ namespace transport } } - void SSUSession::ScheduleTermination () - { - m_Timer.cancel (); - m_Timer.expires_from_now (boost::posix_time::seconds(GetTerminationTimeout ())); - m_Timer.async_wait (std::bind (&SSUSession::HandleTerminationTimer, - shared_from_this (), std::placeholders::_1)); - } - - void SSUSession::HandleTerminationTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogWarning, "SSU: no activity with ", m_RemoteEndpoint, " for ", GetTerminationTimeout (), " seconds"); - Failed (); - } - } - - void SSUSession::SendI2NPMessages (const std::vector >& msgs) { GetService ().post (std::bind (&SSUSession::PostI2NPMessages, shared_from_this (), msgs)); @@ -1126,7 +1108,7 @@ namespace transport FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); Send (buf, 48); LogPrint (eLogDebug, "SSU: keep-alive sent"); - ScheduleTermination (); + m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); } } diff --git a/SSUSession.h b/SSUSession.h index c2f24ce3..69669187 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -77,6 +77,7 @@ namespace transport void WaitForIntroduction (); void Close (); void Done (); + void Failed (); boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; void SendI2NPMessages (const std::vector >& msgs); @@ -114,7 +115,6 @@ namespace transport void ProcessRelayResponse (const uint8_t * buf, size_t len); void ProcessRelayIntro (const uint8_t * buf, size_t len); void Established (); - void Failed (); void ScheduleConnectTimer (); void HandleConnectTimer (const boost::system::error_code& ecode); void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); @@ -131,15 +131,12 @@ namespace transport void DecryptSessionKey (uint8_t * buf, size_t len); bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey); - void ScheduleTermination (); - void HandleTerminationTimer (const boost::system::error_code& ecode); - private: friend class SSUData; // TODO: change in later SSUServer& m_Server; boost::asio::ip::udp::endpoint m_RemoteEndpoint; - boost::asio::deadline_timer m_Timer; + boost::asio::deadline_timer m_ConnectTimer; bool m_IsPeerTest; SessionState m_State; bool m_IsSessionKey; diff --git a/TransportSession.h b/TransportSession.h index 1b057bc7..9c97d02e 100644 --- a/TransportSession.h +++ b/TransportSession.h @@ -9,6 +9,7 @@ #include "Crypto.h" #include "RouterInfo.h" #include "I2NPProtocol.h" +#include "Timestamp.h" namespace i2p { @@ -54,7 +55,8 @@ namespace transport public: TransportSession (std::shared_ptr router, int terminationTimeout): - m_DHKeysPair (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout) + m_DHKeysPair (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout), + m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()) { if (router) m_RemoteIdentity = router->GetRouterIdentity (); @@ -72,6 +74,8 @@ namespace transport int GetTerminationTimeout () const { return m_TerminationTimeout; }; void SetTerminationTimeout (int terminationTimeout) { m_TerminationTimeout = terminationTimeout; }; + bool IsTerminationTimeoutExpired (uint64_t ts) const + { return ts >= m_LastActivityTimestamp + GetTerminationTimeout (); }; virtual void SendI2NPMessages (const std::vector >& msgs) = 0; @@ -82,6 +86,7 @@ namespace transport size_t m_NumSentBytes, m_NumReceivedBytes; bool m_IsOutgoing; int m_TerminationTimeout; + uint64_t m_LastActivityTimestamp; }; } } From 32669cb07fe112dea5d677f7e55729ee3918640d Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 24 Aug 2016 12:34:18 -0400 Subject: [PATCH 1803/6300] stop termination timer on shutdown --- SSU.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SSU.cpp b/SSU.cpp index 3d3fefdd..72091e00 100644 --- a/SSU.cpp +++ b/SSU.cpp @@ -77,6 +77,8 @@ namespace transport { DeleteAllSessions (); m_IsRunning = false; + m_TerminationTimer.cancel (); + m_TerminationTimerV6.cancel (); m_Service.stop (); m_Socket.close (); m_ServiceV6.stop (); From c4171a01bdd9e173bdc644665c2bc12eab31948b Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 26 Aug 2016 09:48:19 -0400 Subject: [PATCH 1804/6300] fix #622. extract correct CN --- Reseed.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Reseed.cpp b/Reseed.cpp index 48d7a53f..21635791 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -345,11 +345,21 @@ namespace data // extract issuer name char name[100]; X509_NAME_oneline (X509_get_issuer_name(cert), name, 100); + char * cn = strstr (name, "CN="); + if (cn) + { + cn += 3; + char * terminator = strchr (cn, '/'); + if (terminator) terminator[0] = 0; + } // extract RSA key (we need n only, e = 65537) RSA * key = X509_get_pubkey (cert)->pkey.rsa; PublicKey value; i2p::crypto::bn2buf (key->n, value, 512); - m_SigningKeys[name] = value; + if (cn) + m_SigningKeys[cn] = value; + else + LogPrint (eLogError, "Reseed: Can't find CN field in ", filename); } SSL_free (ssl); } From fc5fc5bbee016b41ab447b2a9960b0d96f5733f0 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 26 Aug 2016 10:06:28 -0400 Subject: [PATCH 1805/6300] don't throw exception if connection failed --- NTCPSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 41252f97..10af063d 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -952,7 +952,7 @@ namespace transport { if (ecode) { - LogPrint (eLogError, "NTCP: Can't connect to ", conn->GetSocket ().remote_endpoint (), ": ", ecode.message ()); + LogPrint (eLogError, "NTCP: Connect error ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); conn->Terminate (); From 205b61e4cf334307ff38fcc7dafffc7ae616ba9c Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 26 Aug 2016 14:00:00 +0000 Subject: [PATCH 1806/6300] * HTTPServer : fix tag --- HTTPServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index fdc5b38f..52ef09e3 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -422,7 +422,7 @@ namespace http { s << " Accept transit tunnels
    \r\n"; #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) if (Daemon.gracefullShutdownInterval) - s << " Cancel gracefull shutdown
    "; + s << " Cancel gracefull shutdown
    "; else s << " Start gracefull shutdown
    \r\n"; #endif From 26440d94f1ea4c75cf9a450eeb9c7c2a70f8ec4e Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 26 Aug 2016 14:00:00 +0000 Subject: [PATCH 1807/6300] * HTTPServer : keep response data for async_write() --- HTTPServer.cpp | 4 ++-- HTTPServer.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 52ef09e3..309e3f6b 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -762,8 +762,8 @@ namespace http { reply.add_header("Content-Type", "text/html"); reply.body = content; - std::string res = reply.to_string(); - boost::asio::async_write (*m_Socket, boost::asio::buffer(res), + m_SendBuffer = reply.to_string(); + boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); } diff --git a/HTTPServer.h b/HTTPServer.h index bf7f5c65..5246af8b 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -31,6 +31,7 @@ namespace http { boost::asio::deadline_timer m_Timer; char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; size_t m_BufferLen; + std::string m_SendBuffer; bool needAuth; std::string user; std::string pass; From abaf36a2de45c8b3dc2cf981455bd94492681896 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 27 Aug 2016 09:29:18 -0400 Subject: [PATCH 1808/6300] try unbreaking static build --- Makefile.linux | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile.linux b/Makefile.linux index b0549f43..ddff76a7 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -32,9 +32,14 @@ ifeq ($(USE_STATIC),yes) # Using 'getaddrinfo' in statically linked applications requires at runtime # the shared libraries from the glibc version used for linking LIBDIR := /usr/lib - LDLIBS = -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options - LDLIBS += -lssl -lcrypto -lz -ldl -lpthread -lrt - LDLIBS += -static-libstdc++ -static-libgcc -static + LDLIBS = $(LIBDIR)/libboost_system.a + LDLIBS += $(LIBDIR)/libboost_date_time.a + LDLIBS += $(LIBDIR)/libboost_filesystem.a + LDLIBS += $(LIBDIR)/libboost_program_options.a + LDLIBS += $(LIBDIR)/libssl.a + LDLIBS += $(LIBDIR)/libcrypto.a + LDLIBS += $(LIBDIR)/libz.a + LDLIBS += -lpthread -static-libstdc++ -static-libgcc -lrt USE_AESNI := no else LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread From 0b21fce94efd7d4d7df2ded923aa6d95e49f68d0 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 27 Aug 2016 13:17:34 -0400 Subject: [PATCH 1809/6300] try adding garlic and session tags to datagram destination --- Datagram.cpp | 267 ++++++++++++++++++++++++++++++++++++++++-------- Datagram.h | 65 ++++++++++-- Destination.cpp | 2 + Garlic.h | 2 +- LeaseSet.cpp | 11 +- LeaseSet.h | 3 + TunnelPool.h | 1 - 7 files changed, 300 insertions(+), 51 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index aed831b7..72dfe67c 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -12,12 +12,17 @@ namespace i2p namespace datagram { DatagramDestination::DatagramDestination (std::shared_ptr owner): - m_Owner (owner.get()), m_Receiver (nullptr) + m_Owner (owner.get()), + m_CleanupTimer(owner->GetService()), + m_Receiver (nullptr) { + ScheduleCleanup(); } DatagramDestination::~DatagramDestination () { + m_CleanupTimer.cancel(); + m_Sessions.clear(); } void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort) @@ -41,45 +46,11 @@ namespace datagram else owner->Sign (buf1, len, signature); - auto msg = CreateDataMessage (buf, len + headerLen, fromPort, toPort); - auto remote = owner->FindLeaseSet (ident); - if (remote) - owner->GetService ().post (std::bind (&DatagramDestination::SendMsg, this, msg, remote)); - else - owner->RequestDestination (ident, std::bind (&DatagramDestination::HandleLeaseSetRequestComplete, this, std::placeholders::_1, msg)); - } + auto msg = CreateDataMessage (buf, len + headerLen, fromPort, toPort); + auto session = ObtainSession(ident); + session->SendMsg(msg); + } - void DatagramDestination::HandleLeaseSetRequestComplete (std::shared_ptr remote, std::shared_ptr msg) - { - if (remote) - SendMsg (msg, remote); - } - - void DatagramDestination::SendMsg (std::shared_ptr msg, std::shared_ptr remote) - { - auto outboundTunnel = m_Owner->GetTunnelPool ()->GetNextOutboundTunnel (); - auto leases = remote->GetNonExpiredLeases (); - if (!leases.empty () && outboundTunnel) - { - std::vector msgs; - uint32_t i = rand () % leases.size (); - auto garlic = m_Owner->WrapMessage (remote, msg, true); - msgs.push_back (i2p::tunnel::TunnelMessageBlock - { - i2p::tunnel::eDeliveryTypeTunnel, - leases[i]->tunnelGateway, leases[i]->tunnelID, - garlic - }); - outboundTunnel->SendTunnelDataMsg (msgs); - } - else - { - if (outboundTunnel) - LogPrint (eLogWarning, "Failed to send datagram. All leases expired"); - else - LogPrint (eLogWarning, "Failed to send datagram. No outbound tunnels"); - } - } void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { @@ -139,7 +110,223 @@ namespace datagram else msg = nullptr; return msg; - } + } + + void DatagramDestination::ScheduleCleanup() + { + m_CleanupTimer.expires_from_now(boost::posix_time::seconds(DATAGRAM_SESSION_CLEANUP_INTERVAL)); + m_CleanupTimer.async_wait(std::bind(&DatagramDestination::HandleCleanUp, this, std::placeholders::_1)); + } + + void DatagramDestination::HandleCleanUp(const boost::system::error_code & ecode) + { + if(ecode) + return; + std::lock_guard lock(m_SessionsMutex); + auto now = i2p::util::GetMillisecondsSinceEpoch(); + LogPrint(eLogDebug, "DatagramDestination: clean up sessions"); + std::vector expiredSessions; + // for each session ... + for (auto & e : m_Sessions) { + // check if expired + if(now - e.second->LastActivity() >= DATAGRAM_SESSION_MAX_IDLE) + expiredSessions.push_back(e.first); // we are expired + } + // for each expired session ... + for (auto & ident : expiredSessions) { + // remove the expired session + LogPrint(eLogInfo, "DatagramDestination: expiring idle session with ", ident.ToBase32()); + m_Sessions.erase(ident); + } + } + + std::shared_ptr DatagramDestination::ObtainSession(const i2p::data::IdentHash & ident) + { + std::shared_ptr session = nullptr; + std::lock_guard lock(m_SessionsMutex); + auto itr = m_Sessions.find(ident); + if (itr == m_Sessions.end()) { + // not found, create new session + session = std::make_shared(m_Owner, ident); + m_Sessions[ident] = session; + } else { + session = itr->second; + } + return session; + } + + DatagramSession::DatagramSession(i2p::client::ClientDestination * localDestination, + const i2p::data::IdentHash & remoteIdent) : + m_LocalDestination(localDestination), + m_RemoteIdentity(remoteIdent), + m_LastUse(i2p::util::GetMillisecondsSinceEpoch()) + { + } + + void DatagramSession::SendMsg(std::shared_ptr msg) + { + // we used this session + m_LastUse = i2p::util::GetMillisecondsSinceEpoch(); + // schedule send + m_LocalDestination->GetService().post(std::bind(&DatagramSession::HandleSend, this, msg)); + } + + void DatagramSession::HandleSend(std::shared_ptr msg) + { + // do we have a routing session? + if(m_RoutingSession) + { + // do we have a routing path ? + auto routingPath = m_RoutingSession->GetSharedRoutingPath(); + if(!routingPath) + { + // no routing path, try getting one + routingPath = GetNextRoutingPath(); + if(routingPath) // remember the routing path if we got one + m_RoutingSession->SetSharedRoutingPath(routingPath); + } + // make sure we have a routing path + if (routingPath) + { + auto outboundTunnel = routingPath->outboundTunnel; + if (outboundTunnel) + { + if(outboundTunnel->IsEstablished()) + { + // we have a routing path and routing session and the outbound tunnel we are using is good + // wrap message with routing session and send down routing path's outbound tunnel wrapped for the IBGW + auto m = m_RoutingSession->WrapSingleMessage(msg); + routingPath->outboundTunnel->SendTunnelDataMsg({i2p::tunnel::TunnelMessageBlock{ + i2p::tunnel::eDeliveryTypeTunnel, + routingPath->remoteLease->tunnelGateway, routingPath->remoteLease->tunnelID, + m + }}); + return; + } + } + } + } + // we couldn't send so let's try resetting the routing path and updating lease set + ResetRoutingPath(); + UpdateLeaseSet(msg); + + } + + std::shared_ptr DatagramSession::GetNextRoutingPath() + { + std::shared_ptr outboundTunnel = nullptr; + std::shared_ptr routingPath = nullptr; + // get existing routing path if we have one + if(m_RoutingSession) + routingPath = m_RoutingSession->GetSharedRoutingPath(); + // do we have an existing outbound tunnel and routing path? + if(routingPath && routingPath->outboundTunnel) + { + // is the outbound tunnel we are using good? + if (routingPath->outboundTunnel->IsEstablished()) + { + // ya so let's stick with it + outboundTunnel = routingPath->outboundTunnel; + } + else + outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(routingPath->outboundTunnel); // no so we'll switch outbound tunnels + // don't reuse the old path as we are making a new one + routingPath = nullptr; + } + // do we have an outbound tunnel that works already ? + if(!outboundTunnel) + outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(); // no, let's get a new outbound tunnel as we probably just started + + if(outboundTunnel) + { + // get next available lease + auto lease = GetNextLease(); + if(lease) + { + // we have a valid lease to use and an outbound tunnel + // create new routing path + uint32_t now = i2p::util::GetSecondsSinceEpoch(); + routingPath = std::make_shared(i2p::garlic::GarlicRoutingPath{ + outboundTunnel, + lease, + 0, + now, + 0 + }); + } + } + return routingPath; + } + + void DatagramSession::ResetRoutingPath() + { + if(m_RoutingSession) + { + auto routingPath = m_RoutingSession->GetSharedRoutingPath(); + if(routingPath && routingPath->remoteLease) // we have a remote lease already specified and a routing path + { + // get outbound tunnel on this path + auto outboundTunnel = routingPath->outboundTunnel; + // is this outbound tunnel there and established + if (outboundTunnel && outboundTunnel->IsEstablished()) + m_InvalidIBGW.push_back(routingPath->remoteLease->tunnelGateway); // yes, let's mark remote lease as dead because the outbound tunnel seems fine + } + // reset the routing path + m_RoutingSession->SetSharedRoutingPath(nullptr); + } + } + + std::shared_ptr DatagramSession::GetNextLease() + { + std::shared_ptr next = nullptr; + if(m_RemoteLeaseSet) + { + std::vector exclude; + for(const auto & ident : m_InvalidIBGW) + exclude.push_back(ident); + // find get all leases that are not in our ban list + auto leases = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding( [&exclude] (const i2p::data::Lease & l) -> bool { + if(exclude.size()) + { + auto end = std::end(exclude); + return std::find_if(exclude.begin(), end, [l] ( const i2p::data::IdentHash & ident) -> bool { + return ident == l.tunnelGateway; + }) != end; + } + else + return false; + }); + if(leases.size()) + { + // pick random valid next lease + uint32_t idx = rand() % leases.size(); + next = leases[idx]; + } + } + return next; + } + + void DatagramSession::UpdateLeaseSet(std::shared_ptr msg) + { + LogPrint(eLogInfo, "DatagramSession: updating lease set"); + m_LocalDestination->RequestDestination(m_RemoteIdentity, std::bind(&DatagramSession::HandleGotLeaseSet, this, std::placeholders::_1, msg)); + } + + void DatagramSession::HandleGotLeaseSet(std::shared_ptr remoteIdent, std::shared_ptr msg) + { + if(remoteIdent) { + // update routing session + if(m_RoutingSession) + m_RoutingSession = nullptr; + m_RoutingSession = m_LocalDestination->GetRoutingSession(remoteIdent, true); + // clear invalid IBGW as we have a new lease set + m_InvalidIBGW.clear(); + m_RemoteLeaseSet = remoteIdent; + // send the message that was queued if it was provided + if(msg) + HandleSend(msg); + } + } } } diff --git a/Datagram.h b/Datagram.h index 51fd3553..dd3ba3ed 100644 --- a/Datagram.h +++ b/Datagram.h @@ -9,6 +9,7 @@ #include "Identity.h" #include "LeaseSet.h" #include "I2NPProtocol.h" +#include "Garlic.h" namespace i2p { @@ -18,7 +19,52 @@ namespace client } namespace datagram { - const size_t MAX_DATAGRAM_SIZE = 32768; + + // seconds interval for cleanup timer + const int DATAGRAM_SESSION_CLEANUP_INTERVAL = 30; + // milliseconds for max session idle time (10 minutes) + const uint64_t DATAGRAM_SESSION_MAX_IDLE = 3600 * 1000; + + + class DatagramSession + { + public: + DatagramSession(i2p::client::ClientDestination * localDestination, + const i2p::data::IdentHash & remoteIdent); + + /** send an i2np message to remote endpoint for this session */ + void SendMsg(std::shared_ptr msg); + /** get the last time in milliseconds for when we used this datagram session */ + uint64_t LastActivity() const { return m_LastUse; } + private: + + /** get next usable routing path, try reusing outbound tunnels */ + std::shared_ptr GetNextRoutingPath(); + /** + * mark current routing path as invalid and clear it + * if the outbound tunnel we were using was okay don't use the IBGW in the routing path's lease next time + */ + void ResetRoutingPath(); + + /** get next usable lease, does not fetch or update if expired or have no lease set */ + std::shared_ptr GetNextLease(); + + void HandleSend(std::shared_ptr msg); + void HandleGotLeaseSet(std::shared_ptr remoteIdent, + std::shared_ptr msg); + void UpdateLeaseSet(std::shared_ptr msg=nullptr); + + private: + i2p::client::ClientDestination * m_LocalDestination; + i2p::data::IdentHash m_RemoteIdentity; + std::shared_ptr m_RoutingSession; + // Ident hash of IBGW that are invalid + std::vector m_InvalidIBGW; + std::shared_ptr m_RemoteLeaseSet; + uint64_t m_LastUse; + }; + + const size_t MAX_DATAGRAM_SIZE = 32768; class DatagramDestination { typedef std::function Receiver; @@ -36,19 +82,26 @@ namespace datagram void SetReceiver (const Receiver& receiver, uint16_t port) { m_ReceiversByPorts[port] = receiver; }; void ResetReceiver (uint16_t port) { m_ReceiversByPorts.erase (port); }; - + private: - - void HandleLeaseSetRequestComplete (std::shared_ptr leaseSet, std::shared_ptr msg); + // clean up after next tick + void ScheduleCleanup(); + + // clean up stale sessions and expire tags + void HandleCleanUp(const boost::system::error_code & ecode); + + std::shared_ptr ObtainSession(const i2p::data::IdentHash & ident); std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); - void SendMsg (std::shared_ptr msg, std::shared_ptr remote); + void HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); private: - i2p::client::ClientDestination * m_Owner; + boost::asio::deadline_timer m_CleanupTimer; Receiver m_Receiver; // default + std::mutex m_SessionsMutex; + std::map > m_Sessions; std::map m_ReceiversByPorts; i2p::data::GzipInflator m_Inflator; diff --git a/Destination.cpp b/Destination.cpp index 56681884..349c5785 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -701,6 +701,8 @@ namespace client m_StreamingDestination = nullptr; for (auto& it: m_StreamingDestinationsByPorts) it.second->Stop (); + if(m_DatagramDestination) + delete m_DatagramDestination; m_DatagramDestination = nullptr; return true; } diff --git a/Garlic.h b/Garlic.h index 6a92b94a..b37c20e8 100644 --- a/Garlic.h +++ b/Garlic.h @@ -107,7 +107,7 @@ namespace garlic std::shared_ptr GetSharedRoutingPath (); void SetSharedRoutingPath (std::shared_ptr path); - + private: size_t CreateAESBlock (uint8_t * buf, std::shared_ptr msg); diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 72f236a5..ab0407cc 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -169,8 +169,13 @@ namespace data if (now >= m_ExpirationTime) return true; return m_ExpirationTime - now <= dlt; } - - const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const + + const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const + { + return GetNonExpiredLeasesExcluding( [] (const Lease & l) -> bool { return false; }, withThreshold); + } + + const std::vector > LeaseSet::GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold) const { auto ts = i2p::util::GetMillisecondsSinceEpoch (); std::vector > leases; @@ -181,7 +186,7 @@ namespace data endDate += LEASE_ENDDATE_THRESHOLD; else endDate -= LEASE_ENDDATE_THRESHOLD; - if (ts < endDate) + if (ts < endDate && !exclude(*it)) leases.push_back (it); } return leases; diff --git a/LeaseSet.h b/LeaseSet.h index d9e74bab..9b6b7844 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -38,6 +38,8 @@ namespace data }; }; + typedef std::function LeaseInspectFunc; + const size_t MAX_LS_BUFFER_SIZE = 3072; const size_t LEASE_SIZE = 44; // 32 + 4 + 8 const uint8_t MAX_NUM_LEASES = 16; @@ -56,6 +58,7 @@ namespace data size_t GetBufferLen () const { return m_BufferLen; }; bool IsValid () const { return m_IsValid; }; const std::vector > GetNonExpiredLeases (bool withThreshold = true) const; + const std::vector > GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold = true) const; bool HasExpiredLeases () const; bool IsExpired () const; bool IsEmpty () const { return m_Leases.empty (); }; diff --git a/TunnelPool.h b/TunnelPool.h index 0cd2057d..b32a554f 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -45,7 +45,6 @@ namespace tunnel std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr) const; std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr) const; std::shared_ptr GetNewOutboundTunnel (std::shared_ptr old) const; - void TestTunnels (); void ProcessGarlicMessage (std::shared_ptr msg); void ProcessDeliveryStatus (std::shared_ptr msg); From 35b68db847e5e4eaada0ae0d2880ec19b38503e9 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 27 Aug 2016 15:45:56 -0400 Subject: [PATCH 1810/6300] schedule cleanup again and add logging --- Datagram.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Datagram.cpp b/Datagram.cpp index 72dfe67c..18a147b8 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -138,6 +138,7 @@ namespace datagram LogPrint(eLogInfo, "DatagramDestination: expiring idle session with ", ident.ToBase32()); m_Sessions.erase(ident); } + ScheduleCleanup(); } std::shared_ptr DatagramDestination::ObtainSession(const i2p::data::IdentHash & ident) @@ -180,6 +181,7 @@ namespace datagram auto routingPath = m_RoutingSession->GetSharedRoutingPath(); if(!routingPath) { + LogPrint(eLogDebug, "DatagramSession: try getting new routing path"); // no routing path, try getting one routingPath = GetNextRoutingPath(); if(routingPath) // remember the routing path if we got one From 7d03a41e3e8a12913bb281cadc3fc67a399052cc Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 27 Aug 2016 16:09:02 -0400 Subject: [PATCH 1811/6300] try manual expiration of tags --- Datagram.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Datagram.cpp b/Datagram.cpp index 18a147b8..9a5724aa 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -138,6 +138,8 @@ namespace datagram LogPrint(eLogInfo, "DatagramDestination: expiring idle session with ", ident.ToBase32()); m_Sessions.erase(ident); } + m_LocalDestination->CleanupExpiredTags(); + m_LocalDestination->CleanupUnconfirmedTags(); ScheduleCleanup(); } From 2ce64e1bf5b45a829ade0d4ab9f759ee023ad54f Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 27 Aug 2016 16:10:18 -0400 Subject: [PATCH 1812/6300] fix typo --- Datagram.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 9a5724aa..b73cf647 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -138,8 +138,8 @@ namespace datagram LogPrint(eLogInfo, "DatagramDestination: expiring idle session with ", ident.ToBase32()); m_Sessions.erase(ident); } - m_LocalDestination->CleanupExpiredTags(); - m_LocalDestination->CleanupUnconfirmedTags(); + m_Owner->CleanupExpiredTags(); + m_Owner->CleanupUnconfirmedTags(); ScheduleCleanup(); } From 5685c376cb2b5cc6666e6d30bb222512a761fc1f Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 27 Aug 2016 16:13:11 -0400 Subject: [PATCH 1813/6300] fix broken build --- Datagram.cpp | 1 - Datagram.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index b73cf647..13a4af96 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -139,7 +139,6 @@ namespace datagram m_Sessions.erase(ident); } m_Owner->CleanupExpiredTags(); - m_Owner->CleanupUnconfirmedTags(); ScheduleCleanup(); } diff --git a/Datagram.h b/Datagram.h index dd3ba3ed..bb1bc285 100644 --- a/Datagram.h +++ b/Datagram.h @@ -21,7 +21,7 @@ namespace datagram { // seconds interval for cleanup timer - const int DATAGRAM_SESSION_CLEANUP_INTERVAL = 30; + const int DATAGRAM_SESSION_CLEANUP_INTERVAL = 3; // milliseconds for max session idle time (10 minutes) const uint64_t DATAGRAM_SESSION_MAX_IDLE = 3600 * 1000; From c6556b8442e218e44025ce69281dec91ecd357d5 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 29 Aug 2016 10:41:15 -0400 Subject: [PATCH 1814/6300] make sure m_RTO > 0 in Streaming.cpp so it doesn't hang --- Streaming.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Streaming.cpp b/Streaming.cpp index 6be2a98f..0908ff4a 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -663,6 +663,9 @@ namespace stream void Stream::ScheduleResend () { m_ResendTimer.cancel (); + // check for invalid value + if (m_RTO <= 0) + m_RTO = 1; m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, shared_from_this (), std::placeholders::_1)); From 7d37b02cff9c846e20f387b48c87caaeaceca3a9 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 29 Aug 2016 10:42:51 -0400 Subject: [PATCH 1815/6300] datagram fixes --- AddressBook.cpp | 5 ++--- I2PTunnel.cpp | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 78e9731f..9f1af809 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -547,8 +547,7 @@ namespace client auto datagram = dest->GetDatagramDestination (); if(datagram == nullptr) datagram = dest->CreateDatagramDestination(); datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), - ADDRESS_RESPONSE_DATAGRAM_PORT); + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); } } @@ -559,7 +558,7 @@ namespace client { auto datagram = dest->GetDatagramDestination (); if (datagram) - datagram->ResetReceiver (ADDRESS_RESPONSE_DATAGRAM_PORT); + datagram->ResetReceiver (); } } diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 69360c2c..4c106d4d 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -590,14 +590,14 @@ namespace client m_LocalDest = localDestination; m_LocalDest->Start(); auto dgram = m_LocalDest->CreateDatagramDestination(); - dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), 0); + dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); } I2PUDPServerTunnel::~I2PUDPServerTunnel() { auto dgram = m_LocalDest->GetDatagramDestination(); if (dgram) { - dgram->ResetReceiver(0); + dgram->ResetReceiver(); } LogPrint(eLogInfo, "UDPServer: done"); } @@ -671,7 +671,7 @@ namespace client I2PUDPClientTunnel::~I2PUDPClientTunnel() { auto dgram = m_LocalDest->GetDatagramDestination(); if (dgram) { - dgram->ResetReceiver(LocalPort); + dgram->ResetReceiver(); } if (m_Session) delete m_Session; m_cancel_resolve = true; From 37b80f0ce3fc6b867db2e1941b36f4eca41da0bf Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 29 Aug 2016 10:41:15 -0400 Subject: [PATCH 1816/6300] make sure m_RTO > 0 in Streaming.cpp so it doesn't hang --- Streaming.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Streaming.cpp b/Streaming.cpp index 90c363ea..1103dba1 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -658,6 +658,9 @@ namespace stream void Stream::ScheduleResend () { m_ResendTimer.cancel (); + // check for invalid value + if (m_RTO <= 0) + m_RTO = 1; m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, shared_from_this (), std::placeholders::_1)); From 28fdd992c99930f16d7f514084033845754db76a Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 29 Aug 2016 12:09:37 -0400 Subject: [PATCH 1817/6300] add hooks for custom peer selection --- TunnelPool.cpp | 12 +++++++++--- TunnelPool.h | 17 +++++++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index 119556aa..ba7a86a3 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -15,7 +15,8 @@ namespace tunnel { TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), - m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_IsActive (true) + m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_IsActive (true), + m_CustomPeerSelector(nullptr) { } @@ -327,9 +328,14 @@ namespace tunnel bool TunnelPool::SelectPeers (std::vector >& peers, bool isInbound) { - if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; - if (numHops <= 0) return true; // peers is empty + // peers is empty + if (numHops <= 0) return true; + // custom peer selector in use + if (m_CustomPeerSelector) return m_CustomPeerSelector->SelectPeers(peers, numHops, isInbound); + // explicit peers in use + if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); + auto prevHop = i2p::context.GetSharedRouterInfo (); if(i2p::transport::transports.RoutesRestricted()) { diff --git a/TunnelPool.h b/TunnelPool.h index b32a554f..bffecba5 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -23,6 +23,16 @@ namespace tunnel class InboundTunnel; class OutboundTunnel; + /** interface for custom tunnel peer selection algorithm */ + struct ITunnelPeerSelector + { + typedef std::shared_ptr Peer; + typedef std::vector TunnelPath; + virtual bool SelectPeers(TunnelPath & peers, int hops, bool isInbound) = 0; + }; + + typedef std::shared_ptr TunnelPeerSelector; + class TunnelPool: public std::enable_shared_from_this // per local destination { public: @@ -55,7 +65,10 @@ namespace tunnel int GetNumInboundTunnels () const { return m_NumInboundTunnels; }; int GetNumOutboundTunnels () const { return m_NumOutboundTunnels; }; - + + void SetCustomPeerSelector(TunnelPeerSelector selector); + TunnelPeerSelector GetCustomPeerSelector() const { return m_CustomPeerSelector; } + private: void CreateInboundTunnel (); @@ -79,7 +92,7 @@ namespace tunnel mutable std::mutex m_TestsMutex; std::map, std::shared_ptr > > m_Tests; bool m_IsActive; - + TunnelPeerSelector m_CustomPeerSelector; public: // for HTTP only From fec49e5609f282f6f275696ba2f5ad39af4e593d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 29 Aug 2016 14:16:29 -0400 Subject: [PATCH 1818/6300] add hooks for visiting netdb --- FS.cpp | 9 ++++++++- FS.h | 4 ++++ NetDb.cpp | 15 +++++++++++++++ NetDb.h | 10 ++++++++-- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/FS.cpp b/FS.cpp index 663c1916..a809e8c4 100644 --- a/FS.cpp +++ b/FS.cpp @@ -158,6 +158,13 @@ namespace fs { } void HashedStorage::Traverse(std::vector & files) { + Iterate([&files] (const std::string & fname) { + files.push_back(fname); + }); + } + + void HashedStorage::Iterate(FilenameVisitor v) + { boost::filesystem::path p(root); boost::filesystem::recursive_directory_iterator it(p); boost::filesystem::recursive_directory_iterator end; @@ -166,7 +173,7 @@ namespace fs { if (!boost::filesystem::is_regular_file( it->status() )) continue; const std::string & t = it->path().string(); - files.push_back(t); + v(t); } } } // fs diff --git a/FS.h b/FS.h index 0437ccf9..c476aa63 100644 --- a/FS.h +++ b/FS.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace i2p { namespace fs { @@ -43,6 +44,7 @@ namespace fs { std::string suffix; /**< suffix of file in storage (extension) */ public: + typedef std::function FilenameVisitor; HashedStorage(const char *n, const char *p1, const char *p2, const char *s): name(n), prefix1(p1), prefix2(p2), suffix(s) {}; @@ -58,6 +60,8 @@ namespace fs { void Remove(const std::string & ident); /** find all files in storage and store list in provided vector */ void Traverse(std::vector & files); + /** visit every file in this storage with a visitor */ + void Iterate(FilenameVisitor v); }; /** @brief Returns current application name, default 'i2pd' */ diff --git a/NetDb.cpp b/NetDb.cpp index dc018326..80b36db5 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -329,6 +329,21 @@ namespace data for ( auto & entry : m_LeaseSets) v(entry.first, entry.second); } + + void NetDb::VisitStoredRouterInfos(RouterInfoVisitor v) + { + m_Storage.Iterate([v] (const std::string & filename) { + const i2p::data::RouterInfo ri(filename); + v(ri); + }); + } + + void NetDb::VisitRouterInfos(RouterInfoVisitor v) + { + std::unique_lock lock(m_RouterInfosMutex); + for ( const auto & item : m_RouterInfos ) + v(*item.second); + } void NetDb::Load () { diff --git a/NetDb.h b/NetDb.h index 43b069b9..12d667d3 100644 --- a/NetDb.h +++ b/NetDb.h @@ -35,7 +35,10 @@ namespace data /** function for visiting a leaseset stored in a floodfill */ typedef std::function)> LeaseSetVisitor; - + + /** function for visiting a router info we have locally */ + typedef std::function RouterInfoVisitor; + class NetDb { public: @@ -86,7 +89,10 @@ namespace data /** visit all lease sets we currently store */ void VisitLeaseSets(LeaseSetVisitor v); - + /** visit all router infos we have currently on disk, usually insanely expensive, does not access in memory RI */ + void VisitStoredRouterInfos(RouterInfoVisitor v); + /** visit all router infos we have loaded in memory, cheaper than VisitLocalRouterInfos but locks access while visiting */ + void VisitRouterInfos(RouterInfoVisitor v); private: void Load (); From 10ffdb2766ca049f648ed8fcbdf662d081dc1068 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 29 Aug 2016 15:26:19 -0400 Subject: [PATCH 1819/6300] add NetDb::WaitForReady --- NetDb.cpp | 14 +++++++++++--- NetDb.h | 8 ++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 80b36db5..dca9900d 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -23,7 +23,7 @@ namespace data { NetDb netdb; - NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_HiddenMode(false) + NetDb::NetDb (): m_Ready(new std::promise()), m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_HiddenMode(false) { } @@ -38,13 +38,14 @@ namespace data m_Storage.SetPlace(i2p::fs::GetDataDir()); m_Storage.Init(i2p::data::GetBase64SubstitutionTable(), 64); InitProfilesStorage (); - m_Families.LoadCertificates (); + m_Families.LoadCertificates (); Load (); if (m_RouterInfos.size () < 25) // reseed if # of router less than 50 Reseed (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&NetDb::Run, this)); + m_Ready->set_value(); } void NetDb::Stop () @@ -68,7 +69,14 @@ namespace data m_Requests.Stop (); } } - + + void NetDb::WaitForReady() + { + m_Ready->get_future().wait(); + delete m_Ready; + m_Ready = nullptr; + } + void NetDb::Run () { uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0; diff --git a/NetDb.h b/NetDb.h index 12d667d3..7cf2730e 100644 --- a/NetDb.h +++ b/NetDb.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "Base.h" #include "Gzip.h" @@ -48,7 +49,9 @@ namespace data void Start (); void Stop (); - + /** block until netdb is ready, call only once*/ + void WaitForReady(); + bool AddRouterInfo (const uint8_t * buf, int len); bool AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len); bool AddLeaseSet (const IdentHash& ident, const uint8_t * buf, int len, std::shared_ptr from); @@ -109,7 +112,8 @@ namespace data std::shared_ptr GetRandomRouter (Filter filter) const; private: - + std::promise * m_Ready; + mutable std::mutex m_LeaseSetsMutex; std::map > m_LeaseSets; mutable std::mutex m_RouterInfosMutex; From ce97fa87e773db809fca1441bd607345a7e30e48 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 29 Aug 2016 15:34:59 -0400 Subject: [PATCH 1820/6300] don't use std::promise * --- NetDb.cpp | 10 ++++------ NetDb.h | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index dca9900d..16bd8229 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -23,7 +23,7 @@ namespace data { NetDb netdb; - NetDb::NetDb (): m_Ready(new std::promise()), m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_HiddenMode(false) + NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat"), m_HiddenMode(false) { } @@ -34,7 +34,7 @@ namespace data } void NetDb::Start () - { + { m_Storage.SetPlace(i2p::fs::GetDataDir()); m_Storage.Init(i2p::data::GetBase64SubstitutionTable(), 64); InitProfilesStorage (); @@ -45,7 +45,7 @@ namespace data m_IsRunning = true; m_Thread = new std::thread (std::bind (&NetDb::Run, this)); - m_Ready->set_value(); + m_Ready.set_value(); } void NetDb::Stop () @@ -72,9 +72,7 @@ namespace data void NetDb::WaitForReady() { - m_Ready->get_future().wait(); - delete m_Ready; - m_Ready = nullptr; + m_Ready.get_future().get(); } void NetDb::Run () diff --git a/NetDb.h b/NetDb.h index 7cf2730e..324feb87 100644 --- a/NetDb.h +++ b/NetDb.h @@ -112,7 +112,7 @@ namespace data std::shared_ptr GetRandomRouter (Filter filter) const; private: - std::promise * m_Ready; + std::promise m_Ready; mutable std::mutex m_LeaseSetsMutex; std::map > m_LeaseSets; From 85e65da4925ed77a3bd3ba5a9c8d16f27ddb6ad6 Mon Sep 17 00:00:00 2001 From: xcps Date: Tue, 30 Aug 2016 01:48:37 +0500 Subject: [PATCH 1821/6300] server tunnel on linux binds on 127.x.x.x --- I2PTunnel.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index ad06328b..fc4da719 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -50,9 +50,21 @@ namespace client void I2PTunnelConnection::Connect () { - if (m_Socket) + if (m_Socket) { +#ifdef __linux__ + // bind to 127.x.x.x address + // where x.x.x are first three bytes from ident + m_Socket->open (boost::asio::ip::tcp::v4 ()); + boost::asio::ip::address_v4::bytes_type bytes; + const uint8_t * ident = m_Stream->GetRemoteIdentity ()->GetIdentHash (); + bytes[0] = 127; + memcpy (bytes.data ()+1, ident, 3); + boost::asio::ip::address ourIP = boost::asio::ip::address_v4 (bytes); + m_Socket->bind (boost::asio::ip::tcp::endpoint (ourIP, 0)); +#endif m_Socket->async_connect (m_RemoteEndpoint, std::bind (&I2PTunnelConnection::HandleConnect, shared_from_this (), std::placeholders::_1)); + } } void I2PTunnelConnection::Terminate () From 87d1058de3b0436d4fa040c637ff53f1a34d96c0 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 29 Aug 2016 16:57:34 -0400 Subject: [PATCH 1822/6300] fix --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 16bd8229..7d761e2d 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -72,7 +72,7 @@ namespace data void NetDb::WaitForReady() { - m_Ready.get_future().get(); + m_Ready.get_future().wait(); } void NetDb::Run () From c0cba7b376ca36ae40c7ed669602c82fc47f46e6 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 29 Aug 2016 16:59:17 -0400 Subject: [PATCH 1823/6300] move ready to run --- NetDb.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 7d761e2d..a274908b 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -45,7 +45,6 @@ namespace data m_IsRunning = true; m_Thread = new std::thread (std::bind (&NetDb::Run, this)); - m_Ready.set_value(); } void NetDb::Stop () @@ -77,6 +76,11 @@ namespace data void NetDb::Run () { + try { + m_Ready.set_value(); + } catch( std::future_error & ex) { + (void) ex; + } uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0; while (m_IsRunning) { From f2893097a7870c567ef33c92bf48896e194fcc51 Mon Sep 17 00:00:00 2001 From: xcps Date: Tue, 30 Aug 2016 02:53:26 +0500 Subject: [PATCH 1824/6300] check before bind to 127.x.x.x --- I2PTunnel.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index fc4da719..cca56120 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -54,13 +54,18 @@ namespace client #ifdef __linux__ // bind to 127.x.x.x address // where x.x.x are first three bytes from ident - m_Socket->open (boost::asio::ip::tcp::v4 ()); - boost::asio::ip::address_v4::bytes_type bytes; - const uint8_t * ident = m_Stream->GetRemoteIdentity ()->GetIdentHash (); - bytes[0] = 127; - memcpy (bytes.data ()+1, ident, 3); - boost::asio::ip::address ourIP = boost::asio::ip::address_v4 (bytes); - m_Socket->bind (boost::asio::ip::tcp::endpoint (ourIP, 0)); + + if (m_RemoteEndpoint.address ().is_v4 () && + m_RemoteEndpoint.address ().to_v4 ().to_bytes ()[0] == 127) + { + m_Socket->open (boost::asio::ip::tcp::v4 ()); + boost::asio::ip::address_v4::bytes_type bytes; + const uint8_t * ident = m_Stream->GetRemoteIdentity ()->GetIdentHash (); + bytes[0] = 127; + memcpy (bytes.data ()+1, ident, 3); + boost::asio::ip::address ourIP = boost::asio::ip::address_v4 (bytes); + m_Socket->bind (boost::asio::ip::tcp::endpoint (ourIP, 0)); + } #endif m_Socket->async_connect (m_RemoteEndpoint, std::bind (&I2PTunnelConnection::HandleConnect, shared_from_this (), std::placeholders::_1)); From ac88c1a8f1a640439a8f98338de04cd082211fe5 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 30 Aug 2016 13:27:57 -0400 Subject: [PATCH 1825/6300] add ClientDestination::Ready --- Destination.cpp | 30 ++++++++++++++++++++++++++++-- Destination.h | 16 ++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 349c5785..73fa7a0c 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -168,7 +168,7 @@ namespace client else return false; } - + std::shared_ptr LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident) { std::lock_guard lock(m_RemoteLeaseSetsMutex); @@ -665,7 +665,8 @@ namespace client ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): LeaseSetDestination (isPublic, params), - m_Keys (keys), m_DatagramDestination (nullptr) + m_Keys (keys), m_DatagramDestination (nullptr), + m_ReadyChecker(GetService()) { if (isPublic) PersistTemporaryKeys (); @@ -697,6 +698,7 @@ namespace client { if (LeaseSetDestination::Stop ()) { + m_ReadyChecker.cancel(); m_StreamingDestination->Stop (); m_StreamingDestination = nullptr; for (auto& it: m_StreamingDestinationsByPorts) @@ -710,6 +712,30 @@ namespace client return false; } + void ClientDestination::Ready(ReadyPromise & p) + { + ScheduleCheckForReady(&p); + } + + void ClientDestination::ScheduleCheckForReady(ReadyPromise * p) + { + // tick every 100ms + m_ReadyChecker.expires_from_now(boost::posix_time::milliseconds(100)); + m_ReadyChecker.async_wait([&, p] (const boost::system::error_code & ecode) { + HandleCheckForReady(ecode, p); + }); + } + + void ClientDestination::HandleCheckForReady(const boost::system::error_code & ecode, ReadyPromise * p) + { + if(ecode) // error happened + p->set_value(nullptr); + else if(IsReady()) // we are ready + p->set_value(std::shared_ptr(this)); + else // we are not ready + ScheduleCheckForReady(p); + } + void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); diff --git a/Destination.h b/Destination.h index 3faab9cc..8150c72d 100644 --- a/Destination.h +++ b/Destination.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "Identity.h" #include "TunnelPool.h" @@ -143,13 +144,19 @@ namespace client class ClientDestination: public LeaseSetDestination { public: - + // type for informing that a client destination is ready + typedef std::promise > ReadyPromise; + ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); ~ClientDestination (); bool Start (); bool Stop (); - + + // informs promise with shared_from_this() when this destination is ready to use + // if cancelled before ready, informs promise with nullptr + void Ready(ReadyPromise & p); + const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; @@ -183,6 +190,9 @@ namespace client { return std::static_pointer_cast(shared_from_this ()); } void PersistTemporaryKeys (); + void ScheduleCheckForReady(ReadyPromise * p); + void HandleCheckForReady(const boost::system::error_code & ecode, ReadyPromise * p); + private: i2p::data::PrivateKeys m_Keys; @@ -192,6 +202,8 @@ namespace client std::map > m_StreamingDestinationsByPorts; i2p::datagram::DatagramDestination * m_DatagramDestination; + boost::asio::deadline_timer m_ReadyChecker; + public: // for HTTP only From fa8548fe341fd7d13d0bbbfd397135237c03c8a4 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 30 Aug 2016 15:11:39 -0400 Subject: [PATCH 1826/6300] implement SetCustomPeerSelector and ensure locking is good --- TunnelPool.cpp | 27 ++++++++++++++++++++++++--- TunnelPool.h | 7 ++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/TunnelPool.cpp b/TunnelPool.cpp index ba7a86a3..35272f2c 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -331,8 +331,12 @@ namespace tunnel int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; // peers is empty if (numHops <= 0) return true; - // custom peer selector in use - if (m_CustomPeerSelector) return m_CustomPeerSelector->SelectPeers(peers, numHops, isInbound); + // custom peer selector in use ? + { + std::lock_guard lock(m_CustomPeerSelectorMutex); + if (m_CustomPeerSelector) + return m_CustomPeerSelector->SelectPeers(peers, numHops, isInbound); + } // explicit peers in use if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); @@ -483,6 +487,23 @@ namespace tunnel LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); auto tunnel = tunnels.CreateInboundTunnel (std::make_shared(outboundTunnel->GetInvertedPeers ()), outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); - } + } + + void TunnelPool::SetCustomPeerSelector(TunnelPeerSelector selector) + { + std::lock_guard lock(m_CustomPeerSelectorMutex); + m_CustomPeerSelector = selector; + } + + void TunnelPool::UnsetCustomPeerSelector() + { + SetCustomPeerSelector(nullptr); + } + + bool TunnelPool::HasCustomPeerSelector() + { + std::lock_guard lock(m_CustomPeerSelectorMutex); + return m_CustomPeerSelector != nullptr; + } } } diff --git a/TunnelPool.h b/TunnelPool.h index bffecba5..d5bcf18f 100644 --- a/TunnelPool.h +++ b/TunnelPool.h @@ -67,10 +67,10 @@ namespace tunnel int GetNumOutboundTunnels () const { return m_NumOutboundTunnels; }; void SetCustomPeerSelector(TunnelPeerSelector selector); - TunnelPeerSelector GetCustomPeerSelector() const { return m_CustomPeerSelector; } - + void UnsetCustomPeerSelector(); + bool HasCustomPeerSelector(); private: - + void CreateInboundTunnel (); void CreateOutboundTunnel (); void CreatePairedInboundTunnel (std::shared_ptr outboundTunnel); @@ -92,6 +92,7 @@ namespace tunnel mutable std::mutex m_TestsMutex; std::map, std::shared_ptr > > m_Tests; bool m_IsActive; + std::mutex m_CustomPeerSelectorMutex; TunnelPeerSelector m_CustomPeerSelector; public: From 970557660ee9b1bdd14ba9201bae5f2fc789332b Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 30 Aug 2016 15:54:53 -0400 Subject: [PATCH 1827/6300] Add NetDb::VisitRandomRouterInfos --- NetDb.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++---------- NetDb.h | 8 +++++--- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index a274908b..e1d49c5c 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -68,19 +68,9 @@ namespace data m_Requests.Stop (); } } - - void NetDb::WaitForReady() - { - m_Ready.get_future().wait(); - } void NetDb::Run () { - try { - m_Ready.set_value(); - } catch( std::future_error & ex) { - (void) ex; - } uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0; while (m_IsRunning) { @@ -354,6 +344,50 @@ namespace data for ( const auto & item : m_RouterInfos ) v(*item.second); } + + size_t NetDb::VisitRandomRouterInfos(RouterInfoFilter filter, RouterInfoVisitor v, size_t n) + { + std::vector > found; + const size_t max_iters_per_cyle = 3; + size_t iters = max_iters_per_cyle; + while(n) + { + std::unique_lock lock(m_RouterInfosMutex); + uint32_t idx = rand () % m_RouterInfos.size (); + uint32_t i = 0; + for (const auto & it : m_RouterInfos) { + if(i >= idx) // are we at the random start point? + { + // yes, check if we want this one + if(filter(*it.second)) + { + // we have a match + --n; + found.push_back(it.second); + // reset max iterations per cycle + iters = max_iters_per_cyle; + break; + } + } + else // not there yet + ++i; + } + --iters; + // have we tried enough this cycle ? + if(!iters) { + // yes let's try the next cycle + --n; + iters = max_iters_per_cyle; + } + } + // visit the ones we found + size_t visited = 0; + for(const auto & ri : found ) { + v(*ri); + ++visited; + } + return visited; + } void NetDb::Load () { diff --git a/NetDb.h b/NetDb.h index 324feb87..294e1c83 100644 --- a/NetDb.h +++ b/NetDb.h @@ -39,6 +39,9 @@ namespace data /** function for visiting a router info we have locally */ typedef std::function RouterInfoVisitor; + + /** function for visiting a router info and determining if we want to use it */ + typedef std::function RouterInfoFilter; class NetDb { @@ -49,8 +52,6 @@ namespace data void Start (); void Stop (); - /** block until netdb is ready, call only once*/ - void WaitForReady(); bool AddRouterInfo (const uint8_t * buf, int len); bool AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len); @@ -96,6 +97,8 @@ namespace data void VisitStoredRouterInfos(RouterInfoVisitor v); /** visit all router infos we have loaded in memory, cheaper than VisitLocalRouterInfos but locks access while visiting */ void VisitRouterInfos(RouterInfoVisitor v); + /** visit N random router that match using filter, then visit them with a visitor, return number of RouterInfos that were visited */ + size_t VisitRandomRouterInfos(RouterInfoFilter f, RouterInfoVisitor v, size_t n); private: void Load (); @@ -112,7 +115,6 @@ namespace data std::shared_ptr GetRandomRouter (Filter filter) const; private: - std::promise m_Ready; mutable std::mutex m_LeaseSetsMutex; std::map > m_LeaseSets; From ab763c38d9e7051612c7853e7452b86045a7fc1d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 30 Aug 2016 19:59:24 -0400 Subject: [PATCH 1828/6300] use shared pointers --- NetDb.cpp | 6 +++--- NetDb.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index e1d49c5c..d0a75c3a 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -342,7 +342,7 @@ namespace data { std::unique_lock lock(m_RouterInfosMutex); for ( const auto & item : m_RouterInfos ) - v(*item.second); + v(item.second); } size_t NetDb::VisitRandomRouterInfos(RouterInfoFilter filter, RouterInfoVisitor v, size_t n) @@ -359,7 +359,7 @@ namespace data if(i >= idx) // are we at the random start point? { // yes, check if we want this one - if(filter(*it.second)) + if(filter(it.second)) { // we have a match --n; @@ -383,7 +383,7 @@ namespace data // visit the ones we found size_t visited = 0; for(const auto & ri : found ) { - v(*ri); + v(ri); ++visited; } return visited; diff --git a/NetDb.h b/NetDb.h index 294e1c83..d8ee148a 100644 --- a/NetDb.h +++ b/NetDb.h @@ -38,10 +38,10 @@ namespace data typedef std::function)> LeaseSetVisitor; /** function for visiting a router info we have locally */ - typedef std::function RouterInfoVisitor; + typedef std::function)> RouterInfoVisitor; /** function for visiting a router info and determining if we want to use it */ - typedef std::function RouterInfoFilter; + typedef std::function)> RouterInfoFilter; class NetDb { From a68326490d7e9092ef64f14defed3281712a2b8e Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 30 Aug 2016 20:02:27 -0400 Subject: [PATCH 1829/6300] fix --- NetDb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index d0a75c3a..367477a5 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -333,7 +333,7 @@ namespace data void NetDb::VisitStoredRouterInfos(RouterInfoVisitor v) { m_Storage.Iterate([v] (const std::string & filename) { - const i2p::data::RouterInfo ri(filename); + auto ri = std::make_shared(filename); v(ri); }); } From 8cb69c14826ec2988afb6ca6367c3c8c3bf89d7a Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 31 Aug 2016 22:47:32 -0400 Subject: [PATCH 1830/6300] fixed #624. correct v6 address size --- SSUSession.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SSUSession.cpp b/SSUSession.cpp index 9245f652..26c14570 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -991,7 +991,7 @@ namespace transport else // v6 { boost::asio::ip::address_v6::bytes_type bytes; - memcpy (bytes.data (), address, 6); + memcpy (bytes.data (), address, 16); addr = boost::asio::ip::address_v6 (bytes); } SendPeerTest (nonce, addr, be16toh (port), introKey); // to Alice with her address received from Bob @@ -1033,7 +1033,7 @@ namespace transport } else if (address.is_v6 ()) { - *payload = 6; + *payload = 16; memcpy (payload + 1, address.to_v6 ().to_bytes ().data (), 16); // our IP V6 } else From aa687afd37d244cedb0939daaa7bd1867f75f26a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 1 Sep 2016 09:48:04 -0400 Subject: [PATCH 1831/6300] updated LeaseSet must be sent after completion --- Destination.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 42351787..5842e26d 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -203,6 +203,7 @@ namespace client void LeaseSetDestination::SetLeaseSet (i2p::data::LocalLeaseSet * newLeaseSet) { m_LeaseSet.reset (newLeaseSet); + i2p::garlic::GarlicDestination::SetLeaseSetUpdated (); if (m_IsPublic) { m_PublishVerificationTimer.cancel (); @@ -391,8 +392,7 @@ namespace client } void LeaseSetDestination::SetLeaseSetUpdated () - { - i2p::garlic::GarlicDestination::SetLeaseSetUpdated (); + { UpdateLeaseSet (); } From 3ea624e1db30a72fc6e24e0ae0a1b06c39867996 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 1 Sep 2016 15:54:48 -0400 Subject: [PATCH 1832/6300] cosmetic fix --- NetDb.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NetDb.cpp b/NetDb.cpp index 367477a5..846f4b3c 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -350,7 +350,7 @@ namespace data std::vector > found; const size_t max_iters_per_cyle = 3; size_t iters = max_iters_per_cyle; - while(n) + while(n > 0) { std::unique_lock lock(m_RouterInfosMutex); uint32_t idx = rand () % m_RouterInfos.size (); @@ -372,6 +372,8 @@ namespace data else // not there yet ++i; } + // we have enough + if(n == 0) break; --iters; // have we tried enough this cycle ? if(!iters) { From c65dc44f205c545499f71c31a69a0f8ad0e4521f Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 09:38:53 -0400 Subject: [PATCH 1833/6300] Fix up I2PTunnel UDP tunnels --- ClientContext.cpp | 33 ++++++----- ClientContext.h | 3 +- I2PTunnel.cpp | 84 +++++++++++++++++----------- I2PTunnel.h | 138 +++++++++++++++++++++++++--------------------- 4 files changed, 149 insertions(+), 109 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 3b814260..e89e7eb7 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -404,17 +404,19 @@ namespace client } if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { // udp client - // TODO: ip6 and hostnames + // TODO: hostnames boost::asio::ip::udp::endpoint end(boost::asio::ip::address::from_string(address), port); - if (!localDestination) { + if (!localDestination) + { localDestination = m_SharedLocalDestination; } auto clientTunnel = new I2PUDPClientTunnel(name, dest, end, localDestination, destinationPort); - if(m_ClientForwards.insert(std::make_pair(end, std::unique_ptr(clientTunnel))).second) { + if(m_ClientForwards.insert(std::make_pair(end, std::unique_ptr(clientTunnel))).second) + { clientTunnel->Start(); - } else { - LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); } + else + LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); } else { // tcp client @@ -443,7 +445,8 @@ namespace client bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); uint32_t maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); - + std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, "127.0.0.1"); + // I2CP std::map options; ReadI2CPOptions (section, options); @@ -455,22 +458,26 @@ namespace client localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); if (!localDestination) localDestination = CreateNewLocalDestination (k, true, &options); - if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { + if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) + { // udp server tunnel - // TODO: ipv6 and hostnames + // TODO: hostnames + auto localAddress = boost::asio::ip::address::from_string(address); boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); - I2PUDPServerTunnel * serverTunnel = new I2PUDPServerTunnel(name, localDestination, endpoint, port); + I2PUDPServerTunnel * serverTunnel = new I2PUDPServerTunnel(name, localDestination, localAddress, endpoint, port); std::lock_guard lock(m_ForwardsMutex); if(m_ServerForwards.insert( std::make_pair( std::make_pair( localDestination->GetIdentHash(), port), - std::unique_ptr(serverTunnel))).second) { + std::unique_ptr(serverTunnel))).second) + { serverTunnel->Start(); - LogPrint(eLogInfo, "Clients: I2P Server Forward created for UDP Endpoint ", host, ":", port, " via ",localDestination->GetIdentHash().ToBase32()); - } else { - LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); + LogPrint(eLogInfo, "Clients: I2P Server Forward created for UDP Endpoint ", host, ":", port, " bound on ", address, " for ",localDestination->GetIdentHash().ToBase32()); } + else + LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); + continue; } diff --git a/ClientContext.h b/ClientContext.h index 5a054f29..0ff7993f 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -41,7 +41,8 @@ namespace client const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist"; const char I2P_SERVER_TUNNEL_GZIP[] = "gzip"; const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword"; - + const char I2P_SERVER_TUNNEL_ADDRESS[] = "address"; + class ClientContext { public: diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 6cc21e85..db8831be 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -532,7 +532,7 @@ namespace client { std::lock_guard lock(m_SessionsMutex); auto session = ObtainUDPSession(from, toPort, fromPort); - session->IPSocket.send_to(boost::asio::buffer(buf, len), m_Endpoint); + session->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); session->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); } @@ -548,20 +548,25 @@ namespace client UDPSession * I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) { auto ih = from.GetIdentHash(); - for ( UDPSession * s : m_Sessions ) { - if ( s->Identity == ih) { - /** found existing */ + for ( UDPSession * s : m_Sessions ) + { + if ( s->Identity == ih) + { + /** found existing session */ LogPrint(eLogDebug, "UDPServer: found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32()); return s; } } - /** create new */ - boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); - m_Sessions.push_back(new UDPSession(ep, m_LocalDest, m_Endpoint, ih, localPort, remotePort)); + /** create new udp session */ + boost::asio::ip::udp::endpoint ep(m_LocalAddress, 0); + m_Sessions.push_back(new UDPSession(ep, m_LocalDest, m_RemoteEndpoint, ih, localPort, remotePort)); return m_Sessions.back(); } - UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash to, uint16_t ourPort, uint16_t theirPort) : + UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, + const std::shared_ptr & localDestination, + boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash to, + uint16_t ourPort, uint16_t theirPort) : m_Destination(localDestination->GetDatagramDestination()), m_Service(localDestination->GetService()), IPSocket(localDestination->GetService(), localEndpoint), @@ -577,18 +582,20 @@ namespace client void UDPSession::Receive() { LogPrint(eLogDebug, "UDPSession: Receive"); - IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); + IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), + FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); } void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) { - if(!ecode) { + if(!ecode) + { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); LastActivity = i2p::util::GetMillisecondsSinceEpoch(); uint8_t * data = new uint8_t[len]; memcpy(data, m_Buffer, len); m_Service.post([&,len, data] () { - m_Destination->SendDatagramTo(data, len, Identity, 0, 0); + m_Destination->SendDatagramTo(data, len, Identity, 0, 0); delete [] data; }); @@ -600,9 +607,12 @@ namespace client - I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint forwardTo, uint16_t port) : + I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, + const boost::asio::ip::address& localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port) : + m_LocalAddress(localAddress), + m_Name(name), LocalPort(port), - m_Endpoint(forwardTo) + m_RemoteEndpoint(forwardTo) { m_LocalDest = localDestination; m_LocalDest->Start(); @@ -613,9 +623,8 @@ namespace client I2PUDPServerTunnel::~I2PUDPServerTunnel() { auto dgram = m_LocalDest->GetDatagramDestination(); - if (dgram) { - dgram->ResetReceiver(); - } + if (dgram) dgram->ResetReceiver(); + LogPrint(eLogInfo, "UDPServer: done"); } @@ -623,7 +632,11 @@ namespace client m_LocalDest->Start(); } - I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort) : + I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, + boost::asio::ip::udp::endpoint localEndpoint, + std::shared_ptr localDestination, + uint16_t remotePort) : + m_Name(name), m_Session(nullptr), m_RemoteDest(remoteDest), m_RemoteIdent(nullptr), @@ -654,50 +667,57 @@ namespace client m_RemoteIdent = new i2p::data::IdentHash; m_RemoteIdent->Fill(0); - while(!context.GetAddressBook().GetIdentHash(m_RemoteDest, *m_RemoteIdent) && !m_cancel_resolve) { + while(!context.GetAddressBook().GetIdentHash(m_RemoteDest, *m_RemoteIdent) && !m_cancel_resolve) + { LogPrint(eLogWarning, "UDP Tunnel: failed to lookup ", m_RemoteDest); std::this_thread::sleep_for(std::chrono::seconds(1)); } - if(m_cancel_resolve) { + if(m_cancel_resolve) + { LogPrint(eLogError, "UDP Tunnel: lookup of ", m_RemoteDest, " was cancelled"); return; } LogPrint(eLogInfo, "UDP Tunnel: resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32()); // delete existing session if(m_Session) delete m_Session; + boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); m_Session = new UDPSession(m_LocalEndpoint, m_LocalDest, ep, *m_RemoteIdent, LocalPort, RemotePort); } void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { - if(m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) { + if(m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) + { // address match - if(m_Session) { + if(m_Session) + { // tell session LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", from.GetIdentHash().ToBase32()); m_Session->IPSocket.send_to(boost::asio::buffer(buf, len), m_Session->FromEndpoint); - } else { - LogPrint(eLogWarning, "UDP Client: no session"); } - } else { - LogPrint(eLogWarning, "UDP Client: unwarrented traffic from ", from.GetIdentHash().ToBase32()); + else + LogPrint(eLogWarning, "UDP Client: no session"); } + else + LogPrint(eLogWarning, "UDP Client: unwarrented traffic from ", from.GetIdentHash().ToBase32()); + } - + I2PUDPClientTunnel::~I2PUDPClientTunnel() { auto dgram = m_LocalDest->GetDatagramDestination(); - if (dgram) { - dgram->ResetReceiver(); - } + if (dgram) dgram->ResetReceiver(); + if (m_Session) delete m_Session; m_cancel_resolve = true; - if(m_ResolveThread) { + + if(m_ResolveThread) + { m_ResolveThread->join(); delete m_ResolveThread; m_ResolveThread = nullptr; } if (m_RemoteIdent) delete m_RemoteIdent; } -} -} +} +} diff --git a/I2PTunnel.h b/I2PTunnel.h index 63341679..76bfc36b 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -132,74 +132,86 @@ namespace client }; - /** 2 minute timeout for udp sessions */ - const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; + /** 2 minute timeout for udp sessions */ + const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; - /** max size for i2p udp */ - const size_t I2P_UDP_MAX_MTU = i2p::datagram::MAX_DATAGRAM_SIZE; - - struct UDPSession - { - i2p::datagram::DatagramDestination * m_Destination; - boost::asio::io_service & m_Service; - boost::asio::ip::udp::socket IPSocket; - i2p::data::IdentHash Identity; - boost::asio::ip::udp::endpoint FromEndpoint; - boost::asio::ip::udp::endpoint SendEndpoint; - uint64_t LastActivity; + /** max size for i2p udp */ + const size_t I2P_UDP_MAX_MTU = i2p::datagram::MAX_DATAGRAM_SIZE; + + struct UDPSession + { + i2p::datagram::DatagramDestination * m_Destination; + boost::asio::io_service & m_Service; + boost::asio::ip::udp::socket IPSocket; + i2p::data::IdentHash Identity; + boost::asio::ip::udp::endpoint FromEndpoint; + boost::asio::ip::udp::endpoint SendEndpoint; + uint64_t LastActivity; - uint16_t LocalPort; - uint16_t RemotePort; + uint16_t LocalPort; + uint16_t RemotePort; - uint8_t m_Buffer[I2P_UDP_MAX_MTU]; - - UDPSession(boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash ident, uint16_t ourPort, uint16_t theirPort); + uint8_t m_Buffer[I2P_UDP_MAX_MTU]; + + UDPSession(boost::asio::ip::udp::endpoint localEndpoint, + const std::shared_ptr & localDestination, + boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash ident, + uint16_t ourPort, uint16_t theirPort); + void HandleReceived(const boost::system::error_code & ecode, std::size_t len); + void Receive(); + }; - void HandleReceived(const boost::system::error_code & ecode, std::size_t len); - void Receive(); - }; + /** server side udp tunnel, many i2p inbound to 1 ip outbound */ + class I2PUDPServerTunnel + { + public: + I2PUDPServerTunnel(const std::string & name, + std::shared_ptr localDestination, + const boost::asio::ip::address & localAddress, + boost::asio::ip::udp::endpoint forwardTo, uint16_t port); + ~I2PUDPServerTunnel(); + /** expire stale udp conversations */ + void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); + void Start(); + const char * GetName() const { return m_Name.c_str(); } + private: + void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + UDPSession * ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); + private: + const std::string m_Name; + const uint16_t LocalPort; + boost::asio::ip::address m_LocalAddress; + boost::asio::ip::udp::endpoint m_RemoteEndpoint; + std::mutex m_SessionsMutex; + std::vector m_Sessions; + std::shared_ptr m_LocalDest; + }; - /** server side udp tunnel, many i2p inbound to 1 ip outbound */ - class I2PUDPServerTunnel - { - public: - I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::udp::endpoint forwardTo, uint16_t port); - ~I2PUDPServerTunnel(); - /** expire stale udp conversations */ - void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); - void Start(); - private: - void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - UDPSession * ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); - private: - const uint16_t LocalPort; - boost::asio::ip::udp::endpoint m_Endpoint; - std::mutex m_SessionsMutex; - std::vector m_Sessions; - std::shared_ptr m_LocalDest; - uint8_t m_Buffer[I2P_UDP_MAX_MTU]; - }; - - class I2PUDPClientTunnel - { - public: - I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort); - ~I2PUDPClientTunnel(); - void Start(); - private: - void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void TryResolving(); - UDPSession * m_Session; - const std::string m_RemoteDest; - std::shared_ptr m_LocalDest; - const boost::asio::ip::udp::endpoint m_LocalEndpoint; - i2p::data::IdentHash * m_RemoteIdent; - std::thread * m_ResolveThread; - uint16_t LocalPort; - uint16_t RemotePort; - bool m_cancel_resolve; - }; - + class I2PUDPClientTunnel + { + public: + I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, + boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, + uint16_t remotePort); + ~I2PUDPClientTunnel(); + void Start(); + const char * GetName() const { return m_Name.c_str(); } + + private: + void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void TryResolving(); + const std::string m_Name; + UDPSession * m_Session; + const std::string m_RemoteDest; + std::shared_ptr m_LocalDest; + const boost::asio::ip::udp::endpoint m_LocalEndpoint; + i2p::data::IdentHash * m_RemoteIdent; + std::thread * m_ResolveThread; + uint16_t LocalPort; + uint16_t RemotePort; + bool m_cancel_resolve; + }; + class I2PServerTunnel: public I2PService { public: From caace05ba69ec8c364da16009520ed4cfdef1c7d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 10:01:23 -0400 Subject: [PATCH 1834/6300] fix up compiler warnings --- ClientContext.cpp | 8 ++++---- I2PTunnel.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index e89e7eb7..3e33ae07 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -531,6 +531,7 @@ namespace client LogPrint (eLogInfo, "Clients: ", numClientTunnels, " I2P client tunnels created"); LogPrint (eLogInfo, "Clients: ", numServerTunnels, " I2P server tunnels created"); } + void ClientContext::ScheduleCleanupUDP() { // schedule cleanup in 1 second @@ -540,11 +541,10 @@ namespace client void ClientContext::CleanupUDP(const boost::system::error_code & ecode) { - if(!ecode) { + if(!ecode) + { std::lock_guard lock(m_ForwardsMutex); - for ( auto & s : m_ServerForwards ) { - s.second->ExpireStale(); - } + for ( auto & s : m_ServerForwards ) s.second->ExpireStale(); ScheduleCleanupUDP(); } } diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index db8831be..087221a3 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -609,9 +609,9 @@ namespace client I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, const boost::asio::ip::address& localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port) : - m_LocalAddress(localAddress), m_Name(name), LocalPort(port), + m_LocalAddress(localAddress), m_RemoteEndpoint(forwardTo) { m_LocalDest = localDestination; @@ -639,9 +639,9 @@ namespace client m_Name(name), m_Session(nullptr), m_RemoteDest(remoteDest), - m_RemoteIdent(nullptr), m_LocalDest(localDestination), m_LocalEndpoint(localEndpoint), + m_RemoteIdent(nullptr), m_ResolveThread(nullptr), LocalPort(localEndpoint.port()), RemotePort(remotePort), From c770bcbf9630a07e5420cbbc12814858c3b4a830 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 10:24:06 -0400 Subject: [PATCH 1835/6300] prevent race condition in datagram destination clean up style a bit --- AddressBook.cpp | 12 ++++++------ Datagram.cpp | 20 ++++++++++++++------ Datagram.h | 14 +++++++++----- Tag.h | 9 +++++---- 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 9f1af809..b993f456 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -543,11 +543,12 @@ namespace client auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { - auto datagram = dest->GetDatagramDestination (); - if(datagram == nullptr) datagram = dest->CreateDatagramDestination(); - datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + if (!datagram) + datagram = dest->CreateDatagramDestination (); + datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), + ADDRESS_RESPONSE_DATAGRAM_PORT); } } @@ -557,8 +558,7 @@ namespace client if (dest) { auto datagram = dest->GetDatagramDestination (); - if (datagram) - datagram->ResetReceiver (); + if (datagram) datagram->ResetReceiver (ADDRESS_RESPONSE_DATAGRAM_PORT); } } diff --git a/Datagram.cpp b/Datagram.cpp index 13a4af96..cdd468b1 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -71,18 +71,26 @@ namespace datagram if (verified) { - auto it = m_ReceiversByPorts.find (toPort); - if (it != m_ReceiversByPorts.end ()) - it->second (identity, fromPort, toPort, buf + headerLen, len -headerLen); - else if (m_Receiver != nullptr) - m_Receiver (identity, fromPort, toPort, buf + headerLen, len -headerLen); + auto r = FindReceiver(toPort); + if(r) + r(identity, fromPort, toPort, buf + headerLen, len -headerLen); else - LogPrint (eLogWarning, "Receiver for datagram is not set"); + LogPrint (eLogWarning, "DatagramDestination: no receiver for port ", toPort); } else LogPrint (eLogWarning, "Datagram signature verification failed"); } + DatagramDestination::Receiver DatagramDestination::FindReceiver(uint16_t port) + { + std::lock_guard lock(m_ReceiversMutex); + Receiver r = m_Receiver; + auto itr = m_ReceiversByPorts.find(port); + if (itr != m_ReceiversByPorts.end()) + r = itr->second; + return r; + } + void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { // unzip it diff --git a/Datagram.h b/Datagram.h index bb1bc285..887c82d2 100644 --- a/Datagram.h +++ b/Datagram.h @@ -80,8 +80,8 @@ namespace datagram void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; void ResetReceiver () { m_Receiver = nullptr; }; - void SetReceiver (const Receiver& receiver, uint16_t port) { m_ReceiversByPorts[port] = receiver; }; - void ResetReceiver (uint16_t port) { m_ReceiversByPorts.erase (port); }; + void SetReceiver (const Receiver& receiver, uint16_t port) { std::lock_guard lock(m_ReceiversMutex); m_ReceiversByPorts[port] = receiver; }; + void ResetReceiver (uint16_t port) { std::lock_guard lock(m_ReceiversMutex); m_ReceiversByPorts.erase (port); }; private: // clean up after next tick @@ -96,12 +96,16 @@ namespace datagram void HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + /** find a receiver by port, if none by port is found try default receiever, otherwise returns nullptr */ + Receiver FindReceiver(uint16_t port); + private: i2p::client::ClientDestination * m_Owner; - boost::asio::deadline_timer m_CleanupTimer; + boost::asio::deadline_timer m_CleanupTimer; Receiver m_Receiver; // default - std::mutex m_SessionsMutex; - std::map > m_Sessions; + std::mutex m_SessionsMutex; + std::map > m_Sessions; + std::mutex m_ReceiversMutex; std::map m_ReceiversByPorts; i2p::data::GzipInflator m_Inflator; diff --git a/Tag.h b/Tag.h index 6e5638a4..4b393c57 100644 --- a/Tag.h +++ b/Tag.h @@ -51,10 +51,11 @@ namespace data { } /** fill with a value */ - void Fill(uint8_t c) { - memset(m_Buf, c, sz); - } - + void Fill(uint8_t c) + { + memset(m_Buf, c, sz); + } + std::string ToBase64 () const { char str[sz*2]; From 9acbb2203cca1c4e6c3de4e9abe1f22582a54523 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 11:46:47 -0400 Subject: [PATCH 1836/6300] Update Indentation and don't spam route changes in datagram sessions --- ClientContext.cpp | 177 ++++++++--------- ClientContext.h | 33 ++-- Datagram.cpp | 490 +++++++++++++++++++++++++--------------------- Datagram.h | 117 ++++++----- LeaseSet.cpp | 3 +- LeaseSet.h | 11 +- Streaming.cpp | 12 +- 7 files changed, 461 insertions(+), 382 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 3e33ae07..3c4d63c0 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -17,8 +17,8 @@ namespace client ClientContext::ClientContext (): m_SharedLocalDestination (nullptr), m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr), - m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr), - m_CleanupUDPTimer(m_Service, boost::posix_time::seconds(1)) + m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr), + m_CleanupUDPTimer(m_Service, boost::posix_time::seconds(1)) { } @@ -88,14 +88,14 @@ namespace client } } - if ( m_ServiceThread == nullptr ) { - m_ServiceThread = new std::thread([&] () { - LogPrint(eLogInfo, "ClientContext: starting service"); - m_Service.run(); - LogPrint(eLogError, "ClientContext: service died"); - }); - ScheduleCleanupUDP(); - } + if ( m_ServiceThread == nullptr ) { + m_ServiceThread = new std::thread([&] () { + LogPrint(eLogInfo, "ClientContext: starting service"); + m_Service.run(); + LogPrint(eLogError, "ClientContext: service died"); + }); + ScheduleCleanupUDP(); + } // I2P tunnels @@ -211,23 +211,24 @@ namespace client m_AddressBook.Stop (); { - std::lock_guard lock(m_ForwardsMutex); - m_ServerForwards.clear(); - m_ClientForwards.clear(); - } - - + std::lock_guard lock(m_ForwardsMutex); + m_ServerForwards.clear(); + m_ClientForwards.clear(); + } + + for (auto& it: m_Destinations) it.second->Stop (); m_Destinations.clear (); m_SharedLocalDestination = nullptr; - // stop io service thread - if(m_ServiceThread) { - m_Service.stop(); - m_ServiceThread->join(); - delete m_ServiceThread; - m_ServiceThread = nullptr; - } + // stop io service thread + if(m_ServiceThread) + { + m_Service.stop(); + m_ServiceThread->join(); + delete m_ServiceThread; + m_ServiceThread = nullptr; + } } void ClientContext::ReloadConfig () @@ -402,34 +403,34 @@ namespace client localDestination = CreateNewLocalDestination (k, type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT, &options); } } - if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { - // udp client - // TODO: hostnames - boost::asio::ip::udp::endpoint end(boost::asio::ip::address::from_string(address), port); - if (!localDestination) - { - localDestination = m_SharedLocalDestination; - } - auto clientTunnel = new I2PUDPClientTunnel(name, dest, end, localDestination, destinationPort); - if(m_ClientForwards.insert(std::make_pair(end, std::unique_ptr(clientTunnel))).second) - { - clientTunnel->Start(); - } - else - LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); + if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { + // udp client + // TODO: hostnames + boost::asio::ip::udp::endpoint end(boost::asio::ip::address::from_string(address), port); + if (!localDestination) + { + localDestination = m_SharedLocalDestination; + } + auto clientTunnel = new I2PUDPClientTunnel(name, dest, end, localDestination, destinationPort); + if(m_ClientForwards.insert(std::make_pair(end, std::unique_ptr(clientTunnel))).second) + { + clientTunnel->Start(); + } + else + LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); - } else { - // tcp client - auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); - if (m_ClientTunnels.insert (std::make_pair (clientTunnel->GetAcceptor ().local_endpoint (), - std::unique_ptr(clientTunnel))).second) - { - clientTunnel->Start (); - numClientTunnels++; - } - else - LogPrint (eLogError, "Clients: I2P client tunnel for endpoint ", clientTunnel->GetAcceptor ().local_endpoint (), " already exists"); - } + } else { + // tcp client + auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); + if (m_ClientTunnels.insert (std::make_pair (clientTunnel->GetAcceptor ().local_endpoint (), + std::unique_ptr(clientTunnel))).second) + { + clientTunnel->Start (); + numClientTunnels++; + } + else + LogPrint (eLogError, "Clients: I2P client tunnel for endpoint ", clientTunnel->GetAcceptor ().local_endpoint (), " already exists"); + } } else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC || type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { @@ -458,28 +459,28 @@ namespace client localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); if (!localDestination) localDestination = CreateNewLocalDestination (k, true, &options); - if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) - { - // udp server tunnel - // TODO: hostnames - auto localAddress = boost::asio::ip::address::from_string(address); - boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); - I2PUDPServerTunnel * serverTunnel = new I2PUDPServerTunnel(name, localDestination, localAddress, endpoint, port); - std::lock_guard lock(m_ForwardsMutex); - if(m_ServerForwards.insert( - std::make_pair( - std::make_pair( - localDestination->GetIdentHash(), port), - std::unique_ptr(serverTunnel))).second) - { - serverTunnel->Start(); - LogPrint(eLogInfo, "Clients: I2P Server Forward created for UDP Endpoint ", host, ":", port, " bound on ", address, " for ",localDestination->GetIdentHash().ToBase32()); - } - else - LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); - - continue; - } + if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) + { + // udp server tunnel + // TODO: hostnames + auto localAddress = boost::asio::ip::address::from_string(address); + boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); + I2PUDPServerTunnel * serverTunnel = new I2PUDPServerTunnel(name, localDestination, localAddress, endpoint, port); + std::lock_guard lock(m_ForwardsMutex); + if(m_ServerForwards.insert( + std::make_pair( + std::make_pair( + localDestination->GetIdentHash(), port), + std::unique_ptr(serverTunnel))).second) + { + serverTunnel->Start(); + LogPrint(eLogInfo, "Clients: I2P Server Forward created for UDP Endpoint ", host, ":", port, " bound on ", address, " for ",localDestination->GetIdentHash().ToBase32()); + } + else + LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); + + continue; + } I2PServerTunnel * serverTunnel; if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) @@ -512,7 +513,7 @@ namespace client std::make_pair (localDestination->GetIdentHash (), inPort), std::unique_ptr(serverTunnel))).second) { - serverTunnel->Start (); + serverTunnel->Start (); numServerTunnels++; } else @@ -532,21 +533,21 @@ namespace client LogPrint (eLogInfo, "Clients: ", numServerTunnels, " I2P server tunnels created"); } - void ClientContext::ScheduleCleanupUDP() - { - // schedule cleanup in 1 second - m_CleanupUDPTimer.expires_at(m_CleanupUDPTimer.expires_at() + boost::posix_time::seconds(1)); - m_CleanupUDPTimer.async_wait(std::bind(&ClientContext::CleanupUDP, this, std::placeholders::_1)); - } + void ClientContext::ScheduleCleanupUDP() + { + // schedule cleanup in 1 second + m_CleanupUDPTimer.expires_at(m_CleanupUDPTimer.expires_at() + boost::posix_time::seconds(1)); + m_CleanupUDPTimer.async_wait(std::bind(&ClientContext::CleanupUDP, this, std::placeholders::_1)); + } - void ClientContext::CleanupUDP(const boost::system::error_code & ecode) - { - if(!ecode) - { - std::lock_guard lock(m_ForwardsMutex); - for ( auto & s : m_ServerForwards ) s.second->ExpireStale(); - ScheduleCleanupUDP(); - } - } + void ClientContext::CleanupUDP(const boost::system::error_code & ecode) + { + if(!ecode) + { + std::lock_guard lock(m_ForwardsMutex); + for ( auto & s : m_ServerForwards ) s.second->ExpireStale(); + ScheduleCleanupUDP(); + } + } } } diff --git a/ClientContext.h b/ClientContext.h index 0ff7993f..87dbec0f 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -24,8 +24,8 @@ namespace client const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc"; - const char I2P_TUNNELS_SECTION_TYPE_UDPCLIENT[] = "udpclient"; - const char I2P_TUNNELS_SECTION_TYPE_UDPSERVER[] = "udpserver"; + const char I2P_TUNNELS_SECTION_TYPE_UDPCLIENT[] = "udpclient"; + const char I2P_TUNNELS_SECTION_TYPE_UDPSERVER[] = "udpserver"; const char I2P_CLIENT_TUNNEL_PORT[] = "port"; const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination"; @@ -75,10 +75,10 @@ namespace client template void ReadI2CPOptions (const Section& section, std::map& options) const; - void CleanupUDP(const boost::system::error_code & ecode); - void ScheduleCleanupUDP(); - - private: + void CleanupUDP(const boost::system::error_code & ecode); + void ScheduleCleanupUDP(); + + private: std::mutex m_DestinationsMutex; std::map > m_Destinations; @@ -91,25 +91,26 @@ namespace client std::map > m_ClientTunnels; // local endpoint->tunnel std::map, std::unique_ptr > m_ServerTunnels; // ->tunnel - std::mutex m_ForwardsMutex; - - std::map > m_ClientForwards; // local endpoint -> udp tunnel - std::map, std::unique_ptr > m_ServerForwards; // -> udp tunnel - - SAMBridge * m_SamBridge; + std::mutex m_ForwardsMutex; + std::map > m_ClientForwards; // local endpoint -> udp tunnel + std::map, std::unique_ptr > m_ServerForwards; // -> udp tunnel + + SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; I2CPServer * m_I2CPServer; - boost::asio::io_service m_Service; - std::thread * m_ServiceThread; + boost::asio::io_service m_Service; + std::thread * m_ServiceThread; - boost::asio::deadline_timer m_CleanupUDPTimer; - + boost::asio::deadline_timer m_CleanupUDPTimer; + public: // for HTTP const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; }; const decltype(m_ClientTunnels)& GetClientTunnels () const { return m_ClientTunnels; }; const decltype(m_ServerTunnels)& GetServerTunnels () const { return m_ServerTunnels; }; + const decltype(m_ClientForwards)& GetClientForwards () const { return m_ClientForwards; } + const decltype(m_ServerForwards)& GetServerForwards () const { return m_ServerForwards; } }; extern ClientContext context; diff --git a/Datagram.cpp b/Datagram.cpp index cdd468b1..37559922 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -13,22 +13,22 @@ namespace datagram { DatagramDestination::DatagramDestination (std::shared_ptr owner): m_Owner (owner.get()), - m_CleanupTimer(owner->GetService()), - m_Receiver (nullptr) + m_CleanupTimer(owner->GetService()), + m_Receiver (nullptr) { - ScheduleCleanup(); + ScheduleCleanup(); } DatagramDestination::~DatagramDestination () { - m_CleanupTimer.cancel(); - m_Sessions.clear(); + m_CleanupTimer.cancel(); + m_Sessions.clear(); } void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort) { - auto owner = m_Owner; - auto i = owner->GetIdentity(); + auto owner = m_Owner; + auto i = owner->GetIdentity(); uint8_t buf[MAX_DATAGRAM_SIZE]; auto identityLen = i->ToBuffer (buf, MAX_DATAGRAM_SIZE); uint8_t * signature = buf + identityLen; @@ -41,15 +41,15 @@ namespace datagram { uint8_t hash[32]; SHA256(buf1, len, hash); - owner->Sign (hash, 32, signature); + owner->Sign (hash, 32, signature); } else owner->Sign (buf1, len, signature); auto msg = CreateDataMessage (buf, len + headerLen, fromPort, toPort); - auto session = ObtainSession(ident); - session->SendMsg(msg); - } + auto session = ObtainSession(ident); + session->SendMsg(msg); + } void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) @@ -120,224 +120,274 @@ namespace datagram return msg; } - void DatagramDestination::ScheduleCleanup() - { - m_CleanupTimer.expires_from_now(boost::posix_time::seconds(DATAGRAM_SESSION_CLEANUP_INTERVAL)); - m_CleanupTimer.async_wait(std::bind(&DatagramDestination::HandleCleanUp, this, std::placeholders::_1)); - } + void DatagramDestination::ScheduleCleanup() + { + m_CleanupTimer.expires_from_now(boost::posix_time::seconds(DATAGRAM_SESSION_CLEANUP_INTERVAL)); + m_CleanupTimer.async_wait(std::bind(&DatagramDestination::HandleCleanUp, this, std::placeholders::_1)); + } - void DatagramDestination::HandleCleanUp(const boost::system::error_code & ecode) - { - if(ecode) - return; - std::lock_guard lock(m_SessionsMutex); - auto now = i2p::util::GetMillisecondsSinceEpoch(); - LogPrint(eLogDebug, "DatagramDestination: clean up sessions"); - std::vector expiredSessions; - // for each session ... - for (auto & e : m_Sessions) { - // check if expired - if(now - e.second->LastActivity() >= DATAGRAM_SESSION_MAX_IDLE) - expiredSessions.push_back(e.first); // we are expired - } - // for each expired session ... - for (auto & ident : expiredSessions) { - // remove the expired session - LogPrint(eLogInfo, "DatagramDestination: expiring idle session with ", ident.ToBase32()); - m_Sessions.erase(ident); - } - m_Owner->CleanupExpiredTags(); - ScheduleCleanup(); - } - - std::shared_ptr DatagramDestination::ObtainSession(const i2p::data::IdentHash & ident) - { - std::shared_ptr session = nullptr; - std::lock_guard lock(m_SessionsMutex); - auto itr = m_Sessions.find(ident); - if (itr == m_Sessions.end()) { - // not found, create new session - session = std::make_shared(m_Owner, ident); - m_Sessions[ident] = session; - } else { - session = itr->second; - } - return session; - } + void DatagramDestination::HandleCleanUp(const boost::system::error_code & ecode) + { + if(ecode) + return; + std::lock_guard lock(m_SessionsMutex); + auto now = i2p::util::GetMillisecondsSinceEpoch(); + LogPrint(eLogDebug, "DatagramDestination: clean up sessions"); + std::vector expiredSessions; + // for each session ... + for (auto & e : m_Sessions) { + // check if expired + if(now - e.second->LastActivity() >= DATAGRAM_SESSION_MAX_IDLE) + expiredSessions.push_back(e.first); // we are expired + } + // for each expired session ... + for (auto & ident : expiredSessions) { + // remove the expired session + LogPrint(eLogInfo, "DatagramDestination: expiring idle session with ", ident.ToBase32()); + m_Sessions.erase(ident); + } + m_Owner->CleanupExpiredTags(); + ScheduleCleanup(); + } + + std::shared_ptr DatagramDestination::ObtainSession(const i2p::data::IdentHash & ident) + { + std::shared_ptr session = nullptr; + std::lock_guard lock(m_SessionsMutex); + auto itr = m_Sessions.find(ident); + if (itr == m_Sessions.end()) { + // not found, create new session + session = std::make_shared(m_Owner, ident); + m_Sessions[ident] = session; + } else { + session = itr->second; + } + return session; + } - DatagramSession::DatagramSession(i2p::client::ClientDestination * localDestination, - const i2p::data::IdentHash & remoteIdent) : - m_LocalDestination(localDestination), - m_RemoteIdentity(remoteIdent), - m_LastUse(i2p::util::GetMillisecondsSinceEpoch()) - { - } + DatagramSession::DatagramSession(i2p::client::ClientDestination * localDestination, + const i2p::data::IdentHash & remoteIdent) : + m_LocalDestination(localDestination), + m_RemoteIdentity(remoteIdent), + m_LastUse(i2p::util::GetMillisecondsSinceEpoch ()), + m_LastPathChange(0), + m_LastSuccess(0) + { + } - void DatagramSession::SendMsg(std::shared_ptr msg) - { - // we used this session - m_LastUse = i2p::util::GetMillisecondsSinceEpoch(); - // schedule send - m_LocalDestination->GetService().post(std::bind(&DatagramSession::HandleSend, this, msg)); - } + void DatagramSession::SendMsg(std::shared_ptr msg) + { + // we used this session + m_LastUse = i2p::util::GetMillisecondsSinceEpoch(); + // schedule send + m_LocalDestination->GetService().post(std::bind(&DatagramSession::HandleSend, this, msg)); + } - void DatagramSession::HandleSend(std::shared_ptr msg) - { - // do we have a routing session? - if(m_RoutingSession) - { - // do we have a routing path ? - auto routingPath = m_RoutingSession->GetSharedRoutingPath(); - if(!routingPath) - { - LogPrint(eLogDebug, "DatagramSession: try getting new routing path"); - // no routing path, try getting one - routingPath = GetNextRoutingPath(); - if(routingPath) // remember the routing path if we got one - m_RoutingSession->SetSharedRoutingPath(routingPath); - } - // make sure we have a routing path - if (routingPath) - { - auto outboundTunnel = routingPath->outboundTunnel; - if (outboundTunnel) - { - if(outboundTunnel->IsEstablished()) - { - // we have a routing path and routing session and the outbound tunnel we are using is good - // wrap message with routing session and send down routing path's outbound tunnel wrapped for the IBGW - auto m = m_RoutingSession->WrapSingleMessage(msg); - routingPath->outboundTunnel->SendTunnelDataMsg({i2p::tunnel::TunnelMessageBlock{ - i2p::tunnel::eDeliveryTypeTunnel, - routingPath->remoteLease->tunnelGateway, routingPath->remoteLease->tunnelID, - m - }}); - return; - } - } - } - } - // we couldn't send so let's try resetting the routing path and updating lease set - ResetRoutingPath(); - UpdateLeaseSet(msg); - - } + void DatagramSession::HandleSend(std::shared_ptr msg) + { + // do we have a routing session? + if(m_RoutingSession) + { + // should we switch paths? + if(ShouldUpdateRoutingPath ()) + { + LogPrint(eLogDebug, "DatagramSession: try getting new routing path"); + // try switching paths + UpdateRoutingPath (GetNextRoutingPath ()); + } + auto routingPath = m_RoutingSession->GetSharedRoutingPath (); + // make sure we have a routing path + if (routingPath) + { + auto outboundTunnel = routingPath->outboundTunnel; + if (outboundTunnel) + { + if(outboundTunnel->IsEstablished()) + { + m_LastSuccess = i2p::util::GetMillisecondsSinceEpoch (); + // we have a routing path and routing session and the outbound tunnel we are using is good + // wrap message with routing session and send down routing path's outbound tunnel wrapped for the IBGW + auto m = m_RoutingSession->WrapSingleMessage(msg); + routingPath->outboundTunnel->SendTunnelDataMsg({i2p::tunnel::TunnelMessageBlock{ + i2p::tunnel::eDeliveryTypeTunnel, + routingPath->remoteLease->tunnelGateway, routingPath->remoteLease->tunnelID, + m + }}); + return; + } + } + } + } + auto now = i2p::util::GetMillisecondsSinceEpoch (); + // if this path looks dead reset the routing path since we didn't seem to be able to get a path in time + if (now - m_LastPathChange >= DATAGRAM_SESSION_PATH_TIMEOUT ) ResetRoutingPath(); + UpdateLeaseSet(msg); + + } - std::shared_ptr DatagramSession::GetNextRoutingPath() - { - std::shared_ptr outboundTunnel = nullptr; - std::shared_ptr routingPath = nullptr; - // get existing routing path if we have one - if(m_RoutingSession) - routingPath = m_RoutingSession->GetSharedRoutingPath(); - // do we have an existing outbound tunnel and routing path? - if(routingPath && routingPath->outboundTunnel) - { - // is the outbound tunnel we are using good? - if (routingPath->outboundTunnel->IsEstablished()) - { - // ya so let's stick with it - outboundTunnel = routingPath->outboundTunnel; - } - else - outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(routingPath->outboundTunnel); // no so we'll switch outbound tunnels - // don't reuse the old path as we are making a new one - routingPath = nullptr; - } - // do we have an outbound tunnel that works already ? - if(!outboundTunnel) - outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(); // no, let's get a new outbound tunnel as we probably just started + void DatagramSession::UpdateRoutingPath(const std::shared_ptr & path) + { + // we can't update routing path because we have no routing session + if(!m_RoutingSession) return; + // set routing path and update time we last updated the routing path + m_RoutingSession->SetSharedRoutingPath (path); + m_LastPathChange = i2p::util::GetMillisecondsSinceEpoch (); + } - if(outboundTunnel) - { - // get next available lease - auto lease = GetNextLease(); - if(lease) - { - // we have a valid lease to use and an outbound tunnel - // create new routing path - uint32_t now = i2p::util::GetSecondsSinceEpoch(); - routingPath = std::make_shared(i2p::garlic::GarlicRoutingPath{ - outboundTunnel, - lease, - 0, - now, - 0 - }); - } - } - return routingPath; - } + bool DatagramSession::ShouldUpdateRoutingPath() const + { + auto now = i2p::util::GetMillisecondsSinceEpoch (); + // we need to rotate paths becuase the routing path is too old + if (now - m_LastPathChange >= DATAGRAM_SESSION_PATH_SWITCH_INTERVAL) return true; + // our path looks dead so we need to rotate paths + if (now - m_LastSuccess >= DATAGRAM_SESSION_PATH_TIMEOUT) return true; + // if we have a routing session and routing path we don't need to switch paths + return m_RoutingSession != nullptr && m_RoutingSession->GetSharedRoutingPath () != nullptr; + } - void DatagramSession::ResetRoutingPath() - { - if(m_RoutingSession) - { - auto routingPath = m_RoutingSession->GetSharedRoutingPath(); - if(routingPath && routingPath->remoteLease) // we have a remote lease already specified and a routing path - { - // get outbound tunnel on this path - auto outboundTunnel = routingPath->outboundTunnel; - // is this outbound tunnel there and established - if (outboundTunnel && outboundTunnel->IsEstablished()) - m_InvalidIBGW.push_back(routingPath->remoteLease->tunnelGateway); // yes, let's mark remote lease as dead because the outbound tunnel seems fine - } - // reset the routing path - m_RoutingSession->SetSharedRoutingPath(nullptr); - } - } - std::shared_ptr DatagramSession::GetNextLease() - { - std::shared_ptr next = nullptr; - if(m_RemoteLeaseSet) - { - std::vector exclude; - for(const auto & ident : m_InvalidIBGW) - exclude.push_back(ident); - // find get all leases that are not in our ban list - auto leases = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding( [&exclude] (const i2p::data::Lease & l) -> bool { - if(exclude.size()) - { - auto end = std::end(exclude); - return std::find_if(exclude.begin(), end, [l] ( const i2p::data::IdentHash & ident) -> bool { - return ident == l.tunnelGateway; - }) != end; - } - else - return false; - }); - if(leases.size()) - { - // pick random valid next lease - uint32_t idx = rand() % leases.size(); - next = leases[idx]; - } - } - return next; - } - - void DatagramSession::UpdateLeaseSet(std::shared_ptr msg) - { - LogPrint(eLogInfo, "DatagramSession: updating lease set"); - m_LocalDestination->RequestDestination(m_RemoteIdentity, std::bind(&DatagramSession::HandleGotLeaseSet, this, std::placeholders::_1, msg)); - } + bool DatagramSession::ShouldSwitchLease() const + { + auto now = i2p::util::GetMillisecondsSinceEpoch (); + std::shared_ptr routingPath = nullptr; + std::shared_ptr currentLease = nullptr; + if(m_RoutingSession) + routingPath = m_RoutingSession->GetSharedRoutingPath (); + if(routingPath) + currentLease = routingPath->remoteLease; + if(currentLease) // if we have a lease return true if it's about to expire otherwise return false + return now - currentLease->ExpiresWithin( DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE ); + // we have no current lease, we should switch + return true; + } + + std::shared_ptr DatagramSession::GetNextRoutingPath() + { + std::shared_ptr outboundTunnel = nullptr; + std::shared_ptr routingPath = nullptr; + // get existing routing path if we have one + if(m_RoutingSession) + routingPath = m_RoutingSession->GetSharedRoutingPath(); + // do we have an existing outbound tunnel and routing path? + if(routingPath && routingPath->outboundTunnel) + { + // is the outbound tunnel we are using good? + if (routingPath->outboundTunnel->IsEstablished()) + { + // ya so let's stick with it + outboundTunnel = routingPath->outboundTunnel; + } + else + outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(routingPath->outboundTunnel); // no so we'll switch outbound tunnels + } + // do we have an outbound tunnel that works already ? + if(!outboundTunnel) + outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(); // no, let's get a new outbound tunnel as we probably just started - void DatagramSession::HandleGotLeaseSet(std::shared_ptr remoteIdent, std::shared_ptr msg) - { - if(remoteIdent) { - // update routing session - if(m_RoutingSession) - m_RoutingSession = nullptr; - m_RoutingSession = m_LocalDestination->GetRoutingSession(remoteIdent, true); - // clear invalid IBGW as we have a new lease set - m_InvalidIBGW.clear(); - m_RemoteLeaseSet = remoteIdent; - // send the message that was queued if it was provided - if(msg) - HandleSend(msg); - } - } + if(outboundTunnel) + { + std::shared_ptr lease = nullptr; + // should we switch leases ? + if (ShouldSwitchLease ()) + { + // yes, get next available lease + lease = GetNextLease(); + } + else if (routingPath) + { + // stick with the lease we have if we have one + lease = routingPath->remoteLease; + } + if(lease) + { + // we have a valid lease to use and an outbound tunnel + // create new routing path + uint32_t now = i2p::util::GetSecondsSinceEpoch(); + routingPath = std::make_shared(i2p::garlic::GarlicRoutingPath{ + outboundTunnel, + lease, + 0, + now, + 0 + }); + } + else // we don't have a new routing path to give + routingPath = nullptr; + } + return routingPath; + } + + void DatagramSession::ResetRoutingPath() + { + if(m_RoutingSession) + { + auto routingPath = m_RoutingSession->GetSharedRoutingPath(); + if(routingPath && routingPath->remoteLease) // we have a remote lease already specified and a routing path + { + // get outbound tunnel on this path + auto outboundTunnel = routingPath->outboundTunnel; + // is this outbound tunnel there and established + if (outboundTunnel && outboundTunnel->IsEstablished()) + m_InvalidIBGW.push_back(routingPath->remoteLease->tunnelGateway); // yes, let's mark remote lease as dead because the outbound tunnel seems fine + } + // reset the routing path + UpdateRoutingPath(nullptr); + } + } + + std::shared_ptr DatagramSession::GetNextLease() + { + auto now = i2p::util::GetMillisecondsSinceEpoch (); + std::shared_ptr next = nullptr; + if(m_RemoteLeaseSet) + { + std::vector exclude; + for(const auto & ident : m_InvalidIBGW) + exclude.push_back(ident); + // find get all leases that are not in our ban list and are not going to expire within our lease set handover window + fudge + auto leases = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding( [&exclude, now] (const i2p::data::Lease & l) -> bool { + if(exclude.size()) + { + auto end = std::end(exclude); + return std::find_if(exclude.begin(), end, [l, now] ( const i2p::data::IdentHash & ident) -> bool { + return ident == l.tunnelGateway || l.ExpiresWithin (DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE); + }) != end; + } + else + return l.ExpiresWithin (DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE); + }); + if(leases.size()) + { + // pick random valid next lease + uint32_t idx = rand() % leases.size(); + next = leases[idx]; + } + } + return next; + } + + void DatagramSession::UpdateLeaseSet(std::shared_ptr msg) + { + LogPrint(eLogInfo, "DatagramSession: updating lease set"); + m_LocalDestination->RequestDestination(m_RemoteIdentity, std::bind(&DatagramSession::HandleGotLeaseSet, this, std::placeholders::_1, msg)); + } + + void DatagramSession::HandleGotLeaseSet(std::shared_ptr remoteIdent, std::shared_ptr msg) + { + if(remoteIdent) + { + // update routing session + if(m_RoutingSession) + m_RoutingSession = nullptr; + m_RoutingSession = m_LocalDestination->GetRoutingSession(remoteIdent, true); + // clear invalid IBGW as we have a new lease set + m_InvalidIBGW.clear(); + m_RemoteLeaseSet = remoteIdent; + // send the message that was queued if it was provided + if(msg) + HandleSend(msg); + } + } } } diff --git a/Datagram.h b/Datagram.h index 887c82d2..fd1f290d 100644 --- a/Datagram.h +++ b/Datagram.h @@ -20,51 +20,70 @@ namespace client namespace datagram { - // seconds interval for cleanup timer - const int DATAGRAM_SESSION_CLEANUP_INTERVAL = 3; - // milliseconds for max session idle time (10 minutes) - const uint64_t DATAGRAM_SESSION_MAX_IDLE = 3600 * 1000; + // seconds interval for cleanup timer + const int DATAGRAM_SESSION_CLEANUP_INTERVAL = 3; + // milliseconds for max session idle time + const uint64_t DATAGRAM_SESSION_MAX_IDLE = 10 * 60 * 1000; + // milliseconds for how long we try sticking to a dead routing path before trying to switch + const uint64_t DATAGRAM_SESSION_PATH_TIMEOUT = 5000; + // milliseconds interval a routing path is used before switching + const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 60 * 1000; + // milliseconds before lease expire should we try switching leases + const uint64_t DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW = 10 * 1000; + // milliseconds fudge factor for leases handover + const uint64_t DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE = 1000; - - class DatagramSession - { - public: - DatagramSession(i2p::client::ClientDestination * localDestination, - const i2p::data::IdentHash & remoteIdent); + + class DatagramSession + { + public: + DatagramSession(i2p::client::ClientDestination * localDestination, + const i2p::data::IdentHash & remoteIdent); - /** send an i2np message to remote endpoint for this session */ - void SendMsg(std::shared_ptr msg); - /** get the last time in milliseconds for when we used this datagram session */ - uint64_t LastActivity() const { return m_LastUse; } - private: + /** send an i2np message to remote endpoint for this session */ + void SendMsg(std::shared_ptr msg); + /** get the last time in milliseconds for when we used this datagram session */ + uint64_t LastActivity() const { return m_LastUse; } + private: - /** get next usable routing path, try reusing outbound tunnels */ - std::shared_ptr GetNextRoutingPath(); - /** - * mark current routing path as invalid and clear it - * if the outbound tunnel we were using was okay don't use the IBGW in the routing path's lease next time - */ - void ResetRoutingPath(); + /** update our routing path we are using, mark that we have changed paths */ + void UpdateRoutingPath(const std::shared_ptr & path); - /** get next usable lease, does not fetch or update if expired or have no lease set */ - std::shared_ptr GetNextLease(); - - void HandleSend(std::shared_ptr msg); - void HandleGotLeaseSet(std::shared_ptr remoteIdent, - std::shared_ptr msg); - void UpdateLeaseSet(std::shared_ptr msg=nullptr); - - private: - i2p::client::ClientDestination * m_LocalDestination; - i2p::data::IdentHash m_RemoteIdentity; - std::shared_ptr m_RoutingSession; - // Ident hash of IBGW that are invalid - std::vector m_InvalidIBGW; - std::shared_ptr m_RemoteLeaseSet; - uint64_t m_LastUse; - }; - - const size_t MAX_DATAGRAM_SIZE = 32768; + /** return true if we should switch routing paths because of path lifetime or timeout otherwise false */ + bool ShouldUpdateRoutingPath() const; + + /** return true if we should switch the lease for out routing path otherwise return false */ + bool ShouldSwitchLease() const; + + /** get next usable routing path, try reusing outbound tunnels */ + std::shared_ptr GetNextRoutingPath(); + /** + * mark current routing path as invalid and clear it + * if the outbound tunnel we were using was okay don't use the IBGW in the routing path's lease next time + */ + void ResetRoutingPath(); + + /** get next usable lease, does not fetch or update if expired or have no lease set */ + std::shared_ptr GetNextLease(); + + void HandleSend(std::shared_ptr msg); + void HandleGotLeaseSet(std::shared_ptr remoteIdent, + std::shared_ptr msg); + void UpdateLeaseSet(std::shared_ptr msg=nullptr); + + private: + i2p::client::ClientDestination * m_LocalDestination; + i2p::data::IdentHash m_RemoteIdentity; + std::shared_ptr m_RoutingSession; + // Ident hash of IBGW that are invalid + std::vector m_InvalidIBGW; + std::shared_ptr m_RemoteLeaseSet; + uint64_t m_LastUse; + uint64_t m_LastPathChange; + uint64_t m_LastSuccess; + }; + + const size_t MAX_DATAGRAM_SIZE = 32768; class DatagramDestination { typedef std::function Receiver; @@ -82,15 +101,15 @@ namespace datagram void SetReceiver (const Receiver& receiver, uint16_t port) { std::lock_guard lock(m_ReceiversMutex); m_ReceiversByPorts[port] = receiver; }; void ResetReceiver (uint16_t port) { std::lock_guard lock(m_ReceiversMutex); m_ReceiversByPorts.erase (port); }; - + private: - // clean up after next tick - void ScheduleCleanup(); - - // clean up stale sessions and expire tags - void HandleCleanUp(const boost::system::error_code & ecode); - - std::shared_ptr ObtainSession(const i2p::data::IdentHash & ident); + // clean up after next tick + void ScheduleCleanup(); + + // clean up stale sessions and expire tags + void HandleCleanUp(const boost::system::error_code & ecode); + + std::shared_ptr ObtainSession(const i2p::data::IdentHash & ident); std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); diff --git a/LeaseSet.cpp b/LeaseSet.cpp index ab0407cc..04dc77c5 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -163,9 +163,10 @@ namespace data return ExtractTimestamp (buf, len) > ExtractTimestamp (m_Buffer, m_BufferLen); } - bool LeaseSet::ExpiresSoon(const uint64_t dlt) const + bool LeaseSet::ExpiresSoon(const uint64_t dlt, const uint64_t fudge) const { auto now = i2p::util::GetMillisecondsSinceEpoch (); + if (fudge) now += rand() % fudge; if (now >= m_ExpirationTime) return true; return m_ExpirationTime - now <= dlt; } diff --git a/LeaseSet.h b/LeaseSet.h index 9b6b7844..ed21eccd 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -7,6 +7,7 @@ #include #include #include "Identity.h" +#include "Timestamp.h" namespace i2p { @@ -24,7 +25,13 @@ namespace data IdentHash tunnelGateway; uint32_t tunnelID; uint64_t endDate; // 0 means invalid - bool isUpdated; // trasient + bool isUpdated; // trasient + /* return true if this lease expires within t millisecond + fudge factor */ + bool ExpiresWithin( const uint64_t t, const uint64_t fudge = 1000 ) const { + auto expire = i2p::util::GetMillisecondsSinceEpoch (); + if(fudge) expire += rand() % fudge; + return expire - endDate >= t; + } }; struct LeaseCmp @@ -63,7 +70,7 @@ namespace data bool IsExpired () const; bool IsEmpty () const { return m_Leases.empty (); }; uint64_t GetExpirationTime () const { return m_ExpirationTime; }; - bool ExpiresSoon(const uint64_t dlt=1000 * 5) const ; + bool ExpiresSoon(const uint64_t dlt=1000 * 5, const uint64_t fudge = 0) const ; bool operator== (const LeaseSet& other) const { return m_BufferLen == other.m_BufferLen && !memcmp (m_Buffer, other.m_Buffer, m_BufferLen); }; diff --git a/Streaming.cpp b/Streaming.cpp index 0908ff4a..308ba02b 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -268,12 +268,12 @@ namespace stream } } auto sentPacket = *it; - uint64_t rtt = ts - sentPacket->sendTime; - if(ts < sentPacket->sendTime) - { - LogPrint(eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime); - rtt = 1; - } + uint64_t rtt = ts - sentPacket->sendTime; + if(ts < sentPacket->sendTime) + { + LogPrint(eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime); + rtt = 1; + } m_RTT = (m_RTT*seqn + rtt)/(seqn + 1); m_RTO = m_RTT*1.5; // TODO: implement it better LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt, " sentTime=", sentPacket->sendTime); From fa1021df5942c16108ce6bdf2d9b370617a0bfd6 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 13:58:34 -0400 Subject: [PATCH 1837/6300] add webui for udp tunnels --- ClientContext.cpp | 16 +++++++++++++++ ClientContext.h | 4 +++- Datagram.cpp | 33 ++++++++++++++++++++++++++++++ Datagram.h | 21 +++++++++++++++++++ HTTPServer.cpp | 31 ++++++++++++++++++++++++++++ I2PTunnel.cpp | 52 +++++++++++++++++++++++++++++++++++++++++++++++ I2PTunnel.h | 27 ++++++++++++++++++++++-- 7 files changed, 181 insertions(+), 3 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 3c4d63c0..6ef7ec60 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -276,6 +276,22 @@ namespace client return success; } + std::vector ClientContext::GetForwardInfosFor(const i2p::data::IdentHash & destination) + { + std::lock_guard lock(m_ForwardsMutex); + for(auto & c : m_ClientForwards) + { + if (c.second->IsLocalDestination(destination)) + return c.second->GetSessions(); + } + for(auto & s : m_ServerForwards) + { + if(std::get<0>(s.first) == destination) + return s.second->GetSessions(); + } + return {}; + } + std::shared_ptr ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, const std::map * params) { diff --git a/ClientContext.h b/ClientContext.h index 87dbec0f..7faf04ca 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -66,7 +66,9 @@ namespace client AddressBook& GetAddressBook () { return m_AddressBook; }; const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; - + + std::vector GetForwardInfosFor(const i2p::data::IdentHash & destination); + private: void ReadTunnels (); diff --git a/Datagram.cpp b/Datagram.cpp index 37559922..42354945 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -165,6 +165,16 @@ namespace datagram return session; } + std::shared_ptr DatagramDestination::GetInfoForRemote(const i2p::data::IdentHash & remote) + { + std::lock_guard lock(m_SessionsMutex); + for ( auto & item : m_Sessions) + { + if(item.first == remote) return std::make_shared(item.second->GetSessionInfo()); + } + return nullptr; + } + DatagramSession::DatagramSession(i2p::client::ClientDestination * localDestination, const i2p::data::IdentHash & remoteIdent) : m_LocalDestination(localDestination), @@ -183,6 +193,29 @@ namespace datagram m_LocalDestination->GetService().post(std::bind(&DatagramSession::HandleSend, this, msg)); } + DatagramSession::Info DatagramSession::GetSessionInfo() const + { + if(!m_RoutingSession) + return DatagramSession::Info{nullptr, nullptr, m_LastUse, m_LastSuccess}; + + auto routingPath = m_RoutingSession->GetSharedRoutingPath(); + if (!routingPath) + return DatagramSession::Info{nullptr, nullptr, m_LastUse, m_LastSuccess}; + auto lease = routingPath->remoteLease; + auto tunnel = routingPath->outboundTunnel; + if(lease) + { + if(tunnel) + return DatagramSession::Info{new i2p::data::IdentHash(lease->tunnelGateway), new i2p::data::IdentHash(tunnel->GetEndpointIdentHash()), m_LastUse, m_LastSuccess}; + else + return DatagramSession::Info{new i2p::data::IdentHash(lease->tunnelGateway), nullptr, m_LastUse, m_LastSuccess}; + } + else if(tunnel) + return DatagramSession::Info{nullptr, new i2p::data::IdentHash(tunnel->GetEndpointIdentHash()), m_LastUse, m_LastSuccess}; + else + return DatagramSession::Info{nullptr, nullptr, m_LastUse, m_LastSuccess}; + } + void DatagramSession::HandleSend(std::shared_ptr msg) { // do we have a routing session? diff --git a/Datagram.h b/Datagram.h index fd1f290d..a44133f7 100644 --- a/Datagram.h +++ b/Datagram.h @@ -44,6 +44,24 @@ namespace datagram void SendMsg(std::shared_ptr msg); /** get the last time in milliseconds for when we used this datagram session */ uint64_t LastActivity() const { return m_LastUse; } + /** get the last time in milliseconds when we successfully sent data */ + uint64_t LastSuccess() const { return m_LastSuccess; } + struct Info + { + const i2p::data::IdentHash * IBGW; + const i2p::data::IdentHash * OBEP; + const uint64_t activity; + const uint64_t success; + ~Info() + { + if(IBGW) delete IBGW; + if(OBEP) delete OBEP; + } + }; + + Info GetSessionInfo() const; + + private: /** update our routing path we are using, mark that we have changed paths */ @@ -90,6 +108,7 @@ namespace datagram public: + DatagramDestination (std::shared_ptr owner); ~DatagramDestination (); @@ -101,6 +120,8 @@ namespace datagram void SetReceiver (const Receiver& receiver, uint16_t port) { std::lock_guard lock(m_ReceiversMutex); m_ReceiversByPorts[port] = receiver; }; void ResetReceiver (uint16_t port) { std::lock_guard lock(m_ReceiversMutex); m_ReceiversByPorts.erase (port); }; + + std::shared_ptr GetInfoForRemote(const i2p::data::IdentHash & remote); private: // clean up after next tick diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 309e3f6b..56756277 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -337,7 +337,38 @@ namespace http { s << "

    "; s << ""; s << "
    \r\n" << std::endl; + } + s << "
    Streams
    StreamIDDestination
    " << it->GetSendStreamID () << "" << i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()) << "" << (int)it->GetStatus () << "
    " << it->GetWindowSize () << "" << (int)it->GetStatus () << "

    \r\n"; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + auto forward = i2p::client::context.GetForwardInfosFor(dest->GetIdentHash()); + for (auto & info : forward) + { + s << ""; + s << ""; + s << ""; + + s << ""; + s << ""; + auto sec = std::chrono::duration >( std::chrono::milliseconds(info.idle) ); + s << ""; + s << "
    \r\n"; } + s << "
    Forwards
    Remote DestinationIBGWOBEPUDP ConverstationIdle Time
    " << info.RemoteIdent.ToBase32() << ""; + if(info.CurrentIBGW) + s << info.CurrentIBGW->ToBase64(); + else + s << "(none)"; + s << ""; + if(info.CurrentOBEP) + s << info.CurrentOBEP->ToBase64(); + else + s << "(none)"; + s << "" << info.LocalEndpoint << " ⇄ " << info.RemoteEndpoint << "" << sec.count() << " seconds
    \r\n"; } } diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 087221a3..386c0cd2 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -631,6 +631,30 @@ namespace client void I2PUDPServerTunnel::Start() { m_LocalDest->Start(); } + + std::vector I2PUDPServerTunnel::GetSessions() + { + std::vector sessions; + auto localident = m_LocalDest->GetIdentHash(); + std::lock_guard lock(m_SessionsMutex); + for ( UDPSession * s : m_Sessions ) + { + if (!s->m_Destination) continue; + auto info = s->m_Destination->GetInfoForRemote(s->Identity); + if(!info) continue; + sessions.push_back(DatagramSessionInfo{ + m_Name, + localident, + s->Identity, + info->IBGW, + info->OBEP, + s->IPSocket.local_endpoint(), + s->SendEndpoint, + info->success + }); + } + return sessions; + } I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, @@ -662,6 +686,34 @@ namespace client m_ResolveThread = new std::thread(std::bind(&I2PUDPClientTunnel::TryResolving, this)); } + std::vector I2PUDPClientTunnel::GetSessions() + { + std::vector infos; + if(m_Session && m_LocalDest) + { + auto localident = m_LocalDest->GetIdentHash(); + auto s = m_Session; + if (s->m_Destination) + { + auto info = m_Session->m_Destination->GetInfoForRemote(s->Identity); + if(!info) + { + infos.push_back(DatagramSessionInfo{ + m_Name, + localident, + s->Identity, + info->IBGW, + info->OBEP, + s->IPSocket.local_endpoint(), + s->SendEndpoint, + info->success + }); + } + } + } + return infos; + } + void I2PUDPClientTunnel::TryResolving() { LogPrint(eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); m_RemoteIdent = new i2p::data::IdentHash; diff --git a/I2PTunnel.h b/I2PTunnel.h index 76bfc36b..7fe58574 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -137,7 +137,28 @@ namespace client /** max size for i2p udp */ const size_t I2P_UDP_MAX_MTU = i2p::datagram::MAX_DATAGRAM_SIZE; - + + /** read only info about a datagram session */ + struct DatagramSessionInfo + { + /** the name of this forward */ + const std::string Name; + /** ident hash of local destination */ + const i2p::data::IdentHash LocalIdent; + /** ident hash of remote destination */ + const i2p::data::IdentHash RemoteIdent; + /** ident hash of IBGW in use currently in this session or nullptr if none is set */ + const i2p::data::IdentHash * CurrentIBGW; + /** ident hash of OBEP in use for this session or nullptr if none is set */ + const i2p::data::IdentHash * CurrentOBEP; + /** i2p router's udp endpoint */ + const boost::asio::ip::udp::endpoint LocalEndpoint; + /** client's udp endpoint */ + const boost::asio::ip::udp::endpoint RemoteEndpoint; + /** how long has this converstation been idle in ms */ + const uint64_t idle; + }; + struct UDPSession { i2p::datagram::DatagramDestination * m_Destination; @@ -174,6 +195,7 @@ namespace client void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); void Start(); const char * GetName() const { return m_Name.c_str(); } + std::vector GetSessions(); private: void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); UDPSession * ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); @@ -196,7 +218,8 @@ namespace client ~I2PUDPClientTunnel(); void Start(); const char * GetName() const { return m_Name.c_str(); } - + std::vector GetSessions(); + bool IsLocalDestination(const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } private: void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void TryResolving(); From 571c630d9360ddc7621c2e415f1f66c0ea78c8a2 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 14:37:36 -0400 Subject: [PATCH 1838/6300] try creating routing session if not present --- Datagram.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Datagram.cpp b/Datagram.cpp index 42354945..8b4c440b 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -260,7 +260,8 @@ namespace datagram void DatagramSession::UpdateRoutingPath(const std::shared_ptr & path) { - // we can't update routing path because we have no routing session + if(m_RoutingSession == nullptr && m_RemoteLeaseSet) + m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); if(!m_RoutingSession) return; // set routing path and update time we last updated the routing path m_RoutingSession->SetSharedRoutingPath (path); From 8cdd3a0abbf3fc779f772611e29f7755334be85a Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 14:42:27 -0400 Subject: [PATCH 1839/6300] update routing path when we get a new lease set --- Datagram.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Datagram.cpp b/Datagram.cpp index 8b4c440b..1d1f5d2d 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -417,6 +417,8 @@ namespace datagram // clear invalid IBGW as we have a new lease set m_InvalidIBGW.clear(); m_RemoteLeaseSet = remoteIdent; + // update routing path + UpdateRoutingPath(GetNextRoutingPath()); // send the message that was queued if it was provided if(msg) HandleSend(msg); From 7cc805b2034a0b49904990051a397b0ff50f5aac Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 14:56:51 -0400 Subject: [PATCH 1840/6300] update datagram session logic --- Datagram.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 1d1f5d2d..0802295a 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -146,7 +146,6 @@ namespace datagram LogPrint(eLogInfo, "DatagramDestination: expiring idle session with ", ident.ToBase32()); m_Sessions.erase(ident); } - m_Owner->CleanupExpiredTags(); ScheduleCleanup(); } @@ -174,7 +173,7 @@ namespace datagram } return nullptr; } - + DatagramSession::DatagramSession(i2p::client::ClientDestination * localDestination, const i2p::data::IdentHash & remoteIdent) : m_LocalDestination(localDestination), @@ -215,9 +214,14 @@ namespace datagram else return DatagramSession::Info{nullptr, nullptr, m_LastUse, m_LastSuccess}; } - + void DatagramSession::HandleSend(std::shared_ptr msg) { + if(!m_RoutingSession) + { + // try to get one + if(m_RemoteLeaseSet) m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); + } // do we have a routing session? if(m_RoutingSession) { @@ -226,7 +230,11 @@ namespace datagram { LogPrint(eLogDebug, "DatagramSession: try getting new routing path"); // try switching paths - UpdateRoutingPath (GetNextRoutingPath ()); + auto path = GetNextRoutingPath(); + if(path) + UpdateRoutingPath (path); + else + ResetRoutingPath(); } auto routingPath = m_RoutingSession->GetSharedRoutingPath (); // make sure we have a routing path @@ -282,7 +290,6 @@ namespace datagram bool DatagramSession::ShouldSwitchLease() const { - auto now = i2p::util::GetMillisecondsSinceEpoch (); std::shared_ptr routingPath = nullptr; std::shared_ptr currentLease = nullptr; if(m_RoutingSession) @@ -290,7 +297,7 @@ namespace datagram if(routingPath) currentLease = routingPath->remoteLease; if(currentLease) // if we have a lease return true if it's about to expire otherwise return false - return now - currentLease->ExpiresWithin( DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE ); + return currentLease->ExpiresWithin( DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE ); // we have no current lease, we should switch return true; } @@ -418,7 +425,11 @@ namespace datagram m_InvalidIBGW.clear(); m_RemoteLeaseSet = remoteIdent; // update routing path - UpdateRoutingPath(GetNextRoutingPath()); + auto path = GetNextRoutingPath(); + if (path) + UpdateRoutingPath(path); + else + ResetRoutingPath(); // send the message that was queued if it was provided if(msg) HandleSend(msg); From 75981491a7b5a197d4730d54a94c97e481517806 Mon Sep 17 00:00:00 2001 From: libre-net-society Date: Sat, 3 Sep 2016 22:23:48 +0300 Subject: [PATCH 1841/6300] adding usage docs --- docs/index.rst | 1 + docs/usage.md | 130 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 docs/usage.md diff --git a/docs/index.rst b/docs/index.rst index 13cd9edb..5507b075 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -35,5 +35,6 @@ Contents: build_notes_android configuration family + usage diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 00000000..cb678cb4 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,130 @@ +Usage and tutorials +=================== + + +i2pd can be used for: + +* [anonymous websites](#browsing-and-hosting-websites) +* [anonymous chats](#using-and-hosting-chat-servers) +* [anonymous file sharing](#file-sharing) + +and many more. + +## Browsing and hosting websites + +### Browse anonymous websites + +To browse anonymous websites inside Invisible Internet, configure your web browser to use HTTP proxy 127.0.0.1:4444 (available by default in i2pd). + +In Firefox: Preferences -> Advanced -> Network tab -> Connection Settings -> choose Manual proxy configuration, Enter HTTP proxy 127.0.0.1, Port 4444 + +In Chromium: run chromium executable with key + + chromium --proxy-server="http://127.0.0.1:4444" + +Note that if you wish to stay anonymous too you'll need to tune your browser for better privacy. Do your own research, [can start here](http://www.howtogeek.com/102032/how-to-optimize-mozilla-firefox-for-maximum-privacy/). + +Big list of Invisible Internet websites can be found at [identiguy.i2p](http://identiguy.i2p). + +### Host anonymous website + + +If you wish to run your own website in Invisible Internet, follow those steps: + +1) Run your webserver and find out which host:port it uses (for example, 127.0.0.1:8080). + +2) Configure i2pd to create HTTP server tunnel. Put in your ~/.i2pd/tunnels.conf file: + + [anon-website] + type = http + host = 127.0.0.1 + port = 8080 + keys = anon-website.dat + +3) Restart i2pd. + +4) Find b32 destination of your website. + +Go to webconsole -> [I2P tunnels page](http://127.0.0.1:7070/?page=i2p_tunnels). Look for Sever tunnels and you will see address like \.b32.i2p next to anon-website. + +Website is now available in Invisible Internet by visiting this address. + +5) (Optional) Register short and rememberable .i2p domain on [inr.i2p](http://inr.i2p). + + +## Using and hosting chat servers + +### Running anonymous IRC server + +1) Run your IRC server software and find out which host:port it uses (for example, 127.0.0.1:5555). + +For small private IRC servers you can use [miniircd](https://github.com/jrosdahl/miniircd), for large public networks [UnreadIRCd](https://www.unrealircd.org/). + +2) Configure i2pd to create IRC server tunnel. + +Simplest case, if your server does not support WebIRC, add this to ~/.i2pd/tunnels.conf: + + [anon-chatserver] + type = irc + host = 127.0.0.1 + port = 5555 + keys = chatserver-key.dat + +And that is it. + +Alternatively, if your IRC server supports WebIRC, for example, UnreadIRCd, put this into UnrealIRCd config: + + webirc { + mask 127.0.0.1; + password your_password; + }; + +Also change line: + + modes-on-connect "+ixw"; + +to + + modes-on-connect "+iw"; + +And this in ~/.i2pd/tunnels.conf: + + [anon-chatserver] + type = irc + host = 127.0.0.1 + port = 5555 + keys = chatserver-key.dat + webircpassword = your_password + +3) Restart i2pd. + +4) Find b32 destination of your anonymous IRC server. + +Go to webconsole -> [I2P tunnels page](http://127.0.0.1:7070/?page=i2p_tunnels). Look for Sever tunnels and you will see address like \.b32.i2p next to anon-chatserver. + +Clients will use this address to connect to your server anonymously. + +### Connect to anonymous IRC server + +To connect to IRC server at *walker.i2p*, add this to ~/.i2pd/tunnels.conf: + + [IRC2] + type = client + address = 127.0.0.1 + port = 6669 + destination = walker.i2p + #keys = walker-keys.dat + +Restart i2pd, then connect to irc://127.0.0.1:6669 with your IRC client. + +## File sharing + +You can share and download torrents with [Transmission-I2P](https://github.com/l-n-s/transmission-i2p). + +Alternative torrent-clients are [Robert](http://en.wikipedia.org/wiki/Robert_%28P2P_Software%29) and [Vuze](https://en.wikipedia.org/wiki/Vuze). + +Robert uses BOB protocol, i2pd must be run with parameter --bob.enabled=true. + +Vuze uses I2CP protocol, i2pd must be run with parameter --i2cp.enabled=true. + +Also, visit [postman tracker](http://tracker2.postman.i2p). From 682334d844205154add31dbc2afd1f3285c43013 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 15:35:32 -0400 Subject: [PATCH 1842/6300] fix typo --- LeaseSet.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LeaseSet.h b/LeaseSet.h index ed21eccd..95d8bf90 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -30,7 +30,7 @@ namespace data bool ExpiresWithin( const uint64_t t, const uint64_t fudge = 1000 ) const { auto expire = i2p::util::GetMillisecondsSinceEpoch (); if(fudge) expire += rand() % fudge; - return expire - endDate >= t; + return endDate - expire >= t; } }; From 68b0775e4bd431334a3975eb12929738d3566aff Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 15:35:42 -0400 Subject: [PATCH 1843/6300] update datagram path logic --- Datagram.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Datagram.cpp b/Datagram.cpp index 0802295a..1a97afc5 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -221,6 +221,11 @@ namespace datagram { // try to get one if(m_RemoteLeaseSet) m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); + else + { + UpdateLeaseSet(msg); + return; + } } // do we have a routing session? if(m_RoutingSession) @@ -261,7 +266,7 @@ namespace datagram } auto now = i2p::util::GetMillisecondsSinceEpoch (); // if this path looks dead reset the routing path since we didn't seem to be able to get a path in time - if (now - m_LastPathChange >= DATAGRAM_SESSION_PATH_TIMEOUT ) ResetRoutingPath(); + if (m_LastPathChange && now - m_LastPathChange >= DATAGRAM_SESSION_PATH_TIMEOUT ) ResetRoutingPath(); UpdateLeaseSet(msg); } From 783c0c7c7ba63db4b9773add67ed73c1754cc14e Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 15:53:28 -0400 Subject: [PATCH 1844/6300] update datagram lease selection --- Datagram.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 1a97afc5..3d5dec8b 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -341,9 +341,16 @@ namespace datagram } else if (routingPath) { - // stick with the lease we have if we have one - lease = routingPath->remoteLease; + if(routingPath->remoteLease) + { + if(routingPath->remoteLease->ExpiresSoon()) + lease = GetNextLease(); + else + lease = routingPath->remoteLease; + } } + else + lease = GetNextLease(); if(lease) { // we have a valid lease to use and an outbound tunnel @@ -408,6 +415,8 @@ namespace datagram uint32_t idx = rand() % leases.size(); next = leases[idx]; } + else + LogPrint(eLogWarning, "DatagramDestination: no leases to use"); } return next; } From 7ea8509dfe1583bfa8834812791bb885f3c858b6 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 15:54:22 -0400 Subject: [PATCH 1845/6300] fix typo --- Datagram.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Datagram.cpp b/Datagram.cpp index 3d5dec8b..ba365cd5 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -343,7 +343,7 @@ namespace datagram { if(routingPath->remoteLease) { - if(routingPath->remoteLease->ExpiresSoon()) + if(routingPath->remoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE)) lease = GetNextLease(); else lease = routingPath->remoteLease; From d37a790b57aaa73191fafb8d014bb23d10e1be6c Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 15:58:52 -0400 Subject: [PATCH 1846/6300] fix another typo --- Datagram.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index ba365cd5..ba010639 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -403,8 +403,8 @@ namespace datagram { auto end = std::end(exclude); return std::find_if(exclude.begin(), end, [l, now] ( const i2p::data::IdentHash & ident) -> bool { - return ident == l.tunnelGateway || l.ExpiresWithin (DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE); - }) != end; + return ident == l.tunnelGateway; + }) != end || l.ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE); } else return l.ExpiresWithin (DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE); From 7e99be12b0c90ad30b8c103b7d8c56e2210f6e5a Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 16:03:38 -0400 Subject: [PATCH 1847/6300] fix typo --- Datagram.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index ba010639..5fd12b28 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -399,15 +399,17 @@ namespace datagram exclude.push_back(ident); // find get all leases that are not in our ban list and are not going to expire within our lease set handover window + fudge auto leases = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding( [&exclude, now] (const i2p::data::Lease & l) -> bool { + if(l.ExpiresWithin (DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE)) + return true; if(exclude.size()) { auto end = std::end(exclude); return std::find_if(exclude.begin(), end, [l, now] ( const i2p::data::IdentHash & ident) -> bool { return ident == l.tunnelGateway; - }) != end || l.ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE); + }) != end; } else - return l.ExpiresWithin (DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE); + return false; }); if(leases.size()) { From 10ffd5c1abd1168073e9c30b9a3205d4a3628ac4 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 16:06:14 -0400 Subject: [PATCH 1848/6300] don't check for expired lease --- Datagram.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 5fd12b28..adecb41f 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -399,8 +399,6 @@ namespace datagram exclude.push_back(ident); // find get all leases that are not in our ban list and are not going to expire within our lease set handover window + fudge auto leases = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding( [&exclude, now] (const i2p::data::Lease & l) -> bool { - if(l.ExpiresWithin (DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE)) - return true; if(exclude.size()) { auto end = std::end(exclude); From 2f61dd1c41b768f82aec57ae0a468065d5f88e55 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 16:12:43 -0400 Subject: [PATCH 1849/6300] fix double free --- Datagram.cpp | 6 +++--- Tag.h | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index adecb41f..a3da6527 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -205,12 +205,12 @@ namespace datagram if(lease) { if(tunnel) - return DatagramSession::Info{new i2p::data::IdentHash(lease->tunnelGateway), new i2p::data::IdentHash(tunnel->GetEndpointIdentHash()), m_LastUse, m_LastSuccess}; + return DatagramSession::Info{new i2p::data::IdentHash(lease->tunnelGateway.data()), new i2p::data::IdentHash(tunnel->GetEndpointIdentHash().data()), m_LastUse, m_LastSuccess}; else - return DatagramSession::Info{new i2p::data::IdentHash(lease->tunnelGateway), nullptr, m_LastUse, m_LastSuccess}; + return DatagramSession::Info{new i2p::data::IdentHash(lease->tunnelGateway.data()), nullptr, m_LastUse, m_LastSuccess}; } else if(tunnel) - return DatagramSession::Info{nullptr, new i2p::data::IdentHash(tunnel->GetEndpointIdentHash()), m_LastUse, m_LastSuccess}; + return DatagramSession::Info{nullptr, new i2p::data::IdentHash(tunnel->GetEndpointIdentHash().data()), m_LastUse, m_LastSuccess}; else return DatagramSession::Info{nullptr, nullptr, m_LastUse, m_LastSuccess}; } diff --git a/Tag.h b/Tag.h index 4b393c57..0861cb8e 100644 --- a/Tag.h +++ b/Tag.h @@ -50,6 +50,8 @@ namespace data { return true; } + const uint8_t * data() const { return m_Buf; } + /** fill with a value */ void Fill(uint8_t c) { From d336d920e8b8cdf5351df681f02bc5d2e25789c0 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 16:16:16 -0400 Subject: [PATCH 1850/6300] fix typo --- I2PTunnel.cpp | 2 +- Tag.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 386c0cd2..688866c8 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -696,7 +696,7 @@ namespace client if (s->m_Destination) { auto info = m_Session->m_Destination->GetInfoForRemote(s->Identity); - if(!info) + if(info) { infos.push_back(DatagramSessionInfo{ m_Name, diff --git a/Tag.h b/Tag.h index 0861cb8e..92e2f1a5 100644 --- a/Tag.h +++ b/Tag.h @@ -20,7 +20,7 @@ namespace data { { public: - Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); }; + Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); }; Tag (const Tag& ) = default; #ifndef _WIN32 // FIXME!!! msvs 2013 can't compile it Tag (Tag&& ) = default; From 82f46464f3afa7dde91a37677a1f23dbde9e2362 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 16:29:50 -0400 Subject: [PATCH 1851/6300] prevent double free --- Datagram.cpp | 6 +++--- Datagram.h | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index a3da6527..2247dff1 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -205,12 +205,12 @@ namespace datagram if(lease) { if(tunnel) - return DatagramSession::Info{new i2p::data::IdentHash(lease->tunnelGateway.data()), new i2p::data::IdentHash(tunnel->GetEndpointIdentHash().data()), m_LastUse, m_LastSuccess}; + return DatagramSession::Info{lease->tunnelGateway, tunnel->GetEndpointIdentHash(), m_LastUse, m_LastSuccess}; else - return DatagramSession::Info{new i2p::data::IdentHash(lease->tunnelGateway.data()), nullptr, m_LastUse, m_LastSuccess}; + return DatagramSession::Info{lease->tunnelGateway, nullptr, m_LastUse, m_LastSuccess}; } else if(tunnel) - return DatagramSession::Info{nullptr, new i2p::data::IdentHash(tunnel->GetEndpointIdentHash().data()), m_LastUse, m_LastSuccess}; + return DatagramSession::Info{nullptr, tunnel->GetEndpointIdentHash(), m_LastUse, m_LastSuccess}; else return DatagramSession::Info{nullptr, nullptr, m_LastUse, m_LastSuccess}; } diff --git a/Datagram.h b/Datagram.h index a44133f7..add946f0 100644 --- a/Datagram.h +++ b/Datagram.h @@ -52,6 +52,12 @@ namespace datagram const i2p::data::IdentHash * OBEP; const uint64_t activity; const uint64_t success; + Info() : IBGW(nullptr), OBEP(nullptr), activity(0), success(0) {} + Info(const i2p::data::IdentHash & ibgw, const i2p::data::IdentHash & obep, const uint64_t a, const uint64_t s) : + IBGW(new i2p::data::IdentHash(ibgw.data())), + OBEP(new i2p::data::IdentHash(obep.data())), + activity(a), + success(s) {} ~Info() { if(IBGW) delete IBGW; From f0bc2a3645466136483aee10f6df20710a08e872 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 16:43:02 -0400 Subject: [PATCH 1852/6300] add null checks --- Datagram.cpp | 12 ++++++------ Datagram.h | 11 +++++++---- HTTPServer.cpp | 4 ++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 2247dff1..14f9f385 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -195,24 +195,24 @@ namespace datagram DatagramSession::Info DatagramSession::GetSessionInfo() const { if(!m_RoutingSession) - return DatagramSession::Info{nullptr, nullptr, m_LastUse, m_LastSuccess}; + return DatagramSession::Info(nullptr, nullptr, m_LastUse, m_LastSuccess); auto routingPath = m_RoutingSession->GetSharedRoutingPath(); if (!routingPath) - return DatagramSession::Info{nullptr, nullptr, m_LastUse, m_LastSuccess}; + return DatagramSession::Info(nullptr, nullptr, m_LastUse, m_LastSuccess); auto lease = routingPath->remoteLease; auto tunnel = routingPath->outboundTunnel; if(lease) { if(tunnel) - return DatagramSession::Info{lease->tunnelGateway, tunnel->GetEndpointIdentHash(), m_LastUse, m_LastSuccess}; + return DatagramSession::Info(lease->tunnelGateway, tunnel->GetEndpointIdentHash(), m_LastUse, m_LastSuccess); else - return DatagramSession::Info{lease->tunnelGateway, nullptr, m_LastUse, m_LastSuccess}; + return DatagramSession::Info(lease->tunnelGateway, nullptr, m_LastUse, m_LastSuccess); } else if(tunnel) - return DatagramSession::Info{nullptr, tunnel->GetEndpointIdentHash(), m_LastUse, m_LastSuccess}; + return DatagramSession::Info(nullptr, tunnel->GetEndpointIdentHash(), m_LastUse, m_LastSuccess); else - return DatagramSession::Info{nullptr, nullptr, m_LastUse, m_LastSuccess}; + return DatagramSession::Info(nullptr, nullptr, m_LastUse, m_LastSuccess); } void DatagramSession::HandleSend(std::shared_ptr msg) diff --git a/Datagram.h b/Datagram.h index add946f0..bc358a41 100644 --- a/Datagram.h +++ b/Datagram.h @@ -53,11 +53,14 @@ namespace datagram const uint64_t activity; const uint64_t success; Info() : IBGW(nullptr), OBEP(nullptr), activity(0), success(0) {} - Info(const i2p::data::IdentHash & ibgw, const i2p::data::IdentHash & obep, const uint64_t a, const uint64_t s) : - IBGW(new i2p::data::IdentHash(ibgw.data())), - OBEP(new i2p::data::IdentHash(obep.data())), + Info(const uint8_t * ibgw, const uint8_t * obep, const uint64_t a, const uint64_t s) : activity(a), - success(s) {} + success(s) { + if(ibgw) IBGW = new i2p::data::IdentHash(ibgw); + else IBGW = nullptr; + if(obep) OBEP = new i2p::data::IdentHash(obep); + else OBEP = nullptr; + } ~Info() { if(IBGW) delete IBGW; diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 56756277..f1e1e453 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -352,14 +352,14 @@ namespace http { s << "" << info.RemoteIdent.ToBase32() << ""; s << ""; if(info.CurrentIBGW) - s << info.CurrentIBGW->ToBase64(); + s << info.CurrentIBGW->ToBase64().c_str(); else s << "(none)"; s << ""; s << ""; if(info.CurrentOBEP) - s << info.CurrentOBEP->ToBase64(); + s << info.CurrentOBEP->ToBase64().c_str(); else s << "(none)"; s << ""; From 1015188c4eb44787594f55994f88d2b32d04923e Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 16:54:39 -0400 Subject: [PATCH 1853/6300] use shared pointers --- Datagram.h | 13 ++++--------- HTTPServer.cpp | 6 +++--- I2PTunnel.h | 4 ++-- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/Datagram.h b/Datagram.h index bc358a41..06b3048e 100644 --- a/Datagram.h +++ b/Datagram.h @@ -48,24 +48,19 @@ namespace datagram uint64_t LastSuccess() const { return m_LastSuccess; } struct Info { - const i2p::data::IdentHash * IBGW; - const i2p::data::IdentHash * OBEP; + std::shared_ptr IBGW; + std::shared_ptr OBEP; const uint64_t activity; const uint64_t success; Info() : IBGW(nullptr), OBEP(nullptr), activity(0), success(0) {} Info(const uint8_t * ibgw, const uint8_t * obep, const uint64_t a, const uint64_t s) : activity(a), success(s) { - if(ibgw) IBGW = new i2p::data::IdentHash(ibgw); + if(ibgw) IBGW = std::make_shared(ibgw); else IBGW = nullptr; - if(obep) OBEP = new i2p::data::IdentHash(obep); + if(obep) OBEP = std::make_shared(obep); else OBEP = nullptr; } - ~Info() - { - if(IBGW) delete IBGW; - if(OBEP) delete OBEP; - } }; Info GetSessionInfo() const; diff --git a/HTTPServer.cpp b/HTTPServer.cpp index f1e1e453..01338ff2 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -346,20 +346,20 @@ namespace http { s << "Idle Time"; s << ""; auto forward = i2p::client::context.GetForwardInfosFor(dest->GetIdentHash()); - for (auto & info : forward) + for (auto info : forward) { s << ""; s << "" << info.RemoteIdent.ToBase32() << ""; s << ""; if(info.CurrentIBGW) - s << info.CurrentIBGW->ToBase64().c_str(); + s << std::string(info.CurrentIBGW->ToBase64()); else s << "(none)"; s << ""; s << ""; if(info.CurrentOBEP) - s << info.CurrentOBEP->ToBase64().c_str(); + s << std::string(info.CurrentOBEP->ToBase64()); else s << "(none)"; s << ""; diff --git a/I2PTunnel.h b/I2PTunnel.h index 7fe58574..0d4d85a1 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -148,9 +148,9 @@ namespace client /** ident hash of remote destination */ const i2p::data::IdentHash RemoteIdent; /** ident hash of IBGW in use currently in this session or nullptr if none is set */ - const i2p::data::IdentHash * CurrentIBGW; + std::shared_ptr CurrentIBGW; /** ident hash of OBEP in use for this session or nullptr if none is set */ - const i2p::data::IdentHash * CurrentOBEP; + std::shared_ptr CurrentOBEP; /** i2p router's udp endpoint */ const boost::asio::ip::udp::endpoint LocalEndpoint; /** client's udp endpoint */ From 8a29dfc3fa433b5eac1fb133b2553fa1cc4ae239 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 17:53:46 -0400 Subject: [PATCH 1854/6300] tabify and use shared pointers --- ClientContext.cpp | 19 ++- ClientContext.h | 2 +- HTTPServer.cpp | 15 +-- I2PTunnel.cpp | 337 +++++++++++++++++++++++----------------------- I2PTunnel.h | 51 +++---- 5 files changed, 211 insertions(+), 213 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 6ef7ec60..3eee42b2 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -276,20 +276,27 @@ namespace client return success; } - std::vector ClientContext::GetForwardInfosFor(const i2p::data::IdentHash & destination) + std::vector > ClientContext::GetForwardInfosFor(const i2p::data::IdentHash & destination) { + std::vector > infos; std::lock_guard lock(m_ForwardsMutex); - for(auto & c : m_ClientForwards) + for(const auto & c : m_ClientForwards) { if (c.second->IsLocalDestination(destination)) - return c.second->GetSessions(); + { + for (auto & i : c.second->GetSessions()) infos.push_back(i); + break; + } } - for(auto & s : m_ServerForwards) + for(const auto & s : m_ServerForwards) { if(std::get<0>(s.first) == destination) - return s.second->GetSessions(); + { + for( auto & i : s.second->GetSessions()) infos.push_back(i); + break; + } } - return {}; + return infos; } std::shared_ptr ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, diff --git a/ClientContext.h b/ClientContext.h index 7faf04ca..581ff466 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -67,7 +67,7 @@ namespace client AddressBook& GetAddressBook () { return m_AddressBook; }; const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; - std::vector GetForwardInfosFor(const i2p::data::IdentHash & destination); + std::vector > GetForwardInfosFor(const i2p::data::IdentHash & destination); private: diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 01338ff2..a8434bef 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -343,29 +343,26 @@ namespace http { s << "IBGW"; s << "OBEP"; s << "UDP Converstation"; - s << "Idle Time"; s << ""; auto forward = i2p::client::context.GetForwardInfosFor(dest->GetIdentHash()); for (auto info : forward) { s << ""; - s << "" << info.RemoteIdent.ToBase32() << ""; + s << "" << info->RemoteIdent->ToBase32() << ""; s << ""; - if(info.CurrentIBGW) - s << std::string(info.CurrentIBGW->ToBase64()); + if(info->CurrentIBGW) + s << info->CurrentIBGW->ToBase64(); else s << "(none)"; s << ""; s << ""; - if(info.CurrentOBEP) - s << std::string(info.CurrentOBEP->ToBase64()); + if(info->CurrentOBEP) + s << info->CurrentOBEP->ToBase64(); else s << "(none)"; s << ""; - s << "" << info.LocalEndpoint << " ⇄ " << info.RemoteEndpoint << ""; - auto sec = std::chrono::duration >( std::chrono::milliseconds(info.idle) ); - s << "" << sec.count() << " seconds "; + s << "" << info->LocalEndpoint << " ⇄ " << info->RemoteEndpoint << ""; s << "
    \r\n"; } s << "\r\n"; diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 688866c8..0c58ba9d 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -569,207 +569,200 @@ namespace client uint16_t ourPort, uint16_t theirPort) : m_Destination(localDestination->GetDatagramDestination()), m_Service(localDestination->GetService()), - IPSocket(localDestination->GetService(), localEndpoint), - Identity(to), - SendEndpoint(endpoint), - LastActivity(i2p::util::GetMillisecondsSinceEpoch()), - LocalPort(ourPort), - RemotePort(theirPort) - { - Receive(); - } - - - void UDPSession::Receive() { - LogPrint(eLogDebug, "UDPSession: Receive"); - IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), - FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); - } - - void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) - { - if(!ecode) - { - LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); - LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - uint8_t * data = new uint8_t[len]; - memcpy(data, m_Buffer, len); - m_Service.post([&,len, data] () { - m_Destination->SendDatagramTo(data, len, Identity, 0, 0); - delete [] data; - }); - - Receive(); - } else { - LogPrint(eLogError, "UDPSession: ", ecode.message()); - } - } - - - - I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, - const boost::asio::ip::address& localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port) : - m_Name(name), - LocalPort(port), - m_LocalAddress(localAddress), - m_RemoteEndpoint(forwardTo) - { - m_LocalDest = localDestination; - m_LocalDest->Start(); - auto dgram = m_LocalDest->CreateDatagramDestination(); - dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); - } - - I2PUDPServerTunnel::~I2PUDPServerTunnel() - { - auto dgram = m_LocalDest->GetDatagramDestination(); - if (dgram) dgram->ResetReceiver(); - - LogPrint(eLogInfo, "UDPServer: done"); - } - - void I2PUDPServerTunnel::Start() { - m_LocalDest->Start(); - } - - std::vector I2PUDPServerTunnel::GetSessions() + IPSocket(localDestination->GetService(), localEndpoint), + Identity(to), + SendEndpoint(endpoint), + LastActivity(i2p::util::GetMillisecondsSinceEpoch()), + LocalPort(ourPort), + RemotePort(theirPort) { - std::vector sessions; - auto localident = m_LocalDest->GetIdentHash(); + Receive(); + } + + + void UDPSession::Receive() { + LogPrint(eLogDebug, "UDPSession: Receive"); + IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), + FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); + } + + void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) + { + if(!ecode) + { + LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); + LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + uint8_t * data = new uint8_t[len]; + memcpy(data, m_Buffer, len); + m_Service.post([&,len, data] () { + m_Destination->SendDatagramTo(data, len, Identity, 0, 0); + delete [] data; + }); + + Receive(); + } else { + LogPrint(eLogError, "UDPSession: ", ecode.message()); + } + } + + + + I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, + const boost::asio::ip::address& localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port) : + m_Name(name), + LocalPort(port), + m_LocalAddress(localAddress), + m_RemoteEndpoint(forwardTo) + { + m_LocalDest = localDestination; + m_LocalDest->Start(); + auto dgram = m_LocalDest->CreateDatagramDestination(); + dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + } + + I2PUDPServerTunnel::~I2PUDPServerTunnel() + { + auto dgram = m_LocalDest->GetDatagramDestination(); + if (dgram) dgram->ResetReceiver(); + + LogPrint(eLogInfo, "UDPServer: done"); + } + + void I2PUDPServerTunnel::Start() { + m_LocalDest->Start(); + } + + std::vector > I2PUDPServerTunnel::GetSessions() + { + std::vector > sessions; std::lock_guard lock(m_SessionsMutex); for ( UDPSession * s : m_Sessions ) { if (!s->m_Destination) continue; auto info = s->m_Destination->GetInfoForRemote(s->Identity); if(!info) continue; - sessions.push_back(DatagramSessionInfo{ - m_Name, - localident, - s->Identity, - info->IBGW, - info->OBEP, - s->IPSocket.local_endpoint(), - s->SendEndpoint, - info->success - }); + + auto sinfo = std::make_shared(); + sinfo->Name = m_Name; + sinfo->LocalIdent = std::make_shared(m_LocalDest->GetIdentHash().data()); + sinfo->RemoteIdent = std::make_shared(s->Identity.data()); + sinfo->CurrentIBGW = info->IBGW; + sinfo->CurrentOBEP = info->OBEP; + sessions.push_back(sinfo); } return sessions; } - - I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, - boost::asio::ip::udp::endpoint localEndpoint, - std::shared_ptr localDestination, - uint16_t remotePort) : - m_Name(name), - m_Session(nullptr), - m_RemoteDest(remoteDest), - m_LocalDest(localDestination), - m_LocalEndpoint(localEndpoint), - m_RemoteIdent(nullptr), - m_ResolveThread(nullptr), - LocalPort(localEndpoint.port()), - RemotePort(remotePort), - m_cancel_resolve(false) - { - auto dgram = m_LocalDest->CreateDatagramDestination(); - dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, - std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4, - std::placeholders::_5)); - } - - - - void I2PUDPClientTunnel::Start() { - m_LocalDest->Start(); - if (m_ResolveThread == nullptr) - m_ResolveThread = new std::thread(std::bind(&I2PUDPClientTunnel::TryResolving, this)); - } - - std::vector I2PUDPClientTunnel::GetSessions() + + I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, + boost::asio::ip::udp::endpoint localEndpoint, + std::shared_ptr localDestination, + uint16_t remotePort) : + m_Name(name), + m_Session(nullptr), + m_RemoteDest(remoteDest), + m_LocalDest(localDestination), + m_LocalEndpoint(localEndpoint), + m_RemoteIdent(nullptr), + m_ResolveThread(nullptr), + LocalPort(localEndpoint.port()), + RemotePort(remotePort), + m_cancel_resolve(false) { - std::vector infos; + auto dgram = m_LocalDest->CreateDatagramDestination(); + dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4, + std::placeholders::_5)); + } + + + + void I2PUDPClientTunnel::Start() { + m_LocalDest->Start(); + if (m_ResolveThread == nullptr) + m_ResolveThread = new std::thread(std::bind(&I2PUDPClientTunnel::TryResolving, this)); + } + + std::vector > I2PUDPClientTunnel::GetSessions() + { + std::vector > infos; if(m_Session && m_LocalDest) { - auto localident = m_LocalDest->GetIdentHash(); auto s = m_Session; if (s->m_Destination) { auto info = m_Session->m_Destination->GetInfoForRemote(s->Identity); if(info) { - infos.push_back(DatagramSessionInfo{ - m_Name, - localident, - s->Identity, - info->IBGW, - info->OBEP, - s->IPSocket.local_endpoint(), - s->SendEndpoint, - info->success - }); + auto sinfo = std::make_shared(); + sinfo->Name = m_Name; + sinfo->LocalIdent = std::make_shared(m_LocalDest->GetIdentHash().data()); + sinfo->RemoteIdent = std::make_shared(s->Identity.data()); + sinfo->CurrentIBGW = info->IBGW; + sinfo->CurrentOBEP = info->OBEP; + infos.push_back(sinfo); } } } return infos; } - void I2PUDPClientTunnel::TryResolving() { - LogPrint(eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); - m_RemoteIdent = new i2p::data::IdentHash; - m_RemoteIdent->Fill(0); + void I2PUDPClientTunnel::TryResolving() { + LogPrint(eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); + m_RemoteIdent = new i2p::data::IdentHash; + m_RemoteIdent->Fill(0); - while(!context.GetAddressBook().GetIdentHash(m_RemoteDest, *m_RemoteIdent) && !m_cancel_resolve) - { - LogPrint(eLogWarning, "UDP Tunnel: failed to lookup ", m_RemoteDest); - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - if(m_cancel_resolve) - { - LogPrint(eLogError, "UDP Tunnel: lookup of ", m_RemoteDest, " was cancelled"); - return; - } - LogPrint(eLogInfo, "UDP Tunnel: resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32()); - // delete existing session - if(m_Session) delete m_Session; - - boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); - m_Session = new UDPSession(m_LocalEndpoint, m_LocalDest, ep, *m_RemoteIdent, LocalPort, RemotePort); - } + while(!context.GetAddressBook().GetIdentHash(m_RemoteDest, *m_RemoteIdent) && !m_cancel_resolve) + { + LogPrint(eLogWarning, "UDP Tunnel: failed to lookup ", m_RemoteDest); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + if(m_cancel_resolve) + { + LogPrint(eLogError, "UDP Tunnel: lookup of ", m_RemoteDest, " was cancelled"); + return; + } + LogPrint(eLogInfo, "UDP Tunnel: resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32()); + // delete existing session + if(m_Session) delete m_Session; + + boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); + m_Session = new UDPSession(m_LocalEndpoint, m_LocalDest, ep, *m_RemoteIdent, LocalPort, RemotePort); + } - void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) - { - if(m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) - { - // address match - if(m_Session) - { - // tell session - LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", from.GetIdentHash().ToBase32()); - m_Session->IPSocket.send_to(boost::asio::buffer(buf, len), m_Session->FromEndpoint); - } - else - LogPrint(eLogWarning, "UDP Client: no session"); - } - else - LogPrint(eLogWarning, "UDP Client: unwarrented traffic from ", from.GetIdentHash().ToBase32()); - - } + void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + if(m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) + { + // address match + if(m_Session) + { + // tell session + LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", from.GetIdentHash().ToBase32()); + m_Session->IPSocket.send_to(boost::asio::buffer(buf, len), m_Session->FromEndpoint); + } + else + LogPrint(eLogWarning, "UDP Client: no session"); + } + else + LogPrint(eLogWarning, "UDP Client: unwarrented traffic from ", from.GetIdentHash().ToBase32()); + + } - I2PUDPClientTunnel::~I2PUDPClientTunnel() { - auto dgram = m_LocalDest->GetDatagramDestination(); - if (dgram) dgram->ResetReceiver(); + I2PUDPClientTunnel::~I2PUDPClientTunnel() { + auto dgram = m_LocalDest->GetDatagramDestination(); + if (dgram) dgram->ResetReceiver(); - if (m_Session) delete m_Session; - m_cancel_resolve = true; + if (m_Session) delete m_Session; + m_cancel_resolve = true; - if(m_ResolveThread) - { - m_ResolveThread->join(); - delete m_ResolveThread; - m_ResolveThread = nullptr; - } - if (m_RemoteIdent) delete m_RemoteIdent; - } + if(m_ResolveThread) + { + m_ResolveThread->join(); + delete m_ResolveThread; + m_ResolveThread = nullptr; + } + if (m_RemoteIdent) delete m_RemoteIdent; + } } } diff --git a/I2PTunnel.h b/I2PTunnel.h index 0d4d85a1..2418da30 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -138,27 +138,6 @@ namespace client /** max size for i2p udp */ const size_t I2P_UDP_MAX_MTU = i2p::datagram::MAX_DATAGRAM_SIZE; - /** read only info about a datagram session */ - struct DatagramSessionInfo - { - /** the name of this forward */ - const std::string Name; - /** ident hash of local destination */ - const i2p::data::IdentHash LocalIdent; - /** ident hash of remote destination */ - const i2p::data::IdentHash RemoteIdent; - /** ident hash of IBGW in use currently in this session or nullptr if none is set */ - std::shared_ptr CurrentIBGW; - /** ident hash of OBEP in use for this session or nullptr if none is set */ - std::shared_ptr CurrentOBEP; - /** i2p router's udp endpoint */ - const boost::asio::ip::udp::endpoint LocalEndpoint; - /** client's udp endpoint */ - const boost::asio::ip::udp::endpoint RemoteEndpoint; - /** how long has this converstation been idle in ms */ - const uint64_t idle; - }; - struct UDPSession { i2p::datagram::DatagramDestination * m_Destination; @@ -182,9 +161,31 @@ namespace client void Receive(); }; + + /** read only info about a datagram session */ + struct DatagramSessionInfo + { + /** the name of this forward */ + std::string Name; + /** ident hash of local destination */ + std::shared_ptr LocalIdent; + /** ident hash of remote destination */ + std::shared_ptr RemoteIdent; + /** ident hash of IBGW in use currently in this session or nullptr if none is set */ + std::shared_ptr CurrentIBGW; + /** ident hash of OBEP in use for this session or nullptr if none is set */ + std::shared_ptr CurrentOBEP; + /** i2p router's udp endpoint */ + boost::asio::ip::udp::endpoint LocalEndpoint; + /** client's udp endpoint */ + boost::asio::ip::udp::endpoint RemoteEndpoint; + /** how long has this converstation been idle in ms */ + uint64_t idle; + }; + /** server side udp tunnel, many i2p inbound to 1 ip outbound */ class I2PUDPServerTunnel - { + { public: I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, @@ -195,7 +196,7 @@ namespace client void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); void Start(); const char * GetName() const { return m_Name.c_str(); } - std::vector GetSessions(); + std::vector > GetSessions(); private: void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); UDPSession * ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); @@ -209,7 +210,7 @@ namespace client std::shared_ptr m_LocalDest; }; - class I2PUDPClientTunnel + class I2PUDPClientTunnel { public: I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, @@ -218,7 +219,7 @@ namespace client ~I2PUDPClientTunnel(); void Start(); const char * GetName() const { return m_Name.c_str(); } - std::vector GetSessions(); + std::vector > GetSessions(); bool IsLocalDestination(const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } private: void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); From 7ae09fa1fe47ff4b2ced4c5cec6ca7de08e6efc3 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 17:58:16 -0400 Subject: [PATCH 1855/6300] try fixing memory errors --- HTTPServer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index a8434bef..3fdfcca5 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -351,14 +351,14 @@ namespace http { s << "" << info->RemoteIdent->ToBase32() << ""; s << ""; if(info->CurrentIBGW) - s << info->CurrentIBGW->ToBase64(); + s << info->CurrentIBGW->ToBase64().c_str(); else s << "(none)"; s << ""; s << ""; if(info->CurrentOBEP) - s << info->CurrentOBEP->ToBase64(); + s << info->CurrentOBEP->ToBase64().c_str(); else s << "(none)"; s << ""; From f64f8758064cf7aacf58a49925767184bf6ac363 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 18:04:54 -0400 Subject: [PATCH 1856/6300] don't show udp tunnels in ui yet --- HTTPServer.cpp | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 3fdfcca5..759c8ba3 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -338,34 +338,7 @@ namespace http { s << "" << (int)it->GetStatus () << ""; s << "
    \r\n" << std::endl; } - s << "
    \r\n"; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - auto forward = i2p::client::context.GetForwardInfosFor(dest->GetIdentHash()); - for (auto info : forward) - { - s << ""; - s << ""; - s << ""; - - s << ""; - s << ""; - s << "
    \r\n"; - } - s << "
    Forwards
    Remote DestinationIBGWOBEPUDP Converstation
    " << info->RemoteIdent->ToBase32() << ""; - if(info->CurrentIBGW) - s << info->CurrentIBGW->ToBase64().c_str(); - else - s << "(none)"; - s << ""; - if(info->CurrentOBEP) - s << info->CurrentOBEP->ToBase64().c_str(); - else - s << "(none)"; - s << "" << info->LocalEndpoint << " ⇄ " << info->RemoteEndpoint << "
    \r\n"; + s << ""; } } From f4d1b87f736c10e0c6bfacffeb4de1f561494b34 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 3 Sep 2016 18:34:18 -0400 Subject: [PATCH 1857/6300] expire tags --- Datagram.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Datagram.cpp b/Datagram.cpp index 14f9f385..cecbcc40 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -146,6 +146,7 @@ namespace datagram LogPrint(eLogInfo, "DatagramDestination: expiring idle session with ", ident.ToBase32()); m_Sessions.erase(ident); } + m_Owner->CleanupExpiredTags(); ScheduleCleanup(); } From 722f1c4430c30ed2643c3dd2e3dc60c1782c5009 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 4 Sep 2016 16:42:48 -0400 Subject: [PATCH 1858/6300] try fixing webui freeze --- HTTPServer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 309e3f6b..db46404f 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -828,7 +828,13 @@ namespace http { std::shared_ptr newSocket) { if (ecode) + { + if(newSocket) newSocket->close(); + LogPrint(eLogError, "HTTP Server: error handling accept ", ecode.message()); + if(ecode != boost::asio::error::operation_aborted) + Accept(); return; + } CreateConnection(newSocket); Accept (); } From f3a61007a7094841e9137a393474f355e0bbeafe Mon Sep 17 00:00:00 2001 From: "Solomenchuk, Vlad" Date: Tue, 6 Sep 2016 12:26:59 -0700 Subject: [PATCH 1859/6300] build instruction for iOS --- build/CMakeLists.txt | 9 ++++- docs/build_notes_ios.md | 85 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 docs/build_notes_ios.md diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index f9f21fd4..dda0a67c 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -187,7 +187,14 @@ endif() # TODO: once CMake 3.1+ becomes mainstream, see e.g. http://stackoverflow.com/a/29871891/673826 # use imported Threads::Threads instead set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package ( Threads REQUIRED ) +if (IOS) + set(CMAKE_THREAD_LIBS_INIT "-lpthread") + set(CMAKE_HAVE_THREADS_LIBRARY 1) + set(CMAKE_USE_WIN32_THREADS_INIT 0) + set(CMAKE_USE_PTHREADS_INIT 1) +else() + find_package ( Threads REQUIRED ) +endif() if(THREADS_HAVE_PTHREAD_ARG) # compile time flag set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") endif() diff --git a/docs/build_notes_ios.md b/docs/build_notes_ios.md new file mode 100644 index 00000000..26688d21 --- /dev/null +++ b/docs/build_notes_ios.md @@ -0,0 +1,85 @@ +Building on iOS +=================== + +How to build i2pd for iOS 9 and iOS Simulator 386/x64 + +Prerequisites +-------------- + +XCode7+, cmake 3.2+ + +Dependencies +-------------- +- precompiled openssl +- precompiled boost with modules `filesystem`, `program_options`, `date_time` and `system` +- ios-cmake toolchain from https://github.com/vovasty/ios-cmake.git + +Building +------------------------ +Assume you have folder structure + +``` +lib + libboost_date_time.a + libboost_filesystem.a + libboost_program_options.a + libboost_system.a + libboost.a + libcrypto.a + libssl.a +include + boost + openssl +ios-cmake +i2pd +``` + + +```bash +mkdir -p build/simulator/lib build/ios/lib include/i2pd + +pushd build/simulator && \ +cmake -DIOS_PLATFORM=SIMULATOR \ + -DPATCH=/usr/bin/patch \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=../../ios-cmake/toolchain/iOS.cmake \ + -DWITH_STATIC=yes \ + -DWITH_BINARY=no \ + -DBoost_INCLUDE_DIR=../../include \ + -DOPENSSL_INCLUDE_DIR=../../include \ + -DBoost_LIBRARY_DIR=../../lib \ + -DOPENSSL_SSL_LIBRARY=../../lib/libssl.a \ + -DOPENSSL_CRYPTO_LIBRARY=../../lib/libcrypto.a \ + ../../i2pd/build && \ +make -j16 VERBOSE=1 && \ +popd + +pushd build/ios +cmake -DIOS_PLATFORM=OS \ + -DPATCH=/usr/bin/patch \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=../../ios-cmake/toolchain/iOS.cmake \ + -DWITH_STATIC=yes \ + -DWITH_BINARY=no \ + -DBoost_INCLUDE_DIR=../../include \ + -DOPENSSL_INCLUDE_DIR=../../include \ + -DBoost_LIBRARY_DIR=../../lib \ + -DOPENSSL_SSL_LIBRARY=../../lib/libssl.a \ + -DOPENSSL_CRYPTO_LIBRARY=../../lib/libcrypto.a \ + ../../i2pd/build && \ +make -j16 VERBOSE=1 && \ +popd + +libtool -static -o lib/libi2pdclient.a build/*/libi2pdclient.a +libtool -static -o lib/libi2pd.a build/*/libi2pd.a + +cp i2pd/*.h include/i2pd +``` + +Include into project +--------------------------- +1. add all libraries in `lib` folder to `Project linked frameworks`. +2. add `libc++` and `libz` libraries from system libraries to `Project linked frameworks`. +3. add path to i2p headers to your `Headers search paths` + +Alternatively you may use swift wrapper https://github.com/vovasty/SwiftyI2P.git \ No newline at end of file From 6e0d6dcac59012dffa8fea713af838491d863fdb Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 7 Sep 2016 13:25:11 -0400 Subject: [PATCH 1860/6300] reselect tunnels if LeaseSet delivery was not confirmed --- Garlic.cpp | 14 ++++++++------ Garlic.h | 1 + Streaming.cpp | 8 ++++++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Garlic.cpp b/Garlic.cpp index c3c0e045..3521ffcf 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -247,7 +247,7 @@ namespace garlic size_t GarlicRoutingSession::CreateGarlicPayload (uint8_t * payload, std::shared_ptr msg, UnconfirmedTags * newTags) { - uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); uint32_t msgID; RAND_bytes ((uint8_t *)&msgID, 4); size_t size = 0; @@ -258,9 +258,11 @@ namespace garlic if (m_Owner) { // resubmit non-confirmed LeaseSet - if (m_LeaseSetUpdateStatus == eLeaseSetSubmitted && - i2p::util::GetMillisecondsSinceEpoch () > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT) - m_LeaseSetUpdateStatus = eLeaseSetUpdated; + if (m_LeaseSetUpdateStatus == eLeaseSetSubmitted && ts > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT) + { + m_LeaseSetUpdateStatus = eLeaseSetUpdated; + SetSharedRoutingPath (nullptr); // invalidate path since leaseset was not confirmed + } // attach DeviveryStatus if necessary if (newTags || m_LeaseSetUpdateStatus == eLeaseSetUpdated) // new tags created or leaseset updated @@ -286,7 +288,7 @@ namespace garlic { m_LeaseSetUpdateStatus = eLeaseSetSubmitted; m_LeaseSetUpdateMsgID = msgID; - m_LeaseSetSubmissionTime = i2p::util::GetMillisecondsSinceEpoch (); + m_LeaseSetSubmissionTime = ts; // clove if our leaseSet must be attached auto leaseSet = CreateDatabaseStoreMsg (m_Owner->GetLeaseSet ()); size += CreateGarlicClove (payload + size, leaseSet, false); @@ -303,7 +305,7 @@ namespace garlic size += 3; htobe32buf (payload + size, msgID); // MessageID size += 4; - htobe64buf (payload + size, ts); // Expiration of message + htobe64buf (payload + size, ts + 8000); // Expiration of message, 8 sec size += 8; return size; } diff --git a/Garlic.h b/Garlic.h index 6a92b94a..8d2e850a 100644 --- a/Garlic.h +++ b/Garlic.h @@ -104,6 +104,7 @@ namespace garlic { if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated; }; + bool IsLeaseSetNonConfirmed () const { return m_LeaseSetUpdateStatus == eLeaseSetSubmitted; }; std::shared_ptr GetSharedRoutingPath (); void SetSharedRoutingPath (std::shared_ptr path); diff --git a/Streaming.cpp b/Streaming.cpp index 1103dba1..e6f5b32f 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -736,7 +736,15 @@ namespace stream return; } if (m_Status == eStreamStatusOpen) + { + if (m_RoutingSession && m_RoutingSession->IsLeaseSetNonConfirmed ()) + { + // seems something went wrong and we should re-select tunnels + m_CurrentOutboundTunnel = nullptr; + m_CurrentRemoteLease = nullptr; + } SendQuickAck (); + } m_IsAckSendScheduled = false; } } From db716737227dd9044def5d9ca4c9c4e05f7fce65 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Sep 2016 09:19:30 -0400 Subject: [PATCH 1861/6300] fixed #629. catch HTTPServer exceptions --- HTTPServer.cpp | 19 ++++++++++++++++--- HTTPServer.h | 1 + 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index bffe8614..1bb558e3 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -769,7 +769,7 @@ namespace http { } HTTPServer::HTTPServer (const std::string& address, int port): - m_Thread (nullptr), m_Work (m_Service), + m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)) { } @@ -798,6 +798,7 @@ namespace http { i2p::config::SetOption("http.pass", pass); LogPrint(eLogInfo, "HTTPServer: password set to ", pass); } + m_IsRunning = true; m_Thread = std::unique_ptr(new std::thread (std::bind (&HTTPServer::Run, this))); m_Acceptor.listen (); Accept (); @@ -805,9 +806,11 @@ namespace http { void HTTPServer::Stop () { + m_IsRunning = false; m_Acceptor.close(); m_Service.stop (); - if (m_Thread) { + if (m_Thread) + { m_Thread->join (); m_Thread = nullptr; } @@ -815,7 +818,17 @@ namespace http { void HTTPServer::Run () { - m_Service.run (); + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "HTTPServer: runtime exception: ", ex.what ()); + } + } } void HTTPServer::Accept () diff --git a/HTTPServer.h b/HTTPServer.h index 5246af8b..4a32702d 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -57,6 +57,7 @@ namespace http { private: + bool m_IsRunning; std::unique_ptr m_Thread; boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; From bee407ea34edc1fe7f0939396267d7dac5131402 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Sep 2016 10:16:42 -0400 Subject: [PATCH 1862/6300] clean-up datagram session toghters with leasesets and tags --- Datagram.cpp | 40 +++++++++++++++------------------------- Datagram.h | 14 ++++---------- Destination.cpp | 7 +++++++ Destination.h | 2 ++ 4 files changed, 28 insertions(+), 35 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index cecbcc40..fa05f5a9 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -12,16 +12,12 @@ namespace i2p namespace datagram { DatagramDestination::DatagramDestination (std::shared_ptr owner): - m_Owner (owner.get()), - m_CleanupTimer(owner->GetService()), - m_Receiver (nullptr) + m_Owner (owner.get()), m_Receiver (nullptr) { - ScheduleCleanup(); } DatagramDestination::~DatagramDestination () { - m_CleanupTimer.cancel(); m_Sessions.clear(); } @@ -120,34 +116,28 @@ namespace datagram return msg; } - void DatagramDestination::ScheduleCleanup() + void DatagramDestination::CleanUp () { - m_CleanupTimer.expires_from_now(boost::posix_time::seconds(DATAGRAM_SESSION_CLEANUP_INTERVAL)); - m_CleanupTimer.async_wait(std::bind(&DatagramDestination::HandleCleanUp, this, std::placeholders::_1)); - } - - void DatagramDestination::HandleCleanUp(const boost::system::error_code & ecode) - { - if(ecode) - return; - std::lock_guard lock(m_SessionsMutex); - auto now = i2p::util::GetMillisecondsSinceEpoch(); - LogPrint(eLogDebug, "DatagramDestination: clean up sessions"); std::vector expiredSessions; - // for each session ... - for (auto & e : m_Sessions) { - // check if expired - if(now - e.second->LastActivity() >= DATAGRAM_SESSION_MAX_IDLE) - expiredSessions.push_back(e.first); // we are expired + { + std::lock_guard lock(m_SessionsMutex); + auto now = i2p::util::GetMillisecondsSinceEpoch(); + LogPrint(eLogDebug, "DatagramDestination: clean up sessions"); + // for each session ... + for (auto & e : m_Sessions) + { + // check if expired + if(now - e.second->LastActivity() >= DATAGRAM_SESSION_MAX_IDLE) + expiredSessions.push_back(e.first); // we are expired + } } // for each expired session ... - for (auto & ident : expiredSessions) { + for (auto & ident : expiredSessions) + { // remove the expired session LogPrint(eLogInfo, "DatagramDestination: expiring idle session with ", ident.ToBase32()); m_Sessions.erase(ident); } - m_Owner->CleanupExpiredTags(); - ScheduleCleanup(); } std::shared_ptr DatagramDestination::ObtainSession(const i2p::data::IdentHash & ident) diff --git a/Datagram.h b/Datagram.h index 06b3048e..f3aae6f9 100644 --- a/Datagram.h +++ b/Datagram.h @@ -19,9 +19,6 @@ namespace client } namespace datagram { - - // seconds interval for cleanup timer - const int DATAGRAM_SESSION_CLEANUP_INTERVAL = 3; // milliseconds for max session idle time const uint64_t DATAGRAM_SESSION_MAX_IDLE = 10 * 60 * 1000; // milliseconds for how long we try sticking to a dead routing path before trying to switch @@ -127,13 +124,11 @@ namespace datagram std::shared_ptr GetInfoForRemote(const i2p::data::IdentHash & remote); + // clean up stale sessions + void CleanUp (); + private: - // clean up after next tick - void ScheduleCleanup(); - - // clean up stale sessions and expire tags - void HandleCleanUp(const boost::system::error_code & ecode); - + std::shared_ptr ObtainSession(const i2p::data::IdentHash & ident); std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); @@ -145,7 +140,6 @@ namespace datagram private: i2p::client::ClientDestination * m_Owner; - boost::asio::deadline_timer m_CleanupTimer; Receiver m_Receiver; // default std::mutex m_SessionsMutex; std::map > m_Sessions; diff --git a/Destination.cpp b/Destination.cpp index 03d0dd69..48717f5f 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -641,6 +641,7 @@ namespace client { CleanupExpiredTags (); CleanupRemoteLeaseSets (); + CleanupDestination (); m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, shared_from_this (), std::placeholders::_1)); @@ -892,5 +893,11 @@ namespace client Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); // TODO SetLeaseSet (leaseSet); } + + void ClientDestination::CleanupDestination () + { + if (m_DatagramDestination) m_DatagramDestination->CleanUp (); + } + } } diff --git a/Destination.h b/Destination.h index 8150c72d..22ffa603 100644 --- a/Destination.h +++ b/Destination.h @@ -96,6 +96,7 @@ namespace client protected: void SetLeaseSet (i2p::data::LocalLeaseSet * newLeaseSet); + virtual void CleanupDestination () {}; // additional clean up in derived classes // I2CP virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0; virtual void CreateNewLeaseSet (std::vector > tunnels) = 0; @@ -180,6 +181,7 @@ namespace client protected: + void CleanupDestination (); // I2CP void HandleDataMessage (const uint8_t * buf, size_t len); void CreateNewLeaseSet (std::vector > tunnels); From a4762fe65cf6e7b760ef7c3fdb90bb4d0f29004b Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Sep 2016 10:46:13 -0400 Subject: [PATCH 1863/6300] remove expired session through one pass --- Datagram.cpp | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index fa05f5a9..53b13d50 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -117,26 +117,21 @@ namespace datagram } void DatagramDestination::CleanUp () - { - std::vector expiredSessions; + { + auto now = i2p::util::GetMillisecondsSinceEpoch(); + LogPrint(eLogDebug, "DatagramDestination: clean up sessions"); + std::lock_guard lock(m_SessionsMutex); + // for each session ... + for (auto it = m_Sessions.begin (); it != m_Sessions.end (); ) { - std::lock_guard lock(m_SessionsMutex); - auto now = i2p::util::GetMillisecondsSinceEpoch(); - LogPrint(eLogDebug, "DatagramDestination: clean up sessions"); - // for each session ... - for (auto & e : m_Sessions) + // check if expired + if (now - it->second->LastActivity() >= DATAGRAM_SESSION_MAX_IDLE) { - // check if expired - if(now - e.second->LastActivity() >= DATAGRAM_SESSION_MAX_IDLE) - expiredSessions.push_back(e.first); // we are expired + LogPrint(eLogInfo, "DatagramDestination: expiring idle session with ", it->first.ToBase32()); + it = m_Sessions.erase (it); // we are expired } - } - // for each expired session ... - for (auto & ident : expiredSessions) - { - // remove the expired session - LogPrint(eLogInfo, "DatagramDestination: expiring idle session with ", ident.ToBase32()); - m_Sessions.erase(ident); + else + it++; } } From 6885761f87f4378260331427a4d6f8fe61408fcc Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Sep 2016 10:56:22 -0400 Subject: [PATCH 1864/6300] check if sessions list is empty --- Datagram.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Datagram.cpp b/Datagram.cpp index 53b13d50..b9188864 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -118,6 +118,7 @@ namespace datagram void DatagramDestination::CleanUp () { + if (m_Sessions.empty ()) return; auto now = i2p::util::GetMillisecondsSinceEpoch(); LogPrint(eLogDebug, "DatagramDestination: clean up sessions"); std::lock_guard lock(m_SessionsMutex); From 516380f979e0f3b76f913d8cbc52f3324d345e23 Mon Sep 17 00:00:00 2001 From: Rabit Date: Sun, 11 Sep 2016 01:15:22 +0400 Subject: [PATCH 1865/6300] Fixed upstart forking Upstart can't track daemonize fork without expect fork --- debian/i2pd.upstart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debian/i2pd.upstart b/debian/i2pd.upstart index fd2b24f0..cfa80e8d 100644 --- a/debian/i2pd.upstart +++ b/debian/i2pd.upstart @@ -6,4 +6,6 @@ stop on runlevel [016] or unmounting-filesystem # these can be overridden in /etc/init/i2pd.override env LOGFILE="/var/log/i2pd.log" +expect fork + exec /usr/sbin/i2pd --daemon --service --log=file --logfile=$LOGFILE From ed874fe3ea57e46fcbcd4a48b63c30d783b121ae Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 11 Sep 2016 21:36:17 -0400 Subject: [PATCH 1866/6300] check if RouterInfo has been decompressed completely --- NetDb.cpp | 7 ++++++- RouterInfo.cpp | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/NetDb.cpp b/NetDb.cpp index 846f4b3c..2c9c4395 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -556,8 +556,13 @@ namespace data } uint8_t uncompressed[2048]; size_t uncompressedSize = m_Inflator.Inflate (buf + offset, size, uncompressed, 2048); - if (uncompressedSize) + if (uncompressedSize && uncompressedSize < 2048) updated = AddRouterInfo (ident, uncompressed, uncompressedSize); + else + { + LogPrint (eLogError, "NetDb: decompression failed ", uncompressedSize); + return; + } } if (replyToken && context.IsFloodfill () && updated) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 130355b8..2aefab31 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -125,7 +125,8 @@ namespace data m_IsUnreachable = true; return; } - std::stringstream str (std::string ((char *)m_Buffer + identityLen, m_BufferLen - identityLen)); + std::stringstream str; + str.write ((const char *)m_Buffer + identityLen, m_BufferLen - identityLen); ReadFromStream (str); if (!str) { From 75065f29f78e452afa1a9e5f6a12f0f49412535a Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 12 Sep 2016 11:39:33 -0400 Subject: [PATCH 1867/6300] check if field is incomplete --- RouterInfo.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 2aefab31..d672d669 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -183,6 +183,7 @@ namespace data s.seekg (1, std::ios_base::cur); r++; // = r += ReadString (value, 255, s); s.seekg (1, std::ios_base::cur); r++; // ; + if (!s) return; if (!strcmp (key, "host")) { boost::system::error_code ecode; @@ -224,6 +225,11 @@ namespace data size_t l = strlen(key); unsigned char index = key[l-1] - '0'; // TODO: key[l-1] = 0; + if (index > 9) + { + LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped"); + if (s) continue; else return; + } if (index >= address.introducers.size ()) address.introducers.resize (index + 1); Introducer& introducer = address.introducers.at (index); @@ -263,6 +269,7 @@ namespace data s.seekg (1, std::ios_base::cur); r++; // = r += ReadString (value, 255, s); s.seekg (1, std::ios_base::cur); r++; // ; + if (!s) return; m_Properties[key] = value; // extract caps @@ -271,7 +278,7 @@ namespace data // check netId else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != I2PD_NET_ID) { - LogPrint (eLogError, "Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); + LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); m_IsUnreachable = true; } // family @@ -544,6 +551,7 @@ namespace data if (l < len) { s.read (str, l); + if (!s) l = 0; // failed, return empty string str[l] = 0; } else From 325b36272739992a6a7c165cbc29b92c0890e24d Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 12 Sep 2016 12:05:01 -0400 Subject: [PATCH 1868/6300] show UDP tunnels --- HTTPServer.cpp | 26 ++++++++++++++++++++++++++ I2PTunnel.h | 8 ++++++++ 2 files changed, 34 insertions(+) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 1bb558e3..2e9a02ca 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -565,6 +565,32 @@ namespace http { s << ":" << it.second->GetLocalPort (); s << "
    \r\n"<< std::endl; } + auto& clientForwards = i2p::client::context.GetClientForwards (); + if (!clientForwards.empty ()) + { + s << "
    \r\nClient Forwards:
    \r\n
    \r\n"; + for (auto& it: clientForwards) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << ""; + s << it.second->GetName () << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
    \r\n"<< std::endl; + } + } + auto& serverForwards = i2p::client::context.GetServerForwards (); + if (!serverForwards.empty ()) + { + s << "
    \r\nServer Forwards:
    \r\n
    \r\n"; + for (auto& it: serverForwards) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << ""; + s << it.second->GetName () << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
    \r\n"<< std::endl; + } + } } HTTPConnection::HTTPConnection (std::shared_ptr socket): diff --git a/I2PTunnel.h b/I2PTunnel.h index 2418da30..dce9f812 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -197,9 +197,13 @@ namespace client void Start(); const char * GetName() const { return m_Name.c_str(); } std::vector > GetSessions(); + std::shared_ptr GetLocalDestination () const { return m_LocalDest; } + private: + void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); UDPSession * ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); + private: const std::string m_Name; const uint16_t LocalPort; @@ -220,7 +224,11 @@ namespace client void Start(); const char * GetName() const { return m_Name.c_str(); } std::vector > GetSessions(); + bool IsLocalDestination(const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } + + std::shared_ptr GetLocalDestination () const { return m_LocalDest; } + private: void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void TryResolving(); From f9a5f4955c959081ac6e5bbf4d1952a825da99e5 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 12 Sep 2016 21:37:43 -0400 Subject: [PATCH 1869/6300] check RI signture before processing --- RouterInfo.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index d672d669..9b2a4195 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -125,15 +125,6 @@ namespace data m_IsUnreachable = true; return; } - std::stringstream str; - str.write ((const char *)m_Buffer + identityLen, m_BufferLen - identityLen); - ReadFromStream (str); - if (!str) - { - LogPrint (eLogError, "RouterInfo: malformed message"); - m_IsUnreachable = true; - return; - } if (verifySignature) { // verify signature @@ -142,9 +133,20 @@ namespace data { LogPrint (eLogError, "RouterInfo: signature verification failed"); m_IsUnreachable = true; + return; } m_RouterIdentity->DropVerifier (); } + // parse RI + std::stringstream str; + str.write ((const char *)m_Buffer + identityLen, m_BufferLen - identityLen); + ReadFromStream (str); + if (!str) + { + LogPrint (eLogError, "RouterInfo: malformed message"); + m_IsUnreachable = true; + } + } void RouterInfo::ReadFromStream (std::istream& s) From fee5f959fd075ec20eafe7f09263855b47687340 Mon Sep 17 00:00:00 2001 From: brain5lug Date: Fri, 16 Sep 2016 01:47:38 +0300 Subject: [PATCH 1870/6300] perfect forwarding for logging arguments --- Log.cpp | 2 +- Log.h | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Log.cpp b/Log.cpp index 590f3d0f..5660821f 100644 --- a/Log.cpp +++ b/Log.cpp @@ -10,7 +10,7 @@ namespace i2p { namespace log { - Log logger; + static Log logger; /** * @brief Maps our loglevel to their symbolic name */ diff --git a/Log.h b/Log.h index bedc98f8..79bbeb3f 100644 --- a/Log.h +++ b/Log.h @@ -162,17 +162,17 @@ namespace log { /** internal usage only -- folding args array to single string */ template -void LogPrint (std::stringstream& s, TValue arg) +void LogPrint (std::stringstream& s, TValue&& arg) noexcept { - s << arg; + s << std::forward(arg); } /** internal usage only -- folding args array to single string */ template -void LogPrint (std::stringstream& s, TValue arg, TArgs... args) +void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept { - LogPrint (s, arg); - LogPrint (s, args...); + LogPrint (s, std::forward(arg)); + LogPrint (s, std::forward(args)...); } /** @@ -181,7 +181,7 @@ void LogPrint (std::stringstream& s, TValue arg, TArgs... args) * @param args Array of message parts */ template -void LogPrint (LogLevel level, TArgs... args) +void LogPrint (LogLevel level, TArgs&&... args) noexcept { i2p::log::Log &log = i2p::log::Logger(); if (level > log.GetLogLevel ()) @@ -194,7 +194,7 @@ void LogPrint (LogLevel level, TArgs... args) ss << LOG_COLOR_ERROR; else if (level == eLogWarning) // if log level is WARN color log message yellow ss << LOG_COLOR_WARNING; - LogPrint (ss, args ...); + LogPrint (ss, std::forward(args)...); // reset color ss << LOG_COLOR_RESET; From fbb5bb2f05de11cd15ed66677f16490253b6dc70 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 16 Sep 2016 10:31:11 -0400 Subject: [PATCH 1871/6300] fix #634.don't create timer in constructor --- ClientContext.cpp | 51 +++++++++++++++++++++-------------------------- ClientContext.h | 5 +---- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/ClientContext.cpp b/ClientContext.cpp index 3eee42b2..b7300ad2 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -17,8 +17,7 @@ namespace client ClientContext::ClientContext (): m_SharedLocalDestination (nullptr), m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr), - m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr), - m_CleanupUDPTimer(m_Service, boost::posix_time::seconds(1)) + m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr) { } @@ -88,19 +87,8 @@ namespace client } } - if ( m_ServiceThread == nullptr ) { - m_ServiceThread = new std::thread([&] () { - LogPrint(eLogInfo, "ClientContext: starting service"); - m_Service.run(); - LogPrint(eLogError, "ClientContext: service died"); - }); - ScheduleCleanupUDP(); - } - - // I2P tunnels - ReadTunnels (); - + ReadTunnels (); // SAM bool sam; i2p::config::GetOption("sam.enabled", sam); @@ -149,6 +137,13 @@ namespace client } m_AddressBook.StartResolvers (); + + // start UDP cleanup + if (!m_ServerForwards.empty ()) + { + m_CleanupUDPTimer.reset (new boost::asio::deadline_timer(m_SharedLocalDestination->GetService ())); + ScheduleCleanupUDP(); + } } void ClientContext::Stop () @@ -210,25 +205,22 @@ namespace client LogPrint(eLogInfo, "Clients: stopping AddressBook"); m_AddressBook.Stop (); - { + { std::lock_guard lock(m_ForwardsMutex); m_ServerForwards.clear(); m_ClientForwards.clear(); } - + if (m_CleanupUDPTimer) + { + m_CleanupUDPTimer->cancel (); + m_CleanupUDPTimer = nullptr; + } + for (auto& it: m_Destinations) it.second->Stop (); m_Destinations.clear (); m_SharedLocalDestination = nullptr; - // stop io service thread - if(m_ServiceThread) - { - m_Service.stop(); - m_ServiceThread->join(); - delete m_ServiceThread; - m_ServiceThread = nullptr; - } } void ClientContext::ReloadConfig () @@ -558,9 +550,12 @@ namespace client void ClientContext::ScheduleCleanupUDP() { - // schedule cleanup in 1 second - m_CleanupUDPTimer.expires_at(m_CleanupUDPTimer.expires_at() + boost::posix_time::seconds(1)); - m_CleanupUDPTimer.async_wait(std::bind(&ClientContext::CleanupUDP, this, std::placeholders::_1)); + if (m_CleanupUDPTimer) + { + // schedule cleanup in 17 seconds + m_CleanupUDPTimer->expires_from_now (boost::posix_time::seconds (17)); + m_CleanupUDPTimer->async_wait(std::bind(&ClientContext::CleanupUDP, this, std::placeholders::_1)); + } } void ClientContext::CleanupUDP(const boost::system::error_code & ecode) @@ -568,7 +563,7 @@ namespace client if(!ecode) { std::lock_guard lock(m_ForwardsMutex); - for ( auto & s : m_ServerForwards ) s.second->ExpireStale(); + for (auto & s : m_ServerForwards ) s.second->ExpireStale(); ScheduleCleanupUDP(); } } diff --git a/ClientContext.h b/ClientContext.h index 581ff466..1a41acfc 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -101,10 +101,7 @@ namespace client BOBCommandChannel * m_BOBCommandChannel; I2CPServer * m_I2CPServer; - boost::asio::io_service m_Service; - std::thread * m_ServiceThread; - - boost::asio::deadline_timer m_CleanupUDPTimer; + std::unique_ptr m_CleanupUDPTimer; public: // for HTTP From cb91891f2209aa66afa72e055b2efd5aef496eb4 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 16 Sep 2016 16:18:50 -0400 Subject: [PATCH 1872/6300] check buffer size --- Family.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Family.cpp b/Family.cpp index ff09f2f5..c1840e51 100644 --- a/Family.cpp +++ b/Family.cpp @@ -114,6 +114,12 @@ namespace data { uint8_t buf[50], signatureBuf[64]; size_t len = family.length (), signatureLen = strlen (signature); + if (len + 32 > 50) + { + LogPrint (eLogError, "Family: ", family, " is too long"); + return false; + } + memcpy (buf, family.c_str (), len); memcpy (buf + len, (const uint8_t *)ident, 32); len += 32; From 949be436a60dd8369d982a0527c6b010a7be159a Mon Sep 17 00:00:00 2001 From: l-n-s Date: Fri, 16 Sep 2016 22:56:51 +0000 Subject: [PATCH 1873/6300] Adding option to configure reseed URLs --- Config.cpp | 17 +++++++++++++++++ Reseed.cpp | 25 +++++-------------------- docs/configuration.md | 1 + 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/Config.cpp b/Config.cpp index a629f2d4..33231aa4 100644 --- a/Config.cpp +++ b/Config.cpp @@ -151,6 +151,23 @@ namespace config { options_description reseed("Reseed options"); reseed.add_options() ("reseed.file", value()->default_value(""), "Path to .su3 file") +#ifdef MESHNET + ("reseed.urls", value()->default_value("https://reseed.i2p.rocks:8443/"), "Reseed URLs, separated by comma") +#else + ("reseed.urls", value()->default_value( + "https://reseed.i2p-projekt.de/," + "https://i2p.mooo.com/netDb/," + "https://netdb.i2p2.no/," + "https://us.reseed.i2p2.no:444/," + "https://uk.reseed.i2p2.no:444/," + "https://i2p.manas.ca:8443/," + "https://i2p-0.manas.ca:8443/," + "https://reseed.i2p.vzaws.com:8443/," + "https://user.mx24.eu/," + "https://download.xxlspeed.com/," + "https://reseed-ru.lngserv.ru/" + ), "Reseed URLs, separated by comma") +#endif ; options_description trust("Trust options"); diff --git a/Reseed.cpp b/Reseed.cpp index 21635791..a51dcad4 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -22,26 +23,6 @@ namespace i2p { namespace data { - static std::vector httpsReseedHostList = - { -#ifdef MESHNET - // meshnet i2p reseeds - "https://reseed.i2p.rocks:8443/" -#else - // mainline i2p reseeds - "https://reseed.i2p-projekt.de/", // Only HTTPS - "https://i2p.mooo.com/netDb/", - "https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required - "https://us.reseed.i2p2.no:444/", - "https://uk.reseed.i2p2.no:444/", - "https://i2p.manas.ca:8443/", - "https://i2p-0.manas.ca:8443/", - "https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support - "https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support - "https://download.xxlspeed.com/", // Only HTTPS and SU3 (v3) support - "https://reseed-ru.lngserv.ru/" -#endif - }; Reseeder::Reseeder() { @@ -53,6 +34,10 @@ namespace data int Reseeder::ReseedNowSU3 () { + std::string reseedURLs; i2p::config::GetOption("reseed.urls", reseedURLs); + std::vector httpsReseedHostList; + boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on); + std::string filename; i2p::config::GetOption("reseed.file", filename); if (filename.length() > 0) // reseed file is specified { diff --git a/docs/configuration.md b/docs/configuration.md index 32cd7f49..31082dc2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -73,6 +73,7 @@ All options below still possible in cmdline, but better write it in config file: * --precomputation.elgamal= - Use ElGamal precomputated tables. false for x64 and true for other platforms by default * --reseed.file - Full path to SU3 file to reseed from +* --reseed.urls - Reseed URLs, separated by comma * --limits.transittunnels= - Override maximum number of transit tunnels. 2500 by default From 6c7316408bf0b16adbbb152a3e3275a3995fa4e1 Mon Sep 17 00:00:00 2001 From: brain5lug Date: Sat, 17 Sep 2016 11:00:21 +0300 Subject: [PATCH 1874/6300] address sanitizer configuration option have been added --- build/CMakeLists.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index dda0a67c..61b8fdf9 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -16,6 +16,7 @@ option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_PCH "Use precompiled header" OFF) option(WITH_GUI "Include GUI (currently MS Windows only)" ON) option(WITH_MESHNET "Build for cjdns test network" OFF) +option(WITH_ADDRSANITIZER "Build with address sanitizer (linux only)" OFF) # paths set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) @@ -183,6 +184,15 @@ if (WITH_AESNI) add_definitions ( -DAESNI ) endif() +if (WITH_ADDRSANITIZER) + if (NOT MSVC) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer" ) + set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address" ) + else () + error ("MSVC does not support address sanitizer option") + endif() +endif() + # libraries # TODO: once CMake 3.1+ becomes mainstream, see e.g. http://stackoverflow.com/a/29871891/673826 # use imported Threads::Threads instead @@ -335,6 +345,7 @@ message(STATUS " STATIC BUILD : ${WITH_STATIC}") message(STATUS " UPnP : ${WITH_UPNP}") message(STATUS " PCH : ${WITH_PCH}") message(STATUS " MESHNET : ${WITH_MESHNET}") +message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") message(STATUS "---------------------------------------") #Handle paths nicely From 440516e95fcc6708db2e705168caadd1ac4c93a0 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 18 Sep 2016 18:42:21 -0400 Subject: [PATCH 1875/6300] detect clock skew --- HTTPServer.cpp | 1 + RouterContext.h | 3 ++- SSUSession.cpp | 18 ++++++++++++++++++ SSUSession.h | 1 + 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 2e9a02ca..2e469f98 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -180,6 +180,7 @@ namespace http { case eRouterStatusOK: s << "OK"; break; case eRouterStatusTesting: s << "Testing"; break; case eRouterStatusFirewalled: s << "Firewalled"; break; + case eRouterStatusError: s << "Error"; break; default: s << "Unknown"; } s << "
    \r\n"; diff --git a/RouterContext.h b/RouterContext.h index 27e0947d..23c03593 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -20,7 +20,8 @@ namespace i2p { eRouterStatusOK = 0, eRouterStatusTesting = 1, - eRouterStatusFirewalled = 2 + eRouterStatusFirewalled = 2, + eRouterStatusError = 3 }; class RouterContext: public i2p::garlic::GarlicDestination diff --git a/SSUSession.cpp b/SSUSession.cpp index 26c14570..ca714aed 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -272,6 +272,16 @@ namespace transport s.Insert (payload, 8); // relayTag and signed on time m_RelayTag = bufbe32toh (payload); payload += 4; // relayTag + if (i2p::context.GetStatus () == eRouterStatusTesting) + { + auto ts = i2p::util::GetSecondsSinceEpoch (); + uint32_t signedOnTime = bufbe32toh(payload); + if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) + { + LogPrint (eLogError, "SSU: clock skew detected ", (int)ts - signedOnTime, ". Check your clock"); + i2p::context.SetStatus (eRouterStatusError); + } + } payload += 4; // signed on time // decrypt signature size_t signatureLen = m_RemoteIdentity->GetSignatureLen (); @@ -310,6 +320,14 @@ namespace transport SetRemoteIdentity (std::make_shared (payload, identitySize)); m_Data.UpdatePacketSize (m_RemoteIdentity->GetIdentHash ()); payload += identitySize; // identity + auto ts = i2p::util::GetSecondsSinceEpoch (); + uint32_t signedOnTime = bufbe32toh(payload); + if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) + { + LogPrint (eLogError, "SSU message 'confirmed' time difference ", (int)ts - signedOnTime, " exceeds clock skew"); + Failed (); + return; + } if (m_SignedData) m_SignedData->Insert (payload, 4); // insert Alice's signed on time payload += 4; // signed-on time diff --git a/SSUSession.h b/SSUSession.h index 69669187..4838be2a 100644 --- a/SSUSession.h +++ b/SSUSession.h @@ -27,6 +27,7 @@ namespace transport const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes + const int SSU_CLOCK_SKEW = 60; // in seconds // payload types (4 bits) const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0; From a64e1b2aa62f28fa0d2e399afb9d23be8c3d1071 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Mon, 19 Sep 2016 20:22:15 +0300 Subject: [PATCH 1876/6300] add sliders for LeaseSets --- HTTPServer.cpp | 44 +++++++++++++------------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 2e469f98..8a63658c 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -345,50 +345,32 @@ namespace http { void ShowLeasesSets(std::stringstream& s) { - s << "
    LeaseSets:

    "; + s << "
    LeaseSets (click on to show info):

    \r\n"; + int counter = 1; // for each lease set i2p::data::netdb.VisitLeaseSets( - [&s](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) + [&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) { // create copy of lease set so we extract leases i2p::data::LeaseSet ls(leaseSet->GetBuffer(), leaseSet->GetBufferLen()); - // begin lease set entry s << "
    "; - // invalid ? + s << "'>\r\n"; if (!ls.IsValid()) - s << "
    !! Invalid !!
    "; - // ident - s << "
    " << dest.ToBase32() << "
    "; - // LeaseSet time - s << "
    expires: " << ls.GetExpirationTime() << "
    "; - // get non expired leases + s << "
    !! Invalid !!
    \r\n"; + s << "
    \r\n"; + s << "\r\n

    \r\n"; + s << "Expires: " << ls.GetExpirationTime() << "
    \r\n"; auto leases = ls.GetNonExpiredLeases(); - // show non expired leases - s << "

    Non Expired Leases: " << leases.size() << "
    "; - // for each lease - s << "
    "; + s << "Non Expired Leases: " << leases.size() << "
    \r\n"; for ( auto & l : leases ) { - // begin lease - s << "
    "; - // gateway - s << "
    Gateway: " << l->tunnelGateway.ToBase64() << "
    "; - // tunnel id - s << "
    TunnelID: " << l->tunnelID << "
    "; - // end date - s << "
    EndDate: " << l->endDate << "
    "; - // end lease - s << "
    "; + s << "Gateway: " << l->tunnelGateway.ToBase64() << "
    \r\n"; + s << "TunnelID: " << l->tunnelID << "
    \r\n"; + s << "EndDate: " << l->endDate << "
    \r\n"; } - // end for each lease - s << "
    "; - // end lease set entry - s << "
    "; - // linebreak - s << "
    "; + s << "

    \r\n
    \r\n
    \r\n"; } ); // end for each lease set From 7a51407f6d9269b819614329b621f1bbd091bddc Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 19 Sep 2016 21:37:04 -0400 Subject: [PATCH 1877/6300] show error message in the web-console --- HTTPServer.cpp | 13 ++++++++++++- RouterContext.cpp | 3 ++- RouterContext.h | 11 ++++++++++- SSUSession.cpp | 2 +- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 8a63658c..487e9c83 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -180,7 +180,18 @@ namespace http { case eRouterStatusOK: s << "OK"; break; case eRouterStatusTesting: s << "Testing"; break; case eRouterStatusFirewalled: s << "Firewalled"; break; - case eRouterStatusError: s << "Error"; break; + case eRouterStatusError: + { + s << "Error"; + switch (i2p::context.GetError ()) + { + case eRouterErrorClockSkew: + s << "
    Clock skew"; + break; + default: ; + } + break; + } default: s << "Unknown"; } s << "
    \r\n"; diff --git a/RouterContext.cpp b/RouterContext.cpp index c92b575b..6824adb8 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -18,7 +18,7 @@ namespace i2p RouterContext::RouterContext (): m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false), - m_StartupTime (0), m_Status (eRouterStatusOK ) + m_StartupTime (0), m_Status (eRouterStatusOK), m_Error (eRouterErrorNone) { } @@ -95,6 +95,7 @@ namespace i2p if (status != m_Status) { m_Status = status; + m_Error = eRouterErrorNone; switch (m_Status) { case eRouterStatusOK: diff --git a/RouterContext.h b/RouterContext.h index 23c03593..b89b3140 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -24,6 +24,12 @@ namespace i2p eRouterStatusError = 3 }; + enum RouterError + { + eRouterErrorNone = 0, + eRouterErrorClockSkew = 1 + }; + class RouterContext: public i2p::garlic::GarlicDestination { public: @@ -50,7 +56,9 @@ namespace i2p uint64_t GetBandwidthLimit () const { return m_BandwidthLimit; }; RouterStatus GetStatus () const { return m_Status; }; void SetStatus (RouterStatus status); - + RouterError GetError () const { return m_Error; }; + void SetError (RouterError error) { m_Status = eRouterStatusError; m_Error = error; }; + void UpdatePort (int port); // called from Daemon void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer); @@ -108,6 +116,7 @@ namespace i2p uint64_t m_StartupTime; // in seconds since epoch uint32_t m_BandwidthLimit; // allowed bandwidth RouterStatus m_Status; + RouterError m_Error; std::mutex m_GarlicMutex; }; diff --git a/SSUSession.cpp b/SSUSession.cpp index ca714aed..5cd59164 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -279,7 +279,7 @@ namespace transport if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) { LogPrint (eLogError, "SSU: clock skew detected ", (int)ts - signedOnTime, ". Check your clock"); - i2p::context.SetStatus (eRouterStatusError); + i2p::context.SetError (eRouterErrorClockSkew); } } payload += 4; // signed on time From 315f67225490d8e32078e58208b64147cfddc861 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Sep 2016 12:02:52 -0400 Subject: [PATCH 1878/6300] Timestamp.cpp added --- Timestamp.cpp | 10 ++++++++++ Timestamp.h | 2 ++ android/jni/Android.mk | 1 + build/CMakeLists.txt | 1 + filelist.mk | 2 +- qt/i2pd_qt/i2pd_qt.pro | 3 ++- 6 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 Timestamp.cpp diff --git a/Timestamp.cpp b/Timestamp.cpp new file mode 100644 index 00000000..f935df62 --- /dev/null +++ b/Timestamp.cpp @@ -0,0 +1,10 @@ +#include "Timestamp.h" + +namespace i2p +{ +namespace util +{ + std::chrono::system_clock::duration g_TimeOffset = std::chrono::system_clock::duration::zero (); +} +} + diff --git a/Timestamp.h b/Timestamp.h index d48cb164..2e61d856 100644 --- a/Timestamp.h +++ b/Timestamp.h @@ -8,6 +8,8 @@ namespace i2p { namespace util { + extern std::chrono::system_clock::duration g_TimeOffset; + inline uint64_t GetMillisecondsSinceEpoch () { return std::chrono::duration_cast( diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 90a679b2..a31fcfb2 100755 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -57,6 +57,7 @@ LOCAL_SRC_FILES := DaemonAndroid.cpp i2pd_android.cpp \ ../../TunnelEndpoint.cpp \ ../../TunnelGateway.cpp \ ../../TunnelPool.cpp \ + ../../Timestamp.cpp \ ../../util.cpp \ ../../i2pd.cpp ../../UPnP.cpp diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 61b8fdf9..95f223ef 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -55,6 +55,7 @@ set (LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/Datagram.cpp" "${CMAKE_SOURCE_DIR}/Family.cpp" "${CMAKE_SOURCE_DIR}/Signature.cpp" + "${CMAKE_SOURCE_DIR}/Timestamp.cpp" "${CMAKE_SOURCE_DIR}/api.cpp" ) diff --git a/filelist.mk b/filelist.mk index db243866..cb1263e3 100644 --- a/filelist.mk +++ b/filelist.mk @@ -5,7 +5,7 @@ LIB_SRC = \ SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp TransitTunnel.cpp \ Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp TunnelGateway.cpp \ Destination.cpp Base.cpp I2PEndian.cpp FS.cpp Config.cpp Family.cpp \ - Config.cpp HTTP.cpp util.cpp api.cpp + Config.cpp HTTP.cpp Timestamp.cpp util.cpp api.cpp LIB_CLIENT_SRC = \ AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \ diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 90ab6c10..0972f65c 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -35,7 +35,8 @@ SOURCES += DaemonQT.cpp mainwindow.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 ../../UPnP.cpp ../../util.cpp ../../Gzip.cpp ../../i2pd.cpp + ../../TunnelPool.cpp ../../UPnP.cpp ../../Gzip.cpp ../../Timestamp.cpp ../../util.cpp \ + ../../i2pd.cpp HEADERS += DaemonQT.h mainwindow.h \ ../../HTTPServer.h ../../I2PControl.h ../../UPnP.h ../../Daemon.h ../../Config.h \ From 2ad927b677415b7f54f92701669d6676a53a3d2f Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Sep 2016 16:18:51 -0400 Subject: [PATCH 1879/6300] NTP request --- Timestamp.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Timestamp.cpp b/Timestamp.cpp index f935df62..fbe51ea1 100644 --- a/Timestamp.cpp +++ b/Timestamp.cpp @@ -1,3 +1,7 @@ +#include +#include +#include +#include "I2PEndian.h" #include "Timestamp.h" namespace i2p @@ -5,6 +9,42 @@ namespace i2p namespace util { std::chrono::system_clock::duration g_TimeOffset = std::chrono::system_clock::duration::zero (); + + void SyncTimeWithNTP (const std::string& address) + { + boost::asio::io_service service; + boost::asio::ip::udp::resolver::query query (boost::asio::ip::udp::v4 (), address, "ntp"); + boost::system::error_code ec; + auto it = boost::asio::ip::udp::resolver (service).resolve (query, ec); + if (!ec && it != boost::asio::ip::udp::resolver::iterator()) + { + auto ep = (*it).endpoint (); // take first one + boost::asio::ip::udp::socket socket (service); + socket.open (boost::asio::ip::udp::v4 (), ec); + if (!ec) + { + uint8_t request[48];// 48 bytes NTP request + memset (request, 0, 48); + request[0] = 0x80; // client mode, version 0 + uint8_t * response = new uint8_t[1500]; // MTU + size_t len = 0; + try + { + socket.send_to (boost::asio::buffer (request, 48), ep); + len = socket.receive_from (boost::asio::buffer (response, 1500), ep); + } + catch (std::exception& e) + { + } + if (len >= 8) + { + uint32_t ts = bufbe32toh (response + 4); + if (ts > 2208988800U) ts -= 2208988800U; // 1/1/1970 from 1/1/1900 + } + delete[] response; + } + } + } } } From dba355eccd01398cba53c43878cf4766f2b11008 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 23 Sep 2016 13:15:08 -0400 Subject: [PATCH 1880/6300] use atomic_store for addresses' list re-assignment --- RouterInfo.cpp | 12 +++++++----- RouterInfo.h | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 9b2a4195..68c238ff 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -3,6 +3,8 @@ #include "I2PEndian.h" #include #include +#include +#include #include "version.h" #include "Crypto.h" #include "Base.h" @@ -17,14 +19,14 @@ namespace data { RouterInfo::RouterInfo (): m_Buffer (nullptr) { - m_Addresses = std::make_shared(); // create empty list + m_Addresses = boost::make_shared(); // create empty list } RouterInfo::RouterInfo (const std::string& fullPath): m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0) { - m_Addresses = std::make_shared(); // create empty list + m_Addresses = boost::make_shared(); // create empty list m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; ReadFromFile (); } @@ -32,7 +34,7 @@ namespace data RouterInfo::RouterInfo (const uint8_t * buf, int len): m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0) { - m_Addresses = std::make_shared(); // create empty list + m_Addresses = boost::make_shared(); // create empty list m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; memcpy (m_Buffer, buf, len); m_BufferLen = len; @@ -154,7 +156,7 @@ namespace data s.read ((char *)&m_Timestamp, sizeof (m_Timestamp)); m_Timestamp = be64toh (m_Timestamp); // read addresses - auto addresses = std::make_shared(); + auto addresses = boost::make_shared(); uint8_t numAddresses; s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return; bool introducers = false; @@ -255,7 +257,7 @@ namespace data m_SupportedTransports |= supportedTransports; } } - m_Addresses = addresses; + boost::atomic_store (&m_Addresses, addresses); // read peers uint8_t numPeers; s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return; diff --git a/RouterInfo.h b/RouterInfo.h index f4c077ef..e8a4ea75 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "Identity.h" #include "Profiling.h" @@ -201,7 +202,7 @@ namespace data uint8_t * m_Buffer; size_t m_BufferLen; uint64_t m_Timestamp; - std::shared_ptr m_Addresses; + boost::shared_ptr m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9 std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; uint8_t m_SupportedTransports, m_Caps; From 08c1359a275b2fb0a03afd4955e309fadbbf347e Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Sat, 24 Sep 2016 13:50:14 +0300 Subject: [PATCH 1881/6300] fixed MiniUPnP link --- docs/build_notes_windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index 827d0123..e0fb1d8c 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -97,7 +97,7 @@ Requirements for building: * [CMake](https://cmake.org/) (tested with 3.1.3) * [Visual Studio Community Edition](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) (tested with VS2013 Update 4) * [Boost](http://www.boost.org/) (tested with 1.59) -* Optionally [MiniUPnP](http://miniupnp.free.f) (tested with 1.9), we need only few client headers +* Optionally [MiniUPnP](http://miniupnp.free.fr) (tested with 1.9), we need only few client headers * OpenSSL (tested with 1.0.1p and 1.0.2e), if building from sources (recommended), you'll need as well * [Netwide assembler](www.nasm.us) * Strawberry Perl or ActiveState Perl, do NOT try msys2 perl, it won't work From fa092c01623982895dc55be12cd46326b761cefc Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Sat, 24 Sep 2016 14:26:59 +0300 Subject: [PATCH 1882/6300] Fixed links --- docs/build_notes_windows.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index e0fb1d8c..a217d80d 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -27,7 +27,7 @@ msys2 ### x86 (32-bit architecture) -Get install file msys2-i686-20150916.exe from https://msys2.github.io. +Get install file msys2-i686-*.exe from https://msys2.github.io. open MSYS2 Shell (from Start menu). Install all prerequisites and download i2pd source: @@ -44,7 +44,7 @@ make ### x64 (64-bit architecture) -Get install file msys2-x86_64-20150916.exe from https://msys2.github.io. +Get install file msys2-x86_64-*.exe from https://msys2.github.io. open MSYS2 Shell (from Start menu). Install all prerequisites and download i2pd source: @@ -99,7 +99,7 @@ Requirements for building: * [Boost](http://www.boost.org/) (tested with 1.59) * Optionally [MiniUPnP](http://miniupnp.free.fr) (tested with 1.9), we need only few client headers * OpenSSL (tested with 1.0.1p and 1.0.2e), if building from sources (recommended), you'll need as well - * [Netwide assembler](www.nasm.us) + * [Netwide assembler](http://www.nasm.us/) * Strawberry Perl or ActiveState Perl, do NOT try msys2 perl, it won't work From e5e09c9b5119e8197c41526241790e66da2ecd21 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 24 Sep 2016 08:29:08 -0400 Subject: [PATCH 1883/6300] check for boost version --- RouterInfo.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 68c238ff..ff08286a 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -3,8 +3,10 @@ #include "I2PEndian.h" #include #include -#include #include +#if (BOOST_VERSION >= 105300) +#include +#endif #include "version.h" #include "Crypto.h" #include "Base.h" @@ -257,7 +259,11 @@ namespace data m_SupportedTransports |= supportedTransports; } } +#if (BOOST_VERSION >= 105300) boost::atomic_store (&m_Addresses, addresses); +#else + m_Addresses = addresses; // race condition +#endif // read peers uint8_t numPeers; s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return; From d025ba279334a718d3f62be1c59fbe4d90e096e2 Mon Sep 17 00:00:00 2001 From: alexandr Date: Mon, 26 Sep 2016 01:37:00 +0500 Subject: [PATCH 1884/6300] Fixed visibility of variable outboundTunnel --- I2CP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2CP.cpp b/I2CP.cpp index 0352cd15..41f81155 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -109,7 +109,7 @@ namespace client } else { - auto outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); + outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); auto leases = remote->GetNonExpiredLeases (); if (!leases.empty ()) remoteLease = leases[rand () % leases.size ()]; From b03712a30e71cb654f259f894d519d6a9eb9d67e Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 25 Sep 2016 17:23:21 -0400 Subject: [PATCH 1885/6300] correct outbound tunnel selection --- I2CP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2CP.cpp b/I2CP.cpp index 0352cd15..41f81155 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -109,7 +109,7 @@ namespace client } else { - auto outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); + outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); auto leases = remote->GetNonExpiredLeases (); if (!leases.empty ()) remoteLease = leases[rand () % leases.size ()]; From d6aca6fa00b322e9f965327641e8d2a68edadd1d Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 29 Sep 2016 11:24:52 -0400 Subject: [PATCH 1886/6300] always send reply --- I2CP.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 41f81155..e0139b9f 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -66,12 +66,19 @@ namespace client memcpy (buf + 4, payload, len); msg->len += len + 4; msg->FillI2NPMessageHeader (eI2NPData); + auto s = GetSharedFromThis (); auto remote = FindLeaseSet (ident); if (remote) - GetService ().post (std::bind (&I2CPDestination::SendMsg, GetSharedFromThis (), msg, remote)); + { + GetService ().post ( + [s, msg, remote, nonce]() + { + bool sent = s->SendMsg (msg, remote); + s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); + }); + } else { - auto s = GetSharedFromThis (); RequestDestination (ident, [s, msg, nonce](std::shared_ptr ls) { From e4d60929392de06f9430ca5e442746f9aab133c6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 1 Oct 2016 15:05:35 -0400 Subject: [PATCH 1887/6300] copy addresses list atomically --- RouterInfo.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RouterInfo.cpp b/RouterInfo.cpp index ff08286a..f445ca02 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -771,7 +771,11 @@ namespace data std::shared_ptr RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const { +#if (BOOST_VERSION >= 105300) + auto addresses = boost::atomic_load (&m_Addresses); +#else auto addresses = m_Addresses; +#endif for (const auto& address : *addresses) { if (address->transportStyle == s) From f79ad91a9a271058e8a6dc40d80f855e8999be3d Mon Sep 17 00:00:00 2001 From: alexandr Date: Mon, 3 Oct 2016 20:01:31 +0500 Subject: [PATCH 1888/6300] probably fix hanging of call I2CP-SendMsgTo-FindLeaseSet --- Destination.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 48717f5f..f06c7711 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -171,7 +171,7 @@ namespace client std::shared_ptr LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident) { - std::lock_guard lock(m_RemoteLeaseSetsMutex); + std::unique_lock lock(m_RemoteLeaseSetsMutex); auto it = m_RemoteLeaseSets.find (ident); if (it != m_RemoteLeaseSets.end ()) { @@ -186,7 +186,6 @@ namespace client { ls->PopulateLeases(); { - std::lock_guard _lock(m_RemoteLeaseSetsMutex); m_RemoteLeaseSets[ident] = ls; } } @@ -203,15 +202,12 @@ namespace client if (ls && !ls->IsExpired ()) { ls->PopulateLeases (); // since we don't store them in netdb - { - std::lock_guard lock(m_RemoteLeaseSetsMutex); - m_RemoteLeaseSets[ident] = ls; - } + m_RemoteLeaseSets[ident] = ls; return ls; } } return nullptr; - } + } std::shared_ptr LeaseSetDestination::GetLeaseSet () { From 8ff2627e8eef895bc840d335aeca5db947077d79 Mon Sep 17 00:00:00 2001 From: alexandr Date: Mon, 3 Oct 2016 20:06:10 +0500 Subject: [PATCH 1889/6300] minimize count of errors "I2CP: Failed to send message. No outbound tunnels" --- I2CP.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index e0139b9f..04f21408 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -103,7 +103,8 @@ namespace client } auto path = remoteSession->GetSharedRoutingPath (); std::shared_ptr outboundTunnel; - std::shared_ptr remoteLease; + std::shared_ptr remoteLease; + bool unconfirmedTags=false; if (path) { if (!remoteSession->CleanupUnconfirmedTags ()) // no stuck tags @@ -112,9 +113,12 @@ namespace client remoteLease = path->remoteLease; } else + { remoteSession->SetSharedRoutingPath (nullptr); + unconfirmedTags=true; + } } - else + if (!path || unconfirmedTags) { outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); auto leases = remote->GetNonExpiredLeases (); From 31dde394ebff8e08f00d515722e2508d065867b1 Mon Sep 17 00:00:00 2001 From: alexandr Date: Mon, 3 Oct 2016 20:20:45 +0500 Subject: [PATCH 1890/6300] remove unnecessary brackets --- Destination.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index f06c7711..dbdec0c7 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -185,9 +185,7 @@ namespace client if(ls && !ls->IsExpired()) { ls->PopulateLeases(); - { - m_RemoteLeaseSets[ident] = ls; - } + m_RemoteLeaseSets[ident] = ls; } }); } From 404715e02d4b565d0f8869c80f0e20094d8f97ba Mon Sep 17 00:00:00 2001 From: brain5lug Date: Mon, 3 Oct 2016 23:24:22 +0300 Subject: [PATCH 1891/6300] thread sanitizer configuration option have been added --- build/CMakeLists.txt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 95f223ef..00048942 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -16,7 +16,8 @@ option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_PCH "Use precompiled header" OFF) option(WITH_GUI "Include GUI (currently MS Windows only)" ON) option(WITH_MESHNET "Build for cjdns test network" OFF) -option(WITH_ADDRSANITIZER "Build with address sanitizer (linux only)" OFF) +option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) +option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) # paths set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) @@ -190,10 +191,22 @@ if (WITH_ADDRSANITIZER) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer" ) set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address" ) else () - error ("MSVC does not support address sanitizer option") + message( SEND_ERROR "MSVC does not support address sanitizer option") endif() endif() +if (WITH_THREADSANITIZER) + if (WITH_ADDRSANITIZER) + message( FATAL_ERROR "thread sanitizer option cannot be combined with address sanitizer") + elseif (NOT MSVC) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread" ) + set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread" ) + else () + message( SEND_ERROR "MSVC does not support address sanitizer option") + endif() +endif() + + # libraries # TODO: once CMake 3.1+ becomes mainstream, see e.g. http://stackoverflow.com/a/29871891/673826 # use imported Threads::Threads instead @@ -347,6 +360,7 @@ message(STATUS " UPnP : ${WITH_UPNP}") message(STATUS " PCH : ${WITH_PCH}") message(STATUS " MESHNET : ${WITH_MESHNET}") message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") +message(STATUS " THEADSANITIZER : ${WITH_THREADSANITIZER}") message(STATUS "---------------------------------------") #Handle paths nicely From 5350078543d0ac04a608f2c03efd4b9e1b8f9509 Mon Sep 17 00:00:00 2001 From: brain5lug Date: Tue, 4 Oct 2016 00:24:42 +0300 Subject: [PATCH 1892/6300] Unused parameter warnings removal --- AddressBook.cpp | 3 ++- BOB.cpp | 42 +++++++++++++++++++++--------------------- Destination.cpp | 6 +++--- Family.cpp | 2 +- I2CP.cpp | 15 ++++++++------- I2PControl.cpp | 8 ++++---- I2PTunnel.cpp | 5 +++-- LeaseSet.cpp | 2 +- RouterContext.cpp | 2 +- SAM.cpp | 15 ++++++++------- SOCKS.cpp | 4 ++-- SSUSession.cpp | 6 +++--- Streaming.cpp | 2 +- TransitTunnel.cpp | 4 ++-- Transports.cpp | 4 ++-- Tunnel.cpp | 6 +++--- 16 files changed, 65 insertions(+), 61 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index b993f456..67eb7566 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -599,7 +599,8 @@ namespace client } } - void AddressBook::HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + void AddressBook::HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t /*fromPort*/, + uint16_t /*toPort*/, const uint8_t * buf, size_t len) { if (len < 44) { diff --git a/BOB.cpp b/BOB.cpp index 8ffffba6..2d96f4b2 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -284,7 +284,7 @@ namespace client std::placeholders::_1, std::placeholders::_2)); } - void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) + void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t /*bytes_transferred*/) { if (ecode) { @@ -338,20 +338,20 @@ namespace client Send (len); } - void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len) + void BOBCommandSession::ZapCommandHandler (const char * /*operand*/, size_t /*len*/) { LogPrint (eLogDebug, "BOB: zap"); Terminate (); } - void BOBCommandSession::QuitCommandHandler (const char * operand, size_t len) + void BOBCommandSession::QuitCommandHandler (const char * /*operand*/, size_t /*len*/) { LogPrint (eLogDebug, "BOB: quit"); m_IsOpen = false; SendReplyOK ("Bye!"); } - void BOBCommandSession::StartCommandHandler (const char * operand, size_t len) + void BOBCommandSession::StartCommandHandler (const char * /*operand*/, size_t /*len*/) { LogPrint (eLogDebug, "BOB: start ", m_Nickname); if (m_IsActive) @@ -373,7 +373,7 @@ namespace client m_IsActive = true; } - void BOBCommandSession::StopCommandHandler (const char * operand, size_t len) + void BOBCommandSession::StopCommandHandler (const char * /*operand*/, size_t /*len*/) { LogPrint (eLogDebug, "BOB: stop ", m_Nickname); if (!m_IsActive) @@ -392,7 +392,7 @@ namespace client m_IsActive = false; } - void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len) + void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t /*len*/) { LogPrint (eLogDebug, "BOB: setnick ", operand); m_Nickname = operand; @@ -401,7 +401,7 @@ namespace client SendReplyOK (msg.c_str ()); } - void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t len) + void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t /*len*/) { LogPrint (eLogDebug, "BOB: getnick ", operand); m_CurrentDestination = m_Owner.FindDestination (operand); @@ -420,40 +420,40 @@ namespace client SendReplyError ("no nickname has been set"); } - void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len) + void BOBCommandSession::NewkeysCommandHandler (const char* /*operand*/, size_t /*len*/) { LogPrint (eLogDebug, "BOB: newkeys"); m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (); SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } - void BOBCommandSession::SetkeysCommandHandler (const char * operand, size_t len) + void BOBCommandSession::SetkeysCommandHandler (const char* operand, size_t /*len*/) { LogPrint (eLogDebug, "BOB: setkeys ", operand); m_Keys.FromBase64 (operand); SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } - void BOBCommandSession::GetkeysCommandHandler (const char * operand, size_t len) + void BOBCommandSession::GetkeysCommandHandler (const char* /*operand*/, size_t /*len*/) { LogPrint (eLogDebug, "BOB: getkeys"); SendReplyOK (m_Keys.ToBase64 ().c_str ()); } - void BOBCommandSession::GetdestCommandHandler (const char * operand, size_t len) + void BOBCommandSession::GetdestCommandHandler (const char* /*operand*/, size_t /*len*/) { LogPrint (eLogDebug, "BOB: getdest"); SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } - void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len) + void BOBCommandSession::OuthostCommandHandler (const char* operand, size_t /*len*/) { LogPrint (eLogDebug, "BOB: outhost ", operand); m_Address = operand; SendReplyOK ("outhost set"); } - void BOBCommandSession::OutportCommandHandler (const char * operand, size_t len) + void BOBCommandSession::OutportCommandHandler (const char* operand, size_t /*len*/) { LogPrint (eLogDebug, "BOB: outport ", operand); m_OutPort = boost::lexical_cast(operand); @@ -463,14 +463,14 @@ namespace client SendReplyError ("port out of range"); } - void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len) + void BOBCommandSession::InhostCommandHandler (const char* operand, size_t /*len*/) { LogPrint (eLogDebug, "BOB: inhost ", operand); m_Address = operand; SendReplyOK ("inhost set"); } - void BOBCommandSession::InportCommandHandler (const char * operand, size_t len) + void BOBCommandSession::InportCommandHandler (const char* operand, size_t /*len*/) { LogPrint (eLogDebug, "BOB: inport ", operand); m_InPort = boost::lexical_cast(operand); @@ -480,7 +480,7 @@ namespace client SendReplyError ("port out of range"); } - void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len) + void BOBCommandSession::QuietCommandHandler (const char* /*operand*/, size_t /*len*/) { LogPrint (eLogDebug, "BOB: quiet"); if (m_Nickname.length () > 0) @@ -497,7 +497,7 @@ namespace client SendReplyError ("no nickname has been set"); } - void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len) + void BOBCommandSession::LookupCommandHandler (const char* operand, size_t /*len*/) { LogPrint (eLogDebug, "BOB: lookup ", operand); i2p::data::IdentHash ident; @@ -525,7 +525,7 @@ namespace client } } - void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len) + void BOBCommandSession::ClearCommandHandler (const char* /*operand*/, size_t /*len*/) { LogPrint (eLogDebug, "BOB: clear"); m_Owner.DeleteDestination (m_Nickname); @@ -533,7 +533,7 @@ namespace client SendReplyOK ("cleared"); } - void BOBCommandSession::ListCommandHandler (const char * operand, size_t len) + void BOBCommandSession::ListCommandHandler (const char* /*operand*/, size_t /*len*/) { LogPrint (eLogDebug, "BOB: list"); const auto& destinations = m_Owner.GetDestinations (); @@ -542,7 +542,7 @@ namespace client SendReplyOK ("Listing done"); } - void BOBCommandSession::OptionCommandHandler (const char * operand, size_t len) + void BOBCommandSession::OptionCommandHandler (const char* operand, size_t /*len*/) { LogPrint (eLogDebug, "BOB: option ", operand); const char * value = strchr (operand, '='); @@ -561,7 +561,7 @@ namespace client SendReplyError ("malformed"); } - void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len) + void BOBCommandSession::StatusCommandHandler (const char* operand, size_t /*len*/) { LogPrint (eLogDebug, "BOB: status ", operand); if (m_Nickname == operand) diff --git a/Destination.cpp b/Destination.cpp index 48717f5f..0aefa6ab 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -265,7 +265,7 @@ namespace client m_Service.post (std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msg)); } - void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) + void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t /*len*/, std::shared_ptr from) { uint8_t typeID = buf[I2NP_HEADER_TYPEID_OFFSET]; switch (typeID) @@ -353,7 +353,7 @@ namespace client } } - void LeaseSetDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len) + void LeaseSetDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t /*len*/) { i2p::data::IdentHash key (buf); int num = buf[32]; // num @@ -737,7 +737,7 @@ namespace client ScheduleCheckForReady(p); } - void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) + void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t /*len*/) { uint32_t length = bufbe32toh (buf); buf += 4; diff --git a/Family.cpp b/Family.cpp index c1840e51..b37af204 100644 --- a/Family.cpp +++ b/Family.cpp @@ -110,7 +110,7 @@ namespace data } bool Families::VerifyFamily (const std::string& family, const IdentHash& ident, - const char * signature, const char * key) + const char * signature, const char * /*key*/) { uint8_t buf[50], signatureBuf[64]; size_t len = family.length (), signatureLen = strlen (signature); diff --git a/I2CP.cpp b/I2CP.cpp index 41f81155..3c8ed9bb 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -186,7 +186,7 @@ namespace client std::bind (&I2CPSession::HandleReceivedHeader, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } - void I2CPSession::HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t bytes_transferred) + void I2CPSession::HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t /*bytes_transferred*/) { if (ecode) Terminate (); @@ -213,7 +213,7 @@ namespace client std::bind (&I2CPSession::HandleReceivedPayload, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } - void I2CPSession::HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred) + void I2CPSession::HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t /*bytes_transferred*/) { if (ecode) Terminate (); @@ -270,7 +270,8 @@ namespace client LogPrint (eLogError, "I2CP: Can't write to the socket"); } - void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf) + void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, + std::size_t /*bytes_transferred*/, const uint8_t* buf) { delete[] buf; if (ecode && ecode != boost::asio::error::operation_aborted) @@ -385,7 +386,7 @@ namespace client } } - void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len) + void I2CPSession::DestroySessionMessageHandler (const uint8_t* /*buf*/, size_t /*len*/) { SendSessionStatusMessage (0); // destroy LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " destroyed"); @@ -396,7 +397,7 @@ namespace client } } - void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len) + void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t* /*buf*/, size_t /*len*/) { // TODO: implement actual reconfiguration SendSessionStatusMessage (2); // updated @@ -558,7 +559,7 @@ namespace client } } - void I2CPSession::DestLookupMessageHandler (const uint8_t * buf, size_t len) + void I2CPSession::DestLookupMessageHandler (const uint8_t * buf, size_t /*len*/) { if (m_Destination) { @@ -595,7 +596,7 @@ namespace client SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, buf, 32); } - void I2CPSession::GetBandwidthLimitsMessageHandler (const uint8_t * buf, size_t len) + void I2CPSession::GetBandwidthLimitsMessageHandler (const uint8_t* /*buf*/, size_t /*len*/) { uint8_t limits[64]; memset (limits, 0, 64); diff --git a/I2PControl.cpp b/I2PControl.cpp index 3e2e3997..7f063f35 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -295,8 +295,8 @@ namespace client std::placeholders::_1, std::placeholders::_2, socket, buf)); } - void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, - std::shared_ptr socket, std::shared_ptr buf) + void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t /*bytes_transferred*/, + std::shared_ptr /*socket*/, std::shared_ptr /*buf*/) { if (ecode) { LogPrint (eLogError, "I2PControl: write error: ", ecode.message ()); @@ -453,7 +453,7 @@ namespace client InsertParam (results, "Shutdown", ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent m_ShutdownTimer.async_wait ( - [](const boost::system::error_code& ecode) + [](const boost::system::error_code&) { Daemon.running = 0; }); @@ -467,7 +467,7 @@ namespace client InsertParam (results, "ShutdownGraceful", ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second m_ShutdownTimer.async_wait ( - [](const boost::system::error_code& ecode) + [](const boost::system::error_code&) { Daemon.running = 0; }); diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 0c58ba9d..a9c5c313 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -439,7 +439,7 @@ namespace client } void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, - std::shared_ptr resolver) + std::shared_ptr /*resolver*/) { if (!ecode) { @@ -730,7 +730,8 @@ namespace client m_Session = new UDPSession(m_LocalEndpoint, m_LocalDest, ep, *m_RemoteIdent, LocalPort, RemotePort); } - void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t /*fromPort*/, + uint16_t /*toPort*/, const uint8_t * buf, size_t len) { if(m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) { diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 04dc77c5..4ddd07f3 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -173,7 +173,7 @@ namespace data const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const { - return GetNonExpiredLeasesExcluding( [] (const Lease & l) -> bool { return false; }, withThreshold); + return GetNonExpiredLeasesExcluding( [] (const Lease &) -> bool { return false; }, withThreshold); } const std::vector > LeaseSet::GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold) const diff --git a/RouterContext.cpp b/RouterContext.cpp index 6824adb8..15084bd3 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -424,7 +424,7 @@ namespace i2p return i2p::tunnel::tunnels.GetExploratoryPool (); } - void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) + void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t /*len*/, std::shared_ptr from) { i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); } diff --git a/SAM.cpp b/SAM.cpp index 2864cd6f..ce85fc8d 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -135,7 +135,7 @@ namespace client } } - void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred) + void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t /*bytes_transferred*/) { if (ecode) { @@ -166,7 +166,7 @@ namespace client } } - void SAMSocket::HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close) + void SAMSocket::HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t /*bytes_transferred*/, bool close) { if (ecode) { @@ -262,7 +262,7 @@ namespace client } } - void SAMSocket::ProcessSessionCreate (char * buf, size_t len) + void SAMSocket::ProcessSessionCreate (char * buf, size_t /*len*/) { LogPrint (eLogDebug, "SAM: session create: ", buf); std::map params; @@ -333,7 +333,7 @@ namespace client SendMessageReply (m_Buffer, l2, false); } - void SAMSocket::ProcessStreamConnect (char * buf, size_t len) + void SAMSocket::ProcessStreamConnect (char * buf, size_t /*len*/) { LogPrint (eLogDebug, "SAM: stream connect: ", buf); std::map params; @@ -389,7 +389,7 @@ namespace client } } - void SAMSocket::ProcessStreamAccept (char * buf, size_t len) + void SAMSocket::ProcessStreamAccept (char * buf, size_t /*len*/) { LogPrint (eLogDebug, "SAM: stream accept: ", buf); std::map params; @@ -460,7 +460,7 @@ namespace client SendMessageReply (m_Buffer, len, false); } - void SAMSocket::ProcessNamingLookup (char * buf, size_t len) + void SAMSocket::ProcessNamingLookup (char * buf, size_t /*len*/) { LogPrint (eLogDebug, "SAM: naming lookup: ", buf); std::map params; @@ -652,7 +652,8 @@ namespace client LogPrint (eLogWarning, "SAM: I2P acceptor has been reset"); } - void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t /*fromPort*/, + uint16_t /*toPort*/, const uint8_t* buf, size_t len) { LogPrint (eLogDebug, "SAM: datagram received ", len); auto base64 = from.ToBase64 (); diff --git a/SOCKS.cpp b/SOCKS.cpp index 9d85963b..a52fe612 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -696,7 +696,7 @@ namespace proxy } - void SOCKSHandler::HandleUpstreamData(uint8_t * dataptr, std::size_t len) + void SOCKSHandler::HandleUpstreamData(uint8_t* /*dataptr*/, std::size_t len) { if (m_state == UPSTREAM_HANDSHAKE) { m_upstream_response_len += len; @@ -739,7 +739,7 @@ namespace proxy } } - void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) + void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator) { if (ecode) { LogPrint(eLogWarning, "SOCKS: could not connect to upstream proxy: ", ecode.message()); diff --git a/SSUSession.cpp b/SSUSession.cpp index 5cd59164..fbb5b11d 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -536,7 +536,7 @@ namespace transport Send (buf, msgLen); } - void SSUSession::ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) + void SSUSession::ProcessRelayRequest (const uint8_t * buf, size_t /*len*/, const boost::asio::ip::udp::endpoint& from) { uint32_t relayTag = bufbe32toh (buf); auto session = m_Server.FindRelaySession (relayTag); @@ -637,7 +637,7 @@ namespace transport LogPrint (eLogDebug, "SSU: relay intro sent"); } - void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len) + void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t /*len*/) { LogPrint (eLogDebug, "SSU message: Relay response received"); uint8_t remoteSize = *buf; @@ -689,7 +689,7 @@ namespace transport LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce); } - void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) + void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t /*len*/) { uint8_t size = *buf; if (size == 4) diff --git a/Streaming.cpp b/Streaming.cpp index 02e738c8..e8f5a897 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -729,7 +729,7 @@ namespace stream } } - void Stream::HandleAckSendTimer (const boost::system::error_code& ecode) + void Stream::HandleAckSendTimer (const boost::system::error_code&) { if (m_IsAckSendScheduled) { diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index dfe01a05..d06f5134 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -51,12 +51,12 @@ namespace tunnel } } - void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) + void TransitTunnel::SendTunnelDataMsg (std::shared_ptr /*msg*/) { LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); } - void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr /*msg*/) { LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); } diff --git a/Transports.cpp b/Transports.cpp index a29cac15..e0d2df7a 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -394,7 +394,7 @@ namespace transport } void Transports::HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, - i2p::data::IdentHash ident, std::shared_ptr resolver) + i2p::data::IdentHash ident, std::shared_ptr /*resolver*/) { auto it1 = m_Peers.find (ident); if (it1 != m_Peers.end ()) @@ -437,7 +437,7 @@ namespace transport } void Transports::HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, - i2p::data::IdentHash ident, std::shared_ptr resolver) + i2p::data::IdentHash ident, std::shared_ptr /*resolver*/) { auto it1 = m_Peers.find (ident); if (it1 != m_Peers.end ()) diff --git a/Tunnel.cpp b/Tunnel.cpp index 7d2e6735..3749c9ca 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -90,7 +90,7 @@ namespace tunnel i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); } - bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) + bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t /*len*/) { LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); @@ -161,7 +161,7 @@ namespace tunnel } } - void Tunnel::SendTunnelDataMsg (std::shared_ptr msg) + void Tunnel::SendTunnelDataMsg (std::shared_ptr /*msg*/) { LogPrint (eLogWarning, "Tunnel: Can't send I2NP messages without delivery instructions"); } @@ -256,7 +256,7 @@ namespace tunnel m_Gateway.SendBuffer (); } - void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr /*msg*/) { LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ()); } From 012ade500061da29693340a48cdd6b8e67d02293 Mon Sep 17 00:00:00 2001 From: Pavel Melkozerov Date: Tue, 4 Oct 2016 18:13:45 +0300 Subject: [PATCH 1893/6300] Added extra-cmake-modules --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index bb81757b..be16e890 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -142,9 +142,9 @@ install: - if not defined msvc ( C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -Sy bash pacman pacman-mirrors msys2-runtime msys2-runtime-devel cmake" && if "%x64%" == "1" ( - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-x86_64-openssl mingw-w64-x86_64-boost mingw-w64-x86_64-miniupnpc" + C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-x86_64-openssl mingw-w64-x86_64-boost mingw-w64-x86_64-miniupnpc mingw-w64-x86_64-extra-cmake-modules" ) else ( - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-i686-openssl mingw-w64-i686-boost mingw-w64-i686-miniupnpc" + C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-i686-openssl mingw-w64-i686-boost mingw-w64-i686-miniupnpc mingw-w64-i686-extra-cmake-modules" ) ) cache: From e8e3db688803309c7411b29f7b6ca0c159872f98 Mon Sep 17 00:00:00 2001 From: alexandr Date: Wed, 5 Oct 2016 01:20:43 +0500 Subject: [PATCH 1894/6300] fix f79ad91 --- Destination.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Destination.cpp b/Destination.cpp index dbdec0c7..0b8f2859 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -185,6 +185,7 @@ namespace client if(ls && !ls->IsExpired()) { ls->PopulateLeases(); + std::unique_lock lock(m_RemoteLeaseSetsMutex); m_RemoteLeaseSets[ident] = ls; } }); From cb0f9684673432dfe391985c82ca5b1b948c0277 Mon Sep 17 00:00:00 2001 From: alexandr Date: Wed, 5 Oct 2016 06:45:41 +0500 Subject: [PATCH 1895/6300] Added building option "USE_ASLR" --- Makefile.mingw | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile.mingw b/Makefile.mingw index 85b6b455..5a8e8fc0 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -37,11 +37,16 @@ ifeq ($(USE_WIN32_APP), yes) DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif -ifeq ($(USE_AESNI),1) +ifeq ($(USE_AESNI),yes) CPU_FLAGS = -maes -DAESNI else CPU_FLAGS = -msse endif +ifeq ($(USE_ASLR),yes) + LDFLAGS += -Wl,--nxcompat -Wl,--high-entropy-va \ + -Wl,--dynamicbase,--export-all-symbols +endif + obj/%.o : %.rc $(WINDRES) -i $< -o $@ From ae2b5dfd3ef4f6b343e2d4f81162444164049a64 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 4 Oct 2016 17:50:18 -0400 Subject: [PATCH 1896/6300] fix udp tunnel route switching logic --- Datagram.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index b9188864..92c2509c 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -276,7 +276,7 @@ namespace datagram // our path looks dead so we need to rotate paths if (now - m_LastSuccess >= DATAGRAM_SESSION_PATH_TIMEOUT) return true; // if we have a routing session and routing path we don't need to switch paths - return m_RoutingSession != nullptr && m_RoutingSession->GetSharedRoutingPath () != nullptr; + return m_RoutingSession == nullptr || m_RoutingSession->GetSharedRoutingPath () == nullptr; } @@ -291,7 +291,7 @@ namespace datagram if(currentLease) // if we have a lease return true if it's about to expire otherwise return false return currentLease->ExpiresWithin( DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW, DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE ); // we have no current lease, we should switch - return true; + return currentLease == nullptr; } std::shared_ptr DatagramSession::GetNextRoutingPath() From 30dfe129106dd2bb5240d3d877c03f378bc10b20 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 5 Oct 2016 10:45:41 -0400 Subject: [PATCH 1897/6300] try fixing appveyor --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index bb81757b..ddacb73b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,7 +22,7 @@ init: if exist \OpenSSL rmdir /S /Q \OpenSSL environment: - BOOST_ROOT: C:\Libraries\boost_1_59_0 + BOOST_ROOT: C:\Libraries\boost_1_60_0 MINIUPNPC: miniupnpc-1.9.20151026 OPENSSL: OpenSSL_1_0_2e ZLIB: zlib-1.2.8 @@ -167,7 +167,7 @@ build_script: echo "bitness=%bitness%; static=%static%; dll=%dll%; type=%type%; generator=%generator%; variant=%variant%; cmake=%cmake%; cmake_extra=%cmake_extra%" - if not defined msvc ( - C:\msys64\usr\bin\bash -lc "export PATH=/mingw%bitness%/bin:/usr/bin && cd /c/projects/build && CC=/mingw%bitness%/bin/gcc.exe CXX=/mingw%bitness%/bin/g++.exe /usr/bin/cmake /c/projects/i2pd/build -G 'Unix Makefiles' -DWITH_AESNI=ON -DWITH_UPNP=ON %cmake% %cmake_extra% -DWITH_STATIC=%static% -DWITH_HARDENING=ON -DCMAKE_INSTALL_PREFIX:PATH=/c/projects/instdir -DCMAKE_FIND_ROOT_PATH=/mingw%bitness% && make install" + C:\msys64\usr\bin\bash -lc "export PATH=/mingw%bitness%/bin:/usr/bin && cd /c/projects/build && CC=/mingw%bitness%/bin/gcc.exe CXX=/mingw%bitness%/bin/g++.exe /usr/bin/cmake /c/projects/i2pd/build -G 'Unix Makefiles' -DBOOSTROOT=/mingw%bitness%/ -DWITH_AESNI=ON -DWITH_UPNP=ON %cmake% %cmake_extra% -DWITH_STATIC=%static% -DWITH_HARDENING=ON -DCMAKE_INSTALL_PREFIX:PATH=/c/projects/instdir -DCMAKE_FIND_ROOT_PATH=/mingw%bitness% && make install" && 7z a -tzip -mx9 -mmt C:\projects\i2pd\i2pd-mingw-win%bitness%-%type%.zip C:\projects\instdir\* C:\msys64\mingw%bitness%\bin\zlib1.dll C:\msys64\mingw%bitness%\bin\*eay32.dll ) - rem We are fine with multiple generated configurations in MS solution. Will use later From 4a3bf46c30878bf5d3bcacd3eda11b285b219c19 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 5 Oct 2016 11:03:51 -0400 Subject: [PATCH 1898/6300] Revert "try fixing appveyor" This reverts commit 30dfe129106dd2bb5240d3d877c03f378bc10b20. --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ddacb73b..bb81757b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,7 +22,7 @@ init: if exist \OpenSSL rmdir /S /Q \OpenSSL environment: - BOOST_ROOT: C:\Libraries\boost_1_60_0 + BOOST_ROOT: C:\Libraries\boost_1_59_0 MINIUPNPC: miniupnpc-1.9.20151026 OPENSSL: OpenSSL_1_0_2e ZLIB: zlib-1.2.8 @@ -167,7 +167,7 @@ build_script: echo "bitness=%bitness%; static=%static%; dll=%dll%; type=%type%; generator=%generator%; variant=%variant%; cmake=%cmake%; cmake_extra=%cmake_extra%" - if not defined msvc ( - C:\msys64\usr\bin\bash -lc "export PATH=/mingw%bitness%/bin:/usr/bin && cd /c/projects/build && CC=/mingw%bitness%/bin/gcc.exe CXX=/mingw%bitness%/bin/g++.exe /usr/bin/cmake /c/projects/i2pd/build -G 'Unix Makefiles' -DBOOSTROOT=/mingw%bitness%/ -DWITH_AESNI=ON -DWITH_UPNP=ON %cmake% %cmake_extra% -DWITH_STATIC=%static% -DWITH_HARDENING=ON -DCMAKE_INSTALL_PREFIX:PATH=/c/projects/instdir -DCMAKE_FIND_ROOT_PATH=/mingw%bitness% && make install" + C:\msys64\usr\bin\bash -lc "export PATH=/mingw%bitness%/bin:/usr/bin && cd /c/projects/build && CC=/mingw%bitness%/bin/gcc.exe CXX=/mingw%bitness%/bin/g++.exe /usr/bin/cmake /c/projects/i2pd/build -G 'Unix Makefiles' -DWITH_AESNI=ON -DWITH_UPNP=ON %cmake% %cmake_extra% -DWITH_STATIC=%static% -DWITH_HARDENING=ON -DCMAKE_INSTALL_PREFIX:PATH=/c/projects/instdir -DCMAKE_FIND_ROOT_PATH=/mingw%bitness% && make install" && 7z a -tzip -mx9 -mmt C:\projects\i2pd\i2pd-mingw-win%bitness%-%type%.zip C:\projects\instdir\* C:\msys64\mingw%bitness%\bin\zlib1.dll C:\msys64\mingw%bitness%\bin\*eay32.dll ) - rem We are fine with multiple generated configurations in MS solution. Will use later From 71d4221af2604850363ed890949fe5227af85cd3 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 5 Oct 2016 10:06:06 -0400 Subject: [PATCH 1899/6300] add keyinfo tool --- .gitignore | 10 ++++- Makefile | 8 +++- contrib/tools/Makefile | 7 ++++ contrib/tools/README.md | 6 +++ contrib/tools/keyinfo/Makefile | 8 ++++ contrib/tools/keyinfo/README.md | 5 +++ contrib/tools/keyinfo/keyinfo.cpp | 70 +++++++++++++++++++++++++++++++ 7 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 contrib/tools/Makefile create mode 100644 contrib/tools/README.md create mode 100644 contrib/tools/keyinfo/Makefile create mode 100644 contrib/tools/keyinfo/README.md create mode 100644 contrib/tools/keyinfo/keyinfo.cpp diff --git a/.gitignore b/.gitignore index b6cffd15..73297488 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ netDb autom4te.cache .deps stamp-h1 -Makefile +#Makefile config.h config.h.in~ config.log @@ -238,3 +238,11 @@ pip-log.txt # Sphinx docs/_build /androidIdea/ + + +# emacs files +*~ +*\#* + +# gdb files +.gdb_history \ No newline at end of file diff --git a/Makefile b/Makefile index 147bedd4..c68b977e 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,13 @@ $(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) $(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) ar -r $@ $^ -clean: +tools: $(ARLIB) + $(MAKE) -C contrib/tools/ + +clean-tools: + $(MAKE) -C contrib/tools/ clean + +clean: clean-tools rm -rf obj rm -rf docs/generated $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) diff --git a/contrib/tools/Makefile b/contrib/tools/Makefile new file mode 100644 index 00000000..6c60bb59 --- /dev/null +++ b/contrib/tools/Makefile @@ -0,0 +1,7 @@ + + +all: + $(MAKE) -C keyinfo keyinfo + +clean: + $(MAKE) -C keyinfo clean diff --git a/contrib/tools/README.md b/contrib/tools/README.md new file mode 100644 index 00000000..e598adfa --- /dev/null +++ b/contrib/tools/README.md @@ -0,0 +1,6 @@ +Collection of i2pd related tools + +# Buildling + + +run `make tools` in the root of the repo diff --git a/contrib/tools/keyinfo/Makefile b/contrib/tools/keyinfo/Makefile new file mode 100644 index 00000000..44a13ff7 --- /dev/null +++ b/contrib/tools/keyinfo/Makefile @@ -0,0 +1,8 @@ +CXXFLAGS = -I ../../../ +all: keyinfo + +keyinfo: + $(CXX) -o keyinfo keyinfo.cpp $(CXXFLAGS) -std=c++11 ../../../libi2pd.a -lssl -lcrypto -lboost_system + +clean: + $(RM) keyinfo diff --git a/contrib/tools/keyinfo/README.md b/contrib/tools/keyinfo/README.md new file mode 100644 index 00000000..b3c39a4b --- /dev/null +++ b/contrib/tools/keyinfo/README.md @@ -0,0 +1,5 @@ +# keyinfo + +print information about a private key file + + diff --git a/contrib/tools/keyinfo/keyinfo.cpp b/contrib/tools/keyinfo/keyinfo.cpp new file mode 100644 index 00000000..6b6689c6 --- /dev/null +++ b/contrib/tools/keyinfo/keyinfo.cpp @@ -0,0 +1,70 @@ +#include "Identity.h" +#include +#include +#include +#include +#include + +int main(int argc, char * argv[]) +{ + if(argc == 1) { + std::cout << "usage: " << argv[0] << " [-v] [-d] privatekey.dat" << std::endl; + return -1; + } + int opt; + bool print_full = false; + bool verbose = false; + while((opt = getopt(argc, argv, "vd"))!=-1) { + switch(opt){ + case 'v': + verbose = true; + break; + case 'd': + print_full = true; + break; + default: + std::cout << "usage: " << argv[0] << " [-v] [-d] privatekey.dat" << std::endl; + return -1; + } + } + std::string fname(argv[optind]); + i2p::data::PrivateKeys keys; + { + std::vector buff; + std::ifstream inf; + inf.open(fname); + if (!inf.is_open()) { + std::cout << "cannot open private key file " << fname << std::endl; + return 2; + } + inf.seekg(0, std::ios::end); + const std::size_t len = inf.tellg(); + inf.seekg(0, std::ios::beg); + buff.resize(len); + inf.read((char*)buff.data(), buff.size()); + if (!keys.FromBuffer(buff.data(), buff.size())) { + std::cout << "bad key file format" << std::endl; + return 3; + } + } + auto dest = keys.GetPublic(); + if(!dest) { + std::cout << "failed to extract public key" << std::endl; + return 3; + } + + const auto & ident = dest->GetIdentHash(); + if (verbose) { + std::cout << "Destination: " << dest->ToBase64() << std::endl; + std::cout << "Destination Hash: " << ident.ToBase64() << std::endl; + std::cout << "B32 Address: " << ident.ToBase32() << ".b32.i2p" << std::endl; + std::cout << "Signature Type: " << (int) dest->GetSigningKeyType() << std::endl; + std::cout << "Encryption Type: " << (int) dest->GetCryptoKeyType() << std::endl; + } else { + if(print_full) { + std::cout << dest->ToBase64() << std::endl; + } else { + std::cout << ident.ToBase32() << ".b32.i2p" << std::endl; + } + } +} From 5a796a86d70e9be041fa8d785d85b0441572b399 Mon Sep 17 00:00:00 2001 From: brain5lug Date: Mon, 3 Oct 2016 23:24:22 +0300 Subject: [PATCH 1900/6300] thread sanitizer configuration option have been added --- build/CMakeLists.txt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 95f223ef..00048942 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -16,7 +16,8 @@ option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_PCH "Use precompiled header" OFF) option(WITH_GUI "Include GUI (currently MS Windows only)" ON) option(WITH_MESHNET "Build for cjdns test network" OFF) -option(WITH_ADDRSANITIZER "Build with address sanitizer (linux only)" OFF) +option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) +option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) # paths set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) @@ -190,10 +191,22 @@ if (WITH_ADDRSANITIZER) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer" ) set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address" ) else () - error ("MSVC does not support address sanitizer option") + message( SEND_ERROR "MSVC does not support address sanitizer option") endif() endif() +if (WITH_THREADSANITIZER) + if (WITH_ADDRSANITIZER) + message( FATAL_ERROR "thread sanitizer option cannot be combined with address sanitizer") + elseif (NOT MSVC) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread" ) + set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread" ) + else () + message( SEND_ERROR "MSVC does not support address sanitizer option") + endif() +endif() + + # libraries # TODO: once CMake 3.1+ becomes mainstream, see e.g. http://stackoverflow.com/a/29871891/673826 # use imported Threads::Threads instead @@ -347,6 +360,7 @@ message(STATUS " UPnP : ${WITH_UPNP}") message(STATUS " PCH : ${WITH_PCH}") message(STATUS " MESHNET : ${WITH_MESHNET}") message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") +message(STATUS " THEADSANITIZER : ${WITH_THREADSANITIZER}") message(STATUS "---------------------------------------") #Handle paths nicely From 77ec4b5cad20ae6af5f89744d97337369cf6326e Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 9 Oct 2016 14:57:15 -0400 Subject: [PATCH 1901/6300] added warning --- Makefile.mingw | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.mingw b/Makefile.mingw index 85b6b455..2ab918b4 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -37,6 +37,7 @@ ifeq ($(USE_WIN32_APP), yes) DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif +# don't change following line to ifeq ($(USE_AESNI),yes) !!! ifeq ($(USE_AESNI),1) CPU_FLAGS = -maes -DAESNI else From 0fc4e01b1eca3c4c0719487c5c0ef4acb4fd1141 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 10 Oct 2016 08:18:54 -0400 Subject: [PATCH 1902/6300] remove tools --- Makefile | 8 +--- contrib/tools/Makefile | 7 ---- contrib/tools/README.md | 6 --- contrib/tools/keyinfo/Makefile | 8 ---- contrib/tools/keyinfo/README.md | 5 --- contrib/tools/keyinfo/keyinfo.cpp | 70 ------------------------------- 6 files changed, 1 insertion(+), 103 deletions(-) delete mode 100644 contrib/tools/Makefile delete mode 100644 contrib/tools/README.md delete mode 100644 contrib/tools/keyinfo/Makefile delete mode 100644 contrib/tools/keyinfo/README.md delete mode 100644 contrib/tools/keyinfo/keyinfo.cpp diff --git a/Makefile b/Makefile index c68b977e..147bedd4 100644 --- a/Makefile +++ b/Makefile @@ -80,13 +80,7 @@ $(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) $(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) ar -r $@ $^ -tools: $(ARLIB) - $(MAKE) -C contrib/tools/ - -clean-tools: - $(MAKE) -C contrib/tools/ clean - -clean: clean-tools +clean: rm -rf obj rm -rf docs/generated $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) diff --git a/contrib/tools/Makefile b/contrib/tools/Makefile deleted file mode 100644 index 6c60bb59..00000000 --- a/contrib/tools/Makefile +++ /dev/null @@ -1,7 +0,0 @@ - - -all: - $(MAKE) -C keyinfo keyinfo - -clean: - $(MAKE) -C keyinfo clean diff --git a/contrib/tools/README.md b/contrib/tools/README.md deleted file mode 100644 index e598adfa..00000000 --- a/contrib/tools/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Collection of i2pd related tools - -# Buildling - - -run `make tools` in the root of the repo diff --git a/contrib/tools/keyinfo/Makefile b/contrib/tools/keyinfo/Makefile deleted file mode 100644 index 44a13ff7..00000000 --- a/contrib/tools/keyinfo/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -CXXFLAGS = -I ../../../ -all: keyinfo - -keyinfo: - $(CXX) -o keyinfo keyinfo.cpp $(CXXFLAGS) -std=c++11 ../../../libi2pd.a -lssl -lcrypto -lboost_system - -clean: - $(RM) keyinfo diff --git a/contrib/tools/keyinfo/README.md b/contrib/tools/keyinfo/README.md deleted file mode 100644 index b3c39a4b..00000000 --- a/contrib/tools/keyinfo/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# keyinfo - -print information about a private key file - - diff --git a/contrib/tools/keyinfo/keyinfo.cpp b/contrib/tools/keyinfo/keyinfo.cpp deleted file mode 100644 index 6b6689c6..00000000 --- a/contrib/tools/keyinfo/keyinfo.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "Identity.h" -#include -#include -#include -#include -#include - -int main(int argc, char * argv[]) -{ - if(argc == 1) { - std::cout << "usage: " << argv[0] << " [-v] [-d] privatekey.dat" << std::endl; - return -1; - } - int opt; - bool print_full = false; - bool verbose = false; - while((opt = getopt(argc, argv, "vd"))!=-1) { - switch(opt){ - case 'v': - verbose = true; - break; - case 'd': - print_full = true; - break; - default: - std::cout << "usage: " << argv[0] << " [-v] [-d] privatekey.dat" << std::endl; - return -1; - } - } - std::string fname(argv[optind]); - i2p::data::PrivateKeys keys; - { - std::vector buff; - std::ifstream inf; - inf.open(fname); - if (!inf.is_open()) { - std::cout << "cannot open private key file " << fname << std::endl; - return 2; - } - inf.seekg(0, std::ios::end); - const std::size_t len = inf.tellg(); - inf.seekg(0, std::ios::beg); - buff.resize(len); - inf.read((char*)buff.data(), buff.size()); - if (!keys.FromBuffer(buff.data(), buff.size())) { - std::cout << "bad key file format" << std::endl; - return 3; - } - } - auto dest = keys.GetPublic(); - if(!dest) { - std::cout << "failed to extract public key" << std::endl; - return 3; - } - - const auto & ident = dest->GetIdentHash(); - if (verbose) { - std::cout << "Destination: " << dest->ToBase64() << std::endl; - std::cout << "Destination Hash: " << ident.ToBase64() << std::endl; - std::cout << "B32 Address: " << ident.ToBase32() << ".b32.i2p" << std::endl; - std::cout << "Signature Type: " << (int) dest->GetSigningKeyType() << std::endl; - std::cout << "Encryption Type: " << (int) dest->GetCryptoKeyType() << std::endl; - } else { - if(print_full) { - std::cout << dest->ToBase64() << std::endl; - } else { - std::cout << ident.ToBase32() << ".b32.i2p" << std::endl; - } - } -} From 8ba142eb4573d44b549bb220caae3313a9145f8d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 6 Oct 2016 10:28:56 -0400 Subject: [PATCH 1903/6300] increase datagram session switching interval --- Datagram.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Datagram.h b/Datagram.h index f3aae6f9..c4ab8cd2 100644 --- a/Datagram.h +++ b/Datagram.h @@ -24,7 +24,7 @@ namespace datagram // milliseconds for how long we try sticking to a dead routing path before trying to switch const uint64_t DATAGRAM_SESSION_PATH_TIMEOUT = 5000; // milliseconds interval a routing path is used before switching - const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 60 * 1000; + const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 5 * 60 * 1000; // milliseconds before lease expire should we try switching leases const uint64_t DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW = 10 * 1000; // milliseconds fudge factor for leases handover From 43c3bdf7c55141ecc6747b9c6cde7d45ed413bc0 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 6 Oct 2016 13:41:18 -0400 Subject: [PATCH 1904/6300] fix --- Datagram.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Datagram.h b/Datagram.h index c4ab8cd2..f5397e56 100644 --- a/Datagram.h +++ b/Datagram.h @@ -24,7 +24,7 @@ namespace datagram // milliseconds for how long we try sticking to a dead routing path before trying to switch const uint64_t DATAGRAM_SESSION_PATH_TIMEOUT = 5000; // milliseconds interval a routing path is used before switching - const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 5 * 60 * 1000; + const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 20 * 60 * 1000; // milliseconds before lease expire should we try switching leases const uint64_t DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW = 10 * 1000; // milliseconds fudge factor for leases handover From 577d9ddf6571fb05aecc7b391363ba0040bb1a4c Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 9 Oct 2016 10:55:55 -0400 Subject: [PATCH 1905/6300] fix memory leak with udp tunnel --- Datagram.cpp | 31 ++++++++++++++++++------------- Datagram.h | 3 ++- I2PTunnel.cpp | 16 +++++----------- I2PTunnel.h | 2 +- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 92c2509c..0fce1166 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -12,8 +12,10 @@ namespace i2p namespace datagram { DatagramDestination::DatagramDestination (std::shared_ptr owner): - m_Owner (owner.get()), m_Receiver (nullptr) + m_Owner (owner.get()), + m_Receiver (nullptr) { + m_Identity.FromBase64 (owner->GetIdentity()->ToBase64()); } DatagramDestination::~DatagramDestination () @@ -24,16 +26,16 @@ namespace datagram void DatagramDestination::SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint16_t fromPort, uint16_t toPort) { auto owner = m_Owner; - auto i = owner->GetIdentity(); - uint8_t buf[MAX_DATAGRAM_SIZE]; - auto identityLen = i->ToBuffer (buf, MAX_DATAGRAM_SIZE); + std::vector v(MAX_DATAGRAM_SIZE); + uint8_t * buf = v.data(); + auto identityLen = m_Identity.ToBuffer (buf, MAX_DATAGRAM_SIZE); uint8_t * signature = buf + identityLen; - auto signatureLen = i->GetSignatureLen (); + auto signatureLen = m_Identity.GetSignatureLen (); uint8_t * buf1 = signature + signatureLen; size_t headerLen = identityLen + signatureLen; memcpy (buf1, payload, len); - if (i->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) + if (m_Identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) { uint8_t hash[32]; SHA256(buf1, len, hash); @@ -48,7 +50,7 @@ namespace datagram } - void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort,uint8_t * const &buf, size_t len) { i2p::data::IdentityEx identity; size_t identityLen = identity.FromBuffer (buf, len); @@ -93,7 +95,7 @@ namespace datagram uint8_t uncompressed[MAX_DATAGRAM_SIZE]; size_t uncompressedLen = m_Inflator.Inflate (buf, len, uncompressed, MAX_DATAGRAM_SIZE); if (uncompressedLen) - HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen); + HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen); } std::shared_ptr DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) @@ -121,7 +123,7 @@ namespace datagram if (m_Sessions.empty ()) return; auto now = i2p::util::GetMillisecondsSinceEpoch(); LogPrint(eLogDebug, "DatagramDestination: clean up sessions"); - std::lock_guard lock(m_SessionsMutex); + std::unique_lock lock(m_SessionsMutex); // for each session ... for (auto it = m_Sessions.begin (); it != m_Sessions.end (); ) { @@ -270,13 +272,16 @@ namespace datagram bool DatagramSession::ShouldUpdateRoutingPath() const { + bool dead = m_RoutingSession == nullptr || m_RoutingSession->GetSharedRoutingPath () == nullptr; auto now = i2p::util::GetMillisecondsSinceEpoch (); // we need to rotate paths becuase the routing path is too old - if (now - m_LastPathChange >= DATAGRAM_SESSION_PATH_SWITCH_INTERVAL) return true; - // our path looks dead so we need to rotate paths - if (now - m_LastSuccess >= DATAGRAM_SESSION_PATH_TIMEOUT) return true; + // if (now - m_LastPathChange >= DATAGRAM_SESSION_PATH_SWITCH_INTERVAL) return true; + // too fast switching paths + if (now - m_LastPathChange < DATAGRAM_SESSION_PATH_MIN_LIFETIME ) return false; + // our path looks dead so we need to rotate paths + if (now - m_LastSuccess >= DATAGRAM_SESSION_PATH_TIMEOUT) return !dead; // if we have a routing session and routing path we don't need to switch paths - return m_RoutingSession == nullptr || m_RoutingSession->GetSharedRoutingPath () == nullptr; + return dead; } diff --git a/Datagram.h b/Datagram.h index f5397e56..aa7f3ba1 100644 --- a/Datagram.h +++ b/Datagram.h @@ -133,13 +133,14 @@ namespace datagram std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); - void HandleDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len); /** find a receiver by port, if none by port is found try default receiever, otherwise returns nullptr */ Receiver FindReceiver(uint16_t port); private: i2p::client::ClientDestination * m_Owner; + i2p::data::IdentityEx m_Identity; Receiver m_Receiver; // default std::mutex m_SessionsMutex; std::map > m_Sessions; diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index a9c5c313..a1814058 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -559,23 +559,23 @@ namespace client } /** create new udp session */ boost::asio::ip::udp::endpoint ep(m_LocalAddress, 0); - m_Sessions.push_back(new UDPSession(ep, m_LocalDest, m_RemoteEndpoint, ih, localPort, remotePort)); + m_Sessions.push_back(new UDPSession(ep, m_LocalDest, m_RemoteEndpoint, &ih, localPort, remotePort)); return m_Sessions.back(); } UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, - boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash to, + boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash * to, uint16_t ourPort, uint16_t theirPort) : m_Destination(localDestination->GetDatagramDestination()), m_Service(localDestination->GetService()), IPSocket(localDestination->GetService(), localEndpoint), - Identity(to), SendEndpoint(endpoint), LastActivity(i2p::util::GetMillisecondsSinceEpoch()), LocalPort(ourPort), RemotePort(theirPort) { + memcpy(Identity, to->data(), 32); Receive(); } @@ -592,13 +592,7 @@ namespace client { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - uint8_t * data = new uint8_t[len]; - memcpy(data, m_Buffer, len); - m_Service.post([&,len, data] () { - m_Destination->SendDatagramTo(data, len, Identity, 0, 0); - delete [] data; - }); - + m_Destination->SendDatagramTo(m_Buffer, len, Identity, 0, 0); Receive(); } else { LogPrint(eLogError, "UDPSession: ", ecode.message()); @@ -727,7 +721,7 @@ namespace client if(m_Session) delete m_Session; boost::asio::ip::udp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 0); - m_Session = new UDPSession(m_LocalEndpoint, m_LocalDest, ep, *m_RemoteIdent, LocalPort, RemotePort); + m_Session = new UDPSession(m_LocalEndpoint, m_LocalDest, ep, m_RemoteIdent, LocalPort, RemotePort); } void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t /*fromPort*/, diff --git a/I2PTunnel.h b/I2PTunnel.h index dce9f812..e6f0e84f 100644 --- a/I2PTunnel.h +++ b/I2PTunnel.h @@ -155,7 +155,7 @@ namespace client UDPSession(boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, - boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash ident, + boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash * ident, uint16_t ourPort, uint16_t theirPort); void HandleReceived(const boost::system::error_code & ecode, std::size_t len); void Receive(); From 7506619f4cde2e22197af117eb0943d1f5e4314c Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 8 Oct 2016 11:58:26 -0400 Subject: [PATCH 1906/6300] add minimum path lifetime --- Datagram.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Datagram.h b/Datagram.h index aa7f3ba1..614a4734 100644 --- a/Datagram.h +++ b/Datagram.h @@ -22,14 +22,15 @@ namespace datagram // milliseconds for max session idle time const uint64_t DATAGRAM_SESSION_MAX_IDLE = 10 * 60 * 1000; // milliseconds for how long we try sticking to a dead routing path before trying to switch - const uint64_t DATAGRAM_SESSION_PATH_TIMEOUT = 5000; + const uint64_t DATAGRAM_SESSION_PATH_TIMEOUT = 10 * 1000; // milliseconds interval a routing path is used before switching const uint64_t DATAGRAM_SESSION_PATH_SWITCH_INTERVAL = 20 * 60 * 1000; // milliseconds before lease expire should we try switching leases const uint64_t DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW = 10 * 1000; // milliseconds fudge factor for leases handover const uint64_t DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE = 1000; - + // milliseconds minimum time between path switches + const uint64_t DATAGRAM_SESSION_PATH_MIN_LIFETIME = 5 * 1000; class DatagramSession { From 8a95b5b5b0012cea0b3472f9885375463ce4d7c9 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 10 Oct 2016 08:30:33 -0400 Subject: [PATCH 1907/6300] tabify --- Datagram.cpp | 6 +++--- Datagram.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Datagram.cpp b/Datagram.cpp index 0fce1166..d0b0737a 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -276,9 +276,9 @@ namespace datagram auto now = i2p::util::GetMillisecondsSinceEpoch (); // we need to rotate paths becuase the routing path is too old // if (now - m_LastPathChange >= DATAGRAM_SESSION_PATH_SWITCH_INTERVAL) return true; - // too fast switching paths - if (now - m_LastPathChange < DATAGRAM_SESSION_PATH_MIN_LIFETIME ) return false; - // our path looks dead so we need to rotate paths + // too fast switching paths + if (now - m_LastPathChange < DATAGRAM_SESSION_PATH_MIN_LIFETIME ) return false; + // our path looks dead so we need to rotate paths if (now - m_LastSuccess >= DATAGRAM_SESSION_PATH_TIMEOUT) return !dead; // if we have a routing session and routing path we don't need to switch paths return dead; diff --git a/Datagram.h b/Datagram.h index 614a4734..dc63cccb 100644 --- a/Datagram.h +++ b/Datagram.h @@ -29,8 +29,8 @@ namespace datagram const uint64_t DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW = 10 * 1000; // milliseconds fudge factor for leases handover const uint64_t DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE = 1000; - // milliseconds minimum time between path switches - const uint64_t DATAGRAM_SESSION_PATH_MIN_LIFETIME = 5 * 1000; + // milliseconds minimum time between path switches + const uint64_t DATAGRAM_SESSION_PATH_MIN_LIFETIME = 5 * 1000; class DatagramSession { From f91f3796a889e6ae385f85ec893718d1920d4684 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 10 Oct 2016 08:59:45 -0400 Subject: [PATCH 1908/6300] make sure verifier gets created once --- Identity.cpp | 25 +++++++++++++++++-------- Identity.h | 2 ++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index 6d37d34e..0a3eeffb 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -35,11 +35,12 @@ namespace data } IdentityEx::IdentityEx (): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { } - IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type) + IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type): + m_IsVerifierCreated (false) { memcpy (m_StandardIdentity.publicKey, publicKey, sizeof (m_StandardIdentity.publicKey)); if (type != SIGNING_KEY_TYPE_DSA_SHA1) @@ -135,19 +136,19 @@ namespace data } IdentityEx::IdentityEx (const uint8_t * buf, size_t len): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { FromBuffer (buf, len); } IdentityEx::IdentityEx (const IdentityEx& other): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { *this = other; } IdentityEx::IdentityEx (const Identity& standard): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { *this = standard; } @@ -173,6 +174,7 @@ namespace data m_ExtendedBuffer = nullptr; m_Verifier = nullptr; + m_IsVerifierCreated = false; return *this; } @@ -187,6 +189,7 @@ namespace data m_ExtendedLen = 0; m_Verifier = nullptr; + m_IsVerifierCreated = false; return *this; } @@ -374,7 +377,13 @@ namespace data void IdentityEx::UpdateVerifier (i2p::crypto::Verifier * verifier) const { if (!m_Verifier || !verifier) - m_Verifier.reset (verifier); + { + auto created = m_IsVerifierCreated.exchange (true); + if (!created) + m_Verifier.reset (verifier); + else + delete verifier; + } else delete verifier; } @@ -457,8 +466,8 @@ namespace data void PrivateKeys::Sign (const uint8_t * buf, int len, uint8_t * signature) const { if (!m_Signer) - CreateSigner(); - m_Signer->Sign (buf, len, signature); + CreateSigner(); + m_Signer->Sign (buf, len, signature); } void PrivateKeys::CreateSigner () const diff --git a/Identity.h b/Identity.h index 8f3e9e9d..49dada48 100644 --- a/Identity.h +++ b/Identity.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "Base.h" #include "Signature.h" @@ -104,6 +105,7 @@ namespace data Identity m_StandardIdentity; IdentHash m_IdentHash; mutable std::unique_ptr m_Verifier; + mutable std::atomic_bool m_IsVerifierCreated; // make sure we don't create twice size_t m_ExtendedLen; uint8_t * m_ExtendedBuffer; }; From 84ca992e9128f78bc147e510fd2a2895d77a1e4f Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 10 Oct 2016 08:59:48 -0400 Subject: [PATCH 1909/6300] Revert "Unused parameter warnings removal" This reverts commit 5350078543d0ac04a608f2c03efd4b9e1b8f9509. --- AddressBook.cpp | 3 +-- BOB.cpp | 42 +++++++++++++++++++++--------------------- Destination.cpp | 6 +++--- Family.cpp | 2 +- I2CP.cpp | 15 +++++++-------- I2PControl.cpp | 8 ++++---- I2PTunnel.cpp | 5 ++--- LeaseSet.cpp | 2 +- RouterContext.cpp | 2 +- SAM.cpp | 15 +++++++-------- SOCKS.cpp | 4 ++-- SSUSession.cpp | 6 +++--- Streaming.cpp | 2 +- TransitTunnel.cpp | 4 ++-- Transports.cpp | 4 ++-- Tunnel.cpp | 6 +++--- 16 files changed, 61 insertions(+), 65 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index 67eb7566..b993f456 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -599,8 +599,7 @@ namespace client } } - void AddressBook::HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t /*fromPort*/, - uint16_t /*toPort*/, const uint8_t * buf, size_t len) + void AddressBook::HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if (len < 44) { diff --git a/BOB.cpp b/BOB.cpp index 2d96f4b2..8ffffba6 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -284,7 +284,7 @@ namespace client std::placeholders::_1, std::placeholders::_2)); } - void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t /*bytes_transferred*/) + void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { @@ -338,20 +338,20 @@ namespace client Send (len); } - void BOBCommandSession::ZapCommandHandler (const char * /*operand*/, size_t /*len*/) + void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: zap"); Terminate (); } - void BOBCommandSession::QuitCommandHandler (const char * /*operand*/, size_t /*len*/) + void BOBCommandSession::QuitCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: quit"); m_IsOpen = false; SendReplyOK ("Bye!"); } - void BOBCommandSession::StartCommandHandler (const char * /*operand*/, size_t /*len*/) + void BOBCommandSession::StartCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: start ", m_Nickname); if (m_IsActive) @@ -373,7 +373,7 @@ namespace client m_IsActive = true; } - void BOBCommandSession::StopCommandHandler (const char * /*operand*/, size_t /*len*/) + void BOBCommandSession::StopCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: stop ", m_Nickname); if (!m_IsActive) @@ -392,7 +392,7 @@ namespace client m_IsActive = false; } - void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t /*len*/) + void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: setnick ", operand); m_Nickname = operand; @@ -401,7 +401,7 @@ namespace client SendReplyOK (msg.c_str ()); } - void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t /*len*/) + void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getnick ", operand); m_CurrentDestination = m_Owner.FindDestination (operand); @@ -420,40 +420,40 @@ namespace client SendReplyError ("no nickname has been set"); } - void BOBCommandSession::NewkeysCommandHandler (const char* /*operand*/, size_t /*len*/) + void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: newkeys"); m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (); SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } - void BOBCommandSession::SetkeysCommandHandler (const char* operand, size_t /*len*/) + void BOBCommandSession::SetkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: setkeys ", operand); m_Keys.FromBase64 (operand); SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } - void BOBCommandSession::GetkeysCommandHandler (const char* /*operand*/, size_t /*len*/) + void BOBCommandSession::GetkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getkeys"); SendReplyOK (m_Keys.ToBase64 ().c_str ()); } - void BOBCommandSession::GetdestCommandHandler (const char* /*operand*/, size_t /*len*/) + void BOBCommandSession::GetdestCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getdest"); SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } - void BOBCommandSession::OuthostCommandHandler (const char* operand, size_t /*len*/) + void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: outhost ", operand); m_Address = operand; SendReplyOK ("outhost set"); } - void BOBCommandSession::OutportCommandHandler (const char* operand, size_t /*len*/) + void BOBCommandSession::OutportCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: outport ", operand); m_OutPort = boost::lexical_cast(operand); @@ -463,14 +463,14 @@ namespace client SendReplyError ("port out of range"); } - void BOBCommandSession::InhostCommandHandler (const char* operand, size_t /*len*/) + void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: inhost ", operand); m_Address = operand; SendReplyOK ("inhost set"); } - void BOBCommandSession::InportCommandHandler (const char* operand, size_t /*len*/) + void BOBCommandSession::InportCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: inport ", operand); m_InPort = boost::lexical_cast(operand); @@ -480,7 +480,7 @@ namespace client SendReplyError ("port out of range"); } - void BOBCommandSession::QuietCommandHandler (const char* /*operand*/, size_t /*len*/) + void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: quiet"); if (m_Nickname.length () > 0) @@ -497,7 +497,7 @@ namespace client SendReplyError ("no nickname has been set"); } - void BOBCommandSession::LookupCommandHandler (const char* operand, size_t /*len*/) + void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: lookup ", operand); i2p::data::IdentHash ident; @@ -525,7 +525,7 @@ namespace client } } - void BOBCommandSession::ClearCommandHandler (const char* /*operand*/, size_t /*len*/) + void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: clear"); m_Owner.DeleteDestination (m_Nickname); @@ -533,7 +533,7 @@ namespace client SendReplyOK ("cleared"); } - void BOBCommandSession::ListCommandHandler (const char* /*operand*/, size_t /*len*/) + void BOBCommandSession::ListCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: list"); const auto& destinations = m_Owner.GetDestinations (); @@ -542,7 +542,7 @@ namespace client SendReplyOK ("Listing done"); } - void BOBCommandSession::OptionCommandHandler (const char* operand, size_t /*len*/) + void BOBCommandSession::OptionCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: option ", operand); const char * value = strchr (operand, '='); @@ -561,7 +561,7 @@ namespace client SendReplyError ("malformed"); } - void BOBCommandSession::StatusCommandHandler (const char* operand, size_t /*len*/) + void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: status ", operand); if (m_Nickname == operand) diff --git a/Destination.cpp b/Destination.cpp index d859d6b0..0b8f2859 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -260,7 +260,7 @@ namespace client m_Service.post (std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msg)); } - void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t /*len*/, std::shared_ptr from) + void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { uint8_t typeID = buf[I2NP_HEADER_TYPEID_OFFSET]; switch (typeID) @@ -348,7 +348,7 @@ namespace client } } - void LeaseSetDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t /*len*/) + void LeaseSetDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len) { i2p::data::IdentHash key (buf); int num = buf[32]; // num @@ -732,7 +732,7 @@ namespace client ScheduleCheckForReady(p); } - void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t /*len*/) + void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); buf += 4; diff --git a/Family.cpp b/Family.cpp index b37af204..c1840e51 100644 --- a/Family.cpp +++ b/Family.cpp @@ -110,7 +110,7 @@ namespace data } bool Families::VerifyFamily (const std::string& family, const IdentHash& ident, - const char * signature, const char * /*key*/) + const char * signature, const char * key) { uint8_t buf[50], signatureBuf[64]; size_t len = family.length (), signatureLen = strlen (signature); diff --git a/I2CP.cpp b/I2CP.cpp index 7100250e..04f21408 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -197,7 +197,7 @@ namespace client std::bind (&I2CPSession::HandleReceivedHeader, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } - void I2CPSession::HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t /*bytes_transferred*/) + void I2CPSession::HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) Terminate (); @@ -224,7 +224,7 @@ namespace client std::bind (&I2CPSession::HandleReceivedPayload, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } - void I2CPSession::HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t /*bytes_transferred*/) + void I2CPSession::HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) Terminate (); @@ -281,8 +281,7 @@ namespace client LogPrint (eLogError, "I2CP: Can't write to the socket"); } - void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, - std::size_t /*bytes_transferred*/, const uint8_t* buf) + void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf) { delete[] buf; if (ecode && ecode != boost::asio::error::operation_aborted) @@ -397,7 +396,7 @@ namespace client } } - void I2CPSession::DestroySessionMessageHandler (const uint8_t* /*buf*/, size_t /*len*/) + void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len) { SendSessionStatusMessage (0); // destroy LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " destroyed"); @@ -408,7 +407,7 @@ namespace client } } - void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t* /*buf*/, size_t /*len*/) + void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len) { // TODO: implement actual reconfiguration SendSessionStatusMessage (2); // updated @@ -570,7 +569,7 @@ namespace client } } - void I2CPSession::DestLookupMessageHandler (const uint8_t * buf, size_t /*len*/) + void I2CPSession::DestLookupMessageHandler (const uint8_t * buf, size_t len) { if (m_Destination) { @@ -607,7 +606,7 @@ namespace client SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, buf, 32); } - void I2CPSession::GetBandwidthLimitsMessageHandler (const uint8_t* /*buf*/, size_t /*len*/) + void I2CPSession::GetBandwidthLimitsMessageHandler (const uint8_t * buf, size_t len) { uint8_t limits[64]; memset (limits, 0, 64); diff --git a/I2PControl.cpp b/I2PControl.cpp index 7f063f35..3e2e3997 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -295,8 +295,8 @@ namespace client std::placeholders::_1, std::placeholders::_2, socket, buf)); } - void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t /*bytes_transferred*/, - std::shared_ptr /*socket*/, std::shared_ptr /*buf*/) + void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, + std::shared_ptr socket, std::shared_ptr buf) { if (ecode) { LogPrint (eLogError, "I2PControl: write error: ", ecode.message ()); @@ -453,7 +453,7 @@ namespace client InsertParam (results, "Shutdown", ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent m_ShutdownTimer.async_wait ( - [](const boost::system::error_code&) + [](const boost::system::error_code& ecode) { Daemon.running = 0; }); @@ -467,7 +467,7 @@ namespace client InsertParam (results, "ShutdownGraceful", ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second m_ShutdownTimer.async_wait ( - [](const boost::system::error_code&) + [](const boost::system::error_code& ecode) { Daemon.running = 0; }); diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index a1814058..be48b83a 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -439,7 +439,7 @@ namespace client } void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, - std::shared_ptr /*resolver*/) + std::shared_ptr resolver) { if (!ecode) { @@ -724,8 +724,7 @@ namespace client m_Session = new UDPSession(m_LocalEndpoint, m_LocalDest, ep, m_RemoteIdent, LocalPort, RemotePort); } - void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t /*fromPort*/, - uint16_t /*toPort*/, const uint8_t * buf, size_t len) + void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if(m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) { diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 4ddd07f3..04dc77c5 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -173,7 +173,7 @@ namespace data const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const { - return GetNonExpiredLeasesExcluding( [] (const Lease &) -> bool { return false; }, withThreshold); + return GetNonExpiredLeasesExcluding( [] (const Lease & l) -> bool { return false; }, withThreshold); } const std::vector > LeaseSet::GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold) const diff --git a/RouterContext.cpp b/RouterContext.cpp index 15084bd3..6824adb8 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -424,7 +424,7 @@ namespace i2p return i2p::tunnel::tunnels.GetExploratoryPool (); } - void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t /*len*/, std::shared_ptr from) + void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); } diff --git a/SAM.cpp b/SAM.cpp index ce85fc8d..2864cd6f 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -135,7 +135,7 @@ namespace client } } - void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t /*bytes_transferred*/) + void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { @@ -166,7 +166,7 @@ namespace client } } - void SAMSocket::HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t /*bytes_transferred*/, bool close) + void SAMSocket::HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close) { if (ecode) { @@ -262,7 +262,7 @@ namespace client } } - void SAMSocket::ProcessSessionCreate (char * buf, size_t /*len*/) + void SAMSocket::ProcessSessionCreate (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: session create: ", buf); std::map params; @@ -333,7 +333,7 @@ namespace client SendMessageReply (m_Buffer, l2, false); } - void SAMSocket::ProcessStreamConnect (char * buf, size_t /*len*/) + void SAMSocket::ProcessStreamConnect (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: stream connect: ", buf); std::map params; @@ -389,7 +389,7 @@ namespace client } } - void SAMSocket::ProcessStreamAccept (char * buf, size_t /*len*/) + void SAMSocket::ProcessStreamAccept (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: stream accept: ", buf); std::map params; @@ -460,7 +460,7 @@ namespace client SendMessageReply (m_Buffer, len, false); } - void SAMSocket::ProcessNamingLookup (char * buf, size_t /*len*/) + void SAMSocket::ProcessNamingLookup (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: naming lookup: ", buf); std::map params; @@ -652,8 +652,7 @@ namespace client LogPrint (eLogWarning, "SAM: I2P acceptor has been reset"); } - void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t /*fromPort*/, - uint16_t /*toPort*/, const uint8_t* buf, size_t len) + void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SAM: datagram received ", len); auto base64 = from.ToBase64 (); diff --git a/SOCKS.cpp b/SOCKS.cpp index a52fe612..9d85963b 100644 --- a/SOCKS.cpp +++ b/SOCKS.cpp @@ -696,7 +696,7 @@ namespace proxy } - void SOCKSHandler::HandleUpstreamData(uint8_t* /*dataptr*/, std::size_t len) + void SOCKSHandler::HandleUpstreamData(uint8_t * dataptr, std::size_t len) { if (m_state == UPSTREAM_HANDSHAKE) { m_upstream_response_len += len; @@ -739,7 +739,7 @@ namespace proxy } } - void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator) + void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) { if (ecode) { LogPrint(eLogWarning, "SOCKS: could not connect to upstream proxy: ", ecode.message()); diff --git a/SSUSession.cpp b/SSUSession.cpp index fbb5b11d..5cd59164 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -536,7 +536,7 @@ namespace transport Send (buf, msgLen); } - void SSUSession::ProcessRelayRequest (const uint8_t * buf, size_t /*len*/, const boost::asio::ip::udp::endpoint& from) + void SSUSession::ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) { uint32_t relayTag = bufbe32toh (buf); auto session = m_Server.FindRelaySession (relayTag); @@ -637,7 +637,7 @@ namespace transport LogPrint (eLogDebug, "SSU: relay intro sent"); } - void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t /*len*/) + void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SSU message: Relay response received"); uint8_t remoteSize = *buf; @@ -689,7 +689,7 @@ namespace transport LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce); } - void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t /*len*/) + void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) { uint8_t size = *buf; if (size == 4) diff --git a/Streaming.cpp b/Streaming.cpp index e8f5a897..02e738c8 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -729,7 +729,7 @@ namespace stream } } - void Stream::HandleAckSendTimer (const boost::system::error_code&) + void Stream::HandleAckSendTimer (const boost::system::error_code& ecode) { if (m_IsAckSendScheduled) { diff --git a/TransitTunnel.cpp b/TransitTunnel.cpp index d06f5134..dfe01a05 100644 --- a/TransitTunnel.cpp +++ b/TransitTunnel.cpp @@ -51,12 +51,12 @@ namespace tunnel } } - void TransitTunnel::SendTunnelDataMsg (std::shared_ptr /*msg*/) + void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) { LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); } - void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr /*msg*/) + void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); } diff --git a/Transports.cpp b/Transports.cpp index e0d2df7a..a29cac15 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -394,7 +394,7 @@ namespace transport } void Transports::HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, - i2p::data::IdentHash ident, std::shared_ptr /*resolver*/) + i2p::data::IdentHash ident, std::shared_ptr resolver) { auto it1 = m_Peers.find (ident); if (it1 != m_Peers.end ()) @@ -437,7 +437,7 @@ namespace transport } void Transports::HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, - i2p::data::IdentHash ident, std::shared_ptr /*resolver*/) + i2p::data::IdentHash ident, std::shared_ptr resolver) { auto it1 = m_Peers.find (ident); if (it1 != m_Peers.end ()) diff --git a/Tunnel.cpp b/Tunnel.cpp index 3749c9ca..7d2e6735 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -90,7 +90,7 @@ namespace tunnel i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); } - bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t /*len*/) + bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) { LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); @@ -161,7 +161,7 @@ namespace tunnel } } - void Tunnel::SendTunnelDataMsg (std::shared_ptr /*msg*/) + void Tunnel::SendTunnelDataMsg (std::shared_ptr msg) { LogPrint (eLogWarning, "Tunnel: Can't send I2NP messages without delivery instructions"); } @@ -256,7 +256,7 @@ namespace tunnel m_Gateway.SendBuffer (); } - void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr /*msg*/) + void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ()); } From a332d687043959cc57fd7015e4cb98123e25103b Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 10 Oct 2016 09:02:39 -0400 Subject: [PATCH 1910/6300] Revert "fix f79ad91" This reverts commit e8e3db688803309c7411b29f7b6ca0c159872f98. --- Destination.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Destination.cpp b/Destination.cpp index 0b8f2859..dbdec0c7 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -185,7 +185,6 @@ namespace client if(ls && !ls->IsExpired()) { ls->PopulateLeases(); - std::unique_lock lock(m_RemoteLeaseSetsMutex); m_RemoteLeaseSets[ident] = ls; } }); From 3095e142474b46227f991adfe7e05772eec1083e Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 10 Oct 2016 09:04:24 -0400 Subject: [PATCH 1911/6300] undo weird mutex changes --- Destination.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index dbdec0c7..a1e1858e 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -171,7 +171,7 @@ namespace client std::shared_ptr LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident) { - std::unique_lock lock(m_RemoteLeaseSetsMutex); + std::lock_guard lock(m_RemoteLeaseSetsMutex); auto it = m_RemoteLeaseSets.find (ident); if (it != m_RemoteLeaseSets.end ()) { @@ -185,7 +185,10 @@ namespace client if(ls && !ls->IsExpired()) { ls->PopulateLeases(); - m_RemoteLeaseSets[ident] = ls; + { + std::lock_guard l(m_RemoteLeaseSetsMutex); + m_RemoteLeaseSets[ident] = ls; + } } }); } From 456d9e79e61214c16fadbcc447e3893b78d32942 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 10 Oct 2016 09:06:32 -0400 Subject: [PATCH 1912/6300] Revert "minimize count of errors "I2CP: Failed to send message. No outbound tunnels"" This reverts commit 8ff2627e8eef895bc840d335aeca5db947077d79. --- I2CP.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/I2CP.cpp b/I2CP.cpp index 04f21408..e0139b9f 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -103,8 +103,7 @@ namespace client } auto path = remoteSession->GetSharedRoutingPath (); std::shared_ptr outboundTunnel; - std::shared_ptr remoteLease; - bool unconfirmedTags=false; + std::shared_ptr remoteLease; if (path) { if (!remoteSession->CleanupUnconfirmedTags ()) // no stuck tags @@ -113,12 +112,9 @@ namespace client remoteLease = path->remoteLease; } else - { remoteSession->SetSharedRoutingPath (nullptr); - unconfirmedTags=true; - } } - if (!path || unconfirmedTags) + else { outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); auto leases = remote->GetNonExpiredLeases (); From e8d8b290a69b47827e40ade735d1a7c1024fa26a Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Mon, 10 Oct 2016 09:07:49 -0400 Subject: [PATCH 1913/6300] rename --- Destination.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Destination.cpp b/Destination.cpp index a1e1858e..ccf1e6e2 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -186,7 +186,7 @@ namespace client { ls->PopulateLeases(); { - std::lock_guard l(m_RemoteLeaseSetsMutex); + std::lock_guard _lock(m_RemoteLeaseSetsMutex); m_RemoteLeaseSets[ident] = ls; } } From 3fa4e2f58dea118effe02aba21e3341f2348bf44 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Mon, 10 Oct 2016 15:00:36 +0000 Subject: [PATCH 1914/6300] Update README.md --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f5ce9624..7fc994bb 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,28 @@ i2pd ==== -i2pd is a full-featured C++ implementation of -[I2P](https://geti2p.net/en/about/intro) client. +i2pd (I2P Daemon) is a full-featured C++ implementation of I2P client. -I2P (Invisible Internet Project) is anonymous network which works on top of -public Internet. Privacy and anonymity are achieved by strong encryption and -bouncing your traffic through thousands of I2P nodes all around the world. +I2P (Invisible Internet Protocol) is a universal anonymous network layer. +All communications over I2P are anonymous and end-to-end encrypted, participants +don't reveal their real IP addresses. -We are building network which helps people to communicate and share information +I2P client is a software used for building and using anonymous I2P +networks. Such networks can work on top of ordinary Internet connection, LAN +and even wireless mesh-networks. + +I2P is commonly used for anonymous peer-to-peer applications (filesharing, +cryptocurrencies) and anonymous client-server applications (websites, instant +messengers, chat-servers). + +I2P allows people from all around the world to communicate and share information without restrictions. * [Website](http://i2pd.website) * [Documentation](https://i2pd.readthedocs.io/en/latest/) * [Wiki](https://github.com/PurpleI2P/i2pd/wiki) * [Tickets/Issues](https://github.com/PurpleI2P/i2pd/issues) -* [Twitter](https://twitter.com/i2porignal) +* [Twitter](https://twitter.com/hashtag/i2pd) Installing ---------- From c78ec12e99f8b10a5876ba966dc9339abc614850 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Mon, 10 Oct 2016 15:07:22 +0000 Subject: [PATCH 1915/6300] Add specs link --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7fc994bb..798209ad 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ without restrictions. * [Documentation](https://i2pd.readthedocs.io/en/latest/) * [Wiki](https://github.com/PurpleI2P/i2pd/wiki) * [Tickets/Issues](https://github.com/PurpleI2P/i2pd/issues) +* [Specifications](https://geti2p.net/spec) * [Twitter](https://twitter.com/hashtag/i2pd) Installing From 6d6352162230a9b9f1b693fe7a000c2f43337edd Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 10 Oct 2016 11:46:52 -0400 Subject: [PATCH 1916/6300] update miniupnpc instructions --- docs/build_notes_windows.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/build_notes_windows.md b/docs/build_notes_windows.md index a217d80d..2b4b96b6 100644 --- a/docs/build_notes_windows.md +++ b/docs/build_notes_windows.md @@ -87,7 +87,19 @@ You should be able to run ./i2pd . If you need to start from the new shell, consider starting *MinGW-w64 Win32 Shell* instead of *MSYS2 Shell* as it adds`/minw32/bin` to the PATH. +### UPnP +You can install it through the MSYS2 +and build with USE_UPNP key. +```bash +pacman -S mingw-w64-i686-miniupnpc +make USE_UPNP=yes +``` +or +```bash +pacman -S mingw-w64-x86_64-miniupnpc +make USE_UPNP=yes +``` Using Visual Studio ------------------- @@ -160,20 +172,7 @@ port. You'd want to have include headers around to build i2pd with support for this. Unpack client source code in a sibling folder, e.g. C:\dev\miniupnpc . You may want to remove version number from folder name included in downloaded archive. - -You can also install it through the MSYS2 -and build with USE_UPNP key. - -```bash -pacman -S mingw-w64-i686-miniupnpc -make USE_UPNP=yes -``` -or -```bash -pacman -S mingw-x86_64-miniupnpc -make USE_UPNP=yes -``` - + ### Creating Visual Studio project Start CMake GUI, navigate to i2pd directory, choose building directory, e.g. ./out, and configure options. From f10d9e1332a747968b70fbc058a0f3820ca957e1 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 10 Oct 2016 16:31:26 -0400 Subject: [PATCH 1917/6300] update reseed lists --- Config.cpp | 4 +-- .../reseed/atomike_at_mail.i2p.crt | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 contrib/certificates/reseed/atomike_at_mail.i2p.crt diff --git a/Config.cpp b/Config.cpp index 33231aa4..84633532 100644 --- a/Config.cpp +++ b/Config.cpp @@ -163,9 +163,9 @@ namespace config { "https://i2p.manas.ca:8443/," "https://i2p-0.manas.ca:8443/," "https://reseed.i2p.vzaws.com:8443/," - "https://user.mx24.eu/," "https://download.xxlspeed.com/," - "https://reseed-ru.lngserv.ru/" + "https://reseed-ru.lngserv.ru/," + "https://reseed.atomike.ninja/" ), "Reseed URLs, separated by comma") #endif ; diff --git a/contrib/certificates/reseed/atomike_at_mail.i2p.crt b/contrib/certificates/reseed/atomike_at_mail.i2p.crt new file mode 100644 index 00000000..1e724f00 --- /dev/null +++ b/contrib/certificates/reseed/atomike_at_mail.i2p.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +cGpnw4a2u9sHZIUNHzoGq32+7UWXsBI5Ow== +-----END CERTIFICATE----- From 141fb78237bd6dd0d4b9dee80093ff8c9a0840fa Mon Sep 17 00:00:00 2001 From: brain5lug Date: Tue, 11 Oct 2016 00:19:34 +0300 Subject: [PATCH 1918/6300] Tag class clean-up --- Tag.h | 137 +++++++++++++++++++++++++++------------------------------- 1 file changed, 64 insertions(+), 73 deletions(-) diff --git a/Tag.h b/Tag.h index 92e2f1a5..e6e7ffd4 100644 --- a/Tag.h +++ b/Tag.h @@ -1,3 +1,6 @@ +#ifndef TAG_H__ +#define TAG_H__ + /* * Copyright (c) 2013-2016, The PurpleI2P Project * @@ -6,92 +9,80 @@ * See full license text in LICENSE file at top of project tree */ -#ifndef TAG_H__ -#define TAG_H__ - -#include /* memcpy */ - +#include +#include #include "Base.h" namespace i2p { namespace data { - template - class Tag + +template +class Tag +{ + BOOST_STATIC_ASSERT_MSG(sz % 8 == 0, "Tag size must be multiple of 8 bytes"); + +public: + + Tag () = default; + Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); } + + bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); } + bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; } + + uint8_t * operator()() { return m_Buf; } + const uint8_t * operator()() const { return m_Buf; } + + operator uint8_t * () { return m_Buf; } + operator const uint8_t * () const { return m_Buf; } + + const uint8_t * data() const { return m_Buf; } + const uint64_t * GetLL () const { return ll; } + + bool IsZero () const { - public: + for (size_t i = 0; i < sz/8; ++i) + if (ll[i]) return false; + return true; + } - Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); }; - Tag (const Tag& ) = default; -#ifndef _WIN32 // FIXME!!! msvs 2013 can't compile it - Tag (Tag&& ) = default; -#endif - Tag () = default; - - Tag& operator= (const Tag& ) = default; -#ifndef _WIN32 - Tag& operator= (Tag&& ) = default; -#endif - - uint8_t * operator()() { return m_Buf; }; - const uint8_t * operator()() const { return m_Buf; }; - - operator uint8_t * () { return m_Buf; }; - operator const uint8_t * () const { return m_Buf; }; - - const uint64_t * GetLL () const { return ll; }; - - bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); }; - bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; }; - - bool IsZero () const - { - for (int i = 0; i < sz/8; i++) - if (ll[i]) return false; - return true; - } - - const uint8_t * data() const { return m_Buf; } - - /** fill with a value */ - void Fill(uint8_t c) - { - memset(m_Buf, c, sz); - } + void Fill(uint8_t c) + { + memset(m_Buf, c, sz); + } - std::string ToBase64 () const - { - char str[sz*2]; - int l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2); - str[l] = 0; - return std::string (str); - } + std::string ToBase64 () const + { + char str[sz*2]; + size_t l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2); + return std::string (str, str + l); + } - std::string ToBase32 () const - { - char str[sz*2]; - int l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2); - str[l] = 0; - return std::string (str); - } + std::string ToBase32 () const + { + char str[sz*2]; + size_t l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2); + return std::string (str, str + l); + } - void FromBase32 (const std::string& s) - { - i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); - } + void FromBase32 (const std::string& s) + { + i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); + } - void FromBase64 (const std::string& s) - { - i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); - } + void FromBase64 (const std::string& s) + { + i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); + } - private: +private: - union // 8 bytes alignment - { - uint8_t m_Buf[sz]; - uint64_t ll[sz/8]; - }; + union // 8 bytes aligned + { + uint8_t m_Buf[sz]; + uint64_t ll[sz/8]; }; +}; + } // data } // i2p From 16c37a0f3d55a9f138de7d1c1df6bfabfcaa393c Mon Sep 17 00:00:00 2001 From: brain5lug Date: Tue, 11 Oct 2016 00:46:18 +0300 Subject: [PATCH 1919/6300] indentation fix for missed Fill function --- Tag.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tag.h b/Tag.h index e6e7ffd4..30dfa654 100644 --- a/Tag.h +++ b/Tag.h @@ -45,10 +45,10 @@ public: return true; } - void Fill(uint8_t c) - { - memset(m_Buf, c, sz); - } + void Fill(uint8_t c) + { + memset(m_Buf, c, sz); + } std::string ToBase64 () const { From e78ccc6bec772b976e1031c4bc14428f7b73d90d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 11 Oct 2016 07:31:16 -0400 Subject: [PATCH 1920/6300] fixed #651. check is destination is set --- BOB.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index 8ffffba6..fc49749f 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -568,10 +568,15 @@ namespace client { std::stringstream s; s << "DATA"; s << " NICKNAME: "; s << m_Nickname; - if (m_CurrentDestination->GetLocalDestination ()->IsReady ()) - s << " STARTING: false RUNNING: true STOPPING: false"; + if (m_CurrentDestination) + { + if (m_CurrentDestination->GetLocalDestination ()->IsReady ()) + s << " STARTING: false RUNNING: true STOPPING: false"; + else + s << " STARTING: true RUNNING: false STOPPING: false"; + } else - s << " STARTING: true RUNNING: false STOPPING: false"; + s << " STARTING: false RUNNING: false STOPPING: false"; s << " KEYS: true"; s << " QUIET: "; s << (m_IsQuiet ? "true":"false"); if (m_InPort) { From b1f8f9830b16468718c4fa247595275315ab37c2 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 11 Oct 2016 10:18:42 -0400 Subject: [PATCH 1921/6300] fixed another cases for #651 --- BOB.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index fc49749f..28e7f68b 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -437,8 +437,11 @@ namespace client void BOBCommandSession::GetkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getkeys"); - SendReplyOK (m_Keys.ToBase64 ().c_str ()); - } + if (m_Keys.GetPublic ()) // keys are set ? + SendReplyOK (m_Keys.ToBase64 ().c_str ()); + else + SendReplyError ("keys are not set"); + } void BOBCommandSession::GetdestCommandHandler (const char * operand, size_t len) { @@ -506,6 +509,11 @@ namespace client SendReplyError ("Address Not found"); return; } + if (!m_CurrentDestination) + { + SendReplyError ("session not created"); + return; + } auto localDestination = m_CurrentDestination->GetLocalDestination (); auto leaseSet = localDestination->FindLeaseSet (ident); if (leaseSet) From f17df1f16de4baff4bb00e1b071255348d30aea8 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 11 Oct 2016 12:06:40 -0400 Subject: [PATCH 1922/6300] spinlock added --- Identity.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Identity.cpp b/Identity.cpp index 0a3eeffb..99da059e 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -376,13 +376,16 @@ namespace data void IdentityEx::UpdateVerifier (i2p::crypto::Verifier * verifier) const { - if (!m_Verifier || !verifier) + if (!m_Verifier) { auto created = m_IsVerifierCreated.exchange (true); if (!created) m_Verifier.reset (verifier); else + { delete verifier; + while (!m_Verifier) ; // spin lock + } } else delete verifier; @@ -391,7 +394,8 @@ namespace data void IdentityEx::DropVerifier () const { // TODO: potential race condition with Verify - m_Verifier = nullptr; + m_IsVerifierCreated = false; + m_Verifier = nullptr; } PrivateKeys& PrivateKeys::operator=(const Keys& keys) From f0d098d0efb31e7473a186942e1201343578cae3 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 11 Oct 2016 13:39:07 -0400 Subject: [PATCH 1923/6300] use shared local destination for lookup if destination is not set --- BOB.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/BOB.cpp b/BOB.cpp index 28e7f68b..d68c798f 100644 --- a/BOB.cpp +++ b/BOB.cpp @@ -504,17 +504,12 @@ namespace client { LogPrint (eLogDebug, "BOB: lookup ", operand); i2p::data::IdentHash ident; - if (!context.GetAddressBook ().GetIdentHash (operand, ident) || !m_CurrentDestination) + if (!context.GetAddressBook ().GetIdentHash (operand, ident)) { SendReplyError ("Address Not found"); return; } - if (!m_CurrentDestination) - { - SendReplyError ("session not created"); - return; - } - auto localDestination = m_CurrentDestination->GetLocalDestination (); + auto localDestination = m_CurrentDestination ? m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); auto leaseSet = localDestination->FindLeaseSet (ident); if (leaseSet) SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ()); From fe8a0c1a6b104aa159fc1bf9cd9ea6f77f3fa297 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 11 Oct 2016 15:02:23 -0400 Subject: [PATCH 1924/6300] #622. Force SU3 verification by reseed.verify --- Config.cpp | 1 + Reseed.cpp | 92 ++++++++++++++++++++++++------------------- docs/configuration.md | 5 ++- 3 files changed, 56 insertions(+), 42 deletions(-) diff --git a/Config.cpp b/Config.cpp index 84633532..9e981dc3 100644 --- a/Config.cpp +++ b/Config.cpp @@ -150,6 +150,7 @@ namespace config { options_description reseed("Reseed options"); reseed.add_options() + ("reseed.verify", value()->default_value(false), "Verify .su3 signature") ("reseed.file", value()->default_value(""), "Path to .su3 file") #ifdef MESHNET ("reseed.urls", value()->default_value("https://reseed.i2p.rocks:8443/"), "Reseed URLs, separated by comma") diff --git a/Reseed.cpp b/Reseed.cpp index a51dcad4..d8a265db 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -131,52 +131,64 @@ namespace data s.read (signerID, signerIDLength); // signerID signerID[signerIDLength] = 0; - //try to verify signature - auto it = m_SigningKeys.find (signerID); - if (it != m_SigningKeys.end ()) - { - // TODO: implement all signature types - if (signatureType == SIGNING_KEY_TYPE_RSA_SHA512_4096) + bool verify; i2p::config::GetOption("reseed.verify", verify); + if (verify) + { + //try to verify signature + auto it = m_SigningKeys.find (signerID); + if (it != m_SigningKeys.end ()) { - size_t pos = s.tellg (); - size_t tbsLen = pos + contentLength; - uint8_t * tbs = new uint8_t[tbsLen]; - s.seekg (0, std::ios::beg); - s.read ((char *)tbs, tbsLen); - uint8_t * signature = new uint8_t[signatureLength]; - s.read ((char *)signature, signatureLength); - // RSA-raw + // TODO: implement all signature types + if (signatureType == SIGNING_KEY_TYPE_RSA_SHA512_4096) { - // calculate digest - uint8_t digest[64]; - SHA512 (tbs, tbsLen, digest); - // encrypt signature - BN_CTX * bnctx = BN_CTX_new (); - BIGNUM * s = BN_new (), * n = BN_new (); - BN_bin2bn (signature, signatureLength, s); - BN_bin2bn (it->second, i2p::crypto::RSASHA5124096_KEY_LENGTH, n); - BN_mod_exp (s, s, i2p::crypto::GetRSAE (), n, bnctx); // s = s^e mod n - uint8_t * enSigBuf = new uint8_t[signatureLength]; - i2p::crypto::bn2buf (s, enSigBuf, signatureLength); - // digest is right aligned - // we can't use RSA_verify due wrong padding in SU3 - if (memcmp (enSigBuf + (signatureLength - 64), digest, 64)) - LogPrint (eLogWarning, "Reseed: SU3 signature verification failed"); - delete[] enSigBuf; - BN_free (s); BN_free (n); - BN_CTX_free (bnctx); - } + size_t pos = s.tellg (); + size_t tbsLen = pos + contentLength; + uint8_t * tbs = new uint8_t[tbsLen]; + s.seekg (0, std::ios::beg); + s.read ((char *)tbs, tbsLen); + uint8_t * signature = new uint8_t[signatureLength]; + s.read ((char *)signature, signatureLength); + // RSA-raw + { + // calculate digest + uint8_t digest[64]; + SHA512 (tbs, tbsLen, digest); + // encrypt signature + BN_CTX * bnctx = BN_CTX_new (); + BIGNUM * s = BN_new (), * n = BN_new (); + BN_bin2bn (signature, signatureLength, s); + BN_bin2bn (it->second, i2p::crypto::RSASHA5124096_KEY_LENGTH, n); + BN_mod_exp (s, s, i2p::crypto::GetRSAE (), n, bnctx); // s = s^e mod n + uint8_t * enSigBuf = new uint8_t[signatureLength]; + i2p::crypto::bn2buf (s, enSigBuf, signatureLength); + // digest is right aligned + // we can't use RSA_verify due wrong padding in SU3 + if (memcmp (enSigBuf + (signatureLength - 64), digest, 64)) + LogPrint (eLogWarning, "Reseed: SU3 signature verification failed"); + else + verify = false; // verified + delete[] enSigBuf; + BN_free (s); BN_free (n); + BN_CTX_free (bnctx); + } - delete[] signature; - delete[] tbs; - s.seekg (pos, std::ios::beg); + delete[] signature; + delete[] tbs; + s.seekg (pos, std::ios::beg); + } + else + LogPrint (eLogWarning, "Reseed: Signature type ", signatureType, " is not supported"); } else - LogPrint (eLogWarning, "Reseed: Signature type ", signatureType, " is not supported"); + LogPrint (eLogWarning, "Reseed: Certificate for ", signerID, " not loaded"); } - else - LogPrint (eLogWarning, "Reseed: Certificate for ", signerID, " not loaded"); - + + if (verify) // not verified + { + LogPrint (eLogError, "Reseed: SU3 verification failed"); + return 0; + } + // handle content int numFiles = 0; size_t contentPos = s.tellg (); diff --git a/docs/configuration.md b/docs/configuration.md index 31082dc2..dc27f78d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -72,8 +72,9 @@ All options below still possible in cmdline, but better write it in config file: * --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 -* --reseed.file - Full path to SU3 file to reseed from -* --reseed.urls - Reseed URLs, separated by comma +* --reseed.verify= - Request SU3 signature verification +* --reseed.file= - Full path to SU3 file to reseed from +* --reseed.urls= - Reseed URLs, separated by comma * --limits.transittunnels= - Override maximum number of transit tunnels. 2500 by default From 470a6f0ab2ba189e034adfd676b23b82bf8543b8 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Wed, 12 Oct 2016 10:23:43 +0000 Subject: [PATCH 1925/6300] Add addressbook options + improved docs --- AddressBook.cpp | 23 +++++++++++++++++++---- AddressBook.h | 5 ----- Config.cpp | 11 ++++++++++- docs/configuration.md | 2 ++ docs/i2pd.conf | 14 ++++++++++++-- docs/index.rst | 2 ++ 6 files changed, 45 insertions(+), 12 deletions(-) diff --git a/AddressBook.cpp b/AddressBook.cpp index b993f456..db424308 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "Base.h" #include "util.h" #include "Identity.h" @@ -15,6 +16,7 @@ #include "NetDb.h" #include "ClientContext.h" #include "AddressBook.h" +#include "Config.h" namespace i2p { @@ -404,9 +406,21 @@ namespace client m_Subscriptions.push_back (std::make_shared (*this, s)); } LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); + LogPrint (eLogWarning, "Addressbook: subscriptions.txt usage is deprecated, use config file instead"); } - else - LogPrint (eLogWarning, "Addressbook: subscriptions.txt not found in datadir"); + else if (!i2p::config::IsDefault("addressbook.subscriptions")) + { + // using config file items + std::string subscriptionURLs; i2p::config::GetOption("addressbook.subscriptions", subscriptionURLs); + std::vector subsList; + boost::split(subsList, subscriptionURLs, boost::is_any_of(","), boost::token_compress_on); + + for (size_t i = 0; i < subsList.size (); i++) + { + m_Subscriptions.push_back (std::make_shared (*this, subsList[i])); + } + LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); + } } else LogPrint (eLogError, "Addressbook: subscriptions already loaded"); @@ -511,10 +525,11 @@ namespace client { if (!m_IsLoaded) { - // download it from http://i2p-projekt.i2p/hosts.txt + // download it from default subscription LogPrint (eLogInfo, "Addressbook: trying to download it from default subscription."); + std::string defaultSubURL; i2p::config::GetOption("addressbook.defaulturl", defaultSubURL); if (!m_DefaultSubscription) - m_DefaultSubscription = std::make_shared(*this, DEFAULT_SUBSCRIPTION_ADDRESS); + m_DefaultSubscription = std::make_shared(*this, defaultSubURL); m_IsDownloading = true; std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription)); load_hosts.detach(); // TODO: use join diff --git a/AddressBook.h b/AddressBook.h index 7f559cd0..a1cb3def 100644 --- a/AddressBook.h +++ b/AddressBook.h @@ -18,11 +18,6 @@ namespace i2p { namespace client { -#ifdef MESHNET - const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://i42ofzetmgicvui5sshinfckpijix2udewbam4sjo6x5fbukltia.b32.i2p/hosts.txt"; -#else - const char DEFAULT_SUBSCRIPTION_ADDRESS[] = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt"; -#endif const int INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT = 3; // in minutes const int INITIAL_SUBSCRIPTION_RETRY_TIMEOUT = 1; // in minutes const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours) diff --git a/Config.cpp b/Config.cpp index 9e981dc3..f7e591be 100644 --- a/Config.cpp +++ b/Config.cpp @@ -171,12 +171,20 @@ namespace config { #endif ; + options_description addressbook("AddressBook options"); + addressbook.add_options() + ("addressbook.defaulturl", value()->default_value( + "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt" + ), "AddressBook subscription URL for initial setup") + ("addressbook.subscriptions", value()->default_value(""), + "AddressBook subscriptions URLs, separated by comma"); + options_description trust("Trust options"); trust.add_options() ("trust.enabled", value()->default_value(false), "enable explicit trust options") ("trust.family", value()->default_value(""), "Router Familiy to trust for first hops") ("trust.hidden", value()->default_value(false), "should we hide our router from other routers?"); - + m_OptionsDesc .add(general) .add(limits) @@ -190,6 +198,7 @@ namespace config { .add(upnp) .add(precomputation) .add(reseed) + .add(addressbook) .add(trust) ; } diff --git a/docs/configuration.md b/docs/configuration.md index dc27f78d..0a774ab9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -75,6 +75,8 @@ All options below still possible in cmdline, but better write it in config file: * --reseed.verify= - Request SU3 signature verification * --reseed.file= - Full path to SU3 file to reseed from * --reseed.urls= - Reseed URLs, separated by comma +* --addressbook.defaulturl= - AddressBook subscription URL for initial setup +* --addressbook.subscriptions= - AddressBook subscriptions URLs, separated by comma * --limits.transittunnels= - Override maximum number of transit tunnels. 2500 by default diff --git a/docs/i2pd.conf b/docs/i2pd.conf index 94287bd0..9ade3663 100644 --- a/docs/i2pd.conf +++ b/docs/i2pd.conf @@ -92,8 +92,18 @@ ipv6 = false # name = I2Pd [reseed] -## Path to reseed .su3 file (if ) -# file = +## URLs to request reseed data from, separated by comma +## Default: "mainline" I2P Network reseeds +# urls = https://reseed.i2p-projekt.de/,https://i2p.mooo.com/netDb/,https://netdb.i2p2.no/ +## Path to reseed data file (.su3) for manual reseeding +# file = /path/to/i2pseeds.su3 + +[addressbook] +## AddressBook subscription URL for initial setup +## Default: inr.i2p at "mainline" I2P Network +# defaulturl = http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt +## Optional subscriptions URLs, separated by comma +# subscriptions = http://inr.i2p/export/alive-hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt [http] ## Uncomment and set to 'false' to disable Web Console diff --git a/docs/index.rst b/docs/index.rst index 5507b075..8cfddb24 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,7 +32,9 @@ Contents: build_requirements build_notes_unix build_notes_windows + build_notes_cross build_notes_android + build_notes_ios configuration family usage From 9359f5b2967b463e1177aec9ccea8eafcab869cc Mon Sep 17 00:00:00 2001 From: l-n-s Date: Wed, 12 Oct 2016 12:09:58 +0000 Subject: [PATCH 1926/6300] Update README.md Less complicated description --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 798209ad..1fbf6c43 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,9 @@ All communications over I2P are anonymous and end-to-end encrypted, participants don't reveal their real IP addresses. I2P client is a software used for building and using anonymous I2P -networks. Such networks can work on top of ordinary Internet connection, LAN -and even wireless mesh-networks. - -I2P is commonly used for anonymous peer-to-peer applications (filesharing, -cryptocurrencies) and anonymous client-server applications (websites, instant -messengers, chat-servers). +networks. Such networks are commonly used for anonymous peer-to-peer +applications (filesharing, cryptocurrencies) and anonymous client-server +applications (websites, instant messengers, chat-servers). I2P allows people from all around the world to communicate and share information without restrictions. From 93ed032015fa9c15c875f016074c8779ce3ca353 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 12 Oct 2016 09:39:16 -0400 Subject: [PATCH 1927/6300] avoid potential deadlock --- Destination.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index ccf1e6e2..d400259c 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -171,28 +171,35 @@ namespace client std::shared_ptr LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident) { - std::lock_guard lock(m_RemoteLeaseSetsMutex); - auto it = m_RemoteLeaseSets.find (ident); - if (it != m_RemoteLeaseSets.end ()) + std::shared_ptr remoteLS; { - if (!it->second->IsExpired ()) + std::lock_guard lock(m_RemoteLeaseSetsMutex); + auto it = m_RemoteLeaseSets.find (ident); + if (it != m_RemoteLeaseSets.end ()) + remoteLS = it->second; + } + + if (remoteLS) + { + if (!remoteLS->IsExpired ()) { - if (it->second->ExpiresSoon()) + if (remoteLS->ExpiresSoon()) { LogPrint(eLogDebug, "Destination: Lease Set expires soon, updating before expire"); // update now before expiration for smooth handover - RequestDestination(ident, [this, ident] (std::shared_ptr ls) { + auto s = shared_from_this (); + RequestDestination(ident, [s, ident] (std::shared_ptr ls) { if(ls && !ls->IsExpired()) { ls->PopulateLeases(); { - std::lock_guard _lock(m_RemoteLeaseSetsMutex); - m_RemoteLeaseSets[ident] = ls; + std::lock_guard _lock(s->m_RemoteLeaseSetsMutex); + s->m_RemoteLeaseSets[ident] = ls; } } }); } - return it->second; + return remoteLS; } else LogPrint (eLogWarning, "Destination: remote LeaseSet expired"); @@ -203,6 +210,7 @@ namespace client if (ls && !ls->IsExpired ()) { ls->PopulateLeases (); // since we don't store them in netdb + std::lock_guard _lock(m_RemoteLeaseSetsMutex); m_RemoteLeaseSets[ident] = ls; return ls; } From 99983798a47d4b880185223783a0184eb4934932 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 12 Oct 2016 11:26:48 -0400 Subject: [PATCH 1928/6300] configurable netid --- Config.cpp | 1 + I2NPProtocol.cpp | 2 +- RouterContext.cpp | 6 ++++-- RouterContext.h | 5 ++++- RouterInfo.cpp | 3 ++- docs/configuration.md | 1 + 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Config.cpp b/Config.cpp index f7e591be..924f244b 100644 --- a/Config.cpp +++ b/Config.cpp @@ -49,6 +49,7 @@ namespace config { ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv4", value()->zero_tokens()->default_value(true), "Enable communication through ipv4") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") + ("netid", value()->default_value(I2PD_NET_ID), "Specify NetID. Main I2P is 2") ("daemon", value()->zero_tokens()->default_value(false), "Router will go to background after start") ("service", value()->zero_tokens()->default_value(false), "Router will use system folders like '/var/lib/i2pd'") ("notransit", value()->zero_tokens()->default_value(false), "Router will not accept transit tunnels at startup") diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index cbd4c3fc..cdc4fb9b 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -102,7 +102,7 @@ namespace i2p { RAND_bytes ((uint8_t *)&msgID, 4); htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID); - htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, I2PD_NET_ID); + htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, i2p::context.GetNetID ()); } m->len += DELIVERY_STATUS_SIZE; m->FillI2NPMessageHeader (eI2NPDeliveryStatus); diff --git a/RouterContext.cpp b/RouterContext.cpp index 6824adb8..b4bbefb6 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -18,7 +18,8 @@ namespace i2p RouterContext::RouterContext (): m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false), - m_StartupTime (0), m_Status (eRouterStatusOK), m_Error (eRouterErrorNone) + m_StartupTime (0), m_Status (eRouterStatusOK), m_Error (eRouterErrorNone), + m_NetID (I2PD_NET_ID) { } @@ -76,7 +77,8 @@ namespace i2p } routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC - routerInfo.SetProperty ("netId", std::to_string (I2PD_NET_ID)); + i2p::config::GetOption("netid", m_NetID); + routerInfo.SetProperty ("netId", std::to_string (m_NetID)); routerInfo.SetProperty ("router.version", I2P_VERSION); routerInfo.CreateBuffer (m_Keys); m_RouterInfo.SetRouterIdentity (GetIdentity ()); diff --git a/RouterContext.h b/RouterContext.h index b89b3140..7ce310ee 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -58,7 +58,9 @@ namespace i2p void SetStatus (RouterStatus status); RouterError GetError () const { return m_Error; }; void SetError (RouterError error) { m_Status = eRouterStatusError; m_Error = error; }; - + int GetNetID () const { return m_NetID; }; + void SetNetID (int netID) { m_NetID = netID; }; + void UpdatePort (int port); // called from Daemon void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer); @@ -117,6 +119,7 @@ namespace i2p uint32_t m_BandwidthLimit; // allowed bandwidth RouterStatus m_Status; RouterError m_Error; + int m_NetID; std::mutex m_GarlicMutex; }; diff --git a/RouterInfo.cpp b/RouterInfo.cpp index f445ca02..b570d6c2 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -13,6 +13,7 @@ #include "Timestamp.h" #include "Log.h" #include "NetDb.h" +#include "RouterContext.h" #include "RouterInfo.h" namespace i2p @@ -286,7 +287,7 @@ namespace data if (!strcmp (key, "caps")) ExtractCaps (value); // check netId - else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != I2PD_NET_ID) + else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != i2p::context.GetNetID ()) { LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); m_IsUnreachable = true; diff --git a/docs/configuration.md b/docs/configuration.md index 0a774ab9..49d8c98d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -25,6 +25,7 @@ If you are upgrading your very old router (< 2.3.0) see also [this](config_opts_ * --floodfill - Router will be floodfill. false by default * --bandwidth= - Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000) * --family= - Name of a family, router belongs to +* --netid= - Network ID, router belongs to. Main I2P is 2. Windows-specific options: From eba824f5d0a11bfb8e991324011a26a3e6408e32 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Wed, 12 Oct 2016 19:03:35 +0300 Subject: [PATCH 1929/6300] script for inno setup --- Win32/installer.iss | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 Win32/installer.iss diff --git a/Win32/installer.iss b/Win32/installer.iss new file mode 100644 index 00000000..fe348a4c --- /dev/null +++ b/Win32/installer.iss @@ -0,0 +1,32 @@ +#define I2Pd_AppName "i2pd" +#define I2Pd_ver "2.10.0" + +[Setup] +AppName={#I2Pd_AppName} +AppVersion={#I2Pd_ver} +DefaultDirName={pf}\I2Pd +DefaultGroupName=I2Pd +UninstallDisplayIcon={app}\I2Pd.exe +OutputDir=. +LicenseFile=../LICENSE +OutputBaseFilename=setup_{#I2Pd_AppName}_v{#I2Pd_ver} +InternalCompressLevel=ultra64 +Compression=lzma/ultra64 +SolidCompression=true +ArchitecturesInstallIn64BitMode=x64 + +[Files] +Source: "..\i2pd_x86.exe"; DestDir: "{app}"; DestName: "i2pd.exe"; Flags: ignoreversion; Check: not IsWin64 +Source: "..\i2pd_x64.exe"; DestDir: "{app}"; DestName: "i2pd.exe"; Flags: ignoreversion; Check: IsWin64 +Source: "..\README.md"; DestDir: "{app}"; DestName: "Readme.txt"; Flags: onlyifdoesntexist +Source: "..\docs\i2pd.conf"; DestDir: "{userappdata}\i2pd"; Flags: onlyifdoesntexist +Source: "..\docs\subscriptions.txt"; DestDir: "{userappdata}\i2pd"; Flags: onlyifdoesntexist +Source: "..\docs\tunnels.conf"; DestDir: "{userappdata}\i2pd"; Flags: onlyifdoesntexist +Source: "..\contrib\certificates"; DestDir: "{userappdata}\i2pd"; Flags: onlyifdoesntexist recursesubdirs createallsubdirs + +[Icons] +Name: "{group}\I2Pd"; Filename: "{app}\i2pd.exe" +Name: "{group}\Readme"; Filename: "{app}\Readme.txt" + +[UninstallDelete] +Type: filesandordirs; Name: {app}\* From 85c7bfa160cfb6d42717ee13d2e23da880af2b2c Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Wed, 12 Oct 2016 19:30:20 +0300 Subject: [PATCH 1930/6300] Update installer.iss --- Win32/installer.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Win32/installer.iss b/Win32/installer.iss index fe348a4c..7bfe63d9 100644 --- a/Win32/installer.iss +++ b/Win32/installer.iss @@ -22,7 +22,7 @@ Source: "..\README.md"; DestDir: "{app}"; DestName: "Readme.txt"; Flags: onlyifd Source: "..\docs\i2pd.conf"; DestDir: "{userappdata}\i2pd"; Flags: onlyifdoesntexist Source: "..\docs\subscriptions.txt"; DestDir: "{userappdata}\i2pd"; Flags: onlyifdoesntexist Source: "..\docs\tunnels.conf"; DestDir: "{userappdata}\i2pd"; Flags: onlyifdoesntexist -Source: "..\contrib\certificates"; DestDir: "{userappdata}\i2pd"; Flags: onlyifdoesntexist recursesubdirs createallsubdirs +Source: "..\contrib\*"; DestDir: "{userappdata}\i2pd"; Flags: onlyifdoesntexist recursesubdirs createallsubdirs [Icons] Name: "{group}\I2Pd"; Filename: "{app}\i2pd.exe" From e45e5df37754a74956917a1e9c293e6f5835f885 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 12 Oct 2016 12:31:27 -0400 Subject: [PATCH 1931/6300] openssl 1.1 DSA functions --- Crypto.cpp | 7 ++----- Crypto.h | 10 ++++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Crypto.cpp b/Crypto.cpp index 885c65f4..94d1c4c6 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -135,11 +135,8 @@ namespace crypto DSA * CreateDSA () { DSA * dsa = DSA_new (); - dsa->p = BN_dup (dsap); - dsa->q = BN_dup (dsaq); - dsa->g = BN_dup (dsag); - dsa->priv_key = NULL; - dsa->pub_key = NULL; + DSA_set0_pqg (dsa, BN_dup (dsap), BN_dup (dsaq), BN_dup (dsag)); + DSA_set0_key (dsa, NULL, NULL); return dsa; } diff --git a/Crypto.h b/Crypto.h index a66f51b7..19ea0a13 100644 --- a/Crypto.h +++ b/Crypto.h @@ -279,6 +279,16 @@ namespace crypto void InitCrypto (bool precomputation); void TerminateCrypto (); + +// take care about openssl version +#include +#if (OPENSSL_VERSION_NUMBER < 0x010100000) // 1.1.0 +// define getters and setters introduced in 1.1.0 +inline int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) { d->p = p; d->q = q; d->g = g; return 1; } +inline int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) { d->pub_key = pub_key; d->priv_key = priv_key; return 1; } + +#endif + } } From fbf75ea3b9df45704af7dc69b6333264afcc33c8 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 12 Oct 2016 13:28:22 -0400 Subject: [PATCH 1932/6300] check if signer/verifier is set already --- Identity.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Identity.cpp b/Identity.cpp index 99da059e..30051914 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -309,6 +309,7 @@ namespace data void IdentityEx::CreateVerifier () const { + if (m_Verifier) return; // don't create again auto keyType = GetSigningKeyType (); switch (keyType) { @@ -476,6 +477,7 @@ namespace data void PrivateKeys::CreateSigner () const { + if (m_Signer) return; switch (m_Public->GetSigningKeyType ()) { case SIGNING_KEY_TYPE_DSA_SHA1: From 32b47bee2ca71a7a0b4e801be5dc27b39f6ed45c Mon Sep 17 00:00:00 2001 From: l-n-s Date: Wed, 12 Oct 2016 17:52:07 +0000 Subject: [PATCH 1933/6300] Update README.md Add link to Russian docs --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1fbf6c43..f4e0e3cf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ i2pd ==== +[РуÑÑÐºÐ°Ñ Ð²ÐµÑ€ÑиÑ](https://github.com/PurpleI2P/i2pd_docs_ru/blob/master/README.md) + i2pd (I2P Daemon) is a full-featured C++ implementation of I2P client. I2P (Invisible Internet Protocol) is a universal anonymous network layer. From 07a1651fa205d5d328c5279c2fc0c1752dbcbd26 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Thu, 13 Oct 2016 07:45:30 +0000 Subject: [PATCH 1934/6300] Update usage.md fix for readthedocs --- docs/usage.md | 66 +++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index cb678cb4..9480a556 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -35,19 +35,19 @@ If you wish to run your own website in Invisible Internet, follow those steps: 2) Configure i2pd to create HTTP server tunnel. Put in your ~/.i2pd/tunnels.conf file: - [anon-website] - type = http - host = 127.0.0.1 - port = 8080 - keys = anon-website.dat + [anon-website] + type = http + host = 127.0.0.1 + port = 8080 + keys = anon-website.dat 3) Restart i2pd. 4) Find b32 destination of your website. -Go to webconsole -> [I2P tunnels page](http://127.0.0.1:7070/?page=i2p_tunnels). Look for Sever tunnels and you will see address like \.b32.i2p next to anon-website. + Go to webconsole -> [I2P tunnels page](http://127.0.0.1:7070/?page=i2p_tunnels). Look for Sever tunnels and you will see address like \.b32.i2p next to anon-website. -Website is now available in Invisible Internet by visiting this address. + Website is now available in Invisible Internet by visiting this address. 5) (Optional) Register short and rememberable .i2p domain on [inr.i2p](http://inr.i2p). @@ -58,51 +58,51 @@ Website is now available in Invisible Internet by visiting this address. 1) Run your IRC server software and find out which host:port it uses (for example, 127.0.0.1:5555). -For small private IRC servers you can use [miniircd](https://github.com/jrosdahl/miniircd), for large public networks [UnreadIRCd](https://www.unrealircd.org/). + For small private IRC servers you can use [miniircd](https://github.com/jrosdahl/miniircd), for large public networks [UnreadIRCd](https://www.unrealircd.org/). 2) Configure i2pd to create IRC server tunnel. -Simplest case, if your server does not support WebIRC, add this to ~/.i2pd/tunnels.conf: + Simplest case, if your server does not support WebIRC, add this to ~/.i2pd/tunnels.conf: - [anon-chatserver] - type = irc - host = 127.0.0.1 - port = 5555 - keys = chatserver-key.dat + [anon-chatserver] + type = irc + host = 127.0.0.1 + port = 5555 + keys = chatserver-key.dat -And that is it. + And that is it. -Alternatively, if your IRC server supports WebIRC, for example, UnreadIRCd, put this into UnrealIRCd config: + Alternatively, if your IRC server supports WebIRC, for example, UnreadIRCd, put this into UnrealIRCd config: - webirc { - mask 127.0.0.1; - password your_password; - }; + webirc { + mask 127.0.0.1; + password your_password; + }; -Also change line: + Also change line: - modes-on-connect "+ixw"; + modes-on-connect "+ixw"; -to + to - modes-on-connect "+iw"; + modes-on-connect "+iw"; -And this in ~/.i2pd/tunnels.conf: + And this in ~/.i2pd/tunnels.conf: - [anon-chatserver] - type = irc - host = 127.0.0.1 - port = 5555 - keys = chatserver-key.dat - webircpassword = your_password + [anon-chatserver] + type = irc + host = 127.0.0.1 + port = 5555 + keys = chatserver-key.dat + webircpassword = your_password 3) Restart i2pd. 4) Find b32 destination of your anonymous IRC server. -Go to webconsole -> [I2P tunnels page](http://127.0.0.1:7070/?page=i2p_tunnels). Look for Sever tunnels and you will see address like \.b32.i2p next to anon-chatserver. + Go to webconsole -> [I2P tunnels page](http://127.0.0.1:7070/?page=i2p_tunnels). Look for Sever tunnels and you will see address like \.b32.i2p next to anon-chatserver. -Clients will use this address to connect to your server anonymously. + Clients will use this address to connect to your server anonymously. ### Connect to anonymous IRC server From bde5d27a202b055188c43df868e69b46cbd0ed8e Mon Sep 17 00:00:00 2001 From: l-n-s Date: Thu, 13 Oct 2016 16:56:23 +0000 Subject: [PATCH 1935/6300] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f4e0e3cf..368223fa 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ i2pd from source on your OS. * Mac OS X * FreeBSD * Android +* iOS Using i2pd ---------- From f687728c3aa2d33df8c96fe35f02b64aa38ab308 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Sun, 16 Oct 2016 10:59:48 +0000 Subject: [PATCH 1936/6300] edit link to usage documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 368223fa..77527562 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ i2pd from source on your OS. Using i2pd ---------- -See [documentation](https://i2pd.readthedocs.io/en/latest/) and +See [documentation](https://i2pd.readthedocs.io/en/latest/usage.html) and [example config file](https://github.com/PurpleI2P/i2pd/blob/openssl/docs/i2pd.conf). Donations From 04ee419951171b84139722f5b6efae937a45c6ed Mon Sep 17 00:00:00 2001 From: l-n-s Date: Sun, 16 Oct 2016 11:04:59 +0000 Subject: [PATCH 1937/6300] small fixes for docs --- docs/build_notes_unix.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index 18e51e62..970024d2 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -4,8 +4,9 @@ Building on Unix systems First of all we need to make sure that all dependencies are satisfied. This doc is trying to cover: -* [Debian/Ubuntu](#debianubuntu) (contains packaging instructions) -* [Fedora/Centos](#fedoracentos) +* [Debian/Ubuntu](#debian-ubuntu) (contains packaging instructions) +* [Fedora/Centos](#fedora-centos) +* [Fedora/Centos](#mac-os-x) * [FreeBSD](#freebsd) Make sure you have all required dependencies for your system successfully installed. @@ -73,7 +74,8 @@ sudo yum install make cmake gcc gcc-c++ *Latest Fedora system using [DNF](https://en.wikipedia.org/wiki/DNF_(software)) instead of YUM by default, you may prefer to use DNF, but YUM should be ok* -> *Centos 7 has CMake 2.8.11 in the official repositories that too old to build i2pd, CMake >=2.8.12 is required* +> *Centos 7 has CMake 2.8.11 in the official repositories that too old to build i2pd, CMake >=2.8.12 is required.* +> > You could build CMake for Centos manualy(WARNING there are a lot of build dependencies!): > ```bash > wget https://kojipkgs.fedoraproject.org/packages/cmake/2.8.12/3.fc21/src/cmake-2.8.12-3.fc21.src.rpm @@ -95,7 +97,7 @@ miniupnpc-devel MAC OS X -------- -Requires homebrew +Requires [homebrew](http://brew.sh/) ```bash brew install libressl boost From 1ceda52f5924f4d5730b8ae944f87c0a7d9565c5 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 16 Oct 2016 07:52:45 -0400 Subject: [PATCH 1938/6300] 2.10.0 --- version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index 00064c00..3814f5f4 100644 --- a/version.h +++ b/version.h @@ -7,7 +7,7 @@ #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 9 +#define I2PD_VERSION_MINOR 10 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) @@ -21,7 +21,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 26 +#define I2P_VERSION_MICRO 27 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) From a943cc09fe8a4d7295ac63c6e96e96448e48bf22 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 16 Oct 2016 07:58:26 -0400 Subject: [PATCH 1939/6300] 2.10.0 --- debian/changelog | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/debian/changelog b/debian/changelog index 6600f8e4..45c5ca67 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +i2pd (2.10.0-1) unstable; urgency=low + + * updated to version 2.10.0/0.9.27 + * eliminated subsriptions.txt + + -- orignal Sun, 16 2016 13:55:40 +0000 + i2pd (2.9.0-1) unstable; urgency=low * updated to version 2.9.0 From 12c67b5db4ef24a0744a1477c557d5bc9f39a71c Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 16 Oct 2016 08:35:48 -0400 Subject: [PATCH 1940/6300] 2.10.0 --- android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index b6cc6f26..b7e13979 100755 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="2.10.0"> From c42e2fe02ddea91807a1f479d1f6a160e76c8682 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Sun, 16 Oct 2016 13:17:00 +0000 Subject: [PATCH 1941/6300] Update i2pd.conf --- docs/i2pd.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/i2pd.conf b/docs/i2pd.conf index 9ade3663..5e2770f6 100644 --- a/docs/i2pd.conf +++ b/docs/i2pd.conf @@ -92,6 +92,8 @@ ipv6 = false # name = I2Pd [reseed] +## Enable or disable reseed data verification. +verify = true ## URLs to request reseed data from, separated by comma ## Default: "mainline" I2P Network reseeds # urls = https://reseed.i2p-projekt.de/,https://i2p.mooo.com/netDb/,https://netdb.i2p2.no/ From 2edd64470be91b19048548fce32ea8cd86da5d9c Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 16 Oct 2016 09:19:48 -0400 Subject: [PATCH 1942/6300] Update changelog --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 45c5ca67..639629d1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,7 +1,7 @@ i2pd (2.10.0-1) unstable; urgency=low * updated to version 2.10.0/0.9.27 - * eliminated subsriptions.txt + * reseed.verify set to true by default -- orignal Sun, 16 2016 13:55:40 +0000 From 3643d6b5d5077f6c81120b23b8fc404264b9dbc9 Mon Sep 17 00:00:00 2001 From: MXPLRS | Kirill Date: Mon, 17 Oct 2016 07:37:40 +0300 Subject: [PATCH 1943/6300] Update changelog --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 639629d1..1ecafa34 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,7 +3,7 @@ i2pd (2.10.0-1) unstable; urgency=low * updated to version 2.10.0/0.9.27 * reseed.verify set to true by default - -- orignal Sun, 16 2016 13:55:40 +0000 + -- orignal Sun, 16 Oct 2016 13:55:40 +0000 i2pd (2.9.0-1) unstable; urgency=low From d97acacae60da84d5ee34e56111fd93c530ecc21 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 17 Oct 2016 18:45:20 -0400 Subject: [PATCH 1944/6300] sequential LeaseSet request --- Destination.cpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index d400259c..80eecaa2 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -373,26 +373,24 @@ namespace client for (int i = 0; i < num; i++) { i2p::data::IdentHash peerHash (buf + 33 + i*32); - auto floodfill = i2p::data::netdb.FindRouter (peerHash); - if (floodfill) + if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash)) { - LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", peerHash.ToBase64 ()); - if (SendLeaseSetRequest (key, floodfill, request)) - found = true; - } - else - { LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); // TODO: recheck this message i2p::data::netdb.RequestDestination (peerHash); } } - if (!found) - LogPrint (eLogError, "Destination: Suggested floodfills are not presented in netDb"); + + auto floodfill = i2p::data::netdb.GetClosestFloodfill (key, request->excluded); + if (floodfill) + { + LogPrint (eLogInfo, "Destination: Requesting ", key.ToBase64 (), " at ", floodfill->GetIdentHash ().ToBase64 ()); + if (SendLeaseSetRequest (key, floodfill, request)) + found = true; + } } - else - LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills"); if (!found) - { + { + LogPrint (eLogInfo, "Destination: ", key.ToBase64 (), " was not found on ", MAX_NUM_FLOODFILLS_PER_REQUEST, " floodfills"); if (request->requestComplete) request->requestComplete (nullptr); m_LeaseSetRequests.erase (key); } From 442a0c48e7f6ce7c67b06a3839a6360d695e1430 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 19 Oct 2016 10:23:02 -0400 Subject: [PATCH 1945/6300] fixed #675. I2LUA define --- Destination.cpp | 2 ++ Destination.h | 15 +++++++++------ NetDb.h | 1 - 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 80eecaa2..d020d96e 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -716,6 +716,7 @@ namespace client return false; } +#ifdef I2LUA void ClientDestination::Ready(ReadyPromise & p) { ScheduleCheckForReady(&p); @@ -739,6 +740,7 @@ namespace client else // we are not ready ScheduleCheckForReady(p); } +#endif void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) { diff --git a/Destination.h b/Destination.h index 22ffa603..e2d532e8 100644 --- a/Destination.h +++ b/Destination.h @@ -8,7 +8,9 @@ #include #include #include +#ifdef I2LUA #include +#endif #include #include "Identity.h" #include "TunnelPool.h" @@ -145,18 +147,19 @@ namespace client class ClientDestination: public LeaseSetDestination { public: +#ifdef I2LUA // type for informing that a client destination is ready typedef std::promise > ReadyPromise; + // informs promise with shared_from_this() when this destination is ready to use + // if cancelled before ready, informs promise with nullptr + void Ready(ReadyPromise & p); +#endif ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); ~ClientDestination (); bool Start (); bool Stop (); - - // informs promise with shared_from_this() when this destination is ready to use - // if cancelled before ready, informs promise with nullptr - void Ready(ReadyPromise & p); const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; @@ -191,10 +194,10 @@ namespace client std::shared_ptr GetSharedFromThis () { return std::static_pointer_cast(shared_from_this ()); } void PersistTemporaryKeys (); - +#ifdef I2LUA void ScheduleCheckForReady(ReadyPromise * p); void HandleCheckForReady(const boost::system::error_code & ecode, ReadyPromise * p); - +#endif private: i2p::data::PrivateKeys m_Keys; diff --git a/NetDb.h b/NetDb.h index d8ee148a..d295ebbe 100644 --- a/NetDb.h +++ b/NetDb.h @@ -8,7 +8,6 @@ #include #include #include -#include #include "Base.h" #include "Gzip.h" From cb324ca723e9cc890ea3d71bec1eac360987aef0 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 19 Oct 2016 12:54:13 -0400 Subject: [PATCH 1946/6300] portable windows data directory --- FS.cpp | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/FS.cpp b/FS.cpp index a809e8c4..84b30f21 100644 --- a/FS.cpp +++ b/FS.cpp @@ -45,9 +45,19 @@ namespace fs { return; } #if defined(WIN32) || defined(_WIN32) - char localAppData[MAX_PATH]; - SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); - dataDir = std::string(localAppData) + "\\" + appName; + char localAppData[MAX_PATH]; + // check executable directory first + GetModuleFileName (NULL, localAppData, MAX_PATH); + auto execPath = boost::filesystem::path(localAppData).parent_path(); + // if config file exists in .exe's folder use it + if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string + dataDir = execPath.string (); + else + { + // otherwise %appdata% + SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); + dataDir = std::string(localAppData) + "\\" + appName; + } return; #elif defined(MAC_OSX) char *home = getenv("HOME"); @@ -57,12 +67,12 @@ namespace fs { #else /* other unix */ #if defined(ANDROID) if (boost::filesystem::exists("/sdcard")) - { - dataDir = "/sdcard/" + appName; + { + dataDir = "/sdcard/" + appName; return; - } - // otherwise use /data/files -#endif + } + // otherwise use /data/files +#endif char *home = getenv("HOME"); if (isService) { dataDir = "/var/lib/" + appName; @@ -112,10 +122,10 @@ namespace fs { bool CreateDirectory (const std::string& path) { - if (boost::filesystem::exists(path) && + if (boost::filesystem::exists(path) && boost::filesystem::is_directory (boost::filesystem::status (path))) return true; return boost::filesystem::create_directory(path); - } + } void HashedStorage::SetPlace(const std::string &path) { root = path + i2p::fs::dirSep + name; @@ -125,7 +135,7 @@ namespace fs { if (!boost::filesystem::exists(root)) { boost::filesystem::create_directories(root); } - + for (size_t i = 0; i < count; i++) { auto p = root + i2p::fs::dirSep + prefix1 + chars[i]; if (boost::filesystem::exists(p)) From 0df04501076a14e33894975a862560a02d7a001c Mon Sep 17 00:00:00 2001 From: Anatolii Vorona Date: Thu, 20 Oct 2016 12:18:59 +0200 Subject: [PATCH 1947/6300] added spec and service files --- i2pd.service | 16 +++++++ i2pd.spec | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 i2pd.service create mode 100644 i2pd.spec diff --git a/i2pd.service b/i2pd.service new file mode 100644 index 00000000..b14af025 --- /dev/null +++ b/i2pd.service @@ -0,0 +1,16 @@ +[Unit] +Description=I2P router +After=network.target + +[Service] +User=i2pd +Group=i2pd +Type=simple +ExecStart=/usr/bin/i2pd --service +PIDFile=/var/lib/i2pd/i2pd.pid +Restart=always +PrivateTmp=true + +[Install] +WantedBy=multi-user.target + diff --git a/i2pd.spec b/i2pd.spec new file mode 100644 index 00000000..8b66d911 --- /dev/null +++ b/i2pd.spec @@ -0,0 +1,129 @@ +Name: i2pd +Version: 2.10.0 +Release: 2%{?dist} +Summary: I2P router written in C++ + +License: BSD +URL: https://github.com/PurpleI2P/i2pd +Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz +Source1: i2pd.service + +%if 0%{?rhel} == 7 +BuildRequires: cmake3 +%else +BuildRequires: cmake +%endif + +BuildRequires: chrpath +BuildRequires: gcc-c++ +BuildRequires: zlib-devel +BuildRequires: boost-devel +BuildRequires: openssl-devel +BuildRequires: miniupnpc-devel +BuildRequires: systemd-units + +%description +C++ implementation of I2P. + + +%package systemd +Summary: Files to run I2P router under systemd +Requires: i2pd +Requires: systemd +Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd +Obsoletes: %{name}-daemon + + +%description systemd +C++ implementation of I2P. + +This package contains systemd unit file to run i2pd as a system service +using dedicated user's permissions. + + +%prep +%setup -q + + +%build +cd build +%if 0%{?rhel} == 7 +%cmake3 \ + -DWITH_LIBRARY=OFF \ + -DWITH_UPNP=ON \ + -DWITH_HARDENING=ON \ + -DBUILD_SHARED_LIBS:BOOL=OFF +%else +%cmake \ + -DWITH_LIBRARY=OFF \ + -DWITH_UPNP=ON \ + -DWITH_HARDENING=ON \ + -DBUILD_SHARED_LIBS:BOOL=OFF +%endif + +make %{?_smp_mflags} + + +%install +cd build +chrpath -d i2pd +install -D -m 755 i2pd %{buildroot}%{_bindir}/i2pd +install -D -m 644 %{SOURCE1} %{buildroot}/%{_unitdir}/i2pd.service +install -d -m 700 %{buildroot}/%{_sharedstatedir}/i2pd + + +%pre systemd +getent group i2pd >/dev/null || %{_sbindir}/groupadd -r i2pd +getent passwd i2pd >/dev/null || \ + %{_sbindir}/useradd -r -g i2pd -s %{_sbindir}/nologin \ + -d %{_sharedstatedir}/i2pd -c 'I2P Service' i2pd + + +%post systemd +%systemd_post i2pd.service + + +%preun systemd +%systemd_preun i2pd.service + + +%postun systemd +%systemd_postun_with_restart i2pd.service + + +%files +%doc LICENSE README.md +%_bindir/i2pd + + +%files systemd +/%_unitdir/i2pd.service +%dir %attr(0700,i2pd,i2pd) %_sharedstatedir/i2pd + + +%changelog +* Tue Oct 20 2016 Anatolii Vorona - 2.10.0-2 +- add support C7 + +* Sun Oct 16 2016 Oleg Girko - 2.10.0-1 +- update to 2.10.0 + +* Sun Aug 14 2016 Oleg Girko - 2.9.0-1 +- update to 2.9.0 + +* Sun Aug 07 2016 Oleg Girko - 2.8.0-2 +- rename daemon subpackage to systemd + +* Sat Aug 06 2016 Oleg Girko - 2.8.0-1 +- update to 2.8.0 +- remove wrong rpath from i2pd binary +- add daemon subpackage with systemd unit file + +* Sat May 21 2016 Oleg Girko - 2.7.0-1 +- update to 2.7.0 + +* Tue Apr 05 2016 Oleg Girko - 2.6.0-1 +- update to 2.6.0 + +* Tue Jan 26 2016 Yaroslav Sidlovsky - 2.3.0-1 +- initial package for version 2.3.0 From f4cb4c1756e5e52501a0f2d2f45b2f8ade5a6398 Mon Sep 17 00:00:00 2001 From: BOPOHA Date: Thu, 20 Oct 2016 13:41:41 +0200 Subject: [PATCH 1948/6300] fixed Centos 7 notes --- docs/build_notes_unix.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index 970024d2..b1cc4148 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -74,16 +74,6 @@ sudo yum install make cmake gcc gcc-c++ *Latest Fedora system using [DNF](https://en.wikipedia.org/wiki/DNF_(software)) instead of YUM by default, you may prefer to use DNF, but YUM should be ok* -> *Centos 7 has CMake 2.8.11 in the official repositories that too old to build i2pd, CMake >=2.8.12 is required.* -> -> You could build CMake for Centos manualy(WARNING there are a lot of build dependencies!): -> ```bash -> wget https://kojipkgs.fedoraproject.org/packages/cmake/2.8.12/3.fc21/src/cmake-2.8.12-3.fc21.src.rpm -> yum-builddep cmake-2.8.12-3.fc21.src.rpm -> rpmbuild --rebuild cmake-2.8.12-3.fc21.src.rpm -> yum install ~/rpmbuild/RPMS/x86_64/cmake-2.8.12-3.el7.centos.x86_64.rpm -> ``` - Also you will need a bunch of development libraries ```bash sudo yum install boost-devel openssl-devel @@ -93,6 +83,15 @@ If you need UPnP support (don't forget to run CMake with `WITH_UPNP=ON`) miniupn ```bash miniupnpc-devel ``` +> *Centos 7 has CMake 2.8.11 in the official repositories that too old to build i2pd, CMake >=2.8.12 is required.* +> +> But you can use cmake3 from the epel repository: +> ```bash +> yum install epel-release -y +> yum install make cmake3 gcc gcc-c++ miniupnpc-devel boost-devel openssl-devel -y +> cmake3 -DWITH_LIBRARY=OFF -DWITH_UPNP=ON -DWITH_HARDENING=ON -DBUILD_SHARED_LIBS:BOOL=OFF +> make +> ``` MAC OS X -------- From b683c07d5560e4813c7a2c3a684e0d50c6f31ac5 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Thu, 20 Oct 2016 13:08:38 +0000 Subject: [PATCH 1949/6300] move rpm-related files to contrib folder --- i2pd.service => contrib/rpm/i2pd.service | 0 i2pd.spec => contrib/rpm/i2pd.spec | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename i2pd.service => contrib/rpm/i2pd.service (100%) rename i2pd.spec => contrib/rpm/i2pd.spec (100%) diff --git a/i2pd.service b/contrib/rpm/i2pd.service similarity index 100% rename from i2pd.service rename to contrib/rpm/i2pd.service diff --git a/i2pd.spec b/contrib/rpm/i2pd.spec similarity index 100% rename from i2pd.spec rename to contrib/rpm/i2pd.spec From c15e53e9c070380c1950c7a763330c462cc39b88 Mon Sep 17 00:00:00 2001 From: Anatolii Vorona Date: Thu, 20 Oct 2016 15:49:56 +0200 Subject: [PATCH 1950/6300] fix paths --- contrib/rpm/i2pd.spec | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 8b66d911..bb58d28d 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,12 +1,11 @@ Name: i2pd Version: 2.10.0 -Release: 2%{?dist} +Release: 3%{?dist} Summary: I2P router written in C++ License: BSD URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz -Source1: i2pd.service %if 0%{?rhel} == 7 BuildRequires: cmake3 @@ -68,7 +67,7 @@ make %{?_smp_mflags} cd build chrpath -d i2pd install -D -m 755 i2pd %{buildroot}%{_bindir}/i2pd -install -D -m 644 %{SOURCE1} %{buildroot}/%{_unitdir}/i2pd.service +install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/rpm/i2pd.service %{buildroot}/%{_unitdir}/i2pd.service install -d -m 700 %{buildroot}/%{_sharedstatedir}/i2pd @@ -102,8 +101,9 @@ getent passwd i2pd >/dev/null || \ %changelog -* Tue Oct 20 2016 Anatolii Vorona - 2.10.0-2 +* Tue Oct 20 2016 Anatolii Vorona - 2.10.0-3 - add support C7 +- move rpm-related files to contrib folder * Sun Oct 16 2016 Oleg Girko - 2.10.0-1 - update to 2.10.0 From ed09c1171bc2b5ce3e41d8051d7e404343026005 Mon Sep 17 00:00:00 2001 From: Vlad Glagolev Date: Thu, 20 Oct 2016 10:37:45 -0400 Subject: [PATCH 1951/6300] fixed build with LibreSSL --- Crypto.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Crypto.h b/Crypto.h index 19ea0a13..4408a193 100644 --- a/Crypto.h +++ b/Crypto.h @@ -282,7 +282,7 @@ namespace crypto // take care about openssl version #include -#if (OPENSSL_VERSION_NUMBER < 0x010100000) // 1.1.0 +#if (OPENSSL_VERSION_NUMBER < 0x010100000) || defined(LIBRESSL_VERSION_NUMBER) // 1.1.0 or LibreSSL // define getters and setters introduced in 1.1.0 inline int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) { d->p = p; d->q = q; d->g = g; return 1; } inline int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) { d->pub_key = pub_key; d->priv_key = priv_key; return 1; } From 25c188496131f71ae752e6ae40b147e7f4e48e4a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 20 Oct 2016 15:20:08 -0400 Subject: [PATCH 1952/6300] correct stream termination --- I2PTunnel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index be48b83a..0ce9a7e3 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -175,7 +175,9 @@ namespace client Write (m_StreamBuffer, bytes_transferred); // postpone termination else Terminate (); - } + } + else + Terminate (); } else Write (m_StreamBuffer, bytes_transferred); From b68381db5802eea64120f3f4095ce55089bc64fe Mon Sep 17 00:00:00 2001 From: Vlad Glagolev Date: Sat, 22 Oct 2016 16:38:45 -0400 Subject: [PATCH 1953/6300] fixed build with OpenBSD --- DaemonLinux.cpp | 2 ++ Makefile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 22d7dec8..87e50a72 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -75,10 +75,12 @@ namespace i2p return false; } +#if !defined(__OpenBSD__) // point std{in,out,err} descriptors to /dev/null stdin = freopen("/dev/null", "r", stdin); stdout = freopen("/dev/null", "w", stdout); stderr = freopen("/dev/null", "w", stderr); +#endif } // Pidfile diff --git a/Makefile b/Makefile index 147bedd4..5cb32004 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ ifeq ($(UNAME),Darwin) else include Makefile.osx endif -else ifeq ($(shell echo $(UNAME) | $(GREP) -c FreeBSD),1) +else ifeq ($(shell echo $(UNAME) | $(GREP) -Ec '(Free|Open)BSD'),1) DAEMON_SRC += DaemonLinux.cpp include Makefile.bsd else ifeq ($(UNAME),Linux) From 3167ae21b0d95a9445e8ea96957811ac91eed2fe Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 22 Oct 2016 20:08:15 -0400 Subject: [PATCH 1954/6300] send own LeasetSet through a stalled stream --- Garlic.h | 3 ++- Streaming.cpp | 26 ++++++++++++++++++++++++++ Streaming.h | 24 ++++++++++++++++++------ 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Garlic.h b/Garlic.h index 76d8eaf3..0fa5403c 100644 --- a/Garlic.h +++ b/Garlic.h @@ -105,7 +105,8 @@ namespace garlic if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated; }; bool IsLeaseSetNonConfirmed () const { return m_LeaseSetUpdateStatus == eLeaseSetSubmitted; }; - + bool IsLeaseSetUpdated () const { return m_LeaseSetUpdateStatus == eLeaseSetUpdated; }; + std::shared_ptr GetSharedRoutingPath (); void SetSharedRoutingPath (std::shared_ptr path); diff --git a/Streaming.cpp b/Streaming.cpp index 02e738c8..78465b87 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -659,6 +659,29 @@ namespace stream LogPrint (eLogWarning, "Streaming: All leases are expired, sSID=", m_SendStreamID); } + void Stream::SendUpdatedLeaseSet () + { + if (m_RoutingSession && m_RoutingSession->IsLeaseSetUpdated ()) + { + if (!m_CurrentRemoteLease) + UpdateCurrentRemoteLease (true); + if (m_CurrentRemoteLease) + { + auto msg = m_RoutingSession->WrapSingleMessage (nullptr); + auto outboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (); + if (outboundTunnel) + m_CurrentOutboundTunnel->SendTunnelDataMsg ( + { + i2p::tunnel::TunnelMessageBlock + { + i2p::tunnel::eDeliveryTypeTunnel, + m_CurrentRemoteLease->tunnelGateway, m_CurrentRemoteLease->tunnelID, + msg + } + }); + } + } + } void Stream::ScheduleResend () { @@ -760,7 +783,10 @@ namespace stream { m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); if (!m_RemoteLeaseSet) + { LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt + } } if (m_RemoteLeaseSet) { diff --git a/Streaming.h b/Streaming.h index efa4d69e..75ce4c56 100644 --- a/Streaming.h +++ b/Streaming.h @@ -51,6 +51,7 @@ namespace stream const int INITIAL_RTO = 9000; // in milliseconds const size_t MAX_PENDING_INCOMING_BACKLOG = 128; const int PENDING_INCOMING_TIMEOUT = 10; // in seconds + const int MAX_RECEIVE_TIMEOUT = 60; // in seconds /** i2cp option for limiting inbound stremaing connections */ const char I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN[] = "maxconns"; @@ -161,6 +162,7 @@ namespace stream void SendClose (); bool SendPacket (Packet * packet); void SendPackets (const std::vector& packets); + void SendUpdatedLeaseSet (); void SavePacket (Packet * packet); void ProcessPacket (Packet * packet); @@ -170,7 +172,7 @@ namespace stream void UpdateCurrentRemoteLease (bool expired = false); template - void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler); + void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout); void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); @@ -282,18 +284,19 @@ namespace stream m_Service.post ([=](void) { if (!m_ReceiveQueue.empty () || m_Status == eStreamStatusReset) - s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler); + s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler, 0); else { - s->m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(timeout)); + int t = (timeout > MAX_RECEIVE_TIMEOUT) ? MAX_RECEIVE_TIMEOUT : timeout; + s->m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(t)); s->m_ReceiveTimer.async_wait ([=](const boost::system::error_code& ecode) - { s->HandleReceiveTimer (ecode, buffer, handler); }); + { s->HandleReceiveTimer (ecode, buffer, handler, timeout - t); }); } }); } template - void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler) + void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout) { size_t received = ConcatenatePackets (boost::asio::buffer_cast(buffer), boost::asio::buffer_size(buffer)); if (received > 0) @@ -307,8 +310,17 @@ namespace stream handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), 0); } else + { // timeout expired - handler (boost::asio::error::make_error_code (boost::asio::error::timed_out), received); + if (remainingTimeout <= 0) + handler (boost::asio::error::make_error_code (boost::asio::error::timed_out), received); + else + { + // itermediate iterrupt + SendUpdatedLeaseSet (); // send our leaseset if applicable + AsyncReceive (buffer, handler, remainingTimeout); + } + } } } } From 6688f9a5ef85e8cd59e61f66b02945b1f46b8e69 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sun, 23 Oct 2016 08:14:21 -0400 Subject: [PATCH 1955/6300] update cmake for i2lua --- build/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 00048942..f6697612 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -18,6 +18,7 @@ option(WITH_GUI "Include GUI (currently MS Windows only)" ON) option(WITH_MESHNET "Build for cjdns test network" OFF) option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) +option(WITH_I2LUA "Build for i2lua" OFF) # paths set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) @@ -64,6 +65,10 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR MSYS) list (APPEND LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/I2PEndian.cpp") endif () +if (WITH_I2LUA) + add_definitions(-DI2LUA) +endif() + add_library(libi2pd ${LIBI2PD_SRC}) set_target_properties(libi2pd PROPERTIES PREFIX "") install(TARGETS libi2pd @@ -361,6 +366,7 @@ message(STATUS " PCH : ${WITH_PCH}") message(STATUS " MESHNET : ${WITH_MESHNET}") message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") message(STATUS " THEADSANITIZER : ${WITH_THREADSANITIZER}") +message(STATUS " I2LUA : ${WITH_I2LUA}") message(STATUS "---------------------------------------") #Handle paths nicely From c5e2ec5e00c282a3be9a4809236189c8bb616dbc Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 23 Oct 2016 16:16:08 -0400 Subject: [PATCH 1956/6300] random remote lease selection for LeaseSet update --- Streaming.cpp | 23 ++++++++++++++++------- Streaming.h | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 78465b87..31787087 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -661,25 +661,34 @@ namespace stream void Stream::SendUpdatedLeaseSet () { - if (m_RoutingSession && m_RoutingSession->IsLeaseSetUpdated ()) + if (m_RoutingSession && m_RemoteLeaseSet && + (m_RoutingSession->IsLeaseSetUpdated () || m_RoutingSession->IsLeaseSetNonConfirmed ())) { - if (!m_CurrentRemoteLease) - UpdateCurrentRemoteLease (true); - if (m_CurrentRemoteLease) + auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // with threshold + if (leases.empty ()) { - auto msg = m_RoutingSession->WrapSingleMessage (nullptr); auto outboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (); if (outboundTunnel) - m_CurrentOutboundTunnel->SendTunnelDataMsg ( + { + auto lease = leases[rand () % leases.size ()]; + auto msg = m_RoutingSession->WrapSingleMessage (nullptr); + outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, - m_CurrentRemoteLease->tunnelGateway, m_CurrentRemoteLease->tunnelID, + lease->tunnelGateway, lease->tunnelID, msg } }); + LogPrint (eLogDebug, "Streaming: Updated LeaseSet sent. sSID=", m_SendStreamID); + } } + else + { + LogPrint (eLogWarning, "Streaming: Can't sent updated LeaseSet. Remote LeaseSet expired. sSID=", m_SendStreamID); + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); + } } } diff --git a/Streaming.h b/Streaming.h index 75ce4c56..2be724ed 100644 --- a/Streaming.h +++ b/Streaming.h @@ -51,7 +51,7 @@ namespace stream const int INITIAL_RTO = 9000; // in milliseconds const size_t MAX_PENDING_INCOMING_BACKLOG = 128; const int PENDING_INCOMING_TIMEOUT = 10; // in seconds - const int MAX_RECEIVE_TIMEOUT = 60; // in seconds + const int MAX_RECEIVE_TIMEOUT = 30; // in seconds /** i2cp option for limiting inbound stremaing connections */ const char I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN[] = "maxconns"; From 28cf351878f41215ea091bf4768c5b8d3bd5afac Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 24 Oct 2016 07:11:18 -0400 Subject: [PATCH 1957/6300] fixed typo --- Streaming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index 31787087..5eae1628 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -665,7 +665,7 @@ namespace stream (m_RoutingSession->IsLeaseSetUpdated () || m_RoutingSession->IsLeaseSetNonConfirmed ())) { auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // with threshold - if (leases.empty ()) + if (!leases.empty ()) { auto outboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (); if (outboundTunnel) From 4ee9b4524d416660dcd09e023bc7b4d650ea3b36 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 24 Oct 2016 10:33:46 -0400 Subject: [PATCH 1958/6300] correct netid handling --- Daemon.cpp | 3 +++ RouterContext.cpp | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Daemon.cpp b/Daemon.cpp index 7dec30a9..e0c45ac3 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -138,6 +138,9 @@ namespace i2p uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); SetMaxNumTransitTunnels (transitTunnels); + int netID; i2p::config::GetOption("netid", netID); + i2p::context.SetNetID (netID); + bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); if (isFloodfill) { LogPrint(eLogInfo, "Daemon: router will be floodfill"); diff --git a/RouterContext.cpp b/RouterContext.cpp index b4bbefb6..309e1104 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -77,7 +77,6 @@ namespace i2p } routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC - i2p::config::GetOption("netid", m_NetID); routerInfo.SetProperty ("netId", std::to_string (m_NetID)); routerInfo.SetProperty ("router.version", I2P_VERSION); routerInfo.CreateBuffer (m_Keys); From c74db4b81c962563a51a6813bf7e8a4fe62097b8 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 24 Oct 2016 20:58:25 -0400 Subject: [PATCH 1959/6300] resubmit non-confirmed LeaseSet --- Garlic.h | 1 + Streaming.cpp | 42 +++++++++++++++++------------------------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/Garlic.h b/Garlic.h index 0fa5403c..a3ab7d92 100644 --- a/Garlic.h +++ b/Garlic.h @@ -106,6 +106,7 @@ namespace garlic }; bool IsLeaseSetNonConfirmed () const { return m_LeaseSetUpdateStatus == eLeaseSetSubmitted; }; bool IsLeaseSetUpdated () const { return m_LeaseSetUpdateStatus == eLeaseSetUpdated; }; + uint64_t GetLeaseSetSubmissionTime () const { return m_LeaseSetSubmissionTime; } std::shared_ptr GetSharedRoutingPath (); void SetSharedRoutingPath (std::shared_ptr path); diff --git a/Streaming.cpp b/Streaming.cpp index 5eae1628..077adce4 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -661,33 +661,25 @@ namespace stream void Stream::SendUpdatedLeaseSet () { - if (m_RoutingSession && m_RemoteLeaseSet && - (m_RoutingSession->IsLeaseSetUpdated () || m_RoutingSession->IsLeaseSetNonConfirmed ())) - { - auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // with threshold - if (!leases.empty ()) - { - auto outboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (); - if (outboundTunnel) - { - auto lease = leases[rand () % leases.size ()]; - auto msg = m_RoutingSession->WrapSingleMessage (nullptr); - outboundTunnel->SendTunnelDataMsg ( - { - i2p::tunnel::TunnelMessageBlock - { - i2p::tunnel::eDeliveryTypeTunnel, - lease->tunnelGateway, lease->tunnelID, - msg - } - }); - LogPrint (eLogDebug, "Streaming: Updated LeaseSet sent. sSID=", m_SendStreamID); + if (m_RoutingSession) + { + if (m_RoutingSession->IsLeaseSetNonConfirmed ()) + { + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASET_CONFIRMATION_TIMEOUT) + { + // LeaseSet was not confirmed, should try other tunnels + LogPrint (eLogWarning, "Streaming: LeaseSet was not confrimed in ", i2p::garlic::LEASET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit"); + m_RoutingSession->SetSharedRoutingPath (nullptr); + m_CurrentOutboundTunnel = nullptr; + m_CurrentRemoteLease = nullptr; + SendQuickAck (); } - } - else + } + else if (m_RoutingSession->IsLeaseSetUpdated ()) { - LogPrint (eLogWarning, "Streaming: Can't sent updated LeaseSet. Remote LeaseSet expired. sSID=", m_SendStreamID); - m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); + LogPrint (eLogDebug, "Streaming: sending updated LeaseSet"); + SendQuickAck (); } } } From d8510ead43482723216b5952cdd7c04a3ee8313e Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 25 Oct 2016 14:07:34 -0400 Subject: [PATCH 1960/6300] don't return expired LeaseSet --- Destination.cpp | 25 +++++++++++++++++-------- Destination.h | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index d020d96e..5c81a64b 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -202,7 +202,12 @@ namespace client return remoteLS; } else + { LogPrint (eLogWarning, "Destination: remote LeaseSet expired"); + std::lock_guard lock(m_RemoteLeaseSetsMutex); + m_RemoteLeaseSets.erase (ident); + return nullptr; + } } else { @@ -223,12 +228,16 @@ namespace client if (!m_Pool) return nullptr; if (!m_LeaseSet) UpdateLeaseSet (); + std::lock_guard l(m_LeaseSetMutex); return m_LeaseSet; } void LeaseSetDestination::SetLeaseSet (i2p::data::LocalLeaseSet * newLeaseSet) { - m_LeaseSet.reset (newLeaseSet); + { + std::lock_guard l(m_LeaseSetMutex); + m_LeaseSet.reset (newLeaseSet); + } i2p::garlic::GarlicDestination::SetLeaseSetUpdated (); if (m_IsPublic) { @@ -371,14 +380,14 @@ namespace client if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST) { for (int i = 0; i < num; i++) + { + i2p::data::IdentHash peerHash (buf + 33 + i*32); + if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash)) { - i2p::data::IdentHash peerHash (buf + 33 + i*32); - if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash)) - { - LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); // TODO: recheck this message - i2p::data::netdb.RequestDestination (peerHash); - } - } + LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); // TODO: recheck this message + i2p::data::netdb.RequestDestination (peerHash); + } + } auto floodfill = i2p::data::netdb.GetClosestFloodfill (key, request->excluded); if (floodfill) diff --git a/Destination.h b/Destination.h index e2d532e8..ba19a32a 100644 --- a/Destination.h +++ b/Destination.h @@ -131,6 +131,7 @@ namespace client std::map > m_LeaseSetRequests; std::shared_ptr m_Pool; + std::mutex m_LeaseSetMutex; std::shared_ptr m_LeaseSet; bool m_IsPublic; uint32_t m_PublishReplyToken; From 8e1687e7b3dcc482c9919473a7dbdf6f6716cc81 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 26 Oct 2016 00:00:00 +0000 Subject: [PATCH 1961/6300] * reorganize docs in build-notes*.md --- docs/build_requirements.md | 15 --------------- .../android.md} | 0 docs/{build_notes_cross.md => building/cross.md} | 0 docs/{build_notes_ios.md => building/ios.md} | 0 docs/building/requirements.md | 14 ++++++++++++++ docs/{build_notes_unix.md => building/unix.md} | 0 .../windows.md} | 4 +--- docs/index.rst | 13 ++++++------- 8 files changed, 21 insertions(+), 25 deletions(-) delete mode 100644 docs/build_requirements.md rename docs/{build_notes_android.md => building/android.md} (100%) rename docs/{build_notes_cross.md => building/cross.md} (100%) rename docs/{build_notes_ios.md => building/ios.md} (100%) create mode 100644 docs/building/requirements.md rename docs/{build_notes_unix.md => building/unix.md} (100%) rename docs/{build_notes_windows.md => building/windows.md} (98%) diff --git a/docs/build_requirements.md b/docs/build_requirements.md deleted file mode 100644 index 3e0dfea9..00000000 --- a/docs/build_requirements.md +++ /dev/null @@ -1,15 +0,0 @@ -Build requirements -============ - -Linux/FreeBSD/OSX ------------------ - -GCC 4.7 or newer, Boost 1.49 or newer, openssl, zlib. Clang can be used instead of GCC. - -Windows -------- - -VS2013 (known to work with 12.0.21005.1 or newer), Boost 1.46 or newer, -crypto++ 5.62. See Win32/README-Build.txt for instructions on how to build i2pd -and its dependencies. - diff --git a/docs/build_notes_android.md b/docs/building/android.md similarity index 100% rename from docs/build_notes_android.md rename to docs/building/android.md diff --git a/docs/build_notes_cross.md b/docs/building/cross.md similarity index 100% rename from docs/build_notes_cross.md rename to docs/building/cross.md diff --git a/docs/build_notes_ios.md b/docs/building/ios.md similarity index 100% rename from docs/build_notes_ios.md rename to docs/building/ios.md diff --git a/docs/building/requirements.md b/docs/building/requirements.md new file mode 100644 index 00000000..8712a03d --- /dev/null +++ b/docs/building/requirements.md @@ -0,0 +1,14 @@ +Build requirements +================== + +In general, for building i2pd you need several things: + +* compiler with c++11 support (for example: gcc >= 4.7, clang) +* boost >= 1.49 +* openssl library +* zlib library (openssl already depends on it) + +Optional tools: + +* cmake >= 2.8 (or 3.3+ if you want to use precompiled headers on windows) +* miniupnp library (for upnp support) diff --git a/docs/build_notes_unix.md b/docs/building/unix.md similarity index 100% rename from docs/build_notes_unix.md rename to docs/building/unix.md diff --git a/docs/build_notes_windows.md b/docs/building/windows.md similarity index 98% rename from docs/build_notes_windows.md rename to docs/building/windows.md index 2b4b96b6..5c9fda5a 100644 --- a/docs/build_notes_windows.md +++ b/docs/building/windows.md @@ -3,7 +3,7 @@ Building on Windows There are two approaches available to build i2pd on Windows. The best one depends on your needs and personal preferences. One is to use -msys2 and [unix alike infrastructure](build_notes_unix.md). Another +msys2 and [unix alike infrastructure](unix.md). Another one is to use Visual Studio. While there might be no difference for end users of i2pd daemon, developers, however, shall be wary of differences in C++ name mangling between the two compilers when making @@ -20,8 +20,6 @@ development location for the sake of convenience. Adjust paths accordingly if it is not the case. Note that msys uses unix-alike paths like /c/dev/ for C:\dev\. - - msys2 ----- diff --git a/docs/index.rst b/docs/index.rst index 8cfddb24..c1061171 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,14 +29,13 @@ Contents: .. toctree:: :maxdepth: 2 - build_requirements - build_notes_unix - build_notes_windows - build_notes_cross - build_notes_android - build_notes_ios + building/requirements + building/unix + building/windows + building/cross + building/android + building/ios configuration family usage - From 890807b8d71bb849b9b005615790ed211f45868e Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 26 Oct 2016 00:00:00 +0000 Subject: [PATCH 1962/6300] * build docs : markdown cleanup & reformatting --- docs/building/android.md | 62 ++++++----- docs/building/cross.md | 123 +++++++++++----------- docs/building/ios.md | 48 ++++----- docs/building/unix.md | 219 ++++++++++++++++++--------------------- docs/building/windows.md | 146 ++++++++++---------------- 5 files changed, 278 insertions(+), 320 deletions(-) diff --git a/docs/building/android.md b/docs/building/android.md index 93b8b77e..3e9ab089 100644 --- a/docs/building/android.md +++ b/docs/building/android.md @@ -8,50 +8,58 @@ Pre-requesties You need to install Android SDK, NDK and QT with android support. -- [SDK](https://developer.android.com/studio/index.html) (choose command line tools only) -- [NDK](https://developer.android.com/ndk/downloads/index.html) -- [QT](https://www.qt.io/download-open-source/)(for QT only). Choose one for your platform for android. For example QT 5.6 under Linux would be [this file](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-linux-x64-android-5.6.1-1.run ) +- [SDK](https://developer.android.com/studio/index.html) (choose command line tools only) +- [NDK](https://developer.android.com/ndk/downloads/index.html) +- [QT](https://www.qt.io/download-open-source/)(for QT only). + Choose one for your platform for android. For example QT 5.6 under Linux would be [this file](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-linux-x64-android-5.6.1-1.run) You also need Java JDK and Ant. QT-Creator (for QT only) ------------------------ -Open QT-creator that should be installed with QT. + +Open QT-creator that should be installed with QT. Go to Settings/Anndroid and specify correct paths to SDK and NDK. -If everything is correct you will see two set avaiable: +If everything is correct you will see two set avaiable: Android for armeabi-v7a (gcc, qt) and Android for x86 (gcc, qt). Dependencies -------------- -Take following pre-compiled binaries from PurpleI2P's repositories. -```bash -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 -``` +Take following pre-compiled binaries from PurpleI2P's repositories. + + 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 Building the app with QT ------------------------ -- Open qt/i2pd_qt/i2pd_qt.pro in the QT-creator -- Change line MAIN_PATH = /path/to/libraries to an actual path where you put the dependancies to -- Select appropriate project (usually armeabi-v7a) and build -- You will find an .apk file in android-build/bin folder + +- Open `qt/i2pd_qt/i2pd_qt.pro` in the QT-creator +- Change line `MAIN_PATH = /path/to/libraries` to an actual path where you put the dependancies to +- Select appropriate project (usually armeabi-v7a) and build +- You will find an .apk file in `android-build/bin` folder Building the app without QT --------------------------- -- Change line I2PD_LIBS_PATH in android/jni/Application.mk to an actual path where you put the dependancies to -- Run 'ndk-build -j4' from andorid folder -- Create or edit file 'local.properties'. Place 'sdk.dir=' and 'ndk.dir=' -- Run 'ant clean debug' + +- Change line `I2PD_LIBS_PATH` in `android/jni/Application.mk` to an actual path where you put the dependancies to +- Run `ndk-build -j4` from andorid folder +- Create or edit file 'local.properties'. Place 'sdk.dir=' and 'ndk.dir=' +- Run `ant clean debug` Creating release .apk ---------------------- -In order to create release .apk you must obtain a Java keystore file(.jks). Either you have in already, or you can generate it yourself using keytool, or from one of you existing well-know ceritificates. For example, i2pd release are signed with this [certificate](https://github.com/PurpleI2P/i2pd/blob/openssl/contrib/certificates/router/orignal_at_mail.i2p.crt). -Create file 'ant.propeties' -key.store='path to keystore file' -key.alias='alias name' -key.store.password='keystore password' -key.alias.password='alias password' -Run 'ant clean release' + +In order to create release .apk you must obtain a Java keystore file(.jks). Either you have in already, or you can generate it yourself using keytool, or from one of you existing well-know ceritificates. +For example, i2pd release are signed with this [certificate](https://github.com/PurpleI2P/i2pd/blob/openssl/contrib/certificates/router/orignal_at_mail.i2p.crt). + +Create file 'ant.propeties': + + key.store='path to keystore file' + key.alias='alias name' + key.store.password='keystore password' + key.alias.password='alias password' + +Run `ant clean release` diff --git a/docs/building/cross.md b/docs/building/cross.md index 78d60e6a..fc884cad 100644 --- a/docs/building/cross.md +++ b/docs/building/cross.md @@ -2,74 +2,73 @@ Cross compilation notes ======================= Static 64 bit windows binary on Ubuntu 15.10 (Wily Werewolf) ---------------------------------------------------------------------- +------------------------------------------------------------ Install cross compiler and friends -```sh -sudo apt-get install g++-mingw-w64-x86-64 -``` + + sudo apt-get install g++-mingw-w64-x86-64 + Default is to use Win32 threading model which lacks std::mutex and such. So we change defaults -```sh -sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix -``` -From now on we assume we have everything in `~/dev/`. Get Boost sources unpacked into `~/dev/boost_1_60_0/` -and change directory to it. + + sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix + +From now on we assume we have everything in `~/dev/`. Get Boost sources unpacked into `~/dev/boost_1_60_0/` and change directory to it. Now add out cross compiler configuration. Warning: the following will wipe out whatever you had in there. -```sh -echo "using gcc : mingw : x86_64-w64-mingw32-g++ ;" > ~/user-config.jam -``` + + echo "using gcc : mingw : x86_64-w64-mingw32-g++ ;" > ~/user-config.jam + Proceed with building Boost normal way, but let's define dedicated staging directory -```sh -./bootstrap.sh -./b2 toolset=gcc-mingw target-os=windows variant=release link=static runtime-link=static address-model=64 \ - --build-type=minimal --with-filesystem --with-program_options --with-date_time \ - --stagedir=stage-mingw-64 -cd .. -``` + + ./bootstrap.sh + ./b2 toolset=gcc-mingw target-os=windows variant=release link=static runtime-link=static address-model=64 \ + --build-type=minimal --with-filesystem --with-program_options --with-date_time \ + --stagedir=stage-mingw-64 + cd .. + Now we get & build OpenSSL -```sh -git clone https://github.com/openssl/openssl -cd openssl -git checkout OpenSSL_1_0_2g -./Configure mingw64 no-rc2 no-rc4 no-rc5 no-idea no-bf no-cast no-whirlpool no-md2 no-md4 no-ripemd no-mdc2 \ - no-camellia no-seed no-comp no-krb5 no-gmp no-rfc3779 no-ec2m no-ssl2 no-jpake no-srp no-sctp no-srtp \ - --prefix=~/dev/stage --cross-compile-prefix=x86_64-w64-mingw32- -make depend -make -make install -cd .. -``` -and Zlib -```sh -git clone https://github.com/madler/zlib -cd zlib -git checkout v1.2.8 -CC=x86_64-w64-mingw32-gcc CFLAGS=-O3 ./configure --static --64 --prefix=~/dev/stage -make -make install -cd .. -``` + + git clone https://github.com/openssl/openssl + cd openssl + git checkout OpenSSL_1_0_2g + ./Configure mingw64 no-rc2 no-rc4 no-rc5 no-idea no-bf no-cast no-whirlpool no-md2 no-md4 no-ripemd no-mdc2 \ + no-camellia no-seed no-comp no-krb5 no-gmp no-rfc3779 no-ec2m no-ssl2 no-jpake no-srp no-sctp no-srtp \ + --prefix=~/dev/stage --cross-compile-prefix=x86_64-w64-mingw32- + make depend + make + make install + cd .. + +...and zlib + + git clone https://github.com/madler/zlib + cd zlib + git checkout v1.2.8 + CC=x86_64-w64-mingw32-gcc CFLAGS=-O3 ./configure --static --64 --prefix=~/dev/stage + make + make install + cd .. + Now we prepare cross toolchain hint file for CMake, let's name it `~/dev/toolchain-mingw.cmake` -```cmake -SET(CMAKE_SYSTEM_NAME Windows) -SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) -SET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) -SET(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) -SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -``` -Download miniupnpc, unpack, and symlink it into `~/dev/miniupnpc/`. + + set(CMAKE_SYSTEM_NAME Windows) + set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) + set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) + set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) + set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +Download miniupnpc, unpack and symlink it into `~/dev/miniupnpc/`. Finally, we can build i2pd with all that goodness -```sh -git clone https://github.com/PurpleI2P/i2pd -mkdir i2pd-mingw-64-build -cd i2pd-mingw-64-build -BOOST_ROOT=~/dev/boost_1_60_0 cmake -G 'Unix Makefiles' ~/dev/i2pd/build -DBUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE=~/dev/toolchain-mingw.cmake -DWITH_AESNI=ON -DWITH_UPNP=ON -DWITH_STATIC=ON \ - -DWITH_HARDENING=ON -DCMAKE_INSTALL_PREFIX:PATH=~/dev/i2pd-mingw-64-static \ - -DZLIB_ROOT=~/dev/stage -DBOOST_LIBRARYDIR:PATH=~/dev/boost_1_60_0/stage-mingw-64/lib \ - -DOPENSSL_ROOT_DIR:PATH=~/dev/stage -make -x86_64-w64-mingw32-strip i2pd.exe -``` + + git clone https://github.com/PurpleI2P/i2pd + mkdir i2pd-mingw-64-build + cd i2pd-mingw-64-build + BOOST_ROOT=~/dev/boost_1_60_0 cmake -G 'Unix Makefiles' ~/dev/i2pd/build -DBUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=~/dev/toolchain-mingw.cmake -DWITH_AESNI=ON -DWITH_UPNP=ON -DWITH_STATIC=ON \ + -DWITH_HARDENING=ON -DCMAKE_INSTALL_PREFIX:PATH=~/dev/i2pd-mingw-64-static \ + -DZLIB_ROOT=~/dev/stage -DBOOST_LIBRARYDIR:PATH=~/dev/boost_1_60_0/stage-mingw-64/lib \ + -DOPENSSL_ROOT_DIR:PATH=~/dev/stage + make + x86_64-w64-mingw32-strip i2pd.exe + By now, you should have a release build with stripped symbols. diff --git a/docs/building/ios.md b/docs/building/ios.md index 26688d21..c1753569 100644 --- a/docs/building/ios.md +++ b/docs/building/ios.md @@ -9,31 +9,30 @@ Prerequisites XCode7+, cmake 3.2+ Dependencies --------------- +------------ + - precompiled openssl - precompiled boost with modules `filesystem`, `program_options`, `date_time` and `system` -- ios-cmake toolchain from https://github.com/vovasty/ios-cmake.git +- ios-cmake toolchain from `https://github.com/vovasty/ios-cmake.git` Building ------------------------- +-------- + Assume you have folder structure -``` -lib - libboost_date_time.a - libboost_filesystem.a - libboost_program_options.a - libboost_system.a - libboost.a - libcrypto.a - libssl.a -include - boost - openssl -ios-cmake -i2pd -``` - + lib/ + libboost_date_time.a + libboost_filesystem.a + libboost_program_options.a + libboost_system.a + libboost.a + libcrypto.a + libssl.a + include/ + boost/ + openssl/ + ios-cmake/ + i2pd/ ```bash mkdir -p build/simulator/lib build/ios/lib include/i2pd @@ -77,9 +76,10 @@ cp i2pd/*.h include/i2pd ``` Include into project ---------------------------- -1. add all libraries in `lib` folder to `Project linked frameworks`. -2. add `libc++` and `libz` libraries from system libraries to `Project linked frameworks`. -3. add path to i2p headers to your `Headers search paths` +-------------------- -Alternatively you may use swift wrapper https://github.com/vovasty/SwiftyI2P.git \ No newline at end of file +- add all libraries in `lib` folder to `Project linked frameworks`. +- add `libc++` and `libz` libraries from system libraries to `Project linked frameworks`. +- add path to i2p headers to your `Headers search paths` + +Alternatively you may use swift wrapper `https://github.com/vovasty/SwiftyI2P.git` diff --git a/docs/building/unix.md b/docs/building/unix.md index b1cc4148..b7a638f5 100644 --- a/docs/building/unix.md +++ b/docs/building/unix.md @@ -4,129 +4,34 @@ Building on Unix systems First of all we need to make sure that all dependencies are satisfied. This doc is trying to cover: + * [Debian/Ubuntu](#debian-ubuntu) (contains packaging instructions) * [Fedora/Centos](#fedora-centos) * [Fedora/Centos](#mac-os-x) * [FreeBSD](#freebsd) Make sure you have all required dependencies for your system successfully installed. +See [this](requirements.md) page for common requirements. If so then we are ready to go! Let's clone the repository and start building the i2pd: -```bash -git clone https://github.com/PurpleI2P/i2pd.git -cd i2pd/build -cmake -DCMAKE_BUILD_TYPE=Release # more options could be passed, see "CMake Options" -make # you may add VERBOSE=1 to cmdline for debugging -``` + + git clone https://github.com/PurpleI2P/i2pd.git + +Generic build process looks like this (with cmake): + + cd i2pd/build + cmake . # see "CMake Options" section below + make # you may add VERBOSE=1 to cmdline for debugging + +..or with quick-and-dirty way with just make: + + cd i2pd/ + make After successfull build i2pd could be installed with: -```bash -make install -``` -or you can just use 'make' once you have all dependencies (boost and openssl) installed -```bash -git clone https://github.com/PurpleI2P/i2pd.git -cd i2pd -make -``` - -Debian/Ubuntu -------------- - -You will need a compiler and other tools that could be installed with `build-essential` package: -```bash -sudo apt-get install build-essential -``` - -Also you will need a bunch of development libraries: -```bash -sudo apt-get install \ - libboost-chrono-dev \ - libboost-date-time-dev \ - libboost-filesystem-dev \ - libboost-program-options-dev \ - libboost-system-dev \ - libboost-thread-dev \ - libssl-dev -``` - -If you need UPnP support (don't forget to run CMake with `WITH_UPNP=ON`) miniupnpc development library should be installed: -```bash -sudo apt-get install libminiupnpc-dev -``` - -You may also build deb-package with the following: -```bash -sudo apt-get install fakeroot devscripts -cd i2pd -debuild --no-tgz-check -``` - -Fedora/Centos -------------- - -You will need a compiler and other tools to perform a build: -```bash -sudo yum install make cmake gcc gcc-c++ -``` - -*Latest Fedora system using [DNF](https://en.wikipedia.org/wiki/DNF_(software)) instead of YUM by default, you may prefer to use DNF, but YUM should be ok* - -Also you will need a bunch of development libraries -```bash -sudo yum install boost-devel openssl-devel -``` - -If you need UPnP support (don't forget to run CMake with `WITH_UPNP=ON`) miniupnpc development library should be installed: -```bash -miniupnpc-devel -``` -> *Centos 7 has CMake 2.8.11 in the official repositories that too old to build i2pd, CMake >=2.8.12 is required.* -> -> But you can use cmake3 from the epel repository: -> ```bash -> yum install epel-release -y -> yum install make cmake3 gcc gcc-c++ miniupnpc-devel boost-devel openssl-devel -y -> cmake3 -DWITH_LIBRARY=OFF -DWITH_UPNP=ON -DWITH_HARDENING=ON -DBUILD_SHARED_LIBS:BOOL=OFF -> make -> ``` - -MAC OS X --------- - -Requires [homebrew](http://brew.sh/) - -```bash -brew install libressl boost -``` - -Then build: -```bash -make HOMEBREW=1 -``` - - -FreeBSD -------- - -For 10.X use clang. You would also need boost and openssl ports. -Type gmake, it invokes Makefile.bsd, make necessary changes there is required. - -Branch 9.X has gcc v4.2, that knows nothing about required c++11 standart. - -Required ports: - -* `devel/cmake` -* `devel/boost-libs` -* `lang/gcc47`(or later version) - -To use newer compiler you should set these variables(replace "47" with your actual gcc version): -```bash -export CC=/usr/local/bin/gcc47 -export CXX=/usr/local/bin/g++47 -``` + make install CMake Options ------------- @@ -137,12 +42,94 @@ Available CMake options(each option has a form of `=`, for more info * `WITH_BINARY` build i2pd itself * `WITH_LIBRARY` build libi2pd * `WITH_STATIC` build static versions of library and i2pd binary -* `WITH_UPNP` build with UPnP support (requires libupnp) -* `WITH_AESNI` build with AES-NI support (ON/OFF) +* `WITH_UPNP` build with UPnP support (requires libminiupnp) +* `WITH_AESNI` build with AES-NI support (ON/OFF) * `WITH_HARDENING` enable hardening features (ON/OFF) (gcc only) * `WITH_PCH` use pre-compiled header (experimental, speeds up build) Also there is `-L` flag for CMake that could be used to list current cached options: -```bash -cmake -L -``` + + cmake -L + +Debian/Ubuntu +------------- + +You will need a compiler and other tools that could be installed with `build-essential` package: + + sudo apt-get install build-essential + +Also you will need a bunch of development libraries: + + sudo apt-get install \ + libboost-chrono-dev \ + libboost-date-time-dev \ + libboost-filesystem-dev \ + libboost-program-options-dev \ + libboost-system-dev \ + libboost-thread-dev \ + libssl-dev + +If you need UPnP support miniupnpc development library should be installed (don't forget to rerun CMake with needed option): + + sudo apt-get install libminiupnpc-dev + +You may also build deb-package with the following: + + sudo apt-get install fakeroot devscripts + cd i2pd + debuild --no-tgz-check + +Fedora/Centos +------------- + +You will need a compiler and other tools to perform a build: + + sudo yum install make cmake gcc gcc-c++ + +Also you will need a bunch of development libraries + + sudo yum install boost-devel openssl-devel + +If you need UPnP support miniupnpc development library should be installed (don't forget to rerun CMake with needed option): + + sudo yum install miniupnpc-devel + +Latest Fedora systems using [DNF](https://en.wikipedia.org/wiki/DNF_(software)) instead of YUM by default, you may prefer to use DNF, but YUM should be ok + +Centos 7 has CMake 2.8.11 in the official repositories that too old to build i2pd, CMake >=2.8.12 is required. +But you can use cmake3 from the epel repository: + + yum install epel-release -y + yum install make cmake3 gcc gcc-c++ miniupnpc-devel boost-devel openssl-devel -y + +...and then use 'cmake3' instead 'cmake'. + +MAC OS X +-------- + +Requires [homebrew](http://brew.sh) + + brew install boost libressl + +Then build: + + make HOMEBREW=1 + +FreeBSD +------- + +For 10.X use clang. You would also need devel/boost-libs, security/openssl and devel/gmake ports. +Type gmake, it invokes Makefile.bsd, make necessary changes there is required. + +Branch 9.X has gcc v4.2, that is too old (not supports -std=c++11) + +Required ports: + +* `devel/cmake` +* `devel/boost-libs` +* `lang/gcc47`(or later version) + +To use newer compiler you should set these variables(replace "47" with your actual gcc version): + + export CC=/usr/local/bin/gcc47 + export CXX=/usr/local/bin/g++47 diff --git a/docs/building/windows.md b/docs/building/windows.md index 5c9fda5a..0aa76877 100644 --- a/docs/building/windows.md +++ b/docs/building/windows.md @@ -23,81 +23,55 @@ paths like /c/dev/ for C:\dev\. msys2 ----- -### x86 (32-bit architecture) +Get install file `msys2-$ARCH-*.exe` from `https://msys2.github.io` -Get install file msys2-i686-*.exe from https://msys2.github.io. -open MSYS2 Shell (from Start menu). -Install all prerequisites and download i2pd source: +Where $ARCH is `i686` or `x86_64` (matching your system). -```bash -pacman -S mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-gcc git make -mkdir -p /c/dev/i2pd -cd /c/dev/i2pd -git clone https://github.com/PurpleI2P/i2pd.git -cd i2pd -export PATH=/mingw32/bin:/usr/bin # we need compiler on PATH which is usually heavily cluttered on Windows -make -``` - - -### x64 (64-bit architecture) - -Get install file msys2-x86_64-*.exe from https://msys2.github.io. -open MSYS2 Shell (from Start menu). -Install all prerequisites and download i2pd source: - -```bash -pacman -S mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-gcc git make -mkdir -p /c/dev/i2pd -cd /c/dev/i2pd -git clone https://github.com/PurpleI2P/i2pd.git -cd i2pd -export PATH=/mingw64/bin:/usr/bin # we need compiler on PATH which is usually heavily cluttered on Windows -make -``` +- Open MSYS2 Shell (from Start menu). +- Install all prerequisites and download i2pd source: + export ARCH='i686' # or 'x86_64' + export MINGW='mingw32' # or 'mingw64' + pacman -S mingw-w64-$ARCH-boost mingw-w64-$ARCH-openssl mingw-w64-$ARCH-gcc git make + mkdir -p /c/dev/i2pd + cd /c/dev/i2pd + git clone https://github.com/PurpleI2P/i2pd.git + cd i2pd + # we need compiler on PATH which is usually heavily cluttered on Windows + export PATH=/$MINGW/bin:/usr/bin + make ### Caveats -It is important to restrict PATH as described above. If you have -Strawberry Perl and/or Mercurial installed, it will pick up gcc & -openssl from the wrong places. +It is important to restrict PATH as described above. +If you have Strawberry Perl and/or Mercurial installed, +it will pick up gcc & openssl from the wrong places. -If you do use precompiled headers to speed up compilation -(recommended), things can go wrong if compiler options have changed -for whatever reason. Just delete `stdafx.h.gch` found in your build -folder, note the file extension. - -If you are an Arch Linux user, refrain from updating system with -`pacman -Syu`. Always update runtime separately as described on the -home page, otherwise you might end up with DLLs incompatibility -problems. +If you do use precompiled headers to speed up compilation (recommended), +things can go wrong if compiler options have changed for whatever reason. +Just delete `stdafx.h.gch` found in your build folder, note the file extension. +If you are an Arch Linux user, refrain from updating system with `pacman -Syu`. +Always update runtime separately as described on the home page, +otherwise you might end up with DLLs incompatibility problems. ### AES-NI -If your processor has -[AES instruction set](https://en.wikipedia.org/wiki/AES_instruction_set), -you use `make USE_AESNI=1`. No check is done however, it -will compile, but it might crash with `Illegal instruction` if not supported. +If your processor has [AES instruction set](https://en.wikipedia.org/wiki/AES_instruction_set), +use `make USE_AESNI=1` instead just `make`. No check is done however, it will compile, +but it might crash with `Illegal instruction` if this feature is not supported by your processor. -You should be able to run ./i2pd . If you need to start from the new -shell, consider starting *MinGW-w64 Win32 Shell* instead of *MSYS2 Shell* as -it adds`/minw32/bin` to the PATH. +You should be able to run ./i2pd . If you need to start from the new shell, +consider starting *MinGW-w64 Win32 Shell* instead of *MSYS2 Shell* +as it adds `/minw32/bin` to the PATH. ### UPnP -You can install it through the MSYS2 -and build with USE_UPNP key. -```bash -pacman -S mingw-w64-i686-miniupnpc -make USE_UPNP=yes -``` -or -```bash -pacman -S mingw-w64-x86_64-miniupnpc -make USE_UPNP=yes -``` +You can install it through the MSYS2 and build with `USE_UPNP` key. + + export ARCH='i686' # or 'x86_64' + pacman -S mingw-w64-$ARCH-miniupnpc + make USE_UPNP=yes Using Visual Studio ------------------- @@ -112,7 +86,6 @@ Requirements for building: * [Netwide assembler](http://www.nasm.us/) * Strawberry Perl or ActiveState Perl, do NOT try msys2 perl, it won't work - ### Building Boost Open a Command Prompt (there is no need to start Visual Studio command @@ -129,11 +102,9 @@ If you are on 64-bit Windows and you want to build 64-bit version as well After Boost is compiled, set the environment variable `BOOST_ROOT` to the directory Boost was unpacked to, e.g., C:\dev\boost. -If you are planning on building only particular variant, e.g. Debug -only and static linking, and/or you are out of space/time, you might -consider `--build-type=minimal`. Take a look at -[appveyor.yml](../appveyor.yml) for details on how test builds are done. - +If you are planning on building only particular variant, e.g. Debug only and static linking, +and/or you are out of space/time, you might consider `--build-type=minimal`. +Take a look at [appveyor.yml](../appveyor.yml) for details on how test builds are done. ### Building OpenSSL @@ -153,23 +124,19 @@ Now open Visual Studio command prompt and change directory to that with OpenSSL You should have it installed into C:\OpenSSL-Win32 by now. -Note that you might consider providing `-DOPENSSL_ROOT_DIR` to CMake -and/or create a symlink (with mklink /J) to C:\OpenSSL if you plan on -maintaining multiple versions, e.g. 64 bit and/or -static/shared. Consult `C:\Program Files -(x86)\CMake\share\cmake-3.3\Modules\FindOpenSSL.cmake` for details. - +Note that you might consider providing `-DOPENSSL_ROOT_DIR` to CMake and/or +create a symlink (with mklink /J) to C:\OpenSSL if you plan on maintain +multiple versions, e.g. 64 bit and/or static/shared. +See `C:\Program Files (x86)\CMake\share\cmake-3.3\Modules\FindOpenSSL.cmake` for details. ### Get miniupnpc -If you are behind a UPnP enabled router and don't feel like manually -configuring port forwarding, you should consider using -[MiniUPnP](http://miniupnp.free.fr) client. I2pd can be built capable -of using miniupnpc shared library (DLL) to open up necessary -port. You'd want to have include headers around to build i2pd with -support for this. Unpack client source code in a sibling folder, -e.g. C:\dev\miniupnpc . You may want to remove version number from -folder name included in downloaded archive. +If you are behind a UPnP enabled router and don't feel like manually configuring port forwarding, +you should consider using [MiniUPnP](http://miniupnp.free.fr) client. +I2pd can be built capable of using miniupnpc shared library (DLL) to open up necessary port. +You'd want to have include headers around to build i2pd with support for this. +Unpack client source code to subdir, e.g. `C:\dev\miniupnpc`. +You may want to remove version number from folder name included in downloaded archive. ### Creating Visual Studio project @@ -177,19 +144,16 @@ Start CMake GUI, navigate to i2pd directory, choose building directory, e.g. ./ Alternatively, if you feel adventurous, try that from the command line -``` -cd -mkdir out -cd out -cmake ..\build -G "Visual Studio 12 2013" -DWITH_UPNP=ON -DWITH_PCH=ON -DCMAKE_INSTALL_PREFIX:PATH=C:\dev\Debug_Win32_stage -``` - -WITH_UPNP will stay off, if necessary files are not found. + mkdir i2pd\out + cd i2pd\out + cmake ..\build -G "Visual Studio 12 2013" -DWITH_UPNP=ON -DWITH_PCH=ON -DCMAKE_INSTALL_PREFIX:PATH=C:\dev\Debug_Win32_stage +If necessary files are not found `WITH_UPNP` will stay off. ### Building i2pd -You can open generated solution/project with Visual Studio and build -from there, alternatively you can use `cmake --build . --config Release --target install` or +You can open generated solution/project with Visual Studio and build from there, +alternatively you can use `cmake --build . --config Release --target install` or [MSBuild tool](https://msdn.microsoft.com/en-us/library/dd293626.aspx) -`msbuild i2pd.sln /p:Configuration=Release`. + + msbuild i2pd.sln /p:Configuration=Release From be7f4c5da757dc5af833e2d4f92c68eec4797706 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 26 Oct 2016 00:00:00 +0000 Subject: [PATCH 1963/6300] * update changelog --- ChangeLog | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ChangeLog b/ChangeLog index 2fdc0082..98b33e81 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,21 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.10.0] - 2016-10-17 +### Added +- Datagram i2p tunnels +- Unique local addresses for server tunnels +- Configurable list of reseed servers and initial addressbook +- Configurable netid +- Initial iOS support + +### Changed +- Reduced file descriptiors usage +- Strict reseed checks enabled by default + +## Fixed +- Multiple fixes in I2CP and BOB implementations + ## [2.9.0] - 2016-08-12 ### Changed - Proxy refactoring & speedup From b8dcdece387c0df4033d9abad58bf437a4cb535e Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 26 Oct 2016 00:00:00 +0000 Subject: [PATCH 1964/6300] * Destination.cpp : drop use of i2p::util::lexical_cast(), make more compact code --- Destination.cpp | 110 +++++++++++++++++------------------------------- 1 file changed, 38 insertions(+), 72 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index 5c81a64b..ee44b6f4 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -1,6 +1,5 @@ #include #include -#include #include "Crypto.h" #include "Log.h" #include "FS.h" @@ -18,83 +17,50 @@ namespace client m_PublishReplyToken (0), m_PublishConfirmationTimer (m_Service), m_PublishVerificationTimer (m_Service), m_CleanupTimer (m_Service) { - int inboundTunnelLen = DEFAULT_INBOUND_TUNNEL_LENGTH; - int outboundTunnelLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH; - int inboundTunnelsQuantity = DEFAULT_INBOUND_TUNNELS_QUANTITY; - int outboundTunnelsQuantity = DEFAULT_OUTBOUND_TUNNELS_QUANTITY; + int inLen = DEFAULT_INBOUND_TUNNEL_LENGTH; + int inQty = DEFAULT_INBOUND_TUNNELS_QUANTITY; + int outLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH; + int outQty = DEFAULT_OUTBOUND_TUNNELS_QUANTITY; int numTags = DEFAULT_TAGS_TO_SEND; std::shared_ptr > explicitPeers; - if (params) - { - auto it = params->find (I2CP_PARAM_INBOUND_TUNNEL_LENGTH); - if (it != params->end ()) - { - - int len = i2p::util::lexical_cast(it->second, inboundTunnelLen); - if (len >= 0) + try { + if (params) { + auto it = params->find (I2CP_PARAM_INBOUND_TUNNEL_LENGTH); + if (it != params->end ()) + inLen = std::stoi(it->second); + it = params->find (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH); + if (it != params->end ()) + outLen = std::stoi(it->second); + it = params->find (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY); + if (it != params->end ()) + inQty = std::stoi(it->second); + it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY); + if (it != params->end ()) + outQty = std::stoi(it->second); + it = params->find (I2CP_PARAM_TAGS_TO_SEND); + if (it != params->end ()) + numTags = std::stoi(it->second); + LogPrint (eLogInfo, "Destination: parameters for tunnel set to: ", inQty, " inbound (", inLen, " hops), ", outQty, " outbound (", outLen, " hops), ", numTags, " tags"); + it = params->find (I2CP_PARAM_EXPLICIT_PEERS); + if (it != params->end ()) { - inboundTunnelLen = len; + explicitPeers = std::make_shared >(); + std::stringstream ss(it->second); + std::string b64; + while (std::getline (ss, b64, ',')) + { + i2p::data::IdentHash ident; + ident.FromBase64 (b64); + explicitPeers->push_back (ident); + LogPrint (eLogInfo, "Destination: Added to explicit peers list: ", b64); + } } - LogPrint (eLogInfo, "Destination: Inbound tunnel length set to ", inboundTunnelLen); - } - it = params->find (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH); - if (it != params->end ()) - { - - int len = i2p::util::lexical_cast(it->second, outboundTunnelLen); - if (len >= 0) - { - outboundTunnelLen = len; - } - LogPrint (eLogInfo, "Destination: Outbound tunnel length set to ", outboundTunnelLen); - } - it = params->find (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY); - if (it != params->end ()) - { - int quantity = i2p::util::lexical_cast(it->second, inboundTunnelsQuantity); - if (quantity > 0) - { - inboundTunnelsQuantity = quantity; - LogPrint (eLogInfo, "Destination: Inbound tunnels quantity set to ", quantity); - } } - it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY); - if (it != params->end ()) - { - int quantity = i2p::util::lexical_cast(it->second, outboundTunnelsQuantity); - if (quantity > 0) - { - outboundTunnelsQuantity = quantity; - LogPrint (eLogInfo, "Destination: Outbound tunnels quantity set to ", quantity); - } - } - it = params->find (I2CP_PARAM_TAGS_TO_SEND); - if (it != params->end ()) - { - int tagsToSend = i2p::util::lexical_cast(it->second, numTags); - if (tagsToSend > 0) - { - numTags = tagsToSend; - LogPrint (eLogInfo, "Destination: Tags to send set to ", tagsToSend); - } - } - it = params->find (I2CP_PARAM_EXPLICIT_PEERS); - if (it != params->end ()) - { - explicitPeers = std::make_shared >(); - std::stringstream ss(it->second); - std::string b64; - while (std::getline (ss, b64, ',')) - { - i2p::data::IdentHash ident; - ident.FromBase64 (b64); - explicitPeers->push_back (ident); - } - LogPrint (eLogInfo, "Destination: Explicit peers set to ", it->second); - } - } + } catch (std::exception & ex) { + LogPrint(eLogError, "Destination: unable to parse parameters for destination: ", ex.what()); + } SetNumTags (numTags); - m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inboundTunnelLen, outboundTunnelLen, inboundTunnelsQuantity, outboundTunnelsQuantity); + m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inLen, outLen, inQty, outQty); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); } From 143aaa2d28d939e598204035d7fabaee418f0ed7 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 26 Oct 2016 00:00:00 +0000 Subject: [PATCH 1965/6300] * util.h : drop i2p::util::lexical_cast(), not used anymore (#314) --- util.h | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/util.h b/util.h index 77b94995..ee4aade6 100644 --- a/util.h +++ b/util.h @@ -5,9 +5,9 @@ #include #include #include -#include #ifdef ANDROID +#include namespace std { template @@ -22,18 +22,6 @@ namespace i2p { namespace util { - /** - wrapper arround boost::lexical_cast that "never" fails - */ - template - T lexical_cast(const std::string & str, const T fallback) { - try { - return boost::lexical_cast(str); - } catch ( ... ) { - return fallback; - } - } - namespace net { int GetMTU (const boost::asio::ip::address& localAddress); From 9368a93279a2142a846140e066ee0739b4ee20e3 Mon Sep 17 00:00:00 2001 From: hagen Date: Wed, 26 Oct 2016 00:00:00 +0000 Subject: [PATCH 1966/6300] * fgrep can't be used with regex --- Makefile | 2 +- Makefile.linux | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5cb32004..0e50364b 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ ARLIB := libi2pd.a SHLIB_CLIENT := libi2pdclient.so ARLIB_CLIENT := libi2pdclient.a I2PD := i2pd -GREP := fgrep +GREP := grep DEPS := obj/make.dep include filelist.mk diff --git a/Makefile.linux b/Makefile.linux index ddff76a7..072fa685 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -55,7 +55,7 @@ IS_64 := $(shell $(CXX) -dumpmachine 2>&1 | $(GREP) -c "64") ifeq ($(USE_AESNI),yes) ifeq ($(IS_64),1) #check if AES-NI is supported by CPU -ifneq ($(shell grep -c aes /proc/cpuinfo),0) +ifneq ($(shell $(GREP) -c aes /proc/cpuinfo),0) CPU_FLAGS = -maes -DAESNI endif endif From 1286f1c968b8f7f4499dd4ab18525f0346d373a5 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 26 Oct 2016 13:02:19 -0400 Subject: [PATCH 1967/6300] inalidate shared routing path --- Destination.cpp | 14 +++++++------- Streaming.cpp | 10 +++++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Destination.cpp b/Destination.cpp index ee44b6f4..c8384ff9 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -280,7 +280,7 @@ namespace client std::shared_ptr leaseSet; if (buf[DATABASE_STORE_TYPE_OFFSET] == 1) // LeaseSet { - LogPrint (eLogDebug, "Remote LeaseSet"); + LogPrint (eLogDebug, "Destination: Remote LeaseSet"); std::lock_guard lock(m_RemoteLeaseSetsMutex); auto it = m_RemoteLeaseSets.find (buf + DATABASE_STORE_KEY_OFFSET); if (it != m_RemoteLeaseSets.end ()) @@ -290,16 +290,16 @@ namespace client { leaseSet->Update (buf + offset, len - offset); if (leaseSet->IsValid ()) - LogPrint (eLogDebug, "Remote LeaseSet updated"); + LogPrint (eLogDebug, "Destination: Remote LeaseSet updated"); else { - LogPrint (eLogDebug, "Remote LeaseSet update failed"); + LogPrint (eLogDebug, "Destination: Remote LeaseSet update failed"); m_RemoteLeaseSets.erase (it); leaseSet = nullptr; } } else - LogPrint (eLogDebug, "Remote LeaseSet is older. Not updated"); + LogPrint (eLogDebug, "Destination: Remote LeaseSet is older. Not updated"); } else { @@ -308,15 +308,15 @@ namespace client { if (leaseSet->GetIdentHash () != GetIdentHash ()) { - LogPrint (eLogDebug, "New remote LeaseSet added"); + LogPrint (eLogDebug, "Destination: New remote LeaseSet added"); m_RemoteLeaseSets[buf + DATABASE_STORE_KEY_OFFSET] = leaseSet; } else - LogPrint (eLogDebug, "Own remote LeaseSet dropped"); + LogPrint (eLogDebug, "Destination: Own remote LeaseSet dropped"); } else { - LogPrint (eLogError, "New remote LeaseSet failed"); + LogPrint (eLogError, "Destination: New remote LeaseSet failed"); leaseSet = nullptr; } } diff --git a/Streaming.cpp b/Streaming.cpp index 077adce4..a98edc69 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -656,7 +656,11 @@ namespace stream m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs); } else - LogPrint (eLogWarning, "Streaming: All leases are expired, sSID=", m_SendStreamID); + { + LogPrint (eLogWarning, "Streaming: Remote lease is not available, sSID=", m_SendStreamID); + if (m_RoutingSession) + m_RoutingSession->SetSharedRoutingPath (nullptr); // invalidate routing path + } } void Stream::SendUpdatedLeaseSet () @@ -824,13 +828,17 @@ namespace stream } else { + LogPrint (eLogWarning, "Streaming: All remote leases are expired"); m_RemoteLeaseSet = nullptr; m_CurrentRemoteLease = nullptr; // we have requested expired before, no need to do it twice } } else + { + LogPrint (eLogWarning, "Streaming: Remote LeaseSet not found"); m_CurrentRemoteLease = nullptr; + } } StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): From a8a4ef82cd3eda269eab635d52c1319db5a847c3 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 26 Oct 2016 16:19:32 -0400 Subject: [PATCH 1968/6300] fixed android build --- util.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util.h b/util.h index ee4aade6..b8de8b5a 100644 --- a/util.h +++ b/util.h @@ -15,6 +15,11 @@ std::string to_string(T value) { return boost::lexical_cast(value); } + +inline int stoi(const std::string& str) +{ + return boost::lexical_cast(str); +} } #endif From d708e7f682a1f8345c25e4e58463e7663e417194 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 26 Oct 2016 21:40:06 -0400 Subject: [PATCH 1969/6300] check if a lease has been excluded from LeaseSet --- Streaming.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index a98edc69..f1ceb7c9 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -637,7 +637,8 @@ namespace stream } auto ts = i2p::util::GetMillisecondsSinceEpoch (); - if (!m_CurrentRemoteLease || ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD) + if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate || // excluded from LeaseSet + ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD) UpdateCurrentRemoteLease (true); if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) { From 2dcb91b28450d60bb4bb6c3dc3c66d0fd0bbe766 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 27 Oct 2016 20:46:05 -0400 Subject: [PATCH 1970/6300] don't create same incoming stream twice --- Streaming.cpp | 12 +++++++++++- Streaming.h | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Streaming.cpp b/Streaming.cpp index f1ceb7c9..852d11a6 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -844,6 +844,7 @@ namespace stream StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), + m_LastIncomingReceiveStreamID (0), m_PendingIncomingTimer (m_Owner->GetService ()), m_ConnTrackTimer(m_Owner->GetService()), m_ConnsPerMinute(DEFAULT_MAX_CONNS_PER_MIN), @@ -899,8 +900,15 @@ namespace stream { if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream { - auto incomingStream = CreateNewIncomingStream (); uint32_t receiveStreamID = packet->GetReceiveStreamID (); + if (receiveStreamID == m_LastIncomingReceiveStreamID) + { + // already pending + LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists"); + delete packet; // drop it, because previous should be connected + return; + } + auto incomingStream = CreateNewIncomingStream (); incomingStream->HandleNextPacket (packet); // SYN auto ident = incomingStream->GetRemoteIdentity(); if(ident) @@ -914,6 +922,8 @@ namespace stream return; } } + m_LastIncomingReceiveStreamID = receiveStreamID; + // handle saved packets if any { auto it = m_SavedPackets.find (receiveStreamID); diff --git a/Streaming.h b/Streaming.h index 2be724ed..e4a1562c 100644 --- a/Streaming.h +++ b/Streaming.h @@ -253,6 +253,7 @@ namespace stream std::mutex m_StreamsMutex; std::map > m_Streams; // sendStreamID->stream Acceptor m_Acceptor; + uint32_t m_LastIncomingReceiveStreamID; std::list > m_PendingIncomingStreams; boost::asio::deadline_timer m_PendingIncomingTimer; std::map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN From 5c64c2ff42dc478058095a18daed640e873628e0 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 28 Oct 2016 11:33:11 -0400 Subject: [PATCH 1971/6300] handle stream ternimation properly --- SAM.cpp | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/SAM.cpp b/SAM.cpp index 2864cd6f..6707a6dc 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -587,10 +587,28 @@ namespace client void SAMSocket::I2PReceive () { if (m_Stream) - m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE), - std::bind (&SAMSocket::HandleI2PReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2), - SAM_SOCKET_CONNECTION_MAX_IDLE); + { + if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || + m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular + { + m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE), + std::bind (&SAMSocket::HandleI2PReceive, shared_from_this (), + std::placeholders::_1, std::placeholders::_2), + SAM_SOCKET_CONNECTION_MAX_IDLE); + } + else // closed by peer + { + // get remaning data + auto len = m_Stream->ReadSome (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE); + if (len > 0) // still some data + { + boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, len), + std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1)); + } + else // no more data + Terminate (); + } + } } void SAMSocket::HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) @@ -599,6 +617,14 @@ namespace client { LogPrint (eLogError, "SAM: stream read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) + { + if (bytes_transferred > 0) + boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, bytes_transferred), + std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1)); // postpone termination + else + Terminate (); + } + else Terminate (); } else From 5f396d6311949cf08eb5d0ba209cac2313b0073b Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 28 Oct 2016 12:50:26 -0400 Subject: [PATCH 1972/6300] add option to only connect to certain routers --- Config.cpp | 1 + Daemon.cpp | 36 +++++++++++++++++--- NetDb.cpp | 11 +++--- Transports.cpp | 90 ++++++++++++++++++++++++++++++++++++++++++-------- Transports.h | 10 +++++- Tunnel.cpp | 8 +++-- 6 files changed, 130 insertions(+), 26 deletions(-) diff --git a/Config.cpp b/Config.cpp index 924f244b..80a2ef8a 100644 --- a/Config.cpp +++ b/Config.cpp @@ -184,6 +184,7 @@ namespace config { trust.add_options() ("trust.enabled", value()->default_value(false), "enable explicit trust options") ("trust.family", value()->default_value(""), "Router Familiy to trust for first hops") + ("trust.routers", value()->default_value(""), "Only Connect to these routers") ("trust.hidden", value()->default_value(false), "should we hide our router from other routers?"); m_OptionsDesc diff --git a/Daemon.cpp b/Daemon.cpp index e0c45ac3..ce2f4173 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -194,12 +194,40 @@ namespace i2p { LogPrint(eLogInfo, "Daemon: explicit trust enabled"); std::string fam; i2p::config::GetOption("trust.family", fam); + std::string routers; i2p::config::GetOption("trust.routers", routers); + bool restricted = false; if (fam.length() > 0) { - LogPrint(eLogInfo, "Daemon: setting restricted routes to use family ", fam); - i2p::transport::transports.RestrictRoutes({fam}); - } else - LogPrint(eLogError, "Daemon: no family specified for restricted routes"); + std::set fams; + size_t pos = 0, comma; + do + { + comma = fam.find (',', pos); + fams.insert (fam.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); + pos = comma + 1; + } + while (comma != std::string::npos); + i2p::transport::transports.RestrictRoutesToFamilies(fams); + restricted = fams.size() > 0; + } + if (routers.length() > 0) { + std::set idents; + size_t pos = 0, comma; + do + { + comma = routers.find (',', pos); + i2p::data::IdentHash ident; + ident.FromBase64 (routers.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); + idents.insert (ident); + pos = comma + 1; + } + while (comma != std::string::npos); + LogPrint(eLogInfo, "Daemon: setting restricted routes to use ", idents.size(), " trusted routesrs"); + i2p::transport::transports.RestrictRoutesToRouters(idents); + restricted = idents.size() > 0; + } + if(!restricted) + LogPrint(eLogError, "Daemon: no trusted routers of families specififed"); } bool hidden; i2p::config::GetOption("trust.hidden", hidden); if (hidden) diff --git a/NetDb.cpp b/NetDb.cpp index 2c9c4395..dbfa4bcb 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -126,10 +126,8 @@ namespace data i2p::context.CleanupDestination (); lastDestinationCleanup = ts; } - // if we're in hidden mode don't publish or explore - // if (m_HiddenMode) continue; - - if (ts - lastPublish >= NETDB_PUBLISH_INTERVAL) // publish + + if (ts - lastPublish >= NETDB_PUBLISH_INTERVAL && !m_HiddenMode) // publish { Publish (); lastPublish = ts; @@ -147,8 +145,9 @@ namespace data numRouters = 800/numRouters; if (numRouters < 1) numRouters = 1; if (numRouters > 9) numRouters = 9; - m_Requests.ManageRequests (); - Explore (numRouters); + m_Requests.ManageRequests (); + if(!m_HiddenMode) + Explore (numRouters); lastExploratory = ts; } } diff --git a/Transports.cpp b/Transports.cpp index a29cac15..2efcbedf 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -240,7 +240,8 @@ namespace transport for (auto& it: msgs) i2p::HandleI2NPMessage (it); return; - } + } + if(RoutesRestricted() && ! IsRestrictedPeer(ident)) return; auto it = m_Peers.find (ident); if (it == m_Peers.end ()) { @@ -494,6 +495,12 @@ namespace transport void Transports::DetectExternalIP () { + if (RoutesRestricted()) + { + LogPrint(eLogInfo, "Transports: restricted routes enabled, not detecting ip"); + i2p::context.SetStatus (eRouterStatusFirewalled); + return; + } if (m_SSUServer) { #ifndef MESHNET @@ -520,8 +527,10 @@ namespace transport void Transports::PeerTest () { + if (RoutesRestricted()) return; if (m_SSUServer) { + bool statusChanged = false; for (int i = 0; i < 5; i++) { @@ -578,6 +587,12 @@ namespace transport } else // incoming connection { + if(RoutesRestricted() && ! IsRestrictedPeer(ident)) { + // not trusted + LogPrint(eLogWarning, "Transports: closing untrusted inbound connection from ", ident.ToBase64()); + session->Done(); + return; + } session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore std::unique_lock l(m_PeersMutex); m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} })); @@ -655,7 +670,7 @@ namespace transport std::advance (it, rand () % m_Peers.size ()); return it != m_Peers.end () ? it->second.router : nullptr; } - void Transports::RestrictRoutes(std::vector families) + void Transports::RestrictRoutesToFamilies(std::set families) { std::lock_guard lock(m_FamilyMutex); m_TrustedFamilies.clear(); @@ -663,22 +678,71 @@ namespace transport m_TrustedFamilies.push_back(fam); } + void Transports::RestrictRoutesToRouters(std::set routers) + { + std::unique_lock lock(m_TrustedRoutersMutex); + m_TrustedRouters.clear(); + for (const auto & ri : routers ) + m_TrustedRouters.push_back(ri); + } + bool Transports::RoutesRestricted() const { - std::lock_guard lock(m_FamilyMutex); - return m_TrustedFamilies.size() > 0; + std::unique_lock famlock(m_FamilyMutex); + std::unique_lock routerslock(m_TrustedRoutersMutex); + return m_TrustedFamilies.size() > 0 || m_TrustedRouters.size() > 0; } /** XXX: if routes are not restricted this dies */ - std::shared_ptr Transports::GetRestrictedPeer() const { - std::string fam; - { - std::lock_guard lock(m_FamilyMutex); - // TODO: random family (?) - fam = m_TrustedFamilies[0]; - } - boost::to_lower(fam); - return i2p::data::netdb.GetRandomRouterInFamily(fam); + std::shared_ptr Transports::GetRestrictedPeer() const + { + { + std::lock_guard l(m_FamilyMutex); + std::string fam; + auto sz = m_TrustedFamilies.size(); + if(sz > 1) + { + auto it = m_TrustedFamilies.begin (); + std::advance(it, rand() % sz); + fam = *it; + boost::to_lower(fam); + } + else if (sz == 1) + { + fam = m_TrustedFamilies[0]; + } + if (fam.size()) + return i2p::data::netdb.GetRandomRouterInFamily(fam); + } + { + std::unique_lock l(m_TrustedRoutersMutex); + auto sz = m_TrustedRouters.size(); + if (sz) + { + if(sz == 1) + return i2p::data::netdb.FindRouter(m_TrustedRouters[0]); + auto it = m_TrustedRouters.begin(); + std::advance(it, rand() % sz); + return i2p::data::netdb.FindRouter(*it); + } + } + return nullptr; } + + bool Transports::IsRestrictedPeer(const i2p::data::IdentHash & ih) const + { + { + std::unique_lock l(m_TrustedRoutersMutex); + for (const auto & r : m_TrustedRouters ) + if ( r == ih ) return true; + } + { + std::unique_lock l(m_FamilyMutex); + auto ri = i2p::data::netdb.FindRouter(ih); + for (const auto & fam : m_TrustedFamilies) + if(ri->IsFamily(fam)) return true; + } + return false; + } } } diff --git a/Transports.h b/Transports.h index 81063916..9ecfd719 100644 --- a/Transports.h +++ b/Transports.h @@ -110,7 +110,11 @@ namespace transport /** do we want to use restricted routes? */ bool RoutesRestricted() const; /** restrict routes to use only these router families for first hops */ - void RestrictRoutes(std::vector families); + void RestrictRoutesToFamilies(std::set families); + /** restrict routes to use only these routers for first hops */ + void RestrictRoutesToRouters(std::set routers); + + bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const; void PeerTest (); @@ -157,6 +161,10 @@ namespace transport /** which router families to trust for first hops */ std::vector m_TrustedFamilies; mutable std::mutex m_FamilyMutex; + + /** which routers for first hop to trust */ + std::vector m_TrustedRouters; + mutable std::mutex m_TrustedRoutersMutex; public: diff --git a/Tunnel.cpp b/Tunnel.cpp index 7d2e6735..44d92d75 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -644,7 +644,9 @@ namespace tunnel { // trying to create one more oubound tunnel auto inboundTunnel = GetNextInboundTunnel (); - auto router = i2p::data::netdb.GetRandomRouter (); + auto router = i2p::transport::transports.RoutesRestricted() ? + i2p::transport::transports.GetRestrictedPeer() : + i2p::data::netdb.GetRandomRouter (); if (!inboundTunnel || !router) return; LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel"); CreateTunnel ( @@ -706,7 +708,9 @@ namespace tunnel if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 5) { // trying to create one more inbound tunnel - auto router = i2p::data::netdb.GetRandomRouter (); + auto router = i2p::transport::transports.RoutesRestricted() ? + i2p::transport::transports.GetRestrictedPeer() : + i2p::data::netdb.GetRandomRouter (); if (!router) { LogPrint (eLogWarning, "Tunnel: can't find any router, skip creating tunnel"); return; From c5e1823f1588c8904ab5f0b1ffbcd682f19ef476 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 28 Oct 2016 13:11:50 -0400 Subject: [PATCH 1973/6300] dont't set to firewalled, ssu will try introducers --- Transports.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Transports.cpp b/Transports.cpp index 2efcbedf..cfbc3739 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -498,7 +498,7 @@ namespace transport if (RoutesRestricted()) { LogPrint(eLogInfo, "Transports: restricted routes enabled, not detecting ip"); - i2p::context.SetStatus (eRouterStatusFirewalled); + i2p::context.SetStatus (eRouterStatusOK); return; } if (m_SSUServer) From 578083df3ef5ba5e2babd1a88e2b3a459a91e4a2 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Fri, 28 Oct 2016 13:18:35 -0400 Subject: [PATCH 1974/6300] Add libdl (-ldl) flag. Fixes openssl errors when building statically. --- Makefile.linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.linux b/Makefile.linux index 072fa685..28081c81 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -39,7 +39,7 @@ ifeq ($(USE_STATIC),yes) LDLIBS += $(LIBDIR)/libssl.a LDLIBS += $(LIBDIR)/libcrypto.a LDLIBS += $(LIBDIR)/libz.a - LDLIBS += -lpthread -static-libstdc++ -static-libgcc -lrt + LDLIBS += -lpthread -static-libstdc++ -static-libgcc -lrt -ldl USE_AESNI := no else LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread From 4f4748b8dfdaba6563672adf5cd73908444fe2e9 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Fri, 28 Oct 2016 15:57:18 -0400 Subject: [PATCH 1975/6300] Update nat option: if nat=false, skip reachability testing --- Config.cpp | 10 +--------- Transports.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/Config.cpp b/Config.cpp index 924f244b..6a4c20d1 100644 --- a/Config.cpp +++ b/Config.cpp @@ -27,10 +27,6 @@ namespace config { variables_map m_Options; void Init() { - bool nat = true; -#ifdef MESHNET - nat = false; -#endif options_description general("General options"); general.add_options() @@ -45,7 +41,7 @@ namespace config { ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") ("host", value()->default_value("0.0.0.0"), "External IP") ("ifname", value()->default_value(""), "network interface to bind to") - ("nat", value()->zero_tokens()->default_value(nat), "should we assume we are behind NAT?") + ("nat", value()->zero_tokens()->default_value(true), "should we assume we are behind NAT?") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv4", value()->zero_tokens()->default_value(true), "Enable communication through ipv4") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") @@ -153,9 +149,6 @@ namespace config { reseed.add_options() ("reseed.verify", value()->default_value(false), "Verify .su3 signature") ("reseed.file", value()->default_value(""), "Path to .su3 file") -#ifdef MESHNET - ("reseed.urls", value()->default_value("https://reseed.i2p.rocks:8443/"), "Reseed URLs, separated by comma") -#else ("reseed.urls", value()->default_value( "https://reseed.i2p-projekt.de/," "https://i2p.mooo.com/netDb/," @@ -169,7 +162,6 @@ namespace config { "https://reseed-ru.lngserv.ru/," "https://reseed.atomike.ninja/" ), "Reseed URLs, separated by comma") -#endif ; options_description addressbook("AddressBook options"); diff --git a/Transports.cpp b/Transports.cpp index a29cac15..adb8da06 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -4,6 +4,7 @@ #include "I2NPProtocol.h" #include "NetDb.h" #include "Transports.h" +#include "Config.h" using namespace i2p::data; @@ -496,9 +497,9 @@ namespace transport { if (m_SSUServer) { -#ifndef MESHNET - i2p::context.SetStatus (eRouterStatusTesting); -#endif + bool nat; i2p::config::GetOption("nat", nat); + if (nat) + i2p::context.SetStatus (eRouterStatusTesting); for (int i = 0; i < 5; i++) { From df36b0eb7ea4406a0d0910b1794ff91aec86ac78 Mon Sep 17 00:00:00 2001 From: l-n-s Date: Fri, 28 Oct 2016 16:17:48 -0400 Subject: [PATCH 1976/6300] Uppercase first letters in config help --- Config.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Config.cpp b/Config.cpp index 6a4c20d1..8ac6d7d3 100644 --- a/Config.cpp +++ b/Config.cpp @@ -40,8 +40,8 @@ namespace config { ("family", value()->default_value(""), "Specify a family, router belongs to") ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") ("host", value()->default_value("0.0.0.0"), "External IP") - ("ifname", value()->default_value(""), "network interface to bind to") - ("nat", value()->zero_tokens()->default_value(true), "should we assume we are behind NAT?") + ("ifname", value()->default_value(""), "Network interface to bind to") + ("nat", value()->zero_tokens()->default_value(true), "Should we assume we are behind NAT?") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv4", value()->zero_tokens()->default_value(true), "Enable communication through ipv4") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") @@ -51,8 +51,8 @@ namespace config { ("notransit", value()->zero_tokens()->default_value(false), "Router will not accept transit tunnels at startup") ("floodfill", value()->zero_tokens()->default_value(false), "Router will be floodfill") ("bandwidth", value()->default_value(""), "Bandwidth limit: integer in kbps or letters: L (32), O (256), P (2048), X (>9000)") - ("ntcp", value()->zero_tokens()->default_value(true), "enable ntcp transport") - ("ssu", value()->zero_tokens()->default_value(true), "enable ssu transport") + ("ntcp", value()->zero_tokens()->default_value(true), "Enable NTCP transport") + ("ssu", value()->zero_tokens()->default_value(true), "Enable SSU transport") #ifdef _WIN32 ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") ("insomnia", value()->zero_tokens()->default_value(false), "Prevent system from sleeping") @@ -174,9 +174,9 @@ namespace config { options_description trust("Trust options"); trust.add_options() - ("trust.enabled", value()->default_value(false), "enable explicit trust options") + ("trust.enabled", value()->default_value(false), "Enable explicit trust options") ("trust.family", value()->default_value(""), "Router Familiy to trust for first hops") - ("trust.hidden", value()->default_value(false), "should we hide our router from other routers?"); + ("trust.hidden", value()->default_value(false), "Should we hide our router from other routers?"); m_OptionsDesc .add(general) From 647175cf12bfb08fea148ae4904e4fdaa250c28e Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 30 Oct 2016 09:29:43 -0400 Subject: [PATCH 1977/6300] correct RTO reset --- Streaming.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Streaming.cpp b/Streaming.cpp index 852d11a6..97cfad0a 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -693,8 +693,7 @@ namespace stream { m_ResendTimer.cancel (); // check for invalid value - if (m_RTO <= 0) - m_RTO = 1; + if (m_RTO <= 0) m_RTO = INITIAL_RTO; m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, shared_from_this (), std::placeholders::_1)); @@ -901,13 +900,13 @@ namespace stream if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream { uint32_t receiveStreamID = packet->GetReceiveStreamID (); - if (receiveStreamID == m_LastIncomingReceiveStreamID) + /* if (receiveStreamID == m_LastIncomingReceiveStreamID) { // already pending LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists"); delete packet; // drop it, because previous should be connected return; - } + } */ auto incomingStream = CreateNewIncomingStream (); incomingStream->HandleNextPacket (packet); // SYN auto ident = incomingStream->GetRemoteIdentity(); From 754ad20effbf8558529d2c855b011467bc6a2bc8 Mon Sep 17 00:00:00 2001 From: Lehi Toskin Date: Mon, 31 Oct 2016 03:27:27 -0700 Subject: [PATCH 1978/6300] gracefull -> graceful --- Daemon.h | 2 +- DaemonLinux.cpp | 14 +++++++------- HTTPServer.cpp | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Daemon.h b/Daemon.h index 85b31240..2bc05462 100644 --- a/Daemon.h +++ b/Daemon.h @@ -97,7 +97,7 @@ namespace i2p public: - int gracefullShutdownInterval; // in seconds + int gracefulShutdownInterval; // in seconds }; #endif diff --git a/DaemonLinux.cpp b/DaemonLinux.cpp index 87e50a72..7d791bf2 100644 --- a/DaemonLinux.cpp +++ b/DaemonLinux.cpp @@ -25,11 +25,11 @@ void handle_signal(int sig) i2p::client::context.ReloadConfig(); break; case SIGINT: - if (i2p::context.AcceptsTunnels () && !Daemon.gracefullShutdownInterval) + if (i2p::context.AcceptsTunnels () && !Daemon.gracefulShutdownInterval) { i2p::context.SetAcceptsTunnels (false); - Daemon.gracefullShutdownInterval = 10*60; // 10 minutes - LogPrint(eLogInfo, "Graceful shutdown after ", Daemon.gracefullShutdownInterval, " seconds"); + Daemon.gracefulShutdownInterval = 10*60; // 10 minutes + LogPrint(eLogInfo, "Graceful shutdown after ", Daemon.gracefulShutdownInterval, " seconds"); } else Daemon.running = 0; @@ -110,7 +110,7 @@ namespace i2p return false; } } - gracefullShutdownInterval = 0; // not specified + gracefulShutdownInterval = 0; // not specified // Signal handler struct sigaction sa; @@ -137,10 +137,10 @@ namespace i2p while (running) { std::this_thread::sleep_for (std::chrono::seconds(1)); - if (gracefullShutdownInterval) + if (gracefulShutdownInterval) { - gracefullShutdownInterval--; // - 1 second - if (gracefullShutdownInterval <= 0) + gracefulShutdownInterval--; // - 1 second + if (gracefulShutdownInterval <= 0) { LogPrint(eLogInfo, "Graceful shutdown"); return; diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 487e9c83..890daf9e 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -196,7 +196,7 @@ namespace http { } s << "
    \r\n"; #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) - if (auto remains = Daemon.gracefullShutdownInterval) { + if (auto remains = Daemon.gracefulShutdownInterval) { s << "Stopping in: "; s << remains << " seconds"; s << "
    \r\n"; @@ -416,13 +416,13 @@ namespace http { else s << " Accept transit tunnels
    \r\n"; #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) - if (Daemon.gracefullShutdownInterval) - s << " Cancel gracefull shutdown
    "; + if (Daemon.gracefulShutdownInterval) + s << " Cancel graceful shutdown
    "; else - s << " Start gracefull shutdown
    \r\n"; + s << " Start graceful shutdown
    \r\n"; #endif #ifdef WIN32_APP - s << " Gracefull shutdown
    \r\n"; + s << " Graceful shutdown
    \r\n"; #endif s << " Force shutdown
    \r\n"; } @@ -755,7 +755,7 @@ namespace http { else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { i2p::context.SetAcceptsTunnels (false); #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) - Daemon.gracefullShutdownInterval = 10*60; + Daemon.gracefulShutdownInterval = 10*60; #endif #ifdef WIN32_APP i2p::win32::GracefulShutdown (); @@ -763,7 +763,7 @@ namespace http { } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) - Daemon.gracefullShutdownInterval = 0; + Daemon.gracefulShutdownInterval = 0; #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { Daemon.running = false; From bef628212efcae396cf345b0d747287534bc300c Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Oct 2016 09:46:59 -0400 Subject: [PATCH 1979/6300] fixed corrupted buffer duing IRC handshake --- I2PTunnel.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/I2PTunnel.cpp b/I2PTunnel.cpp index 0ce9a7e3..8756da8b 100644 --- a/I2PTunnel.cpp +++ b/I2PTunnel.cpp @@ -275,26 +275,24 @@ namespace client void I2PTunnelConnectionIRC::Write (const uint8_t * buf, size_t len) { - if (m_NeedsWebIrc) { + m_OutPacket.str (""); + if (m_NeedsWebIrc) + { m_NeedsWebIrc = false; - m_OutPacket.str (""); - m_OutPacket << "WEBIRC " << this->m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) << " 127.0.0.1\n"; - I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ()); + m_OutPacket << "WEBIRC " << m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) << " 127.0.0.1\n"; } - std::string line; - m_OutPacket.str (""); m_InPacket.clear (); m_InPacket.write ((const char *)buf, len); while (!m_InPacket.eof () && !m_InPacket.fail ()) { + std::string line; std::getline (m_InPacket, line); - if (line.length () == 0 && m_InPacket.eof ()) { + if (line.length () == 0 && m_InPacket.eof ()) m_InPacket.str (""); - } auto pos = line.find ("USER"); - if (pos != std::string::npos && pos == 0) + if (!pos) // start of line { pos = line.find (" "); pos++; @@ -304,9 +302,9 @@ namespace client m_OutPacket << line.substr (0, pos); m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()); m_OutPacket << line.substr (nextpos) << '\n'; - } else { + } + else m_OutPacket << line << '\n'; - } } I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ()); } From a41f1797851bcfc81e5fde2b57d91f3bfac241f8 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Oct 2016 14:00:31 -0400 Subject: [PATCH 1980/6300] get home directory from EXTERNAL_STORAGE for andorid --- FS.cpp | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/FS.cpp b/FS.cpp index 84b30f21..74da7ee9 100644 --- a/FS.cpp +++ b/FS.cpp @@ -45,18 +45,18 @@ namespace fs { return; } #if defined(WIN32) || defined(_WIN32) - char localAppData[MAX_PATH]; - // check executable directory first - GetModuleFileName (NULL, localAppData, MAX_PATH); - auto execPath = boost::filesystem::path(localAppData).parent_path(); - // if config file exists in .exe's folder use it - if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string - dataDir = execPath.string (); - else - { - // otherwise %appdata% - SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); - dataDir = std::string(localAppData) + "\\" + appName; + char localAppData[MAX_PATH]; + // check executable directory first + GetModuleFileName (NULL, localAppData, MAX_PATH); + auto execPath = boost::filesystem::path(localAppData).parent_path(); + // if config file exists in .exe's folder use it + if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string + dataDir = execPath.string (); + else + { + // otherwise %appdata% + SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData); + dataDir = std::string(localAppData) + "\\" + appName; } return; #elif defined(MAC_OSX) @@ -66,9 +66,11 @@ namespace fs { return; #else /* other unix */ #if defined(ANDROID) - if (boost::filesystem::exists("/sdcard")) + const char * ext = getenv("EXTERNAL_STORAGE"); + if (!ext) ext = "/sdcard"; + if (boost::filesystem::exists(ext)) { - dataDir = "/sdcard/" + appName; + dataDir = ext + appName; return; } // otherwise use /data/files From a4883cfa15cb316441c2537d5425520bce3f86f8 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Oct 2016 15:13:43 -0400 Subject: [PATCH 1981/6300] print tunnel peers in direct order --- Tunnel.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tunnel.cpp b/Tunnel.cpp index 44d92d75..0c1b278a 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -184,10 +184,11 @@ namespace tunnel void Tunnel::PrintHops (std::stringstream& s) const { - for (auto& it: m_Hops) + // hops are in inverted order, we must print in direct order + for (auto it = m_Hops.rbegin (); it != m_Hops.rend (); it++) { s << " ⇒ "; - s << i2p::data::GetIdentHashAbbreviation (it->ident->GetIdentHash ()); + s << i2p::data::GetIdentHashAbbreviation ((*it)->ident->GetIdentHash ()); } } From b526718846421bc98db4fdaf6e0255aa197b610b Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Oct 2016 15:42:50 -0400 Subject: [PATCH 1982/6300] show HTTP proxy as client tunnel --- ClientContext.h | 1 + HTTPServer.cpp | 9 +++++++++ I2PService.h | 1 + 3 files changed, 11 insertions(+) diff --git a/ClientContext.h b/ClientContext.h index 1a41acfc..d82058c6 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -110,6 +110,7 @@ namespace client const decltype(m_ServerTunnels)& GetServerTunnels () const { return m_ServerTunnels; }; const decltype(m_ClientForwards)& GetClientForwards () const { return m_ClientForwards; } const decltype(m_ServerForwards)& GetServerForwards () const { return m_ServerForwards; } + const i2p::proxy::HTTPProxy * GetHttpProxy () const { return m_HttpProxy; } }; extern ClientContext context; diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 890daf9e..99edf508 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -549,6 +549,15 @@ namespace http { s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
    \r\n"<< std::endl; } + auto httpProxy = i2p::client::context.GetHttpProxy (); + if (httpProxy) + { + auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); + s << ""; + s << "HTTP Proxy" << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
    \r\n"<< std::endl; + } s << "
    \r\nServer Tunnels:
    \r\n
    \r\n"; for (auto& it: i2p::client::context.GetServerTunnels ()) { diff --git a/I2PService.h b/I2PService.h index 2df11909..59746a6f 100644 --- a/I2PService.h +++ b/I2PService.h @@ -38,6 +38,7 @@ namespace client } inline std::shared_ptr GetLocalDestination () { return m_LocalDestination; } + inline std::shared_ptr GetLocalDestination () const { return m_LocalDestination; } inline void SetLocalDestination (std::shared_ptr dest) { m_LocalDestination = dest; } void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port = 0); From 3d4e2a275c5be14014fd54b1c1ccb5590366379a Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Oct 2016 18:10:33 -0400 Subject: [PATCH 1983/6300] correct separator for android --- FS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS.cpp b/FS.cpp index 74da7ee9..64484b15 100644 --- a/FS.cpp +++ b/FS.cpp @@ -70,7 +70,7 @@ namespace fs { if (!ext) ext = "/sdcard"; if (boost::filesystem::exists(ext)) { - dataDir = ext + appName; + dataDir = std::string (ext) + "/" + appName; return; } // otherwise use /data/files From b4e9ed7d18f076ed090ffb8912f6346e29c04285 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 20 Oct 2016 09:12:15 -0400 Subject: [PATCH 1984/6300] add web socket ui --- ClientContext.cpp | 2 +- ClientContext.h | 2 +- Config.cpp | 15 ++++- Daemon.cpp | 25 +++++++- Event.cpp | 28 +++++++++ Event.h | 37 ++++++++++++ I2NPProtocol.cpp | 1 - Log.h | 4 +- NTCPSession.cpp | 4 ++ SSUData.cpp | 8 +++ TransportSession.h | 4 +- Transports.cpp | 15 ++++- Websocket.cpp | 136 +++++++++++++++++++++++++++++++++++++++++++ Websocket.h | 30 ++++++++++ build/CMakeLists.txt | 20 +++++++ 15 files changed, 322 insertions(+), 9 deletions(-) create mode 100644 Event.cpp create mode 100644 Event.h create mode 100644 Websocket.cpp create mode 100644 Websocket.h diff --git a/ClientContext.cpp b/ClientContext.cpp index b7300ad2..215768c2 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -290,7 +290,7 @@ namespace client } return infos; } - + std::shared_ptr ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, const std::map * params) { diff --git a/ClientContext.h b/ClientContext.h index d82058c6..356f28c4 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -68,7 +68,7 @@ namespace client const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; std::vector > GetForwardInfosFor(const i2p::data::IdentHash & destination); - + private: void ReadTunnels (); diff --git a/Config.cpp b/Config.cpp index 4750a9e3..99f4b0bb 100644 --- a/Config.cpp +++ b/Config.cpp @@ -176,9 +176,21 @@ namespace config { trust.add_options() ("trust.enabled", value()->default_value(false), "Enable explicit trust options") ("trust.family", value()->default_value(""), "Router Familiy to trust for first hops") +<<<<<<< HEAD ("trust.routers", value()->default_value(""), "Only Connect to these routers") ("trust.hidden", value()->default_value(false), "Should we hide our router from other routers?"); +======= + ("trust.routerInfo", value()->default_value(""), "Path to routerInfo of floodfill to use with floodfill friend mode") + ("trust.hidden", value()->default_value(true), "should we hide our router from other routers?"); + + options_description websocket("Websocket Options"); + websocket.add_options() + ("websockets.enabled", value()->default_value(false), "enable websocket server") + ("websockets.address", value()->default_value("127.0.0.1"), "address to bind websocket server on") + ("websockets.port", value()->default_value(7666), "port to bind websocket server on"); + +>>>>>>> bda4170... add web socket ui m_OptionsDesc .add(general) .add(limits) @@ -193,7 +205,8 @@ namespace config { .add(precomputation) .add(reseed) .add(addressbook) - .add(trust) + .add(trust) + .add(websocket) ; } diff --git a/Daemon.cpp b/Daemon.cpp index ce2f4173..d8b501a3 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -25,6 +25,9 @@ #include "UPnP.h" #include "util.h" +#include "Event.h" +#include "Websocket.h" + namespace i2p { namespace util @@ -38,6 +41,7 @@ namespace i2p std::unique_ptr httpServer; std::unique_ptr m_I2PControlService; std::unique_ptr UPnP; + std::unique_ptr m_WebsocketServer; }; Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {} @@ -291,11 +295,23 @@ namespace i2p d.m_I2PControlService->Start (); } + bool websocket; i2p::config::GetOption("websockets.enabled", websocket); + if(websocket) { + std::string websocketAddr; i2p::config::GetOption("websockets.address", websocketAddr); + uint16_t websocketPort; i2p::config::GetOption("websockets.port", websocketPort); + LogPrint(eLogInfo, "Daemon: starting Websocket server at ", websocketAddr, ":", websocketPort); + d.m_WebsocketServer = std::unique_ptr(new i2p::event::WebsocketServer (websocketAddr, websocketPort)); + d.m_WebsocketServer->Start(); + i2p::event::core.SetListener(d.m_WebsocketServer->ToListener()); + } + + return true; } bool Daemon_Singleton::stop() { + i2p::event::core.SetListener(nullptr); LogPrint(eLogInfo, "Daemon: shutting down"); LogPrint(eLogInfo, "Daemon: stopping Client"); i2p::client::context.Stop(); @@ -321,7 +337,14 @@ namespace i2p LogPrint(eLogInfo, "Daemon: stopping I2PControl"); d.m_I2PControlService->Stop (); d.m_I2PControlService = nullptr; - } + } + + if (d.m_WebsocketServer) { + LogPrint(eLogInfo, "Daemon: stopping Websocket server"); + d.m_WebsocketServer->Stop(); + d.m_WebsocketServer = nullptr; + } + i2p::crypto::TerminateCrypto (); return true; diff --git a/Event.cpp b/Event.cpp new file mode 100644 index 00000000..45e4e09c --- /dev/null +++ b/Event.cpp @@ -0,0 +1,28 @@ +#include "Event.h" +#include "Log.h" + +namespace i2p +{ + namespace event + { + EventCore core; + + void EventCore::SetListener(EventListener * l) + { + m_listener = l; + LogPrint(eLogInfo, "Event: listener set"); + } + + void EventCore::QueueEvent(const EventType & ev) + { + if(m_listener) + m_listener->HandleEvent(ev); + } + } +} + +void EmitEvent(const EventType & e) +{ + i2p::event::core.QueueEvent(e); +} + diff --git a/Event.h b/Event.h new file mode 100644 index 00000000..e9830289 --- /dev/null +++ b/Event.h @@ -0,0 +1,37 @@ +#ifndef EVENT_H__ +#define EVENT_H__ +#include +#include +#include + +#include + +typedef std::map EventType; + +namespace i2p +{ + namespace event + { + class EventListener { + public: + virtual ~EventListener() {}; + virtual void HandleEvent(const EventType & ev) = 0; + }; + + class EventCore + { + public: + void QueueEvent(const EventType & ev); + void SetListener(EventListener * l); + + private: + EventListener * m_listener = nullptr; + }; + + extern EventCore core; + } +} + +void EmitEvent(const EventType & ev); + +#endif diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index cdc4fb9b..96b30cb6 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -547,7 +547,6 @@ namespace i2p uint8_t typeID = msg[I2NP_HEADER_TYPEID_OFFSET]; uint32_t msgID = bufbe32toh (msg + I2NP_HEADER_MSGID_OFFSET); LogPrint (eLogDebug, "I2NP: msg received len=", len,", type=", (int)typeID, ", msgID=", (unsigned int)msgID); - uint8_t * buf = msg + I2NP_HEADER_SIZE; int size = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET); switch (typeID) diff --git a/Log.h b/Log.h index 79bbeb3f..b4eb70cd 100644 --- a/Log.h +++ b/Log.h @@ -152,13 +152,13 @@ namespace log { std::string text; /**< message text as single string */ LogLevel level; /**< message level */ std::thread::id tid; /**< id of thread that generated message */ - + LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {}; }; Log & Logger(); } // log -} // i2p +} /** internal usage only -- folding args array to single string */ template diff --git a/NTCPSession.cpp b/NTCPSession.cpp index 20dc9ab0..876b8a4c 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -11,6 +11,7 @@ #include "Transports.h" #include "NetDb.h" #include "NTCPSession.h" +#include "Event.h" using namespace i2p::crypto; @@ -604,7 +605,10 @@ namespace transport if (!memcmp (m_NextMessage->buf + m_NextMessageOffset - 4, checksum, 4)) { if (!m_NextMessage->IsExpired ()) + { + EmitEvent({{"type", "transport.recvmsg"} , {"ident", GetIdentHashBase64()}, {"number", "1"}}); m_Handler.PutNextMessage (m_NextMessage); + } else LogPrint (eLogInfo, "NTCP: message expired"); } diff --git a/SSUData.cpp b/SSUData.cpp index e5abfd54..a1e18c0b 100644 --- a/SSUData.cpp +++ b/SSUData.cpp @@ -5,6 +5,7 @@ #include "NetDb.h" #include "SSU.h" #include "SSUData.h" +#include "Event.h" namespace i2p { @@ -233,9 +234,16 @@ namespace transport if (!m_ReceivedMessages.count (msgID)) { m_ReceivedMessages.insert (msgID); +<<<<<<< HEAD m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch (); if (!msg->IsExpired ()) +======= + m_LastMessageReceivedTime = i2p::util::GetSinceEpoch
    \r\n\r\n

    \r\n"; + s << "

    \r\n\r\n

    \r\n"; for(auto& it: dest->GetLeaseSets ()) s << it.second->GetIdentHash ().ToBase32 () << "
    \r\n"; s << "

    \r\n
    \r\n"; - } + } else + s << "LeaseSets: 0
    \r\n"; auto pool = dest->GetTunnelPool (); if (pool) { @@ -401,7 +413,7 @@ namespace http { void ShowLeasesSets(std::stringstream& s) { - s << "
    LeaseSets (click on to show info):

    \r\n"; + s << "LeaseSets:
    \r\n
    \r\n"; int counter = 1; // for each lease set i2p::data::netdb.VisitLeaseSets( @@ -434,6 +446,7 @@ namespace http { void ShowTunnels (std::stringstream& s) { + s << "Tunnels:
    \r\n
    \r\n"; s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
    \r\n"; s << "Inbound tunnels:
    \r\n"; @@ -457,7 +470,7 @@ namespace http { static void ShowCommands (std::stringstream& s, uint32_t token) { /* commands */ - s << "Router Commands
    \r\n"; + s << "Router Commands
    \r\n
    \r\n"; s << " Run peer test
    \r\n"; //s << " Reload config
    \r\n"; if (i2p::context.AcceptsTunnels ()) @@ -529,13 +542,13 @@ namespace http { } if (!tmp_s.str ().empty ()) { - s << "NTCP ( " << cnt << " )
    \r\n"; - s << tmp_s.str () << "
    \r\n"; + s << "
    \r\n\r\n

    "; + s << tmp_s.str () << "

    \r\n
    \r\n"; } if (!tmp_s6.str ().empty ()) { - s << "NTCP6 ( " << cnt6 << " )
    \r\n"; - s << tmp_s6.str () << "
    \r\n"; + s << "
    \r\n\r\n

    "; + s << tmp_s6.str () << "

    \r\n
    \r\n"; } } } @@ -545,7 +558,7 @@ namespace http { auto sessions = ssuServer->GetSessions (); if (!sessions.empty ()) { - s << "SSU ( " << (int) sessions.size() << " )
    \r\n"; + s << "
    \r\n\r\n

    "; for (const auto& it: sessions) { auto endpoint = it.second->GetRemoteEndpoint (); @@ -557,12 +570,12 @@ namespace http { s << " [itag:" << it.second->GetRelayTag () << "]"; s << "
    \r\n" << std::endl; } - s << "
    \r\n"; + s << "

    \r\n
    \r\n"; } auto sessions6 = ssuServer->GetSessionsV6 (); if (!sessions6.empty ()) { - s << "SSU6 ( " << (int) sessions6.size() << " )
    \r\n"; + s << "
    \r\n\r\n

    "; for (const auto& it: sessions6) { auto endpoint = it.second->GetRemoteEndpoint (); @@ -574,7 +587,7 @@ namespace http { s << " [itag:" << it.second->GetRelayTag () << "]"; s << "
    \r\n" << std::endl; } - s << "
    \r\n"; + s << "

    \r\n
    \r\n"; } } } diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 1ce09aeb..4eb06424 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -71,12 +71,12 @@ namespace client } try { - m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy",httpProxyAddr, httpProxyPort, httpOutProxyURL, localDestination); - m_HttpProxy->Start(); + m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, httpOutProxyURL, localDestination); + m_HttpProxy->Start(); } catch (std::exception& e) { - LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); + LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); } } @@ -107,7 +107,7 @@ namespace client } try { - m_SocksProxy = new i2p::proxy::SOCKSProxy( "SOCKS", socksProxyAddr, socksProxyPort, + m_SocksProxy = new i2p::proxy::SOCKSProxy("SOCKS", socksProxyAddr, socksProxyPort, socksOutProxy, socksOutProxyAddr, socksOutProxyPort, localDestination); m_SocksProxy->Start(); } diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 4e7cacf1..4b5d0dce 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -65,11 +65,11 @@ namespace client std::shared_ptr GetSharedLocalDestination () const { return m_SharedLocalDestination; }; std::shared_ptr CreateNewLocalDestination (bool isPublic = false, // transient - i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1, + i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, - const std::map * params = nullptr); // used by SAM only + const std::map * params = nullptr); // used by SAM only std::shared_ptr CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, - const std::map * params = nullptr); + const std::map * params = nullptr); std::shared_ptr CreateNewMatchedTunnelDestination(const i2p::data::PrivateKeys &keys, const std::string & name, const std::map * params = nullptr); void DeleteLocalDestination (std::shared_ptr destination); std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; @@ -78,6 +78,7 @@ namespace client i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); AddressBook& GetAddressBook () { return m_AddressBook; }; + const BOBCommandChannel * GetBOBCommandChannel () const { return m_BOBCommandChannel; }; const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; const I2CPServer * GetI2CPServer () const { return m_I2CPServer; }; From a6fb3b602e5030e06a902f264c35f59f670e2006 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 15 Nov 2017 21:30:00 +0300 Subject: [PATCH 2685/6300] add loglevel none (closing #998) --- contrib/i2pd.conf | 3 ++- libi2pd/Log.cpp | 37 +++++++++++++++++++++---------------- libi2pd/Log.h | 5 +++-- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 4dd21897..d9fe951b 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -23,7 +23,8 @@ # log = file ## Path to logfile (default - autodetect) # logfile = /var/log/i2pd.log -## Log messages above this level (debug, *info, warn, error) +## Log messages above this level (debug, *info, warn, error, none) +## If you set it to none, logging will be disabled # loglevel = info ## Path to storage of i2pd data (RI, keys, peer profiles, ...) diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index 5df8a9fd..07ffd591 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -16,10 +16,11 @@ namespace log { */ static const char * g_LogLevelStr[eNumLogLevels] = { + "none", // eLogNone "error", // eLogError "warn", // eLogWarn "info", // eLogInfo - "debug" // eLogDebug + "debug" // eLogDebug }; /** @@ -46,6 +47,7 @@ namespace log { static inline int GetSyslogPrio (enum LogLevel l) { int priority = LOG_DEBUG; switch (l) { + case eLogNone : priority = LOG_CRIT; break; case eLogError : priority = LOG_ERR; break; case eLogWarning : priority = LOG_WARNING; break; case eLogInfo : priority = LOG_INFO; break; @@ -71,15 +73,15 @@ namespace log { void Log::Start () { if (!m_IsRunning) - { - m_IsRunning = true; + { + m_IsRunning = true; m_Thread = new std::thread (std::bind (&Log::Run, this)); } } - void Log::Stop () + void Log::Stop () { - switch (m_Destination) + switch (m_Destination) { #ifndef _WIN32 case eLogSyslog : @@ -97,15 +99,16 @@ namespace log { m_IsRunning = false; m_Queue.WakeUp (); if (m_Thread) - { - m_Thread->join (); + { + m_Thread->join (); delete m_Thread; m_Thread = nullptr; - } + } } void Log::SetLogLevel (const std::string& level) { - if (level == "error") { m_MinLevel = eLogError; } + if (level == "none") { m_MinLevel = eLogNone; } + else if (level == "error") { m_MinLevel = eLogError; } else if (level == "warn") { m_MinLevel = eLogWarning; } else if (level == "info") { m_MinLevel = eLogInfo; } else if (level == "debug") { m_MinLevel = eLogDebug; } @@ -115,7 +118,7 @@ namespace log { } LogPrint(eLogInfo, "Log: min messages level set to ", level); } - + const char * Log::TimeAsString(std::time_t t) { if (t != m_LastTimestamp) { strftime(m_LastDateTime, sizeof(m_LastDateTime), m_TimeFormat.c_str(), localtime(&t)); @@ -129,7 +132,7 @@ namespace log { * Unfortunately, with current startup process with late fork() this * will give us nothing but pain. Maybe later. See in NetDb as example. */ - void Log::Process(std::shared_ptr msg) + void Log::Process(std::shared_ptr msg) { if (!msg) return; std::hash hasher; @@ -171,19 +174,20 @@ namespace log { if (m_IsRunning) m_Queue.Wait (); } - } + } - void Log::Append(std::shared_ptr & msg) + void Log::Append(std::shared_ptr & msg) { m_Queue.Put(msg); } - void Log::SendTo (const std::string& path) + void Log::SendTo (const std::string& path) { - if (m_LogStream) m_LogStream = nullptr; // close previous + if (m_LogStream) m_LogStream = nullptr; // close previous + if (m_MinLevel == eLogNone) return; auto flags = std::ofstream::out | std::ofstream::app; auto os = std::make_shared (path, flags); - if (os->is_open ()) + if (os->is_open ()) { m_HasColors = false; m_Logfile = path; @@ -202,6 +206,7 @@ namespace log { #ifndef _WIN32 void Log::SendTo(const char *name, int facility) { + if (m_MinLevel == eLogNone) return; m_HasColors = false; m_Destination = eLogSyslog; m_LogStream = nullptr; diff --git a/libi2pd/Log.h b/libi2pd/Log.h index bec741a8..74b4dc01 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -25,10 +25,11 @@ enum LogLevel { - eLogError = 0, + eLogNone = 0, + eLogError, eLogWarning, eLogInfo, - eLogDebug, + eLogDebug, eNumLogLevels }; From 7477d2c2198a154bdfeebb92cdd3bfde24191b2d Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 15 Nov 2017 21:51:03 +0300 Subject: [PATCH 2686/6300] fix forgotten log colors --- libi2pd/Log.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index 07ffd591..b664a5d9 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -28,9 +28,10 @@ namespace log { * @note Using ISO 6429 (ANSI) color sequences */ #ifdef _WIN32 - static const char *LogMsgColors[] = { "", "", "", "", "" }; + static const char *LogMsgColors[] = { "", "", "", "", "", "" }; #else /* UNIX */ static const char *LogMsgColors[] = { + [eLogNone] = "\033[0m", /* reset */ [eLogError] = "\033[1;31m", /* red */ [eLogWarning] = "\033[1;33m", /* yellow */ [eLogInfo] = "\033[1;36m", /* cyan */ From 9c97ee64073e21de5df567a92aa4c2f20ab55c3e Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 16 Nov 2017 01:13:42 +0300 Subject: [PATCH 2687/6300] check for existing addressbook record --- libi2pd_client/HTTPProxy.cpp | 44 ++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index da8df579..ac5d907d 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -54,7 +54,7 @@ namespace proxy { void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); - bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64); + bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64, bool & confirm); void SanitizeHTTPRequest(i2p::http::HTTPReq & req); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); @@ -182,13 +182,15 @@ namespace proxy { std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64) + bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64, bool & confirm) { + confirm = false; const char *param = "i2paddresshelper="; std::size_t pos = url.query.find(param); std::size_t len = std::strlen(param); std::map params; + if (pos == std::string::npos) return false; /* not found */ if (!url.parse_query(params)) @@ -197,6 +199,8 @@ namespace proxy { std::string value = params["i2paddresshelper"]; len += value.length(); b64 = i2p::http::UrlDecode(value); + // if we need update exists, request formed with update param + if (params["update"] == "true") { len += std::strlen("&update=true"); confirm = true; } url.query.replace(pos, len, ""); return true; } @@ -242,24 +246,36 @@ namespace proxy { /* parsing success, now let's look inside request */ LogPrint(eLogDebug, "HTTPProxy: requested: ", m_ClientRequest.uri); m_RequestURL.parse(m_ClientRequest.uri); + bool m_Confirm; - if (ExtractAddressHelper(m_RequestURL, b64)) + if (ExtractAddressHelper(m_RequestURL, b64, m_Confirm)) { bool addresshelper; i2p::config::GetOption("httpproxy.addresshelper", addresshelper); if (!addresshelper) { - LogPrint(eLogWarning, "HTTPProxy: addresshelper disabled"); - GenericProxyError("Invalid request", "adddresshelper is not supported"); + LogPrint(eLogWarning, "HTTPProxy: addresshelper request rejected"); + GenericProxyError("Invalid request", "addresshelper is not supported"); return true; } - i2p::client::context.GetAddressBook ().InsertAddress (m_RequestURL.host, b64); - LogPrint (eLogInfo, "HTTPProxy: added b64 from addresshelper for ", m_RequestURL.host); - std::string full_url = m_RequestURL.to_string(); - std::stringstream ss; - ss << "Host " << m_RequestURL.host << " added to router's addressbook from helper. " - << "Click here to proceed."; - GenericProxyInfo("Addresshelper found", ss.str().c_str()); - return true; /* request processed */ + if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm) + { + i2p::client::context.GetAddressBook ().InsertAddress (m_RequestURL.host, b64); + LogPrint (eLogInfo, "HTTPProxy: added b64 from addresshelper for ", m_RequestURL.host); + std::string full_url = m_RequestURL.to_string(); + std::stringstream ss; + ss << "Host " << m_RequestURL.host << " added to router's addressbook from helper. " + << "Click here to proceed."; + GenericProxyInfo("Addresshelper found", ss.str().c_str()); + return true; /* request processed */ + } + else + { + std::stringstream ss; + ss << "Host " << m_RequestURL.host << " already in router's addressbook. " + << "Click here to update record."; + GenericProxyInfo("Addresshelper found", ss.str().c_str()); + return true; /* request processed */ + } } std::string dest_host; uint16_t dest_port; @@ -570,7 +586,7 @@ namespace proxy { { if (!stream) { LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); - GenericProxyError("Host is down", "Can't create connection to requested host, it may be down"); + GenericProxyError("Host is down", "Can't create connection to requested host, it may be down. Please try again later."); return; } if (Kill()) From 1ba1fa37f908a53dca0946be763842d679a2c6fc Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 17 Nov 2017 20:43:00 +0300 Subject: [PATCH 2688/6300] update travis-ci stuff (#1006) * enable osx in travis config * fix brew commands, change comiler * disable clang build with make on linux * update README * tabulation fix in Crypto.cpp --- .travis.yml | 69 +++++--- Makefile.homebrew | 25 +-- Makefile.osx | 24 +-- README.md | 6 +- libi2pd/Crypto.cpp | 389 ++++++++++++++++++++++----------------------- 5 files changed, 267 insertions(+), 246 deletions(-) diff --git a/.travis.yml b/.travis.yml index 819c75f4..7dd14872 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,34 +2,53 @@ language: cpp cache: apt: true os: - - linux -sudo: required +- linux +- osx dist: trusty +sudo: required +compiler: +- g++ +- clang++ +env: + global: + - MAKEFLAGS="-j 2" + matrix: + - BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes + - BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no + - BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes + - BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no +matrix: + exclude: + - os: osx + env: BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes + - os: osx + env: BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no + - os: linux + compiler: clang++ + env: BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes + - os: linux + compiler: clang++ + env: BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no addons: apt: packages: - - build-essential - - cmake - - g++ - - clang - - libboost-chrono-dev - - libboost-date-time-dev - - libboost-filesystem-dev - - libboost-program-options-dev - - libboost-system-dev - - libboost-thread-dev - - libminiupnpc-dev - - libssl-dev -compiler: - - gcc - - clang + - build-essential + - cmake + - g++ + - clang + - libboost-chrono-dev + - libboost-date-time-dev + - libboost-filesystem-dev + - libboost-program-options-dev + - libboost-system-dev + - libboost-thread-dev + - libminiupnpc-dev + - libssl-dev before_install: - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install openssl miniupnpc ; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew unlink boost openssl && brew link boost openssl -f ; fi -env: - matrix: - - BUILD_TYPE=Release UPNP=ON - - BUILD_TYPE=Release UPNP=OFF +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install libressl miniupnpc ; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated boost || brew upgrade boost ; fi script: - - cd build && cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DWITH_UPNP=${UPNP} && make +- if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "cmake" ]]; then cd build && cmake -DCMAKE_BUILD_TYPE=Release -DWITH_UPNP=${UPNP} && make ; fi +- if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "make" ]]; then make USE_UPNP=${MAKE_UPNP} ; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then make HOMEBREW=1 USE_UPNP=${MAKE_UPNP} ; fi diff --git a/Makefile.homebrew b/Makefile.homebrew index 4f69ae36..287dea05 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -3,27 +3,30 @@ BREWROOT = /usr/local BOOSTROOT = ${BREWROOT}/opt/boost SSLROOT = ${BREWROOT}/opt/libressl UPNPROOT = ${BREWROOT}/opt/miniupnpc -CXX = clang++ CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX -Wno-overloaded-virtual INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include +ifndef TRAVIS + CXX = clang++ +endif + ifeq ($(USE_STATIC),yes) -LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_date_time.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a -lpthread + LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_date_time.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a -lpthread else -LDFLAGS = -L${SSLROOT}/lib -L${BOOSTROOT}/lib -LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread + LDFLAGS = -L${SSLROOT}/lib -L${BOOSTROOT}/lib + LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread endif ifeq ($(USE_UPNP),yes) LDFLAGS += -ldl CXXFLAGS += -DUSE_UPNP - INCFLAGS += -I${UPNPROOT}/include - ifeq ($(USE_STATIC),yes) - LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a - else - LDFLAGS += -L${UPNPROOT}/lib - LDLIBS += -lminiupnpc - endif + INCFLAGS += -I${UPNPROOT}/include + ifeq ($(USE_STATIC),yes) + LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a + else + LDFLAGS += -L${UPNPROOT}/lib + LDLIBS += -lminiupnpc + endif endif # OSX Notes diff --git a/Makefile.osx b/Makefile.osx index 8d6a1c65..f6a0847d 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -5,29 +5,29 @@ INCFLAGS = -I/usr/local/include LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib ifeq ($(USE_STATIC),yes) -LDLIBS = -lz /usr/local/lib/libcrypto.a /usr/local/lib/libssl.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_date_time.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread + LDLIBS = -lz /usr/local/lib/libcrypto.a /usr/local/lib/libssl.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_date_time.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread else -LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread + LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread endif ifeq ($(USE_UPNP),yes) - LDFLAGS += -ldl - CXXFLAGS += -DUSE_UPNP -ifeq ($(USE_STATIC),yes) - LDLIBS += /usr/local/lib/libminiupnpc.a -else - LDLIBS += -lminiupnpc -endif + LDFLAGS += -ldl + CXXFLAGS += -DUSE_UPNP + ifeq ($(USE_STATIC),yes) + LDLIBS += /usr/local/lib/libminiupnpc.a + else + LDLIBS += -lminiupnpc + endif endif ifeq ($(USE_AESNI),1) - CXXFLAGS += -maes -DAESNI + CXXFLAGS += -maes -DAESNI else - CXXFLAGS += -msse + CXXFLAGS += -msse endif ifeq ($(USE_AVX),1) - CXXFLAGS += -mavx + CXXFLAGS += -mavx endif # Disabled, since it will be the default make rule. I think its better diff --git a/README.md b/README.md index c941eb3b..d5d7ceb9 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,9 @@ Build instructions: **Supported systems:** -* GNU/Linux x86/x64 - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) -* Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) -* Mac OS X +* GNU/Linux x86/x64 - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) +* Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) +* Mac OS X - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) * FreeBSD * Android * iOS diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index a9f63dc7..fd0ef4bd 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -14,20 +14,20 @@ namespace i2p { namespace crypto -{ +{ const uint8_t elgp_[256]= { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, - 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, + 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, - 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, + 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, - 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, + 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, @@ -36,7 +36,7 @@ namespace crypto }; const int elgg_ = 2; - + const uint8_t dsap_[128]= { 0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c, @@ -45,7 +45,7 @@ namespace crypto 0x76, 0x55, 0xc4, 0x96, 0x4a, 0xfa, 0xa2, 0xb3, 0x37, 0xe9, 0x6a, 0xd3, 0x16, 0xb9, 0xfb, 0x1c, 0xc5, 0x64, 0xb5, 0xae, 0xc5, 0xb6, 0x9a, 0x9f, 0xf6, 0xc3, 0xe4, 0x54, 0x87, 0x07, 0xfe, 0xf8, 0x50, 0x3d, 0x91, 0xdd, 0x86, 0x02, 0xe8, 0x67, 0xe6, 0xd3, 0x5d, 0x22, 0x35, 0xc1, 0x86, 0x9c, - 0xe2, 0x47, 0x9c, 0x3b, 0x9d, 0x54, 0x01, 0xde, 0x04, 0xe0, 0x72, 0x7f, 0xb3, 0x3d, 0x65, 0x11, + 0xe2, 0x47, 0x9c, 0x3b, 0x9d, 0x54, 0x01, 0xde, 0x04, 0xe0, 0x72, 0x7f, 0xb3, 0x3d, 0x65, 0x11, 0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93 }; @@ -63,27 +63,27 @@ namespace crypto 0x3c, 0x43, 0x1f, 0x46, 0x98, 0x59, 0x9d, 0xda, 0x02, 0x45, 0x18, 0x24, 0xff, 0x36, 0x97, 0x52, 0x59, 0x36, 0x47, 0xcc, 0x3d, 0xdc, 0x19, 0x7d, 0xe9, 0x85, 0xe4, 0x3d, 0x13, 0x6c, 0xdc, 0xfc, 0x6b, 0xd5, 0x40, 0x9c, 0xd2, 0xf4, 0x50, 0x82, 0x11, 0x42, 0xa5, 0xe6, 0xf8, 0xeb, 0x1c, 0x3a, - 0xb5, 0xd0, 0x48, 0x4b, 0x81, 0x29, 0xfc, 0xf1, 0x7b, 0xce, 0x4f, 0x7f, 0x33, 0x32, 0x1c, 0x3c, - 0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82 + 0xb5, 0xd0, 0x48, 0x4b, 0x81, 0x29, 0xfc, 0xf1, 0x7b, 0xce, 0x4f, 0x7f, 0x33, 0x32, 0x1c, 0x3c, + 0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82 }; - const int rsae_ = 65537; - + const int rsae_ = 65537; + struct CryptoConstants { // DH/ElGamal BIGNUM * elgp; - BIGNUM * elgg; + BIGNUM * elgg; // DSA - BIGNUM * dsap; + BIGNUM * dsap; BIGNUM * dsaq; BIGNUM * dsag; // RSA BIGNUM * rsae; - - CryptoConstants (const uint8_t * elgp_, int elgg_, const uint8_t * dsap_, + + CryptoConstants (const uint8_t * elgp_, int elgg_, const uint8_t * dsap_, const uint8_t * dsaq_, const uint8_t * dsag_, int rsae_) { elgp = BN_new (); @@ -99,18 +99,18 @@ namespace crypto rsae = BN_new (); BN_set_word (rsae, rsae_); } - + ~CryptoConstants () { BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae); - } - }; + } + }; static const CryptoConstants& GetCryptoConstants () { - static CryptoConstants cryptoConstants (elgp_, elgg_, dsap_, dsaq_, dsag_, rsae_); + static CryptoConstants cryptoConstants (elgp_, elgg_, dsap_, dsaq_, dsag_, rsae_); return cryptoConstants; - } + } bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len) { @@ -119,34 +119,34 @@ namespace crypto BN_bn2bin (bn, buf + offset); memset (buf, 0, offset); return true; - } + } // RSA - #define rsae GetCryptoConstants ().rsae + #define rsae GetCryptoConstants ().rsae const BIGNUM * GetRSAE () { return rsae; - } + } // DSA - #define dsap GetCryptoConstants ().dsap + #define dsap GetCryptoConstants ().dsap #define dsaq GetCryptoConstants ().dsaq - #define dsag GetCryptoConstants ().dsag + #define dsag GetCryptoConstants ().dsag DSA * CreateDSA () { DSA * dsa = DSA_new (); - DSA_set0_pqg (dsa, BN_dup (dsap), BN_dup (dsaq), BN_dup (dsag)); + DSA_set0_pqg (dsa, BN_dup (dsap), BN_dup (dsaq), BN_dup (dsag)); DSA_set0_key (dsa, NULL, NULL); return dsa; } -// DH/ElGamal +// DH/ElGamal const int ELGAMAL_SHORT_EXPONENT_NUM_BITS = 226; const int ELGAMAL_SHORT_EXPONENT_NUM_BYTES = ELGAMAL_SHORT_EXPONENT_NUM_BITS/8+1; const int ELGAMAL_FULL_EXPONENT_NUM_BITS = 2048; const int ELGAMAL_FULL_EXPONENT_NUM_BYTES = ELGAMAL_FULL_EXPONENT_NUM_BITS/8; - + #define elgp GetCryptoConstants ().elgp #define elgg GetCryptoConstants ().elgg @@ -156,14 +156,14 @@ namespace crypto if (len <= 0) return; BN_CTX * ctx = BN_CTX_new (); g_MontCtx = BN_MONT_CTX_new (); - BN_MONT_CTX_set (g_MontCtx, elgp, ctx); + BN_MONT_CTX_set (g_MontCtx, elgp, ctx); auto montCtx = BN_MONT_CTX_new (); BN_MONT_CTX_copy (montCtx, g_MontCtx); for (int i = 0; i < len; i++) { table[i][0] = BN_new (); - if (!i) - BN_to_montgomery (table[0][0], elgg, montCtx, ctx); + if (!i) + BN_to_montgomery (table[0][0], elgg, montCtx, ctx); else BN_mod_mul_montgomery (table[i][0], table[i-1][254], table[i-1][0], montCtx, ctx); for (int j = 1; j < 255; j++) @@ -174,7 +174,7 @@ namespace crypto } BN_MONT_CTX_free (montCtx); BN_CTX_free (ctx); - } + } static void DestroyElggTable (BIGNUM * table[][255], int len) { @@ -186,9 +186,9 @@ namespace crypto } BN_MONT_CTX_free (g_MontCtx); } - + static BIGNUM * ElggPow (const uint8_t * exp, int len, BIGNUM * table[][255], BN_CTX * ctx) - // exp is in Big Endian + // exp is in Big Endian { if (len <= 0) return nullptr; auto montCtx = BN_MONT_CTX_new (); @@ -200,15 +200,15 @@ namespace crypto { if (exp[i]) BN_mod_mul_montgomery (res, res, table[len-1-i][exp[i]-1], montCtx, ctx); - } - else if (exp[i]) + } + else if (exp[i]) res = BN_dup (table[len-i-1][exp[i]-1]); - } + } if (res) BN_from_montgomery (res, res, montCtx, ctx); BN_MONT_CTX_free (montCtx); return res; - } + } static BIGNUM * ElggPow (const BIGNUM * exp, BIGNUM * table[][255], BN_CTX * ctx) { @@ -218,64 +218,64 @@ namespace crypto auto ret = ElggPow (buf, len, table, ctx); delete[] buf; return ret; - } + } + + static BIGNUM * (* g_ElggTable)[255] = nullptr; - static BIGNUM * (* g_ElggTable)[255] = nullptr; - // DH - + DHKeys::DHKeys () { m_DH = DH_new (); DH_set0_pqg (m_DH, BN_dup (elgp), NULL, BN_dup (elgg)); DH_set0_key (m_DH, NULL, NULL); } - + DHKeys::~DHKeys () { DH_free (m_DH); - } + } void DHKeys::GenerateKeys () { - BIGNUM * priv_key = NULL, * pub_key = NULL; -#if !defined(__x86_64__) // use short exponent for non x64 + BIGNUM * priv_key = NULL, * pub_key = NULL; +#if !defined(__x86_64__) // use short exponent for non x64 priv_key = BN_new (); BN_rand (priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1); -#endif +#endif if (g_ElggTable) - { + { #if defined(__x86_64__) priv_key = BN_new (); BN_rand (priv_key, ELGAMAL_FULL_EXPONENT_NUM_BITS, 0, 1); -#endif +#endif auto ctx = BN_CTX_new (); pub_key = ElggPow (priv_key, g_ElggTable, ctx); DH_set0_key (m_DH, pub_key, priv_key); BN_CTX_free (ctx); - } + } else { DH_set0_key (m_DH, NULL, priv_key); DH_generate_key (m_DH); DH_get0_key (m_DH, (const BIGNUM **)&pub_key, (const BIGNUM **)&priv_key); - } + } bn2buf (pub_key, m_PublicKey, 256); - } - + } + void DHKeys::Agree (const uint8_t * pub, uint8_t * shared) { BIGNUM * pk = BN_bin2bn (pub, 256, NULL); DH_compute_key (shared, pk, m_DH); BN_free (pk); - } - + } + // ElGamal void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) { BN_CTX_start (ctx); - // everything, but a, because a might come from table + // everything, but a, because a might come from table BIGNUM * k = BN_CTX_get (ctx); BIGNUM * y = BN_CTX_get (ctx); BIGNUM * b1 = BN_CTX_get (ctx); @@ -285,13 +285,13 @@ namespace crypto BN_rand (k, ELGAMAL_FULL_EXPONENT_NUM_BITS, -1, 1); // full exponent for x64 #else BN_rand (k, ELGAMAL_SHORT_EXPONENT_NUM_BITS, -1, 1); // short exponent of 226 bits -#endif +#endif // calculate a - BIGNUM * a; + BIGNUM * a; if (g_ElggTable) a = ElggPow (k, g_ElggTable, ctx); else - { + { a = BN_new (); BN_mod_exp (a, elgg, k, elgp, ctx); } @@ -315,17 +315,17 @@ namespace crypto bn2buf (a, encrypted + 1, 256); encrypted[257] = 0; bn2buf (b, encrypted + 258, 256); - } + } else { bn2buf (a, encrypted, 256); bn2buf (b, encrypted + 256, 256); - } + } BN_free (a); BN_CTX_end (ctx); } - bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) { BN_CTX_start (ctx); @@ -334,11 +334,11 @@ namespace crypto BN_sub (x, elgp, x); BN_sub_word (x, 1); // x = elgp - x- 1 BN_bin2bn (zeroPadding ? encrypted + 1 : encrypted, 256, a); BN_bin2bn (zeroPadding ? encrypted + 258 : encrypted + 256, 256, b); - // m = b*(a^x mod p) mod p + // m = b*(a^x mod p) mod p BN_mod_exp (x, a, x, elgp, ctx); - BN_mod_mul (b, b, x, elgp, ctx); + BN_mod_mul (b, b, x, elgp, ctx); uint8_t m[255]; - bn2buf (b, m, 255); + bn2buf (b, m, 255); BN_CTX_end (ctx); uint8_t hash[32]; SHA256 (m + 33, 222, hash); @@ -346,14 +346,14 @@ namespace crypto { LogPrint (eLogError, "ElGamal decrypt hash doesn't match"); return false; - } + } memcpy (data, m + 33, 222); return true; - } + } void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub) { -#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER) +#if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER) RAND_bytes (priv, 256); #else // lower 226 bits (28 bytes and 2 bits) only. short exponent @@ -364,10 +364,10 @@ namespace crypto priv[numZeroBytes] &= 0x03; #endif BN_CTX * ctx = BN_CTX_new (); - BIGNUM * p = BN_new (); + BIGNUM * p = BN_new (); BN_bin2bn (priv, 256, p); BN_mod_exp (p, elgg, p, elgp, ctx); - bn2buf (p, pub, 256); + bn2buf (p, pub, 256); BN_free (p); BN_CTX_free (ctx); } @@ -385,15 +385,15 @@ namespace crypto auto p = EC_POINT_new (curve); EC_POINT_mul (curve, p, k, nullptr, nullptr, ctx); BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx); - EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); + EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); encrypted[0] = 0; bn2buf (x, encrypted + 1, len); bn2buf (y, encrypted + 1 + len, len); RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len); // ecryption key and iv - EC_POINT_mul (curve, p, nullptr, key, k, ctx); + EC_POINT_mul (curve, p, nullptr, key, k, ctx); EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); - uint8_t keyBuf[64], iv[64], shared[32]; + uint8_t keyBuf[64], iv[64], shared[32]; bn2buf (x, keyBuf, len); bn2buf (y, iv, len); SHA256 (keyBuf, len, shared); @@ -421,16 +421,16 @@ namespace crypto int len = BN_num_bytes (q); // point for shared secret BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx); - BN_bin2bn (encrypted + 1, len, x); - BN_bin2bn (encrypted + 1 + len, len, y); + BN_bin2bn (encrypted + 1, len, x); + BN_bin2bn (encrypted + 1 + len, len, y); auto p = EC_POINT_new (curve); if (EC_POINT_set_affine_coordinates_GFp (curve, p, x, y, nullptr)) { auto s = EC_POINT_new (curve); - EC_POINT_mul (curve, s, nullptr, p, key, ctx); + EC_POINT_mul (curve, s, nullptr, p, key, ctx); EC_POINT_get_affine_coordinates_GFp (curve, s, x, y, nullptr); EC_POINT_free (s); - uint8_t keyBuf[64], iv[64], shared[32]; + uint8_t keyBuf[64], iv[64], shared[32]; bn2buf (x, keyBuf, len); bn2buf (y, iv, len); SHA256 (keyBuf, len, shared); @@ -442,21 +442,21 @@ namespace crypto decryption.Decrypt (encrypted + 258, 256, m); // verify and copy uint8_t hash[32]; - SHA256 (m + 33, 222, hash); + SHA256 (m + 33, 222, hash); if (!memcmp (m + 1, hash, 32)) - memcpy (data, m + 33, 222); + memcpy (data, m + 33, 222); else { LogPrint (eLogError, "ECIES decrypt hash doesn't match"); ret = false; - } + } } else { LogPrint (eLogError, "ECIES decrypt point is invalid"); ret = false; } - + EC_POINT_free (p); BN_CTX_end (ctx); return ret; @@ -468,7 +468,7 @@ namespace crypto BIGNUM * q = BN_new (); EC_GROUP_get_order(curve, q, ctx); priv = BN_new (); - BN_rand_range (priv, q); + BN_rand_range (priv, q); pub = EC_POINT_new (curve); EC_POINT_mul (curve, pub, priv, nullptr, nullptr, ctx); BN_free (q); @@ -477,17 +477,17 @@ namespace crypto // HMAC const uint64_t IPAD = 0x3636363636363636; - const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C; + const uint64_t OPAD = 0x5C5C5C5C5C5C5C5C; -#if defined(__AVX__) +#if defined(__AVX__) static const uint64_t ipads[] = { IPAD, IPAD, IPAD, IPAD }; static const uint64_t opads[] = { OPAD, OPAD, OPAD, OPAD }; #endif - + void HMACMD5Digest (uint8_t * msg, size_t len, const MACKey& key, uint8_t * digest) // key is 32 bytes // digest is 16 bytes - // block size is 64 bytes + // block size is 64 bytes { uint64_t buf[256]; uint64_t hash[12]; // 96 bytes @@ -498,53 +498,53 @@ namespace crypto "vmovups %[ipad], %%ymm1 \n" "vmovups %%ymm1, 32(%[buf]) \n" "vxorps %%ymm0, %%ymm1, %%ymm1 \n" - "vmovups %%ymm1, (%[buf]) \n" + "vmovups %%ymm1, (%[buf]) \n" "vmovups %[opad], %%ymm1 \n" - "vmovups %%ymm1, 32(%[hash]) \n" + "vmovups %%ymm1, 32(%[hash]) \n" "vxorps %%ymm0, %%ymm1, %%ymm1 \n" "vmovups %%ymm1, (%[hash]) \n" "vzeroall \n" // end of AVX - "movups %%xmm0, 80(%[hash]) \n" // zero last 16 bytes - : + "movups %%xmm0, 80(%[hash]) \n" // zero last 16 bytes + : : [key]"m"(*(const uint8_t *)key), [ipad]"m"(*ipads), [opad]"m"(*opads), - [buf]"r"(buf), [hash]"r"(hash) + [buf]"r"(buf), [hash]"r"(hash) : "memory", "%xmm0" // TODO: change to %ymm0 later ); #else // ikeypad - buf[0] = key.GetLL ()[0] ^ IPAD; - buf[1] = key.GetLL ()[1] ^ IPAD; - buf[2] = key.GetLL ()[2] ^ IPAD; - buf[3] = key.GetLL ()[3] ^ IPAD; + buf[0] = key.GetLL ()[0] ^ IPAD; + buf[1] = key.GetLL ()[1] ^ IPAD; + buf[2] = key.GetLL ()[2] ^ IPAD; + buf[3] = key.GetLL ()[3] ^ IPAD; buf[4] = IPAD; buf[5] = IPAD; buf[6] = IPAD; buf[7] = IPAD; - // okeypad - hash[0] = key.GetLL ()[0] ^ OPAD; - hash[1] = key.GetLL ()[1] ^ OPAD; - hash[2] = key.GetLL ()[2] ^ OPAD; - hash[3] = key.GetLL ()[3] ^ OPAD; + // okeypad + hash[0] = key.GetLL ()[0] ^ OPAD; + hash[1] = key.GetLL ()[1] ^ OPAD; + hash[2] = key.GetLL ()[2] ^ OPAD; + hash[3] = key.GetLL ()[3] ^ OPAD; hash[4] = OPAD; hash[5] = OPAD; hash[6] = OPAD; hash[7] = OPAD; // fill last 16 bytes with zeros (first hash size assumed 32 bytes in I2P) - memset (hash + 10, 0, 16); + memset (hash + 10, 0, 16); #endif // concatenate with msg memcpy (buf + 8, msg, len); // calculate first hash - MD5((uint8_t *)buf, len + 64, (uint8_t *)(hash + 8)); // 16 bytes - + MD5((uint8_t *)buf, len + 64, (uint8_t *)(hash + 8)); // 16 bytes + // calculate digest MD5((uint8_t *)hash, 96, digest); } // AES #ifdef AESNI - + #define KeyExpansion256(round0,round1) \ "pshufd $0xff, %%xmm2, %%xmm2 \n" \ "movaps %%xmm1, %%xmm4 \n" \ @@ -566,7 +566,7 @@ namespace crypto "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm3 \n" \ "pxor %%xmm2, %%xmm3 \n" \ - "movaps %%xmm3, "#round1"(%[sched]) \n" + "movaps %%xmm3, "#round1"(%[sched]) \n" void ECBCryptoAESNI::ExpandKey (const AESKey& key) { @@ -591,7 +591,7 @@ namespace crypto "aeskeygenassist $64, %%xmm3, %%xmm2 \n" // key expansion final "pshufd $0xff, %%xmm2, %%xmm2 \n" - "movaps %%xmm1, %%xmm4 \n" + "movaps %%xmm1, %%xmm4 \n" "pslldq $4, %%xmm4 \n" "pxor %%xmm4, %%xmm1 \n" "pslldq $4, %%xmm4 \n" @@ -622,17 +622,17 @@ namespace crypto "aesenc 192(%["#sched"]), %%xmm0 \n" \ "aesenc 208(%["#sched"]), %%xmm0 \n" \ "aesenclast 224(%["#sched"]), %%xmm0 \n" - + void ECBEncryptionAESNI::Encrypt (const ChipherBlock * in, ChipherBlock * out) { __asm__ ( "movups (%[in]), %%xmm0 \n" EncryptAES256(sched) - "movups %%xmm0, (%[out]) \n" + "movups %%xmm0, (%[out]) \n" : : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory" ); - } + } #define DecryptAES256(sched) \ "pxor 224(%["#sched"]), %%xmm0 \n" \ @@ -650,22 +650,22 @@ namespace crypto "aesdec 32(%["#sched"]), %%xmm0 \n" \ "aesdec 16(%["#sched"]), %%xmm0 \n" \ "aesdeclast (%["#sched"]), %%xmm0 \n" - + void ECBDecryptionAESNI::Decrypt (const ChipherBlock * in, ChipherBlock * out) { __asm__ ( "movups (%[in]), %%xmm0 \n" DecryptAES256(sched) - "movups %%xmm0, (%[out]) \n" + "movups %%xmm0, (%[out]) \n" : : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory" - ); + ); } #define CallAESIMC(offset) \ "movaps "#offset"(%[shed]), %%xmm0 \n" \ "aesimc %%xmm0, %%xmm0 \n" \ - "movaps %%xmm0, "#offset"(%[shed]) \n" + "movaps %%xmm0, "#offset"(%[shed]) \n" void ECBDecryptionAESNI::SetKey (const AESKey& key) { @@ -690,7 +690,7 @@ namespace crypto ); } -#endif +#endif void CBCEncryption::Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out) @@ -698,31 +698,31 @@ namespace crypto #ifdef AESNI __asm__ ( - "movups (%[iv]), %%xmm1 \n" - "1: \n" - "movups (%[in]), %%xmm0 \n" - "pxor %%xmm1, %%xmm0 \n" - EncryptAES256(sched) - "movaps %%xmm0, %%xmm1 \n" - "movups %%xmm0, (%[out]) \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "dec %[num] \n" - "jnz 1b \n" - "movups %%xmm1, (%[iv]) \n" - : - : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), + "movups (%[iv]), %%xmm1 \n" + "1: \n" + "movups (%[in]), %%xmm0 \n" + "pxor %%xmm1, %%xmm0 \n" + EncryptAES256(sched) + "movaps %%xmm0, %%xmm1 \n" + "movups %%xmm0, (%[out]) \n" + "add $16, %[in] \n" + "add $16, %[out] \n" + "dec %[num] \n" + "jnz 1b \n" + "movups %%xmm1, (%[iv]) \n" + : + : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), [in]"r"(in), [out]"r"(out), [num]"r"(numBlocks) : "%xmm0", "%xmm1", "cc", "memory" - ); -#else + ); +#else for (int i = 0; i < numBlocks; i++) { *m_LastBlock.GetChipherBlock () ^= in[i]; m_ECBEncryption.Encrypt (m_LastBlock.GetChipherBlock (), m_LastBlock.GetChipherBlock ()); out[i] = *m_LastBlock.GetChipherBlock (); } -#endif +#endif } void CBCEncryption::Encrypt (const uint8_t * in, std::size_t len, uint8_t * out) @@ -730,7 +730,7 @@ namespace crypto // len/16 int numBlocks = len >> 4; if (numBlocks > 0) - Encrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); + Encrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); } void CBCEncryption::Encrypt (const uint8_t * in, uint8_t * out) @@ -740,17 +740,17 @@ namespace crypto ( "movups (%[iv]), %%xmm1 \n" "movups (%[in]), %%xmm0 \n" - "pxor %%xmm1, %%xmm0 \n" - EncryptAES256(sched) + "pxor %%xmm1, %%xmm0 \n" + EncryptAES256(sched) "movups %%xmm0, (%[out]) \n" "movups %%xmm0, (%[iv]) \n" - : - : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), + : + : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "%xmm1", "memory" - ); + ); #else - Encrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out); + Encrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out); #endif } @@ -760,23 +760,23 @@ namespace crypto __asm__ ( "movups (%[iv]), %%xmm1 \n" - "1: \n" - "movups (%[in]), %%xmm0 \n" + "1: \n" + "movups (%[in]), %%xmm0 \n" "movaps %%xmm0, %%xmm2 \n" - DecryptAES256(sched) + DecryptAES256(sched) "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" + "movups %%xmm0, (%[out]) \n" "movaps %%xmm2, %%xmm1 \n" - "add $16, %[in] \n" - "add $16, %[out] \n" - "dec %[num] \n" - "jnz 1b \n" - "movups %%xmm1, (%[iv]) \n" - : - : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), + "add $16, %[in] \n" + "add $16, %[out] \n" + "dec %[num] \n" + "jnz 1b \n" + "movups %%xmm1, (%[iv]) \n" + : + : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), [in]"r"(in), [out]"r"(out), [num]"r"(numBlocks) : "%xmm0", "%xmm1", "%xmm2", "cc", "memory" - ); + ); #else for (int i = 0; i < numBlocks; i++) { @@ -792,7 +792,7 @@ namespace crypto { int numBlocks = len >> 4; if (numBlocks > 0) - Decrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); + Decrypt (numBlocks, (const ChipherBlock *)in, (ChipherBlock *)out); } void CBCDecryption::Decrypt (const uint8_t * in, uint8_t * out) @@ -801,18 +801,18 @@ namespace crypto __asm__ ( "movups (%[iv]), %%xmm1 \n" - "movups (%[in]), %%xmm0 \n" + "movups (%[in]), %%xmm0 \n" "movups %%xmm0, (%[iv]) \n" - DecryptAES256(sched) + DecryptAES256(sched) "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" - : - : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), + "movups %%xmm0, (%[out]) \n" + : + : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "%xmm1", "memory" ); #else - Decrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out); + Decrypt (1, (const ChipherBlock *)in, (ChipherBlock *)out); #endif } @@ -821,7 +821,7 @@ namespace crypto #ifdef AESNI __asm__ ( - // encrypt IV + // encrypt IV "movups (%[in]), %%xmm0 \n" EncryptAES256(sched_iv) "movaps %%xmm0, %%xmm1 \n" @@ -831,16 +831,16 @@ namespace crypto // encrypt data, IV is xmm1 "1: \n" "add $16, %[in] \n" - "add $16, %[out] \n" - "movups (%[in]), %%xmm0 \n" - "pxor %%xmm1, %%xmm0 \n" - EncryptAES256(sched_l) - "movaps %%xmm0, %%xmm1 \n" - "movups %%xmm0, (%[out]) \n" - "dec %[num] \n" - "jnz 1b \n" - : - : [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.GetKeySchedule ()), + "add $16, %[out] \n" + "movups (%[in]), %%xmm0 \n" + "pxor %%xmm1, %%xmm0 \n" + EncryptAES256(sched_l) + "movaps %%xmm0, %%xmm1 \n" + "movups %%xmm0, (%[out]) \n" + "dec %[num] \n" + "jnz 1b \n" + : + : [sched_iv]"r"(m_IVEncryption.GetKeySchedule ()), [sched_l]"r"(m_LayerEncryption.GetKeySchedule ()), [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes : "%xmm0", "%xmm1", "cc", "memory" ); @@ -857,7 +857,7 @@ namespace crypto #ifdef AESNI __asm__ ( - // decrypt IV + // decrypt IV "movups (%[in]), %%xmm0 \n" DecryptAES256(sched_iv) "movaps %%xmm0, %%xmm1 \n" @@ -867,27 +867,27 @@ namespace crypto // decrypt data, IV is xmm1 "1: \n" "add $16, %[in] \n" - "add $16, %[out] \n" + "add $16, %[out] \n" "movups (%[in]), %%xmm0 \n" "movaps %%xmm0, %%xmm2 \n" - DecryptAES256(sched_l) + DecryptAES256(sched_l) "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" + "movups %%xmm0, (%[out]) \n" "movaps %%xmm2, %%xmm1 \n" - "dec %[num] \n" - "jnz 1b \n" - : - : [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.GetKeySchedule ()), - [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes + "dec %[num] \n" + "jnz 1b \n" + : + : [sched_iv]"r"(m_IVDecryption.GetKeySchedule ()), [sched_l]"r"(m_LayerDecryption.GetKeySchedule ()), + [in]"r"(in), [out]"r"(out), [num]"r"(63) // 63 blocks = 1008 bytes : "%xmm0", "%xmm1", "%xmm2", "cc", "memory" ); #else m_IVDecryption.Decrypt ((const ChipherBlock *)in, (ChipherBlock *)out); // iv - m_LayerDecryption.SetIV (out); + m_LayerDecryption.SetIV (out); m_LayerDecryption.Decrypt (in + 16, i2p::tunnel::TUNNEL_DATA_ENCRYPTED_SIZE, out + 16); // data m_IVDecryption.Decrypt ((ChipherBlock *)out, (ChipherBlock *)out); // double iv #endif - } + } /* std::vector > m_OpenSSLMutexes; static void OpensslLockingCallback(int mode, int type, const char * file, int line) @@ -898,45 +898,44 @@ namespace crypto m_OpenSSLMutexes[type]->lock (); else m_OpenSSLMutexes[type]->unlock (); - } + } }*/ - + void InitCrypto (bool precomputation) { SSL_library_init (); /* auto numLocks = CRYPTO_num_locks(); for (int i = 0; i < numLocks; i++) - m_OpenSSLMutexes.emplace_back (new std::mutex); + m_OpenSSLMutexes.emplace_back (new std::mutex); CRYPTO_set_locking_callback (OpensslLockingCallback);*/ if (precomputation) - { + { #if defined(__x86_64__) g_ElggTable = new BIGNUM * [ELGAMAL_FULL_EXPONENT_NUM_BYTES][255]; PrecalculateElggTable (g_ElggTable, ELGAMAL_FULL_EXPONENT_NUM_BYTES); -#else +#else g_ElggTable = new BIGNUM * [ELGAMAL_SHORT_EXPONENT_NUM_BYTES][255]; PrecalculateElggTable (g_ElggTable, ELGAMAL_SHORT_EXPONENT_NUM_BYTES); -#endif - } +#endif + } } - + void TerminateCrypto () { if (g_ElggTable) - { + { DestroyElggTable (g_ElggTable, -#if defined(__x86_64__) +#if defined(__x86_64__) ELGAMAL_FULL_EXPONENT_NUM_BYTES #else - ELGAMAL_SHORT_EXPONENT_NUM_BYTES -#endif - ); + ELGAMAL_SHORT_EXPONENT_NUM_BYTES +#endif + ); delete[] g_ElggTable; g_ElggTable = nullptr; - } + } /* CRYPTO_set_locking_callback (nullptr); m_OpenSSLMutexes.clear ();*/ - } + } } } - From 5109d40d8e871d4871d17f14343e534165cfc0c8 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 17 Nov 2017 14:28:48 -0500 Subject: [PATCH 2689/6300] don't publish unknown crypto type to Java floodfill again --- libi2pd/Destination.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index f57de0c3..16912daf 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -483,9 +483,22 @@ namespace client { if (m_PublishReplyToken) { - LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again"); m_PublishReplyToken = 0; - Publish (); + if (GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) + { + LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again"); + Publish (); + } + else + { + LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ()); + // Java floodfill never sends confirmantion back for unknown crypto type + // assume it successive and try to verify + m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); + m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, + shared_from_this (), std::placeholders::_1)); + + } } } } From 65db96e663c94807c6e71c6c0c89ab8fe77246f0 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 18 Nov 2017 09:50:05 -0500 Subject: [PATCH 2690/6300] reseed proxy --- libi2pd/Config.cpp | 1 + libi2pd/NTCPSession.cpp | 4 +- libi2pd/Reseed.cpp | 240 +++++++++++++++++++++++++++++++--------- 3 files changed, 192 insertions(+), 53 deletions(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 4997852a..636e4986 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -179,6 +179,7 @@ namespace config { ("reseed.floodfill", value()->default_value(""), "Path to router info of floodfill to reseed from") ("reseed.file", value()->default_value(""), "Path to local .su3 file or HTTPS URL to reseed from") ("reseed.zipfile", value()->default_value(""), "Path to local .zip file to reseed from") + ("reseed.proxy", value()->default_value(""), "url for reseed proxy, supports http/socks") ("reseed.urls", value()->default_value( "https://reseed.i2p-projekt.de/," "https://i2p.mooo.com/netDb/," diff --git a/libi2pd/NTCPSession.cpp b/libi2pd/NTCPSession.cpp index 27f30600..a340089c 100644 --- a/libi2pd/NTCPSession.cpp +++ b/libi2pd/NTCPSession.cpp @@ -1247,7 +1247,7 @@ namespace transport return; } buff[4] = (uint8_t) addrsize; - memcpy(buff+4, host.c_str(), addrsize); + memcpy(buff+5, host.c_str(), addrsize); } htobe16buf(buff+sz, port); sz += 2; @@ -1259,7 +1259,7 @@ namespace transport } }); - boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff, sz), [=](const boost::system::error_code & e, std::size_t transferred) { + boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff, 10), [=](const boost::system::error_code & e, std::size_t transferred) { if(e) { LogPrint(eLogError, "NTCP: socks proxy read error ", e.message()); diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index f35425da..21fa790e 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -489,6 +489,27 @@ namespace data std::string Reseeder::HttpsRequest (const std::string& address) { + i2p::http::URL proxyUrl; + std::string proxy; i2p::config::GetOption("reseed.proxy", proxy); + // check for proxy url + if(proxy.size()) { + // parse + if(proxyUrl.parse(proxy)) { + if (proxyUrl.schema == "http" && !proxyUrl.port) { + proxyUrl.port = 80; + } else if (proxyUrl.schema == "socks" && !proxyUrl.port) { + proxyUrl.port = 1080; + } + // check for valid proxy url schema + if (proxyUrl.schema != "http" && proxyUrl.schema != "socks") { + LogPrint(eLogError, "Reseed: bad proxy url: ", proxy); + return ""; + } + } else { + LogPrint(eLogError, "Reseed: bad proxy url: ", proxy); + return ""; + } + } i2p::http::URL url; if (!url.parse(address)) { LogPrint(eLogError, "Reseed: failed to parse url: ", address); @@ -500,68 +521,185 @@ namespace data 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 (url.host, std::to_string(url.port)), ecode); - if (!ecode) + + boost::asio::ssl::context ctx(service, boost::asio::ssl::context::sslv23); + ctx.set_verify_mode(boost::asio::ssl::context::verify_none); + boost::asio::ssl::stream s(service, ctx); + + if(proxyUrl.schema.size()) { - boost::asio::ssl::context ctx(service, boost::asio::ssl::context::sslv23); - ctx.set_verify_mode(boost::asio::ssl::context::verify_none); - boost::asio::ssl::stream s(service, ctx); - s.lowest_layer().connect (*it, ecode); - if (!ecode) + // proxy connection + auto it = boost::asio::ip::tcp::resolver(service).resolve ( + boost::asio::ip::tcp::resolver::query (proxyUrl.host, std::to_string(proxyUrl.port)), ecode); + if(!ecode) { - SSL_set_tlsext_host_name(s.native_handle(), url.host.c_str ()); - s.handshake (boost::asio::ssl::stream_base::client, ecode); - if (!ecode) + s.lowest_layer().connect(*it, ecode); + if(!ecode) { - LogPrint (eLogDebug, "Reseed: Connected to ", url.host, ":", url.port); - i2p::http::HTTPReq req; - req.uri = url.to_string(); - req.AddHeader("User-Agent", "Wget/1.11.4"); - req.AddHeader("Connection", "close"); - s.write_some (boost::asio::buffer (req.to_string())); - // read response - std::stringstream rs; - 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 - 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); + auto & sock = s.next_layer(); + if(proxyUrl.schema == "http") + { + i2p::http::HTTPReq proxyReq; + i2p::http::HTTPRes proxyRes; + proxyReq.method = "CONNECT"; + proxyReq.version = "HTTP/1.1"; + proxyReq.uri = url.host + ":" + std::to_string(url.port); + + boost::asio::streambuf writebuf, readbuf; + + std::ostream out(&writebuf); + out << proxyReq.to_string(); + + boost::asio::write(sock, writebuf.data(), boost::asio::transfer_all(), ecode); + if (ecode) + { + sock.close(); + LogPrint(eLogError, "Reseed: HTTP CONNECT write error: ", ecode.message()); + return ""; + } + boost::asio::read_until(sock, readbuf, "\r\n\r\n", ecode); + if (ecode) + { + sock.close(); + LogPrint(eLogError, "Reseed: HTTP CONNECT read error: ", ecode.message()); + return ""; + } + if(proxyRes.parse(boost::asio::buffer_cast(readbuf.data()), readbuf.size()) <= 0) + { + sock.close(); + LogPrint(eLogError, "Reseed: HTTP CONNECT malformed reply"); + return ""; + } + if(proxyRes.code != 200) + { + sock.close(); + LogPrint(eLogError, "Reseed: HTTP CONNECT got bad status: ", proxyRes.code); + return ""; + } + } + else + { + // assume socks if not http, is checked before this for other types + // TODO: support username/password auth etc + uint8_t hs_writebuf[3] = {0x05, 0x01, 0x00}; + uint8_t hs_readbuf[2]; + boost::asio::write(sock, boost::asio::buffer(hs_writebuf, 3), boost::asio::transfer_all(), ecode); + if(ecode) + { + sock.close(); + LogPrint(eLogError, "Reseed: SOCKS handshake write failed: ", ecode.message()); + return ""; + } + boost::asio::read(sock, boost::asio::buffer(hs_readbuf, 2), ecode); + if(ecode) + { + sock.close(); + LogPrint(eLogError, "Reseed: SOCKS handshake read failed: ", ecode.message()); + return ""; + } + size_t sz = 0; + uint8_t buf[256]; + + buf[0] = 0x05; + buf[1] = 0x01; + buf[2] = 0x00; + buf[3] = 0x03; + sz += 4; + size_t hostsz = url.host.size(); + if(1 + 2 + hostsz + sz > sizeof(buf)) + { + sock.close(); + LogPrint(eLogError, "Reseed: SOCKS handshake failed, hostname too big: ", url.host); + return ""; + } + buf[4] = (uint8_t) hostsz; + memcpy(buf+5, url.host.c_str(), hostsz); + sz += hostsz + 1; + htobe16buf(buf+sz, url.port); + sz += 2; + boost::asio::write(sock, boost::asio::buffer(buf, sz), boost::asio::transfer_all(), ecode); + if(ecode) + { + sock.close(); + LogPrint(eLogError, "Reseed: SOCKS handshake failed writing: ", ecode.message()); + return ""; + } + boost::asio::read(sock, boost::asio::buffer(buf, 10), ecode); + if(ecode) + { + sock.close(); + LogPrint(eLogError, "Reseed: SOCKS handshake failed reading: ", ecode.message()); + return ""; + } + if(buf[1] != 0x00) + { + sock.close(); + LogPrint(eLogError, "Reseed: SOCKS handshake bad reply code: ", std::to_string(buf[1])); 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 ", url.host, ": ", ecode.message ()); } else - LogPrint (eLogError, "Reseed: Couldn't resolve address ", url.host, ": ", ecode.message ()); + { + // direct connection + 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) + s.lowest_layer().connect (*it, ecode); + } + if (!ecode) + { + SSL_set_tlsext_host_name(s.native_handle(), url.host.c_str ()); + s.handshake (boost::asio::ssl::stream_base::client, ecode); + if (!ecode) + { + LogPrint (eLogDebug, "Reseed: Connected to ", url.host, ":", url.port); + i2p::http::HTTPReq req; + req.uri = url.to_string(); + req.AddHeader("User-Agent", "Wget/1.11.4"); + req.AddHeader("Connection", "close"); + s.write_some (boost::asio::buffer (req.to_string())); + // read response + std::stringstream rs; + 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 + 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 ", url.host, ": ", ecode.message ()); return ""; - } + } } } From fb46de5ca665cb11fbbde86957276b91ff9528f8 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 18 Nov 2017 23:56:52 +0300 Subject: [PATCH 2691/6300] Delete old R4SAS's reseed cert --- .../certificates/reseed/r4sas_at_mail.i2p.crt | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 contrib/certificates/reseed/r4sas_at_mail.i2p.crt diff --git a/contrib/certificates/reseed/r4sas_at_mail.i2p.crt b/contrib/certificates/reseed/r4sas_at_mail.i2p.crt deleted file mode 100644 index b0c8d749..00000000 --- a/contrib/certificates/reseed/r4sas_at_mail.i2p.crt +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFezCCA2OgAwIBAgIEb8xTzzANBgkqhkiG9w0BAQ0FADBuMQswCQYDVQQGEwJY -WDELMAkGA1UECBMCWFgxHjAcBgNVBAcTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEL -MAkGA1UEChMCWFgxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOcjRzYXNAbWFpbC5p -MnAwHhcNMTYwODExMjIyNDM2WhcNMjYwODExMjIyNDM2WjBuMQswCQYDVQQGEwJY -WDELMAkGA1UECBMCWFgxHjAcBgNVBAcTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEL -MAkGA1UEChMCWFgxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOcjRzYXNAbWFpbC5p -MnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDjUMy/aYd0i6Oc3rdc -24V/fM2vhviH+cNhAOXsMSrwDSVbFQkuDPIfq4fo1A25rsyULR57vy7XKA51OstX -GvREPDhth4cMZjthq0f8AVzPq2vIk8Po65uvKR190yupPQ4FhvGeRkHkqp+SqoIJ -lClD8xZEHrUHSYZotm5TLWIgSwa4DuO1q3bMRI8oIWzqhv99FtlmHlC8fjVUN4mR -2czhABr0u6RMPOtJwTVxWgT1PKXiLWfmeHb63TcPYGgpJ39iMDOjtgY9jYueoO8J -uGJJtkGRIRjOuhDFE9NUlNnljUxUDWvMU7zCO4ozaKMZgoxr1WoIO6ubI/003I53 -sZ0Q5h8yfz+QreEw3wzjxnQSkejG5c3NIvJSiu0ylOqDWmnj0v1Jv/P0qAMU4bt/ -ZWj0GOrYfPn9STg0VxMOQwQ2o15GAcbr6PFI56U2IJhZAeER3hIe2kOl6591jQ67 -zvOjPRRh2q05Ss8yo7nEpYUiB/FrE6RssJ5tVwX6e6Tq4Z1frINanIkUkToTkypP -Fn2T/KV2lak9rLuxzvhiDobu5iGCR323zFcFEpGq4Wsopx1uRT9+71G/ejw8pKTf -kQ7XiGaaxFyZuMuOz3bFkTuoTmAkUQTlRjGw2DmKZi/apcN+VQgpq9tQpS10pEUy -DCVdtw1AdlOnwb+Hf3X0Uz6OjwIDAQABoyEwHzAdBgNVHQ4EFgQUqLBlSlnqCo25 -sIduMPm4iROMqkAwDQYJKoZIhvcNAQENBQADggIBAGWv8rDTzqhHkjqDOT+Ba2bs -gVddpCNa94RQoOn2DUSu4c+yuWJLSctjpX7gswB6qvWk5Ojfafop8jJW8zuozJrO -76b9345S/VnnbHVSoVfIpF9Fve1Xc8nvU4ylRcAMwhf8N3Md5Yc1kb+P7NtTTwMZ -TBR3xY3fVxv3qTpKApWQKkUiqM7yJKOfS8xcK/pjO/3oRUwfA9DHugCUpgSidlN+ -JkZmgwAcA3/WMlDdNKmKnWLGB2Ea+W6kIx5TDFfjf11rbjuwXhDLyaOK88qlN0W2 -hYa31UDSEYYQd3gMG1gjVc+9vZA/Vr0+SF5ULN9QLjB18CVIdPv92mBjJQRmJSVW -b1qwZI0jf/V+1fu9H9r7sE4CId3+WGOek3UNRNZLOVZCSiFq/b9cswcQZGjw6aE+ -1FNjw1HW9CLoNcg74Kr98QouOoeRSofQYZiYqaM9Sz/MsinYMIRGRGw3Uq1uNRo0 -WgoOngmZSKGaW5PFR19uuuNIVB4fCShqBVyrguW4xIskta1JVFoggFeOeTwk6/kH -S5roMzyB/kzv83A2IB0VxqbiDj8khgdm1Us6HCCmU+iTRVyG28gFklCJ8dQfxgGH -W2gpIwvxYLyNP14/7E1oF7/NfHmyjAVzYnR5Xw2wE4tvSHuIrHhj6Q26VB3vze6j -E/w1AJEepnw/KfHqS3bw ------END CERTIFICATE----- From 611c1a7502c729f3cbd37b3ba8e3b2d7d361cb82 Mon Sep 17 00:00:00 2001 From: hypnosis-i2p Date: Sun, 19 Nov 2017 19:26:36 +0800 Subject: [PATCH 2692/6300] updated qt to build and to reflect some core changes like log dest --- qt/i2pd_qt/SignatureTypeComboboxFactory.h | 7 ------- qt/i2pd_qt/mainwindow.cpp | 11 ++++++----- qt/i2pd_qt/mainwindow.h | 1 - 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/qt/i2pd_qt/SignatureTypeComboboxFactory.h b/qt/i2pd_qt/SignatureTypeComboboxFactory.h index 2380b67a..41245dac 100644 --- a/qt/i2pd_qt/SignatureTypeComboboxFactory.h +++ b/qt/i2pd_qt/SignatureTypeComboboxFactory.h @@ -70,13 +70,6 @@ public: addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "GOSTR3410_TC26_A_512_GOSTR3411_512", 0), SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512); //10 if(selectedSigType==SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} ++index; - // TODO: remove later - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256_TEST", 0), SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256_TEST); //65281 - if(selectedSigType==SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256_TEST){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; - addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "GOSTR3410_TC26_A_512_GOSTR3411_512_TEST", 0), SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512_TEST); //65282 - if(selectedSigType==SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512_TEST){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} - ++index; if(!foundSelected){ addItem(signatureTypeCombobox, QString::number(selectedSigType), selectedSigType); //unknown sigtype signatureTypeCombobox->setCurrentIndex(index); diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 93da099a..e9e95ce1 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -155,8 +155,9 @@ MainWindow::MainWindow(QWidget *parent) : uiSettings->logDestinationComboBox->clear(); uiSettings->logDestinationComboBox->insertItems(0, QStringList() - << QApplication::translate("MainWindow", "stdout", 0) - << QApplication::translate("MainWindow", "file", 0) + << QApplication::translate("MainWindow", "syslog", 0) + << QApplication::translate("MainWindow", "stdout", 0) + << QApplication::translate("MainWindow", "file", 0) ); initLogDestinationCombobox( OPTION("","log",[]{return "";}), uiSettings->logDestinationComboBox); @@ -302,9 +303,9 @@ MainWindow::MainWindow(QWidget *parent) : } void MainWindow::logDestinationComboBoxValueChanged(const QString & text) { - bool stdout = text==QString("stdout"); - uiSettings->logFileLineEdit->setEnabled(!stdout); - uiSettings->logFileBrowsePushButton->setEnabled(!stdout); + bool fileEnabled = text==QString("file"); + uiSettings->logFileLineEdit->setEnabled(fileEnabled); + uiSettings->logFileBrowsePushButton->setEnabled(fileEnabled); } diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 7e55a65f..cac97a1f 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -224,7 +224,6 @@ public: } virtual void saveToStringStream(std::stringstream& out){ std::string logDest = comboBox->currentText().toStdString(); - if(logDest==std::string("stdout"))logDest=""; optionValue=logDest; MainWindowItem::saveToStringStream(out); } From 4485d6fdf455cb91a89f36ab469c50d43d312bd7 Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Tue, 21 Nov 2017 14:35:39 +0300 Subject: [PATCH 2693/6300] Fix flags -stdlib should not be changed. It breaks build on e.g. FreeBSD where libc++ is used. --- build/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index c01e0ba2..db0fd33f 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -180,7 +180,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # more tweaks if (NOT (MSVC OR MSYS OR APPLE)) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions -stdlib=libstdc++" ) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions" ) endif() endif () From 474158dd1811165bc7f370718494cfebd01fc239 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 21 Nov 2017 11:04:32 -0500 Subject: [PATCH 2694/6300] rollback. build error --- build/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index db0fd33f..c01e0ba2 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -180,7 +180,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # more tweaks if (NOT (MSVC OR MSYS OR APPLE)) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions" ) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions -stdlib=libstdc++" ) endif() endif () From b71e20dfa363b37dda04c4dc471d98a7717b4b8f Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 21 Nov 2017 13:25:40 -0500 Subject: [PATCH 2695/6300] changed back --- build/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index c01e0ba2..6b0198c7 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -180,7 +180,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # more tweaks if (NOT (MSVC OR MSYS OR APPLE)) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions -stdlib=libstdc++" ) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions ) endif() endif () From 6d01a3a7d1a377c0878068ad18d03a1a81f48479 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 21 Nov 2017 21:33:24 +0300 Subject: [PATCH 2696/6300] fix (quote) --- build/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 6b0198c7..db0fd33f 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -180,7 +180,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # more tweaks if (NOT (MSVC OR MSYS OR APPLE)) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions ) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions" ) endif() endif () From 492d71a9244ff03a10c3b43f84dd79a63320af15 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 22 Nov 2017 14:49:45 -0500 Subject: [PATCH 2697/6300] transient keys --- libi2pd_client/ClientContext.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 4eb06424..e340c3f1 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -284,6 +284,13 @@ namespace client bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType) { + if (filename == "transient") + { + keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); + LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); + return true; + } + bool success = true; std::string fullPath = i2p::fs::DataDirPath (filename); std::ifstream s(fullPath, std::ifstream::binary); From e1b58039026a661ff6c39eeca4f2a04c9a2b3bbf Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 23 Nov 2017 12:26:42 -0500 Subject: [PATCH 2698/6300] fix overflow --- libi2pd/Streaming.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index a1cf536e..f7c3cdf8 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -242,18 +242,25 @@ namespace stream if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) { - uint8_t signature[256]; + uint8_t signature[512]; auto signatureLen = m_RemoteIdentity->GetSignatureLen (); - memcpy (signature, optionData, signatureLen); - memset (const_cast(optionData), 0, signatureLen); - if (!m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature)) + if(signatureLen <= sizeof(signature)) { - LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); - Close (); - flags |= PACKET_FLAG_CLOSE; + memcpy (signature, optionData, signatureLen); + memset (const_cast(optionData), 0, signatureLen); + if (!m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature)) + { + LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); + Close (); + flags |= PACKET_FLAG_CLOSE; + } + memcpy (const_cast(optionData), signature, signatureLen); + optionData += signatureLen; + } + else + { + LogPrint(eLogError, "Streaming: Signature too big, ", signatureLen, " bytes"); } - memcpy (const_cast(optionData), signature, signatureLen); - optionData += signatureLen; } packet->offset = packet->GetPayload () - packet->buf; From ffad1ecd6df1d8827f928eefcf072074ad828a71 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Thu, 23 Nov 2017 13:45:46 -0500 Subject: [PATCH 2699/6300] reduce buffer size --- libi2pd/Streaming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index f7c3cdf8..2bf267fa 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -242,7 +242,7 @@ namespace stream if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) { - uint8_t signature[512]; + uint8_t signature[256]; auto signatureLen = m_RemoteIdentity->GetSignatureLen (); if(signatureLen <= sizeof(signature)) { From a5b1b24fee481889112cc70aeb44048f584f11bc Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 24 Nov 2017 15:37:17 -0500 Subject: [PATCH 2700/6300] implement i2p.streaming.connectDelay option --- libi2pd/Destination.cpp | 12 ++++++++++-- libi2pd/Destination.h | 6 ++++++ libi2pd/Streaming.cpp | 10 ++++++---- libi2pd/Streaming.h | 4 ++-- libi2pd_client/ClientContext.cpp | 3 ++- libi2pd_client/ClientContext.h | 4 ++-- 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 16912daf..4b6e8bc8 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -715,8 +715,8 @@ namespace client } ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): - LeaseSetDestination (isPublic, params), - m_Keys (keys), m_DatagramDestination (nullptr), m_RefCounter (0), + LeaseSetDestination (isPublic, params), m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), + m_DatagramDestination (nullptr), m_RefCounter (0), m_ReadyChecker(GetService()) { if (isPublic) @@ -727,6 +727,14 @@ namespace client m_Decryptor = m_Keys.CreateDecryptor (m_EncryptionPrivateKey); if (isPublic) LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created"); + + // extract streaming params + if (params) + { + auto it = params->find (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY); + if (it != params->end ()) + m_StreamingAckDelay = std::stoi(it->second); + } } ClientDestination::~ClientDestination () diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 3b87d1d0..d11b10dd 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -58,6 +58,10 @@ namespace client const char I2CP_PARAM_MAX_TUNNEL_LATENCY[] = "latency.max"; const int DEFAULT_MAX_TUNNEL_LATENCY = 0; + // streaming + const char I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY[] = "i2p.streaming.initialAckDelay"; + const int DEFAULT_INITIAL_ACK_DELAY = 200; // milliseconds + typedef std::function stream)> StreamRequestComplete; class LeaseSetDestination: public i2p::garlic::GarlicDestination, @@ -199,6 +203,7 @@ namespace client void StopAcceptingStreams (); bool IsAcceptingStreams () const; void AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor); + int GetStreamingAckDelay () const { return m_StreamingAckDelay; } // datagram i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; @@ -230,6 +235,7 @@ namespace client uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; std::shared_ptr m_Decryptor; + int m_StreamingAckDelay; std::shared_ptr m_StreamingDestination; // default std::map > m_StreamingDestinationsByPorts; i2p::datagram::DatagramDestination * m_DatagramDestination; diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 2bf267fa..ef655776 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -62,6 +62,7 @@ namespace stream m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), + m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0) { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); @@ -73,7 +74,8 @@ namespace stream m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE), - m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0) + m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), + m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0) { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); } @@ -161,7 +163,7 @@ namespace stream { m_IsAckSendScheduled = true; auto ackTimeout = m_RTT/10; - if (ackTimeout > ACK_SEND_TIMEOUT) ackTimeout = ACK_SEND_TIMEOUT; + if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ackTimeout)); m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, shared_from_this (), std::placeholders::_1)); @@ -199,7 +201,7 @@ namespace stream { // wait for SYN m_IsAckSendScheduled = true; - m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ACK_SEND_TIMEOUT)); + m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(SYN_TIMEOUT)); m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, shared_from_this (), std::placeholders::_1)); } @@ -805,7 +807,7 @@ namespace stream { if (m_LastReceivedSequenceNumber < 0) { - LogPrint (eLogWarning, "Streaming: SYN has not been received after ", ACK_SEND_TIMEOUT, " milliseconds after follow on, terminate rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); + LogPrint (eLogWarning, "Streaming: SYN has not been received after ", SYN_TIMEOUT, " milliseconds after follow on, terminate rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); m_Status = eStreamStatusReset; Close (); return; diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index 94b356e8..025ceb63 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -42,13 +42,13 @@ namespace stream const size_t STREAMING_MTU = 1730; const size_t MAX_PACKET_SIZE = 4096; const size_t COMPRESSION_THRESHOLD_SIZE = 66; - const int ACK_SEND_TIMEOUT = 200; // in milliseconds const int MAX_NUM_RESEND_ATTEMPTS = 6; const int WINDOW_SIZE = 6; // in messages const int MIN_WINDOW_SIZE = 1; const int MAX_WINDOW_SIZE = 128; const int INITIAL_RTT = 8000; // in milliseconds const int INITIAL_RTO = 9000; // in milliseconds + const int SYN_TIMEOUT = 200; // how long we wait for SYN after follow-on, in milliseconds const size_t MAX_PENDING_INCOMING_BACKLOG = 128; const int PENDING_INCOMING_TIMEOUT = 10; // in seconds const int MAX_RECEIVE_TIMEOUT = 30; // in seconds @@ -242,7 +242,7 @@ namespace stream std::mutex m_SendBufferMutex; SendBufferQueue m_SendBuffer; - int m_WindowSize, m_RTT, m_RTO; + int m_WindowSize, m_RTT, m_RTO, m_AckDelay; uint64_t m_LastWindowSizeIncreaseTime; int m_NumResendAttempts; }; diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index e340c3f1..f823a92b 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -431,6 +431,7 @@ namespace client options[I2CP_PARAM_TAGS_TO_SEND] = GetI2CPOption (section, I2CP_PARAM_TAGS_TO_SEND, DEFAULT_TAGS_TO_SEND); options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY); options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY); + options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); } void ClientContext::ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const @@ -447,7 +448,7 @@ namespace client if (i2p::config::GetOption(prefix + I2CP_PARAM_MIN_TUNNEL_LATENCY, value)) options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_MAX_TUNNEL_LATENCY, value)) - options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = value; + options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = value; } void ClientContext::ReadTunnels () diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 4b5d0dce..9b65ef32 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -90,8 +90,8 @@ namespace client template std::string GetI2CPOption (const Section& section, const std::string& name, const Type& value) const; template - void ReadI2CPOptions (const Section& section, std::map& options) const; - void ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const; + void ReadI2CPOptions (const Section& section, std::map& options) const; // for tunnels + void ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const; // for HTTP and SOCKS proxy void CleanupUDP(const boost::system::error_code & ecode); void ScheduleCleanupUDP(); From c69c369502bb9bc0bd0ecf5afd43d40aa1f03bba Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 26 Nov 2017 12:30:18 +0300 Subject: [PATCH 2701/6300] close div, update qt gitignore --- daemon/HTTPServer.cpp | 12 ++++++------ qt/i2pd_qt/.gitignore | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 9c039df4..5fa65679 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -275,13 +275,13 @@ namespace http { s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
    \r\n
    \r\n"; s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); - s << "\r\n"; + s << "\r\n"; s << "
    Services
    ServiceState
    " << "HTTP Proxy" << "
    " << "SOCKS Proxy" << "
    " << "BOB" << "
    " << "SAM" << "
    " << "I2CP" << "
    " << "HTTP Proxy" << "
    " << "SOCKS Proxy" << "
    " << "BOB" << "
    " << "SAM" << "
    " << "I2CP" << "
    " << "I2PControl" << "
    " << "I2PControl" << "
    \r\n"; } diff --git a/qt/i2pd_qt/.gitignore b/qt/i2pd_qt/.gitignore index 3bbbf71a..3abca1bd 100644 --- a/qt/i2pd_qt/.gitignore +++ b/qt/i2pd_qt/.gitignore @@ -3,5 +3,7 @@ moc_* ui_* qrc_* i2pd_qt -Makefile +Makefile* *.stash +object_script.* +i2pd_qt_plugin_import.cpp \ No newline at end of file From ab6bc52a0f297aea443dbc6322564216b91e4b4d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 28 Nov 2017 10:59:11 -0500 Subject: [PATCH 2702/6300] don't create destination with RSA signature --- libi2pd/Identity.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 7c4f777d..ed03b7eb 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -634,14 +634,10 @@ namespace data i2p::crypto::CreateECDSAP521RandomKeys (keys.m_SigningPrivateKey, signingPublicKey); break; case SIGNING_KEY_TYPE_RSA_SHA256_2048: - i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA2562048_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey); - break; case SIGNING_KEY_TYPE_RSA_SHA384_3072: - i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA3843072_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey); - break; case SIGNING_KEY_TYPE_RSA_SHA512_4096: - i2p::crypto::CreateRSARandomKeys (i2p::crypto::RSASHA5124096_KEY_LENGTH, keys.m_SigningPrivateKey, signingPublicKey); - break; + LogPrint (eLogWarning, "Identity: RSA signature type is not supported. Create EdDSA"); + // no break here case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: i2p::crypto::CreateEDDSA25519RandomKeys (keys.m_SigningPrivateKey, signingPublicKey); break; @@ -652,7 +648,7 @@ namespace data i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410TC26A512, keys.m_SigningPrivateKey, signingPublicKey); break; default: - LogPrint (eLogError, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1"); + LogPrint (eLogWarning, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1"); return PrivateKeys (i2p::data::CreateRandomKeys ()); // DSA-SHA1 } // encryption From 272090fc8f1727354fb87983acdd9793ce90d58a Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 28 Nov 2017 11:33:51 -0500 Subject: [PATCH 2703/6300] don't accept streams from RSA detinations --- libi2pd/Identity.cpp | 6 ++++++ libi2pd/Identity.h | 1 + libi2pd/RouterInfo.cpp | 5 ++--- libi2pd/Streaming.cpp | 7 +++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index ed03b7eb..23f32bc8 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -324,6 +324,12 @@ namespace data return SIGNING_KEY_TYPE_DSA_SHA1; } + bool IdentityEx::IsRSA () const + { + auto sigType = GetSigningKeyType (); + return sigType <= SIGNING_KEY_TYPE_RSA_SHA512_4096 && sigType >= SIGNING_KEY_TYPE_RSA_SHA256_2048; + } + CryptoKeyType IdentityEx::GetCryptoKeyType () const { if (m_StandardIdentity.certificate[0] == CERTIFICATE_TYPE_KEY && m_ExtendedLen >= 4) diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index 0495b490..53ab4a7c 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -103,6 +103,7 @@ namespace data size_t GetSignatureLen () const; bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; SigningKeyType GetSigningKeyType () const; + bool IsRSA () const; // signing key type CryptoKeyType GetCryptoKeyType () const; void DropVerifier () const; // to save memory diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 7417b5ae..642373f2 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -133,10 +133,9 @@ namespace data if (verifySignature) { // reject RSA signatures - auto sigType = m_RouterIdentity->GetSigningKeyType (); - if (sigType <= SIGNING_KEY_TYPE_RSA_SHA512_4096 && sigType >= SIGNING_KEY_TYPE_RSA_SHA256_2048) + if (m_RouterIdentity->IsRSA ()) { - LogPrint (eLogError, "RouterInfo: RSA signature type ", sigType, " is not allowed"); + LogPrint (eLogError, "RouterInfo: RSA signature type is not allowed"); m_IsUnreachable = true; return; } diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index ef655776..b0a08a4d 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -230,6 +230,13 @@ namespace stream if (flags & PACKET_FLAG_FROM_INCLUDED) { m_RemoteIdentity = std::make_shared(optionData, packet->GetOptionSize ()); + if (m_RemoteIdentity->IsRSA ()) + { + LogPrint (eLogInfo, "Streaming: Incoming stream from RSA destination ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " Discarded"); + m_LocalDestination.DeletePacket (packet); + Terminate (); + return; + } optionData += m_RemoteIdentity->GetFullLen (); if (!m_RemoteLeaseSet) LogPrint (eLogDebug, "Streaming: Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), ", sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); From a1e820182c20b85b6be5e27699915f4b0c92bd0a Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 28 Nov 2017 13:24:07 -0500 Subject: [PATCH 2704/6300] CRYPTO_TYPE for DEST GENERATE --- libi2pd_client/SAM.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 537b4c15..140140fc 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -489,11 +489,15 @@ namespace client ExtractParams (buf, params); // extract signature type i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; + i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; auto it = params.find (SAM_PARAM_SIGNATURE_TYPE); if (it != params.end ()) // TODO: extract string values signatureType = std::stoi(it->second); - auto keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType); + it = params.find (SAM_PARAM_CRYPTO_TYPE); + if (it != params.end ()) + cryptoType = std::stoi(it->second); + auto keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); From df18692af981012e5052216845d3eaad59d3034f Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 1 Dec 2017 12:57:05 -0500 Subject: [PATCH 2705/6300] check I2NP messsage buffer size --- libi2pd/Destination.cpp | 4 +-- libi2pd/Garlic.cpp | 56 ++++++++++++++++++++++++++++++++------- libi2pd/I2NPProtocol.cpp | 28 +++++++++++++++++--- libi2pd/I2NPProtocol.h | 2 +- libi2pd/RouterContext.cpp | 2 +- 5 files changed, 76 insertions(+), 16 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 4b6e8bc8..0c3a1512 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -283,7 +283,7 @@ namespace client break; case eI2NPDeliveryStatus: // we assume tunnel tests non-encrypted - HandleDeliveryStatusMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); + HandleDeliveryStatusMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len), from)); break; case eI2NPDatabaseStore: HandleDatabaseStoreMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET)); @@ -292,7 +292,7 @@ namespace client HandleDatabaseSearchReplyMessage (buf + I2NP_HEADER_SIZE, bufbe16toh (buf + I2NP_HEADER_SIZE_OFFSET)); break; default: - i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); + i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len), from)); } } diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index e84312f8..3212da01 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -512,12 +512,17 @@ namespace garlic void GarlicDestination::HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr from) { - const uint8_t * buf1 = buf; + if (len < 1) + { + LogPrint (eLogError, "Garlic: payload is too short"); + return; + } int numCloves = buf[0]; LogPrint (eLogDebug, "Garlic: ", numCloves," cloves"); - buf++; + buf++; len--; for (int i = 0; i < numCloves; i++) { + const uint8_t * buf1 = buf; // delivery instructions uint8_t flag = buf[0]; buf++; // flag @@ -527,17 +532,29 @@ namespace garlic LogPrint (eLogWarning, "Garlic: clove encrypted"); buf += 32; } + ptrdiff_t offset = buf - buf1; GarlicDeliveryType deliveryType = (GarlicDeliveryType)((flag >> 5) & 0x03); switch (deliveryType) { case eGarlicDeliveryTypeLocal: LogPrint (eLogDebug, "Garlic: type local"); - HandleI2NPMessage (buf, len, from); + if (offset > (int)len) + { + LogPrint (eLogError, "Garlic: message is too short"); + break; + } + HandleI2NPMessage (buf, len - offset, from); break; case eGarlicDeliveryTypeDestination: LogPrint (eLogDebug, "Garlic: type destination"); buf += 32; // destination. check it later or for multiple destinations - HandleI2NPMessage (buf, len, from); + offset = buf1 - buf; + if (offset > (int)len) + { + LogPrint (eLogError, "Garlic: message is too short"); + break; + } + HandleI2NPMessage (buf, len - offset, from); break; case eGarlicDeliveryTypeTunnel: { @@ -545,9 +562,15 @@ namespace garlic // gwHash and gwTunnel sequence is reverted uint8_t * gwHash = buf; buf += 32; + offset = buf1 - buf; + if (offset + 4 > (int)len) + { + LogPrint (eLogError, "Garlic: message is too short"); + break; + } uint32_t gwTunnel = bufbe32toh (buf); - buf += 4; - auto msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from); + buf += 4; offset += 4; + auto msg = CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len - offset), from); if (from) // received through an inbound tunnel { std::shared_ptr tunnel; @@ -568,9 +591,17 @@ namespace garlic { uint8_t * ident = buf; buf += 32; + offset = buf1 - buf; if (!from) // received directly + { + if (offset > (int)len) + { + LogPrint (eLogError, "Garlic: message is too short"); + break; + } i2p::transport::transports.SendMessage (ident, - CreateI2NPMessage (buf, GetI2NPMessageLength (buf))); + CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len - offset))); + } else LogPrint (eLogWarning, "Garlic: type router for inbound tunnels not supported"); break; @@ -578,15 +609,22 @@ namespace garlic default: LogPrint (eLogWarning, "Garlic: unknown delivery type ", (int)deliveryType); } - buf += GetI2NPMessageLength (buf); // I2NP + if (offset > (int)len) + { + LogPrint (eLogError, "Garlic: message is too short"); + break; + } + buf += GetI2NPMessageLength (buf, len - offset); // I2NP buf += 4; // CloveID buf += 8; // Date buf += 3; // Certificate - if (buf - buf1 > (int)len) + offset = buf1 - buf; + if (offset > (int)len) { LogPrint (eLogError, "Garlic: clove is too long"); break; } + len -= offset; } } diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 7fde4893..5719a1b0 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -546,18 +546,40 @@ namespace i2p return msg; } - size_t GetI2NPMessageLength (const uint8_t * msg) + size_t GetI2NPMessageLength (const uint8_t * msg, size_t len) { - return bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET) + I2NP_HEADER_SIZE; + if (len < I2NP_HEADER_SIZE_OFFSET + 2) + { + LogPrint (eLogError, "I2NP: message length ", len, " is smaller than header"); + return len; + } + auto l = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET) + I2NP_HEADER_SIZE; + if (l > len) + { + LogPrint (eLogError, "I2NP: message length ", l, " exceeds buffer length ", len); + l = len; + } + return l; } void HandleI2NPMessage (uint8_t * msg, size_t len) { + if (len < I2NP_HEADER_SIZE) + { + LogPrint (eLogError, "I2NP: message length ", len, " is smaller than header"); + return; + } uint8_t typeID = msg[I2NP_HEADER_TYPEID_OFFSET]; uint32_t msgID = bufbe32toh (msg + I2NP_HEADER_MSGID_OFFSET); LogPrint (eLogDebug, "I2NP: msg received len=", len,", type=", (int)typeID, ", msgID=", (unsigned int)msgID); uint8_t * buf = msg + I2NP_HEADER_SIZE; - int size = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET); + auto size = bufbe16toh (msg + I2NP_HEADER_SIZE_OFFSET); + len -= I2NP_HEADER_SIZE; + if (size > len) + { + LogPrint (eLogError, "I2NP: payload size ", size, " exceeds buffer length ", len); + size = len; + } switch (typeID) { case eI2NPVariableTunnelBuild: diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 2f8aac7b..3f36b0a8 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -243,7 +243,7 @@ namespace tunnel const uint8_t * buf, size_t len, uint32_t replyMsgID = 0); std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr msg); - size_t GetI2NPMessageLength (const uint8_t * msg); + size_t GetI2NPMessageLength (const uint8_t * msg, size_t len); void HandleI2NPMessage (uint8_t * msg, size_t len); void HandleI2NPMessage (std::shared_ptr msg); diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index a49f1718..80a03abf 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -454,7 +454,7 @@ namespace i2p void RouterContext::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { - i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf), from)); + i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len), from)); } void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) From fff34e77f566d238744d0cfba140168aa28c1e26 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 1 Dec 2017 13:43:00 -0500 Subject: [PATCH 2706/6300] pass signature and crypto type to newkeys --- libi2pd_client/BOB.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp index 4539eac8..e4a060d0 100644 --- a/libi2pd_client/BOB.cpp +++ b/libi2pd_client/BOB.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include "Log.h" #include "ClientContext.h" #include "util.h" @@ -434,7 +436,15 @@ namespace client void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: newkeys"); - m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (); + std::vector params; + boost::split (params, operand, boost::is_any_of(" "), boost::token_compress_on); + i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; + i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; + if (params.size () > 0) + signatureType = std::stoi(params[0]); + if (params.size () > 1) + cryptoType = std::stoi(params[1]); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } From 92bebb7ecc0a552836752769c6431ed6952c424a Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 1 Dec 2017 22:18:04 +0300 Subject: [PATCH 2707/6300] webconsole update (#1017) * webconsole exploratory tunnel mark * loglevel on commands page * fix line break on destination page --- daemon/HTTPServer.cpp | 79 ++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 5fa65679..224744b7 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -51,8 +51,8 @@ namespace http { const char *cssStyles = "\r\n"; const char HTTP_PAGE_TUNNELS[] = "tunnels"; @@ -89,10 +91,12 @@ namespace http { const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; const char HTTP_COMMAND_LOGLEVEL[] = "set_loglevel"; + const char HTTP_COMMAND_KILLSTREAM[] = "closestream"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; static std::string ConvertTime (uint64_t time); + std::map HTTPConnection::m_Tokens; static void ShowUptime (std::stringstream& s, int seconds) { @@ -203,10 +207,7 @@ namespace http { s << "ERROR: " << string << "
    \r\n"; } - void ShowStatus ( - std::stringstream& s, - bool includeHiddenContent, - i2p::http::OutputFormatEnum outputFormat) + void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) { s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); @@ -253,12 +254,12 @@ namespace http { ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " KiB/s)
    \r\n"; s << "Data path: " << i2p::fs::GetDataDir() << "
    \r\n"; - s << "
    "; - if((outputFormat==OutputFormatEnum::forWebConsole)||!includeHiddenContent) { - s << "\r\n\r\n

    \r\n"; - } - if(includeHiddenContent) { - s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
    \r\n"; + s << "

    "; + if((outputFormat==OutputFormatEnum::forWebConsole)||!includeHiddenContent) { + s << "\r\n\r\n

    \r\n"; + } + if(includeHiddenContent) { + s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
    \r\n"; s << "Router Family: " << i2p::context.GetRouterInfo().GetProperty("family") << "
    \r\n"; s << "Router Caps: " << i2p::context.GetRouterInfo().GetProperty("caps") << "
    \r\n"; s << "Our external address:" << "
    \r\n" ; @@ -292,12 +293,12 @@ namespace http { } s << address->host.to_string() << ":" << address->port << "
    \r\n"; } - } + } s << "

    \r\n
    \r\n"; - if(outputFormat==OutputFormatEnum::forQtUi) { - s << "
    "; - } - s << "Routers: " << i2p::data::netdb.GetNumRouters () << " "; + if(outputFormat==OutputFormatEnum::forQtUi) { + s << "
    "; + } + s << "Routers: " << i2p::data::netdb.GetNumRouters () << " "; s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
    \r\n"; @@ -308,17 +309,17 @@ namespace http { s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
    \r\n
    \r\n"; - if(outputFormat==OutputFormatEnum::forWebConsole) { - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); - s << "\r\n"; - s << "
    Services
    ServiceState
    " << "HTTP Proxy" << "
    " << "SOCKS Proxy" << "
    " << "BOB" << "
    " << "SAM" << "
    " << "I2CP" << "
    " << "I2PControl" << "
    \r\n"; - } + if(outputFormat==OutputFormatEnum::forWebConsole) { + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); + s << "\r\n"; + s << "
    Services
    ServiceState
    " << "HTTP Proxy" << "
    " << "SOCKS Proxy" << "
    " << "BOB" << "
    " << "SAM" << "
    " << "I2CP" << "
    " << "I2PControl" << "
    \r\n"; + } } void ShowLocalDestinations (std::stringstream& s) @@ -352,7 +353,7 @@ namespace http { static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr dest) { - s << "Base64:
    \r\n
    \r\n
    \r\n"; if (dest->IsEncryptedLeaseSet ()) { @@ -403,19 +404,21 @@ namespace http { s << "
    \r\n"; } - void ShowLocalDestination (std::stringstream& s, const std::string& b32) + void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token) { s << "Local Destination:
    \r\n
    \r\n"; i2p::data::IdentHash ident; ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); + if (dest) { ShowLeaseSetDestination (s, dest); // show streams - s << "\r\n"; - s << ""; - s << ""; + s << "
    Streams
    StreamIDDestination
    \r\n\r\n\r\n"; + s << ""; + s << ""; s << ""; s << ""; s << ""; @@ -424,13 +427,20 @@ namespace http { s << ""; s << ""; s << ""; - s << "\r\n"; + s << "\r\n\r\n\r\n"; for (const auto& it: dest->GetAllStreams ()) { + auto streamDest = i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()); s << ""; - s << ""; - s << ""; + s << ""; + if (it->GetRecvStreamID ()) { + s << ""; + } else { + s << ""; s << ""; s << ""; s << ""; @@ -441,7 +451,7 @@ namespace http { s << ""; s << "\r\n"; } - s << "
    Streams
    StreamID"; // Stream closing button column + s << "DestinationSentReceivedOutRTTWindowStatus
    " << it->GetSendStreamID () << "" << i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()) << "" << it->GetRecvStreamID () << ""; + } + s << "" << streamDest << "" << it->GetNumSentBytes () << "" << it->GetNumReceivedBytes () << "" << it->GetSendQueueSize () << "" << (int)it->GetStatus () << "
    "; + s << "\r\n"; } } @@ -858,7 +868,8 @@ namespace http { m_Socket->close (); } - bool HTTPConnection::CheckAuth (const HTTPReq & req) { + bool HTTPConnection::CheckAuth (const HTTPReq & req) + { /* method #1: http://user:pass@127.0.0.1:7070/ */ if (req.uri.find('@') != std::string::npos) { URL url; @@ -920,7 +931,7 @@ namespace http { } else if (req.uri.find("cmd=") != std::string::npos) { HandleCommand (req, res, s); } else { - ShowStatus (s, true, i2p::http::OutputFormatEnum::forWebConsole); + ShowStatus (s, true, i2p::http::OutputFormatEnum::forWebConsole); res.add_header("Refresh", "10"); } ShowPageTail (s); @@ -930,7 +941,23 @@ namespace http { SendReply (res, content); } - std::map HTTPConnection::m_Tokens; + uint32_t HTTPConnection::CreateToken () + { + uint32_t token; + RAND_bytes ((uint8_t *)&token, 4); + token &= 0x7FFFFFFF; // clear first bit + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_Tokens.begin (); it != m_Tokens.end (); ) + { + if (ts > it->second + TOKEN_EXPIRATION_TIMEOUT) + it = m_Tokens.erase (it); + else + ++it; + } + m_Tokens[token] = ts; + return token; + } + void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s) { std::map params; @@ -947,18 +974,7 @@ namespace http { ShowTunnels (s); else if (page == HTTP_PAGE_COMMANDS) { - uint32_t token; - RAND_bytes ((uint8_t *)&token, 4); - token &= 0x7FFFFFFF; // clear first bit - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_Tokens.begin (); it != m_Tokens.end (); ) - { - if (ts > it->second + TOKEN_EXPIRATION_TIMEOUT) - it = m_Tokens.erase (it); - else - ++it; - } - m_Tokens[token] = ts; + uint32_t token = CreateToken (); ShowCommands (s, token); } else if (page == HTTP_PAGE_TRANSIT_TUNNELS) @@ -966,7 +982,10 @@ namespace http { else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) ShowLocalDestinations (s); else if (page == HTTP_PAGE_LOCAL_DESTINATION) - ShowLocalDestination (s, params["b32"]); + { + uint32_t token = CreateToken (); + ShowLocalDestination (s, params["b32"], token); + } else if (page == HTTP_PAGE_I2CP_LOCAL_DESTINATION) ShowI2CPLocalDestination (s, params["i2cp_id"]); else if (page == HTTP_PAGE_SAM_SESSIONS) @@ -992,7 +1011,10 @@ namespace http { url.parse(req.uri); url.parse_query(params); + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + std::string redirect = "5; url=" + webroot + "?page=commands"; std::string token = params["token"]; + if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) { ShowError(s, "Invalid token"); @@ -1008,36 +1030,74 @@ namespace http { i2p::context.SetAcceptsTunnels (true); else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT) i2p::context.SetAcceptsTunnels (false); - else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { + else if (cmd == HTTP_COMMAND_SHUTDOWN_START) + { i2p::context.SetAcceptsTunnels (false); #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) Daemon.gracefulShutdownInterval = 10*60; #elif defined(WIN32_APP) i2p::win32::GracefulShutdown (); #endif - } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { + } + else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) + { i2p::context.SetAcceptsTunnels (true); #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) Daemon.gracefulShutdownInterval = 0; #elif defined(WIN32_APP) i2p::win32::StopGracefulShutdown (); #endif - } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { + } + else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) + { #ifndef WIN32_APP Daemon.running = false; #else i2p::win32::StopWin32App (); #endif - } else if (cmd == HTTP_COMMAND_LOGLEVEL){ + } + else if (cmd == HTTP_COMMAND_LOGLEVEL) + { std::string level = params["level"]; SetLogLevel (level); - } else { + } + else if (cmd == HTTP_COMMAND_KILLSTREAM) + { + std::string b32 = params["b32"]; + uint32_t streamID = std::stoul(params["streamID"], nullptr); + + i2p::data::IdentHash ident; + ident.FromBase32 (b32); + auto dest = i2p::client::context.FindLocalDestination (ident); + + if (streamID) + { + if (dest) + { + if(dest->DeleteStream (streamID)) + s << "SUCCESS: Stream closed

    \r\n"; + else + s << "ERROR: Stream not found or already was closed

    \r\n"; + } + else + s << "ERROR: Destination not found

    \r\n"; + } + else + s << "ERROR: StreamID can be null

    \r\n"; + + s << "Return to destination page
    \r\n"; + s << "

    You will be redirected back in 5 seconds"; + redirect = "5; url=" + webroot + "?page=local_destination&b32=" + b32; + res.add_header("Refresh", redirect.c_str()); + return; + } + else + { res.code = 400; ShowError(s, "Unknown command: " + cmd); return; } - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - std::string redirect = "5; url=" + webroot + "?page=commands"; + s << "SUCCESS: Command accepted

    \r\n"; s << "Back to commands list
    \r\n"; s << "

    You will be redirected in 5 seconds"; diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index 3d32ed2b..f5ac95fc 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -35,6 +35,7 @@ namespace http void HandlePage (const HTTPReq & req, HTTPRes & res, std::stringstream& data); void HandleCommand (const HTTPReq & req, HTTPRes & res, std::stringstream& data); void SendReply (HTTPRes & res, std::string & content); + uint32_t CreateToken (); private: From dd9b5faa5c072c24aadf4ca6c31d6ac00be992e1 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 5 Mar 2020 18:44:15 -0500 Subject: [PATCH 3588/6300] fixed crash on termination --- libi2pd_client/I2CP.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index a14588a8..c09b7f4d 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -814,8 +814,11 @@ namespace client { m_IsRunning = false; m_Acceptor.cancel (); - for (auto& it: m_Sessions) - it.second->Stop (); + { + auto sessions = m_Sessions; + for (auto& it: sessions) + it.second->Stop (); + } m_Sessions.clear (); m_Service.stop (); if (m_Thread) From 64da62dbe69be040215babad89fc481f0e429c00 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 7 Mar 2020 18:46:40 -0500 Subject: [PATCH 3589/6300] alsways store latest symmkey --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 33 ++++++++++++++--------- libi2pd/ECIESX25519AEADRatchetSession.h | 4 +-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index d2d07343..a066dc17 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -39,25 +39,28 @@ namespace garlic return m_KeyData.GetTag (); } - const uint8_t * RatchetTagSet::GetSymmKey (int index) + void RatchetTagSet::GetSymmKey (int index, uint8_t * key) { - // TODO: store intermediate keys - if (m_NextSymmKeyIndex > 0 && index == m_NextSymmKeyIndex) + if (m_NextSymmKeyIndex > 0 && index >= m_NextSymmKeyIndex) { - i2p::crypto::HKDF (m_CurrentSymmKeyCK, nullptr, 0, "SymmetricRatchet", m_CurrentSymmKeyCK); - m_NextSymmKeyIndex++; + auto num = index + 1 - m_NextSymmKeyIndex; + for (int i = 0; i < num; i++) + i2p::crypto::HKDF (m_CurrentSymmKeyCK, nullptr, 0, "SymmetricRatchet", m_CurrentSymmKeyCK); + m_NextSymmKeyIndex += num; + memcpy (key, m_CurrentSymmKeyCK + 32, 32); } else - CalculateSymmKeyCK (index); - return m_CurrentSymmKeyCK + 32; + CalculateSymmKeyCK (index, key); } - void RatchetTagSet::CalculateSymmKeyCK (int index) + void RatchetTagSet::CalculateSymmKeyCK (int index, uint8_t * key) { - i2p::crypto::HKDF (m_SymmKeyCK, nullptr, 0, "SymmetricRatchet", m_CurrentSymmKeyCK); // keydata_0 = HKDF(symmKey_ck, SYMMKEY_CONSTANT, "SymmetricRatchet", 64) + // TODO: store intermediate keys + uint8_t currentSymmKeyCK[64]; + i2p::crypto::HKDF (m_SymmKeyCK, nullptr, 0, "SymmetricRatchet", currentSymmKeyCK); // keydata_0 = HKDF(symmKey_ck, SYMMKEY_CONSTANT, "SymmetricRatchet", 64) for (int i = 0; i < index; i++) - i2p::crypto::HKDF (m_CurrentSymmKeyCK, nullptr, 0, "SymmetricRatchet", m_CurrentSymmKeyCK); // keydata_n = HKDF(symmKey_chainKey_(n-1), SYMMKEY_CONSTANT, "SymmetricRatchet", 64) - m_NextSymmKeyIndex = index + 1; + i2p::crypto::HKDF (currentSymmKeyCK, nullptr, 0, "SymmetricRatchet", currentSymmKeyCK); // keydata_n = HKDF(symmKey_chainKey_(n-1), SYMMKEY_CONSTANT, "SymmetricRatchet", 64) + memcpy (key, currentSymmKeyCK + 32, 32); } ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner): @@ -373,7 +376,9 @@ namespace garlic memcpy (out, &tag, 8); // ad = The session tag, 8 bytes // ciphertext = ENCRYPT(k, n, payload, ad) - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, out, 8, m_SendTagset.GetSymmKey (index), nonce, out + 8, outLen - 8, true)) // encrypt + uint8_t key[32]; + m_SendTagset.GetSymmKey (index, key); + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, out, 8, key, nonce, out + 8, outLen - 8, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return false; @@ -387,7 +392,9 @@ namespace garlic CreateNonce (index, nonce); // tag's index len -= 8; // tag std::vector payload (len - 16); - if (!i2p::crypto::AEADChaCha20Poly1305 (buf + 8, len - 16, buf, 8, m_ReceiveTagset.GetSymmKey (index), nonce, payload.data (), len - 16, false)) // decrypt + uint8_t key[32]; + m_ReceiveTagset.GetSymmKey (index, key); + if (!i2p::crypto::AEADChaCha20Poly1305 (buf + 8, len - 16, buf, 8, key, nonce, payload.data (), len - 16, false)) // decrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); return false; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index e5836cd4..f23c569c 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -22,11 +22,11 @@ namespace garlic void NextSessionTagRatchet (); uint64_t GetNextSessionTag (); int GetNextIndex () const { return m_NextIndex; }; - const uint8_t * GetSymmKey (int index); + void GetSymmKey (int index, uint8_t * key); private: - void CalculateSymmKeyCK (int index); + void CalculateSymmKeyCK (int index, uint8_t * key); private: From 4adc741de319ee2afc6f51f180fff33db63869b6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 8 Mar 2020 18:13:41 -0400 Subject: [PATCH 3590/6300] send DeliveryStatusMsg for LeaseSet --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 53 ++++++++++++++++++-- libi2pd/ECIESX25519AEADRatchetSession.h | 1 + libi2pd/Garlic.cpp | 60 ++++++++++++++--------- libi2pd/Garlic.h | 11 +++-- 4 files changed, 96 insertions(+), 29 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index a066dc17..5f2eb5c8 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -6,6 +6,8 @@ #include "Tag.h" #include "I2PEndian.h" #include "Timestamp.h" +#include "Tunnel.h" +#include "TunnelPool.h" #include "ECIESX25519AEADRatchetSession.h" namespace i2p @@ -460,12 +462,22 @@ namespace garlic std::vector ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr msg) { + uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); size_t payloadLen = 7; // datatime if (msg && m_Destination) payloadLen += msg->GetPayloadLength () + 13 + 32; - auto leaseSet = CreateDatabaseStoreMsg (GetOwner ()->GetLeaseSet ()); - if (leaseSet) + auto leaseSet = (GetLeaseSetUpdateStatus () == eLeaseSetUpdated) ? CreateDatabaseStoreMsg (GetOwner ()->GetLeaseSet ()) : nullptr; + std::shared_ptr deliveryStatus; + if (leaseSet) + { payloadLen += leaseSet->GetPayloadLength () + 13; + deliveryStatus = CreateEncryptedDeliveryStatusMsg (leaseSet->GetMsgID ()); + payloadLen += deliveryStatus->GetPayloadLength () + 49; + if (GetLeaseSetUpdateMsgID ()) GetOwner ()->RemoveDeliveryStatusSession (GetLeaseSetUpdateMsgID ()); // remove previous + SetLeaseSetUpdateStatus (eLeaseSetSubmitted); + SetLeaseSetUpdateMsgID (leaseSet->GetMsgID ()); + SetLeaseSetSubmissionTime (ts); + } uint8_t paddingSize; RAND_bytes (&paddingSize, 1); paddingSize &= 0x0F; paddingSize++; // 1 - 16 @@ -475,10 +487,13 @@ namespace garlic // DateTime v[offset] = eECIESx25519BlkDateTime; offset++; htobe16buf (v.data () + offset, 4); offset += 2; - htobe32buf (v.data () + offset, i2p::util::GetSecondsSinceEpoch ()); offset += 4; + htobe32buf (v.data () + offset, ts/1000); offset += 4; // in seconds // LeaseSet if (leaseSet) offset += CreateGarlicClove (leaseSet, v.data () + offset, payloadLen - offset); + // DeliveryStatus + if (deliveryStatus) + offset += CreateDeliveryStatusClove (deliveryStatus, v.data () + offset, payloadLen - offset); // msg if (msg && m_Destination) offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset, true); @@ -513,6 +528,38 @@ namespace garlic return cloveSize + 3; } + size_t ECIESX25519AEADRatchetSession::CreateDeliveryStatusClove (std::shared_ptr msg, uint8_t * buf, size_t len) + { + uint16_t cloveSize = msg->GetPayloadLength () + 9 + 37 /* delivery instruction */; + if ((int)len < cloveSize + 3) return 0; + buf[0] = eECIESx25519BlkGalicClove; // clove type + htobe16buf (buf + 1, cloveSize); // size + buf += 3; + if (GetOwner ()) + { + auto inboundTunnel = GetOwner ()->GetTunnelPool ()->GetNextInboundTunnel (); + if (inboundTunnel) + { + // delivery instructions + *buf = eGarlicDeliveryTypeTunnel << 5; buf++; // delivery instructions flag tunnel + // hash and tunnelID sequence is reversed for Garlic + memcpy (buf, inboundTunnel->GetNextIdentHash (), 32); buf += 32;// To Hash + htobe32buf (buf, inboundTunnel->GetNextTunnelID ()); buf += 4;// tunnelID + } + else + { + LogPrint (eLogError, "Garlic: No inbound tunnels in the pool for DeliveryStatus"); + return 0; + } + htobe32buf (buf + 1, msg->GetMsgID ()); // msgID + htobe32buf (buf + 5, msg->GetExpiration ()/1000); // expiration in seconds + memcpy (buf + 9, msg->GetPayload (), msg->GetPayloadLength ()); + } + else + return 0; + return cloveSize + 3; + } + void ECIESX25519AEADRatchetSession::GenerateMoreReceiveTags (int numTags) { for (int i = 0; i < numTags; i++) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index f23c569c..4d273c13 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -107,6 +107,7 @@ namespace garlic std::vector CreatePayload (std::shared_ptr msg); size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len, bool isDestination = false); + size_t CreateDeliveryStatusClove (std::shared_ptr msg, uint8_t * buf, size_t len); void GenerateMoreReceiveTags (int numTags); diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 45c97172..dd63572c 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -58,6 +58,34 @@ namespace garlic m_SharedRoutingPath = path; } + bool GarlicRoutingSession::MessageConfirmed (uint32_t msgID) + { + if (msgID == GetLeaseSetUpdateMsgID ()) + { + SetLeaseSetUpdateStatus (eLeaseSetUpToDate); + SetLeaseSetUpdateMsgID (0); + LogPrint (eLogInfo, "Garlic: LeaseSet update confirmed"); + return true; + } + return false; + } + + std::shared_ptr GarlicRoutingSession::CreateEncryptedDeliveryStatusMsg (uint32_t msgID) + { + auto msg = CreateDeliveryStatusMsg (msgID); + if (GetOwner ()) + { + //encrypt + uint8_t key[32], tag[32]; + RAND_bytes (key, 32); // random session key + RAND_bytes (tag, 32); // random session tag + GetOwner ()->SubmitSessionKey (key, tag); + ElGamalAESSession garlic (key, tag); + msg = garlic.WrapSingleMessage (msg); + } + return msg; + } + ElGamalAESSession::ElGamalAESSession (GarlicDestination * owner, std::shared_ptr destination, int numTags, bool attachLeaseSet): GarlicRoutingSession (owner, attachLeaseSet), @@ -289,19 +317,12 @@ namespace garlic htobe32buf (buf + size, inboundTunnel->GetNextTunnelID ()); // tunnelID size += 4; // create msg - auto msg = CreateDeliveryStatusMsg (msgID); - if (GetOwner ()) - { - //encrypt - uint8_t key[32], tag[32]; - RAND_bytes (key, 32); // random session key - RAND_bytes (tag, 32); // random session tag - GetOwner ()->SubmitSessionKey (key, tag); - ElGamalAESSession garlic (key, tag); - msg = garlic.WrapSingleMessage (msg); - } - memcpy (buf + size, msg->GetBuffer (), msg->GetLength ()); - size += msg->GetLength (); + auto msg = CreateEncryptedDeliveryStatusMsg (msgID); + if (msg) + { + memcpy (buf + size, msg->GetBuffer (), msg->GetLength ()); + size += msg->GetLength (); + } // fill clove uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec uint32_t cloveID; @@ -334,17 +355,12 @@ namespace garlic return tags; } - void ElGamalAESSession::MessageConfirmed (uint32_t msgID) + bool ElGamalAESSession::MessageConfirmed (uint32_t msgID) { TagsConfirmed (msgID); - if (msgID == GetLeaseSetUpdateMsgID ()) - { - SetLeaseSetUpdateStatus (eLeaseSetUpToDate); - SetLeaseSetUpdateMsgID (0); - LogPrint (eLogInfo, "Garlic: LeaseSet update confirmed"); - } - else + if (!GarlicRoutingSession::MessageConfirmed (msgID)) CleanupExpiredTags (); + return true; } void ElGamalAESSession::TagsConfirmed (uint32_t msgID) @@ -775,7 +791,7 @@ namespace garlic void GarlicDestination::HandleDeliveryStatusMessage (uint32_t msgID) { - ElGamalAESSessionPtr session; + GarlicRoutingSessionPtr session; { std::unique_lock l(m_DeliveryStatusSessionsMutex); auto it = m_DeliveryStatusSessions.find (msgID); diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 9c256b48..be0ac117 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -104,7 +104,8 @@ namespace garlic virtual ~GarlicRoutingSession (); virtual std::shared_ptr WrapSingleMessage (std::shared_ptr msg) = 0; virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession - + virtual bool MessageConfirmed (uint32_t msgID); + void SetLeaseSetUpdated () { if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated; @@ -118,7 +119,7 @@ namespace garlic GarlicDestination * GetOwner () const { return m_Owner; } void SetOwner (GarlicDestination * owner) { m_Owner = owner; } - + protected: LeaseSetUpdateStatus GetLeaseSetUpdateStatus () const { return m_LeaseSetUpdateStatus; } @@ -127,6 +128,8 @@ namespace garlic void SetLeaseSetUpdateMsgID (uint32_t msgID) { m_LeaseSetUpdateMsgID = msgID; } void SetLeaseSetSubmissionTime (uint64_t ts) { m_LeaseSetSubmissionTime = ts; } + std::shared_ptr CreateEncryptedDeliveryStatusMsg (uint32_t msgID); + private: GarlicDestination * m_Owner; @@ -165,7 +168,7 @@ namespace garlic std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - void MessageConfirmed (uint32_t msgID); + bool MessageConfirmed (uint32_t msgID); bool CleanupExpiredTags (); // returns true if something left bool CleanupUnconfirmedTags (); // returns true if something has been deleted @@ -267,7 +270,7 @@ namespace garlic std::unordered_map m_ECIESx25519Tags; // session tag -> session // DeliveryStatus std::mutex m_DeliveryStatusSessionsMutex; - std::unordered_map m_DeliveryStatusSessions; // msgID -> session + std::unordered_map m_DeliveryStatusSessions; // msgID -> session public: From 3c534798643d9b0e7f7a529b24beacd679732281 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 8 Mar 2020 20:58:59 -0400 Subject: [PATCH 3591/6300] update LeaseSet for ECIESX25519AEADRatchet sessions --- libi2pd/Garlic.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index dd63572c..9ac5540f 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -810,8 +810,12 @@ namespace garlic void GarlicDestination::SetLeaseSetUpdated () { - std::unique_lock l(m_SessionsMutex); - for (auto& it: m_Sessions) + { + std::unique_lock l(m_SessionsMutex); + for (auto& it: m_Sessions) + it.second->SetLeaseSetUpdated (); + } + for (auto& it: m_ECIESx25519Sessions) it.second->SetLeaseSetUpdated (); } From ee73ee365f5d02a4fc29832745f70a381b085aa1 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 10 Mar 2020 21:49:04 +0800 Subject: [PATCH 3592/6300] some work on qt --- .gitignore | 4 + qt/i2pd_qt/DelayedSaveManager.cpp | 3 + qt/i2pd_qt/DelayedSaveManager.h | 24 +++++ qt/i2pd_qt/DelayedSaveManagerImpl.cpp | 140 ++++++++++++++++++++++++++ qt/i2pd_qt/DelayedSaveManagerImpl.h | 82 +++++++++++++++ qt/i2pd_qt/Saver.cpp | 6 ++ qt/i2pd_qt/Saver.h | 22 ++++ qt/i2pd_qt/SaverImpl.cpp | 79 +++++++++++++++ qt/i2pd_qt/SaverImpl.h | 33 ++++++ qt/i2pd_qt/TunnelPane.cpp | 2 +- qt/i2pd_qt/i2pd_qt.pro | 14 ++- qt/i2pd_qt/mainwindow.cpp | 103 ++++++++----------- qt/i2pd_qt/mainwindow.h | 29 ++++-- 13 files changed, 466 insertions(+), 75 deletions(-) create mode 100644 qt/i2pd_qt/DelayedSaveManager.cpp create mode 100644 qt/i2pd_qt/DelayedSaveManager.h create mode 100644 qt/i2pd_qt/DelayedSaveManagerImpl.cpp create mode 100644 qt/i2pd_qt/DelayedSaveManagerImpl.h create mode 100644 qt/i2pd_qt/Saver.cpp create mode 100644 qt/i2pd_qt/Saver.h create mode 100644 qt/i2pd_qt/SaverImpl.cpp create mode 100644 qt/i2pd_qt/SaverImpl.h diff --git a/.gitignore b/.gitignore index c2db70e0..68aea504 100644 --- a/.gitignore +++ b/.gitignore @@ -264,3 +264,7 @@ qt/i2pd_qt/*.ui_* #unknown android stuff android/libs/ + +#various logs +*LOGS/ + diff --git a/qt/i2pd_qt/DelayedSaveManager.cpp b/qt/i2pd_qt/DelayedSaveManager.cpp new file mode 100644 index 00000000..8e17d111 --- /dev/null +++ b/qt/i2pd_qt/DelayedSaveManager.cpp @@ -0,0 +1,3 @@ +#include "DelayedSaveManager.h" + +DelayedSaveManager::DelayedSaveManager(){} diff --git a/qt/i2pd_qt/DelayedSaveManager.h b/qt/i2pd_qt/DelayedSaveManager.h new file mode 100644 index 00000000..9a9a997b --- /dev/null +++ b/qt/i2pd_qt/DelayedSaveManager.h @@ -0,0 +1,24 @@ +#ifndef DELAYEDSAVEMANAGER_H +#define DELAYEDSAVEMANAGER_H + +#include "Saver.h" + +class DelayedSaveManager +{ +public: + DelayedSaveManager(); + + virtual void setSaver(Saver* saver)=0; + + typedef unsigned int DATA_SERIAL_TYPE; + + virtual void delayedSave(DATA_SERIAL_TYPE dataSerial, bool needsTunnelFocus, std::string tunnelNameToFocus)=0; + + //returns false iff save failed + virtual bool appExiting()=0; + + virtual bool needsFocusOnTunnel()=0; + virtual std::string getTunnelNameToFocus()=0; +}; + +#endif // DELAYEDSAVEMANAGER_H diff --git a/qt/i2pd_qt/DelayedSaveManagerImpl.cpp b/qt/i2pd_qt/DelayedSaveManagerImpl.cpp new file mode 100644 index 00000000..5588d7e9 --- /dev/null +++ b/qt/i2pd_qt/DelayedSaveManagerImpl.cpp @@ -0,0 +1,140 @@ +#include "DelayedSaveManagerImpl.h" + +DelayedSaveManagerImpl::DelayedSaveManagerImpl() : + saver(nullptr), + lastDataSerialSeen(DelayedSaveManagerImpl::INITIAL_DATA_SERIAL), + lastSaveStartedTimestamp(A_VERY_OBSOLETE_TIMESTAMP), + exiting(false), + thread(new DelayedSaveThread(this)) +{ +} + +void DelayedSaveManagerImpl::setSaver(Saver* saver) { + this->saver = saver; +} + +void DelayedSaveManagerImpl::start() { + thread->start(); +} + +bool DelayedSaveManagerImpl::isSaverValid() { + return saver != nullptr; +} + +void DelayedSaveManagerImpl::delayedSave(DATA_SERIAL_TYPE dataSerial, bool focusOnTunnel, std::string tunnelNameToFocus) { + if(lastDataSerialSeen==dataSerial)return; + this->focusOnTunnel = focusOnTunnel; + this->tunnelNameToFocus = tunnelNameToFocus; + lastDataSerialSeen=dataSerial; + assert(isSaverValid()); + TIMESTAMP_TYPE now = getTime(); + TIMESTAMP_TYPE wakeTime = lastSaveStartedTimestamp + DelayedSaveThread::WAIT_TIME_MILLIS; + if(now < wakeTime) { + //defer save until lastSaveStartedTimestamp + DelayedSaveThread::WAIT_TIME_MILLIS + thread->deferSaveUntil(wakeTime); + return; + } + lastSaveStartedTimestamp = now; + thread->startSavingNow(); +} + +bool DelayedSaveManagerImpl::appExiting() { + exiting=true; + thread->wakeThreadAndJoinThread(); + assert(isSaverValid()); + saver->save(false, ""); + return true; +} + +DelayedSaveThread::DelayedSaveThread(DelayedSaveManagerImpl* delayedSaveManagerImpl_): + delayedSaveManagerImpl(delayedSaveManagerImpl_), + mutex(new QMutex()), + waitCondition(new QWaitCondition()), + saveNow(false), + defer(false) +{ + mutex->lock(); +} + +DelayedSaveThread::~DelayedSaveThread(){ + mutex->unlock(); + delete mutex; + delete waitCondition; +} + +void DelayedSaveThread::run() { + forever { + if(delayedSaveManagerImpl->isExiting())return; + waitCondition->wait(mutex, WAIT_TIME_MILLIS); + if(delayedSaveManagerImpl->isExiting())return; + Saver* saver = delayedSaveManagerImpl->getSaver(); + assert(saver!=nullptr); + if(saveNow) { + saveNow = false; + const bool focusOnTunnel = delayedSaveManagerImpl->needsFocusOnTunnel(); + const std::string tunnelNameToFocus = delayedSaveManagerImpl->getTunnelNameToFocus(); + saver->save(focusOnTunnel, tunnelNameToFocus); + continue; + } + if(defer) { + defer=false; +#define max(a,b) (((a)>(b))?(a):(b)) + forever { + TIMESTAMP_TYPE now = DelayedSaveManagerImpl::getTime(); + TIMESTAMP_TYPE millisToWait = max(wakeTime-now, 0); + if(millisToWait>0) { + waitCondition->wait(mutex, millisToWait); + if(delayedSaveManagerImpl->isExiting())return; + continue; + } + const bool focusOnTunnel = delayedSaveManagerImpl->needsFocusOnTunnel(); + const std::string tunnelNameToFocus = delayedSaveManagerImpl->getTunnelNameToFocus(); + saver->save(focusOnTunnel, tunnelNameToFocus); + break; //break inner loop + } + } + } +} + +void DelayedSaveThread::wakeThreadAndJoinThread() { + waitCondition->wakeAll(); + quit(); + wait();//join //"similar to the POSIX pthread_join()" +} + +DelayedSaveManagerImpl::TIMESTAMP_TYPE DelayedSaveManagerImpl::getTime() { + return QDateTime::currentMSecsSinceEpoch(); +} + +void DelayedSaveThread::deferSaveUntil(TIMESTAMP_TYPE wakeTime_) { + wakeTime = wakeTime_; + defer = true; + waitCondition->wakeAll(); +} + +void DelayedSaveThread::startSavingNow() { + //mutex->lock(); + saveNow=true; + waitCondition->wakeAll(); + //mutex->unlock(); +} + +DelayedSaveManagerImpl::~DelayedSaveManagerImpl() { + thread->wakeThreadAndJoinThread(); + delete thread; +} + +bool DelayedSaveManagerImpl::isExiting() { + return exiting; +} +Saver* DelayedSaveManagerImpl::getSaver() { + return saver; +} + +bool DelayedSaveManagerImpl::needsFocusOnTunnel() { + return focusOnTunnel; +} + +std::string DelayedSaveManagerImpl::getTunnelNameToFocus() { + return tunnelNameToFocus; +} diff --git a/qt/i2pd_qt/DelayedSaveManagerImpl.h b/qt/i2pd_qt/DelayedSaveManagerImpl.h new file mode 100644 index 00000000..0d8c7592 --- /dev/null +++ b/qt/i2pd_qt/DelayedSaveManagerImpl.h @@ -0,0 +1,82 @@ +#ifndef DELAYEDSAVEMANAGERIMPL_H +#define DELAYEDSAVEMANAGERIMPL_H + +#include +#include +#include +#include +#include + +#include "mainwindow.h" +#include "DelayedSaveManager.h" +#include "Saver.h" + +class DelayedSaveManagerImpl; + +class DelayedSaveThread : public QThread +{ + Q_OBJECT + +public: + static constexpr unsigned long WAIT_TIME_MILLIS = 1000L; + + typedef qint64 TIMESTAMP_TYPE; + static constexpr TIMESTAMP_TYPE A_VERY_OBSOLETE_TIMESTAMP=0; + + DelayedSaveThread(DelayedSaveManagerImpl* delayedSaveManagerImpl); + virtual ~DelayedSaveThread(); + + void run() override; + + void deferSaveUntil(TIMESTAMP_TYPE wakeTime); + void startSavingNow(); + + void wakeThreadAndJoinThread(); + +private: + DelayedSaveManagerImpl* delayedSaveManagerImpl; + QMutex* mutex; + QWaitCondition* waitCondition; + volatile bool saveNow; + volatile bool defer; + volatile TIMESTAMP_TYPE wakeTime; +}; + +class DelayedSaveManagerImpl : public DelayedSaveManager +{ +public: + DelayedSaveManagerImpl(); + virtual ~DelayedSaveManagerImpl(); + virtual void setSaver(Saver* saver); + virtual void start(); + virtual void delayedSave(DATA_SERIAL_TYPE dataSerial, bool focusOnTunnel, std::string tunnelNameToFocus); + virtual bool appExiting(); + + typedef DelayedSaveThread::TIMESTAMP_TYPE TIMESTAMP_TYPE; + + static constexpr DATA_SERIAL_TYPE INITIAL_DATA_SERIAL=0; + bool isExiting(); + Saver* getSaver(); + static TIMESTAMP_TYPE getTime(); + + bool needsFocusOnTunnel(); + std::string getTunnelNameToFocus(); + +private: + Saver* saver; + bool isSaverValid(); + + volatile DATA_SERIAL_TYPE lastDataSerialSeen; + + static constexpr TIMESTAMP_TYPE A_VERY_OBSOLETE_TIMESTAMP=DelayedSaveThread::A_VERY_OBSOLETE_TIMESTAMP; + TIMESTAMP_TYPE lastSaveStartedTimestamp; + + volatile bool exiting; + DelayedSaveThread* thread; + void wakeThreadAndJoinThread(); + + volatile bool focusOnTunnel; + std::string tunnelNameToFocus; +}; + +#endif // DELAYEDSAVEMANAGERIMPL_H diff --git a/qt/i2pd_qt/Saver.cpp b/qt/i2pd_qt/Saver.cpp new file mode 100644 index 00000000..4841acb0 --- /dev/null +++ b/qt/i2pd_qt/Saver.cpp @@ -0,0 +1,6 @@ +#include "Saver.h" + +Saver::Saver() +{ + +} diff --git a/qt/i2pd_qt/Saver.h b/qt/i2pd_qt/Saver.h new file mode 100644 index 00000000..cefe0220 --- /dev/null +++ b/qt/i2pd_qt/Saver.h @@ -0,0 +1,22 @@ +#ifndef SAVER_H +#define SAVER_H + +#include +#include +#include + +class Saver : public QObject +{ + Q_OBJECT + +public: + Saver(); + //false iff failures + virtual bool save(const bool focusOnTunnel, const std::string& tunnelNameToFocus)=0; + +signals: + void reloadTunnelsConfigAndUISignal(const QString); + +}; + +#endif // SAVER_H diff --git a/qt/i2pd_qt/SaverImpl.cpp b/qt/i2pd_qt/SaverImpl.cpp new file mode 100644 index 00000000..f35ef5b7 --- /dev/null +++ b/qt/i2pd_qt/SaverImpl.cpp @@ -0,0 +1,79 @@ +#include "SaverImpl.h" + +#include +#include +#include + +#include "QList" +#include "QString" + +#include "mainwindow.h" + +SaverImpl::SaverImpl(MainWindow *mainWindowPtr_, QList * configItems_, std::map* tunnelConfigs_) : + configItems(configItems_), tunnelConfigs(tunnelConfigs_), confpath(), tunconfpath(), mainWindowPtr(mainWindowPtr_) +{} + +SaverImpl::~SaverImpl() {} + +bool SaverImpl::save(const bool focusOnTunnel, const std::string& tunnelNameToFocus) { + //save main config + { + std::stringstream out; + for(QList::iterator it = configItems->begin(); it!= configItems->end(); ++it) { + MainWindowItem* item = *it; + item->saveToStringStream(out); + } + + using namespace std; + + + QString backup=confpath+"~"; + if(QFile::exists(backup)) QFile::remove(backup);//TODO handle errors + if(QFile::exists(confpath)) QFile::rename(confpath, backup);//TODO handle errors + ofstream outfile; + outfile.open(confpath.toStdString());//TODO handle errors + outfile << out.str().c_str(); + outfile.close(); + } + + //save tunnels config + { + std::stringstream out; + + for (std::map::iterator it=tunnelConfigs->begin(); it!=tunnelConfigs->end(); ++it) { + //const std::string& name = it->first; + TunnelConfig* tunconf = it->second; + tunconf->saveHeaderToStringStream(out); + tunconf->saveToStringStream(out); + tunconf->saveI2CPParametersToStringStream(out); + } + + using namespace std; + + QString backup=tunconfpath+"~"; + if(QFile::exists(backup)) QFile::remove(backup);//TODO handle errors + if(QFile::exists(tunconfpath)) QFile::rename(tunconfpath, backup);//TODO handle errors + ofstream outfile; + outfile.open(tunconfpath.toStdString());//TODO handle errors + outfile << out.str().c_str(); + outfile.close(); + } + + //reload saved configs +#if 0 + i2p::client::context.ReloadConfig(); +#endif + + if(focusOnTunnel) emit reloadTunnelsConfigAndUISignal(QString::fromStdString(tunnelNameToFocus)); + + return true; +} + +void SaverImpl::setConfPath(QString& confpath_) { confpath = confpath_; } + +void SaverImpl::setTunnelsConfPath(QString& tunconfpath_) { tunconfpath = tunconfpath_; } + +/*void SaverImpl::setTunnelFocus(bool focusOnTunnel, std::string tunnelNameToFocus) { + this->focusOnTunnel=focusOnTunnel; + this->tunnelNameToFocus=tunnelNameToFocus; +}*/ diff --git a/qt/i2pd_qt/SaverImpl.h b/qt/i2pd_qt/SaverImpl.h new file mode 100644 index 00000000..c44877a2 --- /dev/null +++ b/qt/i2pd_qt/SaverImpl.h @@ -0,0 +1,33 @@ +#ifndef SAVERIMPL_H +#define SAVERIMPL_H + +#include +#include + +#include +#include "QList" + +#include "mainwindow.h" +#include "TunnelConfig.h" +#include "Saver.h" + +class MainWindowItem; +class TunnelConfig; + +class SaverImpl : public Saver +{ +public: + SaverImpl(MainWindow *mainWindowPtr_, QList * configItems_, std::map* tunnelConfigs_); + virtual ~SaverImpl(); + virtual bool save(const bool focusOnTunnel, const std::string& tunnelNameToFocus); + void setConfPath(QString& confpath_); + void setTunnelsConfPath(QString& tunconfpath_); +private: + QList * configItems; + std::map* tunnelConfigs; + QString confpath; + QString tunconfpath; + MainWindow* mainWindowPtr; +}; + +#endif // SAVERIMPL_H diff --git a/qt/i2pd_qt/TunnelPane.cpp b/qt/i2pd_qt/TunnelPane.cpp index fb840276..3813eafc 100644 --- a/qt/i2pd_qt/TunnelPane.cpp +++ b/qt/i2pd_qt/TunnelPane.cpp @@ -64,7 +64,7 @@ void TunnelPane::setupTunnelPane( //type { - const QString& type = tunnelConfig->getType(); + //const QString& type = tunnelConfig->getType(); QHBoxLayout * horizontalLayout_ = new QHBoxLayout(); horizontalLayout_->setObjectName(QStringLiteral("horizontalLayout_")); typeLabel = new QLabel(gridLayoutWidget_2); diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 15565c5c..0cb73a2f 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -62,6 +62,7 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \ ../../libi2pd/TunnelGateway.cpp \ ../../libi2pd/TunnelPool.cpp \ ../../libi2pd/util.cpp \ + ../../libi2pd/Elligator.cpp \ ../../libi2pd_client/AddressBook.cpp \ ../../libi2pd_client/BOB.cpp \ ../../libi2pd_client/ClientContext.cpp \ @@ -89,7 +90,11 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \ pagewithbackbutton.cpp \ widgetlock.cpp \ widgetlockregistry.cpp \ - logviewermanager.cpp + logviewermanager.cpp \ + DelayedSaveManager.cpp \ + Saver.cpp \ + DelayedSaveManagerImpl.cpp \ + SaverImpl.cpp HEADERS += DaemonQT.h mainwindow.h \ ../../libi2pd/api.h \ @@ -147,6 +152,7 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../libi2pd/TunnelPool.h \ ../../libi2pd/util.h \ ../../libi2pd/version.h \ + ../../libi2pd/Elligator.h \ ../../libi2pd_client/AddressBook.h \ ../../libi2pd_client/BOB.h \ ../../libi2pd_client/ClientContext.h \ @@ -175,7 +181,11 @@ HEADERS += DaemonQT.h mainwindow.h \ widgetlock.h \ widgetlockregistry.h \ i2pd.rc \ - logviewermanager.h + logviewermanager.h \ + DelayedSaveManager.h \ + Saver.h \ + DelayedSaveManagerImpl.h \ + SaverImpl.h INCLUDEPATH += ../../libi2pd INCLUDEPATH += ../../libi2pd_client diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index e4c9f2a7..3b1890c4 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -1,11 +1,8 @@ -#include -#include #include "mainwindow.h" #include "ui_mainwindow.h" #include "ui_statusbuttons.h" #include "ui_routercommandswidget.h" #include "ui_generalsettingswidget.h" -#include #include #include #include @@ -29,17 +26,22 @@ #include "logviewermanager.h" +#include "DelayedSaveManagerImpl.h" +#include "SaverImpl.h" + std::string programOptionsWriterCurrentSection; MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *parent) : QMainWindow(parent) ,logStream(logStream_) -#ifndef ANDROID - ,quitting(false) -#endif + ,delayedSaveManagerPtr(new DelayedSaveManagerImpl()) + ,dataSerial(DelayedSaveManagerImpl::INITIAL_DATA_SERIAL) ,wasSelectingAtStatusMainPage(false) ,showHiddenInfoStatusMainPage(false) ,logViewerManagerPtr(nullptr) +#ifndef ANDROID + ,quitting(false) +#endif ,ui(new Ui::MainWindow) ,statusButtonsUI(new Ui::StatusButtonsForm) ,routerCommandsUI(new Ui::routerCommandsWidget) @@ -51,9 +53,14 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren ,datadir() ,confpath() ,tunconfpath() + ,tunnelConfigs() ,tunnelsPageUpdateListener(this) + ,saverPtr(new SaverImpl(this, &configItems, &tunnelConfigs)) { + assert(delayedSaveManagerPtr!=nullptr); + assert(saverPtr!=nullptr); + ui->setupUi(this); statusButtonsUI->setupUi(ui->statusButtonsPane); routerCommandsUI->setupUi(routerCommandsParent); @@ -75,7 +82,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren ui->stackedWidget->setCurrentIndex(0); ui->settingsScrollArea->resize(uiSettings->settingsContentsGridLayout->sizeHint().width()+10,380); - QScrollBar* const barSett = ui->settingsScrollArea->verticalScrollBar(); + //QScrollBar* const barSett = ui->settingsScrollArea->verticalScrollBar(); int w = 683; int h = 3060; ui->settingsContents->setFixedSize(w, h); @@ -270,7 +277,13 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren widgetlocks.add(new widgetlock(uiSettings->comboBox_httpPorxySignatureType,uiSettings->httpProxySignTypeComboEditPushButton)); widgetlocks.add(new widgetlock(uiSettings->comboBox_socksProxySignatureType,uiSettings->socksProxySignTypeComboEditPushButton)); - loadAllConfigs(); + loadAllConfigs(saverPtr); + + QObject::connect(saverPtr, SIGNAL(reloadTunnelsConfigAndUISignal(const QString)), + this, SLOT(reloadTunnelsConfigAndUI_QString(const QString))); + + delayedSaveManagerPtr->setSaver(saverPtr); + delayedSaveManagerPtr->start(); QObject::connect(uiSettings->logDestinationComboBox, SIGNAL(currentIndexChanged(const QString &)), this, SLOT(logDestinationComboBoxValueChanged(const QString &))); @@ -292,7 +305,6 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren QObject::connect(uiSettings->tunnelsConfigFileLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(reloadTunnelsConfigAndUI())); - QObject::connect(ui->addServerTunnelPushButton, SIGNAL(released()), this, SLOT(addServerTunnelPushButtonReleased())); QObject::connect(ui->addClientTunnelPushButton, SIGNAL(released()), this, SLOT(addClientTunnelPushButtonReleased())); @@ -307,7 +319,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren logViewerManagerPtr=new LogViewerManager(logStream_,ui->logViewerTextEdit,this); assert(logViewerManagerPtr!=nullptr); - onLoggingOptionsChange(); + //onLoggingOptionsChange(); //QMetaObject::connectSlotsByName(this); } @@ -500,6 +512,8 @@ void MainWindow::handleQuitButton() { quitting=true; #endif close(); + delayedSaveManagerPtr->appExiting(); + qDebug("Performing quit"); QApplication::instance()->quit(); } @@ -526,6 +540,7 @@ void MainWindow::handleGracefulQuitTimerEvent() { quitting=true; #endif close(); + delayedSaveManagerPtr->appExiting(); qDebug("Performing quit"); QApplication::instance()->quit(); } @@ -534,6 +549,8 @@ MainWindow::~MainWindow() { qDebug("Destroying main window"); delete statusPageUpdateTimer; + delete delayedSaveManagerPtr; + delete saverPtr; for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { MainWindowItem* item = *it; item->deleteLater(); @@ -594,7 +611,7 @@ NonGUIOptionItem* MainWindow::initNonGUIOption(ConfigOption option) { return retValue; } -void MainWindow::loadAllConfigs(){ +void MainWindow::loadAllConfigs(SaverImpl* saverPtr){ //BORROWED FROM ??? //TODO move this code into single location std::string config; i2p::config::GetOption("conf", config); @@ -635,6 +652,9 @@ void MainWindow::loadAllConfigs(){ this->datadir = datadir.c_str(); this->tunconfpath = tunConf.c_str(); + saverPtr->setConfPath(this->confpath); + saverPtr->setTunnelsConfPath(this->tunconfpath); + for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { MainWindowItem* item = *it; item->loadFromConfigOption(); @@ -642,10 +662,10 @@ void MainWindow::loadAllConfigs(){ ReadTunnelsConfig(); - onLoggingOptionsChange(); + //onLoggingOptionsChange(); } /** returns false iff not valid items present and save was aborted */ -bool MainWindow::saveAllConfigs(){ +bool MainWindow::saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocus){ QString cannotSaveSettings = QApplication::tr("Cannot save settings."); programOptionsWriterCurrentSection=""; /*if(!logFileNameOption->lineEdit->text().trimmed().isEmpty())logOption->optionValue=boost::any(std::string("file")); @@ -653,7 +673,6 @@ bool MainWindow::saveAllConfigs(){ daemonOption->optionValue=boost::any(false); serviceOption->optionValue=boost::any(false); - std::stringstream out; for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { MainWindowItem* item = *it; if(!item->isValid()){ @@ -661,27 +680,8 @@ bool MainWindow::saveAllConfigs(){ return false; } } - - for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { - MainWindowItem* item = *it; - item->saveToStringStream(out); - } - - using namespace std; - - - QString backup=confpath+"~"; - if(QFile::exists(backup)) QFile::remove(backup);//TODO handle errors - if(QFile::exists(confpath)) QFile::rename(confpath, backup);//TODO handle errors - ofstream outfile; - outfile.open(confpath.toStdString());//TODO handle errors - outfile << out.str().c_str(); - outfile.close(); - - SaveTunnelsConfig(); - - onLoggingOptionsChange(); - + delayedSaveManagerPtr->delayedSave(++dataSerial, focusOnTunnel, tunnelNameToFocus); + //onLoggingOptionsChange(); return true; } @@ -711,7 +711,7 @@ void MainWindow::updated() { adjustSizesAccordingToWrongLabel(); applyTunnelsUiToConfigs(); - saveAllConfigs(); + saveAllConfigs(false); } void MainWindowItem::installListeners(MainWindow *mainWindow) {} @@ -784,6 +784,10 @@ bool MainWindow::applyTunnelsUiToConfigs() { return true; } +void MainWindow::reloadTunnelsConfigAndUI_QString(const QString tunnelNameToFocus) { + reloadTunnelsConfigAndUI(tunnelNameToFocus.toStdString()); +} + void MainWindow::reloadTunnelsConfigAndUI(std::string tunnelNameToFocus) { deleteTunnelForms(); for (std::map::iterator it=tunnelConfigs.begin(); it!=tunnelConfigs.end(); ++it) { @@ -795,31 +799,6 @@ void MainWindow::reloadTunnelsConfigAndUI(std::string tunnelNameToFocus) { appendTunnelForms(tunnelNameToFocus); } -void MainWindow::SaveTunnelsConfig() { - std::stringstream out; - - for (std::map::iterator it=tunnelConfigs.begin(); it!=tunnelConfigs.end(); ++it) { - const std::string& name = it->first; - TunnelConfig* tunconf = it->second; - tunconf->saveHeaderToStringStream(out); - tunconf->saveToStringStream(out); - tunconf->saveI2CPParametersToStringStream(out); - } - - using namespace std; - - QString backup=tunconfpath+"~"; - if(QFile::exists(backup)) QFile::remove(backup);//TODO handle errors - if(QFile::exists(tunconfpath)) QFile::rename(tunconfpath, backup);//TODO handle errors - ofstream outfile; - outfile.open(tunconfpath.toStdString());//TODO handle errors - outfile << out.str().c_str(); - outfile.close(); - - i2p::client::context.ReloadConfig(); - -} - void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::updated(std::string oldName, TunnelConfig* tunConf) { if(oldName!=tunConf->getName()) { //name has changed @@ -827,7 +806,7 @@ void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::updated(std::string ol if(it!=mainWindow->tunnelConfigs.end())mainWindow->tunnelConfigs.erase(it); mainWindow->tunnelConfigs[tunConf->getName()]=tunConf; } - mainWindow->saveAllConfigs(); + mainWindow->saveAllConfigs(true, tunConf->getName()); } void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::needsDeleting(std::string oldName){ diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 048f44af..bfbc527f 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -62,6 +62,12 @@ #include "widgetlockregistry.h" #include "widgetlock.h" +#include "DelayedSaveManager.h" +#include "DelayedSaveManagerImpl.h" +#include "SaverImpl.h" + +class SaverImpl; + class LogViewerManager; template @@ -373,10 +379,14 @@ using namespace i2p::qt; class Controller; +class DelayedSaveManagerImpl; + class MainWindow : public QMainWindow { Q_OBJECT private: std::shared_ptr logStream; + DelayedSaveManagerImpl* delayedSaveManagerPtr; + DelayedSaveManager::DATA_SERIAL_TYPE dataSerial; public: explicit MainWindow(std::shared_ptr logStream_, QWidget *parent=nullptr); ~MainWindow(); @@ -502,16 +512,16 @@ protected: void initStringBox(ConfigOption option, QLineEdit* lineEdit); NonGUIOptionItem* initNonGUIOption(ConfigOption option); - void loadAllConfigs(); + void loadAllConfigs(SaverImpl* saverPtr); public slots: /** returns false iff not valid items present and save was aborted */ - bool saveAllConfigs(); - void SaveTunnelsConfig(); + bool saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocus=""); void reloadTunnelsConfigAndUI(std::string tunnelNameToFocus); //focus none void reloadTunnelsConfigAndUI() { reloadTunnelsConfigAndUI(""); } + void reloadTunnelsConfigAndUI_QString(const QString tunnelNameToFocus); void addServerTunnelPushButtonReleased(); void addClientTunnelPushButtonReleased(); @@ -578,8 +588,7 @@ private: tunnelConfigs.erase(it); delete tc; } - saveAllConfigs(); - reloadTunnelsConfigAndUI(""); + saveAllConfigs(false); } std::string GenerateNewTunnelName() { @@ -614,8 +623,7 @@ private: destinationPort, sigType); - saveAllConfigs(); - reloadTunnelsConfigAndUI(name); + saveAllConfigs(true, name); } void CreateDefaultServerTunnel() {//TODO dedup default values with ReadTunnelsConfig() and with ClientContext.cpp::ReadTunnels () @@ -651,8 +659,7 @@ private: isUniqueLocal); - saveAllConfigs(); - reloadTunnelsConfigAndUI(name); + saveAllConfigs(true, name); } void ReadTunnelsConfig() //TODO deduplicate the code with ClientContext.cpp::ReadTunnels () @@ -793,7 +800,9 @@ private: TunnelsPageUpdateListenerMainWindowImpl tunnelsPageUpdateListener; - void onLoggingOptionsChange() {} + //void onLoggingOptionsChange() {} + + SaverImpl* saverPtr; }; #endif // MAINWINDOW_H From 0e38e43315836d1797caba87e82d5fc0340bf090 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 10 Mar 2020 23:22:49 +0800 Subject: [PATCH 3593/6300] some qt work. fixed on slow computers; now faster as delayed save is implemented --- .gitignore | 4 ++- daemon/HTTPServer.h | 2 +- qt/i2pd_qt/DelayedSaveManager.h | 2 +- qt/i2pd_qt/DelayedSaveManagerImpl.cpp | 6 ++--- qt/i2pd_qt/DelayedSaveManagerImpl.h | 8 +++--- qt/i2pd_qt/TunnelConfig.h | 7 +++++- qt/i2pd_qt/TunnelPane.cpp | 5 ++++ qt/i2pd_qt/TunnelPane.h | 2 ++ qt/i2pd_qt/mainwindow.cpp | 36 ++++++++++++++++++++++++++- qt/i2pd_qt/mainwindow.h | 3 +++ 10 files changed, 63 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 68aea504..3319fa10 100644 --- a/.gitignore +++ b/.gitignore @@ -258,7 +258,7 @@ build/Makefile # qt -qt/i2pd_qt/*.ui.autosave +qt/i2pd_qt/*.autosave qt/i2pd_qt/*.ui.bk* qt/i2pd_qt/*.ui_* @@ -268,3 +268,5 @@ android/libs/ #various logs *LOGS/ +qt/build-*.sh* + diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index f5ac95fc..abc4fea5 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -89,7 +89,7 @@ namespace http void ShowTransports (std::stringstream& s); void ShowSAMSessions (std::stringstream& s); void ShowI2PTunnels (std::stringstream& s); - void ShowLocalDestination (std::stringstream& s, const std::string& b32); + void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token); } // http } // i2p diff --git a/qt/i2pd_qt/DelayedSaveManager.h b/qt/i2pd_qt/DelayedSaveManager.h index 9a9a997b..b09aa28f 100644 --- a/qt/i2pd_qt/DelayedSaveManager.h +++ b/qt/i2pd_qt/DelayedSaveManager.h @@ -18,7 +18,7 @@ public: virtual bool appExiting()=0; virtual bool needsFocusOnTunnel()=0; - virtual std::string getTunnelNameToFocus()=0; + virtual std::string& getTunnelNameToFocus()=0; }; #endif // DELAYEDSAVEMANAGER_H diff --git a/qt/i2pd_qt/DelayedSaveManagerImpl.cpp b/qt/i2pd_qt/DelayedSaveManagerImpl.cpp index 5588d7e9..f6704c17 100644 --- a/qt/i2pd_qt/DelayedSaveManagerImpl.cpp +++ b/qt/i2pd_qt/DelayedSaveManagerImpl.cpp @@ -21,10 +21,10 @@ bool DelayedSaveManagerImpl::isSaverValid() { return saver != nullptr; } -void DelayedSaveManagerImpl::delayedSave(DATA_SERIAL_TYPE dataSerial, bool focusOnTunnel, std::string tunnelNameToFocus) { +void DelayedSaveManagerImpl::delayedSave(DATA_SERIAL_TYPE dataSerial, bool focusOnTunnel, std::string tunnelNameToFocus_) { if(lastDataSerialSeen==dataSerial)return; this->focusOnTunnel = focusOnTunnel; - this->tunnelNameToFocus = tunnelNameToFocus; + tunnelNameToFocus = tunnelNameToFocus_; lastDataSerialSeen=dataSerial; assert(isSaverValid()); TIMESTAMP_TYPE now = getTime(); @@ -135,6 +135,6 @@ bool DelayedSaveManagerImpl::needsFocusOnTunnel() { return focusOnTunnel; } -std::string DelayedSaveManagerImpl::getTunnelNameToFocus() { +std::string& DelayedSaveManagerImpl::getTunnelNameToFocus() { return tunnelNameToFocus; } diff --git a/qt/i2pd_qt/DelayedSaveManagerImpl.h b/qt/i2pd_qt/DelayedSaveManagerImpl.h index 0d8c7592..cb1f7568 100644 --- a/qt/i2pd_qt/DelayedSaveManagerImpl.h +++ b/qt/i2pd_qt/DelayedSaveManagerImpl.h @@ -60,22 +60,22 @@ public: static TIMESTAMP_TYPE getTime(); bool needsFocusOnTunnel(); - std::string getTunnelNameToFocus(); + std::string& getTunnelNameToFocus(); private: Saver* saver; bool isSaverValid(); - volatile DATA_SERIAL_TYPE lastDataSerialSeen; + DATA_SERIAL_TYPE lastDataSerialSeen; static constexpr TIMESTAMP_TYPE A_VERY_OBSOLETE_TIMESTAMP=DelayedSaveThread::A_VERY_OBSOLETE_TIMESTAMP; TIMESTAMP_TYPE lastSaveStartedTimestamp; - volatile bool exiting; + bool exiting; DelayedSaveThread* thread; void wakeThreadAndJoinThread(); - volatile bool focusOnTunnel; + bool focusOnTunnel; std::string tunnelNameToFocus; }; diff --git a/qt/i2pd_qt/TunnelConfig.h b/qt/i2pd_qt/TunnelConfig.h index 58a1fa0b..059d48b5 100644 --- a/qt/i2pd_qt/TunnelConfig.h +++ b/qt/i2pd_qt/TunnelConfig.h @@ -40,6 +40,9 @@ public: class ClientTunnelConfig; class ServerTunnelConfig; + +class TunnelPane; + class TunnelConfig { /* const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; @@ -54,6 +57,7 @@ class TunnelConfig { */ QString type; std::string name; + TunnelPane* tunnelPane; public: TunnelConfig(std::string name_, QString& type_, I2CPParameters& i2cpParameters_): type(type_), name(name_), i2cpParameters(i2cpParameters_) {} @@ -68,7 +72,8 @@ public: virtual void saveToStringStream(std::stringstream& out)=0; virtual ClientTunnelConfig* asClientTunnelConfig()=0; virtual ServerTunnelConfig* asServerTunnelConfig()=0; - + void setTunnelPane(TunnelPane* tp){this->tunnelPane = tp;} + TunnelPane* getTunnelPane() {return tunnelPane;} private: I2CPParameters i2cpParameters; }; diff --git a/qt/i2pd_qt/TunnelPane.cpp b/qt/i2pd_qt/TunnelPane.cpp index 3813eafc..c64b37ab 100644 --- a/qt/i2pd_qt/TunnelPane.cpp +++ b/qt/i2pd_qt/TunnelPane.cpp @@ -83,6 +83,11 @@ void TunnelPane::setupTunnelPane( retranslateTunnelForm(*this); } +void TunnelPane::deleteWidget() { + //gridLayoutWidget_2->deleteLater(); + tunnelGroupBox->deleteLater(); +} + void TunnelPane::appendControlsForI2CPParameters(I2CPParameters& i2cpParameters, int& gridIndex) { { //number of hops of an inbound tunnel diff --git a/qt/i2pd_qt/TunnelPane.h b/qt/i2pd_qt/TunnelPane.h index a7012810..71331b4e 100644 --- a/qt/i2pd_qt/TunnelPane.h +++ b/qt/i2pd_qt/TunnelPane.h @@ -41,6 +41,8 @@ public: virtual ServerTunnelPane* asServerTunnelPane()=0; virtual ClientTunnelPane* asClientTunnelPane()=0; + void deleteWidget(); + protected: MainWindow* mainWindow; QWidget * wrongInputPane; diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 3b1890c4..97a4ee69 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -664,6 +664,36 @@ void MainWindow::loadAllConfigs(SaverImpl* saverPtr){ //onLoggingOptionsChange(); } + +void MainWindow::layoutTunnels() { + + int height=0; + ui->tunnelsScrollAreaWidgetContents->setGeometry(0,0,0,0); + for(std::map::iterator it = tunnelConfigs.begin(); it != tunnelConfigs.end(); ++it) { + const std::string& name=it->first; + TunnelConfig* tunconf = it->second; + TunnelPane * tunnelPane=tunconf->getTunnelPane(); + if(!tunnelPane)continue; + int h=tunnelPane->height(); + height+=h; + //qDebug() << "tun.height:" << height << "sz:" << tunnelPanes.size(); + //int h=tunnelPane->appendClientTunnelForm(ctc, ui->tunnelsScrollAreaWidgetContents, tunnelPanes.size(), height); + } + //qDebug() << "tun.setting height:" << height; + ui->tunnelsScrollAreaWidgetContents->setGeometry(QRect(0, 0, 621, height)); + /*QList childWidgets = ui->tunnelsScrollAreaWidgetContents->findChildren(); + foreach(QWidget* widget, childWidgets) + widget->show();*/ +} + +void MainWindow::deleteTunnelFromUI(std::string tunnelName, TunnelConfig* cnf) { + TunnelPane* tp = cnf->getTunnelPane(); + if(!tp)return; + tunnelPanes.remove(tp); + tp->deleteWidget(); + layoutTunnels(); +} + /** returns false iff not valid items present and save was aborted */ bool MainWindow::saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocus){ QString cannotSaveSettings = QApplication::tr("Cannot save settings."); @@ -681,6 +711,7 @@ bool MainWindow::saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocu } } delayedSaveManagerPtr->delayedSave(++dataSerial, focusOnTunnel, tunnelNameToFocus); + //onLoggingOptionsChange(); return true; } @@ -725,6 +756,7 @@ void MainWindow::appendTunnelForms(std::string tunnelNameToFocus) { ServerTunnelConfig* stc = tunconf->asServerTunnelConfig(); if(stc){ ServerTunnelPane * tunnelPane=new ServerTunnelPane(&tunnelsPageUpdateListener, stc, ui->wrongInputLabel, ui->wrongInputLabel, this); + tunconf->setTunnelPane(tunnelPane); int h=tunnelPane->appendServerTunnelForm(stc, ui->tunnelsScrollAreaWidgetContents, tunnelPanes.size(), height); height+=h; //qDebug() << "tun.height:" << height << "sz:" << tunnelPanes.size(); @@ -738,6 +770,7 @@ void MainWindow::appendTunnelForms(std::string tunnelNameToFocus) { ClientTunnelConfig* ctc = tunconf->asClientTunnelConfig(); if(ctc){ ClientTunnelPane * tunnelPane=new ClientTunnelPane(&tunnelsPageUpdateListener, ctc, ui->wrongInputLabel, ui->wrongInputLabel, this); + tunconf->setTunnelPane(tunnelPane); int h=tunnelPane->appendClientTunnelForm(ctc, ui->tunnelsScrollAreaWidgetContents, tunnelPanes.size(), height); height+=h; //qDebug() << "tun.height:" << height << "sz:" << tunnelPanes.size(); @@ -854,7 +887,8 @@ void MainWindow::anchorClickedHandler(const QUrl & link) { pageWithBackButton->show(); textBrowser->hide(); std::stringstream s; - i2p::http::ShowLocalDestination(s,str.toStdString()); + std::string strstd = str.toStdString(); + i2p::http::ShowLocalDestination(s,strstd,0); childTextBrowser->setHtml(QString::fromStdString(s.str())); } } diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index bfbc527f..91c99122 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -513,6 +513,7 @@ protected: NonGUIOptionItem* initNonGUIOption(ConfigOption option); void loadAllConfigs(SaverImpl* saverPtr); + void layoutTunnels(); public slots: /** returns false iff not valid items present and save was aborted */ @@ -540,6 +541,7 @@ private: void appendTunnelForms(std::string tunnelNameToFocus); void deleteTunnelForms(); + void deleteTunnelFromUI(std::string tunnelName, TunnelConfig* cnf); template std::string GetI2CPOption (const Section& section, const std::string& name, const Type& value) const @@ -585,6 +587,7 @@ private: std::map::const_iterator it=tunnelConfigs.find(name); if(it!=tunnelConfigs.end()){ TunnelConfig* tc=it->second; + deleteTunnelFromUI(name, tc); tunnelConfigs.erase(it); delete tc; } From dd8200e8b0a9f2f7bf56a95db2ccacace81eb572 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 12 Mar 2020 03:50:21 +0300 Subject: [PATCH 3594/6300] cleanup websocks Signed-off-by: R4SAS --- build/CMakeLists.txt | 1 - filelist.mk | 2 +- libi2pd_client/ClientContext.cpp | 7 +--- libi2pd_client/WebSocks.cpp | 72 -------------------------------- libi2pd_client/WebSocks.h | 34 --------------- qt/i2pd_qt/i2pd_qt.pro | 2 - 6 files changed, 3 insertions(+), 115 deletions(-) delete mode 100644 libi2pd_client/WebSocks.cpp delete mode 100644 libi2pd_client/WebSocks.h diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 8b3553d2..2bdfb396 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -117,7 +117,6 @@ set (CLIENT_SRC "${LIBI2PD_CLIENT_SRC_DIR}/SOCKS.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/HTTPProxy.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/I2CP.cpp" - "${LIBI2PD_CLIENT_SRC_DIR}/WebSocks.cpp" ) add_library(libi2pdclient ${CLIENT_SRC}) diff --git a/filelist.mk b/filelist.mk index 72773975..7d13fb2f 100644 --- a/filelist.mk +++ b/filelist.mk @@ -11,7 +11,7 @@ LIB_SRC = $(wildcard $(LIB_SRC_DIR)/*.cpp) #LIB_CLIENT_SRC = \ # AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp MatchedDestination.cpp \ -# SAM.cpp SOCKS.cpp HTTPProxy.cpp I2CP.cpp WebSocks.cpp +# SAM.cpp SOCKS.cpp HTTPProxy.cpp I2CP.cpp LIB_CLIENT_SRC = $(wildcard $(LIB_CLIENT_SRC_DIR)/*.cpp) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index d72f40a8..722fb242 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -9,7 +9,6 @@ #include "util.h" #include "ClientContext.h" #include "SOCKS.h" -#include "WebSocks.h" #include "MatchedDestination.h" namespace i2p @@ -598,10 +597,8 @@ namespace client } else if (type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS) { - // websocks proxy - auto tun = std::make_shared(address, port, localDestination); - clientTunnel = tun; - clientEndpoint = tun->GetLocalEndpoint(); + LogPrint(eLogError, "Clients: I2P Client tunnel websocks is deprecated"); + continue; } else { diff --git a/libi2pd_client/WebSocks.cpp b/libi2pd_client/WebSocks.cpp deleted file mode 100644 index a3e8c1bf..00000000 --- a/libi2pd_client/WebSocks.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "WebSocks.h" -#include "Log.h" -#include - -namespace i2p -{ -namespace client -{ - class WebSocksImpl - { - public: - WebSocksImpl(const std::string & addr, int port) : m_Addr(addr), m_Port(port) - { - } - - ~WebSocksImpl() - { - } - - void Start() - { - LogPrint(eLogInfo, "[Tunnels] starting websocks tunnel at %s:%d is rejected: WebSockets is deprecated", m_Addr, m_Port); - } - - void Stop() - { - } - - void InitializeDestination(WebSocks * parent) - { - } - - boost::asio::ip::tcp::endpoint GetLocalEndpoint() - { - return boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(m_Addr), m_Port); - } - - std::string m_Addr; - int m_Port; - - }; -} -} - -namespace i2p -{ -namespace client -{ - WebSocks::WebSocks(const std::string & addr, int port, std::shared_ptr localDestination) : m_Impl(new WebSocksImpl(addr, port)) - { - m_Impl->InitializeDestination(this); - } - WebSocks::~WebSocks() { delete m_Impl; } - - void WebSocks::Start() - { - m_Impl->Start(); - GetLocalDestination()->Start(); - } - - boost::asio::ip::tcp::endpoint WebSocks::GetLocalEndpoint() const - { - return m_Impl->GetLocalEndpoint(); - } - - void WebSocks::Stop() - { - m_Impl->Stop(); - GetLocalDestination()->Stop(); - } -} -} diff --git a/libi2pd_client/WebSocks.h b/libi2pd_client/WebSocks.h deleted file mode 100644 index 2314659f..00000000 --- a/libi2pd_client/WebSocks.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef WEBSOCKS_H_ -#define WEBSOCKS_H_ -#include -#include -#include "I2PService.h" -#include "Destination.h" - -namespace i2p -{ -namespace client -{ - - class WebSocksImpl; - - /** @brief websocket socks proxy server */ - class WebSocks : public i2p::client::I2PService - { - public: - WebSocks(const std::string & addr, int port, std::shared_ptr localDestination); - ~WebSocks(); - - void Start(); - void Stop(); - - boost::asio::ip::tcp::endpoint GetLocalEndpoint() const; - - const char * GetName() { return "WebSOCKS Proxy"; } - - private: - WebSocksImpl * m_Impl; - }; -} -} -#endif diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index aa87a765..6eec3a16 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -73,7 +73,6 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \ ../../libi2pd_client/MatchedDestination.cpp \ ../../libi2pd_client/SAM.cpp \ ../../libi2pd_client/SOCKS.cpp \ - ../../libi2pd_client/WebSocks.cpp \ ../../daemon/Daemon.cpp \ ../../daemon/HTTPServer.cpp \ ../../daemon/I2PControl.cpp \ @@ -162,7 +161,6 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../libi2pd_client/MatchedDestination.h \ ../../libi2pd_client/SAM.h \ ../../libi2pd_client/SOCKS.h \ - ../../libi2pd_client/WebSocks.h \ ../../daemon/Daemon.h \ ../../daemon/HTTPServer.h \ ../../daemon/I2PControl.h \ From 45145fa50af40a5270223930cd92d33df452db82 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 14 Mar 2020 09:33:48 -0400 Subject: [PATCH 3595/6300] add ECIESX25519AEADRatchet session to delivery status --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 1 + libi2pd/Garlic.cpp | 2 +- libi2pd/Garlic.h | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 5f2eb5c8..4705c708 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -477,6 +477,7 @@ namespace garlic SetLeaseSetUpdateStatus (eLeaseSetSubmitted); SetLeaseSetUpdateMsgID (leaseSet->GetMsgID ()); SetLeaseSetSubmissionTime (ts); + GetOwner ()->DeliveryStatusSent (shared_from_this (), leaseSet->GetMsgID ()); } uint8_t paddingSize; RAND_bytes (&paddingSize, 1); diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 9ac5540f..99b737b1 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -783,7 +783,7 @@ namespace garlic m_DeliveryStatusSessions.erase (msgID); } - void GarlicDestination::DeliveryStatusSent (ElGamalAESSessionPtr session, uint32_t msgID) + void GarlicDestination::DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID) { std::unique_lock l(m_DeliveryStatusSessionsMutex); m_DeliveryStatusSessions[msgID] = session; diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index be0ac117..396a38fe 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -226,7 +226,7 @@ namespace garlic void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread - void DeliveryStatusSent (ElGamalAESSessionPtr session, uint32_t msgID); + void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); void AddECIESx25519SessionTag (int index, uint64_t tag, ECIESX25519AEADRatchetSessionPtr session); void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); From b5bc05ac2b2ed9375d689207190af89b5344c37d Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 14 Mar 2020 16:35:34 -0400 Subject: [PATCH 3596/6300] delete unconfirmed LeaseSet and DeliveryStatus --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 6 ++++++ libi2pd/ECIESX25519AEADRatchetSession.h | 2 +- libi2pd/Garlic.cpp | 19 ++++++++++++------- libi2pd/Garlic.h | 3 ++- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 4705c708..8c293f15 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -571,6 +571,12 @@ namespace garlic } m_NumReceiveTags += numTags; } + + bool ECIESX25519AEADRatchetSession::CheckExpired (uint64_t ts) + { + CleanupUnconfirmedLeaseSet (ts); + return ts > m_LastActivityTimestamp + ECIESX25519_EXPIRATION_TIMEOUT; + } } } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 4d273c13..db9807a5 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -85,7 +85,7 @@ namespace garlic if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest)); } - bool IsExpired (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_EXPIRATION_TIMEOUT; } + bool CheckExpired (uint64_t ts); // true is expired bool CanBeRestarted (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_RESTART_TIMEOUT; } private: diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 99b737b1..97f80b41 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -70,6 +70,16 @@ namespace garlic return false; } + void GarlicRoutingSession::CleanupUnconfirmedLeaseSet (uint64_t ts) + { + if (m_LeaseSetUpdateMsgID && ts*1000LL > m_LeaseSetSubmissionTime + LEASET_CONFIRMATION_TIMEOUT) + { + if (GetOwner ()) + GetOwner ()->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID); + m_LeaseSetUpdateMsgID = 0; + } + } + std::shared_ptr GarlicRoutingSession::CreateEncryptedDeliveryStatusMsg (uint32_t msgID) { auto msg = CreateDeliveryStatusMsg (msgID); @@ -390,12 +400,7 @@ namespace garlic ++it; } CleanupUnconfirmedTags (); - if (GetLeaseSetUpdateMsgID () && ts*1000LL > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT) - { - if (GetOwner ()) - GetOwner ()->RemoveDeliveryStatusSession (GetLeaseSetUpdateMsgID ()); - SetLeaseSetUpdateMsgID (0); - } + CleanupUnconfirmedLeaseSet (ts); return !m_SessionTags.empty () || !m_UnconfirmedTagsMsgs.empty (); } @@ -767,7 +772,7 @@ namespace garlic for (auto it = m_ECIESx25519Sessions.begin (); it != m_ECIESx25519Sessions.end ();) { - if (it->second->IsExpired (ts)) + if (it->second->CheckExpired (ts)) { it->second->SetOwner (nullptr); it = m_ECIESx25519Sessions.erase (it); diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 396a38fe..06da679d 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -113,7 +113,8 @@ namespace garlic bool IsLeaseSetNonConfirmed () const { return m_LeaseSetUpdateStatus == eLeaseSetSubmitted; }; bool IsLeaseSetUpdated () const { return m_LeaseSetUpdateStatus == eLeaseSetUpdated; }; uint64_t GetLeaseSetSubmissionTime () const { return m_LeaseSetSubmissionTime; } - + void CleanupUnconfirmedLeaseSet (uint64_t ts); + std::shared_ptr GetSharedRoutingPath (); void SetSharedRoutingPath (std::shared_ptr path); From 5da92437a136cf5938062c5ec4ceaf28832c5aff Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 16 Mar 2020 16:41:07 -0400 Subject: [PATCH 3597/6300] set msg type for deliverystatus --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 8c293f15..8d300a16 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -552,6 +552,7 @@ namespace garlic LogPrint (eLogError, "Garlic: No inbound tunnels in the pool for DeliveryStatus"); return 0; } + *buf = msg->GetTypeID (); // I2NP msg type htobe32buf (buf + 1, msg->GetMsgID ()); // msgID htobe32buf (buf + 5, msg->GetExpiration ()/1000); // expiration in seconds memcpy (buf + 9, msg->GetPayload (), msg->GetPayloadLength ()); From f3b0e57a5440586c3f7de2ed97b5b742f52c995c Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 Mar 2020 18:03:03 -0400 Subject: [PATCH 3598/6300] publish multiple encryption keys --- libi2pd/Destination.cpp | 5 +++-- libi2pd/LeaseSet.cpp | 18 ++++++++++++------ libi2pd/LeaseSet.h | 9 ++++++++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index d4620ed9..60f5062c 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1149,10 +1149,11 @@ namespace client else { // standard LS2 (type 3) first - auto keyLen = m_Decryptor ? m_Decryptor->GetPublicKeyLen () : 256; + uint16_t keyLen = m_Decryptor ? m_Decryptor->GetPublicKeyLen () : 256; bool isPublishedEncrypted = GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; auto ls2 = std::make_shared (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2, - m_Keys, m_EncryptionKeyType, keyLen, m_EncryptionPublicKey, tunnels, IsPublic (), isPublishedEncrypted); + m_Keys, i2p::data::LocalLeaseSet2::KeySections { {m_EncryptionKeyType, keyLen, m_EncryptionPublicKey} }, + tunnels, IsPublic (), isPublishedEncrypted); if (isPublishedEncrypted) // encrypt if type 5 ls2 = std::make_shared (ls2, m_Keys, GetAuthType (), m_AuthKeys); leaseSet = ls2; diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index 8d1b9524..e2da1d21 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -748,7 +748,7 @@ namespace data } LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, - uint16_t keyType, uint16_t keyLen, const uint8_t * encryptionPublicKey, + const KeySections& encryptionKeys, std::vector > tunnels, bool isPublic, bool isPublishedEncrypted): LocalLeaseSet (keys.GetPublic (), nullptr, 0) @@ -757,8 +757,11 @@ namespace data // assume standard LS2 int num = tunnels.size (); if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES; + size_t keySectionsLen = 0; + for (const auto& it: encryptionKeys) + keySectionsLen += 2/*key type*/ + 2/*key len*/ + it.keyLen/*key*/; m_BufferLen = identity->GetFullLen () + 4/*published*/ + 2/*expires*/ + 2/*flag*/ + 2/*properties len*/ + - 1/*num keys*/ + 2/*key type*/ + 2/*key len*/ + keyLen/*key*/ + 1/*num leases*/ + num*LEASE2_SIZE + keys.GetSignatureLen (); + 1/*num keys*/ + keySectionsLen + 1/*num leases*/ + num*LEASE2_SIZE + keys.GetSignatureLen (); uint16_t flags = 0; if (keys.IsOfflineSignature ()) { @@ -789,10 +792,13 @@ namespace data } htobe16buf (m_Buffer + offset, 0); offset += 2; // properties len // keys - m_Buffer[offset] = 1; offset++; // 1 key - htobe16buf (m_Buffer + offset, keyType); offset += 2; // key type - htobe16buf (m_Buffer + offset, keyLen); offset += 2; // key len - memcpy (m_Buffer + offset, encryptionPublicKey, keyLen); offset += keyLen; // key + m_Buffer[offset] = encryptionKeys.size (); offset++; // 1 key + for (const auto& it: encryptionKeys) + { + htobe16buf (m_Buffer + offset, it.keyType); offset += 2; // key type + htobe16buf (m_Buffer + offset, it.keyLen); offset += 2; // key len + memcpy (m_Buffer + offset, it.encryptionPublicKey, it.keyLen); offset += it.keyLen; // key + } // leases uint32_t expirationTime = 0; // in seconds m_Buffer[offset] = num; offset++; // num leases diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index a51570f7..791d77e3 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -231,8 +231,15 @@ namespace data { public: + struct KeySection + { + uint16_t keyType, keyLen; + const uint8_t * encryptionPublicKey; + }; + typedef std::vector KeySections; + LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, - uint16_t keyType, uint16_t keyLen, const uint8_t * encryptionPublicKey, + const KeySections& encryptionKeys, std::vector > tunnels, bool isPublic, bool isPublishedEncrypted = false); LocalLeaseSet2 (uint8_t storeType, std::shared_ptr identity, const uint8_t * buf, size_t len); // from I2CP From 2fcaa7d260fa9b657e9e1e24f00a03f964c7febf Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 19 Mar 2020 02:26:55 +0300 Subject: [PATCH 3599/6300] [webconsole] rework spoilers; print tags, leases, router info in table Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 240 ++++++++++++++++++++++++------------------ 1 file changed, 137 insertions(+), 103 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 36e26785..a7bd693f 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -54,7 +54,7 @@ namespace http { " body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: #FAFAFA; color: #103456; }\r\n" " a, .slide label { text-decoration: none; color: #894C84; }\r\n" " a:hover, .slide label:hover { color: #FAFAFA; background: #894C84; }\r\n" - " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none; color: initial;}\r\n" + " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none; color: initial; width: 1.5em;}\r\n" " .header { font-size: 2.5em; text-align: center; margin: 1.5em 0; color: #894C84; }\r\n" " .wrapper { margin: 0 auto; padding: 1em; max-width: 60em; }\r\n" " .left { float: left; position: absolute; }\r\n" @@ -64,12 +64,13 @@ namespace http { " .tunnel.failed { color: #D33F3F; }\r\n" " .tunnel.building { color: #434343; }\r\n" " caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n" - " table { width: 100%; border-collapse: collapse; text-align: center; }\r\n" - " .slide p, .slide [type='checkbox'] { display: none; }\r\n" - " .slide [type='checkbox']:checked ~ p { display: block; margin-top: 0; padding: 0; }\r\n" + " table { display: table; border-collapse: collapse; text-align: center; }\r\n" + " table.extaddr { text-align: left; }\r\n table.services { width: 100%; }" + " .streamdest { width: 120px; max-width: 240px; overflow: hidden; text-overflow: ellipsis;}\r\n" + " .slide div.content, .slide [type='checkbox'] { display: none; }\r\n" + " .slide [type='checkbox']:checked ~ div.content { display: block; margin-top: 0; padding: 0; }\r\n" " .disabled:after { color: #D33F3F; content: \"Disabled\" }\r\n" " .enabled:after { color: #56B734; content: \"Enabled\" }\r\n" - " .streamdest { width: 120px; max-width: 240px; overflow: hidden; text-overflow: ellipsis;}\r\n" "\r\n"; const char HTTP_PAGE_TUNNELS[] = "tunnels"; @@ -181,8 +182,10 @@ namespace http { "

    \r\n" " Main page
    \r\n
    \r\n" " Router commands
    \r\n" - " Local destinations
    \r\n" - " LeaseSets
    \r\n" + " Local destinations
    \r\n"; + if (i2p::context.IsFloodfill ()) + s << " LeaseSets
    \r\n"; + s << " Tunnels
    \r\n" " Transit tunnels
    \r\n" " Transports
    \r\n" @@ -234,11 +237,8 @@ namespace http { } s << "
    \r\n"; #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) - if (auto remains = Daemon.gracefulShutdownInterval) { - s << "Stopping in: "; - s << remains << " seconds"; - s << "
    \r\n"; - } + if (auto remains = Daemon.gracefulShutdownInterval) + s << "Stopping in: " << remains << " seconds
    \r\n"; #endif auto family = i2p::context.GetFamily (); if (family.length () > 0) @@ -256,45 +256,50 @@ namespace http { s << "Data path: " << i2p::fs::GetDataDir() << "
    \r\n"; s << "
    "; if((outputFormat==OutputFormatEnum::forWebConsole)||!includeHiddenContent) { - s << "\r\n\r\n

    \r\n"; + s << "\r\n\r\n

    \r\n"; } if(includeHiddenContent) { s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
    \r\n"; - s << "Router Family: " << i2p::context.GetRouterInfo().GetProperty("family") << "
    \r\n"; + if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) + s << "Router Family: " << i2p::context.GetRouterInfo().GetProperty("family") << "
    \r\n"; s << "Router Caps: " << i2p::context.GetRouterInfo().GetProperty("caps") << "
    \r\n"; - s << "Our external address:" << "
    \r\n" ; + s << "Our external address:" << "
    \r\n\r\n"; for (const auto& address : i2p::context.GetRouterInfo().GetAddresses()) { + s << "\r\n"; if (address->IsNTCP2 () && !address->IsPublishedNTCP2 ()) { - s << "NTCP2"; + s << "\r\n\r\n"; continue; } switch (address->transportStyle) { case i2p::data::RouterInfo::eTransportNTCP: { - s << "NTCP"; + s << "\r\n"; break; } case i2p::data::RouterInfo::eTransportSSU: + { + s << "\r\n"; + break; + } default: - s << "Unknown  "; + s << "\r\n"; } - s << address->host.to_string() << ":" << address->port << "
    \r\n"; + s << "\r\n\r\n"; } + s << "
    NTCP2"; if (address->host.is_v6 ()) s << "v6"; - s << "   supported
    \r\n"; + s << "
    supported
    NTCP"; if (address->IsPublishedNTCP2 ()) s << "2"; if (address->host.is_v6 ()) s << "v6"; - s << "  "; + s << "SSU"; if (address->host.is_v6 ()) - s << "SSUv6     "; - else - s << "SSU     "; - break; + s << "v6"; + s << "Unknown" << address->host.to_string() << ":" << address->port << "
    \r\n"; } - s << "

    \r\n
    \r\n"; + s << "\r\n
    \r\n
    \r\n"; if(outputFormat==OutputFormatEnum::forQtUi) { s << "
    "; } @@ -310,15 +315,15 @@ namespace http { s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
    \r\n
    \r\n"; if(outputFormat==OutputFormatEnum::forWebConsole) { - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); - s << "\r\n"; - s << "
    Services
    ServiceState
    " << "HTTP Proxy" << "
    " << "SOCKS Proxy" << "
    " << "BOB" << "
    " << "SAM" << "
    " << "I2CP" << "
    " << "I2PControl" << "
    \r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "
    Services
    " << "HTTP Proxy" << "
    " << "SOCKS Proxy" << "
    " << "BOB" << "
    " << "SAM" << "
    " << "I2CP" << "
    " << "I2PControl" << "
    \r\n"; } } @@ -358,19 +363,20 @@ namespace http { if (dest->IsEncryptedLeaseSet ()) { i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); - s << "
    \r\n\r\n

    \r\n"; + s << "

    \r\n\r\n
    \r\n"; s << blinded.ToB33 () << ".b32.i2p
    \r\n"; - s << "

    \r\n
    \r\n"; + s << "
    \r\n
    \r\n"; } if(dest->GetNumRemoteLeaseSets()) { - s << "
    \r\n\r\n

    \r\n"; + s << "

    \r\n\r\n
    \r\n"; for(auto& it: dest->GetLeaseSets ()) - s << it.first.ToBase32 () << " " << (int)it.second->GetStoreType () << "
    \r\n"; - s << "

    \r\n\r\n"; + s << "\r\n"; + s << "
    AddressTypeEncType
    " << it.first.ToBase32 () << "" << (int)it.second->GetStoreType () << "" << (int)it.second->GetEncryptionType () <<"
    \r\n
    \r\n
    \r\n
    \r\n"; } else - s << "LeaseSets: 0
    \r\n"; + s << "LeaseSets: 0
    \r\n
    \r\n"; auto pool = dest->GetTunnelPool (); if (pool) { @@ -395,10 +401,11 @@ namespace http { if (!dest->GetSessions ().empty ()) { std::stringstream tmp_s; uint32_t out_tags = 0; for (const auto& it: dest->GetSessions ()) { - tmp_s << i2p::client::context.GetAddressBook ().ToAddress(it.first) << " " << it.second->GetNumOutgoingTags () << "
    \r\n"; + tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "" << it.second->GetNumOutgoingTags () << "\r\n"; out_tags = out_tags + it.second->GetNumOutgoingTags (); } - s << "
    \r\n\r\n

    \r\n" << tmp_s.str () << "

    \r\n
    \r\n"; + s << "
    \r\n\r\n" + << "
    \r\n\r\n" << tmp_s.str () << "
    DestinationAmount
    \r\n
    \r\n
    \r\n"; } else s << "Outgoing: 0
    \r\n"; s << "
    \r\n"; @@ -473,46 +480,57 @@ namespace http { void ShowLeasesSets(std::stringstream& s) { - s << "LeaseSets:
    \r\n
    \r\n"; - int counter = 1; - // for each lease set - i2p::data::netdb.VisitLeaseSets( - [&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) - { - // create copy of lease set so we extract leases - auto storeType = leaseSet->GetStoreType (); - std::unique_ptr ls; - if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET) - ls.reset (new i2p::data::LeaseSet (leaseSet->GetBuffer(), leaseSet->GetBufferLen())); - else - ls.reset (new i2p::data::LeaseSet2 (storeType, leaseSet->GetBuffer(), leaseSet->GetBufferLen())); - if (!ls) return; - s << "
    \r\n"; - if (!ls->IsValid()) - s << "
    !! Invalid !!
    \r\n"; - s << "
    \r\n"; - s << "\r\n

    \r\n"; - s << "Store type: " << (int)storeType << "
    \r\n"; - s << "Expires: " << ConvertTime(ls->GetExpirationTime()) << "
    \r\n"; - if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) - { - // leases information is available - auto leases = ls->GetNonExpiredLeases(); - s << "Non Expired Leases: " << leases.size() << "
    \r\n"; - for ( auto & l : leases ) + if (i2p::data::netdb.GetNumLeaseSets ()) + { + s << "LeaseSets:
    \r\n
    \r\n"; + int counter = 1; + // for each lease set + i2p::data::netdb.VisitLeaseSets( + [&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) + { + // create copy of lease set so we extract leases + auto storeType = leaseSet->GetStoreType (); + std::unique_ptr ls; + if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET) + ls.reset (new i2p::data::LeaseSet (leaseSet->GetBuffer(), leaseSet->GetBufferLen())); + else + ls.reset (new i2p::data::LeaseSet2 (storeType, leaseSet->GetBuffer(), leaseSet->GetBufferLen())); + if (!ls) return; + s << "

    \r\n"; + if (!ls->IsValid()) + s << "
    !! Invalid !!
    \r\n"; + s << "
    \r\n"; + s << "\r\n
    \r\n"; + s << "Store type: " << (int)storeType << "
    \r\n"; + s << "Expires: " << ConvertTime(ls->GetExpirationTime()) << "
    \r\n"; + if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) { - s << "Gateway: " << l->tunnelGateway.ToBase64() << "
    \r\n"; - s << "TunnelID: " << l->tunnelID << "
    \r\n"; - s << "EndDate: " << ConvertTime(l->endDate) << "
    \r\n"; + // leases information is available + auto leases = ls->GetNonExpiredLeases(); + s << "Non Expired Leases: " << leases.size() << "
    \r\n"; + for ( auto & l : leases ) + { + s << "Gateway: " << l->tunnelGateway.ToBase64() << "
    \r\n"; + s << "TunnelID: " << l->tunnelID << "
    \r\n"; + s << "EndDate: " << ConvertTime(l->endDate) << "
    \r\n"; + } } - } - s << "

    \r\n
    \r\n
    \r\n"; - } - ); - // end for each lease set + s << "
    \r\n
    \r\n
    \r\n"; + } + ); + // end for each lease set + } + else if (!i2p::context.IsFloodfill ()) + { + s << "LeaseSets: not floodfill.
    \r\n"; + } + else + { + s << "LeaseSets: 0
    \r\n"; + } } void ShowTunnels (std::stringstream& s) @@ -574,16 +592,23 @@ namespace http { void ShowTransitTunnels (std::stringstream& s) { - s << "Transit tunnels:
    \r\n
    \r\n"; - for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) + if(i2p::tunnel::tunnels.CountTransitTunnels()) { - if (std::dynamic_pointer_cast(it)) - s << it->GetTunnelID () << " ⇒ "; - else if (std::dynamic_pointer_cast(it)) - s << " ⇒ " << it->GetTunnelID (); - else - s << " ⇒ " << it->GetTunnelID () << " ⇒ "; - s << " " << it->GetNumTransmittedBytes () << "
    \r\n"; + s << "Transit tunnels:
    \r\n
    \r\n"; + for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) + { + if (std::dynamic_pointer_cast(it)) + s << it->GetTunnelID () << " ⇒ "; + else if (std::dynamic_pointer_cast(it)) + s << " ⇒ " << it->GetTunnelID (); + else + s << " ⇒ " << it->GetTunnelID () << " ⇒ "; + s << " " << it->GetNumTransmittedBytes () << "
    \r\n"; + } + } + else + { + s << "Transit tunnels: no transit tunnels currently built.
    \r\n"; } } @@ -617,13 +642,15 @@ namespace http { } if (!tmp_s.str ().empty ()) { - s << "
    \r\n\r\n

    "; - s << tmp_s.str () << "

    \r\n
    \r\n"; + s << "
    \r\n\r\n
    " + << tmp_s.str () << "
    \r\n
    \r\n"; } if (!tmp_s6.str ().empty ()) { - s << "
    \r\n\r\n

    "; - s << tmp_s6.str () << "

    \r\n
    \r\n"; + s << "
    \r\n\r\n
    " + << tmp_s6.str () << "
    \r\n
    \r\n"; } } @@ -650,7 +677,7 @@ namespace http { auto sessions = ssuServer->GetSessions (); if (!sessions.empty ()) { - s << "
    \r\n\r\n

    "; + s << "

    \r\n\r\n
    "; for (const auto& it: sessions) { auto endpoint = it.second->GetRemoteEndpoint (); @@ -662,12 +689,12 @@ namespace http { s << " [itag:" << it.second->GetRelayTag () << "]"; s << "
    \r\n" << std::endl; } - s << "

    \r\n
    \r\n"; + s << "
    \r\n
    \r\n"; } auto sessions6 = ssuServer->GetSessionsV6 (); if (!sessions6.empty ()) { - s << "
    \r\n\r\n

    "; + s << "

    \r\n\r\n
    "; for (const auto& it: sessions6) { auto endpoint = it.second->GetRemoteEndpoint (); @@ -679,7 +706,7 @@ namespace http { s << " [itag:" << it.second->GetRelayTag () << "]"; s << "
    \r\n" << std::endl; } - s << "

    \r\n
    \r\n"; + s << "
    \r\n
    \r\n"; } } } @@ -688,17 +715,24 @@ namespace http { { std::string webroot; i2p::config::GetOption("http.webroot", webroot); auto sam = i2p::client::context.GetSAMBridge (); - if (!sam) { + if (!sam) + { ShowError(s, "SAM disabled"); return; } - s << "SAM Sessions:
    \r\n
    \r\n"; - for (auto& it: sam->GetSessions ()) + + if(sam->GetSessions ().size ()) { - auto& name = it.second->localDestination->GetNickname (); - s << ""; - s << name << " (" << it.first << ")
    \r\n" << std::endl; + s << "SAM Sessions:
    \r\n
    \r\n"; + for (auto& it: sam->GetSessions ()) + { + auto& name = it.second->localDestination->GetNickname (); + s << ""; + s << name << " (" << it.first << ")
    \r\n" << std::endl; + } } + else + s << "SAM Sessions: no sessions currently running.
    \r\n"; } static void ShowSAMSession (std::stringstream& s, const std::string& id) From 22497080973e25e8b9b5fd93b86af44f795a18e3 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 19 Mar 2020 02:34:45 +0300 Subject: [PATCH 3600/6300] [webconsole] remove excess tag Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index a7bd693f..5eac08a9 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -299,7 +299,7 @@ namespace http { } s << "\r\n"; } - s << "\r\n
    \r\n
    \r\n"; + s << "
    \r\n\r\n"; if(outputFormat==OutputFormatEnum::forQtUi) { s << "
    "; } From 3ca17fdc039f46996038d5bb501539a728e06f8a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Mar 2020 18:33:42 -0400 Subject: [PATCH 3601/6300] support multiple encryption keys --- libi2pd/Destination.cpp | 11 +++++++++-- libi2pd/Destination.h | 9 +++++---- libi2pd/ECIESX25519AEADRatchetSession.cpp | 4 ++-- libi2pd/Garlic.cpp | 4 ++-- libi2pd/Identity.h | 4 ++-- libi2pd_client/I2CP.h | 3 ++- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 60f5062c..46d3fc57 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -388,7 +388,7 @@ namespace client if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET) leaseSet = std::make_shared (buf + offset, len - offset); // LeaseSet else - leaseSet = std::make_shared (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetEncryptionType ()); // LeaseSet2 + leaseSet = std::make_shared (buf[DATABASE_STORE_TYPE_OFFSET], buf + offset, len - offset, true, GetPreferredCryptoType () ); // LeaseSet2 if (leaseSet->IsValid () && leaseSet->GetIdentHash () == key) { if (leaseSet->GetIdentHash () != GetIdentHash ()) @@ -412,7 +412,7 @@ namespace client auto it2 = m_LeaseSetRequests.find (key); if (it2 != m_LeaseSetRequests.end () && it2->second->requestedBlindedKey) { - auto ls2 = std::make_shared (buf + offset, len - offset, it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? *m_LeaseSetPrivKey : nullptr, GetEncryptionType ()); + auto ls2 = std::make_shared (buf + offset, len - offset, it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? *m_LeaseSetPrivKey : nullptr, GetPreferredCryptoType ()); if (ls2->IsValid ()) { m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key @@ -822,6 +822,13 @@ namespace client } } + i2p::data::CryptoKeyType LeaseSetDestination::GetPreferredCryptoType () const + { + if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET)) + return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET; + return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; + } + ClientDestination::ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): LeaseSetDestination (service, isPublic, params), diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 23ed5188..f26200a0 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -161,6 +161,7 @@ namespace client void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleCleanupTimer (const boost::system::error_code& ecode); void CleanupRemoteLeaseSets (); + i2p::data::CryptoKeyType GetPreferredCryptoType () const; private: @@ -232,14 +233,14 @@ namespace client int GetStreamingAckDelay () const { return m_StreamingAckDelay; } // datagram - i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; - i2p::datagram::DatagramDestination * CreateDatagramDestination (); + i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; + i2p::datagram::DatagramDestination * CreateDatagramDestination (); // implements LocalDestination bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const; std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; - i2p::data::CryptoKeyType GetEncryptionType () const { return m_EncryptionKeyType; }; - const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; + bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { return m_EncryptionKeyType == keyType; }; + const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const { return m_EncryptionPublicKey; }; protected: diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 8d300a16..2cea2172 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -126,7 +126,7 @@ namespace garlic if (!GetOwner ()) return false; // we are Bob // KDF1 - MixHash (GetOwner ()->GetEncryptionPublicKey (), 32); // h = SHA256(h || bpk) + MixHash (GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET), 32); // h = SHA256(h || bpk) if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk)) { @@ -235,7 +235,7 @@ namespace garlic // encrypt static key section uint8_t nonce[12]; CreateNonce (0, nonce); - if (!i2p::crypto::AEADChaCha20Poly1305 (GetOwner ()->GetEncryptionPublicKey (), 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET), 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Static section AEAD encryption failed "); return false; diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 97f80b41..629e0d76 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -488,7 +488,7 @@ namespace garlic else { // tag not found. Handle depending on encryption type - if (GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET) + if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET)) { HandleECIESx25519 (buf, length); return; @@ -680,7 +680,7 @@ namespace garlic std::shared_ptr destination, bool attachLeaseSet) { if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET && - GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET) + SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET)) { ECIESX25519AEADRatchetSessionPtr session; uint8_t staticKey[32]; diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index f217f3c5..9c78f552 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -228,8 +228,8 @@ namespace data virtual std::shared_ptr GetIdentity () const = 0; const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; - virtual CryptoKeyType GetEncryptionType () const { return GetIdentity ()->GetCryptoKeyType (); }; // override for LeaseSet - virtual const uint8_t * GetEncryptionPublicKey () const { return GetIdentity ()->GetEncryptionPublicKey (); }; // override for LeaseSet + virtual bool SupportsEncryptionType (CryptoKeyType keyType) const { return GetIdentity ()->GetCryptoKeyType () == keyType; }; // override for LeaseSet + virtual const uint8_t * GetEncryptionPublicKey (CryptoKeyType keyType) const { return GetIdentity ()->GetEncryptionPublicKey (); }; // override for LeaseSet }; } } diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 68b4415a..848378e0 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -80,7 +80,8 @@ namespace client // implements LocalDestination bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const; - i2p::data::CryptoKeyType GetEncryptionType () const { return m_EncryptionKeyType; }; + bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { return m_EncryptionKeyType == keyType; }; + // TODO: implement GetEncryptionPublicKey std::shared_ptr GetIdentity () const { return m_Identity; }; protected: From b6b25dc9f3c6d57c11f8fd8c2980e64133f4a682 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 20 Mar 2020 17:44:53 +0300 Subject: [PATCH 3602/6300] update log messages Signed-off-by: R4SAS --- libi2pd/Garlic.cpp | 2 +- libi2pd/LeaseSet.cpp | 2 +- libi2pd/SSUData.cpp | 6 +++--- libi2pd/Transports.cpp | 13 +++++++------ 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 629e0d76..02d1cd83 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -890,7 +890,7 @@ namespace garlic m_Tags.insert (std::make_pair (SessionTag (tag, ts), decryption)); } if (!m_Tags.empty ()) - LogPrint (eLogInfo, m_Tags.size (), " loaded for ", ident); + LogPrint (eLogInfo, "Garlic: " m_Tags.size (), " tags loaded for ", ident); } } i2p::fs::Remove (path); diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index e2da1d21..bcfede33 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -159,7 +159,7 @@ namespace data } } else - LogPrint (eLogWarning, "LeaseSet: Lease is expired already "); + LogPrint (eLogWarning, "LeaseSet: Lease is expired already"); } uint64_t LeaseSet::ExtractTimestamp (const uint8_t * buf, size_t len) const diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index add58739..866da277 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -442,7 +442,7 @@ namespace transport } catch (boost::system::system_error& ec) { - LogPrint (eLogWarning, "SSU: Can't resend data fragment ", ec.what ()); + LogPrint (eLogWarning, "SSU: Can't resend message ", it->first, " data fragment: ", ec.what ()); } } @@ -452,7 +452,7 @@ namespace transport } else { - LogPrint (eLogInfo, "SSU: message has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted"); + LogPrint (eLogInfo, "SSU: message ", it->first, " has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted"); it = m_SentMessages.erase (it); } } @@ -488,7 +488,7 @@ namespace transport { if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) { - LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); + LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); it = m_IncompleteMessages.erase (it); } else diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 97c29c70..804f26cb 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -271,11 +271,11 @@ namespace transport m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); - if (m_IsNAT) - { - m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); - m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); - } + if (m_IsNAT) + { + m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); + m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); + } } void Transports::Stop () @@ -589,6 +589,7 @@ namespace transport if (RoutesRestricted() || !i2p::context.SupportsV4 ()) return; if (m_SSUServer) { + LogPrint (eLogInfo, "Transports: Started peer test"); bool statusChanged = false; for (int i = 0; i < 5; i++) { @@ -604,7 +605,7 @@ namespace transport } } if (!statusChanged) - LogPrint (eLogWarning, "Can't find routers for peer test"); + LogPrint (eLogWarning, "Transports: Can't find routers for peer test"); } } From 168da33d8bc5689ee0ecb01e4c05ca785e3662ba Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 20 Mar 2020 18:43:54 +0300 Subject: [PATCH 3603/6300] add comma Signed-off-by: R4SAS --- libi2pd/Garlic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 02d1cd83..ae6599fc 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -890,7 +890,7 @@ namespace garlic m_Tags.insert (std::make_pair (SessionTag (tag, ts), decryption)); } if (!m_Tags.empty ()) - LogPrint (eLogInfo, "Garlic: " m_Tags.size (), " tags loaded for ", ident); + LogPrint (eLogInfo, "Garlic: ", m_Tags.size (), " tags loaded for ", ident); } } i2p::fs::Remove (path); From 962c2160c77cce4460950fb6a231725235daf89d Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Mar 2020 17:43:37 -0400 Subject: [PATCH 3604/6300] set actual LeaseSet2 buffer size --- libi2pd/LeaseSet.cpp | 15 +++++++++++++++ libi2pd/LeaseSet.h | 1 + 2 files changed, 16 insertions(+) diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index bcfede33..b411ca0e 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -251,6 +251,13 @@ namespace data memcpy (m_Buffer, buf, len); } + void LeaseSet::SetBufferLen (size_t len) + { + if (len <= m_BufferLen) m_BufferLen = len; + else + LogPrint (eLogError, "LeaseSet2: actual buffer size ", len , " exceeds full buffer size ", m_BufferLen); + } + LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases, CryptoKeyType preferredCrypto): LeaseSet (storeLeases), m_StoreType (storeType), m_EncryptionType (preferredCrypto) { @@ -331,6 +338,8 @@ namespace data VerifySignature (identity, buf, len, offset); SetIsValid (verified); } + offset += m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : identity->GetSignatureLen (); + SetBufferLen (offset); } template @@ -537,6 +546,12 @@ namespace data else LogPrint (eLogError, "LeaseSet2: unexpected LeaseSet type ", (int)innerPlainText[0], " inside encrypted LeaseSet"); } + else + { + // we set actual length of encrypted buffer + offset += m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : blindedVerifier->GetSignatureLen (); + SetBufferLen (offset); + } } // helper for ExtractClientAuthData diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index 791d77e3..0d255644 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -97,6 +97,7 @@ namespace data // called from LeaseSet2 LeaseSet (bool storeLeases); void SetBuffer (const uint8_t * buf, size_t len); + void SetBufferLen (size_t len); void SetIdentity (std::shared_ptr identity) { m_Identity = identity; }; void SetExpirationTime (uint64_t t) { m_ExpirationTime = t; }; void SetIsValid (bool isValid) { m_IsValid = isValid; }; From ff19bab80087947c7f364d60edfd8af63478b9c7 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 21 Mar 2020 16:21:51 -0400 Subject: [PATCH 3605/6300] set only key correctly --- libi2pd_client/I2CP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index c09b7f4d..ccd94e46 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -572,7 +572,7 @@ namespace client uint16_t keyType = bufbe16toh (buf + offset); offset += 2; // encryption type uint16_t keyLen = bufbe16toh (buf + offset); offset += 2; // private key length if (offset + keyLen > len) return; - if (keyType > currentKeyType) + if (!currentKey || keyType > currentKeyType) { currentKeyType = keyType; currentKey = buf + offset; From 6fb80f226acd76c50e4f615111a8ba01e279c4d5 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Mar 2020 08:14:20 -0400 Subject: [PATCH 3606/6300] reopen socked and restart receiver on exception --- libi2pd/SSU.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index daf7e1e9..4eb958de 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -160,6 +160,13 @@ namespace transport catch (std::exception& ex) { LogPrint (eLogError, "SSU: receivers runtime exception: ", ex.what ()); + if (m_IsRunning) + { + // restart socket + m_Socket.close (); + OpenSocket (); + Receive (); + } } } } @@ -175,6 +182,12 @@ namespace transport catch (std::exception& ex) { LogPrint (eLogError, "SSU: v6 receivers runtime exception: ", ex.what ()); + if (m_IsRunning) + { + m_SocketV6.close (); + OpenSocketV6 (); + ReceiveV6 (); + } } } } From fe9ac10f02da049c7966c0ecb2cd84c8e94698c8 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Mar 2020 21:21:12 -0400 Subject: [PATCH 3607/6300] generate new tags based on last received index --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 4 +--- libi2pd/ECIESX25519AEADRatchetSession.h | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 2cea2172..5dd78747 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -402,8 +402,7 @@ namespace garlic return false; } HandlePayload (payload.data (), len - 16); - if (m_NumReceiveTags > 0)m_NumReceiveTags--; - if (m_NumReceiveTags <= GetOwner ()->GetNumTags ()*2/3) + if (m_ReceiveTagset.GetNextIndex () - index <= GetOwner ()->GetNumTags ()*2/3) GenerateMoreReceiveTags (GetOwner ()->GetNumTags ()); return true; } @@ -570,7 +569,6 @@ namespace garlic uint64_t tag = m_ReceiveTagset.GetNextSessionTag (); GetOwner ()->AddECIESx25519SessionTag (index, tag, shared_from_this ()); } - m_NumReceiveTags += numTags; } bool ECIESX25519AEADRatchetSession::CheckExpired (uint64_t ts) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index db9807a5..95875013 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -119,7 +119,6 @@ namespace garlic SessionState m_State = eSessionStateNew; uint64_t m_LastActivityTimestamp = 0; // incoming RatchetTagSet m_SendTagset, m_ReceiveTagset; - int m_NumReceiveTags = 0; std::unique_ptr m_Destination;// TODO: might not need it }; } From 744e893dcef13db3d47aea1d877cf97032fa0c03 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Mar 2020 18:09:57 -0400 Subject: [PATCH 3608/6300] check message length --- libi2pd_client/I2CP.cpp | 12 ++++++++++-- libi2pd_client/I2CP.h | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index ccd94e46..eec21f06 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -246,8 +246,16 @@ namespace client m_PayloadLen = bufbe32toh (m_Header + I2CP_HEADER_LENGTH_OFFSET); if (m_PayloadLen > 0) { - m_Payload = new uint8_t[m_PayloadLen]; - ReceivePayload (); + if (m_PayloadLen <= I2CP_MAX_MESSAGE_LENGTH) + { + m_Payload = new uint8_t[m_PayloadLen]; + ReceivePayload (); + } + else + { + LogPrint (eLogError, "I2CP: Unexpected payload length ", m_PayloadLen); + Terminate (); + } } else // no following payload { diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 848378e0..f675318f 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -24,7 +24,8 @@ namespace client { const uint8_t I2CP_PROTOCOL_BYTE = 0x2A; const size_t I2CP_SESSION_BUFFER_SIZE = 4096; - + const size_t I2CP_MAX_MESSAGE_LENGTH = 65535; + const size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; const size_t I2CP_HEADER_SIZE = I2CP_HEADER_TYPE_OFFSET + 1; From 869d0156ce38de301e32c5a35a698059c4a0d5d9 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Mar 2020 19:03:38 -0400 Subject: [PATCH 3609/6300] handle Ack request --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 25 +++++++++++++++++++++-- libi2pd/ECIESX25519AEADRatchetSession.h | 6 +++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 5dd78747..c57553d4 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -38,6 +38,7 @@ namespace garlic { i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) m_NextIndex++; + if (m_NextIndex >= 65535) m_NextIndex = 0; // TODO: dirty hack, should create new tagset return m_KeyData.GetTag (); } @@ -178,7 +179,7 @@ namespace garlic return true; } - void ECIESX25519AEADRatchetSession::HandlePayload (const uint8_t * buf, size_t len) + void ECIESX25519AEADRatchetSession::HandlePayload (const uint8_t * buf, size_t len, int index) { size_t offset = 0; while (offset < len) @@ -207,6 +208,12 @@ namespace garlic case eECIESx25519BlkPadding: LogPrint (eLogDebug, "Garlic: padding"); break; + case eECIESx25519BlkAckRequest: + { + LogPrint (eLogDebug, "Garlic: ack request"); + m_AckRequests.push_back ( {bufbe16toh (buf + offset), index}); + break; + } default: LogPrint (eLogWarning, "Garlic: Unknown block type ", (int)blk); } @@ -401,7 +408,7 @@ namespace garlic LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); return false; } - HandlePayload (payload.data (), len - 16); + HandlePayload (payload.data (), len - 16, index); if (m_ReceiveTagset.GetNextIndex () - index <= GetOwner ()->GetNumTags ()*2/3) GenerateMoreReceiveTags (GetOwner ()->GetNumTags ()); return true; @@ -478,6 +485,8 @@ namespace garlic SetLeaseSetSubmissionTime (ts); GetOwner ()->DeliveryStatusSent (shared_from_this (), leaseSet->GetMsgID ()); } + if (m_AckRequests.size () > 0) + payloadLen += m_AckRequests.size ()*4 + 3; uint8_t paddingSize; RAND_bytes (&paddingSize, 1); paddingSize &= 0x0F; paddingSize++; // 1 - 16 @@ -497,6 +506,18 @@ namespace garlic // msg if (msg && m_Destination) offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset, true); + // ack + if (m_AckRequests.size () > 0) + { + v[offset] = eECIESx25519BlkAck; offset++; + htobe16buf (v.data () + offset, m_AckRequests.size ()*4); offset += 2; + for (auto& it: m_AckRequests) + { + htobe16buf (v.data () + offset, it.first); offset += 2; + htobe16buf (v.data () + offset, it.second); offset += 2; + } + m_AckRequests.clear (); + } // padding v[offset] = eECIESx25519BlkPadding; offset++; htobe16buf (v.data () + offset, paddingSize); offset += 2; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 95875013..56fb48cf 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "Identity.h" #include "Crypto.h" #include "Garlic.h" @@ -51,6 +52,8 @@ namespace garlic eECIESx25519BlkTermination = 4, eECIESx25519BlkOptions = 5, eECIESx25519BlkNextSessionKey = 7, + eECIESx25519BlkAck = 8, + eECIESx25519BlkAckRequest = 9, eECIESx25519BlkGalicClove = 11, eECIESx25519BlkPadding = 254 }; @@ -99,7 +102,7 @@ namespace garlic bool HandleNewIncomingSession (const uint8_t * buf, size_t len); bool HandleNewOutgoingSessionReply (const uint8_t * buf, size_t len); bool HandleExistingSessionMessage (const uint8_t * buf, size_t len, int index); - void HandlePayload (const uint8_t * buf, size_t len); + void HandlePayload (const uint8_t * buf, size_t len, int index = 0); bool NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); @@ -120,6 +123,7 @@ namespace garlic uint64_t m_LastActivityTimestamp = 0; // incoming RatchetTagSet m_SendTagset, m_ReceiveTagset; std::unique_ptr m_Destination;// TODO: might not need it + std::list > m_AckRequests; // (key_id, indeX) }; } } From f4ca6bbb52d8d2029662be8e72305fb21c29acba Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 Mar 2020 19:27:10 -0400 Subject: [PATCH 3610/6300] fixed race with identity verifier --- libi2pd/Identity.cpp | 49 ++++++++++++++++++++------------------------ libi2pd/Identity.h | 5 +++-- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 3f9633ed..1026eb02 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -34,12 +34,11 @@ namespace data } IdentityEx::IdentityEx (): - m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { } - IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type, CryptoKeyType cryptoType): - m_IsVerifierCreated (false) + IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type, CryptoKeyType cryptoType) { memcpy (m_StandardIdentity.publicKey, publicKey, 256); // publicKey in awlays assumed 256 regardless actual size, padding must be taken care of if (type != SIGNING_KEY_TYPE_DSA_SHA1) @@ -141,19 +140,19 @@ namespace data } IdentityEx::IdentityEx (const uint8_t * buf, size_t len): - m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { FromBuffer (buf, len); } IdentityEx::IdentityEx (const IdentityEx& other): - m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { *this = other; } IdentityEx::IdentityEx (const Identity& standard): - m_IsVerifierCreated (false), m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0), m_ExtendedBuffer (nullptr) { *this = standard; } @@ -161,6 +160,7 @@ namespace data IdentityEx::~IdentityEx () { delete[] m_ExtendedBuffer; + delete m_Verifier; } IdentityEx& IdentityEx::operator=(const IdentityEx& other) @@ -178,8 +178,8 @@ namespace data else m_ExtendedBuffer = nullptr; + delete m_Verifier; m_Verifier = nullptr; - m_IsVerifierCreated = false; return *this; } @@ -193,8 +193,8 @@ namespace data m_ExtendedBuffer = nullptr; m_ExtendedLen = 0; + delete m_Verifier; m_Verifier = nullptr; - m_IsVerifierCreated = false; return *this; } @@ -233,6 +233,7 @@ namespace data } SHA256(buf, GetFullLen (), m_IdentHash); + delete m_Verifier; m_Verifier = nullptr; return GetFullLen (); @@ -381,33 +382,27 @@ namespace data void IdentityEx::UpdateVerifier (i2p::crypto::Verifier * verifier) const { - if (!m_Verifier) + bool del = false; { - auto created = m_IsVerifierCreated.exchange (true); - if (!created) - m_Verifier.reset (verifier); + std::lock_guard l(m_VerifierMutex); + if (!m_Verifier) + m_Verifier = verifier; else - { - delete verifier; - int count = 0; - while (!m_Verifier && count < 500) // 5 seconds - { - std::this_thread::sleep_for (std::chrono::milliseconds(10)); - count++; - } - if (!m_Verifier) - LogPrint (eLogError, "Identity: couldn't get verifier in 5 seconds"); - } + del = true; } - else + if (del) delete verifier; } void IdentityEx::DropVerifier () const { - // TODO: potential race condition with Verify - m_IsVerifierCreated = false; - m_Verifier = nullptr; + i2p::crypto::Verifier * verifier; + { + std::lock_guard l(m_VerifierMutex); + verifier = m_Verifier; + m_Verifier = nullptr; + } + delete verifier; } std::shared_ptr IdentityEx::CreateEncryptor (CryptoKeyType keyType, const uint8_t * key) diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index 9c78f552..0ee87beb 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "Base.h" #include "Signature.h" #include "CryptoKey.h" @@ -125,8 +126,8 @@ namespace data Identity m_StandardIdentity; IdentHash m_IdentHash; - mutable std::unique_ptr m_Verifier; - mutable std::atomic_bool m_IsVerifierCreated; // make sure we don't create twice + mutable i2p::crypto::Verifier * m_Verifier = nullptr; + mutable std::mutex m_VerifierMutex; size_t m_ExtendedLen; uint8_t * m_ExtendedBuffer; }; From f21af4068fd1c9890eb467d94af718e717bd8b21 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 31 Mar 2020 17:35:51 -0400 Subject: [PATCH 3611/6300] preferred crypto type for Decrypt --- libi2pd/Destination.cpp | 2 +- libi2pd/Destination.h | 2 +- libi2pd/ECIESX25519AEADRatchetSession.cpp | 10 +++++----- libi2pd/ECIESX25519AEADRatchetSession.h | 2 +- libi2pd/Garlic.cpp | 2 +- libi2pd/Identity.h | 2 +- libi2pd/RouterContext.cpp | 2 +- libi2pd/RouterContext.h | 2 +- libi2pd_client/I2CP.cpp | 2 +- libi2pd_client/I2CP.h | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 46d3fc57..fb0add02 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1173,7 +1173,7 @@ namespace client if (m_DatagramDestination) m_DatagramDestination->CleanUp (); } - bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const + bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const { if (m_Decryptor) return m_Decryptor->Decrypt (encrypted, data, ctx, true); diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index f26200a0..c5c2c16f 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -237,7 +237,7 @@ namespace client i2p::datagram::DatagramDestination * CreateDatagramDestination (); // implements LocalDestination - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { return m_EncryptionKeyType == keyType; }; const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const { return m_EncryptionPublicKey; }; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index c57553d4..1523d775 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -138,7 +138,7 @@ namespace garlic MixHash (m_Aepk, 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; - GetOwner ()->Decrypt (m_Aepk, sharedSecret, nullptr); // x25519(bsk, aepk) + GetOwner ()->Decrypt (m_Aepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET); // x25519(bsk, aepk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) // decrypt flags/static @@ -160,7 +160,7 @@ namespace garlic { // static key, fs is apk memcpy (m_RemoteStaticKey, fs, 32); - GetOwner ()->Decrypt (fs, sharedSecret, nullptr); // x25519(bsk, apk) + GetOwner ()->Decrypt (fs, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET); // x25519(bsk, apk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) } else // all zeros flags @@ -211,7 +211,7 @@ namespace garlic case eECIESx25519BlkAckRequest: { LogPrint (eLogDebug, "Garlic: ack request"); - m_AckRequests.push_back ( {bufbe16toh (buf + offset), index}); + m_AckRequests.push_back ({0, index}); // TODO: use actual tagsetid break; } default: @@ -250,7 +250,7 @@ namespace garlic MixHash (out + offset, 48); // h = SHA256(h || ciphertext) offset += 48; // KDF2 - GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr); // x25519 (ask, bpk) + GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET); // x25519 (ask, bpk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) // encrypt payload if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt @@ -339,7 +339,7 @@ namespace garlic uint8_t sharedSecret[32]; m_EphemeralKeys.Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) - GetOwner ()->Decrypt (bepk, sharedSecret, nullptr); // x25519 (ask, bepk) + GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RARCHET); // x25519 (ask, bepk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) uint8_t nonce[12]; CreateNonce (0, nonce); diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 56fb48cf..ed1fa17d 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -123,7 +123,7 @@ namespace garlic uint64_t m_LastActivityTimestamp = 0; // incoming RatchetTagSet m_SendTagset, m_ReceiveTagset; std::unique_ptr m_Destination;// TODO: might not need it - std::list > m_AckRequests; // (key_id, indeX) + std::list > m_AckRequests; // (tagsetid, index) }; } } diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index ae6599fc..fd6e8fee 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -495,7 +495,7 @@ namespace garlic } // otherwise assume ElGamal/AES ElGamalBlock elGamal; - if (length >= 514 && Decrypt (buf, (uint8_t *)&elGamal, m_Ctx)) + if (length >= 514 && Decrypt (buf, (uint8_t *)&elGamal, m_Ctx, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)) { auto decryption = std::make_shared(elGamal.sessionKey); uint8_t iv[32]; // IV is first 16 bytes diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index 0ee87beb..663f46b5 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -225,7 +225,7 @@ namespace data public: virtual ~LocalDestination() {}; - virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const = 0; + virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL) const = 0; virtual std::shared_ptr GetIdentity () const = 0; const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 6c63ef79..b86ed8f2 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -724,7 +724,7 @@ namespace i2p return std::chrono::duration_cast (std::chrono::steady_clock::now() - m_StartupTime).count (); } - bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const + bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const { return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, true) : false; } diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index dfc05fe7..28c324c4 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -108,7 +108,7 @@ namespace i2p // implements LocalDestination std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; void SetLeaseSetUpdated () {}; diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index eec21f06..f4c8a91e 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -59,7 +59,7 @@ namespace client m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), m_EncryptionPrivateKey); } - bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const + bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const { if (m_Decryptor) return m_Decryptor->Decrypt (encrypted, data, ctx, true); diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index f675318f..7f590555 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -80,7 +80,7 @@ namespace client void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession // implements LocalDestination - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { return m_EncryptionKeyType == keyType; }; // TODO: implement GetEncryptionPublicKey std::shared_ptr GetIdentity () const { return m_Identity; }; From 8872d1f389a63cf2823d9562396eb34b01bd3e63 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 1 Apr 2020 09:54:10 -0400 Subject: [PATCH 3612/6300] mutex for m_RemoteIdentity --- libi2pd/TransportSession.h | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/libi2pd/TransportSession.h b/libi2pd/TransportSession.h index 49067ce2..55c39c7a 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "Identity.h" #include "Crypto.h" #include "RouterInfo.h" @@ -67,8 +68,16 @@ namespace transport std::string GetIdentHashBase64() const { return m_RemoteIdentity ? m_RemoteIdentity->GetIdentHash().ToBase64() : ""; } - std::shared_ptr GetRemoteIdentity () { return m_RemoteIdentity; }; - void SetRemoteIdentity (std::shared_ptr ident) { m_RemoteIdentity = ident; }; + std::shared_ptr GetRemoteIdentity () + { + std::lock_guard l(m_RemoteIdentityMutex); + return m_RemoteIdentity; + } + void SetRemoteIdentity (std::shared_ptr ident) + { + std::lock_guard l(m_RemoteIdentityMutex); + m_RemoteIdentity = ident; + } size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; @@ -85,6 +94,7 @@ namespace transport protected: std::shared_ptr m_RemoteIdentity; + mutable std::mutex m_RemoteIdentityMutex; std::shared_ptr m_DHKeysPair; // X - for client and Y - for server size_t m_NumSentBytes, m_NumReceivedBytes; bool m_IsOutgoing; From aa7750bfd345aa04aefa4b08e4fedf97d6997c04 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 2 Apr 2020 21:48:39 -0400 Subject: [PATCH 3613/6300] keep sending new session reply until first established session message received --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 36 +++++++++++++++++++---- libi2pd/ECIESX25519AEADRatchetSession.h | 5 +++- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 1523d775..c3f7320d 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -298,7 +298,8 @@ namespace garlic return false; } MixHash (out + offset, 16); // h = SHA256(h || ciphertext) - out += 16; + offset += 16; + memcpy (m_NSRHeader, out, 56); // for possible next NSR // KDF for payload uint8_t keydata[64]; i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) @@ -308,18 +309,33 @@ namespace garlic m_SendTagset.DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) m_SendTagset.NextSessionTagRatchet (); GenerateMoreReceiveTags (GetOwner ()->GetNumTags ()); - i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) + i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", m_NSRKey, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) // encrypt payload - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, keydata, nonce, out + offset, len + 16, true)) // encrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset, len + 16, true)) // encrypt { - LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); + LogPrint (eLogWarning, "Garlic: NSR payload section AEAD encryption failed"); return false; } - m_State = eSessionStateEstablished; + m_State = eSessionStateNewSessionReplySent; return true; } + bool ECIESX25519AEADRatchetSession::NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) + { + // we are Bob and sent NSR already + memcpy (out, m_NSRHeader, 56); + uint8_t nonce[12]; + CreateNonce (0, nonce); + // encrypt payload + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + 56, len + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Next NSR payload section AEAD encryption failed"); + return false; + } + return true; + } + bool ECIESX25519AEADRatchetSession::HandleNewOutgoingSessionReply (const uint8_t * buf, size_t len) { // we are Alice @@ -419,6 +435,11 @@ namespace garlic m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); switch (m_State) { + case eSessionStateNewSessionReplySent: + m_State = eSessionStateEstablished; +#if (__cplusplus >= 201703L) // C++ 17 or higher + [[fallthrough]]; +#endif case eSessionStateEstablished: return HandleExistingSessionMessage (buf, len, index); case eSessionStateNew: @@ -456,6 +477,11 @@ namespace garlic return nullptr; len += 72; break; + case eSessionStateNewSessionReplySent: + if (!NextNewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) + return nullptr; + len += 72; + break; default: return nullptr; } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index ed1fa17d..82675331 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -69,6 +69,7 @@ namespace garlic eSessionStateNew =0, eSessionStateNewSessionReceived, eSessionStateNewSessionSent, + eSessionStateNewSessionReplySent, eSessionStateEstablished }; @@ -106,6 +107,7 @@ namespace garlic bool NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); + bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); std::vector CreatePayload (std::shared_ptr msg); @@ -117,7 +119,8 @@ namespace garlic private: uint8_t m_H[32], m_CK[64] /* [chainkey, key] */, m_RemoteStaticKey[32]; - uint8_t m_Aepk[32]; // Alice's ephemeral keys TODO: for incoming only + uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only + uint8_t m_NSRHeader[56], m_NSRKey[32]; // new session reply, for incoming only i2p::crypto::X25519Keys m_EphemeralKeys; SessionState m_State = eSessionStateNew; uint64_t m_LastActivityTimestamp = 0; // incoming From d503f0756470ec1d27228295fd906cf47f97fe3e Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 26 Mar 2020 09:54:51 +0300 Subject: [PATCH 3614/6300] suppress GCC 7 (bug 77728) psabi note Suppresses messages like that: note: parameter passing for argument of type <...> will change in GCC 7.1 Signed-off-by: R4SAS --- Makefile.linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.linux b/Makefile.linux index 9db660c2..fa2f2fdf 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -1,5 +1,5 @@ # set defaults instead redefine -CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation +CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation -Wno-psabi LDFLAGS ?= ${LD_DEBUG} ## NOTE: The NEEDED_CXXFLAGS are here so that custom CXXFLAGS can be specified at build time From a9436aa9af5a97daef899f352c9b774d4ce40e6d Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 3 Apr 2020 14:31:18 +0300 Subject: [PATCH 3615/6300] drop i2lua Signed-off-by: R4SAS --- build/CMakeLists.txt | 6 ------ libi2pd/Destination.cpp | 26 -------------------------- libi2pd/Destination.h | 20 +++----------------- libi2pd/Tunnel.cpp | 6 ------ libi2pd/TunnelPool.cpp | 10 ---------- libi2pd/TunnelPool.h | 10 ---------- libi2pd_client/MatchedDestination.cpp | 5 ----- libi2pd_client/MatchedDestination.h | 1 - 8 files changed, 3 insertions(+), 81 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 2bdfb396..1a459bf8 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -19,7 +19,6 @@ option(WITH_GUI "Include GUI (currently MS Windows only)" ON) option(WITH_MESHNET "Build for cjdns test network" OFF) option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) -option(WITH_I2LUA "Build for i2lua" OFF) # paths set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) @@ -88,10 +87,6 @@ if (WIN32 OR MSYS) list (APPEND LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/I2PEndian.cpp") endif () -if (WITH_I2LUA) - add_definitions(-DI2LUA) -endif() - add_library(libi2pd ${LIBI2PD_SRC}) set_target_properties(libi2pd PROPERTIES PREFIX "") @@ -414,7 +409,6 @@ message(STATUS " PCH : ${WITH_PCH}") message(STATUS " MESHNET : ${WITH_MESHNET}") message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") message(STATUS " THREADSANITIZER : ${WITH_THREADSANITIZER}") -message(STATUS " I2LUA : ${WITH_I2LUA}") message(STATUS "---------------------------------------") #Handle paths nicely diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index fb0add02..98c4b323 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -931,32 +931,6 @@ namespace client } } -#ifdef I2LUA - void ClientDestination::Ready(ReadyPromise & p) - { - ScheduleCheckForReady(&p); - } - - void ClientDestination::ScheduleCheckForReady(ReadyPromise * p) - { - // tick every 100ms - m_ReadyChecker.expires_from_now(boost::posix_time::milliseconds(100)); - m_ReadyChecker.async_wait([&, p] (const boost::system::error_code & ecode) { - HandleCheckForReady(ecode, p); - }); - } - - void ClientDestination::HandleCheckForReady(const boost::system::error_code & ecode, ReadyPromise * p) - { - if(ecode) // error happened - p->set_value(nullptr); - else if(IsReady()) // we are ready - p->set_value(std::shared_ptr(this)); - else // we are not ready - ScheduleCheckForReady(p); - } -#endif - void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index c5c2c16f..83a38816 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -8,9 +8,6 @@ #include #include #include -#ifdef I2LUA -#include -#endif #include #include "Identity.h" #include "TunnelPool.h" @@ -196,13 +193,6 @@ namespace client class ClientDestination: public LeaseSetDestination { public: -#ifdef I2LUA - // type for informing that a client destination is ready - typedef std::promise > ReadyPromise; - // informs promise with shared_from_this() when this destination is ready to use - // if cancelled before ready, informs promise with nullptr - void Ready(ReadyPromise & p); -#endif ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); @@ -251,14 +241,10 @@ namespace client private: - std::shared_ptr GetSharedFromThis () - { return std::static_pointer_cast(shared_from_this ()); } + std::shared_ptr GetSharedFromThis () { + return std::static_pointer_cast(shared_from_this ()); + } void PersistTemporaryKeys (); -#ifdef I2LUA - void ScheduleCheckForReady(ReadyPromise * p); - void HandleCheckForReady(const boost::system::error_code & ecode, ReadyPromise * p); -#endif - void ReadAuthKey (const std::string& group, const std::map * params); private: diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 9dacddac..879ee2d3 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -576,7 +576,6 @@ namespace tunnel for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();) { auto tunnel = it->second; - auto pool = tunnel->GetTunnelPool(); switch (tunnel->GetState ()) { case eTunnelStatePending: @@ -599,8 +598,6 @@ namespace tunnel hop = hop->next; } } - // for i2lua - if(pool) pool->OnTunnelBuildResult(tunnel, eBuildResultTimeout); // delete it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; @@ -610,9 +607,6 @@ namespace tunnel break; case eTunnelStateBuildFailed: LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " failed, deleted"); - // for i2lua - if(pool) pool->OnTunnelBuildResult(tunnel, eBuildResultRejected); - it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; break; diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index b3e3c7d2..7256b2db 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -88,8 +88,6 @@ namespace tunnel } if (m_LocalDestination) m_LocalDestination->SetLeaseSetUpdated (); - - OnTunnelBuildResult(createdTunnel, eBuildResultOkay); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) @@ -112,8 +110,6 @@ namespace tunnel std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.insert (createdTunnel); } - OnTunnelBuildResult(createdTunnel, eBuildResultOkay); - //CreatePairedInboundTunnel (createdTunnel); } @@ -596,11 +592,5 @@ namespace tunnel } return tun; } - - void TunnelPool::OnTunnelBuildResult(std::shared_ptr tunnel, TunnelBuildResult result) - { - auto peers = tunnel->GetPeers(); - if(m_CustomPeerSelector) m_CustomPeerSelector->OnBuildResult(peers, tunnel->IsInbound(), result); - } } } diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index fc46930c..d6fb91cc 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -23,13 +23,6 @@ namespace tunnel class InboundTunnel; class OutboundTunnel; - - enum TunnelBuildResult { - eBuildResultOkay, // tunnel was built okay - eBuildResultRejected, // tunnel build was explicitly rejected - eBuildResultTimeout // tunnel build timed out - }; - typedef std::shared_ptr Peer; typedef std::vector Path; @@ -38,7 +31,6 @@ namespace tunnel { virtual ~ITunnelPeerSelector() {}; virtual bool SelectPeers(Path & peers, int hops, bool isInbound) = 0; - virtual bool OnBuildResult(const Path & peers, bool isInbound, TunnelBuildResult result) = 0; }; @@ -98,8 +90,6 @@ namespace tunnel std::shared_ptr GetLowestLatencyInboundTunnel(std::shared_ptr exclude=nullptr) const; std::shared_ptr GetLowestLatencyOutboundTunnel(std::shared_ptr exclude=nullptr) const; - void OnTunnelBuildResult(std::shared_ptr tunnel, TunnelBuildResult result); - // for overriding tunnel peer selection std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; diff --git a/libi2pd_client/MatchedDestination.cpp b/libi2pd_client/MatchedDestination.cpp index 3d75a3ca..5cec178f 100644 --- a/libi2pd_client/MatchedDestination.cpp +++ b/libi2pd_client/MatchedDestination.cpp @@ -94,10 +94,5 @@ namespace client } return true; } - - bool MatchedTunnelDestination::OnBuildResult(const i2p::tunnel::Path & path, bool inbound, i2p::tunnel::TunnelBuildResult result) - { - return true; - } } } diff --git a/libi2pd_client/MatchedDestination.h b/libi2pd_client/MatchedDestination.h index 67b874e6..9d61799a 100644 --- a/libi2pd_client/MatchedDestination.h +++ b/libi2pd_client/MatchedDestination.h @@ -18,7 +18,6 @@ namespace client void Stop(); bool SelectPeers(i2p::tunnel::Path & peers, int hops, bool inbound); - bool OnBuildResult(const i2p::tunnel::Path & peers, bool inbound, i2p::tunnel::TunnelBuildResult result); private: void ResolveCurrentLeaseSet(); From 4e1319d874ca788672dba47a942efa8f849dc675 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 7 Apr 2020 11:40:18 -0400 Subject: [PATCH 3616/6300] handle ECIESFlag in DatabaseLookup at floodfill --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 35 +++++++++++++++++++++++ libi2pd/ECIESX25519AEADRatchetSession.h | 2 ++ libi2pd/I2NPProtocol.h | 1 + libi2pd/NetDb.cpp | 21 ++++++++++---- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index c3f7320d..4371124e 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -623,6 +623,41 @@ namespace garlic CleanupUnconfirmedLeaseSet (ts); return ts > m_LastActivityTimestamp + ECIESX25519_EXPIRATION_TIMEOUT; } + + std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag) + { + auto m = NewI2NPMessage (); + m->Align (12); // in order to get buf aligned to 16 (12 + 4) + uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length + uint8_t nonce[12]; + memset (nonce, 0, 12); // n = 0 + size_t offset = 0; + memcpy (buf + offset, &tag, 8); offset += 8; + auto payload = buf + offset; + uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; + size_t len = cloveSize + 3; + payload[0] = eECIESx25519BlkGalicClove; // clove type + htobe16buf (payload + 1, cloveSize); // size + payload += 3; + *payload = 0; payload++; // flag and delivery instructions + *payload = msg->GetTypeID (); // I2NP msg type + htobe32buf (payload + 1, msg->GetMsgID ()); // msgID + htobe32buf (payload + 5, msg->GetExpiration ()/1000); // expiration in seconds + memcpy (payload + 9, msg->GetPayload (), msg->GetPayloadLength ()); + + if (!i2p::crypto::AEADChaCha20Poly1305 (buf + offset, len, buf, 8, key, nonce, buf + offset, len + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); + return nullptr; + } + offset += len + 16; + + htobe32buf (m->GetPayload (), offset); + m->len += offset + 4; + m->FillI2NPMessageHeader (eI2NPGarlic); + return m; + } + } } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 82675331..3185a7fc 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -128,6 +128,8 @@ namespace garlic std::unique_ptr m_Destination;// TODO: might not need it std::list > m_AckRequests; // (tagsetid, index) }; + + std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag); } } diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index ca074aed..5714afce 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -95,6 +95,7 @@ namespace i2p // DatabaseLookup flags const uint8_t DATABASE_LOOKUP_DELIVERY_FLAG = 0x01; const uint8_t DATABASE_LOOKUP_ENCRYPTION_FLAG = 0x02; + const uint8_t DATABASE_LOOKUP_ECIES_FLAG = 0x10; const uint8_t DATABASE_LOOKUP_TYPE_FLAGS_MASK = 0x0C; const uint8_t DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP = 0; const uint8_t DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP = 0x04; // 0100 diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 6d69f131..5d452d1d 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -15,8 +15,9 @@ #include "NTCP2.h" #include "RouterContext.h" #include "Garlic.h" -#include "NetDb.hpp" +#include "ECIESX25519AEADRatchetSession.h" #include "Config.h" +#include "NetDb.hpp" using namespace i2p::transport; @@ -949,10 +950,20 @@ namespace data const uint8_t numTags = excluded[32]; if (numTags) { - const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag - i2p::garlic::ElGamalAESSession garlic (sessionKey, sessionTag); - replyMsg = garlic.WrapSingleMessage (replyMsg); - if(replyMsg == nullptr) LogPrint(eLogError, "NetDb: failed to wrap message"); + if (flag & DATABASE_LOOKUP_ECIES_FLAG) + { + uint64_t tag; + memcpy (&tag, excluded + 33, 8); + replyMsg = i2p::garlic::WrapECIESX25519AEADRatchetMessage (replyMsg, sessionKey, tag); + } + else + { + const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag + i2p::garlic::ElGamalAESSession garlic (sessionKey, sessionTag); + replyMsg = garlic.WrapSingleMessage (replyMsg); + } + if (!replyMsg) + LogPrint (eLogError, "NetDb: failed to wrap message"); } else LogPrint(eLogWarning, "NetDb: encrypted reply requested but no tags provided"); From 49c1e477369cc08a94ea6eb57d19b4e91f81fc42 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 8 Apr 2020 18:02:12 -0400 Subject: [PATCH 3617/6300] correct termination if session already exists --- libi2pd/NTCP2.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 3dfd6250..a92c77ce 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -706,9 +706,13 @@ namespace transport // ready to communicate auto existing = i2p::data::netdb.FindRouter (ri.GetRouterIdentity ()->GetIdentHash ()); // check if exists already SetRemoteIdentity (existing ? existing->GetRouterIdentity () : ri.GetRouterIdentity ()); - m_Server.AddNTCP2Session (shared_from_this (), true); - Established (); - ReceiveLength (); + if (m_Server.AddNTCP2Session (shared_from_this (), true)) + { + Established (); + ReceiveLength (); + } + else + Terminate (); } else Terminate (); @@ -1258,7 +1262,6 @@ namespace transport if (it != m_NTCP2Sessions.end ()) { LogPrint (eLogWarning, "NTCP2: session to ", ident.ToBase64 (), " already exists"); - session->Terminate(); return false; } m_NTCP2Sessions.insert (std::make_pair (ident, session)); @@ -1301,6 +1304,8 @@ namespace transport }); conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port), std::bind (&NTCP2Server::HandleConnect, this, std::placeholders::_1, conn, timer)); } + else + conn->Terminate (); }); } From b7c206c44bacaf611b06855f9d6292d88b196978 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 9 Apr 2020 15:00:38 -0400 Subject: [PATCH 3618/6300] replace by new incoming session --- libi2pd/NTCP2.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index a92c77ce..85d83bf0 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1262,7 +1262,11 @@ namespace transport if (it != m_NTCP2Sessions.end ()) { LogPrint (eLogWarning, "NTCP2: session to ", ident.ToBase64 (), " already exists"); - return false; + if (incoming) + // replace by new session + it->second->Terminate (); + else + return false; } m_NTCP2Sessions.insert (std::make_pair (ident, session)); return true; From b3974cb52a2eb5bf2201af6b5e82410024b643e9 Mon Sep 17 00:00:00 2001 From: r4sas Date: Fri, 10 Apr 2020 02:34:47 +0000 Subject: [PATCH 3619/6300] [webconsole] security hardening headers (closes #1464) Signed-off-by: r4sas --- daemon/HTTPServer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 5eac08a9..f93c3531 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1141,6 +1141,8 @@ namespace http { void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) { reply.add_header("X-Frame-Options", "SAMEORIGIN"); + reply.add_header("X-Content-Type-Options", "nosniff"); + reply.add_header("X-XSS-Protection", "1; mode=block"); reply.add_header("Content-Type", "text/html"); reply.body = content; From 5e606573b1b0ea640a6ed21fd9a0707541587854 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Apr 2020 12:57:47 -0400 Subject: [PATCH 3620/6300] 2.31.0 --- ChangeLog | 19 +++++++++++++++++++ Win32/installer.iss | 2 +- android/build.gradle | 4 ++-- appveyor.yml | 2 +- contrib/rpm/i2pd-git.spec | 5 ++++- contrib/rpm/i2pd.spec | 5 ++++- debian/changelog | 6 ++++++ libi2pd/version.h | 2 +- qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml | 1 + 9 files changed, 39 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index 293764a6..39d62f21 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,25 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.31.0] - 2020-04-10 +### Added +- NTCP2 HTTP proxy +- Publish LeaseSet2 for I2CP destinations +- Show status page on main activity for android +- Handle ECIESFlag in DatabaseLookup at floodfill +- C++17 features for eligible compilers +### Changed +- Droped Websockets and Lua support +- Send DeliveryStatusMsg for LeaseSet for ECIES-X25519-AEAD-Ratchet +- Keep sending new session reply until established for ECIES-X25519-AEAD-Ratchet +- Updated SSU log messages +- Security hardening headers in web console +- Various QT changes +### Fixed +- NTCP2 socket descriptors leak +- Race condition with router's identity in transport sessions +- Not terminated streams remain forever + ## [2.30.0] - 2020-02-25 ### Added - Single threaded SAM diff --git a/Win32/installer.iss b/Win32/installer.iss index d4f6f247..8e66c17a 100644 --- a/Win32/installer.iss +++ b/Win32/installer.iss @@ -1,5 +1,5 @@ #define I2Pd_AppName "i2pd" -#define I2Pd_ver "2.30.0" +#define I2Pd_ver "2.31.0" #define I2Pd_Publisher "PurpleI2P" [Setup] diff --git a/android/build.gradle b/android/build.gradle index 6fcb4521..d3c5c16b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -30,8 +30,8 @@ android { applicationId "org.purplei2p.i2pd" targetSdkVersion 29 minSdkVersion 14 - versionCode 2300 - versionName "2.30.0" + versionCode 2310 + versionName "2.31.0" setProperty("archivesBaseName", archivesBaseName + "-" + versionName) ndk { abiFilters 'armeabi-v7a' diff --git a/appveyor.yml b/appveyor.yml index 8c4a1a9d..d6dcf060 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.30.0.{build} +version: 2.31.0.{build} pull_requests: do_not_increment_build_number: true branches: diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index f9571ca0..b182dca4 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -1,7 +1,7 @@ %define git_hash %(git rev-parse HEAD | cut -c -7) Name: i2pd-git -Version: 2.30.0 +Version: 2.31.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -118,6 +118,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Fri Apr 10 2020 orignal - 2.31.0 +- update to 2.31.0 + * Tue Feb 25 2020 orignal - 2.30.0 - update to 2.30.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index b3cca8ab..5ece769c 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.30.0 +Version: 2.31.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -116,6 +116,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Fri Apr 10 2020 orignal - 2.31.0 +- update to 2.31.0 + * Tue Feb 25 2020 orignal - 2.30.0 - update to 2.30.0 diff --git a/debian/changelog b/debian/changelog index da177cbd..3824d4f8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.31.0-1) unstable; urgency=medium + + * updated to version 2.31.0 + + -- orignal Fri, 10 Apr 2020 16:00:00 +0000 + i2pd (2.30.0-1) unstable; urgency=medium * updated to version 2.30.0/0.9.45 diff --git a/libi2pd/version.h b/libi2pd/version.h index 36fbe2ca..4ad1a546 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -7,7 +7,7 @@ #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 30 +#define I2PD_VERSION_MINOR 31 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml index 00b6974a..4c027bc0 100644 --- a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml +++ b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml @@ -35,6 +35,7 @@ + From 4e37df26a3ba285ebd9c1918472d59dfb530f2cd Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 10 Apr 2020 20:33:54 +0300 Subject: [PATCH 3621/6300] 2.31.0 Signed-off-by: R4SAS --- ChangeLog | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 39d62f21..930be261 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,7 +3,7 @@ ## [2.31.0] - 2020-04-10 ### Added -- NTCP2 HTTP proxy +- NTCP2 through HTTP proxy - Publish LeaseSet2 for I2CP destinations - Show status page on main activity for android - Handle ECIESFlag in DatabaseLookup at floodfill @@ -13,7 +13,9 @@ - Send DeliveryStatusMsg for LeaseSet for ECIES-X25519-AEAD-Ratchet - Keep sending new session reply until established for ECIES-X25519-AEAD-Ratchet - Updated SSU log messages +- Reopen SSU socket on exception - Security hardening headers in web console +- Various web console changes - Various QT changes ### Fixed - NTCP2 socket descriptors leak From 95fa835191d052feee915cfa047230a05176e646 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 11 Apr 2020 23:28:45 +0300 Subject: [PATCH 3622/6300] [android] update strings, menus, add reloading tunnels item Signed-off-by: R4SAS --- android/.gitignore | 2 + android/AndroidManifest.xml | 5 +- android/jni/i2pd_android.cpp | 6 + android/jni/org_purplei2p_i2pd_I2PD_JNI.h | 3 + android/res/layout/activity_perms_asker.xml | 7 +- .../res/layout/activity_perms_explanation.xml | 6 +- android/res/layout/webview.xml | 11 +- android/res/menu/options_main.xml | 30 +- android/res/values-ru/strings.xml | 18 +- android/res/values/strings.xml | 20 +- .../org/purplei2p/i2pd/DaemonSingleton.java | 76 +++- .../src/org/purplei2p/i2pd/I2PDActivity.java | 382 +++++++++--------- android/src/org/purplei2p/i2pd/I2PD_JNI.java | 2 + 13 files changed, 326 insertions(+), 242 deletions(-) diff --git a/android/.gitignore b/android/.gitignore index 666c6694..57a0a6cd 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -4,6 +4,7 @@ bin libs log* obj +.cxx .gradle .idea .externalNativeBuild @@ -14,3 +15,4 @@ android.iml build *.iml *.local +*.jks diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index a95e3773..88985138 100755 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -17,8 +17,9 @@ android:label="@string/app_name" android:theme="@android:style/Theme.Holo.Light.DarkActionBar" android:requestLegacyExternalStorage="true" - android:usesCleartextTraffic="true" + android:usesCleartextTraffic="true" > + @@ -30,10 +31,10 @@ android:label="@string/app_name"> - + diff --git a/android/jni/i2pd_android.cpp b/android/jni/i2pd_android.cpp index da908648..bb058b5d 100755 --- a/android/jni/i2pd_android.cpp +++ b/android/jni/i2pd_android.cpp @@ -2,6 +2,7 @@ #include "org_purplei2p_i2pd_I2PD_JNI.h" #include "DaemonAndroid.h" #include "RouterContext.h" +#include "ClientContext.h" #include "Transports.h" JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith @@ -61,6 +62,11 @@ JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startAcceptingTunnels i2p::context.SetAcceptsTunnels (true); } +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_reloadTunnelsConfigs + (JNIEnv *env, jclass clazz) { + i2p::client::context.ReloadConfig(); +} + JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged (JNIEnv *env, jclass clazz, jboolean isConnected) { bool isConnectedBool = (bool) isConnected; diff --git a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h index 6939a153..6d809e63 100644 --- a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h +++ b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h @@ -27,6 +27,9 @@ JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startAcceptingTunnels (JNIEnv *, jclass); +JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_reloadTunnelsConfigs + (JNIEnv *, jclass); + JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged (JNIEnv * env, jclass clazz, jboolean isConnected); diff --git a/android/res/layout/activity_perms_asker.xml b/android/res/layout/activity_perms_asker.xml index d2d12cb6..778c9ef5 100644 --- a/android/res/layout/activity_perms_asker.xml +++ b/android/res/layout/activity_perms_asker.xml @@ -15,13 +15,12 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/horizontal_page_margin" - android:visibility="gone" - /> + android:visibility="gone" /> \r\n"; + s << "\r\n
    \r\n"; } void ShowTransitTunnels (std::stringstream& s) @@ -1125,6 +1137,19 @@ namespace http { res.add_header("Refresh", redirect.c_str()); return; } + else if (cmd == HTTP_COMMAND_LIMITTRANSIT) + { + uint32_t limit = std::stoul(params["limit"], nullptr); + if (limit > 0 && limit <= 65535) + SetMaxNumTransitTunnels (limit); + else { + s << "ERROR: Transit tunnels count must not exceed 65535

    \r\n"; + s << "Back to commands list
    \r\n"; + s << "
    \r\n\r\n" << tmp_s.str () << "
    DestinationAmount
    \r\n
    \r\n\r\n"; } else s << "Outgoing: 0
    \r\n"; + auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags (); + if (numECIESx25519Tags > 0) + s << "Incoming ECIESx25519: " << numECIESx25519Tags << "
    "; + if (!dest->GetECIESx25519Sessions ().empty ()) + { + s << "
    \r\nECIESx25519Tags sessions
    "; + for (const auto& it: dest->GetECIESx25519Sessions ()) + s << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) + << " " << it.second->GetState () << "
    \r\n"; + } s << "
    \r\n"; } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 995bbe4a..6dd842e4 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -156,6 +156,15 @@ namespace garlic std::list > m_AckRequests; // (tagsetid, index) bool m_SendReverseKey = false, m_SendForwardKey = false; std::unique_ptr m_NextReceiveRatchet, m_NextSendRatchet; + + public: + + // for HTTP only + int GetState () const { return (int)m_State; } + i2p::data::IdentHash GetDestination () const + { + return m_Destination ? *m_Destination : i2p::data::IdentHash (); + } }; std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag); diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index ffb66245..2c71abe8 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -276,7 +276,9 @@ namespace garlic // for HTTP only size_t GetNumIncomingTags () const { return m_Tags.size (); } + size_t GetNumIncomingECIESx25519Tags () const { return m_ECIESx25519Tags.size (); } const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; + const decltype(m_ECIESx25519Sessions)& GetECIESx25519Sessions () const { return m_ECIESx25519Sessions; } }; void CleanUpTagsFiles (); From 1aa0da33820eb091c76e7b95553cace3416245eb Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 30 Apr 2020 04:44:13 +0300 Subject: [PATCH 3654/6300] [NTCP2] fix socks proxy support Signed-off-by: R4SAS --- libi2pd/Config.cpp | 10 ++-- libi2pd/NTCP2.cpp | 119 +++++++++++++++++---------------------- libi2pd/NTCP2.h | 2 +- libi2pd/NTCPSession.cpp | 1 - libi2pd/Transports.cpp | 56 +++++++++--------- libi2pd_client/SOCKS.cpp | 42 +++++++------- 6 files changed, 109 insertions(+), 121 deletions(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index cd17660a..580e9156 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -58,9 +58,9 @@ namespace config { ("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)") ("bandwidth", value()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") ("share", value()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)") - ("ntcp", value()->default_value(false), "Enable NTCP transport (default: disabled)") + ("ntcp", value()->default_value(false), "Enable NTCP transport (default: disabled)") ("ssu", value()->default_value(true), "Enable SSU transport (default: enabled)") - ("ntcpproxy", value()->default_value(""), "Proxy URL for NTCP transport") + ("ntcpproxy", value()->default_value(""), "Proxy URL for NTCP transport") #ifdef _WIN32 ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") ("insomnia", bool_switch()->default_value(false), "Prevent system from sleeping (default: disabled)") @@ -97,7 +97,8 @@ namespace config { ("httpproxy.address", value()->default_value("127.0.0.1"), "HTTP Proxy listen address") ("httpproxy.port", value()->default_value(4444), "HTTP Proxy listen port") ("httpproxy.keys", value()->default_value(""), "File to persist HTTP Proxy keys") - ("httpproxy.signaturetype", value()->default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), "Signature type for new keys. 7 (EdDSA) by default") + ("httpproxy.signaturetype", value()-> + default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), "Signature type for new keys. 7 (EdDSA) by default") ("httpproxy.inbound.length", value()->default_value("3"), "HTTP proxy inbound tunnel length") ("httpproxy.outbound.length", value()->default_value("3"), "HTTP proxy outbound tunnel length") ("httpproxy.inbound.quantity", value()->default_value("5"), "HTTP proxy inbound tunnels quantity") @@ -114,7 +115,8 @@ namespace config { ("socksproxy.address", value()->default_value("127.0.0.1"), "SOCKS Proxy listen address") ("socksproxy.port", value()->default_value(4447), "SOCKS Proxy listen port") ("socksproxy.keys", value()->default_value(""), "File to persist SOCKS Proxy keys") - ("socksproxy.signaturetype", value()->default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), "Signature type for new keys. 7 (EdDSA) by default") + ("socksproxy.signaturetype", value()-> + default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), "Signature type for new keys. 7 (EdDSA) by default") ("socksproxy.inbound.length", value()->default_value("3"), "SOCKS proxy inbound tunnel length") ("socksproxy.outbound.length", value()->default_value("3"), "SOCKS proxy outbound tunnel length") ("socksproxy.inbound.quantity", value()->default_value("5"), "SOCKS proxy inbound tunnels quantity") diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 85d83bf0..e7c37fdd 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1148,7 +1148,7 @@ namespace transport NTCP2Server::NTCP2Server (): RunnableServiceWithWork ("NTCP2"), m_TerminationTimer (GetService ()), - m_Resolver(GetService ()) + m_Resolver(GetService ()), m_ProxyType(eNoProxy), m_ProxyEndpoint(nullptr) { } @@ -1164,25 +1164,23 @@ namespace transport StartIOService (); if(UsingProxy()) { - LogPrint(eLogError, "NTCP2: USING PROXY "); + LogPrint(eLogInfo, "NTCP2: Using proxy to connect to peers"); // TODO: resolve proxy until it is resolved boost::asio::ip::tcp::resolver::query q(m_ProxyAddress, std::to_string(m_ProxyPort)); boost::system::error_code e; auto itr = m_Resolver.resolve(q, e); if(e) - { LogPrint(eLogError, "NTCP2: Failed to resolve proxy ", e.message()); - } else { m_ProxyEndpoint.reset (new boost::asio::ip::tcp::endpoint(*itr)); if (m_ProxyEndpoint) - LogPrint(eLogError, "NTCP2: m_ProxyEndpoint ", *m_ProxyEndpoint); + LogPrint(eLogDebug, "NTCP2: m_ProxyEndpoint ", *m_ProxyEndpoint); } } else { - LogPrint(eLogError, "NTCP2: NOTUSING PROXY "); + LogPrint(eLogInfo, "NTCP2: Proxy is not used"); auto& addresses = context.GetRouterInfo ().GetAddresses (); for (const auto& address: addresses) { @@ -1426,6 +1424,31 @@ namespace transport } } + void NTCP2Server::ConnectWithProxy (const std::string& host, uint16_t port, RemoteAddressType addrtype, std::shared_ptr conn) + { + if(!m_ProxyEndpoint) return; + GetService().post([this, host, port, addrtype, conn]() { + if (this->AddNTCP2Session (conn)) + { + + auto timer = std::make_shared(GetService()); + auto timeout = NTCP_CONNECT_TIMEOUT * 5; + conn->SetTerminationTimeout(timeout * 2); + timer->expires_from_now (boost::posix_time::seconds(timeout)); + timer->async_wait ([conn, timeout](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + LogPrint (eLogInfo, "NTCP2: Not connected in ", timeout, " seconds"); + i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); + conn->Terminate (); + } + }); + conn->GetSocket ().async_connect (*m_ProxyEndpoint, std::bind (&NTCP2Server::HandleProxyConnect, this, std::placeholders::_1, conn, timer, host, port, addrtype)); + } + }); + } + void NTCP2Server::UseProxy(ProxyType proxytype, const std::string & addr, uint16_t port) { m_ProxyType = proxytype; @@ -1443,14 +1466,14 @@ namespace transport return; } switch (m_ProxyType) - { + { case eSocksProxy: { // TODO: support username/password auth etc static const uint8_t buff[3] = {0x05, 0x01, 0x00}; - boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff, 3), boost::asio::transfer_all(), - [] (const boost::system::error_code & ec, std::size_t transferred) - { + boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff, 3), boost::asio::transfer_all(), + [] (const boost::system::error_code & ec, std::size_t transferred) + { (void) transferred; if(ec) { @@ -1505,8 +1528,8 @@ namespace transport out << req.to_string(); boost::asio::async_write(conn->GetSocket(), writebuff.data(), boost::asio::transfer_all(), - [](const boost::system::error_code & ec, std::size_t transferred) - { + [](const boost::system::error_code & ec, std::size_t transferred) + { (void) transferred; if(ec) LogPrint(eLogError, "NTCP2: http proxy write error ", ec.message()); @@ -1549,38 +1572,13 @@ namespace transport } default: LogPrint(eLogError, "NTCP2: unknown proxy type, invalid state"); - } + } } - void NTCP2Server::ConnectWithProxy (const std::string& host, uint16_t port, RemoteAddressType addrtype, std::shared_ptr conn) - { - if(!m_ProxyEndpoint) return; - GetService().post([this, host, port, addrtype, conn]() { - if (this->AddNTCP2Session (conn)) - { - - auto timer = std::make_shared(GetService()); - auto timeout = NTCP_CONNECT_TIMEOUT * 5; - conn->SetTerminationTimeout(timeout * 2); - timer->expires_from_now (boost::posix_time::seconds(timeout)); - timer->async_wait ([conn, timeout](const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogInfo, "NTCP2: Not connected in ", timeout, " seconds"); - i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); - conn->Terminate (); - } - }); - conn->GetSocket ().async_connect (*m_ProxyEndpoint, std::bind (&NTCP2Server::HandleProxyConnect, this, std::placeholders::_1, conn, timer, host, port, addrtype)); - } - }); - } - void NTCP2Server::AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType addrtype) { // build request - size_t sz = 0; + size_t sz = 6; // header + port auto buff = std::make_shared >(256); auto readbuff = std::make_shared >(256); (*buff)[0] = 0x05; @@ -1590,37 +1588,27 @@ namespace transport if(addrtype == eIP4Address) { (*buff)[3] = 0x01; - auto addr = boost::asio::ip::address::from_string(host).to_v4(); - auto addrbytes = addr.to_bytes(); - auto addrsize = addrbytes.size(); - memcpy(buff->data () + 4, addrbytes.data(), addrsize); + auto addrbytes = boost::asio::ip::address::from_string(host).to_v4().to_bytes(); + sz += 4; + memcpy(buff->data () + 4, addrbytes.data(), 4); } else if (addrtype == eIP6Address) { (*buff)[3] = 0x04; - auto addr = boost::asio::ip::address::from_string(host).to_v6(); - auto addrbytes = addr.to_bytes(); - auto addrsize = addrbytes.size(); - memcpy(buff->data () + 4, addrbytes.data(), addrsize); + auto addrbytes = boost::asio::ip::address::from_string(host).to_v6().to_bytes(); + sz += 16; + memcpy(buff->data () + 4, addrbytes.data(), 16); } else if (addrtype == eHostname) { - (*buff)[3] = 0x03; - size_t addrsize = host.size(); - sz = addrsize + 1 + 4; - if (2 + sz > buff->size ()) - { - // too big - return; - } - (*buff)[4] = (uint8_t) addrsize; - memcpy(buff->data() + 5, host.c_str(), addrsize); + // We mustn't really fall here because all connections are made to IP addresses + LogPrint(eLogError, "NTCP2: Tried to connect to domain name via socks proxy"); + return; } - htobe16buf(buff->data () + sz, port); - sz += 2; - boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff->data (), sz), boost::asio::transfer_all(), - [](const boost::system::error_code & ec, std::size_t written) - { + htobe16buf(buff->data () + sz - 2, port); + boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff->data (), sz), boost::asio::transfer_all(), + [buff](const boost::system::error_code & ec, std::size_t written) + { if(ec) { LogPrint(eLogError, "NTCP2: failed to write handshake to socks proxy ", ec.message()); @@ -1628,9 +1616,9 @@ namespace transport } }); - boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 10), - [timer, conn, sz, readbuff](const boost::system::error_code & e, std::size_t transferred) - { + boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 10), + [timer, conn, sz, readbuff](const boost::system::error_code & e, std::size_t transferred) + { if(e) { LogPrint(eLogError, "NTCP2: socks proxy read error ", e.message()); @@ -1650,6 +1638,5 @@ namespace transport conn->Terminate(); }); } - } } diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index 11ae5995..10170fda 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -274,7 +274,7 @@ namespace transport std::map > m_NTCP2Sessions; std::list > m_PendingIncomingSessions; - ProxyType m_ProxyType =eNoProxy; + ProxyType m_ProxyType; std::string m_ProxyAddress; uint16_t m_ProxyPort; boost::asio::ip::tcp::resolver m_Resolver; diff --git a/libi2pd/NTCPSession.cpp b/libi2pd/NTCPSession.cpp index 9711ffb0..ae94553c 100644 --- a/libi2pd/NTCPSession.cpp +++ b/libi2pd/NTCPSession.cpp @@ -1193,7 +1193,6 @@ namespace transport void NTCPServer::AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType addrtype) { - // build request size_t sz = 0; uint8_t buff[256]; diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 804f26cb..bb74e2ad 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -176,7 +176,7 @@ namespace transport if (proxyurl.schema == "http") proxytype = NTCPServer::eHTTPProxy; - m_NTCPServer->UseProxy(proxytype, proxyurl.host, proxyurl.port) ; + m_NTCPServer->UseProxy(proxytype, proxyurl.host, proxyurl.port); m_NTCPServer->Start(); if(!m_NTCPServer->NetworkIsReady()) { @@ -194,7 +194,7 @@ namespace transport return; } // create NTCP2. TODO: move to acceptor - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) { if(!ntcp2proxy.empty()) @@ -209,7 +209,7 @@ namespace transport if (proxyurl.schema == "http") proxytype = NTCP2Server::eHTTPProxy; - m_NTCP2Server->UseProxy(proxytype, proxyurl.host, proxyurl.port) ; + m_NTCP2Server->UseProxy(proxytype, proxyurl.host, proxyurl.port); m_NTCP2Server->Start(); } else @@ -424,36 +424,36 @@ namespace transport { if (peer.router) // we have RI already { - if (!peer.numAttempts) // NTCP2 - { - peer.numAttempts++; - if (m_NTCP2Server) // we support NTCP2 - { - // NTCP2 have priority over NTCP - auto address = peer.router->GetNTCP2Address (true, !context.SupportsV6 ()); // published only - if (address) - { - auto s = std::make_shared (*m_NTCP2Server, peer.router); + if (!peer.numAttempts) // NTCP2 + { + peer.numAttempts++; + if (m_NTCP2Server) // we support NTCP2 + { + // NTCP2 have priority over NTCP + auto address = peer.router->GetNTCP2Address (true, !context.SupportsV6 ()); // published only + if (address) + { + auto s = std::make_shared (*m_NTCP2Server, peer.router); - if(m_NTCP2Server->UsingProxy()) - { - NTCP2Server::RemoteAddressType remote = NTCP2Server::eIP4Address; - std::string addr = address->host.to_string(); + if(m_NTCP2Server->UsingProxy()) + { + NTCP2Server::RemoteAddressType remote = NTCP2Server::eIP4Address; + std::string addr = address->host.to_string(); - if(address->host.is_v6()) - remote = NTCP2Server::eIP6Address; + if(address->host.is_v6()) + remote = NTCP2Server::eIP6Address; - m_NTCP2Server->ConnectWithProxy(addr, address->port, remote, s); - } - else - m_NTCP2Server->Connect (address->host, address->port, s); - return true; - } - } - } + m_NTCP2Server->ConnectWithProxy(addr, address->port, remote, s); + } + else + m_NTCP2Server->Connect (address->host, address->port, s); + return true; + } + } + } if (peer.numAttempts == 1) // NTCP1 { - peer.numAttempts++; + peer.numAttempts++; auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ()); if (address && m_NTCPServer) { diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index 9cb69590..a70ac2b7 100644 --- a/libi2pd_client/SOCKS.cpp +++ b/libi2pd_client/SOCKS.cpp @@ -118,7 +118,7 @@ namespace proxy boost::asio::const_buffers_1 GenerateSOCKS5SelectAuth(authMethods method); boost::asio::const_buffers_1 GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port); boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port); - boost::asio::const_buffers_1 GenerateUpstreamRequest(); + boost::asio::const_buffers_1 GenerateUpstreamRequest(); bool Socks5ChooseAuth(); void SocksRequestFailed(errTypes error); void SocksRequestSuccess(); @@ -128,27 +128,27 @@ namespace proxy void HandleStreamRequestComplete (std::shared_ptr stream); void ForwardSOCKS(); - void SocksUpstreamSuccess(); - void AsyncUpstreamSockRead(); - void SendUpstreamRequest(); + void SocksUpstreamSuccess(); + void AsyncUpstreamSockRead(); + void SendUpstreamRequest(); void HandleUpstreamData(uint8_t * buff, std::size_t len); - void HandleUpstreamSockSend(const boost::system::error_code & ecode, std::size_t bytes_transfered); - void HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); - void HandleUpstreamConnected(const boost::system::error_code & ecode, + void HandleUpstreamSockSend(const boost::system::error_code & ecode, std::size_t bytes_transfered); + void HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); + void HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr); - void HandleUpstreamResolved(const boost::system::error_code & ecode, + void HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr); - boost::asio::ip::tcp::resolver m_proxy_resolver; - uint8_t m_sock_buff[socks_buffer_size]; - std::shared_ptr m_sock, m_upstreamSock; + boost::asio::ip::tcp::resolver m_proxy_resolver; + uint8_t m_sock_buff[socks_buffer_size]; + std::shared_ptr m_sock, m_upstreamSock; std::shared_ptr m_stream; uint8_t *m_remaining_data; //Data left to be sent uint8_t *m_remaining_upstream_data; //upstream data left to be forwarded uint8_t m_response[7+max_socks_hostname_size]; - uint8_t m_upstream_response[SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE]; - uint8_t m_upstream_request[14+max_socks_hostname_size]; - std::size_t m_upstream_response_len; + uint8_t m_upstream_response[SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE]; + uint8_t m_upstream_request[14+max_socks_hostname_size]; + std::size_t m_upstream_response_len; address m_address; //Address std::size_t m_remaining_data_len; //Size of the data left to be sent uint32_t m_4aip; //Used in 4a requests @@ -161,11 +161,11 @@ namespace proxy cmdTypes m_cmd; // Command requested state m_state; const bool m_UseUpstreamProxy; // do we want to use the upstream proxy for non i2p addresses? - const std::string m_UpstreamProxyAddress; - const uint16_t m_UpstreamProxyPort; + const std::string m_UpstreamProxyAddress; + const uint16_t m_UpstreamProxyPort; public: - SOCKSHandler(SOCKSServer * parent, std::shared_ptr sock, const std::string & upstreamAddr, const uint16_t upstreamPort, const bool useUpstream) : + SOCKSHandler(SOCKSServer * parent, std::shared_ptr sock, const std::string & upstreamAddr, const uint16_t upstreamPort, const bool useUpstream) : I2PServiceHandler(parent), m_proxy_resolver(parent->GetService()), m_sock(sock), m_stream(nullptr), @@ -226,7 +226,7 @@ namespace proxy boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port) { - size_t size = 6; + size_t size = 6; // header + port assert(error <= SOCKS5_ADDR_UNSUP); m_response[0] = '\x05'; //Version m_response[1] = error; //Response code @@ -235,15 +235,15 @@ namespace proxy switch (type) { case ADDR_IPV4: - size = 10; + size += 4; htobe32buf(m_response + 4, addr.ip); break; case ADDR_IPV6: - size = 22; + size += 16; memcpy(m_response + 4, addr.ipv6, 16); break; case ADDR_DNS: - size = 7 + addr.dns.size; + size += (1 + addr.dns.size); /* name length + domain name */ m_response[4] = addr.dns.size; memcpy(m_response + 5, addr.dns.value, addr.dns.size); // replace type to IPv4 for support socks5 clients From f5712c419848bd9c4ea27dfe66253ad8a6c61a1f Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 30 Apr 2020 04:59:05 +0300 Subject: [PATCH 3655/6300] remove not needed initialization for pointer Signed-off-by: R4SAS --- libi2pd/NTCP2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index e7c37fdd..4efcc92f 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1148,7 +1148,7 @@ namespace transport NTCP2Server::NTCP2Server (): RunnableServiceWithWork ("NTCP2"), m_TerminationTimer (GetService ()), - m_Resolver(GetService ()), m_ProxyType(eNoProxy), m_ProxyEndpoint(nullptr) + m_Resolver(GetService ()), m_ProxyType(eNoProxy) { } @@ -1412,7 +1412,7 @@ namespace transport if ((*it)->IsEstablished () || (*it)->IsTerminationTimeoutExpired (ts)) { (*it)->Terminate (); - it = m_PendingIncomingSessions.erase (it); // etsablished of expired + it = m_PendingIncomingSessions.erase (it); // established of expired } else if ((*it)->IsTerminated ()) it = m_PendingIncomingSessions.erase (it); // already terminated From c36747603681f3919a7ee26a2cb68a2adecbd968 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 30 Apr 2020 16:21:49 +0300 Subject: [PATCH 3656/6300] [webconsole] fix printing information about ECIESx25519 tags/sessions Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index bc1eccd5..8b0323ce 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -399,28 +399,36 @@ namespace http { } } s << "
    \r\n"; - s << "Tags
    Incoming: " << dest->GetNumIncomingTags () << "
    "; + + s << "Tags
    \r\nIncoming: " << dest->GetNumIncomingTags () << "
    \r\n"; if (!dest->GetSessions ().empty ()) { std::stringstream tmp_s; uint32_t out_tags = 0; for (const auto& it: dest->GetSessions ()) { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "" << it.second->GetNumOutgoingTags () << "\r\n"; - out_tags = out_tags + it.second->GetNumOutgoingTags (); + out_tags += it.second->GetNumOutgoingTags (); } s << "
    \r\n\r\n" << "
    \r\n\r\n" << tmp_s.str () << "
    DestinationAmount
    \r\n
    \r\n
    \r\n"; } else s << "Outgoing: 0
    \r\n"; - auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags (); - if (numECIESx25519Tags > 0) - s << "Incoming ECIESx25519: " << numECIESx25519Tags << "
    "; - if (!dest->GetECIESx25519Sessions ().empty ()) - { - s << "
    \r\nECIESx25519Tags sessions
    "; - for (const auto& it: dest->GetECIESx25519Sessions ()) - s << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) - << " " << it.second->GetState () << "
    \r\n"; - } s << "
    \r\n"; + + auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags (); + if (numECIESx25519Tags > 0) { + s << "ECIESx25519
    \r\nIncoming Tags: " << numECIESx25519Tags << "
    \r\n"; + if (!dest->GetECIESx25519Sessions ().empty ()) + { + std::stringstream tmp_s; uint32_t ecies_sessions = 0; + for (const auto& it: dest->GetECIESx25519Sessions ()) { + tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "" << it.second->GetState () << "\r\n"; + ecies_sessions++; + } + s << "
    \r\n\r\n" + << "
    \r\n\r\n" << tmp_s.str () << "
    DestinationStatus
    \r\n
    \r\n
    \r\n"; + } else + s << "Tags sessions: 0
    \r\n"; + s << "
    \r\n"; + } } void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token) From c4f9f7da06123b678b40271d528697a78baf1e23 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 30 Apr 2020 13:45:26 -0400 Subject: [PATCH 3657/6300] fixed warning --- libi2pd/NTCP2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 4efcc92f..2091b373 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1148,7 +1148,7 @@ namespace transport NTCP2Server::NTCP2Server (): RunnableServiceWithWork ("NTCP2"), m_TerminationTimer (GetService ()), - m_Resolver(GetService ()), m_ProxyType(eNoProxy) + m_ProxyType(eNoProxy), m_Resolver(GetService ()) { } From 17e69e67b1391705e44348599524632ba44cf4d9 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 30 Apr 2020 15:38:15 -0400 Subject: [PATCH 3658/6300] create additional tags for NSR tagset --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 025340c8..56fc5948 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -600,14 +600,11 @@ namespace garlic #endif case eSessionStateEstablished: if (HandleExistingSessionMessage (buf, len, receiveTagset, index)) return true; - if (index < ECIESX25519_NSR_NUM_GENERATED_TAGS) - { - // check NSR just in case - LogPrint (eLogDebug, "Garlic: check for out of order NSR with index ", index); - return HandleNewOutgoingSessionReply (buf, len); - } - else - return false; + // check NSR just in case + LogPrint (eLogDebug, "Garlic: check for out of order NSR with index ", index); + if (receiveTagset->GetNextIndex () - index < ECIESX25519_NSR_NUM_GENERATED_TAGS/2) + GenerateMoreReceiveTags (receiveTagset, ECIESX25519_NSR_NUM_GENERATED_TAGS); + return HandleNewOutgoingSessionReply (buf, len); case eSessionStateNew: return HandleNewIncomingSession (buf, len); case eSessionStateNewSessionSent: @@ -620,12 +617,12 @@ namespace garlic std::shared_ptr ECIESX25519AEADRatchetSession::WrapSingleMessage (std::shared_ptr msg) { - auto m = NewI2NPMessage (); - m->Align (12); // in order to get buf aligned to 16 (12 + 4) - uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length auto payload = CreatePayload (msg, m_State != eSessionStateEstablished); size_t len = payload.size (); - + auto m = NewI2NPMessage (len + 100); // 96 + 4 + m->Align (12); // in order to get buf aligned to 16 (12 + 4) + uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length + switch (m_State) { case eSessionStateEstablished: From ec4e17f75c0109bcb3a0d70ff67b98e10ef2368f Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 30 Apr 2020 21:27:35 -0400 Subject: [PATCH 3659/6300] cleanup previous tagsets --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 10 +++++++++- libi2pd/ECIESX25519AEADRatchetSession.h | 21 +++++++++++++-------- libi2pd/Garlic.cpp | 2 +- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 56fc5948..788d4d6d 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -78,6 +78,12 @@ namespace garlic LogPrint (eLogError, "Garlic: Missing symmetric key for index ", index); } } + + void RatchetTagSet::Expire () + { + if (!m_ExpirationTimestamp) + m_ExpirationTimestamp = i2p::util::GetSecondsSinceEpoch () + ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT; + } ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet): GarlicRoutingSession (owner, attachLeaseSet) @@ -311,7 +317,8 @@ namespace garlic newTagset->SetTagSetID (tagsetID); newTagset->DHInitialize (receiveTagset->GetNextRootKey (), tagsetKey); newTagset->NextSessionTagRatchet (); - GenerateMoreReceiveTags (newTagset, ECIESX25519_MAX_NUM_GENERATED_TAGS); + GenerateMoreReceiveTags (newTagset, ECIESX25519_MAX_NUM_GENERATED_TAGS); + receiveTagset->Expire (); LogPrint (eLogDebug, "Garlic: next receive tagset ", tagsetID, " created"); } } @@ -608,6 +615,7 @@ namespace garlic case eSessionStateNew: return HandleNewIncomingSession (buf, len); case eSessionStateNewSessionSent: + receiveTagset->Expire (); // NSR tagset return HandleNewOutgoingSessionReply (buf, len); default: return false; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 6dd842e4..cf4eb3d7 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -17,6 +17,15 @@ namespace i2p { namespace garlic { + const int ECIESX25519_RESTART_TIMEOUT = 120; // number of second of inactivity we should restart after + const int ECIESX25519_EXPIRATION_TIMEOUT = 480; // in seconds + const int ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT = 600; // in seconds + const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180 + const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 1024; // number of tags we request new tagset after + const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; + const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 160; + const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; + class ECIESX25519AEADRatchetSession; class RatchetTagSet { @@ -34,6 +43,9 @@ namespace garlic std::shared_ptr GetSession () { return m_Session.lock (); }; int GetTagSetID () const { return m_TagSetID; }; void SetTagSetID (int tagsetID) { m_TagSetID = tagsetID; }; + + void Expire (); + bool IsExpired (uint64_t ts) const { return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; }; private: @@ -52,6 +64,7 @@ namespace garlic std::unordered_map > m_ItermediateSymmKeys; std::weak_ptr m_Session; int m_TagSetID = 0; + uint64_t m_ExpirationTimestamp = 0; }; enum ECIESx25519BlockType @@ -71,14 +84,6 @@ namespace garlic const uint8_t ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG = 0x02; const uint8_t ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG = 0x04; - const int ECIESX25519_RESTART_TIMEOUT = 120; // number of second of inactivity we should restart after - const int ECIESX25519_EXPIRATION_TIMEOUT = 480; // in seconds - const int ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT = 600; // in seconds - const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 1024; // number of tags we request new tagset after - const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; - const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 160; - const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; - class ECIESX25519AEADRatchetSession: public GarlicRoutingSession, public std::enable_shared_from_this { enum SessionState diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index fe5e8d69..fb3451d0 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -788,7 +788,7 @@ namespace garlic // ECIESx25519 for (auto it = m_ECIESx25519Tags.begin (); it != m_ECIESx25519Tags.end ();) { - if (ts > it->second.creationTime + ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT) + if (it->second.tagset->IsExpired (ts) || ts > it->second.creationTime + ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT) it = m_ECIESx25519Tags.erase (it); else ++it; From d48db501e03a81685e3419186f7831107df8e716 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 1 May 2020 07:33:05 -0400 Subject: [PATCH 3660/6300] max payload is always 1730 --- libi2pd/Streaming.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 3efcfb99..3214e81b 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -505,7 +505,7 @@ namespace stream memset (signature, 0, signatureLen); // zeroes for now size += signatureLen; // signature htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size - size += m_SendBuffer.Get (packet + size, STREAMING_MTU - size); // payload + size += m_SendBuffer.Get (packet + size, STREAMING_MTU); // payload m_LocalDestination.GetOwner ()->Sign (packet, size, signature); } else @@ -515,7 +515,7 @@ namespace stream size += 2; // flags htobuf16 (packet + size, 0); // no options size += 2; // options size - size += m_SendBuffer.Get(packet + size, STREAMING_MTU - size); // payload + size += m_SendBuffer.Get(packet + size, STREAMING_MTU); // payload } p->len = size; packets.push_back (p); From c49e544781fc615879fd51d68bb338e9ec6afcab Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 1 May 2020 14:30:56 -0400 Subject: [PATCH 3661/6300] allow longer families --- libi2pd/Family.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/Family.cpp b/libi2pd/Family.cpp index b7cbf7d4..bc99bb52 100644 --- a/libi2pd/Family.cpp +++ b/libi2pd/Family.cpp @@ -113,9 +113,9 @@ namespace data bool Families::VerifyFamily (const std::string& family, const IdentHash& ident, const char * signature, const char * key) { - uint8_t buf[50], signatureBuf[64]; + uint8_t buf[100], signatureBuf[64]; size_t len = family.length (), signatureLen = strlen (signature); - if (len + 32 > 50) + if (len + 32 > 100) { LogPrint (eLogError, "Family: ", family, " is too long"); return false; From e301387896cec0f8a30753adc7ab86d248879f79 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 2 May 2020 11:13:40 -0400 Subject: [PATCH 3662/6300] don't calculate checsum for Data message send through ECIESX25519AEADRatchet session --- libi2pd/ECIESX25519AEADRatchetSession.h | 2 ++ libi2pd/Garlic.h | 1 + libi2pd/I2NPProtocol.cpp | 4 ++-- libi2pd/I2NPProtocol.h | 2 +- libi2pd/Streaming.cpp | 10 ++++++---- libi2pd/Streaming.h | 7 +++---- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index cf4eb3d7..79e920b5 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -122,6 +122,8 @@ namespace garlic bool CheckExpired (uint64_t ts); // true is expired bool CanBeRestarted (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_RESTART_TIMEOUT; } + bool IsRatchets () const { return true; }; + private: void ResetKeys (); diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 2c71abe8..6c81dabf 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -105,6 +105,7 @@ namespace garlic virtual std::shared_ptr WrapSingleMessage (std::shared_ptr msg) = 0; virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession virtual bool MessageConfirmed (uint32_t msgID); + virtual bool IsRatchets () const { return false; }; void SetLeaseSetUpdated () { diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 0e7c245e..933fa707 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -39,14 +39,14 @@ namespace i2p return (len < I2NP_MAX_SHORT_MESSAGE_SIZE - I2NP_HEADER_SIZE - 2) ? NewI2NPShortMessage () : NewI2NPMessage (); } - void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID) + void I2NPMessage::FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID, bool checksum) { SetTypeID (msgType); if (!replyMsgID) RAND_bytes ((uint8_t *)&replyMsgID, 4); SetMsgID (replyMsgID); SetExpiration (i2p::util::GetMillisecondsSinceEpoch () + I2NP_MESSAGE_EXPIRATION_TIMEOUT); UpdateSize (); - UpdateChks (); + if (checksum) UpdateChks (); } void I2NPMessage::RenewI2NPMessageHeader () diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 2fca1538..c8957b5f 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -218,7 +218,7 @@ namespace tunnel memcpy (ntcp2 + I2NP_HEADER_TYPEID_OFFSET, GetHeader () + I2NP_HEADER_TYPEID_OFFSET, 5); // typeid + msgid } - void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0); + void FillI2NPMessageHeader (I2NPMessageType msgType, uint32_t replyMsgID = 0, bool checksum = true); void RenewI2NPMessageHeader (); bool IsExpired () const; }; diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 3214e81b..6b547170 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -756,7 +756,8 @@ namespace stream std::vector msgs; for (auto it: packets) { - auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage (it->GetBuffer (), it->GetLength (), m_Port)); + auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage ( + it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets ())); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, @@ -1207,9 +1208,10 @@ namespace stream DeletePacket (uncompressed); } - std::shared_ptr StreamingDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort) + std::shared_ptr StreamingDestination::CreateDataMessage ( + const uint8_t * payload, size_t len, uint16_t toPort, bool checksum) { - auto msg = NewI2NPShortMessage (); + auto msg = m_I2NPMsgsPool.AcquireShared (); if (!m_Gzip || len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE) m_Deflator.SetCompressionLevel (Z_NO_COMPRESSION); else @@ -1225,7 +1227,7 @@ namespace stream htobe16buf (buf + 6, toPort); // destination port buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol msg->len += size; - msg->FillI2NPMessageHeader (eI2NPData); + msg->FillI2NPMessageHeader (eI2NPData, 0, checksum); } else msg = nullptr; diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index 985a7eca..64e4c18d 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -255,20 +255,18 @@ namespace stream void ResetAcceptor (); bool IsAcceptorSet () const { return m_Acceptor != nullptr; }; void AcceptOnce (const Acceptor& acceptor); + void AcceptOnceAcceptor (std::shared_ptr stream, Acceptor acceptor, Acceptor prev); std::shared_ptr GetOwner () const { return m_Owner; }; void SetOwner (std::shared_ptr owner) { m_Owner = owner; }; uint16_t GetLocalPort () const { return m_LocalPort; }; void HandleDataMessagePayload (const uint8_t * buf, size_t len); - std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort); + std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort, bool checksum = true); Packet * NewPacket () { return m_PacketsPool.Acquire(); } void DeletePacket (Packet * p) { return m_PacketsPool.Release(p); } - - void AcceptOnceAcceptor (std::shared_ptr stream, Acceptor acceptor, Acceptor prev); - private: void HandleNextPacket (Packet * packet); @@ -289,6 +287,7 @@ namespace stream std::map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN i2p::util::MemoryPool m_PacketsPool; + i2p::util::MemoryPool > m_I2NPMsgsPool; public: From 1eead0e885d2fbeaa4f60aea1682eab895c85dff Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 2 May 2020 21:18:44 -0400 Subject: [PATCH 3663/6300] GzipNoCompression witout zlib calls --- libi2pd/Gzip.cpp | 15 +++++++++++++++ libi2pd/Gzip.h | 9 +++++++-- libi2pd/Streaming.cpp | 8 +++----- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/libi2pd/Gzip.cpp b/libi2pd/Gzip.cpp index 1c06e941..a03640a7 100644 --- a/libi2pd/Gzip.cpp +++ b/libi2pd/Gzip.cpp @@ -10,6 +10,7 @@ #include /* memset */ #include #include "Log.h" +#include "I2PEndian.h" #include "Gzip.h" namespace i2p @@ -111,5 +112,19 @@ namespace data LogPrint (eLogError, "Gzip: Deflate error ", err); return 0; } + + size_t GzipNoCompression (const uint8_t * in, uint16_t inLen, uint8_t * out, size_t outLen) + { + static const uint8_t gzipHeader[11] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x01 }; + if (outLen < (size_t)inLen + 23) return 0; + memcpy (out, gzipHeader, 11); + htole16buf (out + 11, inLen); + htole16buf (out + 13, 0xffff - inLen); + memcpy (out + 15, in, inLen); + htole32buf (out + inLen + 15, crc32 (0, in, inLen)); + htole32buf (out + inLen + 19, inLen); + return inLen + 23; + } + } // data } // i2p diff --git a/libi2pd/Gzip.h b/libi2pd/Gzip.h index 35661abe..cf08d920 100644 --- a/libi2pd/Gzip.h +++ b/libi2pd/Gzip.h @@ -3,8 +3,10 @@ #include -namespace i2p { -namespace data { +namespace i2p +{ +namespace data +{ class GzipInflator { public: @@ -38,6 +40,9 @@ namespace data { z_stream m_Deflator; bool m_IsDirty; }; + + size_t GzipNoCompression (const uint8_t * in, uint16_t inLen, uint8_t * out, size_t outLen); // for < 64K + } // data } // i2p diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 6b547170..2a4175f4 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -1212,14 +1212,12 @@ namespace stream const uint8_t * payload, size_t len, uint16_t toPort, bool checksum) { auto msg = m_I2NPMsgsPool.AcquireShared (); - if (!m_Gzip || len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE) - m_Deflator.SetCompressionLevel (Z_NO_COMPRESSION); - else - m_Deflator.SetCompressionLevel (Z_DEFAULT_COMPRESSION); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for lengthlength msg->len += 4; - size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); + size_t size = (!m_Gzip || len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE)? + i2p::data::GzipNoCompression (payload, len, buf, msg->maxLen - msg->len): + m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); if (size) { htobe32buf (msg->GetPayload (), size); // length From dff510c181469f4bd6d4d0063ea3f3cc376d8cf2 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 3 May 2020 09:27:17 -0400 Subject: [PATCH 3664/6300] set best compression for RouterInfo --- libi2pd/Gzip.cpp | 5 ++++- libi2pd/I2NPProtocol.cpp | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/libi2pd/Gzip.cpp b/libi2pd/Gzip.cpp index a03640a7..2ca711b0 100644 --- a/libi2pd/Gzip.cpp +++ b/libi2pd/Gzip.cpp @@ -107,7 +107,10 @@ namespace data m_Deflator.avail_out = outLen; int err; if ((err = deflate (&m_Deflator, Z_FINISH)) == Z_STREAM_END) + { + out[9] = 0xff; // OS is always unknown return outLen - m_Deflator.avail_out; + } // else LogPrint (eLogError, "Gzip: Deflate error ", err); return 0; @@ -115,7 +118,7 @@ namespace data size_t GzipNoCompression (const uint8_t * in, uint16_t inLen, uint8_t * out, size_t outLen) { - static const uint8_t gzipHeader[11] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x01 }; + static const uint8_t gzipHeader[11] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01 }; if (outLen < (size_t)inLen + 23) return 0; memcpy (out, gzipHeader, 11); htole16buf (out + 11, inLen); diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 933fa707..a16ce580 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -250,6 +250,7 @@ namespace i2p buf += 2; m->len += (buf - payload); // payload size i2p::data::GzipDeflator deflator; + deflator.SetCompressionLevel (Z_BEST_COMPRESSION); size_t size = deflator.Deflate (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); if (size) { From b7ba8f8e93a58dadf9eadfba3824149e5435bc82 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 3 May 2020 13:23:08 -0400 Subject: [PATCH 3665/6300] precalculate initial h and ck --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 788d4d6d..7fdf351e 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -97,11 +97,18 @@ namespace garlic void ECIESX25519AEADRatchetSession::ResetKeys () { - // TODO : use precalculated hashes - static const char protocolName[41] = "Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"; // 40 bytes - SHA256 ((const uint8_t *)protocolName, 40, m_H); - memcpy (m_CK, m_H, 32); - SHA256 (m_H, 32, m_H); + static const uint8_t protocolNameHash[32] = + { + 0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba, + 0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c + }; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes + static const uint8_t hh[32] = + { + 0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32, + 0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c + }; // SHA256 (protocolNameHash) + memcpy (m_CK, protocolNameHash, 32); + memcpy (m_H, hh, 32); } void ECIESX25519AEADRatchetSession::MixHash (const uint8_t * buf, size_t len) From 4d48d35ad7901346a3a2936d3d6f5294bb69995f Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 17 May 2019 10:28:58 +0300 Subject: [PATCH 3666/6300] [SSU] handle socket binding errors Signed-off-by: R4SAS --- libi2pd/NTCP2.cpp | 8 ++++---- libi2pd/NTCPSession.cpp | 7 ++++--- libi2pd/SSU.cpp | 28 +++++++++++++++++++--------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 2091b373..195f664f 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1195,11 +1195,11 @@ namespace transport } catch ( std::exception & ex ) { - LogPrint(eLogError, "NTCP2: Failed to bind to ip4 port ",address->port, ex.what()); + LogPrint(eLogError, "NTCP2: Failed to bind to v4 port ",address->port, ex.what()); continue; } - LogPrint (eLogInfo, "NTCP2: Start listening TCP port ", address->port); + LogPrint (eLogInfo, "NTCP2: Start listening v6 TCP port ", address->port); auto conn = std::make_shared(*this); m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, conn, std::placeholders::_1)); } @@ -1214,11 +1214,11 @@ namespace transport m_NTCP2V6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port)); m_NTCP2V6Acceptor->listen (); - LogPrint (eLogInfo, "NTCP2: Start listening V6 TCP port ", address->port); + LogPrint (eLogInfo, "NTCP2: Start listening v6 TCP port ", address->port); auto conn = std::make_shared (*this); m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, conn, std::placeholders::_1)); } catch ( std::exception & ex ) { - LogPrint(eLogError, "NTCP2: failed to bind to ip6 port ", address->port); + LogPrint(eLogError, "NTCP2: failed to bind to v6 port ", address->port); continue; } } diff --git a/libi2pd/NTCPSession.cpp b/libi2pd/NTCPSession.cpp index ae94553c..9fe5d415 100644 --- a/libi2pd/NTCPSession.cpp +++ b/libi2pd/NTCPSession.cpp @@ -820,9 +820,10 @@ namespace transport try { m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port)); + LogPrint (eLogInfo, "NTCP: Start listening v6 TCP port ", address->port); } catch ( std::exception & ex ) { /** fail to bind ip4 */ - LogPrint(eLogError, "NTCP: Failed to bind to ip4 port ",address->port, ex.what()); + LogPrint(eLogError, "NTCP: Failed to bind to v4 port ",address->port, ex.what()); continue; } @@ -841,11 +842,11 @@ namespace transport m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port)); m_NTCPV6Acceptor->listen (); - LogPrint (eLogInfo, "NTCP: Start listening V6 TCP port ", address->port); + LogPrint (eLogInfo, "NTCP: Start listening v6 TCP port ", address->port); auto conn = std::make_shared (*this); m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this, conn, std::placeholders::_1)); } catch ( std::exception & ex ) { - LogPrint(eLogError, "NTCP: failed to bind to ip6 port ", address->port); + LogPrint(eLogError, "NTCP: failed to bind to v6 port ", address->port); continue; } } diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index 4eb958de..f101443f 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -45,19 +45,29 @@ namespace transport void SSUServer::OpenSocket () { - m_Socket.open (boost::asio::ip::udp::v4()); - m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); - m_Socket.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); - m_Socket.bind (m_Endpoint); + try { + m_Socket.open (boost::asio::ip::udp::v4()); + m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); + m_Socket.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); + m_Socket.bind (m_Endpoint); + LogPrint (eLogInfo, "SSU: Start listening v4 port ", m_Endpoint.port()); + } catch ( std::exception & ex ) { + LogPrint (eLogError, "SSU: failed to bind to v4 port ", m_Endpoint.port(), ": ", ex.what()); + } } void SSUServer::OpenSocketV6 () { - m_SocketV6.open (boost::asio::ip::udp::v6()); - m_SocketV6.set_option (boost::asio::ip::v6_only (true)); - m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); - m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); - m_SocketV6.bind (m_EndpointV6); + try { + m_SocketV6.open (boost::asio::ip::udp::v6()); + m_SocketV6.set_option (boost::asio::ip::v6_only (true)); + m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); + m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); + m_SocketV6.bind (m_EndpointV6); + LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port()); + } catch ( std::exception & ex ) { + LogPrint (eLogError, "SSU: failed to bind to v6 port ", m_EndpointV6.port(), ": ", ex.what()); + } } void SSUServer::Start () From d991cc3b96b2560bacaa27f6c55b8db17057a85c Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 17 May 2019 11:04:44 +0300 Subject: [PATCH 3667/6300] [services] handle binding errors in tunnels, webconsole Signed-off-by: R4SAS --- daemon/Daemon.cpp | 2 +- daemon/HTTPServer.cpp | 13 +++++++++---- libi2pd_client/I2PService.cpp | 10 +++++++--- libi2pd_client/I2PTunnel.cpp | 2 +- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 2c6b34d7..b0596634 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -317,7 +317,7 @@ namespace i2p bool http; i2p::config::GetOption("http.enabled", http); if (http) { std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); - uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); + uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); LogPrint(eLogInfo, "Daemon: starting HTTP Server at ", httpAddr, ":", httpPort); d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); d.httpServer->Start(); diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 8b0323ce..3104dba8 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1226,10 +1226,15 @@ namespace http { i2p::config::SetOption("http.pass", pass); LogPrint(eLogInfo, "HTTPServer: password set to ", pass); } - m_IsRunning = true; - m_Thread = std::unique_ptr(new std::thread (std::bind (&HTTPServer::Run, this))); - m_Acceptor.listen (); - Accept (); + + try { + m_IsRunning = true; + m_Thread = std::unique_ptr(new std::thread (std::bind (&HTTPServer::Run, this))); + m_Acceptor.listen (); + Accept (); + } catch (std::exception& ex) { + LogPrint (eLogError, "HTTPServer: failed to start webconsole: ", ex.what ()); + } } void HTTPServer::Stop () diff --git a/libi2pd_client/I2PService.cpp b/libi2pd_client/I2PService.cpp index 7157020f..c795fc5f 100644 --- a/libi2pd_client/I2PService.cpp +++ b/libi2pd_client/I2PService.cpp @@ -283,10 +283,14 @@ namespace client void TCPIPAcceptor::Start () { m_Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), m_LocalEndpoint)); - //update the local end point in case port has been set zero and got updated now + // update the local end point in case port has been set zero and got updated now m_LocalEndpoint = m_Acceptor->local_endpoint(); - m_Acceptor->listen (); - Accept (); + try { + m_Acceptor->listen (); + Accept (); + } catch (std::exception& ex) { + LogPrint (eLogError, "I2PService: failed to start ", GetName(), " acceptor: ", ex.what ()); + } } void TCPIPAcceptor::Stop () diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 290ce11e..b7487441 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -389,7 +389,7 @@ namespace client } - /* This handler tries to stablish a connection with the desired server and dies if it fails to do so */ + /* This handler tries to establish a connection with the desired server and dies if it fails to do so */ class I2PClientTunnelHandler: public I2PServiceHandler, public std::enable_shared_from_this { public: From 42d4781a9685266f489c6e509d614caf5352db77 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 5 May 2020 02:36:34 +0300 Subject: [PATCH 3668/6300] [windows] add binding exceptions messagebox notifications, update exceptions handling code Signed-off-by: R4SAS --- Win32/Win32App.cpp | 3 +- daemon/Daemon.cpp | 41 +++++++----- daemon/HTTPServer.cpp | 12 ++-- libi2pd/Log.h | 43 ++++++++++++ libi2pd/NTCP2.cpp | 10 ++- libi2pd/NTCPSession.cpp | 10 ++- libi2pd/SSU.cpp | 14 ++-- libi2pd_client/ClientContext.cpp | 108 ++++++++++++++++--------------- libi2pd_client/I2PService.cpp | 16 ++--- 9 files changed, 160 insertions(+), 97 deletions(-) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index dd614df2..7ae4e419 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -432,7 +432,7 @@ namespace win32 HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); - UnSubscribeFromEvents(); + // UnSubscribeFromEvents(); // TODO: understand why unsubscribing crashes app UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); } @@ -451,6 +451,5 @@ namespace win32 PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_STOP_GRACEFUL_SHUTDOWN, 0), 0); return hWnd; } - } } diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index b0596634..75829ef6 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -71,18 +71,13 @@ namespace i2p i2p::fs::Init(); datadir = i2p::fs::GetDataDir(); - // TODO: drop old name detection in v2.8.0 + if (config == "") { - config = i2p::fs::DataDirPath("i2p.conf"); - if (i2p::fs::Exists (config)) { - LogPrint(eLogWarning, "Daemon: please rename i2p.conf to i2pd.conf here: ", config); - } else { - config = i2p::fs::DataDirPath("i2pd.conf"); - if (!i2p::fs::Exists (config)) { - // use i2pd.conf only if exists - config = ""; /* reset */ - } + config = i2p::fs::DataDirPath("i2pd.conf"); + if (!i2p::fs::Exists (config)) { + // use i2pd.conf only if exists + config = ""; /* reset */ } } @@ -265,7 +260,7 @@ namespace i2p restricted = idents.size() > 0; } if(!restricted) - LogPrint(eLogError, "Daemon: no trusted routers of families specififed"); + LogPrint(eLogError, "Daemon: no trusted routers of families specified"); } bool hidden; i2p::config::GetOption("trust.hidden", hidden); @@ -318,9 +313,16 @@ namespace i2p if (http) { std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); - LogPrint(eLogInfo, "Daemon: starting HTTP Server at ", httpAddr, ":", httpPort); - d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); - d.httpServer->Start(); + LogPrint(eLogInfo, "Daemon: starting webconsole at ", httpAddr, ":", httpPort); + try { + d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); + d.httpServer->Start(); + } catch (std::exception& ex) { + LogPrint (eLogError, "Daemon: failed to start webconsole: ", ex.what ()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start webconsole at ", httpAddr, ":", httpPort, ": ", ex.what ()); +#endif + } } @@ -336,8 +338,15 @@ namespace i2p std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr); uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort); LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort); - d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); - d.m_I2PControlService->Start (); + try { + d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); + d.m_I2PControlService->Start (); + } catch (std::exception& ex) { + LogPrint (eLogError, "Daemon: failed to start I2PControl: ", ex.what ()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start I2PControl service at ", i2pcpAddr, ":", i2pcpPort, ": ", ex.what ()); +#endif + } } return true; } diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 3104dba8..a1cba3ce 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1227,14 +1227,10 @@ namespace http { LogPrint(eLogInfo, "HTTPServer: password set to ", pass); } - try { - m_IsRunning = true; - m_Thread = std::unique_ptr(new std::thread (std::bind (&HTTPServer::Run, this))); - m_Acceptor.listen (); - Accept (); - } catch (std::exception& ex) { - LogPrint (eLogError, "HTTPServer: failed to start webconsole: ", ex.what ()); - } + m_Thread = std::unique_ptr(new std::thread (std::bind (&HTTPServer::Run, this))); + m_Acceptor.listen (); + Accept (); + m_IsRunning = true; } void HTTPServer::Stop () diff --git a/libi2pd/Log.h b/libi2pd/Log.h index ba10b5e9..8343e3b3 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -23,6 +23,11 @@ #include #endif +#ifdef WIN32_APP +#include +#include "Win32/Win32App.h" +#endif + enum LogLevel { eLogNone = 0, @@ -197,4 +202,42 @@ void LogPrint (LogLevel level, TArgs&&... args) noexcept log.Append(msg); } +#ifdef WIN32_APP +/** + * @brief Show message box for user with message in it + * @param level Message level (eLogError, eLogInfo, ...) + * @param args Array of message parts + */ +template +void ShowMessageBox (LogLevel level, TArgs&&... args) noexcept +{ + // fold message to single string + std::stringstream ss(""); + +#if (__cplusplus >= 201703L) // C++ 17 or higher + (LogPrint (ss, std::forward(args)), ...); +#else + LogPrint (ss, std::forward(args)...); +#endif + + HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); + if (!hWnd) hWnd = NULL; + + UINT uType; + switch (level) { + case eLogError : + case eLogWarning : + uType = MB_ICONWARNING; + break; + case eLogNone : + case eLogInfo : + case eLogDebug : + default : + uType = MB_ICONINFORMATION; + break; + } + MessageBox( hWnd, TEXT(ss.str ().c_str ()), TEXT("i2pd"), uType | MB_TASKMODAL | MB_OK ); +} +#endif // WIN32_APP + #endif // LOG_H__ diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 195f664f..dba73e50 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1195,7 +1195,10 @@ namespace transport } catch ( std::exception & ex ) { - LogPrint(eLogError, "NTCP2: Failed to bind to v4 port ",address->port, ex.what()); + LogPrint(eLogError, "NTCP2: Failed to bind to v4 port ", address->port, ex.what()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start IPv4 NTCP2 transport at port ", address->port, ": ", ex.what ()); +#endif continue; } @@ -1218,7 +1221,10 @@ namespace transport auto conn = std::make_shared (*this); m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, conn, std::placeholders::_1)); } catch ( std::exception & ex ) { - LogPrint(eLogError, "NTCP2: failed to bind to v6 port ", address->port); + LogPrint(eLogError, "NTCP2: failed to bind to v6 port ", address->port, ": ", ex.what()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start IPv6 NTCP2 transport at port ", address->port, ": ", ex.what ()); +#endif continue; } } diff --git a/libi2pd/NTCPSession.cpp b/libi2pd/NTCPSession.cpp index 9fe5d415..fe420ac6 100644 --- a/libi2pd/NTCPSession.cpp +++ b/libi2pd/NTCPSession.cpp @@ -823,7 +823,10 @@ namespace transport LogPrint (eLogInfo, "NTCP: Start listening v6 TCP port ", address->port); } catch ( std::exception & ex ) { /** fail to bind ip4 */ - LogPrint(eLogError, "NTCP: Failed to bind to v4 port ",address->port, ex.what()); + LogPrint(eLogError, "NTCP: Failed to bind to v4 port ", address->port, ": ", ex.what()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start IPv4 NTCP transport at port ", address->port, ": ", ex.what ()); +#endif continue; } @@ -846,7 +849,10 @@ namespace transport auto conn = std::make_shared (*this); m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this, conn, std::placeholders::_1)); } catch ( std::exception & ex ) { - LogPrint(eLogError, "NTCP: failed to bind to v6 port ", address->port); + LogPrint(eLogError, "NTCP: failed to bind to v6 port ", address->port, ": ", ex.what()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start IPv6 NTCP transport at port ", address->port, ": ", ex.what ()); +#endif continue; } } diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index f101443f..ae2caf62 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -53,6 +53,9 @@ namespace transport LogPrint (eLogInfo, "SSU: Start listening v4 port ", m_Endpoint.port()); } catch ( std::exception & ex ) { LogPrint (eLogError, "SSU: failed to bind to v4 port ", m_Endpoint.port(), ": ", ex.what()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start IPv4 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); +#endif } } @@ -67,6 +70,9 @@ namespace transport LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port()); } catch ( std::exception & ex ) { LogPrint (eLogError, "SSU: failed to bind to v6 port ", m_EndpointV6.port(), ": ", ex.what()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start IPv6 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); +#endif } } @@ -197,7 +203,7 @@ namespace transport m_SocketV6.close (); OpenSocketV6 (); ReceiveV6 (); - } + } } } } @@ -580,7 +586,7 @@ namespace transport { return session->GetState () == eSessionStateEstablished && session != excluded; } - ); + ); } template @@ -604,7 +610,7 @@ namespace transport { return session->GetState () == eSessionStateEstablished && session != excluded; } - ); + ); } std::set SSUServer::FindIntroducers (int maxNumIntroducers) @@ -620,7 +626,7 @@ namespace transport session->GetState () == eSessionStateEstablished && ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION; } - ); + ); if (session) { ret.insert (session.get ()); diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index fa0532c3..1e8b23cb 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -58,14 +58,14 @@ namespace client uint16_t samPort; i2p::config::GetOption("sam.port", samPort); bool singleThread; i2p::config::GetOption("sam.singlethread", singleThread); LogPrint(eLogInfo, "Clients: starting SAM bridge at ", samAddr, ":", samPort); - try - { + try { m_SamBridge = new SAMBridge (samAddr, samPort, singleThread); - m_SamBridge->Start (); - } - catch (std::exception& e) - { - LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what()); + m_SamBridge->Start (); + } catch (std::exception& e) { + LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start SAM bridge at ", samAddr, ":", samPort, ": ", e.what ()); +#endif } } @@ -76,10 +76,13 @@ namespace client uint16_t bobPort; i2p::config::GetOption("bob.port", bobPort); LogPrint(eLogInfo, "Clients: starting BOB command channel at ", bobAddr, ":", bobPort); try { - m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); - m_BOBCommandChannel->Start (); + m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); + m_BOBCommandChannel->Start (); } catch (std::exception& e) { - LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what()); + LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start BOB bridge at ", bobAddr, ":", bobPort, ": ", e.what ()); +#endif } } @@ -90,14 +93,14 @@ namespace client std::string i2cpAddr; i2p::config::GetOption("i2cp.address", i2cpAddr); uint16_t i2cpPort; i2p::config::GetOption("i2cp.port", i2cpPort); LogPrint(eLogInfo, "Clients: starting I2CP at ", i2cpAddr, ":", i2cpPort); - try - { + try { m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort); m_I2CPServer->Start (); - } - catch (std::exception& e) - { + } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in I2CP: ", e.what()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start I2CP at ", i2cpAddr, ":", i2cpPort, ": ", e.what ()); +#endif } } @@ -501,16 +504,12 @@ namespace client LogPrint (eLogInfo, "Clients: ", numServerTunnels, " I2P server tunnels created"); } - void ClientContext::ReadTunnels (const std::string& tunConf, int& numClientTunnels, int& numServerTunnels) { boost::property_tree::ptree pt; - try - { + try { boost::property_tree::read_ini (tunConf, pt); - } - catch (std::exception& ex) - { + } catch (std::exception& ex) { LogPrint (eLogWarning, "Clients: Can't read ", tunConf, ": ", ex.what ()); return; } @@ -565,14 +564,11 @@ namespace client // TODO: hostnames boost::asio::ip::udp::endpoint end(boost::asio::ip::address::from_string(address), port); if (!localDestination) - { localDestination = m_SharedLocalDestination; - } + auto clientTunnel = std::make_shared(name, dest, end, localDestination, destinationPort); if(m_ClientForwards.insert(std::make_pair(end, clientTunnel)).second) - { clientTunnel->Start(); - } else LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); @@ -598,7 +594,10 @@ namespace client } else if (type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS) { - LogPrint(eLogError, "Clients: I2P Client tunnel websocks is deprecated"); + LogPrint(eLogWarning, "Clients: I2P Client tunnel websocks is deprecated, not starting ", name, " tunnel"); +#ifdef WIN32_APP + ShowMessageBox (eLogWarning, "Skipped websocks tunnel ", name, " because it's support is discontinued."); +#endif continue; } else @@ -608,6 +607,7 @@ namespace client clientTunnel = tun; clientEndpoint = tun->GetLocalEndpoint (); } + uint32_t timeout = section.second.get(I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT, 0); if(timeout) { @@ -657,8 +657,8 @@ namespace client // I2CP std::map options; - ReadI2CPOptions (section, options); - + ReadI2CPOptions (section, options); + std::shared_ptr localDestination = nullptr; i2p::data::PrivateKeys k; if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) @@ -745,12 +745,14 @@ namespace client } else - LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", tunConf); - + LogPrint (eLogWarning, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf); } catch (std::exception& ex) { LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start tunnel ", name, ": ", ex.what ()); +#endif } } } @@ -761,12 +763,12 @@ namespace client bool httproxy; i2p::config::GetOption("httpproxy.enabled", httproxy); if (httproxy) { - std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys); - std::string httpProxyAddr; i2p::config::GetOption("httpproxy.address", httpProxyAddr); - uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort); - i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); - std::string httpOutProxyURL; i2p::config::GetOption("httpproxy.outproxy", httpOutProxyURL); - bool httpAddresshelper; i2p::config::GetOption("httpproxy.addresshelper", httpAddresshelper); + std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys); + std::string httpProxyAddr; i2p::config::GetOption("httpproxy.address", httpProxyAddr); + uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort); + i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); + std::string httpOutProxyURL; i2p::config::GetOption("httpproxy.outproxy", httpOutProxyURL); + bool httpAddresshelper; i2p::config::GetOption("httpproxy.addresshelper", httpAddresshelper); LogPrint(eLogInfo, "Clients: starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); if (httpProxyKeys.length () > 0) { @@ -781,14 +783,14 @@ namespace client else LogPrint(eLogError, "Clients: failed to load HTTP Proxy key"); } - try - { + try { m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, httpOutProxyURL, httpAddresshelper, localDestination); m_HttpProxy->Start(); - } - catch (std::exception& e) - { + } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort, ": ", e.what ()); +#endif } } } @@ -799,13 +801,13 @@ namespace client bool socksproxy; i2p::config::GetOption("socksproxy.enabled", socksproxy); if (socksproxy) { - std::string socksProxyKeys; i2p::config::GetOption("socksproxy.keys", socksProxyKeys); - std::string socksProxyAddr; i2p::config::GetOption("socksproxy.address", socksProxyAddr); - uint16_t socksProxyPort; i2p::config::GetOption("socksproxy.port", socksProxyPort); - bool socksOutProxy; i2p::config::GetOption("socksproxy.outproxy.enabled", socksOutProxy); - std::string socksOutProxyAddr; i2p::config::GetOption("socksproxy.outproxy", socksOutProxyAddr); - uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort); - i2p::data::SigningKeyType sigType; i2p::config::GetOption("socksproxy.signaturetype", sigType); + std::string socksProxyKeys; i2p::config::GetOption("socksproxy.keys", socksProxyKeys); + std::string socksProxyAddr; i2p::config::GetOption("socksproxy.address", socksProxyAddr); + uint16_t socksProxyPort; i2p::config::GetOption("socksproxy.port", socksProxyPort); + bool socksOutProxy; i2p::config::GetOption("socksproxy.outproxy.enabled", socksOutProxy); + std::string socksOutProxyAddr; i2p::config::GetOption("socksproxy.outproxy", socksOutProxyAddr); + uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort); + i2p::data::SigningKeyType sigType; i2p::config::GetOption("socksproxy.signaturetype", sigType); LogPrint(eLogInfo, "Clients: starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); if (socksProxyKeys.length () > 0) { @@ -820,15 +822,15 @@ namespace client else LogPrint(eLogError, "Clients: failed to load SOCKS Proxy key"); } - try - { + try { m_SocksProxy = new i2p::proxy::SOCKSProxy("SOCKS", socksProxyAddr, socksProxyPort, socksOutProxy, socksOutProxyAddr, socksOutProxyPort, localDestination); m_SocksProxy->Start(); - } - catch (std::exception& e) - { + } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in SOCKS Proxy: ", e.what()); +#ifdef WIN32_APP + ShowMessageBox (eLogError, "Unable to start SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort, ": ", e.what ()); +#endif } } } diff --git a/libi2pd_client/I2PService.cpp b/libi2pd_client/I2PService.cpp index c795fc5f..31f34710 100644 --- a/libi2pd_client/I2PService.cpp +++ b/libi2pd_client/I2PService.cpp @@ -84,7 +84,7 @@ namespace client auto itr = m_ReadyCallbacks.begin(); while(itr != m_ReadyCallbacks.end()) { - if(itr->second != NEVER_TIMES_OUT && now >= itr->second) + if(itr->second != NEVER_TIMES_OUT && now >= itr->second) { itr->first(boost::asio::error::timed_out); itr = m_ReadyCallbacks.erase(itr); @@ -94,9 +94,9 @@ namespace client } } if(!ec && m_ReadyCallbacks.size()) - TriggerReadyCheckTimer(); + TriggerReadyCheckTimer(); else - m_ReadyTimerTriggered = false; + m_ReadyTimerTriggered = false; } void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port) { @@ -118,7 +118,7 @@ namespace client AddReadyCallback([this, streamRequestComplete, address, port] (const boost::system::error_code & ec) { if(ec) { - LogPrint(eLogWarning, "I2PService::CeateStream() ", ec.message()); + LogPrint(eLogWarning, "I2PService::CreateStream() ", ec.message()); streamRequestComplete(nullptr); } else @@ -285,12 +285,8 @@ namespace client m_Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), m_LocalEndpoint)); // update the local end point in case port has been set zero and got updated now m_LocalEndpoint = m_Acceptor->local_endpoint(); - try { - m_Acceptor->listen (); - Accept (); - } catch (std::exception& ex) { - LogPrint (eLogError, "I2PService: failed to start ", GetName(), " acceptor: ", ex.what ()); - } + m_Acceptor->listen (); + Accept (); } void TCPIPAcceptor::Stop () From b19755644764d6016893363e3bce11cd1e5eef98 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 May 2020 08:11:01 -0400 Subject: [PATCH 3669/6300] remove dependency from Win32App --- libi2pd/Log.h | 479 +++++++++++++++++++++++++------------------------- 1 file changed, 236 insertions(+), 243 deletions(-) diff --git a/libi2pd/Log.h b/libi2pd/Log.h index 8343e3b3..2737d5ed 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -1,243 +1,236 @@ -/* -* Copyright (c) 2013-2016, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef LOG_H__ -#define LOG_H__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "Queue.h" - -#ifndef _WIN32 -#include -#endif - -#ifdef WIN32_APP -#include -#include "Win32/Win32App.h" -#endif - -enum LogLevel -{ - eLogNone = 0, - eLogError, - eLogWarning, - eLogInfo, - eLogDebug, - eNumLogLevels -}; - -enum LogType { - eLogStdout = 0, - eLogStream, - eLogFile, -#ifndef _WIN32 - eLogSyslog, -#endif -}; - -namespace i2p { -namespace log { - - struct LogMsg; /* forward declaration */ - - class Log - { - private: - - enum LogType m_Destination; - enum LogLevel m_MinLevel; - std::shared_ptr m_LogStream; - std::string m_Logfile; - std::time_t m_LastTimestamp; - char m_LastDateTime[64]; - i2p::util::Queue > m_Queue; - bool m_HasColors; - std::string m_TimeFormat; - volatile bool m_IsRunning; - std::thread * m_Thread; - - private: - - /** prevent making copies */ - Log (const Log &); - const Log& operator=(const Log&); - - void Run (); - void Process (std::shared_ptr msg); - - /** - * @brief Makes formatted string from unix timestamp - * @param ts Second since epoch - * - * This function internally caches the result for last provided value - */ - const char * TimeAsString(std::time_t ts); - - public: - - Log (); - ~Log (); - - LogType GetLogType () { return m_Destination; }; - LogLevel GetLogLevel () { return m_MinLevel; }; - - void Start (); - void Stop (); - - /** - * @brief Sets minimal allowed level for log messages - * @param level String with wanted minimal msg level - */ - void SetLogLevel (const std::string& level); - - /** - * @brief Sets log destination to logfile - * @param path Path to logfile - */ - void SendTo (const std::string &path); - - /** - * @brief Sets log destination to given output stream - * @param os Output stream - */ - void SendTo (std::shared_ptr os); - - /** - * @brief Sets format for timestamps in log - * @param format String with timestamp format - */ - void SetTimeFormat (std::string format) { m_TimeFormat = format; }; - - #ifndef _WIN32 - /** - * @brief Sets log destination to syslog - * @param name Wanted program name - * @param facility Wanted log category - */ - void SendTo (const char *name, int facility); - #endif - - /** - * @brief Format log message and write to output stream/syslog - * @param msg Pointer to processed message - */ - void Append(std::shared_ptr &); - - /** @brief Reopen log file */ - void Reopen(); - }; - - /** - * @struct LogMsg - * @brief Log message container - * - * We creating it somewhere with LogPrint(), - * then put in MsgQueue for later processing. - */ - struct LogMsg { - std::time_t timestamp; - std::string text; /**< message text as single string */ - LogLevel level; /**< message level */ - std::thread::id tid; /**< id of thread that generated message */ - - LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {}; - }; - - Log & Logger(); -} // log -} - -/** internal usage only -- folding args array to single string */ -template -void LogPrint (std::stringstream& s, TValue&& arg) noexcept -{ - s << std::forward(arg); -} - -#if (__cplusplus < 201703L) // below C++ 17 -/** internal usage only -- folding args array to single string */ -template -void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept -{ - LogPrint (s, std::forward(arg)); - LogPrint (s, std::forward(args)...); -} -#endif - -/** - * @brief Create log message and send it to queue - * @param level Message level (eLogError, eLogInfo, ...) - * @param args Array of message parts - */ -template -void LogPrint (LogLevel level, TArgs&&... args) noexcept -{ - i2p::log::Log &log = i2p::log::Logger(); - if (level > log.GetLogLevel ()) - return; - - // fold message to single string - std::stringstream ss(""); - -#if (__cplusplus >= 201703L) // C++ 17 or higher - (LogPrint (ss, std::forward(args)), ...); -#else - LogPrint (ss, std::forward(args)...); -#endif - - auto msg = std::make_shared(level, std::time(nullptr), ss.str()); - msg->tid = std::this_thread::get_id(); - log.Append(msg); -} - -#ifdef WIN32_APP -/** - * @brief Show message box for user with message in it - * @param level Message level (eLogError, eLogInfo, ...) - * @param args Array of message parts - */ -template -void ShowMessageBox (LogLevel level, TArgs&&... args) noexcept -{ - // fold message to single string - std::stringstream ss(""); - -#if (__cplusplus >= 201703L) // C++ 17 or higher - (LogPrint (ss, std::forward(args)), ...); -#else - LogPrint (ss, std::forward(args)...); -#endif - - HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); - if (!hWnd) hWnd = NULL; - - UINT uType; - switch (level) { - case eLogError : - case eLogWarning : - uType = MB_ICONWARNING; - break; - case eLogNone : - case eLogInfo : - case eLogDebug : - default : - uType = MB_ICONINFORMATION; - break; - } - MessageBox( hWnd, TEXT(ss.str ().c_str ()), TEXT("i2pd"), uType | MB_TASKMODAL | MB_OK ); -} -#endif // WIN32_APP - -#endif // LOG_H__ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef LOG_H__ +#define LOG_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "Queue.h" + +#ifndef _WIN32 +#include +#endif + +#ifdef WIN32_APP +#include // TODO: move away to win32app +#endif + +enum LogLevel +{ + eLogNone = 0, + eLogError, + eLogWarning, + eLogInfo, + eLogDebug, + eNumLogLevels +}; + +enum LogType { + eLogStdout = 0, + eLogStream, + eLogFile, +#ifndef _WIN32 + eLogSyslog, +#endif +}; + +namespace i2p { +namespace log { + + struct LogMsg; /* forward declaration */ + + class Log + { + private: + + enum LogType m_Destination; + enum LogLevel m_MinLevel; + std::shared_ptr m_LogStream; + std::string m_Logfile; + std::time_t m_LastTimestamp; + char m_LastDateTime[64]; + i2p::util::Queue > m_Queue; + bool m_HasColors; + std::string m_TimeFormat; + volatile bool m_IsRunning; + std::thread * m_Thread; + + private: + + /** prevent making copies */ + Log (const Log &); + const Log& operator=(const Log&); + + void Run (); + void Process (std::shared_ptr msg); + + /** + * @brief Makes formatted string from unix timestamp + * @param ts Second since epoch + * + * This function internally caches the result for last provided value + */ + const char * TimeAsString(std::time_t ts); + + public: + + Log (); + ~Log (); + + LogType GetLogType () { return m_Destination; }; + LogLevel GetLogLevel () { return m_MinLevel; }; + + void Start (); + void Stop (); + + /** + * @brief Sets minimal allowed level for log messages + * @param level String with wanted minimal msg level + */ + void SetLogLevel (const std::string& level); + + /** + * @brief Sets log destination to logfile + * @param path Path to logfile + */ + void SendTo (const std::string &path); + + /** + * @brief Sets log destination to given output stream + * @param os Output stream + */ + void SendTo (std::shared_ptr os); + + /** + * @brief Sets format for timestamps in log + * @param format String with timestamp format + */ + void SetTimeFormat (std::string format) { m_TimeFormat = format; }; + + #ifndef _WIN32 + /** + * @brief Sets log destination to syslog + * @param name Wanted program name + * @param facility Wanted log category + */ + void SendTo (const char *name, int facility); + #endif + + /** + * @brief Format log message and write to output stream/syslog + * @param msg Pointer to processed message + */ + void Append(std::shared_ptr &); + + /** @brief Reopen log file */ + void Reopen(); + }; + + /** + * @struct LogMsg + * @brief Log message container + * + * We creating it somewhere with LogPrint(), + * then put in MsgQueue for later processing. + */ + struct LogMsg { + std::time_t timestamp; + std::string text; /**< message text as single string */ + LogLevel level; /**< message level */ + std::thread::id tid; /**< id of thread that generated message */ + + LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {}; + }; + + Log & Logger(); +} // log +} + +/** internal usage only -- folding args array to single string */ +template +void LogPrint (std::stringstream& s, TValue&& arg) noexcept +{ + s << std::forward(arg); +} + +#if (__cplusplus < 201703L) // below C++ 17 +/** internal usage only -- folding args array to single string */ +template +void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept +{ + LogPrint (s, std::forward(arg)); + LogPrint (s, std::forward(args)...); +} +#endif + +/** + * @brief Create log message and send it to queue + * @param level Message level (eLogError, eLogInfo, ...) + * @param args Array of message parts + */ +template +void LogPrint (LogLevel level, TArgs&&... args) noexcept +{ + i2p::log::Log &log = i2p::log::Logger(); + if (level > log.GetLogLevel ()) + return; + + // fold message to single string + std::stringstream ss(""); + +#if (__cplusplus >= 201703L) // C++ 17 or higher + (LogPrint (ss, std::forward(args)), ...); +#else + LogPrint (ss, std::forward(args)...); +#endif + + auto msg = std::make_shared(level, std::time(nullptr), ss.str()); + msg->tid = std::this_thread::get_id(); + log.Append(msg); +} + +#ifdef WIN32_APP +/** + * @brief Show message box for user with message in it + * @param level Message level (eLogError, eLogInfo, ...) + * @param args Array of message parts + */ +template +void ShowMessageBox (LogLevel level, TArgs&&... args) noexcept +{ + // fold message to single string + std::stringstream ss(""); + +#if (__cplusplus >= 201703L) // C++ 17 or higher + (LogPrint (ss, std::forward(args)), ...); +#else + LogPrint (ss, std::forward(args)...); +#endif + + UINT uType; + switch (level) + { + case eLogError : + case eLogWarning : + uType = MB_ICONWARNING; + break; + default : + uType = MB_ICONINFORMATION; + } + MessageBox(0, TEXT(ss.str ().c_str ()), TEXT("i2pd"), uType | MB_TASKMODAL | MB_OK ); +} +#endif // WIN32_APP + +#endif // LOG_H__ From 53b43353eb121e5d4a091d7501ef905d7205269a Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 May 2020 08:27:56 -0400 Subject: [PATCH 3670/6300] fixed formatting --- libi2pd/Log.h | 472 +++++++++++++++++++++++++------------------------- 1 file changed, 236 insertions(+), 236 deletions(-) diff --git a/libi2pd/Log.h b/libi2pd/Log.h index 2737d5ed..eb3ce327 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -1,236 +1,236 @@ -/* -* Copyright (c) 2013-2016, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef LOG_H__ -#define LOG_H__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "Queue.h" - -#ifndef _WIN32 -#include -#endif - -#ifdef WIN32_APP -#include // TODO: move away to win32app -#endif - -enum LogLevel -{ - eLogNone = 0, - eLogError, - eLogWarning, - eLogInfo, - eLogDebug, - eNumLogLevels -}; - -enum LogType { - eLogStdout = 0, - eLogStream, - eLogFile, -#ifndef _WIN32 - eLogSyslog, -#endif -}; - -namespace i2p { -namespace log { - - struct LogMsg; /* forward declaration */ - - class Log - { - private: - - enum LogType m_Destination; - enum LogLevel m_MinLevel; - std::shared_ptr m_LogStream; - std::string m_Logfile; - std::time_t m_LastTimestamp; - char m_LastDateTime[64]; - i2p::util::Queue > m_Queue; - bool m_HasColors; - std::string m_TimeFormat; - volatile bool m_IsRunning; - std::thread * m_Thread; - - private: - - /** prevent making copies */ - Log (const Log &); - const Log& operator=(const Log&); - - void Run (); - void Process (std::shared_ptr msg); - - /** - * @brief Makes formatted string from unix timestamp - * @param ts Second since epoch - * - * This function internally caches the result for last provided value - */ - const char * TimeAsString(std::time_t ts); - - public: - - Log (); - ~Log (); - - LogType GetLogType () { return m_Destination; }; - LogLevel GetLogLevel () { return m_MinLevel; }; - - void Start (); - void Stop (); - - /** - * @brief Sets minimal allowed level for log messages - * @param level String with wanted minimal msg level - */ - void SetLogLevel (const std::string& level); - - /** - * @brief Sets log destination to logfile - * @param path Path to logfile - */ - void SendTo (const std::string &path); - - /** - * @brief Sets log destination to given output stream - * @param os Output stream - */ - void SendTo (std::shared_ptr os); - - /** - * @brief Sets format for timestamps in log - * @param format String with timestamp format - */ - void SetTimeFormat (std::string format) { m_TimeFormat = format; }; - - #ifndef _WIN32 - /** - * @brief Sets log destination to syslog - * @param name Wanted program name - * @param facility Wanted log category - */ - void SendTo (const char *name, int facility); - #endif - - /** - * @brief Format log message and write to output stream/syslog - * @param msg Pointer to processed message - */ - void Append(std::shared_ptr &); - - /** @brief Reopen log file */ - void Reopen(); - }; - - /** - * @struct LogMsg - * @brief Log message container - * - * We creating it somewhere with LogPrint(), - * then put in MsgQueue for later processing. - */ - struct LogMsg { - std::time_t timestamp; - std::string text; /**< message text as single string */ - LogLevel level; /**< message level */ - std::thread::id tid; /**< id of thread that generated message */ - - LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {}; - }; - - Log & Logger(); -} // log -} - -/** internal usage only -- folding args array to single string */ -template -void LogPrint (std::stringstream& s, TValue&& arg) noexcept -{ - s << std::forward(arg); -} - -#if (__cplusplus < 201703L) // below C++ 17 -/** internal usage only -- folding args array to single string */ -template -void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept -{ - LogPrint (s, std::forward(arg)); - LogPrint (s, std::forward(args)...); -} -#endif - -/** - * @brief Create log message and send it to queue - * @param level Message level (eLogError, eLogInfo, ...) - * @param args Array of message parts - */ -template -void LogPrint (LogLevel level, TArgs&&... args) noexcept -{ - i2p::log::Log &log = i2p::log::Logger(); - if (level > log.GetLogLevel ()) - return; - - // fold message to single string - std::stringstream ss(""); - -#if (__cplusplus >= 201703L) // C++ 17 or higher - (LogPrint (ss, std::forward(args)), ...); -#else - LogPrint (ss, std::forward(args)...); -#endif - - auto msg = std::make_shared(level, std::time(nullptr), ss.str()); - msg->tid = std::this_thread::get_id(); - log.Append(msg); -} - -#ifdef WIN32_APP -/** - * @brief Show message box for user with message in it - * @param level Message level (eLogError, eLogInfo, ...) - * @param args Array of message parts - */ -template -void ShowMessageBox (LogLevel level, TArgs&&... args) noexcept -{ - // fold message to single string - std::stringstream ss(""); - -#if (__cplusplus >= 201703L) // C++ 17 or higher - (LogPrint (ss, std::forward(args)), ...); -#else - LogPrint (ss, std::forward(args)...); -#endif - - UINT uType; - switch (level) - { - case eLogError : - case eLogWarning : - uType = MB_ICONWARNING; - break; - default : - uType = MB_ICONINFORMATION; - } - MessageBox(0, TEXT(ss.str ().c_str ()), TEXT("i2pd"), uType | MB_TASKMODAL | MB_OK ); -} -#endif // WIN32_APP - -#endif // LOG_H__ +/* +* Copyright (c) 2013-2016, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef LOG_H__ +#define LOG_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "Queue.h" + +#ifndef _WIN32 +#include +#endif + +#ifdef WIN32_APP +#include // TODO: move away to win32app +#endif + +enum LogLevel +{ + eLogNone = 0, + eLogError, + eLogWarning, + eLogInfo, + eLogDebug, + eNumLogLevels +}; + +enum LogType { + eLogStdout = 0, + eLogStream, + eLogFile, +#ifndef _WIN32 + eLogSyslog, +#endif +}; + +namespace i2p { +namespace log { + + struct LogMsg; /* forward declaration */ + + class Log + { + private: + + enum LogType m_Destination; + enum LogLevel m_MinLevel; + std::shared_ptr m_LogStream; + std::string m_Logfile; + std::time_t m_LastTimestamp; + char m_LastDateTime[64]; + i2p::util::Queue > m_Queue; + bool m_HasColors; + std::string m_TimeFormat; + volatile bool m_IsRunning; + std::thread * m_Thread; + + private: + + /** prevent making copies */ + Log (const Log &); + const Log& operator=(const Log&); + + void Run (); + void Process (std::shared_ptr msg); + + /** + * @brief Makes formatted string from unix timestamp + * @param ts Second since epoch + * + * This function internally caches the result for last provided value + */ + const char * TimeAsString(std::time_t ts); + + public: + + Log (); + ~Log (); + + LogType GetLogType () { return m_Destination; }; + LogLevel GetLogLevel () { return m_MinLevel; }; + + void Start (); + void Stop (); + + /** + * @brief Sets minimal allowed level for log messages + * @param level String with wanted minimal msg level + */ + void SetLogLevel (const std::string& level); + + /** + * @brief Sets log destination to logfile + * @param path Path to logfile + */ + void SendTo (const std::string &path); + + /** + * @brief Sets log destination to given output stream + * @param os Output stream + */ + void SendTo (std::shared_ptr os); + + /** + * @brief Sets format for timestamps in log + * @param format String with timestamp format + */ + void SetTimeFormat (std::string format) { m_TimeFormat = format; }; + + #ifndef _WIN32 + /** + * @brief Sets log destination to syslog + * @param name Wanted program name + * @param facility Wanted log category + */ + void SendTo (const char *name, int facility); + #endif + + /** + * @brief Format log message and write to output stream/syslog + * @param msg Pointer to processed message + */ + void Append(std::shared_ptr &); + + /** @brief Reopen log file */ + void Reopen(); + }; + + /** + * @struct LogMsg + * @brief Log message container + * + * We creating it somewhere with LogPrint(), + * then put in MsgQueue for later processing. + */ + struct LogMsg { + std::time_t timestamp; + std::string text; /**< message text as single string */ + LogLevel level; /**< message level */ + std::thread::id tid; /**< id of thread that generated message */ + + LogMsg (LogLevel lvl, std::time_t ts, const std::string & txt): timestamp(ts), text(txt), level(lvl) {}; + }; + + Log & Logger(); +} // log +} + +/** internal usage only -- folding args array to single string */ +template +void LogPrint (std::stringstream& s, TValue&& arg) noexcept +{ + s << std::forward(arg); +} + +#if (__cplusplus < 201703L) // below C++ 17 +/** internal usage only -- folding args array to single string */ +template +void LogPrint (std::stringstream& s, TValue&& arg, TArgs&&... args) noexcept +{ + LogPrint (s, std::forward(arg)); + LogPrint (s, std::forward(args)...); +} +#endif + +/** + * @brief Create log message and send it to queue + * @param level Message level (eLogError, eLogInfo, ...) + * @param args Array of message parts + */ +template +void LogPrint (LogLevel level, TArgs&&... args) noexcept +{ + i2p::log::Log &log = i2p::log::Logger(); + if (level > log.GetLogLevel ()) + return; + + // fold message to single string + std::stringstream ss(""); + +#if (__cplusplus >= 201703L) // C++ 17 or higher + (LogPrint (ss, std::forward(args)), ...); +#else + LogPrint (ss, std::forward(args)...); +#endif + + auto msg = std::make_shared(level, std::time(nullptr), ss.str()); + msg->tid = std::this_thread::get_id(); + log.Append(msg); +} + +#ifdef WIN32_APP +/** + * @brief Show message box for user with message in it + * @param level Message level (eLogError, eLogInfo, ...) + * @param args Array of message parts + */ +template +void ShowMessageBox (LogLevel level, TArgs&&... args) noexcept +{ + // fold message to single string + std::stringstream ss(""); + +#if (__cplusplus >= 201703L) // C++ 17 or higher + (LogPrint (ss, std::forward(args)), ...); +#else + LogPrint (ss, std::forward(args)...); +#endif + + UINT uType; + switch (level) + { + case eLogError : + case eLogWarning : + uType = MB_ICONWARNING; + break; + default : + uType = MB_ICONINFORMATION; + } + MessageBox(0, TEXT(ss.str ().c_str ()), TEXT("i2pd"), uType | MB_TASKMODAL | MB_OK ); +} +#endif // WIN32_APP + +#endif // LOG_H__ From bb7f03857c3b3c9d95f2a3a374d3d6e90167d138 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 May 2020 09:35:41 -0400 Subject: [PATCH 3671/6300] ThrowFatal function --- libi2pd/Log.h | 25 +++++-------- libi2pd/NTCP2.cpp | 12 +++---- libi2pd/NTCPSession.cpp | 16 ++++----- libi2pd/SSU.cpp | 22 ++++++------ libi2pd_client/ClientContext.cpp | 62 ++++++++++++++++---------------- 5 files changed, 64 insertions(+), 73 deletions(-) diff --git a/libi2pd/Log.h b/libi2pd/Log.h index eb3ce327..88d5c4db 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -201,36 +201,27 @@ void LogPrint (LogLevel level, TArgs&&... args) noexcept log.Append(msg); } -#ifdef WIN32_APP + /** - * @brief Show message box for user with message in it - * @param level Message level (eLogError, eLogInfo, ...) + * @brief Throw fatal error message with the list of arguments * @param args Array of message parts */ template -void ShowMessageBox (LogLevel level, TArgs&&... args) noexcept +void ThrowFatal (TArgs&&... args) noexcept { // fold message to single string std::stringstream ss(""); - #if (__cplusplus >= 201703L) // C++ 17 or higher (LogPrint (ss, std::forward(args)), ...); #else LogPrint (ss, std::forward(args)...); #endif - UINT uType; - switch (level) - { - case eLogError : - case eLogWarning : - uType = MB_ICONWARNING; - break; - default : - uType = MB_ICONINFORMATION; - } - MessageBox(0, TEXT(ss.str ().c_str ()), TEXT("i2pd"), uType | MB_TASKMODAL | MB_OK ); +#ifdef WIN32_APP + MessageBox(0, TEXT(ss.str ().c_str ()), TEXT("i2pd"), MB_ICONERROR | MB_TASKMODAL | MB_OK ); +#else + std::cout << ss.str (); +#endif } -#endif // WIN32_APP #endif // LOG_H__ diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index dba73e50..e52f0111 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1196,9 +1196,7 @@ namespace transport catch ( std::exception & ex ) { LogPrint(eLogError, "NTCP2: Failed to bind to v4 port ", address->port, ex.what()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start IPv4 NTCP2 transport at port ", address->port, ": ", ex.what ()); -#endif + ThrowFatal ("Unable to start IPv4 NTCP2 transport at port ", address->port, ": ", ex.what ()); continue; } @@ -1220,11 +1218,11 @@ namespace transport LogPrint (eLogInfo, "NTCP2: Start listening v6 TCP port ", address->port); auto conn = std::make_shared (*this); m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, conn, std::placeholders::_1)); - } catch ( std::exception & ex ) { + } + catch ( std::exception & ex ) + { LogPrint(eLogError, "NTCP2: failed to bind to v6 port ", address->port, ": ", ex.what()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start IPv6 NTCP2 transport at port ", address->port, ": ", ex.what ()); -#endif + ThrowFatal ("Unable to start IPv6 NTCP2 transport at port ", address->port, ": ", ex.what ()); continue; } } diff --git a/libi2pd/NTCPSession.cpp b/libi2pd/NTCPSession.cpp index fe420ac6..4ab3c5d0 100644 --- a/libi2pd/NTCPSession.cpp +++ b/libi2pd/NTCPSession.cpp @@ -821,12 +821,12 @@ namespace transport { m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port)); LogPrint (eLogInfo, "NTCP: Start listening v6 TCP port ", address->port); - } catch ( std::exception & ex ) { + } + catch ( std::exception & ex ) + { /** fail to bind ip4 */ LogPrint(eLogError, "NTCP: Failed to bind to v4 port ", address->port, ": ", ex.what()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start IPv4 NTCP transport at port ", address->port, ": ", ex.what ()); -#endif + ThrowFatal ("Unable to start IPv4 NTCP transport at port ", address->port, ": ", ex.what ()); continue; } @@ -848,11 +848,11 @@ namespace transport LogPrint (eLogInfo, "NTCP: Start listening v6 TCP port ", address->port); auto conn = std::make_shared (*this); m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this, conn, std::placeholders::_1)); - } catch ( std::exception & ex ) { + } + catch ( std::exception & ex ) + { LogPrint(eLogError, "NTCP: failed to bind to v6 port ", address->port, ": ", ex.what()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start IPv6 NTCP transport at port ", address->port, ": ", ex.what ()); -#endif + ThrowFatal (eLogError, "Unable to start IPv6 NTCP transport at port ", address->port, ": ", ex.what ()); continue; } } diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index ae2caf62..74cbe39b 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -45,34 +45,36 @@ namespace transport void SSUServer::OpenSocket () { - try { + try + { m_Socket.open (boost::asio::ip::udp::v4()); m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); m_Socket.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); m_Socket.bind (m_Endpoint); LogPrint (eLogInfo, "SSU: Start listening v4 port ", m_Endpoint.port()); - } catch ( std::exception & ex ) { + } + catch ( std::exception & ex ) + { LogPrint (eLogError, "SSU: failed to bind to v4 port ", m_Endpoint.port(), ": ", ex.what()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start IPv4 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); -#endif + ThrowFatal ("Unable to start IPv4 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); } } void SSUServer::OpenSocketV6 () { - try { + try + { m_SocketV6.open (boost::asio::ip::udp::v6()); m_SocketV6.set_option (boost::asio::ip::v6_only (true)); m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); m_SocketV6.bind (m_EndpointV6); LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port()); - } catch ( std::exception & ex ) { + } + catch ( std::exception & ex ) + { LogPrint (eLogError, "SSU: failed to bind to v6 port ", m_EndpointV6.port(), ": ", ex.what()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start IPv6 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); -#endif + ThrowFatal ("Unable to start IPv6 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); } } diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 1e8b23cb..c8a9ddfe 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -58,14 +58,15 @@ namespace client uint16_t samPort; i2p::config::GetOption("sam.port", samPort); bool singleThread; i2p::config::GetOption("sam.singlethread", singleThread); LogPrint(eLogInfo, "Clients: starting SAM bridge at ", samAddr, ":", samPort); - try { + try + { m_SamBridge = new SAMBridge (samAddr, samPort, singleThread); m_SamBridge->Start (); - } catch (std::exception& e) { + } + catch (std::exception& e) + { LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start SAM bridge at ", samAddr, ":", samPort, ": ", e.what ()); -#endif + ThrowFatal ("Unable to start SAM bridge at ", samAddr, ":", samPort, ": ", e.what ()); } } @@ -75,14 +76,15 @@ namespace client std::string bobAddr; i2p::config::GetOption("bob.address", bobAddr); uint16_t bobPort; i2p::config::GetOption("bob.port", bobPort); LogPrint(eLogInfo, "Clients: starting BOB command channel at ", bobAddr, ":", bobPort); - try { + try + { m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); m_BOBCommandChannel->Start (); - } catch (std::exception& e) { + } + catch (std::exception& e) + { LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start BOB bridge at ", bobAddr, ":", bobPort, ": ", e.what ()); -#endif + ThrowFatal ("Unable to start BOB bridge at ", bobAddr, ":", bobPort, ": ", e.what ()); } } @@ -93,14 +95,15 @@ namespace client std::string i2cpAddr; i2p::config::GetOption("i2cp.address", i2cpAddr); uint16_t i2cpPort; i2p::config::GetOption("i2cp.port", i2cpPort); LogPrint(eLogInfo, "Clients: starting I2CP at ", i2cpAddr, ":", i2cpPort); - try { + try + { m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort); m_I2CPServer->Start (); - } catch (std::exception& e) { + } + catch (std::exception& e) + { LogPrint(eLogError, "Clients: Exception in I2CP: ", e.what()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start I2CP at ", i2cpAddr, ":", i2cpPort, ": ", e.what ()); -#endif + ThrowFatal ("Unable to start I2CP at ", i2cpAddr, ":", i2cpPort, ": ", e.what ()); } } @@ -595,9 +598,6 @@ namespace client else if (type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS) { LogPrint(eLogWarning, "Clients: I2P Client tunnel websocks is deprecated, not starting ", name, " tunnel"); -#ifdef WIN32_APP - ShowMessageBox (eLogWarning, "Skipped websocks tunnel ", name, " because it's support is discontinued."); -#endif continue; } else @@ -750,9 +750,7 @@ namespace client catch (std::exception& ex) { LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start tunnel ", name, ": ", ex.what ()); -#endif + ThrowFatal ("Unable to start tunnel ", name, ": ", ex.what ()); } } } @@ -783,14 +781,15 @@ namespace client else LogPrint(eLogError, "Clients: failed to load HTTP Proxy key"); } - try { + try + { m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, httpOutProxyURL, httpAddresshelper, localDestination); m_HttpProxy->Start(); - } catch (std::exception& e) { + } + catch (std::exception& e) + { LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort, ": ", e.what ()); -#endif + ThrowFatal ("Unable to start HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort, ": ", e.what ()); } } } @@ -822,15 +821,16 @@ namespace client else LogPrint(eLogError, "Clients: failed to load SOCKS Proxy key"); } - try { + try + { m_SocksProxy = new i2p::proxy::SOCKSProxy("SOCKS", socksProxyAddr, socksProxyPort, socksOutProxy, socksOutProxyAddr, socksOutProxyPort, localDestination); m_SocksProxy->Start(); - } catch (std::exception& e) { + } + catch (std::exception& e) + { LogPrint(eLogError, "Clients: Exception in SOCKS Proxy: ", e.what()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort, ": ", e.what ()); -#endif + ThrowFatal ("Unable to start SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort, ": ", e.what ()); } } } From dbe1e3f577c05d3fe0c6a50ceacc26d2b8e9daff Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 May 2020 10:16:16 -0400 Subject: [PATCH 3672/6300] ThrowFatal function --- daemon/Daemon.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 75829ef6..fc71123f 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -314,14 +314,15 @@ namespace i2p std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); LogPrint(eLogInfo, "Daemon: starting webconsole at ", httpAddr, ":", httpPort); - try { + try + { d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); d.httpServer->Start(); - } catch (std::exception& ex) { + } + catch (std::exception& ex) + { LogPrint (eLogError, "Daemon: failed to start webconsole: ", ex.what ()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start webconsole at ", httpAddr, ":", httpPort, ": ", ex.what ()); -#endif + ThrowFatal ("Unable to start webconsole at ", httpAddr, ":", httpPort, ": ", ex.what ()); } } @@ -338,14 +339,15 @@ namespace i2p std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr); uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort); LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort); - try { + try + { d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); d.m_I2PControlService->Start (); - } catch (std::exception& ex) { + } + catch (std::exception& ex) + { LogPrint (eLogError, "Daemon: failed to start I2PControl: ", ex.what ()); -#ifdef WIN32_APP - ShowMessageBox (eLogError, "Unable to start I2PControl service at ", i2pcpAddr, ":", i2pcpPort, ": ", ex.what ()); -#endif + ThrowFatal ("Unable to start I2PControl service at ", i2pcpAddr, ":", i2pcpPort, ": ", ex.what ()); } } return true; From d7d70b707ffe0435c80b1d908a51b4047d94e741 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 May 2020 11:13:59 -0400 Subject: [PATCH 3673/6300] configurable throw function --- Win32/DaemonWin32.cpp | 6 ++++++ libi2pd/Log.cpp | 6 ++++++ libi2pd/Log.h | 19 ++++++++----------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Win32/DaemonWin32.cpp b/Win32/DaemonWin32.cpp index 9fffe7fb..5524aff3 100644 --- a/Win32/DaemonWin32.cpp +++ b/Win32/DaemonWin32.cpp @@ -8,6 +8,7 @@ #ifdef _WIN32 #include "Win32/Win32Service.h" #ifdef WIN32_APP +#include #include "Win32/Win32App.h" #endif @@ -23,6 +24,11 @@ namespace util setlocale(LC_ALL, "Russian"); setlocale(LC_TIME, "C"); + i2p::log::SetThrowFunction ([](const std::string& s) + { + MessageBox(0, TEXT(s.c_str ()), TEXT("i2pd"), MB_ICONERROR | MB_TASKMODAL | MB_OK ); + }); + if (!Daemon_Singleton::init(argc, argv)) return false; diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index 7d77aaf8..65602674 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -236,5 +236,11 @@ namespace log { Log & Logger() { return logger; } + + static ThrowFunction g_ThrowFunction; + ThrowFunction GetThrowFunction () { return g_ThrowFunction; } + void SetThrowFunction (ThrowFunction f) { g_ThrowFunction = f; } + } // log } // i2p + diff --git a/libi2pd/Log.h b/libi2pd/Log.h index 88d5c4db..556654e2 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -17,16 +17,13 @@ #include #include #include +#include #include "Queue.h" #ifndef _WIN32 #include #endif -#ifdef WIN32_APP -#include // TODO: move away to win32app -#endif - enum LogLevel { eLogNone = 0, @@ -155,6 +152,10 @@ namespace log { }; Log & Logger(); + + typedef std::function ThrowFunction; + ThrowFunction GetThrowFunction (); + void SetThrowFunction (ThrowFunction f); } // log } @@ -201,7 +202,6 @@ void LogPrint (LogLevel level, TArgs&&... args) noexcept log.Append(msg); } - /** * @brief Throw fatal error message with the list of arguments * @param args Array of message parts @@ -209,6 +209,8 @@ void LogPrint (LogLevel level, TArgs&&... args) noexcept template void ThrowFatal (TArgs&&... args) noexcept { + auto f = i2p::log::GetThrowFunction (); + if (!f) return; // fold message to single string std::stringstream ss(""); #if (__cplusplus >= 201703L) // C++ 17 or higher @@ -216,12 +218,7 @@ void ThrowFatal (TArgs&&... args) noexcept #else LogPrint (ss, std::forward(args)...); #endif - -#ifdef WIN32_APP - MessageBox(0, TEXT(ss.str ().c_str ()), TEXT("i2pd"), MB_ICONERROR | MB_TASKMODAL | MB_OK ); -#else - std::cout << ss.str (); -#endif + f (ss.str ()); } #endif // LOG_H__ From c4d9c039300d94bb91279396dd4ccca363a481a6 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 May 2020 13:01:23 -0400 Subject: [PATCH 3674/6300] handle termination block --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 29 ++++++++++++++--------- libi2pd/ECIESX25519AEADRatchetSession.h | 4 ++-- libi2pd/Garlic.cpp | 9 +++++++ libi2pd/Garlic.h | 1 + 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 7fdf351e..4c7d15ef 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -223,16 +223,8 @@ namespace garlic switch (blk) { case eECIESx25519BlkGalicClove: - GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size); - break; - case eECIESx25519BlkDateTime: - LogPrint (eLogDebug, "Garlic: datetime"); - break; - case eECIESx25519BlkOptions: - LogPrint (eLogDebug, "Garlic: options"); - break; - case eECIESx25519BlkPadding: - LogPrint (eLogDebug, "Garlic: padding"); + if (GetOwner ()) + GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size); break; case eECIESx25519BlkNextKey: LogPrint (eLogDebug, "Garlic: next key"); @@ -256,6 +248,21 @@ namespace garlic m_AckRequests.push_back ({receiveTagset->GetTagSetID (), index}); break; } + case eECIESx25519BlkTermination: + LogPrint (eLogDebug, "Garlic: termination"); + if (GetOwner ()) + GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey); + if (receiveTagset) receiveTagset->Expire (); + break; + case eECIESx25519BlkDateTime: + LogPrint (eLogDebug, "Garlic: datetime"); + break; + case eECIESx25519BlkOptions: + LogPrint (eLogDebug, "Garlic: options"); + break; + case eECIESx25519BlkPadding: + LogPrint (eLogDebug, "Garlic: padding"); + break; default: LogPrint (eLogWarning, "Garlic: Unknown block type ", (int)blk); } @@ -852,7 +859,7 @@ namespace garlic CleanupUnconfirmedLeaseSet (ts); return ts > m_LastActivityTimestamp + ECIESX25519_EXPIRATION_TIMEOUT; } - + std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag) { auto m = NewI2NPMessage (); diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 79e920b5..29d75a6b 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -21,7 +21,7 @@ namespace garlic const int ECIESX25519_EXPIRATION_TIMEOUT = 480; // in seconds const int ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT = 600; // in seconds const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180 - const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 1024; // number of tags we request new tagset after + const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 4096; // number of tags we request new tagset after const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 160; const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; @@ -110,7 +110,7 @@ namespace garlic bool HandleNextMessage (const uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - + const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index fb3451d0..8ba4a8a1 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -1013,5 +1013,14 @@ namespace garlic m_ECIESx25519Sessions.emplace (staticKeyTag, session); } + void GarlicDestination::RemoveECIESx25519Session (const uint8_t * staticKey) + { + auto it = m_ECIESx25519Sessions.find (staticKey); + if (it != m_ECIESx25519Sessions.end ()) + { + it->second->SetOwner (nullptr); + m_ECIESx25519Sessions.erase (it); + } + } } } diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 6c81dabf..86c9e432 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -233,6 +233,7 @@ namespace garlic void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); void AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset); void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); + void RemoveECIESx25519Session (const uint8_t * staticKey); void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); virtual void ProcessGarlicMessage (std::shared_ptr msg); From d50319064794c011b3045bf586d513a08aeb09e2 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 May 2020 10:08:01 -0400 Subject: [PATCH 3675/6300] fixed crash of encrypted leaseset without authentication --- libi2pd/Destination.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 1473d41f..650ab04b 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -415,7 +415,10 @@ namespace client auto it2 = m_LeaseSetRequests.find (key); if (it2 != m_LeaseSetRequests.end () && it2->second->requestedBlindedKey) { - auto ls2 = std::make_shared (buf + offset, len - offset, it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? *m_LeaseSetPrivKey : nullptr, GetPreferredCryptoType ()); + const uint8_t * secret = nullptr; + if (m_LeaseSetPrivKey) secret = *m_LeaseSetPrivKey; // m_LeaseSetPrivKey ? *m_LeaseSetPrivKey: nullptr invokes copy contructor + auto ls2 = std::make_shared (buf + offset, len - offset, + it2->second->requestedBlindedKey, secret, GetPreferredCryptoType ()); if (ls2->IsValid ()) { m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key From 9b6facf3b01a1377577a959a5b3eab0895834911 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 May 2020 14:08:54 -0400 Subject: [PATCH 3676/6300] fixed crash of encrypted leaseset without authentication --- libi2pd/Destination.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 650ab04b..736390a2 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -415,10 +415,8 @@ namespace client auto it2 = m_LeaseSetRequests.find (key); if (it2 != m_LeaseSetRequests.end () && it2->second->requestedBlindedKey) { - const uint8_t * secret = nullptr; - if (m_LeaseSetPrivKey) secret = *m_LeaseSetPrivKey; // m_LeaseSetPrivKey ? *m_LeaseSetPrivKey: nullptr invokes copy contructor auto ls2 = std::make_shared (buf + offset, len - offset, - it2->second->requestedBlindedKey, secret, GetPreferredCryptoType ()); + it2->second->requestedBlindedKey, m_LeaseSetPrivKey ? ((const uint8_t *)*m_LeaseSetPrivKey) : nullptr , GetPreferredCryptoType ()); if (ls2->IsValid ()) { m_RemoteLeaseSets[ls2->GetIdentHash ()] = ls2; // ident is not key From 789ff702ac5eca7901c3a96a3d58e0af002b9f77 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 May 2020 14:54:41 -0400 Subject: [PATCH 3677/6300] fixed sudden webconsole hangs --- daemon/HTTPServer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index a1cba3ce..de052a2b 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1227,10 +1227,10 @@ namespace http { LogPrint(eLogInfo, "HTTPServer: password set to ", pass); } - m_Thread = std::unique_ptr(new std::thread (std::bind (&HTTPServer::Run, this))); + m_IsRunning = true; + m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this))); m_Acceptor.listen (); Accept (); - m_IsRunning = true; } void HTTPServer::Stop () From 24c5f07153216f14d7b57cd67b7984eed40e1f76 Mon Sep 17 00:00:00 2001 From: Anatolii Vorona Date: Thu, 7 May 2020 12:11:30 +0200 Subject: [PATCH 3678/6300] added logrotate config --- contrib/rpm/i2pd-git.spec | 16 +++++++++++----- contrib/rpm/i2pd.logrotate | 9 +++++++++ contrib/rpm/i2pd.spec | 14 ++++++++++---- 3 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 contrib/rpm/i2pd.logrotate diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index b182dca4..6844a25f 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -24,6 +24,7 @@ BuildRequires: openssl-devel BuildRequires: miniupnpc-devel BuildRequires: systemd-units +Requires: logrotate Requires: systemd Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd @@ -73,10 +74,11 @@ pushd build chrpath -d i2pd %{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd -%{__install} -D -m 755 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf -%{__install} -D -m 755 %{_builddir}/%{name}-%{version}/contrib/subscriptions.txt %{buildroot}%{_sysconfdir}/i2pd/subscriptions.txt -%{__install} -D -m 755 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf -%{__install} -D -m 755 %{_builddir}/%{name}-%{version}/contrib/tunnels.d/README %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf.d/README +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/subscriptions.txt %{buildroot}%{_sysconfdir}/i2pd/subscriptions.txt +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.d/README %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf.d/README +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/rpm/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/rpm/i2pd.service %{buildroot}%{_unitdir}/i2pd.service %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/debian/i2pd.1 %{buildroot}%{_mandir}/man1/i2pd.1 %{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd @@ -108,16 +110,20 @@ getent passwd i2pd >/dev/null || \ %files %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d %{_sbindir}/i2pd -%config(noreplace) %{_sysconfdir}/i2pd/* +%config(noreplace) %{_sysconfdir}/i2pd/*.conf %{_unitdir}/i2pd.service %{_mandir}/man1/i2pd.1* %dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd %dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd %{_datadir}/%{name}/certificates %{_sharedstatedir}/i2pd/certificates +%{_sysconfdir}/logrotate.d/i2pd %changelog +* Thu May 7 2020 Anatolii Vorona - 2.31.0-2 +- added RPM logrotate config + * Fri Apr 10 2020 orignal - 2.31.0 - update to 2.31.0 diff --git a/contrib/rpm/i2pd.logrotate b/contrib/rpm/i2pd.logrotate new file mode 100644 index 00000000..795280d4 --- /dev/null +++ b/contrib/rpm/i2pd.logrotate @@ -0,0 +1,9 @@ +"/var/log/i2pd/*.log" { + copytruncate + daily + rotate 5 + compress + delaycompress + missingok + notifempty +} diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 5ece769c..bcbade2f 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,6 +1,6 @@ Name: i2pd Version: 2.31.0 -Release: 1%{?dist} +Release: 2%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -22,6 +22,7 @@ BuildRequires: openssl-devel BuildRequires: miniupnpc-devel BuildRequires: systemd-units +Requires: logrotate Requires: systemd Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd @@ -71,8 +72,9 @@ pushd build chrpath -d i2pd install -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd -install -D -m 755 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf -install -D -m 755 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf +install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf +install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf +install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/rpm/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd install -d -m 755 %{buildroot}%{_datadir}/i2pd install -d -m 755 %{buildroot}%{_datadir}/i2pd/tunnels.conf.d %{__cp} -r %{_builddir}/%{name}-%{version}/contrib/certificates/ %{buildroot}%{_datadir}/i2pd/certificates @@ -107,15 +109,19 @@ getent passwd i2pd >/dev/null || \ %doc LICENSE README.md %{_sbindir}/i2pd %{_datadir}/i2pd/certificates -%config(noreplace) %{_sysconfdir}/i2pd/* +%config(noreplace) %{_sysconfdir}/i2pd/*.conf %config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/* /%{_unitdir}/i2pd.service %dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd %dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd %{_sharedstatedir}/i2pd/certificates +%{_sysconfdir}/logrotate.d/i2pd %changelog +* Thu May 7 2020 Anatolii Vorona - 2.31.0-2 +- added RPM logrotate config + * Fri Apr 10 2020 orignal - 2.31.0 - update to 2.31.0 From 9274881c183f689364fc8b9c0c3279aeb5d09398 Mon Sep 17 00:00:00 2001 From: Anatolii Vorona Date: Fri, 8 May 2020 18:45:28 +0200 Subject: [PATCH 3679/6300] update logrotate config for reusing in debian --- contrib/{rpm => }/i2pd.logrotate | 0 contrib/rpm/i2pd-git.spec | 6 +++--- contrib/rpm/i2pd.service | 1 - contrib/rpm/i2pd.spec | 8 ++++---- 4 files changed, 7 insertions(+), 8 deletions(-) rename contrib/{rpm => }/i2pd.logrotate (100%) delete mode 120000 contrib/rpm/i2pd.service diff --git a/contrib/rpm/i2pd.logrotate b/contrib/i2pd.logrotate similarity index 100% rename from contrib/rpm/i2pd.logrotate rename to contrib/i2pd.logrotate diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 6844a25f..6967f949 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -78,8 +78,8 @@ chrpath -d i2pd %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/subscriptions.txt %{buildroot}%{_sysconfdir}/i2pd/subscriptions.txt %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.d/README %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf.d/README -%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/rpm/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd -%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/rpm/i2pd.service %{buildroot}%{_unitdir}/i2pd.service +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.service %{buildroot}%{_unitdir}/i2pd.service %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/debian/i2pd.1 %{buildroot}%{_mandir}/man1/i2pd.1 %{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd %{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd @@ -121,7 +121,7 @@ getent passwd i2pd >/dev/null || \ %changelog -* Thu May 7 2020 Anatolii Vorona - 2.31.0-2 +* Thu May 7 2020 Anatolii Vorona - 2.31.0-3 - added RPM logrotate config * Fri Apr 10 2020 orignal - 2.31.0 diff --git a/contrib/rpm/i2pd.service b/contrib/rpm/i2pd.service deleted file mode 120000 index ca477e3b..00000000 --- a/contrib/rpm/i2pd.service +++ /dev/null @@ -1 +0,0 @@ -../i2pd.service \ No newline at end of file diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index bcbade2f..49a5e666 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,6 +1,6 @@ Name: i2pd Version: 2.31.0 -Release: 2%{?dist} +Release: 3%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -74,12 +74,12 @@ chrpath -d i2pd install -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf -install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/rpm/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd +install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd install -d -m 755 %{buildroot}%{_datadir}/i2pd install -d -m 755 %{buildroot}%{_datadir}/i2pd/tunnels.conf.d %{__cp} -r %{_builddir}/%{name}-%{version}/contrib/certificates/ %{buildroot}%{_datadir}/i2pd/certificates %{__cp} -r %{_builddir}/%{name}-%{version}/contrib/tunnels.d/ %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf.d -install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/rpm/i2pd.service %{buildroot}%{_unitdir}/i2pd.service +install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.service %{buildroot}%{_unitdir}/i2pd.service install -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd install -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd ln -s %{_datadir}/%{name}/certificates %{buildroot}%{_sharedstatedir}/i2pd/certificates @@ -119,7 +119,7 @@ getent passwd i2pd >/dev/null || \ %changelog -* Thu May 7 2020 Anatolii Vorona - 2.31.0-2 +* Thu May 7 2020 Anatolii Vorona - 2.31.0-3 - added RPM logrotate config * Fri Apr 10 2020 orignal - 2.31.0 From a96c205830734dfa80986ad4a50e17f09a5c549b Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 8 May 2020 14:20:13 -0400 Subject: [PATCH 3680/6300] allow encryption type param for encrypted LeaseSet --- libi2pd/Destination.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 736390a2..105cd2e6 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -845,7 +845,8 @@ namespace client // extract encryption type params for LS2 std::set encryptionKeyTypes; - if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2 && params) + if ((GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2 || + GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) && params) { auto it = params->find (I2CP_PARAM_LEASESET_ENCRYPTION_TYPE); if (it != params->end ()) From 86782f347986fba71321e68f41f37b67b91b9677 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 May 2020 18:30:04 -0400 Subject: [PATCH 3681/6300] eliminate extra buffer allocation for incoming packets --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 22 +++++++++++----------- libi2pd/ECIESX25519AEADRatchetSession.h | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 4c7d15ef..bccf56f6 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -178,8 +178,6 @@ namespace garlic MixHash (buf, 48); // h = SHA256(h || ciphertext) buf += 48; len -= 48; // 32 data + 16 poly - // decrypt payload - std::vector payload (len - 16); // KDF2 for payload bool isStatic = !i2p::data::Tag<32> (fs).IsZero (); if (isStatic) @@ -191,6 +189,9 @@ namespace garlic } else // all zeros flags CreateNonce (1, nonce); + + // decrypt payload + std::vector payload (len - 16); // we must save original ciphertext if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD verification failed"); @@ -489,7 +490,7 @@ namespace garlic return true; } - bool ECIESX25519AEADRatchetSession::HandleNewOutgoingSessionReply (const uint8_t * buf, size_t len) + bool ECIESX25519AEADRatchetSession::HandleNewOutgoingSessionReply (uint8_t * buf, size_t len) { // we are Alice LogPrint (eLogDebug, "Garlic: reply received"); @@ -541,8 +542,7 @@ namespace garlic } i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) // decrypt payload - std::vector payload (len - 16); - if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, keydata, nonce, payload.data (), len - 16, false)) // decrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, keydata, nonce, buf, len - 16, false)) // decrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); return false; @@ -554,7 +554,7 @@ namespace garlic GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); } memcpy (m_H, h, 32); // restore m_H - HandlePayload (payload.data (), len - 16, nullptr, 0); + HandlePayload (buf, len - 16, nullptr, 0); // we have received reply to NS with LeaseSet in it SetLeaseSetUpdateStatus (eLeaseSetUpToDate); @@ -584,21 +584,21 @@ namespace garlic return true; } - bool ECIESX25519AEADRatchetSession::HandleExistingSessionMessage (const uint8_t * buf, size_t len, + bool ECIESX25519AEADRatchetSession::HandleExistingSessionMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index) { uint8_t nonce[12]; CreateNonce (index, nonce); // tag's index len -= 8; // tag - std::vector payload (len - 16); + uint8_t * payload = buf + 8; uint8_t key[32]; receiveTagset->GetSymmKey (index, key); - if (!i2p::crypto::AEADChaCha20Poly1305 (buf + 8, len - 16, buf, 8, key, nonce, payload.data (), len - 16, false)) // decrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len - 16, buf, 8, key, nonce, payload, len - 16, false)) // decrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); return false; } - HandlePayload (payload.data (), len - 16, receiveTagset, index); + HandlePayload (payload, len - 16, receiveTagset, index); int moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4 if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; moreTags -= (receiveTagset->GetNextIndex () - index); @@ -607,7 +607,7 @@ namespace garlic return true; } - bool ECIESX25519AEADRatchetSession::HandleNextMessage (const uint8_t * buf, size_t len, + bool ECIESX25519AEADRatchetSession::HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index) { m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 29d75a6b..001f5e51 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -108,7 +108,7 @@ namespace garlic ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet); ~ECIESX25519AEADRatchetSession (); - bool HandleNextMessage (const uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); + bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); std::shared_ptr WrapSingleMessage (std::shared_ptr msg); const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } @@ -133,8 +133,8 @@ namespace garlic std::shared_ptr CreateNewSessionTagset (); bool HandleNewIncomingSession (const uint8_t * buf, size_t len); - bool HandleNewOutgoingSessionReply (const uint8_t * buf, size_t len); - bool HandleExistingSessionMessage (const uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index); + bool HandleNewOutgoingSessionReply (uint8_t * buf, size_t len); + bool HandleExistingSessionMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index); void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index); void HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset); From 23be4c01dfcc32d5d747e5ccce9a00526170ae3b Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 13 May 2020 18:09:26 -0400 Subject: [PATCH 3682/6300] CreateLeaseSetClove --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 51 ++++++++++------------- libi2pd/ECIESX25519AEADRatchetSession.h | 4 +- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index bccf56f6..4f4f56ce 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -684,10 +684,10 @@ namespace garlic if (first) payloadLen += 7;// datatime if (msg && m_Destination) payloadLen += msg->GetPayloadLength () + 13 + 32; - auto leaseSet = (GetLeaseSetUpdateStatus () == eLeaseSetUpdated) ? CreateDatabaseStoreMsg (GetOwner ()->GetLeaseSet ()) : nullptr; + auto leaseSet = (GetLeaseSetUpdateStatus () == eLeaseSetUpdated) ? GetOwner ()->GetLeaseSet () : nullptr; if (leaseSet) { - payloadLen += leaseSet->GetPayloadLength () + 13; + payloadLen += leaseSet->GetBufferLen () + DATABASE_STORE_HEADER_SIZE + 13; if (!first) { // ack request @@ -725,7 +725,7 @@ namespace garlic // LeaseSet if (leaseSet) { - offset += CreateGarlicClove (leaseSet, v.data () + offset, payloadLen - offset); + offset += CreateLeaseSetClove (leaseSet, ts, v.data () + offset, payloadLen - offset); if (!first) { // ack request @@ -815,38 +815,31 @@ namespace garlic return cloveSize + 3; } - size_t ECIESX25519AEADRatchetSession::CreateDeliveryStatusClove (std::shared_ptr msg, uint8_t * buf, size_t len) + size_t ECIESX25519AEADRatchetSession::CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len) { - uint16_t cloveSize = msg->GetPayloadLength () + 9 + 37 /* delivery instruction */; + if (!ls || ls->GetStoreType () != i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) + { + LogPrint (eLogError, "Garlic: Incorrect LeasetSet type to send"); + return 0; + } + uint16_t cloveSize = 1 + 9 + DATABASE_STORE_HEADER_SIZE + ls->GetBufferLen (); // to local if ((int)len < cloveSize + 3) return 0; buf[0] = eECIESx25519BlkGalicClove; // clove type htobe16buf (buf + 1, cloveSize); // size buf += 3; - if (GetOwner ()) - { - auto inboundTunnel = GetOwner ()->GetTunnelPool ()->GetNextInboundTunnel (); - if (inboundTunnel) - { - // delivery instructions - *buf = eGarlicDeliveryTypeTunnel << 5; buf++; // delivery instructions flag tunnel - // hash and tunnelID sequence is reversed for Garlic - memcpy (buf, inboundTunnel->GetNextIdentHash (), 32); buf += 32;// To Hash - htobe32buf (buf, inboundTunnel->GetNextTunnelID ()); buf += 4;// tunnelID - } - else - { - LogPrint (eLogError, "Garlic: No inbound tunnels in the pool for DeliveryStatus"); - return 0; - } - *buf = msg->GetTypeID (); // I2NP msg type - htobe32buf (buf + 1, msg->GetMsgID ()); // msgID - htobe32buf (buf + 5, msg->GetExpiration ()/1000); // expiration in seconds - memcpy (buf + 9, msg->GetPayload (), msg->GetPayloadLength ()); - } - else - return 0; + *buf = 0; buf++; // flag and delivery instructions + *buf = eI2NPDatabaseStore; buf++; // I2NP msg type + RAND_bytes (buf, 4); buf += 4; // msgID + htobe32buf (buf, (ts + I2NP_MESSAGE_EXPIRATION_TIMEOUT)/1000); buf += 4; // expiration + // payload + memcpy (buf + DATABASE_STORE_KEY_OFFSET, ls->GetStoreHash (), 32); + buf[DATABASE_STORE_TYPE_OFFSET] = i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2; + memset (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0, 4); // replyToken = 0 + buf += DATABASE_STORE_HEADER_SIZE; + memcpy (buf, ls->GetBuffer (), ls->GetBufferLen ()); + return cloveSize + 3; - } + } void ECIESX25519AEADRatchetSession::GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags) { diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 001f5e51..ce5e2eb4 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -145,8 +145,8 @@ namespace garlic std::vector CreatePayload (std::shared_ptr msg, bool first); size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len, bool isDestination = false); - size_t CreateDeliveryStatusClove (std::shared_ptr msg, uint8_t * buf, size_t len); - + size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); + void GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags); void NewNextSendRatchet (); From b5b195e6283b916592df2c192374d70049e84f3d Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 14 May 2020 20:59:52 +0300 Subject: [PATCH 3683/6300] [windows] fix msys build Signed-off-by: R4SAS --- Win32/Win32Service.cpp | 2 +- libi2pd_client/I2PTunnel.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Win32/Win32Service.cpp b/Win32/Win32Service.cpp index 717bc887..9d1dedb4 100644 --- a/Win32/Win32Service.cpp +++ b/Win32/Win32Service.cpp @@ -4,7 +4,7 @@ #include "Win32Service.h" #include -#include +//#include #include #include "Daemon.h" diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index b7487441..78bc8018 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -69,15 +69,15 @@ namespace client return ourIP; } +#ifdef __linux__ static void MapToLoopback(const std::shared_ptr & sock, const i2p::data::IdentHash & addr) { - // bind to 127.x.x.x address // where x.x.x are first three bytes from ident auto ourIP = GetLoopbackAddressFor(addr); sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0)); - } +#endif void I2PTunnelConnection::Connect (bool isUniqueLocal) { From 716378bd6bb2e8417fd208fabcda9c80d7593b64 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 14 May 2020 21:52:33 +0300 Subject: [PATCH 3684/6300] [makefile] fix build with g++ 10 Signed-off-by: R4SAS --- Makefile.linux | 2 +- Makefile.mingw | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Makefile.linux b/Makefile.linux index fa2f2fdf..305978dd 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -20,7 +20,7 @@ else ifeq ($(shell expr match ${CXXVER} "4\.[7-9]"),3) # gcc 4.7 - 4.9 else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6 NEEDED_CXXFLAGS += -std=c++11 LDLIBS = -latomic -else ifeq ($(shell expr match ${CXXVER} "[7-9]"),1) # gcc >= 7 +else ifeq ($(shell expr match ${CXXVER} "[7-9]\|1[0-9]"),2) # gcc >= 7 NEEDED_CXXFLAGS += -std=c++17 LDLIBS = -latomic else # not supported diff --git a/Makefile.mingw b/Makefile.mingw index 1b1b4b25..251e2e90 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -2,10 +2,19 @@ USE_WIN32_APP=yes CXX = g++ WINDRES = windres CXXFLAGS := ${CXX_DEBUG} -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN -NEEDED_CXXFLAGS = -std=c++11 INCFLAGS = -Idaemon -I. LDFLAGS := ${LD_DEBUG} -Wl,-Bstatic -static-libgcc -static-libstdc++ +# detect proper flag for c++11 support by compilers +CXXVER := $(shell $(CXX) -dumpversion) +ifeq ($(shell expr match ${CXXVER} "[4]\.[7-9]\|4\.1[0-9]\|[5-6]"),4) # gcc 4.7 - 6 + NEEDED_CXXFLAGS += -std=c++11 +else ifeq ($(shell expr match ${CXXVER} "[7-9]\|1[0-9]"),2) # gcc >= 7 + NEEDED_CXXFLAGS += -std=c++17 +else # not supported +$(error Compiler too old) +endif + # Boost libraries suffix BOOST_SUFFIX = -mt From 1e4d2fd05380c9702ed6cfb4d83b9fc30c51e14f Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 14 May 2020 15:45:25 -0400 Subject: [PATCH 3685/6300] fixed for g++10 --- Makefile.linux | 2 +- Makefile.mingw | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.linux b/Makefile.linux index 305978dd..1cdf0a48 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -20,7 +20,7 @@ else ifeq ($(shell expr match ${CXXVER} "4\.[7-9]"),3) # gcc 4.7 - 4.9 else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6 NEEDED_CXXFLAGS += -std=c++11 LDLIBS = -latomic -else ifeq ($(shell expr match ${CXXVER} "[7-9]\|1[0-9]"),2) # gcc >= 7 +else ifeq ($(shell expr match ${CXXVER} "[1,7-9]"),1) # gcc >= 7 NEEDED_CXXFLAGS += -std=c++17 LDLIBS = -latomic else # not supported diff --git a/Makefile.mingw b/Makefile.mingw index 251e2e90..2782b715 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -9,7 +9,7 @@ LDFLAGS := ${LD_DEBUG} -Wl,-Bstatic -static-libgcc -static-libstdc++ CXXVER := $(shell $(CXX) -dumpversion) ifeq ($(shell expr match ${CXXVER} "[4]\.[7-9]\|4\.1[0-9]\|[5-6]"),4) # gcc 4.7 - 6 NEEDED_CXXFLAGS += -std=c++11 -else ifeq ($(shell expr match ${CXXVER} "[7-9]\|1[0-9]"),2) # gcc >= 7 +else ifeq ($(shell expr match ${CXXVER} "[1,7-9]"),1) # gcc >= 7 NEEDED_CXXFLAGS += -std=c++17 else # not supported $(error Compiler too old) From 0b1cfb2102dfde98617e643dcc36e1ca35c93b9e Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 16 May 2020 19:10:17 -0400 Subject: [PATCH 3686/6300] send response to recived datagram from ECIESX25519AEADRatchet session --- libi2pd/Datagram.cpp | 9 ++++++--- libi2pd/ECIESX25519AEADRatchetSession.cpp | 21 ++++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 7e04fce8..a69c86b0 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -247,6 +247,8 @@ namespace datagram auto path = GetSharedRoutingPath(); if(path) path->updateTime = i2p::util::GetSecondsSinceEpoch (); + if (m_RoutingSession && m_RoutingSession->IsRatchets ()) + SendMsg (nullptr); // send empty message in case if we have some data to send } std::shared_ptr DatagramSession::GetSharedRoutingPath () @@ -352,14 +354,14 @@ namespace datagram void DatagramSession::HandleSend(std::shared_ptr msg) { - m_SendQueue.push_back(msg); + if (msg || m_SendQueue.empty ()) + m_SendQueue.push_back(msg); // flush queue right away if full if(m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) FlushSendQueue(); } void DatagramSession::FlushSendQueue () { - std::vector send; auto routingPath = GetSharedRoutingPath(); // if we don't have a routing path we will drop all queued messages @@ -368,7 +370,8 @@ namespace datagram for (const auto & msg : m_SendQueue) { auto m = m_RoutingSession->WrapSingleMessage(msg); - send.push_back(i2p::tunnel::TunnelMessageBlock{i2p::tunnel::eDeliveryTypeTunnel,routingPath->remoteLease->tunnelGateway, routingPath->remoteLease->tunnelID, m}); + if (m) + send.push_back(i2p::tunnel::TunnelMessageBlock{i2p::tunnel::eDeliveryTypeTunnel,routingPath->remoteLease->tunnelGateway, routingPath->remoteLease->tunnelID, m}); } routingPath->outboundTunnel->SendTunnelDataMsg(send); } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 4f4f56ce..513ac626 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -641,6 +641,7 @@ namespace garlic { auto payload = CreatePayload (msg, m_State != eSessionStateEstablished); size_t len = payload.size (); + if (!len) return nullptr; auto m = NewI2NPMessage (len + 100); // 96 + 4 m->Align (12); // in order to get buf aligned to 16 (12 + 4) uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length @@ -708,11 +709,14 @@ namespace garlic { payloadLen += 6; if (m_NextSendRatchet->newKey) payloadLen += 32; + } + uint8_t paddingSize = 0; + if (payloadLen) + { + RAND_bytes (&paddingSize, 1); + paddingSize &= 0x0F; paddingSize++; // 1 - 16 + payloadLen += paddingSize + 3; } - uint8_t paddingSize; - RAND_bytes (&paddingSize, 1); - paddingSize &= 0x0F; paddingSize++; // 1 - 16 - payloadLen += paddingSize + 3; std::vector v(payloadLen); size_t offset = 0; // DateTime @@ -785,9 +789,12 @@ namespace garlic } } // padding - v[offset] = eECIESx25519BlkPadding; offset++; - htobe16buf (v.data () + offset, paddingSize); offset += 2; - memset (v.data () + offset, 0, paddingSize); offset += paddingSize; + if (paddingSize) + { + v[offset] = eECIESx25519BlkPadding; offset++; + htobe16buf (v.data () + offset, paddingSize); offset += 2; + memset (v.data () + offset, 0, paddingSize); offset += paddingSize; + } return v; } From 329439d0ae472c9d700a38ff6f6c1494ae429f51 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 17 May 2020 16:49:31 -0400 Subject: [PATCH 3687/6300] don't copy datagram payload --- libi2pd/Datagram.cpp | 32 ++++++++++++++++---------------- libi2pd/Datagram.h | 5 ++++- libi2pd/Gzip.cpp | 30 ++++++++++++++++++++++++++++++ libi2pd/Gzip.h | 2 ++ 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index a69c86b0..04e01796 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -24,34 +24,32 @@ namespace datagram void DatagramDestination::SendDatagramTo(const uint8_t * payload, size_t len, const i2p::data::IdentHash & identity, uint16_t fromPort, uint16_t toPort) { auto owner = m_Owner; - std::vector v(MAX_DATAGRAM_SIZE); - uint8_t * buf = v.data(); - auto localIdentity = m_Owner->GetIdentity (); - auto identityLen = localIdentity->ToBuffer (buf, MAX_DATAGRAM_SIZE); - uint8_t * signature = buf + identityLen; + auto localIdentity = m_Owner->GetIdentity (); + auto identityLen = localIdentity->GetFullLen (); auto signatureLen = localIdentity->GetSignatureLen (); - uint8_t * buf1 = signature + signatureLen; size_t headerLen = identityLen + signatureLen; - - memcpy (buf1, payload, len); + + std::vector header(headerLen); + localIdentity->ToBuffer (header.data (), identityLen); + uint8_t * signature = header.data () + identityLen; if (localIdentity->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) { uint8_t hash[32]; - SHA256(buf1, len, hash); + SHA256(payload, len, hash); owner->Sign (hash, 32, signature); } else - owner->Sign (buf1, len, signature); + owner->Sign (payload, len, signature); - auto msg = CreateDataMessage (buf, len + headerLen, fromPort, toPort); auto session = ObtainSession(identity); + auto msg = CreateDataMessage ({{header.data (), headerLen}, {payload, len}}, fromPort, toPort, false, !session->IsRatchets ()); // datagram session->SendMsg(msg); } void DatagramDestination::SendRawDatagramTo(const uint8_t * payload, size_t len, const i2p::data::IdentHash & identity, uint16_t fromPort, uint16_t toPort) { - auto msg = CreateDataMessage (payload, len, fromPort, toPort, true); // raw auto session = ObtainSession(identity); + auto msg = CreateDataMessage ({{payload, len}}, fromPort, toPort, true, !session->IsRatchets ()); // raw session->SendMsg(msg); } @@ -122,12 +120,14 @@ namespace datagram } - std::shared_ptr DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort, bool isRaw) + std::shared_ptr DatagramDestination::CreateDataMessage ( + const std::vector >& payloads, + uint16_t fromPort, uint16_t toPort, bool isRaw, bool checksum) { auto msg = NewI2NPMessage (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for length - size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); + size_t size = m_Deflator.Deflate (payloads, buf, msg->maxLen - msg->len); if (size) { htobe32buf (msg->GetPayload (), size); // length @@ -135,7 +135,7 @@ namespace datagram htobe16buf (buf + 6, toPort); // destination port buf[9] = isRaw ? i2p::client::PROTOCOL_TYPE_RAW : i2p::client::PROTOCOL_TYPE_DATAGRAM; // raw or datagram protocol msg->len += size + 4; - msg->FillI2NPMessageHeader (eI2NPData); + msg->FillI2NPMessageHeader (eI2NPData, 0, checksum); } else msg = nullptr; @@ -247,7 +247,7 @@ namespace datagram auto path = GetSharedRoutingPath(); if(path) path->updateTime = i2p::util::GetSecondsSinceEpoch (); - if (m_RoutingSession && m_RoutingSession->IsRatchets ()) + if (IsRatchets ()) SendMsg (nullptr); // send empty message in case if we have some data to send } diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index 0cfec838..87f3a7a8 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -51,6 +51,8 @@ namespace datagram /** get the last time in milliseconds for when we used this datagram session */ uint64_t LastActivity() const { return m_LastUse; } + bool IsRatchets () const { return m_RoutingSession && m_RoutingSession->IsRatchets (); } + struct Info { std::shared_ptr IBGW; @@ -130,7 +132,8 @@ namespace datagram std::shared_ptr ObtainSession(const i2p::data::IdentHash & ident); - std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort, bool isRaw = false); + std::shared_ptr CreateDataMessage (const std::vector >& payloads, + uint16_t fromPort, uint16_t toPort, bool isRaw = false, bool checksum = true); void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len); void HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); diff --git a/libi2pd/Gzip.cpp b/libi2pd/Gzip.cpp index 2ca711b0..cb6a79af 100644 --- a/libi2pd/Gzip.cpp +++ b/libi2pd/Gzip.cpp @@ -116,6 +116,36 @@ namespace data return 0; } + size_t GzipDeflator::Deflate (const std::vector >& bufs, uint8_t * out, size_t outLen) + { + if (m_IsDirty) deflateReset (&m_Deflator); + m_IsDirty = true; + size_t offset = 0; + int err; + for (const auto& it: bufs) + { + m_Deflator.next_in = const_cast(it.first); + m_Deflator.avail_in = it.second; + m_Deflator.next_out = out + offset; + m_Deflator.avail_out = outLen - offset; + auto flush = (it == bufs.back ()) ? Z_FINISH : Z_NO_FLUSH; + err = deflate (&m_Deflator, flush); + if (err) + { + if (flush && err == Z_STREAM_END) + { + out[9] = 0xff; // OS is always unknown + return outLen - m_Deflator.avail_out; + } + break; + } + offset = outLen - m_Deflator.avail_out; + } + // else + LogPrint (eLogError, "Gzip: Deflate error ", err); + return 0; + } + size_t GzipNoCompression (const uint8_t * in, uint16_t inLen, uint8_t * out, size_t outLen) { static const uint8_t gzipHeader[11] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01 }; diff --git a/libi2pd/Gzip.h b/libi2pd/Gzip.h index cf08d920..e5b8775e 100644 --- a/libi2pd/Gzip.h +++ b/libi2pd/Gzip.h @@ -2,6 +2,7 @@ #define GZIP_H__ #include +#include namespace i2p { @@ -34,6 +35,7 @@ namespace data void SetCompressionLevel (int level); size_t Deflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen); + size_t Deflate (const std::vector >& bufs, uint8_t * out, size_t outLen); private: From e1b1032df9501b3962067305ed346dc9e33725e7 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 May 2020 08:29:09 -0400 Subject: [PATCH 3688/6300] reseeds update --- .../reseed/hankhill19580_at_gmail.com.crt | 34 +++++++++++++++++++ libi2pd/Config.cpp | 5 +-- 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 contrib/certificates/reseed/hankhill19580_at_gmail.com.crt diff --git a/contrib/certificates/reseed/hankhill19580_at_gmail.com.crt b/contrib/certificates/reseed/hankhill19580_at_gmail.com.crt new file mode 100644 index 00000000..2f177ee1 --- /dev/null +++ b/contrib/certificates/reseed/hankhill19580_at_gmail.com.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +CCOJyu3XtyzFf+tAeixOnuQ= +-----END CERTIFICATE----- diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 580e9156..b741b118 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -192,15 +192,12 @@ namespace config { ("reseed.urls", value()->default_value( "https://reseed.i2p-projekt.de/," "https://i2p.mooo.com/netDb/," - "https://netdb.i2p2.no/," "https://reseed.i2p2.no/," - "https://reseed2.i2p2.no/," - // "https://us.reseed.i2p2.no:444/," // mamoth's shit - // "https://uk.reseed.i2p2.no:444/," // mamoth's shit "https://reseed-fr.i2pd.xyz/," "https://reseed.memcpy.io/," "https://reseed.onion.im/," "https://i2pseed.creativecowpat.net:8443/," + "https://reseed.i2pgit.org/," "https://i2p.novg.net/" ), "Reseed URLs, separated by comma") ; From d4bfeab36ce06f1eba42cfeda0bbfcec068dbea4 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 May 2020 12:01:13 -0400 Subject: [PATCH 3689/6300] pass gzip parameter to UDP tunnels --- libi2pd/Datagram.cpp | 5 +++-- libi2pd/Datagram.h | 3 ++- libi2pd/Destination.cpp | 4 ++-- libi2pd/Destination.h | 2 +- libi2pd_client/ClientContext.cpp | 5 +++-- libi2pd_client/ClientContext.h | 1 + libi2pd_client/I2PTunnel.cpp | 8 ++++---- libi2pd_client/I2PTunnel.h | 4 ++-- 8 files changed, 18 insertions(+), 14 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 04e01796..7eb20460 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -11,8 +11,8 @@ namespace i2p { namespace datagram { - DatagramDestination::DatagramDestination (std::shared_ptr owner): - m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr) + DatagramDestination::DatagramDestination (std::shared_ptr owner, bool gzip): + m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr), m_Gzip (gzip) { } @@ -127,6 +127,7 @@ namespace datagram auto msg = NewI2NPMessage (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for length + m_Deflator.SetCompressionLevel (m_Gzip ? Z_DEFAULT_COMPRESSION : Z_NO_COMPRESSION); size_t size = m_Deflator.Deflate (payloads, buf, msg->maxLen - msg->len); if (size) { diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index 87f3a7a8..5fcaec3a 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -107,7 +107,7 @@ namespace datagram public: - DatagramDestination (std::shared_ptr owner); + DatagramDestination (std::shared_ptr owner, bool gzip); ~DatagramDestination (); void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); @@ -146,6 +146,7 @@ namespace datagram std::shared_ptr m_Owner; Receiver m_Receiver; // default RawReceiver m_RawReceiver; // default + bool m_Gzip; // gzip compression of data messages std::mutex m_SessionsMutex; std::map m_Sessions; std::mutex m_ReceiversMutex; diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 105cd2e6..782e2841 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1105,10 +1105,10 @@ namespace client return dest; } - i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination () + i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination (bool gzip) { if (m_DatagramDestination == nullptr) - m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis ()); + m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis (), gzip); return m_DatagramDestination; } diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 8c423d77..04cf05f4 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -236,7 +236,7 @@ namespace client // datagram i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; - i2p::datagram::DatagramDestination * CreateDatagramDestination (); + i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); // implements LocalDestination bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index c8a9ddfe..765d3dec 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -569,7 +569,8 @@ namespace client if (!localDestination) localDestination = m_SharedLocalDestination; - auto clientTunnel = std::make_shared(name, dest, end, localDestination, destinationPort); + bool gzip = section.second.get (I2P_CLIENT_TUNNEL_GZIP, true); + auto clientTunnel = std::make_shared(name, dest, end, localDestination, destinationPort, gzip); if(m_ClientForwards.insert(std::make_pair(end, clientTunnel)).second) clientTunnel->Start(); else @@ -672,7 +673,7 @@ namespace client // TODO: hostnames auto localAddress = boost::asio::ip::address::from_string(address); boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); - auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, port); + auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, port, gzip); if(!isUniqueLocal) { LogPrint(eLogInfo, "Clients: disabling loopback address mapping"); diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 610914bf..1a56575f 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -33,6 +33,7 @@ namespace client const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination"; const char I2P_CLIENT_TUNNEL_KEYS[] = "keys"; + const char I2P_CLIENT_TUNNEL_GZIP[] = "gzip"; const char I2P_CLIENT_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_CLIENT_TUNNEL_CRYPTO_TYPE[] = "cryptotype"; const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport"; diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 78bc8018..dc2a8811 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -696,7 +696,7 @@ namespace client } I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, - boost::asio::ip::address localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port) : + boost::asio::ip::address localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port, bool gzip) : m_IsUniqueLocal(true), m_Name(name), m_LocalAddress(localAddress), @@ -704,7 +704,7 @@ namespace client { m_LocalDest = localDestination; m_LocalDest->Start(); - auto dgram = m_LocalDest->CreateDatagramDestination(); + auto dgram = m_LocalDest->CreateDatagramDestination(gzip); dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); } @@ -745,7 +745,7 @@ namespace client I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, - uint16_t remotePort) : + uint16_t remotePort, bool gzip) : m_Name(name), m_RemoteDest(remoteDest), m_LocalDest(localDestination), @@ -756,7 +756,7 @@ namespace client RemotePort(remotePort), m_cancel_resolve(false) { - auto dgram = m_LocalDest->CreateDatagramDestination(); + auto dgram = m_LocalDest->CreateDatagramDestination(gzip); dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index 0d1ac9a8..fbe2f7bb 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -198,7 +198,7 @@ namespace client I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::address localAddress, - boost::asio::ip::udp::endpoint forwardTo, uint16_t port); + boost::asio::ip::udp::endpoint forwardTo, uint16_t port, bool gzip); ~I2PUDPServerTunnel(); /** expire stale udp conversations */ void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); @@ -228,7 +228,7 @@ namespace client public: I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, - uint16_t remotePort); + uint16_t remotePort, bool gzip); ~I2PUDPClientTunnel(); void Start(); const char * GetName() const { return m_Name.c_str(); } From 1c8d662e30f591bb27ac58cc39e4341eaad57bcb Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 May 2020 16:42:06 -0400 Subject: [PATCH 3690/6300] don't add padding for optimal packet size --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 10 +++++++--- libi2pd/ECIESX25519AEADRatchetSession.h | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 513ac626..18e1c7b6 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -713,9 +713,13 @@ namespace garlic uint8_t paddingSize = 0; if (payloadLen) { - RAND_bytes (&paddingSize, 1); - paddingSize &= 0x0F; paddingSize++; // 1 - 16 - payloadLen += paddingSize + 3; + // don't create padding if we are close to optimal size + if (first || payloadLen + 19 <= ECIESX25519_OPTIMAL_PAYLOAD_SIZE || payloadLen > ECIESX25519_OPTIMAL_PAYLOAD_SIZE) + { + RAND_bytes (&paddingSize, 1); + paddingSize &= 0x0F; paddingSize++; // 1 - 16 + payloadLen += paddingSize + 3; + } } std::vector v(payloadLen); size_t offset = 0; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index ce5e2eb4..14f97ce9 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -26,6 +26,9 @@ namespace garlic const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 160; const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; + const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */ + // - 16 /* I2NP header */ - 16 /* poly hash */ - 8 /* tag */ - 4 /* garlic length */ + class ECIESX25519AEADRatchetSession; class RatchetTagSet { From 7b418b3adfe1ef59922aa65658a8603243bc9c34 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 May 2020 17:51:45 -0400 Subject: [PATCH 3691/6300] insert whole message to queue --- libi2pd/Streaming.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 2a4175f4..3355d99a 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -423,15 +423,8 @@ namespace stream size_t Stream::Send (const uint8_t * buf, size_t len) { - size_t sent = len; - while(len > MAX_PACKET_SIZE) - { - AsyncSend (buf, MAX_PACKET_SIZE, nullptr); - buf += MAX_PACKET_SIZE; - len -= MAX_PACKET_SIZE; - } AsyncSend (buf, len, nullptr); - return sent; + return len; } void Stream::AsyncSend (const uint8_t * buf, size_t len, SendHandler handler) From c7c6e5917a49f234e190fa16cd29a687ececc1d0 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 May 2020 20:45:25 -0400 Subject: [PATCH 3692/6300] Streaming MTU size 1812 for ECIESX25519AEADRatchet --- libi2pd/Streaming.cpp | 16 +++++++++++----- libi2pd/Streaming.h | 2 ++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 3355d99a..ad3f6209 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -63,7 +63,7 @@ namespace stream m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), - m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0) + m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0), m_MTU (STREAMING_MTU) { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); m_RemoteIdentity = remote->GetIdentity (); @@ -75,7 +75,7 @@ namespace stream m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), - m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0) + m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0), m_MTU (STREAMING_MTU) { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); } @@ -473,6 +473,12 @@ namespace stream { // initial packet m_Status = eStreamStatusOpen; + if (!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ());; + if (m_RemoteLeaseSet) + { + m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); + m_MTU = m_RoutingSession->IsRatchets () ? STREAMING_MTU_RATCHETS : STREAMING_MTU; + } uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED | PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED; if (isNoAck) flags |= PACKET_FLAG_NO_ACK; @@ -486,7 +492,7 @@ namespace stream size += 2; // options size m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen); size += identityLen; // from - htobe16buf (packet + size, STREAMING_MTU); + htobe16buf (packet + size, m_MTU); size += 2; // max packet size if (isOfflineSignature) { @@ -498,7 +504,7 @@ namespace stream memset (signature, 0, signatureLen); // zeroes for now size += signatureLen; // signature htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size - size += m_SendBuffer.Get (packet + size, STREAMING_MTU); // payload + size += m_SendBuffer.Get (packet + size, m_MTU); // payload m_LocalDestination.GetOwner ()->Sign (packet, size, signature); } else @@ -508,7 +514,7 @@ namespace stream size += 2; // flags htobuf16 (packet + size, 0); // no options size += 2; // options size - size += m_SendBuffer.Get(packet + size, STREAMING_MTU); // payload + size += m_SendBuffer.Get(packet + size, m_MTU); // payload } p->len = size; packets.push_back (p); diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index 64e4c18d..d3d47794 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -41,6 +41,7 @@ namespace stream const uint16_t PACKET_FLAG_OFFLINE_SIGNATURE = 0x0800; const size_t STREAMING_MTU = 1730; + const size_t STREAMING_MTU_RATCHETS = 1812; const size_t MAX_PACKET_SIZE = 4096; const size_t COMPRESSION_THRESHOLD_SIZE = 66; const int MAX_NUM_RESEND_ATTEMPTS = 6; @@ -234,6 +235,7 @@ namespace stream int m_WindowSize, m_RTT, m_RTO, m_AckDelay; uint64_t m_LastWindowSizeIncreaseTime; int m_NumResendAttempts; + size_t m_MTU; }; class StreamingDestination: public std::enable_shared_from_this From 9fb59e128b601513086f0d30e2cce10d49c56efb Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 May 2020 22:31:36 -0400 Subject: [PATCH 3693/6300] resubmit updated LeaseSet if not confirmed --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 18e1c7b6..9781a70a 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -685,7 +685,10 @@ namespace garlic if (first) payloadLen += 7;// datatime if (msg && m_Destination) payloadLen += msg->GetPayloadLength () + 13 + 32; - auto leaseSet = (GetLeaseSetUpdateStatus () == eLeaseSetUpdated) ? GetOwner ()->GetLeaseSet () : nullptr; + auto leaseSet = (GetLeaseSetUpdateStatus () == eLeaseSetUpdated || + (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && + ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT)) ? + GetOwner ()->GetLeaseSet () : nullptr; if (leaseSet) { payloadLen += leaseSet->GetBufferLen () + DATABASE_STORE_HEADER_SIZE + 13; From 3db4421aa7f808924cb7b0317665f9e03631f0b2 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 19 May 2020 10:48:23 -0400 Subject: [PATCH 3694/6300] don't invoke gzip for decompression if no compression --- libi2pd/Gzip.cpp | 42 +++++++++++++++++++++++++++++------------- libi2pd/I2PEndian.h | 15 +++++++++++++++ 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/libi2pd/Gzip.cpp b/libi2pd/Gzip.cpp index cb6a79af..f098987a 100644 --- a/libi2pd/Gzip.cpp +++ b/libi2pd/Gzip.cpp @@ -32,18 +32,34 @@ namespace data size_t GzipInflator::Inflate (const uint8_t * in, size_t inLen, uint8_t * out, size_t outLen) { - if (m_IsDirty) inflateReset (&m_Inflator); - m_IsDirty = true; - m_Inflator.next_in = const_cast(in); - m_Inflator.avail_in = inLen; - m_Inflator.next_out = out; - m_Inflator.avail_out = outLen; - int err; - if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END) - return outLen - m_Inflator.avail_out; - // else - LogPrint (eLogError, "Gzip: Inflate error ", err); - return 0; + if (inLen < 23) return 0; + if (in[10] == 0x01) // non compressed + { + size_t len = bufle16toh (in + 11); + if (len + 23 < inLen) + { + LogPrint (eLogError, "Gzip: Incorrect length"); + return 0; + } + if (len > outLen) len = outLen; + memcpy (out, in + 15, len); + return len; + } + else + { + if (m_IsDirty) inflateReset (&m_Inflator); + m_IsDirty = true; + m_Inflator.next_in = const_cast(in); + m_Inflator.avail_in = inLen; + m_Inflator.next_out = out; + m_Inflator.avail_out = outLen; + int err; + if ((err = inflate (&m_Inflator, Z_NO_FLUSH)) == Z_STREAM_END) + return outLen - m_Inflator.avail_out; + // else + LogPrint (eLogError, "Gzip: Inflate error ", err); + return 0; + } } void GzipInflator::Inflate (const uint8_t * in, size_t inLen, std::ostream& os) @@ -148,7 +164,7 @@ namespace data size_t GzipNoCompression (const uint8_t * in, uint16_t inLen, uint8_t * out, size_t outLen) { - static const uint8_t gzipHeader[11] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01 }; + static const uint8_t gzipHeader[11] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x01 }; if (outLen < (size_t)inLen + 23) return 0; memcpy (out, gzipHeader, 11); htole16buf (out + 11, inLen); diff --git a/libi2pd/I2PEndian.h b/libi2pd/I2PEndian.h index 0798f688..04a9480e 100644 --- a/libi2pd/I2PEndian.h +++ b/libi2pd/I2PEndian.h @@ -128,5 +128,20 @@ inline void htole64buf(void *buf, uint64_t big64) htobuf64(buf, htole64(big64)); } +inline uint16_t bufle16toh(const void *buf) +{ + return le16toh(buf16toh(buf)); +} + +inline uint32_t bufle32toh(const void *buf) +{ + return le32toh(buf32toh(buf)); +} + +inline uint64_t bufle64toh(const void *buf) +{ + return le64toh(buf64toh(buf)); +} + #endif // I2PENDIAN_H__ From 7ebf2f010c463bcb3ccf30b3efa4d0846eead255 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 19 May 2020 19:03:12 -0400 Subject: [PATCH 3695/6300] shorter padding for optimal packet length --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 9781a70a..7b386d92 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -716,12 +716,18 @@ namespace garlic uint8_t paddingSize = 0; if (payloadLen) { - // don't create padding if we are close to optimal size - if (first || payloadLen + 19 <= ECIESX25519_OPTIMAL_PAYLOAD_SIZE || payloadLen > ECIESX25519_OPTIMAL_PAYLOAD_SIZE) - { + int delta = (int)ECIESX25519_OPTIMAL_PAYLOAD_SIZE - (int)payloadLen; + if (delta < 0 || delta > 3) // don't create padding if we are close to optimal size + { RAND_bytes (&paddingSize, 1); - paddingSize &= 0x0F; paddingSize++; // 1 - 16 - payloadLen += paddingSize + 3; + paddingSize &= 0x0F; // 0 - 15 + if (delta > 3) + { + delta -= 3; + if (paddingSize >= delta) paddingSize %= delta; + } + paddingSize++; + payloadLen += paddingSize + 3; } } std::vector v(payloadLen); From 648d035a0fe6cd44fab7108f09314b9d75965231 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 19 May 2020 21:02:32 -0400 Subject: [PATCH 3696/6300] GzipNoCompression for datagrams --- libi2pd/Datagram.cpp | 4 ++-- libi2pd/Gzip.cpp | 22 ++++++++++++++++++++++ libi2pd/Gzip.h | 1 + 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 7eb20460..08cd86bf 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -127,8 +127,8 @@ namespace datagram auto msg = NewI2NPMessage (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for length - m_Deflator.SetCompressionLevel (m_Gzip ? Z_DEFAULT_COMPRESSION : Z_NO_COMPRESSION); - size_t size = m_Deflator.Deflate (payloads, buf, msg->maxLen - msg->len); + size_t size = m_Gzip ? m_Deflator.Deflate (payloads, buf, msg->maxLen - msg->len) : + i2p::data::GzipNoCompression (payloads, buf, msg->maxLen - msg->len); if (size) { htobe32buf (msg->GetPayload (), size); // length diff --git a/libi2pd/Gzip.cpp b/libi2pd/Gzip.cpp index f098987a..f36620ae 100644 --- a/libi2pd/Gzip.cpp +++ b/libi2pd/Gzip.cpp @@ -174,6 +174,28 @@ namespace data htole32buf (out + inLen + 19, inLen); return inLen + 23; } + + size_t GzipNoCompression (const std::vector >& bufs, uint8_t * out, size_t outLen) + { + static const uint8_t gzipHeader[11] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x01 }; + memcpy (out, gzipHeader, 11); + uint32_t crc = 0; + size_t len = 0, len1; + for (const auto& it: bufs) + { + len1 = len; + len += it.second; + if (outLen < len + 23) return 0; + memcpy (out + 15 + len1, it.first, it.second); + crc = crc32 (crc, it.first, it.second); + } + if (len > 0xffff) return 0; + htole32buf (out + len + 15, crc); + htole32buf (out + len + 19, len); + htole16buf (out + 11, len); + htole16buf (out + 13, 0xffff - len); + return len + 23; + } } // data } // i2p diff --git a/libi2pd/Gzip.h b/libi2pd/Gzip.h index e5b8775e..16776d35 100644 --- a/libi2pd/Gzip.h +++ b/libi2pd/Gzip.h @@ -44,6 +44,7 @@ namespace data }; size_t GzipNoCompression (const uint8_t * in, uint16_t inLen, uint8_t * out, size_t outLen); // for < 64K + size_t GzipNoCompression (const std::vector >& bufs, uint8_t * out, size_t outLen); // for total size < 64K } // data } // i2p From db6a0e6ad9124ef9b6217af487b3c844234ba5e1 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 20 May 2020 09:17:54 +0000 Subject: [PATCH 3697/6300] [cmake] remove windows build support (#1517) Removes support for MSVC, MSYS, MinGW and included NSIS installer in cmake --- build/CMakeLists.txt | 527 ++++++++++------------------------ build/cmake-zlib-amd64.patch | 10 - build/cmake-zlib-static.patch | 28 -- 3 files changed, 158 insertions(+), 407 deletions(-) delete mode 100644 build/cmake-zlib-amd64.patch delete mode 100644 build/cmake-zlib-static.patch diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 1a459bf8..8271ce3a 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -1,28 +1,32 @@ -cmake_minimum_required ( VERSION 2.8.12 ) +cmake_minimum_required(VERSION 2.8.12) # this addresses CMP0059 with CMake > 3.3 for PCH flags -cmake_policy( VERSION 2.8.12 ) -project ( "i2pd" ) +cmake_policy(VERSION 2.8.12) +project("i2pd") # for debugging #set(CMAKE_VERBOSE_MAKEFILE on) +# Win32 build with cmake is not supported +if(WIN32 OR MSVC OR MSYS OR MINGW) + message(SEND_ERROR "cmake build for windows is not supported. Please use MSYS2 with makefiles in project root.") +endif() + # configurale options -option(WITH_AESNI "Use AES-NI instructions set" OFF) -option(WITH_AVX "Use AVX instructions" OFF) -option(WITH_HARDENING "Use hardening compiler flags" OFF) -option(WITH_LIBRARY "Build library" ON) -option(WITH_BINARY "Build binary" ON) -option(WITH_STATIC "Static build" OFF) -option(WITH_UPNP "Include support for UPnP client" OFF) -option(WITH_PCH "Use precompiled header" OFF) -option(WITH_GUI "Include GUI (currently MS Windows only)" ON) -option(WITH_MESHNET "Build for cjdns test network" OFF) -option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) -option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) +option(WITH_AESNI "Use AES-NI instructions set" OFF) +option(WITH_AVX "Use AVX instructions" OFF) +option(WITH_HARDENING "Use hardening compiler flags" OFF) +option(WITH_LIBRARY "Build library" ON) +option(WITH_BINARY "Build binary" ON) +option(WITH_STATIC "Static build" OFF) +option(WITH_UPNP "Include support for UPnP client" OFF) +option(WITH_PCH "Use precompiled header" OFF) +option(WITH_MESHNET "Build for cjdns test network" OFF) +option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) +option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) # paths -set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) -set ( CMAKE_SOURCE_DIR ".." ) +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules") +set(CMAKE_SOURCE_DIR "..") # architecture include(TargetArch) @@ -30,67 +34,65 @@ target_architecture(ARCHITECTURE) set(LIBI2PD_SRC_DIR ../libi2pd) set(LIBI2PD_CLIENT_SRC_DIR ../libi2pd_client) +set(DAEMON_SRC_DIR ../daemon) include_directories(${LIBI2PD_SRC_DIR}) include_directories(${LIBI2PD_CLIENT_SRC_DIR}) +include_directories(${DAEMON_SRC_DIR}) -set (LIBI2PD_SRC +set(LIBI2PD_SRC + "${LIBI2PD_SRC_DIR}/api.cpp" + "${LIBI2PD_SRC_DIR}/Base.cpp" + "${LIBI2PD_SRC_DIR}/Blinding.cpp" "${LIBI2PD_SRC_DIR}/BloomFilter.cpp" + "${LIBI2PD_SRC_DIR}/ChaCha20.cpp" "${LIBI2PD_SRC_DIR}/Config.cpp" "${LIBI2PD_SRC_DIR}/CPU.cpp" "${LIBI2PD_SRC_DIR}/Crypto.cpp" "${LIBI2PD_SRC_DIR}/CryptoKey.cpp" + "${LIBI2PD_SRC_DIR}/Datagram.cpp" + "${LIBI2PD_SRC_DIR}/Destination.cpp" + "${LIBI2PD_SRC_DIR}/ECIESX25519AEADRatchetSession.cpp" + "${LIBI2PD_SRC_DIR}/Ed25519.cpp" + "${LIBI2PD_SRC_DIR}/Elligator.cpp" + "${LIBI2PD_SRC_DIR}/Family.cpp" + "${LIBI2PD_SRC_DIR}/FS.cpp" "${LIBI2PD_SRC_DIR}/Garlic.cpp" + "${LIBI2PD_SRC_DIR}/Gost.cpp" "${LIBI2PD_SRC_DIR}/Gzip.cpp" "${LIBI2PD_SRC_DIR}/HTTP.cpp" "${LIBI2PD_SRC_DIR}/I2NPProtocol.cpp" "${LIBI2PD_SRC_DIR}/Identity.cpp" "${LIBI2PD_SRC_DIR}/LeaseSet.cpp" - "${LIBI2PD_SRC_DIR}/FS.cpp" "${LIBI2PD_SRC_DIR}/Log.cpp" - "${LIBI2PD_SRC_DIR}/NTCPSession.cpp" - "${LIBI2PD_SRC_DIR}/NetDbRequests.cpp" "${LIBI2PD_SRC_DIR}/NetDb.cpp" + "${LIBI2PD_SRC_DIR}/NetDbRequests.cpp" + "${LIBI2PD_SRC_DIR}/NTCP2.cpp" + "${LIBI2PD_SRC_DIR}/NTCPSession.cpp" + "${LIBI2PD_SRC_DIR}/Poly1305.cpp" "${LIBI2PD_SRC_DIR}/Profiling.cpp" "${LIBI2PD_SRC_DIR}/Reseed.cpp" "${LIBI2PD_SRC_DIR}/RouterContext.cpp" "${LIBI2PD_SRC_DIR}/RouterInfo.cpp" + "${LIBI2PD_SRC_DIR}/Signature.cpp" "${LIBI2PD_SRC_DIR}/SSU.cpp" "${LIBI2PD_SRC_DIR}/SSUData.cpp" "${LIBI2PD_SRC_DIR}/SSUSession.cpp" "${LIBI2PD_SRC_DIR}/Streaming.cpp" - "${LIBI2PD_SRC_DIR}/Destination.cpp" - "${LIBI2PD_SRC_DIR}/TransitTunnel.cpp" - "${LIBI2PD_SRC_DIR}/Tunnel.cpp" - "${LIBI2PD_SRC_DIR}/TunnelGateway.cpp" - "${LIBI2PD_SRC_DIR}/Transports.cpp" - "${LIBI2PD_SRC_DIR}/TunnelEndpoint.cpp" - "${LIBI2PD_SRC_DIR}/TunnelPool.cpp" - "${LIBI2PD_SRC_DIR}/Base.cpp" - "${LIBI2PD_SRC_DIR}/util.cpp" - "${LIBI2PD_SRC_DIR}/Datagram.cpp" - "${LIBI2PD_SRC_DIR}/Family.cpp" - "${LIBI2PD_SRC_DIR}/Signature.cpp" "${LIBI2PD_SRC_DIR}/Timestamp.cpp" - "${LIBI2PD_SRC_DIR}/api.cpp" - "${LIBI2PD_SRC_DIR}/Gost.cpp" - "${LIBI2PD_SRC_DIR}/ChaCha20.cpp" - "${LIBI2PD_SRC_DIR}/Poly1305.cpp" - "${LIBI2PD_SRC_DIR}/Ed25519.cpp" - "${LIBI2PD_SRC_DIR}/NTCP2.cpp" - "${LIBI2PD_SRC_DIR}/Blinding.cpp" - "${LIBI2PD_SRC_DIR}/Elligator.cpp" - "${LIBI2PD_SRC_DIR}/ECIESX25519AEADRatchetSession.cpp" + "${LIBI2PD_SRC_DIR}/TransitTunnel.cpp" + "${LIBI2PD_SRC_DIR}/Transports.cpp" + "${LIBI2PD_SRC_DIR}/Tunnel.cpp" + "${LIBI2PD_SRC_DIR}/TunnelEndpoint.cpp" + "${LIBI2PD_SRC_DIR}/TunnelGateway.cpp" + "${LIBI2PD_SRC_DIR}/TunnelPool.cpp" + "${LIBI2PD_SRC_DIR}/util.cpp" ) -if (WIN32 OR MSYS) - list (APPEND LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/I2PEndian.cpp") -endif () - add_library(libi2pd ${LIBI2PD_SRC}) set_target_properties(libi2pd PROPERTIES PREFIX "") -if (WITH_LIBRARY) +if(WITH_LIBRARY) install(TARGETS libi2pd EXPORT libi2pd ARCHIVE DESTINATION lib @@ -101,7 +103,7 @@ if (WITH_LIBRARY) # install(EXPORT libi2pd DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() -set (CLIENT_SRC +set(CLIENT_SRC "${LIBI2PD_CLIENT_SRC_DIR}/AddressBook.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/BOB.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/ClientContext.cpp" @@ -117,7 +119,7 @@ set (CLIENT_SRC add_library(libi2pdclient ${CLIENT_SRC}) set_target_properties(libi2pdclient PROPERTIES PREFIX "") -if (WITH_LIBRARY) +if(WITH_LIBRARY) install(TARGETS libi2pdclient EXPORT libi2pdclient ARCHIVE DESTINATION lib @@ -125,9 +127,7 @@ if (WITH_LIBRARY) COMPONENT Libraries) endif() -set(DAEMON_SRC_DIR ../daemon) - -set (DAEMON_SRC +set(DAEMON_SRC "${DAEMON_SRC_DIR}/Daemon.cpp" "${DAEMON_SRC_DIR}/HTTPServer.cpp" "${DAEMON_SRC_DIR}/I2PControl.cpp" @@ -135,37 +135,20 @@ set (DAEMON_SRC "${DAEMON_SRC_DIR}/UPnP.cpp" ) -if (WITH_MESHNET) +if(WITH_MESHNET) add_definitions(-DMESHNET) -endif () +endif() -if (WITH_UPNP) +if(WITH_UPNP) add_definitions(-DUSE_UPNP) - if (NOT MSVC AND NOT MSYS) - set(DL_LIB ${CMAKE_DL_LIBS}) - endif () -endif () +endif() -# compiler flags customization (by vendor) -if (MSVC) - add_definitions( -DWIN32_LEAN_AND_MEAN -DNOMINMAX ) - # TODO Check & report to Boost dev, there should be no need for these two - add_definitions( -DBOOST_THREAD_NO_LIB -DBOOST_CHRONO_NO_LIB ) - set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL" ) - set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /INCREMENTAL:NO /LTCG" ) - set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} /GL" ) - set( CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /INCREMENTAL:NO /LTCG" ) -else() - if (MSYS OR MINGW) - add_definitions( -DWIN32_LEAN_AND_MEAN ) - endif () - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch -Wno-unused-parameter" ) - set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic" ) - # TODO: The following is incompatible with static build and enabled hardening for OpenWRT. - # Multiple definitions of __stack_chk_fail (libssp & libc) - set( CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -flto -s -ffunction-sections -fdata-sections" ) - set( CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections" ) # -flto is added from above -endif () +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch -Wno-unused-parameter") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic") +# TODO: The following is incompatible with static build and enabled hardening for OpenWRT. +# Multiple definitions of __stack_chk_fail(libssp & libc) +set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -flto -s -ffunction-sections -fdata-sections") +set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections") # -flto is added from above # check for c++17 & c++11 support include(CheckCXXCompilerFlag) @@ -179,72 +162,53 @@ else() message(SEND_ERROR "C++17 nor C++11 standard not seems to be supported by compiler. Too old version?") endif() -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe") - if (WITH_HARDENING) - add_definitions( "-D_FORTIFY_SOURCE=2" ) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat -Wformat-security -Werror=format-security" ) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector --param ssp-buffer-size=4" ) - endif () -elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe") + if(WITH_HARDENING) + add_definitions("-D_FORTIFY_SOURCE=2") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat -Wformat-security -Werror=format-security") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector --param ssp-buffer-size=4") + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # more tweaks - if (LINUX) - set (CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libstdc++" ) # required for + if(LINUX) + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libstdc++") # required for list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++") # required to link with -stdlib=libstdc++ endif() - if (NOT (MSVC OR MSYS OR APPLE)) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions" ) + if(NOT APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions") endif() -endif () +endif() -if (WITH_HARDENING AND MSVC) - # Most security options like dynamic base, buffer & stack checks are ON by default - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /guard:cf" ) -endif () - -# compiler flags customization (by system) -if (UNIX) - list (APPEND DAEMON_SRC "${DAEMON_SRC_DIR}/UnixDaemon.cpp") - if (NOT (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR APPLE)) +# compiler flags customization(by system) +if(UNIX) + list(APPEND DAEMON_SRC "${DAEMON_SRC_DIR}/UnixDaemon.cpp") + if(NOT(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR APPLE)) # "'sleep_for' is not a member of 'std::this_thread'" in gcc 4.7/4.8 - add_definitions( "-D_GLIBCXX_USE_NANOSLEEP=1" ) - endif () -elseif (WIN32 OR MSYS) - list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/DaemonWin32.cpp") - if (WITH_GUI) - list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32App.cpp") - set_source_files_properties("${CMAKE_SOURCE_DIR}/Win32/DaemonWin32.cpp" - PROPERTIES COMPILE_DEFINITIONS WIN32_APP) - endif () - list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32Service.cpp") - list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Resource.rc") -endif () - -if (WITH_AESNI) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes" ) -endif() - -if (WITH_AVX) - set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx" ) -endif() - -if (WITH_ADDRSANITIZER) - if (NOT MSVC) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer" ) - set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address" ) - else () - message( SEND_ERROR "MSVC does not support address sanitizer option") + add_definitions("-D_GLIBCXX_USE_NANOSLEEP=1") endif() endif() -if (WITH_THREADSANITIZER) - if (WITH_ADDRSANITIZER) - message( FATAL_ERROR "thread sanitizer option cannot be combined with address sanitizer") - elseif (NOT MSVC) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread" ) - set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread" ) - else () - message( SEND_ERROR "MSVC does not support address sanitizer option") +if(WITH_AESNI) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes") + add_definitions(-DAESNI) +endif() + +if(WITH_AVX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx") +endif() + +if(WITH_ADDRSANITIZER) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") +endif() + +if(WITH_THREADSANITIZER) + if(WITH_ADDRSANITIZER) + message(FATAL_ERROR "thread sanitizer option cannot be combined with address sanitizer") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread") endif() endif() @@ -253,141 +217,87 @@ endif() # TODO: once CMake 3.1+ becomes mainstream, see e.g. http://stackoverflow.com/a/29871891/673826 # use imported Threads::Threads instead set(THREADS_PREFER_PTHREAD_FLAG ON) -if (IOS) +if(IOS) set(CMAKE_THREAD_LIBS_INIT "-lpthread") set(CMAKE_HAVE_THREADS_LIBRARY 1) set(CMAKE_USE_WIN32_THREADS_INIT 0) set(CMAKE_USE_PTHREADS_INIT 1) else() - find_package ( Threads REQUIRED ) + find_package(Threads REQUIRED) endif() if(THREADS_HAVE_PTHREAD_ARG) # compile time flag set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") endif() -if (WITH_STATIC) +if(WITH_STATIC) set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_RUNTIME ON) - if (WIN32 AND NOT MSYS AND NOT MINGW) - # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace - foreach(flag_var - CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif(${flag_var} MATCHES "/MD") - endforeach(flag_var) - else () - set(CMAKE_FIND_LIBRARY_SUFFIXES .a) - endif () set(BUILD_SHARED_LIBS OFF) - if (${CMAKE_CXX_COMPILER} MATCHES ".*-openwrt-.*") - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread" ) - # set( CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,--whole-archive -lpthread -Wl,--no-whole-archive" ) - set( CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,-u,pthread_create,-u,pthread_once,-u,pthread_mutex_lock,-u,pthread_mutex_unlock,-u,pthread_join,-u,pthread_equal,-u,pthread_detach,-u,pthread_cond_wait,-u,pthread_cond_signal,-u,pthread_cond_destroy,-u,pthread_cond_broadcast,-u,pthread_cancel" ) - endif () + if(${CMAKE_CXX_COMPILER} MATCHES ".*-openwrt-.*") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") + # set(CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,--whole-archive -lpthread -Wl,--no-whole-archive") + set(CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,-u,pthread_create,-u,pthread_once,-u,pthread_mutex_lock,-u,pthread_mutex_unlock,-u,pthread_join,-u,pthread_equal,-u,pthread_detach,-u,pthread_cond_wait,-u,pthread_cond_signal,-u,pthread_cond_destroy,-u,pthread_cond_broadcast,-u,pthread_cancel") + endif() else() - if (NOT WIN32 AND NOT MSYS) - # TODO: Consider separate compilation for LIBI2PD_SRC for library. - # No need in -fPIC overhead for binary if not interested in library - # HINT: revert c266cff CMakeLists.txt: compilation speed up - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" ) - endif () + # TODO: Consider separate compilation for LIBI2PD_SRC for library. + # No need in -fPIC overhead for binary if not interested in library + # HINT: revert c266cff CMakeLists.txt: compilation speed up + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") add_definitions(-DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_DATE_TIME_DYN_LINK -DBOOST_REGEX_DYN_LINK) -endif () +endif() -if (WITH_PCH) +if(WITH_PCH) include_directories(BEFORE ${CMAKE_BINARY_DIR}) add_library(stdafx STATIC "${LIBI2PD_SRC_DIR}/stdafx.cpp") - if(MSVC) - target_compile_options(stdafx PRIVATE /Ycstdafx.h /Zm155) - add_custom_command(TARGET stdafx POST_BUILD - COMMAND xcopy /y stdafx.dir\\$\\*.pdb libi2pd.dir\\$\\ - COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pdclient.dir\\$\\ - COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pd.dir\\$\\ - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) - target_compile_options(libi2pd PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") - target_compile_options(libi2pdclient PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") - else() - string(TOUPPER ${CMAKE_BUILD_TYPE} BTU) - get_directory_property(DEFS DEFINITIONS) - string(REPLACE " " ";" FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BTU}} ${DEFS}") - add_custom_command(TARGET stdafx PRE_BUILD - COMMAND ${CMAKE_CXX_COMPILER} ${FLAGS} -c ${CMAKE_CURRENT_SOURCE_DIR}/../libi2pd/stdafx.h -o ${CMAKE_BINARY_DIR}/stdafx.h.gch - ) - target_compile_options(libi2pd PRIVATE -include libi2pd/stdafx.h) - target_compile_options(libi2pdclient PRIVATE -include libi2pd/stdafx.h) - endif() + string(TOUPPER ${CMAKE_BUILD_TYPE} BTU) + get_directory_property(DEFS DEFINITIONS) + string(REPLACE " " ";" FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BTU}} ${DEFS}") + add_custom_command(TARGET stdafx PRE_BUILD + COMMAND ${CMAKE_CXX_COMPILER} ${FLAGS} -c ${CMAKE_CURRENT_SOURCE_DIR}/../libi2pd/stdafx.h -o ${CMAKE_BINARY_DIR}/stdafx.h.gch + ) + target_compile_options(libi2pd PRIVATE -include libi2pd/stdafx.h) + target_compile_options(libi2pdclient PRIVATE -include libi2pd/stdafx.h) target_link_libraries(libi2pd stdafx) endif() target_link_libraries(libi2pdclient libi2pd) -find_package ( Boost COMPONENTS system filesystem program_options date_time REQUIRED ) +find_package(Boost COMPONENTS system filesystem program_options date_time REQUIRED) if(NOT DEFINED Boost_INCLUDE_DIRS) message(SEND_ERROR "Boost is not found, or your boost version was below 1.46. Please download Boost!") endif() -find_package ( OpenSSL REQUIRED ) +find_package(OpenSSL REQUIRED) if(NOT DEFINED OPENSSL_INCLUDE_DIR) message(SEND_ERROR "Could not find OpenSSL. Please download and install it first!") endif() -if (WITH_UPNP) - find_package ( MiniUPnPc REQUIRED ) - include_directories( SYSTEM ${MINIUPNPC_INCLUDE_DIR} ) +if(WITH_UPNP) + find_package(MiniUPnPc REQUIRED) + if(NOT MINIUPNPC_FOUND) + message(SEND_ERROR "Could not find MiniUPnPc. Please download and install it first!") + else() + include_directories(SYSTEM ${MINIUPNPC_INCLUDE_DIR}) + endif() endif() -find_package ( ZLIB ) -if (NOT ZLIB_FOUND ) - # We are probably on Windows - find_program( PATCH patch C:/Program Files/Git/usr/bin C:/msys64/usr/bin C:/msys32/usr/bin C:/Strawberry/c/bin ) - include( ExternalProject ) - if( CMAKE_SIZEOF_VOID_P EQUAL 8 ) - set( ZLIB_EXTRA -DAMD64=ON ) - else() - set( ZLIB_EXTRA -DASM686=ON "-DCMAKE_ASM_MASM_FLAGS=/W0 /safeseh" ) - endif() - ExternalProject_Add(zlib-project - URL https://zlib.net/zlib-1.2.11.tar.gz - URL_HASH SHA256=c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1 - PREFIX ${CMAKE_CURRENT_BINARY_DIR}/zlib - PATCH_COMMAND "${PATCH}" -p0 < ${CMAKE_CURRENT_SOURCE_DIR}/cmake-zlib-static.patch - && "${PATCH}" -p0 < ${CMAKE_CURRENT_SOURCE_DIR}/cmake-zlib-amd64.patch - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= - -DWITH_STATIC=${WITH_STATIC} ${ZLIB_EXTRA} - ) - if (WITH_PCH) - add_dependencies( stdafx zlib-project ) - else () - add_dependencies( libi2pd zlib-project ) - endif () - # ExternalProject_Get_Property(zlib-project install_dir) - set ( ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/zlib/include" CACHE FILEPATH "zlib include dir" FORCE) - if (NOT WITH_STATIC) - set ( ZLIB_LIBRARY debug zlibd optimized zlib CACHE STRING "zlib libraries" FORCE) - endif () - link_directories(${CMAKE_CURRENT_BINARY_DIR}/zlib/lib) -else() +find_package(ZLIB) +if(ZLIB_FOUND) link_directories(${ZLIB_ROOT}/lib) -endif () -if (WITH_STATIC AND (MSVC OR MSYS)) - set ( ZLIB_LIBRARY debug zlibstaticd optimized zlibstatic CACHE STRING "zlib libraries" FORCE) -endif () +endif() # load includes -include_directories( SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ) - +include_directories(SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}) # warn if for meshnet -if (WITH_MESHNET) +if(WITH_MESHNET) message(STATUS "Building for testnet") message(WARNING "This build will NOT work on mainline i2p") endif() -include(CheckAtomic) - +if(NOT MSYS) + include(CheckAtomic) +endif() # show summary message(STATUS "---------------------------------------") @@ -414,32 +324,24 @@ message(STATUS "---------------------------------------") #Handle paths nicely include(GNUInstallDirs) -if (WITH_BINARY) - add_executable ( "${PROJECT_NAME}" ${DAEMON_SRC} ) - if (WIN32 AND WITH_GUI) - set_target_properties("${PROJECT_NAME}" PROPERTIES WIN32_EXECUTABLE TRUE ) - endif() - if(NOT MSVC) - if (WITH_STATIC) - set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static" ) - endif () +if(WITH_BINARY) + add_executable("${PROJECT_NAME}" ${DAEMON_SRC}) + + if(WITH_STATIC) + set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static") endif() - if (WITH_PCH) - if (MSVC) - target_compile_options("${PROJECT_NAME}" PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") - else() - target_compile_options("${PROJECT_NAME}" PRIVATE -include libi2pd/stdafx.h) - endif() + if(WITH_PCH) + target_compile_options("${PROJECT_NAME}" PRIVATE -include libi2pd/stdafx.h) endif() - if (WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT MSYS AND NOT MINGW) - set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-z relro -z now" ) - endif () + if(WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-z relro -z now") + endif() - if (WITH_UPNP) - target_link_libraries("${PROJECT_NAME}" "${MINIUPNPC_LIBRARY}") - endif () + if(WITH_UPNP) + set(UPNP_LIB ${MINIUPNPC_LIBRARY}) + endif() # FindBoost pulls pthread for thread which is broken for static linking at least on Ubuntu 15.04 list(GET Boost_LIBRARIES -1 LAST_Boost_LIBRARIES) @@ -447,128 +349,15 @@ if (WITH_BINARY) list(REMOVE_AT Boost_LIBRARIES -1) endif() - if (MSYS OR MINGW) - set (MINGW_EXTRA -lws2_32 -lmswsock -liphlpapi ) - endif () - if (WITH_STATIC) + + if(WITH_STATIC) set(DL_LIB ${CMAKE_DL_LIBS}) endif() + target_link_libraries(libi2pd ${Boost_LIBRARIES} ${ZLIB_LIBRARY}) - target_link_libraries( "${PROJECT_NAME}" libi2pd libi2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) + target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${UPNP_LIB} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) - set (APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") - set (DIRS "${Boost_LIBRARY_DIR};${OPENSSL_INCLUDE_DIR}/../bin;${ZLIB_INCLUDE_DIR}/../bin;/mingw32/bin") - if (MSVC) - install(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} CONFIGURATIONS DEBUG RELWITHDEBINFO COMPONENT Symbols) - # TODO Somehow this picks lots of unrelevant stuff with MSYS. OS X testing needed. - INSTALL(CODE " - include(BundleUtilities) - fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\") - " COMPONENT Runtime) - endif () -endif () - -install(FILES ../LICENSE - DESTINATION . - COMPONENT Runtime - ) -# Take a copy on Appveyor -install(FILES "C:/projects/openssl-$ENV{OPENSSL}/LICENSE" - DESTINATION . - COMPONENT Runtime - RENAME LICENSE_OPENSSL - OPTIONAL # for local builds only! - ) - -file(GLOB_RECURSE I2PD_SOURCES "../libi2pd/*.cpp" "../libi2pd_client/*.cpp" "../daemon/*.cpp" "../build" "../Win32" "../Makefile*") -install(FILES ${I2PD_SOURCES} DESTINATION src/ COMPONENT Source) -# install(DIRECTORY ../ DESTINATION src/ -# # OPTIONAL -# COMPONENT Source FILES_MATCHING -# PATTERN .git EXCLUDE -# PATTERN "*.cpp" -# ) - -file(GLOB I2PD_HEADERS "../libi2pd/*.h" "../libi2pd_client/*.h" "../daemon/*.h") -install(FILES ${I2PD_HEADERS} DESTINATION src/ COMPONENT Headers) -# install(DIRECTORY ../ DESTINATION src/ -# # OPTIONAL -# COMPONENT Headers FILES_MATCHING -# PATTERN .git EXCLUDE -# PATTERN "*.h" -# ) - -set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Purple I2P, a C++ I2P daemon") -set(CPACK_PACKAGE_VENDOR "Purple I2P") -set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../README.md") -set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE") -file(READ ../libi2pd/version.h version_h) -string(REGEX REPLACE ".*I2PD_VERSION_MAJOR ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_MAJOR "${version_h}") -string(REGEX REPLACE ".*I2PD_VERSION_MINOR ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_MINOR "${version_h}") -string(REGEX REPLACE ".*I2PD_VERSION_MICRO ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_MICRO "${version_h}") -string(REGEX REPLACE ".*I2PD_VERSION_PATCH ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_PATCH "${version_h}") -set(CPACK_PACKAGE_INSTALL_DIRECTORY "Purple I2P")# ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}") -include(CPackComponent) -cpack_add_component(Runtime - DESCRIPTION "Main files" - REQUIRED INSTALL_TYPES minimal) -cpack_add_component(Symbols - DISPLAY_NAME "Debug symbols" - DESCRIPTION "Debug symbols for use with WinDbg or Visual Studio" - INSTALL_TYPES recommended full - ) -cpack_add_component(Libraries - DESCRIPTION "Binary libraries for development" - INSTALL_TYPES full dev3rd - ) -cpack_add_component(Source - DISPLAY_NAME "Source code" - DESCRIPTION "I2pd source code" - INSTALL_TYPES full - ) -cpack_add_component(Headers - DISPLAY_NAME "Header files" - DESCRIPTION "I2pd header files for development" - INSTALL_TYPES full dev3rd - ) -install(FILES ${MINIUPNPC_INCLUDE_DIR}/miniupnpc/miniupnpc.dll - DESTINATION bin - COMPONENT MiniUPnPc - OPTIONAL - ) -install(FILES ${MINIUPNPC_INCLUDE_DIR}/miniupnpc/LICENSE - DESTINATION . - COMPONENT MiniUPnPc - RENAME LICENSE_MINIUPNPC - OPTIONAL - ) -cpack_add_component(MiniUPnPc - INSTALL_TYPES full recommended - # DOWNLOADED - # ARCHIVE_FILE miniupnpc-win32.zip - ) -cpack_add_install_type(recommended DISPLAY_NAME Recommended) -cpack_add_install_type(dev3rd DISPLAY_NAME "Third party development") -cpack_add_install_type(full DISPLAY_NAME Full) -cpack_add_install_type(minimal DISPLAY_NAME Minimal) -if((WIN32 OR MSYS) AND NOT UNIX) - # There is a bug in NSI that does not handle full unix paths properly. Make - # sure there is at least one set of four (4) backlasshes. - set(CPACK_NSIS_DEFINES "RequestExecutionLevel user") - set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/../Win32\\\\mask.bmp") - set(CPACK_NSIS_INSTALLED_ICON_NAME "bin/i2pd.exe") - SET(CPACK_NSIS_DISPLAY_NAME "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") - set(CPACK_NSIS_HELP_LINK "https:\\\\\\\\github.com\\\\PurpleI2P\\\\i2pd\\\\issues") - set(CPACK_NSIS_URL_INFO_ABOUT "https:\\\\\\\\github.com\\\\PurpleI2P\\\\i2pd") - set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Install i2pd as windows service.lnk' '$INSTDIR\\\\bin\\\\i2pd.exe' '--service=install' -CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Remove i2pd windows service.lnk' '$INSTDIR\\\\bin\\\\i2pd.exe' '--service=remove'") - set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\Install i2pd as windows service.lnk' -Delete '$SMPROGRAMS\\\\$START_MENU\\\\Remove i2pd windows service.lnk'") -else() - set(CPACK_STRIP_FILES "bin/i2pd") - set(CPACK_SOURCE_STRIP_FILES "") + set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") + set(DIRS "${Boost_LIBRARY_DIR};${OPENSSL_INCLUDE_DIR}/../bin;${ZLIB_INCLUDE_DIR}/../bin;/mingw32/bin") endif() -set(CPACK_PACKAGE_EXECUTABLES "i2pd" "C++ I2P daemon") -set(CPACK_SOURCE_GENERATOR "TGZ") -include(CPack) diff --git a/build/cmake-zlib-amd64.patch b/build/cmake-zlib-amd64.patch deleted file mode 100644 index 7ea1d9fe..00000000 --- a/build/cmake-zlib-amd64.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- CMakeLists.txt.orig 2015-12-07 14:19:36.447689600 -0600 -+++ CMakeLists.txt 2015-12-07 14:18:23.004419900 -0600 -@@ -165,6 +165,7 @@ - ENABLE_LANGUAGE(ASM_MASM) - set(ZLIB_ASMS - contrib/masmx64/gvmat64.asm -+ contrib/masmx64/inffas8664.c - contrib/masmx64/inffasx64.asm - ) - endif() diff --git a/build/cmake-zlib-static.patch b/build/cmake-zlib-static.patch deleted file mode 100644 index 68f1400e..00000000 --- a/build/cmake-zlib-static.patch +++ /dev/null @@ -1,28 +0,0 @@ ---- CMakeLists.txt.orig 2013-04-28 17:57:10.000000000 -0500 -+++ CMakeLists.txt 2015-12-03 12:53:52.371087900 -0600 -@@ -7,6 +7,7 @@ - - option(ASM686 "Enable building i686 assembly implementation") - option(AMD64 "Enable building amd64 assembly implementation") -+option(WITH_STATIC "Static runtime on Windows" OFF) - - set(INSTALL_BIN_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Installation directory for executables") - set(INSTALL_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Installation directory for libraries") -@@ -66,6 +67,17 @@ - include_directories(${CMAKE_CURRENT_SOURCE_DIR}) - endif() - -+if(WITH_STATIC AND (MSVC OR MSYS)) -+ # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace -+ foreach(flag_var -+ CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE -+ CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) -+ if(${flag_var} MATCHES "/MD") -+ string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") -+ endif(${flag_var} MATCHES "/MD") -+ endforeach(flag_var) -+endif() -+ - if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) - # If we're doing an out of source build and the user has a zconf.h - # in their source tree... From bdd75e11714d0d15f2d261f86b62e50558073dc7 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 20 May 2020 14:59:18 -0400 Subject: [PATCH 3698/6300] build client tunnels through router with version >= 0.9.36 --- libi2pd/NetDb.cpp | 3 ++- libi2pd/NetDb.hpp | 2 ++ libi2pd/RouterInfo.cpp | 20 ++++++++++++++++++-- libi2pd/RouterInfo.h | 3 +++ libi2pd/version.h | 2 ++ 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 3466cf2b..86bf160e 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1121,7 +1121,8 @@ namespace data { return !router->IsHidden () && router != compatibleWith && router->IsCompatible (*compatibleWith) && - (router->GetCaps () & RouterInfo::eHighBandwidth); + (router->GetCaps () & RouterInfo::eHighBandwidth) && + router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION; }); } diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index d9e10504..6e251ecb 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -21,6 +21,7 @@ #include "Reseed.h" #include "NetDbRequests.h" #include "Family.h" +#include "version.h" namespace i2p { @@ -32,6 +33,7 @@ namespace data 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_PUBLISH_INTERVAL = 60*40; + const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0,9,36); // 0.9.36 /** function for visiting a leaseset stored in a floodfill */ typedef std::function)> LeaseSetVisitor; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 37c2af7e..9b3b7e89 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -27,7 +27,7 @@ namespace data RouterInfo::RouterInfo (const std::string& fullPath): m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false), - m_SupportedTransports (0), m_Caps (0) + m_SupportedTransports (0), m_Caps (0), m_Version (0) { m_Addresses = boost::make_shared(); // create empty list m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; @@ -35,7 +35,8 @@ namespace data } 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_Version (0) { m_Addresses = boost::make_shared(); // create empty list if (len <= MAX_RI_BUFFER_SIZE) @@ -340,6 +341,21 @@ namespace data // extract caps if (!strcmp (key, "caps")) ExtractCaps (value); + // extract version + else if (!strcmp (key, ROUTER_INFO_PROPERTY_VERSION)) + { + m_Version = 0; + char * ch = value; + while (*ch) + { + if (*ch >= '0' && *ch <= '9') + { + m_Version *= 10; + m_Version += (*ch - '0'); + } + ch++; + } + } // check netId else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != i2p::context.GetNetID ()) { diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index b23625e0..f8598b1e 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -19,6 +19,7 @@ namespace data const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets"; const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters"; const char ROUTER_INFO_PROPERTY_NETID[] = "netId"; + const char ROUTER_INFO_PROPERTY_VERSION[] = "router.version"; const char ROUTER_INFO_PROPERTY_FAMILY[] = "family"; const char ROUTER_INFO_PROPERTY_FAMILY_SIG[] = "family.sig"; @@ -142,6 +143,7 @@ namespace data void SetRouterIdentity (std::shared_ptr identity); std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; uint64_t GetTimestamp () const { return m_Timestamp; }; + int GetVersion () const { return m_Version; }; Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr std::shared_ptr GetNTCPAddress (bool v4only = true) const; std::shared_ptr GetNTCP2Address (bool publishedOnly, bool v4only = true) const; @@ -235,6 +237,7 @@ namespace data std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; uint8_t m_SupportedTransports, m_Caps; + int m_Version; mutable std::shared_ptr m_Profile; }; } diff --git a/libi2pd/version.h b/libi2pd/version.h index 4ad1a546..fa8f9a0f 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -5,6 +5,7 @@ #define STRINGIZE(x) #x #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) +#define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 #define I2PD_VERSION_MINOR 31 @@ -24,5 +25,6 @@ #define I2P_VERSION_MICRO 45 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) +#define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #endif From 9318388007cff0495b4b360d0480f4fc1219a9dc Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 20 May 2020 22:30:02 +0300 Subject: [PATCH 3699/6300] [apparmor] add one more resolv.conf path (reported by user with ubuntu 18.04) Signed-off-by: R4SAS --- contrib/apparmor/usr.sbin.i2pd | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/apparmor/usr.sbin.i2pd b/contrib/apparmor/usr.sbin.i2pd index afa59563..6cc80609 100644 --- a/contrib/apparmor/usr.sbin.i2pd +++ b/contrib/apparmor/usr.sbin.i2pd @@ -19,6 +19,7 @@ /etc/nsswitch.conf r, /etc/resolv.conf r, /run/resolvconf/resolv.conf r, + /run/systemd/resolve/resolv.conf r, /run/systemd/resolve/stub-resolv.conf r, # path specific (feel free to modify if you have another paths) From e5901dad9177dc1740149d164143b1230fe78854 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 21 May 2020 14:52:44 -0400 Subject: [PATCH 3700/6300] resend not more than half of window --- libi2pd/Streaming.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index ad3f6209..e2047b0d 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -824,6 +824,8 @@ namespace stream } // collect packets to resend + int maxNumPackets = (m_WindowSize >> 1); // /2 + if (maxNumPackets < WINDOW_SIZE) maxNumPackets = WINDOW_SIZE; auto ts = i2p::util::GetMillisecondsSinceEpoch (); std::vector packets; for (auto it : m_SentPackets) @@ -832,6 +834,8 @@ namespace stream { it->sendTime = ts; packets.push_back (it); + maxNumPackets--; + if (maxNumPackets <= 0) break; } } @@ -843,7 +847,7 @@ namespace stream switch (m_NumResendAttempts) { case 1: // congesion avoidance - m_WindowSize /= 2; + m_WindowSize >>= 1; // /2 if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; break; case 2: From 153aaa6d21459d50fbab54c9c553e6322a489ad1 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 21 May 2020 15:33:12 -0400 Subject: [PATCH 3701/6300] no compression for RouterInfo gzip --- libi2pd/I2NPProtocol.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index a16ce580..3a139792 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -249,9 +249,7 @@ namespace i2p uint8_t * sizePtr = buf; buf += 2; m->len += (buf - payload); // payload size - i2p::data::GzipDeflator deflator; - deflator.SetCompressionLevel (Z_BEST_COMPRESSION); - size_t size = deflator.Deflate (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); + size_t size = i2p::data::GzipNoCompression (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); if (size) { htobe16buf (sizePtr, size); // size From a6c9ee446a3a89a45f741037256c88e56613f378 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 21 May 2020 15:36:16 -0400 Subject: [PATCH 3702/6300] LeaseSet and encryption type for http and socks proxy --- libi2pd_client/ClientContext.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 765d3dec..51f10f5e 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -468,6 +468,10 @@ namespace client options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_MAX_TUNNEL_LATENCY, value)) options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = value; + if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_TYPE, value)) + options[I2CP_PARAM_LEASESET_TYPE] = value; + if (i2p::config::GetOption(prefix + I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, value)) + options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = value; } void ClientContext::ReadTunnels () From f133a7f9fd3b70a9d23c6fc83cda772d11d5702a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 21 May 2020 18:58:28 -0400 Subject: [PATCH 3703/6300] resend outstading packets again --- libi2pd/Streaming.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index e2047b0d..0af7d902 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -408,11 +408,14 @@ namespace stream else break; } - if (m_SentPackets.empty ()) - m_ResendTimer.cancel (); if (acknowledged) { + bool isPreviousResend = m_NumResendAttempts > 0; m_NumResendAttempts = 0; + if (m_SentPackets.empty ()) + m_ResendTimer.cancel (); + else if (isPreviousResend) // resend outstanding + HandleResendTimer (boost::system::error_code ()); // no error SendBuffer (); } if (m_Status == eStreamStatusClosed) From 0c2b0081b5cc4ffbedd09bed70a7141f43ca6312 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 21 May 2020 19:38:25 -0400 Subject: [PATCH 3704/6300] rollback --- libi2pd/Streaming.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 0af7d902..00ee35b0 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -408,14 +408,11 @@ namespace stream else break; } + if (m_SentPackets.empty ()) + m_ResendTimer.cancel (); if (acknowledged) { - bool isPreviousResend = m_NumResendAttempts > 0; m_NumResendAttempts = 0; - if (m_SentPackets.empty ()) - m_ResendTimer.cancel (); - else if (isPreviousResend) // resend outstanding - HandleResendTimer (boost::system::error_code ()); // no error SendBuffer (); } if (m_Status == eStreamStatusClosed) @@ -827,8 +824,6 @@ namespace stream } // collect packets to resend - int maxNumPackets = (m_WindowSize >> 1); // /2 - if (maxNumPackets < WINDOW_SIZE) maxNumPackets = WINDOW_SIZE; auto ts = i2p::util::GetMillisecondsSinceEpoch (); std::vector packets; for (auto it : m_SentPackets) @@ -837,8 +832,6 @@ namespace stream { it->sendTime = ts; packets.push_back (it); - maxNumPackets--; - if (maxNumPackets <= 0) break; } } From 46ee427ee33c8a727a9bb838b5d5961145d917c3 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 21 May 2020 21:54:00 -0400 Subject: [PATCH 3705/6300] common header for repliable datagrams --- libi2pd/Datagram.cpp | 23 +++++++++-------------- libi2pd/Datagram.h | 2 ++ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 08cd86bf..6441b5e2 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -1,5 +1,4 @@ #include -#include #include "Crypto.h" #include "Log.h" #include "TunnelBase.h" @@ -14,6 +13,10 @@ namespace datagram DatagramDestination::DatagramDestination (std::shared_ptr owner, bool gzip): m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr), m_Gzip (gzip) { + auto identityLen = m_Owner->GetIdentity ()->GetFullLen (); + m_From.resize (identityLen); + m_Owner->GetIdentity ()->ToBuffer (m_From.data (), identityLen); + m_Signature.resize (m_Owner->GetIdentity ()->GetSignatureLen ()); } DatagramDestination::~DatagramDestination () @@ -23,26 +26,18 @@ namespace datagram void DatagramDestination::SendDatagramTo(const uint8_t * payload, size_t len, const i2p::data::IdentHash & identity, uint16_t fromPort, uint16_t toPort) { - auto owner = m_Owner; - auto localIdentity = m_Owner->GetIdentity (); - auto identityLen = localIdentity->GetFullLen (); - auto signatureLen = localIdentity->GetSignatureLen (); - size_t headerLen = identityLen + signatureLen; - - std::vector header(headerLen); - localIdentity->ToBuffer (header.data (), identityLen); - uint8_t * signature = header.data () + identityLen; - if (localIdentity->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) + if (m_Owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) { uint8_t hash[32]; SHA256(payload, len, hash); - owner->Sign (hash, 32, signature); + m_Owner->Sign (hash, 32, m_Signature.data ()); } else - owner->Sign (payload, len, signature); + m_Owner->Sign (payload, len, m_Signature.data ()); auto session = ObtainSession(identity); - auto msg = CreateDataMessage ({{header.data (), headerLen}, {payload, len}}, fromPort, toPort, false, !session->IsRatchets ()); // datagram + auto msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {m_Signature.data (), m_Signature.size ()}, {payload, len}}, + fromPort, toPort, false, !session->IsRatchets ()); // datagram session->SendMsg(msg); } diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index 5fcaec3a..ea2a4e13 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "Base.h" #include "Identity.h" #include "LeaseSet.h" @@ -154,6 +155,7 @@ namespace datagram i2p::data::GzipInflator m_Inflator; i2p::data::GzipDeflator m_Deflator; + std::vector m_From, m_Signature; }; } } From 78640532e1c0e50b122df45702e9861e6402d2af Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 22 May 2020 13:01:25 +0000 Subject: [PATCH 3706/6300] [appveyor] add build fix (#1520) Add fix due to msys2/MSYS2-packages#1967 --- appveyor.yml | 33 +++++++++++-------------------- build/appveyor-msys2-upgrade.bash | 10 ++++++++++ 2 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 build/appveyor-msys2-upgrade.bash diff --git a/appveyor.yml b/appveyor.yml index d6dcf060..45a16dd8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,34 +14,25 @@ environment: CHERE_INVOKING: enabled_from_arguments matrix: - MSYSTEM: MINGW64 + MSYS_PACKAGES: mingw-w64-x86_64-boost mingw-w64-x86_64-miniupnpc + MSYS_BITNESS: 64 - MSYSTEM: MINGW32 + MSYS_PACKAGES: mingw-w64-i686-boost mingw-w64-i686-miniupnpc + MSYS_BITNESS: 32 install: - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" +# TODO: revert that change when appveyor's images will be updated +- c:\msys64\usr\bin\bash -l "build/appveyor-msys2-upgrade.bash" +# update runtime - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" - -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" - -- if "%MSYSTEM%" == "MINGW64" ( - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-x86_64-boost mingw-w64-x86_64-miniupnpc" - ) else ( - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-i686-boost mingw-w64-i686-miniupnpc" - ) - -- if "%MSYSTEM%" == "MINGW64" ( - set "bitness=64" - ) else ( - set "bitness=32" - ) +# update packages and install required +- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu ${MSYS_PACKAGES}" build_script: -- cmd: >- - cd \projects\i2pd - - echo MSYSTEM = %MSYSTEM%, bitness = %bitness% - -- c:\msys64\usr\bin\bash -lc "make USE_UPNP=yes -j2" -- 7z a -tzip -mx9 -mmt i2pd-mingw-win%bitness%.zip i2pd.exe +- echo MSYSTEM = %MSYSTEM%, bitness = %MSYS_BITNESS% +- c:\msys64\usr\bin\bash -lc "make USE_UPNP=yes -j3" +- 7z a -tzip -mx9 -mmt i2pd-mingw-win%MSYS_BITNESS%.zip i2pd.exe test: off diff --git a/build/appveyor-msys2-upgrade.bash b/build/appveyor-msys2-upgrade.bash new file mode 100644 index 00000000..20c6af69 --- /dev/null +++ b/build/appveyor-msys2-upgrade.bash @@ -0,0 +1,10 @@ +set -e -x + +base_url='http://repo.msys2.org/msys/x86_64/' +packages="libzstd-1.4.4-2-x86_64.pkg.tar.xz pacman-5.2.1-6-x86_64.pkg.tar.xz zstd-1.4.4-2-x86_64.pkg.tar.xz" +for p in $packages +do + curl "${base_url}$p" -o "$p" +done +pacman -U --noconfirm $packages +rm -f $packages From 9633c247f0ebf4c0613fea92b4a1b59795f6f2c4 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 22 May 2020 19:34:42 +0300 Subject: [PATCH 3707/6300] [readme] update docker badges --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 25e6c3e5..ec7548f0 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Snapcraft release](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd) [![License](https://img.shields.io/github/license/PurpleI2P/i2pd.svg)](https://github.com/PurpleI2P/i2pd/blob/openssl/LICENSE) [![Packaging status](https://repology.org/badge/tiny-repos/i2pd.svg)](https://repology.org/project/i2pd/versions) +[![Docker Pulls](https://img.shields.io/docker/pulls/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd) i2pd ==== @@ -68,7 +69,7 @@ Build instructions: * Alpine, ArchLinux, openSUSE, Gentoo, Debian, Ubuntu, etc. * Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) * Mac OS X - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) -* Docker image - [![Build Status](https://dockerbuildbadges.quelltext.eu/status.svg?organization=meeh&repository=i2pd)](https://hub.docker.com/r/meeh/i2pd/builds/) +* Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) * Snap - [![Snap Status](https://build.snapcraft.io/badge/PurpleI2P/i2pd-snap.svg)](https://build.snapcraft.io/user/PurpleI2P/i2pd-snap) * FreeBSD * Android From 7a5146ea7455136d507843c2180a3761ab401bc7 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 1 Mar 2020 13:25:50 +0300 Subject: [PATCH 3708/6300] fix code syle(spaces->tabs, tabulations) Signed-off-by: R4SAS --- Win32/DaemonWin32.cpp | 4 +- Win32/Win32App.cpp | 910 +++++++++++----------- Win32/Win32App.h | 34 +- Win32/Win32Service.h | 70 +- Win32/resource.h | 22 +- daemon/Daemon.cpp | 45 +- daemon/Daemon.h | 14 +- daemon/HTTPServer.cpp | 4 +- daemon/HTTPServer.h | 22 +- daemon/I2PControl.cpp | 49 +- daemon/I2PControl.h | 1 + daemon/UPnP.cpp | 44 +- daemon/UPnP.h | 60 +- daemon/UnixDaemon.cpp | 1 - daemon/i2pd.cpp | 3 +- libi2pd/Base.cpp | 214 ++--- libi2pd/Base.h | 12 +- libi2pd/Blinding.cpp | 79 +- libi2pd/Blinding.h | 8 +- libi2pd/CPU.h | 6 +- libi2pd/ChaCha20.cpp | 115 ++- libi2pd/ChaCha20.h | 30 +- libi2pd/Config.cpp | 2 +- libi2pd/Config.h | 169 ++-- libi2pd/Crypto.cpp | 118 ++- libi2pd/Crypto.h | 30 +- libi2pd/CryptoKey.cpp | 7 +- libi2pd/CryptoKey.h | 13 +- libi2pd/CryptoWorker.h | 1 - libi2pd/Datagram.cpp | 32 +- libi2pd/Datagram.h | 97 +-- libi2pd/Destination.cpp | 119 ++- libi2pd/Destination.h | 31 +- libi2pd/ECIESX25519AEADRatchetSession.cpp | 752 +++++++++--------- libi2pd/ECIESX25519AEADRatchetSession.h | 164 ++-- libi2pd/Ed25519.cpp | 43 +- libi2pd/Ed25519.h | 14 +- libi2pd/Elligator.cpp | 87 +-- libi2pd/Elligator.h | 6 +- libi2pd/FS.h | 221 +++--- libi2pd/Family.cpp | 1 - libi2pd/Garlic.cpp | 188 ++--- libi2pd/Garlic.h | 106 +-- libi2pd/Gost.h | 8 +- libi2pd/Gzip.cpp | 26 +- libi2pd/Gzip.h | 5 +- libi2pd/I2NPProtocol.cpp | 28 +- libi2pd/I2NPProtocol.h | 6 +- libi2pd/Identity.cpp | 50 +- libi2pd/Identity.h | 13 +- libi2pd/LeaseSet.cpp | 224 +++--- libi2pd/LeaseSet.h | 39 +- libi2pd/LittleBigEndian.h | 324 ++++---- libi2pd/Log.cpp | 24 +- libi2pd/Log.h | 6 +- libi2pd/NTCP2.cpp | 20 +- libi2pd/NTCP2.h | 1 - libi2pd/NTCPSession.cpp | 15 +- libi2pd/NetDb.cpp | 28 +- libi2pd/NetDb.hpp | 37 +- libi2pd/NetDbRequests.cpp | 5 +- libi2pd/NetDbRequests.h | 1 - libi2pd/Poly1305.cpp | 3 +- libi2pd/Poly1305.h | 15 +- libi2pd/Reseed.cpp | 120 +-- libi2pd/Reseed.h | 2 +- libi2pd/RouterContext.cpp | 8 +- libi2pd/RouterContext.h | 14 +- libi2pd/RouterInfo.cpp | 54 +- libi2pd/RouterInfo.h | 4 +- libi2pd/SSU.cpp | 23 +- libi2pd/SSU.h | 3 +- libi2pd/SSUData.cpp | 3 +- libi2pd/SSUData.h | 1 - libi2pd/SSUSession.cpp | 5 +- libi2pd/SSUSession.h | 9 +- libi2pd/Signature.cpp | 52 +- libi2pd/Signature.h | 61 +- libi2pd/Siphash.h | 238 +++--- libi2pd/Streaming.cpp | 54 +- libi2pd/Tag.h | 142 ++-- libi2pd/Timestamp.cpp | 13 +- libi2pd/Timestamp.h | 13 +- libi2pd/TransitTunnel.cpp | 4 +- libi2pd/TransportSession.h | 8 +- libi2pd/Transports.cpp | 14 +- libi2pd/Transports.h | 20 +- libi2pd/TunnelEndpoint.cpp | 3 +- libi2pd/TunnelGateway.cpp | 5 +- libi2pd/TunnelPool.cpp | 12 +- libi2pd/TunnelPool.h | 24 +- libi2pd/api.cpp | 5 +- libi2pd/api.h | 1 - libi2pd/util.cpp | 51 +- libi2pd/util.h | 16 +- libi2pd_client/AddressBook.cpp | 62 +- libi2pd_client/AddressBook.h | 6 +- libi2pd_client/BOB.cpp | 50 +- libi2pd_client/BOB.h | 27 +- libi2pd_client/ClientContext.cpp | 64 +- libi2pd_client/ClientContext.h | 10 +- libi2pd_client/HTTPProxy.cpp | 12 +- libi2pd_client/HTTPProxy.h | 3 + libi2pd_client/I2CP.cpp | 53 +- libi2pd_client/I2CP.h | 9 +- libi2pd_client/I2PService.cpp | 22 +- libi2pd_client/I2PService.h | 26 +- libi2pd_client/I2PTunnel.cpp | 12 +- libi2pd_client/I2PTunnel.h | 36 +- libi2pd_client/MatchedDestination.cpp | 8 +- libi2pd_client/MatchedDestination.h | 32 +- libi2pd_client/SAM.cpp | 99 ++- libi2pd_client/SAM.h | 17 +- libi2pd_client/SOCKS.cpp | 9 +- libi2pd_client/SOCKS.h | 2 + 115 files changed, 3206 insertions(+), 3161 deletions(-) diff --git a/Win32/DaemonWin32.cpp b/Win32/DaemonWin32.cpp index 5524aff3..7ba3ffc3 100644 --- a/Win32/DaemonWin32.cpp +++ b/Win32/DaemonWin32.cpp @@ -27,8 +27,8 @@ namespace util i2p::log::SetThrowFunction ([](const std::string& s) { MessageBox(0, TEXT(s.c_str ()), TEXT("i2pd"), MB_ICONERROR | MB_TASKMODAL | MB_OK ); - }); - + }); + if (!Daemon_Singleton::init(argc, argv)) return false; diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 7ae4e419..3bcb24b3 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -1,455 +1,455 @@ -#include -#include -#include -#include -#include "ClientContext.h" -#include "Config.h" -#include "NetDb.hpp" -#include "RouterContext.h" -#include "Transports.h" -#include "Tunnel.h" -#include "version.h" -#include "resource.h" -#include "Daemon.h" -#include "Win32App.h" -#include "Win32NetState.h" - -#define ID_ABOUT 2000 -#define ID_EXIT 2001 -#define ID_CONSOLE 2002 -#define ID_APP 2003 -#define ID_GRACEFUL_SHUTDOWN 2004 -#define ID_STOP_GRACEFUL_SHUTDOWN 2005 -#define ID_RELOAD 2006 -#define ID_ACCEPT_TRANSIT 2007 -#define ID_DECLINE_TRANSIT 2008 - -#define ID_TRAY_ICON 2050 -#define WM_TRAYICON (WM_USER + 1) - -#define IDT_GRACEFUL_SHUTDOWN_TIMER 2100 -#define FRAME_UPDATE_TIMER 2101 -#define IDT_GRACEFUL_TUNNELCHECK_TIMER 2102 - -namespace i2p -{ -namespace win32 -{ - static DWORD GracefulShutdownEndtime = 0; - - typedef DWORD (* IPN)(); - IPN GetTickCountLocal = (IPN)GetProcAddress (GetModuleHandle ("KERNEL32.dll"), "GetTickCount"); - - static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) - { - HMENU hPopup = CreatePopupMenu(); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_CONSOLE, "Open &console"); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "Show app"); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); - if(!i2p::context.AcceptsTunnels()) - InsertMenu (hPopup, -1, - i2p::util::DaemonWin32::Instance ().isGraceful ? MF_BYPOSITION | MF_STRING | MF_GRAYED : MF_BYPOSITION | MF_STRING, - ID_ACCEPT_TRANSIT, "Accept &transit"); - else - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DECLINE_TRANSIT, "Decline &transit"); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_RELOAD, "&Reload tunnels config"); - if (!i2p::util::DaemonWin32::Instance ().isGraceful) - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_GRACEFUL_SHUTDOWN, "&Graceful shutdown"); - else - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_STOP_GRACEFUL_SHUTDOWN, "Stop &graceful shutdown"); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_EXIT, "E&xit"); - SetMenuDefaultItem (hPopup, ID_CONSOLE, FALSE); - SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); - - POINT p; - if (!curpos) - { - GetCursorPos (&p); - curpos = &p; - } - - WORD cmd = TrackPopupMenu (hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, curpos->x, curpos->y, 0, hWnd, NULL); - SendMessage (hWnd, WM_COMMAND, cmd, 0); - - DestroyMenu(hPopup); - } - - static void AddTrayIcon (HWND hWnd) - { - NOTIFYICONDATA nid; - memset(&nid, 0, sizeof(nid)); - nid.cbSize = sizeof(nid); - nid.hWnd = hWnd; - nid.uID = ID_TRAY_ICON; - nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; - nid.uCallbackMessage = WM_TRAYICON; - nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (MAINICON)); - strcpy (nid.szTip, "i2pd"); - strcpy (nid.szInfo, "i2pd is starting"); - Shell_NotifyIcon(NIM_ADD, &nid ); - } - - static void RemoveTrayIcon (HWND hWnd) - { - NOTIFYICONDATA nid; - nid.hWnd = hWnd; - nid.uID = ID_TRAY_ICON; - Shell_NotifyIcon (NIM_DELETE, &nid); - } - - static void ShowUptime (std::stringstream& s, int seconds) - { - int num; - - if ((num = seconds / 86400) > 0) { - s << num << " days, "; - seconds -= num * 86400; - } - if ((num = seconds / 3600) > 0) { - s << num << " hours, "; - seconds -= num * 3600; - } - if ((num = seconds / 60) > 0) { - s << num << " min, "; - seconds -= num * 60; - } - s << seconds << " seconds\n"; - } - - template static void ShowTransfered (std::stringstream& s, size transfer) - { - auto bytes = transfer & 0x03ff; - transfer >>= 10; - auto kbytes = transfer & 0x03ff; - transfer >>= 10; - auto mbytes = transfer & 0x03ff; - transfer >>= 10; - auto gbytes = transfer & 0x03ff; - - if (gbytes) - s << gbytes << " GB, "; - if (mbytes) - s << mbytes << " MB, "; - if (kbytes) - s << kbytes << " KB, "; - s << bytes << " Bytes\n"; - } - - static void PrintMainWindowText (std::stringstream& s) - { - s << "\n"; - s << "Status: "; - switch (i2p::context.GetStatus()) - { - case eRouterStatusOK: s << "OK"; break; - case eRouterStatusTesting: s << "Testing"; break; - case eRouterStatusFirewalled: s << "Firewalled"; break; - case eRouterStatusError: - { - switch (i2p::context.GetError()) - { - case eRouterErrorClockSkew: s << "Clock skew"; break; - default: s << "Error"; - } - break; - } - default: s << "Unknown"; - } - s << "; "; - s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; - s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); - if (GracefulShutdownEndtime != 0) - { - DWORD GracefulTimeLeft = (GracefulShutdownEndtime - GetTickCountLocal()) / 1000; - s << "Graceful shutdown, time left: "; ShowUptime(s, GracefulTimeLeft); - } - else - s << "\n"; - s << "Inbound: " << i2p::transport::transports.GetInBandwidth() / 1024 << " KiB/s; "; - s << "Outbound: " << i2p::transport::transports.GetOutBandwidth() / 1024 << " KiB/s\n"; - s << "Received: "; ShowTransfered (s, i2p::transport::transports.GetTotalReceivedBytes()); - s << "Sent: "; ShowTransfered (s, i2p::transport::transports.GetTotalSentBytes()); - s << "\n"; - s << "Routers: " << i2p::data::netdb.GetNumRouters () << "; "; - s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << "; "; - s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "\n"; - s << "Tunnels: "; - s << "In: " << i2p::tunnel::tunnels.CountInboundTunnels() << "; "; - s << "Out: " << i2p::tunnel::tunnels.CountOutboundTunnels() << "; "; - s << "Transit: " << i2p::tunnel::tunnels.CountTransitTunnels() << "\n"; - s << "\n"; - } - - static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) - { - static UINT s_uTaskbarRestart; - - switch (uMsg) - { - case WM_CREATE: - { - s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); - AddTrayIcon (hWnd); - break; - } - case WM_CLOSE: - { - RemoveTrayIcon (hWnd); - KillTimer (hWnd, FRAME_UPDATE_TIMER); - KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); - KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); - PostQuitMessage (0); - break; - } - case WM_COMMAND: - { - switch (LOWORD(wParam)) - { - case ID_ABOUT: - { - std::stringstream text; - text << "Version: " << I2PD_VERSION << " " << CODENAME; - MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); - return 0; - } - case ID_EXIT: - { - PostMessage (hWnd, WM_CLOSE, 0, 0); - return 0; - } - case ID_ACCEPT_TRANSIT: - { - i2p::context.SetAcceptsTunnels (true); - std::stringstream text; - text << "I2Pd now accept transit tunnels"; - MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); - return 0; - } - case ID_DECLINE_TRANSIT: - { - i2p::context.SetAcceptsTunnels (false); - std::stringstream text; - text << "I2Pd now decline new transit tunnels"; - MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); - return 0; - } - case ID_GRACEFUL_SHUTDOWN: - { - i2p::context.SetAcceptsTunnels (false); - SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes - SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); // check tunnels every second - GracefulShutdownEndtime = GetTickCountLocal() + 10*60*1000; - i2p::util::DaemonWin32::Instance ().isGraceful = true; - return 0; - } - case ID_STOP_GRACEFUL_SHUTDOWN: - { - i2p::context.SetAcceptsTunnels (true); - KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); - KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); - GracefulShutdownEndtime = 0; - i2p::util::DaemonWin32::Instance ().isGraceful = false; - return 0; - } - case ID_RELOAD: - { - i2p::client::context.ReloadConfig(); - std::stringstream text; - text << "I2Pd reloading configs..."; - MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); - return 0; - } - case ID_CONSOLE: - { - char buf[30]; - std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); - uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); - snprintf(buf, 30, "http://%s:%d", httpAddr.c_str(), httpPort); - ShellExecute(NULL, "open", buf, NULL, NULL, SW_SHOWNORMAL); - return 0; - } - case ID_APP: - { - ShowWindow(hWnd, SW_SHOW); - SetTimer(hWnd, FRAME_UPDATE_TIMER, 3000, NULL); - return 0; - } - } - break; - } - case WM_SYSCOMMAND: - { - switch (wParam) - { - case SC_MINIMIZE: - { - ShowWindow(hWnd, SW_HIDE); - KillTimer (hWnd, FRAME_UPDATE_TIMER); - return 0; - } - case SC_CLOSE: - { - std::string close; i2p::config::GetOption("close", close); - if (0 == close.compare("ask")) - switch(::MessageBox(hWnd, "Would you like to minimize instead of exiting?" - " You can add 'close' configuration option. Valid values are: ask, minimize, exit.", - "Minimize instead of exiting?", MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON1)) - { - case IDYES: close = "minimize"; break; - case IDNO: close = "exit"; break; - default: return 0; - } - if (0 == close.compare("minimize")) - { - ShowWindow(hWnd, SW_HIDE); - KillTimer (hWnd, FRAME_UPDATE_TIMER); - return 0; - } - if (0 != close.compare("exit")) - { - ::MessageBox(hWnd, close.c_str(), "Unknown close action in config", MB_OK | MB_ICONWARNING); - return 0; - } - } - } - } - case WM_TRAYICON: - { - switch (lParam) - { - case WM_LBUTTONUP: - case WM_RBUTTONUP: - { - SetForegroundWindow (hWnd); - ShowPopupMenu(hWnd, NULL, -1); - PostMessage (hWnd, WM_APP + 1, 0, 0); - break; - } - } - break; - } - case WM_TIMER: - { - switch(wParam) - { - case IDT_GRACEFUL_SHUTDOWN_TIMER: - { - GracefulShutdownEndtime = 0; - PostMessage (hWnd, WM_CLOSE, 0, 0); // exit - return 0; - } - case IDT_GRACEFUL_TUNNELCHECK_TIMER: - { - if (i2p::tunnel::tunnels.CountTransitTunnels() == 0) - PostMessage (hWnd, WM_CLOSE, 0, 0); - else - SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); - return 0; - } - case FRAME_UPDATE_TIMER: - { - InvalidateRect(hWnd, NULL, TRUE); - return 0; - } - } - break; - } - case WM_PAINT: - { - HDC hDC; - PAINTSTRUCT ps; - RECT rp; - HFONT hFont; - std::stringstream s; PrintMainWindowText (s); - hDC = BeginPaint (hWnd, &ps); - GetClientRect(hWnd, &rp); - SetTextColor(hDC, 0x00D43B69); - hFont = CreateFont(18,0,0,0,0,0,0,0,DEFAULT_CHARSET,0,0,0,0,TEXT("Times New Roman")); - SelectObject(hDC,hFont); - DrawText(hDC, TEXT(s.str().c_str()), s.str().length(), &rp, DT_CENTER|DT_VCENTER); - DeleteObject(hFont); - EndPaint(hWnd, &ps); - break; - } - default: - { - if (uMsg == s_uTaskbarRestart) - AddTrayIcon (hWnd); - break; - } - } - return DefWindowProc( hWnd, uMsg, wParam, lParam); - } - - bool StartWin32App () - { - if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) - { - MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); - return false; - } - // register main window - auto hInst = GetModuleHandle(NULL); - WNDCLASSEX wclx; - memset (&wclx, 0, sizeof(wclx)); - wclx.cbSize = sizeof(wclx); - wclx.style = 0; - wclx.lpfnWndProc = WndProc; - //wclx.cbClsExtra = 0; - //wclx.cbWndExtra = 0; - wclx.hInstance = hInst; - wclx.hIcon = LoadIcon (hInst, MAKEINTRESOURCE(MAINICON)); - wclx.hCursor = LoadCursor (NULL, IDC_ARROW); - //wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); - wclx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); - wclx.lpszMenuName = NULL; - wclx.lpszClassName = I2PD_WIN32_CLASSNAME; - RegisterClassEx (&wclx); - // create new window - if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 100, 100, 350, 210, NULL, NULL, hInst, NULL)) - { - MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); - return false; - } - SubscribeToEvents(); - return true; - } - - int RunWin32App () - { - MSG msg; - while (GetMessage (&msg, NULL, 0, 0 )) - { - TranslateMessage (&msg); - DispatchMessage (&msg); - } - return msg.wParam; - } - - void StopWin32App () - { - HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); - if (hWnd) - PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); - // UnSubscribeFromEvents(); // TODO: understand why unsubscribing crashes app - UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); - } - - bool GracefulShutdown () - { - HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); - if (hWnd) - PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_GRACEFUL_SHUTDOWN, 0), 0); - return hWnd; - } - - bool StopGracefulShutdown () - { - HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); - if (hWnd) - PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_STOP_GRACEFUL_SHUTDOWN, 0), 0); - return hWnd; - } -} -} +#include +#include +#include +#include +#include "ClientContext.h" +#include "Config.h" +#include "NetDb.hpp" +#include "RouterContext.h" +#include "Transports.h" +#include "Tunnel.h" +#include "version.h" +#include "resource.h" +#include "Daemon.h" +#include "Win32App.h" +#include "Win32NetState.h" + +#define ID_ABOUT 2000 +#define ID_EXIT 2001 +#define ID_CONSOLE 2002 +#define ID_APP 2003 +#define ID_GRACEFUL_SHUTDOWN 2004 +#define ID_STOP_GRACEFUL_SHUTDOWN 2005 +#define ID_RELOAD 2006 +#define ID_ACCEPT_TRANSIT 2007 +#define ID_DECLINE_TRANSIT 2008 + +#define ID_TRAY_ICON 2050 +#define WM_TRAYICON (WM_USER + 1) + +#define IDT_GRACEFUL_SHUTDOWN_TIMER 2100 +#define FRAME_UPDATE_TIMER 2101 +#define IDT_GRACEFUL_TUNNELCHECK_TIMER 2102 + +namespace i2p +{ +namespace win32 +{ + static DWORD GracefulShutdownEndtime = 0; + + typedef DWORD (* IPN)(); + IPN GetTickCountLocal = (IPN)GetProcAddress (GetModuleHandle ("KERNEL32.dll"), "GetTickCount"); + + static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) + { + HMENU hPopup = CreatePopupMenu(); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_CONSOLE, "Open &console"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "Show app"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); + if(!i2p::context.AcceptsTunnels()) + InsertMenu (hPopup, -1, + i2p::util::DaemonWin32::Instance ().isGraceful ? MF_BYPOSITION | MF_STRING | MF_GRAYED : MF_BYPOSITION | MF_STRING, + ID_ACCEPT_TRANSIT, "Accept &transit"); + else + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DECLINE_TRANSIT, "Decline &transit"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_RELOAD, "&Reload tunnels config"); + if (!i2p::util::DaemonWin32::Instance ().isGraceful) + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_GRACEFUL_SHUTDOWN, "&Graceful shutdown"); + else + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_STOP_GRACEFUL_SHUTDOWN, "Stop &graceful shutdown"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_EXIT, "E&xit"); + SetMenuDefaultItem (hPopup, ID_CONSOLE, FALSE); + SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); + + POINT p; + if (!curpos) + { + GetCursorPos (&p); + curpos = &p; + } + + WORD cmd = TrackPopupMenu (hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, curpos->x, curpos->y, 0, hWnd, NULL); + SendMessage (hWnd, WM_COMMAND, cmd, 0); + + DestroyMenu(hPopup); + } + + static void AddTrayIcon (HWND hWnd) + { + NOTIFYICONDATA nid; + memset(&nid, 0, sizeof(nid)); + nid.cbSize = sizeof(nid); + nid.hWnd = hWnd; + nid.uID = ID_TRAY_ICON; + nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; + nid.uCallbackMessage = WM_TRAYICON; + nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (MAINICON)); + strcpy (nid.szTip, "i2pd"); + strcpy (nid.szInfo, "i2pd is starting"); + Shell_NotifyIcon(NIM_ADD, &nid ); + } + + static void RemoveTrayIcon (HWND hWnd) + { + NOTIFYICONDATA nid; + nid.hWnd = hWnd; + nid.uID = ID_TRAY_ICON; + Shell_NotifyIcon (NIM_DELETE, &nid); + } + + static void ShowUptime (std::stringstream& s, int seconds) + { + int num; + + if ((num = seconds / 86400) > 0) { + s << num << " days, "; + seconds -= num * 86400; + } + if ((num = seconds / 3600) > 0) { + s << num << " hours, "; + seconds -= num * 3600; + } + if ((num = seconds / 60) > 0) { + s << num << " min, "; + seconds -= num * 60; + } + s << seconds << " seconds\n"; + } + + template static void ShowTransfered (std::stringstream& s, size transfer) + { + auto bytes = transfer & 0x03ff; + transfer >>= 10; + auto kbytes = transfer & 0x03ff; + transfer >>= 10; + auto mbytes = transfer & 0x03ff; + transfer >>= 10; + auto gbytes = transfer & 0x03ff; + + if (gbytes) + s << gbytes << " GB, "; + if (mbytes) + s << mbytes << " MB, "; + if (kbytes) + s << kbytes << " KB, "; + s << bytes << " Bytes\n"; + } + + static void PrintMainWindowText (std::stringstream& s) + { + s << "\n"; + s << "Status: "; + switch (i2p::context.GetStatus()) + { + case eRouterStatusOK: s << "OK"; break; + case eRouterStatusTesting: s << "Testing"; break; + case eRouterStatusFirewalled: s << "Firewalled"; break; + case eRouterStatusError: + { + switch (i2p::context.GetError()) + { + case eRouterErrorClockSkew: s << "Clock skew"; break; + default: s << "Error"; + } + break; + } + default: s << "Unknown"; + } + s << "; "; + s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; + s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); + if (GracefulShutdownEndtime != 0) + { + DWORD GracefulTimeLeft = (GracefulShutdownEndtime - GetTickCountLocal()) / 1000; + s << "Graceful shutdown, time left: "; ShowUptime(s, GracefulTimeLeft); + } + else + s << "\n"; + s << "Inbound: " << i2p::transport::transports.GetInBandwidth() / 1024 << " KiB/s; "; + s << "Outbound: " << i2p::transport::transports.GetOutBandwidth() / 1024 << " KiB/s\n"; + s << "Received: "; ShowTransfered (s, i2p::transport::transports.GetTotalReceivedBytes()); + s << "Sent: "; ShowTransfered (s, i2p::transport::transports.GetTotalSentBytes()); + s << "\n"; + s << "Routers: " << i2p::data::netdb.GetNumRouters () << "; "; + s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << "; "; + s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "\n"; + s << "Tunnels: "; + s << "In: " << i2p::tunnel::tunnels.CountInboundTunnels() << "; "; + s << "Out: " << i2p::tunnel::tunnels.CountOutboundTunnels() << "; "; + s << "Transit: " << i2p::tunnel::tunnels.CountTransitTunnels() << "\n"; + s << "\n"; + } + + static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + static UINT s_uTaskbarRestart; + + switch (uMsg) + { + case WM_CREATE: + { + s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); + AddTrayIcon (hWnd); + break; + } + case WM_CLOSE: + { + RemoveTrayIcon (hWnd); + KillTimer (hWnd, FRAME_UPDATE_TIMER); + KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); + KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); + PostQuitMessage (0); + break; + } + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case ID_ABOUT: + { + std::stringstream text; + text << "Version: " << I2PD_VERSION << " " << CODENAME; + MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); + return 0; + } + case ID_EXIT: + { + PostMessage (hWnd, WM_CLOSE, 0, 0); + return 0; + } + case ID_ACCEPT_TRANSIT: + { + i2p::context.SetAcceptsTunnels (true); + std::stringstream text; + text << "I2Pd now accept transit tunnels"; + MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); + return 0; + } + case ID_DECLINE_TRANSIT: + { + i2p::context.SetAcceptsTunnels (false); + std::stringstream text; + text << "I2Pd now decline new transit tunnels"; + MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); + return 0; + } + case ID_GRACEFUL_SHUTDOWN: + { + i2p::context.SetAcceptsTunnels (false); + SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes + SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); // check tunnels every second + GracefulShutdownEndtime = GetTickCountLocal() + 10*60*1000; + i2p::util::DaemonWin32::Instance ().isGraceful = true; + return 0; + } + case ID_STOP_GRACEFUL_SHUTDOWN: + { + i2p::context.SetAcceptsTunnels (true); + KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); + KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); + GracefulShutdownEndtime = 0; + i2p::util::DaemonWin32::Instance ().isGraceful = false; + return 0; + } + case ID_RELOAD: + { + i2p::client::context.ReloadConfig(); + std::stringstream text; + text << "I2Pd reloading configs..."; + MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); + return 0; + } + case ID_CONSOLE: + { + char buf[30]; + std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); + uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); + snprintf(buf, 30, "http://%s:%d", httpAddr.c_str(), httpPort); + ShellExecute(NULL, "open", buf, NULL, NULL, SW_SHOWNORMAL); + return 0; + } + case ID_APP: + { + ShowWindow(hWnd, SW_SHOW); + SetTimer(hWnd, FRAME_UPDATE_TIMER, 3000, NULL); + return 0; + } + } + break; + } + case WM_SYSCOMMAND: + { + switch (wParam) + { + case SC_MINIMIZE: + { + ShowWindow(hWnd, SW_HIDE); + KillTimer (hWnd, FRAME_UPDATE_TIMER); + return 0; + } + case SC_CLOSE: + { + std::string close; i2p::config::GetOption("close", close); + if (0 == close.compare("ask")) + switch(::MessageBox(hWnd, "Would you like to minimize instead of exiting?" + " You can add 'close' configuration option. Valid values are: ask, minimize, exit.", + "Minimize instead of exiting?", MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON1)) + { + case IDYES: close = "minimize"; break; + case IDNO: close = "exit"; break; + default: return 0; + } + if (0 == close.compare("minimize")) + { + ShowWindow(hWnd, SW_HIDE); + KillTimer (hWnd, FRAME_UPDATE_TIMER); + return 0; + } + if (0 != close.compare("exit")) + { + ::MessageBox(hWnd, close.c_str(), "Unknown close action in config", MB_OK | MB_ICONWARNING); + return 0; + } + } + } + } + case WM_TRAYICON: + { + switch (lParam) + { + case WM_LBUTTONUP: + case WM_RBUTTONUP: + { + SetForegroundWindow (hWnd); + ShowPopupMenu(hWnd, NULL, -1); + PostMessage (hWnd, WM_APP + 1, 0, 0); + break; + } + } + break; + } + case WM_TIMER: + { + switch(wParam) + { + case IDT_GRACEFUL_SHUTDOWN_TIMER: + { + GracefulShutdownEndtime = 0; + PostMessage (hWnd, WM_CLOSE, 0, 0); // exit + return 0; + } + case IDT_GRACEFUL_TUNNELCHECK_TIMER: + { + if (i2p::tunnel::tunnels.CountTransitTunnels() == 0) + PostMessage (hWnd, WM_CLOSE, 0, 0); + else + SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); + return 0; + } + case FRAME_UPDATE_TIMER: + { + InvalidateRect(hWnd, NULL, TRUE); + return 0; + } + } + break; + } + case WM_PAINT: + { + HDC hDC; + PAINTSTRUCT ps; + RECT rp; + HFONT hFont; + std::stringstream s; PrintMainWindowText (s); + hDC = BeginPaint (hWnd, &ps); + GetClientRect(hWnd, &rp); + SetTextColor(hDC, 0x00D43B69); + hFont = CreateFont(18,0,0,0,0,0,0,0,DEFAULT_CHARSET,0,0,0,0,TEXT("Times New Roman")); + SelectObject(hDC,hFont); + DrawText(hDC, TEXT(s.str().c_str()), s.str().length(), &rp, DT_CENTER|DT_VCENTER); + DeleteObject(hFont); + EndPaint(hWnd, &ps); + break; + } + default: + { + if (uMsg == s_uTaskbarRestart) + AddTrayIcon (hWnd); + break; + } + } + return DefWindowProc( hWnd, uMsg, wParam, lParam); + } + + bool StartWin32App () + { + if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) + { + MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); + return false; + } + // register main window + auto hInst = GetModuleHandle(NULL); + WNDCLASSEX wclx; + memset (&wclx, 0, sizeof(wclx)); + wclx.cbSize = sizeof(wclx); + wclx.style = 0; + wclx.lpfnWndProc = WndProc; + //wclx.cbClsExtra = 0; + //wclx.cbWndExtra = 0; + wclx.hInstance = hInst; + wclx.hIcon = LoadIcon (hInst, MAKEINTRESOURCE(MAINICON)); + wclx.hCursor = LoadCursor (NULL, IDC_ARROW); + //wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wclx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wclx.lpszMenuName = NULL; + wclx.lpszClassName = I2PD_WIN32_CLASSNAME; + RegisterClassEx (&wclx); + // create new window + if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 100, 100, 350, 210, NULL, NULL, hInst, NULL)) + { + MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); + return false; + } + SubscribeToEvents(); + return true; + } + + int RunWin32App () + { + MSG msg; + while (GetMessage (&msg, NULL, 0, 0 )) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + return msg.wParam; + } + + void StopWin32App () + { + HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); + if (hWnd) + PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); + // UnSubscribeFromEvents(); // TODO: understand why unsubscribing crashes app + UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); + } + + bool GracefulShutdown () + { + HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); + if (hWnd) + PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_GRACEFUL_SHUTDOWN, 0), 0); + return hWnd; + } + + bool StopGracefulShutdown () + { + HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); + if (hWnd) + PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_STOP_GRACEFUL_SHUTDOWN, 0), 0); + return hWnd; + } +} +} diff --git a/Win32/Win32App.h b/Win32/Win32App.h index b230eb06..23082842 100644 --- a/Win32/Win32App.h +++ b/Win32/Win32App.h @@ -1,17 +1,17 @@ -#ifndef WIN32APP_H__ -#define WIN32APP_H__ - -#define I2PD_WIN32_CLASSNAME "i2pd main window" - -namespace i2p -{ -namespace win32 -{ - bool StartWin32App (); - void StopWin32App (); - int RunWin32App (); - bool GracefulShutdown (); - bool StopGracefulShutdown (); -} -} -#endif // WIN32APP_H__ +#ifndef WIN32APP_H__ +#define WIN32APP_H__ + +#define I2PD_WIN32_CLASSNAME "i2pd main window" + +namespace i2p +{ +namespace win32 +{ + bool StartWin32App (); + void StopWin32App (); + int RunWin32App (); + bool GracefulShutdown (); + bool StopGracefulShutdown (); +} +} +#endif // WIN32APP_H__ diff --git a/Win32/Win32Service.h b/Win32/Win32Service.h index 2830d237..05a6b9f9 100644 --- a/Win32/Win32Service.h +++ b/Win32/Win32Service.h @@ -26,48 +26,48 @@ class I2PService { -public: + public: - I2PService(PSTR pszServiceName, - BOOL fCanStop = TRUE, - BOOL fCanShutdown = TRUE, - BOOL fCanPauseContinue = FALSE); + I2PService(PSTR pszServiceName, + BOOL fCanStop = TRUE, + BOOL fCanShutdown = TRUE, + BOOL fCanPauseContinue = FALSE); - virtual ~I2PService(void); + virtual ~I2PService(void); - static BOOL isService(); - static BOOL Run(I2PService &service); - void Stop(); + static BOOL isService(); + static BOOL Run(I2PService &service); + void Stop(); -protected: + protected: - virtual void OnStart(DWORD dwArgc, PSTR *pszArgv); - virtual void OnStop(); - virtual void OnPause(); - virtual void OnContinue(); - virtual void OnShutdown(); - void SetServiceStatus(DWORD dwCurrentState, - DWORD dwWin32ExitCode = NO_ERROR, - DWORD dwWaitHint = 0); + virtual void OnStart(DWORD dwArgc, PSTR *pszArgv); + virtual void OnStop(); + virtual void OnPause(); + virtual void OnContinue(); + virtual void OnShutdown(); + void SetServiceStatus(DWORD dwCurrentState, + DWORD dwWin32ExitCode = NO_ERROR, + DWORD dwWaitHint = 0); -private: + private: - static void WINAPI ServiceMain(DWORD dwArgc, LPSTR *lpszArgv); - static void WINAPI ServiceCtrlHandler(DWORD dwCtrl); - void WorkerThread(); - void Start(DWORD dwArgc, PSTR *pszArgv); - void Pause(); - void Continue(); - void Shutdown(); - static I2PService* s_service; - PSTR m_name; - SERVICE_STATUS m_status; - SERVICE_STATUS_HANDLE m_statusHandle; + static void WINAPI ServiceMain(DWORD dwArgc, LPSTR *lpszArgv); + static void WINAPI ServiceCtrlHandler(DWORD dwCtrl); + void WorkerThread(); + void Start(DWORD dwArgc, PSTR *pszArgv); + void Pause(); + void Continue(); + void Shutdown(); + static I2PService* s_service; + PSTR m_name; + SERVICE_STATUS m_status; + SERVICE_STATUS_HANDLE m_statusHandle; - BOOL m_fStopping; - HANDLE m_hStoppedEvent; + BOOL m_fStopping; + HANDLE m_hStoppedEvent; - std::thread* _worker; + std::thread* _worker; }; void InstallService( @@ -77,8 +77,8 @@ void InstallService( PCSTR pszDependencies, PCSTR pszAccount, PCSTR pszPassword - ); +); void UninstallService(PCSTR pszServiceName); -#endif // WIN_32_SERVICE_H__ \ No newline at end of file +#endif // WIN_32_SERVICE_H__ diff --git a/Win32/resource.h b/Win32/resource.h index 3b188481..daa1ddcb 100644 --- a/Win32/resource.h +++ b/Win32/resource.h @@ -1,11 +1,11 @@ -//{{NO_DEPENDENCIES}} -#define MAINICON 101 - -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +#define MAINICON 101 + +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index fc71123f..dd302fce 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -127,8 +127,8 @@ namespace i2p i2p::context.SetNetID (netID); i2p::context.Init (); - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); #ifdef MESHNET // manual override for meshnet ipv4 = false; @@ -143,8 +143,8 @@ namespace i2p i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); - bool ntcp; i2p::config::GetOption("ntcp", ntcp); - i2p::context.PublishNTCPAddress (ntcp, !ipv6); + bool ntcp; i2p::config::GetOption("ntcp", ntcp); + i2p::context.PublishNTCPAddress (ntcp, !ipv6); bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) { @@ -172,10 +172,13 @@ namespace i2p SetMaxNumTransitTunnels (transitTunnels); bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); - if (isFloodfill) { + if (isFloodfill) + { LogPrint(eLogInfo, "Daemon: router will be floodfill"); i2p::context.SetFloodfill (true); - } else { + } + else + { i2p::context.SetFloodfill (false); } @@ -243,7 +246,8 @@ namespace i2p i2p::transport::transports.RestrictRoutesToFamilies(fams); restricted = fams.size() > 0; } - if (routers.length() > 0) { + if (routers.length() > 0) + { std::set idents; size_t pos = 0, comma; do @@ -279,7 +283,8 @@ namespace i2p i2p::data::netdb.Start(); bool upnp; i2p::config::GetOption("upnp.enabled", upnp); - if (upnp) { + if (upnp) + { d.UPnP = std::unique_ptr(new i2p::transport::UPnP); d.UPnP->Start (); } @@ -292,15 +297,15 @@ namespace i2p } 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"); if(!ssu) LogPrint(eLogInfo, "Daemon: ssu disabled"); if(!ntcp) LogPrint(eLogInfo, "Daemon: ntcp disabled"); i2p::transport::transports.Start(ntcp, ssu); - if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundNTCP2()) + if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundNTCP2()) LogPrint(eLogInfo, "Daemon: Transports started"); - else + else { LogPrint(eLogError, "Daemon: failed to start Transports"); /** shut down netdb right away */ @@ -310,23 +315,23 @@ namespace i2p } bool http; i2p::config::GetOption("http.enabled", http); - if (http) { + if (http) + { std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); LogPrint(eLogInfo, "Daemon: starting webconsole at ", httpAddr, ":", httpPort); - try + try { d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); d.httpServer->Start(); - } - catch (std::exception& ex) + } + catch (std::exception& ex) { LogPrint (eLogError, "Daemon: failed to start webconsole: ", ex.what ()); ThrowFatal ("Unable to start webconsole at ", httpAddr, ":", httpPort, ": ", ex.what ()); } } - LogPrint(eLogInfo, "Daemon: starting Tunnels"); i2p::tunnel::tunnels.Start(); @@ -339,12 +344,12 @@ namespace i2p std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr); uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort); LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort); - try + try { d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); d.m_I2PControlService->Start (); - } - catch (std::exception& ex) + } + catch (std::exception& ex) { LogPrint (eLogError, "Daemon: failed to start I2PControl: ", ex.what ()); ThrowFatal ("Unable to start I2PControl service at ", i2pcpAddr, ":", i2pcpPort, ": ", ex.what ()); @@ -361,7 +366,7 @@ namespace i2p LogPrint(eLogInfo, "Daemon: stopping Tunnels"); i2p::tunnel::tunnels.Stop(); - if (d.UPnP) + if (d.UPnP) { d.UPnP->Stop (); d.UPnP = nullptr; diff --git a/daemon/Daemon.h b/daemon/Daemon.h index 1745b980..050cc7e8 100644 --- a/daemon/Daemon.h +++ b/daemon/Daemon.h @@ -13,9 +13,10 @@ namespace util class Daemon_Singleton { public: - virtual bool init(int argc, char* argv[], std::shared_ptr logstream); - virtual bool init(int argc, char* argv[]); - virtual bool start(); + + virtual bool init(int argc, char* argv[], std::shared_ptr logstream); + virtual bool init(int argc, char* argv[]); + virtual bool start(); virtual bool stop(); virtual void run () {}; @@ -23,6 +24,7 @@ namespace util bool running; protected: + Daemon_Singleton(); virtual ~Daemon_Singleton(); @@ -39,6 +41,7 @@ namespace util class DaemonQT: public i2p::util::Daemon_Singleton { public: + static DaemonQT& Instance() { static DaemonQT instance; @@ -51,6 +54,7 @@ namespace util class DaemonWin32 : public Daemon_Singleton { public: + static DaemonWin32& Instance() { static DaemonWin32 instance; @@ -72,6 +76,7 @@ namespace util class DaemonAndroid: public i2p::util::Daemon_Singleton { public: + static DaemonAndroid& Instance() { static DaemonAndroid instance; @@ -83,6 +88,7 @@ namespace util class DaemonLinux : public Daemon_Singleton { public: + static DaemonLinux& Instance() { static DaemonLinux instance; @@ -94,10 +100,12 @@ namespace util void run (); private: + std::string pidfile; int pidFH; public: + int gracefulShutdownInterval; // in seconds }; #endif diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index de052a2b..6f903e7e 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -892,8 +892,8 @@ namespace http { void HTTPConnection::Receive () { m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), - std::bind(&HTTPConnection::HandleReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); + std::bind(&HTTPConnection::HandleReceive, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); } void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index abc4fea5..fe62c271 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -79,17 +79,17 @@ namespace http std::string m_Hostname; }; - //all the below functions are also used by Qt GUI, see mainwindow.cpp -> getStatusPageHtml - enum OutputFormatEnum { forWebConsole, forQtUi }; - void ShowStatus (std::stringstream& s, bool includeHiddenContent, OutputFormatEnum outputFormat); - void ShowLocalDestinations (std::stringstream& s); - void ShowLeasesSets(std::stringstream& s); - void ShowTunnels (std::stringstream& s); - void ShowTransitTunnels (std::stringstream& s); - void ShowTransports (std::stringstream& s); - void ShowSAMSessions (std::stringstream& s); - void ShowI2PTunnels (std::stringstream& s); - void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token); + //all the below functions are also used by Qt GUI, see mainwindow.cpp -> getStatusPageHtml + enum OutputFormatEnum { forWebConsole, forQtUi }; + void ShowStatus (std::stringstream& s, bool includeHiddenContent, OutputFormatEnum outputFormat); + void ShowLocalDestinations (std::stringstream& s); + void ShowLeasesSets(std::stringstream& s); + void ShowTunnels (std::stringstream& s); + void ShowTransitTunnels (std::stringstream& s); + void ShowTransports (std::stringstream& s); + void ShowSAMSessions (std::stringstream& s); + void ShowI2PTunnels (std::stringstream& s); + void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token); } // http } // i2p diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index acf054f1..fb00dc29 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -59,29 +59,28 @@ namespace client m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem); // handlers - m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; - m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; - m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; - m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler; - m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; - m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler; - m_MethodHandlers["ClientServicesInfo"] = &I2PControlService::ClientServicesInfoHandler; + m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; + m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; + m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; + m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler; + m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; + m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler; + m_MethodHandlers["ClientServicesInfo"] = &I2PControlService::ClientServicesInfoHandler; // I2PControl m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler; // RouterInfo - m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlService::UptimeHandler; - m_RouterInfoHandlers["i2p.router.version"] = &I2PControlService::VersionHandler; - m_RouterInfoHandlers["i2p.router.status"] = &I2PControlService::StatusHandler; - m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlService::NetDbKnownPeersHandler; - m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlService::NetDbActivePeersHandler; - m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlService::InboundBandwidth1S; - m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlService::OutboundBandwidth1S; - m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlService::NetStatusHandler; + m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlService::UptimeHandler; + m_RouterInfoHandlers["i2p.router.version"] = &I2PControlService::VersionHandler; + m_RouterInfoHandlers["i2p.router.status"] = &I2PControlService::StatusHandler; + m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlService::NetDbKnownPeersHandler; + m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlService::NetDbActivePeersHandler; + m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlService::InboundBandwidth1S; + m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlService::OutboundBandwidth1S; + m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlService::NetStatusHandler; m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlService::TunnelsParticipatingHandler; - m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = -&I2PControlService::TunnelsSuccessRateHandler; + m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = &I2PControlService::TunnelsSuccessRateHandler; m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlService::NetTotalReceivedBytes; m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlService::NetTotalSentBytes; @@ -97,10 +96,10 @@ namespace client // ClientServicesInfo m_ClientServicesInfoHandlers["I2PTunnel"] = &I2PControlService::I2PTunnelInfoHandler; m_ClientServicesInfoHandlers["HTTPProxy"] = &I2PControlService::HTTPProxyInfoHandler; - m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlService::SOCKSInfoHandler; - m_ClientServicesInfoHandlers["SAM"] = &I2PControlService::SAMInfoHandler; - m_ClientServicesInfoHandlers["BOB"] = &I2PControlService::BOBInfoHandler; - m_ClientServicesInfoHandlers["I2CP"] = &I2PControlService::I2CPInfoHandler; + m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlService::SOCKSInfoHandler; + m_ClientServicesInfoHandlers["SAM"] = &I2PControlService::SAMInfoHandler; + m_ClientServicesInfoHandlers["BOB"] = &I2PControlService::BOBInfoHandler; + m_ClientServicesInfoHandlers["I2CP"] = &I2PControlService::I2CPInfoHandler; } I2PControlService::~I2PControlService () @@ -401,8 +400,8 @@ namespace client auto it1 = m_RouterInfoHandlers.find (it->first); if (it1 != m_RouterInfoHandlers.end ()) { - if (!first) results << ","; - else first = false; + if (!first) results << ","; + else first = false; (this->*(it1->second))(results); } else @@ -500,7 +499,7 @@ namespace client m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) - { + { Daemon.running = 0; }); } @@ -514,7 +513,7 @@ namespace client m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) - { + { Daemon.running = 0; }); } diff --git a/daemon/I2PControl.h b/daemon/I2PControl.h index 3233ad12..eb28af76 100644 --- a/daemon/I2PControl.h +++ b/daemon/I2PControl.h @@ -27,6 +27,7 @@ namespace client class I2PControlService { typedef boost::asio::ssl::stream ssl_socket; + public: I2PControlService (const std::string& address, int port); diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index b2ebb005..92e79c11 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -80,10 +80,10 @@ namespace transport void UPnP::Discover () { bool isError; - int err; + int err; #if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) - err = UPNPDISCOVER_SUCCESS; + err = UPNPDISCOVER_SUCCESS; #if (MINIUPNPC_API_VERSION >= 14) m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0, 0, 2, &err); @@ -92,9 +92,9 @@ namespace transport #endif isError = err != UPNPDISCOVER_SUCCESS; -#else // MINIUPNPC_API_VERSION >= 8 - err = 0; - m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0); +#else // MINIUPNPC_API_VERSION >= 8 + err = 0; + m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0); isError = m_Devlist == NULL; #endif // MINIUPNPC_API_VERSION >= 8 { @@ -105,15 +105,15 @@ namespace transport if (isError) { - LogPrint (eLogError, "UPnP: unable to discover Internet Gateway Devices: error ", err); + LogPrint (eLogError, "UPnP: unable to discover Internet Gateway Devices: error ", err); return; } err = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); - m_upnpUrlsInitialized=err!=0; + m_upnpUrlsInitialized = err != 0; if (err == UPNP_IGD_VALID_CONNECTED) { - err = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); + err = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); if(err != UPNPCOMMAND_SUCCESS) { LogPrint (eLogError, "UPnP: unable to get external address: error ", err); @@ -124,14 +124,14 @@ namespace transport LogPrint (eLogError, "UPnP: found Internet Gateway Device ", m_upnpUrls.controlURL); if (!m_externalIPAddress[0]) { - LogPrint (eLogError, "UPnP: found Internet Gateway Device doesn't know our external address"); + LogPrint (eLogError, "UPnP: found Internet Gateway Device doesn't know our external address"); return; } } } else { - LogPrint (eLogError, "UPnP: unable to find valid Internet Gateway Device: error ", err); + LogPrint (eLogError, "UPnP: unable to find valid Internet Gateway Device: error ", err); return; } @@ -182,7 +182,7 @@ namespace transport err = CheckMapping (strPort.c_str (), strType.c_str ()); if (err != UPNPCOMMAND_SUCCESS) // if mapping not found { - LogPrint (eLogDebug, "UPnP: possibly port ", strPort, " is not forwarded: return code ", err); + LogPrint (eLogDebug, "UPnP: possibly port ", strPort, " is not forwarded: return code ", err); #if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) err = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), NULL, NULL); @@ -202,7 +202,7 @@ namespace transport } else { - LogPrint (eLogDebug, "UPnP: external forward from ", m_NetworkAddr, ":", strPort, " exists on current Internet Gateway Device"); + LogPrint (eLogDebug, "UPnP: external forward from ", m_NetworkAddr, ":", strPort, " exists on current Internet Gateway Device"); return; } } @@ -219,14 +219,14 @@ namespace transport void UPnP::CloseMapping (std::shared_ptr address) { - if(!m_upnpUrlsInitialized) { - return; - } + if(!m_upnpUrlsInitialized) { + return; + } std::string strType (GetProto (address)), strPort (std::to_string (address->port)); int err = UPNPCOMMAND_SUCCESS; - + err = CheckMapping (strPort.c_str (), strType.c_str ()); - if (err == UPNPCOMMAND_SUCCESS) + if (err == UPNPCOMMAND_SUCCESS) { err = UPNP_DeletePortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), NULL); LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", err); @@ -237,11 +237,11 @@ namespace transport { freeUPNPDevlist (m_Devlist); m_Devlist = 0; - if(m_upnpUrlsInitialized){ - FreeUPNPUrls (&m_upnpUrls); - m_upnpUrlsInitialized=false; - } - } + if(m_upnpUrlsInitialized){ + FreeUPNPUrls (&m_upnpUrls); + m_upnpUrlsInitialized=false; + } + } std::string UPnP::GetProto (std::shared_ptr address) { diff --git a/daemon/UPnP.h b/daemon/UPnP.h index e66f0aff..29f9e3e6 100644 --- a/daemon/UPnP.h +++ b/daemon/UPnP.h @@ -33,41 +33,41 @@ namespace transport { public: - UPnP (); - ~UPnP (); - void Close (); + UPnP (); + ~UPnP (); + void Close (); - void Start (); - void Stop (); + void Start (); + void Stop (); private: - void Discover (); - int CheckMapping (const char* port, const char* type); - void PortMapping (); - void TryPortMapping (std::shared_ptr address); - void CloseMapping (); - void CloseMapping (std::shared_ptr address); + void Discover (); + int CheckMapping (const char* port, const char* type); + 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); + void Run (); + std::string GetProto (std::shared_ptr address); private: - bool m_IsRunning; - std::unique_ptr m_Thread; - std::condition_variable m_Started; - std::mutex m_StartedMutex; - boost::asio::io_service m_Service; - boost::asio::deadline_timer m_Timer; - bool m_upnpUrlsInitialized=false; - struct UPNPUrls m_upnpUrls; - struct IGDdatas m_upnpData; + bool m_IsRunning; + std::unique_ptr m_Thread; + std::condition_variable m_Started; + std::mutex m_StartedMutex; + boost::asio::io_service m_Service; + boost::asio::deadline_timer m_Timer; + bool m_upnpUrlsInitialized = false; + struct UPNPUrls m_upnpUrls; + struct IGDdatas m_upnpData; - // For miniupnpc - struct UPNPDev * m_Devlist = 0; - char m_NetworkAddr[64]; - char m_externalIPAddress[40]; + // For miniupnpc + struct UPNPDev * m_Devlist = 0; + char m_NetworkAddr[64]; + char m_externalIPAddress[40]; }; } } @@ -79,10 +79,10 @@ namespace transport { class UPnP { public: - UPnP () {}; - ~UPnP () {}; - void Start () { LogPrint(eLogWarning, "UPnP: this module was disabled at compile-time"); } - void Stop () {}; + UPnP () {}; + ~UPnP () {}; + void Start () { LogPrint(eLogWarning, "UPnP: this module was disabled at compile-time"); } + void Stop () {}; }; } } diff --git a/daemon/UnixDaemon.cpp b/daemon/UnixDaemon.cpp index b7af779c..a59fc693 100644 --- a/daemon/UnixDaemon.cpp +++ b/daemon/UnixDaemon.cpp @@ -196,5 +196,4 @@ namespace i2p } } } - #endif diff --git a/daemon/i2pd.cpp b/daemon/i2pd.cpp index 425c2560..7b68c073 100644 --- a/daemon/i2pd.cpp +++ b/daemon/i2pd.cpp @@ -2,7 +2,6 @@ #include "Daemon.h" #if defined(QT_GUI_LIB) - namespace i2p { namespace qt @@ -10,11 +9,11 @@ namespace qt int RunQT (int argc, char* argv[]); } } + int main( int argc, char* argv[] ) { return i2p::qt::RunQT (argc, argv); } - #else int main( int argc, char* argv[] ) { diff --git a/libi2pd/Base.cpp b/libi2pd/Base.cpp index f80f2751..51f5f225 100644 --- a/libi2pd/Base.cpp +++ b/libi2pd/Base.cpp @@ -7,7 +7,8 @@ namespace i2p { namespace data { - static const char T32[32] = { + static const char T32[32] = + { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', @@ -29,15 +30,16 @@ namespace data * Direct Substitution Table */ - static const char T64[64] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '-', '~' + static const char T64[64] = + { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '-', '~' }; const char * GetBase64SubstitutionTable () @@ -67,14 +69,12 @@ namespace data * */ - size_t /* Number of bytes in the encoded buffer */ - ByteStreamToBase64 ( - const uint8_t * InBuffer, /* Input buffer, binary data */ - size_t InCount, /* Number of bytes in the input buffer */ - char * OutBuffer, /* output buffer */ - size_t len /* length of output buffer */ + size_t ByteStreamToBase64 ( /* Number of bytes in the encoded buffer */ + const uint8_t * InBuffer, /* Input buffer, binary data */ + size_t InCount, /* Number of bytes in the input buffer */ + char * OutBuffer, /* output buffer */ + size_t len /* length of output buffer */ ) - { unsigned char * ps; unsigned char * pd; @@ -83,55 +83,60 @@ namespace data int i; int n; int m; - size_t outCount; + size_t outCount; ps = (unsigned char *)InBuffer; - n = InCount/3; - m = InCount%3; + n = InCount / 3; + m = InCount % 3; if (!m) - outCount = 4*n; + outCount = 4 * n; else - outCount = 4*(n+1); + outCount = 4 * (n + 1); + if (outCount > len) return 0; + pd = (unsigned char *)OutBuffer; - for ( i = 0; i>= 2; /* base64 digit #1 */ - *pd++ = T64[acc_1]; - acc_1 = *ps++; - acc_2 |= acc_1 >> 4; /* base64 digit #2 */ - *pd++ = T64[acc_2]; - acc_1 &= 0x0f; - acc_1 <<=2; - acc_2 = *ps++; - acc_1 |= acc_2>>6; /* base64 digit #3 */ - *pd++ = T64[acc_1]; - acc_2 &= 0x3f; /* base64 digit #4 */ - *pd++ = T64[acc_2]; + for ( i = 0; i < n; i++ ) + { + acc_1 = *ps++; + acc_2 = (acc_1 << 4) & 0x30; + acc_1 >>= 2; /* base64 digit #1 */ + *pd++ = T64[acc_1]; + acc_1 = *ps++; + acc_2 |= acc_1 >> 4; /* base64 digit #2 */ + *pd++ = T64[acc_2]; + acc_1 &= 0x0f; + acc_1 <<= 2; + acc_2 = *ps++; + acc_1 |= acc_2 >> 6; /* base64 digit #3 */ + *pd++ = T64[acc_1]; + acc_2 &= 0x3f; /* base64 digit #4 */ + *pd++ = T64[acc_2]; } - if ( m == 1 ){ - acc_1 = *ps++; - acc_2 = (acc_1<<4)&0x3f; /* base64 digit #2 */ - acc_1 >>= 2; /* base64 digit #1 */ - *pd++ = T64[acc_1]; - *pd++ = T64[acc_2]; - *pd++ = P64; - *pd++ = P64; + if ( m == 1 ) + { + acc_1 = *ps++; + acc_2 = (acc_1 << 4) & 0x3f; /* base64 digit #2 */ + acc_1 >>= 2; /* base64 digit #1 */ + *pd++ = T64[acc_1]; + *pd++ = T64[acc_2]; + *pd++ = P64; + *pd++ = P64; } - else if ( m == 2 ){ - acc_1 = *ps++; - acc_2 = (acc_1<<4)&0x3f; - acc_1 >>= 2; /* base64 digit #1 */ - *pd++ = T64[acc_1]; - acc_1 = *ps++; - acc_2 |= acc_1 >> 4; /* base64 digit #2 */ - *pd++ = T64[acc_2]; - acc_1 &= 0x0f; - acc_1 <<=2; /* base64 digit #3 */ - *pd++ = T64[acc_1]; - *pd++ = P64; + else if ( m == 2 ) + { + acc_1 = *ps++; + acc_2 = (acc_1 << 4) & 0x3f; + acc_1 >>= 2; /* base64 digit #1 */ + *pd++ = T64[acc_1]; + acc_1 = *ps++; + acc_2 |= acc_1 >> 4; /* base64 digit #2 */ + *pd++ = T64[acc_2]; + acc_1 &= 0x0f; + acc_1 <<= 2; /* base64 digit #3 */ + *pd++ = T64[acc_1]; + *pd++ = P64; } return outCount; @@ -147,12 +152,11 @@ namespace data * */ - size_t /* Number of output bytes */ - Base64ToByteStream ( - const char * InBuffer, /* BASE64 encoded buffer */ - size_t InCount, /* Number of input bytes */ - uint8_t * OutBuffer, /* output buffer length */ - size_t len /* length of output buffer */ + size_t Base64ToByteStream ( /* Number of output bytes */ + const char * InBuffer, /* BASE64 encoded buffer */ + size_t InCount, /* Number of input bytes */ + uint8_t * OutBuffer, /* output buffer length */ + size_t len /* length of output buffer */ ) { unsigned char * ps; @@ -162,42 +166,52 @@ namespace data int i; int n; int m; - size_t outCount; + size_t outCount; + + if (isFirstTime) + iT64Build(); + + n = InCount / 4; + m = InCount % 4; - if (isFirstTime) iT64Build(); - n = InCount/4; - m = InCount%4; if (InCount && !m) - outCount = 3*n; - else { - outCount = 0; - return 0; + outCount = 3 * n; + else + { + outCount = 0; + return 0; } ps = (unsigned char *)(InBuffer + InCount - 1); - while ( *ps-- == P64 ) outCount--; + while ( *ps-- == P64 ) + outCount--; ps = (unsigned char *)InBuffer; - if (outCount > len) return -1; + if (outCount > len) + return -1; + pd = OutBuffer; auto endOfOutBuffer = OutBuffer + outCount; - for ( i = 0; i < n; i++ ){ - acc_1 = iT64[*ps++]; - acc_2 = iT64[*ps++]; - acc_1 <<= 2; - acc_1 |= acc_2>>4; - *pd++ = acc_1; - if (pd >= endOfOutBuffer) break; + for ( i = 0; i < n; i++ ) + { + acc_1 = iT64[*ps++]; + acc_2 = iT64[*ps++]; + acc_1 <<= 2; + acc_1 |= acc_2 >> 4; + *pd++ = acc_1; + if (pd >= endOfOutBuffer) + break; - acc_2 <<= 4; - acc_1 = iT64[*ps++]; - acc_2 |= acc_1 >> 2; - *pd++ = acc_2; - if (pd >= endOfOutBuffer) break; + acc_2 <<= 4; + acc_1 = iT64[*ps++]; + acc_2 |= acc_1 >> 2; + *pd++ = acc_2; + if (pd >= endOfOutBuffer) + break; - acc_2 = iT64[*ps++]; - acc_2 |= acc_1 << 6; - *pd++ = acc_2; + acc_2 = iT64[*ps++]; + acc_2 |= acc_1 << 6; + *pd++ = acc_2; } return outCount; @@ -206,20 +220,25 @@ namespace data size_t Base64EncodingBufferSize (const size_t input_size) { auto d = div (input_size, 3); - if (d.rem) d.quot++; - return 4*d.quot; + if (d.rem) + d.quot++; + + return 4 * d.quot; } std::string ToBase64Standard (const std::string& in) { - auto len = Base64EncodingBufferSize (in.length ()); - char * str = new char[len+1]; + auto len = Base64EncodingBufferSize (in.length ()); + char * str = new char[len + 1]; auto l = ByteStreamToBase64 ((const uint8_t *)in.c_str (), in.length (), str, len); str[l] = 0; // replace '-' by '+' and '~' by '/' for (size_t i = 0; i < l; i++) - if (str[i] == '-') str[i] = '+'; - else if (str[i] == '~') str[i] = '/'; + if (str[i] == '-') + str[i] = '+'; + else if (str[i] == '~') + str[i] = '/'; + std::string s(str); delete[] str; return s; @@ -236,10 +255,10 @@ namespace data static void iT64Build() { - int i; + int i; isFirstTime = 0; - for ( i=0; i<256; i++ ) iT64[i] = -1; - for ( i=0; i<64; i++ ) iT64[(int)T64[i]] = i; + for ( i = 0; i < 256; i++ ) iT64[i] = -1; + for ( i = 0; i < 64; i++ ) iT64[(int)T64[i]] = i; iT64[(int)P64] = 0; } @@ -302,4 +321,3 @@ namespace data } } } - diff --git a/libi2pd/Base.h b/libi2pd/Base.h index a273f468..5d550092 100644 --- a/libi2pd/Base.h +++ b/libi2pd/Base.h @@ -15,13 +15,13 @@ namespace data { size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen); size_t ByteStreamToBase32 (const uint8_t * InBuf, size_t len, char * outBuf, size_t outLen); - /** - Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes - */ + /** + Compute the size for a buffer to contain encoded base64 given that the size of the input is input_size bytes + */ size_t Base64EncodingBufferSize(const size_t input_size); - - std::string ToBase64Standard (const std::string& in); // using standard table, for Proxy-Authorization - + + std::string ToBase64Standard (const std::string& in); // using standard table, for Proxy-Authorization + } // data } // i2p diff --git a/libi2pd/Blinding.cpp b/libi2pd/Blinding.cpp index 8d3f4b40..287d3648 100644 --- a/libi2pd/Blinding.cpp +++ b/libi2pd/Blinding.cpp @@ -17,21 +17,21 @@ namespace i2p namespace data { static EC_POINT * BlindPublicKeyECDSA (const EC_GROUP * group, const EC_POINT * pub, const uint8_t * seed) - { + { BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); - BIGNUM * q = BN_CTX_get (ctx); + BIGNUM * q = BN_CTX_get (ctx); EC_GROUP_get_order (group, q, ctx); // calculate alpha = seed mod q BIGNUM * alpha = BN_CTX_get (ctx); - BN_bin2bn (seed, 64, alpha); // seed is in BigEndian + BN_bin2bn (seed, 64, alpha); // seed is in BigEndian BN_mod (alpha, alpha, q, ctx); // % q // A' = BLIND_PUBKEY(A, alpha) = A + DERIVE_PUBLIC(alpha) auto p = EC_POINT_new (group); EC_POINT_mul (group, p, alpha, nullptr, nullptr, ctx); // B*alpha EC_POINT_add (group, p, pub, p, ctx); // pub + B*alpha BN_CTX_end (ctx); - BN_CTX_free (ctx); + BN_CTX_free (ctx); return p; } @@ -39,18 +39,18 @@ namespace data { BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); - BIGNUM * q = BN_CTX_get (ctx); + BIGNUM * q = BN_CTX_get (ctx); EC_GROUP_get_order (group, q, ctx); // calculate alpha = seed mod q BIGNUM * alpha = BN_CTX_get (ctx); - BN_bin2bn (seed, 64, alpha); // seed is in BigEndian - BN_mod (alpha, alpha, q, ctx); // % q + BN_bin2bn (seed, 64, alpha); // seed is in BigEndian + BN_mod (alpha, alpha, q, ctx); // % q BN_add (alpha, alpha, priv); // alpha = alpha + priv - // a' = BLIND_PRIVKEY(a, alpha) = (a + alpha) mod q + // a' = BLIND_PRIVKEY(a, alpha) = (a + alpha) mod q BN_mod (blindedPriv, alpha, q, ctx); // % q BN_CTX_end (ctx); BN_CTX_free (ctx); - } + } static void BlindEncodedPublicKeyECDSA (size_t publicKeyLen, const EC_GROUP * group, const uint8_t * pub, const uint8_t * seed, uint8_t * blindedPub) { @@ -63,7 +63,7 @@ namespace data EC_POINT_get_affine_coordinates_GFp (group, p1, x, y, NULL); EC_POINT_free (p1); i2p::crypto::bn2buf (x, blindedPub, publicKeyLen/2); - i2p::crypto::bn2buf (y, blindedPub + publicKeyLen/2, publicKeyLen/2); + i2p::crypto::bn2buf (y, blindedPub + publicKeyLen/2, publicKeyLen/2); BN_free (x); BN_free (y); } @@ -85,7 +85,7 @@ namespace data i2p::crypto::bn2buf (x, blindedPub, publicKeyLen/2); i2p::crypto::bn2buf (y, blindedPub + publicKeyLen/2, publicKeyLen/2); BN_free (x); BN_free (y); - } + } template static size_t BlindECDSA (i2p::data::SigningKeyType sigType, const uint8_t * key, const uint8_t * seed, Fn blind, Args&&...args) @@ -97,7 +97,7 @@ namespace data { case i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256: { - publicKeyLength = i2p::crypto::ECDSAP256_KEY_LENGTH; + publicKeyLength = i2p::crypto::ECDSAP256_KEY_LENGTH; group = EC_GROUP_new_by_curve_name (NID_X9_62_prime256v1); break; } @@ -116,18 +116,18 @@ namespace data default: LogPrint (eLogError, "Blinding: signature type ", (int)sigType, " is not ECDSA"); } - if (group) + if (group) { blind (publicKeyLength, group, key, seed, std::forward(args)...); EC_GROUP_free (group); - } + } return publicKeyLength; } //---------------------------------------------------------- const uint8_t B33_TWO_BYTES_SIGTYPE_FLAG = 0x01; - const uint8_t B33_PER_SECRET_FLAG = 0x02; // not used for now + const uint8_t B33_PER_SECRET_FLAG = 0x02; // not used for now const uint8_t B33_PER_CLIENT_AUTH_FLAG = 0x04; BlindedPublicKey::BlindedPublicKey (std::shared_ptr identity, bool clientAuth): @@ -138,7 +138,7 @@ namespace data m_PublicKey.resize (len); memcpy (m_PublicKey.data (), identity->GetSigningPublicKeyBuffer (), len); m_SigType = identity->GetSigningKeyType (); - m_BlindedSigType = m_SigType; + m_BlindedSigType = m_SigType; } BlindedPublicKey::BlindedPublicKey (const std::string& b33): @@ -150,12 +150,12 @@ namespace data { LogPrint (eLogError, "Blinding: malformed b33 ", b33); return; - } - uint32_t checksum = crc32 (0, addr + 3, l - 3); + } + uint32_t checksum = crc32 (0, addr + 3, l - 3); // checksum is Little Endian - addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); + addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); uint8_t flags = addr[0]; - size_t offset = 1; + size_t offset = 1; if (flags & B33_TWO_BYTES_SIGTYPE_FLAG) // two bytes signatures { m_SigType = bufbe16toh (addr + offset); offset += 2; @@ -178,7 +178,7 @@ namespace data memcpy (m_PublicKey.data (), addr + offset, len); } else - LogPrint (eLogError, "Blinding: public key in b33 address is too short for signature type ", (int)m_SigType); + LogPrint (eLogError, "Blinding: public key in b33 address is too short for signature type ", (int)m_SigType); } else LogPrint (eLogError, "Blinding: unknown signature type ", (int)m_SigType, " in b33"); @@ -189,25 +189,25 @@ namespace data if (m_PublicKey.size () > 32) return ""; // assume 25519 uint8_t addr[35]; char str[60]; // TODO: define actual length uint8_t flags = 0; - if (m_IsClientAuth) flags |= B33_PER_CLIENT_AUTH_FLAG; + if (m_IsClientAuth) flags |= B33_PER_CLIENT_AUTH_FLAG; addr[0] = flags; // flags addr[1] = m_SigType; // sig type addr[2] = m_BlindedSigType; // blinded sig type memcpy (addr + 3, m_PublicKey.data (), m_PublicKey.size ()); - uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ()); + uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ()); // checksum is Little Endian - addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); + addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16); auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60); return std::string (str, str + l); } void BlindedPublicKey::GetCredential (uint8_t * credential) const { - // A = destination's signing public key + // A = destination's signing public key // stA = signature type of A, 2 bytes big endian uint16_t stA = htobe16 (GetSigType ()); // stA1 = signature type of blinded A, 2 bytes big endian - uint16_t stA1 = htobe16 (GetBlindedSigType ()); + uint16_t stA1 = htobe16 (GetBlindedSigType ()); // credential = H("credential", A || stA || stA1) H ("credential", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, credential); } @@ -224,15 +224,15 @@ namespace data { uint16_t stA = htobe16 (GetSigType ()), stA1 = htobe16 (GetBlindedSigType ()); uint8_t salt[32]; - //seed = HKDF(H("I2PGenerateAlpha", keydata), datestring || secret, "i2pblinding1", 64) + //seed = HKDF(H("I2PGenerateAlpha", keydata), datestring || secret, "i2pblinding1", 64) H ("I2PGenerateAlpha", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, salt); i2p::crypto::HKDF (salt, (const uint8_t *)date, 8, "i2pblinding1", seed); } size_t BlindedPublicKey::GetBlindedKey (const char * date, uint8_t * blindedKey) const { - uint8_t seed[64]; - GenerateAlpha (date, seed); + uint8_t seed[64]; + GenerateAlpha (date, seed); size_t publicKeyLength = 0; switch (m_SigType) @@ -244,7 +244,7 @@ namespace data break; case i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: case i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: - i2p::crypto::GetEd25519 ()->BlindPublicKey (GetPublicKey (), seed, blindedKey); + i2p::crypto::GetEd25519 ()->BlindPublicKey (GetPublicKey (), seed, blindedKey); publicKeyLength = i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; break; default: @@ -255,8 +255,8 @@ namespace data size_t BlindedPublicKey::BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const { - uint8_t seed[64]; - GenerateAlpha (date, seed); + uint8_t seed[64]; + GenerateAlpha (date, seed); size_t publicKeyLength = 0; switch (m_SigType) { @@ -272,15 +272,15 @@ namespace data default: LogPrint (eLogError, "Blinding: can't blind signature type ", (int)m_SigType); } - return publicKeyLength; + return publicKeyLength; } - void BlindedPublicKey::H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const + void BlindedPublicKey::H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const { SHA256_CTX ctx; SHA256_Init (&ctx); SHA256_Update (&ctx, p.c_str (), p.length ()); - for (const auto& it: bufs) + for (const auto& it: bufs) SHA256_Update (&ctx, it.first, it.second); SHA256_Final (hash, &ctx); } @@ -289,15 +289,15 @@ namespace data { i2p::data::IdentHash hash; uint8_t blinded[128]; - size_t publicKeyLength = 0; + size_t publicKeyLength = 0; if (date) publicKeyLength = GetBlindedKey (date, blinded); else { char currentDate[9]; i2p::util::GetCurrentDate (currentDate); - publicKeyLength = GetBlindedKey (currentDate, blinded); - } + publicKeyLength = GetBlindedKey (currentDate, blinded); + } if (publicKeyLength) { auto stA1 = htobe16 (m_BlindedSigType); @@ -308,10 +308,9 @@ namespace data SHA256_Final ((uint8_t *)hash, &ctx); } else - LogPrint (eLogError, "Blinding: blinded key type ", (int)m_BlindedSigType, " is not supported"); + LogPrint (eLogError, "Blinding: blinded key type ", (int)m_BlindedSigType, " is not supported"); return hash; } } } - diff --git a/libi2pd/Blinding.h b/libi2pd/Blinding.h index 65b8c0f8..3e0ad3fc 100644 --- a/libi2pd/Blinding.h +++ b/libi2pd/Blinding.h @@ -15,7 +15,7 @@ namespace data public: BlindedPublicKey (std::shared_ptr identity, bool clientAuth = false); - BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p + BlindedPublicKey (const std::string& b33); // from b33 without .b32.i2p std::string ToB33 () const; const uint8_t * GetPublicKey () const { return m_PublicKey.data (); }; @@ -25,14 +25,14 @@ namespace data bool IsValid () const { return GetSigType (); }; // signature type 0 means invalid void GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const; // 32 bytes - size_t GetBlindedKey (const char * date, uint8_t * blindedKey) const; // date is 8 chars "YYYYMMDD", return public key length - size_t BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const; // date is 8 chars "YYYYMMDD", return public key length + size_t GetBlindedKey (const char * date, uint8_t * blindedKey) const; // date is 8 chars "YYYYMMDD", return public key length + size_t BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const; // date is 8 chars "YYYYMMDD", return public key length i2p::data::IdentHash GetStoreHash (const char * date = nullptr) const; // date is 8 chars "YYYYMMDD", use current if null private: void GetCredential (uint8_t * credential) const; // 32 bytes - void GenerateAlpha (const char * date, uint8_t * seed) const; // 64 bytes, date is 8 chars "YYYYMMDD" + void GenerateAlpha (const char * date, uint8_t * seed) const; // 64 bytes, date is 8 chars "YYYYMMDD" void H (const std::string& p, const std::vector >& bufs, uint8_t * hash) const; private: diff --git a/libi2pd/CPU.h b/libi2pd/CPU.h index b4c19607..e1fd450c 100644 --- a/libi2pd/CPU.h +++ b/libi2pd/CPU.h @@ -5,10 +5,10 @@ namespace i2p { namespace cpu { - extern bool aesni; - extern bool avx; + extern bool aesni; + extern bool avx; - void Detect(); + void Detect(); } } diff --git a/libi2pd/ChaCha20.cpp b/libi2pd/ChaCha20.cpp index 222111b7..1d1e9bc6 100644 --- a/libi2pd/ChaCha20.cpp +++ b/libi2pd/ChaCha20.cpp @@ -12,73 +12,72 @@ #include "I2PEndian.h" #include "ChaCha20.h" -#if !OPENSSL_AEAD_CHACHA20_POLY1305 +#if !OPENSSL_AEAD_CHACHA20_POLY1305 namespace i2p { namespace crypto { namespace chacha { -void u32t8le(uint32_t v, uint8_t * p) +void u32t8le(uint32_t v, uint8_t * p) { - p[0] = v & 0xff; - p[1] = (v >> 8) & 0xff; - p[2] = (v >> 16) & 0xff; - p[3] = (v >> 24) & 0xff; + p[0] = v & 0xff; + p[1] = (v >> 8) & 0xff; + p[2] = (v >> 16) & 0xff; + p[3] = (v >> 24) & 0xff; } -uint32_t u8t32le(const uint8_t * p) +uint32_t u8t32le(const uint8_t * p) { - uint32_t value = p[3]; + uint32_t value = p[3]; - value = (value << 8) | p[2]; - value = (value << 8) | p[1]; - value = (value << 8) | p[0]; + value = (value << 8) | p[2]; + value = (value << 8) | p[1]; + value = (value << 8) | p[0]; - return value; + return value; } -uint32_t rotl32(uint32_t x, int n) +uint32_t rotl32(uint32_t x, int n) { - return x << n | (x >> (-n & 31)); + return x << n | (x >> (-n & 31)); } -void quarterround(uint32_t *x, int a, int b, int c, int d) +void quarterround(uint32_t *x, int a, int b, int c, int d) { - x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 16); - x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 12); - x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 8); - x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 7); + x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 16); + x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 12); + x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 8); + x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 7); } void Chacha20Block::operator << (const Chacha20State & st) { int i; - for (i = 0; i < 16; i++) + for (i = 0; i < 16; i++) u32t8le(st.data[i], data + (i << 2)); } void block (Chacha20State &input, int rounds) { - int i; - Chacha20State x; - x.Copy(input); - - for (i = rounds; i > 0; i -= 2) - { - quarterround(x.data, 0, 4, 8, 12); - quarterround(x.data, 1, 5, 9, 13); - quarterround(x.data, 2, 6, 10, 14); - quarterround(x.data, 3, 7, 11, 15); - quarterround(x.data, 0, 5, 10, 15); - quarterround(x.data, 1, 6, 11, 12); - quarterround(x.data, 2, 7, 8, 13); - quarterround(x.data, 3, 4, 9, 14); - } - x += input; - input.block << x; + int i; + Chacha20State x; + x.Copy(input); + for (i = rounds; i > 0; i -= 2) + { + quarterround(x.data, 0, 4, 8, 12); + quarterround(x.data, 1, 5, 9, 13); + quarterround(x.data, 2, 6, 10, 14); + quarterround(x.data, 3, 7, 11, 15); + quarterround(x.data, 0, 5, 10, 15); + quarterround(x.data, 1, 6, 11, 12); + quarterround(x.data, 2, 7, 8, 13); + quarterround(x.data, 3, 4, 9, 14); + } + x += input; + input.block << x; } void Chacha20Init (Chacha20State& state, const uint8_t * nonce, const uint8_t * key, uint32_t counter) @@ -87,52 +86,52 @@ void Chacha20Init (Chacha20State& state, const uint8_t * nonce, const uint8_t * state.data[1] = 0x3320646e; state.data[2] = 0x79622d32; state.data[3] = 0x6b206574; - for (size_t i = 0; i < 8; i++) - state.data[4 + i] = chacha::u8t32le(key + i * 4); - + for (size_t i = 0; i < 8; i++) + state.data[4 + i] = chacha::u8t32le(key + i * 4); + state.data[12] = htole32 (counter); - for (size_t i = 0; i < 3; i++) - state.data[13 + i] = chacha::u8t32le(nonce + i * 4); + for (size_t i = 0; i < 3; i++) + state.data[13 + i] = chacha::u8t32le(nonce + i * 4); } void Chacha20SetCounter (Chacha20State& state, uint32_t counter) { state.data[12] = htole32 (counter); state.offset = 0; -} +} void Chacha20Encrypt (Chacha20State& state, uint8_t * buf, size_t sz) -{ +{ if (state.offset > 0) { - // previous block if any - auto s = chacha::blocksize - state.offset; + // previous block if any + auto s = chacha::blocksize - state.offset; if (sz < s) s = sz; for (size_t i = 0; i < s; i++) buf[i] ^= state.block.data[state.offset + i]; buf += s; sz -= s; state.offset += s; - if (state.offset >= chacha::blocksize) state.offset = 0; + if (state.offset >= chacha::blocksize) state.offset = 0; } - for (size_t i = 0; i < sz; i += chacha::blocksize) + for (size_t i = 0; i < sz; i += chacha::blocksize) { - chacha::block(state, chacha::rounds); - state.data[12]++; - for (size_t j = i; j < i + chacha::blocksize; j++) - { - if (j >= sz) + chacha::block(state, chacha::rounds); + state.data[12]++; + for (size_t j = i; j < i + chacha::blocksize; j++) + { + if (j >= sz) { state.offset = j & 0x3F; // % 64 break; } - buf[j] ^= state.block.data[j - i]; - } + buf[j] ^= state.block.data[j - i]; + } } } + } // namespace chacha +} // namespace crypto +} // namespace i2p -} -} #endif - diff --git a/libi2pd/ChaCha20.h b/libi2pd/ChaCha20.h index b2eec320..a8bb1d56 100644 --- a/libi2pd/ChaCha20.h +++ b/libi2pd/ChaCha20.h @@ -21,23 +21,23 @@ namespace i2p { namespace crypto { - const std::size_t CHACHA20_KEY_BYTES = 32; - const std::size_t CHACHA20_NOUNCE_BYTES = 12; - + const std::size_t CHACHA20_KEY_BYTES = 32; + const std::size_t CHACHA20_NOUNCE_BYTES = 12; + namespace chacha { - constexpr std::size_t blocksize = 64; + constexpr std::size_t blocksize = 64; constexpr int rounds = 20; struct Chacha20State; struct Chacha20Block { - Chacha20Block () {}; - Chacha20Block (Chacha20Block &&) = delete; + Chacha20Block () {}; + Chacha20Block (Chacha20Block &&) = delete; - uint8_t data[blocksize]; + uint8_t data[blocksize]; - void operator << (const Chacha20State & st); + void operator << (const Chacha20State & st); }; struct Chacha20State @@ -54,19 +54,19 @@ namespace chacha void Copy(const Chacha20State & other) { - memcpy(data, other.data, sizeof(uint32_t) * 16); + memcpy(data, other.data, sizeof(uint32_t) * 16); } uint32_t data[16]; Chacha20Block block; - size_t offset; + size_t offset; }; void Chacha20Init (Chacha20State& state, const uint8_t * nonce, const uint8_t * key, uint32_t counter); void Chacha20SetCounter (Chacha20State& state, uint32_t counter); - void Chacha20Encrypt (Chacha20State& state, uint8_t * buf, size_t sz); // encrypt buf in place -} -} -} -#endif + void Chacha20Encrypt (Chacha20State& state, uint8_t * buf, size_t sz); // encrypt buf in place +} // namespace chacha +} // namespace crypto +} // namespace i2p #endif +#endif diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index b741b118..59b51c00 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -242,7 +242,7 @@ namespace config { "1.pool.ntp.org," "2.pool.ntp.org," "3.pool.ntp.org" - ), "Comma separated list of NTCP servers") + ), "Comma separated list of NTCP servers") ("nettime.ntpsyncinterval", value()->default_value(72), "NTP sync interval in hours (default: 72)") ; diff --git a/libi2pd/Config.h b/libi2pd/Config.h index 679ae3bb..679795b1 100644 --- a/libi2pd/Config.h +++ b/libi2pd/Config.h @@ -18,98 +18,101 @@ namespace i2p { namespace config { - extern boost::program_options::variables_map m_Options; + extern boost::program_options::variables_map m_Options; - /** - * @brief Initialize list of acceptable parameters - * - * Should be called before any Parse* functions. - */ - void Init(); + /** + * @brief Initialize list of acceptable parameters + * + * Should be called before any Parse* functions. + */ + void Init(); - /** - * @brief Parse cmdline parameters, and show help if requested - * @param argc Cmdline arguments count, should be passed from main(). - * @param argv Cmdline parameters array, should be passed from main() - * - * If --help is given in parameters, shows its list with description - * and terminates the program with exitcode 0. - * - * In case of parameter misuse boost throws an exception. - * We internally handle type boost::program_options::unknown_option, - * and then terminate the program with exitcode 1. - * - * Other exceptions will be passed to higher level. - */ - void ParseCmdline(int argc, char* argv[], bool ignoreUnknown = false); + /** + * @brief Parse cmdline parameters, and show help if requested + * @param argc Cmdline arguments count, should be passed from main(). + * @param argv Cmdline parameters array, should be passed from main() + * + * If --help is given in parameters, shows its list with description + * and terminates the program with exitcode 0. + * + * In case of parameter misuse boost throws an exception. + * We internally handle type boost::program_options::unknown_option, + * and then terminate the program with exitcode 1. + * + * Other exceptions will be passed to higher level. + */ + void ParseCmdline(int argc, char* argv[], bool ignoreUnknown = false); - /** - * @brief Load and parse given config file - * @param path Path to config file - * - * If error occurred when opening file path is points to, - * we show the error message and terminate program. - * - * In case of parameter misuse boost throws an exception. - * We internally handle type boost::program_options::unknown_option, - * and then terminate program with exitcode 1. - * - * Other exceptions will be passed to higher level. - */ - void ParseConfig(const std::string& path); + /** + * @brief Load and parse given config file + * @param path Path to config file + * + * If error occurred when opening file path is points to, + * we show the error message and terminate program. + * + * In case of parameter misuse boost throws an exception. + * We internally handle type boost::program_options::unknown_option, + * and then terminate program with exitcode 1. + * + * Other exceptions will be passed to higher level. + */ + void ParseConfig(const std::string& path); - /** - * @brief Used to combine options from cmdline, config and default values - */ - void Finalize(); + /** + * @brief Used to combine options from cmdline, config and default values + */ + void Finalize(); - /* @brief Accessor to parameters by name - * @param name Name of the requested parameter - * @param value Variable where to store option - * @return this function returns false if parameter not found - * - * Example: uint16_t port; GetOption("sam.port", port); - */ - template - bool GetOption(const char *name, T& value) { - if (!m_Options.count(name)) - return false; - value = m_Options[name].as(); - return true; - } + /** + * @brief Accessor to parameters by name + * @param name Name of the requested parameter + * @param value Variable where to store option + * @return this function returns false if parameter not found + * + * Example: uint16_t port; GetOption("sam.port", port); + */ + template + bool GetOption(const char *name, T& value) + { + if (!m_Options.count(name)) + return false; + value = m_Options[name].as(); + return true; + } - template - bool GetOption(const std::string& name, T& value) - { - return GetOption (name.c_str (), value); - } + template + bool GetOption(const std::string& name, T& value) + { + return GetOption (name.c_str (), value); + } - bool GetOptionAsAny(const char *name, boost::any& value); - bool GetOptionAsAny(const std::string& name, boost::any& value); + bool GetOptionAsAny(const char *name, boost::any& value); + bool GetOptionAsAny(const std::string& name, boost::any& value); - /** - * @brief Set value of given parameter - * @param name Name of settable parameter - * @param value New parameter value - * @return true if value set up successful, false otherwise - * - * Example: uint16_t port = 2827; SetOption("bob.port", port); - */ - template - bool SetOption(const char *name, const T& value) { - if (!m_Options.count(name)) - return false; - m_Options.at(name).value() = value; - notify(m_Options); - return true; - } + /** + * @brief Set value of given parameter + * @param name Name of settable parameter + * @param value New parameter value + * @return true if value set up successful, false otherwise + * + * Example: uint16_t port = 2827; SetOption("bob.port", port); + */ + template + bool SetOption(const char *name, const T& value) + { + if (!m_Options.count(name)) + return false; + m_Options.at(name).value() = value; + notify(m_Options); + return true; + } - /** - * @brief Check is value explicitly given or default - * @param name Name of checked parameter - * @return true if value set to default, false otherwise - */ - bool IsDefault(const char *name); + /** + * @brief Check is value explicitly given or default + * @param name Name of checked parameter + * @return true if value set to default, false otherwise + */ + bool IsDefault(const char *name); } } diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 567ae574..d4d3519e 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -286,14 +286,14 @@ namespace crypto #if OPENSSL_X25519 m_Ctx = EVP_PKEY_CTX_new_id (NID_X25519, NULL); m_Pkey = nullptr; -#else +#else m_Ctx = BN_CTX_new (); -#endif - } +#endif + } X25519Keys::X25519Keys (const uint8_t * priv, const uint8_t * pub) { -#if OPENSSL_X25519 +#if OPENSSL_X25519 m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32); m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL); if (pub) @@ -302,33 +302,33 @@ namespace crypto { size_t len = 32; EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); - } + } #else - m_Ctx = BN_CTX_new (); + m_Ctx = BN_CTX_new (); memcpy (m_PrivateKey, priv, 32); if (pub) memcpy (m_PublicKey, pub, 32); else GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx); -#endif - } - +#endif + } + X25519Keys::~X25519Keys () { #if OPENSSL_X25519 EVP_PKEY_CTX_free (m_Ctx); - if (m_Pkey) EVP_PKEY_free (m_Pkey); -#else + if (m_Pkey) EVP_PKEY_free (m_Pkey); +#else BN_CTX_free (m_Ctx); -#endif - } +#endif + } void X25519Keys::GenerateKeys () { #if OPENSSL_X25519 if (m_Pkey) - { - EVP_PKEY_free (m_Pkey); + { + EVP_PKEY_free (m_Pkey); m_Pkey = nullptr; } EVP_PKEY_keygen_init (m_Ctx); @@ -337,26 +337,26 @@ namespace crypto m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL); // TODO: do we really need to re-create m_Ctx? size_t len = 32; EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); -#else +#else RAND_bytes (m_PrivateKey, 32); GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx); -#endif - } +#endif + } void X25519Keys::Agree (const uint8_t * pub, uint8_t * shared) { -#if OPENSSL_X25519 +#if OPENSSL_X25519 EVP_PKEY_derive_init (m_Ctx); auto pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_X25519, NULL, pub, 32); EVP_PKEY_derive_set_peer (m_Ctx, pkey); size_t len = 32; EVP_PKEY_derive (m_Ctx, shared, &len); EVP_PKEY_free (pkey); -#else +#else GetEd25519 ()->ScalarMul (pub, m_PrivateKey, shared, m_Ctx); -#endif - } - +#endif + } + void X25519Keys::GetPrivateKey (uint8_t * priv) const { #if OPENSSL_X25519 @@ -369,14 +369,14 @@ namespace crypto void X25519Keys::SetPrivateKey (const uint8_t * priv) { -#if OPENSSL_X25519 - if (m_Ctx) EVP_PKEY_CTX_free (m_Ctx); +#if OPENSSL_X25519 + if (m_Ctx) EVP_PKEY_CTX_free (m_Ctx); if (m_Pkey) EVP_PKEY_free (m_Pkey); m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32); m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL); #else memcpy (m_PrivateKey, priv, 32); -#endif +#endif } // ElGamal @@ -681,12 +681,12 @@ namespace crypto // AES #ifdef __AES__ - #ifdef ARM64AES - void init_aesenc(void){ + #ifdef ARM64AES + void init_aesenc(void){ // TODO: Implementation - } + } - #endif + #endif #define KeyExpansion256(round0,round1) \ "pshufd $0xff, %%xmm2, %%xmm2 \n" \ @@ -884,7 +884,6 @@ namespace crypto } } - void CBCEncryption::Encrypt (int numBlocks, const ChipherBlock * in, ChipherBlock * out) { #ifdef __AES__ @@ -1139,10 +1138,10 @@ namespace crypto } EVP_CIPHER_CTX_free (ctx); -#else +#else chacha::Chacha20State state; // generate one time poly key - chacha::Chacha20Init (state, nonce, key, 0); + chacha::Chacha20Init (state, nonce, key, 0); uint64_t polyKey[8]; memset(polyKey, 0, sizeof(polyKey)); chacha::Chacha20Encrypt (state, (uint8_t *)polyKey, 64); @@ -1158,7 +1157,7 @@ namespace crypto { // padding1 rem = 16 - rem; - polyHash.Update (padding, rem); + polyHash.Update (padding, rem); } } // encrypt/decrypt data and add to hash @@ -1174,20 +1173,20 @@ namespace crypto { polyHash.Update (buf, msgLen); // before decryption chacha::Chacha20Encrypt (state, buf, msgLen); // decrypt - } + } auto rem = msgLen & 0x0F; // %16 if (rem) { // padding2 rem = 16 - rem; - polyHash.Update (padding, rem); + polyHash.Update (padding, rem); } // adLen and msgLen htole64buf (padding, adLen); htole64buf (padding + 8, msgLen); - polyHash.Update (padding, 16); - + polyHash.Update (padding, 16); + if (encrypt) // calculate Poly1305 tag and write in after encrypted data polyHash.Finish ((uint64_t *)(buf + msgLen)); @@ -1195,7 +1194,7 @@ namespace crypto { uint64_t tag[4]; // calculate Poly1305 tag - polyHash.Finish (tag); + polyHash.Finish (tag); if (memcmp (tag, msg + msgLen, 16)) ret = false; // compare with provided } #endif @@ -1211,20 +1210,20 @@ namespace crypto EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0); EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce); - for (const auto& it: bufs) + for (const auto& it: bufs) EVP_EncryptUpdate(ctx, it.first, &outlen, it.first, it.second); EVP_EncryptFinal_ex(ctx, NULL, &outlen); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, mac); EVP_CIPHER_CTX_free (ctx); -#else +#else chacha::Chacha20State state; // generate one time poly key - chacha::Chacha20Init (state, nonce, key, 0); + chacha::Chacha20Init (state, nonce, key, 0); uint64_t polyKey[8]; memset(polyKey, 0, sizeof(polyKey)); chacha::Chacha20Encrypt (state, (uint8_t *)polyKey, 64); Poly1305 polyHash (polyKey); - // encrypt buffers + // encrypt buffers Chacha20SetCounter (state, 1); size_t size = 0; for (const auto& it: bufs) @@ -1234,22 +1233,22 @@ namespace crypto size += it.second; } // padding - uint8_t padding[16]; + uint8_t padding[16]; memset (padding, 0, 16); auto rem = size & 0x0F; // %16 if (rem) { // padding2 rem = 16 - rem; - polyHash.Update (padding, rem); + polyHash.Update (padding, rem); } // adLen and msgLen // adLen is always zero htole64buf (padding + 8, size); - polyHash.Update (padding, 16); + polyHash.Update (padding, 16); // MAC - polyHash.Finish ((uint64_t *)mac); -#endif + polyHash.Finish ((uint64_t *)mac); +#endif } void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out) @@ -1265,13 +1264,13 @@ namespace crypto EVP_CIPHER_CTX_free (ctx); #else chacha::Chacha20State state; - chacha::Chacha20Init (state, nonce, key, 1); + chacha::Chacha20Init (state, nonce, key, 1); if (out != msg) memcpy (out, msg, msgLen); chacha::Chacha20Encrypt (state, out, msgLen); #endif } - void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, + void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen) { #if OPENSSL_HKDF @@ -1279,10 +1278,10 @@ namespace crypto EVP_PKEY_derive_init (pctx); EVP_PKEY_CTX_set_hkdf_md (pctx, EVP_sha256()); if (key && keyLen) - { + { EVP_PKEY_CTX_set1_hkdf_salt (pctx, salt, 32); EVP_PKEY_CTX_set1_hkdf_key (pctx, key, keyLen); - } + } else { // zerolen @@ -1290,22 +1289,22 @@ namespace crypto uint8_t tempKey[32]; unsigned int len; HMAC(EVP_sha256(), salt, 32, nullptr, 0, tempKey, &len); EVP_PKEY_CTX_set1_hkdf_key (pctx, tempKey, len); - } + } if (info.length () > 0) EVP_PKEY_CTX_add1_hkdf_info (pctx, info.c_str (), info.length ()); EVP_PKEY_derive (pctx, out, &outLen); EVP_PKEY_CTX_free (pctx); #else uint8_t prk[32]; unsigned int len; - HMAC(EVP_sha256(), salt, 32, key, keyLen, prk, &len); + HMAC(EVP_sha256(), salt, 32, key, keyLen, prk, &len); auto l = info.length (); memcpy (out, info.c_str (), l); out[l] = 0x01; HMAC(EVP_sha256(), prk, 32, out, l + 1, out, &len); if (outLen > 32) // 64 - { + { memcpy (out + 32, info.c_str (), l); out[l + 32] = 0x02; - HMAC(EVP_sha256(), prk, 32, out, l + 33, out + 32, &len); - } + HMAC(EVP_sha256(), prk, 32, out, l + 33, out + 32, &len); + } #endif } @@ -1323,10 +1322,10 @@ namespace crypto } }*/ - + void InitCrypto (bool precomputation) { - i2p::cpu::Detect (); + i2p::cpu::Detect (); #if LEGACY_OPENSSL SSL_library_init (); #endif @@ -1364,4 +1363,3 @@ namespace crypto } } } - diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 32410daf..2dfd6d0e 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -28,13 +28,13 @@ #else # define LEGACY_OPENSSL 0 # if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1 -# define OPENSSL_HKDF 1 -# define OPENSSL_EDDSA 1 -# define OPENSSL_X25519 1 -# define OPENSSL_SIPHASH 1 +# define OPENSSL_HKDF 1 +# define OPENSSL_EDDSA 1 +# define OPENSSL_X25519 1 +# define OPENSSL_SIPHASH 1 # endif # if !defined OPENSSL_NO_CHACHA && !defined OPENSSL_NO_POLY1305 // some builds might not include them -# define OPENSSL_AEAD_CHACHA20_POLY1305 1 +# define OPENSSL_AEAD_CHACHA20_POLY1305 1 # endif #endif @@ -81,20 +81,20 @@ namespace crypto const uint8_t * GetPublicKey () const { return m_PublicKey; }; void GetPrivateKey (uint8_t * priv) const; void SetPrivateKey (const uint8_t * priv); // wihout calculating public - void Agree (const uint8_t * pub, uint8_t * shared); + void Agree (const uint8_t * pub, uint8_t * shared); private: - uint8_t m_PublicKey[32]; + uint8_t m_PublicKey[32]; #if OPENSSL_X25519 EVP_PKEY_CTX * m_Ctx; EVP_PKEY * m_Pkey; -#else +#else BN_CTX * m_Ctx; uint8_t m_PrivateKey[32]; -#endif +#endif }; - + // ElGamal void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding = false); @@ -117,15 +117,15 @@ namespace crypto void operator^=(const ChipherBlock& other) // XOR { if (!(((size_t)buf | (size_t)other.buf) & 0x03)) // multiple of 4 ? - { + { for (int i = 0; i < 4; i++) reinterpret_cast(buf)[i] ^= reinterpret_cast(other.buf)[i]; - } + } else - { + { for (int i = 0; i < 16; i++) buf[i] ^= other.buf[i]; - } + } } }; @@ -297,7 +297,7 @@ namespace crypto // HKDF - void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32 + void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32 // init and terminate void InitCrypto (bool precomputation); diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp index 878e984a..2abcb111 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -151,7 +151,7 @@ namespace crypto ECIESX25519AEADRatchetEncryptor::ECIESX25519AEADRatchetEncryptor (const uint8_t * pub) { memcpy (m_PublicKey, pub, 32); - } + } void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub, BN_CTX *, bool) { @@ -166,16 +166,15 @@ namespace crypto bool ECIESX25519AEADRatchetDecryptor::Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding) { m_StaticKeys.Agree (epub, sharedSecret); - return true; + return true; } void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub) { X25519Keys k; - k.GenerateKeys (); + k.GenerateKeys (); k.GetPrivateKey (priv); memcpy (pub, k.GetPublicKey (), 32); } } } - diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h index 701e9482..43bd3aaa 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -45,7 +45,7 @@ namespace crypto ElGamalDecryptor (const uint8_t * priv); bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); size_t GetPublicKeyLen () const { return 256; }; - + private: uint8_t m_PrivateKey[256]; @@ -76,7 +76,7 @@ namespace crypto ~ECIESP256Decryptor (); bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); size_t GetPublicKeyLen () const { return 64; }; - + private: EC_GROUP * m_Curve; @@ -109,7 +109,7 @@ namespace crypto ~ECIESGOSTR3410Decryptor (); bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); size_t GetPublicKeyLen () const { return 64; }; - + private: BIGNUM * m_PrivateKey; @@ -119,14 +119,14 @@ namespace crypto // ECIES-X25519-AEAD-Ratchet - class ECIESX25519AEADRatchetEncryptor: public CryptoKeyEncryptor + class ECIESX25519AEADRatchetEncryptor: public CryptoKeyEncryptor { public: ECIESX25519AEADRatchetEncryptor (const uint8_t * pub); ~ECIESX25519AEADRatchetEncryptor () {}; void Encrypt (const uint8_t *, uint8_t * pub, BN_CTX *, bool); - // copies m_PublicKey to pub + // copies m_PublicKey to pub private: @@ -139,7 +139,7 @@ namespace crypto ECIESX25519AEADRatchetDecryptor (const uint8_t * priv); ~ECIESX25519AEADRatchetDecryptor () {}; - bool Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding); + bool Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding); // agree with static and return in sharedSecret (32 bytes) size_t GetPublicKeyLen () const { return 32; }; @@ -153,4 +153,3 @@ namespace crypto } #endif - diff --git a/libi2pd/CryptoWorker.h b/libi2pd/CryptoWorker.h index d43e356c..baeb7f14 100644 --- a/libi2pd/CryptoWorker.h +++ b/libi2pd/CryptoWorker.h @@ -77,5 +77,4 @@ namespace worker } } - #endif diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 6441b5e2..2da73649 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -13,10 +13,10 @@ namespace datagram DatagramDestination::DatagramDestination (std::shared_ptr owner, bool gzip): m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr), m_Gzip (gzip) { - auto identityLen = m_Owner->GetIdentity ()->GetFullLen (); - m_From.resize (identityLen); - m_Owner->GetIdentity ()->ToBuffer (m_From.data (), identityLen); - m_Signature.resize (m_Owner->GetIdentity ()->GetSignatureLen ()); + auto identityLen = m_Owner->GetIdentity ()->GetFullLen (); + m_From.resize (identityLen); + m_Owner->GetIdentity ()->ToBuffer (m_From.data (), identityLen); + m_Signature.resize (m_Owner->GetIdentity ()->GetSignatureLen ()); } DatagramDestination::~DatagramDestination () @@ -36,7 +36,7 @@ namespace datagram m_Owner->Sign (payload, len, m_Signature.data ()); auto session = ObtainSession(identity); - auto msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {m_Signature.data (), m_Signature.size ()}, {payload, len}}, + auto msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {m_Signature.data (), m_Signature.size ()}, {payload, len}}, fromPort, toPort, false, !session->IsRatchets ()); // datagram session->SendMsg(msg); } @@ -46,8 +46,8 @@ namespace datagram auto session = ObtainSession(identity); auto msg = CreateDataMessage ({{payload, len}}, fromPort, toPort, true, !session->IsRatchets ()); // raw session->SendMsg(msg); - } - + } + void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort,uint8_t * const &buf, size_t len) { i2p::data::IdentityEx identity; @@ -86,8 +86,8 @@ namespace datagram m_RawReceiver (fromPort, toPort, buf, len); else LogPrint (eLogWarning, "DatagramDestination: no receiver for raw datagram"); - } - + } + DatagramDestination::Receiver DatagramDestination::FindReceiver(uint16_t port) { std::lock_guard lock(m_ReceiversMutex); @@ -107,23 +107,22 @@ namespace datagram { if (isRaw) HandleRawDatagram (fromPort, toPort, uncompressed, uncompressedLen); - else + else HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen); - } + } else LogPrint (eLogWarning, "Datagram: decompression failed"); } - std::shared_ptr DatagramDestination::CreateDataMessage ( - const std::vector >& payloads, - uint16_t fromPort, uint16_t toPort, bool isRaw, bool checksum) + const std::vector >& payloads, + uint16_t fromPort, uint16_t toPort, bool isRaw, bool checksum) { auto msg = NewI2NPMessage (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for length size_t size = m_Gzip ? m_Deflator.Deflate (payloads, buf, msg->maxLen - msg->len) : - i2p::data::GzipNoCompression (payloads, buf, msg->maxLen - msg->len); + i2p::data::GzipNoCompression (payloads, buf, msg->maxLen - msg->len); if (size) { htobe32buf (msg->GetPayload (), size); // length @@ -186,7 +185,7 @@ namespace datagram } DatagramSession::DatagramSession(std::shared_ptr localDestination, - const i2p::data::IdentHash & remoteIdent) : + const i2p::data::IdentHash & remoteIdent) : m_LocalDestination(localDestination), m_RemoteIdent(remoteIdent), m_SendQueueTimer(localDestination->GetService()), @@ -384,4 +383,3 @@ namespace datagram } } } - diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index ea2a4e13..72e7340c 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -32,68 +32,71 @@ namespace datagram const uint64_t DATAGRAM_SESSION_LEASE_HANDOVER_FUDGE = 1000; // milliseconds minimum time between path switches const uint64_t DATAGRAM_SESSION_PATH_MIN_LIFETIME = 5 * 1000; - // max 64 messages buffered in send queue for each datagram session - const size_t DATAGRAM_SEND_QUEUE_MAX_SIZE = 64; + // max 64 messages buffered in send queue for each datagram session + const size_t DATAGRAM_SEND_QUEUE_MAX_SIZE = 64; class DatagramSession : public std::enable_shared_from_this { - public: - DatagramSession(std::shared_ptr localDestination, const i2p::data::IdentHash & remoteIdent); - void Start (); - void Stop (); + public: + + DatagramSession(std::shared_ptr localDestination, const i2p::data::IdentHash & remoteIdent); + + void Start (); + void Stop (); - /** @brief ack the garlic routing path */ - void Ack(); + /** @brief ack the garlic routing path */ + void Ack(); - /** send an i2np message to remote endpoint for this session */ - void SendMsg(std::shared_ptr msg); - /** get the last time in milliseconds for when we used this datagram session */ - uint64_t LastActivity() const { return m_LastUse; } + /** send an i2np message to remote endpoint for this session */ + void SendMsg(std::shared_ptr msg); + /** get the last time in milliseconds for when we used this datagram session */ + uint64_t LastActivity() const { return m_LastUse; } bool IsRatchets () const { return m_RoutingSession && m_RoutingSession->IsRatchets (); } - + struct Info { std::shared_ptr IBGW; std::shared_ptr OBEP; const uint64_t activity; - Info() : IBGW(nullptr), OBEP(nullptr), activity(0) {} - Info(const uint8_t * ibgw, const uint8_t * obep, const uint64_t a) : - activity(a) { - if(ibgw) IBGW = std::make_shared(ibgw); - else IBGW = nullptr; - if(obep) OBEP = std::make_shared(obep); - else OBEP = nullptr; - } - }; + Info() : IBGW(nullptr), OBEP(nullptr), activity(0) {} + Info(const uint8_t * ibgw, const uint8_t * obep, const uint64_t a) : + activity(a) { + if(ibgw) IBGW = std::make_shared(ibgw); + else IBGW = nullptr; + if(obep) OBEP = std::make_shared(obep); + else OBEP = nullptr; + } + }; - Info GetSessionInfo() const; + Info GetSessionInfo() const; - private: + private: - void FlushSendQueue(); - void ScheduleFlushSendQueue(); + void FlushSendQueue(); + void ScheduleFlushSendQueue(); - void HandleSend(std::shared_ptr msg); + void HandleSend(std::shared_ptr msg); - std::shared_ptr GetSharedRoutingPath(); + std::shared_ptr GetSharedRoutingPath(); - void HandleLeaseSetUpdated(std::shared_ptr ls); + void HandleLeaseSetUpdated(std::shared_ptr ls); - private: - std::shared_ptr m_LocalDestination; - i2p::data::IdentHash m_RemoteIdent; - std::shared_ptr m_RemoteLeaseSet; - std::shared_ptr m_RoutingSession; - std::shared_ptr m_CurrentRemoteLease; - std::shared_ptr m_CurrentOutboundTunnel; - boost::asio::deadline_timer m_SendQueueTimer; - std::vector > m_SendQueue; - uint64_t m_LastUse; - bool m_RequestingLS; + private: + + std::shared_ptr m_LocalDestination; + i2p::data::IdentHash m_RemoteIdent; + std::shared_ptr m_RemoteLeaseSet; + std::shared_ptr m_RoutingSession; + std::shared_ptr m_CurrentRemoteLease; + std::shared_ptr m_CurrentOutboundTunnel; + boost::asio::deadline_timer m_SendQueueTimer; + std::vector > m_SendQueue; + uint64_t m_LastUse; + bool m_RequestingLS; }; typedef std::shared_ptr DatagramSession_ptr; @@ -104,17 +107,15 @@ namespace datagram typedef std::function Receiver; typedef std::function RawReceiver; - public: - - DatagramDestination (std::shared_ptr owner, bool gzip); + DatagramDestination (std::shared_ptr owner, bool gzip); ~DatagramDestination (); void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); void SendRawDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw = false); - + void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; void ResetReceiver () { m_Receiver = nullptr; }; @@ -123,7 +124,7 @@ namespace datagram void SetRawReceiver (const RawReceiver& receiver) { m_RawReceiver = receiver; }; void ResetRawReceiver () { m_RawReceiver = nullptr; }; - + std::shared_ptr GetInfoForRemote(const i2p::data::IdentHash & remote); // clean up stale sessions @@ -131,14 +132,14 @@ namespace datagram private: - std::shared_ptr ObtainSession(const i2p::data::IdentHash & ident); + std::shared_ptr ObtainSession(const i2p::data::IdentHash & ident); - std::shared_ptr CreateDataMessage (const std::vector >& payloads, + std::shared_ptr CreateDataMessage (const std::vector >& payloads, uint16_t fromPort, uint16_t toPort, bool isRaw = false, bool checksum = true); void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len); void HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - + /** find a receiver by port, if none by port is found try default receiever, otherwise returns nullptr */ Receiver FindReceiver(uint16_t port); diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 782e2841..921e55f0 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -16,8 +16,8 @@ namespace i2p namespace client { LeaseSetDestination::LeaseSetDestination (boost::asio::io_service& service, - bool isPublic, const std::map * params): - m_Service (service), m_IsPublic (isPublic), m_PublishReplyToken (0), + bool isPublic, const std::map * params): + m_Service (service), m_IsPublic (isPublic), m_PublishReplyToken (0), m_LastSubmissionTime (0), m_PublishConfirmationTimer (m_Service), m_PublishVerificationTimer (m_Service), m_PublishDelayTimer (m_Service), m_CleanupTimer (m_Service), m_LeaseSetType (DEFAULT_LEASESET_TYPE), m_AuthType (i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_NONE) @@ -160,13 +160,12 @@ namespace client bool LeaseSetDestination::Reconfigure(std::map params) { - auto itr = params.find("i2cp.dontPublishLeaseSet"); if (itr != params.end()) { m_IsPublic = itr->second != "true"; } - + int inLen, outLen, inQuant, outQuant, numTags, minLatency, maxLatency; std::map intOpts = { {I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen}, @@ -185,7 +184,7 @@ namespace client outQuant = pool->GetNumOutboundTunnels(); minLatency = 0; maxLatency = 0; - + for (auto & opt : intOpts) { itr = params.find(opt.first); @@ -197,7 +196,7 @@ namespace client pool->RequireLatency(minLatency, maxLatency); return pool->Reconfigure(inLen, outLen, inQuant, outQuant); } - + std::shared_ptr LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident) { std::shared_ptr remoteLS; @@ -266,7 +265,7 @@ namespace client std::lock_guard l(m_LeaseSetMutex); return m_LeaseSet; } - + void LeaseSetDestination::SetLeaseSet (std::shared_ptr newLeaseSet) { { @@ -281,7 +280,7 @@ namespace client { s->m_PublishVerificationTimer.cancel (); s->Publish (); - }); + }); } } @@ -565,12 +564,12 @@ namespace client m_PublishReplyToken = 0; if (GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) { - LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again"); + LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds, will try again"); Publish (); } else { - LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ()); + LogPrint (eLogWarning, "Destination: Publish confirmation was not received in ", PUBLISH_CONFIRMATION_TIMEOUT, " seconds from Java floodfill for crypto type ", (int)GetIdentity ()->GetCryptoKeyType ()); // Java floodfill never sends confirmation back for unknown crypto type // assume it successive and try to verify m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); @@ -591,7 +590,7 @@ namespace client { LogPrint (eLogWarning, "Destination: couldn't verify LeaseSet for ", GetIdentHash().ToBase32()); return; - } + } auto s = shared_from_this (); // we must capture this for gcc 4.7 due the bug RequestLeaseSet (ls->GetStoreHash (), @@ -643,9 +642,9 @@ namespace client if (requestComplete) m_Service.post ([requestComplete](void){requestComplete (nullptr);}); return false; - } + } auto storeHash = dest->GetStoreHash (); - auto leaseSet = FindLeaseSet (storeHash); + auto leaseSet = FindLeaseSet (storeHash); if (leaseSet) { if (requestComplete) @@ -720,7 +719,7 @@ namespace client } bool LeaseSetDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest, - std::shared_ptr nextFloodfill, std::shared_ptr request) + std::shared_ptr nextFloodfill, std::shared_ptr request) { if (!request->replyTunnel || !request->replyTunnel->IsEstablished ()) request->replyTunnel = m_Pool->GetNextInboundTunnel (); @@ -783,7 +782,7 @@ namespace client } else { - LogPrint (eLogWarning, "Destination: ", dest.ToBase64 (), " was not found within ", MAX_LEASESET_REQUEST_TIMEOUT, " seconds"); + LogPrint (eLogWarning, "Destination: ", dest.ToBase64 (), " was not found within ", MAX_LEASESET_REQUEST_TIMEOUT, " seconds"); done = true; } @@ -829,13 +828,13 @@ namespace client i2p::data::CryptoKeyType LeaseSetDestination::GetPreferredCryptoType () const { if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) - return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET; - return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; - } - - ClientDestination::ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, + return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET; + return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; + } + + ClientDestination::ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): - LeaseSetDestination (service, isPublic, params), + LeaseSetDestination (service, isPublic, params), m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), m_DatagramDestination (nullptr), m_RefCounter (0), m_ReadyChecker(service) @@ -846,7 +845,7 @@ namespace client // extract encryption type params for LS2 std::set encryptionKeyTypes; if ((GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2 || - GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) && params) + GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) && params) { auto it = params->find (I2CP_PARAM_LEASESET_ENCRYPTION_TYPE); if (it != params->end ()) @@ -864,37 +863,37 @@ namespace client { LogPrint (eLogInfo, "Destination: Unexpected crypto type ", it1, ". ", ex.what ()); continue; - } - } - } - } - // if no param or valid crypto type use from identity - bool isSingleKey = false; - if (encryptionKeyTypes.empty ()) + } + } + } + } + // if no param or valid crypto type use from identity + bool isSingleKey = false; + if (encryptionKeyTypes.empty ()) { isSingleKey = true; encryptionKeyTypes.insert (GetIdentity ()->GetCryptoKeyType ()); - } + } for (auto& it: encryptionKeyTypes) - { + { auto encryptionKey = new EncryptionKey (it); - if (isPublic) + if (isPublic) PersistTemporaryKeys (encryptionKey, isSingleKey); else encryptionKey->GenerateKeys (); - encryptionKey->CreateDecryptor (); + encryptionKey->CreateDecryptor (); if (it == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) m_ECIESx25519EncryptionKey.reset (encryptionKey); else - m_StandardEncryptionKey.reset (encryptionKey); - } - + m_StandardEncryptionKey.reset (encryptionKey); + } + if (isPublic) LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created"); try - { + { if (params) { // extract streaming params @@ -910,9 +909,9 @@ namespace client { m_AuthKeys = std::make_shared >(); if (authType == i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_DH) - ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_DH, params); + ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_DH, params); else if (authType == i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_PSK) - ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_PSK, params); + ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_PSK, params); else LogPrint (eLogError, "Destination: Unexpected auth type ", authType); if (m_AuthKeys->size ()) @@ -920,7 +919,7 @@ namespace client else { LogPrint (eLogError, "Destination: No auth keys read for auth type ", authType); - m_AuthKeys = nullptr; + m_AuthKeys = nullptr; } } } @@ -1002,7 +1001,7 @@ namespace client m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length, true); else LogPrint (eLogError, "Destination: Missing raw datagram destination"); - break; + break; default: LogPrint (eLogError, "Destination: Data: unexpected protocol ", buf[9]); } @@ -1105,7 +1104,7 @@ namespace client return dest; } - i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination (bool gzip) + i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination (bool gzip) { if (m_DatagramDestination == nullptr) m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis (), gzip); @@ -1130,7 +1129,7 @@ namespace client { if (!keys) return; std::string ident = GetIdentHash().ToBase32(); - std::string path = i2p::fs::DataDirPath("destinations", + std::string path = i2p::fs::DataDirPath("destinations", isSingleKey ? (ident + ".dat") : (ident + "." + std::to_string (keys->keyType) + ".dat")); std::ifstream f(path, std::ifstream::binary); @@ -1142,12 +1141,12 @@ namespace client LogPrint (eLogInfo, "Destination: Creating new temporary keys of type for address ", ident, ".b32.i2p"); memset (keys->priv, 0, 256); - memset (keys->pub, 0, 256); + memset (keys->pub, 0, 256); keys->GenerateKeys (); // TODO:: persist crypto key type std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out); if (f1) { - f1.write ((char *)keys->pub, 256); + f1.write ((char *)keys->pub, 256); f1.write ((char *)keys->priv, 256); return; } @@ -1160,11 +1159,11 @@ namespace client if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) { if (m_StandardEncryptionKey) - { + { leaseSet = std::make_shared (GetIdentity (), m_StandardEncryptionKey->pub, tunnels); // sign - Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); - } + Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); + } else LogPrint (eLogError, "Destinations: Wrong encryption key type for LeaseSet type 1"); } @@ -1175,9 +1174,9 @@ namespace client if (m_ECIESx25519EncryptionKey) keySections.push_back ({m_ECIESx25519EncryptionKey->keyType, 32, m_ECIESx25519EncryptionKey->pub} ); if (m_StandardEncryptionKey) - keySections.push_back ({m_StandardEncryptionKey->keyType, (uint16_t)m_StandardEncryptionKey->decryptor->GetPublicKeyLen (), m_StandardEncryptionKey->pub} ); + keySections.push_back ({m_StandardEncryptionKey->keyType, (uint16_t)m_StandardEncryptionKey->decryptor->GetPublicKeyLen (), m_StandardEncryptionKey->pub} ); - bool isPublishedEncrypted = GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; + bool isPublishedEncrypted = GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; auto ls2 = std::make_shared (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2, m_Keys, keySections, tunnels, IsPublic (), isPublishedEncrypted); if (isPublishedEncrypted) // encrypt if type 5 @@ -1204,18 +1203,18 @@ namespace client return false; } - bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const - { + bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const + { return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET ? (bool)m_ECIESx25519EncryptionKey : (bool)m_StandardEncryptionKey; } - const uint8_t * ClientDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const - { + const uint8_t * ClientDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const + { if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) return m_ECIESx25519EncryptionKey ? m_ECIESx25519EncryptionKey->pub : nullptr; return m_StandardEncryptionKey ? m_StandardEncryptionKey->pub : nullptr; } - + void ClientDestination::ReadAuthKey (const std::string& group, const std::map * params) { for (auto it: *params) @@ -1229,7 +1228,7 @@ namespace client m_AuthKeys->push_back (pubKey); else LogPrint (eLogError, "Destination: Unexpected auth key ", it.second.substr (pos+1)); - } + } } } @@ -1241,10 +1240,10 @@ namespace client if (it.second->DeleteStream (recvStreamID)) return true; return false; - } - + } + RunnableClientDestination::RunnableClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): - RunnableService ("Destination"), + RunnableService ("Destination"), ClientDestination (GetIOService (), keys, isPublic, params) { } @@ -1253,7 +1252,7 @@ namespace client { if (IsRunning ()) Stop (); - } + } void RunnableClientDestination::Start () { diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 04cf05f4..dbdc3704 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -52,13 +52,13 @@ namespace client const char I2CP_PARAM_INBOUND_NICKNAME[] = "inbound.nickname"; const char I2CP_PARAM_OUTBOUND_NICKNAME[] = "outbound.nickname"; const char I2CP_PARAM_LEASESET_TYPE[] = "i2cp.leaseSetType"; - const int DEFAULT_LEASESET_TYPE = 1; + const int DEFAULT_LEASESET_TYPE = 1; const char I2CP_PARAM_LEASESET_ENCRYPTION_TYPE[] = "i2cp.leaseSetEncType"; const char I2CP_PARAM_LEASESET_PRIV_KEY[] = "i2cp.leaseSetPrivKey"; // PSK decryption key, base64 const char I2CP_PARAM_LEASESET_AUTH_TYPE[] = "i2cp.leaseSetAuthType"; const char I2CP_PARAM_LEASESET_CLIENT_DH[] = "i2cp.leaseSetClient.dh"; // group of i2cp.leaseSetClient.dh.nnn const char I2CP_PARAM_LEASESET_CLIENT_PSK[] = "i2cp.leaseSetClient.psk"; // group of i2cp.leaseSetClient.psk.nnn - + // latency const char I2CP_PARAM_MIN_TUNNEL_LATENCY[] = "latency.min"; const int DEFAULT_MIN_TUNNEL_LATENCY = 0; @@ -94,7 +94,6 @@ namespace client } }; - public: LeaseSetDestination (boost::asio::io_service& service, bool isPublic, const std::map * params = nullptr); @@ -107,12 +106,12 @@ namespace client /** i2cp reconfigure */ virtual bool Reconfigure(std::map i2cpOpts); - + std::shared_ptr GetTunnelPool () { return m_Pool; }; bool IsReady () const { return m_LeaseSet && !m_LeaseSet->IsExpired () && m_Pool->GetOutboundTunnels ().size () > 0; }; std::shared_ptr FindLeaseSet (const i2p::data::IdentHash& ident); bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); - bool RequestDestinationWithEncryptedLeaseSet (std::shared_ptr dest, RequestComplete requestComplete = nullptr); + bool RequestDestinationWithEncryptedLeaseSet (std::shared_ptr dest, RequestComplete requestComplete = nullptr); void CancelDestinationRequest (const i2p::data::IdentHash& dest, bool notify = true); void CancelDestinationRequestWithEncryptedLeaseSet (std::shared_ptr dest, bool notify = true); @@ -155,7 +154,7 @@ namespace client void HandleDeliveryStatusMessage (uint32_t msgID); void RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete, std::shared_ptr requestedBlindedKey = nullptr); - bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, std::shared_ptr request); + bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, std::shared_ptr request); void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleCleanupTimer (const boost::system::error_code& ecode); void CleanupRemoteLeaseSets (); @@ -202,11 +201,11 @@ namespace client EncryptionKey (i2p::data::CryptoKeyType t):keyType(t) { memset (pub, 0, 256); memset (priv, 0, 256); }; void GenerateKeys () { i2p::data::PrivateKeys::GenerateCryptoKeyPair (keyType, priv, pub); }; void CreateDecryptor () { decryptor = i2p::data::PrivateKeys::CreateDecryptor (keyType, priv); }; - }; - + }; + public: - ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, + ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); ~ClientDestination (); @@ -235,13 +234,13 @@ namespace client int GetStreamingAckDelay () const { return m_StreamingAckDelay; } // datagram - i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; - i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); + i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; + i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); // implements LocalDestination bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; - bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; + bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; protected: @@ -259,8 +258,8 @@ namespace client void PersistTemporaryKeys (EncryptionKey * keys, bool isSingleKey); void ReadAuthKey (const std::string& group, const std::map * params); - private: - + private: + i2p::data::PrivateKeys m_Keys; std::unique_ptr m_StandardEncryptionKey; std::unique_ptr m_ECIESx25519EncryptionKey; @@ -273,7 +272,7 @@ namespace client boost::asio::deadline_timer m_ReadyChecker; - std::shared_ptr > m_AuthKeys; // we don't need them for I2CP + std::shared_ptr > m_AuthKeys; // we don't need them for I2CP public: @@ -287,7 +286,7 @@ namespace client public: RunnableClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); - ~RunnableClientDestination (); + ~RunnableClientDestination (); void Start (); void Stop (); diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 7b386d92..f3b1a685 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -15,54 +15,54 @@ namespace i2p namespace garlic { - void RatchetTagSet::DHInitialize (const uint8_t * rootKey, const uint8_t * k) - { - // DH_INITIALIZE(rootKey, k) - uint8_t keydata[64]; - i2p::crypto::HKDF (rootKey, k, 32, "KDFDHRatchetStep", keydata); // keydata = HKDF(rootKey, k, "KDFDHRatchetStep", 64) - memcpy (m_NextRootKey, keydata, 32); // nextRootKey = keydata[0:31] - i2p::crypto::HKDF (keydata + 32, nullptr, 0, "TagAndKeyGenKeys", m_KeyData.buf); - // [sessTag_ck, symmKey_ck] = HKDF(keydata[32:63], ZEROLEN, "TagAndKeyGenKeys", 64) + void RatchetTagSet::DHInitialize (const uint8_t * rootKey, const uint8_t * k) + { + // DH_INITIALIZE(rootKey, k) + uint8_t keydata[64]; + i2p::crypto::HKDF (rootKey, k, 32, "KDFDHRatchetStep", keydata); // keydata = HKDF(rootKey, k, "KDFDHRatchetStep", 64) + memcpy (m_NextRootKey, keydata, 32); // nextRootKey = keydata[0:31] + i2p::crypto::HKDF (keydata + 32, nullptr, 0, "TagAndKeyGenKeys", m_KeyData.buf); + // [sessTag_ck, symmKey_ck] = HKDF(keydata[32:63], ZEROLEN, "TagAndKeyGenKeys", 64) memcpy (m_SymmKeyCK, m_KeyData.buf + 32, 32); m_NextSymmKeyIndex = 0; - } + } - void RatchetTagSet::NextSessionTagRatchet () - { - i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), nullptr, 0, "STInitialization", m_KeyData.buf); // [sessTag_ck, sesstag_constant] = HKDF(sessTag_ck, ZEROLEN, "STInitialization", 64) - memcpy (m_SessTagConstant, m_KeyData.GetSessTagConstant (), 32); + void RatchetTagSet::NextSessionTagRatchet () + { + i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), nullptr, 0, "STInitialization", m_KeyData.buf); // [sessTag_ck, sesstag_constant] = HKDF(sessTag_ck, ZEROLEN, "STInitialization", 64) + memcpy (m_SessTagConstant, m_KeyData.GetSessTagConstant (), 32); m_NextIndex = 0; - } + } - uint64_t RatchetTagSet::GetNextSessionTag () - { - i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) - m_NextIndex++; - if (m_NextIndex >= 65535) + uint64_t RatchetTagSet::GetNextSessionTag () + { + i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) + m_NextIndex++; + if (m_NextIndex >= 65535) { LogPrint (eLogError, "Garlic: Tagset ", GetTagSetID (), " is empty"); return 0; - } - return m_KeyData.GetTag (); - } + } + return m_KeyData.GetTag (); + } void RatchetTagSet::GetSymmKey (int index, uint8_t * key) { if (index >= m_NextSymmKeyIndex) - { + { auto num = index + 1 - m_NextSymmKeyIndex; if (!m_NextSymmKeyIndex) { i2p::crypto::HKDF (m_SymmKeyCK, nullptr, 0, "SymmetricRatchet", m_CurrentSymmKeyCK); // keydata_0 = HKDF(symmKey_ck, SYMMKEY_CONSTANT, "SymmetricRatchet", 64) m_NextSymmKeyIndex = 1; num--; - } + } for (int i = 0; i < num; i++) - { + { i2p::crypto::HKDF (m_CurrentSymmKeyCK, nullptr, 0, "SymmetricRatchet", m_CurrentSymmKeyCK); if (i < num - 1) m_ItermediateSymmKeys.emplace (m_NextSymmKeyIndex + i, m_CurrentSymmKeyCK + 32); - } + } m_NextSymmKeyIndex += num; memcpy (key, m_CurrentSymmKeyCK + 32, 32); } @@ -70,30 +70,30 @@ namespace garlic { auto it = m_ItermediateSymmKeys.find (index); if (it != m_ItermediateSymmKeys.end ()) - { + { memcpy (key, it->second, 32); m_ItermediateSymmKeys.erase (it); - } + } else LogPrint (eLogError, "Garlic: Missing symmetric key for index ", index); - } - } + } + } void RatchetTagSet::Expire () { if (!m_ExpirationTimestamp) m_ExpirationTimestamp = i2p::util::GetSecondsSinceEpoch () + ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT; - } - - ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet): - GarlicRoutingSession (owner, attachLeaseSet) - { - ResetKeys (); - } + } - ECIESX25519AEADRatchetSession::~ECIESX25519AEADRatchetSession () - { - } + ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet): + GarlicRoutingSession (owner, attachLeaseSet) + { + ResetKeys (); + } + + ECIESX25519AEADRatchetSession::~ECIESX25519AEADRatchetSession () + { + } void ECIESX25519AEADRatchetSession::ResetKeys () { @@ -109,65 +109,65 @@ namespace garlic }; // SHA256 (protocolNameHash) memcpy (m_CK, protocolNameHash, 32); memcpy (m_H, hh, 32); - } - - void ECIESX25519AEADRatchetSession::MixHash (const uint8_t * buf, size_t len) - { - SHA256_CTX ctx; + } + + void ECIESX25519AEADRatchetSession::MixHash (const uint8_t * buf, size_t len) + { + SHA256_CTX ctx; SHA256_Init (&ctx); SHA256_Update (&ctx, m_H, 32); SHA256_Update (&ctx, buf, len); SHA256_Final (m_H, &ctx); - } - - void ECIESX25519AEADRatchetSession::CreateNonce (uint64_t seqn, uint8_t * nonce) - { - memset (nonce, 0, 4); - htole64buf (nonce + 4, seqn); } - bool ECIESX25519AEADRatchetSession::GenerateEphemeralKeysAndEncode (uint8_t * buf) - { - for (int i = 0; i < 10; i++) - { - m_EphemeralKeys.GenerateKeys (); - if (i2p::crypto::GetElligator ()->Encode (m_EphemeralKeys.GetPublicKey (), buf)) - return true; // success - } - return false; - } + void ECIESX25519AEADRatchetSession::CreateNonce (uint64_t seqn, uint8_t * nonce) + { + memset (nonce, 0, 4); + htole64buf (nonce + 4, seqn); + } - std::shared_ptr ECIESX25519AEADRatchetSession::CreateNewSessionTagset () - { - uint8_t tagsetKey[32]; - i2p::crypto::HKDF (m_CK, nullptr, 0, "SessionReplyTags", tagsetKey, 32); // tagsetKey = HKDF(chainKey, ZEROLEN, "SessionReplyTags", 32) - // Session Tag Ratchet - auto tagsetNsr = std::make_shared(shared_from_this ()); - tagsetNsr->DHInitialize (m_CK, tagsetKey); // tagset_nsr = DH_INITIALIZE(chainKey, tagsetKey) - tagsetNsr->NextSessionTagRatchet (); - return tagsetNsr; - } - - bool ECIESX25519AEADRatchetSession::HandleNewIncomingSession (const uint8_t * buf, size_t len) - { - if (!GetOwner ()) return false; - // we are Bob - // KDF1 - MixHash (GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET), 32); // h = SHA256(h || bpk) - - if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk)) - { - LogPrint (eLogError, "Garlic: Can't decode elligator"); - return false; + bool ECIESX25519AEADRatchetSession::GenerateEphemeralKeysAndEncode (uint8_t * buf) + { + for (int i = 0; i < 10; i++) + { + m_EphemeralKeys.GenerateKeys (); + if (i2p::crypto::GetElligator ()->Encode (m_EphemeralKeys.GetPublicKey (), buf)) + return true; // success } - buf += 32; len -= 32; - MixHash (m_Aepk, 32); // h = SHA256(h || aepk) - - uint8_t sharedSecret[32]; + return false; + } + + std::shared_ptr ECIESX25519AEADRatchetSession::CreateNewSessionTagset () + { + uint8_t tagsetKey[32]; + i2p::crypto::HKDF (m_CK, nullptr, 0, "SessionReplyTags", tagsetKey, 32); // tagsetKey = HKDF(chainKey, ZEROLEN, "SessionReplyTags", 32) + // Session Tag Ratchet + auto tagsetNsr = std::make_shared(shared_from_this ()); + tagsetNsr->DHInitialize (m_CK, tagsetKey); // tagset_nsr = DH_INITIALIZE(chainKey, tagsetKey) + tagsetNsr->NextSessionTagRatchet (); + return tagsetNsr; + } + + bool ECIESX25519AEADRatchetSession::HandleNewIncomingSession (const uint8_t * buf, size_t len) + { + if (!GetOwner ()) return false; + // we are Bob + // KDF1 + MixHash (GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET), 32); // h = SHA256(h || bpk) + + if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk)) + { + LogPrint (eLogError, "Garlic: Can't decode elligator"); + return false; + } + buf += 32; len -= 32; + MixHash (m_Aepk, 32); // h = SHA256(h || aepk) + + uint8_t sharedSecret[32]; GetOwner ()->Decrypt (m_Aepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519(bsk, aepk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) - - // decrypt flags/static + + // decrypt flags/static uint8_t nonce[12], fs[32]; CreateNonce (0, nonce); if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 32, m_H, 32, m_CK + 32, nonce, fs, 32, false)) // decrypt @@ -179,17 +179,17 @@ namespace garlic buf += 48; len -= 48; // 32 data + 16 poly // KDF2 for payload - bool isStatic = !i2p::data::Tag<32> (fs).IsZero (); + bool isStatic = !i2p::data::Tag<32> (fs).IsZero (); if (isStatic) { // static key, fs is apk - memcpy (m_RemoteStaticKey, fs, 32); + memcpy (m_RemoteStaticKey, fs, 32); GetOwner ()->Decrypt (fs, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519(bsk, apk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) } else // all zeros flags CreateNonce (1, nonce); - + // decrypt payload std::vector payload (len - 16); // we must save original ciphertext if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt @@ -198,17 +198,17 @@ namespace garlic return false; } if (isStatic) MixHash (buf, len); // h = SHA256(h || ciphertext) - m_State = eSessionStateNewSessionReceived; + m_State = eSessionStateNewSessionReceived; GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); - HandlePayload (payload.data (), len - 16, nullptr, 0); + HandlePayload (payload.data (), len - 16, nullptr, 0); - return true; - } + return true; + } - void ECIESX25519AEADRatchetSession::HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index) - { - size_t offset = 0; + void ECIESX25519AEADRatchetSession::HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index) + { + size_t offset = 0; while (offset < len) { uint8_t blk = buf[offset]; @@ -228,48 +228,48 @@ namespace garlic GetOwner ()->HandleECIESx25519GarlicClove (buf + offset, size); break; case eECIESx25519BlkNextKey: - LogPrint (eLogDebug, "Garlic: next key"); + LogPrint (eLogDebug, "Garlic: next key"); HandleNextKey (buf + offset, size, receiveTagset); break; case eECIESx25519BlkAck: - { + { LogPrint (eLogDebug, "Garlic: ack"); int numAcks = size >> 2; // /4 - auto offset1 = offset; + auto offset1 = offset; for (auto i = 0; i < numAcks; i++) - { + { offset1 += 2; // tagsetid MessageConfirmed (bufbe16toh (buf + offset1)); offset1 += 2; // N } break; - } + } case eECIESx25519BlkAckRequest: - { + { LogPrint (eLogDebug, "Garlic: ack request"); - m_AckRequests.push_back ({receiveTagset->GetTagSetID (), index}); - break; - } + m_AckRequests.push_back ({receiveTagset->GetTagSetID (), index}); + break; + } case eECIESx25519BlkTermination: LogPrint (eLogDebug, "Garlic: termination"); if (GetOwner ()) GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey); if (receiveTagset) receiveTagset->Expire (); - break; + break; case eECIESx25519BlkDateTime: LogPrint (eLogDebug, "Garlic: datetime"); - break; + break; case eECIESx25519BlkOptions: LogPrint (eLogDebug, "Garlic: options"); break; case eECIESx25519BlkPadding: LogPrint (eLogDebug, "Garlic: padding"); - break; + break; default: LogPrint (eLogWarning, "Garlic: Unknown block type ", (int)blk); } offset += size; } - } + } void ECIESX25519AEADRatchetSession::HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset) { @@ -279,7 +279,7 @@ namespace garlic if (!m_SendForwardKey || !m_NextSendRatchet) return; uint16_t keyID = bufbe16toh (buf); buf += 2; // keyID if (((!m_NextSendRatchet->newKey || !m_NextSendRatchet->keyID) && keyID == m_NextSendRatchet->keyID) || - (m_NextSendRatchet->newKey && keyID == m_NextSendRatchet->keyID -1)) + (m_NextSendRatchet->newKey && keyID == m_NextSendRatchet->keyID -1)) { if (flag & ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG) memcpy (m_NextSendRatchet->remote, buf, 32); @@ -288,15 +288,15 @@ namespace garlic i2p::crypto::HKDF (sharedSecret, nullptr, 0, "XDHRatchetTagSet", tagsetKey, 32); // tagsetKey = HKDF(sharedSecret, ZEROLEN, "XDHRatchetTagSet", 32) auto newTagset = std::make_shared (shared_from_this ()); newTagset->SetTagSetID (1 + m_NextSendRatchet->keyID + keyID); - newTagset->DHInitialize (m_SendTagset->GetNextRootKey (), tagsetKey); + newTagset->DHInitialize (m_SendTagset->GetNextRootKey (), tagsetKey); newTagset->NextSessionTagRatchet (); - m_SendTagset = newTagset; + m_SendTagset = newTagset; m_SendForwardKey = false; LogPrint (eLogDebug, "Garlic: next send tagset ", newTagset->GetTagSetID (), " created"); } else LogPrint (eLogDebug, "Garlic: Unexpected next key ", keyID); - } + } else { uint16_t keyID = bufbe16toh (buf); buf += 2; // keyID @@ -305,38 +305,38 @@ namespace garlic if (!m_NextReceiveRatchet) m_NextReceiveRatchet.reset (new DHRatchet ()); else - { - if (keyID == m_NextReceiveRatchet->keyID && newKey == m_NextReceiveRatchet->newKey) + { + if (keyID == m_NextReceiveRatchet->keyID && newKey == m_NextReceiveRatchet->newKey) { LogPrint (eLogDebug, "Garlic: Duplicate ", newKey ? "new" : "old", " key ", keyID, " received"); return; - } + } m_NextReceiveRatchet->keyID = keyID; } int tagsetID = 2*keyID; if (newKey) - { + { m_NextReceiveRatchet->key.GenerateKeys (); m_NextReceiveRatchet->newKey = true; tagsetID++; - } + } else m_NextReceiveRatchet->newKey = false; if (flag & ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG) memcpy (m_NextReceiveRatchet->remote, buf, 32); - + uint8_t sharedSecret[32], tagsetKey[32]; m_NextReceiveRatchet->key.Agree (m_NextReceiveRatchet->remote, sharedSecret); i2p::crypto::HKDF (sharedSecret, nullptr, 0, "XDHRatchetTagSet", tagsetKey, 32); // tagsetKey = HKDF(sharedSecret, ZEROLEN, "XDHRatchetTagSet", 32) - auto newTagset = std::make_shared(shared_from_this ()); - newTagset->SetTagSetID (tagsetID); - newTagset->DHInitialize (receiveTagset->GetNextRootKey (), tagsetKey); + auto newTagset = std::make_shared(shared_from_this ()); + newTagset->SetTagSetID (tagsetID); + newTagset->DHInitialize (receiveTagset->GetNextRootKey (), tagsetKey); newTagset->NextSessionTagRatchet (); - GenerateMoreReceiveTags (newTagset, ECIESX25519_MAX_NUM_GENERATED_TAGS); + GenerateMoreReceiveTags (newTagset, ECIESX25519_MAX_NUM_GENERATED_TAGS); receiveTagset->Expire (); LogPrint (eLogDebug, "Garlic: next receive tagset ", tagsetID, " created"); - } - } + } + } void ECIESX25519AEADRatchetSession::NewNextSendRatchet () { @@ -346,49 +346,49 @@ namespace garlic { m_NextSendRatchet->keyID++; m_NextSendRatchet->newKey = true; - } + } else m_NextSendRatchet->newKey = false; - } + } else m_NextSendRatchet.reset (new DHRatchet ()); if (m_NextSendRatchet->newKey) m_NextSendRatchet->key.GenerateKeys (); - + m_SendForwardKey = true; LogPrint (eLogDebug, "Garlic: new send ratchet ", m_NextSendRatchet->newKey ? "new" : "old", " key ", m_NextSendRatchet->keyID, " created"); - } - - bool ECIESX25519AEADRatchetSession::NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) - { - ResetKeys (); - // we are Alice, bpk is m_RemoteStaticKey - size_t offset = 0; - if (!GenerateEphemeralKeysAndEncode (out + offset)) - { - LogPrint (eLogError, "Garlic: Can't encode elligator"); - return false; - } - offset += 32; + } - // KDF1 - MixHash (m_RemoteStaticKey, 32); // h = SHA256(h || bpk) - MixHash (m_EphemeralKeys.GetPublicKey (), 32); // h = SHA256(h || aepk) - uint8_t sharedSecret[32]; + bool ECIESX25519AEADRatchetSession::NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) + { + ResetKeys (); + // we are Alice, bpk is m_RemoteStaticKey + size_t offset = 0; + if (!GenerateEphemeralKeysAndEncode (out + offset)) + { + LogPrint (eLogError, "Garlic: Can't encode elligator"); + return false; + } + offset += 32; + + // KDF1 + MixHash (m_RemoteStaticKey, 32); // h = SHA256(h || bpk) + MixHash (m_EphemeralKeys.GetPublicKey (), 32); // h = SHA256(h || aepk) + uint8_t sharedSecret[32]; m_EphemeralKeys.Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) - // encrypt static key section - uint8_t nonce[12]; + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + // encrypt static key section + uint8_t nonce[12]; CreateNonce (0, nonce); if (!i2p::crypto::AEADChaCha20Poly1305 (GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET), 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Static section AEAD encryption failed "); return false; } - MixHash (out + offset, 48); // h = SHA256(h || ciphertext) - offset += 48; - // KDF2 - GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bpk) + MixHash (out + offset, 48); // h = SHA256(h || ciphertext) + offset += 48; + // KDF2 + GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bpk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) // encrypt payload if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt @@ -399,169 +399,169 @@ namespace garlic MixHash (out + offset, len + 16); // h = SHA256(h || ciphertext) m_State = eSessionStateNewSessionSent; - if (GetOwner ()) + if (GetOwner ()) GenerateMoreReceiveTags (CreateNewSessionTagset (), ECIESX25519_NSR_NUM_GENERATED_TAGS); - return true; - } + return true; + } - bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) - { - // we are Bob + bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) + { + // we are Bob m_NSRTagset = CreateNewSessionTagset (); - uint64_t tag = m_NSRTagset->GetNextSessionTag (); - - size_t offset = 0; - memcpy (out + offset, &tag, 8); - offset += 8; - if (!GenerateEphemeralKeysAndEncode (out + offset)) // bepk - { + uint64_t tag = m_NSRTagset->GetNextSessionTag (); + + size_t offset = 0; + memcpy (out + offset, &tag, 8); + offset += 8; + if (!GenerateEphemeralKeysAndEncode (out + offset)) // bepk + { LogPrint (eLogError, "Garlic: Can't encode elligator"); - return false; + return false; } memcpy (m_NSREncodedKey, out + offset, 56); // for possible next NSR memcpy (m_NSRH, m_H, 32); - offset += 32; - // KDF for Reply Key Section - MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) - MixHash (m_EphemeralKeys.GetPublicKey (), 32); // h = SHA256(h || bepk) - uint8_t sharedSecret[32]; - m_EphemeralKeys.Agree (m_Aepk, sharedSecret); // sharedSecret = x25519(besk, aepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) - m_EphemeralKeys.Agree (m_RemoteStaticKey, sharedSecret); // sharedSecret = x25519(besk, apk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + offset += 32; + // KDF for Reply Key Section + MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) + MixHash (m_EphemeralKeys.GetPublicKey (), 32); // h = SHA256(h || bepk) + uint8_t sharedSecret[32]; + m_EphemeralKeys.Agree (m_Aepk, sharedSecret); // sharedSecret = x25519(besk, aepk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) + m_EphemeralKeys.Agree (m_RemoteStaticKey, sharedSecret); // sharedSecret = x25519(besk, apk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) uint8_t nonce[12]; CreateNonce (0, nonce); - // calulate hash for zero length + // calculate hash for zero length if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + offset, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) { LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); return false; } - MixHash (out + offset, 16); // h = SHA256(h || ciphertext) - offset += 16; - // KDF for payload - uint8_t keydata[64]; - i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) + MixHash (out + offset, 16); // h = SHA256(h || ciphertext) + offset += 16; + // KDF for payload + uint8_t keydata[64]; + i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) // k_ab = keydata[0:31], k_ba = keydata[32:63] auto receiveTagset = std::make_shared(shared_from_this ()); - receiveTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) + receiveTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) receiveTagset->NextSessionTagRatchet (); m_SendTagset = std::make_shared(shared_from_this ()); - m_SendTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) - m_SendTagset->NextSessionTagRatchet (); + m_SendTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) + m_SendTagset->NextSessionTagRatchet (); GenerateMoreReceiveTags (receiveTagset, ECIESX25519_MIN_NUM_GENERATED_TAGS); - i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", m_NSRKey, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) - // encrypt payload - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset, len + 16, true)) // encrypt + i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", m_NSRKey, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) + // encrypt payload + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: NSR payload section AEAD encryption failed"); return false; } m_State = eSessionStateNewSessionReplySent; - - return true; - } + + return true; + } bool ECIESX25519AEADRatchetSession::NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) - { - // we are Bob and sent NSR already - uint64_t tag = m_NSRTagset->GetNextSessionTag (); // next tag - memcpy (out, &tag, 8); + { + // we are Bob and sent NSR already + uint64_t tag = m_NSRTagset->GetNextSessionTag (); // next tag + memcpy (out, &tag, 8); memcpy (out + 8, m_NSREncodedKey, 32); - // recalculte h with new tag + // recalculate h with new tag memcpy (m_H, m_NSRH, 32); MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) - MixHash (m_EphemeralKeys.GetPublicKey (), 32); // h = SHA256(h || bepk) + MixHash (m_EphemeralKeys.GetPublicKey (), 32); // h = SHA256(h || bepk) uint8_t nonce[12]; - CreateNonce (0, nonce); + CreateNonce (0, nonce); if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + 40, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) { LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); return false; } - MixHash (out + 40, 16); // h = SHA256(h || ciphertext) + MixHash (out + 40, 16); // h = SHA256(h || ciphertext) // encrypt payload - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + 56, len + 16, true)) // encrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + 56, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Next NSR payload section AEAD encryption failed"); return false; } return true; - } - - bool ECIESX25519AEADRatchetSession::HandleNewOutgoingSessionReply (uint8_t * buf, size_t len) - { + } + + bool ECIESX25519AEADRatchetSession::HandleNewOutgoingSessionReply (uint8_t * buf, size_t len) + { // we are Alice LogPrint (eLogDebug, "Garlic: reply received"); const uint8_t * tag = buf; buf += 8; len -= 8; // tag - uint8_t bepk[32]; // Bob's ephemeral key + uint8_t bepk[32]; // Bob's ephemeral key if (!i2p::crypto::GetElligator ()->Decode (buf, bepk)) - { + { LogPrint (eLogError, "Garlic: Can't decode elligator"); - return false; - } + return false; + } buf += 32; len -= 32; - // KDF for Reply Key Section + // KDF for Reply Key Section uint8_t h[32]; memcpy (h, m_H, 32); // save m_H - MixHash (tag, 8); // h = SHA256(h || tag) - MixHash (bepk, 32); // h = SHA256(h || bepk) - uint8_t sharedSecret[32]; + MixHash (tag, 8); // h = SHA256(h || tag) + MixHash (bepk, 32); // h = SHA256(h || bepk) + uint8_t sharedSecret[32]; if (m_State == eSessionStateNewSessionSent) - { + { // only fist time, we assume ephemeral keys the same - m_EphemeralKeys.Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) + m_EphemeralKeys.Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bepk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) - } + } uint8_t nonce[12]; CreateNonce (0, nonce); - // calulate hash for zero length - if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 0, m_H, 32, m_CK + 32, nonce, sharedSecret/* can be anyting */, 0, false)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only + // calculate hash for zero length + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, 0, m_H, 32, m_CK + 32, nonce, sharedSecret/* can be anything */, 0, false)) // decrypt, DECRYPT(k, n, ZEROLEN, ad) verification only { LogPrint (eLogWarning, "Garlic: Reply key section AEAD decryption failed"); return false; } - MixHash (buf, 16); // h = SHA256(h || ciphertext) + MixHash (buf, 16); // h = SHA256(h || ciphertext) buf += 16; len -= 16; // KDF for payload - uint8_t keydata[64]; - i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) + uint8_t keydata[64]; + i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) if (m_State == eSessionStateNewSessionSent) - { - // k_ab = keydata[0:31], k_ba = keydata[32:63] + { + // k_ab = keydata[0:31], k_ba = keydata[32:63] m_SendTagset = std::make_shared(shared_from_this ()); - m_SendTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) + m_SendTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) m_SendTagset->NextSessionTagRatchet (); auto receiveTagset = std::make_shared(shared_from_this ()); - receiveTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) + receiveTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) receiveTagset->NextSessionTagRatchet (); GenerateMoreReceiveTags (receiveTagset, ECIESX25519_MIN_NUM_GENERATED_TAGS); - } - i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) + } + i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) // decrypt payload - if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, keydata, nonce, buf, len - 16, false)) // decrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, keydata, nonce, buf, len - 16, false)) // decrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); return false; } if (m_State == eSessionStateNewSessionSent) - { + { m_State = eSessionStateEstablished; GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); } - memcpy (m_H, h, 32); // restore m_H - HandlePayload (buf, len - 16, nullptr, 0); + memcpy (m_H, h, 32); // restore m_H + HandlePayload (buf, len - 16, nullptr, 0); // we have received reply to NS with LeaseSet in it SetLeaseSetUpdateStatus (eLeaseSetUpToDate); SetLeaseSetUpdateMsgID (0); - - return true; - } + + return true; + } bool ECIESX25519AEADRatchetSession::NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { @@ -578,18 +578,18 @@ namespace garlic { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return false; - } + } if (index >= ECIESX25519_TAGSET_MAX_NUM_TAGS && !m_SendForwardKey) - NewNextSendRatchet (); + NewNextSendRatchet (); return true; } - bool ECIESX25519AEADRatchetSession::HandleExistingSessionMessage (uint8_t * buf, size_t len, + bool ECIESX25519AEADRatchetSession::HandleExistingSessionMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index) { uint8_t nonce[12]; CreateNonce (index, nonce); // tag's index - len -= 8; // tag + len -= 8; // tag uint8_t * payload = buf + 8; uint8_t key[32]; receiveTagset->GetSymmKey (index, key); @@ -597,17 +597,17 @@ namespace garlic { LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); return false; - } - HandlePayload (payload, len - 16, receiveTagset, index); + } + HandlePayload (payload, len - 16, receiveTagset, index); int moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4 if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; - moreTags -= (receiveTagset->GetNextIndex () - index); + moreTags -= (receiveTagset->GetNextIndex () - index); if (moreTags > 0) - GenerateMoreReceiveTags (receiveTagset, moreTags); + GenerateMoreReceiveTags (receiveTagset, moreTags); return true; } - bool ECIESX25519AEADRatchetSession::HandleNextMessage (uint8_t * buf, size_t len, + bool ECIESX25519AEADRatchetSession::HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index) { m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); @@ -617,8 +617,8 @@ namespace garlic m_State = eSessionStateEstablished; m_NSRTagset = nullptr; #if (__cplusplus >= 201703L) // C++ 17 or higher - [[fallthrough]]; -#endif + [[fallthrough]]; +#endif case eSessionStateEstablished: if (HandleExistingSessionMessage (buf, len, receiveTagset, index)) return true; // check NSR just in case @@ -637,77 +637,77 @@ namespace garlic return true; } - std::shared_ptr ECIESX25519AEADRatchetSession::WrapSingleMessage (std::shared_ptr msg) - { - auto payload = CreatePayload (msg, m_State != eSessionStateEstablished); - size_t len = payload.size (); + std::shared_ptr ECIESX25519AEADRatchetSession::WrapSingleMessage (std::shared_ptr msg) + { + auto payload = CreatePayload (msg, m_State != eSessionStateEstablished); + size_t len = payload.size (); if (!len) return nullptr; auto m = NewI2NPMessage (len + 100); // 96 + 4 m->Align (12); // in order to get buf aligned to 16 (12 + 4) uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length - - switch (m_State) - { + + switch (m_State) + { case eSessionStateEstablished: if (!NewExistingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) return nullptr; len += 24; - break; - case eSessionStateNew: - if (!NewOutgoingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) - return nullptr; - len += 96; - break; - case eSessionStateNewSessionReceived: - if (!NewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) - return nullptr; - len += 72; - break; + break; + case eSessionStateNew: + if (!NewOutgoingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) + return nullptr; + len += 96; + break; + case eSessionStateNewSessionReceived: + if (!NewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) + return nullptr; + len += 72; + break; case eSessionStateNewSessionReplySent: if (!NextNewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) - return nullptr; - len += 72; - break; - default: - return nullptr; - } - - htobe32buf (m->GetPayload (), len); + return nullptr; + len += 72; + break; + default: + return nullptr; + } + + htobe32buf (m->GetPayload (), len); m->len += len + 4; m->FillI2NPMessageHeader (eI2NPGarlic); return m; - } + } - std::vector ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr msg, bool first) - { + std::vector ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr msg, bool first) + { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); - size_t payloadLen = 0; - if (first) payloadLen += 7;// datatime - if (msg && m_Destination) - payloadLen += msg->GetPayloadLength () + 13 + 32; + size_t payloadLen = 0; + if (first) payloadLen += 7;// datatime + if (msg && m_Destination) + payloadLen += msg->GetPayloadLength () + 13 + 32; auto leaseSet = (GetLeaseSetUpdateStatus () == eLeaseSetUpdated || - (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && - ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT)) ? + (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && + ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT)) ? GetOwner ()->GetLeaseSet () : nullptr; if (leaseSet) - { - payloadLen += leaseSet->GetBufferLen () + DATABASE_STORE_HEADER_SIZE + 13; - if (!first) + { + payloadLen += leaseSet->GetBufferLen () + DATABASE_STORE_HEADER_SIZE + 13; + if (!first) { // ack request SetLeaseSetUpdateStatus (eLeaseSetSubmitted); SetLeaseSetUpdateMsgID (m_SendTagset->GetNextIndex ()); SetLeaseSetSubmissionTime (ts); - payloadLen += 4; - } - } + payloadLen += 4; + } + } if (m_AckRequests.size () > 0) payloadLen += m_AckRequests.size ()*4 + 3; if (m_SendReverseKey) - { + { payloadLen += 6; if (m_NextReceiveRatchet->newKey) payloadLen += 32; - } + } if (m_SendForwardKey) { payloadLen += 6; @@ -715,110 +715,110 @@ namespace garlic } uint8_t paddingSize = 0; if (payloadLen) - { + { int delta = (int)ECIESX25519_OPTIMAL_PAYLOAD_SIZE - (int)payloadLen; if (delta < 0 || delta > 3) // don't create padding if we are close to optimal size { RAND_bytes (&paddingSize, 1); paddingSize &= 0x0F; // 0 - 15 if (delta > 3) - { + { delta -= 3; - if (paddingSize >= delta) paddingSize %= delta; - } + if (paddingSize >= delta) paddingSize %= delta; + } paddingSize++; - payloadLen += paddingSize + 3; - } - } - std::vector v(payloadLen); - size_t offset = 0; - // DateTime + payloadLen += paddingSize + 3; + } + } + std::vector v(payloadLen); + size_t offset = 0; + // DateTime if (first) - { - v[offset] = eECIESx25519BlkDateTime; offset++; - htobe16buf (v.data () + offset, 4); offset += 2; - htobe32buf (v.data () + offset, ts/1000); offset += 4; // in seconds - } - // LeaseSet - if (leaseSet) - { - offset += CreateLeaseSetClove (leaseSet, ts, v.data () + offset, payloadLen - offset); + { + v[offset] = eECIESx25519BlkDateTime; offset++; + htobe16buf (v.data () + offset, 4); offset += 2; + htobe32buf (v.data () + offset, ts/1000); offset += 4; // in seconds + } + // LeaseSet + if (leaseSet) + { + offset += CreateLeaseSetClove (leaseSet, ts, v.data () + offset, payloadLen - offset); if (!first) - { + { // ack request v[offset] = eECIESx25519BlkAckRequest; offset++; htobe16buf (v.data () + offset, 1); offset += 2; v[offset] = 0; offset++; // flags - } - } - // msg - if (msg && m_Destination) - offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset, true); + } + } + // msg + if (msg && m_Destination) + offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset, true); // ack if (m_AckRequests.size () > 0) { v[offset] = eECIESx25519BlkAck; offset++; - htobe16buf (v.data () + offset, m_AckRequests.size ()*4); offset += 2; + htobe16buf (v.data () + offset, m_AckRequests.size () * 4); offset += 2; for (auto& it: m_AckRequests) { htobe16buf (v.data () + offset, it.first); offset += 2; htobe16buf (v.data () + offset, it.second); offset += 2; - } + } m_AckRequests.clear (); - } + } // next keys if (m_SendReverseKey) { v[offset] = eECIESx25519BlkNextKey; offset++; htobe16buf (v.data () + offset, m_NextReceiveRatchet->newKey ? 35 : 3); offset += 2; - v[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; + v[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; int keyID = m_NextReceiveRatchet->keyID - 1; - if (m_NextReceiveRatchet->newKey) - { + if (m_NextReceiveRatchet->newKey) + { v[offset] |= ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG; keyID++; - } + } offset++; // flag htobe16buf (v.data () + offset, keyID); offset += 2; // keyid if (m_NextReceiveRatchet->newKey) - { - memcpy (v.data () + offset, m_NextReceiveRatchet->key.GetPublicKey (), 32); + { + memcpy (v.data () + offset, m_NextReceiveRatchet->key.GetPublicKey (), 32); offset += 32; // public key - } + } m_SendReverseKey = false; - } + } if (m_SendForwardKey) { v[offset] = eECIESx25519BlkNextKey; offset++; htobe16buf (v.data () + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; - v[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; + v[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; if (!m_NextSendRatchet->keyID) v[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only offset++; // flag htobe16buf (v.data () + offset, m_NextSendRatchet->keyID); offset += 2; // keyid if (m_NextSendRatchet->newKey) - { - memcpy (v.data () + offset, m_NextSendRatchet->key.GetPublicKey (), 32); + { + memcpy (v.data () + offset, m_NextSendRatchet->key.GetPublicKey (), 32); offset += 32; // public key - } - } - // padding + } + } + // padding if (paddingSize) - { - v[offset] = eECIESx25519BlkPadding; offset++; - htobe16buf (v.data () + offset, paddingSize); offset += 2; - memset (v.data () + offset, 0, paddingSize); offset += paddingSize; - } - return v; - } + { + v[offset] = eECIESx25519BlkPadding; offset++; + htobe16buf (v.data () + offset, paddingSize); offset += 2; + memset (v.data () + offset, 0, paddingSize); offset += paddingSize; + } + return v; + } - size_t ECIESX25519AEADRatchetSession::CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len, bool isDestination) - { - if (!msg) return 0; - uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; + size_t ECIESX25519AEADRatchetSession::CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len, bool isDestination) + { + if (!msg) return 0; + uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; if (isDestination) cloveSize += 32; - if ((int)len < cloveSize + 3) return 0; - buf[0] = eECIESx25519BlkGalicClove; // clove type - htobe16buf (buf + 1, cloveSize); // size + if ((int)len < cloveSize + 3) return 0; + buf[0] = eECIESx25519BlkGalicClove; // clove type + htobe16buf (buf + 1, cloveSize); // size buf += 3; if (isDestination) { @@ -826,88 +826,86 @@ namespace garlic memcpy (buf + 1, *m_Destination, 32); buf += 32; } else - *buf = 0; + *buf = 0; buf++; // flag and delivery instructions - *buf = msg->GetTypeID (); // I2NP msg type - htobe32buf (buf + 1, msg->GetMsgID ()); // msgID - htobe32buf (buf + 5, msg->GetExpiration ()/1000); // expiration in seconds - memcpy (buf + 9, msg->GetPayload (), msg->GetPayloadLength ()); - return cloveSize + 3; - } + *buf = msg->GetTypeID (); // I2NP msg type + htobe32buf (buf + 1, msg->GetMsgID ()); // msgID + htobe32buf (buf + 5, msg->GetExpiration () / 1000); // expiration in seconds + memcpy (buf + 9, msg->GetPayload (), msg->GetPayloadLength ()); + return cloveSize + 3; + } size_t ECIESX25519AEADRatchetSession::CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len) - { + { if (!ls || ls->GetStoreType () != i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) { LogPrint (eLogError, "Garlic: Incorrect LeasetSet type to send"); return 0; - } + } uint16_t cloveSize = 1 + 9 + DATABASE_STORE_HEADER_SIZE + ls->GetBufferLen (); // to local if ((int)len < cloveSize + 3) return 0; buf[0] = eECIESx25519BlkGalicClove; // clove type - htobe16buf (buf + 1, cloveSize); // size + htobe16buf (buf + 1, cloveSize); // size buf += 3; *buf = 0; buf++; // flag and delivery instructions *buf = eI2NPDatabaseStore; buf++; // I2NP msg type RAND_bytes (buf, 4); buf += 4; // msgID - htobe32buf (buf, (ts + I2NP_MESSAGE_EXPIRATION_TIMEOUT)/1000); buf += 4; // expiration + htobe32buf (buf, (ts + I2NP_MESSAGE_EXPIRATION_TIMEOUT)/1000); buf += 4; // expiration // payload memcpy (buf + DATABASE_STORE_KEY_OFFSET, ls->GetStoreHash (), 32); buf[DATABASE_STORE_TYPE_OFFSET] = i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2; - memset (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0, 4); // replyToken = 0 + memset (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0, 4); // replyToken = 0 buf += DATABASE_STORE_HEADER_SIZE; memcpy (buf, ls->GetBuffer (), ls->GetBufferLen ()); return cloveSize + 3; } - + void ECIESX25519AEADRatchetSession::GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags) { for (int i = 0; i < numTags; i++) GetOwner ()->AddECIESx25519SessionNextTag (receiveTagset); - } + } bool ECIESX25519AEADRatchetSession::CheckExpired (uint64_t ts) - { + { CleanupUnconfirmedLeaseSet (ts); - return ts > m_LastActivityTimestamp + ECIESX25519_EXPIRATION_TIMEOUT; - } - + return ts > m_LastActivityTimestamp + ECIESX25519_EXPIRATION_TIMEOUT; + } + std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag) { auto m = NewI2NPMessage (); m->Align (12); // in order to get buf aligned to 16 (12 + 4) uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length uint8_t nonce[12]; - memset (nonce, 0, 12); // n = 0 + memset (nonce, 0, 12); // n = 0 size_t offset = 0; memcpy (buf + offset, &tag, 8); offset += 8; auto payload = buf + offset; uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; size_t len = cloveSize + 3; - payload[0] = eECIESx25519BlkGalicClove; // clove type - htobe16buf (payload + 1, cloveSize); // size + payload[0] = eECIESx25519BlkGalicClove; // clove type + htobe16buf (payload + 1, cloveSize); // size payload += 3; - *payload = 0; payload++; // flag and delivery instructions - *payload = msg->GetTypeID (); // I2NP msg type - htobe32buf (payload + 1, msg->GetMsgID ()); // msgID - htobe32buf (payload + 5, msg->GetExpiration ()/1000); // expiration in seconds - memcpy (payload + 9, msg->GetPayload (), msg->GetPayloadLength ()); + *payload = 0; payload++; // flag and delivery instructions + *payload = msg->GetTypeID (); // I2NP msg type + htobe32buf (payload + 1, msg->GetMsgID ()); // msgID + htobe32buf (payload + 5, msg->GetExpiration () / 1000); // expiration in seconds + memcpy (payload + 9, msg->GetPayload (), msg->GetPayloadLength ()); if (!i2p::crypto::AEADChaCha20Poly1305 (buf + offset, len, buf, 8, key, nonce, buf + offset, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return nullptr; - } + } offset += len + 16; - + htobe32buf (m->GetPayload (), offset); m->len += offset + 4; m->FillI2NPMessageHeader (eI2NPGarlic); return m; } - + } } - - diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 14f97ce9..d5a83f89 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -25,22 +25,22 @@ namespace garlic const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 160; const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; - - const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */ - // - 16 /* I2NP header */ - 16 /* poly hash */ - 8 /* tag */ - 4 /* garlic length */ - - class ECIESX25519AEADRatchetSession; - class RatchetTagSet - { - public: - RatchetTagSet (std::shared_ptr session): m_Session (session) {}; - - void DHInitialize (const uint8_t * rootKey, const uint8_t * k); - void NextSessionTagRatchet (); - uint64_t GetNextSessionTag (); + const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */ + // - 16 /* I2NP header */ - 16 /* poly hash */ - 8 /* tag */ - 4 /* garlic length */ + + class ECIESX25519AEADRatchetSession; + class RatchetTagSet + { + public: + + RatchetTagSet (std::shared_ptr session): m_Session (session) {}; + + void DHInitialize (const uint8_t * rootKey, const uint8_t * k); + void NextSessionTagRatchet (); + uint64_t GetNextSessionTag (); const uint8_t * GetNextRootKey () const { return m_NextRootKey; }; - int GetNextIndex () const { return m_NextIndex; }; + int GetNextIndex () const { return m_NextIndex; }; void GetSymmKey (int index, uint8_t * key); std::shared_ptr GetSession () { return m_Session.lock (); }; @@ -49,54 +49,54 @@ namespace garlic void Expire (); bool IsExpired (uint64_t ts) const { return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; }; - + private: - - union - { - uint64_t ll[8]; - uint8_t buf[64]; - const uint8_t * GetSessTagCK () const { return buf; }; // sessTag_chainKey = keydata[0:31] - const uint8_t * GetSessTagConstant () const { return buf + 32; }; // SESSTAG_CONSTANT = keydata[32:63] - uint64_t GetTag () const { return ll[4]; }; // tag = keydata[32:39] - - } m_KeyData; - uint8_t m_SessTagConstant[32], m_SymmKeyCK[32], m_CurrentSymmKeyCK[64], m_NextRootKey[32]; - int m_NextIndex, m_NextSymmKeyIndex; - std::unordered_map > m_ItermediateSymmKeys; - std::weak_ptr m_Session; - int m_TagSetID = 0; - uint64_t m_ExpirationTimestamp = 0; - }; + union + { + uint64_t ll[8]; + uint8_t buf[64]; - enum ECIESx25519BlockType + const uint8_t * GetSessTagCK () const { return buf; }; // sessTag_chainKey = keydata[0:31] + const uint8_t * GetSessTagConstant () const { return buf + 32; }; // SESSTAG_CONSTANT = keydata[32:63] + uint64_t GetTag () const { return ll[4]; }; // tag = keydata[32:39] + + } m_KeyData; + uint8_t m_SessTagConstant[32], m_SymmKeyCK[32], m_CurrentSymmKeyCK[64], m_NextRootKey[32]; + int m_NextIndex, m_NextSymmKeyIndex; + std::unordered_map > m_ItermediateSymmKeys; + std::weak_ptr m_Session; + int m_TagSetID = 0; + uint64_t m_ExpirationTimestamp = 0; + }; + + enum ECIESx25519BlockType { - eECIESx25519BlkDateTime = 0, - eECIESx25519BlkSessionID = 1, + eECIESx25519BlkDateTime = 0, + eECIESx25519BlkSessionID = 1, eECIESx25519BlkTermination = 4, - eECIESx25519BlkOptions = 5, - eECIESx25519BlkNextKey = 7, - eECIESx25519BlkAck = 8, - eECIESx25519BlkAckRequest = 9, - eECIESx25519BlkGalicClove = 11, - eECIESx25519BlkPadding = 254 - }; + eECIESx25519BlkOptions = 5, + eECIESx25519BlkNextKey = 7, + eECIESx25519BlkAck = 8, + eECIESx25519BlkAckRequest = 9, + eECIESx25519BlkGalicClove = 11, + eECIESx25519BlkPadding = 254 + }; const uint8_t ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG = 0x01; const uint8_t ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG = 0x02; const uint8_t ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG = 0x04; - - class ECIESX25519AEADRatchetSession: public GarlicRoutingSession, public std::enable_shared_from_this - { - enum SessionState - { - eSessionStateNew =0, - eSessionStateNewSessionReceived, + + class ECIESX25519AEADRatchetSession: public GarlicRoutingSession, public std::enable_shared_from_this + { + enum SessionState + { + eSessionStateNew = 0, + eSessionStateNewSessionReceived, eSessionStateNewSessionSent, eSessionStateNewSessionReplySent, - eSessionStateEstablished - }; + eSessionStateEstablished + }; struct DHRatchet { @@ -104,65 +104,65 @@ namespace garlic i2p::crypto::X25519Keys key; uint8_t remote[32]; // last remote public key bool newKey = true; - }; - - public: + }; - ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet); - ~ECIESX25519AEADRatchetSession (); + public: + + ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet); + ~ECIESX25519AEADRatchetSession (); bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); - std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - - const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } + std::shared_ptr WrapSingleMessage (std::shared_ptr msg); + + const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } void SetDestination (const i2p::data::IdentHash& dest) // TODO: { if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest)); } - + bool CheckExpired (uint64_t ts); // true is expired bool CanBeRestarted (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_RESTART_TIMEOUT; } bool IsRatchets () const { return true; }; - - private: + + private: void ResetKeys (); - void MixHash (const uint8_t * buf, size_t len); + void MixHash (const uint8_t * buf, size_t len); void CreateNonce (uint64_t seqn, uint8_t * nonce); - bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes + bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes std::shared_ptr CreateNewSessionTagset (); bool HandleNewIncomingSession (const uint8_t * buf, size_t len); - bool HandleNewOutgoingSessionReply (uint8_t * buf, size_t len); + bool HandleNewOutgoingSessionReply (uint8_t * buf, size_t len); bool HandleExistingSessionMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index); - void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index); + void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index); void HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset); - - bool NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); + + bool NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); + bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - - std::vector CreatePayload (std::shared_ptr msg, bool first); - size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len, bool isDestination = false); + + std::vector CreatePayload (std::shared_ptr msg, bool first); + size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len, bool isDestination = false); size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); - + void GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags); void NewNextSendRatchet (); - - private: - uint8_t m_H[32], m_CK[64] /* [chainkey, key] */, m_RemoteStaticKey[32]; + private: + + uint8_t m_H[32], m_CK[64] /* [chainkey, key] */, m_RemoteStaticKey[32]; uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only - i2p::crypto::X25519Keys m_EphemeralKeys; - SessionState m_State = eSessionStateNew; + i2p::crypto::X25519Keys m_EphemeralKeys; + SessionState m_State = eSessionStateNew; uint64_t m_LastActivityTimestamp = 0; // incoming - std::shared_ptr m_SendTagset, m_NSRTagset; - std::unique_ptr m_Destination;// TODO: might not need it + std::shared_ptr m_SendTagset, m_NSRTagset; + std::unique_ptr m_Destination;// TODO: might not need it std::list > m_AckRequests; // (tagsetid, index) bool m_SendReverseKey = false, m_SendForwardKey = false; std::unique_ptr m_NextReceiveRatchet, m_NextSendRatchet; @@ -174,8 +174,8 @@ namespace garlic i2p::data::IdentHash GetDestination () const { return m_Destination ? *m_Destination : i2p::data::IdentHash (); - } - }; + } + }; std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag); } diff --git a/libi2pd/Ed25519.cpp b/libi2pd/Ed25519.cpp index 69fec4e8..8de5e3a1 100644 --- a/libi2pd/Ed25519.cpp +++ b/libi2pd/Ed25519.cpp @@ -121,7 +121,7 @@ namespace crypto return passed; } - void Ed25519::Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, + void Ed25519::Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, uint8_t * signature) const { BN_CTX * bnCtx = BN_CTX_new (); @@ -153,7 +153,7 @@ namespace crypto BN_CTX_free (bnCtx); } - void Ed25519::SignRedDSA (const uint8_t * privateKey, const uint8_t * publicKeyEncoded, + void Ed25519::SignRedDSA (const uint8_t * privateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, uint8_t * signature) const { BN_CTX * bnCtx = BN_CTX_new (); @@ -164,16 +164,16 @@ namespace crypto SHA512_CTX ctx; SHA512_Init (&ctx); SHA512_Update (&ctx, T, 80); - SHA512_Update (&ctx, publicKeyEncoded, 32); + SHA512_Update (&ctx, publicKeyEncoded, 32); SHA512_Update (&ctx, buf, len); // data uint8_t digest[64]; SHA512_Final (digest, &ctx); - BIGNUM * r = DecodeBN<64> (digest); - BN_mod (r, r, l, bnCtx); // % l + BIGNUM * r = DecodeBN<64> (digest); + BN_mod (r, r, l, bnCtx); // % l EncodeBN (r, digest, 32); // calculate R uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf - EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); + EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); // calculate S SHA512_Init (&ctx); SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R @@ -182,7 +182,7 @@ namespace crypto SHA512_Final (digest, &ctx); BIGNUM * h = DecodeBN<64> (digest); // S = (r + h*a) % l - BIGNUM * a = DecodeBN (privateKey); + BIGNUM * a = DecodeBN (privateKey); BN_mod_mul (h, h, a, l, bnCtx); // %l BN_mod_add (h, h, r, l, bnCtx); // %l memcpy (signature, R, EDDSA25519_SIGNATURE_LENGTH/2); @@ -190,7 +190,7 @@ namespace crypto BN_free (r); BN_free (h); BN_free (a); BN_CTX_free (bnCtx); } - + EDDSAPoint Ed25519::Sum (const EDDSAPoint& p1, const EDDSAPoint& p2, BN_CTX * ctx) const { // x3 = (x1*y2+y1*x2)*(z1*z2-d*t1*t2) @@ -467,7 +467,7 @@ namespace crypto --bits; auto k_t = BN_is_bit_set(k, bits) ? 1 : 0; swap ^= k_t; - if (swap) + if (swap) { std::swap (x2, x3); std::swap (z2, z3); @@ -492,7 +492,7 @@ namespace crypto BN_mod_mul(z3, x1, z2, q, ctx); BN_mod_mul(z2, tmp1, tmp0, q, ctx); } - if (swap) + if (swap) { std::swap (x2, x3); std::swap (z2, z3); @@ -533,9 +533,9 @@ namespace crypto { BN_CTX * ctx = BN_CTX_new (); // calculate alpha = seed mod l - BIGNUM * alpha = DecodeBN<64> (seed); // seed is in Little Endian + BIGNUM * alpha = DecodeBN<64> (seed); // seed is in Little Endian BN_mod (alpha, alpha, l, ctx); // % l - uint8_t priv[32]; + uint8_t priv[32]; EncodeBN (alpha, priv, 32); // back to Little Endian BN_free (alpha); // A' = BLIND_PUBKEY(A, alpha) = A + DERIVE_PUBLIC(alpha) @@ -548,16 +548,16 @@ namespace crypto { BN_CTX * ctx = BN_CTX_new (); // calculate alpha = seed mod l - BIGNUM * alpha = DecodeBN<64> (seed); // seed is in Little Endian + BIGNUM * alpha = DecodeBN<64> (seed); // seed is in Little Endian BN_mod (alpha, alpha, l, ctx); // % l - BIGNUM * p = DecodeBN<32> (priv); // priv is in Little Endian + BIGNUM * p = DecodeBN<32> (priv); // priv is in Little Endian BN_add (alpha, alpha, p); // alpha = alpha + priv - // a' = BLIND_PRIVKEY(a, alpha) = (a + alpha) mod L + // a' = BLIND_PRIVKEY(a, alpha) = (a + alpha) mod L BN_mod (alpha, alpha, l, ctx); // % l EncodeBN (alpha, blindedPriv, 32); - // A' = DERIVE_PUBLIC(a') + // A' = DERIVE_PUBLIC(a') auto A1 = MulB (blindedPriv, ctx); - EncodePublicKey (A1, blindedPub, ctx); + EncodePublicKey (A1, blindedPub, ctx); BN_free (alpha); BN_free (p); BN_CTX_free (ctx); } @@ -574,14 +574,14 @@ namespace crypto { uint8_t seed[32]; RAND_bytes (seed, 32); - BIGNUM * p = DecodeBN<32> (seed); + BIGNUM * p = DecodeBN<32> (seed); BN_CTX * ctx = BN_CTX_new (); BN_mod (p, p, l, ctx); // % l - EncodeBN (p, priv, 32); + EncodeBN (p, priv, 32); BN_CTX_free (ctx); BN_free (p); - } - + } + static std::unique_ptr g_Ed25519; std::unique_ptr& GetEd25519 () { @@ -597,4 +597,3 @@ namespace crypto } } } - diff --git a/libi2pd/Ed25519.h b/libi2pd/Ed25519.h index 84501b03..10061ad0 100644 --- a/libi2pd/Ed25519.h +++ b/libi2pd/Ed25519.h @@ -86,10 +86,10 @@ namespace crypto bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature) const; void Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, uint8_t * signature) const; void SignRedDSA (const uint8_t * privateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, uint8_t * signature) const; - + static void ExpandPrivateKey (const uint8_t * key, uint8_t * expandedKey); // key - 32 bytes, expandedKey - 64 bytes void CreateRedDSAPrivateKey (uint8_t * priv); // priv is 32 bytes - + private: EDDSAPoint Sum (const EDDSAPoint& p1, const EDDSAPoint& p2, BN_CTX * ctx) const; @@ -97,8 +97,8 @@ namespace crypto EDDSAPoint Mul (const EDDSAPoint& p, const BIGNUM * e, BN_CTX * ctx) const; EDDSAPoint MulB (const uint8_t * e, BN_CTX * ctx) const; // B*e, e is 32 bytes Little Endian EDDSAPoint Normalize (const EDDSAPoint& p, BN_CTX * ctx) const; - - bool IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const; + + bool IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const; BIGNUM * RecoverX (const BIGNUM * y, BN_CTX * ctx) const; EDDSAPoint DecodePoint (const uint8_t * buf, BN_CTX * ctx) const; void EncodePoint (const EDDSAPoint& p, uint8_t * buf) const; @@ -109,7 +109,7 @@ namespace crypto #if !OPENSSL_X25519 // for x25519 - BIGNUM * ScalarMul (const BIGNUM * p, const BIGNUM * e, BN_CTX * ctx) const; + BIGNUM * ScalarMul (const BIGNUM * p, const BIGNUM * e, BN_CTX * ctx) const; #endif private: @@ -121,13 +121,11 @@ namespace crypto // if j > 128 we use 256 - j and carry 1 to next byte // Bi256[0][0] = B, base point EDDSAPoint Bi256Carry; // Bi256[32][0] - }; + }; std::unique_ptr& GetEd25519 (); } } - #endif - diff --git a/libi2pd/Elligator.cpp b/libi2pd/Elligator.cpp index 48a5a7ac..8fefb2ae 100644 --- a/libi2pd/Elligator.cpp +++ b/libi2pd/Elligator.cpp @@ -6,7 +6,7 @@ namespace i2p { namespace crypto { - + Elligator2::Elligator2 () { // TODO: share with Ed22519 @@ -21,32 +21,32 @@ namespace crypto A = BN_new (); BN_set_word (A, 486662); nA = BN_new (); BN_sub (nA, p, A); - BN_CTX * ctx = BN_CTX_new (); - // calculate sqrt(-1) + BN_CTX * ctx = BN_CTX_new (); + // calculate sqrt(-1) sqrtn1 = BN_new (); - BN_set_word (sqrtn1, 2); + BN_set_word (sqrtn1, 2); BN_mod_exp (sqrtn1, sqrtn1, p14, p, ctx); // 2^((p-1)/4 - + u = BN_new (); BN_set_word (u, 2); - iu = BN_new (); BN_mod_inverse (iu, u, p, ctx); - + iu = BN_new (); BN_mod_inverse (iu, u, p, ctx); + BN_CTX_free (ctx); } Elligator2::~Elligator2 () { BN_free (p); BN_free (p38); BN_free (p12); BN_free (p14); - BN_free (sqrtn1); BN_free (A); BN_free (nA); - BN_free (u); BN_free (iu); + BN_free (sqrtn1); BN_free (A); BN_free (nA); + BN_free (u); BN_free (iu); } bool Elligator2::Encode (const uint8_t * key, uint8_t * encoded, bool highY, bool random) const - { + { bool ret = true; BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); - uint8_t key1[32]; + uint8_t key1[32]; for (size_t i = 0; i < 16; i++) // from Little Endian { key1[i] = key[31 - i]; @@ -59,35 +59,35 @@ namespace crypto BIGNUM * uxxA = BN_CTX_get (ctx); // u*x*xA BN_mod_mul (uxxA, u, x, p, ctx); - BN_mod_mul (uxxA, uxxA, xA, p, ctx); - + BN_mod_mul (uxxA, uxxA, xA, p, ctx); + if (Legendre (uxxA, ctx) != -1) - { - uint8_t randByte = 0; // random highest bits and high y + { + uint8_t randByte = 0; // random highest bits and high y if (random) - { - RAND_bytes (&randByte, 1); - highY = randByte & 0x01; + { + RAND_bytes (&randByte, 1); + highY = randByte & 0x01; } - + BIGNUM * r = BN_CTX_get (ctx); if (highY) { BN_mod_inverse (r, x, p, ctx); - BN_mod_mul (r, r, xA, p, ctx); + BN_mod_mul (r, r, xA, p, ctx); } else { - BN_mod_inverse (r, xA, p, ctx); + BN_mod_inverse (r, xA, p, ctx); BN_mod_mul (r, r, x, p, ctx); - } - BN_mod_mul (r, r, iu, p, ctx); - + } + BN_mod_mul (r, r, iu, p, ctx); + SquareRoot (r, r, ctx); bn2buf (r, encoded, 32); if (random) - encoded[0] |= (randByte & 0xC0); // copy two highest bits from randByte + encoded[0] |= (randByte & 0xC0); // copy two highest bits from randByte for (size_t i = 0; i < 16; i++) // To Little Endian { uint8_t tmp = encoded[i]; @@ -98,7 +98,7 @@ namespace crypto else ret = false; - BN_CTX_end (ctx); + BN_CTX_end (ctx); BN_CTX_free (ctx); return ret; } @@ -109,31 +109,31 @@ namespace crypto BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); - uint8_t encoded1[32]; + uint8_t encoded1[32]; for (size_t i = 0; i < 16; i++) // from Little Endian { encoded1[i] = encoded[31 - i]; encoded1[31 - i] = encoded[i]; } - encoded1[0] &= 0x3F; // drop two highest bits + encoded1[0] &= 0x3F; // drop two highest bits BIGNUM * r = BN_CTX_get (ctx); BN_bin2bn (encoded1, 32, r); if (BN_cmp (r, p12) <= 0) // r < (p-1)/2 { // v = -A/(1+u*r^2) - BIGNUM * v = BN_CTX_get (ctx); BN_mod_sqr (v, r, p, ctx); + BIGNUM * v = BN_CTX_get (ctx); BN_mod_sqr (v, r, p, ctx); BN_mod_mul (v, v, u, p, ctx); BN_add_word (v, 1); - BN_mod_inverse (v, v, p, ctx); + BN_mod_inverse (v, v, p, ctx); BN_mod_mul (v, v, nA, p, ctx); BIGNUM * vpA = BN_CTX_get (ctx); BN_add (vpA, v, A); // v + A // t = v^3+A*v^2+v = v^2*(v+A)+v - BIGNUM * t = BN_CTX_get (ctx); BN_mod_sqr (t, v, p, ctx); - BN_mod_mul (t, t, vpA, p, ctx); - BN_mod_add (t, t, v, p, ctx); + BIGNUM * t = BN_CTX_get (ctx); BN_mod_sqr (t, v, p, ctx); + BN_mod_mul (t, t, vpA, p, ctx); + BN_mod_add (t, t, v, p, ctx); int legendre = Legendre (t, ctx); BIGNUM * x = BN_CTX_get (ctx); @@ -143,9 +143,9 @@ namespace crypto { BN_sub (x, p, v); BN_mod_sub (x, x, A, p, ctx); - } - - bn2buf (x, key, 32); + } + + bn2buf (x, key, 32); for (size_t i = 0; i < 16; i++) // To Little Endian { uint8_t tmp = key[i]; @@ -156,7 +156,7 @@ namespace crypto else ret = false; - BN_CTX_end (ctx); + BN_CTX_end (ctx); BN_CTX_free (ctx); return ret; @@ -170,21 +170,21 @@ namespace crypto BN_add_word (t, 1); if (!BN_cmp (t, p)) - BN_mod_mul (r, r, sqrtn1, p, ctx); + BN_mod_mul (r, r, sqrtn1, p, ctx); if (BN_cmp (r, p12) > 0) // r > (p-1)/2 BN_sub (r, p, r); - } - + } + int Elligator2::Legendre (const BIGNUM * a, BN_CTX * ctx) const { // assume a < p, so don't check for a % p = 0, but a = 0 only - if (BN_is_zero(a)) return 0; + if (BN_is_zero(a)) return 0; BIGNUM * r = BN_CTX_get (ctx); BN_mod_exp (r, a, p12, p, ctx); // r = a^((p-1)/2) mod p - if (BN_is_word(r, 1)) + if (BN_is_word(r, 1)) return 1; - else if (BN_is_zero(r)) + else if (BN_is_zero(r)) return 0; return -1; } @@ -204,4 +204,3 @@ namespace crypto } } } - diff --git a/libi2pd/Elligator.h b/libi2pd/Elligator.h index 7cdcbbfe..56fbc2eb 100644 --- a/libi2pd/Elligator.h +++ b/libi2pd/Elligator.h @@ -20,11 +20,11 @@ namespace crypto bool Encode (const uint8_t * key, uint8_t * encoded, bool highY = false, bool random = true) const; bool Decode (const uint8_t * encoded, uint8_t * key) const; - private: + private: void SquareRoot (const BIGNUM * x, BIGNUM * r, BN_CTX * ctx) const; int Legendre (const BIGNUM * a, BN_CTX * ctx) const; // a/p - + private: BIGNUM * p, * p38, * p12, * p14, * sqrtn1, * A, * nA, * u, * iu; @@ -35,5 +35,3 @@ namespace crypto } #endif - - diff --git a/libi2pd/FS.h b/libi2pd/FS.h index 6f112218..054ac2a3 100644 --- a/libi2pd/FS.h +++ b/libi2pd/FS.h @@ -17,133 +17,136 @@ namespace i2p { namespace fs { - extern std::string dirSep; + extern std::string dirSep; - /** - * @brief Class to work with NetDb & Router profiles - * - * Usage: - * - * const char alphabet[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; - * auto h = HashedStorage("name", "y", "z-", ".txt"); - * h.SetPlace("/tmp/hs-test"); - * h.GetName() -> gives "name" - * h.GetRoot() -> gives "/tmp/hs-test/name" - * h.Init(alphabet, 8); <- creates needed dirs, 8 is size of alphabet - * h.Path("abcd"); <- returns /tmp/hs-test/name/ya/z-abcd.txt - * h.Remove("abcd"); <- removes /tmp/hs-test/name/ya/z-abcd.txt, if it exists - * std::vector files; - * h.Traverse(files); <- finds all files in storage and saves in given vector - */ - class HashedStorage { - protected: - std::string root; /**< path to storage with it's name included */ - std::string name; /**< name of the storage */ - std::string prefix1; /**< hashed directory prefix */ - std::string prefix2; /**< prefix of file in storage */ - std::string suffix; /**< suffix of file in storage (extension) */ + /** + * @brief Class to work with NetDb & Router profiles + * + * Usage: + * + * const char alphabet[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; + * auto h = HashedStorage("name", "y", "z-", ".txt"); + * h.SetPlace("/tmp/hs-test"); + * h.GetName() -> gives "name" + * h.GetRoot() -> gives "/tmp/hs-test/name" + * h.Init(alphabet, 8); <- creates needed dirs, 8 is size of alphabet + * h.Path("abcd"); <- returns /tmp/hs-test/name/ya/z-abcd.txt + * h.Remove("abcd"); <- removes /tmp/hs-test/name/ya/z-abcd.txt, if it exists + * std::vector files; + * h.Traverse(files); <- finds all files in storage and saves in given vector + */ + class HashedStorage + { + protected: - public: - typedef std::function FilenameVisitor; - HashedStorage(const char *n, const char *p1, const char *p2, const char *s): - name(n), prefix1(p1), prefix2(p2), suffix(s) {}; + std::string root; /**< path to storage with it's name included */ + std::string name; /**< name of the storage */ + std::string prefix1; /**< hashed directory prefix */ + std::string prefix2; /**< prefix of file in storage */ + std::string suffix; /**< suffix of file in storage (extension) */ - /** create subdirs in storage */ - bool Init(const char* chars, size_t cnt); - const std::string & GetRoot() const { return root; } - const std::string & GetName() const { return name; } - /** set directory where to place storage directory */ - void SetPlace(const std::string & path); - /** path to file with given ident */ - std::string Path(const std::string & ident) const; - /** remove file by ident */ - void Remove(const std::string & ident); - /** find all files in storage and store list in provided vector */ - void Traverse(std::vector & files); - /** visit every file in this storage with a visitor */ - void Iterate(FilenameVisitor v); - }; + public: - /** @brief Returns current application name, default 'i2pd' */ + typedef std::function FilenameVisitor; + HashedStorage(const char *n, const char *p1, const char *p2, const char *s): + name(n), prefix1(p1), prefix2(p2), suffix(s) {}; + + /** create subdirs in storage */ + bool Init(const char* chars, size_t cnt); + const std::string & GetRoot() const { return root; } + const std::string & GetName() const { return name; } + /** set directory where to place storage directory */ + void SetPlace(const std::string & path); + /** path to file with given ident */ + std::string Path(const std::string & ident) const; + /** remove file by ident */ + void Remove(const std::string & ident); + /** find all files in storage and store list in provided vector */ + void Traverse(std::vector & files); + /** visit every file in this storage with a visitor */ + void Iterate(FilenameVisitor v); + }; + + /** @brief Returns current application name, default 'i2pd' */ const std::string & GetAppName (); - /** @brief Set application name, affects autodetection of datadir */ + /** @brief Set application name, affects autodetection of datadir */ void SetAppName (const std::string& name); - /** @brief Returns datadir path */ - const std::string & GetDataDir(); + /** @brief Returns datadir path */ + const std::string & GetDataDir(); - /** - * @brief Set datadir either from cmdline option or using autodetection - * @param cmdline_param Value of cmdline parameter --datadir= - * @param isService Value of cmdline parameter --service - * - * Examples of autodetected paths: - * - * Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\ - * Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\ - * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/ - * Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/ - */ - void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); + /** + * @brief Set datadir either from cmdline option or using autodetection + * @param cmdline_param Value of cmdline parameter --datadir= + * @param isService Value of cmdline parameter --service + * + * Examples of autodetected paths: + * + * Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\ + * Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\ + * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/ + * Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/ + */ + void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); - /** - * @brief Create subdirectories inside datadir - */ - bool Init(); + /** + * @brief Create subdirectories inside datadir + */ + bool Init(); - /** - * @brief Get list of files in directory - * @param path Path to directory - * @param files Vector to store found files - * @return true on success and false if directory not exists - */ - bool ReadDir(const std::string & path, std::vector & files); + /** + * @brief Get list of files in directory + * @param path Path to directory + * @param files Vector to store found files + * @return true on success and false if directory not exists + */ + bool ReadDir(const std::string & path, std::vector & files); - /** - * @brief Remove file with given path - * @param path Absolute path to file - * @return true on success, false if file not exists, throws exception on error - */ - bool Remove(const std::string & path); + /** + * @brief Remove file with given path + * @param path Absolute path to file + * @return true on success, false if file not exists, throws exception on error + */ + bool Remove(const std::string & path); - /** - * @brief Check existence of file - * @param path Absolute path to file - * @return true if file exists, false otherwise - */ - bool Exists(const std::string & path); + /** + * @brief Check existence of file + * @param path Absolute path to file + * @return true if file exists, false otherwise + */ + bool Exists(const std::string & path); - uint32_t GetLastUpdateTime (const std::string & path); // seconds since epoch + uint32_t GetLastUpdateTime (const std::string & path); // seconds since epoch - bool CreateDirectory (const std::string& path); + bool CreateDirectory (const std::string& path); - template - void _ExpandPath(std::stringstream & path, T c) { - path << i2p::fs::dirSep << c; - } + template + void _ExpandPath(std::stringstream & path, T c) { + path << i2p::fs::dirSep << c; + } - template - void _ExpandPath(std::stringstream & path, T c, Other ... other) { - _ExpandPath(path, c); - _ExpandPath(path, other ...); - } + template + void _ExpandPath(std::stringstream & path, T c, Other ... other) { + _ExpandPath(path, c); + _ExpandPath(path, other ...); + } - /** - * @brief Get path relative to datadir - * - * Examples (with datadir = "/tmp/i2pd"): - * - * i2p::fs::Path("test") -> '/tmp/i2pd/test' - * i2p::fs::Path("test", "file.txt") -> '/tmp/i2pd/test/file.txt' - */ - template - std::string DataDirPath(Other ... components) { - std::stringstream s(""); - s << i2p::fs::GetDataDir(); - _ExpandPath(s, components ...); + /** + * @brief Get path relative to datadir + * + * Examples (with datadir = "/tmp/i2pd"): + * + * i2p::fs::Path("test") -> '/tmp/i2pd/test' + * i2p::fs::Path("test", "file.txt") -> '/tmp/i2pd/test/file.txt' + */ + template + std::string DataDirPath(Other ... components) { + std::stringstream s(""); + s << i2p::fs::GetDataDir(); + _ExpandPath(s, components ...); - return s.str(); - } + return s.str(); + } template std::string StorageRootPath (const Storage& storage, Filename... filenames) diff --git a/libi2pd/Family.cpp b/libi2pd/Family.cpp index bc99bb52..e5ac9990 100644 --- a/libi2pd/Family.cpp +++ b/libi2pd/Family.cpp @@ -179,4 +179,3 @@ namespace data } } } - diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 8ba4a8a1..5d07155e 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -38,9 +38,9 @@ namespace garlic if (!m_SharedRoutingPath) return nullptr; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); if (m_SharedRoutingPath->numTimesUsed >= ROUTING_PATH_MAX_NUM_TIMES_USED || - !m_SharedRoutingPath->outboundTunnel->IsEstablished () || + !m_SharedRoutingPath->outboundTunnel->IsEstablished () || ts*1000LL > m_SharedRoutingPath->remoteLease->endDate || - ts > m_SharedRoutingPath->updateTime + ROUTING_PATH_EXPIRATION_TIMEOUT) + ts > m_SharedRoutingPath->updateTime + ROUTING_PATH_EXPIRATION_TIMEOUT) m_SharedRoutingPath = nullptr; if (m_SharedRoutingPath) m_SharedRoutingPath->numTimesUsed++; return m_SharedRoutingPath; @@ -68,7 +68,7 @@ namespace garlic return true; } return false; - } + } void GarlicRoutingSession::CleanupUnconfirmedLeaseSet (uint64_t ts) { @@ -78,8 +78,8 @@ namespace garlic GetOwner ()->RemoveDeliveryStatusSession (m_LeaseSetUpdateMsgID); m_LeaseSetUpdateMsgID = 0; } - } - + } + std::shared_ptr GarlicRoutingSession::CreateEncryptedDeliveryStatusMsg (uint32_t msgID) { auto msg = CreateDeliveryStatusMsg (msgID); @@ -94,12 +94,12 @@ namespace garlic msg = garlic.WrapSingleMessage (msg); } return msg; - } - - ElGamalAESSession::ElGamalAESSession (GarlicDestination * owner, - std::shared_ptr destination, int numTags, bool attachLeaseSet): - GarlicRoutingSession (owner, attachLeaseSet), - m_Destination (destination), m_NumTags (numTags) + } + + ElGamalAESSession::ElGamalAESSession (GarlicDestination * owner, + std::shared_ptr destination, int numTags, bool attachLeaseSet): + GarlicRoutingSession (owner, attachLeaseSet), + m_Destination (destination), m_NumTags (numTags) { // create new session tags and session key RAND_bytes (m_SessionKey, 32); @@ -115,7 +115,7 @@ namespace garlic m_SessionTags.back ().creationTime = i2p::util::GetSecondsSinceEpoch (); } - std::shared_ptr ElGamalAESSession::WrapSingleMessage (std::shared_ptr msg) + std::shared_ptr ElGamalAESSession::WrapSingleMessage (std::shared_ptr msg) { auto m = NewI2NPMessage (); m->Align (12); // in order to get buf aligned to 16 (12 + 4) @@ -181,7 +181,7 @@ namespace garlic return m; } - size_t ElGamalAESSession::CreateAESBlock (uint8_t * buf, std::shared_ptr msg) + size_t ElGamalAESSession::CreateAESBlock (uint8_t * buf, std::shared_ptr msg) { size_t blockSize = 0; bool createNewTags = GetOwner () && m_NumTags && ((int)m_SessionTags.size () <= m_NumTags*2/3); @@ -329,10 +329,10 @@ namespace garlic // create msg auto msg = CreateEncryptedDeliveryStatusMsg (msgID); if (msg) - { + { memcpy (buf + size, msg->GetBuffer (), msg->GetLength ()); size += msg->GetLength (); - } + } // fill clove uint64_t ts = i2p::util::GetMillisecondsSinceEpoch () + 8000; // 8 sec uint32_t cloveID; @@ -353,7 +353,7 @@ namespace garlic return size; } - ElGamalAESSession::UnconfirmedTags * ElGamalAESSession::GenerateSessionTags () + ElGamalAESSession::UnconfirmedTags * ElGamalAESSession::GenerateSessionTags () { auto tags = new UnconfirmedTags (m_NumTags); tags->tagsCreationTime = i2p::util::GetSecondsSinceEpoch (); @@ -487,7 +487,7 @@ namespace garlic LogPrint (eLogWarning, "Garlic: message length ", length, " is less than 32 bytes"); } else - { + { bool found = false; if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) { @@ -500,15 +500,15 @@ namespace garlic found = true; auto session = it1->second.tagset->GetSession (); if (!session || !session->HandleNextMessage (buf, length, it1->second.tagset, it1->second.index)) - LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); + LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); m_ECIESx25519Tags.erase (it1); } - } - - if (!found) // assume new session + } + + if (!found) // assume new session { - // AES tag not found. Handle depending on encryption type - // try ElGamal/AES first if leading block is 514 + // AES tag not found. Handle depending on encryption type + // try ElGamal/AES first if leading block is 514 ElGamalBlock elGamal; if (mod == 2 && length >= 514 && SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) && Decrypt (buf, (uint8_t *)&elGamal, m_Ctx, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)) @@ -521,16 +521,16 @@ namespace garlic HandleAESBlock (buf + 514, length - 514, decryption, msg->from); } else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) - { - // otherwise ECIESx25519 + { + // otherwise ECIESx25519 auto session = std::make_shared (this, false); // incoming if (!session->HandleNextMessage (buf, length, nullptr, 0)) - LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); - } + LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); + } else - LogPrint (eLogError, "Garlic: Failed to decrypt message"); + LogPrint (eLogError, "Garlic: Failed to decrypt message"); } - } + } } void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr decryption, @@ -703,41 +703,41 @@ namespace garlic std::shared_ptr GarlicDestination::GetRoutingSession ( std::shared_ptr destination, bool attachLeaseSet) { - if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET && - SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) - { - ECIESX25519AEADRatchetSessionPtr session; - uint8_t staticKey[32]; - destination->Encrypt (nullptr, staticKey, nullptr); // we are supposed to get static key - auto it = m_ECIESx25519Sessions.find (staticKey); + if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET && + SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) + { + ECIESX25519AEADRatchetSessionPtr session; + uint8_t staticKey[32]; + destination->Encrypt (nullptr, staticKey, nullptr); // we are supposed to get static key + auto it = m_ECIESx25519Sessions.find (staticKey); if (it != m_ECIESx25519Sessions.end ()) - session = it->second; - if (!session) + session = it->second; + if (!session) { session = std::make_shared (this, true); session->SetRemoteStaticKey (staticKey); - } + } session->SetDestination (destination->GetIdentHash ()); // TODO: remove - return session; - } - else - { - ElGamalAESSessionPtr session; - { - std::unique_lock l(m_SessionsMutex); - auto it = m_Sessions.find (destination->GetIdentHash ()); - if (it != m_Sessions.end ()) - session = it->second; - } - if (!session) - { - session = std::make_shared (this, destination, - attachLeaseSet ? m_NumTags : 4, attachLeaseSet); // specified num tags for connections and 4 for LS requests - std::unique_lock l(m_SessionsMutex); - m_Sessions[destination->GetIdentHash ()] = session; - } - return session; - } + return session; + } + else + { + ElGamalAESSessionPtr session; + { + std::unique_lock l(m_SessionsMutex); + auto it = m_Sessions.find (destination->GetIdentHash ()); + if (it != m_Sessions.end ()) + session = it->second; + } + if (!session) + { + session = std::make_shared (this, destination, + attachLeaseSet ? m_NumTags : 4, attachLeaseSet); // specified num tags for connections and 4 for LS requests + std::unique_lock l(m_SessionsMutex); + m_Sessions[destination->GetIdentHash ()] = session; + } + return session; + } } void GarlicDestination::CleanupExpiredTags () @@ -791,7 +791,7 @@ namespace garlic if (it->second.tagset->IsExpired (ts) || ts > it->second.creationTime + ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT) it = m_ECIESx25519Tags.erase (it); else - ++it; + ++it; } for (auto it = m_ECIESx25519Sessions.begin (); it != m_ECIESx25519Sessions.end ();) @@ -800,7 +800,7 @@ namespace garlic { it->second->SetOwner (nullptr); it = m_ECIESx25519Sessions.erase (it); - } + } else ++it; } @@ -925,28 +925,28 @@ namespace garlic std::vector files; i2p::fs::ReadDir (i2p::fs::DataDirPath("tags"), files); uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it: files) + for (auto it: files) if (ts >= i2p::fs::GetLastUpdateTime (it) + INCOMING_TAGS_EXPIRATION_TIMEOUT) i2p::fs::Remove (it); } - void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len) - { - const uint8_t * buf1 = buf; + void GarlicDestination::HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len) + { + const uint8_t * buf1 = buf; uint8_t flag = buf[0]; buf++; // flag GarlicDeliveryType deliveryType = (GarlicDeliveryType)((flag >> 5) & 0x03); switch (deliveryType) { case eGarlicDeliveryTypeDestination: - LogPrint (eLogDebug, "Garlic: type destination"); + LogPrint (eLogDebug, "Garlic: type destination"); buf += 32; // TODO: check destination -#if (__cplusplus >= 201703L) // C++ 17 or higher - [[fallthrough]]; -#endif +#if (__cplusplus >= 201703L) // C++ 17 or higher + [[fallthrough]]; +#endif // no break here case eGarlicDeliveryTypeLocal: { - LogPrint (eLogDebug, "Garlic: type local"); + LogPrint (eLogDebug, "Garlic: type local"); I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid buf += (4 + 4); // msgID + expiration ptrdiff_t offset = buf - buf1; @@ -957,8 +957,8 @@ namespace garlic break; } case eGarlicDeliveryTypeTunnel: - { - LogPrint (eLogDebug, "Garlic: type tunnel"); + { + LogPrint (eLogDebug, "Garlic: type tunnel"); // gwHash and gwTunnel sequence is reverted const uint8_t * gwHash = buf; buf += 32; @@ -968,47 +968,47 @@ namespace garlic LogPrint (eLogError, "Garlic: message is too short"); break; } - uint32_t gwTunnel = bufbe32toh (buf); buf += 4; - I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid + uint32_t gwTunnel = bufbe32toh (buf); buf += 4; + I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid buf += (4 + 4); // msgID + expiration - offset += 13; + offset += 13; if (GetTunnelPool ()) - { - auto tunnel = GetTunnelPool ()->GetNextOutboundTunnel (); - if (tunnel) - tunnel->SendTunnelDataMsg (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset)); + { + auto tunnel = GetTunnelPool ()->GetNextOutboundTunnel (); + if (tunnel) + tunnel->SendTunnelDataMsg (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset)); else - LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); - } + LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); + } else LogPrint (eLogError, "Garlic: Tunnel pool is not set for inbound tunnel"); - break; - } + break; + } default: LogPrint (eLogWarning, "Garlic: unexpected delivery type ", (int)deliveryType); - } - } + } + } - void GarlicDestination::AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset) - { + void GarlicDestination::AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset) + { auto index = tagset->GetNextIndex (); uint64_t tag = tagset->GetNextSessionTag (); - m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{index, tagset, i2p::util::GetSecondsSinceEpoch ()}); - } + m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{index, tagset, i2p::util::GetSecondsSinceEpoch ()}); + } void GarlicDestination::AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session) { i2p::data::Tag<32> staticKeyTag (staticKey); auto it = m_ECIESx25519Sessions.find (staticKeyTag); if (it != m_ECIESx25519Sessions.end ()) - { + { if (it->second->CanBeRestarted (i2p::util::GetSecondsSinceEpoch ())) m_ECIESx25519Sessions.erase (it); else { - LogPrint (eLogInfo, "Garlic: ECIESx25519 session with static key ", staticKeyTag.ToBase64 (), " already exists"); + LogPrint (eLogInfo, "Garlic: ECIESx25519 session with static key ", staticKeyTag.ToBase64 (), " already exists"); return; - } + } } m_ECIESx25519Sessions.emplace (staticKeyTag, session); } @@ -1020,7 +1020,7 @@ namespace garlic { it->second->SetOwner (nullptr); m_ECIESx25519Sessions.erase (it); - } - } + } + } } } diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 86c9e432..55ebe795 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -87,8 +87,8 @@ namespace garlic class GarlicDestination; class GarlicRoutingSession { - protected: - + protected: + enum LeaseSetUpdateStatus { eLeaseSetUpToDate = 0, @@ -103,10 +103,10 @@ namespace garlic GarlicRoutingSession (); virtual ~GarlicRoutingSession (); virtual std::shared_ptr WrapSingleMessage (std::shared_ptr msg) = 0; - virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession + virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession virtual bool MessageConfirmed (uint32_t msgID); virtual bool IsRatchets () const { return false; }; - + void SetLeaseSetUpdated () { if (m_LeaseSetUpdateStatus != eLeaseSetDoNotSend) m_LeaseSetUpdateStatus = eLeaseSetUpdated; @@ -115,23 +115,23 @@ namespace garlic bool IsLeaseSetUpdated () const { return m_LeaseSetUpdateStatus == eLeaseSetUpdated; }; uint64_t GetLeaseSetSubmissionTime () const { return m_LeaseSetSubmissionTime; } void CleanupUnconfirmedLeaseSet (uint64_t ts); - + std::shared_ptr GetSharedRoutingPath (); void SetSharedRoutingPath (std::shared_ptr path); GarlicDestination * GetOwner () const { return m_Owner; } void SetOwner (GarlicDestination * owner) { m_Owner = owner; } - - protected: - - LeaseSetUpdateStatus GetLeaseSetUpdateStatus () const { return m_LeaseSetUpdateStatus; } - void SetLeaseSetUpdateStatus (LeaseSetUpdateStatus status) { m_LeaseSetUpdateStatus = status; } - uint32_t GetLeaseSetUpdateMsgID () const { return m_LeaseSetUpdateMsgID; } - void SetLeaseSetUpdateMsgID (uint32_t msgID) { m_LeaseSetUpdateMsgID = msgID; } - void SetLeaseSetSubmissionTime (uint64_t ts) { m_LeaseSetSubmissionTime = ts; } + + protected: + + LeaseSetUpdateStatus GetLeaseSetUpdateStatus () const { return m_LeaseSetUpdateStatus; } + void SetLeaseSetUpdateStatus (LeaseSetUpdateStatus status) { m_LeaseSetUpdateStatus = status; } + uint32_t GetLeaseSetUpdateMsgID () const { return m_LeaseSetUpdateMsgID; } + void SetLeaseSetUpdateMsgID (uint32_t msgID) { m_LeaseSetUpdateMsgID = msgID; } + void SetLeaseSetSubmissionTime (uint64_t ts) { m_LeaseSetSubmissionTime = ts; } std::shared_ptr CreateEncryptedDeliveryStatusMsg (uint32_t msgID); - + private: GarlicDestination * m_Owner; @@ -143,38 +143,39 @@ namespace garlic std::shared_ptr m_SharedRoutingPath; public: + // for HTTP only virtual size_t GetNumOutgoingTags () const { return 0; }; }; - //using GarlicRoutingSessionPtr = std::shared_ptr; - typedef std::shared_ptr GarlicRoutingSessionPtr; // TODO: replace to using after switch to 4.8 + //using GarlicRoutingSessionPtr = std::shared_ptr; + typedef std::shared_ptr GarlicRoutingSessionPtr; // TODO: replace to using after switch to 4.8 - class ElGamalAESSession: public GarlicRoutingSession, public std::enable_shared_from_this - { - struct UnconfirmedTags - { - UnconfirmedTags (int n): numTags (n), tagsCreationTime (0) { sessionTags = new SessionTag[numTags]; }; - ~UnconfirmedTags () { delete[] sessionTags; }; - uint32_t msgID; - int numTags; - SessionTag * sessionTags; - uint32_t tagsCreationTime; - }; + class ElGamalAESSession: public GarlicRoutingSession, public std::enable_shared_from_this + { + struct UnconfirmedTags + { + UnconfirmedTags (int n): numTags (n), tagsCreationTime (0) { sessionTags = new SessionTag[numTags]; }; + ~UnconfirmedTags () { delete[] sessionTags; }; + uint32_t msgID; + int numTags; + SessionTag * sessionTags; + uint32_t tagsCreationTime; + }; - public: + public: - ElGamalAESSession (GarlicDestination * owner, std::shared_ptr destination, + ElGamalAESSession (GarlicDestination * owner, std::shared_ptr destination, int numTags, bool attachLeaseSet); ElGamalAESSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption ~ElGamalAESSession () {}; - std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - - bool MessageConfirmed (uint32_t msgID); + std::shared_ptr WrapSingleMessage (std::shared_ptr msg); + + bool MessageConfirmed (uint32_t msgID); bool CleanupExpiredTags (); // returns true if something left bool CleanupUnconfirmedTags (); // returns true if something has been deleted - private: + private: size_t CreateAESBlock (uint8_t * buf, std::shared_ptr msg); size_t CreateGarlicPayload (uint8_t * payload, std::shared_ptr msg, UnconfirmedTags * newTags); @@ -183,32 +184,33 @@ namespace garlic void TagsConfirmed (uint32_t msgID); UnconfirmedTags * GenerateSessionTags (); - - private: - - std::shared_ptr m_Destination; - i2p::crypto::AESKey m_SessionKey; + private: + + std::shared_ptr m_Destination; + + i2p::crypto::AESKey m_SessionKey; std::list m_SessionTags; int m_NumTags; std::map > m_UnconfirmedTagsMsgs; // msgID->tags - i2p::crypto::CBCEncryption m_Encryption; + i2p::crypto::CBCEncryption m_Encryption; + + public: - public: // for HTTP only - size_t GetNumOutgoingTags () const { return m_SessionTags.size (); }; - }; - typedef std::shared_ptr ElGamalAESSessionPtr; + size_t GetNumOutgoingTags () const { return m_SessionTags.size (); }; + }; + typedef std::shared_ptr ElGamalAESSessionPtr; - class ECIESX25519AEADRatchetSession; - typedef std::shared_ptr ECIESX25519AEADRatchetSessionPtr; + class ECIESX25519AEADRatchetSession; + typedef std::shared_ptr ECIESX25519AEADRatchetSessionPtr; class RatchetTagSet; typedef std::shared_ptr RatchetTagSetPtr; struct ECIESX25519AEADRatchetIndexTagset - { - int index; - RatchetTagSetPtr tagset; + { + int index; + RatchetTagSetPtr tagset; uint64_t creationTime; // seconds since epoch }; @@ -226,12 +228,12 @@ namespace garlic void CleanupExpiredTags (); void RemoveDeliveryStatusSession (uint32_t msgID); std::shared_ptr WrapMessage (std::shared_ptr destination, - std::shared_ptr msg, bool attachLeaseSet = false); + std::shared_ptr msg, bool attachLeaseSet = false); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); - void AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset); + void AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset); void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); void RemoveECIESx25519Session (const uint8_t * staticKey); void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); @@ -266,10 +268,10 @@ namespace garlic int m_NumTags; std::mutex m_SessionsMutex; std::unordered_map m_Sessions; - std::unordered_map, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session + std::unordered_map, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session // incoming std::unordered_map, std::hash > > m_Tags; - std::unordered_map m_ECIESx25519Tags; // session tag -> session + std::unordered_map m_ECIESx25519Tags; // session tag -> session // DeliveryStatus std::mutex m_DeliveryStatusSessionsMutex; std::unordered_map m_DeliveryStatusSessions; // msgID -> session diff --git a/libi2pd/Gost.h b/libi2pd/Gost.h index 30386104..a4cc9741 100644 --- a/libi2pd/Gost.h +++ b/libi2pd/Gost.h @@ -13,11 +13,11 @@ namespace crypto enum GOSTR3410ParamSet { - eGOSTR3410CryptoProA = 0, // 1.2.643.2.2.35.1 + eGOSTR3410CryptoProA = 0, // 1.2.643.2.2.35.1 // XchA = A, XchB = C - //eGOSTR3410CryptoProXchA, // 1.2.643.2.2.36.0 - //eGOSTR3410CryptoProXchB, // 1.2.643.2.2.36.1 - eGOSTR3410TC26A512, // 1.2.643.7.1.2.1.2.1 + //eGOSTR3410CryptoProXchA, // 1.2.643.2.2.36.0 + //eGOSTR3410CryptoProXchB, // 1.2.643.2.2.36.1 + eGOSTR3410TC26A512, // 1.2.643.7.1.2.1.2.1 eGOSTR3410NumParamSets }; diff --git a/libi2pd/Gzip.cpp b/libi2pd/Gzip.cpp index f36620ae..5709719b 100644 --- a/libi2pd/Gzip.cpp +++ b/libi2pd/Gzip.cpp @@ -40,13 +40,13 @@ namespace data { LogPrint (eLogError, "Gzip: Incorrect length"); return 0; - } + } if (len > outLen) len = outLen; memcpy (out, in + 15, len); return len; } else - { + { if (m_IsDirty) inflateReset (&m_Inflator); m_IsDirty = true; m_Inflator.next_in = const_cast(in); @@ -59,7 +59,7 @@ namespace data // else LogPrint (eLogError, "Gzip: Inflate error ", err); return 0; - } + } } void GzipInflator::Inflate (const uint8_t * in, size_t inLen, std::ostream& os) @@ -126,7 +126,7 @@ namespace data { out[9] = 0xff; // OS is always unknown return outLen - m_Deflator.avail_out; - } + } // else LogPrint (eLogError, "Gzip: Deflate error ", err); return 0; @@ -147,21 +147,21 @@ namespace data auto flush = (it == bufs.back ()) ? Z_FINISH : Z_NO_FLUSH; err = deflate (&m_Deflator, flush); if (err) - { + { if (flush && err == Z_STREAM_END) { out[9] = 0xff; // OS is always unknown return outLen - m_Deflator.avail_out; - } + } break; } offset = outLen - m_Deflator.avail_out; - } + } // else LogPrint (eLogError, "Gzip: Deflate error ", err); return 0; - } - + } + size_t GzipNoCompression (const uint8_t * in, uint16_t inLen, uint8_t * out, size_t outLen) { static const uint8_t gzipHeader[11] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x01 }; @@ -176,7 +176,7 @@ namespace data } size_t GzipNoCompression (const std::vector >& bufs, uint8_t * out, size_t outLen) - { + { static const uint8_t gzipHeader[11] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x01 }; memcpy (out, gzipHeader, 11); uint32_t crc = 0; @@ -186,9 +186,9 @@ namespace data len1 = len; len += it.second; if (outLen < len + 23) return 0; - memcpy (out + 15 + len1, it.first, it.second); + memcpy (out + 15 + len1, it.first, it.second); crc = crc32 (crc, it.first, it.second); - } + } if (len > 0xffff) return 0; htole32buf (out + len + 15, crc); htole32buf (out + len + 19, len); @@ -196,6 +196,6 @@ namespace data htole16buf (out + 13, 0xffff - len); return len + 23; } - + } // data } // i2p diff --git a/libi2pd/Gzip.h b/libi2pd/Gzip.h index 16776d35..6489b79b 100644 --- a/libi2pd/Gzip.h +++ b/libi2pd/Gzip.h @@ -4,9 +4,9 @@ #include #include -namespace i2p +namespace i2p { -namespace data +namespace data { class GzipInflator { @@ -45,7 +45,6 @@ namespace data size_t GzipNoCompression (const uint8_t * in, uint16_t inLen, uint8_t * out, size_t outLen); // for < 64K size_t GzipNoCompression (const std::vector >& bufs, uint8_t * out, size_t outLen); // for total size < 64K - } // data } // i2p diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 3a139792..170bb864 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -180,9 +180,9 @@ namespace i2p // excluded if (cnt > 512) { - LogPrint (eLogWarning, "I2NP: Too many peers to exclude ", cnt, " for DatabaseLookup"); + LogPrint (eLogWarning, "I2NP: Too many peers to exclude ", cnt, " for DatabaseLookup"); cnt = 0; - } + } htobe16buf (buf, cnt); buf += 2; if (cnt > 0) @@ -205,7 +205,7 @@ namespace i2p } std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, - std::vector routers) + std::vector routers) { auto m = NewI2NPShortMessage (); uint8_t * buf = m->GetPayload (); @@ -268,7 +268,7 @@ namespace i2p auto m = NewI2NPShortMessage (); uint8_t * payload = m->GetPayload (); memcpy (payload + DATABASE_STORE_KEY_OFFSET, storeHash, 32); - payload[DATABASE_STORE_TYPE_OFFSET] = leaseSet->GetStoreType (); // 1 for LeaseSet + payload[DATABASE_STORE_TYPE_OFFSET] = leaseSet->GetStoreType (); // 1 for LeaseSet htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); size_t size = DATABASE_STORE_HEADER_SIZE; memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ()); @@ -278,7 +278,7 @@ namespace i2p return m; } - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken, std::shared_ptr replyTunnel) + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken, std::shared_ptr replyTunnel) { if (!leaseSet) return nullptr; auto m = NewI2NPShortMessage (); @@ -347,11 +347,11 @@ namespace i2p auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, - clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, + clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80, - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET ] & 0x40); + clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET ] & 0x40); i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 0; } @@ -386,7 +386,7 @@ namespace i2p return; } - auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); + auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); if (tunnel) { // endpoint of inbound tunnel @@ -414,7 +414,7 @@ namespace i2p transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), eI2NPVariableTunnelBuildReply, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } else transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, @@ -440,7 +440,7 @@ namespace i2p transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), eI2NPTunnelBuildReply, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } else transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, @@ -592,13 +592,13 @@ namespace i2p switch (typeID) { case eI2NPVariableTunnelBuild: - HandleVariableTunnelBuildMsg (msgID, buf, size); + HandleVariableTunnelBuildMsg (msgID, buf, size); break; case eI2NPVariableTunnelBuildReply: HandleVariableTunnelBuildReplyMsg (msgID, buf, size); break; case eI2NPTunnelBuild: - HandleTunnelBuildMsg (buf, size); + HandleTunnelBuildMsg (buf, size); break; case eI2NPTunnelBuildReply: // TODO: @@ -667,7 +667,7 @@ namespace i2p Flush (); } - void I2NPMessagesHandler::PutNextMessage (std::shared_ptr msg) + void I2NPMessagesHandler::PutNextMessage (std::shared_ptr msg) { if (msg) { diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index c8957b5f..4ecba2eb 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -27,7 +27,7 @@ namespace i2p const size_t I2NP_SHORT_HEADER_SIZE = I2NP_SHORT_HEADER_EXPIRATION_OFFSET + 4; // I2NP NTCP2 header - const size_t I2NP_NTCP2_HEADER_SIZE = I2NP_HEADER_EXPIRATION_OFFSET + 4; + const size_t I2NP_NTCP2_HEADER_SIZE = I2NP_HEADER_EXPIRATION_OFFSET + 4; // Tunnel Gateway header const size_t TUNNEL_GATEWAY_HEADER_TUNNELID_OFFSET = 0; @@ -75,7 +75,7 @@ namespace i2p enum I2NPMessageType { - eI2NPDummyMsg = 0, + eI2NPDummyMsg = 0, eI2NPDatabaseStore = 1, eI2NPDatabaseLookup = 2, eI2NPDatabaseSearchReply = 3, @@ -209,7 +209,7 @@ namespace tunnel SetExpiration (bufbe32toh (ntcp2 + I2NP_HEADER_EXPIRATION_OFFSET)*1000LL); SetSize (len - offset - I2NP_HEADER_SIZE); SetChks (0); - } + } void ToNTCP2 () { diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 0c2dc50e..77a1f7e2 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -356,7 +356,7 @@ namespace data } return nullptr; } - + void IdentityEx::CreateVerifier () const { if (m_Verifier) return; // don't create again @@ -369,15 +369,15 @@ namespace data else { // for P521 - uint8_t * signingKey = new uint8_t[keyLen]; + uint8_t * signingKey = new uint8_t[keyLen]; memcpy (signingKey, m_StandardIdentity.signingKey, 128); - size_t excessLen = keyLen - 128; + size_t excessLen = keyLen - 128; memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types verifier->SetPublicKey (signingKey); - delete[] signingKey; - } - } - UpdateVerifier (verifier); + delete[] signingKey; + } + } + UpdateVerifier (verifier); } void IdentityEx::UpdateVerifier (i2p::crypto::Verifier * verifier) const @@ -390,7 +390,7 @@ namespace data else del = true; } - if (del) + if (del) delete verifier; } @@ -426,7 +426,7 @@ namespace data LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)keyType); }; return nullptr; - } + } std::shared_ptr IdentityEx::CreateEncryptor (const uint8_t * key) const { @@ -460,9 +460,9 @@ namespace data return *this; } - size_t PrivateKeys::GetFullLen () const - { - size_t ret = m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen (); + size_t PrivateKeys::GetFullLen () const + { + size_t ret = m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen (); if (IsOfflineSignature ()) ret += m_OfflineSignature.size () + m_TransientSigningPrivateKeyLen; return ret; @@ -483,7 +483,7 @@ namespace data // check if signing private key is all zeros bool allzeros = true; for (size_t i = 0; i < signingPrivateKeySize; i++) - if (m_SigningPrivateKey[i]) + if (m_SigningPrivateKey[i]) { allzeros = false; break; @@ -500,7 +500,7 @@ namespace data if (keyLen + ret > len) return 0; transientVerifier->SetPublicKey (buf + ret); ret += keyLen; if (m_Public->GetSignatureLen () + ret > len) return 0; - if (!m_Public->Verify (offlineInfo, keyLen + 6, buf + ret)) + if (!m_Public->Verify (offlineInfo, keyLen + 6, buf + ret)) { LogPrint (eLogError, "Identity: offline signature verification failed"); return 0; @@ -511,7 +511,7 @@ namespace data size_t offlineInfoLen = buf + ret - offlineInfo; m_OfflineSignature.resize (offlineInfoLen); memcpy (m_OfflineSignature.data (), offlineInfo, offlineInfoLen); - // override signing private key + // override signing private key m_TransientSigningPrivateKeyLen = transientVerifier->GetPrivateKeyLen (); if (m_TransientSigningPrivateKeyLen + ret > len || m_TransientSigningPrivateKeyLen > 128) return 0; memcpy (m_SigningPrivateKey, buf + ret, m_TransientSigningPrivateKeyLen); @@ -586,10 +586,10 @@ namespace data else CreateSigner (m_Public->GetSigningKeyType ()); } - + void PrivateKeys::CreateSigner (SigningKeyType keyType) const { - if (m_Signer) return; + if (m_Signer) return; if (keyType == SIGNING_KEY_TYPE_DSA_SHA1) m_Signer.reset (new i2p::crypto::DSASigner (m_SigningPrivateKey, m_Public->GetStandardIdentity ().signingKey)); else if (keyType == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 && !IsOfflineSignature ()) @@ -599,7 +599,7 @@ namespace data // public key is not required auto signer = CreateSigner (keyType, m_SigningPrivateKey); if (signer) m_Signer.reset (signer); - } + } } i2p::crypto::Signer * PrivateKeys::CreateSigner (SigningKeyType keyType, const uint8_t * priv) @@ -630,8 +630,8 @@ namespace data return new i2p::crypto::GOSTR3410_512_Signer (i2p::crypto::eGOSTR3410TC26A512, priv); break; case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: - return new i2p::crypto::RedDSA25519Signer (priv); - break; + return new i2p::crypto::RedDSA25519Signer (priv); + break; default: LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported"); } @@ -719,8 +719,8 @@ namespace data case SIGNING_KEY_TYPE_RSA_SHA512_4096: LogPrint (eLogWarning, "Identity: RSA signature type is not supported. Creating EdDSA"); #if (__cplusplus >= 201703L) // C++ 17 or higher - [[fallthrough]]; -#endif + [[fallthrough]]; +#endif // no break here case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: i2p::crypto::CreateEDDSA25519RandomKeys (priv, pub); @@ -733,7 +733,7 @@ namespace data break; case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519: i2p::crypto::CreateRedDSA25519RandomKeys (priv, pub); - break; + break; default: LogPrint (eLogWarning, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1"); i2p::crypto::CreateDSARandomKeys (priv, pub); // DSA-SHA1 @@ -765,7 +765,7 @@ namespace data PrivateKeys PrivateKeys::CreateOfflineKeys (SigningKeyType type, uint32_t expires) const { PrivateKeys keys (*this); - std::unique_ptr verifier (IdentityEx::CreateVerifier (type)); + std::unique_ptr verifier (IdentityEx::CreateVerifier (type)); if (verifier) { size_t pubKeyLen = verifier->GetPublicKeyLen (); @@ -775,7 +775,7 @@ namespace data htobe32buf (keys.m_OfflineSignature.data (), expires); // expires htobe16buf (keys.m_OfflineSignature.data () + 4, type); // type GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, keys.m_OfflineSignature.data () + 6); // public key - Sign (keys.m_OfflineSignature.data (), pubKeyLen + 6, keys.m_OfflineSignature.data () + 6 + pubKeyLen); // signature + Sign (keys.m_OfflineSignature.data (), pubKeyLen + 6, keys.m_OfflineSignature.data () + 6 + pubKeyLen); // signature // recreate signer keys.m_Signer = nullptr; keys.CreateSigner (type); diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index da14e141..08baba01 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -56,7 +56,7 @@ namespace data const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0; const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC = 1; - const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET = 4; + const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET = 4; const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST = 65280; // TODO: remove later const uint16_t CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC = 65281; // TODO: use GOST R 34.11 instead SHA256 and GOST 28147-89 instead AES @@ -115,7 +115,7 @@ namespace data void RecalculateIdentHash(uint8_t * buff=nullptr); static i2p::crypto::Verifier * CreateVerifier (SigningKeyType keyType); - static std::shared_ptr CreateEncryptor (CryptoKeyType keyType, const uint8_t * key); + static std::shared_ptr CreateEncryptor (CryptoKeyType keyType, const uint8_t * key); private: @@ -148,7 +148,7 @@ namespace data const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; }; size_t GetSignatureLen () const; // might not match identity bool IsOfflineSignature () const { return m_TransientSignatureLen > 0; }; - uint8_t * GetPadding(); + uint8_t * GetPadding(); void RecalculateIdentHash(uint8_t * buf=nullptr) { m_Public->RecalculateIdentHash(buf); } void Sign (const uint8_t * buf, int len, uint8_t * signature) const; @@ -163,12 +163,12 @@ namespace data static std::shared_ptr CreateDecryptor (CryptoKeyType cryptoType, const uint8_t * key); static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1, CryptoKeyType cryptoType = CRYPTO_KEY_TYPE_ELGAMAL); - static void GenerateSigningKeyPair (SigningKeyType type, uint8_t * priv, uint8_t * pub); + static void GenerateSigningKeyPair (SigningKeyType type, uint8_t * priv, uint8_t * pub); static void GenerateCryptoKeyPair (CryptoKeyType type, uint8_t * priv, uint8_t * pub); // priv and pub are 256 bytes long static i2p::crypto::Signer * CreateSigner (SigningKeyType keyType, const uint8_t * priv); // offline keys - PrivateKeys CreateOfflineKeys (SigningKeyType type, uint32_t expires) const; + PrivateKeys CreateOfflineKeys (SigningKeyType type, uint32_t expires) const; const std::vector& GetOfflineSignature () const { return m_OfflineSignature; }; private: @@ -204,7 +204,7 @@ namespace data IdentHash CreateRoutingKey (const IdentHash& ident); XORMetric operator^(const IdentHash& key1, const IdentHash& key2); - // destination for delivery instuctions + // destination for delivery instructions class RoutingDestination { public: @@ -235,5 +235,4 @@ namespace data } } - #endif diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index 6ce77792..1aa7fa6d 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -12,9 +12,8 @@ namespace i2p { namespace data { - LeaseSet::LeaseSet (bool storeLeases): - m_IsValid (false), m_StoreLeases (storeLeases), m_ExpirationTime (0), m_EncryptionKey (nullptr), + m_IsValid (false), m_StoreLeases (storeLeases), m_ExpirationTime (0), m_EncryptionKey (nullptr), m_Buffer (nullptr), m_BufferLen (0) { } @@ -62,7 +61,7 @@ namespace data { if (!m_EncryptionKey) m_EncryptionKey = new uint8_t[256]; memcpy (m_EncryptionKey, m_Buffer + size, 256); - } + } size += 256; // encryption key size += m_Identity->GetSigningPublicKeyLen (); // unused signing key uint8_t num = m_Buffer[size]; @@ -191,10 +190,10 @@ namespace data return m_ExpirationTime - now <= dlt; } - const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const - { - return GetNonExpiredLeasesExcluding( [] (const Lease & l) -> bool { return false; }, withThreshold); - } + const std::vector > LeaseSet::GetNonExpiredLeases (bool withThreshold) const + { + return GetNonExpiredLeasesExcluding( [] (const Lease & l) -> bool { return false; }, withThreshold); + } const std::vector > LeaseSet::GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold) const { @@ -249,19 +248,19 @@ namespace data if (len <= m_BufferLen) m_BufferLen = len; else LogPrint (eLogError, "LeaseSet2: actual buffer size ", len , " exceeds full buffer size ", m_BufferLen); - } - + } + LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases, CryptoKeyType preferredCrypto): LeaseSet (storeLeases), m_StoreType (storeType), m_EncryptionType (preferredCrypto) - { - SetBuffer (buf, len); + { + SetBuffer (buf, len); if (storeType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) ReadFromBufferEncrypted (buf, len, nullptr, nullptr); else ReadFromBuffer (buf, len); } - LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, + LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret, CryptoKeyType preferredCrypto): LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2), m_EncryptionType (preferredCrypto) { @@ -269,10 +268,10 @@ namespace data } void LeaseSet2::Update (const uint8_t * buf, size_t len, bool verifySignature) - { + { SetBuffer (buf, len); if (GetStoreType () != NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) - ReadFromBuffer (buf, len, false, verifySignature); + ReadFromBuffer (buf, len, false, verifySignature); // TODO: implement encrypted } @@ -281,13 +280,13 @@ namespace data uint64_t expiration; return ExtractPublishedTimestamp (buf, len, expiration) > m_PublishedTimestamp; } - + void LeaseSet2::ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity, bool verifySignature) { // standard LS2 header std::shared_ptr identity; if (readIdentity) - { + { identity = std::make_shared(buf, len); SetIdentity (identity); } @@ -304,7 +303,7 @@ namespace data // transient key m_TransientVerifier = ProcessOfflineSignature (identity, buf, len, offset); if (!m_TransientVerifier) - { + { LogPrint (eLogError, "LeaseSet2: offline signature failed"); return; } @@ -314,7 +313,7 @@ namespace data { m_IsPublishedEncrypted = true; m_IsPublic = true; - } + } // type specific part size_t s = 0; switch (m_StoreType) @@ -331,11 +330,11 @@ namespace data if (!s) return; offset += s; if (verifySignature || m_TransientVerifier) - { + { // verify signature bool verified = m_TransientVerifier ? VerifySignature (m_TransientVerifier, buf, len, offset) : - VerifySignature (identity, buf, len, offset); - SetIsValid (verified); + VerifySignature (identity, buf, len, offset); + SetIsValid (verified); } offset += m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : identity->GetSignatureLen (); SetBufferLen (offset); @@ -346,10 +345,10 @@ namespace data { if (signatureOffset + verifier->GetSignatureLen () > len) return false; // we assume buf inside DatabaseStore message, so buf[-1] is valid memory - // change it for signature verification, and restore back + // change it for signature verification, and restore back uint8_t c = buf[-1]; const_cast(buf)[-1] = m_StoreType; - bool verified = verifier->Verify (buf - 1, signatureOffset + 1, buf + signatureOffset); + bool verified = verifier->Verify (buf - 1, signatureOffset + 1, buf + signatureOffset); const_cast(buf)[-1] = c; if (!verified) LogPrint (eLogWarning, "LeaseSet2: verification failed"); @@ -360,7 +359,7 @@ namespace data { size_t offset = 0; // properties - uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; + uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; offset += propertiesLen; // skip for now. TODO: implement properties if (offset + 1 >= len) return 0; // key sections @@ -371,7 +370,7 @@ namespace data { uint16_t keyType = bufbe16toh (buf + offset); offset += 2; // encryption key type if (offset + 2 >= len) return 0; - uint16_t encryptionKeyLen = bufbe16toh (buf + offset); offset += 2; + uint16_t encryptionKeyLen = bufbe16toh (buf + offset); offset += 2; if (offset + encryptionKeyLen >= len) return 0; if (IsStoreLeases () && !preferredKeyFound) // create encryptor with leases only { @@ -384,10 +383,10 @@ namespace data if (keyType == preferredKeyType) preferredKeyFound = true; } } - offset += encryptionKeyLen; - } + offset += encryptionKeyLen; + } // leases - if (offset + 1 >= len) return 0; + if (offset + 1 >= len) return 0; int numLeases = buf[offset]; offset++; auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (IsStoreLeases ()) @@ -413,9 +412,9 @@ namespace data { size_t offset = 0; // properties - uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; + uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2; offset += propertiesLen; // skip for now. TODO: implement properties - // entries + // entries if (offset + 1 >= len) return 0; int numEntries = buf[offset]; offset++; for (int i = 0; i < numEntries; i++) @@ -423,12 +422,12 @@ namespace data if (offset + 40 >= len) return 0; offset += 32; // hash offset += 3; // flags - offset += 1; // cost + offset += 1; // cost offset += 4; // expires } // revocations if (offset + 1 >= len) return 0; - int numRevocations = buf[offset]; offset++; + int numRevocations = buf[offset]; offset++; for (int i = 0; i < numRevocations; i++) { if (offset + 32 > len) return 0; @@ -446,7 +445,7 @@ namespace data uint16_t blindedKeyType = bufbe16toh (stA1); offset += 2; std::unique_ptr blindedVerifier (i2p::data::IdentityEx::CreateVerifier (blindedKeyType)); if (!blindedVerifier) return; - auto blindedKeyLen = blindedVerifier->GetPublicKeyLen (); + auto blindedKeyLen = blindedVerifier->GetPublicKeyLen (); if (offset + blindedKeyLen >= len) return; const uint8_t * blindedPublicKey = buf + offset; blindedVerifier->SetPublicKey (blindedPublicKey); offset += blindedKeyLen; @@ -461,7 +460,7 @@ namespace data { // transient key m_TransientVerifier = ProcessOfflineSignature (blindedVerifier, buf, len, offset); - if (!m_TransientVerifier) + if (!m_TransientVerifier) { LogPrint (eLogError, "LeaseSet2: offline signature failed"); return; @@ -470,16 +469,16 @@ namespace data // outer ciphertext if (offset + 2 > len) return; uint16_t lenOuterCiphertext = bufbe16toh (buf + offset); offset += 2; - const uint8_t * outerCiphertext = buf + offset; - offset += lenOuterCiphertext; + const uint8_t * outerCiphertext = buf + offset; + offset += lenOuterCiphertext; // verify signature bool verified = m_TransientVerifier ? VerifySignature (m_TransientVerifier, buf, len, offset) : - VerifySignature (blindedVerifier, buf, len, offset); + VerifySignature (blindedVerifier, buf, len, offset); SetIsValid (verified); // handle ciphertext if (verified && key && lenOuterCiphertext >= 32) { - SetIsValid (false); // we must verify it again in Layer 2 + SetIsValid (false); // we must verify it again in Layer 2 if (blindedKeyType == key->GetBlindedSigType ()) { // verify blinding @@ -491,13 +490,13 @@ namespace data { LogPrint (eLogError, "LeaseSet2: blinded public key doesn't match"); return; - } - } + } + } else { LogPrint (eLogError, "LeaseSet2: Unexpected blinded key type ", blindedKeyType, " instead ", key->GetBlindedSigType ()); return; - } + } // outer key // outerInput = subcredential || publishedTimestamp uint8_t subcredential[36]; @@ -518,10 +517,10 @@ namespace data // innerSalt = innerCiphertext[0:32] // keys = HKDF(innerSalt, innerInput, "ELS2_L2K", 44) uint8_t innerInput[68]; - size_t authDataLen = ExtractClientAuthData (outerPlainText.data (), lenOuterPlaintext, secret, subcredential, innerInput); + size_t authDataLen = ExtractClientAuthData (outerPlainText.data (), lenOuterPlaintext, secret, subcredential, innerInput); if (authDataLen > 0) { - memcpy (innerInput + 32, subcredential, 36); + memcpy (innerInput + 32, subcredential, 36); i2p::crypto::HKDF (outerPlainText.data () + 1 + authDataLen, innerInput, 68, "ELS2_L2K", keys); } else @@ -537,20 +536,20 @@ namespace data if (innerPlainText[0] == NETDB_STORE_TYPE_STANDARD_LEASESET2 || innerPlainText[0] == NETDB_STORE_TYPE_META_LEASESET2) { // override store type and buffer - m_StoreType = innerPlainText[0]; + m_StoreType = innerPlainText[0]; SetBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1); // parse and verify Layer 2 ReadFromBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1); } else LogPrint (eLogError, "LeaseSet2: unexpected LeaseSet type ", (int)innerPlainText[0], " inside encrypted LeaseSet"); - } + } else { // we set actual length of encrypted buffer offset += m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : blindedVerifier->GetSignatureLen (); SetBufferLen (offset); - } + } } // helper for ExtractClientAuthData @@ -563,12 +562,12 @@ namespace data { // clientKey_i = okm[0:31] // clientIV_i = okm[32:43] - i2p::crypto::ChaCha20 (authClients + i*40 + 8, 32, okm, okm + 32, authCookie); // clientCookie_i + i2p::crypto::ChaCha20 (authClients + i*40 + 8, 32, okm, okm + 32, authCookie); // clientCookie_i return true; - } + } } - return false; - } + return false; + } size_t LeaseSet2::ExtractClientAuthData (const uint8_t * buf, size_t len, const uint8_t * secret, const uint8_t * subcredential, uint8_t * authCookie) const { @@ -581,7 +580,7 @@ namespace data const uint8_t * ephemeralPublicKey = buf + offset; offset += 32; // ephemeralPublicKey uint16_t numClients = bufbe16toh (buf + offset); offset += 2; // clients const uint8_t * authClients = buf + offset; offset += numClients*40; // authClients - if (offset > len) + if (offset > len) { LogPrint (eLogError, "LeaseSet2: Too many clients ", numClients, " in DH auth data"); return 0; @@ -595,19 +594,19 @@ namespace data memcpy (authInput + 32, ck.GetPublicKey (), 32); // cpk_i memcpy (authInput + 64, subcredential, 36); uint8_t okm[64]; // 52 actual data - i2p::crypto::HKDF (ephemeralPublicKey, authInput, 100, "ELS2_XCA", okm); + i2p::crypto::HKDF (ephemeralPublicKey, authInput, 100, "ELS2_XCA", okm); if (!GetAuthCookie (authClients, numClients, okm, authCookie)) - LogPrint (eLogError, "LeaseSet2: Client cookie DH not found"); + LogPrint (eLogError, "LeaseSet2: Client cookie DH not found"); } else - LogPrint (eLogError, "LeaseSet2: Can't calculate authCookie: csk_i is not provided"); + LogPrint (eLogError, "LeaseSet2: Can't calculate authCookie: csk_i is not provided"); } else if (flag & 0x02) // PSK, bit 1 is set to 1 { const uint8_t * authSalt = buf + offset; offset += 32; // authSalt uint16_t numClients = bufbe16toh (buf + offset); offset += 2; // clients const uint8_t * authClients = buf + offset; offset += numClients*40; // authClients - if (offset > len) + if (offset > len) { LogPrint (eLogError, "LeaseSet2: Too many clients ", numClients, " in PSK auth data"); return 0; @@ -619,7 +618,7 @@ namespace data memcpy (authInput, secret, 32); memcpy (authInput + 32, subcredential, 36); uint8_t okm[64]; // 52 actual data - i2p::crypto::HKDF (authSalt, authInput, 68, "ELS2PSKA", okm); + i2p::crypto::HKDF (authSalt, authInput, 68, "ELS2PSKA", okm); if (!GetAuthCookie (authClients, numClients, okm, authCookie)) LogPrint (eLogError, "LeaseSet2: Client cookie PSK not found"); } @@ -627,7 +626,7 @@ namespace data LogPrint (eLogError, "LeaseSet2: Can't calculate authCookie: psk_i is not provided"); } else - LogPrint (eLogError, "LeaseSet2: unknown client auth type ", (int)flag); + LogPrint (eLogError, "LeaseSet2: unknown client auth type ", (int)flag); } return offset - 1; } @@ -636,7 +635,7 @@ namespace data { auto encryptor = m_Encryptor; // TODO: atomic if (encryptor) - encryptor->Encrypt (data, encrypted, ctx, true); + encryptor->Encrypt (data, encrypted, ctx, true); } uint64_t LeaseSet2::ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const @@ -648,7 +647,7 @@ namespace data uint64_t LeaseSet2::ExtractPublishedTimestamp (const uint8_t * buf, size_t len, uint64_t& expiration) const { - if (len < 8) return 0; + if (len < 8) return 0; if (m_StoreType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) { // encrypted LS2 @@ -656,11 +655,11 @@ namespace data uint16_t blindedKeyType = bufbe16toh (buf + offset); offset += 2; std::unique_ptr blindedVerifier (i2p::data::IdentityEx::CreateVerifier (blindedKeyType)); if (!blindedVerifier) return 0 ; - auto blindedKeyLen = blindedVerifier->GetPublicKeyLen (); + auto blindedKeyLen = blindedVerifier->GetPublicKeyLen (); if (offset + blindedKeyLen + 6 >= len) return 0; offset += blindedKeyLen; - uint32_t timestamp = bufbe32toh (buf + offset); offset += 4; - uint16_t expires = bufbe16toh (buf + offset); offset += 2; + uint32_t timestamp = bufbe32toh (buf + offset); offset += 4; + uint16_t expires = bufbe16toh (buf + offset); offset += 2; expiration = (timestamp + expires)* 1000LL; return timestamp; } @@ -670,13 +669,13 @@ namespace data if (!identity) return 0; size_t offset = identity->GetFullLen (); if (offset + 6 >= len) return 0; - uint32_t timestamp = bufbe32toh (buf + offset); offset += 4; - uint16_t expires = bufbe16toh (buf + offset); offset += 2; + uint32_t timestamp = bufbe32toh (buf + offset); offset += 4; + uint16_t expires = bufbe16toh (buf + offset); offset += 2; expiration = (timestamp + expires)* 1000LL; return timestamp; } - } - + } + LocalLeaseSet::LocalLeaseSet (std::shared_ptr identity, const uint8_t * encryptionPublicKey, std::vector > tunnels): m_ExpirationTime (0), m_Identity (identity) { @@ -770,36 +769,35 @@ namespace data return ident.Verify(ptr, leases - ptr, leases); } - LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, - const KeySections& encryptionKeys, - std::vector > tunnels, + LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, + const KeySections& encryptionKeys, std::vector > tunnels, bool isPublic, bool isPublishedEncrypted): LocalLeaseSet (keys.GetPublic (), nullptr, 0) { auto identity = keys.GetPublic (); - // assume standard LS2 + // assume standard LS2 int num = tunnels.size (); if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES; size_t keySectionsLen = 0; for (const auto& it: encryptionKeys) - keySectionsLen += 2/*key type*/ + 2/*key len*/ + it.keyLen/*key*/; + keySectionsLen += 2/*key type*/ + 2/*key len*/ + it.keyLen/*key*/; m_BufferLen = identity->GetFullLen () + 4/*published*/ + 2/*expires*/ + 2/*flag*/ + 2/*properties len*/ + 1/*num keys*/ + keySectionsLen + 1/*num leases*/ + num*LEASE2_SIZE + keys.GetSignatureLen (); uint16_t flags = 0; - if (keys.IsOfflineSignature ()) + if (keys.IsOfflineSignature ()) { flags |= LEASESET2_FLAG_OFFLINE_KEYS; - m_BufferLen += keys.GetOfflineSignature ().size (); + m_BufferLen += keys.GetOfflineSignature ().size (); } - if (isPublishedEncrypted) + if (isPublishedEncrypted) { flags |= LEASESET2_FLAG_PUBLISHED_ENCRYPTED; - isPublic = true; + isPublic = true; } if (!isPublic) flags |= LEASESET2_FLAG_UNPUBLISHED_LEASESET; m_Buffer = new uint8_t[m_BufferLen + 1]; - m_Buffer[0] = storeType; + m_Buffer[0] = storeType; // LS2 header auto offset = identity->ToBuffer (m_Buffer + 1, m_BufferLen) + 1; auto timestamp = i2p::util::GetSecondsSinceEpoch (); @@ -814,14 +812,14 @@ namespace data offset += offlineSignature.size (); } htobe16buf (m_Buffer + offset, 0); offset += 2; // properties len - // keys + // keys m_Buffer[offset] = encryptionKeys.size (); offset++; // 1 key for (const auto& it: encryptionKeys) - { - htobe16buf (m_Buffer + offset, it.keyType); offset += 2; // key type - htobe16buf (m_Buffer + offset, it.keyLen); offset += 2; // key len + { + htobe16buf (m_Buffer + offset, it.keyType); offset += 2; // key type + htobe16buf (m_Buffer + offset, it.keyLen); offset += 2; // key len memcpy (m_Buffer + offset, it.encryptionPublicKey, it.keyLen); offset += it.keyLen; // key - } + } // leases uint32_t expirationTime = 0; // in seconds m_Buffer[offset] = num; offset++; // num leases @@ -835,11 +833,11 @@ namespace data if (ts > expirationTime) expirationTime = ts; htobe32buf (m_Buffer + offset, ts); offset += 4; // end date - } + } // update expiration - SetExpirationTime (expirationTime*1000LL); + SetExpirationTime (expirationTime*1000LL); auto expires = expirationTime - timestamp; - htobe16buf (expiresBuf, expires > 0 ? expires : 0); + htobe16buf (expiresBuf, expires > 0 ? expires : 0); // sign keys.Sign (m_Buffer, offset, m_Buffer + offset); // LS + leading store type } @@ -853,7 +851,7 @@ namespace data m_Buffer[0] = storeType; } - LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys, + LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys, int authType, std::shared_ptr > authKeys): LocalLeaseSet2 (ls->GetIdentity ()), m_InnerLeaseSet (ls) { @@ -863,16 +861,16 @@ namespace data { if (authType == ENCRYPTED_LEASESET_AUTH_TYPE_DH) layer1Flags |= 0x01; // DH, authentication scheme 0, auth bit 1 else if (authType == ENCRYPTED_LEASESET_AUTH_TYPE_PSK) layer1Flags |= 0x03; // PSK, authentication scheme 1, auth bit 1 - if (layer1Flags) + if (layer1Flags) lenOuterPlaintext += 32 + 2 + authKeys->size ()*40; // auth data len - } + } size_t lenOuterCiphertext = lenOuterPlaintext + 32; - - m_BufferLen = 2/*blinded sig type*/ + 32/*blinded pub key*/ + 4/*published*/ + 2/*expires*/ + 2/*flags*/ + 2/*lenOuterCiphertext*/ + lenOuterCiphertext + 64/*signature*/; - m_Buffer = new uint8_t[m_BufferLen + 1]; + + m_BufferLen = 2/*blinded sig type*/ + 32/*blinded pub key*/ + 4/*published*/ + 2/*expires*/ + 2/*flags*/ + 2/*lenOuterCiphertext*/ + lenOuterCiphertext + 64/*signature*/; + m_Buffer = new uint8_t[m_BufferLen + 1]; m_Buffer[0] = NETDB_STORE_TYPE_ENCRYPTED_LEASESET2; BlindedPublicKey blindedKey (ls->GetIdentity ()); - auto timestamp = i2p::util::GetSecondsSinceEpoch (); + auto timestamp = i2p::util::GetSecondsSinceEpoch (); char date[9]; i2p::util::GetDateString (timestamp, date); uint8_t blindedPriv[64], blindedPub[128]; // 64 and 128 max @@ -883,25 +881,25 @@ namespace data memcpy (m_Buffer + offset, blindedPub, publicKeyLen); offset += publicKeyLen; // Blinded Public Key htobe32buf (m_Buffer + offset, timestamp); offset += 4; // published timestamp (seconds) auto nextMidnight = (timestamp/86400LL + 1)*86400LL; // 86400 = 24*3600 seconds - auto expirationTime = ls->GetExpirationTime ()/1000LL; + auto expirationTime = ls->GetExpirationTime ()/1000LL; if (expirationTime > nextMidnight) expirationTime = nextMidnight; SetExpirationTime (expirationTime*1000LL); htobe16buf (m_Buffer + offset, expirationTime > timestamp ? expirationTime - timestamp : 0); offset += 2; // expires uint16_t flags = 0; - htobe16buf (m_Buffer + offset, flags); offset += 2; // flags + htobe16buf (m_Buffer + offset, flags); offset += 2; // flags htobe16buf (m_Buffer + offset, lenOuterCiphertext); offset += 2; // lenOuterCiphertext // outerChipherText - // Layer 1 + // Layer 1 uint8_t subcredential[36]; blindedKey.GetSubcredential (blindedPub, 32, subcredential); htobe32buf (subcredential + 32, timestamp); // outerInput = subcredential || publishedTimestamp // keys = HKDF(outerSalt, outerInput, "ELS2_L1K", 44) uint8_t keys1[64]; // 44 bytes actual data - RAND_bytes (m_Buffer + offset, 32); // outerSalt = CSRNG(32) + RAND_bytes (m_Buffer + offset, 32); // outerSalt = CSRNG(32) i2p::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L1K", keys1); offset += 32; // outerSalt - uint8_t * outerPlainText = m_Buffer + offset; - m_Buffer[offset] = layer1Flags; offset++; // layer 1 flags + uint8_t * outerPlainText = m_Buffer + offset; + m_Buffer[offset] = layer1Flags; offset++; // layer 1 flags // auth data uint8_t innerInput[68]; // authCookie || subcredential || publishedTimestamp if (layer1Flags) @@ -909,20 +907,20 @@ namespace data RAND_bytes (innerInput, 32); // authCookie CreateClientAuthData (subcredential, authType, authKeys, innerInput, m_Buffer + offset); offset += 32 + 2 + authKeys->size ()*40; // auth clients - } + } // Layer 2 // keys = HKDF(outerSalt, outerInput, "ELS2_L2K", 44) uint8_t keys2[64]; // 44 bytes actual data - RAND_bytes (m_Buffer + offset, 32); // innerSalt = CSRNG(32) + RAND_bytes (m_Buffer + offset, 32); // innerSalt = CSRNG(32) if (layer1Flags) { memcpy (innerInput + 32, subcredential, 36); // + subcredential || publishedTimestamp - i2p::crypto::HKDF (m_Buffer + offset, innerInput, 68, "ELS2_L2K", keys2); + i2p::crypto::HKDF (m_Buffer + offset, innerInput, 68, "ELS2_L2K", keys2); } else i2p::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L2K", keys2); // no authCookie - offset += 32; // innerSalt - m_Buffer[offset] = ls->GetStoreType (); + offset += 32; // innerSalt + m_Buffer[offset] = ls->GetStoreType (); memcpy (m_Buffer + offset + 1, ls->GetBuffer (), ls->GetBufferLen ()); i2p::crypto::ChaCha20 (m_Buffer + offset, lenInnerPlaintext, keys2, keys2 + 32, m_Buffer + offset); // encrypt Layer 2 offset += lenInnerPlaintext; @@ -930,14 +928,14 @@ namespace data // signature blindedSigner->Sign (m_Buffer, offset, m_Buffer + offset); // store hash - m_StoreHash = blindedKey.GetStoreHash (date); + m_StoreHash = blindedKey.GetStoreHash (date); } LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr identity, const uint8_t * buf, size_t len): - LocalLeaseSet2 (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2, identity, buf, len) + LocalLeaseSet2 (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2, identity, buf, len) { - // fill inner LeaseSet2 - auto blindedKey = std::make_shared(identity); + // fill inner LeaseSet2 + auto blindedKey = std::make_shared(identity); i2p::data::LeaseSet2 ls (buf, len, blindedKey); // inner layer if (ls.IsValid ()) { @@ -945,10 +943,10 @@ namespace data m_StoreHash = blindedKey->GetStoreHash (); } else - LogPrint (eLogError, "LeaseSet2: couldn't extract inner layer"); + LogPrint (eLogError, "LeaseSet2: couldn't extract inner layer"); } - void LocalEncryptedLeaseSet2::CreateClientAuthData (const uint8_t * subcredential, int authType, std::shared_ptr > authKeys, const uint8_t * authCookie, uint8_t * authData) const + void LocalEncryptedLeaseSet2::CreateClientAuthData (const uint8_t * subcredential, int authType, std::shared_ptr > authKeys, const uint8_t * authCookie, uint8_t * authData) const { if (authType == ENCRYPTED_LEASESET_AUTH_TYPE_DH) { @@ -963,9 +961,9 @@ namespace data ek.Agree (it, authInput); // sharedSecret = DH(esk, cpk_i) memcpy (authInput + 32, it, 32); uint8_t okm[64]; // 52 actual data - i2p::crypto::HKDF (ek.GetPublicKey (), authInput, 100, "ELS2_XCA", okm); + i2p::crypto::HKDF (ek.GetPublicKey (), authInput, 100, "ELS2_XCA", okm); memcpy (authData, okm + 44, 8); authData += 8; // clientID_i - i2p::crypto::ChaCha20 (authCookie, 32, okm, okm + 32, authData); authData += 32; // clientCookie_i + i2p::crypto::ChaCha20 (authCookie, 32, okm, okm + 32, authData); authData += 32; // clientCookie_i } } else // assume PSK @@ -980,10 +978,10 @@ namespace data { memcpy (authInput, it, 32); uint8_t okm[64]; // 52 actual data - i2p::crypto::HKDF (authSalt, authInput, 68, "ELS2PSKA", okm); + i2p::crypto::HKDF (authSalt, authInput, 68, "ELS2PSKA", okm); memcpy (authData, okm + 44, 8); authData += 8; // clientID_i - i2p::crypto::ChaCha20 (authCookie, 32, okm, okm + 32, authData); authData += 32; // clientCookie_i - } + i2p::crypto::ChaCha20 (authCookie, 32, okm, okm + 32, authData); authData += 32; // clientCookie_i + } } } } diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index 4d084da3..479594e1 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -48,11 +48,11 @@ namespace data }; }; - typedef std::function LeaseInspectFunc; + typedef std::function LeaseInspectFunc; const size_t MAX_LS_BUFFER_SIZE = 3072; const size_t LEASE_SIZE = 44; // 32 + 4 + 8 - const size_t LEASE2_SIZE = 40; // 32 + 4 + 4 + const size_t LEASE2_SIZE = 40; // 32 + 4 + 4 const uint8_t MAX_NUM_LEASES = 16; const uint8_t NETDB_STORE_TYPE_LEASESET = 1; @@ -70,7 +70,7 @@ namespace data size_t GetBufferLen () const { return m_BufferLen; }; bool IsValid () const { return m_IsValid; }; const std::vector > GetNonExpiredLeases (bool withThreshold = true) const; - const std::vector > GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold = true) const; + const std::vector > GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold = true) const; bool HasExpiredLeases () const; bool IsExpired () const; bool IsEmpty () const { return m_Leases.empty (); }; @@ -80,7 +80,7 @@ namespace data { return m_BufferLen == other.m_BufferLen && !memcmp (m_Buffer, other.m_Buffer, m_BufferLen); }; virtual uint8_t GetStoreType () const { return NETDB_STORE_TYPE_LEASESET; }; virtual uint32_t GetPublishedTimestamp () const { return 0; }; // should be set for LeaseSet2 only - virtual std::shared_ptr GetTransientVerifier () const { return nullptr; }; + virtual std::shared_ptr GetTransientVerifier () const { return nullptr; }; virtual bool IsPublishedEncrypted () const { return false; }; // implements RoutingDestination @@ -97,7 +97,7 @@ namespace data // called from LeaseSet2 LeaseSet (bool storeLeases); void SetBuffer (const uint8_t * buf, size_t len); - void SetBufferLen (size_t len); + void SetBufferLen (size_t len); void SetIdentity (std::shared_ptr identity) { m_Identity = identity; }; void SetExpirationTime (uint64_t t) { m_ExpirationTime = t; }; void SetIsValid (bool isValid) { m_IsValid = isValid; }; @@ -130,7 +130,7 @@ namespace data const uint8_t NETDB_STORE_TYPE_META_LEASESET2 = 7; const uint16_t LEASESET2_FLAG_OFFLINE_KEYS = 0x0001; - const uint16_t LEASESET2_FLAG_UNPUBLISHED_LEASESET = 0x0002; + const uint16_t LEASESET2_FLAG_UNPUBLISHED_LEASESET = 0x0002; const uint16_t LEASESET2_FLAG_PUBLISHED_ENCRYPTED = 0x0004; class LeaseSet2: public LeaseSet @@ -167,7 +167,7 @@ namespace data private: - uint8_t m_StoreType; + uint8_t m_StoreType; uint32_t m_PublishedTimestamp = 0; bool m_IsPublic = true, m_IsPublishedEncrypted = false; std::shared_ptr m_TransientVerifier; @@ -175,7 +175,7 @@ namespace data std::shared_ptr m_Encryptor; // for standardLS2 }; - // also called from Streaming.cpp + // also called from Streaming.cpp template std::shared_ptr ProcessOfflineSignature (const Verifier& verifier, const uint8_t * buf, size_t len, size_t& offset) { @@ -191,7 +191,7 @@ namespace data transientVerifier->SetPublicKey (buf + offset); offset += keyLen; if (offset + verifier->GetSignatureLen () >= len) return nullptr; if (!verifier->Verify (signedData, keyLen + 6, buf + offset)) return nullptr; - offset += verifier->GetSignatureLen (); + offset += verifier->GetSignatureLen (); return transientVerifier; } @@ -238,17 +238,18 @@ namespace data { uint16_t keyType, keyLen; const uint8_t * encryptionPublicKey; - }; + }; typedef std::vector KeySections; - - LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, - const KeySections& encryptionKeys, - std::vector > tunnels, + + LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys, + const KeySections& encryptionKeys, + std::vector > tunnels, bool isPublic, bool isPublishedEncrypted = false); - LocalLeaseSet2 (uint8_t storeType, std::shared_ptr identity, const uint8_t * buf, size_t len); // from I2CP - + + LocalLeaseSet2 (uint8_t storeType, std::shared_ptr identity, const uint8_t * buf, size_t len); // from I2CP + virtual ~LocalLeaseSet2 () { delete[] m_Buffer; }; - + uint8_t * GetBuffer () const { return m_Buffer + 1; }; size_t GetBufferLen () const { return m_BufferLen; }; @@ -269,13 +270,13 @@ namespace data const int ENCRYPTED_LEASESET_AUTH_TYPE_DH = 1; const int ENCRYPTED_LEASESET_AUTH_TYPE_PSK = 2; - typedef i2p::data::Tag<32> AuthPublicKey; + typedef i2p::data::Tag<32> AuthPublicKey; class LocalEncryptedLeaseSet2: public LocalLeaseSet2 { public: - LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys, int authType = ENCRYPTED_LEASESET_AUTH_TYPE_NONE, std::shared_ptr > authKeys = nullptr); + LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys, int authType = ENCRYPTED_LEASESET_AUTH_TYPE_NONE, std::shared_ptr > authKeys = nullptr); LocalEncryptedLeaseSet2 (std::shared_ptr identity, const uint8_t * buf, size_t len); // from I2CP diff --git a/libi2pd/LittleBigEndian.h b/libi2pd/LittleBigEndian.h index 69f10ee9..55039fb6 100644 --- a/libi2pd/LittleBigEndian.h +++ b/libi2pd/LittleBigEndian.h @@ -29,35 +29,35 @@ struct BigEndian; template struct LittleEndian { - union - { - unsigned char bytes[sizeof(T)]; - T raw_value; - }; + union + { + unsigned char bytes[sizeof(T)]; + T raw_value; + }; - LittleEndian(T t = T()) - { - operator =(t); - } + LittleEndian(T t = T()) + { + operator =(t); + } - LittleEndian(const LittleEndian & t) - { - raw_value = t.raw_value; - } + LittleEndian(const LittleEndian & t) + { + raw_value = t.raw_value; + } - LittleEndian(const BigEndian & t) - { - for (unsigned i = 0; i < sizeof(T); i++) - bytes[i] = t.bytes[sizeof(T)-1-i]; - } + LittleEndian(const BigEndian & t) + { + for (unsigned i = 0; i < sizeof(T); i++) + bytes[i] = t.bytes[sizeof(T)-1-i]; + } - operator const T() const - { - T t = T(); - for (unsigned i = 0; i < sizeof(T); i++) - t |= T(bytes[i]) << (i << 3); - return t; - } + operator const T() const + { + T t = T(); + for (unsigned i = 0; i < sizeof(T); i++) + t |= T(bytes[i]) << (i << 3); + return t; + } const T operator = (const T t) { @@ -66,68 +66,68 @@ struct LittleEndian return t; } - // operators + // operators - const T operator += (const T t) - { - return (*this = *this + t); - } + const T operator += (const T t) + { + return (*this = *this + t); + } - const T operator -= (const T t) - { - return (*this = *this - t); - } + const T operator -= (const T t) + { + return (*this = *this - t); + } - const T operator *= (const T t) - { - return (*this = *this * t); - } + const T operator *= (const T t) + { + return (*this = *this * t); + } - const T operator /= (const T t) - { - return (*this = *this / t); - } + const T operator /= (const T t) + { + return (*this = *this / t); + } - const T operator %= (const T t) - { - return (*this = *this % t); - } + const T operator %= (const T t) + { + return (*this = *this % t); + } - LittleEndian operator ++ (int) - { - LittleEndian tmp(*this); - operator ++ (); - return tmp; - } + LittleEndian operator ++ (int) + { + LittleEndian tmp(*this); + operator ++ (); + return tmp; + } - LittleEndian & operator ++ () - { - for (unsigned i = 0; i < sizeof(T); i++) - { - ++bytes[i]; - if (bytes[i] != 0) - break; - } - return (*this); - } + LittleEndian & operator ++ () + { + for (unsigned i = 0; i < sizeof(T); i++) + { + ++bytes[i]; + if (bytes[i] != 0) + break; + } + return (*this); + } - LittleEndian operator -- (int) - { - LittleEndian tmp(*this); - operator -- (); - return tmp; - } + LittleEndian operator -- (int) + { + LittleEndian tmp(*this); + operator -- (); + return tmp; + } - LittleEndian & operator -- () - { - for (unsigned i = 0; i < sizeof(T); i++) - { - --bytes[i]; - if (bytes[i] != (T)(-1)) - break; - } - return (*this); - } + LittleEndian & operator -- () + { + for (unsigned i = 0; i < sizeof(T); i++) + { + --bytes[i]; + if (bytes[i] != (T)(-1)) + break; + } + return (*this); + } }; #pragma pack(pop) @@ -137,105 +137,105 @@ struct LittleEndian template struct BigEndian { - union - { - unsigned char bytes[sizeof(T)]; - T raw_value; - }; + union + { + unsigned char bytes[sizeof(T)]; + T raw_value; + }; - BigEndian(T t = T()) - { - operator =(t); - } + BigEndian(T t = T()) + { + operator =(t); + } - BigEndian(const BigEndian & t) - { - raw_value = t.raw_value; - } + BigEndian(const BigEndian & t) + { + raw_value = t.raw_value; + } - BigEndian(const LittleEndian & t) - { - for (unsigned i = 0; i < sizeof(T); i++) - bytes[i] = t.bytes[sizeof(T)-1-i]; - } + BigEndian(const LittleEndian & t) + { + for (unsigned i = 0; i < sizeof(T); i++) + bytes[i] = t.bytes[sizeof(T)-1-i]; + } - operator const T() const - { - T t = T(); - for (unsigned i = 0; i < sizeof(T); i++) - t |= T(bytes[sizeof(T) - 1 - i]) << (i << 3); - return t; - } + operator const T() const + { + T t = T(); + for (unsigned i = 0; i < sizeof(T); i++) + t |= T(bytes[sizeof(T) - 1 - i]) << (i << 3); + return t; + } - const T operator = (const T t) - { - for (unsigned i = 0; i < sizeof(T); i++) - bytes[sizeof(T) - 1 - i] = t >> (i << 3); - return t; - } + const T operator = (const T t) + { + for (unsigned i = 0; i < sizeof(T); i++) + bytes[sizeof(T) - 1 - i] = t >> (i << 3); + return t; + } - // operators + // operators - const T operator += (const T t) - { - return (*this = *this + t); - } + const T operator += (const T t) + { + return (*this = *this + t); + } - const T operator -= (const T t) - { - return (*this = *this - t); - } + const T operator -= (const T t) + { + return (*this = *this - t); + } - const T operator *= (const T t) - { - return (*this = *this * t); - } + const T operator *= (const T t) + { + return (*this = *this * t); + } - const T operator /= (const T t) - { - return (*this = *this / t); - } + const T operator /= (const T t) + { + return (*this = *this / t); + } - const T operator %= (const T t) - { - return (*this = *this % t); - } + const T operator %= (const T t) + { + return (*this = *this % t); + } - BigEndian operator ++ (int) - { - BigEndian tmp(*this); - operator ++ (); - return tmp; - } + BigEndian operator ++ (int) + { + BigEndian tmp(*this); + operator ++ (); + return tmp; + } - BigEndian & operator ++ () - { - for (unsigned i = 0; i < sizeof(T); i++) - { - ++bytes[sizeof(T) - 1 - i]; - if (bytes[sizeof(T) - 1 - i] != 0) - break; - } - return (*this); - } + BigEndian & operator ++ () + { + for (unsigned i = 0; i < sizeof(T); i++) + { + ++bytes[sizeof(T) - 1 - i]; + if (bytes[sizeof(T) - 1 - i] != 0) + break; + } + return (*this); + } - BigEndian operator -- (int) - { - BigEndian tmp(*this); - operator -- (); - return tmp; - } + BigEndian operator -- (int) + { + BigEndian tmp(*this); + operator -- (); + return tmp; + } - BigEndian & operator -- () - { - for (unsigned i = 0; i < sizeof(T); i++) - { - --bytes[sizeof(T) - 1 - i]; - if (bytes[sizeof(T) - 1 - i] != (T)(-1)) - break; - } - return (*this); - } + BigEndian & operator -- () + { + for (unsigned i = 0; i < sizeof(T); i++) + { + --bytes[sizeof(T) - 1 - i]; + if (bytes[sizeof(T) - 1 - i] != (T)(-1)) + break; + } + return (*this); + } }; #pragma pack(pop) diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index 65602674..6f2eb5bd 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -110,18 +110,18 @@ namespace log { } } - std::string str_tolower(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), - // static_cast(std::tolower) // wrong - // [](int c){ return std::tolower(c); } // wrong - // [](char c){ return std::tolower(c); } // wrong - [](unsigned char c){ return std::tolower(c); } // correct - ); - return s; - } + std::string str_tolower(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), + // static_cast(std::tolower) // wrong + // [](int c){ return std::tolower(c); } // wrong + // [](char c){ return std::tolower(c); } // wrong + [](unsigned char c){ return std::tolower(c); } // correct + ); + return s; + } - void Log::SetLogLevel (const std::string& level_) { - std::string level=str_tolower(level_); + void Log::SetLogLevel (const std::string& level_) { + std::string level=str_tolower(level_); if (level == "none") { m_MinLevel = eLogNone; } else if (level == "error") { m_MinLevel = eLogError; } else if (level == "warn") { m_MinLevel = eLogWarning; } @@ -240,7 +240,7 @@ namespace log { static ThrowFunction g_ThrowFunction; ThrowFunction GetThrowFunction () { return g_ThrowFunction; } void SetThrowFunction (ThrowFunction f) { g_ThrowFunction = f; } - + } // log } // i2p diff --git a/libi2pd/Log.h b/libi2pd/Log.h index 556654e2..fbd122a8 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -155,9 +155,9 @@ namespace log { typedef std::function ThrowFunction; ThrowFunction GetThrowFunction (); - void SetThrowFunction (ThrowFunction f); + void SetThrowFunction (ThrowFunction f); } // log -} +} // i2p /** internal usage only -- folding args array to single string */ template @@ -203,7 +203,7 @@ void LogPrint (LogLevel level, TArgs&&... args) noexcept } /** - * @brief Throw fatal error message with the list of arguments + * @brief Throw fatal error message with the list of arguments * @param args Array of message parts */ template diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index e52f0111..eec3a5a5 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -114,7 +114,7 @@ namespace transport void NTCP2Establisher::KDF2Bob () { - KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ()); + KeyDerivationFunction2 (m_SessionRequestBuffer, m_SessionRequestBufferLen, GetPub ()); } void NTCP2Establisher::KDF3Alice () @@ -707,7 +707,7 @@ namespace transport auto existing = i2p::data::netdb.FindRouter (ri.GetRouterIdentity ()->GetIdentHash ()); // check if exists already SetRemoteIdentity (existing ? existing->GetRouterIdentity () : ri.GetRouterIdentity ()); if (m_Server.AddNTCP2Session (shared_from_this (), true)) - { + { Established (); ReceiveLength (); } @@ -1200,7 +1200,7 @@ namespace transport continue; } - LogPrint (eLogInfo, "NTCP2: Start listening v6 TCP port ", address->port); + LogPrint (eLogInfo, "NTCP2: Start listening v4 TCP port ", address->port); auto conn = std::make_shared(*this); m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, conn, std::placeholders::_1)); } @@ -1218,8 +1218,8 @@ namespace transport LogPrint (eLogInfo, "NTCP2: Start listening v6 TCP port ", address->port); auto conn = std::make_shared (*this); m_NTCP2V6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAcceptV6, this, conn, std::placeholders::_1)); - } - catch ( std::exception & ex ) + } + catch ( std::exception & ex ) { LogPrint(eLogError, "NTCP2: failed to bind to v6 port ", address->port, ": ", ex.what()); ThrowFatal ("Unable to start IPv6 NTCP2 transport at port ", address->port, ": ", ex.what ()); @@ -1485,7 +1485,7 @@ namespace transport } }); auto readbuff = std::make_shared >(2); - boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 2), + boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff->data (), 2), [this, readbuff, timer, conn, host, port, addrtype](const boost::system::error_code & ec, std::size_t transferred) { if(ec) @@ -1531,8 +1531,8 @@ namespace transport std::ostream out(&writebuff); out << req.to_string(); - boost::asio::async_write(conn->GetSocket(), writebuff.data(), boost::asio::transfer_all(), - [](const boost::system::error_code & ec, std::size_t transferred) + boost::asio::async_write(conn->GetSocket(), writebuff.data(), boost::asio::transfer_all(), + [](const boost::system::error_code & ec, std::size_t transferred) { (void) transferred; if(ec) @@ -1540,8 +1540,8 @@ namespace transport }); boost::asio::streambuf * readbuff = new boost::asio::streambuf; - boost::asio::async_read_until(conn->GetSocket(), *readbuff, "\r\n\r\n", - [this, readbuff, timer, conn] (const boost::system::error_code & ec, std::size_t transferred) + boost::asio::async_read_until(conn->GetSocket(), *readbuff, "\r\n\r\n", + [this, readbuff, timer, conn] (const boost::system::error_code & ec, std::size_t transferred) { if(ec) { diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index 10170fda..8b8c6acb 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -126,7 +126,6 @@ namespace transport { public: - NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter = nullptr); ~NTCP2Session (); void Terminate (); diff --git a/libi2pd/NTCPSession.cpp b/libi2pd/NTCPSession.cpp index 4ab3c5d0..56c69e05 100644 --- a/libi2pd/NTCPSession.cpp +++ b/libi2pd/NTCPSession.cpp @@ -26,7 +26,7 @@ namespace transport { std::shared_ptr session; }; - + NTCPSession::NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter): TransportSession (in_RemoteRouter, NTCP_ESTABLISH_TIMEOUT), m_Server (server), m_Socket (m_Server.GetService ()), @@ -468,7 +468,7 @@ namespace transport LogPrint (eLogError, "NTCP: Phase 4 read error: ", ecode.message (), ". Check your clock"); if (ecode != boost::asio::error::operation_aborted) { - // this router doesn't like us + // this router doesn't like us i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); Terminate (); } @@ -736,13 +736,11 @@ namespace transport } } - void NTCPSession::SendTimeSyncMessage () { Send (nullptr); } - void NTCPSession::SendI2NPMessages (const std::vector >& msgs) { m_Server.GetService ().post (std::bind (&NTCPSession::PostI2NPMessages, shared_from_this (), msgs)); @@ -821,8 +819,8 @@ namespace transport { m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port)); LogPrint (eLogInfo, "NTCP: Start listening v6 TCP port ", address->port); - } - catch ( std::exception & ex ) + } + catch ( std::exception & ex ) { /** fail to bind ip4 */ LogPrint(eLogError, "NTCP: Failed to bind to v4 port ", address->port, ": ", ex.what()); @@ -848,8 +846,8 @@ namespace transport LogPrint (eLogInfo, "NTCP: Start listening v6 TCP port ", address->port); auto conn = std::make_shared (*this); m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this, conn, std::placeholders::_1)); - } - catch ( std::exception & ex ) + } + catch ( std::exception & ex ) { LogPrint(eLogError, "NTCP: failed to bind to v6 port ", address->port, ": ", ex.what()); ThrowFatal (eLogError, "Unable to start IPv6 NTCP transport at port ", address->port, ": ", ex.what ()); @@ -904,7 +902,6 @@ namespace transport } } - void NTCPServer::Run () { while (m_IsRunning) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 86bf160e..30916f1a 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -216,10 +216,10 @@ namespace data LogPrint (eLogDebug, "NetDb: RouterInfo floodfill status updated: ", ident.ToBase64()); std::unique_lock l(m_FloodfillsMutex); if (wasFloodfill) - m_Floodfills.remove (r); - else + m_Floodfills.remove (r); + else m_Floodfills.push_back (r); - } + } } else { @@ -390,7 +390,7 @@ namespace data } } - m_Reseeder->Bootstrap (); + m_Reseeder->Bootstrap (); } void NetDb::ReseedFromFloodfill(const RouterInfo & ri, int numRouters, int numFloodfills) @@ -532,12 +532,12 @@ namespace data auto total = m_RouterInfos.size (); uint64_t expirationTimeout = NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); - auto uptime = i2p::context.GetUptime (); + auto uptime = i2p::context.GetUptime (); // routers don't expire if less than 90 or uptime is less than 1 hour bool checkForExpiration = total > NETDB_MIN_ROUTERS && uptime > 600; // 10 minutes if (checkForExpiration && uptime > 3600) // 1 hour expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL : - NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; + NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; for (auto& it: m_RouterInfos) { @@ -555,7 +555,7 @@ namespace data // find & mark expired routers if (it.second->UsesIntroducer ()) { - if (ts > it.second->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL) + if (ts > it.second->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL) // RouterInfo expires after 1 hour if uses introducer it.second->SetUnreachable (true); } @@ -873,7 +873,7 @@ namespace data std::shared_ptr replyMsg; if (lookupType == DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP) { - LogPrint (eLogInfo, "NetDb: exploratory close to ", key, " ", numExcluded, " excluded"); + LogPrint (eLogInfo, "NetDb: exploratory close to ", key, " ", numExcluded, " excluded"); std::set excludedRouters; for (int i = 0; i < numExcluded; i++) { @@ -894,7 +894,7 @@ namespace data } else { - if (lookupType == DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP || + if (lookupType == DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP || lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP) { auto router = FindRouter (ident); @@ -907,7 +907,7 @@ namespace data } } - if (!replyMsg && (lookupType == DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP || + if (!replyMsg && (lookupType == DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP || lookupType == DATABASE_LOOKUP_TYPE_NORMAL_LOOKUP)) { auto leaseSet = FindLeaseSet (ident); @@ -957,12 +957,12 @@ namespace data replyMsg = i2p::garlic::WrapECIESX25519AEADRatchetMessage (replyMsg, sessionKey, tag); } else - { + { const i2p::garlic::SessionTag sessionTag(excluded + 33); // take first tag i2p::garlic::ElGamalAESSession garlic (sessionKey, sessionTag); replyMsg = garlic.WrapSingleMessage (replyMsg); - } - if (!replyMsg) + } + if (!replyMsg) LogPrint (eLogError, "NetDb: failed to wrap message"); } else @@ -1103,7 +1103,7 @@ namespace data { return !router->IsHidden () && router->IsSSUV6 (); }); - } + } std::shared_ptr NetDb::GetRandomIntroducer () const { diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index 6e251ecb..47a6fab5 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -28,12 +28,12 @@ namespace i2p namespace data { const int NETDB_MIN_ROUTERS = 90; - const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60*60; // 1 hour, in seconds - const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65*60; - const int NETDB_MIN_EXPIRATION_TIMEOUT = 90*60; // 1.5 hours - const int NETDB_MAX_EXPIRATION_TIMEOUT = 27*60*60; // 27 hours - const int NETDB_PUBLISH_INTERVAL = 60*40; - const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0,9,36); // 0.9.36 + const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60 * 60; // 1 hour, in seconds + const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65 * 60; + const int NETDB_MIN_EXPIRATION_TIMEOUT = 90 * 60; // 1.5 hours + const int NETDB_MAX_EXPIRATION_TIMEOUT = 27 * 60 * 60; // 27 hours + const int NETDB_PUBLISH_INTERVAL = 60 * 40; + const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 36); // 0.9.36 /** function for visiting a leaseset stored in a floodfill */ typedef std::function)> LeaseSetVisitor; @@ -63,13 +63,13 @@ namespace data std::shared_ptr FindRouterProfile (const IdentHash& ident) const; void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr); - void RequestDestinationFrom (const IdentHash& destination, const IdentHash & from, bool exploritory, RequestedDestination::RequestComplete requestComplete = nullptr); + void RequestDestinationFrom (const IdentHash& destination, const IdentHash & from, bool exploritory, RequestedDestination::RequestComplete requestComplete = nullptr); void HandleDatabaseStoreMsg (std::shared_ptr msg); void HandleDatabaseSearchReplyMsg (std::shared_ptr msg); void HandleDatabaseLookupMsg (std::shared_ptr msg); void HandleNTCP2RouterInfoMsg (std::shared_ptr m); - + std::shared_ptr GetRandomRouter () const; std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith) const; std::shared_ptr GetHighBandwidthRandomRouter (std::shared_ptr compatibleWith) const; @@ -80,13 +80,13 @@ namespace data std::vector GetClosestFloodfills (const IdentHash& destination, size_t num, std::set& excluded, bool closeThanUsOnly = false) const; std::shared_ptr GetClosestNonFloodfill (const IdentHash& destination, const std::set& excluded) const; - std::shared_ptr GetRandomRouterInFamily(const std::string & fam) const; + std::shared_ptr GetRandomRouterInFamily(const std::string & fam) const; void SetUnreachable (const IdentHash& ident, bool unreachable); void PostI2NPMsg (std::shared_ptr msg); - /** set hidden mode, aka don't publish our RI to netdb and don't explore */ - void SetHidden(bool hide); + /** set hidden mode, aka don't publish our RI to netdb and don't explore */ + void SetHidden(bool hide); void Reseed (); Families& GetFamilies () { return m_Families; }; @@ -119,12 +119,13 @@ namespace data void ManageLeaseSets (); void ManageRequests (); - void ReseedFromFloodfill(const RouterInfo & ri, int numRouters=40, int numFloodfills=20); + void ReseedFromFloodfill(const RouterInfo & ri, int numRouters = 40, int numFloodfills = 20); std::shared_ptr AddRouterInfo (const uint8_t * buf, int len, bool& updated); std::shared_ptr AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len, bool& updated); - template - std::shared_ptr GetRandomRouter (Filter filter) const; + + template + std::shared_ptr GetRandomRouter (Filter filter) const; private: @@ -150,12 +151,12 @@ namespace data bool m_PersistProfiles; - /** router info we are bootstrapping from or nullptr if we are not currently doing that*/ - std::shared_ptr m_FloodfillBootstrap; + /** router info we are bootstrapping from or nullptr if we are not currently doing that*/ + std::shared_ptr m_FloodfillBootstrap; - /** true if in hidden mode */ - bool m_HiddenMode; + /** true if in hidden mode */ + bool m_HiddenMode; }; extern NetDb netdb; diff --git a/libi2pd/NetDbRequests.cpp b/libi2pd/NetDbRequests.cpp index f1853124..b1eb9380 100644 --- a/libi2pd/NetDbRequests.cpp +++ b/libi2pd/NetDbRequests.cpp @@ -14,8 +14,8 @@ namespace data std::shared_ptr msg; if(replyTunnel) msg = i2p::CreateRouterInfoDatabaseLookupMsg (m_Destination, - replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory, - &m_ExcludedPeers); + replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory, + &m_ExcludedPeers); else msg = i2p::CreateRouterInfoDatabaseLookupMsg(m_Destination, i2p::context.GetIdentHash(), 0, m_IsExploratory, &m_ExcludedPeers); if(router) @@ -158,4 +158,3 @@ namespace data } } } - diff --git a/libi2pd/NetDbRequests.h b/libi2pd/NetDbRequests.h index 7a7a55ab..c5e077bf 100644 --- a/libi2pd/NetDbRequests.h +++ b/libi2pd/NetDbRequests.h @@ -66,4 +66,3 @@ namespace data } #endif - diff --git a/libi2pd/Poly1305.cpp b/libi2pd/Poly1305.cpp index bf94c9a9..23098d74 100644 --- a/libi2pd/Poly1305.cpp +++ b/libi2pd/Poly1305.cpp @@ -7,12 +7,11 @@ */ -#if !OPENSSL_AEAD_CHACHA20_POLY1305 +#if !OPENSSL_AEAD_CHACHA20_POLY1305 namespace i2p { namespace crypto { - void Poly1305HMAC(uint64_t * out, const uint64_t * key, const uint8_t * buf, std::size_t sz) { Poly1305 p(key); diff --git a/libi2pd/Poly1305.h b/libi2pd/Poly1305.h index 800fbfd9..f91a037e 100644 --- a/libi2pd/Poly1305.h +++ b/libi2pd/Poly1305.h @@ -1,9 +1,9 @@ /** - This code is licensed under the MCGSI Public License - Copyright 2018 Jeff Becker - - Kovri go write your own code - + * This code is licensed under the MCGSI Public License + * Copyright 2018 Jeff Becker + * + * Kovri go write your own code + * */ #ifndef LIBI2PD_POLY1305_H #define LIBI2PD_POLY1305_H @@ -11,7 +11,7 @@ #include #include "Crypto.h" -#if !OPENSSL_AEAD_CHACHA20_POLY1305 +#if !OPENSSL_AEAD_CHACHA20_POLY1305 namespace i2p { namespace crypto @@ -24,7 +24,6 @@ namespace crypto namespace poly1305 { - struct LongBlock { unsigned long data[17]; @@ -252,8 +251,8 @@ namespace crypto poly1305::LongBlock m_HR; uint8_t m_Final; }; + void Poly1305HMAC(uint64_t * out, const uint64_t * key, const uint8_t * buf, std::size_t sz); - } } #endif diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 98f95dc3..4f26e520 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -32,73 +32,76 @@ namespace data { } - /** @brief tries to bootstrap into I2P network (from local files and servers, with respect of options) - */ - void Reseeder::Bootstrap () - { - std::string su3FileName; i2p::config::GetOption("reseed.file", su3FileName); - std::string zipFileName; i2p::config::GetOption("reseed.zipfile", zipFileName); + /** + @brief tries to bootstrap into I2P network (from local files and servers, with respect of options) + */ + void Reseeder::Bootstrap () + { + std::string su3FileName; i2p::config::GetOption("reseed.file", su3FileName); + std::string zipFileName; i2p::config::GetOption("reseed.zipfile", zipFileName); - if (su3FileName.length() > 0) // bootstrap from SU3 file or URL - { - int num; - if (su3FileName.length() > 8 && su3FileName.substr(0, 8) == "https://") - { - num = ReseedFromSU3Url (su3FileName); // from https URL - } - else - { - num = ProcessSU3File (su3FileName.c_str ()); - } - if (num == 0) - LogPrint (eLogWarning, "Reseed: failed to reseed from ", su3FileName); - } - else if (zipFileName.length() > 0) // bootstrap from ZIP file - { - int num = ProcessZIPFile (zipFileName.c_str ()); - if (num == 0) - LogPrint (eLogWarning, "Reseed: failed to reseed from ", zipFileName); - } - else // bootstrap from reseed servers - { - int num = ReseedFromServers (); - if (num == 0) - LogPrint (eLogWarning, "Reseed: failed to reseed from servers"); - } - } + if (su3FileName.length() > 0) // bootstrap from SU3 file or URL + { + int num; + if (su3FileName.length() > 8 && su3FileName.substr(0, 8) == "https://") + { + num = ReseedFromSU3Url (su3FileName); // from https URL + } + else + { + num = ProcessSU3File (su3FileName.c_str ()); + } + if (num == 0) + LogPrint (eLogWarning, "Reseed: failed to reseed from ", su3FileName); + } + else if (zipFileName.length() > 0) // bootstrap from ZIP file + { + int num = ProcessZIPFile (zipFileName.c_str ()); + if (num == 0) + LogPrint (eLogWarning, "Reseed: failed to reseed from ", zipFileName); + } + else // bootstrap from reseed servers + { + int num = ReseedFromServers (); + if (num == 0) + LogPrint (eLogWarning, "Reseed: failed to reseed from servers"); + } + } - /** @brief bootstrap from random server, retry 10 times - * @return number of entries added to netDb - */ + /** + * @brief bootstrap from random server, retry 10 times + * @return number of entries added to netDb + */ int Reseeder::ReseedFromServers () { std::string reseedURLs; i2p::config::GetOption("reseed.urls", reseedURLs); - std::vector httpsReseedHostList; - boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on); + std::vector httpsReseedHostList; + boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on); - if (reseedURLs.length () == 0) - { - LogPrint (eLogWarning, "Reseed: No reseed servers specified"); - return 0; - } + if (reseedURLs.length () == 0) + { + LogPrint (eLogWarning, "Reseed: No reseed servers specified"); + return 0; + } - int reseedRetries = 0; - while (reseedRetries < 10) - { - auto ind = rand () % httpsReseedHostList.size (); - std::string reseedUrl = httpsReseedHostList[ind] + "i2pseeds.su3"; - auto num = ReseedFromSU3Url (reseedUrl); - if (num > 0) return num; // success - reseedRetries++; - } - LogPrint (eLogWarning, "Reseed: failed to reseed from servers after 10 attempts"); - return 0; + int reseedRetries = 0; + while (reseedRetries < 10) + { + auto ind = rand () % httpsReseedHostList.size (); + std::string reseedUrl = httpsReseedHostList[ind] + "i2pseeds.su3"; + auto num = ReseedFromSU3Url (reseedUrl); + if (num > 0) return num; // success + reseedRetries++; + } + LogPrint (eLogWarning, "Reseed: failed to reseed from servers after 10 attempts"); + return 0; } - /** @brief bootstrap from HTTPS URL with SU3 file - * @param url - * @return number of entries added to netDb - */ + /** + * @brief bootstrap from HTTPS URL with SU3 file + * @param url + * @return number of entries added to netDb + */ int Reseeder::ReseedFromSU3Url (const std::string& url) { LogPrint (eLogInfo, "Reseed: Downloading SU3 from ", url); @@ -702,4 +705,3 @@ namespace data } } } - diff --git a/libi2pd/Reseed.h b/libi2pd/Reseed.h index a69969bf..5f402988 100644 --- a/libi2pd/Reseed.h +++ b/libi2pd/Reseed.h @@ -21,7 +21,7 @@ namespace data Reseeder(); ~Reseeder(); - void Bootstrap (); + void Bootstrap (); int ReseedFromServers (); int ReseedFromSU3Url (const std::string& url); int ProcessSU3File (const char * filename); diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index b86ed8f2..b855fc29 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -338,10 +338,10 @@ namespace i2p { case low : /* not set */; break; case extra : caps |= i2p::data::RouterInfo::eExtraBandwidth; break; // 'P' - case unlim : caps |= i2p::data::RouterInfo::eExtraBandwidth; - #if (__cplusplus >= 201703L) // C++ 17 or higher - [[fallthrough]]; - #endif + case unlim : caps |= i2p::data::RouterInfo::eExtraBandwidth; +#if (__cplusplus >= 201703L) // C++ 17 or higher + [[fallthrough]]; +#endif // no break here, extra + high means 'X' case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break; } diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 28c324c4..6382189b 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -15,7 +15,7 @@ namespace i2p { const char ROUTER_INFO[] = "router.info"; const char ROUTER_KEYS[] = "router.keys"; - const char NTCP2_KEYS[] = "ntcp2.keys"; + const char NTCP2_KEYS[] = "ntcp2.keys"; const int ROUTER_INFO_UPDATE_INTERVAL = 1800; // 30 minutes enum RouterStatus @@ -36,12 +36,12 @@ namespace i2p { private: - struct NTCP2PrivateKeys + struct NTCP2PrivateKeys { uint8_t staticPublicKey[32]; uint8_t staticPrivateKey[32]; uint8_t iv[16]; - }; + }; public: @@ -63,7 +63,7 @@ namespace i2p const uint8_t * GetNTCP2StaticPublicKey () const { return m_NTCP2Keys ? m_NTCP2Keys->staticPublicKey : nullptr; }; const uint8_t * GetNTCP2StaticPrivateKey () const { return m_NTCP2Keys ? m_NTCP2Keys->staticPrivateKey : nullptr; }; const uint8_t * GetNTCP2IV () const { return m_NTCP2Keys ? m_NTCP2Keys->iv : nullptr; }; - i2p::crypto::X25519Keys& GetStaticKeys (); + i2p::crypto::X25519Keys& GetStaticKeys (); uint32_t GetUptime () const; // in seconds uint64_t GetLastUpdateTime () const { return m_LastUpdateTime; }; @@ -77,7 +77,7 @@ namespace i2p void SetNetID (int netID) { m_NetID = netID; }; bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const; - void UpdatePort (int port); // called from Daemon + void UpdatePort (int port); // called from Daemon void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon void PublishNTCP2Address (int port, bool publish = true, bool v4only = false); void UpdateNTCP2Address (bool enable); @@ -124,7 +124,7 @@ namespace i2p // implements GarlicDestination void HandleI2NPMessage (const uint8_t * buf, size_t len); - bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) { return false; }; // not implemented + bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) { return false; }; // not implemented private: @@ -141,7 +141,7 @@ namespace i2p i2p::data::PrivateKeys m_Keys; std::shared_ptr m_Decryptor; uint64_t m_LastUpdateTime; // in seconds - bool m_AcceptsTunnels, m_IsFloodfill; + bool m_AcceptsTunnels, m_IsFloodfill; std::chrono::time_point m_StartupTime; uint64_t m_BandwidthLimit; // allowed bandwidth int m_ShareRatio; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 9b3b7e89..bf81a8af 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -35,12 +35,12 @@ namespace data } RouterInfo::RouterInfo (const uint8_t * buf, int len): - m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), + m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), m_Caps (0), m_Version (0) { m_Addresses = boost::make_shared(); // create empty list if (len <= MAX_RI_BUFFER_SIZE) - { + { m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; memcpy (m_Buffer, buf, len); m_BufferLen = len; @@ -173,7 +173,6 @@ namespace data LogPrint (eLogError, "RouterInfo: malformed message"); m_IsUnreachable = true; } - } void RouterInfo::ReadFromStream (std::istream& s) @@ -192,7 +191,7 @@ namespace data s.read ((char *)&address->cost, sizeof (address->cost)); s.read ((char *)&address->date, sizeof (address->date)); bool isNTCP2Only = false; - char transportStyle[6]; + char transportStyle[6]; auto transportStyleLen = ReadString (transportStyle, 6, s) - 1; if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2 { @@ -231,13 +230,13 @@ namespace data address->host.to_string (ecode); if (!ecode) #endif - { + { // add supported protocol if (address->host.is_v4 ()) supportedTransports |= (address->transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4; else supportedTransports |= (address->transportStyle == eTransportNTCP) ? eNTCPV6 : eSSUV6; - } + } } } else if (!strcmp (key, "port")) @@ -262,15 +261,15 @@ namespace data { if (!address->ntcp2) address->ntcp2.reset (new NTCP2Ext ()); supportedTransports |= (address->host.is_v4 ()) ? eNTCP2V4 : eNTCP2V6; - Base64ToByteStream (value, strlen (value), address->ntcp2->staticKey, 32); - } + Base64ToByteStream (value, strlen (value), address->ntcp2->staticKey, 32); + } else if (!strcmp (key, "i")) // ntcp2 iv { if (!address->ntcp2) address->ntcp2.reset (new NTCP2Ext ()); supportedTransports |= (address->host.is_v4 ()) ? eNTCP2V4 : eNTCP2V6; - Base64ToByteStream (value, strlen (value), address->ntcp2->iv, 16); + Base64ToByteStream (value, strlen (value), address->ntcp2->iv, 16); address->ntcp2->isPublished = true; // presence if "i" means "published" - } + } else if (key[0] == 'i') { // introducers @@ -278,7 +277,7 @@ namespace data { LogPrint (eLogError, "RouterInfo: Introducer is presented for non-SSU address. Skipped"); continue; - } + } introducers = true; size_t l = strlen(key); unsigned char index = key[l-1] - '0'; // TODO: @@ -309,7 +308,7 @@ namespace data } if (introducers) supportedTransports |= eSSUV4; // in case if host is not presented if (isNTCP2Only && address->ntcp2) address->ntcp2->isNTCP2Only = true; - if (supportedTransports) + if (supportedTransports) { addresses->push_back(address); m_SupportedTransports |= supportedTransports; @@ -349,13 +348,13 @@ namespace data while (*ch) { if (*ch >= '0' && *ch <= '9') - { - m_Version *= 10; + { + m_Version *= 10; m_Version += (*ch - '0'); - } + } ch++; - } - } + } + } // check netId else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != i2p::context.GetNetID ()) { @@ -384,9 +383,10 @@ namespace data SetUnreachable (true); } - bool RouterInfo::IsFamily(const std::string & fam) const { - return m_Family == fam; - } + bool RouterInfo::IsFamily(const std::string & fam) const + { + return m_Family == fam; + } void RouterInfo::ExtractCaps (const char * value) { @@ -580,7 +580,7 @@ namespace data properties << '='; WriteString (boost::lexical_cast(address.port), properties); properties << ';'; - } + } if (address.IsNTCP2 ()) { // publish s and v for NTCP2 @@ -588,7 +588,7 @@ namespace data WriteString (address.ntcp2->staticKey.ToBase64 (), properties); properties << ';'; WriteString ("v", properties); properties << '='; WriteString ("2", properties); properties << ';'; - } + } uint16_t size = htobe16 (properties.str ().size ()); s.write ((char *)&size, sizeof (size)); @@ -742,7 +742,7 @@ namespace data addr->ntcp2->isNTCP2Only = true; // NTCP2 only address if (port) addr->ntcp2->isPublished = true; memcpy (addr->ntcp2->staticKey, staticKey, 32); - memcpy (addr->ntcp2->iv, iv, 16); + memcpy (addr->ntcp2->iv, iv, 16); m_Addresses->push_back(std::move(addr)); } @@ -854,7 +854,7 @@ namespace data m_SupportedTransports |= eNTCPV6 | eSSUV6 | eNTCP2V6; } - void RouterInfo::EnableV4 () + void RouterInfo::EnableV4 () { if (!IsV4 ()) m_SupportedTransports |= eNTCPV4 | eSSUV4 | eNTCP2V4; @@ -877,7 +877,7 @@ namespace data } } - void RouterInfo::DisableV4 () + void RouterInfo::DisableV4 () { if (IsV4 ()) { @@ -926,7 +926,7 @@ namespace data }); } - template + template std::shared_ptr RouterInfo::GetAddress (Filter filter) const { // TODO: make it more generic using comparator @@ -937,7 +937,7 @@ namespace data #endif for (const auto& address : *addresses) if (filter (address)) return address; - + return nullptr; } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index f8598b1e..5bdb0f8f 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -203,8 +203,8 @@ namespace data void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; }; bool IsNewer (const uint8_t * buf, size_t len) const; - /** return true if we are in a router family and the signature is valid */ - bool IsFamily(const std::string & fam) const; + /** return true if we are in a router family and the signature is valid */ + bool IsFamily(const std::string & fam) const; // implements RoutingDestination std::shared_ptr GetIdentity () const { return m_RouterIdentity; }; diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index 74cbe39b..4a33ff3a 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -45,15 +45,15 @@ namespace transport void SSUServer::OpenSocket () { - try + try { m_Socket.open (boost::asio::ip::udp::v4()); m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); m_Socket.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); m_Socket.bind (m_Endpoint); LogPrint (eLogInfo, "SSU: Start listening v4 port ", m_Endpoint.port()); - } - catch ( std::exception & ex ) + } + catch ( std::exception & ex ) { LogPrint (eLogError, "SSU: failed to bind to v4 port ", m_Endpoint.port(), ": ", ex.what()); ThrowFatal ("Unable to start IPv4 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); @@ -62,7 +62,7 @@ namespace transport void SSUServer::OpenSocketV6 () { - try + try { m_SocketV6.open (boost::asio::ip::udp::v6()); m_SocketV6.set_option (boost::asio::ip::v6_only (true)); @@ -70,8 +70,8 @@ namespace transport m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); m_SocketV6.bind (m_EndpointV6); LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port()); - } - catch ( std::exception & ex ) + } + catch ( std::exception & ex ) { LogPrint (eLogError, "SSU: failed to bind to v6 port ", m_EndpointV6.port(), ": ", ex.what()); ThrowFatal ("Unable to start IPv6 SSU transport at port ", m_Endpoint.port(), ": ", ex.what ()); @@ -184,7 +184,7 @@ namespace transport m_Socket.close (); OpenSocket (); Receive (); - } + } } } } @@ -359,10 +359,10 @@ namespace transport { if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous { - if (session) - { - session->FlushData (); - session = nullptr; + if (session) + { + session->FlushData (); + session = nullptr; } auto it = sessions->find (packet->from); if (it != sessions->end ()) @@ -826,4 +826,3 @@ namespace transport } } } - diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h index 10e5ec06..942d0e64 100644 --- a/libi2pd/SSU.h +++ b/libi2pd/SSU.h @@ -40,7 +40,7 @@ namespace transport public: SSUServer (int port); - SSUServer (const boost::asio::ip::address & addr, int port); // ipv6 only constructor + SSUServer (const boost::asio::ip::address & addr, int port); // ipv6 only constructor ~SSUServer (); void Start (); void Stop (); @@ -135,4 +135,3 @@ namespace transport } #endif - diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 866da277..4eb90522 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -496,7 +496,7 @@ namespace transport } // decay if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || - i2p::util::GetSecondsSinceEpoch () > m_LastMessageReceivedTime + DECAY_INTERVAL) + i2p::util::GetSecondsSinceEpoch () > m_LastMessageReceivedTime + DECAY_INTERVAL) m_ReceivedMessages.clear (); ScheduleIncompleteMessagesCleanup (); @@ -504,4 +504,3 @@ namespace transport } } } - diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index fbd167bf..98d60c41 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -128,4 +128,3 @@ namespace transport } #endif - diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index 88dbcf04..e29af479 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -336,7 +336,7 @@ namespace transport m_SignedData->Insert (payload, 4); // insert Alice's signed on time payload += 4; // signed-on time size_t paddingSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen (); - paddingSize &= 0x0F; // %16 + paddingSize &= 0x0F; // %16 if (paddingSize > 0) paddingSize = 16 - paddingSize; payload += paddingSize; // verify signature @@ -934,7 +934,7 @@ namespace transport for (const auto& it: msgs) if (it) { - if (it->GetLength () <= SSU_MAX_I2NP_MESSAGE_SIZE) + if (it->GetLength () <= SSU_MAX_I2NP_MESSAGE_SIZE) m_Data.Send (it); else LogPrint (eLogError, "SSU: I2NP message of size ", it->GetLength (), " can't be sent. Dropped"); @@ -1206,4 +1206,3 @@ namespace transport } } } - diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h index 8f81838a..4f73158a 100644 --- a/libi2pd/SSUSession.h +++ b/libi2pd/SSUSession.h @@ -28,7 +28,7 @@ namespace transport const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes const int SSU_CLOCK_SKEW = 60; // in seconds - const size_t SSU_MAX_I2NP_MESSAGE_SIZE = 32768; + const size_t SSU_MAX_I2NP_MESSAGE_SIZE = 32768; // payload types (4 bits) const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0; @@ -81,12 +81,12 @@ namespace transport void Done (); void Failed (); const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; - + bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; void SendI2NPMessages (const std::vector >& msgs); void SendPeerTest (); // Alice - SessionState GetState () const { return m_State; }; + SessionState GetState () const { return m_State; }; size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; @@ -158,10 +158,7 @@ namespace transport std::unique_ptr m_SignedData; // we need it for SessionConfirmed only std::map > m_RelayRequests; // nonce->Charlie }; - - } } #endif - diff --git a/libi2pd/Signature.cpp b/libi2pd/Signature.cpp index 21d08f30..64aff5bc 100644 --- a/libi2pd/Signature.cpp +++ b/libi2pd/Signature.cpp @@ -6,11 +6,11 @@ namespace i2p { namespace crypto { -#if OPENSSL_EDDSA +#if OPENSSL_EDDSA EDDSA25519Verifier::EDDSA25519Verifier (): m_Pkey (nullptr) { - m_MDCtx = EVP_MD_CTX_create (); + m_MDCtx = EVP_MD_CTX_create (); } EDDSA25519Verifier::~EDDSA25519Verifier () @@ -23,21 +23,21 @@ namespace crypto { m_Pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, m_Pkey); - } - + } + bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { return EVP_DigestVerify (m_MDCtx, signature, 64, buf, len); } - -#else + +#else EDDSA25519Verifier::EDDSA25519Verifier () { } EDDSA25519Verifier::~EDDSA25519Verifier () { - } + } void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) { @@ -45,8 +45,8 @@ namespace crypto BN_CTX * ctx = BN_CTX_new (); m_PublicKey = GetEd25519 ()->DecodePublicKey (m_PublicKeyEncoded, ctx); BN_CTX_free (ctx); - } - + } + bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { uint8_t digest[64]; @@ -83,19 +83,19 @@ namespace crypto EDDSA25519SignerCompat::~EDDSA25519SignerCompat () { - } - + } + void EDDSA25519SignerCompat::Sign (const uint8_t * buf, int len, uint8_t * signature) const { GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature); } - -#if OPENSSL_EDDSA + +#if OPENSSL_EDDSA EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey): m_Fallback (nullptr) - { + { m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); - uint8_t publicKey[EDDSA25519_PUBLIC_KEY_LENGTH]; + uint8_t publicKey[EDDSA25519_PUBLIC_KEY_LENGTH]; size_t len = EDDSA25519_PUBLIC_KEY_LENGTH; EVP_PKEY_get_raw_public_key (m_Pkey, publicKey, &len); if (signingPublicKey && memcmp (publicKey, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH)) @@ -105,10 +105,10 @@ namespace crypto m_Fallback = new EDDSA25519SignerCompat (signingPrivateKey, signingPublicKey); } else - { - m_MDCtx = EVP_MD_CTX_create (); + { + m_MDCtx = EVP_MD_CTX_create (); EVP_DigestSignInit (m_MDCtx, NULL, NULL, NULL, m_Pkey); - } + } } EDDSA25519Signer::~EDDSA25519Signer () @@ -118,22 +118,20 @@ namespace crypto { EVP_MD_CTX_destroy (m_MDCtx); EVP_PKEY_free (m_Pkey); - } + } } void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const { if (m_Fallback) return m_Fallback->Sign (buf, len, signature); else - { - size_t l = 64; + { + size_t l = 64; uint8_t sig[64]; // temporary buffer for signature. openssl issue #7232 - EVP_DigestSign (m_MDCtx, sig, &l, buf, len); + EVP_DigestSign (m_MDCtx, sig, &l, buf, len); memcpy (signature, sig, 64); - } - } -#endif + } + } +#endif } } - - diff --git a/libi2pd/Signature.h b/libi2pd/Signature.h index 0c5f27d6..26184639 100644 --- a/libi2pd/Signature.h +++ b/libi2pd/Signature.h @@ -46,9 +46,9 @@ namespace crypto { m_PublicKey = CreateDSA (); } - + void SetPublicKey (const uint8_t * signingKey) - { + { DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL); } @@ -163,9 +163,9 @@ namespace crypto { m_PublicKey = EC_KEY_new_by_curve_name (curve); } - + void SetPublicKey (const uint8_t * signingKey) - { + { BIGNUM * x = BN_bin2bn (signingKey, keyLen/2, NULL); BIGNUM * y = BN_bin2bn (signingKey + keyLen/2, keyLen/2, NULL); EC_KEY_set_public_key_affine_coordinates (m_PublicKey, x, y); @@ -287,7 +287,7 @@ namespace crypto EDDSA25519Verifier (); void SetPublicKey (const uint8_t * signingKey); ~EDDSA25519Verifier (); - + bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; }; @@ -298,10 +298,10 @@ namespace crypto #if OPENSSL_EDDSA EVP_PKEY * m_Pkey; EVP_MD_CTX * m_MDCtx; -#else +#else EDDSAPoint m_PublicKey; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; -#endif +#endif }; class EDDSA25519SignerCompat: public Signer @@ -311,17 +311,17 @@ namespace crypto EDDSA25519SignerCompat (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey = nullptr); // we pass signingPublicKey to check if it matches private key ~EDDSA25519SignerCompat (); - + void Sign (const uint8_t * buf, int len, uint8_t * signature) const; const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; // for keys creation private: - + uint8_t m_ExpandedPrivateKey[64]; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; -#if OPENSSL_EDDSA +#if OPENSSL_EDDSA class EDDSA25519Signer: public Signer { public: @@ -329,23 +329,23 @@ namespace crypto EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey = nullptr); // we pass signingPublicKey to check if it matches private key ~EDDSA25519Signer (); - + void Sign (const uint8_t * buf, int len, uint8_t * signature) const; private: EVP_PKEY * m_Pkey; - EVP_MD_CTX * m_MDCtx; + EVP_MD_CTX * m_MDCtx; EDDSA25519SignerCompat * m_Fallback; }; #else typedef EDDSA25519SignerCompat EDDSA25519Signer; - -#endif - + +#endif + inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { -#if OPENSSL_EDDSA +#if OPENSSL_EDDSA EVP_PKEY *pkey = NULL; EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id (EVP_PKEY_ED25519, NULL); EVP_PKEY_keygen_init (pctx); @@ -355,12 +355,12 @@ namespace crypto EVP_PKEY_get_raw_public_key (pkey, signingPublicKey, &len); len = EDDSA25519_PRIVATE_KEY_LENGTH; EVP_PKEY_get_raw_private_key (pkey, signingPrivateKey, &len); - EVP_PKEY_free (pkey); -#else + EVP_PKEY_free (pkey); +#else RAND_bytes (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH); EDDSA25519Signer signer (signingPrivateKey); memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); -#endif +#endif } @@ -399,18 +399,18 @@ namespace crypto GOSTR3410Verifier (GOSTR3410ParamSet paramSet): m_ParamSet (paramSet), m_PublicKey (nullptr) { - } + } - void SetPublicKey (const uint8_t * signingKey) + void SetPublicKey (const uint8_t * signingKey) { BIGNUM * x = BN_bin2bn (signingKey, GetPublicKeyLen ()/2, NULL); BIGNUM * y = BN_bin2bn (signingKey + GetPublicKeyLen ()/2, GetPublicKeyLen ()/2, NULL); m_PublicKey = GetGOSTR3410Curve (m_ParamSet)->CreatePoint (x, y); BN_free (x); BN_free (y); } - ~GOSTR3410Verifier () - { - if (m_PublicKey) EC_POINT_free (m_PublicKey); + ~GOSTR3410Verifier () + { + if (m_PublicKey) EC_POINT_free (m_PublicKey); } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const @@ -501,18 +501,18 @@ namespace crypto auto publicKey = GetEd25519 ()->GeneratePublicKey (m_PrivateKey, ctx); GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx); BN_CTX_free (ctx); - } + } ~RedDSA25519Signer () {}; - + void Sign (const uint8_t * buf, int len, uint8_t * signature) const { - GetEd25519 ()->SignRedDSA (m_PrivateKey, m_PublicKeyEncoded, buf, len, signature); + GetEd25519 ()->SignRedDSA (m_PrivateKey, m_PublicKeyEncoded, buf, len, signature); } - + const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; // for keys creation private: - + uint8_t m_PrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH]; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; @@ -521,10 +521,9 @@ namespace crypto { GetEd25519 ()->CreateRedDSAPrivateKey (signingPrivateKey); RedDSA25519Signer signer (signingPrivateKey); - memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); + memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); } } } #endif - diff --git a/libi2pd/Siphash.h b/libi2pd/Siphash.h index 70822466..78b22b90 100644 --- a/libi2pd/Siphash.h +++ b/libi2pd/Siphash.h @@ -1,9 +1,9 @@ /** - This code is licensed under the MCGSI Public License - Copyright 2018 Jeff Becker - - Kovri go write your own code - + * This code is licensed under the MCGSI Public License + * Copyright 2018 Jeff Becker + * + * Kovri go write your own code + * */ #ifndef SIPHASH_H #define SIPHASH_H @@ -14,140 +14,140 @@ #if !OPENSSL_SIPHASH namespace i2p { -namespace crypto +namespace crypto { - namespace siphash - { - constexpr int crounds = 2; - constexpr int drounds = 4; + namespace siphash + { + constexpr int crounds = 2; + constexpr int drounds = 4; - inline uint64_t rotl(const uint64_t & x, int b) - { - uint64_t ret = x << b; - ret |= x >> (64 - b); - return ret; - } + inline uint64_t rotl(const uint64_t & x, int b) + { + uint64_t ret = x << b; + ret |= x >> (64 - b); + return ret; + } - inline void u32to8le(const uint32_t & v, uint8_t * p) - { - p[0] = (uint8_t) v; - p[1] = (uint8_t) (v >> 8); - p[2] = (uint8_t) (v >> 16); - p[3] = (uint8_t) (v >> 24); - } + inline void u32to8le(const uint32_t & v, uint8_t * p) + { + p[0] = (uint8_t) v; + p[1] = (uint8_t) (v >> 8); + p[2] = (uint8_t) (v >> 16); + p[3] = (uint8_t) (v >> 24); + } - inline void u64to8le(const uint64_t & v, uint8_t * p) - { - p[0] = v & 0xff; - p[1] = (v >> 8) & 0xff; - p[2] = (v >> 16) & 0xff; - p[3] = (v >> 24) & 0xff; - p[4] = (v >> 32) & 0xff; - p[5] = (v >> 40) & 0xff; - p[6] = (v >> 48) & 0xff; - p[7] = (v >> 56) & 0xff; - } + inline void u64to8le(const uint64_t & v, uint8_t * p) + { + p[0] = v & 0xff; + p[1] = (v >> 8) & 0xff; + p[2] = (v >> 16) & 0xff; + p[3] = (v >> 24) & 0xff; + p[4] = (v >> 32) & 0xff; + p[5] = (v >> 40) & 0xff; + p[6] = (v >> 48) & 0xff; + p[7] = (v >> 56) & 0xff; + } - inline uint64_t u8to64le(const uint8_t * p) - { - uint64_t i = 0; - int idx = 0; - while(idx < 8) - { - i |= ((uint64_t) p[idx]) << (idx * 8); - ++idx; - } - return i; - } - - inline void round(uint64_t & _v0, uint64_t & _v1, uint64_t & _v2, uint64_t & _v3) - { - _v0 += _v1; - _v1 = rotl(_v1, 13); - _v1 ^= _v0; - _v0 = rotl(_v0, 32); - _v2 += _v3; - _v3 = rotl(_v3, 16); - _v3 ^= _v2; - _v0 += _v3; - _v3 = rotl(_v3, 21); - _v3 ^= _v0; - _v2 += _v1; - _v1 = rotl(_v1, 17); - _v1 ^= _v2; - _v2 = rotl(_v2, 32); - } - } - - /** hashsz must be 8 or 16 */ - template - inline void Siphash(uint8_t * h, const uint8_t * buf, std::size_t bufsz, const uint8_t * key) - { - uint64_t v0 = 0x736f6d6570736575ULL; - uint64_t v1 = 0x646f72616e646f6dULL; - uint64_t v2 = 0x6c7967656e657261ULL; - uint64_t v3 = 0x7465646279746573ULL; - const uint64_t k0 = siphash::u8to64le(key); - const uint64_t k1 = siphash::u8to64le(key + 8); - uint64_t msg; - int i; - const uint8_t * end = buf + bufsz - (bufsz % sizeof(uint64_t)); - auto left = bufsz & 7; - uint64_t b = ((uint64_t)bufsz) << 56; - v3 ^= k1; - v2 ^= k0; - v1 ^= k1; - v0 ^= k0; + inline uint64_t u8to64le(const uint8_t * p) + { + uint64_t i = 0; + int idx = 0; + while(idx < 8) + { + i |= ((uint64_t) p[idx]) << (idx * 8); + ++idx; + } + return i; + } - if(hashsz == 16) v1 ^= 0xee; + inline void round(uint64_t & _v0, uint64_t & _v1, uint64_t & _v2, uint64_t & _v3) + { + _v0 += _v1; + _v1 = rotl(_v1, 13); + _v1 ^= _v0; + _v0 = rotl(_v0, 32); + _v2 += _v3; + _v3 = rotl(_v3, 16); + _v3 ^= _v2; + _v0 += _v3; + _v3 = rotl(_v3, 21); + _v3 ^= _v0; + _v2 += _v1; + _v1 = rotl(_v1, 17); + _v1 ^= _v2; + _v2 = rotl(_v2, 32); + } + } - while(buf != end) - { - msg = siphash::u8to64le(buf); - v3 ^= msg; - for(i = 0; i < siphash::crounds; ++i) - siphash::round(v0, v1, v2, v3); - - v0 ^= msg; - buf += 8; - } + /** hashsz must be 8 or 16 */ + template + inline void Siphash(uint8_t * h, const uint8_t * buf, std::size_t bufsz, const uint8_t * key) + { + uint64_t v0 = 0x736f6d6570736575ULL; + uint64_t v1 = 0x646f72616e646f6dULL; + uint64_t v2 = 0x6c7967656e657261ULL; + uint64_t v3 = 0x7465646279746573ULL; + const uint64_t k0 = siphash::u8to64le(key); + const uint64_t k1 = siphash::u8to64le(key + 8); + uint64_t msg; + int i; + const uint8_t * end = buf + bufsz - (bufsz % sizeof(uint64_t)); + auto left = bufsz & 7; + uint64_t b = ((uint64_t)bufsz) << 56; + v3 ^= k1; + v2 ^= k0; + v1 ^= k1; + v0 ^= k0; - while(left) - { - --left; - b |= ((uint64_t)(buf[left])) << (left * 8); - } + if(hashsz == 16) v1 ^= 0xee; - v3 ^= b; + while(buf != end) + { + msg = siphash::u8to64le(buf); + v3 ^= msg; + for(i = 0; i < siphash::crounds; ++i) + siphash::round(v0, v1, v2, v3); - for(i = 0; i < siphash::crounds; ++i) - siphash::round(v0, v1, v2, v3); + v0 ^= msg; + buf += 8; + } - v0 ^= b; + while(left) + { + --left; + b |= ((uint64_t)(buf[left])) << (left * 8); + } + + v3 ^= b; + + for(i = 0; i < siphash::crounds; ++i) + siphash::round(v0, v1, v2, v3); + + v0 ^= b; - if(hashsz == 16) - v2 ^= 0xee; - else - v2 ^= 0xff; + if(hashsz == 16) + v2 ^= 0xee; + else + v2 ^= 0xff; - for(i = 0; i < siphash::drounds; ++i) - siphash::round(v0, v1, v2, v3); + for(i = 0; i < siphash::drounds; ++i) + siphash::round(v0, v1, v2, v3); - b = v0 ^ v1 ^ v2 ^ v3; + b = v0 ^ v1 ^ v2 ^ v3; - siphash::u64to8le(b, h); + siphash::u64to8le(b, h); - if(hashsz == 8) return; + if(hashsz == 8) return; - v1 ^= 0xdd; + v1 ^= 0xdd; - for (i = 0; i < siphash::drounds; ++i) - siphash::round(v0, v1, v2, v3); + for (i = 0; i < siphash::drounds; ++i) + siphash::round(v0, v1, v2, v3); - b = v0 ^ v1 ^ v2 ^ v3; - siphash::u64to8le(b, h + 8); - } + b = v0 ^ v1 ^ v2 ^ v3; + siphash::u64to8le(b, h + 8); + } } } #endif diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 00ee35b0..ac527cb4 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -34,7 +34,7 @@ namespace stream else { // partially - rem = len - offset; + rem = len - offset; memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), len - offset); nextBuffer->offset += (len - offset); offset = len; // break @@ -60,7 +60,7 @@ namespace stream m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), - m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), + m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0), m_MTU (STREAMING_MTU) @@ -73,7 +73,7 @@ namespace stream m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), - m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE), + m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0), m_MTU (STREAMING_MTU) { @@ -228,7 +228,7 @@ namespace stream Terminate (); return; } - + packet->offset = packet->GetPayload () - packet->buf; if (packet->GetLength () > 0) { @@ -258,7 +258,7 @@ namespace stream bool Stream::ProcessOptions (uint16_t flags, Packet * packet) { const uint8_t * optionData = packet->GetOptionData (); - size_t optionSize = packet->GetOptionSize (); + size_t optionSize = packet->GetOptionSize (); if (flags & PACKET_FLAG_DELAY_REQUESTED) optionData += 2; @@ -269,7 +269,7 @@ namespace stream m_RemoteIdentity = std::make_shared(optionData, optionSize); if (m_RemoteIdentity->IsRSA ()) { - LogPrint (eLogInfo, "Streaming: Incoming stream from RSA destination ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " Discarded"); + LogPrint (eLogInfo, "Streaming: Incoming stream from RSA destination ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " Discarded"); return false; } optionData += m_RemoteIdentity->GetFullLen (); @@ -306,11 +306,11 @@ namespace stream size_t offset = 0; m_TransientVerifier = i2p::data::ProcessOfflineSignature (m_RemoteIdentity, optionData, optionSize - (optionData - packet->GetOptionData ()), offset); optionData += offset; - if (!m_TransientVerifier) + if (!m_TransientVerifier) { LogPrint (eLogError, "Streaming: offline signature failed"); return false; - } + } } } @@ -322,7 +322,7 @@ namespace stream { memcpy (signature, optionData, signatureLen); memset (const_cast(optionData), 0, signatureLen); - bool verified = m_TransientVerifier ? + bool verified = m_TransientVerifier ? m_TransientVerifier->Verify (packet->GetBuffer (), packet->GetLength (), signature) : m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature); if (!verified) @@ -340,7 +340,7 @@ namespace stream return false; } } - return true; + return true; } void Stream::ProcessAck (Packet * packet) @@ -435,7 +435,7 @@ namespace stream m_SendBuffer.Add (buf, len, handler); } else if (handler) - handler(boost::system::error_code ()); + handler(boost::system::error_code ()); m_Service.post (std::bind (&Stream::SendBuffer, shared_from_this ())); } @@ -471,14 +471,14 @@ namespace stream size++; // resend delay if (m_Status == eStreamStatusNew) { - // initial packet + // initial packet m_Status = eStreamStatusOpen; if (!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ());; if (m_RemoteLeaseSet) - { + { m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); m_MTU = m_RoutingSession->IsRatchets () ? STREAMING_MTU_RATCHETS : STREAMING_MTU; - } + } uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED | PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED; if (isNoAck) flags |= PACKET_FLAG_NO_ACK; @@ -488,7 +488,7 @@ namespace stream size += 2; // flags size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen (); size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); - uint8_t * optionsSize = packet + size; // set options size later + uint8_t * optionsSize = packet + size; // set options size later size += 2; // options size m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen); size += identityLen; // from @@ -654,7 +654,7 @@ namespace stream size += 4; // receiveStreamID htobe32buf (packet + size, m_SequenceNumber++); size += 4; // sequenceNum - htobe32buf (packet + size, m_LastReceivedSequenceNumber >= 0 ? m_LastReceivedSequenceNumber : 0); + htobe32buf (packet + size, m_LastReceivedSequenceNumber >= 0 ? m_LastReceivedSequenceNumber : 0); size += 4; // ack Through packet[size] = 0; size++; // NACK count @@ -748,7 +748,7 @@ namespace stream auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate || // excluded from LeaseSet - ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD) + ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD) UpdateCurrentRemoteLease (true); if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) { @@ -785,7 +785,7 @@ namespace stream if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASET_CONFIRMATION_TIMEOUT) { // LeaseSet was not confirmed, should try other tunnels - LogPrint (eLogWarning, "Streaming: LeaseSet was not confirmed in ", i2p::garlic::LEASET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit"); + LogPrint (eLogWarning, "Streaming: LeaseSet was not confirmed in ", i2p::garlic::LEASET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit"); m_RoutingSession->SetSharedRoutingPath (nullptr); m_CurrentOutboundTunnel = nullptr; m_CurrentRemoteLease = nullptr; @@ -848,9 +848,9 @@ namespace stream break; case 2: m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change first time -#if (__cplusplus >= 201703L) // C++ 17 or higher - [[fallthrough]]; -#endif +#if (__cplusplus >= 201703L) // C++ 17 or higher + [[fallthrough]]; +#endif // no break here case 4: if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); @@ -911,7 +911,7 @@ namespace stream // LeaseSet updated m_RemoteIdentity = m_RemoteLeaseSet->GetIdentity (); m_TransientVerifier = m_RemoteLeaseSet->GetTransientVerifier (); - } + } } if (m_RemoteLeaseSet) { @@ -926,7 +926,7 @@ namespace stream m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet ( std::make_shared(m_RemoteIdentity)); else - m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); + m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold } if (!leases.empty ()) @@ -994,7 +994,7 @@ namespace stream { std::unique_lock l(m_StreamsMutex); for (auto it: m_Streams) - it.second->Terminate (false); // we delete here + it.second->Terminate (false); // we delete here m_Streams.clear (); m_IncomingStreams.clear (); } @@ -1136,8 +1136,8 @@ namespace stream return false; DeleteStream (it->second); return true; - } - + } + void StreamingDestination::SetAcceptor (const Acceptor& acceptor) { m_Acceptor = acceptor; // we must set it immediately for IsAcceptorSet @@ -1164,7 +1164,7 @@ namespace stream m_Owner->GetService ().post([acceptor, this](void) { if (!m_PendingIncomingStreams.empty ()) - { + { acceptor (m_PendingIncomingStreams.front ()); m_PendingIncomingStreams.pop_front (); if (m_PendingIncomingStreams.empty ()) diff --git a/libi2pd/Tag.h b/libi2pd/Tag.h index 010b160f..eefab6de 100644 --- a/libi2pd/Tag.h +++ b/libi2pd/Tag.h @@ -16,93 +16,91 @@ namespace i2p { namespace data { - -template -class Tag -{ - BOOST_STATIC_ASSERT_MSG(sz % 8 == 0, "Tag size must be multiple of 8 bytes"); - -public: - - Tag () = default; - Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); } - - bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); } - bool operator!= (const Tag& other) const { return !(*this == other); } - bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; } - - uint8_t * operator()() { return m_Buf; } - const uint8_t * operator()() const { return m_Buf; } - - operator uint8_t * () { return m_Buf; } - operator const uint8_t * () const { return m_Buf; } - - const uint8_t * data() const { return m_Buf; } - const uint64_t * GetLL () const { return ll; } - - bool IsZero () const + template + class Tag { - for (size_t i = 0; i < sz/8; ++i) - if (ll[i]) return false; - return true; - } + BOOST_STATIC_ASSERT_MSG(sz % 8 == 0, "Tag size must be multiple of 8 bytes"); - void Fill(uint8_t c) - { - memset(m_Buf, c, sz); - } + public: - void Randomize() - { - RAND_bytes(m_Buf, sz); - } + Tag () = default; + Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); } - std::string ToBase64 () const - { - char str[sz*2]; - size_t l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2); - return std::string (str, str + l); - } + bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); } + bool operator!= (const Tag& other) const { return !(*this == other); } + bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; } - std::string ToBase32 () const - { - char str[sz*2]; - size_t l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2); - return std::string (str, str + l); - } + uint8_t * operator()() { return m_Buf; } + const uint8_t * operator()() const { return m_Buf; } - size_t FromBase32 (const std::string& s) - { - return i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); - } + operator uint8_t * () { return m_Buf; } + operator const uint8_t * () const { return m_Buf; } - size_t FromBase64 (const std::string& s) - { - return i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); - } + const uint8_t * data() const { return m_Buf; } + const uint64_t * GetLL () const { return ll; } -private: + bool IsZero () const + { + for (size_t i = 0; i < sz/8; ++i) + if (ll[i]) return false; + return true; + } - union // 8 bytes aligned - { - uint8_t m_Buf[sz]; - uint64_t ll[sz/8]; + void Fill(uint8_t c) + { + memset(m_Buf, c, sz); + } + + void Randomize() + { + RAND_bytes(m_Buf, sz); + } + + std::string ToBase64 () const + { + char str[sz*2]; + size_t l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2); + return std::string (str, str + l); + } + + std::string ToBase32 () const + { + char str[sz*2]; + size_t l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2); + return std::string (str, str + l); + } + + size_t FromBase32 (const std::string& s) + { + return i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); + } + + size_t FromBase64 (const std::string& s) + { + return i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); + } + + private: + + union // 8 bytes aligned + { + uint8_t m_Buf[sz]; + uint64_t ll[sz/8]; + }; }; -}; - } // data } // i2p namespace std { - // hash for std::unordered_map - template struct hash > - { - size_t operator()(const i2p::data::Tag& s) const - { - return s.GetLL ()[0]; - } - }; + // hash for std::unordered_map + template struct hash > + { + size_t operator()(const i2p::data::Tag& s) const + { + return s.GetLL ()[0]; + } + }; } #endif /* TAG_H__ */ diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index acc9cb10..9e435e21 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -24,26 +24,26 @@ namespace util static uint64_t GetLocalMillisecondsSinceEpoch () { return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count (); + std::chrono::system_clock::now().time_since_epoch()).count (); } static uint32_t GetLocalHoursSinceEpoch () { return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count (); + std::chrono::system_clock::now().time_since_epoch()).count (); } static uint64_t GetLocalSecondsSinceEpoch () { return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count (); + std::chrono::system_clock::now().time_since_epoch()).count (); } static int64_t g_TimeOffset = 0; // in seconds static void SyncTimeWithNTP (const std::string& address) { - LogPrint (eLogInfo, "Timestamp: NTP request to ", address); + LogPrint (eLogInfo, "Timestamp: NTP request to ", address); boost::asio::io_service service; boost::asio::ip::udp::resolver::query query (boost::asio::ip::udp::v4 (), address, "ntp"); boost::system::error_code ec; @@ -148,11 +148,11 @@ namespace util } void NTPTimeSync::Sync () - { + { if (m_NTPServersList.size () > 0) SyncTimeWithNTP (m_NTPServersList[rand () % m_NTPServersList.size ()]); else - m_IsRunning = false; + m_IsRunning = false; if (m_IsRunning) { @@ -200,4 +200,3 @@ namespace util } } } - diff --git a/libi2pd/Timestamp.h b/libi2pd/Timestamp.h index 31dc86aa..7d183cd3 100644 --- a/libi2pd/Timestamp.h +++ b/libi2pd/Timestamp.h @@ -15,8 +15,8 @@ namespace util uint32_t GetHoursSinceEpoch (); uint64_t GetSecondsSinceEpoch (); - void GetCurrentDate (char * date); // returns date as YYYYMMDD string, 9 bytes - void GetDateString (uint64_t timestamp, char * date); // timestap is seconds since epoch, returns date as YYYYMMDD string, 9 bytes + void GetCurrentDate (char * date); // returns date as YYYYMMDD string, 9 bytes + void GetDateString (uint64_t timestamp, char * date); // timestap is seconds since epoch, returns date as YYYYMMDD string, 9 bytes class NTPTimeSync { @@ -26,17 +26,17 @@ namespace util ~NTPTimeSync (); void Start (); - void Stop (); + void Stop (); private: - + void Run (); - void Sync (); + void Sync (); private: bool m_IsRunning; - std::unique_ptr m_Thread; + std::unique_ptr m_Thread; boost::asio::io_service m_Service; boost::asio::deadline_timer m_Timer; int m_SyncInterval; @@ -46,4 +46,3 @@ namespace util } #endif - diff --git a/libi2pd/TransitTunnel.cpp b/libi2pd/TransitTunnel.cpp index 1adc4178..02aac23f 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.cpp @@ -12,7 +12,7 @@ namespace i2p namespace tunnel { TransitTunnel::TransitTunnel (uint32_t receiveTunnelID, - const uint8_t * nextIdent, uint32_t nextTunnelID, + const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey): TunnelBase (receiveTunnelID, nextTunnelID, nextIdent) { @@ -88,7 +88,7 @@ namespace tunnel std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, - const uint8_t * layerKey,const uint8_t * ivKey, + const uint8_t * layerKey,const uint8_t * ivKey, bool isGateway, bool isEndpoint) { if (isEndpoint) diff --git a/libi2pd/TransportSession.h b/libi2pd/TransportSession.h index 55c39c7a..69b772cb 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -68,15 +68,15 @@ namespace transport std::string GetIdentHashBase64() const { return m_RemoteIdentity ? m_RemoteIdentity->GetIdentHash().ToBase64() : ""; } - std::shared_ptr GetRemoteIdentity () + std::shared_ptr GetRemoteIdentity () { std::lock_guard l(m_RemoteIdentityMutex); - return m_RemoteIdentity; + return m_RemoteIdentity; } - void SetRemoteIdentity (std::shared_ptr ident) + void SetRemoteIdentity (std::shared_ptr ident) { std::lock_guard l(m_RemoteIdentityMutex); - m_RemoteIdentity = ident; + m_RemoteIdentity = ident; } size_t GetNumSentBytes () const { return m_NumSentBytes; }; diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index bb74e2ad..15820584 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -220,10 +220,10 @@ namespace transport return; } else - { + { m_NTCP2Server = new NTCP2Server (); m_NTCP2Server->Start (); - } + } } // create acceptors @@ -390,7 +390,7 @@ namespace transport { auto r = netdb.FindRouter (ident); { - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); it = m_Peers.insert (std::pair(ident, { 0, r, {}, i2p::util::GetSecondsSinceEpoch (), {} })).first; } @@ -553,13 +553,13 @@ namespace transport { auto router = i2p::data::netdb.GetRandomPeerTestRouter (isv4); // v4 only if v4 if (router) - m_SSUServer->CreateSession (router, true, isv4); // peer test + m_SSUServer->CreateSession (router, true, isv4); // peer test else { // if not peer test capable routers found pick any router = i2p::data::netdb.GetRandomRouter (); if (router && router->IsSSU ()) - m_SSUServer->CreateSession (router); // no peer test + m_SSUServer->CreateSession (router); // no peer test } } if (i2p::context.SupportsV6 ()) @@ -574,7 +574,7 @@ namespace transport if (addr) m_SSUServer->GetServiceV6 ().post ([this, router, addr] { - m_SSUServer->CreateDirectSession (router, { addr->host, (uint16_t)addr->port }, false); + m_SSUServer->CreateDirectSession (router, { addr->host, (uint16_t)addr->port }, false); }); } } @@ -713,7 +713,7 @@ namespace transport { profile->TunnelNonReplied(); } - std::unique_lock l(m_PeersMutex); + std::unique_lock l(m_PeersMutex); it = m_Peers.erase (it); } else diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index 117092ad..37cfa269 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -103,7 +103,7 @@ namespace transport uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; }; uint64_t GetTotalTransitTransmittedBytes () const { return m_TotalTransitTransmittedBytes; } void UpdateTotalTransitTransmittedBytes (uint32_t add) { m_TotalTransitTransmittedBytes += add; }; - uint32_t GetInBandwidth () const { return m_InBandwidth; }; + uint32_t GetInBandwidth () const { return m_InBandwidth; }; uint32_t GetOutBandwidth () const { return m_OutBandwidth; }; uint32_t GetTransitBandwidth () const { return m_TransitBandwidth; }; bool IsBandwidthExceeded () const; @@ -111,16 +111,16 @@ namespace transport size_t GetNumPeers () const { return m_Peers.size (); }; std::shared_ptr GetRandomPeer () const; - /** get a trusted first hop for restricted routes */ - std::shared_ptr GetRestrictedPeer() const; - /** do we want to use restricted routes? */ - bool RoutesRestricted() const; - /** restrict routes to use only these router families for first hops */ - void RestrictRoutesToFamilies(std::set families); - /** restrict routes to use only these routers for first hops */ - void RestrictRoutesToRouters(std::set routers); + /** get a trusted first hop for restricted routes */ + std::shared_ptr GetRestrictedPeer() const; + /** do we want to use restricted routes? */ + bool RoutesRestricted() const; + /** restrict routes to use only these router families for first hops */ + void RestrictRoutesToFamilies(std::set families); + /** restrict routes to use only these routers for first hops */ + void RestrictRoutesToRouters(std::set routers); - bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const; + bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const; void PeerTest (); diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp index 324d5315..6e453d91 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -64,8 +64,7 @@ namespace tunnel m.hash = i2p::data::IdentHash (fragment); fragment += 32; // to hash break; - default: - ; + default: ; } bool isFragmented = flag & 0x08; diff --git a/libi2pd/TunnelGateway.cpp b/libi2pd/TunnelGateway.cpp index 7d0069a9..3f4069e1 100644 --- a/libi2pd/TunnelGateway.cpp +++ b/libi2pd/TunnelGateway.cpp @@ -51,7 +51,7 @@ namespace tunnel // create fragments const std::shared_ptr & msg = block.data; size_t fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length - + if (!messageCreated && fullMsgLen > m_RemainingSize) // check if we should complete previous message { size_t numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE; @@ -172,7 +172,7 @@ namespace tunnel SHA256(payload, size+16, hash); memcpy (buf+20, hash, 4); // checksum payload[-1] = 0; // zero - ptrdiff_t paddingSize = payload - buf - 25; // 25 = 24 + 1 + ptrdiff_t paddingSize = payload - buf - 25; // 25 = 24 + 1 if (paddingSize > 0) { // non-zero padding @@ -219,4 +219,3 @@ namespace tunnel } } } - diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 7256b2db..54459960 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -16,7 +16,6 @@ namespace i2p { namespace tunnel { - TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_IsActive (true), @@ -67,7 +66,8 @@ namespace tunnel m_Tests.clear (); } - bool TunnelPool::Reconfigure(int inHops, int outHops, int inQuant, int outQuant) { + bool TunnelPool::Reconfigure(int inHops, int outHops, int inQuant, int outQuant) + { if( inHops >= 0 && outHops >= 0 && inQuant > 0 && outQuant > 0) { m_NumInboundHops = inHops; @@ -78,7 +78,7 @@ namespace tunnel } return false; } - + void TunnelPool::TunnelCreated (std::shared_ptr createdTunnel) { if (!m_IsActive) return; @@ -180,8 +180,8 @@ namespace tunnel { if (it->IsEstablished () && it != excluded) { - tunnel = it; - i++; + tunnel = it; + i++; } if (i > ind && tunnel) break; } @@ -411,7 +411,7 @@ namespace tunnel { std::lock_guard lock(m_CustomPeerSelectorMutex); if (m_CustomPeerSelector) - return m_CustomPeerSelector->SelectPeers(peers, numHops, isInbound); + return m_CustomPeerSelector->SelectPeers(peers, numHops, isInbound); } // explicit peers in use if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index d6fb91cc..149a5efa 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -75,23 +75,23 @@ namespace tunnel /** i2cp reconfigure */ bool Reconfigure(int inboundHops, int outboundHops, int inboundQuant, int outboundQuant); - + void SetCustomPeerSelector(ITunnelPeerSelector * selector); void UnsetCustomPeerSelector(); bool HasCustomPeerSelector(); - /** @brief make this tunnel pool yield tunnels that fit latency range [min, max] */ - void RequireLatency(uint64_t min, uint64_t max) { m_MinLatency = min; m_MaxLatency = max; } + /** @brief make this tunnel pool yield tunnels that fit latency range [min, max] */ + void RequireLatency(uint64_t min, uint64_t max) { m_MinLatency = min; m_MaxLatency = max; } - /** @brief return true if this tunnel pool has a latency requirement */ - bool HasLatencyRequirement() const { return m_MinLatency > 0 && m_MaxLatency > 0; } + /** @brief return true if this tunnel pool has a latency requirement */ + bool HasLatencyRequirement() const { return m_MinLatency > 0 && m_MaxLatency > 0; } - /** @brief get the lowest latency tunnel in this tunnel pool regardless of latency requirements */ - std::shared_ptr GetLowestLatencyInboundTunnel(std::shared_ptr exclude=nullptr) const; - std::shared_ptr GetLowestLatencyOutboundTunnel(std::shared_ptr exclude=nullptr) const; + /** @brief get the lowest latency tunnel in this tunnel pool regardless of latency requirements */ + std::shared_ptr GetLowestLatencyInboundTunnel(std::shared_ptr exclude = nullptr) const; + std::shared_ptr GetLowestLatencyOutboundTunnel(std::shared_ptr exclude = nullptr) const; - // for overriding tunnel peer selection - std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; + // for overriding tunnel peer selection + std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; private: @@ -118,8 +118,8 @@ namespace tunnel std::mutex m_CustomPeerSelectorMutex; ITunnelPeerSelector * m_CustomPeerSelector; - uint64_t m_MinLatency=0; // if > 0 this tunnel pool will try building tunnels with minimum latency by ms - uint64_t m_MaxLatency=0; // if > 0 this tunnel pool will try building tunnels with maximum latency by ms + uint64_t m_MinLatency = 0; // if > 0 this tunnel pool will try building tunnels with minimum latency by ms + uint64_t m_MaxLatency = 0; // if > 0 this tunnel pool will try building tunnels with maximum latency by ms public: diff --git a/libi2pd/api.cpp b/libi2pd/api.cpp index 0a76bda7..3bac4878 100644 --- a/libi2pd/api.cpp +++ b/libi2pd/api.cpp @@ -31,8 +31,8 @@ namespace api bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); i2p::crypto::InitCrypto (precomputation); - int netID; i2p::config::GetOption("netid", netID); - i2p::context.SetNetID (netID); + int netID; i2p::config::GetOption("netid", netID); + i2p::context.SetNetID (netID); i2p::context.Init (); } @@ -133,4 +133,3 @@ namespace api } } } - diff --git a/libi2pd/api.h b/libi2pd/api.h index f64590d1..444667f0 100644 --- a/libi2pd/api.h +++ b/libi2pd/api.h @@ -35,4 +35,3 @@ namespace api } #endif - diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index c1b741fc..37898167 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -64,9 +64,9 @@ namespace util { m_IsRunning = true; m_Thread.reset (new std::thread (std::bind (& RunnableService::Run, this))); - } + } } - + void RunnableService::StopIOService () { if (m_IsRunning) @@ -79,7 +79,7 @@ namespace util m_Thread = nullptr; } } - } + } void RunnableService::Run () { @@ -94,28 +94,28 @@ namespace util LogPrint (eLogError, m_Name, ": runtime exception: ", ex.what ()); } } - } - + } + namespace net { #ifdef WIN32 bool IsWindowsXPorLater() { - static bool isRequested = false; - static bool isXP = false; - if (!isRequested) - { - // request - OSVERSIONINFO osvi; + static bool isRequested = false; + static bool isXP = false; + if (!isRequested) + { + // request + OSVERSIONINFO osvi; - ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx(&osvi); + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osvi); - isXP = osvi.dwMajorVersion <= 5; - isRequested = true; - } - return isXP; + isXP = osvi.dwMajorVersion <= 5; + isRequested = true; + } + return isXP; } int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback) @@ -246,22 +246,24 @@ namespace net std::string localAddressUniversal = localAddress.to_string(); #endif - typedef int (* IPN)(int af, const char *src, void *dst); + typedef int (* IPN)(int af, const char *src, void *dst); IPN inetpton = (IPN)GetProcAddress (GetModuleHandle ("ws2_32.dll"), "InetPton"); - if (!inetpton) inetpton = inet_pton_xp; // use own implementation if not found + if (!inetpton) inetpton = inet_pton_xp; // use own implementation if not found if(localAddress.is_v4()) { sockaddr_in inputAddress; - inetpton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); + inetpton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); return GetMTUWindowsIpv4(inputAddress, fallback); } else if(localAddress.is_v6()) { sockaddr_in6 inputAddress; - inetpton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); + inetpton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); return GetMTUWindowsIpv6(inputAddress, fallback); - } else { + } + else + { LogPrint(eLogError, "NetIface: GetMTU(): address family is not supported"); return fallback; } @@ -355,7 +357,7 @@ namespace net if (cur_ifname == ifname && cur->ifa_addr && cur->ifa_addr->sa_family == af) { // match - char addr[INET6_ADDRSTRLEN]; + char addr[INET6_ADDRSTRLEN]; memset (addr, 0, INET6_ADDRSTRLEN); if(af == AF_INET) inet_ntop(af, &((sockaddr_in *)cur->ifa_addr)->sin_addr, addr, INET6_ADDRSTRLEN); @@ -379,7 +381,6 @@ namespace net LogPrint(eLogWarning, "NetIface: cannot find ipv4 address for interface ", ifname); } return boost::asio::ip::address::from_string(fallback); - #endif } } diff --git a/libi2pd/util.h b/libi2pd/util.h index 2202ccd9..aa83ed7b 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -132,34 +132,34 @@ namespace util boost::asio::io_service& GetIOService () { return m_Service; } bool IsRunning () const { return m_IsRunning; }; - + void StartIOService (); void StopIOService (); private: void Run (); - + private: std::string m_Name; volatile bool m_IsRunning; std::unique_ptr m_Thread; boost::asio::io_service m_Service; - }; + }; class RunnableServiceWithWork: public RunnableService { protected: - RunnableServiceWithWork (const std::string& name): - RunnableService (name), m_Work (GetIOService ()) {} - + RunnableServiceWithWork (const std::string& name): + RunnableService (name), m_Work (GetIOService ()) {} + private: boost::asio::io_service::work m_Work; - }; - + }; + namespace net { int GetMTU (const boost::asio::ip::address& localAddress); diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index 0e40c76e..8e14c526 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -31,7 +31,7 @@ namespace client std::string etagsPath, indexPath, localPath; public: - AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") + AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") { i2p::config::GetOption("persist.addressbook", m_IsPersist); } @@ -77,10 +77,10 @@ namespace client std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const { - if (!m_IsPersist) + if (!m_IsPersist) { LogPrint(eLogDebug, "Addressbook: Persistence is disabled"); - return nullptr; + return nullptr; } std::string filename = storage.Path(ident.ToBase32()); std::ifstream f(filename, std::ifstream::binary); @@ -121,7 +121,7 @@ namespace client void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident) { - if (!m_IsPersist) return; + if (!m_IsPersist) return; storage.Remove( ident.ToBase32() ); } @@ -189,7 +189,7 @@ namespace client } for (const auto& it: addresses) - { + { f << it.first << ","; if (it.second->IsIdentHash ()) f << it.second->identHash.ToBase32 (); @@ -251,12 +251,12 @@ namespace client if (blindedPublicKey->IsValid ()) addressType = eAddressBlindedPublicKey; } - } + } Address::Address (const i2p::data::IdentHash& hash) { addressType = eAddressIndentHash; - identHash = hash; + identHash = hash; } AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), m_IsDownloading (false), @@ -322,7 +322,7 @@ namespace client { auto pos = address.find(".b32.i2p"); if (pos != std::string::npos) - { + { auto addr = std::make_shared(address.substr (0, pos)); return addr->IsValid () ? addr : nullptr; } @@ -333,10 +333,10 @@ namespace client { auto addr = FindAddress (address); if (!addr) - LookupAddress (address); // TODO: + LookupAddress (address); // TODO: return addr; - } - } + } + } // if not .b32 we assume full base64 address i2p::data::IdentityEx dest; if (!dest.FromBase64 (address)) @@ -359,10 +359,10 @@ namespace client { m_Addresses[address] = std::make_shared

    You will be redirected back in 5 seconds"; + res.add_header("Refresh", redirect.c_str()); + return; + } + } else { res.code = 400; diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index b0233901..275aa69d 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -316,13 +316,23 @@ namespace i2p static uint16_t g_MaxNumTransitTunnels = DEFAULT_MAX_NUM_TRANSIT_TUNNELS; // TODO: void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels) { - if (maxNumTransitTunnels > 0 && maxNumTransitTunnels <= 10000 && g_MaxNumTransitTunnels != maxNumTransitTunnels) + if (maxNumTransitTunnels > 0 && g_MaxNumTransitTunnels != maxNumTransitTunnels) { - LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels); + if (maxNumTransitTunnels <= 65535) { + LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels); + } else { + LogPrint (eLogWarning, "I2NP: Requested number of transit tunnels exceeds 65535, limited"); + maxNumTransitTunnels = 65535; + } g_MaxNumTransitTunnels = maxNumTransitTunnels; } } + uint16_t GetMaxNumTransitTunnels () + { + return g_MaxNumTransitTunnels; + } + bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) { for (int i = 0; i < num; i++) diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 5714afce..2fca1538 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -285,6 +285,7 @@ namespace tunnel const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 2500; void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels); + uint16_t GetMaxNumTransitTunnels (); } #endif From 51e3d5f7bc122d81eb0c0466e3fb3b7511ea4ecd Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 26 Apr 2020 19:27:31 -0400 Subject: [PATCH 3639/6300] create next tagset --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 70 ++++++++++++++++++++++- libi2pd/ECIESX25519AEADRatchetSession.h | 9 +-- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 2a30713e..8d820eb6 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -38,7 +38,11 @@ namespace garlic { i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) m_NextIndex++; - if (m_NextIndex >= 65535) m_NextIndex = 0; // TODO: dirty hack, should create new tagset + if (m_NextIndex >= 65535) + { + LogPrint (eLogError, "Garlic: Tagset ", GetTagSetID (), " is empty"); + return 0; + } return m_KeyData.GetTag (); } @@ -251,7 +255,26 @@ namespace garlic uint8_t flag = buf[0]; buf++; // flag if (flag & ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG) { - // TODO: implement + if (!m_NextSendRatchet) return; + uint16_t keyID = bufbe16toh (buf); buf += 2; // keyID + if (((!m_NextSendRatchet->newKey || !m_NextSendRatchet->keyID) && keyID == m_NextSendRatchet->keyID) || + (m_NextSendRatchet->newKey && keyID == m_NextSendRatchet->keyID -1)) + { + if (flag & ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG) + memcpy (m_NextSendRatchet->remote, buf, 32); + uint8_t sharedSecret[32], tagsetKey[32]; + m_NextSendRatchet->key.Agree (m_NextSendRatchet->remote, sharedSecret); + i2p::crypto::HKDF (sharedSecret, nullptr, 0, "XDHRatchetTagSet", tagsetKey, 32); // tagsetKey = HKDF(sharedSecret, ZEROLEN, "XDHRatchetTagSet", 32) + auto newTagset = std::make_shared (shared_from_this ()); + newTagset->SetTagSetID (1 + m_NextSendRatchet->keyID + keyID); + newTagset->DHInitialize (m_SendTagset->GetNextRootKey (), tagsetKey); + newTagset->NextSessionTagRatchet (); + m_SendTagset = newTagset; + m_SendForwardKey = false; + LogPrint (eLogDebug, "Garlic: next send tagset ", newTagset->GetTagSetID (), " created"); + } + else + LogPrint (eLogDebug, "Garlic: Unexpected next key ", keyID); } else { @@ -292,6 +315,26 @@ namespace garlic LogPrint (eLogDebug, "Garlic: next receive tagset ", tagsetID, " created"); } } + + void ECIESX25519AEADRatchetSession::NewNextSendRatchet () + { + auto newTagset = new DHRatchet (); + if (m_NextSendRatchet) + { + newTagset->keyID = m_NextSendRatchet->keyID; + if (!newTagset->newKey || !newTagset->keyID) + { + newTagset->keyID++; + newTagset->newKey = true; + } + else + newTagset->newKey = false; + } + if (newTagset->newKey) + newTagset->key.GenerateKeys (); + m_NextSendRatchet.reset (newTagset); + m_SendForwardKey = true; + } bool ECIESX25519AEADRatchetSession::NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { @@ -487,7 +530,9 @@ namespace garlic { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return false; - } + } + if (index >= ECIESX25519_TAGSET_MAX_NUM_TAGS && !m_SendForwardKey) + NewNextSendRatchet (); return true; } @@ -601,6 +646,11 @@ namespace garlic payloadLen += 6; if (m_NextReceiveRatchet->newKey) payloadLen += 32; } + if (m_SendForwardKey) + { + payloadLen += 6; + if (m_NextSendRatchet->newKey) payloadLen += 32; + } uint8_t paddingSize; RAND_bytes (&paddingSize, 1); paddingSize &= 0x0F; paddingSize++; // 1 - 16 @@ -662,6 +712,20 @@ namespace garlic } m_SendReverseKey = false; } + if (m_SendForwardKey) + { + v[offset] = eECIESx25519BlkNextKey; offset++; + htobe16buf (v.data () + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; + v[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; + if (!m_NextSendRatchet->keyID) v[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only + offset++; // flag + htobe16buf (v.data () + offset, m_NextSendRatchet->keyID); offset += 2; // keyid + if (m_NextSendRatchet->newKey) + { + memcpy (v.data () + offset, m_NextSendRatchet->key.GetPublicKey (), 32); + offset += 32; // public key + } + } // padding v[offset] = eECIESx25519BlkPadding; offset++; htobe16buf (v.data () + offset, paddingSize); offset += 2; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 963429ed..0a051810 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -73,6 +73,7 @@ namespace garlic const int ECIESX25519_RESTART_TIMEOUT = 120; // number of second of inactivity we should restart after const int ECIESX25519_EXPIRATION_TIMEOUT = 600; // in seconds + const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 60000; // number of tags we request new tagset after class ECIESX25519AEADRatchetSession: public GarlicRoutingSession, public std::enable_shared_from_this { @@ -90,7 +91,7 @@ namespace garlic int keyID = 0; i2p::crypto::X25519Keys key; uint8_t remote[32]; // last remote public key - bool newKey; + bool newKey = true; }; public: @@ -136,6 +137,7 @@ namespace garlic size_t CreateDeliveryStatusClove (std::shared_ptr msg, uint8_t * buf, size_t len); void GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags); + void NewNextSendRatchet (); private: @@ -148,9 +150,8 @@ namespace garlic std::shared_ptr m_SendTagset; std::unique_ptr m_Destination;// TODO: might not need it std::list > m_AckRequests; // (tagsetid, index) - int m_SendKeyID = 0, m_ReceiveKeyID = 0; - bool m_SendReverseKey = false; - std::unique_ptr m_NextReceiveRatchet; + bool m_SendReverseKey = false, m_SendForwardKey = false; + std::unique_ptr m_NextReceiveRatchet, m_NextSendRatchet; }; std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag); From 50a77fedca2c6d9119b8a10237e3bcc54367f8c1 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 26 Apr 2020 19:37:00 -0400 Subject: [PATCH 3640/6300] removed trivial check --- libi2pd/I2NPProtocol.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 275aa69d..0e7c245e 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -318,12 +318,7 @@ namespace i2p { if (maxNumTransitTunnels > 0 && g_MaxNumTransitTunnels != maxNumTransitTunnels) { - if (maxNumTransitTunnels <= 65535) { - LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels); - } else { - LogPrint (eLogWarning, "I2NP: Requested number of transit tunnels exceeds 65535, limited"); - maxNumTransitTunnels = 65535; - } + LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels); g_MaxNumTransitTunnels = maxNumTransitTunnels; } } From 5700e18257993a7f18e69629f92a53d111292c5e Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 27 Apr 2020 13:23:29 +0300 Subject: [PATCH 3641/6300] [FS] read tunnels configs which ends with .conf only Signed-off-by: R4SAS --- libi2pd_client/ClientContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 722fb242..fa0532c3 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -490,6 +490,7 @@ namespace client { for (auto& it: files) { + if (it.substr(it.size() - 5) != ".conf") continue; // skip files which not ends with ".conf" LogPrint(eLogDebug, "Clients: tunnels extra config file: ", it); ReadTunnels (it, numClientTunnels, numServerTunnels); } From e6fdf5ad8da59072f431268e27cb9b736441fd15 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 27 Apr 2020 13:59:00 +0300 Subject: [PATCH 3642/6300] [Log] create logfile even if loglevel is "none" Signed-off-by: R4SAS --- libi2pd/Log.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index 79b4a511..7d77aaf8 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -199,7 +199,6 @@ namespace log { void Log::SendTo (const std::string& path) { if (m_LogStream) m_LogStream = nullptr; // close previous - if (m_MinLevel == eLogNone) return; auto flags = std::ofstream::out | std::ofstream::app; auto os = std::make_shared (path, flags); if (os->is_open ()) From 142a138cfceb96c35d05217606525a8f21255018 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 27 Apr 2020 09:35:02 -0400 Subject: [PATCH 3643/6300] store previous reverse key --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 8d820eb6..b02e49f8 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -255,12 +255,12 @@ namespace garlic uint8_t flag = buf[0]; buf++; // flag if (flag & ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG) { - if (!m_NextSendRatchet) return; + if (!m_SendForwardKey || !m_NextSendRatchet) return; uint16_t keyID = bufbe16toh (buf); buf += 2; // keyID if (((!m_NextSendRatchet->newKey || !m_NextSendRatchet->keyID) && keyID == m_NextSendRatchet->keyID) || (m_NextSendRatchet->newKey && keyID == m_NextSendRatchet->keyID -1)) { - if (flag & ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG) + if (flag & ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG) memcpy (m_NextSendRatchet->remote, buf, 32); uint8_t sharedSecret[32], tagsetKey[32]; m_NextSendRatchet->key.Agree (m_NextSendRatchet->remote, sharedSecret); @@ -318,22 +318,23 @@ namespace garlic void ECIESX25519AEADRatchetSession::NewNextSendRatchet () { - auto newTagset = new DHRatchet (); if (m_NextSendRatchet) { - newTagset->keyID = m_NextSendRatchet->keyID; - if (!newTagset->newKey || !newTagset->keyID) + if (!m_NextSendRatchet->newKey || !m_NextSendRatchet->keyID) { - newTagset->keyID++; - newTagset->newKey = true; + m_NextSendRatchet->keyID++; + m_NextSendRatchet->newKey = true; } else - newTagset->newKey = false; + m_NextSendRatchet->newKey = false; } - if (newTagset->newKey) - newTagset->key.GenerateKeys (); - m_NextSendRatchet.reset (newTagset); + else + m_NextSendRatchet.reset (new DHRatchet ()); + if (m_NextSendRatchet->newKey) + m_NextSendRatchet->key.GenerateKeys (); + m_SendForwardKey = true; + LogPrint (eLogDebug, "Garlic: new send ratchet ", m_NextSendRatchet->newKey ? "new" : "old", " key ", m_NextSendRatchet->keyID, " created"); } bool ECIESX25519AEADRatchetSession::NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) From f77a58b2dc03ef3478d8b8a13da3b13ea315109a Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 27 Apr 2020 18:53:02 -0400 Subject: [PATCH 3644/6300] set some ECIESx25519 params --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 13 ++++++++----- libi2pd/ECIESX25519AEADRatchetSession.h | 9 ++++++--- libi2pd/Garlic.cpp | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index b02e49f8..4dba003d 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -311,7 +311,7 @@ namespace garlic newTagset->SetTagSetID (tagsetID); newTagset->DHInitialize (receiveTagset->GetNextRootKey (), tagsetKey); newTagset->NextSessionTagRatchet (); - GenerateMoreReceiveTags (newTagset, GetOwner ()->GetNumTags ()); + GenerateMoreReceiveTags (newTagset, ECIESX25519_MAX_NUM_GENERATED_TAGS); LogPrint (eLogDebug, "Garlic: next receive tagset ", tagsetID, " created"); } } @@ -426,7 +426,7 @@ namespace garlic m_SendTagset = std::make_shared(shared_from_this ()); m_SendTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) m_SendTagset->NextSessionTagRatchet (); - GenerateMoreReceiveTags (receiveTagset, GetOwner ()->GetNumTags ()); + GenerateMoreReceiveTags (receiveTagset, ECIESX25519_MIN_NUM_GENERATED_TAGS); i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", m_NSRKey, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) // encrypt payload if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset, len + 16, true)) // encrypt @@ -495,7 +495,7 @@ namespace garlic auto receiveTagset = std::make_shared(shared_from_this ()); receiveTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) receiveTagset->NextSessionTagRatchet (); - GenerateMoreReceiveTags (receiveTagset, GetOwner ()->GetNumTags ()); + GenerateMoreReceiveTags (receiveTagset, ECIESX25519_MIN_NUM_GENERATED_TAGS); i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) // decrypt payload std::vector payload (len - 16); @@ -552,8 +552,11 @@ namespace garlic return false; } HandlePayload (payload.data (), len - 16, receiveTagset, index); - if (receiveTagset->GetNextIndex () - index <= GetOwner ()->GetNumTags ()*2/3) - GenerateMoreReceiveTags (receiveTagset, GetOwner ()->GetNumTags ()); + int moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4 + if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; + moreTags -= (receiveTagset->GetNextIndex () - index); + if (moreTags > 0) + GenerateMoreReceiveTags (receiveTagset, moreTags); return true; } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 0a051810..5cba8868 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -72,9 +72,12 @@ namespace garlic const uint8_t ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG = 0x04; const int ECIESX25519_RESTART_TIMEOUT = 120; // number of second of inactivity we should restart after - const int ECIESX25519_EXPIRATION_TIMEOUT = 600; // in seconds - const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 60000; // number of tags we request new tagset after - + const int ECIESX25519_EXPIRATION_TIMEOUT = 480; // in seconds + const int ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT = 600; // in seconds + const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 1024; // number of tags we request new tagset after + const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; + const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 160; + class ECIESX25519AEADRatchetSession: public GarlicRoutingSession, public std::enable_shared_from_this { enum SessionState diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index f0e3c10e..fe5e8d69 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -788,7 +788,7 @@ namespace garlic // ECIESx25519 for (auto it = m_ECIESx25519Tags.begin (); it != m_ECIESx25519Tags.end ();) { - if (ts > it->second.creationTime + INCOMING_TAGS_EXPIRATION_TIMEOUT) + if (ts > it->second.creationTime + ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT) it = m_ECIESx25519Tags.erase (it); else ++it; From 7b22ef4270de78e2bd013edff6fd9795bb5b6c2c Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 28 Apr 2020 14:47:53 -0400 Subject: [PATCH 3645/6300] create incoming NSR tagset --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 2 +- libi2pd/ECIESX25519AEADRatchetSession.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 4dba003d..06f6c84e 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -378,7 +378,7 @@ namespace garlic m_State = eSessionStateNewSessionSent; if (GetOwner ()) - GetOwner ()->AddECIESx25519SessionNextTag (CreateNewSessionTagset ()); + GenerateMoreReceiveTags (CreateNewSessionTagset (), ECIESX25519_NSR_NUM_GENERATED_TAGS); return true; } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 5cba8868..20676405 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -77,6 +77,7 @@ namespace garlic const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 1024; // number of tags we request new tagset after const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 160; + const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; class ECIESX25519AEADRatchetSession: public GarlicRoutingSession, public std::enable_shared_from_this { From 0a431594f89a2e57f12f5bc9c29fd5905b44a726 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 29 Apr 2020 00:56:43 +0300 Subject: [PATCH 3646/6300] [Log] Change default loglevel (closes #1230) Signed-off-by: R4SAS --- contrib/i2pd.conf | 10 +++++----- libi2pd/Config.cpp | 30 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 1ef725bb..f8786a33 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -13,10 +13,10 @@ ## Tunnels config files path ## Use that path to store separated tunnels in different config files. ## Default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d -# tunnelsdir = /var/lib/i2pd/tunnels.conf.d +# tunnelsdir = /var/lib/i2pd/tunnels.d ## Where to write pidfile (don't write by default) -# pidfile = /var/run/i2pd.pid +# pidfile = /run/i2pd.pid ## Logging configuration section ## By default logs go to stdout with level 'info' and higher @@ -27,10 +27,10 @@ ## * syslog - use syslog, see man 3 syslog # log = file ## Path to logfile (default - autodetect) -# logfile = /var/log/i2pd.log -## Log messages above this level (debug, *info, warn, error, none) +# logfile = /var/log/i2pd/i2pd.log +## Log messages above this level (debug, info, *warn, error, none) ## If you set it to none, logging will be disabled -# loglevel = info +# loglevel = warn ## Write full CLF-formatted date and time to log (default: write only time) # logclftime = true diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 975a027e..cd17660a 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -35,11 +35,11 @@ namespace config { ("version", "Show i2pd version") ("conf", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf)") ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf)") - ("tunnelsdir", value()->default_value(""), "Path to extra tunnels' configs folder (default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d") + ("tunnelsdir", value()->default_value(""), "Path to extra tunnels' configs folder (default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d") ("pidfile", value()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)") ("log", value()->default_value(""), "Logs destination: stdout, file, syslog (stdout if not set)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") - ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error, none)") + ("loglevel", value()->default_value("warn"), "Set the minimal level of log messages (debug, info, warn, error, none)") ("logclftime", bool_switch()->default_value(false), "Write full CLF-formatted date and time to log (default: disabled, write only time)") ("family", value()->default_value(""), "Specify a family, router belongs to") ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") @@ -88,7 +88,7 @@ namespace config { ("http.pass", value()->default_value(""), "Password for basic auth (default: random, see logs)") ("http.strictheaders", value()->default_value(true), "Enable strict host checking on WebUI") ("http.hostname", value()->default_value("localhost"), "Expected hostname for WebUI") - ("http.webroot", value()->default_value("/"), "WebUI root path (default: / )") + ("http.webroot", value()->default_value("/"), "WebUI root path (default: / )") ; options_description httpproxy("HTTP Proxy options"); @@ -131,7 +131,7 @@ namespace config { ("sam.enabled", value()->default_value(true), "Enable or disable SAM Application bridge") ("sam.address", value()->default_value("127.0.0.1"), "SAM listen address") ("sam.port", value()->default_value(7656), "SAM listen port") - ("sam.singlethread", value()->default_value(true), "Sessions run in the SAM bridge's thread") + ("sam.singlethread", value()->default_value(true), "Sessions run in the SAM bridge's thread") ; options_description bob("BOB options"); @@ -191,15 +191,15 @@ namespace config { "https://reseed.i2p-projekt.de/," "https://i2p.mooo.com/netDb/," "https://netdb.i2p2.no/," - "https://reseed.i2p2.no/," - "https://reseed2.i2p2.no/," + "https://reseed.i2p2.no/," + "https://reseed2.i2p2.no/," // "https://us.reseed.i2p2.no:444/," // mamoth's shit // "https://uk.reseed.i2p2.no:444/," // mamoth's shit "https://reseed-fr.i2pd.xyz/," "https://reseed.memcpy.io/," "https://reseed.onion.im/," "https://i2pseed.creativecowpat.net:8443/," - "https://i2p.novg.net/" + "https://i2p.novg.net/" ), "Reseed URLs, separated by comma") ; @@ -228,29 +228,29 @@ namespace config { options_description ntcp2("NTCP2 Options"); ntcp2.add_options() - ("ntcp2.enabled", value()->default_value(true), "Enable NTCP2 (default: enabled)") - ("ntcp2.published", value()->default_value(true), "Publish NTCP2 (default: enabled)") - ("ntcp2.port", value()->default_value(0), "Port to listen for incoming NTCP2 connections (default: auto)") + ("ntcp2.enabled", value()->default_value(true), "Enable NTCP2 (default: enabled)") + ("ntcp2.published", value()->default_value(true), "Publish NTCP2 (default: enabled)") + ("ntcp2.port", value()->default_value(0), "Port to listen for incoming NTCP2 connections (default: auto)") ("ntcp2.addressv6", value()->default_value("::"), "Address to bind NTCP2 on") - ("ntcp2.proxy", value()->default_value(""), "Proxy URL for NTCP2 transport") + ("ntcp2.proxy", value()->default_value(""), "Proxy URL for NTCP2 transport") ; options_description nettime("Time sync options"); nettime.add_options() - ("nettime.enabled", value()->default_value(false), "Disable time sync (default: disabled)") + ("nettime.enabled", value()->default_value(false), "Disable time sync (default: disabled)") ("nettime.ntpservers", value()->default_value( "0.pool.ntp.org," "1.pool.ntp.org," "2.pool.ntp.org," "3.pool.ntp.org" ), "Comma separated list of NTCP servers") - ("nettime.ntpsyncinterval", value()->default_value(72), "NTP sync interval in hours (default: 72)") + ("nettime.ntpsyncinterval", value()->default_value(72), "NTP sync interval in hours (default: 72)") ; options_description persist("Network information persisting options"); persist.add_options() - ("persist.profiles", value()->default_value(true), "Persist peer profiles (default: true)") - ("persist.addressbook", value()->default_value(true), "Persist full addresses (default: true)") + ("persist.profiles", value()->default_value(true), "Persist peer profiles (default: true)") + ("persist.addressbook", value()->default_value(true), "Persist full addresses (default: true)") ; m_OptionsDesc From 65e1871cd73adc07f9c0fb6180b63764c7bbf781 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 28 Apr 2020 18:23:13 -0400 Subject: [PATCH 3647/6300] new tag for each NSR --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 25 ++++++++++++++++++----- libi2pd/ECIESX25519AEADRatchetSession.h | 4 ++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 06f6c84e..43049d56 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -386,7 +386,8 @@ namespace garlic bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob - uint64_t tag = CreateNewSessionTagset ()->GetNextSessionTag (); + m_NSRTagset = CreateNewSessionTagset (); + uint64_t tag = m_NSRTagset->GetNextSessionTag (); size_t offset = 0; memcpy (out + offset, &tag, 8); @@ -396,6 +397,8 @@ namespace garlic LogPrint (eLogError, "Garlic: Can't encode elligator"); return false; } + memcpy (m_NSREncodedKey, out + offset, 56); // for possible next NSR + memcpy (m_NSRH, m_H, 32); offset += 32; // KDF for Reply Key Section MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) @@ -408,14 +411,13 @@ namespace garlic uint8_t nonce[12]; CreateNonce (0, nonce); // calulate hash for zero length - if (!i2p::crypto::AEADChaCha20Poly1305 (sharedSecret /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + offset, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) + if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + offset, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) { LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); return false; } MixHash (out + offset, 16); // h = SHA256(h || ciphertext) offset += 16; - memcpy (m_NSRHeader, out, 56); // for possible next NSR // KDF for payload uint8_t keydata[64]; i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) @@ -442,9 +444,21 @@ namespace garlic bool ECIESX25519AEADRatchetSession::NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob and sent NSR already - memcpy (out, m_NSRHeader, 56); + uint64_t tag = m_NSRTagset->GetNextSessionTag (); // next tag + memcpy (out, &tag, 8); + memcpy (out + 8, m_NSREncodedKey, 32); + // recalculte h with new tag + memcpy (m_H, m_NSRH, 32); + MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) + MixHash (m_EphemeralKeys.GetPublicKey (), 32); // h = SHA256(h || bepk) uint8_t nonce[12]; - CreateNonce (0, nonce); + CreateNonce (0, nonce); + if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + 40, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) + { + LogPrint (eLogWarning, "Garlic: Reply key section AEAD encryption failed"); + return false; + } + MixHash (out + 40, 16); // h = SHA256(h || ciphertext) // encrypt payload if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + 56, len + 16, true)) // encrypt { @@ -568,6 +582,7 @@ namespace garlic { case eSessionStateNewSessionReplySent: m_State = eSessionStateEstablished; + m_NSRTagset = nullptr; #if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; #endif diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 20676405..995bbe4a 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -147,11 +147,11 @@ namespace garlic uint8_t m_H[32], m_CK[64] /* [chainkey, key] */, m_RemoteStaticKey[32]; uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only - uint8_t m_NSRHeader[56], m_NSRKey[32]; // new session reply, for incoming only + uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only i2p::crypto::X25519Keys m_EphemeralKeys; SessionState m_State = eSessionStateNew; uint64_t m_LastActivityTimestamp = 0; // incoming - std::shared_ptr m_SendTagset; + std::shared_ptr m_SendTagset, m_NSRTagset; std::unique_ptr m_Destination;// TODO: might not need it std::list > m_AckRequests; // (tagsetid, index) bool m_SendReverseKey = false, m_SendForwardKey = false; From c0de9455bbda0d18e7db75aa382a9bfa166a36a8 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 29 Apr 2020 02:11:35 +0300 Subject: [PATCH 3648/6300] [android] stop immediatly if no transit tunnels available while graceful shutdown Signed-off-by: R4SAS --- android/jni/i2pd_android.cpp | 6 ++++++ android/jni/org_purplei2p_i2pd_I2PD_JNI.h | 3 +++ android/src/org/purplei2p/i2pd/DaemonSingleton.java | 4 ++++ android/src/org/purplei2p/i2pd/I2PDActivity.java | 11 ++++++++--- android/src/org/purplei2p/i2pd/I2PD_JNI.java | 2 ++ 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/android/jni/i2pd_android.cpp b/android/jni/i2pd_android.cpp index bb058b5d..177bfd7c 100755 --- a/android/jni/i2pd_android.cpp +++ b/android/jni/i2pd_android.cpp @@ -4,6 +4,7 @@ #include "RouterContext.h" #include "ClientContext.h" #include "Transports.h" +#include "Tunnel.h" JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith (JNIEnv *env, jclass clazz) { @@ -98,3 +99,8 @@ JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_setDataDir // Set DataDir i2p::android::SetDataDir(dataDir); } + +JNIEXPORT jint JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_GetTransitTunnelsCount + (JNIEnv *env, jclass clazz) { + return i2p::tunnel::tunnels.CountTransitTunnels(); +} diff --git a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h index 6d809e63..d5c2f15f 100644 --- a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h +++ b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h @@ -36,6 +36,9 @@ JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_setDataDir (JNIEnv *env, jclass clazz, jstring jdataDir); +JNIEXPORT jint JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_GetTransitTunnelsCount + (JNIEnv *, jclass); + #ifdef __cplusplus } #endif diff --git a/android/src/org/purplei2p/i2pd/DaemonSingleton.java b/android/src/org/purplei2p/i2pd/DaemonSingleton.java index 49a323ae..e9e4fc06 100644 --- a/android/src/org/purplei2p/i2pd/DaemonSingleton.java +++ b/android/src/org/purplei2p/i2pd/DaemonSingleton.java @@ -65,6 +65,10 @@ public class DaemonSingleton { } } + public synchronized int GetTransitTunnelsCount() { + return I2PD_JNI.GetTransitTunnelsCount(); + } + private volatile boolean startedOkay; public enum State { diff --git a/android/src/org/purplei2p/i2pd/I2PDActivity.java b/android/src/org/purplei2p/i2pd/I2PDActivity.java index 5303a4da..8295e9f1 100755 --- a/android/src/org/purplei2p/i2pd/I2PDActivity.java +++ b/android/src/org/purplei2p/i2pd/I2PDActivity.java @@ -74,7 +74,7 @@ public class I2PDActivity extends Activity { processAssets(); runOnUiThread(() -> { try { - if (textView==null) + if (textView == null) return; Throwable tr = daemon.getLastThrowable(); if (tr!=null) { @@ -128,7 +128,7 @@ public class I2PDActivity extends Activity { doBindService(); final Timer gracefulQuitTimer = getGracefulQuitTimer(); - if (gracefulQuitTimer!=null) { + if (gracefulQuitTimer != null) { long gracefulStopAtMillis; synchronized (graceStartedMillis_LOCK) { gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS; @@ -373,6 +373,11 @@ public class I2PDActivity extends Activity { if (gracefulQuitTimerOld != null) gracefulQuitTimerOld.cancel(); + if(daemon.GetTransitTunnelsCount() <= 0) { // no tunnels left + Log.d(TAG, "no transit tunnels left, stopping"); + i2pdStop(); + } + final Timer gracefulQuitTimer = new Timer(true); setGracefulQuitTimer(gracefulQuitTimer); gracefulQuitTimer.schedule(new TimerTask() { @@ -480,7 +485,7 @@ public class I2PDActivity extends Activity { private void deleteRecursive(File fileOrDirectory) { if (fileOrDirectory.isDirectory()) { File[] files = fileOrDirectory.listFiles(); - if (files!=null) { + if (files != null) { for (File child : files) { deleteRecursive(child); } diff --git a/android/src/org/purplei2p/i2pd/I2PD_JNI.java b/android/src/org/purplei2p/i2pd/I2PD_JNI.java index deb7d6b9..582b102b 100644 --- a/android/src/org/purplei2p/i2pd/I2PD_JNI.java +++ b/android/src/org/purplei2p/i2pd/I2PD_JNI.java @@ -22,6 +22,8 @@ public class I2PD_JNI { public static native void setDataDir(String jdataDir); + public static native int GetTransitTunnelsCount(); + public static void loadLibraries() { //System.loadLibrary("c++_shared"); System.loadLibrary("i2pd"); From 3d9c844dca41d464920077cd73d46a374175f0b5 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 28 Apr 2020 22:03:13 -0400 Subject: [PATCH 3649/6300] handle out of order NSR --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 52 ++++++++++++++++------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 43049d56..025340c8 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -482,13 +482,18 @@ namespace garlic } buf += 32; len -= 32; // KDF for Reply Key Section + uint8_t h[32]; memcpy (h, m_H, 32); // save m_H MixHash (tag, 8); // h = SHA256(h || tag) MixHash (bepk, 32); // h = SHA256(h || bepk) uint8_t sharedSecret[32]; - m_EphemeralKeys.Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) - GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + if (m_State == eSessionStateNewSessionSent) + { + // only fist time, we assume ephemeral keys the same + m_EphemeralKeys.Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) + GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bepk) + i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + } uint8_t nonce[12]; CreateNonce (0, nonce); // calulate hash for zero length @@ -502,14 +507,17 @@ namespace garlic // KDF for payload uint8_t keydata[64]; i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) - // k_ab = keydata[0:31], k_ba = keydata[32:63] - m_SendTagset = std::make_shared(shared_from_this ()); - m_SendTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) - m_SendTagset->NextSessionTagRatchet (); - auto receiveTagset = std::make_shared(shared_from_this ()); - receiveTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) - receiveTagset->NextSessionTagRatchet (); - GenerateMoreReceiveTags (receiveTagset, ECIESX25519_MIN_NUM_GENERATED_TAGS); + if (m_State == eSessionStateNewSessionSent) + { + // k_ab = keydata[0:31], k_ba = keydata[32:63] + m_SendTagset = std::make_shared(shared_from_this ()); + m_SendTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) + m_SendTagset->NextSessionTagRatchet (); + auto receiveTagset = std::make_shared(shared_from_this ()); + receiveTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) + receiveTagset->NextSessionTagRatchet (); + GenerateMoreReceiveTags (receiveTagset, ECIESX25519_MIN_NUM_GENERATED_TAGS); + } i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) // decrypt payload std::vector payload (len - 16); @@ -518,9 +526,13 @@ namespace garlic LogPrint (eLogWarning, "Garlic: Payload section AEAD decryption failed"); return false; } - - m_State = eSessionStateEstablished; - GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); + + if (m_State == eSessionStateNewSessionSent) + { + m_State = eSessionStateEstablished; + GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); + } + memcpy (m_H, h, 32); // restore m_H HandlePayload (payload.data (), len - 16, nullptr, 0); // we have received reply to NS with LeaseSet in it @@ -587,7 +599,15 @@ namespace garlic [[fallthrough]]; #endif case eSessionStateEstablished: - return HandleExistingSessionMessage (buf, len, receiveTagset, index); + if (HandleExistingSessionMessage (buf, len, receiveTagset, index)) return true; + if (index < ECIESX25519_NSR_NUM_GENERATED_TAGS) + { + // check NSR just in case + LogPrint (eLogDebug, "Garlic: check for out of order NSR with index ", index); + return HandleNewOutgoingSessionReply (buf, len); + } + else + return false; case eSessionStateNew: return HandleNewIncomingSession (buf, len); case eSessionStateNewSessionSent: From 16b992d7055fdd1258572674cc8b5f9391af9867 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 29 Apr 2020 16:55:25 +0300 Subject: [PATCH 3650/6300] update info about pidfile defaults (closes #1136) Signed-off-by: R4SAS --- contrib/i2pd.conf | 2 +- daemon/UnixDaemon.cpp | 3 +-- daemon/i2pd.cpp | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index f8786a33..2174abe8 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -15,7 +15,7 @@ ## Default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d # tunnelsdir = /var/lib/i2pd/tunnels.d -## Where to write pidfile (don't write by default) +## Where to write pidfile (default: i2pd.pid, not used in Windows) # pidfile = /run/i2pd.pid ## Logging configuration section diff --git a/daemon/UnixDaemon.cpp b/daemon/UnixDaemon.cpp index 3dd38fba..b7af779c 100644 --- a/daemon/UnixDaemon.cpp +++ b/daemon/UnixDaemon.cpp @@ -167,7 +167,7 @@ namespace i2p sigaction(SIGABRT, &sa, 0); sigaction(SIGTERM, &sa, 0); sigaction(SIGINT, &sa, 0); - sigaction(SIGPIPE, &sa, 0); + sigaction(SIGPIPE, &sa, 0); return Daemon_Singleton::start(); } @@ -175,7 +175,6 @@ namespace i2p bool DaemonLinux::stop() { i2p::fs::Remove(pidfile); - return Daemon_Singleton::stop(); } diff --git a/daemon/i2pd.cpp b/daemon/i2pd.cpp index 8718ad0c..425c2560 100644 --- a/daemon/i2pd.cpp +++ b/daemon/i2pd.cpp @@ -7,18 +7,18 @@ namespace i2p { namespace qt { - int RunQT (int argc, char* argv[]); + int RunQT (int argc, char* argv[]); } } int main( int argc, char* argv[] ) { - return i2p::qt::RunQT (argc, argv); + return i2p::qt::RunQT (argc, argv); } #else int main( int argc, char* argv[] ) { - if (Daemon.init(argc, argv)) + if (Daemon.init(argc, argv)) { if (Daemon.start()) Daemon.run (); From 627d8cfe6965c26b0907a7b8574ada7bcf1a73a8 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 29 Apr 2020 17:11:48 -0400 Subject: [PATCH 3651/6300] correct timestamp check for LeaseSet2 --- libi2pd/LeaseSet.cpp | 36 ++++++++++++++++++++++-------------- libi2pd/LeaseSet.h | 10 ++++++---- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index b411ca0e..6ce77792 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -149,20 +149,13 @@ namespace data auto ret = m_Leases.insert (std::make_shared(lease)); if (!ret.second) (*ret.first)->endDate = lease.endDate; // update existing (*ret.first)->isUpdated = true; - // check if lease's gateway is in our netDb - if (!netdb.FindRouter (lease.tunnelGateway)) - { - // if not found request it - LogPrint (eLogInfo, "LeaseSet: Lease's tunnel gateway not found, requesting"); - netdb.RequestDestination (lease.tunnelGateway); - } } } else LogPrint (eLogWarning, "LeaseSet: Lease is expired already"); } - uint64_t LeaseSet::ExtractTimestamp (const uint8_t * buf, size_t len) const + uint64_t LeaseSet::ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const { if (!m_Identity) return 0; size_t size = m_Identity->GetFullLen (); @@ -187,7 +180,7 @@ namespace data bool LeaseSet::IsNewer (const uint8_t * buf, size_t len) const { - return ExtractTimestamp (buf, len) > ExtractTimestamp (m_Buffer, m_BufferLen); + return ExtractExpirationTimestamp (buf, len) > (m_ExpirationTime ? m_ExpirationTime : ExtractExpirationTimestamp (m_Buffer, m_BufferLen)); } bool LeaseSet::ExpiresSoon(const uint64_t dlt, const uint64_t fudge) const @@ -282,6 +275,12 @@ namespace data ReadFromBuffer (buf, len, false, verifySignature); // TODO: implement encrypted } + + bool LeaseSet2::IsNewer (const uint8_t * buf, size_t len) const + { + uint64_t expiration; + return ExtractPublishedTimestamp (buf, len, expiration) > m_PublishedTimestamp; + } void LeaseSet2::ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity, bool verifySignature) { @@ -640,7 +639,14 @@ namespace data encryptor->Encrypt (data, encrypted, ctx, true); } - uint64_t LeaseSet2::ExtractTimestamp (const uint8_t * buf, size_t len) const + uint64_t LeaseSet2::ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const + { + uint64_t expiration = 0; + ExtractPublishedTimestamp (buf, len, expiration); + return expiration; + } + + uint64_t LeaseSet2::ExtractPublishedTimestamp (const uint8_t * buf, size_t len, uint64_t& expiration) const { if (len < 8) return 0; if (m_StoreType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) @@ -655,7 +661,8 @@ namespace data offset += blindedKeyLen; uint32_t timestamp = bufbe32toh (buf + offset); offset += 4; uint16_t expires = bufbe16toh (buf + offset); offset += 2; - return (timestamp + expires)* 1000LL; + expiration = (timestamp + expires)* 1000LL; + return timestamp; } else { @@ -665,10 +672,11 @@ namespace data if (offset + 6 >= len) return 0; uint32_t timestamp = bufbe32toh (buf + offset); offset += 4; uint16_t expires = bufbe16toh (buf + offset); offset += 2; - return (timestamp + expires)* 1000LL; + expiration = (timestamp + expires)* 1000LL; + return timestamp; } - } - + } + LocalLeaseSet::LocalLeaseSet (std::shared_ptr identity, const uint8_t * encryptionPublicKey, std::vector > tunnels): m_ExpirationTime (0), m_Identity (identity) { diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index 0d255644..4d084da3 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -63,14 +63,14 @@ namespace data LeaseSet (const uint8_t * buf, size_t len, bool storeLeases = true); virtual ~LeaseSet () { delete[] m_EncryptionKey; delete[] m_Buffer; }; virtual void Update (const uint8_t * buf, size_t len, bool verifySignature = true); - bool IsNewer (const uint8_t * buf, size_t len) const; + virtual bool IsNewer (const uint8_t * buf, size_t len) const; void PopulateLeases (); // from buffer const uint8_t * GetBuffer () const { return m_Buffer; }; size_t GetBufferLen () const { return m_BufferLen; }; bool IsValid () const { return m_IsValid; }; const std::vector > GetNonExpiredLeases (bool withThreshold = true) const; - const std::vector > GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold = true) const; + const std::vector > GetNonExpiredLeasesExcluding (LeaseInspectFunc exclude, bool withThreshold = true) const; bool HasExpiredLeases () const; bool IsExpired () const; bool IsEmpty () const { return m_Leases.empty (); }; @@ -106,7 +106,7 @@ namespace data private: void ReadFromBuffer (bool readIdentity = true, bool verifySignature = true); - virtual uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const; // returns max expiration time + virtual uint64_t ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const; // returns max expiration time private: @@ -145,6 +145,7 @@ namespace data bool IsPublishedEncrypted () const { return m_IsPublishedEncrypted; }; std::shared_ptr GetTransientVerifier () const { return m_TransientVerifier; }; void Update (const uint8_t * buf, size_t len, bool verifySignature); + bool IsNewer (const uint8_t * buf, size_t len) const; // implements RoutingDestination void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; @@ -160,7 +161,8 @@ namespace data template bool VerifySignature (Verifier& verifier, const uint8_t * buf, size_t len, size_t signatureOffset); - uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const; + uint64_t ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const; + uint64_t ExtractPublishedTimestamp (const uint8_t * buf, size_t len, uint64_t& expiration) const; size_t ExtractClientAuthData (const uint8_t * buf, size_t len, const uint8_t * secret, const uint8_t * subcredential, uint8_t * authCookie) const; // subcredential is subcredential + timestamp, return length of autData without flag private: From 7133a07f389de35e273d65e24b066ec1396f2d26 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 30 Apr 2020 01:52:44 +0300 Subject: [PATCH 3652/6300] [SOCKS] wrap DNS type requests response as IPv4 (fixes netcat usage, closes #1336) Signed-off-by: R4SAS --- libi2pd_client/SOCKS.cpp | 69 ++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index 8742d4c5..9cb69590 100644 --- a/libi2pd_client/SOCKS.cpp +++ b/libi2pd_client/SOCKS.cpp @@ -135,9 +135,9 @@ namespace proxy void HandleUpstreamSockSend(const boost::system::error_code & ecode, std::size_t bytes_transfered); void HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void HandleUpstreamConnected(const boost::system::error_code & ecode, - boost::asio::ip::tcp::resolver::iterator itr); + boost::asio::ip::tcp::resolver::iterator itr); void HandleUpstreamResolved(const boost::system::error_code & ecode, - boost::asio::ip::tcp::resolver::iterator itr); + boost::asio::ip::tcp::resolver::iterator itr); boost::asio::ip::tcp::resolver m_proxy_resolver; uint8_t m_sock_buff[socks_buffer_size]; @@ -184,8 +184,8 @@ namespace proxy LogPrint(eLogDebug, "SOCKS: async sock read"); if (m_sock) { m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size), - std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); + std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); } else { LogPrint(eLogError,"SOCKS: no socket for read"); } @@ -219,8 +219,8 @@ namespace proxy assert(error >= SOCKS4_OK); m_response[0] = '\x00'; //Version m_response[1] = error; //Response code - htobe16buf(m_response+2,port); //Port - htobe32buf(m_response+4,ip); //IP + htobe16buf(m_response + 2, port); //Port + htobe32buf(m_response + 4, ip); //IP return boost::asio::const_buffers_1(m_response,8); } @@ -236,20 +236,23 @@ namespace proxy { case ADDR_IPV4: size = 10; - htobe32buf(m_response+4,addr.ip); + htobe32buf(m_response + 4, addr.ip); break; case ADDR_IPV6: size = 22; - memcpy(m_response+4,addr.ipv6, 16); + memcpy(m_response + 4, addr.ipv6, 16); break; case ADDR_DNS: - size = 7+addr.dns.size; + size = 7 + addr.dns.size; m_response[4] = addr.dns.size; - memcpy(m_response+5,addr.dns.value, addr.dns.size); + memcpy(m_response + 5, addr.dns.value, addr.dns.size); + // replace type to IPv4 for support socks5 clients + // without domain name resolving support (like netcat) + m_response[3] = ADDR_IPV4; break; } - htobe16buf(m_response+size-2,port); //Port - return boost::asio::const_buffers_1(m_response,size); + htobe16buf(m_response + size - 2, port); //Port + return boost::asio::const_buffers_1(m_response, size); } boost::asio::const_buffers_1 SOCKSHandler::GenerateUpstreamRequest() @@ -259,7 +262,7 @@ namespace proxy // SOCKS 4a m_upstream_request[0] = '\x04'; //version m_upstream_request[1] = m_cmd; - htobe16buf(m_upstream_request+2, m_port); + htobe16buf(m_upstream_request + 2, m_port); m_upstream_request[4] = 0; m_upstream_request[5] = 0; m_upstream_request[6] = 0; @@ -270,7 +273,7 @@ namespace proxy m_upstream_request[10] = 'p'; m_upstream_request[11] = 'd'; m_upstream_request[12] = 0; - upstreamRequestSize += 13; + upstreamRequestSize += 13; if (m_address.dns.size <= max_socks_hostname_size - ( upstreamRequestSize + 1) ) { // bounds check okay memcpy(m_upstream_request + upstreamRequestSize, m_address.dns.value, m_address.dns.size); @@ -285,21 +288,19 @@ namespace proxy bool SOCKSHandler::Socks5ChooseAuth() { - m_response[0] = '\x05'; //Version - m_response[1] = m_authchosen; //Response code - boost::asio::const_buffers_1 response(m_response,2); + m_response[0] = '\x05'; // Version + m_response[1] = m_authchosen; // Response code + boost::asio::const_buffers_1 response(m_response, 2); if (m_authchosen == AUTH_UNACCEPTABLE) { LogPrint(eLogWarning, "SOCKS: v5 authentication negotiation failed"); - boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, - shared_from_this(), std::placeholders::_1)); + boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, shared_from_this(), std::placeholders::_1)); return false; } else { LogPrint(eLogDebug, "SOCKS: v5 choosing authentication method: ", m_authchosen); - boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse, - shared_from_this(), std::placeholders::_1)); + boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse, shared_from_this(), std::placeholders::_1)); return true; } } @@ -313,7 +314,7 @@ namespace proxy { case SOCKS4: LogPrint(eLogWarning, "SOCKS: v4 request failed: ", error); - if (error < SOCKS4_OK) error = SOCKS4_FAIL; //Transparently map SOCKS5 errors + if (error < SOCKS4_OK) error = SOCKS4_FAIL; // Transparently map SOCKS5 errors response = GenerateSOCKS4Response(error, m_4aip, m_port); break; case SOCKS5: @@ -322,13 +323,13 @@ namespace proxy break; } boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, - shared_from_this(), std::placeholders::_1)); + shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::SocksRequestSuccess() { boost::asio::const_buffers_1 response(nullptr,0); - //TODO: this should depend on things like the command type and callbacks may change + // TODO: this should depend on things like the command type and callbacks may change switch (m_socksv) { case SOCKS4: @@ -339,12 +340,11 @@ namespace proxy LogPrint(eLogInfo, "SOCKS: v5 connection success"); auto s = i2p::client::context.GetAddressBook().ToAddress(GetOwner()->GetLocalDestination()->GetIdentHash()); address ad; ad.dns.FromString(s); - //HACK only 16 bits passed in port as SOCKS5 doesn't allow for more + // HACK only 16 bits passed in port as SOCKS5 doesn't allow for more response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, ad, m_stream->GetRecvStreamID()); break; } - boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone, - shared_from_this(), std::placeholders::_1)); + boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone, shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::EnterState(SOCKSHandler::state nstate, uint8_t parseleft) { @@ -366,12 +366,12 @@ namespace proxy { if ( m_cmd != CMD_CONNECT ) { - //TODO: we need to support binds and other shit! + // TODO: we need to support binds and other shit! LogPrint(eLogError, "SOCKS: unsupported command: ", m_cmd); SocksRequestFailed(SOCKS5_CMD_UNSUP); return false; } - //TODO: we may want to support other address types! + // TODO: we may want to support other address types! if ( m_addrtype != ADDR_DNS ) { switch (m_socksv) @@ -433,9 +433,9 @@ namespace proxy break; case CMD_UDP: if (m_socksv == SOCKS5) break; -#if (__cplusplus >= 201703L) // C++ 17 or higher +#if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; -#endif +#endif default: LogPrint(eLogError, "SOCKS: invalid command: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); @@ -652,8 +652,8 @@ namespace proxy LogPrint(eLogDebug, "SOCKS: async upstream sock read"); if (m_upstreamSock) { m_upstreamSock->async_read_some(boost::asio::buffer(m_upstream_response, SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE), - std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); + std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(), + std::placeholders::_1, std::placeholders::_2)); } else { LogPrint(eLogError, "SOCKS: no upstream socket for read"); SocksRequestFailed(SOCKS5_GEN_FAIL); @@ -735,8 +735,7 @@ namespace proxy LogPrint(eLogInfo, "SOCKS: negotiating with upstream proxy"); EnterState(UPSTREAM_HANDSHAKE); if (m_upstreamSock) { - boost::asio::write(*m_upstreamSock, - GenerateUpstreamRequest()); + boost::asio::write(*m_upstreamSock, GenerateUpstreamRequest()); AsyncUpstreamSockRead(); } else { LogPrint(eLogError, "SOCKS: no upstream socket to send handshake to"); From 27d69894d474092f97990b726d787cd5245ddda5 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 29 Apr 2020 20:50:31 -0400 Subject: [PATCH 3653/6300] show ECIESx25519 session and tag on the web console --- daemon/HTTPServer.cpp | 11 +++++++++++ libi2pd/ECIESX25519AEADRatchetSession.h | 9 +++++++++ libi2pd/Garlic.h | 2 ++ 3 files changed, 22 insertions(+) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index ef1e0716..bc1eccd5 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -22,6 +22,7 @@ #include "HTTPServer.h" #include "Daemon.h" #include "util.h" +#include "ECIESX25519AEADRatchetSession.h" #ifdef WIN32_APP #include "Win32/Win32App.h" #endif @@ -409,6 +410,16 @@ namespace http { << "

    (jump.substr (0, pos)); LogPrint (eLogInfo, "Addressbook: added ", address," -> ", jump); - } + } else - { - // assume base64 + { + // assume base64 auto ident = std::make_shared(); if (ident->FromBase64 (jump)) { @@ -487,18 +487,18 @@ namespace client LogPrint (eLogWarning, "Addressbook: subscriptions.txt usage is deprecated, use config file instead"); } else if (!i2p::config::IsDefault("addressbook.subscriptions")) - { - // using config file items - std::string subscriptionURLs; i2p::config::GetOption("addressbook.subscriptions", subscriptionURLs); - std::vector subsList; - boost::split(subsList, subscriptionURLs, boost::is_any_of(","), boost::token_compress_on); + { + // using config file items + std::string subscriptionURLs; i2p::config::GetOption("addressbook.subscriptions", subscriptionURLs); + std::vector subsList; + boost::split(subsList, subscriptionURLs, boost::is_any_of(","), boost::token_compress_on); - for (size_t i = 0; i < subsList.size (); i++) - { - m_Subscriptions.push_back (std::make_shared (*this, subsList[i])); - } - LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); - } + for (size_t i = 0; i < subsList.size (); i++) + { + m_Subscriptions.push_back (std::make_shared (*this, subsList[i])); + } + LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); + } } else LogPrint (eLogError, "Addressbook: subscriptions already loaded"); @@ -515,7 +515,7 @@ namespace client if (dot != std::string::npos) { auto domain = it.first.substr (dot + 1); - auto it1 = m_Addresses.find (domain); // find domain in our addressbook + auto it1 = m_Addresses.find (domain); // find domain in our addressbook if (it1 != m_Addresses.end () && it1->second->IsIdentHash ()) { auto dest = context.FindLocalDestination (it1->second->identHash); @@ -610,7 +610,7 @@ namespace client { // download it from default subscription LogPrint (eLogInfo, "Addressbook: trying to download it from default subscription."); - std::string defaultSubURL; i2p::config::GetOption("addressbook.defaulturl", defaultSubURL); + std::string defaultSubURL; i2p::config::GetOption("addressbook.defaulturl", defaultSubURL); if (!m_DefaultSubscription) m_DefaultSubscription = std::make_shared(*this, defaultSubURL); m_IsDownloading = true; @@ -743,13 +743,13 @@ namespace client i2p::http::URL url; // must be run in separate thread LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link); - if (!url.parse(m_Link)) + if (!url.parse(m_Link)) { LogPrint(eLogError, "Addressbook: failed to parse url: ", m_Link); return false; } auto addr = m_Book.GetAddress (url.host); - if (!addr || !addr->IsIdentHash ()) + if (!addr || !addr->IsIdentHash ()) { LogPrint (eLogError, "Addressbook: Can't resolve ", url.host); return false; @@ -802,7 +802,7 @@ namespace client /* convert url to relative */ url.schema = ""; url.host = ""; - req.uri = url.to_string(); + 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()); @@ -920,7 +920,7 @@ namespace client { auto datagram = m_LocalDestination->GetDatagramDestination (); if (datagram) - datagram->ResetReceiver (ADDRESS_RESOLVER_DATAGRAM_PORT); + datagram->ResetReceiver (ADDRESS_RESOLVER_DATAGRAM_PORT); } } diff --git a/libi2pd_client/AddressBook.h b/libi2pd_client/AddressBook.h index 82ba167b..fb69dad3 100644 --- a/libi2pd_client/AddressBook.h +++ b/libi2pd_client/AddressBook.h @@ -37,8 +37,8 @@ namespace client i2p::data::IdentHash identHash; std::shared_ptr blindedPublicKey; - Address (const std::string& b32); - Address (const i2p::data::IdentHash& hash); + Address (const std::string& b32); + Address (const i2p::data::IdentHash& hash); bool IsIdentHash () const { return addressType == eAddressIndentHash; }; bool IsValid () const { return addressType != eAddressInvalid; }; }; @@ -160,5 +160,3 @@ namespace client } #endif - - diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp index c7bbdb46..e5d5404b 100644 --- a/libi2pd_client/BOB.cpp +++ b/libi2pd_client/BOB.cpp @@ -50,10 +50,10 @@ namespace client void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr receiver) { receiver->socket->async_read_some (boost::asio::buffer( - receiver->buffer + receiver->bufferOffset, - BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset), + receiver->buffer + receiver->bufferOffset, + BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset), std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this, - std::placeholders::_1, std::placeholders::_2, receiver)); + std::placeholders::_1, std::placeholders::_2, receiver)); } void BOBI2PInboundTunnel::HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred, @@ -255,7 +255,7 @@ namespace client std::bind(&BOBCommandSession::HandleReceivedLine, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } - + void BOBCommandSession::HandleReceivedLine(const boost::system::error_code& ecode, std::size_t bytes_transferred) { if(ecode) @@ -267,14 +267,14 @@ namespace client else { std::string line; - + std::istream is(&m_ReceiveBuffer); std::getline(is, line); - + std::string command, operand; std::istringstream iss(line); iss >> command >> operand; - + // process command auto& handlers = m_Owner.GetCommandHandlers(); auto it = handlers.find(command); @@ -346,7 +346,7 @@ namespace client std::ostream os(&m_SendBuffer); os << data << std::endl; } - + void BOBCommandSession::BuildStatusLine(bool currentTunnel, BOBDestination *dest, std::string &out) { // helper lambdas @@ -355,7 +355,7 @@ namespace client const auto destExists = [](const BOBDestination * const dest) { return dest != nullptr; }; const auto destReady = [](const BOBDestination * const dest) { return dest->GetLocalDestination()->IsReady(); }; const auto bool_str = [](const bool v) { return v ? "true" : "false"; }; // bool -> str - + // tunnel info const std::string nickname = currentTunnel ? m_Nickname : dest->GetNickname(); const bool quiet = currentTunnel ? m_IsQuiet : dest->GetQuiet(); @@ -367,7 +367,7 @@ namespace client const bool starting = destExists(dest) && !destReady(dest); const bool running = destExists(dest) && destReady(dest); const bool stopping = false; - + // build line std::stringstream ss; ss << "DATA " @@ -433,11 +433,11 @@ namespace client return; } } - + if (!m_CurrentDestination) { m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options), // deleted in clear command - m_Nickname, m_InHost, m_OutHost, m_InPort, m_OutPort, m_IsQuiet); + m_Nickname, m_InHost, m_OutHost, m_InPort, m_OutPort, m_IsQuiet); m_Owner.AddDestination (m_Nickname, m_CurrentDestination); } if (m_InPort) @@ -613,25 +613,24 @@ namespace client } auto localDestination = m_CurrentDestination ? m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); if (addr->IsIdentHash ()) - { + { // we might have leaseset already auto leaseSet = localDestination->FindLeaseSet (addr->identHash); if (leaseSet) - { + { SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ()); return; } } // trying to request - auto s = shared_from_this (); - auto requstCallback = - [s](std::shared_ptr ls) - { - if (ls) - s->SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); - else - s->SendReplyError ("LeaseSet Not found"); - }; + auto s = shared_from_this (); + auto requstCallback = [s](std::shared_ptr ls) + { + if (ls) + s->SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); + else + s->SendReplyError ("LeaseSet Not found"); + }; if (addr->IsIdentHash ()) localDestination->RequestDestination (addr->identHash, requstCallback); else @@ -794,7 +793,7 @@ namespace client BOBCommandChannel::~BOBCommandChannel () { - if (IsRunning ()) + if (IsRunning ()) Stop (); for (const auto& it: m_Destinations) delete it.second; @@ -856,8 +855,7 @@ namespace client session->SendVersion (); } else - LogPrint (eLogError, "BOB: accept error: ", ecode.message ()); + LogPrint (eLogError, "BOB: accept error: ", ecode.message ()); } } } - diff --git a/libi2pd_client/BOB.h b/libi2pd_client/BOB.h index 15a0afaf..a3d58148 100644 --- a/libi2pd_client/BOB.h +++ b/libi2pd_client/BOB.h @@ -39,7 +39,7 @@ namespace client const char BOB_COMMAND_OPTION[] = "option"; const char BOB_COMMAND_STATUS[] = "status"; const char BOB_COMMAND_HELP[] = "help"; - + const char BOB_HELP_ZAP[] = "zap - Shuts down BOB."; const char BOB_HELP_QUIT[] = "quit - Quits this session with BOB."; const char BOB_HELP_START[] = "start - Starts the current nicknamed tunnel."; @@ -75,15 +75,15 @@ namespace client class BOBI2PInboundTunnel: public BOBI2PTunnel { - struct AddressReceiver - { - std::shared_ptr socket; - char buffer[BOB_COMMAND_BUFFER_SIZE + 1]; // for destination base64 address - uint8_t * data; // pointer to buffer - size_t dataLen, bufferOffset; + struct AddressReceiver + { + std::shared_ptr socket; + char buffer[BOB_COMMAND_BUFFER_SIZE + 1]; // for destination base64 address + uint8_t * data; // pointer to buffer + size_t dataLen, bufferOffset; - AddressReceiver (): data (nullptr), dataLen (0), bufferOffset (0) {}; - }; + AddressReceiver (): data (nullptr), dataLen (0), bufferOffset (0) {}; + }; public: @@ -115,7 +115,7 @@ namespace client { public: - BOBI2POutboundTunnel (const std::string& outhost, int port, std::shared_ptr localDestination, bool quiet); + BOBI2POutboundTunnel (const std::string& outhost, int port, std::shared_ptr localDestination, bool quiet); void Start (); void Stop (); @@ -162,7 +162,7 @@ namespace client std::shared_ptr m_LocalDestination; BOBI2POutboundTunnel * m_OutboundTunnel; BOBI2PInboundTunnel * m_InboundTunnel; - + std::string m_Nickname; std::string m_InHost, m_OutHost; int m_InPort, m_OutPort; @@ -215,14 +215,14 @@ namespace client void SendReplyOK (const char * msg = nullptr); void SendReplyError (const char * msg); void SendRaw (const char * data); - + void BuildStatusLine(bool currentTunnel, BOBDestination *destination, std::string &out); private: BOBCommandChannel& m_Owner; boost::asio::ip::tcp::socket m_Socket; - boost::asio::streambuf m_ReceiveBuffer, m_SendBuffer; + boost::asio::streambuf m_ReceiveBuffer, m_SendBuffer; bool m_IsOpen, m_IsQuiet, m_IsActive; std::string m_Nickname, m_InHost, m_OutHost; int m_InPort, m_OutPort; @@ -269,4 +269,3 @@ namespace client } #endif - diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 51f10f5e..03c756a1 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -52,18 +52,18 @@ namespace client // SAM bool sam; i2p::config::GetOption("sam.enabled", sam); - if (sam) + if (sam) { std::string samAddr; i2p::config::GetOption("sam.address", samAddr); uint16_t samPort; i2p::config::GetOption("sam.port", samPort); - bool singleThread; i2p::config::GetOption("sam.singlethread", singleThread); + bool singleThread; i2p::config::GetOption("sam.singlethread", singleThread); LogPrint(eLogInfo, "Clients: starting SAM bridge at ", samAddr, ":", samPort); - try + try { m_SamBridge = new SAMBridge (samAddr, samPort, singleThread); m_SamBridge->Start (); - } - catch (std::exception& e) + } + catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what()); ThrowFatal ("Unable to start SAM bridge at ", samAddr, ":", samPort, ": ", e.what ()); @@ -76,12 +76,12 @@ namespace client std::string bobAddr; i2p::config::GetOption("bob.address", bobAddr); uint16_t bobPort; i2p::config::GetOption("bob.port", bobPort); LogPrint(eLogInfo, "Clients: starting BOB command channel at ", bobAddr, ":", bobPort); - try + try { m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); m_BOBCommandChannel->Start (); - } - catch (std::exception& e) + } + catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what()); ThrowFatal ("Unable to start BOB bridge at ", bobAddr, ":", bobPort, ": ", e.what ()); @@ -95,12 +95,12 @@ namespace client std::string i2cpAddr; i2p::config::GetOption("i2cp.address", i2cpAddr); uint16_t i2cpPort; i2p::config::GetOption("i2cp.port", i2cpPort); LogPrint(eLogInfo, "Clients: starting I2CP at ", i2cpAddr, ":", i2cpPort); - try + try { m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort); m_I2CPServer->Start (); - } - catch (std::exception& e) + } + catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in I2CP: ", e.what()); ThrowFatal ("Unable to start I2CP at ", i2cpAddr, ":", i2cpPort, ": ", e.what ()); @@ -407,13 +407,13 @@ namespace client template std::string ClientContext::GetI2CPOption (const Section& section, const std::string& name, const Type& value) const { - return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value)); + return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value)); } template std::string ClientContext::GetI2CPStringOption (const Section& section, const std::string& name, const std::string& value) const { - return section.second.get (boost::property_tree::ptree::path_type (name, '/'), value); + return section.second.get (boost::property_tree::ptree::path_type (name, '/'), value); } template @@ -423,13 +423,13 @@ namespace client { if (it.first.length () >= group.length () && !it.first.compare (0, group.length (), group)) options[it.first] = it.second.get_value (""); - } + } } - + template void ClientContext::ReadI2CPOptions (const Section& section, std::map& options) const { - options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); + options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); @@ -528,10 +528,10 @@ namespace client { std::string type = section.second.get (I2P_TUNNELS_SECTION_TYPE); if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT - || type == I2P_TUNNELS_SECTION_TYPE_SOCKS - || type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS - || type == I2P_TUNNELS_SECTION_TYPE_HTTPPROXY - || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) + || type == I2P_TUNNELS_SECTION_TYPE_SOCKS + || type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS + || type == I2P_TUNNELS_SECTION_TYPE_HTTPPROXY + || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { // mandatory params std::string dest; @@ -640,9 +640,9 @@ namespace client } } else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER - || type == I2P_TUNNELS_SECTION_TYPE_HTTP - || type == I2P_TUNNELS_SECTION_TYPE_IRC - || type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) + || type == I2P_TUNNELS_SECTION_TYPE_HTTP + || type == I2P_TUNNELS_SECTION_TYPE_IRC + || type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // mandatory params std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); @@ -699,7 +699,7 @@ namespace client continue; } - std::shared_ptr serverTunnel; + std::shared_ptr serverTunnel; if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) serverTunnel = std::make_shared (name, host, port, localDestination, hostOverride, inPort, gzip); else if (type == I2P_TUNNELS_SECTION_TYPE_IRC) @@ -745,7 +745,7 @@ namespace client ins.first->second->SetLocalDestination (serverTunnel->GetLocalDestination ()); } ins.first->second->isUpdated = true; - LogPrint (eLogInfo, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists"); + LogPrint (eLogInfo, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists"); } } @@ -769,9 +769,9 @@ namespace client std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys); std::string httpProxyAddr; i2p::config::GetOption("httpproxy.address", httpProxyAddr); uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort); - i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); std::string httpOutProxyURL; i2p::config::GetOption("httpproxy.outproxy", httpOutProxyURL); bool httpAddresshelper; i2p::config::GetOption("httpproxy.addresshelper", httpAddresshelper); + i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); LogPrint(eLogInfo, "Clients: starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); if (httpProxyKeys.length () > 0) { @@ -786,12 +786,12 @@ namespace client else LogPrint(eLogError, "Clients: failed to load HTTP Proxy key"); } - try + try { m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, httpOutProxyURL, httpAddresshelper, localDestination); m_HttpProxy->Start(); - } - catch (std::exception& e) + } + catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); ThrowFatal ("Unable to start HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort, ": ", e.what ()); @@ -826,13 +826,13 @@ namespace client else LogPrint(eLogError, "Clients: failed to load SOCKS Proxy key"); } - try + try { m_SocksProxy = new i2p::proxy::SOCKSProxy("SOCKS", socksProxyAddr, socksProxyPort, socksOutProxy, socksOutProxyAddr, socksOutProxyPort, localDestination); m_SocksProxy->Start(); - } - catch (std::exception& e) + } + catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in SOCKS Proxy: ", e.what()); ThrowFatal ("Unable to start SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort, ": ", e.what ()); diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 1a56575f..110f0970 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -75,10 +75,11 @@ namespace client const std::map * params = nullptr); // same as previous but on external io_service std::shared_ptr CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); - std::shared_ptr CreateNewLocalDestination (boost::asio::io_service& service, - const i2p::data::PrivateKeys& keys, bool isPublic = true, + std::shared_ptr CreateNewLocalDestination (boost::asio::io_service& service, + const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); // same as previous but on external io_service - std::shared_ptr CreateNewMatchedTunnelDestination(const i2p::data::PrivateKeys &keys, const std::string & name, const std::map * params = nullptr); + std::shared_ptr CreateNewMatchedTunnelDestination(const i2p::data::PrivateKeys &keys, + const std::string & name, const std::map * params = nullptr); void DeleteLocalDestination (std::shared_ptr destination); std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, @@ -114,7 +115,7 @@ namespace client template void VisitTunnels (Visitor v); // Visitor: (I2PService *) -> bool, true means retain - void CreateNewSharedLocalDestination (); + void CreateNewSharedLocalDestination (); void AddLocalDestination (std::shared_ptr localDestination); private: @@ -141,6 +142,7 @@ namespace client std::unique_ptr m_CleanupUDPTimer; public: + // for HTTP const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; }; const decltype(m_ClientTunnels)& GetClientTunnels () const { return m_ClientTunnels; }; diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index af4d2913..0c915921 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -191,7 +191,7 @@ namespace proxy { res.body = ss.str(); std::string response = res.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(response), boost::asio::transfer_all(), - std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); + std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64, bool & confirm) @@ -406,7 +406,7 @@ namespace proxy { void HTTPReqHandler::ForwardToUpstreamProxy() { LogPrint(eLogDebug, "HTTPProxy: forward to upstream"); - // build http requset + // build http request m_ClientRequestURL = m_RequestURL; LogPrint(eLogDebug, "HTTPProxy: ", m_ClientRequestURL.host); @@ -458,7 +458,7 @@ namespace proxy { if (!m_ProxyURL.port) m_ProxyURL.port = 9050; // default to tor default if not specified boost::asio::ip::tcp::resolver::query q(m_ProxyURL.host, std::to_string(m_ProxyURL.port)); m_proxy_resolver.async_resolve(q, std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) { - m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamSocksProxyConnect, this, std::placeholders::_1)); + m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamSocksProxyConnect, this, std::placeholders::_1)); })); } else @@ -562,14 +562,16 @@ namespace proxy { if(m_ClientRequest.method == "CONNECT") { m_ClientResponse.code = 200; m_send_buf = m_ClientResponse.to_string(); - boost::asio::async_write(*m_sock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&] (const boost::system::error_code & ec, std::size_t transferred) { + boost::asio::async_write(*m_sock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&] (const boost::system::error_code & ec, std::size_t transferred) + { if(ec) GenericProxyError("socks proxy error", ec.message().c_str()); else HandoverToUpstreamProxy(); }); } else { m_send_buf = m_ClientRequestBuffer.str(); LogPrint(eLogDebug, "HTTPProxy: send ", m_send_buf.size(), " bytes"); - boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&](const boost::system::error_code & ec, std::size_t transferred) { + boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&](const boost::system::error_code & ec, std::size_t transferred) + { if(ec) GenericProxyError("failed to send request to upstream", ec.message().c_str()); else HandoverToUpstreamProxy(); }); diff --git a/libi2pd_client/HTTPProxy.h b/libi2pd_client/HTTPProxy.h index 590166e3..4504eedd 100644 --- a/libi2pd_client/HTTPProxy.h +++ b/libi2pd_client/HTTPProxy.h @@ -6,6 +6,7 @@ namespace proxy { class HTTPProxy: public i2p::client::TCPIPAcceptor { public: + HTTPProxy(const std::string& name, const std::string& address, int port, const std::string & outproxy, bool addresshelper, std::shared_ptr localDestination); HTTPProxy(const std::string& name, const std::string& address, int port, std::shared_ptr localDestination = nullptr) : HTTPProxy(name, address, port, "", true, localDestination) {} ; @@ -15,11 +16,13 @@ namespace proxy { bool GetHelperSupport() { return m_Addresshelper; } protected: + // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return m_Name.c_str (); } private: + std::string m_Name; std::string m_OutproxyUrl; bool m_Addresshelper; diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index f4c8a91e..2cea5098 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -24,7 +24,7 @@ namespace client { I2CPDestination::I2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params): - RunnableService ("I2CP"), LeaseSetDestination (GetIOService (), isPublic, ¶ms), + RunnableService ("I2CP"), LeaseSetDestination (GetIOService (), isPublic, ¶ms), m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()) { } @@ -33,26 +33,26 @@ namespace client { if (IsRunning ()) Stop (); - } - + } + void I2CPDestination::Start () { if (!IsRunning ()) - { + { LeaseSetDestination::Start (); StartIOService (); - } + } } - + void I2CPDestination::Stop () { if (IsRunning ()) - { + { LeaseSetDestination::Stop (); StopIOService (); - } - } - + } + } + void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) { memcpy (m_EncryptionPrivateKey, key, 256); @@ -98,7 +98,7 @@ namespace client auto ls = (storeType == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) ? std::make_shared (m_Identity, buf, len): std::make_shared (storeType, m_Identity, buf, len); - ls->SetExpirationTime (m_LeaseSetExpirationTime); + ls->SetExpirationTime (m_LeaseSetExpirationTime); SetLeaseSet (ls); } @@ -221,7 +221,7 @@ namespace client auto s = shared_from_this (); m_Socket->async_read_some (boost::asio::buffer (m_Header, 1), [s](const boost::system::error_code& ecode, std::size_t bytes_transferred) - { + { if (!ecode && bytes_transferred > 0 && s->m_Header[0] == I2CP_PROTOCOL_BYTE) s->ReceiveHeader (); else @@ -247,15 +247,15 @@ namespace client if (m_PayloadLen > 0) { if (m_PayloadLen <= I2CP_MAX_MESSAGE_LENGTH) - { + { m_Payload = new uint8_t[m_PayloadLen]; ReceivePayload (); } else { - LogPrint (eLogError, "I2CP: Unexpected payload length ", m_PayloadLen); + LogPrint (eLogError, "I2CP: Unexpected payload length ", m_PayloadLen); Terminate (); - } + } } else // no following payload { @@ -323,7 +323,7 @@ namespace client memcpy (buf + I2CP_HEADER_SIZE, payload, len); boost::asio::async_write (*socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, buf)); + std::placeholders::_1, std::placeholders::_2, buf)); } else LogPrint (eLogError, "I2CP: Can't write to the socket"); @@ -510,8 +510,8 @@ namespace client } else LogPrint(eLogError, "I2CP: short message"); - SendSessionStatusMessage (status); - } + SendSessionStatusMessage (status); + } void I2CPSession::SendSessionStatusMessage (uint8_t status) { @@ -568,12 +568,12 @@ namespace client { LogPrint (eLogError, "I2CP: invalid LeaseSet2 of type ", storeType); return; - } + } offset += ls.GetBufferLen (); // private keys int numPrivateKeys = buf[offset]; offset++; uint16_t currentKeyType = 0; - const uint8_t * currentKey = nullptr; + const uint8_t * currentKey = nullptr; for (int i = 0; i < numPrivateKeys; i++) { if (offset + 4 > len) return; @@ -586,7 +586,7 @@ namespace client currentKey = buf + offset; } offset += keyLen; - } + } // TODO: support multiple keys if (currentKey) { @@ -594,7 +594,7 @@ namespace client m_Destination->SetEncryptionType (currentKeyType); } - m_Destination->LeaseSet2Created (storeType, ls.GetBuffer (), ls.GetBufferLen ()); + m_Destination->LeaseSet2Created (storeType, ls.GetBuffer (), ls.GetBufferLen ()); } } else @@ -779,14 +779,14 @@ namespace client memcpy (buf + I2CP_HEADER_SIZE + 10, payload, len); boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, buf)); + std::placeholders::_1, std::placeholders::_2, buf)); } I2CPServer::I2CPServer (const std::string& interface, int port): m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, #ifdef ANDROID - I2CPSession::proto::endpoint(std::string (1, '\0') + interface)) // leading 0 for abstract address + I2CPSession::proto::endpoint(std::string (1, '\0') + interface)) // leading 0 for abstract address #else I2CPSession::proto::endpoint(boost::asio::ip::address::from_string(interface), port)) #endif @@ -823,10 +823,10 @@ namespace client m_IsRunning = false; m_Acceptor.cancel (); { - auto sessions = m_Sessions; + auto sessions = m_Sessions; for (auto& it: sessions) it.second->Stop (); - } + } m_Sessions.clear (); m_Service.stop (); if (m_Thread) @@ -899,4 +899,3 @@ namespace client } } } - diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 7f590555..08dbaa21 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -25,7 +25,7 @@ namespace client const uint8_t I2CP_PROTOCOL_BYTE = 0x2A; const size_t I2CP_SESSION_BUFFER_SIZE = 4096; const size_t I2CP_MAX_MESSAGE_LENGTH = 65535; - + const size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; const size_t I2CP_HEADER_SIZE = I2CP_HEADER_TYPE_OFFSET + 1; @@ -69,10 +69,10 @@ namespace client I2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params); ~I2CPDestination (); - + void Start (); void Stop (); - + void SetEncryptionPrivateKey (const uint8_t * key); void SetEncryptionType (i2p::data::CryptoKeyType keyType) { m_EncryptionKeyType = keyType; }; void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession @@ -82,7 +82,7 @@ namespace client // implements LocalDestination bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { return m_EncryptionKeyType == keyType; }; - // TODO: implement GetEncryptionPublicKey + // TODO: implement GetEncryptionPublicKey std::shared_ptr GetIdentity () const { return m_Identity; }; protected: @@ -220,4 +220,3 @@ namespace client } #endif - diff --git a/libi2pd_client/I2PService.cpp b/libi2pd_client/I2PService.cpp index 31f34710..7a30decd 100644 --- a/libi2pd_client/I2PService.cpp +++ b/libi2pd_client/I2PService.cpp @@ -115,22 +115,24 @@ namespace client { if(m_ConnectTimeout && !m_LocalDestination->IsReady()) { - AddReadyCallback([this, streamRequestComplete, address, port] (const boost::system::error_code & ec) { + AddReadyCallback([this, streamRequestComplete, address, port] (const boost::system::error_code & ec) + { if(ec) { LogPrint(eLogWarning, "I2PService::CreateStream() ", ec.message()); streamRequestComplete(nullptr); } else - { if (address->IsIdentHash ()) + { + if (address->IsIdentHash ()) this->m_LocalDestination->CreateStream(streamRequestComplete, address->identHash, port); else - this->m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port); + this->m_LocalDestination->CreateStream (streamRequestComplete, address->blindedPublicKey, port); } }); } else - { + { if (address->IsIdentHash ()) m_LocalDestination->CreateStream (streamRequestComplete, address->identHash, port); else @@ -180,7 +182,7 @@ namespace client { m_up->async_read_some(boost::asio::buffer(m_upstream_to_down_buf, TCP_IP_PIPE_BUFFER_SIZE), std::bind(&TCPIPPipe::HandleUpstreamReceived, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); + std::placeholders::_1, std::placeholders::_2)); } else LogPrint(eLogError, "TCPIPPipe: upstream receive: no socket"); @@ -191,7 +193,7 @@ namespace client if (m_down) { m_down->async_read_some(boost::asio::buffer(m_downstream_to_up_buf, TCP_IP_PIPE_BUFFER_SIZE), std::bind(&TCPIPPipe::HandleDownstreamReceived, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); + std::placeholders::_1, std::placeholders::_2)); } else LogPrint(eLogError, "TCPIPPipe: downstream receive: no socket"); @@ -205,8 +207,8 @@ namespace client boost::asio::async_write(*m_up, boost::asio::buffer(m_upstream_buf, len), boost::asio::transfer_all(), std::bind(&TCPIPPipe::HandleUpstreamWrite, - shared_from_this(), - std::placeholders::_1)); + shared_from_this(), + std::placeholders::_1)); } else LogPrint(eLogError, "TCPIPPipe: upstream write: no socket"); @@ -220,8 +222,8 @@ namespace client boost::asio::async_write(*m_down, boost::asio::buffer(m_downstream_buf, len), boost::asio::transfer_all(), std::bind(&TCPIPPipe::HandleDownstreamWrite, - shared_from_this(), - std::placeholders::_1)); + shared_from_this(), + std::placeholders::_1)); } else LogPrint(eLogError, "TCPIPPipe: downstream write: no socket"); diff --git a/libi2pd_client/I2PService.h b/libi2pd_client/I2PService.h index e0dfd2da..be935b93 100644 --- a/libi2pd_client/I2PService.h +++ b/libi2pd_client/I2PService.h @@ -18,10 +18,12 @@ namespace client class I2PService : public std::enable_shared_from_this { public: + typedef std::function ReadyCallback; public: - I2PService (std::shared_ptr localDestination = nullptr); + + I2PService (std::shared_ptr localDestination = nullptr); I2PService (i2p::data::SigningKeyType kt); virtual ~I2PService (); @@ -42,7 +44,7 @@ namespace client void AddReadyCallback(ReadyCallback cb); inline std::shared_ptr GetLocalDestination () { return m_LocalDestination; } - inline std::shared_ptr GetLocalDestination () const { return m_LocalDestination; } + inline std::shared_ptr GetLocalDestination () const { return m_LocalDestination; } inline void SetLocalDestination (std::shared_ptr dest) { if (m_LocalDestination) m_LocalDestination->Release (); @@ -59,21 +61,24 @@ namespace client virtual const char* GetName() { return "Generic I2P Service"; } private: + void TriggerReadyCheckTimer(); void HandleReadyCheckTimer(const boost::system::error_code & ec); private: + std::shared_ptr m_LocalDestination; std::unordered_set > m_Handlers; std::mutex m_HandlersMutex; std::vector > m_ReadyCallbacks; boost::asio::deadline_timer m_ReadyTimer; - bool m_ReadyTimerTriggered; + bool m_ReadyTimerTriggered; uint32_t m_ConnectTimeout; - const size_t NEVER_TIMES_OUT = 0; - + const size_t NEVER_TIMES_OUT = 0; + public: + bool isUpdated; // transient, used during reload only }; @@ -81,6 +86,7 @@ namespace client class I2PServiceHandler { public: + I2PServiceHandler(I2PService * parent) : m_Service(parent), m_Dead(false) { } virtual ~I2PServiceHandler() { } //If you override this make sure you call it from the children @@ -89,6 +95,7 @@ namespace client void Terminate () { Kill (); }; protected: + // Call when terminating or handing over to avoid race conditions inline bool Kill () { return m_Dead.exchange(true); } // Call to know if the handler is dead @@ -99,6 +106,7 @@ namespace client inline I2PService * GetOwner() { return m_Service; } private: + I2PService *m_Service; std::atomic m_Dead; //To avoid cleaning up multiple times }; @@ -109,11 +117,13 @@ namespace client class TCPIPPipe: public I2PServiceHandler, public std::enable_shared_from_this { public: + TCPIPPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream); ~TCPIPPipe(); void Start(); protected: + void Terminate(); void AsyncReceiveUpstream(); void AsyncReceiveDownstream(); @@ -125,6 +135,7 @@ namespace client void DownstreamWrite(size_t len); private: + uint8_t m_upstream_to_down_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_to_up_buf[TCP_IP_PIPE_BUFFER_SIZE]; uint8_t m_upstream_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_buf[TCP_IP_PIPE_BUFFER_SIZE]; std::shared_ptr m_up, m_down; @@ -135,6 +146,7 @@ namespace client class TCPIPAcceptor: public I2PService { public: + TCPIPAcceptor (const std::string& address, int port, std::shared_ptr localDestination = nullptr) : I2PService(localDestination), m_LocalEndpoint (boost::asio::ip::address::from_string(address), port), @@ -149,14 +161,16 @@ namespace client //If you override this make sure you call it from the children void Stop (); - const boost::asio::ip::tcp::endpoint& GetLocalEndpoint () const { return m_LocalEndpoint; }; + const boost::asio::ip::tcp::endpoint& GetLocalEndpoint () const { return m_LocalEndpoint; }; virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; } protected: + virtual std::shared_ptr CreateHandler(std::shared_ptr socket) = 0; private: + void Accept(); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); boost::asio::ip::tcp::endpoint m_LocalEndpoint; diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index dc2a8811..b7a15c26 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -168,7 +168,7 @@ namespace client { m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), std::bind (&I2PTunnelConnection::HandleStreamReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2), + std::placeholders::_1, std::placeholders::_2), I2P_TUNNEL_CONNECTION_MAX_IDLE); } else // closed by peer @@ -257,7 +257,7 @@ namespace client if (!m_ConnectionSent && !line.compare(0, 10, "Connection")) { /* close connection, if not Connection: (U|u)pgrade (for websocket) */ - auto x = line.find("pgrade"); + auto x = line.find("pgrade"); if (x != std::string::npos && std::tolower(line[x - 1]) == 'u') m_OutHeader << line << "\r\n"; else @@ -281,7 +281,7 @@ namespace client if (endOfHeader) { if (!m_ConnectionSent) m_OutHeader << "Connection: close\r\n"; - if (!m_ProxyConnectionSent) m_OutHeader << "Proxy-Connection: close\r\n"; + if (!m_ProxyConnectionSent) m_OutHeader << "Proxy-Connection: close\r\n"; m_OutHeader << "\r\n"; // end of header m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header m_InHeader.str (""); @@ -462,7 +462,7 @@ namespace client } /* HACK: maybe we should create a caching IdentHash provider in AddressBook */ - std::shared_ptr I2PClientTunnel::GetAddress () + std::shared_ptr I2PClientTunnel::GetAddress () { if (!m_Address) { @@ -477,7 +477,7 @@ namespace client { auto address = GetAddress (); if (address) - return std::make_shared(this, address, m_DestinationPort, socket); + return std::make_shared(this, address, m_DestinationPort, socket); else return nullptr; } @@ -826,7 +826,7 @@ namespace client { LogPrint(eLogError, "UDP Tunnel: ", m_RemoteDest, " not found"); return; - } + } m_RemoteIdent = new i2p::data::IdentHash; *m_RemoteIdent = addr->identHash; LogPrint(eLogInfo, "UDP Tunnel: resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32()); diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index fbe2f7bb..94f63e48 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -30,6 +30,7 @@ namespace client class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this { public: + I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr leaseSet, int port = 0); // to I2P I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, @@ -41,6 +42,7 @@ namespace client void Connect (bool isUniqueLocal = true); protected: + void Terminate (); void Receive (); @@ -55,6 +57,7 @@ namespace client std::shared_ptr GetSocket () const { return m_Socket; }; private: + uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; std::shared_ptr m_Socket; std::shared_ptr m_Stream; @@ -65,15 +68,18 @@ namespace client class I2PClientTunnelConnectionHTTP: public I2PTunnelConnection { public: + I2PClientTunnelConnectionHTTP (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream): I2PTunnelConnection (owner, socket, stream), m_HeaderSent (false), m_ConnectionSent (false), m_ProxyConnectionSent (false) {}; protected: + void Write (const uint8_t * buf, size_t len); private: + std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent, m_ConnectionSent, m_ProxyConnectionSent; }; @@ -81,14 +87,17 @@ namespace client class I2PServerTunnelConnectionHTTP: public I2PTunnelConnection { public: + I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& host); protected: + void Write (const uint8_t * buf, size_t len); private: + std::string m_Host; std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent; @@ -98,14 +107,17 @@ namespace client class I2PTunnelConnectionIRC: public I2PTunnelConnection { public: + I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass); protected: + void Write (const uint8_t * buf, size_t len); private: + std::shared_ptr m_From; std::stringstream m_OutPacket, m_InPacket; bool m_NeedsWebIrc; @@ -116,10 +128,12 @@ namespace client class I2PClientTunnel: public TCPIPAcceptor { protected: + // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); public: + I2PClientTunnel (const std::string& name, const std::string& destination, const std::string& address, int port, std::shared_ptr localDestination, int destinationPort = 0); ~I2PClientTunnel () {} @@ -130,9 +144,11 @@ namespace client const char* GetName() { return m_Name.c_str (); } private: + std::shared_ptr GetAddress (); private: + std::string m_Name, m_Destination; std::shared_ptr m_Address; int m_DestinationPort; @@ -160,9 +176,9 @@ namespace client uint8_t m_Buffer[I2P_UDP_MAX_MTU]; UDPSession(boost::asio::ip::udp::endpoint localEndpoint, - const std::shared_ptr & localDestination, - boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash * ident, - uint16_t ourPort, uint16_t theirPort); + const std::shared_ptr & localDestination, + boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash * ident, + uint16_t ourPort, uint16_t theirPort); void HandleReceived(const boost::system::error_code & ecode, std::size_t len); void Receive(); }; @@ -195,6 +211,7 @@ namespace client class I2PUDPServerTunnel { public: + I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::address localAddress, @@ -210,10 +227,12 @@ namespace client void SetUniqueLocal(bool isUniqueLocal = true) { m_IsUniqueLocal = isUniqueLocal; } private: + void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); UDPSessionPtr ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); private: + bool m_IsUniqueLocal; const std::string m_Name; boost::asio::ip::address m_LocalAddress; @@ -226,6 +245,7 @@ namespace client class I2PUDPClientTunnel { public: + I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort, bool gzip); @@ -240,6 +260,7 @@ namespace client void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); private: + typedef std::pair UDPConvo; void RecvFromLocal(); void HandleRecvFromLocal(const boost::system::error_code & e, std::size_t transferred); @@ -263,6 +284,7 @@ namespace client class I2PServerTunnel: public I2PService { public: + I2PServerTunnel (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, int inport = 0, bool gzip = true); @@ -282,6 +304,7 @@ namespace client const char* GetName() { return m_Name.c_str (); } private: + void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, std::shared_ptr resolver); @@ -290,6 +313,7 @@ namespace client virtual std::shared_ptr CreateI2PConnection (std::shared_ptr stream); private: + bool m_IsUniqueLocal; std::string m_Name, m_Address; int m_Port; @@ -302,28 +326,34 @@ namespace client class I2PServerTunnelHTTP: public I2PServerTunnel { public: + I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& host, int inport = 0, bool gzip = true); private: + std::shared_ptr CreateI2PConnection (std::shared_ptr stream); private: + std::string m_Host; }; class I2PServerTunnelIRC: public I2PServerTunnel { public: + I2PServerTunnelIRC (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& webircpass, int inport = 0, bool gzip = true); private: + std::shared_ptr CreateI2PConnection (std::shared_ptr stream); private: + std::string m_WebircPass; }; } diff --git a/libi2pd_client/MatchedDestination.cpp b/libi2pd_client/MatchedDestination.cpp index 5cec178f..a4c82504 100644 --- a/libi2pd_client/MatchedDestination.cpp +++ b/libi2pd_client/MatchedDestination.cpp @@ -14,10 +14,10 @@ namespace client void MatchedTunnelDestination::ResolveCurrentLeaseSet() { - auto addr = i2p::client::context.GetAddressBook().GetAddress (m_RemoteName); + auto addr = i2p::client::context.GetAddressBook().GetAddress (m_RemoteName); if(addr && addr->IsIdentHash ()) { - m_RemoteIdent = addr->identHash; + m_RemoteIdent = addr->identHash; auto ls = FindLeaseSet(m_RemoteIdent); if(ls) HandleFoundCurrentLeaseSet(ls); @@ -39,7 +39,7 @@ namespace client { m_ResolveTimer->expires_from_now(boost::posix_time::seconds(1)); m_ResolveTimer->async_wait([&](const boost::system::error_code & ec) { - if(!ec) ResolveCurrentLeaseSet(); + if(!ec) ResolveCurrentLeaseSet(); }); } } @@ -50,7 +50,7 @@ namespace client ClientDestination::Start(); m_ResolveTimer = std::make_shared(GetService()); GetTunnelPool()->SetCustomPeerSelector(this); - ResolveCurrentLeaseSet(); + ResolveCurrentLeaseSet(); } void MatchedTunnelDestination::Stop() diff --git a/libi2pd_client/MatchedDestination.h b/libi2pd_client/MatchedDestination.h index 9d61799a..53cae203 100644 --- a/libi2pd_client/MatchedDestination.h +++ b/libi2pd_client/MatchedDestination.h @@ -8,26 +8,30 @@ namespace i2p namespace client { /** - client tunnel that uses same OBEP as IBGW of each remote lease for a remote destination + * client tunnel that uses same OBEP as IBGW of each remote lease for a remote destination */ class MatchedTunnelDestination : public RunnableClientDestination, public i2p::tunnel::ITunnelPeerSelector { - public: - MatchedTunnelDestination(const i2p::data::PrivateKeys& keys, const std::string & remoteName, const std::map * params = nullptr); - void Start(); - void Stop(); + public: - bool SelectPeers(i2p::tunnel::Path & peers, int hops, bool inbound); + MatchedTunnelDestination(const i2p::data::PrivateKeys& keys, const std::string & remoteName, + const std::map * params = nullptr); + void Start(); + void Stop(); - private: - void ResolveCurrentLeaseSet(); - void HandleFoundCurrentLeaseSet(std::shared_ptr ls); + bool SelectPeers(i2p::tunnel::Path & peers, int hops, bool inbound); - private: - std::string m_RemoteName; - i2p::data::IdentHash m_RemoteIdent; - std::shared_ptr m_RemoteLeaseSet; - std::shared_ptr m_ResolveTimer; + private: + + void ResolveCurrentLeaseSet(); + void HandleFoundCurrentLeaseSet(std::shared_ptr ls); + + private: + + std::string m_RemoteName; + i2p::data::IdentHash m_RemoteIdent; + std::shared_ptr m_RemoteLeaseSet; + std::shared_ptr m_ResolveTimer; }; } } diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index d0942221..086cbe69 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -17,7 +17,7 @@ namespace client { SAMSocket::SAMSocket (SAMBridge& owner): m_Owner (owner), m_Socket(owner.GetService()), m_Timer (m_Owner.GetService ()), - m_BufferOffset (0), + m_BufferOffset (0), m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false), m_IsAccepting (false), m_Stream (nullptr) { @@ -26,7 +26,7 @@ namespace client SAMSocket::~SAMSocket () { m_Stream = nullptr; - } + } void SAMSocket::Terminate (const char* reason) { @@ -54,8 +54,7 @@ namespace client } break; } - default: - ; + default: ; } m_SocketType = eSAMSocketTypeTerminated; if (m_Socket.is_open ()) @@ -68,7 +67,7 @@ namespace client } void SAMSocket::ReceiveHandshake () - { + { m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE), std::bind(&SAMSocket::HandleHandshakeReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -152,7 +151,7 @@ namespace client size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ()); #endif boost::asio::async_write (m_Socket, boost::asio::buffer (m_Buffer, l), boost::asio::transfer_all (), - std::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (), + std::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } else @@ -170,7 +169,7 @@ namespace client { return id == m_ID; } - + void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) @@ -350,7 +349,7 @@ namespace client } std::shared_ptr forward = nullptr; - if ((type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) && + if ((type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) && params.find(SAM_VALUE_HOST) != params.end() && params.find(SAM_VALUE_PORT) != params.end()) { // udp forward selected @@ -372,7 +371,7 @@ namespace client } forward = std::make_shared(addr, port); } - + //ensure we actually received a destination if (destination.empty()) { @@ -381,7 +380,7 @@ namespace client } if (destination != SAM_VALUE_TRANSIENT) - { + { //ensure it's a base64 string i2p::data::PrivateKeys keys; if (!keys.FromBase64(destination)) @@ -389,7 +388,7 @@ namespace client SendMessageReply(SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true); return; } - } + } // create destination auto session = m_Owner.CreateSession (id, type, destination == SAM_VALUE_TRANSIENT ? "" : destination, ¶ms); @@ -542,7 +541,7 @@ namespace client m_SocketType = eSAMSocketTypeAcceptor; if (!session->localDestination->IsAcceptingStreams ()) { - m_IsAccepting = true; + m_IsAccepting = true; session->localDestination->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1)); } SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); @@ -567,7 +566,7 @@ namespace client { i2p::data::IdentityEx dest; dest.FromBase64 (params[SAM_PARAM_DESTINATION]); - if (session->Type == eSAMSessionTypeDatagram) + if (session->Type == eSAMSessionTypeDatagram) d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); else // raw d->SendRawDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); @@ -598,20 +597,20 @@ namespace client if (it != params.end ()) { if (!m_Owner.ResolveSignatureType (it->second, signatureType)) - LogPrint (eLogWarning, "SAM: ", SAM_PARAM_SIGNATURE_TYPE, " is invalid ", it->second); + LogPrint (eLogWarning, "SAM: ", SAM_PARAM_SIGNATURE_TYPE, " is invalid ", it->second); } it = params.find (SAM_PARAM_CRYPTO_TYPE); if (it != params.end ()) { try - { + { cryptoType = std::stoi(it->second); } - catch (const std::exception& ex) + catch (const std::exception& ex) { - LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); - } - } + LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); + } + } auto keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, @@ -647,12 +646,12 @@ namespace client else dest->RequestDestination (addr->identHash, std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, - shared_from_this (), std::placeholders::_1, name)); + shared_from_this (), std::placeholders::_1, name)); } else dest->RequestDestinationWithEncryptedLeaseSet (addr->blindedPublicKey, std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, - shared_from_this (), std::placeholders::_1, name)); + shared_from_this (), std::placeholders::_1, name)); } else { @@ -762,7 +761,7 @@ namespace client if (m_Stream) { if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || - m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular + m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular { m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE), std::bind (&SAMSocket::HandleI2PReceive, shared_from_this(), @@ -800,7 +799,7 @@ namespace client { delete [] buff; } - + void SAMSocket::WriteI2PData(size_t sz) { boost::asio::async_write ( @@ -809,7 +808,7 @@ namespace client boost::asio::transfer_all(), std::bind(&SAMSocket::HandleWriteI2PData, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } - + void SAMSocket::HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) @@ -955,7 +954,7 @@ namespace client auto ep = session->UDPEndpoint; if (ep) // udp forward enabled - m_Owner.SendTo(buf, len, ep); + m_Owner.SendTo(buf, len, ep); else { #ifdef _MSC_VER @@ -978,7 +977,7 @@ namespace client { m_Owner.GetService ().post (std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this())); } - + SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type, std::shared_ptr dest): m_Bridge(parent), localDestination (dest), @@ -986,7 +985,7 @@ namespace client Name(id), Type (type) { } - + SAMSession::~SAMSession () { i2p::client::context.DeleteLocalDestination (localDestination); @@ -1001,7 +1000,7 @@ namespace client } SAMBridge::SAMBridge (const std::string& address, int port, bool singleThread): - RunnableService ("SAM"), m_IsSingleThread (singleThread), + RunnableService ("SAM"), m_IsSingleThread (singleThread), m_Acceptor (GetIOService (), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), m_DatagramEndpoint (boost::asio::ip::address::from_string(address), port-1), m_DatagramSocket (GetIOService (), m_DatagramEndpoint), m_SignatureTypes @@ -1063,7 +1062,7 @@ namespace client std::unique_lock lock(m_OpenSocketsMutex); m_OpenSockets.remove_if([socket](const std::shared_ptr & item) -> bool { return item == socket; }); } - + void SAMBridge::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) { if (!ecode) @@ -1089,7 +1088,7 @@ namespace client Accept (); } - std::shared_ptr SAMBridge::CreateSession (const std::string& id, SAMSessionType type, + std::shared_ptr SAMBridge::CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, const std::map * params) { std::shared_ptr localDestination = nullptr; @@ -1097,7 +1096,7 @@ namespace client { i2p::data::PrivateKeys keys; if (!keys.FromBase64 (destination)) return nullptr; - localDestination = m_IsSingleThread ? + localDestination = m_IsSingleThread ? i2p::client::context.CreateNewLocalDestination (GetIOService (), keys, true, params) : i2p::client::context.CreateNewLocalDestination (keys, true, params); } @@ -1110,24 +1109,24 @@ namespace client { auto it = params->find (SAM_PARAM_SIGNATURE_TYPE); if (it != params->end ()) - { + { if (!ResolveSignatureType (it->second, signatureType)) - LogPrint (eLogWarning, "SAM: ", SAM_PARAM_SIGNATURE_TYPE, " is invalid ", it->second); - } + LogPrint (eLogWarning, "SAM: ", SAM_PARAM_SIGNATURE_TYPE, " is invalid ", it->second); + } it = params->find (SAM_PARAM_CRYPTO_TYPE); if (it != params->end ()) - { + { try - { + { cryptoType = std::stoi(it->second); } - catch (const std::exception& ex) + catch (const std::exception& ex) { - LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); - } - } + LogPrint (eLogWarning, "SAM: ", SAM_PARAM_CRYPTO_TYPE, "error: ", ex.what ()); + } + } } - localDestination = m_IsSingleThread ? + localDestination = m_IsSingleThread ? i2p::client::context.CreateNewLocalDestination (GetIOService (), true, signatureType, cryptoType, params) : i2p::client::context.CreateNewLocalDestination (true, signatureType, cryptoType, params); } @@ -1165,11 +1164,11 @@ namespace client { auto timer = std::make_shared(GetService ()); timer->expires_from_now (boost::posix_time::seconds(5)); // postpone destination clean for 5 seconds - timer->async_wait ([timer, session](const boost::system::error_code& ecode) + timer->async_wait ([timer, session](const boost::system::error_code& ecode) { // session's destructor is called here }); - } + } } } @@ -1193,7 +1192,7 @@ namespace client } return list; } - + void SAMBridge::SendTo(const uint8_t * buf, size_t len, std::shared_ptr remote) { if(remote) @@ -1261,7 +1260,7 @@ namespace client bool SAMBridge::ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const { try - { + { type = std::stoi (name); } catch (const std::invalid_argument& ex) @@ -1273,12 +1272,12 @@ namespace client else return false; } - catch (const std::exception& ex) + catch (const std::exception& ex) { - return false; - } - // name has been resolved - return true; + return false; + } + // name has been resolved + return true; } } } diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index 5a447c06..51a8bc57 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -87,7 +87,7 @@ namespace client typedef boost::asio::ip::tcp::socket Socket_t; SAMSocket (SAMBridge& owner); - ~SAMSocket (); + ~SAMSocket (); Socket_t& GetSocket () { return m_Socket; }; void ReceiveHandshake (); @@ -97,10 +97,11 @@ namespace client void Terminate (const char* reason); bool IsSession(const std::string & id) const; - - private: + + private: + void TerminateClose() { Terminate(nullptr); } - + void HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred); @@ -137,7 +138,7 @@ namespace client void HandleWriteI2PDataImmediate(const boost::system::error_code & ec, uint8_t * buff); void HandleStreamSend(const boost::system::error_code & ec); - + private: SAMBridge& m_Owner; @@ -186,7 +187,7 @@ namespace client void Stop (); boost::asio::io_service& GetService () { return GetIOService (); }; - std::shared_ptr CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient + std::shared_ptr CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient const std::map * params); void CloseSession (const std::string& id); std::shared_ptr FindSession (const std::string& id) const; @@ -197,8 +198,8 @@ namespace client void SendTo(const uint8_t * buf, size_t len, std::shared_ptr remote); void RemoveSocket(const std::shared_ptr & socket); - - bool ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const; + + bool ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const; private: diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index a70ac2b7..272ac178 100644 --- a/libi2pd_client/SOCKS.cpp +++ b/libi2pd_client/SOCKS.cpp @@ -41,6 +41,7 @@ namespace proxy class SOCKSHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: + enum state { GET_SOCKSV, @@ -137,7 +138,7 @@ namespace proxy void HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr); void HandleUpstreamResolved(const boost::system::error_code & ecode, - boost::asio::ip::tcp::resolver::iterator itr); + boost::asio::ip::tcp::resolver::iterator itr); boost::asio::ip::tcp::resolver m_proxy_resolver; uint8_t m_sock_buff[socks_buffer_size]; @@ -165,6 +166,7 @@ namespace proxy const uint16_t m_UpstreamProxyPort; public: + SOCKSHandler(SOCKSServer * parent, std::shared_ptr sock, const std::string & upstreamAddr, const uint16_t upstreamPort, const bool useUpstream) : I2PServiceHandler(parent), m_proxy_resolver(parent->GetService()), @@ -652,8 +654,7 @@ namespace proxy LogPrint(eLogDebug, "SOCKS: async upstream sock read"); if (m_upstreamSock) { m_upstreamSock->async_read_some(boost::asio::buffer(m_upstream_response, SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE), - std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(), - std::placeholders::_1, std::placeholders::_2)); + std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { LogPrint(eLogError, "SOCKS: no upstream socket for read"); SocksRequestFailed(SOCKS5_GEN_FAIL); @@ -773,7 +774,7 @@ namespace proxy SOCKSServer::SOCKSServer(const std::string& name, const std::string& address, int port, bool outEnable, const std::string& outAddress, uint16_t outPort, std::shared_ptr localDestination) : - TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), m_Name (name) + TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), m_Name (name) { m_UseUpstreamProxy = false; if (outAddress.length() > 0 && outEnable) diff --git a/libi2pd_client/SOCKS.h b/libi2pd_client/SOCKS.h index 87f08de4..fc43415a 100644 --- a/libi2pd_client/SOCKS.h +++ b/libi2pd_client/SOCKS.h @@ -14,6 +14,7 @@ namespace proxy class SOCKSServer: public i2p::client::TCPIPAcceptor { public: + SOCKSServer(const std::string& name, const std::string& address, int port, bool outEnable, const std::string& outAddress, uint16_t outPort, std::shared_ptr localDestination = nullptr); ~SOCKSServer() {}; @@ -21,6 +22,7 @@ namespace proxy void SetUpstreamProxy(const std::string & addr, const uint16_t port); protected: + // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return m_Name.c_str (); } From 8bae4975fbd923c55971278f6b389bef155b9258 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 22 May 2020 16:18:41 +0300 Subject: [PATCH 3709/6300] add copyright headers Signed-off-by: R4SAS --- Win32/DaemonWin32.cpp | 8 ++++++++ Win32/Win32App.cpp | 8 ++++++++ Win32/Win32App.h | 8 ++++++++ Win32/Win32NetState.cpp | 8 ++++++++ Win32/Win32NetState.h | 8 ++++++++ Win32/Win32Service.cpp | 8 ++++++++ Win32/Win32Service.h | 8 ++++++++ android/jni/DaemonAndroid.cpp | 8 ++++++++ android/jni/DaemonAndroid.h | 8 ++++++++ android/jni/i2pd_android.cpp | 8 ++++++++ android/jni/org_purplei2p_i2pd_I2PD_JNI.h | 8 ++++++++ daemon/Daemon.cpp | 8 ++++++++ daemon/Daemon.h | 8 ++++++++ daemon/HTTPServer.cpp | 8 ++++++++ daemon/HTTPServer.h | 8 ++++++++ daemon/I2PControl.cpp | 8 ++++++++ daemon/I2PControl.h | 8 ++++++++ daemon/UPnP.cpp | 8 ++++++++ daemon/UPnP.h | 8 ++++++++ daemon/UnixDaemon.cpp | 8 ++++++++ daemon/i2pd.cpp | 8 ++++++++ libi2pd/Base.cpp | 8 ++++++++ libi2pd/Base.h | 8 ++++++++ libi2pd/Blinding.cpp | 8 ++++++++ libi2pd/Blinding.h | 8 ++++++++ libi2pd/BloomFilter.cpp | 8 ++++++++ libi2pd/BloomFilter.h | 8 ++++++++ libi2pd/CPU.cpp | 8 ++++++++ libi2pd/CPU.h | 8 ++++++++ libi2pd/ChaCha20.cpp | 2 +- libi2pd/ChaCha20.h | 2 +- libi2pd/Config.cpp | 2 +- libi2pd/Config.h | 8 ++++++++ libi2pd/Crypto.cpp | 8 ++++++++ libi2pd/Crypto.h | 8 ++++++++ libi2pd/CryptoKey.cpp | 8 ++++++++ libi2pd/CryptoKey.h | 8 ++++++++ libi2pd/CryptoWorker.h | 8 ++++++++ libi2pd/Datagram.cpp | 8 ++++++++ libi2pd/Datagram.h | 8 ++++++++ libi2pd/Destination.cpp | 8 ++++++++ libi2pd/Destination.h | 8 ++++++++ libi2pd/ECIESX25519AEADRatchetSession.cpp | 8 ++++++++ libi2pd/ECIESX25519AEADRatchetSession.h | 8 ++++++++ libi2pd/Ed25519.cpp | 8 ++++++++ libi2pd/Ed25519.h | 8 ++++++++ libi2pd/Elligator.cpp | 8 ++++++++ libi2pd/Elligator.h | 8 ++++++++ libi2pd/FS.cpp | 2 +- libi2pd/FS.h | 2 +- libi2pd/Family.cpp | 8 ++++++++ libi2pd/Family.h | 8 ++++++++ libi2pd/Garlic.cpp | 8 ++++++++ libi2pd/Garlic.h | 8 ++++++++ libi2pd/Gost.cpp | 8 ++++++++ libi2pd/Gost.h | 8 ++++++++ libi2pd/Gzip.cpp | 2 +- libi2pd/Gzip.h | 8 ++++++++ libi2pd/HTTP.cpp | 2 +- libi2pd/HTTP.h | 2 +- libi2pd/I2NPProtocol.cpp | 8 ++++++++ libi2pd/I2NPProtocol.h | 8 ++++++++ libi2pd/I2PEndian.cpp | 8 ++++++++ libi2pd/I2PEndian.h | 8 ++++++++ libi2pd/Identity.cpp | 8 ++++++++ libi2pd/Identity.h | 8 ++++++++ libi2pd/LeaseSet.cpp | 8 ++++++++ libi2pd/LeaseSet.h | 8 ++++++++ libi2pd/LittleBigEndian.h | 8 ++++++++ libi2pd/Log.cpp | 2 +- libi2pd/Log.h | 2 +- libi2pd/NTCPSession.cpp | 8 ++++++++ libi2pd/NTCPSession.h | 8 ++++++++ libi2pd/NetDb.cpp | 8 ++++++++ libi2pd/NetDb.hpp | 8 ++++++++ libi2pd/NetDbRequests.cpp | 8 ++++++++ libi2pd/NetDbRequests.h | 8 ++++++++ libi2pd/Profiling.cpp | 8 ++++++++ libi2pd/Profiling.h | 8 ++++++++ libi2pd/Queue.h | 8 ++++++++ libi2pd/Reseed.cpp | 8 ++++++++ libi2pd/Reseed.h | 8 ++++++++ libi2pd/RouterContext.cpp | 8 ++++++++ libi2pd/RouterContext.h | 8 ++++++++ libi2pd/RouterInfo.cpp | 8 ++++++++ libi2pd/RouterInfo.h | 8 ++++++++ libi2pd/SSU.cpp | 8 ++++++++ libi2pd/SSU.h | 8 ++++++++ libi2pd/SSUData.cpp | 8 ++++++++ libi2pd/SSUData.h | 8 ++++++++ libi2pd/SSUSession.cpp | 8 ++++++++ libi2pd/SSUSession.h | 8 ++++++++ libi2pd/Signature.cpp | 8 ++++++++ libi2pd/Signature.h | 8 ++++++++ libi2pd/Streaming.cpp | 8 ++++++++ libi2pd/Streaming.h | 8 ++++++++ libi2pd/Tag.h | 8 ++++++++ libi2pd/Timestamp.cpp | 8 ++++++++ libi2pd/Timestamp.h | 8 ++++++++ libi2pd/TransitTunnel.cpp | 8 ++++++++ libi2pd/TransitTunnel.h | 8 ++++++++ libi2pd/TransportSession.h | 8 ++++++++ libi2pd/Transports.cpp | 8 ++++++++ libi2pd/Transports.h | 8 ++++++++ libi2pd/Tunnel.cpp | 8 ++++++++ libi2pd/Tunnel.h | 8 ++++++++ libi2pd/TunnelBase.h | 8 ++++++++ libi2pd/TunnelConfig.h | 8 ++++++++ libi2pd/TunnelEndpoint.cpp | 8 ++++++++ libi2pd/TunnelEndpoint.h | 8 ++++++++ libi2pd/TunnelGateway.cpp | 8 ++++++++ libi2pd/TunnelGateway.h | 8 ++++++++ libi2pd/TunnelPool.cpp | 8 ++++++++ libi2pd/TunnelPool.h | 8 ++++++++ libi2pd/api.cpp | 8 ++++++++ libi2pd/api.h | 8 ++++++++ libi2pd/util.cpp | 8 ++++++++ libi2pd/util.h | 8 ++++++++ libi2pd/version.h | 8 ++++++++ libi2pd_client/AddressBook.cpp | 8 ++++++++ libi2pd_client/AddressBook.h | 8 ++++++++ libi2pd_client/BOB.cpp | 8 ++++++++ libi2pd_client/BOB.h | 8 ++++++++ libi2pd_client/ClientContext.cpp | 8 ++++++++ libi2pd_client/ClientContext.h | 8 ++++++++ libi2pd_client/HTTPProxy.cpp | 2 +- libi2pd_client/HTTPProxy.h | 8 ++++++++ libi2pd_client/I2CP.cpp | 2 +- libi2pd_client/I2CP.h | 2 +- libi2pd_client/I2PService.cpp | 8 ++++++++ libi2pd_client/I2PService.h | 8 ++++++++ libi2pd_client/I2PTunnel.cpp | 8 ++++++++ libi2pd_client/I2PTunnel.h | 8 ++++++++ libi2pd_client/MatchedDestination.cpp | 8 ++++++++ libi2pd_client/MatchedDestination.h | 8 ++++++++ libi2pd_client/SAM.cpp | 8 ++++++++ libi2pd_client/SAM.h | 8 ++++++++ libi2pd_client/SOCKS.cpp | 8 ++++++++ libi2pd_client/SOCKS.h | 8 ++++++++ 139 files changed, 1021 insertions(+), 13 deletions(-) diff --git a/Win32/DaemonWin32.cpp b/Win32/DaemonWin32.cpp index 7ba3ffc3..1214ff68 100644 --- a/Win32/DaemonWin32.cpp +++ b/Win32/DaemonWin32.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include "Config.h" diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 3bcb24b3..df345b11 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/Win32/Win32App.h b/Win32/Win32App.h index 23082842..d242f7d3 100644 --- a/Win32/Win32App.h +++ b/Win32/Win32App.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef WIN32APP_H__ #define WIN32APP_H__ diff --git a/Win32/Win32NetState.cpp b/Win32/Win32NetState.cpp index 9a650179..dd4dd08c 100644 --- a/Win32/Win32NetState.cpp +++ b/Win32/Win32NetState.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #if WINVER != 0x0501 // supported since Vista #include "Win32NetState.h" #include diff --git a/Win32/Win32NetState.h b/Win32/Win32NetState.h index f043b171..bcb6c8d7 100644 --- a/Win32/Win32NetState.h +++ b/Win32/Win32NetState.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef WIN_32_NETSTATE_H__ #define WIN_32_NETSTATE_H__ diff --git a/Win32/Win32Service.cpp b/Win32/Win32Service.cpp index 9d1dedb4..7a6d7abf 100644 --- a/Win32/Win32Service.cpp +++ b/Win32/Win32Service.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifdef _WIN32 #define _CRT_SECURE_NO_WARNINGS // to use freopen #endif diff --git a/Win32/Win32Service.h b/Win32/Win32Service.h index 05a6b9f9..40fff787 100644 --- a/Win32/Win32Service.h +++ b/Win32/Win32Service.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef WIN_32_SERVICE_H__ #define WIN_32_SERVICE_H__ diff --git a/android/jni/DaemonAndroid.cpp b/android/jni/DaemonAndroid.cpp index c3a1b805..39c06ea0 100644 --- a/android/jni/DaemonAndroid.cpp +++ b/android/jni/DaemonAndroid.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/android/jni/DaemonAndroid.h b/android/jni/DaemonAndroid.h index 64bf64fd..912f6f49 100644 --- a/android/jni/DaemonAndroid.h +++ b/android/jni/DaemonAndroid.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef DAEMON_ANDROID_H #define DAEMON_ANDROID_H diff --git a/android/jni/i2pd_android.cpp b/android/jni/i2pd_android.cpp index 177bfd7c..c6e309dd 100755 --- a/android/jni/i2pd_android.cpp +++ b/android/jni/i2pd_android.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "org_purplei2p_i2pd_I2PD_JNI.h" #include "DaemonAndroid.h" diff --git a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h index d5c2f15f..68935ad1 100644 --- a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h +++ b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class org_purplei2p_i2pd_I2PD_JNI */ diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index dd302fce..264013cf 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include diff --git a/daemon/Daemon.h b/daemon/Daemon.h index 050cc7e8..836c2a8e 100644 --- a/daemon/Daemon.h +++ b/daemon/Daemon.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef DAEMON_H__ #define DAEMON_H__ diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 6f903e7e..7268ae01 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index fe62c271..a977e3e8 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef HTTP_SERVER_H__ #define HTTP_SERVER_H__ diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index fb00dc29..77614f2f 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/daemon/I2PControl.h b/daemon/I2PControl.h index eb28af76..d731c24e 100644 --- a/daemon/I2PControl.h +++ b/daemon/I2PControl.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef I2P_CONTROL_H__ #define I2P_CONTROL_H__ diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index 92e79c11..852122f2 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifdef USE_UPNP #include #include diff --git a/daemon/UPnP.h b/daemon/UPnP.h index 29f9e3e6..e8220e24 100644 --- a/daemon/UPnP.h +++ b/daemon/UPnP.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef __UPNP_H__ #define __UPNP_H__ diff --git a/daemon/UnixDaemon.cpp b/daemon/UnixDaemon.cpp index a59fc693..ffc5f1c0 100644 --- a/daemon/UnixDaemon.cpp +++ b/daemon/UnixDaemon.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include "Daemon.h" #ifndef _WIN32 diff --git a/daemon/i2pd.cpp b/daemon/i2pd.cpp index 7b68c073..028aa916 100644 --- a/daemon/i2pd.cpp +++ b/daemon/i2pd.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "Daemon.h" diff --git a/libi2pd/Base.cpp b/libi2pd/Base.cpp index 51f5f225..921c20af 100644 --- a/libi2pd/Base.cpp +++ b/libi2pd/Base.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include diff --git a/libi2pd/Base.h b/libi2pd/Base.h index 5d550092..073d9b40 100644 --- a/libi2pd/Base.h +++ b/libi2pd/Base.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef BASE_H__ #define BASE_H__ diff --git a/libi2pd/Blinding.cpp b/libi2pd/Blinding.cpp index 287d3648..6770d223 100644 --- a/libi2pd/Blinding.cpp +++ b/libi2pd/Blinding.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include // for crc32 #include #include diff --git a/libi2pd/Blinding.h b/libi2pd/Blinding.h index 3e0ad3fc..2f670882 100644 --- a/libi2pd/Blinding.h +++ b/libi2pd/Blinding.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef BLINDING_H__ #define BLINDING_H__ diff --git a/libi2pd/BloomFilter.cpp b/libi2pd/BloomFilter.cpp index b92039df..de077e60 100644 --- a/libi2pd/BloomFilter.cpp +++ b/libi2pd/BloomFilter.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include "BloomFilter.h" #include "I2PEndian.h" #include diff --git a/libi2pd/BloomFilter.h b/libi2pd/BloomFilter.h index 2745fbf5..ade854e4 100644 --- a/libi2pd/BloomFilter.h +++ b/libi2pd/BloomFilter.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef BLOOM_FILTER_H_ #define BLOOM_FILTER_H_ #include diff --git a/libi2pd/CPU.cpp b/libi2pd/CPU.cpp index a707c3dc..e7eff473 100644 --- a/libi2pd/CPU.cpp +++ b/libi2pd/CPU.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include "CPU.h" #if defined(__x86_64__) || defined(__i386__) #include diff --git a/libi2pd/CPU.h b/libi2pd/CPU.h index e1fd450c..9677b293 100644 --- a/libi2pd/CPU.h +++ b/libi2pd/CPU.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef LIBI2PD_CPU_H #define LIBI2PD_CPU_H diff --git a/libi2pd/ChaCha20.cpp b/libi2pd/ChaCha20.cpp index 1d1e9bc6..66bc135f 100644 --- a/libi2pd/ChaCha20.cpp +++ b/libi2pd/ChaCha20.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2018, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd/ChaCha20.h b/libi2pd/ChaCha20.h index a8bb1d56..4364024b 100644 --- a/libi2pd/ChaCha20.h +++ b/libi2pd/ChaCha20.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2018, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 59b51c00..19d72104 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2017, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd/Config.h b/libi2pd/Config.h index 679795b1..dac5fc80 100644 --- a/libi2pd/Config.h +++ b/libi2pd/Config.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef CONFIG_H #define CONFIG_H diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index d4d3519e..6db124c0 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 2dfd6d0e..56c8c10b 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef CRYPTO_H__ #define CRYPTO_H__ diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp index 2abcb111..d786e193 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "Log.h" #include "Gost.h" diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h index 43bd3aaa..1c8b0a71 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef CRYPTO_KEY_H__ #define CRYPTO_KEY_H__ diff --git a/libi2pd/CryptoWorker.h b/libi2pd/CryptoWorker.h index baeb7f14..27b012e7 100644 --- a/libi2pd/CryptoWorker.h +++ b/libi2pd/CryptoWorker.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef CRYPTO_WORKER_H_ #define CRYPTO_WORKER_H_ diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 2da73649..85b435d1 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "Crypto.h" #include "Log.h" diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index 72e7340c..e81f738a 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef DATAGRAM_H__ #define DATAGRAM_H__ diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 921e55f0..cd447773 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index dbdc3704..139423c6 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef DESTINATION_H__ #define DESTINATION_H__ diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index f3b1a685..61e67339 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include "Log.h" diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index d5a83f89..dd958060 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef ECIES_X25519_AEAD_RATCHET_SESSION_H__ #define ECIES_X25519_AEAD_RATCHET_SESSION_H__ diff --git a/libi2pd/Ed25519.cpp b/libi2pd/Ed25519.cpp index 8de5e3a1..791bd685 100644 --- a/libi2pd/Ed25519.cpp +++ b/libi2pd/Ed25519.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "Log.h" #include "Crypto.h" diff --git a/libi2pd/Ed25519.h b/libi2pd/Ed25519.h index 10061ad0..28d4e930 100644 --- a/libi2pd/Ed25519.h +++ b/libi2pd/Ed25519.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef ED25519_H__ #define ED25519_H__ diff --git a/libi2pd/Elligator.cpp b/libi2pd/Elligator.cpp index 8fefb2ae..712e514a 100644 --- a/libi2pd/Elligator.cpp +++ b/libi2pd/Elligator.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "Crypto.h" #include "Elligator.h" diff --git a/libi2pd/Elligator.h b/libi2pd/Elligator.h index 56fbc2eb..eacb03cd 100644 --- a/libi2pd/Elligator.h +++ b/libi2pd/Elligator.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef ELLIGATOR_H__ #define ELLIGATOR_H__ diff --git a/libi2pd/FS.cpp b/libi2pd/FS.cpp index f8b3ed7e..32fc3ec0 100644 --- a/libi2pd/FS.cpp +++ b/libi2pd/FS.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2016, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd/FS.h b/libi2pd/FS.h index 054ac2a3..698e9b6b 100644 --- a/libi2pd/FS.h +++ b/libi2pd/FS.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2016, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd/Family.cpp b/libi2pd/Family.cpp index e5ac9990..fbb7b9ee 100644 --- a/libi2pd/Family.cpp +++ b/libi2pd/Family.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd/Family.h b/libi2pd/Family.h index a1b5a789..2a9149ba 100644 --- a/libi2pd/Family.h +++ b/libi2pd/Family.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef FAMILY_H__ #define FAMILY_H__ diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 5d07155e..7e717829 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "I2PEndian.h" #include diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 55ebe795..6b34231a 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef GARLIC_H__ #define GARLIC_H__ diff --git a/libi2pd/Gost.cpp b/libi2pd/Gost.cpp index c401f8be..5e84a95d 100644 --- a/libi2pd/Gost.cpp +++ b/libi2pd/Gost.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd/Gost.h b/libi2pd/Gost.h index a4cc9741..0a79c30b 100644 --- a/libi2pd/Gost.h +++ b/libi2pd/Gost.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef GOST_H__ #define GOST_H__ diff --git a/libi2pd/Gzip.cpp b/libi2pd/Gzip.cpp index 5709719b..07c6a96e 100644 --- a/libi2pd/Gzip.cpp +++ b/libi2pd/Gzip.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2017, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd/Gzip.h b/libi2pd/Gzip.h index 6489b79b..d0bb28ed 100644 --- a/libi2pd/Gzip.h +++ b/libi2pd/Gzip.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef GZIP_H__ #define GZIP_H__ diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index 64acb6ea..4f7b03a1 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2019, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd/HTTP.h b/libi2pd/HTTP.h index f156fef0..c0cf1285 100644 --- a/libi2pd/HTTP.h +++ b/libi2pd/HTTP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2019, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 170bb864..a03ad7ed 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include "Base.h" diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 4ecba2eb..695de798 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef I2NP_PROTOCOL_H__ #define I2NP_PROTOCOL_H__ diff --git a/libi2pd/I2PEndian.cpp b/libi2pd/I2PEndian.cpp index b8a041d8..32ca4e26 100644 --- a/libi2pd/I2PEndian.cpp +++ b/libi2pd/I2PEndian.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include "I2PEndian.h" // http://habrahabr.ru/post/121811/ diff --git a/libi2pd/I2PEndian.h b/libi2pd/I2PEndian.h index 04a9480e..9ffc28d0 100644 --- a/libi2pd/I2PEndian.h +++ b/libi2pd/I2PEndian.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef I2PENDIAN_H__ #define I2PENDIAN_H__ #include diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 77a1f7e2..b2b5f2b4 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include "Crypto.h" #include "I2PEndian.h" #include "Log.h" diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index 08baba01..534b8f4c 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef IDENTITY_H__ #define IDENTITY_H__ diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index 1aa7fa6d..6b8b7545 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "I2PEndian.h" #include "Crypto.h" diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index 479594e1..cd31bf30 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef LEASE_SET_H__ #define LEASE_SET_H__ diff --git a/libi2pd/LittleBigEndian.h b/libi2pd/LittleBigEndian.h index 55039fb6..8c081187 100644 --- a/libi2pd/LittleBigEndian.h +++ b/libi2pd/LittleBigEndian.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + // LittleBigEndian.h fixed for 64-bits added union // diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index 6f2eb5bd..a0014841 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2016, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd/Log.h b/libi2pd/Log.h index fbd122a8..972a00e1 100644 --- a/libi2pd/Log.h +++ b/libi2pd/Log.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2016, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd/NTCPSession.cpp b/libi2pd/NTCPSession.cpp index 56c69e05..4d3f1da6 100644 --- a/libi2pd/NTCPSession.cpp +++ b/libi2pd/NTCPSession.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd/NTCPSession.h b/libi2pd/NTCPSession.h index e2207eb7..d3aa6f7c 100644 --- a/libi2pd/NTCPSession.h +++ b/libi2pd/NTCPSession.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef NTCP_SESSION_H__ #define NTCP_SESSION_H__ diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 30916f1a..466016c5 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index 47a6fab5..1c65969a 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef NETDB_H__ #define NETDB_H__ // this file is called NetDb.hpp to resolve conflict with libc's netdb.h on case insensitive fs diff --git a/libi2pd/NetDbRequests.cpp b/libi2pd/NetDbRequests.cpp index b1eb9380..e7aab34c 100644 --- a/libi2pd/NetDbRequests.cpp +++ b/libi2pd/NetDbRequests.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include "Log.h" #include "I2NPProtocol.h" #include "Transports.h" diff --git a/libi2pd/NetDbRequests.h b/libi2pd/NetDbRequests.h index c5e077bf..16ea430d 100644 --- a/libi2pd/NetDbRequests.h +++ b/libi2pd/NetDbRequests.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef NETDB_REQUESTS_H__ #define NETDB_REQUESTS_H__ diff --git a/libi2pd/Profiling.cpp b/libi2pd/Profiling.cpp index 3840eb32..850774d9 100644 --- a/libi2pd/Profiling.cpp +++ b/libi2pd/Profiling.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd/Profiling.h b/libi2pd/Profiling.h index 4ba6702f..dab50e6b 100644 --- a/libi2pd/Profiling.h +++ b/libi2pd/Profiling.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef PROFILING_H__ #define PROFILING_H__ diff --git a/libi2pd/Queue.h b/libi2pd/Queue.h index 0438cc37..d43567a5 100644 --- a/libi2pd/Queue.h +++ b/libi2pd/Queue.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef QUEUE_H__ #define QUEUE_H__ diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 4f26e520..2812f413 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd/Reseed.h b/libi2pd/Reseed.h index 5f402988..345b45bf 100644 --- a/libi2pd/Reseed.h +++ b/libi2pd/Reseed.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef RESEED_H #define RESEED_H diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index b855fc29..cbbd961e 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include "Config.h" diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 6382189b..6f08a87a 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef ROUTER_CONTEXT_H__ #define ROUTER_CONTEXT_H__ diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index bf81a8af..91c12b60 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include "I2PEndian.h" diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 5bdb0f8f..ef902c0e 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef ROUTER_INFO_H__ #define ROUTER_INFO_H__ diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index 4a33ff3a..c435715f 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include "Log.h" diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h index 942d0e64..6a79f754 100644 --- a/libi2pd/SSU.h +++ b/libi2pd/SSU.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef SSU_H__ #define SSU_H__ diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 4eb90522..5068f006 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include "Log.h" diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index 98d60c41..f4a5ba4f 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef SSU_DATA_H__ #define SSU_DATA_H__ diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index e29af479..73699d6a 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "version.h" #include "Crypto.h" diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h index 4f73158a..066e01eb 100644 --- a/libi2pd/SSUSession.h +++ b/libi2pd/SSUSession.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef SSU_SESSION_H__ #define SSU_SESSION_H__ diff --git a/libi2pd/Signature.cpp b/libi2pd/Signature.cpp index 64aff5bc..88ee4060 100644 --- a/libi2pd/Signature.cpp +++ b/libi2pd/Signature.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "Log.h" #include "Signature.h" diff --git a/libi2pd/Signature.h b/libi2pd/Signature.h index 26184639..18084603 100644 --- a/libi2pd/Signature.h +++ b/libi2pd/Signature.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef SIGNATURE_H__ #define SIGNATURE_H__ diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index ac527cb4..ff8915c0 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include "Crypto.h" #include "Log.h" #include "RouterInfo.h" diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index d3d47794..a56b0565 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef STREAMING_H__ #define STREAMING_H__ diff --git a/libi2pd/Tag.h b/libi2pd/Tag.h index eefab6de..3856abd9 100644 --- a/libi2pd/Tag.h +++ b/libi2pd/Tag.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef TAG_H__ #define TAG_H__ diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index 9e435e21..4362a878 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd/Timestamp.h b/libi2pd/Timestamp.h index 7d183cd3..91175a49 100644 --- a/libi2pd/Timestamp.h +++ b/libi2pd/Timestamp.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef TIMESTAMP_H__ #define TIMESTAMP_H__ diff --git a/libi2pd/TransitTunnel.cpp b/libi2pd/TransitTunnel.cpp index 02aac23f..73ca977c 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "I2PEndian.h" #include "Log.h" diff --git a/libi2pd/TransitTunnel.h b/libi2pd/TransitTunnel.h index 5b891dc3..e71ec750 100644 --- a/libi2pd/TransitTunnel.h +++ b/libi2pd/TransitTunnel.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef TRANSIT_TUNNEL_H__ #define TRANSIT_TUNNEL_H__ diff --git a/libi2pd/TransportSession.h b/libi2pd/TransportSession.h index 69b772cb..a97f246f 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef TRANSPORT_SESSION_H__ #define TRANSPORT_SESSION_H__ diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 15820584..2f5e92f5 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include "Log.h" #include "Crypto.h" #include "RouterContext.h" diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index 37cfa269..cdfbfccf 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef TRANSPORTS_H__ #define TRANSPORTS_H__ diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 879ee2d3..fe7e36af 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "I2PEndian.h" #include diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index f97bcc63..1fb12af9 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef TUNNEL_H__ #define TUNNEL_H__ diff --git a/libi2pd/TunnelBase.h b/libi2pd/TunnelBase.h index 53782ae3..f98066d3 100644 --- a/libi2pd/TunnelBase.h +++ b/libi2pd/TunnelBase.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef TUNNEL_BASE_H__ #define TUNNEL_BASE_H__ diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 48e66f2e..0bd8a842 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef TUNNEL_CONFIG_H__ #define TUNNEL_CONFIG_H__ diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp index 6e453d91..eb70bdca 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include "I2PEndian.h" #include #include "Crypto.h" diff --git a/libi2pd/TunnelEndpoint.h b/libi2pd/TunnelEndpoint.h index c2ffe53d..43b836f1 100644 --- a/libi2pd/TunnelEndpoint.h +++ b/libi2pd/TunnelEndpoint.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef TUNNEL_ENDPOINT_H__ #define TUNNEL_ENDPOINT_H__ diff --git a/libi2pd/TunnelGateway.cpp b/libi2pd/TunnelGateway.cpp index 3f4069e1..8f01bc4e 100644 --- a/libi2pd/TunnelGateway.cpp +++ b/libi2pd/TunnelGateway.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "Crypto.h" #include "I2PEndian.h" diff --git a/libi2pd/TunnelGateway.h b/libi2pd/TunnelGateway.h index 7959b57b..01101a36 100644 --- a/libi2pd/TunnelGateway.h +++ b/libi2pd/TunnelGateway.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef TUNNEL_GATEWAY_H__ #define TUNNEL_GATEWAY_H__ diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 54459960..d0fd401f 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include "I2PEndian.h" diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index 149a5efa..04ff4ae7 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef TUNNEL_POOL__ #define TUNNEL_POOL__ diff --git a/libi2pd/api.cpp b/libi2pd/api.cpp index 3bac4878..569fbd8c 100644 --- a/libi2pd/api.cpp +++ b/libi2pd/api.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include "Config.h" diff --git a/libi2pd/api.h b/libi2pd/api.h index 444667f0..9b0256d8 100644 --- a/libi2pd/api.h +++ b/libi2pd/api.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef API_H__ #define API_H__ diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 37898167..f5204a50 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd/util.h b/libi2pd/util.h index aa83ed7b..cb8fd8f1 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef UTIL_H #define UTIL_H diff --git a/libi2pd/version.h b/libi2pd/version.h index fa8f9a0f..d862f46e 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef _VERSION_H_ #define _VERSION_H_ diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index 8e14c526..8b8e781d 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd_client/AddressBook.h b/libi2pd_client/AddressBook.h index fb69dad3..04600792 100644 --- a/libi2pd_client/AddressBook.h +++ b/libi2pd_client/AddressBook.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef ADDRESS_BOOK_H__ #define ADDRESS_BOOK_H__ diff --git a/libi2pd_client/BOB.cpp b/libi2pd_client/BOB.cpp index e5d5404b..c4447808 100644 --- a/libi2pd_client/BOB.cpp +++ b/libi2pd_client/BOB.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "Log.h" #include "ClientContext.h" diff --git a/libi2pd_client/BOB.h b/libi2pd_client/BOB.h index a3d58148..74418011 100644 --- a/libi2pd_client/BOB.h +++ b/libi2pd_client/BOB.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef BOB_H__ #define BOB_H__ diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 03c756a1..6a58d63c 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 110f0970..8a4835c8 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef CLIENT_CONTEXT_H__ #define CLIENT_CONTEXT_H__ diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index 0c915921..64080752 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2019, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd_client/HTTPProxy.h b/libi2pd_client/HTTPProxy.h index 4504eedd..69ed4cef 100644 --- a/libi2pd_client/HTTPProxy.h +++ b/libi2pd_client/HTTPProxy.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef HTTP_PROXY_H__ #define HTTP_PROXY_H__ diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 2cea5098..fef5d6b6 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2019, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 08dbaa21..f4e83f06 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2019, The PurpleI2P Project +* Copyright (c) 2013-2020, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * diff --git a/libi2pd_client/I2PService.cpp b/libi2pd_client/I2PService.cpp index 7a30decd..83838106 100644 --- a/libi2pd_client/I2PService.cpp +++ b/libi2pd_client/I2PService.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include "Destination.h" #include "Identity.h" #include "ClientContext.h" diff --git a/libi2pd_client/I2PService.h b/libi2pd_client/I2PService.h index be935b93..e14f85c1 100644 --- a/libi2pd_client/I2PService.h +++ b/libi2pd_client/I2PService.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef I2PSERVICE_H__ #define I2PSERVICE_H__ diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index b7a15c26..5b384bff 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include "Base.h" #include "Log.h" diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index 94f63e48..d4db80d8 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef I2PTUNNEL_H__ #define I2PTUNNEL_H__ diff --git a/libi2pd_client/MatchedDestination.cpp b/libi2pd_client/MatchedDestination.cpp index a4c82504..4ffa7442 100644 --- a/libi2pd_client/MatchedDestination.cpp +++ b/libi2pd_client/MatchedDestination.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include "MatchedDestination.h" #include "Log.h" #include "ClientContext.h" diff --git a/libi2pd_client/MatchedDestination.h b/libi2pd_client/MatchedDestination.h index 53cae203..30ad8942 100644 --- a/libi2pd_client/MatchedDestination.h +++ b/libi2pd_client/MatchedDestination.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef MATCHED_DESTINATION_H_ #define MATCHED_DESTINATION_H_ #include "Destination.h" diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 086cbe69..2c6b711d 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #ifdef _MSC_VER diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index 51a8bc57..ceda5253 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef SAM_H__ #define SAM_H__ diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index 272ac178..52d7799b 100644 --- a/libi2pd_client/SOCKS.cpp +++ b/libi2pd_client/SOCKS.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include diff --git a/libi2pd_client/SOCKS.h b/libi2pd_client/SOCKS.h index fc43415a..f41cfd72 100644 --- a/libi2pd_client/SOCKS.h +++ b/libi2pd_client/SOCKS.h @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #ifndef SOCKS_H__ #define SOCKS_H__ From ead89c767ac6641c55ed70d1488ec8ee3461dea6 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 22 May 2020 18:32:44 -0400 Subject: [PATCH 3710/6300] compress longer RouterInfo --- libi2pd/I2NPProtocol.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index a03ad7ed..53afbca4 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -257,7 +257,14 @@ namespace i2p uint8_t * sizePtr = buf; buf += 2; m->len += (buf - payload); // payload size - size_t size = i2p::data::GzipNoCompression (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); + size_t size = 0; + if (router->GetBufferLen () + (buf - payload) <= 940) // fits one tunnel message + size = i2p::data::GzipNoCompression (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); + else + { + i2p::data::GzipDeflator deflator; + size = deflator.Deflate (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); + } if (size) { htobe16buf (sizePtr, size); // size From 86e8614934d0111756cc521cbaf06832b38e48fe Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 23 May 2020 10:20:22 -0400 Subject: [PATCH 3711/6300] allow session restart after 2 minutes from creation --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 4 +++- libi2pd/ECIESX25519AEADRatchetSession.h | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 61e67339..2f43b554 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -467,7 +467,8 @@ namespace garlic return false; } m_State = eSessionStateNewSessionReplySent; - + m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); + return true; } @@ -559,6 +560,7 @@ namespace garlic if (m_State == eSessionStateNewSessionSent) { m_State = eSessionStateEstablished; + m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); } memcpy (m_H, h, 32); // restore m_H diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index dd958060..27ab9c71 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -131,7 +131,7 @@ namespace garlic } bool CheckExpired (uint64_t ts); // true is expired - bool CanBeRestarted (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_RESTART_TIMEOUT; } + bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; } bool IsRatchets () const { return true; }; @@ -168,7 +168,7 @@ namespace garlic uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only i2p::crypto::X25519Keys m_EphemeralKeys; SessionState m_State = eSessionStateNew; - uint64_t m_LastActivityTimestamp = 0; // incoming + uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0; // incoming std::shared_ptr m_SendTagset, m_NSRTagset; std::unique_ptr m_Destination;// TODO: might not need it std::list > m_AckRequests; // (tagsetid, index) From 45aa78d953dc0a42e8f63e52914dd6953862a86a Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 23 May 2020 20:40:27 +0300 Subject: [PATCH 3712/6300] revert 7133a07 - it broke usage in some SOCKS implementations Signed-off-by: R4SAS --- libi2pd_client/SOCKS.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index 52d7799b..6edd5677 100644 --- a/libi2pd_client/SOCKS.cpp +++ b/libi2pd_client/SOCKS.cpp @@ -256,9 +256,6 @@ namespace proxy size += (1 + addr.dns.size); /* name length + domain name */ m_response[4] = addr.dns.size; memcpy(m_response + 5, addr.dns.value, addr.dns.size); - // replace type to IPv4 for support socks5 clients - // without domain name resolving support (like netcat) - m_response[3] = ADDR_IPV4; break; } htobe16buf(m_response + size - 2, port); //Port From 5a32082624a2956be3ef36c1ef58ea96513e585b Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 23 May 2020 15:58:11 -0400 Subject: [PATCH 3713/6300] recreate session after 90 seconds incativity --- libi2pd/Datagram.cpp | 3 ++- libi2pd/ECIESX25519AEADRatchetSession.h | 6 ++++-- libi2pd/Garlic.cpp | 10 ++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 85b435d1..04792bc5 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -256,7 +256,8 @@ namespace datagram std::shared_ptr DatagramSession::GetSharedRoutingPath () { - if(!m_RoutingSession) { + if (!m_RoutingSession || !m_RoutingSession->GetOwner ()) + { if(!m_RemoteLeaseSet) { m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 27ab9c71..108788d5 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -25,8 +25,9 @@ namespace i2p { namespace garlic { - const int ECIESX25519_RESTART_TIMEOUT = 120; // number of second of inactivity we should restart after + const int ECIESX25519_RESTART_TIMEOUT = 120; // number of second since session creation we can restart session after const int ECIESX25519_EXPIRATION_TIMEOUT = 480; // in seconds + const int ECIESX25519_INACTIVITY_TIMEOUT = 90; // number of second we receive nothing and should restart if we can const int ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT = 600; // in seconds const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180 const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 4096; // number of tags we request new tagset after @@ -132,7 +133,8 @@ namespace garlic bool CheckExpired (uint64_t ts); // true is expired bool CanBeRestarted (uint64_t ts) const { return ts > m_SessionCreatedTimestamp + ECIESX25519_RESTART_TIMEOUT; } - + bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); } + bool IsRatchets () const { return true; }; private: diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 7e717829..1170634d 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -719,7 +719,14 @@ namespace garlic destination->Encrypt (nullptr, staticKey, nullptr); // we are supposed to get static key auto it = m_ECIESx25519Sessions.find (staticKey); if (it != m_ECIESx25519Sessions.end ()) + { session = it->second; + if (session->IsInactive (i2p::util::GetSecondsSinceEpoch ())) + { + LogPrint (eLogDebug, "Garlic: session restarted"); + session = nullptr; + } + } if (!session) { session = std::make_shared (this, true); @@ -1011,7 +1018,10 @@ namespace garlic if (it != m_ECIESx25519Sessions.end ()) { if (it->second->CanBeRestarted (i2p::util::GetSecondsSinceEpoch ())) + { + it->second->SetOwner (nullptr); // detach m_ECIESx25519Sessions.erase (it); + } else { LogPrint (eLogInfo, "Garlic: ECIESx25519 session with static key ", staticKeyTag.ToBase64 (), " already exists"); From 71564f0d10132c49f29e29b17a98cd83728465f3 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 24 May 2020 10:30:00 -0400 Subject: [PATCH 3714/6300] set default i2cp.leaseSetEncType=0,4 for http and socks proxy for android --- android/assets/i2pd.conf | 2 ++ libi2pd/Config.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/android/assets/i2pd.conf b/android/assets/i2pd.conf index 312a24ea..14254248 100644 --- a/android/assets/i2pd.conf +++ b/android/assets/i2pd.conf @@ -42,6 +42,8 @@ inbound.quantity = 5 outbound.length = 1 outbound.quantity = 5 signaturetype=7 +i2cp.leaseSetType=3 +i2cp.leaseSetEncType=0,4 keys = proxy-keys.dat # addresshelper = true # outproxy = http://false.i2p diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 19d72104..0358d3e9 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -107,6 +107,8 @@ namespace config { ("httpproxy.latency.max", value()->default_value("0"), "HTTP proxy max latency for tunnels") ("httpproxy.outproxy", value()->default_value(""), "HTTP proxy upstream out proxy url") ("httpproxy.addresshelper", value()->default_value(true), "Enable or disable addresshelper") + ("httpproxy.i2cp.leaseSetType", value()->default_value("1"), "Local destination's LeaseSet type") + ("httpproxy.i2cp.leaseSetEncType", value()->default_value("0"), "Local destination's LeaseSet encryption type") ; options_description socksproxy("SOCKS Proxy options"); @@ -126,6 +128,8 @@ namespace config { ("socksproxy.outproxy.enabled", value()->default_value(false), "Enable or disable SOCKS outproxy") ("socksproxy.outproxy", value()->default_value("127.0.0.1"), "Upstream outproxy address for SOCKS Proxy") ("socksproxy.outproxyport", value()->default_value(9050), "Upstream outproxy port for SOCKS Proxy") + ("socksproxy.i2cp.leaseSetType", value()->default_value("1"), "Local destination's LeaseSet type") + ("socksproxy.i2cp.leaseSetEncType", value()->default_value("0"), "Local destination's LeaseSet encryption type") ; options_description sam("SAM bridge options"); From 1975adc48f8ddd6ce618ea1150078c6f0e0ea385 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 24 May 2020 14:14:16 -0400 Subject: [PATCH 3715/6300] print remote peer for queues --- libi2pd/NTCP2.cpp | 3 ++- libi2pd/Transports.cpp | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index eec3a5a5..8b44a795 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1135,7 +1135,8 @@ namespace transport SendQueue (); else if (m_SendQueue.size () > NTCP2_MAX_OUTGOING_QUEUE_SIZE) { - LogPrint (eLogWarning, "NTCP2: outgoing messages queue size exceeds ", NTCP2_MAX_OUTGOING_QUEUE_SIZE); + LogPrint (eLogWarning, "NTCP2: outgoing messages queue size to ", + GetIdentHashBase64(), " exceeds ", NTCP2_MAX_OUTGOING_QUEUE_SIZE); Terminate (); } } diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 2f5e92f5..7157306d 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -389,7 +389,7 @@ namespace transport m_LoopbackHandler.Flush (); return; } - if(RoutesRestricted() && ! IsRestrictedPeer(ident)) return; + if(RoutesRestricted() && !IsRestrictedPeer(ident)) return; auto it = m_Peers.find (ident); if (it == m_Peers.end ()) { @@ -421,7 +421,8 @@ namespace transport } else { - LogPrint (eLogWarning, "Transports: delayed messages queue size exceeds ", MAX_NUM_DELAYED_MESSAGES); + LogPrint (eLogWarning, "Transports: delayed messages queue size to ", + ident.ToBase64 (), " exceeds ", MAX_NUM_DELAYED_MESSAGES); std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } From 50c8a84037d762551e200921f41e46ccd3250357 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 25 May 2020 03:53:54 +0300 Subject: [PATCH 3716/6300] [SOCKS] overwrite connection info after establishing connection to i2p host (closes #1336) Signed-off-by: R4SAS --- libi2pd_client/SOCKS.cpp | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/libi2pd_client/SOCKS.cpp b/libi2pd_client/SOCKS.cpp index 6edd5677..c5428c86 100644 --- a/libi2pd_client/SOCKS.cpp +++ b/libi2pd_client/SOCKS.cpp @@ -227,38 +227,50 @@ namespace proxy boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port) { assert(error >= SOCKS4_OK); - m_response[0] = '\x00'; //Version - m_response[1] = error; //Response code - htobe16buf(m_response + 2, port); //Port - htobe32buf(m_response + 4, ip); //IP + m_response[0] = '\x00'; // version + m_response[1] = error; // response code + htobe16buf(m_response + 2, port); // port + htobe32buf(m_response + 4, ip); // IP return boost::asio::const_buffers_1(m_response,8); } boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port) { - size_t size = 6; // header + port + size_t size = 6; // header + port assert(error <= SOCKS5_ADDR_UNSUP); - m_response[0] = '\x05'; //Version - m_response[1] = error; //Response code - m_response[2] = '\x00'; //RSV - m_response[3] = type; //Address type + m_response[0] = '\x05'; // version + m_response[1] = error; // response code + m_response[2] = '\x00'; // reserved + m_response[3] = type; // address type switch (type) { case ADDR_IPV4: size += 4; htobe32buf(m_response + 4, addr.ip); + htobe16buf(m_response + size - 2, port); break; case ADDR_IPV6: size += 16; memcpy(m_response + 4, addr.ipv6, 16); + htobe16buf(m_response + size - 2, port); break; case ADDR_DNS: - size += (1 + addr.dns.size); /* name length + domain name */ - m_response[4] = addr.dns.size; - memcpy(m_response + 5, addr.dns.value, addr.dns.size); + std::string address(addr.dns.value, addr.dns.size); + if(address.substr(addr.dns.size - 4, 4) == ".i2p") // overwrite if requested address inside I2P + { + m_response[3] = ADDR_IPV4; + size += 4; + memset(m_response + 4, 0, 6); // six HEX zeros + } + else + { + size += (1 + addr.dns.size); /* name length + resolved address */ + m_response[4] = addr.dns.size; + memcpy(m_response + 5, addr.dns.value, addr.dns.size); + htobe16buf(m_response + size - 2, port); + } break; } - htobe16buf(m_response + size - 2, port); //Port return boost::asio::const_buffers_1(m_response, size); } From 0e0169d22be7227e36875038a51a1f69104726f5 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 25 May 2020 08:37:47 -0400 Subject: [PATCH 3717/6300] 2.32.0 --- ChangeLog | 24 +++++++++++++++++++ Win32/installer.iss | 2 +- android/build.gradle | 4 ++-- appveyor.yml | 2 +- libi2pd/version.h | 4 ++-- qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml | 1 + 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index 930be261..69fa58b8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,30 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.32.0] - 2020-05-25 +### Added +- Multiple encryption types for local destinations +- Next key and tagset for ECIES-X25519-AEAD-Ratchet +- NTCP2 through SOCKS proxy +- Throw error message if any port to bind is occupied +- gzip parameter for UDP tunnels +- Show ECIES-X25519-AEAD-Ratchet sessions and tags on the web console +- Simplified implementation of gzip for no compression mode +- Allow ECIES-X25519-AEAD-Ratchet session restart after 2 minutes +### Changed +- Select peers for client tunnels among routers >= 0.9.36 +- Check ECIES flag for encrypted lookup reply +- Streaming MTU size 1812 for ECIES-X25519-AEAD-Ratchet +- Don't calculate checksum for Data message send through ECIES-X25519-AEAD-Ratchet +- Catch network connectivity status for Windows +- Stop as soon as no more transit tunnels during graceful shutdown for Android +- RouterInfo gzip compression level depends on size +- Send response to received datagram from ECIES-X25519-AEAD-Ratchet session +- Reseeds list +### Fixed +- Correct timestamp check for LeaseSet2 +- Encrypted leaseset without authentication + ## [2.31.0] - 2020-04-10 ### Added - NTCP2 through HTTP proxy diff --git a/Win32/installer.iss b/Win32/installer.iss index 8e66c17a..88856558 100644 --- a/Win32/installer.iss +++ b/Win32/installer.iss @@ -1,5 +1,5 @@ #define I2Pd_AppName "i2pd" -#define I2Pd_ver "2.31.0" +#define I2Pd_ver "2.32.0" #define I2Pd_Publisher "PurpleI2P" [Setup] diff --git a/android/build.gradle b/android/build.gradle index d3c5c16b..ef6bf36e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -30,8 +30,8 @@ android { applicationId "org.purplei2p.i2pd" targetSdkVersion 29 minSdkVersion 14 - versionCode 2310 - versionName "2.31.0" + versionCode 2320 + versionName "2.32.0" setProperty("archivesBaseName", archivesBaseName + "-" + versionName) ndk { abiFilters 'armeabi-v7a' diff --git a/appveyor.yml b/appveyor.yml index 45a16dd8..a5ad1bfd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.31.0.{build} +version: 2.32.0.{build} pull_requests: do_not_increment_build_number: true branches: diff --git a/libi2pd/version.h b/libi2pd/version.h index d862f46e..346cf8a7 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -16,7 +16,7 @@ #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 31 +#define I2PD_VERSION_MINOR 32 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) @@ -30,7 +30,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 45 +#define I2P_VERSION_MICRO 46 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml index 4c027bc0..da9627cc 100644 --- a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml +++ b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml @@ -35,6 +35,7 @@ + From 1dcb8787963830396652c9cc56be93496d667357 Mon Sep 17 00:00:00 2001 From: r4sas Date: Mon, 25 May 2020 12:43:16 +0000 Subject: [PATCH 3718/6300] update debian and rpm stuff to 2.32.0 Signed-off-by: r4sas --- contrib/debian/i2pd.tmpfile | 2 +- contrib/i2pd.logrotate | 14 +++++++------- contrib/rpm/i2pd-git.spec | 4 ++++ contrib/rpm/i2pd.spec | 4 ++++ debian/changelog | 9 +++++++++ debian/copyright | 6 +++--- debian/i2pd.logrotate | 10 +--------- debian/postrm | 2 +- 8 files changed, 30 insertions(+), 21 deletions(-) mode change 100644 => 120000 debian/i2pd.logrotate diff --git a/contrib/debian/i2pd.tmpfile b/contrib/debian/i2pd.tmpfile index 6cd19112..e9bfebc6 100644 --- a/contrib/debian/i2pd.tmpfile +++ b/contrib/debian/i2pd.tmpfile @@ -1,2 +1,2 @@ -d /var/run/i2pd 0755 i2pd i2pd - - +d /run/i2pd 0755 i2pd i2pd - - d /var/log/i2pd 0755 i2pd i2pd - - diff --git a/contrib/i2pd.logrotate b/contrib/i2pd.logrotate index 795280d4..d0ca70ad 100644 --- a/contrib/i2pd.logrotate +++ b/contrib/i2pd.logrotate @@ -1,9 +1,9 @@ "/var/log/i2pd/*.log" { - copytruncate - daily - rotate 5 - compress - delaycompress - missingok - notifempty + copytruncate + daily + rotate 5 + compress + delaycompress + missingok + notifempty } diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 6967f949..22bbb505 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -121,6 +121,10 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon May 25 2020 r4sas - 2.32.0 +- update to 2.32.0 +- updated systemd service file (#1394) + * Thu May 7 2020 Anatolii Vorona - 2.31.0-3 - added RPM logrotate config diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 49a5e666..646e4fb6 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -119,6 +119,10 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon May 25 2020 r4sas - 2.32.0 +- update to 2.32.0 +- updated systemd service file (#1394) + * Thu May 7 2020 Anatolii Vorona - 2.31.0-3 - added RPM logrotate config diff --git a/debian/changelog b/debian/changelog index 3824d4f8..43c3fc60 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +i2pd (2.32.0-1) unstable; urgency=high + + * updated to version 2.32.0/0.9.46 + * updated systemd service file (see #1394) + * updated apparmor profile (see 9318388007cff0495b4b360d0480f4fc1219a9dc) + * updated logrotate config and moved it to contrib + + -- r4sas Mon, 25 May 2020 12:45:00 +0000 + i2pd (2.31.0-1) unstable; urgency=medium * updated to version 2.31.0 diff --git a/debian/copyright b/debian/copyright index 3c513020..9f18f53a 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,7 +3,7 @@ Upstream-Name: i2pd Source: https://github.com/PurpleI2P Files: * -Copyright: 2013-2017 PurpleI2P +Copyright: 2013-2020 PurpleI2P License: BSD-3-clause Files: qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl @@ -16,8 +16,8 @@ License: BSD-2-Clause Files: debian/* Copyright: 2013-2015 Kill Your TV 2014-2016 hagen - 2016-2017 R4SAS - 2017-2018 Yangfl + 2016-2020 R4SAS + 2017-2020 Yangfl License: GPL-2+ License: BSD-3-clause diff --git a/debian/i2pd.logrotate b/debian/i2pd.logrotate deleted file mode 100644 index 63c44270..00000000 --- a/debian/i2pd.logrotate +++ /dev/null @@ -1,9 +0,0 @@ -/var/log/i2pd/i2pd.log { - rotate 6 - daily - missingok - notifempty - compress - delaycompress - copytruncate -} diff --git a/debian/i2pd.logrotate b/debian/i2pd.logrotate new file mode 120000 index 00000000..2630046a --- /dev/null +++ b/debian/i2pd.logrotate @@ -0,0 +1 @@ +/opt/build/i2pd/contrib/i2pd.logrotate \ No newline at end of file diff --git a/debian/postrm b/debian/postrm index 53ab15e7..ba69785e 100755 --- a/debian/postrm +++ b/debian/postrm @@ -6,7 +6,7 @@ if [ "$1" = "purge" ]; then rm -rf /etc/i2pd rm -rf /var/lib/i2pd rm -rf /var/log/i2pd - rm -rf /var/run/i2pd + rm -rf /run/i2pd fi #DEBHELPER# From 6bd44f0e4b6804a5feb817c7487d9bfa05ee0f72 Mon Sep 17 00:00:00 2001 From: r4sas Date: Mon, 25 May 2020 13:06:11 +0000 Subject: [PATCH 3719/6300] 2.32.0 Signed-off-by: r4sas --- ChangeLog | 17 +++++++++++------ qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index 69fa58b8..f080e1bc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,7 @@ - Show ECIES-X25519-AEAD-Ratchet sessions and tags on the web console - Simplified implementation of gzip for no compression mode - Allow ECIES-X25519-AEAD-Ratchet session restart after 2 minutes +- Added logrotate config for rpm package ### Changed - Select peers for client tunnels among routers >= 0.9.36 - Check ECIES flag for encrypted lookup reply @@ -20,10 +21,14 @@ - Stop as soon as no more transit tunnels during graceful shutdown for Android - RouterInfo gzip compression level depends on size - Send response to received datagram from ECIES-X25519-AEAD-Ratchet session +- Update webconsole functional +- Increased max transit tunnels limit - Reseeds list +- Dropped windows support in cmake ### Fixed -- Correct timestamp check for LeaseSet2 -- Encrypted leaseset without authentication +- Correct timestamp check for LeaseSet2 +- Encrypted leaseset without authentication +- Change SOCKS proxy connection response for clients without socks5h support (#1336) ## [2.31.0] - 2020-04-10 ### Added @@ -66,7 +71,7 @@ ### Added - Client auth flag for b33 address ### Changed -- Remove incoming NTCP2 session from pending list when established +- Remove incoming NTCP2 session from pending list when established - Handle errors for NTCP2 SessionConfrimed send ### Fixed - Failure to start on Windows XP @@ -115,7 +120,7 @@ ## [2.25.0] - 2019-05-09 ### Added - Create, publish and handle encrypted LeaseSet2 -- Support of b33 addresses +- Support of b33 addresses - RedDSA key blinding - .b32.i2p addresses in jump links - ntcp2.addressv6 parameter @@ -147,7 +152,7 @@ - Correct SAM response for invalid key - SAM crash on termination for Windows - Race condition for publishing - + ## [2.23.0] - 2019-01-21 ### Added - Standard LeaseSet2 support @@ -206,7 +211,7 @@ - NTCP2 is enabled by default - Show lease's expiration time in readable format in the web console ### Fixed -- Correct names for transports in the web console +- Correct names for transports in the web console ## [2.19.0] - 2018-06-26 ### Added diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml index da9627cc..452d750f 100644 --- a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml +++ b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml @@ -35,7 +35,7 @@ - + From 2facf144438baa3e8822ab43bcd054372a028b93 Mon Sep 17 00:00:00 2001 From: r4sas Date: Mon, 25 May 2020 13:09:02 +0000 Subject: [PATCH 3720/6300] fix symbolic link Signed-off-by: r4sas --- debian/i2pd.logrotate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/i2pd.logrotate b/debian/i2pd.logrotate index 2630046a..40dea037 120000 --- a/debian/i2pd.logrotate +++ b/debian/i2pd.logrotate @@ -1 +1 @@ -/opt/build/i2pd/contrib/i2pd.logrotate \ No newline at end of file +../contrib/i2pd.logrotate \ No newline at end of file From d226834eef933b6b0b1ec051017463cb45bf62cd Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 25 May 2020 13:33:02 +0000 Subject: [PATCH 3721/6300] update debian patch Signed-off-by: R4SAS --- debian/patches/01-tune-build-opts.patch | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/debian/patches/01-tune-build-opts.patch b/debian/patches/01-tune-build-opts.patch index dd2b4638..e06a8621 100644 --- a/debian/patches/01-tune-build-opts.patch +++ b/debian/patches/01-tune-build-opts.patch @@ -1,17 +1,15 @@ -diff --git a/Makefile b/Makefile -index bdadfe0..2f71eec 100644 ---- a/Makefile -+++ b/Makefile -@@ -9,10 +9,10 @@ DEPS := obj/make.dep - +Index: i2pd/Makefile +=================================================================== +--- i2pd.orig/Makefile ++++ i2pd/Makefile +@@ -13,8 +13,8 @@ DAEMON_SRC_DIR := daemon + include filelist.mk - + -USE_AESNI := yes -+USE_AESNI := no -USE_AVX := yes ++USE_AESNI := no +USE_AVX := no USE_STATIC := no USE_MESHNET := no USE_UPNP := no - DEBUG := yes - From dba6d681083ebe271eb5b70bae0b206f9a218f28 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 25 May 2020 13:42:26 +0000 Subject: [PATCH 3722/6300] update debian patch Signed-off-by: R4SAS --- debian/patches/02-fix-1210.patch | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/debian/patches/02-fix-1210.patch b/debian/patches/02-fix-1210.patch index 6a4caf94..9ad9bb0f 100644 --- a/debian/patches/02-fix-1210.patch +++ b/debian/patches/02-fix-1210.patch @@ -4,10 +4,12 @@ Author: r4sas Bug: https://github.com/PurpleI2P/i2pd/issues/1210 Reviewed-By: r4sas -Last-Update: 2018-08-25 +Last-Update: 2020-05-25 ---- a/contrib/i2pd.service -+++ b/contrib/i2pd.service +Index: i2pd/contrib/i2pd.service +=================================================================== +--- i2pd.orig/contrib/i2pd.service ++++ i2pd/contrib/i2pd.service @@ -6,10 +6,10 @@ After=network.target [Service] User=i2pd @@ -21,5 +23,5 @@ Last-Update: 2018-08-25 +#LogsDirectory=i2pd +#LogsDirectoryMode=0700 Type=forking - ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/var/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service - ExecReload=/bin/kill -HUP $MAINPID + ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --tunconf=/etc/i2pd/tunnels.conf --tunnelsdir=/etc/i2pd/tunnels.conf.d --pidfile=/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service + ExecReload=/bin/sh -c "kill -HUP $MAINPID" From 8e0f1de25a8e4aade51b8acbec0cd72622c507f1 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 25 May 2020 20:28:52 +0300 Subject: [PATCH 3723/6300] 2.32.0 - [RPM] fix build in fedora copr Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 4 +++- contrib/rpm/i2pd.spec | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 22bbb505..6cb09f0d 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -1,7 +1,7 @@ %define git_hash %(git rev-parse HEAD | cut -c -7) Name: i2pd-git -Version: 2.31.0 +Version: 2.32.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -111,6 +111,8 @@ getent passwd i2pd >/dev/null || \ %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d %{_sbindir}/i2pd %config(noreplace) %{_sysconfdir}/i2pd/*.conf +%config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/* +%{_sysconfdir}/i2pd/subscriptions.txt %{_unitdir}/i2pd.service %{_mandir}/man1/i2pd.1* %dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 646e4fb6..62ef68e7 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,6 +1,6 @@ Name: i2pd -Version: 2.31.0 -Release: 3%{?dist} +Version: 2.32.0 +Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -111,6 +111,7 @@ getent passwd i2pd >/dev/null || \ %{_datadir}/i2pd/certificates %config(noreplace) %{_sysconfdir}/i2pd/*.conf %config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/* +%{_sysconfdir}/i2pd/subscriptions.txt /%{_unitdir}/i2pd.service %dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd %dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd From 60b1b2ca4af37fde2858d2c711269036f715a3da Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 25 May 2020 21:22:50 +0300 Subject: [PATCH 3724/6300] [RPM] update spec files Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 19 ++++++++++--------- contrib/rpm/i2pd.spec | 38 ++++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 6cb09f0d..1a557758 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -10,7 +10,7 @@ License: BSD URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/openssl/i2pd-openssl.tar.gz -%if 0%{?rhel} == 7 +%if 0%{?rhel} == 7 BuildRequires: cmake3 %else BuildRequires: cmake @@ -24,8 +24,8 @@ BuildRequires: openssl-devel BuildRequires: miniupnpc-devel BuildRequires: systemd-units -Requires: logrotate -Requires: systemd +Requires: logrotate +Requires: systemd Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd %description @@ -74,17 +74,18 @@ pushd build chrpath -d i2pd %{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd +%{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd +%{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd/tunnels.conf.d +%{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd +%{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/subscriptions.txt %{buildroot}%{_sysconfdir}/i2pd/subscriptions.txt %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf -%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.d/README %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf.d/README %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.service %{buildroot}%{_unitdir}/i2pd.service %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/debian/i2pd.1 %{buildroot}%{_mandir}/man1/i2pd.1 -%{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd -%{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd -%{__install} -d -m 755 %{buildroot}%{_datadir}/%{name} -%{__cp} -r %{_builddir}/%{name}-%{version}/contrib/certificates/ %{buildroot}%{_datadir}/%{name}/certificates +%{__cp} -r %{_builddir}/%{name}-%{version}/contrib/certificates/ %{buildroot}%{_datadir}/i2pd/certificates +%{__cp} -r %{_builddir}/%{name}-%{version}/contrib/tunnels.d/ %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf.d ln -s %{_datadir}/%{name}/certificates %{buildroot}%{_sharedstatedir}/i2pd/certificates @@ -117,7 +118,7 @@ getent passwd i2pd >/dev/null || \ %{_mandir}/man1/i2pd.1* %dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd %dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd -%{_datadir}/%{name}/certificates +%{_datadir}/i2pd/certificates %{_sharedstatedir}/i2pd/certificates %{_sysconfdir}/logrotate.d/i2pd diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 62ef68e7..b11f8b67 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,6 +1,6 @@ Name: i2pd Version: 2.32.0 -Release: 1%{?dist} +Release: 2%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -8,7 +8,7 @@ License: BSD URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz -%if 0%{?rhel} == 7 +%if 0%{?rhel} == 7 BuildRequires: cmake3 %else BuildRequires: cmake @@ -22,8 +22,8 @@ BuildRequires: openssl-devel BuildRequires: miniupnpc-devel BuildRequires: systemd-units -Requires: logrotate -Requires: systemd +Requires: logrotate +Requires: systemd Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd %description @@ -71,19 +71,20 @@ pushd build %endif chrpath -d i2pd -install -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd -install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf -install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf -install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd -install -d -m 755 %{buildroot}%{_datadir}/i2pd -install -d -m 755 %{buildroot}%{_datadir}/i2pd/tunnels.conf.d +%{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd +%{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd +%{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd/tunnels.conf.d +%{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd +%{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/subscriptions.txt %{buildroot}%{_sysconfdir}/i2pd/subscriptions.txt +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/tunnels.conf %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/i2pd +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.service %{buildroot}%{_unitdir}/i2pd.service +%{__install} -D -m 644 %{_builddir}/%{name}-%{version}/debian/i2pd.1 %{buildroot}%{_mandir}/man1/i2pd.1 %{__cp} -r %{_builddir}/%{name}-%{version}/contrib/certificates/ %{buildroot}%{_datadir}/i2pd/certificates %{__cp} -r %{_builddir}/%{name}-%{version}/contrib/tunnels.d/ %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf.d -install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.service %{buildroot}%{_unitdir}/i2pd.service -install -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd -install -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd ln -s %{_datadir}/%{name}/certificates %{buildroot}%{_sharedstatedir}/i2pd/certificates -ln -s %{_datadir}/i2pd/tunnels.conf.d %{buildroot}%{_sysconfdir}/i2pd/tunnels.conf.d %pre @@ -106,15 +107,16 @@ getent passwd i2pd >/dev/null || \ %files -%doc LICENSE README.md +%doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d %{_sbindir}/i2pd -%{_datadir}/i2pd/certificates %config(noreplace) %{_sysconfdir}/i2pd/*.conf %config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/* %{_sysconfdir}/i2pd/subscriptions.txt -/%{_unitdir}/i2pd.service -%dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd +%{_unitdir}/i2pd.service +%{_mandir}/man1/i2pd.1* %dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd +%dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd +%{_datadir}/i2pd/certificates %{_sharedstatedir}/i2pd/certificates %{_sysconfdir}/logrotate.d/i2pd From a4c4bf4b584cd01633b570336adda383ce052761 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 25 May 2020 22:30:18 +0300 Subject: [PATCH 3725/6300] [RPM] update spec files Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 46 +++++++++++++++++++-------------------- contrib/rpm/i2pd.spec | 46 +++++++++++++++++++-------------------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 1a557758..41a9dbe9 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -1,32 +1,32 @@ %define git_hash %(git rev-parse HEAD | cut -c -7) -Name: i2pd-git -Version: 2.32.0 -Release: git%{git_hash}%{?dist} -Summary: I2P router written in C++ -Conflicts: i2pd +Name: i2pd-git +Version: 2.32.0 +Release: git%{git_hash}%{?dist} +Summary: I2P router written in C++ +Conflicts: i2pd -License: BSD -URL: https://github.com/PurpleI2P/i2pd -Source0: https://github.com/PurpleI2P/i2pd/archive/openssl/i2pd-openssl.tar.gz +License: BSD +URL: https://github.com/PurpleI2P/i2pd +Source0: https://github.com/PurpleI2P/i2pd/archive/openssl/i2pd-openssl.tar.gz %if 0%{?rhel} == 7 -BuildRequires: cmake3 +BuildRequires: cmake3 %else -BuildRequires: cmake +BuildRequires: cmake %endif -BuildRequires: chrpath -BuildRequires: gcc-c++ -BuildRequires: zlib-devel -BuildRequires: boost-devel -BuildRequires: openssl-devel -BuildRequires: miniupnpc-devel -BuildRequires: systemd-units +BuildRequires: chrpath +BuildRequires: gcc-c++ +BuildRequires: zlib-devel +BuildRequires: boost-devel +BuildRequires: openssl-devel +BuildRequires: miniupnpc-devel +BuildRequires: systemd-units -Requires: logrotate -Requires: systemd -Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd +Requires: logrotate +Requires: systemd +Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd %description C++ implementation of I2P. @@ -112,15 +112,15 @@ getent passwd i2pd >/dev/null || \ %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d %{_sbindir}/i2pd %config(noreplace) %{_sysconfdir}/i2pd/*.conf -%config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/* -%{_sysconfdir}/i2pd/subscriptions.txt +%config %{_sysconfdir}/i2pd/subscriptions.txt +%doc %{_sysconfdir}/i2pd/tunnels.conf.d/README +%{_sysconfdir}/logrotate.d/i2pd %{_unitdir}/i2pd.service %{_mandir}/man1/i2pd.1* %dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd %dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd %{_datadir}/i2pd/certificates %{_sharedstatedir}/i2pd/certificates -%{_sysconfdir}/logrotate.d/i2pd %changelog diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index b11f8b67..cd9856cb 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,30 +1,30 @@ -Name: i2pd -Version: 2.32.0 -Release: 2%{?dist} -Summary: I2P router written in C++ -Conflicts: i2pd-git +Name: i2pd +Version: 2.32.0 +Release: 2%{?dist} +Summary: I2P router written in C++ +Conflicts: i2pd-git -License: BSD -URL: https://github.com/PurpleI2P/i2pd -Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz +License: BSD +URL: https://github.com/PurpleI2P/i2pd +Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz %if 0%{?rhel} == 7 -BuildRequires: cmake3 +BuildRequires: cmake3 %else -BuildRequires: cmake +BuildRequires: cmake %endif -BuildRequires: chrpath -BuildRequires: gcc-c++ -BuildRequires: zlib-devel -BuildRequires: boost-devel -BuildRequires: openssl-devel -BuildRequires: miniupnpc-devel -BuildRequires: systemd-units +BuildRequires: chrpath +BuildRequires: gcc-c++ +BuildRequires: zlib-devel +BuildRequires: boost-devel +BuildRequires: openssl-devel +BuildRequires: miniupnpc-devel +BuildRequires: systemd-units -Requires: logrotate -Requires: systemd -Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd +Requires: logrotate +Requires: systemd +Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd %description C++ implementation of I2P. @@ -110,15 +110,15 @@ getent passwd i2pd >/dev/null || \ %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d %{_sbindir}/i2pd %config(noreplace) %{_sysconfdir}/i2pd/*.conf -%config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/* -%{_sysconfdir}/i2pd/subscriptions.txt +%config %{_sysconfdir}/i2pd/subscriptions.txt +%doc %{_sysconfdir}/i2pd/tunnels.conf.d/README +%{_sysconfdir}/logrotate.d/i2pd %{_unitdir}/i2pd.service %{_mandir}/man1/i2pd.1* %dir %attr(0700,i2pd,i2pd) %{_sharedstatedir}/i2pd %dir %attr(0700,i2pd,i2pd) %{_localstatedir}/log/i2pd %{_datadir}/i2pd/certificates %{_sharedstatedir}/i2pd/certificates -%{_sysconfdir}/logrotate.d/i2pd %changelog From 64c986ebbb5d11f6143ade4fbb3277f18a13c574 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 25 May 2020 23:01:02 +0300 Subject: [PATCH 3726/6300] [RPM] update spec files Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 2 +- contrib/rpm/i2pd.spec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 41a9dbe9..3473a9d0 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -75,7 +75,6 @@ pushd build chrpath -d i2pd %{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd %{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd -%{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd/tunnels.conf.d %{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd %{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf @@ -112,6 +111,7 @@ getent passwd i2pd >/dev/null || \ %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d %{_sbindir}/i2pd %config(noreplace) %{_sysconfdir}/i2pd/*.conf +%config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/*.conf %config %{_sysconfdir}/i2pd/subscriptions.txt %doc %{_sysconfdir}/i2pd/tunnels.conf.d/README %{_sysconfdir}/logrotate.d/i2pd diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index cd9856cb..588a6839 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -73,7 +73,6 @@ pushd build chrpath -d i2pd %{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd %{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd -%{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd/tunnels.conf.d %{__install} -d -m 700 %{buildroot}%{_sharedstatedir}/i2pd %{__install} -d -m 700 %{buildroot}%{_localstatedir}/log/i2pd %{__install} -D -m 644 %{_builddir}/%{name}-%{version}/contrib/i2pd.conf %{buildroot}%{_sysconfdir}/i2pd/i2pd.conf @@ -110,6 +109,7 @@ getent passwd i2pd >/dev/null || \ %doc LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf contrib/tunnels.d %{_sbindir}/i2pd %config(noreplace) %{_sysconfdir}/i2pd/*.conf +%config(noreplace) %{_sysconfdir}/i2pd/tunnels.conf.d/*.conf %config %{_sysconfdir}/i2pd/subscriptions.txt %doc %{_sysconfdir}/i2pd/tunnels.conf.d/README %{_sysconfdir}/logrotate.d/i2pd From bdb918cdb38057a1a7ce991dbbf2e7fc7e75fbdc Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 25 May 2020 21:40:46 -0400 Subject: [PATCH 3727/6300] honour explicitPeer param in tunnels --- libi2pd_client/ClientContext.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 6a58d63c..54007c95 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -459,6 +459,8 @@ namespace client else if (authType == "2") // PSK ReadI2CPOptionsGroup (section, I2CP_PARAM_LEASESET_CLIENT_PSK, options); } + std::string explicitPeers = GetI2CPStringOption(section, I2CP_PARAM_EXPLICIT_PEERS, ""); + if (explicitPeers.length () > 0) options[I2CP_PARAM_EXPLICIT_PEERS] = explicitPeers; } void ClientContext::ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const From 37ec90c436f2e34705d39ffd944cfab729028e89 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 26 May 2020 16:47:45 -0400 Subject: [PATCH 3728/6300] don't gererate more tags for detached session --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 2f43b554..310d263e 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -612,7 +612,7 @@ namespace garlic int moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4 if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; moreTags -= (receiveTagset->GetNextIndex () - index); - if (moreTags > 0) + if (moreTags > 0 && GetOwner ()) GenerateMoreReceiveTags (receiveTagset, moreTags); return true; } From 45e8d5c50ecb9fdf8399d82c8f5a15d633741d77 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 27 May 2020 21:27:16 +0300 Subject: [PATCH 3729/6300] Return deprecated websocket config options for compatibility Closes #1523 Signed-off-by: R4SAS --- libi2pd/Config.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 0358d3e9..d11152dd 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -221,6 +221,14 @@ namespace config { ("trust.hidden", value()->default_value(false), "Should we hide our router from other routers?") ; + // Save deprecated websocket options for compatibility + options_description websocket("Websocket Options"); + websocket.add_options() + ("websockets.enabled", value()->default_value(false), "Deprecated option") + ("websockets.address", value()->default_value(""), "Deprecated option") + ("websockets.port", value()->default_value(0), "Deprecated option") + ; + options_description exploratory("Exploratory Options"); exploratory.add_options() ("exploratory.inbound.length", value()->default_value(2), "Exploratory inbound tunnel length") @@ -271,6 +279,7 @@ namespace config { .add(reseed) .add(addressbook) .add(trust) + .add(websocket) // deprecated .add(exploratory) .add(ntcp2) .add(nettime) From 0dc212d97c75ba1fa852af6889386f82f5ec289e Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 28 May 2020 13:46:02 -0400 Subject: [PATCH 3730/6300] fixed non-updating LeaseSet1 --- libi2pd/LeaseSet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index 6b8b7545..b90d6d85 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -187,7 +187,7 @@ namespace data bool LeaseSet::IsNewer (const uint8_t * buf, size_t len) const { - return ExtractExpirationTimestamp (buf, len) > (m_ExpirationTime ? m_ExpirationTime : ExtractExpirationTimestamp (m_Buffer, m_BufferLen)); + return ExtractExpirationTimestamp (buf, len) > ExtractExpirationTimestamp (m_Buffer, m_BufferLen); } bool LeaseSet::ExpiresSoon(const uint64_t dlt, const uint64_t fudge) const From 9135772f8959c0a965085657dd8300a928304c64 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 2 Jun 2020 19:26:36 +0300 Subject: [PATCH 3731/6300] 2.32.1 Signed-off-by: R4SAS --- ChangeLog | 8 ++++++++ Win32/installer.iss | 2 +- android/build.gradle | 4 ++-- appveyor.yml | 2 +- contrib/rpm/i2pd-git.spec | 5 ++++- contrib/rpm/i2pd.spec | 7 +++++-- debian/changelog | 6 ++++++ libi2pd/version.h | 2 +- qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml | 1 + 9 files changed, 29 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index f080e1bc..33792a97 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,14 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.32.1] - 2020-06-02 +### Added +- Read explicit peers in tunnels config +### Fixed +- Generation of tags for detached sessions +- Non-updating LeaseSet1 +- Start when deprecated websocket options present in i2pd.conf + ## [2.32.0] - 2020-05-25 ### Added - Multiple encryption types for local destinations diff --git a/Win32/installer.iss b/Win32/installer.iss index 88856558..40724d7a 100644 --- a/Win32/installer.iss +++ b/Win32/installer.iss @@ -1,5 +1,5 @@ #define I2Pd_AppName "i2pd" -#define I2Pd_ver "2.32.0" +#define I2Pd_ver "2.32.1" #define I2Pd_Publisher "PurpleI2P" [Setup] diff --git a/android/build.gradle b/android/build.gradle index ef6bf36e..c9e8429c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -30,8 +30,8 @@ android { applicationId "org.purplei2p.i2pd" targetSdkVersion 29 minSdkVersion 14 - versionCode 2320 - versionName "2.32.0" + versionCode 2321 + versionName "2.32.1" setProperty("archivesBaseName", archivesBaseName + "-" + versionName) ndk { abiFilters 'armeabi-v7a' diff --git a/appveyor.yml b/appveyor.yml index a5ad1bfd..0567127c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.32.0.{build} +version: 2.32.1.{build} pull_requests: do_not_increment_build_number: true branches: diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 3473a9d0..7079ec23 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -1,7 +1,7 @@ %define git_hash %(git rev-parse HEAD | cut -c -7) Name: i2pd-git -Version: 2.32.0 +Version: 2.32.1 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -124,6 +124,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Tue Jun 02 2020 r4sas - 2.32.1 +- update to 2.32.1 + * Mon May 25 2020 r4sas - 2.32.0 - update to 2.32.0 - updated systemd service file (#1394) diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 588a6839..5c0b8fbd 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,6 +1,6 @@ Name: i2pd -Version: 2.32.0 -Release: 2%{?dist} +Version: 2.32.1 +Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -122,6 +122,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Tue Jun 02 2020 r4sas - 2.32.1 +- update to 2.32.1 + * Mon May 25 2020 r4sas - 2.32.0 - update to 2.32.0 - updated systemd service file (#1394) diff --git a/debian/changelog b/debian/changelog index 43c3fc60..b01f9519 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.32.1-1) unstable; urgency=high + + * updated to version 2.32.1 + + -- r4sas Tue, 02 Jun 2020 16:30:00 +0000 + i2pd (2.32.0-1) unstable; urgency=high * updated to version 2.32.0/0.9.46 diff --git a/libi2pd/version.h b/libi2pd/version.h index 346cf8a7..c234516f 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -17,7 +17,7 @@ #define I2PD_VERSION_MAJOR 2 #define I2PD_VERSION_MINOR 32 -#define I2PD_VERSION_MICRO 0 +#define I2PD_VERSION_MICRO 1 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) #define VERSION I2PD_VERSION diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml index 452d750f..75c57fb7 100644 --- a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml +++ b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml @@ -35,6 +35,7 @@ + From e1356965308b3554d001003b9bce737603030ffa Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 3 Jun 2020 16:05:19 -0400 Subject: [PATCH 3732/6300] support multiple encryption keys through the I2CP --- libi2pd/Crypto.cpp | 9 ++++++++- libi2pd/Crypto.h | 2 +- libi2pd/CryptoKey.cpp | 2 +- libi2pd/CryptoKey.h | 3 ++- libi2pd_client/I2CP.cpp | 44 +++++++++++++++++++++++++++-------------- libi2pd_client/I2CP.h | 9 +++++---- 6 files changed, 46 insertions(+), 23 deletions(-) diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 6db124c0..2f10e91d 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -375,15 +375,22 @@ namespace crypto #endif } - void X25519Keys::SetPrivateKey (const uint8_t * priv) + void X25519Keys::SetPrivateKey (const uint8_t * priv, bool calculatePublic) { #if OPENSSL_X25519 if (m_Ctx) EVP_PKEY_CTX_free (m_Ctx); if (m_Pkey) EVP_PKEY_free (m_Pkey); m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_X25519, NULL, priv, 32); m_Ctx = EVP_PKEY_CTX_new (m_Pkey, NULL); + if (calculatePublic) + { + size_t len = 32; + EVP_PKEY_get_raw_public_key (m_Pkey, m_PublicKey, &len); + } #else memcpy (m_PrivateKey, priv, 32); + if (calculatePublic) + GetEd25519 ()->ScalarMulB (m_PrivateKey, m_PublicKey, m_Ctx); #endif } diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 56c8c10b..9ca3163f 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -88,7 +88,7 @@ namespace crypto void GenerateKeys (); const uint8_t * GetPublicKey () const { return m_PublicKey; }; void GetPrivateKey (uint8_t * priv) const; - void SetPrivateKey (const uint8_t * priv); // wihout calculating public + void SetPrivateKey (const uint8_t * priv, bool calculatePublic = false); void Agree (const uint8_t * pub, uint8_t * shared); private: diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp index d786e193..b8f66e4d 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -166,7 +166,7 @@ namespace crypto memcpy (pub, m_PublicKey, 32); } - ECIESX25519AEADRatchetDecryptor::ECIESX25519AEADRatchetDecryptor (const uint8_t * priv) + ECIESX25519AEADRatchetDecryptor::ECIESX25519AEADRatchetDecryptor (const uint8_t * priv, bool calculatePublic) { m_StaticKeys.SetPrivateKey (priv); } diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h index 1c8b0a71..fb69558f 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -145,11 +145,12 @@ namespace crypto { public: - ECIESX25519AEADRatchetDecryptor (const uint8_t * priv); + ECIESX25519AEADRatchetDecryptor (const uint8_t * priv, bool calculatePublic = false); ~ECIESX25519AEADRatchetDecryptor () {}; bool Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding); // agree with static and return in sharedSecret (32 bytes) size_t GetPublicKeyLen () const { return 32; }; + const uint8_t * GetPubicKey () const { return m_StaticKeys.GetPublicKey (); }; private: diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index fef5d6b6..5a2f0901 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -55,12 +55,18 @@ namespace client void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) { - memcpy (m_EncryptionPrivateKey, key, 256); - m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), m_EncryptionPrivateKey); + m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), key); } + void I2CPDestination::SetECIESx25519EncryptionPrivateKey (const uint8_t * key) + { + m_ECIESx25519Decryptor = std::make_shared(key, true); // calculate public + } + bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const { + if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET && m_ECIESx25519Decryptor) + return m_ECIESx25519Decryptor->Decrypt (encrypted, data, ctx, true); if (m_Decryptor) return m_Decryptor->Decrypt (encrypted, data, ctx, true); else @@ -68,6 +74,19 @@ namespace client return false; } + const uint8_t * I2CPDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const + { + if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET && m_ECIESx25519Decryptor) + return m_ECIESx25519Decryptor->GetPubicKey (); + return nullptr; + } + + bool I2CPDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const + { + return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET ? (bool)m_ECIESx25519Decryptor : m_EncryptionKeyType == keyType; + } + + void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); @@ -77,7 +96,8 @@ namespace client void I2CPDestination::CreateNewLeaseSet (std::vector > tunnels) { - i2p::data::LocalLeaseSet ls (m_Identity, m_EncryptionPrivateKey, tunnels); // we don't care about encryption key + uint8_t priv[256] = {0}; + i2p::data::LocalLeaseSet ls (m_Identity, priv, tunnels); // we don't care about encryption key, we need leases only m_LeaseSetExpirationTime = ls.GetExpirationTime (); uint8_t * leases = ls.GetLeases (); leases[-1] = tunnels.size (); @@ -572,28 +592,22 @@ namespace client offset += ls.GetBufferLen (); // private keys int numPrivateKeys = buf[offset]; offset++; - uint16_t currentKeyType = 0; - const uint8_t * currentKey = nullptr; for (int i = 0; i < numPrivateKeys; i++) { if (offset + 4 > len) return; uint16_t keyType = bufbe16toh (buf + offset); offset += 2; // encryption type uint16_t keyLen = bufbe16toh (buf + offset); offset += 2; // private key length if (offset + keyLen > len) return; - if (!currentKey || keyType > currentKeyType) + if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + m_Destination->SetECIESx25519EncryptionPrivateKey (buf + offset); + else { - currentKeyType = keyType; - currentKey = buf + offset; + m_Destination->SetEncryptionType (keyType); + m_Destination->SetEncryptionPrivateKey (buf + offset); } offset += keyLen; } - // TODO: support multiple keys - if (currentKey) - { - m_Destination->SetEncryptionPrivateKey (currentKey); - m_Destination->SetEncryptionType (currentKeyType); - } - + m_Destination->LeaseSet2Created (storeType, ls.GetBuffer (), ls.GetBufferLen ()); } } diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index f4e83f06..db38ecdc 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -75,14 +75,15 @@ namespace client void SetEncryptionPrivateKey (const uint8_t * key); void SetEncryptionType (i2p::data::CryptoKeyType keyType) { m_EncryptionKeyType = keyType; }; + void SetECIESx25519EncryptionPrivateKey (const uint8_t * key); void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession void LeaseSet2Created (uint8_t storeType, const uint8_t * buf, size_t len); // called from I2CPSession void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession // implements LocalDestination bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; - bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { return m_EncryptionKeyType == keyType; }; - // TODO: implement GetEncryptionPublicKey + bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; + const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; // for 4 only std::shared_ptr GetIdentity () const { return m_Identity; }; protected: @@ -101,9 +102,9 @@ namespace client std::shared_ptr m_Owner; std::shared_ptr m_Identity; - uint8_t m_EncryptionPrivateKey[256]; i2p::data::CryptoKeyType m_EncryptionKeyType; - std::shared_ptr m_Decryptor; + std::shared_ptr m_Decryptor; // standard + std::shared_ptr m_ECIESx25519Decryptor; uint64_t m_LeaseSetExpirationTime; }; From 438a225487d023f0f88905f0e1e1bbad07905801 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 3 Jun 2020 19:58:36 -0400 Subject: [PATCH 3733/6300] pass calculatePublic --- libi2pd/CryptoKey.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp index b8f66e4d..4518a347 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -168,7 +168,7 @@ namespace crypto ECIESX25519AEADRatchetDecryptor::ECIESX25519AEADRatchetDecryptor (const uint8_t * priv, bool calculatePublic) { - m_StaticKeys.SetPrivateKey (priv); + m_StaticKeys.SetPrivateKey (priv, calculatePublic); } bool ECIESX25519AEADRatchetDecryptor::Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding) From 4ae41513acd14a370514e029ebf9c4a8b32af3f5 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 4 Jun 2020 18:19:38 -0400 Subject: [PATCH 3734/6300] save new session with NSR tagset --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 11 ++++++----- libi2pd/ECIESX25519AEADRatchetSession.h | 14 +++++++++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 310d263e..a578131d 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -150,7 +150,8 @@ namespace garlic uint8_t tagsetKey[32]; i2p::crypto::HKDF (m_CK, nullptr, 0, "SessionReplyTags", tagsetKey, 32); // tagsetKey = HKDF(chainKey, ZEROLEN, "SessionReplyTags", 32) // Session Tag Ratchet - auto tagsetNsr = std::make_shared(shared_from_this ()); + auto tagsetNsr = (m_State == eSessionStateNewSessionReceived) ? std::make_shared(shared_from_this ()): + std::make_shared(shared_from_this ()); tagsetNsr->DHInitialize (m_CK, tagsetKey); // tagset_nsr = DH_INITIALIZE(chainKey, tagsetKey) tagsetNsr->NextSessionTagRatchet (); return tagsetNsr; @@ -416,8 +417,8 @@ namespace garlic bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob - m_NSRTagset = CreateNewSessionTagset (); - uint64_t tag = m_NSRTagset->GetNextSessionTag (); + m_NSRSendTagset = CreateNewSessionTagset (); + uint64_t tag = m_NSRSendTagset->GetNextSessionTag (); size_t offset = 0; memcpy (out + offset, &tag, 8); @@ -475,7 +476,7 @@ namespace garlic bool ECIESX25519AEADRatchetSession::NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob and sent NSR already - uint64_t tag = m_NSRTagset->GetNextSessionTag (); // next tag + uint64_t tag = m_NSRSendTagset->GetNextSessionTag (); // next tag memcpy (out, &tag, 8); memcpy (out + 8, m_NSREncodedKey, 32); // recalculate h with new tag @@ -625,7 +626,7 @@ namespace garlic { case eSessionStateNewSessionReplySent: m_State = eSessionStateEstablished; - m_NSRTagset = nullptr; + m_NSRSendTagset = nullptr; #if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; #endif diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 108788d5..d121842d 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -79,6 +79,18 @@ namespace garlic uint64_t m_ExpirationTimestamp = 0; }; + class NSRatchetTagSet: public RatchetTagSet + { + public: + + NSRatchetTagSet (std::shared_ptr session): + RatchetTagSet (session), m_DummySession (session) {}; + + private: + + std::shared_ptr m_DummySession; // we need a strong pointer for NS + }; + enum ECIESx25519BlockType { eECIESx25519BlkDateTime = 0, @@ -171,7 +183,7 @@ namespace garlic i2p::crypto::X25519Keys m_EphemeralKeys; SessionState m_State = eSessionStateNew; uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0; // incoming - std::shared_ptr m_SendTagset, m_NSRTagset; + std::shared_ptr m_SendTagset, m_NSRSendTagset; std::unique_ptr m_Destination;// TODO: might not need it std::list > m_AckRequests; // (tagsetid, index) bool m_SendReverseKey = false, m_SendForwardKey = false; From 55ff6beb7d76f89c1ae7237ba6b8ab5fa5727a14 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 5 Jun 2020 09:23:50 -0400 Subject: [PATCH 3735/6300] don't create ECIESx25519 again if key was not changed --- libi2pd_client/I2CP.cpp | 6 +++++- libi2pd_client/I2CP.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 5a2f0901..b0f334f5 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -60,7 +60,11 @@ namespace client void I2CPDestination::SetECIESx25519EncryptionPrivateKey (const uint8_t * key) { - m_ECIESx25519Decryptor = std::make_shared(key, true); // calculate public + if (!m_ECIESx25519Decryptor || memcmp (m_ECIESx25519PrivateKey, key, 32)) // new key? + { + m_ECIESx25519Decryptor = std::make_shared(key, true); // calculate public + memcpy (m_ECIESx25519PrivateKey, key, 32); + } } bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index db38ecdc..adb648f4 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -105,6 +105,7 @@ namespace client i2p::data::CryptoKeyType m_EncryptionKeyType; std::shared_ptr m_Decryptor; // standard std::shared_ptr m_ECIESx25519Decryptor; + uint8_t m_ECIESx25519PrivateKey[32]; uint64_t m_LeaseSetExpirationTime; }; From 6735b2686b6c13a36546dd794ee49b4d583565e0 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 5 Jun 2020 15:41:30 -0400 Subject: [PATCH 3736/6300] set LeaseSet2 for ECIESx25519 --- libi2pd/Destination.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index cd447773..6d5fb916 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -892,7 +892,11 @@ namespace client encryptionKey->GenerateKeys (); encryptionKey->CreateDecryptor (); if (it == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + { m_ECIESx25519EncryptionKey.reset (encryptionKey); + if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) + SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // Rathets must use LeaseSet2 + } else m_StandardEncryptionKey.reset (encryptionKey); } From 221c14cf0e2ae481bbaa7edfd4257511e02ec9ac Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 7 Jun 2020 16:24:11 -0400 Subject: [PATCH 3737/6300] don't lookup UDP session if port was not changed --- libi2pd_client/I2PTunnel.cpp | 28 ++++++++++++++++++---------- libi2pd_client/I2PTunnel.h | 5 +++-- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 5b384bff..ea2517d7 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -634,7 +634,7 @@ namespace client uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); std::vector removePorts; for (const auto & s : m_Sessions) { - if (now - s.second.second >= delta) + if (now - s.second->second >= delta) removePorts.push_back(s.first); } for(auto port : removePorts) { @@ -761,7 +761,7 @@ namespace client m_RemoteIdent(nullptr), m_ResolveThread(nullptr), m_LocalSocket(localDestination->GetService(), localEndpoint), - RemotePort(remotePort), + RemotePort(remotePort), m_LastPort (0), m_cancel_resolve(false) { auto dgram = m_LocalDest->CreateDatagramDestination(gzip); @@ -796,16 +796,24 @@ namespace client return; // drop, remote not resolved } auto remotePort = m_RecvEndpoint.port(); - auto itr = m_Sessions.find(remotePort); - if (itr == m_Sessions.end()) { - // track new udp convo - m_Sessions[remotePort] = {boost::asio::ip::udp::endpoint(m_RecvEndpoint), 0}; - } + if (!m_LastPort || m_LastPort != remotePort) + { + auto itr = m_Sessions.find(remotePort); + if (itr != m_Sessions.end()) + m_LastSession = itr->second; + else + { + m_LastSession = std::make_shared(boost::asio::ip::udp::endpoint(m_RecvEndpoint), 0); + m_Sessions.emplace (remotePort, m_LastSession); + } + m_LastPort = remotePort; + } // send off to remote i2p destination LogPrint(eLogDebug, "UDP Client: send ", transferred, " to ", m_RemoteIdent->ToBase32(), ":", RemotePort); m_LocalDest->GetDatagramDestination()->SendDatagramTo(m_RecvBuff, transferred, *m_RemoteIdent, remotePort, RemotePort); // mark convo as active - m_Sessions[remotePort].second = i2p::util::GetMillisecondsSinceEpoch(); + if (m_LastSession) + m_LastSession->second = i2p::util::GetMillisecondsSinceEpoch(); RecvFromLocal(); } @@ -851,9 +859,9 @@ namespace client // found convo if (len > 0) { LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", from.GetIdentHash().ToBase32()); - m_LocalSocket.send_to(boost::asio::buffer(buf, len), itr->second.first); + m_LocalSocket.send_to(boost::asio::buffer(buf, len), itr->second->first); // mark convo as active - itr->second.second = i2p::util::GetMillisecondsSinceEpoch(); + itr->second->second = i2p::util::GetMillisecondsSinceEpoch(); } } else diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index d4db80d8..99934196 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -276,7 +276,7 @@ namespace client void TryResolving(); const std::string m_Name; std::mutex m_SessionsMutex; - std::map m_Sessions; // maps i2p port -> local udp convo + std::unordered_map > m_Sessions; // maps i2p port -> local udp convo const std::string m_RemoteDest; std::shared_ptr m_LocalDest; const boost::asio::ip::udp::endpoint m_LocalEndpoint; @@ -285,8 +285,9 @@ namespace client boost::asio::ip::udp::socket m_LocalSocket; boost::asio::ip::udp::endpoint m_RecvEndpoint; uint8_t m_RecvBuff[I2P_UDP_MAX_MTU]; - uint16_t RemotePort; + uint16_t RemotePort, m_LastPort; bool m_cancel_resolve; + std::shared_ptr m_LastSession; }; class I2PServerTunnel: public I2PService From 6d7847f2dfc2da364f5303207f3665533bfbbbbc Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Jun 2020 16:26:45 -0400 Subject: [PATCH 3738/6300] send bulk datagrams --- libi2pd/Datagram.cpp | 14 ++++++++++++-- libi2pd/Datagram.h | 3 ++- libi2pd_client/I2PTunnel.cpp | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 04792bc5..5a6b8d90 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -56,6 +56,11 @@ namespace datagram session->SendMsg(msg); } + void DatagramDestination::FlushSendQueue (const i2p::data::IdentHash & ident) + { + ObtainSession(ident)->FlushSendQueue (); + } + void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort,uint8_t * const &buf, size_t len) { i2p::data::IdentityEx identity; @@ -366,6 +371,7 @@ namespace datagram void DatagramSession::FlushSendQueue () { + if (m_SendQueue.empty ()) return; std::vector send; auto routingPath = GetSharedRoutingPath(); // if we don't have a routing path we will drop all queued messages @@ -380,7 +386,6 @@ namespace datagram routingPath->outboundTunnel->SendTunnelDataMsg(send); } m_SendQueue.clear(); - ScheduleFlushSendQueue(); } void DatagramSession::ScheduleFlushSendQueue() @@ -388,7 +393,12 @@ namespace datagram boost::posix_time::milliseconds dlt(10); m_SendQueueTimer.expires_from_now(dlt); auto self = shared_from_this(); - m_SendQueueTimer.async_wait([self](const boost::system::error_code & ec) { if(ec) return; self->FlushSendQueue(); }); + m_SendQueueTimer.async_wait([self](const boost::system::error_code & ec) + { + if(ec) return; + self->FlushSendQueue(); + self->ScheduleFlushSendQueue(); + }); } } } diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index e81f738a..b7abcc03 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -59,6 +59,7 @@ namespace datagram /** send an i2np message to remote endpoint for this session */ void SendMsg(std::shared_ptr msg); + void FlushSendQueue(); /** get the last time in milliseconds for when we used this datagram session */ uint64_t LastActivity() const { return m_LastUse; } @@ -84,7 +85,6 @@ namespace datagram private: - void FlushSendQueue(); void ScheduleFlushSendQueue(); void HandleSend(std::shared_ptr msg); @@ -122,6 +122,7 @@ namespace datagram void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); void SendRawDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); + void FlushSendQueue (const i2p::data::IdentHash & ident); void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw = false); void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index ea2517d7..048c9962 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -811,6 +811,24 @@ namespace client // send off to remote i2p destination LogPrint(eLogDebug, "UDP Client: send ", transferred, " to ", m_RemoteIdent->ToBase32(), ":", RemotePort); m_LocalDest->GetDatagramDestination()->SendDatagramTo(m_RecvBuff, transferred, *m_RemoteIdent, remotePort, RemotePort); + size_t numPackets = 0; + while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) + { + boost::system::error_code ec; + size_t moreBytes = m_LocalSocket.available(ec); + if (ec || !moreBytes) break; + transferred = m_LocalSocket.receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, 0, ec); + remotePort = m_RecvEndpoint.port(); + // TODO: check remotePort + m_LocalDest->GetDatagramDestination()->SendDatagramTo(m_RecvBuff, transferred, *m_RemoteIdent, remotePort, RemotePort); + numPackets++; + } + if (numPackets) + { + LogPrint(eLogDebug, "UDP Client: sent ", numPackets, " more packets to ", m_RemoteIdent->ToBase32()); + m_LocalDest->GetDatagramDestination()->FlushSendQueue (*m_RemoteIdent); + } + // mark convo as active if (m_LastSession) m_LastSession->second = i2p::util::GetMillisecondsSinceEpoch(); From f077836bf5518d4fe19aea425dbaa7b5b12eaea1 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Jun 2020 19:20:24 -0400 Subject: [PATCH 3739/6300] store DatagramSession for bulk --- libi2pd/Datagram.cpp | 53 ++++++++++++++++++++++++------------ libi2pd/Datagram.h | 7 ++++- libi2pd_client/I2PTunnel.cpp | 9 +++--- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 5a6b8d90..c4877106 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -34,31 +34,48 @@ namespace datagram void DatagramDestination::SendDatagramTo(const uint8_t * payload, size_t len, const i2p::data::IdentHash & identity, uint16_t fromPort, uint16_t toPort) { - if (m_Owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) - { - uint8_t hash[32]; - SHA256(payload, len, hash); - m_Owner->Sign (hash, 32, m_Signature.data ()); - } - else - m_Owner->Sign (payload, len, m_Signature.data ()); - - auto session = ObtainSession(identity); - auto msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {m_Signature.data (), m_Signature.size ()}, {payload, len}}, - fromPort, toPort, false, !session->IsRatchets ()); // datagram - session->SendMsg(msg); + SendDatagram (ObtainSession(identity), payload, len, fromPort, toPort); } void DatagramDestination::SendRawDatagramTo(const uint8_t * payload, size_t len, const i2p::data::IdentHash & identity, uint16_t fromPort, uint16_t toPort) { - auto session = ObtainSession(identity); - auto msg = CreateDataMessage ({{payload, len}}, fromPort, toPort, true, !session->IsRatchets ()); // raw - session->SendMsg(msg); + SendRawDatagram (ObtainSession(identity), payload, len, fromPort, toPort); } - void DatagramDestination::FlushSendQueue (const i2p::data::IdentHash & ident) + std::shared_ptr DatagramDestination::GetSession(const i2p::data::IdentHash & ident) { - ObtainSession(ident)->FlushSendQueue (); + return ObtainSession(ident); + } + + void DatagramDestination::SendDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) + { + if (session) + { + if (m_Owner->GetIdentity ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) + { + uint8_t hash[32]; + SHA256(payload, len, hash); + m_Owner->Sign (hash, 32, m_Signature.data ()); + } + else + m_Owner->Sign (payload, len, m_Signature.data ()); + + auto msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {m_Signature.data (), m_Signature.size ()}, {payload, len}}, + fromPort, toPort, false, !session->IsRatchets ()); // datagram + session->SendMsg(msg); + } + } + + void DatagramDestination::SendRawDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) + { + if (session) + session->SendMsg(CreateDataMessage ({{payload, len}}, fromPort, toPort, true, !session->IsRatchets ())); // raw + } + + void DatagramDestination::FlushSendQueue (std::shared_ptr session) + { + if (session) + session->FlushSendQueue (); } void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort,uint8_t * const &buf, size_t len) diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index b7abcc03..fa9d2371 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -122,7 +122,12 @@ namespace datagram void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); void SendRawDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); - void FlushSendQueue (const i2p::data::IdentHash & ident); + + std::shared_ptr GetSession(const i2p::data::IdentHash & ident); + void SendDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); + void SendRawDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); + void FlushSendQueue (std::shared_ptr session); + void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw = false); void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; }; diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 048c9962..0b2bb7e0 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -810,7 +810,8 @@ namespace client } // send off to remote i2p destination LogPrint(eLogDebug, "UDP Client: send ", transferred, " to ", m_RemoteIdent->ToBase32(), ":", RemotePort); - m_LocalDest->GetDatagramDestination()->SendDatagramTo(m_RecvBuff, transferred, *m_RemoteIdent, remotePort, RemotePort); + auto session = m_LocalDest->GetDatagramDestination()->GetSession (*m_RemoteIdent); + m_LocalDest->GetDatagramDestination()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); size_t numPackets = 0; while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) { @@ -820,14 +821,12 @@ namespace client transferred = m_LocalSocket.receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, 0, ec); remotePort = m_RecvEndpoint.port(); // TODO: check remotePort - m_LocalDest->GetDatagramDestination()->SendDatagramTo(m_RecvBuff, transferred, *m_RemoteIdent, remotePort, RemotePort); + m_LocalDest->GetDatagramDestination()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); numPackets++; } if (numPackets) - { LogPrint(eLogDebug, "UDP Client: sent ", numPackets, " more packets to ", m_RemoteIdent->ToBase32()); - m_LocalDest->GetDatagramDestination()->FlushSendQueue (*m_RemoteIdent); - } + m_LocalDest->GetDatagramDestination()->FlushSendQueue (session); // mark convo as active if (m_LastSession) From a8f227f7594ae827764d1e7445cfe1824da689bf Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 9 Jun 2020 21:48:47 -0400 Subject: [PATCH 3740/6300] send raw follow-on datagrams --- libi2pd_client/I2PTunnel.cpp | 22 +++++++++++++++++----- libi2pd_client/I2PTunnel.h | 2 ++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 0b2bb7e0..48b1e79d 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -611,12 +611,23 @@ namespace client void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { - std::lock_guard lock(m_SessionsMutex); - auto session = ObtainUDPSession(from, toPort, fromPort); - session->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); - session->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + { + std::lock_guard lock(m_SessionsMutex); + m_LastSession = ObtainUDPSession(from, toPort, fromPort); + } + m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); + m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); } + void I2PUDPServerTunnel::HandleRecvFromI2PRaw (uint16_t, uint16_t, const uint8_t * buf, size_t len) + { + if (m_LastSession) + { + m_LastSession->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); + m_LastSession->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + } + } + void I2PUDPServerTunnel::ExpireStale(const uint64_t delta) { std::lock_guard lock(m_SessionsMutex); uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); @@ -714,6 +725,7 @@ namespace client m_LocalDest->Start(); auto dgram = m_LocalDest->CreateDatagramDestination(gzip); dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + dgram->SetRawReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2PRaw, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); } I2PUDPServerTunnel::~I2PUDPServerTunnel() @@ -821,7 +833,7 @@ namespace client transferred = m_LocalSocket.receive_from (boost::asio::buffer (m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, 0, ec); remotePort = m_RecvEndpoint.port(); // TODO: check remotePort - m_LocalDest->GetDatagramDestination()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); + m_LocalDest->GetDatagramDestination()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); numPackets++; } if (numPackets) diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index 99934196..19e44e79 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -237,6 +237,7 @@ namespace client private: void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); UDPSessionPtr ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); private: @@ -248,6 +249,7 @@ namespace client std::mutex m_SessionsMutex; std::vector m_Sessions; std::shared_ptr m_LocalDest; + UDPSessionPtr m_LastSession; }; class I2PUDPClientTunnel From 0639cce784a0506cff7be8fbc6b9edb2b9a420b7 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 10 Jun 2020 05:11:26 +0300 Subject: [PATCH 3741/6300] [SAM] fix ECDSA signatures names Signed-off-by: R4SAS --- libi2pd_client/SAM.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 2c6b711d..4723d168 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1015,8 +1015,8 @@ namespace client { {"DSA_SHA1", i2p::data::SIGNING_KEY_TYPE_DSA_SHA1}, {"ECDSA_SHA256_P256", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256}, - {"ECDSA_SHA256_P384", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384}, - {"ECDSA_SHA256_P521", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521}, + {"ECDSA_SHA384_P384", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA384_P384}, + {"ECDSA_SHA512_P521", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521}, {"EdDSA_SHA512_Ed25519", i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519}, {"GOST_GOSTR3411256_GOSTR3410CRYPTOPROA", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256}, {"GOST_GOSTR3411512_GOSTR3410TC26A512", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512}, From a33cad4b709fe62a488cbcd7447e3f35dd8ab3bf Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 10 Jun 2020 11:57:40 -0400 Subject: [PATCH 3742/6300] eliminate datagram send timer --- libi2pd/Datagram.cpp | 26 +++++++------------------- libi2pd/Datagram.h | 3 --- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index c4877106..99318ec9 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -34,12 +34,16 @@ namespace datagram void DatagramDestination::SendDatagramTo(const uint8_t * payload, size_t len, const i2p::data::IdentHash & identity, uint16_t fromPort, uint16_t toPort) { - SendDatagram (ObtainSession(identity), payload, len, fromPort, toPort); + auto session = ObtainSession(identity); + SendDatagram (session, payload, len, fromPort, toPort); + FlushSendQueue (session); } void DatagramDestination::SendRawDatagramTo(const uint8_t * payload, size_t len, const i2p::data::IdentHash & identity, uint16_t fromPort, uint16_t toPort) { - SendRawDatagram (ObtainSession(identity), payload, len, fromPort, toPort); + auto session = ObtainSession(identity); + SendRawDatagram (session, payload, len, fromPort, toPort); + FlushSendQueue (session); } std::shared_ptr DatagramDestination::GetSession(const i2p::data::IdentHash & ident) @@ -218,7 +222,6 @@ namespace datagram const i2p::data::IdentHash & remoteIdent) : m_LocalDestination(localDestination), m_RemoteIdent(remoteIdent), - m_SendQueueTimer(localDestination->GetService()), m_RequestingLS(false) { } @@ -226,12 +229,10 @@ namespace datagram void DatagramSession::Start () { m_LastUse = i2p::util::GetMillisecondsSinceEpoch (); - ScheduleFlushSendQueue(); } void DatagramSession::Stop () { - m_SendQueueTimer.cancel (); } void DatagramSession::SendMsg(std::shared_ptr msg) @@ -383,7 +384,7 @@ namespace datagram if (msg || m_SendQueue.empty ()) m_SendQueue.push_back(msg); // flush queue right away if full - if(m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) FlushSendQueue(); + if(!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) FlushSendQueue(); } void DatagramSession::FlushSendQueue () @@ -404,18 +405,5 @@ namespace datagram } m_SendQueue.clear(); } - - void DatagramSession::ScheduleFlushSendQueue() - { - boost::posix_time::milliseconds dlt(10); - m_SendQueueTimer.expires_from_now(dlt); - auto self = shared_from_this(); - m_SendQueueTimer.async_wait([self](const boost::system::error_code & ec) - { - if(ec) return; - self->FlushSendQueue(); - self->ScheduleFlushSendQueue(); - }); - } } } diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index fa9d2371..2aabcb1f 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -85,8 +85,6 @@ namespace datagram private: - void ScheduleFlushSendQueue(); - void HandleSend(std::shared_ptr msg); std::shared_ptr GetSharedRoutingPath(); @@ -101,7 +99,6 @@ namespace datagram std::shared_ptr m_RoutingSession; std::shared_ptr m_CurrentRemoteLease; std::shared_ptr m_CurrentOutboundTunnel; - boost::asio::deadline_timer m_SendQueueTimer; std::vector > m_SendQueue; uint64_t m_LastUse; bool m_RequestingLS; From 44bb8f6f1691176978390b753a325b92b42fbf00 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 10 Jun 2020 21:19:37 -0400 Subject: [PATCH 3743/6300] allocated datagram I2NP from memory pool --- libi2pd/Datagram.cpp | 18 ++++++------------ libi2pd/Datagram.h | 4 ++-- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 99318ec9..5a196c3a 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -152,7 +152,7 @@ namespace datagram const std::vector >& payloads, uint16_t fromPort, uint16_t toPort, bool isRaw, bool checksum) { - auto msg = NewI2NPMessage (); + auto msg = m_I2NPMsgsPool.AcquireShared (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for length size_t size = m_Gzip ? m_Deflator.Deflate (payloads, buf, msg->maxLen - msg->len) : @@ -239,9 +239,11 @@ namespace datagram { // we used this session m_LastUse = i2p::util::GetMillisecondsSinceEpoch(); - // schedule send - auto self = shared_from_this(); - m_LocalDestination->GetService().post(std::bind(&DatagramSession::HandleSend, self, msg)); + if (msg || m_SendQueue.empty ()) + m_SendQueue.push_back(msg); + // flush queue right away if full + if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) + FlushSendQueue(); } DatagramSession::Info DatagramSession::GetSessionInfo() const @@ -379,14 +381,6 @@ namespace datagram if(ls && ls->GetExpirationTime() > oldExpire) m_RemoteLeaseSet = ls; } - void DatagramSession::HandleSend(std::shared_ptr msg) - { - if (msg || m_SendQueue.empty ()) - m_SendQueue.push_back(msg); - // flush queue right away if full - if(!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) FlushSendQueue(); - } - void DatagramSession::FlushSendQueue () { if (m_SendQueue.empty ()) return; diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index 2aabcb1f..94fe422c 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -85,8 +85,6 @@ namespace datagram private: - void HandleSend(std::shared_ptr msg); - std::shared_ptr GetSharedRoutingPath(); void HandleLeaseSetUpdated(std::shared_ptr ls); @@ -119,6 +117,7 @@ namespace datagram void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); void SendRawDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0); + // TODO: implement calls from other thread from SAM std::shared_ptr GetSession(const i2p::data::IdentHash & ident); void SendDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort); @@ -168,6 +167,7 @@ namespace datagram i2p::data::GzipInflator m_Inflator; i2p::data::GzipDeflator m_Deflator; std::vector m_From, m_Signature; + i2p::util::MemoryPool > m_I2NPMsgsPool; }; } } From 6a0174293e0e828d52b06cb5d261cdf183a865ec Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 11 Jun 2020 22:04:32 -0400 Subject: [PATCH 3744/6300] send raw datagrams in opposite direction --- libi2pd_client/I2PTunnel.cpp | 61 ++++++++++++++++++++++++------------ libi2pd_client/I2PTunnel.h | 4 +++ 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 48b1e79d..74175b14 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -611,6 +611,7 @@ namespace client void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { + if (!m_LastSession || m_LastSession->Identity.GetLL()[0] != from.GetIdentHash ().GetLL()[0]) { std::lock_guard lock(m_SessionsMutex); m_LastSession = ObtainUDPSession(from, toPort, fromPort); @@ -658,7 +659,7 @@ namespace client auto ih = from.GetIdentHash(); for (auto & s : m_Sessions ) { - if ( s->Identity == ih) + if (s->Identity.GetLL()[0] == ih.GetLL()[0]) { /** found existing session */ LogPrint(eLogDebug, "UDPServer: found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32()); @@ -707,11 +708,25 @@ namespace client { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); LastActivity = i2p::util::GetMillisecondsSinceEpoch(); - m_Destination->SendDatagramTo(m_Buffer, len, Identity, LocalPort, RemotePort); + auto session = m_Destination->GetSession (Identity); + m_Destination->SendDatagram(session, m_Buffer, len, LocalPort, RemotePort); + size_t numPackets = 0; + while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) + { + boost::system::error_code ec; + size_t moreBytes = IPSocket.available(ec); + if (ec || !moreBytes) break; + len = IPSocket.receive_from (boost::asio::buffer (m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, 0, ec); + m_Destination->SendRawDatagram (session, m_Buffer, len, LocalPort, RemotePort); + numPackets++; + } + if (numPackets > 0) + LogPrint(eLogDebug, "UDPSession: forward more ", numPackets, "packets B from ", FromEndpoint); + m_Destination->FlushSendQueue (session); Receive(); - } else { + } + else LogPrint(eLogError, "UDPSession: ", ecode.message()); - } } I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, @@ -781,6 +796,8 @@ namespace client std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + dgram->SetRawReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); } void I2PUDPClientTunnel::Start() { @@ -880,26 +897,30 @@ namespace client void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if(m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) - { - auto itr = m_Sessions.find(toPort); - // found convo ? - if(itr != m_Sessions.end()) - { - // found convo - if (len > 0) { - LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", from.GetIdentHash().ToBase32()); - m_LocalSocket.send_to(boost::asio::buffer(buf, len), itr->second->first); - // mark convo as active - itr->second->second = i2p::util::GetMillisecondsSinceEpoch(); - } - } - else - LogPrint(eLogWarning, "UDP Client: not tracking udp session using port ", (int) toPort); - } + HandleRecvFromI2PRaw (fromPort, toPort, buf, len); else LogPrint(eLogWarning, "UDP Client: unwarranted traffic from ", from.GetIdentHash().ToBase32()); } + void I2PUDPClientTunnel::HandleRecvFromI2PRaw(uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) + { + auto itr = m_Sessions.find(toPort); + // found convo ? + if(itr != m_Sessions.end()) + { + // found convo + if (len > 0) + { + LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", m_RemoteIdent ? m_RemoteIdent->ToBase32() : ""); + m_LocalSocket.send_to(boost::asio::buffer(buf, len), itr->second->first); + // mark convo as active + itr->second->second = i2p::util::GetMillisecondsSinceEpoch(); + } + } + else + LogPrint(eLogWarning, "UDP Client: not tracking udp session using port ", (int) toPort); + } + I2PUDPClientTunnel::~I2PUDPClientTunnel() { auto dgram = m_LocalDest->GetDatagramDestination(); if (dgram) dgram->ResetReceiver(); diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index 19e44e79..19d553da 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -275,7 +275,11 @@ namespace client void RecvFromLocal(); void HandleRecvFromLocal(const boost::system::error_code & e, std::size_t transferred); void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); + void HandleRecvFromI2PRaw(uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void TryResolving(); + + private: + const std::string m_Name; std::mutex m_SessionsMutex; std::unordered_map > m_Sessions; // maps i2p port -> local udp convo From 5993cc857a94abd10ebf05fbe7c1426719a1b9a4 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Jun 2020 16:03:12 -0400 Subject: [PATCH 3745/6300] start new tunnel message if remining is too small --- libi2pd/TunnelGateway.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/TunnelGateway.cpp b/libi2pd/TunnelGateway.cpp index 8f01bc4e..317926ae 100644 --- a/libi2pd/TunnelGateway.cpp +++ b/libi2pd/TunnelGateway.cpp @@ -66,7 +66,7 @@ namespace tunnel // length of bytes doesn't fit full tunnel message // every follow-on fragment adds 7 bytes size_t nonFit = (fullMsgLen + numFollowOnFragments*7) % TUNNEL_DATA_MAX_PAYLOAD_SIZE; - if (!nonFit || nonFit > m_RemainingSize) + if (!nonFit || nonFit > m_RemainingSize || m_RemainingSize < fullMsgLen/5) { CompleteCurrentTunnelDataMessage (); CreateCurrentTunnelDataMessage (); From 5e0a8ed2324b4a6966fff0fe2b70a21e3c15dd14 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Jun 2020 16:06:07 -0400 Subject: [PATCH 3746/6300] set UDP receive buffer size --- libi2pd_client/I2PTunnel.cpp | 3 +++ libi2pd_client/I2PTunnel.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 74175b14..d05a97b7 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -692,6 +692,7 @@ namespace client LocalPort(ourPort), RemotePort(theirPort) { + IPSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); memcpy(Identity, to->data(), 32); Receive(); } @@ -791,6 +792,8 @@ namespace client RemotePort(remotePort), m_LastPort (0), m_cancel_resolve(false) { + m_LocalSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); + auto dgram = m_LocalDest->CreateDatagramDestination(gzip); dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index 19d553da..eb1d8ed9 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -167,7 +167,7 @@ namespace client const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; /** max size for i2p udp */ - const size_t I2P_UDP_MAX_MTU = i2p::datagram::MAX_DATAGRAM_SIZE; + const size_t I2P_UDP_MAX_MTU = 64*1024; struct UDPSession { From 61897ae16c151f67e73bb3f13c3c0e513acb05e9 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 12 Jun 2020 20:42:54 -0400 Subject: [PATCH 3747/6300] crypto.ratchet.inboundTags --- libi2pd/Destination.cpp | 3 +++ libi2pd/Destination.h | 2 ++ libi2pd/ECIESX25519AEADRatchetSession.cpp | 31 +++++++++++++++++------ libi2pd/Garlic.cpp | 3 ++- libi2pd/Garlic.h | 3 +++ libi2pd_client/ClientContext.cpp | 2 ++ 6 files changed, 35 insertions(+), 9 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 6d5fb916..a764224c 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -56,6 +56,9 @@ namespace client if (it != params->end ()) numTags = std::stoi(it->second); LogPrint (eLogInfo, "Destination: parameters for tunnel set to: ", inQty, " inbound (", inLen, " hops), ", outQty, " outbound (", outLen, " hops), ", numTags, " tags"); + it = params->find (I2CP_PARAM_RATCHET_INBOUND_TAGS); + if (it != params->end ()) + SetNumRatchetInboundTags (std::stoi(it->second)); it = params->find (I2CP_PARAM_EXPLICIT_PEERS); if (it != params->end ()) { diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 139423c6..1d5c0a55 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -57,6 +57,8 @@ namespace client const int STREAM_REQUEST_TIMEOUT = 60; //in seconds const char I2CP_PARAM_TAGS_TO_SEND[] = "crypto.tagsToSend"; const int DEFAULT_TAGS_TO_SEND = 40; + const char I2CP_PARAM_RATCHET_INBOUND_TAGS[] = "crypto.ratchet.inboundTags"; + const char I2CP_PARAM_RATCHET_OUTBOUND_TAGS[] = "crypto.ratchet.outboundTags"; // not used yet const char I2CP_PARAM_INBOUND_NICKNAME[] = "inbound.nickname"; const char I2CP_PARAM_OUTBOUND_NICKNAME[] = "outbound.nickname"; const char I2CP_PARAM_LEASESET_TYPE[] = "i2cp.leaseSetType"; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index a578131d..ad91382f 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -341,7 +341,8 @@ namespace garlic newTagset->SetTagSetID (tagsetID); newTagset->DHInitialize (receiveTagset->GetNextRootKey (), tagsetKey); newTagset->NextSessionTagRatchet (); - GenerateMoreReceiveTags (newTagset, ECIESX25519_MAX_NUM_GENERATED_TAGS); + GenerateMoreReceiveTags (newTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ? + GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MAX_NUM_GENERATED_TAGS); receiveTagset->Expire (); LogPrint (eLogDebug, "Garlic: next receive tagset ", tagsetID, " created"); } @@ -459,7 +460,8 @@ namespace garlic m_SendTagset = std::make_shared(shared_from_this ()); m_SendTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) m_SendTagset->NextSessionTagRatchet (); - GenerateMoreReceiveTags (receiveTagset, ECIESX25519_MIN_NUM_GENERATED_TAGS); + GenerateMoreReceiveTags (receiveTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ? + GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MIN_NUM_GENERATED_TAGS); i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", m_NSRKey, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) // encrypt payload if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_NSRKey, nonce, out + offset, len + 16, true)) // encrypt @@ -548,7 +550,8 @@ namespace garlic auto receiveTagset = std::make_shared(shared_from_this ()); receiveTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) receiveTagset->NextSessionTagRatchet (); - GenerateMoreReceiveTags (receiveTagset, ECIESX25519_MIN_NUM_GENERATED_TAGS); + GenerateMoreReceiveTags (receiveTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ? + GetOwner ()->GetNumRatchetInboundTags () : ECIESX25519_MIN_NUM_GENERATED_TAGS); } i2p::crypto::HKDF (keydata + 32, nullptr, 0, "AttachPayloadKDF", keydata, 32); // k = HKDF(k_ba, ZEROLEN, "AttachPayloadKDF", 32) // decrypt payload @@ -610,11 +613,23 @@ namespace garlic return false; } HandlePayload (payload, len - 16, receiveTagset, index); - int moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4 - if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; - moreTags -= (receiveTagset->GetNextIndex () - index); - if (moreTags > 0 && GetOwner ()) - GenerateMoreReceiveTags (receiveTagset, moreTags); + if (GetOwner ()) + { + int moreTags = 0; + if (GetOwner ()->GetNumRatchetInboundTags () > 0) // override in settings? + { + if (receiveTagset->GetNextIndex () - index < GetOwner ()->GetNumRatchetInboundTags ()/2) + moreTags = GetOwner ()->GetNumRatchetInboundTags (); + } + else + { + moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4 + if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; + moreTags -= (receiveTagset->GetNextIndex () - index); + } + if (moreTags > 0) + GenerateMoreReceiveTags (receiveTagset, moreTags); + } return true; } diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 1170634d..84a0519e 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -432,7 +432,8 @@ namespace garlic return ret; } - GarlicDestination::GarlicDestination (): m_NumTags (32) // 32 tags by default + GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default + m_NumRatchetInboundTags (0) // 0 means standard { m_Ctx = BN_CTX_new (); } diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 6b34231a..70893bec 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -232,6 +232,8 @@ namespace garlic void CleanUp (); void SetNumTags (int numTags) { m_NumTags = numTags; }; int GetNumTags () const { return m_NumTags; }; + void SetNumRatchetInboundTags (int numTags) { m_NumRatchetInboundTags = numTags; }; + int GetNumRatchetInboundTags () const { return m_NumRatchetInboundTags; }; std::shared_ptr GetRoutingSession (std::shared_ptr destination, bool attachLeaseSet); void CleanupExpiredTags (); void RemoveDeliveryStatusSession (uint32_t msgID); @@ -278,6 +280,7 @@ namespace garlic std::unordered_map m_Sessions; std::unordered_map, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session // incoming + int m_NumRatchetInboundTags; std::unordered_map, std::hash > > m_Tags; std::unordered_map m_ECIESx25519Tags; // session tag -> session // DeliveryStatus diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 54007c95..0348aeeb 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -461,6 +461,8 @@ namespace client } std::string explicitPeers = GetI2CPStringOption(section, I2CP_PARAM_EXPLICIT_PEERS, ""); if (explicitPeers.length () > 0) options[I2CP_PARAM_EXPLICIT_PEERS] = explicitPeers; + std::string ratchetInboundTags = GetI2CPStringOption(section, I2CP_PARAM_RATCHET_INBOUND_TAGS, ""); + if (ratchetInboundTags.length () > 0) options[I2CP_PARAM_RATCHET_INBOUND_TAGS] = ratchetInboundTags; } void ClientContext::ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const From 79858d4372f1754363ce61059208f5ba4c4329d5 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 13 Jun 2020 18:19:24 +0300 Subject: [PATCH 3748/6300] [webconsole] adaptive styling Signed-off-by: R4SAS --- android/assets/tunnels.conf | 2 +- daemon/HTTPServer.cpp | 60 ++++++++++++++++++++++++------------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/android/assets/tunnels.conf b/android/assets/tunnels.conf index e95fdf2e..c39a0220 100644 --- a/android/assets/tunnels.conf +++ b/android/assets/tunnels.conf @@ -1,4 +1,4 @@ -[IRC-IRC2P] +#[IRC-IRC2P] #type = client #address = 127.0.0.1 #port = 6668 diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 7268ae01..a9588436 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -63,23 +63,40 @@ namespace http { " body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: #FAFAFA; color: #103456; }\r\n" " a, .slide label { text-decoration: none; color: #894C84; }\r\n" " a:hover, .slide label:hover { color: #FAFAFA; background: #894C84; }\r\n" - " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none; color: initial; padding: 0 5px; }\r\n" - " .header { font-size: 2.5em; text-align: center; margin: 1.5em 0; color: #894C84; }\r\n" - " .wrapper { margin: 0 auto; padding: 1em; max-width: 60em; }\r\n" - " .left { float: left; position: absolute; }\r\n" - " .right { float: left; font-size: 1em; margin-left: 13em; max-width: 46em; overflow: auto; }\r\n" + " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n" + " color: initial; padding: 0 5px; border: 1px solid #894C84; }\r\n" + " .header { font-size: 2.5em; text-align: center; margin: 1em 0; color: #894C84; }\r\n" + " .wrapper { margin: 0 auto; padding: 1em; max-width: 56em; }\r\n" + " .menu { float: left; } .menu a { display: block; padding: 2px; }\r\n" + " .content { float: left; font-size: 1em; margin-left: 4em; max-width: 46em; overflow: auto; }\r\n" " .tunnel.established { color: #56B734; }\r\n" " .tunnel.expiring { color: #D3AE3F; }\r\n" " .tunnel.failed { color: #D33F3F; }\r\n" " .tunnel.building { color: #434343; }\r\n" " caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n" " table { display: table; border-collapse: collapse; text-align: center; }\r\n" - " table.extaddr { text-align: left; }\r\n table.services { width: 100%; }" + " table.extaddr { text-align: left; } table.services { width: 100%; }\r\n" " .streamdest { width: 120px; max-width: 240px; overflow: hidden; text-overflow: ellipsis;}\r\n" " .slide div.content, .slide [type='checkbox'] { display: none; }\r\n" " .slide [type='checkbox']:checked ~ div.content { display: block; margin-top: 0; padding: 0; }\r\n" " .disabled:after { color: #D33F3F; content: \"Disabled\" }\r\n" " .enabled:after { color: #56B734; content: \"Enabled\" }\r\n" + " @media screen and (max-width: 980px) {\r\n" /* adaptive style */ + " .menu { width: 100%; display: block; float: none; position: unset; font-size: 24px;\r\n" + " text-align: center; }\r\n" + " .content { float: none; margin: 0; margin-top: 16px; max-width: 100%; width: 100%; font-size: 1.2em;\r\n" + " text-align: center; line-height: 28px; }\r\n" + " a, .slide label { /* margin-right: 10px; */ display: block; /* font-size: 18px; */ }\r\n" + " .header { margin: 0.5em 0; } small {display: block}\r\n" + " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n" + " color: initial; margin-top: 10px; padding: 6px; border: 1px solid #894c84; width: -webkit-fill-available; }\r\n" + " input { width: 35%; height: 50px; text-align: center; /* margin-top: 15px; */ padding: 5px;\r\n" + " border: 2px solid #ccc; -webkit-border-radius: 5px; border-radius: 5px; font-size: 35px; }\r\n" + " textarea { width: -webkit-fill-available; height: auto; padding:5px; border:2px solid #ccc;\r\n" + " -webkit-border-radius: 5px; border-radius: 5px; font-size: 22px; }\r\n" + " button[type=submit] { padding: 5px 15px; background: #ccc; border: 0 none; cursor: pointer;\r\n" + " -webkit-border-radius: 5px; border-radius: 5px; position: relative; height: 50px; display: -webkit-inline-box; margin-top: 25px; }\r\n" + " }\r\n" /* adaptive style */ "\r\n"; const char HTTP_PAGE_TUNNELS[] = "tunnels"; @@ -181,30 +198,31 @@ namespace http { #else " \r\n" #endif + " \r\n" " \r\n" " Purple I2P " VERSION " Webconsole\r\n" << cssStyles << "\r\n"; s << "\r\n" - "
    i2pd webconsole
    \r\n" - "
    \r\n" - "
    \r\n" - " Main page
    \r\n
    \r\n" - " Router commands
    \r\n" - " Local destinations
    \r\n"; + "
    i2pd webconsole
    \r\n" + "
    \r\n" + "
    \r\n" + " Main page
    \r\n" + " Router commands\r\n" + " Local destinations\r\n"; if (i2p::context.IsFloodfill ()) - s << " LeaseSets
    \r\n"; + s << " LeaseSets\r\n"; s << - " Tunnels
    \r\n" - " Transit tunnels
    \r\n" - " Transports
    \r\n" - " I2P tunnels
    \r\n"; + " Tunnels\r\n" + " Transit tunnels\r\n" + " Transports\r\n" + " I2P tunnels\r\n"; if (i2p::client::context.GetSAMBridge ()) - s << " SAM sessions
    \r\n"; + s << " SAM sessions\r\n"; s << "
    \r\n" - "
    "; + "
    "; } static void ShowPageTail (std::stringstream& s) @@ -416,7 +434,7 @@ namespace http { out_tags += it.second->GetNumOutgoingTags (); } s << "
    \r\n\r\n" - << "
    \r\n\r\n" << tmp_s.str () << "
    DestinationAmount
    \r\n
    \r\n
    \r\n"; + << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationAmount
    \r\n
    \r\n
    \r\n"; } else s << "Outgoing: 0
    \r\n"; s << "
    \r\n"; @@ -432,7 +450,7 @@ namespace http { ecies_sessions++; } s << "
    \r\n\r\n" - << "
    \r\n\r\n" << tmp_s.str () << "
    DestinationStatus
    \r\n
    \r\n
    \r\n"; + << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationStatus
    \r\n
    \r\n
    \r\n"; } else s << "Tags sessions: 0
    \r\n"; s << "
    \r\n"; From a5fed64f38c7f0b21f31297161ef15fa50f1d79e Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 13 Jun 2020 18:33:39 +0300 Subject: [PATCH 3749/6300] [webconsole] update sliders html and css Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index a9588436..41f198c8 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -66,7 +66,7 @@ namespace http { " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n" " color: initial; padding: 0 5px; border: 1px solid #894C84; }\r\n" " .header { font-size: 2.5em; text-align: center; margin: 1em 0; color: #894C84; }\r\n" - " .wrapper { margin: 0 auto; padding: 1em; max-width: 56em; }\r\n" + " .wrapper { margin: 0 auto; padding: 1em; max-width: 58em; }\r\n" " .menu { float: left; } .menu a { display: block; padding: 2px; }\r\n" " .content { float: left; font-size: 1em; margin-left: 4em; max-width: 46em; overflow: auto; }\r\n" " .tunnel.established { color: #56B734; }\r\n" @@ -77,8 +77,8 @@ namespace http { " table { display: table; border-collapse: collapse; text-align: center; }\r\n" " table.extaddr { text-align: left; } table.services { width: 100%; }\r\n" " .streamdest { width: 120px; max-width: 240px; overflow: hidden; text-overflow: ellipsis;}\r\n" - " .slide div.content, .slide [type='checkbox'] { display: none; }\r\n" - " .slide [type='checkbox']:checked ~ div.content { display: block; margin-top: 0; padding: 0; }\r\n" + " .slide div.slidecontent, .slide [type=\"checkbox\"] { display: none; }\r\n" + " .slide [type=\"checkbox\"]:checked ~ div.slidecontent { display: block; margin-top: 0; padding: 0; }\r\n" " .disabled:after { color: #D33F3F; content: \"Disabled\" }\r\n" " .enabled:after { color: #56B734; content: \"Enabled\" }\r\n" " @media screen and (max-width: 980px) {\r\n" /* adaptive style */ @@ -284,7 +284,7 @@ namespace http { s << "Data path: " << i2p::fs::GetDataDir() << "
    \r\n"; s << "
    "; if((outputFormat==OutputFormatEnum::forWebConsole)||!includeHiddenContent) { - s << "\r\n\r\n
    \r\n"; + s << "\r\n\r\n
    \r\n"; } if(includeHiddenContent) { s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
    \r\n"; @@ -391,7 +391,7 @@ namespace http { if (dest->IsEncryptedLeaseSet ()) { i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); - s << "
    \r\n\r\n
    \r\n"; + s << "
    \r\n\r\n
    \r\n"; s << blinded.ToB33 () << ".b32.i2p
    \r\n"; s << "
    \r\n
    \r\n"; } @@ -399,7 +399,7 @@ namespace http { if(dest->GetNumRemoteLeaseSets()) { s << "
    \r\n\r\n
    \r\n"; + << "\r\n\r\n
    \r\n
    AddressTypeEncType
    "; for(auto& it: dest->GetLeaseSets ()) s << "\r\n"; s << "
    AddressTypeEncType
    " << it.first.ToBase32 () << "" << (int)it.second->GetStoreType () << "" << (int)it.second->GetEncryptionType () <<"
    \r\n
    \r\n
    \r\n
    \r\n"; @@ -433,8 +433,8 @@ namespace http { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "" << it.second->GetNumOutgoingTags () << "\r\n"; out_tags += it.second->GetNumOutgoingTags (); } - s << "
    \r\n\r\n" - << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationAmount
    \r\n
    \r\n
    \r\n"; + s << "
    \r\n\r\n" + << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationAmount
    \r\n
    \r\n
    \r\n"; } else s << "Outgoing: 0
    \r\n"; s << "
    \r\n"; @@ -449,8 +449,8 @@ namespace http { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "" << it.second->GetState () << "\r\n"; ecies_sessions++; } - s << "
    \r\n\r\n" - << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationStatus
    \r\n
    \r\n
    \r\n"; + s << "
    \r\n\r\n" + << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationStatus
    \r\n
    \r\n
    \r\n"; } else s << "Tags sessions: 0
    \r\n"; s << "
    \r\n"; @@ -549,7 +549,7 @@ namespace http { if (!ls->IsValid()) s << "
    !! Invalid !!
    \r\n"; s << "
    \r\n"; - s << "\r\n
    \r\n"; + s << "\r\n
    \r\n"; s << "Store type: " << (int)storeType << "
    \r\n"; s << "Expires: " << ConvertTime(ls->GetExpirationTime()) << "
    \r\n"; if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) @@ -700,13 +700,13 @@ namespace http { if (!tmp_s.str ().empty ()) { s << "
    \r\n\r\n
    " + << " ( " << cnt << " )\r\n\r\n
    " << tmp_s.str () << "
    \r\n
    \r\n"; } if (!tmp_s6.str ().empty ()) { s << "
    \r\n\r\n
    " + << "v6 ( " << cnt6 << " )\r\n\r\n
    " << tmp_s6.str () << "
    \r\n
    \r\n"; } } @@ -734,7 +734,7 @@ namespace http { auto sessions = ssuServer->GetSessions (); if (!sessions.empty ()) { - s << "
    \r\n\r\n
    "; + s << "
    \r\n\r\n
    "; for (const auto& it: sessions) { auto endpoint = it.second->GetRemoteEndpoint (); @@ -751,7 +751,7 @@ namespace http { auto sessions6 = ssuServer->GetSessionsV6 (); if (!sessions6.empty ()) { - s << "
    \r\n\r\n
    "; + s << "
    \r\n\r\n
    "; for (const auto& it: sessions6) { auto endpoint = it.second->GetRemoteEndpoint (); From 1a39f7e5c608c6dd2443aef27534f05d9e56f4f9 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 13 Jun 2020 16:18:12 -0400 Subject: [PATCH 3750/6300] GarlicRoutingPath per session --- libi2pd/Datagram.cpp | 118 +++++++++++++++++++------------------------ libi2pd/Datagram.h | 2 - 2 files changed, 53 insertions(+), 67 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 5a196c3a..ad679cc0 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -281,94 +281,82 @@ namespace datagram std::shared_ptr DatagramSession::GetSharedRoutingPath () { - if (!m_RoutingSession || !m_RoutingSession->GetOwner ()) + if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) { - if(!m_RemoteLeaseSet) { - m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); - } - if(!m_RemoteLeaseSet) { - // no remote lease set - if(!m_RequestingLS) { + m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); + if (!m_RemoteLeaseSet) + { + if(!m_RequestingLS) + { m_RequestingLS = true; m_LocalDestination->RequestDestination(m_RemoteIdent, std::bind(&DatagramSession::HandleLeaseSetUpdated, this, std::placeholders::_1)); } return nullptr; - } + } + } + + if (!m_RoutingSession || !m_RoutingSession->GetOwner ()) m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); - } + auto path = m_RoutingSession->GetSharedRoutingPath(); - if(path) { - if (m_CurrentOutboundTunnel && !m_CurrentOutboundTunnel->IsEstablished()) { + if (path) + { + if (path->outboundTunnel && !path->outboundTunnel->IsEstablished ()) // bad outbound tunnel, switch outbound tunnel - m_CurrentOutboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(m_CurrentOutboundTunnel); - path->outboundTunnel = m_CurrentOutboundTunnel; - } - if(m_CurrentRemoteLease && m_CurrentRemoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) { + path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(path->outboundTunnel); + + if (path->remoteLease && path->remoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) + { // bad lease, switch to next one - if(m_RemoteLeaseSet && m_RemoteLeaseSet->IsExpired()) - m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); - if(m_RemoteLeaseSet) { - auto ls = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding([&](const i2p::data::Lease& l) -> bool { - return l.tunnelID == m_CurrentRemoteLease->tunnelID; - }); + if (m_RemoteLeaseSet) + { + auto ls = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding( + [&](const i2p::data::Lease& l) -> bool + { + return l.tunnelID == path->remoteLease->tunnelID; + }); auto sz = ls.size(); - if (sz) { + if (sz) + { auto idx = rand() % sz; - m_CurrentRemoteLease = ls[idx]; + path->remoteLease = ls[idx]; } - } else { + else + path->remoteLease = nullptr; + } + else // no remote lease set? LogPrint(eLogWarning, "DatagramSession: no cached remote lease set for ", m_RemoteIdent.ToBase32()); - } - path->remoteLease = m_CurrentRemoteLease; } - } else { + } + else + { // no current path, make one path = std::make_shared(); - // switch outbound tunnel if bad - if(m_CurrentOutboundTunnel == nullptr || ! m_CurrentOutboundTunnel->IsEstablished()) { - m_CurrentOutboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(m_CurrentOutboundTunnel); - } - // switch lease if bad - if(m_CurrentRemoteLease && m_CurrentRemoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) { - if(!m_RemoteLeaseSet) { - m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); - } - if(m_RemoteLeaseSet) { - // pick random next good lease - auto ls = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding([&] (const i2p::data::Lease & l) -> bool { - if(m_CurrentRemoteLease) - return l.tunnelGateway == m_CurrentRemoteLease->tunnelGateway; - return false; - }); - auto sz = ls.size(); - if(sz) { - auto idx = rand() % sz; - m_CurrentRemoteLease = ls[idx]; - } - } else { - // no remote lease set currently, bail - LogPrint(eLogWarning, "DatagramSession: no remote lease set found for ", m_RemoteIdent.ToBase32()); - return nullptr; - } - } else if (!m_CurrentRemoteLease) { - if(!m_RemoteLeaseSet) m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); - if (m_RemoteLeaseSet) + path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(); + + if (m_RemoteLeaseSet) + { + // pick random next good lease + auto ls = m_RemoteLeaseSet->GetNonExpiredLeases(); + auto sz = ls.size(); + if (sz) { - auto ls = m_RemoteLeaseSet->GetNonExpiredLeases(); - auto sz = ls.size(); - if (sz) { - auto idx = rand() % sz; - m_CurrentRemoteLease = ls[idx]; - } + auto idx = rand() % sz; + path->remoteLease = ls[idx]; } + + + } + else + { + // no remote lease set currently, bail + LogPrint(eLogWarning, "DatagramSession: no remote lease set found for ", m_RemoteIdent.ToBase32()); + return nullptr; } - path->outboundTunnel = m_CurrentOutboundTunnel; - path->remoteLease = m_CurrentRemoteLease; m_RoutingSession->SetSharedRoutingPath(path); } return path; - } void DatagramSession::HandleLeaseSetUpdated(std::shared_ptr ls) diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index 94fe422c..66e9e909 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -95,8 +95,6 @@ namespace datagram i2p::data::IdentHash m_RemoteIdent; std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_RoutingSession; - std::shared_ptr m_CurrentRemoteLease; - std::shared_ptr m_CurrentOutboundTunnel; std::vector > m_SendQueue; uint64_t m_LastUse; bool m_RequestingLS; From 0f309377ecf57c9edc23324da8368c22624fa5b1 Mon Sep 17 00:00:00 2001 From: Anton Nesterov Date: Sat, 13 Jun 2020 20:46:17 +0000 Subject: [PATCH 3751/6300] Improve AppArmor profile - give it a name - import needed abstractions - allow local additions - cleanup --- contrib/apparmor/usr.sbin.i2pd | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/contrib/apparmor/usr.sbin.i2pd b/contrib/apparmor/usr.sbin.i2pd index 6cc80609..1e47cd74 100644 --- a/contrib/apparmor/usr.sbin.i2pd +++ b/contrib/apparmor/usr.sbin.i2pd @@ -4,34 +4,22 @@ # #include -/usr/sbin/i2pd { +profile i2pd /{usr/,}sbin/i2pd { #include - - network inet dgram, - network inet stream, - network inet6 dgram, - network inet6 stream, - network netlink raw, - - /etc/gai.conf r, - /etc/host.conf r, - /etc/hosts r, - /etc/nsswitch.conf r, - /etc/resolv.conf r, - /run/resolvconf/resolv.conf r, - /run/systemd/resolve/resolv.conf r, - /run/systemd/resolve/stub-resolv.conf r, + #include + #include # path specific (feel free to modify if you have another paths) /etc/i2pd/** r, - /run/i2pd/i2pd.pid rwk, /var/lib/i2pd/** rw, /var/log/i2pd/i2pd.log w, - /var/run/i2pd/i2pd.pid rwk, - /usr/sbin/i2pd mr, - /usr/share/i2pd/** r, + /{var/,}run/i2pd/i2pd.pid rwk, + /{usr/,}sbin/i2pd mr, + @{system_share_dirs}/i2pd/** r, # user homedir (if started not by init.d or systemd) owner @{HOME}/.i2pd/ rw, owner @{HOME}/.i2pd/** rwk, + + #include if exists } From 69194118dfe3c6e0201efc3db6671bbf1d81e32e Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 13 Jun 2020 21:24:16 -0400 Subject: [PATCH 3752/6300] generate random padding length in bulk --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 9 +++++++-- libi2pd/ECIESX25519AEADRatchetSession.h | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index ad91382f..87fd13ed 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -96,6 +96,7 @@ namespace garlic ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet): GarlicRoutingSession (owner, attachLeaseSet) { + RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0; ResetKeys (); } @@ -745,8 +746,12 @@ namespace garlic int delta = (int)ECIESX25519_OPTIMAL_PAYLOAD_SIZE - (int)payloadLen; if (delta < 0 || delta > 3) // don't create padding if we are close to optimal size { - RAND_bytes (&paddingSize, 1); - paddingSize &= 0x0F; // 0 - 15 + paddingSize = m_PaddingSizes[m_NextPaddingSize++] & 0x0F; // 0 - 15 + if (m_NextPaddingSize >= 32) + { + RAND_bytes (m_PaddingSizes, 32); + m_NextPaddingSize = 0; + } if (delta > 3) { delta -= 3; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index d121842d..727c48ac 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -188,7 +188,8 @@ namespace garlic std::list > m_AckRequests; // (tagsetid, index) bool m_SendReverseKey = false, m_SendForwardKey = false; std::unique_ptr m_NextReceiveRatchet, m_NextSendRatchet; - + uint8_t m_PaddingSizes[32], m_NextPaddingSize; + public: // for HTTP only From 1e609acb035dba27116d3f3b4f3a63740aa52e3d Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 14 Jun 2020 11:16:08 -0400 Subject: [PATCH 3753/6300] keep sending through first successive routing path --- libi2pd/Datagram.cpp | 20 +++++++++++++++++--- libi2pd/Datagram.h | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index ad679cc0..844611d3 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -296,7 +296,23 @@ namespace datagram } if (!m_RoutingSession || !m_RoutingSession->GetOwner ()) - m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); + { + bool found = false; + for (auto& it: m_PendingRoutingSessions) + if (it->GetOwner ()) // found established session + { + m_RoutingSession = it; + m_PendingRoutingSessions.clear (); + found = true; + break; + } + if (!found) + { + m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); + if (!m_RoutingSession->GetOwner ()) + m_PendingRoutingSessions.push_back (m_RoutingSession); + } + } auto path = m_RoutingSession->GetSharedRoutingPath(); if (path) @@ -345,8 +361,6 @@ namespace datagram auto idx = rand() % sz; path->remoteLease = ls[idx]; } - - } else { diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index 66e9e909..5dd6c8b6 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -95,6 +95,7 @@ namespace datagram i2p::data::IdentHash m_RemoteIdent; std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_RoutingSession; + std::vector > m_PendingRoutingSessions; std::vector > m_SendQueue; uint64_t m_LastUse; bool m_RequestingLS; From 8d903a09e2a20d790fe287ca66eb4b4c458c8efa Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 14 Jun 2020 22:18:41 +0300 Subject: [PATCH 3754/6300] [Docker] drop boost-python2 --- contrib/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index 6b6250d0..be3feea7 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -36,7 +36,7 @@ RUN apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib && cd /usr/local/bin \ && strip i2pd \ && rm -fr /tmp/build && apk --no-cache --purge del build-dependendencies build-base fortify-headers boost-dev zlib-dev openssl-dev \ - boost-python3 python3 gdbm boost-unit_test_framework boost-python2 linux-headers boost-prg_exec_monitor \ + boost-python3 python3 gdbm boost-unit_test_framework linux-headers boost-prg_exec_monitor \ boost-serialization boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre2 \ libtool g++ gcc From 70e4cbc0236d253b68d6747c0a3c046f9c095384 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 15 Jun 2020 20:10:47 -0400 Subject: [PATCH 3755/6300] differentiate UDP server sessions by port --- libi2pd_client/I2PTunnel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index d05a97b7..95f56873 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -611,7 +611,7 @@ namespace client void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { - if (!m_LastSession || m_LastSession->Identity.GetLL()[0] != from.GetIdentHash ().GetLL()[0]) + if (!m_LastSession || m_LastSession->Identity.GetLL()[0] != from.GetIdentHash ().GetLL()[0] || fromPort != m_LastSession->RemotePort) { std::lock_guard lock(m_SessionsMutex); m_LastSession = ObtainUDPSession(from, toPort, fromPort); @@ -659,7 +659,7 @@ namespace client auto ih = from.GetIdentHash(); for (auto & s : m_Sessions ) { - if (s->Identity.GetLL()[0] == ih.GetLL()[0]) + if (s->Identity.GetLL()[0] == ih.GetLL()[0] && remotePort == s->RemotePort) { /** found existing session */ LogPrint(eLogDebug, "UDPServer: found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32()); From 31494267e5856b0f685f323e6cfcbbd66dde16dd Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Jun 2020 14:24:25 -0400 Subject: [PATCH 3756/6300] fixed datagram idle crash --- libi2pd/Datagram.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 844611d3..b9938043 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -318,8 +318,12 @@ namespace datagram if (path) { if (path->outboundTunnel && !path->outboundTunnel->IsEstablished ()) + { // bad outbound tunnel, switch outbound tunnel path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(path->outboundTunnel); + if (!path->outboundTunnel) + m_RoutingSession->SetSharedRoutingPath (nullptr); + } if (path->remoteLease && path->remoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) { @@ -338,11 +342,14 @@ namespace datagram path->remoteLease = ls[idx]; } else - path->remoteLease = nullptr; + m_RoutingSession->SetSharedRoutingPath (nullptr); } else + { // no remote lease set? LogPrint(eLogWarning, "DatagramSession: no cached remote lease set for ", m_RemoteIdent.ToBase32()); + m_RoutingSession->SetSharedRoutingPath (nullptr); + } } } else @@ -350,7 +357,8 @@ namespace datagram // no current path, make one path = std::make_shared(); path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(); - + if (!path->outboundTunnel) return nullptr; + if (m_RemoteLeaseSet) { // pick random next good lease @@ -361,6 +369,8 @@ namespace datagram auto idx = rand() % sz; path->remoteLease = ls[idx]; } + else + return nullptr; } else { From 951ec567c7769e2f4b8adf1c5876052260821e89 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 17 Jun 2020 21:06:35 -0400 Subject: [PATCH 3757/6300] don't try to connect though teminated local destination --- libi2pd_client/SAM.cpp | 23 +++++++++++++++-------- libi2pd_client/SAM.h | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 4723d168..63b9c7ed 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -494,7 +494,7 @@ namespace client context.GetAddressBook().InsertFullAddress(dest); auto leaseSet = session->localDestination->FindLeaseSet(dest->GetIdentHash()); if (leaseSet) - Connect(leaseSet); + Connect(leaseSet, session); else { session->localDestination->RequestDestination(dest->GetIdentHash(), @@ -509,18 +509,25 @@ namespace client SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } - void SAMSocket::Connect (std::shared_ptr remote) + void SAMSocket::Connect (std::shared_ptr remote, std::shared_ptr session) { - auto session = m_Owner.FindSession(m_ID); - if(session) + if (!session) session = m_Owner.FindSession(m_ID); + if (session) { m_SocketType = eSAMSocketTypeStream; m_Stream = session->localDestination->CreateStream (remote); - m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send - m_BufferOffset = 0; - I2PReceive (); - SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); + if (m_Stream) + { + m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send + m_BufferOffset = 0; + I2PReceive (); + SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); + } + else + SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } + else + SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } void SAMSocket::HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet) diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index ceda5253..7b1702f5 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -134,7 +134,7 @@ namespace client size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0 void ExtractParams (char * buf, std::map& params); - void Connect (std::shared_ptr remote); + void Connect (std::shared_ptr remote, std::shared_ptr session = nullptr); void HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet); void SendNamingLookupReply (std::shared_ptr identity); void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, std::string name); From a0b35ebd3ef68dd4d4ccacf6cef218000e618c2f Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 22 Jun 2020 22:32:18 -0400 Subject: [PATCH 3758/6300] mark NTCP2 unreachable routers --- libi2pd/NTCP2.cpp | 3 ++- libi2pd/Transports.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 8b44a795..60eab17c 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1305,7 +1305,8 @@ namespace transport if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogInfo, "NTCP2: Not connected in ", timeout, " seconds"); - //i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); + if (conn->GetRemoteIdentity ()) + i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); conn->Terminate (); } }); diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 7157306d..6f163e5f 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -440,7 +440,7 @@ namespace transport { // NTCP2 have priority over NTCP auto address = peer.router->GetNTCP2Address (true, !context.SupportsV6 ()); // published only - if (address) + if (address && !peer.router->IsUnreachable ()) { auto s = std::make_shared (*m_NTCP2Server, peer.router); From 61e9c31f0dc3a3bda295fd4fdb0f80a3ac099ca9 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 24 Jun 2020 11:29:54 -0400 Subject: [PATCH 3759/6300] don't hold RouterInfo after successive connect --- libi2pd/Transports.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 6f163e5f..7da0f63b 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -431,6 +431,8 @@ namespace transport bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer) { + if (!peer.router) // reconnect + peer.router = netdb.FindRouter (ident); // try to get new one from netdb if (peer.router) // we have RI already { if (!peer.numAttempts) // NTCP2 @@ -638,6 +640,7 @@ namespace transport auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { + it->second.router = nullptr; // we don't need RouterInfo after successive connect bool sendDatabaseStore = true; if (it->second.delayedMessages.size () > 0) { From df9965e1295de377516cb3a15aa68ef1a86c71f6 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 29 Jun 2020 18:19:31 -0400 Subject: [PATCH 3760/6300] use unordered_map for peers --- libi2pd/Transports.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index cdfbfccf..95c7aedb 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -157,7 +157,7 @@ namespace transport SSUServer * m_SSUServer; NTCP2Server * m_NTCP2Server; mutable std::mutex m_PeersMutex; - std::map m_Peers; + std::unordered_map m_Peers; DHKeysPairSupplier m_DHKeysPairSupplier; From 1f31fdc257ec0a0f44fd263d0fb0ed9fa14464a8 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 29 Jun 2020 20:02:09 -0400 Subject: [PATCH 3761/6300] pre-calculate ephemeral keys for x25519 --- libi2pd/NTCP2.cpp | 8 +++---- libi2pd/NTCP2.h | 4 ++-- libi2pd/Transports.cpp | 52 +++++++++++++++++++++++++++++------------- libi2pd/Transports.h | 23 ++++++++++++------- 4 files changed, 57 insertions(+), 30 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 60eab17c..f1f7e59f 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -83,7 +83,7 @@ namespace transport void NTCP2Establisher::KDF1Alice () { - KeyDerivationFunction1 (m_RemoteStaticKey, m_EphemeralKeys, m_RemoteStaticKey, GetPub ()); + KeyDerivationFunction1 (m_RemoteStaticKey, *m_EphemeralKeys, m_RemoteStaticKey, GetPub ()); } void NTCP2Establisher::KDF1Bob () @@ -102,7 +102,7 @@ namespace transport // x25519 between remote pub and ephemaral priv uint8_t inputKeyMaterial[32]; - m_EphemeralKeys.Agree (GetRemotePub (), inputKeyMaterial); + m_EphemeralKeys->Agree (GetRemotePub (), inputKeyMaterial); MixKey (inputKeyMaterial); } @@ -127,13 +127,13 @@ namespace transport void NTCP2Establisher::KDF3Bob () { uint8_t inputKeyMaterial[32]; - m_EphemeralKeys.Agree (m_RemoteStaticKey, inputKeyMaterial); + m_EphemeralKeys->Agree (m_RemoteStaticKey, inputKeyMaterial); MixKey (inputKeyMaterial); } void NTCP2Establisher::CreateEphemeralKey () { - m_EphemeralKeys.GenerateKeys (); + m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); } void NTCP2Establisher::CreateSessionRequestMessage () diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index 8b8c6acb..df72fed0 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -79,7 +79,7 @@ namespace transport NTCP2Establisher (); ~NTCP2Establisher (); - const uint8_t * GetPub () const { return m_EphemeralKeys.GetPublicKey (); }; + const uint8_t * GetPub () const { return m_EphemeralKeys->GetPublicKey (); }; const uint8_t * GetRemotePub () const { return m_RemoteEphemeralPublicKey; }; // Y for Alice and X for Bob uint8_t * GetRemotePub () { return m_RemoteEphemeralPublicKey; }; // to set @@ -110,7 +110,7 @@ namespace transport bool ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce); bool ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf); - i2p::crypto::X25519Keys m_EphemeralKeys; + std::shared_ptr m_EphemeralKeys; uint8_t m_RemoteEphemeralPublicKey[32]; // x25519 uint8_t m_RemoteStaticKey[32], m_IV[16], m_H[32] /*h*/, m_CK[64] /* [ck, k]*/; i2p::data::IdentHash m_RemoteIdentHash; diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 7da0f63b..edfe33fa 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -21,23 +21,27 @@ namespace i2p { namespace transport { - DHKeysPairSupplier::DHKeysPairSupplier (int size): + template + EphemeralKeysSupplier::EphemeralKeysSupplier (int size): m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr) { } - DHKeysPairSupplier::~DHKeysPairSupplier () + template + EphemeralKeysSupplier::~EphemeralKeysSupplier () { Stop (); } - void DHKeysPairSupplier::Start () + template + void EphemeralKeysSupplier::Start () { m_IsRunning = true; - m_Thread = new std::thread (std::bind (&DHKeysPairSupplier::Run, this)); + m_Thread = new std::thread (std::bind (&EphemeralKeysSupplier::Run, this)); } - void DHKeysPairSupplier::Stop () + template + void EphemeralKeysSupplier::Stop () { { std::unique_lock l(m_AcquiredMutex); @@ -52,19 +56,20 @@ namespace transport } } - void DHKeysPairSupplier::Run () + template + void EphemeralKeysSupplier::Run () { while (m_IsRunning) { int num, total = 0; while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < 10) { - CreateDHKeysPairs (num); + CreateEphemeralKeys (num); total += num; } if (total >= 10) { - LogPrint (eLogWarning, "Transports: ", total, " DH keys generated at the time"); + LogPrint (eLogWarning, "Transports: ", total, " ephemeral keys generated at the time"); std::this_thread::sleep_for (std::chrono::seconds(1)); // take a break } else @@ -76,24 +81,26 @@ namespace transport } } - void DHKeysPairSupplier::CreateDHKeysPairs (int num) + template + void EphemeralKeysSupplier::CreateEphemeralKeys (int num) { if (num > 0) { for (int i = 0; i < num; i++) { - auto pair = std::make_shared (); + auto pair = std::make_shared (); pair->GenerateKeys (); - std::unique_lock l(m_AcquiredMutex); + std::unique_lock l(m_AcquiredMutex); m_Queue.push (pair); } } } - std::shared_ptr DHKeysPairSupplier::Acquire () + template + std::shared_ptr EphemeralKeysSupplier::Acquire () { { - std::unique_lock l(m_AcquiredMutex); + std::unique_lock l(m_AcquiredMutex); if (!m_Queue.empty ()) { auto pair = m_Queue.front (); @@ -103,12 +110,13 @@ namespace transport } } // queue is empty, create new - auto pair = std::make_shared (); + auto pair = std::make_shared (); pair->GenerateKeys (); return pair; } - void DHKeysPairSupplier::Return (std::shared_ptr pair) + template + void EphemeralKeysSupplier::Return (std::shared_ptr pair) { if (pair) { @@ -126,7 +134,7 @@ namespace transport m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_Thread (nullptr), m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), m_NTCPServer (nullptr), m_SSUServer (nullptr), m_NTCP2Server (nullptr), - m_DHKeysPairSupplier (5), // 5 pre-generated keys + m_DHKeysPairSupplier (5), m_X25519KeysPairSupplier (5), // 5 pre-generated keys m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0), m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0), m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), @@ -158,6 +166,7 @@ namespace transport i2p::config::GetOption("nat", m_IsNAT); m_DHKeysPairSupplier.Start (); + m_X25519KeysPairSupplier.Start (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); std::string ntcpproxy; i2p::config::GetOption("ntcpproxy", ntcpproxy); @@ -312,6 +321,7 @@ namespace transport } m_DHKeysPairSupplier.Stop (); + m_X25519KeysPairSupplier.Stop (); m_IsRunning = false; if (m_Service) m_Service->stop (); if (m_Thread) @@ -630,6 +640,16 @@ namespace transport m_DHKeysPairSupplier.Return (pair); } + std::shared_ptr Transports::GetNextX25519KeysPair () + { + return m_X25519KeysPairSupplier.Acquire (); + } + + void Transports::ReuseX25519KeysPair (std::shared_ptr pair) + { + m_X25519KeysPairSupplier.Return (pair); + } + void Transports::PeerConnected (std::shared_ptr session) { m_Service->post([session, this]() diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index 95c7aedb..48eee4b4 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -32,33 +32,37 @@ namespace i2p { namespace transport { - class DHKeysPairSupplier + template + class EphemeralKeysSupplier { + // called from this file only, so implementation is in Transports.cpp public: - DHKeysPairSupplier (int size); - ~DHKeysPairSupplier (); + EphemeralKeysSupplier (int size); + ~EphemeralKeysSupplier (); void Start (); void Stop (); - std::shared_ptr Acquire (); - void Return (std::shared_ptr pair); + std::shared_ptr Acquire (); + void Return (std::shared_ptr pair); private: void Run (); - void CreateDHKeysPairs (int num); + void CreateEphemeralKeys (int num); private: const int m_QueueSize; - std::queue > m_Queue; + std::queue > m_Queue; bool m_IsRunning; std::thread * m_Thread; std::condition_variable m_Acquired; std::mutex m_AcquiredMutex; }; - + typedef EphemeralKeysSupplier DHKeysPairSupplier; + typedef EphemeralKeysSupplier X25519KeysPairSupplier; + struct Peer { int numAttempts; @@ -97,6 +101,8 @@ namespace transport boost::asio::io_service& GetService () { return *m_Service; }; std::shared_ptr GetNextDHKeysPair (); void ReuseDHKeysPair (std::shared_ptr pair); + std::shared_ptr GetNextX25519KeysPair (); + void ReuseX25519KeysPair (std::shared_ptr pair); void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); void SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs); @@ -160,6 +166,7 @@ namespace transport std::unordered_map m_Peers; DHKeysPairSupplier m_DHKeysPairSupplier; + X25519KeysPairSupplier m_X25519KeysPairSupplier; std::atomic m_TotalSentBytes, m_TotalReceivedBytes, m_TotalTransitTransmittedBytes; uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; // bytes per second From 5f1e66d64b2c92af5082f9b29b5e6d52ad4130d4 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 30 Jun 2020 13:00:41 -0400 Subject: [PATCH 3762/6300] use pre-calculated x25519 ephemeral keys for ratchets --- libi2pd/Crypto.h | 4 ++ libi2pd/ECIESX25519AEADRatchetSession.cpp | 59 +++++++++++++++++------ libi2pd/ECIESX25519AEADRatchetSession.h | 4 +- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 9ca3163f..ab84e56a 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -91,6 +91,9 @@ namespace crypto void SetPrivateKey (const uint8_t * priv, bool calculatePublic = false); void Agree (const uint8_t * pub, uint8_t * shared); + bool IsElligatorIneligible () const { return m_IsElligatorIneligible; } + void SetElligatorIneligible () { m_IsElligatorIneligible = true; } + private: uint8_t m_PublicKey[32]; @@ -101,6 +104,7 @@ namespace crypto BN_CTX * m_Ctx; uint8_t m_PrivateKey[32]; #endif + bool m_IsElligatorIneligible = false; // true if definitly ineligible }; // ElGamal diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 87fd13ed..8e0e743d 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -16,6 +16,7 @@ #include "Timestamp.h" #include "Tunnel.h" #include "TunnelPool.h" +#include "Transports.h" #include "ECIESX25519AEADRatchetSession.h" namespace i2p @@ -137,12 +138,38 @@ namespace garlic bool ECIESX25519AEADRatchetSession::GenerateEphemeralKeysAndEncode (uint8_t * buf) { + bool ineligible = false; + while (!ineligible) + { + m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + ineligible = m_EphemeralKeys->IsElligatorIneligible (); + if (!ineligible) // we haven't tried it yet + { + if (i2p::crypto::GetElligator ()->Encode (m_EphemeralKeys->GetPublicKey (), buf)) + return true; // success + // otherwise return back + m_EphemeralKeys->SetElligatorIneligible (); + i2p::transport::transports.ReuseX25519KeysPair (m_EphemeralKeys); + } + else + i2p::transport::transports.ReuseX25519KeysPair (m_EphemeralKeys); + } + // we still didn't find elligator eligible pair for (int i = 0; i < 10; i++) { - m_EphemeralKeys.GenerateKeys (); - if (i2p::crypto::GetElligator ()->Encode (m_EphemeralKeys.GetPublicKey (), buf)) + // create new + m_EphemeralKeys = std::make_shared(); + m_EphemeralKeys->GenerateKeys (); + if (i2p::crypto::GetElligator ()->Encode (m_EphemeralKeys->GetPublicKey (), buf)) return true; // success + else + { + // let NTCP2 use it + m_EphemeralKeys->SetElligatorIneligible (); + i2p::transport::transports.ReuseX25519KeysPair (m_EphemeralKeys); + } } + LogPrint (eLogError, "Garlic: Can't generate elligator eligible x25519 keys"); return false; } @@ -294,7 +321,7 @@ namespace garlic if (flag & ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG) memcpy (m_NextSendRatchet->remote, buf, 32); uint8_t sharedSecret[32], tagsetKey[32]; - m_NextSendRatchet->key.Agree (m_NextSendRatchet->remote, sharedSecret); + m_NextSendRatchet->key->Agree (m_NextSendRatchet->remote, sharedSecret); i2p::crypto::HKDF (sharedSecret, nullptr, 0, "XDHRatchetTagSet", tagsetKey, 32); // tagsetKey = HKDF(sharedSecret, ZEROLEN, "XDHRatchetTagSet", 32) auto newTagset = std::make_shared (shared_from_this ()); newTagset->SetTagSetID (1 + m_NextSendRatchet->keyID + keyID); @@ -326,7 +353,7 @@ namespace garlic int tagsetID = 2*keyID; if (newKey) { - m_NextReceiveRatchet->key.GenerateKeys (); + m_NextReceiveRatchet->key = i2p::transport::transports.GetNextX25519KeysPair (); m_NextReceiveRatchet->newKey = true; tagsetID++; } @@ -336,7 +363,7 @@ namespace garlic memcpy (m_NextReceiveRatchet->remote, buf, 32); uint8_t sharedSecret[32], tagsetKey[32]; - m_NextReceiveRatchet->key.Agree (m_NextReceiveRatchet->remote, sharedSecret); + m_NextReceiveRatchet->key->Agree (m_NextReceiveRatchet->remote, sharedSecret); i2p::crypto::HKDF (sharedSecret, nullptr, 0, "XDHRatchetTagSet", tagsetKey, 32); // tagsetKey = HKDF(sharedSecret, ZEROLEN, "XDHRatchetTagSet", 32) auto newTagset = std::make_shared(shared_from_this ()); newTagset->SetTagSetID (tagsetID); @@ -364,7 +391,7 @@ namespace garlic else m_NextSendRatchet.reset (new DHRatchet ()); if (m_NextSendRatchet->newKey) - m_NextSendRatchet->key.GenerateKeys (); + m_NextSendRatchet->key->GenerateKeys (); m_SendForwardKey = true; LogPrint (eLogDebug, "Garlic: new send ratchet ", m_NextSendRatchet->newKey ? "new" : "old", " key ", m_NextSendRatchet->keyID, " created"); @@ -384,9 +411,9 @@ namespace garlic // KDF1 MixHash (m_RemoteStaticKey, 32); // h = SHA256(h || bpk) - MixHash (m_EphemeralKeys.GetPublicKey (), 32); // h = SHA256(h || aepk) + MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; - m_EphemeralKeys.Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) + m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) // encrypt static key section uint8_t nonce[12]; @@ -435,11 +462,11 @@ namespace garlic offset += 32; // KDF for Reply Key Section MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) - MixHash (m_EphemeralKeys.GetPublicKey (), 32); // h = SHA256(h || bepk) + MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk) uint8_t sharedSecret[32]; - m_EphemeralKeys.Agree (m_Aepk, sharedSecret); // sharedSecret = x25519(besk, aepk) + m_EphemeralKeys->Agree (m_Aepk, sharedSecret); // sharedSecret = x25519(besk, aepk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) - m_EphemeralKeys.Agree (m_RemoteStaticKey, sharedSecret); // sharedSecret = x25519(besk, apk) + m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // sharedSecret = x25519(besk, apk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) uint8_t nonce[12]; CreateNonce (0, nonce); @@ -485,7 +512,7 @@ namespace garlic // recalculate h with new tag memcpy (m_H, m_NSRH, 32); MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) - MixHash (m_EphemeralKeys.GetPublicKey (), 32); // h = SHA256(h || bepk) + MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk) uint8_t nonce[12]; CreateNonce (0, nonce); if (!i2p::crypto::AEADChaCha20Poly1305 (nonce /* can be anything */, 0, m_H, 32, m_CK + 32, nonce, out + 40, 16, true)) // encrypt, ciphertext = ENCRYPT(k, n, ZEROLEN, ad) @@ -524,7 +551,7 @@ namespace garlic if (m_State == eSessionStateNewSessionSent) { // only fist time, we assume ephemeral keys the same - m_EphemeralKeys.Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) + m_EphemeralKeys->Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bepk) i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) @@ -565,6 +592,7 @@ namespace garlic if (m_State == eSessionStateNewSessionSent) { m_State = eSessionStateEstablished; + m_EphemeralKeys = nullptr; m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); } @@ -643,6 +671,7 @@ namespace garlic case eSessionStateNewSessionReplySent: m_State = eSessionStateEstablished; m_NSRSendTagset = nullptr; + m_EphemeralKeys = nullptr; #if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; #endif @@ -813,7 +842,7 @@ namespace garlic htobe16buf (v.data () + offset, keyID); offset += 2; // keyid if (m_NextReceiveRatchet->newKey) { - memcpy (v.data () + offset, m_NextReceiveRatchet->key.GetPublicKey (), 32); + memcpy (v.data () + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); offset += 32; // public key } m_SendReverseKey = false; @@ -828,7 +857,7 @@ namespace garlic htobe16buf (v.data () + offset, m_NextSendRatchet->keyID); offset += 2; // keyid if (m_NextSendRatchet->newKey) { - memcpy (v.data () + offset, m_NextSendRatchet->key.GetPublicKey (), 32); + memcpy (v.data () + offset, m_NextSendRatchet->key->GetPublicKey (), 32); offset += 32; // public key } } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 727c48ac..2dccd865 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -122,7 +122,7 @@ namespace garlic struct DHRatchet { int keyID = 0; - i2p::crypto::X25519Keys key; + std::shared_ptr key; uint8_t remote[32]; // last remote public key bool newKey = true; }; @@ -180,7 +180,7 @@ namespace garlic uint8_t m_H[32], m_CK[64] /* [chainkey, key] */, m_RemoteStaticKey[32]; uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only - i2p::crypto::X25519Keys m_EphemeralKeys; + std::shared_ptr m_EphemeralKeys; SessionState m_State = eSessionStateNew; uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0; // incoming std::shared_ptr m_SendTagset, m_NSRSendTagset; From 6f2e6ed8873d68f8bdd9e626ed7ac5912beeffd0 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 30 Jun 2020 15:05:17 -0400 Subject: [PATCH 3763/6300] key for next send ratchet --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 8e0e743d..d403c9bd 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -391,7 +391,7 @@ namespace garlic else m_NextSendRatchet.reset (new DHRatchet ()); if (m_NextSendRatchet->newKey) - m_NextSendRatchet->key->GenerateKeys (); + m_NextSendRatchet->key = i2p::transport::transports.GetNextX25519KeysPair (); m_SendForwardKey = true; LogPrint (eLogDebug, "Garlic: new send ratchet ", m_NextSendRatchet->newKey ? "new" : "old", " key ", m_NextSendRatchet->keyID, " created"); From 6f17624742b4af654b1c4c604172beb7b2c48787 Mon Sep 17 00:00:00 2001 From: user Date: Sun, 5 Jul 2020 12:53:15 +0800 Subject: [PATCH 3764/6300] Android.mk : openssl-1.1.1d-clang instead of openssl-1.1.1a-clang --- android/jni/Android.mk | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 2376509e..07dc7080 100755 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -53,15 +53,15 @@ include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := crypto -LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/$(TARGET_ARCH_ABI)/lib/libcrypto.a -LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/include +LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1d-clang/$(TARGET_ARCH_ABI)/lib/libcrypto.a +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1d-clang/include include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := ssl -LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/$(TARGET_ARCH_ABI)/lib/libssl.a -LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1a-clang/include +LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1d-clang/$(TARGET_ARCH_ABI)/lib/libssl.a +LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1d-clang/include LOCAL_STATIC_LIBRARIES := crypto include $(PREBUILT_STATIC_LIBRARY) From e15b2cc5d6531037850b855b36b025ad371b962b Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 15 Jun 2020 09:01:17 +0300 Subject: [PATCH 3765/6300] [webconsole] rework lists with tunnels, transit, etc Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 166 ++++++++++++++++++++++++------------------ 1 file changed, 95 insertions(+), 71 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 41f198c8..c387a402 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -67,35 +67,36 @@ namespace http { " color: initial; padding: 0 5px; border: 1px solid #894C84; }\r\n" " .header { font-size: 2.5em; text-align: center; margin: 1em 0; color: #894C84; }\r\n" " .wrapper { margin: 0 auto; padding: 1em; max-width: 58em; }\r\n" - " .menu { float: left; } .menu a { display: block; padding: 2px; }\r\n" + " .menu { float: left; } .menu a, .commands a { display: block; }\r\n" + " .listitem { display: block; font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n" " .content { float: left; font-size: 1em; margin-left: 4em; max-width: 46em; overflow: auto; }\r\n" - " .tunnel.established { color: #56B734; }\r\n" - " .tunnel.expiring { color: #D3AE3F; }\r\n" - " .tunnel.failed { color: #D33F3F; }\r\n" - " .tunnel.building { color: #434343; }\r\n" + " .tunnel.established { color: #56B734; } .tunnel.expiring { color: #D3AE3F; }\r\n" + " .tunnel.failed { color: #D33F3F; } .tunnel.building { color: #434343; }\r\n" " caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n" " table { display: table; border-collapse: collapse; text-align: center; }\r\n" " table.extaddr { text-align: left; } table.services { width: 100%; }\r\n" + " textarea { word-break: break-all; }\r\n" " .streamdest { width: 120px; max-width: 240px; overflow: hidden; text-overflow: ellipsis;}\r\n" " .slide div.slidecontent, .slide [type=\"checkbox\"] { display: none; }\r\n" " .slide [type=\"checkbox\"]:checked ~ div.slidecontent { display: block; margin-top: 0; padding: 0; }\r\n" " .disabled:after { color: #D33F3F; content: \"Disabled\" }\r\n" " .enabled:after { color: #56B734; content: \"Enabled\" }\r\n" " @media screen and (max-width: 980px) {\r\n" /* adaptive style */ - " .menu { width: 100%; display: block; float: none; position: unset; font-size: 24px;\r\n" + " .menu { width: 100%; display: block; float: none; position: unset; font-size: 16px;\r\n" + " text-align: center; }\r\n" + " .menu a, .commands a { padding: 2px; }\r\n" + " .content { float: none; margin: 0; margin-top: 16px; max-width: 100%; width: 100%;\r\n" " text-align: center; }\r\n" - " .content { float: none; margin: 0; margin-top: 16px; max-width: 100%; width: 100%; font-size: 1.2em;\r\n" - " text-align: center; line-height: 28px; }\r\n" " a, .slide label { /* margin-right: 10px; */ display: block; /* font-size: 18px; */ }\r\n" - " .header { margin: 0.5em 0; } small {display: block}\r\n" + " .header { margin: unset; font-size: 1.5em; } small {display: block}\r\n" " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n" " color: initial; margin-top: 10px; padding: 6px; border: 1px solid #894c84; width: -webkit-fill-available; }\r\n" - " input { width: 35%; height: 50px; text-align: center; /* margin-top: 15px; */ padding: 5px;\r\n" - " border: 2px solid #ccc; -webkit-border-radius: 5px; border-radius: 5px; font-size: 35px; }\r\n" + " input { width: 35%; text-align: center; padding: 5px;\r\n" + " border: 2px solid #ccc; -webkit-border-radius: 5px; border-radius: 5px; font-size: 24px; }\r\n" " textarea { width: -webkit-fill-available; height: auto; padding:5px; border:2px solid #ccc;\r\n" - " -webkit-border-radius: 5px; border-radius: 5px; font-size: 22px; }\r\n" + " -webkit-border-radius: 5px; border-radius: 5px; font-size: 12px; }\r\n" " button[type=submit] { padding: 5px 15px; background: #ccc; border: 0 none; cursor: pointer;\r\n" - " -webkit-border-radius: 5px; border-radius: 5px; position: relative; height: 50px; display: -webkit-inline-box; margin-top: 25px; }\r\n" + " -webkit-border-radius: 5px; border-radius: 5px; position: relative; height: 36px; display: -webkit-inline-box; margin-top: 10px; }\r\n" " }\r\n" /* adaptive style */ "\r\n"; @@ -228,7 +229,7 @@ namespace http { static void ShowPageTail (std::stringstream& s) { s << - "
    \r\n" + "
    \r\n
    \r\n" "\r\n" "\r\n"; } @@ -358,18 +359,19 @@ namespace http { void ShowLocalDestinations (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "Local Destinations:
    \r\n
    \r\n"; + s << "Local Destinations:
    \r\n
    \r\n"; for (auto& it: i2p::client::context.GetDestinations ()) { auto ident = it.second->GetIdentHash (); - s << ""; - s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
    \r\n" << std::endl; + s << "\r\n" << std::endl; } + s << "
    \r\n"; auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer && !(i2cpServer->GetSessions ().empty ())) { - s << "
    I2CP Local Destinations:
    \r\n
    \r\n"; + s << "
    I2CP Local Destinations:
    \r\n
    \r\n"; for (auto& it: i2cpServer->GetSessions ()) { auto dest = it.second->GetDestination (); @@ -377,10 +379,11 @@ namespace http { { auto ident = dest->GetIdentHash (); auto& name = dest->GetNickname (); - s << "[ "; - s << name << " ] ⇔ " << i2p::client::context.GetAddressBook ().ToAddress(ident) <<"
    \r\n" << std::endl; + s << "
    [ "; + s << name << " ] ⇔ " << i2p::client::context.GetAddressBook ().ToAddress(ident) <<"
    \r\n" << std::endl; } } + s << "
    \r\n"; } } @@ -581,54 +584,59 @@ namespace http { void ShowTunnels (std::stringstream& s) { - s << "Tunnels:
    \r\n
    \r\n"; - s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
    \r\n"; + s << "Tunnels:
    \r\n"; + s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
    \r\n
    \r\n"; auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); - s << "Inbound tunnels:
    \r\n"; + s << "Inbound tunnels:
    \r\n
    \r\n"; for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { + s << "
    "; it->Print(s); if(it->LatencyIsKnown()) s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); + s << "
    \r\n"; } - s << "
    \r\n"; - s << "Outbound tunnels:
    \r\n"; + s << "
    \r\n
    \r\n"; + s << "Outbound tunnels:
    \r\n
    \r\n"; for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { + s << "
    "; it->Print(s); if(it->LatencyIsKnown()) s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); + s << "
    \r\n"; } - s << "
    \r\n"; + s << "
    \r\n"; } static void ShowCommands (std::stringstream& s, uint32_t token) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); /* commands */ - s << "Router Commands
    \r\n
    \r\n"; - s << " Run peer test
    \r\n"; + s << "Router Commands
    \r\n
    \r\n
    \r\n"; + s << " Run peer test\r\n"; //s << " Reload config
    \r\n"; if (i2p::context.AcceptsTunnels ()) - s << " Decline transit tunnels
    \r\n"; + s << " Decline transit tunnels\r\n"; else - s << " Accept transit tunnels
    \r\n"; + s << " Accept transit tunnels\r\n"; #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) if (Daemon.gracefulShutdownInterval) - s << " Cancel graceful shutdown
    "; + s << " Cancel graceful shutdown\r\n"; else s << " Start graceful shutdown
    \r\n"; #elif defined(WIN32_APP) if (i2p::util::DaemonWin32::Instance().isGraceful) - s << " Cancel graceful shutdown
    "; + s << " Cancel graceful shutdown\r\n"; else - s << " Graceful shutdown
    \r\n"; + s << " Graceful shutdown\r\n"; #endif - s << " Force shutdown
    \r\n
    \r\n"; + s << " Force shutdown\r\n"; + s << "
    "; - s << "Note: any action done here are not persistent and not changes your config files.\r\n
    \r\n"; + s << "
    \r\nNote: any action done here are not persistent and not changes your config files.\r\n
    \r\n"; s << "Logging level
    \r\n"; s << " none \r\n"; @@ -651,17 +659,19 @@ namespace http { { if(i2p::tunnel::tunnels.CountTransitTunnels()) { - s << "Transit tunnels:
    \r\n
    \r\n"; + s << "Transit tunnels:
    \r\n
    \r\n"; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { + s << "
    \r\n"; if (std::dynamic_pointer_cast(it)) s << it->GetTunnelID () << " ⇒ "; else if (std::dynamic_pointer_cast(it)) s << " ⇒ " << it->GetTunnelID (); else s << " ⇒ " << it->GetTunnelID () << " ⇒ "; - s << " " << it->GetNumTransmittedBytes () << "
    \r\n"; + s << " " << it->GetNumTransmittedBytes () << "
    \r\n"; } + s << "
    \r\n"; } else { @@ -677,43 +687,44 @@ namespace http { { if (it.second && it.second->IsEstablished () && !it.second->GetSocket ().remote_endpoint ().address ().is_v6 ()) { - // incoming connection doesn't have remote RI + tmp_s << "
    \r\n"; if (it.second->IsOutgoing ()) tmp_s << " ⇒ "; tmp_s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " << it.second->GetSocket ().remote_endpoint().address ().to_string (); if (!it.second->IsOutgoing ()) tmp_s << " ⇒ "; tmp_s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - tmp_s << "
    \r\n" << std::endl; + tmp_s << "
    \r\n" << std::endl; cnt++; } if (it.second && it.second->IsEstablished () && it.second->GetSocket ().remote_endpoint ().address ().is_v6 ()) { + tmp_s6 << "
    \r\n"; if (it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; tmp_s6 << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " << "[" << it.second->GetSocket ().remote_endpoint().address ().to_string () << "]"; if (!it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; tmp_s6 << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; - tmp_s6 << "
    \r\n" << std::endl; + tmp_s6 << "
    \r\n" << std::endl; cnt6++; } } if (!tmp_s.str ().empty ()) { s << "
    \r\n\r\n
    " + << " ( " << cnt << " )\r\n\r\n
    " << tmp_s.str () << "
    \r\n
    \r\n"; } if (!tmp_s6.str ().empty ()) { s << "
    \r\n\r\n
    " + << "v6 ( " << cnt6 << " )\r\n\r\n
    " << tmp_s6.str () << "
    \r\n
    \r\n"; } } void ShowTransports (std::stringstream& s) { - s << "Transports:
    \r\n
    \r\n"; + s << "Transports:
    \r\n"; auto ntcpServer = i2p::transport::transports.GetNTCPServer (); if (ntcpServer) { @@ -734,9 +745,10 @@ namespace http { auto sessions = ssuServer->GetSessions (); if (!sessions.empty ()) { - s << "
    \r\n\r\n
    "; + s << "
    \r\n\r\n
    "; for (const auto& it: sessions) { + s << "
    \r\n"; auto endpoint = it.second->GetRemoteEndpoint (); if (it.second->IsOutgoing ()) s << " ⇒ "; s << endpoint.address ().to_string () << ":" << endpoint.port (); @@ -744,16 +756,17 @@ namespace http { s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; if (it.second->GetRelayTag ()) s << " [itag:" << it.second->GetRelayTag () << "]"; - s << "
    \r\n" << std::endl; + s << "
    \r\n" << std::endl; } s << "
    \r\n
    \r\n"; } auto sessions6 = ssuServer->GetSessionsV6 (); if (!sessions6.empty ()) { - s << "
    \r\n\r\n
    "; + s << "
    \r\n\r\n
    "; for (const auto& it: sessions6) { + s << "
    \r\n"; auto endpoint = it.second->GetRemoteEndpoint (); if (it.second->IsOutgoing ()) s << " ⇒ "; s << "[" << endpoint.address ().to_string () << "]:" << endpoint.port (); @@ -761,7 +774,7 @@ namespace http { s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; if (it.second->GetRelayTag ()) s << " [itag:" << it.second->GetRelayTag () << "]"; - s << "
    \r\n" << std::endl; + s << "
    \r\n" << std::endl; } s << "
    \r\n
    \r\n"; } @@ -780,13 +793,14 @@ namespace http { if(sam->GetSessions ().size ()) { - s << "SAM Sessions:
    \r\n
    \r\n"; + s << "SAM Sessions:
    \r\n
    \r\n"; for (auto& it: sam->GetSessions ()) { auto& name = it.second->localDestination->GetNickname (); - s << ""; - s << name << " (" << it.first << ")
    \r\n" << std::endl; + s << "\r\n" << std::endl; } + s << "
    \r\n"; } else s << "SAM Sessions: no sessions currently running.
    \r\n"; @@ -794,25 +808,28 @@ namespace http { static void ShowSAMSession (std::stringstream& s, const std::string& id) { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "SAM Session:
    \r\n
    \r\n"; auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { ShowError(s, "SAM disabled"); return; } + auto session = sam->FindSession (id); if (!session) { ShowError(s, "SAM session not found"); return; } + + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + s << "SAM Session:
    \r\n
    \r\n"; auto& ident = session->localDestination->GetIdentHash(); - s << ""; - s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
    \r\n"; + s << "\r\n"; s << "
    \r\n"; - s << "Streams:
    \r\n"; + s << "Streams:
    \r\n
    \r\n"; for (const auto& it: sam->ListSockets(id)) { + s << "
    "; switch (it->GetSocketType ()) { case i2p::client::eSAMSocketTypeSession : s << "session"; break; @@ -821,78 +838,85 @@ namespace http { default: s << "unknown"; break; } s << " [" << it->GetSocket ().remote_endpoint() << "]"; - s << "
    \r\n"; + s << "
    \r\n"; } + s << "
    \r\n"; } void ShowI2PTunnels (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "Client Tunnels:
    \r\n
    \r\n"; + s << "Client Tunnels:
    \r\n
    \r\n"; for (auto& it: i2p::client::context.GetClientTunnels ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << "
    "; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
    \r\n"<< std::endl; + s << "
    \r\n"<< std::endl; } auto httpProxy = i2p::client::context.GetHttpProxy (); if (httpProxy) { auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << "
    "; s << "HTTP Proxy" << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
    \r\n"<< std::endl; + s << "
    \r\n"<< std::endl; } auto socksProxy = i2p::client::context.GetSocksProxy (); if (socksProxy) { auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << "
    "; s << "SOCKS Proxy" << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
    \r\n"<< std::endl; + s << "
    \r\n"<< std::endl; } + s << "
    \r\n"; + auto& serverTunnels = i2p::client::context.GetServerTunnels (); if (!serverTunnels.empty ()) { - s << "
    \r\nServer Tunnels:
    \r\n
    \r\n"; + s << "
    \r\nServer Tunnels:
    \r\n
    \r\n"; for (auto& it: serverTunnels) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << "
    "; s << it.second->GetName () << " ⇒ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << ":" << it.second->GetLocalPort (); - s << "
    \r\n"<< std::endl; + s << "
    \r\n"<< std::endl; } + s << "
    \r\n"; } + auto& clientForwards = i2p::client::context.GetClientForwards (); if (!clientForwards.empty ()) { - s << "
    \r\nClient Forwards:
    \r\n
    \r\n"; + s << "
    \r\nClient Forwards:
    \r\n
    \r\n"; for (auto& it: clientForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << "
    "; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
    \r\n"<< std::endl; + s << "
    \r\n"<< std::endl; } + s << "
    \r\n"; } auto& serverForwards = i2p::client::context.GetServerForwards (); if (!serverForwards.empty ()) { - s << "
    \r\nServer Forwards:
    \r\n
    \r\n"; + s << "
    \r\nServer Forwards:
    \r\n
    \r\n"; for (auto& it: serverForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << ""; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
    \r\n"<< std::endl; + s << "
    \r\n"<< std::endl; } + s << "
    \r\n"; } } From 2d65402cedf9d788609b47bbeac7faaab88c8591 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 15 Jun 2020 13:05:01 +0300 Subject: [PATCH 3766/6300] [webconsole] update styles Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index c387a402..416cf1e4 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -69,6 +69,7 @@ namespace http { " .wrapper { margin: 0 auto; padding: 1em; max-width: 58em; }\r\n" " .menu { float: left; } .menu a, .commands a { display: block; }\r\n" " .listitem { display: block; font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n" + " .tableitem { font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n" " .content { float: left; font-size: 1em; margin-left: 4em; max-width: 46em; overflow: auto; }\r\n" " .tunnel.established { color: #56B734; } .tunnel.expiring { color: #D3AE3F; }\r\n" " .tunnel.failed { color: #D33F3F; } .tunnel.building { color: #434343; }\r\n" @@ -172,7 +173,7 @@ namespace http { default: state = "unknown"; break; } s << " " << state << ((explr) ? " (exploratory)" : "") << ", "; - s << " " << (int) (bytes / 1024) << " KiB
    \r\n"; + s << " " << (int) (bytes / 1024) << " KiB\r\n"; } static void SetLogLevel (const std::string& level) @@ -389,7 +390,7 @@ namespace http { static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr dest) { - s << "Base64:
    \r\n
    \r\n
    \r\n"; if (dest->IsEncryptedLeaseSet ()) { @@ -402,29 +403,34 @@ namespace http { if(dest->GetNumRemoteLeaseSets()) { s << "
    \r\n\r\n
    \r\n"; + << "\r\n\r\n
    \r\n
    AddressTypeEncType
    "; for(auto& it: dest->GetLeaseSets ()) s << "\r\n"; s << "
    AddressTypeEncType
    " << it.first.ToBase32 () << "" << (int)it.second->GetStoreType () << "" << (int)it.second->GetEncryptionType () <<"
    \r\n
    \r\n
    \r\n
    \r\n"; } else s << "LeaseSets: 0
    \r\n
    \r\n"; + auto pool = dest->GetTunnelPool (); if (pool) { - s << "Inbound tunnels:
    \r\n"; + s << "Inbound tunnels:
    \r\n
    \r\n"; for (auto & it : pool->GetInboundTunnels ()) { + s << "
    "; it->Print(s); if(it->LatencyIsKnown()) s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); + s << "
    \r\n"; } s << "
    \r\n"; - s << "Outbound tunnels:
    \r\n"; + s << "Outbound tunnels:
    \r\n
    \r\n"; for (auto & it : pool->GetOutboundTunnels ()) { + s << "
    "; it->Print(s); if(it->LatencyIsKnown()) s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); + s << "
    \r\n"; } } s << "
    \r\n"; @@ -437,7 +443,7 @@ namespace http { out_tags += it.second->GetNumOutgoingTags (); } s << "
    \r\n\r\n" - << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationAmount
    \r\n
    \r\n
    \r\n"; + << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationAmount
    \r\n
    \r\n
    \r\n"; } else s << "Outgoing: 0
    \r\n"; s << "
    \r\n"; @@ -453,7 +459,7 @@ namespace http { ecies_sessions++; } s << "
    \r\n\r\n" - << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationStatus
    \r\n
    \r\n
    \r\n"; + << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationStatus
    \r\n
    \r\n
    \r\n"; } else s << "Tags sessions: 0
    \r\n"; s << "
    \r\n"; @@ -483,11 +489,12 @@ namespace http { s << "RTT"; s << "Window"; s << "Status"; - s << "\r\n\r\n\r\n"; + s << "\r\n\r\n\r\n"; for (const auto& it: dest->GetAllStreams ()) { auto streamDest = i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()); + std::string streamDestShort = streamDest.substr(0,12) + "….b32.i2p"; s << ""; s << "" << it->GetRecvStreamID () << ""; if (it->GetRecvStreamID ()) { @@ -496,7 +503,7 @@ namespace http { } else { s << ""; } - s << "" << streamDest << ""; + s << "" << streamDestShort << ""; s << "" << it->GetNumSentBytes () << ""; s << "" << it->GetNumReceivedBytes () << ""; s << "" << it->GetSendQueueSize () << ""; @@ -531,7 +538,7 @@ namespace http { { if (i2p::data::netdb.GetNumLeaseSets ()) { - s << "LeaseSets:
    \r\n
    \r\n"; + s << "LeaseSets:
    \r\n
    \r\n"; int counter = 1; // for each lease set i2p::data::netdb.VisitLeaseSets( @@ -545,13 +552,13 @@ namespace http { else ls.reset (new i2p::data::LeaseSet2 (storeType, leaseSet->GetBuffer(), leaseSet->GetBufferLen())); if (!ls) return; - s << "
    \r\n"; + s << "\">\r\n"; if (!ls->IsValid()) - s << "
    !! Invalid !!
    \r\n"; - s << "
    \r\n"; + s << "
    !! Invalid !!
    \r\n"; + s << "
    \r\n"; s << "\r\n
    \r\n"; s << "Store type: " << (int)storeType << "
    \r\n"; s << "Expires: " << ConvertTime(ls->GetExpirationTime()) << "
    \r\n"; @@ -922,7 +929,7 @@ namespace http { std::string ConvertTime (uint64_t time) { - ldiv_t divTime = ldiv(time,1000); + lldiv_t divTime = lldiv(time, 1000); time_t t = divTime.quot; struct tm *tm = localtime(&t); char date[128]; From 4e4c11702371bbbcc9ee87a02448bb36f8815fb4 Mon Sep 17 00:00:00 2001 From: potatowipedlifereverse Date: Tue, 7 Jul 2020 02:01:56 +0300 Subject: [PATCH 3767/6300] whitelist synonim for accesslist --- libi2pd_client/ClientContext.cpp | 2 ++ libi2pd_client/ClientContext.h | 1 + 2 files changed, 3 insertions(+) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 0348aeeb..e8e8eb2d 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -663,6 +663,8 @@ namespace client // optional params int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); + if(accessList == "") + accessList=section.second.get (I2P_SERVER_TUNNEL_WHITE_LIST, ""); std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 8a4835c8..05b754fe 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -54,6 +54,7 @@ namespace client const char I2P_SERVER_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_SERVER_TUNNEL_INPORT[] = "inport"; const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist"; + const char I2P_SERVER_TUNNEL_WHITE_LIST[] = "whitelist"; const char I2P_SERVER_TUNNEL_GZIP[] = "gzip"; const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword"; const char I2P_SERVER_TUNNEL_ADDRESS[] = "address"; From 67b94d3533f8e0f0eca2443344c012d06acc5ab5 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 7 Jul 2020 15:38:20 -0400 Subject: [PATCH 3768/6300] unordered_map for RouterInfos and LeaseSets --- libi2pd/NetDb.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index 1c65969a..d4501ab9 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -11,7 +11,7 @@ // this file is called NetDb.hpp to resolve conflict with libc's netdb.h on case insensitive fs #include #include -#include +#include #include #include #include @@ -138,9 +138,9 @@ namespace data private: mutable std::mutex m_LeaseSetsMutex; - std::map > m_LeaseSets; + std::unordered_map > m_LeaseSets; mutable std::mutex m_RouterInfosMutex; - std::map > m_RouterInfos; + std::unordered_map > m_RouterInfos; mutable std::mutex m_FloodfillsMutex; std::list > m_Floodfills; From c41554109b76ad3ff4e9b621c86e0f91a533dc54 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 15 Jul 2020 16:20:35 -0400 Subject: [PATCH 3769/6300] change datagram routing path if nothing comes back in 10 seconds --- libi2pd/Datagram.cpp | 7 ++ libi2pd/ECIESX25519AEADRatchetSession.cpp | 140 +++++++++++----------- libi2pd/ECIESX25519AEADRatchetSession.h | 7 +- libi2pd/Garlic.h | 1 + 4 files changed, 85 insertions(+), 70 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index b9938043..559b0a8b 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -315,6 +315,13 @@ namespace datagram } auto path = m_RoutingSession->GetSharedRoutingPath(); + if (path && m_RoutingSession->IsRatchets () && + m_LastUse > m_RoutingSession->GetLastActivityTimestamp ()*1000 + DATAGRAM_SESSION_PATH_TIMEOUT) + { + m_RoutingSession->SetSharedRoutingPath (nullptr); + path = nullptr; + } + if (path) { if (path->outboundTunnel && !path->outboundTunnel->IsEstablished ()) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index d403c9bd..dac5dbe0 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -770,7 +770,7 @@ namespace garlic if (m_NextSendRatchet->newKey) payloadLen += 32; } uint8_t paddingSize = 0; - if (payloadLen) + if (payloadLen || ts > m_LastSentTimestamp + ECIESX25519_SEND_INACTIVITY_TIMEOUT) { int delta = (int)ECIESX25519_OPTIMAL_PAYLOAD_SIZE - (int)payloadLen; if (delta < 0 || delta > 3) // don't create padding if we are close to optimal size @@ -791,83 +791,87 @@ namespace garlic } } std::vector v(payloadLen); - size_t offset = 0; - // DateTime - if (first) - { - v[offset] = eECIESx25519BlkDateTime; offset++; - htobe16buf (v.data () + offset, 4); offset += 2; - htobe32buf (v.data () + offset, ts/1000); offset += 4; // in seconds - } - // LeaseSet - if (leaseSet) - { - offset += CreateLeaseSetClove (leaseSet, ts, v.data () + offset, payloadLen - offset); - if (!first) + if (payloadLen) + { + m_LastSentTimestamp = ts; + size_t offset = 0; + // DateTime + if (first) { - // ack request - v[offset] = eECIESx25519BlkAckRequest; offset++; - htobe16buf (v.data () + offset, 1); offset += 2; - v[offset] = 0; offset++; // flags + v[offset] = eECIESx25519BlkDateTime; offset++; + htobe16buf (v.data () + offset, 4); offset += 2; + htobe32buf (v.data () + offset, ts/1000); offset += 4; // in seconds } - } - // msg - if (msg && m_Destination) - offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset, true); - // ack - if (m_AckRequests.size () > 0) - { - v[offset] = eECIESx25519BlkAck; offset++; - htobe16buf (v.data () + offset, m_AckRequests.size () * 4); offset += 2; - for (auto& it: m_AckRequests) + // LeaseSet + if (leaseSet) { - htobe16buf (v.data () + offset, it.first); offset += 2; - htobe16buf (v.data () + offset, it.second); offset += 2; + offset += CreateLeaseSetClove (leaseSet, ts, v.data () + offset, payloadLen - offset); + if (!first) + { + // ack request + v[offset] = eECIESx25519BlkAckRequest; offset++; + htobe16buf (v.data () + offset, 1); offset += 2; + v[offset] = 0; offset++; // flags + } } - m_AckRequests.clear (); - } - // next keys - if (m_SendReverseKey) - { - v[offset] = eECIESx25519BlkNextKey; offset++; - htobe16buf (v.data () + offset, m_NextReceiveRatchet->newKey ? 35 : 3); offset += 2; - v[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; - int keyID = m_NextReceiveRatchet->keyID - 1; - if (m_NextReceiveRatchet->newKey) + // msg + if (msg && m_Destination) + offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset, true); + // ack + if (m_AckRequests.size () > 0) { - v[offset] |= ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG; - keyID++; + v[offset] = eECIESx25519BlkAck; offset++; + htobe16buf (v.data () + offset, m_AckRequests.size () * 4); offset += 2; + for (auto& it: m_AckRequests) + { + htobe16buf (v.data () + offset, it.first); offset += 2; + htobe16buf (v.data () + offset, it.second); offset += 2; + } + m_AckRequests.clear (); } - offset++; // flag - htobe16buf (v.data () + offset, keyID); offset += 2; // keyid - if (m_NextReceiveRatchet->newKey) + // next keys + if (m_SendReverseKey) { - memcpy (v.data () + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); - offset += 32; // public key + v[offset] = eECIESx25519BlkNextKey; offset++; + htobe16buf (v.data () + offset, m_NextReceiveRatchet->newKey ? 35 : 3); offset += 2; + v[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; + int keyID = m_NextReceiveRatchet->keyID - 1; + if (m_NextReceiveRatchet->newKey) + { + v[offset] |= ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG; + keyID++; + } + offset++; // flag + htobe16buf (v.data () + offset, keyID); offset += 2; // keyid + if (m_NextReceiveRatchet->newKey) + { + memcpy (v.data () + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); + offset += 32; // public key + } + m_SendReverseKey = false; } - m_SendReverseKey = false; - } - if (m_SendForwardKey) - { - v[offset] = eECIESx25519BlkNextKey; offset++; - htobe16buf (v.data () + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; - v[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; - if (!m_NextSendRatchet->keyID) v[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only - offset++; // flag - htobe16buf (v.data () + offset, m_NextSendRatchet->keyID); offset += 2; // keyid - if (m_NextSendRatchet->newKey) + if (m_SendForwardKey) { - memcpy (v.data () + offset, m_NextSendRatchet->key->GetPublicKey (), 32); - offset += 32; // public key + v[offset] = eECIESx25519BlkNextKey; offset++; + htobe16buf (v.data () + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; + v[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; + if (!m_NextSendRatchet->keyID) v[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only + offset++; // flag + htobe16buf (v.data () + offset, m_NextSendRatchet->keyID); offset += 2; // keyid + if (m_NextSendRatchet->newKey) + { + memcpy (v.data () + offset, m_NextSendRatchet->key->GetPublicKey (), 32); + offset += 32; // public key + } } - } - // padding - if (paddingSize) - { - v[offset] = eECIESx25519BlkPadding; offset++; - htobe16buf (v.data () + offset, paddingSize); offset += 2; - memset (v.data () + offset, 0, paddingSize); offset += paddingSize; - } + // padding + if (paddingSize) + { + v[offset] = eECIESx25519BlkPadding; offset++; + htobe16buf (v.data () + offset, paddingSize); offset += 2; + memset (v.data () + offset, 0, paddingSize); offset += paddingSize; + } + } return v; } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 2dccd865..cccb4092 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -27,7 +27,8 @@ namespace garlic { const int ECIESX25519_RESTART_TIMEOUT = 120; // number of second since session creation we can restart session after const int ECIESX25519_EXPIRATION_TIMEOUT = 480; // in seconds - const int ECIESX25519_INACTIVITY_TIMEOUT = 90; // number of second we receive nothing and should restart if we can + const int ECIESX25519_INACTIVITY_TIMEOUT = 90; // number of seconds we receive nothing and should restart if we can + const int ECIESX25519_SEND_INACTIVITY_TIMEOUT = 5000; // number of milliseconds we can send empty(pyaload only) packet after const int ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT = 600; // in seconds const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180 const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 4096; // number of tags we request new tagset after @@ -148,6 +149,7 @@ namespace garlic bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); } bool IsRatchets () const { return true; }; + uint64_t GetLastActivityTimestamp () const { return m_LastActivityTimestamp; }; private: @@ -182,7 +184,8 @@ namespace garlic uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only std::shared_ptr m_EphemeralKeys; SessionState m_State = eSessionStateNew; - uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0; // incoming + uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming + m_LastSentTimestamp = 0; // in milliseconds std::shared_ptr m_SendTagset, m_NSRSendTagset; std::unique_ptr m_Destination;// TODO: might not need it std::list > m_AckRequests; // (tagsetid, index) diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 70893bec..2168ee81 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -114,6 +114,7 @@ namespace garlic virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession virtual bool MessageConfirmed (uint32_t msgID); virtual bool IsRatchets () const { return false; }; + virtual uint64_t GetLastActivityTimestamp () const { return 0; }; // non-zero for rathets only void SetLeaseSetUpdated () { From 3ef8b3dcbb55a3b1714eb46f3cd86bec2d53000a Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 24 Jul 2020 20:44:01 -0400 Subject: [PATCH 3770/6300] don't send repliable datagram after less then 100 milliseconds --- libi2pd_client/I2PTunnel.cpp | 16 ++++++++++++---- libi2pd_client/I2PTunnel.h | 3 ++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 95f56873..3575e062 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -708,9 +708,12 @@ namespace client if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); - LastActivity = i2p::util::GetMillisecondsSinceEpoch(); + auto ts = i2p::util::GetMillisecondsSinceEpoch(); auto session = m_Destination->GetSession (Identity); - m_Destination->SendDatagram(session, m_Buffer, len, LocalPort, RemotePort); + if (ts > LastActivity + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) + m_Destination->SendDatagram(session, m_Buffer, len, LocalPort, RemotePort); + else + m_Destination->SendRawDatagram(session, m_Buffer, len, LocalPort, RemotePort); size_t numPackets = 0; while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) { @@ -724,6 +727,7 @@ namespace client if (numPackets > 0) LogPrint(eLogDebug, "UDPSession: forward more ", numPackets, "packets B from ", FromEndpoint); m_Destination->FlushSendQueue (session); + LastActivity = ts; Receive(); } else @@ -841,9 +845,13 @@ namespace client m_LastPort = remotePort; } // send off to remote i2p destination + auto ts = i2p::util::GetMillisecondsSinceEpoch(); LogPrint(eLogDebug, "UDP Client: send ", transferred, " to ", m_RemoteIdent->ToBase32(), ":", RemotePort); auto session = m_LocalDest->GetDatagramDestination()->GetSession (*m_RemoteIdent); - m_LocalDest->GetDatagramDestination()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); + if (ts > m_LastSession->second + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) + m_LocalDest->GetDatagramDestination()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); + else + m_LocalDest->GetDatagramDestination()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); size_t numPackets = 0; while (numPackets < i2p::datagram::DATAGRAM_SEND_QUEUE_MAX_SIZE) { @@ -862,7 +870,7 @@ namespace client // mark convo as active if (m_LastSession) - m_LastSession->second = i2p::util::GetMillisecondsSinceEpoch(); + m_LastSession->second = ts; RecvFromLocal(); } diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index eb1d8ed9..b76a1027 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -165,7 +165,8 @@ namespace client /** 2 minute timeout for udp sessions */ const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; - + const uint64_t I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL = 100; // in milliseconds + /** max size for i2p udp */ const size_t I2P_UDP_MAX_MTU = 64*1024; From c3aa6b9cda922a4c76221c3383287ae60a20a038 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 29 Jul 2020 17:47:46 -0400 Subject: [PATCH 3771/6300] use delivery type local if destination is not secified --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 15 +++++++++------ libi2pd/ECIESX25519AEADRatchetSession.h | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index dac5dbe0..51ca9242 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -739,8 +739,11 @@ namespace garlic uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); size_t payloadLen = 0; if (first) payloadLen += 7;// datatime - if (msg && m_Destination) - payloadLen += msg->GetPayloadLength () + 13 + 32; + if (msg) + { + payloadLen += msg->GetPayloadLength () + 13; + if (m_Destination) payloadLen += 32; + } auto leaseSet = (GetLeaseSetUpdateStatus () == eLeaseSetUpdated || (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT)) ? @@ -816,7 +819,7 @@ namespace garlic } // msg if (msg && m_Destination) - offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset, true); + offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset); // ack if (m_AckRequests.size () > 0) { @@ -875,16 +878,16 @@ namespace garlic return v; } - size_t ECIESX25519AEADRatchetSession::CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len, bool isDestination) + size_t ECIESX25519AEADRatchetSession::CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len) { if (!msg) return 0; uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; - if (isDestination) cloveSize += 32; + if (m_Destination) cloveSize += 32; if ((int)len < cloveSize + 3) return 0; buf[0] = eECIESx25519BlkGalicClove; // clove type htobe16buf (buf + 1, cloveSize); // size buf += 3; - if (isDestination) + if (m_Destination) { *buf = (eGarlicDeliveryTypeDestination << 5); memcpy (buf + 1, *m_Destination, 32); buf += 32; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index cccb4092..73e129d7 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -171,7 +171,7 @@ namespace garlic bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); std::vector CreatePayload (std::shared_ptr msg, bool first); - size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len, bool isDestination = false); + size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len); size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); void GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags); From 9636d82b37ff021cd7ee7a7a0d55e89624e4a183 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 3 Aug 2020 18:31:03 -0400 Subject: [PATCH 3772/6300] MixHash for SessionConfirmed processing --- libi2pd/NTCP2.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index f1f7e59f..4e3b219c 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -338,11 +338,8 @@ namespace transport KDF3Bob (); if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionConfirmedBuffer + 48, m3p2Len - 16, GetH (), 32, GetK (), nonce, m3p2Buf, m3p2Len - 16, false)) // decrypt - { // caclulate new h again for KDF data - memcpy (m_SessionConfirmedBuffer + 16, m_H, 32); // h || ciphertext - SHA256 (m_SessionConfirmedBuffer + 16, m3p2Len + 32, m_H); //h = SHA256(h || ciphertext); - } + MixHash (m_SessionConfirmedBuffer + 48, m3p2Len); // h = SHA256(h || ciphertext) else { LogPrint (eLogWarning, "NTCP2: SessionConfirmed Part2 AEAD verification failed "); From 8e252265744cc8fdb942b90c2b27b8968d45698c Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 8 Aug 2020 13:34:27 -0400 Subject: [PATCH 3773/6300] use unordered_map for incomplete and sent messages --- libi2pd/SSUData.cpp | 2 +- libi2pd/SSUData.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 5068f006..fde684d0 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -301,7 +301,7 @@ namespace transport void SSUData::Send (std::shared_ptr msg) { uint32_t msgID = msg->ToSSU (); - if (m_SentMessages.count (msgID) > 0) + if (m_SentMessages.find (msgID) != m_SentMessages.end()) { LogPrint (eLogWarning, "SSU: message ", msgID, " already sent"); return; diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index f4a5ba4f..2e606053 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include #include @@ -124,8 +124,8 @@ namespace transport private: SSUSession& m_Session; - std::map > m_IncompleteMessages; - std::map > m_SentMessages; + std::unordered_map > m_IncompleteMessages; + std::unordered_map > m_SentMessages; std::unordered_set m_ReceivedMessages; boost::asio::deadline_timer m_ResendTimer, m_IncompleteMessagesCleanupTimer; int m_MaxPacketSize, m_PacketSize; From e50abbb250da622191cebcb2c9d9f989087bfd8b Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 8 Aug 2020 19:01:55 -0400 Subject: [PATCH 3774/6300] avoid replay upon SSU packet resend --- libi2pd/SSUData.cpp | 23 +++++++++++++++-------- libi2pd/SSUSession.cpp | 26 ++++++++++++++++---------- libi2pd/SSUSession.h | 1 + 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index fde684d0..bd188ab7 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -326,8 +326,7 @@ namespace transport { Fragment * fragment = new Fragment; fragment->fragmentNum = fragmentNum; - uint8_t * buf = fragment->buf; - uint8_t * payload = buf + sizeof (SSUHeader); + uint8_t * payload = fragment->buf + sizeof (SSUHeader); *payload = DATA_FLAG_WANT_REPLY; // for compatibility payload++; *payload = 1; // always 1 message fragment per message @@ -346,14 +345,20 @@ namespace transport payload += 3; memcpy (payload, msgBuf, size); - size += payload - buf; - if (size & 0x0F) // make sure 16 bytes boundary - size = ((size >> 4) + 1) << 4; // (/16 + 1)*16 + size += payload - fragment->buf; + uint8_t rem = size & 0x0F; + if (rem) // make sure 16 bytes boundary + { + auto padding = 16 - rem; + memset (fragment->buf + size, 0, padding); + size += padding; + } fragment->len = size; fragments.push_back (std::unique_ptr (fragment)); // encrypt message with session key - m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, size); + uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; + m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, fragment->buf, size, buf); try { m_Session.Send (buf, size); @@ -432,6 +437,7 @@ namespace transport { if (ecode != boost::asio::error::operation_aborted) { + uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); int numResent = 0; for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();) @@ -444,8 +450,9 @@ namespace transport if (f) { try - { - m_Session.Send (f->buf, f->len); // resend + { + m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, f->buf, f->len, buf); + m_Session.Send (buf, f->len); // resend numResent++; } catch (boost::system::system_error& ec) diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index 73699d6a..fca074b3 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -744,27 +744,33 @@ namespace transport } void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len) + { + FillHeaderAndEncrypt (payloadType, buf, len, buf); + } + + void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out) { if (len < sizeof (SSUHeader)) { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } - SSUHeader * header = (SSUHeader *)buf; + SSUHeader * header = (SSUHeader *)out; RAND_bytes (header->iv, 16); // random iv m_SessionKeyEncryption.SetIV (header->iv); - header->flag = payloadType << 4; // MSB is 0 - htobe32buf (header->time, i2p::util::GetSecondsSinceEpoch ()); - uint8_t * encrypted = &header->flag; - uint16_t encryptedLen = len - (encrypted - buf); - m_SessionKeyEncryption.Encrypt (encrypted, encryptedLen, encrypted); - // assume actual buffer size is 18 (16 + 2) bytes more - memcpy (buf + len, header->iv, 16); + SSUHeader * inHeader = (SSUHeader *)in; + inHeader->flag = payloadType << 4; // MSB is 0 + htobe32buf (inHeader->time, i2p::util::GetSecondsSinceEpoch ()); + uint8_t * encrypted = &header->flag, * clear = &inHeader->flag; + uint16_t encryptedLen = len - (encrypted - out); + m_SessionKeyEncryption.Encrypt (clear, encryptedLen, encrypted); + // assume actual out buffer size is 18 (16 + 2) bytes more + memcpy (out + len, header->iv, 16); uint16_t netid = i2p::context.GetNetID (); - htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); + htobe16buf (out + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8)); i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, m_MacKey, header->mac); } - + void SSUSession::Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey) { if (len < sizeof (SSUHeader)) diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h index 066e01eb..ea820517 100644 --- a/libi2pd/SSUSession.h +++ b/libi2pd/SSUSession.h @@ -138,6 +138,7 @@ namespace transport void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag = 0); void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key + void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * in, size_t len, uint8_t * out); // with session key void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey); void DecryptSessionKey (uint8_t * buf, size_t len); bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey); From 6fec92c012b8fa067284f26355c427610f8624fb Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 10 Aug 2020 17:49:46 -0400 Subject: [PATCH 3775/6300] shared transient addresses --- libi2pd_client/ClientContext.cpp | 52 +++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index e8e8eb2d..fc48ddbb 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -253,7 +253,8 @@ namespace client bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType) { - if (filename == "transient") + static const std::string transient("transient"); + if (!filename.compare (0, transient.length (), transient)) // starts with transient { keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); @@ -533,6 +534,7 @@ namespace client return; } + std::map > destinations; // keys -> destination for (auto& section: pt) { std::string name = section.first; @@ -564,18 +566,25 @@ namespace client std::shared_ptr localDestination = nullptr; if (keys.length () > 0) { - i2p::data::PrivateKeys k; - if(LoadPrivateKeys (k, keys, sigType, cryptoType)) - { - localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); - if (!localDestination) + auto it = destinations.find (keys); + if (it != destinations.end ()) + localDestination = it->second; + else + { + i2p::data::PrivateKeys k; + if(LoadPrivateKeys (k, keys, sigType, cryptoType)) { - if(matchTunnels) - localDestination = CreateNewMatchedTunnelDestination(k, dest, &options); - else - localDestination = CreateNewLocalDestination (k, type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT, &options); + localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); + if (!localDestination) + { + if(matchTunnels) + localDestination = CreateNewMatchedTunnelDestination(k, dest, &options); + else + localDestination = CreateNewLocalDestination (k, type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT, &options); + destinations[keys] = localDestination; + } } - } + } } if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { @@ -679,12 +688,21 @@ namespace client ReadI2CPOptions (section, options); std::shared_ptr localDestination = nullptr; - i2p::data::PrivateKeys k; - if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) - continue; - localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); - if (!localDestination) - localDestination = CreateNewLocalDestination (k, true, &options); + auto it = destinations.find (keys); + if (it != destinations.end ()) + localDestination = it->second; + else + { + i2p::data::PrivateKeys k; + if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) + continue; + localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); + if (!localDestination) + { + localDestination = CreateNewLocalDestination (k, true, &options); + destinations[keys] = localDestination; + } + } if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // udp server tunnel From e7ff6fbffc437adef64e198eed8ae34085741cc9 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 14 Aug 2020 09:54:31 -0400 Subject: [PATCH 3776/6300] don't save invalid addreses --- libi2pd_client/AddressBook.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index 8b8e781d..7af1272d 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -198,13 +198,18 @@ namespace client for (const auto& it: addresses) { - f << it.first << ","; - if (it.second->IsIdentHash ()) - f << it.second->identHash.ToBase32 (); + if (it.second->IsValid ()) + { + f << it.first << ","; + if (it.second->IsIdentHash ()) + f << it.second->identHash.ToBase32 (); + else + f << it.second->blindedPublicKey->ToB33 (); + f << std::endl; + num++; + } else - f << it.second->blindedPublicKey->ToB33 (); - f << std::endl; - num++; + LogPrint (eLogWarning, "Addressbook: invalid address ", it.first); } LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); return num; From 3159b06988a215a745eba48c1e1c8c41fcf231c3 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 15 Aug 2020 13:53:49 -0400 Subject: [PATCH 3777/6300] reseeds update --- .../certificates/reseed/bugme_at_mail.i2p.crt | 32 ----------------- .../reseed/reseed_at_diva.exchange.crt | 34 +++++++++++++++++++ libi2pd/Config.cpp | 2 +- 3 files changed, 35 insertions(+), 33 deletions(-) delete mode 100644 contrib/certificates/reseed/bugme_at_mail.i2p.crt create mode 100644 contrib/certificates/reseed/reseed_at_diva.exchange.crt diff --git a/contrib/certificates/reseed/bugme_at_mail.i2p.crt b/contrib/certificates/reseed/bugme_at_mail.i2p.crt deleted file mode 100644 index 2b6acac0..00000000 --- a/contrib/certificates/reseed/bugme_at_mail.i2p.crt +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFezCCA2OgAwIBAgIEUQYyQjANBgkqhkiG9w0BAQ0FADBuMQswCQYDVQQGEwJY -WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt -b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOYnVnbWVAbWFpbC5p -MnAwHhcNMTQxMTA2MDkxMTE0WhcNMjQxMTA1MDkxMTE0WjBuMQswCQYDVQQGEwJY -WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt -b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOYnVnbWVAbWFpbC5p -MnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCrThOH0eSDT0VnCSBC -sqYmAydWH+O8eNttDXr2mSvZLhvAW+6/xHTkKhaWvkIvvS0Vh8hujMnD90Cgp4Fk -TKCxMj9K527o5xIZwWW05OevbjlBwIpVLO1PjmsfsoD1nIX14eEzJSEoAulKsv7V -jGUC/6hC11mmVvH9buQLSRv6sCjuAcMszmw3TAD+XYBIs+z57KuwYXtX3+OA543c -l1/ZKLYkkwY8cwzZqWDVWqTKP5TfVae58t40HhJk3bOsr21FZsaOjlmao3GO+d/3 -exKuUGJRcolSqskL3sZ1ovFqko81obvvx0upI0YA0iMr/NRGl3VPuf/LJvRppYGc -LsJHgy9TIgtHvaXRi5Nt4CbKl9sZh/7WkkTTI5YGvevu00btlabAN+DSAZZqdsB3 -wY8HhM1MHiA9SWsqwU65TwErcRrjNna2FiDHEu0xk5+/iAGl6CSKHZBmNcYKXSv8 -cwShB0jjmciK0a05nC638RPgj0fng7KRrSglyzfjXRrljmZ40LSBL/GGMZMWpOM7 -mEsBH5UZJ/2BEmjc9X9257zBdx8BK8y1TXpAligpNBsERcTw1WP1PJ35einZvlXW -qI3GwMf0sl26sn+evcK0gDl27jVDZ45MtNQEq64M4NV3Tn9zq0eg/39YvjVeqrI5 -l7sxmYqYGR6BuSncwdc4x+t6swIDAQABoyEwHzAdBgNVHQ4EFgQU/REZ7NMbVZHr -Xkao6Q8Ccqv2kAMwDQYJKoZIhvcNAQENBQADggIBACc2YjLVNbl1kJUdg2klCLJt -5LjNTiIZa2Cha5GStlC/lyoRRge6+q/y9TN3tTptlzLPS9pI9EE1GfIQaE+HAk+e -/bC3KUOAHgVuETvsNAbfpaVsPCdWpFuXmp/4b9iDN7qZy4afTKUPA/Ir/cLfNp14 -JULfP4z2yFOsCQZ5viNFAs1u99FrwobV2LBzUSIJQewsksuOwj96zIyau0Y629oJ -k+og88Tifd9EH3MVZNGhdpojQDDdwHQSITnCDgfRP5yER1WIA4jg6l+mM90QkvLY -5NjWTna5kJ3X6UizvgCk365yzT2sbN3R9UGXfCJa9GBcnnviJtJF3+/gC0abwY2f -NtVYp32Xky45NY/NdRhDg0bjHP3psxmX+Sc0M9NuQcDQ+fUR+CzM0IGeiszkzXOs -RG+bOou2cZ81G4oxWdAALHIRrn7VvLGlkFMxiIZyhYcTGQZzsTPT6n18dY99+DAV -yQWZfIRdm8DOnt0G+cwfeohc/9ZwDmj4jJAAi0aeTXdY6NEGIVydk6MAycEhg2Hx -9EV96kRwZNIW0AGY8CozECFL3Eyo2ClQVV4Q35SsBibsitDjM03usc2DJ/qjynXA -C8HoOSWgbddiBvqZueqK8GdhykOy3J3ysr+MNN/lbG48LqkQr1OWxev9rGGQ6RJT -wpBgPyAFAwouPy1whmnx ------END CERTIFICATE----- diff --git a/contrib/certificates/reseed/reseed_at_diva.exchange.crt b/contrib/certificates/reseed/reseed_at_diva.exchange.crt new file mode 100644 index 00000000..04b1524b --- /dev/null +++ b/contrib/certificates/reseed/reseed_at_diva.exchange.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +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 +LD8d2z2Lgg== +-----END CERTIFICATE----- diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index d11152dd..6ce3a714 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -195,7 +195,7 @@ namespace config { ("reseed.proxy", value()->default_value(""), "url for reseed proxy, supports http/socks") ("reseed.urls", value()->default_value( "https://reseed.i2p-projekt.de/," - "https://i2p.mooo.com/netDb/," + "https://reseed.diva.exchange/," "https://reseed.i2p2.no/," "https://reseed-fr.i2pd.xyz/," "https://reseed.memcpy.io/," From 0777bad2c3b92607825c25898383346ae877a236 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 23 Aug 2020 22:25:54 +0300 Subject: [PATCH 3778/6300] [webconsole] fix warning, mobile page width Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 416cf1e4..d6b7a2ef 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -83,17 +83,18 @@ namespace http { " .disabled:after { color: #D33F3F; content: \"Disabled\" }\r\n" " .enabled:after { color: #56B734; content: \"Enabled\" }\r\n" " @media screen and (max-width: 980px) {\r\n" /* adaptive style */ + " body { padding: 1.5em 0 0 0; }\r\n" " .menu { width: 100%; display: block; float: none; position: unset; font-size: 16px;\r\n" " text-align: center; }\r\n" " .menu a, .commands a { padding: 2px; }\r\n" - " .content { float: none; margin: 0; margin-top: 16px; max-width: 100%; width: 100%;\r\n" + " .content { float: none; margin-left: unset; margin-top: 16px; max-width: 100%; width: 100%;\r\n" " text-align: center; }\r\n" " a, .slide label { /* margin-right: 10px; */ display: block; /* font-size: 18px; */ }\r\n" " .header { margin: unset; font-size: 1.5em; } small {display: block}\r\n" " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n" " color: initial; margin-top: 10px; padding: 6px; border: 1px solid #894c84; width: -webkit-fill-available; }\r\n" " input { width: 35%; text-align: center; padding: 5px;\r\n" - " border: 2px solid #ccc; -webkit-border-radius: 5px; border-radius: 5px; font-size: 24px; }\r\n" + " border: 2px solid #ccc; -webkit-border-radius: 5px; border-radius: 5px; font-size: 18px; }\r\n" " textarea { width: -webkit-fill-available; height: auto; padding:5px; border:2px solid #ccc;\r\n" " -webkit-border-radius: 5px; border-radius: 5px; font-size: 12px; }\r\n" " button[type=submit] { padding: 5px 15px; background: #ccc; border: 0 none; cursor: pointer;\r\n" @@ -933,7 +934,7 @@ namespace http { time_t t = divTime.quot; struct tm *tm = localtime(&t); char date[128]; - snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03ld", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem); + snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem); return date; } From 954781262c1be24673c9c7d57527eefe921aa065 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 24 Aug 2020 12:27:39 -0400 Subject: [PATCH 3779/6300] 2.33.0 --- ChangeLog | 23 +++++++++++++++++++ Win32/installer.iss | 2 +- android/build.gradle | 4 ++-- contrib/rpm/i2pd-git.spec | 5 +++- contrib/rpm/i2pd.spec | 5 +++- debian/changelog | 6 +++++ libi2pd/version.h | 6 ++--- qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml | 1 + 8 files changed, 44 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index 33792a97..362b28bf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,29 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.33.0] - 2020-08-24 +### Added +- Shared transient addresses +- crypto.ratchet.inboundTags paramater +- Multiple encryption keys through I2CP +- Pre-calculated x25519 ephemeral keys +- Change datagram routing path if nothing comes back in 10 seconds +- Shared routing path for datagram session +### Changed +- UDP tunnels send mix of repliable and raw datagrams in bulk +- Encrypt SSU packet again upon resend +- Start new tunnel message if remaining buffer is too small +- Use LeaseSet2 for ECIES-X25519-AEAD-Ratchet automatically +- Save new ECIES-X25519-AEAD-Ratchet session with NSR tagset +- Generate random padding lengths for ECIES-X25519-AEAD-Ratchet in bulk +- Webconsole layout +- Reseed servers list +### Fixed +- Don't connect through terminated SAM destination +- Differentiate UDP server sessions by port +- ECIES-X25519-AEAD-Ratchet through I2CP +- Don't save invalid address to AddressBook + ## [2.32.1] - 2020-06-02 ### Added - Read explicit peers in tunnels config diff --git a/Win32/installer.iss b/Win32/installer.iss index 40724d7a..fbbeba53 100644 --- a/Win32/installer.iss +++ b/Win32/installer.iss @@ -1,5 +1,5 @@ #define I2Pd_AppName "i2pd" -#define I2Pd_ver "2.32.1" +#define I2Pd_ver "2.33.0" #define I2Pd_Publisher "PurpleI2P" [Setup] diff --git a/android/build.gradle b/android/build.gradle index c9e8429c..ace2047a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -30,8 +30,8 @@ android { applicationId "org.purplei2p.i2pd" targetSdkVersion 29 minSdkVersion 14 - versionCode 2321 - versionName "2.32.1" + versionCode 2330 + versionName "2.33.0" setProperty("archivesBaseName", archivesBaseName + "-" + versionName) ndk { abiFilters 'armeabi-v7a' diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 7079ec23..00bea0b0 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -1,7 +1,7 @@ %define git_hash %(git rev-parse HEAD | cut -c -7) Name: i2pd-git -Version: 2.32.1 +Version: 2.33.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -124,6 +124,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Aug 24 2020 orignal - 2.33.0 +- update to 2.33.0 + * Tue Jun 02 2020 r4sas - 2.32.1 - update to 2.32.1 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 5c0b8fbd..22287f24 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.32.1 +Version: 2.33.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -122,6 +122,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Aug 24 2020 orignal - 2.33.0 +- update to 2.33.0 + * Tue Jun 02 2020 r4sas - 2.32.1 - update to 2.32.1 diff --git a/debian/changelog b/debian/changelog index b01f9519..bac3f3b3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.33.0-1) unstable; urgency=medium + + * updated to version 2.33.0/0.9.47 + + -- orignal Mon, 24 Aug 2020 16:00:00 +0000 + i2pd (2.32.1-1) unstable; urgency=high * updated to version 2.32.1 diff --git a/libi2pd/version.h b/libi2pd/version.h index c234516f..db30eee8 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -16,8 +16,8 @@ #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 32 -#define I2PD_VERSION_MICRO 1 +#define I2PD_VERSION_MINOR 33 +#define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) #define VERSION I2PD_VERSION @@ -30,7 +30,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 46 +#define I2P_VERSION_MICRO 47 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml index 75c57fb7..d3fa2e9e 100644 --- a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml +++ b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml @@ -35,6 +35,7 @@ + From a0685d804d158796adbdc96deb30fdadc65aa4e5 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 24 Aug 2020 12:48:09 -0400 Subject: [PATCH 3780/6300] 2.33.0 --- ChangeLog | 2 ++ appveyor.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 362b28bf..beef7a5a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,6 +23,8 @@ - Differentiate UDP server sessions by port - ECIES-X25519-AEAD-Ratchet through I2CP - Don't save invalid address to AddressBook +- ECDSA signatures names in SAM +- AppArmor profile ## [2.32.1] - 2020-06-02 ### Added diff --git a/appveyor.yml b/appveyor.yml index 0567127c..a6278d2d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.32.1.{build} +version: 2.33.0.{build} pull_requests: do_not_increment_build_number: true branches: From a05a54b38edba546dc77f9d60037ab4dcec1129e Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 7 Sep 2020 18:45:05 -0400 Subject: [PATCH 3781/6300] trim behind ECIESx25519 tags --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 3 +++ libi2pd/ECIESX25519AEADRatchetSession.h | 6 ++++-- libi2pd/Garlic.cpp | 21 ++++++++++++--------- libi2pd/Garlic.h | 1 - 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 51ca9242..229578a1 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -657,7 +657,10 @@ namespace garlic moreTags -= (receiveTagset->GetNextIndex () - index); } if (moreTags > 0) + { GenerateMoreReceiveTags (receiveTagset, moreTags); + receiveTagset->MoveTrimBehind (moreTags >> 1); // /2 + } } return true; } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 73e129d7..c9f21463 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -56,10 +56,12 @@ namespace garlic std::shared_ptr GetSession () { return m_Session.lock (); }; int GetTagSetID () const { return m_TagSetID; }; void SetTagSetID (int tagsetID) { m_TagSetID = tagsetID; }; + void MoveTrimBehind (int offset) { m_TrimBehindIndex += offset; }; void Expire (); bool IsExpired (uint64_t ts) const { return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; }; - + bool IsIndexExpired (int index) const { return m_Session.expired () || index < m_TrimBehindIndex; }; + private: union @@ -73,7 +75,7 @@ namespace garlic } m_KeyData; uint8_t m_SessTagConstant[32], m_SymmKeyCK[32], m_CurrentSymmKeyCK[64], m_NextRootKey[32]; - int m_NextIndex, m_NextSymmKeyIndex; + int m_NextIndex, m_NextSymmKeyIndex, m_TrimBehindIndex = 0; std::unordered_map > m_ItermediateSymmKeys; std::weak_ptr m_Session; int m_TagSetID = 0; diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 84a0519e..6d50aa1e 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -802,14 +802,6 @@ namespace garlic } } // ECIESx25519 - for (auto it = m_ECIESx25519Tags.begin (); it != m_ECIESx25519Tags.end ();) - { - if (it->second.tagset->IsExpired (ts) || ts > it->second.creationTime + ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT) - it = m_ECIESx25519Tags.erase (it); - else - ++it; - } - for (auto it = m_ECIESx25519Sessions.begin (); it != m_ECIESx25519Sessions.end ();) { if (it->second->CheckExpired (ts)) @@ -820,6 +812,17 @@ namespace garlic else ++it; } + + numExpiredTags = 0; + for (auto it = m_ECIESx25519Tags.begin (); it != m_ECIESx25519Tags.end ();) + { + if (it->second.tagset->IsExpired (ts) || it->second.tagset->IsIndexExpired (it->second.index)) + it = m_ECIESx25519Tags.erase (it); + else + ++it; + } + if (numExpiredTags > 0) + LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " ECIESx25519 tags expired for ", GetIdentHash().ToBase64 ()); } void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID) @@ -1009,7 +1012,7 @@ namespace garlic { auto index = tagset->GetNextIndex (); uint64_t tag = tagset->GetNextSessionTag (); - m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{index, tagset, i2p::util::GetSecondsSinceEpoch ()}); + m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{index, tagset}); } void GarlicDestination::AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session) diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 2168ee81..b97eaab2 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -220,7 +220,6 @@ namespace garlic { int index; RatchetTagSetPtr tagset; - uint64_t creationTime; // seconds since epoch }; class GarlicDestination: public i2p::data::LocalDestination From da1e52357f9b11dd45e240afbc8d1e6248eeb9b5 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 8 Sep 2020 07:46:55 -0400 Subject: [PATCH 3782/6300] delete symmkey on cleanup --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 9 ++++++++- libi2pd/ECIESX25519AEADRatchetSession.h | 3 ++- libi2pd/Garlic.cpp | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 229578a1..885dc1fc 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -88,6 +88,11 @@ namespace garlic } } + void RatchetTagSet::DeleteSymmKey (int index) + { + m_ItermediateSymmKeys.erase (index); + } + void RatchetTagSet::Expire () { if (!m_ExpirationTimestamp) @@ -659,7 +664,9 @@ namespace garlic if (moreTags > 0) { GenerateMoreReceiveTags (receiveTagset, moreTags); - receiveTagset->MoveTrimBehind (moreTags >> 1); // /2 + index -= (moreTags >> 1); // /2 + if (index > 0) + receiveTagset->SetTrimBehind (index); } } return true; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index c9f21463..c7451b98 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -52,11 +52,12 @@ namespace garlic const uint8_t * GetNextRootKey () const { return m_NextRootKey; }; int GetNextIndex () const { return m_NextIndex; }; void GetSymmKey (int index, uint8_t * key); + void DeleteSymmKey (int index); std::shared_ptr GetSession () { return m_Session.lock (); }; int GetTagSetID () const { return m_TagSetID; }; void SetTagSetID (int tagsetID) { m_TagSetID = tagsetID; }; - void MoveTrimBehind (int offset) { m_TrimBehindIndex += offset; }; + void SetTrimBehind (int index) { if (index > m_TrimBehindIndex) m_TrimBehindIndex = index; }; void Expire (); bool IsExpired (uint64_t ts) const { return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; }; diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 6d50aa1e..6bdbb630 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -817,7 +817,10 @@ namespace garlic for (auto it = m_ECIESx25519Tags.begin (); it != m_ECIESx25519Tags.end ();) { if (it->second.tagset->IsExpired (ts) || it->second.tagset->IsIndexExpired (it->second.index)) + { + it->second.tagset->DeleteSymmKey (it->second.index); it = m_ECIESx25519Tags.erase (it); + } else ++it; } From d0d71c93af3859fd24491678b37d500f1e4769ef Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 10 Sep 2020 19:27:29 -0400 Subject: [PATCH 3783/6300] set LeaseSet type to 3 for ratchets if not specified --- libi2pd/Destination.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index a764224c..c8940383 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -855,8 +855,7 @@ namespace client // extract encryption type params for LS2 std::set encryptionKeyTypes; - if ((GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2 || - GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) && params) + if (params) { auto it = params->find (I2CP_PARAM_LEASESET_ENCRYPTION_TYPE); if (it != params->end ()) From 2b0d1a2190743135f3a412c0d106b2e520e304d0 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 15 Sep 2020 19:39:18 -0400 Subject: [PATCH 3784/6300] implement DatabaseLookupTagSet --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 50 +++++++++++++++++++++++ libi2pd/ECIESX25519AEADRatchetSession.h | 21 +++++++++- libi2pd/Garlic.cpp | 3 +- 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 885dc1fc..aef273ed 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -99,6 +99,56 @@ namespace garlic m_ExpirationTimestamp = i2p::util::GetSecondsSinceEpoch () + ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT; } + bool RatchetTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) + { + auto session = GetSession (); + if (!session) return false; + return session->HandleNextMessage (buf, len, shared_from_this (), index); + } + + DatabaseLookupTagSet::DatabaseLookupTagSet (GarlicDestination * destination, const uint8_t * key): + RatchetTagSet (nullptr), m_Destination (destination) + { + memcpy (m_Key, key, 32); + Expire (); + } + + bool DatabaseLookupTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) + { + if (len < 24) return false; + uint8_t nonce[12]; + memset (nonce, 0, 12); // n = 0 + size_t offset = 8; // first 8 bytes is reply tag used as AD + len -= 16; // poly1305 + if (!i2p::crypto::AEADChaCha20Poly1305 (buf + offset, len - offset, buf, 8, m_Key, nonce, buf + offset, len - offset, false)) // decrypt + { + LogPrint (eLogWarning, "Garlic: Lookup reply AEAD decryption failed"); + return false; + } + // we assume 1 I2NP block with delivery type local + if (offset + 3 > len) + { + LogPrint (eLogWarning, "Garlic: Lookup reply is too short ", len); + return false; + } + if (buf[offset] != eECIESx25519BlkGalicClove) + { + LogPrint (eLogWarning, "Garlic: Lookup reply unexpected block ", (int)buf[offset]); + return false; + } + offset++; + auto size = bufbe16toh (buf + offset); + offset += 2; + if (offset + size > len) + { + LogPrint (eLogWarning, "Garlic: Lookup reply block is too long ", size); + return false; + } + if (m_Destination) + m_Destination->HandleECIESx25519GarlicClove (buf + offset, size); + return true; + } + ECIESX25519AEADRatchetSession::ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet): GarlicRoutingSession (owner, attachLeaseSet) { diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index c7451b98..af0b5de5 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -40,7 +40,7 @@ namespace garlic // - 16 /* I2NP header */ - 16 /* poly hash */ - 8 /* tag */ - 4 /* garlic length */ class ECIESX25519AEADRatchetSession; - class RatchetTagSet + class RatchetTagSet: public std::enable_shared_from_this { public: @@ -61,7 +61,9 @@ namespace garlic void Expire (); bool IsExpired (uint64_t ts) const { return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; }; - bool IsIndexExpired (int index) const { return m_Session.expired () || index < m_TrimBehindIndex; }; + virtual bool IsIndexExpired (int index) const { return m_Session.expired () || index < m_TrimBehindIndex; }; + + virtual bool HandleNextMessage (uint8_t * buf, size_t len, int index); private: @@ -94,6 +96,21 @@ namespace garlic std::shared_ptr m_DummySession; // we need a strong pointer for NS }; + + class DatabaseLookupTagSet: public RatchetTagSet + { + public: + + DatabaseLookupTagSet (GarlicDestination * destination, const uint8_t * key); + + bool IsIndexExpired (int index) const { return false; }; + bool HandleNextMessage (uint8_t * buf, size_t len, int index); + + private: + + GarlicDestination * m_Destination; + uint8_t m_Key[32]; + }; enum ECIESx25519BlockType { diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 6bdbb630..05d7fd3e 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -507,8 +507,7 @@ namespace garlic if (it1 != m_ECIESx25519Tags.end ()) { found = true; - auto session = it1->second.tagset->GetSession (); - if (!session || !session->HandleNextMessage (buf, length, it1->second.tagset, it1->second.index)) + if (!it1->second.tagset->HandleNextMessage (buf, length, it1->second.index)) LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); m_ECIESx25519Tags.erase (it1); } From 024c29b180d2ad6bfcf6179d64119a14ef94f9c4 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 17 Sep 2020 21:11:46 -0400 Subject: [PATCH 3785/6300] eliminate boost/bind --- daemon/HTTPServer.cpp | 5 ++-- daemon/UPnP.cpp | 53 ++++++++++++++++++------------------------ libi2pd/SSU.cpp | 1 - libi2pd/SSUData.cpp | 1 - libi2pd/SSUSession.cpp | 1 - 5 files changed, 24 insertions(+), 37 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index d6b7a2ef..52b655b9 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include "Base.h" @@ -1321,8 +1320,8 @@ namespace http { void HTTPServer::Accept () { auto newSocket = std::make_shared (m_Service); - m_Acceptor.async_accept (*newSocket, boost::bind (&HTTPServer::HandleAccept, this, - boost::asio::placeholders::error, newSocket)); + m_Acceptor.async_accept (*newSocket, std::bind (&HTTPServer::HandleAccept, this, + std::placeholders::_1, newSocket)); } void HTTPServer::HandleAccept(const boost::system::error_code& ecode, diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index 852122f2..92a41011 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -1,18 +1,9 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #ifdef USE_UPNP #include #include #include #include -#include #include "Log.h" @@ -88,10 +79,10 @@ namespace transport void UPnP::Discover () { bool isError; - int err; + int err; #if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) - err = UPNPDISCOVER_SUCCESS; + err = UPNPDISCOVER_SUCCESS; #if (MINIUPNPC_API_VERSION >= 14) m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0, 0, 2, &err); @@ -100,9 +91,9 @@ namespace transport #endif isError = err != UPNPDISCOVER_SUCCESS; -#else // MINIUPNPC_API_VERSION >= 8 - err = 0; - m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0); +#else // MINIUPNPC_API_VERSION >= 8 + err = 0; + m_Devlist = upnpDiscover (UPNP_RESPONSE_TIMEOUT, NULL, NULL, 0); isError = m_Devlist == NULL; #endif // MINIUPNPC_API_VERSION >= 8 { @@ -113,15 +104,15 @@ namespace transport if (isError) { - LogPrint (eLogError, "UPnP: unable to discover Internet Gateway Devices: error ", err); + LogPrint (eLogError, "UPnP: unable to discover Internet Gateway Devices: error ", err); return; } err = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); - m_upnpUrlsInitialized = err != 0; + m_upnpUrlsInitialized=err!=0; if (err == UPNP_IGD_VALID_CONNECTED) { - err = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); + err = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); if(err != UPNPCOMMAND_SUCCESS) { LogPrint (eLogError, "UPnP: unable to get external address: error ", err); @@ -132,14 +123,14 @@ namespace transport LogPrint (eLogError, "UPnP: found Internet Gateway Device ", m_upnpUrls.controlURL); if (!m_externalIPAddress[0]) { - LogPrint (eLogError, "UPnP: found Internet Gateway Device doesn't know our external address"); + LogPrint (eLogError, "UPnP: found Internet Gateway Device doesn't know our external address"); return; } } } else { - LogPrint (eLogError, "UPnP: unable to find valid Internet Gateway Device: error ", err); + LogPrint (eLogError, "UPnP: unable to find valid Internet Gateway Device: error ", err); return; } @@ -190,7 +181,7 @@ namespace transport err = CheckMapping (strPort.c_str (), strType.c_str ()); if (err != UPNPCOMMAND_SUCCESS) // if mapping not found { - LogPrint (eLogDebug, "UPnP: possibly port ", strPort, " is not forwarded: return code ", err); + LogPrint (eLogDebug, "UPnP: possibly port ", strPort, " is not forwarded: return code ", err); #if ((MINIUPNPC_API_VERSION >= 8) || defined (UPNPDISCOVER_SUCCESS)) err = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), NULL, NULL); @@ -210,7 +201,7 @@ namespace transport } else { - LogPrint (eLogDebug, "UPnP: external forward from ", m_NetworkAddr, ":", strPort, " exists on current Internet Gateway Device"); + LogPrint (eLogDebug, "UPnP: external forward from ", m_NetworkAddr, ":", strPort, " exists on current Internet Gateway Device"); return; } } @@ -227,14 +218,14 @@ namespace transport void UPnP::CloseMapping (std::shared_ptr address) { - if(!m_upnpUrlsInitialized) { - return; - } + if(!m_upnpUrlsInitialized) { + return; + } std::string strType (GetProto (address)), strPort (std::to_string (address->port)); int err = UPNPCOMMAND_SUCCESS; - + err = CheckMapping (strPort.c_str (), strType.c_str ()); - if (err == UPNPCOMMAND_SUCCESS) + if (err == UPNPCOMMAND_SUCCESS) { err = UPNP_DeletePortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), NULL); LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", err); @@ -245,11 +236,11 @@ namespace transport { freeUPNPDevlist (m_Devlist); m_Devlist = 0; - if(m_upnpUrlsInitialized){ - FreeUPNPUrls (&m_upnpUrls); - m_upnpUrlsInitialized=false; - } - } + if(m_upnpUrlsInitialized){ + FreeUPNPUrls (&m_upnpUrls); + m_upnpUrlsInitialized=false; + } + } std::string UPnP::GetProto (std::shared_ptr address) { diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index c435715f..0f4526bd 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -7,7 +7,6 @@ */ #include -#include #include "Log.h" #include "Timestamp.h" #include "RouterContext.h" diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index bd188ab7..4e0e712f 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -7,7 +7,6 @@ */ #include -#include #include "Log.h" #include "Timestamp.h" #include "NetDb.hpp" diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index fca074b3..399b2fc7 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -6,7 +6,6 @@ * See full license text in LICENSE file at top of project tree */ -#include #include "version.h" #include "Crypto.h" #include "Log.h" From 09fdb068d2cbf70c4c4162748bb047331de69b47 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 19 Sep 2020 21:15:42 -0400 Subject: [PATCH 3786/6300] Database lookups from ECIES destinations --- libi2pd/Destination.cpp | 11 ++++++++--- libi2pd/Garlic.cpp | 8 ++++++++ libi2pd/Garlic.h | 1 + libi2pd/I2NPProtocol.cpp | 18 ++++++++++++++---- libi2pd/I2NPProtocol.h | 5 +++-- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index c8940383..6b51fe1a 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -744,14 +744,19 @@ namespace client request->excluded.insert (nextFloodfill->GetIdentHash ()); request->requestTimeoutTimer.cancel (); + bool isECIES = SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) && + nextFloodfill->GetVersion () >= MAKE_VERSION_NUMBER(0, 9, 46); // >= 0.9.46; uint8_t replyKey[32], replyTag[32]; RAND_bytes (replyKey, 32); // random session key - RAND_bytes (replyTag, 32); // random session tag - AddSessionKey (replyKey, replyTag); + RAND_bytes (replyTag, isECIES ? 8 : 32); // random session tag + if (isECIES) + AddECIESx25519Key (replyKey, replyTag); + else + AddSessionKey (replyKey, replyTag); auto msg = WrapMessage (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, - request->replyTunnel, replyKey, replyTag)); + request->replyTunnel, replyKey, replyTag, isECIES)); request->outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 05d7fd3e..429a2092 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -460,6 +460,14 @@ namespace garlic } } + void GarlicDestination::AddECIESx25519Key (const uint8_t * key, const uint8_t * tag) + { + uint64_t t; + memcpy (&t, tag, 8); + auto tagset = std::make_shared(this, key); + m_ECIESx25519Tags.emplace (t, ECIESX25519AEADRatchetIndexTagset{0, tagset}); + } + bool GarlicDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) { AddSessionKey (key, tag); diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index b97eaab2..f1e363df 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -241,6 +241,7 @@ namespace garlic std::shared_ptr msg, bool attachLeaseSet = false); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag + void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); void AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset); diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 53afbca4..36e7a763 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -171,7 +171,8 @@ namespace i2p std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, const std::set& excludedFloodfills, - std::shared_ptr replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag) + std::shared_ptr replyTunnel, const uint8_t * replyKey, + const uint8_t * replyTag, bool replyECIES) { int cnt = excludedFloodfills.size (); auto m = cnt > 7 ? NewI2NPMessage () : NewI2NPShortMessage (); @@ -180,7 +181,8 @@ namespace i2p buf += 32; memcpy (buf, replyTunnel->GetNextIdentHash (), 32); // reply tunnel GW buf += 32; - *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_ENCRYPTION_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags + *buf = DATABASE_LOOKUP_DELIVERY_FLAG | DATABASE_LOOKUP_TYPE_LEASESET_LOOKUP; // flags + *buf |= (replyECIES ? DATABASE_LOOKUP_ECIES_FLAG : DATABASE_LOOKUP_ENCRYPTION_FLAG); buf ++; htobe32buf (buf, replyTunnel->GetNextTunnelID ()); // reply tunnel ID buf += 4; @@ -204,8 +206,16 @@ namespace i2p // encryption memcpy (buf, replyKey, 32); buf[32] = 1; // 1 tag - memcpy (buf + 33, replyTag, 32); - buf += 65; + if (replyECIES) + { + memcpy (buf + 33, replyTag, 8); // 8 bytes tag + buf += 41; + } + else + { + memcpy (buf + 33, replyTag, 32); // 32 bytes tag + buf += 65; + } m->len += (buf - m->GetPayload ()); m->FillI2NPMessageHeader (eI2NPDatabaseLookup); diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 695de798..fe5ca968 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -251,8 +251,9 @@ namespace tunnel std::shared_ptr CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from, uint32_t replyTunnelID, bool exploratory = false, std::set * excludedPeers = nullptr); std::shared_ptr CreateLeaseSetDatabaseLookupMsg (const i2p::data::IdentHash& dest, - const std::set& excludedFloodfills, - std::shared_ptr replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag); + const std::set& excludedFloodfills, + std::shared_ptr replyTunnel, + const uint8_t * replyKey, const uint8_t * replyTag, bool replyECIES = false); std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0); From f939a7b349a930f2f2da5aee654095c50fa53500 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 21 Sep 2020 19:22:53 -0400 Subject: [PATCH 3787/6300] reduce variable tunnel build length to 4 --- libi2pd/Tunnel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 1fb12af9..a50bc31a 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -36,7 +36,7 @@ namespace tunnel const int TUNNEL_EXPIRATION_THRESHOLD = 60; // 1 minute const int TUNNEL_RECREATION_THRESHOLD = 90; // 1.5 minutes const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds - const int STANDARD_NUM_RECORDS = 5; // in VariableTunnelBuild message + const int STANDARD_NUM_RECORDS = 4; // in VariableTunnelBuild message enum TunnelState { From 335f9394a5727596fc9a196b4c622464721e69b9 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 26 Sep 2020 19:32:19 -0400 Subject: [PATCH 3788/6300] drop gcc 4.7 support --- Makefile.linux | 2 +- daemon/I2PControl.cpp | 69 +++++++++++++++-------------------------- libi2pd/Destination.cpp | 3 +- 3 files changed, 27 insertions(+), 47 deletions(-) diff --git a/Makefile.linux b/Makefile.linux index 1cdf0a48..3ef4793c 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -15,7 +15,7 @@ ifeq ($(shell expr match $(CXX) 'clang'),5) NEEDED_CXXFLAGS += -std=c++11 else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10 NEEDED_CXXFLAGS += -std=c++11 -else ifeq ($(shell expr match ${CXXVER} "4\.[7-9]"),3) # gcc 4.7 - 4.9 +else ifeq ($(shell expr match ${CXXVER} "4\.[8-9]"),3) # gcc 4.8 - 4.9 NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 else ifeq ($(shell expr match ${CXXVER} "[5-6]"),1) # gcc 5 - 6 NEEDED_CXXFLAGS += -std=c++11 diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index 77614f2f..e8c6e031 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include #include @@ -14,12 +6,7 @@ #include #include #include - -// There is bug in boost 1.49 with gcc 4.7 coming with Debian Wheezy -#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) -#if !GCC47_BOOST149 #include -#endif #include "Crypto.h" #include "FS.h" @@ -67,28 +54,29 @@ namespace client m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem); // handlers - m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; - m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; - m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; - m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler; - m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; - m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler; - m_MethodHandlers["ClientServicesInfo"] = &I2PControlService::ClientServicesInfoHandler; + m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; + m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; + m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; + m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler; + m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; + m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler; + m_MethodHandlers["ClientServicesInfo"] = &I2PControlService::ClientServicesInfoHandler; // I2PControl m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler; // RouterInfo - m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlService::UptimeHandler; - m_RouterInfoHandlers["i2p.router.version"] = &I2PControlService::VersionHandler; - m_RouterInfoHandlers["i2p.router.status"] = &I2PControlService::StatusHandler; - m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlService::NetDbKnownPeersHandler; - m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlService::NetDbActivePeersHandler; - m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlService::InboundBandwidth1S; - m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlService::OutboundBandwidth1S; - m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlService::NetStatusHandler; + m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlService::UptimeHandler; + m_RouterInfoHandlers["i2p.router.version"] = &I2PControlService::VersionHandler; + m_RouterInfoHandlers["i2p.router.status"] = &I2PControlService::StatusHandler; + m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlService::NetDbKnownPeersHandler; + m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlService::NetDbActivePeersHandler; + m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlService::InboundBandwidth1S; + m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlService::OutboundBandwidth1S; + m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlService::NetStatusHandler; m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlService::TunnelsParticipatingHandler; - m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = &I2PControlService::TunnelsSuccessRateHandler; + m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = +&I2PControlService::TunnelsSuccessRateHandler; m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlService::NetTotalReceivedBytes; m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlService::NetTotalSentBytes; @@ -104,10 +92,10 @@ namespace client // ClientServicesInfo m_ClientServicesInfoHandlers["I2PTunnel"] = &I2PControlService::I2PTunnelInfoHandler; m_ClientServicesInfoHandlers["HTTPProxy"] = &I2PControlService::HTTPProxyInfoHandler; - m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlService::SOCKSInfoHandler; - m_ClientServicesInfoHandlers["SAM"] = &I2PControlService::SAMInfoHandler; - m_ClientServicesInfoHandlers["BOB"] = &I2PControlService::BOBInfoHandler; - m_ClientServicesInfoHandlers["I2CP"] = &I2PControlService::I2CPInfoHandler; + m_ClientServicesInfoHandlers["SOCKS"] = &I2PControlService::SOCKSInfoHandler; + m_ClientServicesInfoHandlers["SAM"] = &I2PControlService::SAMInfoHandler; + m_ClientServicesInfoHandlers["BOB"] = &I2PControlService::BOBInfoHandler; + m_ClientServicesInfoHandlers["I2CP"] = &I2PControlService::I2CPInfoHandler; } I2PControlService::~I2PControlService () @@ -242,12 +230,6 @@ namespace client } } std::ostringstream response; -#if GCC47_BOOST149 - LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7"); - response << "{\"id\":null,\"error\":"; - response << "{\"code\":-32603,\"message\":\"JSON requests is not supported with this version of boost\"},"; - response << "\"jsonrpc\":\"2.0\"}"; -#else boost::property_tree::ptree pt; boost::property_tree::read_json (ss, pt); @@ -267,7 +249,6 @@ namespace client response << "{\"code\":-32601,\"message\":\"Method not found\"},"; response << "\"jsonrpc\":\"2.0\"}"; } -#endif SendResponse (socket, buf, response, isHtml); } catch (std::exception& ex) @@ -408,8 +389,8 @@ namespace client auto it1 = m_RouterInfoHandlers.find (it->first); if (it1 != m_RouterInfoHandlers.end ()) { - if (!first) results << ","; - else first = false; + if (!first) results << ","; + else first = false; (this->*(it1->second))(results); } else @@ -507,7 +488,7 @@ namespace client m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) - { + { Daemon.running = 0; }); } @@ -521,7 +502,7 @@ namespace client m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) - { + { Daemon.running = 0; }); } diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 6b51fe1a..cd0ad9ab 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -603,9 +603,8 @@ namespace client return; } auto s = shared_from_this (); - // we must capture this for gcc 4.7 due the bug RequestLeaseSet (ls->GetStoreHash (), - [s, ls, this](std::shared_ptr leaseSet) + [s, ls](std::shared_ptr leaseSet) { if (leaseSet) { From 4d850793727f067a64c12499123325a911da1a89 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 27 Sep 2020 17:46:15 -0400 Subject: [PATCH 3789/6300] correct addressbook request --- libi2pd_client/AddressBook.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index 7af1272d..c044a047 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -806,6 +806,7 @@ namespace client i2p::http::HTTPReq req; req.AddHeader("Host", dest_host); req.AddHeader("User-Agent", "Wget/1.11.4"); + req.AddHeader("Accept-Encoding", "gzip"); req.AddHeader("X-Accept-Encoding", "x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0"); req.AddHeader("Connection", "close"); if (!m_Etag.empty()) @@ -816,6 +817,7 @@ namespace client url.schema = ""; url.host = ""; req.uri = url.to_string(); + req.version = "HTTP/1.1"; 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()); @@ -895,7 +897,7 @@ namespace client i2p::http::MergeChunkedResponse (in, out); response = out.str(); } - else if (res.is_gzipped()) + if (res.is_gzipped()) { std::stringstream out; i2p::data::GzipInflator inflator; From 949fc47f311bb7c33ef073fc5e05cb369214718a Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 27 Sep 2020 19:07:58 -0400 Subject: [PATCH 3790/6300] two tunnels for shared local destination --- libi2pd_client/ClientContext.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index fc48ddbb..b24ce3ce 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -401,7 +401,14 @@ namespace client void ClientContext::CreateNewSharedLocalDestination () { - m_SharedLocalDestination = CreateNewLocalDestination (); // non-public, EDDSA + std::map params + { + { I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, "2" }, + { I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, "2" }, + { I2CP_PARAM_LEASESET_TYPE, "3" } + }; + m_SharedLocalDestination = CreateNewLocalDestination (false, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, + i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, ¶ms); // non-public, EDDSA m_SharedLocalDestination->Acquire (); } From 489c38ec5b13c1be33b22ff887f96f925cd49866 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 27 Sep 2020 19:19:48 -0400 Subject: [PATCH 3791/6300] read Last-Modified --- libi2pd_client/AddressBook.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index c044a047..5c1cffbb 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -889,7 +889,7 @@ namespace client /* 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"); + it = res.headers.find("Last-Modified"); if (it != res.headers.end()) m_LastModified = it->second; if (res.is_chunked()) { From 5f42888b6118277b1569551296fef3759593ff25 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 28 Sep 2020 03:43:47 +0300 Subject: [PATCH 3792/6300] [appveyor] disable fix introdued in 7864053 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index a6278d2d..378fadc8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,7 +23,7 @@ environment: install: - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" # TODO: revert that change when appveyor's images will be updated -- c:\msys64\usr\bin\bash -l "build/appveyor-msys2-upgrade.bash" +#- c:\msys64\usr\bin\bash -l "build/appveyor-msys2-upgrade.bash" # update runtime - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" # update packages and install required From dec7a9a01c30238399d5b5bb86c36d4376708d58 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 27 Sep 2020 20:50:57 -0400 Subject: [PATCH 3793/6300] shared transient destination between proxies --- libi2pd/Config.cpp | 4 ++-- libi2pd_client/ClientContext.cpp | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 6ce3a714..7668d6a7 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -96,7 +96,7 @@ namespace config { ("httpproxy.enabled", value()->default_value(true), "Enable or disable HTTP Proxy") ("httpproxy.address", value()->default_value("127.0.0.1"), "HTTP Proxy listen address") ("httpproxy.port", value()->default_value(4444), "HTTP Proxy listen port") - ("httpproxy.keys", value()->default_value(""), "File to persist HTTP Proxy keys") + ("httpproxy.keys", value()->default_value("transient-proxy"), "File to persist HTTP Proxy keys. Transient by default") ("httpproxy.signaturetype", value()-> default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), "Signature type for new keys. 7 (EdDSA) by default") ("httpproxy.inbound.length", value()->default_value("3"), "HTTP proxy inbound tunnel length") @@ -116,7 +116,7 @@ namespace config { ("socksproxy.enabled", value()->default_value(true), "Enable or disable SOCKS Proxy") ("socksproxy.address", value()->default_value("127.0.0.1"), "SOCKS Proxy listen address") ("socksproxy.port", value()->default_value(4447), "SOCKS Proxy listen port") - ("socksproxy.keys", value()->default_value(""), "File to persist SOCKS Proxy keys") + ("socksproxy.keys", value()->default_value("transient-proxy"), "File to persist SOCKS Proxy keys. Transient by default") ("socksproxy.signaturetype", value()-> default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519), "Signature type for new keys. 7 (EdDSA) by default") ("socksproxy.inbound.length", value()->default_value("3"), "SOCKS proxy inbound tunnel length") diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index b24ce3ce..e1316a7f 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -844,6 +844,8 @@ namespace client bool socksproxy; i2p::config::GetOption("socksproxy.enabled", socksproxy); if (socksproxy) { + std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys); + // we still need httpProxyKeys to compare with sockProxyKeys std::string socksProxyKeys; i2p::config::GetOption("socksproxy.keys", socksProxyKeys); std::string socksProxyAddr; i2p::config::GetOption("socksproxy.address", socksProxyAddr); uint16_t socksProxyPort; i2p::config::GetOption("socksproxy.port", socksProxyPort); @@ -852,7 +854,12 @@ namespace client uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort); i2p::data::SigningKeyType sigType; i2p::config::GetOption("socksproxy.signaturetype", sigType); LogPrint(eLogInfo, "Clients: starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); - if (socksProxyKeys.length () > 0) + if (httpProxyKeys == socksProxyKeys && m_HttpProxy) + { + localDestination = m_HttpProxy->GetLocalDestination (); + localDestination->Acquire (); + } + else if (socksProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; if (LoadPrivateKeys (keys, socksProxyKeys, sigType)) From 730c6aff11e7617cde385e4e0e63a1644db642e0 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 28 Sep 2020 04:08:39 +0300 Subject: [PATCH 3794/6300] Update appveyor.yml --- appveyor.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 378fadc8..0e7dc3a9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,9 +21,13 @@ environment: MSYS_BITNESS: 32 install: +# install new signing keyring +- c:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" +- c:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" +- c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" +- c:\msys64\usr\bin\bash -lc "pacman -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" +# remove packages which can break build - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" -# TODO: revert that change when appveyor's images will be updated -#- c:\msys64\usr\bin\bash -l "build/appveyor-msys2-upgrade.bash" # update runtime - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" # update packages and install required From cb41c0455185beff299f10c46c0b8033609ae543 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 28 Sep 2020 04:10:11 +0300 Subject: [PATCH 3795/6300] [appveyor] install keyring package without question --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 0e7dc3a9..11f6c9fd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,7 +25,7 @@ install: - c:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" - c:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" -- c:\msys64\usr\bin\bash -lc "pacman -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" +- c:\msys64\usr\bin\bash -lc "pacman -y -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" # remove packages which can break build - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" # update runtime From 208707c00b86d130ae7ddd565b03c27b0e5c1438 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 28 Sep 2020 04:11:40 +0300 Subject: [PATCH 3796/6300] [appveyor] install keyring package without question --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 11f6c9fd..5b365634 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,7 +25,7 @@ install: - c:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" - c:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" -- c:\msys64\usr\bin\bash -lc "pacman -y -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" +- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" # remove packages which can break build - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" # update runtime From 7d34f1e8831d9b3d5702281ae64e19ecf68b7664 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 28 Sep 2020 04:20:18 +0300 Subject: [PATCH 3797/6300] [appveyor] add delay before second try --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 5b365634..c233f535 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,6 +30,8 @@ install: - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" # update runtime - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" +# Sleep for 5 seconds before next try? +- ping -n 5 127.0.0.1 > nul # update packages and install required - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu ${MSYS_PACKAGES}" From 221b7cbf76bf6a3244f354a0181ace6300e5d96a Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 28 Sep 2020 04:29:43 +0300 Subject: [PATCH 3798/6300] [appveyor] kill bash before second try Ok, just waiting for bash termination doesn't works, so lets kill it. --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index c233f535..4b6c8b9a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,8 +30,8 @@ install: - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" # update runtime - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" -# Sleep for 5 seconds before next try? -- ping -n 5 127.0.0.1 > nul +# Kill bash before next try +- taskkill /T /F /IM bash.exe # update packages and install required - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu ${MSYS_PACKAGES}" From beb5d26e6d1aff804438285f9918216f56e3a298 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 28 Sep 2020 04:44:18 +0300 Subject: [PATCH 3799/6300] [appveyor] kill gpg --- appveyor.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 4b6c8b9a..07308d10 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,7 +31,9 @@ install: # update runtime - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" # Kill bash before next try -- taskkill /T /F /IM bash.exe +- taskkill /T /F /IM bash.exe /IM gpg.exe /IM gpg-agent.exe +# For debug purposes +- tasklist # update packages and install required - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu ${MSYS_PACKAGES}" From 873b4f3178d2d6fc570d206fa11512f38394a2a6 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 28 Sep 2020 04:48:57 +0300 Subject: [PATCH 3800/6300] [appveyor] suppress error code --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 07308d10..83dee927 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,7 +31,7 @@ install: # update runtime - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" # Kill bash before next try -- taskkill /T /F /IM bash.exe /IM gpg.exe /IM gpg-agent.exe +- taskkill /T /F /IM bash.exe /IM gpg.exe /IM gpg-agent.exe | exit /B 0 # For debug purposes - tasklist # update packages and install required From ac5a4fe70ff4d6226da8c9bac150f1f4f7e8044d Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 28 Sep 2020 04:51:13 +0300 Subject: [PATCH 3801/6300] [appveyor] remove tasklist print --- appveyor.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 83dee927..9d035653 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -32,8 +32,6 @@ install: - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" # Kill bash before next try - taskkill /T /F /IM bash.exe /IM gpg.exe /IM gpg-agent.exe | exit /B 0 -# For debug purposes -- tasklist # update packages and install required - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu ${MSYS_PACKAGES}" From fd9229c4676b158137532bb61aee0ffb84408263 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 30 Sep 2020 17:12:28 -0400 Subject: [PATCH 3802/6300] ping/pong for streaming --- libi2pd/Destination.cpp | 10 +++++++--- libi2pd/Destination.h | 6 +++++- libi2pd/Streaming.cpp | 31 +++++++++++++++++++++++++++++++ libi2pd/Streaming.h | 2 ++ libi2pd_client/ClientContext.cpp | 1 + 5 files changed, 46 insertions(+), 4 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index cd0ad9ab..af3b4cbc 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -849,8 +849,9 @@ namespace client ClientDestination::ClientDestination (boost::asio::io_service& service, const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): - LeaseSetDestination (service, isPublic, params), - m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), + LeaseSetDestination (service, isPublic, params), + m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY), + m_IsStreamingAnswerPings (DEFAULT_ANSWER_PINGS), m_DatagramDestination (nullptr), m_RefCounter (0), m_ReadyChecker(service) { @@ -918,7 +919,10 @@ namespace client auto it = params->find (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY); if (it != params->end ()) m_StreamingAckDelay = std::stoi(it->second); - + it = params->find (I2CP_PARAM_STREAMING_ANSWER_PINGS); + if (it != params->end ()) + m_IsStreamingAnswerPings = (it->second == "true"); + if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) { // authentication for encrypted LeaseSet diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 1d5c0a55..a7567534 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -78,6 +78,8 @@ namespace client // streaming const char I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY[] = "i2p.streaming.initialAckDelay"; const int DEFAULT_INITIAL_ACK_DELAY = 200; // milliseconds + const char I2CP_PARAM_STREAMING_ANSWER_PINGS[] = "i2p.streaming.answerPings"; + const int DEFAULT_ANSWER_PINGS = true; typedef std::function stream)> StreamRequestComplete; @@ -242,7 +244,8 @@ namespace client bool IsAcceptingStreams () const; void AcceptOnce (const i2p::stream::StreamingDestination::Acceptor& acceptor); int GetStreamingAckDelay () const { return m_StreamingAckDelay; } - + bool IsStreamingAnswerPings () const { return m_IsStreamingAnswerPings; } + // datagram i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); @@ -275,6 +278,7 @@ namespace client std::unique_ptr m_ECIESx25519EncryptionKey; int m_StreamingAckDelay; + bool m_IsStreamingAnswerPings; std::shared_ptr m_StreamingDestination; // default std::map > m_StreamingDestinationsByPorts; i2p::datagram::DatagramDestination * m_DatagramDestination; diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index ff8915c0..ab08f41f 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -351,6 +351,28 @@ namespace stream return true; } + void Stream::HandlePing (Packet * packet) + { + uint16_t flags = packet->GetFlags (); + if (ProcessOptions (flags, packet) && m_RemoteIdentity) + { + // send pong + Packet p; + memset (p.buf, 0, 22); // minimal header all zeroes + memcpy (p.buf + 4, packet->buf, 4); // but receiveStreamID is the sendStreamID from the ping + htobe16buf (p.buf + 18, PACKET_FLAG_ECHO); // and echo flag + ssize_t payloadLen = packet->len - (packet->GetPayload () - packet->buf); + if (payloadLen > 0) + memcpy (p.buf + 22, packet->GetPayload (), payloadLen); + else + payloadLen = 0; + p.len = payloadLen + 22; + SendPackets (std::vector { &p }); + LogPrint (eLogDebug, "Streaming: Pong of ", p.len, " bytes sent"); + } + m_LocalDestination.DeletePacket (packet); + } + void Stream::ProcessAck (Packet * packet) { bool acknowledged = false; @@ -609,6 +631,7 @@ namespace stream packet[size] = 0; size++; // NACK count } + packet[size] = 0; size++; // resend delay htobuf16 (packet + size, 0); // no flags set size += 2; // flags @@ -666,6 +689,7 @@ namespace stream size += 4; // ack Through packet[size] = 0; size++; // NACK count + packet[size] = 0; size++; // resend delay htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); size += 2; // flags @@ -1016,6 +1040,13 @@ namespace stream auto it = m_Streams.find (sendStreamID); if (it != m_Streams.end ()) it->second->HandleNextPacket (packet); + else if (packet->IsEcho () && m_Owner->IsStreamingAnswerPings ()) + { + // ping + LogPrint (eLogInfo, "Streaming: Ping received sSID=", sendStreamID); + auto s = std::make_shared (m_Owner->GetService (), *this); + s->HandlePing (packet); + } else { LogPrint (eLogInfo, "Streaming: Unknown stream sSID=", sendStreamID); diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index a56b0565..e8b8db91 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -87,6 +87,7 @@ namespace stream bool IsSYN () const { return GetFlags () & PACKET_FLAG_SYNCHRONIZE; }; bool IsNoAck () const { return GetFlags () & PACKET_FLAG_NO_ACK; }; + bool IsEcho () const { return GetFlags () & PACKET_FLAG_ECHO; }; }; struct PacketCmp @@ -168,6 +169,7 @@ namespace stream StreamingDestination& GetLocalDestination () { return m_LocalDestination; }; void HandleNextPacket (Packet * packet); + void HandlePing (Packet * packet); size_t Send (const uint8_t * buf, size_t len); void AsyncSend (const uint8_t * buf, size_t len, SendHandler handler); diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index e1316a7f..a70add7d 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -453,6 +453,7 @@ namespace client options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY); options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY); options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); + options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, DEFAULT_ANSWER_PINGS); options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, ""); if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType; From ee8429199740b69a4e75cc5eb65b62857f1a306e Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 30 Sep 2020 19:06:13 -0400 Subject: [PATCH 3803/6300] handle i2p.streaming.answerPings properly --- libi2pd/Destination.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index af3b4cbc..e88ac411 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -13,6 +13,7 @@ #include #include #include "Crypto.h" +#include "Config.h" #include "Log.h" #include "FS.h" #include "Timestamp.h" @@ -921,7 +922,7 @@ namespace client m_StreamingAckDelay = std::stoi(it->second); it = params->find (I2CP_PARAM_STREAMING_ANSWER_PINGS); if (it != params->end ()) - m_IsStreamingAnswerPings = (it->second == "true"); + i2p::config::GetOption (it->second, m_IsStreamingAnswerPings); if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) { From 3a2724ec586e9fb7ac6c61b4b4290a0a93aa28e4 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 2 Oct 2020 13:13:27 -0400 Subject: [PATCH 3804/6300] single thread for I2CP --- libi2pd/Config.cpp | 1 + libi2pd_client/ClientContext.cpp | 3 +- libi2pd_client/I2CP.cpp | 101 ++++++++++++++----------------- libi2pd_client/I2CP.h | 37 ++++++----- 4 files changed, 70 insertions(+), 72 deletions(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 7668d6a7..b8016a7c 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -152,6 +152,7 @@ namespace config { ("i2cp.enabled", value()->default_value(false), "Enable or disable I2CP") ("i2cp.address", value()->default_value("127.0.0.1"), "I2CP listen address") ("i2cp.port", value()->default_value(7654), "I2CP listen port") + ("i2cp.singlethread", value()->default_value(true), "Destinations run in the I2CP server's thread") ; options_description i2pcontrol("I2PControl options"); diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index a70add7d..e83e293a 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -102,10 +102,11 @@ namespace client { std::string i2cpAddr; i2p::config::GetOption("i2cp.address", i2cpAddr); uint16_t i2cpPort; i2p::config::GetOption("i2cp.port", i2cpPort); + bool singleThread; i2p::config::GetOption("i2cp.singlethread", singleThread); LogPrint(eLogInfo, "Clients: starting I2CP at ", i2cpAddr, ":", i2cpPort); try { - m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort); + m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort, singleThread); m_I2CPServer->Start (); } catch (std::exception& e) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index b0f334f5..24c2496b 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -23,36 +23,13 @@ namespace i2p namespace client { - I2CPDestination::I2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params): - RunnableService ("I2CP"), LeaseSetDestination (GetIOService (), isPublic, ¶ms), + I2CPDestination::I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, + std::shared_ptr identity, bool isPublic, const std::map& params): + LeaseSetDestination (service, isPublic, ¶ms), m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()) { } - I2CPDestination::~I2CPDestination () - { - if (IsRunning ()) - Stop (); - } - - void I2CPDestination::Start () - { - if (!IsRunning ()) - { - LeaseSetDestination::Start (); - StartIOService (); - } - } - - void I2CPDestination::Stop () - { - if (IsRunning ()) - { - LeaseSetDestination::Stop (); - StopIOService (); - } - } - void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) { m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), key); @@ -217,6 +194,37 @@ namespace client } } + RunnableI2CPDestination::RunnableI2CPDestination (std::shared_ptr owner, + std::shared_ptr identity, bool isPublic, const std::map& params): + RunnableService ("I2CP"), + I2CPDestination (GetIOService (), owner, identity, isPublic, params) + { + } + + RunnableI2CPDestination::~RunnableI2CPDestination () + { + if (IsRunning ()) + Stop (); + } + + void RunnableI2CPDestination::Start () + { + if (!IsRunning ()) + { + I2CPDestination::Start (); + StartIOService (); + } + } + + void RunnableI2CPDestination::Stop () + { + if (IsRunning ()) + { + I2CPDestination::Stop (); + StopIOService (); + } + } + I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_Payload (nullptr), m_SessionID (0xFFFF), m_MessageID (0), m_IsSendAccepted (true) @@ -451,7 +459,9 @@ namespace client if (params[I2CP_PARAM_DONT_PUBLISH_LEASESET] == "true") isPublic = false; if (!m_Destination) { - m_Destination = std::make_shared(shared_from_this (), identity, isPublic, params); + m_Destination = m_Owner.IsSingleThread () ? + std::make_shared(m_Owner.GetService (), shared_from_this (), identity, isPublic, params): + std::make_shared(shared_from_this (), identity, isPublic, params); SendSessionStatusMessage (1); // created LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created"); m_Destination->Start (); @@ -800,9 +810,9 @@ namespace client std::placeholders::_1, std::placeholders::_2, buf)); } - I2CPServer::I2CPServer (const std::string& interface, int port): - m_IsRunning (false), m_Thread (nullptr), - m_Acceptor (m_Service, + I2CPServer::I2CPServer (const std::string& interface, int port, bool isSingleThread): + RunnableService ("I2CP"), m_IsSingleThread (isSingleThread), + m_Acceptor (GetIOService (), #ifdef ANDROID I2CPSession::proto::endpoint(std::string (1, '\0') + interface)) // leading 0 for abstract address #else @@ -825,20 +835,18 @@ namespace client I2CPServer::~I2CPServer () { - if (m_IsRunning) + if (IsRunning ()) Stop (); } void I2CPServer::Start () { Accept (); - m_IsRunning = true; - m_Thread = new std::thread (std::bind (&I2CPServer::Run, this)); + StartIOService (); } void I2CPServer::Stop () { - m_IsRunning = false; m_Acceptor.cancel (); { auto sessions = m_Sessions; @@ -846,33 +854,12 @@ namespace client it.second->Stop (); } m_Sessions.clear (); - m_Service.stop (); - if (m_Thread) - { - m_Thread->join (); - delete m_Thread; - m_Thread = nullptr; - } - } - - void I2CPServer::Run () - { - while (m_IsRunning) - { - try - { - m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "I2CP: runtime exception: ", ex.what ()); - } - } + StopIOService (); } void I2CPServer::Accept () { - auto newSocket = std::make_shared (m_Service); + auto newSocket = std::make_shared (GetIOService ()); m_Acceptor.async_accept (*newSocket, std::bind (&I2CPServer::HandleAccept, this, std::placeholders::_1, newSocket)); } diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index adb648f4..4c6b7531 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -63,16 +63,14 @@ namespace client const char I2CP_PARAM_MESSAGE_RELIABILITY[] = "i2cp.messageReliability"; class I2CPSession; - class I2CPDestination: private i2p::util::RunnableService, public LeaseSetDestination + class I2CPDestination: public LeaseSetDestination { public: - I2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params); - ~I2CPDestination (); - - void Start (); - void Stop (); - + I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, + std::shared_ptr identity, bool isPublic, const std::map& params); + ~I2CPDestination () {}; + void SetEncryptionPrivateKey (const uint8_t * key); void SetEncryptionType (i2p::data::CryptoKeyType keyType) { m_EncryptionKeyType = keyType; }; void SetECIESx25519EncryptionPrivateKey (const uint8_t * key); @@ -109,6 +107,18 @@ namespace client uint64_t m_LeaseSetExpirationTime; }; + class RunnableI2CPDestination: private i2p::util::RunnableService, public I2CPDestination + { + public: + + RunnableI2CPDestination (std::shared_ptr owner, std::shared_ptr identity, + bool isPublic, const std::map& params); + ~RunnableI2CPDestination (); + + void Start (); + void Stop (); + }; + class I2CPServer; class I2CPSession: public std::enable_shared_from_this { @@ -179,17 +189,18 @@ namespace client }; typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); - class I2CPServer + class I2CPServer: private i2p::util::RunnableService { public: - I2CPServer (const std::string& interface, int port); + I2CPServer (const std::string& interface, int port, bool isSingleThread); ~I2CPServer (); void Start (); void Stop (); - boost::asio::io_service& GetService () { return m_Service; }; - + boost::asio::io_service& GetService () { return GetIOService (); }; + bool IsSingleThread () const { return m_IsSingleThread; }; + bool InsertSession (std::shared_ptr session); void RemoveSession (uint16_t sessionID); @@ -203,12 +214,10 @@ namespace client private: + bool m_IsSingleThread; I2CPMessageHandler m_MessagesHandlers[256]; std::map > m_Sessions; - bool m_IsRunning; - std::thread * m_Thread; - boost::asio::io_service m_Service; I2CPSession::proto::acceptor m_Acceptor; public: From 9450dc84dacfd6950bab8534160337c9c281c950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AD=D1=80=D0=B8=D0=BA=20=D0=97=D0=B0=D0=BC=D0=B0=D0=B1?= =?UTF-8?q?=D1=83=D0=B2=D0=B0=D1=80=D0=B0=D0=B5=D0=B2=E2=80=90=D0=81=D0=BC?= =?UTF-8?q?=D0=BE=D0=BB=D0=BA=D1=83=D1=83?= Date: Sun, 4 Oct 2020 03:32:02 +0700 Subject: [PATCH 3805/6300] Update Win32NetState.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QueryInterface должна увеличивать Ñчётчик ÑÑылок. --- Win32/Win32NetState.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Win32/Win32NetState.h b/Win32/Win32NetState.h index bcb6c8d7..c2ddaee4 100644 --- a/Win32/Win32NetState.h +++ b/Win32/Win32NetState.h @@ -31,7 +31,8 @@ public: } else { Result = E_NOINTERFACE; } - + AddRef(); + return Result; } @@ -90,4 +91,4 @@ void SubscribeToEvents() { } void UnSubscribeFromEvents() { } #endif // WINVER -#endif \ No newline at end of file +#endif From 8483464aab8763ccf141b365a878359f37bc3d80 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 3 Oct 2020 17:20:04 -0400 Subject: [PATCH 3806/6300] don't attach our RouterInfo to router's request --- libi2pd/Destination.cpp | 12 ++++++------ libi2pd/NetDb.cpp | 40 ++++++++++++++++++++-------------------- libi2pd/NetDb.hpp | 2 +- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index e88ac411..669fd791 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -465,14 +465,14 @@ namespace client if (request->excluded.size () < MAX_NUM_FLOODFILLS_PER_REQUEST) { for (int i = 0; i < num; i++) - { - i2p::data::IdentHash peerHash (buf + 33 + i*32); - if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash)) { - LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); // TODO: recheck this message - i2p::data::netdb.RequestDestination (peerHash); + i2p::data::IdentHash peerHash (buf + 33 + i*32); + if (!request->excluded.count (peerHash) && !i2p::data::netdb.FindRouter (peerHash)) + { + LogPrint (eLogInfo, "Destination: Found new floodfill, request it"); + i2p::data::netdb.RequestDestination (peerHash, nullptr, false); // through exploratory + } } - } auto floodfill = i2p::data::netdb.GetClosestFloodfill (key, request->excluded); if (floodfill) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 466016c5..5930baf7 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -610,7 +610,7 @@ namespace data } } - void NetDb::RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete) + void NetDb::RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete, bool direct) { auto dest = m_Requests.CreateRequest (destination, false, requestComplete); // non-exploratory if (!dest) @@ -621,7 +621,23 @@ namespace data auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ()); if (floodfill) - transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); + { + if (direct) + transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); + else + { + auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); + auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; + auto inbound = pool ? pool->GetNextInboundTunnel () : nullptr; + if (outbound && inbound) + outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, dest->CreateRequestMessage (floodfill, inbound)); + else + { + LogPrint (eLogError, "NetDb: ", destination.ToBase64(), " destination requested, but no tunnels found"); + m_Requests.RequestComplete (destination, nullptr); + } + } + } else { LogPrint (eLogError, "NetDb: ", destination.ToBase64(), " destination requested, but no floodfills found"); @@ -775,37 +791,21 @@ namespace data // reply to our destination. Try other floodfills if (outbound && inbound) { - std::vector msgs; auto count = dest->GetExcludedPeers ().size (); if (count < 7) { auto nextFloodfill = GetClosestFloodfill (dest->GetDestination (), dest->GetExcludedPeers ()); if (nextFloodfill) { - // tell floodfill about us - msgs.push_back (i2p::tunnel::TunnelMessageBlock - { - i2p::tunnel::eDeliveryTypeRouter, - nextFloodfill->GetIdentHash (), 0, - CreateDatabaseStoreMsg () - }); - // request destination LogPrint (eLogDebug, "NetDb: Try ", key, " at ", count, " floodfill ", nextFloodfill->GetIdentHash ().ToBase64 ()); - auto msg = dest->CreateRequestMessage (nextFloodfill, inbound); - msgs.push_back (i2p::tunnel::TunnelMessageBlock - { - i2p::tunnel::eDeliveryTypeRouter, - nextFloodfill->GetIdentHash (), 0, msg - }); + outbound->SendTunnelDataMsg (nextFloodfill->GetIdentHash (), 0, + dest->CreateRequestMessage (nextFloodfill, inbound)); deleteDest = false; } } else LogPrint (eLogWarning, "NetDb: ", key, " was not found on ", count, " floodfills"); - - if (msgs.size () > 0) - outbound->SendTunnelDataMsg (msgs); } } diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index d4501ab9..f9a57ccd 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -70,7 +70,7 @@ namespace data std::shared_ptr FindLeaseSet (const IdentHash& destination) const; std::shared_ptr FindRouterProfile (const IdentHash& ident) const; - void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr); + void RequestDestination (const IdentHash& destination, RequestedDestination::RequestComplete requestComplete = nullptr, bool direct = true); void RequestDestinationFrom (const IdentHash& destination, const IdentHash & from, bool exploritory, RequestedDestination::RequestComplete requestComplete = nullptr); void HandleDatabaseStoreMsg (std::shared_ptr msg); From c601a2986f3febae1430baf162c09640db30038c Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 4 Oct 2020 00:35:12 +0300 Subject: [PATCH 3807/6300] [appveyor] test without manual keyring installation --- appveyor.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 9d035653..00dd8c1b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,10 +22,10 @@ environment: install: # install new signing keyring -- c:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" -- c:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" -- c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" +#- c:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" +#- c:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" +#- c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" +#- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" # remove packages which can break build - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" # update runtime From cfda807057f361f32f3563cb40b06f14f261dfaf Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 4 Oct 2020 00:45:30 +0300 Subject: [PATCH 3808/6300] [appveyor] use different mirror for keyring Default repo mirror is not available, changed to other one. --- appveyor.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 00dd8c1b..d5f67b14 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,10 +22,10 @@ environment: install: # install new signing keyring -#- c:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" -#- c:\msys64\usr\bin\bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" -#- c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" -#- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" +- c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" +- c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" +- c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" +- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" # remove packages which can break build - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" # update runtime From a8d23b5439d363ae3eae3dd7989b1a9a51732a53 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 3 Oct 2020 18:46:12 -0400 Subject: [PATCH 3809/6300] disable NTCP for good --- libi2pd/Config.cpp | 2 +- libi2pd/RouterContext.cpp | 83 +++++++++++---------------------------- libi2pd/RouterInfo.cpp | 26 ++++-------- libi2pd/RouterInfo.h | 2 - libi2pd/Transports.cpp | 36 +---------------- 5 files changed, 33 insertions(+), 116 deletions(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index b8016a7c..e0dc66d8 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -58,7 +58,7 @@ namespace config { ("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)") ("bandwidth", value()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") ("share", value()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)") - ("ntcp", value()->default_value(false), "Enable NTCP transport (default: disabled)") + ("ntcp", value()->default_value(false), "Ignored. Always false") ("ssu", value()->default_value(true), "Enable SSU transport (default: enabled)") ("ntcpproxy", value()->default_value(""), "Proxy URL for NTCP transport") #ifdef _WIN32 diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index cbbd961e..7333e613 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -63,7 +63,6 @@ namespace i2p bool ipv4; i2p::config::GetOption("ipv4", ipv4); bool ipv6; i2p::config::GetOption("ipv6", ipv6); bool ssu; i2p::config::GetOption("ssu", ssu); - bool ntcp; i2p::config::GetOption("ntcp", ntcp); bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool nat; i2p::config::GetOption("nat", nat); std::string ifname; i2p::config::GetOption("ifname", ifname); @@ -83,8 +82,6 @@ namespace i2p if (ssu) routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); - if (ntcp) - routerInfo.AddNTCPAddress (host.c_str(), port); } if (ipv6) { @@ -99,8 +96,6 @@ namespace i2p if (ssu) routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); - if (ntcp) - routerInfo.AddNTCPAddress (host.c_str(), port); } routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | @@ -115,20 +110,17 @@ namespace i2p { if (!m_NTCP2Keys) NewNTCP2Keys (); UpdateNTCP2Address (true); - if (!ntcp) // NTCP2 should replace NTCP + bool published; i2p::config::GetOption("ntcp2.published", published); + if (published) { - bool published; i2p::config::GetOption("ntcp2.published", published); - if (published) + PublishNTCP2Address (port, true); + if (ipv6) { - PublishNTCP2Address (port, true); - if (ipv6) - { - // add NTCP2 ipv6 address - std::string host = "::1"; - if (!i2p::config::IsDefault ("ntcp2.addressv6")) - i2p::config::GetOption ("ntcp2.addressv6", host); - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (host), port); - } + // add NTCP2 ipv6 address + std::string host = "::1"; + if (!i2p::config::IsDefault ("ntcp2.addressv6")) + i2p::config::GetOption ("ntcp2.addressv6", host); + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (host), port); } } } @@ -444,16 +436,10 @@ namespace i2p addr->ssu->introducers.clear (); port = addr->port; } - // remove NTCP or NTCP2 v4 address - bool ntcp; i2p::config::GetOption("ntcp", ntcp); - if (ntcp) - PublishNTCPAddress (false); - else - { - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) - PublishNTCP2Address (port, false, true); - } + // remove NTCP2 v4 address + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) + PublishNTCP2Address (port, false, true); // update UpdateRouterInfo (); } @@ -477,23 +463,16 @@ namespace i2p addr->ssu->introducers.clear (); port = addr->port; } - // insert NTCP or NTCP2 back - bool ntcp; i2p::config::GetOption("ntcp", ntcp); - if (ntcp) - PublishNTCPAddress (true); - else + // insert NTCP2 back + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) { - // ntcp2 - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) + bool published; i2p::config::GetOption ("ntcp2.published", published); + if (published) { - bool published; i2p::config::GetOption ("ntcp2.published", published); - if (published) - { - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - PublishNTCP2Address (ntcp2Port, true, true); - } + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + PublishNTCP2Address (ntcp2Port, true, true); } } // update @@ -506,7 +485,7 @@ namespace i2p { m_RouterInfo.EnableV6 (); // insert v6 addresses if necessary - bool foundSSU = false, foundNTCP = false, foundNTCP2 = false; + bool foundSSU = false, foundNTCP2 = false; uint16_t port = 0; auto& addresses = m_RouterInfo.GetAddresses (); for (auto& addr: addresses) @@ -515,12 +494,8 @@ namespace i2p { if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU) foundSSU = true; - else if (addr->IsNTCP2 ()) - { - if (addr->IsPublishedNTCP2 ()) foundNTCP2 = true; - } - else - foundNTCP = true; + else if (addr->IsPublishedNTCP2 ()) + foundNTCP2 = true; } port = addr->port; } @@ -552,16 +527,6 @@ namespace i2p m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (ntcp2Host), ntcp2Port); } } - // NTCP - if (!foundNTCP) - { - bool ntcp; i2p::config::GetOption("ntcp", ntcp); - if (ntcp) - { - std::string host = "::1"; - m_RouterInfo.AddNTCPAddress (host.c_str (), port); - } - } } else m_RouterInfo.DisableV6 (); diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 91c12b60..703c853f 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -316,7 +316,7 @@ namespace data } if (introducers) supportedTransports |= eSSUV4; // in case if host is not presented if (isNTCP2Only && address->ntcp2) address->ntcp2->isNTCP2Only = true; - if (supportedTransports) + if (supportedTransports & ~(eNTCPV4 | eNTCPV6)) // exclude NTCP only { addresses->push_back(address); m_SupportedTransports |= supportedTransports; @@ -477,7 +477,12 @@ namespace data s.write ((const char *)&address.date, sizeof (address.date)); std::stringstream properties; if (address.transportStyle == eTransportNTCP) - WriteString (address.IsNTCP2 () ? "NTCP2" : "NTCP", s); + { + if (address.IsNTCP2 ()) + WriteString ("NTCP2", s); + else + continue; // don't write NTCP address + } else if (address.transportStyle == eTransportSSU) { WriteString ("SSU", s); @@ -817,14 +822,6 @@ namespace data return ""; } - bool RouterInfo::IsNTCP (bool v4only) const - { - if (v4only) - return m_SupportedTransports & eNTCPV4; - else - return m_SupportedTransports & (eNTCPV4 | eNTCPV6); - } - bool RouterInfo::IsSSU (bool v4only) const { if (v4only) @@ -907,15 +904,6 @@ namespace data return m_Caps & Caps::eUnreachable; // non-reachable } - std::shared_ptr RouterInfo::GetNTCPAddress (bool v4only) const - { - return GetAddress ( - [v4only](std::shared_ptr address)->bool - { - return (address->transportStyle == eTransportNTCP) && !address->IsNTCP2Only () && (!v4only || address->host.is_v4 ()); - }); - } - std::shared_ptr RouterInfo::GetSSUAddress (bool v4only) const { return GetAddress ( diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index ef902c0e..1c0063b0 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -153,7 +153,6 @@ namespace data uint64_t GetTimestamp () const { return m_Timestamp; }; int GetVersion () const { return m_Version; }; Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr - std::shared_ptr GetNTCPAddress (bool v4only = true) const; std::shared_ptr GetNTCP2Address (bool publishedOnly, bool v4only = true) const; std::shared_ptr GetSSUAddress (bool v4only = true) const; std::shared_ptr GetSSUV6Address () const; @@ -169,7 +168,6 @@ namespace data void ClearProperties () { m_Properties.clear (); }; bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; bool IsReachable () const { return m_Caps & Caps::eReachable; }; - bool IsNTCP (bool v4only = true) const; bool IsSSU (bool v4only = true) const; bool IsSSUV6 () const; bool IsNTCP2 (bool v4only = true) const; diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index edfe33fa..54bf3fd9 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -472,41 +472,7 @@ namespace transport } } } - if (peer.numAttempts == 1) // NTCP1 - { - peer.numAttempts++; - auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ()); - if (address && m_NTCPServer) - { - if (!peer.router->UsesIntroducer () && !peer.router->IsUnreachable ()) - { - if(!m_NTCPServer->ShouldLimit()) - { - auto s = std::make_shared (*m_NTCPServer, peer.router); - if(m_NTCPServer->UsingProxy()) - { - NTCPServer::RemoteAddressType remote = NTCPServer::eIP4Address; - std::string addr = address->host.to_string(); - - if(address->host.is_v6()) - remote = NTCPServer::eIP6Address; - - m_NTCPServer->ConnectWithProxy(addr, address->port, remote, s); - } - else - m_NTCPServer->Connect (address->host, address->port, s); - return true; - } - else - { - LogPrint(eLogWarning, "Transports: NTCP Limit hit falling back to SSU"); - } - } - } - else - LogPrint (eLogDebug, "Transports: NTCP address is not present for ", i2p::data::GetIdentHashAbbreviation (ident), ", trying SSU"); - } - if (peer.numAttempts == 2)// SSU + if (peer.numAttempts == 1)// SSU { peer.numAttempts++; if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ())) From faae2709d9e8ba7fede3ae73228dec493250cd17 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 3 Oct 2020 21:58:20 -0400 Subject: [PATCH 3810/6300] removed NTCP --- build/CMakeLists.txt | 1 - daemon/Daemon.cpp | 60 +- daemon/HTTPServer.cpp | 7 - libi2pd/NTCP2.cpp | 2 +- libi2pd/NTCPSession.cpp | 1317 --------------------------------------- libi2pd/NTCPSession.h | 242 ------- libi2pd/Transports.cpp | 70 +-- libi2pd/Transports.h | 6 +- qt/i2pd_qt/i2pd_qt.pro | 2 - 9 files changed, 30 insertions(+), 1677 deletions(-) delete mode 100644 libi2pd/NTCPSession.cpp delete mode 100644 libi2pd/NTCPSession.h diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 8271ce3a..4825855b 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -68,7 +68,6 @@ set(LIBI2PD_SRC "${LIBI2PD_SRC_DIR}/NetDb.cpp" "${LIBI2PD_SRC_DIR}/NetDbRequests.cpp" "${LIBI2PD_SRC_DIR}/NTCP2.cpp" - "${LIBI2PD_SRC_DIR}/NTCPSession.cpp" "${LIBI2PD_SRC_DIR}/Poly1305.cpp" "${LIBI2PD_SRC_DIR}/Profiling.cpp" "${LIBI2PD_SRC_DIR}/Reseed.cpp" diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 264013cf..5f98d47a 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -1,11 +1,3 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - #include #include @@ -17,7 +9,6 @@ #include "Base.h" #include "version.h" #include "Transports.h" -#include "NTCPSession.h" #include "RouterInfo.h" #include "RouterContext.h" #include "Tunnel.h" @@ -135,8 +126,8 @@ namespace i2p i2p::context.SetNetID (netID); i2p::context.Init (); - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); #ifdef MESHNET // manual override for meshnet ipv4 = false; @@ -151,8 +142,8 @@ namespace i2p i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); - bool ntcp; i2p::config::GetOption("ntcp", ntcp); - i2p::context.PublishNTCPAddress (ntcp, !ipv6); + bool ntcp; i2p::config::GetOption("ntcp", ntcp); + i2p::context.PublishNTCPAddress (ntcp, !ipv6); bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) { @@ -180,13 +171,10 @@ namespace i2p SetMaxNumTransitTunnels (transitTunnels); bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); - if (isFloodfill) - { + if (isFloodfill) { LogPrint(eLogInfo, "Daemon: router will be floodfill"); i2p::context.SetFloodfill (true); - } - else - { + } else { i2p::context.SetFloodfill (false); } @@ -254,8 +242,7 @@ namespace i2p i2p::transport::transports.RestrictRoutesToFamilies(fams); restricted = fams.size() > 0; } - if (routers.length() > 0) - { + if (routers.length() > 0) { std::set idents; size_t pos = 0, comma; do @@ -291,8 +278,7 @@ namespace i2p i2p::data::netdb.Start(); bool upnp; i2p::config::GetOption("upnp.enabled", upnp); - if (upnp) - { + if (upnp) { d.UPnP = std::unique_ptr(new i2p::transport::UPnP); d.UPnP->Start (); } @@ -304,16 +290,16 @@ namespace i2p d.m_NTPSync->Start (); } - bool ntcp; i2p::config::GetOption("ntcp", ntcp); - bool ssu; i2p::config::GetOption("ssu", ssu); + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + bool ssu; i2p::config::GetOption("ssu", ssu); LogPrint(eLogInfo, "Daemon: starting Transports"); if(!ssu) LogPrint(eLogInfo, "Daemon: ssu disabled"); - if(!ntcp) LogPrint(eLogInfo, "Daemon: ntcp disabled"); + if(!ntcp2) LogPrint(eLogInfo, "Daemon: ntcp2 disabled"); - i2p::transport::transports.Start(ntcp, ssu); - if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundNTCP2()) + i2p::transport::transports.Start(ntcp2, ssu); + if (i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundNTCP2()) LogPrint(eLogInfo, "Daemon: Transports started"); - else + else { LogPrint(eLogError, "Daemon: failed to start Transports"); /** shut down netdb right away */ @@ -323,23 +309,23 @@ namespace i2p } bool http; i2p::config::GetOption("http.enabled", http); - if (http) - { + if (http) { std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); LogPrint(eLogInfo, "Daemon: starting webconsole at ", httpAddr, ":", httpPort); - try + try { d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); d.httpServer->Start(); - } - catch (std::exception& ex) + } + catch (std::exception& ex) { LogPrint (eLogError, "Daemon: failed to start webconsole: ", ex.what ()); ThrowFatal ("Unable to start webconsole at ", httpAddr, ":", httpPort, ": ", ex.what ()); } } + LogPrint(eLogInfo, "Daemon: starting Tunnels"); i2p::tunnel::tunnels.Start(); @@ -352,12 +338,12 @@ namespace i2p std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr); uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort); LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort); - try + try { d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); d.m_I2PControlService->Start (); - } - catch (std::exception& ex) + } + catch (std::exception& ex) { LogPrint (eLogError, "Daemon: failed to start I2PControl: ", ex.what ()); ThrowFatal ("Unable to start I2PControl service at ", i2pcpAddr, ":", i2pcpPort, ": ", ex.what ()); @@ -374,7 +360,7 @@ namespace i2p LogPrint(eLogInfo, "Daemon: stopping Tunnels"); i2p::tunnel::tunnels.Stop(); - if (d.UPnP) + if (d.UPnP) { d.UPnP->Stop (); d.UPnP = nullptr; diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 52b655b9..19734038 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -732,13 +732,6 @@ namespace http { void ShowTransports (std::stringstream& s) { s << "Transports:
    \r\n"; - auto ntcpServer = i2p::transport::transports.GetNTCPServer (); - if (ntcpServer) - { - auto sessions = ntcpServer->GetNTCPSessions (); - if (!sessions.empty ()) - ShowNTCPTransports (s, sessions, "NTCP"); - } auto ntcp2Server = i2p::transport::transports.GetNTCP2Server (); if (ntcp2Server) { diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 4e3b219c..cdf660b7 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1435,7 +1435,7 @@ namespace transport { auto timer = std::make_shared(GetService()); - auto timeout = NTCP_CONNECT_TIMEOUT * 5; + auto timeout = NTCP2_CONNECT_TIMEOUT * 5; conn->SetTerminationTimeout(timeout * 2); timer->expires_from_now (boost::posix_time::seconds(timeout)); timer->async_wait ([conn, timeout](const boost::system::error_code& ecode) diff --git a/libi2pd/NTCPSession.cpp b/libi2pd/NTCPSession.cpp deleted file mode 100644 index 4d3f1da6..00000000 --- a/libi2pd/NTCPSession.cpp +++ /dev/null @@ -1,1317 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include - -#include "I2PEndian.h" -#include "Base.h" -#include "Crypto.h" -#include "Log.h" -#include "Timestamp.h" -#include "I2NPProtocol.h" -#include "RouterContext.h" -#include "Transports.h" -#include "NetDb.hpp" -#include "NTCPSession.h" -#include "HTTP.h" -#include "util.h" - -using namespace i2p::crypto; - -namespace i2p -{ -namespace transport -{ - - struct NTCPWork - { - std::shared_ptr session; - }; - - NTCPSession::NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter): - TransportSession (in_RemoteRouter, NTCP_ESTABLISH_TIMEOUT), - m_Server (server), m_Socket (m_Server.GetService ()), - m_IsEstablished (false), m_IsTerminated (false), - m_ReceiveBufferOffset (0), m_NextMessage (nullptr), m_IsSending (false) - { - m_Establisher = new Establisher; - } - - NTCPSession::~NTCPSession () - { - delete m_Establisher; - } - - void NTCPSession::CreateAESKey (uint8_t * pubKey) - { - uint8_t sharedKey[256]; - m_DHKeysPair->Agree (pubKey, sharedKey); // time consuming operation - - i2p::crypto::AESKey aesKey; - if (sharedKey[0] & 0x80) - { - aesKey[0] = 0; - memcpy (aesKey + 1, sharedKey, 31); - } - else if (sharedKey[0]) - memcpy (aesKey, sharedKey, 32); - else - { - // find first non-zero byte - uint8_t * nonZero = sharedKey + 1; - while (!*nonZero) - { - nonZero++; - if (nonZero - sharedKey > 32) - { - LogPrint (eLogWarning, "NTCP: First 32 bytes of shared key is all zeros, ignored"); - return; - } - } - memcpy (aesKey, nonZero, 32); - } - - m_Decryption.SetKey (aesKey); - m_Encryption.SetKey (aesKey); - } - - void NTCPSession::Done () - { - m_Server.GetService ().post (std::bind (&NTCPSession::Terminate, shared_from_this ())); - } - - void NTCPSession::Terminate () - { - if (!m_IsTerminated) - { - m_IsTerminated = true; - m_IsEstablished = false; - m_Socket.close (); - transports.PeerDisconnected (shared_from_this ()); - m_Server.RemoveNTCPSession (shared_from_this ()); - m_SendQueue.clear (); - m_NextMessage = nullptr; - LogPrint (eLogDebug, "NTCP: session terminated"); - } - } - - void NTCPSession::Connected () - { - m_IsEstablished = true; - - delete m_Establisher; - m_Establisher = nullptr; - - m_DHKeysPair = nullptr; - - SetTerminationTimeout (NTCP_TERMINATION_TIMEOUT); - SendTimeSyncMessage (); - transports.PeerConnected (shared_from_this ()); - } - - boost::asio::io_service & NTCPSession::GetService() - { - return m_Server.GetService(); - } - - void NTCPSession::ClientLogin () - { - if (!m_DHKeysPair) - m_DHKeysPair = transports.GetNextDHKeysPair (); - // send Phase1 - const uint8_t * x = m_DHKeysPair->GetPublicKey (); - memcpy (m_Establisher->phase1.pubKey, x, 256); - SHA256(x, 256, m_Establisher->phase1.HXxorHI); - const uint8_t * ident = m_RemoteIdentity->GetIdentHash (); - for (int i = 0; i < 32; i++) - m_Establisher->phase1.HXxorHI[i] ^= ident[i]; - - boost::asio::async_write (m_Socket, boost::asio::buffer (&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandlePhase1Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); - } - - void NTCPSession::ServerLogin () - { - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - // receive Phase1 - boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase1, sizeof (NTCPPhase1)), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandlePhase1Received, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); - } - - void NTCPSession::HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - (void) bytes_transferred; - if (ecode) - { - LogPrint (eLogInfo, "NTCP: couldn't send Phase 1 message: ", ecode.message ()); - if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - else - { - boost::asio::async_read (m_Socket, boost::asio::buffer(&m_Establisher->phase2, sizeof (NTCPPhase2)), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandlePhase2Received, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); - } - } - - void NTCPSession::HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - (void) bytes_transferred; - if (ecode) - { - LogPrint (eLogInfo, "NTCP: phase 1 read error: ", ecode.message ()); - if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - else - { - // verify ident - uint8_t digest[32]; - SHA256(m_Establisher->phase1.pubKey, 256, digest); - const uint8_t * ident = i2p::context.GetIdentHash (); - for (int i = 0; i < 32; i++) - { - if ((m_Establisher->phase1.HXxorHI[i] ^ ident[i]) != digest[i]) - { - LogPrint (eLogError, "NTCP: phase 1 error: ident mismatch"); - Terminate (); - return; - } - } - // TODO: check for number of pending keys - auto work = new NTCPWork{shared_from_this()}; - m_Server.Work(work->session, [work, this]() -> std::function { - if (!work->session->m_DHKeysPair) - work->session->m_DHKeysPair = transports.GetNextDHKeysPair (); - work->session->CreateAESKey (work->session->m_Establisher->phase1.pubKey); - return std::bind(&NTCPSession::SendPhase2, work->session, work); - }); - } - } - - void NTCPSession::SendPhase2 (NTCPWork * work) - { - if(work) - delete work; - const uint8_t * y = m_DHKeysPair->GetPublicKey (); - memcpy (m_Establisher->phase2.pubKey, y, 256); - uint8_t xy[512]; - memcpy (xy, m_Establisher->phase1.pubKey, 256); - memcpy (xy + 256, y, 256); - SHA256(xy, 512, m_Establisher->phase2.encrypted.hxy); - uint32_t tsB = htobe32 (i2p::util::GetSecondsSinceEpoch ()); - memcpy (m_Establisher->phase2.encrypted.timestamp, &tsB, 4); - RAND_bytes (m_Establisher->phase2.encrypted.filler, 12); - - m_Encryption.SetIV (y + 240); - m_Decryption.SetIV (m_Establisher->phase1.HXxorHI + 16); - - m_Encryption.Encrypt ((uint8_t *)&m_Establisher->phase2.encrypted, sizeof(m_Establisher->phase2.encrypted), (uint8_t *)&m_Establisher->phase2.encrypted); - boost::asio::async_write(m_Socket, boost::asio::buffer (&m_Establisher->phase2, sizeof (NTCPPhase2)), boost::asio::transfer_all(), - std::bind(&NTCPSession::HandlePhase2Sent, shared_from_this(), std::placeholders::_1, std::placeholders::_2, tsB)); - } - - void NTCPSession::HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB) - { - (void) bytes_transferred; - if (ecode) - { - LogPrint (eLogInfo, "NTCP: Couldn't send Phase 2 message: ", ecode.message ()); - if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - else - { - boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer, NTCP_DEFAULT_PHASE3_SIZE), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandlePhase3Received, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, tsB)); - } - } - - void NTCPSession::HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - (void) bytes_transferred; - if (ecode) - { - LogPrint (eLogInfo, "NTCP: Phase 2 read error: ", ecode.message (), ". Wrong ident assumed"); - if (ecode != boost::asio::error::operation_aborted) - { - // this RI is not valid - i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); - transports.ReuseDHKeysPair (m_DHKeysPair); - m_DHKeysPair = nullptr; - Terminate (); - } - } - else - { - auto work = new NTCPWork{shared_from_this()}; - m_Server.Work(work->session, [work, this]() -> std::function { - work->session->CreateAESKey (work->session->m_Establisher->phase2.pubKey); - return std::bind(&NTCPSession::HandlePhase2, work->session, work); - }); - } - } - - void NTCPSession::HandlePhase2 (NTCPWork * work) - { - if(work) delete work; - m_Decryption.SetIV (m_Establisher->phase2.pubKey + 240); - m_Encryption.SetIV (m_Establisher->phase1.HXxorHI + 16); - - m_Decryption.Decrypt((uint8_t *)&m_Establisher->phase2.encrypted, sizeof(m_Establisher->phase2.encrypted), (uint8_t *)&m_Establisher->phase2.encrypted); - // verify - uint8_t xy[512]; - memcpy (xy, m_DHKeysPair->GetPublicKey (), 256); - memcpy (xy + 256, m_Establisher->phase2.pubKey, 256); - uint8_t digest[32]; - SHA256 (xy, 512, digest); - if (memcmp(m_Establisher->phase2.encrypted.hxy, digest, 32)) - { - LogPrint (eLogError, "NTCP: Phase 2 process error: incorrect hash"); - transports.ReuseDHKeysPair (m_DHKeysPair); - m_DHKeysPair = nullptr; - Terminate (); - return ; - } - SendPhase3 (); - } - - void NTCPSession::SendPhase3 () - { - auto& keys = i2p::context.GetPrivateKeys (); - uint8_t * buf = m_ReceiveBuffer; - htobe16buf (buf, keys.GetPublic ()->GetFullLen ()); - buf += 2; - buf += i2p::context.GetIdentity ()->ToBuffer (buf, NTCP_BUFFER_SIZE); - uint32_t tsA = htobe32 (i2p::util::GetSecondsSinceEpoch ()); - htobuf32(buf,tsA); - buf += 4; - size_t signatureLen = keys.GetPublic ()->GetSignatureLen (); - size_t len = (buf - m_ReceiveBuffer) + signatureLen; - size_t paddingSize = len & 0x0F; // %16 - if (paddingSize > 0) - { - paddingSize = 16 - paddingSize; - // fill padding with random data - RAND_bytes(buf, paddingSize); - buf += paddingSize; - len += paddingSize; - } - - SignedData s; - s.Insert (m_Establisher->phase1.pubKey, 256); // x - s.Insert (m_Establisher->phase2.pubKey, 256); // y - s.Insert (m_RemoteIdentity->GetIdentHash (), 32); // ident - s.Insert (tsA); // tsA - s.Insert (m_Establisher->phase2.encrypted.timestamp, 4); // tsB - s.Sign (keys, buf); - - m_Encryption.Encrypt(m_ReceiveBuffer, len, m_ReceiveBuffer); - boost::asio::async_write (m_Socket, boost::asio::buffer (m_ReceiveBuffer, len), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandlePhase3Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, tsA)); - } - - void NTCPSession::HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA) - { - (void) bytes_transferred; - if (ecode) - { - LogPrint (eLogInfo, "NTCP: Couldn't send Phase 3 message: ", ecode.message ()); - if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - else - { - // wait for phase4 - auto signatureLen = m_RemoteIdentity->GetSignatureLen (); - size_t paddingSize = signatureLen & 0x0F; // %16 - if (paddingSize > 0) signatureLen += (16 - paddingSize); - boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer, signatureLen), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandlePhase4Received, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, tsA)); - } - } - - void NTCPSession::HandlePhase3Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB) - { - if (ecode) - { - LogPrint (eLogInfo, "NTCP: Phase 3 read error: ", ecode.message ()); - if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - else - { - m_Decryption.Decrypt (m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer); - uint8_t * buf = m_ReceiveBuffer; - uint16_t size = bufbe16toh (buf); - auto identity = std::make_shared (buf + 2, size); - if (m_Server.FindNTCPSession (identity->GetIdentHash ())) - { - LogPrint (eLogInfo, "NTCP: session already exists"); - Terminate (); - } - auto existing = i2p::data::netdb.FindRouter (identity->GetIdentHash ()); // check if exists already - SetRemoteIdentity (existing ? existing->GetRouterIdentity () : identity); - - size_t expectedSize = size + 2/*size*/ + 4/*timestamp*/ + m_RemoteIdentity->GetSignatureLen (); - size_t paddingLen = expectedSize & 0x0F; - if (paddingLen) paddingLen = (16 - paddingLen); - if (expectedSize > NTCP_DEFAULT_PHASE3_SIZE) - { - // we need more bytes for Phase3 - expectedSize += paddingLen; - boost::asio::async_read (m_Socket, boost::asio::buffer(m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, expectedSize - NTCP_DEFAULT_PHASE3_SIZE), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandlePhase3ExtraReceived, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, tsB, paddingLen)); - } - else - HandlePhase3 (tsB, paddingLen); - } - } - - void NTCPSession::HandlePhase3ExtraReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB, size_t paddingLen) - { - if (ecode) - { - LogPrint (eLogInfo, "NTCP: Phase 3 extra read error: ", ecode.message ()); - if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - else - { - m_Decryption.Decrypt (m_ReceiveBuffer + NTCP_DEFAULT_PHASE3_SIZE, bytes_transferred, m_ReceiveBuffer+ NTCP_DEFAULT_PHASE3_SIZE); - HandlePhase3 (tsB, paddingLen); - } - } - - void NTCPSession::HandlePhase3 (uint32_t tsB, size_t paddingLen) - { - uint8_t * buf = m_ReceiveBuffer + m_RemoteIdentity->GetFullLen () + 2 /*size*/; - uint32_t tsA = buf32toh(buf); - buf += 4; - buf += paddingLen; - - // check timestamp - auto ts = i2p::util::GetSecondsSinceEpoch (); - uint32_t tsA1 = be32toh (tsA); - if (tsA1 < ts - NTCP_CLOCK_SKEW || tsA1 > ts + NTCP_CLOCK_SKEW) - { - LogPrint (eLogError, "NTCP: Phase3 time difference ", (int)(ts - tsA1), " exceeds clock skew"); - Terminate (); - return; - } - - // check signature - SignedData s; - s.Insert (m_Establisher->phase1.pubKey, 256); // x - s.Insert (m_Establisher->phase2.pubKey, 256); // y - s.Insert (i2p::context.GetRouterInfo ().GetIdentHash (), 32); // ident - s.Insert (tsA); // tsA - s.Insert (tsB); // tsB - if (!s.Verify (m_RemoteIdentity, buf)) - { - LogPrint (eLogError, "NTCP: signature verification failed"); - Terminate (); - return; - } - - SendPhase4 (tsA, tsB); - } - - void NTCPSession::SendPhase4 (uint32_t tsA, uint32_t tsB) - { - SignedData s; - s.Insert (m_Establisher->phase1.pubKey, 256); // x - s.Insert (m_Establisher->phase2.pubKey, 256); // y - s.Insert (m_RemoteIdentity->GetIdentHash (), 32); // ident - s.Insert (tsA); // tsA - s.Insert (tsB); // tsB - auto& keys = i2p::context.GetPrivateKeys (); - auto signatureLen = keys.GetPublic ()->GetSignatureLen (); - s.Sign (keys, m_ReceiveBuffer); - size_t paddingSize = signatureLen & 0x0F; // %16 - if (paddingSize > 0) signatureLen += (16 - paddingSize); - m_Encryption.Encrypt (m_ReceiveBuffer, signatureLen, m_ReceiveBuffer); - - boost::asio::async_write (m_Socket, boost::asio::buffer (m_ReceiveBuffer, signatureLen), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandlePhase4Sent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); - } - - void NTCPSession::HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - (void) bytes_transferred; - if (ecode) - { - LogPrint (eLogWarning, "NTCP: Couldn't send Phase 4 message: ", ecode.message ()); - if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - else - { - LogPrint (eLogDebug, "NTCP: Server session from ", m_Socket.remote_endpoint (), " connected"); - m_Server.AddNTCPSession (shared_from_this ()); - - Connected (); - m_ReceiveBufferOffset = 0; - m_NextMessage = nullptr; - Receive (); - } - } - - void NTCPSession::HandlePhase4Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA) - { - if (ecode) - { - LogPrint (eLogError, "NTCP: Phase 4 read error: ", ecode.message (), ". Check your clock"); - if (ecode != boost::asio::error::operation_aborted) - { - // this router doesn't like us - i2p::data::netdb.SetUnreachable (GetRemoteIdentity ()->GetIdentHash (), true); - Terminate (); - } - } - else - { - m_Decryption.Decrypt(m_ReceiveBuffer, bytes_transferred, m_ReceiveBuffer); - - // check timestamp - uint32_t tsB = bufbe32toh (m_Establisher->phase2.encrypted.timestamp); - auto ts = i2p::util::GetSecondsSinceEpoch (); - if (tsB < ts - NTCP_CLOCK_SKEW || tsB > ts + NTCP_CLOCK_SKEW) - { - LogPrint (eLogError, "NTCP: Phase4 time difference ", (int)(ts - tsB), " exceeds clock skew"); - Terminate (); - return; - } - - // verify signature - SignedData s; - s.Insert (m_Establisher->phase1.pubKey, 256); // x - s.Insert (m_Establisher->phase2.pubKey, 256); // y - s.Insert (i2p::context.GetIdentHash (), 32); // ident - s.Insert (tsA); // tsA - s.Insert (m_Establisher->phase2.encrypted.timestamp, 4); // tsB - - if (!s.Verify (m_RemoteIdentity, m_ReceiveBuffer)) - { - LogPrint (eLogError, "NTCP: Phase 4 process error: signature verification failed"); - Terminate (); - return; - } - LogPrint (eLogDebug, "NTCP: session to ", m_Socket.remote_endpoint (), " connected"); - Connected (); - - m_ReceiveBufferOffset = 0; - m_NextMessage = nullptr; - Receive (); - } - } - - void NTCPSession::Receive () - { - m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, NTCP_BUFFER_SIZE - m_ReceiveBufferOffset), - std::bind(&NTCPSession::HandleReceived, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); - } - - void NTCPSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (ecode) - { - if (ecode != boost::asio::error::operation_aborted) - LogPrint (eLogDebug, "NTCP: Read error: ", ecode.message ()); - //if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - else - { - m_NumReceivedBytes += bytes_transferred; - i2p::transport::transports.UpdateReceivedBytes (bytes_transferred); - m_ReceiveBufferOffset += bytes_transferred; - - if (m_ReceiveBufferOffset >= 16) - { - // process received data - uint8_t * nextBlock = m_ReceiveBuffer; - while (m_ReceiveBufferOffset >= 16) - { - if (!DecryptNextBlock (nextBlock)) // 16 bytes - { - Terminate (); - return; - } - nextBlock += 16; - m_ReceiveBufferOffset -= 16; - } - if (m_ReceiveBufferOffset > 0) - memcpy (m_ReceiveBuffer, nextBlock, m_ReceiveBufferOffset); - } - - // read and process more is available - boost::system::error_code ec; - size_t moreBytes = m_Socket.available(ec); - if (moreBytes && !ec) - { - uint8_t * buf = nullptr, * moreBuf = m_ReceiveBuffer; - if (moreBytes + m_ReceiveBufferOffset > NTCP_BUFFER_SIZE) - { - buf = new uint8_t[moreBytes + m_ReceiveBufferOffset + 16]; - moreBuf = buf; - uint8_t rem = ((size_t)buf) & 0x0f; - if (rem) moreBuf += (16 - rem); // align 16 - if (m_ReceiveBufferOffset) - memcpy (moreBuf, m_ReceiveBuffer, m_ReceiveBufferOffset); - } - moreBytes = m_Socket.read_some (boost::asio::buffer (moreBuf + m_ReceiveBufferOffset, moreBytes), ec); - if (ec) - { - LogPrint (eLogInfo, "NTCP: Read more bytes error: ", ec.message ()); - delete[] buf; - Terminate (); - return; - } - m_ReceiveBufferOffset += moreBytes; - m_NumReceivedBytes += moreBytes; - i2p::transport::transports.UpdateReceivedBytes (moreBytes); - // process more data - uint8_t * nextBlock = moreBuf; - while (m_ReceiveBufferOffset >= 16) - { - if (!DecryptNextBlock (nextBlock)) // 16 bytes - { - delete[] buf; - Terminate (); - return; - } - nextBlock += 16; - m_ReceiveBufferOffset -= 16; - } - if (m_ReceiveBufferOffset > 0) - memcpy (m_ReceiveBuffer, nextBlock, m_ReceiveBufferOffset); // nextBlock points to memory inside buf - delete[] buf; - } - m_Handler.Flush (); - - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - Receive (); - } - } - - bool NTCPSession::DecryptNextBlock (const uint8_t * encrypted) // 16 bytes - { - if (!m_NextMessage) // new message, header expected - { - // decrypt header and extract length - uint8_t buf[16]; - m_Decryption.Decrypt (encrypted, buf); - uint16_t dataSize = bufbe16toh (buf); - if (dataSize) - { - // new message - if (dataSize + 16U + 15U > NTCP_MAX_MESSAGE_SIZE - 2) // + 6 + padding - { - LogPrint (eLogError, "NTCP: data size ", dataSize, " exceeds max size"); - return false; - } - m_NextMessage = (dataSize + 16U + 15U) <= I2NP_MAX_SHORT_MESSAGE_SIZE - 2 ? NewI2NPShortMessage () : NewI2NPMessage (); - m_NextMessage->Align (16); - m_NextMessage->offset += 2; // size field - m_NextMessage->len = m_NextMessage->offset + dataSize; - memcpy (m_NextMessage->GetBuffer () - 2, buf, 16); - m_NextMessageOffset = 16; - } - else - { - // timestamp - int diff = (int)bufbe32toh (buf + 2) - (int)i2p::util::GetSecondsSinceEpoch (); - LogPrint (eLogInfo, "NTCP: Timestamp. Time difference ", diff, " seconds"); - return true; - } - } - else // message continues - { - m_Decryption.Decrypt (encrypted, m_NextMessage->GetBuffer () - 2 + m_NextMessageOffset); - m_NextMessageOffset += 16; - } - - if (m_NextMessageOffset >= m_NextMessage->GetLength () + 2 + 4) // +checksum - { - // we have a complete I2NP message - uint8_t checksum[4]; - htobe32buf (checksum, adler32 (adler32 (0, Z_NULL, 0), m_NextMessage->GetBuffer () - 2, m_NextMessageOffset - 4)); - if (!memcmp (m_NextMessage->GetBuffer () - 2 + m_NextMessageOffset - 4, checksum, 4)) - { - if (!m_NextMessage->IsExpired ()) - { - m_Handler.PutNextMessage (m_NextMessage); - } - else - LogPrint (eLogInfo, "NTCP: message expired"); - } - else - LogPrint (eLogWarning, "NTCP: Incorrect adler checksum of message, dropped"); - m_NextMessage = nullptr; - } - return true; - } - - void NTCPSession::Send (std::shared_ptr msg) - { - m_IsSending = true; - boost::asio::async_write (m_Socket, CreateMsgBuffer (msg), boost::asio::transfer_all (), - std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::vector >{ msg })); - } - - boost::asio::const_buffers_1 NTCPSession::CreateMsgBuffer (std::shared_ptr msg) - { - uint8_t * sendBuffer; - int len; - - if (msg) - { - // regular I2NP - if (msg->offset < 2) - LogPrint (eLogError, "NTCP: Malformed I2NP message"); // TODO: - sendBuffer = msg->GetBuffer () - 2; - len = msg->GetLength (); - htobe16buf (sendBuffer, len); - } - else - { - // prepare timestamp - sendBuffer = m_TimeSyncBuffer; - len = 4; - htobuf16(sendBuffer, 0); - htobe32buf (sendBuffer + 2, i2p::util::GetSecondsSinceEpoch ()); - } - int rem = (len + 6) & 0x0F; // %16 - int padding = 0; - if (rem > 0) { - padding = 16 - rem; - // fill with random padding - RAND_bytes(sendBuffer + len + 2, padding); - } - htobe32buf (sendBuffer + len + 2 + padding, adler32 (adler32 (0, Z_NULL, 0), sendBuffer, len + 2+ padding)); - - int l = len + padding + 6; - m_Encryption.Encrypt(sendBuffer, l, sendBuffer); - return boost::asio::buffer ((const uint8_t *)sendBuffer, l); - } - - - void NTCPSession::Send (const std::vector >& msgs) - { - m_IsSending = true; - std::vector bufs; - for (const auto& it: msgs) - bufs.push_back (CreateMsgBuffer (it)); - boost::asio::async_write (m_Socket, bufs, boost::asio::transfer_all (), - std::bind(&NTCPSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, msgs)); - } - - void NTCPSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector > msgs) - { - (void) msgs; - m_IsSending = false; - if (ecode) - { - LogPrint (eLogWarning, "NTCP: Couldn't send msgs: ", ecode.message ()); - // we shouldn't call Terminate () here, because HandleReceive takes care - // TODO: 'delete this' statement in Terminate () must be eliminated later - // Terminate (); - } - else - { - m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); - m_NumSentBytes += bytes_transferred; - i2p::transport::transports.UpdateSentBytes (bytes_transferred); - if (!m_SendQueue.empty()) - { - Send (m_SendQueue); - m_SendQueue.clear (); - } - } - } - - void NTCPSession::SendTimeSyncMessage () - { - Send (nullptr); - } - - void NTCPSession::SendI2NPMessages (const std::vector >& msgs) - { - m_Server.GetService ().post (std::bind (&NTCPSession::PostI2NPMessages, shared_from_this (), msgs)); - } - - void NTCPSession::PostI2NPMessages (std::vector > msgs) - { - if (m_IsTerminated) return; - if (m_IsSending) - { - if (m_SendQueue.size () < NTCP_MAX_OUTGOING_QUEUE_SIZE) - { - for (const auto& it: msgs) - m_SendQueue.push_back (it); - } - else - { - LogPrint (eLogWarning, "NTCP: outgoing messages queue size exceeds ", NTCP_MAX_OUTGOING_QUEUE_SIZE); - Terminate (); - } - } - else - Send (msgs); - } - -//----------------------------------------- - NTCPServer::NTCPServer (int workers): - m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), - m_TerminationTimer (m_Service), m_NTCPAcceptor (nullptr), m_NTCPV6Acceptor (nullptr), - m_ProxyType(eNoProxy), m_Resolver(m_Service), m_ProxyEndpoint(nullptr), - m_SoftLimit(0), m_HardLimit(0) - { - if(workers <= 0) workers = 1; - m_CryptoPool = std::make_shared(workers); - } - - NTCPServer::~NTCPServer () - { - Stop (); - } - - void NTCPServer::Start () - { - if (!m_IsRunning) - { - m_IsRunning = true; - m_Thread = new std::thread (std::bind (&NTCPServer::Run, this)); - // we are using a proxy, don't create any acceptors - if(UsingProxy()) - { - // TODO: resolve proxy until it is resolved - boost::asio::ip::tcp::resolver::query q(m_ProxyAddress, std::to_string(m_ProxyPort)); - boost::system::error_code e; - auto itr = m_Resolver.resolve(q, e); - if(e) - { - LogPrint(eLogError, "NTCP: Failed to resolve proxy ", e.message()); - } - else - { - m_ProxyEndpoint = new boost::asio::ip::tcp::endpoint(*itr); - } - } - else - { - // create acceptors - auto& addresses = context.GetRouterInfo ().GetAddresses (); - for (const auto& address: addresses) - { - if (!address) continue; - if (address->transportStyle == i2p::data::RouterInfo::eTransportNTCP && !address->IsNTCP2 ()) - { - if (address->host.is_v4()) - { - try - { - m_NTCPAcceptor = new boost::asio::ip::tcp::acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), address->port)); - LogPrint (eLogInfo, "NTCP: Start listening v6 TCP port ", address->port); - } - catch ( std::exception & ex ) - { - /** fail to bind ip4 */ - LogPrint(eLogError, "NTCP: Failed to bind to v4 port ", address->port, ": ", ex.what()); - ThrowFatal ("Unable to start IPv4 NTCP transport at port ", address->port, ": ", ex.what ()); - continue; - } - - LogPrint (eLogInfo, "NTCP: Start listening TCP port ", address->port); - auto conn = std::make_shared(*this); - m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, conn, std::placeholders::_1)); - } - else if (address->host.is_v6() && context.SupportsV6 ()) - { - m_NTCPV6Acceptor = new boost::asio::ip::tcp::acceptor (m_Service); - try - { - m_NTCPV6Acceptor->open (boost::asio::ip::tcp::v6()); - m_NTCPV6Acceptor->set_option (boost::asio::ip::v6_only (true)); - m_NTCPV6Acceptor->set_option (boost::asio::socket_base::reuse_address (true)); - m_NTCPV6Acceptor->bind (boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port)); - m_NTCPV6Acceptor->listen (); - - LogPrint (eLogInfo, "NTCP: Start listening v6 TCP port ", address->port); - auto conn = std::make_shared (*this); - m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this, conn, std::placeholders::_1)); - } - catch ( std::exception & ex ) - { - LogPrint(eLogError, "NTCP: failed to bind to v6 port ", address->port, ": ", ex.what()); - ThrowFatal (eLogError, "Unable to start IPv6 NTCP transport at port ", address->port, ": ", ex.what ()); - continue; - } - } - } - } - } - ScheduleTermination (); - } - } - - void NTCPServer::Stop () - { - { - // we have to copy it because Terminate changes m_NTCPSessions - auto ntcpSessions = m_NTCPSessions; - for (auto& it: ntcpSessions) - it.second->Terminate (); - for (auto& it: m_PendingIncomingSessions) - it->Terminate (); - } - m_NTCPSessions.clear (); - - if (m_IsRunning) - { - m_IsRunning = false; - m_TerminationTimer.cancel (); - if (m_NTCPAcceptor) - { - delete m_NTCPAcceptor; - m_NTCPAcceptor = nullptr; - } - if (m_NTCPV6Acceptor) - { - delete m_NTCPV6Acceptor; - m_NTCPV6Acceptor = nullptr; - } - m_Service.stop (); - if (m_Thread) - { - m_Thread->join (); - delete m_Thread; - m_Thread = nullptr; - } - if(m_ProxyEndpoint) - { - delete m_ProxyEndpoint; - m_ProxyEndpoint = nullptr; - } - } - } - - void NTCPServer::Run () - { - while (m_IsRunning) - { - try - { - m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "NTCP: runtime exception: ", ex.what ()); - } - } - } - - bool NTCPServer::AddNTCPSession (std::shared_ptr session) - { - if (!session || !session->GetRemoteIdentity ()) return false; - auto& ident = session->GetRemoteIdentity ()->GetIdentHash (); - auto it = m_NTCPSessions.find (ident); - if (it != m_NTCPSessions.end ()) - { - LogPrint (eLogWarning, "NTCP: session to ", ident.ToBase64 (), " already exists"); - session->Terminate(); - return false; - } - m_NTCPSessions.insert (std::pair >(ident, session)); - return true; - } - - void NTCPServer::RemoveNTCPSession (std::shared_ptr session) - { - if (session && session->GetRemoteIdentity ()) - m_NTCPSessions.erase (session->GetRemoteIdentity ()->GetIdentHash ()); - } - - std::shared_ptr NTCPServer::FindNTCPSession (const i2p::data::IdentHash& ident) - { - auto it = m_NTCPSessions.find (ident); - if (it != m_NTCPSessions.end ()) - return it->second; - return nullptr; - } - - void NTCPServer::HandleAccept (std::shared_ptr conn, const boost::system::error_code& error) - { - if (!error) - { - boost::system::error_code ec; - auto ep = conn->GetSocket ().remote_endpoint(ec); - if (!ec) - { - if(ShouldLimit()) - { - // hit limit, close premature - LogPrint(eLogWarning, "NTCP: limiting with backoff session from ", ep); - conn->Terminate(); - return; - } - LogPrint (eLogDebug, "NTCP: Connected from ", ep); - if (conn) - { - conn->ServerLogin (); - m_PendingIncomingSessions.push_back (conn); - } - } - else - LogPrint (eLogError, "NTCP: Connected from error ", ec.message ()); - } - - - if (error != boost::asio::error::operation_aborted) - { - conn = std::make_shared (*this); - m_NTCPAcceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAccept, this, - conn, std::placeholders::_1)); - } - } - - void NTCPServer::HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error) - { - if (!error) - { - boost::system::error_code ec; - auto ep = conn->GetSocket ().remote_endpoint(ec); - if (!ec) - { - if(ShouldLimit()) - { - // hit limit, close premature - LogPrint(eLogWarning, "NTCP: limiting with backoff on session from ", ep); - conn->Terminate(); - return; - } - - LogPrint (eLogDebug, "NTCP: Connected from ", ep); - if (conn) - { - conn->ServerLogin (); - m_PendingIncomingSessions.push_back (conn); - } - } - else - LogPrint (eLogError, "NTCP: Connected from error ", ec.message ()); - } - - if (error != boost::asio::error::operation_aborted) - { - conn = std::make_shared (*this); - m_NTCPV6Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCPServer::HandleAcceptV6, this, - conn, std::placeholders::_1)); - } - } - - void NTCPServer::Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr conn) - { - LogPrint (eLogDebug, "NTCP: Connecting to ", address ,":", port); - m_Service.post([=]() { - if (this->AddNTCPSession (conn)) - { - - auto timer = std::make_shared(m_Service); - timer->expires_from_now (boost::posix_time::seconds(NTCP_CONNECT_TIMEOUT)); - timer->async_wait ([conn](const boost::system::error_code& ecode) { - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogInfo, "NTCP: Not connected in ", NTCP_CONNECT_TIMEOUT, " seconds"); - conn->Terminate (); - } - }); - conn->GetSocket ().async_connect (boost::asio::ip::tcp::endpoint (address, port), std::bind (&NTCPServer::HandleConnect, this, std::placeholders::_1, conn, timer)); - } - }); - } - - void NTCPServer::ConnectWithProxy (const std::string& host, uint16_t port, RemoteAddressType addrtype, std::shared_ptr conn) - { - if(m_ProxyEndpoint == nullptr) - { - return; - } - m_Service.post([=]() { - if (this->AddNTCPSession (conn)) - { - - auto timer = std::make_shared(m_Service); - auto timeout = NTCP_CONNECT_TIMEOUT * 5; - conn->SetTerminationTimeout(timeout * 2); - timer->expires_from_now (boost::posix_time::seconds(timeout)); - timer->async_wait ([conn, timeout](const boost::system::error_code& ecode) { - if (ecode != boost::asio::error::operation_aborted) - { - LogPrint (eLogInfo, "NTCP: Not connected in ", timeout, " seconds"); - i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); - conn->Terminate (); - } - }); - conn->GetSocket ().async_connect (*m_ProxyEndpoint, std::bind (&NTCPServer::HandleProxyConnect, this, std::placeholders::_1, conn, timer, host, port, addrtype)); - } - }); - } - - void NTCPServer::HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer) - { - timer->cancel (); - if (ecode) - { - LogPrint (eLogInfo, "NTCP: Connect error ", ecode.message ()); - if (ecode != boost::asio::error::operation_aborted) - i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); - conn->Terminate (); - } - else - { - LogPrint (eLogDebug, "NTCP: Connected to ", conn->GetSocket ().remote_endpoint ()); - conn->ClientLogin (); - } - } - - void NTCPServer::UseProxy(ProxyType proxytype, const std::string & addr, uint16_t port) - { - m_ProxyType = proxytype; - m_ProxyAddress = addr; - m_ProxyPort = port; - } - - void NTCPServer::HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType addrtype) - { - if(ecode) - { - LogPrint(eLogWarning, "NTCP: failed to connect to proxy ", ecode.message()); - timer->cancel(); - conn->Terminate(); - return; - } - if(m_ProxyType == eSocksProxy) - { - // TODO: support username/password auth etc - uint8_t buff[3] = {0x05, 0x01, 0x00}; - boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff, 3), boost::asio::transfer_all(), [=] (const boost::system::error_code & ec, std::size_t transferred) { - (void) transferred; - if(ec) - { - LogPrint(eLogWarning, "NTCP: socks5 write error ", ec.message()); - } - }); - uint8_t readbuff[2]; - boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff, 2), [=](const boost::system::error_code & ec, std::size_t transferred) { - if(ec) - { - LogPrint(eLogError, "NTCP: socks5 read error ", ec.message()); - timer->cancel(); - conn->Terminate(); - return; - } - else if(transferred == 2) - { - if(readbuff[1] == 0x00) - { - AfterSocksHandshake(conn, timer, host, port, addrtype); - return; - } - else if (readbuff[1] == 0xff) - { - LogPrint(eLogError, "NTCP: socks5 proxy rejected authentication"); - timer->cancel(); - conn->Terminate(); - return; - } - } - LogPrint(eLogError, "NTCP: socks5 server gave invalid response"); - timer->cancel(); - conn->Terminate(); - }); - } - else if(m_ProxyType == eHTTPProxy) - { - i2p::http::HTTPReq req; - req.method = "CONNECT"; - req.version ="HTTP/1.1"; - if(addrtype == eIP6Address) - req.uri = "[" + host + "]:" + std::to_string(port); - else - req.uri = host + ":" + std::to_string(port); - - boost::asio::streambuf writebuff; - std::ostream out(&writebuff); - out << req.to_string(); - - boost::asio::async_write(conn->GetSocket(), writebuff.data(), boost::asio::transfer_all(), [=](const boost::system::error_code & ec, std::size_t transferred) { - (void) transferred; - if(ec) - LogPrint(eLogError, "NTCP: http proxy write error ", ec.message()); - }); - - boost::asio::streambuf * readbuff = new boost::asio::streambuf; - boost::asio::async_read_until(conn->GetSocket(), *readbuff, "\r\n\r\n", [=] (const boost::system::error_code & ec, std::size_t transferred) { - if(ec) - { - LogPrint(eLogError, "NTCP: http proxy read error ", ec.message()); - timer->cancel(); - conn->Terminate(); - } - else - { - readbuff->commit(transferred); - i2p::http::HTTPRes res; - if(res.parse(boost::asio::buffer_cast(readbuff->data()), readbuff->size()) > 0) - { - if(res.code == 200) - { - timer->cancel(); - conn->ClientLogin(); - delete readbuff; - return; - } - else - { - LogPrint(eLogError, "NTCP: http proxy rejected request ", res.code); - } - } - else - LogPrint(eLogError, "NTCP: http proxy gave malformed response"); - timer->cancel(); - conn->Terminate(); - delete readbuff; - } - }); - } - else - LogPrint(eLogError, "NTCP: unknown proxy type, invalid state"); - } - - void NTCPServer::AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType addrtype) - { - // build request - size_t sz = 0; - uint8_t buff[256]; - uint8_t readbuff[256]; - buff[0] = 0x05; - buff[1] = 0x01; - buff[2] = 0x00; - - if(addrtype == eIP4Address) - { - buff[3] = 0x01; - auto addr = boost::asio::ip::address::from_string(host).to_v4(); - auto addrbytes = addr.to_bytes(); - auto addrsize = addrbytes.size(); - memcpy(buff+4, addrbytes.data(), addrsize); - } - else if (addrtype == eIP6Address) - { - buff[3] = 0x04; - auto addr = boost::asio::ip::address::from_string(host).to_v6(); - auto addrbytes = addr.to_bytes(); - auto addrsize = addrbytes.size(); - memcpy(buff+4, addrbytes.data(), addrsize); - } - else if (addrtype == eHostname) - { - buff[3] = 0x03; - size_t addrsize = host.size(); - sz = addrsize + 1 + 4; - if (2 + sz > sizeof(buff)) - { - // too big - return; - } - buff[4] = (uint8_t) addrsize; - memcpy(buff+5, host.c_str(), addrsize); - } - htobe16buf(buff+sz, port); - sz += 2; - boost::asio::async_write(conn->GetSocket(), boost::asio::buffer(buff, sz), boost::asio::transfer_all(), [=](const boost::system::error_code & ec, std::size_t written) { - if(ec) - { - LogPrint(eLogError, "NTCP: failed to write handshake to socks proxy ", ec.message()); - return; - } - }); - - boost::asio::async_read(conn->GetSocket(), boost::asio::buffer(readbuff, 10), [=](const boost::system::error_code & e, std::size_t transferred) { - if(e) - { - LogPrint(eLogError, "NTCP: socks proxy read error ", e.message()); - } - else if(transferred == sz) - { - if( readbuff[1] == 0x00) - { - timer->cancel(); - conn->ClientLogin(); - return; - } - } - if(!e) - i2p::data::netdb.SetUnreachable (conn->GetRemoteIdentity ()->GetIdentHash (), true); - timer->cancel(); - conn->Terminate(); - }); - } - - void NTCPServer::ScheduleTermination () - { - m_TerminationTimer.expires_from_now (boost::posix_time::seconds(NTCP_TERMINATION_CHECK_TIMEOUT)); - m_TerminationTimer.async_wait (std::bind (&NTCPServer::HandleTerminationTimer, - this, std::placeholders::_1)); - } - - void NTCPServer::HandleTerminationTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) - { - auto ts = i2p::util::GetSecondsSinceEpoch (); - // established - for (auto& it: m_NTCPSessions) - if (it.second->IsTerminationTimeoutExpired (ts)) - { - auto session = it.second; - // Terminate modifies m_NTCPSession, so we postpone it - m_Service.post ([session] { - LogPrint (eLogDebug, "NTCP: No activity for ", session->GetTerminationTimeout (), " seconds"); - session->Terminate (); - }); - } - // pending - for (auto it = m_PendingIncomingSessions.begin (); it != m_PendingIncomingSessions.end ();) - { - if ((*it)->IsEstablished () || (*it)->IsTerminated ()) - it = m_PendingIncomingSessions.erase (it); // established or terminated - else if ((*it)->IsTerminationTimeoutExpired (ts)) - { - (*it)->Terminate (); - it = m_PendingIncomingSessions.erase (it); // expired - } - else - it++; - } - - ScheduleTermination (); - } - } -} -} diff --git a/libi2pd/NTCPSession.h b/libi2pd/NTCPSession.h deleted file mode 100644 index d3aa6f7c..00000000 --- a/libi2pd/NTCPSession.h +++ /dev/null @@ -1,242 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef NTCP_SESSION_H__ -#define NTCP_SESSION_H__ - -#include -#include -#include -#include -#include -#include -#include "Crypto.h" -#include "Identity.h" -#include "RouterInfo.h" -#include "I2NPProtocol.h" -#include "TransportSession.h" -#include "CryptoWorker.h" - -namespace i2p -{ -namespace transport -{ - struct NTCPPhase1 - { - uint8_t pubKey[256]; - uint8_t HXxorHI[32]; - }; - - struct NTCPPhase2 - { - uint8_t pubKey[256]; - struct - { - uint8_t hxy[32]; - uint8_t timestamp[4]; - uint8_t filler[12]; - } encrypted; - }; - - struct NTCPWork; - - const size_t NTCP_MAX_MESSAGE_SIZE = 16384; - const size_t NTCP_BUFFER_SIZE = 1028; // fits 1 tunnel data message - const int NTCP_CONNECT_TIMEOUT = 5; // 5 seconds - const int NTCP_ESTABLISH_TIMEOUT = 10; // 10 seconds - const int NTCP_TERMINATION_TIMEOUT = 120; // 2 minutes - const int NTCP_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds - 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_CLOCK_SKEW = 60; // in seconds - const int NTCP_MAX_OUTGOING_QUEUE_SIZE = 200; // how many messages we can queue up - - class NTCPServer; - class NTCPSession: public TransportSession, public std::enable_shared_from_this - { - public: - - NTCPSession (NTCPServer& server, std::shared_ptr in_RemoteRouter = nullptr); - ~NTCPSession (); - void Terminate (); - void Done (); - - boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; - boost::asio::io_service & GetService(); - bool IsEstablished () const { return m_IsEstablished; }; - bool IsTerminated () const { return m_IsTerminated; }; - - void ClientLogin (); - void ServerLogin (); - void SendI2NPMessages (const std::vector >& msgs); - - private: - - void PostI2NPMessages (std::vector > msgs); - void Connected (); - void SendTimeSyncMessage (); - void SetIsEstablished (bool isEstablished) { m_IsEstablished = isEstablished; } - - void CreateAESKey (uint8_t * pubKey); - - // client - void SendPhase3 (); - void HandlePhase1Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandlePhase2Received (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandlePhase2 (NTCPWork * work=nullptr); - void HandlePhase3Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA); - void HandlePhase4Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsA); - - //server - void SendPhase2 (NTCPWork * work=nullptr); - void SendPhase4 (uint32_t tsA, uint32_t tsB); - void HandlePhase1Received (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandlePhase2Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB); - void HandlePhase3Received (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB); - void HandlePhase3ExtraReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred, uint32_t tsB, size_t paddingLen); - void HandlePhase3 (uint32_t tsB, size_t paddingLen); - void HandlePhase4Sent (const boost::system::error_code& ecode, std::size_t bytes_transferred); - - // common - void Receive (); - void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); - bool DecryptNextBlock (const uint8_t * encrypted); - - void Send (std::shared_ptr msg); - boost::asio::const_buffers_1 CreateMsgBuffer (std::shared_ptr msg); - void Send (const std::vector >& msgs); - void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector > msgs); - - private: - - NTCPServer& m_Server; - boost::asio::ip::tcp::socket m_Socket; - bool m_IsEstablished, m_IsTerminated; - - i2p::crypto::CBCDecryption m_Decryption; - i2p::crypto::CBCEncryption m_Encryption; - - struct Establisher - { - NTCPPhase1 phase1; - NTCPPhase2 phase2; - } * m_Establisher; - - i2p::crypto::AESAlignedBuffer m_ReceiveBuffer; - i2p::crypto::AESAlignedBuffer<16> m_TimeSyncBuffer; - int m_ReceiveBufferOffset; - - std::shared_ptr m_NextMessage; - size_t m_NextMessageOffset; - i2p::I2NPMessagesHandler m_Handler; - - bool m_IsSending; - std::vector > m_SendQueue; - }; - - // TODO: move to NTCP.h/.cpp - class NTCPServer - { - public: - - typedef i2p::worker::ThreadPool Pool; - - enum RemoteAddressType - { - eIP4Address, - eIP6Address, - eHostname - }; - - enum ProxyType - { - eNoProxy, - eSocksProxy, - eHTTPProxy - }; - - - NTCPServer (int workers=4); - ~NTCPServer (); - - void Start (); - void Stop (); - - bool AddNTCPSession (std::shared_ptr session); - void RemoveNTCPSession (std::shared_ptr session); - std::shared_ptr FindNTCPSession (const i2p::data::IdentHash& ident); - void ConnectWithProxy (const std::string& addr, uint16_t port, RemoteAddressType addrtype, std::shared_ptr conn); - void Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr conn); - - bool IsBoundV4() const { return m_NTCPAcceptor != nullptr; }; - bool IsBoundV6() const { return m_NTCPV6Acceptor != nullptr; }; - bool NetworkIsReady() const { return IsBoundV4() || IsBoundV6() || UsingProxy(); }; - bool UsingProxy() const { return m_ProxyType != eNoProxy; }; - - void UseProxy(ProxyType proxy, const std::string & address, uint16_t port); - - boost::asio::io_service& GetService () { return m_Service; }; - - void SetSessionLimits(uint16_t softLimit, uint16_t hardLimit) { m_SoftLimit = softLimit; m_HardLimit = hardLimit; } - bool ShouldLimit() const { return ShouldHardLimit() || ShouldSoftLimit(); } - void Work(std::shared_ptr conn, Pool::WorkFunc work) - { - m_CryptoPool->Offer({conn, work}); - } - private: - - /** @brief return true for hard limit */ - bool ShouldHardLimit() const { return m_HardLimit && m_NTCPSessions.size() >= m_HardLimit; } - - /** @brief return true for probabalistic soft backoff */ - bool ShouldSoftLimit() const - { - auto sessions = m_NTCPSessions.size(); - return sessions && m_SoftLimit && m_SoftLimit < sessions && ( rand() % sessions ) <= m_SoftLimit; - } - void Run (); - void HandleAccept (std::shared_ptr conn, const boost::system::error_code& error); - void HandleAcceptV6 (std::shared_ptr conn, const boost::system::error_code& error); - - void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer); - - void HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType adddrtype); - void AfterSocksHandshake(std::shared_ptr conn, std::shared_ptr timer, const std::string & host, uint16_t port, RemoteAddressType adddrtype); - - // timer - void ScheduleTermination (); - void HandleTerminationTimer (const boost::system::error_code& ecode); - - private: - - bool m_IsRunning; - std::thread * m_Thread; - boost::asio::io_service m_Service; - boost::asio::io_service::work m_Work; - boost::asio::deadline_timer m_TerminationTimer; - boost::asio::ip::tcp::acceptor * m_NTCPAcceptor, * m_NTCPV6Acceptor; - std::map > m_NTCPSessions; // access from m_Thread only - std::list > m_PendingIncomingSessions; - - ProxyType m_ProxyType; - std::string m_ProxyAddress; - uint16_t m_ProxyPort; - boost::asio::ip::tcp::resolver m_Resolver; - boost::asio::ip::tcp::endpoint * m_ProxyEndpoint; - - std::shared_ptr m_CryptoPool; - - uint16_t m_SoftLimit, m_HardLimit; - public: - - // for HTTP/I2PControl - const decltype(m_NTCPSessions)& GetNTCPSessions () const { return m_NTCPSessions; }; - }; -} -} - -#endif diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 54bf3fd9..19e17f52 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -133,7 +133,7 @@ namespace transport Transports::Transports (): m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_Thread (nullptr), m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), - m_NTCPServer (nullptr), m_SSUServer (nullptr), m_NTCP2Server (nullptr), + m_SSUServer (nullptr), m_NTCP2Server (nullptr), m_DHKeysPairSupplier (5), m_X25519KeysPairSupplier (5), // 5 pre-generated keys m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0), m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0), @@ -154,7 +154,7 @@ namespace transport } } - void Transports::Start (bool enableNTCP, bool enableSSU) + void Transports::Start (bool enableNTCP2, bool enableSSU) { if (!m_Service) { @@ -169,50 +169,10 @@ namespace transport m_X25519KeysPairSupplier.Start (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); - std::string ntcpproxy; i2p::config::GetOption("ntcpproxy", ntcpproxy); std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); - i2p::http::URL proxyurl; - uint16_t softLimit, hardLimit, threads; - i2p::config::GetOption("limits.ntcpsoft", softLimit); - i2p::config::GetOption("limits.ntcphard", hardLimit); - i2p::config::GetOption("limits.ntcpthreads", threads); - if(softLimit > 0 && hardLimit > 0 && softLimit >= hardLimit) - { - LogPrint(eLogError, "ntcp soft limit must be less than ntcp hard limit"); - return; - } - if(ntcpproxy.size() && enableNTCP) - { - if(proxyurl.parse(ntcpproxy)) - { - if(proxyurl.schema == "socks" || proxyurl.schema == "http") - { - m_NTCPServer = new NTCPServer(threads); - m_NTCPServer->SetSessionLimits(softLimit, hardLimit); - NTCPServer::ProxyType proxytype = NTCPServer::eSocksProxy; - - if (proxyurl.schema == "http") - proxytype = NTCPServer::eHTTPProxy; - m_NTCPServer->UseProxy(proxytype, proxyurl.host, proxyurl.port); - m_NTCPServer->Start(); - if(!m_NTCPServer->NetworkIsReady()) - { - LogPrint(eLogError, "Transports: NTCP failed to start with proxy"); - m_NTCPServer->Stop(); - delete m_NTCPServer; - m_NTCPServer = nullptr; - } - } - else - LogPrint(eLogError, "Transports: unsupported NTCP proxy URL ", ntcpproxy); - } - else - LogPrint(eLogError, "Transports: invalid NTCP proxy url ", ntcpproxy); - return; - } + i2p::http::URL proxyurl; // create NTCP2. TODO: move to acceptor - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) + if (enableNTCP2) { if(!ntcp2proxy.empty()) { @@ -248,20 +208,6 @@ namespace transport for (const auto& address : addresses) { if (!address) continue; - if (m_NTCPServer == nullptr && enableNTCP) - { - m_NTCPServer = new NTCPServer (threads); - m_NTCPServer->SetSessionLimits(softLimit, hardLimit); - m_NTCPServer->Start (); - if (!(m_NTCPServer->IsBoundV6() || m_NTCPServer->IsBoundV4())) { - /** failed to bind to NTCP */ - LogPrint(eLogError, "Transports: failed to bind to TCP"); - m_NTCPServer->Stop(); - delete m_NTCPServer; - m_NTCPServer = nullptr; - } - } - if (address->transportStyle == RouterInfo::eTransportSSU) { if (m_SSUServer == nullptr && enableSSU) @@ -306,13 +252,7 @@ namespace transport delete m_SSUServer; m_SSUServer = nullptr; } - if (m_NTCPServer) - { - m_NTCPServer->Stop (); - delete m_NTCPServer; - m_NTCPServer = nullptr; - } - + if (m_NTCP2Server) { m_NTCP2Server->Stop (); diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index 48eee4b4..c3008b09 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -21,7 +21,6 @@ #include #include #include "TransportSession.h" -#include "NTCPSession.h" #include "SSU.h" #include "NTCP2.h" #include "RouterInfo.h" @@ -88,10 +87,9 @@ namespace transport Transports (); ~Transports (); - void Start (bool enableNTCP=true, bool enableSSU=true); + void Start (bool enableNTCP2=true, bool enableSSU=true); void Stop (); - bool IsBoundNTCP() const { return m_NTCPServer != nullptr; } bool IsBoundSSU() const { return m_SSUServer != nullptr; } bool IsBoundNTCP2() const { return m_NTCP2Server != nullptr; } @@ -159,7 +157,6 @@ namespace transport boost::asio::io_service::work * m_Work; boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer; - NTCPServer * m_NTCPServer; SSUServer * m_SSUServer; NTCP2Server * m_NTCP2Server; mutable std::mutex m_PeersMutex; @@ -186,7 +183,6 @@ namespace transport public: // for HTTP only - const NTCPServer * GetNTCPServer () const { return m_NTCPServer; }; const SSUServer * GetSSUServer () const { return m_SSUServer; }; const NTCP2Server * GetNTCP2Server () const { return m_NTCP2Server; }; const decltype(m_Peers)& GetPeers () const { return m_Peers; }; diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 6eec3a16..468d2d00 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -42,7 +42,6 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \ ../../libi2pd/NetDb.cpp \ ../../libi2pd/NetDbRequests.cpp \ ../../libi2pd/NTCP2.cpp \ - ../../libi2pd/NTCPSession.cpp \ ../../libi2pd/Poly1305.cpp \ ../../libi2pd/Profiling.cpp \ ../../libi2pd/Reseed.cpp \ @@ -123,7 +122,6 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../libi2pd/NetDb.hpp \ ../../libi2pd/NetDbRequests.h \ ../../libi2pd/NTCP2.h \ - ../../libi2pd/NTCPSession.h \ ../../libi2pd/Poly1305.h \ ../../libi2pd/Profiling.h \ ../../libi2pd/Queue.h \ From c2f13a1496272237d348ed1f76d0836d4daaca8e Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 3 Oct 2020 22:29:52 -0400 Subject: [PATCH 3811/6300] some cleanup --- daemon/Daemon.cpp | 5 ++--- libi2pd/RouterContext.cpp | 45 +++++++-------------------------------- libi2pd/RouterContext.h | 2 +- libi2pd/RouterInfo.cpp | 14 ------------ libi2pd/RouterInfo.h | 1 - 5 files changed, 11 insertions(+), 56 deletions(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 5f98d47a..f5d4d24f 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -142,8 +142,7 @@ namespace i2p i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); - bool ntcp; i2p::config::GetOption("ntcp", ntcp); - i2p::context.PublishNTCPAddress (ntcp, !ipv6); + i2p::context.RemoveNTCPAddress (!ipv6); // TODO: remove later bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) { @@ -151,7 +150,7 @@ namespace i2p if (published) { uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port); - if (!ntcp && !ntcp2port) ntcp2port = port; // use standard port + if (!ntcp2port) ntcp2port = port; // use standard port i2p::context.PublishNTCP2Address (ntcp2port, true); // publish if (ipv6) { diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 7333e613..69e69d7c 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -373,48 +373,19 @@ namespace i2p return m_RouterInfo.GetCaps () & i2p::data::RouterInfo::eUnreachable; } - void RouterContext::PublishNTCPAddress (bool publish, bool v4only) + void RouterContext::RemoveNTCPAddress (bool v4only) { auto& addresses = m_RouterInfo.GetAddresses (); - if (publish) + for (auto it = addresses.begin (); it != addresses.end ();) { - for (const auto& addr : addresses) // v4 + if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP && !(*it)->IsNTCP2 () && + (!v4only || (*it)->host.is_v4 ())) { - if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU && - addr->host.is_v4 ()) - { - // insert NTCP address with host/port from SSU - m_RouterInfo.AddNTCPAddress (addr->host.to_string ().c_str (), addr->port); - break; - } - } - if (!v4only) - { - for (const auto& addr : addresses) // v6 - { - if (addr->transportStyle == i2p::data::RouterInfo::eTransportSSU && - addr->host.is_v6 ()) - { - // insert NTCP address with host/port from SSU - m_RouterInfo.AddNTCPAddress (addr->host.to_string ().c_str (), addr->port); - break; - } - } - } - } - else - { - for (auto it = addresses.begin (); it != addresses.end ();) - { - if ((*it)->transportStyle == i2p::data::RouterInfo::eTransportNTCP && !(*it)->IsNTCP2 () && - (!v4only || (*it)->host.is_v4 ())) - { - it = addresses.erase (it); - if (v4only) break; // otherwise might be more than one address - } - else - ++it; + it = addresses.erase (it); + if (v4only) break; // otherwise might be more than one address } + else + ++it; } } diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 6f08a87a..a576d6b6 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -89,7 +89,7 @@ namespace i2p void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon void PublishNTCP2Address (int port, bool publish = true, bool v4only = false); void UpdateNTCP2Address (bool enable); - void PublishNTCPAddress (bool publish, bool v4only = true); + void RemoveNTCPAddress (bool v4only = true); // delete NTCP address for older routers. TODO: remove later bool AddIntroducer (const i2p::data::RouterInfo::Introducer& introducer); void RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); bool IsUnreachable () const; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 703c853f..12f81a42 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -709,20 +709,6 @@ namespace data s.write (str.c_str (), len); } - void RouterInfo::AddNTCPAddress (const char * host, int port) - { - auto addr = std::make_shared
    (); - addr->host = boost::asio::ip::address::from_string (host); - addr->port = port; - addr->transportStyle = eTransportNTCP; - addr->cost = 6; - addr->date = 0; - for (const auto& it: *m_Addresses) // don't insert same address twice - if (*it == *addr) return; - m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4; - m_Addresses->push_front(std::move(addr)); // always make NTCP first - } - void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu) { auto addr = std::make_shared
    (); diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 1c0063b0..282c34f9 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -157,7 +157,6 @@ namespace data std::shared_ptr GetSSUAddress (bool v4only = true) const; std::shared_ptr GetSSUV6Address () const; - void AddNTCPAddress (const char * host, int port); void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0); bool AddIntroducer (const Introducer& introducer); From d3f7eea0a3e505a9adee2780036a646e6ef008fe Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 4 Oct 2020 09:05:57 +0300 Subject: [PATCH 3812/6300] [appveyor] Disable unavailable repository Ref: https://github.com/msys2/MINGW-packages/issues/7084 --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index d5f67b14..0b105031 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,6 +26,9 @@ install: - c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" +# disable inaccessible miror (doing same for sourceforge because it's laggy) - https://github.com/msys2/MINGW-packages/issues/7084 +- c:\msys64\usr\bin\bash -lc "sed -i 's/^Server\ =\ http:\/\/repo\.msys2\.org/#Server\ =\ http:\/\/repo\.msys2\.org/g' /etc/pacman.d/mirrorlist.*" +- c:\msys64\usr\bin\bash -lc "sed -i 's/^Server\ =\ https:\/\/sourceforge\.net/#Server\ =\ https:\/\/sourceforge\.net/g' /etc/pacman.d/mirrorlist.*" # remove packages which can break build - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" # update runtime From 7ceb81cc8302d3e504b9a3106c39e0579ad7706d Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 4 Oct 2020 09:16:43 +0300 Subject: [PATCH 3813/6300] [appveyor] clean packages cache after disabling mirrors --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 0b105031..350a2c05 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,9 +26,10 @@ install: - c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" -# disable inaccessible miror (doing same for sourceforge because it's laggy) - https://github.com/msys2/MINGW-packages/issues/7084 +# disable inaccessible miror (doing same for sourceforge because it's laggy) and clean cache - https://github.com/msys2/MINGW-packages/issues/7084 - c:\msys64\usr\bin\bash -lc "sed -i 's/^Server\ =\ http:\/\/repo\.msys2\.org/#Server\ =\ http:\/\/repo\.msys2\.org/g' /etc/pacman.d/mirrorlist.*" - c:\msys64\usr\bin\bash -lc "sed -i 's/^Server\ =\ https:\/\/sourceforge\.net/#Server\ =\ https:\/\/sourceforge\.net/g' /etc/pacman.d/mirrorlist.*" +- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syy" # remove packages which can break build - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" # update runtime From 65e15d74fc255cc920a0ae30164454e480b377cd Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 4 Oct 2020 09:32:21 +0300 Subject: [PATCH 3814/6300] [appveyor] print mirrorlist (testing) --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 350a2c05..c993a6ba 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,10 +26,10 @@ install: - c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" -# disable inaccessible miror (doing same for sourceforge because it's laggy) and clean cache - https://github.com/msys2/MINGW-packages/issues/7084 +# disable inaccessible miror (doing same for sourceforge because it's laggy) - https://github.com/msys2/MINGW-packages/issues/7084 - c:\msys64\usr\bin\bash -lc "sed -i 's/^Server\ =\ http:\/\/repo\.msys2\.org/#Server\ =\ http:\/\/repo\.msys2\.org/g' /etc/pacman.d/mirrorlist.*" - c:\msys64\usr\bin\bash -lc "sed -i 's/^Server\ =\ https:\/\/sourceforge\.net/#Server\ =\ https:\/\/sourceforge\.net/g' /etc/pacman.d/mirrorlist.*" -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syy" +- c:\msys64\usr\bin\bash -lc "cat /etc/pacman.d/mirrorlist.*" # remove packages which can break build - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" # update runtime From e614226926ce51f217364d0630130c245fb36437 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 4 Oct 2020 09:39:40 +0300 Subject: [PATCH 3815/6300] [appveyor] change repository disabling way (testing) --- appveyor.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index c993a6ba..346cda4c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,9 +26,8 @@ install: - c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" -# disable inaccessible miror (doing same for sourceforge because it's laggy) - https://github.com/msys2/MINGW-packages/issues/7084 -- c:\msys64\usr\bin\bash -lc "sed -i 's/^Server\ =\ http:\/\/repo\.msys2\.org/#Server\ =\ http:\/\/repo\.msys2\.org/g' /etc/pacman.d/mirrorlist.*" -- c:\msys64\usr\bin\bash -lc "sed -i 's/^Server\ =\ https:\/\/sourceforge\.net/#Server\ =\ https:\/\/sourceforge\.net/g' /etc/pacman.d/mirrorlist.*" +# disable inaccessible miror - https://github.com/msys2/MINGW-packages/issues/7084 +- c:\msys64\usr\bin\bash -lc "sed -e '/repo\.msys2\.org/ s/^#*/#/' -i /etc/pacman.d/mirrorlist.*" - c:\msys64\usr\bin\bash -lc "cat /etc/pacman.d/mirrorlist.*" # remove packages which can break build - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" From 77231bfc6c03c8176065699b0c535c7a6b8e0cc9 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 4 Oct 2020 09:53:31 +0300 Subject: [PATCH 3816/6300] [appeveyor] rewrite mirrorlist (testing) https://github.com/msys2/MINGW-packages/issues/7084#issuecomment-703211308 --- appveyor.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 346cda4c..c6b562c4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,8 +26,10 @@ install: - c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" -# disable inaccessible miror - https://github.com/msys2/MINGW-packages/issues/7084 -- c:\msys64\usr\bin\bash -lc "sed -e '/repo\.msys2\.org/ s/^#*/#/' -i /etc/pacman.d/mirrorlist.*" +# disable inaccessible miror (something block sed from changing files, so rewrite them) - https://github.com/msys2/MINGW-packages/issues/7084 +- c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/mingw/x86_64/' > /etc/pacman.d/mirrorlist.mingw64" +- c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/mingw/i686/' > /etc/pacman.d/mirrorlist.mingw32" +- c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/msys/$arch/' > /etc/pacman.d/mirrorlist.msys" - c:\msys64\usr\bin\bash -lc "cat /etc/pacman.d/mirrorlist.*" # remove packages which can break build - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" From 67b76809eab7b57c66295b0224e334ef68a4b2dc Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 4 Oct 2020 09:57:45 +0300 Subject: [PATCH 3817/6300] [appveyor] rewrite mirrorlist after updating runtime If pacman was updated on runtime update, changes which we domne earlier will be rewrited by config from package --- appveyor.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index c6b562c4..10165e5f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,13 +30,16 @@ install: - c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/mingw/x86_64/' > /etc/pacman.d/mirrorlist.mingw64" - c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/mingw/i686/' > /etc/pacman.d/mirrorlist.mingw32" - c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/msys/$arch/' > /etc/pacman.d/mirrorlist.msys" -- c:\msys64\usr\bin\bash -lc "cat /etc/pacman.d/mirrorlist.*" # remove packages which can break build - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" # update runtime - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" # Kill bash before next try - taskkill /T /F /IM bash.exe /IM gpg.exe /IM gpg-agent.exe | exit /B 0 +# rewrite mirrorlist again because pacman update can rewrite it +- c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/mingw/x86_64/' > /etc/pacman.d/mirrorlist.mingw64" +- c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/mingw/i686/' > /etc/pacman.d/mirrorlist.mingw32" +- c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/msys/$arch/' > /etc/pacman.d/mirrorlist.msys" # update packages and install required - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu ${MSYS_PACKAGES}" From 243f6e755b27b58b5e33b41b8022c7afc0bd5881 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 4 Oct 2020 09:34:15 -0400 Subject: [PATCH 3818/6300] restore copyright header --- daemon/Daemon.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index f5d4d24f..1b9c0a2c 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include From d218c9a9832f7419e11c94573a5a866bdef4a6e6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 4 Oct 2020 10:12:33 -0400 Subject: [PATCH 3819/6300] disable ntcpproxy --- libi2pd/Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index e0dc66d8..1c565083 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -60,7 +60,7 @@ namespace config { ("share", value()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)") ("ntcp", value()->default_value(false), "Ignored. Always false") ("ssu", value()->default_value(true), "Enable SSU transport (default: enabled)") - ("ntcpproxy", value()->default_value(""), "Proxy URL for NTCP transport") + ("ntcpproxy", value()->default_value(""), "Ignored") #ifdef _WIN32 ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") ("insomnia", bool_switch()->default_value(false), "Prevent system from sleeping (default: disabled)") From 59032d515b0b9cb41dec1b0abc7adf8394dbfd0e Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 4 Oct 2020 19:52:12 -0400 Subject: [PATCH 3820/6300] i2p.streaming.answerPings=false by default for client tunnels --- libi2pd_client/ClientContext.cpp | 8 ++++---- libi2pd_client/ClientContext.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index e83e293a..04f50e6f 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -444,7 +444,7 @@ namespace client } template - void ClientContext::ReadI2CPOptions (const Section& section, std::map& options) const + void ClientContext::ReadI2CPOptions (const Section& section, bool isServer, std::map& options) const { options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); @@ -454,7 +454,7 @@ namespace client options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY); options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY); options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); - options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, DEFAULT_ANSWER_PINGS); + options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false); options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, ""); if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType; @@ -570,7 +570,7 @@ namespace client i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); // I2CP std::map options; - ReadI2CPOptions (section, options); + ReadI2CPOptions (section, false, options); std::shared_ptr localDestination = nullptr; if (keys.length () > 0) @@ -694,7 +694,7 @@ namespace client // I2CP std::map options; - ReadI2CPOptions (section, options); + ReadI2CPOptions (section, true, options); std::shared_ptr localDestination = nullptr; auto it = destinations.find (keys); diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 05b754fe..076aaa5f 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -115,7 +115,7 @@ namespace client template void ReadI2CPOptionsGroup (const Section& section, const std::string& group, std::map& options) const; template - void ReadI2CPOptions (const Section& section, std::map& options) const; // for tunnels + void ReadI2CPOptions (const Section& section, bool isServer, std::map& options) const; // for tunnels void ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const; // for HTTP and SOCKS proxy void CleanupUDP(const boost::system::error_code & ecode); From 471c46ad8e2f9f6869e005105730bfb87fa69e0b Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 6 Oct 2020 16:22:40 -0400 Subject: [PATCH 3821/6300] remove some HTTP headers from response --- libi2pd_client/I2PTunnel.cpp | 104 ++++++++++++++++++++++++++--------- libi2pd_client/I2PTunnel.h | 6 +- 2 files changed, 83 insertions(+), 27 deletions(-) diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 3575e062..946d55d5 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -139,22 +139,25 @@ namespace client } } else - { - if (m_Stream) - { - auto s = shared_from_this (); - m_Stream->AsyncSend (m_Buffer, bytes_transferred, - [s](const boost::system::error_code& ecode) - { - if (!ecode) - s->Receive (); - else - s->Terminate (); - }); - } - } + WriteToStream (m_Buffer, bytes_transferred); } + void I2PTunnelConnection::WriteToStream (const uint8_t * buf, size_t len) + { + if (m_Stream) + { + auto s = shared_from_this (); + m_Stream->AsyncSend (buf, len, + [s](const boost::system::error_code& ecode) + { + if (!ecode) + s->Receive (); + else + s->Terminate (); + }); + } + } + void I2PTunnelConnection::HandleWrite (const boost::system::error_code& ecode) { if (ecode) @@ -302,7 +305,8 @@ namespace client I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& host): - I2PTunnelConnection (owner, stream, socket, target), m_Host (host), m_HeaderSent (false), m_From (stream->GetRemoteIdentity ()) + I2PTunnelConnection (owner, stream, socket, target), m_Host (host), + m_HeaderSent (false), m_ResponseHeaderSent (false), m_From (stream->GetRemoteIdentity ()) { } @@ -333,12 +337,59 @@ namespace client else break; } - // add X-I2P fields - if (m_From) + + if (endOfHeader) { - m_OutHeader << X_I2P_DEST_B32 << ": " << context.GetAddressBook ().ToAddress(m_From->GetIdentHash ()) << "\r\n"; - m_OutHeader << X_I2P_DEST_HASH << ": " << m_From->GetIdentHash ().ToBase64 () << "\r\n"; - m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n"; + // add X-I2P fields + if (m_From) + { + m_OutHeader << X_I2P_DEST_B32 << ": " << context.GetAddressBook ().ToAddress(m_From->GetIdentHash ()) << "\r\n"; + m_OutHeader << X_I2P_DEST_HASH << ": " << m_From->GetIdentHash ().ToBase64 () << "\r\n"; + m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n"; + } + + m_OutHeader << "\r\n"; // end of header + m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header + m_InHeader.str (""); + m_From = nullptr; + m_HeaderSent = true; + I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); + } + } + } + + void I2PServerTunnelConnectionHTTP::WriteToStream (const uint8_t * buf, size_t len) + { + if (m_ResponseHeaderSent) + I2PTunnelConnection::WriteToStream (buf, len); + else + { + m_InHeader.clear (); + if (m_InHeader.str ().empty ()) m_OutHeader.str (""); // start of response + m_InHeader.write ((const char *)buf, len); + std::string line; + bool endOfHeader = false; + while (!endOfHeader) + { + std::getline(m_InHeader, line); + if (!m_InHeader.fail ()) + { + if (line == "\r") endOfHeader = true; + else + { + if (line.find ("Server:") != std::string::npos) continue; // exclude "Server:" + if (line.find ("Date:") != std::string::npos) continue; // exclude "Date"" + if (line[0] == 'X') + { + if (line.find ("X-Runtime:") != std::string::npos) continue; // exclude "X-Runtime:" + if (line.find ("X-Powered-By:") != std::string::npos) continue; // exclude "X-Powered-By:" + } + + m_OutHeader << line << "\n"; + } + } + else + break; } if (endOfHeader) @@ -346,12 +397,15 @@ namespace client m_OutHeader << "\r\n"; // end of header m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header m_InHeader.str (""); - m_HeaderSent = true; - I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); - } - } + m_ResponseHeaderSent = true; + I2PTunnelConnection::WriteToStream ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); + m_OutHeader.str (""); + } + else + Receive (); + } } - + I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass): diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index b76a1027..f7185dfd 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -57,7 +57,8 @@ namespace client void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); virtual void Write (const uint8_t * buf, size_t len); // can be overloaded void HandleWrite (const boost::system::error_code& ecode); - + virtual void WriteToStream (const uint8_t * buf, size_t len); // can be overloaded + void StreamReceive (); void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleConnect (const boost::system::error_code& ecode); @@ -103,12 +104,13 @@ namespace client protected: void Write (const uint8_t * buf, size_t len); + void WriteToStream (const uint8_t * buf, size_t len); private: std::string m_Host; std::stringstream m_InHeader, m_OutHeader; - bool m_HeaderSent; + bool m_HeaderSent, m_ResponseHeaderSent; std::shared_ptr m_From; }; From 724662498330644693bd74df24aef144778c5bdd Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 6 Oct 2020 19:24:03 -0400 Subject: [PATCH 3822/6300] list of headers to remove --- libi2pd_client/I2PTunnel.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 946d55d5..081294c6 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -328,7 +328,7 @@ namespace client if (line == "\r") endOfHeader = true; else { - if (m_Host.length () > 0 && line.find ("Host:") != std::string::npos) + if (m_Host.length () > 0 && !line.compare(0, 5, "Host:")) m_OutHeader << "Host: " << m_Host << "\r\n"; // override host else m_OutHeader << line << "\n"; @@ -377,15 +377,19 @@ namespace client if (line == "\r") endOfHeader = true; else { - if (line.find ("Server:") != std::string::npos) continue; // exclude "Server:" - if (line.find ("Date:") != std::string::npos) continue; // exclude "Date"" - if (line[0] == 'X') - { - if (line.find ("X-Runtime:") != std::string::npos) continue; // exclude "X-Runtime:" - if (line.find ("X-Powered-By:") != std::string::npos) continue; // exclude "X-Powered-By:" - } - - m_OutHeader << line << "\n"; + static const std::vector excluded // list of excluded headers + { + "Server:", "Date:", "X-Runtime:", "X-Powered-By:", "Proxy" + }; + bool matched = false; + for (const auto& it: excluded) + if (!line.compare(0, it.length (), it)) + { + matched = true; + break; + } + if (!matched) + m_OutHeader << line << "\n"; } } else From b0c690d836822e4bd9fdc9d545aba4dc9054594f Mon Sep 17 00:00:00 2001 From: user Date: Wed, 7 Oct 2020 19:25:02 +0800 Subject: [PATCH 3823/6300] qt: build* added to .gitignore --- qt/i2pd_qt/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/qt/i2pd_qt/.gitignore b/qt/i2pd_qt/.gitignore index f1d57c58..bbf3e1e4 100644 --- a/qt/i2pd_qt/.gitignore +++ b/qt/i2pd_qt/.gitignore @@ -8,4 +8,5 @@ Makefile* object_script.* i2pd_qt_plugin_import.cpp i2pd_qt.pro.autosave* +build* From f6ff232106ee59d4feca5c22803c9b5896cf5a3b Mon Sep 17 00:00:00 2001 From: user Date: Wed, 7 Oct 2020 23:16:06 +0800 Subject: [PATCH 3824/6300] qt: crypto type added --- qt/i2pd_qt/ClientTunnelPane.cpp | 17 ++++++++++ qt/i2pd_qt/ClientTunnelPane.h | 13 ++++++++ qt/i2pd_qt/ServerTunnelPane.cpp | 17 ++++++++++ qt/i2pd_qt/ServerTunnelPane.h | 13 ++++++++ qt/i2pd_qt/TunnelConfig.cpp | 8 +++-- qt/i2pd_qt/TunnelConfig.h | 15 ++++++--- qt/i2pd_qt/mainwindow.cpp | 56 +++++++++++++++++++++++++++------ qt/i2pd_qt/mainwindow.h | 31 +++++++++++++----- 8 files changed, 146 insertions(+), 24 deletions(-) diff --git a/qt/i2pd_qt/ClientTunnelPane.cpp b/qt/i2pd_qt/ClientTunnelPane.cpp index 256d0510..fbfa74cb 100644 --- a/qt/i2pd_qt/ClientTunnelPane.cpp +++ b/qt/i2pd_qt/ClientTunnelPane.cpp @@ -168,6 +168,23 @@ int ClientTunnelPane::appendClientTunnelForm( horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } + { + int cryptoType = tunnelConfig->getcryptoType(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + ui.cryptoTypeLabel = new QLabel(gridLayoutWidget_2); + cryptoTypeLabel->setObjectName(QStringLiteral("cryptoTypeLabel")); + horizontalLayout_2->addWidget(cryptoTypeLabel); + ui.cryptoTypeLineEdit = new QLineEdit(gridLayoutWidget_2); + cryptoTypeLineEdit->setObjectName(QStringLiteral("cryptoTypeLineEdit")); + cryptoTypeLineEdit->setText(QString::number(cryptoType)); + cryptoTypeLineEdit->setMaximumWidth(80); + QObject::connect(cryptoTypeLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(cryptoTypeLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } { i2p::data::SigningKeyType sigType = tunnelConfig->getsigType(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); diff --git a/qt/i2pd_qt/ClientTunnelPane.h b/qt/i2pd_qt/ClientTunnelPane.h index c2e076b7..80d331de 100644 --- a/qt/i2pd_qt/ClientTunnelPane.h +++ b/qt/i2pd_qt/ClientTunnelPane.h @@ -53,6 +53,10 @@ private: QLabel * sigTypeLabel; QComboBox * sigTypeComboBox; + //cryptoType + QLabel * cryptoTypeLabel; + QLineEdit * cryptoTypeLineEdit; + protected slots: virtual void setGroupBoxTitle(const QString & title); @@ -61,6 +65,7 @@ private: typeLabel->setText(QApplication::translate("cltTunForm", "Client tunnel type:", 0)); destinationLabel->setText(QApplication::translate("cltTunForm", "Destination:", 0)); portLabel->setText(QApplication::translate("cltTunForm", "Port:", 0)); + cryptoTypeLabel->setText(QApplication::translate("cltTunForm", "Crypto type:", 0)); keysLabel->setText(QApplication::translate("cltTunForm", "Keys:", 0)); destinationPortLabel->setText(QApplication::translate("cltTunForm", "Destination port:", 0)); addressLabel->setText(QApplication::translate("cltTunForm", "Address:", 0)); @@ -86,6 +91,14 @@ protected: } ctc->setport(portInt); + auto cryptoTypeStr=cryptoTypeLineEdit->text(); + int cryptoTypeInt=cryptoTypeStr.toInt(&ok); + if(!ok){ + highlightWrongInput(QApplication::tr("Bad crypto type, must be int.")+" "+cannotSaveSettings,cryptoTypeLineEdit); + return false; + } + ctc->setcryptoType(cryptoTypeInt); + ctc->setkeys(keysLineEdit->text().toStdString()); ctc->setaddress(addressLineEdit->text().toStdString()); diff --git a/qt/i2pd_qt/ServerTunnelPane.cpp b/qt/i2pd_qt/ServerTunnelPane.cpp index bc6389a9..cd751f5d 100644 --- a/qt/i2pd_qt/ServerTunnelPane.cpp +++ b/qt/i2pd_qt/ServerTunnelPane.cpp @@ -235,6 +235,23 @@ int ServerTunnelPane::appendServerTunnelForm( horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } + { + int cryptoType = tunnelConfig->getcryptoType(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + ui.cryptoTypeLabel = new QLabel(gridLayoutWidget_2); + cryptoTypeLabel->setObjectName(QStringLiteral("cryptoTypeLabel")); + horizontalLayout_2->addWidget(cryptoTypeLabel); + ui.cryptoTypeLineEdit = new QLineEdit(gridLayoutWidget_2); + cryptoTypeLineEdit->setObjectName(QStringLiteral("cryptoTypeLineEdit")); + cryptoTypeLineEdit->setText(QString::number(cryptoType)); + cryptoTypeLineEdit->setMaximumWidth(80); + QObject::connect(cryptoTypeLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(cryptoTypeLineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); + } { I2CPParameters& i2cpParameters = tunnelConfig->getI2cpParameters(); appendControlsForI2CPParameters(i2cpParameters, gridIndex); diff --git a/qt/i2pd_qt/ServerTunnelPane.h b/qt/i2pd_qt/ServerTunnelPane.h index 0a07267b..92ee4da5 100644 --- a/qt/i2pd_qt/ServerTunnelPane.h +++ b/qt/i2pd_qt/ServerTunnelPane.h @@ -65,6 +65,10 @@ private: QLabel * inPortLabel; QLineEdit * inPortLineEdit; + //cryptoType + QLabel * cryptoTypeLabel; + QLineEdit * cryptoTypeLineEdit; + //accessList QLabel * accessListLabel; QLineEdit * accessListLineEdit; @@ -101,6 +105,7 @@ private: portLabel->setText(QApplication::translate("srvTunForm", "Port:", 0)); keysLabel->setText(QApplication::translate("srvTunForm", "Keys:", 0)); inPortLabel->setText(QApplication::translate("srvTunForm", "InPort:", 0)); + cryptoTypeLabel->setText(QApplication::translate("srvTunForm", "Crypto type:", 0)); accessListLabel->setText(QApplication::translate("srvTunForm", "Access list:", 0)); hostOverrideLabel->setText(QApplication::translate("srvTunForm", "Host override:", 0)); webIRCPassLabel->setText(QApplication::translate("srvTunForm", "WebIRC password:", 0)); @@ -129,6 +134,14 @@ protected: } stc->setport(portInt); + auto cryptoTypeStr=cryptoTypeLineEdit->text(); + int cryptoTypeInt=cryptoTypeStr.toInt(&ok); + if(!ok){ + highlightWrongInput(QApplication::tr("Bad crypto type, must be int.")+" "+cannotSaveSettings,cryptoTypeLineEdit); + return false; + } + stc->setcryptoType(cryptoTypeInt); + stc->setkeys(keysLineEdit->text().toStdString()); auto str=inPortLineEdit->text(); diff --git a/qt/i2pd_qt/TunnelConfig.cpp b/qt/i2pd_qt/TunnelConfig.cpp index 8ed72930..04fa6132 100644 --- a/qt/i2pd_qt/TunnelConfig.cpp +++ b/qt/i2pd_qt/TunnelConfig.cpp @@ -32,6 +32,7 @@ void ClientTunnelConfig::saveToStringStream(std::stringstream& out) { << "port=" << port << "\n" << "destination=" << dest << "\n" << "destinationport=" << destinationPort << "\n" + << "cryptoType=" << getcryptoType() << "\n" << "signaturetype=" << sigType << "\n"; if(!keys.empty()) out << "keys=" << keys << "\n"; } @@ -41,9 +42,10 @@ void ServerTunnelConfig::saveToStringStream(std::stringstream& out) { out << "host=" << host << "\n" << "port=" << port << "\n" << "signaturetype=" << sigType << "\n" - << "inport=" << inPort << "\n" - << "accesslist=" << accessList << "\n" - << "gzip=" << (gzip?"true":"false") << "\n" + << "inport=" << inPort << "\n"; + if(accessList.size()>0) { out << "accesslist=" << accessList << "\n"; } + out << "gzip=" << (gzip?"true":"false") << "\n" + << "cryptoType=" << getcryptoType() << "\n" << "enableuniquelocal=" << (isUniqueLocal?"true":"false") << "\n" << "address=" << address << "\n" << "hostoverride=" << hostOverride << "\n" diff --git a/qt/i2pd_qt/TunnelConfig.h b/qt/i2pd_qt/TunnelConfig.h index 059d48b5..7f262215 100644 --- a/qt/i2pd_qt/TunnelConfig.h +++ b/qt/i2pd_qt/TunnelConfig.h @@ -58,12 +58,14 @@ class TunnelConfig { QString type; std::string name; TunnelPane* tunnelPane; + int cryptoType; public: - TunnelConfig(std::string name_, QString& type_, I2CPParameters& i2cpParameters_): - type(type_), name(name_), i2cpParameters(i2cpParameters_) {} + TunnelConfig(std::string name_, QString& type_, I2CPParameters& i2cpParameters_, int cryptoType_): + type(type_), name(name_), cryptoType(cryptoType_), i2cpParameters(i2cpParameters_) {} virtual ~TunnelConfig(){} const QString& getType(){return type;} const std::string& getName(){return name;} + int getcryptoType(){return cryptoType;} void setType(const QString& type_){type=type_;} void setName(const std::string& name_){name=name_;} I2CPParameters& getI2cpParameters(){return i2cpParameters;} @@ -74,6 +76,7 @@ public: virtual ServerTunnelConfig* asServerTunnelConfig()=0; void setTunnelPane(TunnelPane* tp){this->tunnelPane = tp;} TunnelPane* getTunnelPane() {return tunnelPane;} + void setcryptoType(int cryptoType_){cryptoType=cryptoType_;} private: I2CPParameters i2cpParameters; }; @@ -114,13 +117,14 @@ public: std::string keys_, std::string address_, int destinationPort_, - i2p::data::SigningKeyType sigType_): TunnelConfig(name_, type_, i2cpParameters_), + i2p::data::SigningKeyType sigType_, + int cryptoType_): TunnelConfig(name_, type_, i2cpParameters_, cryptoType_), dest(dest_), port(port_), keys(keys_), address(address_), destinationPort(destinationPort_), - sigType(sigType_){} + sigType(sigType_) {} std::string& getdest(){return dest;} int getport(){return port;} std::string & getkeys(){return keys;} @@ -188,7 +192,8 @@ public: bool gzip_, i2p::data::SigningKeyType sigType_, std::string address_, - bool isUniqueLocal_): TunnelConfig(name_, type_, i2cpParameters_), + bool isUniqueLocal_, + int cryptoType_): TunnelConfig(name_, type_, i2cpParameters_, cryptoType_), host(host_), port(port_), keys(keys_), diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 0ce0e2f8..14a57f8a 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -159,12 +159,8 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren # define OPTION(section,option,defaultValueGetter) ConfigOption(QString(section),QString(option)) initFileChooser( OPTION("","conf",[](){return "";}), uiSettings->configFileLineEdit, uiSettings->configFileBrowsePushButton); - initFolderChooser( OPTION("","datadir",[]{return "";}), uiSettings->dataFolderLineEdit, uiSettings->dataFolderBrowsePushButton); initFileChooser( OPTION("","tunconf",[](){return "";}), uiSettings->tunnelsConfigFileLineEdit, uiSettings->tunnelsConfigFileBrowsePushButton); - initFileChooser( OPTION("","pidfile",[]{return "";}), uiSettings->pidFileLineEdit, uiSettings->pidFileBrowsePushButton); - daemonOption=initNonGUIOption( OPTION("","daemon",[]{return "";})); - serviceOption=initNonGUIOption( OPTION("","service",[]{return "";})); uiSettings->logDestinationComboBox->clear(); uiSettings->logDestinationComboBox->insertItems(0, QStringList() @@ -176,20 +172,28 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren logFileNameOption=initFileChooser( OPTION("","logfile",[]{return "";}), uiSettings->logFileLineEdit, uiSettings->logFileBrowsePushButton); initLogLevelCombobox(OPTION("","loglevel",[]{return "";}), uiSettings->logLevelComboBox); - + //TODO add option "logclftime" "Write full CLF-formatted date and time to log (default: write only time)" + initFolderChooser( OPTION("","datadir",[]{return "";}), uiSettings->dataFolderLineEdit, uiSettings->dataFolderBrowsePushButton); initIPAddressBox( OPTION("","host",[]{return "";}), uiSettings->routerExternalHostLineEdit, tr("Router external address -> Host")); initTCPPortBox( OPTION("","port",[]{return "";}), uiSettings->routerExternalPortLineEdit, tr("Router external address -> Port")); - + daemonOption=initNonGUIOption( OPTION("","daemon",[]{return "";})); + serviceOption=initNonGUIOption( OPTION("","service",[]{return "";})); + //TODO add option "ifname4" Network interface to bind to for IPv4 + //TODO add option "ifname6" Network interface to bind to for IPv6 + //TODO add option "nat" If true, assume we are behind NAT. true by default + //TODO add option "ipv4" Enable communication through IPv4. true by default initCheckBox( OPTION("","ipv6",[]{return "false";}), uiSettings->ipv6CheckBox); initCheckBox( OPTION("","notransit",[]{return "false";}), uiSettings->notransitCheckBox); initCheckBox( OPTION("","floodfill",[]{return "false";}), uiSettings->floodfillCheckBox); initStringBox( OPTION("","bandwidth",[]{return "";}), uiSettings->bandwidthLineEdit); + //TODO add option "share" Max % of bandwidth limit for transit. 0-100. 100 by default initStringBox( OPTION("","family",[]{return "";}), uiSettings->familyLineEdit); initIntegerBox( OPTION("","netid",[]{return "2";}), uiSettings->netIdLineEdit, tr("NetID")); + //TODO add option "ssu" Enable SSU transport protocol (use UDP). true by default #ifdef Q_OS_WIN - initCheckBox( OPTION("","insomnia",[]{return "";}), uiSettings->insomniaCheckBox); initNonGUIOption( OPTION("","svcctl",[]{return "";})); + initCheckBox( OPTION("","insomnia",[]{return "";}), uiSettings->insomniaCheckBox); initNonGUIOption( OPTION("","close",[]{return "";})); #else uiSettings->insomniaCheckBox->setEnabled(false); @@ -201,17 +205,22 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initCheckBox( OPTION("http","auth",[]{return "";}), uiSettings->webconsoleBasicAuthCheckBox); initStringBox( OPTION("http","user",[]{return "i2pd";}), uiSettings->webconsoleUserNameLineEditBasicAuth); initStringBox( OPTION("http","pass",[]{return "";}), uiSettings->webconsolePasswordLineEditBasicAuth); + //TODO add option "http.strictheaders Enable strict host checking on WebUI. true by default + //TODO add option "http.hostname Expected hostname for WebUI (default: localhost) initCheckBox( OPTION("httpproxy","enabled",[]{return "";}), uiSettings->httpProxyEnabledCheckBox); initIPAddressBox( OPTION("httpproxy","address",[]{return "";}), uiSettings->httpProxyAddressLineEdit, tr("HTTP proxy -> IP address")); initTCPPortBox( OPTION("httpproxy","port",[]{return "4444";}), uiSettings->httpProxyPortLineEdit, tr("HTTP proxy -> Port")); + //TODO add option "httpproxy.addresshelper Enable address helper (jump). true by default initFileChooser( OPTION("httpproxy","keys",[]{return "";}), uiSettings->httpProxyKeyFileLineEdit, uiSettings->httpProxyKeyFilePushButton); - initSignatureTypeCombobox(OPTION("httpproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_httpPorxySignatureType); initStringBox( OPTION("httpproxy","inbound.length",[]{return "3";}), uiSettings->httpProxyInboundTunnelsLenLineEdit); initStringBox( OPTION("httpproxy","inbound.quantity",[]{return "5";}), uiSettings->httpProxyInboundTunnQuantityLineEdit); initStringBox( OPTION("httpproxy","outbound.length",[]{return "3";}), uiSettings->httpProxyOutBoundTunnLenLineEdit); initStringBox( OPTION("httpproxy","outbound.quantity",[]{return "5";}), uiSettings->httpProxyOutboundTunnQuantityLineEdit); + //TODO add option "httpproxy.outproxy HTTP proxy upstream out proxy url (like http://false.i2p) + //TODO add option "httpproxy.i2cp.leaseSetType Type of LeaseSet to be sent. 1, 3 or 5. 1 by default + //TODO add option "httpproxy.i2cp.leaseSetEncType Comma separated encryption types to be used in LeaseSet type 3 or 5 initCheckBox( OPTION("socksproxy","enabled",[]{return "";}), uiSettings->socksProxyEnabledCheckBox); initIPAddressBox( OPTION("socksproxy","address",[]{return "";}), uiSettings->socksProxyAddressLineEdit, tr("Socks proxy -> IP address")); @@ -224,10 +233,13 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initStringBox( OPTION("socksproxy","outbound.quantity",[]{return "";}), uiSettings->socksProxyOutboundTunnQuantityLineEdit); initIPAddressBox( OPTION("socksproxy","outproxy",[]{return "";}), uiSettings->outproxyAddressLineEdit, tr("Socks proxy -> Outproxy address")); initTCPPortBox( OPTION("socksproxy","outproxyport",[]{return "";}), uiSettings->outproxyPortLineEdit, tr("Socks proxy -> Outproxy port")); + //TODO add option "socksproxy.i2cp.leaseSetType Type of LeaseSet to be sent. 1, 3 or 5. 1 by default + //TODO add option "socksproxy.i2cp.leaseSetEncType Comma separated encryption types to be used in LeaseSet type 3 or 5 initCheckBox( OPTION("sam","enabled",[]{return "false";}), uiSettings->samEnabledCheckBox); initIPAddressBox( OPTION("sam","address",[]{return "";}), uiSettings->samAddressLineEdit, tr("SAM -> IP address")); initTCPPortBox( OPTION("sam","port",[]{return "7656";}), uiSettings->samPortLineEdit, tr("SAM -> Port")); + //TODO add option "sam.singlethread If false every SAM session runs in own thread. true by default initCheckBox( OPTION("bob","enabled",[]{return "false";}), uiSettings->bobEnabledCheckBox); initIPAddressBox( OPTION("bob","address",[]{return "";}), uiSettings->bobAddressLineEdit, tr("BOB -> IP address")); @@ -236,6 +248,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initCheckBox( OPTION("i2cp","enabled",[]{return "false";}), uiSettings->i2cpEnabledCheckBox); initIPAddressBox( OPTION("i2cp","address",[]{return "";}), uiSettings->i2cpAddressLineEdit, tr("I2CP -> IP address")); initTCPPortBox( OPTION("i2cp","port",[]{return "7654";}), uiSettings->i2cpPortLineEdit, tr("I2CP -> Port")); + //TODO add option "i2cp.singlethread If false every I2CP session runs in own thread. true by default initCheckBox( OPTION("i2pcontrol","enabled",[]{return "false";}), uiSettings->i2pControlEnabledCheckBox); initIPAddressBox( OPTION("i2pcontrol","address",[]{return "";}), uiSettings->i2pControlAddressLineEdit, tr("I2PControl -> IP address")); @@ -252,6 +265,9 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initCheckBox( OPTION("reseed","verify",[]{return "";}), uiSettings->reseedVerifyCheckBox); initFileChooser( OPTION("reseed","file",[]{return "";}), uiSettings->reseedFileLineEdit, uiSettings->reseedFileBrowsePushButton); initStringBox( OPTION("reseed","urls",[]{return "";}), uiSettings->reseedURLsLineEdit); + //TODO add option "reseed.zipfile Path to local .zip file to reseed from + //TODO add option "reseed.threshold Minimum number of known routers before requesting reseed. 25 by default + //TODO add option "reseed.proxy Url for https/socks reseed proxy initStringBox( OPTION("addressbook","defaulturl",[]{return "";}), uiSettings->addressbookDefaultURLLineEdit); initStringBox( OPTION("addressbook","subscriptions",[]{return "";}), uiSettings->addressbookSubscriptionsURLslineEdit); @@ -259,12 +275,34 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initUInt16Box( OPTION("limits","transittunnels",[]{return "2500";}), uiSettings->maxNumOfTransitTunnelsLineEdit, tr("maxNumberOfTransitTunnels")); initUInt16Box( OPTION("limits","openfiles",[]{return "0";}), uiSettings->maxNumOfOpenFilesLineEdit, tr("maxNumberOfOpenFiles")); initUInt32Box( OPTION("limits","coresize",[]{return "0";}), uiSettings->coreFileMaxSizeNumberLineEdit, tr("coreFileMaxSize")); + //TODO add option "limits.ntcpsoft Threshold to start probabalistic backoff with ntcp sessions (0 - use system limit) + //TODO add option "limits.ntcphard Maximum number of ntcp sessions (0 - use system limit) initCheckBox( OPTION("trust","enabled",[]{return "false";}), uiSettings->checkBoxTrustEnable); initStringBox( OPTION("trust","family",[]{return "";}), uiSettings->lineEditTrustFamily); initStringBox( OPTION("trust","routers",[]{return "";}), uiSettings->lineEditTrustRouters); initCheckBox( OPTION("trust","hidden",[]{return "false";}), uiSettings->checkBoxTrustHidden); + //TODO add option "websockets.enabled Enable websocket server. Disabled by default + //TODO add option "websockets.address Address to bind websocket server on. 127.0.0.1 by default + //TODO add option "websockets.port Port to bind websocket server on. 7666 by default + + //TODO add option "exploratory.inbound.length Exploratory inbound tunnels length. 2 by default + //TODO add option "exploratory.inbound.quantity Exploratory inbound tunnels quantity. 3 by default + //TODO add option "exploratory.outbound.length Exploratory outbound tunnels length. 2 by default + //TODO add option "exploratory.outbound.quantity Exploratory outbound tunnels length. 3 by default + + //TODO add option "ntcp2.enabled Enable NTCP2. Enabled by default + //TODO add option "ntcp2.published Enable incoming NTCP2 connections. Disabled by default + //TODO add option "ntcp2.port Port to listen for incoming NTCP2 connections (default: auto) + //TODO add option "ntcp2.addressv6 External IPv6 for incoming connections + //TODO add option "ntcp2.proxy Specify proxy server for NTCP2. Should be http://address:port or socks://address:port + + //TODO add option "nettime.enabled Enable NTP sync. Disabled by default + //TODO add option "nettime.ntpservers Comma-separated list of NTP server. pool.ntp.org by default + //TODO add option "nettime.ntpsyncinterval NTP time sync interval in hours. 72 by default + + //TODO add option "persist.profiles Enable peer profile persisting to disk. Enabled by default # undef OPTION //widgetlocks.add(new widgetlock(widget,lockbtn)); @@ -666,7 +704,7 @@ void MainWindow::layoutTunnels() { int height=0; ui->tunnelsScrollAreaWidgetContents->setGeometry(0,0,0,0); for(std::map::iterator it = tunnelConfigs.begin(); it != tunnelConfigs.end(); ++it) { - const std::string& name=it->first; + //const std::string& name=it->first; TunnelConfig* tunconf = it->second; TunnelPane * tunnelPane=tunconf->getTunnelPane(); if(!tunnelPane)continue; diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 91c99122..5fefbc06 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -104,10 +104,13 @@ class MainWindowItem : public QObject { QWidget* widgetToFocus; QString requirementToBeValid; public: - MainWindowItem(ConfigOption option_, QWidget* widgetToFocus_, QString requirementToBeValid_) : option(option_), widgetToFocus(widgetToFocus_), requirementToBeValid(requirementToBeValid_) {} + MainWindowItem(ConfigOption option_, QWidget* widgetToFocus_, QString requirementToBeValid_) : + option(option_), widgetToFocus(widgetToFocus_), requirementToBeValid(requirementToBeValid_), + optionValuePresent(false) {} QWidget* getWidgetToFocus(){return widgetToFocus;} QString& getRequirementToBeValid() { return requirementToBeValid; } ConfigOption& getConfigOption() { return option; } + bool optionValuePresent; boost::any optionValue; virtual ~MainWindowItem(){} virtual void installListeners(MainWindow *mainWindow); @@ -118,7 +121,8 @@ public: //qDebug() << "loadFromConfigOption[" << optName.c_str() << "]"; boost::any programOption; i2p::config::GetOptionAsAny(optName, programOption); - optionValue=programOption.empty()?boost::any(std::string("")) + optionValuePresent=!programOption.empty(); + optionValue=!optionValuePresent?boost::any(std::string("")) :boost::any_cast(programOption).value(); } virtual void saveToStringStream(std::stringstream& out){ @@ -126,7 +130,7 @@ public: std::string v = boost::any_cast(optionValue); if(v.empty())return; } - if(optionValue.empty())return; + if(!optionValuePresent || optionValue.empty())return; std::string rtti = optionValue.type().name(); std::string optName=""; if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); @@ -180,6 +184,7 @@ public: virtual void saveToStringStream(std::stringstream& out){ optionValue=fromString(lineEdit->text()); + optionValuePresent=true; MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } @@ -234,6 +239,7 @@ public: virtual void saveToStringStream(std::stringstream& out){ std::string logDest = comboBox->currentText().toStdString(); optionValue=logDest; + optionValuePresent=true; MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } @@ -251,6 +257,7 @@ public: } virtual void saveToStringStream(std::stringstream& out){ optionValue=comboBox->currentText().toStdString(); + optionValuePresent=true; MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } @@ -268,6 +275,7 @@ public: virtual void saveToStringStream(std::stringstream& out){ uint16_t selected = SignatureTypeComboBoxFactory::getSigType(comboBox->currentData()); optionValue=(unsigned short)selected; + optionValuePresent=true; MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } @@ -284,6 +292,7 @@ public: } virtual void saveToStringStream(std::stringstream& out){ optionValue=checkBox->isChecked(); + optionValuePresent=true; MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } @@ -613,6 +622,7 @@ private: std::string keys = ""; std::string address = "127.0.0.1"; int destinationPort = 0; + int cryptoType = 0; i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; // I2CP I2CPParameters i2cpParameters; @@ -624,7 +634,8 @@ private: keys, address, destinationPort, - sigType); + sigType, + cryptoType); saveAllConfigs(true, name); } @@ -643,6 +654,7 @@ private: i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; std::string address = "127.0.0.1"; bool isUniqueLocal = true; + int cryptoType = 0; // I2CP I2CPParameters i2cpParameters; @@ -659,7 +671,8 @@ private: gzip, sigType, address, - isUniqueLocal); + isUniqueLocal, + cryptoType); saveAllConfigs(true, name); @@ -712,6 +725,7 @@ private: // optional params std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); + int cryptoType = section.second.get(I2P_CLIENT_TUNNEL_CRYPTO_TYPE, 0); int destinationPort = section.second.get(I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); std::cout << "had read tunnel destinationPort: " << destinationPort << std::endl; i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); @@ -726,7 +740,8 @@ private: keys, address, destinationPort, - sigType); + sigType, + cryptoType); } else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP @@ -746,6 +761,7 @@ private: i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, "127.0.0.1"); bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); + int cryptoType = section.second.get(I2P_CLIENT_TUNNEL_CRYPTO_TYPE, 0); // I2CP std::map options; @@ -779,7 +795,8 @@ private: gzip, sigType, address, - isUniqueLocal); + isUniqueLocal, + cryptoType); } else LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", tunConf);//TODO show err box and disable the tunn gui From e2a1cd12c36d1d00120b260158fe487ef269e17f Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 7 Oct 2020 21:13:26 -0400 Subject: [PATCH 3825/6300] don't delete unreachable routers if too few --- libi2pd/NetDb.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 5930baf7..4ce839ee 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -560,6 +560,9 @@ namespace data updatedCount++; continue; } + // make router reachable back if too few routers + if (it.second->IsUnreachable () && total - deletedCount < NETDB_MIN_ROUTERS) + it.second->SetUnreachable (false); // find & mark expired routers if (it.second->UsesIntroducer ()) { @@ -575,7 +578,7 @@ namespace data // delete RI file m_Storage.Remove(ident); deletedCount++; - if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false; + if (total - deletedCount < NETDB_MIN_ROUTERS) checkForExpiration = false; } } // m_RouterInfos iteration From 01087450659a52fd2f5ca065c0355ba81c5bc024 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 8 Oct 2020 15:11:55 +0800 Subject: [PATCH 3826/6300] qt: bool optionValuePresent removed --- qt/i2pd_qt/mainwindow.h | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 5fefbc06..6e187b5b 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -105,12 +105,10 @@ class MainWindowItem : public QObject { QString requirementToBeValid; public: MainWindowItem(ConfigOption option_, QWidget* widgetToFocus_, QString requirementToBeValid_) : - option(option_), widgetToFocus(widgetToFocus_), requirementToBeValid(requirementToBeValid_), - optionValuePresent(false) {} + option(option_), widgetToFocus(widgetToFocus_), requirementToBeValid(requirementToBeValid_) {} QWidget* getWidgetToFocus(){return widgetToFocus;} QString& getRequirementToBeValid() { return requirementToBeValid; } ConfigOption& getConfigOption() { return option; } - bool optionValuePresent; boost::any optionValue; virtual ~MainWindowItem(){} virtual void installListeners(MainWindow *mainWindow); @@ -121,8 +119,7 @@ public: //qDebug() << "loadFromConfigOption[" << optName.c_str() << "]"; boost::any programOption; i2p::config::GetOptionAsAny(optName, programOption); - optionValuePresent=!programOption.empty(); - optionValue=!optionValuePresent?boost::any(std::string("")) + optionValue=programOption.empty()?boost::any(std::string("")) :boost::any_cast(programOption).value(); } virtual void saveToStringStream(std::stringstream& out){ @@ -130,7 +127,7 @@ public: std::string v = boost::any_cast(optionValue); if(v.empty())return; } - if(!optionValuePresent || optionValue.empty())return; + if(optionValue.empty())return; std::string rtti = optionValue.type().name(); std::string optName=""; if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); @@ -184,7 +181,6 @@ public: virtual void saveToStringStream(std::stringstream& out){ optionValue=fromString(lineEdit->text()); - optionValuePresent=true; MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } @@ -239,7 +235,6 @@ public: virtual void saveToStringStream(std::stringstream& out){ std::string logDest = comboBox->currentText().toStdString(); optionValue=logDest; - optionValuePresent=true; MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } @@ -257,7 +252,6 @@ public: } virtual void saveToStringStream(std::stringstream& out){ optionValue=comboBox->currentText().toStdString(); - optionValuePresent=true; MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } @@ -275,7 +269,6 @@ public: virtual void saveToStringStream(std::stringstream& out){ uint16_t selected = SignatureTypeComboBoxFactory::getSigType(comboBox->currentData()); optionValue=(unsigned short)selected; - optionValuePresent=true; MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } @@ -292,7 +285,6 @@ public: } virtual void saveToStringStream(std::stringstream& out){ optionValue=checkBox->isChecked(); - optionValuePresent=true; MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } From e21dac21c8b813e023fbbf44296eb4a90df3792c Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 11 Oct 2020 14:02:12 -0400 Subject: [PATCH 3827/6300] fixed #1557. don't try to send empty message --- libi2pd/Tunnel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index fe7e36af..704706ba 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -303,6 +303,7 @@ namespace tunnel { for (auto& msg : msgs) { + if (!msg.data) continue; switch (msg.deliveryType) { case eDeliveryTypeLocal: From ffa0f0afd91b786e13a479087d4092de5bed121a Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 11 Oct 2020 17:51:40 -0400 Subject: [PATCH 3828/6300] check network status --- daemon/HTTPServer.cpp | 3 +++ libi2pd/NetDb.cpp | 3 ++- libi2pd/RouterContext.h | 3 ++- libi2pd/Transports.cpp | 15 ++++++++++++++- libi2pd/Transports.h | 5 +++-- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 19734038..d00387aa 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -259,6 +259,9 @@ namespace http { case eRouterErrorClockSkew: s << "
    Clock skew"; break; + case eRouterErrorOffline: + s << "
    Offline"; + break; default: ; } break; diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 4ce839ee..318db953 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -125,7 +125,8 @@ namespace data } } if (!m_IsRunning) break; - + if (!i2p::transport::transports.IsOnline ()) continue; // don't manage netdb when offline + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); if (ts - lastManageRequest >= 15) // manage requests every 15 seconds { diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index a576d6b6..37e1791d 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -37,7 +37,8 @@ namespace i2p enum RouterError { eRouterErrorNone = 0, - eRouterErrorClockSkew = 1 + eRouterErrorClockSkew = 1, + eRouterErrorOffline = 2 }; class RouterContext: public i2p::garlic::GarlicDestination diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 19e17f52..8a44bc3e 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -321,7 +321,8 @@ namespace transport void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) { - SendMessages (ident, std::vector > {msg }); + if (m_IsOnline) + SendMessages (ident, std::vector > {msg }); } void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs) @@ -756,5 +757,17 @@ namespace transport } return false; } + + void Transports::SetOnline (bool online) + { + if (m_IsOnline != online) + { + m_IsOnline = online; + if (online) + PeerTest (); + else + i2p::context.SetError (eRouterErrorOffline); + } + } } } diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index c3008b09..661337a2 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -94,7 +94,7 @@ namespace transport bool IsBoundNTCP2() const { return m_NTCP2Server != nullptr; } bool IsOnline() const { return m_IsOnline; }; - void SetOnline (bool online) { m_IsOnline = online; }; + void SetOnline (bool online); boost::asio::io_service& GetService () { return *m_Service; }; std::shared_ptr GetNextDHKeysPair (); @@ -151,7 +151,8 @@ namespace transport private: - bool m_IsOnline, m_IsRunning, m_IsNAT; + volatile bool m_IsOnline; + bool m_IsRunning, m_IsNAT; std::thread * m_Thread; boost::asio::io_service * m_Service; boost::asio::io_service::work * m_Work; From 0b372a344c0738f62420a5db3048adab428a44a2 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 12 Oct 2020 07:29:46 +0300 Subject: [PATCH 3829/6300] [webconsole] change error status print format Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index d00387aa..3b9f6eaa 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -257,11 +257,11 @@ namespace http { switch (i2p::context.GetError ()) { case eRouterErrorClockSkew: - s << "
    Clock skew"; + s << " - Clock skew"; break; case eRouterErrorOffline: - s << "
    Offline"; - break; + s << " - Offline"; + break; default: ; } break; From 99d046ca11430bd59ee0bf67456f90dd648daf67 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 12 Oct 2020 07:31:57 +0300 Subject: [PATCH 3830/6300] [http] handle WebDAV methods Signed-off-by: R4SAS --- libi2pd/HTTP.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index 4f7b03a1..484f4c0c 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -16,8 +16,8 @@ namespace i2p { namespace http { const std::vector HTTP_METHODS = { - "GET", "HEAD", "POST", "PUT", "PATCH", - "DELETE", "OPTIONS", "CONNECT", "PROPFIND" + "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "CONNECT", // HTTP basic methods + "COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK", "SEARCH" // WebDAV methods, for SEARCH see rfc5323 }; const std::vector HTTP_VERSIONS = { "HTTP/1.0", "HTTP/1.1" From 85e9da82b023323da1541be4d2e5eed01d9d03cf Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 12 Oct 2020 17:36:44 +0300 Subject: [PATCH 3831/6300] [transports] validate IP when trying connect to remote peer for being in reserved IP range Signed-off-by: R4SAS --- daemon/Daemon.cpp | 6 ++++-- libi2pd/Config.cpp | 1 + libi2pd/SSU.cpp | 1 + libi2pd/Transports.cpp | 47 +++++++++++++++++++++------------------- libi2pd/Transports.h | 5 ++++- libi2pd/util.cpp | 49 +++++++++++++++++++++++++++++++++++++++++- libi2pd/util.h | 1 + 7 files changed, 84 insertions(+), 26 deletions(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 1b9c0a2c..0317704a 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -299,14 +299,16 @@ namespace i2p bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ssu; i2p::config::GetOption("ssu", ssu); + bool checkInReserved; i2p::config::GetOption("reservedrange", checkInReserved); LogPrint(eLogInfo, "Daemon: starting Transports"); if(!ssu) LogPrint(eLogInfo, "Daemon: ssu disabled"); if(!ntcp2) LogPrint(eLogInfo, "Daemon: ntcp2 disabled"); + i2p::transport::transports.SetCheckReserved(checkInReserved); i2p::transport::transports.Start(ntcp2, ssu); - if (i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundNTCP2()) + if (i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundNTCP2()) LogPrint(eLogInfo, "Daemon: Transports started"); - else + else { LogPrint(eLogError, "Daemon: failed to start Transports"); /** shut down netdb right away */ diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 1c565083..b924a108 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -51,6 +51,7 @@ namespace config { ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv4", value()->default_value(true), "Enable communication through ipv4 (default: enabled)") ("ipv6", bool_switch()->default_value(false), "Enable communication through ipv6 (default: disabled)") + ("reservedrange", value()->default_value(true), "Check remote RI for being in blacklist of reserved IP ranges (default: enabled)") ("netid", value()->default_value(I2PD_NET_ID), "Specify NetID. Main I2P is 2") ("daemon", bool_switch()->default_value(false), "Router will go to background after start (default: disabled)") ("service", bool_switch()->default_value(false), "Router will use system folders like '/var/lib/i2pd' (default: disabled)") diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index 0f4526bd..f9c06f37 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -459,6 +459,7 @@ namespace transport // otherwise create new session auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); sessions[remoteEndpoint] = session; + // connect LogPrint (eLogDebug, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 8a44bc3e..ca86cf42 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -27,20 +27,20 @@ namespace transport { } - template + template EphemeralKeysSupplier::~EphemeralKeysSupplier () { Stop (); } - template + template void EphemeralKeysSupplier::Start () { m_IsRunning = true; m_Thread = new std::thread (std::bind (&EphemeralKeysSupplier::Run, this)); } - template + template void EphemeralKeysSupplier::Stop () { { @@ -56,7 +56,7 @@ namespace transport } } - template + template void EphemeralKeysSupplier::Run () { while (m_IsRunning) @@ -81,7 +81,7 @@ namespace transport } } - template + template void EphemeralKeysSupplier::CreateEphemeralKeys (int num) { if (num > 0) @@ -96,7 +96,7 @@ namespace transport } } - template + template std::shared_ptr EphemeralKeysSupplier::Acquire () { { @@ -115,7 +115,7 @@ namespace transport return pair; } - template + template void EphemeralKeysSupplier::Return (std::shared_ptr pair) { if (pair) @@ -131,8 +131,8 @@ namespace transport Transports transports; Transports::Transports (): - m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_Thread (nullptr), m_Service (nullptr), - m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), + m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_СheckReserved(true), m_Thread (nullptr), + m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), m_SSUServer (nullptr), m_NTCP2Server (nullptr), m_DHKeysPairSupplier (5), m_X25519KeysPairSupplier (5), // 5 pre-generated keys m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0), @@ -170,7 +170,7 @@ namespace transport m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); std::string ntcp2proxy; i2p::config::GetOption("ntcp2.proxy", ntcp2proxy); - i2p::http::URL proxyurl; + i2p::http::URL proxyurl; // create NTCP2. TODO: move to acceptor if (enableNTCP2) { @@ -252,7 +252,7 @@ namespace transport delete m_SSUServer; m_SSUServer = nullptr; } - + if (m_NTCP2Server) { m_NTCP2Server->Stop (); @@ -372,7 +372,7 @@ namespace transport } else { - LogPrint (eLogWarning, "Transports: delayed messages queue size to ", + LogPrint (eLogWarning, "Transports: delayed messages queue size to ", ident.ToBase64 (), " exceeds ", MAX_NUM_DELAYED_MESSAGES); std::unique_lock l(m_PeersMutex); m_Peers.erase (it); @@ -393,7 +393,7 @@ namespace transport { // NTCP2 have priority over NTCP auto address = peer.router->GetNTCP2Address (true, !context.SupportsV6 ()); // published only - if (address && !peer.router->IsUnreachable ()) + if (address && !peer.router->IsUnreachable () && (!m_СheckReserved || !i2p::util::net::IsInReservedRange(address->host))) { auto s = std::make_shared (*m_NTCP2Server, peer.router); @@ -419,8 +419,11 @@ namespace transport if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ())) { auto address = peer.router->GetSSUAddress (!context.SupportsV6 ()); - m_SSUServer->CreateSession (peer.router, address->host, address->port); - return true; + if (!m_СheckReserved || !i2p::util::net::IsInReservedRange(address->host)) + { + m_SSUServer->CreateSession (peer.router, address->host, address->port); + return true; + } } } LogPrint (eLogInfo, "Transports: No NTCP or SSU addresses available"); @@ -556,7 +559,7 @@ namespace transport { m_X25519KeysPairSupplier.Return (pair); } - + void Transports::PeerConnected (std::shared_ptr session) { m_Service->post([session, this]() @@ -758,16 +761,16 @@ namespace transport return false; } - void Transports::SetOnline (bool online) - { + void Transports::SetOnline (bool online) + { if (m_IsOnline != online) - { - m_IsOnline = online; + { + m_IsOnline = online; if (online) PeerTest (); else i2p::context.SetError (eRouterErrorOffline); - } - } + } + } } } diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index 661337a2..55170ee8 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -136,6 +136,9 @@ namespace transport void PeerTest (); + void SetCheckReserved (bool check) { m_СheckReserved = check; }; + bool IsCheckReserved () { return m_СheckReserved; }; + private: void Run (); @@ -152,7 +155,7 @@ namespace transport private: volatile bool m_IsOnline; - bool m_IsRunning, m_IsNAT; + bool m_IsRunning, m_IsNAT, m_СheckReserved; std::thread * m_Thread; boost::asio::io_service * m_Service; boost::asio::io_service::work * m_Work; diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index f5204a50..2cc101c7 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -61,6 +61,9 @@ int inet_pton_xp(int af, const char *src, void *dst) #include #endif +#define address_pair_v4(a,b) { boost::asio::ip::address_v4::from_string (a).to_ulong (), boost::asio::ip::address_v4::from_string (b).to_ulong () } +#define address_pair_v6(a,b) { boost::asio::ip::address_v6::from_string (a).to_bytes (), boost::asio::ip::address_v6::from_string (b).to_bytes () } + namespace i2p { namespace util @@ -391,6 +394,50 @@ namespace net return boost::asio::ip::address::from_string(fallback); #endif } -} + + bool IsInReservedRange(const boost::asio::ip::address& host) { + // https://en.wikipedia.org/wiki/Reserved_IP_addresses + if(host.is_v4()) + { + static const std::vector< std::pair > reservedIPv4Ranges { + address_pair_v4("0.0.0.0", "0.255.255.255"), + address_pair_v4("10.0.0.0", "10.255.255.255"), + address_pair_v4("100.64.0.0", "100.127.255.255"), + address_pair_v4("127.0.0.0", "127.255.255.255"), + address_pair_v4("169.254.0.0", "169.254.255.255"), + address_pair_v4("172.16.0.0", "172.31.255.255"), + address_pair_v4("192.0.0.0", "192.0.0.255"), + address_pair_v4("192.0.2.0", "192.0.2.255"), + address_pair_v4("192.88.99.0", "192.88.99.255"), + address_pair_v4("192.168.0.0", "192.168.255.255"), + address_pair_v4("198.18.0.0", "192.19.255.255"), + address_pair_v4("198.51.100.0", "198.51.100.255"), + address_pair_v4("203.0.113.0", "203.0.113.255"), + address_pair_v4("224.0.0.0", "255.255.255.255") + }; + + uint32_t ipv4_address = host.to_v4 ().to_ulong (); + for(const auto& it : reservedIPv4Ranges) { + if (ipv4_address >= it.first && ipv4_address <= it.second) + return true; + } + } + if(host.is_v6()) + { + static const std::vector< std::pair > reservedIPv6Ranges { + address_pair_v6("2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"), + address_pair_v6("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), + address_pair_v6("fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + }; + + boost::asio::ip::address_v6::bytes_type ipv6_address = host.to_v6 ().to_bytes (); + for(const auto& it : reservedIPv6Ranges) { + if (ipv6_address >= it.first && ipv6_address <= it.second) + return true; + } + } + return false; + } +} // net } // util } // i2p diff --git a/libi2pd/util.h b/libi2pd/util.h index cb8fd8f1..56ce1e08 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -172,6 +172,7 @@ namespace util { int GetMTU (const boost::asio::ip::address& localAddress); const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6=false); + bool IsInReservedRange(const boost::asio::ip::address& host); } } } From 18bb4a71c2fe044f9435671c424e162c8145b3c8 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 12 Oct 2020 18:27:25 +0300 Subject: [PATCH 3832/6300] fix incorrect chars in variable Signed-off-by: R4SAS --- libi2pd/Transports.cpp | 6 +++--- libi2pd/Transports.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index ca86cf42..8fe4a731 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -131,7 +131,7 @@ namespace transport Transports transports; Transports::Transports (): - m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_СheckReserved(true), m_Thread (nullptr), + m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_CheckReserved(true), m_Thread (nullptr), m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), m_SSUServer (nullptr), m_NTCP2Server (nullptr), m_DHKeysPairSupplier (5), m_X25519KeysPairSupplier (5), // 5 pre-generated keys @@ -393,7 +393,7 @@ namespace transport { // NTCP2 have priority over NTCP auto address = peer.router->GetNTCP2Address (true, !context.SupportsV6 ()); // published only - if (address && !peer.router->IsUnreachable () && (!m_СheckReserved || !i2p::util::net::IsInReservedRange(address->host))) + if (address && !peer.router->IsUnreachable () && (!m_CheckReserved || !i2p::util::net::IsInReservedRange(address->host))) { auto s = std::make_shared (*m_NTCP2Server, peer.router); @@ -419,7 +419,7 @@ namespace transport if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ())) { auto address = peer.router->GetSSUAddress (!context.SupportsV6 ()); - if (!m_СheckReserved || !i2p::util::net::IsInReservedRange(address->host)) + if (!m_CheckReserved || !i2p::util::net::IsInReservedRange(address->host)) { m_SSUServer->CreateSession (peer.router, address->host, address->port); return true; diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index 55170ee8..7108fbac 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -136,8 +136,8 @@ namespace transport void PeerTest (); - void SetCheckReserved (bool check) { m_СheckReserved = check; }; - bool IsCheckReserved () { return m_СheckReserved; }; + void SetCheckReserved (bool check) { m_CheckReserved = check; }; + bool IsCheckReserved () { return m_CheckReserved; }; private: @@ -155,7 +155,7 @@ namespace transport private: volatile bool m_IsOnline; - bool m_IsRunning, m_IsNAT, m_СheckReserved; + bool m_IsRunning, m_IsNAT, m_CheckReserved; std::thread * m_Thread; boost::asio::io_service * m_Service; boost::asio::io_service::work * m_Work; From e3464add50dd04f9adf797a31c9f7bf8326c2a4b Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 12 Oct 2020 17:15:17 -0400 Subject: [PATCH 3833/6300] don't create new tunnels if offline --- libi2pd/Tunnel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 704706ba..c91d0215 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -522,7 +522,7 @@ namespace tunnel } uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - if (ts - lastTs >= 15) // manage tunnels every 15 seconds + if (ts - lastTs >= 15 && i2p::transport::transports.IsOnline()) // manage tunnels every 15 seconds { ManageTunnels (); lastTs = ts; From 3f45a11f12a0b513ea1a8568e48a74e56feca5e9 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 13 Oct 2020 15:22:39 +0300 Subject: [PATCH 3834/6300] [SSU] handle ICMP responses Windows network stack can forward ICMP to socket and simple deleting of packet can cause socket death. Same thing can happen on other systems but without socket death. Signed-off-by: R4SAS --- libi2pd/SSU.cpp | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index f9c06f37..af76ab2b 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -242,10 +242,16 @@ namespace transport void SSUServer::Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to) { + boost::system::error_code ec; if (to.protocol () == boost::asio::ip::udp::v4()) - m_Socket.send_to (boost::asio::buffer (buf, len), to); + m_Socket.send_to (boost::asio::buffer (buf, len), to, 0, ec); else - m_SocketV6.send_to (boost::asio::buffer (buf, len), to); + m_SocketV6.send_to (boost::asio::buffer (buf, len), to, 0, ec); + + if (ec) + { + LogPrint (eLogError, "SSU: send exception: ", ec.message (), " while trying to send data to ", to.address (), ":", to.port (), " (length: ", len, ")"); + } } void SSUServer::Receive () @@ -264,7 +270,13 @@ namespace transport void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) { - if (!ecode) + if (!ecode || + ecode == boost::asio::error::connection_refused || + ecode == boost::asio::error::connection_reset || + ecode == boost::asio::error::network_unreachable || + ecode == boost::asio::error::host_unreachable) + // just try continue reading when received ICMP response otherwise socket can crash, + // but better to find out which host were sent it and mark that router as unreachable { packet->len = bytes_transferred; std::vector packets; @@ -286,7 +298,7 @@ namespace transport } else { - LogPrint (eLogError, "SSU: receive_from error: ", ec.message ()); + LogPrint (eLogError, "SSU: receive_from error: code ", ec.value(), ": ", ec.message ()); delete packet; break; } @@ -301,7 +313,7 @@ namespace transport delete packet; if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogError, "SSU: receive error: ", ecode.message ()); + LogPrint (eLogError, "SSU: receive error: code ", ecode.value(), ": ", ecode.message ()); m_Socket.close (); OpenSocket (); Receive (); @@ -311,7 +323,13 @@ namespace transport void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) { - if (!ecode) + if (!ecode || + ecode == boost::asio::error::connection_refused || + ecode == boost::asio::error::connection_reset || + ecode == boost::asio::error::network_unreachable || + ecode == boost::asio::error::host_unreachable) + // just try continue reading when received ICMP response otherwise socket can crash, + // but better to find out which host were sent it and mark that router as unreachable { packet->len = bytes_transferred; std::vector packets; @@ -333,7 +351,7 @@ namespace transport } else { - LogPrint (eLogError, "SSU: v6 receive_from error: ", ec.message ()); + LogPrint (eLogError, "SSU: v6 receive_from error: code ", ec.value(), ": ", ec.message ()); delete packet; break; } @@ -348,7 +366,7 @@ namespace transport delete packet; if (ecode != boost::asio::error::operation_aborted) { - LogPrint (eLogError, "SSU: v6 receive error: ", ecode.message ()); + LogPrint (eLogError, "SSU: v6 receive error: code ", ecode.value(), ": ", ecode.message ()); m_SocketV6.close (); OpenSocketV6 (); ReceiveV6 (); From 614921276e0f490f2cf7d94504049b0e40f18972 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 13 Oct 2020 12:33:27 +0000 Subject: [PATCH 3835/6300] [appveyor] update configuration to support cache (#1559) --- appveyor.yml | 45 ++++++++++++++++--------------- build/appveyor-msys2-upgrade.bash | 10 ------- 2 files changed, 24 insertions(+), 31 deletions(-) delete mode 100644 build/appveyor-msys2-upgrade.bash diff --git a/appveyor.yml b/appveyor.yml index 10165e5f..3cf51c2b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,46 +9,49 @@ os: Visual Studio 2015 shallow_clone: true clone_depth: 1 +# avoid building 32-bit if 64-bit failed already +matrix: + fast_finish: true + environment: + APPVEYOR_SAVE_CACHE_ON_ERROR: true MSYS2_PATH_TYPE: inherit CHERE_INVOKING: enabled_from_arguments matrix: - MSYSTEM: MINGW64 - MSYS_PACKAGES: mingw-w64-x86_64-boost mingw-w64-x86_64-miniupnpc - MSYS_BITNESS: 64 - MSYSTEM: MINGW32 - MSYS_PACKAGES: mingw-w64-i686-boost mingw-w64-i686-miniupnpc - MSYS_BITNESS: 32 + +cache: + - c:\msys64\var\cache\pacman\pkg\ install: -# install new signing keyring -- c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" -- c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" -- c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" +# install new signing keyring +- c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" +- c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" +- c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" -# disable inaccessible miror (something block sed from changing files, so rewrite them) - https://github.com/msys2/MINGW-packages/issues/7084 -- c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/mingw/x86_64/' > /etc/pacman.d/mirrorlist.mingw64" -- c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/mingw/i686/' > /etc/pacman.d/mirrorlist.mingw32" -- c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/msys/$arch/' > /etc/pacman.d/mirrorlist.msys" # remove packages which can break build - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" # update runtime - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" # Kill bash before next try - taskkill /T /F /IM bash.exe /IM gpg.exe /IM gpg-agent.exe | exit /B 0 -# rewrite mirrorlist again because pacman update can rewrite it -- c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/mingw/x86_64/' > /etc/pacman.d/mirrorlist.mingw64" -- c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/mingw/i686/' > /etc/pacman.d/mirrorlist.mingw32" -- c:\msys64\usr\bin\bash -lc "echo 'Server = https://mirror.yandex.ru/mirrors/msys2/msys/$arch/' > /etc/pacman.d/mirrorlist.msys" # update packages and install required -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu ${MSYS_PACKAGES}" +- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu $MINGW_PACKAGE_PREFIX-boost $MINGW_PACKAGE_PREFIX-miniupnpc" build_script: -- echo MSYSTEM = %MSYSTEM%, bitness = %MSYS_BITNESS% -- c:\msys64\usr\bin\bash -lc "make USE_UPNP=yes -j3" -- 7z a -tzip -mx9 -mmt i2pd-mingw-win%MSYS_BITNESS%.zip i2pd.exe +- c:\msys64\usr\bin\bash -lc "make USE_UPNP=yes DEBUG=no -j3" +# prepare archive for uploading +- set "FILELIST=i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates contrib/tunnels.d" +- echo This is development build, use it carefully! For running in portable mode, move all files from contrib directory here. > README.txt +- 7z a -tzip -mx9 -mmt i2pd-%APPVEYOR_BUILD_VERSION%-%APPVEYOR_REPO_COMMIT:~0,7%-mingw-win%MSYSTEM:~-2%.zip %FILELIST% + +after_build: +- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Sc" test: off +deploy: off + artifacts: -- path: i2pd-mingw-win*.zip +- path: i2pd-*.zip diff --git a/build/appveyor-msys2-upgrade.bash b/build/appveyor-msys2-upgrade.bash deleted file mode 100644 index 20c6af69..00000000 --- a/build/appveyor-msys2-upgrade.bash +++ /dev/null @@ -1,10 +0,0 @@ -set -e -x - -base_url='http://repo.msys2.org/msys/x86_64/' -packages="libzstd-1.4.4-2-x86_64.pkg.tar.xz pacman-5.2.1-6-x86_64.pkg.tar.xz zstd-1.4.4-2-x86_64.pkg.tar.xz" -for p in $packages -do - curl "${base_url}$p" -o "$p" -done -pacman -U --noconfirm $packages -rm -f $packages From acc5592f590248067e41cdbed383eb337f3ebe6b Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 13 Oct 2020 21:12:52 -0400 Subject: [PATCH 3836/6300] create DH keys for SSU session directly --- libi2pd/SSUSession.cpp | 12 ++++++++---- libi2pd/SSUSession.h | 1 + libi2pd/TransportSession.h | 3 +-- libi2pd/Transports.cpp | 14 +------------- libi2pd/Transports.h | 4 ---- 5 files changed, 11 insertions(+), 23 deletions(-) diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index 399b2fc7..9ce6e2e3 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -224,7 +224,11 @@ namespace transport return; } if (!m_DHKeysPair) - m_DHKeysPair = transports.GetNextDHKeysPair (); + { + auto pair = std::make_shared (); + pair->GenerateKeys (); + m_DHKeysPair = pair; + } CreateAESandMacKey (buf + headerSize); SendSessionCreated (buf + headerSize, sendRelayTag); } @@ -826,9 +830,9 @@ namespace transport { if (m_State == eSessionStateUnknown) { - // set connect timer - ScheduleConnectTimer (); - m_DHKeysPair = transports.GetNextDHKeysPair (); + ScheduleConnectTimer (); // set connect timer + m_DHKeysPair = std::make_shared (); + m_DHKeysPair->GenerateKeys (); SendSessionRequest (); } } diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h index ea820517..3aa04638 100644 --- a/libi2pd/SSUSession.h +++ b/libi2pd/SSUSession.h @@ -166,6 +166,7 @@ namespace transport bool m_IsDataReceived; std::unique_ptr m_SignedData; // we need it for SessionConfirmed only std::map > m_RelayRequests; // nonce->Charlie + std::shared_ptr m_DHKeysPair; // X - for client and Y - for server }; } } diff --git a/libi2pd/TransportSession.h b/libi2pd/TransportSession.h index a97f246f..12d4894b 100644 --- a/libi2pd/TransportSession.h +++ b/libi2pd/TransportSession.h @@ -64,7 +64,7 @@ namespace transport public: TransportSession (std::shared_ptr router, int terminationTimeout): - m_DHKeysPair (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout), + m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout), m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()) { if (router) @@ -103,7 +103,6 @@ namespace transport std::shared_ptr m_RemoteIdentity; mutable std::mutex m_RemoteIdentityMutex; - std::shared_ptr m_DHKeysPair; // X - for client and Y - for server size_t m_NumSentBytes, m_NumReceivedBytes; bool m_IsOutgoing; int m_TerminationTimeout; diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 8fe4a731..4e0aa6a5 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -134,7 +134,7 @@ namespace transport m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_CheckReserved(true), m_Thread (nullptr), m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), m_SSUServer (nullptr), m_NTCP2Server (nullptr), - m_DHKeysPairSupplier (5), m_X25519KeysPairSupplier (5), // 5 pre-generated keys + m_X25519KeysPairSupplier (5), // 5 pre-generated keys m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0), m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0), m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), @@ -165,7 +165,6 @@ namespace transport } i2p::config::GetOption("nat", m_IsNAT); - m_DHKeysPairSupplier.Start (); m_X25519KeysPairSupplier.Start (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); @@ -260,7 +259,6 @@ namespace transport m_NTCP2Server = nullptr; } - m_DHKeysPairSupplier.Stop (); m_X25519KeysPairSupplier.Stop (); m_IsRunning = false; if (m_Service) m_Service->stop (); @@ -540,16 +538,6 @@ namespace transport } } - std::shared_ptr Transports::GetNextDHKeysPair () - { - return m_DHKeysPairSupplier.Acquire (); - } - - void Transports::ReuseDHKeysPair (std::shared_ptr pair) - { - m_DHKeysPairSupplier.Return (pair); - } - std::shared_ptr Transports::GetNextX25519KeysPair () { return m_X25519KeysPairSupplier.Acquire (); diff --git a/libi2pd/Transports.h b/libi2pd/Transports.h index 7108fbac..d480840a 100644 --- a/libi2pd/Transports.h +++ b/libi2pd/Transports.h @@ -59,7 +59,6 @@ namespace transport std::condition_variable m_Acquired; std::mutex m_AcquiredMutex; }; - typedef EphemeralKeysSupplier DHKeysPairSupplier; typedef EphemeralKeysSupplier X25519KeysPairSupplier; struct Peer @@ -97,8 +96,6 @@ namespace transport void SetOnline (bool online); boost::asio::io_service& GetService () { return *m_Service; }; - std::shared_ptr GetNextDHKeysPair (); - void ReuseDHKeysPair (std::shared_ptr pair); std::shared_ptr GetNextX25519KeysPair (); void ReuseX25519KeysPair (std::shared_ptr pair); @@ -166,7 +163,6 @@ namespace transport mutable std::mutex m_PeersMutex; std::unordered_map m_Peers; - DHKeysPairSupplier m_DHKeysPairSupplier; X25519KeysPairSupplier m_X25519KeysPairSupplier; std::atomic m_TotalSentBytes, m_TotalReceivedBytes, m_TotalTransitTransmittedBytes; From 11691fb44abea05940f12b3579b8c73e2e28ca56 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 14 Oct 2020 07:20:26 +0300 Subject: [PATCH 3837/6300] create GH workflow Add workflow to build on ubuntu with make --- .github/workflows/build.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..41b71bdf --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,18 @@ +name: Build on Ubuntu with make + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-16.04 + strategy: + fail-fast: true + matrix: + with_upnp: ['yes', 'no'] + steps: + - uses: actions/checkout@v2 + - name: Building with USE_UPNP=${{ matrix.with_upnp }} flag + - name: install packages + run: apt-get install build-essential libboost-date-time-dev libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libminiupnpc-dev libssl-dev zlib1g-dev + - name: build application + run: make USE_AVX=no USE_AESNI=no USE_UPNP=yes USE_UPNP=${{ matrix.with_upnp }} -j3 From 1dbc35f13d2e120c2e15d00223f31d648de61072 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 14 Oct 2020 07:22:00 +0300 Subject: [PATCH 3838/6300] fix workflow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 41b71bdf..0aac49f4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,8 +10,8 @@ jobs: matrix: with_upnp: ['yes', 'no'] steps: - - uses: actions/checkout@v2 - name: Building with USE_UPNP=${{ matrix.with_upnp }} flag + uses: actions/checkout@v2 - name: install packages run: apt-get install build-essential libboost-date-time-dev libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libminiupnpc-dev libssl-dev zlib1g-dev - name: build application From 44d3854a1393e660f0cd6e8d740862666f18c0e4 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 14 Oct 2020 07:24:02 +0300 Subject: [PATCH 3839/6300] [workflow] use sudo when installing packages --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0aac49f4..518a148c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,6 @@ jobs: - name: Building with USE_UPNP=${{ matrix.with_upnp }} flag uses: actions/checkout@v2 - name: install packages - run: apt-get install build-essential libboost-date-time-dev libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libminiupnpc-dev libssl-dev zlib1g-dev + run: sudo apt-get install build-essential libboost-date-time-dev libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libminiupnpc-dev libssl-dev zlib1g-dev - name: build application - run: make USE_AVX=no USE_AESNI=no USE_UPNP=yes USE_UPNP=${{ matrix.with_upnp }} -j3 + run: make USE_AVX=no USE_AESNI=no USE_UPNP=${{ matrix.with_upnp }} -j3 From 36fc0daa126393f62911c99e2f037e90cdb9ffb3 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 14 Oct 2020 07:36:16 +0300 Subject: [PATCH 3840/6300] [workflow] use latest boost from PPA --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 518a148c..77704af5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,9 @@ jobs: - name: Building with USE_UPNP=${{ matrix.with_upnp }} flag uses: actions/checkout@v2 - name: install packages - run: sudo apt-get install build-essential libboost-date-time-dev libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libminiupnpc-dev libssl-dev zlib1g-dev + run: | + sudo add-apt-repository ppa:mhier/libboost-latest + sudo apt-get update + sudo apt-get install build-essential libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev - name: build application run: make USE_AVX=no USE_AESNI=no USE_UPNP=${{ matrix.with_upnp }} -j3 From 8e24d1b909a9e910023231bdbd82ee60f5d3d42b Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 14 Oct 2020 07:44:22 +0300 Subject: [PATCH 3841/6300] [workflow] change options order Apply name for job, not for step. --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 77704af5..ba6acbf8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,14 +4,14 @@ on: [push, pull_request] jobs: build: + name: Building with USE_UPNP=${{ matrix.with_upnp }} flag runs-on: ubuntu-16.04 strategy: fail-fast: true matrix: with_upnp: ['yes', 'no'] steps: - - name: Building with USE_UPNP=${{ matrix.with_upnp }} flag - uses: actions/checkout@v2 + - uses: actions/checkout@v2 - name: install packages run: | sudo add-apt-repository ppa:mhier/libboost-latest From d9d31521f9d240020846f4aff7a3938208d10541 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 14 Oct 2020 08:06:22 +0300 Subject: [PATCH 3842/6300] [workflow] add windows build --- .github/workflows/build-windows.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/build-windows.yml diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml new file mode 100644 index 00000000..9fd66bbc --- /dev/null +++ b/.github/workflows/build-windows.yml @@ -0,0 +1,29 @@ +name: Build on Windows + +on: [push, pull_request] + +defaults: + run: + shell: msys2 {0} + +jobs: + build: + name: Building for ${{ matrix.arch }} + runs-on: windows-latest + strategy: + fail-fast: true + matrix: + include: [ + { msystem: MINGW64, arch: x86_64 }, + { msystem: MINGW32, arch: i686 } + ] + steps: + - uses: actions/checkout@v2 + - name: Setup MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.msystem }} + install: base-devel mingw-w64-${{ matrix.arch }}-boost mingw-w64-${{ matrix.arch }}-miniupnpc + update: true + - name: build application + run: make USE_UPNP=yes DEBUG=no -j3 From 2648f1ba89d5032262a72ca8b2d2d8a70e441b9a Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 14 Oct 2020 08:14:33 +0300 Subject: [PATCH 3843/6300] [workflow] install required packages --- .github/workflows/build-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 9fd66bbc..9763f75b 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -23,7 +23,7 @@ jobs: uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.msystem }} - install: base-devel mingw-w64-${{ matrix.arch }}-boost mingw-w64-${{ matrix.arch }}-miniupnpc + install: base-devel mingw-w64-${{ matrix.arch }}-gcc mingw-w64-${{ matrix.arch }}-boost mingw-w64-${{ matrix.arch }}-openssl mingw-w64-${{ matrix.arch }}-miniupnpc update: true - name: build application run: make USE_UPNP=yes DEBUG=no -j3 From 050390c5c44441ea5bd3917330cf908640309f38 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 14 Oct 2020 21:37:39 +0800 Subject: [PATCH 3844/6300] qt: all new general options added from docs --- qt/i2pd_qt/TunnelPane.cpp | 28 + qt/i2pd_qt/TunnelPane.h | 28 + qt/i2pd_qt/generalsettingswidget.ui | 2716 ++++++++++++++++++--------- qt/i2pd_qt/mainwindow.cpp | 82 +- qt/i2pd_qt/mainwindow.h | 3 +- 5 files changed, 1949 insertions(+), 908 deletions(-) diff --git a/qt/i2pd_qt/TunnelPane.cpp b/qt/i2pd_qt/TunnelPane.cpp index c64b37ab..60b0b6d5 100644 --- a/qt/i2pd_qt/TunnelPane.cpp +++ b/qt/i2pd_qt/TunnelPane.cpp @@ -185,6 +185,34 @@ void TunnelPane::appendControlsForI2CPParameters(I2CPParameters& i2cpParameters, tunnelGridLayout->addLayout(horizontalLayout_2); } + { + //explicitPeers -- list of comma-separated b64 addresses of peers to use, default: unset + } + + { + //i2p.streaming.initialAckDelay -- milliseconds to wait before sending Ack. 200 by default + } + + { + //i2p.streaming.answerPings -- enable sending pongs. true by default + } + + { + //i2cp.leaseSetType -- type of LeaseSet to be sent. 1, 3 or 5. 1 by default + } + + { + //i2cp.leaseSetEncType -- comma separated encryption types to be used in LeaseSet type 3 or 5. Identity's type by default + } + + { + //i2cp.leaseSetPrivKey -- decryption key for encrypted LeaseSet in base64. PSK or private DH + } + + { + //i2cp.leaseSetAuthType -- authentication type for encrypted LeaseSet. 0 - no authentication(default), 1 - DH, 2 - PSK + } + retranslateI2CPParameters(); } diff --git a/qt/i2pd_qt/TunnelPane.h b/qt/i2pd_qt/TunnelPane.h index 71331b4e..6a13b7a0 100644 --- a/qt/i2pd_qt/TunnelPane.h +++ b/qt/i2pd_qt/TunnelPane.h @@ -133,6 +133,34 @@ private: inbound_quantityLabel->setText(QApplication::translate("tunForm", "Number of inbound tunnels:", 0));; outbound_quantityLabel->setText(QApplication::translate("tunForm", "Number of outbound tunnels:", 0));; crypto_tagsToSendLabel->setText(QApplication::translate("tunForm", "Number of ElGamal/AES tags to send:", 0));; + + { + //explicitPeers -- list of comma-separated b64 addresses of peers to use, default: unset + } + + { + //i2p.streaming.initialAckDelay -- milliseconds to wait before sending Ack. 200 by default + } + + { + //i2p.streaming.answerPings -- enable sending pongs. true by default + } + + { + //i2cp.leaseSetType -- type of LeaseSet to be sent. 1, 3 or 5. 1 by default + } + + { + //i2cp.leaseSetEncType -- comma separated encryption types to be used in LeaseSet type 3 or 5. Identity's type by default + } + + { + //i2cp.leaseSetPrivKey -- decryption key for encrypted LeaseSet in base64. PSK or private DH + } + + { + //i2cp.leaseSetAuthType -- authentication type for encrypted LeaseSet. 0 - no authentication(default), 1 - DH, 2 - PSK + } } }; diff --git a/qt/i2pd_qt/generalsettingswidget.ui b/qt/i2pd_qt/generalsettingswidget.ui index 9e59132f..e64f9e35 100644 --- a/qt/i2pd_qt/generalsettingswidget.ui +++ b/qt/i2pd_qt/generalsettingswidget.ui @@ -7,7 +7,7 @@ 0 0 679 - 3033 + 4152 @@ -25,14 +25,38 @@ 0 0 679 - 3052 + 4151 - + - QLayout::SetMinAndMaxSize + QLayout::SetDefaultConstraint - + + + + + 0 + 60 + + + + + 16777215 + 60 + + + + + 13 + + + + General options + + + + @@ -100,14 +124,8 @@ - - - - - 0 - 0 - - + + 0 @@ -121,44 +139,26 @@ - Tunnels configuration file: + Data folder (for storage of i2pd data — RI, keys, peer profiles, …): - + 0 - 18 + 20 661 31 - + QLayout::SetMaximumSize - + - - - - 0 - 0 - - - - - 0 - 27 - - - - - 16777215 - 27 - - + Browse… @@ -168,7 +168,7 @@ - + @@ -236,183 +236,86 @@ - - + + + + + 0 + 0 + + 0 - 98 + 51 16777215 - 98 + 51 - SAM interface + Tunnels configuration file: - + 0 - 20 - 97 - 22 - - - - Enabled - - - - - - 0 - 40 + 18 661 31 - + + + QLayout::SetMaximumSize + - - - IP address to listen on: - - + - - - - - - Qt::Horizontal + + + + 0 + 0 + - + - 40 - 20 + 0 + 27 - - - - - - - - 0 - 70 - 661 - 31 - - - - - - - Port to listen on: - - - - - - 80 - 16777215 + 16777215 + 27 + + Browse… + - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 60 - - - - - 16777215 - 60 - - - - - 13 - - - - Windows-specific options - - - - - - - - 0 - 44 - - - - - 16777215 - 44 - - - - Cryptography - - - - - 0 - 20 - 661 - 22 - - - - Use ElGamal precomputed tables - - - - - + 0 - 107 + 112 16777215 - 107 + 112 @@ -432,7 +335,7 @@ - QLayout::SetMinimumSize + QLayout::SetMinAndMaxSize @@ -539,11 +442,715 @@ + + + + Write full CLF-formatted date and time to log + + + - + + + + + 0 + 0 + + + + + 0 + 390 + + + + + 16777215 + 390 + + + + Router options + + + + + 0 + 20 + 661 + 368 + + + + + + + Enable communication through ipv4 + + + + + + + Enable communication through ipv6 + + + + + + + Router will not accept transit tunnels at startup + + + + + + + Router will be floodfill + + + + + + + Enable SSU transport protocol (use UDP) + + + + + + + Assume we are behind NAT + + + + + + + + + Bandwidth limit (integer or a letter): + + + + + + + + + + KBps + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Family (name of a family router belongs to): + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QLayout::SetMaximumSize + + + + + NetID (network ID router belongs to. The main I2P ID is 2): + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Network interface to bind to for IPv4: + + + + + + + + + + + + + + Network interface to bind to for IPv6: + + + + + + + + + + + + + + Max % of bandwidth limit for transit. 0-100: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + 230 + + + + + 16777215 + 230 + + + + HTTP webconsole + + + + + 0 + 20 + 97 + 22 + + + + Enabled + + + + + + 0 + 40 + 661 + 31 + + + + + + + IP address to listen on: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 70 + 661 + 31 + + + + + + + Port to listen on: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 100 + 321 + 22 + + + + Enable basic HTTP auth + + + + + + 60 + 120 + 601 + 31 + + + + + + + Username: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 60 + 150 + 601 + 31 + + + + + + + Password: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 1 + 182 + 661 + 21 + + + + Enable strict host checking on web UI + + + + + + -1 + 200 + 661 + 27 + + + + + + + Expected hostname for web UI: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 78 + + + + + 16777215 + 78 + + + + Addressbook settings + + + + + 0 + 20 + 661 + 31 + + + + + + + Addressbook default subscription URL for initial setup: + + + + + + + + + + + + 0 + 50 + 661 + 31 + + + + + + + Addressbook subscriptions URLs, separated by comma: + + + + + + + + + + + + + + + 0 + 190 + + + + + 16777215 + 190 + + + + Reseeding + + + + + 0 + 20 + 661 + 22 + + + + Request SU3 signature verification + + + + + + 0 + 40 + 661 + 31 + + + + + + + SU3 file to reseed from: + + + + + + + + + + Browse… + + + + + + + + + 0 + 100 + 661 + 31 + + + + + + + Reseed URLs, separated by comma: + + + + + + + + + + + + 0 + 70 + 661 + 31 + + + + + + + Path to local .zip file to reseed from: + + + + + + + + + + Browse... + + + + + + + + + 0 + 130 + 661 + 31 + + + + + + + Minimum number of known routers before requesting reseed: + + + + + + + + + + Qt::Horizontal + + + + 50 + 20 + + + + + + + + + + 0 + 160 + 661 + 31 + + + + + + + URL for https/socks reseed proxy: + + + + + + + + + + + @@ -610,116 +1217,7 @@ - - - - - 0 - 98 - - - - - 16777215 - 98 - - - - I2CP interface - - - - - 0 - 20 - 97 - 22 - - - - Enabled - - - - - - 0 - 40 - 661 - 31 - - - - - - - IP address to listen on: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 70 - 661 - 31 - - - - - - - Port to listen on: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - + @@ -828,215 +1326,111 @@ - - + + 0 - 60 + 170 16777215 - 60 - - - - - 13 - - - - General options - - - - - - - - 0 - 0 - - - - - 0 - 98 - - - - - 16777215 - 98 + 170 - Router external address (for incoming connections) + Trust options - - Qt::AlignJustify|Qt::AlignTop - - + 0 20 661 - 81 + 21 - - - QLayout::SetMinAndMaxSize - - - - - QLayout::SetMinAndMaxSize - - - - - Host: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - QLayout::SetMinAndMaxSize - - - - - Port (leave 0 to auto-assign): - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - + + Enable explicit trust options + - - - - - - - 0 - 78 - - - - - 16777215 - 78 - - - - Addressbook settings - - + + + + 390 + 40 + 271 + 23 + + + + 0 - 20 - 661 - 31 + 40 + 391 + 42 - - - - - Addressbook default subscription URL for initial setup: - - - - - - - + + Make direct I2P connections only to +routers in specified Family: + - + 0 - 50 + 82 661 - 31 + 42 - - - - - Addressbook subscriptions URLs, separated by comma: - - - - - - - + + Make direct I2P connections only to routers specified here. +Comma separated list of base64 identities: + + + + + + 0 + 124 + 661 + 23 + + + + + + + 0 + 147 + 661 + 21 + + + + Should we hide our router from other routers? + - + 0 - 280 + 400 16777215 - 280 + 400 @@ -1371,78 +1765,225 @@ - - - - - - - 0 - 60 - - - - - 16777215 - 60 - - - - - 13 - - - - Various options - - - - - - - - 0 - 51 - - - - - 16777215 - 51 - - - - Data folder (for storage of i2pd data — RI, keys, peer profiles, …): - - + 0 - 20 + 280 661 - 31 + 23 - - - QLayout::SetMaximumSize - + + Enable address helper (jump) + + + + + + 0 + 300 + 661 + 95 + + + - + + + + + HTTP proxy upstream out proxy URL (like http://false.i2p): + + + + + + + - - - Browse… - - + + + + + Type of LeaseSet to be sent. 1, 3 or 5: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Comma-separated encryption types to be used in LeaseSet type 3 or 5: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - + + + + + 0 + 121 + + + + + 16777215 + 121 + + + + SAM interface + + + + + 0 + 20 + 97 + 22 + + + + Enabled + + + + + + 0 + 40 + 661 + 31 + + + + + + + IP address to listen on: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 70 + 661 + 31 + + + + + + + Port to listen on: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 100 + 621 + 23 + + + + Single thread for all SAM sessions + + + + + + 0 @@ -1452,127 +1993,88 @@ 0 - 215 + 98 16777215 - 215 + 98 - Router options + Router external address (for incoming connections) - + + Qt::AlignJustify|Qt::AlignTop + + 0 20 661 - 188 + 81 - + + + QLayout::SetMinAndMaxSize + - - - Enable communication through ipv6 - - - - - - - Router will not accept transit tunnels at startup - - - - - - - Router will be floodfill - - - - - - - - - Bandwidth limit (integer or a letter): - - - - - - - - - - KBps - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Family (name of a family router belongs to): - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - + - QLayout::SetMaximumSize + QLayout::SetMinAndMaxSize - + - NetID (network ID router belongs to. The main I2P ID is 2): + Host: - + - + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QLayout::SetMinAndMaxSize + + + + + Port (leave 0 to auto-assign): + + + + + + + + 80 + 16777215 + + + + + + Qt::Horizontal @@ -1590,8 +2092,8 @@ - - + + 0 @@ -1714,226 +2216,251 @@ - - + + + + + 0 + 0 + + 0 - 98 + 128 16777215 - 98 + 128 - Reseeding + Nettime options - + 0 20 - 661 - 22 + 671 + 101 - - Request SU3 signature verification - - - - - - 0 - 40 - 661 - 31 - - - + + + QLayout::SetMinAndMaxSize + - + - SU3 file to reseed from: + Enable NTP sync - + + + + + Comma-separated list of NTP servers: + + + + + + + - - - Browse… - - - - - - - - - 0 - 70 - 661 - 31 - - - - - - - Reseed URLs, separated by comma: - - - - - + + + + + NTP time sync interval in hours: + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - + + + + + 0 + 0 + + 0 - 170 + 215 16777215 - 170 + 215 - Trust options + NTCP2 options - + - 0 - 20 - 661 - 21 + -1 + 19 + 671 + 191 - - Enable explicit trust options - - - - - - 390 - 40 - 271 - 23 - - - - - - - 0 - 40 - 391 - 42 - - - - Make direct I2P connections only to -routers in specified Family: - - - - - - 0 - 82 - 661 - 42 - - - - Make direct I2P connections only to routers specified here. -Comma separated list of base64 identities: - - - - - - 0 - 124 - 661 - 23 - - - - - - - 0 - 147 - 661 - 21 - - - - Should we hide our router from other routers? - + + + QLayout::SetMinAndMaxSize + + + + + Enable NTCP2 + + + + + + + Enable incoming NTCP2 connections + + + + + + + + + Port to listen for incoming NTCP2 connections: + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + External IPv6 address for incoming connections: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Specify proxy server for NTCP2: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Proxy server should be http://address:port or socks://address:port + + + + - - - - - 0 - 60 - - - - - 16777215 - 60 - - - - - 13 - - - - Ports - - - - - - - - 0 - 22 - - - - - 16777215 - 22 - - - - Insomnia (prevent system from sleeping) - - - - + @@ -2135,24 +2662,56 @@ Comma separated list of base64 identities: - - + + 0 - 179 + 44 16777215 - 179 + 44 - HTTP webconsole + Cryptography - + + + + 0 + 20 + 661 + 22 + + + + Use ElGamal precomputed tables + + + + + + + + + 0 + 121 + + + + + 16777215 + 121 + + + + I2CP interface + + 0 @@ -2165,7 +2724,7 @@ Comma separated list of base64 identities: Enabled - + 0 @@ -2174,19 +2733,19 @@ Comma separated list of base64 identities: 31 - + - + IP address to listen on: - + - + Qt::Horizontal @@ -2200,7 +2759,7 @@ Comma separated list of base64 identities: - + 0 @@ -2209,16 +2768,16 @@ Comma separated list of base64 identities: 31 - + - + Port to listen on: - + 80 @@ -2228,7 +2787,7 @@ Comma separated list of base64 identities: - + Qt::Horizontal @@ -2242,103 +2801,57 @@ Comma separated list of base64 identities: - + 0 100 - 321 - 22 + 651 + 23 - Enable basic HTTP auth + Single thread for all I2CP sessions - - - - 60 - 120 - 601 - 31 - - - - - - - Username: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 60 - 150 - 601 - 31 - - - - - - - Password: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - + + 0 - 335 + 60 16777215 - 335 + 60 + + + + + 13 + + + + Ports + + + + + + + + 0 + 405 + + + + + 16777215 + 405 @@ -2750,6 +3263,479 @@ Comma separated list of base64 identities: + + + + -1 + 340 + 661 + 62 + + + + + + + + + Type of LeaseSet to be sent. 1, 3 or 5: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Comma-separated encryption types to be used in LeaseSet type 3 or 5: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 128 + + + + + 16777215 + 128 + + + + Websocket Server + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + -1 + 19 + 681 + 101 + + + + + QLayout::SetMinAndMaxSize + + + + + Enable websocket server + + + + + + + + + Address to bind websocket server on: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Port to bind websocket server on: + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + 60 + + + + + 16777215 + 60 + + + + + 13 + + + + Various options + + + + + + + + 0 + 0 + + + + + 0 + 160 + + + + + 16777215 + 160 + + + + Exploratory Tunnels + + + + + -1 + 19 + 671 + 131 + + + + + QLayout::SetMinAndMaxSize + + + + + + + Exploratory inbound tunnels length: + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Exploratory inbound tunnels quantity: + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Exploratory outbound tunnels length: + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Exploratory outbound tunnels quantity: + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 56 + + + + + 16777215 + 56 + + + + Persist profiles + + + + + 0 + 20 + 681 + 31 + + + + + QLayout::SetMinAndMaxSize + + + + + Enable peer profile persisting to disk + + + + + + + + + + + + 0 + 60 + + + + + 16777215 + 60 + + + + + 13 + + + + Windows-specific options + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + + 16777215 + 22 + + + + Insomnia (prevent system from sleeping) + diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 14a57f8a..d4d34109 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -81,10 +81,10 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren onResize(); ui->stackedWidget->setCurrentIndex(0); - ui->settingsScrollArea->resize(uiSettings->settingsContentsGridLayout->sizeHint().width()+10,380); + ui->settingsScrollArea->resize(uiSettings->settingsContentsQVBoxLayout->sizeHint().width()+10,380); //QScrollBar* const barSett = ui->settingsScrollArea->verticalScrollBar(); int w = 683; - int h = 3060; + int h = 4000; ui->settingsContents->setFixedSize(w, h); ui->settingsContents->setGeometry(QRect(0,0,w,h)); @@ -172,24 +172,24 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren logFileNameOption=initFileChooser( OPTION("","logfile",[]{return "";}), uiSettings->logFileLineEdit, uiSettings->logFileBrowsePushButton); initLogLevelCombobox(OPTION("","loglevel",[]{return "";}), uiSettings->logLevelComboBox); - //TODO add option "logclftime" "Write full CLF-formatted date and time to log (default: write only time)" + initCheckBox( OPTION("","logclftime",[]{return "false";}), uiSettings->logclftimeCheckBox);//"Write full CLF-formatted date and time to log (default: write only time)" initFolderChooser( OPTION("","datadir",[]{return "";}), uiSettings->dataFolderLineEdit, uiSettings->dataFolderBrowsePushButton); initIPAddressBox( OPTION("","host",[]{return "";}), uiSettings->routerExternalHostLineEdit, tr("Router external address -> Host")); initTCPPortBox( OPTION("","port",[]{return "";}), uiSettings->routerExternalPortLineEdit, tr("Router external address -> Port")); daemonOption=initNonGUIOption( OPTION("","daemon",[]{return "";})); serviceOption=initNonGUIOption( OPTION("","service",[]{return "";})); - //TODO add option "ifname4" Network interface to bind to for IPv4 - //TODO add option "ifname6" Network interface to bind to for IPv6 - //TODO add option "nat" If true, assume we are behind NAT. true by default - //TODO add option "ipv4" Enable communication through IPv4. true by default + initStringBox( OPTION("","ifname4",[]{return "";}), uiSettings->ifname4LineEdit);//Network interface to bind to for IPv4 + initStringBox( OPTION("","ifname6",[]{return "";}), uiSettings->ifname6LineEdit);//Network interface to bind to for IPv6 + initCheckBox( OPTION("","nat",[]{return "true";}), uiSettings->natCheckBox);//If true, assume we are behind NAT. true by default + initCheckBox( OPTION("","ipv4",[]{return "true";}), uiSettings->ipv4CheckBox);//Enable communication through IPv4. true by default initCheckBox( OPTION("","ipv6",[]{return "false";}), uiSettings->ipv6CheckBox); initCheckBox( OPTION("","notransit",[]{return "false";}), uiSettings->notransitCheckBox); initCheckBox( OPTION("","floodfill",[]{return "false";}), uiSettings->floodfillCheckBox); initStringBox( OPTION("","bandwidth",[]{return "";}), uiSettings->bandwidthLineEdit); - //TODO add option "share" Max % of bandwidth limit for transit. 0-100. 100 by default + initIntegerBox( OPTION("","share",[]{return "100";}), uiSettings->shareLineEdit, tr("Share"));//Max % of bandwidth limit for transit. 0-100. 100 by default initStringBox( OPTION("","family",[]{return "";}), uiSettings->familyLineEdit); initIntegerBox( OPTION("","netid",[]{return "2";}), uiSettings->netIdLineEdit, tr("NetID")); - //TODO add option "ssu" Enable SSU transport protocol (use UDP). true by default + initCheckBox( OPTION("","ssu",[]{return "true";}), uiSettings->ssuCheckBox);//Enable SSU transport protocol (use UDP). true by default #ifdef Q_OS_WIN initNonGUIOption( OPTION("","svcctl",[]{return "";})); @@ -205,22 +205,22 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initCheckBox( OPTION("http","auth",[]{return "";}), uiSettings->webconsoleBasicAuthCheckBox); initStringBox( OPTION("http","user",[]{return "i2pd";}), uiSettings->webconsoleUserNameLineEditBasicAuth); initStringBox( OPTION("http","pass",[]{return "";}), uiSettings->webconsolePasswordLineEditBasicAuth); - //TODO add option "http.strictheaders Enable strict host checking on WebUI. true by default - //TODO add option "http.hostname Expected hostname for WebUI (default: localhost) + initCheckBox( OPTION("http","strictheaders",[]{return "true";}), uiSettings->httpStrictHeadersCheckBox);//TODO add option Enable strict host checking on WebUI. true by default + initStringBox( OPTION("http","hostname",[]{return "localhost";}), uiSettings->httpHostnameLineEdit);//TODO add option Expected hostname for WebUI (default: localhost) initCheckBox( OPTION("httpproxy","enabled",[]{return "";}), uiSettings->httpProxyEnabledCheckBox); initIPAddressBox( OPTION("httpproxy","address",[]{return "";}), uiSettings->httpProxyAddressLineEdit, tr("HTTP proxy -> IP address")); initTCPPortBox( OPTION("httpproxy","port",[]{return "4444";}), uiSettings->httpProxyPortLineEdit, tr("HTTP proxy -> Port")); - //TODO add option "httpproxy.addresshelper Enable address helper (jump). true by default + initCheckBox( OPTION("httpproxy","addresshelper",[]{return "true";}), uiSettings->httpProxyAddressHelperCheckBox);//TODO add option Enable address helper (jump). true by default initFileChooser( OPTION("httpproxy","keys",[]{return "";}), uiSettings->httpProxyKeyFileLineEdit, uiSettings->httpProxyKeyFilePushButton); initSignatureTypeCombobox(OPTION("httpproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_httpPorxySignatureType); initStringBox( OPTION("httpproxy","inbound.length",[]{return "3";}), uiSettings->httpProxyInboundTunnelsLenLineEdit); initStringBox( OPTION("httpproxy","inbound.quantity",[]{return "5";}), uiSettings->httpProxyInboundTunnQuantityLineEdit); initStringBox( OPTION("httpproxy","outbound.length",[]{return "3";}), uiSettings->httpProxyOutBoundTunnLenLineEdit); initStringBox( OPTION("httpproxy","outbound.quantity",[]{return "5";}), uiSettings->httpProxyOutboundTunnQuantityLineEdit); - //TODO add option "httpproxy.outproxy HTTP proxy upstream out proxy url (like http://false.i2p) - //TODO add option "httpproxy.i2cp.leaseSetType Type of LeaseSet to be sent. 1, 3 or 5. 1 by default - //TODO add option "httpproxy.i2cp.leaseSetEncType Comma separated encryption types to be used in LeaseSet type 3 or 5 + initStringBox( OPTION("httpproxy","outproxy",[]{return "";}), uiSettings->httpProxyOutproxyLineEdit);//TODO add option HTTP proxy upstream out proxy url (like http://false.i2p) + initStringBox( OPTION("httpproxy","i2cp.leaseSetType",[]{return "1";}), uiSettings->httpProxyI2cpLeaseSetTypeLineEdit);//TODO add option Type of LeaseSet to be sent. 1, 3 or 5. 1 by default + initStringBox( OPTION("httpproxy","i2cp.leaseSetEncType",[]{return "";}), uiSettings->httpProxyI2cpLeaseSetEncTypeLineEdit);//TODO add option Comma separated encryption types to be used in LeaseSet type 3 or 5 initCheckBox( OPTION("socksproxy","enabled",[]{return "";}), uiSettings->socksProxyEnabledCheckBox); initIPAddressBox( OPTION("socksproxy","address",[]{return "";}), uiSettings->socksProxyAddressLineEdit, tr("Socks proxy -> IP address")); @@ -231,15 +231,15 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initStringBox( OPTION("socksproxy","inbound.quantity",[]{return "";}), uiSettings->socksProxyInboundTunnQuantityLineEdit); initStringBox( OPTION("socksproxy","outbound.length",[]{return "";}), uiSettings->socksProxyOutBoundTunnLenLineEdit); initStringBox( OPTION("socksproxy","outbound.quantity",[]{return "";}), uiSettings->socksProxyOutboundTunnQuantityLineEdit); - initIPAddressBox( OPTION("socksproxy","outproxy",[]{return "";}), uiSettings->outproxyAddressLineEdit, tr("Socks proxy -> Outproxy address")); - initTCPPortBox( OPTION("socksproxy","outproxyport",[]{return "";}), uiSettings->outproxyPortLineEdit, tr("Socks proxy -> Outproxy port")); - //TODO add option "socksproxy.i2cp.leaseSetType Type of LeaseSet to be sent. 1, 3 or 5. 1 by default - //TODO add option "socksproxy.i2cp.leaseSetEncType Comma separated encryption types to be used in LeaseSet type 3 or 5 + initIPAddressBox( OPTION("socksproxy","outproxy",[]{return "";}), uiSettings->outproxyAddressLineEdit, tr("Socks proxy -> Outproxy address")); + initTCPPortBox( OPTION("socksproxy","outproxyport",[]{return "";}), uiSettings->outproxyPortLineEdit, tr("Socks proxy -> Outproxy port")); + initStringBox( OPTION("socksproxy","i2cp.leaseSetType",[]{return "1";}), uiSettings->socksProxyI2cpLeaseSetTypeLineEdit);//TODO add option Type of LeaseSet to be sent. 1, 3 or 5. 1 by default + initStringBox( OPTION("socksproxy","i2cp.leaseSetEncType",[]{return "";}), uiSettings->socksProxyI2cpLeaseSetEncTypeLineEdit);//TODO add option Comma separated encryption types to be used in LeaseSet type 3 or 5 initCheckBox( OPTION("sam","enabled",[]{return "false";}), uiSettings->samEnabledCheckBox); initIPAddressBox( OPTION("sam","address",[]{return "";}), uiSettings->samAddressLineEdit, tr("SAM -> IP address")); initTCPPortBox( OPTION("sam","port",[]{return "7656";}), uiSettings->samPortLineEdit, tr("SAM -> Port")); - //TODO add option "sam.singlethread If false every SAM session runs in own thread. true by default + initCheckBox( OPTION("sam","singlethread",[]{return "true";}), uiSettings->samSingleThreadCheckBox);//If false every SAM session runs in own thread. true by default initCheckBox( OPTION("bob","enabled",[]{return "false";}), uiSettings->bobEnabledCheckBox); initIPAddressBox( OPTION("bob","address",[]{return "";}), uiSettings->bobAddressLineEdit, tr("BOB -> IP address")); @@ -248,7 +248,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initCheckBox( OPTION("i2cp","enabled",[]{return "false";}), uiSettings->i2cpEnabledCheckBox); initIPAddressBox( OPTION("i2cp","address",[]{return "";}), uiSettings->i2cpAddressLineEdit, tr("I2CP -> IP address")); initTCPPortBox( OPTION("i2cp","port",[]{return "7654";}), uiSettings->i2cpPortLineEdit, tr("I2CP -> Port")); - //TODO add option "i2cp.singlethread If false every I2CP session runs in own thread. true by default + //initCheckBox( OPTION("i2cp","singlethread",[]{return "true";}), uiSettings->i2cpSingleThreadCheckBox);//If false every I2CP session runs in own thread. true by default initCheckBox( OPTION("i2pcontrol","enabled",[]{return "false";}), uiSettings->i2pControlEnabledCheckBox); initIPAddressBox( OPTION("i2pcontrol","address",[]{return "";}), uiSettings->i2pControlAddressLineEdit, tr("I2PControl -> IP address")); @@ -265,9 +265,9 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initCheckBox( OPTION("reseed","verify",[]{return "";}), uiSettings->reseedVerifyCheckBox); initFileChooser( OPTION("reseed","file",[]{return "";}), uiSettings->reseedFileLineEdit, uiSettings->reseedFileBrowsePushButton); initStringBox( OPTION("reseed","urls",[]{return "";}), uiSettings->reseedURLsLineEdit); - //TODO add option "reseed.zipfile Path to local .zip file to reseed from - //TODO add option "reseed.threshold Minimum number of known routers before requesting reseed. 25 by default - //TODO add option "reseed.proxy Url for https/socks reseed proxy + initFileChooser( OPTION("reseed","zipfile",[]{return "";}), uiSettings->reseedZipFileLineEdit, uiSettings->reseedZipFileBrowsePushButton); //Path to local .zip file to reseed from + initUInt16Box( OPTION("reseed","threshold",[]{return "25";}), uiSettings->reseedThresholdNumberLineEdit, tr("reseedThreshold")); //Minimum number of known routers before requesting reseed. 25 by default + initStringBox( OPTION("reseed","proxy",[]{return "";}), uiSettings->reseedProxyLineEdit);//URL for https/socks reseed proxy initStringBox( OPTION("addressbook","defaulturl",[]{return "";}), uiSettings->addressbookDefaultURLLineEdit); initStringBox( OPTION("addressbook","subscriptions",[]{return "";}), uiSettings->addressbookSubscriptionsURLslineEdit); @@ -275,34 +275,32 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initUInt16Box( OPTION("limits","transittunnels",[]{return "2500";}), uiSettings->maxNumOfTransitTunnelsLineEdit, tr("maxNumberOfTransitTunnels")); initUInt16Box( OPTION("limits","openfiles",[]{return "0";}), uiSettings->maxNumOfOpenFilesLineEdit, tr("maxNumberOfOpenFiles")); initUInt32Box( OPTION("limits","coresize",[]{return "0";}), uiSettings->coreFileMaxSizeNumberLineEdit, tr("coreFileMaxSize")); - //TODO add option "limits.ntcpsoft Threshold to start probabalistic backoff with ntcp sessions (0 - use system limit) - //TODO add option "limits.ntcphard Maximum number of ntcp sessions (0 - use system limit) initCheckBox( OPTION("trust","enabled",[]{return "false";}), uiSettings->checkBoxTrustEnable); initStringBox( OPTION("trust","family",[]{return "";}), uiSettings->lineEditTrustFamily); initStringBox( OPTION("trust","routers",[]{return "";}), uiSettings->lineEditTrustRouters); initCheckBox( OPTION("trust","hidden",[]{return "false";}), uiSettings->checkBoxTrustHidden); - //TODO add option "websockets.enabled Enable websocket server. Disabled by default - //TODO add option "websockets.address Address to bind websocket server on. 127.0.0.1 by default - //TODO add option "websockets.port Port to bind websocket server on. 7666 by default + initCheckBox( OPTION("websockets","enabled",[]{return "false";}), uiSettings->checkBoxWebsocketsEnable); //Enable websocket server. Disabled by default + initIPAddressBox( OPTION("websockets","address",[]{return "127.0.0.1";}), uiSettings->websocketsAddressLineEdit, tr("Websockets -> IP address")); //Address to bind websocket server on. 127.0.0.1 by default + initTCPPortBox( OPTION("websockets","port",[]{return "7666";}), uiSettings->websocketsPortLineEdit, tr("Websockets -> Port")); //Port to bind websocket server on. 7666 by default - //TODO add option "exploratory.inbound.length Exploratory inbound tunnels length. 2 by default - //TODO add option "exploratory.inbound.quantity Exploratory inbound tunnels quantity. 3 by default - //TODO add option "exploratory.outbound.length Exploratory outbound tunnels length. 2 by default - //TODO add option "exploratory.outbound.quantity Exploratory outbound tunnels length. 3 by default + initIntegerBox( OPTION("exploratory","inbound.length",[]{return "2";}), uiSettings->exploratoryInboundTunnelsLengthNumberLineEdit, tr("exploratoryInboundTunnelsLength"));//Exploratory inbound tunnels length. 2 by default + initIntegerBox( OPTION("exploratory","inbound.quantity",[]{return "3";}), uiSettings->exploratoryInboundTunnelsQuantityNumberLineEdit, tr("exploratoryInboundTunnelsQuantity"));//Exploratory inbound tunnels quantity. 3 by default + initIntegerBox( OPTION("exploratory","outbound.length",[]{return "2";}), uiSettings->exploratoryOutboundTunnelsLengthNumberLineEdit, tr("exploratoryOutboundTunnelsLength"));//Exploratory outbound tunnels length. 2 by default + initIntegerBox( OPTION("exploratory","outbound.quantity",[]{return "3";}), uiSettings->exploratoryOutboundTunnelsQuantityNumberLineEdit, tr("exploratoryOutboundTunnelsQuantity"));//Exploratory outbound tunnels length. 3 by default - //TODO add option "ntcp2.enabled Enable NTCP2. Enabled by default - //TODO add option "ntcp2.published Enable incoming NTCP2 connections. Disabled by default - //TODO add option "ntcp2.port Port to listen for incoming NTCP2 connections (default: auto) - //TODO add option "ntcp2.addressv6 External IPv6 for incoming connections - //TODO add option "ntcp2.proxy Specify proxy server for NTCP2. Should be http://address:port or socks://address:port + initCheckBox( OPTION("ntcp2","enabled",[]{return "true";}), uiSettings->checkBoxNtcp2Enable); //Enable NTCP2. Enabled by default + initCheckBox( OPTION("ntcp2","published",[]{return "false";}), uiSettings->checkBoxNtcp2Published); //Enable incoming NTCP2 connections. Disabled by default + initTCPPortBox( OPTION("ntcp2","port",[]{return "0";}), uiSettings->ntcp2PortLineEdit, tr("NTCP2 -> Port")); //Port to listen for incoming NTCP2 connections (default: auto) + initIPAddressBox( OPTION("ntcp2","addressv6",[]{return "::";}), uiSettings->ntcp2AddressV6LineEdit, tr("NTCP2 -> IPv6 address")); //External IPv6 for incoming connections + initStringBox( OPTION("ntcp2","proxy",[]{return "";}), uiSettings->lineEditNtcp2Proxy); //Specify proxy server for NTCP2. Should be http://address:port or socks://address:port - //TODO add option "nettime.enabled Enable NTP sync. Disabled by default - //TODO add option "nettime.ntpservers Comma-separated list of NTP server. pool.ntp.org by default - //TODO add option "nettime.ntpsyncinterval NTP time sync interval in hours. 72 by default + initCheckBox( OPTION("nettime","enabled",[]{return "false";}), uiSettings->checkBoxNettimeEnable); //Enable NTP sync. Disabled by default + initStringBox( OPTION("nettime","ntpservers",[]{return "pool.ntp.org";}), uiSettings->lineEditNetTimeNtpServers); //Comma-separated list of NTP servers. pool.ntp.org by default + initIntegerBox( OPTION("nettime","ntpsyncinterval",[]{return "72";}), uiSettings->nettimeNtpSyncIntervalNumberLineEdit, tr("nettimeNtpSyncInterval")); //NTP time sync interval in hours. 72 by default - //TODO add option "persist.profiles Enable peer profile persisting to disk. Enabled by default + initCheckBox( OPTION("persist","profiles",[]{return "true";}), uiSettings->checkBoxPersistProfiles);//Enable peer profile persisting to disk. Enabled by default # undef OPTION //widgetlocks.add(new widgetlock(widget,lockbtn)); diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 6e187b5b..b4f57f8f 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -116,7 +116,7 @@ public: std::string optName=""; if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); optName+=option.option.toStdString(); - //qDebug() << "loadFromConfigOption[" << optName.c_str() << "]"; + qDebug() << "loadFromConfigOption[" << optName.c_str() << "]"; boost::any programOption; i2p::config::GetOptionAsAny(optName, programOption); optionValue=programOption.empty()?boost::any(std::string("")) @@ -281,6 +281,7 @@ public: virtual void installListeners(MainWindow *mainWindow); virtual void loadFromConfigOption(){ MainWindowItem::loadFromConfigOption(); + qDebug() << "setting value for checkbox " << checkBox->text(); checkBox->setChecked(boost::any_cast(optionValue)); } virtual void saveToStringStream(std::stringstream& out){ From 417b5ed6cc15379f150bfec3f5fd5c36f9c65bfb Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 14 Oct 2020 21:06:51 -0400 Subject: [PATCH 3845/6300] handle SSU v4 and v6 messages in one thread --- libi2pd/SSU.cpp | 46 ++++++++++-------------------------------- libi2pd/SSU.h | 8 +++----- libi2pd/SSUSession.cpp | 2 +- libi2pd/Transports.cpp | 2 +- 4 files changed, 16 insertions(+), 42 deletions(-) diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index af76ab2b..07c95a7a 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -19,27 +19,25 @@ namespace transport { SSUServer::SSUServer (const boost::asio::ip::address & addr, int port): - m_OnlyV6(true), m_IsRunning(false), - m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr), - m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_WorkV6 (m_ServiceV6), + m_OnlyV6(true), m_IsRunning(false), m_Thread (nullptr), + m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), m_EndpointV6 (addr, port), m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversServiceV6), m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), - m_TerminationTimerV6 (m_ServiceV6) + m_TerminationTimerV6 (m_Service) { OpenSocketV6 (); } SSUServer::SSUServer (int port): - m_OnlyV6(false), m_IsRunning(false), - m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr), - m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_WorkV6 (m_ServiceV6), + m_OnlyV6(false), m_IsRunning(false), m_Thread (nullptr), + m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6), m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service), - m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_ServiceV6) + m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_Service) { OpenSocket (); if (context.SupportsV6 ()) @@ -98,7 +96,8 @@ namespace transport if (context.SupportsV6 ()) { m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); - m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this)); + if (!m_Thread) + m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); ScheduleTerminationV6 (); } @@ -114,7 +113,6 @@ namespace transport m_TerminationTimerV6.cancel (); m_Service.stop (); m_Socket.close (); - m_ServiceV6.stop (); m_SocketV6.close (); m_ReceiversService.stop (); m_ReceiversServiceV6.stop (); @@ -136,12 +134,6 @@ namespace transport delete m_ReceiversThreadV6; m_ReceiversThreadV6 = nullptr; } - if (m_ThreadV6) - { - m_ThreadV6->join (); - delete m_ThreadV6; - m_ThreadV6 = nullptr; - } } void SSUServer::Run () @@ -159,21 +151,6 @@ namespace transport } } - void SSUServer::RunV6 () - { - while (m_IsRunning) - { - try - { - m_ServiceV6.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "SSU: v6 server runtime exception: ", ex.what ()); - } - } - } - void SSUServer::RunReceivers () { while (m_IsRunning) @@ -358,7 +335,7 @@ namespace transport } } - m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6)); + m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6)); ReceiveV6 (); } else @@ -456,8 +433,7 @@ namespace transport else { boost::asio::ip::udp::endpoint remoteEndpoint (addr, port); - auto& s = addr.is_v6 () ? m_ServiceV6 : m_Service; - s.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); + m_Service.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); } } } @@ -841,7 +817,7 @@ namespace transport auto session = it.second; if (it.first != session->GetRemoteEndpoint ()) LogPrint (eLogWarning, "SSU: remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first); - m_ServiceV6.post ([session] + m_Service.post ([session] { LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); session->Failed (); diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h index 6a79f754..213f379f 100644 --- a/libi2pd/SSU.h +++ b/libi2pd/SSU.h @@ -64,7 +64,6 @@ namespace transport void DeleteAllSessions (); boost::asio::io_service& GetService () { return m_Service; }; - boost::asio::io_service& GetServiceV6 () { return m_ServiceV6; }; const boost::asio::ip::udp::endpoint& GetEndpoint () const { return m_Endpoint; }; void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to); void AddRelay (uint32_t tag, std::shared_ptr relay); @@ -82,7 +81,6 @@ namespace transport void OpenSocket (); void OpenSocketV6 (); void Run (); - void RunV6 (); void RunReceivers (); void RunReceiversV6 (); void Receive (); @@ -122,9 +120,9 @@ namespace transport bool m_OnlyV6; bool m_IsRunning; - std::thread * m_Thread, * m_ThreadV6, * m_ReceiversThread, * m_ReceiversThreadV6; - boost::asio::io_service m_Service, m_ServiceV6, m_ReceiversService, m_ReceiversServiceV6; - boost::asio::io_service::work m_Work, m_WorkV6, m_ReceiversWork, m_ReceiversWorkV6; + std::thread * m_Thread, * m_ReceiversThread, * m_ReceiversThreadV6; + boost::asio::io_service m_Service, m_ReceiversService, m_ReceiversServiceV6; + boost::asio::io_service::work m_Work, m_ReceiversWork, m_ReceiversWorkV6; boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6; boost::asio::ip::udp::socket m_Socket, m_SocketV6; boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer, diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index 9ce6e2e3..860c2be3 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -49,7 +49,7 @@ namespace transport boost::asio::io_service& SSUSession::GetService () { - return IsV6 () ? m_Server.GetServiceV6 () : m_Server.GetService (); + return m_Server.GetService (); } void SSUSession::CreateAESandMacKey (const uint8_t * pubKey) diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 4e0aa6a5..c6e90ad2 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -501,7 +501,7 @@ namespace transport { auto addr = router->GetSSUV6Address (); if (addr) - m_SSUServer->GetServiceV6 ().post ([this, router, addr] + m_SSUServer->GetService ().post ([this, router, addr] { m_SSUServer->CreateDirectSession (router, { addr->host, (uint16_t)addr->port }, false); }); From da94d407380f3750e8116b78ce685d34edacb88a Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 18 Oct 2020 14:39:58 -0400 Subject: [PATCH 3846/6300] check if session is terminated before receive --- libi2pd_client/I2CP.cpp | 25 +++++++++++++++++-------- libi2pd_client/I2CP.h | 2 +- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 24c2496b..80274d86 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -226,14 +226,13 @@ namespace client } I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): - m_Owner (owner), m_Socket (socket), m_Payload (nullptr), - m_SessionID (0xFFFF), m_MessageID (0), m_IsSendAccepted (true) + m_Owner (owner), m_Socket (socket), m_SessionID (0xFFFF), + m_MessageID (0), m_IsSendAccepted (true) { } I2CPSession::~I2CPSession () { - delete[] m_Payload; } void I2CPSession::Start () @@ -264,6 +263,11 @@ namespace client void I2CPSession::ReceiveHeader () { + if (!m_Socket) + { + LogPrint (eLogError, "I2CP: Can't receive header"); + return; + } boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Header, I2CP_HEADER_SIZE), boost::asio::transfer_all (), std::bind (&I2CPSession::HandleReceivedHeader, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -279,10 +283,7 @@ namespace client if (m_PayloadLen > 0) { if (m_PayloadLen <= I2CP_MAX_MESSAGE_LENGTH) - { - m_Payload = new uint8_t[m_PayloadLen]; ReceivePayload (); - } else { LogPrint (eLogError, "I2CP: Unexpected payload length ", m_PayloadLen); @@ -299,6 +300,11 @@ namespace client void I2CPSession::ReceivePayload () { + if (!m_Socket) + { + LogPrint (eLogError, "I2CP: Can't receive payload"); + return; + } boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Payload, m_PayloadLen), boost::asio::transfer_all (), std::bind (&I2CPSession::HandleReceivedPayload, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -311,8 +317,6 @@ namespace client else { HandleMessage (); - delete[] m_Payload; - m_Payload = nullptr; m_PayloadLen = 0; ReceiveHeader (); // next message } @@ -345,6 +349,11 @@ namespace client void I2CPSession::SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len) { + if (len > I2CP_MAX_MESSAGE_LENGTH) + { + LogPrint (eLogError, "I2CP: Message to send is too long ", len); + return; + } auto socket = m_Socket; if (socket) { diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 4c6b7531..c5dc80e7 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -179,7 +179,7 @@ namespace client I2CPServer& m_Owner; std::shared_ptr m_Socket; - uint8_t m_Header[I2CP_HEADER_SIZE], * m_Payload; + uint8_t m_Header[I2CP_HEADER_SIZE], m_Payload[I2CP_MAX_MESSAGE_LENGTH]; size_t m_PayloadLen; std::shared_ptr m_Destination; From 387830e07a90f1f5b16608ea2fc5cd0161698754 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 19 Oct 2020 18:26:01 -0400 Subject: [PATCH 3847/6300] encyption type 0,4 by default for client tunnels --- libi2pd/Config.cpp | 8 ++++---- libi2pd_client/ClientContext.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index b924a108..fc479532 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -108,8 +108,8 @@ namespace config { ("httpproxy.latency.max", value()->default_value("0"), "HTTP proxy max latency for tunnels") ("httpproxy.outproxy", value()->default_value(""), "HTTP proxy upstream out proxy url") ("httpproxy.addresshelper", value()->default_value(true), "Enable or disable addresshelper") - ("httpproxy.i2cp.leaseSetType", value()->default_value("1"), "Local destination's LeaseSet type") - ("httpproxy.i2cp.leaseSetEncType", value()->default_value("0"), "Local destination's LeaseSet encryption type") + ("httpproxy.i2cp.leaseSetType", value()->default_value("3"), "Local destination's LeaseSet type") + ("httpproxy.i2cp.leaseSetEncType", value()->default_value("0,4"), "Local destination's LeaseSet encryption type") ; options_description socksproxy("SOCKS Proxy options"); @@ -129,8 +129,8 @@ namespace config { ("socksproxy.outproxy.enabled", value()->default_value(false), "Enable or disable SOCKS outproxy") ("socksproxy.outproxy", value()->default_value("127.0.0.1"), "Upstream outproxy address for SOCKS Proxy") ("socksproxy.outproxyport", value()->default_value(9050), "Upstream outproxy port for SOCKS Proxy") - ("socksproxy.i2cp.leaseSetType", value()->default_value("1"), "Local destination's LeaseSet type") - ("socksproxy.i2cp.leaseSetEncType", value()->default_value("0"), "Local destination's LeaseSet encryption type") + ("socksproxy.i2cp.leaseSetType", value()->default_value("3"), "Local destination's LeaseSet type") + ("socksproxy.i2cp.leaseSetEncType", value()->default_value("0,4"), "Local destination's LeaseSet encryption type") ; options_description sam("SAM bridge options"); diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 04f50e6f..ab868916 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -456,7 +456,7 @@ namespace client options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false); options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); - std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, ""); + std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, isServer ? "" : "0,4"); if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType; std::string privKey = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_PRIV_KEY, ""); if (privKey.length () > 0) options[I2CP_PARAM_LEASESET_PRIV_KEY] = privKey; From b7ebb3ea3d1b275e567c4cd015749355911f3f60 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 20 Oct 2020 19:38:49 +0300 Subject: [PATCH 3848/6300] [android] support NetworkCallback for network state changes Signed-off-by: R4SAS --- .../src/org/purplei2p/i2pd/I2PDActivity.java | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/android/src/org/purplei2p/i2pd/I2PDActivity.java b/android/src/org/purplei2p/i2pd/I2PDActivity.java index 8295e9f1..777ca748 100755 --- a/android/src/org/purplei2p/i2pd/I2PDActivity.java +++ b/android/src/org/purplei2p/i2pd/I2PDActivity.java @@ -15,6 +15,7 @@ import java.util.TimerTask; import android.Manifest; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; @@ -25,6 +26,10 @@ import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.res.AssetManager; import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; import android.net.Uri; import android.os.Bundle; import android.os.Build; @@ -41,6 +46,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; @@ -63,6 +69,7 @@ public class I2PDActivity extends Activity { private TextView textView; private boolean assetsCopied; + private NetworkStateCallback networkCallback; private String i2pdpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd/"; //private ConfigParser parser = new ConfigParser(i2pdpath); // TODO: @@ -116,7 +123,7 @@ public class I2PDActivity extends Activity { daemonStateUpdatedListener.daemonStateUpdate(); // request permissions - if (Build.VERSION.SDK_INT >= 23) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, @@ -137,6 +144,10 @@ public class I2PDActivity extends Activity { } openBatteryOptimizationDialogIfNeeded(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + registerNetworkCallback(); + } } @Override @@ -244,7 +255,7 @@ public class I2PDActivity extends Activity { } private boolean isBatteryOptimizationsOpenOsDialogApiAvailable() { - return android.os.Build.VERSION.SDK_INT >= 23; + return android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; } @Override @@ -631,6 +642,33 @@ public class I2PDActivity extends Activity { return "show_battery_optimization" + (device == null ? "" : device); } + @TargetApi(Build.VERSION_CODES.M) + private void registerNetworkCallback() { + ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkRequest request = new NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + .build(); + networkCallback = new NetworkStateCallback(); + connectivityManager.registerNetworkCallback(request, networkCallback); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private final class NetworkStateCallback extends ConnectivityManager.NetworkCallback { + @Override + public void onAvailable(Network network) { + super.onAvailable(network); + I2PD_JNI.onNetworkStateChanged(true); + Log.i(TAG, "NetworkCallback.onAvailable"); + } + + @Override + public void onLost(Network network) { + super.onLost(network); + I2PD_JNI.onNetworkStateChanged(false); + Log.i(TAG, " NetworkCallback.onLost"); + } + } + private void quit() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { From 17f5bcbd1cd3d40c082203130e7acc31e60a15b1 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 21 Oct 2020 14:46:31 +0800 Subject: [PATCH 3849/6300] qt: newer options added; not tested --- qt/i2pd_qt/TunnelConfig.cpp | 12 +++ qt/i2pd_qt/TunnelConfig.h | 26 +++++- qt/i2pd_qt/TunnelPane.cpp | 119 ++++++++++++++++++++++++++++ qt/i2pd_qt/TunnelPane.h | 63 ++++++++------- qt/i2pd_qt/generalsettingswidget.ui | 15 +++- qt/i2pd_qt/i2pd_qt.pro | 3 +- qt/i2pd_qt/mainwindow.cpp | 17 ++-- qt/i2pd_qt/mainwindow.h | 36 +++++++++ 8 files changed, 249 insertions(+), 42 deletions(-) diff --git a/qt/i2pd_qt/TunnelConfig.cpp b/qt/i2pd_qt/TunnelConfig.cpp index 04fa6132..a2cdf92b 100644 --- a/qt/i2pd_qt/TunnelConfig.cpp +++ b/qt/i2pd_qt/TunnelConfig.cpp @@ -24,6 +24,18 @@ void TunnelConfig::saveI2CPParametersToStringStream(std::stringstream& out) { if (!i2cpParameters.getExplicitPeers().isEmpty()) //todo #947 out << i2p::client::I2CP_PARAM_EXPLICIT_PEERS << "=" << i2cpParameters.getExplicitPeers().toStdString() << "\n"; + out << i2p::client::I2CP_PARAM_LEASESET_AUTH_TYPE << "=" + << i2cpParameters.get_i2cp_leaseSetAuthType().toStdString() << "\n"; + out << i2p::client::I2CP_PARAM_LEASESET_ENCRYPTION_TYPE << "=" + << i2cpParameters.get_i2cp_leaseSetEncType().toStdString() << "\n"; + out << i2p::client::I2CP_PARAM_LEASESET_PRIV_KEY << "=" + << i2cpParameters.get_i2cp_leaseSetPrivKey().toStdString() << "\n"; + out << i2p::client::I2CP_PARAM_LEASESET_TYPE << "=" + << i2cpParameters.get_i2cp_leaseSetType().toStdString() << "\n"; + out << i2p::client::I2CP_PARAM_STREAMING_ANSWER_PINGS << "=" + << (i2cpParameters.get_i2p_streaming_answerPings() ? "true" : "false") << "\n"; + out << i2p::client::I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY << "=" + << i2cpParameters.get_i2p_streaming_initialAckDelay().toStdString() << "\n"; out << "\n"; } diff --git a/qt/i2pd_qt/TunnelConfig.h b/qt/i2pd_qt/TunnelConfig.h index 7f262215..530a525c 100644 --- a/qt/i2pd_qt/TunnelConfig.h +++ b/qt/i2pd_qt/TunnelConfig.h @@ -16,25 +16,49 @@ class I2CPParameters{ QString outbound_quantity; //number of outbound tunnels. 5 by default QString crypto_tagsToSend; //number of ElGamal/AES tags to send. 40 by default; too low value may cause problems with tunnel building QString explicitPeers; //list of comma-separated b64 addresses of peers to use, default: unset + QString i2p_streaming_initialAckDelay; //i2p.streaming.initialAckDelay -- milliseconds to wait before sending Ack. 200 by default + bool i2p_streaming_answerPings; //i2p.streaming.answerPings -- enable sending pongs. true by default + QString i2cp_leaseSetType; //i2cp.leaseSetType -- type of LeaseSet to be sent. 1, 3 or 5. 1 by default + QString i2cp_leaseSetEncType; //i2cp.leaseSetEncType -- comma separated encryption types to be used in LeaseSet type 3 or 5. Identity's type by default + QString i2cp_leaseSetPrivKey; //i2cp.leaseSetPrivKey -- decryption key for encrypted LeaseSet in base64. PSK or private DH + QString i2cp_leaseSetAuthType; //i2cp.leaseSetAuthType -- authentication type for encrypted LeaseSet. 0 - no authentication(default), 1 - DH, 2 - PSK public: I2CPParameters(): inbound_length(), outbound_length(), inbound_quantity(), outbound_quantity(), crypto_tagsToSend(), - explicitPeers() {} + explicitPeers(), + i2p_streaming_initialAckDelay(), + i2p_streaming_answerPings(true), + i2cp_leaseSetType(), + i2cp_leaseSetEncType(), + i2cp_leaseSetPrivKey(), + i2cp_leaseSetAuthType() {} const QString& getInbound_length(){return inbound_length;} const QString& getOutbound_length(){return outbound_length;} const QString& getInbound_quantity(){return inbound_quantity;} const QString& getOutbound_quantity(){return outbound_quantity;} const QString& getCrypto_tagsToSend(){return crypto_tagsToSend;} const QString& getExplicitPeers(){return explicitPeers;} + const QString& get_i2p_streaming_initialAckDelay(){return i2p_streaming_initialAckDelay;} + bool get_i2p_streaming_answerPings(){return i2p_streaming_answerPings;} + const QString& get_i2cp_leaseSetType(){return i2cp_leaseSetType;} + const QString& get_i2cp_leaseSetEncType(){return i2cp_leaseSetEncType;} + const QString& get_i2cp_leaseSetPrivKey(){return i2cp_leaseSetPrivKey;} + const QString& get_i2cp_leaseSetAuthType(){return i2cp_leaseSetAuthType;} void setInbound_length(QString inbound_length_){inbound_length=inbound_length_;} void setOutbound_length(QString outbound_length_){outbound_length=outbound_length_;} void setInbound_quantity(QString inbound_quantity_){inbound_quantity=inbound_quantity_;} void setOutbound_quantity(QString outbound_quantity_){outbound_quantity=outbound_quantity_;} void setCrypto_tagsToSend(QString crypto_tagsToSend_){crypto_tagsToSend=crypto_tagsToSend_;} void setExplicitPeers(QString explicitPeers_){explicitPeers=explicitPeers_;} + void set_i2p_streaming_initialAckDelay(QString i2p_streaming_initialAckDelay_){i2p_streaming_initialAckDelay=i2p_streaming_initialAckDelay_;} + void set_i2p_streaming_answerPings(bool i2p_streaming_answerPings_){i2p_streaming_answerPings=i2p_streaming_answerPings_;} + void set_i2cp_leaseSetType(QString i2cp_leaseSetType_){i2cp_leaseSetType=i2cp_leaseSetType_;} + void set_i2cp_leaseSetEncType(QString i2cp_leaseSetEncType_){i2cp_leaseSetEncType=i2cp_leaseSetEncType_;} + void set_i2cp_leaseSetPrivKey(QString i2cp_leaseSetPrivKey_){i2cp_leaseSetPrivKey=i2cp_leaseSetPrivKey_;} + void set_i2cp_leaseSetAuthType(QString i2cp_leaseSetAuthType_){i2cp_leaseSetAuthType=i2cp_leaseSetAuthType_;} }; diff --git a/qt/i2pd_qt/TunnelPane.cpp b/qt/i2pd_qt/TunnelPane.cpp index 60b0b6d5..ffacfd61 100644 --- a/qt/i2pd_qt/TunnelPane.cpp +++ b/qt/i2pd_qt/TunnelPane.cpp @@ -187,30 +187,149 @@ void TunnelPane::appendControlsForI2CPParameters(I2CPParameters& i2cpParameters, { //explicitPeers -- list of comma-separated b64 addresses of peers to use, default: unset + const QString& value=i2cpParameters.getExplicitPeers(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + QLabel *_Label; + explicitPeersLabel = _Label = new QLabel(gridLayoutWidget_2); + _Label->setObjectName(QStringLiteral("_Label")); + horizontalLayout_2->addWidget(_Label); + QLineEdit *_LineEdit; + explicitPeersLineEdit = _LineEdit = new QLineEdit(gridLayoutWidget_2); + _LineEdit->setObjectName(QStringLiteral("_LineEdit")); + _LineEdit->setText(value); + _LineEdit->setMaximumWidth(80); + QObject::connect(_LineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(_LineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); } { //i2p.streaming.initialAckDelay -- milliseconds to wait before sending Ack. 200 by default + const QString& value=i2cpParameters.get_i2p_streaming_initialAckDelay(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + QLabel *_Label; + i2p_streaming_initialAckDelayLabel = _Label = new QLabel(gridLayoutWidget_2); + _Label->setObjectName(QStringLiteral("_Label")); + horizontalLayout_2->addWidget(_Label); + QLineEdit *_LineEdit; + i2p_streaming_initialAckDelayLineEdit = _LineEdit = new QLineEdit(gridLayoutWidget_2); + _LineEdit->setObjectName(QStringLiteral("_LineEdit")); + _LineEdit->setText(value); + _LineEdit->setMaximumWidth(80); + QObject::connect(_LineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(_LineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); } { //i2p.streaming.answerPings -- enable sending pongs. true by default + const bool value=i2cpParameters.get_i2p_streaming_answerPings(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + QCheckBox *_CheckBox; + i2p_streaming_answerPingsCheckBox = _CheckBox = new QCheckBox(gridLayoutWidget_2); + _CheckBox->setObjectName(QStringLiteral("_CheckBox")); + horizontalLayout_2->addWidget(_CheckBox); + _CheckBox->setChecked(value); + QObject::connect(_CheckBox, SIGNAL(toggled(bool)), + this, SLOT(updated())); + tunnelGridLayout->addLayout(horizontalLayout_2); } { //i2cp.leaseSetType -- type of LeaseSet to be sent. 1, 3 or 5. 1 by default + const QString& value=i2cpParameters.get_i2cp_leaseSetType(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + QLabel *_Label; + i2cp_leaseSetTypeLabel = _Label = new QLabel(gridLayoutWidget_2); + _Label->setObjectName(QStringLiteral("_Label")); + horizontalLayout_2->addWidget(_Label); + QLineEdit *_LineEdit; + i2cp_leaseSetTypeLineEdit = _LineEdit = new QLineEdit(gridLayoutWidget_2); + _LineEdit->setObjectName(QStringLiteral("_LineEdit")); + _LineEdit->setText(value); + _LineEdit->setMaximumWidth(80); + QObject::connect(_LineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(_LineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); } { //i2cp.leaseSetEncType -- comma separated encryption types to be used in LeaseSet type 3 or 5. Identity's type by default + const QString& value=i2cpParameters.get_i2cp_leaseSetEncType(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + QLabel *_Label; + i2cp_leaseSetEncTypeLabel = _Label = new QLabel(gridLayoutWidget_2); + _Label->setObjectName(QStringLiteral("_Label")); + horizontalLayout_2->addWidget(_Label); + QLineEdit *_LineEdit; + i2cp_leaseSetEncTypeLineEdit = _LineEdit = new QLineEdit(gridLayoutWidget_2); + _LineEdit->setObjectName(QStringLiteral("_LineEdit")); + _LineEdit->setText(value); + _LineEdit->setMaximumWidth(80); + QObject::connect(_LineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(_LineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); } { //i2cp.leaseSetPrivKey -- decryption key for encrypted LeaseSet in base64. PSK or private DH + const QString& value=i2cpParameters.get_i2cp_leaseSetPrivKey(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + QLabel *_Label; + i2cp_leaseSetPrivKeyLabel = _Label = new QLabel(gridLayoutWidget_2); + _Label->setObjectName(QStringLiteral("_Label")); + horizontalLayout_2->addWidget(_Label); + QLineEdit *_LineEdit; + i2cp_leaseSetPrivKeyLineEdit = _LineEdit = new QLineEdit(gridLayoutWidget_2); + _LineEdit->setObjectName(QStringLiteral("_LineEdit")); + _LineEdit->setText(value); + _LineEdit->setMaximumWidth(80); + QObject::connect(_LineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(_LineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); } { //i2cp.leaseSetAuthType -- authentication type for encrypted LeaseSet. 0 - no authentication(default), 1 - DH, 2 - PSK + const QString& value=i2cpParameters.get_i2cp_leaseSetAuthType(); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + QLabel *_Label; + i2cp_leaseSetAuthTypeLabel = _Label = new QLabel(gridLayoutWidget_2); + _Label->setObjectName(QStringLiteral("_Label")); + horizontalLayout_2->addWidget(_Label); + QLineEdit *_LineEdit; + i2cp_leaseSetAuthTypeLineEdit = _LineEdit = new QLineEdit(gridLayoutWidget_2); + _LineEdit->setObjectName(QStringLiteral("_LineEdit")); + _LineEdit->setText(value); + _LineEdit->setMaximumWidth(80); + QObject::connect(_LineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updated())); + horizontalLayout_2->addWidget(_LineEdit); + QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout_2->addItem(horizontalSpacer); + tunnelGridLayout->addLayout(horizontalLayout_2); } retranslateI2CPParameters(); diff --git a/qt/i2pd_qt/TunnelPane.h b/qt/i2pd_qt/TunnelPane.h index 6a13b7a0..61071059 100644 --- a/qt/i2pd_qt/TunnelPane.h +++ b/qt/i2pd_qt/TunnelPane.h @@ -11,6 +11,7 @@ #include "QLineEdit" #include "QGroupBox" #include "QVBoxLayout" +#include "QCheckBox" #include "TunnelConfig.h" @@ -89,6 +90,27 @@ protected: QLabel * crypto_tagsToSendLabel; QLineEdit * crypto_tagsToSendLineEdit; + QLabel * explicitPeersLabel; + QLineEdit * explicitPeersLineEdit; + + QLabel * i2p_streaming_initialAckDelayLabel; + QLineEdit * i2p_streaming_initialAckDelayLineEdit; + + QCheckBox * i2p_streaming_answerPingsCheckBox; + + QLabel * i2cp_leaseSetTypeLabel; + QLineEdit * i2cp_leaseSetTypeLineEdit; + + QLabel * i2cp_leaseSetEncTypeLabel; + QLineEdit * i2cp_leaseSetEncTypeLineEdit; + + QLabel * i2cp_leaseSetPrivKeyLabel; + QLineEdit * i2cp_leaseSetPrivKeyLineEdit; + + QLabel * i2cp_leaseSetAuthTypeLabel; + QLineEdit * i2cp_leaseSetAuthTypeLineEdit; + + QString readTunnelTypeComboboxData(); //should be created by factory @@ -105,6 +127,12 @@ public: i2cpParams.setOutbound_length(outbound_lengthLineEdit->text()); i2cpParams.setOutbound_quantity(outbound_quantityLineEdit->text()); i2cpParams.setCrypto_tagsToSend(crypto_tagsToSendLineEdit->text()); + i2cpParams.set_i2cp_leaseSetAuthType(i2cp_leaseSetAuthTypeLineEdit->text()); + i2cpParams.set_i2cp_leaseSetEncType(i2cp_leaseSetEncTypeLineEdit->text()); + i2cpParams.set_i2cp_leaseSetPrivKey(i2cp_leaseSetPrivKeyLineEdit->text()); + i2cpParams.set_i2cp_leaseSetType(i2cp_leaseSetTypeLineEdit->text()); + i2cpParams.set_i2p_streaming_answerPings(i2p_streaming_answerPingsCheckBox->isChecked()); + i2cpParams.set_i2p_streaming_initialAckDelay(i2p_streaming_initialAckDelayLineEdit->text()); return true; } protected: @@ -133,34 +161,13 @@ private: inbound_quantityLabel->setText(QApplication::translate("tunForm", "Number of inbound tunnels:", 0));; outbound_quantityLabel->setText(QApplication::translate("tunForm", "Number of outbound tunnels:", 0));; crypto_tagsToSendLabel->setText(QApplication::translate("tunForm", "Number of ElGamal/AES tags to send:", 0));; - - { - //explicitPeers -- list of comma-separated b64 addresses of peers to use, default: unset - } - - { - //i2p.streaming.initialAckDelay -- milliseconds to wait before sending Ack. 200 by default - } - - { - //i2p.streaming.answerPings -- enable sending pongs. true by default - } - - { - //i2cp.leaseSetType -- type of LeaseSet to be sent. 1, 3 or 5. 1 by default - } - - { - //i2cp.leaseSetEncType -- comma separated encryption types to be used in LeaseSet type 3 or 5. Identity's type by default - } - - { - //i2cp.leaseSetPrivKey -- decryption key for encrypted LeaseSet in base64. PSK or private DH - } - - { - //i2cp.leaseSetAuthType -- authentication type for encrypted LeaseSet. 0 - no authentication(default), 1 - DH, 2 - PSK - } + explicitPeersLabel->setText(QApplication::translate("tunForm", "List of comma-separated b64 addresses of peers to use:", 0));; + i2p_streaming_initialAckDelayLabel->setText(QApplication::translate("tunForm", "Milliseconds to wait before sending Ack:", 0)); + i2p_streaming_answerPingsCheckBox->setText(QApplication::translate("tunForm", "Enable sending pongs", 0)); + i2cp_leaseSetTypeLabel->setText(QApplication::translate("tunForm", "Type of LeaseSet to be sent. 1, 3 or 5:", 0)); + i2cp_leaseSetEncTypeLabel->setText(QApplication::translate("tunForm", "Comma-separated encryption types to be used in LeaseSet type 3 or 5:", 0)); + i2cp_leaseSetPrivKeyLabel->setText(QApplication::translate("tunForm", "Decryption key for encrypted LeaseSet in base64. PSK or private DH:", 0)); + i2cp_leaseSetAuthTypeLabel->setText(QApplication::translate("tunForm", "Authentication type for encrypted LeaseSet. 0 - no authentication (default), 1 - DH, 2 - PSK:", 0)); } }; diff --git a/qt/i2pd_qt/generalsettingswidget.ui b/qt/i2pd_qt/generalsettingswidget.ui index e64f9e35..1c71de22 100644 --- a/qt/i2pd_qt/generalsettingswidget.ui +++ b/qt/i2pd_qt/generalsettingswidget.ui @@ -25,7 +25,7 @@ 0 0 679 - 4151 + 4178 @@ -464,13 +464,13 @@ 0 - 390 + 417 16777215 - 390 + 417 @@ -482,7 +482,7 @@ 0 20 661 - 368 + 397 @@ -528,6 +528,13 @@ + + + + Check remote RI for being in blacklist of reserved IP ranges + + + diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 468d2d00..2f44b814 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -4,7 +4,8 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = i2pd_qt TEMPLATE = app -QMAKE_CXXFLAGS *= -std=c++11 -Wno-unused-parameter -Wno-maybe-uninitialized +QMAKE_CXXFLAGS *= -Wno-unused-parameter -Wno-maybe-uninitialized +CONFIG += strict_c++ c++11 DEFINES += USE_UPNP diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index d4d34109..feb1b4a9 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -190,6 +190,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initStringBox( OPTION("","family",[]{return "";}), uiSettings->familyLineEdit); initIntegerBox( OPTION("","netid",[]{return "2";}), uiSettings->netIdLineEdit, tr("NetID")); initCheckBox( OPTION("","ssu",[]{return "true";}), uiSettings->ssuCheckBox);//Enable SSU transport protocol (use UDP). true by default + initCheckBox( OPTION("","reservedrange",[]{return "true";}), uiSettings->reservedrange_checkbox); #ifdef Q_OS_WIN initNonGUIOption( OPTION("","svcctl",[]{return "";})); @@ -205,22 +206,22 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initCheckBox( OPTION("http","auth",[]{return "";}), uiSettings->webconsoleBasicAuthCheckBox); initStringBox( OPTION("http","user",[]{return "i2pd";}), uiSettings->webconsoleUserNameLineEditBasicAuth); initStringBox( OPTION("http","pass",[]{return "";}), uiSettings->webconsolePasswordLineEditBasicAuth); - initCheckBox( OPTION("http","strictheaders",[]{return "true";}), uiSettings->httpStrictHeadersCheckBox);//TODO add option Enable strict host checking on WebUI. true by default - initStringBox( OPTION("http","hostname",[]{return "localhost";}), uiSettings->httpHostnameLineEdit);//TODO add option Expected hostname for WebUI (default: localhost) + initCheckBox( OPTION("http","strictheaders",[]{return "true";}), uiSettings->httpStrictHeadersCheckBox);//Enable strict host checking on WebUI. true by default + initStringBox( OPTION("http","hostname",[]{return "localhost";}), uiSettings->httpHostnameLineEdit);//Expected hostname for WebUI (default: localhost) initCheckBox( OPTION("httpproxy","enabled",[]{return "";}), uiSettings->httpProxyEnabledCheckBox); initIPAddressBox( OPTION("httpproxy","address",[]{return "";}), uiSettings->httpProxyAddressLineEdit, tr("HTTP proxy -> IP address")); initTCPPortBox( OPTION("httpproxy","port",[]{return "4444";}), uiSettings->httpProxyPortLineEdit, tr("HTTP proxy -> Port")); - initCheckBox( OPTION("httpproxy","addresshelper",[]{return "true";}), uiSettings->httpProxyAddressHelperCheckBox);//TODO add option Enable address helper (jump). true by default + initCheckBox( OPTION("httpproxy","addresshelper",[]{return "true";}), uiSettings->httpProxyAddressHelperCheckBox);//Enable address helper (jump). true by default initFileChooser( OPTION("httpproxy","keys",[]{return "";}), uiSettings->httpProxyKeyFileLineEdit, uiSettings->httpProxyKeyFilePushButton); initSignatureTypeCombobox(OPTION("httpproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_httpPorxySignatureType); initStringBox( OPTION("httpproxy","inbound.length",[]{return "3";}), uiSettings->httpProxyInboundTunnelsLenLineEdit); initStringBox( OPTION("httpproxy","inbound.quantity",[]{return "5";}), uiSettings->httpProxyInboundTunnQuantityLineEdit); initStringBox( OPTION("httpproxy","outbound.length",[]{return "3";}), uiSettings->httpProxyOutBoundTunnLenLineEdit); initStringBox( OPTION("httpproxy","outbound.quantity",[]{return "5";}), uiSettings->httpProxyOutboundTunnQuantityLineEdit); - initStringBox( OPTION("httpproxy","outproxy",[]{return "";}), uiSettings->httpProxyOutproxyLineEdit);//TODO add option HTTP proxy upstream out proxy url (like http://false.i2p) - initStringBox( OPTION("httpproxy","i2cp.leaseSetType",[]{return "1";}), uiSettings->httpProxyI2cpLeaseSetTypeLineEdit);//TODO add option Type of LeaseSet to be sent. 1, 3 or 5. 1 by default - initStringBox( OPTION("httpproxy","i2cp.leaseSetEncType",[]{return "";}), uiSettings->httpProxyI2cpLeaseSetEncTypeLineEdit);//TODO add option Comma separated encryption types to be used in LeaseSet type 3 or 5 + initStringBox( OPTION("httpproxy","outproxy",[]{return "";}), uiSettings->httpProxyOutproxyLineEdit);//HTTP proxy upstream out proxy url (like http://false.i2p) + initStringBox( OPTION("httpproxy","i2cp.leaseSetType",[]{return "1";}), uiSettings->httpProxyI2cpLeaseSetTypeLineEdit);//Type of LeaseSet to be sent. 1, 3 or 5. 1 by default + initStringBox( OPTION("httpproxy","i2cp.leaseSetEncType",[]{return "";}), uiSettings->httpProxyI2cpLeaseSetEncTypeLineEdit);//Comma separated encryption types to be used in LeaseSet type 3 or 5 initCheckBox( OPTION("socksproxy","enabled",[]{return "";}), uiSettings->socksProxyEnabledCheckBox); initIPAddressBox( OPTION("socksproxy","address",[]{return "";}), uiSettings->socksProxyAddressLineEdit, tr("Socks proxy -> IP address")); @@ -233,8 +234,8 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initStringBox( OPTION("socksproxy","outbound.quantity",[]{return "";}), uiSettings->socksProxyOutboundTunnQuantityLineEdit); initIPAddressBox( OPTION("socksproxy","outproxy",[]{return "";}), uiSettings->outproxyAddressLineEdit, tr("Socks proxy -> Outproxy address")); initTCPPortBox( OPTION("socksproxy","outproxyport",[]{return "";}), uiSettings->outproxyPortLineEdit, tr("Socks proxy -> Outproxy port")); - initStringBox( OPTION("socksproxy","i2cp.leaseSetType",[]{return "1";}), uiSettings->socksProxyI2cpLeaseSetTypeLineEdit);//TODO add option Type of LeaseSet to be sent. 1, 3 or 5. 1 by default - initStringBox( OPTION("socksproxy","i2cp.leaseSetEncType",[]{return "";}), uiSettings->socksProxyI2cpLeaseSetEncTypeLineEdit);//TODO add option Comma separated encryption types to be used in LeaseSet type 3 or 5 + initStringBox( OPTION("socksproxy","i2cp.leaseSetType",[]{return "1";}), uiSettings->socksProxyI2cpLeaseSetTypeLineEdit);//Type of LeaseSet to be sent. 1, 3 or 5. 1 by default + initStringBox( OPTION("socksproxy","i2cp.leaseSetEncType",[]{return "";}), uiSettings->socksProxyI2cpLeaseSetEncTypeLineEdit);//Comma separated encryption types to be used in LeaseSet type 3 or 5 initCheckBox( OPTION("sam","enabled",[]{return "false";}), uiSettings->samEnabledCheckBox); initIPAddressBox( OPTION("sam","address",[]{return "";}), uiSettings->samAddressLineEdit, tr("SAM -> IP address")); diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index b4f57f8f..77c8826b 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -545,6 +545,18 @@ private: void deleteTunnelForms(); void deleteTunnelFromUI(std::string tunnelName, TunnelConfig* cnf); + template + std::string GetI2CPOption (const Section& section, const std::string& name, const std::string& value) const + { + return section.second.get (boost::property_tree::ptree::path_type (name, '/'), value); + } + + template + std::string GetI2CPOption (const Section& section, const std::string& name, const char* value) const + { + return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::string (value)); + } + template std::string GetI2CPOption (const Section& section, const std::string& name, const Type& value) const { @@ -565,6 +577,19 @@ private: param.setOutbound_quantity(QString(_OUTBOUND_TUNNELS_QUANTITY.c_str())); std::string _TAGS_TO_SEND = options[I2CP_PARAM_TAGS_TO_SEND] = GetI2CPOption (section, I2CP_PARAM_TAGS_TO_SEND, DEFAULT_TAGS_TO_SEND); param.setCrypto_tagsToSend(QString(_TAGS_TO_SEND.c_str())); + std::string _i2cp_leaseSetAuthType = options[I2CP_PARAM_LEASESET_AUTH_TYPE] = GetI2CPOption (section, I2CP_PARAM_LEASESET_AUTH_TYPE, 0); + param.set_i2cp_leaseSetAuthType(QString(_i2cp_leaseSetAuthType.c_str())); + const char DEFAULT_LEASESET_ENCRYPTION_TYPE[] = ""; + std::string _i2cp_leaseSetEncType = options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = GetI2CPOption (section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, DEFAULT_LEASESET_ENCRYPTION_TYPE);//todo Identity's type by default + param.set_i2cp_leaseSetEncType(QString(_i2cp_leaseSetEncType.c_str())); + std::string _i2cp_leaseSetPrivKey = options[I2CP_PARAM_LEASESET_PRIV_KEY] = GetI2CPOption (section, I2CP_PARAM_LEASESET_PRIV_KEY, ""); + param.set_i2cp_leaseSetPrivKey(QString(_i2cp_leaseSetPrivKey.c_str())); + std::string _i2cp_leaseSetType = options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption (section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); + param.set_i2cp_leaseSetType(QString(_i2cp_leaseSetType.c_str())); + std::string _i2p_streaming_answerPings= options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption (section, I2CP_PARAM_STREAMING_ANSWER_PINGS, DEFAULT_ANSWER_PINGS); + param.set_i2p_streaming_answerPings((_i2p_streaming_answerPings.compare("true")==0)||(_i2p_streaming_answerPings.compare("yes")==0)); + std::string _i2p_streaming_initialAckDelay = options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption (section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); + param.set_i2p_streaming_initialAckDelay(QString(_i2p_streaming_initialAckDelay.c_str())); options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY);//TODO include into param options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY);//TODO include into param } @@ -582,6 +607,17 @@ private: param.setOutbound_quantity(QString::number(_OUTBOUND_TUNNELS_QUANTITY)); const int _TAGS_TO_SEND = DEFAULT_TAGS_TO_SEND; param.setCrypto_tagsToSend(QString::number(_TAGS_TO_SEND)); + const int _i2cp_leaseSetAuthType = 0; + param.set_i2cp_leaseSetAuthType(QString::number(_i2cp_leaseSetAuthType)); + const QString _i2cp_leaseSetEncType = "0,4"; //todo Identity's type by default + param.set_i2cp_leaseSetEncType(_i2cp_leaseSetEncType); + param.set_i2cp_leaseSetPrivKey(""); + const int _i2cp_leaseSetType = DEFAULT_LEASESET_TYPE; + param.set_i2cp_leaseSetType(QString::number(_i2cp_leaseSetType)); + bool _i2p_streaming_answerPings= DEFAULT_ANSWER_PINGS; + param.set_i2p_streaming_answerPings(_i2p_streaming_answerPings); + const int _i2p_streaming_initialAckDelay = DEFAULT_INITIAL_ACK_DELAY; + param.set_i2p_streaming_initialAckDelay(QString::number(_i2p_streaming_initialAckDelay)); } From 4001f48a2858008eb84157b5b9d6e745db77a503 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 21 Oct 2020 18:12:39 +0800 Subject: [PATCH 3850/6300] qt: visual fixes & more --- qt/i2pd_qt/.gitignore | 1 + qt/i2pd_qt/TunnelPane.cpp | 2 +- qt/i2pd_qt/TunnelPane.h | 4 +- qt/i2pd_qt/generalsettingswidget.ui | 79 ++++++++++++++++++++++------- qt/i2pd_qt/mainwindow.cpp | 14 ++++- 5 files changed, 79 insertions(+), 21 deletions(-) diff --git a/qt/i2pd_qt/.gitignore b/qt/i2pd_qt/.gitignore index bbf3e1e4..1f9d6012 100644 --- a/qt/i2pd_qt/.gitignore +++ b/qt/i2pd_qt/.gitignore @@ -9,4 +9,5 @@ object_script.* i2pd_qt_plugin_import.cpp i2pd_qt.pro.autosave* build* +nohup.out diff --git a/qt/i2pd_qt/TunnelPane.cpp b/qt/i2pd_qt/TunnelPane.cpp index ffacfd61..4b873ac1 100644 --- a/qt/i2pd_qt/TunnelPane.cpp +++ b/qt/i2pd_qt/TunnelPane.cpp @@ -31,7 +31,7 @@ void TunnelPane::setupTunnelPane( this->gridLayoutWidget_2=gridLayoutWidget_2; tunnelGridLayout = new QVBoxLayout(gridLayoutWidget_2); tunnelGridLayout->setObjectName(QStringLiteral("tunnelGridLayout")); - tunnelGridLayout->setContentsMargins(5, 5, 5, 5); + tunnelGridLayout->setContentsMargins(10, 25, 10, 10); tunnelGridLayout->setSpacing(5); //header diff --git a/qt/i2pd_qt/TunnelPane.h b/qt/i2pd_qt/TunnelPane.h index 61071059..59303afd 100644 --- a/qt/i2pd_qt/TunnelPane.h +++ b/qt/i2pd_qt/TunnelPane.h @@ -165,9 +165,9 @@ private: i2p_streaming_initialAckDelayLabel->setText(QApplication::translate("tunForm", "Milliseconds to wait before sending Ack:", 0)); i2p_streaming_answerPingsCheckBox->setText(QApplication::translate("tunForm", "Enable sending pongs", 0)); i2cp_leaseSetTypeLabel->setText(QApplication::translate("tunForm", "Type of LeaseSet to be sent. 1, 3 or 5:", 0)); - i2cp_leaseSetEncTypeLabel->setText(QApplication::translate("tunForm", "Comma-separated encryption types to be used in LeaseSet type 3 or 5:", 0)); + i2cp_leaseSetEncTypeLabel->setText(QApplication::translate("tunForm", "Comma-separ. encr. types to be used in LeaseSet type 3 or 5:", 0)); i2cp_leaseSetPrivKeyLabel->setText(QApplication::translate("tunForm", "Decryption key for encrypted LeaseSet in base64. PSK or private DH:", 0)); - i2cp_leaseSetAuthTypeLabel->setText(QApplication::translate("tunForm", "Authentication type for encrypted LeaseSet. 0 - no authentication (default), 1 - DH, 2 - PSK:", 0)); + i2cp_leaseSetAuthTypeLabel->setText(QApplication::translate("tunForm", "Auth type for encrypted LeaseSet. 0 - no auth, 1 - DH, 2 - PSK:", 0)); } }; diff --git a/qt/i2pd_qt/generalsettingswidget.ui b/qt/i2pd_qt/generalsettingswidget.ui index 1c71de22..eabaa473 100644 --- a/qt/i2pd_qt/generalsettingswidget.ui +++ b/qt/i2pd_qt/generalsettingswidget.ui @@ -19,13 +19,28 @@ GeneralSettingsContentsForm + + QGroupBox { + font: bold; + border: 1px solid silver; + border-radius: 6px; + margin-top: 6px; +} + +QGroupBox::title { + subcontrol-origin: margin; + left: 7px; + padding: 0px 5px 0px 5px; +} + + 0 0 679 - 4178 + 4242 @@ -59,7 +74,7 @@ - + 0 0 @@ -67,18 +82,36 @@ 0 - 51 + 60 16777215 - 51 + 60 + + QGroupBox { + font: bold; + border: 1px solid silver; + border-radius: 6px; + margin-top: 6px; +} + +QGroupBox::title { + subcontrol-origin: margin; + left: 7px; + padding: 0px 5px 0px 5px; +} + + Configuration file: + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + @@ -90,7 +123,7 @@ - QLayout::SetMaximumSize + QLayout::SetMinAndMaxSize @@ -100,19 +133,19 @@ 0 - 0 + 25 0 - 27 + 0 16777215 - 27 + 25 @@ -309,13 +342,13 @@ 0 - 112 + 130 16777215 - 112 + 130 @@ -2329,13 +2362,13 @@ Comma separated list of base64 identities: 0 - 215 + 225 16777215 - 215 + 225 @@ -3341,7 +3374,7 @@ Comma separated list of base64 identities: - + 0 0 @@ -3349,13 +3382,13 @@ Comma separated list of base64 identities: 0 - 128 + 145 16777215 - 128 + 300 @@ -3370,12 +3403,24 @@ Comma separated list of base64 identities: -1 19 681 - 101 + 124 - QLayout::SetMinAndMaxSize + QLayout::SetDefaultConstraint + + + 10 + + + 10 + + + 10 + + + 10 diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index feb1b4a9..95712b68 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -84,7 +84,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren ui->settingsScrollArea->resize(uiSettings->settingsContentsQVBoxLayout->sizeHint().width()+10,380); //QScrollBar* const barSett = ui->settingsScrollArea->verticalScrollBar(); int w = 683; - int h = 4000; + int h = 4250; ui->settingsContents->setFixedSize(w, h); ui->settingsContents->setGeometry(QRect(0,0,w,h)); @@ -324,6 +324,18 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren ui->tunnelsScrollAreaWidgetContents->setGeometry(QRect(0, 0, 621, 451)); + ui->tunnelsScrollAreaWidgetContents->setStyleSheet("QGroupBox { " \ + " font: bold;" \ + " border: 1px solid silver;" \ + " border-radius: 6px;" \ + " margin-top: 6px;" \ + "}" \ + "QGroupBox::title {" \ + " subcontrol-origin: margin;" \ + " left: 7px;" \ + " padding: 0px 5px 0px 5px;" \ + "}"); + appendTunnelForms(""); uiSettings->configFileLineEdit->setEnabled(false); From 365fce922c6fe14c24736548ded2dd59fee3971c Mon Sep 17 00:00:00 2001 From: user Date: Thu, 22 Oct 2020 00:35:59 +0800 Subject: [PATCH 3851/6300] qt: socks defaults fixes, socks outproxy enabled checkbox added; visual fixes --- qt/i2pd_qt/generalsettingswidget.ui | 1017 ++++++++++++++------------- qt/i2pd_qt/mainwindow.cpp | 7 +- 2 files changed, 516 insertions(+), 508 deletions(-) diff --git a/qt/i2pd_qt/generalsettingswidget.ui b/qt/i2pd_qt/generalsettingswidget.ui index eabaa473..7a35c0a5 100644 --- a/qt/i2pd_qt/generalsettingswidget.ui +++ b/qt/i2pd_qt/generalsettingswidget.ui @@ -40,7 +40,7 @@ QGroupBox::title { 0 0 679 - 4242 + 4434 @@ -82,13 +82,13 @@ QGroupBox::title { 0 - 60 + 80 16777215 - 60 + 80 @@ -112,46 +112,62 @@ QGroupBox::title { Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - + - 0 - 18 - 661 - 31 + 12 + 19 + 651 + 51 - - - QLayout::SetMinAndMaxSize - + - - - - - - - 0 - 25 - + + + QLayout::SetMinAndMaxSize - - - 0 - 0 - + + 10 - - - 16777215 - 25 - + + 10 - - Browse… + + 10 - + + 10 + + + + + + + + + 0 + 27 + + + + + 0 + 0 + + + + + 16777215 + 27 + + + + Browse… + + + + @@ -162,40 +178,56 @@ QGroupBox::title { 0 - 51 + 80 16777215 - 51 + 80 Data folder (for storage of i2pd data — RI, keys, peer profiles, …): - + - 0 - 20 - 661 - 31 + 12 + 19 + 651 + 51 - - - QLayout::SetMaximumSize + + + 10 + + + 10 + + + 10 + + + 10 - - - - - - Browse… + + + QLayout::SetMaximumSize - + + + + + + + Browse… + + + + @@ -212,58 +244,74 @@ QGroupBox::title { 0 - 51 + 80 16777215 - 51 + 80 Pid file: - + - 0 - 18 - 661 - 31 + 12 + 19 + 651 + 51 - - - QLayout::SetMaximumSize + + + 10 + + + 10 + + + 10 + + + 10 - - - - - - - 0 - 0 - + + + QLayout::SetMaximumSize - - - 0 - 27 - - - - - 16777215 - 27 - - - - Browse… - - + + + + + + + + 0 + 0 + + + + + 0 + 27 + + + + + 16777215 + 27 + + + + Browse… + + + + @@ -280,58 +328,74 @@ QGroupBox::title { 0 - 51 + 80 16777215 - 51 + 80 Tunnels configuration file: - + - 0 - 18 - 661 - 31 + 12 + 19 + 651 + 51 - - - QLayout::SetMaximumSize + + + 10 + + + 10 + + + 10 + + + 10 - - - - - - - 0 - 0 - + + + QLayout::SetMaximumSize - - - 0 - 27 - - - - - 16777215 - 27 - - - - Browse… - - + + + + + + + + 0 + 0 + + + + + 0 + 27 + + + + + 16777215 + 27 + + + + Browse… + + + + @@ -2885,434 +2949,377 @@ Comma separated list of base64 identities: 0 - 405 + 500 16777215 - 405 + 500 Socks proxy - + - 0 + 9 20 - 97 - 22 - - - - Enabled - - - - - - 0 - 40 661 - 31 + 470 - + + + 10 + + + 10 + + + 10 + + + 10 + - + - IP address to listen on: + Enabled - + + + + + IP address to listen on: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 70 - 661 - 31 - - - - - - - Port to listen on: - - + + + + + Port to listen on: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - - 80 - 16777215 - - - + + + + + Keys file: + + + + + + + + + + Browse… + + + + - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 100 - 661 - 31 - - - - - - - Keys file: - - + + + + + Signature type: + + + + + + + + + + Edit + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - + + + + + Inbound tunnels length: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - Browse… - - - - - - - - - 0 - 160 - 661 - 31 - - - - - - - Inbound tunnels length: - - + + + + + Inbound tunnels quantity: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - - 80 - 16777215 - - - + + + + + Outbound tunnels length: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 190 - 661 - 31 - - - - - - - Inbound tunnels quantity: - - + + + + + Outbound tunnels quantity: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - - 80 - 16777215 - + + + 0 - + + 0 + + + + + Outproxy enabled + + + + - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 220 - 661 - 31 - - - - - - - Outbound tunnels length: - - + + + + + Outproxy address: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - - 80 - 16777215 - - - + + + + + Outproxy port: + + + + + + + + 80 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 250 - 661 - 31 - - - - - - - Outbound tunnels quantity: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 280 - 661 - 31 - - - - - - - Outproxy address: - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 310 - 661 - 31 - - - - - - - Outproxy port: - - - - - - - - 80 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - 130 - 661 - 31 - - - - - - - Signature type: - - - - - - - - - - Edit - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - -1 - 340 - 661 - 62 - - - diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 95712b68..89178ee0 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -84,7 +84,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren ui->settingsScrollArea->resize(uiSettings->settingsContentsQVBoxLayout->sizeHint().width()+10,380); //QScrollBar* const barSett = ui->settingsScrollArea->verticalScrollBar(); int w = 683; - int h = 4250; + int h = 4550; ui->settingsContents->setFixedSize(w, h); ui->settingsContents->setGeometry(QRect(0,0,w,h)); @@ -232,8 +232,9 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initStringBox( OPTION("socksproxy","inbound.quantity",[]{return "";}), uiSettings->socksProxyInboundTunnQuantityLineEdit); initStringBox( OPTION("socksproxy","outbound.length",[]{return "";}), uiSettings->socksProxyOutBoundTunnLenLineEdit); initStringBox( OPTION("socksproxy","outbound.quantity",[]{return "";}), uiSettings->socksProxyOutboundTunnQuantityLineEdit); - initIPAddressBox( OPTION("socksproxy","outproxy",[]{return "";}), uiSettings->outproxyAddressLineEdit, tr("Socks proxy -> Outproxy address")); - initTCPPortBox( OPTION("socksproxy","outproxyport",[]{return "";}), uiSettings->outproxyPortLineEdit, tr("Socks proxy -> Outproxy port")); + initCheckBox( OPTION("socksproxy","outproxy.enabled",[]{return "false";}), uiSettings->socksOutproxyEnabledCheckBox); + initIPAddressBox( OPTION("socksproxy","outproxy",[]{return "127.0.0.1";}), uiSettings->outproxyAddressLineEdit, tr("Socks proxy -> Outproxy address")); + initTCPPortBox( OPTION("socksproxy","outproxyport",[]{return "9050";}), uiSettings->outproxyPortLineEdit, tr("Socks proxy -> Outproxy port")); initStringBox( OPTION("socksproxy","i2cp.leaseSetType",[]{return "1";}), uiSettings->socksProxyI2cpLeaseSetTypeLineEdit);//Type of LeaseSet to be sent. 1, 3 or 5. 1 by default initStringBox( OPTION("socksproxy","i2cp.leaseSetEncType",[]{return "";}), uiSettings->socksProxyI2cpLeaseSetEncTypeLineEdit);//Comma separated encryption types to be used in LeaseSet type 3 or 5 From 49bf735c22947f57c4dfad4aaa8536895a804d96 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Oct 2020 18:59:16 -0400 Subject: [PATCH 3852/6300] don't set destination to routers --- libi2pd/Garlic.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 429a2092..5f76bca1 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -740,7 +740,8 @@ namespace garlic session = std::make_shared (this, true); session->SetRemoteStaticKey (staticKey); } - session->SetDestination (destination->GetIdentHash ()); // TODO: remove + if (destination->IsDestination ()) + session->SetDestination (destination->GetIdentHash ()); // TODO: remove return session; } else From 801ecaa41c988537b4abd54965c443e9fc060544 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Oct 2020 19:03:51 -0400 Subject: [PATCH 3853/6300] temporary exclude routers with non ElGamal crypto types --- libi2pd/RouterInfo.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 12f81a42..8420044d 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -153,6 +153,12 @@ namespace data m_IsUnreachable = true; return; } + if (m_RouterIdentity->GetCryptoKeyType () != CRYPTO_KEY_TYPE_ELGAMAL) + { + // we support ElGamal only. TODO: remove later + m_IsUnreachable = true; + return; + } if (verifySignature) { // reject RSA signatures From d65a282e9d788a524b688a168859eb4d14735e09 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 22 Oct 2020 18:34:15 -0400 Subject: [PATCH 3854/6300] check routers with non ElGamal encryptions for lookup, publish and tunnel build --- libi2pd/Destination.cpp | 11 +++++++---- libi2pd/RouterInfo.cpp | 6 ------ libi2pd/TunnelConfig.h | 13 ++++++++++++- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 669fd791..927e98a0 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -559,7 +559,9 @@ namespace client m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); - auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound)); + auto msg = i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound); + if (floodfill->GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) // TODO: remove when implemented + msg = WrapMessage (floodfill, msg); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); @@ -754,9 +756,10 @@ namespace client else AddSessionKey (replyKey, replyTag); - auto msg = WrapMessage (nextFloodfill, - CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, - request->replyTunnel, replyKey, replyTag, isECIES)); + auto msg = CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, + request->replyTunnel, replyKey, replyTag, isECIES); + if (nextFloodfill->GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) // TODO: remove when implemented + msg = WrapMessage (nextFloodfill, msg); request->outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 8420044d..12f81a42 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -153,12 +153,6 @@ namespace data m_IsUnreachable = true; return; } - if (m_RouterIdentity->GetCryptoKeyType () != CRYPTO_KEY_TYPE_ELGAMAL) - { - // we support ElGamal only. TODO: remove later - m_IsUnreachable = true; - return; - } if (verifySignature) { // reject RSA signatures diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 0bd8a842..3a2fa44b 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -112,9 +112,20 @@ namespace tunnel RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); auto encryptor = ident->CreateEncryptor (nullptr); if (encryptor) - encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); + { + if (ident->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + EncryptECIES (encryptor, clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx); + else + encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); + } memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } + + void EncryptECIES (std::shared_ptr& encryptor, + const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx) const + { + memset (encrypted, 0, 512); // TODO: implement + } }; class TunnelConfig From 57d6c7a3b3c30d9b12492b18960f0de94e7d7bde Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 22 Oct 2020 21:06:23 -0400 Subject: [PATCH 3855/6300] Added TunnelConfig.cpp. Removed CryptoWroker.h --- build/CMakeLists.txt | 1 + libi2pd/CryptoWorker.h | 88 ------------------------------ libi2pd/TunnelConfig.cpp | 114 +++++++++++++++++++++++++++++++++++++++ libi2pd/TunnelConfig.h | 99 +++------------------------------- qt/i2pd_qt/i2pd_qt.pro | 2 +- 5 files changed, 123 insertions(+), 181 deletions(-) delete mode 100644 libi2pd/CryptoWorker.h create mode 100644 libi2pd/TunnelConfig.cpp diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 4825855b..827e20d3 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -85,6 +85,7 @@ set(LIBI2PD_SRC "${LIBI2PD_SRC_DIR}/TunnelEndpoint.cpp" "${LIBI2PD_SRC_DIR}/TunnelGateway.cpp" "${LIBI2PD_SRC_DIR}/TunnelPool.cpp" + "${LIBI2PD_SRC_DIR}/TunnelConfig.cpp" "${LIBI2PD_SRC_DIR}/util.cpp" ) diff --git a/libi2pd/CryptoWorker.h b/libi2pd/CryptoWorker.h deleted file mode 100644 index 27b012e7..00000000 --- a/libi2pd/CryptoWorker.h +++ /dev/null @@ -1,88 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef CRYPTO_WORKER_H_ -#define CRYPTO_WORKER_H_ - -#include -#include -#include -#include -#include -#include - -namespace i2p -{ -namespace worker -{ - template - struct ThreadPool - { - typedef std::function ResultFunc; - typedef std::function WorkFunc; - typedef std::pair, WorkFunc> Job; - typedef std::mutex mtx_t; - typedef std::unique_lock lock_t; - typedef std::condition_variable cond_t; - ThreadPool(int workers) - { - stop = false; - if(workers > 0) - { - while(workers--) - { - threads.emplace_back([this] { - for (;;) - { - Job job; - { - lock_t lock(this->queue_mutex); - this->condition.wait( - lock, [this] { return this->stop || !this->jobs.empty(); }); - if (this->stop && this->jobs.empty()) return; - job = std::move(this->jobs.front()); - this->jobs.pop_front(); - } - ResultFunc result = job.second(); - job.first->GetService().post(result); - } - }); - } - } - }; - - void Offer(const Job & job) - { - { - lock_t lock(queue_mutex); - if (stop) return; - jobs.emplace_back(job); - } - condition.notify_one(); - } - - ~ThreadPool() - { - { - lock_t lock(queue_mutex); - stop = true; - } - condition.notify_all(); - for(auto &t: threads) t.join(); - } - - std::vector threads; - std::deque jobs; - mtx_t queue_mutex; - cond_t condition; - bool stop; - }; -} -} - -#endif diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp new file mode 100644 index 00000000..3b7955d4 --- /dev/null +++ b/libi2pd/TunnelConfig.cpp @@ -0,0 +1,114 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +* +*/ + +#include +#include +#include "Transports.h" +#include "Timestamp.h" +#include "I2PEndian.h" +#include "I2NPProtocol.h" +#include "TunnelConfig.h" + +namespace i2p +{ +namespace tunnel +{ + TunnelHopConfig::TunnelHopConfig (std::shared_ptr r) + { + RAND_bytes (layerKey, 32); + RAND_bytes (ivKey, 32); + RAND_bytes (replyKey, 32); + RAND_bytes (replyIV, 16); + RAND_bytes ((uint8_t *)&tunnelID, 4); + if (!tunnelID) tunnelID = 1; // tunnelID can't be zero + isGateway = true; + isEndpoint = true; + ident = r; + //nextRouter = nullptr; + nextTunnelID = 0; + + next = nullptr; + prev = nullptr; + } + + void TunnelHopConfig::SetNextIdent (const i2p::data::IdentHash& ident) + { + nextIdent = ident; + isEndpoint = false; + RAND_bytes ((uint8_t *)&nextTunnelID, 4); + if (!nextTunnelID) nextTunnelID = 1; // tunnelID can't be zero + } + + void TunnelHopConfig::SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) + { + nextIdent = replyIdent; + nextTunnelID = replyTunnelID; + isEndpoint = true; + } + + void TunnelHopConfig::SetNext (TunnelHopConfig * n) + { + next = n; + if (next) + { + next->prev = this; + next->isGateway = false; + isEndpoint = false; + nextIdent = next->ident->GetIdentHash (); + nextTunnelID = next->tunnelID; + } + } + + void TunnelHopConfig::SetPrev (TunnelHopConfig * p) + { + prev = p; + if (prev) + { + prev->next = this; + prev->isEndpoint = false; + isGateway = false; + } + } + + void TunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) const + { + uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); + memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); + htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); + memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); + uint8_t flag = 0; + if (isGateway) flag |= 0x80; + if (isEndpoint) flag |= 0x40; + clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; + htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); + htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); + RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); + auto encryptor = ident->CreateEncryptor (nullptr); + if (encryptor) + { + if (ident->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + EncryptECIES (encryptor, clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx); + else + encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); + } + memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); + } + + void TunnelHopConfig::EncryptECIES (std::shared_ptr& encryptor, + const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx) const + { + memset (encrypted, 0, 512); // TODO: implement + } +} +} \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 3a2fa44b..24e6f836 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -9,13 +9,9 @@ #ifndef TUNNEL_CONFIG_H__ #define TUNNEL_CONFIG_H__ -#include -#include #include -#include #include "Identity.h" #include "RouterContext.h" -#include "Timestamp.h" namespace i2p { @@ -35,97 +31,16 @@ namespace tunnel TunnelHopConfig * next, * prev; int recordIndex; // record # in tunnel build message - TunnelHopConfig (std::shared_ptr r) - { - RAND_bytes (layerKey, 32); - RAND_bytes (ivKey, 32); - RAND_bytes (replyKey, 32); - RAND_bytes (replyIV, 16); - RAND_bytes ((uint8_t *)&tunnelID, 4); - if (!tunnelID) tunnelID = 1; // tunnelID can't be zero - isGateway = true; - isEndpoint = true; - ident = r; - //nextRouter = nullptr; - nextTunnelID = 0; + TunnelHopConfig (std::shared_ptr r); - next = nullptr; - prev = nullptr; - } - - void SetNextIdent (const i2p::data::IdentHash& ident) - { - nextIdent = ident; - isEndpoint = false; - RAND_bytes ((uint8_t *)&nextTunnelID, 4); - if (!nextTunnelID) nextTunnelID = 1; // tunnelID can't be zero - } - - void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) - { - nextIdent = replyIdent; - nextTunnelID = replyTunnelID; - isEndpoint = true; - } - - void SetNext (TunnelHopConfig * n) - { - next = n; - if (next) - { - next->prev = this; - next->isGateway = false; - isEndpoint = false; - nextIdent = next->ident->GetIdentHash (); - nextTunnelID = next->tunnelID; - } - } - - void SetPrev (TunnelHopConfig * p) - { - prev = p; - if (prev) - { - prev->next = this; - prev->isEndpoint = false; - isGateway = false; - } - } - - void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) const - { - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); - htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); - uint8_t flag = 0; - if (isGateway) flag |= 0x80; - if (isEndpoint) flag |= 0x40; - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; - htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); - htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); - RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); - auto encryptor = ident->CreateEncryptor (nullptr); - if (encryptor) - { - if (ident->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) - EncryptECIES (encryptor, clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx); - else - encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); - } - memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); - } + void SetNextIdent (const i2p::data::IdentHash& ident); + void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent); + void SetNext (TunnelHopConfig * n); + void SetPrev (TunnelHopConfig * p); + void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) const; void EncryptECIES (std::shared_ptr& encryptor, - const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx) const - { - memset (encrypted, 0, 512); // TODO: implement - } + const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx) const; }; class TunnelConfig diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 2f44b814..534d5f5a 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -60,6 +60,7 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \ ../../libi2pd/TunnelEndpoint.cpp \ ../../libi2pd/TunnelGateway.cpp \ ../../libi2pd/TunnelPool.cpp \ + ../../libi2pd/TunnelConfig.cpp \ ../../libi2pd/util.cpp \ ../../libi2pd/Elligator.cpp \ ../../libi2pd/ECIESX25519AEADRatchetSession.cpp \ @@ -104,7 +105,6 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../libi2pd/CPU.h \ ../../libi2pd/Crypto.h \ ../../libi2pd/CryptoKey.h \ - ../../libi2pd/CryptoWorker.h \ ../../libi2pd/Datagram.h \ ../../libi2pd/Destination.h \ ../../libi2pd/Ed25519.h \ From b6175132eb84649dd98d84a8475d29a9cb9072e1 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 23 Oct 2020 21:41:42 +0800 Subject: [PATCH 3856/6300] android: fix for pre-init jni calls; processAssets moved to a logical place --- .../org/purplei2p/i2pd/DaemonSingleton.java | 181 -------- .../src/org/purplei2p/i2pd/DaemonWrapper.java | 387 ++++++++++++++++++ .../org/purplei2p/i2pd/ForegroundService.java | 98 +++-- .../src/org/purplei2p/i2pd/I2PDActivity.java | 236 +---------- 4 files changed, 466 insertions(+), 436 deletions(-) delete mode 100644 android/src/org/purplei2p/i2pd/DaemonSingleton.java create mode 100644 android/src/org/purplei2p/i2pd/DaemonWrapper.java diff --git a/android/src/org/purplei2p/i2pd/DaemonSingleton.java b/android/src/org/purplei2p/i2pd/DaemonSingleton.java deleted file mode 100644 index e9e4fc06..00000000 --- a/android/src/org/purplei2p/i2pd/DaemonSingleton.java +++ /dev/null @@ -1,181 +0,0 @@ -package org.purplei2p.i2pd; - -import java.util.HashSet; -import java.util.Set; -import android.os.Environment; -import android.util.Log; - -import org.purplei2p.i2pd.R; - -public class DaemonSingleton { - private static final String TAG = "i2pd"; - private static final DaemonSingleton instance = new DaemonSingleton(); - - public interface StateUpdateListener { - void daemonStateUpdate(); - } - - private final Set stateUpdateListeners = new HashSet<>(); - - public static DaemonSingleton getInstance() { - return instance; - } - - public synchronized void addStateChangeListener(StateUpdateListener listener) { - stateUpdateListeners.add(listener); - } - - public synchronized void removeStateChangeListener(StateUpdateListener listener) { - stateUpdateListeners.remove(listener); - } - - private synchronized void setState(State newState) { - if (newState == null) - throw new NullPointerException(); - - State oldState = state; - - if (oldState == null) - throw new NullPointerException(); - - if (oldState.equals(newState)) - return; - - state = newState; - fireStateUpdate1(); - } - - public synchronized void stopAcceptingTunnels() { - if (isStartedOkay()) { - setState(State.gracefulShutdownInProgress); - I2PD_JNI.stopAcceptingTunnels(); - } - } - - public synchronized void startAcceptingTunnels() { - if (isStartedOkay()) { - setState(State.startedOkay); - I2PD_JNI.startAcceptingTunnels(); - } - } - - public synchronized void reloadTunnelsConfigs() { - if (isStartedOkay()) { - I2PD_JNI.reloadTunnelsConfigs(); - } - } - - public synchronized int GetTransitTunnelsCount() { - return I2PD_JNI.GetTransitTunnelsCount(); - } - - private volatile boolean startedOkay; - - public enum State { - uninitialized(R.string.uninitialized), - starting(R.string.starting), - jniLibraryLoaded(R.string.jniLibraryLoaded), - startedOkay(R.string.startedOkay), - startFailed(R.string.startFailed), - gracefulShutdownInProgress(R.string.gracefulShutdownInProgress), - stopped(R.string.stopped); - - State(int statusStringResourceId) { - this.statusStringResourceId = statusStringResourceId; - } - - private final int statusStringResourceId; - - public int getStatusStringResourceId() { - return statusStringResourceId; - } - }; - - private volatile State state = State.uninitialized; - - public State getState() { - return state; - } - - { - setState(State.starting); - new Thread(new Runnable() { - - @Override - public void run() { - try { - I2PD_JNI.loadLibraries(); - setState(State.jniLibraryLoaded); - } catch (Throwable tr) { - lastThrowable = tr; - setState(State.startFailed); - return; - } - try { - synchronized (DaemonSingleton.this) { - I2PD_JNI.setDataDir(Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd"); - daemonStartResult = I2PD_JNI.startDaemon(); - if ("ok".equals(daemonStartResult)) { - setState(State.startedOkay); - setStartedOkay(true); - } else - setState(State.startFailed); - } - } catch (Throwable tr) { - lastThrowable = tr; - setState(State.startFailed); - } - } - - }, "i2pdDaemonStart").start(); - } - - private Throwable lastThrowable; - private String daemonStartResult = "N/A"; - - private void fireStateUpdate1() { - Log.i(TAG, "daemon state change: " + state); - for (StateUpdateListener listener : stateUpdateListeners) { - try { - listener.daemonStateUpdate(); - } 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); - setState(State.stopped); - } - } -} diff --git a/android/src/org/purplei2p/i2pd/DaemonWrapper.java b/android/src/org/purplei2p/i2pd/DaemonWrapper.java new file mode 100644 index 00000000..7f9fcd93 --- /dev/null +++ b/android/src/org/purplei2p/i2pd/DaemonWrapper.java @@ -0,0 +1,387 @@ +package org.purplei2p.i2pd; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Set; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.AssetManager; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.os.Build; +import android.os.Environment; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +public class DaemonWrapper { + private static final String TAG = "i2pd"; + private final AssetManager assetManager; + private final ConnectivityManager connectivityManager; + private String i2pdpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd/"; + private boolean assetsCopied; + + public interface StateUpdateListener { + void daemonStateUpdate(); + } + + private final Set stateUpdateListeners = new HashSet<>(); + + public synchronized void addStateChangeListener(StateUpdateListener listener) { + stateUpdateListeners.add(listener); + } + + public synchronized void removeStateChangeListener(StateUpdateListener listener) { + stateUpdateListeners.remove(listener); + } + + private synchronized void setState(State newState) { + if (newState == null) + throw new NullPointerException(); + + State oldState = state; + + if (oldState == null) + throw new NullPointerException(); + + if (oldState.equals(newState)) + return; + + state = newState; + fireStateUpdate1(); + } + + public synchronized void stopAcceptingTunnels() { + if (isStartedOkay()) { + setState(State.gracefulShutdownInProgress); + I2PD_JNI.stopAcceptingTunnels(); + } + } + + public synchronized void startAcceptingTunnels() { + if (isStartedOkay()) { + setState(State.startedOkay); + I2PD_JNI.startAcceptingTunnels(); + } + } + + public synchronized void reloadTunnelsConfigs() { + if (isStartedOkay()) { + I2PD_JNI.reloadTunnelsConfigs(); + } + } + + public synchronized int GetTransitTunnelsCount() { + return I2PD_JNI.GetTransitTunnelsCount(); + } + + private volatile boolean startedOkay; + + public enum State { + uninitialized(R.string.uninitialized), + starting(R.string.starting), + jniLibraryLoaded(R.string.jniLibraryLoaded), + startedOkay(R.string.startedOkay), + startFailed(R.string.startFailed), + gracefulShutdownInProgress(R.string.gracefulShutdownInProgress), + stopped(R.string.stopped); + + State(int statusStringResourceId) { + this.statusStringResourceId = statusStringResourceId; + } + + private final int statusStringResourceId; + + public int getStatusStringResourceId() { + return statusStringResourceId; + } + }; + + private volatile State state = State.uninitialized; + + public State getState() { + return state; + } + + public DaemonWrapper(AssetManager assetManager, ConnectivityManager connectivityManager){ + this.assetManager = assetManager; + this.connectivityManager = connectivityManager; + setState(State.starting); + new Thread(() -> { + try { + processAssets(); + I2PD_JNI.loadLibraries(); + setState(State.jniLibraryLoaded); + registerNetworkCallback(); + } catch (Throwable tr) { + lastThrowable = tr; + setState(State.startFailed); + return; + } + try { + synchronized (DaemonWrapper.this) { + I2PD_JNI.setDataDir(Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd"); + daemonStartResult = I2PD_JNI.startDaemon(); + if ("ok".equals(daemonStartResult)) { + setState(State.startedOkay); + setStartedOkay(true); + } else + setState(State.startFailed); + } + } catch (Throwable tr) { + lastThrowable = tr; + setState(State.startFailed); + } + }, "i2pdDaemonStart").start(); + } + + private Throwable lastThrowable; + private String daemonStartResult = "N/A"; + + private void fireStateUpdate1() { + Log.i(TAG, "daemon state change: " + state); + for (StateUpdateListener listener : stateUpdateListeners) { + try { + listener.daemonStateUpdate(); + } 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); + setState(State.stopped); + } + } + + private void processAssets() { + if (!assetsCopied) { + try { + assetsCopied = true; // prevent from running on every state update + + File holderFile = new File(i2pdpath, "assets.ready"); + String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX + StringBuilder text = new StringBuilder(); + + if (holderFile.exists()) { + try { // if holder file exists, read assets version string + FileReader fileReader = new FileReader(holderFile); + + try { + BufferedReader br = new BufferedReader(fileReader); + + try { + String line; + + while ((line = br.readLine()) != null) { + text.append(line); + } + }finally { + try { + br.close(); + } catch (IOException e) { + Log.e(TAG, "", e); + } + } + } finally { + try { + fileReader.close(); + } catch (IOException e) { + Log.e(TAG, "", e); + } + } + } catch (IOException e) { + Log.e(TAG, "", e); + } + } + + // if version differs from current app version or null, try to delete certificates folder + if (!text.toString().contains(versionName)) + try { + boolean deleteResult = holderFile.delete(); + if (!deleteResult) + Log.e(TAG, "holderFile.delete() returned " + deleteResult + ", absolute path='" + holderFile.getAbsolutePath() + "'"); + File certPath = new File(i2pdpath, "certificates"); + deleteRecursive(certPath); + } + catch (Throwable tr) { + Log.e(TAG, "", tr); + } + + // copy assets. If processed file exists, it won't be overwritten + copyAsset("addressbook"); + copyAsset("certificates"); + copyAsset("tunnels.d"); + copyAsset("i2pd.conf"); + copyAsset("subscriptions.txt"); + copyAsset("tunnels.conf"); + + // update holder file about successful copying + FileWriter writer = new FileWriter(holderFile); + try { + writer.append(versionName); + } finally { + try { + writer.close(); + } catch (IOException e) { + Log.e(TAG,"on writer close", e); + } + } + } + catch (Throwable tr) + { + Log.e(TAG,"on assets copying", tr); + } + } + } + + /** + * Copy the asset at the specified path to this app's data directory. If the + * asset is a directory, its contents are also copied. + * + * @param path + * Path to asset, relative to app's assets directory. + */ + private void copyAsset(String path) { + AssetManager manager = assetManager; + + // If we have a directory, we make it and recurse. If a file, we copy its + // contents. + try { + String[] contents = manager.list(path); + + // The documentation suggests that list throws an IOException, but doesn't + // say under what conditions. It'd be nice if it did so when the path was + // to a file. That doesn't appear to be the case. If the returned array is + // null or has 0 length, we assume the path is to a file. This means empty + // directories will get turned into files. + if (contents == null || contents.length == 0) { + copyFileAsset(path); + return; + } + + // Make the directory. + File dir = new File(i2pdpath, path); + boolean result = dir.mkdirs(); + Log.d(TAG, "dir.mkdirs() returned " + result); + + // Recurse on the contents. + for (String entry : contents) { + copyAsset(path + '/' + entry); + } + } catch (IOException e) { + Log.e(TAG, "ex ignored for path='" + path + "'", e); + } + } + + /** + * Copy the asset file specified by path to app's data directory. Assumes + * parent directories have already been created. + * + * @param path + * Path to asset, relative to app's assets directory. + */ + private void copyFileAsset(String path) { + File file = new File(i2pdpath, path); + if (!file.exists()) { + try { + try (InputStream in = assetManager.open(path)) { + try (OutputStream out = new FileOutputStream(file)) { + byte[] buffer = new byte[1024]; + int read = in.read(buffer); + while (read != -1) { + out.write(buffer, 0, read); + read = in.read(buffer); + } + } + } + } catch (IOException e) { + Log.e(TAG, "", e); + } + } + } + + private void deleteRecursive(File fileOrDirectory) { + if (fileOrDirectory.isDirectory()) { + File[] files = fileOrDirectory.listFiles(); + if (files != null) { + for (File child : files) { + deleteRecursive(child); + } + } + } + boolean deleteResult = fileOrDirectory.delete(); + if (!deleteResult) + Log.e(TAG, "fileOrDirectory.delete() returned " + deleteResult + ", absolute path='" + fileOrDirectory.getAbsolutePath() + "'"); + } + + public void registerNetworkCallback(){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) registerNetworkCallback0(); + } + + @TargetApi(Build.VERSION_CODES.M) + private void registerNetworkCallback0() { + NetworkRequest request = new NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + .build(); + NetworkStateCallbackImpl networkCallback = new NetworkStateCallbackImpl(); + connectivityManager.registerNetworkCallback(request, networkCallback); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private static final class NetworkStateCallbackImpl extends ConnectivityManager.NetworkCallback { + @Override + public void onAvailable(Network network) { + super.onAvailable(network); + I2PD_JNI.onNetworkStateChanged(true); + Log.i(TAG, "NetworkCallback.onAvailable"); + } + + @Override + public void onLost(Network network) { + super.onLost(network); + I2PD_JNI.onNetworkStateChanged(false); + Log.i(TAG, " NetworkCallback.onLost"); + } + } +} diff --git a/android/src/org/purplei2p/i2pd/ForegroundService.java b/android/src/org/purplei2p/i2pd/ForegroundService.java index c1b1cc26..c1c918ac 100644 --- a/android/src/org/purplei2p/i2pd/ForegroundService.java +++ b/android/src/org/purplei2p/i2pd/ForegroundService.java @@ -19,8 +19,12 @@ public class ForegroundService extends Service { private volatile boolean shown; - private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = - new DaemonSingleton.StateUpdateListener() { + private static ForegroundService instance; + + private static volatile DaemonWrapper daemon; + + private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = + new DaemonWrapper.StateUpdateListener() { @Override public void daemonStateUpdate() { @@ -40,7 +44,7 @@ public class ForegroundService extends Service { // Unique Identification Number for the Notification. // We use it on Notification start, and to cancel it. - private int NOTIFICATION = 1; + private static final int NOTIFICATION = 1; /** * Class for clients to access. Because we know this service always @@ -53,16 +57,25 @@ public class ForegroundService extends Service { } } + public static void init(DaemonWrapper daemon) { + ForegroundService.daemon = daemon; + initCheck(); + } + + private static void initCheck() { + if(instance!=null && daemon!=null) instance.setListener(); + } + @Override public void onCreate() { notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + instance = this; + initCheck(); + } - synchronized (this) { - DaemonSingleton.getInstance().addStateChangeListener(daemonStateUpdatedListener); - if (!shown) daemonStateUpdatedListener.daemonStateUpdate(); - } - // Tell the user we started. -// Toast.makeText(this, R.string.i2pd_service_started, Toast.LENGTH_SHORT).show(); + private void setListener() { + daemon.addStateChangeListener(daemonStateUpdatedListener); + if (!shown) daemonStateUpdatedListener.daemonStateUpdate(); } @Override @@ -73,8 +86,17 @@ public class ForegroundService extends Service { @Override public void onDestroy() { - DaemonSingleton.getInstance().removeStateChangeListener(daemonStateUpdatedListener); cancelNotification(); + deinitCheck(); + instance=null; + } + + public static void deinit() { + deinitCheck(); + } + + private static void deinitCheck() { + if(daemon!=null && instance!=null)daemon.removeStateChangeListener(instance.daemonStateUpdatedListener); } private synchronized void cancelNotification() { @@ -101,35 +123,39 @@ public class ForegroundService extends Service { * Show a notification while this service is running. */ private synchronized void showNotification() { - // In this sample, we'll use the same text for the ticker and the expanded notification - CharSequence text = getText(DaemonSingleton.getInstance().getState().getStatusStringResourceId()); + if(daemon!=null) { + // In this sample, we'll use the same text for the ticker and the expanded notification + CharSequence text = getText(daemon.getState().getStatusStringResourceId()); - // The PendingIntent to launch our activity if the user selects this notification - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, - new Intent(this, I2PDActivity.class), 0); + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, I2PDActivity.class), 0); - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : ""; + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : ""; - // Set the info for the views that show in the notification panel. - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId) - .setOngoing(true) - .setSmallIcon(R.drawable.itoopie_notification_icon); // the status icon - if(Build.VERSION.SDK_INT >= 16) builder = builder.setPriority(Notification.PRIORITY_DEFAULT); - if(Build.VERSION.SDK_INT >= 21) builder = builder.setCategory(Notification.CATEGORY_SERVICE); - Notification notification = builder - .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(); + // Set the info for the views that show in the notification panel. + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId) + .setOngoing(true) + .setSmallIcon(R.drawable.itoopie_notification_icon); // the status icon + if (Build.VERSION.SDK_INT >= 16) + builder = builder.setPriority(Notification.PRIORITY_DEFAULT); + if (Build.VERSION.SDK_INT >= 21) + builder = builder.setCategory(Notification.CATEGORY_SERVICE); + Notification notification = builder + .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); - shown = true; + // Send the notification. + //mNM.notify(NOTIFICATION, notification); + startForeground(NOTIFICATION, notification); + shown = true; + } } @RequiresApi(Build.VERSION_CODES.O) @@ -144,6 +170,4 @@ public class ForegroundService extends Service { else Log.e(TAG, "error: NOTIFICATION_SERVICE is null"); return channelId; } - - private static final DaemonSingleton daemon = DaemonSingleton.getInstance(); } diff --git a/android/src/org/purplei2p/i2pd/I2PDActivity.java b/android/src/org/purplei2p/i2pd/I2PDActivity.java index 777ca748..0801a655 100755 --- a/android/src/org/purplei2p/i2pd/I2PDActivity.java +++ b/android/src/org/purplei2p/i2pd/I2PDActivity.java @@ -1,13 +1,5 @@ package org.purplei2p.i2pd; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.BufferedReader; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Timer; @@ -24,7 +16,6 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; -import android.content.res.AssetManager; import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.Network; @@ -33,7 +24,6 @@ import android.net.NetworkRequest; import android.net.Uri; import android.os.Bundle; import android.os.Build; -import android.os.Environment; import android.os.IBinder; import android.os.PowerManager; import android.preference.PreferenceManager; @@ -60,25 +50,19 @@ import android.webkit.WebViewClient; import static android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS; public class I2PDActivity extends Activity { - private WebView webView; - private static final String TAG = "i2pdActvt"; private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1; public static final int GRACEFUL_DELAY_MILLIS = 10 * 60 * 1000; public static final String PACKAGE_URI_SCHEME = "package:"; private TextView textView; - private boolean assetsCopied; - private NetworkStateCallback networkCallback; - private String i2pdpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd/"; - //private ConfigParser parser = new ConfigParser(i2pdpath); // TODO: + //private ConfigParser parser = new ConfigParser(i2pdpath); // TODO - private static final DaemonSingleton daemon = DaemonSingleton.getInstance(); + private static volatile DaemonWrapper daemon; - private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = new DaemonSingleton.StateUpdateListener() { + private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = new DaemonWrapper.StateUpdateListener() { @Override public void daemonStateUpdate() { - processAssets(); runOnUiThread(() -> { try { if (textView == null) @@ -88,9 +72,9 @@ public class I2PDActivity extends Activity { textView.setText(throwableToString(tr)); return; } - DaemonSingleton.State state = daemon.getState(); - String startResultStr = DaemonSingleton.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : ""; - String graceStr = DaemonSingleton.State.gracefulShutdownInProgress.equals(state) ? String.format(": %s %s", formatGraceTimeRemaining(), getText(R.string.remaining)) : ""; + DaemonWrapper.State state = daemon.getState(); + String startResultStr = DaemonWrapper.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : ""; + String graceStr = DaemonWrapper.State.gracefulShutdownInProgress.equals(state) ? String.format(": %s %s", formatGraceTimeRemaining(), getText(R.string.remaining)) : ""; textView.setText(String.format("%s%s%s", getText(state.getStatusStringResourceId()), startResultStr, graceStr)); } catch (Throwable tr) { Log.e(TAG,"error ignored",tr); @@ -117,6 +101,12 @@ public class I2PDActivity extends Activity { Log.i(TAG, "onCreate"); super.onCreate(savedInstanceState); + if (daemon==null) { + ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + daemon = new DaemonWrapper(getAssets(), connectivityManager); + } + ForegroundService.init(daemon); + textView = new TextView(this); setContentView(textView); daemon.addStateChangeListener(daemonStateUpdatedListener); @@ -145,15 +135,13 @@ public class I2PDActivity extends Activity { openBatteryOptimizationDialogIfNeeded(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - registerNetworkCallback(); - } } @Override protected void onDestroy() { super.onDestroy(); textView = null; + ForegroundService.deinit(); daemon.removeStateChangeListener(daemonStateUpdatedListener); //cancelGracefulStop0(); try { @@ -289,13 +277,13 @@ public class I2PDActivity extends Activity { case R.id.action_start_webview: setContentView(R.layout.webview); - this.webView = (WebView) findViewById(R.id.webview1); - this.webView.setWebViewClient(new WebViewClient()); + final WebView webView = findViewById(R.id.webview1); + webView.setWebViewClient(new WebViewClient()); - WebSettings webSettings = this.webView.getSettings(); + final WebSettings webSettings = webView.getSettings(); webSettings.setBuiltInZoomControls(true); webSettings.setJavaScriptEnabled(false); - this.webView.loadUrl("http://127.0.0.1:7070"); // TODO: instead 7070 I2Pd....HttpPort + webView.loadUrl("http://127.0.0.1:7070"); // TODO: instead 7070 I2Pd....HttpPort break; } @@ -335,7 +323,7 @@ public class I2PDActivity extends Activity { private static volatile Timer gracefulQuitTimer; private void i2pdGracefulStop() { - if (daemon.getState() == DaemonSingleton.State.stopped) { + if (daemon.getState() == DaemonWrapper.State.stopped) { Toast.makeText(this, R.string.already_stopped, Toast.LENGTH_SHORT).show(); return; } @@ -427,167 +415,6 @@ public class I2PDActivity extends Activity { }); } - /** - * Copy the asset at the specified path to this app's data directory. If the - * asset is a directory, its contents are also copied. - * - * @param path - * Path to asset, relative to app's assets directory. - */ - private void copyAsset(String path) { - AssetManager manager = getAssets(); - - // If we have a directory, we make it and recurse. If a file, we copy its - // contents. - try { - String[] contents = manager.list(path); - - // The documentation suggests that list throws an IOException, but doesn't - // say under what conditions. It'd be nice if it did so when the path was - // to a file. That doesn't appear to be the case. If the returned array is - // null or has 0 length, we assume the path is to a file. This means empty - // directories will get turned into files. - if (contents == null || contents.length == 0) { - copyFileAsset(path); - return; - } - - // Make the directory. - File dir = new File(i2pdpath, path); - boolean result = dir.mkdirs(); - Log.d(TAG, "dir.mkdirs() returned " + result); - - // Recurse on the contents. - for (String entry : contents) { - copyAsset(path + '/' + entry); - } - } catch (IOException e) { - Log.e(TAG, "ex ignored for path='" + path + "'", e); - } - } - - /** - * Copy the asset file specified by path to app's data directory. Assumes - * parent directories have already been created. - * - * @param path - * Path to asset, relative to app's assets directory. - */ - private void copyFileAsset(String path) { - File file = new File(i2pdpath, path); - if (!file.exists()) { - try { - try (InputStream in = getAssets().open(path)) { - try (OutputStream out = new FileOutputStream(file)) { - byte[] buffer = new byte[1024]; - int read = in.read(buffer); - while (read != -1) { - out.write(buffer, 0, read); - read = in.read(buffer); - } - } - } - } catch (IOException e) { - Log.e(TAG, "", e); - } - } - } - - private void deleteRecursive(File fileOrDirectory) { - if (fileOrDirectory.isDirectory()) { - File[] files = fileOrDirectory.listFiles(); - if (files != null) { - for (File child : files) { - deleteRecursive(child); - } - } - } - boolean deleteResult = fileOrDirectory.delete(); - if (!deleteResult) - Log.e(TAG, "fileOrDirectory.delete() returned " + deleteResult + ", absolute path='" + fileOrDirectory.getAbsolutePath() + "'"); - } - - private void processAssets() { - if (!assetsCopied) { - try { - assetsCopied = true; // prevent from running on every state update - - File holderFile = new File(i2pdpath, "assets.ready"); - String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX - StringBuilder text = new StringBuilder(); - - if (holderFile.exists()) { - try { // if holder file exists, read assets version string - FileReader fileReader = new FileReader(holderFile); - - try { - BufferedReader br = new BufferedReader(fileReader); - - try { - String line; - - while ((line = br.readLine()) != null) { - text.append(line); - } - }finally { - try { - br.close(); - } catch (IOException e) { - Log.e(TAG, "", e); - } - } - } finally { - try { - fileReader.close(); - } catch (IOException e) { - Log.e(TAG, "", e); - } - } - } catch (IOException e) { - Log.e(TAG, "", e); - } - } - - // if version differs from current app version or null, try to delete certificates folder - if (!text.toString().contains(versionName)) - try { - boolean deleteResult = holderFile.delete(); - if (!deleteResult) - Log.e(TAG, "holderFile.delete() returned " + deleteResult + ", absolute path='" + holderFile.getAbsolutePath() + "'"); - File certPath = new File(i2pdpath, "certificates"); - deleteRecursive(certPath); - } - catch (Throwable tr) { - Log.e(TAG, "", tr); - } - - // copy assets. If processed file exists, it won't be overwritten - copyAsset("addressbook"); - copyAsset("certificates"); - copyAsset("tunnels.d"); - copyAsset("i2pd.conf"); - copyAsset("subscriptions.txt"); - copyAsset("tunnels.conf"); - - // update holder file about successful copying - FileWriter writer = new FileWriter(holderFile); - try { - writer.append(versionName); - } finally { - try { - writer.close(); - } catch (IOException e) { - Log.e(TAG,"on writer close", e); - } - } - } - catch (Throwable tr) - { - Log.e(TAG,"on assets copying", tr); - } - } - } - @SuppressLint("BatteryLife") private void openBatteryOptimizationDialogIfNeeded() { boolean questionEnabled = getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true); @@ -642,33 +469,6 @@ public class I2PDActivity extends Activity { return "show_battery_optimization" + (device == null ? "" : device); } - @TargetApi(Build.VERSION_CODES.M) - private void registerNetworkCallback() { - ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkRequest request = new NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - .build(); - networkCallback = new NetworkStateCallback(); - connectivityManager.registerNetworkCallback(request, networkCallback); - } - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - private final class NetworkStateCallback extends ConnectivityManager.NetworkCallback { - @Override - public void onAvailable(Network network) { - super.onAvailable(network); - I2PD_JNI.onNetworkStateChanged(true); - Log.i(TAG, "NetworkCallback.onAvailable"); - } - - @Override - public void onLost(Network network) { - super.onLost(network); - I2PD_JNI.onNetworkStateChanged(false); - Log.i(TAG, " NetworkCallback.onLost"); - } - } - private void quit() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { From db3e48a81a87c8ce70e727f815c011c7a3f62d52 Mon Sep 17 00:00:00 2001 From: user Date: Sat, 24 Oct 2020 03:52:53 +0800 Subject: [PATCH 3857/6300] android: more logical daemon state changes --- .../src/org/purplei2p/i2pd/DaemonWrapper.java | 41 +++---- .../org/purplei2p/i2pd/ForegroundService.java | 111 ++++++++++-------- .../src/org/purplei2p/i2pd/I2PDActivity.java | 53 +++++---- 3 files changed, 104 insertions(+), 101 deletions(-) diff --git a/android/src/org/purplei2p/i2pd/DaemonWrapper.java b/android/src/org/purplei2p/i2pd/DaemonWrapper.java index 7f9fcd93..414a3ed1 100644 --- a/android/src/org/purplei2p/i2pd/DaemonWrapper.java +++ b/android/src/org/purplei2p/i2pd/DaemonWrapper.java @@ -12,7 +12,6 @@ import java.util.HashSet; import java.util.Set; import android.annotation.TargetApi; -import android.content.Context; import android.content.res.AssetManager; import android.net.ConnectivityManager; import android.net.Network; @@ -32,7 +31,7 @@ public class DaemonWrapper { private boolean assetsCopied; public interface StateUpdateListener { - void daemonStateUpdate(); + void daemonStateUpdate(State oldValue, State newValue); } private final Set stateUpdateListeners = new HashSet<>(); @@ -58,7 +57,7 @@ public class DaemonWrapper { return; state = newState; - fireStateUpdate1(); + fireStateUpdate1(oldState, newState); } public synchronized void stopAcceptingTunnels() { @@ -81,12 +80,10 @@ public class DaemonWrapper { } } - public synchronized int GetTransitTunnelsCount() { + public int getTransitTunnelsCount() { return I2PD_JNI.GetTransitTunnelsCount(); } - private volatile boolean startedOkay; - public enum State { uninitialized(R.string.uninitialized), starting(R.string.starting), @@ -105,7 +102,11 @@ public class DaemonWrapper { public int getStatusStringResourceId() { return statusStringResourceId; } - }; + + public boolean isStartedOkay() { + return equals(State.startedOkay) || equals(State.gracefulShutdownInProgress); + } + } private volatile State state = State.uninitialized; @@ -134,7 +135,6 @@ public class DaemonWrapper { daemonStartResult = I2PD_JNI.startDaemon(); if ("ok".equals(daemonStartResult)) { setState(State.startedOkay); - setStartedOkay(true); } else setState(State.startFailed); } @@ -148,11 +148,11 @@ public class DaemonWrapper { private Throwable lastThrowable; private String daemonStartResult = "N/A"; - private void fireStateUpdate1() { + private void fireStateUpdate1(State oldValue, State newValue) { Log.i(TAG, "daemon state change: " + state); for (StateUpdateListener listener : stateUpdateListeners) { try { - listener.daemonStateUpdate(); + listener.daemonStateUpdate(oldValue, newValue); } catch (Throwable tr) { Log.e(TAG, "exception in listener ignored", tr); } @@ -167,18 +167,8 @@ public class DaemonWrapper { 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; - } + return getState().isStartedOkay(); } public synchronized void stopDaemon() { @@ -189,7 +179,6 @@ public class DaemonWrapper { Log.e(TAG, "", tr); } - setStartedOkay(false); setState(State.stopped); } } @@ -197,7 +186,7 @@ public class DaemonWrapper { private void processAssets() { if (!assetsCopied) { try { - assetsCopied = true; // prevent from running on every state update + assetsCopied = true; File holderFile = new File(i2pdpath, "assets.ready"); String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX @@ -283,12 +272,10 @@ public class DaemonWrapper { * Path to asset, relative to app's assets directory. */ private void copyAsset(String path) { - AssetManager manager = assetManager; - // If we have a directory, we make it and recurse. If a file, we copy its // contents. try { - String[] contents = manager.list(path); + String[] contents = assetManager.list(path); // The documentation suggests that list throws an IOException, but doesn't // say under what conditions. It'd be nice if it did so when the path was @@ -355,7 +342,7 @@ public class DaemonWrapper { Log.e(TAG, "fileOrDirectory.delete() returned " + deleteResult + ", absolute path='" + fileOrDirectory.getAbsolutePath() + "'"); } - public void registerNetworkCallback(){ + private void registerNetworkCallback(){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) registerNetworkCallback0(); } diff --git a/android/src/org/purplei2p/i2pd/ForegroundService.java b/android/src/org/purplei2p/i2pd/ForegroundService.java index c1c918ac..c97b7f1f 100644 --- a/android/src/org/purplei2p/i2pd/ForegroundService.java +++ b/android/src/org/purplei2p/i2pd/ForegroundService.java @@ -23,22 +23,28 @@ public class ForegroundService extends Service { private static volatile DaemonWrapper daemon; + private static final Object initDeinitLock = new Object(); + private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = new DaemonWrapper.StateUpdateListener() { @Override - public void daemonStateUpdate() { - try { - synchronized (ForegroundService.this) { - if (shown) cancelNotification(); - showNotification(); - } - } catch (Throwable tr) { - Log.e(TAG,"error ignored",tr); - } + public void daemonStateUpdate(DaemonWrapper.State oldValue, DaemonWrapper.State newValue) { + updateNotificationText(); } }; + private void updateNotificationText() { + try { + synchronized (initDeinitLock) { + if (shown) cancelNotification(); + showNotification(); + } + } catch (Throwable tr) { + Log.e(TAG,"error ignored",tr); + } + } + private NotificationManager notificationManager; @@ -63,7 +69,9 @@ public class ForegroundService extends Service { } private static void initCheck() { - if(instance!=null && daemon!=null) instance.setListener(); + synchronized (initDeinitLock) { + if (instance != null && daemon != null) instance.setListener(); + } } @Override @@ -75,7 +83,7 @@ public class ForegroundService extends Service { private void setListener() { daemon.addStateChangeListener(daemonStateUpdatedListener); - if (!shown) daemonStateUpdatedListener.daemonStateUpdate(); + updateNotificationText(); } @Override @@ -96,18 +104,23 @@ public class ForegroundService extends Service { } private static void deinitCheck() { - if(daemon!=null && instance!=null)daemon.removeStateChangeListener(instance.daemonStateUpdatedListener); + synchronized (initDeinitLock) { + if (daemon != null && instance != null) + daemon.removeStateChangeListener(instance.daemonStateUpdatedListener); + } } - private synchronized void cancelNotification() { - // Cancel the persistent notification. - notificationManager.cancel(NOTIFICATION); + private void cancelNotification() { + synchronized (initDeinitLock) { + // Cancel the persistent notification. + notificationManager.cancel(NOTIFICATION); - stopForeground(true); + stopForeground(true); - // Tell the user we stopped. - //Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show(); - shown=false; + // Tell the user we stopped. + //Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show(); + shown = false; + } } @Override @@ -122,39 +135,41 @@ public class ForegroundService extends Service { /** * Show a notification while this service is running. */ - private synchronized void showNotification() { - if(daemon!=null) { - // In this sample, we'll use the same text for the ticker and the expanded notification - CharSequence text = getText(daemon.getState().getStatusStringResourceId()); + private void showNotification() { + synchronized (initDeinitLock) { + if (daemon != null) { + // In this sample, we'll use the same text for the ticker and the expanded notification + CharSequence text = getText(daemon.getState().getStatusStringResourceId()); - // The PendingIntent to launch our activity if the user selects this notification - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, - new Intent(this, I2PDActivity.class), 0); + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, I2PDActivity.class), 0); - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : ""; + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : ""; - // Set the info for the views that show in the notification panel. - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId) - .setOngoing(true) - .setSmallIcon(R.drawable.itoopie_notification_icon); // the status icon - if (Build.VERSION.SDK_INT >= 16) - builder = builder.setPriority(Notification.PRIORITY_DEFAULT); - if (Build.VERSION.SDK_INT >= 21) - builder = builder.setCategory(Notification.CATEGORY_SERVICE); - Notification notification = builder - .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(); + // Set the info for the views that show in the notification panel. + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId) + .setOngoing(true) + .setSmallIcon(R.drawable.itoopie_notification_icon); // the status icon + if (Build.VERSION.SDK_INT >= 16) + builder = builder.setPriority(Notification.PRIORITY_DEFAULT); + if (Build.VERSION.SDK_INT >= 21) + builder = builder.setCategory(Notification.CATEGORY_SERVICE); + Notification notification = builder + .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); - shown = true; + // Send the notification. + //mNM.notify(NOTIFICATION, notification); + startForeground(NOTIFICATION, notification); + shown = true; + } } } diff --git a/android/src/org/purplei2p/i2pd/I2PDActivity.java b/android/src/org/purplei2p/i2pd/I2PDActivity.java index 0801a655..97870e63 100755 --- a/android/src/org/purplei2p/i2pd/I2PDActivity.java +++ b/android/src/org/purplei2p/i2pd/I2PDActivity.java @@ -7,7 +7,6 @@ import java.util.TimerTask; import android.Manifest; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; @@ -18,9 +17,6 @@ import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkRequest; import android.net.Uri; import android.os.Bundle; import android.os.Build; @@ -36,7 +32,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; @@ -62,26 +57,31 @@ public class I2PDActivity extends Activity { private final DaemonWrapper.StateUpdateListener daemonStateUpdatedListener = new DaemonWrapper.StateUpdateListener() { @Override - public void daemonStateUpdate() { - runOnUiThread(() -> { - try { - if (textView == null) - return; - Throwable tr = daemon.getLastThrowable(); - if (tr!=null) { - textView.setText(throwableToString(tr)); - return; - } - DaemonWrapper.State state = daemon.getState(); - String startResultStr = DaemonWrapper.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : ""; - String graceStr = DaemonWrapper.State.gracefulShutdownInProgress.equals(state) ? String.format(": %s %s", formatGraceTimeRemaining(), getText(R.string.remaining)) : ""; - textView.setText(String.format("%s%s%s", getText(state.getStatusStringResourceId()), startResultStr, graceStr)); - } catch (Throwable tr) { - Log.e(TAG,"error ignored",tr); - } - }); + public void daemonStateUpdate(DaemonWrapper.State oldValue, DaemonWrapper.State newValue) { + updateStatusText(); } }; + + private void updateStatusText() { + runOnUiThread(() -> { + try { + if (textView == null) + return; + Throwable tr = daemon.getLastThrowable(); + if (tr!=null) { + textView.setText(throwableToString(tr)); + return; + } + DaemonWrapper.State state = daemon.getState(); + String startResultStr = DaemonWrapper.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : ""; + String graceStr = DaemonWrapper.State.gracefulShutdownInProgress.equals(state) ? String.format(": %s %s", formatGraceTimeRemaining(), getText(R.string.remaining)) : ""; + textView.setText(String.format("%s%s%s", getText(state.getStatusStringResourceId()), startResultStr, graceStr)); + } catch (Throwable tr) { + Log.e(TAG,"error ignored",tr); + } + }); + } + private static volatile long graceStartedMillis; private static final Object graceStartedMillis_LOCK = new Object(); private Menu optionsMenu; @@ -110,7 +110,7 @@ public class I2PDActivity extends Activity { textView = new TextView(this); setContentView(textView); daemon.addStateChangeListener(daemonStateUpdatedListener); - daemonStateUpdatedListener.daemonStateUpdate(); + daemonStateUpdatedListener.daemonStateUpdate(DaemonWrapper.State.uninitialized, daemon.getState()); // request permissions if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -372,9 +372,10 @@ public class I2PDActivity extends Activity { if (gracefulQuitTimerOld != null) gracefulQuitTimerOld.cancel(); - if(daemon.GetTransitTunnelsCount() <= 0) { // no tunnels left + if(daemon.getTransitTunnelsCount() <= 0) { // no tunnels left Log.d(TAG, "no transit tunnels left, stopping"); i2pdStop(); + return; } final Timer gracefulQuitTimer = new Timer(true); @@ -390,7 +391,7 @@ public class I2PDActivity extends Activity { final TimerTask tickerTask = new TimerTask() { @Override public void run() { - daemonStateUpdatedListener.daemonStateUpdate(); + updateStatusText(); } }; gracefulQuitTimer.scheduleAtFixedRate(tickerTask, 0/*start delay*/, 1000/*millis period*/); From c93ee0d65d9dfa8318b8919d00b2b7f8adf34fe1 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 23 Oct 2020 15:53:22 -0400 Subject: [PATCH 3858/6300] tunnels through ECIES routers --- libi2pd/I2NPProtocol.h | 19 +++++++++ libi2pd/Timestamp.cpp | 33 ++++++++++----- libi2pd/Timestamp.h | 3 +- libi2pd/Tunnel.cpp | 22 ++++++++-- libi2pd/TunnelConfig.cpp | 88 +++++++++++++++++++++++++++++++--------- libi2pd/TunnelConfig.h | 11 +++-- 6 files changed, 137 insertions(+), 39 deletions(-) diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index fe5ca968..03f0f439 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -81,6 +81,25 @@ namespace i2p const size_t BUILD_RESPONSE_RECORD_PADDING_SIZE = 495; const size_t BUILD_RESPONSE_RECORD_RET_OFFSET = BUILD_RESPONSE_RECORD_PADDING_OFFSET + BUILD_RESPONSE_RECORD_PADDING_SIZE; + // ECIES BuildRequestRecordClearText + const size_t ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; + const size_t ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; + const size_t ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET = ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4; + const size_t ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET = ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32; + const size_t ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET = ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET + 32; + const size_t ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET = ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET + 32; + const size_t ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET = ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET + 32; + const size_t ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET = ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET + 16; + const size_t ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET = ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET + 1; + const size_t ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET = ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET + 3; + const size_t ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET = ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; + const size_t ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET = ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET + 4; + const size_t ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET = ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; + const size_t ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE = 464; + + // ECIES BuildResponseRecord + const size_t ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET = 511; + enum I2NPMessageType { eI2NPDummyMsg = 0, diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index 4362a878..45684333 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -35,18 +35,24 @@ namespace util std::chrono::system_clock::now().time_since_epoch()).count (); } - static uint32_t GetLocalHoursSinceEpoch () - { - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count (); - } - static uint64_t GetLocalSecondsSinceEpoch () { return std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count (); } + static uint32_t GetLocalMinutesSinceEpoch () + { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count (); + } + + static uint32_t GetLocalHoursSinceEpoch () + { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count (); + } + static int64_t g_TimeOffset = 0; // in seconds static void SyncTimeWithNTP (const std::string& address) @@ -178,14 +184,19 @@ namespace util return GetLocalMillisecondsSinceEpoch () + g_TimeOffset*1000; } - uint32_t GetHoursSinceEpoch () - { - return GetLocalHoursSinceEpoch () + g_TimeOffset/3600; - } - uint64_t GetSecondsSinceEpoch () { return GetLocalSecondsSinceEpoch () + g_TimeOffset; + } + + uint32_t GetMinutesSinceEpoch () + { + return GetLocalMinutesSinceEpoch () + g_TimeOffset/60; + } + + uint32_t GetHoursSinceEpoch () + { + return GetLocalHoursSinceEpoch () + g_TimeOffset/3600; } void GetCurrentDate (char * date) diff --git a/libi2pd/Timestamp.h b/libi2pd/Timestamp.h index 91175a49..b46f423d 100644 --- a/libi2pd/Timestamp.h +++ b/libi2pd/Timestamp.h @@ -20,8 +20,9 @@ namespace i2p namespace util { uint64_t GetMillisecondsSinceEpoch (); - uint32_t GetHoursSinceEpoch (); uint64_t GetSecondsSinceEpoch (); + uint32_t GetMinutesSinceEpoch (); + uint32_t GetHoursSinceEpoch (); void GetCurrentDate (char * date); // returns date as YYYYMMDD string, 9 bytes void GetDateString (uint64_t timestamp, char * date); // timestap is seconds since epoch, returns date as YYYYMMDD string, 9 bytes diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index c91d0215..bfe466e3 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -111,7 +111,7 @@ namespace tunnel while (hop) { decryption.SetKey (hop->replyKey); - // decrypt records before and including current hop + // decrypt records before and current hop TunnelHopConfig * hop1 = hop; while (hop1) { @@ -119,8 +119,22 @@ namespace tunnel if (idx >= 0 && idx < msg[0]) { uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE; - decryption.SetIV (hop->replyIV); - decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); + if (hop1 == hop && hop1->IsECIES ()) + { + uint8_t nonce[12]; + memset (nonce, 0, 12); + if (!i2p::crypto::AEADChaCha20Poly1305 (record, TUNNEL_BUILD_RECORD_SIZE - 16, + hop->h, 32, hop->ck, nonce, record, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt + { + LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); + return false; + } + } + else + { + decryption.SetIV (hop->replyIV); + decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); + } } else LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); @@ -134,7 +148,7 @@ namespace tunnel while (hop) { const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE; - uint8_t ret = record[BUILD_RESPONSE_RECORD_RET_OFFSET]; + uint8_t ret = record[hop->IsECIES () ? ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET : BUILD_RESPONSE_RECORD_RET_OFFSET]; LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); if (profile) diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 3b7955d4..00f2a091 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -9,6 +9,9 @@ #include #include +#include +#include "Crypto.h" +#include "Log.h" #include "Transports.h" #include "Timestamp.h" #include "I2PEndian.h" @@ -76,39 +79,86 @@ namespace tunnel } } - void TunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) const + void TunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) { - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); - htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); uint8_t flag = 0; if (isGateway) flag |= 0x80; if (isEndpoint) flag |= 0x40; - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; - htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); - htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); - RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); auto encryptor = ident->CreateEncryptor (nullptr); - if (encryptor) + if (IsECIES ()) { - if (ident->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; + memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, i2p::util::GetSecondsSinceEpoch () + 600); // 10 minutes + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); + memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); + if (encryptor) EncryptECIES (encryptor, clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx); - else + } + else + { + uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); + memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); + htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); + memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); + clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; + htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); + htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); + RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); + if (encryptor) encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); } memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } void TunnelHopConfig::EncryptECIES (std::shared_ptr& encryptor, - const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx) const + const uint8_t * plainText, uint8_t * encrypted, BN_CTX * ctx) { - memset (encrypted, 0, 512); // TODO: implement + static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars + memcpy (ck, protocolName, 32); // ck = h = protocol_name || 0 + SHA256 (ck, 32, h); // h = SHA256(h); + uint8_t hepk[32]; + encryptor->Encrypt (nullptr, hepk, nullptr, false); + MixHash (hepk, 32); + auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + memcpy (encrypted, ephemeralKeys->GetPublicKey (), 32); encrypted += 32; + uint8_t sharedSecret[32]; + ephemeralKeys->Agree (hepk, sharedSecret); // x25519(sesk, hepk) + uint8_t keydata[64]; + i2p::crypto::HKDF (ck, sharedSecret, 32, "", keydata); + memcpy (ck, keydata, 32); + uint8_t nonce[12]; + memset (nonce, 0, 12); + if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, h, 32, + keydata + 32, nonce, encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Tunnel: Plaintext AEAD encryption failed"); + return; + } + MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) } + + void TunnelHopConfig::MixHash (const uint8_t * buf, size_t len) + { + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, h, 32); + SHA256_Update (&ctx, buf, len); + SHA256_Final (h, &ctx); + } } } \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 24e6f836..0e757071 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -30,17 +30,20 @@ namespace tunnel TunnelHopConfig * next, * prev; int recordIndex; // record # in tunnel build message - + uint8_t ck[32], h[32]; // for ECIES + TunnelHopConfig (std::shared_ptr r); - + void SetNextIdent (const i2p::data::IdentHash& ident); void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent); void SetNext (TunnelHopConfig * n); void SetPrev (TunnelHopConfig * p); - void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) const; + bool IsECIES () const { return ident->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET; }; + void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); void EncryptECIES (std::shared_ptr& encryptor, - const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx) const; + const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx); + void MixHash (const uint8_t * buf, size_t len); }; class TunnelConfig From ef5495bfb2626a42c7d4dce870c03afe18c42430 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 23 Oct 2020 22:14:00 -0400 Subject: [PATCH 3859/6300] padding for x25519 crypto key --- libi2pd/Identity.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index b2b5f2b4..490b8692 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -48,7 +48,13 @@ namespace data IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type, CryptoKeyType cryptoType) { - memcpy (m_StandardIdentity.publicKey, publicKey, 256); // publicKey in awlays assumed 256 regardless actual size, padding must be taken care of + if (cryptoType == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + { + memcpy (m_StandardIdentity.publicKey, publicKey, 32); + RAND_bytes (m_StandardIdentity.publicKey, 224); + } + else + memcpy (m_StandardIdentity.publicKey, publicKey, 256); if (type != SIGNING_KEY_TYPE_DSA_SHA1) { size_t excessLen = 0; From bfcf3cfbf132ff904bcfca6557da061db4090bd5 Mon Sep 17 00:00:00 2001 From: user Date: Sat, 24 Oct 2020 12:40:22 +0800 Subject: [PATCH 3860/6300] Fixes #1563 --- android/AndroidManifest.xml | 11 ++--- android/build.gradle | 4 +- .../{webview.xml => activity_web_console.xml} | 4 +- .../src/org/purplei2p/i2pd/I2PDActivity.java | 11 +---- .../purplei2p/i2pd/WebConsoleActivity.java | 41 +++++++++++++++++++ 5 files changed, 55 insertions(+), 16 deletions(-) rename android/res/layout/{webview.xml => activity_web_console.xml} (82%) create mode 100644 android/src/org/purplei2p/i2pd/WebConsoleActivity.java diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 88985138..8b011b8e 100755 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -15,10 +15,10 @@ android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/app_name" - android:theme="@android:style/Theme.Holo.Light.DarkActionBar" android:requestLegacyExternalStorage="true" - android:usesCleartextTraffic="true" - > + android:theme="@android:style/Theme.Holo.Light.DarkActionBar" + android:usesCleartextTraffic="true"> + @@ -31,10 +31,10 @@ android:label="@string/app_name"> + - @@ -52,4 +52,5 @@ android:value="org.purplei2p.i2pd.I2PDPermsAskerActivity" /> - + + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index ace2047a..7d25e28b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -21,6 +21,8 @@ repositories { dependencies { implementation 'androidx.core:core:1.0.2' + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' } android { @@ -90,7 +92,7 @@ android { } } -ext.abiCodes = ['armeabi-v7a':1, 'x86':2, 'arm64-v8a':3, 'x86_64':4] +ext.abiCodes = ['armeabi-v7a': 1, 'x86': 2, 'arm64-v8a': 3, 'x86_64': 4] import com.android.build.OutputFile android.applicationVariants.all { variant -> diff --git a/android/res/layout/webview.xml b/android/res/layout/activity_web_console.xml similarity index 82% rename from android/res/layout/webview.xml rename to android/res/layout/activity_web_console.xml index 887896a7..5724b387 100644 --- a/android/res/layout/webview.xml +++ b/android/res/layout/activity_web_console.xml @@ -1,12 +1,14 @@ + + tools:context=".WebConsoleActivity"> + diff --git a/android/src/org/purplei2p/i2pd/I2PDActivity.java b/android/src/org/purplei2p/i2pd/I2PDActivity.java index 97870e63..9645d4f7 100755 --- a/android/src/org/purplei2p/i2pd/I2PDActivity.java +++ b/android/src/org/purplei2p/i2pd/I2PDActivity.java @@ -276,15 +276,8 @@ public class I2PDActivity extends Activity { return true; case R.id.action_start_webview: - setContentView(R.layout.webview); - final WebView webView = findViewById(R.id.webview1); - webView.setWebViewClient(new WebViewClient()); - - final WebSettings webSettings = webView.getSettings(); - webSettings.setBuiltInZoomControls(true); - webSettings.setJavaScriptEnabled(false); - webView.loadUrl("http://127.0.0.1:7070"); // TODO: instead 7070 I2Pd....HttpPort - break; + startActivity(new Intent(getApplicationContext(), WebConsoleActivity.class)); + return true; } return super.onOptionsItemSelected(item); diff --git a/android/src/org/purplei2p/i2pd/WebConsoleActivity.java b/android/src/org/purplei2p/i2pd/WebConsoleActivity.java new file mode 100644 index 00000000..24e6baeb --- /dev/null +++ b/android/src/org/purplei2p/i2pd/WebConsoleActivity.java @@ -0,0 +1,41 @@ +package org.purplei2p.i2pd; + +import androidx.appcompat.app.AppCompatActivity; + +import android.app.Activity; +import android.os.Bundle; +import android.view.MenuItem; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import java.util.Objects; + +public class WebConsoleActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_web_console); + + Objects.requireNonNull(getActionBar()).setDisplayHomeAsUpEnabled(true); + + final WebView webView = findViewById(R.id.webview1); + webView.setWebViewClient(new WebViewClient()); + + final WebSettings webSettings = webView.getSettings(); + webSettings.setBuiltInZoomControls(true); + webSettings.setJavaScriptEnabled(false); + webView.loadUrl("http://127.0.0.1:7070"); // TODO: instead 7070 I2Pd....HttpPort + } + + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id==android.R.id.home) { + finish(); + return true; + } + return false; + } +} From 21d99e355c2c70c2195985393ec81e81a200d6be Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 24 Oct 2020 15:48:56 -0400 Subject: [PATCH 3861/6300] MixHash(sepk) added --- libi2pd/TunnelConfig.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 00f2a091..5c2cbd53 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -98,7 +98,7 @@ namespace tunnel clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, i2p::util::GetSecondsSinceEpoch () + 600); // 10 minutes + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, 600); // +10 minutes htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); if (encryptor) @@ -133,9 +133,11 @@ namespace tunnel SHA256 (ck, 32, h); // h = SHA256(h); uint8_t hepk[32]; encryptor->Encrypt (nullptr, hepk, nullptr, false); - MixHash (hepk, 32); + MixHash (hepk, 32); // h = SHA256(h || hepk) auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); - memcpy (encrypted, ephemeralKeys->GetPublicKey (), 32); encrypted += 32; + memcpy (encrypted, ephemeralKeys->GetPublicKey (), 32); + MixHash (encrypted, 32); // h = SHA256(h || sepk) + encrypted += 32; uint8_t sharedSecret[32]; ephemeralKeys->Agree (hepk, sharedSecret); // x25519(sesk, hepk) uint8_t keydata[64]; From e9f11e204e02314c455da391940d33a2834e7be2 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 24 Oct 2020 21:22:48 -0400 Subject: [PATCH 3862/6300] check if session is terminated before send --- libi2pd_client/I2CP.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 80274d86..d5436936 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -806,17 +806,23 @@ namespace client void I2CPSession::SendMessagePayloadMessage (const uint8_t * payload, size_t len) { // we don't use SendI2CPMessage to eliminate additional copy - auto l = len + 10 + I2CP_HEADER_SIZE; - uint8_t * buf = new uint8_t[l]; - htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 10); - buf[I2CP_HEADER_TYPE_OFFSET] = I2CP_MESSAGE_PAYLOAD_MESSAGE; - htobe16buf (buf + I2CP_HEADER_SIZE, m_SessionID); - htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++); - htobe32buf (buf + I2CP_HEADER_SIZE + 6, len); - memcpy (buf + I2CP_HEADER_SIZE + 10, payload, len); - boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), - std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, buf)); + auto socket = m_Socket; + if (socket) + { + auto l = len + 10 + I2CP_HEADER_SIZE; + uint8_t * buf = new uint8_t[l]; + htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 10); + buf[I2CP_HEADER_TYPE_OFFSET] = I2CP_MESSAGE_PAYLOAD_MESSAGE; + htobe16buf (buf + I2CP_HEADER_SIZE, m_SessionID); + htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++); + htobe32buf (buf + I2CP_HEADER_SIZE + 6, len); + memcpy (buf + I2CP_HEADER_SIZE + 10, payload, len); + boost::asio::async_write (*socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), + std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), + std::placeholders::_1, std::placeholders::_2, buf)); + } + else + LogPrint (eLogError, "I2CP: Can't write to the socket"); } I2CPServer::I2CPServer (const std::string& interface, int port, bool isSingleThread): From b35f43d79e48116e18931c994a416497e3107924 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 25 Oct 2020 17:20:15 -0400 Subject: [PATCH 3863/6300] initial implementation of STREAM FORWARD --- libi2pd_client/SAM.cpp | 92 ++++++++++++++++++++++++++++++++++++++---- libi2pd_client/SAM.h | 9 ++++- 2 files changed, 92 insertions(+), 9 deletions(-) diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 63b9c7ed..2bdbf1ee 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -54,6 +54,7 @@ namespace client break; } case eSAMSocketTypeAcceptor: + case eSAMSocketTypeForward: { if (Session) { @@ -263,6 +264,8 @@ namespace client ProcessStreamConnect (separator + 1, bytes_transferred - (separator - m_Buffer) - 1, bytes_transferred - (eol - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_STREAM_ACCEPT)) ProcessStreamAccept (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); + else if (!strcmp (m_Buffer, SAM_STREAM_FORWARD)) + ProcessStreamForward (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_DEST_GENERATE)) ProcessDestGenerate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP)) @@ -358,12 +361,12 @@ namespace client std::shared_ptr forward = nullptr; if ((type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) && - params.find(SAM_VALUE_HOST) != params.end() && params.find(SAM_VALUE_PORT) != params.end()) + params.find(SAM_PARAM_HOST) != params.end() && params.find(SAM_PARAM_PORT) != params.end()) { // udp forward selected boost::system::error_code e; // TODO: support hostnames in udp forward - auto addr = boost::asio::ip::address::from_string(params[SAM_VALUE_HOST], e); + auto addr = boost::asio::ip::address::from_string(params[SAM_PARAM_HOST], e); if (e) { // not an ip address @@ -371,7 +374,7 @@ namespace client return; } - auto port = std::stoi(params[SAM_VALUE_PORT]); + auto port = std::stoi(params[SAM_PARAM_PORT]); if (port == -1) { SendI2PError("Invalid port"); @@ -565,6 +568,51 @@ namespace client SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } + void SAMSocket::ProcessStreamForward (char * buf, size_t len) + { + LogPrint (eLogDebug, "SAM: stream forward: ", buf); + std::map params; + ExtractParams (buf, params); + std::string& id = params[SAM_PARAM_ID]; + auto session = m_Owner.FindSession (id); + if (!session) + { + SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); + return; + } + if (session->localDestination->IsAcceptingStreams ()) + { + SendI2PError ("Already accepting"); + return; + } + auto it = params.find (SAM_PARAM_PORT); + if (it == params.end ()) + { + SendI2PError ("PORT is missing"); + return; + } + auto port = std::stoi (it->second); + if (port <= 0 || port >= 0xFFFF) + { + SendI2PError ("Invalid PORT"); + return; + } + boost::system::error_code ec; + auto ep = m_Socket.remote_endpoint (ec); + if (ec) + { + SendI2PError ("Socket error"); + return; + } + ep.port (port); + m_SocketType = eSAMSocketTypeForward; + m_ID = id; + m_IsAccepting = true; + session->localDestination->AcceptStreams (std::bind (&SAMSocket::HandleI2PForward, + shared_from_this (), std::placeholders::_1, ep)); + SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); + } + size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data) { LogPrint (eLogDebug, "SAM: datagram send: ", buf, " ", len); @@ -917,6 +965,33 @@ namespace client LogPrint (eLogWarning, "SAM: I2P acceptor has been reset"); } + void SAMSocket::HandleI2PForward (std::shared_ptr stream, + boost::asio::ip::tcp::endpoint ep) + { + if (stream) + { + LogPrint (eLogDebug, "SAM: incoming forward I2P connection for session ", m_ID); + auto newSocket = std::make_shared(m_Owner); + newSocket->SetSocketType (eSAMSocketTypeStream); + auto s = shared_from_this (); + newSocket->GetSocket ().async_connect (ep, + [s, newSocket, stream](const boost::system::error_code& ecode) + { + if (!ecode) + { + s->m_Owner.AddSocket (newSocket); + newSocket->Receive (); + newSocket->m_Stream = stream; + newSocket->I2PReceive (); + } + else + stream->AsyncClose (); + }); + } + else + LogPrint (eLogWarning, "SAM: I2P forward acceptor has been reset"); + } + void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SAM: datagram received ", len); @@ -1072,6 +1147,12 @@ namespace client std::placeholders::_1, newSocket)); } + void SAMBridge::AddSocket(std::shared_ptr socket) + { + std::unique_lock lock(m_OpenSocketsMutex); + m_OpenSockets.push_back(socket); + } + void SAMBridge::RemoveSocket(const std::shared_ptr & socket) { std::unique_lock lock(m_OpenSocketsMutex); @@ -1087,10 +1168,7 @@ namespace client if (!ec) { LogPrint (eLogDebug, "SAM: new connection from ", ep); - { - std::unique_lock l(m_OpenSocketsMutex); - m_OpenSockets.push_back(socket); - } + AddSocket (socket); socket->ReceiveHandshake (); } else diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index 7b1702f5..fafd7d1c 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -48,6 +48,7 @@ namespace client const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER\n"; const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR\n"; const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT"; + const char SAM_STREAM_FORWARD[] = "STREAM FORWARD"; const char SAM_DATAGRAM_SEND[] = "DATAGRAM SEND"; const char SAM_RAW_SEND[] = "RAW SEND"; const char SAM_DEST_GENERATE[] = "DEST GENERATE"; @@ -69,14 +70,14 @@ namespace client const char SAM_PARAM_SIGNATURE_TYPE[] = "SIGNATURE_TYPE"; const char SAM_PARAM_CRYPTO_TYPE[] = "CRYPTO_TYPE"; const char SAM_PARAM_SIZE[] = "SIZE"; + const char SAM_PARAM_HOST[] = "HOST"; + const char SAM_PARAM_PORT[] = "PORT"; const char SAM_VALUE_TRANSIENT[] = "TRANSIENT"; const char SAM_VALUE_STREAM[] = "STREAM"; const char SAM_VALUE_DATAGRAM[] = "DATAGRAM"; const char SAM_VALUE_RAW[] = "RAW"; const char SAM_VALUE_TRUE[] = "true"; const char SAM_VALUE_FALSE[] = "false"; - const char SAM_VALUE_HOST[] = "HOST"; - const char SAM_VALUE_PORT[] = "PORT"; enum SAMSocketType { @@ -84,6 +85,7 @@ namespace client eSAMSocketTypeSession, eSAMSocketTypeStream, eSAMSocketTypeAcceptor, + eSAMSocketTypeForward, eSAMSocketTypeTerminated }; @@ -121,6 +123,7 @@ namespace client void I2PReceive (); void HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleI2PAccept (std::shared_ptr stream); + void HandleI2PForward (std::shared_ptr stream, boost::asio::ip::tcp::endpoint ep); void HandleWriteI2PData (const boost::system::error_code& ecode, size_t sz); void HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); @@ -128,6 +131,7 @@ namespace client void ProcessSessionCreate (char * buf, size_t len); void ProcessStreamConnect (char * buf, size_t len, size_t rem); void ProcessStreamAccept (char * buf, size_t len); + void ProcessStreamForward (char * buf, size_t len); void ProcessDestGenerate (char * buf, size_t len); void ProcessNamingLookup (char * buf, size_t len); void SendI2PError(const std::string & msg); @@ -205,6 +209,7 @@ namespace client /** send raw data to remote endpoint from our UDP Socket */ void SendTo(const uint8_t * buf, size_t len, std::shared_ptr remote); + void AddSocket(std::shared_ptr socket); void RemoveSocket(const std::shared_ptr & socket); bool ResolveSignatureType (const std::string& name, i2p::data::SigningKeyType& type) const; From e41bbcb2bb12ae8a981c04f3aeec787fb53bbd92 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 26 Oct 2020 11:19:37 -0400 Subject: [PATCH 3864/6300] handle SILENT for STREAM FORWARD --- libi2pd_client/SAM.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 2bdbf1ee..2e436637 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -608,6 +608,8 @@ namespace client m_SocketType = eSAMSocketTypeForward; m_ID = id; m_IsAccepting = true; + std::string& silent = params[SAM_PARAM_SILENT]; + if (silent == SAM_VALUE_TRUE) m_IsSilent = true; session->localDestination->AcceptStreams (std::bind (&SAMSocket::HandleI2PForward, shared_from_this (), std::placeholders::_1, ep)); SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); @@ -982,7 +984,17 @@ namespace client s->m_Owner.AddSocket (newSocket); newSocket->Receive (); newSocket->m_Stream = stream; - newSocket->I2PReceive (); + newSocket->m_ID = s->m_ID; + if (!s->m_IsSilent) + { + // get remote peer address + auto dest = stream->GetRemoteIdentity()->ToBase64 (); + memcpy (newSocket->m_StreamBuffer, dest.c_str (), dest.length ()); + newSocket->m_StreamBuffer[dest.length ()] = '\n'; + newSocket->HandleI2PReceive (boost::system::error_code (),dest.length () + 1); // we send identity like it has been received from stream + } + else + newSocket->I2PReceive (); } else stream->AsyncClose (); From cc0367b079f958a9ab4e796ac3a2cccf39a0730a Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 26 Oct 2020 16:06:19 -0400 Subject: [PATCH 3865/6300] always send STREAM STATUS reply to STREAM FORWARD --- daemon/HTTPServer.cpp | 1 + libi2pd_client/SAM.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 3b9f6eaa..59c530f9 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -838,6 +838,7 @@ namespace http { case i2p::client::eSAMSocketTypeSession : s << "session"; break; case i2p::client::eSAMSocketTypeStream : s << "stream"; break; case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break; + case i2p::client::eSAMSocketTypeForward : s << "forward"; break; default: s << "unknown"; break; } s << " [" << it->GetSocket ().remote_endpoint() << "]"; diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 2e436637..6624c6f9 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -199,7 +199,7 @@ namespace client { LogPrint (eLogDebug, "SAMSocket::SendMessageReply, close=",close?"true":"false", " reason: ", msg); - if (!m_IsSilent) + if (!m_IsSilent || m_SocketType == eSAMSocketTypeForward) boost::asio::async_write (m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (), std::bind(&SAMSocket::HandleMessageReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, close)); From 56f3bdd74677b3c87fe738e95653557e41da2168 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 27 Oct 2020 11:52:02 +0300 Subject: [PATCH 3866/6300] [win32] handle WinAPI errors in SSU Windows can throw WinAPI errors which are not handled by boost asio Signed-off-by: R4SAS --- libi2pd/SSU.cpp | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index 07c95a7a..d29f5cd7 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -13,6 +13,10 @@ #include "NetDb.hpp" #include "SSU.h" +#ifdef _WIN32 +#include +#endif + namespace i2p { namespace transport @@ -247,11 +251,17 @@ namespace transport void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) { - if (!ecode || - ecode == boost::asio::error::connection_refused || - ecode == boost::asio::error::connection_reset || - ecode == boost::asio::error::network_unreachable || - ecode == boost::asio::error::host_unreachable) + if (!ecode + || ecode == boost::asio::error::connection_refused + || ecode == boost::asio::error::connection_reset + || ecode == boost::asio::error::network_unreachable + || ecode == boost::asio::error::host_unreachable +#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO + || ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_ + || ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_ + || ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_ +#endif + ) // just try continue reading when received ICMP response otherwise socket can crash, // but better to find out which host were sent it and mark that router as unreachable { @@ -300,11 +310,17 @@ namespace transport void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) { - if (!ecode || - ecode == boost::asio::error::connection_refused || - ecode == boost::asio::error::connection_reset || - ecode == boost::asio::error::network_unreachable || - ecode == boost::asio::error::host_unreachable) + if (!ecode + || ecode == boost::asio::error::connection_refused + || ecode == boost::asio::error::connection_reset + || ecode == boost::asio::error::network_unreachable + || ecode == boost::asio::error::host_unreachable +#ifdef _WIN32 // windows can throw WinAPI error, which is not handled by ASIO + || ecode.value() == boost::winapi::ERROR_CONNECTION_REFUSED_ + || ecode.value() == boost::winapi::ERROR_NETWORK_UNREACHABLE_ + || ecode.value() == boost::winapi::ERROR_HOST_UNREACHABLE_ +#endif + ) // just try continue reading when received ICMP response otherwise socket can crash, // but better to find out which host were sent it and mark that router as unreachable { From c400372a79dcc93b75121f6fc5ba5e938a6cc185 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 27 Oct 2020 08:32:38 -0400 Subject: [PATCH 3867/6300] create new ratchets session if previous was not replied --- libi2pd/ECIESX25519AEADRatchetSession.h | 1 + libi2pd/Garlic.h | 1 + libi2pd/Streaming.cpp | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index af0b5de5..1c377b28 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -169,6 +169,7 @@ namespace garlic bool IsInactive (uint64_t ts) const { return ts > m_LastActivityTimestamp + ECIESX25519_INACTIVITY_TIMEOUT && CanBeRestarted (ts); } bool IsRatchets () const { return true; }; + bool IsReadyToSend () const { return m_State != eSessionStateNewSessionSent; }; uint64_t GetLastActivityTimestamp () const { return m_LastActivityTimestamp; }; private: diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index f1e363df..3c5d5de6 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -114,6 +114,7 @@ namespace garlic virtual bool CleanupUnconfirmedTags () { return false; }; // for I2CP, override in ElGamalAESSession virtual bool MessageConfirmed (uint32_t msgID); virtual bool IsRatchets () const { return false; }; + virtual bool IsReadyToSend () const { return true; }; virtual uint64_t GetLastActivityTimestamp () const { return 0; }; // non-zero for rathets only void SetLeaseSetUpdated () diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index ab08f41f..21c6d6ce 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -756,7 +756,7 @@ namespace stream return; } } - if (!m_RoutingSession || !m_RoutingSession->GetOwner ()) // expired and detached + if (!m_RoutingSession || !m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) // expired and detached or new session sent m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); if (!m_CurrentOutboundTunnel && m_RoutingSession) // first message to send { From c63818f355dae64727e82cb01c37a3f4a6fdf09a Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 27 Oct 2020 12:27:08 -0400 Subject: [PATCH 3868/6300] 2.34.0 --- ChangeLog | 29 +++++++++++++++++++ Win32/installer.iss | 2 +- android/build.gradle | 4 +-- appveyor.yml | 2 +- contrib/rpm/i2pd-git.spec | 5 +++- contrib/rpm/i2pd.spec | 5 +++- debian/changelog | 6 ++++ libi2pd/version.h | 2 +- qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml | 1 + 9 files changed, 49 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index beef7a5a..dbce5fb2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,35 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.34.0] - 2020-10-27 +### Added +- Ping responses for streaming +- STREAM FORWARD for SAM +- Tunnels through ECIES-x25519 routers +- Single thread for I2CP +- Shared transient destination between proxies +- Database lookups from ECIES destinations with ratchets response +- Handle WebDAV HTTP methods +- Don't try to connect or build tunnels if offline +- Validate IP when trying connect to remote peer +- Handle ICMP responses and WinAPI errors for SSU +### Changed +- Removed NTCP +- Dropped gcc 4.7 support +- Encyption type 0,4 by default for client tunnels +- Stripped out some HTTP header for HTTP server response +- HTTP 1.1 addressbook requests +- Set LeaseSet type to 3 for ratchets if not specified +- Handle SSU v4 and v6 messages in one thread +- Eliminate DH keys thread +### Fixed +- Random crashes on I2CP session disconnect +- Stream through racthets hangs if first SYN was not acked +- Check "Last-Modified" instead "If-Modified-Since" for addressbook reponse +- Trim behind ECIESx25519 tags +- Few bugs with Android main activity +- QT visual and layout issues + ## [2.33.0] - 2020-08-24 ### Added - Shared transient addresses diff --git a/Win32/installer.iss b/Win32/installer.iss index fbbeba53..cc40c409 100644 --- a/Win32/installer.iss +++ b/Win32/installer.iss @@ -1,5 +1,5 @@ #define I2Pd_AppName "i2pd" -#define I2Pd_ver "2.33.0" +#define I2Pd_ver "2.34.0" #define I2Pd_Publisher "PurpleI2P" [Setup] diff --git a/android/build.gradle b/android/build.gradle index 7d25e28b..225b5d21 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -32,8 +32,8 @@ android { applicationId "org.purplei2p.i2pd" targetSdkVersion 29 minSdkVersion 14 - versionCode 2330 - versionName "2.33.0" + versionCode 2340 + versionName "2.34.0" setProperty("archivesBaseName", archivesBaseName + "-" + versionName) ndk { abiFilters 'armeabi-v7a' diff --git a/appveyor.yml b/appveyor.yml index 3cf51c2b..a8e7d30d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.33.0.{build} +version: 2.34.0.{build} pull_requests: do_not_increment_build_number: true branches: diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 00bea0b0..b1f28de1 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -1,7 +1,7 @@ %define git_hash %(git rev-parse HEAD | cut -c -7) Name: i2pd-git -Version: 2.33.0 +Version: 2.34.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -124,6 +124,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Tue Oct 27 2020 orignal - 2.34.0 +- update to 2.34.0 + * Mon Aug 24 2020 orignal - 2.33.0 - update to 2.33.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 22287f24..150be6a4 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.33.0 +Version: 2.34.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -122,6 +122,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Tue Oct 27 2020 orignal - 2.34.0 +- update to 2.34.0 + * Mon Aug 24 2020 orignal - 2.33.0 - update to 2.33.0 diff --git a/debian/changelog b/debian/changelog index bac3f3b3..b79f6cd6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.34.0-1) unstable; urgency=medium + + * updated to version 2.34.0 + + -- orignal Tue, 27 Oct 2020 16:00:00 +0000 + i2pd (2.33.0-1) unstable; urgency=medium * updated to version 2.33.0/0.9.47 diff --git a/libi2pd/version.h b/libi2pd/version.h index db30eee8..26ced1c3 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -16,7 +16,7 @@ #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 33 +#define I2PD_VERSION_MINOR 34 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml index d3fa2e9e..97a759c2 100644 --- a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml +++ b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml @@ -35,6 +35,7 @@ + From 979282a0d4784ab1003e7ed333ccbf2295110af2 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 28 Oct 2020 03:11:14 +0800 Subject: [PATCH 3869/6300] qt .pro now uses libi2pd.a and libi2pclient.a instead of sources --- qt/i2pd_qt/i2pd_qt.pro | 146 ++++------------------------------------- 1 file changed, 13 insertions(+), 133 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 534d5f5a..80968fcb 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -17,68 +17,6 @@ CONFIG(debug, debug|release) { } SOURCES += DaemonQT.cpp mainwindow.cpp \ - ../../libi2pd/api.cpp \ - ../../libi2pd/Base.cpp \ - ../../libi2pd/Blinding.cpp \ - ../../libi2pd/BloomFilter.cpp \ - ../../libi2pd/ChaCha20.cpp \ - ../../libi2pd/Config.cpp \ - ../../libi2pd/CPU.cpp \ - ../../libi2pd/Crypto.cpp \ - ../../libi2pd/CryptoKey.cpp \ - ../../libi2pd/Datagram.cpp \ - ../../libi2pd/Destination.cpp \ - ../../libi2pd/Ed25519.cpp \ - ../../libi2pd/Family.cpp \ - ../../libi2pd/FS.cpp \ - ../../libi2pd/Garlic.cpp \ - ../../libi2pd/Gost.cpp \ - ../../libi2pd/Gzip.cpp \ - ../../libi2pd/HTTP.cpp \ - ../../libi2pd/I2NPProtocol.cpp \ - ../../libi2pd/I2PEndian.cpp \ - ../../libi2pd/Identity.cpp \ - ../../libi2pd/LeaseSet.cpp \ - ../../libi2pd/Log.cpp \ - ../../libi2pd/NetDb.cpp \ - ../../libi2pd/NetDbRequests.cpp \ - ../../libi2pd/NTCP2.cpp \ - ../../libi2pd/Poly1305.cpp \ - ../../libi2pd/Profiling.cpp \ - ../../libi2pd/Reseed.cpp \ - ../../libi2pd/RouterContext.cpp \ - ../../libi2pd/RouterInfo.cpp \ - ../../libi2pd/Signature.cpp \ - ../../libi2pd/SSU.cpp \ - ../../libi2pd/SSUData.cpp \ - ../../libi2pd/SSUSession.cpp \ - ../../libi2pd/Streaming.cpp \ - ../../libi2pd/Timestamp.cpp \ - ../../libi2pd/TransitTunnel.cpp \ - ../../libi2pd/Transports.cpp \ - ../../libi2pd/Tunnel.cpp \ - ../../libi2pd/TunnelEndpoint.cpp \ - ../../libi2pd/TunnelGateway.cpp \ - ../../libi2pd/TunnelPool.cpp \ - ../../libi2pd/TunnelConfig.cpp \ - ../../libi2pd/util.cpp \ - ../../libi2pd/Elligator.cpp \ - ../../libi2pd/ECIESX25519AEADRatchetSession.cpp \ - ../../libi2pd_client/AddressBook.cpp \ - ../../libi2pd_client/BOB.cpp \ - ../../libi2pd_client/ClientContext.cpp \ - ../../libi2pd_client/HTTPProxy.cpp \ - ../../libi2pd_client/I2CP.cpp \ - ../../libi2pd_client/I2PService.cpp \ - ../../libi2pd_client/I2PTunnel.cpp \ - ../../libi2pd_client/MatchedDestination.cpp \ - ../../libi2pd_client/SAM.cpp \ - ../../libi2pd_client/SOCKS.cpp \ - ../../daemon/Daemon.cpp \ - ../../daemon/HTTPServer.cpp \ - ../../daemon/I2PControl.cpp \ - ../../daemon/i2pd.cpp \ - ../../daemon/UPnP.cpp \ ClientTunnelPane.cpp \ MainWindowItems.cpp \ ServerTunnelPane.cpp \ @@ -93,77 +31,14 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \ DelayedSaveManager.cpp \ Saver.cpp \ DelayedSaveManagerImpl.cpp \ - SaverImpl.cpp + SaverImpl.cpp \ + ../../daemon/Daemon.cpp \ + ../../daemon/HTTPServer.cpp \ + ../../daemon/I2PControl.cpp \ + ../../daemon/i2pd.cpp \ + ../../daemon/UPnP.cpp HEADERS += DaemonQT.h mainwindow.h \ - ../../libi2pd/api.h \ - ../../libi2pd/Base.h \ - ../../libi2pd/Blinding.h \ - ../../libi2pd/BloomFilter.h \ - ../../libi2pd/ChaCha20.h \ - ../../libi2pd/Config.h \ - ../../libi2pd/CPU.h \ - ../../libi2pd/Crypto.h \ - ../../libi2pd/CryptoKey.h \ - ../../libi2pd/Datagram.h \ - ../../libi2pd/Destination.h \ - ../../libi2pd/Ed25519.h \ - ../../libi2pd/Family.h \ - ../../libi2pd/FS.h \ - ../../libi2pd/Garlic.h \ - ../../libi2pd/Gost.h \ - ../../libi2pd/Gzip.h \ - ../../libi2pd/HTTP.h \ - ../../libi2pd/I2NPProtocol.h \ - ../../libi2pd/I2PEndian.h \ - ../../libi2pd/Identity.h \ - ../../libi2pd/LeaseSet.h \ - ../../libi2pd/LittleBigEndian.h \ - ../../libi2pd/Log.h \ - ../../libi2pd/NetDb.hpp \ - ../../libi2pd/NetDbRequests.h \ - ../../libi2pd/NTCP2.h \ - ../../libi2pd/Poly1305.h \ - ../../libi2pd/Profiling.h \ - ../../libi2pd/Queue.h \ - ../../libi2pd/Reseed.h \ - ../../libi2pd/RouterContext.h \ - ../../libi2pd/RouterInfo.h \ - ../../libi2pd/Signature.h \ - ../../libi2pd/Siphash.h \ - ../../libi2pd/SSU.h \ - ../../libi2pd/SSUData.h \ - ../../libi2pd/SSUSession.h \ - ../../libi2pd/Streaming.h \ - ../../libi2pd/Tag.h \ - ../../libi2pd/Timestamp.h \ - ../../libi2pd/TransitTunnel.h \ - ../../libi2pd/Transports.h \ - ../../libi2pd/TransportSession.h \ - ../../libi2pd/Tunnel.h \ - ../../libi2pd/TunnelBase.h \ - ../../libi2pd/TunnelConfig.h \ - ../../libi2pd/TunnelEndpoint.h \ - ../../libi2pd/TunnelGateway.h \ - ../../libi2pd/TunnelPool.h \ - ../../libi2pd/util.h \ - ../../libi2pd/version.h \ - ../../libi2pd/Elligator.h \ - ../../libi2pd/ECIESX25519AEADRatchetSession.h \ - ../../libi2pd_client/AddressBook.h \ - ../../libi2pd_client/BOB.h \ - ../../libi2pd_client/ClientContext.h \ - ../../libi2pd_client/HTTPProxy.h \ - ../../libi2pd_client/I2CP.h \ - ../../libi2pd_client/I2PService.h \ - ../../libi2pd_client/I2PTunnel.h \ - ../../libi2pd_client/MatchedDestination.h \ - ../../libi2pd_client/SAM.h \ - ../../libi2pd_client/SOCKS.h \ - ../../daemon/Daemon.h \ - ../../daemon/HTTPServer.h \ - ../../daemon/I2PControl.h \ - ../../daemon/UPnP.h \ ClientTunnelPane.h \ MainWindowItems.h \ ServerTunnelPane.h \ @@ -180,7 +55,12 @@ HEADERS += DaemonQT.h mainwindow.h \ DelayedSaveManager.h \ Saver.h \ DelayedSaveManagerImpl.h \ - SaverImpl.h + SaverImpl.h \ + ../../daemon/Daemon.h \ + ../../daemon/HTTPServer.h \ + ../../daemon/I2PControl.h \ + ../../daemon/UPnP.h + INCLUDEPATH += ../../libi2pd INCLUDEPATH += ../../libi2pd_client @@ -193,7 +73,7 @@ FORMS += mainwindow.ui \ routercommandswidget.ui \ generalsettingswidget.ui -LIBS += -lz +LIBS += ../../libi2pd.a ../../libi2pdclient.a -lz macx { message("using mac os x target") From 2b4a91cc806f7826a8b6b24c852f916eb1de100d Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 27 Oct 2020 19:34:38 +0000 Subject: [PATCH 3870/6300] [actions] Rename worker and jobs --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ba6acbf8..5e5152e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,10 +1,10 @@ -name: Build on Ubuntu with make +name: Build on Ubuntu on: [push, pull_request] jobs: build: - name: Building with USE_UPNP=${{ matrix.with_upnp }} flag + name: With USE_UPNP=${{ matrix.with_upnp }} runs-on: ubuntu-16.04 strategy: fail-fast: true From 0c29aeb9bead9a8f4039ecf4206bcb2d30975733 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 27 Oct 2020 19:40:22 +0000 Subject: [PATCH 3871/6300] [actions] add qt gui builder --- .github/workflows/build-qt.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/build-qt.yml diff --git a/.github/workflows/build-qt.yml b/.github/workflows/build-qt.yml new file mode 100644 index 00000000..6b247f1b --- /dev/null +++ b/.github/workflows/build-qt.yml @@ -0,0 +1,20 @@ +name: Build on Ubuntu + +on: [push, pull_request] + +jobs: + build: + name: With QT GUI + runs-on: ubuntu-16.04 + steps: + - uses: actions/checkout@v2 + - name: install packages + run: | + sudo add-apt-repository ppa:mhier/libboost-latest + sudo apt-get update + sudo apt-get install build-essential qt5-default libqt5gui5 libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev + - name: build application + run: | + cd qt/i2pd_qt + qmake + make USE_AVX=no USE_AESNI=no USE_UPNP=yes -j3 From a47aa8c282be0b23133922cbcf7c89034ce181bf Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 27 Oct 2020 19:55:48 +0000 Subject: [PATCH 3872/6300] [actions] build i2pd library before building gui --- .github/workflows/build-qt.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-qt.yml b/.github/workflows/build-qt.yml index 6b247f1b..6211c4c7 100644 --- a/.github/workflows/build-qt.yml +++ b/.github/workflows/build-qt.yml @@ -15,6 +15,7 @@ jobs: sudo apt-get install build-essential qt5-default libqt5gui5 libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev - name: build application run: | + make -j3 USE_AVX=no USE_AESNI=no USE_UPNP=yes DEBUG=no mk_obj_dir libi2pd.a libi2pdclient.a cd qt/i2pd_qt qmake - make USE_AVX=no USE_AESNI=no USE_UPNP=yes -j3 + make -j3 From e444519889b75d570d025e5dfcd0a15a60ca1eba Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 27 Oct 2020 16:46:39 -0400 Subject: [PATCH 3873/6300] excluded appcompat --- android/build.gradle | 2 -- android/src/org/purplei2p/i2pd/WebConsoleActivity.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 225b5d21..2c6c782d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -21,8 +21,6 @@ repositories { dependencies { implementation 'androidx.core:core:1.0.2' - implementation 'androidx.appcompat:appcompat:1.0.2' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' } android { diff --git a/android/src/org/purplei2p/i2pd/WebConsoleActivity.java b/android/src/org/purplei2p/i2pd/WebConsoleActivity.java index 24e6baeb..0fcf37fe 100644 --- a/android/src/org/purplei2p/i2pd/WebConsoleActivity.java +++ b/android/src/org/purplei2p/i2pd/WebConsoleActivity.java @@ -1,7 +1,5 @@ package org.purplei2p.i2pd; -import androidx.appcompat.app.AppCompatActivity; - import android.app.Activity; import android.os.Bundle; import android.view.MenuItem; From 33f2ddb6960288f7c48d2b8d0fa8632051746817 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 28 Oct 2020 20:07:28 +0300 Subject: [PATCH 3874/6300] [QT] fix build with prebuild i2pd libs Signed-off-by: R4SAS --- .github/workflows/build-qt.yml | 1 - qt/i2pd_qt/i2pd_qt.pro | 24 ++++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-qt.yml b/.github/workflows/build-qt.yml index 6211c4c7..d4fde2b9 100644 --- a/.github/workflows/build-qt.yml +++ b/.github/workflows/build-qt.yml @@ -15,7 +15,6 @@ jobs: sudo apt-get install build-essential qt5-default libqt5gui5 libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev - name: build application run: | - make -j3 USE_AVX=no USE_AESNI=no USE_UPNP=yes DEBUG=no mk_obj_dir libi2pd.a libi2pdclient.a cd qt/i2pd_qt qmake make -j3 diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 80968fcb..1064e953 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -7,8 +7,6 @@ TEMPLATE = app QMAKE_CXXFLAGS *= -Wno-unused-parameter -Wno-maybe-uninitialized CONFIG += strict_c++ c++11 -DEFINES += USE_UPNP - CONFIG(debug, debug|release) { message(Debug build) DEFINES += DEBUG_WITH_DEFAULT_LOGGING @@ -75,6 +73,22 @@ FORMS += mainwindow.ui \ LIBS += ../../libi2pd.a ../../libi2pdclient.a -lz +libi2pd.commands = cd $$PWD/../../ && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) OPT=\"$$QMAKE_CXXFLAGS $$QMAKE_CXXFLAGS_RELEASE\" USE_UPNP=yes DEBUG=no mk_obj_dir libi2pd.a +libi2pd.target = $$PWD/../../libi2pd.a +libi2pd.depends = FORCE + +libi2pdclient.commands = cd $$PWD/../../ && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) OPT=\"$$QMAKE_CXXFLAGS $$QMAKE_CXXFLAGS_RELEASE\" USE_UPNP=yes DEBUG=no mk_obj_dir libi2pdclient.a +libi2pdclient.target = $$PWD/../../libi2pdclient.a +libi2pdclient.depends = FORCE + +cleani2pd.commands = cd $$PWD/../../ && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) clean +cleani2pd.depends = clean + +PRE_TARGETDEPS += $$PWD/../../libi2pd.a $$PWD/../../libi2pdclient.a +QMAKE_EXTRA_TARGETS += cleani2pd libi2pd libi2pdclient +CLEAN_DEPS += cleani2pd + + macx { message("using mac os x target") BREWROOT=/usr/local @@ -110,10 +124,12 @@ windows { QMAKE_CXXFLAGS_RELEASE = -Os QMAKE_LFLAGS = -Wl,-Bstatic -static-libgcc -static-libstdc++ -mwindows - #linker's -s means "strip" + # linker's -s means "strip" QMAKE_LFLAGS_RELEASE += -s - LIBS = -lminiupnpc \ + LIBS = \ + $$PWD/../../libi2pd.a $$PWD/../../libi2pdclient.a \ + -lminiupnpc \ -lboost_system$$BOOST_SUFFIX \ -lboost_date_time$$BOOST_SUFFIX \ -lboost_filesystem$$BOOST_SUFFIX \ From bf0496299413a69fd364db54b79ec0b2388976e9 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 28 Oct 2020 20:47:16 +0300 Subject: [PATCH 3875/6300] [QT] change i2pd make command Signed-off-by: R4SAS --- qt/i2pd_qt/i2pd_qt.pro | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 1064e953..54818f71 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -71,13 +71,13 @@ FORMS += mainwindow.ui \ routercommandswidget.ui \ generalsettingswidget.ui -LIBS += ../../libi2pd.a ../../libi2pdclient.a -lz +LIBS += $$PWD/../../libi2pd.a $$PWD/../../libi2pdclient.a -lz -libi2pd.commands = cd $$PWD/../../ && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) OPT=\"$$QMAKE_CXXFLAGS $$QMAKE_CXXFLAGS_RELEASE\" USE_UPNP=yes DEBUG=no mk_obj_dir libi2pd.a +libi2pd.commands = cd $$PWD/../../ && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes DEBUG=no api libi2pd.target = $$PWD/../../libi2pd.a libi2pd.depends = FORCE -libi2pdclient.commands = cd $$PWD/../../ && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) OPT=\"$$QMAKE_CXXFLAGS $$QMAKE_CXXFLAGS_RELEASE\" USE_UPNP=yes DEBUG=no mk_obj_dir libi2pdclient.a +libi2pdclient.commands = cd $$PWD/../../ && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes DEBUG=no api_client libi2pdclient.target = $$PWD/../../libi2pdclient.a libi2pdclient.depends = FORCE From bdbd060229cf3d0808b83148ed89e7939d70499d Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 28 Oct 2020 21:02:41 +0300 Subject: [PATCH 3876/6300] [QT] create obj dirs before building i2pd Signed-off-by: R4SAS --- qt/i2pd_qt/i2pd_qt.pro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 54818f71..34b8ebfc 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -73,11 +73,11 @@ FORMS += mainwindow.ui \ LIBS += $$PWD/../../libi2pd.a $$PWD/../../libi2pdclient.a -lz -libi2pd.commands = cd $$PWD/../../ && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes DEBUG=no api +libi2pd.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes DEBUG=no api libi2pd.target = $$PWD/../../libi2pd.a libi2pd.depends = FORCE -libi2pdclient.commands = cd $$PWD/../../ && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes DEBUG=no api_client +libi2pdclient.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd_client && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes DEBUG=no api_client libi2pdclient.target = $$PWD/../../libi2pdclient.a libi2pdclient.depends = FORCE From d02a0c9b3a1b9f2cbee8bf364382443cfa081443 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 28 Oct 2020 21:18:02 +0300 Subject: [PATCH 3877/6300] [QT] don't build i2pd with aesni/avx for compatability with arm64 Signed-off-by: R4SAS --- qt/i2pd_qt/i2pd_qt.pro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 34b8ebfc..dba69143 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -73,11 +73,11 @@ FORMS += mainwindow.ui \ LIBS += $$PWD/../../libi2pd.a $$PWD/../../libi2pdclient.a -lz -libi2pd.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes DEBUG=no api +libi2pd.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_AVX=no USE_AESNI=no USE_UPNP=yes DEBUG=no api libi2pd.target = $$PWD/../../libi2pd.a libi2pd.depends = FORCE -libi2pdclient.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd_client && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes DEBUG=no api_client +libi2pdclient.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd_client && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_AVX=no USE_AESNI=no USE_UPNP=yes DEBUG=no api_client libi2pdclient.target = $$PWD/../../libi2pdclient.a libi2pdclient.depends = FORCE From 5d256e1d800a2f7cd9f92f3fb3e27522ad2afe0d Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 28 Oct 2020 15:35:39 -0400 Subject: [PATCH 3878/6300] don't allow STREAM CONNECT and STREAM ACCEPT in command session --- libi2pd_client/SAM.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index 6624c6f9..d758f31e 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -472,6 +472,11 @@ namespace client void SAMSocket::ProcessStreamConnect (char * buf, size_t len, size_t rem) { LogPrint (eLogDebug, "SAM: stream connect: ", buf); + if ( m_SocketType != eSAMSocketTypeUnknown) + { + SendI2PError ("Socket already in use"); + return; + } std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; @@ -547,6 +552,11 @@ namespace client void SAMSocket::ProcessStreamAccept (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: stream accept: ", buf); + if ( m_SocketType != eSAMSocketTypeUnknown) + { + SendI2PError ("Socket already in use"); + return; + } std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; From 812d312a9e4c1807f768bdcb97bbccb154bef207 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 29 Oct 2020 00:38:47 +0300 Subject: [PATCH 3879/6300] [RPM] fix build on fedora >= 33 Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index b1f28de1..58c87cfb 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -56,14 +56,23 @@ cd build %endif %endif -%if 0%{?mageia} > 7 -pushd build -make %{?_smp_mflags} -popd -%else -make %{?_smp_mflags} +%if 0%{?fedora} >= 33 +pushd %{_arch}-redhat-linux-gnu %endif +%if 0%{?mageia} > 7 +pushd build +%endif + +make %{?_smp_mflags} + +%if 0%{?fedora} >= 33 +popd +%endif + +%if 0%{?mageia} > 7 +popd +%endif %install pushd build From 530eba1b911dcc682c05194d67c7a425267feb18 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 29 Oct 2020 00:51:01 +0300 Subject: [PATCH 3880/6300] [RPM] fix build on fedora >= 33 Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 58c87cfb..1fd8ce8d 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -57,7 +57,7 @@ cd build %endif %if 0%{?fedora} >= 33 -pushd %{_arch}-redhat-linux-gnu +pushd %{_target_platform} %endif %if 0%{?mageia} > 7 From b2f0278180c2e53b7a877c9993d37beaac4bc8d0 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 29 Oct 2020 01:03:36 +0300 Subject: [PATCH 3881/6300] [RPM] fix build on fedora >= 33 Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 1fd8ce8d..59d5f5ab 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -81,6 +81,10 @@ pushd build pushd build %endif +%if 0%{?fedora} >= 33 +pushd %{_target_platform} +%endif + chrpath -d i2pd %{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd %{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd From aaf6c1ea8b7fce01f10be087f5c4dc3680167649 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 29 Oct 2020 01:17:07 +0300 Subject: [PATCH 3882/6300] [RPM] fix build on fedora >= 33 Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 8 ++++---- contrib/rpm/i2pd.spec | 25 +++++++++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 59d5f5ab..a320d6aa 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -77,14 +77,14 @@ popd %install pushd build -%if 0%{?mageia} -pushd build -%endif - %if 0%{?fedora} >= 33 pushd %{_target_platform} %endif +%if 0%{?mageia} +pushd build +%endif + chrpath -d i2pd %{__install} -D -m 755 i2pd %{buildroot}%{_sbindir}/i2pd %{__install} -d -m 755 %{buildroot}%{_datadir}/i2pd diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 150be6a4..3f5ef260 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -54,18 +54,31 @@ cd build %endif %endif -%if 0%{?mageia} > 7 -pushd build -make %{?_smp_mflags} -popd -%else -make %{?_smp_mflags} +%if 0%{?fedora} >= 33 +pushd %{_target_platform} %endif +%if 0%{?mageia} > 7 +pushd build +%endif + +make %{?_smp_mflags} + +%if 0%{?fedora} >= 33 +popd +%endif + +%if 0%{?mageia} > 7 +popd +%endif %install pushd build +%if 0%{?fedora} >= 33 +pushd %{_target_platform} +%endif + %if 0%{?mageia} pushd build %endif From 9f2a2e44a323ba94c7782e3d0e0c4a93c63dba6f Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 28 Oct 2020 21:53:11 -0400 Subject: [PATCH 3883/6300] common MixHash and MixKey --- libi2pd/Crypto.cpp | 18 ++++++++++++++++-- libi2pd/Crypto.h | 10 ++++++++++ libi2pd/NTCP2.cpp | 15 --------------- libi2pd/NTCP2.h | 6 ++---- libi2pd/Tunnel.cpp | 2 +- libi2pd/TunnelConfig.cpp | 22 +++++----------------- libi2pd/TunnelConfig.h | 5 ++--- 7 files changed, 36 insertions(+), 42 deletions(-) diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 2f10e91d..ea2cd675 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -1323,6 +1323,21 @@ namespace crypto #endif } + void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len) + { + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, m_H, 32); + SHA256_Update (&ctx, buf, len); + SHA256_Final (m_H, &ctx); + } + + void NoiseSymmetricState::MixKey (const uint8_t * sharedSecret) + { + HKDF (m_CK, sharedSecret, 32, "", m_CK); + // new ck is m_CK[0:31], key is m_CK[32:63] + } + // init and terminate /* std::vector > m_OpenSSLMutexes; @@ -1336,8 +1351,7 @@ namespace crypto m_OpenSSLMutexes[type]->unlock (); } }*/ - - + void InitCrypto (bool precomputation) { i2p::cpu::Detect (); diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index ab84e56a..205be44d 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -311,6 +311,16 @@ namespace crypto void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out, size_t outLen = 64); // salt - 32, out - 32 or 64, info <= 32 +// Noise + + struct NoiseSymmetricState + { + uint8_t m_H[32] /*h*/, m_CK[64] /*[ck, k]*/; + + void MixHash (const uint8_t * buf, size_t len); + void MixKey (const uint8_t * sharedSecret); + }; + // init and terminate void InitCrypto (bool precomputation); void TerminateCrypto (); diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index cdf660b7..7a88e4d8 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -39,21 +39,6 @@ namespace transport delete[] m_SessionConfirmedBuffer; } - void NTCP2Establisher::MixKey (const uint8_t * inputKeyMaterial) - { - i2p::crypto::HKDF (m_CK, inputKeyMaterial, 32, "", m_CK); - // ck is m_CK[0:31], k is m_CK[32:63] - } - - void NTCP2Establisher::MixHash (const uint8_t * buf, size_t len) - { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, m_H, 32); - SHA256_Update (&ctx, buf, len); - SHA256_Final (m_H, &ctx); - } - void NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub) { static const uint8_t protocolNameHash[] = diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index df72fed0..351f17b5 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -74,7 +74,7 @@ namespace transport // RouterInfo flags const uint8_t NTCP2_ROUTER_INFO_FLAG_REQUEST_FLOOD = 0x01; - struct NTCP2Establisher + struct NTCP2Establisher: private i2p::crypto::NoiseSymmetricState { NTCP2Establisher (); ~NTCP2Establisher (); @@ -94,8 +94,6 @@ namespace transport void KDF3Alice (); // for SessionConfirmed part 2 void KDF3Bob (); - void MixKey (const uint8_t * inputKeyMaterial); - void MixHash (const uint8_t * buf, size_t len); void KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub); // for SessionRequest, (pub, priv) for DH void KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub); // for SessionCreate void CreateEphemeralKey (); @@ -112,7 +110,7 @@ namespace transport std::shared_ptr m_EphemeralKeys; uint8_t m_RemoteEphemeralPublicKey[32]; // x25519 - uint8_t m_RemoteStaticKey[32], m_IV[16], m_H[32] /*h*/, m_CK[64] /* [ck, k]*/; + uint8_t m_RemoteStaticKey[32], m_IV[16]; i2p::data::IdentHash m_RemoteIdentHash; uint16_t m3p2Len; diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index bfe466e3..a74c8c91 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -124,7 +124,7 @@ namespace tunnel uint8_t nonce[12]; memset (nonce, 0, 12); if (!i2p::crypto::AEADChaCha20Poly1305 (record, TUNNEL_BUILD_RECORD_SIZE - 16, - hop->h, 32, hop->ck, nonce, record, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt + hop->m_H, 32, hop->m_CK, nonce, record, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt { LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); return false; diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 5c2cbd53..61e878d4 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -10,7 +10,6 @@ #include #include #include -#include "Crypto.h" #include "Log.h" #include "Transports.h" #include "Timestamp.h" @@ -129,8 +128,8 @@ namespace tunnel const uint8_t * plainText, uint8_t * encrypted, BN_CTX * ctx) { static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars - memcpy (ck, protocolName, 32); // ck = h = protocol_name || 0 - SHA256 (ck, 32, h); // h = SHA256(h); + memcpy (m_CK, protocolName, 32); // ck = h = protocol_name || 0 + SHA256 (m_CK, 32, m_H); // h = SHA256(h); uint8_t hepk[32]; encryptor->Encrypt (nullptr, hepk, nullptr, false); MixHash (hepk, 32); // h = SHA256(h || hepk) @@ -140,27 +139,16 @@ namespace tunnel encrypted += 32; uint8_t sharedSecret[32]; ephemeralKeys->Agree (hepk, sharedSecret); // x25519(sesk, hepk) - uint8_t keydata[64]; - i2p::crypto::HKDF (ck, sharedSecret, 32, "", keydata); - memcpy (ck, keydata, 32); + MixKey (sharedSecret); uint8_t nonce[12]; memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, h, 32, - keydata + 32, nonce, encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16, true)) // encrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, m_H, 32, + m_CK + 32, nonce, encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16, true)) // encrypt { LogPrint (eLogWarning, "Tunnel: Plaintext AEAD encryption failed"); return; } MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) } - - void TunnelHopConfig::MixHash (const uint8_t * buf, size_t len) - { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, h, 32); - SHA256_Update (&ctx, buf, len); - SHA256_Final (h, &ctx); - } } } \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 0e757071..261394b4 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -12,12 +12,13 @@ #include #include "Identity.h" #include "RouterContext.h" +#include "Crypto.h" namespace i2p { namespace tunnel { - struct TunnelHopConfig + struct TunnelHopConfig: public i2p::crypto::NoiseSymmetricState { std::shared_ptr ident; i2p::data::IdentHash nextIdent; @@ -30,7 +31,6 @@ namespace tunnel TunnelHopConfig * next, * prev; int recordIndex; // record # in tunnel build message - uint8_t ck[32], h[32]; // for ECIES TunnelHopConfig (std::shared_ptr r); @@ -43,7 +43,6 @@ namespace tunnel void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); void EncryptECIES (std::shared_ptr& encryptor, const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx); - void MixHash (const uint8_t * buf, size_t len); }; class TunnelConfig From b12fa97a38ca9c9fc15800e2106e483687fee33a Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 29 Oct 2020 18:41:21 -0400 Subject: [PATCH 3884/6300] 32 bytes private key for ECIESx25519 --- libi2pd/Identity.cpp | 20 ++++++++++++++------ libi2pd/Identity.h | 1 + 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 490b8692..c6e5a884 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -476,7 +476,7 @@ namespace data size_t PrivateKeys::GetFullLen () const { - size_t ret = m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen (); + size_t ret = m_Public->GetFullLen () + GetPrivateKeyLen () + m_Public->GetSigningPrivateKeyLen (); if (IsOfflineSignature ()) ret += m_OfflineSignature.size () + m_TransientSigningPrivateKeyLen; return ret; @@ -486,9 +486,10 @@ namespace data { m_Public = std::make_shared(); size_t ret = m_Public->FromBuffer (buf, len); - if (!ret || ret + 256 > len) return 0; // overflow - memcpy (m_PrivateKey, buf + ret, 256); // private key always 256 - ret += 256; + auto cryptoKeyLen = GetPrivateKeyLen (); + if (!ret || ret + cryptoKeyLen > len) return 0; // overflow + memcpy (m_PrivateKey, buf + ret, cryptoKeyLen); + ret += cryptoKeyLen; size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen (); if(signingPrivateKeySize + ret > len || signingPrivateKeySize > 128) return 0; // overflow memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize); @@ -540,8 +541,9 @@ namespace data size_t PrivateKeys::ToBuffer (uint8_t * buf, size_t len) const { size_t ret = m_Public->ToBuffer (buf, len); - memcpy (buf + ret, m_PrivateKey, 256); // private key always 256 - ret += 256; + auto cryptoKeyLen = GetPrivateKeyLen (); + memcpy (buf + ret, m_PrivateKey, cryptoKeyLen); + ret += cryptoKeyLen; size_t signingPrivateKeySize = m_Public->GetSigningPrivateKeyLen (); if(ret + signingPrivateKeySize > len) return 0; // overflow if (IsOfflineSignature ()) @@ -657,6 +659,12 @@ namespace data return IsOfflineSignature () ? m_TransientSignatureLen : m_Public->GetSignatureLen (); } + size_t PrivateKeys::GetPrivateKeyLen () const + { + // private key length always 256, but type 4 + return (m_Public->GetCryptoKeyType () == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) ? 32 : 256; + } + uint8_t * PrivateKeys::GetPadding() { if(m_Public->GetSigningKeyType () == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519) diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index 534b8f4c..a36e7209 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -183,6 +183,7 @@ namespace data void CreateSigner () const; void CreateSigner (SigningKeyType keyType) const; + size_t GetPrivateKeyLen () const; private: From 3907c17cf5529034a349a0519fe84baa083a6d77 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 2 Nov 2020 18:49:07 -0500 Subject: [PATCH 3885/6300] handle TunnelBuildMessage for ECIES router --- libi2pd/I2NPProtocol.cpp | 53 +++++++++++++++++++++++++++++++-------- libi2pd/I2NPProtocol.h | 1 + libi2pd/RouterContext.cpp | 35 ++++++++++++++++++++++++-- libi2pd/RouterContext.h | 8 ++++-- libi2pd/TunnelConfig.cpp | 11 +++++--- libi2pd/TunnelConfig.h | 2 ++ 6 files changed, 93 insertions(+), 17 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 36e7a763..7778b9e4 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -363,37 +363,70 @@ namespace i2p BN_CTX * ctx = BN_CTX_new (); i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText, ctx); BN_CTX_free (ctx); + uint8_t retCode = 0; + bool isECIES = i2p::context.IsECIES (); // replace record to reply if (i2p::context.AcceptsTunnels () && i2p::tunnel::tunnels.GetTransitTunnels ().size () <= g_MaxNumTransitTunnels && !i2p::transport::transports.IsBandwidthExceeded () && !i2p::transport::transports.IsTransitBandwidthExceeded ()) { - auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( + auto transitTunnel = isECIES ? + i2p::tunnel::CreateTransitTunnel ( + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, + clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80, + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) : + i2p::tunnel::CreateTransitTunnel ( bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80, - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET ] & 0x40); + clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40); i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); - record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 0; } else - record[BUILD_RESPONSE_RECORD_RET_OFFSET] = 30; // always reject with bandwidth reason (30) + retCode = 30; // always reject with bandwidth reason (30) - //TODO: fill filler - SHA256 (record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1, // + 1 byte of ret - record + BUILD_RESPONSE_RECORD_HASH_OFFSET); + if (isECIES) + { + memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; + } + else + { + record[BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; + SHA256 (record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1, // + 1 byte of ret + record + BUILD_RESPONSE_RECORD_HASH_OFFSET); + } // encrypt reply i2p::crypto::CBCEncryption encryption; for (int j = 0; j < num; j++) { - encryption.SetKey (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); - encryption.SetIV (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; - encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); + if (isECIES && j == i) + { + uint8_t nonce[12]; + memset (nonce, 0, 12); + auto noiseState = std::move (i2p::context.GetCurrentNoiseState ()); + if (!noiseState || !i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState->m_H, 32, noiseState->m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); + return false; + } + } + else + { + encryption.SetKey (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); + encryption.SetIV (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); + encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); + } } return true; } diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 03f0f439..b8fbb303 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -98,6 +98,7 @@ namespace i2p const size_t ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE = 464; // ECIES BuildResponseRecord + const size_t ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET = 0; const size_t ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET = 511; enum I2NPMessageType diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 69e69d7c..da9b42fc 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -19,6 +19,7 @@ #include "version.h" #include "Log.h" #include "Family.h" +#include "TunnelConfig.h" #include "RouterContext.h" namespace i2p @@ -41,6 +42,13 @@ namespace i2p CreateNewRouter (); m_Decryptor = m_Keys.CreateDecryptor (nullptr); UpdateRouterInfo (); + if (IsECIES ()) + { + auto initState = new i2p::crypto::NoiseSymmetricState (); + i2p::tunnel::InitBuildRequestRecordNoiseState (*initState); + initState->MixHash (GetIdentity ()->GetEncryptionPublicKey (), 32); // h = SHA256(h || hepk) + m_InitialNoiseState.reset (initState); + } } void RouterContext::CreateNewRouter () @@ -673,9 +681,32 @@ namespace i2p return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, true) : false; } - bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const + bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) { - return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, false) : false; + if (!m_Decryptor) return false; + if (IsECIES ()) + { + if (!m_InitialNoiseState) return false; + // m_InitialNoiseState is h = SHA256(h || hepk) + m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState (*m_InitialNoiseState)); + m_CurrentNoiseState->MixHash (encrypted, 32); // h = SHA256(h || sepk) + uint8_t sharedSecret[32]; + m_Decryptor->Decrypt (encrypted, sharedSecret, ctx, false); + m_CurrentNoiseState->MixKey (sharedSecret); + encrypted += 32; + uint8_t nonce[12]; + memset (nonce, 0, 12); + if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, TUNNEL_BUILD_RECORD_SIZE - 16, + m_CurrentNoiseState->m_H, 32, m_CurrentNoiseState->m_CK, nonce, data, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt + { + LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); + return false; + } + m_CurrentNoiseState->MixHash (encrypted, TUNNEL_BUILD_RECORD_SIZE); // h = SHA256(h || ciphertext) + return true; + } + else + return m_Decryptor->Decrypt (encrypted, data, ctx, false); } i2p::crypto::X25519Keys& RouterContext::GetStaticKeys () diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 37e1791d..402d3816 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -84,7 +84,7 @@ namespace i2p void SetError (RouterError error) { m_Status = eRouterStatusError; m_Error = error; }; int GetNetID () const { return m_NetID; }; void SetNetID (int netID) { m_NetID = netID; }; - bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const; + bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx); void UpdatePort (int port); // called from Daemon void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon @@ -109,7 +109,9 @@ namespace i2p bool SupportsV4 () const { return m_RouterInfo.IsV4 (); }; void SetSupportsV6 (bool supportsV6); void SetSupportsV4 (bool supportsV4); - + bool IsECIES () const { return GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET; }; + std::unique_ptr& GetCurrentNoiseState () { return m_CurrentNoiseState; }; + void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove void UpdateStats (); void UpdateTimestamp (uint64_t ts); // in seconds, called from NetDb before publishing @@ -160,6 +162,8 @@ namespace i2p std::mutex m_GarlicMutex; std::unique_ptr m_NTCP2Keys; std::unique_ptr m_StaticKeys; + // for ECIESx25519 + std::unique_ptr m_InitialNoiseState, m_CurrentNoiseState; }; extern RouterContext context; diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 61e878d4..c0ebb766 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -127,9 +127,7 @@ namespace tunnel void TunnelHopConfig::EncryptECIES (std::shared_ptr& encryptor, const uint8_t * plainText, uint8_t * encrypted, BN_CTX * ctx) { - static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars - memcpy (m_CK, protocolName, 32); // ck = h = protocol_name || 0 - SHA256 (m_CK, 32, m_H); // h = SHA256(h); + InitBuildRequestRecordNoiseState (*this); uint8_t hepk[32]; encryptor->Encrypt (nullptr, hepk, nullptr, false); MixHash (hepk, 32); // h = SHA256(h || hepk) @@ -150,5 +148,12 @@ namespace tunnel } MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) } + + void InitBuildRequestRecordNoiseState (i2p::crypto::NoiseSymmetricState& state) + { + static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars + memcpy (state.m_CK, protocolName, 32); // ck = h = protocol_name || 0 + SHA256 (state.m_CK, 32, state.m_H); // h = SHA256(h); + } } } \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 261394b4..518c1aad 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -45,6 +45,8 @@ namespace tunnel const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx); }; + void InitBuildRequestRecordNoiseState (i2p::crypto::NoiseSymmetricState& state); + class TunnelConfig { public: From d820b8036edfb6045eebd9223658b30b03570868 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Nov 2020 09:20:14 -0500 Subject: [PATCH 3886/6300] correct transient signature length --- libi2pd/Streaming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 21c6d6ce..80e8aecf 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -325,7 +325,7 @@ namespace stream if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) { uint8_t signature[256]; - auto signatureLen = m_RemoteIdentity->GetSignatureLen (); + auto signatureLen = m_TransientVerifier ? m_TransientVerifier->GetSignatureLen () : m_RemoteIdentity->GetSignatureLen (); if(signatureLen <= sizeof(signature)) { memcpy (signature, optionData, signatureLen); From 4e7aafeec13338e92c5293e848b4a207cdca9bce Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Nov 2020 15:23:13 -0500 Subject: [PATCH 3887/6300] send transit tunnel reply for ECIES router --- libi2pd/I2NPProtocol.cpp | 46 +++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 7778b9e4..4dbb4e18 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -463,22 +463,44 @@ namespace i2p } else { - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - if (HandleBuildRequestRecords (num, buf + 1, clearText)) + if (i2p::context.IsECIES ()) { - if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel + uint8_t clearText[TUNNEL_BUILD_RECORD_SIZE]; + if (HandleBuildRequestRecords (num, buf + 1, clearText)) { - // so we send it to reply tunnel - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - eI2NPVariableTunnelBuildReply, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel + { + // so we send it to reply tunnel + transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + eI2NPVariableTunnelBuildReply, buf, len, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + else + transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } - else - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } + else + { + uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (HandleBuildRequestRecords (num, buf + 1, clearText)) + { + if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel + { + // so we send it to reply tunnel + transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + eI2NPVariableTunnelBuildReply, buf, len, + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + else + transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, + bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } + } } } From f94d03465a25a04937b2d6a05da0fa3857cefcf3 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Nov 2020 15:38:25 -0500 Subject: [PATCH 3888/6300] don't create transit tunnel if decyrption failed --- libi2pd/I2NPProtocol.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 4dbb4e18..3cc254e6 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -361,8 +361,9 @@ namespace i2p { LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours"); BN_CTX * ctx = BN_CTX_new (); - i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText, ctx); + bool success = i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText, ctx); BN_CTX_free (ctx); + if(!success) return false; uint8_t retCode = 0; bool isECIES = i2p::context.IsECIES (); // replace record to reply From b8064b9b4b6a4534d7e4089d57376b32c55cb144 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Nov 2020 15:42:53 -0500 Subject: [PATCH 3889/6300] copy noise state --- libi2pd/RouterContext.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index da9b42fc..8b5f5b77 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -687,8 +687,10 @@ namespace i2p if (IsECIES ()) { if (!m_InitialNoiseState) return false; + m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState ()); // m_InitialNoiseState is h = SHA256(h || hepk) - m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState (*m_InitialNoiseState)); + memcpy (m_CurrentNoiseState->m_CK, m_InitialNoiseState->m_CK, 64); + memcpy (m_CurrentNoiseState->m_H, m_InitialNoiseState->m_H, 32); m_CurrentNoiseState->MixHash (encrypted, 32); // h = SHA256(h || sepk) uint8_t sharedSecret[32]; m_Decryptor->Decrypt (encrypted, sharedSecret, ctx, false); From 942b2b05e70b7882ca735aa038ed17ca85ed55ba Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Nov 2020 15:53:47 -0500 Subject: [PATCH 3890/6300] correct key for AEAD decryption --- libi2pd/RouterContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 8b5f5b77..4d60e193 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -699,7 +699,7 @@ namespace i2p uint8_t nonce[12]; memset (nonce, 0, 12); if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, TUNNEL_BUILD_RECORD_SIZE - 16, - m_CurrentNoiseState->m_H, 32, m_CurrentNoiseState->m_CK, nonce, data, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt + m_CurrentNoiseState->m_H, 32, m_CurrentNoiseState->m_CK + 32, nonce, data, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt { LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); return false; From bd04f92087ea151f60d59637f6099d884c157ea3 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Nov 2020 18:41:27 -0500 Subject: [PATCH 3891/6300] correct public key for ECIES address --- libi2pd/Identity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index c6e5a884..7784cc90 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -51,7 +51,7 @@ namespace data if (cryptoType == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) { memcpy (m_StandardIdentity.publicKey, publicKey, 32); - RAND_bytes (m_StandardIdentity.publicKey, 224); + RAND_bytes (m_StandardIdentity.publicKey + 32, 224); } else memcpy (m_StandardIdentity.publicKey, publicKey, 256); From d5f3d6111e7d80552a3a65b100e512d4e487770c Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 4 Nov 2020 11:52:33 -0500 Subject: [PATCH 3892/6300] correct tunnel build record size to decrept --- libi2pd/RouterContext.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 4d60e193..7525eceb 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -698,8 +698,8 @@ namespace i2p encrypted += 32; uint8_t nonce[12]; memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, TUNNEL_BUILD_RECORD_SIZE - 16, - m_CurrentNoiseState->m_H, 32, m_CurrentNoiseState->m_CK + 32, nonce, data, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, + m_CurrentNoiseState->m_H, 32, m_CurrentNoiseState->m_CK + 32, nonce, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, false)) // decrypt { LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); return false; From 21501cbf819a4bf20a82e3c902c8bedd9a6208c1 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 4 Nov 2020 13:31:28 -0500 Subject: [PATCH 3893/6300] correct MixHash after decryption --- libi2pd/RouterContext.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 7525eceb..4a142291 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -687,10 +687,8 @@ namespace i2p if (IsECIES ()) { if (!m_InitialNoiseState) return false; - m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState ()); // m_InitialNoiseState is h = SHA256(h || hepk) - memcpy (m_CurrentNoiseState->m_CK, m_InitialNoiseState->m_CK, 64); - memcpy (m_CurrentNoiseState->m_H, m_InitialNoiseState->m_H, 32); + m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState (*m_InitialNoiseState)); m_CurrentNoiseState->MixHash (encrypted, 32); // h = SHA256(h || sepk) uint8_t sharedSecret[32]; m_Decryptor->Decrypt (encrypted, sharedSecret, ctx, false); @@ -704,7 +702,7 @@ namespace i2p LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); return false; } - m_CurrentNoiseState->MixHash (encrypted, TUNNEL_BUILD_RECORD_SIZE); // h = SHA256(h || ciphertext) + m_CurrentNoiseState->MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) return true; } else From 1740715c001ff4e25ad7ffdc8f076b00ef4cab4e Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 4 Nov 2020 21:04:28 -0500 Subject: [PATCH 3894/6300] correct reply key and IV for ECIES record --- libi2pd/I2NPProtocol.cpp | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 3cc254e6..283c5641 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -410,20 +410,29 @@ namespace i2p for (int j = 0; j < num; j++) { uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; - if (isECIES && j == i) - { - uint8_t nonce[12]; - memset (nonce, 0, 12); - auto noiseState = std::move (i2p::context.GetCurrentNoiseState ()); - if (!noiseState || !i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState->m_H, 32, noiseState->m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + if (isECIES) + { + if (j == i) { - LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); - return false; + uint8_t nonce[12]; + memset (nonce, 0, 12); + auto noiseState = std::move (i2p::context.GetCurrentNoiseState ()); + if (!noiseState || !i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState->m_H, 32, noiseState->m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); + return false; + } + } + else + { + encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); + encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); + encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); } } else - { + { encryption.SetKey (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); encryption.SetIV (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); @@ -466,7 +475,7 @@ namespace i2p { if (i2p::context.IsECIES ()) { - uint8_t clearText[TUNNEL_BUILD_RECORD_SIZE]; + uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; if (HandleBuildRequestRecords (num, buf + 1, clearText)) { if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel From 6362a7bba55fd405d2ddd7a896970ddabb9f446d Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 5 Nov 2020 15:27:37 -0500 Subject: [PATCH 3895/6300] decrypt garlic on ECIES router --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 34 ++++++++++------------- libi2pd/RouterContext.cpp | 9 ++++++ libi2pd/RouterContext.h | 2 +- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index aef273ed..8b67960c 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -176,15 +176,6 @@ namespace garlic memcpy (m_H, hh, 32); } - void ECIESX25519AEADRatchetSession::MixHash (const uint8_t * buf, size_t len) - { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, m_H, 32); - SHA256_Update (&ctx, buf, len); - SHA256_Final (m_H, &ctx); - } - void ECIESX25519AEADRatchetSession::CreateNonce (uint64_t seqn, uint8_t * nonce) { memset (nonce, 0, 4); @@ -257,7 +248,7 @@ namespace garlic uint8_t sharedSecret[32]; GetOwner ()->Decrypt (m_Aepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519(bsk, aepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + MixKey (sharedSecret); // decrypt flags/static uint8_t nonce[12], fs[32]; @@ -277,7 +268,7 @@ namespace garlic // static key, fs is apk memcpy (m_RemoteStaticKey, fs, 32); GetOwner ()->Decrypt (fs, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519(bsk, apk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + MixKey (sharedSecret); } else // all zeros flags CreateNonce (1, nonce); @@ -289,10 +280,13 @@ namespace garlic LogPrint (eLogWarning, "Garlic: Payload section AEAD verification failed"); return false; } - if (isStatic) MixHash (buf, len); // h = SHA256(h || ciphertext) + m_State = eSessionStateNewSessionReceived; - GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); - + if (isStatic) + { + MixHash (buf, len); // h = SHA256(h || ciphertext) + GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); + } HandlePayload (payload.data (), len - 16, nullptr, 0); return true; @@ -469,7 +463,7 @@ namespace garlic MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + MixKey (sharedSecret); // encrypt static key section uint8_t nonce[12]; CreateNonce (0, nonce); @@ -482,7 +476,7 @@ namespace garlic offset += 48; // KDF2 GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bpk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + MixKey (sharedSecret); // encrypt payload if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt { @@ -520,9 +514,9 @@ namespace garlic MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk) uint8_t sharedSecret[32]; m_EphemeralKeys->Agree (m_Aepk, sharedSecret); // sharedSecret = x25519(besk, aepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) + MixKey (sharedSecret); m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // sharedSecret = x25519(besk, apk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + MixKey (sharedSecret); uint8_t nonce[12]; CreateNonce (0, nonce); // calculate hash for zero length @@ -607,9 +601,9 @@ namespace garlic { // only fist time, we assume ephemeral keys the same m_EphemeralKeys->Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK, 32); // chainKey = HKDF(chainKey, sharedSecret, "", 32) + MixKey (sharedSecret); GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bepk) - i2p::crypto::HKDF (m_CK, sharedSecret, 32, "", m_CK); // [chainKey, key] = HKDF(chainKey, sharedSecret, "", 64) + MixKey (sharedSecret); } uint8_t nonce[12]; CreateNonce (0, nonce); diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 4a142291..74f8ad34 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -653,6 +653,15 @@ namespace i2p i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len))); } + bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) + { + auto msg = CreateI2NPMessage (typeID, payload, len); + if (!msg) return false; + i2p::HandleI2NPMessage (msg); + return true; + } + + void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) { std::unique_lock l(m_GarlicMutex); diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 402d3816..139cc35e 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -135,7 +135,7 @@ namespace i2p // implements GarlicDestination void HandleI2NPMessage (const uint8_t * buf, size_t len); - bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) { return false; }; // not implemented + bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len); private: From 4ba1be2dc08a26a646e8c9946fbe9aeefc6425ec Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 5 Nov 2020 21:21:46 -0500 Subject: [PATCH 3896/6300] one time garlic encryption for ECIES routers --- libi2pd/Destination.cpp | 10 ++--- libi2pd/ECIESX25519AEADRatchetSession.cpp | 49 ++++++++++++++++++----- libi2pd/ECIESX25519AEADRatchetSession.h | 17 ++++---- libi2pd/Garlic.cpp | 17 ++++++-- libi2pd/Garlic.h | 4 +- 5 files changed, 66 insertions(+), 31 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 927e98a0..bf6047b2 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -559,9 +559,7 @@ namespace client m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); - auto msg = i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound); - if (floodfill->GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) // TODO: remove when implemented - msg = WrapMessage (floodfill, msg); + auto msg = WrapMessageForRouter (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); @@ -756,10 +754,8 @@ namespace client else AddSessionKey (replyKey, replyTag); - auto msg = CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, - request->replyTunnel, replyKey, replyTag, isECIES); - if (nextFloodfill->GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) // TODO: remove when implemented - msg = WrapMessage (nextFloodfill, msg); + auto msg = WrapMessageForRouter (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, + request->replyTunnel, replyKey, replyTag, isECIES)); request->outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 8b67960c..bd8d2fe6 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -446,7 +446,7 @@ namespace garlic LogPrint (eLogDebug, "Garlic: new send ratchet ", m_NextSendRatchet->newKey ? "new" : "old", " key ", m_NextSendRatchet->keyID, " created"); } - bool ECIESX25519AEADRatchetSession::NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) + bool ECIESX25519AEADRatchetSession::NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen, bool isStatic) { ResetKeys (); // we are Alice, bpk is m_RemoteStaticKey @@ -464,31 +464,47 @@ namespace garlic uint8_t sharedSecret[32]; m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) MixKey (sharedSecret); - // encrypt static key section + // encrypt flags/static key section uint8_t nonce[12]; CreateNonce (0, nonce); - if (!i2p::crypto::AEADChaCha20Poly1305 (GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET), 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt + const uint8_t * fs; + if (isStatic) + fs = GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); + else { - LogPrint (eLogWarning, "Garlic: Static section AEAD encryption failed "); + memset (out + offset, 0, 32); // all zeros flags section + fs = out + offset; + } + if (!i2p::crypto::AEADChaCha20Poly1305 (fs, 32, m_H, 32, m_CK + 32, nonce, out + offset, 48, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Flags/static section AEAD encryption failed "); return false; } + MixHash (out + offset, 48); // h = SHA256(h || ciphertext) offset += 48; // KDF2 - GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bpk) - MixKey (sharedSecret); + if (isStatic) + { + GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bpk) + MixKey (sharedSecret); + } + else + CreateNonce (1, nonce); // encrypt payload if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return false; } - MixHash (out + offset, len + 16); // h = SHA256(h || ciphertext) - + m_State = eSessionStateNewSessionSent; - if (GetOwner ()) - GenerateMoreReceiveTags (CreateNewSessionTagset (), ECIESX25519_NSR_NUM_GENERATED_TAGS); - + if (isStatic) + { + MixHash (out + offset, len + 16); // h = SHA256(h || ciphertext) + if (GetOwner ()) + GenerateMoreReceiveTags (CreateNewSessionTagset (), ECIESX25519_NSR_NUM_GENERATED_TAGS); + } return true; } @@ -778,6 +794,11 @@ namespace garlic return nullptr; len += 72; break; + case eSessionStateOneTime: + if (!NewOutgoingSessionMessage (payload.data (), payload.size (), buf, m->maxLen, false)) + return nullptr; + len += 96; + break; default: return nullptr; } @@ -788,6 +809,12 @@ namespace garlic return m; } + std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg) + { + m_State = eSessionStateOneTime; + return WrapSingleMessage (msg); + } + std::vector ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr msg, bool first) { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 1c377b28..755531a3 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -31,7 +31,7 @@ namespace garlic const int ECIESX25519_SEND_INACTIVITY_TIMEOUT = 5000; // number of milliseconds we can send empty(pyaload only) packet after const int ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT = 600; // in seconds const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180 - const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 4096; // number of tags we request new tagset after + const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 8192; // number of tags we request new tagset after const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 160; const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; @@ -129,7 +129,9 @@ namespace garlic const uint8_t ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG = 0x02; const uint8_t ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG = 0x04; - class ECIESX25519AEADRatchetSession: public GarlicRoutingSession, public std::enable_shared_from_this + class ECIESX25519AEADRatchetSession: public GarlicRoutingSession, + private i2p::crypto::NoiseSymmetricState, + public std::enable_shared_from_this { enum SessionState { @@ -137,7 +139,8 @@ namespace garlic eSessionStateNewSessionReceived, eSessionStateNewSessionSent, eSessionStateNewSessionReplySent, - eSessionStateEstablished + eSessionStateEstablished, + eSessionStateOneTime }; struct DHRatchet @@ -155,7 +158,8 @@ namespace garlic bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - + std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg); + const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } @@ -175,7 +179,6 @@ namespace garlic private: void ResetKeys (); - void MixHash (const uint8_t * buf, size_t len); void CreateNonce (uint64_t seqn, uint8_t * nonce); bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes std::shared_ptr CreateNewSessionTagset (); @@ -186,7 +189,7 @@ namespace garlic void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index); void HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset); - bool NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); + bool NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen, bool isStatic = true); bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); @@ -200,7 +203,7 @@ namespace garlic private: - uint8_t m_H[32], m_CK[64] /* [chainkey, key] */, m_RemoteStaticKey[32]; + uint8_t m_RemoteStaticKey[32]; uint8_t m_Aepk[32]; // Alice's ephemeral keys, for incoming only uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only std::shared_ptr m_EphemeralKeys; diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 5f76bca1..d8d034a1 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -709,11 +709,20 @@ namespace garlic } } - std::shared_ptr GarlicDestination::WrapMessage (std::shared_ptr destination, - std::shared_ptr msg, bool attachLeaseSet) + std::shared_ptr GarlicDestination::WrapMessageForRouter (std::shared_ptr router, + std::shared_ptr msg) { - auto session = GetRoutingSession (destination, attachLeaseSet); - return session->WrapSingleMessage (msg); + if (router->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + { + auto session = std::make_shared(this, false); + session->SetRemoteStaticKey (router->GetIdentity ()->GetEncryptionPublicKey ()); + return session->WrapOneTimeMessage (msg); + } + else + { + auto session = GetRoutingSession (router, false); + return session->WrapSingleMessage (msg); + } } std::shared_ptr GarlicDestination::GetRoutingSession ( diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 3c5d5de6..8df830c7 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -238,8 +238,8 @@ namespace garlic std::shared_ptr GetRoutingSession (std::shared_ptr destination, bool attachLeaseSet); void CleanupExpiredTags (); void RemoveDeliveryStatusSession (uint32_t msgID); - std::shared_ptr WrapMessage (std::shared_ptr destination, - std::shared_ptr msg, bool attachLeaseSet = false); + std::shared_ptr WrapMessageForRouter (std::shared_ptr router, + std::shared_ptr msg); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag From 07b77443ddbdacf9b49221d675d9c37e997a2c46 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 7 Nov 2020 18:28:38 -0500 Subject: [PATCH 3897/6300] don't handle TunnelBuild message for ECIES router --- libi2pd/I2NPProtocol.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 283c5641..5ad6df78 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -448,7 +448,7 @@ namespace i2p { int num = buf[0]; LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); - if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1) + if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1) { LogPrint (eLogError, "VaribleTunnelBuild message of ", num, " records is too short ", len); return; @@ -516,7 +516,12 @@ namespace i2p void HandleTunnelBuildMsg (uint8_t * buf, size_t len) { - if (len < NUM_TUNNEL_BUILD_RECORDS*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE) + if (i2p::context.IsECIES ()) + { + LogPrint (eLogWarning, "TunnelBuild is too old for ECIES router"); + return; + } + if (len < NUM_TUNNEL_BUILD_RECORDS*TUNNEL_BUILD_RECORD_SIZE) { LogPrint (eLogError, "TunnelBuild message is too short ", len); return; From 1c7780a423659ddd7fa49d9c672992a505a9dcb8 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 9 Nov 2020 15:35:50 -0500 Subject: [PATCH 3898/6300] garlic clove block for router --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index bd8d2fe6..1d51e67e 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -899,7 +899,7 @@ namespace garlic } } // msg - if (msg && m_Destination) + if (msg) offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset); // ack if (m_AckRequests.size () > 0) From 7e874eaa7ce707f45a3b2a46db74ae8d48b114a3 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 12 Nov 2020 15:15:02 -0500 Subject: [PATCH 3899/6300] pre-calculated h --- libi2pd/TunnelConfig.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index c0ebb766..a1b234a1 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -152,8 +152,13 @@ namespace tunnel void InitBuildRequestRecordNoiseState (i2p::crypto::NoiseSymmetricState& state) { static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars + static const uint8_t hh[32] = + { + 0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1, + 0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54 + }; // SHA256 (protocol_name || 0) memcpy (state.m_CK, protocolName, 32); // ck = h = protocol_name || 0 - SHA256 (state.m_CK, 32, state.m_H); // h = SHA256(h); + memcpy (state.m_H, hh, 32); // h = SHA256(h) } } } \ No newline at end of file From 62cd9fffa39bc296268b6134cd903d2705023835 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 15 Nov 2020 01:31:20 +0300 Subject: [PATCH 3900/6300] Automate AES-NI and AVX detection on runtime, make it default on x86-based systems (#1578) Rework CPU extensions detection code and build with AES-NI and AVX support by default --- .github/workflows/build-osx.yml | 20 +++ .github/workflows/build-qt.yml | 20 --- .github/workflows/build.yml | 18 ++- .gitignore | 3 +- Makefile | 7 +- Makefile.homebrew | 7 +- Makefile.linux | 21 +-- Makefile.mingw | 17 +-- Makefile.osx | 8 +- build/CMakeLists.txt | 16 +- build/build_mingw.cmd | 29 ++-- .../installer.iss => build/win_installer.iss | 13 +- contrib/i2pd.conf | 9 ++ daemon/Daemon.cpp | 5 +- debian/compat | 2 +- .../{02-fix-1210.patch => 01-fix-1210.patch} | 0 debian/patches/01-tune-build-opts.patch | 15 -- debian/patches/series | 3 +- libi2pd/CPU.cpp | 33 ++-- libi2pd/CPU.h | 2 +- libi2pd/Config.cpp | 18 ++- libi2pd/Crypto.cpp | 143 +++++++++--------- libi2pd/Crypto.h | 13 +- libi2pd/Identity.cpp | 2 +- libi2pd/api.cpp | 5 +- qt/i2pd_qt/i2pd_qt.pro | 16 +- 26 files changed, 208 insertions(+), 237 deletions(-) create mode 100644 .github/workflows/build-osx.yml delete mode 100644 .github/workflows/build-qt.yml rename Win32/installer.iss => build/win_installer.iss (75%) rename debian/patches/{02-fix-1210.patch => 01-fix-1210.patch} (100%) delete mode 100644 debian/patches/01-tune-build-opts.patch diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml new file mode 100644 index 00000000..50672d26 --- /dev/null +++ b/.github/workflows/build-osx.yml @@ -0,0 +1,20 @@ +name: Build on OSX + +on: [push, pull_request] + +jobs: + build: + name: With USE_UPNP=${{ matrix.with_upnp }} + runs-on: macOS-latest + strategy: + fail-fast: true + matrix: + with_upnp: ['yes', 'no'] + steps: + - uses: actions/checkout@v2 + - name: install packages + run: | + brew update + brew install boost miniupnpc openssl@1.1 + - name: build application + run: make HOMEBREW=1 USE_UPNP=${{ matrix.with_upnp }} PREFIX=$GITHUB_WORKSPACE/output -j3 diff --git a/.github/workflows/build-qt.yml b/.github/workflows/build-qt.yml deleted file mode 100644 index d4fde2b9..00000000 --- a/.github/workflows/build-qt.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Build on Ubuntu - -on: [push, pull_request] - -jobs: - build: - name: With QT GUI - runs-on: ubuntu-16.04 - steps: - - uses: actions/checkout@v2 - - name: install packages - run: | - sudo add-apt-repository ppa:mhier/libboost-latest - sudo apt-get update - sudo apt-get install build-essential qt5-default libqt5gui5 libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev - - name: build application - run: | - cd qt/i2pd_qt - qmake - make -j3 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e5152e0..bb995219 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,4 +18,20 @@ jobs: sudo apt-get update sudo apt-get install build-essential libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev - name: build application - run: make USE_AVX=no USE_AESNI=no USE_UPNP=${{ matrix.with_upnp }} -j3 + run: make USE_UPNP=${{ matrix.with_upnp }} -j3 + + build_qt: + name: With QT GUI + runs-on: ubuntu-16.04 + steps: + - uses: actions/checkout@v2 + - name: install packages + run: | + sudo add-apt-repository ppa:mhier/libboost-latest + sudo apt-get update + sudo apt-get install build-essential qt5-default libqt5gui5 libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev + - name: build application + run: | + cd qt/i2pd_qt + qmake + make -j3 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3319fa10..2506fd93 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,12 @@ router.info router.keys i2p -libi2pd.so netDb /i2pd /libi2pd.a /libi2pdclient.a +/libi2pd.so +/libi2pdclient.so *.exe diff --git a/Makefile b/Makefile index 35d76b82..c359d7c7 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,6 @@ DAEMON_SRC_DIR := daemon include filelist.mk USE_AESNI := yes -USE_AVX := yes USE_STATIC := no USE_MESHNET := no USE_UPNP := no @@ -77,7 +76,7 @@ deps: mk_obj_dir @sed -i -e '/\.o:/ s/^/obj\//' $(DEPS) obj/%.o: %.cpp - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS) -c -o $@ $< + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -c -o $@ $< # '-' is 'ignore if missing' on first run -include $(DEPS) @@ -88,11 +87,11 @@ $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) endif $(SHLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) - $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) $(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) $(AR) -r $@ $^ diff --git a/Makefile.homebrew b/Makefile.homebrew index 64301c02..c1992296 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -35,10 +35,7 @@ endif # Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2 # Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic ifeq ($(USE_AESNI),yes) - CXXFLAGS += -maes -endif -ifeq ($(USE_AVX),1) - CXXFLAGS += -mavx + CXXFLAGS += -D__AES__ -maes endif install: all @@ -51,4 +48,4 @@ install: all @ln -sf ${PREFIX}/share/i2pd/certificates ${PREFIX}/var/lib/i2pd/ @ln -sf ${PREFIX}/etc/i2pd/i2pd.conf ${PREFIX}/var/lib/i2pd/i2pd.conf @ln -sf ${PREFIX}/etc/i2pd/subscriptions.txt ${PREFIX}/var/lib/i2pd/subscriptions.txt - @ln -sf ${PREFIX}/etc/i2pd/tunnels.conf ${PREFIX}/var/lib/i2pd/tunnels.conf \ No newline at end of file + @ln -sf ${PREFIX}/etc/i2pd/tunnels.conf ${PREFIX}/var/lib/i2pd/tunnels.conf diff --git a/Makefile.linux b/Makefile.linux index 3ef4793c..6a7590c1 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -1,5 +1,5 @@ # set defaults instead redefine -CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation -Wno-psabi +CXXFLAGS ?= ${CXX_DEBUG} -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi LDFLAGS ?= ${LD_DEBUG} ## NOTE: The NEEDED_CXXFLAGS are here so that custom CXXFLAGS can be specified at build time @@ -49,7 +49,7 @@ endif # UPNP Support (miniupnpc 1.5 and higher) ifeq ($(USE_UPNP),yes) - CXXFLAGS += -DUSE_UPNP + NEEDED_CXXFLAGS += -DUSE_UPNP ifeq ($(USE_STATIC),yes) LDLIBS += $(LIBDIR)/libminiupnpc.a else @@ -58,20 +58,7 @@ endif endif ifeq ($(USE_AESNI),yes) -#check if AES-NI is supported by CPU -ifneq ($(shell $(GREP) -c aes /proc/cpuinfo),0) - machine := $(shell uname -m) - ifeq ($(machine), aarch64) - CXXFLAGS += -DARM64AES - else - CPU_FLAGS += -maes - endif -endif -endif - -ifeq ($(USE_AVX),yes) -#check if AVX supported by CPU -ifneq ($(shell $(GREP) -c avx /proc/cpuinfo),0) - CPU_FLAGS += -mavx +ifeq (, $(findstring arm, $(SYS))$(findstring aarch64, $(SYS))) # no arm and aarch64 in dumpmachine + NEEDED_CXXFLAGS += -D__AES__ -maes endif endif diff --git a/Makefile.mingw b/Makefile.mingw index 2782b715..764606b6 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,7 +1,7 @@ USE_WIN32_APP=yes CXX = g++ WINDRES = windres -CXXFLAGS := ${CXX_DEBUG} -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN +CXXFLAGS := ${CXX_DEBUG} -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN -fPIC -msse INCFLAGS = -Idaemon -I. LDFLAGS := ${LD_DEBUG} -Wl,-Bstatic -static-libgcc -static-libstdc++ @@ -42,25 +42,18 @@ LDLIBS += \ -lpthread ifeq ($(USE_WIN32_APP), yes) - CXXFLAGS += -DWIN32_APP + NEEDED_CXXFLAGS += -DWIN32_APP LDFLAGS += -mwindows DAEMON_RC += Win32/Resource.rc DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif ifeq ($(USE_WINXP_FLAGS), yes) - CXXFLAGS += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 + NEEDED_CXXFLAGS += -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 endif -# don't change following line to ifeq ($(USE_AESNI),yes) !!! -ifeq ($(USE_AESNI),1) - CPU_FLAGS += -maes -else - CPU_FLAGS += -msse -endif - -ifeq ($(USE_AVX),1) - CPU_FLAGS += -mavx +ifeq ($(USE_AESNI),yes) + NEEDED_CXXFLAGS += -D__AES__ -maes endif ifeq ($(USE_ASLR),yes) diff --git a/Makefile.osx b/Makefile.osx index c6af4fc2..2e52585e 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -22,12 +22,8 @@ ifeq ($(USE_UPNP),yes) endif endif -ifeq ($(USE_AESNI),1) - CXXFLAGS += -maes +ifeq ($(USE_AESNI),yes) + CXXFLAGS += -D__AES__ -maes else CXXFLAGS += -msse endif - -ifeq ($(USE_AVX),1) - CXXFLAGS += -mavx -endif diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 827e20d3..6f9fbae6 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -11,9 +11,8 @@ if(WIN32 OR MSVC OR MSYS OR MINGW) message(SEND_ERROR "cmake build for windows is not supported. Please use MSYS2 with makefiles in project root.") endif() -# configurale options -option(WITH_AESNI "Use AES-NI instructions set" OFF) -option(WITH_AVX "Use AVX instructions" OFF) +# configurable options +option(WITH_AESNI "Use AES-NI instructions set" ON) option(WITH_HARDENING "Use hardening compiler flags" OFF) option(WITH_LIBRARY "Build library" ON) option(WITH_BINARY "Build binary" ON) @@ -189,13 +188,11 @@ if(UNIX) endif() endif() -if(WITH_AESNI) +# Note: AES-NI and AVX is available on x86-based CPU's. +# Here also ARM64 implementation, but currently we don't support it. +if(WITH_AESNI AND (ARCHITECTURE MATCHES "x86_64" OR ARCHITECTURE MATCHES "i386")) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes") - add_definitions(-DAESNI) -endif() - -if(WITH_AVX) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx") + add_definitions(-D__AES__) endif() if(WITH_ADDRSANITIZER) @@ -309,7 +306,6 @@ message(STATUS "Architecture : ${ARCHITECTURE}") message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}") message(STATUS "Options:") message(STATUS " AESNI : ${WITH_AESNI}") -message(STATUS " AVX : ${WITH_AVX}") message(STATUS " HARDENING : ${WITH_HARDENING}") message(STATUS " LIBRARY : ${WITH_LIBRARY}") message(STATUS " BINARY : ${WITH_BINARY}") diff --git a/build/build_mingw.cmd b/build/build_mingw.cmd index ec861bb2..37a1d454 100644 --- a/build/build_mingw.cmd +++ b/build/build_mingw.cmd @@ -30,10 +30,10 @@ REM we must work in root of repo cd .. REM deleting old log files -del /S build_*.log >> nul +del /S build_*.log >> nul 2>&1 echo Receiving latest commit and cleaning up... -%xSH% "git pull && make clean" > build/build_git.log 2>&1 +%xSH% "git checkout contrib/* && git pull && make clean" > build/build.log 2>&1 echo. REM set to variable current commit hash @@ -43,16 +43,17 @@ FOR /F "usebackq" %%a IN (`%xSH% 'git describe --tags'`) DO ( %xSH% "echo To use configs and certificates, move all files and certificates folder from contrib directory here. > README.txt" >> nul +REM converting configuration files to DOS format (usable in default notepad) +%xSH% "unix2dos contrib/i2pd.conf contrib/tunnels.conf contrib/tunnels.d/*" > build/build.log 2>&1 + REM starting building set MSYSTEM=MINGW32 set bitness=32 call :BUILDING -echo. set MSYSTEM=MINGW64 set bitness=64 call :BUILDING -echo. REM building for WinXP set "WD=C:\msys64-xp\usr\bin\" @@ -62,7 +63,10 @@ set "xSH=%WD%bash -lc" call :BUILDING_XP echo. -del README.txt >> nul +REM compile installer +C:\PROGRA~2\INNOSE~1\ISCC.exe build\win_installer.iss + +del README.txt i2pd_x32.exe i2pd_x64.exe i2pd_xp.exe >> nul echo Build complete... pause @@ -70,20 +74,13 @@ exit /b 0 :BUILDING %xSH% "make clean" >> nul -echo Building i2pd %tag% for win%bitness%: -echo Build AVX+AESNI... -%xSH% "make DEBUG=no USE_UPNP=yes USE_AVX=1 USE_AESNI=1 -j%threads% && zip -r9 build/i2pd_%tag%_win%bitness%_mingw_avx_aesni.zip %FILELIST% && make clean" > build/build_win%bitness%_avx_aesni_%tag%.log 2>&1 -echo Build AVX... -%xSH% "make DEBUG=no USE_UPNP=yes USE_AVX=1 -j%threads% && zip -r9 build/i2pd_%tag%_win%bitness%_mingw_avx.zip %FILELIST% && make clean" > build/build_win%bitness%_avx_%tag%.log 2>&1 -echo Build AESNI... -%xSH% "make DEBUG=no USE_UPNP=yes USE_AESNI=1 -j%threads% && zip -r9 build/i2pd_%tag%_win%bitness%_mingw_aesni.zip %FILELIST% && make clean" > build/build_win%bitness%_aesni_%tag%.log 2>&1 -echo Build without extensions... -%xSH% "make DEBUG=no USE_UPNP=yes -j%threads% && zip -r9 build/i2pd_%tag%_win%bitness%_mingw.zip %FILELIST% && make clean" > build/build_win%bitness%_%tag%.log 2>&1 +echo Building i2pd %tag% for win%bitness% +%xSH% "make DEBUG=no USE_UPNP=yes -j%threads% && cp i2pd.exe i2pd_x%bitness%.exe && zip -r9 build/i2pd_%tag%_win%bitness%_mingw.zip %FILELIST% && make clean" > build/build_win%bitness%_%tag%.log 2>&1 goto EOF :BUILDING_XP %xSH% "make clean" >> nul -echo Building i2pd %tag% for winxp... -%xSH% "make DEBUG=no USE_UPNP=yes USE_WINXP_FLAGS=yes -j%threads% && zip -r9 build/i2pd_%tag%_winxp_mingw.zip %FILELIST% && make clean" > build/build_winxp_%tag%.log 2>&1 +echo Building i2pd %tag% for winxp +%xSH% "make DEBUG=no USE_UPNP=yes USE_WINXP_FLAGS=yes -j%threads% && cp i2pd.exe i2pd_xp.exe && zip -r9 build/i2pd_%tag%_winxp_mingw.zip %FILELIST% && make clean" > build/build_winxp_%tag%.log 2>&1 :EOF \ No newline at end of file diff --git a/Win32/installer.iss b/build/win_installer.iss similarity index 75% rename from Win32/installer.iss rename to build/win_installer.iss index cc40c409..007cd643 100644 --- a/Win32/installer.iss +++ b/build/win_installer.iss @@ -1,6 +1,7 @@ #define I2Pd_AppName "i2pd" -#define I2Pd_ver "2.34.0" #define I2Pd_Publisher "PurpleI2P" +; Get application version from compiled binary +#define I2Pd_ver GetFileVersionString(AddBackslash(SourcePath) + "..\i2pd_x64.exe") [Setup] AppName={#I2Pd_AppName} @@ -10,9 +11,9 @@ DefaultDirName={pf}\I2Pd DefaultGroupName=I2Pd UninstallDisplayIcon={app}\I2Pd.exe OutputDir=. -LicenseFile=../LICENSE +LicenseFile=..\LICENSE OutputBaseFilename=setup_{#I2Pd_AppName}_v{#I2Pd_ver} -SetupIconFile=mask.ico +SetupIconFile=..\Win32\mask.ico InternalCompressLevel=ultra64 Compression=lzma/ultra64 SolidCompression=true @@ -23,10 +24,12 @@ AppID={{621A23E0-3CF4-4BD6-97BC-4835EA5206A2} AppPublisherURL=http://i2pd.website/ AppSupportURL=https://github.com/PurpleI2P/i2pd/issues AppUpdatesURL=https://github.com/PurpleI2P/i2pd/releases +CloseApplications=yes [Files] -Source: ..\i2pd_x86.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: not IsWin64 -Source: ..\i2pd_x64.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: IsWin64 +Source: ..\i2pd_x32.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: not IsWin64; MinVersion: 6.0 +Source: ..\i2pd_x64.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: IsWin64; MinVersion: 6.0 +Source: ..\i2pd_xp.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: IsWin64; OnlyBelowVersion: 6.0 Source: ..\README.md; DestDir: {app}; DestName: Readme.txt; Flags: onlyifdoesntexist Source: ..\contrib\i2pd.conf; DestDir: {userappdata}\i2pd; Flags: onlyifdoesntexist Source: ..\contrib\subscriptions.txt; DestDir: {userappdata}\i2pd; Flags: onlyifdoesntexist diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 2174abe8..5ef39bc9 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -229,3 +229,12 @@ verify = true [persist] ## Save peer profiles on disk (default: true) # profiles = true + +[cpuext] +## Use CPU AES-NI instructions set when work with cryptography when available (default: true) +# aesni = true +## Use CPU AVX instructions set when work with cryptography when available (default: true) +# avx = true +## Force usage of CPU instructions set, even if they not found +## DO NOT TOUCH that option if you really don't know what are you doing! +# force = false \ No newline at end of file diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 0317704a..839495a5 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -128,7 +128,10 @@ namespace i2p LogPrint(eLogDebug, "FS: data directory: ", datadir); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); - i2p::crypto::InitCrypto (precomputation); + bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); + bool avx; i2p::config::GetOption("cpuext.avx", avx); + bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt); + i2p::crypto::InitCrypto (precomputation, aesni, avx, forceCpuExt); int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); diff --git a/debian/compat b/debian/compat index f11c82a4..9a037142 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -9 \ No newline at end of file +10 \ No newline at end of file diff --git a/debian/patches/02-fix-1210.patch b/debian/patches/01-fix-1210.patch similarity index 100% rename from debian/patches/02-fix-1210.patch rename to debian/patches/01-fix-1210.patch diff --git a/debian/patches/01-tune-build-opts.patch b/debian/patches/01-tune-build-opts.patch deleted file mode 100644 index e06a8621..00000000 --- a/debian/patches/01-tune-build-opts.patch +++ /dev/null @@ -1,15 +0,0 @@ -Index: i2pd/Makefile -=================================================================== ---- i2pd.orig/Makefile -+++ i2pd/Makefile -@@ -13,8 +13,8 @@ DAEMON_SRC_DIR := daemon - - include filelist.mk - --USE_AESNI := yes --USE_AVX := yes -+USE_AESNI := no -+USE_AVX := no - USE_STATIC := no - USE_MESHNET := no - USE_UPNP := no diff --git a/debian/patches/series b/debian/patches/series index 002802b5..07f821c8 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,2 +1 @@ -01-tune-build-opts.patch -02-fix-1210.patch +01-fix-1210.patch diff --git a/libi2pd/CPU.cpp b/libi2pd/CPU.cpp index e7eff473..0bd830e3 100644 --- a/libi2pd/CPU.cpp +++ b/libi2pd/CPU.cpp @@ -27,37 +27,24 @@ namespace cpu bool aesni = false; bool avx = false; - void Detect() + void Detect(bool AesSwitch, bool AvxSwitch, bool force) { -#if defined(__AES__) || defined(__AVX__) - #if defined(__x86_64__) || defined(__i386__) int info[4]; __cpuid(0, info[0], info[1], info[2], info[3]); if (info[0] >= 0x00000001) { __cpuid(0x00000001, info[0], info[1], info[2], info[3]); -#ifdef __AES__ - aesni = info[2] & bit_AES; // AESNI -#endif // __AES__ -#ifdef __AVX__ - avx = info[2] & bit_AVX; // AVX -#endif // __AVX__ + if ((info[2] & bit_AES && AesSwitch) || (AesSwitch && force)) { + aesni = true; + } + if ((info[2] & bit_AVX && AvxSwitch) || (AvxSwitch && force)) { + avx = true; + } } -#endif // defined(__x86_64__) || defined(__i386__) +#endif // defined(__x86_64__) || defined(__i386__) -#ifdef __AES__ - if(aesni) - { - LogPrint(eLogInfo, "AESNI enabled"); - } -#endif // __AES__ -#ifdef __AVX__ - if(avx) - { - LogPrint(eLogInfo, "AVX enabled"); - } -#endif // __AVX__ -#endif // defined(__AES__) || defined(__AVX__) + LogPrint(eLogInfo, "AESNI ", (aesni ? "enabled" : "disabled")); + LogPrint(eLogInfo, "AVX ", (avx ? "enabled" : "disabled")); } } } diff --git a/libi2pd/CPU.h b/libi2pd/CPU.h index 9677b293..f021bccb 100644 --- a/libi2pd/CPU.h +++ b/libi2pd/CPU.h @@ -16,7 +16,7 @@ namespace cpu extern bool aesni; extern bool avx; - void Detect(); + void Detect(bool AesSwitch, bool AvxSwitch, bool force); } } diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index fc479532..d99fdde1 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -47,11 +47,11 @@ namespace config { ("ifname", value()->default_value(""), "Network interface to bind to") ("ifname4", value()->default_value(""), "Network interface to bind to for ipv4") ("ifname6", value()->default_value(""), "Network interface to bind to for ipv6") - ("nat", value()->default_value(true), "Should we assume we are behind NAT? (default: enabled)") + ("nat", bool_switch()->default_value(true), "Should we assume we are behind NAT? (default: enabled)") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") - ("ipv4", value()->default_value(true), "Enable communication through ipv4 (default: enabled)") + ("ipv4", bool_switch()->default_value(true), "Enable communication through ipv4 (default: enabled)") ("ipv6", bool_switch()->default_value(false), "Enable communication through ipv6 (default: disabled)") - ("reservedrange", value()->default_value(true), "Check remote RI for being in blacklist of reserved IP ranges (default: enabled)") + ("reservedrange", bool_switch()->default_value(true), "Check remote RI for being in blacklist of reserved IP ranges (default: enabled)") ("netid", value()->default_value(I2PD_NET_ID), "Specify NetID. Main I2P is 2") ("daemon", bool_switch()->default_value(false), "Router will go to background after start (default: disabled)") ("service", bool_switch()->default_value(false), "Router will use system folders like '/var/lib/i2pd' (default: disabled)") @@ -59,8 +59,8 @@ namespace config { ("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)") ("bandwidth", value()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)") ("share", value()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)") - ("ntcp", value()->default_value(false), "Ignored. Always false") - ("ssu", value()->default_value(true), "Enable SSU transport (default: enabled)") + ("ntcp", bool_switch()->default_value(false), "Ignored. Always false") + ("ssu", bool_switch()->default_value(true), "Enable SSU transport (default: enabled)") ("ntcpproxy", value()->default_value(""), "Ignored") #ifdef _WIN32 ("svcctl", value()->default_value(""), "Windows service management ('install' or 'remove')") @@ -266,6 +266,13 @@ namespace config { ("persist.addressbook", value()->default_value(true), "Persist full addresses (default: true)") ; + options_description cpuext("CPU encryption extensions options"); + cpuext.add_options() + ("cpuext.aesni", bool_switch()->default_value(true), "Use auto detection for AESNI CPU extensions. If false, AESNI will be not used") + ("cpuext.avx", bool_switch()->default_value(true), "Use auto detection for AVX CPU extensions. If false, AVX will be not used") + ("cpuext.force", bool_switch()->default_value(false), "Force usage of CPU extensions. Useful when cpuinfo is not available on virtual machines") + ; + m_OptionsDesc .add(general) .add(limits) @@ -286,6 +293,7 @@ namespace config { .add(ntcp2) .add(nettime) .add(persist) + .add(cpuext) ; } diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index ea2cd675..523828ce 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -119,7 +119,7 @@ namespace crypto ~CryptoConstants () { - BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae); + BN_free (elgp); BN_free (elgg); BN_free (dsap); BN_free (dsaq); BN_free (dsag); BN_free (rsae); } }; @@ -522,7 +522,7 @@ namespace crypto bn2buf (y, encrypted + len, len); RAND_bytes (encrypted + 2*len, 256 - 2*len); } - // ecryption key and iv + // encryption key and iv EC_POINT_mul (curve, p, nullptr, key, k, ctx); EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); uint8_t keyBuf[64], iv[64], shared[32]; @@ -638,7 +638,7 @@ namespace crypto { uint64_t buf[256]; uint64_t hash[12]; // 96 bytes -#ifdef __AVX__ +#if defined(__x86_64__) || defined(__i386__) if(i2p::cpu::avx) { __asm__ @@ -657,7 +657,7 @@ namespace crypto : : [key]"m"(*(const uint8_t *)key), [ipad]"m"(*ipads), [opad]"m"(*opads), [buf]"r"(buf), [hash]"r"(hash) - : "memory", "%xmm0" // TODO: change to %ymm0 later + : "memory", "%xmm0" // TODO: change to %ymm0 later ); } else @@ -688,7 +688,7 @@ namespace crypto // concatenate with msg memcpy (buf + 8, msg, len); // calculate first hash - MD5((uint8_t *)buf, len + 64, (uint8_t *)(hash + 8)); // 16 bytes + MD5((uint8_t *)buf, len + 64, (uint8_t *)(hash + 8)); // 16 bytes // calculate digest MD5((uint8_t *)hash, 96, digest); @@ -696,35 +696,28 @@ namespace crypto // AES #ifdef __AES__ - #ifdef ARM64AES - void init_aesenc(void){ - // TODO: Implementation - } - - #endif - #define KeyExpansion256(round0,round1) \ - "pshufd $0xff, %%xmm2, %%xmm2 \n" \ - "movaps %%xmm1, %%xmm4 \n" \ - "pslldq $4, %%xmm4 \n" \ + "pshufd $0xff, %%xmm2, %%xmm2 \n" \ + "movaps %%xmm1, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm1 \n" \ - "pslldq $4, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm1 \n" \ - "pslldq $4, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm1 \n" \ "pxor %%xmm2, %%xmm1 \n" \ - "movaps %%xmm1, "#round0"(%[sched]) \n" \ + "movaps %%xmm1, "#round0"(%[sched]) \n" \ "aeskeygenassist $0, %%xmm1, %%xmm4 \n" \ - "pshufd $0xaa, %%xmm4, %%xmm2 \n" \ - "movaps %%xmm3, %%xmm4 \n" \ - "pslldq $4, %%xmm4 \n" \ + "pshufd $0xaa, %%xmm4, %%xmm2 \n" \ + "movaps %%xmm3, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm3 \n" \ - "pslldq $4, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm3 \n" \ - "pslldq $4, %%xmm4 \n" \ + "pslldq $4, %%xmm4 \n" \ "pxor %%xmm4, %%xmm3 \n" \ "pxor %%xmm2, %%xmm3 \n" \ - "movaps %%xmm3, "#round1"(%[sched]) \n" + "movaps %%xmm3, "#round1"(%[sched]) \n" #endif #ifdef __AES__ @@ -750,16 +743,16 @@ namespace crypto KeyExpansion256(192,208) "aeskeygenassist $64, %%xmm3, %%xmm2 \n" // key expansion final - "pshufd $0xff, %%xmm2, %%xmm2 \n" - "movaps %%xmm1, %%xmm4 \n" - "pslldq $4, %%xmm4 \n" + "pshufd $0xff, %%xmm2, %%xmm2 \n" + "movaps %%xmm1, %%xmm4 \n" + "pslldq $4, %%xmm4 \n" "pxor %%xmm4, %%xmm1 \n" - "pslldq $4, %%xmm4 \n" + "pslldq $4, %%xmm4 \n" "pxor %%xmm4, %%xmm1 \n" - "pslldq $4, %%xmm4 \n" + "pslldq $4, %%xmm4 \n" "pxor %%xmm4, %%xmm1 \n" "pxor %%xmm2, %%xmm1 \n" - "movups %%xmm1, 224(%[sched]) \n" + "movups %%xmm1, 224(%[sched]) \n" : // output : [key]"r"((const uint8_t *)key), [sched]"r"(GetKeySchedule ()) // input : "%xmm1", "%xmm2", "%xmm3", "%xmm4", "memory" // clogged @@ -794,9 +787,9 @@ namespace crypto { __asm__ ( - "movups (%[in]), %%xmm0 \n" + "movups (%[in]), %%xmm0 \n" EncryptAES256(sched) - "movups %%xmm0, (%[out]) \n" + "movups %%xmm0, (%[out]) \n" : : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory" ); } @@ -833,9 +826,9 @@ namespace crypto { __asm__ ( - "movups (%[in]), %%xmm0 \n" + "movups (%[in]), %%xmm0 \n" DecryptAES256(sched) - "movups %%xmm0, (%[out]) \n" + "movups %%xmm0, (%[out]) \n" : : [sched]"r"(GetKeySchedule ()), [in]"r"(in), [out]"r"(out) : "%xmm0", "memory" ); } @@ -848,7 +841,7 @@ namespace crypto #ifdef __AES__ #define CallAESIMC(offset) \ - "movaps "#offset"(%[shed]), %%xmm0 \n" \ + "movaps "#offset"(%[shed]), %%xmm0 \n" \ "aesimc %%xmm0, %%xmm0 \n" \ "movaps %%xmm0, "#offset"(%[shed]) \n" #endif @@ -873,7 +866,7 @@ namespace crypto if(i2p::cpu::aesni) { ExpandKey (key); // expand encryption key first - // then invert it using aesimc + // then invert it using aesimc __asm__ ( CallAESIMC(16) @@ -906,18 +899,18 @@ namespace crypto { __asm__ ( - "movups (%[iv]), %%xmm1 \n" + "movups (%[iv]), %%xmm1 \n" "1: \n" - "movups (%[in]), %%xmm0 \n" + "movups (%[in]), %%xmm0 \n" "pxor %%xmm1, %%xmm0 \n" EncryptAES256(sched) - "movaps %%xmm0, %%xmm1 \n" - "movups %%xmm0, (%[out]) \n" + "movaps %%xmm0, %%xmm1 \n" + "movups %%xmm0, (%[out]) \n" "add $16, %[in] \n" "add $16, %[out] \n" "dec %[num] \n" "jnz 1b \n" - "movups %%xmm1, (%[iv]) \n" + "movups %%xmm1, (%[iv]) \n" : : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), [in]"r"(in), [out]"r"(out), [num]"r"(numBlocks) @@ -951,12 +944,12 @@ namespace crypto { __asm__ ( - "movups (%[iv]), %%xmm1 \n" - "movups (%[in]), %%xmm0 \n" + "movups (%[iv]), %%xmm1 \n" + "movups (%[in]), %%xmm0 \n" "pxor %%xmm1, %%xmm0 \n" EncryptAES256(sched) - "movups %%xmm0, (%[out]) \n" - "movups %%xmm0, (%[iv]) \n" + "movups %%xmm0, (%[out]) \n" + "movups %%xmm0, (%[iv]) \n" : : [iv]"r"((uint8_t *)m_LastBlock), [sched]"r"(m_ECBEncryption.GetKeySchedule ()), [in]"r"(in), [out]"r"(out) @@ -975,19 +968,19 @@ namespace crypto { __asm__ ( - "movups (%[iv]), %%xmm1 \n" + "movups (%[iv]), %%xmm1 \n" "1: \n" - "movups (%[in]), %%xmm0 \n" + "movups (%[in]), %%xmm0 \n" "movaps %%xmm0, %%xmm2 \n" DecryptAES256(sched) "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" + "movups %%xmm0, (%[out]) \n" "movaps %%xmm2, %%xmm1 \n" "add $16, %[in] \n" "add $16, %[out] \n" "dec %[num] \n" "jnz 1b \n" - "movups %%xmm1, (%[iv]) \n" + "movups %%xmm1, (%[iv]) \n" : : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), [in]"r"(in), [out]"r"(out), [num]"r"(numBlocks) @@ -1021,12 +1014,12 @@ namespace crypto { __asm__ ( - "movups (%[iv]), %%xmm1 \n" - "movups (%[in]), %%xmm0 \n" - "movups %%xmm0, (%[iv]) \n" + "movups (%[iv]), %%xmm1 \n" + "movups (%[in]), %%xmm0 \n" + "movups %%xmm0, (%[iv]) \n" DecryptAES256(sched) "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" + "movups %%xmm0, (%[out]) \n" : : [iv]"r"((uint8_t *)m_IV), [sched]"r"(m_ECBDecryption.GetKeySchedule ()), [in]"r"(in), [out]"r"(out) @@ -1046,7 +1039,7 @@ namespace crypto __asm__ ( // encrypt IV - "movups (%[in]), %%xmm0 \n" + "movups (%[in]), %%xmm0 \n" EncryptAES256(sched_iv) "movaps %%xmm0, %%xmm1 \n" // double IV encryption @@ -1056,11 +1049,11 @@ namespace crypto "1: \n" "add $16, %[in] \n" "add $16, %[out] \n" - "movups (%[in]), %%xmm0 \n" + "movups (%[in]), %%xmm0 \n" "pxor %%xmm1, %%xmm0 \n" EncryptAES256(sched_l) - "movaps %%xmm0, %%xmm1 \n" - "movups %%xmm0, (%[out]) \n" + "movaps %%xmm0, %%xmm1 \n" + "movups %%xmm0, (%[out]) \n" "dec %[num] \n" "jnz 1b \n" : @@ -1097,11 +1090,11 @@ namespace crypto "1: \n" "add $16, %[in] \n" "add $16, %[out] \n" - "movups (%[in]), %%xmm0 \n" + "movups (%[in]), %%xmm0 \n" "movaps %%xmm0, %%xmm2 \n" DecryptAES256(sched_l) "pxor %%xmm1, %%xmm0 \n" - "movups %%xmm0, (%[out]) \n" + "movups %%xmm0, (%[out]) \n" "movaps %%xmm2, %%xmm1 \n" "dec %[num] \n" "jnz 1b \n" @@ -1324,23 +1317,23 @@ namespace crypto } void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len) - { - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, m_H, 32); - SHA256_Update (&ctx, buf, len); - SHA256_Final (m_H, &ctx); - } + { + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, m_H, 32); + SHA256_Update (&ctx, buf, len); + SHA256_Final (m_H, &ctx); + } - void NoiseSymmetricState::MixKey (const uint8_t * sharedSecret) - { - HKDF (m_CK, sharedSecret, 32, "", m_CK); + void NoiseSymmetricState::MixKey (const uint8_t * sharedSecret) + { + HKDF (m_CK, sharedSecret, 32, "", m_CK); // new ck is m_CK[0:31], key is m_CK[32:63] - } - + } + // init and terminate -/* std::vector > m_OpenSSLMutexes; +/* std::vector > m_OpenSSLMutexes; static void OpensslLockingCallback(int mode, int type, const char * file, int line) { if (type > 0 && (size_t)type < m_OpenSSLMutexes.size ()) @@ -1351,10 +1344,10 @@ namespace crypto m_OpenSSLMutexes[type]->unlock (); } }*/ - - void InitCrypto (bool precomputation) + + void InitCrypto (bool precomputation, bool aesni, bool avx, bool force) { - i2p::cpu::Detect (); + i2p::cpu::Detect (aesni, avx, force); #if LEGACY_OPENSSL SSL_library_init (); #endif diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 205be44d..da7a4bf4 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -169,9 +169,6 @@ namespace crypto #ifdef __AES__ - #ifdef ARM64AES - void init_aesenc(void) __attribute__((constructor)); - #endif class ECBCryptoAESNI { public: @@ -316,13 +313,13 @@ namespace crypto struct NoiseSymmetricState { uint8_t m_H[32] /*h*/, m_CK[64] /*[ck, k]*/; - + void MixHash (const uint8_t * buf, size_t len); - void MixKey (const uint8_t * sharedSecret); - }; - + void MixKey (const uint8_t * sharedSecret); + }; + // init and terminate - void InitCrypto (bool precomputation); + void InitCrypto (bool precomputation, bool aesni, bool avx, bool force); void TerminateCrypto (); } } diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 7784cc90..88523492 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -828,7 +828,7 @@ namespace data XORMetric operator^(const IdentHash& key1, const IdentHash& key2) { XORMetric m; -#ifdef __AVX__ +#if defined(__x86_64__) || defined(__i386__) if(i2p::cpu::avx) { __asm__ diff --git a/libi2pd/api.cpp b/libi2pd/api.cpp index 569fbd8c..faeee84d 100644 --- a/libi2pd/api.cpp +++ b/libi2pd/api.cpp @@ -37,7 +37,10 @@ namespace api i2p::fs::Init(); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); - i2p::crypto::InitCrypto (precomputation); + bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); + bool avx; i2p::config::GetOption("cpuext.avx", avx); + bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt); + i2p::crypto::InitCrypto (precomputation, aesni, avx, forceCpuExt); int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index dba69143..eed4cd7b 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -4,14 +4,16 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = i2pd_qt TEMPLATE = app -QMAKE_CXXFLAGS *= -Wno-unused-parameter -Wno-maybe-uninitialized +QMAKE_CXXFLAGS *= -Wno-unused-parameter -Wno-maybe-uninitialized -Wno-deprecated-copy CONFIG += strict_c++ c++11 CONFIG(debug, debug|release) { message(Debug build) DEFINES += DEBUG_WITH_DEFAULT_LOGGING + I2PDMAKE += DEBUG=yes } else { message(Release build) + I2PDMAKE += DEBUG=no } SOURCES += DaemonQT.cpp mainwindow.cpp \ @@ -73,19 +75,19 @@ FORMS += mainwindow.ui \ LIBS += $$PWD/../../libi2pd.a $$PWD/../../libi2pdclient.a -lz -libi2pd.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_AVX=no USE_AESNI=no USE_UPNP=yes DEBUG=no api +libi2pd.commands = @echo Building i2pd libraries libi2pd.target = $$PWD/../../libi2pd.a -libi2pd.depends = FORCE +libi2pd.depends = i2pd FORCE -libi2pdclient.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd_client && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_AVX=no USE_AESNI=no USE_UPNP=yes DEBUG=no api_client -libi2pdclient.target = $$PWD/../../libi2pdclient.a -libi2pdclient.depends = FORCE +i2pd.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd_client && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes $$I2PDMAKE api_client +i2pd.target += $$PWD/../../libi2pdclient.a +i2pd.depends = FORCE cleani2pd.commands = cd $$PWD/../../ && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) clean cleani2pd.depends = clean PRE_TARGETDEPS += $$PWD/../../libi2pd.a $$PWD/../../libi2pdclient.a -QMAKE_EXTRA_TARGETS += cleani2pd libi2pd libi2pdclient +QMAKE_EXTRA_TARGETS += cleani2pd i2pd libi2pd CLEAN_DEPS += cleani2pd From 8b3a7486c74b27199a08dc500e2a19dfe16e53a0 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 14 Nov 2020 18:28:50 -0500 Subject: [PATCH 3901/6300] rename CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET to CRYPTO_KEY_TYPE_ECIES_X25519_AEAD --- libi2pd/Destination.cpp | 14 +++++++------- libi2pd/ECIESX25519AEADRatchetSession.cpp | 12 ++++++------ libi2pd/Garlic.cpp | 11 ++++++----- libi2pd/Identity.cpp | 14 +++++++------- libi2pd/Identity.h | 2 +- libi2pd/RouterContext.h | 2 +- libi2pd/TunnelConfig.h | 2 +- libi2pd_client/I2CP.cpp | 8 ++++---- 8 files changed, 33 insertions(+), 32 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index bf6047b2..d51fc63d 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -744,7 +744,7 @@ namespace client request->excluded.insert (nextFloodfill->GetIdentHash ()); request->requestTimeoutTimer.cancel (); - bool isECIES = SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) && + bool isECIES = SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) && nextFloodfill->GetVersion () >= MAKE_VERSION_NUMBER(0, 9, 46); // >= 0.9.46; uint8_t replyKey[32], replyTag[32]; RAND_bytes (replyKey, 32); // random session key @@ -842,8 +842,8 @@ namespace client i2p::data::CryptoKeyType LeaseSetDestination::GetPreferredCryptoType () const { - if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) - return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET; + if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) + return i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; return i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; } @@ -898,7 +898,7 @@ namespace client else encryptionKey->GenerateKeys (); encryptionKey->CreateDecryptor (); - if (it == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + if (it == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) { m_ECIESx25519EncryptionKey.reset (encryptionKey); if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET) @@ -1215,7 +1215,7 @@ namespace client bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const { - if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) if (m_ECIESx25519EncryptionKey && m_ECIESx25519EncryptionKey->decryptor) return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data, ctx, true); if (m_StandardEncryptionKey && m_StandardEncryptionKey->decryptor) @@ -1227,12 +1227,12 @@ namespace client bool ClientDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { - return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET ? (bool)m_ECIESx25519EncryptionKey : (bool)m_StandardEncryptionKey; + return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519EncryptionKey : (bool)m_StandardEncryptionKey; } const uint8_t * ClientDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const { - if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) return m_ECIESx25519EncryptionKey ? m_ECIESx25519EncryptionKey->pub : nullptr; return m_StandardEncryptionKey ? m_StandardEncryptionKey->pub : nullptr; } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 1d51e67e..3dfe4ca5 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -236,7 +236,7 @@ namespace garlic if (!GetOwner ()) return false; // we are Bob // KDF1 - MixHash (GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET), 32); // h = SHA256(h || bpk) + MixHash (GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD), 32); // h = SHA256(h || bpk) if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk)) { @@ -247,7 +247,7 @@ namespace garlic MixHash (m_Aepk, 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; - GetOwner ()->Decrypt (m_Aepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519(bsk, aepk) + GetOwner ()->Decrypt (m_Aepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519(bsk, aepk) MixKey (sharedSecret); // decrypt flags/static @@ -267,7 +267,7 @@ namespace garlic { // static key, fs is apk memcpy (m_RemoteStaticKey, fs, 32); - GetOwner ()->Decrypt (fs, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519(bsk, apk) + GetOwner ()->Decrypt (fs, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519(bsk, apk) MixKey (sharedSecret); } else // all zeros flags @@ -469,7 +469,7 @@ namespace garlic CreateNonce (0, nonce); const uint8_t * fs; if (isStatic) - fs = GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); + fs = GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); else { memset (out + offset, 0, 32); // all zeros flags section @@ -486,7 +486,7 @@ namespace garlic // KDF2 if (isStatic) { - GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bpk) + GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bpk) MixKey (sharedSecret); } else @@ -618,7 +618,7 @@ namespace garlic // only fist time, we assume ephemeral keys the same m_EphemeralKeys->Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) MixKey (sharedSecret); - GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET); // x25519 (ask, bepk) + GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) MixKey (sharedSecret); } uint8_t nonce[12]; diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index d8d034a1..2fe97156 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -506,7 +506,7 @@ namespace garlic else { bool found = false; - if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) + if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) { // try ECIESx25519 tag uint64_t tag; @@ -536,7 +536,7 @@ namespace garlic decryption->Decrypt(buf + 514, length - 514, buf + 514); HandleAESBlock (buf + 514, length - 514, decryption, msg->from); } - else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) + else if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) { // otherwise ECIESx25519 auto session = std::make_shared (this, false); // incoming @@ -712,7 +712,7 @@ namespace garlic std::shared_ptr GarlicDestination::WrapMessageForRouter (std::shared_ptr router, std::shared_ptr msg) { - if (router->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + if (router->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) { auto session = std::make_shared(this, false); session->SetRemoteStaticKey (router->GetIdentity ()->GetEncryptionPublicKey ()); @@ -728,8 +728,8 @@ namespace garlic std::shared_ptr GarlicDestination::GetRoutingSession ( std::shared_ptr destination, bool attachLeaseSet) { - if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET && - SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET)) + if (destination->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && + SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) { ECIESX25519AEADRatchetSessionPtr session; uint8_t staticKey[32]; @@ -771,6 +771,7 @@ namespace garlic } return session; } + return nullptr; } void GarlicDestination::CleanupExpiredTags () diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 88523492..89f6cda0 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -48,7 +48,7 @@ namespace data IdentityEx::IdentityEx(const uint8_t * publicKey, const uint8_t * signingKey, SigningKeyType type, CryptoKeyType cryptoType) { - if (cryptoType == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + if (cryptoType == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) { memcpy (m_StandardIdentity.publicKey, publicKey, 32); RAND_bytes (m_StandardIdentity.publicKey + 32, 224); @@ -426,7 +426,7 @@ namespace data case CRYPTO_KEY_TYPE_ELGAMAL: return std::make_shared(key); break; - case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET: + case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: return std::make_shared(key); break; case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: @@ -662,7 +662,7 @@ namespace data size_t PrivateKeys::GetPrivateKeyLen () const { // private key length always 256, but type 4 - return (m_Public->GetCryptoKeyType () == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) ? 32 : 256; + return (m_Public->GetCryptoKeyType () == CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) ? 32 : 256; } uint8_t * PrivateKeys::GetPadding() @@ -687,6 +687,9 @@ namespace data case CRYPTO_KEY_TYPE_ELGAMAL: return std::make_shared(key); break; + case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: + return std::make_shared(key); + break; case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC: case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST: return std::make_shared(key); @@ -694,9 +697,6 @@ namespace data case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC: return std::make_shared(key); break; - case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET: - return std::make_shared(key); - break; default: LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)cryptoType); }; @@ -776,7 +776,7 @@ namespace data case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC: i2p::crypto::CreateECIESGOSTR3410RandomKeys (priv, pub); break; - case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET: + case CRYPTO_KEY_TYPE_ECIES_X25519_AEAD: i2p::crypto::CreateECIESX25519AEADRatchetRandomKeys (priv, pub); break; default: diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index a36e7209..e9cf63ed 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -64,7 +64,7 @@ namespace data const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0; const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC = 1; - const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET = 4; + const uint16_t CRYPTO_KEY_TYPE_ECIES_X25519_AEAD = 4; const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST = 65280; // TODO: remove later const uint16_t CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC = 65281; // TODO: use GOST R 34.11 instead SHA256 and GOST 28147-89 instead AES diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 139cc35e..8c13b842 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -109,7 +109,7 @@ namespace i2p bool SupportsV4 () const { return m_RouterInfo.IsV4 (); }; void SetSupportsV6 (bool supportsV6); void SetSupportsV4 (bool supportsV4); - bool IsECIES () const { return GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET; }; + bool IsECIES () const { return GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; std::unique_ptr& GetCurrentNoiseState () { return m_CurrentNoiseState; }; void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 518c1aad..9aed0d25 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -39,7 +39,7 @@ namespace tunnel void SetNext (TunnelHopConfig * n); void SetPrev (TunnelHopConfig * p); - bool IsECIES () const { return ident->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET; }; + bool IsECIES () const { return ident->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); void EncryptECIES (std::shared_ptr& encryptor, const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx); diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index d5436936..2b9df0fa 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -46,7 +46,7 @@ namespace client bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const { - if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET && m_ECIESx25519Decryptor) + if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && m_ECIESx25519Decryptor) return m_ECIESx25519Decryptor->Decrypt (encrypted, data, ctx, true); if (m_Decryptor) return m_Decryptor->Decrypt (encrypted, data, ctx, true); @@ -57,14 +57,14 @@ namespace client const uint8_t * I2CPDestination::GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const { - if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET && m_ECIESx25519Decryptor) + if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && m_ECIESx25519Decryptor) return m_ECIESx25519Decryptor->GetPubicKey (); return nullptr; } bool I2CPDestination::SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const { - return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET ? (bool)m_ECIESx25519Decryptor : m_EncryptionKeyType == keyType; + return keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ? (bool)m_ECIESx25519Decryptor : m_EncryptionKeyType == keyType; } @@ -621,7 +621,7 @@ namespace client uint16_t keyType = bufbe16toh (buf + offset); offset += 2; // encryption type uint16_t keyLen = bufbe16toh (buf + offset); offset += 2; // private key length if (offset + keyLen > len) return; - if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD_RATCHET) + if (keyType == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) m_Destination->SetECIESx25519EncryptionPrivateKey (buf + offset); else { From 1f6be38145911ef72901eb7bdc4cee881bd33ea1 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 15 Nov 2020 13:06:02 -0500 Subject: [PATCH 3902/6300] wait for publish confirmation or publish to another floodfill --- libi2pd/NetDb.cpp | 45 ++++++++++++++++++++++++++++----------- libi2pd/NetDb.hpp | 9 +++++++- libi2pd/RouterContext.cpp | 9 ++++++-- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 318db953..0768b838 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -111,6 +111,9 @@ namespace data case eI2NPDatabaseLookup: HandleDatabaseLookupMsg (msg); break; + case eI2NPDeliveryStatus: + HandleDeliveryStatusMsg (msg); + break; case eI2NPDummyMsg: // plain RouterInfo from NTCP2 with flags for now HandleNTCP2RouterInfoMsg (msg); @@ -148,10 +151,12 @@ namespace data lastDestinationCleanup = ts; } - if (ts - lastPublish >= NETDB_PUBLISH_INTERVAL) // update timestamp and publish + if (!m_HiddenMode && i2p::transport::transports.IsOnline () && + ((m_PublishReplyToken && ts - lastPublish >= NETDB_PUBLISH_CONFIRMATION_TIMEOUT) || + ts - lastPublish >= NETDB_PUBLISH_INTERVAL)) // update timestamp and publish { i2p::context.UpdateTimestamp (ts); - if (!m_HiddenMode) Publish (); + Publish (); lastPublish = ts; } if (ts - lastExploratory >= 30) // exploratory every 30 seconds @@ -992,6 +997,16 @@ namespace data } } + void NetDb::HandleDeliveryStatusMsg (std::shared_ptr msg) + { + if (m_PublishReplyToken == bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET)) + { + LogPrint (eLogInfo, "NetDb: Publishing confirmed. reply token=", m_PublishReplyToken); + m_PublishExcluded.clear (); + m_PublishReplyToken = 0; + } + } + void NetDb::Explore (int numDestinations) { // new requests @@ -1045,18 +1060,22 @@ namespace data void NetDb::Publish () { i2p::context.UpdateStats (); // for floodfill - std::set excluded; // TODO: fill up later - for (int i = 0; i < 2; i++) + + if (m_PublishExcluded.size () > NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS) { - auto floodfill = GetClosestFloodfill (i2p::context.GetRouterInfo ().GetIdentHash (), excluded); - if (floodfill) - { - uint32_t replyToken; - RAND_bytes ((uint8_t *)&replyToken, 4); - LogPrint (eLogInfo, "NetDb: Publishing our RouterInfo to ", i2p::data::GetIdentHashAbbreviation(floodfill->GetIdentHash ()), ". reply token=", replyToken); - transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken)); - excluded.insert (floodfill->GetIdentHash ()); - } + LogPrint (eLogError, "NetDb: Couldn't publish our RouterInfo to ", NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS, " closest routers. Try again"); + m_PublishExcluded.clear (); + } + + auto floodfill = GetClosestFloodfill (i2p::context.GetIdentHash (), m_PublishExcluded); + if (floodfill) + { + uint32_t replyToken; + RAND_bytes ((uint8_t *)&replyToken, 4); + LogPrint (eLogInfo, "NetDb: Publishing our RouterInfo to ", i2p::data::GetIdentHashAbbreviation(floodfill->GetIdentHash ()), ". reply token=", replyToken); + m_PublishExcluded.insert (floodfill->GetIdentHash ()); + m_PublishReplyToken = replyToken; + transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken)); } } diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index f9a57ccd..afd1f562 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -41,6 +41,8 @@ namespace data 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_PUBLISH_INTERVAL = 60 * 40; + const int NETDB_PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds + const int NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 36); // 0.9.36 /** function for visiting a leaseset stored in a floodfill */ @@ -77,6 +79,7 @@ namespace data void HandleDatabaseSearchReplyMsg (std::shared_ptr msg); void HandleDatabaseLookupMsg (std::shared_ptr msg); void HandleNTCP2RouterInfoMsg (std::shared_ptr m); + void HandleDeliveryStatusMsg (std::shared_ptr msg); std::shared_ptr GetRandomRouter () const; std::shared_ptr GetRandomRouter (std::shared_ptr compatibleWith) const; @@ -115,6 +118,8 @@ namespace data void ClearRouterInfos () { m_RouterInfos.clear (); }; + uint32_t GetPublishReplyToken () const { return m_PublishReplyToken; }; + private: void Load (); @@ -162,9 +167,11 @@ namespace data /** router info we are bootstrapping from or nullptr if we are not currently doing that*/ std::shared_ptr m_FloodfillBootstrap; - /** true if in hidden mode */ bool m_HiddenMode; + + std::set m_PublishExcluded; + uint32_t m_PublishReplyToken = 0; }; extern NetDb netdb; diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 74f8ad34..375e0b9a 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -670,8 +670,13 @@ namespace i2p void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr msg) { - std::unique_lock l(m_GarlicMutex); - i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); + if (i2p::data::netdb.GetPublishReplyToken () == bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET)) + i2p::data::netdb.PostI2NPMsg (msg); + else + { + std::unique_lock l(m_GarlicMutex); + i2p::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg); + } } void RouterContext::CleanupDestination () From af20b13c7a54a852a16024111714d17585aecdee Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 15 Nov 2020 17:02:01 -0500 Subject: [PATCH 3903/6300] create paired inbound tunnels if no inbound tunnels yet --- libi2pd/TunnelPool.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index d0fd401f..909248ba 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -118,7 +118,6 @@ namespace tunnel std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.insert (createdTunnel); } - //CreatePairedInboundTunnel (createdTunnel); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) @@ -235,6 +234,15 @@ namespace tunnel for (const auto& it : m_InboundTunnels) if (it->IsEstablished ()) num++; } + if (!num && !m_OutboundTunnels.empty ()) + { + for (auto it: m_OutboundTunnels) + { + CreatePairedInboundTunnel (it); + num++; + if (num >= m_NumInboundTunnels) break; + } + } for (int i = num; i < m_NumInboundTunnels; i++) CreateInboundTunnel (); From 44ca315c7559f44190853b9aa3da88979e89fe2b Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 15 Nov 2020 19:38:34 -0500 Subject: [PATCH 3904/6300] don't build tunnels for all pools at the time --- libi2pd/Tunnel.cpp | 28 ++++++++++++++++------------ libi2pd/Tunnel.h | 4 ++-- libi2pd/TunnelPool.cpp | 15 +++++++++++++-- libi2pd/TunnelPool.h | 4 ++++ 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index a74c8c91..ad918787 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -474,7 +474,7 @@ namespace tunnel { std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready - uint64_t lastTs = 0; + uint64_t lastTs = 0, lastPoolsTs = 0; while (m_IsRunning) { try @@ -535,12 +535,20 @@ namespace tunnel while (msg); } - uint64_t ts = i2p::util::GetSecondsSinceEpoch (); - if (ts - lastTs >= 15 && i2p::transport::transports.IsOnline()) // manage tunnels every 15 seconds + if (i2p::transport::transports.IsOnline()) { - ManageTunnels (); - lastTs = ts; - } + uint64_t ts = i2p::util::GetSecondsSinceEpoch (); + if (ts - lastTs >= 15) // manage tunnels every 15 seconds + { + ManageTunnels (); + lastTs = ts; + } + if (ts - lastPoolsTs >= 5) // manage pools every 5 seconds + { + ManageTunnelPools (ts); + lastPoolsTs = ts; + } + } } catch (std::exception& ex) { @@ -582,7 +590,6 @@ namespace tunnel ManageInboundTunnels (); ManageOutboundTunnels (); ManageTransitTunnels (); - ManageTunnelPools (); } void Tunnels::ManagePendingTunnels () @@ -794,16 +801,13 @@ namespace tunnel } } - void Tunnels::ManageTunnelPools () + void Tunnels::ManageTunnelPools (uint64_t ts) { std::unique_lock l(m_PoolsMutex); for (auto& pool : m_Pools) { if (pool && pool->IsActive ()) - { - pool->CreateTunnels (); - pool->TestTunnels (); - } + pool->ManageTunnels (ts); } } diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index a50bc31a..37c5cddb 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -230,7 +230,7 @@ namespace tunnel void ManagePendingTunnels (); template void ManagePendingTunnels (PendingTunnels& pendingTunnels); - void ManageTunnelPools (); + void ManageTunnelPools (uint64_t ts); std::shared_ptr CreateZeroHopsInboundTunnel (); std::shared_ptr CreateZeroHopsOutboundTunnel (); @@ -249,7 +249,7 @@ namespace tunnel std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; - + // some stats int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 909248ba..a44fc33d 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -26,9 +26,10 @@ namespace tunnel { TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), - m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_IsActive (true), - m_CustomPeerSelector(nullptr) + m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), + m_IsActive (true), m_CustomPeerSelector(nullptr) { + m_NextManageTime = i2p::util::GetSecondsSinceEpoch () + rand () % TUNNEL_POOL_MANAGE_INTERVAL; } TunnelPool::~TunnelPool () @@ -321,6 +322,16 @@ namespace tunnel } } + void TunnelPool::ManageTunnels (uint64_t ts) + { + if (ts > m_NextManageTime) + { + CreateTunnels (); + TestTunnels (); + m_NextManageTime = ts + TUNNEL_POOL_MANAGE_INTERVAL + (rand () % TUNNEL_POOL_MANAGE_INTERVAL)/2; + } + } + void TunnelPool::ProcessGarlicMessage (std::shared_ptr msg) { if (m_LocalDestination) diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index 04ff4ae7..d1024d04 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -27,6 +27,8 @@ namespace i2p { namespace tunnel { + const int TUNNEL_POOL_MANAGE_INTERVAL = 10; // in seconds + class Tunnel; class InboundTunnel; class OutboundTunnel; @@ -69,6 +71,7 @@ namespace tunnel std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr) const; std::shared_ptr GetNewOutboundTunnel (std::shared_ptr old) const; void TestTunnels (); + void ManageTunnels (uint64_t ts); void ProcessGarlicMessage (std::shared_ptr msg); void ProcessDeliveryStatus (std::shared_ptr msg); @@ -123,6 +126,7 @@ namespace tunnel mutable std::mutex m_TestsMutex; std::map, std::shared_ptr > > m_Tests; bool m_IsActive; + uint64_t m_NextManageTime; // in seconds std::mutex m_CustomPeerSelectorMutex; ITunnelPeerSelector * m_CustomPeerSelector; From 1ae98b7fe1604a7b8ba6023fb48ff5da049fb258 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 16 Nov 2020 03:38:04 +0300 Subject: [PATCH 3905/6300] [webconsole] graceful timer for windows --- Win32/Win32App.cpp | 15 ++++++--------- Win32/Win32App.h | 2 ++ daemon/HTTPServer.cpp | 13 +++++++++++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index df345b11..1785ffd6 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -43,10 +43,7 @@ namespace i2p { namespace win32 { - static DWORD GracefulShutdownEndtime = 0; - - typedef DWORD (* IPN)(); - IPN GetTickCountLocal = (IPN)GetProcAddress (GetModuleHandle ("KERNEL32.dll"), "GetTickCount"); + DWORD g_GracefulShutdownEndtime = 0; static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) { @@ -167,9 +164,9 @@ namespace win32 s << "; "; s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); - if (GracefulShutdownEndtime != 0) + if (g_GracefulShutdownEndtime != 0) { - DWORD GracefulTimeLeft = (GracefulShutdownEndtime - GetTickCountLocal()) / 1000; + DWORD GracefulTimeLeft = (g_GracefulShutdownEndtime - GetTickCount()) / 1000; s << "Graceful shutdown, time left: "; ShowUptime(s, GracefulTimeLeft); } else @@ -247,7 +244,7 @@ namespace win32 i2p::context.SetAcceptsTunnels (false); SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); // check tunnels every second - GracefulShutdownEndtime = GetTickCountLocal() + 10*60*1000; + g_GracefulShutdownEndtime = GetTickCount() + 10*60*1000; i2p::util::DaemonWin32::Instance ().isGraceful = true; return 0; } @@ -256,7 +253,7 @@ namespace win32 i2p::context.SetAcceptsTunnels (true); KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); - GracefulShutdownEndtime = 0; + g_GracefulShutdownEndtime = 0; i2p::util::DaemonWin32::Instance ().isGraceful = false; return 0; } @@ -343,7 +340,7 @@ namespace win32 { case IDT_GRACEFUL_SHUTDOWN_TIMER: { - GracefulShutdownEndtime = 0; + g_GracefulShutdownEndtime = 0; PostMessage (hWnd, WM_CLOSE, 0, 0); // exit return 0; } diff --git a/Win32/Win32App.h b/Win32/Win32App.h index d242f7d3..ebe49efd 100644 --- a/Win32/Win32App.h +++ b/Win32/Win32App.h @@ -15,6 +15,8 @@ namespace i2p { namespace win32 { + extern DWORD g_GracefulShutdownEndtime; + bool StartWin32App (); void StopWin32App (); int RunWin32App (); diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 59c530f9..ad549a00 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -270,8 +270,17 @@ namespace http { } s << "
    \r\n"; #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) - if (auto remains = Daemon.gracefulShutdownInterval) - s << "Stopping in: " << remains << " seconds
    \r\n"; + if (auto remains = Daemon.gracefulShutdownInterval) { + s << "Stopping in: "; + ShowUptime(s, remains); + s << "
    \r\n"; +#elif defined(WIN32_APP) + if (i2p::win32::g_GracefulShutdownEndtime != 0) { + uint16_t remains = (i2p::win32::g_GracefulShutdownEndtime - GetTickCount()) / 1000; + s << "Stopping in: "; + ShowUptime(s, remains); + s << "
    \r\n"; + } #endif auto family = i2p::context.GetFamily (); if (family.length () > 0) From 2bd6daeb8de30d6445961ba02f2975acc678bc61 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 16 Nov 2020 03:38:52 +0300 Subject: [PATCH 3906/6300] disable aes/avx for winxp by default --- libi2pd/CPU.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libi2pd/CPU.cpp b/libi2pd/CPU.cpp index 0bd830e3..0804e2ac 100644 --- a/libi2pd/CPU.cpp +++ b/libi2pd/CPU.cpp @@ -34,10 +34,18 @@ namespace cpu __cpuid(0, info[0], info[1], info[2], info[3]); if (info[0] >= 0x00000001) { __cpuid(0x00000001, info[0], info[1], info[2], info[3]); +#if defined (_WIN32) && (WINVER == 0x0501) // WinXP + if (AesSwitch && force) { // only if forced +#else if ((info[2] & bit_AES && AesSwitch) || (AesSwitch && force)) { +#endif aesni = true; } +#if defined (_WIN32) && (WINVER == 0x0501) // WinXP + if (AvxSwitch && force) { // only if forced +#else if ((info[2] & bit_AVX && AvxSwitch) || (AvxSwitch && force)) { +#endif avx = true; } } From 4a44b18b97c65211faec7cc6742a4592a5662db8 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 15 Nov 2020 19:56:16 -0500 Subject: [PATCH 3907/6300] fixed typo --- daemon/HTTPServer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index ad549a00..3c9ddde6 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -274,6 +274,7 @@ namespace http { s << "Stopping in: "; ShowUptime(s, remains); s << "
    \r\n"; + } #elif defined(WIN32_APP) if (i2p::win32::g_GracefulShutdownEndtime != 0) { uint16_t remains = (i2p::win32::g_GracefulShutdownEndtime - GetTickCount()) / 1000; From b4369470cb88c43c87e657537c4e42add3d9fd4a Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 15 Nov 2020 20:05:27 -0500 Subject: [PATCH 3908/6300] publish updated RouterInfo --- libi2pd/NetDb.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 0768b838..9a90e952 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -153,6 +153,7 @@ namespace data if (!m_HiddenMode && i2p::transport::transports.IsOnline () && ((m_PublishReplyToken && ts - lastPublish >= NETDB_PUBLISH_CONFIRMATION_TIMEOUT) || + i2p::context.GetLastUpdateTime () > lastPublish || ts - lastPublish >= NETDB_PUBLISH_INTERVAL)) // update timestamp and publish { i2p::context.UpdateTimestamp (ts); From c69c4ae8a019d3d220a522797d0987877bd9da9e Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 15 Nov 2020 21:46:49 -0500 Subject: [PATCH 3909/6300] don't publish too fast --- libi2pd/NetDb.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 9a90e952..4399fb15 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -151,14 +151,22 @@ namespace data lastDestinationCleanup = ts; } - if (!m_HiddenMode && i2p::transport::transports.IsOnline () && - ((m_PublishReplyToken && ts - lastPublish >= NETDB_PUBLISH_CONFIRMATION_TIMEOUT) || - i2p::context.GetLastUpdateTime () > lastPublish || - ts - lastPublish >= NETDB_PUBLISH_INTERVAL)) // update timestamp and publish + // publish + if (!m_HiddenMode && i2p::transport::transports.IsOnline ()) { - i2p::context.UpdateTimestamp (ts); - Publish (); - lastPublish = ts; + bool publish = false; + if (m_PublishReplyToken) + { + if (ts - lastPublish >= NETDB_PUBLISH_CONFIRMATION_TIMEOUT) publish = true; + } + else if (i2p::context.GetLastUpdateTime () > lastPublish || + ts - lastPublish >= NETDB_PUBLISH_INTERVAL) publish = true; + if (publish) // update timestamp and publish + { + i2p::context.UpdateTimestamp (ts); + Publish (); + lastPublish = ts; + } } if (ts - lastExploratory >= 30) // exploratory every 30 seconds { From 3b630fe546d76e2c19e7540d3406888c7413e15b Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 16 Nov 2020 10:04:38 -0500 Subject: [PATCH 3910/6300] fixed race condition --- libi2pd/TunnelPool.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index a44fc33d..a898d69e 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -292,6 +292,8 @@ namespace tunnel } // new tests + std::unique_lock l1(m_OutboundTunnelsMutex); + std::unique_lock l2(m_InboundTunnelsMutex); auto it1 = m_OutboundTunnels.begin (); auto it2 = m_InboundTunnels.begin (); while (it1 != m_OutboundTunnels.end () && it2 != m_InboundTunnels.end ()) From 3925540517d31d62d1ad89158e2a075c9d539463 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 16 Nov 2020 12:56:22 -0500 Subject: [PATCH 3911/6300] don't update expired tunnels --- libi2pd/TunnelPool.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index a898d69e..87413c3f 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -292,8 +292,6 @@ namespace tunnel } // new tests - std::unique_lock l1(m_OutboundTunnelsMutex); - std::unique_lock l2(m_InboundTunnelsMutex); auto it1 = m_OutboundTunnels.begin (); auto it2 = m_InboundTunnels.begin (); while (it1 != m_OutboundTunnels.end () && it2 != m_InboundTunnels.end ()) @@ -363,17 +361,24 @@ namespace tunnel } if (found) { - // restore from test failed state if any - if (test.first->GetState () == eTunnelStateTestFailed) - test.first->SetState (eTunnelStateEstablished); - if (test.second->GetState () == eTunnelStateTestFailed) - test.second->SetState (eTunnelStateEstablished); uint64_t dlt = i2p::util::GetMillisecondsSinceEpoch () - timestamp; LogPrint (eLogDebug, "Tunnels: test of ", msgID, " successful. ", dlt, " milliseconds"); - // update latency uint64_t latency = dlt / 2; - test.first->AddLatencySample(latency); - test.second->AddLatencySample(latency); + // restore from test failed state if any + if (test.first) + { + if (test.first->GetState () == eTunnelStateTestFailed) + test.first->SetState (eTunnelStateEstablished); + // update latency + test.first->AddLatencySample(latency); + } + if (test.second) + { + if (test.second->GetState () == eTunnelStateTestFailed) + test.second->SetState (eTunnelStateEstablished); + // update latency + test.second->AddLatencySample(latency); + } } else { From 0a3af12ee984c0964994dbacd9fd170c08f3c75e Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 17 Nov 2020 17:59:40 +0300 Subject: [PATCH 3912/6300] [make] track changes in includes Signed-off-by: R4SAS --- Makefile | 26 +++++++++++++------------- Makefile.mingw | 4 ++-- Win32/DaemonWin32.cpp | 4 ++-- build/build_mingw.cmd | 14 +++++++------- build/win_installer.iss | 3 ++- daemon/HTTPServer.cpp | 5 +++-- 6 files changed, 29 insertions(+), 27 deletions(-) diff --git a/Makefile b/Makefile index c359d7c7..7bb3ceda 100644 --- a/Makefile +++ b/Makefile @@ -4,13 +4,12 @@ ARLIB := libi2pd.a SHLIB_CLIENT := libi2pdclient.so ARLIB_CLIENT := libi2pdclient.a I2PD := i2pd -GREP := grep -DEPS := obj/make.dep LIB_SRC_DIR := libi2pd LIB_CLIENT_SRC_DIR := libi2pd_client DAEMON_SRC_DIR := daemon +# import source files lists include filelist.mk USE_AESNI := yes @@ -50,7 +49,12 @@ ifeq ($(USE_MESHNET),yes) NEEDED_CXXFLAGS += -DMESHNET endif -NEEDED_CXXFLAGS += -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) +NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) + +LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) +LIB_CLIENT_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) +DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) +DEPS := $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) @@ -71,32 +75,29 @@ api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. -deps: mk_obj_dir - $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) -MM *.cpp > $(DEPS) - @sed -i -e '/\.o:/ s/^/obj\//' $(DEPS) - obj/%.o: %.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -c -o $@ $< # '-' is 'ignore if missing' on first run -include $(DEPS) -DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(CXX) -o $@ $^ $(LDFLAGS) $(LDLIBS) -$(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) +$(SHLIB): $(LIB_OBJS) ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) endif -$(SHLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) +$(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) +ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) +endif -$(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) +$(ARLIB): $(LIB_OBJS) $(AR) -r $@ $^ -$(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) +$(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) $(AR) -r $@ $^ clean: @@ -122,7 +123,6 @@ doxygen: .PHONY: all .PHONY: clean -.PHONY: deps .PHONY: doxygen .PHONY: dist .PHONY: last-dist diff --git a/Makefile.mingw b/Makefile.mingw index 764606b6..4cd2c292 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,8 +1,8 @@ USE_WIN32_APP=yes CXX = g++ WINDRES = windres -CXXFLAGS := ${CXX_DEBUG} -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN -fPIC -msse -INCFLAGS = -Idaemon -I. +CXXFLAGS := $(CXX_DEBUG) -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN -fPIC -msse +INCFLAGS = -I$(DAEMON_SRC_DIR) -IWin32 LDFLAGS := ${LD_DEBUG} -Wl,-Bstatic -static-libgcc -static-libstdc++ # detect proper flag for c++11 support by compilers diff --git a/Win32/DaemonWin32.cpp b/Win32/DaemonWin32.cpp index 1214ff68..30f4f92e 100644 --- a/Win32/DaemonWin32.cpp +++ b/Win32/DaemonWin32.cpp @@ -14,10 +14,10 @@ #include "Log.h" #ifdef _WIN32 -#include "Win32/Win32Service.h" +#include "Win32Service.h" #ifdef WIN32_APP #include -#include "Win32/Win32App.h" +#include "Win32App.h" #endif namespace i2p diff --git a/build/build_mingw.cmd b/build/build_mingw.cmd index 37a1d454..e791039e 100644 --- a/build/build_mingw.cmd +++ b/build/build_mingw.cmd @@ -23,8 +23,8 @@ set "xSH=%WD%bash -lc" set "FILELIST=i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates contrib/tunnels.d" -REM detecting number of processors and subtract 1. -set /a threads=%NUMBER_OF_PROCESSORS%-1 +REM detecting number of processors +set /a threads=%NUMBER_OF_PROCESSORS% REM we must work in root of repo cd .. @@ -33,7 +33,7 @@ REM deleting old log files del /S build_*.log >> nul 2>&1 echo Receiving latest commit and cleaning up... -%xSH% "git checkout contrib/* && git pull && make clean" > build/build.log 2>&1 +%xSH% "git checkout contrib/* && git pull && make clean" > build\build.log 2>&1 echo. REM set to variable current commit hash @@ -44,7 +44,7 @@ FOR /F "usebackq" %%a IN (`%xSH% 'git describe --tags'`) DO ( %xSH% "echo To use configs and certificates, move all files and certificates folder from contrib directory here. > README.txt" >> nul REM converting configuration files to DOS format (usable in default notepad) -%xSH% "unix2dos contrib/i2pd.conf contrib/tunnels.conf contrib/tunnels.d/*" > build/build.log 2>&1 +%xSH% "unix2dos contrib/i2pd.conf contrib/tunnels.conf contrib/tunnels.d/*" >> build\build.log 2>&1 REM starting building set MSYSTEM=MINGW32 @@ -64,7 +64,7 @@ call :BUILDING_XP echo. REM compile installer -C:\PROGRA~2\INNOSE~1\ISCC.exe build\win_installer.iss +C:\PROGRA~2\INNOSE~1\ISCC.exe /dI2Pd_ver="%tag%" build\win_installer.iss >> build\build.log 2>&1 del README.txt i2pd_x32.exe i2pd_x64.exe i2pd_xp.exe >> nul @@ -75,12 +75,12 @@ exit /b 0 :BUILDING %xSH% "make clean" >> nul echo Building i2pd %tag% for win%bitness% -%xSH% "make DEBUG=no USE_UPNP=yes -j%threads% && cp i2pd.exe i2pd_x%bitness%.exe && zip -r9 build/i2pd_%tag%_win%bitness%_mingw.zip %FILELIST% && make clean" > build/build_win%bitness%_%tag%.log 2>&1 +%xSH% "make DEBUG=no USE_UPNP=yes -j%threads% && cp i2pd.exe i2pd_x%bitness%.exe && zip -r9 build/i2pd_%tag%_win%bitness%_mingw.zip %FILELIST% && make clean" > build\build_win%bitness%_%tag%.log 2>&1 goto EOF :BUILDING_XP %xSH% "make clean" >> nul echo Building i2pd %tag% for winxp -%xSH% "make DEBUG=no USE_UPNP=yes USE_WINXP_FLAGS=yes -j%threads% && cp i2pd.exe i2pd_xp.exe && zip -r9 build/i2pd_%tag%_winxp_mingw.zip %FILELIST% && make clean" > build/build_winxp_%tag%.log 2>&1 +%xSH% "make DEBUG=no USE_UPNP=yes USE_WINXP_FLAGS=yes -j%threads% && cp i2pd.exe i2pd_xp.exe && zip -r9 build/i2pd_%tag%_winxp_mingw.zip %FILELIST% && make clean" > build\build_winxp_%tag%.log 2>&1 :EOF \ No newline at end of file diff --git a/build/win_installer.iss b/build/win_installer.iss index 007cd643..8a93a2c7 100644 --- a/build/win_installer.iss +++ b/build/win_installer.iss @@ -1,7 +1,8 @@ #define I2Pd_AppName "i2pd" #define I2Pd_Publisher "PurpleI2P" ; Get application version from compiled binary -#define I2Pd_ver GetFileVersionString(AddBackslash(SourcePath) + "..\i2pd_x64.exe") +; Disabled to use definition from command line +;#define I2Pd_ver GetFileVersionString(AddBackslash(SourcePath) + "..\i2pd_x64.exe") [Setup] AppName={#I2Pd_AppName} diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 3c9ddde6..ecc5acdd 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -30,8 +30,9 @@ #include "Daemon.h" #include "util.h" #include "ECIESX25519AEADRatchetSession.h" + #ifdef WIN32_APP -#include "Win32/Win32App.h" +#include "Win32App.h" #endif // For image and info @@ -274,7 +275,7 @@ namespace http { s << "Stopping in: "; ShowUptime(s, remains); s << "
    \r\n"; - } + } #elif defined(WIN32_APP) if (i2p::win32::g_GracefulShutdownEndtime != 0) { uint16_t remains = (i2p::win32::g_GracefulShutdownEndtime - GetTickCount()) / 1000; From 85d796f906b8e02f1fcc073624203caaca1ae3d0 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 17 Nov 2020 21:39:46 +0300 Subject: [PATCH 3913/6300] [actions] obj directories before make on windows --- .github/workflows/build-windows.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 9763f75b..c979e43f 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -26,4 +26,6 @@ jobs: install: base-devel mingw-w64-${{ matrix.arch }}-gcc mingw-w64-${{ matrix.arch }}-boost mingw-w64-${{ matrix.arch }}-openssl mingw-w64-${{ matrix.arch }}-miniupnpc update: true - name: build application - run: make USE_UPNP=yes DEBUG=no -j3 + run: | + mkdir -p obj/Win32 obj/libi2pd obj/libi2pd_client obj/daemon + make USE_UPNP=yes DEBUG=no -j3 From feaecbe177700d86ed0eb5729190a59173093bda Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 Nov 2020 15:02:06 -0500 Subject: [PATCH 3914/6300] own local destination for each 'transient' --- libi2pd_client/ClientContext.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index ab868916..860e8cfd 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -590,7 +590,8 @@ namespace client localDestination = CreateNewMatchedTunnelDestination(k, dest, &options); else localDestination = CreateNewLocalDestination (k, type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT, &options); - destinations[keys] = localDestination; + if (keys != "transient") + destinations[keys] = localDestination; } } } From d8381e94866f138160b19d58f01880040ec8fb12 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 18 Nov 2020 18:11:29 -0500 Subject: [PATCH 3915/6300] disable encryption to ECIES routers --- libi2pd/Destination.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index d51fc63d..44180ae5 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -559,7 +559,9 @@ namespace client m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); - auto msg = WrapMessageForRouter (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound)); + auto msg = i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound); + if (floodfill->GetIdentity ()->GetCryptoKeyType () != i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // TODO: remove whan implemented + msg = WrapMessageForRouter (floodfill, msg); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); @@ -754,8 +756,9 @@ namespace client else AddSessionKey (replyKey, replyTag); - auto msg = WrapMessageForRouter (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, - request->replyTunnel, replyKey, replyTag, isECIES)); + auto msg = CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, request->replyTunnel, replyKey, replyTag, isECIES); + if (nextFloodfill->GetIdentity ()->GetCryptoKeyType () != i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // TODO: remove whan implemented + msg = WrapMessageForRouter (nextFloodfill, msg); request->outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock From 30d6bd144bf4d90d7d38ea43545cbce674e34237 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 19 Nov 2020 15:41:00 -0500 Subject: [PATCH 3916/6300] don't replace an adddress by one with DSA signature --- libi2pd_client/AddressBook.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index 5c1cffbb..d695b8eb 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -454,17 +454,18 @@ namespace client auto it = m_Addresses.find (name); if (it != m_Addresses.end ()) // already exists ? { - if (it->second->IsIdentHash () && it->second->identHash != ident->GetIdentHash ()) // address changed? + if (it->second->IsIdentHash () && it->second->identHash != ident->GetIdentHash () && // address changed? + ident->GetSigningKeyType () != i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) // don't replace by DSA { it->second->identHash = ident->GetIdentHash (); m_Storage->AddAddress (ident); + m_Storage->RemoveAddress (it->second->identHash); LogPrint (eLogInfo, "Addressbook: updated host: ", name); } } else { - //m_Addresses.emplace (name, std::make_shared
    (ident->GetIdentHash ())); - m_Addresses[name] = std::make_shared
    (ident->GetIdentHash ()); // for gcc 4.7 + m_Addresses.emplace (name, std::make_shared
    (ident->GetIdentHash ())); m_Storage->AddAddress (ident); if (is_update) LogPrint (eLogInfo, "Addressbook: added new host: ", name); From 0436a65baa95ef1eae32e9d06c65669687a3b2f5 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Nov 2020 20:31:50 -0500 Subject: [PATCH 3917/6300] upddate DSA router keys --- libi2pd/RouterContext.cpp | 53 +++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 375e0b9a..220fbc4f 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -564,31 +564,42 @@ namespace i2p bool RouterContext::Load () { - std::ifstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ifstream::in | std::ifstream::binary); - if (!fk.is_open ()) return false; - fk.seekg (0, std::ios::end); - size_t len = fk.tellg(); - fk.seekg (0, std::ios::beg); + { + std::ifstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ifstream::in | std::ifstream::binary); + if (!fk.is_open ()) return false; + fk.seekg (0, std::ios::end); + size_t len = fk.tellg(); + fk.seekg (0, std::ios::beg); - if (len == sizeof (i2p::data::Keys)) // old keys file format - { - i2p::data::Keys keys; - fk.read ((char *)&keys, sizeof (keys)); - m_Keys = keys; + if (len == sizeof (i2p::data::Keys)) // old keys file format + { + i2p::data::Keys keys; + fk.read ((char *)&keys, sizeof (keys)); + m_Keys = keys; + } + else // new keys file format + { + uint8_t * buf = new uint8_t[len]; + fk.read ((char *)buf, len); + m_Keys.FromBuffer (buf, len); + delete[] buf; + } } - else // new keys file format + std::shared_ptr oldIdentity; + if (m_Keys.GetPublic ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1) { - uint8_t * buf = new uint8_t[len]; - fk.read ((char *)buf, len); - m_Keys.FromBuffer (buf, len); - delete[] buf; - } + // update keys + LogPrint (eLogInfo, "Router: router keys are obsolete. Creating new"); + oldIdentity = m_Keys.GetPublic (); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); + SaveKeys (); + } // read NTCP2 keys if available std::ifstream n2k (i2p::fs::DataDirPath (NTCP2_KEYS), std::ifstream::in | std::ifstream::binary); if (n2k) { n2k.seekg (0, std::ios::end); - len = n2k.tellg(); + size_t len = n2k.tellg(); n2k.seekg (0, std::ios::beg); if (len == sizeof (NTCP2PrivateKeys)) { @@ -598,17 +609,15 @@ namespace i2p n2k.close (); } // read RouterInfo - m_RouterInfo.SetRouterIdentity (GetIdentity ()); + m_RouterInfo.SetRouterIdentity (oldIdentity ? oldIdentity : GetIdentity ()); i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO)); if (!routerInfo.IsUnreachable ()) // router.info looks good { m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); + if (oldIdentity) + m_RouterInfo.SetRouterIdentity (GetIdentity ()); // from new keys m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION); m_RouterInfo.SetProperty ("router.version", I2P_VERSION); - - // Migration to 0.9.24. TODO: remove later - m_RouterInfo.DeleteProperty ("coreVersion"); - m_RouterInfo.DeleteProperty ("stat_uptime"); } else { From f4486bc075387828c5daec6c3d4b894cbd1458b3 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 20 Nov 2020 21:48:33 -0500 Subject: [PATCH 3918/6300] take intro key from right address --- libi2pd/SSUSession.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index 860c2be3..b4f2fcdb 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -30,14 +30,15 @@ namespace transport if (router) { // we are client - auto address = router->GetSSUAddress (false); + auto address = IsV6 () ? router->GetSSUV6Address () : router->GetSSUAddress (true); if (address) m_IntroKey = address->ssu->key; m_Data.AdjustPacketSize (router); // mtu } else { // we are server - auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); + auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : + i2p::context.GetRouterInfo ().GetSSUAddress (true); if (address) m_IntroKey = address->ssu->key; } m_CreationTime = i2p::util::GetSecondsSinceEpoch (); From 2266c3877c01af077733e54d9ab2cf5df0a42d3a Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 21 Nov 2020 19:45:06 +0300 Subject: [PATCH 3919/6300] update reseeds Signed-off-by: R4SAS --- .../certificates/reseed/meeh_at_mail.i2p.crt | 32 ------------------- libi2pd/Config.cpp | 5 ++- 2 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 contrib/certificates/reseed/meeh_at_mail.i2p.crt diff --git a/contrib/certificates/reseed/meeh_at_mail.i2p.crt b/contrib/certificates/reseed/meeh_at_mail.i2p.crt deleted file mode 100644 index 6014c96f..00000000 --- a/contrib/certificates/reseed/meeh_at_mail.i2p.crt +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFeTCCA2GgAwIBAgIEZZozujANBgkqhkiG9w0BAQ0FADBtMQswCQYDVQQGEwJY -WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt -b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEWMBQGA1UEAwwNbWVlaEBtYWlsLmky -cDAeFw0xNDA2MjgyMjQ5MDlaFw0yNDA2MjcyMjQ5MDlaMG0xCzAJBgNVBAYTAlhY -MQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBBbm9ueW1v -dXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRYwFAYDVQQDDA1tZWVoQG1haWwuaTJw -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnVnmPE4uUvCky0yCnnVH -cJEDqzwDPupx0zr0YDlhZk5VOPPecx5haayJ/V6nXPc1aVVWn+CHfedcF2aBgN4K -5aBueS/l6l5WHcv02DofAqlTmyAws3oQeR1qoTuW24cKRtLR7h5bxv63f6bgp6e+ -RihFNez6UxErnRPuJOJEO2Im6EgVp6fz7tQ7R35zxAUeES2YILPySvzy2vYm/EEG -jXX7Ap2A5svVo90xCMOeUZ/55vLsjyIshN+tV87U4xwvAkUmwsmWVHm3BQpHkI6z -zMJie6epB8Bqm0GYm0EcElJH4OCxGTvDLoghpswbuUO7iy3JSfoL7ZCnoiQdK9K4 -yVVChj8lG+r7KaTowK96iZep+sZefjOt5VFGuW2Fi/WBv3ldiLlJAo/ZfrUM4+vG -fyNBXbl6bX87uTCGOT1p3dazo+zJMsAZ+Y93DlM/mDEWFa1kKNrs74syzaWEqF4L -KQE6VoYn80OOzafSigTVQgSwUtQtB0XGhMzJhyxU2XHWe1LFIy7Pta0B+lDiZj7c -I8nXxYjsDfEu/Elj/Ra9N6bH0awmgB5JDa+Tbir+oEM5SyDfpSaCGuatdGxjweGI -kVmFU0SqCZV/8TXbIu6MUVzTZMZVT94edifFSRad4fqw7eZbSXlPu++3d1/btn6h -ibM04nkv0mm+FxCKB/wdAkECAwEAAaMhMB8wHQYDVR0OBBYEFO7jIkSRkoXyJcho -9/Q0gDOINa5EMA0GCSqGSIb3DQEBDQUAA4ICAQBzfWO7+8HWOKLaYWToJ6XZbpNF -3wXv1yC4W/HRR80m4JSsq9r0d7838Nvd7vLVP6MY6MaVb/JnV76FdQ5WQ6ticD0Y -o3zmpqqbKVSspN0lrkig4surT88AjfVQz/vEIzKNQEbpzc3hC2LCiE2u+cK/ix4j -b9RohnaPvwLnew5RNQRpcmk+XejaNITISr2yQIwXL7TEYy8HdGCfzFSSFhKe9vkb -GsWS5ASrUzRoprswmlgRe8gEHI+d51Z7mWgna0/5mBz9bH/3QXtpxlLWm3bVV+kt -pZjQDTHE0GqG2YsD1Gmp4LU/JFhCojMTtiPCXmr9KFtpiVlx06DuKm5PC8Ak+5w+ -m/DQYYfv9z+AA5Y430bjnzwg67bhqVyyek4wcDQinFswv3h4bIB7CJujDcEqXXza -lhG1ufPPCUTMrVjh7AShohZraqlSlyQPY9vEppLwD4W1d+MqDHM7ljOH7gQYaUPi -wE30AdXEOxLZcT3aRKxkKf2esNofSuUC/+NXQvPjpuI4UJKO3eegi+M9dbnKoNWs -MPPLPpycecWPheFYM5K6Ao63cjlUY2wYwCfDTFgjA5q8i/Rp7i6Z6fLE3YWJ4VdR -WOFB7hlluQ//jMW6M1qz6IYXmlUjcXl81VEvlOH/QBNrPvX3I3SYXYgVRnVGUudB -o3eNsanvTU+TIFBh2Q== ------END CERTIFICATE----- diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index d99fdde1..d16d82d3 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -197,13 +197,12 @@ namespace config { ("reseed.proxy", value()->default_value(""), "url for reseed proxy, supports http/socks") ("reseed.urls", value()->default_value( "https://reseed.i2p-projekt.de/," - "https://reseed.diva.exchange/," - "https://reseed.i2p2.no/," + "https://reseed.diva.exchange/," "https://reseed-fr.i2pd.xyz/," "https://reseed.memcpy.io/," "https://reseed.onion.im/," "https://i2pseed.creativecowpat.net:8443/," - "https://reseed.i2pgit.org/," + "https://reseed.i2pgit.org/," "https://i2p.novg.net/" ), "Reseed URLs, separated by comma") ; From 3dfb44de3114728b44cd84c7aba29f4d4e68af9c Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 21 Nov 2020 14:27:08 -0500 Subject: [PATCH 3920/6300] exclude DSA floodfills --- libi2pd/NetDb.cpp | 4 ++-- libi2pd/RouterInfo.h | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 4399fb15..e280e31b 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -240,7 +240,7 @@ namespace data std::unique_lock l(m_FloodfillsMutex); if (wasFloodfill) m_Floodfills.remove (r); - else + else if (r->IsEligibleFloodfill ()) m_Floodfills.push_back (r); } } @@ -263,7 +263,7 @@ namespace data if (inserted) { LogPrint (eLogInfo, "NetDb: RouterInfo added: ", ident.ToBase64()); - if (r->IsFloodfill () && r->IsReachable ()) // floodfill must be reachable + if (r->IsFloodfill () && r->IsEligibleFloodfill ()) { std::unique_lock l(m_FloodfillsMutex); m_Floodfills.push_back (r); diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 282c34f9..a4b2fd22 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -184,7 +184,9 @@ namespace data bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; - + // floodfill must be reachable and not DSA + bool IsEligibleFloodfill () const { return IsReachable () && GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; }; + uint8_t GetCaps () const { return m_Caps; }; void SetCaps (uint8_t caps); void SetCaps (const char * caps); From c875ff923a4a77311728aa7fdb9df72dcf47ca67 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 21 Nov 2020 18:44:40 -0500 Subject: [PATCH 3921/6300] random intro key --- libi2pd/RouterContext.cpp | 6 +++--- libi2pd/RouterInfo.cpp | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 220fbc4f..cdab624c 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -89,7 +89,7 @@ namespace i2p host = i2p::util::net::GetInterfaceAddress(ifname4, false).to_string(); if (ssu) - routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + routerInfo.AddSSUAddress (host.c_str(), port, nullptr); } if (ipv6) { @@ -103,7 +103,7 @@ namespace i2p host = i2p::util::net::GetInterfaceAddress(ifname6, true).to_string(); if (ssu) - routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ()); + routerInfo.AddSSUAddress (host.c_str(), port, nullptr); } routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | @@ -486,7 +486,7 @@ namespace i2p if (ssu) { std::string host = "::1"; // TODO: read host - m_RouterInfo.AddSSUAddress (host.c_str (), port, GetIdentHash ()); + m_RouterInfo.AddSSUAddress (host.c_str (), port, nullptr); } } // NTCP2 diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 12f81a42..3fc512f6 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -719,7 +719,10 @@ namespace data addr->date = 0; addr->ssu.reset (new SSUExt ()); addr->ssu->mtu = mtu; - memcpy (addr->ssu->key, key, 32); + if (key) + memcpy (addr->ssu->key, key, 32); + else + RAND_bytes (addr->ssu->key, 32); for (const auto& it: *m_Addresses) // don't insert same address twice if (*it == *addr) return; m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; From 771480e368af28ed6218e76f586e5700eacd3c40 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Nov 2020 17:36:00 -0500 Subject: [PATCH 3922/6300] send queue for incoming I2CP messages --- libi2pd_client/I2CP.cpp | 85 ++++++++++++++++++++++++++++++++++------- libi2pd_client/I2CP.h | 11 +++++- 2 files changed, 82 insertions(+), 14 deletions(-) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 2b9df0fa..05d61474 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -227,12 +227,17 @@ namespace client I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_SessionID (0xFFFF), - m_MessageID (0), m_IsSendAccepted (true) + m_MessageID (0), m_IsSendAccepted (true), m_IsSending (false) { } I2CPSession::~I2CPSession () { + if (m_SendQueue) + { + for (auto& it: *m_SendQueue) + delete[] boost::asio::buffer_cast(it); + } } void I2CPSession::Start () @@ -358,25 +363,64 @@ namespace client if (socket) { auto l = len + I2CP_HEADER_SIZE; - uint8_t * buf = new uint8_t[l]; + uint8_t * buf = m_IsSending ? new uint8_t[l] : m_SendBuffer; htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len); buf[I2CP_HEADER_TYPE_OFFSET] = type; memcpy (buf + I2CP_HEADER_SIZE, payload, len); - boost::asio::async_write (*socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), - std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, buf)); + if (m_IsSending) + { + if (!m_SendQueue) + m_SendQueue = std::make_shared (); + if (m_SendQueue->size () > I2CP_MAX_SEND_QUEUE_SIZE) + { + LogPrint (eLogError, "I2CP: Queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); + return; + } + m_SendQueue->push_back ({buf, l}); + } + else + { + m_IsSending = true; + boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, l), boost::asio::transfer_all (), + std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); + } } else LogPrint (eLogError, "I2CP: Can't write to the socket"); } - void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf) + void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { - delete[] buf; - if (ecode && ecode != boost::asio::error::operation_aborted) - Terminate (); + if (ecode) + { + if (ecode != boost::asio::error::operation_aborted) + Terminate (); + } + else if (m_SendQueue) + { + auto socket = m_Socket; + if (socket) + { + auto queue = m_SendQueue; + m_SendQueue = nullptr; + boost::asio::async_write (*socket, *queue, boost::asio::transfer_all (), + std::bind(&I2CPSession::HandleI2CPMessageSentQueue, shared_from_this (), + std::placeholders::_1, std::placeholders::_2, queue)); + } + } + else + m_IsSending = false; } + void I2CPSession::HandleI2CPMessageSentQueue (const boost::system::error_code& ecode, std::size_t bytes_transferred, SendQueue queue) + { + for (auto& it: *queue) + delete[] boost::asio::buffer_cast(it);; + + HandleI2CPMessageSent (ecode, bytes_transferred); + } + std::string I2CPSession::ExtractString (const uint8_t * buf, size_t len) { uint8_t l = buf[0]; @@ -810,16 +854,31 @@ namespace client if (socket) { auto l = len + 10 + I2CP_HEADER_SIZE; - uint8_t * buf = new uint8_t[l]; + uint8_t * buf = m_IsSending ? new uint8_t[l] : m_SendBuffer; htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 10); buf[I2CP_HEADER_TYPE_OFFSET] = I2CP_MESSAGE_PAYLOAD_MESSAGE; htobe16buf (buf + I2CP_HEADER_SIZE, m_SessionID); htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++); htobe32buf (buf + I2CP_HEADER_SIZE + 6, len); memcpy (buf + I2CP_HEADER_SIZE + 10, payload, len); - boost::asio::async_write (*socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), - std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, buf)); + if (m_IsSending) + { + if (!m_SendQueue) + m_SendQueue = std::make_shared (); + if (m_SendQueue->size () > I2CP_MAX_SEND_QUEUE_SIZE) + { + LogPrint (eLogError, "I2CP: Queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); + return; + } + m_SendQueue->push_back ({buf, l}); + } + else + { + m_IsSending = true; + boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, l), boost::asio::transfer_all (), + std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); + } } else LogPrint (eLogError, "I2CP: Can't write to the socket"); diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index c5dc80e7..51d503aa 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -25,6 +25,7 @@ namespace client const uint8_t I2CP_PROTOCOL_BYTE = 0x2A; const size_t I2CP_SESSION_BUFFER_SIZE = 4096; const size_t I2CP_MAX_MESSAGE_LENGTH = 65535; + const size_t I2CP_MAX_SEND_QUEUE_SIZE = 256; const size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; @@ -122,6 +123,8 @@ namespace client class I2CPServer; class I2CPSession: public std::enable_shared_from_this { + typedef std::shared_ptr > SendQueue; + public: #ifdef ANDROID @@ -167,7 +170,8 @@ namespace client void HandleMessage (); void Terminate (); - void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf); + void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void HandleI2CPMessageSentQueue (const boost::system::error_code& ecode, std::size_t bytes_transferred, SendQueue queue); std::string ExtractString (const uint8_t * buf, size_t len); size_t PutString (uint8_t * buf, size_t len, const std::string& str); void ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping); @@ -186,6 +190,11 @@ namespace client uint16_t m_SessionID; uint32_t m_MessageID; bool m_IsSendAccepted; + + // to client + bool m_IsSending; + uint8_t m_SendBuffer[I2CP_MAX_MESSAGE_LENGTH]; + SendQueue m_SendQueue; }; typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); From bc330ff0ea134d5d18d927609d3870192829f70f Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 23 Nov 2020 01:44:21 +0300 Subject: [PATCH 3923/6300] update makefiles, license year Signed-off-by: R4SAS --- LICENSE | 2 +- Makefile | 5 +++-- Makefile.linux | 16 ++++++++-------- Makefile.mingw | 11 ++++++----- Win32/Resource.rc2 | 2 +- build/build_mingw.cmd | 2 +- contrib/android_binary_pack/build-archive | 2 +- contrib/android_binary_pack/i2pd | 10 +++++----- libi2pd/FS.cpp | 6 +++--- libi2pd/Timestamp.cpp | 2 +- libi2pd/util.cpp | 12 ++++++------ qt/i2pd_qt/i2pd.rc | 2 +- 12 files changed, 37 insertions(+), 35 deletions(-) diff --git a/LICENSE b/LICENSE index 2cb10225..9a1e4527 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2015, The PurpleI2P Project +Copyright (c) 2013-2020, The PurpleI2P Project All rights reserved. diff --git a/Makefile b/Makefile index 7bb3ceda..3e0b23cf 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) LIB_CLIENT_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) -DEPS := $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) +DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) @@ -82,7 +82,7 @@ obj/%.o: %.cpp -include $(DEPS) $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) - $(CXX) -o $@ $^ $(LDFLAGS) $(LDLIBS) + $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) $(SHLIB): $(LIB_OBJS) ifneq ($(USE_STATIC),yes) @@ -130,3 +130,4 @@ doxygen: .PHONY: api_client .PHONY: mk_obj_dir .PHONY: install +.PHONY: strip diff --git a/Makefile.linux b/Makefile.linux index 6a7590c1..ee6a902b 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -33,7 +33,7 @@ ifeq ($(USE_STATIC),yes) # NOTE: on glibc you will get this warning: # Using 'getaddrinfo' in statically linked applications requires at runtime # the shared libraries from the glibc version used for linking - LIBDIR := /usr/lib + LIBDIR := /usr/lib/$(SYS) LDLIBS += $(LIBDIR)/libboost_system.a LDLIBS += $(LIBDIR)/libboost_date_time.a LDLIBS += $(LIBDIR)/libboost_filesystem.a @@ -41,20 +41,20 @@ ifeq ($(USE_STATIC),yes) LDLIBS += $(LIBDIR)/libssl.a LDLIBS += $(LIBDIR)/libcrypto.a LDLIBS += $(LIBDIR)/libz.a - LDLIBS += -lpthread -static-libstdc++ -static-libgcc -lrt -ldl - USE_AESNI := no +ifeq ($(USE_UPNP),yes) + LDLIBS += $(LIBDIR)/libminiupnpc.a +endif + LDLIBS += -lpthread -ldl else LDLIBS += -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread +ifeq ($(USE_UPNP),yes) + LDLIBS += -lminiupnpc +endif endif # UPNP Support (miniupnpc 1.5 and higher) ifeq ($(USE_UPNP),yes) NEEDED_CXXFLAGS += -DUSE_UPNP -ifeq ($(USE_STATIC),yes) - LDLIBS += $(LIBDIR)/libminiupnpc.a -else - LDLIBS += -lminiupnpc -endif endif ifeq ($(USE_AESNI),yes) diff --git a/Makefile.mingw b/Makefile.mingw index 4cd2c292..ce1966a1 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -1,9 +1,11 @@ -USE_WIN32_APP=yes -CXX = g++ +# Build application with GUI (tray, main window) +USE_WIN32_APP := yes + WINDRES = windres -CXXFLAGS := $(CXX_DEBUG) -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN -fPIC -msse + +CXXFLAGS := $(CXX_DEBUG) -D_MT -DWIN32_LEAN_AND_MEAN -fPIC -msse INCFLAGS = -I$(DAEMON_SRC_DIR) -IWin32 -LDFLAGS := ${LD_DEBUG} -Wl,-Bstatic -static-libgcc -static-libstdc++ +LDFLAGS := ${LD_DEBUG} -Wl,-Bstatic -static-libgcc # detect proper flag for c++11 support by compilers CXXVER := $(shell $(CXX) -dumpversion) @@ -38,7 +40,6 @@ LDLIBS += \ -liphlpapi \ -lole32 \ -luuid \ - -lstdc++ \ -lpthread ifeq ($(USE_WIN32_APP), yes) diff --git a/Win32/Resource.rc2 b/Win32/Resource.rc2 index 6a4f481d..9eecbc1f 100644 --- a/Win32/Resource.rc2 +++ b/Win32/Resource.rc2 @@ -25,7 +25,7 @@ BEGIN VALUE "FileDescription", "C++ I2P daemon" VALUE "FileVersion", I2PD_VERSION VALUE "InternalName", CODENAME - VALUE "LegalCopyright", "Copyright (C) 2013-2017, The PurpleI2P Project" + VALUE "LegalCopyright", "Copyright (C) 2013-2020, The PurpleI2P Project" VALUE "OriginalFilename", "i2pd" VALUE "ProductName", "Purple I2P" VALUE "ProductVersion", I2P_VERSION diff --git a/build/build_mingw.cmd b/build/build_mingw.cmd index e791039e..aaf25843 100644 --- a/build/build_mingw.cmd +++ b/build/build_mingw.cmd @@ -2,7 +2,7 @@ setlocal enableextensions enabledelayedexpansion title Building i2pd -REM Copyright (c) 2013-2017, The PurpleI2P Project +REM Copyright (c) 2013-2020, The PurpleI2P Project REM This file is part of Purple i2pd project and licensed under BSD3 REM See full license text in LICENSE file at top of project tree diff --git a/contrib/android_binary_pack/build-archive b/contrib/android_binary_pack/build-archive index bb56cace..c439dd7f 100755 --- a/contrib/android_binary_pack/build-archive +++ b/contrib/android_binary_pack/build-archive @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2013-2017, The PurpleI2P Project +# Copyright (c) 2013-2020, The PurpleI2P Project # # This file is part of Purple i2pd project and licensed under BSD3 # diff --git a/contrib/android_binary_pack/i2pd b/contrib/android_binary_pack/i2pd index aeaae804..c31cb4ad 100755 --- a/contrib/android_binary_pack/i2pd +++ b/contrib/android_binary_pack/i2pd @@ -1,6 +1,6 @@ #!/bin/sh -# Copyright (c) 2013-2019, The PurpleI2P Project +# Copyright (c) 2013-2020, The PurpleI2P Project # # This file is part of Purple i2pd project and licensed under BSD3 # @@ -21,13 +21,13 @@ arch=$(uname -m) screenfind=$(which screen) if [ -z $screenfind ]; then - echo "Can't find 'screen' installed. That script needs it!"; - exit 1; + echo "Can't find 'screen' installed. That script needs it!"; + exit 1; fi if [ -z i2pd-$arch ]; then - echo "Can't find i2pd binary for your archtecture."; - exit 1; + echo "Can't find i2pd binary for your archtecture."; + exit 1; fi screen -AmdS i2pd ./i2pd-$arch --datadir=$DIR diff --git a/libi2pd/FS.cpp b/libi2pd/FS.cpp index 32fc3ec0..bd1a7ad2 100644 --- a/libi2pd/FS.cpp +++ b/libi2pd/FS.cpp @@ -46,13 +46,13 @@ namespace fs { dataDir = cmdline_param; return; } -#if defined(WIN32) || defined(_WIN32) +#ifdef _WIN32 char localAppData[MAX_PATH]; // check executable directory first if(!GetModuleFileName(NULL, localAppData, MAX_PATH)) { -#if defined(WIN32_APP) +#ifdef WIN32_APP MessageBox(NULL, TEXT("Unable to get application path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); #else fprintf(stderr, "Error: Unable to get application path!"); @@ -70,7 +70,7 @@ namespace fs { { if(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK) { -#if defined(WIN32_APP) +#ifdef WIN32_APP MessageBox(NULL, TEXT("Unable to get AppData path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); #else fprintf(stderr, "Error: Unable to get AppData path!"); diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index 45684333..0490350e 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -19,7 +19,7 @@ #include "I2PEndian.h" #include "Timestamp.h" -#ifdef WIN32 +#ifdef _WIN32 #ifndef _WIN64 #define _USE_32BIT_TIME_T #endif diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 2cc101c7..24814ad3 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -13,7 +13,7 @@ #include "util.h" #include "Log.h" -#ifdef WIN32 +#ifdef _WIN32 #include #include #include @@ -56,7 +56,7 @@ int inet_pton_xp(int af, const char *src, void *dst) } return 0; } -#else /* !WIN32 => UNIX */ +#else /* !_WIN32 => UNIX */ #include #include #endif @@ -109,7 +109,7 @@ namespace util namespace net { -#ifdef WIN32 +#ifdef _WIN32 bool IsWindowsXPorLater() { static bool isRequested = false; @@ -333,13 +333,13 @@ namespace net return mtu; } -#endif // WIN32 +#endif // _WIN32 int GetMTU(const boost::asio::ip::address& localAddress) { int fallback = localAddress.is_v6 () ? 1280 : 620; // fallback MTU -#ifdef WIN32 +#ifdef _WIN32 return GetMTUWindows(localAddress, fallback); #else return GetMTUUnix(localAddress, fallback); @@ -349,7 +349,7 @@ namespace net const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6) { -#ifdef WIN32 +#ifdef _WIN32 LogPrint(eLogError, "NetIface: cannot get address by interface name, not implemented on WIN32"); if(ipv6) return boost::asio::ip::address::from_string("::1"); diff --git a/qt/i2pd_qt/i2pd.rc b/qt/i2pd_qt/i2pd.rc index bebdf1d6..d31e591e 100644 --- a/qt/i2pd_qt/i2pd.rc +++ b/qt/i2pd_qt/i2pd.rc @@ -17,7 +17,7 @@ BEGIN VALUE "FileDescription", "I2Pd Qt" VALUE "FileVersion", I2PD_VERSION VALUE "InternalName", "i2pd-qt" - VALUE "LegalCopyright", "Copyright (C) 2013-2018, The PurpleI2P Project" + VALUE "LegalCopyright", "Copyright (C) 2013-2020, The PurpleI2P Project" VALUE "LegalTrademarks1", "Distributed under the BSD 3-Clause software license, see the accompanying file COPYING or https://opensource.org/licenses/BSD-3-Clause." VALUE "OriginalFilename", "i2pd_qt.exe" VALUE "ProductName", "i2pd-qt" From 86e3b977e492e832933596f46753c73a85299d5e Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Nov 2020 21:41:27 -0500 Subject: [PATCH 3924/6300] check I2CP message size --- libi2pd_client/I2CP.cpp | 115 ++++++++++++++++++---------------------- libi2pd_client/I2CP.h | 3 +- 2 files changed, 55 insertions(+), 63 deletions(-) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 05d61474..a2e2d79f 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -354,40 +354,17 @@ namespace client void I2CPSession::SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len) { - if (len > I2CP_MAX_MESSAGE_LENGTH) + auto l = len + I2CP_HEADER_SIZE; + if (l > I2CP_MAX_MESSAGE_LENGTH) { - LogPrint (eLogError, "I2CP: Message to send is too long ", len); + LogPrint (eLogError, "I2CP: Message to send is too long ", l); return; } - auto socket = m_Socket; - if (socket) - { - auto l = len + I2CP_HEADER_SIZE; - uint8_t * buf = m_IsSending ? new uint8_t[l] : m_SendBuffer; - htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len); - buf[I2CP_HEADER_TYPE_OFFSET] = type; - memcpy (buf + I2CP_HEADER_SIZE, payload, len); - if (m_IsSending) - { - if (!m_SendQueue) - m_SendQueue = std::make_shared (); - if (m_SendQueue->size () > I2CP_MAX_SEND_QUEUE_SIZE) - { - LogPrint (eLogError, "I2CP: Queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); - return; - } - m_SendQueue->push_back ({buf, l}); - } - else - { - m_IsSending = true; - boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, l), boost::asio::transfer_all (), - std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); - } - } - else - LogPrint (eLogError, "I2CP: Can't write to the socket"); + uint8_t * buf = m_IsSending ? new uint8_t[l] : m_SendBuffer; + htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len); + buf[I2CP_HEADER_TYPE_OFFSET] = type; + memcpy (buf + I2CP_HEADER_SIZE, payload, len); + SendBuffer (buf, l); } void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) @@ -420,6 +397,38 @@ namespace client HandleI2CPMessageSent (ecode, bytes_transferred); } + + void I2CPSession::SendBuffer (uint8_t * buf, size_t len) + { + auto socket = m_Socket; + if (socket) + { + if (m_IsSending) + { + auto sendQueue = m_SendQueue; + if (!sendQueue) + { + sendQueue = std::make_shared (); + m_SendQueue = sendQueue; + } + else if (sendQueue->size () > I2CP_MAX_SEND_QUEUE_SIZE) + { + LogPrint (eLogError, "I2CP: Queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); + return; + } + sendQueue->push_back ({buf, len}); + } + else + { + m_IsSending = true; + boost::asio::async_write (*socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (), + std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); + } + } + else + LogPrint (eLogError, "I2CP: Can't write to the socket"); + } std::string I2CPSession::ExtractString (const uint8_t * buf, size_t len) { @@ -850,38 +859,20 @@ namespace client void I2CPSession::SendMessagePayloadMessage (const uint8_t * payload, size_t len) { // we don't use SendI2CPMessage to eliminate additional copy - auto socket = m_Socket; - if (socket) + auto l = len + 10 + I2CP_HEADER_SIZE; + if (l > I2CP_MAX_MESSAGE_LENGTH) { - auto l = len + 10 + I2CP_HEADER_SIZE; - uint8_t * buf = m_IsSending ? new uint8_t[l] : m_SendBuffer; - htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 10); - buf[I2CP_HEADER_TYPE_OFFSET] = I2CP_MESSAGE_PAYLOAD_MESSAGE; - htobe16buf (buf + I2CP_HEADER_SIZE, m_SessionID); - htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++); - htobe32buf (buf + I2CP_HEADER_SIZE + 6, len); - memcpy (buf + I2CP_HEADER_SIZE + 10, payload, len); - if (m_IsSending) - { - if (!m_SendQueue) - m_SendQueue = std::make_shared (); - if (m_SendQueue->size () > I2CP_MAX_SEND_QUEUE_SIZE) - { - LogPrint (eLogError, "I2CP: Queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); - return; - } - m_SendQueue->push_back ({buf, l}); - } - else - { - m_IsSending = true; - boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, l), boost::asio::transfer_all (), - std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); - } - } - else - LogPrint (eLogError, "I2CP: Can't write to the socket"); + LogPrint (eLogError, "I2CP: Message to send is too long ", l); + return; + } + uint8_t * buf = m_IsSending ? new uint8_t[l] : m_SendBuffer; + htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 10); + buf[I2CP_HEADER_TYPE_OFFSET] = I2CP_MESSAGE_PAYLOAD_MESSAGE; + htobe16buf (buf + I2CP_HEADER_SIZE, m_SessionID); + htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++); + htobe32buf (buf + I2CP_HEADER_SIZE + 6, len); + memcpy (buf + I2CP_HEADER_SIZE + 10, payload, len); + SendBuffer (buf, l); } I2CPServer::I2CPServer (const std::string& interface, int port, bool isSingleThread): diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 51d503aa..ac3acd45 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -169,7 +169,8 @@ namespace client void HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleMessage (); void Terminate (); - + void SendBuffer (uint8_t * buf, size_t len); + void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleI2CPMessageSentQueue (const boost::system::error_code& ecode, std::size_t bytes_transferred, SendQueue queue); std::string ExtractString (const uint8_t * buf, size_t len); From 9301e39af786865665b046be85eb044ba6e72b9d Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Nov 2020 12:49:18 -0500 Subject: [PATCH 3925/6300] minimal version for floodfill 0.9.28 --- libi2pd/NetDb.hpp | 1 + libi2pd/RouterInfo.cpp | 7 +++++++ libi2pd/RouterInfo.h | 3 +-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index afd1f562..845217e1 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -44,6 +44,7 @@ namespace data const int NETDB_PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds const int NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 36); // 0.9.36 + const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 28); // 0.9.28 /** function for visiting a leaseset stored in a floodfill */ typedef std::function)> LeaseSetVisitor; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 3fc512f6..13776b81 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -948,5 +948,12 @@ namespace data if (encryptor) encryptor->Encrypt (data, encrypted, ctx, true); } + + bool RouterInfo::IsEligibleFloodfill () const + { + // floodfill must be reachable, >= 0.9.28 and not DSA + return IsReachable () && m_Version >= NETDB_MIN_FLOODFILL_VERSION && + GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; + } } } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index a4b2fd22..733ea44a 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -184,8 +184,7 @@ namespace data bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; - // floodfill must be reachable and not DSA - bool IsEligibleFloodfill () const { return IsReachable () && GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; }; + bool IsEligibleFloodfill () const; uint8_t GetCaps () const { return m_Caps; }; void SetCaps (uint8_t caps); From 1c5b350c2b90d53dbed1841087e4098c30903a20 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Nov 2020 18:55:48 -0500 Subject: [PATCH 3926/6300] TCP_QUICKACK --- libi2pd/NTCP2.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 7a88e4d8..bb54437e 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -742,6 +742,10 @@ namespace transport void NTCP2Session::ReceiveLength () { if (IsTerminated ()) return; +#ifdef __linux__ + const int one = 1; + setsockopt(m_Socket.native_handle(), IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one)); +#endif boost::asio::async_read (m_Socket, boost::asio::buffer(&m_NextReceivedLen, 2), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleReceivedLength, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -793,6 +797,10 @@ namespace transport void NTCP2Session::Receive () { if (IsTerminated ()) return; +#ifdef __linux__ + const int one = 1; + setsockopt(m_Socket.native_handle(), IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one)); +#endif boost::asio::async_read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } From c833b16544a8081d5390cb954e82a1d4b5dbffd1 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Nov 2020 09:15:45 -0500 Subject: [PATCH 3927/6300] check if session expired before generating more tags --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 3dfe4ca5..00efee48 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1011,8 +1011,11 @@ namespace garlic void ECIESX25519AEADRatchetSession::GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags) { - for (int i = 0; i < numTags; i++) - GetOwner ()->AddECIESx25519SessionNextTag (receiveTagset); + if (GetOwner ()) + { + for (int i = 0; i < numTags; i++) + GetOwner ()->AddECIESx25519SessionNextTag (receiveTagset); + } } bool ECIESX25519AEADRatchetSession::CheckExpired (uint64_t ts) From ad36738f57fb8709893e27cb92dffe269d7ec337 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 27 Nov 2020 13:37:03 -0500 Subject: [PATCH 3928/6300] detach session from destination upon termination --- libi2pd_client/I2CP.cpp | 20 +++++++++++++++----- libi2pd_client/I2CP.h | 2 ++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index a2e2d79f..5b8c09b2 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -30,6 +30,12 @@ namespace client { } + void I2CPDestination::Stop () + { + LeaseSetDestination::Stop (); + m_Owner = nullptr; + } + void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) { m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), key); @@ -72,7 +78,8 @@ namespace client { uint32_t length = bufbe32toh (buf); if (length > len - 4) length = len - 4; - m_Owner->SendMessagePayloadMessage (buf + 4, length); + if (m_Owner) + m_Owner->SendMessagePayloadMessage (buf + 4, length); } void I2CPDestination::CreateNewLeaseSet (std::vector > tunnels) @@ -84,7 +91,8 @@ namespace client leases[-1] = tunnels.size (); htobe16buf (leases - 3, m_Owner->GetSessionID ()); size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); - m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); + if (m_Owner) + m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); } void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) @@ -119,7 +127,8 @@ namespace client [s, msg, remote, nonce]() { bool sent = s->SendMsg (msg, remote); - s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); + if (s->m_Owner) + s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); }); } else @@ -130,9 +139,10 @@ namespace client if (ls) { bool sent = s->SendMsg (msg, ls); - s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); + if (s->m_Owner) + s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); } - else + else if (s->m_Owner) s->m_Owner->SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLeaseSet); }); } diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index ac3acd45..460b8638 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -71,6 +71,8 @@ namespace client I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params); ~I2CPDestination () {}; + + void Stop (); void SetEncryptionPrivateKey (const uint8_t * key); void SetEncryptionType (i2p::data::CryptoKeyType keyType) { m_EncryptionKeyType = keyType; }; From 242fb7db14c84e5a8ff960ab3affb8a1a0a6ae81 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 28 Nov 2020 10:09:38 -0500 Subject: [PATCH 3929/6300] terminate I2CP session if destroyed explicitly --- libi2pd_client/I2CP.cpp | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 5b8c09b2..9e9d17ee 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -89,10 +89,16 @@ namespace client m_LeaseSetExpirationTime = ls.GetExpirationTime (); uint8_t * leases = ls.GetLeases (); leases[-1] = tunnels.size (); - htobe16buf (leases - 3, m_Owner->GetSessionID ()); - size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); if (m_Owner) - m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); + { + uint16_t sessionID = m_Owner->GetSessionID (); + if (sessionID != 0xFFFF) + { + htobe16buf (leases - 3, sessionID); + size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); + m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); + } + } } void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) @@ -243,11 +249,7 @@ namespace client I2CPSession::~I2CPSession () { - if (m_SendQueue) - { - for (auto& it: *m_SendQueue) - delete[] boost::asio::buffer_cast(it); - } + Terminate (); } void I2CPSession::Start () @@ -358,8 +360,18 @@ namespace client m_Socket->close (); m_Socket = nullptr; } - m_Owner.RemoveSession (GetSessionID ()); - LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " terminated"); + if (m_SendQueue) + { + for (auto& it: *m_SendQueue) + delete[] boost::asio::buffer_cast(it); + m_SendQueue = nullptr; + } + if (m_SessionID != 0xFFFF) + { + m_Owner.RemoveSession (GetSessionID ()); + LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " terminated"); + m_SessionID = 0xFFFF; + } } void I2CPSession::SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len) @@ -555,11 +567,7 @@ namespace client { SendSessionStatusMessage (0); // destroy LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " destroyed"); - if (m_Destination) - { - m_Destination->Stop (); - m_Destination = 0; - } + Terminate (); } void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len) From ff971563dbdc8e18b1b894284ea5b71252d4b8e9 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 28 Nov 2020 22:25:06 -0500 Subject: [PATCH 3930/6300] cleanup queue after buffers deletion --- libi2pd_client/I2CP.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 9e9d17ee..82ee7841 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -364,6 +364,7 @@ namespace client { for (auto& it: *m_SendQueue) delete[] boost::asio::buffer_cast(it); + m_SendQueue->clear (); m_SendQueue = nullptr; } if (m_SessionID != 0xFFFF) @@ -415,7 +416,8 @@ namespace client void I2CPSession::HandleI2CPMessageSentQueue (const boost::system::error_code& ecode, std::size_t bytes_transferred, SendQueue queue) { for (auto& it: *queue) - delete[] boost::asio::buffer_cast(it);; + delete[] boost::asio::buffer_cast(it); + queue->clear (); HandleI2CPMessageSent (ecode, bytes_transferred); } From 746f53ba0792d786e3038bc9f466a8bc7d0e9198 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 29 Nov 2020 14:59:34 -0500 Subject: [PATCH 3931/6300] use SendBufferQueue for queued messages from I2P --- libi2pd/Streaming.cpp | 12 ++++- libi2pd/Streaming.h | 6 +++ libi2pd_client/I2CP.cpp | 113 +++++++++++++++++++--------------------- libi2pd_client/I2CP.h | 11 ++-- 4 files changed, 75 insertions(+), 67 deletions(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 80e8aecf..05b34d9e 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -21,10 +21,18 @@ namespace stream { void SendBufferQueue::Add (const uint8_t * buf, size_t len, SendHandler handler) { - m_Buffers.push_back (std::make_shared(buf, len, handler)); - m_Size += len; + Add (std::make_shared(buf, len, handler)); } + void SendBufferQueue::Add (std::shared_ptr buf) + { + if (buf) + { + m_Buffers.push_back (buf); + m_Size += buf->len; + } + } + size_t SendBufferQueue::Get (uint8_t * buf, size_t len) { size_t offset = 0; diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index e8b8db91..ab3c4439 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -111,6 +111,11 @@ namespace stream buf = new uint8_t[len]; memcpy (buf, b, len); } + SendBuffer (size_t l): // creat empty buffer + len(l), offset (0) + { + buf = new uint8_t[len]; + } ~SendBuffer () { delete[] buf; @@ -129,6 +134,7 @@ namespace stream ~SendBufferQueue () { CleanUp (); }; void Add (const uint8_t * buf, size_t len, SendHandler handler); + void Add (std::shared_ptr buf); size_t Get (uint8_t * buf, size_t len); size_t GetSize () const { return m_Size; }; bool IsEmpty () const { return m_Buffers.empty (); }; diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 82ee7841..cb618b5d 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -360,13 +360,8 @@ namespace client m_Socket->close (); m_Socket = nullptr; } - if (m_SendQueue) - { - for (auto& it: *m_SendQueue) - delete[] boost::asio::buffer_cast(it); - m_SendQueue->clear (); - m_SendQueue = nullptr; - } + if (!m_SendQueue.IsEmpty ()) + m_SendQueue.CleanUp (); if (m_SessionID != 0xFFFF) { m_Owner.RemoveSession (GetSessionID ()); @@ -383,11 +378,32 @@ namespace client LogPrint (eLogError, "I2CP: Message to send is too long ", l); return; } - uint8_t * buf = m_IsSending ? new uint8_t[l] : m_SendBuffer; + auto sendBuf = m_IsSending ? std::make_shared (l) : nullptr; + uint8_t * buf = sendBuf ? sendBuf->buf : m_SendBuffer; htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len); buf[I2CP_HEADER_TYPE_OFFSET] = type; memcpy (buf + I2CP_HEADER_SIZE, payload, len); - SendBuffer (buf, l); + if (sendBuf) + { + if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE) + m_SendQueue.Add (sendBuf); + else + { + LogPrint (eLogWarning, "I2CP: send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); + return; + } + } + else + { + auto socket = m_Socket; + if (socket) + { + m_IsSending = true; + boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, l), + boost::asio::transfer_all (), std::bind(&I2CPSession::HandleI2CPMessageSent, + shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + } + } } void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) @@ -397,62 +413,22 @@ namespace client if (ecode != boost::asio::error::operation_aborted) Terminate (); } - else if (m_SendQueue) + else if (!m_SendQueue.IsEmpty ()) { auto socket = m_Socket; if (socket) { - auto queue = m_SendQueue; - m_SendQueue = nullptr; - boost::asio::async_write (*socket, *queue, boost::asio::transfer_all (), - std::bind(&I2CPSession::HandleI2CPMessageSentQueue, shared_from_this (), - std::placeholders::_1, std::placeholders::_2, queue)); + auto len = m_SendQueue.Get (m_SendBuffer, I2CP_MAX_MESSAGE_LENGTH); + boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, len), + boost::asio::transfer_all (),std::bind(&I2CPSession::HandleI2CPMessageSent, + shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } + else + m_IsSending = false; } else m_IsSending = false; } - - void I2CPSession::HandleI2CPMessageSentQueue (const boost::system::error_code& ecode, std::size_t bytes_transferred, SendQueue queue) - { - for (auto& it: *queue) - delete[] boost::asio::buffer_cast(it); - queue->clear (); - - HandleI2CPMessageSent (ecode, bytes_transferred); - } - - void I2CPSession::SendBuffer (uint8_t * buf, size_t len) - { - auto socket = m_Socket; - if (socket) - { - if (m_IsSending) - { - auto sendQueue = m_SendQueue; - if (!sendQueue) - { - sendQueue = std::make_shared (); - m_SendQueue = sendQueue; - } - else if (sendQueue->size () > I2CP_MAX_SEND_QUEUE_SIZE) - { - LogPrint (eLogError, "I2CP: Queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); - return; - } - sendQueue->push_back ({buf, len}); - } - else - { - m_IsSending = true; - boost::asio::async_write (*socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (), - std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); - } - } - else - LogPrint (eLogError, "I2CP: Can't write to the socket"); - } std::string I2CPSession::ExtractString (const uint8_t * buf, size_t len) { @@ -885,14 +861,35 @@ namespace client LogPrint (eLogError, "I2CP: Message to send is too long ", l); return; } - uint8_t * buf = m_IsSending ? new uint8_t[l] : m_SendBuffer; + auto sendBuf = m_IsSending ? std::make_shared (l) : nullptr; + uint8_t * buf = sendBuf ? sendBuf->buf : m_SendBuffer; htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 10); buf[I2CP_HEADER_TYPE_OFFSET] = I2CP_MESSAGE_PAYLOAD_MESSAGE; htobe16buf (buf + I2CP_HEADER_SIZE, m_SessionID); htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++); htobe32buf (buf + I2CP_HEADER_SIZE + 6, len); memcpy (buf + I2CP_HEADER_SIZE + 10, payload, len); - SendBuffer (buf, l); + if (sendBuf) + { + if (m_SendQueue.GetSize () < I2CP_MAX_SEND_QUEUE_SIZE) + m_SendQueue.Add (sendBuf); + else + { + LogPrint (eLogWarning, "I2CP: send queue size exceeds ", I2CP_MAX_SEND_QUEUE_SIZE); + return; + } + } + else + { + auto socket = m_Socket; + if (socket) + { + m_IsSending = true; + boost::asio::async_write (*socket, boost::asio::buffer (m_SendBuffer, l), + boost::asio::transfer_all (), std::bind(&I2CPSession::HandleI2CPMessageSent, + shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + } + } } I2CPServer::I2CPServer (const std::string& interface, int port, bool isSingleThread): diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 460b8638..32f32221 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -17,6 +17,7 @@ #include #include "util.h" #include "Destination.h" +#include "Streaming.h" namespace i2p { @@ -25,7 +26,7 @@ namespace client const uint8_t I2CP_PROTOCOL_BYTE = 0x2A; const size_t I2CP_SESSION_BUFFER_SIZE = 4096; const size_t I2CP_MAX_MESSAGE_LENGTH = 65535; - const size_t I2CP_MAX_SEND_QUEUE_SIZE = 256; + const size_t I2CP_MAX_SEND_QUEUE_SIZE = 1024*1024; // in bytes, 1M const size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; @@ -125,8 +126,6 @@ namespace client class I2CPServer; class I2CPSession: public std::enable_shared_from_this { - typedef std::shared_ptr > SendQueue; - public: #ifdef ANDROID @@ -171,14 +170,12 @@ namespace client void HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleMessage (); void Terminate (); - void SendBuffer (uint8_t * buf, size_t len); void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandleI2CPMessageSentQueue (const boost::system::error_code& ecode, std::size_t bytes_transferred, SendQueue queue); + std::string ExtractString (const uint8_t * buf, size_t len); size_t PutString (uint8_t * buf, size_t len, const std::string& str); void ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping); - void SendSessionStatusMessage (uint8_t status); void SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity); @@ -197,7 +194,7 @@ namespace client // to client bool m_IsSending; uint8_t m_SendBuffer[I2CP_MAX_MESSAGE_LENGTH]; - SendQueue m_SendQueue; + i2p::stream::SendBufferQueue m_SendQueue; }; typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); From 58153c3579b7e0e9dd1632891b770b82d295ec19 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 30 Nov 2020 04:10:13 +0300 Subject: [PATCH 3932/6300] [webconsole] fix content block width Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index ecc5acdd..9f420cf0 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -70,7 +70,7 @@ namespace http { " .menu { float: left; } .menu a, .commands a { display: block; }\r\n" " .listitem { display: block; font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n" " .tableitem { font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n" - " .content { float: left; font-size: 1em; margin-left: 4em; max-width: 46em; overflow: auto; }\r\n" + " .content { float: left; font-size: 1em; margin-left: 4em; max-width: 45em; overflow: auto; }\r\n" " .tunnel.established { color: #56B734; } .tunnel.expiring { color: #D3AE3F; }\r\n" " .tunnel.failed { color: #D33F3F; } .tunnel.building { color: #434343; }\r\n" " caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n" From 0ab95b1b8791cfe2fab856b42b5ffbd883a15906 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 30 Nov 2020 12:50:15 -0500 Subject: [PATCH 3933/6300] 2.35.0 --- ChangeLog | 22 +++++++++++++++++++ android/build.gradle | 4 ++-- appveyor.yml | 2 +- contrib/rpm/i2pd-git.spec | 5 ++++- contrib/rpm/i2pd.spec | 5 ++++- debian/changelog | 6 +++++ libi2pd/version.h | 4 ++-- qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml | 1 + 8 files changed, 42 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index dbce5fb2..bc9b28df 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,28 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog +## [2.35.0] - 2020-11-30 +### Added +- ECIES-x25519 routers +- Random intro keys for SSU +- Graceful shutdown timer for windows +- Send queue for I2CP messages +- Update DSA router keys to EdDSA +- TCP_QUICKACK for NTCP2 sockets on Linux +### Changed +- Exclude floodfills with DSA signatures and < 0.9.28 +- Random intervals between tunnel tests and manage for tunnel pools +- Don't replace an addressbook record by one with DSA signature +- Publish RouterInfo after update +- Create paired inbound tunnels if no inbound tunnels yet +- Reseed servers list +### Fixed +- Transient signature length, if different from identity +- Terminate I2CP session if destroyed +- RouterInfo publishing confirmation +- Check if ECIES-X25519-AEAD-Ratchet session expired before generating more tags +- Correct block size for delivery type local for ECIES-X25519-AEAD-Ratchet + ## [2.34.0] - 2020-10-27 ### Added - Ping responses for streaming diff --git a/android/build.gradle b/android/build.gradle index 2c6c782d..dbe75759 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -30,8 +30,8 @@ android { applicationId "org.purplei2p.i2pd" targetSdkVersion 29 minSdkVersion 14 - versionCode 2340 - versionName "2.34.0" + versionCode 2350 + versionName "2.35.0" setProperty("archivesBaseName", archivesBaseName + "-" + versionName) ndk { abiFilters 'armeabi-v7a' diff --git a/appveyor.yml b/appveyor.yml index a8e7d30d..cc6d130b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.34.0.{build} +version: 2.35.0.{build} pull_requests: do_not_increment_build_number: true branches: diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index a320d6aa..d622ced5 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -1,7 +1,7 @@ %define git_hash %(git rev-parse HEAD | cut -c -7) Name: i2pd-git -Version: 2.34.0 +Version: 2.35.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -137,6 +137,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Nov 30 2020 orignal - 2.35.0 +- update to 2.35.0 + * Tue Oct 27 2020 orignal - 2.34.0 - update to 2.34.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 3f5ef260..d147c269 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.34.0 +Version: 2.35.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -135,6 +135,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Nov 30 2020 orignal - 2.35.0 +- update to 2.35.0 + * Tue Oct 27 2020 orignal - 2.34.0 - update to 2.34.0 diff --git a/debian/changelog b/debian/changelog index b79f6cd6..7ca75409 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.35.0-1) unstable; urgency=high + + * updated to version 2.35.0/0.9.48 + + -- orignal Mon, 30 Nov 2020 16:00:00 +0000 + i2pd (2.34.0-1) unstable; urgency=medium * updated to version 2.34.0 diff --git a/libi2pd/version.h b/libi2pd/version.h index 26ced1c3..f28d88c0 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -16,7 +16,7 @@ #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 34 +#define I2PD_VERSION_MINOR 35 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) @@ -30,7 +30,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 47 +#define I2P_VERSION_MICRO 48 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml index 97a759c2..e23729a8 100644 --- a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml +++ b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml @@ -35,6 +35,7 @@ + From ad84944d20c9dd86058f6bdd6c10ea15d0f62425 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 1 Dec 2020 03:55:41 +0300 Subject: [PATCH 3934/6300] [make] change AES support check --- Makefile.linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.linux b/Makefile.linux index ee6a902b..da5f68b4 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -58,7 +58,7 @@ ifeq ($(USE_UPNP),yes) endif ifeq ($(USE_AESNI),yes) -ifeq (, $(findstring arm, $(SYS))$(findstring aarch64, $(SYS))) # no arm and aarch64 in dumpmachine +ifneq (, $(findstring i386, $(SYS))$(findstring i686, $(SYS))$(findstring x86_64, $(SYS))) # only x86-based CPU supports that NEEDED_CXXFLAGS += -D__AES__ -maes endif endif From 2f57013e02cb105926e6919551b67bfcee751f89 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 1 Dec 2020 05:07:41 +0300 Subject: [PATCH 3935/6300] [qt] update project file Some build systems didn't create required folders for object files, so create them manually with additional call of `mk_obj_dir` target. --- qt/i2pd_qt/i2pd_qt.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index eed4cd7b..004b6273 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -79,7 +79,7 @@ libi2pd.commands = @echo Building i2pd libraries libi2pd.target = $$PWD/../../libi2pd.a libi2pd.depends = i2pd FORCE -i2pd.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd_client && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes $$I2PDMAKE api_client +i2pd.commands = cd $$PWD/../../ && mkdir -p obj/libi2pd obj/libi2pd_client && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) USE_UPNP=yes $$I2PDMAKE mk_obj_dir api_client i2pd.target += $$PWD/../../libi2pdclient.a i2pd.depends = FORCE From ce14ea6fe5b6b849a734d4683b3a97e076a7fb53 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 3 Dec 2020 09:35:43 +0300 Subject: [PATCH 3936/6300] [windows] add file version to installer Signed-off-by: R4SAS --- build/build_mingw.cmd | 2 +- build/win_installer.iss | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/build/build_mingw.cmd b/build/build_mingw.cmd index aaf25843..e8f1378b 100644 --- a/build/build_mingw.cmd +++ b/build/build_mingw.cmd @@ -64,7 +64,7 @@ call :BUILDING_XP echo. REM compile installer -C:\PROGRA~2\INNOSE~1\ISCC.exe /dI2Pd_ver="%tag%" build\win_installer.iss >> build\build.log 2>&1 +C:\PROGRA~2\INNOSE~1\ISCC.exe /dI2Pd_TextVer="%tag%" /dI2Pd_Ver="%tag%.0" build\win_installer.iss >> build\build.log 2>&1 del README.txt i2pd_x32.exe i2pd_x64.exe i2pd_xp.exe >> nul diff --git a/build/win_installer.iss b/build/win_installer.iss index 8a93a2c7..97cd4408 100644 --- a/build/win_installer.iss +++ b/build/win_installer.iss @@ -6,25 +6,35 @@ [Setup] AppName={#I2Pd_AppName} -AppVersion={#I2Pd_ver} +AppVersion={#I2Pd_TextVer} AppPublisher={#I2Pd_Publisher} + DefaultDirName={pf}\I2Pd DefaultGroupName=I2Pd UninstallDisplayIcon={app}\I2Pd.exe OutputDir=. +OutputBaseFilename=setup_{#I2Pd_AppName}_v{#I2Pd_TextVer} + LicenseFile=..\LICENSE -OutputBaseFilename=setup_{#I2Pd_AppName}_v{#I2Pd_ver} SetupIconFile=..\Win32\mask.ico + InternalCompressLevel=ultra64 Compression=lzma/ultra64 SolidCompression=true + ArchitecturesInstallIn64BitMode=x64 -AppVerName={#I2Pd_AppName} ExtraDiskSpaceRequired=15 + AppID={{621A23E0-3CF4-4BD6-97BC-4835EA5206A2} +AppVerName={#I2Pd_AppName} +AppCopyright=Copyright (c) 2013-2020, The PurpleI2P Project AppPublisherURL=http://i2pd.website/ AppSupportURL=https://github.com/PurpleI2P/i2pd/issues AppUpdatesURL=https://github.com/PurpleI2P/i2pd/releases + +VersionInfoProductVersion={#I2Pd_Ver} +VersionInfoVersion={#I2Pd_Ver} + CloseApplications=yes [Files] From 32fc6482ccc3394f19a86b15ee920cbcffb92417 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 3 Dec 2020 17:58:37 -0500 Subject: [PATCH 3937/6300] moved Noise initializations to Crypto.cpp --- libi2pd/Crypto.cpp | 61 +++++++++++++++++++++++ libi2pd/Crypto.h | 4 ++ libi2pd/ECIESX25519AEADRatchetSession.cpp | 24 ++------- libi2pd/ECIESX25519AEADRatchetSession.h | 1 - libi2pd/NTCP2.cpp | 18 +------ libi2pd/RouterContext.cpp | 4 +- libi2pd/TunnelConfig.cpp | 15 +----- libi2pd/TunnelConfig.h | 2 - 8 files changed, 71 insertions(+), 58 deletions(-) diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 523828ce..6055f888 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -1316,6 +1316,8 @@ namespace crypto #endif } +// Noise + void NoiseSymmetricState::MixHash (const uint8_t * buf, size_t len) { SHA256_CTX ctx; @@ -1331,6 +1333,65 @@ namespace crypto // new ck is m_CK[0:31], key is m_CK[32:63] } + void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub) + { + // pub is Bob's public static key + static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars + static const uint8_t hh[32] = + { + 0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1, + 0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54 + }; // hh = SHA256(protocol_name || 0) + memcpy (state.m_CK, protocolName, 32); // ck = protocol_name || 0 + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, hh, 32); + SHA256_Update (&ctx, pub, 32); + SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub) + } + + void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub) + { + // pub is Bob's public static key + static const uint8_t protocolNameHash[] = + { + 0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed, + 0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71 + }; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256") + static const uint8_t hh[32] = + { + 0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5, + 0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e + }; // SHA256 (protocolNameHash) + memcpy (state.m_CK, protocolNameHash, 32); + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, hh, 32); + SHA256_Update (&ctx, pub, 32); + SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub) + } + + void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub) + { + // pub is Bob's public static key + static const uint8_t protocolNameHash[32] = + { + 0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba, + 0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c + }; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes + static const uint8_t hh[32] = + { + 0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32, + 0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c + }; // SHA256 (protocolNameHash) + memcpy (state.m_CK, protocolNameHash, 32); + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, hh, 32); + SHA256_Update (&ctx, pub, 32); + SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub) + } + // init and terminate /* std::vector > m_OpenSSLMutexes; diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index da7a4bf4..c63cc45e 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -318,6 +318,10 @@ namespace crypto void MixKey (const uint8_t * sharedSecret); }; + void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_N (tunnels, router) + void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_XK (NTCP2) + void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub); // Noise_IK (ratchets) + // init and terminate void InitCrypto (bool precomputation, bool aesni, bool avx, bool force); void TerminateCrypto (); diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 00efee48..b3b30a1b 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -153,29 +153,12 @@ namespace garlic GarlicRoutingSession (owner, attachLeaseSet) { RAND_bytes (m_PaddingSizes, 32); m_NextPaddingSize = 0; - ResetKeys (); } ECIESX25519AEADRatchetSession::~ECIESX25519AEADRatchetSession () { } - void ECIESX25519AEADRatchetSession::ResetKeys () - { - static const uint8_t protocolNameHash[32] = - { - 0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba, - 0xac, 0xbe, 0x00, 0x54, 0xad, 0x17, 0x8f, 0x80, 0x79, 0xa6, 0x46, 0x82, 0x7e, 0x6e, 0xe4, 0x0c - }; // SHA256("Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256"), 40 bytes - static const uint8_t hh[32] = - { - 0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32, - 0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c - }; // SHA256 (protocolNameHash) - memcpy (m_CK, protocolNameHash, 32); - memcpy (m_H, hh, 32); - } - void ECIESX25519AEADRatchetSession::CreateNonce (uint64_t seqn, uint8_t * nonce) { memset (nonce, 0, 4); @@ -236,8 +219,8 @@ namespace garlic if (!GetOwner ()) return false; // we are Bob // KDF1 - MixHash (GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD), 32); // h = SHA256(h || bpk) - + i2p::crypto::InitNoiseIKState (*this, GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk + if (!i2p::crypto::GetElligator ()->Decode (buf, m_Aepk)) { LogPrint (eLogError, "Garlic: Can't decode elligator"); @@ -448,7 +431,6 @@ namespace garlic bool ECIESX25519AEADRatchetSession::NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen, bool isStatic) { - ResetKeys (); // we are Alice, bpk is m_RemoteStaticKey size_t offset = 0; if (!GenerateEphemeralKeysAndEncode (out + offset)) @@ -459,7 +441,7 @@ namespace garlic offset += 32; // KDF1 - MixHash (m_RemoteStaticKey, 32); // h = SHA256(h || bpk) + i2p::crypto::InitNoiseIKState (*this, m_RemoteStaticKey); // bpk MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 755531a3..e71d9782 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -178,7 +178,6 @@ namespace garlic private: - void ResetKeys (); void CreateNonce (uint64_t seqn, uint8_t * nonce); bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes std::shared_ptr CreateNewSessionTagset (); diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index bb54437e..1706399d 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -41,23 +41,7 @@ namespace transport void NTCP2Establisher::KeyDerivationFunction1 (const uint8_t * pub, i2p::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub) { - static const uint8_t protocolNameHash[] = - { - 0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed, - 0xf2, 0x28, 0x98, 0x17, 0x71, 0x21, 0x8c, 0x1f, 0x62, 0x4e, 0x20, 0x6f, 0x28, 0xd3, 0x2f, 0x71 - }; // SHA256 ("Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256") - static const uint8_t hh[32] = - { - 0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5, - 0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e - }; // SHA256 (protocolNameHash) - memcpy (m_CK, protocolNameHash, 32); - // h = SHA256(hh || rs) - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, hh, 32); - SHA256_Update (&ctx, rs, 32); - SHA256_Final (m_H, &ctx); + i2p::crypto::InitNoiseXKState (*this, rs); // h = SHA256(h || epub) MixHash (epub, 32); // x25519 between pub and priv diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index cdab624c..3c066532 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -19,7 +19,6 @@ #include "version.h" #include "Log.h" #include "Family.h" -#include "TunnelConfig.h" #include "RouterContext.h" namespace i2p @@ -45,8 +44,7 @@ namespace i2p if (IsECIES ()) { auto initState = new i2p::crypto::NoiseSymmetricState (); - i2p::tunnel::InitBuildRequestRecordNoiseState (*initState); - initState->MixHash (GetIdentity ()->GetEncryptionPublicKey (), 32); // h = SHA256(h || hepk) + i2p::crypto::InitNoiseNState (*initState, GetIdentity ()->GetEncryptionPublicKey ()); m_InitialNoiseState.reset (initState); } } diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index a1b234a1..d43483c0 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -127,10 +127,9 @@ namespace tunnel void TunnelHopConfig::EncryptECIES (std::shared_ptr& encryptor, const uint8_t * plainText, uint8_t * encrypted, BN_CTX * ctx) { - InitBuildRequestRecordNoiseState (*this); uint8_t hepk[32]; encryptor->Encrypt (nullptr, hepk, nullptr, false); - MixHash (hepk, 32); // h = SHA256(h || hepk) + i2p::crypto::InitNoiseNState (*this, hepk); auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); memcpy (encrypted, ephemeralKeys->GetPublicKey (), 32); MixHash (encrypted, 32); // h = SHA256(h || sepk) @@ -148,17 +147,5 @@ namespace tunnel } MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) } - - void InitBuildRequestRecordNoiseState (i2p::crypto::NoiseSymmetricState& state) - { - static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars - static const uint8_t hh[32] = - { - 0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1, - 0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54 - }; // SHA256 (protocol_name || 0) - memcpy (state.m_CK, protocolName, 32); // ck = h = protocol_name || 0 - memcpy (state.m_H, hh, 32); // h = SHA256(h) - } } } \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 9aed0d25..45693970 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -44,8 +44,6 @@ namespace tunnel void EncryptECIES (std::shared_ptr& encryptor, const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx); }; - - void InitBuildRequestRecordNoiseState (i2p::crypto::NoiseSymmetricState& state); class TunnelConfig { From abdf92c084e825008f5539ae7fcee9435fa7ee47 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 3 Dec 2020 19:43:43 -0500 Subject: [PATCH 3938/6300] encrypt message for ECIES router --- libi2pd/Crypto.cpp | 38 ++++++++++------------- libi2pd/ECIESX25519AEADRatchetSession.cpp | 36 +++++++++++++++++++-- libi2pd/ECIESX25519AEADRatchetSession.h | 8 +++-- libi2pd/Garlic.cpp | 2 +- 4 files changed, 55 insertions(+), 29 deletions(-) diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 6055f888..68712fc7 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -1333,26 +1333,31 @@ namespace crypto // new ck is m_CK[0:31], key is m_CK[32:63] } + static void InitNoiseState (NoiseSymmetricState& state, const uint8_t * ck, + const uint8_t * hh, const uint8_t * pub) + { + // pub is Bob's public static key, hh = SHA256(h) + memcpy (state.m_CK, ck, 32); + SHA256_CTX ctx; + SHA256_Init (&ctx); + SHA256_Update (&ctx, hh, 32); + SHA256_Update (&ctx, pub, 32); + SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub) + } + void InitNoiseNState (NoiseSymmetricState& state, const uint8_t * pub) { - // pub is Bob's public static key static const char protocolName[] = "Noise_N_25519_ChaChaPoly_SHA256"; // 31 chars static const uint8_t hh[32] = { 0x69, 0x4d, 0x52, 0x44, 0x5a, 0x27, 0xd9, 0xad, 0xfa, 0xd2, 0x9c, 0x76, 0x32, 0x39, 0x5d, 0xc1, 0xe4, 0x35, 0x4c, 0x69, 0xb4, 0xf9, 0x2e, 0xac, 0x8a, 0x1e, 0xe4, 0x6a, 0x9e, 0xd2, 0x15, 0x54 }; // hh = SHA256(protocol_name || 0) - memcpy (state.m_CK, protocolName, 32); // ck = protocol_name || 0 - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, hh, 32); - SHA256_Update (&ctx, pub, 32); - SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub) + InitNoiseState (state, (const uint8_t *)protocolName, hh, pub); // ck = protocol_name || 0 } - + void InitNoiseXKState (NoiseSymmetricState& state, const uint8_t * pub) { - // pub is Bob's public static key static const uint8_t protocolNameHash[] = { 0x72, 0xe8, 0x42, 0xc5, 0x45, 0xe1, 0x80, 0x80, 0xd3, 0x9c, 0x44, 0x93, 0xbb, 0x91, 0xd7, 0xed, @@ -1363,17 +1368,11 @@ namespace crypto 0x49, 0xff, 0x48, 0x3f, 0xc4, 0x04, 0xb9, 0xb2, 0x6b, 0x11, 0x94, 0x36, 0x72, 0xff, 0x05, 0xb5, 0x61, 0x27, 0x03, 0x31, 0xba, 0x89, 0xb8, 0xfc, 0x33, 0x15, 0x93, 0x87, 0x57, 0xdd, 0x3d, 0x1e }; // SHA256 (protocolNameHash) - memcpy (state.m_CK, protocolNameHash, 32); - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, hh, 32); - SHA256_Update (&ctx, pub, 32); - SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub) + InitNoiseState (state, protocolNameHash, hh, pub); } void InitNoiseIKState (NoiseSymmetricState& state, const uint8_t * pub) { - // pub is Bob's public static key static const uint8_t protocolNameHash[32] = { 0x4c, 0xaf, 0x11, 0xef, 0x2c, 0x8e, 0x36, 0x56, 0x4c, 0x53, 0xe8, 0x88, 0x85, 0x06, 0x4d, 0xba, @@ -1384,12 +1383,7 @@ namespace crypto 0x9c, 0xcf, 0x85, 0x2c, 0xc9, 0x3b, 0xb9, 0x50, 0x44, 0x41, 0xe9, 0x50, 0xe0, 0x1d, 0x52, 0x32, 0x2e, 0x0d, 0x47, 0xad, 0xd1, 0xe9, 0xa5, 0x55, 0xf7, 0x55, 0xb5, 0x69, 0xae, 0x18, 0x3b, 0x5c }; // SHA256 (protocolNameHash) - memcpy (state.m_CK, protocolNameHash, 32); - SHA256_CTX ctx; - SHA256_Init (&ctx); - SHA256_Update (&ctx, hh, 32); - SHA256_Update (&ctx, pub, 32); - SHA256_Final (state.m_H, &ctx); // h = MixHash(pub) = SHA256(hh || pub) + InitNoiseState (state, protocolNameHash, hh, pub); } // init and terminate diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index b3b30a1b..042a318c 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -490,6 +490,31 @@ namespace garlic return true; } + bool ECIESX25519AEADRatchetSession::NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) + { + // we are Alice, router's bpk is m_RemoteStaticKey + i2p::crypto::InitNoiseNState (*this, m_RemoteStaticKey); + size_t offset = 0; + m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + memcpy (out + offset, m_EphemeralKeys->GetPublicKey (), 32); + MixHash (out + offset, 32); // h = SHA256(h || aepk) + offset += 32; + uint8_t sharedSecret[32]; + m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) + MixKey (sharedSecret); + uint8_t nonce[12]; + memset (nonce, 0, 12); + // encrypt payload + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); + return false; + } + + m_State = eSessionStateNewSessionSent; + return true; + } + bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob @@ -549,7 +574,7 @@ namespace garlic return true; } - + bool ECIESX25519AEADRatchetSession::NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob and sent NSR already @@ -781,6 +806,11 @@ namespace garlic return nullptr; len += 96; break; + case eSessionStateForRouter: + if (!NewOutgoingMessageForRouter (payload.data (), payload.size (), buf, m->maxLen)) + return nullptr; + len += 48; + break; default: return nullptr; } @@ -791,9 +821,9 @@ namespace garlic return m; } - std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg) + std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter) { - m_State = eSessionStateOneTime; + m_State = isForRouter ? eSessionStateForRouter : eSessionStateOneTime; return WrapSingleMessage (msg); } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index e71d9782..1788eeb8 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -140,7 +140,8 @@ namespace garlic eSessionStateNewSessionSent, eSessionStateNewSessionReplySent, eSessionStateEstablished, - eSessionStateOneTime + eSessionStateOneTime, + eSessionStateForRouter }; struct DHRatchet @@ -158,7 +159,7 @@ namespace garlic bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg); + std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter = false); const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } @@ -192,7 +193,8 @@ namespace garlic bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - + bool NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); + std::vector CreatePayload (std::shared_ptr msg, bool first); size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len); size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 2fe97156..1d228811 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -716,7 +716,7 @@ namespace garlic { auto session = std::make_shared(this, false); session->SetRemoteStaticKey (router->GetIdentity ()->GetEncryptionPublicKey ()); - return session->WrapOneTimeMessage (msg); + return session->WrapOneTimeMessage (msg, true); } else { From e2fcab34b71c182f94bed304588897fd46c0ab5f Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 3 Dec 2020 22:01:58 -0500 Subject: [PATCH 3939/6300] deccrypt and handle garlic message for ECIES router --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 24 ++++++++++++++++++++++- libi2pd/ECIESX25519AEADRatchetSession.h | 1 + libi2pd/RouterContext.cpp | 17 +++++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 042a318c..ecc7412f 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -503,7 +503,7 @@ namespace garlic m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) MixKey (sharedSecret); uint8_t nonce[12]; - memset (nonce, 0, 12); + CreateNonce (0, nonce); // encrypt payload if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt { @@ -770,6 +770,28 @@ namespace garlic return true; } + bool ECIESX25519AEADRatchetSession::HandleNextMessageForRouter (const uint8_t * buf, size_t len) + { + if (!GetOwner ()) return false; + // we are Bob + i2p::crypto::InitNoiseNState (*this, GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk + MixHash (buf, 32); + uint8_t sharedSecret[32]; + GetOwner ()->Decrypt (buf, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519(bsk, aepk) + MixKey (sharedSecret); + buf += 32; len -= 32; + uint8_t nonce[12]; + CreateNonce (0, nonce); + std::vector payload (len - 16); + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_H, 32, m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt + { + LogPrint (eLogWarning, "Garlic: Payload for router AEAD verification failed"); + return false; + } + HandlePayload (payload.data (), len - 16, nullptr, 0); + return true; + } + std::shared_ptr ECIESX25519AEADRatchetSession::WrapSingleMessage (std::shared_ptr msg) { auto payload = CreatePayload (msg, m_State != eSessionStateEstablished); diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 1788eeb8..59be94c1 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -158,6 +158,7 @@ namespace garlic ~ECIESX25519AEADRatchetSession (); bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); + bool HandleNextMessageForRouter (const uint8_t * buf, size_t len); std::shared_ptr WrapSingleMessage (std::shared_ptr msg); std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter = false); diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 3c066532..5cfbd943 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -19,6 +19,7 @@ #include "version.h" #include "Log.h" #include "Family.h" +#include "ECIESX25519AEADRatchetSession.h" #include "RouterContext.h" namespace i2p @@ -672,7 +673,21 @@ namespace i2p void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) { std::unique_lock l(m_GarlicMutex); - i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); + if (IsECIES ()) + { + uint8_t * buf = msg->GetPayload (); + uint32_t len = bufbe32toh (buf); + if (len > msg->GetLength ()) + { + LogPrint (eLogWarning, "Router: garlic message length ", len, " exceeds I2NP message length ", msg->GetLength ()); + return; + } + buf += 4; + auto session = std::make_shared(this, false); + session->HandleNextMessageForRouter (buf, len); + } + else + i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); } void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr msg) From 36473e388903b38b64364a30c72642096bfab386 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 4 Dec 2020 18:36:49 +0300 Subject: [PATCH 3940/6300] add naming to threads Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 2 ++ daemon/I2PControl.cpp | 2 ++ daemon/UPnP.cpp | 2 ++ libi2pd/Log.cpp | 2 ++ libi2pd/NTCP2.h | 1 - libi2pd/NetDb.cpp | 3 +++ libi2pd/SSU.cpp | 13 ++++++++++--- libi2pd/Timestamp.cpp | 2 ++ libi2pd/Transports.cpp | 5 +++++ libi2pd/Tunnel.cpp | 2 ++ libi2pd/util.cpp | 1 + libi2pd_client/AddressBook.cpp | 1 + libi2pd_client/I2PTunnel.cpp | 10 ++++++---- 13 files changed, 38 insertions(+), 8 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 9f420cf0..73849e4d 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -1312,6 +1313,7 @@ namespace http { void HTTPServer::Run () { + pthread_setname_np(pthread_self(), "Webconsole"); while (m_IsRunning) { try diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index e8c6e031..1fde9109 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "Crypto.h" #include "FS.h" @@ -131,6 +132,7 @@ namespace client void I2PControlService::Run () { + pthread_setname_np(pthread_self(), "I2PC"); while (m_IsRunning) { try { diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index 92a41011..0076ddd7 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -1,6 +1,7 @@ #ifdef USE_UPNP #include #include +#include #include #include @@ -60,6 +61,7 @@ namespace transport void UPnP::Run () { + pthread_setname_np(pthread_self(), "UPnP"); while (m_IsRunning) { try diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index a0014841..d7386642 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -7,6 +7,7 @@ */ #include "Log.h" +#include //for std::transform #include @@ -179,6 +180,7 @@ namespace log { void Log::Run () { + pthread_setname_np(pthread_self(), "Logging"); Reopen (); while (m_IsRunning) { diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index 351f17b5..5c9ecac9 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -11,7 +11,6 @@ #include #include -#include #include #include #include diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index e280e31b..66dac010 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "I2PEndian.h" #include "Base.h" @@ -88,6 +89,8 @@ namespace data void NetDb::Run () { + pthread_setname_np(pthread_self(), "NetDB"); + uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0; while (m_IsRunning) { diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index d29f5cd7..a75306bb 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -7,6 +7,7 @@ */ #include +#include #include "Log.h" #include "Timestamp.h" #include "RouterContext.h" @@ -23,7 +24,7 @@ namespace transport { SSUServer::SSUServer (const boost::asio::ip::address & addr, int port): - m_OnlyV6(true), m_IsRunning(false), m_Thread (nullptr), + m_OnlyV6(true), m_IsRunning(false), m_Thread (nullptr), m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), m_EndpointV6 (addr, port), m_Socket (m_ReceiversService, m_Endpoint), @@ -36,7 +37,7 @@ namespace transport SSUServer::SSUServer (int port): m_OnlyV6(false), m_IsRunning(false), m_Thread (nullptr), - m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), + m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6), @@ -100,7 +101,7 @@ namespace transport if (context.SupportsV6 ()) { m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); - if (!m_Thread) + if (!m_Thread) m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); ScheduleTerminationV6 (); @@ -142,6 +143,8 @@ namespace transport void SSUServer::Run () { + pthread_setname_np(pthread_self(), "SSU"); + while (m_IsRunning) { try @@ -157,6 +160,8 @@ namespace transport void SSUServer::RunReceivers () { + pthread_setname_np(pthread_self(), "SSUv4"); + while (m_IsRunning) { try @@ -179,6 +184,8 @@ namespace transport void SSUServer::RunReceiversV6 () { + pthread_setname_np(pthread_self(), "SSUv6"); + while (m_IsRunning) { try diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index 0490350e..db5c3946 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "Config.h" #include "Log.h" #include "I2PEndian.h" @@ -148,6 +149,7 @@ namespace util void NTPTimeSync::Run () { + pthread_setname_np(pthread_self(), "Timesync"); while (m_IsRunning) { try diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index c6e90ad2..585ef8e0 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -6,6 +6,7 @@ * See full license text in LICENSE file at top of project tree */ +#include #include "Log.h" #include "Crypto.h" #include "RouterContext.h" @@ -59,6 +60,8 @@ namespace transport template void EphemeralKeysSupplier::Run () { + pthread_setname_np(pthread_self(), "Ephemerals"); + while (m_IsRunning) { int num, total = 0; @@ -272,6 +275,8 @@ namespace transport void Transports::Run () { + pthread_setname_np(pthread_self(), "Transports"); + while (m_IsRunning && m_Service) { try diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index ad918787..a5f20963 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -10,6 +10,7 @@ #include "I2PEndian.h" #include #include +#include #include #include #include "Crypto.h" @@ -472,6 +473,7 @@ namespace tunnel void Tunnels::Run () { + pthread_setname_np(pthread_self(), "Tunnels"); std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready uint64_t lastTs = 0, lastPoolsTs = 0; diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 24814ad3..e98f939f 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -94,6 +94,7 @@ namespace util void RunnableService::Run () { + pthread_setname_np(pthread_self(), m_Name.c_str()); while (m_IsRunning) { try diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index d695b8eb..6ecbddaf 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -748,6 +748,7 @@ namespace client void AddressBookSubscription::CheckUpdates () { + pthread_setname_np(pthread_self(), "Addressbook"); bool result = MakeRequest (); m_Book.DownloadComplete (result, m_Ident, m_Etag, m_LastModified); } diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 081294c6..18b7c5a0 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -7,6 +7,7 @@ */ #include +#include #include "Base.h" #include "Log.h" #include "Destination.h" @@ -862,7 +863,7 @@ namespace client std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); dgram->SetRawReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2PRaw, this, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); } void I2PUDPClientTunnel::Start() { @@ -891,11 +892,11 @@ namespace client } auto remotePort = m_RecvEndpoint.port(); if (!m_LastPort || m_LastPort != remotePort) - { + { auto itr = m_Sessions.find(remotePort); - if (itr != m_Sessions.end()) + if (itr != m_Sessions.end()) m_LastSession = itr->second; - else + else { m_LastSession = std::make_shared(boost::asio::ip::udp::endpoint(m_RecvEndpoint), 0); m_Sessions.emplace (remotePort, m_LastSession); @@ -941,6 +942,7 @@ namespace client void I2PUDPClientTunnel::TryResolving() { LogPrint(eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); + pthread_setname_np(pthread_self(), "UDP Resolver"); std::shared_ptr addr; while(!(addr = context.GetAddressBook().GetAddress(m_RemoteDest)) && !m_cancel_resolve) From a843165cb49661d0d134eab013d09b781ab99dd5 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 4 Dec 2020 19:15:06 -0500 Subject: [PATCH 3941/6300] try ratchets tag first --- libi2pd/Garlic.cpp | 73 +++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 1d228811..9c008bc0 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -485,42 +485,43 @@ namespace garlic } auto mod = length & 0x0f; // %16 buf += 4; // length - auto it = !mod ? m_Tags.find (SessionTag(buf)) : m_Tags.end (); // AES block is multiple of 16 - // AES tag might be used even if encryption type is not ElGamal/AES - if (it != m_Tags.end ()) - { - // tag found. Use AES - auto decryption = it->second; - m_Tags.erase (it); // tag might be used only once - if (length >= 32) - { - uint8_t iv[32]; // IV is first 16 bytes - SHA256(buf, 32, iv); - decryption->SetIV (iv); - decryption->Decrypt (buf + 32, length - 32, buf + 32); - HandleAESBlock (buf + 32, length - 32, decryption, msg->from); - } - else - LogPrint (eLogWarning, "Garlic: message length ", length, " is less than 32 bytes"); - } - else - { - bool found = false; - if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) - { - // try ECIESx25519 tag - uint64_t tag; - memcpy (&tag, buf, 8); - auto it1 = m_ECIESx25519Tags.find (tag); - if (it1 != m_ECIESx25519Tags.end ()) - { - found = true; - if (!it1->second.tagset->HandleNextMessage (buf, length, it1->second.index)) - LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); - m_ECIESx25519Tags.erase (it1); - } - } + bool found = false; + if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) + { + // try ECIESx25519 tag + uint64_t tag; + memcpy (&tag, buf, 8); + auto it1 = m_ECIESx25519Tags.find (tag); + if (it1 != m_ECIESx25519Tags.end ()) + { + found = true; + if (!it1->second.tagset->HandleNextMessage (buf, length, it1->second.index)) + LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); + m_ECIESx25519Tags.erase (it1); + } + } + if (!found) + { + auto it = !mod ? m_Tags.find (SessionTag(buf)) : m_Tags.end (); // AES block is multiple of 16 + // AES tag might be used even if encryption type is not ElGamal/AES + if (it != m_Tags.end ()) // try AES tag + { + // tag found. Use AES + auto decryption = it->second; + m_Tags.erase (it); // tag might be used only once + if (length >= 32) + { + uint8_t iv[32]; // IV is first 16 bytes + SHA256(buf, 32, iv); + decryption->SetIV (iv); + decryption->Decrypt (buf + 32, length - 32, buf + 32); + HandleAESBlock (buf + 32, length - 32, decryption, msg->from); + found = true; + } + else + LogPrint (eLogWarning, "Garlic: message length ", length, " is less than 32 bytes"); + } if (!found) // assume new session { // AES tag not found. Handle depending on encryption type @@ -545,7 +546,7 @@ namespace garlic } else LogPrint (eLogError, "Garlic: Failed to decrypt message"); - } + } } } From aace200899a26c840f320794d30e8327fee17ad4 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 5 Dec 2020 08:26:21 -0500 Subject: [PATCH 3942/6300] don't create paired zero hops tunnel --- libi2pd/TunnelPool.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 87413c3f..94462c93 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -235,7 +235,7 @@ namespace tunnel for (const auto& it : m_InboundTunnels) if (it->IsEstablished ()) num++; } - if (!num && !m_OutboundTunnels.empty ()) + if (!num && !m_OutboundTunnels.empty () && m_NumOutboundHops > 0) { for (auto it: m_OutboundTunnels) { @@ -559,7 +559,7 @@ namespace tunnel { config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); } - if(m_NumOutboundHops == 0 || config) + if (!m_NumOutboundHops || config) { auto newTunnel = tunnels.CreateOutboundTunnel (config); newTunnel->SetTunnelPool (shared_from_this ()); @@ -574,8 +574,12 @@ namespace tunnel void TunnelPool::CreatePairedInboundTunnel (std::shared_ptr outboundTunnel) { LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); - auto tunnel = tunnels.CreateInboundTunnel (std::make_shared(outboundTunnel->GetInvertedPeers ()), outboundTunnel); + auto tunnel = tunnels.CreateInboundTunnel ( + m_NumOutboundHops > 0 ? std::make_shared(outboundTunnel->GetInvertedPeers ()) : nullptr, + outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); + if (tunnel->IsEstablished ()) // zero hops + TunnelCreated (tunnel); } void TunnelPool::SetCustomPeerSelector(ITunnelPeerSelector * selector) From 3100d4f90224094608215bba049f52e81e4e75da Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 7 Dec 2020 06:22:16 +0300 Subject: [PATCH 3943/6300] move thread naming to util Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 4 ++-- daemon/I2PControl.cpp | 4 ++-- daemon/UPnP.cpp | 4 ++-- libi2pd/Log.cpp | 5 +++-- libi2pd/NetDb.cpp | 12 +++++------ libi2pd/SSU.cpp | 8 ++++---- libi2pd/Timestamp.cpp | 5 +++-- libi2pd/Transports.cpp | 6 +++--- libi2pd/Tunnel.cpp | 4 ++-- libi2pd/util.cpp | 40 +++++++++++++++++++++++++++--------- libi2pd/util.h | 6 ++++-- libi2pd_client/I2PTunnel.cpp | 4 ++-- 12 files changed, 63 insertions(+), 39 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 73849e4d..25b6ab19 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -1313,7 +1312,8 @@ namespace http { void HTTPServer::Run () { - pthread_setname_np(pthread_self(), "Webconsole"); + i2p::util::SetThreadName("Webconsole"); + while (m_IsRunning) { try diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index 1fde9109..3f0033e5 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include "Crypto.h" #include "FS.h" @@ -132,7 +131,8 @@ namespace client void I2PControlService::Run () { - pthread_setname_np(pthread_self(), "I2PC"); + i2p::util::SetThreadName("I2PC"); + while (m_IsRunning) { try { diff --git a/daemon/UPnP.cpp b/daemon/UPnP.cpp index 0076ddd7..6ea33c46 100644 --- a/daemon/UPnP.cpp +++ b/daemon/UPnP.cpp @@ -1,7 +1,6 @@ #ifdef USE_UPNP #include #include -#include #include #include @@ -61,7 +60,8 @@ namespace transport void UPnP::Run () { - pthread_setname_np(pthread_self(), "UPnP"); + i2p::util::SetThreadName("UPnP"); + while (m_IsRunning) { try diff --git a/libi2pd/Log.cpp b/libi2pd/Log.cpp index d7386642..2b555663 100644 --- a/libi2pd/Log.cpp +++ b/libi2pd/Log.cpp @@ -7,7 +7,7 @@ */ #include "Log.h" -#include +#include "util.h" //for std::transform #include @@ -180,7 +180,8 @@ namespace log { void Log::Run () { - pthread_setname_np(pthread_self(), "Logging"); + i2p::util::SetThreadName("Logging"); + Reopen (); while (m_IsRunning) { diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 66dac010..20b54376 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include "I2PEndian.h" #include "Base.h" @@ -27,6 +26,7 @@ #include "ECIESX25519AEADRatchetSession.h" #include "Config.h" #include "NetDb.hpp" +#include "util.h" using namespace i2p::transport; @@ -89,7 +89,7 @@ namespace data void NetDb::Run () { - pthread_setname_np(pthread_self(), "NetDB"); + i2p::util::SetThreadName("NetDB"); uint32_t lastSave = 0, lastPublish = 0, lastExploratory = 0, lastManageRequest = 0, lastDestinationCleanup = 0; while (m_IsRunning) @@ -116,7 +116,7 @@ namespace data break; case eI2NPDeliveryStatus: HandleDeliveryStatusMsg (msg); - break; + break; case eI2NPDummyMsg: // plain RouterInfo from NTCP2 with flags for now HandleNTCP2RouterInfoMsg (msg); @@ -158,12 +158,12 @@ namespace data if (!m_HiddenMode && i2p::transport::transports.IsOnline ()) { bool publish = false; - if (m_PublishReplyToken) - { + if (m_PublishReplyToken) + { if (ts - lastPublish >= NETDB_PUBLISH_CONFIRMATION_TIMEOUT) publish = true; } else if (i2p::context.GetLastUpdateTime () > lastPublish || - ts - lastPublish >= NETDB_PUBLISH_INTERVAL) publish = true; + ts - lastPublish >= NETDB_PUBLISH_INTERVAL) publish = true; if (publish) // update timestamp and publish { i2p::context.UpdateTimestamp (ts); diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index a75306bb..3b6280dd 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -7,12 +7,12 @@ */ #include -#include #include "Log.h" #include "Timestamp.h" #include "RouterContext.h" #include "NetDb.hpp" #include "SSU.h" +#include "util.h" #ifdef _WIN32 #include @@ -143,7 +143,7 @@ namespace transport void SSUServer::Run () { - pthread_setname_np(pthread_self(), "SSU"); + i2p::util::SetThreadName("SSU"); while (m_IsRunning) { @@ -160,7 +160,7 @@ namespace transport void SSUServer::RunReceivers () { - pthread_setname_np(pthread_self(), "SSUv4"); + i2p::util::SetThreadName("SSUv4"); while (m_IsRunning) { @@ -184,7 +184,7 @@ namespace transport void SSUServer::RunReceiversV6 () { - pthread_setname_np(pthread_self(), "SSUv6"); + i2p::util::SetThreadName("SSUv6"); while (m_IsRunning) { diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index db5c3946..3cd336ed 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -14,11 +14,11 @@ #include #include #include -#include #include "Config.h" #include "Log.h" #include "I2PEndian.h" #include "Timestamp.h" +#include "util.h" #ifdef _WIN32 #ifndef _WIN64 @@ -149,7 +149,8 @@ namespace util void NTPTimeSync::Run () { - pthread_setname_np(pthread_self(), "Timesync"); + i2p::util::SetThreadName("Timesync"); + while (m_IsRunning) { try diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 585ef8e0..5c2fcb6d 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -6,7 +6,6 @@ * See full license text in LICENSE file at top of project tree */ -#include #include "Log.h" #include "Crypto.h" #include "RouterContext.h" @@ -15,6 +14,7 @@ #include "Transports.h" #include "Config.h" #include "HTTP.h" +#include "util.h" using namespace i2p::data; @@ -60,7 +60,7 @@ namespace transport template void EphemeralKeysSupplier::Run () { - pthread_setname_np(pthread_self(), "Ephemerals"); + i2p::util::SetThreadName("Ephemerals"); while (m_IsRunning) { @@ -275,7 +275,7 @@ namespace transport void Transports::Run () { - pthread_setname_np(pthread_self(), "Transports"); + i2p::util::SetThreadName("Transports"); while (m_IsRunning && m_Service) { diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index a5f20963..42eeeb5d 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -10,7 +10,6 @@ #include "I2PEndian.h" #include #include -#include #include #include #include "Crypto.h" @@ -23,6 +22,7 @@ #include "Config.h" #include "Tunnel.h" #include "TunnelPool.h" +#include "util.h" namespace i2p { @@ -473,7 +473,7 @@ namespace tunnel void Tunnels::Run () { - pthread_setname_np(pthread_self(), "Tunnels"); + i2p::util::SetThreadName("Tunnels"); std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready uint64_t lastTs = 0, lastPoolsTs = 0; diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index e98f939f..2e8e67ba 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -13,6 +13,15 @@ #include "util.h" #include "Log.h" +#if not defined (__FreeBSD__) +#include +#endif + +#if defined(__OpenBSD__) || defined(__FreeBSD__) +#include +#endif + + #ifdef _WIN32 #include #include @@ -32,7 +41,7 @@ // inet_pton exists Windows since Vista, but XP doesn't have that function! // This function was written by Petar Korponai?. See http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found -int inet_pton_xp(int af, const char *src, void *dst) +int inet_pton_xp (int af, const char *src, void *dst) { struct sockaddr_storage ss; int size = sizeof (ss); @@ -94,7 +103,8 @@ namespace util void RunnableService::Run () { - pthread_setname_np(pthread_self(), m_Name.c_str()); + SetThreadName(m_Name.c_str()); + while (m_IsRunning) { try @@ -108,10 +118,20 @@ namespace util } } + void SetThreadName (const char *name) { +#if defined (__APPLE__) + pthread_setname_np(name); +#elif defined(__FreeBSD__) + pthread_set_name_np(pthread_self(), name) +#else + pthread_setname_np(pthread_self(), name); +#endif + } + namespace net { #ifdef _WIN32 - bool IsWindowsXPorLater() + bool IsWindowsXPorLater () { static bool isRequested = false; static bool isXP = false; @@ -130,7 +150,7 @@ namespace net return isXP; } - int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback) + int GetMTUWindowsIpv4 (sockaddr_in inputAddress, int fallback) { ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; @@ -184,7 +204,7 @@ namespace net return fallback; } - int GetMTUWindowsIpv6(sockaddr_in6 inputAddress, int fallback) + int GetMTUWindowsIpv6 (sockaddr_in6 inputAddress, int fallback) { ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; @@ -249,7 +269,7 @@ namespace net return fallback; } - int GetMTUWindows(const boost::asio::ip::address& localAddress, int fallback) + int GetMTUWindows (const boost::asio::ip::address& localAddress, int fallback) { #ifdef UNICODE string localAddress_temporary = localAddress.to_string(); @@ -281,7 +301,7 @@ namespace net } } #else // assume unix - int GetMTUUnix(const boost::asio::ip::address& localAddress, int fallback) + int GetMTUUnix (const boost::asio::ip::address& localAddress, int fallback) { ifaddrs* ifaddr, *ifa = nullptr; if(getifaddrs(&ifaddr) == -1) @@ -336,7 +356,7 @@ namespace net } #endif // _WIN32 - int GetMTU(const boost::asio::ip::address& localAddress) + int GetMTU (const boost::asio::ip::address& localAddress) { int fallback = localAddress.is_v6 () ? 1280 : 620; // fallback MTU @@ -348,7 +368,7 @@ namespace net return fallback; } - const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6) + const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6) { #ifdef _WIN32 LogPrint(eLogError, "NetIface: cannot get address by interface name, not implemented on WIN32"); @@ -396,7 +416,7 @@ namespace net #endif } - bool IsInReservedRange(const boost::asio::ip::address& host) { + bool IsInReservedRange (const boost::asio::ip::address& host) { // https://en.wikipedia.org/wiki/Reserved_IP_addresses if(host.is_v4()) { diff --git a/libi2pd/util.h b/libi2pd/util.h index 56ce1e08..f6222b9f 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -168,11 +168,13 @@ namespace util boost::asio::io_service::work m_Work; }; + void SetThreadName (const char *name); + namespace net { int GetMTU (const boost::asio::ip::address& localAddress); - const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6=false); - bool IsInReservedRange(const boost::asio::ip::address& host); + const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6=false); + bool IsInReservedRange (const boost::asio::ip::address& host); } } } diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 18b7c5a0..61c42b24 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -7,12 +7,12 @@ */ #include -#include #include "Base.h" #include "Log.h" #include "Destination.h" #include "ClientContext.h" #include "I2PTunnel.h" +#include "util.h" namespace i2p { @@ -941,8 +941,8 @@ namespace client } void I2PUDPClientTunnel::TryResolving() { + i2p::util::SetThreadName("UDP Resolver"); LogPrint(eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); - pthread_setname_np(pthread_self(), "UDP Resolver"); std::shared_ptr addr; while(!(addr = context.GetAddressBook().GetAddress(m_RemoteDest)) && !m_cancel_resolve) From 9a2c6a76197ebf6531acb6b4d5cf7d7d955b3695 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 7 Dec 2020 06:31:46 +0300 Subject: [PATCH 3944/6300] move thread naming to util Signed-off-by: R4SAS --- libi2pd_client/AddressBook.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index 6ecbddaf..a69bd660 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -748,7 +748,8 @@ namespace client void AddressBookSubscription::CheckUpdates () { - pthread_setname_np(pthread_self(), "Addressbook"); + i2p::util::SetThreadName("Addressbook"); + bool result = MakeRequest (); m_Book.DownloadComplete (result, m_Ident, m_Etag, m_LastModified); } From ac67cd7f9adf9a8ac36faaab876883fc7526ceb8 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 7 Dec 2020 08:36:06 +0300 Subject: [PATCH 3945/6300] add FreeBSD builder for GHA (#1595) --- .github/workflows/build-freebsd.yml | 20 ++++++++++++++++++++ libi2pd/util.cpp | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-freebsd.yml diff --git a/.github/workflows/build-freebsd.yml b/.github/workflows/build-freebsd.yml new file mode 100644 index 00000000..dbc4028c --- /dev/null +++ b/.github/workflows/build-freebsd.yml @@ -0,0 +1,20 @@ +name: Build on FreeBSD + +on: [push, pull_request] + +jobs: + build: + runs-on: macos-latest + name: with UPnP + steps: + - uses: actions/checkout@v2 + - name: Test in FreeBSD + id: test + uses: vmactions/freebsd-vm@v0.0.9 + with: + usesh: true + prepare: pkg install -y devel/cmake devel/gmake devel/boost-libs security/openssl net/miniupnpc + run: | + cd build + cmake -DWITH_UPNP=ON -DCMAKE_BUILD_TYPE=Release . + gmake -j2 diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 2e8e67ba..e763a848 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -122,7 +122,7 @@ namespace util #if defined (__APPLE__) pthread_setname_np(name); #elif defined(__FreeBSD__) - pthread_set_name_np(pthread_self(), name) + pthread_set_name_np(pthread_self(), name); #else pthread_setname_np(pthread_self(), name); #endif From bfc3acb83469df640882d5cb3d02c2f26e044c11 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 7 Dec 2020 19:47:50 +0300 Subject: [PATCH 3946/6300] use correct function for thread naming on OpenBSD Signed-off-by: R4SAS --- libi2pd/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index e763a848..794a3f14 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -121,7 +121,7 @@ namespace util void SetThreadName (const char *name) { #if defined (__APPLE__) pthread_setname_np(name); -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); #else pthread_setname_np(pthread_self(), name); From ba79b94e0680c71f47200fc0b0b227a780730705 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 8 Dec 2020 15:16:40 -0500 Subject: [PATCH 3947/6300] try to generate missing ECIESx25519 tag in last tagset --- libi2pd/Garlic.cpp | 35 +++++++++++++++++++++++++++++------ libi2pd/Garlic.h | 3 ++- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 9c008bc0..2d08f339 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -487,17 +487,19 @@ namespace garlic buf += 4; // length bool found = false; + uint64_t tag; if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) { - // try ECIESx25519 tag - uint64_t tag; + // try ECIESx25519 tag memcpy (&tag, buf, 8); auto it1 = m_ECIESx25519Tags.find (tag); if (it1 != m_ECIESx25519Tags.end ()) { found = true; - if (!it1->second.tagset->HandleNextMessage (buf, length, it1->second.index)) - LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); + if (it1->second.tagset->HandleNextMessage (buf, length, it1->second.index)) + m_LastTagset = it1->second.tagset; + else + LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); m_ECIESx25519Tags.erase (it1); } } @@ -542,7 +544,25 @@ namespace garlic // otherwise ECIESx25519 auto session = std::make_shared (this, false); // incoming if (!session->HandleNextMessage (buf, length, nullptr, 0)) - LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); + { + // try to gererate more tags for last tagset + if (m_LastTagset) + { + for (int i = 0; i < ECIESX25519_MAX_NUM_GENERATED_TAGS; i++) + { + LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); + if (AddECIESx25519SessionNextTag (m_LastTagset) == tag) + { + if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[tag].index)) + found = true; + break; + } + } + if (!found) m_LastTagset = nullptr; + } + if (!found) + LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); + } } else LogPrint (eLogError, "Garlic: Failed to decrypt message"); @@ -845,6 +865,8 @@ namespace garlic } if (numExpiredTags > 0) LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " ECIESx25519 tags expired for ", GetIdentHash().ToBase64 ()); + if (m_LastTagset && m_LastTagset->IsExpired (ts)) + m_LastTagset = nullptr; } void GarlicDestination::RemoveDeliveryStatusSession (uint32_t msgID) @@ -1030,11 +1052,12 @@ namespace garlic } } - void GarlicDestination::AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset) + uint64_t GarlicDestination::AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset) { auto index = tagset->GetNextIndex (); uint64_t tag = tagset->GetNextSessionTag (); m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{index, tagset}); + return tag; } void GarlicDestination::AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session) diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 8df830c7..379477e8 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -245,7 +245,7 @@ namespace garlic void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); - void AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset); + uint64_t AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset); void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); void RemoveECIESx25519Session (const uint8_t * staticKey); void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); @@ -285,6 +285,7 @@ namespace garlic int m_NumRatchetInboundTags; std::unordered_map, std::hash > > m_Tags; std::unordered_map m_ECIESx25519Tags; // session tag -> session + RatchetTagSetPtr m_LastTagset; // tagset last message came for // DeliveryStatus std::mutex m_DeliveryStatusSessionsMutex; std::unordered_map m_DeliveryStatusSessions; // msgID -> session From ca3b8191510c1006d031d02c50edcf6b4f6a6e8f Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 10 Dec 2020 18:32:41 +0300 Subject: [PATCH 3948/6300] [avx] check ig c++ target supports AVX Signed-off-by: R4SAS --- libi2pd/Crypto.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 68712fc7..3e6279ff 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -638,7 +638,7 @@ namespace crypto { uint64_t buf[256]; uint64_t hash[12]; // 96 bytes -#if defined(__x86_64__) || defined(__i386__) +#if (defined(__x86_64__) || defined(__i386__)) && defined(__AVX__) // not all X86 targets supports AVX (like old Pentium, see #1600) if(i2p::cpu::avx) { __asm__ From 7373dae026e50f2cb3754bbd4b108b4a90291c85 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 12 Dec 2020 09:54:07 +0300 Subject: [PATCH 3949/6300] [avx] check if c++ target supports AVX (closes #1600) Signed-off-by: R4SAS --- libi2pd/Identity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 89f6cda0..9dfaa1fc 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -828,7 +828,7 @@ namespace data XORMetric operator^(const IdentHash& key1, const IdentHash& key2) { XORMetric m; -#if defined(__x86_64__) || defined(__i386__) +#if (defined(__x86_64__) || defined(__i386__)) && defined(__AVX__) // not all X86 targets supports AVX (like old Pentium, see #1600) if(i2p::cpu::avx) { __asm__ From c91a8711e358fcf0970876d07e75d0eb9506c473 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 12 Dec 2020 17:14:58 -0500 Subject: [PATCH 3950/6300] encrypted requests to ECIES floodfills --- libi2pd/Destination.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 44180ae5..8f3b495a 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -559,9 +559,7 @@ namespace client m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); - auto msg = i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound); - if (floodfill->GetIdentity ()->GetCryptoKeyType () != i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // TODO: remove whan implemented - msg = WrapMessageForRouter (floodfill, msg); + auto msg = WrapMessageForRouter (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); @@ -755,10 +753,8 @@ namespace client AddECIESx25519Key (replyKey, replyTag); else AddSessionKey (replyKey, replyTag); - - auto msg = CreateLeaseSetDatabaseLookupMsg (dest, request->excluded, request->replyTunnel, replyKey, replyTag, isECIES); - if (nextFloodfill->GetIdentity ()->GetCryptoKeyType () != i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // TODO: remove whan implemented - msg = WrapMessageForRouter (nextFloodfill, msg); + auto msg = WrapMessageForRouter (nextFloodfill, CreateLeaseSetDatabaseLookupMsg (dest, + request->excluded, request->replyTunnel, replyKey, replyTag, isECIES)); request->outboundTunnel->SendTunnelDataMsg ( { i2p::tunnel::TunnelMessageBlock From fc2dc9a0199bf1983673ead4adde19b6ec1c9dec Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 12 Dec 2020 21:40:07 -0500 Subject: [PATCH 3951/6300] cumulative ACK bitfields --- libi2pd/SSUData.cpp | 35 +++++++++++++++++++---------------- libi2pd/SSUData.h | 6 ++++-- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 4e0e712f..5458cc97 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -185,7 +185,12 @@ namespace transport std::unique_ptr(new IncompleteMessage (msg)))).first; } std::unique_ptr& incompleteMessage = it->second; - + // mark fragment as received + if (fragmentNum < 64) + incompleteMessage->receivedFragmentsBits |= (0x01 << fragmentNum); + else + LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); + // handle current fragment if (fragmentNum == incompleteMessage->nextFragmentNum) { @@ -220,7 +225,7 @@ namespace transport // missing fragment LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast); - if (incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) + if (incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); else LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); @@ -266,7 +271,7 @@ namespace transport } } else - SendFragmentAck (msgID, fragmentNum); + SendFragmentAck (msgID, incompleteMessage->receivedFragmentsBits); buf += fragmentSize; } } @@ -394,13 +399,9 @@ namespace transport m_Session.Send (buf, 48); } - void SSUData::SendFragmentAck (uint32_t msgID, int fragmentNum) + void SSUData::SendFragmentAck (uint32_t msgID, uint64_t bits) { - if (fragmentNum > 64) - { - LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); - return; - } + if (!bits) return; uint8_t buf[64 + 18] = {0}; uint8_t * payload = buf + sizeof (SSUHeader); *payload = DATA_FLAG_ACK_BITFIELDS_INCLUDED; // flag @@ -410,14 +411,16 @@ namespace transport // one ack *(uint32_t *)(payload) = htobe32 (msgID); // msgID payload += 4; - div_t d = div (fragmentNum, 7); - memset (payload, 0x80, d.quot); // 0x80 means non-last - payload += d.quot; - *payload = 0x01 << d.rem; // set corresponding bit - payload++; + size_t len = 0; + while (bits) + { + *payload = (bits & 0x7F); // next 7 bits + bits >>= 7; + if (bits) *payload &= 0x80; // 0x80 means non-last + payload++; len++; + } *payload = 0; // number of fragments - - size_t len = d.quot < 4 ? 48 : 64; // 48 = 37 + 7 + 4 (3+1) + len = (len <= 4) ? 48 : 64; // 48 = 37 + 7 + 4 // encrypt message with session key m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, len); m_Session.Send (buf, len); diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index 2e606053..902c009a 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -75,9 +75,11 @@ namespace transport std::shared_ptr msg; int nextFragmentNum; uint32_t lastFragmentInsertTime; // in seconds + uint64_t receivedFragmentsBits; std::set, FragmentCmp> savedFragments; - IncompleteMessage (std::shared_ptr m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0) {}; + IncompleteMessage (std::shared_ptr m): msg (m), nextFragmentNum (0), + lastFragmentInsertTime (0), receivedFragmentsBits (0) {}; void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); }; @@ -109,7 +111,7 @@ namespace transport private: void SendMsgAck (uint32_t msgID); - void SendFragmentAck (uint32_t msgID, int fragmentNum); + void SendFragmentAck (uint32_t msgID, uint64_t bits); void ProcessAcks (uint8_t *& buf, uint8_t flag); void ProcessFragments (uint8_t * buf); void ProcessSentMessageAck (uint32_t msgID); From 31f0c350771a431710002dfffb9e94d38d913fa9 Mon Sep 17 00:00:00 2001 From: gxcreator Date: Sun, 13 Dec 2020 17:22:59 +0000 Subject: [PATCH 3952/6300] Docker: Move DEFAULT_ARGS to Dockerfile . --- contrib/docker/Dockerfile | 1 + contrib/docker/entrypoint.sh | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index be3feea7..adb7ba75 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -11,6 +11,7 @@ ENV REPO_URL=${REPO_URL} ENV I2PD_HOME="/home/i2pd" ENV DATA_DIR="${I2PD_HOME}/data" +ENV DEFAULT_ARGS=" --datadir=$DATA_DIR --reseed.verify=true --upnp.enabled=false --http.enabled=true --http.address=0.0.0.0 --httpproxy.enabled=true --httpproxy.address=0.0.0.0 --socksproxy.enabled=true --socksproxy.address=0.0.0.0 --sam.enabled=true --sam.address=0.0.0.0" RUN mkdir -p "$I2PD_HOME" "$DATA_DIR" \ && adduser -S -h "$I2PD_HOME" i2pd \ diff --git a/contrib/docker/entrypoint.sh b/contrib/docker/entrypoint.sh index ca9dddd4..ce897e3c 100644 --- a/contrib/docker/entrypoint.sh +++ b/contrib/docker/entrypoint.sh @@ -2,7 +2,6 @@ COMMAND=/usr/local/bin/i2pd # To make ports exposeable # Note: $DATA_DIR is defined in /etc/profile -DEFAULT_ARGS=" --datadir=$DATA_DIR --reseed.verify=true --upnp.enabled=false --http.enabled=true --http.address=0.0.0.0 --httpproxy.enabled=true --httpproxy.address=0.0.0.0 --socksproxy.enabled=true --socksproxy.address=0.0.0.0 --sam.enabled=true --sam.address=0.0.0.0" if [ "$1" = "--help" ]; then set -- $COMMAND --help From 65945b3462a5d52dfb0b235dd4dacaea73ee30f6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 13 Dec 2020 21:55:51 -0500 Subject: [PATCH 3953/6300] correct offline signature size for close packet --- libi2pd/Streaming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 05b34d9e..f6bf7e8b 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -701,7 +701,7 @@ namespace stream size++; // resend delay htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); size += 2; // flags - size_t signatureLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetSignatureLen (); + size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); htobe16buf (packet + size, signatureLen); // signature only size += 2; // options size uint8_t * signature = packet + size; From bf91e16b5d7e0d922c96f95a519c8033ce790b65 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 15 Dec 2020 15:04:20 -0500 Subject: [PATCH 3954/6300] gererate specified number of tags if misssing tag --- libi2pd/Garlic.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 2d08f339..02ded2f4 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -548,7 +548,8 @@ namespace garlic // try to gererate more tags for last tagset if (m_LastTagset) { - for (int i = 0; i < ECIESX25519_MAX_NUM_GENERATED_TAGS; i++) + auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS); + for (int i = 0; i < maxTags; i++) { LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); if (AddECIESx25519SessionNextTag (m_LastTagset) == tag) From 06a7e181cd6a44cf09aeb5046a446e821673c1f9 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 15 Dec 2020 16:06:32 -0500 Subject: [PATCH 3955/6300] ECIES for new routers --- libi2pd/RouterContext.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 5cfbd943..d258b341 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -52,7 +52,8 @@ namespace i2p void RouterContext::CreateNewRouter () { - m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, + i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); SaveKeys (); NewRouterInfo (); } @@ -590,7 +591,8 @@ namespace i2p // update keys LogPrint (eLogInfo, "Router: router keys are obsolete. Creating new"); oldIdentity = m_Keys.GetPublic (); - m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); + m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, + i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); SaveKeys (); } // read NTCP2 keys if available From 082c4f1104b64ede18f4524e3e75977ca35e1819 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 17 Dec 2020 22:17:05 +0800 Subject: [PATCH 3956/6300] qt: added about box --- qt/i2pd_qt/AboutDialog.cpp | 23 ++++ qt/i2pd_qt/AboutDialog.h | 22 ++++ qt/i2pd_qt/AboutDialog.ui | 194 +++++++++++++++++++++++++++++ qt/i2pd_qt/BuildDateTimeQt.h | 7 ++ qt/i2pd_qt/i2pd_qt.pro | 21 +++- qt/i2pd_qt/mainwindow.cpp | 44 ++++++- qt/i2pd_qt/mainwindow.h | 1 + qt/i2pd_qt/mainwindow.ui | 67 +++++++--- qt/i2pd_qt/routercommandswidget.ui | 4 +- 9 files changed, 353 insertions(+), 30 deletions(-) create mode 100644 qt/i2pd_qt/AboutDialog.cpp create mode 100644 qt/i2pd_qt/AboutDialog.h create mode 100644 qt/i2pd_qt/AboutDialog.ui create mode 100644 qt/i2pd_qt/BuildDateTimeQt.h diff --git a/qt/i2pd_qt/AboutDialog.cpp b/qt/i2pd_qt/AboutDialog.cpp new file mode 100644 index 00000000..2428c2da --- /dev/null +++ b/qt/i2pd_qt/AboutDialog.cpp @@ -0,0 +1,23 @@ +#include "AboutDialog.h" +#include "ui_AboutDialog.h" +#include +#include "version.h" +#include "BuildDateTimeQt.h" + +AboutDialog::AboutDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::AboutDialog) +{ + qDebug() << "AboutDialog::AboutDialog()" << endl; + ui->setupUi(this); + ui->i2pdVersionLabel->setText(I2PD_VERSION); + ui->i2pVersionLabel->setText(I2P_VERSION); + ui->buildDateTimeLabel->setText(BUILD_DATE_TIME_QT); + ui->vcsCommitInfoLabel->setText(VCS_COMMIT_INFO); +} + +AboutDialog::~AboutDialog() +{ + qDebug() << "AboutDialog::~AboutDialog()" << endl; + delete ui; +} diff --git a/qt/i2pd_qt/AboutDialog.h b/qt/i2pd_qt/AboutDialog.h new file mode 100644 index 00000000..d462de28 --- /dev/null +++ b/qt/i2pd_qt/AboutDialog.h @@ -0,0 +1,22 @@ +#ifndef ABOUTDIALOG_H +#define ABOUTDIALOG_H + +#include + +namespace Ui { +class AboutDialog; +} + +class AboutDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AboutDialog(QWidget *parent = 0); + ~AboutDialog(); + +private: + Ui::AboutDialog *ui; +}; + +#endif // ABOUTDIALOG_H diff --git a/qt/i2pd_qt/AboutDialog.ui b/qt/i2pd_qt/AboutDialog.ui new file mode 100644 index 00000000..6e662706 --- /dev/null +++ b/qt/i2pd_qt/AboutDialog.ui @@ -0,0 +1,194 @@ + + + AboutDialog + + + Qt::WindowModal + + + + 0 + 0 + 400 + 199 + + + + About i2pd_qt + + + + :/icons/mask:/icons/mask + + + + + 10 + 160 + 381 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + 10 + 10 + 381 + 17 + + + + <html><head/><body><p><span style=" font-weight:600;">About i2pd_qt</span></p></body></html> + + + + + + 10 + 40 + 131 + 17 + + + + i2pd Version: + + + + + + 10 + 70 + 131 + 17 + + + + Build Date/Time: + + + + + + 150 + 40 + 241 + 20 + + + + I2PD_VERSION_LABEL + + + + + + 10 + 130 + 131 + 17 + + + + I2P Version: + + + + + + 150 + 130 + 241 + 17 + + + + I2P_VERSION_LABEL + + + + + + 150 + 70 + 241 + 20 + + + + BUILD_DATE_TIME_LABEL + + + + + + 10 + 100 + 131 + 17 + + + + Version Control: + + + + + + 150 + 100 + 241 + 17 + + + + VCS_COMMIT_INFO_LABEL + + + + + + + + + buttonBox + accepted() + AboutDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AboutDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/qt/i2pd_qt/BuildDateTimeQt.h b/qt/i2pd_qt/BuildDateTimeQt.h new file mode 100644 index 00000000..139e2083 --- /dev/null +++ b/qt/i2pd_qt/BuildDateTimeQt.h @@ -0,0 +1,7 @@ +#ifndef BUILDDATETIMEQT_H +#define BUILDDATETIMEQT_H + +#include +const QString BUILD_DATE_TIME_QT = QStringLiteral(__DATE__ " " __TIME__); + +#endif // BUILDDATETIMEQT_H diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index 004b6273..a572e454 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -36,7 +36,8 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \ ../../daemon/HTTPServer.cpp \ ../../daemon/I2PControl.cpp \ ../../daemon/i2pd.cpp \ - ../../daemon/UPnP.cpp + ../../daemon/UPnP.cpp \ + AboutDialog.cpp HEADERS += DaemonQT.h mainwindow.h \ ClientTunnelPane.h \ @@ -59,8 +60,9 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../daemon/Daemon.h \ ../../daemon/HTTPServer.h \ ../../daemon/I2PControl.h \ - ../../daemon/UPnP.h - + ../../daemon/UPnP.h \ + AboutDialog.h \ + BuildDateTimeQt.h INCLUDEPATH += ../../libi2pd INCLUDEPATH += ../../libi2pd_client @@ -71,7 +73,8 @@ FORMS += mainwindow.ui \ tunnelform.ui \ statusbuttons.ui \ routercommandswidget.ui \ - generalsettingswidget.ui + generalsettingswidget.ui \ + AboutDialog.ui LIBS += $$PWD/../../libi2pd.a $$PWD/../../libi2pdclient.a -lz @@ -86,6 +89,16 @@ i2pd.depends = FORCE cleani2pd.commands = cd $$PWD/../../ && CC=$$QMAKE_CC CXX=$$QMAKE_CXX $(MAKE) clean cleani2pd.depends = clean +BuildDateTimeQtTarget.target = BuildDateTimeQt.h +BuildDateTimeQtTarget.depends = FORCE +# 'touch' is unix-only; will probably break on non-unix, TBD +BuildDateTimeQtTarget.commands = touch $$PWD/BuildDateTimeQt.h +PRE_TARGETDEPS += BuildDateTimeQt.h +QMAKE_EXTRA_TARGETS += BuildDateTimeQtTarget + +# git only, port to other VCS, too. TBD +DEFINES += VCS_COMMIT_INFO="\\\"git:$(shell git -C \""$$_PRO_FILE_PWD_"\" describe)\\\"" + PRE_TARGETDEPS += $$PWD/../../libi2pd.a $$PWD/../../libi2pdclient.a QMAKE_EXTRA_TARGETS += cleani2pd i2pd libi2pd CLEAN_DEPS += cleani2pd diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 89178ee0..925992a5 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -1,4 +1,5 @@ #include "mainwindow.h" +#include "AboutDialog.h" #include "ui_mainwindow.h" #include "ui_statusbuttons.h" #include "ui_routercommandswidget.h" @@ -8,6 +9,13 @@ #include #include #include + +#include +#include +#include +#include +#include + #include "RouterContext.h" #include "Config.h" #include "FS.h" @@ -65,6 +73,10 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren statusButtonsUI->setupUi(ui->statusButtonsPane); routerCommandsUI->setupUi(routerCommandsParent); uiSettings->setupUi(ui->settingsContents); + + ui->aboutHrefLabel->setText("

    " + "i2pd_qt
    Version " I2PD_VERSION " · About...

    "); + routerCommandsParent->hide(); ui->verticalLayout_2->addWidget(routerCommandsParent); //,statusHtmlUI(new Ui::StatusHtmlPaneForm) @@ -76,15 +88,16 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren setWindowTitle(QApplication::translate("AppTitle","I2PD")); //TODO handle resizes and change the below into resize() call - setFixedHeight(550); - ui->centralWidget->setFixedHeight(550); + constexpr auto WINDOW_HEIGHT = 610; + setFixedHeight(WINDOW_HEIGHT); + ui->centralWidget->setFixedHeight(WINDOW_HEIGHT); onResize(); ui->stackedWidget->setCurrentIndex(0); ui->settingsScrollArea->resize(uiSettings->settingsContentsQVBoxLayout->sizeHint().width()+10,380); //QScrollBar* const barSett = ui->settingsScrollArea->verticalScrollBar(); - int w = 683; - int h = 4550; + constexpr auto w = 683; + constexpr auto h = 4550; ui->settingsContents->setFixedSize(w, h); ui->settingsContents->setGeometry(QRect(0,0,w,h)); @@ -143,6 +156,8 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren QObject::connect(routerCommandsUI->acceptTransitTunnelsPushButton, SIGNAL(released()), this, SLOT(enableTransit())); QObject::connect(routerCommandsUI->declineTransitTunnelsPushButton, SIGNAL(released()), this, SLOT(disableTransit())); + QObject::connect(ui->aboutHrefLabel, SIGNAL(linkActivated(const QString &)), this, SLOT(showAboutBox(const QString &))); + QObject::connect(ui->logViewerPushButton, SIGNAL(released()), this, SLOT(showLogViewerPage())); QObject::connect(ui->settingsPagePushButton, SIGNAL(released()), this, SLOT(showSettingsPage())); @@ -400,6 +415,27 @@ void MainWindow::showStatusPage(StatusPage newStatusPage){ } wasSelectingAtStatusMainPage=false; } + +void MainWindow::showAboutBox(const QString & href) { + qDebug() << "MainWindow::showAboutBox(), href:" << href << endl; + AboutDialog dialog(this); + + if (!QGuiApplication::styleHints()->showIsFullScreen() && !QGuiApplication::styleHints()->showIsMaximized()) { + const QWindow * windowHandle = dialog.windowHandle(); + qDebug()<<"AboutDialog windowHandle ptr: "<<(size_t)windowHandle<screen():nullptr; //Qt 5.14+: dialog.screen() + qDebug()<<"AboutDialog screen ptr: "<<(size_t)screen<availableGeometry(); + //dialog.resize(availableGeometry.width() / 3, availableGeometry.height() * 2 / 3); + dialog.move((availableGeometry.width() - dialog.width()) / 2, + (availableGeometry.height() - dialog.height()) / 2); + } + } + //dialog.show(); + (void) dialog.exec(); +} + void MainWindow::showLogViewerPage(){ui->stackedWidget->setCurrentIndex(1);setStatusButtonsVisible(false);} void MainWindow::showSettingsPage(){ui->stackedWidget->setCurrentIndex(2);setStatusButtonsVisible(false);} void MainWindow::showTunnelsPage(){ui->stackedWidget->setCurrentIndex(3);setStatusButtonsVisible(false);} diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 77c8826b..2114155b 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -442,6 +442,7 @@ public slots: void showTunnelsPage(); void showRestartPage(); void showQuitPage(); + void showAboutBox(const QString & href); private: StatusPage statusPage; diff --git a/qt/i2pd_qt/mainwindow.ui b/qt/i2pd_qt/mainwindow.ui index dcdf88bd..8f942b08 100644 --- a/qt/i2pd_qt/mainwindow.ui +++ b/qt/i2pd_qt/mainwindow.ui @@ -7,7 +7,7 @@ 0 0 908 - 554 + 604 @@ -35,13 +35,13 @@ 908 - 550 + 600 908 - 550 + 600 @@ -50,7 +50,7 @@ 10 10 888 - 531 + 596 @@ -58,7 +58,7 @@ QLayout::SetMaximumSize - + QLayout::SetMinimumSize @@ -67,7 +67,7 @@ 0 0 170 - 496 + 596 @@ -172,6 +172,33 @@ + + + + + 9 + + + + Qt::NoContextMenu + + + Show app name, version and build date + + + <html><head/><body><p><a href="about:i2pd_qt"><span style="text-decoration: none; color:#a0a0a0;"><span style="font-weight: 500;">i2pd_qt</span><br/>Version SHORT_VERSION · About...</span></a></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 6 + + + 0 + + + @@ -629,7 +656,7 @@ - 713 + 707 713 @@ -648,8 +675,8 @@ 0 0 - 713 - 531 + 707 + 586 @@ -690,8 +717,8 @@ 0 0 - 711 - 531 + 707 + 586 @@ -753,8 +780,8 @@ 0 0 - 711 - 531 + 707 + 586 @@ -798,8 +825,8 @@ 0 0 - 81 - 28 + 693 + 498 @@ -820,8 +847,8 @@ 0 0 - 711 - 531 + 707 + 586 @@ -903,8 +930,8 @@ 0 0 - 711 - 531 + 707 + 586 @@ -958,7 +985,7 @@ 0 0 - 711 + 707 531 diff --git a/qt/i2pd_qt/routercommandswidget.ui b/qt/i2pd_qt/routercommandswidget.ui index c5098e8e..f95db1fd 100644 --- a/qt/i2pd_qt/routercommandswidget.ui +++ b/qt/i2pd_qt/routercommandswidget.ui @@ -6,7 +6,7 @@ 0 0 - 711 + 707 300 @@ -24,7 +24,7 @@ 0 0 - 711 + 707 301 From 776dc7ec5277d436631fbfff917b60c2c27a4d72 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 17 Dec 2020 22:30:14 +0800 Subject: [PATCH 3957/6300] qt: about box fixed for older qt5 --- qt/i2pd_qt/mainwindow.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 925992a5..48484349 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -420,6 +420,8 @@ void MainWindow::showAboutBox(const QString & href) { qDebug() << "MainWindow::showAboutBox(), href:" << href << endl; AboutDialog dialog(this); + /* + //doesn't work on older qt5: ‘class QStyleHints’ has no member named ‘showIsMaximized’ if (!QGuiApplication::styleHints()->showIsFullScreen() && !QGuiApplication::styleHints()->showIsMaximized()) { const QWindow * windowHandle = dialog.windowHandle(); qDebug()<<"AboutDialog windowHandle ptr: "<<(size_t)windowHandle< Date: Thu, 17 Dec 2020 22:45:10 +0800 Subject: [PATCH 3958/6300] qt: removed a few debug log lines --- qt/i2pd_qt/AboutDialog.cpp | 2 -- qt/i2pd_qt/mainwindow.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/qt/i2pd_qt/AboutDialog.cpp b/qt/i2pd_qt/AboutDialog.cpp index 2428c2da..ac92694a 100644 --- a/qt/i2pd_qt/AboutDialog.cpp +++ b/qt/i2pd_qt/AboutDialog.cpp @@ -8,7 +8,6 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog) { - qDebug() << "AboutDialog::AboutDialog()" << endl; ui->setupUi(this); ui->i2pdVersionLabel->setText(I2PD_VERSION); ui->i2pVersionLabel->setText(I2P_VERSION); @@ -18,6 +17,5 @@ AboutDialog::AboutDialog(QWidget *parent) : AboutDialog::~AboutDialog() { - qDebug() << "AboutDialog::~AboutDialog()" << endl; delete ui; } diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 48484349..70975fb2 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -417,7 +417,6 @@ void MainWindow::showStatusPage(StatusPage newStatusPage){ } void MainWindow::showAboutBox(const QString & href) { - qDebug() << "MainWindow::showAboutBox(), href:" << href << endl; AboutDialog dialog(this); /* From d3bf8c2417a34a6dbe29d85ba82d026e51e596f8 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 17 Dec 2020 23:15:56 +0800 Subject: [PATCH 3959/6300] data: ignored *.tmp.xml --- qt/i2pd_qt/data/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 qt/i2pd_qt/data/.gitignore diff --git a/qt/i2pd_qt/data/.gitignore b/qt/i2pd_qt/data/.gitignore new file mode 100644 index 00000000..dde94bce --- /dev/null +++ b/qt/i2pd_qt/data/.gitignore @@ -0,0 +1,2 @@ +*.tmp.xml + From ccc604c0f4604bf18082f2effe5b2e9174980f37 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Dec 2020 01:13:50 +0800 Subject: [PATCH 3960/6300] qt: fixes #1180 --- qt/i2pd_qt/ClientTunnelPane.h | 7 +++ qt/i2pd_qt/I2pdQtUtil.cpp | 12 ++++ qt/i2pd_qt/I2pdQtUtil.h | 9 +++ qt/i2pd_qt/ServerTunnelPane.h | 11 ++++ qt/i2pd_qt/TunnelPane.cpp | 8 ++- qt/i2pd_qt/TunnelPane.h | 17 ++++++ qt/i2pd_qt/i2pd_qt.pro | 6 +- qt/i2pd_qt/mainwindow.cpp | 47 +++++++++++----- qt/i2pd_qt/mainwindow.h | 103 ++++++++++++++++++++-------------- 9 files changed, 161 insertions(+), 59 deletions(-) create mode 100644 qt/i2pd_qt/I2pdQtUtil.cpp create mode 100644 qt/i2pd_qt/I2pdQtUtil.h diff --git a/qt/i2pd_qt/ClientTunnelPane.h b/qt/i2pd_qt/ClientTunnelPane.h index 80d331de..f6bf4376 100644 --- a/qt/i2pd_qt/ClientTunnelPane.h +++ b/qt/i2pd_qt/ClientTunnelPane.h @@ -79,6 +79,13 @@ protected: ClientTunnelConfig* ctc=tunnelConfig->asClientTunnelConfig(); assert(ctc!=nullptr); + if(!isValidSingleLine(destinationLineEdit))return false; + if(!isValidSingleLine(portLineEdit))return false; + if(!isValidSingleLine(cryptoTypeLineEdit))return false; + if(!isValidSingleLine(keysLineEdit))return false; + if(!isValidSingleLine(addressLineEdit))return false; + if(!isValidSingleLine(destinationPortLineEdit))return false; + //destination ctc->setdest(destinationLineEdit->text().toStdString()); diff --git a/qt/i2pd_qt/I2pdQtUtil.cpp b/qt/i2pd_qt/I2pdQtUtil.cpp new file mode 100644 index 00000000..35a7adff --- /dev/null +++ b/qt/i2pd_qt/I2pdQtUtil.cpp @@ -0,0 +1,12 @@ +#include "I2pdQtUtil.h" + +bool isValidSingleLine(QLineEdit* widget, WrongInputPageEnum inputPage, MainWindow* mainWindow) { + bool correct = !widget->text().contains(QRegularExpression("[\r\n]"), nullptr); + if(!correct) { + mainWindow->highlightWrongInput( + QApplication::tr("Single line input expected, but it's multiline"), + inputPage, + widget); + } + return correct; +} diff --git a/qt/i2pd_qt/I2pdQtUtil.h b/qt/i2pd_qt/I2pdQtUtil.h new file mode 100644 index 00000000..90c3e521 --- /dev/null +++ b/qt/i2pd_qt/I2pdQtUtil.h @@ -0,0 +1,9 @@ +#ifndef I2pdQtUtil_H +#define I2pdQtUtil_H + +class QLineEdit; +#include "mainwindow.h" + +bool isValidSingleLine(QLineEdit* widget, WrongInputPageEnum inputPage, MainWindow* mainWindow); + +#endif // I2pdQtUtil_H diff --git a/qt/i2pd_qt/ServerTunnelPane.h b/qt/i2pd_qt/ServerTunnelPane.h index 92ee4da5..de844249 100644 --- a/qt/i2pd_qt/ServerTunnelPane.h +++ b/qt/i2pd_qt/ServerTunnelPane.h @@ -124,6 +124,17 @@ protected: if(!ok)return false; ServerTunnelConfig* stc=tunnelConfig->asServerTunnelConfig(); assert(stc!=nullptr); + + if(!isValidSingleLine(hostLineEdit))return false; + if(!isValidSingleLine(portLineEdit))return false; + if(!isValidSingleLine(cryptoTypeLineEdit))return false; + if(!isValidSingleLine(keysLineEdit))return false; + if(!isValidSingleLine(inPortLineEdit))return false; + if(!isValidSingleLine(accessListLineEdit))return false; + if(!isValidSingleLine(hostOverrideLineEdit))return false; + if(!isValidSingleLine(webIRCPassLineEdit))return false; + if(!isValidSingleLine(addressLineEdit))return false; + stc->sethost(hostLineEdit->text().toStdString()); auto portStr=portLineEdit->text(); diff --git a/qt/i2pd_qt/TunnelPane.cpp b/qt/i2pd_qt/TunnelPane.cpp index 4b873ac1..7e15a6a6 100644 --- a/qt/i2pd_qt/TunnelPane.cpp +++ b/qt/i2pd_qt/TunnelPane.cpp @@ -4,6 +4,8 @@ #include "mainwindow.h" #include "ui_mainwindow.h" +#include "I2pdQtUtil.h" + TunnelPane::TunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener_, TunnelConfig* tunnelConfig_, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow_): QObject(), mainWindow(mainWindow_), @@ -47,8 +49,6 @@ void TunnelPane::setupTunnelPane( nameLineEdit->setText(tunnelName); setGroupBoxTitle(tunnelName); - QObject::connect(nameLineEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(setGroupBoxTitle(const QString &))); QObject::connect(nameLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); @@ -399,3 +399,7 @@ void TunnelPane::hideWrongInputLabel() const { wrongInputPane->setVisible(false); mainWindow->adjustSizesAccordingToWrongLabel(); } + +bool TunnelPane::isValidSingleLine(QLineEdit* widget) { + return ::isValidSingleLine(widget, WrongInputPageEnum::tunnelsSettingsPage, mainWindow); +} diff --git a/qt/i2pd_qt/TunnelPane.h b/qt/i2pd_qt/TunnelPane.h index 59303afd..ac21181a 100644 --- a/qt/i2pd_qt/TunnelPane.h +++ b/qt/i2pd_qt/TunnelPane.h @@ -119,6 +119,21 @@ protected: public: //returns false when invalid data at UI virtual bool applyDataFromUIToTunnelConfig() { + if(!isValidSingleLine(nameLineEdit)){ + setGroupBoxTitle(QApplication::translate("tunPage", "invalid_tunnel_name")); + return false; + } + if(!isValidSingleLine(inbound_lengthLineEdit))return false; + if(!isValidSingleLine(inbound_quantityLineEdit))return false; + if(!isValidSingleLine(outbound_lengthLineEdit))return false; + if(!isValidSingleLine(outbound_quantityLineEdit))return false; + if(!isValidSingleLine(crypto_tagsToSendLineEdit))return false; + if(!isValidSingleLine(i2cp_leaseSetAuthTypeLineEdit))return false; + if(!isValidSingleLine(i2cp_leaseSetEncTypeLineEdit))return false; + if(!isValidSingleLine(i2cp_leaseSetPrivKeyLineEdit))return false; + if(!isValidSingleLine(i2cp_leaseSetTypeLineEdit))return false; + if(!isValidSingleLine(i2p_streaming_initialAckDelayLineEdit))return false; + setGroupBoxTitle(nameLineEdit->text()); tunnelConfig->setName(nameLineEdit->text().toStdString()); tunnelConfig->setType(readTunnelTypeComboboxData()); I2CPParameters& i2cpParams=tunnelConfig->getI2cpParameters(); @@ -169,6 +184,8 @@ private: i2cp_leaseSetPrivKeyLabel->setText(QApplication::translate("tunForm", "Decryption key for encrypted LeaseSet in base64. PSK or private DH:", 0)); i2cp_leaseSetAuthTypeLabel->setText(QApplication::translate("tunForm", "Auth type for encrypted LeaseSet. 0 - no auth, 1 - DH, 2 - PSK:", 0)); } +protected: + bool isValidSingleLine(QLineEdit* widget); }; #endif // TUNNELPANE_H diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index a572e454..c4e55f53 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -37,7 +37,8 @@ SOURCES += DaemonQT.cpp mainwindow.cpp \ ../../daemon/I2PControl.cpp \ ../../daemon/i2pd.cpp \ ../../daemon/UPnP.cpp \ - AboutDialog.cpp + AboutDialog.cpp \ + I2pdQtUtil.cpp HEADERS += DaemonQT.h mainwindow.h \ ClientTunnelPane.h \ @@ -62,7 +63,8 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../daemon/I2PControl.h \ ../../daemon/UPnP.h \ AboutDialog.h \ - BuildDateTimeQt.h + BuildDateTimeQt.h \ + I2pdQtUtil.h INCLUDEPATH += ../../libi2pd INCLUDEPATH += ../../libi2pd_client diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 70975fb2..de2c9fb8 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -1,4 +1,5 @@ #include "mainwindow.h" +#include "I2pdQtUtil.h" #include "AboutDialog.h" #include "ui_mainwindow.h" #include "ui_statusbuttons.h" @@ -16,6 +17,8 @@ #include #include +#include + #include "RouterContext.h" #include "Config.h" #include "FS.h" @@ -37,6 +40,7 @@ #include "DelayedSaveManagerImpl.h" #include "SaverImpl.h" + std::string programOptionsWriterCurrentSection; MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *parent) : @@ -647,13 +651,13 @@ MainWindow::~MainWindow() FileChooserItem* MainWindow::initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton){ FileChooserItem* retVal; - retVal=new FileChooserItem(option, fileNameLineEdit, fileBrowsePushButton); + retVal=new FileChooserItem(option, fileNameLineEdit, fileBrowsePushButton, this); MainWindowItem* super=retVal; configItems.append(super); return retVal; } void MainWindow::initFolderChooser(ConfigOption option, QLineEdit* folderLineEdit, QPushButton* folderBrowsePushButton){ - configItems.append(new FolderChooserItem(option, folderLineEdit, folderBrowsePushButton)); + configItems.append(new FolderChooserItem(option, folderLineEdit, folderBrowsePushButton, this)); } /*void MainWindow::initCombobox(ConfigOption option, QComboBox* comboBox){ configItems.append(new ComboBoxItem(option, comboBox)); @@ -669,25 +673,25 @@ void MainWindow::initSignatureTypeCombobox(ConfigOption option, QComboBox* combo configItems.append(new SignatureTypeComboBoxItem(option, comboBox)); } void MainWindow::initIPAddressBox(ConfigOption option, QLineEdit* addressLineEdit, QString fieldNameTranslated){ - configItems.append(new IPAddressStringItem(option, addressLineEdit, fieldNameTranslated)); + configItems.append(new IPAddressStringItem(option, addressLineEdit, fieldNameTranslated, this)); } void MainWindow::initTCPPortBox(ConfigOption option, QLineEdit* portLineEdit, QString fieldNameTranslated){ - configItems.append(new TCPPortStringItem(option, portLineEdit, fieldNameTranslated)); + configItems.append(new TCPPortStringItem(option, portLineEdit, fieldNameTranslated, this)); } void MainWindow::initCheckBox(ConfigOption option, QCheckBox* checkBox) { configItems.append(new CheckBoxItem(option, checkBox)); } void MainWindow::initIntegerBox(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ - configItems.append(new IntegerStringItem(option, numberLineEdit, fieldNameTranslated)); + configItems.append(new IntegerStringItem(option, numberLineEdit, fieldNameTranslated, this)); } void MainWindow::initUInt32Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ - configItems.append(new UInt32StringItem(option, numberLineEdit, fieldNameTranslated)); + configItems.append(new UInt32StringItem(option, numberLineEdit, fieldNameTranslated, this)); } void MainWindow::initUInt16Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ - configItems.append(new UInt16StringItem(option, numberLineEdit, fieldNameTranslated)); + configItems.append(new UInt16StringItem(option, numberLineEdit, fieldNameTranslated, this)); } void MainWindow::initStringBox(ConfigOption option, QLineEdit* lineEdit){ - configItems.append(new BaseStringItem(option, lineEdit, QString())); + configItems.append(new BaseStringItem(option, lineEdit, QString(), this)); } NonGUIOptionItem* MainWindow::initNonGUIOption(ConfigOption option) { NonGUIOptionItem * retValue; @@ -789,12 +793,17 @@ bool MainWindow::saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocu for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { MainWindowItem* item = *it; - if(!item->isValid()){ - highlightWrongInput(QApplication::tr("Invalid value for")+" "+item->getConfigOption().section+"::"+item->getConfigOption().option+". "+item->getRequirementToBeValid()+" "+cannotSaveSettings, item->getWidgetToFocus()); + bool alreadyDisplayedIfWrong=false; + if(!item->isValid(alreadyDisplayedIfWrong)){ + if(!alreadyDisplayedIfWrong) + highlightWrongInput( + QApplication::tr("Invalid value for")+" "+item->getConfigOption().section+"::"+item->getConfigOption().option+". "+item->getRequirementToBeValid()+" "+cannotSaveSettings, + WrongInputPageEnum::generalSettingsPage, + item->getWidgetToFocus()); return false; } } - delayedSaveManagerPtr->delayedSave(++dataSerial, focusOnTunnel, tunnelNameToFocus); + delayedSaveManagerPtr->delayedSave(++dataSerial, focusOnTunnel, tunnelNameToFocus);//TODO does dataSerial work? //FIXME //onLoggingOptionsChange(); return true; @@ -814,6 +823,11 @@ void FolderChooserItem::pushButtonReleased() { void BaseStringItem::installListeners(MainWindow *mainWindow) { QObject::connect(lineEdit, SIGNAL(textChanged(const QString &)), mainWindow, SLOT(updated())); } +bool BaseStringItem::isValid(bool & alreadyDisplayedIfWrong) { + alreadyDisplayedIfWrong=true; + return ::isValidSingleLine(lineEdit, WrongInputPageEnum::generalSettingsPage, mainWindow); +} + void ComboBoxItem::installListeners(MainWindow *mainWindow) { QObject::connect(comboBox, SIGNAL(currentIndexChanged(int)), mainWindow, SLOT(updated())); } @@ -825,7 +839,8 @@ void MainWindow::updated() { ui->wrongInputLabel->setVisible(false); adjustSizesAccordingToWrongLabel(); - applyTunnelsUiToConfigs(); + bool correct = applyTunnelsUiToConfigs(); + if(!correct) return; saveAllConfigs(false); } @@ -1010,11 +1025,15 @@ void MainWindow::adjustSizesAccordingToWrongLabel() { } } -void MainWindow::highlightWrongInput(QString warningText, QWidget* widgetToFocus) { +void MainWindow::highlightWrongInput(QString warningText, WrongInputPageEnum inputPage, QWidget* widgetToFocus) { bool redVisible = ui->wrongInputLabel->isVisible(); ui->wrongInputLabel->setVisible(true); ui->wrongInputLabel->setText(warningText); if(!redVisible)adjustSizesAccordingToWrongLabel(); if(widgetToFocus){ui->settingsScrollArea->ensureWidgetVisible(widgetToFocus);widgetToFocus->setFocus();} - showSettingsPage(); + switch(inputPage) { + case WrongInputPageEnum::generalSettingsPage: showSettingsPage(); break; + case WrongInputPageEnum::tunnelsSettingsPage: showTunnelsPage(); break; + default: assert(false); break; + } } diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 2114155b..34228e43 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -1,6 +1,8 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +enum WrongInputPageEnum { generalSettingsPage, tunnelsSettingsPage }; + #include #include #include @@ -66,6 +68,8 @@ #include "DelayedSaveManagerImpl.h" #include "SaverImpl.h" +#include "I2pdQtUtil.h" + class SaverImpl; class LogViewerManager; @@ -155,19 +159,24 @@ public: }else out << boost::any_cast(optionValue); //let it throw out << "\n\n"; } - virtual bool isValid(){return true;} + virtual bool isValid(bool & alreadyDisplayedIfWrong){alreadyDisplayedIfWrong=false;return true;} }; class NonGUIOptionItem : public MainWindowItem { public: - NonGUIOptionItem(ConfigOption option_) : MainWindowItem(option_, nullptr, QString()) {}; + NonGUIOptionItem(ConfigOption option_) : MainWindowItem(option_, nullptr, QString()) {} virtual ~NonGUIOptionItem(){} - virtual bool isValid() { return true; } + //virtual bool isValid(bool & alreadyDisplayedIfWrong) { return true; } }; class BaseStringItem : public MainWindowItem { Q_OBJECT public: QLineEdit* lineEdit; - BaseStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString requirementToBeValid_) : MainWindowItem(option_, lineEdit_, requirementToBeValid_), lineEdit(lineEdit_){}; + MainWindow *mainWindow; + BaseStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString requirementToBeValid_, MainWindow* mainWindow_): + MainWindowItem(option_, lineEdit_, requirementToBeValid_), + lineEdit(lineEdit_), + mainWindow(mainWindow_) + {}; virtual ~BaseStringItem(){} virtual void installListeners(MainWindow *mainWindow); virtual QString toString(){ @@ -183,13 +192,13 @@ public: optionValue=fromString(lineEdit->text()); MainWindowItem::saveToStringStream(out); } - virtual bool isValid() { return true; } + virtual bool isValid(bool & alreadyDisplayedIfWrong); }; class FileOrFolderChooserItem : public BaseStringItem { public: QPushButton* browsePushButton; - FileOrFolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : - BaseStringItem(option_, lineEdit_, QString()), browsePushButton(browsePushButton_) {} + FileOrFolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw) : + BaseStringItem(option_, lineEdit_, QString(), mw), browsePushButton(browsePushButton_) {} virtual ~FileOrFolderChooserItem(){} }; class FileChooserItem : public FileOrFolderChooserItem { @@ -197,8 +206,8 @@ class FileChooserItem : public FileOrFolderChooserItem { private slots: void pushButtonReleased(); public: - FileChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : - FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_) { + FileChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw) : + FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_, mw) { QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); } }; @@ -207,20 +216,20 @@ class FolderChooserItem : public FileOrFolderChooserItem{ private slots: void pushButtonReleased(); public: - FolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : - FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_) { + FolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw) : + FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_, mw) { QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); } }; class ComboBoxItem : public MainWindowItem { public: QComboBox* comboBox; - ComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : MainWindowItem(option_,comboBox_,QString()), comboBox(comboBox_){}; + ComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : MainWindowItem(option_,comboBox_,QString()), comboBox(comboBox_){} virtual ~ComboBoxItem(){} virtual void installListeners(MainWindow *mainWindow); virtual void loadFromConfigOption()=0; virtual void saveToStringStream(std::stringstream& out)=0; - virtual bool isValid() { return true; } + //virtual bool isValid(bool & alreadyDisplayedIfWrong) { return ; } }; class LogDestinationComboBoxItem : public ComboBoxItem { public: @@ -237,13 +246,13 @@ public: optionValue=logDest; MainWindowItem::saveToStringStream(out); } - virtual bool isValid() { return true; } + //virtual bool isValid(bool & alreadyDisplayedIfWrong) { return true; } Q_OBJECT }; class LogLevelComboBoxItem : public ComboBoxItem { public: - LogLevelComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {}; + LogLevelComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {} virtual ~LogLevelComboBoxItem(){} virtual void loadFromConfigOption(){ MainWindowItem::loadFromConfigOption(); @@ -254,11 +263,11 @@ public: optionValue=comboBox->currentText().toStdString(); MainWindowItem::saveToStringStream(out); } - virtual bool isValid() { return true; } + //virtual bool isValid(bool & alreadyDisplayedIfWrong) { return true; } }; class SignatureTypeComboBoxItem : public ComboBoxItem { public: - SignatureTypeComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {}; + SignatureTypeComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {} virtual ~SignatureTypeComboBoxItem(){} virtual void loadFromConfigOption(){ MainWindowItem::loadFromConfigOption(); @@ -271,12 +280,12 @@ public: optionValue=(unsigned short)selected; MainWindowItem::saveToStringStream(out); } - virtual bool isValid() { return true; } + //virtual bool isValid(bool & alreadyDisplayedIfWrong) { return true; } }; class CheckBoxItem : public MainWindowItem { public: QCheckBox* checkBox; - CheckBoxItem(ConfigOption option_, QCheckBox* checkBox_) : MainWindowItem(option_,checkBox_,QString()), checkBox(checkBox_){}; + CheckBoxItem(ConfigOption option_, QCheckBox* checkBox_) : MainWindowItem(option_,checkBox_,QString()), checkBox(checkBox_){} virtual ~CheckBoxItem(){} virtual void installListeners(MainWindow *mainWindow); virtual void loadFromConfigOption(){ @@ -288,22 +297,25 @@ public: optionValue=checkBox->isChecked(); MainWindowItem::saveToStringStream(out); } - virtual bool isValid() { return true; } + //virtual bool isValid(bool & alreadyDisplayedIfWrong) { return true; } }; class BaseFormattedStringItem : public BaseStringItem { public: QString fieldNameTranslated; - BaseFormattedStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, QString requirementToBeValid_) : - BaseStringItem(option_, lineEdit_, requirementToBeValid_), fieldNameTranslated(fieldNameTranslated_) {}; + BaseFormattedStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, QString requirementToBeValid_, MainWindow* mw) : + BaseStringItem(option_, lineEdit_, requirementToBeValid_, mw), fieldNameTranslated(fieldNameTranslated_) {} virtual ~BaseFormattedStringItem(){} - virtual bool isValid()=0; + //virtual bool isValid(bool & alreadyDisplayedIfWrong)=0; }; class IntegerStringItem : public BaseFormattedStringItem { public: - IntegerStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be a valid integer.")) {}; + IntegerStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, MainWindow* mw) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be a valid integer."), mw) {} virtual ~IntegerStringItem(){} - virtual bool isValid(){ + virtual bool isValid(bool & alreadyDisplayedIfWrong){ + bool correct = BaseFormattedStringItem::isValid(alreadyDisplayedIfWrong); + if(!correct)return false; + alreadyDisplayedIfWrong = false; auto str=lineEdit->text(); bool ok; str.toInt(&ok); @@ -314,10 +326,13 @@ public: }; class UShortStringItem : public BaseFormattedStringItem { public: - UShortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned short integer.")) {}; + UShortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, MainWindow* mw) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned short integer."), mw) {} virtual ~UShortStringItem(){} - virtual bool isValid(){ + virtual bool isValid(bool & alreadyDisplayedIfWrong){ + bool correct = BaseFormattedStringItem::isValid(alreadyDisplayedIfWrong); + if(!correct)return false; + alreadyDisplayedIfWrong = false; auto str=lineEdit->text(); bool ok; str.toUShort(&ok); @@ -328,10 +343,13 @@ public: }; class UInt32StringItem : public BaseFormattedStringItem { public: - UInt32StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 32-bit integer.")) {}; + UInt32StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, MainWindow* mw) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 32-bit integer."), mw) {} virtual ~UInt32StringItem(){} - virtual bool isValid(){ + virtual bool isValid(bool & alreadyDisplayedIfWrong){ + bool correct = BaseFormattedStringItem::isValid(alreadyDisplayedIfWrong); + if(!correct)return false; + alreadyDisplayedIfWrong = false; auto str=lineEdit->text(); bool ok; str.toUInt(&ok); @@ -342,10 +360,13 @@ public: }; class UInt16StringItem : public BaseFormattedStringItem { public: - UInt16StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 16-bit integer.")) {}; + UInt16StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, MainWindow* mw) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 16-bit integer."), mw) {} virtual ~UInt16StringItem(){} - virtual bool isValid(){ + virtual bool isValid(bool & alreadyDisplayedIfWrong){ + bool correct = BaseFormattedStringItem::isValid(alreadyDisplayedIfWrong); + if(!correct)return false; + alreadyDisplayedIfWrong = false; auto str=lineEdit->text(); bool ok; str.toUShort(&ok); @@ -356,14 +377,14 @@ public: }; class IPAddressStringItem : public BaseFormattedStringItem { public: - IPAddressStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be an IPv4 address")) {}; - virtual bool isValid(){return true;}//todo + IPAddressStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, MainWindow* mw) : + BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be an IPv4 address"), mw) {} + //virtual bool isValid(bool & alreadyDisplayedIfWrong){return true;}//todo }; class TCPPortStringItem : public UShortStringItem { public: - TCPPortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : - UShortStringItem(option_, lineEdit_, fieldNameTranslated_) {}; + TCPPortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, MainWindow* mw) : + UShortStringItem(option_, lineEdit_, fieldNameTranslated_,mw) {} }; namespace Ui { @@ -395,7 +416,7 @@ public: void setI2PController(i2p::qt::Controller* controller_); - void highlightWrongInput(QString warningText, QWidget* widgetToFocus); + void highlightWrongInput(QString warningText, WrongInputPageEnum inputPage, QWidget* widgetToFocus); //typedef std::function DefaultValueGetter; From ca78601ada535f222198838f4d59c62bc8a8f95d Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Dec 2020 02:00:57 +0800 Subject: [PATCH 3961/6300] qt: visual fixes --- qt/i2pd_qt/mainwindow.cpp | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index de2c9fb8..3d0dc551 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -997,30 +997,33 @@ void MainWindow::backClickedFromChild() { } void MainWindow::adjustSizesAccordingToWrongLabel() { + constexpr auto HEIGHT = 581; + constexpr auto WIDTH = 707; if(ui->wrongInputLabel->isVisible()) { int dh = ui->wrongInputLabel->height()+ui->verticalLayout_7->layout()->spacing(); ui->verticalLayout_7->invalidate(); ui->wrongInputLabel->adjustSize(); ui->stackedWidget->adjustSize(); - ui->stackedWidget->setFixedHeight(531-dh); - ui->settingsPage->setFixedHeight(531-dh); - ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, 711, 531-dh)); - ui->stackedWidget->setFixedHeight(531-dh); - ui->settingsScrollArea->setFixedHeight(531-dh-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); + const auto height = HEIGHT - dh; + ui->stackedWidget->setFixedHeight(height); + ui->settingsPage->setFixedHeight(height); + ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, WIDTH, height)); + ui->stackedWidget->setFixedHeight(height); + ui->settingsScrollArea->setFixedHeight(height-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); ui->settingsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); - ui->tunnelsScrollArea->setFixedHeight(531-dh-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); + ui->tunnelsScrollArea->setFixedHeight(height-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); ui->tunnelsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); }else{ ui->verticalLayout_7->invalidate(); ui->wrongInputLabel->adjustSize(); ui->stackedWidget->adjustSize(); - ui->stackedWidget->setFixedHeight(531); - ui->settingsPage->setFixedHeight(531); - ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, 711, 531)); - ui->stackedWidget->setFixedHeight(531); - ui->settingsScrollArea->setFixedHeight(531-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); + ui->stackedWidget->setFixedHeight(HEIGHT); + ui->settingsPage->setFixedHeight(HEIGHT); + ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, WIDTH, HEIGHT)); + ui->stackedWidget->setFixedHeight(HEIGHT); + ui->settingsScrollArea->setFixedHeight(HEIGHT-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); ui->settingsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); - ui->tunnelsScrollArea->setFixedHeight(531-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); + ui->tunnelsScrollArea->setFixedHeight(HEIGHT-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); ui->tunnelsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); } } From 9c6e3ff1d774948c77e31478742a6f4096235f0d Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Dec 2020 03:39:08 +0800 Subject: [PATCH 3962/6300] qt: fixes #1582 --- qt/i2pd_qt/generalsettingswidget.ui | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qt/i2pd_qt/generalsettingswidget.ui b/qt/i2pd_qt/generalsettingswidget.ui index 7a35c0a5..e81d6f1e 100644 --- a/qt/i2pd_qt/generalsettingswidget.ui +++ b/qt/i2pd_qt/generalsettingswidget.ui @@ -495,6 +495,11 @@ QGroupBox::title { + + + None + + Error From 669fb62a541cbd28b60b095824de12a0f7237ad7 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Dec 2020 05:37:01 +0800 Subject: [PATCH 3963/6300] qt: fixed great ui pains with tunnels editing --- qt/i2pd_qt/DelayedSaveManager.h | 6 ++++-- qt/i2pd_qt/DelayedSaveManagerImpl.cpp | 28 +++++++++++++-------------- qt/i2pd_qt/DelayedSaveManagerImpl.h | 24 ++++++++++++----------- qt/i2pd_qt/I2pdQtTypes.h | 7 +++++++ qt/i2pd_qt/Saver.h | 7 +++++-- qt/i2pd_qt/SaverImpl.cpp | 10 ++++++---- qt/i2pd_qt/SaverImpl.h | 2 +- qt/i2pd_qt/i2pd_qt.pro | 3 ++- qt/i2pd_qt/mainwindow.cpp | 16 ++++++++------- qt/i2pd_qt/mainwindow.h | 18 ++++++++--------- 10 files changed, 68 insertions(+), 53 deletions(-) create mode 100644 qt/i2pd_qt/I2pdQtTypes.h diff --git a/qt/i2pd_qt/DelayedSaveManager.h b/qt/i2pd_qt/DelayedSaveManager.h index b09aa28f..23821439 100644 --- a/qt/i2pd_qt/DelayedSaveManager.h +++ b/qt/i2pd_qt/DelayedSaveManager.h @@ -2,6 +2,7 @@ #define DELAYEDSAVEMANAGER_H #include "Saver.h" +#include "I2pdQtTypes.h" class DelayedSaveManager { @@ -12,13 +13,14 @@ public: typedef unsigned int DATA_SERIAL_TYPE; - virtual void delayedSave(DATA_SERIAL_TYPE dataSerial, bool needsTunnelFocus, std::string tunnelNameToFocus)=0; + virtual void delayedSave(bool reloadAfterSave, DATA_SERIAL_TYPE dataSerial, FocusEnum focusOn, std::string tunnelNameToFocus, QWidget* widgetToFocus)=0; //returns false iff save failed virtual bool appExiting()=0; - virtual bool needsFocusOnTunnel()=0; + virtual FocusEnum getFocusOn()=0; virtual std::string& getTunnelNameToFocus()=0; + virtual QWidget* getWidgetToFocus()=0; }; #endif // DELAYEDSAVEMANAGER_H diff --git a/qt/i2pd_qt/DelayedSaveManagerImpl.cpp b/qt/i2pd_qt/DelayedSaveManagerImpl.cpp index f6704c17..b0c7c4d2 100644 --- a/qt/i2pd_qt/DelayedSaveManagerImpl.cpp +++ b/qt/i2pd_qt/DelayedSaveManagerImpl.cpp @@ -1,6 +1,7 @@ #include "DelayedSaveManagerImpl.h" DelayedSaveManagerImpl::DelayedSaveManagerImpl() : + widgetToFocus(nullptr), saver(nullptr), lastDataSerialSeen(DelayedSaveManagerImpl::INITIAL_DATA_SERIAL), lastSaveStartedTimestamp(A_VERY_OBSOLETE_TIMESTAMP), @@ -21,10 +22,12 @@ bool DelayedSaveManagerImpl::isSaverValid() { return saver != nullptr; } -void DelayedSaveManagerImpl::delayedSave(DATA_SERIAL_TYPE dataSerial, bool focusOnTunnel, std::string tunnelNameToFocus_) { +void DelayedSaveManagerImpl::delayedSave(bool reloadAfterSave, DATA_SERIAL_TYPE dataSerial, FocusEnum focusOn, std::string tunnelNameToFocus, QWidget* widgetToFocus) { if(lastDataSerialSeen==dataSerial)return; - this->focusOnTunnel = focusOnTunnel; - tunnelNameToFocus = tunnelNameToFocus_; + this->reloadAfterSave = reloadAfterSave; + this->focusOn = focusOn; + this->tunnelNameToFocus = tunnelNameToFocus; + this->widgetToFocus = widgetToFocus; lastDataSerialSeen=dataSerial; assert(isSaverValid()); TIMESTAMP_TYPE now = getTime(); @@ -42,7 +45,7 @@ bool DelayedSaveManagerImpl::appExiting() { exiting=true; thread->wakeThreadAndJoinThread(); assert(isSaverValid()); - saver->save(false, ""); + saver->save(false, FocusEnum::noFocus); return true; } @@ -71,9 +74,10 @@ void DelayedSaveThread::run() { assert(saver!=nullptr); if(saveNow) { saveNow = false; - const bool focusOnTunnel = delayedSaveManagerImpl->needsFocusOnTunnel(); + const FocusEnum focusOn = delayedSaveManagerImpl->getFocusOn(); const std::string tunnelNameToFocus = delayedSaveManagerImpl->getTunnelNameToFocus(); - saver->save(focusOnTunnel, tunnelNameToFocus); + QWidget* widgetToFocus = delayedSaveManagerImpl->getWidgetToFocus(); + saver->save(delayedSaveManagerImpl->isReloadAfterSave(), focusOn, tunnelNameToFocus, widgetToFocus); continue; } if(defer) { @@ -87,9 +91,10 @@ void DelayedSaveThread::run() { if(delayedSaveManagerImpl->isExiting())return; continue; } - const bool focusOnTunnel = delayedSaveManagerImpl->needsFocusOnTunnel(); + const FocusEnum focusOn = delayedSaveManagerImpl->getFocusOn(); const std::string tunnelNameToFocus = delayedSaveManagerImpl->getTunnelNameToFocus(); - saver->save(focusOnTunnel, tunnelNameToFocus); + QWidget* widgetToFocus = delayedSaveManagerImpl->getWidgetToFocus(); + saver->save(delayedSaveManagerImpl->isReloadAfterSave(), focusOn, tunnelNameToFocus, widgetToFocus); break; //break inner loop } } @@ -131,10 +136,3 @@ Saver* DelayedSaveManagerImpl::getSaver() { return saver; } -bool DelayedSaveManagerImpl::needsFocusOnTunnel() { - return focusOnTunnel; -} - -std::string& DelayedSaveManagerImpl::getTunnelNameToFocus() { - return tunnelNameToFocus; -} diff --git a/qt/i2pd_qt/DelayedSaveManagerImpl.h b/qt/i2pd_qt/DelayedSaveManagerImpl.h index cb1f7568..c9a77c31 100644 --- a/qt/i2pd_qt/DelayedSaveManagerImpl.h +++ b/qt/i2pd_qt/DelayedSaveManagerImpl.h @@ -7,14 +7,14 @@ #include #include -#include "mainwindow.h" +#include "I2pdQtTypes.h" #include "DelayedSaveManager.h" #include "Saver.h" class DelayedSaveManagerImpl; +class Saver; -class DelayedSaveThread : public QThread -{ +class DelayedSaveThread : public QThread { Q_OBJECT public: @@ -42,14 +42,17 @@ private: volatile TIMESTAMP_TYPE wakeTime; }; -class DelayedSaveManagerImpl : public DelayedSaveManager -{ +class DelayedSaveManagerImpl : public DelayedSaveManager { + FocusEnum focusOn; + std::string tunnelNameToFocus; + QWidget* widgetToFocus; + bool reloadAfterSave; public: DelayedSaveManagerImpl(); virtual ~DelayedSaveManagerImpl(); virtual void setSaver(Saver* saver); virtual void start(); - virtual void delayedSave(DATA_SERIAL_TYPE dataSerial, bool focusOnTunnel, std::string tunnelNameToFocus); + virtual void delayedSave(bool reloadAfterSave, DATA_SERIAL_TYPE dataSerial, FocusEnum focusOn, std::string tunnelNameToFocus, QWidget* widgetToFocus); virtual bool appExiting(); typedef DelayedSaveThread::TIMESTAMP_TYPE TIMESTAMP_TYPE; @@ -59,8 +62,10 @@ public: Saver* getSaver(); static TIMESTAMP_TYPE getTime(); - bool needsFocusOnTunnel(); - std::string& getTunnelNameToFocus(); + bool isReloadAfterSave() { return reloadAfterSave; } + FocusEnum getFocusOn() { return focusOn; } + std::string& getTunnelNameToFocus() { return tunnelNameToFocus; } + QWidget* getWidgetToFocus() { return widgetToFocus; } private: Saver* saver; @@ -74,9 +79,6 @@ private: bool exiting; DelayedSaveThread* thread; void wakeThreadAndJoinThread(); - - bool focusOnTunnel; - std::string tunnelNameToFocus; }; #endif // DELAYEDSAVEMANAGERIMPL_H diff --git a/qt/i2pd_qt/I2pdQtTypes.h b/qt/i2pd_qt/I2pdQtTypes.h new file mode 100644 index 00000000..c7b1917a --- /dev/null +++ b/qt/i2pd_qt/I2pdQtTypes.h @@ -0,0 +1,7 @@ +#ifndef I2PDQTTYPES_H +#define I2PDQTTYPES_H + +enum WrongInputPageEnum { generalSettingsPage, tunnelsSettingsPage }; +enum FocusEnum { noFocus, focusOnTunnelName, focusOnWidget }; + +#endif // I2PDQTTYPES_H diff --git a/qt/i2pd_qt/Saver.h b/qt/i2pd_qt/Saver.h index cefe0220..bf971fb6 100644 --- a/qt/i2pd_qt/Saver.h +++ b/qt/i2pd_qt/Saver.h @@ -4,6 +4,9 @@ #include #include #include +class QWidget; + +#include "I2pdQtTypes.h" class Saver : public QObject { @@ -11,8 +14,8 @@ class Saver : public QObject public: Saver(); - //false iff failures - virtual bool save(const bool focusOnTunnel, const std::string& tunnelNameToFocus)=0; + //FocusEnum::focusNone iff failures //??? wtf + virtual bool save(bool reloadAfterSave, const FocusEnum focusOn, const std::string& tunnelNameToFocus="", QWidget* widgetToFocus=nullptr)=0; signals: void reloadTunnelsConfigAndUISignal(const QString); diff --git a/qt/i2pd_qt/SaverImpl.cpp b/qt/i2pd_qt/SaverImpl.cpp index f35ef5b7..fee2526b 100644 --- a/qt/i2pd_qt/SaverImpl.cpp +++ b/qt/i2pd_qt/SaverImpl.cpp @@ -15,7 +15,7 @@ SaverImpl::SaverImpl(MainWindow *mainWindowPtr_, QList * config SaverImpl::~SaverImpl() {} -bool SaverImpl::save(const bool focusOnTunnel, const std::string& tunnelNameToFocus) { +bool SaverImpl::save(bool reloadAfterSave, const FocusEnum focusOn, const std::string& tunnelNameToFocus, QWidget* widgetToFocus) { //save main config { std::stringstream out; @@ -59,12 +59,14 @@ bool SaverImpl::save(const bool focusOnTunnel, const std::string& tunnelNameToFo outfile.close(); } - //reload saved configs + if(reloadAfterSave) { + //reload saved configs #if 0 - i2p::client::context.ReloadConfig(); + i2p::client::context.ReloadConfig(); #endif - if(focusOnTunnel) emit reloadTunnelsConfigAndUISignal(QString::fromStdString(tunnelNameToFocus)); + if(reloadAfterSave) emit reloadTunnelsConfigAndUISignal(focusOn==FocusEnum::focusOnTunnelName?QString::fromStdString(tunnelNameToFocus):""); + } return true; } diff --git a/qt/i2pd_qt/SaverImpl.h b/qt/i2pd_qt/SaverImpl.h index c44877a2..d20f1bbf 100644 --- a/qt/i2pd_qt/SaverImpl.h +++ b/qt/i2pd_qt/SaverImpl.h @@ -19,7 +19,7 @@ class SaverImpl : public Saver public: SaverImpl(MainWindow *mainWindowPtr_, QList * configItems_, std::map* tunnelConfigs_); virtual ~SaverImpl(); - virtual bool save(const bool focusOnTunnel, const std::string& tunnelNameToFocus); + virtual bool save(bool reloadAfterSave, const FocusEnum focusOn, const std::string& tunnelNameToFocus, QWidget* widgetToFocus); void setConfPath(QString& confpath_); void setTunnelsConfPath(QString& tunconfpath_); private: diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index c4e55f53..b5830d4c 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -64,7 +64,8 @@ HEADERS += DaemonQT.h mainwindow.h \ ../../daemon/UPnP.h \ AboutDialog.h \ BuildDateTimeQt.h \ - I2pdQtUtil.h + I2pdQtUtil.h \ + I2pdQtTypes.h INCLUDEPATH += ../../libi2pd INCLUDEPATH += ../../libi2pd_client diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 3d0dc551..2c6e7523 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -783,7 +783,7 @@ void MainWindow::deleteTunnelFromUI(std::string tunnelName, TunnelConfig* cnf) { } /** returns false iff not valid items present and save was aborted */ -bool MainWindow::saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocus){ +bool MainWindow::saveAllConfigs(bool reloadAfterSave, FocusEnum focusOn, std::string tunnelNameToFocus, QWidget* widgetToFocus){ QString cannotSaveSettings = QApplication::tr("Cannot save settings."); programOptionsWriterCurrentSection=""; /*if(!logFileNameOption->lineEdit->text().trimmed().isEmpty())logOption->optionValue=boost::any(std::string("file")); @@ -803,7 +803,7 @@ bool MainWindow::saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocu return false; } } - delayedSaveManagerPtr->delayedSave(++dataSerial, focusOnTunnel, tunnelNameToFocus);//TODO does dataSerial work? //FIXME + delayedSaveManagerPtr->delayedSave(reloadAfterSave, ++dataSerial, focusOn, tunnelNameToFocus, widgetToFocus);//TODO does dataSerial work? //FIXME //onLoggingOptionsChange(); return true; @@ -841,7 +841,7 @@ void MainWindow::updated() { bool correct = applyTunnelsUiToConfigs(); if(!correct) return; - saveAllConfigs(false); + saveAllConfigs(false, FocusEnum::noFocus); } void MainWindowItem::installListeners(MainWindow *mainWindow) {} @@ -916,11 +916,11 @@ bool MainWindow::applyTunnelsUiToConfigs() { return true; } -void MainWindow::reloadTunnelsConfigAndUI_QString(const QString tunnelNameToFocus) { - reloadTunnelsConfigAndUI(tunnelNameToFocus.toStdString()); +void MainWindow::reloadTunnelsConfigAndUI_QString(QString tunnelNameToFocus) { + reloadTunnelsConfigAndUI(tunnelNameToFocus.toStdString(), nullptr); } -void MainWindow::reloadTunnelsConfigAndUI(std::string tunnelNameToFocus) { +void MainWindow::reloadTunnelsConfigAndUI(std::string tunnelNameToFocus, QWidget* widgetToFocus) { deleteTunnelForms(); for (std::map::iterator it=tunnelConfigs.begin(); it!=tunnelConfigs.end(); ++it) { TunnelConfig* tunconf = it->second; @@ -937,8 +937,10 @@ void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::updated(std::string ol std::map::const_iterator it=mainWindow->tunnelConfigs.find(oldName); if(it!=mainWindow->tunnelConfigs.end())mainWindow->tunnelConfigs.erase(it); mainWindow->tunnelConfigs[tunConf->getName()]=tunConf; + mainWindow->saveAllConfigs(true, FocusEnum::focusOnTunnelName, tunConf->getName()); } - mainWindow->saveAllConfigs(true, tunConf->getName()); + else + mainWindow->saveAllConfigs(false, FocusEnum::noFocus); } void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::needsDeleting(std::string oldName){ diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 34228e43..241d4efb 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -1,8 +1,6 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H -enum WrongInputPageEnum { generalSettingsPage, tunnelsSettingsPage }; - #include #include #include @@ -136,7 +134,7 @@ public: std::string optName=""; if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); optName+=option.option.toStdString(); - qDebug() << "Writing option" << optName.c_str() << "of type" << rtti.c_str(); + //qDebug() << "Writing option" << optName.c_str() << "of type" << rtti.c_str(); std::string sectionAsStdStr = option.section.toStdString(); if(!option.section.isEmpty() && sectionAsStdStr!=programOptionsWriterCurrentSection) { @@ -541,12 +539,12 @@ protected: public slots: /** returns false iff not valid items present and save was aborted */ - bool saveAllConfigs(bool focusOnTunnel, std::string tunnelNameToFocus=""); - void reloadTunnelsConfigAndUI(std::string tunnelNameToFocus); + bool saveAllConfigs(bool reloadAfterSave, FocusEnum focusOn, std::string tunnelNameToFocus="", QWidget* widgetToFocus=nullptr); + void reloadTunnelsConfigAndUI(std::string tunnelNameToFocus, QWidget* widgetToFocus); + void reloadTunnelsConfigAndUI() { reloadTunnelsConfigAndUI("", nullptr); } //focus none - void reloadTunnelsConfigAndUI() { reloadTunnelsConfigAndUI(""); } - void reloadTunnelsConfigAndUI_QString(const QString tunnelNameToFocus); + void reloadTunnelsConfigAndUI_QString(QString tunnelNameToFocus); void addServerTunnelPushButtonReleased(); void addClientTunnelPushButtonReleased(); @@ -651,7 +649,7 @@ private: tunnelConfigs.erase(it); delete tc; } - saveAllConfigs(false); + saveAllConfigs(true, FocusEnum::noFocus); } std::string GenerateNewTunnelName() { @@ -688,7 +686,7 @@ private: sigType, cryptoType); - saveAllConfigs(true, name); + saveAllConfigs(true, FocusEnum::focusOnTunnelName, name); } void CreateDefaultServerTunnel() {//TODO dedup default values with ReadTunnelsConfig() and with ClientContext.cpp::ReadTunnels () @@ -726,7 +724,7 @@ private: cryptoType); - saveAllConfigs(true, name); + saveAllConfigs(true, FocusEnum::focusOnTunnelName, name); } void ReadTunnelsConfig() //TODO deduplicate the code with ClientContext.cpp::ReadTunnels () From 83b10fba62b31395f82c0559665e532022308917 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Dec 2020 05:45:11 +0800 Subject: [PATCH 3964/6300] qt: added assert.h - it is needed for ci circumstances --- qt/i2pd_qt/DelayedSaveManagerImpl.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qt/i2pd_qt/DelayedSaveManagerImpl.cpp b/qt/i2pd_qt/DelayedSaveManagerImpl.cpp index b0c7c4d2..bad6fdb5 100644 --- a/qt/i2pd_qt/DelayedSaveManagerImpl.cpp +++ b/qt/i2pd_qt/DelayedSaveManagerImpl.cpp @@ -1,5 +1,7 @@ #include "DelayedSaveManagerImpl.h" +#include + DelayedSaveManagerImpl::DelayedSaveManagerImpl() : widgetToFocus(nullptr), saver(nullptr), From 370ab6307a28552ee776e53710c4bfad9534bc36 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Dec 2020 06:34:22 +0800 Subject: [PATCH 3965/6300] qt: fixes #1581 --- qt/i2pd_qt/mainwindow.cpp | 31 +++++++++++++++++-------------- qt/i2pd_qt/mainwindow.h | 16 +++++++++------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 2c6e7523..1cebc230 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -177,9 +177,9 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren # define OPTION(section,option,defaultValueGetter) ConfigOption(QString(section),QString(option)) - initFileChooser( OPTION("","conf",[](){return "";}), uiSettings->configFileLineEdit, uiSettings->configFileBrowsePushButton); - initFileChooser( OPTION("","tunconf",[](){return "";}), uiSettings->tunnelsConfigFileLineEdit, uiSettings->tunnelsConfigFileBrowsePushButton); - initFileChooser( OPTION("","pidfile",[]{return "";}), uiSettings->pidFileLineEdit, uiSettings->pidFileBrowsePushButton); + initFileChooser( OPTION("","conf",[](){return "";}), uiSettings->configFileLineEdit, uiSettings->configFileBrowsePushButton, false); + initFileChooser( OPTION("","tunconf",[](){return "";}), uiSettings->tunnelsConfigFileLineEdit, uiSettings->tunnelsConfigFileBrowsePushButton, false); + initFileChooser( OPTION("","pidfile",[]{return "";}), uiSettings->pidFileLineEdit, uiSettings->pidFileBrowsePushButton, false); uiSettings->logDestinationComboBox->clear(); uiSettings->logDestinationComboBox->insertItems(0, QStringList() @@ -189,7 +189,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren ); initLogDestinationCombobox( OPTION("","log",[]{return "";}), uiSettings->logDestinationComboBox); - logFileNameOption=initFileChooser( OPTION("","logfile",[]{return "";}), uiSettings->logFileLineEdit, uiSettings->logFileBrowsePushButton); + logFileNameOption=initFileChooser( OPTION("","logfile",[]{return "";}), uiSettings->logFileLineEdit, uiSettings->logFileBrowsePushButton, false); initLogLevelCombobox(OPTION("","loglevel",[]{return "";}), uiSettings->logLevelComboBox); initCheckBox( OPTION("","logclftime",[]{return "false";}), uiSettings->logclftimeCheckBox);//"Write full CLF-formatted date and time to log (default: write only time)" initFolderChooser( OPTION("","datadir",[]{return "";}), uiSettings->dataFolderLineEdit, uiSettings->dataFolderBrowsePushButton); @@ -232,7 +232,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initIPAddressBox( OPTION("httpproxy","address",[]{return "";}), uiSettings->httpProxyAddressLineEdit, tr("HTTP proxy -> IP address")); initTCPPortBox( OPTION("httpproxy","port",[]{return "4444";}), uiSettings->httpProxyPortLineEdit, tr("HTTP proxy -> Port")); initCheckBox( OPTION("httpproxy","addresshelper",[]{return "true";}), uiSettings->httpProxyAddressHelperCheckBox);//Enable address helper (jump). true by default - initFileChooser( OPTION("httpproxy","keys",[]{return "";}), uiSettings->httpProxyKeyFileLineEdit, uiSettings->httpProxyKeyFilePushButton); + initFileChooser( OPTION("httpproxy","keys",[]{return "";}), uiSettings->httpProxyKeyFileLineEdit, uiSettings->httpProxyKeyFilePushButton, false); initSignatureTypeCombobox(OPTION("httpproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_httpPorxySignatureType); initStringBox( OPTION("httpproxy","inbound.length",[]{return "3";}), uiSettings->httpProxyInboundTunnelsLenLineEdit); initStringBox( OPTION("httpproxy","inbound.quantity",[]{return "5";}), uiSettings->httpProxyInboundTunnQuantityLineEdit); @@ -245,7 +245,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initCheckBox( OPTION("socksproxy","enabled",[]{return "";}), uiSettings->socksProxyEnabledCheckBox); initIPAddressBox( OPTION("socksproxy","address",[]{return "";}), uiSettings->socksProxyAddressLineEdit, tr("Socks proxy -> IP address")); initTCPPortBox( OPTION("socksproxy","port",[]{return "4447";}), uiSettings->socksProxyPortLineEdit, tr("Socks proxy -> Port")); - initFileChooser( OPTION("socksproxy","keys",[]{return "";}), uiSettings->socksProxyKeyFileLineEdit, uiSettings->socksProxyKeyFilePushButton); + initFileChooser( OPTION("socksproxy","keys",[]{return "";}), uiSettings->socksProxyKeyFileLineEdit, uiSettings->socksProxyKeyFilePushButton, false); initSignatureTypeCombobox(OPTION("socksproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_socksProxySignatureType); initStringBox( OPTION("socksproxy","inbound.length",[]{return "";}), uiSettings->socksProxyInboundTunnelsLenLineEdit); initStringBox( OPTION("socksproxy","inbound.quantity",[]{return "";}), uiSettings->socksProxyInboundTunnQuantityLineEdit); @@ -275,8 +275,8 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initIPAddressBox( OPTION("i2pcontrol","address",[]{return "";}), uiSettings->i2pControlAddressLineEdit, tr("I2PControl -> IP address")); initTCPPortBox( OPTION("i2pcontrol","port",[]{return "7650";}), uiSettings->i2pControlPortLineEdit, tr("I2PControl -> Port")); initStringBox( OPTION("i2pcontrol","password",[]{return "";}), uiSettings->i2pControlPasswordLineEdit); - initFileChooser( OPTION("i2pcontrol","cert",[]{return "i2pcontrol.crt.pem";}), uiSettings->i2pControlCertFileLineEdit, uiSettings->i2pControlCertFileBrowsePushButton); - initFileChooser( OPTION("i2pcontrol","key",[]{return "i2pcontrol.key.pem";}), uiSettings->i2pControlKeyFileLineEdit, uiSettings->i2pControlKeyFileBrowsePushButton); + initFileChooser( OPTION("i2pcontrol","cert",[]{return "i2pcontrol.crt.pem";}), uiSettings->i2pControlCertFileLineEdit, uiSettings->i2pControlCertFileBrowsePushButton, true); + initFileChooser( OPTION("i2pcontrol","key",[]{return "i2pcontrol.key.pem";}), uiSettings->i2pControlKeyFileLineEdit, uiSettings->i2pControlKeyFileBrowsePushButton, true); initCheckBox( OPTION("upnp","enabled",[]{return "true";}), uiSettings->enableUPnPCheckBox); initStringBox( OPTION("upnp","name",[]{return "I2Pd";}), uiSettings->upnpNameLineEdit); @@ -284,9 +284,9 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren initCheckBox( OPTION("precomputation","elgamal",[]{return "false";}), uiSettings->useElGamalPrecomputedTablesCheckBox); initCheckBox( OPTION("reseed","verify",[]{return "";}), uiSettings->reseedVerifyCheckBox); - initFileChooser( OPTION("reseed","file",[]{return "";}), uiSettings->reseedFileLineEdit, uiSettings->reseedFileBrowsePushButton); + initFileChooser( OPTION("reseed","file",[]{return "";}), uiSettings->reseedFileLineEdit, uiSettings->reseedFileBrowsePushButton, true); initStringBox( OPTION("reseed","urls",[]{return "";}), uiSettings->reseedURLsLineEdit); - initFileChooser( OPTION("reseed","zipfile",[]{return "";}), uiSettings->reseedZipFileLineEdit, uiSettings->reseedZipFileBrowsePushButton); //Path to local .zip file to reseed from + initFileChooser( OPTION("reseed","zipfile",[]{return "";}), uiSettings->reseedZipFileLineEdit, uiSettings->reseedZipFileBrowsePushButton, true); //Path to local .zip file to reseed from initUInt16Box( OPTION("reseed","threshold",[]{return "25";}), uiSettings->reseedThresholdNumberLineEdit, tr("reseedThreshold")); //Minimum number of known routers before requesting reseed. 25 by default initStringBox( OPTION("reseed","proxy",[]{return "";}), uiSettings->reseedProxyLineEdit);//URL for https/socks reseed proxy @@ -649,15 +649,15 @@ MainWindow::~MainWindow() //QMessageBox::information(0, "Debug", "mw destructor 2"); } -FileChooserItem* MainWindow::initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton){ +FileChooserItem* MainWindow::initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton, bool requireExistingFile){ FileChooserItem* retVal; - retVal=new FileChooserItem(option, fileNameLineEdit, fileBrowsePushButton, this); + retVal=new FileChooserItem(option, fileNameLineEdit, fileBrowsePushButton, this, requireExistingFile); MainWindowItem* super=retVal; configItems.append(super); return retVal; } void MainWindow::initFolderChooser(ConfigOption option, QLineEdit* folderLineEdit, QPushButton* folderBrowsePushButton){ - configItems.append(new FolderChooserItem(option, folderLineEdit, folderBrowsePushButton, this)); + configItems.append(new FolderChooserItem(option, folderLineEdit, folderBrowsePushButton, this, true)); } /*void MainWindow::initCombobox(ConfigOption option, QComboBox* comboBox){ configItems.append(new ComboBoxItem(option, comboBox)); @@ -811,11 +811,14 @@ bool MainWindow::saveAllConfigs(bool reloadAfterSave, FocusEnum focusOn, std::st void FileChooserItem::pushButtonReleased() { QString fileName = lineEdit->text().trimmed(); - fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), fileName, tr("All Files (*.*)")); + fileName = requireExistingFile ? + QFileDialog::getOpenFileName(nullptr, tr("Open File"), fileName, tr("All Files (*.*)")) : + QFileDialog::getSaveFileName(nullptr, tr("Open File"), fileName, tr("All Files (*.*)")); if(fileName.length()>0)lineEdit->setText(fileName); } void FolderChooserItem::pushButtonReleased() { QString fileName = lineEdit->text().trimmed(); + assert(requireExistingFile); fileName = QFileDialog::getExistingDirectory(nullptr, tr("Open Folder"), fileName); if(fileName.length()>0)lineEdit->setText(fileName); } diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 241d4efb..6843a98c 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -193,10 +193,12 @@ public: virtual bool isValid(bool & alreadyDisplayedIfWrong); }; class FileOrFolderChooserItem : public BaseStringItem { +protected: + const bool requireExistingFile; public: QPushButton* browsePushButton; - FileOrFolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw) : - BaseStringItem(option_, lineEdit_, QString(), mw), browsePushButton(browsePushButton_) {} + FileOrFolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw, bool requireExistingFile_) : + BaseStringItem(option_, lineEdit_, QString(), mw), requireExistingFile(requireExistingFile_), browsePushButton(browsePushButton_) {} virtual ~FileOrFolderChooserItem(){} }; class FileChooserItem : public FileOrFolderChooserItem { @@ -204,8 +206,8 @@ class FileChooserItem : public FileOrFolderChooserItem { private slots: void pushButtonReleased(); public: - FileChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw) : - FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_, mw) { + FileChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw, bool requireExistingFile) : + FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_, mw, requireExistingFile) { QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); } }; @@ -214,8 +216,8 @@ class FolderChooserItem : public FileOrFolderChooserItem{ private slots: void pushButtonReleased(); public: - FolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw) : - FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_, mw) { + FolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw, bool requireExistingFolder) : + FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_, mw, requireExistingFolder) { QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); } }; @@ -519,7 +521,7 @@ protected: //LogDestinationComboBoxItem* logOption; FileChooserItem* logFileNameOption; - FileChooserItem* initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton); + FileChooserItem* initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton, bool requireExistingFile); void initFolderChooser(ConfigOption option, QLineEdit* folderLineEdit, QPushButton* folderBrowsePushButton); //void initCombobox(ConfigOption option, QComboBox* comboBox); void initLogDestinationCombobox(ConfigOption option, QComboBox* comboBox); From d4b648510275705b40acdd9d93817cea48354fc1 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Dec 2020 06:57:49 +0800 Subject: [PATCH 3966/6300] qt: small improv --- qt/i2pd_qt/i2pd_qt.pro | 3 +++ qt/i2pd_qt/mainwindow.h | 7 ++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index b5830d4c..c445348a 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -9,7 +9,10 @@ CONFIG += strict_c++ c++11 CONFIG(debug, debug|release) { message(Debug build) + + # do not redirect logging to std::ostream and to Log pane DEFINES += DEBUG_WITH_DEFAULT_LOGGING + I2PDMAKE += DEBUG=yes } else { message(Release build) diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 6843a98c..412a060e 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -118,7 +118,7 @@ public: std::string optName=""; if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); optName+=option.option.toStdString(); - qDebug() << "loadFromConfigOption[" << optName.c_str() << "]"; + //qDebug() << "loadFromConfigOption[" << optName.c_str() << "]"; boost::any programOption; i2p::config::GetOptionAsAny(optName, programOption); optionValue=programOption.empty()?boost::any(std::string("")) @@ -290,7 +290,7 @@ public: virtual void installListeners(MainWindow *mainWindow); virtual void loadFromConfigOption(){ MainWindowItem::loadFromConfigOption(); - qDebug() << "setting value for checkbox " << checkBox->text(); + //qDebug() << "setting value for checkbox " << checkBox->text(); checkBox->setChecked(boost::any_cast(optionValue)); } virtual void saveToStringStream(std::stringstream& out){ @@ -769,16 +769,13 @@ private: std::string dest; if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); - std::cout << "had read tunnel dest: " << dest << std::endl; } int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); - std::cout << "had read tunnel port: " << port << std::endl; // optional params std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); int cryptoType = section.second.get(I2P_CLIENT_TUNNEL_CRYPTO_TYPE, 0); int destinationPort = section.second.get(I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); - std::cout << "had read tunnel destinationPort: " << destinationPort << std::endl; i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); // I2CP std::map options; From 242e3d007c0a733598a7486556ec05a0d6d292c8 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Dec 2020 07:17:01 +0800 Subject: [PATCH 3967/6300] qt: fixes #1529 --- qt/i2pd_qt/mainwindow.cpp | 6 +++--- qt/i2pd_qt/mainwindow.h | 23 +++++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 1cebc230..50698e7f 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -177,7 +177,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren # define OPTION(section,option,defaultValueGetter) ConfigOption(QString(section),QString(option)) - initFileChooser( OPTION("","conf",[](){return "";}), uiSettings->configFileLineEdit, uiSettings->configFileBrowsePushButton, false); + initFileChooser( OPTION("","conf",[](){return "";}), uiSettings->configFileLineEdit, uiSettings->configFileBrowsePushButton, false, true); initFileChooser( OPTION("","tunconf",[](){return "";}), uiSettings->tunnelsConfigFileLineEdit, uiSettings->tunnelsConfigFileBrowsePushButton, false); initFileChooser( OPTION("","pidfile",[]{return "";}), uiSettings->pidFileLineEdit, uiSettings->pidFileBrowsePushButton, false); @@ -649,9 +649,9 @@ MainWindow::~MainWindow() //QMessageBox::information(0, "Debug", "mw destructor 2"); } -FileChooserItem* MainWindow::initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton, bool requireExistingFile){ +FileChooserItem* MainWindow::initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton, bool requireExistingFile, bool readOnly){ FileChooserItem* retVal; - retVal=new FileChooserItem(option, fileNameLineEdit, fileBrowsePushButton, this, requireExistingFile); + retVal=new FileChooserItem(option, fileNameLineEdit, fileBrowsePushButton, this, requireExistingFile, readOnly); MainWindowItem* super=retVal; configItems.append(super); return retVal; diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 412a060e..9d848acf 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -102,12 +102,14 @@ class MainWindow; class MainWindowItem : public QObject { Q_OBJECT +private: ConfigOption option; QWidget* widgetToFocus; QString requirementToBeValid; + const bool readOnly; public: - MainWindowItem(ConfigOption option_, QWidget* widgetToFocus_, QString requirementToBeValid_) : - option(option_), widgetToFocus(widgetToFocus_), requirementToBeValid(requirementToBeValid_) {} + MainWindowItem(ConfigOption option_, QWidget* widgetToFocus_, QString requirementToBeValid_, bool readOnly_=false) : + option(option_), widgetToFocus(widgetToFocus_), requirementToBeValid(requirementToBeValid_), readOnly(readOnly_) {} QWidget* getWidgetToFocus(){return widgetToFocus;} QString& getRequirementToBeValid() { return requirementToBeValid; } ConfigOption& getConfigOption() { return option; } @@ -125,6 +127,7 @@ public: :boost::any_cast(programOption).value(); } virtual void saveToStringStream(std::stringstream& out){ + if(readOnly)return; //should readOnly items (conf=) error somewhere, instead of silently skipping save? if(isType(optionValue)) { std::string v = boost::any_cast(optionValue); if(v.empty())return; @@ -170,8 +173,8 @@ class BaseStringItem : public MainWindowItem { public: QLineEdit* lineEdit; MainWindow *mainWindow; - BaseStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString requirementToBeValid_, MainWindow* mainWindow_): - MainWindowItem(option_, lineEdit_, requirementToBeValid_), + BaseStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString requirementToBeValid_, MainWindow* mainWindow_, bool readOnly=false): + MainWindowItem(option_, lineEdit_, requirementToBeValid_, readOnly), lineEdit(lineEdit_), mainWindow(mainWindow_) {}; @@ -197,8 +200,8 @@ protected: const bool requireExistingFile; public: QPushButton* browsePushButton; - FileOrFolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw, bool requireExistingFile_) : - BaseStringItem(option_, lineEdit_, QString(), mw), requireExistingFile(requireExistingFile_), browsePushButton(browsePushButton_) {} + FileOrFolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw, bool requireExistingFile_, bool readOnly) : + BaseStringItem(option_, lineEdit_, QString(), mw, readOnly), requireExistingFile(requireExistingFile_), browsePushButton(browsePushButton_) {} virtual ~FileOrFolderChooserItem(){} }; class FileChooserItem : public FileOrFolderChooserItem { @@ -206,8 +209,8 @@ class FileChooserItem : public FileOrFolderChooserItem { private slots: void pushButtonReleased(); public: - FileChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw, bool requireExistingFile) : - FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_, mw, requireExistingFile) { + FileChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw, bool requireExistingFile, bool readOnly) : + FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_, mw, requireExistingFile, readOnly) { QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); } }; @@ -217,7 +220,7 @@ private slots: void pushButtonReleased(); public: FolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_, MainWindow* mw, bool requireExistingFolder) : - FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_, mw, requireExistingFolder) { + FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_, mw, requireExistingFolder, false) { QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); } }; @@ -521,7 +524,7 @@ protected: //LogDestinationComboBoxItem* logOption; FileChooserItem* logFileNameOption; - FileChooserItem* initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton, bool requireExistingFile); + FileChooserItem* initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton, bool requireExistingFile, bool readOnly=false); void initFolderChooser(ConfigOption option, QLineEdit* folderLineEdit, QPushButton* folderBrowsePushButton); //void initCombobox(ConfigOption option, QComboBox* comboBox); void initLogDestinationCombobox(ConfigOption option, QComboBox* comboBox); From d7342586a696f17bf3ec293f115994d85c3331ed Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Dec 2020 07:44:37 +0800 Subject: [PATCH 3968/6300] qt: fixes #1593 --- qt/i2pd_qt/i2pd_qt.pro | 2 ++ qt/i2pd_qt/mainwindow.cpp | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro index c445348a..5aefe3f5 100644 --- a/qt/i2pd_qt/i2pd_qt.pro +++ b/qt/i2pd_qt/i2pd_qt.pro @@ -13,9 +13,11 @@ CONFIG(debug, debug|release) { # do not redirect logging to std::ostream and to Log pane DEFINES += DEBUG_WITH_DEFAULT_LOGGING + DEFINES += I2PD_QT_DEBUG I2PDMAKE += DEBUG=yes } else { message(Release build) + DEFINES += I2PD_QT_RELEASE I2PDMAKE += DEBUG=no } diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 50698e7f..386b06d7 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -188,6 +188,9 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren << QApplication::translate("MainWindow", "file", 0) ); initLogDestinationCombobox( OPTION("","log",[]{return "";}), uiSettings->logDestinationComboBox); +#ifdef I2PD_QT_RELEASE + uiSettings->logDestinationComboBox->setEnabled(false); // #1593 +#endif logFileNameOption=initFileChooser( OPTION("","logfile",[]{return "";}), uiSettings->logFileLineEdit, uiSettings->logFileBrowsePushButton, false); initLogLevelCombobox(OPTION("","loglevel",[]{return "";}), uiSettings->logLevelComboBox); @@ -325,7 +328,15 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren # undef OPTION //widgetlocks.add(new widgetlock(widget,lockbtn)); + + + // #1593 +#ifdef I2PD_QT_RELEASE + uiSettings->logDestComboEditPushButton->setEnabled(false); +#else widgetlocks.add(new widgetlock(uiSettings->logDestinationComboBox,uiSettings->logDestComboEditPushButton)); +#endif + widgetlocks.add(new widgetlock(uiSettings->logLevelComboBox,uiSettings->logLevelComboEditPushButton)); widgetlocks.add(new widgetlock(uiSettings->comboBox_httpPorxySignatureType,uiSettings->httpProxySignTypeComboEditPushButton)); widgetlocks.add(new widgetlock(uiSettings->comboBox_socksProxySignatureType,uiSettings->socksProxySignTypeComboEditPushButton)); From 8c61e7d227c387f2edd54d97121fb5f78324ce75 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 17 Dec 2020 18:58:30 -0500 Subject: [PATCH 3969/6300] replace LeaseSet completely if store type changes --- libi2pd/Destination.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 8f3b495a..52ede959 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -379,7 +379,8 @@ namespace client LogPrint (eLogDebug, "Destination: Remote LeaseSet"); std::lock_guard lock(m_RemoteLeaseSetsMutex); auto it = m_RemoteLeaseSets.find (key); - if (it != m_RemoteLeaseSets.end ()) + if (it != m_RemoteLeaseSets.end () && + it->second->GetStoreType () == buf[DATABASE_STORE_TYPE_OFFSET]) // update only if same type { leaseSet = it->second; if (leaseSet->IsNewer (buf + offset, len - offset)) @@ -399,6 +400,7 @@ namespace client } else { + // add or replace if (buf[DATABASE_STORE_TYPE_OFFSET] == i2p::data::NETDB_STORE_TYPE_LEASESET) leaseSet = std::make_shared (buf + offset, len - offset); // LeaseSet else From 0b084956e610e3c730b66aa4d2442275b6e67404 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Dec 2020 09:04:40 +0800 Subject: [PATCH 3970/6300] qt: stream.kill hrefs done - step to completion of #914 --- qt/i2pd_qt/mainwindow.cpp | 56 ++++++++++++++++++++++++++++++++++----- qt/i2pd_qt/mainwindow.h | 1 + 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 386b06d7..034b9c4a 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -45,6 +45,7 @@ std::string programOptionsWriterCurrentSection; MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *parent) : QMainWindow(parent) + ,currentLocalDestinationB32("") ,logStream(logStream_) ,delayedSaveManagerPtr(new DelayedSaveManagerImpl()) ,dataSerial(DelayedSaveManagerImpl::INITIAL_DATA_SERIAL) @@ -135,6 +136,7 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren //childTextBrowser->setOpenExternalLinks(false); childTextBrowser->setOpenLinks(false); connect(textBrowser, SIGNAL(anchorClicked(const QUrl&)), this, SLOT(anchorClickedHandler(const QUrl&))); + connect(childTextBrowser, SIGNAL(anchorClicked(const QUrl&)), this, SLOT(anchorClickedHandler(const QUrl&))); pageWithBackButton = new PageWithBackButton(this, childTextBrowser); ui->verticalLayout_2->addWidget(pageWithBackButton); pageWithBackButton->hide(); @@ -992,20 +994,60 @@ void MainWindow::anchorClickedHandler(const QUrl & link) { qDebug()< params; + i2p::http::URL url; + url.parse(str.toStdString()); + url.parse_query(params); + const std::string page = params["page"]; + const std::string cmd = params["cmd"]; + if(page == "local_destination") { + std::string b32 = params["b32"]; + currentLocalDestinationB32 = b32; pageWithBackButton->show(); textBrowser->hide(); std::stringstream s; - std::string strstd = str.toStdString(); + std::string strstd = currentLocalDestinationB32; i2p::http::ShowLocalDestination(s,strstd,0); childTextBrowser->setHtml(QString::fromStdString(s.str())); } + if(cmd == "closestream") { + std::string b32 = params["b32"]; + uint32_t streamID = std::stoul(params["streamID"], nullptr); + + i2p::data::IdentHash ident; + ident.FromBase32 (b32); + auto dest = i2p::client::context.FindLocalDestination (ident); + + if (streamID) { + if (dest) { + if(dest->DeleteStream (streamID)) + QMessageBox::information( + this, + QApplication::tr("Success"), + QApplication::tr("SUCCESS: Stream closed")); + else + QMessageBox::critical( + this, + QApplication::tr("Error"), + QApplication::tr("ERROR: Stream not found or already was closed")); + } + else + QMessageBox::critical( + this, + QApplication::tr("Error"), + QApplication::tr("ERROR: Destination not found")); + } + else + QMessageBox::critical( + this, + QApplication::tr("Error"), + QApplication::tr("ERROR: StreamID is null")); + std::stringstream s; + std::string strstd = currentLocalDestinationB32; + i2p::http::ShowLocalDestination(s,strstd,0); + childTextBrowser->setHtml(QString::fromStdString(s.str())); + } } void MainWindow::backClickedFromChild() { diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 9d848acf..789e4cb0 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -410,6 +410,7 @@ class DelayedSaveManagerImpl; class MainWindow : public QMainWindow { Q_OBJECT private: + std::string currentLocalDestinationB32; std::shared_ptr logStream; DelayedSaveManagerImpl* delayedSaveManagerPtr; DelayedSaveManager::DATA_SERIAL_TYPE dataSerial; From 5c2f1f36e8d1df08c98d87c6c141e5155012cab1 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Dec 2020 09:40:58 +0800 Subject: [PATCH 3971/6300] qt: sam session is now shown at qt->sam sessions, work towards #914 --- daemon/HTTPServer.cpp | 2 +- daemon/HTTPServer.h | 1 + qt/i2pd_qt/mainwindow.cpp | 12 +++++++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 25b6ab19..c0a0b72b 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -820,7 +820,7 @@ namespace http { s << "SAM Sessions: no sessions currently running.
    \r\n"; } - static void ShowSAMSession (std::stringstream& s, const std::string& id) + void ShowSAMSession (std::stringstream& s, const std::string& id) { auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index a977e3e8..9b50fc32 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -98,6 +98,7 @@ namespace http void ShowSAMSessions (std::stringstream& s); void ShowI2PTunnels (std::stringstream& s); void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token); + void ShowSAMSession (std::stringstream& s, const std::string& id); } // http } // i2p diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index 034b9c4a..e5ce2729 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -1001,7 +1001,14 @@ void MainWindow::anchorClickedHandler(const QUrl & link) { url.parse_query(params); const std::string page = params["page"]; const std::string cmd = params["cmd"]; - if(page == "local_destination") { + if(page == "sam_session") { + const std::string samID = params["sam_id"]; + pageWithBackButton->show(); + textBrowser->hide(); + std::stringstream s; + i2p::http::ShowSAMSession (s, samID); + childTextBrowser->setHtml(QString::fromStdString(s.str())); + } else if(page == "local_destination") { std::string b32 = params["b32"]; currentLocalDestinationB32 = b32; pageWithBackButton->show(); @@ -1010,8 +1017,7 @@ void MainWindow::anchorClickedHandler(const QUrl & link) { std::string strstd = currentLocalDestinationB32; i2p::http::ShowLocalDestination(s,strstd,0); childTextBrowser->setHtml(QString::fromStdString(s.str())); - } - if(cmd == "closestream") { + } else if(cmd == "closestream") { std::string b32 = params["b32"]; uint32_t streamID = std::stoul(params["streamID"], nullptr); From a0d90717c335cfaf700cdb86e2819bd49e9c7a43 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Dec 2020 10:06:57 +0800 Subject: [PATCH 3972/6300] qt: i2cp server page is now shown, work towards #914 --- daemon/HTTPServer.cpp | 2 +- daemon/HTTPServer.h | 1 + qt/i2pd_qt/mainwindow.cpp | 13 +++++++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index c0a0b72b..aba30fd7 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -532,7 +532,7 @@ namespace http { } } - static void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) + void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) { auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer) diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index 9b50fc32..8e1520b8 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -99,6 +99,7 @@ namespace http void ShowI2PTunnels (std::stringstream& s); void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token); void ShowSAMSession (std::stringstream& s, const std::string& id); + void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id); } // http } // i2p diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index e5ce2729..e0123dc8 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -1001,14 +1001,13 @@ void MainWindow::anchorClickedHandler(const QUrl & link) { url.parse_query(params); const std::string page = params["page"]; const std::string cmd = params["cmd"]; - if(page == "sam_session") { - const std::string samID = params["sam_id"]; + if (page == "sam_session") { pageWithBackButton->show(); textBrowser->hide(); std::stringstream s; - i2p::http::ShowSAMSession (s, samID); + i2p::http::ShowSAMSession (s, params["sam_id"]); childTextBrowser->setHtml(QString::fromStdString(s.str())); - } else if(page == "local_destination") { + } else if (page == "local_destination") { std::string b32 = params["b32"]; currentLocalDestinationB32 = b32; pageWithBackButton->show(); @@ -1017,6 +1016,12 @@ void MainWindow::anchorClickedHandler(const QUrl & link) { std::string strstd = currentLocalDestinationB32; i2p::http::ShowLocalDestination(s,strstd,0); childTextBrowser->setHtml(QString::fromStdString(s.str())); + } else if (page == "i2cp_local_destination") { + pageWithBackButton->show(); + textBrowser->hide(); + std::stringstream s; + i2p::http::ShowI2CPLocalDestination (s, params["i2cp_id"]); + childTextBrowser->setHtml(QString::fromStdString(s.str())); } else if(cmd == "closestream") { std::string b32 = params["b32"]; uint32_t streamID = std::stoul(params["streamID"], nullptr); From a61d7fe115b3b3a34f10fc2877d20085ec5b0049 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 18 Dec 2020 20:48:08 -0500 Subject: [PATCH 3973/6300] set correct NAME for NAMING REPLY --- libi2pd_client/SAM.cpp | 14 +++++++------- libi2pd_client/SAM.h | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index d758f31e..9f7e771e 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -708,16 +708,16 @@ namespace client auto session = m_Owner.FindSession(m_ID); auto dest = session == nullptr ? context.GetSharedLocalDestination() : session->localDestination; if (name == "ME") - SendNamingLookupReply (dest->GetIdentity ()); + SendNamingLookupReply (name, dest->GetIdentity ()); else if ((identity = context.GetAddressBook ().GetFullAddress (name)) != nullptr) - SendNamingLookupReply (identity); + SendNamingLookupReply (name, identity); else if ((addr = context.GetAddressBook ().GetAddress (name))) { if (addr->IsIdentHash ()) { auto leaseSet = dest->FindLeaseSet (addr->identHash); if (leaseSet) - SendNamingLookupReply (leaseSet->GetIdentity ()); + SendNamingLookupReply (name, leaseSet->GetIdentity ()); else dest->RequestDestination (addr->identHash, std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, @@ -756,7 +756,7 @@ namespace client if (leaseSet) { context.GetAddressBook ().InsertFullAddress (leaseSet->GetIdentity ()); - SendNamingLookupReply (leaseSet->GetIdentity ()); + SendNamingLookupReply (name, leaseSet->GetIdentity ()); } else { @@ -770,13 +770,13 @@ namespace client } } - void SAMSocket::SendNamingLookupReply (std::shared_ptr identity) + void SAMSocket::SendNamingLookupReply (const std::string& name, std::shared_ptr identity) { auto base64 = identity->ToBase64 (); #ifdef _MSC_VER - size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ()); + size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, name.c_str (), base64.c_str ()); #else - size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ()); + size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, name.c_str (), base64.c_str ()); #endif SendMessageReply (m_Buffer, l, false); } diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h index fafd7d1c..9495bf6f 100644 --- a/libi2pd_client/SAM.h +++ b/libi2pd_client/SAM.h @@ -55,7 +55,7 @@ namespace client const char SAM_DEST_REPLY[] = "DEST REPLY PUB=%s PRIV=%s\n"; const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n"; const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP"; - const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n"; + const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=%s VALUE=%s\n"; const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n"; const char SAM_RAW_RECEIVED[] = "RAW RECEIVED SIZE=%lu\n"; const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n"; @@ -140,7 +140,7 @@ namespace client void Connect (std::shared_ptr remote, std::shared_ptr session = nullptr); void HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet); - void SendNamingLookupReply (std::shared_ptr identity); + void SendNamingLookupReply (const std::string& name, std::shared_ptr identity); void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, std::string name); void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode); void SendSessionCreateReplyOk (); From ae1b1da34282ec95a244b97eb3832a70af27300d Mon Sep 17 00:00:00 2001 From: user Date: Sat, 19 Dec 2020 21:16:40 +0800 Subject: [PATCH 3974/6300] qt: log level ui control now synced with core and log pane ui at runtime --- qt/i2pd_qt/mainwindow.cpp | 15 +++++++++++++++ qt/i2pd_qt/mainwindow.h | 3 +++ 2 files changed, 18 insertions(+) diff --git a/qt/i2pd_qt/mainwindow.cpp b/qt/i2pd_qt/mainwindow.cpp index e0123dc8..7aa868fc 100644 --- a/qt/i2pd_qt/mainwindow.cpp +++ b/qt/i2pd_qt/mainwindow.cpp @@ -196,6 +196,9 @@ MainWindow::MainWindow(std::shared_ptr logStream_, QWidget *paren logFileNameOption=initFileChooser( OPTION("","logfile",[]{return "";}), uiSettings->logFileLineEdit, uiSettings->logFileBrowsePushButton, false); initLogLevelCombobox(OPTION("","loglevel",[]{return "";}), uiSettings->logLevelComboBox); + + QObject::connect(uiSettings->logLevelComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(syncLogLevel(int))); + initCheckBox( OPTION("","logclftime",[]{return "false";}), uiSettings->logclftimeCheckBox);//"Write full CLF-formatted date and time to log (default: write only time)" initFolderChooser( OPTION("","datadir",[]{return "";}), uiSettings->dataFolderLineEdit, uiSettings->dataFolderBrowsePushButton); initIPAddressBox( OPTION("","host",[]{return "";}), uiSettings->routerExternalHostLineEdit, tr("Router external address -> Host")); @@ -1109,3 +1112,15 @@ void MainWindow::highlightWrongInput(QString warningText, WrongInputPageEnum inp default: assert(false); break; } } + +void MainWindow::syncLogLevel (int /*comboBoxIndex*/) { + std::string level = uiSettings->logLevelComboBox->currentText().toLower().toStdString(); + if (level == "none" || level == "error" || level == "warn" || level == "info" || level == "debug") + i2p::log::Logger().SetLogLevel(level); + else { + LogPrint(eLogError, "unknown loglevel set attempted"); + return; + } + i2p::log::Logger().Reopen (); +} + diff --git a/qt/i2pd_qt/mainwindow.h b/qt/i2pd_qt/mainwindow.h index 789e4cb0..e1ddcc6e 100644 --- a/qt/i2pd_qt/mainwindow.h +++ b/qt/i2pd_qt/mainwindow.h @@ -453,7 +453,10 @@ private slots: void runPeerTest(); void enableTransit(); void disableTransit(); + public slots: + void syncLogLevel (int comboBoxIndex); + void showStatus_local_destinations_Page(); void showStatus_leasesets_Page(); void showStatus_tunnels_Page(); From da7e2f25808bba3e5dc6c59d2e37407895cb7573 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 19 Dec 2020 15:07:12 -0500 Subject: [PATCH 3975/6300] don't send message through non-established session --- libi2pd/Datagram.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 559b0a8b..9d61f5e0 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -295,11 +295,11 @@ namespace datagram } } - if (!m_RoutingSession || !m_RoutingSession->GetOwner ()) + if (!m_RoutingSession || !m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) { bool found = false; for (auto& it: m_PendingRoutingSessions) - if (it->GetOwner ()) // found established session + if (it->GetOwner () && m_RoutingSession->IsReadyToSend ()) // found established session { m_RoutingSession = it; m_PendingRoutingSessions.clear (); @@ -309,7 +309,7 @@ namespace datagram if (!found) { m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); - if (!m_RoutingSession->GetOwner ()) + if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) m_PendingRoutingSessions.push_back (m_RoutingSession); } } From f2e4d5f06c8d21885f70429b0a9a2d912db1a8a1 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 20 Dec 2020 19:52:06 -0500 Subject: [PATCH 3976/6300] trim behind not affter max generated tags --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index ecc7412f..a2c5b612 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -298,7 +298,10 @@ namespace garlic break; case eECIESx25519BlkNextKey: LogPrint (eLogDebug, "Garlic: next key"); - HandleNextKey (buf + offset, size, receiveTagset); + if (receiveTagset) + HandleNextKey (buf + offset, size, receiveTagset); + else + LogPrint (eLogError, "Garlic: Unexpected next key block"); break; case eECIESx25519BlkAck: { @@ -721,20 +724,19 @@ namespace garlic { if (receiveTagset->GetNextIndex () - index < GetOwner ()->GetNumRatchetInboundTags ()/2) moreTags = GetOwner ()->GetNumRatchetInboundTags (); + index -= GetOwner ()->GetNumRatchetInboundTags (); // trim behind } else { moreTags = ECIESX25519_MIN_NUM_GENERATED_TAGS + (index >> 2); // N/4 if (moreTags > ECIESX25519_MAX_NUM_GENERATED_TAGS) moreTags = ECIESX25519_MAX_NUM_GENERATED_TAGS; moreTags -= (receiveTagset->GetNextIndex () - index); + index -= ECIESX25519_MAX_NUM_GENERATED_TAGS; // trim behind } if (moreTags > 0) - { GenerateMoreReceiveTags (receiveTagset, moreTags); - index -= (moreTags >> 1); // /2 - if (index > 0) - receiveTagset->SetTrimBehind (index); - } + if (index > 0) + receiveTagset->SetTrimBehind (index); } return true; } From d34dc397e880eddb4af241a420440de4d4543894 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 24 Dec 2020 14:06:34 -0500 Subject: [PATCH 3977/6300] changed to 320 tags max --- libi2pd/ECIESX25519AEADRatchetSession.h | 2 +- libi2pd/Garlic.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 59be94c1..805ea8d6 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -33,7 +33,7 @@ namespace garlic const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180 const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 8192; // number of tags we request new tagset after const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; - const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 160; + const int ECIESX25519_MAX_NUM_GENERATED_TAGS = 320; const int ECIESX25519_NSR_NUM_GENERATED_TAGS = 12; const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */ diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 02ded2f4..019cc387 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -551,9 +551,9 @@ namespace garlic auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS); for (int i = 0; i < maxTags; i++) { - LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); if (AddECIESx25519SessionNextTag (m_LastTagset) == tag) { + LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[tag].index)) found = true; break; From b4236b04c6eed5d20b569df27eada2bfcab02d2d Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 25 Dec 2020 09:01:55 -0500 Subject: [PATCH 3978/6300] leaset creation timeout --- libi2pd_client/I2CP.cpp | 24 +++++++++++++++++++++++- libi2pd_client/I2CP.h | 5 ++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index cb618b5d..c9db4a82 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -26,7 +26,8 @@ namespace client I2CPDestination::I2CPDestination (boost::asio::io_service& service, std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params): LeaseSetDestination (service, isPublic, ¶ms), - m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()) + m_Owner (owner), m_Identity (identity), m_EncryptionKeyType (m_Identity->GetCryptoKeyType ()), + m_IsCreatingLeaseSet (false), m_LeaseSetCreationTimer (service) { } @@ -34,6 +35,7 @@ namespace client { LeaseSetDestination::Stop (); m_Owner = nullptr; + m_LeaseSetCreationTimer.cancel (); } void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) @@ -84,6 +86,11 @@ namespace client void I2CPDestination::CreateNewLeaseSet (std::vector > tunnels) { + if (m_IsCreatingLeaseSet) + { + LogPrint (eLogInfo, "I2CP: LeaseSet is being created"); + return; + } uint8_t priv[256] = {0}; i2p::data::LocalLeaseSet ls (m_Identity, priv, tunnels); // we don't care about encryption key, we need leases only m_LeaseSetExpirationTime = ls.GetExpirationTime (); @@ -94,15 +101,28 @@ namespace client uint16_t sessionID = m_Owner->GetSessionID (); if (sessionID != 0xFFFF) { + m_IsCreatingLeaseSet = true; htobe16buf (leases - 3, sessionID); size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); + m_LeaseSetCreationTimer.expires_from_now (boost::posix_time::seconds (I2CP_LEASESET_CREATION_TIMEOUT)); + auto s = GetSharedFromThis (); + m_LeaseSetCreationTimer.async_wait ([s](const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + LogPrint (eLogInfo, "I2CP: LeaseSet creation timeout expired. Terminate"); + if (s->m_Owner) s->m_Owner->Stop (); + } + }); } } } void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) { + m_IsCreatingLeaseSet = false; + m_LeaseSetCreationTimer.cancel (); auto ls = std::make_shared (m_Identity, buf, len); ls->SetExpirationTime (m_LeaseSetExpirationTime); SetLeaseSet (ls); @@ -110,6 +130,8 @@ namespace client void I2CPDestination::LeaseSet2Created (uint8_t storeType, const uint8_t * buf, size_t len) { + m_IsCreatingLeaseSet = false; + m_LeaseSetCreationTimer.cancel (); auto ls = (storeType == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) ? std::make_shared (m_Identity, buf, len): std::make_shared (storeType, m_Identity, buf, len); diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 32f32221..da7d8ffa 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -27,7 +27,8 @@ namespace client const size_t I2CP_SESSION_BUFFER_SIZE = 4096; const size_t I2CP_MAX_MESSAGE_LENGTH = 65535; const size_t I2CP_MAX_SEND_QUEUE_SIZE = 1024*1024; // in bytes, 1M - + const int I2CP_LEASESET_CREATION_TIMEOUT = 10; // in seconds + const size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; const size_t I2CP_HEADER_SIZE = I2CP_HEADER_TYPE_OFFSET + 1; @@ -109,6 +110,8 @@ namespace client std::shared_ptr m_ECIESx25519Decryptor; uint8_t m_ECIESx25519PrivateKey[32]; uint64_t m_LeaseSetExpirationTime; + bool m_IsCreatingLeaseSet; + boost::asio::deadline_timer m_LeaseSetCreationTimer; }; class RunnableI2CPDestination: private i2p::util::RunnableService, public I2CPDestination From 86ff0d86dbdc3df86c7a41e7d2ba64adc00a04ab Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 26 Dec 2020 17:18:29 -0500 Subject: [PATCH 3979/6300] check if new tag was created --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 18 ++++++++++++++++-- libi2pd/Garlic.cpp | 13 ++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index a2c5b612..01be859b 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -45,13 +45,13 @@ namespace garlic uint64_t RatchetTagSet::GetNextSessionTag () { - i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) m_NextIndex++; if (m_NextIndex >= 65535) { LogPrint (eLogError, "Garlic: Tagset ", GetTagSetID (), " is empty"); return 0; } + i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) return m_KeyData.GetTag (); } @@ -687,6 +687,13 @@ namespace garlic auto index = m_SendTagset->GetNextIndex (); CreateNonce (index, nonce); // tag's index uint64_t tag = m_SendTagset->GetNextSessionTag (); + if (!tag) + { + LogPrint (eLogError, "Garlic: can't create new ECIES-X25519-AEAD-Ratchet tag for send tagset"); + if (GetOwner ()) + GetOwner ()->RemoveECIESx25519Session (m_RemoteStaticKey); + return false; + } memcpy (out, &tag, 8); // ad = The session tag, 8 bytes // ciphertext = ENCRYPT(k, n, payload, ad) @@ -1050,7 +1057,14 @@ namespace garlic if (GetOwner ()) { for (int i = 0; i < numTags; i++) - GetOwner ()->AddECIESx25519SessionNextTag (receiveTagset); + { + auto tag = GetOwner ()->AddECIESx25519SessionNextTag (receiveTagset); + if (!tag) + { + LogPrint (eLogError, "Garlic: can't create new ECIES-X25519-AEAD-Ratchet tag for receive tagset"); + break; + } + } } } diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 019cc387..aff92837 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -546,12 +546,18 @@ namespace garlic if (!session->HandleNextMessage (buf, length, nullptr, 0)) { // try to gererate more tags for last tagset - if (m_LastTagset) + if (m_LastTagset && m_LastTagset->GetNextIndex () < 2*ECIESX25519_TAGSET_MAX_NUM_TAGS) { auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS); for (int i = 0; i < maxTags; i++) { - if (AddECIESx25519SessionNextTag (m_LastTagset) == tag) + auto nextTag = AddECIESx25519SessionNextTag (m_LastTagset); + if (!nextTag) + { + LogPrint (eLogError, "Garlic: can't create new ECIES-X25519-AEAD-Ratchet tag for last tagset"); + break; + } + if (nextTag == tag) { LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[tag].index)) @@ -1057,7 +1063,8 @@ namespace garlic { auto index = tagset->GetNextIndex (); uint64_t tag = tagset->GetNextSessionTag (); - m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{index, tagset}); + if (tag) + m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{index, tagset}); return tag; } From 7ce92118e4451956fd8b5c9b224a236e2ebe8a26 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 27 Dec 2020 11:18:53 -0500 Subject: [PATCH 3980/6300] handle follow-on NSR messages --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 36 ++++++++++++----------- libi2pd/ECIESX25519AEADRatchetSession.h | 5 +++- libi2pd/util.h | 14 +++++++++ 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 01be859b..d2286b15 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -9,6 +9,7 @@ #include #include #include "Log.h" +#include "util.h" #include "Crypto.h" #include "Elligator.h" #include "Tag.h" @@ -619,18 +620,15 @@ namespace garlic } buf += 32; len -= 32; // KDF for Reply Key Section - uint8_t h[32]; memcpy (h, m_H, 32); // save m_H + i2p::util::SaveStateHelper s(*this); // restore noise state on exit MixHash (tag, 8); // h = SHA256(h || tag) MixHash (bepk, 32); // h = SHA256(h || bepk) uint8_t sharedSecret[32]; - if (m_State == eSessionStateNewSessionSent) - { - // only fist time, we assume ephemeral keys the same - m_EphemeralKeys->Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) - MixKey (sharedSecret); - GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) - MixKey (sharedSecret); - } + m_EphemeralKeys->Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) + MixKey (sharedSecret); + GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) + MixKey (sharedSecret); + uint8_t nonce[12]; CreateNonce (0, nonce); // calculate hash for zero length @@ -646,6 +644,7 @@ namespace garlic i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) if (m_State == eSessionStateNewSessionSent) { + // only first time, then we keep using existing tagsets // k_ab = keydata[0:31], k_ba = keydata[32:63] m_SendTagset = std::make_shared(shared_from_this ()); m_SendTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) @@ -667,11 +666,10 @@ namespace garlic if (m_State == eSessionStateNewSessionSent) { m_State = eSessionStateEstablished; - m_EphemeralKeys = nullptr; + //m_EphemeralKeys = nullptr; // TODO: delete after a while m_SessionCreatedTimestamp = i2p::util::GetSecondsSinceEpoch (); GetOwner ()->AddECIESx25519Session (m_RemoteStaticKey, shared_from_this ()); } - memcpy (m_H, h, 32); // restore m_H HandlePayload (buf, len - 16, nullptr, 0); // we have received reply to NS with LeaseSet in it @@ -762,12 +760,16 @@ namespace garlic [[fallthrough]]; #endif case eSessionStateEstablished: - if (HandleExistingSessionMessage (buf, len, receiveTagset, index)) return true; - // check NSR just in case - LogPrint (eLogDebug, "Garlic: check for out of order NSR with index ", index); - if (receiveTagset->GetNextIndex () - index < ECIESX25519_NSR_NUM_GENERATED_TAGS/2) - GenerateMoreReceiveTags (receiveTagset, ECIESX25519_NSR_NUM_GENERATED_TAGS); - return HandleNewOutgoingSessionReply (buf, len); + if (receiveTagset->IsNS ()) + { + // our of sequence NSR + LogPrint (eLogDebug, "Garlic: check for out of order NSR with index ", index); + if (receiveTagset->GetNextIndex () - index < ECIESX25519_NSR_NUM_GENERATED_TAGS/2) + GenerateMoreReceiveTags (receiveTagset, ECIESX25519_NSR_NUM_GENERATED_TAGS); + return HandleNewOutgoingSessionReply (buf, len); + } + else + return HandleExistingSessionMessage (buf, len, receiveTagset, index); case eSessionStateNew: return HandleNewIncomingSession (buf, len); case eSessionStateNewSessionSent: diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 805ea8d6..86e3ffc0 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -45,7 +45,8 @@ namespace garlic public: RatchetTagSet (std::shared_ptr session): m_Session (session) {}; - + virtual bool IsNS () const { return false; }; + void DHInitialize (const uint8_t * rootKey, const uint8_t * k); void NextSessionTagRatchet (); uint64_t GetNextSessionTag (); @@ -91,6 +92,8 @@ namespace garlic NSRatchetTagSet (std::shared_ptr session): RatchetTagSet (session), m_DummySession (session) {}; + + bool IsNS () const { return true; }; private: diff --git a/libi2pd/util.h b/libi2pd/util.h index f6222b9f..e6de09ed 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -170,6 +170,20 @@ namespace util void SetThreadName (const char *name); + template + class SaveStateHelper + { + public: + + SaveStateHelper (T& orig): m_Original (orig), m_Copy (orig) {}; + ~SaveStateHelper () { m_Original = m_Copy; }; + + private: + + T& m_Original; + T m_Copy; + }; + namespace net { int GetMTU (const boost::asio::ip::address& localAddress); From 726bd0d63b2553347b285e559718533634178b63 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 1 Jan 2021 15:03:11 -0500 Subject: [PATCH 3981/6300] check if x25519 key is valid --- libi2pd/Crypto.cpp | 5 ++- libi2pd/Crypto.h | 2 +- libi2pd/CryptoKey.cpp | 3 +- libi2pd/ECIESX25519AEADRatchetSession.cpp | 48 +++++++++++++++++++---- libi2pd/Garlic.cpp | 1 + libi2pd/RouterContext.cpp | 6 ++- 6 files changed, 52 insertions(+), 13 deletions(-) diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 3e6279ff..68850a9d 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -351,11 +351,13 @@ namespace crypto #endif } - void X25519Keys::Agree (const uint8_t * pub, uint8_t * shared) + bool X25519Keys::Agree (const uint8_t * pub, uint8_t * shared) { + if (pub[31] & 0x80) return false; // not x25519 key #if OPENSSL_X25519 EVP_PKEY_derive_init (m_Ctx); auto pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_X25519, NULL, pub, 32); + if (!pkey) return false; EVP_PKEY_derive_set_peer (m_Ctx, pkey); size_t len = 32; EVP_PKEY_derive (m_Ctx, shared, &len); @@ -363,6 +365,7 @@ namespace crypto #else GetEd25519 ()->ScalarMul (pub, m_PrivateKey, shared, m_Ctx); #endif + return true; } void X25519Keys::GetPrivateKey (uint8_t * priv) const diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index c63cc45e..a6c64c70 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -89,7 +89,7 @@ namespace crypto const uint8_t * GetPublicKey () const { return m_PublicKey; }; void GetPrivateKey (uint8_t * priv) const; void SetPrivateKey (const uint8_t * priv, bool calculatePublic = false); - void Agree (const uint8_t * pub, uint8_t * shared); + bool Agree (const uint8_t * pub, uint8_t * shared); bool IsElligatorIneligible () const { return m_IsElligatorIneligible; } void SetElligatorIneligible () { m_IsElligatorIneligible = true; } diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp index 4518a347..ad93d386 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -173,8 +173,7 @@ namespace crypto bool ECIESX25519AEADRatchetDecryptor::Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding) { - m_StaticKeys.Agree (epub, sharedSecret); - return true; + return m_StaticKeys.Agree (epub, sharedSecret); } void CreateECIESX25519AEADRatchetRandomKeys (uint8_t * priv, uint8_t * pub) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index d2286b15..cbf5175a 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -231,7 +231,11 @@ namespace garlic MixHash (m_Aepk, 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; - GetOwner ()->Decrypt (m_Aepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519(bsk, aepk) + if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key"); + return false; + } MixKey (sharedSecret); // decrypt flags/static @@ -251,7 +255,11 @@ namespace garlic { // static key, fs is apk memcpy (m_RemoteStaticKey, fs, 32); - GetOwner ()->Decrypt (fs, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519(bsk, apk) + if (!GetOwner ()->Decrypt (fs, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, apk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); + return false; + } MixKey (sharedSecret); } else // all zeros flags @@ -448,7 +456,11 @@ namespace garlic i2p::crypto::InitNoiseIKState (*this, m_RemoteStaticKey); // bpk MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; - m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) + if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); + return false; + } MixKey (sharedSecret); // encrypt flags/static key section uint8_t nonce[12]; @@ -504,7 +516,11 @@ namespace garlic MixHash (out + offset, 32); // h = SHA256(h || aepk) offset += 32; uint8_t sharedSecret[32]; - m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // x25519(aesk, bpk) + if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); + return false; + } MixKey (sharedSecret); uint8_t nonce[12]; CreateNonce (0, nonce); @@ -540,9 +556,17 @@ namespace garlic MixHash ((const uint8_t *)&tag, 8); // h = SHA256(h || tag) MixHash (m_EphemeralKeys->GetPublicKey (), 32); // h = SHA256(h || bepk) uint8_t sharedSecret[32]; - m_EphemeralKeys->Agree (m_Aepk, sharedSecret); // sharedSecret = x25519(besk, aepk) + if (!m_EphemeralKeys->Agree (m_Aepk, sharedSecret)) // sharedSecret = x25519(besk, aepk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key"); + return false; + } MixKey (sharedSecret); - m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret); // sharedSecret = x25519(besk, apk) + if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // sharedSecret = x25519(besk, apk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); + return false; + } MixKey (sharedSecret); uint8_t nonce[12]; CreateNonce (0, nonce); @@ -624,7 +648,11 @@ namespace garlic MixHash (tag, 8); // h = SHA256(h || tag) MixHash (bepk, 32); // h = SHA256(h || bepk) uint8_t sharedSecret[32]; - m_EphemeralKeys->Agree (bepk, sharedSecret); // sharedSecret = x25519(aesk, bepk) + if (!m_EphemeralKeys->Agree (bepk, sharedSecret)) // sharedSecret = x25519(aesk, bepk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Bob ephemeral key"); + return false; + } MixKey (sharedSecret); GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) MixKey (sharedSecret); @@ -788,7 +816,11 @@ namespace garlic i2p::crypto::InitNoiseNState (*this, GetOwner ()->GetEncryptionPublicKey (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)); // bpk MixHash (buf, 32); uint8_t sharedSecret[32]; - GetOwner ()->Decrypt (buf, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519(bsk, aepk) + if (!GetOwner ()->Decrypt (buf, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) + { + LogPrint (eLogWarning, "Garlic: Incorrect N ephemeral public key"); + return false; + } MixKey (sharedSecret); buf += 32; len -= 32; uint8_t nonce[12]; diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index aff92837..6f9a37b9 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -866,6 +866,7 @@ namespace garlic { it->second.tagset->DeleteSymmKey (it->second.index); it = m_ECIESx25519Tags.erase (it); + numExpiredTags++; } else ++it; diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index d258b341..ce1eede0 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -729,7 +729,11 @@ namespace i2p m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState (*m_InitialNoiseState)); m_CurrentNoiseState->MixHash (encrypted, 32); // h = SHA256(h || sepk) uint8_t sharedSecret[32]; - m_Decryptor->Decrypt (encrypted, sharedSecret, ctx, false); + if (!m_Decryptor->Decrypt (encrypted, sharedSecret, ctx, false)) + { + LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); + return false; + } m_CurrentNoiseState->MixKey (sharedSecret); encrypted += 32; uint8_t nonce[12]; From ee3cd44f975f395a760f39d3522bef2089169447 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 4 Jan 2021 18:20:16 -0500 Subject: [PATCH 3982/6300] ReceiveRatchetTagSet --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 30 ++++++------ libi2pd/ECIESX25519AEADRatchetSession.h | 57 +++++++++++++++-------- libi2pd/Garlic.cpp | 2 +- libi2pd/Garlic.h | 10 ++-- 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index cbf5175a..2a0f5cb4 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -100,7 +100,7 @@ namespace garlic m_ExpirationTimestamp = i2p::util::GetSecondsSinceEpoch () + ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT; } - bool RatchetTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) + bool ReceiveRatchetTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) { auto session = GetSession (); if (!session) return false; @@ -108,7 +108,7 @@ namespace garlic } DatabaseLookupTagSet::DatabaseLookupTagSet (GarlicDestination * destination, const uint8_t * key): - RatchetTagSet (nullptr), m_Destination (destination) + ReceiveRatchetTagSet (nullptr), m_Destination (destination) { memcpy (m_Key, key, 32); Expire (); @@ -203,12 +203,12 @@ namespace garlic return false; } - std::shared_ptr ECIESX25519AEADRatchetSession::CreateNewSessionTagset () + std::shared_ptr ECIESX25519AEADRatchetSession::CreateNewSessionTagset () { uint8_t tagsetKey[32]; i2p::crypto::HKDF (m_CK, nullptr, 0, "SessionReplyTags", tagsetKey, 32); // tagsetKey = HKDF(chainKey, ZEROLEN, "SessionReplyTags", 32) // Session Tag Ratchet - auto tagsetNsr = (m_State == eSessionStateNewSessionReceived) ? std::make_shared(shared_from_this ()): + auto tagsetNsr = (m_State == eSessionStateNewSessionReceived) ? std::make_shared(shared_from_this ()): std::make_shared(shared_from_this ()); tagsetNsr->DHInitialize (m_CK, tagsetKey); // tagset_nsr = DH_INITIALIZE(chainKey, tagsetKey) tagsetNsr->NextSessionTagRatchet (); @@ -284,7 +284,7 @@ namespace garlic return true; } - void ECIESX25519AEADRatchetSession::HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index) + void ECIESX25519AEADRatchetSession::HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index) { size_t offset = 0; while (offset < len) @@ -352,7 +352,7 @@ namespace garlic } } - void ECIESX25519AEADRatchetSession::HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset) + void ECIESX25519AEADRatchetSession::HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset) { uint8_t flag = buf[0]; buf++; // flag if (flag & ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG) @@ -367,7 +367,7 @@ namespace garlic uint8_t sharedSecret[32], tagsetKey[32]; m_NextSendRatchet->key->Agree (m_NextSendRatchet->remote, sharedSecret); i2p::crypto::HKDF (sharedSecret, nullptr, 0, "XDHRatchetTagSet", tagsetKey, 32); // tagsetKey = HKDF(sharedSecret, ZEROLEN, "XDHRatchetTagSet", 32) - auto newTagset = std::make_shared (shared_from_this ()); + auto newTagset = std::make_shared (); newTagset->SetTagSetID (1 + m_NextSendRatchet->keyID + keyID); newTagset->DHInitialize (m_SendTagset->GetNextRootKey (), tagsetKey); newTagset->NextSessionTagRatchet (); @@ -409,7 +409,7 @@ namespace garlic uint8_t sharedSecret[32], tagsetKey[32]; m_NextReceiveRatchet->key->Agree (m_NextReceiveRatchet->remote, sharedSecret); i2p::crypto::HKDF (sharedSecret, nullptr, 0, "XDHRatchetTagSet", tagsetKey, 32); // tagsetKey = HKDF(sharedSecret, ZEROLEN, "XDHRatchetTagSet", 32) - auto newTagset = std::make_shared(shared_from_this ()); + auto newTagset = std::make_shared(shared_from_this ()); newTagset->SetTagSetID (tagsetID); newTagset->DHInitialize (receiveTagset->GetNextRootKey (), tagsetKey); newTagset->NextSessionTagRatchet (); @@ -582,10 +582,10 @@ namespace garlic uint8_t keydata[64]; i2p::crypto::HKDF (m_CK, nullptr, 0, "", keydata); // keydata = HKDF(chainKey, ZEROLEN, "", 64) // k_ab = keydata[0:31], k_ba = keydata[32:63] - auto receiveTagset = std::make_shared(shared_from_this ()); + auto receiveTagset = std::make_shared(shared_from_this()); receiveTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) receiveTagset->NextSessionTagRatchet (); - m_SendTagset = std::make_shared(shared_from_this ()); + m_SendTagset = std::make_shared(); m_SendTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) m_SendTagset->NextSessionTagRatchet (); GenerateMoreReceiveTags (receiveTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ? @@ -674,10 +674,10 @@ namespace garlic { // only first time, then we keep using existing tagsets // k_ab = keydata[0:31], k_ba = keydata[32:63] - m_SendTagset = std::make_shared(shared_from_this ()); + m_SendTagset = std::make_shared(); m_SendTagset->DHInitialize (m_CK, keydata); // tagset_ab = DH_INITIALIZE(chainKey, k_ab) m_SendTagset->NextSessionTagRatchet (); - auto receiveTagset = std::make_shared(shared_from_this ()); + auto receiveTagset = std::make_shared(shared_from_this ()); receiveTagset->DHInitialize (m_CK, keydata + 32); // tagset_ba = DH_INITIALIZE(chainKey, k_ba) receiveTagset->NextSessionTagRatchet (); GenerateMoreReceiveTags (receiveTagset, (GetOwner () && GetOwner ()->GetNumRatchetInboundTags () > 0) ? @@ -736,7 +736,7 @@ namespace garlic } bool ECIESX25519AEADRatchetSession::HandleExistingSessionMessage (uint8_t * buf, size_t len, - std::shared_ptr receiveTagset, int index) + std::shared_ptr receiveTagset, int index) { uint8_t nonce[12]; CreateNonce (index, nonce); // tag's index @@ -775,7 +775,7 @@ namespace garlic } bool ECIESX25519AEADRatchetSession::HandleNextMessage (uint8_t * buf, size_t len, - std::shared_ptr receiveTagset, int index) + std::shared_ptr receiveTagset, int index) { m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); switch (m_State) @@ -1086,7 +1086,7 @@ namespace garlic return cloveSize + 3; } - void ECIESX25519AEADRatchetSession::GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags) + void ECIESX25519AEADRatchetSession::GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags) { if (GetOwner ()) { diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 86e3ffc0..0cc7935e 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -39,12 +39,13 @@ namespace garlic const size_t ECIESX25519_OPTIMAL_PAYLOAD_SIZE = 1912; // 1912 = 1956 /* to fit 2 tunnel messages */ // - 16 /* I2NP header */ - 16 /* poly hash */ - 8 /* tag */ - 4 /* garlic length */ - class ECIESX25519AEADRatchetSession; - class RatchetTagSet: public std::enable_shared_from_this + class RatchetTagSet { public: - RatchetTagSet (std::shared_ptr session): m_Session (session) {}; + RatchetTagSet () {}; + virtual ~RatchetTagSet () {}; + virtual bool IsNS () const { return false; }; void DHInitialize (const uint8_t * rootKey, const uint8_t * k); @@ -55,16 +56,12 @@ namespace garlic void GetSymmKey (int index, uint8_t * key); void DeleteSymmKey (int index); - std::shared_ptr GetSession () { return m_Session.lock (); }; int GetTagSetID () const { return m_TagSetID; }; void SetTagSetID (int tagsetID) { m_TagSetID = tagsetID; }; - void SetTrimBehind (int index) { if (index > m_TrimBehindIndex) m_TrimBehindIndex = index; }; - + void Expire (); bool IsExpired (uint64_t ts) const { return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; }; - virtual bool IsIndexExpired (int index) const { return m_Session.expired () || index < m_TrimBehindIndex; }; - - virtual bool HandleNextMessage (uint8_t * buf, size_t len, int index); + private: @@ -79,19 +76,39 @@ namespace garlic } m_KeyData; uint8_t m_SessTagConstant[32], m_SymmKeyCK[32], m_CurrentSymmKeyCK[64], m_NextRootKey[32]; - int m_NextIndex, m_NextSymmKeyIndex, m_TrimBehindIndex = 0; + int m_NextIndex, m_NextSymmKeyIndex; std::unordered_map > m_ItermediateSymmKeys; - std::weak_ptr m_Session; + int m_TagSetID = 0; uint64_t m_ExpirationTimestamp = 0; }; - class NSRatchetTagSet: public RatchetTagSet + class ECIESX25519AEADRatchetSession; + class ReceiveRatchetTagSet: public RatchetTagSet, + public std::enable_shared_from_this + { + public: + + ReceiveRatchetTagSet (std::shared_ptr session): m_Session (session) {}; + + std::shared_ptr GetSession () { return m_Session.lock (); }; + void SetTrimBehind (int index) { if (index > m_TrimBehindIndex) m_TrimBehindIndex = index; }; + + virtual bool IsIndexExpired (int index) const { return m_Session.expired () || index < m_TrimBehindIndex; }; + virtual bool HandleNextMessage (uint8_t * buf, size_t len, int index); + + private: + + int m_TrimBehindIndex = 0; + std::weak_ptr m_Session; + }; + + class NSRatchetTagSet: public ReceiveRatchetTagSet { public: NSRatchetTagSet (std::shared_ptr session): - RatchetTagSet (session), m_DummySession (session) {}; + ReceiveRatchetTagSet (session), m_DummySession (session) {}; bool IsNS () const { return true; }; @@ -100,7 +117,7 @@ namespace garlic std::shared_ptr m_DummySession; // we need a strong pointer for NS }; - class DatabaseLookupTagSet: public RatchetTagSet + class DatabaseLookupTagSet: public ReceiveRatchetTagSet { public: @@ -160,7 +177,7 @@ namespace garlic ECIESX25519AEADRatchetSession (GarlicDestination * owner, bool attachLeaseSet); ~ECIESX25519AEADRatchetSession (); - bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); + bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); bool HandleNextMessageForRouter (const uint8_t * buf, size_t len); std::shared_ptr WrapSingleMessage (std::shared_ptr msg); std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter = false); @@ -185,13 +202,13 @@ namespace garlic void CreateNonce (uint64_t seqn, uint8_t * nonce); bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes - std::shared_ptr CreateNewSessionTagset (); + std::shared_ptr CreateNewSessionTagset (); bool HandleNewIncomingSession (const uint8_t * buf, size_t len); bool HandleNewOutgoingSessionReply (uint8_t * buf, size_t len); - bool HandleExistingSessionMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index); - void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index); - void HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset); + bool HandleExistingSessionMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index); + void HandlePayload (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset, int index); + void HandleNextKey (const uint8_t * buf, size_t len, const std::shared_ptr& receiveTagset); bool NewOutgoingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen, bool isStatic = true); bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); @@ -203,7 +220,7 @@ namespace garlic size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len); size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); - void GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags); + void GenerateMoreReceiveTags (std::shared_ptr receiveTagset, int numTags); void NewNextSendRatchet (); private: diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 6f9a37b9..17406b4f 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -1060,7 +1060,7 @@ namespace garlic } } - uint64_t GarlicDestination::AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset) + uint64_t GarlicDestination::AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset) { auto index = tagset->GetNextIndex (); uint64_t tag = tagset->GetNextSessionTag (); diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 379477e8..a8806921 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -215,12 +215,12 @@ namespace garlic class ECIESX25519AEADRatchetSession; typedef std::shared_ptr ECIESX25519AEADRatchetSessionPtr; - class RatchetTagSet; - typedef std::shared_ptr RatchetTagSetPtr; + class ReceiveRatchetTagSet; + typedef std::shared_ptr ReceiveRatchetTagSetPtr; struct ECIESX25519AEADRatchetIndexTagset { int index; - RatchetTagSetPtr tagset; + ReceiveRatchetTagSetPtr tagset; }; class GarlicDestination: public i2p::data::LocalDestination @@ -245,7 +245,7 @@ namespace garlic void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); - uint64_t AddECIESx25519SessionNextTag (RatchetTagSetPtr tagset); + uint64_t AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset); void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); void RemoveECIESx25519Session (const uint8_t * staticKey); void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); @@ -285,7 +285,7 @@ namespace garlic int m_NumRatchetInboundTags; std::unordered_map, std::hash > > m_Tags; std::unordered_map m_ECIESx25519Tags; // session tag -> session - RatchetTagSetPtr m_LastTagset; // tagset last message came for + ReceiveRatchetTagSetPtr m_LastTagset; // tagset last message came for // DeliveryStatus std::mutex m_DeliveryStatusSessionsMutex; std::unordered_map m_DeliveryStatusSessions; // msgID -> session From bc4a97774f049a53e8f1a841a3d1488dc4900681 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 4 Jan 2021 20:15:48 -0500 Subject: [PATCH 3983/6300] strong pointer to session for receive tagset --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 19 +++++++++----- libi2pd/ECIESX25519AEADRatchetSession.h | 32 +++++++---------------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 2a0f5cb4..a83c9285 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -100,6 +100,11 @@ namespace garlic m_ExpirationTimestamp = i2p::util::GetSecondsSinceEpoch () + ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT; } + bool ReceiveRatchetTagSet::IsIndexExpired (int index) const + { + return index < m_TrimBehindIndex || !m_Session || !m_Session->GetOwner (); + } + bool ReceiveRatchetTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) { auto session = GetSession (); @@ -203,16 +208,13 @@ namespace garlic return false; } - std::shared_ptr ECIESX25519AEADRatchetSession::CreateNewSessionTagset () + void ECIESX25519AEADRatchetSession::InitNewSessionTagset (std::shared_ptr tagsetNsr) const { uint8_t tagsetKey[32]; i2p::crypto::HKDF (m_CK, nullptr, 0, "SessionReplyTags", tagsetKey, 32); // tagsetKey = HKDF(chainKey, ZEROLEN, "SessionReplyTags", 32) // Session Tag Ratchet - auto tagsetNsr = (m_State == eSessionStateNewSessionReceived) ? std::make_shared(shared_from_this ()): - std::make_shared(shared_from_this ()); tagsetNsr->DHInitialize (m_CK, tagsetKey); // tagset_nsr = DH_INITIALIZE(chainKey, tagsetKey) tagsetNsr->NextSessionTagRatchet (); - return tagsetNsr; } bool ECIESX25519AEADRatchetSession::HandleNewIncomingSession (const uint8_t * buf, size_t len) @@ -501,7 +503,11 @@ namespace garlic { MixHash (out + offset, len + 16); // h = SHA256(h || ciphertext) if (GetOwner ()) - GenerateMoreReceiveTags (CreateNewSessionTagset (), ECIESX25519_NSR_NUM_GENERATED_TAGS); + { + auto tagsetNsr = std::make_shared(shared_from_this (), true); + InitNewSessionTagset (tagsetNsr); + GenerateMoreReceiveTags (tagsetNsr, ECIESX25519_NSR_NUM_GENERATED_TAGS); + } } return true; } @@ -538,7 +544,8 @@ namespace garlic bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob - m_NSRSendTagset = CreateNewSessionTagset (); + m_NSRSendTagset = std::make_shared(); + InitNewSessionTagset (m_NSRSendTagset); uint64_t tag = m_NSRSendTagset->GetNextSessionTag (); size_t offset = 0; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 0cc7935e..526f2ea4 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -46,8 +46,6 @@ namespace garlic RatchetTagSet () {}; virtual ~RatchetTagSet () {}; - virtual bool IsNS () const { return false; }; - void DHInitialize (const uint8_t * rootKey, const uint8_t * k); void NextSessionTagRatchet (); uint64_t GetNextSessionTag (); @@ -60,8 +58,7 @@ namespace garlic void SetTagSetID (int tagsetID) { m_TagSetID = tagsetID; }; void Expire (); - bool IsExpired (uint64_t ts) const { return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; }; - + bool IsExpired (uint64_t ts) const { return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; }; private: @@ -89,32 +86,21 @@ namespace garlic { public: - ReceiveRatchetTagSet (std::shared_ptr session): m_Session (session) {}; + ReceiveRatchetTagSet (std::shared_ptr session, bool isNS = false): + m_Session (session), m_IsNS (isNS) {}; - std::shared_ptr GetSession () { return m_Session.lock (); }; + bool IsNS () const { return m_IsNS; }; + std::shared_ptr GetSession () { return m_Session; }; void SetTrimBehind (int index) { if (index > m_TrimBehindIndex) m_TrimBehindIndex = index; }; - virtual bool IsIndexExpired (int index) const { return m_Session.expired () || index < m_TrimBehindIndex; }; + virtual bool IsIndexExpired (int index) const; virtual bool HandleNextMessage (uint8_t * buf, size_t len, int index); private: int m_TrimBehindIndex = 0; - std::weak_ptr m_Session; - }; - - class NSRatchetTagSet: public ReceiveRatchetTagSet - { - public: - - NSRatchetTagSet (std::shared_ptr session): - ReceiveRatchetTagSet (session), m_DummySession (session) {}; - - bool IsNS () const { return true; }; - - private: - - std::shared_ptr m_DummySession; // we need a strong pointer for NS + std::shared_ptr m_Session; + bool m_IsNS; }; class DatabaseLookupTagSet: public ReceiveRatchetTagSet @@ -202,7 +188,7 @@ namespace garlic void CreateNonce (uint64_t seqn, uint8_t * nonce); bool GenerateEphemeralKeysAndEncode (uint8_t * buf); // buf is 32 bytes - std::shared_ptr CreateNewSessionTagset (); + void InitNewSessionTagset (std::shared_ptr tagsetNsr) const; bool HandleNewIncomingSession (const uint8_t * buf, size_t len); bool HandleNewOutgoingSessionReply (uint8_t * buf, size_t len); From b1262d54dec0de2f659873428bf53ba408dd4aee Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 Jan 2021 15:56:48 -0500 Subject: [PATCH 3984/6300] don't detach ECIESx25519 session from destination --- libi2pd/Datagram.cpp | 2 +- libi2pd/ECIESX25519AEADRatchetSession.cpp | 9 +++++++-- libi2pd/ECIESX25519AEADRatchetSession.h | 18 ++++++++++-------- libi2pd/Garlic.cpp | 11 +++++++++-- libi2pd/Garlic.h | 1 + libi2pd/Streaming.cpp | 2 +- 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 9d61f5e0..32db8ff1 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -295,7 +295,7 @@ namespace datagram } } - if (!m_RoutingSession || !m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) + if (!m_RoutingSession || m_RoutingSession->IsTerminated () || !m_RoutingSession->IsReadyToSend ()) { bool found = false; for (auto& it: m_PendingRoutingSessions) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index a83c9285..870995e8 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -94,15 +94,20 @@ namespace garlic m_ItermediateSymmKeys.erase (index); } - void RatchetTagSet::Expire () + void ReceiveRatchetTagSet::Expire () { if (!m_ExpirationTimestamp) m_ExpirationTimestamp = i2p::util::GetSecondsSinceEpoch () + ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT; } + bool ReceiveRatchetTagSet::IsExpired (uint64_t ts) const + { + return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; + } + bool ReceiveRatchetTagSet::IsIndexExpired (int index) const { - return index < m_TrimBehindIndex || !m_Session || !m_Session->GetOwner (); + return index < m_TrimBehindIndex || !m_Session || m_Session->IsTerminated (); } bool ReceiveRatchetTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 526f2ea4..974f1b15 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -56,10 +56,7 @@ namespace garlic int GetTagSetID () const { return m_TagSetID; }; void SetTagSetID (int tagsetID) { m_TagSetID = tagsetID; }; - - void Expire (); - bool IsExpired (uint64_t ts) const { return m_ExpirationTimestamp && ts > m_ExpirationTimestamp; }; - + private: union @@ -77,7 +74,6 @@ namespace garlic std::unordered_map > m_ItermediateSymmKeys; int m_TagSetID = 0; - uint64_t m_ExpirationTimestamp = 0; }; class ECIESX25519AEADRatchetSession; @@ -93,14 +89,18 @@ namespace garlic std::shared_ptr GetSession () { return m_Session; }; void SetTrimBehind (int index) { if (index > m_TrimBehindIndex) m_TrimBehindIndex = index; }; + void Expire (); + bool IsExpired (uint64_t ts) const; + virtual bool IsIndexExpired (int index) const; virtual bool HandleNextMessage (uint8_t * buf, size_t len, int index); private: - + int m_TrimBehindIndex = 0; std::shared_ptr m_Session; bool m_IsNS; + uint64_t m_ExpirationTimestamp = 0; }; class DatabaseLookupTagSet: public ReceiveRatchetTagSet @@ -170,7 +170,8 @@ namespace garlic const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } - + + void Terminate () { m_IsTerminated = true; } void SetDestination (const i2p::data::IdentHash& dest) // TODO: { if (!m_Destination) m_Destination.reset (new i2p::data::IdentHash (dest)); @@ -182,6 +183,7 @@ namespace garlic bool IsRatchets () const { return true; }; bool IsReadyToSend () const { return m_State != eSessionStateNewSessionSent; }; + bool IsTerminated () const { return m_IsTerminated; } uint64_t GetLastActivityTimestamp () const { return m_LastActivityTimestamp; }; private: @@ -221,7 +223,7 @@ namespace garlic std::shared_ptr m_SendTagset, m_NSRSendTagset; std::unique_ptr m_Destination;// TODO: might not need it std::list > m_AckRequests; // (tagsetid, index) - bool m_SendReverseKey = false, m_SendForwardKey = false; + bool m_SendReverseKey = false, m_SendForwardKey = false, m_IsTerminated = false; std::unique_ptr m_NextReceiveRatchet, m_NextSendRatchet; uint8_t m_PaddingSizes[32], m_NextPaddingSize; diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 17406b4f..a2b012db 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -445,9 +445,16 @@ namespace garlic void GarlicDestination::CleanUp () { + for (auto it: m_Sessions) + it.second->SetOwner (nullptr); m_Sessions.clear (); m_DeliveryStatusSessions.clear (); m_Tags.clear (); + for (auto it: m_ECIESx25519Sessions) + { + it.second->Terminate (); + it.second->SetOwner (nullptr); + } m_ECIESx25519Sessions.clear (); m_ECIESx25519Tags.clear (); } @@ -852,7 +859,7 @@ namespace garlic { if (it->second->CheckExpired (ts)) { - it->second->SetOwner (nullptr); + it->second->Terminate (); it = m_ECIESx25519Sessions.erase (it); } else @@ -1077,7 +1084,7 @@ namespace garlic { if (it->second->CanBeRestarted (i2p::util::GetSecondsSinceEpoch ())) { - it->second->SetOwner (nullptr); // detach + it->second->Terminate (); // detach m_ECIESx25519Sessions.erase (it); } else diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index a8806921..4288e74b 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -115,6 +115,7 @@ namespace garlic virtual bool MessageConfirmed (uint32_t msgID); virtual bool IsRatchets () const { return false; }; virtual bool IsReadyToSend () const { return true; }; + virtual bool IsTerminated () const { return !GetOwner (); }; virtual uint64_t GetLastActivityTimestamp () const { return 0; }; // non-zero for rathets only void SetLeaseSetUpdated () diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index f6bf7e8b..a873a660 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -764,7 +764,7 @@ namespace stream return; } } - if (!m_RoutingSession || !m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) // expired and detached or new session sent + if (!m_RoutingSession || m_RoutingSession->IsTerminated () || !m_RoutingSession->IsReadyToSend ()) // expired and detached or new session sent m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); if (!m_CurrentOutboundTunnel && m_RoutingSession) // first message to send { From aedcd1bcc031448eb56a138d6e767bd9780196ef Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 7 Jan 2021 14:51:23 -0500 Subject: [PATCH 3985/6300] remove tag after tagset expiration --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 2 +- libi2pd/Garlic.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 870995e8..b8c47b73 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -107,7 +107,7 @@ namespace garlic bool ReceiveRatchetTagSet::IsIndexExpired (int index) const { - return index < m_TrimBehindIndex || !m_Session || m_Session->IsTerminated (); + return index < m_TrimBehindIndex; } bool ReceiveRatchetTagSet::HandleNextMessage (uint8_t * buf, size_t len, int index) diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index a2b012db..42aca5be 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -876,7 +876,12 @@ namespace garlic numExpiredTags++; } else + { + auto session = it->second.tagset->GetSession (); + if (!session || session->IsTerminated()) + it->second.tagset->Expire (); ++it; + } } if (numExpiredTags > 0) LogPrint (eLogDebug, "Garlic: ", numExpiredTags, " ECIESx25519 tags expired for ", GetIdentHash().ToBase64 ()); @@ -1101,7 +1106,7 @@ namespace garlic auto it = m_ECIESx25519Sessions.find (staticKey); if (it != m_ECIESx25519Sessions.end ()) { - it->second->SetOwner (nullptr); + it->second->Terminate (); m_ECIESx25519Sessions.erase (it); } } From 29176dd9bff9b244c3dcf8b4c3063d8da409cf6b Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 9 Jan 2021 18:59:09 -0500 Subject: [PATCH 3986/6300] count last send time for expiration --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 3 ++- libi2pd/ECIESX25519AEADRatchetSession.h | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index b8c47b73..bfba7852 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1117,7 +1117,8 @@ namespace garlic bool ECIESX25519AEADRatchetSession::CheckExpired (uint64_t ts) { CleanupUnconfirmedLeaseSet (ts); - return ts > m_LastActivityTimestamp + ECIESX25519_EXPIRATION_TIMEOUT; + return ts > m_LastActivityTimestamp + ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT && // seconds + ts*1000 > m_LastSentTimestamp + ECIESX25519_SEND_EXPIRATION_TIMEOUT*1000; // milliseconds } std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 974f1b15..3aaa3d62 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -26,10 +26,10 @@ namespace i2p namespace garlic { const int ECIESX25519_RESTART_TIMEOUT = 120; // number of second since session creation we can restart session after - const int ECIESX25519_EXPIRATION_TIMEOUT = 480; // in seconds const int ECIESX25519_INACTIVITY_TIMEOUT = 90; // number of seconds we receive nothing and should restart if we can const int ECIESX25519_SEND_INACTIVITY_TIMEOUT = 5000; // number of milliseconds we can send empty(pyaload only) packet after - const int ECIESX25519_INCOMING_TAGS_EXPIRATION_TIMEOUT = 600; // in seconds + const int ECIESX25519_SEND_EXPIRATION_TIMEOUT = 480; // in seconds + const int ECIESX25519_RECEIVE_EXPIRATION_TIMEOUT = 600; // in seconds const int ECIESX25519_PREVIOUS_TAGSET_EXPIRATION_TIMEOUT = 180; // 180 const int ECIESX25519_TAGSET_MAX_NUM_TAGS = 8192; // number of tags we request new tagset after const int ECIESX25519_MIN_NUM_GENERATED_TAGS = 24; @@ -218,7 +218,7 @@ namespace garlic uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only std::shared_ptr m_EphemeralKeys; SessionState m_State = eSessionStateNew; - uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming + uint64_t m_SessionCreatedTimestamp = 0, m_LastActivityTimestamp = 0, // incoming (in seconds) m_LastSentTimestamp = 0; // in milliseconds std::shared_ptr m_SendTagset, m_NSRSendTagset; std::unique_ptr m_Destination;// TODO: might not need it From 2bc0850b0fe363015401ecdadd422101ab7fab92 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 13 Jan 2021 21:06:12 +0300 Subject: [PATCH 3987/6300] [android] add refresh on swipe in webconsole Signed-off-by: R4SAS --- android/AndroidManifest.xml | 4 +++ android/build.gradle | 1 + android/res/layout/activity_web_console.xml | 16 +++++++--- .../purplei2p/i2pd/WebConsoleActivity.java | 32 +++++++++++++++++-- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 8b011b8e..d0e8ecb6 100755 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -18,6 +18,10 @@ android:requestLegacyExternalStorage="true" android:theme="@android:style/Theme.Holo.Light.DarkActionBar" android:usesCleartextTraffic="true"> + + diff --git a/android/build.gradle b/android/build.gradle index dbe75759..0c208c91 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -21,6 +21,7 @@ repositories { dependencies { implementation 'androidx.core:core:1.0.2' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' } android { diff --git a/android/res/layout/activity_web_console.xml b/android/res/layout/activity_web_console.xml index 5724b387..60d22a0b 100644 --- a/android/res/layout/activity_web_console.xml +++ b/android/res/layout/activity_web_console.xml @@ -1,14 +1,22 @@ - - + android:layout_height="fill_parent" + android:id="@+id/swipe"> + + + + + diff --git a/android/src/org/purplei2p/i2pd/WebConsoleActivity.java b/android/src/org/purplei2p/i2pd/WebConsoleActivity.java index 0fcf37fe..5e20e8fa 100644 --- a/android/src/org/purplei2p/i2pd/WebConsoleActivity.java +++ b/android/src/org/purplei2p/i2pd/WebConsoleActivity.java @@ -2,14 +2,18 @@ package org.purplei2p.i2pd; import android.app.Activity; import android.os.Bundle; +import android.os.Handler; import android.view.MenuItem; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import java.util.Objects; public class WebConsoleActivity extends Activity { + private WebView webView; + private SwipeRefreshLayout swipeRefreshLayout; @Override protected void onCreate(Bundle savedInstanceState) { @@ -18,19 +22,43 @@ public class WebConsoleActivity extends Activity { Objects.requireNonNull(getActionBar()).setDisplayHomeAsUpEnabled(true); - final WebView webView = findViewById(R.id.webview1); + webView = (WebView) findViewById(R.id.webview1); webView.setWebViewClient(new WebViewClient()); final WebSettings webSettings = webView.getSettings(); webSettings.setBuiltInZoomControls(true); webSettings.setJavaScriptEnabled(false); webView.loadUrl("http://127.0.0.1:7070"); // TODO: instead 7070 I2Pd....HttpPort + + swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe); + swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + swipeRefreshLayout.setRefreshing(true); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + swipeRefreshLayout.setRefreshing(false); + webView.reload(); + } + }, 1000); + } + }); + } + + @Override + public void onBackPressed() { + if (webView.canGoBack()) { + webView.goBack(); + } else { + super.onBackPressed(); + } } public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); - if (id==android.R.id.home) { + if (id == android.R.id.home) { finish(); return true; } From 8f25b66760ed2863c2468f464ab6e959ba3a816b Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 14 Jan 2021 11:24:03 -0500 Subject: [PATCH 3988/6300] limit tunnel length to 8 hops --- libi2pd/Tunnel.cpp | 4 ++-- libi2pd/Tunnel.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 42eeeb5d..5355a7b6 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -42,8 +42,8 @@ namespace tunnel void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { auto numHops = m_Config->GetNumHops (); - int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops; - auto msg = NewI2NPShortMessage (); + int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; + auto msg = numRecords <= STANDARD_NUM_RECORDS ? NewI2NPShortMessage () : NewI2NPMessage (); *msg->GetPayload () = numRecords; msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1; // shuffle records diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 37c5cddb..7e8edca7 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -37,7 +37,8 @@ namespace tunnel const int TUNNEL_RECREATION_THRESHOLD = 90; // 1.5 minutes const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds const int STANDARD_NUM_RECORDS = 4; // in VariableTunnelBuild message - + const int MAX_NUM_RECORDS = 8; + enum TunnelState { eTunnelStatePending, From 1235d18d67baec57a2f4a54b8ef7b5b16582af26 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 17 Jan 2021 17:15:41 -0500 Subject: [PATCH 3989/6300] pass address to NTCP2 session --- libi2pd/NTCP2.cpp | 8 +++++--- libi2pd/NTCP2.h | 3 ++- libi2pd/Transports.cpp | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 1706399d..eb03dc62 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -317,7 +317,8 @@ namespace transport return true; } - NTCP2Session::NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter): + NTCP2Session::NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter, + std::shared_ptr addr): TransportSession (in_RemoteRouter, NTCP2_ESTABLISH_TIMEOUT), m_Server (server), m_Socket (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false), @@ -332,7 +333,8 @@ namespace transport if (in_RemoteRouter) // Alice { m_Establisher->m_RemoteIdentHash = GetRemoteIdentity ()->GetIdentHash (); - auto addr = in_RemoteRouter->GetNTCP2Address (true); // we need a published address + if (!addr) + addr = in_RemoteRouter->GetNTCP2Address (true); // we need a published address if (addr) { memcpy (m_Establisher->m_RemoteStaticKey, addr->ntcp2->staticKey, 32); @@ -653,7 +655,7 @@ namespace transport SendTerminationAndTerminate (eNTCP2Message3Error); return; } - auto addr = ri.GetNTCP2Address (false); // any NTCP2 address + auto addr = ri.GetNTCP2Address (false, false); // any NTCP2 address including v6 if (!addr) { LogPrint (eLogError, "NTCP2: No NTCP2 address found in SessionConfirmed"); diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index 5c9ecac9..f80ba75a 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -123,7 +123,8 @@ namespace transport { public: - NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter = nullptr); + NTCP2Session (NTCP2Server& server, std::shared_ptr in_RemoteRouter = nullptr, + std::shared_ptr addr = nullptr); ~NTCP2Session (); void Terminate (); void TerminateByTimeout (); diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 5c2fcb6d..574810b8 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -398,7 +398,7 @@ namespace transport auto address = peer.router->GetNTCP2Address (true, !context.SupportsV6 ()); // published only if (address && !peer.router->IsUnreachable () && (!m_CheckReserved || !i2p::util::net::IsInReservedRange(address->host))) { - auto s = std::make_shared (*m_NTCP2Server, peer.router); + auto s = std::make_shared (*m_NTCP2Server, peer.router, address); if(m_NTCP2Server->UsingProxy()) { From 1a9e11d86daf5ccf5c11161fd9db9c1622f82819 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 Jan 2021 12:58:27 -0500 Subject: [PATCH 3990/6300] don't send updated LeaseSet through a terminated session --- libi2pd/Streaming.cpp | 4 +++- libi2pd/Streaming.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index a873a660..0d482303 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -817,7 +817,7 @@ namespace stream void Stream::SendUpdatedLeaseSet () { - if (m_RoutingSession) + if (m_RoutingSession && !m_RoutingSession->IsTerminated ()) { if (m_RoutingSession->IsLeaseSetNonConfirmed ()) { @@ -838,6 +838,8 @@ namespace stream SendQuickAck (); } } + else + SendQuickAck (); } void Stream::ScheduleResend () diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index ab3c4439..fe035136 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -61,7 +61,7 @@ namespace stream const int SYN_TIMEOUT = 200; // how long we wait for SYN after follow-on, in milliseconds const size_t MAX_PENDING_INCOMING_BACKLOG = 128; const int PENDING_INCOMING_TIMEOUT = 10; // in seconds - const int MAX_RECEIVE_TIMEOUT = 30; // in seconds + const int MAX_RECEIVE_TIMEOUT = 20; // in seconds struct Packet { From e0cec79ad67d5df227eaa682c5b4dc837865ab3e Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 Jan 2021 18:58:16 -0500 Subject: [PATCH 3991/6300] try both ipv4 and ipv6 NTCP2 addresses if presented --- libi2pd/RouterInfo.cpp | 18 ++++++++++++++++++ libi2pd/RouterInfo.h | 2 ++ libi2pd/Transports.cpp | 22 +++++++++++++++++----- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 13776b81..afbaadbf 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -935,6 +935,24 @@ namespace data }); } + std::shared_ptr RouterInfo::GetPublishedNTCP2V4Address () const + { + return GetAddress ( + [](std::shared_ptr address)->bool + { + return address->IsPublishedNTCP2 () && address->host.is_v4 (); + }); + } + + std::shared_ptr RouterInfo::GetPublishedNTCP2V6Address () const + { + return GetAddress ( + [](std::shared_ptr address)->bool + { + return address->IsPublishedNTCP2 () && address->host.is_v6 (); + }); + } + std::shared_ptr RouterInfo::GetProfile () const { if (!m_Profile) diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 733ea44a..ff767a8b 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -154,6 +154,8 @@ namespace data int GetVersion () const { return m_Version; }; Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr std::shared_ptr GetNTCP2Address (bool publishedOnly, bool v4only = true) const; + std::shared_ptr GetPublishedNTCP2V4Address () const; + std::shared_ptr GetPublishedNTCP2V6Address () const; std::shared_ptr GetSSUAddress (bool v4only = true) const; std::shared_ptr GetSSUV6Address () const; diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 574810b8..c559c65c 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -389,13 +389,23 @@ namespace transport peer.router = netdb.FindRouter (ident); // try to get new one from netdb if (peer.router) // we have RI already { - if (!peer.numAttempts) // NTCP2 + if (peer.numAttempts < 2) // NTCP2, 0 - ipv6, 1- ipv4 { - peer.numAttempts++; if (m_NTCP2Server) // we support NTCP2 { - // NTCP2 have priority over NTCP - auto address = peer.router->GetNTCP2Address (true, !context.SupportsV6 ()); // published only + std::shared_ptr address; + if (!peer.numAttempts) // NTCP2 ipv6 + { + if (context.SupportsV6 ()) + address = peer.router->GetPublishedNTCP2V6Address (); + peer.numAttempts++; + } + if (!address && peer.numAttempts == 1) // NTCP2 ipv4 + { + if (context.SupportsV4 ()) + address = peer.router->GetPublishedNTCP2V4Address (); + peer.numAttempts++; + } if (address && !peer.router->IsUnreachable () && (!m_CheckReserved || !i2p::util::net::IsInReservedRange(address->host))) { auto s = std::make_shared (*m_NTCP2Server, peer.router, address); @@ -415,8 +425,10 @@ namespace transport return true; } } + else + peer.numAttempts = 2; // switch to SSU } - if (peer.numAttempts == 1)// SSU + if (peer.numAttempts == 2)// SSU { peer.numAttempts++; if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ())) From 6fc5f88a3bb6feeb700b44b92da2355fae2d00b3 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 20 Jan 2021 19:19:34 -0500 Subject: [PATCH 3992/6300] dump addressbook in hosts.txt format --- libi2pd/Config.cpp | 3 +- libi2pd_client/AddressBook.cpp | 83 ++++++++++++++++++++++------------ 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index d16d82d3..b17b2609 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -212,7 +212,8 @@ namespace config { ("addressbook.defaulturl", value()->default_value( "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt" ), "AddressBook subscription URL for initial setup") - ("addressbook.subscriptions", value()->default_value(""), "AddressBook subscriptions URLs, separated by comma"); + ("addressbook.subscriptions", value()->default_value(""), "AddressBook subscriptions URLs, separated by comma") + ("addressbook.hostsfile", value()->default_value(""), "File to dump addresses in hosts.txt format"); options_description trust("Trust options"); trust.add_options() diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index a69bd660..ba2d8276 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -34,14 +34,13 @@ namespace client // TODO: this is actually proxy class class AddressBookFilesystemStorage: public AddressBookStorage { - private: - i2p::fs::HashedStorage storage; - std::string etagsPath, indexPath, localPath; - public: + AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") { i2p::config::GetOption("persist.addressbook", m_IsPersist); + if (m_IsPersist) + i2p::config::GetOption("addressbook.hostsfile", m_HostsFile); } std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const; void AddAddress (std::shared_ptr address); @@ -62,7 +61,10 @@ namespace client private: + i2p::fs::HashedStorage storage; + std::string etagsPath, indexPath, localPath; bool m_IsPersist; + std::string m_HostsFile; // file to dump hosts.txt, empty if not used }; bool AddressBookFilesystemStorage::Init() @@ -183,35 +185,59 @@ namespace client int AddressBookFilesystemStorage::Save (const std::map >& addresses) { - if (addresses.empty()) { + if (addresses.empty()) + { LogPrint(eLogWarning, "Addressbook: not saving empty addressbook"); return 0; } int num = 0; - std::ofstream f (indexPath, std::ofstream::out); // in text mode - - if (!f.is_open ()) { - LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath); - return 0; - } - - for (const auto& it: addresses) { - if (it.second->IsValid ()) - { - f << it.first << ","; - if (it.second->IsIdentHash ()) - f << it.second->identHash.ToBase32 (); - else - f << it.second->blindedPublicKey->ToB33 (); - f << std::endl; - num++; + // save index file + std::ofstream f (indexPath, std::ofstream::out); // in text mode + if (f.is_open ()) + { + for (const auto& it: addresses) + { + if (it.second->IsValid ()) + { + f << it.first << ","; + if (it.second->IsIdentHash ()) + f << it.second->identHash.ToBase32 (); + else + f << it.second->blindedPublicKey->ToB33 (); + f << std::endl; + num++; + } + else + LogPrint (eLogWarning, "Addressbook: invalid address ", it.first); + } + LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); } else - LogPrint (eLogWarning, "Addressbook: invalid address ", it.first); - } - LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); + LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath); + } + if (!m_HostsFile.empty ()) + { + // dump full hosts.txt + std::ofstream f (m_HostsFile, std::ofstream::out); // in text mode + if (f.is_open ()) + { + for (const auto& it: addresses) + { + std::shared_ptr addr; + if (it.second->IsIdentHash ()) + { + addr = GetAddress (it.second->identHash); + if (addr) + f << it.first << "=" << addr->ToBase64 () << std::endl; + } + } + } + else + LogPrint (eLogWarning, "Addressbook: Can't open ", m_HostsFile); + } + return num; } @@ -494,7 +520,7 @@ namespace client while (!f.eof ()) { getline(f, s); - if (!s.length()) continue; // skip empty line + if (s.empty () || s[0] == '#') continue; // skip empty line or comment m_Subscriptions.push_back (std::make_shared (*this, s)); } LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); @@ -507,9 +533,10 @@ namespace client std::vector subsList; boost::split(subsList, subscriptionURLs, boost::is_any_of(","), boost::token_compress_on); - for (size_t i = 0; i < subsList.size (); i++) + for (const auto& s: subsList) { - m_Subscriptions.push_back (std::make_shared (*this, subsList[i])); + if (s.empty () || s[0] == '#') continue; // skip empty line or comment + m_Subscriptions.push_back (std::make_shared (*this, s)); } LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); } From 67dab9b6d26bca67a963b455a58fd808eea093aa Mon Sep 17 00:00:00 2001 From: Dimitris Apostolou Date: Thu, 21 Jan 2021 11:07:01 +0200 Subject: [PATCH 3993/6300] Fix clang warning --- libi2pd/NTCP2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index eb03dc62..d72aa014 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1519,7 +1519,7 @@ namespace transport boost::asio::streambuf * readbuff = new boost::asio::streambuf; boost::asio::async_read_until(conn->GetSocket(), *readbuff, "\r\n\r\n", - [this, readbuff, timer, conn] (const boost::system::error_code & ec, std::size_t transferred) + [readbuff, timer, conn] (const boost::system::error_code & ec, std::size_t transferred) { if(ec) { From 9d5bb1b2b696f1f11d5c136e046872390037ff73 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 23 Jan 2021 21:25:52 -0500 Subject: [PATCH 3994/6300] drop routing path for LeaseSet resend --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index bfba7852..81b83477 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -914,10 +914,13 @@ namespace garlic payloadLen += msg->GetPayloadLength () + 13; if (m_Destination) payloadLen += 32; } - auto leaseSet = (GetLeaseSetUpdateStatus () == eLeaseSetUpdated || - (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && - ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT)) ? - GetOwner ()->GetLeaseSet () : nullptr; + if (GetLeaseSetUpdateStatus () == eLeaseSetSubmitted && ts > GetLeaseSetSubmissionTime () + LEASET_CONFIRMATION_TIMEOUT) + { + // resubmit non-confirmed LeaseSet + SetLeaseSetUpdateStatus (eLeaseSetUpdated); + SetSharedRoutingPath (nullptr); // invalidate path since leaseset was not confirmed + } + auto leaseSet = (GetLeaseSetUpdateStatus () == eLeaseSetUpdated) ? GetOwner ()->GetLeaseSet () : nullptr; if (leaseSet) { payloadLen += leaseSet->GetBufferLen () + DATABASE_STORE_HEADER_SIZE + 13; From 2e0019c8c8c7f524710c9abf581e8794eaac267a Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 24 Jan 2021 11:34:11 -0500 Subject: [PATCH 3995/6300] check if NTCP2 address is valid before connection attempt --- libi2pd/Transports.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index c559c65c..b76df540 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -397,16 +397,24 @@ namespace transport if (!peer.numAttempts) // NTCP2 ipv6 { if (context.SupportsV6 ()) + { address = peer.router->GetPublishedNTCP2V6Address (); + if (address && m_CheckReserved && !i2p::util::net::IsInReservedRange(address->host)) + address = nullptr; + } peer.numAttempts++; } if (!address && peer.numAttempts == 1) // NTCP2 ipv4 { - if (context.SupportsV4 ()) + if (context.SupportsV4 () && !peer.router->IsUnreachable ()) + { address = peer.router->GetPublishedNTCP2V4Address (); + if (address && m_CheckReserved && !i2p::util::net::IsInReservedRange(address->host)) + address = nullptr; + } peer.numAttempts++; } - if (address && !peer.router->IsUnreachable () && (!m_CheckReserved || !i2p::util::net::IsInReservedRange(address->host))) + if (address) { auto s = std::make_shared (*m_NTCP2Server, peer.router, address); From 2d998aba435443a190a82e34b31e858ae436e76d Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 24 Jan 2021 15:44:54 -0500 Subject: [PATCH 3996/6300] fixed typo --- libi2pd/Transports.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index b76df540..18459264 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -399,7 +399,7 @@ namespace transport if (context.SupportsV6 ()) { address = peer.router->GetPublishedNTCP2V6Address (); - if (address && m_CheckReserved && !i2p::util::net::IsInReservedRange(address->host)) + if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) address = nullptr; } peer.numAttempts++; @@ -409,7 +409,7 @@ namespace transport if (context.SupportsV4 () && !peer.router->IsUnreachable ()) { address = peer.router->GetPublishedNTCP2V4Address (); - if (address && m_CheckReserved && !i2p::util::net::IsInReservedRange(address->host)) + if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) address = nullptr; } peer.numAttempts++; From 07282ec39f5457b4fc8eb338f1c8bcec5692a627 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 24 Jan 2021 19:42:44 -0500 Subject: [PATCH 3997/6300] get local yggdrasil ipv6 address --- libi2pd/util.cpp | 30 ++++++++++++++++++++++++++++++ libi2pd/util.h | 1 + 2 files changed, 31 insertions(+) diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 794a3f14..bb3f60d1 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -416,6 +416,36 @@ namespace net #endif } + boost::asio::ip::address_v6 GetYggdrasilAddress () + { +#ifdef _WIN32 + // TODO: implement + return boost::asio::ip::address_v6 (); +#else + ifaddrs * addrs = nullptr; + auto err = getifaddrs(&addrs); + if (!err) + { + ifaddrs * cur = addrs; + while(cur) + { + if (cur->ifa_addr && cur->ifa_addr->sa_family == AF_INET6) + { + sockaddr_in6* sa = (sockaddr_in6*)cur->ifa_addr; + if (sa->sin6_addr.s6_addr[0] == 0x02 || sa->sin6_addr.s6_addr[0] == 0x03) + { + boost::asio::ip::address_v6::bytes_type bytes; + memcpy (bytes.data (), &sa->sin6_addr, 16); + return boost::asio::ip::address_v6 (bytes); + } + } + cur = cur->ifa_next; + } + } + return boost::asio::ip::address_v6 (); +#endif + } + bool IsInReservedRange (const boost::asio::ip::address& host) { // https://en.wikipedia.org/wiki/Reserved_IP_addresses if(host.is_v4()) diff --git a/libi2pd/util.h b/libi2pd/util.h index e6de09ed..7a45dc9b 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -188,6 +188,7 @@ namespace util { int GetMTU (const boost::asio::ip::address& localAddress); const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6=false); + boost::asio::ip::address_v6 GetYggdrasilAddress (); bool IsInReservedRange (const boost::asio::ip::address& host); } } From ed4c00e4f4e662d9b3faa40de9a2fa99c265dc3f Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 24 Jan 2021 21:21:35 -0500 Subject: [PATCH 3998/6300] check yggdrasil ipv6 range --- libi2pd/util.cpp | 5 ++++- libi2pd/util.h | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index bb3f60d1..123d233c 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -446,7 +446,8 @@ namespace net #endif } - bool IsInReservedRange (const boost::asio::ip::address& host) { + bool IsInReservedRange (const boost::asio::ip::address& host, bool checkYggdrasil) + { // https://en.wikipedia.org/wiki/Reserved_IP_addresses if(host.is_v4()) { @@ -486,6 +487,8 @@ namespace net if (ipv6_address >= it.first && ipv6_address <= it.second) return true; } + if (checkYggdrasil && (ipv6_address[0] == 0x02 || ipv6_address[0] == 0x03)) // yggdrasil? + return true; } return false; } diff --git a/libi2pd/util.h b/libi2pd/util.h index 7a45dc9b..fedab8e1 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -189,7 +189,7 @@ namespace util int GetMTU (const boost::asio::ip::address& localAddress); const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6=false); boost::asio::ip::address_v6 GetYggdrasilAddress (); - bool IsInReservedRange (const boost::asio::ip::address& host); + bool IsInReservedRange (const boost::asio::ip::address& host, bool checkYggdrasil = true); } } } From d13fbe5549ae328911ce8e98ff97ace4c4d0ef41 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 25 Jan 2021 19:48:33 -0500 Subject: [PATCH 3999/6300] support reseed throught the Yggdrasil --- libi2pd/Config.cpp | 3 ++ libi2pd/HTTP.cpp | 9 +++- libi2pd/Reseed.cpp | 129 ++++++++++++++++++++++++++++++--------------- libi2pd/Reseed.h | 7 ++- 4 files changed, 103 insertions(+), 45 deletions(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index b17b2609..bceca198 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -205,6 +205,9 @@ namespace config { "https://reseed.i2pgit.org/," "https://i2p.novg.net/" ), "Reseed URLs, separated by comma") + ("reseed.yggurls", value()->default_value( + "http://[324:9de3:fea4:f6ac::ace]:7070/" + ), "Reseed URLs through the Yggdrasil, separated by comma") ; options_description addressbook("AddressBook options"); diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index 484f4c0c..d4ca3b8b 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -111,7 +111,14 @@ namespace http { pos_p = pos_c + 1; } /* hostname[:port][/path] */ - pos_c = url.find_first_of(":/", pos_p); + if (url[pos_p] == '[') // ipv6 + { + auto pos_b = url.find(']', pos_p); + if (pos_b == std::string::npos) return false; + pos_c = url.find_first_of(":/", pos_b); + } + else + pos_c = url.find_first_of(":/", pos_p); if (pos_c == std::string::npos) { /* only hostname, without post and path */ host = url.substr(pos_p, std::string::npos); diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 2812f413..80b834e6 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -86,7 +86,15 @@ namespace data std::vector httpsReseedHostList; boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on); - if (reseedURLs.length () == 0) + std::vector yggReseedHostList; + if (!i2p::util::net::GetYggdrasilAddress ().is_unspecified ()) + { + LogPrint (eLogInfo, "Reseed: yggdrasil is supported"); + std::string yggReseedURLs; i2p::config::GetOption("reseed.yggurls", yggReseedURLs); + boost::split(yggReseedHostList, yggReseedURLs, boost::is_any_of(","), boost::token_compress_on); + } + + if (httpsReseedHostList.empty () && yggReseedHostList.empty()) { LogPrint (eLogWarning, "Reseed: No reseed servers specified"); return 0; @@ -95,9 +103,12 @@ namespace data int reseedRetries = 0; while (reseedRetries < 10) { - auto ind = rand () % httpsReseedHostList.size (); - std::string reseedUrl = httpsReseedHostList[ind] + "i2pseeds.su3"; - auto num = ReseedFromSU3Url (reseedUrl); + auto ind = rand () % (httpsReseedHostList.size () + yggReseedHostList.size ()); + bool isHttps = ind < httpsReseedHostList.size (); + std::string reseedUrl = isHttps ? httpsReseedHostList[ind] : + yggReseedHostList[ind - httpsReseedHostList.size ()]; + reseedUrl += "i2pseeds.su3"; + auto num = ReseedFromSU3Url (reseedUrl, isHttps); if (num > 0) return num; // success reseedRetries++; } @@ -110,10 +121,10 @@ namespace data * @param url * @return number of entries added to netDb */ - int Reseeder::ReseedFromSU3Url (const std::string& url) + int Reseeder::ReseedFromSU3Url (const std::string& url, bool isHttps) { LogPrint (eLogInfo, "Reseed: Downloading SU3 from ", url); - std::string su3 = HttpsRequest (url); + std::string su3 = isHttps ? HttpsRequest (url) : YggdrasilRequest (url); if (su3.length () > 0) { std::stringstream s(su3); @@ -667,42 +678,7 @@ namespace data if (!ecode) { LogPrint (eLogDebug, "Reseed: Connected to ", url.host, ":", url.port); - i2p::http::HTTPReq req; - req.uri = url.to_string(); - req.AddHeader("User-Agent", "Wget/1.11.4"); - req.AddHeader("Connection", "close"); - s.write_some (boost::asio::buffer (req.to_string())); - // read response - std::stringstream rs; - 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 - 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; + return ReseedRequest (s, url.to_string()); } else LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ()); @@ -711,5 +687,74 @@ namespace data LogPrint (eLogError, "Reseed: Couldn't connect to ", url.host, ": ", ecode.message ()); return ""; } + + template + std::string Reseeder::ReseedRequest (Stream& s, const std::string& uri) + { + boost::system::error_code ecode; + i2p::http::HTTPReq req; + req.uri = uri; + req.AddHeader("User-Agent", "Wget/1.11.4"); + req.AddHeader("Connection", "close"); + s.write_some (boost::asio::buffer (req.to_string())); + // read response + std::stringstream rs; + 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 + std::string data = rs.str(); + i2p::http::HTTPRes res; + int len = res.parse(data); + if (len <= 0) { + LogPrint(eLogWarning, "Reseed: incomplete/broken response from ", uri); + return ""; + } + if (res.code != 200) { + LogPrint(eLogError, "Reseed: failed to reseed from ", uri, ", http code ", res.code); + return ""; + } + data.erase(0, len); /* drop http headers from response */ + LogPrint(eLogDebug, "Reseed: got ", data.length(), " bytes of data from ", uri); + if (res.is_chunked()) { + std::stringstream in(data), out; + if (!i2p::http::MergeChunkedResponse(in, out)) { + LogPrint(eLogWarning, "Reseed: failed to merge chunked response from ", uri); + return ""; + } + LogPrint(eLogDebug, "Reseed: got ", data.length(), "(", out.tellg(), ") bytes of data from ", uri); + data = out.str(); + } + return data; + } + + std::string Reseeder::YggdrasilRequest (const std::string& address) + { + i2p::http::URL url; + if (!url.parse(address)) + { + LogPrint(eLogError, "Reseed: failed to parse url: ", address); + return ""; + } + url.schema = "http"; + if (!url.port) url.port = 80; + + boost::system::error_code ecode; + boost::asio::io_service service; + boost::asio::ip::tcp::socket s(service, boost::asio::ip::tcp::v6()); + + s.connect (boost::asio::ip::tcp::endpoint (boost::asio::ip::address_v6::from_string (url.host), url.port), ecode); + if (!ecode) + { + LogPrint (eLogDebug, "Reseed: Connected to yggdrasil ", url.host, ":", url.port); + return ReseedRequest (s, url.to_string()); + } + else + LogPrint (eLogError, "Reseed: Couldn't connect to yggdrasil ", url.host, ": ", ecode.message ()); + + return ""; + } } } diff --git a/libi2pd/Reseed.h b/libi2pd/Reseed.h index 345b45bf..8039c07e 100644 --- a/libi2pd/Reseed.h +++ b/libi2pd/Reseed.h @@ -31,7 +31,6 @@ namespace data ~Reseeder(); void Bootstrap (); int ReseedFromServers (); - int ReseedFromSU3Url (const std::string& url); int ProcessSU3File (const char * filename); int ProcessZIPFile (const char * filename); @@ -39,6 +38,7 @@ namespace data private: + int ReseedFromSU3Url (const std::string& url, bool isHttps = true); void LoadCertificate (const std::string& filename); int ProcessSU3Stream (std::istream& s); @@ -47,7 +47,10 @@ namespace data bool FindZipDataDescriptor (std::istream& s); std::string HttpsRequest (const std::string& address); - + std::string YggdrasilRequest (const std::string& address); + template + std::string ReseedRequest (Stream& s, const std::string& uri); + private: std::map m_SigningKeys; From fd73aab7d034267db29720896f4f2db51cca0445 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 25 Jan 2021 19:53:00 -0500 Subject: [PATCH 4000/6300] acetone's certificate --- .../reseed/acetone_at_mail.i2p.crt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 contrib/certificates/reseed/acetone_at_mail.i2p.crt diff --git a/contrib/certificates/reseed/acetone_at_mail.i2p.crt b/contrib/certificates/reseed/acetone_at_mail.i2p.crt new file mode 100644 index 00000000..13f9f17d --- /dev/null +++ b/contrib/certificates/reseed/acetone_at_mail.i2p.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIEctG1gDANBgkqhkiG9w0BAQ0FADBwMQswCQYDVQQGEwJY +WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt +b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEZMBcGA1UEAwwQYWNldG9uZUBtYWls +LmkycDAeFw0yMTAxMjUxMDMyMjBaFw0zMTAxMjMxMDMyMjBaMHAxCzAJBgNVBAYT +AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u +eW1vdXMgTmV0d29yazEMMAoGA1UECwwDSTJQMRkwFwYDVQQDDBBhY2V0b25lQG1h +aWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwqF/BRRmvZ54 +5XArgxbytDi7m7MDjFE/whUADruHj/9jXGCxE8DDiiKTt3yhfakV0SNo5xk7AMD+ +wqiSNC5JCHTm18gd2M4cQLIaOVRqucLLge4XVgk2WPX6OT98wfxh7mqA3wlSdEpj +dY3Txtkf7VfZLicG76/RBtLFW3aBdsn63hZaQqZE4x/5MJyPVZx59+lys5RmMi0o +LpXJy4HOu1/Gl1iKDJoI/ARFG3y7uP/B+ZtZBitJetTs0HcqycnNJq0tVZf2HiGF +JNy67AL4foxNYPXP6QsvXvp6LRpGANaBCkFCBlriSF+x1zO2H3uAkRnuLYXuKIfB +HudejTp4R57VgZGiHYoawHaF17FVAApue9G8O82XYECjhET35B9yFoOBHTvaMxLU +CKrmayH8KMQon95mfe1qpoO3/YDa8DCxkjAfjdtytat7nt2pGZMH6/cLJxcFiofh +RtRVvb+omv/X12j/6iCFrwP4NvBnAZsa736igbjpyee5n+CSyYxd9cJkRX1vQVk7 +WFSqL58Pz+g6CKJmdMPvqNOfUQ6mieBeejmx35B4pLzLcoNxw8R3O1+I2l4dg042 +dEydKRQNwdzOec4jYwnKR40iwIyZxpchXWGRbBdyF5RQCbIIo60QBJlfXMJ2svan +q5lYIeWeY3mlODXu4KH4K09y10KT8FsCAwEAAaMhMB8wHQYDVR0OBBYEFMh+DoIL +APNiu2o+6I9A49joNYQuMA0GCSqGSIb3DQEBDQUAA4ICAQBFeOJi0rmkqN5/E3IB +nE2x4mUeLI82tUcN2D3Yu8J81vy4DnH+oMRQFDtYEHW5pfirRmgSZ7MQwYQnqWLp +iTE7SyCxlqGrmVsYp7PzfS1pUT2QeWPtsNYUDdraG0Zr9BkIGB60VMhjMSa9WUrj +lbchzr6E/j/EsEOE7IK08JxIDKCDZM2LLwis4tAM6tmiylkMf2RlUBIRBs1TCO+q +x3yByttNE2P4nQyQVQpjc1qsaOMvJvbxun37dwo+oTQy+hwkA86BWTDRYdN3xwOk +OfAOtlX6zM/wCKMN0ZRnjZoh59ZCn4JXokt3IjZ4n8qJOuJFRKeKGmGeKA8uaGW8 +ih5tdB99Gu5Z8LOT1FxAJKwQBn5My0JijPoMit4B0WKNC8hy2zc2YvNfflu1ZRj5 +wF4E5ktbtT/LWFSoRPas/GFS8wSXk/kbSB0ArDcRRszb3JHqbALmSQxngz3rfwb3 +SHwQIIg956gjMDueEX5CrGrMqigiK53b9fqtpghUrHDsqtEXqeImpAY65PX1asqo +metDNuETHF7XrAjP7TGJfnrYQyeK90iS7j1G68ScBGkKY2nsTnFoXkSk5s5D338E +SUzPaOlh91spmkVY6gQTVQ7BakADBHw+zBgDA1gBN/4JPvgN36hquj63+aG1cKy3 +3ZUnv2ipo2fpr69NtuBnutK6gw== +-----END CERTIFICATE----- From 5931cb59abe19c0722d3bc51dc99145c067e01f5 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 26 Jan 2021 18:53:56 +0300 Subject: [PATCH 4001/6300] fix thread setname on NetBSD Signed-off-by: R4SAS --- libi2pd/util.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 123d233c..ff27718a 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -119,10 +119,12 @@ namespace util } void SetThreadName (const char *name) { -#if defined (__APPLE__) +#if defined(__APPLE__) pthread_setname_np(name); #elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); +#elif defined(__NetBSD__) + pthread_setname_np(pthread_self(), "%s", (void *)name); #else pthread_setname_np(pthread_self(), name); #endif @@ -437,15 +439,15 @@ namespace net boost::asio::ip::address_v6::bytes_type bytes; memcpy (bytes.data (), &sa->sin6_addr, 16); return boost::asio::ip::address_v6 (bytes); - } - } + } + } cur = cur->ifa_next; - } + } } return boost::asio::ip::address_v6 (); -#endif - } - +#endif + } + bool IsInReservedRange (const boost::asio::ip::address& host, bool checkYggdrasil) { // https://en.wikipedia.org/wiki/Reserved_IP_addresses From 85902b358ac52332dde696c41f46aded8e617ef7 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 26 Jan 2021 13:43:20 -0500 Subject: [PATCH 4002/6300] remove [] from yggdrasil reseed address --- libi2pd/Reseed.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 80b834e6..33471b10 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -91,7 +91,8 @@ namespace data { LogPrint (eLogInfo, "Reseed: yggdrasil is supported"); std::string yggReseedURLs; i2p::config::GetOption("reseed.yggurls", yggReseedURLs); - boost::split(yggReseedHostList, yggReseedURLs, boost::is_any_of(","), boost::token_compress_on); + if (!yggReseedURLs.empty ()) + boost::split(yggReseedHostList, yggReseedURLs, boost::is_any_of(","), boost::token_compress_on); } if (httpsReseedHostList.empty () && yggReseedHostList.empty()) @@ -744,8 +745,11 @@ namespace data boost::system::error_code ecode; boost::asio::io_service service; boost::asio::ip::tcp::socket s(service, boost::asio::ip::tcp::v6()); - - s.connect (boost::asio::ip::tcp::endpoint (boost::asio::ip::address_v6::from_string (url.host), url.port), ecode); + + if (url.host.length () < 2) return ""; // assume [] + auto host = url.host.substr (1, url.host.length () - 2); + LogPrint (eLogDebug, "Reseed: Connecting to yggdrasil ", url.host, ":", url.port); + s.connect (boost::asio::ip::tcp::endpoint (boost::asio::ip::address_v6::from_string (host), url.port), ecode); if (!ecode) { LogPrint (eLogDebug, "Reseed: Connected to yggdrasil ", url.host, ":", url.port); From 7c8280934a7a25db6ac802cffdeab4f2f8470b77 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 27 Jan 2021 06:48:35 +0300 Subject: [PATCH 4003/6300] update addressbook subscriptions Signed-off-by: R4SAS --- contrib/i2pd.conf | 2 +- contrib/subscriptions.txt | 3 ++- libi2pd/Config.cpp | 2 +- libi2pd_client/HTTPProxy.cpp | 5 +++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 5ef39bc9..13801076 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -237,4 +237,4 @@ verify = true # avx = true ## Force usage of CPU instructions set, even if they not found ## DO NOT TOUCH that option if you really don't know what are you doing! -# force = false \ No newline at end of file +# force = false diff --git a/contrib/subscriptions.txt b/contrib/subscriptions.txt index 8f4afb03..76d73993 100644 --- a/contrib/subscriptions.txt +++ b/contrib/subscriptions.txt @@ -1,3 +1,4 @@ -http://inr.i2p/export/alive-hosts.txt +http://reg.i2p/hosts.txt +http://identiguy.i2p/hosts.txt http://stats.i2p/cgi-bin/newhosts.txt http://i2p-projekt.i2p/hosts.txt diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index bceca198..5de489d0 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -213,7 +213,7 @@ namespace config { options_description addressbook("AddressBook options"); addressbook.add_options() ("addressbook.defaulturl", value()->default_value( - "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt" + "http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt" ), "AddressBook subscription URL for initial setup") ("addressbook.subscriptions", value()->default_value(""), "AddressBook subscriptions URLs, separated by comma") ("addressbook.hostsfile", value()->default_value(""), "File to dump addresses in hosts.txt format"); diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index 64080752..b2b07fba 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -32,8 +32,9 @@ namespace i2p { namespace proxy { std::map jumpservices = { - { "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" }, - { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, + { "reg.i2p", "http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/jump/" }, + { "identiguy.i2p", "http://3mzmrus2oron5fxptw7hw2puho3bnqmw2hqy7nw64dsrrjwdilva.b32.i2p/cgi-bin/query?hostname=" }, + { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, }; static const char *pageHead = From 484f69f16b92a26f1847a4ba1f6a24de805b7116 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 28 Jan 2021 13:33:12 -0500 Subject: [PATCH 4004/6300] try to select reachable router of inbound tunnel gateway --- libi2pd/TunnelPool.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 94462c93..784dc84e 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -414,7 +414,8 @@ namespace tunnel else if (i2p::transport::transports.GetNumPeers () > 25) { auto r = i2p::transport::transports.GetRandomPeer (); - if (r && !r->GetProfile ()->IsBad ()) + if (r && !r->GetProfile ()->IsBad () && + (numHops > 1 || !inbound || r->IsReachable ())) // first must be reachable { prevHop = r; peers.push_back (r->GetRouterIdentity ()); @@ -430,6 +431,12 @@ namespace tunnel LogPrint (eLogError, "Tunnels: Can't select next hop for ", prevHop->GetIdentHashBase64 ()); return false; } + if (inbound && (i == numHops - 1) && !hop->IsReachable ()) + { + // if first is not reachable try again + auto hop1 = nextHop (prevHop); + if (hop1) hop = hop1; + } prevHop = hop; peers.push_back (hop->GetRouterIdentity ()); } From df7fda9e0cd5ef0f3104152d158c7e522c481d94 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 29 Jan 2021 07:46:20 -0500 Subject: [PATCH 4005/6300] support ratchets for shared local destination --- libi2pd_client/ClientContext.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 860e8cfd..cac472f9 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -404,9 +404,10 @@ namespace client { std::map params { - { I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, "2" }, - { I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, "2" }, - { I2CP_PARAM_LEASESET_TYPE, "3" } + { I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, "3" }, + { I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, "3" }, + { I2CP_PARAM_LEASESET_TYPE, "3" }, + { I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4" } }; m_SharedLocalDestination = CreateNewLocalDestination (false, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, ¶ms); // non-public, EDDSA From daa3f8699bb4945db37eac20893b35f3a00eea64 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 29 Jan 2021 09:32:33 -0500 Subject: [PATCH 4006/6300] don't detect Yggdrasil for android --- libi2pd/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index ff27718a..a1dbe4a8 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -420,7 +420,7 @@ namespace net boost::asio::ip::address_v6 GetYggdrasilAddress () { -#ifdef _WIN32 +#if (defined(_WIN32) || defined(ANDROID)) // TODO: implement return boost::asio::ip::address_v6 (); #else From 1ba5d258190f3493c6739d820fa23a88c4f845f6 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 29 Jan 2021 12:12:40 -0500 Subject: [PATCH 4007/6300] correct detection of chunked response --- libi2pd/HTTP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index d4ca3b8b..b967cbb5 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -346,7 +346,7 @@ namespace http { auto it = headers.find("Transfer-Encoding"); if (it == headers.end()) return false; - if (it->second.find("chunked") == std::string::npos) + if (it->second.find("chunked") != std::string::npos) return true; return false; } From 82649ab2a7cccb87dd876836a9aa8582b4bb2fce Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 29 Jan 2021 13:27:49 -0500 Subject: [PATCH 4008/6300] IsYggdrasilAddress added --- libi2pd/util.cpp | 16 ++++++++++++++-- libi2pd/util.h | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index a1dbe4a8..a36ab25f 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -418,6 +418,17 @@ namespace net #endif } + static bool IsYggdrasilAddress (const uint8_t addr[16]) + { + return addr[0] == 0x02 || addr[0] == 0x03; + } + + bool IsYggdrasilAddress (const boost::asio::ip::address& addr) + { + if (!addr.is_v6 ()) return false; + return IsYggdrasilAddress (addr.to_v6 ().to_bytes ().data ()); + } + boost::asio::ip::address_v6 GetYggdrasilAddress () { #if (defined(_WIN32) || defined(ANDROID)) @@ -434,7 +445,7 @@ namespace net if (cur->ifa_addr && cur->ifa_addr->sa_family == AF_INET6) { sockaddr_in6* sa = (sockaddr_in6*)cur->ifa_addr; - if (sa->sin6_addr.s6_addr[0] == 0x02 || sa->sin6_addr.s6_addr[0] == 0x03) + if (IsYggdrasilAddress(sa->sin6_addr.s6_addr)) { boost::asio::ip::address_v6::bytes_type bytes; memcpy (bytes.data (), &sa->sin6_addr, 16); @@ -444,6 +455,7 @@ namespace net cur = cur->ifa_next; } } + if(addrs) freeifaddrs(addrs); return boost::asio::ip::address_v6 (); #endif } @@ -489,7 +501,7 @@ namespace net if (ipv6_address >= it.first && ipv6_address <= it.second) return true; } - if (checkYggdrasil && (ipv6_address[0] == 0x02 || ipv6_address[0] == 0x03)) // yggdrasil? + if (checkYggdrasil && IsYggdrasilAddress (ipv6_address.data ())) // yggdrasil? return true; } return false; diff --git a/libi2pd/util.h b/libi2pd/util.h index fedab8e1..bd02c648 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -190,6 +190,7 @@ namespace util const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6=false); boost::asio::ip::address_v6 GetYggdrasilAddress (); bool IsInReservedRange (const boost::asio::ip::address& host, bool checkYggdrasil = true); + bool IsYggdrasilAddress (const boost::asio::ip::address& addr); } } } From 129b4a213532592330f647dc22de144cfcba127a Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 30 Jan 2021 16:50:53 -0500 Subject: [PATCH 4009/6300] don't support NTCP1 address in RouterInfo --- libi2pd/RouterInfo.cpp | 66 +++++++++++++++++++----------------------- libi2pd/RouterInfo.h | 10 ++----- 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index afbaadbf..a1c8821f 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -198,14 +198,14 @@ namespace data auto address = std::make_shared
    (); s.read ((char *)&address->cost, sizeof (address->cost)); s.read ((char *)&address->date, sizeof (address->date)); - bool isNTCP2Only = false; + bool isHost = false, isIntroKey = false, isStaticKey = false; char transportStyle[6]; - auto transportStyleLen = ReadString (transportStyle, 6, s) - 1; + ReadString (transportStyle, 6, s); if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2 - { + { address->transportStyle = eTransportNTCP; - if (transportStyleLen > 4 && transportStyle[4] == '2') isNTCP2Only= true; - } + address->ntcp2.reset (new NTCP2Ext ()); + } else if (!strcmp (transportStyle, "SSU")) { address->transportStyle = eTransportSSU; @@ -230,22 +230,7 @@ namespace data { boost::system::error_code ecode; address->host = boost::asio::ip::address::from_string (value, ecode); - if (!ecode) - { -#if BOOST_VERSION >= 104900 - if (!address->host.is_unspecified ()) // check if address is valid -#else - address->host.to_string (ecode); - if (!ecode) -#endif - { - // add supported protocol - if (address->host.is_v4 ()) - supportedTransports |= (address->transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4; - else - supportedTransports |= (address->transportStyle == eTransportNTCP) ? eNTCPV6 : eSSUV6; - } - } + if (!ecode && !address->host.is_unspecified ()) isHost = true; } else if (!strcmp (key, "port")) address->port = boost::lexical_cast(value); @@ -259,7 +244,7 @@ namespace data else if (!strcmp (key, "key")) { if (address->ssu) - Base64ToByteStream (value, strlen (value), address->ssu->key, 32); + isIntroKey = (Base64ToByteStream (value, strlen (value), address->ssu->key, 32) == 32); else LogPrint (eLogWarning, "RouterInfo: Unexpected field 'key' for NTCP"); } @@ -267,14 +252,11 @@ namespace data ExtractCaps (value); else if (!strcmp (key, "s")) // ntcp2 static key { - if (!address->ntcp2) address->ntcp2.reset (new NTCP2Ext ()); - supportedTransports |= (address->host.is_v4 ()) ? eNTCP2V4 : eNTCP2V6; Base64ToByteStream (value, strlen (value), address->ntcp2->staticKey, 32); + isStaticKey = true; } else if (!strcmp (key, "i")) // ntcp2 iv { - if (!address->ntcp2) address->ntcp2.reset (new NTCP2Ext ()); - supportedTransports |= (address->host.is_v4 ()) ? eNTCP2V4 : eNTCP2V6; Base64ToByteStream (value, strlen (value), address->ntcp2->iv, 16); address->ntcp2->isPublished = true; // presence if "i" means "published" } @@ -314,9 +296,22 @@ namespace data } if (!s) return; } - if (introducers) supportedTransports |= eSSUV4; // in case if host is not presented - if (isNTCP2Only && address->ntcp2) address->ntcp2->isNTCP2Only = true; - if (supportedTransports & ~(eNTCPV4 | eNTCPV6)) // exclude NTCP only + if (address->transportStyle == eTransportNTCP) + { + if (isStaticKey && isHost) + supportedTransports |= address->host.is_v4 () ? eNTCP2V4 : eNTCP2V6; + } + else if (address->transportStyle == eTransportSSU) + { + if (isIntroKey) + { + if (isHost) + supportedTransports |= address->host.is_v4 () ? eSSUV4 : eSSUV6; + else + if (introducers) supportedTransports |= eSSUV4; // in case if host is not presented + } + } + if (supportedTransports) { addresses->push_back(address); m_SupportedTransports |= supportedTransports; @@ -741,7 +736,6 @@ namespace data addr->cost = port ? 3 : 14; // override from RouterContext::PublishNTCP2Address addr->date = 0; addr->ntcp2.reset (new NTCP2Ext ()); - addr->ntcp2->isNTCP2Only = true; // NTCP2 only address if (port) addr->ntcp2->isPublished = true; memcpy (addr->ntcp2->staticKey, staticKey, 32); memcpy (addr->ntcp2->iv, iv, 16); @@ -834,24 +828,24 @@ namespace data bool RouterInfo::IsV6 () const { - return m_SupportedTransports & (eNTCPV6 | eSSUV6 | eNTCP2V6); + return m_SupportedTransports & (eSSUV6 | eNTCP2V6); } bool RouterInfo::IsV4 () const { - return m_SupportedTransports & (eNTCPV4 | eSSUV4 | eNTCP2V4); + return m_SupportedTransports & (eSSUV4 | eNTCP2V4); } void RouterInfo::EnableV6 () { if (!IsV6 ()) - m_SupportedTransports |= eNTCPV6 | eSSUV6 | eNTCP2V6; + m_SupportedTransports |= eSSUV6 | eNTCP2V6; } void RouterInfo::EnableV4 () { if (!IsV4 ()) - m_SupportedTransports |= eNTCPV4 | eSSUV4 | eNTCP2V4; + m_SupportedTransports |= eSSUV4 | eNTCP2V4; } @@ -859,7 +853,7 @@ namespace data { if (IsV6 ()) { - m_SupportedTransports &= ~(eNTCPV6 | eSSUV6 | eNTCP2V6); + m_SupportedTransports &= ~(eSSUV6 | eNTCP2V6); for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; @@ -875,7 +869,7 @@ namespace data { if (IsV4 ()) { - m_SupportedTransports &= ~(eNTCPV4 | eSSUV4 | eNTCP2V4); + m_SupportedTransports &= ~(eSSUV4 | eNTCP2V4); for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index ff767a8b..a5303e84 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -54,12 +54,10 @@ namespace data enum SupportedTranports { - eNTCPV4 = 0x01, - eNTCPV6 = 0x02, + eNTCP2V4 = 0x01, + eNTCP2V6 = 0x02, eSSUV4 = 0x04, - eSSUV6 = 0x08, - eNTCP2V4 = 0x10, - eNTCP2V6 = 0x20 + eSSUV6 = 0x08 }; enum Caps @@ -104,7 +102,6 @@ namespace data Tag<32> staticKey; Tag<16> iv; bool isPublished = false; - bool isNTCP2Only = false; }; struct Address @@ -136,7 +133,6 @@ namespace data bool IsNTCP2 () const { return (bool)ntcp2; }; bool IsPublishedNTCP2 () const { return IsNTCP2 () && ntcp2->isPublished; }; - bool IsNTCP2Only () const { return ntcp2 && ntcp2->isNTCP2Only; }; }; typedef std::list > Addresses; From 9e5935aea5c979a3a41a1c200811687b48356f68 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 30 Jan 2021 18:32:17 -0500 Subject: [PATCH 4010/6300] NTCP2Mesh added --- libi2pd/NTCP2.cpp | 2 +- libi2pd/RouterInfo.cpp | 23 +++++++++++++++++------ libi2pd/RouterInfo.h | 5 +++-- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index d72aa014..1c2a71cf 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -655,7 +655,7 @@ namespace transport SendTerminationAndTerminate (eNTCP2Message3Error); return; } - auto addr = ri.GetNTCP2Address (false, false); // any NTCP2 address including v6 + auto addr = ri.GetNTCP2Address (false); // any NTCP2 address if (!addr) { LogPrint (eLogError, "NTCP2: No NTCP2 address found in SessionConfirmed"); diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index a1c8821f..34d377cb 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -16,6 +16,7 @@ #include #endif #include "version.h" +#include "util.h" #include "Crypto.h" #include "Base.h" #include "Timestamp.h" @@ -298,8 +299,18 @@ namespace data } if (address->transportStyle == eTransportNTCP) { - if (isStaticKey && isHost) - supportedTransports |= address->host.is_v4 () ? eNTCP2V4 : eNTCP2V6; + if (isStaticKey) + { + if (isHost) + { + if (address->host.is_v6 ()) + supportedTransports |= i2p::util::net::IsYggdrasilAddress (address->host) ? eNTCP2V6Mesh : eNTCP2V6; + else + supportedTransports |= eNTCP2V4; + } + else if (!address->ntcp2->isPublished) + supportedTransports |= eNTCP2V4; // most likely, since we don't have host + } } else if (address->transportStyle == eTransportSSU) { @@ -920,12 +931,12 @@ namespace data return nullptr; } - std::shared_ptr RouterInfo::GetNTCP2Address (bool publishedOnly, bool v4only) const + std::shared_ptr RouterInfo::GetNTCP2Address (bool publishedOnly) const { return GetAddress ( - [publishedOnly, v4only](std::shared_ptr address)->bool - { - return address->IsNTCP2 () && (!publishedOnly || address->IsPublishedNTCP2 ()) && (!v4only || address->host.is_v4 ()); + [publishedOnly](std::shared_ptr address)->bool + { + return address->IsNTCP2 () && (!publishedOnly || address->IsPublishedNTCP2 ()); }); } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index a5303e84..50ed3576 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -57,7 +57,8 @@ namespace data eNTCP2V4 = 0x01, eNTCP2V6 = 0x02, eSSUV4 = 0x04, - eSSUV6 = 0x08 + eSSUV6 = 0x08, + eNTCP2V6Mesh = 0x10 }; enum Caps @@ -149,7 +150,7 @@ namespace data uint64_t GetTimestamp () const { return m_Timestamp; }; int GetVersion () const { return m_Version; }; Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr - std::shared_ptr GetNTCP2Address (bool publishedOnly, bool v4only = true) const; + std::shared_ptr GetNTCP2Address (bool publishedOnly) const; std::shared_ptr GetPublishedNTCP2V4Address () const; std::shared_ptr GetPublishedNTCP2V6Address () const; std::shared_ptr GetSSUAddress (bool v4only = true) const; From aad2d68edbc9df8981b1d94ceb723d4e802a1a8b Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 31 Jan 2021 17:25:07 -0500 Subject: [PATCH 4011/6300] NTCP2 transports through the Yggdrasil --- libi2pd/Config.cpp | 6 ++++++ libi2pd/NTCP2.cpp | 2 +- libi2pd/RouterContext.cpp | 9 +++++++++ libi2pd/RouterContext.h | 2 ++ libi2pd/RouterInfo.cpp | 35 +++++++++++++++++++++++++++++++++++ libi2pd/RouterInfo.h | 4 ++++ libi2pd/Transports.cpp | 16 +++++++++++++++- 7 files changed, 72 insertions(+), 2 deletions(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 5de489d0..eb7a230e 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -276,6 +276,11 @@ namespace config { ("cpuext.force", bool_switch()->default_value(false), "Force usage of CPU extensions. Useful when cpuinfo is not available on virtual machines") ; + options_description meshnets("Meshnet transports options"); + meshnets.add_options() + ("meshnets.yggdrasil", bool_switch()->default_value(false), "Support transports through the Yggdrasil (deafult: false)") + ; + m_OptionsDesc .add(general) .add(limits) @@ -297,6 +302,7 @@ namespace config { .add(nettime) .add(persist) .add(cpuext) + .add(meshnets) ; } diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 1c2a71cf..130a6a20 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1181,7 +1181,7 @@ namespace transport auto conn = std::make_shared(*this); m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, conn, std::placeholders::_1)); } - else if (address->host.is_v6() && context.SupportsV6 ()) + else if (address->host.is_v6() && (context.SupportsV6 () || context.SupportsMesh ())) { m_NTCP2V6Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService ())); try diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index ce1eede0..33cfb943 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -521,6 +521,15 @@ namespace i2p UpdateRouterInfo (); } + void RouterContext::SetSupportsMesh (bool supportsmesh) + { + if (supportsmesh) + m_RouterInfo.EnableMesh (); + else + m_RouterInfo.DisableMesh (); + UpdateRouterInfo (); + } + void RouterContext::UpdateNTCP2V6Address (const boost::asio::ip::address& host) { bool updated = false; diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 8c13b842..81921d1c 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -107,8 +107,10 @@ namespace i2p void SetAcceptsTunnels (bool acceptsTunnels) { m_AcceptsTunnels = acceptsTunnels; }; bool SupportsV6 () const { return m_RouterInfo.IsV6 (); }; bool SupportsV4 () const { return m_RouterInfo.IsV4 (); }; + bool SupportsMesh () const { return m_RouterInfo.IsMesh (); }; void SetSupportsV6 (bool supportsV6); void SetSupportsV4 (bool supportsV4); + void SetSupportsMesh (bool supportsmesh); bool IsECIES () const { return GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; std::unique_ptr& GetCurrentNoiseState () { return m_CurrentNoiseState; }; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 34d377cb..3f710aec 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -847,6 +847,11 @@ namespace data return m_SupportedTransports & (eSSUV4 | eNTCP2V4); } + bool RouterInfo::IsMesh () const + { + return m_SupportedTransports & eNTCP2V6Mesh; + } + void RouterInfo::EnableV6 () { if (!IsV6 ()) @@ -892,6 +897,27 @@ namespace data } } + void RouterInfo::EnableMesh () + { + if (!IsMesh ()) + m_SupportedTransports |= eNTCP2V6Mesh; + } + + void RouterInfo::DisableMesh () + { + if (IsMesh ()) + { + m_SupportedTransports &= ~eNTCP2V6Mesh; + for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) + { + auto addr = *it; + if (i2p::util::net::IsYggdrasilAddress (addr->host)) + it = m_Addresses->erase (it); + else + ++it; + } + } + } bool RouterInfo::UsesIntroducer () const { @@ -957,6 +983,15 @@ namespace data return address->IsPublishedNTCP2 () && address->host.is_v6 (); }); } + + std::shared_ptr RouterInfo::GetYggdrasilAddress () const + { + return GetAddress ( + [](std::shared_ptr address)->bool + { + return i2p::util::net::IsYggdrasilAddress (address->host); + }); + } std::shared_ptr RouterInfo::GetProfile () const { diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 50ed3576..fc55a9e2 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -155,6 +155,7 @@ namespace data std::shared_ptr GetPublishedNTCP2V6Address () const; std::shared_ptr GetSSUAddress (bool v4only = true) const; std::shared_ptr GetSSUV6Address () const; + std::shared_ptr GetYggdrasilAddress () const; void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0); @@ -171,10 +172,13 @@ namespace data bool IsNTCP2 (bool v4only = true) const; bool IsV6 () const; bool IsV4 () const; + bool IsMesh () const; void EnableV6 (); void DisableV6 (); void EnableV4 (); void DisableV4 (); + void EnableMesh (); + void DisableMesh (); bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; bool HasValidAddresses () const { return m_SupportedTransports; }; bool UsesIntroducer () const; diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 18459264..5d6fc5f1 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -442,13 +442,27 @@ namespace transport if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ())) { auto address = peer.router->GetSSUAddress (!context.SupportsV6 ()); - if (!m_CheckReserved || !i2p::util::net::IsInReservedRange(address->host)) + if (address && (!m_CheckReserved || !i2p::util::net::IsInReservedRange(address->host))) { m_SSUServer->CreateSession (peer.router, address->host, address->port); return true; } } } + if (peer.numAttempts == 3) // Mesh + { + peer.numAttempts++; + if (context.SupportsMesh () && m_NTCP2Server) + { + auto address = peer.router->GetYggdrasilAddress (); + if (address) + { + auto s = std::make_shared (*m_NTCP2Server, peer.router, address); + m_NTCP2Server->Connect (address->host, address->port, s); + return true; + } + } + } LogPrint (eLogInfo, "Transports: No NTCP or SSU addresses available"); i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed peer.Done (); From ba3acdac754bfbfb77fb31e7daf66bbd4a3feea5 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 31 Jan 2021 17:50:10 -0500 Subject: [PATCH 4012/6300] NTCP2 transports through the Yggdrasil --- daemon/Daemon.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 839495a5..ba11e500 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -144,6 +144,18 @@ namespace i2p ipv4 = false; ipv6 = true; #endif + bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); + boost::asio::ip::address_v6 yggaddr; + if (ygg) + { + yggaddr = i2p::util::net::GetYggdrasilAddress (); + if (yggaddr.is_unspecified ()) + { + LogPrint(eLogWarning, "Daemon: Yggdrasil is not running. Disabled"); + ygg = false; + } + } + uint16_t port; i2p::config::GetOption("port", port); if (!i2p::config::IsDefault("port")) { @@ -152,7 +164,8 @@ namespace i2p } i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); - + i2p::context.SetSupportsMesh (ygg); + i2p::context.RemoveNTCPAddress (!ipv6); // TODO: remove later bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) @@ -170,6 +183,8 @@ namespace i2p if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ()) i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured } + if (ygg) + i2p::context.UpdateNTCP2V6Address (yggaddr); } else i2p::context.PublishNTCP2Address (port, false); // unpublish From c4fc0f4ecf48d09e3f591685bba88ce5d4b22ad3 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 31 Jan 2021 18:30:53 -0500 Subject: [PATCH 4013/6300] add Yggdrasil address --- daemon/Daemon.cpp | 2 +- libi2pd/RouterContext.cpp | 17 ++++++++++++++++- libi2pd/RouterContext.h | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index ba11e500..c53a5d47 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -164,7 +164,7 @@ namespace i2p } i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); - i2p::context.SetSupportsMesh (ygg); + i2p::context.SetSupportsMesh (ygg, yggaddr); i2p::context.RemoveNTCPAddress (!ipv6); // TODO: remove later bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 33cfb943..9faa75aa 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -521,10 +521,25 @@ namespace i2p UpdateRouterInfo (); } - void RouterContext::SetSupportsMesh (bool supportsmesh) + void RouterContext::SetSupportsMesh (bool supportsmesh, const boost::asio::ip::address_v6& host) { if (supportsmesh) + { m_RouterInfo.EnableMesh (); + uint16_t port = 0; + i2p::config::GetOption ("ntcp2.port", port); + if (!port) i2p::config::GetOption("port", port); + if (!port) + { + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr: addresses) + { + port = addr->port; + if (port) break; + } + } + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port); + } else m_RouterInfo.DisableMesh (); UpdateRouterInfo (); diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 81921d1c..1def089a 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -110,7 +110,7 @@ namespace i2p bool SupportsMesh () const { return m_RouterInfo.IsMesh (); }; void SetSupportsV6 (bool supportsV6); void SetSupportsV4 (bool supportsV4); - void SetSupportsMesh (bool supportsmesh); + void SetSupportsMesh (bool supportsmesh, const boost::asio::ip::address_v6& host); bool IsECIES () const { return GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; std::unique_ptr& GetCurrentNoiseState () { return m_CurrentNoiseState; }; From fef4f13b8f99685c79aea4e80c82a408071f0eaf Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 31 Jan 2021 19:09:38 -0500 Subject: [PATCH 4014/6300] don't insert Yggdrasil address twice --- libi2pd/RouterContext.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 9faa75aa..62f5701f 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -529,16 +529,19 @@ namespace i2p uint16_t port = 0; i2p::config::GetOption ("ntcp2.port", port); if (!port) i2p::config::GetOption("port", port); - if (!port) - { - auto& addresses = m_RouterInfo.GetAddresses (); - for (auto& addr: addresses) + bool foundMesh = false; + auto& addresses = m_RouterInfo.GetAddresses (); + for (auto& addr: addresses) + { + if (!port) port = addr->port; + if (i2p::util::net::IsYggdrasilAddress (addr->host)) { - port = addr->port; - if (port) break; - } - } - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port); + foundMesh = true; + break; + } + } + if (!foundMesh) + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port); } else m_RouterInfo.DisableMesh (); From ea19802d3fc05068ec02e2748c83d23a11c9c569 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 1 Feb 2021 12:47:41 -0500 Subject: [PATCH 4015/6300] update right ipv6 only --- libi2pd/RouterContext.cpp | 4 +++- libi2pd/util.cpp | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 62f5701f..df50b67f 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -550,13 +550,15 @@ namespace i2p void RouterContext::UpdateNTCP2V6Address (const boost::asio::ip::address& host) { + bool isYgg = i2p::util::net::IsYggdrasilAddress (host); bool updated = false; auto& addresses = m_RouterInfo.GetAddresses (); for (auto& addr: addresses) { if (addr->IsPublishedNTCP2 ()) { - if (addr->host.is_v6 ()) + bool isYgg1 = i2p::util::net::IsYggdrasilAddress (addr->host); + if (addr->host.is_v6 () && ((isYgg && isYgg1) || (!isYgg && !isYgg1))) { if (addr->host != host) { diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index a36ab25f..b70aed8d 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -449,6 +449,7 @@ namespace net { boost::asio::ip::address_v6::bytes_type bytes; memcpy (bytes.data (), &sa->sin6_addr, 16); + freeifaddrs(addrs); return boost::asio::ip::address_v6 (bytes); } } From bfb1380dd2e8eff5c067412e50e2058078528193 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 1 Feb 2021 13:18:48 -0500 Subject: [PATCH 4016/6300] don't update Yggdrasil address from SSU --- libi2pd/RouterContext.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index df50b67f..96a33041 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -245,7 +245,8 @@ namespace i2p bool updated = false; for (auto& address : m_RouterInfo.GetAddresses ()) { - if (address->host != host && address->IsCompatible (host)) + if (address->host != host && address->IsCompatible (host) && + !i2p::util::net::IsYggdrasilAddress (address->host)) { address->host = host; if (host.is_v6 () && address->transportStyle == i2p::data::RouterInfo::eTransportSSU) From ace80c29e7caf25ea50534370962443faada5991 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 1 Feb 2021 18:00:03 -0500 Subject: [PATCH 4017/6300] meshnets.yggaddress added --- daemon/Daemon.cpp | 21 +++++++++++++++++---- libi2pd/Config.cpp | 1 + libi2pd/RouterInfo.cpp | 3 ++- libi2pd/util.cpp | 4 ++-- libi2pd/util.h | 2 +- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index c53a5d47..70c4affc 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -148,11 +148,24 @@ namespace i2p boost::asio::ip::address_v6 yggaddr; if (ygg) { - yggaddr = i2p::util::net::GetYggdrasilAddress (); - if (yggaddr.is_unspecified ()) + std::string yggaddress; i2p::config::GetOption ("meshnets.yggaddress", yggaddress); + if (!yggaddress.empty ()) { - LogPrint(eLogWarning, "Daemon: Yggdrasil is not running. Disabled"); - ygg = false; + yggaddr = boost::asio::ip::address_v6::from_string (yggaddress); + if (yggaddr.is_unspecified () || i2p::util::net::GetMTU (yggaddr) != 0xFFFF) // ygg's MTU is always 65535 + { + LogPrint(eLogWarning, "Daemon: Can't find Yggdrasil address ", yggaddress); + ygg = false; + } + } + else + { + yggaddr = i2p::util::net::GetYggdrasilAddress (); + if (yggaddr.is_unspecified ()) + { + LogPrint(eLogWarning, "Daemon: Yggdrasil is not running. Disabled"); + ygg = false; + } } } diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index eb7a230e..9da0bbe0 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -279,6 +279,7 @@ namespace config { options_description meshnets("Meshnet transports options"); meshnets.add_options() ("meshnets.yggdrasil", bool_switch()->default_value(false), "Support transports through the Yggdrasil (deafult: false)") + ("meshnets.yggaddress", value()->default_value(""), "Yggdrasil address to publish") ; m_OptionsDesc diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 3f710aec..13988f5f 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -980,7 +980,8 @@ namespace data return GetAddress ( [](std::shared_ptr address)->bool { - return address->IsPublishedNTCP2 () && address->host.is_v6 (); + return address->IsPublishedNTCP2 () && address->host.is_v6 () && + !i2p::util::net::IsYggdrasilAddress (address->host); }); } diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index b70aed8d..f9beacf0 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -461,7 +461,7 @@ namespace net #endif } - bool IsInReservedRange (const boost::asio::ip::address& host, bool checkYggdrasil) + bool IsInReservedRange (const boost::asio::ip::address& host) { // https://en.wikipedia.org/wiki/Reserved_IP_addresses if(host.is_v4()) @@ -502,7 +502,7 @@ namespace net if (ipv6_address >= it.first && ipv6_address <= it.second) return true; } - if (checkYggdrasil && IsYggdrasilAddress (ipv6_address.data ())) // yggdrasil? + if (IsYggdrasilAddress (ipv6_address.data ())) // yggdrasil? return true; } return false; diff --git a/libi2pd/util.h b/libi2pd/util.h index bd02c648..d4b9da51 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -189,7 +189,7 @@ namespace util int GetMTU (const boost::asio::ip::address& localAddress); const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6=false); boost::asio::ip::address_v6 GetYggdrasilAddress (); - bool IsInReservedRange (const boost::asio::ip::address& host, bool checkYggdrasil = true); + bool IsInReservedRange (const boost::asio::ip::address& host); bool IsYggdrasilAddress (const boost::asio::ip::address& addr); } } From 05c7aacfa5e01f92c899fca9f6a700d94cce6810 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 1 Feb 2021 22:24:51 -0500 Subject: [PATCH 4018/6300] check for NTCP for yggdrasil address --- libi2pd/RouterInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 13988f5f..d0784ece 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -990,7 +990,7 @@ namespace data return GetAddress ( [](std::shared_ptr address)->bool { - return i2p::util::net::IsYggdrasilAddress (address->host); + return address->IsPublishedNTCP2 () && i2p::util::net::IsYggdrasilAddress (address->host); }); } From a74f685a5d262a2e66972f47879460f09a52476f Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 2 Feb 2021 19:29:13 -0500 Subject: [PATCH 4019/6300] check local address --- daemon/Daemon.cpp | 3 ++- libi2pd/util.cpp | 11 +++++++++++ libi2pd/util.h | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 70c4affc..80d56651 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -152,7 +152,8 @@ namespace i2p if (!yggaddress.empty ()) { yggaddr = boost::asio::ip::address_v6::from_string (yggaddress); - if (yggaddr.is_unspecified () || i2p::util::net::GetMTU (yggaddr) != 0xFFFF) // ygg's MTU is always 65535 + if (yggaddr.is_unspecified () || !i2p::util::net::IsYggdrasilAddress (yggaddr) || + !i2p::util::net::IsLocalAddress (yggaddr)) { LogPrint(eLogWarning, "Daemon: Can't find Yggdrasil address ", yggaddress); ygg = false; diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index f9beacf0..22530a70 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -461,6 +461,17 @@ namespace net #endif } + bool IsLocalAddress (const boost::asio::ip::address& addr) + { + auto mtu = // TODO: implement better +#ifdef _WIN32 + GetMTUWindows(addr, 0); +#else + GetMTUUnix(addr, 0); +#endif + return mtu > 0; + } + bool IsInReservedRange (const boost::asio::ip::address& host) { // https://en.wikipedia.org/wiki/Reserved_IP_addresses diff --git a/libi2pd/util.h b/libi2pd/util.h index d4b9da51..0cab4121 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -189,6 +189,7 @@ namespace util int GetMTU (const boost::asio::ip::address& localAddress); const boost::asio::ip::address GetInterfaceAddress (const std::string & ifname, bool ipv6=false); boost::asio::ip::address_v6 GetYggdrasilAddress (); + bool IsLocalAddress (const boost::asio::ip::address& addr); bool IsInReservedRange (const boost::asio::ip::address& host); bool IsYggdrasilAddress (const boost::asio::ip::address& addr); } From 0e5dc15005e819bf5b84b62bebe31275683e0fee Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 2 Feb 2021 21:39:16 -0500 Subject: [PATCH 4020/6300] create Yggdrasil address for new router --- libi2pd/RouterContext.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 96a33041..797bb5d7 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -72,6 +72,7 @@ namespace i2p bool ipv6; i2p::config::GetOption("ipv6", ipv6); bool ssu; i2p::config::GetOption("ssu", ssu); bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); bool nat; i2p::config::GetOption("nat", nat); std::string ifname; i2p::config::GetOption("ifname", ifname); std::string ifname4; i2p::config::GetOption("ifname4", ifname4); @@ -105,7 +106,7 @@ namespace i2p if (ssu) routerInfo.AddSSUAddress (host.c_str(), port, nullptr); } - + routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC routerInfo.SetProperty ("netId", std::to_string (m_NetID)); @@ -117,11 +118,12 @@ namespace i2p if (ntcp2) // we don't store iv in the address if non published so we must update it from keys { if (!m_NTCP2Keys) NewNTCP2Keys (); - UpdateNTCP2Address (true); bool published; i2p::config::GetOption("ntcp2.published", published); + if (ipv4 || !published) UpdateNTCP2Address (true); // create not published NTCP2 address if (published) { - PublishNTCP2Address (port, true); + if (ipv4) + PublishNTCP2Address (port, true); if (ipv6) { // add NTCP2 ipv6 address @@ -130,6 +132,15 @@ namespace i2p i2p::config::GetOption ("ntcp2.addressv6", host); m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (host), port); } + if (ygg) + { + auto yggaddr = i2p::util::net::GetYggdrasilAddress (); + if (!yggaddr.is_unspecified ()) + { + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, yggaddr, port); + UpdateRouterInfo (); + } + } } } } From 6966539b86f02cd9fff779908e60f4b944be5ba5 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 1 Feb 2021 14:36:51 +0300 Subject: [PATCH 4021/6300] reindent Datagram, Daemon, update default subscription in config example Signed-off-by: R4SAS --- contrib/i2pd.conf | 4 +- daemon/Daemon.cpp | 685 ++++++++++++++++++++++--------------------- libi2pd/Datagram.cpp | 74 ++--- 3 files changed, 383 insertions(+), 380 deletions(-) diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 13801076..0c2ec5bc 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -192,8 +192,8 @@ verify = true [addressbook] ## AddressBook subscription URL for initial setup -## Default: inr.i2p at "mainline" I2P Network -# defaulturl = http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt +## Default: reg.i2p at "mainline" I2P Network +# defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt ## Optional subscriptions URLs, separated by comma # subscriptions = http://inr.i2p/export/alive-hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 80d56651..ccbfbb1d 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -35,403 +35,406 @@ namespace i2p { - namespace util +namespace util +{ + class Daemon_Singleton::Daemon_Singleton_Private { - class Daemon_Singleton::Daemon_Singleton_Private - { - public: - Daemon_Singleton_Private() {}; - ~Daemon_Singleton_Private() {}; + public: + Daemon_Singleton_Private() {}; + ~Daemon_Singleton_Private() {}; - std::unique_ptr httpServer; - std::unique_ptr m_I2PControlService; - std::unique_ptr UPnP; - std::unique_ptr m_NTPSync; - }; + std::unique_ptr httpServer; + std::unique_ptr m_I2PControlService; + std::unique_ptr UPnP; + std::unique_ptr m_NTPSync; + }; - Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {} - Daemon_Singleton::~Daemon_Singleton() { - delete &d; - } + Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {} + Daemon_Singleton::~Daemon_Singleton() { + delete &d; + } - bool Daemon_Singleton::IsService () const - { - bool service = false; + bool Daemon_Singleton::IsService () const + { + bool service = false; #ifndef _WIN32 - i2p::config::GetOption("service", service); + i2p::config::GetOption("service", service); #endif - return service; - } + return service; + } - bool Daemon_Singleton::init(int argc, char* argv[]) { - return init(argc, argv, nullptr); - } + bool Daemon_Singleton::init(int argc, char* argv[]) { + return init(argc, argv, nullptr); + } - bool Daemon_Singleton::init(int argc, char* argv[], std::shared_ptr logstream) + bool Daemon_Singleton::init(int argc, char* argv[], std::shared_ptr logstream) + { + i2p::config::Init(); + i2p::config::ParseCmdline(argc, argv); + + std::string config; i2p::config::GetOption("conf", config); + std::string datadir; i2p::config::GetOption("datadir", datadir); + i2p::fs::DetectDataDir(datadir, IsService()); + i2p::fs::Init(); + + datadir = i2p::fs::GetDataDir(); + + if (config == "") { - i2p::config::Init(); - i2p::config::ParseCmdline(argc, argv); - - std::string config; i2p::config::GetOption("conf", config); - std::string datadir; i2p::config::GetOption("datadir", datadir); - i2p::fs::DetectDataDir(datadir, IsService()); - i2p::fs::Init(); - - datadir = i2p::fs::GetDataDir(); - - if (config == "") - { - config = i2p::fs::DataDirPath("i2pd.conf"); - if (!i2p::fs::Exists (config)) { - // use i2pd.conf only if exists - config = ""; /* reset */ - } + config = i2p::fs::DataDirPath("i2pd.conf"); + if (!i2p::fs::Exists (config)) { + // use i2pd.conf only if exists + config = ""; /* reset */ } + } - i2p::config::ParseConfig(config); - i2p::config::Finalize(); + i2p::config::ParseConfig(config); + i2p::config::Finalize(); - i2p::config::GetOption("daemon", isDaemon); + i2p::config::GetOption("daemon", isDaemon); - std::string logs = ""; i2p::config::GetOption("log", logs); - std::string logfile = ""; i2p::config::GetOption("logfile", logfile); - std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); - bool logclftime; i2p::config::GetOption("logclftime", logclftime); + std::string logs = ""; i2p::config::GetOption("log", logs); + std::string logfile = ""; i2p::config::GetOption("logfile", logfile); + std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); + bool logclftime; i2p::config::GetOption("logclftime", logclftime); - /* setup logging */ - if (logclftime) - i2p::log::Logger().SetTimeFormat ("[%d/%b/%Y:%H:%M:%S %z]"); + /* setup logging */ + if (logclftime) + i2p::log::Logger().SetTimeFormat ("[%d/%b/%Y:%H:%M:%S %z]"); - if (isDaemon && (logs == "" || logs == "stdout")) - logs = "file"; + if (isDaemon && (logs == "" || logs == "stdout")) + logs = "file"; - i2p::log::Logger().SetLogLevel(loglevel); - if (logstream) { - LogPrint(eLogInfo, "Log: will send messages to std::ostream"); - i2p::log::Logger().SendTo (logstream); - } else if (logs == "file") { - if (logfile == "") - logfile = i2p::fs::DataDirPath("i2pd.log"); - LogPrint(eLogInfo, "Log: will send messages to ", logfile); - i2p::log::Logger().SendTo (logfile); + i2p::log::Logger().SetLogLevel(loglevel); + if (logstream) { + LogPrint(eLogInfo, "Log: will send messages to std::ostream"); + i2p::log::Logger().SendTo (logstream); + } else if (logs == "file") { + if (logfile == "") + logfile = i2p::fs::DataDirPath("i2pd.log"); + LogPrint(eLogInfo, "Log: will send messages to ", logfile); + i2p::log::Logger().SendTo (logfile); #ifndef _WIN32 - } else if (logs == "syslog") { - LogPrint(eLogInfo, "Log: will send messages to syslog"); - i2p::log::Logger().SendTo("i2pd", LOG_DAEMON); + } else if (logs == "syslog") { + LogPrint(eLogInfo, "Log: will send messages to syslog"); + i2p::log::Logger().SendTo("i2pd", LOG_DAEMON); #endif - } else { - // use stdout -- default - } + } else { + // use stdout -- default + } - LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); - LogPrint(eLogDebug, "FS: main config file: ", config); - LogPrint(eLogDebug, "FS: data directory: ", datadir); + LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); + LogPrint(eLogDebug, "FS: main config file: ", config); + LogPrint(eLogDebug, "FS: data directory: ", datadir); - bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); - bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); - bool avx; i2p::config::GetOption("cpuext.avx", avx); - bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt); - i2p::crypto::InitCrypto (precomputation, aesni, avx, forceCpuExt); + bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); + bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); + bool avx; i2p::config::GetOption("cpuext.avx", avx); + bool forceCpuExt; i2p::config::GetOption("cpuext.force", forceCpuExt); + i2p::crypto::InitCrypto (precomputation, aesni, avx, forceCpuExt); - int netID; i2p::config::GetOption("netid", netID); - i2p::context.SetNetID (netID); - i2p::context.Init (); + int netID; i2p::config::GetOption("netid", netID); + i2p::context.SetNetID (netID); + i2p::context.Init (); - bool ipv6; i2p::config::GetOption("ipv6", ipv6); - bool ipv4; i2p::config::GetOption("ipv4", ipv4); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); #ifdef MESHNET - // manual override for meshnet - ipv4 = false; - ipv6 = true; + // manual override for meshnet + ipv4 = false; + ipv6 = true; #endif - bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); - boost::asio::ip::address_v6 yggaddr; - if (ygg) + bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); + boost::asio::ip::address_v6 yggaddr; + if (ygg) + { + std::string yggaddress; i2p::config::GetOption ("meshnets.yggaddress", yggaddress); + if (!yggaddress.empty ()) { - std::string yggaddress; i2p::config::GetOption ("meshnets.yggaddress", yggaddress); - if (!yggaddress.empty ()) + yggaddr = boost::asio::ip::address_v6::from_string (yggaddress); + if (yggaddr.is_unspecified () || !i2p::util::net::IsYggdrasilAddress (yggaddr) || + !i2p::util::net::IsLocalAddress (yggaddr)) { - yggaddr = boost::asio::ip::address_v6::from_string (yggaddress); - if (yggaddr.is_unspecified () || !i2p::util::net::IsYggdrasilAddress (yggaddr) || - !i2p::util::net::IsLocalAddress (yggaddr)) - { - LogPrint(eLogWarning, "Daemon: Can't find Yggdrasil address ", yggaddress); - ygg = false; - } + LogPrint(eLogWarning, "Daemon: Can't find Yggdrasil address ", yggaddress); + ygg = false; } - else - { - yggaddr = i2p::util::net::GetYggdrasilAddress (); - if (yggaddr.is_unspecified ()) - { - LogPrint(eLogWarning, "Daemon: Yggdrasil is not running. Disabled"); - ygg = false; - } - } - } - - uint16_t port; i2p::config::GetOption("port", port); - if (!i2p::config::IsDefault("port")) - { - LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); - i2p::context.UpdatePort (port); - } - i2p::context.SetSupportsV6 (ipv6); - i2p::context.SetSupportsV4 (ipv4); - i2p::context.SetSupportsMesh (ygg, yggaddr); - - i2p::context.RemoveNTCPAddress (!ipv6); // TODO: remove later - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) - { - bool published; i2p::config::GetOption("ntcp2.published", published); - if (published) - { - uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port); - if (!ntcp2port) ntcp2port = port; // use standard port - i2p::context.PublishNTCP2Address (ntcp2port, true); // publish - if (ipv6) - { - std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr); - auto addr = boost::asio::ip::address_v6::from_string (ipv6Addr); - if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ()) - i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured - } - if (ygg) - i2p::context.UpdateNTCP2V6Address (yggaddr); - } - else - i2p::context.PublishNTCP2Address (port, false); // unpublish - } - - bool transit; i2p::config::GetOption("notransit", transit); - i2p::context.SetAcceptsTunnels (!transit); - uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); - SetMaxNumTransitTunnels (transitTunnels); - - bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); - if (isFloodfill) { - LogPrint(eLogInfo, "Daemon: router will be floodfill"); - i2p::context.SetFloodfill (true); - } else { - i2p::context.SetFloodfill (false); - } - - /* this section also honors 'floodfill' flag, if set above */ - std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); - if (bandwidth.length () > 0) - { - if (bandwidth[0] >= 'K' && bandwidth[0] <= 'X') - { - i2p::context.SetBandwidth (bandwidth[0]); - LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps"); - } - else - { - auto value = std::atoi(bandwidth.c_str()); - if (value > 0) - { - i2p::context.SetBandwidth (value); - LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), " KBps"); - } - else - { - LogPrint(eLogInfo, "Daemon: unexpected bandwidth ", bandwidth, ". Set to 'low'"); - i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); - } - } - } - else if (isFloodfill) - { - LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'"); - i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1); } else { - LogPrint(eLogInfo, "Daemon: bandwidth set to 'low'"); - i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); - } - - int shareRatio; i2p::config::GetOption("share", shareRatio); - i2p::context.SetShareRatio (shareRatio); - - std::string family; i2p::config::GetOption("family", family); - i2p::context.SetFamily (family); - if (family.length () > 0) - LogPrint(eLogInfo, "Daemon: family set to ", family); - - bool trust; i2p::config::GetOption("trust.enabled", trust); - if (trust) - { - LogPrint(eLogInfo, "Daemon: explicit trust enabled"); - std::string fam; i2p::config::GetOption("trust.family", fam); - std::string routers; i2p::config::GetOption("trust.routers", routers); - bool restricted = false; - if (fam.length() > 0) + yggaddr = i2p::util::net::GetYggdrasilAddress (); + if (yggaddr.is_unspecified ()) { - std::set fams; - size_t pos = 0, comma; - do - { - comma = fam.find (',', pos); - fams.insert (fam.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); - pos = comma + 1; - } - while (comma != std::string::npos); - i2p::transport::transports.RestrictRoutesToFamilies(fams); - restricted = fams.size() > 0; + LogPrint(eLogWarning, "Daemon: Yggdrasil is not running. Disabled"); + ygg = false; } - if (routers.length() > 0) { - std::set idents; - size_t pos = 0, comma; - do - { - comma = routers.find (',', pos); - i2p::data::IdentHash ident; - ident.FromBase64 (routers.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); - idents.insert (ident); - pos = comma + 1; - } - while (comma != std::string::npos); - LogPrint(eLogInfo, "Daemon: setting restricted routes to use ", idents.size(), " trusted routers"); - i2p::transport::transports.RestrictRoutesToRouters(idents); - restricted = idents.size() > 0; - } - if(!restricted) - LogPrint(eLogError, "Daemon: no trusted routers of families specified"); } - - bool hidden; i2p::config::GetOption("trust.hidden", hidden); - if (hidden) - { - LogPrint(eLogInfo, "Daemon: using hidden mode"); - i2p::data::netdb.SetHidden(true); - } - return true; } - bool Daemon_Singleton::start() + uint16_t port; i2p::config::GetOption("port", port); + if (!i2p::config::IsDefault("port")) { - i2p::log::Logger().Start(); - LogPrint(eLogInfo, "Daemon: starting NetDB"); - i2p::data::netdb.Start(); + LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); + i2p::context.UpdatePort (port); + } + i2p::context.SetSupportsV6 (ipv6); + i2p::context.SetSupportsV4 (ipv4); + i2p::context.SetSupportsMesh (ygg, yggaddr); - bool upnp; i2p::config::GetOption("upnp.enabled", upnp); - if (upnp) { - d.UPnP = std::unique_ptr(new i2p::transport::UPnP); - d.UPnP->Start (); - } - - bool nettime; i2p::config::GetOption("nettime.enabled", nettime); - if (nettime) + i2p::context.RemoveNTCPAddress (!ipv6); // TODO: remove later + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + if (ntcp2) + { + bool published; i2p::config::GetOption("ntcp2.published", published); + if (published) { - d.m_NTPSync = std::unique_ptr(new i2p::util::NTPTimeSync); - d.m_NTPSync->Start (); + uint16_t ntcp2port; i2p::config::GetOption("ntcp2.port", ntcp2port); + if (!ntcp2port) ntcp2port = port; // use standard port + i2p::context.PublishNTCP2Address (ntcp2port, true); // publish + if (ipv6) + { + std::string ipv6Addr; i2p::config::GetOption("ntcp2.addressv6", ipv6Addr); + auto addr = boost::asio::ip::address_v6::from_string (ipv6Addr); + if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ()) + i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured + } + if (ygg) + i2p::context.UpdateNTCP2V6Address (yggaddr); + } } + else + i2p::context.PublishNTCP2Address (port, false); // unpublish + } - bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - bool ssu; i2p::config::GetOption("ssu", ssu); - bool checkInReserved; i2p::config::GetOption("reservedrange", checkInReserved); - LogPrint(eLogInfo, "Daemon: starting Transports"); - if(!ssu) LogPrint(eLogInfo, "Daemon: ssu disabled"); - if(!ntcp2) LogPrint(eLogInfo, "Daemon: ntcp2 disabled"); + bool transit; i2p::config::GetOption("notransit", transit); + i2p::context.SetAcceptsTunnels (!transit); + uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); + SetMaxNumTransitTunnels (transitTunnels); - i2p::transport::transports.SetCheckReserved(checkInReserved); - i2p::transport::transports.Start(ntcp2, ssu); - if (i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundNTCP2()) - LogPrint(eLogInfo, "Daemon: Transports started"); + bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); + if (isFloodfill) { + LogPrint(eLogInfo, "Daemon: router will be floodfill"); + i2p::context.SetFloodfill (true); + } + else + { + i2p::context.SetFloodfill (false); + } + + /* this section also honors 'floodfill' flag, if set above */ + std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); + if (bandwidth.length () > 0) + { + if (bandwidth[0] >= 'K' && bandwidth[0] <= 'X') + { + i2p::context.SetBandwidth (bandwidth[0]); + LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps"); + } else { - LogPrint(eLogError, "Daemon: failed to start Transports"); - /** shut down netdb right away */ - i2p::transport::transports.Stop(); - i2p::data::netdb.Stop(); - return false; - } - - bool http; i2p::config::GetOption("http.enabled", http); - if (http) { - std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); - uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); - LogPrint(eLogInfo, "Daemon: starting webconsole at ", httpAddr, ":", httpPort); - try + auto value = std::atoi(bandwidth.c_str()); + if (value > 0) { - d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); - d.httpServer->Start(); - } - catch (std::exception& ex) + i2p::context.SetBandwidth (value); + LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), " KBps"); + } + else { - LogPrint (eLogError, "Daemon: failed to start webconsole: ", ex.what ()); - ThrowFatal ("Unable to start webconsole at ", httpAddr, ":", httpPort, ": ", ex.what ()); + LogPrint(eLogInfo, "Daemon: unexpected bandwidth ", bandwidth, ". Set to 'low'"); + i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); } } - - - LogPrint(eLogInfo, "Daemon: starting Tunnels"); - i2p::tunnel::tunnels.Start(); - - LogPrint(eLogInfo, "Daemon: starting Client"); - i2p::client::context.Start (); - - // I2P Control Protocol - bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); - if (i2pcontrol) { - std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr); - uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort); - LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort); - try - { - d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); - d.m_I2PControlService->Start (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "Daemon: failed to start I2PControl: ", ex.what ()); - ThrowFatal ("Unable to start I2PControl service at ", i2pcpAddr, ":", i2pcpPort, ": ", ex.what ()); - } - } - return true; + } + else if (isFloodfill) + { + LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'"); + i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1); + } + else + { + LogPrint(eLogInfo, "Daemon: bandwidth set to 'low'"); + i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); } - bool Daemon_Singleton::stop() + int shareRatio; i2p::config::GetOption("share", shareRatio); + i2p::context.SetShareRatio (shareRatio); + + std::string family; i2p::config::GetOption("family", family); + i2p::context.SetFamily (family); + if (family.length () > 0) + LogPrint(eLogInfo, "Daemon: family set to ", family); + + bool trust; i2p::config::GetOption("trust.enabled", trust); + if (trust) { - LogPrint(eLogInfo, "Daemon: shutting down"); - LogPrint(eLogInfo, "Daemon: stopping Client"); - i2p::client::context.Stop(); - LogPrint(eLogInfo, "Daemon: stopping Tunnels"); - i2p::tunnel::tunnels.Stop(); - - if (d.UPnP) + LogPrint(eLogInfo, "Daemon: explicit trust enabled"); + std::string fam; i2p::config::GetOption("trust.family", fam); + std::string routers; i2p::config::GetOption("trust.routers", routers); + bool restricted = false; + if (fam.length() > 0) { - d.UPnP->Stop (); - d.UPnP = nullptr; + std::set fams; + size_t pos = 0, comma; + do + { + comma = fam.find (',', pos); + fams.insert (fam.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); + pos = comma + 1; + } + while (comma != std::string::npos); + i2p::transport::transports.RestrictRoutesToFamilies(fams); + restricted = fams.size() > 0; } - - if (d.m_NTPSync) - { - d.m_NTPSync->Stop (); - d.m_NTPSync = nullptr; + if (routers.length() > 0) { + std::set idents; + size_t pos = 0, comma; + do + { + comma = routers.find (',', pos); + i2p::data::IdentHash ident; + ident.FromBase64 (routers.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); + idents.insert (ident); + pos = comma + 1; + } + while (comma != std::string::npos); + LogPrint(eLogInfo, "Daemon: setting restricted routes to use ", idents.size(), " trusted routers"); + i2p::transport::transports.RestrictRoutesToRouters(idents); + restricted = idents.size() > 0; } + if(!restricted) + LogPrint(eLogError, "Daemon: no trusted routers of families specified"); + } - LogPrint(eLogInfo, "Daemon: stopping Transports"); + bool hidden; i2p::config::GetOption("trust.hidden", hidden); + if (hidden) + { + LogPrint(eLogInfo, "Daemon: using hidden mode"); + i2p::data::netdb.SetHidden(true); + } + return true; + } + + bool Daemon_Singleton::start() + { + i2p::log::Logger().Start(); + LogPrint(eLogInfo, "Daemon: starting NetDB"); + i2p::data::netdb.Start(); + + bool upnp; i2p::config::GetOption("upnp.enabled", upnp); + if (upnp) { + d.UPnP = std::unique_ptr(new i2p::transport::UPnP); + d.UPnP->Start (); + } + + bool nettime; i2p::config::GetOption("nettime.enabled", nettime); + if (nettime) + { + d.m_NTPSync = std::unique_ptr(new i2p::util::NTPTimeSync); + d.m_NTPSync->Start (); + } + + bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); + bool ssu; i2p::config::GetOption("ssu", ssu); + bool checkInReserved; i2p::config::GetOption("reservedrange", checkInReserved); + LogPrint(eLogInfo, "Daemon: starting Transports"); + if(!ssu) LogPrint(eLogInfo, "Daemon: ssu disabled"); + if(!ntcp2) LogPrint(eLogInfo, "Daemon: ntcp2 disabled"); + + i2p::transport::transports.SetCheckReserved(checkInReserved); + i2p::transport::transports.Start(ntcp2, ssu); + if (i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundNTCP2()) + LogPrint(eLogInfo, "Daemon: Transports started"); + else + { + LogPrint(eLogError, "Daemon: failed to start Transports"); + /** shut down netdb right away */ i2p::transport::transports.Stop(); - LogPrint(eLogInfo, "Daemon: stopping NetDB"); i2p::data::netdb.Stop(); - if (d.httpServer) { - LogPrint(eLogInfo, "Daemon: stopping HTTP Server"); - d.httpServer->Stop(); - d.httpServer = nullptr; - } - if (d.m_I2PControlService) - { - LogPrint(eLogInfo, "Daemon: stopping I2PControl"); - d.m_I2PControlService->Stop (); - d.m_I2PControlService = nullptr; - } - i2p::crypto::TerminateCrypto (); - i2p::log::Logger().Stop(); - - return true; + return false; } + + bool http; i2p::config::GetOption("http.enabled", http); + if (http) { + std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); + uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); + LogPrint(eLogInfo, "Daemon: starting webconsole at ", httpAddr, ":", httpPort); + try + { + d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); + d.httpServer->Start(); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Daemon: failed to start webconsole: ", ex.what ()); + ThrowFatal ("Unable to start webconsole at ", httpAddr, ":", httpPort, ": ", ex.what ()); + } + } + + + LogPrint(eLogInfo, "Daemon: starting Tunnels"); + i2p::tunnel::tunnels.Start(); + + LogPrint(eLogInfo, "Daemon: starting Client"); + i2p::client::context.Start (); + + // I2P Control Protocol + bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); + if (i2pcontrol) { + std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr); + uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort); + LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort); + try + { + d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); + d.m_I2PControlService->Start (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "Daemon: failed to start I2PControl: ", ex.what ()); + ThrowFatal ("Unable to start I2PControl service at ", i2pcpAddr, ":", i2pcpPort, ": ", ex.what ()); + } + } + return true; + } + + bool Daemon_Singleton::stop() + { + LogPrint(eLogInfo, "Daemon: shutting down"); + LogPrint(eLogInfo, "Daemon: stopping Client"); + i2p::client::context.Stop(); + LogPrint(eLogInfo, "Daemon: stopping Tunnels"); + i2p::tunnel::tunnels.Stop(); + + if (d.UPnP) + { + d.UPnP->Stop (); + d.UPnP = nullptr; + } + + if (d.m_NTPSync) + { + d.m_NTPSync->Stop (); + d.m_NTPSync = nullptr; + } + + LogPrint(eLogInfo, "Daemon: stopping Transports"); + i2p::transport::transports.Stop(); + LogPrint(eLogInfo, "Daemon: stopping NetDB"); + i2p::data::netdb.Stop(); + if (d.httpServer) { + LogPrint(eLogInfo, "Daemon: stopping HTTP Server"); + d.httpServer->Stop(); + d.httpServer = nullptr; + } + if (d.m_I2PControlService) + { + LogPrint(eLogInfo, "Daemon: stopping I2PControl"); + d.m_I2PControlService->Stop (); + d.m_I2PControlService = nullptr; + } + i2p::crypto::TerminateCrypto (); + i2p::log::Logger().Stop(); + + return true; + } } } diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 32db8ff1..d000a9e0 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -49,8 +49,8 @@ namespace datagram std::shared_ptr DatagramDestination::GetSession(const i2p::data::IdentHash & ident) { return ObtainSession(ident); - } - + } + void DatagramDestination::SendDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) { if (session) @@ -67,21 +67,21 @@ namespace datagram auto msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {m_Signature.data (), m_Signature.size ()}, {payload, len}}, fromPort, toPort, false, !session->IsRatchets ()); // datagram session->SendMsg(msg); - } - } + } + } void DatagramDestination::SendRawDatagram (std::shared_ptr session, const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort) { if (session) session->SendMsg(CreateDataMessage ({{payload, len}}, fromPort, toPort, true, !session->IsRatchets ())); // raw } - + void DatagramDestination::FlushSendQueue (std::shared_ptr session) { if (session) session->FlushSendQueue (); - } - + } + void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort,uint8_t * const &buf, size_t len) { i2p::data::IdentityEx identity; @@ -242,7 +242,7 @@ namespace datagram if (msg || m_SendQueue.empty ()) m_SendQueue.push_back(msg); // flush queue right away if full - if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) + if (!msg || m_SendQueue.size() >= DATAGRAM_SEND_QUEUE_MAX_SIZE) FlushSendQueue(); } @@ -286,14 +286,14 @@ namespace datagram m_RemoteLeaseSet = m_LocalDestination->FindLeaseSet(m_RemoteIdent); if (!m_RemoteLeaseSet) { - if(!m_RequestingLS) + if(!m_RequestingLS) { m_RequestingLS = true; m_LocalDestination->RequestDestination(m_RemoteIdent, std::bind(&DatagramSession::HandleLeaseSetUpdated, this, std::placeholders::_1)); } return nullptr; - } - } + } + } if (!m_RoutingSession || m_RoutingSession->IsTerminated () || !m_RoutingSession->IsReadyToSend ()) { @@ -305,81 +305,81 @@ namespace datagram m_PendingRoutingSessions.clear (); found = true; break; - } + } if (!found) - { + { m_RoutingSession = m_LocalDestination->GetRoutingSession(m_RemoteLeaseSet, true); if (!m_RoutingSession->GetOwner () || !m_RoutingSession->IsReadyToSend ()) m_PendingRoutingSessions.push_back (m_RoutingSession); - } - } - + } + } + auto path = m_RoutingSession->GetSharedRoutingPath(); if (path && m_RoutingSession->IsRatchets () && m_LastUse > m_RoutingSession->GetLastActivityTimestamp ()*1000 + DATAGRAM_SESSION_PATH_TIMEOUT) - { + { m_RoutingSession->SetSharedRoutingPath (nullptr); path = nullptr; } - - if (path) + + if (path) { if (path->outboundTunnel && !path->outboundTunnel->IsEstablished ()) - { + { // bad outbound tunnel, switch outbound tunnel path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(path->outboundTunnel); if (!path->outboundTunnel) m_RoutingSession->SetSharedRoutingPath (nullptr); - } - - if (path->remoteLease && path->remoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) + } + + if (path->remoteLease && path->remoteLease->ExpiresWithin(DATAGRAM_SESSION_LEASE_HANDOVER_WINDOW)) { // bad lease, switch to next one - if (m_RemoteLeaseSet) + if (m_RemoteLeaseSet) { auto ls = m_RemoteLeaseSet->GetNonExpiredLeasesExcluding( - [&](const i2p::data::Lease& l) -> bool + [&](const i2p::data::Lease& l) -> bool { return l.tunnelID == path->remoteLease->tunnelID; }); auto sz = ls.size(); - if (sz) + if (sz) { auto idx = rand() % sz; path->remoteLease = ls[idx]; } else m_RoutingSession->SetSharedRoutingPath (nullptr); - } - else - { + } + else + { // no remote lease set? LogPrint(eLogWarning, "DatagramSession: no cached remote lease set for ", m_RemoteIdent.ToBase32()); m_RoutingSession->SetSharedRoutingPath (nullptr); - } + } } - } - else + } + else { // no current path, make one path = std::make_shared(); path->outboundTunnel = m_LocalDestination->GetTunnelPool()->GetNextOutboundTunnel(); if (!path->outboundTunnel) return nullptr; - - if (m_RemoteLeaseSet) + + if (m_RemoteLeaseSet) { // pick random next good lease auto ls = m_RemoteLeaseSet->GetNonExpiredLeases(); auto sz = ls.size(); - if (sz) + if (sz) { auto idx = rand() % sz; path->remoteLease = ls[idx]; } else return nullptr; - } - else + } + else { // no remote lease set currently, bail LogPrint(eLogWarning, "DatagramSession: no remote lease set found for ", m_RemoteIdent.ToBase32()); From f59d509b15f3c502a747a1184163538436738e0b Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 3 Feb 2021 15:12:27 +0300 Subject: [PATCH 4022/6300] fix rebase result build issue Signed-off-by: R4SAS --- daemon/Daemon.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index ccbfbb1d..e9d9110c 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -199,7 +199,6 @@ namespace util } if (ygg) i2p::context.UpdateNTCP2V6Address (yggaddr); - } } else i2p::context.PublishNTCP2Address (port, false); // unpublish From 33b82b5669ed8a622ad01b4da43cd5b8f847faa0 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 3 Feb 2021 14:24:43 -0500 Subject: [PATCH 4023/6300] check transport compatibility with peer before connecting --- libi2pd/Transports.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 5d6fc5f1..17345901 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -351,6 +351,7 @@ namespace transport try { auto r = netdb.FindRouter (ident); + if (!r || !r->IsCompatible (i2p::context.GetRouterInfo ())) return; { std::unique_lock l(m_PeersMutex); it = m_Peers.insert (std::pair(ident, { 0, r, {}, From 89e8d99294d4f444ae15c40ee5ee801147af680d Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 3 Feb 2021 20:09:43 -0500 Subject: [PATCH 4024/6300] check availability of particular address --- libi2pd/RouterInfo.cpp | 5 +++++ libi2pd/RouterInfo.h | 3 ++- libi2pd/Transports.cpp | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index d0784ece..8da49fc6 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -837,6 +837,11 @@ namespace data return m_SupportedTransports & (eNTCP2V4 | eNTCP2V6); } + bool RouterInfo::IsNTCP2V6 () const + { + return m_SupportedTransports & eNTCP2V6; + } + bool RouterInfo::IsV6 () const { return m_SupportedTransports & (eSSUV6 | eNTCP2V6); diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index fc55a9e2..45670ee5 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -170,6 +170,7 @@ namespace data bool IsSSU (bool v4only = true) const; bool IsSSUV6 () const; bool IsNTCP2 (bool v4only = true) const; + bool IsNTCP2V6 () const; bool IsV6 () const; bool IsV4 () const; bool IsMesh () const; @@ -179,7 +180,7 @@ namespace data void DisableV4 (); void EnableMesh (); void DisableMesh (); - bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; + bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; bool HasValidAddresses () const { return m_SupportedTransports; }; bool UsesIntroducer () const; bool IsIntroducer () const { return m_Caps & eSSUIntroducer; }; diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 17345901..01a4b337 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -397,7 +397,7 @@ namespace transport std::shared_ptr address; if (!peer.numAttempts) // NTCP2 ipv6 { - if (context.SupportsV6 ()) + if (context.GetRouterInfo ().IsNTCP2V6 () && peer.router->IsNTCP2V6 ()) { address = peer.router->GetPublishedNTCP2V6Address (); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) @@ -407,7 +407,7 @@ namespace transport } if (!address && peer.numAttempts == 1) // NTCP2 ipv4 { - if (context.SupportsV4 () && !peer.router->IsUnreachable ()) + if (context.GetRouterInfo ().IsNTCP2 (true) && peer.router->IsNTCP2 (true) && !peer.router->IsUnreachable ()) { address = peer.router->GetPublishedNTCP2V4Address (); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) @@ -453,7 +453,7 @@ namespace transport if (peer.numAttempts == 3) // Mesh { peer.numAttempts++; - if (context.SupportsMesh () && m_NTCP2Server) + if (m_NTCP2Server && context.GetRouterInfo ().IsMesh () && peer.router->IsMesh ()) { auto address = peer.router->GetYggdrasilAddress (); if (address) From dc64d1738ab2d1c2d19bad321bc8c691314ef721 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 4 Feb 2021 21:48:13 -0500 Subject: [PATCH 4025/6300] try both ipv4 and ipv6 SSU addresses if presented --- libi2pd/Transports.cpp | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 01a4b337..2eadda85 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -437,20 +437,41 @@ namespace transport else peer.numAttempts = 2; // switch to SSU } - if (peer.numAttempts == 2)// SSU + if (peer.numAttempts == 2 || peer.numAttempts == 3) // SSU { - peer.numAttempts++; - if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ())) - { - auto address = peer.router->GetSSUAddress (!context.SupportsV6 ()); - if (address && (!m_CheckReserved || !i2p::util::net::IsInReservedRange(address->host))) + if (m_SSUServer) + { + std::shared_ptr address; + if (peer.numAttempts == 2) // SSU ipv6 + { + if (context.GetRouterInfo ().IsSSUV6 () && peer.router->IsSSUV6 ()) + { + address = peer.router->GetSSUV6Address (); + if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) + address = nullptr; + } + peer.numAttempts++; + } + if (!address && peer.numAttempts == 3) // SSU ipv4 + { + if (context.GetRouterInfo ().IsSSU (true) && peer.router->IsSSU (true)) + { + address = peer.router->GetSSUAddress (true); + if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) + address = nullptr; + } + peer.numAttempts++; + } + if (address) { m_SSUServer->CreateSession (peer.router, address->host, address->port); return true; - } + } } + else + peer.numAttempts += 2; // switch to Mesh } - if (peer.numAttempts == 3) // Mesh + if (peer.numAttempts == 4) // Mesh { peer.numAttempts++; if (m_NTCP2Server && context.GetRouterInfo ().IsMesh () && peer.router->IsMesh ()) @@ -464,7 +485,7 @@ namespace transport } } } - LogPrint (eLogInfo, "Transports: No NTCP or SSU addresses available"); + LogPrint (eLogInfo, "Transports: No compatble NTCP2 or SSU addresses available"); i2p::data::netdb.SetUnreachable (ident, true); // we are here because all connection attempts failed peer.Done (); std::unique_lock l(m_PeersMutex); From 2d0e2191979e0730907a6b3921f132842353ee20 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 5 Feb 2021 17:24:11 -0500 Subject: [PATCH 4026/6300] add Yggdrasil adddress even if NTCP2 is not published. Correct reachable capacity --- libi2pd/RouterContext.cpp | 31 +++++++++++++++++++------------ libi2pd/RouterContext.h | 3 ++- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 797bb5d7..1fe66762 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -28,7 +28,7 @@ namespace i2p RouterContext::RouterContext (): m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false), - m_ShareRatio (100), m_Status (eRouterStatusOK), + m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_Error (eRouterErrorNone), m_NetID (I2PD_NET_ID) { } @@ -77,6 +77,7 @@ namespace i2p std::string ifname; i2p::config::GetOption("ifname", ifname); std::string ifname4; i2p::config::GetOption("ifname4", ifname4); std::string ifname6; i2p::config::GetOption("ifname6", ifname6); + uint8_t caps = 0; if (ipv4) { std::string host = "127.0.0.1"; @@ -90,7 +91,10 @@ namespace i2p host = i2p::util::net::GetInterfaceAddress(ifname4, false).to_string(); if (ssu) + { routerInfo.AddSSUAddress (host.c_str(), port, nullptr); + caps |= i2p::data::RouterInfo::eReachable | i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer; // R, BC + } } if (ipv6) { @@ -104,11 +108,13 @@ namespace i2p host = i2p::util::net::GetInterfaceAddress(ifname6, true).to_string(); if (ssu) + { routerInfo.AddSSUAddress (host.c_str(), port, nullptr); + caps |= i2p::data::RouterInfo::eReachable; // R + } } - routerInfo.SetCaps (i2p::data::RouterInfo::eReachable | - i2p::data::RouterInfo::eSSUTesting | i2p::data::RouterInfo::eSSUIntroducer); // LR, BC + routerInfo.SetCaps (caps); // caps + L routerInfo.SetProperty ("netId", std::to_string (m_NetID)); routerInfo.SetProperty ("router.version", I2P_VERSION); routerInfo.CreateBuffer (m_Keys); @@ -132,17 +138,18 @@ namespace i2p i2p::config::GetOption ("ntcp2.addressv6", host); m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address_v6::from_string (host), port); } - if (ygg) - { - auto yggaddr = i2p::util::net::GetYggdrasilAddress (); - if (!yggaddr.is_unspecified ()) - { - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, yggaddr, port); - UpdateRouterInfo (); - } - } } } + if (ygg) + { + auto yggaddr = i2p::util::net::GetYggdrasilAddress (); + if (!yggaddr.is_unspecified ()) + { + if (!m_NTCP2Keys) NewNTCP2Keys (); + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, yggaddr, port); + UpdateRouterInfo (); + } + } } void RouterContext::UpdateRouterInfo () diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 1def089a..12b77d65 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -31,7 +31,8 @@ namespace i2p eRouterStatusOK = 0, eRouterStatusTesting = 1, eRouterStatusFirewalled = 2, - eRouterStatusError = 3 + eRouterStatusError = 3, + eRouterStatusUnknown = 4 }; enum RouterError From 313921da566cfbcebce906326bd410c3707c6e38 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 6 Feb 2021 14:49:42 -0500 Subject: [PATCH 4027/6300] publish and request through exploratory tunnel if floodfill is not reachable --- libi2pd/I2NPProtocol.cpp | 21 ++++++++++++++++----- libi2pd/I2NPProtocol.h | 2 +- libi2pd/NetDb.cpp | 15 ++++++++++++++- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 5ad6df78..6897148b 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -244,7 +244,8 @@ namespace i2p return m; } - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router, uint32_t replyToken) + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router, + uint32_t replyToken, std::shared_ptr replyTunnel) { if (!router) // we send own RouterInfo router = context.GetSharedRouterInfo (); @@ -258,10 +259,20 @@ namespace i2p uint8_t * buf = payload + DATABASE_STORE_HEADER_SIZE; if (replyToken) { - memset (buf, 0, 4); // zero tunnelID means direct reply - buf += 4; - memcpy (buf, router->GetIdentHash (), 32); - buf += 32; + if (replyTunnel) + { + htobe32buf (buf, replyTunnel->GetNextTunnelID ()); + buf += 4; // reply tunnelID + memcpy (buf, replyTunnel->GetNextIdentHash (), 32); + buf += 32; // reply tunnel gateway + } + else + { + memset (buf, 0, 4); // zero tunnelID means direct reply + buf += 4; + memcpy (buf, context.GetIdentHash (), 32); + buf += 32; + } } uint8_t * sizePtr = buf; diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index b8fbb303..c9b0fc04 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -276,7 +276,7 @@ namespace tunnel const uint8_t * replyKey, const uint8_t * replyTag, bool replyECIES = false); std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0); + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0, std::shared_ptr replyTunnel = nullptr); std::shared_ptr CreateDatabaseStoreMsg (const i2p::data::IdentHash& storeHash, std::shared_ptr leaseSet); // for floodfill only std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0, std::shared_ptr replyTunnel = nullptr); bool IsRouterInfoMsg (std::shared_ptr msg); diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 20b54376..c3e33411 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -643,6 +643,7 @@ namespace data auto floodfill = GetClosestFloodfill (destination, dest->GetExcludedPeers ()); if (floodfill) { + if (direct && !floodfill->IsCompatible (i2p::context.GetRouterInfo ())) direct = false; // check if fllodfill is reachable if (direct) transports.SendMessage (floodfill->GetIdentHash (), dest->CreateRequestMessage (floodfill->GetIdentHash ())); else @@ -1087,7 +1088,19 @@ namespace data LogPrint (eLogInfo, "NetDb: Publishing our RouterInfo to ", i2p::data::GetIdentHashAbbreviation(floodfill->GetIdentHash ()), ". reply token=", replyToken); m_PublishExcluded.insert (floodfill->GetIdentHash ()); m_PublishReplyToken = replyToken; - transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken)); + if (floodfill->IsCompatible (i2p::context.GetRouterInfo ())) // able to connect? + // send directly + transports.SendMessage (floodfill->GetIdentHash (), CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken)); + else + { + // otherwise through exploratory + auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); + auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; + auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : nullptr; + if (inbound && outbound) + outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, + CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken, inbound)); + } } } From 374e0cbbc3d1c135471bded4aee02550f50bd8d2 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 6 Feb 2021 18:11:34 -0500 Subject: [PATCH 4028/6300] enable NTCP2 server for Yggdrasil --- daemon/Daemon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index e9d9110c..247f5f7f 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -339,7 +339,7 @@ namespace util if(!ntcp2) LogPrint(eLogInfo, "Daemon: ntcp2 disabled"); i2p::transport::transports.SetCheckReserved(checkInReserved); - i2p::transport::transports.Start(ntcp2, ssu); + i2p::transport::transports.Start(ntcp2 || i2p::context.SupportsMesh (), ssu); if (i2p::transport::transports.IsBoundSSU() || i2p::transport::transports.IsBoundNTCP2()) LogPrint(eLogInfo, "Daemon: Transports started"); else From 1bc3de8df47c63d68ab861bea43048d856dfe503 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 6 Feb 2021 18:23:50 -0500 Subject: [PATCH 4029/6300] add Yggdrasil address without NTCP2 --- daemon/Daemon.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 247f5f7f..91db50fd 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -197,13 +197,13 @@ namespace util if (!addr.is_unspecified () && addr != boost::asio::ip::address_v6::any ()) i2p::context.UpdateNTCP2V6Address (addr); // set ipv6 address if configured } - if (ygg) - i2p::context.UpdateNTCP2V6Address (yggaddr); } else i2p::context.PublishNTCP2Address (port, false); // unpublish } - + if (ygg) + i2p::context.UpdateNTCP2V6Address (yggaddr); + bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetAcceptsTunnels (!transit); uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); From 3b32da4f5ce5dfd7e8428f4836736a6cdb5fe5f3 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 6 Feb 2021 19:07:39 -0500 Subject: [PATCH 4030/6300] don't disable NTCP2 address if Yggdrasil address is presented --- daemon/Daemon.cpp | 4 ++++ libi2pd/RouterContext.cpp | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 91db50fd..e33f3d6d 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -202,7 +202,11 @@ namespace util i2p::context.PublishNTCP2Address (port, false); // unpublish } if (ygg) + { + if (!ntcp2) + i2p::context.PublishNTCP2Address (port, true); i2p::context.UpdateNTCP2V6Address (yggaddr); + } bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetAcceptsTunnels (!transit); diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 1fe66762..b2e83178 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -679,7 +679,8 @@ namespace i2p // read NTCP2 bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); - if (ntcp2) + bool ygg; i2p::config::GetOption("meshnets.yggdrasil", ygg); + if (ntcp2 || ygg) { if (!m_NTCP2Keys) NewNTCP2Keys (); UpdateNTCP2Address (true); // enable NTCP2 From c164601acf82bac02bfac100e8888fc58f6bda50 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 6 Feb 2021 21:25:16 -0500 Subject: [PATCH 4031/6300] reseed from clearnet only if ipv4 or ipv6 is enabled --- libi2pd/Reseed.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 33471b10..2e453b00 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -82,10 +82,17 @@ namespace data */ int Reseeder::ReseedFromServers () { - std::string reseedURLs; i2p::config::GetOption("reseed.urls", reseedURLs); + bool ipv6; i2p::config::GetOption("ipv6", ipv6); + bool ipv4; i2p::config::GetOption("ipv4", ipv4); + std::vector httpsReseedHostList; - boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on); - + if (ipv4 || ipv6) + { + std::string reseedURLs; i2p::config::GetOption("reseed.urls", reseedURLs); + if (!reseedURLs.empty ()) + boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on); + } + std::vector yggReseedHostList; if (!i2p::util::net::GetYggdrasilAddress ().is_unspecified ()) { From 7e4c33d27e9cb1b5711a2c4bcb27689366a74c09 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 7 Feb 2021 10:39:26 -0500 Subject: [PATCH 4032/6300] resend RouterInfo after some interval --- libi2pd/NTCP2.cpp | 11 ++++++++++- libi2pd/NTCP2.h | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 130a6a20..dc485d5b 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -343,6 +343,8 @@ namespace transport else LogPrint (eLogWarning, "NTCP2: Missing NTCP2 parameters"); } + m_NextRouterInfoResendTime = i2p::util::GetSecondsSinceEpoch () + NTCP2_ROUTERINFO_RESEND_INTERVAL + + rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; } NTCP2Session::~NTCP2Session () @@ -1012,7 +1014,14 @@ namespace transport m_NumSentBytes += bytes_transferred; i2p::transport::transports.UpdateSentBytes (bytes_transferred); LogPrint (eLogDebug, "NTCP2: Next frame sent ", bytes_transferred); - SendQueue (); + if (m_LastActivityTimestamp > m_NextRouterInfoResendTime) + { + m_NextRouterInfoResendTime += NTCP2_ROUTERINFO_RESEND_INTERVAL + + rand ()%NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD; + SendRouterInfo (); + } + else + SendQueue (); } } diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index f80ba75a..a7708872 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -34,6 +34,8 @@ namespace transport const int NTCP2_ESTABLISH_TIMEOUT = 10; // 10 seconds const int NTCP2_TERMINATION_TIMEOUT = 120; // 2 minutes const int NTCP2_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds + const int NTCP2_ROUTERINFO_RESEND_INTERVAL = 25*60; // 25 minuntes in seconds + const int NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD = 25*60; // 25 minuntes const int NTCP2_CLOCK_SKEW = 60; // in seconds const int NTCP2_MAX_OUTGOING_QUEUE_SIZE = 500; // how many messages we can queue up @@ -212,6 +214,7 @@ namespace transport bool m_IsSending; std::list > m_SendQueue; + uint64_t m_NextRouterInfoResendTime; // seconds since epoch }; class NTCP2Server: private i2p::util::RunnableServiceWithWork From 9d8eaf0ccb07b82b59753f530c4b73623dd8f3cd Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 8 Feb 2021 19:41:46 +0300 Subject: [PATCH 4033/6300] [win32] dont create notification when taskbar (explorer) restarted Signed-off-by: R4SAS --- Win32/Win32App.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 1785ffd6..15157355 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -80,18 +80,19 @@ namespace win32 DestroyMenu(hPopup); } - static void AddTrayIcon (HWND hWnd) + static void AddTrayIcon (HWND hWnd, bool notify = false) { NOTIFYICONDATA nid; memset(&nid, 0, sizeof(nid)); nid.cbSize = sizeof(nid); nid.hWnd = hWnd; nid.uID = ID_TRAY_ICON; + nid.uFlags = notify ? NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO : NIF_ICON | NIF_MESSAGE | NIF_TIP; nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; nid.uCallbackMessage = WM_TRAYICON; nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (MAINICON)); strcpy (nid.szTip, "i2pd"); - strcpy (nid.szInfo, "i2pd is starting"); + if (notify) strcpy (nid.szInfo, "i2pd is starting"); Shell_NotifyIcon(NIM_ADD, &nid ); } @@ -195,7 +196,7 @@ namespace win32 case WM_CREATE: { s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); - AddTrayIcon (hWnd); + AddTrayIcon (hWnd, true); break; } case WM_CLOSE: @@ -380,7 +381,7 @@ namespace win32 default: { if (uMsg == s_uTaskbarRestart) - AddTrayIcon (hWnd); + AddTrayIcon (hWnd, false); break; } } From 01df1647bcfe7a9b92a90b457fdf2683e338a4f9 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 9 Feb 2021 08:32:35 +0300 Subject: [PATCH 4034/6300] [httpproxy] add viewport and update styles on error Signed-off-by: R4SAS --- libi2pd_client/HTTPProxy.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index b2b07fba..5deaa7b5 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -39,10 +39,12 @@ namespace proxy { static const char *pageHead = "\r\n" + " \r\n" " I2Pd HTTP proxy\r\n" " \r\n" "\r\n" ; From abe1af7b4f3d1cf3e2161965568d65cf3ceda443 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 10 Feb 2021 17:00:35 +0300 Subject: [PATCH 4035/6300] moved qt and android sources inn separate repositories Signed-off-by: R4SAS --- android/.gitignore | 18 - android/AndroidManifest.xml | 60 - android/README.md | 19 - android/assets/addressbook/addresses.csv | 693 --- android/assets/certificates | 1 - android/assets/i2pd.conf | 92 - android/assets/subscriptions.txt | 3 - android/assets/tunnels.conf | 33 - android/assets/tunnels.d | 1 - android/build.gradle | 105 - android/build.xml | 96 - android/gradle.properties | 3 - android/gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - android/gradlew | 172 - android/gradlew.bat | 84 - android/jni/Android.mk | 73 - android/jni/Application.mk | 36 - android/jni/DaemonAndroid.cpp | 222 - android/jni/DaemonAndroid.h | 97 - android/jni/i2pd_android.cpp | 114 - android/jni/org_purplei2p_i2pd_I2PD_JNI.h | 53 - android/proguard-project.txt | 20 - android/project.properties | 14 - android/res/drawable/icon.png | Bin 37177 -> 0 bytes .../drawable/itoopie_notification_icon.png | Bin 1940 -> 0 bytes android/res/layout/activity_perms_asker.xml | 26 - .../res/layout/activity_perms_explanation.xml | 25 - android/res/layout/activity_web_console.xml | 22 - android/res/menu/options_main.xml | 31 - android/res/values-ru/strings.xml | 40 - android/res/values/strings.xml | 41 - android/res/values/template-dimens.xml | 16 - android/settings.gradle | 1 - .../src/org/purplei2p/i2pd/DaemonWrapper.java | 374 -- .../org/purplei2p/i2pd/ForegroundService.java | 188 - .../src/org/purplei2p/i2pd/I2PDActivity.java | 486 --- .../i2pd/I2PDPermsAskerActivity.java | 170 - .../i2pd/I2PDPermsExplanationActivity.java | 38 - android/src/org/purplei2p/i2pd/I2PD_JNI.java | 31 - .../i2pd/NetworkStateChangeReceiver.java | 30 - .../purplei2p/i2pd/WebConsoleActivity.java | 67 - qt/.gitignore | 1 - qt/i2pd_qt/.gitignore | 13 - qt/i2pd_qt/AboutDialog.cpp | 21 - qt/i2pd_qt/AboutDialog.h | 22 - qt/i2pd_qt/AboutDialog.ui | 194 - qt/i2pd_qt/BuildDateTimeQt.h | 7 - qt/i2pd_qt/ClientTunnelPane.cpp | 220 - qt/i2pd_qt/ClientTunnelPane.h | 126 - qt/i2pd_qt/DaemonQT.cpp | 195 - qt/i2pd_qt/DaemonQT.h | 88 - qt/i2pd_qt/DelayedSaveManager.cpp | 3 - qt/i2pd_qt/DelayedSaveManager.h | 26 - qt/i2pd_qt/DelayedSaveManagerImpl.cpp | 140 - qt/i2pd_qt/DelayedSaveManagerImpl.h | 84 - qt/i2pd_qt/I2pdQtTypes.h | 7 - qt/i2pd_qt/I2pdQtUtil.cpp | 12 - qt/i2pd_qt/I2pdQtUtil.h | 9 - qt/i2pd_qt/MainWindowItems.cpp | 2 - qt/i2pd_qt/MainWindowItems.h | 17 - qt/i2pd_qt/README.md | 3 - qt/i2pd_qt/Saver.cpp | 6 - qt/i2pd_qt/Saver.h | 25 - qt/i2pd_qt/SaverImpl.cpp | 81 - qt/i2pd_qt/SaverImpl.h | 33 - qt/i2pd_qt/ServerTunnelPane.cpp | 278 -- qt/i2pd_qt/ServerTunnelPane.h | 183 - qt/i2pd_qt/SignatureTypeComboboxFactory.cpp | 2 - qt/i2pd_qt/SignatureTypeComboboxFactory.h | 86 - qt/i2pd_qt/TunnelConfig.cpp | 67 - qt/i2pd_qt/TunnelConfig.h | 260 -- qt/i2pd_qt/TunnelPane.cpp | 405 -- qt/i2pd_qt/TunnelPane.h | 191 - qt/i2pd_qt/TunnelsPageUpdateListener.h | 12 - qt/i2pd_qt/data/.gitignore | 2 - .../data/icons/128x128/website.i2pd.i2pd.png | Bin 19680 -> 0 bytes .../data/icons/16x16/website.i2pd.i2pd.png | Bin 1379 -> 0 bytes .../data/icons/22x22/website.i2pd.i2pd.png | Bin 1770 -> 0 bytes .../data/icons/24x24/website.i2pd.i2pd.png | Bin 1944 -> 0 bytes .../data/icons/256x256/website.i2pd.i2pd.png | Bin 54469 -> 0 bytes .../data/icons/32x32/website.i2pd.i2pd.png | Bin 2890 -> 0 bytes .../data/icons/48x48/website.i2pd.i2pd.png | Bin 4910 -> 0 bytes .../data/icons/512x512/website.i2pd.i2pd.png | Bin 171666 -> 0 bytes .../data/icons/64x64/website.i2pd.i2pd.png | Bin 7273 -> 0 bytes qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml | 57 - qt/i2pd_qt/data/website.i2pd.i2pd.desktop | 11 - qt/i2pd_qt/generalsettingswidget.ui | 3810 ----------------- qt/i2pd_qt/i2pd.qrc | 6 - qt/i2pd_qt/i2pd.rc | 32 - qt/i2pd_qt/i2pd_qt.pro | 179 - qt/i2pd_qt/logviewermanager.cpp | 45 - qt/i2pd_qt/logviewermanager.h | 130 - qt/i2pd_qt/mainwindow.cpp | 1126 ----- qt/i2pd_qt/mainwindow.h | 883 ---- qt/i2pd_qt/mainwindow.ui | 1053 ----- qt/i2pd_qt/pagewithbackbutton.cpp | 24 - qt/i2pd_qt/pagewithbackbutton.h | 21 - qt/i2pd_qt/resources/icons/mask.ico | Bin 156564 -> 0 bytes qt/i2pd_qt/resources/images/icon.png | Bin 8712 -> 0 bytes qt/i2pd_qt/routercommandswidget.ui | 127 - qt/i2pd_qt/statusbuttons.ui | 163 - qt/i2pd_qt/textbrowsertweaked1.cpp | 9 - qt/i2pd_qt/textbrowsertweaked1.h | 26 - qt/i2pd_qt/tunnelform.ui | 104 - qt/i2pd_qt/widgetlock.cpp | 1 - qt/i2pd_qt/widgetlock.h | 35 - qt/i2pd_qt/widgetlockregistry.cpp | 2 - qt/i2pd_qt/widgetlockregistry.h | 27 - 109 files changed, 14293 deletions(-) delete mode 100644 android/.gitignore delete mode 100755 android/AndroidManifest.xml delete mode 100644 android/README.md delete mode 100644 android/assets/addressbook/addresses.csv delete mode 120000 android/assets/certificates delete mode 100644 android/assets/i2pd.conf delete mode 100644 android/assets/subscriptions.txt delete mode 100644 android/assets/tunnels.conf delete mode 120000 android/assets/tunnels.d delete mode 100644 android/build.gradle delete mode 100644 android/build.xml delete mode 100644 android/gradle.properties delete mode 100644 android/gradle/wrapper/gradle-wrapper.jar delete mode 100644 android/gradle/wrapper/gradle-wrapper.properties delete mode 100755 android/gradlew delete mode 100644 android/gradlew.bat delete mode 100755 android/jni/Android.mk delete mode 100755 android/jni/Application.mk delete mode 100644 android/jni/DaemonAndroid.cpp delete mode 100644 android/jni/DaemonAndroid.h delete mode 100755 android/jni/i2pd_android.cpp delete mode 100644 android/jni/org_purplei2p_i2pd_I2PD_JNI.h delete mode 100644 android/proguard-project.txt delete mode 100644 android/project.properties delete mode 100644 android/res/drawable/icon.png delete mode 100644 android/res/drawable/itoopie_notification_icon.png delete mode 100644 android/res/layout/activity_perms_asker.xml delete mode 100644 android/res/layout/activity_perms_explanation.xml delete mode 100644 android/res/layout/activity_web_console.xml delete mode 100644 android/res/menu/options_main.xml delete mode 100755 android/res/values-ru/strings.xml delete mode 100755 android/res/values/strings.xml delete mode 100644 android/res/values/template-dimens.xml delete mode 100644 android/settings.gradle delete mode 100644 android/src/org/purplei2p/i2pd/DaemonWrapper.java delete mode 100644 android/src/org/purplei2p/i2pd/ForegroundService.java delete mode 100755 android/src/org/purplei2p/i2pd/I2PDActivity.java delete mode 100644 android/src/org/purplei2p/i2pd/I2PDPermsAskerActivity.java delete mode 100644 android/src/org/purplei2p/i2pd/I2PDPermsExplanationActivity.java delete mode 100644 android/src/org/purplei2p/i2pd/I2PD_JNI.java delete mode 100644 android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java delete mode 100644 android/src/org/purplei2p/i2pd/WebConsoleActivity.java delete mode 100644 qt/.gitignore delete mode 100644 qt/i2pd_qt/.gitignore delete mode 100644 qt/i2pd_qt/AboutDialog.cpp delete mode 100644 qt/i2pd_qt/AboutDialog.h delete mode 100644 qt/i2pd_qt/AboutDialog.ui delete mode 100644 qt/i2pd_qt/BuildDateTimeQt.h delete mode 100644 qt/i2pd_qt/ClientTunnelPane.cpp delete mode 100644 qt/i2pd_qt/ClientTunnelPane.h delete mode 100644 qt/i2pd_qt/DaemonQT.cpp delete mode 100644 qt/i2pd_qt/DaemonQT.h delete mode 100644 qt/i2pd_qt/DelayedSaveManager.cpp delete mode 100644 qt/i2pd_qt/DelayedSaveManager.h delete mode 100644 qt/i2pd_qt/DelayedSaveManagerImpl.cpp delete mode 100644 qt/i2pd_qt/DelayedSaveManagerImpl.h delete mode 100644 qt/i2pd_qt/I2pdQtTypes.h delete mode 100644 qt/i2pd_qt/I2pdQtUtil.cpp delete mode 100644 qt/i2pd_qt/I2pdQtUtil.h delete mode 100644 qt/i2pd_qt/MainWindowItems.cpp delete mode 100644 qt/i2pd_qt/MainWindowItems.h delete mode 100644 qt/i2pd_qt/README.md delete mode 100644 qt/i2pd_qt/Saver.cpp delete mode 100644 qt/i2pd_qt/Saver.h delete mode 100644 qt/i2pd_qt/SaverImpl.cpp delete mode 100644 qt/i2pd_qt/SaverImpl.h delete mode 100644 qt/i2pd_qt/ServerTunnelPane.cpp delete mode 100644 qt/i2pd_qt/ServerTunnelPane.h delete mode 100644 qt/i2pd_qt/SignatureTypeComboboxFactory.cpp delete mode 100644 qt/i2pd_qt/SignatureTypeComboboxFactory.h delete mode 100644 qt/i2pd_qt/TunnelConfig.cpp delete mode 100644 qt/i2pd_qt/TunnelConfig.h delete mode 100644 qt/i2pd_qt/TunnelPane.cpp delete mode 100644 qt/i2pd_qt/TunnelPane.h delete mode 100644 qt/i2pd_qt/TunnelsPageUpdateListener.h delete mode 100644 qt/i2pd_qt/data/.gitignore delete mode 100644 qt/i2pd_qt/data/icons/128x128/website.i2pd.i2pd.png delete mode 100644 qt/i2pd_qt/data/icons/16x16/website.i2pd.i2pd.png delete mode 100644 qt/i2pd_qt/data/icons/22x22/website.i2pd.i2pd.png delete mode 100644 qt/i2pd_qt/data/icons/24x24/website.i2pd.i2pd.png delete mode 100644 qt/i2pd_qt/data/icons/256x256/website.i2pd.i2pd.png delete mode 100644 qt/i2pd_qt/data/icons/32x32/website.i2pd.i2pd.png delete mode 100644 qt/i2pd_qt/data/icons/48x48/website.i2pd.i2pd.png delete mode 100644 qt/i2pd_qt/data/icons/512x512/website.i2pd.i2pd.png delete mode 100644 qt/i2pd_qt/data/icons/64x64/website.i2pd.i2pd.png delete mode 100644 qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml delete mode 100644 qt/i2pd_qt/data/website.i2pd.i2pd.desktop delete mode 100644 qt/i2pd_qt/generalsettingswidget.ui delete mode 100644 qt/i2pd_qt/i2pd.qrc delete mode 100644 qt/i2pd_qt/i2pd.rc delete mode 100644 qt/i2pd_qt/i2pd_qt.pro delete mode 100644 qt/i2pd_qt/logviewermanager.cpp delete mode 100644 qt/i2pd_qt/logviewermanager.h delete mode 100644 qt/i2pd_qt/mainwindow.cpp delete mode 100644 qt/i2pd_qt/mainwindow.h delete mode 100644 qt/i2pd_qt/mainwindow.ui delete mode 100644 qt/i2pd_qt/pagewithbackbutton.cpp delete mode 100644 qt/i2pd_qt/pagewithbackbutton.h delete mode 100644 qt/i2pd_qt/resources/icons/mask.ico delete mode 100644 qt/i2pd_qt/resources/images/icon.png delete mode 100644 qt/i2pd_qt/routercommandswidget.ui delete mode 100644 qt/i2pd_qt/statusbuttons.ui delete mode 100644 qt/i2pd_qt/textbrowsertweaked1.cpp delete mode 100644 qt/i2pd_qt/textbrowsertweaked1.h delete mode 100644 qt/i2pd_qt/tunnelform.ui delete mode 100644 qt/i2pd_qt/widgetlock.cpp delete mode 100644 qt/i2pd_qt/widgetlock.h delete mode 100644 qt/i2pd_qt/widgetlockregistry.cpp delete mode 100644 qt/i2pd_qt/widgetlockregistry.h diff --git a/android/.gitignore b/android/.gitignore deleted file mode 100644 index 57a0a6cd..00000000 --- a/android/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -gen -tests -bin -libs -log* -obj -.cxx -.gradle -.idea -.externalNativeBuild -ant.properties -local.properties -build.sh -android.iml -build -*.iml -*.local -*.jks diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml deleted file mode 100755 index d0e8ecb6..00000000 --- a/android/AndroidManifest.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/README.md b/android/README.md deleted file mode 100644 index e0850b15..00000000 --- a/android/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# how to compile? -## Install the gradle + NDK or use android-studio -[https://gradle.org/install/](https://gradle.org/install/) - -## Install the depencies -``` -git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git -b boost-1_72_0 -git clone https://github.com/PurpleI2P/android-ifaddrs.git -git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git -git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git -``` -## Set libs in jni/Application.mk on 24 line: -``` -# change to your own -I2PD_LIBS_PATH = /home/user/i2pd/android/ -``` - -## compile apk file -gradle clean assembleRelease diff --git a/android/assets/addressbook/addresses.csv b/android/assets/addressbook/addresses.csv deleted file mode 100644 index 97a43dfc..00000000 --- a/android/assets/addressbook/addresses.csv +++ /dev/null @@ -1,693 +0,0 @@ -00.i2p,zmzpltxslembpaupg3srh4bbhv5txgh5jmms6sfj4hzsvlv3xugq -0ipfs.i2p,cdii3ou5mve5sfxyirs6kogt4tbvivk2d6o25awbcbazjrlhjeza -0xcc.i2p,gawouxh2sg32cluwlqsnpy3dwedvoqtfroi4evvdvm2pfv7tdadq -1.fcp.freenet.i2p,cuxbeputgxn75ak4nr7ltp7fjktnzl5sul3wstwnsoytbbpb4ixq -102chan.i2p,xxu3lso4h2rh6wmrxiou3ax7r7la7x6dhoepnku3jvrlwp35pefq -1st.i2p,rduua7bhest6rwsmmyttzssfdw3p4eu6bgl3mb4hin32qo3x5zfq -2.fcp.freenet.i2p,ndsznnipoeyapnsg3gj3yi2dzsqduxwalmujm5mzjm7e6x374tta -333.i2p,ctvfe2fimcsdfxmzmd42brnbf7ceenwrbroyjx3wzah5eudjyyza -55cancri.i2p,b4iqenefh2fr4xtuq6civfc6nhnia6e2yo36pf7vcgdvrwmh7xua -adab.i2p,pxjr6f2cig6v7v7ekam3smdnkqgmgseyy5cdwrozdyejm7jknkha -alice.i2p,iq26r2ls2qlkhbn62cvgb6a4iib7m5lkoulohdua5z6uvzlovjtq -always.i2p,wp43sdtuxum6gxbjvyeor35r5yvgtkp3dcu7dv47lx22zeb3relq -amazone.i2p,e6kq73lsxaeyiwpmykdbdo3uy4ppj64bl7y3viegp6mqrilqybqa -amiga.i2p,edy2xappzjjh7bxqounevji4wd2binqkv7gft4usrkan45xhbk5q -amobius.i2p,rj6432agdprun5baai2hj62xfhb4l75uvzl55dhj6z5zzoxv3htq -anarchistfaq.i2p,xosberjz2geveh5dcstztq5kwew6xx2brrqaorkjf2323bjzcd3q -animal.i2p,5iedafy32swqq4t2wcmjb4fvg3onscng7ct7wb237jkvrclaftla -anodex.i2p,25cb5kixhxm6i6c6wequrhi65mez4duc4l5qk6ictbik3tnxlu6a -anoncoin.i2p,nmi3loretkk4zbili32t2e5wyznwoxcsgzmd2z4ll3msgndyqpfa -anongw.i2p,owrnciwubb3f3dctvlmnaknb6tjdxtlzvv7klocb45mmhievdjhq -anonsfw.i2p,ir6hzi66izmvqx3usjl6br3nndkpazonlckrzt3gtltqcy5ralyq -anonymnet.i2p,77ouyl2ane7ffgydosd4ye42g67aomtc4jrusmi76lds5qonlffa -anonynanny.i2p,l2lnhq2dynnmf3m46tcbpcmbbn4kifjgt26go6n2hlapy4drhyja -anonyradio.i2p,cbobsax3rhoyjbk7ii2nd2fnl5bxh3x7bbearokyxgvmudn7o5bq -antipiracyagency.i2p,by4kcmklz7xnkai6ndfio47kts3rndm6wwleegtxghllimikdapq -antipiratbyran.i2p,y2qbhrvuciifbszaqqwxd5t75bomp7kzdqx4yxsrkaq542t75k3a -aosp.i2p,ly7raldsh2na2cgw5yvueyvqqjgx3vbqinecjrqdldgya76i2p2q -arc2.i2p,rnmosuwvtftfcrk5sk7zoyhyadh2g4dhe2mif5ml7qjisgkyw2na -archaicbinarybbs.i2p,t7o2tw36cffedgfr6kahewpkrntofnliuapji2e4rucl3os55epa -archiv.tutorials.i2p,lldr2miowq6353fxy44pnxfk37d6yn2f6kaivzecbmvvnnf5exyq -archive.i2p,x54d5st3dl6mwgfxj6raiekqkypo5pdvuex3n62szwju7hgefiyq -archive.syndie.i2p,abbyu5n3mh3nj7pe3b6byldrxswvva5ttxcafsnnseidanurq3kq -ardor-wallet.i2p,tm23k5ny3umhf6vf3kghnnwacli5zywq5wrr3xcqowbcofuyr4gq -ardvark.i2p,jcmw2sol3hruwc6rfinonx4e23pjkukkg7lg7xt7xb2gpiyyraiq -arf.i2p,o46lsq4u7udxg3qqlidrmpj4lb4nr7ldxmbb2x53nftndaeyxqeq -arkan.i2p,7o5y2lyyrjx5tf6l4fyumywui7msjv5azaaheatvw5sqj7mxbuvq -asciiwhite.i2p,itbzny5ktuenhjwjfqx3jravolhlj5wullhhr2m4qr6k2emnm5dq -aspnet.i2p,tsb7zqru57p4q2a7cto2lko4w5cg4lieglwm6t27c44fkphqmf2a -asylum.i2p,p45ejjw4p2q6nq3mzi6cm6ep35grtzshboidj2lojmrmic22noha -auchan.i2p,6vxz4yp3vhjwbkmxajj7wiikxafwujig63gkhjknbq6xh4rqpm5a -aum.i2p,ohdfneqxapfd3fwfbum4tut7z6k3rnr7rrguoxdrrfe2tln2kpbq -awup.i2p,v6g32duzrkacnrezfbll3pza5u37h7lnukr2wbsk6rqen6prhbga -b.i2p,272kt3gcx6wjurunzaiiwld7s5p4mpjewfubzmlcvw2vie62ckpq -bacardi.i2p,hivhnx2v47vh234c7coi2urj5cyvbl4bu3ypjr7snklortyqeljq -backup.i2p,kepphem42whle3rkfv26wcksmnegdbg6rdp6t3oobdkc2fmzrdkq -badfish.i2p,f6v26gyr4eipy3a7pi2voulw5qvob6dg7zij6xpo2ywbi5tvbu6a -badtoyz.i2p,3qz6ubtwlt2c4iasofjirkckq43u5fgkzyg7mlutcsym5gzhijna -barry.i2p,4kyahq53ol52n23l44tefgeaxqpp3cbb632t5k3umdvqcooevdzq -bash.i2p,s3wouoilbl3mrefxjhp4qoyujgok34e7y6vmpbu6hx4342ivqo4q -bdl.i2p,kp6fnuulenbjm7r26pfbmjcq3u7c7kvxeajodvgr5flcnskdgi5a -bdsm.i2p,pa7fxql5jljegg7j5tglhnnaod2sptq3gxvdn3ji6muqyhgn3poq -betaguru.i2p,d7cduwwhrcc2voameqfkvd66u3advu4jw2p6pysgax35vq6ovriq -beyond.i2p,uaicfqlrpjtitqbqkpfujanj5dollzfzee5glsuls67ekw6hlpoa -bible.i2p,pypz7ca24n3lyp4tm3kvncg3ltp3gd5pgnacc6zltoeffiyyegda -bible4u.i2p,xs6lr2g5jiaajtb3nkno2zmy34eipitrggooxb7wtey7uko7bqmq -bigbrother.i2p,tnxiifs6uticzyg6ac4lhv2l5luwi6xra7yngocro56ive5e4jsq -bitlox.i2p,lqw5khxcdntlv3u4vhn53upcqirplvnc4etjlmoytrzs66ytettq -bittorrent.i2p,pgax2vz572i4zsp6u6paox5xubmjrkqohq6g4hvlp6ruzzy56l5q -bk1k.i2p,nlyegmtyfffo5jfgg5h4dxxnlmqko2g36gpaye5a7vd3is35xxfq -bl.i2p,e73d6uhnfbylza6wqkhxejmqeyfb7thkzw35gn5ojmna64jzyk2a -black.i2p,sjwueu62qpe6dtv5b322k3f23fl4uz3w6qe6wcrwauiwpnymypfq -blackbox.i2p,7josyf7zjieoib3ovmr5a4dh5w64kmfh45lv5h436eljtgfegtqa -blackexchange.i2p,ztgr5kghkyn43fhhkuycroxgfti6cojo3vg4wdd3usqonyvrla5q -blog.curiosity.i2p,yiz6jec5k7ccxdgnh7msqa4ze52bqqmf6rpq6bqdyojra2erd4ta -blog.polecat.i2p,orlccceubewvxo3fbdyydq6e4uuidbs4xd5u2gyqbculnowo3ehq -blog.tinlans.i2p,ylkch2nkrwehakx4z6wiyjbeqwlgasknukdkex6r6yq4xusrjnda -bluebeam.i2p,lvxp3cbcfwtol57d5pmrsck32t7ndutlxubjb4smaf32bynhlk6a -blueheron.i2p,anfb5jrhixjmvkyxctqwkezqer7dbob22wge2bh6wsewbhgnftfa -bnc.i2p,fr4zbcygmx2vdct6nrabakfys4b4derm6jqu2ovppkgqillvlqxa -bob.i2p,i76m7dwm5hnapljendbie6fc5y3mjlkdlduo3tvbwiwmvhxbpyaa -bobcat.i2p,ftuukjtcquuvppt726w37boit7gp5hf2yxwfop35prx3grzzzxlq -bobthebuilder.i2p,qlahgthqhr4uojkkwahnper2cl3ro5f5gtzy5t4lzapbzo4osy6q -boerse.i2p,7633w56hd53sesr6b532r5qlbdnvyl5bnvama6ign6xryaxol4rq -bofh.i2p,auvuinzogu6gc4pwsgbjijuszxgcjygciu2wy53pfz7mo5nfpc5a -boing.i2p,bgsq33bh74j66hn4oh7oovlvuhhdyw22lq2qi2fnv3jyh2ryap3a -books.manveru.i2p,eb2tisc2vr5jvjqrixrozcujiucwxg4m722stxwho5666ipl67zq -bote.i2p,bhjhc3lsdqzoyhxwzyrd63kvyg4br6n2337d74blyintae66mr2a -bozo.i2p,7a2d23h6htprhzrol36vgwgklsbqrnuya4tbaaaspmaeaodt57iq -brittanyworld.i2p,e76umhhic3474sdxiuax25ixyfg7y3z7oojj4fmxvhgv3ruet6aa -bt.i2p,uhkuu54pg47zey76h45tnvsdtpkf5bthbtrjgnaloi5m54h4hlaq -bugfuzz.i2p,ubszn4gsf22vga67rvzzlg4qj2bfcq6o52fmxz46xruawqm6z7rq -burntout.i2p,lkep3fd7tjvxrs25crr2c3jy7xm4s7bqiua5r327zgpw37sgyerq -bytepay.i2p,7amc4ztwkzu3cgsaaaw3223ohuihn5hlsqc6gpf2rxdyptdkyugq -ca.i2pd.i2p,u5safmawcxj5vlrdtqrsqbsndkr5cfenpicgg5euu4xqm73yicba -cases.i2p,kmpmk2fmineaiwublteqlifg4fkmewnhmxqlcgg7qwecz6daj43a -cathugger.i2p,vq43xjjcnejqpzfprws5qzrea2siieshu4tglpdepql2w3w3bpba -cbs.i2p,u3lp7wazvq6opodzwjg5sc5w5kwxehmxd4wcdpt4s4j2k4dx4apq -cerapadus.i2p,zroed2cxga5zeuu6rcvmp2yfi77nzduw7yhdplbeuqkuyxwbrzaq -cerebrum.i2p,u5gtsfn267udwfh2uq35jiabkufifvcbgv456zz34cydutsiw2eq -cgan.i2p,43z65gdr52xe3fxmkumwp3dzhedu4tu4rdtzr24hz5b4awcpfbqa -chat.i2p,ollpwnp6yidc3obbb3famgt6rw5jg5w3k3a6z7hhaegj6gcohiuq -chess.fillament.i2p,tv6wbanei647yf5bie4dhg2wmybkjurezlpdfwftc5ajqlfswwya -chess.i2p,sbnoqznp5yzxals3vs6nzyqaj2fetvonys4e3b3x4ktmfeus54sa -china.i2p,wit6f2zx6dtuqqze6nhbykrds3idppfirxvhf2f7ydqoqf4xdzeq -chitanka.i2p,u4s3jneepk3akoez46kqiwikoezi6zyj2ibjkjyi4uuvsbcojzba -ciaran.i2p,2r3645eete6xwbfu62ogonudcrcgqq25sbnij5v4geru74yrscna -ciphercraft.i2p,7s5pkqbpbfdkxtwuu2e2iwstbikyewvvscy76lij4x5pfbygbjca -closedshop.i2p,6fg67mbw2okopzyonsck4bsy3cy7l2fame56uiysr2cezhjhzdbq -cneal.i2p,g4za73ffigv3ht4jnhzy4dae52djjq7lqcguqsfg3w5cxzqm7nba -co.i2p,3mvo5eifcwplcsoubtvqkzdahwo2sdhfygfdde7lj2glybk4q22q -codevoid.i2p,2mukrqwtinsw27uoejtrz74zxtilyhnnfdyso7j3yo6vaa6nzlaa -colombo-bt.i2p,cyr75zgiu2uuzap5zeosforbgvpfbqos2g6spe4qfulvzpyhnzxa -complication.i2p,x2av6rwj5e5tp64yhdmifdyleo4wblw4ncrrcrabxwscuevpdv7a -comwiz.i2p,6p7zqfotzbd66etl5xqy3p6xvr5ijucru3am2xqa7wmnj6vf3djq -confessions.i2p,lh5vitshufxpmyr44zgyymebo5elc42eda7pxvn5lmtes47c7rxa -connelly.i2p,5yrris3nigb3fapvzrlrcaew6cdmzdknzvgrc7y2jpn3ntqurweq -costeira.i2p,abhty5xlmnyab2kqdxcd56352kcescxoux3p6dbqdrghggyygnxa -cowsay.i2p,q4ghzfpah4ffvm3bhc6fdkrznk5f6jxfjm2daytlparznai5d54q -crstrack.i2p,mm3zx3besctrx6peq5wzzueil237jdgscuvn5ugwilxrwzyuajja -crypthost.i2p,zywhrxtnkjc3rxxvxbocom7ml4hnutomgtuvqrwyf3rhuupnq5ca -crypto.i2p,vffax5jzewwv6pfim55hvhqyynafkygdalvzoqd74lkib3hla3ta -cryptostorm.i2p,mlu7mswyirjf53usqq7gyamvqc6rqihezgdbevov3dkxmkfo57aq -curiosity.i2p,eomeif4xrykxlzhawc3icdilje5iammijos6tyizwhrfh3j7qdvq -cvs.i2p,yd6k7dzpsa2tnlzx4q7xqkmd4qsjk5xk5hbiqpiarwbeyvxaxgba -danwin1210.i2p,eoqdf4no5dxn4tw5n256kkd4lzz3uk4p47np4mepsykpsdzrnvba -darknetnow.i2p,gkx3o5fy7mv7l4psqqnhp35d5iun7rt3soci6ylf3rgb7a5a655q -darknut.i2p,2mk37gtvpk2i63o6vl7vna4dr46rqexxetupgn5efuuins7x3qya -darkrealm.i2p,gbh4eerxdsph7etxsxznfhvmuiz54trlkenakqep343u4xcoekzq -darrob.i2p,hz2xhtpeo6btgiwi6od4qj2575ml5o2246rd5orarruyjhd63zja -dashninja.i2p,dzjzoefy7fx57h5xkdknikvfv3ckbxu2bx5wryn6taud343g2jma -davidkra.i2p,nq7ca2egm563nir3xegfv52ocgmxstpz56droji4jgnzfoosk45a -dcherukhin.i2p,qa4boq364ndjdgow4kadycr5vvch7hofzblcqangh3nobzvyew7a -de-ebook-archiv.i2p,6mhurvyn6b6j6xa4a3wpuz7ovpsejbuncvyl6rnhepasfgdgmn7q -de-ebooks.i2p,epqdyuuhtydkg5muwwq47n7jvr66pq4jheve7ky5euls6klzwuyq -dead.i2p,7ko27dxvicr2sezvykkrfiktlghx5y5onup3f2bas5ipocy6ibvq -deadgod.i2p,63bveyh7wefb44hlia7wtxxb3jal3r67thd6jekmwrtq4ulaaksa -debian-multimedia.i2p,cylxxz2y35x6cvyrl57wu3brckurtexatyi2i5awz3eeamqwjspq -decadence.i2p,pw5ys7k2grjb5myydpv6ohikm6nna7y6u2dro44i4rucgulu3ikq -deepwebradio.i2p,2nait2gdeozkgf6gyhzjfij6mwldwkxxwcvtxobb4b5q5cvtm5la -def2.i2p,cepsrw27kdegwo7ihzouwvgcvw2obswwjs23ollgj7hk2yrce3da -def3.i2p,xbf3ots2purqun7orn72ypkpjmrzbfrkj3u654zfe77hbrbow6la -def4.i2p,yyzdq4fwwmnlojp23drfpfqujln2vcjozjrfzfeuriuqzdq7g4mq -deploy.i2p,ujzspsqkbz5z272eozsrdv4ukl434h3fuliwrfxxnab74jmd7e6a -det.i2p,y6d4fs3rpqrctuv77ltfajf5m4tl4kzcu7rtwhxgiohylfxxow4q -detonate.i2p,nykapdsjjswdkjov7x3jzslhg4ig3cpkhmshxqzijuhbisx25jja -dev.i2p,cfscxpnm3w3qxnlv3oikewxm4qrot4u6dwp52ec2iuo6m7xb5mna -di.i2p,3irnooyt5spqiem66upksabez4f3yyrvvjwkmwyzlbealg64mgxa -diasporg.i2p,edvccoobtjukjgw2os5eetywanbb2mpag5aknkrpia5qx2koksua -diftracker.i2p,m4mer767ipj7mq6l7gdrmrq37yzvsj3kzezd7n7nsfuctntjseka -dm.i2p,heysbdivyeugdbggpscco5wje3dsvwgcpp5ot4sopooebnmiqvtq -docs.i2p,ato242wckzs4eaawlr5matzxudt6t5enw73e4p6r3wajwkxsm3za -docs.i2p2.i2p,las5l45ulwwf5i72nht6vk33sfkidcpr2okpf5b6mvgbk3a2ujna -downloads.legion.i2p,xpmxdpuuptlekyhs7mmdwkvry7h2jbvpqpzsijqe3a5ctxgodesq -dox.i2p,vk27cjdrtegfdnrjqutebgxkpyrfj42trdfbsupl5zn2kp34wb3a -dropbox.i2p,omax2s5n4mzvymidpuxp2yqknf23asvu54uon6cxl6gdrlblnuiq -duck.i2p,3u2mqm3mvcyc27yliky3xnr4khpgfd4eeadhwwjneaqhj25a65ua -dumpteam.i2p,2fwlpuouwxlk2nj4xklvm43m52tqyhqnu2fcfiuv7clvf3wd5nwa -dust.i2p,u6xgh6zhhhvdvefbqksfljfs3nyjvqcrmyamp5bryz5f4injmniq -dvdr-core.i2p,fg6l2ej6qrk5rkyfzdptxx5xkcm4kvdla4gg2tun7z7fm5cxxw5q -dyad.i2p,7n2ljphvp2dep7imoujvydxp4myuxfld3axwfgcny5xc5x6jj6ka -e-reading.i2p,z54dnry6rxtmzcg7e6y3qtsig5yf5fmehuvakcg5wnuahx3iafuq -easygpg2.i2p,bwxry5alzx5ihgrd3glah4eotddblzhalvpheppnw4zcajzqoora -eboochka.i2p,ou7g64d5in4sugv5fgmmzwnunuw5hloixio7puthmrvrkwrp6egq -ebooks.i2p,bvpy6xf6ivyws6mshhqmdmr36pruh2hvoceznzeag52mpu647nzq -echelon.i2p,afvtspvugtd32rsalxircjglh3fhcjzk7gxrm3gw4s2yrpvzk6wq -echo.baffled.i2p,bfr3lyicr72psxvt2umqfb562rtex66w6q3hi3tktzkoyane2iha -eco.i2p,2dq2o5h6c6a674qaduipp55mid5iktumjbswuwmpsrcqaeowdvwa -eddysblog.i2p,ieac3ub4g5sy3wuhsbqfembnpp7f3a37xgcx537ytzsmgfzexnbq -edge.i2p,aknsl5wmzjmwyc4wxutfdwy2w5vgd3vcx52mqx647hcgvyurmqta -eepdot.i2p,t6edyotbxmxvy56fofdvmragvsj65te2gkhvzv5qnblicutyvgoa -eepshare-project.i2p,sn26kom4qyuzouppv4lwnk6bqabdydcegtrilybviibwiq2s4nfq -eepsites.i2p,isskhl4ak3g7qevrarlmblddgr4ugnn3ckalwpjcvxafk5rjgypq -elf.i2p,duz6ey27ohpcp3llylklzdb63lylolzcixad6bh7rt5tkq42qqpa -elgoog.i2p,z6hrgkg2ajmuzlrddjlffrgctx7x7fkipm6c4hdzmohyn5wkr4ya -ems.i2p,734zw4jsegdf55zl3z6s22tqkbxcghu4qvk6q2wevjfmx7xhbn6q -epub-eepsite.i2p,yxvzjwd4vin6pnjauekdufh7lxaijal3kqe2bhakuf47g5zkb6xa -es.hiddenanswers.i2p,cw7ge5ey4ekp5iep2kaw6j54boebtqytpcbnvio2bfpccd5ejzfa -eschaton.i2p,xe75f5hzmrq6rkhsef2geslmi2v2yfngdiysmlmxvh7b4pyyjk4q -esuwiki.i2p,cwxuiwcpymb72vm5vluba66ofhugyf5qeevvwo7e2fqrxl243coa -evil.i2p,ljfl7cujtmxfffcydq77pgkqfxhgbikbc6qxjgkvcpn4wzd73a4a -evilchat.i2p,s5b7l3hzs3ea535vqc5qe2ufnutyxzd63ke5hdvnhz24ltp3pjla -evilgit.i2p,mx5vyoqhg77yuhthwznsxrepjsemq4uwitx4lxdzetk36ryl5rla -exch.i2p,vsyjsbbf2pyggtilpqwqnhgcc7mymjxblamarmxe5hmbxaxvcndq -exchange.gostcoin.i2p,n33uthzyqsbozl2qh5zii2bq2nnvbz6g6c4ew3mwp6uukk6u7wva -exchanged.i2p,ylmulgfskl6uiwac4hw4ecwqdzd3oxtwaemzj25zc6k5q4rkexra -exitpoint.i2p,5zmjurq3enudcenegnxu5hqmfmayz4lxvnik6ulch4xssa2ithta -exotrack.i2p,blbgywsjubw3d2zih2giokakhe3o2cko7jtte4risb3hohbcoyva -explorer.gostcoin.i2p,ktoacmumifddtqdw6ewns3szxths2hq2fat2o7xnwq4y3auga3za -fa.i2p,6n6p3aj6xqhevfojj36dixwbl4reopkhymxmatz7ai5sroh75rka -falafel.i2p,djpn5cbcgmpumwcriuzqistbae66txca2j4apjd2xesfgb7r5zmq -false.i2p,77mpz4z6s4eenjexleclqb36uxvqjtztqikjfqa4sovojh6gwwha -false2.i2p,j5i2tfumh3ti5sdtafwzzbpupmlcbg5drysfay2kxbdpsaljrosa -fantasy-worlds.i2p,62a4xcyyhvfrcq2bkckb7ia37fmrssrgx467tlkxp32fjpq577wq -fcp.entropy.i2p,de6h6ti5z3mcbdcwucu45vplikqyoeddsu3rqy7s2zy5i47j3peq -fcp.i2p,ndsznnipoeyapnsg3gj3yi2dzsqduxwalmujm5mzjm7e6x374tta -fedo.i2p,zoamh7e3k2vf2g6pfy46ho4taujk2f4mxqqsv3gbg554fxbvyfqq -feedspace.i2p,kvtnpx4jylgeyojfhix4x462sqn5uork3roml4sfzotkxx62i4wa -ferret.i2p,kkqie5qmja7bkf3iad4zxhrdarwj7kbrx2m3etn5kmba3shgwj4q -fido.r4sas.i2p,i522xmu63hfbaw2k54cthffcoqmeao6urjyq3jg4hddf6wf57p3q -fifi4all.i2p,v2stz6bsot7sbjzix5tky5dm5ej7gidmjnkvzqjju5xvz5sz6fwa -files.hypercubus.i2p,qfglq25jwieszgyt7muz6dambzqsrmjhhszygzzx2ttubc77sffa -files.i2p,w2sy74xe6oqnuz6sfh5fhkzu7boholgzd5f3anhj47srxwpj2vaa -files.nickster.i2p,yil7dp2hg5pbqyovsiwb2ig6zjsq4tize3fnwemmqdrr6j5itdtq -fillament.i2p,udj2kiino4cylstsj4edpz2jsls77e32jvffn2a4knjn4222s2oq -firerabbit.i2p,awqh7n3wskzl3epyvkdwgarmfybsncm7vye6psg4tpkmplh3mj2q -flibs.i2p,ocdm33e3h5tdml3yyholj4objdwsrhlugfqjnqgdkslmgdzb6b3a -flibusta.i2p,zmw2cyw2vj7f6obx3msmdvdepdhnw2ctc4okza2zjxlukkdfckhq -flipkick.i2p,aso5rzc4ym6g2bcbxjy2n573bmbenkjawva2jg7fhyqhwtwgu6lq -flock.i2p,hflpi33ko5bi2655lx6bpzstdnjqgzrz23inovqjx5zpntyzyb3q -floureszination.i2p,vitpvfb25sikuk3crgcvtcdi7hajxnnq2t6weay3no7ulur2wwwq -forum.fr.i2p,onvelkowkbuwrglhw2cnocggvbdudi75sll5mfirde3cbopjqivq -forum.i2p,33pebl3dijgihcdxxuxm27m3m4rgldi5didiqmjqjtg4q6fla6ya -forum.rus.i2p,zd37rfivydhkiyvau27qxwzmerlzbqtthsa5ohtcww62zrygjaga -forums.i2p,tmlxlzag7lmkgwf6g2msygby3qttxvm6ixlfkq6s6cpgwubp33ya -fproxy.i2p,keknios3gm6kh6onez6x2bm2t7stv54oanvltuagphgdfjdw5e2a -fproxy.tino.i2p,fpaituvuvyxp6xdjnv3i27alnj2ifzcvqdweqb6yj5uybotzvyha -fproxy2.i2p,r4lgw4wmza25g7j5fjocjbwzwthfg4ymcbm52ref3hh2hogskcza -fr.i2p,ia6xlsnygorllplx2owokahtrkospukvsmysz7i7bzw3vejc4hdq -freeciv.nightblade.i2p,rluupsgxbvw5t7jno3apyzlrdirjkljft4gdoy4mxxh4fmd4xzta -freedomarchives.i2p,4ck6oliqfjz3sccpya2q4rh5xkj5xdxkqs76ieml37537nfhwd2q -freedomforum.i2p,abzmusjcm3p3llj4z7b5kkkexpsxcnsylikokouk5txfim3evqua -freefallheavens.i2p,giqnkltyugfmsb4ot5ywpvf3ievuswfurk6bjie4hxi2hh2axajq -freenet.eco.i2p,2kf7ovb35ztqkrurkm76y34jfpwi6go25xj7peznnmxrl7aieo7a -freshcoffee.i2p,sscuukigp6alcb3ylhkcugoejjfw5jqgtqbsbafw4hyku42lgc3q -frooze.i2p,m6ofa5dmyse4b4jg7kfmluuuc4pw5jqu6zh4qnboin4vropxepja -frosk.i2p,63naq7zb3hvbcppj2ng7qwf6ztusp4kwpyrzbt4ptafcdbu4pfjq -frostmirror.i2p,ycz3imuz6yte2zhlapmsm3bsvc46senvc2jxzwsbfdct5c72qulq -fs.i2p,ah4r4vzunzfa67atljlbrdgtg3zak5esh7ablpm6xno6fhqij35q -fsoc.i2p,vaqc4jm2trq7lx2kkglve7rkzxhhaptcwwl32uicx4ehf5k3hx6q -galen.i2p,4weo7zkxscxbcouiqx4mlnb35uwl2lromikzk33er3fljktyvi2q -gaming.i2p,rfxberwod6st2zc6gblqswxjl57nucgc3xrbwss43pe3dvqqzj4q -garden.i2p,qkk2dqx6nocycgt3vinsoc76cxkb4jreybcpgz3fcps2dbe4rowq -gaytorrents.i2p,fnggbr2t2aulr6rvlo4aehotx6wecfob7u3k2nxsnvtm4xex424q -general.i2p,5fklrsztdqpl3hkkwwrrw2rdowrq7wwhwb6h7avvk4fhansp4vvq -gernika.i2p,wpzqv3lxpecdsvcaadvbmrhhwlc7kp4n2mijdv2qjw3zr3ye232a -ginnegappen.i2p,kbhfkzx5jeqhfgss4xixnf4cb3jpuo432l3hxc32feelcmnr3yja -git-ssh.crypthost.i2p,llcp7jvz3hgtt3yzkdgjolwobisgvhv4xqa5a4oddejllyozur5a -git.crypthost.i2p,7frihhdcisdcyrzdbax6jzvx5gvtgwsm7m6kcem2tlaw4jtahbqa -git.psi.i2p,em763732l4b7b7zhaolctpt6wewwr7zw3nsxfchr6qmceizzmgpa -git.repo.i2p,vsd2vtgtuua2vwqsal2mpmxm2b2cpn3qzmqjoeumrrw2p4aot7uq -git.volatile.i2p,gwqdodo2stgwgwusekxpkh3hbtph5jjc3kovmov2e2fbfdxg3woq -glog.i2p,ciaqmqmd2wnws3hcpyboqymauyz4dbwmkb3gm2eckklgvdca4rgq -gloinsblog.i2p,zqazjq6ttjtbf2psrtmmjthjeuxaubi742ujrk2eptcsaoam4k7a -go.i2p,ll6q4lsirhwkln4dqxwqkh2xu4mu3jiy546b4uhe4fypyb4vvx2q -gonzo2000.i2p,nogsv7okydhbvrewv6hb4xdojncvhkusnyib4lglluc4uw67a37a -google.i2p,4p3ajq4cotnflmuv7fhef3ptop5qpm3uzzgp5bahxif3nc4w3ffq -gostcoin.i2p,4gzcllfxktrqzv3uys5k4vgkzbth4gqednwhfpt755yivm3davuq -gott.i2p,dqows7dpftxxl2bd4bgcpkck6knrysdun6mtqy4ms5dxobbvg3ja -greenflog.i2p,zny5ftmhzxulxzyczmeat53qjnue2xtqv2clisc7dg76lwfceecq -gstbtc.i2p,n33uthzyqsbozl2qh5zii2bq2nnvbz6g6c4ew3mwp6uukk6u7wva -gusion.i2p,4qyfdhizjixe2psu7wcvqufix5wlijocehpb2futurcmlhlktrta -guttersnipe.i2p,kizkhzes2bzp45widihremo6geepfk7dl6juourkvzuvlc6y3spq -hack8.i2p,un63fgjgi3auvi7zscznwqfol7ka4johgthvqf635mg3fefsjgpq -hagen.i2p,e2t6rqd2ysbvs53t5nnaf7drllkgk6kfriq3lfuz6mip6xfg644q -heisenberg.i2p,jz4quyw7zt63tmw65jfp76fblwadjss4iyi4puqdg3dye7oaqlvq -heligoland.i2p,gzrjm62ektpqjfsem3r3kwvg6zpjvvhvpjvwfxkm2ay4zu7sp6oq -hidden.i2p,iqodhhqo473qv5gwhjcs2bsrbhlqtpzgpnuumpastfiyhuwb2kyq -hiddenanswers.i2p,kj2kbzt27naifij4ki6bklsa2qfewxnkzbkgvximr4ecm7y4ojdq -hiddenbooru.i2p,zma5du344hy2ip5xcu6xmt4c7dgibnlv5jm4c2fre5nxv44sln3q -hiddenchan.i2p,6y4tltjdgqwfdcz6tqwc7dxhhuradop2vejatisu64nwjzh5tuwa -hiddengate.i2p,rvblcu54jvkkfffp3fobhunsvpgfc6546crcgzielzwe2s5m5hbq -home.duck.i2p,jsh7yfvm2t5urdcnmfzdy4n6vegqskdtlwem53chgxli4ipfmuma -hopekiller.i2p,kcaelbgsvrkiwpx36b4wxofebrl3njx7rgm5amzfmqwbomt44cxa -hotline.i2p,6cczi27iuxkm3aivazaemzltdqgh42ljzurqp43uclbz2lid2uqq -hq.postman.i2p,27ivgyi2xhbwjyqmnx3ufjvc2slg6mv7767hxct74cfwzksjemaq -http.entropy.i2p,ytu7kz5bdoc26nkpw2hajwt3q7n5rcbg2eokyefhmkxmmslimbdq -human.i2p,nrtcelq3humyfvoxmzmngpka6tmyifweouku5mbi5av4lc43hzaa -i2host.i2p,awdf3nnmxxup5q2i6dobhozgcbir7fxpccejwruqcde2ptld443q -i2jump.i2p,633kqgmwzzu6vhkevwvbf2pfyejt3gkes34i6upa4og57fgdfcxa -i2p-bt.postman.i2p,jeudwnx7mekjcowpqo6xpkwn7263c57y5piurrjrdzinjziu4fla -i2p-epub-eepsite.i2p,yxvzjwd4vin6pnjauekdufh7lxaijal3kqe2bhakuf47g5zkb6xa -i2p-javadocs.i2p,icgmr6hhjudl4yxhtuq4pxvss2pzypwddzowajgs5rdz6f55novq -i2p-projekt.i2p,udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna -i2pbote.i2p,tjgidoycrw6s3guetge3kvrvynppqjmvqsosmtbmgqasa6vmsf6a -i2pbuggenie.i2p,bioq5jbcnfopqwvk7qssaxcl7avzeta6mu72jmxjeowflpcrhf6q -i2pchan.i2p,tduxyvfs7fzi26znvph3mu2d2ewaess7emomfci22wvownajphuq -i2pd.i2p,4bpcp4fmvyr46vb4kqjvtxlst6puz4r3dld24umooiy5mesxzspa -i2pdocs.str4d.i2p,yfvbtrhjac3jutdsqzugog6mbz3jtyhpwovrt2mqc5mzv534y7cq -i2peek-a-boo.i2p,qgv64klyy4tgk4ranaznet5sjgi7ccsrawtjx3j5tvekvvfl67aa -i2pforum.i2p,tmipbl5d7ctnz3cib4yd2yivlrssrtpmuuzyqdpqkelzmnqllhda -i2pjump.i2p,2mwcgdjvfvd3xwumzqzqntual3l57h3zo7lwdmkjboeraudpkyka -i2plugins.i2p,bb63kmnmbpitsdu45ez54kmogvvljn3yudksurcxiyq7dn5abt7a -i2pmetrics.i2p,v65p4czypwxrn35zlrfkar2w77vr42acd7gbszegsrqq4u7sip5a -i2pnews.i2p,tc73n4kivdroccekirco7rhgxdg5f3cjvbaapabupeyzrqwv5guq -i2podisy.i2p,3c2jzypzjpxuq2ncr3wn3swn5d4isxlulqgccb6oq5f6zylcrvcq -i2push.i2p,mabdiml4busx53hjh4el5wlyn4go5mgji2dxsfyelagi4v5mzjxq -i2pwiki.i2p,nrbnshsndzb6homcipymkkngngw4s6twediqottzqdfyvrvjw3pq -iamevil.i2p,au7jhslyt4cxkjp365bvqvend3hhykrrhbohtjqlgoqrlijbezja -icu812.i2p,bxgqwfsnr3bgnr6adn62anjcin5nuthqglotb3wn3dgynsfofeva -id3nt.i2p,ufuqdzsxltiz224vq5gnuslt3a3t72dhy5kq6i2xway53m6pzv6q -identiguy.i2p,3mzmrus2oron5fxptw7hw2puho3bnqmw2hqy7nw64dsrrjwdilva -ilcosmista.i2p,6u2rfuq3cyeb7ytjzjxgbfa73ipzpzen5wx3tihyast2f2oeo24q -ilita.i2p,isxls447iuumsb35pq5r3di6xrxr2igugvshqwhi5hj5gvhwvqba -illuminati.i2p,syi6jakreatlm2z22u76izyqvbm4yi4yj7hr7jb63lgru5yhwwla -imhotep.i2p,qegmmhy52bdes2wqot4kfyqyg7xnxm5jzbafdb42rfoafadj2q7a -in.i2p,r5vbv2akbp6txy5amkftia757klgdy44s6cglqhmstpg65xycyjq -infosecurity.i2p,v3gkh5kqzawn2l3uzhw6xnszsh6w3nztjmlwil7p4kyrwrsm2dba -infoserver.i2p,jd3agbakybnhfvkeoxrx7t33iln6suzomv3kxkxf77j7rkonch6q -inproxy.tino.i2p,ex5yf6eqqmjkrzxnkn6cgvefgne24qxsskqnpmarmajoit43pgma -inr.i2p,joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq -instantexchange.i2p,5wiyndm44bysev22kxvczxt37p6o6qroiqykytrvn2yzi55aqfxq -investigaciones.i2p,n7hqd4asxrdwf3zwo7rzv27y2qkcfmakmz6mjar6aw6hlc4c7mha -invisible-internet.i2p,jnpykdpp46zenz4p64eb3opadl5g42dls3rurk2cvq6a3g3rvbvq -io.i2p,tx22i6crnorzuti3x6va4mijsbhoqswy2cfdxjbvprgsq4eerg7q -irc.00.i2p,bvcja52pppgfspp2ueuipoysjnvvoyblz2h6smpxcmanjquogirq -irc.arcturus.i2p,5nywlbn35p2nwsymwpfmicu6fxono6g64vwusxbsvmm2qwz6vupq -irc.baffled.i2p,5zmtoopscym6qagkvpgyn7jnkp6dwnfai745xevkxlou77c2fsjq -irc.carambar.i2p,hxzbpivxqxy6nuae4t6fnkhcgnhs4c72vt6mmsqfmfhrkn2ca6gq -irc.cerapadus.i2p,e4ckznxcxvgyikzjmjsu72i2dbj2d76ogexyukklbjvpcnhp6zzq -irc.dg.i2p,fvp3pkcw4uvijqabwtekcdilklp73gyasuek67wdcs2mucep4caq -irc.duck.i2p,chdpmm4gxffyn24xx5dhxvfd5httu42i5gtoe6cctjlsf4mbofeq -irc.echelon.i2p,ez2czsvej5p3z5bquue5q3thujcodfze7ptybctqhnqc7hms5uzq -irc.freshcoffee.i2p,ubiu2ehtfnrleemgpzsqkahwnvzuaifqa3u4wmaz5maaisd5ycfa -irc.i2p,l3ohmm4ccxvyuxuajeaddiptci5lsrnxtvtyq7iohphrt3oj2evq -irc.ilita.i2p,5xeoyfvtddmo5k3kxzv7b3d5risil6333ntqrr3yvx3yubz5tk3a -irc.ircbnc.i2p,4rqcsqd7xif6r4v55blqvmqu5er6due4eyene3mjorfkts4o3rxa -irc.killyourtv.i2p,wre4majmg2vnbi6id27et7yw6lnpf56wkbm6ftnlwpvxnktq73hq -irc.nickster.i2p,dhq3fhd5scw3jqhj5ge7kqfpprfolcgxfjbaw24obohaiqjtdu7a -irc.orz.i2p,7gifacog4aoons3syybojbbnyqqaaqijhngrehn2xlq3eucuyjcq -irc.postman.i2p,mpvr7qmek2yz2ekegp5rur573z7e77vp3xqt2lfbco5i6nkfppcq -irc.r4sas.i2p,hodhusp73gltozgrnianlbploon3rrvhrzfn5mf2g46o7aaau5la -ircssl.cerapadus.i2p,4x2i745i4w52ss3he2kse6tzwt64pr62yvrcb72lgvrb63fup6ea -irongeeks.i2p,ecduxoion5uc5hnvzjxff6iiwhdwph6gse3dknyvlo7e6gaeho7a -iscofsi.i2p,enjgdxs4um2dmhdb2ajff2egrdijkjji3g47m6unb74swbrqsddq -isotoxin.i2p,wue3ycaccf4texikza3fh6p5yrmtgnooisuypnepo5mo67lmpcqq -itemname.i2p,o35ut7hgywy35okvgkjkv3ufzv2ejv4luap4oytwbyy2jqy6u4vq -ivorytower.i2p,fpwrfvidfexsz7dspofkwtkmmizm7lyralfz5kvykffk7gubvxsq -j.i2p,kjxvohlsf5sdrzxzfcrmvquccnoevi6ytbl63mstsru5wt2dx3ea -jabber-2.i2p,pvnmzgemetkwcuvt45omgowmeznwk5xw3nc3ygeoz7yekqxy57na -jabber.duck.i2p,rhdzvvzraqzzm67zpyegb7knpfrjeffitixqzeyymdoz56uh2rtq -jake.i2p,v2axvy6pqefnla7gun5fmqs4lqe4xfyqovgzcundhxrpcdvfd7cq -jar.i2p,2fthkmujup3xiiu3yple24n6g4emzdiiimbuqwvpdddtsr3c4nrq -jazzy.i2p,ha5c3zafwkt6mwqwjcf4oqwvbwz473652ljjadiwrj4gfkfkjofa -jdot.i2p,kw4jr5qw4bhnj33avkwankjdh3zi7wtahlmgkjwvsv2isskkzgpq -jhor.i2p,c6rnm7oemydhuwzmhwwwxphkzanez5rnn7fkcs3lpgu6gkgtssoa -jikx.i2p,aazr55itvyns4lwppvx5njyx5tjdwemw4w6jbmpegdunznod2ieq -jisko.i2p,jxgfvr663uhr6m65hrgkscshysfshkq32ywdubc4ed7zda3e2pca -jmg.i2p,oglpnq7zungdukmk6gk5fzj5jp6wibuoihqgks453wztrwos4ggq -jnymo.i2p,nbfplxgykyfutyadlfko2rmizdsxox2pee2ahboj5mju4s3putda -jrandom.dev.i2p,htynimemonyzqmn76gworxyfkmqtsa7zcprbrd3i5cxqqm75tuzq -jrandom.i2p,dqows7dpftxxl2bd4bgcpkck6knrysdun6mtqy4ms5dxobbvg3ja -jrrecology.i2p,qxi24gpbum3w3kesuxvheyu3p5u5o6tuvoypaolub2gnvbld57xq -jwebcache.i2p,xdffxnxtjd6ji2zig3cgva7igvl2tiapyjoc7ylbzwqhxudbmvfa -k1773r.i2p,zam7u6vslhemddz347uusuzjdk5wma4h5hcmcqlng4ybbpdbjhnq -kaji.i2p,z5ic7gvm2k4doczphtrnrspl2w5sfbss2de4z3ihjijhtjw67ydq -kaji2.i2p,4lscgc6napekfx7ay5fdcjofeja4fnl7tqcd3fek63t4saavur2a -keys.echelon.i2p,mwfpkdmjur5ytq4og36ym3ychinv36b2a57f4rmgqmtrwepq3fva -keys.i2p,6qv4x7ltaxckd4vbay5s4ntqqflq4efk6oke2d5yzicqrmk443ba -keyserver.sigterm.i2p,isoxvnflrdn7cm76yjlfg5tbcugoito2hur7eidbqmo33xmwz5ga -killyourtv.i2p,aululz24ugumppq56jsaw3d7mkbmcgo7dl2lgeanvpniyk2cbrda -knijka.i2p,knjkodsakcxihwk5w5new76hibywia5zqcgoqgjttzsausnd22oa -knotwork.i2p,2yocdbcjiyfaqgxb4l6oenrrrrie6nydgmbnbfulqg7cik6bozxq -kohaar.i2p,qchpjehbhqjbxdo7w3m55jbkrtsneb7oqoxcr24qttiq6j5g3z5q -krabs.i2p,3yamyk5bgfgovg6zpvtvpdjk37ivjj2wog2w7wha5agzgxxkqaca -kuroneko.i2p,wbit2huhhwlyqp2j4undccuyrodh6qcmzdeyuaoy5o4ym7g5gdgq -kycklingar.i2p,gctswdhp4447yibxfbqg3uq2bvx63qjeqnaoaux75zw73leakyva -lazyguy.i2p,ia6xlsnygorllplx2owokahtrkospukvsmysz7i7bzw3vejc4hdq -legion.i2p,5oirascyhwfy2tr2horw6mixozsre7z6s7jfq7qbnj523q3bkebq -legwork.i2p,cuss2sgthm5wfipnnztrjdvtaczb22hnmr2ohnaqqqz3jf6ubf3a -lenta.i2p,nevfjzoo3eeef3lbj2nqsuwj5qh3veiztiw6gzeu2eokcowns3ra -libertor.i2p,7gajvk4dnnob6wlkoo2zcws7nor3gunvoi7ofalcps5lc76wruuq -library.i2p,brqqaq44vbeagesj5o3sxcnkc5yivkwouafyxa77ciu7l644ei2a -lifebox.i2p,pyqjnycm55cuxow22voqj62qysrjdnb6nbyladaiaiirqi7vp2yq -linuxagent.i2p,ap5riaikrjq2uv5qvy7klzhhqywvqi7wqscyipsewcun7w2eynlq -lists.i2p2.i2p,vmfwbic2brek2ez223j6fc6bl5mmouzqvbsch45msvyyzih3iqua -lm.i2p,yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q -lodikon.i2p,u3f67staiwhqxpacya3clmvurdwd2kp7qcthzhstqnhrmlwc2g4a -lolicatgirls.i2p,a4lzmjyba7aq7hl6okqpds7znnwymolqnr7xhvno2wraqb7uhfla -lolifox.i2p,7fd2clkiotjnaoeigdtxlkkb24eik675ovezjf67x26ysham4zca -longhorn.i2p,pohcihzxzttjclrazhs3p76wt3ih737egb5bovqb6ym3du6z3o7a -lp.i2p,jiklbujn3cbfikf4pca526jgmorx6mxhil3twqmfoteaplx6ddwq -lucky.i2p,wx36m3wnpt2y6bngdpg3ifrarvtkpwnluarx377bllpgvkuhybaa -luckypunk.i2p,y4t6cujjxnnrtln3rgmfbgbh46hic7wkef57krd7opitbgngohka -lunokhod.i2p,3yc6sp7xic4grmpfecbwuij6z3dp5kdgoo362pszaco7io42mnwa -m16.i2p,ucsr3eveuc4mx5y6gxnoaywd4ojvbel5q3ynns6s5yfw3vusmfva -mac7.i2p,3yjowssqzydciwa5zx55kazgf7q7w6g7rkocr7bngy35ii44t34q -madman2003.i2p,a2sam2xbhxbzmeyobphbxrkdwlppoerewq5qvibbyk3ftsr643qq -magix.i2p,cgfnyxv62msfynsfbv3kju22j2mt6tfnopshhmrcmpcrxyts6xwq -magnets.i2p,snz46nez6hrrpg6336neinflw56l3vwatk6bzzytwu77xmsfsoca -manas.i2p,6qolj62ikkoq6wdn3hbvcbdmlvf2rcyv432kgi5uy7mvrczmjtba -manveru.i2p,pbmbofs76wpjnxi55eqtwg4y6ltyij72o4fm4sxfjol3y57ze5sq -marcos.i2p,vpo36bsil2voqaou53zshuegssqaroa5mbrzxfmhjywlbojckalq -marshmallow.i2p,svdqd6j3y3gwryufcl4fkzpmcujgvrvphvk2oy4r7m75xs327e2q -marxists.i2p,lepah55qyp2fhuwxlz7bwrhzckn4gkuofivnofoeuyfpmke5x2hq -mattermost.i2p,x5oovnhnuli5fnwtgkbd5z5jvrvdvprqyuofywx6uoxkk4bie6ya -me.i2p,dbpegthe42sx2yendpesxgispuohjixm4bds7ts5gjxzni5nu6na -meeh.i2p,4oes3rlgrpbkmzv4lqcfili23h3cvpwslqcfjlk6vvguxyggspwa -mesh.firerabbit.i2p,3x5wokr4bjy5z3ynji4fyhvwzv4fvgry3xafi5df5h75doezjytq -messageinabottle.i2p,avfhe3kvrrv7utxn2vre65lg7damxzzsewq3vukwie4llitd254a -metrics.i2p,z45ieamhex2ihqv7oowk5fz4qq47rbvxhhhbaaiinpajbhuevtpq -mhatta.i2p,o4rsxdeepfrnncsnjq675xogp5v5qkbfgbt6ooqeyfvlifobrjxq -microbleu.i2p,mtapervgibruizniems2yyr47pin2wpysyh7m632rigl26vjc6qa -microsoft.i2p,hvaqr5idszdyrjph34amb4mjosqd3ynggoxlnj7ciqhnx7q6plza -mindisl0st.i2p,u7rnqhvsuyxd3fabm4kyzn7brgz3i3cporj2emk2jmbpcmltyf7a -mindspore.i2p,uuh5dd3y2rqa7x2jpggm4p2pg6znarm5uanwsvybe4tk36ymwr4q -modulus.i2p,ctz3o6hdefrzwt3hlg6rjhdcbjk6irppbndq32u6jnn4lz72f62a -monerotools.i2p,5bal7dngxde2ddmhuzbtfken6w5nmxmixtjlrlmxt3wbhnemv73q -monerujo.i2p,puri6y5dtwh6zr4u77ep6ozatun6iz7v4wai2dzxppz7654corlq -morph.i2p,iovyp2dao5rta6g5v6hke2s4ugx2btkpcljddak2yhxfrx3l4dqa -mosbot.i2p,5bhmrp43mjwlzf4x64xgdrkwmw4luvng6eq5waa663a7vnkp732a -mosfet.i2p,s5ynkgagndmpxpf2kmnenv4x72io664gzd2x3qef54ilammnte3q -moxonom.i2p,gcjdrvnlobgexh7ebv276pwmnoj3yoyaqm3w4vmmdha4lgxfinqq -mp3.aum.i2p,n7bmu5dwux7f6gedmdik6zrm77bnls4lkzo2vo3bf4bwegk7vkjq -mp3.tc.i2p,w3ied5s7ldjcvnhxu2gyofe3oogzbplkyxshzfkhspiy2526snsa -mpaa.i2p,m6cqnglo7xlytwxkdsmwf3d23d6lq5r446c3tktb2tdmuah36zya -mrbamboo.i2p,tmpmkx6wlbbrgsnexrqlrib7laoegpbfeop7bnyezegii7hecpxa -mrflibble.i2p,u7k2qcmkrril6yvudvwxjqz7k3dzgp3jdejjjeapej7liselj3eq -mrplod.i2p,fjn5hxtybxyfyvdf6u5v5seg2sjd47hb5by6sa6ais4w3xnrxwyq -mtn.i2p,xisk3h6sku3iqj52uriogaajmnku7pwjux7wa4omx2zloamuw6eq -mtn.i2p-projekt.i2p,f52x5fp6uhq53f5zle5d6rq5un34xgmxgazvilvmzcby37xcmsfa -mtn.i2p2.i2p,l6kuhtmgvbp57d7jwalj5nksi6nr4gfzbz4oit62lxgipb3llt5a -mtn.meeh.i2p,h7ylrsuzzynrxp3jql7anoozyqblavj7eqces6o3wngvuuxhs2la -mudgaard.i2p,yz32lk42gtoesknesfolq3tt4erxxcejcote5pontaeqev3bj2kq -mush.zeit.i2p,dk3sg23kljawxqp3cb6xz5mnzjlyckzvq5jhqs5gnvdsv7wqn6ha -music.i2p,akamh76yi6p7xxbvl3qv3yhaockne57yfuh77acogbgpjmwypvia -mysterious.i2p,p66g2a4nzfkvidd3l7nwphcnfa3ttyu5kiolcb4czec2rn2kvwsq -mywastedlife.i2p,ceumy3puvvsrru5bmfmtgsajsx5qyehqac7l7a23xpwtfs2bvcgq -nacl.i2p,bm2fib3tumer72lopjh4nmqomwvqu2sdfyb2hmr6lnk7jbw3vvia -nano.i2p,ex5ssv7s3hj6jp7hvadxfw3wvbjbvnczxr4pbk7qw26ihiorjmba -nassai.i2p,v653cocvn3i6bgjdm3ciwbdnu32supglv6gn4fh23bohemsp545q -neodome.i2p,5hkhjehj3ct2pvcah7dcylwef2oti3xij5myxbv3pd7rocio5vkq -news-i2pn.i2p,wwcqkwfo5yhe6uribv5tzylk25j5hkdk6gdnyftzd3k7dawlzwca -news.neodome.i2p,trhwcnygfkeqjj6g4xhmrdp4gsjqsye47lsxshbmwbten4ywt5oq -news.underscore.i2p,rl7t3kspoktuatjcu7gf7xleu7y6biibs4fspzo24kll6n7hbq4q -newsbyte.i2p,gsk3rgsejxxrfabjxu5w5plplxsu47aoeoke22vvhlwwllzosnxq -nibble.i2p,jmdxcpdzqafedn3clc4y7u6o56qocfiffrzbzncmtggqtio5qjpa -nic.i2p,vzu5ymab6klevpcdudv4ypisjqaznmt44e6lcg7dwiuza4saibxq -nickster.i2p,zkwsa6kvq2wdhovw5g5wqakpb7rlaylyhfriwmurots5pvwbqauq -nickster2.i2p,eofzi7npzpk4p5gb4qper4hmwgxo6kepo3dheeblakewedxj2bwq -nickyb.i2p,gmpxk4tje7mnud32kg2kjmf36f6cpwqakzc2dxuzjnnz4qr5w4sa -nightblade.i2p,p4gkon7ytswxrbwkl7vruw6mg7kfw5aofovqjgt4c7tnqmbq6lha -ninja.i2p,q6dg6hlb3egzdqz352ri5rc4fx4gcrdeu3tpiyfxlv73yfjgrhya -nm.i2p,3itdpqzyn3ii7sivppo4sxxwhvgtpskzkbokrdibim6gqpvlw5ya -nntp.baffled.i2p,kc6muo2tih5mttbpzecteegvtonuysjidk3emcy4cm4yifzild2a -nntp.duck.i2p,gvzzor4utsqxswvf6jaglfks7yxudlz2s326ftrk56i4lpd2s47q -nntp.fr.i2p,npoztnqadfnu4vrokoh6rusoi3yne47s6jurc3lzhcrzzia5eqva -nntp.i2p,wwdzmeyler4djegvyt2bxmkwrckfgg3epkkwowyb75s47he6df6q -no.i2p,lpsg4x4gdrf7antxcdy47cl6abcqei5ommgzt55retq7go5ku3ba -noname56.i2p,oiyoslismzyxuw7ehxoigmtkdj35idim6flmlplddxuiiif6msfa -nop.i2p,ssag45lathm4gqp46si7c4w4tioyvjpcza5uvz5x2zuljnplylca -normal.i2p,j5fex634r2altzb3kjvu35qekt2r3hgsqzg5qxoy7dp53heu5pma -normanabcd.i2p,si2vh43gvxjnw2shwr24j76xyanow4oa6gbu4idookbraoxl3s3a -nothingburger.i2p,tesfpn757ysc7nih7mxher2b3jstkc3l5fhfcyb5kxhzhvv52trq -nothingspecial.i2p,wzrwqrp52bilqijrlboclynuev4kzpjzfzlvzl5aqxqt5fdnpbga -novospice.i2p,ukqap24nwac4gns77s4zy7j5cagt7l7syb5zo7eukfg3zn5gg5qq -nsa.i2p,nsetvbclpomqxfcit4mghn6z7vdhnza6jdzczby4crnto32uykga -nvspc.i2p,anlncoi2fzbsadbujidqmtji7hshfw3nrkqvbgdleepbxx3d5xra -nxt-wallet.i2p,33pp74k4ivy67z332qpyl3qlcqmi6gxqumrow4bldkblxxlxqq5a -obmen.i2p,vodkv54jaetjw7q2t2iethc4cbi4gjdrmw2ovfmr43mcybt7ekxa -obscuratus.i2p,i4j37hcmfssokfb6w3npup77v6v4awdxzxa65ranu34urjs4cota -ogg.aum.i2p,wchgsx6d6p3czloeqvna2db5jr7odw4v4kqrn4gr4qiipfyrbh5q -ogg.baffled.i2p,tfbvj2xal6lcuxv3hzuw7cw4g3whguombcv2zuotzvul4qtrimgq -ol.i2p,bnb46culzbssz6aipcjkuytanflz6dtndyhmlaxn3pfiv6zqrohq -onboard.i2p,qwlgxrmv62mhdu6bgkh4ufnxowxsatfb6tbs2zr666qyunwqnecq -onelon.i2p,irkvgdnlc6tidoqomre4qr7q4w4qcjfyvbovatgyolk6d4uvcyha -onhere.i2p,vwjowg5exhxxsmt4uhjeumuecf5tvticndq2qilfnhzrdumcnuva -oniichan.i2p,nnkikjorplul4dlytwfovkne66lwo7ln26xzuq33isvixw3wu3yq -onionforum.i2p,yadam2bp6hccgy7uvcigf5cabknovj5hrplcqxnufcu4ey33pu5q -ooo.i2p,iqp5wt326fyai5jajsa3vkkk5uk56ofn4anocgpe5iwlpisq6l7a -opal.i2p,li5kue3hfaqhhvaoxiw2ollhhkw765myhwcijgock5rs4erdqdaa -open4you.i2p,ice6ax5qrzwfwzsy64bctffj6zlzpuzdr5np65zsxlbt7hztyc6a -opendiftracker.i2p,bikpeyxci4zuyy36eau5ycw665dplun4yxamn7vmsastejdqtfoq -openforums.i2p,lho7cvuuzddql24utu7x6mzfsdmxqq7virxp5bcqsxzry2vmwj5q -opentracker.dg2.i2p,w7tpbzncbcocrqtwwm3nezhnnsw4ozadvi2hmvzdhrqzfxfum7wa -orion.i2p,5vntdqqckjex274sma3uqckwqep2czxs5zew25zlntwoofxk3sga -orz.i2p,oxomqkekybmyk6befjlouesit5mhstonzvzd2xnvsk7i6uyrqsfq -os3.i2p,s7x4ww5osrrfein3xgwyq67wnk6lgliw4mzt7shtu66wrb2zdojq -osiristomb.i2p,t3slf77axkv3qm7c3gzpv3jgmkraoqqe2bojr6h66eipibofsyzq -ot.knotwork.i2p,cxhvvfkbp2qbv5qojph7zb46molpe2ffanghnerjag3xdmy6ltxq -outproxy-tor.meeh.i2p,77igjr2pbg73ox5ngqy5ohzvrnur3ezqcogtl4vpuqtrcl3irsqq -outproxy.h2ik.i2p,nwgvfpfarpnyjjl4pwsxr2zdsppcx5we3kos2vlwicbiukopgaza -outproxyng.h2ik.i2p,v32zse2zczzgegelwxbx7n5i2lm2xhh2avltg76h6fz5tb53sfxq -overchan.oniichan.i2p,g7c54d4b7yva4ktpbaabqeu2yx6axalh4gevb44afpbwm23xuuya -p4bl0.i2p,lkgdfm4w6e2kkjhcdzr4ahhz26s3aunhrn6t2or436o73qh4z7ga -pants.i2p,xez3clscjfafkqwk6f473ccp3yvac4kh6rdp6dptwxa2lhixizgq -papel.i2p,mxskjqntn2d34q4ovsnd5mud7cgde734tdjldd3lt4hczh2645zq -pasta-nojs.i2p,dkkl3ab6iovxfqnp44wsjgqaabznvu7u3hugpzyagbeqlxgvx3la -paste.crypthost.i2p,2zaj4u4s4l3lgas2h5p6c6pvzr2dckylkrh5ngabursj4oh25ozq -paste.i2p2.i2p,b2gizskfea4sjxlw6ru2tb6kdrj47dsjc77cijsf5mzh4ogbmfvq -paste.r4sas.i2p,csen43keji3qiw6uobsgzysxyjd225g6446ylq5uuz6ur2glkzaa -pastebin.i2p,mnicncxrg2qqi55qftigiitaheugnj4rpysbk7zabdrirgktelqa -pastethis.i2p,erkqiwnjl7vtysqd3wvddv6tfvnhswarqkbn4blhdlhfxn7cf2ha -pdforge.i2p,wzeg3ehf6d2mqjqji3sd3rns776thvhe2vam2r6gjlmsqis2dctq -perv.i2p,f3k3wm4ae7t7ottfjd4hu6is7zsls73izl2gm2qynzficxcdsiwq -pgp.duck.i2p,wujajyxj3cgsfsbtr3g7g7npv5ft3de6pcstxlav26zq6cxdjmha -pharos.i2p,vathk2pyvaskeie63yyg4tshjkx5xt6zfvhwhgr3de67q46ob3sa -pharoz.i2p,vathk2pyvaskeie63yyg4tshjkx5xt6zfvhwhgr3de67q46ob3sa -phonebooth.i2p,noxia7rv6uvamoy2fkcgyj4ssjpdt4io6lzgx6jl6wujpufxedrq -photo.i2p,fqhuy77ugd5htnubzkyy5guvwboqn6goahtmn2g7feewvdj7k3iq -piespy.i2p,vzusfjzcu5ntnvobcvyzc4dcu4j6ommtnpmba2puk3kexgdzrl7a -pisekot.i2p,7yzdwhy723fodqz4onp6k3nyvixra2sa6dl45tcblhmyoa7i36nq -pizdabol.i2p,5vik2232yfwyltuwzq7ht2yocla46q76ioacin2bfofgy63hz6wa -planet.i2p,y45f23mb2apgywmftrjmfg35oynzfwjed7rxs2mh76pbdeh4fatq -plugins.i2p,wwgtflbaa7od2fxbw4u7q7uugmdclxf56alddvizugwcz5edjgia -polecat.i2p,het5jrdn35nhkanxmom5mjyggyvmn2wdj2agyqlrv4mhzhtmavwq -politguy.i2p,6dkkh3wnlwlr6k7wnlp4dbtf7pebjrph5afra2vqgfjnbihdglkq -pomoyka.i2p,omt56v4jxa4hurbwk44vqbbcwn3eavuynyc24c25cy7grucjh24q -pool.gostcoin.i2p,m4f4k3eeaj7otbc254ccj7d5hivguqgnohwelkibr4ddk43qhywa -pop.mail.i2p,bup6pmac7adgzkb5r6eknk2juczkxigolkwqkbmenawkes5s5qfq -pop.postman.i2p,ipkiowj7x4yjj7jc35yay3c6gauynkkl64gzzyxra3wmyhtfxlya -pravtor.i2p,2sr27o5x2v2pyqro7wl5nl6krrsbizwrzsky5y7pkohwh24gn6xq -pris.i2p,ahiwycgzuutdxvfqu3wseqffdnhy675nes57s4it2uysy5pxmz6a -project-future.i2p,ivqynpfwxzl746gxf376lxqvgktql2lqshzwnwjk2twut6xq7xta -projectmayhem2012-086.i2p,ehkjj4ptsagxlo27wpv4a5dk4zxqf4kg4p6fh35xrlz4y6mhe4eq -protokol.i2p,f4xre35ehc5l6ianjvt3zcktxkjlyp2iwdje65qnu2j6vurhy6nq -proxynet.i2p,7gar5a3n4hzvsgi73iizo65mjza4kujf7feopfxuwu5p6wtwog5a -psi.i2p,avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a -psy.i2p,s3elzoj3wo6v6wqu5ehd56vevpz2vrhhjc5m6mxoazicrl43y62q -psyco.i2p,eoilbrgyaiikxzdtmk2zeoalteupjrvcu3ui23p4wvfqo25bb73q -pt.hiddenanswers.i2p,o5jlxbbnx3byzgmihqye3kysop5jgl3unsrkmurbtr2nrnl2y74a -ptm.i2p,7dna5745ynxgogpjermnq26hwrqyjdlsibpjfmjxlwig247bjisa -ptt.i2p,q7r32j7lc3xgrcw2ym33wv4lfgqbez7vtm4lts7n34qfe3iygeha -pull.git.repo.i2p,3so7htzxzz6h46qvjm3fbd735zl3lrblerlj2xxybhobublcv67q -push.git.repo.i2p,jef4g5vxnqybm4zpouum3lzbl6ti6456q57nbyj5kfyldkempm3a -pycache.awup.i2p,w45lkxdnqhil4sgzanmxce62sv3q4szeowcjb2e72a5y5vbhm4ra -r4sas.i2p,2gafixvoztrndawkmhfxamci5lgd3urwnilxqmlo6ittu552cndq -radio.r4sas.i2p,cv72xsje5ihg6e24atitmhyk2cbml6eggi6b6fjfh2vgw62gdpla -ragnarok.i2p,jpzw6kbuzz3ll2mfi3emcaan4gidyt7ysdhu62r5k5xawrva7kca -ransack.i2p,mqamk4cfykdvhw5kjez2gnvse56gmnqxn7vkvvbuor4k4j2lbbnq -rasputin-sucks.i2p,fdozdbyak4rul4jwpqfisbkcx4xbrkuvf2o5r6fd3xryyrjgvjiq -rebel.i2p,nch2arl45crkyk6bklyk2hrdwjf5nztyxdtoshy6llhwqgxho5jq -red.i2p,fzbdltgsg7jrpz7gmjfvhpcdnw5yrglwspnxqp4zoym3bglntzfa -redpanda.i2p,3wcnp6afz4cikqzdu2ktb5wfz7hb3ejdbpn7ocpy7fmeqyzbaiea -redzara.i2p,ty7bt62rw5ryvk44dd3v5sua6c7wnbpxxqb6v4dohajmwmezi7va -reefer.i2p,4cde25mrrnt5n4nvp5tl62gej33nekfvq2viubmx4xdakhm5pfaa -relatelist.i2p,utrer5zgnou72hs4eztmk37pmzdtfw3d6s23wwl7nk3lkqpzbdiq -repo.i2p,uxe3lqueuuyklel23sf5h25zwgqgjwsofrqchhnptd5y6pedzbxa -repo.r4sas.i2p,ymzx5zgt6qzdg6nhxnecdgbqjd34ery6mpqolnbyo5kcwxadnodq -reseed.i2p,j7xszhsjy7orrnbdys7yykrssv5imkn4eid7n5ikcnxuhpaaw6cq -retrobbs-nntp.i2p,fkyzl24oxcxvjzkx74t3533x7qjketzmvzk6bwn3d6hj5t7hlw6q -retrobbs.i2p,mnn77stihntxdoade3ca2vcf456w6vhhvdsfepdvq5qggikvprxq -retrobbs2.i2p,ejff7jtyaus37slkwgeqrrcmyhpj26carp7n27f5h6s5vlbeiy6q -revo-ua.i2p,hpojpumki22xjwhmhe6zkiy44oanyn7u4ctcfe3in2ibwm5l32hq -riaa.i2p,lfbezn7amkzhswnx7lb4lxihyggl2kuqo5c7vwkcv6bwqmr4cuoa -rideronthestorm.i2p,xrdc2qc7quhumhglpbcuiqxr42nuffv4xj4a73jbr4ygepitibqq -romster.i2p,eaf2stdqdbepylt53egvixdi34g2usvgi7a4oixsja6atkran43a -rootd.i2p,mzbe5wofwn7eaqq4yefrmxizqaxoslwqxrv5qcv2opx5lnhg64dq -rospravosudie.i2p,z55khrnlj6bzhs5zielutm6ae6t2bbhfuiujwlrp3teubqyc4w7q -rotten.i2p,j4bm3rvezlejnb44elniagi5v2gazh7jaqrzhbod2pbxmgeb2frq -rpi.i2p,56p5qxsrvo5ereibevetw2qbj5bronmos7wxunku27g2s4kpbnlq -rslight.i2p,bitag46q3465nylvzuikfwjcj7ewi4gjkjtvuxhn73f6vsxffyiq -rsync.thetower.i2p,w4brpcdod7wnfqhwqrxyt4sbf2acouqfk5wyosfpq4mxq4s35kqa -ru.hiddenanswers.i2p,o6rmndvggfwnuvxwyq54y667fmmurgveerlzufyrhub6w3vkagva -ru.i2p,m7fqktjgtmsb3x7bvfrdx4tf7htnhytnz5qi2ujjcnph33u3hnja -rufurus.i2p,7msryymfdta3ssyz34qur6gi4jyfkvca5iyfmnceviipwu7g2wca -rus.i2p,gh6655arkncnbrzq5tmq4xpn36734d4tdza6flbw5xppye2dt6ga -ruslibgen.i2p,kk566cv37hivbjafiij5ryoui2ebxnm7b25gb3troniixopaj6nq -rutor.i2p,tro5tvvtd2qg34naxhvqp4236it36jjaipbda5vnjmggp55navdq -salt.i2p,6aflphlze6btsbez5cm4x53ydrmwhqrkxsud535d3qjh4wq62rxq -sasquotch.i2p,p6535uyfk2y6etc3t47vd3oqxydznqior5jxcvq5bdxe5kw5th6q -schwarzwald.i2p,4gokilzy73mmudufy3pohgatm42fcstx7uzg5hjvnfyphxpnphuq -sciencebooks.i2p,ypftjpgck75swz3bnsu4nw7rmrlr2vqsn4mwivwt3zcc3rxln5cq -scp.duck.i2p,ghbpsolpnveizxu4wbs7jbs2vj3kntnsexfcdleyhpqdhfpxleda -search.i2p,nz4qj6xaw5fda3rsmsax6yjthqy4c7uak2j3dzcehtkgyso4q46q -secretchat.i2p,cl3j2zxhpw6u6jevny45i557ojhwfxn4g375nnuqhy6lp27mry2q -secure.thetinhat.i2p,4q3qyzgz3ub5npbmt3vqqege5lg4zy62rhbgage4lpvnujwfpala -seeker.i2p,ipll7sit24oyhnwawpvokz5u7dabq6klveuqpx3sbi6o5qemy2bq -seomon.i2p,5mvpsy4h45w4fx7upen7ay3vkrs5klphz5nptmtcqvc3fsajsm4q -septu.i2p,5lqvih7yzbqacfi63hwnmih57dxopu5g2o5o4e2aorq7bt4ooyra -serien.i2p,3z5k3anbbk32thinvwcy4g5al7dmb75fagcm3zgh4rzrt3maphda -ses.i2p,5qfoz6qfgbo7z5sdi26naxstpi2xiltamkcdbhmj6y6q2bo4inja -shiftfox.i2p,wpvnuzslu7hjy4gujvnphtyckchdoxccrlhbyomsmjizykczyseq -shoieq3.i2p,3fjk4nfk3mccch4hdreghnyijcvovsi3yucjz3qzj5sxngqk5j6q -shoronil.i2p,7shqzgmb6tabiwrnwlasruq7pswy2d3emvfhaitehkqgod7i62sa -short.i2p,z5mt5rvnanlex6r3x3jnjhzzfqpv36r4ylesynigytegjmebauba -sion.i2p,lcbmmw2tvplvqh2dq5lmpxl3vnd5o4j3bdul5moa23deakjrso5q -sirup.i2p,aohdp4yajnkitrtw7v2mo3sp7swuqhjfwlsi5xwd7dudzftumsma -site.games.i2p,zeuczucfxeev3k7tvqlfcdpfbnqggheiknyyb5r2q4utn3d2auja -skank.i2p,qiii4iqrj3fwv4ucaji2oykcvsob75jviycv3ghw7dhzxg2kq53q -slack.i2p,gfcsh2yrb2tx7hyvmobriv52skz7qoobn7n7y7n6xaehhh4rpbja -slacker.i2p,wq7m2wdguzweleb666ygv3bmfhha63zj74rub76vfesbyhsyk6iq -smeghead.i2p,ojf4czveeuekxqkjvkszvv7eiop5dg7x2p6rgfzl4ng4xrjk6lja -smtp.mail.i2p,kdn7zx7fgoe4bn5abaaj5cb3e4ql22fklb5veui5yajpj4cxapya -smtp.postman.i2p,jj7pt6chsziz6oxxnzpqj7mzhxm2xfhcrbh7dl3tegifb577vx5q -socks1.tor.i2p,sifawcdexgdmoc3krv46pvvz74nzd6fkju2vzykjxsx3egqsb6wq -sonax.i2p,jmuxdhlok5ggojehesfjlit2e2q3fhzwwfxjndts7vzdshucbjjq -sponge.i2p,o5hu7phy7udffuhts6w5wn5mw3sepwe3hyvw6kthti33wa2xn5tq -squid.i2p,r4ll5zkbokgxlttqc2lrojvvey5yar4xr5prnndvnmggnqzjaeoq -squid2.i2p,hum4wlwizbsckbudcklflei66qxhpxsdkyo4l2rn256smmjleila -sqz.i2p,3jvbwc7sy4lnhj25nj7yepx7omli4ulqirnawv3mz6qlhgokjgzq -ssh.i2p,xpvdadaouc4qr75pteymyozc7mcsynjfkuqqkkla542lpcsqionq -stasher.i2p,6ilgpudnba4kroleunc2weh5txgoxys5yucij5gla6pjyki4oewa -stats.i2p,7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq -status.str4d.i2p,ycyyjo3psqbo45nuz243xvgvwnmzlanzqbzxv3kh6gyjztv7425q -sto-man.i2p,rg4eilfpe24ws6nctix63qw2dlvd2tqgwdcgdxzji6l5bc4dc7aa -str4d.i2p,wrrwzdgsppwl2g2bdohhajz3dh45ui6u3y7yuop5ivvfzxtwnipa -stream.i2p,prmbv3xm63ksoetnhbzqg4nzu2lhqdnqytgsydb7u3quxfrg7rna -streams.darkrealm.i2p,ud3gcmvysjch4lbjr2khmhqpf7r2x5if4q43xkqdptl4k7lc4muq -striker.i2p,4gswsrfpbd44hwjoj33jbqfbwzxfkwpuplb3ydq5zm7nfu2pxvdq -subrosa.i2p,g3lnglrnoual7wyabnwwv37uwhadgbxiqz36pf3f5cwfuxsx4mxq -subterra.i2p,vdmhe4u26unzgd7ysq6w36ubjncms5wzbhzr2gq576sq4xut5zwq -sugadude.i2p,yzjn76iyqard64wgggfrnywkxi7tbfkw7mjhpviqz3p2dguey4yq -suicidal.i2p,yfamynllow5xiqbbca7eh5xn733wtnuti5bi4ovc7dwycntqmiuq -sungo.i2p,h67s3jw56rwfyoxqxj3fngrluybsgxc2meendngkehzqowxnpj3q -surrender.adab.i2p,jgz7xglgfgnjfklrytyn427np2ubipztlm5bxrtbiucayglukrta -susi.i2p,qc6g2qfi2ccw7vjwpst6rwuofgzbeoewsb2usv7rubutf4gzqveq -syncline.i2p,5kcqmhislu3lmr7llgmdl72yu3efhyriljdc6wp774ftpwlcs5ra -syndie-project.i2p,xa63tpfoaqt3zru2ehxjjfbpadwj4ha6qsdvtcqtyr3b7hmt4iaq -syndie.echelon.i2p,vwrl2qmcif722fdkn3ldxcgz76df5cq4qypbndzthxwgmykyewta -syndie.i2p,7lm3yzpuejhpl4tt4l7o4ndqlu7hgijohofh7oaydx7q7kelenbq -syndiemedia.i2p,4lrbbblclodhobn3jadt5bf2yab2pxzoz4ey4a2cvrl44tdv3jma -tabak.i2p,y5o2vwb6kart7ivpnbpk4yte3i7kf2dsx7fy3i6w7htqtxhmbzia -tahoeserve.i2p,yhs7tsjeznxdenmdho5gjmk755wtredfzipb5t272oi5otipfkoa -tc.i2p,qkv2yk6rof3rh7n3eelg5niujae6cmdzcpqbv3wsttedxtqqqj7a -telegram.i2p,i6jow7hymogz2s42xq62gqgej2zdm4xtnmpc6vjcwktdxpdoupja -templar.i2p,zxeralsujowfpyi2ynyjooxy222pzz4apc2qcwrfx5ikhf64et7q -terror.i2p,wsijm6aqz4qtuyn2jedpx6imar5uq4yuhjdgtfqumxbqww47vbnq -thebland.i2p,oiviukgwapzxsrwxsoucpqa47s3wt6nfuhfjxvgbqsyrze2mwrda -thebreton.i2p,woutbsflcrlgppx4y7ag2kawlqijyenvlwrhbbvbkoaksuhf2hkq -thedarkside.i2p,fxt3z33nzkrg5kjrk7bp5vvmu7w2vsn4i6jo6cily3hsm6u664ca -theland.i2p,26ppxbseda6xmim37ksarccdb4q5ctdagfmt2u5aba6xjh452zsa -thetower.i2p,3xqa5nype64y6fxgqjq6r5w2qpiqftoraj2niebumseat4cj654a -thornworld.i2p,vinz4ygmodxarocntyjlfwk2wjpvzndlf4hxss2w2t3fk52oplva -tino.i2p,e4bfnhvaofu4s67ztcgiskos2mqyhskid64dvlqexxs2c2bno3iq -tinyurl.i2p,mc4oxv3v7dnyzpvok7v5qxkwtgjprgyz6w7x3tag4fipsen6rdwa -tome.i2p,qktkxwawgixrm5lzofnj5n24zspbnzxy4pvjm7uvaxvmgwrsuvgq -tor-gw.meeh.i2p,ounrqi7cfemnt66yhnhigt2u27fkctbvct527cp2522ozy3btjza -tor-www-proxy.i2p,xov45rvjks5fe4ofmpblkj23bnwxgslbypbgvchbr7yul2ujej2q -torapa.i2p,eejqjtpko6mdd4opvntbpsuandstrebxpbymfhix7avp5obrw5ta -torrentfinder.i2p,mpc73okj7wq2xl6clofl64cn6v7vrvhpmi6d524nrsvbeuvjxalq -torrfreedom.i2p,nfrjvknwcw47itotkzmk6mdlxmxfxsxhbhlr5ozhlsuavcogv4hq -trac.i2p,kyioa2lgdi2za2fwfwajnb3ljz6zwlx7yzjdpnxnch5uw3iqn6ca -trac.i2p2.i2p,i43xzkihpdq34f2jlmtgiyyay5quafg5rebog7tk7xil2c6kbyoa -tracker-fr.i2p,qfrvqrfoqkistgzo2oxpfduz4ktkhtqopleozs3emblmm36fepea -tracker.awup.i2p,dl47cno335ltvqm6noi5zcij5hpvbj7vjkzuofu262efvu6yp6cq -tracker.crypthost.i2p,ri5a27ioqd4vkik72fawbcryglkmwyy4726uu5j3eg6zqh2jswfq -tracker.fr.i2p,rzwqr7pfibq5wlcq4a7akm6ohfyhz7hchmy4wz5t55lhd7dwao5q -tracker.i2p,lsjcplya2b4hhmezz2jy5gqh6zlk3nskisjkhhwapy3jjly4ds5q -tracker.lodikon.i2p,q2a7tqlyddbyhxhtuia4bmtqpohpp266wsnrkm6cgoahdqrjo3ra -tracker.mastertracker.i2p,tiwurhqvaaguwpz2shdahqmcfze5ejre52ed2rmoadnjkkilskda -tracker.postman.i2p,jfcylf4j3gfmqogkltwy7v5m47wp4h7ffrnfsva6grfdavdn7ueq -tracker.psi.i2p,vmow3h54yljn7zvzbqepdddt5fmygijujycod2q6yznpy2rrzuwa -tracker.thebland.i2p,s5ikrdyjwbcgxmqetxb3nyheizftms7euacuub2hic7defkh3xhq -tracker.welterde.i2p,cfmqlafjfmgkzbt4r3jsfyhgsr5abgxryl6fnz3d3y5a365di5aa -tracker2.postman.i2p,ahsplxkbhemefwvvml7qovzl5a2b5xo5i7lyai7ntdunvcyfdtna -traditio.i2p,wkpjjloylf6jopu2itgpktr45t2xvpjijxilxd5tq4i7wkqgwhhq -trevorreznik.i2p,wc2z6o5fxm2saqzpfcawr63lejwccvzkysmgtfudkrigqopzfdma -true.i2p,pdilhl5vmefyzrrnmak5bnmxqxk2pmw7rpy4f7wbaeppqu2vvugq -trwcln.i2p,evml6jiiujhulsgxkdu3wcmkwbokxlv4is6w5qj46tp3ajz3hqzq -trypanom.i2p,tgv5acj4khwvr6t44cmryohybd2e5o2kndysnzae6qwcr4hzda3q -ts.i2p,nebcjgfx3f7q4wzihqmguwcdeopaf7f6wyk2dojw4bcuku472zxq -ttc.i2p,wb4tsfyvfv4idgrultsq6o7inza4fxkc7dijsfpncbx7zko4cdlq -ttp.i2p,uuczclxejmetohwf2vqewovx3qcumdfh5zecjb3xkcdmk6e5j72a -tumbach.i2p,u6pciacxnpbsq7nwc3tgutywochfd6aysgayijr7jxzoysgxklvq -tutorials.i2p,zy37tq6ynucp3ufoyeegswqjaeofmj57cpm5ecd7nbanh2h6f2ja -ugha.i2p,z3f3owc72awbywk4p6qb5l2mxgitvs6ejztggbpn2a3ddmymfjda -uk.i2p,vydbychnep3mzkzhg43ptewp242issy47whamfbxodc4ma6wc63a -underground.i2p,dlnuthb6tpw3kchlb7xoztyspy4ehlggjhl44l64vbcrulrfeica -underscore.i2p,3gmezyig6gvsjbpkq2kihoskpuqpkfrajmhhm7hpyrjuvtasgepa -unqueued.i2p,3gvn4kwd7z74jxc2sn4ucx52dpvpscxbzjluux3ul4t3eu5g64xq -up.i2p,25it5olgdo7pht25z6buzd32sw7jvc65oziqeuocfozfhgua655q -update.dg.i2p,iqj6ysfh3wl26m4buvyna73yhduifv523l7bwuexxak4mgldexja -update.killyourtv.i2p,gqdfg25jlqtm35qnmt4b7r53d6u2vep4ob23fwd42iyy4j6cvdqq -update.postman.i2p,u5rbu6yohfafplp6lgbbmmcuip34s7g3zqdd63cp27dl3nbd7gtq -utansans.i2p,u2oyre7ygqv4qs5xjjijfg3x7ddwtod6nqwgbomuuzljzvnq4rda -v2mail.i2p,4gg7fykcqe7oaqt4w5fmlarnia7vtmwkv3h45zzgoj6o6crryg5a -vadino.i2p,aalttzlt3z25ktokesceweabm5yyhhvml2z3rfotndgpfyh6myra -visibility.i2p,pwgma3snbsgkddxgb54mrxxkt3l4jzchrtp52vxmw7rbkjygylxq -volatile.i2p,q6rve733tvhgyys57jfw4fymqf3xsnza6dqailcdjcq7w4fa5m3a -vpnbest.i2p,ov5f74ndsy5rfkuyps56waf42vxncufqu5rzm3vsnxkdtogccaea -vudu.i2p,3zlwci7pvgep2igygzyjej24ue7mjsktlhaff6crpsr75yquak2q -w.i2p,j2xorlcb3qxubnthzqu7lt4fvxqn63it4ikwmze55yjkzeeampuq -wa11ed.city.i2p,7mxwtmala3ycg2sybjwwfil7s6dqck2fbemeutghhwu73rznmqoa -wahoo.i2p,vqe5vkpe5wbda7lwekcd2jaj44ar3rawgv54u5rcolezbg5f5vwa -wallet.gostcoin.i2p,reuvum7lgetglafn72chypesvto773oy53zumagrpigkckybrwda -wallsgetbombed.i2p,tzhea5d65fllm4263wztghgw4ijdgibsca5xsecp6lk4xlsbdeuq -web.telegram.i2p,re6cgwg2yrkgaixlqvt5ufajbb3w42fsldlq7k5brpvnd5gp6x5a -wiht.i2p,yojmpj3sh76g3i6ogzgsf7eouipdgdij5o2blcpdgmu5oyjk5xca -wiki.fr.i2p,lrqa7hw52uxjb5q3pedmjs6hzos5zrod4y6a4e25hu7vcjhohvxq -wiki.ilita.i2p,r233yskmowqe4od4he4b37wydr5fqzvj3z77v5fdei2etp2kg34a -wintermute.i2p,4gvlfrdy2rkmem33c342tjntpvqik65wekcvm4275qbkuwotoila -wspucktracker.i2p,ubd2txda3kllumx7ftg4unzgqy536cn6dd2ax6mlhodczfas7rgq -www.aum.i2p,3xolizygkzkqrldncjqsb734szznw2u36lliceuacqnbs2n65aeq -www.baffled.i2p,lqrsfslwu4xnubkk2hofhmuvvr4dia2zevxefinbzdsjurvehtqq -www.fr.i2p,rmkgvlfwo3vkb3xrr6epoypxasdzzuilv3sckcqbo6c4os5jo2ea -www.i2p,ojxyenivrrqvycgbxbm3phgisu5abspzq4g2us4fjlwz4tx222va -www.i2p2.i2p,rjxwbsw4zjhv4zsplma6jmf5nr24e4ymvvbycd3swgiinbvg7oga -www.imule.i2p,657xcllunctawyjtar5kgh3wpt6z4l7ba6mmam5rf7hev5w2lsvq -www.infoserver.i2p,fq7xhxkdcauhwn4loufcadiiy24zbei25elnup33a3gfrdzrtlyq -www.janonymous.i2p,vosqx5qw22hwrzcgsm4ib7hymf5ryovsbtaexqrzmnzshy5bhakq -www.mail.i2p,nctas6ioo7aaekfstv3o45yh6ywzwa3vznrdae52ouupzke5pyba -www.nntp.i2p,kly3o7zmetuwyz7xonnhttw4lj2244pkbibjz26uflyfte3b3dka -www.postman.i2p,rb3srw2gaooyw63q62cp4udrxxa6molr2irbkgrloveylpkkblhq -www.syndie.i2p,vojgy5ep4wffmtpjmpnbpa4gq64bgn4yicuw6qmhbm6nqa2ysrva -www1.squid.i2p,vbh3bltd2duwbukafgj6f6vfi6aigwso7snucp5zohnf66a2hkpa -xc.i2p,mt45a2z3sb2iyy2mwauj4rwa2lwu4peanfy6gx6ybidwnbasusyq -xeha.i2p,oartgetziabrdemxctowp7bbeggc7ktmj7tr4qgk5y5jcz4prbtq -xilog.i2p,eoc5i5q52hutnmsmq56edvooulutaxfikddgdz27otmgtsxmiloq -xmpp.crypthost.i2p,ittkqpjuliwsdewdugkhvgzstejr2jp5tzou7p332lxx4xw7srba -xmpp.rpi.i2p,3yv65pfwiwfuv4ciwtx34clqps6o2mc3vtyltcbqdkcki6untbca -xn--l2bl5aw.i2p,d2epikjh5crt2l5xjmtceqw2ho44hzp6x3u7hgjrd4mi4wywikwa -xolotl.i2p,rwr6rrlmrotxfkxt22mah42cycliy2g5k7hgxyxkpcyyxkd2bgwq -xotc.i2p,gqgvzum3xdgtaahkjfw3layb33vjrucmw5btyhrppm463cz3c5oq -z-lab.i2p,s6g2pz3mrwzsl4ts65ox3scqawfj7mzvd7hn2ekiiycawopkriba -zab.i2p,n4xen5sohufgjhv327ex4qra77f4tpqohlcyoa3atoboknzqazeq -zcash.i2p,zcashmliuw3yd2ptfyd5sadatcpyxj4ldiqahtjzg73cgoevxp4q -zener.i2p,mcbyglflte3dhwhqyafsfpnqtcapqkv2sepqd62wzd7fo2dzz4ca -zerobin.i2p,3564erslxzaoucqasxsjerk4jz2xril7j2cbzd4p7flpb4ut67hq -zeroman.i2p,gq77fmto535koofcd53f6yzcc5y57ccrxg3pb6twhcodc7v5dutq -zeronet.i2p,fe6pk5sibhkr64veqxkfochdfptehyxrrbs3edwjs5ckjbjn4bna -znc.i2p,uw2yt6njjl676fupd72hiezwmd4ouuywowrph6fvhkzhlnvp7jwa -znc.str4d.i2p,ufkajv3stxpxlwgwwb2ae6oixdjircnbwog77qxpxv7nt67rpcxq -zzz.i2p,ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq diff --git a/android/assets/certificates b/android/assets/certificates deleted file mode 120000 index 01b21e5d..00000000 --- a/android/assets/certificates +++ /dev/null @@ -1 +0,0 @@ -../../contrib/certificates \ No newline at end of file diff --git a/android/assets/i2pd.conf b/android/assets/i2pd.conf deleted file mode 100644 index 14254248..00000000 --- a/android/assets/i2pd.conf +++ /dev/null @@ -1,92 +0,0 @@ -## Configuration file for a typical i2pd user -## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/ -## for more options you can use in this file. - -#logfile = /sdcard/i2pd/i2pd.log -loglevel = none -#tunnelsdir = /sdcard/i2pd/tunnels.d - -# host = 1.2.3.4 -# port = 4567 - -ipv4 = true -ipv6 = false - -# ntcp = true -# ntcpproxy = http://127.0.0.1:8118 -# ssu = true - -bandwidth = L -# share = 100 - -# notransit = true -# floodfill = true - -[ntcp2] -enabled = true - -[http] -enabled = true -address = 127.0.0.1 -port = 7070 -# auth = true -# user = i2pd -# pass = changeme - -[httpproxy] -enabled = true -address = 127.0.0.1 -port = 4444 -inbound.length = 1 -inbound.quantity = 5 -outbound.length = 1 -outbound.quantity = 5 -signaturetype=7 -i2cp.leaseSetType=3 -i2cp.leaseSetEncType=0,4 -keys = proxy-keys.dat -# addresshelper = true -# outproxy = http://false.i2p -## httpproxy section also accepts I2CP parameters, like "inbound.length" etc. - -[socksproxy] -enabled = true -address = 127.0.0.1 -port = 4447 -keys = proxy-keys.dat -# outproxy.enabled = false -# outproxy = 127.0.0.1 -# outproxyport = 9050 -## socksproxy section also accepts I2CP parameters, like "inbound.length" etc. - -[sam] -enabled = false -# address = 127.0.0.1 -# port = 7656 - -[precomputation] -elgamal = true - -[upnp] -enabled = true -# name = I2Pd - -[reseed] -verify = true -## Path to local reseed data file (.su3) for manual reseeding -# file = /path/to/i2pseeds.su3 -## or HTTPS URL to reseed from -# file = https://legit-website.com/i2pseeds.su3 -## Path to local ZIP file or HTTPS URL to reseed from -# zipfile = /path/to/netDb.zip -## If you run i2pd behind a proxy server, set proxy server for reseeding here -## Should be http://address:port or socks://address:port -# proxy = http://127.0.0.1:8118 -## Minimum number of known routers, below which i2pd triggers reseeding. 25 by default -# threshold = 25 - -[limits] -transittunnels = 50 - -[persist] -profiles = false diff --git a/android/assets/subscriptions.txt b/android/assets/subscriptions.txt deleted file mode 100644 index 8f4afb03..00000000 --- a/android/assets/subscriptions.txt +++ /dev/null @@ -1,3 +0,0 @@ -http://inr.i2p/export/alive-hosts.txt -http://stats.i2p/cgi-bin/newhosts.txt -http://i2p-projekt.i2p/hosts.txt diff --git a/android/assets/tunnels.conf b/android/assets/tunnels.conf deleted file mode 100644 index c39a0220..00000000 --- a/android/assets/tunnels.conf +++ /dev/null @@ -1,33 +0,0 @@ -#[IRC-IRC2P] -#type = client -#address = 127.0.0.1 -#port = 6668 -#destination = irc.postman.i2p -#destinationport = 6667 -#keys = irc-keys.dat - -#[IRC-ILITA] -#type = client -#address = 127.0.0.1 -#port = 6669 -#destination = irc.ilita.i2p -#destinationport = 6667 -#keys = irc-keys.dat - -#[SMTP] -#type = client -#address = 127.0.0.1 -#port = 7659 -#destination = smtp.postman.i2p -#destinationport = 25 -#keys = smtp-keys.dat - -#[POP3] -#type = client -#address = 127.0.0.1 -#port = 7660 -#destination = pop.postman.i2p -#destinationport = 110 -#keys = pop3-keys.dat - -# see more examples at https://i2pd.readthedocs.io/en/latest/user-guide/tunnels/ diff --git a/android/assets/tunnels.d b/android/assets/tunnels.d deleted file mode 120000 index ff262031..00000000 --- a/android/assets/tunnels.d +++ /dev/null @@ -1 +0,0 @@ -../../contrib/tunnels.d \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index 0c208c91..00000000 --- a/android/build.gradle +++ /dev/null @@ -1,105 +0,0 @@ -buildscript { - repositories { - mavenCentral() - jcenter() - google() - } - dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' - } -} - -apply plugin: 'com.android.application' - -repositories { - jcenter() - maven { - url 'https://maven.google.com' - } - google() -} - -dependencies { - implementation 'androidx.core:core:1.0.2' - implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' -} - -android { - compileSdkVersion 29 - buildToolsVersion "28.0.3" - defaultConfig { - applicationId "org.purplei2p.i2pd" - targetSdkVersion 29 - minSdkVersion 14 - versionCode 2350 - versionName "2.35.0" - setProperty("archivesBaseName", archivesBaseName + "-" + versionName) - ndk { - abiFilters 'armeabi-v7a' - abiFilters 'x86' - //abiFilters 'arm64-v8a' - //abiFilters 'x86_64' - } - externalNativeBuild { - ndkBuild { - arguments "-j3" - } - } - } - sourceSets { - main { - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src'] - res.srcDirs = ['res'] - jniLibs.srcDirs = ['libs'] - assets.srcDirs = ['assets'] - } - } - splits { - abi { - // change that to true if you need splitted apk - enable true - reset() - //include "armeabi-v7a", "arm64-v8a", "x86", "x86_64" - include "armeabi-v7a", "x86" - universalApk true - } - } - signingConfigs { - orignal { - storeFile file("i2pdapk.jks") - storePassword "android" - keyAlias "i2pdapk" - keyPassword "android" - } - } - buildTypes { - release { - minifyEnabled false - signingConfig signingConfigs.orignal - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' - } - } - externalNativeBuild { - ndkBuild { - path './jni/Android.mk' - } - } - compileOptions { - sourceCompatibility = '1.8' - targetCompatibility = '1.8' - } -} - -ext.abiCodes = ['armeabi-v7a': 1, 'x86': 2, 'arm64-v8a': 3, 'x86_64': 4] -import com.android.build.OutputFile - -android.applicationVariants.all { variant -> - variant.outputs.each { output -> - def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI)) - - if (baseAbiVersionCode != null) { - output.versionCodeOverride = baseAbiVersionCode + variant.versionCode - } - } -} diff --git a/android/build.xml b/android/build.xml deleted file mode 100644 index ed8196c3..00000000 --- a/android/build.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index 6678daaf..00000000 --- a/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true -org.gradle.parallel=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index f6b961fd5a86aa5fbfe90f707c3138408be7c718..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54329 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

    <5G2M1WZP5 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 F{|7XC?P&l2 diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 3b803211..00000000 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Tue Aug 20 14:39:08 MSK 2019 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/android/gradlew b/android/gradlew deleted file mode 100755 index cccdd3d5..00000000 --- a/android/gradlew +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env sh - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat deleted file mode 100644 index f9553162..00000000 --- a/android/gradlew.bat +++ /dev/null @@ -1,84 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/android/jni/Android.mk b/android/jni/Android.mk deleted file mode 100755 index 07dc7080..00000000 --- a/android/jni/Android.mk +++ /dev/null @@ -1,73 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := i2pd -LOCAL_CPP_FEATURES := rtti exceptions -LOCAL_C_INCLUDES += $(IFADDRS_PATH) $(LIB_SRC_PATH) $(LIB_CLIENT_SRC_PATH) $(DAEMON_SRC_PATH) -LOCAL_STATIC_LIBRARIES := \ - boost_system \ - boost_date_time \ - boost_filesystem \ - boost_program_options \ - crypto ssl \ - miniupnpc -LOCAL_LDLIBS := -lz - -LOCAL_SRC_FILES := DaemonAndroid.cpp i2pd_android.cpp $(IFADDRS_PATH)/ifaddrs.c \ - $(wildcard $(LIB_SRC_PATH)/*.cpp)\ - $(wildcard $(LIB_CLIENT_SRC_PATH)/*.cpp)\ - $(DAEMON_SRC_PATH)/Daemon.cpp \ - $(DAEMON_SRC_PATH)/UPnP.cpp \ - $(DAEMON_SRC_PATH)/HTTPServer.cpp \ - $(DAEMON_SRC_PATH)/I2PControl.cpp - -include $(BUILD_SHARED_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := boost_system -LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_system.a -LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include -include $(PREBUILT_STATIC_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := boost_date_time -LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_date_time.a -LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include -include $(PREBUILT_STATIC_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := boost_filesystem -LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_filesystem.a -LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include -include $(PREBUILT_STATIC_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := boost_program_options -LOCAL_SRC_FILES := $(BOOST_PATH)/boost-1_72_0/$(TARGET_ARCH_ABI)/lib/libboost_program_options.a -LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost-1_72_0/include -include $(PREBUILT_STATIC_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := crypto -LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1d-clang/$(TARGET_ARCH_ABI)/lib/libcrypto.a -LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1d-clang/include -include $(PREBUILT_STATIC_LIBRARY) - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -LOCAL_MODULE := ssl -LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.1d-clang/$(TARGET_ARCH_ABI)/lib/libssl.a -LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.1d-clang/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)/miniupnpc-2.1/$(TARGET_ARCH_ABI)/lib/libminiupnpc.a -LOCAL_EXPORT_C_INCLUDES := $(MINIUPNP_PATH)/miniupnpc-2.1/include -include $(PREBUILT_STATIC_LIBRARY) diff --git a/android/jni/Application.mk b/android/jni/Application.mk deleted file mode 100755 index 0c291661..00000000 --- a/android/jni/Application.mk +++ /dev/null @@ -1,36 +0,0 @@ -#APP_ABI := armeabi-v7a x86 -#APP_PLATFORM := android-14 - -# ABI arm64-v8a and x86_64 supported only from platform-21 -#APP_ABI := arm64-v8a x86_64 -#APP_PLATFORM := android-21 - -NDK_TOOLCHAIN_VERSION := clang -#APP_STL := c++_shared -APP_STL := c++_static - -# Enable c++17 extensions in source code -APP_CPPFLAGS += -std=c++17 -fexceptions -frtti - -APP_CPPFLAGS += -DANDROID -D__ANDROID__ -DUSE_UPNP -ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) -APP_CPPFLAGS += -DANDROID_ARM7A -endif - -# git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git -b boost-1_72_0 -# 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 - -# don't change me -I2PD_SRC_PATH = $(PWD)/.. - -LIB_SRC_PATH = $(I2PD_SRC_PATH)/libi2pd -LIB_CLIENT_SRC_PATH = $(I2PD_SRC_PATH)/libi2pd_client -DAEMON_SRC_PATH = $(I2PD_SRC_PATH)/daemon diff --git a/android/jni/DaemonAndroid.cpp b/android/jni/DaemonAndroid.cpp deleted file mode 100644 index 39c06ea0..00000000 --- a/android/jni/DaemonAndroid.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include -#include -#include -#include -//#include "mainwindow.h" -#include "FS.h" -#include "DaemonAndroid.h" -#include "Daemon.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."); - } - } -*/ - std::string dataDir = ""; - - 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; - - // make sure assets are ready before proceed - i2p::fs::DetectDataDir(dataDir, false); - int numAttempts = 0; - do - { - if (i2p::fs::Exists (i2p::fs::DataDirPath("assets.ready"))) break; // assets ready - numAttempts++; - std::this_thread::sleep_for (std::chrono::seconds(1)); // otherwise wait for 1 more second - } - while (numAttempts <= 10); // 10 seconds max - 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(); - } - - void SetDataDir(std::string jdataDir) - { - dataDir = jdataDir; - } -} -} diff --git a/android/jni/DaemonAndroid.h b/android/jni/DaemonAndroid.h deleted file mode 100644 index 912f6f49..00000000 --- a/android/jni/DaemonAndroid.h +++ /dev/null @@ -1,97 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#ifndef DAEMON_ANDROID_H -#define DAEMON_ANDROID_H - -#include - -namespace i2p -{ -namespace android -{ - class DaemonAndroidImpl - { - public: - - DaemonAndroidImpl (); - ~DaemonAndroidImpl (); - - //typedef void (*runningChangedCallback)(); - - /** - * @return success - */ - bool init(int argc, char* argv[]); - void start(); - void stop(); - void restart(); - //void setRunningCallback(runningChangedCallback cb); - //bool isRunning(); - private: - //void setRunning(bool running); - private: - //QMutex* mutex; - //bool m_IsRunning; - //runningChangedCallback m_RunningChangedCallback; - }; - - /** - * returns "ok" if daemon init failed - * returns errinfo if daemon initialized and started okay - */ - std::string start(); - - // stops the daemon - void stop(); - - // set datadir received from jni - void SetDataDir(std::string jdataDir); - /* - class Worker : public QObject - { - Q_OBJECT - public: - - Worker (DaemonAndroidImpl& daemon); - - private: - - DaemonAndroidImpl& m_Daemon; - - public slots: - void startDaemon(); - void restartDaemon(); - void stopDaemon(); - - signals: - void resultReady(); - }; - - class Controller : public QObject - { - Q_OBJECT - QThread workerThread; - public: - Controller(DaemonAndroidImpl& daemon); - ~Controller(); - private: - DaemonAndroidImpl& m_Daemon; - - public slots: - void handleResults(){} - signals: - void startDaemon(); - void stopDaemon(); - void restartDaemon(); - }; - */ -} -} - -#endif // DAEMON_ANDROID_H diff --git a/android/jni/i2pd_android.cpp b/android/jni/i2pd_android.cpp deleted file mode 100755 index c6e309dd..00000000 --- a/android/jni/i2pd_android.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include "org_purplei2p_i2pd_I2PD_JNI.h" -#include "DaemonAndroid.h" -#include "RouterContext.h" -#include "ClientContext.h" -#include "Transports.h" -#include "Tunnel.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_startAcceptingTunnels - (JNIEnv *env, jclass clazz) { - i2p::context.SetAcceptsTunnels (true); -} - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_reloadTunnelsConfigs - (JNIEnv *env, jclass clazz) { - i2p::client::context.ReloadConfig(); -} - -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); -} - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_setDataDir - (JNIEnv *env, jclass clazz, jstring jdataDir) { - - /* - // Method 1: convert UTF-16 jstring to std::string (https://stackoverflow.com/a/41820336) - const jclass stringClass = env->GetObjectClass(jdataDir); - const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B"); - const jbyteArray stringJbytes = (jbyteArray) env->CallObjectMethod(jdataDir, getBytes, env->NewStringUTF("UTF-8")); - - size_t length = (size_t) env->GetArrayLength(stringJbytes); - jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL); - - std::string dataDir = std::string((char *)pBytes, length); - env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT); - - env->DeleteLocalRef(stringJbytes); - env->DeleteLocalRef(stringClass); */ - - // Method 2: get string chars and make char array. - auto dataDir = env->GetStringUTFChars(jdataDir, NULL); - env->ReleaseStringUTFChars(jdataDir, dataDir); - - // Set DataDir - i2p::android::SetDataDir(dataDir); -} - -JNIEXPORT jint JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_GetTransitTunnelsCount - (JNIEnv *env, jclass clazz) { - return i2p::tunnel::tunnels.CountTransitTunnels(); -} diff --git a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h b/android/jni/org_purplei2p_i2pd_I2PD_JNI.h deleted file mode 100644 index 68935ad1..00000000 --- a/android/jni/org_purplei2p_i2pd_I2PD_JNI.h +++ /dev/null @@ -1,53 +0,0 @@ -/* -* Copyright (c) 2013-2020, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class org_purplei2p_i2pd_I2PD_JNI */ - -#ifndef _Included_org_purplei2p_i2pd_I2PD_JNI -#define _Included_org_purplei2p_i2pd_I2PD_JNI -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: org_purplei2p_i2pd_I2PD_JNI - * Method: stringFromJNI - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith - (JNIEnv *, jclass); - -JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon - (JNIEnv *, jclass); - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon - (JNIEnv *, jclass); - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels - (JNIEnv *, jclass); - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startAcceptingTunnels - (JNIEnv *, jclass); - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_reloadTunnelsConfigs - (JNIEnv *, jclass); - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged - (JNIEnv * env, jclass clazz, jboolean isConnected); - -JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_setDataDir - (JNIEnv *env, jclass clazz, jstring jdataDir); - -JNIEXPORT jint JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_GetTransitTunnelsCount - (JNIEnv *, jclass); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/android/proguard-project.txt b/android/proguard-project.txt deleted file mode 100644 index f2fe1559..00000000 --- a/android/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/android/project.properties b/android/project.properties deleted file mode 100644 index 82181932..00000000 --- a/android/project.properties +++ /dev/null @@ -1,14 +0,0 @@ -# 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-29 diff --git a/android/res/drawable/icon.png b/android/res/drawable/icon.png deleted file mode 100644 index 9a2f7404a20eef22adc6da0fb1538b6c507c97f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37177 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 RE#UwF002ovPDHLkV1gi%A?yGE diff --git a/android/res/drawable/itoopie_notification_icon.png b/android/res/drawable/itoopie_notification_icon.png deleted file mode 100644 index fa99e7fc66b68ea9550dd70cd00b36611ecd9fa9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1940 zcmXX{2{c>j9#2o7mnjvAqb(hkNjjY=MXRAiD;~LOEY(E1JW^EElCi{AQL*$?aR)Ox z$NDTS*Fi>zsSO`oZ$FSRBSLSCeb=3H1Qi->toh7B-@?L6ch@L^MG8=B?qpE#Ywde zSe<`wYVJ-Sm|?sPAz}@me_Kd9D_yzZHINb0;ib0!$MTBzbW-_B$3a0b^?Gqh_Ih*? z=g3?z-|FyGir_pBALfmlktWw-tDX)Jd!3Xg2>vt~uczjcumjCDM2Hk2OthYBl2Zte zifZ?T=jC>KMN-L-o7!$NxVWSvVRG@{Pjznsg#?SaNHQ0;x<{fweFaU2poX?vhk;o7 z(2+SEUiLaGzp<+X2od;3aj>fqqKdx&KWaNs6m;d>%a+}A+|k2X_kqz0uG{KtKGy2+ zwaDQjj13WLkmuzlmwqB*=n0ChcsRCKZ;FUXf ze9!Jc_DQThZpvh?bDz@qf>;M>e7 zJ+bWswJilNo=x~<{5WMXbICL;$vL^Gkx+gmQe}-$l|i%P*s4B}!hQIm6GV;LC0?8| zgG%F~;Gy8KQ%#Ih1MsYgj5;cMxLeFKJ%A*XnE+oo5&Cr8>X3LZf;}HbPHf`DWhqxL zD?axBc;rivu&Zs_T~5N!%GF>+je*o(=r+k~hYPn%%#$LRyoE!jvw$N~+F(7fNXIpS z>hy8w)4b1L4xTF=pY3@OdW2ak@V}VbzCl{MDVcEi4w4@uK>_}G4iI7g6))JL74$&R z(_gjO?#paH3hK{nvm;qj&j~Bq8YRb{NwHP^lOSh2*xos?3*>oSK7*Ish{$bZL|M&g zD`3tC`RA~LXu9GL0=v`Rtxk}flo5Ah>CoxuA>Tln#ayW(L`XP&YbkXxXNhO9EgB&3 zE0YXDw4EX+IYT2+s}jd&QWpQQs1sW?tj1&xFIR-jp5hfUPyW5iny-$QzyIVL0#KR`C~6h}xo8Xj#{ zr#7rDZ?&x0*c;7WxR32AZFL=`QAA}g(H+|0pEr+HEek+*$B!%XcdG~2*WDFyyj2xC z^%d}ntwDGCzz=3c_0;IM5`Uoz|KVRG>c_pmbZTx4SDGLlvBD0`PRpi+_d#3ePEYv3 z!h5_1?a)GIm?Dwnlh8fVHdC{sWGo>a_6b*iMn}cvWULz;`yFsD?~)PHG>aiP7K`mB z)?fMYF|E-X-gLM(`{XT<-wJMVcR_Q1Np?K7i(41*&Wt^8onsxc?4<=~on+nNxpj)Q z6N;AVQJx*(ks_q>$+&2vm0P#;k!!k0(eu<_AO`YyTgFYtkKY+H-wu8A2ObIi9skPKXxcg+^SI=uS4iIy(+t{T8L@LcCbOH^@)me;H<*}JO}wI;8yv0Y3C3PTAzz1Q-S|3^+@_j{ zl>=@3>QNeot{uOB!OXZ-0842K$)k5&cNTZZ_HpsP>ak8Pn28Snfhs7ft0S1dRIKYW z3}TCH2)bhHmA5LAulnL?*&`cF-C&;x@2q-KfoWbyVk+gs8vK-A Vg*^@~1UD$k!_5bJ;ClJy{{UE{z48D6 diff --git a/android/res/layout/activity_perms_asker.xml b/android/res/layout/activity_perms_asker.xml deleted file mode 100644 index 778c9ef5..00000000 --- a/android/res/layout/activity_perms_asker.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - -

    \r\n
    \r\n"; } + if(dest->IsPublic()) + { + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + auto base32 = dest->GetIdentHash ().ToBase32 (); + s << "
    \r\n\r\n
    \r\n" + "
    \r\n" + " \r\n" + " \r\n" + " \r\n" + " Domain:\r\n\r\n" + " \r\n" + "
    \r\nNote: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.\r\n
    \r\n
    \r\n
    \r\n"; + } + if(dest->GetNumRemoteLeaseSets()) { s << "
    \r\n
    \r\n"; - if(outputFormat==OutputFormatEnum::forQtUi) { + if(outputFormat == OutputFormatEnum::forQtUi) { s << "
    "; } - s << "Routers: " << i2p::data::netdb.GetNumRouters () << " "; - s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; - s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
    \r\n"; + s << "" << tr("Routers") << ": " << i2p::data::netdb.GetNumRouters () << " "; + s << "" << tr("Floodfills") << ": " << i2p::data::netdb.GetNumFloodfills () << " "; + s << "" << tr("LeaseSets") << ": " << i2p::data::netdb.GetNumLeaseSets () << "
    \r\n"; size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); - s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; - s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
    \r\n
    \r\n"; + s << "" << tr("Client Tunnels") << ": " << std::to_string(clientTunnelCount) << " "; + s << "" << tr("Transit Tunnels") << ": " << std::to_string(transitTunnelCount) << "
    \r\n
    \r\n"; if(outputFormat==OutputFormatEnum::forWebConsole) { bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; + s << "
    Services
    " << "HTTP Proxy" << "
    " << "SOCKS Proxy" << "
    \r\n"; + s << "\r\n"; + s << "\r\n"; s << "\r\n"; s << "\r\n"; s << "\r\n"; @@ -390,7 +404,7 @@ namespace http { void ShowLocalDestinations (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "Local Destinations:
    \r\n
    \r\n"; + s << "" << tr("Local Destinations") << ":
    \r\n
    \r\n"; for (auto& it: i2p::client::context.GetDestinations ()) { auto ident = it.second->GetIdentHash (); @@ -402,7 +416,7 @@ namespace http { auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer && !(i2cpServer->GetSessions ().empty ())) { - s << "
    I2CP Local Destinations:
    \r\n
    \r\n"; + s << "
    I2CP "<< tr("Local Destinations") << ":
    \r\n
    \r\n"; for (auto& it: i2cpServer->GetSessions ()) { auto dest = it.second->GetDestination (); @@ -425,7 +439,7 @@ namespace http { if (dest->IsEncryptedLeaseSet ()) { i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); - s << "
    \r\n\r\n
    \r\n"; + s << "
    \r\n\r\n
    \r\n"; s << blinded.ToB33 () << ".b32.i2p
    \r\n"; s << "
    \r\n
    \r\n"; } @@ -434,67 +448,67 @@ namespace http { { std::string webroot; i2p::config::GetOption("http.webroot", webroot); auto base32 = dest->GetIdentHash ().ToBase32 (); - s << "
    \r\n\r\n
    \r\n" + s << "
    \r\n\r\n
    \r\n" "
    \r\n" " \r\n" " \r\n" " \r\n" - " Domain:\r\n\r\n" - " \r\n" + " " << tr("Domain") << ":\r\n\r\n" + " \r\n" "\r\nNote: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.\r\n
    \r\n
    \r\n
    \r\n"; } if(dest->GetNumRemoteLeaseSets()) { - s << "
    \r\n\r\n
    \r\n
    " << tr("Services") << "
    " << "HTTP " << tr("Proxy") << "
    " << "SOCKS " << tr("Proxy") << "
    " << "BOB" << "
    " << "SAM" << "
    " << "I2CP" << "
    "; + s << "
    \r\n\r\n
    \r\n
    AddressTypeEncType
    "; for(auto& it: dest->GetLeaseSets ()) s << "\r\n"; s << "
    "<< tr("Address") << "" << tr("Type") << "" << tr("EncType") << "
    " << it.first.ToBase32 () << "" << (int)it.second->GetStoreType () << "" << (int)it.second->GetEncryptionType () <<"
    \r\n
    \r\n
    \r\n
    \r\n"; } else - s << "LeaseSets: 0
    \r\n
    \r\n"; + s << "" << tr("LeaseSets") << ": 0
    \r\n
    \r\n"; auto pool = dest->GetTunnelPool (); if (pool) { - s << "Inbound tunnels:
    \r\n
    \r\n"; + s << "" << tr("Inbound tunnels") << ":
    \r\n
    \r\n"; for (auto & it : pool->GetInboundTunnels ()) { s << "
    "; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << "ms )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); s << "
    \r\n"; } s << "
    \r\n"; - s << "Outbound tunnels:
    \r\n
    \r\n"; + s << "" << tr("Outbound tunnels") << ":
    \r\n
    \r\n"; for (auto & it : pool->GetOutboundTunnels ()) { s << "
    "; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << "ms )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); s << "
    \r\n"; } } s << "
    \r\n"; - s << "Tags
    \r\nIncoming: " << dest->GetNumIncomingTags () << "
    \r\n"; + s << "" << tr("Tags") << "
    \r\n" << tr("Incoming") << ": " << dest->GetNumIncomingTags () << "
    \r\n"; if (!dest->GetSessions ().empty ()) { std::stringstream tmp_s; uint32_t out_tags = 0; for (const auto& it: dest->GetSessions ()) { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "" << it.second->GetNumOutgoingTags () << "\r\n"; out_tags += it.second->GetNumOutgoingTags (); } - s << "
    \r\n\r\n" - << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationAmount
    \r\n
    \r\n
    \r\n"; + s << "
    \r\n\r\n" + << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    " << tr("Destination") << "" << tr("Amount") << "
    \r\n
    \r\n
    \r\n"; } else - s << "Outgoing: 0
    \r\n"; + s << tr("Outgoing") << ": 0
    \r\n"; s << "
    \r\n"; auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags (); if (numECIESx25519Tags > 0) { - s << "ECIESx25519
    \r\nIncoming Tags: " << numECIESx25519Tags << "
    \r\n"; + s << "ECIESx25519
    \r\n" << tr("Incoming Tags") << ": " << numECIESx25519Tags << "
    \r\n"; if (!dest->GetECIESx25519Sessions ().empty ()) { std::stringstream tmp_s; uint32_t ecies_sessions = 0; @@ -502,17 +516,17 @@ namespace http { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "" << it.second->GetState () << "\r\n"; ecies_sessions++; } - s << "
    \r\n\r\n" - << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationStatus
    \r\n
    \r\n
    \r\n"; + s << "
    \r\n\r\n" + << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    " << tr("Destination") << "" << tr("Status") << "
    \r\n
    \r\n
    \r\n"; } else - s << "Tags sessions: 0
    \r\n"; + s << tr("Tags sessions") << ": 0
    \r\n"; s << "
    \r\n"; } } void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token) { - s << "Local Destination:
    \r\n
    \r\n"; + s << "" << tr("Local Destination") << ":
    \r\n
    \r\n"; i2p::data::IdentHash ident; ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); @@ -521,7 +535,7 @@ namespace http { { ShowLeaseSetDestination (s, dest, token); // show streams - s << "\r\n\r\n\r\n"; + s << "
    Streams
    \r\n\r\n\r\n"; s << ""; s << ""; @@ -543,7 +557,7 @@ namespace http { s << ""; if (it->GetRecvStreamID ()) { s << ""; + << it->GetRecvStreamID () << "&token=" << token << "\" title=\"" << tr("Close stream") << "\"> ✘ "; } else { s << "
    " << tr("Streams") << "
    StreamID"; // Stream closing button column s << "Destination" << it->GetRecvStreamID () << ""; } @@ -567,22 +581,22 @@ namespace http { auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer) { - s << "I2CP Local Destination:
    \r\n
    \r\n"; + s << "I2CP " << tr("Local Destination") << ":
    \r\n
    \r\n"; auto it = i2cpServer->GetSessions ().find (std::stoi (id)); if (it != i2cpServer->GetSessions ().end ()) ShowLeaseSetDestination (s, it->second->GetDestination (), 0); else - ShowError(s, "I2CP session not found"); + ShowError(s, tr("I2CP session not found")); } else - ShowError(s, "I2CP is not enabled"); + ShowError(s, tr("I2CP is not enabled")); } void ShowLeasesSets(std::stringstream& s) { if (i2p::data::netdb.GetNumLeaseSets ()) { - s << "LeaseSets:
    \r\n
    \r\n"; + s << "" << tr("LeaseSets") << ":
    \r\n
    \r\n"; int counter = 1; // for each lease set i2p::data::netdb.VisitLeaseSets( @@ -601,21 +615,21 @@ namespace http { s << " expired"; // additional css class for expired s << "\">\r\n"; if (!ls->IsValid()) - s << "
    !! Invalid !!
    \r\n"; + s << "
    !! " << tr("Invalid") << " !!
    \r\n"; s << "
    \r\n"; s << "\r\n
    \r\n"; - s << "Store type: " << (int)storeType << "
    \r\n"; - s << "Expires: " << ConvertTime(ls->GetExpirationTime()) << "
    \r\n"; + s << "" << tr("Store type") << ": " << (int)storeType << "
    \r\n"; + s << "" << tr("Expires") << ": " << ConvertTime(ls->GetExpirationTime()) << "
    \r\n"; if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) { // leases information is available auto leases = ls->GetNonExpiredLeases(); - s << "Non Expired Leases: " << leases.size() << "
    \r\n"; + s << "" << tr("Non Expired Leases") << ": " << leases.size() << "
    \r\n"; for ( auto & l : leases ) { - s << "Gateway: " << l->tunnelGateway.ToBase64() << "
    \r\n"; - s << "TunnelID: " << l->tunnelID << "
    \r\n"; - s << "EndDate: " << ConvertTime(l->endDate) << "
    \r\n"; + s << "" << tr("Gateway") << ": " << l->tunnelGateway.ToBase64() << "
    \r\n"; + s << "" << tr("TunnelID") << ": " << l->tunnelID << "
    \r\n"; + s << "" << tr("EndDate") << ": " << ConvertTime(l->endDate) << "
    \r\n"; } } s << "
    \r\n
    \r\n
    \r\n"; @@ -625,37 +639,37 @@ namespace http { } else if (!i2p::context.IsFloodfill ()) { - s << "LeaseSets: not floodfill.
    \r\n"; + s << "" << tr("LeaseSets") << ": " << tr("not floodfill") << ".
    \r\n"; } else { - s << "LeaseSets: 0
    \r\n"; + s << "" << tr("LeaseSets") << ": 0
    \r\n"; } } void ShowTunnels (std::stringstream& s) { - s << "Tunnels:
    \r\n"; - s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
    \r\n
    \r\n"; + s << "" << tr("Tunnels") << ":
    \r\n"; + s << "" << tr("Queue size") << ": " << i2p::tunnel::tunnels.GetQueueSize () << "
    \r\n
    \r\n"; auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); - s << "Inbound tunnels:
    \r\n
    \r\n"; + s << "" << tr("Inbound tunnels") << ":
    \r\n
    \r\n"; for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { s << "
    "; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << "ms )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); s << "
    \r\n"; } s << "
    \r\n
    \r\n"; - s << "Outbound tunnels:
    \r\n
    \r\n"; + s << "" << tr("Outbound tunnels") << ":
    \r\n
    \r\n"; for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { s << "
    "; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << "ms )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); s << "
    \r\n"; } @@ -666,30 +680,30 @@ namespace http { { std::string webroot; i2p::config::GetOption("http.webroot", webroot); /* commands */ - s << "Router Commands
    \r\n
    \r\n
    \r\n"; - s << " Run peer test\r\n"; + s << "" << tr("Router commands") << "
    \r\n
    \r\n
    \r\n"; + s << " " << tr("Run peer test") << "\r\n"; //s << " Reload config
    \r\n"; if (i2p::context.AcceptsTunnels ()) - s << " Decline transit tunnels\r\n"; + s << " " << tr("Decline transit tunnels") << "\r\n"; else - s << " Accept transit tunnels\r\n"; + s << " " << tr("Accept transit tunnels") << "\r\n"; #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) if (Daemon.gracefulShutdownInterval) - s << " Cancel graceful shutdown\r\n"; + s << " " << tr("Cancel graceful shutdown") << "\r\n"; else - s << " Start graceful shutdown
    \r\n"; + s << " " << tr("Start graceful shutdown") << "
    \r\n"; #elif defined(WIN32_APP) if (i2p::util::DaemonWin32::Instance().isGraceful) - s << " Cancel graceful shutdown\r\n"; + s << " " << tr("Cancel graceful shutdown") << "\r\n"; else - s << " Graceful shutdown\r\n"; + s << " " << tr("Start graceful shutdown") << "\r\n"; #endif - s << " Force shutdown\r\n"; + s << " " << tr("Force shutdown") << "\r\n"; s << "
    "; - s << "
    \r\nNote: any action done here are not persistent and not changes your config files.\r\n
    \r\n"; + s << "
    \r\n" << tr("Note: any action done here are not persistent and not changes your config files.") << "\r\n
    \r\n"; - s << "Logging level
    \r\n"; + s << "" << tr("Logging level") << "
    \r\n"; s << " none \r\n"; s << " error \r\n"; s << " warn \r\n"; @@ -697,12 +711,12 @@ namespace http { s << " debug
    \r\n
    \r\n"; uint16_t maxTunnels = GetMaxNumTransitTunnels (); - s << "Transit tunnels limit
    \r\n"; + s << "" << tr("Transit tunnels limit") << "
    \r\n"; s << "
    \r\n"; s << " \r\n"; s << " \r\n"; s << " \r\n"; - s << " \r\n"; + s << " \r\n"; s << "
    \r\n
    \r\n"; } @@ -710,7 +724,7 @@ namespace http { { if(i2p::tunnel::tunnels.CountTransitTunnels()) { - s << "Transit tunnels:
    \r\n
    \r\n"; + s << "" << tr("Transit tunnels") << ":
    \r\n
    \r\n"; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { s << "
    \r\n"; @@ -726,7 +740,7 @@ namespace http { } else { - s << "Transit tunnels: no transit tunnels currently built.
    \r\n"; + s << "" << tr("Transit tunnels") << ": " << tr("no transit tunnels currently built") << ".
    \r\n"; } } @@ -775,7 +789,7 @@ namespace http { void ShowTransports (std::stringstream& s) { - s << "Transports:
    \r\n"; + s << "" << tr("Transports") << ":
    \r\n"; auto ntcp2Server = i2p::transport::transports.GetNTCP2Server (); if (ntcp2Server) { @@ -831,13 +845,13 @@ namespace http { auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { - ShowError(s, "SAM disabled"); + ShowError(s, tr("SAM disabled")); return; } if(sam->GetSessions ().size ()) { - s << "SAM Sessions:
    \r\n
    \r\n"; + s << "" << tr("SAM sessions") << ":
    \r\n
    \r\n"; for (auto& it: sam->GetSessions ()) { auto& name = it.second->GetLocalDestination ()->GetNickname (); @@ -847,30 +861,30 @@ namespace http { s << "
    \r\n"; } else - s << "SAM Sessions: no sessions currently running.
    \r\n"; + s << "" << tr("SAM sessions") << ": " << tr("no sessions currently running") << ".
    \r\n"; } void ShowSAMSession (std::stringstream& s, const std::string& id) { auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { - ShowError(s, "SAM disabled"); + ShowError(s, tr("SAM disabled")); return; } auto session = sam->FindSession (id); if (!session) { - ShowError(s, "SAM session not found"); + ShowError(s, tr("SAM session not found")); return; } std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "SAM Session:
    \r\n
    \r\n"; + s << "" << tr("SAM Session") << ":
    \r\n
    \r\n"; auto& ident = session->GetLocalDestination ()->GetIdentHash(); s << "\r\n"; s << "
    \r\n"; - s << "Streams:
    \r\n
    \r\n"; + s << "" << tr("Streams") << ":
    \r\n
    \r\n"; for (const auto& it: sam->ListSockets(id)) { s << "
    "; @@ -879,7 +893,7 @@ namespace http { case i2p::client::eSAMSocketTypeSession : s << "session"; break; case i2p::client::eSAMSocketTypeStream : s << "stream"; break; case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break; - case i2p::client::eSAMSocketTypeForward : s << "forward"; break; + case i2p::client::eSAMSocketTypeForward : s << "forward"; break; default: s << "unknown"; break; } s << " [" << it->GetSocket ().remote_endpoint() << "]"; @@ -891,7 +905,7 @@ namespace http { void ShowI2PTunnels (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "Client Tunnels:
    \r\n
    \r\n"; + s << "" << tr("Client Tunnels") << ":
    \r\n
    \r\n"; for (auto& it: i2p::client::context.GetClientTunnels ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); @@ -905,7 +919,7 @@ namespace http { { auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); s << "
    "; - s << "HTTP Proxy" << " ⇐ "; + s << "HTTP " << tr("Proxy") << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
    \r\n"<< std::endl; } @@ -914,7 +928,7 @@ namespace http { { auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); s << "
    "; - s << "SOCKS Proxy" << " ⇐ "; + s << "SOCKS " << tr("Proxy") << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
    \r\n"<< std::endl; } @@ -922,7 +936,7 @@ namespace http { auto& serverTunnels = i2p::client::context.GetServerTunnels (); if (!serverTunnels.empty ()) { - s << "
    \r\nServer Tunnels:
    \r\n
    \r\n"; + s << "
    \r\n" << tr("Server Tunnels") << ":
    \r\n
    \r\n"; for (auto& it: serverTunnels) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); @@ -938,7 +952,7 @@ namespace http { auto& clientForwards = i2p::client::context.GetClientForwards (); if (!clientForwards.empty ()) { - s << "
    \r\nClient Forwards:
    \r\n
    \r\n"; + s << "
    \r\n" << tr("Client Forwards") << ":
    \r\n
    \r\n"; for (auto& it: clientForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); @@ -952,7 +966,7 @@ namespace http { auto& serverForwards = i2p::client::context.GetServerForwards (); if (!serverForwards.empty ()) { - s << "
    \r\nServer Forwards:
    \r\n
    \r\n"; + s << "
    \r\n" << tr("Server Forwards") << ":
    \r\n
    \r\n"; for (auto& it: serverForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); @@ -1084,7 +1098,7 @@ namespace http { return; } } - // Html5 head start + // HTML head start ShowPageHead (s); if (req.uri.find("page=") != std::string::npos) { HandlePage (req, res, s); @@ -1158,7 +1172,7 @@ namespace http { ShowLeasesSets(s); else { res.code = 400; - ShowError(s, "Unknown page: " + page); + ShowError(s, tr("Unknown page") + ": " + page); return; } } @@ -1177,7 +1191,7 @@ namespace http { if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) { - ShowError(s, "Invalid token"); + ShowError(s, tr("Invalid token")); return; } @@ -1235,18 +1249,18 @@ namespace http { if (dest) { if(dest->DeleteStream (streamID)) - s << "SUCCESS: Stream closed
    \r\n
    \r\n"; + s << "" << tr("SUCCESS") << ": " << tr("Stream closed") << "
    \r\n
    \r\n"; else - s << "ERROR: Stream not found or already was closed
    \r\n
    \r\n"; + s << "" << tr("ERROR") << ": " << tr("Stream not found or already was closed") << "
    \r\n
    \r\n"; } else - s << "ERROR: Destination not found
    \r\n
    \r\n"; + s << "" << tr("ERROR") << ": " << tr("Destination not found") << "
    \r\n
    \r\n"; } else - s << "ERROR: StreamID can be null
    \r\n
    \r\n"; + s << "" << tr("ERROR") << ": " << tr("StreamID can't be null") << "
    \r\n
    \r\n"; - s << "Return to destination page
    \r\n"; - s << "

    You will be redirected back in 5 seconds"; + s << "" << tr("Return to destination page") << "
    \r\n"; + s << "

    " << tr("You will be redirected back in 5 seconds") << ""; redirect = "5; url=" + webroot + "?page=local_destination&b32=" + b32; res.add_header("Refresh", redirect.c_str()); return; @@ -1257,9 +1271,9 @@ namespace http { if (limit > 0 && limit <= 65535) SetMaxNumTransitTunnels (limit); else { - s << "ERROR: Transit tunnels count must not exceed 65535\r\n
    \r\n
    \r\n"; - s << "Back to commands list\r\n
    \r\n"; - s << "

    You will be redirected back in 5 seconds"; + s << "" << tr("ERROR") << ": " << tr("Transit tunnels count must not exceed 65535") << "\r\n
    \r\n
    \r\n"; + s << "" << tr("Back to commands list") << "\r\n
    \r\n"; + s << "

    " << tr("You will be redirected back in 5 seconds") << ""; res.add_header("Refresh", redirect.c_str()); return; } @@ -1292,37 +1306,37 @@ namespace http { auto len = i2p::data::ByteStreamToBase64 (signature, signatureLen, sig, signatureLen*2); sig[len] = 0; out << "#!sig=" << sig; - s << "SUCCESS:
    \r\n

    \r\n" + s << "" << tr("SUCCESS") << ":
    \r\n\r\n" "\r\n
    \r\n
    \r\n" - "Register at reg.i2p:\r\n
    \r\n" - "Description:\r\n\r\n" - "\r\n" + "" << tr("Register at reg.i2p") << ":\r\n
    \r\n" + "" << tr("Description") << ":\r\n\r\n" + "\r\n" "
    \r\n
    \r\n"; delete[] signature; delete[] sig; } else - s << "ERROR: Domain can't end with .b32.i2p\r\n
    \r\n
    \r\n"; + s << "" << tr("ERROR") << ": " << tr("Domain can't end with .b32.i2p") << "\r\n
    \r\n
    \r\n"; } else - s << "ERROR: Domain must end with .i2p\r\n
    \r\n
    \r\n"; + s << "" << tr("ERROR") << ": " << tr("Domain must end with .i2p") << "\r\n
    \r\n
    \r\n"; } else - s << "ERROR: Such destination is not found\r\n
    \r\n
    \r\n"; + s << "" << tr("ERROR") << ": " << tr("Such destination is not found") << "\r\n
    \r\n
    \r\n"; - s << "Return to destination page\r\n"; + s << "" << tr("Return to destination page") << "\r\n"; return; } else { res.code = 400; - ShowError(s, "Unknown command: " + cmd); + ShowError(s, tr("Unknown command") + ": " + cmd); return; } - s << "SUCCESS: Command accepted

    \r\n"; - s << "Back to commands list
    \r\n"; - s << "

    You will be redirected in 5 seconds"; + s << "" << tr("SUCCESS") << ": " << tr("Command accepted") << "

    \r\n"; + s << "" << tr("Back to commands list") << "
    \r\n"; + s << "

    " << tr("You will be redirected in 5 seconds") << ""; res.add_header("Refresh", redirect.c_str()); } diff --git a/i18n/russian.cpp b/i18n/russian.cpp index feb6fc63..71f27613 100644 --- a/i18n/russian.cpp +++ b/i18n/russian.cpp @@ -52,16 +52,170 @@ namespace russian { // language {"http out proxy not implemented", "поддержка внешнего HTTP прокÑи Ñервера не реализована"}, {"cannot connect to upstream http proxy", "не удалоÑÑŒ подключитьÑÑ Ðº вышеÑтоÑщему HTTP прокÑи Ñерверу"}, {"Host is down", "ÐÐ´Ñ€ÐµÑ Ð½ÐµÐ´Ð¾Ñтупен"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Ðе удалоÑÑŒ уÑтановить Ñоединение к запрошенному адреÑу, возможно он не в Ñети. Попробуйте повторить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð¿Ð¾Ð·Ð¶Ðµ."}, + {"Can't create connection to requested host, it may be down. Please try again later.", + "Ðе удалоÑÑŒ уÑтановить Ñоединение к запрошенному адреÑу, возможно он не в Ñети. Попробуйте повторить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð¿Ð¾Ð·Ð¶Ðµ."}, + + // Webconsole // + // cssStyles + {"Disabled", "Выключено"}, + {"Enabled", "Включено"}, + // ShowTraffic + {"KiB", "КиБ"}, + {"MiB", "МиБ"}, + {"GiB", "ГиБ"}, + // ShowTunnelDetails + {"building", "ÑтроитÑÑ"}, + {"failed", "неудачный"}, + {"expiring", "заканчиваетÑÑ"}, + {"established", "работает"}, + {"exploratory", "иÑÑледовательÑкий"}, + {"unknown", "неизвеÑтно"}, + {"i2pd webconsole", "Веб-конÑоль i2pd"}, + // ShowPageHead + {"Main page", "ГлавнаÑ"}, + {"Router commands", "Команды роутера"}, + {"Local destinations", "Локальные назнач."}, + {"LeaseSets", "ЛизÑеты"}, + {"Tunnels", "Туннели"}, + {"Transit tunnels", "Транзит. туннели"}, + {"Transports", "ТранÑпорты"}, + {"I2P tunnels", "I2P туннели"}, + {"SAM sessions", "SAM ÑеÑÑии"}, + // Network Status + {"OK", "OK"}, + {"Testing", "ТеÑтирование"}, + {"Firewalled", "Файрвол"}, + {"Unknown", "ÐеизвеÑтно"}, + {"Proxy", "ПрокÑи"}, + {"Mesh", "MESH-Ñеть"}, + {"Error", "Ошибка"}, + {"Clock skew", "Ðе точное времÑ"}, + {"Offline", "Оффлайн"}, + {"Symmetric NAT", "Симметричный NAT"}, + // Status + {"Uptime", "Ð’ Ñети"}, + {"Network status", "Сетевой ÑтатуÑ"}, + {"Network status v6", "Сетевой ÑÑ‚Ð°Ñ‚ÑƒÑ v6"}, + {"Stopping in", "ОÑтановка через"}, + {"Family", "СемейÑтво"}, + {"Tunnel creation success rate", "УÑпешно поÑтроенных туннелей"}, + {"Received", "Получено"}, + {"Sent", "Отправлено"}, + {"Transit", "Транзит"}, + {"KiB/s", "КиБ/Ñ"}, + {"Data path", "Путь к данным"}, + {"Hidden content. Press on text to see.", "Скрытый контент. Ðажмите на текÑÑ‚ чтобы отобразить."}, + {"Router Ident", "Идентификатор роутера"}, + {"Router Family", "СемейÑтво роутера"}, + {"Router Caps", "Флаги роутера"}, + {"Version", "ВерÑиÑ"}, + {"Our external address", "Ðаш внешний адреÑ"}, + {"supported", "поддерживаетÑÑ"}, + {"Routers", "Роутеры"}, + {"Floodfills", "Флудфилы"}, + {"LeaseSets", "ЛизÑеты"}, + {"Client Tunnels", "КлиентÑкие туннели"}, + {"Transit Tunnels", "Транзитные туннели"}, + {"Services", "СервиÑÑ‹"}, + // ShowLocalDestinations + {"Local Destinations", "Локальные назначениÑ"}, + // ShowLeaseSetDestination + {"Encrypted B33 address", "Шифрованные B33 адреÑа"}, + {"Address registration line", "Строка региÑтрации адреÑа"}, + {"Domain", "Домен"}, + {"Generate", "Сгенерировать"}, + {"Address", "ÐдреÑ"}, + {"Type", "Тип"}, + {"EncType", "ТипШифр"}, + {"Inbound tunnels", "ВходÑщие туннели"}, + {"Outbound tunnels", "ИÑходÑщие туннели"}, + {"ms", "мÑ"}, // milliseconds + {"Tags", "Теги"}, + {"Incoming", "ВходÑщие"}, + {"Outgoing", "ИÑходÑщие"}, + {"Destination", "Ðазначение"}, + {"Amount", "КоличеÑтво"}, + {"Incoming Tags", "ВходÑщие Теги"}, + {"Tags sessions", "СеÑÑии Тегов"}, + {"Status", "СтатуÑ"}, + // ShowLocalDestination + {"Local Destination", "Локальное назначение"}, + {"Streams", "Стримы"}, + {"Close stream", "Закрыть Ñтрим"}, + // ShowI2CPLocalDestination + {"I2CP session not found", "I2CP ÑеÑÑÐ¸Ñ Ð½Ðµ найдена"}, + {"I2CP is not enabled", "I2CP не включен"}, + // ShowLeasesSets + {"Invalid", "Ðекорректный"}, + {"Store type", "Тип хранилища"}, + {"Expires", "ИÑтекает"}, + {"Non Expired Leases", "Ðе иÑтекшие Lease-Ñ‹"}, + {"Gateway", "Шлюз"}, + {"TunnelID", "ID туннелÑ"}, + {"EndDate", "ЗаканчиваетÑÑ"}, + {"not floodfill", "не флудфил"}, + // ShowTunnels + {"Queue size", "Размер очереди"}, + // ShowCommands + {"Run peer test", "ЗапуÑтить теÑтирование"}, + {"Decline transit tunnels", "ОтклонÑть транзитные туннели"}, + {"Accept transit tunnels", "Принимать транзитные туннели"}, + {"Cancel graceful shutdown", "Отменить плавную оÑтановку"}, + {"Start graceful shutdown", "ЗапуÑтить плавную оÑтановку"}, + {"Force shutdown", "ÐŸÑ€Ð¸Ð½ÑƒÐ´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¾Ñтановка"}, + {"Note: any action done here are not persistent and not changes your config files.", + "Примечание: любое дейÑтвие произведенное здеÑÑŒ не ÑвлÑетÑÑ Ð¿Ð¾ÑтоÑнным и не изменÑет ваши конфигурационные файлы."}, + {"Logging level", "Уровень логированиÑ"}, + {"Transit tunnels limit", "Лимит транзитных туннелей"}, + {"Change", "Изменить"}, + // ShowTransitTunnels + {"no transit tunnels currently built", "нет поÑтроенных транзитных туннелей"}, + // ShowSAMSessions/ShowSAMSession + {"SAM disabled", "SAM выключен"}, + {"SAM session not found", "SAM ÑеÑÑÐ¸Ñ Ð½Ðµ найдена"}, + {"no sessions currently running", "нет запущенных ÑеÑÑий"}, + {"SAM Session", "SAM ÑеÑÑиÑ"}, + // ShowI2PTunnels + {"Server Tunnels", "Серверные туннели"}, + {"Client Forwards", "КлиентÑкие переадреÑации"}, + {"Server Forwards", "Серверные переадреÑации"}, + // HandlePage + {"Unknown page", "ÐеизвеÑÑ‚Ð½Ð°Ñ Ñтраница"}, + // HandleCommand, ShowError + {"Invalid token", "Ðеверный токен"}, + {"SUCCESS", "УСПЕШÐО"}, + {"ERROR", "ОШИБКÐ"}, + {"Unknown command", "ÐеизвеÑÑ‚Ð½Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°"}, + {"Command accepted", "Команда принÑта"}, + {"Back to commands list", "ВернутьÑÑ Ðº ÑпиÑку команд"}, + {"You will be redirected in 5 seconds", "Ð’Ñ‹ будете переадреÑованы через 5 Ñекунд"}, + // HTTP_COMMAND_KILLSTREAM + {"Stream closed", "Стрим закрыт"}, + {"Stream not found or already was closed", "Стрим не найден или уже закрыт"}, + {"Destination not found", "Точка Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð½Ðµ найдена"}, + {"StreamID can't be null", "StreamID не может быть пуÑтым"}, + {"Return to destination page", "ВернутьÑÑ Ð½Ð° Ñтраницу точки назначениÑ"}, + {"You will be redirected back in 5 seconds", "Ð’Ñ‹ будете переадреÑованы назад через 5 Ñекунд"}, + // HTTP_COMMAND_LIMITTRANSIT + {"Transit tunnels count must not exceed 65535", "ЧиÑло транзитных туннелей не должно превышать 65535"}, + // HTTP_COMMAND_GET_REG_STRING + {"Register at reg.i2p", "ЗарегиÑтрировать на reg.i2p"}, + {"Description", "ОпиÑание"}, + {"A bit information about service on domain", "Ðемного информации о ÑервиÑе на домене"}, + {"Submit", "Отправить"}, + {"Domain can't end with .b32.i2p", "Домен не может заканчиватьÑÑ Ð½Ð° .b32.i2p"}, + {"Domain must end with .i2p", "Домен должен заканчиватьÑÑ Ð½Ð° .i2p"}, + {"Such destination is not found", "Ð¢Ð°ÐºÐ°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð½Ðµ найдена"}, {"", ""}, }; static std::map> plurals { + // ShowUptime {"days", {"день", "днÑ", "дней"}}, {"hours", {"чаÑ", "чаÑа", "чаÑов"}}, - {"minutes", {"минута", "минуты", "минут"}}, - {"seconds", {"Ñекунда", "Ñекунды", "Ñекунд"}}, + {"minutes", {"минуту", "минуты", "минут"}}, + {"seconds", {"Ñекунду", "Ñекунды", "Ñекунд"}}, {"", {"", ""}}, }; From a4b84517dc66267354a962ca7f1487b8ded14cf2 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 23 May 2021 10:56:20 +0300 Subject: [PATCH 4203/6300] [i18n] rename Russian translation, fix typo Signed-off-by: R4SAS --- i18n/{russian.cpp => Russian.cpp} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename i18n/{russian.cpp => Russian.cpp} (99%) diff --git a/i18n/russian.cpp b/i18n/Russian.cpp similarity index 99% rename from i18n/russian.cpp rename to i18n/Russian.cpp index 71f27613..d208e18f 100644 --- a/i18n/russian.cpp +++ b/i18n/Russian.cpp @@ -66,7 +66,7 @@ namespace russian { // language // ShowTunnelDetails {"building", "ÑтроитÑÑ"}, {"failed", "неудачный"}, - {"expiring", "заканчиваетÑÑ"}, + {"expiring", "иÑтекает"}, {"established", "работает"}, {"exploratory", "иÑÑледовательÑкий"}, {"unknown", "неизвеÑтно"}, From 2db035d23cff8ca17b6ec78d4e8e9a3295968fa7 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 23 May 2021 13:16:52 +0300 Subject: [PATCH 4204/6300] [i18n] fix addresshelper Signed-off-by: R4SAS --- libi2pd_client/HTTPProxy.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index aff165b0..d8b84e82 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -217,7 +217,8 @@ namespace proxy { b64 = i2p::http::UrlDecode(value); // if we need update exists, request formed with update param if (params["update"] == "true") { len += std::strlen("&update=true"); confirm = true; } - url.query.replace(pos - 1, len + 1, ""); // +-1 for taking ? and & before parameter + if (pos != 0 && url.query[pos-1] == '&') { pos--; len++; } // if helper is not only one query option + url.query.replace(pos, len, ""); return true; } From 919bf4e144a838a73e4561119fe0b5bc7723e571 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 23 May 2021 15:39:29 +0300 Subject: [PATCH 4205/6300] [i18n] add cmake build Signed-off-by: R4SAS --- build/CMakeLists.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 6f9fbae6..f6d69b04 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -33,10 +33,12 @@ target_architecture(ARCHITECTURE) set(LIBI2PD_SRC_DIR ../libi2pd) set(LIBI2PD_CLIENT_SRC_DIR ../libi2pd_client) +set(LANG_SRC_DIR ../i18n) set(DAEMON_SRC_DIR ../daemon) include_directories(${LIBI2PD_SRC_DIR}) include_directories(${LIBI2PD_CLIENT_SRC_DIR}) +include_directories(${LANG_SRC_DIR}) include_directories(${DAEMON_SRC_DIR}) set(LIBI2PD_SRC @@ -126,6 +128,11 @@ if(WITH_LIBRARY) COMPONENT Libraries) endif() +set(LANG_SRC + "${LANG_SRC_DIR}/English.cpp" + "${LANG_SRC_DIR}/Russian.cpp" +) + set(DAEMON_SRC "${DAEMON_SRC_DIR}/Daemon.cpp" "${DAEMON_SRC_DIR}/HTTPServer.cpp" @@ -321,7 +328,7 @@ message(STATUS "---------------------------------------") include(GNUInstallDirs) if(WITH_BINARY) - add_executable("${PROJECT_NAME}" ${DAEMON_SRC}) + add_executable("${PROJECT_NAME}" ${LANG_SRC} ${DAEMON_SRC}) if(WITH_STATIC) set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static") From 5207dd4c9eb9178cd0e0867693a359cc91712e3d Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 23 May 2021 15:43:04 +0300 Subject: [PATCH 4206/6300] [gha] update freebsd action --- .github/workflows/build-freebsd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-freebsd.yml b/.github/workflows/build-freebsd.yml index 4e31bed7..c6e0addf 100644 --- a/.github/workflows/build-freebsd.yml +++ b/.github/workflows/build-freebsd.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - name: Test in FreeBSD id: test - uses: vmactions/freebsd-vm@v0.1.2 + uses: vmactions/freebsd-vm@v0.1.4 with: usesh: true prepare: pkg install -y devel/cmake devel/gmake devel/boost-libs security/openssl net/miniupnpc From 69a0fe3040325cbe8c554609d9ec0a2fd617bf82 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 23 May 2021 08:52:27 -0400 Subject: [PATCH 4207/6300] pass arg as reference --- i18n/I18N.h | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/i18n/I18N.h b/i18n/I18N.h index cb3e5c1c..8ce2aa84 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -15,24 +15,30 @@ namespace i2p { namespace i18n { - inline std::string translate (std::string arg) + inline std::string translate (const std::string& arg) { switch (i2p::context.GetLanguage ()) { - case eEnglish: return i2p::i18n::english::GetString (arg); - case eRussian: return i2p::i18n::russian::GetString (arg); - default: return arg; + case eEnglish: + return i2p::i18n::english::GetString (arg); + case eRussian: + return i2p::i18n::russian::GetString (arg); + default: + return arg; } } template - std::string translate (std::string arg, inttype&& n) + std::string translate (const std::string& arg, inttype&& n) { switch (i2p::context.GetLanguage ()) { - case eEnglish: return i2p::i18n::english::GetPlural (arg, (int) n); - case eRussian: return i2p::i18n::russian::GetPlural (arg, (int) n); - default: return arg; + case eEnglish: + return i2p::i18n::english::GetPlural (arg, (int) n); + case eRussian: + return i2p::i18n::russian::GetPlural (arg, (int) n); + default: + return arg; } } From b676d7034f2e784c06c7ffa45f2527545b5bf971 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 23 May 2021 16:30:42 +0300 Subject: [PATCH 4208/6300] [i18n] update translation Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 2 +- i18n/Russian.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 22cefedd..e184dd06 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -455,7 +455,7 @@ namespace http { " \r\n" " " << tr("Domain") << ":\r\n\r\n" " \r\n" - "\r\nNote: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.\r\n

    \r\n
    \r\n
    \r\n"; + "\r\n" << tr("Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.") << "\r\n
    \r\n
    \r\n
    \r\n"; } if(dest->GetNumRemoteLeaseSets()) diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index d208e18f..033ee33d 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -124,6 +124,8 @@ namespace russian { // language {"Address registration line", "Строка региÑтрации адреÑа"}, {"Domain", "Домен"}, {"Generate", "Сгенерировать"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", + "Примечание: Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð½Ð°Ñ Ñтрока может быть иÑпользована только Ð´Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации доменов второго уровнÑ. Ð”Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации поддоменов иÑпользуйте i2pd-tools."}, {"Address", "ÐдреÑ"}, {"Type", "Тип"}, {"EncType", "ТипШифр"}, From 585116a51f9557038f49630ea4b41d25b47e010c Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 23 May 2021 14:20:23 -0400 Subject: [PATCH 4209/6300] XMR added --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d21df6e7..1aa96fb0 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ ETH: 0x9e5bac70d20d1079ceaa111127f4fb3bccce379d DASH: Xw8YUrQpYzP9tZBmbjqxS3M97Q7v3vJKUF ZEC: t1cTckLuXsr1dwVrK4NDzfhehss4NvMadAJ GST: GbD2JSQHBHCKLa9WTHmigJRpyFgmBj4woG +XMR: 497pJc7X4xqKvcLBLpSUtRgWqMMyo24u4btCos3cak6gbMkpobgSU6492ztUcUBghyeHpYeczB55s38NpuHoH5WGNSPDRMH License ------- From d06924b33940170bc6209f5c51b8b38f35e35e2a Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 23 May 2021 14:28:10 -0400 Subject: [PATCH 4210/6300] LeaseSet type 3 by default --- libi2pd/Destination.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 6e149cf5..40c9e9ab 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -62,7 +62,7 @@ namespace client const char I2CP_PARAM_INBOUND_NICKNAME[] = "inbound.nickname"; const char I2CP_PARAM_OUTBOUND_NICKNAME[] = "outbound.nickname"; const char I2CP_PARAM_LEASESET_TYPE[] = "i2cp.leaseSetType"; - const int DEFAULT_LEASESET_TYPE = 1; + const int DEFAULT_LEASESET_TYPE = 3; const char I2CP_PARAM_LEASESET_ENCRYPTION_TYPE[] = "i2cp.leaseSetEncType"; const char I2CP_PARAM_LEASESET_PRIV_KEY[] = "i2cp.leaseSetPrivKey"; // PSK decryption key, base64 const char I2CP_PARAM_LEASESET_AUTH_TYPE[] = "i2cp.leaseSetAuthType"; From 08fafe267add1de917b2cca1dba1de3efb7e2c0c Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 23 May 2021 17:27:14 -0400 Subject: [PATCH 4211/6300] rekey all routers to ECIES --- libi2pd/RouterContext.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 0cfcb18a..78f63137 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -736,14 +736,8 @@ namespace i2p } } std::shared_ptr oldIdentity; - bool rekey = m_Keys.GetPublic ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; - if (!rekey && m_Keys.GetPublic ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) - { - // rekey routers with bandwidth = L (or default) this time - bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); - if (!isFloodfill) rekey = true; - } - if (rekey) + if (m_Keys.GetPublic ()->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1 || + m_Keys.GetPublic ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) { // update keys LogPrint (eLogInfo, "Router: router keys are obsolete. Creating new"); From 1a4250d8cc60afb55e06b5244ca1335c5ef4d20b Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 25 May 2021 00:22:28 +0300 Subject: [PATCH 4212/6300] [i18n] update russian translation Signed-off-by: R4SAS --- i18n/Russian.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index 033ee33d..489bee7b 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -179,8 +179,8 @@ namespace russian { // language {"SAM Session", "SAM ÑеÑÑиÑ"}, // ShowI2PTunnels {"Server Tunnels", "Серверные туннели"}, - {"Client Forwards", "КлиентÑкие переадреÑации"}, - {"Server Forwards", "Серверные переадреÑации"}, + {"Client Forwards", "КлиентÑкие перенаправлениÑ"}, + {"Server Forwards", "Серверные перенаправлениÑ"}, // HandlePage {"Unknown page", "ÐеизвеÑÑ‚Ð½Ð°Ñ Ñтраница"}, // HandleCommand, ShowError From 779f2fa451ac759d9de88820898025ce7c71ec30 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 25 May 2021 22:03:29 +0300 Subject: [PATCH 4213/6300] [i18n] rework localization system Signed-off-by: R4SAS --- daemon/Daemon.cpp | 6 ++-- i18n/English.cpp | 52 +++++++++++++++------------------- i18n/I18N.h | 38 ++++++++++--------------- i18n/I18N_langs.h | 59 +++++++++++++++++++++++++++++---------- i18n/Russian.cpp | 46 +++++++++++++----------------- libi2pd/RouterContext.cpp | 7 +---- libi2pd/RouterContext.h | 12 ++++---- 7 files changed, 111 insertions(+), 109 deletions(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index ec62f5d6..f7f2ef2f 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -32,6 +32,7 @@ #include "UPnP.h" #include "Timestamp.h" #include "util.h" +#include "I18N.h" namespace i2p { @@ -345,10 +346,7 @@ namespace util } std::string httpLang; i2p::config::GetOption("http.lang", httpLang); - if (!httpLang.compare("russian")) - i2p::context.SetLanguage (eRussian); - else - i2p::context.SetLanguage (eEnglish); + i2p::i18n::SetLanguage(httpLang); return true; } diff --git a/i18n/English.cpp b/i18n/English.cpp index 8664a0f0..8b13279a 100644 --- a/i18n/English.cpp +++ b/i18n/English.cpp @@ -1,23 +1,34 @@ +/* +* Copyright (c) 2021, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include +#include +#include "I18N.h" -// Russian localization file - -namespace i2p { -namespace i18n { -namespace english { // language +// English localization file +namespace i2p +{ +namespace i18n +{ +namespace english // language +{ // See for language plural forms here: // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - int plural (int n) { + static int plural (int n) { return n != 1 ? 1 : 0; } static std::map strings { - {"Enabled", "Enabled"}, - {"Disabled", "Disabled"} + {"", ""}, }; static std::map> plurals @@ -25,30 +36,13 @@ namespace english { // language {"days", {"day", "days"}}, {"hours", {"hour", "hours"}}, {"minutes", {"minute", "minutes"}}, - {"seconds", {"second", "seconds"}} + {"seconds", {"second", "seconds"}}, + {"", {"", ""}}, }; - std::string GetString (std::string arg) + std::shared_ptr GetLocale() { - auto it = strings.find(arg); - if (it == strings.end()) - { - return arg; - } else { - return it->second; - } - } - - std::string GetPlural (std::string arg, int n) - { - auto it = plurals.find(arg); - if (it == plurals.end()) - { - return arg; - } else { - int form = plural(n); - return it->second[form]; - } + return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/I18N.h b/i18n/I18N.h index 8ce2aa84..ccb8f413 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -11,37 +11,27 @@ #include "RouterContext.h" - -namespace i2p { -namespace i18n { +namespace i2p +{ +namespace i18n +{ + inline void SetLanguage(const std::string &lang) + { + if (!lang.compare("russian")) + i2p::context.SetLanguage (i2p::i18n::russian::GetLocale()); + else + i2p::context.SetLanguage (i2p::i18n::english::GetLocale()); + } inline std::string translate (const std::string& arg) { - switch (i2p::context.GetLanguage ()) - { - case eEnglish: - return i2p::i18n::english::GetString (arg); - case eRussian: - return i2p::i18n::russian::GetString (arg); - default: - return arg; - } + return i2p::context.GetLanguage ()->GetString (arg); } - template - std::string translate (const std::string& arg, inttype&& n) + inline std::string translate (const std::string& arg, const int& n) { - switch (i2p::context.GetLanguage ()) - { - case eEnglish: - return i2p::i18n::english::GetPlural (arg, (int) n); - case eRussian: - return i2p::i18n::russian::GetPlural (arg, (int) n); - default: - return arg; - } + return i2p::context.GetLanguage ()->GetPlural (arg, n); } - } // i18n } // i2p diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index 24c683b4..07bdc087 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -9,24 +9,55 @@ #ifndef __I18N_LANGS_H__ #define __I18N_LANGS_H__ -namespace i2p { +namespace i2p +{ +namespace i18n +{ + class Locale + { + public: + Locale ( + const std::map& strings, + const std::map>& plurals, + std::function formula + ): m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; -enum Lang { - eEnglish = 0, - eRussian -}; + std::string GetString (const std::string& arg) const + { + const auto it = m_Strings.find(arg); + if (it == m_Strings.end()) + { + return arg; + } + else + { + return it->second; + } + } -namespace i18n { + std::string GetPlural (const std::string& arg, const int& n) const + { + const auto it = m_Plurals.find(arg); + if (it == m_Plurals.end()) + { + return arg; + } + else + { + int form = m_Formula(n); + return it->second[form]; + } + } - namespace english { - std::string GetString (std::string arg); - std::string GetPlural (std::string arg, int n); - } + private: + const std::map m_Strings; + const std::map> m_Plurals; + std::function m_Formula; + }; - namespace russian { - std::string GetString (std::string arg); - std::string GetPlural (std::string arg, int n); - } + // Add localization here with language name as namespace + namespace english { std::shared_ptr GetLocale (); } + namespace russian { std::shared_ptr GetLocale (); } } // i18n } // i2p diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index 489bee7b..1f2c25da 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -1,16 +1,28 @@ +/* +* Copyright (c) 2021, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include +#include +#include "I18N.h" // Russian localization file -namespace i2p { -namespace i18n { -namespace russian { // language - +namespace i2p +{ +namespace i18n +{ +namespace russian // language +{ // See for language plural forms here: // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html - int plural (int n) { + static int plural (int n) { return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; } @@ -218,30 +230,12 @@ namespace russian { // language {"hours", {"чаÑ", "чаÑа", "чаÑов"}}, {"minutes", {"минуту", "минуты", "минут"}}, {"seconds", {"Ñекунду", "Ñекунды", "Ñекунд"}}, - {"", {"", ""}}, + {"", {"", "", ""}}, }; - std::string GetString (std::string arg) + std::shared_ptr GetLocale() { - auto it = strings.find(arg); - if (it == strings.end()) - { - return arg; - } else { - return it->second; - } - } - - std::string GetPlural (std::string arg, int n) - { - auto it = plurals.find(arg); - if (it == plurals.end()) - { - return arg; - } else { - int form = plural(n); - return it->second[form]; - } + return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 78f63137..664b457c 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -29,7 +29,7 @@ namespace i2p RouterContext::RouterContext (): m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false), m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_StatusV6 (eRouterStatusUnknown), - m_Error (eRouterErrorNone), m_NetID (I2PD_NET_ID), m_Language (eEnglish) + m_Error (eRouterErrorNone), m_NetID (I2PD_NET_ID) { } @@ -910,11 +910,6 @@ namespace i2p } } - void RouterContext::SetLanguage (Lang language) - { - m_Language = language; - } - i2p::crypto::X25519Keys& RouterContext::GetStaticKeys () { if (!m_StaticKeys) diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 1cb77a74..ee34c2d8 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -99,7 +99,7 @@ namespace garlic bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data); void UpdatePort (int port); // called from Daemon - void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon + void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon void PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg); void UpdateNTCP2Address (bool enable); void RemoveNTCPAddress (bool v4only = true); // delete NTCP address for older routers. TODO: remove later @@ -125,11 +125,11 @@ namespace garlic void SetSupportsMesh (bool supportsmesh, const boost::asio::ip::address_v6& host); bool IsECIES () const { return GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; std::unique_ptr& GetCurrentNoiseState () { return m_CurrentNoiseState; }; - + void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove void UpdateStats (); void UpdateTimestamp (uint64_t ts); // in seconds, called from NetDb before publishing - void CleanupDestination (); // garlic destination + void CleanupDestination (); // garlic destination // implements LocalDestination std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; @@ -146,8 +146,8 @@ namespace garlic void ProcessDeliveryStatusMessage (std::shared_ptr msg); // i18n - Lang GetLanguage () const { return m_Language; }; - void SetLanguage (Lang language); + std::shared_ptr GetLanguage () { return m_Language; }; + void SetLanguage (const std::shared_ptr language) { m_Language = language; }; protected: @@ -185,7 +185,7 @@ namespace garlic std::unique_ptr m_InitialNoiseState, m_CurrentNoiseState; // i18n - Lang m_Language; + std::shared_ptr m_Language; }; extern RouterContext context; From 0275f7f574b03e2ac8b2f7d1797d7437336bc8e1 Mon Sep 17 00:00:00 2001 From: Artem M Date: Wed, 26 May 2021 10:05:10 +0300 Subject: [PATCH 4214/6300] [i18n] fix two typos in the russian translation (#1659) --- i18n/Russian.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index 1f2c25da..28e07cc1 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -39,7 +39,7 @@ namespace russian // language {"addresshelper is not supported", "addresshelper не поддерживаетÑÑ"}, {"Host", "ÐдреÑ"}, {"added to router's addressbook from helper", "добавлен в адреÑную книгу роутера через хелпер"}, - {"already in router's addressbook", "уже а адреÑной книге роутера"}, + {"already in router's addressbook", "уже в адреÑной книге роутера"}, {"Click", "Ðажмите"}, {"here", "здеÑÑŒ"}, {"to proceed", "чтобы продолжить"}, @@ -51,7 +51,7 @@ namespace russian // language {"bad outproxy settings", "некорректные наÑтройки внешнего прокÑи"}, {"not inside I2P network, but outproxy is not enabled", "не в I2P Ñети, но внешний прокÑи не включен"}, {"unknown outproxy url", "неизвеÑтный URL внешнего прокÑи"}, - {"cannot resolve upstream proxy", "не удаетÑÑ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ð¸Ñ‚ÑŒ внешний прокÑи"}, + {"cannot resolve upstream proxy", "не удаетÑÑ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ð¸Ñ‚ÑŒ вышеÑтоÑщий прокÑи"}, {"hostname too long", "Ð¸Ð¼Ñ Ñ…Ð¾Ñта Ñлишком длинное"}, {"cannot connect to upstream socks proxy", "не удаетÑÑ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡Ð¸Ñ‚ÑŒÑÑ Ðº вышеÑтоÑщему SOCKS прокÑи"}, {"Cannot negotiate with socks proxy", "Ðе удаетÑÑ Ð´Ð¾Ð³Ð¾Ð²Ð¾Ñ€Ð¸Ñ‚ÑŒÑÑ Ñ Ð²Ñ‹ÑˆÐµÑтоÑщим SOCKS прокÑи"}, From bdf63cf82ca38a97e08f7c1cf278664844bd12a0 Mon Sep 17 00:00:00 2001 From: Artem M Date: Wed, 26 May 2021 10:38:58 +0300 Subject: [PATCH 4215/6300] [i18n] add Ukrainian (#1658) --- i18n/Ukrainian.cpp | 243 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 i18n/Ukrainian.cpp diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp new file mode 100644 index 00000000..94c61d80 --- /dev/null +++ b/i18n/Ukrainian.cpp @@ -0,0 +1,243 @@ +/* +* Copyright (c) 2021, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Russian localization file + +namespace i2p +{ +namespace i18n +{ +namespace ukrainian // language +{ + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; + } + + static std::map strings + { + // HTTP Proxy + {"Proxy error", "Помилка прокÑÑ–"}, + {"Proxy info", "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ÐºÑÑ–"}, + {"Proxy error: Host not found", "Помилка прокÑÑ–: ÐдреÑа не знайдена"}, + {"Remote host not found in router's addressbook", "Віддалена адреÑа не знайдена в адреÑній книзі роутера"}, + {"You may try to find this host on jump services below", "Ви можете Ñпробувати знайти дану адреÑу на джамп ÑервіÑах нижче"}, + {"Invalid request", "Ðекоректний запит"}, + {"Proxy unable to parse your request", "ПрокÑÑ– не може розібрати ваш запит"}, + {"addresshelper is not supported", "addresshelper не підтримуєтьÑÑ"}, + {"Host", "ÐдреÑа"}, + {"added to router's addressbook from helper", "доданий в адреÑну книгу роутера через хелпер"}, + {"already in router's addressbook", "вже в адреÑній книзі роутера"}, + {"Click", "ÐатиÑніть"}, + {"here", "тут"}, + {"to proceed", "щоб продовжити"}, + {"to update record", "щоб оновити запиÑ"}, + {"Addresshelper found", "Знайдено addresshelper"}, + {"invalid request uri", "некоректний URI запиту"}, + {"Can't detect destination host from request", "Ðе вдалоÑÑŒ визначити адреÑу Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð· запиту"}, + {"Outproxy failure", "Помилка зовнішнього прокÑÑ–"}, + {"bad outproxy settings", "некоректні Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½ÑŒÐ¾Ð³Ð¾ прокÑÑ–"}, + {"not inside I2P network, but outproxy is not enabled", "не в I2P мережі, але зовнішній прокÑÑ– не включений"}, + {"unknown outproxy url", "невідомий URL зовнішнього прокÑÑ–"}, + {"cannot resolve upstream proxy", "не вдаєтьÑÑ Ð²Ð¸Ð·Ð½Ð°Ñ‡Ð¸Ñ‚Ð¸ виÑхідний прокÑÑ–"}, + {"hostname too long", "ім'Ñ Ð²ÑƒÐ·Ð»Ð° надто довге"}, + {"cannot connect to upstream socks proxy", "не вдаєтьÑÑ Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡Ð¸Ñ‚Ð¸ÑÑ Ð´Ð¾ виÑхідного SOCKS прокÑÑ–"}, + {"Cannot negotiate with socks proxy", "Ðе вдаєтьÑÑ Ð´Ð¾Ð¼Ð¾Ð²Ð¸Ñ‚Ð¸ÑÑ Ð· виÑхідним SOCKS прокÑÑ–"}, + {"CONNECT error", "Помилка CONNECT запиту"}, + {"Failed to Connect", "Ðе вдалоÑÑ Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡Ð¸Ñ‚Ð¸ÑÑ"}, + {"socks proxy error", "помилка SOCKS прокÑÑ–"}, + {"failed to send request to upstream", "не вдалоÑÑ Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²Ð¸Ñ‚Ð¸ запит виÑхідному прокÑÑ–"}, + {"No Reply From socks proxy", "Ðемає відповіді від SOCKS прокÑÑ– Ñервера"}, + {"cannot connect", "не вдалоÑÑ Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡Ð¸Ñ‚Ð¸ÑÑ"}, + {"http out proxy not implemented", "підтримка зовнішнього HTTP прокÑÑ– Ñервера не реалізована"}, + {"cannot connect to upstream http proxy", "не вдалоÑÑ Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡Ð¸Ñ‚Ð¸ÑÑ Ð´Ð¾ виÑхідного HTTP прокÑÑ– Ñервера"}, + {"Host is down", "Вузол недоÑтупний"}, + {"Can't create connection to requested host, it may be down. Please try again later.", + "Ðе вдалоÑÑ Ð²Ñтановити з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð´Ð¾ запитаного вузла, можливо він не в мережі. Спробуйте повторити запит пізніше."}, + + // Webconsole // + // cssStyles + {"Disabled", "Вимкнуто"}, + {"Enabled", "Увімкнуто"}, + // ShowTraffic + {"KiB", "КіБ"}, + {"MiB", "МіБ"}, + {"GiB", "ГіБ"}, + // ShowTunnelDetails + {"building", "будуєтьÑÑ"}, + {"failed", "невдалий"}, + {"expiring", "завершуєтьÑÑ"}, + {"established", "працює"}, + {"exploratory", "доÑлідницький"}, + {"unknown", "невідомо"}, + {"i2pd webconsole", "Веб-конÑоль i2pd"}, + // ShowPageHead + {"Main page", "Головна"}, + {"Router commands", "Команди роутера"}, + {"Local destinations", "Локальні признач."}, + {"LeaseSets", "ЛізÑети"}, + {"Tunnels", "Тунелі"}, + {"Transit tunnels", "Транзит. тунелі"}, + {"Transports", "ТранÑпорти"}, + {"I2P tunnels", "I2P тунелі"}, + {"SAM sessions", "SAM ÑеÑÑ–Ñ—"}, + // Network Status + {"OK", "OK"}, + {"Testing", "ТеÑтуваннÑ"}, + {"Firewalled", "Файрвол"}, + {"Unknown", "Ðевідомо"}, + {"Proxy", "ПрокÑÑ–"}, + {"Mesh", "MESH-мережа"}, + {"Error", "Помилка"}, + {"Clock skew", "Ðеточний чаÑ"}, + {"Offline", "Офлайн"}, + {"Symmetric NAT", "Симетричний NAT"}, + // Status + {"Uptime", "Ð’ мережі"}, + {"Network status", "Мережевий ÑтатуÑ"}, + {"Network status v6", "Мережевий ÑÑ‚Ð°Ñ‚ÑƒÑ v6"}, + {"Stopping in", "Зупинка через"}, + {"Family", "СімейÑтво"}, + {"Tunnel creation success rate", "УÑпішно побудованих тунелів"}, + {"Received", "Отримано"}, + {"Sent", "Відправлено"}, + {"Transit", "Транзит"}, + {"KiB/s", "КіБ/Ñ"}, + {"Data path", "ШлÑÑ… до даних"}, + {"Hidden content. Press on text to see.", "Прихований вміÑÑ‚. ÐатиÑніть на текÑÑ‚ щоб відобразити."}, + {"Router Ident", "Ідентифікатор Роутера"}, + {"Router Family", "СімейÑтво Роутера"}, + {"Router Caps", "Прапорці Роутера"}, + {"Version", "ВерÑÑ–Ñ"}, + {"Our external address", "Ðаша Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ Ð°Ð´Ñ€ÐµÑа"}, + {"supported", "підтримуєтьÑÑ"}, + {"Routers", "Роутери"}, + {"Floodfills", "Флудфіли"}, + {"LeaseSets", "ЛізÑети"}, + {"Client Tunnels", "КлієнтÑькі Тунелі"}, + {"Transit Tunnels", "Транзитні Тунелі"}, + {"Services", "СервіÑи"}, + // ShowLocalDestinations + {"Local Destinations", "Локальні ПризначеннÑ"}, + // ShowLeaseSetDestination + {"Encrypted B33 address", "Шифровані B33 адреÑи"}, + {"Address registration line", "РÑдок реєÑтрації адреÑи"}, + {"Domain", "Домен"}, + {"Generate", "Згенерувати"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", + "Примітка: отриманий Ñ€Ñдок може бути викориÑтаний тільки Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації доменів другого рівнÑ. Ð”Ð»Ñ Ñ€ÐµÑ”Ñтрації піддоменів викориÑтовуйте i2pd-tools."}, + {"Address", "ÐдреÑа"}, + {"Type", "Тип"}, + {"EncType", "ТипШифр"}, + {"Inbound tunnels", "Вхідні тунелі"}, + {"Outbound tunnels", "Вихідні тунелі"}, + {"ms", "мÑ"}, // milliseconds + {"Tags", "Теги"}, + {"Incoming", "Вхідні"}, + {"Outgoing", "Вихідні"}, + {"Destination", "ПризначеннÑ"}, + {"Amount", "КількіÑть"}, + {"Incoming Tags", "Вхідні Теги"}, + {"Tags sessions", "СеÑÑ–Ñ— тегів"}, + {"Status", "СтатуÑ"}, + // ShowLocalDestination + {"Local Destination", "Локальне ПризначеннÑ"}, + {"Streams", "Потоки"}, + {"Close stream", "Закрити потік"}, + // ShowI2CPLocalDestination + {"I2CP session not found", "I2CP ÑеÑÑ–Ñ Ð½Ðµ знайдена"}, + {"I2CP is not enabled", "I2CP не увікнуто"}, + // ShowLeasesSets + {"Invalid", "Ðекоректний"}, + {"Store type", "Тип Ñховища"}, + {"Expires", "ЗавершуєтьÑÑ"}, + {"Non Expired Leases", "Ðе завершені Lease-и"}, + {"Gateway", "Шлюз"}, + {"TunnelID", "ID тунелÑ"}, + {"EndDate", "ЗакінчуєтьÑÑ"}, + {"not floodfill", "не флудфіл"}, + // ShowTunnels + {"Queue size", "Розмір черги"}, + // ShowCommands + {"Run peer test", "ЗапуÑтити теÑтуваннÑ"}, + {"Decline transit tunnels", "ВідхилÑти транзитні тунелі"}, + {"Accept transit tunnels", "Ухвалювати транзитні тунелі"}, + {"Cancel graceful shutdown", "СкаÑувати плавну зупинку"}, + {"Start graceful shutdown", "ЗапуÑтити плавну зупинку"}, + {"Force shutdown", "ПримуÑова зупинка"}, + {"Note: any action done here are not persistent and not changes your config files.", + "Примітка: будь-Ñка зроблена тут Ð´Ñ–Ñ Ð½Ðµ Ñ” поÑтійною та не змінює ваші конфігураційні файли."}, + {"Logging level", "Рівень логуваннÑ"}, + {"Transit tunnels limit", "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‚Ñ€Ð°Ð½Ð·Ð¸Ñ‚Ð½Ð¸Ñ… тунелів"}, + {"Change", "Змінити"}, + // ShowTransitTunnels + {"no transit tunnels currently built", "немає побудованих транзитних тунелів"}, + // ShowSAMSessions/ShowSAMSession + {"SAM disabled", "SAM вимкнуто"}, + {"SAM session not found", "SAM ÑеÑÑ–Ñ Ð½Ðµ знайдена"}, + {"no sessions currently running", "немає запущених ÑеÑій"}, + {"SAM Session", "SAM ÑеÑÑ–Ñ"}, + // ShowI2PTunnels + {"Server Tunnels", "Серверні Тунелі"}, + {"Client Forwards", "КлієнтÑькі ПереÑпрÑмуваннÑ"}, + {"Server Forwards", "Серверні ПереÑпрÑмуваннÑ"}, + // HandlePage + {"Unknown page", "Ðевідома Ñторінка"}, + // HandleCommand, ShowError + {"Invalid token", "Ðевірний токен"}, + {"SUCCESS", "ВДÐЛО"}, + {"ERROR", "ПОМИЛКÐ"}, + {"Unknown command", "Ðевідома команда"}, + {"Command accepted", "Команда прийнÑта"}, + {"Back to commands list", "ПовернутиÑÑ Ð´Ð¾ ÑпиÑку команд"}, + {"You will be redirected in 5 seconds", "Ви будете переадреÑовані через 5 Ñекунд"}, + // HTTP_COMMAND_KILLSTREAM + {"Stream closed", "Потік зачинений"}, + {"Stream not found or already was closed", "Потік не знайдений або вже зачинений"}, + {"Destination not found", "Точка Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ знайдена"}, + {"StreamID can't be null", "Ідентифікатор потоку не може бути порожнім"}, + {"Return to destination page", "ПовернутиÑÑ Ð½Ð° Ñторінку точки призначеннÑ"}, + {"You will be redirected back in 5 seconds", "Ви будете переадреÑовані назад через 5 Ñекунд"}, + // HTTP_COMMAND_LIMITTRANSIT + {"Transit tunnels count must not exceed 65535", "КількіÑть транзитних тунелів не повинна перевищувати 65535"}, + // HTTP_COMMAND_GET_REG_STRING + {"Register at reg.i2p", "ЗареєÑтрувати на reg.i2p"}, + {"Description", "ОпиÑ"}, + {"A bit information about service on domain", "Трохи інформації про ÑÐµÑ€Ð²Ñ–Ñ Ð½Ð° домені"}, + {"Submit", "ÐадіÑлати"}, + {"Domain can't end with .b32.i2p", "Домен не може закінчуватиÑÑ Ð½Ð° .b32.i2p"}, + {"Domain must end with .i2p", "Домен повинен закінчуватиÑÑ Ð½Ð° .i2p"}, + {"Such destination is not found", "Така точка Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ знайдена"}, + {"", ""}, + }; + + static std::map> plurals + { + // ShowUptime + {"days", {"день", "днÑ", "днів"}}, + {"hours", {"годину", "години", "годин"}}, + {"minutes", {"хвилину", "хвилини", "хвилин"}}, + {"seconds", {"Ñекунду", "Ñекунди", "Ñекунд"}}, + {"", {"", "", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p From cc1244126cd6e526aa40e637d6e032ba102a1558 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 26 May 2021 10:50:02 +0300 Subject: [PATCH 4216/6300] [i18n] enable Ukrainian in source Signed-off-by: R4SAS --- contrib/i2pd.conf | 2 +- i18n/I18N.h | 4 +++- i18n/I18N_langs.h | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 3c9c71ff..3b20a809 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -104,7 +104,7 @@ port = 7070 # user = i2pd # pass = changeme ## Select webconsole language -## Currently supported only english (default) and russian languages +## Currently supported english (default), russian and ukrainian languages # lang = english [httpproxy] diff --git a/i18n/I18N.h b/i18n/I18N.h index ccb8f413..a1a78b77 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -19,7 +19,9 @@ namespace i18n { if (!lang.compare("russian")) i2p::context.SetLanguage (i2p::i18n::russian::GetLocale()); - else + if (!lang.compare("ukrainian")) + i2p::context.SetLanguage (i2p::i18n::ukrainian::GetLocale()); + else // fallback i2p::context.SetLanguage (i2p::i18n::english::GetLocale()); } diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index 07bdc087..c094cb79 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -58,6 +58,7 @@ namespace i18n // Add localization here with language name as namespace namespace english { std::shared_ptr GetLocale (); } namespace russian { std::shared_ptr GetLocale (); } + namespace ukrainian { std::shared_ptr GetLocale (); } } // i18n } // i2p From ebce1e34d8ff325e32f6871a66e8300ac5d10f55 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 26 May 2021 12:56:47 +0300 Subject: [PATCH 4217/6300] [i18n] enable Ukrainian in source Signed-off-by: R4SAS --- build/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index f6d69b04..b96cfb6a 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -131,6 +131,7 @@ endif() set(LANG_SRC "${LANG_SRC_DIR}/English.cpp" "${LANG_SRC_DIR}/Russian.cpp" + "${LANG_SRC_DIR}/Ukrainian.cpp" ) set(DAEMON_SRC From 0292227a6b1c5330b04224ae6d4930ba8cb65854 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 26 May 2021 13:15:17 +0300 Subject: [PATCH 4218/6300] [cmake] switch to glob instead filling sources list Signed-off-by: R4SAS --- build/CMakeLists.txt | 70 ++------------------------------------------ 1 file changed, 3 insertions(+), 67 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index b96cfb6a..d1b51f23 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -41,55 +41,7 @@ include_directories(${LIBI2PD_CLIENT_SRC_DIR}) include_directories(${LANG_SRC_DIR}) include_directories(${DAEMON_SRC_DIR}) -set(LIBI2PD_SRC - "${LIBI2PD_SRC_DIR}/api.cpp" - "${LIBI2PD_SRC_DIR}/Base.cpp" - "${LIBI2PD_SRC_DIR}/Blinding.cpp" - "${LIBI2PD_SRC_DIR}/BloomFilter.cpp" - "${LIBI2PD_SRC_DIR}/ChaCha20.cpp" - "${LIBI2PD_SRC_DIR}/Config.cpp" - "${LIBI2PD_SRC_DIR}/CPU.cpp" - "${LIBI2PD_SRC_DIR}/Crypto.cpp" - "${LIBI2PD_SRC_DIR}/CryptoKey.cpp" - "${LIBI2PD_SRC_DIR}/Datagram.cpp" - "${LIBI2PD_SRC_DIR}/Destination.cpp" - "${LIBI2PD_SRC_DIR}/ECIESX25519AEADRatchetSession.cpp" - "${LIBI2PD_SRC_DIR}/Ed25519.cpp" - "${LIBI2PD_SRC_DIR}/Elligator.cpp" - "${LIBI2PD_SRC_DIR}/Family.cpp" - "${LIBI2PD_SRC_DIR}/FS.cpp" - "${LIBI2PD_SRC_DIR}/Garlic.cpp" - "${LIBI2PD_SRC_DIR}/Gost.cpp" - "${LIBI2PD_SRC_DIR}/Gzip.cpp" - "${LIBI2PD_SRC_DIR}/HTTP.cpp" - "${LIBI2PD_SRC_DIR}/I2NPProtocol.cpp" - "${LIBI2PD_SRC_DIR}/Identity.cpp" - "${LIBI2PD_SRC_DIR}/LeaseSet.cpp" - "${LIBI2PD_SRC_DIR}/Log.cpp" - "${LIBI2PD_SRC_DIR}/NetDb.cpp" - "${LIBI2PD_SRC_DIR}/NetDbRequests.cpp" - "${LIBI2PD_SRC_DIR}/NTCP2.cpp" - "${LIBI2PD_SRC_DIR}/Poly1305.cpp" - "${LIBI2PD_SRC_DIR}/Profiling.cpp" - "${LIBI2PD_SRC_DIR}/Reseed.cpp" - "${LIBI2PD_SRC_DIR}/RouterContext.cpp" - "${LIBI2PD_SRC_DIR}/RouterInfo.cpp" - "${LIBI2PD_SRC_DIR}/Signature.cpp" - "${LIBI2PD_SRC_DIR}/SSU.cpp" - "${LIBI2PD_SRC_DIR}/SSUData.cpp" - "${LIBI2PD_SRC_DIR}/SSUSession.cpp" - "${LIBI2PD_SRC_DIR}/Streaming.cpp" - "${LIBI2PD_SRC_DIR}/Timestamp.cpp" - "${LIBI2PD_SRC_DIR}/TransitTunnel.cpp" - "${LIBI2PD_SRC_DIR}/Transports.cpp" - "${LIBI2PD_SRC_DIR}/Tunnel.cpp" - "${LIBI2PD_SRC_DIR}/TunnelEndpoint.cpp" - "${LIBI2PD_SRC_DIR}/TunnelGateway.cpp" - "${LIBI2PD_SRC_DIR}/TunnelPool.cpp" - "${LIBI2PD_SRC_DIR}/TunnelConfig.cpp" - "${LIBI2PD_SRC_DIR}/util.cpp" -) - +FILE(GLOB LIBI2PD_SRC ${LIBI2PD_SRC_DIR}/*.cpp) add_library(libi2pd ${LIBI2PD_SRC}) set_target_properties(libi2pd PROPERTIES PREFIX "") @@ -104,19 +56,7 @@ if(WITH_LIBRARY) # install(EXPORT libi2pd DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() -set(CLIENT_SRC - "${LIBI2PD_CLIENT_SRC_DIR}/AddressBook.cpp" - "${LIBI2PD_CLIENT_SRC_DIR}/BOB.cpp" - "${LIBI2PD_CLIENT_SRC_DIR}/ClientContext.cpp" - "${LIBI2PD_CLIENT_SRC_DIR}/MatchedDestination.cpp" - "${LIBI2PD_CLIENT_SRC_DIR}/I2PTunnel.cpp" - "${LIBI2PD_CLIENT_SRC_DIR}/I2PService.cpp" - "${LIBI2PD_CLIENT_SRC_DIR}/SAM.cpp" - "${LIBI2PD_CLIENT_SRC_DIR}/SOCKS.cpp" - "${LIBI2PD_CLIENT_SRC_DIR}/HTTPProxy.cpp" - "${LIBI2PD_CLIENT_SRC_DIR}/I2CP.cpp" -) - +FILE(GLOB CLIENT_SRC ${LIBI2PD_CLIENT_SRC_DIR}/*.cpp) add_library(libi2pdclient ${CLIENT_SRC}) set_target_properties(libi2pdclient PROPERTIES PREFIX "") @@ -128,11 +68,7 @@ if(WITH_LIBRARY) COMPONENT Libraries) endif() -set(LANG_SRC - "${LANG_SRC_DIR}/English.cpp" - "${LANG_SRC_DIR}/Russian.cpp" - "${LANG_SRC_DIR}/Ukrainian.cpp" -) +FILE(GLOB LANG_SRC ${LANG_SRC_DIR}/*.cpp) set(DAEMON_SRC "${DAEMON_SRC_DIR}/Daemon.cpp" From 35b1842a7204dd30b72b407d3ccd90a3bcd1afef Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 26 May 2021 13:21:15 +0300 Subject: [PATCH 4219/6300] [gha] add cmake build on ubuntu Signed-off-by: R4SAS --- .github/workflows/build.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a776df92..c7b5a667 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,8 +3,8 @@ name: Build on Ubuntu on: [push, pull_request] jobs: - build: - name: With USE_UPNP=${{ matrix.with_upnp }} + build-make: + name: Make with USE_UPNP=${{ matrix.with_upnp }} runs-on: ubuntu-16.04 strategy: fail-fast: true @@ -19,3 +19,22 @@ jobs: sudo apt-get install build-essential libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev - name: build application run: make USE_UPNP=${{ matrix.with_upnp }} -j3 + build-cmake: + name: CMake with -DWITH_UPNP=${{ matrix.with_upnp }} + runs-on: ubuntu-16.04 + strategy: + fail-fast: true + matrix: + with_upnp: ['ON', 'OFF'] + steps: + - uses: actions/checkout@v2 + - name: install packages + run: | + sudo add-apt-repository ppa:mhier/libboost-latest + sudo apt-get update + sudo apt-get install build-essential cmake libboost1.74-dev libminiupnpc-dev libssl-dev zlib1g-dev + - name: build application + run: | + cd build + cmake -DWITH_UPNP=${{ matrix.with_upnp }} . + make -j3 From 5011ecaaa65427e83badde130c1185de27f2dae5 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 26 May 2021 13:27:13 +0300 Subject: [PATCH 4220/6300] [i18n] fix language selection Signed-off-by: R4SAS --- i18n/I18N.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/I18N.h b/i18n/I18N.h index a1a78b77..366e9269 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -19,7 +19,7 @@ namespace i18n { if (!lang.compare("russian")) i2p::context.SetLanguage (i2p::i18n::russian::GetLocale()); - if (!lang.compare("ukrainian")) + else if (!lang.compare("ukrainian")) i2p::context.SetLanguage (i2p::i18n::ukrainian::GetLocale()); else // fallback i2p::context.SetLanguage (i2p::i18n::english::GetLocale()); From 3a53e049bd4fbb085af75810e13970e4471c8a64 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 26 May 2021 13:43:24 +0300 Subject: [PATCH 4221/6300] [gha] switch ubuntu to 18.04 Signed-off-by: R4SAS --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7b5a667..d8828f61 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: build-make: name: Make with USE_UPNP=${{ matrix.with_upnp }} - runs-on: ubuntu-16.04 + runs-on: ubuntu-18.04 strategy: fail-fast: true matrix: @@ -21,7 +21,7 @@ jobs: run: make USE_UPNP=${{ matrix.with_upnp }} -j3 build-cmake: name: CMake with -DWITH_UPNP=${{ matrix.with_upnp }} - runs-on: ubuntu-16.04 + runs-on: ubuntu-18.04 strategy: fail-fast: true matrix: From 8ce5ceef5970478ff39eb59544278f9c633579d7 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 27 May 2021 17:47:59 -0400 Subject: [PATCH 4222/6300] Correct transaltion for "Firewalled" --- i18n/Russian.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index 28e07cc1..7df82d54 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -96,7 +96,7 @@ namespace russian // language // Network Status {"OK", "OK"}, {"Testing", "ТеÑтирование"}, - {"Firewalled", "Файрвол"}, + {"Firewalled", "Заблокировано извне"}, {"Unknown", "ÐеизвеÑтно"}, {"Proxy", "ПрокÑи"}, {"Mesh", "MESH-Ñеть"}, From e77e383efa8021fdcb21557a8073df91b26214c9 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 28 May 2021 18:59:59 +0300 Subject: [PATCH 4223/6300] [docker] add UPnP at compile time (closes #1649) --- contrib/docker/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index adb7ba75..dc9f5501 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -25,24 +25,24 @@ RUN mkdir -p "$I2PD_HOME" "$DATA_DIR" \ # 1. install deps, clone and build. # 2. strip binaries. # 3. Purge all dependencies and other unrelated packages, including build directory. -RUN apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl git \ +RUN apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl miniupnpc-dev git \ && mkdir -p /tmp/build \ && cd /tmp/build && git clone -b ${GIT_BRANCH} ${REPO_URL} \ && cd i2pd \ && if [ -n "${GIT_TAG}" ]; then git checkout tags/${GIT_TAG}; fi \ - && make \ + && make USE_UPNP=yes \ && cp -R contrib/certificates /i2pd_certificates \ && mkdir -p /usr/local/bin \ && mv i2pd /usr/local/bin \ && cd /usr/local/bin \ && strip i2pd \ && rm -fr /tmp/build && apk --no-cache --purge del build-dependendencies build-base fortify-headers boost-dev zlib-dev openssl-dev \ - boost-python3 python3 gdbm boost-unit_test_framework linux-headers boost-prg_exec_monitor \ + miniupnpc-dev boost-python3 python3 gdbm boost-unit_test_framework linux-headers boost-prg_exec_monitor \ boost-serialization boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre2 \ libtool g++ gcc # 2. Adding required libraries to run i2pd to ensure it will run. -RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl musl-utils libstdc++ +RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl miniupnpc musl-utils libstdc++ COPY entrypoint.sh /entrypoint.sh RUN chmod a+x /entrypoint.sh From a0e545a6f1ed29b9f077371857a587349059416a Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 28 May 2021 12:11:24 -0400 Subject: [PATCH 4224/6300] always create new tunnel from exploratory pool --- libi2pd/TunnelPool.cpp | 18 ++++++++++++++++-- libi2pd/TunnelPool.h | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index ed9a1a82..f2f0b4c8 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -393,10 +393,14 @@ namespace tunnel } } + bool TunnelPool::IsExploratory () const + { + return i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this (); + } + std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop, bool reverse) const { - bool isExploratory = (i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this ()); - auto hop = isExploratory ? i2p::data::netdb.GetRandomRouter (prevHop, reverse): + auto hop = IsExploratory () ? i2p::data::netdb.GetRandomRouter (prevHop, reverse): i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop, reverse); if (!hop || hop->GetProfile ()->IsBad ()) @@ -521,6 +525,11 @@ namespace tunnel void TunnelPool::RecreateInboundTunnel (std::shared_ptr tunnel) { + if (IsExploratory ()) // always create new exploratory tunnel + { + CreateInboundTunnel (); + return; + } auto outboundTunnel = GetNextOutboundTunnel (); if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); @@ -567,6 +576,11 @@ namespace tunnel void TunnelPool::RecreateOutboundTunnel (std::shared_ptr tunnel) { + if (IsExploratory ()) // always create new exploratory tunnel + { + CreateOutboundTunnel (); + return; + } auto inboundTunnel = GetNextInboundTunnel (); if (!inboundTunnel) inboundTunnel = tunnels.GetNextInboundTunnel (); diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index 5fd1d83c..164ca7a1 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -77,6 +77,7 @@ namespace tunnel void ProcessGarlicMessage (std::shared_ptr msg); void ProcessDeliveryStatus (std::shared_ptr msg); + bool IsExploratory () const; bool IsActive () const { return m_IsActive; }; void SetActive (bool isActive) { m_IsActive = isActive; }; void DetachTunnels (); From ed42948051afb308cd7585d4284d286f49829a08 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 30 May 2021 03:23:00 +0300 Subject: [PATCH 4225/6300] prefer public ipv6 instead rfc4941 (closes #1251) Wokrs only on linux-based systems. Not tested on other *nix systems, and not works on windows. Signed-off-by: R4SAS --- libi2pd/NTCP2.cpp | 9 +++++++++ libi2pd/SSU.cpp | 9 +++++++++ libi2pd/util.h | 6 +++--- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index d5a03d1c..54d6483c 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -23,6 +23,10 @@ #include "HTTP.h" #include "util.h" +#ifdef __linux__ + #include +#endif + namespace i2p { namespace transport @@ -1198,6 +1202,11 @@ namespace transport ep = boost::asio::ip::tcp::endpoint (m_Address6->address(), address->port); else if (m_YggdrasilAddress && !context.SupportsV6 ()) ep = boost::asio::ip::tcp::endpoint (m_YggdrasilAddress->address(), address->port); +#ifdef __linux__ + // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others + typedef boost::asio::detail::socket_option::boolean ipv6PreferPubAddr; + m_NTCP2V6Acceptor->set_option (ipv6PreferPubAddr (true)); +#endif m_NTCP2V6Acceptor->bind (ep); m_NTCP2V6Acceptor->listen (); diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index f7801bb0..0836f84e 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -14,6 +14,10 @@ #include "SSU.h" #include "util.h" +#ifdef __linux__ + #include +#endif + #ifdef _WIN32 #include #endif @@ -62,6 +66,11 @@ namespace transport m_SocketV6.set_option (boost::asio::ip::v6_only (true)); m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); +#ifdef __linux__ + // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others + typedef boost::asio::detail::socket_option::boolean ipv6PreferPubAddr; + m_SocketV6.set_option (ipv6PreferPubAddr(true)); +#endif m_SocketV6.bind (m_EndpointV6); LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port()); } diff --git a/libi2pd/util.h b/libi2pd/util.h index 0cab4121..000cb74e 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -177,13 +177,13 @@ namespace util SaveStateHelper (T& orig): m_Original (orig), m_Copy (orig) {}; ~SaveStateHelper () { m_Original = m_Copy; }; - + private: T& m_Original; T m_Copy; - }; - + }; + namespace net { int GetMTU (const boost::asio::ip::address& localAddress); From 39319853ab79c3cadd74cfe69e525a4e247d4bab Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 30 May 2021 21:38:14 +0300 Subject: [PATCH 4226/6300] [i18n] add Turkmen translation Signed-off-by: R4SAS --- contrib/i2pd.conf | 2 +- i18n/I18N.h | 2 + i18n/I18N_langs.h | 1 + i18n/Turkmen.cpp | 243 +++++++++++++++++++++++++++++++++++++++++++++ i18n/Ukrainian.cpp | 2 +- 5 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 i18n/Turkmen.cpp diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 3b20a809..f3cea2b5 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -104,7 +104,7 @@ port = 7070 # user = i2pd # pass = changeme ## Select webconsole language -## Currently supported english (default), russian and ukrainian languages +## Currently supported english (default), russian, turkmen and ukrainian languages # lang = english [httpproxy] diff --git a/i18n/I18N.h b/i18n/I18N.h index 366e9269..7d0baf1a 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -19,6 +19,8 @@ namespace i18n { if (!lang.compare("russian")) i2p::context.SetLanguage (i2p::i18n::russian::GetLocale()); + else if (!lang.compare("turkmen")) + i2p::context.SetLanguage (i2p::i18n::turkmen::GetLocale()); else if (!lang.compare("ukrainian")) i2p::context.SetLanguage (i2p::i18n::ukrainian::GetLocale()); else // fallback diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index c094cb79..435141bf 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -58,6 +58,7 @@ namespace i18n // Add localization here with language name as namespace namespace english { std::shared_ptr GetLocale (); } namespace russian { std::shared_ptr GetLocale (); } + namespace turkmen { std::shared_ptr GetLocale (); } namespace ukrainian { std::shared_ptr GetLocale (); } } // i18n diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp new file mode 100644 index 00000000..93bd92fe --- /dev/null +++ b/i18n/Turkmen.cpp @@ -0,0 +1,243 @@ +/* +* Copyright (c) 2021, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Turkmen localization file + +namespace i2p +{ +namespace i18n +{ +namespace turkmen // language +{ + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + // HTTP Proxy + {"Proxy error", "Proksi ýalňyÅŸlygy"}, + {"Proxy info", "Proksi maglumat"}, + {"Proxy error: Host not found", "Proksi ýalňyÅŸlygy: Host tapylmady"}, + {"Remote host not found in router's addressbook", "Uzakdaky öý eýesi marÅŸruteriň salgy kitabynda tapylmady"}, + {"You may try to find this host on jump services below", "AÅŸakdaky böküş hyzmatlarynda bu öý eýesini tapmaga synanyÅŸyp bilersiňiz"}, + {"Invalid request", "Nädogry haýyÅŸ"}, + {"Proxy unable to parse your request", "Proksi haýyÅŸyňyzy derňäp bilmeýär"}, + {"addresshelper is not supported", "Salgylandyryjy goldanok"}, + {"Host", "Adres"}, + {"added to router's addressbook from helper", "marÅŸruteriň adresini kömekçiden goÅŸdy"}, + {"already in router's addressbook", "marÅŸruteriň adres kitaby"}, + {"Click", "Basyň"}, + {"here", "bu ýerde"}, + {"to proceed", "dowam etmek"}, + {"to update record", "recordazgyny täzelemek üçin"}, + {"Addresshelper found", "Forgelper tapyldy"}, + {"invalid request uri", "nädogry haýyÅŸ URI"}, + {"Can't detect destination host from request", "HaýyÅŸdan barmaly ýerini tapyp bilemok"}, + {"Outproxy failure", "DaÅŸarky proksi ýalňyÅŸlyk"}, + {"bad outproxy settings", "daÅŸarky daÅŸarky proksi sazlamalary nädogry"}, + {"not inside I2P network, but outproxy is not enabled", "I2P torunda däl, ýöne daÅŸarky proksi goÅŸulmaýar"}, + {"unknown outproxy url", "näbelli daÅŸarky proksi URL"}, + {"cannot resolve upstream proxy", "has ýokary proksi kesgitläp bilmeýär"}, + {"hostname too long", "hoster eýesi ady gaty uzyn"}, + {"cannot connect to upstream socks proxy", "ýokary jorap SOCKS proksi bilen birigip bolmaýar"}, + {"Cannot negotiate with socks proxy", "Iň ýokary jorap SOCKS proksi bilen ylalaÅŸyp bilmeýärler"}, + {"CONNECT error", "Bagyr haýyÅŸy säwligi"}, + {"Failed to Connect", "Birikdirip bilmedi"}, + {"socks proxy error", "socks proksi ýalňyÅŸlygy"}, + {"failed to send request to upstream", "öý eýesi proksi üçin haýyÅŸ iberip bilmedi"}, + {"No Reply From socks proxy", "Jorap proksi serwerinden hiç hili jogap ýok"}, + {"cannot connect", "birikdirip bilmedi"}, + {"http out proxy not implemented", "daÅŸarky http proksi serwerini goldamak amala aÅŸyrylmaýar"}, + {"cannot connect to upstream http proxy", "ýokary akym HTTP proksi serwerine birigip bilmedi"}, + {"Host is down", "Salgy elýeterli däl"}, + {"Can't create connection to requested host, it may be down. Please try again later.", + "Talap edilýän salgyda birikmäni gurup bilmedim, onlaýn bolup bilmez. Soňra haýyÅŸy soň gaýtalamaga synanyÅŸyň."}, + + // Webconsole // + // cssStyles + {"Disabled", "Öçürildi"}, + {"Enabled", "GoÅŸuldy"}, + // ShowTraffic + {"KiB", "KiB"}, + {"MiB", "MiB"}, + {"GiB", "GiB"}, + // ShowTunnelDetails + {"building", "bina"}, + {"failed", "ÅŸowsuz"}, + {"expiring", "möhleti gutarýar"}, + {"established", "iÅŸleýär"}, + {"exploratory", "gözleg"}, + {"unknown", "näbelli"}, + {"i2pd webconsole", "Web konsoly i2pd"}, + // ShowPageHead + {"Main page", "Esasy sahypa"}, + {"Router commands", "MarÅŸrutizator buýruklary"}, + {"Local destinations", "Ãerli ýerler"}, + {"LeaseSets", "Lizset"}, + {"Tunnels", "Tuneller"}, + {"Transit tunnels", "Tranzit Tunels"}, + {"Transports", "DaÅŸamak"}, + {"I2P tunnels", "I2P tuneller"}, + {"SAM sessions", "SAM Sessiýasy"}, + // Network Status + {"OK", "OK"}, + {"Testing", "Synag etmek"}, + {"Firewalled", "DaÅŸynda petiklendi"}, + {"Unknown", "Näbelli"}, + {"Proxy", "Proksi"}, + {"Mesh", "MESH-tor"}, + {"Error", "ÃalňyÅŸlyk"}, + {"Clock skew", "Takyk wagt däl"}, + {"Offline", "Awtonom"}, + {"Symmetric NAT", "Simmetriklik NAT"}, + // Status + {"Uptime", "Onlaýn onlaýn sözlügi"}, + {"Network status", "Tor ýagdaýy"}, + {"Network status v6", "Tor ýagdaýy v6"}, + {"Stopping in", "soň duruň"}, + {"Family", "MaÅŸgala"}, + {"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"}, + {"Received", "Alnan"}, + {"Sent", "ÃerleÅŸdirildi"}, + {"Transit", "Tranzit"}, + {"KiB/s", "KiB/s"}, + {"Data path", "Maglumat ýoly"}, + {"Hidden content. Press on text to see.", "Gizlin mazmun. Görkezmek üçin tekste basyň."}, + {"Router Ident", "MarÅŸrutly kesgitleýji"}, + {"Router Family", "MarÅŸrutler maÅŸgalasy"}, + {"Router Caps", "Baýdaklar marÅŸruteri"}, + {"Version", "Wersiýasy"}, + {"Our external address", "DaÅŸarky salgymyz"}, + {"supported", "Goldanýar"}, + {"Routers", "MarÅŸrutizatorlar"}, + {"Floodfills", "Fludfillar"}, + {"LeaseSets", "Lizsetllar"}, + {"Client Tunnels", "Müşderi tunelleri"}, + {"Transit Tunnels", "Tranzit Tunelleri"}, + {"Services", "Hyzmatlar"}, + // ShowLocalDestinations + {"Local Destinations", "Ãerli ýerler"}, + // ShowLeaseSetDestination + {"Encrypted B33 address", "Åžifrlenen B33 salgylar"}, + {"Address registration line", "Hasaba alyÅŸ salgysy"}, + {"Domain", "Domen"}, + {"Generate", "Öndürmek"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", + "Bellik: Alnan setir diňe ikinji derejeli domenleri bellige almak üçin ulanylyp bilner. Subýutmalary hasaba almak üçin i2pd ulanyň-tools."}, + {"Address", "Salgysy"}, + {"Type", "Görnüş"}, + {"EncType", "Åžifrlemek görnüşi"}, + {"Inbound tunnels", "Gelýän tuneller"}, + {"Outbound tunnels", "Çykýan tuneller"}, + {"ms", "ms"}, // milliseconds + {"Tags", "Bellikler"}, + {"Incoming", "Gelýän"}, + {"Outgoing", "Çykýan"}, + {"Destination", "Maksat"}, + {"Amount", "Sany"}, + {"Incoming Tags", "Gelýän bellikler"}, + {"Tags sessions", "Sapaklar Tag."}, + {"Status", "Ãagdaýy"}, + // ShowLocalDestination + {"Local Destination", "Ãerli maksat"}, + {"Streams", "Strimlary"}, + {"Close stream", "Yap strim"}, + // ShowI2CPLocalDestination + {"I2CP session not found", "I2CP Sessiýa tapylmady"}, + {"I2CP is not enabled", "I2CP goÅŸulmaýar"}, + // ShowLeasesSets + {"Invalid", "Nädogry"}, + {"Store type", "Ammar görnüşi"}, + {"Expires", "Möhleti gutarýar"}, + {"Non Expired Leases", "Möhleti gutarmady Lizsetlary"}, + {"Gateway", "Derweze"}, + {"TunnelID", "Tuneliň ID"}, + {"EndDate", "Gutarýar"}, + {"not floodfill", "fludfil däl"}, + // ShowTunnels + {"Queue size", "Nobatyň ululygy"}, + // ShowCommands + {"Run peer test", "Synag baÅŸlaň"}, + {"Decline transit tunnels", "Tranzit tunellerini ret ediň"}, + {"Accept transit tunnels", "Tranzit tunellerini alyň"}, + {"Cancel graceful shutdown", "Tekiz durmagy ýatyryň"}, + {"Start graceful shutdown", "Tekiz durmak"}, + {"Force shutdown", "Mejbury duralga"}, + {"Note: any action done here are not persistent and not changes your config files.", + "Bellik: Bu ýerde öndürilen islendik çäre hemiÅŸelik däl we konfigurasiýa faýllaryňyzy üýtgetmeýär.."}, + {"Logging level", "GiriÅŸ derejesi"}, + {"Transit tunnels limit", "Tranzit tunelleriniň çägi"}, + {"Change", "Üýtgetmek"}, + // ShowTransitTunnels + {"no transit tunnels currently built", "gurlan tranzit tunelleri ýok"}, + // ShowSAMSessions/ShowSAMSession + {"SAM disabled", "SAM öçürilen"}, + {"SAM session not found", "SAM Sessiýa tapylmady"}, + {"no sessions currently running", "baÅŸlamagyň sessiýalary ýok"}, + {"SAM Session", "SAM Sessiýa"}, + // ShowI2PTunnels + {"Server Tunnels", "Serwer tunelleri"}, + {"Client Forwards", "Müşderi gönükdirýär"}, + {"Server Forwards", "Serweriň täzeden düzlüleri"}, + // HandlePage + {"Unknown page", "Näbelli sahypa"}, + // HandleCommand, ShowError + {"Invalid token", "Nädogry token"}, + {"SUCCESS", "Üstünlikli"}, + {"ERROR", "ÃalňyÅŸlyk"}, + {"Unknown command", "Näbelli topar"}, + {"Command accepted", "Topar kabul edilýär"}, + {"Back to commands list", "Topar sanawyna dolan"}, + {"You will be redirected in 5 seconds", "5 sekuntdan soň täzeden ugrukdyrylarsyňyz"}, + // HTTP_COMMAND_KILLSTREAM + {"Stream closed", "Strim ýapyk"}, + {"Stream not found or already was closed", "Strim tapylmady ýa-da eýýäm ýapyldy"}, + {"Destination not found", "Niýetlenen ýeri tapylmady"}, + {"StreamID can't be null", "StreamID boÅŸ bolup bilmez"}, + {"Return to destination page", "Barmaly nokadynyň nokadyna gaýdyp geliň"}, + {"You will be redirected back in 5 seconds", "5 sekuntda yzyna iberiler"}, + // HTTP_COMMAND_LIMITTRANSIT + {"Transit tunnels count must not exceed 65535", "Tranzit tagtalaryň sany 65535-den geçmeli däldir"}, + // HTTP_COMMAND_GET_REG_STRING + {"Register at reg.i2p", "Reg.i2P-de hasaba duruň"}, + {"Description", "Beýany"}, + {"A bit information about service on domain", "Domendäki hyzmat barada käbir maglumatlar"}, + {"Submit", "Iber"}, + {"Domain can't end with .b32.i2p", "Domain .b32.i2p bilen gutaryp bilmez."}, + {"Domain must end with .i2p", "Domeni .i2p bilen gutarmaly"}, + {"Such destination is not found", "Bu barmaly ýer tapylmady"}, + {"", ""}, + }; + + static std::map> plurals + { + // ShowUptime + {"days", {"gün", "gün"}}, + {"hours", {"sagat", "sagat"}}, + {"minutes", {"minut", "minut"}}, + {"seconds", {"sekunt", "sekunt"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp index 94c61d80..05e7c786 100644 --- a/i18n/Ukrainian.cpp +++ b/i18n/Ukrainian.cpp @@ -12,7 +12,7 @@ #include #include "I18N.h" -// Russian localization file +// Ukrainian localization file namespace i2p { From be31640010ab6fe6777435302fc231ae3279f73e Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 31 May 2021 00:23:50 +0300 Subject: [PATCH 4227/6300] fix ipv6 preference on linux Signed-off-by: R4SAS --- libi2pd/NTCP2.cpp | 13 ++++++++----- libi2pd/SSU.cpp | 9 ++++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 54d6483c..72e822cd 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1197,16 +1197,19 @@ namespace transport m_NTCP2V6Acceptor->open (boost::asio::ip::tcp::v6()); m_NTCP2V6Acceptor->set_option (boost::asio::ip::v6_only (true)); m_NTCP2V6Acceptor->set_option (boost::asio::socket_base::reuse_address (true)); +#ifdef __linux__ + if (!m_Address6 && !m_YggdrasilAddress) // only if not binded to address + { + // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others + typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; + m_NTCP2V6Acceptor->set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); + } +#endif auto ep = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v6(), address->port); if (m_Address6 && !context.SupportsMesh ()) ep = boost::asio::ip::tcp::endpoint (m_Address6->address(), address->port); else if (m_YggdrasilAddress && !context.SupportsV6 ()) ep = boost::asio::ip::tcp::endpoint (m_YggdrasilAddress->address(), address->port); -#ifdef __linux__ - // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others - typedef boost::asio::detail::socket_option::boolean ipv6PreferPubAddr; - m_NTCP2V6Acceptor->set_option (ipv6PreferPubAddr (true)); -#endif m_NTCP2V6Acceptor->bind (ep); m_NTCP2V6Acceptor->listen (); diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index 0836f84e..f625c89b 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -67,9 +67,12 @@ namespace transport m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); #ifdef __linux__ - // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others - typedef boost::asio::detail::socket_option::boolean ipv6PreferPubAddr; - m_SocketV6.set_option (ipv6PreferPubAddr(true)); + if (m_EndpointV6.address() != boost::asio::ip::address().from_string("::")) // only if not binded to address + { + // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others + typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; + m_SocketV6.set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); + } #endif m_SocketV6.bind (m_EndpointV6); LogPrint (eLogInfo, "SSU: Start listening v6 port ", m_EndpointV6.port()); From 0547d590e1f1cefc1f93204cae17e2795487b61a Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 31 May 2021 00:24:54 +0300 Subject: [PATCH 4228/6300] fix typo Signed-off-by: R4SAS --- libi2pd/SSU.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index f625c89b..e4901f7c 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -67,7 +67,7 @@ namespace transport m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); #ifdef __linux__ - if (m_EndpointV6.address() != boost::asio::ip::address().from_string("::")) // only if not binded to address + if (m_EndpointV6.address() == boost::asio::ip::address().from_string("::")) // only if not binded to address { // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; From ef8c4389e104cf217dae6c3e15d9bff83d6abfd9 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 2 Jun 2021 12:55:08 -0400 Subject: [PATCH 4229/6300] reachable transports added --- libi2pd/RouterInfo.cpp | 45 ++++++++++++++++++++++++++++++++++++++---- libi2pd/RouterInfo.h | 7 ++++--- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 87a987fe..3035bd31 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -36,7 +36,7 @@ namespace data RouterInfo::RouterInfo (const std::string& fullPath): m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false), - m_SupportedTransports (0), m_Caps (0), m_Version (0) + m_SupportedTransports (0), m_ReachableTransports (0), m_Caps (0), m_Version (0) { m_Addresses = boost::make_shared(); // create empty list m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; @@ -45,7 +45,7 @@ namespace data RouterInfo::RouterInfo (const uint8_t * buf, int len): m_IsUpdated (true), m_IsUnreachable (false), m_SupportedTransports (0), - m_Caps (0), m_Version (0) + m_ReachableTransports (0), m_Caps (0), m_Version (0) { m_Addresses = boost::make_shared(); // create empty list if (len <= MAX_RI_BUFFER_SIZE) @@ -84,6 +84,7 @@ namespace data m_IsUpdated = true; m_IsUnreachable = false; m_SupportedTransports = 0; + m_ReachableTransports = 0; m_Caps = 0; // don't clean up m_Addresses, it will be replaced in ReadFromStream m_Properties.clear (); @@ -305,9 +306,10 @@ namespace data if (isHost) { if (address->host.is_v6 ()) - supportedTransports |= i2p::util::net::IsYggdrasilAddress (address->host) ? eNTCP2V6Mesh : eNTCP2V6; + supportedTransports |= (i2p::util::net::IsYggdrasilAddress (address->host) ? eNTCP2V6Mesh : eNTCP2V6); else supportedTransports |= eNTCP2V4; + m_ReachableTransports |= supportedTransports; } else if (!address->published) { @@ -347,10 +349,16 @@ namespace data else it.iPort = 0; } - if (!numValid) address->ssu->introducers.resize (0); + if (numValid) + m_ReachableTransports |= supportedTransports; + else + address->ssu->introducers.resize (0); } else if (isHost && address->port) + { address->published = true; + m_ReachableTransports |= supportedTransports; + } } } if (supportedTransports) @@ -860,6 +868,7 @@ namespace data for (auto& intro: addr->ssu->introducers) if (intro.iTag == introducer.iTag) return false; // already presented addr->ssu->introducers.push_back (introducer); + m_ReachableTransports |= (addr->IsV4 () ? eSSUV4 : eSSUV6); return true; } } @@ -877,6 +886,8 @@ namespace data if (boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e) { addr->ssu->introducers.erase (it); + if (addr->ssu->introducers.empty ()) + m_ReachableTransports &= ~(addr->IsV4 () ? eSSUV4 : eSSUV6); return true; } } @@ -1184,5 +1195,31 @@ namespace data } } } + + void RouterInfo::UpdateSupportedTransports () + { + m_SupportedTransports = 0; + m_ReachableTransports = 0; + for (const auto& addr: *m_Addresses) + { + uint8_t transports = 0; + if (addr->transportStyle == eTransportNTCP) + { + if (addr->IsV4 ()) transports |= eNTCP2V4; + if (addr->IsV6 ()) + transports |= (i2p::util::net::IsYggdrasilAddress (addr->host) ? eNTCP2V6Mesh : eNTCP2V6); + if (addr->IsPublishedNTCP2 ()) + m_ReachableTransports |= transports; + } + else if (addr->transportStyle == eTransportSSU) + { + if (addr->IsV4 ()) transports |= eSSUV4; + if (addr->IsV6 ()) transports |= eSSUV6; + if (addr->IsReachableSSU ()) + m_ReachableTransports |= transports; + } + m_SupportedTransports |= transports; + } + } } } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index dd3e7e13..bea83fda 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -59,7 +59,7 @@ namespace data { public: - enum SupportedTranports + enum SupportedTransports { eNTCP2V4 = 0x01, eNTCP2V6 = 0x02, @@ -147,7 +147,7 @@ namespace data bool IsNTCP2 () const { return (bool)ntcp2; }; bool IsPublishedNTCP2 () const { return IsNTCP2 () && published; }; - bool IsReachableSSU () const { return (bool)ssu && (!host.is_unspecified () || !ssu->introducers.empty ()); }; + bool IsReachableSSU () const { return (bool)ssu && (published || !ssu->introducers.empty ()); }; bool UsesIntroducer () const { return (bool)ssu && !ssu->introducers.empty (); }; bool IsIntroducer () const { return caps & eSSUIntroducer; }; @@ -187,6 +187,7 @@ namespace data std::string GetProperty (const std::string& key) const; // called from RouterContext only void ClearProperties () { m_Properties.clear (); }; void SetUnreachableAddressesTransportCaps (uint8_t transports); // bitmask of AddressCaps + void UpdateSupportedTransports (); bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; bool IsReachable () const { return m_Caps & Caps::eReachable; }; bool IsSSU (bool v4only = true) const; @@ -269,7 +270,7 @@ namespace data boost::shared_ptr m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9 std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; - uint8_t m_SupportedTransports, m_Caps; + uint8_t m_SupportedTransports, m_ReachableTransports, m_Caps; int m_Version; mutable std::shared_ptr m_Profile; }; From 5ce9c0f1e20079505a2549c708f6d3ac8049c88a Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 2 Jun 2021 14:45:21 -0400 Subject: [PATCH 4230/6300] build new tunnels instead slow --- libi2pd/Tunnel.h | 3 +++ libi2pd/TunnelPool.cpp | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 7e8edca7..f30eab14 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -38,6 +38,7 @@ namespace tunnel const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds const int STANDARD_NUM_RECORDS = 4; // in VariableTunnelBuild message const int MAX_NUM_RECORDS = 8; + const int HIGH_LATENCY_PER_HOP = 250; // in milliseconds enum TunnelState { @@ -98,6 +99,8 @@ namespace tunnel bool LatencyFitsRange(uint64_t lowerbound, uint64_t upperbound) const; bool LatencyIsKnown() const { return m_Latency > 0; } + bool IsSlow () const { return LatencyIsKnown() && (int)m_Latency > HIGH_LATENCY_PER_HOP*GetNumHops (); } + protected: void PrintHops (std::stringstream& s) const; diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index f2f0b4c8..b025219f 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -525,7 +525,7 @@ namespace tunnel void TunnelPool::RecreateInboundTunnel (std::shared_ptr tunnel) { - if (IsExploratory ()) // always create new exploratory tunnel + if (IsExploratory () || tunnel->IsSlow ()) // always create new exploratory tunnel or if slow { CreateInboundTunnel (); return; @@ -576,7 +576,7 @@ namespace tunnel void TunnelPool::RecreateOutboundTunnel (std::shared_ptr tunnel) { - if (IsExploratory ()) // always create new exploratory tunnel + if (IsExploratory () || tunnel->IsSlow ()) // always create new exploratory tunnel or if slow { CreateOutboundTunnel (); return; From 8e3e35a36d2f4db0929a88f08bf37dc04e2b81c0 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 2 Jun 2021 19:50:29 -0400 Subject: [PATCH 4231/6300] decrypt short request record --- libi2pd/I2NPProtocol.h | 3 ++ libi2pd/RouterContext.cpp | 64 ++++++++++++++++++++++++--------------- libi2pd/RouterContext.h | 5 ++- 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index c9b0fc04..56afeb0e 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -100,6 +100,9 @@ namespace i2p // ECIES BuildResponseRecord const size_t ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET = 0; const size_t ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET = 511; + + // ShortRequestRecordClearText + const size_t SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE = 172; enum I2NPMessageType { diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 664b457c..d1f503bb 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -875,34 +875,11 @@ namespace i2p bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data) { - if (!m_TunnelDecryptor) return false; if (IsECIES ()) - { - if (!m_InitialNoiseState) return false; - // m_InitialNoiseState is h = SHA256(h || hepk) - m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState (*m_InitialNoiseState)); - m_CurrentNoiseState->MixHash (encrypted, 32); // h = SHA256(h || sepk) - uint8_t sharedSecret[32]; - if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret, nullptr, false)) - { - LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); - return false; - } - m_CurrentNoiseState->MixKey (sharedSecret); - encrypted += 32; - uint8_t nonce[12]; - memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, - m_CurrentNoiseState->m_H, 32, m_CurrentNoiseState->m_CK + 32, nonce, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, false)) // decrypt - { - LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); - return false; - } - m_CurrentNoiseState->MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) - return true; - } + return DecryptECIESTunnelBuildRecord (encrypted, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE); else { + if (!m_TunnelDecryptor) return false; BN_CTX * ctx = BN_CTX_new (); bool success = m_TunnelDecryptor->Decrypt (encrypted, data, ctx, false); BN_CTX_free (ctx); @@ -910,6 +887,43 @@ namespace i2p } } + bool RouterContext::DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize) + { + if (!m_InitialNoiseState || !m_TunnelDecryptor) return false; + // m_InitialNoiseState is h = SHA256(h || hepk) + m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState (*m_InitialNoiseState)); + m_CurrentNoiseState->MixHash (encrypted, 32); // h = SHA256(h || sepk) + uint8_t sharedSecret[32]; + if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret, nullptr, false)) + { + LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); + return false; + } + m_CurrentNoiseState->MixKey (sharedSecret); + encrypted += 32; + uint8_t nonce[12]; + memset (nonce, 0, 12); + if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, clearTextSize, m_CurrentNoiseState->m_H, 32, + m_CurrentNoiseState->m_CK + 32, nonce, data, clearTextSize, false)) // decrypt + { + LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); + return false; + } + m_CurrentNoiseState->MixHash (encrypted, clearTextSize + 16); // h = SHA256(h || ciphertext) + return true; + } + + bool RouterContext::DecryptTunnelShortRequestRecord (const uint8_t * encrypted, uint8_t * data) + { + if (IsECIES ()) + return DecryptECIESTunnelBuildRecord (encrypted, data, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE); + else + { + LogPrint (eLogWarning, "Router: Can't decrypt short request record on non-ECIES router"); + return false; + } + } + i2p::crypto::X25519Keys& RouterContext::GetStaticKeys () { if (!m_StaticKeys) diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index ee34c2d8..10498b3b 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -97,7 +97,8 @@ namespace garlic int GetNetID () const { return m_NetID; }; void SetNetID (int netID) { m_NetID = netID; }; bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data); - + bool DecryptTunnelShortRequestRecord (const uint8_t * encrypted, uint8_t * data); + void UpdatePort (int port); // called from Daemon void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon void PublishNTCP2Address (int port, bool publish, bool v4, bool v6, bool ygg); @@ -164,6 +165,8 @@ namespace garlic bool Load (); void SaveKeys (); + bool DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize); + private: i2p::data::RouterInfo m_RouterInfo; From e740d5fc4fd2c56960fc3e635195a282f302459b Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 4 Jun 2021 12:16:50 -0400 Subject: [PATCH 4232/6300] try to pick non-slow tunnel --- libi2pd/TunnelPool.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index b025219f..f5af249a 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -172,13 +172,16 @@ namespace tunnel { if (tunnels.empty ()) return nullptr; uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0; + bool skipped = false; typename TTunnels::value_type tunnel = nullptr; for (const auto& it: tunnels) { if (it->IsEstablished () && it != excluded) { - if(HasLatencyRequirement() && it->LatencyIsKnown() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency)) { - i ++; + if (it->IsSlow () || (HasLatencyRequirement() && it->LatencyIsKnown() && + !it->LatencyFitsRange(m_MinLatency, m_MaxLatency))) + { + i++; skipped = true; continue; } tunnel = it; @@ -186,7 +189,8 @@ namespace tunnel } if (i > ind && tunnel) break; } - if(HasLatencyRequirement() && !tunnel) { + if (!tunnel && skipped) + { ind = rand () % (tunnels.size ()/2 + 1), i = 0; for (const auto& it: tunnels) { From d752a83eb568c34d529e3437205393e29a7a7cfd Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 4 Jun 2021 18:28:30 -0400 Subject: [PATCH 4233/6300] handle i2cp.dontPublishLeaseSet for all destinations --- libi2pd/Destination.cpp | 13 +++++++++++-- libi2pd/Destination.h | 5 +++-- libi2pd_client/I2CP.cpp | 6 ++---- libi2pd_client/I2CP.h | 1 - 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 9962599b..7bcfe111 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -82,6 +82,14 @@ namespace client if (it != params->end ()) m_Nickname = it->second; // otherwise we set default nickname in Start when we know local address } + it = params->find (I2CP_PARAM_DONT_PUBLISH_LEASESET); + if (it != params->end ()) + { + // oveeride isPublic + bool dontpublish = false; + i2p::config::GetOption (it->second, dontpublish); + m_IsPublic = !dontpublish; + } it = params->find (I2CP_PARAM_LEASESET_TYPE); if (it != params->end ()) m_LeaseSetType = std::stoi(it->second); @@ -509,7 +517,7 @@ namespace client // schedule verification m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, - shared_from_this (), std::placeholders::_1)); + shared_from_this (), std::placeholders::_1)); } else i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msgID); @@ -592,7 +600,8 @@ namespace client // assume it successive and try to verify m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, - shared_from_this (), std::placeholders::_1)); + shared_from_this (), std::placeholders::_1)); + } } } diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 40c9e9ab..6695796d 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -61,14 +61,15 @@ namespace client const char I2CP_PARAM_RATCHET_OUTBOUND_TAGS[] = "crypto.ratchet.outboundTags"; // not used yet const char I2CP_PARAM_INBOUND_NICKNAME[] = "inbound.nickname"; const char I2CP_PARAM_OUTBOUND_NICKNAME[] = "outbound.nickname"; + const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet"; const char I2CP_PARAM_LEASESET_TYPE[] = "i2cp.leaseSetType"; const int DEFAULT_LEASESET_TYPE = 3; const char I2CP_PARAM_LEASESET_ENCRYPTION_TYPE[] = "i2cp.leaseSetEncType"; const char I2CP_PARAM_LEASESET_PRIV_KEY[] = "i2cp.leaseSetPrivKey"; // PSK decryption key, base64 const char I2CP_PARAM_LEASESET_AUTH_TYPE[] = "i2cp.leaseSetAuthType"; const char I2CP_PARAM_LEASESET_CLIENT_DH[] = "i2cp.leaseSetClient.dh"; // group of i2cp.leaseSetClient.dh.nnn - const char I2CP_PARAM_LEASESET_CLIENT_PSK[] = "i2cp.leaseSetClient.psk"; // group of i2cp.leaseSetClient.psk.nnn - + const char I2CP_PARAM_LEASESET_CLIENT_PSK[] = "i2cp.leaseSetClient.psk"; // group of i2cp.leaseSetClient.psk.nnn + // latency const char I2CP_PARAM_MIN_TUNNEL_LATENCY[] = "latency.min"; const int DEFAULT_MIN_TUNNEL_LATENCY = 0; diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index d07c6248..25a5504a 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -544,13 +544,11 @@ namespace client offset += 8; // date if (identity->Verify (buf, offset, buf + offset)) // signature { - bool isPublic = true; - if (params[I2CP_PARAM_DONT_PUBLISH_LEASESET] == "true") isPublic = false; if (!m_Destination) { m_Destination = m_Owner.IsSingleThread () ? - std::make_shared(m_Owner.GetService (), shared_from_this (), identity, isPublic, params): - std::make_shared(shared_from_this (), identity, isPublic, params); + std::make_shared(m_Owner.GetService (), shared_from_this (), identity, true, params): + std::make_shared(shared_from_this (), identity, true, params); SendSessionStatusMessage (1); // created LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created"); m_Destination->Start (); diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 085bf30a..9a8bda4e 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -62,7 +62,6 @@ namespace client }; // params - const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet"; const char I2CP_PARAM_MESSAGE_RELIABILITY[] = "i2cp.messageReliability"; class I2CPSession; From 5fb426b33612d983d3cb78f2e503e1c12d226512 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 6 Jun 2021 13:55:38 -0400 Subject: [PATCH 4234/6300] decrypt and encrypt reply for short tunnel build message --- libi2pd/I2NPProtocol.cpp | 73 +++++++++++++++++++++++++++++++++++++--- libi2pd/I2NPProtocol.h | 8 +++-- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 7f059d72..e47c9add 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -458,7 +458,7 @@ namespace i2p LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); if (len < num*TUNNEL_BUILD_RECORD_SIZE + 1) { - LogPrint (eLogError, "VaribleTunnelBuild message of ", num, " records is too short ", len); + LogPrint (eLogError, "I2NP: VaribleTunnelBuild message of ", num, " records is too short ", len); return; } @@ -526,12 +526,12 @@ namespace i2p { if (i2p::context.IsECIES ()) { - LogPrint (eLogWarning, "TunnelBuild is too old for ECIES router"); + LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router"); return; } if (len < NUM_TUNNEL_BUILD_RECORDS*TUNNEL_BUILD_RECORD_SIZE) { - LogPrint (eLogError, "TunnelBuild message is too short ", len); + LogPrint (eLogError, "I2NP: TunnelBuild message is too short ", len); return; } uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; @@ -558,7 +558,7 @@ namespace i2p LogPrint (eLogDebug, "I2NP: VariableTunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1) { - LogPrint (eLogError, "VaribleTunnelBuildReply message of ", num, " records is too short ", len); + LogPrint (eLogError, "I2NP: VaribleTunnelBuildReply message of ", num, " records is too short ", len); return; } @@ -582,7 +582,66 @@ namespace i2p LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found"); } - + void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + { + if (!i2p::context.IsECIES ()) + { + LogPrint (eLogWarning, "I2NP: ShortTunnelBuild can be handled by ECIES router only"); + return; + } + int num = buf[0]; + LogPrint (eLogDebug, "I2NP: ShortTunnelBuild ", num, " records"); + if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1) + { + LogPrint (eLogError, "I2NP: ShortTunnelBuild message of ", num, " records is too short ", len); + return; + } + // TODO: check replyMsgID + const uint8_t * record = buf + 1; + for (int i = 0; i < num; i++) + { + if (!memcmp (record, (const uint8_t *)i2p::context.GetRouterInfo ().GetIdentHash (), 16)) + { + LogPrint (eLogDebug, "I2NP: Short request record ", i, " is ours"); + uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) + { + LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i); + return; + } + // TODO: fill reply + // encrypt reply + auto noiseState = std::move (i2p::context.GetCurrentNoiseState ()); + if (!noiseState) + { + LogPrint (eLogWarning, "I2NP: Invalid Noise state for short reply encryption"); + return; + } + uint8_t nonce[12]; + memset (nonce, 0, 12); + uint8_t * reply = buf + 1; + for (int j = 0; j < num; j++) + { + if (j == i) + { + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState->m_H, 32, noiseState->m_CK, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); + return; + } + } + else + i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, noiseState->m_CK, nonce, reply); + reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + // TODO: send + return; + } + record += SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + } + std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf) { auto msg = NewI2NPTunnelMessage (); @@ -700,6 +759,9 @@ namespace i2p case eI2NPVariableTunnelBuildReply: HandleVariableTunnelBuildReplyMsg (msgID, buf, size); break; + case eI2NPShortTunnelBuild: + HandleShortTunnelBuildMsg (msgID, buf, size); + break; case eI2NPTunnelBuild: HandleTunnelBuildMsg (buf, size); break; @@ -756,6 +818,7 @@ namespace i2p case eI2NPVariableTunnelBuildReply: case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: + case eI2NPShortTunnelBuild: // forward to tunnel thread i2p::tunnel::tunnels.PostTunnelData (msg); break; diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 56afeb0e..5db93978 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -55,7 +55,8 @@ namespace i2p // TunnelBuild const size_t TUNNEL_BUILD_RECORD_SIZE = 528; - + const size_t SHORT_TUNNEL_BUILD_RECORD_SIZE = 236; + //BuildRequestRecordClearText const size_t BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; const size_t BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET = BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; @@ -102,6 +103,7 @@ namespace i2p const size_t ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET = 511; // ShortRequestRecordClearText + const size_t SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET = 16; const size_t SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE = 172; enum I2NPMessageType @@ -118,7 +120,8 @@ namespace i2p eI2NPTunnelBuild = 21, eI2NPTunnelBuildReply = 22, eI2NPVariableTunnelBuild = 23, - eI2NPVariableTunnelBuildReply = 24 + eI2NPVariableTunnelBuildReply = 24, + eI2NPShortTunnelBuild = 25 }; const int NUM_TUNNEL_BUILD_RECORDS = 8; @@ -287,6 +290,7 @@ namespace tunnel bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); + void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); void HandleTunnelBuildMsg (uint8_t * buf, size_t len); std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf); From 48d9a03aa894169aab777917a9f8b7bace87909c Mon Sep 17 00:00:00 2001 From: acetone <63557806+acetoneRu@users.noreply.github.com> Date: Mon, 7 Jun 2021 12:58:57 -0400 Subject: [PATCH 4235/6300] tbytes in WinApp --- Win32/Win32App.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index db5a1f3d..133b57b6 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -132,7 +132,11 @@ namespace win32 auto mbytes = transfer & 0x03ff; transfer >>= 10; auto gbytes = transfer & 0x03ff; + transfer >>= 10; + auto tbytes = transfer & 0x03ff; + if (tbytes) + s << tbytes << " TB, "; if (gbytes) s << gbytes << " GB, "; if (mbytes) From 857183048513220dc6c29751b094eeb7a193758f Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 7 Jun 2021 18:28:36 -0400 Subject: [PATCH 4236/6300] create transit tunnel and reply for short tunnel build --- libi2pd/I2NPProtocol.cpp | 20 +++++++++++++++++--- libi2pd/I2NPProtocol.h | 9 +++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index e47c9add..760bdf59 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -424,7 +424,7 @@ namespace i2p { uint8_t nonce[12]; memset (nonce, 0, 12); - auto noiseState = std::move (i2p::context.GetCurrentNoiseState ()); + auto& noiseState = i2p::context.GetCurrentNoiseState (); if (!noiseState || !i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, noiseState->m_H, 32, noiseState->m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt { @@ -609,9 +609,19 @@ namespace i2p LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i); return; } + auto& noiseState = i2p::context.GetCurrentNoiseState (); + uint8_t layerKeys[64]; // (layer key, iv key) + i2p::crypto::HKDF (noiseState->m_CK + 32, nullptr, 0, "LayerAndIVKeys", layerKeys); // TODO: correct domain + auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( + bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + layerKeys, layerKeys + 32, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & 0x80, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & 0x40); + i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); // TODO: fill reply // encrypt reply - auto noiseState = std::move (i2p::context.GetCurrentNoiseState ()); if (!noiseState) { LogPrint (eLogWarning, "I2NP: Invalid Noise state for short reply encryption"); @@ -622,6 +632,7 @@ namespace i2p uint8_t * reply = buf + 1; for (int j = 0; j < num; j++) { + nonce[4] = j; // nonce is record # if (j == i) { if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, @@ -635,7 +646,10 @@ namespace i2p i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, noiseState->m_CK, nonce, reply); reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; } - // TODO: send + // TODO: send reply + transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, + CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); return; } record += SHORT_TUNNEL_BUILD_RECORD_SIZE; diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 5db93978..76030e91 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -104,6 +104,15 @@ namespace i2p // ShortRequestRecordClearText const size_t SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET = 16; + const size_t SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; + const size_t SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET = SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_FLAG_OFFSET = SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32; + const size_t SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET = SHORT_REQUEST_RECORD_FLAG_OFFSET + 1; + const size_t SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE = SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET + 2; + const size_t SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET = SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE + 1; + const size_t SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET = SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET = SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET + 4; const size_t SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE = 172; enum I2NPMessageType From d599502b1a31c45095e545f84de4f90c3fc88a4a Mon Sep 17 00:00:00 2001 From: acetone <63557806+acetoneRu@users.noreply.github.com> Date: Mon, 7 Jun 2021 23:49:56 -0400 Subject: [PATCH 4237/6300] 1000Gb+ display --- Win32/Win32App.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 133b57b6..7104ec7f 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -131,12 +131,8 @@ namespace win32 transfer >>= 10; auto mbytes = transfer & 0x03ff; transfer >>= 10; - auto gbytes = transfer & 0x03ff; - transfer >>= 10; - auto tbytes = transfer & 0x03ff; + auto gbytes = transfer; - if (tbytes) - s << tbytes << " TB, "; if (gbytes) s << gbytes << " GB, "; if (mbytes) From 8e4781b0f7d82f2e048d0334a5069e2d8b3ff3f0 Mon Sep 17 00:00:00 2001 From: acetone <63557806+acetoneRu@users.noreply.github.com> Date: Tue, 8 Jun 2021 09:39:28 -0400 Subject: [PATCH 4238/6300] tbytes in WinApp (#1660) --- Win32/Win32App.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index db5a1f3d..7104ec7f 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -131,7 +131,7 @@ namespace win32 transfer >>= 10; auto mbytes = transfer & 0x03ff; transfer >>= 10; - auto gbytes = transfer & 0x03ff; + auto gbytes = transfer; if (gbytes) s << gbytes << " GB, "; From 3b051dbba34bcfd3c985ef93e194c1cfc586a270 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 8 Jun 2021 15:36:27 -0400 Subject: [PATCH 4239/6300] send OutboundTunnelBuildReply --- libi2pd/I2NPProtocol.cpp | 114 ++++++++++++++++++++++++++------------- libi2pd/I2NPProtocol.h | 7 ++- libi2pd/TunnelConfig.cpp | 6 +-- 3 files changed, 84 insertions(+), 43 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 760bdf59..5ed875c5 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,6 +18,7 @@ #include "Tunnel.h" #include "Transports.h" #include "Garlic.h" +#include "ECIESX25519AEADRatchetSession.h" #include "I2NPProtocol.h" #include "version.h" @@ -387,16 +388,16 @@ namespace i2p bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80, - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) : + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) : i2p::tunnel::CreateTransitTunnel ( bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x80, - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40); + clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); } else @@ -486,7 +487,7 @@ namespace i2p uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; if (HandleBuildRequestRecords (num, buf + 1, clearText)) { - if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel + if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel { // so we send it to reply tunnel transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, @@ -505,7 +506,7 @@ namespace i2p uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; if (HandleBuildRequestRecords (num, buf + 1, clearText)) { - if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outboud tunnel + if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel { // so we send it to reply tunnel transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, @@ -537,7 +538,7 @@ namespace i2p uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, buf, clearText)) { - if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & 0x40) // we are endpoint of outbound tunnel + if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outbound tunnel { // so we send it to reply tunnel transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, @@ -610,6 +611,11 @@ namespace i2p return; } auto& noiseState = i2p::context.GetCurrentNoiseState (); + if (!noiseState) + { + LogPrint (eLogWarning, "I2NP: Invalid Noise state for short reply encryption"); + return; + } uint8_t layerKeys[64]; // (layer key, iv key) i2p::crypto::HKDF (noiseState->m_CK + 32, nullptr, 0, "LayerAndIVKeys", layerKeys); // TODO: correct domain auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( @@ -617,39 +623,71 @@ namespace i2p clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), layerKeys, layerKeys + 32, - clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & 0x80, - clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & 0x40); + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); - // TODO: fill reply - // encrypt reply - if (!noiseState) - { - LogPrint (eLogWarning, "I2NP: Invalid Noise state for short reply encryption"); - return; - } - uint8_t nonce[12]; - memset (nonce, 0, 12); - uint8_t * reply = buf + 1; - for (int j = 0; j < num; j++) - { - nonce[4] = j; // nonce is record # - if (j == i) + if (clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) + { + // we are endpoint, create OutboundTunnelBuildReply + auto otbrm = NewI2NPShortMessage (); + auto payload = otbrm->GetPayload (); + payload[0] = num; // num + payload[1] = i; // slot + payload +=2; + // reply + htobe16buf (payload, 3); payload += 2; // length, TODO + memset (payload, 0, 3); payload += 3; // ClearText: no options, and zero ret code. TODO + // ShortBuildReplyRecords. Exclude ours + uint8_t * records = buf + 1; + if (i > 0) + { + memcpy (payload, records, i*SHORT_TUNNEL_BUILD_RECORD_SIZE); + payload += i*SHORT_TUNNEL_BUILD_RECORD_SIZE; + records += i*SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + if (i < num-1) { - if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState->m_H, 32, noiseState->m_CK, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt - { - LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); - return; - } - } - else - i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, noiseState->m_CK, nonce, reply); - reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; + memcpy (payload, records, (num-1-i)*SHORT_TUNNEL_BUILD_RECORD_SIZE); + payload += (num-1-i)*SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + otbrm->len += (payload - otbrm->GetPayload ()); + otbrm->FillI2NPMessageHeader (eI2NPOutboundTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); + uint8_t replyKeys[64]; // (reply key, tag) + i2p::crypto::HKDF (noiseState->m_CK, nullptr, 0, "ReplyKeyAndTag", replyKeys); // TODO: correct domain + uint64_t tag; + memcpy (&tag, replyKeys + 32, 8); + // send garlic to reply tunnel + transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + i2p::garlic::WrapECIESX25519AEADRatchetMessage (otbrm, replyKeys, tag))); } - // TODO: send reply - transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, - CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, - bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + else + { + // we are participant, encrypt reply + uint8_t nonce[12]; + memset (nonce, 0, 12); + uint8_t * reply = buf + 1; + for (int j = 0; j < num; j++) + { + nonce[4] = j; // nonce is record # + if (j == i) + { + // TODO: fill reply + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState->m_H, 32, noiseState->m_CK, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); + return; + } + } + else + i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, noiseState->m_CK, nonce, reply); + reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, + CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + } return; } record += SHORT_TUNNEL_BUILD_RECORD_SIZE; diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 76030e91..ee143734 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -130,9 +130,12 @@ namespace i2p eI2NPTunnelBuildReply = 22, eI2NPVariableTunnelBuild = 23, eI2NPVariableTunnelBuildReply = 24, - eI2NPShortTunnelBuild = 25 + eI2NPShortTunnelBuild = 25, + eI2NPOutboundTunnelBuildReply = 26 }; + const uint8_t TUNNEL_BUILD_RECORD_GATEWAY_FLAG = 0x80; + const uint8_t TUNNEL_BUILD_RECORD_ENDPOINT_FLAG = 0x40; const int NUM_TUNNEL_BUILD_RECORDS = 8; // DatabaseLookup flags diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index d43483c0..8f515c5d 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -81,8 +81,8 @@ namespace tunnel void TunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) { uint8_t flag = 0; - if (isGateway) flag |= 0x80; - if (isEndpoint) flag |= 0x40; + if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; + if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; auto encryptor = ident->CreateEncryptor (nullptr); if (IsECIES ()) { From ed53cbb7b7b544e7c593b9d2bde8d5159fdd36bd Mon Sep 17 00:00:00 2001 From: idk Date: Tue, 8 Jun 2021 16:25:45 -0400 Subject: [PATCH 4240/6300] OK that's my first working C wrapper, but I don't yet know how to do anything other than initialize, start, and stop a router --- libi2pd/capi.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ libi2pd/capi.h | 43 ++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 libi2pd/capi.cpp create mode 100644 libi2pd/capi.h diff --git a/libi2pd/capi.cpp b/libi2pd/capi.cpp new file mode 100644 index 00000000..1decb717 --- /dev/null +++ b/libi2pd/capi.cpp @@ -0,0 +1,80 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include "capi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void C_InitI2P (int argc, char* argv[], const char * appName) +{ + return i2p::api::InitI2P(argc, argv, appName); +} + +void C_TerminateI2P () +{ + return i2p::api::TerminateI2P(); +} + +void C_StartI2P (std::shared_ptr logStream) +{ + return i2p::api::StartI2P(logStream); +} + +void C_StopI2P () +{ + return i2p::api::StopI2P(); +} + +void C_RunPeerTest () +{ + return i2p::api::RunPeerTest(); +} + +std::shared_ptr C_CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, + const std::map * params) +{ + return i2p::api::CreateLocalDestination(keys, isPublic, params); +} + +std::shared_ptr C_CreateTransientLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, + const std::map * params) +{ + return i2p::api::CreateLocalDestination(isPublic, sigType, params); +} + +void C_DestroyLocalDestination (std::shared_ptr dest) +{ + return i2p::api::DestroyLocalDestination(dest); +} + +void C_RequestLeaseSet (std::shared_ptr dest, const i2p::data::IdentHash& remote) +{ + return i2p::api::RequestLeaseSet(dest, remote); +} + +std::shared_ptr C_CreateStream (std::shared_ptr dest, const i2p::data::IdentHash& remote) +{ + return i2p::api::CreateStream(dest, remote); +} + +void C_AcceptStream (std::shared_ptr dest, const i2p::stream::StreamingDestination::Acceptor& acceptor) +{ + return i2p::api::AcceptStream(dest, acceptor); +} + +void C_DestroyStream (std::shared_ptr stream) +{ + return i2p::api::DestroyStream(stream); +} + +#ifdef __cplusplus +} +#endif + diff --git a/libi2pd/capi.h b/libi2pd/capi.h new file mode 100644 index 00000000..70b9228e --- /dev/null +++ b/libi2pd/capi.h @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef CAPI_H__ +#define CAPI_H__ + +#include "api.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// initialization start and stop +void C_InitI2P (int argc, char* argv[], const char * appName); +void C_TerminateI2P (); +void C_StartI2P (std::shared_ptr logStream = nullptr); +// write system log to logStream, if not specified to .log in application's folder +void C_StopI2P (); +void C_RunPeerTest (); // should be called after UPnP + +// destinations +std::shared_ptr C_CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, + const std::map * params = nullptr); +std::shared_ptr C_CreateTransientLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, + const std::map * params = nullptr); // transient destinations usually not published +void C_DestroyLocalDestination (std::shared_ptr dest); + +// streams +void C_RequestLeaseSet (std::shared_ptr dest, const i2p::data::IdentHash& remote); +std::shared_ptr C_CreateStream (std::shared_ptr dest, const i2p::data::IdentHash& remote); +void C_AcceptStream (std::shared_ptr dest, const i2p::stream::StreamingDestination::Acceptor& acceptor); +void C_DestroyStream (std::shared_ptr stream); + +#ifdef __cplusplus +} +#endif + +#endif From 83fd289e4651b4e23c2ebc7b7a58222b5fbcf9b6 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 9 Jun 2021 12:49:50 -0400 Subject: [PATCH 4241/6300] don't re-create noise state for every message --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 9 +++++---- libi2pd/ECIESX25519AEADRatchetSession.h | 5 +++++ libi2pd/I2NPProtocol.cpp | 17 ++++++----------- libi2pd/RouterContext.cpp | 19 ++++++++----------- libi2pd/RouterContext.h | 4 ++-- 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 45affde7..55e962d8 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1109,21 +1109,22 @@ namespace garlic bool RouterIncomingRatchetSession::HandleNextMessage (const uint8_t * buf, size_t len) { if (!GetOwner ()) return false; - i2p::crypto::NoiseSymmetricState state (GetNoiseState ()); + m_CurrentNoiseState = GetNoiseState (); // we are Bob - state.MixHash (buf, 32); + m_CurrentNoiseState.MixHash (buf, 32); uint8_t sharedSecret[32]; if (!GetOwner ()->Decrypt (buf, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) { LogPrint (eLogWarning, "Garlic: Incorrect N ephemeral public key"); return false; } - state.MixKey (sharedSecret); + m_CurrentNoiseState.MixKey (sharedSecret); buf += 32; len -= 32; uint8_t nonce[12]; CreateNonce (0, nonce); std::vector payload (len - 16); - if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, state.m_H, 32, state.m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (buf, len - 16, m_CurrentNoiseState.m_H, 32, + m_CurrentNoiseState.m_CK + 32, nonce, payload.data (), len - 16, false)) // decrypt { LogPrint (eLogWarning, "Garlic: Payload for router AEAD verification failed"); return false; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 43e88ca6..cd7b9b40 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -249,6 +249,11 @@ namespace garlic RouterIncomingRatchetSession (const i2p::crypto::NoiseSymmetricState& initState); bool HandleNextMessage (const uint8_t * buf, size_t len); + i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; + + private: + + i2p::crypto::NoiseSymmetricState m_CurrentNoiseState; }; std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag); diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 5ed875c5..7c4d84c3 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -426,8 +426,8 @@ namespace i2p uint8_t nonce[12]; memset (nonce, 0, 12); auto& noiseState = i2p::context.GetCurrentNoiseState (); - if (!noiseState || !i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState->m_H, 32, noiseState->m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt { LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); return false; @@ -611,13 +611,8 @@ namespace i2p return; } auto& noiseState = i2p::context.GetCurrentNoiseState (); - if (!noiseState) - { - LogPrint (eLogWarning, "I2NP: Invalid Noise state for short reply encryption"); - return; - } uint8_t layerKeys[64]; // (layer key, iv key) - i2p::crypto::HKDF (noiseState->m_CK + 32, nullptr, 0, "LayerAndIVKeys", layerKeys); // TODO: correct domain + i2p::crypto::HKDF (noiseState.m_CK + 32, nullptr, 0, "LayerAndIVKeys", layerKeys); // TODO: correct domain auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, @@ -653,7 +648,7 @@ namespace i2p otbrm->len += (payload - otbrm->GetPayload ()); otbrm->FillI2NPMessageHeader (eI2NPOutboundTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); uint8_t replyKeys[64]; // (reply key, tag) - i2p::crypto::HKDF (noiseState->m_CK, nullptr, 0, "ReplyKeyAndTag", replyKeys); // TODO: correct domain + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "ReplyKeyAndTag", replyKeys); // TODO: correct domain uint64_t tag; memcpy (&tag, replyKeys + 32, 8); // send garlic to reply tunnel @@ -674,14 +669,14 @@ namespace i2p { // TODO: fill reply if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState->m_H, 32, noiseState->m_CK, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + noiseState.m_H, 32, noiseState.m_CK, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt { LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); return; } } else - i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, noiseState->m_CK, nonce, reply); + i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, noiseState.m_CK, nonce, reply); reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; } transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index d1f503bb..78be6305 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -45,10 +45,8 @@ namespace i2p UpdateRouterInfo (); if (IsECIES ()) { - auto initState = new i2p::crypto::NoiseSymmetricState (); - i2p::crypto::InitNoiseNState (*initState, GetIdentity ()->GetEncryptionPublicKey ()); - m_InitialNoiseState.reset (initState); - m_ECIESSession = std::make_shared(*initState); + i2p::crypto::InitNoiseNState (m_InitialNoiseState, GetIdentity ()->GetEncryptionPublicKey ()); + m_ECIESSession = std::make_shared(m_InitialNoiseState); } } @@ -889,27 +887,26 @@ namespace i2p bool RouterContext::DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize) { - if (!m_InitialNoiseState || !m_TunnelDecryptor) return false; // m_InitialNoiseState is h = SHA256(h || hepk) - m_CurrentNoiseState.reset (new i2p::crypto::NoiseSymmetricState (*m_InitialNoiseState)); - m_CurrentNoiseState->MixHash (encrypted, 32); // h = SHA256(h || sepk) + m_CurrentNoiseState = m_InitialNoiseState; + m_CurrentNoiseState.MixHash (encrypted, 32); // h = SHA256(h || sepk) uint8_t sharedSecret[32]; if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret, nullptr, false)) { LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); return false; } - m_CurrentNoiseState->MixKey (sharedSecret); + m_CurrentNoiseState.MixKey (sharedSecret); encrypted += 32; uint8_t nonce[12]; memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, clearTextSize, m_CurrentNoiseState->m_H, 32, - m_CurrentNoiseState->m_CK + 32, nonce, data, clearTextSize, false)) // decrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, clearTextSize, m_CurrentNoiseState.m_H, 32, + m_CurrentNoiseState.m_CK + 32, nonce, data, clearTextSize, false)) // decrypt { LogPrint (eLogWarning, "Router: Tunnel record AEAD decryption failed"); return false; } - m_CurrentNoiseState->MixHash (encrypted, clearTextSize + 16); // h = SHA256(h || ciphertext) + m_CurrentNoiseState.MixHash (encrypted, clearTextSize + 16); // h = SHA256(h || ciphertext) return true; } diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 10498b3b..acb33795 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -125,7 +125,7 @@ namespace garlic void SetSupportsV4 (bool supportsV4); void SetSupportsMesh (bool supportsmesh, const boost::asio::ip::address_v6& host); bool IsECIES () const { return GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; - std::unique_ptr& GetCurrentNoiseState () { return m_CurrentNoiseState; }; + i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove void UpdateStats (); @@ -185,7 +185,7 @@ namespace garlic std::unique_ptr m_NTCP2Keys; std::unique_ptr m_StaticKeys; // for ECIESx25519 - std::unique_ptr m_InitialNoiseState, m_CurrentNoiseState; + i2p::crypto::NoiseSymmetricState m_InitialNoiseState, m_CurrentNoiseState; // i18n std::shared_ptr m_Language; From 8708a0076f34c25cd9ffd03eb341d055f8e1b45e Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 9 Jun 2021 22:22:57 +0300 Subject: [PATCH 4242/6300] fix build with boost < 1.55.0 (closes #1661) Signed-off-by: R4SAS --- libi2pd/NTCP2.cpp | 4 ++++ libi2pd/SSU.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 72e822cd..c231863d 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1201,7 +1201,11 @@ namespace transport if (!m_Address6 && !m_YggdrasilAddress) // only if not binded to address { // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others +#if (BOOST_VERSION >= 105500) typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; +#else + typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; +#endif m_NTCP2V6Acceptor->set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); } #endif diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index e4901f7c..f3da611c 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -70,7 +70,11 @@ namespace transport if (m_EndpointV6.address() == boost::asio::ip::address().from_string("::")) // only if not binded to address { // Set preference to use public IPv6 address -- tested on linux, not works on windows, and not tested on others +#if (BOOST_VERSION >= 105500) typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; +#else + typedef boost::asio::detail::socket_option::integer ipv6PreferAddr; +#endif m_SocketV6.set_option (ipv6PreferAddr(IPV6_PREFER_SRC_PUBLIC | IPV6_PREFER_SRC_HOME | IPV6_PREFER_SRC_NONCGA)); } #endif From a92b93192d843c63f19618c5ad39bd71bba89e31 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 10 Jun 2021 13:24:04 -0400 Subject: [PATCH 4243/6300] reg.i2p for subscriptions --- libi2pd/Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index f5316860..a80778a1 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -221,7 +221,7 @@ namespace config { ("addressbook.defaulturl", value()->default_value( "http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt" ), "AddressBook subscription URL for initial setup") - ("addressbook.subscriptions", value()->default_value(""), "AddressBook subscriptions URLs, separated by comma") + ("addressbook.subscriptions", value()->default_value("http://reg.i2p/hosts.txt"), "AddressBook subscriptions URLs, separated by comma") ("addressbook.hostsfile", value()->default_value(""), "File to dump addresses in hosts.txt format"); options_description trust("Trust options"); From e412b17f709af249ffa20a08cb111090631e8d0e Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 11 Jun 2021 08:34:56 -0400 Subject: [PATCH 4244/6300] don't publish slow tunnel in LeaseSet if possible --- libi2pd/TunnelPool.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index f5af249a..e45c6090 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -142,16 +142,24 @@ namespace tunnel { std::vector > v; int i = 0; + std::shared_ptr slowTunnel; std::unique_lock l(m_InboundTunnelsMutex); for (const auto& it : m_InboundTunnels) { if (i >= num) break; if (it->IsEstablished ()) { - v.push_back (it); - i++; + if (it->IsSlow () && !slowTunnel) + slowTunnel = it; + else + { + v.push_back (it); + i++; + } } } + if (slowTunnel && (int)v.size () < (num/2+1)) + v.push_back (slowTunnel); return v; } From bce6685d0cda5127ff072aeb6a701b666fcd5d49 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 14 Jun 2021 12:36:54 -0400 Subject: [PATCH 4245/6300] correct check of ipv4/ipv6 address --- libi2pd/RouterContext.cpp | 4 ++-- libi2pd/RouterInfo.cpp | 30 ++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 78be6305..50dd6c59 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -484,7 +484,7 @@ namespace i2p addr->ssu->introducers.clear (); port = addr->port; } - // unpiblish NTCP2 addreeses + // unpublish NTCP2 addreeses bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); if (ntcp2) PublishNTCP2Address (port, false, v4, v6, false); @@ -677,7 +677,7 @@ namespace i2p if (addr->IsPublishedNTCP2 ()) { bool isYgg1 = i2p::util::net::IsYggdrasilAddress (addr->host); - if (addr->host.is_v6 () && ((isYgg && isYgg1) || (!isYgg && !isYgg1))) + if (addr->IsV6 () && ((isYgg && isYgg1) || (!isYgg && !isYgg1))) { if (addr->host != host) { diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 3035bd31..dcef9463 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -554,7 +554,7 @@ namespace data if (address.IsNTCP2 ()) { WriteString ("NTCP2", s); - if (address.IsPublishedNTCP2 () && !address.host.is_unspecified ()) + if (address.IsPublishedNTCP2 () && !address.host.is_unspecified () && address.port) isPublished = true; else { @@ -998,9 +998,16 @@ namespace data for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; - addr->caps &= ~AddressCaps::eV6; - if (addr->host.is_v6 ()) - it = m_Addresses->erase (it); + if (addr->IsV6 ()) + { + if (addr->IsV4 ()) + { + addr->caps &= ~AddressCaps::eV6; + ++it; + } + else + it = m_Addresses->erase (it); + } else ++it; } @@ -1015,9 +1022,16 @@ namespace data for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; - addr->caps &= ~AddressCaps::eV4; - if (addr->host.is_v4 ()) - it = m_Addresses->erase (it); + if (addr->IsV4 ()) + { + if (addr->IsV6 ()) + { + addr->caps &= ~AddressCaps::eV4; + ++it; + } + else + it = m_Addresses->erase (it); + } else ++it; } @@ -1188,7 +1202,7 @@ namespace data for (auto& addr: *m_Addresses) { // TODO: implement SSU - if (addr->transportStyle == eTransportNTCP && (!addr->IsPublishedNTCP2 () || addr->port)) + if (addr->transportStyle == eTransportNTCP && !addr->IsPublishedNTCP2 ()) { addr->caps &= ~(eV4 | eV6); addr->caps |= transports; From 631c8c9870bf48b9a4ff7f57daf82ae28c783251 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 14 Jun 2021 21:19:44 -0400 Subject: [PATCH 4246/6300] use correct address type for NTCP2 acceptors --- libi2pd/NTCP2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index c231863d..cb200b42 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1170,7 +1170,7 @@ namespace transport if (!address) continue; if (address->IsPublishedNTCP2 () && address->port) { - if (address->host.is_v4()) + if (address->IsV4()) { try { @@ -1189,7 +1189,7 @@ namespace transport auto conn = std::make_shared(*this); m_NTCP2Acceptor->async_accept(conn->GetSocket (), std::bind (&NTCP2Server::HandleAccept, this, conn, std::placeholders::_1)); } - else if (address->host.is_v6() && (context.SupportsV6 () || context.SupportsMesh ())) + else if (address->IsV6() && (context.SupportsV6 () || context.SupportsMesh ())) { m_NTCP2V6Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService ())); try From 1d973bc3aca52e2aab02876fa6d851911c522d15 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 15 Jun 2021 17:55:09 +0300 Subject: [PATCH 4247/6300] [webconsole] remove extra line break Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index e184dd06..dadb2f8f 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -691,7 +691,7 @@ namespace http { if (Daemon.gracefulShutdownInterval) s << " " << tr("Cancel graceful shutdown") << "\r\n"; else - s << " " << tr("Start graceful shutdown") << "
    \r\n"; + s << " " << tr("Start graceful shutdown") << "\r\n"; #elif defined(WIN32_APP) if (i2p::util::DaemonWin32::Instance().isGraceful) s << " " << tr("Cancel graceful shutdown") << "\r\n"; From b962a330ad863d7c5a8cbbdb7ab156902e8dc7d1 Mon Sep 17 00:00:00 2001 From: idk Date: Tue, 15 Jun 2021 12:02:57 -0400 Subject: [PATCH 4248/6300] Allow passing raw pointers to C wrapper functions, I think --- Makefile | 5 +++++ libi2pd/Destination.h | 8 ++++++++ libi2pd/capi.cpp | 38 ++++++++++++++++++++++---------------- libi2pd/capi.h | 17 +++++++++-------- 4 files changed, 44 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index 40e72918..7a033bf2 100644 --- a/Makefile +++ b/Makefile @@ -136,3 +136,8 @@ doxygen: .PHONY: mk_obj_dir .PHONY: install .PHONY: strip + +##TODO: delete this before a PR +testc: api api_client + g++ -Ii18n -c test.c -o test.o + g++ test.o libi2pd.so libi2pdclient.so -o test.main \ No newline at end of file diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 6695796d..f24e31ca 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -28,6 +28,10 @@ #include "Datagram.h" #include "util.h" +#ifdef __cplusplus +extern "C" { +#endif + namespace i2p { namespace client @@ -312,4 +316,8 @@ namespace client } } +#ifdef __cplusplus +} +#endif + #endif diff --git a/libi2pd/capi.cpp b/libi2pd/capi.cpp index 1decb717..b1f94d1f 100644 --- a/libi2pd/capi.cpp +++ b/libi2pd/capi.cpp @@ -22,9 +22,10 @@ void C_TerminateI2P () return i2p::api::TerminateI2P(); } -void C_StartI2P (std::shared_ptr logStream) +void C_StartI2P (std::ostream *logStream) { - return i2p::api::StartI2P(logStream); + std::shared_ptr cppLogStream(logStream); + return i2p::api::StartI2P(cppLogStream); } void C_StopI2P () @@ -37,41 +38,46 @@ void C_RunPeerTest () return i2p::api::RunPeerTest(); } -std::shared_ptr C_CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, +i2p::client::ClientDestination *C_CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { - return i2p::api::CreateLocalDestination(keys, isPublic, params); + return i2p::api::CreateLocalDestination(keys, isPublic, params).get(); } -std::shared_ptr C_CreateTransientLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, +i2p::client::ClientDestination *C_CreateTransientLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, const std::map * params) { - return i2p::api::CreateLocalDestination(isPublic, sigType, params); + return i2p::api::CreateLocalDestination(isPublic, sigType, params).get(); } -void C_DestroyLocalDestination (std::shared_ptr dest) +void C_DestroyLocalDestination (i2p::client::ClientDestination *dest) { - return i2p::api::DestroyLocalDestination(dest); + std::shared_ptr cppDest(dest); + return i2p::api::DestroyLocalDestination(cppDest); } -void C_RequestLeaseSet (std::shared_ptr dest, const i2p::data::IdentHash& remote) +void C_RequestLeaseSet (i2p::client::ClientDestination *dest, const i2p::data::IdentHash& remote) { - return i2p::api::RequestLeaseSet(dest, remote); + std::shared_ptr cppDest(dest); + return i2p::api::RequestLeaseSet(cppDest, remote); } -std::shared_ptr C_CreateStream (std::shared_ptr dest, const i2p::data::IdentHash& remote) +i2p::stream::Stream *C_CreateStream (i2p::client::ClientDestination *dest, const i2p::data::IdentHash& remote) { - return i2p::api::CreateStream(dest, remote); + std::shared_ptr cppDest(dest); + return i2p::api::CreateStream(cppDest, remote).get(); } -void C_AcceptStream (std::shared_ptr dest, const i2p::stream::StreamingDestination::Acceptor& acceptor) +void C_AcceptStream (i2p::client::ClientDestination *dest, const i2p::stream::StreamingDestination::Acceptor& acceptor) { - return i2p::api::AcceptStream(dest, acceptor); + std::shared_ptr cppDest(dest); + return i2p::api::AcceptStream(cppDest, acceptor); } -void C_DestroyStream (std::shared_ptr stream) +void C_DestroyStream (i2p::stream::Stream *stream) { - return i2p::api::DestroyStream(stream); + std::shared_ptr cppStream(stream); + return i2p::api::DestroyStream(cppStream); } #ifdef __cplusplus diff --git a/libi2pd/capi.h b/libi2pd/capi.h index 70b9228e..341cf39e 100644 --- a/libi2pd/capi.h +++ b/libi2pd/capi.h @@ -11,6 +11,7 @@ #include "api.h" + #ifdef __cplusplus extern "C" { #endif @@ -18,23 +19,23 @@ extern "C" { // initialization start and stop void C_InitI2P (int argc, char* argv[], const char * appName); void C_TerminateI2P (); -void C_StartI2P (std::shared_ptr logStream = nullptr); +void C_StartI2P (std::ostream *logStream = nullptr); // write system log to logStream, if not specified to .log in application's folder void C_StopI2P (); void C_RunPeerTest (); // should be called after UPnP // destinations -std::shared_ptr C_CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, +i2p::client::ClientDestination *C_CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); -std::shared_ptr C_CreateTransientLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, +i2p::client::ClientDestination *C_CreateTransientLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, const std::map * params = nullptr); // transient destinations usually not published -void C_DestroyLocalDestination (std::shared_ptr dest); +void C_DestroyLocalDestination (i2p::client::ClientDestination *dest); // streams -void C_RequestLeaseSet (std::shared_ptr dest, const i2p::data::IdentHash& remote); -std::shared_ptr C_CreateStream (std::shared_ptr dest, const i2p::data::IdentHash& remote); -void C_AcceptStream (std::shared_ptr dest, const i2p::stream::StreamingDestination::Acceptor& acceptor); -void C_DestroyStream (std::shared_ptr stream); +void C_RequestLeaseSet (i2p::client::ClientDestination *dest, const i2p::data::IdentHash& remote); +i2p::stream::Stream *C_CreateStream (i2p::client::ClientDestination *dest, const i2p::data::IdentHash& remote); +void C_AcceptStream (i2p::client::ClientDestination *dest, const i2p::stream::StreamingDestination::Acceptor& acceptor); +void C_DestroyStream (i2p::stream::Stream *stream); #ifdef __cplusplus } From 29c1173e148da874755052a0d4d9606465dc5e25 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 15 Jun 2021 23:22:11 +0300 Subject: [PATCH 4249/6300] [i18n] fixes in translations Signed-off-by: R4SAS --- i18n/Turkmen.cpp | 15 +++++++-------- i18n/Ukrainian.cpp | 11 +++++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp index 93bd92fe..0edc9a6e 100644 --- a/i18n/Turkmen.cpp +++ b/i18n/Turkmen.cpp @@ -61,7 +61,7 @@ namespace turkmen // language {"failed to send request to upstream", "öý eýesi proksi üçin haýyÅŸ iberip bilmedi"}, {"No Reply From socks proxy", "Jorap proksi serwerinden hiç hili jogap ýok"}, {"cannot connect", "birikdirip bilmedi"}, - {"http out proxy not implemented", "daÅŸarky http proksi serwerini goldamak amala aÅŸyrylmaýar"}, + {"http out proxy not implemented", "daÅŸarky HTTP proksi serwerini goldamak amala aÅŸyrylmaýar"}, {"cannot connect to upstream http proxy", "ýokary akym HTTP proksi serwerine birigip bilmedi"}, {"Host is down", "Salgy elýeterli däl"}, {"Can't create connection to requested host, it may be down. Please try again later.", @@ -89,7 +89,7 @@ namespace turkmen // language {"Local destinations", "Ãerli ýerler"}, {"LeaseSets", "Lizset"}, {"Tunnels", "Tuneller"}, - {"Transit tunnels", "Tranzit Tunels"}, + {"Transit tunnels", "Tranzit tunels"}, {"Transports", "DaÅŸamak"}, {"I2P tunnels", "I2P tuneller"}, {"SAM sessions", "SAM Sessiýasy"}, @@ -108,7 +108,7 @@ namespace turkmen // language {"Uptime", "Onlaýn onlaýn sözlügi"}, {"Network status", "Tor ýagdaýy"}, {"Network status v6", "Tor ýagdaýy v6"}, - {"Stopping in", "soň duruň"}, + {"Stopping in", "Soň duruň"}, {"Family", "MaÅŸgala"}, {"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"}, {"Received", "Alnan"}, @@ -122,10 +122,9 @@ namespace turkmen // language {"Router Caps", "Baýdaklar marÅŸruteri"}, {"Version", "Wersiýasy"}, {"Our external address", "DaÅŸarky salgymyz"}, - {"supported", "Goldanýar"}, + {"supported", "goldanýar"}, {"Routers", "MarÅŸrutizatorlar"}, {"Floodfills", "Fludfillar"}, - {"LeaseSets", "Lizsetllar"}, {"Client Tunnels", "Müşderi tunelleri"}, {"Transit Tunnels", "Tranzit Tunelleri"}, {"Services", "Hyzmatlar"}, @@ -150,7 +149,7 @@ namespace turkmen // language {"Destination", "Maksat"}, {"Amount", "Sany"}, {"Incoming Tags", "Gelýän bellikler"}, - {"Tags sessions", "Sapaklar Tag."}, + {"Tags sessions", "Sapaklar bellikler"}, {"Status", "Ãagdaýy"}, // ShowLocalDestination {"Local Destination", "Ãerli maksat"}, @@ -178,7 +177,7 @@ namespace turkmen // language {"Start graceful shutdown", "Tekiz durmak"}, {"Force shutdown", "Mejbury duralga"}, {"Note: any action done here are not persistent and not changes your config files.", - "Bellik: Bu ýerde öndürilen islendik çäre hemiÅŸelik däl we konfigurasiýa faýllaryňyzy üýtgetmeýär.."}, + "Bellik: Bu ýerde öndürilen islendik çäre hemiÅŸelik däl we konfigurasiýa faýllaryňyzy üýtgetmeýär."}, {"Logging level", "GiriÅŸ derejesi"}, {"Transit tunnels limit", "Tranzit tunelleriniň çägi"}, {"Change", "Üýtgetmek"}, @@ -217,7 +216,7 @@ namespace turkmen // language {"Description", "Beýany"}, {"A bit information about service on domain", "Domendäki hyzmat barada käbir maglumatlar"}, {"Submit", "Iber"}, - {"Domain can't end with .b32.i2p", "Domain .b32.i2p bilen gutaryp bilmez."}, + {"Domain can't end with .b32.i2p", "Domain .b32.i2p bilen gutaryp bilmez"}, {"Domain must end with .i2p", "Domeni .i2p bilen gutarmaly"}, {"Such destination is not found", "Bu barmaly ýer tapylmady"}, {"", ""}, diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp index 05e7c786..11878cde 100644 --- a/i18n/Ukrainian.cpp +++ b/i18n/Ukrainian.cpp @@ -86,17 +86,17 @@ namespace ukrainian // language // ShowPageHead {"Main page", "Головна"}, {"Router commands", "Команди роутера"}, - {"Local destinations", "Локальні признач."}, + {"Local destinations", "Локальні призначеннÑ"}, {"LeaseSets", "ЛізÑети"}, {"Tunnels", "Тунелі"}, - {"Transit tunnels", "Транзит. тунелі"}, + {"Transit tunnels", "Транзитні тунелі"}, {"Transports", "ТранÑпорти"}, {"I2P tunnels", "I2P тунелі"}, {"SAM sessions", "SAM ÑеÑÑ–Ñ—"}, // Network Status {"OK", "OK"}, {"Testing", "ТеÑтуваннÑ"}, - {"Firewalled", "Файрвол"}, + {"Firewalled", "Заблоковано ззовні"}, {"Unknown", "Ðевідомо"}, {"Proxy", "ПрокÑÑ–"}, {"Mesh", "MESH-мережа"}, @@ -125,7 +125,6 @@ namespace ukrainian // language {"supported", "підтримуєтьÑÑ"}, {"Routers", "Роутери"}, {"Floodfills", "Флудфіли"}, - {"LeaseSets", "ЛізÑети"}, {"Client Tunnels", "КлієнтÑькі Тунелі"}, {"Transit Tunnels", "Транзитні Тунелі"}, {"Services", "СервіÑи"}, @@ -153,7 +152,7 @@ namespace ukrainian // language {"Tags sessions", "СеÑÑ–Ñ— тегів"}, {"Status", "СтатуÑ"}, // ShowLocalDestination - {"Local Destination", "Локальне ПризначеннÑ"}, + {"Local Destination", "Локальні ПризначеннÑ"}, {"Streams", "Потоки"}, {"Close stream", "Закрити потік"}, // ShowI2CPLocalDestination @@ -197,7 +196,7 @@ namespace ukrainian // language {"Unknown page", "Ðевідома Ñторінка"}, // HandleCommand, ShowError {"Invalid token", "Ðевірний токен"}, - {"SUCCESS", "ВДÐЛО"}, + {"SUCCESS", "УСПІШÐО"}, {"ERROR", "ПОМИЛКÐ"}, {"Unknown command", "Ðевідома команда"}, {"Command accepted", "Команда прийнÑта"}, From eebea7b342e607f312b5d832e16c0ab08eeb38eb Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 15 Jun 2021 23:22:59 +0300 Subject: [PATCH 4250/6300] [i18n] Add translation source in gettext format Signed-off-by: R4SAS --- i18n/English.po | 741 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 741 insertions(+) create mode 100644 i18n/English.po diff --git a/i18n/English.po b/i18n/English.po new file mode 100644 index 00000000..e9317528 --- /dev/null +++ b/i18n/English.po @@ -0,0 +1,741 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: i2pd\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-06-15 20:41+0300\n" +"PO-Revision-Date: 2021-06-15 20:42+0300\n" +"Last-Translator: \n" +"Language-Team: PurpleI2P\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" +"X-Generator: Poedit 3.0\n" +"X-Poedit-SourceCharset: UTF-8\n" +"X-Poedit-Basepath: .\n" +"X-Poedit-KeywordsList: ;tr\n" +"X-Poedit-SearchPath-0: daemon/HTTPServer.cpp\n" +"X-Poedit-SearchPath-1: libi2pd_client/HTTPProxy.cpp\n" + +#: daemon/HTTPServer.cpp:85 +msgid "Disabled" +msgstr "" + +#: daemon/HTTPServer.cpp:86 +msgid "Enabled" +msgstr "" + +#: daemon/HTTPServer.cpp:141 +msgid "days" +msgid_plural "days" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: daemon/HTTPServer.cpp:145 +msgid "hours" +msgid_plural "hours" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: daemon/HTTPServer.cpp:149 +msgid "minutes" +msgid_plural "minutes" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: daemon/HTTPServer.cpp:152 +msgid "seconds" +msgid_plural "seconds" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: daemon/HTTPServer.cpp:160 daemon/HTTPServer.cpp:188 +msgid "KiB" +msgstr "" + +#: daemon/HTTPServer.cpp:162 +msgid "MiB" +msgstr "" + +#: daemon/HTTPServer.cpp:164 +msgid "GiB" +msgstr "" + +#: daemon/HTTPServer.cpp:181 +msgid "building" +msgstr "" + +#: daemon/HTTPServer.cpp:182 +msgid "failed" +msgstr "" + +#: daemon/HTTPServer.cpp:183 +msgid "expiring" +msgstr "" + +#: daemon/HTTPServer.cpp:184 +msgid "established" +msgstr "" + +#: daemon/HTTPServer.cpp:185 +msgid "unknown" +msgstr "" + +#: daemon/HTTPServer.cpp:187 +msgid "exploratory" +msgstr "" + +#: daemon/HTTPServer.cpp:223 +msgid "i2pd webconsole" +msgstr "" + +#: daemon/HTTPServer.cpp:226 +msgid "Main page" +msgstr "" + +#: daemon/HTTPServer.cpp:227 daemon/HTTPServer.cpp:683 +msgid "Router commands" +msgstr "" + +#: daemon/HTTPServer.cpp:228 +msgid "Local destinations" +msgstr "" + +#: daemon/HTTPServer.cpp:230 daemon/HTTPServer.cpp:382 +#: daemon/HTTPServer.cpp:463 daemon/HTTPServer.cpp:469 +#: daemon/HTTPServer.cpp:599 daemon/HTTPServer.cpp:642 +#: daemon/HTTPServer.cpp:646 +msgid "LeaseSets" +msgstr "" + +#: daemon/HTTPServer.cpp:232 daemon/HTTPServer.cpp:652 +msgid "Tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:233 daemon/HTTPServer.cpp:727 +#: daemon/HTTPServer.cpp:743 +msgid "Transit tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:234 daemon/HTTPServer.cpp:792 +msgid "Transports" +msgstr "" + +#: daemon/HTTPServer.cpp:235 +msgid "I2P tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:237 daemon/HTTPServer.cpp:854 +#: daemon/HTTPServer.cpp:864 +msgid "SAM sessions" +msgstr "" + +#: daemon/HTTPServer.cpp:253 daemon/HTTPServer.cpp:1254 +#: daemon/HTTPServer.cpp:1257 daemon/HTTPServer.cpp:1260 +#: daemon/HTTPServer.cpp:1274 daemon/HTTPServer.cpp:1319 +#: daemon/HTTPServer.cpp:1322 daemon/HTTPServer.cpp:1325 +msgid "ERROR" +msgstr "" + +#: daemon/HTTPServer.cpp:260 +msgid "OK" +msgstr "" + +#: daemon/HTTPServer.cpp:261 +msgid "Testing" +msgstr "" + +#: daemon/HTTPServer.cpp:262 +msgid "Firewalled" +msgstr "" + +#: daemon/HTTPServer.cpp:263 daemon/HTTPServer.cpp:284 +#: daemon/HTTPServer.cpp:370 +msgid "Unknown" +msgstr "" + +#: daemon/HTTPServer.cpp:264 daemon/HTTPServer.cpp:394 +#: daemon/HTTPServer.cpp:395 daemon/HTTPServer.cpp:922 +#: daemon/HTTPServer.cpp:931 +msgid "Proxy" +msgstr "" + +#: daemon/HTTPServer.cpp:265 +msgid "Mesh" +msgstr "" + +#: daemon/HTTPServer.cpp:268 +msgid "Error" +msgstr "" + +#: daemon/HTTPServer.cpp:272 +msgid "Clock skew" +msgstr "" + +#: daemon/HTTPServer.cpp:275 +msgid "Offline" +msgstr "" + +#: daemon/HTTPServer.cpp:278 +msgid "Symmetric NAT" +msgstr "" + +#: daemon/HTTPServer.cpp:290 +msgid "Uptime" +msgstr "" + +#: daemon/HTTPServer.cpp:293 +msgid "Network status" +msgstr "" + +#: daemon/HTTPServer.cpp:298 +msgid "Network status v6" +msgstr "" + +#: daemon/HTTPServer.cpp:304 daemon/HTTPServer.cpp:311 +msgid "Stopping in" +msgstr "" + +#: daemon/HTTPServer.cpp:318 +msgid "Family" +msgstr "" + +#: daemon/HTTPServer.cpp:319 +msgid "Tunnel creation success rate" +msgstr "" + +#: daemon/HTTPServer.cpp:320 +msgid "Received" +msgstr "" + +#: daemon/HTTPServer.cpp:322 daemon/HTTPServer.cpp:325 +#: daemon/HTTPServer.cpp:328 +msgid "KiB/s" +msgstr "" + +#: daemon/HTTPServer.cpp:323 +msgid "Sent" +msgstr "" + +#: daemon/HTTPServer.cpp:326 +msgid "Transit" +msgstr "" + +#: daemon/HTTPServer.cpp:329 +msgid "Data path" +msgstr "" + +#: daemon/HTTPServer.cpp:332 +msgid "Hidden content. Press on text to see." +msgstr "" + +#: daemon/HTTPServer.cpp:335 +msgid "Router Ident" +msgstr "" + +#: daemon/HTTPServer.cpp:337 +msgid "Router Family" +msgstr "" + +#: daemon/HTTPServer.cpp:338 +msgid "Router Caps" +msgstr "" + +#: daemon/HTTPServer.cpp:339 +msgid "Version" +msgstr "" + +#: daemon/HTTPServer.cpp:340 +msgid "Our external address" +msgstr "" + +#: daemon/HTTPServer.cpp:348 +msgid "supported" +msgstr "" + +#: daemon/HTTPServer.cpp:380 +msgid "Routers" +msgstr "" + +#: daemon/HTTPServer.cpp:381 +msgid "Floodfills" +msgstr "" + +#: daemon/HTTPServer.cpp:388 daemon/HTTPServer.cpp:908 +msgid "Client Tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:389 +msgid "Transit Tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:393 +msgid "Services" +msgstr "" + +#: daemon/HTTPServer.cpp:407 daemon/HTTPServer.cpp:419 +msgid "Local Destinations" +msgstr "" + +#: daemon/HTTPServer.cpp:442 +msgid "Encrypted B33 address" +msgstr "" + +#: daemon/HTTPServer.cpp:451 +msgid "Address registration line" +msgstr "" + +#: daemon/HTTPServer.cpp:456 +msgid "Domain" +msgstr "" + +#: daemon/HTTPServer.cpp:457 +msgid "Generate" +msgstr "" + +#: daemon/HTTPServer.cpp:458 +msgid "" +"Note: result string can be used only for registering 2LD domains " +"(example.i2p). For registering subdomains please use i2pd-tools." +msgstr "" + +#: daemon/HTTPServer.cpp:464 +msgid "Address" +msgstr "" + +#: daemon/HTTPServer.cpp:464 +msgid "Type" +msgstr "" + +#: daemon/HTTPServer.cpp:464 +msgid "EncType" +msgstr "" + +#: daemon/HTTPServer.cpp:474 daemon/HTTPServer.cpp:657 +msgid "Inbound tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:479 daemon/HTTPServer.cpp:489 +#: daemon/HTTPServer.cpp:662 daemon/HTTPServer.cpp:672 +msgid "ms" +msgstr "" + +#: daemon/HTTPServer.cpp:484 daemon/HTTPServer.cpp:667 +msgid "Outbound tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:496 +msgid "Tags" +msgstr "" + +#: daemon/HTTPServer.cpp:496 +msgid "Incoming" +msgstr "" + +#: daemon/HTTPServer.cpp:503 daemon/HTTPServer.cpp:506 +msgid "Outgoing" +msgstr "" + +#: daemon/HTTPServer.cpp:504 daemon/HTTPServer.cpp:520 +msgid "Destination" +msgstr "" + +#: daemon/HTTPServer.cpp:504 +msgid "Amount" +msgstr "" + +#: daemon/HTTPServer.cpp:511 +msgid "Incoming Tags" +msgstr "" + +#: daemon/HTTPServer.cpp:519 daemon/HTTPServer.cpp:522 +msgid "Tags sessions" +msgstr "" + +#: daemon/HTTPServer.cpp:520 +msgid "Status" +msgstr "" + +#: daemon/HTTPServer.cpp:529 daemon/HTTPServer.cpp:584 +msgid "Local Destination" +msgstr "" + +#: daemon/HTTPServer.cpp:538 daemon/HTTPServer.cpp:887 +msgid "Streams" +msgstr "" + +#: daemon/HTTPServer.cpp:560 +msgid "Close stream" +msgstr "" + +#: daemon/HTTPServer.cpp:589 +msgid "I2CP session not found" +msgstr "" + +#: daemon/HTTPServer.cpp:592 +msgid "I2CP is not enabled" +msgstr "" + +#: daemon/HTTPServer.cpp:618 +msgid "Invalid" +msgstr "" + +#: daemon/HTTPServer.cpp:621 +msgid "Store type" +msgstr "" + +#: daemon/HTTPServer.cpp:622 +msgid "Expires" +msgstr "" + +#: daemon/HTTPServer.cpp:627 +msgid "Non Expired Leases" +msgstr "" + +#: daemon/HTTPServer.cpp:630 +msgid "Gateway" +msgstr "" + +#: daemon/HTTPServer.cpp:631 +msgid "TunnelID" +msgstr "" + +#: daemon/HTTPServer.cpp:632 +msgid "EndDate" +msgstr "" + +#: daemon/HTTPServer.cpp:642 +msgid "not floodfill" +msgstr "" + +#: daemon/HTTPServer.cpp:653 +msgid "Queue size" +msgstr "" + +#: daemon/HTTPServer.cpp:684 +msgid "Run peer test" +msgstr "" + +#: daemon/HTTPServer.cpp:687 +msgid "Decline transit tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:689 +msgid "Accept transit tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:692 daemon/HTTPServer.cpp:697 +msgid "Cancel graceful shutdown" +msgstr "" + +#: daemon/HTTPServer.cpp:694 daemon/HTTPServer.cpp:699 +msgid "Start graceful shutdown" +msgstr "" + +#: daemon/HTTPServer.cpp:701 +msgid "Force shutdown" +msgstr "" + +#: daemon/HTTPServer.cpp:704 +msgid "" +"Note: any action done here are not persistent and not changes your " +"config files." +msgstr "" + +#: daemon/HTTPServer.cpp:706 +msgid "Logging level" +msgstr "" + +#: daemon/HTTPServer.cpp:714 +msgid "Transit tunnels limit" +msgstr "" + +#: daemon/HTTPServer.cpp:719 +msgid "Change" +msgstr "" + +#: daemon/HTTPServer.cpp:743 +msgid "no transit tunnels currently built" +msgstr "" + +#: daemon/HTTPServer.cpp:848 daemon/HTTPServer.cpp:871 +msgid "SAM disabled" +msgstr "" + +#: daemon/HTTPServer.cpp:864 +msgid "no sessions currently running" +msgstr "" + +#: daemon/HTTPServer.cpp:877 +msgid "SAM session not found" +msgstr "" + +#: daemon/HTTPServer.cpp:882 +msgid "SAM Session" +msgstr "" + +#: daemon/HTTPServer.cpp:939 +msgid "Server Tunnels" +msgstr "" + +#: daemon/HTTPServer.cpp:955 +msgid "Client Forwards" +msgstr "" + +#: daemon/HTTPServer.cpp:969 +msgid "Server Forwards" +msgstr "" + +#: daemon/HTTPServer.cpp:1175 +msgid "Unknown page" +msgstr "" + +#: daemon/HTTPServer.cpp:1194 +msgid "Invalid token" +msgstr "" + +#: daemon/HTTPServer.cpp:1252 daemon/HTTPServer.cpp:1309 +#: daemon/HTTPServer.cpp:1337 +msgid "SUCCESS" +msgstr "" + +#: daemon/HTTPServer.cpp:1252 +msgid "Stream closed" +msgstr "" + +#: daemon/HTTPServer.cpp:1254 +msgid "Stream not found or already was closed" +msgstr "" + +#: daemon/HTTPServer.cpp:1257 +msgid "Destination not found" +msgstr "" + +#: daemon/HTTPServer.cpp:1260 +msgid "StreamID can't be null" +msgstr "" + +#: daemon/HTTPServer.cpp:1262 daemon/HTTPServer.cpp:1327 +msgid "Return to destination page" +msgstr "" + +#: daemon/HTTPServer.cpp:1263 daemon/HTTPServer.cpp:1276 +msgid "You will be redirected back in 5 seconds" +msgstr "" + +#: daemon/HTTPServer.cpp:1274 +msgid "Transit tunnels count must not exceed 65535" +msgstr "" + +#: daemon/HTTPServer.cpp:1275 daemon/HTTPServer.cpp:1338 +msgid "Back to commands list" +msgstr "" + +#: daemon/HTTPServer.cpp:1311 +msgid "Register at reg.i2p" +msgstr "" + +#: daemon/HTTPServer.cpp:1312 +msgid "Description" +msgstr "" + +#: daemon/HTTPServer.cpp:1312 +msgid "A bit information about service on domain" +msgstr "" + +#: daemon/HTTPServer.cpp:1313 +msgid "Submit" +msgstr "" + +#: daemon/HTTPServer.cpp:1319 +msgid "Domain can't end with .b32.i2p" +msgstr "" + +#: daemon/HTTPServer.cpp:1322 +msgid "Domain must end with .i2p" +msgstr "" + +#: daemon/HTTPServer.cpp:1325 +msgid "Such destination is not found" +msgstr "" + +#: daemon/HTTPServer.cpp:1333 +msgid "Unknown command" +msgstr "" + +#: daemon/HTTPServer.cpp:1337 +msgid "Command accepted" +msgstr "" + +#: daemon/HTTPServer.cpp:1339 +msgid "You will be redirected in 5 seconds" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:157 +msgid "Proxy error" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:165 +msgid "Proxy info" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:173 +msgid "Proxy error: Host not found" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:174 +msgid "Remote host not found in router's addressbook" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:175 +msgid "You may try to find this host on jump services below" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:273 libi2pd_client/HTTPProxy.cpp:288 +#: libi2pd_client/HTTPProxy.cpp:365 +msgid "Invalid request" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:273 +msgid "Proxy unable to parse your request" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:288 +msgid "addresshelper is not supported" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:297 libi2pd_client/HTTPProxy.cpp:306 +#: libi2pd_client/HTTPProxy.cpp:385 +msgid "Host" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:297 +msgid "added to router's addressbook from helper" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:298 libi2pd_client/HTTPProxy.cpp:307 +msgid "Click" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:298 libi2pd_client/HTTPProxy.cpp:308 +msgid "here" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:298 +msgid "to proceed" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:299 libi2pd_client/HTTPProxy.cpp:309 +msgid "Addresshelper found" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:306 +msgid "already in router's addressbook" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:308 +msgid "to update record" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:322 +msgid "Invalid Request" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:322 +msgid "invalid request uri" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:365 +msgid "Can't detect destination host from request" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:382 libi2pd_client/HTTPProxy.cpp:386 +msgid "Outproxy failure" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:382 +msgid "bad outproxy settings" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:385 +msgid "not inside I2P network, but outproxy is not enabled" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:474 +msgid "unknown outproxy url" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:480 +msgid "cannot resolve upstream proxy" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:488 +msgid "hostname too long" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:515 +msgid "cannot connect to upstream socks proxy" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:521 +msgid "Cannot negotiate with socks proxy" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:563 +msgid "CONNECT error" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:563 +msgid "Failed to Connect" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:574 libi2pd_client/HTTPProxy.cpp:600 +msgid "socks proxy error" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:582 +msgid "failed to send request to upstream" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:603 +msgid "No Reply From socks proxy" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:610 +msgid "cannot connect" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:610 +msgid "http out proxy not implemented" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:611 +msgid "cannot connect to upstream http proxy" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:644 +msgid "Host is down" +msgstr "" + +#: libi2pd_client/HTTPProxy.cpp:644 +msgid "" +"Can't create connection to requested host, it may be down. Please try again " +"later." +msgstr "" + +#~ msgid "day" +#~ msgid_plural "days" +#~ msgstr[0] "день" +#~ msgstr[1] "днÑ" +#~ msgstr[2] "дней" From b91eaf548798a64f54f4be900e0e2289d9f440ba Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 15 Jun 2021 23:30:28 +0300 Subject: [PATCH 4251/6300] [i18n] update gettext description Signed-off-by: R4SAS --- i18n/English.po | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/i18n/English.po b/i18n/English.po index e9317528..37acdb65 100644 --- a/i18n/English.po +++ b/i18n/English.po @@ -1,15 +1,15 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. +# i2pd +# Copyright (C) 2021 PurpleI2P team +# This file is distributed under the same license as the i2pd package. +# R4SAS , 2021. # msgid "" msgstr "" "Project-Id-Version: i2pd\n" -"Report-Msgid-Bugs-To: \n" +"Report-Msgid-Bugs-To: https://github.com/PurpleI2P/i2pd/issues\n" "POT-Creation-Date: 2021-06-15 20:41+0300\n" "PO-Revision-Date: 2021-06-15 20:42+0300\n" -"Last-Translator: \n" +"Last-Translator: R4SAS\n" "Language-Team: PurpleI2P\n" "Language: ru\n" "MIME-Version: 1.0\n" @@ -733,9 +733,3 @@ msgid "" "Can't create connection to requested host, it may be down. Please try again " "later." msgstr "" - -#~ msgid "day" -#~ msgid_plural "days" -#~ msgstr[0] "день" -#~ msgstr[1] "днÑ" -#~ msgstr[2] "дней" From 0bacd4df5fcaace1750fe84853b63fef125f8d8b Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 15 Jun 2021 23:43:33 +0300 Subject: [PATCH 4252/6300] [i18n] update gettext description Signed-off-by: R4SAS --- i18n/English.po | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/i18n/English.po b/i18n/English.po index 37acdb65..10b5b01e 100644 --- a/i18n/English.po +++ b/i18n/English.po @@ -7,16 +7,10 @@ msgid "" msgstr "" "Project-Id-Version: i2pd\n" "Report-Msgid-Bugs-To: https://github.com/PurpleI2P/i2pd/issues\n" -"POT-Creation-Date: 2021-06-15 20:41+0300\n" -"PO-Revision-Date: 2021-06-15 20:42+0300\n" -"Last-Translator: R4SAS\n" -"Language-Team: PurpleI2P\n" -"Language: ru\n" +"POT-Creation-Date: 2021-06-15 17:40\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" "X-Generator: Poedit 3.0\n" "X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-Basepath: .\n" @@ -37,28 +31,22 @@ msgid "days" msgid_plural "days" msgstr[0] "" msgstr[1] "" -msgstr[2] "" - #: daemon/HTTPServer.cpp:145 msgid "hours" msgid_plural "hours" msgstr[0] "" msgstr[1] "" -msgstr[2] "" - #: daemon/HTTPServer.cpp:149 msgid "minutes" msgid_plural "minutes" msgstr[0] "" msgstr[1] "" -msgstr[2] "" #: daemon/HTTPServer.cpp:152 msgid "seconds" msgid_plural "seconds" msgstr[0] "" msgstr[1] "" -msgstr[2] "" #: daemon/HTTPServer.cpp:160 daemon/HTTPServer.cpp:188 msgid "KiB" @@ -328,6 +316,7 @@ msgstr "" #: daemon/HTTPServer.cpp:479 daemon/HTTPServer.cpp:489 #: daemon/HTTPServer.cpp:662 daemon/HTTPServer.cpp:672 +#: Means milliseconds msgid "ms" msgstr "" From c06a5609464c88a6edf4e38a8cc3daa34eb6b65e Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 16 Jun 2021 00:13:26 +0300 Subject: [PATCH 4253/6300] [i18n] use xgettext compatible function format for plural Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 8 ++++---- i18n/English.cpp | 5 +---- i18n/I18N.h | 4 ++-- i18n/I18N_langs.h | 8 ++++---- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index dadb2f8f..7eb296c4 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -138,18 +138,18 @@ namespace http { int num; if ((num = seconds / 86400) > 0) { - s << num << " " << tr("days", num) << ", "; + s << num << " " << tr("day", "days", num) << ", "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { - s << num << " " << tr("hours", num) << ", "; + s << num << " " << tr("hour", "hours", num) << ", "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { - s << num << " " << tr("minutes", num) << ", "; + s << num << " " << tr("minute", "minutes", num) << ", "; seconds -= num * 60; } - s << seconds << " " << tr("seconds", seconds); + s << seconds << " " << tr("second", "seconds", seconds); } static void ShowTraffic (std::stringstream& s, uint64_t bytes) diff --git a/i18n/English.cpp b/i18n/English.cpp index 8b13279a..6015f8e1 100644 --- a/i18n/English.cpp +++ b/i18n/English.cpp @@ -13,6 +13,7 @@ #include "I18N.h" // English localization file +// This is an example translation file without strings in it. namespace i2p { @@ -33,10 +34,6 @@ namespace english // language static std::map> plurals { - {"days", {"day", "days"}}, - {"hours", {"hour", "hours"}}, - {"minutes", {"minute", "minutes"}}, - {"seconds", {"second", "seconds"}}, {"", {"", ""}}, }; diff --git a/i18n/I18N.h b/i18n/I18N.h index 7d0baf1a..4857df15 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -32,9 +32,9 @@ namespace i18n return i2p::context.GetLanguage ()->GetString (arg); } - inline std::string translate (const std::string& arg, const int& n) + inline std::string translate (const std::string& arg, const std::string& arg2, const int& n) { - return i2p::context.GetLanguage ()->GetPlural (arg, n); + return i2p::context.GetLanguage ()->GetPlural (arg, arg2, n); } } // i18n } // i2p diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index 435141bf..3c560a2f 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -35,12 +35,12 @@ namespace i18n } } - std::string GetPlural (const std::string& arg, const int& n) const + std::string GetPlural (const std::string& arg, const std::string& arg2, const int& n) const { - const auto it = m_Plurals.find(arg); - if (it == m_Plurals.end()) + const auto it = m_Plurals.find(arg2); + if (it == m_Plurals.end()) // not found, fallback to english { - return arg; + return n == 1 ? arg : arg2; } else { From dc75868bd392fe86e9546a924d7d184d288ef939 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 15 Jun 2021 19:09:36 -0400 Subject: [PATCH 4254/6300] check Alice's IP address in PeerTest --- libi2pd/SSUSession.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index 339ac8df..59ee578b 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -383,7 +383,7 @@ namespace transport { // tell out peer to now assign relay tag flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; - *payload = 2; payload++; // 1 byte length + *payload = 2; payload++; // 1 byte length uint16_t flags = 0; // clear EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG htobe16buf (payload, flags); payload += 2; @@ -1073,7 +1073,10 @@ namespace transport LogPrint (eLogDebug, "SSU: peer test from Charlie. We are Bob"); auto session = m_Server.GetPeerTestSession (nonce); // session with Alice from PeerTest if (session && session->m_State == eSessionStateEstablished) - session->Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Alice + { + const auto& ep = session->GetRemoteEndpoint (); // Alice's endpoint as known to Bob + session->SendPeerTest (nonce, ep.address (), ep.port (), introKey, false, true); // send back to Alice + } m_Server.RemovePeerTest (nonce); // nonce has been used break; } @@ -1093,9 +1096,12 @@ namespace transport if (port) { LogPrint (eLogDebug, "SSU: peer test from Bob. We are Charlie"); - m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob - SendPeerTest (nonce, addr, port, introKey); // to Alice with her address received from Bob + if (!addr.is_unspecified () && !i2p::util::net::IsInReservedRange(addr)) + { + m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); + SendPeerTest (nonce, addr, port, introKey); // to Alice with her address received from Bob + } } else { From 2ba3f4758a33769d6f50c12beda0a7b0944c5021 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 16 Jun 2021 17:56:08 +0300 Subject: [PATCH 4255/6300] [i18n] move gettext translation template to contrib Signed-off-by: R4SAS --- {i18n => contrib/i18n}/English.po | 8 ++++---- contrib/i18n/regex.txt | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) rename {i18n => contrib/i18n}/English.po (99%) create mode 100644 contrib/i18n/regex.txt diff --git a/i18n/English.po b/contrib/i18n/English.po similarity index 99% rename from i18n/English.po rename to contrib/i18n/English.po index 10b5b01e..1de2ddaa 100644 --- a/i18n/English.po +++ b/contrib/i18n/English.po @@ -27,23 +27,23 @@ msgid "Enabled" msgstr "" #: daemon/HTTPServer.cpp:141 -msgid "days" +msgid "day" msgid_plural "days" msgstr[0] "" msgstr[1] "" #: daemon/HTTPServer.cpp:145 -msgid "hours" +msgid "hour" msgid_plural "hours" msgstr[0] "" msgstr[1] "" #: daemon/HTTPServer.cpp:149 -msgid "minutes" +msgid "minute" msgid_plural "minutes" msgstr[0] "" msgstr[1] "" #: daemon/HTTPServer.cpp:152 -msgid "seconds" +msgid "second" msgid_plural "seconds" msgstr[0] "" msgstr[1] "" diff --git a/contrib/i18n/regex.txt b/contrib/i18n/regex.txt new file mode 100644 index 00000000..768a4b01 --- /dev/null +++ b/contrib/i18n/regex.txt @@ -0,0 +1,7 @@ +Regex for transforming gettext translations to our format + +msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\nmsgstr\[1\]\ \"(.*)\"\n(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? +#{"$2", {"$3", "$4", "$6", "$8", "$10"}},\n + +msgid\ \"(.*)\"\nmsgstr\ \"(.*)\"\n +{"$1", "$2"},\n From 954711e98069da66c06f91c5c5ea38a81c8c0126 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 16 Jun 2021 17:57:02 +0300 Subject: [PATCH 4256/6300] [i18n] pull afrikaans translation from crowdin Signed-off-by: R4SAS --- i18n/Afrikaans.cpp | 74 ++++++++++++++++++++++++++++++++++++++++++++++ i18n/I18N.h | 4 ++- i18n/I18N_langs.h | 7 +++-- 3 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 i18n/Afrikaans.cpp diff --git a/i18n/Afrikaans.cpp b/i18n/Afrikaans.cpp new file mode 100644 index 00000000..098c1105 --- /dev/null +++ b/i18n/Afrikaans.cpp @@ -0,0 +1,74 @@ +/* +* Copyright (c) 2021, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Afrikaans localization file +// This is an example translation file without strings in it. + +namespace i2p +{ +namespace i18n +{ +namespace afrikaans // language +{ + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"Disabled", "Gedeaktiveer"}, + {"Enabled", "Geaktiveer"}, + {"failed", "Het misluk"}, + {"unknown", "onbekend"}, + {"Tunnels", "Tonnels"}, + {"Transit tunnels", "Deurgang tonnels"}, + {"I2P tunnels", "I2P tonnels"}, + {"SAM sessions", "SAM sessies"}, + {"OK", "LEKKER"}, + {"Testing", "Besig om te toets"}, + {"Firewalled", "Vuurmuur'd"}, + {"Unknown", "Onbekend"}, + {"Error", "Fout"}, + {"Offline", "Aflyn"}, + {"Uptime", "Optyd"}, + {"Network status", "Netwerk status"}, + {"Network status v6", "Netwerk status v6"}, + {"Family", "Familie"}, + {"Received", "Ontvang"}, + {"Sent", "Gestuur"}, + {"Hidden content. Press on text to see.", "Hidden content. Druk om te sien."}, + {"Router Ident", "Router Ident"}, + {"Router Family", "Router Familie"}, + {"", ""}, + }; + + static std::map> plurals + { + {"days", {"dag", "dae"}}, + {"hours", {"uur", "ure"}}, + {"minutes", {"minuut", "minute"}}, + {"seconds", {"seconde", "sekondes"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p diff --git a/i18n/I18N.h b/i18n/I18N.h index 4857df15..272f65e8 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -17,7 +17,9 @@ namespace i18n { inline void SetLanguage(const std::string &lang) { - if (!lang.compare("russian")) + if (!lang.compare("afrikaans")) + i2p::context.SetLanguage (i2p::i18n::afrikaans::GetLocale()); + else if (!lang.compare("russian")) i2p::context.SetLanguage (i2p::i18n::russian::GetLocale()); else if (!lang.compare("turkmen")) i2p::context.SetLanguage (i2p::i18n::turkmen::GetLocale()); diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index 3c560a2f..181a4793 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -56,9 +56,10 @@ namespace i18n }; // Add localization here with language name as namespace - namespace english { std::shared_ptr GetLocale (); } - namespace russian { std::shared_ptr GetLocale (); } - namespace turkmen { std::shared_ptr GetLocale (); } + namespace afrikaans { std::shared_ptr GetLocale (); } + namespace english { std::shared_ptr GetLocale (); } + namespace russian { std::shared_ptr GetLocale (); } + namespace turkmen { std::shared_ptr GetLocale (); } namespace ukrainian { std::shared_ptr GetLocale (); } } // i18n From ac594dbd2646542b9a58cfb58c446638ca681015 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 16 Jun 2021 19:12:05 +0300 Subject: [PATCH 4257/6300] Update status badges in README --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1aa96fb0..92a2f46f 100644 --- a/README.md +++ b/README.md @@ -68,15 +68,15 @@ Build instructions: **Supported systems:** -* GNU/Linux - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) +* GNU/Linux - [![Build on Ubuntu](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build.yml) * CentOS / Fedora / Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/) * Alpine, ArchLinux, openSUSE, Gentoo, Debian, Ubuntu, etc. -* Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) -* Mac OS X - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) +* Windows - [![Build on Windows](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml) +* Mac OS X - [![Build on OSX](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml) * Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) -* Snap -* FreeBSD -* Android +* Snap - [![i2pd](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd) [![i2pd](https://snapcraft.io/i2pd/trending.svg?name=0)](https://snapcraft.io/i2pd) +* FreeBSD - [![Build on FreeBSD](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml) +* Android - [![Android CI](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml/badge.svg)](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml) * iOS Using i2pd From a6be32392d153b57ac266b5b4232d5962bd166a3 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 16 Jun 2021 20:41:41 +0000 Subject: [PATCH 4258/6300] update debian packaging files Signed-off-by: R4SAS --- {debian => contrib/openrc}/i2pd.openrc | 0 {debian => contrib/upstart}/i2pd.upstart | 0 debian/compat | 2 +- debian/control | 18 ++------------- debian/copyright | 29 ------------------------ debian/docs | 4 ---- debian/i2pd.dirs | 2 -- debian/i2pd.install | 2 +- debian/postinst | 3 +-- debian/rules | 26 ++++++++------------- debian/watch | 6 ++--- 11 files changed, 18 insertions(+), 74 deletions(-) rename {debian => contrib/openrc}/i2pd.openrc (100%) rename {debian => contrib/upstart}/i2pd.upstart (100%) delete mode 100644 debian/i2pd.dirs diff --git a/debian/i2pd.openrc b/contrib/openrc/i2pd.openrc similarity index 100% rename from debian/i2pd.openrc rename to contrib/openrc/i2pd.openrc diff --git a/debian/i2pd.upstart b/contrib/upstart/i2pd.upstart similarity index 100% rename from debian/i2pd.upstart rename to contrib/upstart/i2pd.upstart diff --git a/debian/compat b/debian/compat index 9a037142..ec635144 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -10 \ No newline at end of file +9 diff --git a/debian/control b/debian/control index 843a153c..318463bc 100644 --- a/debian/control +++ b/debian/control @@ -3,30 +3,16 @@ Section: net Priority: optional Maintainer: r4sas Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.17.2~), gcc (>= 4.7) | clang (>= 3.3), libboost-system-dev (>= 1.46), libboost-date-time-dev (>= 1.46), libboost-filesystem-dev (>= 1.46), libboost-program-options-dev (>= 1.46), libminiupnpc-dev, libssl-dev, zlib1g-dev -Standards-Version: 3.9.6 +Standards-Version: 3.9.8 Homepage: http://i2pd.website/ Vcs-Git: git://github.com/PurpleI2P/i2pd.git Vcs-Browser: https://github.com/PurpleI2P/i2pd Package: i2pd Architecture: any -Pre-Depends: adduser +Pre-Depends: ${misc:Pre-Depends}, adduser Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base, Description: Full-featured C++ implementation of I2P client. I2P (Invisible Internet Protocol) is a universal anonymous network layer. All communications over I2P are anonymous and end-to-end encrypted, participants don't reveal their real IP addresses. - . - This package contains the full-featured C++ implementation of I2P router. - -Package: i2pd-dbg -Architecture: any -Priority: extra -Section: debug -Depends: i2pd (= ${binary:Version}), ${misc:Depends} -Description: i2pd debugging symbols - I2P (Invisible Internet Protocol) is a universal anonymous network layer. All - communications over I2P are anonymous and end-to-end encrypted, participants - don't reveal their real IP addresses. - . - This package contains symbols required for debugging. diff --git a/debian/copyright b/debian/copyright index 9f18f53a..73df9da0 100644 --- a/debian/copyright +++ b/debian/copyright @@ -6,13 +6,6 @@ Files: * Copyright: 2013-2020 PurpleI2P License: BSD-3-clause -Files: qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl - qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl - qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java - qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java -Copyright: 2011-2013 BogDan Vatra -License: BSD-2-Clause - Files: debian/* Copyright: 2013-2015 Kill Your TV 2014-2016 hagen @@ -49,28 +42,6 @@ License: BSD-3-clause TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -License: BSD-2-Clause - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - . - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/debian/docs b/debian/docs index dfa6f572..b43bf86b 100644 --- a/debian/docs +++ b/debian/docs @@ -1,5 +1 @@ README.md -contrib/i2pd.conf -contrib/subscriptions.txt -contrib/tunnels.conf -contrib/tunnels.d diff --git a/debian/i2pd.dirs b/debian/i2pd.dirs deleted file mode 100644 index 3b643352..00000000 --- a/debian/i2pd.dirs +++ /dev/null @@ -1,2 +0,0 @@ -etc/i2pd -var/lib/i2pd diff --git a/debian/i2pd.install b/debian/i2pd.install index d20b2c17..6eb6c8c2 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,5 +1,5 @@ i2pd usr/sbin/ -contrib/i2pd.conf etc/i2pd/ +contrib/i2pd.conf etc/i2pd/ contrib/tunnels.conf etc/i2pd/ contrib/subscriptions.txt etc/i2pd/ contrib/certificates/ usr/share/i2pd/ diff --git a/debian/postinst b/debian/postinst index 9c9e74ae..720753fd 100755 --- a/debian/postinst +++ b/debian/postinst @@ -12,7 +12,6 @@ case "$1" in # Create user and group as a system user. if getent passwd $I2PDUSER > /dev/null 2>&1; then groupadd -f $I2PDUSER || true - usermod -s "/bin/false" -e 1 $I2PDUSER > /dev/null || true else adduser --system --quiet --group --home $I2PDHOME $I2PDUSER fi @@ -23,7 +22,7 @@ case "$1" in chmod 640 $LOGFILE chown -f ${I2PDUSER}:adm $LOGFILE mkdir -p -m0750 $I2PDHOME - chown -f -R -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} + chown -f -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} ;; abort-upgrade|abort-remove|abort-deconfigure) echo "Aborting upgrade" diff --git a/debian/rules b/debian/rules index 77ecd7b7..24a44f55 100755 --- a/debian/rules +++ b/debian/rules @@ -1,22 +1,16 @@ #!/usr/bin/make -f -# -*- makefile -*- - -# Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 -DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow -#DPKG_EXPORT_BUILDFLAGS = 1 -#include /usr/share/dpkg/buildflags.mk -#CXXFLAGS+=$(CPPFLAGS) -#PREFIX=/usr + +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + + +include /usr/share/dpkg/architecture.mk + +export DEB_CXXFLAGS_MAINT_APPEND = -Wall -pedantic -O3 + +export DEB_LDFLAGS_MAINT_APPEND = + %: dh $@ --parallel -# dh_apparmor --profile-name=usr.sbin.i2pd -pi2pd - -override_dh_strip: - dh_strip --dbg-package=i2pd-dbg - -## uncomment this if you have "missing info" problem when building package -#override_dh_shlibdeps: -# dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info diff --git a/debian/watch b/debian/watch index 55cda021..64367c81 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,3 @@ -version=3 -opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/i2pd-$1\.tar\.gz/ \ - https://github.com/PurpleI2P/i2pd/tags .*/v?(\d\S*)\.tar\.gz +version=4 opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%i2pd-$1.tar.gz%" \ + https://github.com/PurpleI2P/i2pd/tags \ + (?:.*?/)?(\d[\d.]*)\.tar\.gz debian uupdate From f07241bff76e949557e9ec8f5df514b8b4a594f8 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 16 Jun 2021 21:14:22 +0000 Subject: [PATCH 4259/6300] add deb building Signed-off-by: R4SAS --- .github/workflows/build.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8828f61..b948380f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,3 +38,35 @@ jobs: cd build cmake -DWITH_UPNP=${{ matrix.with_upnp }} . make -j3 + build-deb-stretch: + name: Build package for stretch + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: change debian changelog + run: | + debchange -v `git describe --tags` -M "trunk build" + - uses: singingwolfboy/build-dpkg-stretch@v1 + id: build + with: + args: --unsigned-source --unsigned-changes + - uses: actions/upload-artifact@v1 + with: + name: ${{ steps.build.outputs.filename }} + path: ${{ steps.build.outputs.filename }} + build-deb-buster: + name: Build package for buster + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: change debian changelog + run: | + debchange -v `git describe --tags` -M "trunk build" + - uses: singingwolfboy/build-dpkg-buster@v1 + id: build + with: + args: --unsigned-source --unsigned-changes + - uses: actions/upload-artifact@v1 + with: + name: ${{ steps.build.outputs.filename }} + path: ${{ steps.build.outputs.filename }} From f9d378f1ceb7c5c39a620658b5f59f000c560803 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 16 Jun 2021 21:19:05 +0000 Subject: [PATCH 4260/6300] [gha] add deb building Signed-off-by: R4SAS --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b948380f..42ec76b8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,6 +45,8 @@ jobs: - uses: actions/checkout@v2 - name: change debian changelog run: | + sudo apt-get update + sudo apt-get install devscripts debchange -v `git describe --tags` -M "trunk build" - uses: singingwolfboy/build-dpkg-stretch@v1 id: build @@ -61,6 +63,8 @@ jobs: - uses: actions/checkout@v2 - name: change debian changelog run: | + sudo apt-get update + sudo apt-get install devscripts debchange -v `git describe --tags` -M "trunk build" - uses: singingwolfboy/build-dpkg-buster@v1 id: build From 8ec478324979ca804d6bce5dc0b42a000d3477c1 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 16 Jun 2021 21:34:59 +0000 Subject: [PATCH 4261/6300] [gha] fetch all history of git repo for packages (needs for describe) Signed-off-by: R4SAS --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 42ec76b8..5027e180 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,6 +43,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: change debian changelog run: | sudo apt-get update @@ -61,6 +63,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: change debian changelog run: | sudo apt-get update From 064ecdb5ec1c8acfd5faa5d00c2a6775bfb706d3 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 16 Jun 2021 21:40:45 +0000 Subject: [PATCH 4262/6300] [gha] do no check source archive for deb build Signed-off-by: R4SAS --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5027e180..3df73f90 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,7 +53,7 @@ jobs: - uses: singingwolfboy/build-dpkg-stretch@v1 id: build with: - args: --unsigned-source --unsigned-changes + args: --unsigned-source --unsigned-changes --no-tgz-check - uses: actions/upload-artifact@v1 with: name: ${{ steps.build.outputs.filename }} @@ -73,7 +73,7 @@ jobs: - uses: singingwolfboy/build-dpkg-buster@v1 id: build with: - args: --unsigned-source --unsigned-changes + args: --unsigned-source --unsigned-changes --no-tgz-check - uses: actions/upload-artifact@v1 with: name: ${{ steps.build.outputs.filename }} From 71df1fc4d6b20f3a74f5d524a95727d459bf239b Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 16 Jun 2021 21:45:14 +0000 Subject: [PATCH 4263/6300] [gha] do not check source archive for deb build Signed-off-by: R4SAS --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3df73f90..fb820daa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,7 +53,7 @@ jobs: - uses: singingwolfboy/build-dpkg-stretch@v1 id: build with: - args: --unsigned-source --unsigned-changes --no-tgz-check + args: --unsigned-source --unsigned-changes -b - uses: actions/upload-artifact@v1 with: name: ${{ steps.build.outputs.filename }} @@ -73,7 +73,7 @@ jobs: - uses: singingwolfboy/build-dpkg-buster@v1 id: build with: - args: --unsigned-source --unsigned-changes --no-tgz-check + args: --unsigned-source --unsigned-changes -b - uses: actions/upload-artifact@v1 with: name: ${{ steps.build.outputs.filename }} From 2c7fff077b0544cd98c69657921f3db0262dd15c Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 16 Jun 2021 22:06:48 +0000 Subject: [PATCH 4264/6300] [gha] add dist name in package changelog Signed-off-by: R4SAS --- .github/workflows/build.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb820daa..22ba60bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,7 +49,7 @@ jobs: run: | sudo apt-get update sudo apt-get install devscripts - debchange -v `git describe --tags` -M "trunk build" + debchange -v "`git describe --tags`-stretch" -M --distribution stretch "trunk build" - uses: singingwolfboy/build-dpkg-stretch@v1 id: build with: @@ -58,6 +58,10 @@ jobs: with: name: ${{ steps.build.outputs.filename }} path: ${{ steps.build.outputs.filename }} + - uses: actions/upload-artifact@v1 + with: + name: ${{ steps.build.outputs.filename-dbgsym }} + path: ${{ steps.build.outputs.filename-dbgsym }} build-deb-buster: name: Build package for buster runs-on: ubuntu-latest @@ -69,7 +73,7 @@ jobs: run: | sudo apt-get update sudo apt-get install devscripts - debchange -v `git describe --tags` -M "trunk build" + debchange -v "`git describe --tags`-buster" -M --distribution buster "trunk build" - uses: singingwolfboy/build-dpkg-buster@v1 id: build with: @@ -78,3 +82,7 @@ jobs: with: name: ${{ steps.build.outputs.filename }} path: ${{ steps.build.outputs.filename }} + - uses: actions/upload-artifact@v1 + with: + name: ${{ steps.build.outputs.filename-dbgsym }} + path: ${{ steps.build.outputs.filename-dbgsym }} From a6af4908d55e11600e698a814bf731c5434799d6 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 16 Jun 2021 18:14:33 -0400 Subject: [PATCH 4265/6300] use m_ReachableTransports bitmask --- libi2pd/RouterContext.cpp | 2 ++ libi2pd/RouterInfo.cpp | 31 +++++++++---------------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 50dd6c59..c527aede 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -489,6 +489,7 @@ namespace i2p if (ntcp2) PublishNTCP2Address (port, false, v4, v6, false); // update + m_RouterInfo.UpdateSupportedTransports (); UpdateRouterInfo (); } @@ -528,6 +529,7 @@ namespace i2p } } // update + m_RouterInfo.UpdateSupportedTransports (); UpdateRouterInfo (); } diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index dcef9463..da456549 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -971,10 +971,10 @@ namespace data { if (!IsV6 ()) { - m_SupportedTransports |= eSSUV6 | eNTCP2V6; uint8_t addressCaps = AddressCaps::eV6; if (IsV4 ()) addressCaps |= AddressCaps::eV4; SetUnreachableAddressesTransportCaps (addressCaps); + UpdateSupportedTransports (); } } @@ -982,10 +982,10 @@ namespace data { if (!IsV4 ()) { - m_SupportedTransports |= eSSUV4 | eNTCP2V4; uint8_t addressCaps = AddressCaps::eV4; if (IsV6 ()) addressCaps |= AddressCaps::eV6; SetUnreachableAddressesTransportCaps (addressCaps); + UpdateSupportedTransports (); } } @@ -994,7 +994,6 @@ namespace data { if (IsV6 ()) { - m_SupportedTransports &= ~(eSSUV6 | eNTCP2V6); for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; @@ -1011,6 +1010,7 @@ namespace data else ++it; } + UpdateSupportedTransports (); } } @@ -1018,7 +1018,6 @@ namespace data { if (IsV4 ()) { - m_SupportedTransports &= ~(eSSUV4 | eNTCP2V4); for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; @@ -1035,13 +1034,17 @@ namespace data else ++it; } + UpdateSupportedTransports (); } } void RouterInfo::EnableMesh () { if (!IsMesh ()) + { m_SupportedTransports |= eNTCP2V6Mesh; + m_ReachableTransports |= eNTCP2V6Mesh; + } } void RouterInfo::DisableMesh () @@ -1049,6 +1052,7 @@ namespace data if (IsMesh ()) { m_SupportedTransports &= ~eNTCP2V6Mesh; + m_ReachableTransports &= ~eNTCP2V6Mesh; for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; @@ -1177,24 +1181,7 @@ namespace data bool RouterInfo::IsReachableFrom (const RouterInfo& other) const { - auto commonTransports = m_SupportedTransports & other.m_SupportedTransports; - if (!commonTransports) return false; - if (commonTransports & eNTCP2V6Mesh) return true; - return (bool)GetAddress ( - [commonTransports](std::shared_ptr address)->bool - { - if (address->IsPublishedNTCP2 ()) - { - if ((commonTransports & eNTCP2V4) && address->IsV4 ()) return true; - if ((commonTransports & eNTCP2V6) && address->IsV6 ()) return true; - } - else if (address->IsReachableSSU ()) - { - if ((commonTransports & eSSUV4) && address->IsV4 ()) return true; - if ((commonTransports & eSSUV6) && address->IsV6 ()) return true; - } - return false; - }); + return m_ReachableTransports & other.m_SupportedTransports; } void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports) From 1dda832e39a5bed13a3d973267e1d9f702589d07 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 17 Jun 2021 10:35:10 +0300 Subject: [PATCH 4266/6300] [gha] build docker containers Build docker containers and publish them to GitHub Container Registry --- .github/workflows/docker.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..d6afa613 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,28 @@ +name: Build Docker containers + +on: push + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + file: contrib/docker/Dockerfile + platforms: linux/amd64,linux/386,linux/arm/v7,linux/arm64,linux/ppc64le + push: true + tags: ghcr.io/PurpleI2P/i2pd:latest From d058b9a5959d1c501442d96a5ee924a3341e7fcf Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 17 Jun 2021 10:38:38 +0300 Subject: [PATCH 4267/6300] [gha] fix repository name to lowercase --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index d6afa613..9f15c30b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -25,4 +25,4 @@ jobs: file: contrib/docker/Dockerfile platforms: linux/amd64,linux/386,linux/arm/v7,linux/arm64,linux/ppc64le push: true - tags: ghcr.io/PurpleI2P/i2pd:latest + tags: ghcr.io/purplei2p/i2pd:latest From 2ee7ed8dda5997c14228b9eb98191e5983d33dea Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 17 Jun 2021 10:59:47 +0300 Subject: [PATCH 4268/6300] [gha] temporary build only amd64 container --- .github/workflows/docker.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9f15c30b..51509008 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -23,6 +23,7 @@ jobs: with: context: . file: contrib/docker/Dockerfile - platforms: linux/amd64,linux/386,linux/arm/v7,linux/arm64,linux/ppc64le + # platforms: linux/amd64,linux/386,linux/arm/v7,linux/arm64,linux/ppc64le + platforms: linux/amd64 push: true tags: ghcr.io/purplei2p/i2pd:latest From 970f47ce3323ec7966885f1fce8c1f42a6f1207c Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 17 Jun 2021 11:03:30 +0300 Subject: [PATCH 4269/6300] [gha] remove context --- .github/workflows/docker.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 51509008..50cfe2d2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -21,7 +21,6 @@ jobs: - name: Build and push uses: docker/build-push-action@v2 with: - context: . file: contrib/docker/Dockerfile # platforms: linux/amd64,linux/386,linux/arm/v7,linux/arm64,linux/ppc64le platforms: linux/amd64 From 3dc19bfd318e5d0bb093c41f0f7d0503d0e5bb70 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 17 Jun 2021 11:07:56 +0300 Subject: [PATCH 4270/6300] [gha] docker - disable cache (test) --- .github/workflows/docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 50cfe2d2..d0e5e28c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -25,4 +25,5 @@ jobs: # platforms: linux/amd64,linux/386,linux/arm/v7,linux/arm64,linux/ppc64le platforms: linux/amd64 push: true + no-cache: true tags: ghcr.io/purplei2p/i2pd:latest From 08a82a0bcd95855c8e02b91b4377929226dc1729 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 17 Jun 2021 12:12:06 -0400 Subject: [PATCH 4271/6300] don't try to connect to a router not reachable from us --- libi2pd/Transports.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 1e40f88c..3aaa12ba 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -401,7 +401,7 @@ namespace transport try { auto r = netdb.FindRouter (ident); - if (!r || r->IsUnreachable () || !r->IsCompatible (i2p::context.GetRouterInfo ())) return; + if (!r || r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ())) return; { std::unique_lock l(m_PeersMutex); it = m_Peers.insert (std::pair(ident, { 0, r, {}, From f56f75bb3f474d7a29c4c6efd85ca99cd90716da Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 17 Jun 2021 17:37:47 +0100 Subject: [PATCH 4272/6300] [gha] add docker building (#1664) --- .github/workflows/docker.yml | 19 +++++++++++++------ contrib/docker/Dockerfile | 3 ++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index d0e5e28c..81c0fcfb 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,29 +1,36 @@ -name: Build Docker containers +name: Build containers on: push jobs: docker: runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: - name: Checkout uses: actions/checkout@v2 + - name: Set up QEMU uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub uses: docker/login-action@v1 with: registry: ghcr.io - username: ${{ github.repository_owner }} + username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push uses: docker/build-push-action@v2 with: - file: contrib/docker/Dockerfile - # platforms: linux/amd64,linux/386,linux/arm/v7,linux/arm64,linux/ppc64le - platforms: linux/amd64 + context: ./contrib/docker + file: ./contrib/docker/Dockerfile + platforms: linux/amd64,linux/386 push: true - no-cache: true tags: ghcr.io/purplei2p/i2pd:latest diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index dc9f5501..71af141e 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -25,7 +25,8 @@ RUN mkdir -p "$I2PD_HOME" "$DATA_DIR" \ # 1. install deps, clone and build. # 2. strip binaries. # 3. Purge all dependencies and other unrelated packages, including build directory. -RUN apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl miniupnpc-dev git \ +RUN apk update \ + && apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib-dev boost-dev build-base openssl-dev openssl miniupnpc-dev git \ && mkdir -p /tmp/build \ && cd /tmp/build && git clone -b ${GIT_BRANCH} ${REPO_URL} \ && cd i2pd \ From a97d2bbb6393c161b5f51dba67d531fc312e7517 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 17 Jun 2021 20:07:10 +0300 Subject: [PATCH 4273/6300] [gha] publish containers to docker hub --- .github/workflows/docker.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 81c0fcfb..0946b431 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -20,6 +20,12 @@ jobs: uses: docker/setup-buildx-action@v1 - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container registry uses: docker/login-action@v1 with: registry: ghcr.io @@ -33,4 +39,6 @@ jobs: file: ./contrib/docker/Dockerfile platforms: linux/amd64,linux/386 push: true - tags: ghcr.io/purplei2p/i2pd:latest + tags: | + purplei2p/i2pd:latest + ghcr.io/purplei2p/i2pd:latest From 3330d2bb0ca098067e39ac41aa4f27e44ab5f05e Mon Sep 17 00:00:00 2001 From: idk Date: Thu, 17 Jun 2021 13:24:19 -0400 Subject: [PATCH 4274/6300] Also Extern Identity, Destination, Streaming headers --- Makefile | 11 ++++++++++- libi2pd/capi.cpp | 9 +++++---- libi2pd/capi.h | 9 +++++---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 7a033bf2..0569ab7c 100644 --- a/Makefile +++ b/Makefile @@ -82,6 +82,13 @@ api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) obj/%.o: %.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -c -o $@ $< +flags: + @echo $(CXXFLAGS) + @echo $(NEEDED_CXXFLAGS) + @echo $(INCFLAGS) + @echo $(LDFLAGS) + @echo $(LDLIBS) + # '-' is 'ignore if missing' on first run -include $(DEPS) @@ -139,5 +146,7 @@ doxygen: ##TODO: delete this before a PR testc: api api_client - g++ -Ii18n -c test.c -o test.o +# g++ -Ii18n -c test.c -o test.o + gcc -Ii18n -c _test.c -o test.o +# gcc -Ii18n -I/usr/include/c++/10 -I/usr/include/x86_64-linux-gnu/c++/10 -llibi2pd.a -c test.c -o test.o g++ test.o libi2pd.so libi2pdclient.so -o test.main \ No newline at end of file diff --git a/libi2pd/capi.cpp b/libi2pd/capi.cpp index b1f94d1f..d507f64e 100644 --- a/libi2pd/capi.cpp +++ b/libi2pd/capi.cpp @@ -6,6 +6,7 @@ * See full license text in LICENSE file at top of project tree */ +#include "api.h" #include "capi.h" #ifdef __cplusplus @@ -22,10 +23,10 @@ void C_TerminateI2P () return i2p::api::TerminateI2P(); } -void C_StartI2P (std::ostream *logStream) +void C_StartI2P ()//std::ostream *logStream) { - std::shared_ptr cppLogStream(logStream); - return i2p::api::StartI2P(cppLogStream); +// std::shared_ptr cppLogStream(logStream); + return i2p::api::StartI2P(nullptr); } void C_StopI2P () @@ -80,7 +81,7 @@ void C_DestroyStream (i2p::stream::Stream *stream) return i2p::api::DestroyStream(cppStream); } + #ifdef __cplusplus } #endif - diff --git a/libi2pd/capi.h b/libi2pd/capi.h index 341cf39e..0ad92b49 100644 --- a/libi2pd/capi.h +++ b/libi2pd/capi.h @@ -9,17 +9,18 @@ #ifndef CAPI_H__ #define CAPI_H__ -#include "api.h" - - #ifdef __cplusplus extern "C" { #endif +#include "Identity.h" +#include "Destination.h" +#include "Streaming.h" + // initialization start and stop void C_InitI2P (int argc, char* argv[], const char * appName); void C_TerminateI2P (); -void C_StartI2P (std::ostream *logStream = nullptr); +void C_StartI2P (); //std::ostream *logStream = nullptr); // write system log to logStream, if not specified to .log in application's folder void C_StopI2P (); void C_RunPeerTest (); // should be called after UPnP From 45ef6cba9d07127d1001911c7072c01ba436efb4 Mon Sep 17 00:00:00 2001 From: idk Date: Thu, 17 Jun 2021 13:46:57 -0400 Subject: [PATCH 4275/6300] Un-mangle Destination in case we need to somehow pass one to DestroyLocalDestination,RequestLeaseSet, etc --- Makefile | 2 +- libi2pd/Identity.h | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0569ab7c..29a03fc1 100644 --- a/Makefile +++ b/Makefile @@ -147,6 +147,6 @@ doxygen: ##TODO: delete this before a PR testc: api api_client # g++ -Ii18n -c test.c -o test.o - gcc -Ii18n -c _test.c -o test.o + gcc -llibi2pd.so -c _test.c -o test.o # gcc -Ii18n -I/usr/include/c++/10 -I/usr/include/x86_64-linux-gnu/c++/10 -llibi2pd.a -c test.c -o test.o g++ test.o libi2pd.so libi2pdclient.so -o test.main \ No newline at end of file diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index e9cf63ed..2e7cd32d 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -20,6 +20,10 @@ #include "Signature.h" #include "CryptoKey.h" +#ifdef __cplusplus +extern "C" { +#endif + namespace i2p { namespace data @@ -244,4 +248,8 @@ namespace data } } +#ifdef __cplusplus +} +#endif + #endif From 669720d8f5f0b84abdc97175dfccaf563a92c6e4 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 17 Jun 2021 21:37:48 +0300 Subject: [PATCH 4276/6300] [gha] build and publish release containers --- .github/workflows/docker.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 0946b431..aced7f39 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,6 +1,6 @@ name: Build containers -on: push +on: [push] jobs: docker: @@ -32,7 +32,8 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push + - name: Build and push trunk container + if: ${{ !startsWith(github.ref, 'refs/tags/') }} uses: docker/build-push-action@v2 with: context: ./contrib/docker @@ -42,3 +43,21 @@ jobs: tags: | purplei2p/i2pd:latest ghcr.io/purplei2p/i2pd:latest + + - name: Set env + if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV + + - name: Build and push release container + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: docker/build-push-action@v2 + with: + context: ./contrib/docker + file: ./contrib/docker/Dockerfile + platforms: linux/amd64,linux/386 + push: true + tags: | + purplei2p/i2pd:latest + purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} + ghcr.io/purplei2p/i2pd:latest + ghcr.io/purplei2p/i2pd:release-${{ env.RELEASE_VERSION }} From 82bb3a9b25f0eeddd59a0dcfdff53e66ab172fab Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 17 Jun 2021 01:26:08 +0300 Subject: [PATCH 4277/6300] [i18n] remove comment line in afrikaans Signed-off-by: R4SAS --- i18n/Afrikaans.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/i18n/Afrikaans.cpp b/i18n/Afrikaans.cpp index 098c1105..d2b652b4 100644 --- a/i18n/Afrikaans.cpp +++ b/i18n/Afrikaans.cpp @@ -13,7 +13,6 @@ #include "I18N.h" // Afrikaans localization file -// This is an example translation file without strings in it. namespace i2p { From e14d3584205d20e232510a8b555ed5fab600bac9 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 17 Jun 2021 22:11:46 +0300 Subject: [PATCH 4278/6300] [docker] add debug commands Adding `g++ -dumpmachine` command on build stage to figure out why docker hub is unable to build container. --- contrib/docker/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index 71af141e..d8140412 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -31,6 +31,7 @@ RUN apk update \ && cd /tmp/build && git clone -b ${GIT_BRANCH} ${REPO_URL} \ && cd i2pd \ && if [ -n "${GIT_TAG}" ]; then git checkout tags/${GIT_TAG}; fi \ + && g++ -dumpmachine \ && make USE_UPNP=yes \ && cp -R contrib/certificates /i2pd_certificates \ && mkdir -p /usr/local/bin \ From 5e11a03f0aa4c9fe75f9a20fb953e2428c7ad5c0 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 17 Jun 2021 22:41:37 +0300 Subject: [PATCH 4279/6300] [docker] fallback to alpine 3.13 https://wiki.alpinelinux.org/wiki/Draft_Release_Notes_for_Alpine_3.14.0#faccessat2 --- contrib/docker/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index d8140412..6470e148 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:latest +FROM alpine:3.13 LABEL authors "Mikal Villa , Darknet Villain " # Expose git branch, tag and URL variables as arguments @@ -31,7 +31,6 @@ RUN apk update \ && cd /tmp/build && git clone -b ${GIT_BRANCH} ${REPO_URL} \ && cd i2pd \ && if [ -n "${GIT_TAG}" ]; then git checkout tags/${GIT_TAG}; fi \ - && g++ -dumpmachine \ && make USE_UPNP=yes \ && cp -R contrib/certificates /i2pd_certificates \ && mkdir -p /usr/local/bin \ From 5013ce56491131b824685efb9b83e07c23236d55 Mon Sep 17 00:00:00 2001 From: idk Date: Thu, 17 Jun 2021 18:25:55 -0400 Subject: [PATCH 4280/6300] Try and figure out why the C Compiler thinks it needs to find iostream when the C++ library has already been compiled. Make the makefile aware of variables in the environment --- Makefile | 35 +++++++++++++++++++++-------------- libi2pd/api.swigcxx | 16 ++++++++++++++++ 2 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 libi2pd/api.swigcxx diff --git a/Makefile b/Makefile index 29a03fc1..97e75c15 100644 --- a/Makefile +++ b/Makefile @@ -13,11 +13,11 @@ DAEMON_SRC_DIR := daemon # import source files lists include filelist.mk -USE_AESNI := yes -USE_STATIC := no -USE_MESHNET := no -USE_UPNP := no -DEBUG := yes +USE_AESNI := $(or $(USE_AESNI),yes) +USE_STATIC := $(or $(USE_STATIC),no) +USE_MESHNET := $(or $(USE_MESHNET),no) +USE_UPNP := $(or $(USE_UPNP),no) +DEBUG := $(or $(DEBUG),yes) ifeq ($(DEBUG),yes) CXX_DEBUG = -g @@ -82,13 +82,6 @@ api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) obj/%.o: %.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -c -o $@ $< -flags: - @echo $(CXXFLAGS) - @echo $(NEEDED_CXXFLAGS) - @echo $(INCFLAGS) - @echo $(LDFLAGS) - @echo $(LDLIBS) - # '-' is 'ignore if missing' on first run -include $(DEPS) @@ -144,9 +137,23 @@ doxygen: .PHONY: install .PHONY: strip +flags: + @echo $(CXXFLAGS) + @echo $(NEEDED_CXXFLAGS) + @echo $(INCFLAGS) + @echo $(LDFLAGS) + @echo $(LDLIBS) + @echo $(USE_AESNI) + @echo $(USE_STATIC) + @echo $(USE_MESHNET) + @echo $(USE_UPNP) + @echo $(DEBUG) + ##TODO: delete this before a PR testc: api api_client # g++ -Ii18n -c test.c -o test.o - gcc -llibi2pd.so -c _test.c -o test.o +# gcc -llibi2pd.so -c _test.c -o test.o + $(CC) -g -Wall -o test.o _test.c libi2pd.a +# gcc -o i2pd _test.c libi2pd.a -lstdc++ -llibi2pd -Llibi2pd # gcc -Ii18n -I/usr/include/c++/10 -I/usr/include/x86_64-linux-gnu/c++/10 -llibi2pd.a -c test.c -o test.o - g++ test.o libi2pd.so libi2pdclient.so -o test.main \ No newline at end of file +# g++ test.o libi2pd.so libi2pdclient.so -o test.main \ No newline at end of file diff --git a/libi2pd/api.swigcxx b/libi2pd/api.swigcxx new file mode 100644 index 00000000..324967ee --- /dev/null +++ b/libi2pd/api.swigcxx @@ -0,0 +1,16 @@ +// See swig.org for more inteface options, +// e.g. map std::string to Go string + +%{ +#include "api.h" +//#include "Streaming.h" +#include "Destination.h" +//#include "Identity.h" +//#include "Tag.h" +%} + +%include "api.h" +//%include "Streaming.h" +//%include "Destination.h" +//%include "Identity.h" +//%include "Tag.h" \ No newline at end of file From 81c83f0d54aca38fb82d1a60e9108cd341ceb69f Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 17 Jun 2021 19:10:57 -0400 Subject: [PATCH 4281/6300] pick ECIES routers only for non-x64 --- libi2pd/NetDb.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index a1d97cba..5e93fabb 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1187,7 +1187,11 @@ namespace data (reverse ? compatibleWith->IsReachableFrom (*router) : router->IsReachableFrom (*compatibleWith)) && (router->GetCaps () & RouterInfo::eHighBandwidth) && +#if defined(__x86_64__) router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION; +#else + router->GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; +#endif }); } From 2185019b59b3a54fb5280508f3130805a9b7f9e4 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 17 Jun 2021 19:46:05 -0400 Subject: [PATCH 4282/6300] check if router is reachable by transport before obtaining address --- libi2pd/RouterInfo.cpp | 5 ----- libi2pd/RouterInfo.h | 3 ++- libi2pd/Transports.cpp | 8 ++++---- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index da456549..3f50a4b7 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1179,11 +1179,6 @@ namespace data }); } - bool RouterInfo::IsReachableFrom (const RouterInfo& other) const - { - return m_ReachableTransports & other.m_SupportedTransports; - } - void RouterInfo::SetUnreachableAddressesTransportCaps (uint8_t transports) { for (auto& addr: *m_Addresses) diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index bea83fda..89654757 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -204,7 +204,8 @@ namespace data void EnableMesh (); void DisableMesh (); bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; - bool IsReachableFrom (const RouterInfo& other) const; + bool IsReachableFrom (const RouterInfo& other) const { return m_ReachableTransports & other.m_SupportedTransports; }; + bool IsReachableBy (SupportedTransports transport) const { return m_ReachableTransports & transport; }; bool HasValidAddresses () const { return m_SupportedTransports; }; bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 3aaa12ba..c2bed2ab 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -447,7 +447,7 @@ namespace transport std::shared_ptr address; if (!peer.numAttempts) // NTCP2 ipv6 { - if (context.GetRouterInfo ().IsNTCP2V6 () && peer.router->IsNTCP2V6 ()) + if (context.GetRouterInfo ().IsNTCP2V6 () && peer.router->IsReachableBy (RouterInfo::eNTCP2V6)) { address = peer.router->GetPublishedNTCP2V6Address (); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) @@ -457,7 +457,7 @@ namespace transport } if (!address && peer.numAttempts == 1) // NTCP2 ipv4 { - if (context.GetRouterInfo ().IsNTCP2 (true) && peer.router->IsNTCP2 (true) && !peer.router->IsUnreachable ()) + if (context.GetRouterInfo ().IsNTCP2 (true) && peer.router->IsReachableBy (RouterInfo::eNTCP2V4)) { address = peer.router->GetPublishedNTCP2V4Address (); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) @@ -485,7 +485,7 @@ namespace transport std::shared_ptr address; if (peer.numAttempts == 2) // SSU ipv6 { - if (context.GetRouterInfo ().IsSSUV6 () && peer.router->IsSSUV6 ()) + if (context.GetRouterInfo ().IsSSUV6 () && peer.router->IsReachableBy (RouterInfo::eSSUV6)) { address = peer.router->GetSSUV6Address (); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) @@ -495,7 +495,7 @@ namespace transport } if (!address && peer.numAttempts == 3) // SSU ipv4 { - if (context.GetRouterInfo ().IsSSU (true) && peer.router->IsSSU (true)) + if (context.GetRouterInfo ().IsSSU (true) && peer.router->IsReachableBy (RouterInfo::eSSUV4)) { address = peer.router->GetSSUAddress (true); if (address && m_CheckReserved && i2p::util::net::IsInReservedRange(address->host)) From 7bc2e74683307952c8c8dd442fffe73519c86882 Mon Sep 17 00:00:00 2001 From: idk Date: Thu, 17 Jun 2021 23:12:22 -0400 Subject: [PATCH 4283/6300] Get it to build from go build --- Makefile | 39 +++++++++++++++++++++++++++++---------- libi2pd/Destination.h | 8 -------- libi2pd/Identity.h | 8 -------- libi2pd/api.go | 10 ++++++++++ libi2pd/api.swigcxx | 6 +++--- libi2pd/capi.cpp | 9 +++++---- libi2pd/capi.h | 11 ++++++----- 7 files changed, 53 insertions(+), 38 deletions(-) create mode 100644 libi2pd/api.go diff --git a/Makefile b/Makefile index 97e75c15..62a64584 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ SYS := $(shell $(CXX) -dumpmachine) SHLIB := libi2pd.so ARLIB := libi2pd.a +SHLIB_LANG := libi2pdlang.so +ARLIB_LANG := libi2pdlang.a SHLIB_CLIENT := libi2pdclient.so ARLIB_CLIENT := libi2pdclient.a I2PD := i2pd @@ -26,6 +28,12 @@ else LD_DEBUG = -s endif +ifeq ($(USE_STATIC),yes) + NEEDED_CXXFLAGS+= -static +else + +endif + ifneq (, $(findstring darwin, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp ifeq ($(HOMEBREW),1) @@ -68,9 +76,10 @@ mk_obj_dir: @mkdir -p obj/$(LANG_SRC_DIR) @mkdir -p obj/$(DAEMON_SRC_DIR) -api: mk_obj_dir $(SHLIB) $(ARLIB) -client: mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) -api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) +api: mk_obj_dir $(SHLIB) $(ARLIB) +client: mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) +api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) +langs: mk_obj_dir $(LANG_OBJS) $(SHLIB_LANG) $(ARLIB_LANG) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. @@ -88,22 +97,31 @@ obj/%.o: %.cpp $(I2PD): $(LANG_OBJS) $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) -$(SHLIB): $(LIB_OBJS) +$(SHLIB): $(LIB_OBJS) ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) endif -$(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) +$(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) endif -$(ARLIB): $(LIB_OBJS) +$(SHLIB_LANG): $(LANG_OBJS) +ifneq ($(USE_STATIC),yes) + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) +endif + +$(ARLIB): $(LIB_OBJS) $(AR) -r $@ $^ -$(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) +$(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) $(AR) -r $@ $^ +$(ARLIB_LANG): $(LANG_OBJS) + $(AR) -r $@ $^ + + clean: $(RM) -r obj $(RM) -r docs/generated @@ -151,9 +169,10 @@ flags: ##TODO: delete this before a PR testc: api api_client -# g++ -Ii18n -c test.c -o test.o + g++ -Ii18n -c _test.c -o test.o # gcc -llibi2pd.so -c _test.c -o test.o - $(CC) -g -Wall -o test.o _test.c libi2pd.a +# $(CXX) $(LDFLAGS) $(LDLIBS) -static -Ii18n -Ilibi2pd -Ilibi2pd_client -g -Wall -o test.o _test.c libi2pd.a libi2pdclient.a #obj/libi2pd/*.o obj/i18n/*.o #libi2pd.so +# $(CXX) $(LDFLAGS) $(LDLIBS) -static -Ii18n -g -Wall -o test.o _test.c libi2pd.a libi2pdclient.a #obj/libi2pd/*.o obj/i18n/*.o #libi2pd.so # gcc -o i2pd _test.c libi2pd.a -lstdc++ -llibi2pd -Llibi2pd # gcc -Ii18n -I/usr/include/c++/10 -I/usr/include/x86_64-linux-gnu/c++/10 -llibi2pd.a -c test.c -o test.o -# g++ test.o libi2pd.so libi2pdclient.so -o test.main \ No newline at end of file + g++ test.o libi2pd.a libi2pdclient.a libi2pdlang.a -o test.main \ No newline at end of file diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index f24e31ca..6695796d 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -28,10 +28,6 @@ #include "Datagram.h" #include "util.h" -#ifdef __cplusplus -extern "C" { -#endif - namespace i2p { namespace client @@ -316,8 +312,4 @@ namespace client } } -#ifdef __cplusplus -} -#endif - #endif diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index 2e7cd32d..e9cf63ed 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -20,10 +20,6 @@ #include "Signature.h" #include "CryptoKey.h" -#ifdef __cplusplus -extern "C" { -#endif - namespace i2p { namespace data @@ -248,8 +244,4 @@ namespace data } } -#ifdef __cplusplus -} -#endif - #endif diff --git a/libi2pd/api.go b/libi2pd/api.go new file mode 100644 index 00000000..d7a19bc9 --- /dev/null +++ b/libi2pd/api.go @@ -0,0 +1,10 @@ +package api + +/* +//void Go_InitI2P (int argc, char argv[], const char * appName){ + +//} +#cgo CPPFLAGS: -I${SRCDIR}/../i18n -I${SRCDIR}/../libi2pd_client -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi -fPIC -D__AES__ -maes +#cgo LDFLAGS: -latomic -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lstdc++ +*/ +import "C" diff --git a/libi2pd/api.swigcxx b/libi2pd/api.swigcxx index 324967ee..fbb1b95a 100644 --- a/libi2pd/api.swigcxx +++ b/libi2pd/api.swigcxx @@ -2,14 +2,14 @@ // e.g. map std::string to Go string %{ -#include "api.h" +#include "capi.h" //#include "Streaming.h" -#include "Destination.h" +//#include "Destination.h" //#include "Identity.h" //#include "Tag.h" %} -%include "api.h" +%include "capi.h" //%include "Streaming.h" //%include "Destination.h" //%include "Identity.h" diff --git a/libi2pd/capi.cpp b/libi2pd/capi.cpp index d507f64e..1a2498b6 100644 --- a/libi2pd/capi.cpp +++ b/libi2pd/capi.cpp @@ -13,9 +13,9 @@ extern "C" { #endif -void C_InitI2P (int argc, char* argv[], const char * appName) +void C_InitI2P (int argc, char argv[], const char * appName) { - return i2p::api::InitI2P(argc, argv, appName); + return i2p::api::InitI2P(argc, &argv, appName); } void C_TerminateI2P () @@ -25,8 +25,9 @@ void C_TerminateI2P () void C_StartI2P ()//std::ostream *logStream) { -// std::shared_ptr cppLogStream(logStream); - return i2p::api::StartI2P(nullptr); + std::shared_ptr logStream; + //cppLogStream(&out); + return i2p::api::StartI2P(logStream); } void C_StopI2P () diff --git a/libi2pd/capi.h b/libi2pd/capi.h index 0ad92b49..8395cfca 100644 --- a/libi2pd/capi.h +++ b/libi2pd/capi.h @@ -9,16 +9,17 @@ #ifndef CAPI_H__ #define CAPI_H__ -#ifdef __cplusplus -extern "C" { -#endif - #include "Identity.h" #include "Destination.h" #include "Streaming.h" +#ifdef __cplusplus +extern "C" { +#endif + // initialization start and stop -void C_InitI2P (int argc, char* argv[], const char * appName); +void C_InitI2P (int argc, char argv[], const char * appName); +//void C_InitI2P (int argc, char** argv, const char * appName); void C_TerminateI2P (); void C_StartI2P (); //std::ostream *logStream = nullptr); // write system log to logStream, if not specified to .log in application's folder From 5bfab0a79639924b666ce20647a8fe469838f0fe Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 18 Jun 2021 06:38:10 +0300 Subject: [PATCH 4284/6300] add certsdir option (#1642) Signed-off-by: R4SAS --- contrib/i2pd.conf | 4 ++++ libi2pd/Config.cpp | 1 + libi2pd/Family.cpp | 9 ++++++++- libi2pd/Reseed.cpp | 9 ++++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index f3cea2b5..7b85a61b 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -15,6 +15,10 @@ ## Default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d # tunnelsdir = /var/lib/i2pd/tunnels.d +## Path to certificates used for verifying .su3, families +## Default: ~/.i2pd/certificates or /var/lib/i2pd/certificates +# certsdir = /var/lib/i2pd/certificates + ## Where to write pidfile (default: i2pd.pid, not used in Windows) # pidfile = /run/i2pd.pid diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index a80778a1..16d50e8c 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -37,6 +37,7 @@ namespace config { ("conf", value()->default_value(""), "Path to main i2pd config file (default: try ~/.i2pd/i2pd.conf or /var/lib/i2pd/i2pd.conf)") ("tunconf", value()->default_value(""), "Path to config with tunnels list and options (default: try ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf)") ("tunnelsdir", value()->default_value(""), "Path to extra tunnels' configs folder (default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d") + ("certsdir", value()->default_value(""), "Path to certificates used for verifying .su3, families (default: ~/.i2pd/certificates or /var/lib/i2pd/certificates") ("pidfile", value()->default_value(""), "Path to pidfile (default: ~/i2pd/i2pd.pid or /var/lib/i2pd/i2pd.pid)") ("log", value()->default_value(""), "Logs destination: stdout, file, syslog (stdout if not set)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") diff --git a/libi2pd/Family.cpp b/libi2pd/Family.cpp index fbb7b9ee..baf846f6 100644 --- a/libi2pd/Family.cpp +++ b/libi2pd/Family.cpp @@ -98,7 +98,14 @@ namespace data void Families::LoadCertificates () { - std::string certDir = i2p::fs::DataDirPath("certificates", "family"); + std::string certDir; + std::string certsdir; i2p::config::GetOption("certsdir", certsdir); + if (!i2p::config::IsDefault("certsdir")) + certDir = certsdir + i2p::fs::dirSep + "family"; + + if (certDir.empty() || !i2p::fs::Exists(certDir)) + std::string certDir = i2p::fs::DataDirPath("certificates", "family"); + std::vector files; int numCertificates = 0; diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 41111ecc..a78058cf 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -497,7 +497,14 @@ namespace data void Reseeder::LoadCertificates () { - std::string certDir = i2p::fs::DataDirPath("certificates", "reseed"); + std::string certDir; + std::string certsdir; i2p::config::GetOption("certsdir", certsdir); + if (!i2p::config::IsDefault("certsdir")) + certDir = certsdir + i2p::fs::dirSep + "reseed"; + + if (certDir.empty() || !i2p::fs::Exists(certDir)) + std::string certDir = i2p::fs::DataDirPath("certificates", "reseed"); + std::vector files; int numCertificates = 0; From d3a49e513c75ba4a78ac8d68a4def828feb6bfcb Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 18 Jun 2021 06:40:58 +0300 Subject: [PATCH 4285/6300] remove repeatable type definition, add include (#1642) Signed-off-by: R4SAS --- libi2pd/Family.cpp | 3 ++- libi2pd/Reseed.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libi2pd/Family.cpp b/libi2pd/Family.cpp index baf846f6..f37a5f54 100644 --- a/libi2pd/Family.cpp +++ b/libi2pd/Family.cpp @@ -13,6 +13,7 @@ #include "FS.h" #include "Log.h" #include "Family.h" +#include "Config.h" namespace i2p { @@ -104,7 +105,7 @@ namespace data certDir = certsdir + i2p::fs::dirSep + "family"; if (certDir.empty() || !i2p::fs::Exists(certDir)) - std::string certDir = i2p::fs::DataDirPath("certificates", "family"); + certDir = i2p::fs::DataDirPath("certificates", "family"); std::vector files; int numCertificates = 0; diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index a78058cf..482506b2 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -503,7 +503,7 @@ namespace data certDir = certsdir + i2p::fs::dirSep + "reseed"; if (certDir.empty() || !i2p::fs::Exists(certDir)) - std::string certDir = i2p::fs::DataDirPath("certificates", "reseed"); + certDir = i2p::fs::DataDirPath("certificates", "reseed"); std::vector files; int numCertificates = 0; From e8ad7b4f79292c8ccded1c22e80089ad9f6a501f Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 18 Jun 2021 10:04:48 +0300 Subject: [PATCH 4286/6300] rework of storing certificates path (#1642) Signed-off-by: R4SAS --- daemon/Daemon.cpp | 6 ++++++ libi2pd/FS.cpp | 20 ++++++++++++++++++++ libi2pd/FS.h | 18 +++++++++++++++++- libi2pd/Family.cpp | 8 +------- libi2pd/Reseed.cpp | 8 +------- 5 files changed, 45 insertions(+), 15 deletions(-) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index f7f2ef2f..445c4dfd 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -94,6 +94,11 @@ namespace util i2p::config::GetOption("daemon", isDaemon); + std::string certsdir; i2p::config::GetOption("certsdir", certsdir); + i2p::fs::SetCertsDir(certsdir); + + certsdir = i2p::fs::GetCertsDir(); + std::string logs = ""; i2p::config::GetOption("log", logs); std::string logfile = ""; i2p::config::GetOption("logfile", logfile); std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); @@ -132,6 +137,7 @@ namespace util LogPrint(eLogNone, "i2pd v", VERSION, " starting"); LogPrint(eLogDebug, "FS: main config file: ", config); LogPrint(eLogDebug, "FS: data directory: ", datadir); + LogPrint(eLogDebug, "FS: certificates directory: ", certsdir); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); bool aesni; i2p::config::GetOption("cpuext.aesni", aesni); diff --git a/libi2pd/FS.cpp b/libi2pd/FS.cpp index 6ac302b0..f6653e55 100644 --- a/libi2pd/FS.cpp +++ b/libi2pd/FS.cpp @@ -24,6 +24,7 @@ namespace i2p { namespace fs { std::string appName = "i2pd"; std::string dataDir = ""; + std::string certsDir = ""; #ifdef _WIN32 std::string dirSep = "\\"; #else @@ -42,6 +43,10 @@ namespace fs { return dataDir; } + const std::string & GetCertsDir () { + return certsDir; + } + const std::string GetUTF8DataDir () { #ifdef _WIN32 boost::filesystem::wpath path (dataDir); @@ -126,6 +131,21 @@ namespace fs { #endif } + void SetCertsDir(const std::string & cmdline_certsdir) { + if (cmdline_certsdir != "") + { + if (cmdline_certsdir[cmdline_certsdir.length()-1] == '/') + certsDir = cmdline_certsdir.substr(0, cmdline_certsdir.size()-1); // strip trailing slash + else + certsDir = cmdline_certsdir; + } + else + { + certsDir = i2p::fs::DataDirPath("certificates"); + } + return; + } + bool Init() { if (!boost::filesystem::exists(dataDir)) boost::filesystem::create_directory(dataDir); diff --git a/libi2pd/FS.h b/libi2pd/FS.h index f07ee35c..d51aa955 100644 --- a/libi2pd/FS.h +++ b/libi2pd/FS.h @@ -75,6 +75,9 @@ namespace fs { /** @brief Returns datadir path */ const std::string & GetDataDir(); + /** @brief Returns certsdir path */ + const std::string & GetCertsDir(); + /** @brief Returns datadir path in UTF-8 encoding */ const std::string GetUTF8DataDir(); @@ -90,7 +93,20 @@ namespace fs { * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/ * Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/ */ - void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); + void DetectDataDir(const std::string & cmdline_datadir, bool isService = false); + + /** + * @brief Set certsdir either from cmdline option or using autodetection + * @param cmdline_param Value of cmdline parameter --certsdir= + * + * Examples of autodetected paths: + * + * Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\certificates + * Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\certificates + * Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/certificates + * Unix: /var/lib/i2pd/certificates (system=1) >> ~/.i2pd/ or /tmp/i2pd/certificates + */ + void SetCertsDir(const std::string & cmdline_certsdir); /** * @brief Create subdirectories inside datadir diff --git a/libi2pd/Family.cpp b/libi2pd/Family.cpp index f37a5f54..a6f0e2ee 100644 --- a/libi2pd/Family.cpp +++ b/libi2pd/Family.cpp @@ -99,13 +99,7 @@ namespace data void Families::LoadCertificates () { - std::string certDir; - std::string certsdir; i2p::config::GetOption("certsdir", certsdir); - if (!i2p::config::IsDefault("certsdir")) - certDir = certsdir + i2p::fs::dirSep + "family"; - - if (certDir.empty() || !i2p::fs::Exists(certDir)) - certDir = i2p::fs::DataDirPath("certificates", "family"); + std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "family"; std::vector files; int numCertificates = 0; diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 482506b2..aec683d4 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -497,13 +497,7 @@ namespace data void Reseeder::LoadCertificates () { - std::string certDir; - std::string certsdir; i2p::config::GetOption("certsdir", certsdir); - if (!i2p::config::IsDefault("certsdir")) - certDir = certsdir + i2p::fs::dirSep + "reseed"; - - if (certDir.empty() || !i2p::fs::Exists(certDir)) - certDir = i2p::fs::DataDirPath("certificates", "reseed"); + std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "reseed"; std::vector files; int numCertificates = 0; From 8e5d2e1b73fa28133c0e51aa0455bd77155ecdf9 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 18 Jun 2021 17:26:18 +0300 Subject: [PATCH 4287/6300] [readme] add gha container build badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 92a2f46f..37c4553a 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Build instructions: * Alpine, ArchLinux, openSUSE, Gentoo, Debian, Ubuntu, etc. * Windows - [![Build on Windows](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-windows.yml) * Mac OS X - [![Build on OSX](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-osx.yml) -* Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) +* Docker image - [![Build Status](https://img.shields.io/docker/cloud/build/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd/builds/) [![Build containers](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/docker.yml) * Snap - [![i2pd](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd) [![i2pd](https://snapcraft.io/i2pd/trending.svg?name=0)](https://snapcraft.io/i2pd) * FreeBSD - [![Build on FreeBSD](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml/badge.svg)](https://github.com/PurpleI2P/i2pd/actions/workflows/build-freebsd.yml) * Android - [![Android CI](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml/badge.svg)](https://github.com/PurpleI2P/i2pd-android/actions/workflows/android.yml) From 6ca28adcbb29df8fd94d1e735ae5791bbd2e5a54 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 18 Jun 2021 18:19:05 -0400 Subject: [PATCH 4288/6300] set address caps and available transports for new address --- libi2pd/RouterContext.cpp | 23 ++++++++++++++--------- libi2pd/RouterInfo.cpp | 16 ++++++++++++++-- libi2pd/RouterInfo.h | 3 ++- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index c527aede..adce291c 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -568,16 +568,21 @@ namespace i2p { bool ntcp2; i2p::config::GetOption("ntcp2.enabled", ntcp2); bool ntcp2Published; i2p::config::GetOption("ntcp2.published", ntcp2Published); - if (ntcp2 && ntcp2Published) + if (ntcp2) { - std::string ntcp2Host; - if (!i2p::config::IsDefault ("ntcp2.addressv6")) - i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); + if (ntcp2Published) + { + std::string ntcp2Host; + if (!i2p::config::IsDefault ("ntcp2.addressv6")) + i2p::config::GetOption ("ntcp2.addressv6", ntcp2Host); + else + ntcp2Host = "::1"; + uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); + if (!ntcp2Port) ntcp2Port = port; + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (ntcp2Host), ntcp2Port); + } else - ntcp2Host = "::1"; - uint16_t ntcp2Port; i2p::config::GetOption ("ntcp2.port", ntcp2Port); - if (!ntcp2Port) ntcp2Port = port; - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (ntcp2Host), ntcp2Port); + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address(), 0, i2p::data::RouterInfo::eV6); } } m_RouterInfo.EnableV6 (); @@ -632,7 +637,7 @@ namespace i2p m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address::from_string (host), ntcp2Port); } else - m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv); + m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, boost::asio::ip::address(), 0, i2p::data::RouterInfo::eV4); } } m_RouterInfo.EnableV4 (); diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 3f50a4b7..a426d9b7 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -840,21 +840,33 @@ namespace data for (const auto& it: *m_Addresses) // don't insert same address twice if (*it == *addr) return; m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; + m_ReachableTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; m_Addresses->push_back(std::move(addr)); } - void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host, int port) + void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, + const boost::asio::ip::address& host, int port, uint8_t caps) { auto addr = std::make_shared
    (); addr->host = host; addr->port = port; addr->transportStyle = eTransportNTCP; - addr->caps = 0; + addr->caps = caps; addr->date = 0; addr->ntcp2.reset (new NTCP2Ext ()); if (port) addr->published = true; memcpy (addr->ntcp2->staticKey, staticKey, 32); memcpy (addr->ntcp2->iv, iv, 16); + if (addr->IsV4 ()) + { + m_SupportedTransports |= eNTCP2V4; + if (addr->published) m_ReachableTransports |= eNTCP2V4; + } + if (addr->IsV6 ()) + { + m_SupportedTransports |= eNTCP2V6; + if (addr->published) m_ReachableTransports |= eNTCP2V6; + } m_Addresses->push_back(std::move(addr)); } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 89654757..f27a5d68 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -179,7 +179,8 @@ namespace data std::shared_ptr GetYggdrasilAddress () const; void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); - void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0); + void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, + const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0, uint8_t caps = 0); bool AddIntroducer (const Introducer& introducer); bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only From f5e7d87f5b8b63424e544729fd475bfd41a4892e Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 19 Jun 2021 14:25:50 -0400 Subject: [PATCH 4289/6300] don't disable floodfill if still reachable by ipv6 --- libi2pd/RouterContext.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index adce291c..b2a19890 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -470,7 +470,8 @@ namespace i2p uint8_t caps = m_RouterInfo.GetCaps (); caps &= ~i2p::data::RouterInfo::eReachable; caps |= i2p::data::RouterInfo::eUnreachable; - caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill + if (v6 || !SupportsV6 ()) + caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill m_RouterInfo.SetCaps (caps); } uint16_t port = 0; From fed04c1a199c4d97f46ae3f64915fbf4e1217934 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 19 Jun 2021 14:44:33 -0400 Subject: [PATCH 4290/6300] requsted router to send to if not in netdb --- libi2pd/Transports.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index c2bed2ab..181c3663 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -401,7 +401,7 @@ namespace transport try { auto r = netdb.FindRouter (ident); - if (!r || r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ())) return; + if (r && (r->IsUnreachable () || !r->IsReachableFrom (i2p::context.GetRouterInfo ()))) return; // router found but non-reachable { std::unique_lock l(m_PeersMutex); it = m_Peers.insert (std::pair(ident, { 0, r, {}, From 84d987810f9a6f657ea854069754d979e648442a Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 20 Jun 2021 09:36:14 +0300 Subject: [PATCH 4291/6300] add afrikaans in config example --- contrib/i2pd.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 7b85a61b..599a2081 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -108,7 +108,8 @@ port = 7070 # user = i2pd # pass = changeme ## Select webconsole language -## Currently supported english (default), russian, turkmen and ukrainian languages +## Currently supported english (default), afrikaans, russian, turkmen and ukrainian languages + # lang = english [httpproxy] From 6971b1e9da863a7260b145dde29f5961c74feaf0 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 20 Jun 2021 20:02:20 +0300 Subject: [PATCH 4292/6300] fix typo in config option description Kudos @iBicha https://github.com/PurpleI2P/i2pd/pull/1662#pullrequestreview-687850246 Signed-off-by: R4SAS --- libi2pd/Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 16d50e8c..e5640ad0 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -285,7 +285,7 @@ namespace config { options_description meshnets("Meshnet transports options"); meshnets.add_options() - ("meshnets.yggdrasil", bool_switch()->default_value(false), "Support transports through the Yggdrasil (deafult: false)") + ("meshnets.yggdrasil", bool_switch()->default_value(false), "Support transports through the Yggdrasil (default: false)") ("meshnets.yggaddress", value()->default_value(""), "Yggdrasil address to publish") ; From 35ba16ff3b986749aaf1b1a2065196a1578c1be4 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 20 Jun 2021 17:20:29 -0400 Subject: [PATCH 4293/6300] fixed #1665. cast to int64_t --- daemon/I2PControl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/I2PControl.cpp b/daemon/I2PControl.cpp index 12602c99..73dde618 100644 --- a/daemon/I2PControl.cpp +++ b/daemon/I2PControl.cpp @@ -406,7 +406,7 @@ namespace client void I2PControlService::UptimeHandler (std::ostringstream& results) { - InsertParam (results, "i2p.router.uptime", (int)i2p::context.GetUptime ()*1000); + InsertParam (results, "i2p.router.uptime", std::to_string (i2p::context.GetUptime ()*1000LL)); } void I2PControlService::VersionHandler (std::ostringstream& results) From 7d51b4c6ed83fe9e9edf0be04998df6404ff1be8 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 21 Jun 2021 21:16:25 +0300 Subject: [PATCH 4294/6300] [i18n] pull ukrainian translation from crowdin (closes #1666) Signed-off-by: R4SAS --- contrib/i18n/regex.txt | 3 + i18n/Ukrainian.cpp | 299 +++++++++++++++++++---------------------- 2 files changed, 139 insertions(+), 163 deletions(-) diff --git a/contrib/i18n/regex.txt b/contrib/i18n/regex.txt index 768a4b01..e74f9d2d 100644 --- a/contrib/i18n/regex.txt +++ b/contrib/i18n/regex.txt @@ -5,3 +5,6 @@ msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\nmsgstr\[1\]\ \"( msgid\ \"(.*)\"\nmsgstr\ \"(.*)\"\n {"$1", "$2"},\n + +^#:(.*)$\n + diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp index 11878cde..5e856c52 100644 --- a/i18n/Ukrainian.cpp +++ b/i18n/Ukrainian.cpp @@ -28,23 +28,151 @@ namespace ukrainian // language static std::map strings { - // HTTP Proxy + {"Disabled", "Вимкнуто"}, + {"Enabled", "Увімкнуто"}, + {"KiB", "КіБ"}, + {"MiB", "МіБ"}, + {"GiB", "ГіБ"}, + {"building", "будуєтьÑÑ"}, + {"failed", "невдалий"}, + {"expiring", "завершуєтьÑÑ"}, + {"established", "працює"}, + {"unknown", "невідомо"}, + {"exploratory", "доÑлідницький"}, + {"i2pd webconsole", "Веб-конÑоль i2pd"}, + {"Main page", "Головна"}, + {"Router commands", "Команди маршрутизатора"}, + {"Local destinations", "Локальні призначеннÑ"}, + {"LeaseSets", "ЛізÑети"}, + {"Tunnels", "Тунелі"}, + {"Transit tunnels", "Транзитні тунелі"}, + {"Transports", "ТранÑпорти"}, + {"I2P tunnels", "I2P тунелі"}, + {"SAM sessions", "SAM ÑеÑÑ–Ñ—"}, + {"ERROR", "ПОМИЛКÐ"}, + {"OK", "OK"}, + {"Testing", "ТеÑтуваннÑ"}, + {"Firewalled", "Заблоковано ззовні"}, + {"Unknown", "Ðевідомо"}, + {"Proxy", "ПрокÑÑ–"}, + {"Mesh", "MESH-мережа"}, + {"Error", "Помилка"}, + {"Clock skew", "Ðеточний чаÑ"}, + {"Offline", "Офлайн"}, + {"Symmetric NAT", "Симетричний NAT"}, + {"Uptime", "У мережі"}, + {"Network status", "Мережевий ÑтатуÑ"}, + {"Network status v6", "Мережевий ÑÑ‚Ð°Ñ‚ÑƒÑ v6"}, + {"Stopping in", "Зупинка через"}, + {"Family", "СімейÑтво"}, + {"Tunnel creation success rate", "УÑпішно побудованих тунелів"}, + {"Received", "Отримано"}, + {"KiB/s", "КіБ/Ñ"}, + {"Sent", "Відправлено"}, + {"Transit", "Транзит"}, + {"Data path", "ШлÑÑ… до даних"}, + {"Hidden content. Press on text to see.", "Прихований вміÑÑ‚. Щоб відобразити, натиÑніть на текÑÑ‚."}, + {"Router Ident", "Ідентифікатор маршрутизатора"}, + {"Router Family", "СімейÑтво маршрутизатора"}, + {"Router Caps", "Прапорці маршрутизатора"}, + {"Version", "ВерÑÑ–Ñ"}, + {"Our external address", "Ðаша Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ Ð°Ð´Ñ€ÐµÑа"}, + {"supported", "підтримуєтьÑÑ"}, + {"Routers", "Маршрутизатори"}, + {"Floodfills", "Флудфіли"}, + {"Client Tunnels", "КлієнтÑькі Тунелі"}, + {"Transit Tunnels", "Транзитні Тунелі"}, + {"Services", "СервіÑи"}, + {"Local Destinations", "Локальні ПризначеннÑ"}, + {"Encrypted B33 address", "Шифровані B33 адреÑи"}, + {"Address registration line", "РÑдок реєÑтрації адреÑи"}, + {"Domain", "Домен"}, + {"Generate", "Згенерувати"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Примітка: отриманий Ñ€Ñдок може бути викориÑтаний тільки Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації доменів другого Ñ€Ñ–Ð²Ð½Ñ (example.i2p). Ð”Ð»Ñ Ñ€ÐµÑ”Ñтрації піддоменів викориÑтовуйте i2pd-tools."}, + {"Address", "ÐдреÑа"}, + {"Type", "Тип"}, + {"EncType", "ТипШифр"}, + {"Inbound tunnels", "Вхідні тунелі"}, + {"ms", "мÑ"}, + {"Outbound tunnels", "Вихідні тунелі"}, + {"Tags", "Теги"}, + {"Incoming", "Вхідні"}, + {"Outgoing", "Вихідні"}, + {"Destination", "ПризначеннÑ"}, + {"Amount", "КількіÑть"}, + {"Incoming Tags", "Вхідні Теги"}, + {"Tags sessions", "СеÑÑ–Ñ— Тегів"}, + {"Status", "СтатуÑ"}, + {"Local Destination", "Локальні ПризначеннÑ"}, + {"Streams", "Потоки"}, + {"Close stream", "Закрити потік"}, + {"I2CP session not found", "I2CP ÑеÑÑ–Ñ Ð½Ðµ знайдена"}, + {"I2CP is not enabled", "I2CP не увікнуто"}, + {"Invalid", "Ðекоректний"}, + {"Store type", "Тип Ñховища"}, + {"Expires", "ЗавершуєтьÑÑ"}, + {"Non Expired Leases", "Ðе завершені Lease-и"}, + {"Gateway", "Шлюз"}, + {"TunnelID", "ID тунелÑ"}, + {"EndDate", "ЗакінчуєтьÑÑ"}, + {"not floodfill", "не флудфіл"}, + {"Queue size", "Розмір черги"}, + {"Run peer test", "ЗапуÑтити теÑтуваннÑ"}, + {"Decline transit tunnels", "ВідхилÑти транзитні тунелі"}, + {"Accept transit tunnels", "Ухвалювати транзитні тунелі"}, + {"Cancel graceful shutdown", "СкаÑувати плавну зупинку"}, + {"Start graceful shutdown", "ЗапуÑтити плавну зупинку"}, + {"Force shutdown", "ПримуÑова зупинка"}, + {"Note: any action done here are not persistent and not changes your config files.", "Примітка: будь-Ñка зроблена тут Ð´Ñ–Ñ Ð½Ðµ Ñ” поÑтійною та не змінює ваші конфігураційні файли."}, + {"Logging level", "Рівень логуваннÑ"}, + {"Transit tunnels limit", "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‚Ñ€Ð°Ð½Ð·Ð¸Ñ‚Ð½Ð¸Ñ… тунелів"}, + {"Change", "Змінити"}, + {"no transit tunnels currently built", "немає побудованих транзитних тунелів"}, + {"SAM disabled", "SAM вимкнуто"}, + {"no sessions currently running", "немає запущених ÑеÑій"}, + {"SAM session not found", "SAM ÑеÑÑ–Ñ Ð½Ðµ знайдена"}, + {"SAM Session", "SAM ÑеÑÑ–Ñ"}, + {"Server Tunnels", "Серверні Тунелі"}, + {"Client Forwards", "КлієнтÑькі ПереÑпрÑмуваннÑ"}, + {"Server Forwards", "Серверні ПереÑпрÑмуваннÑ"}, + {"Unknown page", "Ðевідома Ñторінка"}, + {"Invalid token", "Ðевірний токен"}, + {"SUCCESS", "УСПІШÐО"}, + {"Stream closed", "Потік зачинений"}, + {"Stream not found or already was closed", "Потік не знайдений або вже зачинений"}, + {"Destination not found", "Точка Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ знайдена"}, + {"StreamID can't be null", "Ідентифікатор потоку не може бути порожнім"}, + {"Return to destination page", "ПовернутиÑÑ Ð½Ð° Ñторінку точки призначеннÑ"}, + {"You will be redirected back in 5 seconds", "Ви будете переадреÑовані назад через 5 Ñекунд"}, + {"Transit tunnels count must not exceed 65535", "КількіÑть транзитних тунелів не повинна перевищувати 65535"}, + {"Back to commands list", "ПовернутиÑÑ Ð´Ð¾ ÑпиÑку команд"}, + {"Register at reg.i2p", "ЗареєÑтрувати на reg.i2p"}, + {"Description", "ОпиÑ"}, + {"A bit information about service on domain", "Трохи інформації про ÑÐµÑ€Ð²Ñ–Ñ Ð½Ð° домені"}, + {"Submit", "ÐадіÑлати"}, + {"Domain can't end with .b32.i2p", "Домен не може закінчуватиÑÑ Ð½Ð° .b32.i2p"}, + {"Domain must end with .i2p", "Домен повинен закінчуватиÑÑ Ð½Ð° .i2p"}, + {"Such destination is not found", "Така точка Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ знайдена"}, + {"Unknown command", "Ðевідома команда"}, + {"Command accepted", "Команда прийнÑта"}, + {"You will be redirected in 5 seconds", "Ви будете переадреÑовані через 5 Ñекунд"}, {"Proxy error", "Помилка прокÑÑ–"}, {"Proxy info", "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ÐºÑÑ–"}, {"Proxy error: Host not found", "Помилка прокÑÑ–: ÐдреÑа не знайдена"}, - {"Remote host not found in router's addressbook", "Віддалена адреÑа не знайдена в адреÑній книзі роутера"}, + {"Remote host not found in router's addressbook", "Віддалена адреÑа не знайдена в адреÑній книзі маршрутизатора"}, {"You may try to find this host on jump services below", "Ви можете Ñпробувати знайти дану адреÑу на джамп ÑервіÑах нижче"}, {"Invalid request", "Ðекоректний запит"}, {"Proxy unable to parse your request", "ПрокÑÑ– не може розібрати ваш запит"}, {"addresshelper is not supported", "addresshelper не підтримуєтьÑÑ"}, {"Host", "ÐдреÑа"}, - {"added to router's addressbook from helper", "доданий в адреÑну книгу роутера через хелпер"}, - {"already in router's addressbook", "вже в адреÑній книзі роутера"}, + {"added to router's addressbook from helper", "доданий в адреÑну книгу маршрутизатора через хелпер"}, {"Click", "ÐатиÑніть"}, {"here", "тут"}, {"to proceed", "щоб продовжити"}, - {"to update record", "щоб оновити запиÑ"}, {"Addresshelper found", "Знайдено addresshelper"}, + {"already in router's addressbook", "вже в адреÑній книзі маршрутизатора"}, + {"to update record", "щоб оновити запиÑ"}, + {"Invalid Request", "Ðекоректний Запит"}, {"invalid request uri", "некоректний URI запиту"}, {"Can't detect destination host from request", "Ðе вдалоÑÑŒ визначити адреÑу Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð· запиту"}, {"Outproxy failure", "Помилка зовнішнього прокÑÑ–"}, @@ -64,169 +192,14 @@ namespace ukrainian // language {"http out proxy not implemented", "підтримка зовнішнього HTTP прокÑÑ– Ñервера не реалізована"}, {"cannot connect to upstream http proxy", "не вдалоÑÑ Ð¿Ñ–Ð´ÐºÐ»ÑŽÑ‡Ð¸Ñ‚Ð¸ÑÑ Ð´Ð¾ виÑхідного HTTP прокÑÑ– Ñервера"}, {"Host is down", "Вузол недоÑтупний"}, - {"Can't create connection to requested host, it may be down. Please try again later.", - "Ðе вдалоÑÑ Ð²Ñтановити з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð´Ð¾ запитаного вузла, можливо він не в мережі. Спробуйте повторити запит пізніше."}, - - // Webconsole // - // cssStyles - {"Disabled", "Вимкнуто"}, - {"Enabled", "Увімкнуто"}, - // ShowTraffic - {"KiB", "КіБ"}, - {"MiB", "МіБ"}, - {"GiB", "ГіБ"}, - // ShowTunnelDetails - {"building", "будуєтьÑÑ"}, - {"failed", "невдалий"}, - {"expiring", "завершуєтьÑÑ"}, - {"established", "працює"}, - {"exploratory", "доÑлідницький"}, - {"unknown", "невідомо"}, - {"i2pd webconsole", "Веб-конÑоль i2pd"}, - // ShowPageHead - {"Main page", "Головна"}, - {"Router commands", "Команди роутера"}, - {"Local destinations", "Локальні призначеннÑ"}, - {"LeaseSets", "ЛізÑети"}, - {"Tunnels", "Тунелі"}, - {"Transit tunnels", "Транзитні тунелі"}, - {"Transports", "ТранÑпорти"}, - {"I2P tunnels", "I2P тунелі"}, - {"SAM sessions", "SAM ÑеÑÑ–Ñ—"}, - // Network Status - {"OK", "OK"}, - {"Testing", "ТеÑтуваннÑ"}, - {"Firewalled", "Заблоковано ззовні"}, - {"Unknown", "Ðевідомо"}, - {"Proxy", "ПрокÑÑ–"}, - {"Mesh", "MESH-мережа"}, - {"Error", "Помилка"}, - {"Clock skew", "Ðеточний чаÑ"}, - {"Offline", "Офлайн"}, - {"Symmetric NAT", "Симетричний NAT"}, - // Status - {"Uptime", "Ð’ мережі"}, - {"Network status", "Мережевий ÑтатуÑ"}, - {"Network status v6", "Мережевий ÑÑ‚Ð°Ñ‚ÑƒÑ v6"}, - {"Stopping in", "Зупинка через"}, - {"Family", "СімейÑтво"}, - {"Tunnel creation success rate", "УÑпішно побудованих тунелів"}, - {"Received", "Отримано"}, - {"Sent", "Відправлено"}, - {"Transit", "Транзит"}, - {"KiB/s", "КіБ/Ñ"}, - {"Data path", "ШлÑÑ… до даних"}, - {"Hidden content. Press on text to see.", "Прихований вміÑÑ‚. ÐатиÑніть на текÑÑ‚ щоб відобразити."}, - {"Router Ident", "Ідентифікатор Роутера"}, - {"Router Family", "СімейÑтво Роутера"}, - {"Router Caps", "Прапорці Роутера"}, - {"Version", "ВерÑÑ–Ñ"}, - {"Our external address", "Ðаша Ð·Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ Ð°Ð´Ñ€ÐµÑа"}, - {"supported", "підтримуєтьÑÑ"}, - {"Routers", "Роутери"}, - {"Floodfills", "Флудфіли"}, - {"Client Tunnels", "КлієнтÑькі Тунелі"}, - {"Transit Tunnels", "Транзитні Тунелі"}, - {"Services", "СервіÑи"}, - // ShowLocalDestinations - {"Local Destinations", "Локальні ПризначеннÑ"}, - // ShowLeaseSetDestination - {"Encrypted B33 address", "Шифровані B33 адреÑи"}, - {"Address registration line", "РÑдок реєÑтрації адреÑи"}, - {"Domain", "Домен"}, - {"Generate", "Згенерувати"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", - "Примітка: отриманий Ñ€Ñдок може бути викориÑтаний тільки Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації доменів другого рівнÑ. Ð”Ð»Ñ Ñ€ÐµÑ”Ñтрації піддоменів викориÑтовуйте i2pd-tools."}, - {"Address", "ÐдреÑа"}, - {"Type", "Тип"}, - {"EncType", "ТипШифр"}, - {"Inbound tunnels", "Вхідні тунелі"}, - {"Outbound tunnels", "Вихідні тунелі"}, - {"ms", "мÑ"}, // milliseconds - {"Tags", "Теги"}, - {"Incoming", "Вхідні"}, - {"Outgoing", "Вихідні"}, - {"Destination", "ПризначеннÑ"}, - {"Amount", "КількіÑть"}, - {"Incoming Tags", "Вхідні Теги"}, - {"Tags sessions", "СеÑÑ–Ñ— тегів"}, - {"Status", "СтатуÑ"}, - // ShowLocalDestination - {"Local Destination", "Локальні ПризначеннÑ"}, - {"Streams", "Потоки"}, - {"Close stream", "Закрити потік"}, - // ShowI2CPLocalDestination - {"I2CP session not found", "I2CP ÑеÑÑ–Ñ Ð½Ðµ знайдена"}, - {"I2CP is not enabled", "I2CP не увікнуто"}, - // ShowLeasesSets - {"Invalid", "Ðекоректний"}, - {"Store type", "Тип Ñховища"}, - {"Expires", "ЗавершуєтьÑÑ"}, - {"Non Expired Leases", "Ðе завершені Lease-и"}, - {"Gateway", "Шлюз"}, - {"TunnelID", "ID тунелÑ"}, - {"EndDate", "ЗакінчуєтьÑÑ"}, - {"not floodfill", "не флудфіл"}, - // ShowTunnels - {"Queue size", "Розмір черги"}, - // ShowCommands - {"Run peer test", "ЗапуÑтити теÑтуваннÑ"}, - {"Decline transit tunnels", "ВідхилÑти транзитні тунелі"}, - {"Accept transit tunnels", "Ухвалювати транзитні тунелі"}, - {"Cancel graceful shutdown", "СкаÑувати плавну зупинку"}, - {"Start graceful shutdown", "ЗапуÑтити плавну зупинку"}, - {"Force shutdown", "ПримуÑова зупинка"}, - {"Note: any action done here are not persistent and not changes your config files.", - "Примітка: будь-Ñка зроблена тут Ð´Ñ–Ñ Ð½Ðµ Ñ” поÑтійною та не змінює ваші конфігураційні файли."}, - {"Logging level", "Рівень логуваннÑ"}, - {"Transit tunnels limit", "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‚Ñ€Ð°Ð½Ð·Ð¸Ñ‚Ð½Ð¸Ñ… тунелів"}, - {"Change", "Змінити"}, - // ShowTransitTunnels - {"no transit tunnels currently built", "немає побудованих транзитних тунелів"}, - // ShowSAMSessions/ShowSAMSession - {"SAM disabled", "SAM вимкнуто"}, - {"SAM session not found", "SAM ÑеÑÑ–Ñ Ð½Ðµ знайдена"}, - {"no sessions currently running", "немає запущених ÑеÑій"}, - {"SAM Session", "SAM ÑеÑÑ–Ñ"}, - // ShowI2PTunnels - {"Server Tunnels", "Серверні Тунелі"}, - {"Client Forwards", "КлієнтÑькі ПереÑпрÑмуваннÑ"}, - {"Server Forwards", "Серверні ПереÑпрÑмуваннÑ"}, - // HandlePage - {"Unknown page", "Ðевідома Ñторінка"}, - // HandleCommand, ShowError - {"Invalid token", "Ðевірний токен"}, - {"SUCCESS", "УСПІШÐО"}, - {"ERROR", "ПОМИЛКÐ"}, - {"Unknown command", "Ðевідома команда"}, - {"Command accepted", "Команда прийнÑта"}, - {"Back to commands list", "ПовернутиÑÑ Ð´Ð¾ ÑпиÑку команд"}, - {"You will be redirected in 5 seconds", "Ви будете переадреÑовані через 5 Ñекунд"}, - // HTTP_COMMAND_KILLSTREAM - {"Stream closed", "Потік зачинений"}, - {"Stream not found or already was closed", "Потік не знайдений або вже зачинений"}, - {"Destination not found", "Точка Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ знайдена"}, - {"StreamID can't be null", "Ідентифікатор потоку не може бути порожнім"}, - {"Return to destination page", "ПовернутиÑÑ Ð½Ð° Ñторінку точки призначеннÑ"}, - {"You will be redirected back in 5 seconds", "Ви будете переадреÑовані назад через 5 Ñекунд"}, - // HTTP_COMMAND_LIMITTRANSIT - {"Transit tunnels count must not exceed 65535", "КількіÑть транзитних тунелів не повинна перевищувати 65535"}, - // HTTP_COMMAND_GET_REG_STRING - {"Register at reg.i2p", "ЗареєÑтрувати на reg.i2p"}, - {"Description", "ОпиÑ"}, - {"A bit information about service on domain", "Трохи інформації про ÑÐµÑ€Ð²Ñ–Ñ Ð½Ð° домені"}, - {"Submit", "ÐадіÑлати"}, - {"Domain can't end with .b32.i2p", "Домен не може закінчуватиÑÑ Ð½Ð° .b32.i2p"}, - {"Domain must end with .i2p", "Домен повинен закінчуватиÑÑ Ð½Ð° .i2p"}, - {"Such destination is not found", "Така точка Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ знайдена"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Ðе вдалоÑÑ Ð²Ñтановити з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð´Ð¾ запитаного вузла, можливо він не в мережі. Спробуйте повторити запит пізніше."}, {"", ""}, }; static std::map> plurals { - // ShowUptime - {"days", {"день", "днÑ", "днів"}}, - {"hours", {"годину", "години", "годин"}}, + {"days", {"день", "днÑ", "днів"}}, + {"hours", {"годину", "години", "годин"}}, {"minutes", {"хвилину", "хвилини", "хвилин"}}, {"seconds", {"Ñекунду", "Ñекунди", "Ñекунд"}}, {"", {"", "", ""}}, From f7f50d049b98366726a6fd2518c109930f23e997 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 22 Jun 2021 13:11:02 -0400 Subject: [PATCH 4295/6300] reduce short tunnel build record length --- libi2pd/I2NPProtocol.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index ee143734..cc7c94af 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -55,7 +55,7 @@ namespace i2p // TunnelBuild const size_t TUNNEL_BUILD_RECORD_SIZE = 528; - const size_t SHORT_TUNNEL_BUILD_RECORD_SIZE = 236; + const size_t SHORT_TUNNEL_BUILD_RECORD_SIZE = 218; //BuildRequestRecordClearText const size_t BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; @@ -113,7 +113,7 @@ namespace i2p const size_t SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET = SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE + 1; const size_t SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET = SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; const size_t SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET = SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET + 4; - const size_t SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE = 172; + const size_t SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE = 154; enum I2NPMessageType { From 3c076654794c619eed228adcac075e9c1dea732f Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 22 Jun 2021 15:35:44 -0400 Subject: [PATCH 4296/6300] use unordered_map for incomplete messages --- libi2pd/TunnelEndpoint.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libi2pd/TunnelEndpoint.h b/libi2pd/TunnelEndpoint.h index 43b836f1..6466df3a 100644 --- a/libi2pd/TunnelEndpoint.h +++ b/libi2pd/TunnelEndpoint.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,6 +11,7 @@ #include #include +#include #include #include "I2NPProtocol.h" #include "TunnelBase.h" @@ -54,7 +55,7 @@ namespace tunnel private: - std::map m_IncompleteMessages; + std::unordered_map m_IncompleteMessages; std::map, Fragment> m_OutOfSequenceFragments; // (msgID, fragment#)->fragment bool m_IsInbound; size_t m_NumReceivedBytes; From f5db34b98b4a081bdfac694c82eb5ebe4f3c171b Mon Sep 17 00:00:00 2001 From: idk Date: Wed, 23 Jun 2021 11:18:53 -0400 Subject: [PATCH 4297/6300] C_InitI2P is compatible with more things if it passes argv by reference, it would appear. So to pass arguments to InitI2P you need to turn them back into char* argv[] by tokenizing them and copying them into an array which you then pass to InitI2P from C_InitI2P. The Streaming and Destination Creation parts need to have wrappers for over Identity.h, Streaming.h to be useful so remove them. --- Makefile | 47 ++++--------------- libi2pd/api.go | 5 +-- libi2pd/api.swigcxx | 8 ---- libi2pd/capi.cpp | 107 ++++++++++++++++++++++++++------------------ libi2pd/capi.h | 17 ------- 5 files changed, 73 insertions(+), 111 deletions(-) diff --git a/Makefile b/Makefile index 62a64584..72e68118 100644 --- a/Makefile +++ b/Makefile @@ -28,12 +28,6 @@ else LD_DEBUG = -s endif -ifeq ($(USE_STATIC),yes) - NEEDED_CXXFLAGS+= -static -else - -endif - ifneq (, $(findstring darwin, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp ifeq ($(HOMEBREW),1) @@ -76,9 +70,9 @@ mk_obj_dir: @mkdir -p obj/$(LANG_SRC_DIR) @mkdir -p obj/$(DAEMON_SRC_DIR) -api: mk_obj_dir $(SHLIB) $(ARLIB) -client: mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) -api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) +api: mk_obj_dir $(SHLIB) $(ARLIB) +client: mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) +api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) langs: mk_obj_dir $(LANG_OBJS) $(SHLIB_LANG) $(ARLIB_LANG) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time @@ -97,31 +91,30 @@ obj/%.o: %.cpp $(I2PD): $(LANG_OBJS) $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) -$(SHLIB): $(LIB_OBJS) +$(SHLIB): $(LIB_OBJS) ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) endif -$(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) +$(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) endif -$(SHLIB_LANG): $(LANG_OBJS) +$(SHLIB_LANG): $(LANG_OBJS) ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) endif -$(ARLIB): $(LIB_OBJS) +$(ARLIB): $(LIB_OBJS) $(AR) -r $@ $^ -$(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) +$(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) $(AR) -r $@ $^ -$(ARLIB_LANG): $(LANG_OBJS) +$(ARLIB_LANG): $(LANG_OBJS) $(AR) -r $@ $^ - clean: $(RM) -r obj $(RM) -r docs/generated @@ -154,25 +147,3 @@ doxygen: .PHONY: mk_obj_dir .PHONY: install .PHONY: strip - -flags: - @echo $(CXXFLAGS) - @echo $(NEEDED_CXXFLAGS) - @echo $(INCFLAGS) - @echo $(LDFLAGS) - @echo $(LDLIBS) - @echo $(USE_AESNI) - @echo $(USE_STATIC) - @echo $(USE_MESHNET) - @echo $(USE_UPNP) - @echo $(DEBUG) - -##TODO: delete this before a PR -testc: api api_client - g++ -Ii18n -c _test.c -o test.o -# gcc -llibi2pd.so -c _test.c -o test.o -# $(CXX) $(LDFLAGS) $(LDLIBS) -static -Ii18n -Ilibi2pd -Ilibi2pd_client -g -Wall -o test.o _test.c libi2pd.a libi2pdclient.a #obj/libi2pd/*.o obj/i18n/*.o #libi2pd.so -# $(CXX) $(LDFLAGS) $(LDLIBS) -static -Ii18n -g -Wall -o test.o _test.c libi2pd.a libi2pdclient.a #obj/libi2pd/*.o obj/i18n/*.o #libi2pd.so -# gcc -o i2pd _test.c libi2pd.a -lstdc++ -llibi2pd -Llibi2pd -# gcc -Ii18n -I/usr/include/c++/10 -I/usr/include/x86_64-linux-gnu/c++/10 -llibi2pd.a -c test.c -o test.o - g++ test.o libi2pd.a libi2pdclient.a libi2pdlang.a -o test.main \ No newline at end of file diff --git a/libi2pd/api.go b/libi2pd/api.go index d7a19bc9..48a41a4f 100644 --- a/libi2pd/api.go +++ b/libi2pd/api.go @@ -1,10 +1,7 @@ package api /* -//void Go_InitI2P (int argc, char argv[], const char * appName){ - -//} -#cgo CPPFLAGS: -I${SRCDIR}/../i18n -I${SRCDIR}/../libi2pd_client -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi -fPIC -D__AES__ -maes +#cgo CXXFLAGS: -I${SRCDIR}/../i18n -I${SRCDIR}/../libi2pd_client -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi -fPIC -D__AES__ -maes #cgo LDFLAGS: -latomic -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lstdc++ */ import "C" diff --git a/libi2pd/api.swigcxx b/libi2pd/api.swigcxx index fbb1b95a..3ef6bd36 100644 --- a/libi2pd/api.swigcxx +++ b/libi2pd/api.swigcxx @@ -3,14 +3,6 @@ %{ #include "capi.h" -//#include "Streaming.h" -//#include "Destination.h" -//#include "Identity.h" -//#include "Tag.h" %} %include "capi.h" -//%include "Streaming.h" -//%include "Destination.h" -//%include "Identity.h" -//%include "Tag.h" \ No newline at end of file diff --git a/libi2pd/capi.cpp b/libi2pd/capi.cpp index 1a2498b6..55b1b051 100644 --- a/libi2pd/capi.cpp +++ b/libi2pd/capi.cpp @@ -8,6 +8,64 @@ #include "api.h" #include "capi.h" +#include +#include +#include +#include + + +// Uses the example from: https://stackoverflow.com/a/9210560 +// See also https://stackoverflow.com/questions/9210528/split-string-with-delimiters-in-c/9210560# +// Does not handle consecutive delimiters, this is only for passing +// lists of arguments by value to InitI2P from C_InitI2P +char** str_split(char* a_str, const char a_delim) +{ + char** result = 0; + size_t count = 0; + char* tmp = a_str; + char* last_comma = 0; + char delim[2]; + delim[0] = a_delim; + delim[1] = 0; + + /* Count how many elements will be extracted. */ + while (*tmp) + { + if (a_delim == *tmp) + { + count++; + last_comma = tmp; + } + tmp++; + } + + /* Add space for trailing token. */ + count += last_comma < (a_str + strlen(a_str) - 1); + + /* Add space for terminating null string so caller + knows where the list of returned strings ends. */ + count++; + + result = (char**) malloc(sizeof(char*) * count); + + if (result) + { + size_t idx = 0; + char* token = strtok(a_str, delim); + + while (token) + { + assert(idx < count); + *(result + idx++) = strdup(token); + token = strtok(0, delim); + } + assert(idx == count - 1); + *(result + idx) = 0; + } + + return result; +} + #ifdef __cplusplus extern "C" { @@ -15,7 +73,11 @@ extern "C" { void C_InitI2P (int argc, char argv[], const char * appName) { - return i2p::api::InitI2P(argc, &argv, appName); + const char* delim = " "; + char* vargs = strdup(argv); + char** args = str_split(vargs, *delim); + std::cout << argv; + return i2p::api::InitI2P(argc, args, appName); } void C_TerminateI2P () @@ -40,49 +102,6 @@ void C_RunPeerTest () return i2p::api::RunPeerTest(); } -i2p::client::ClientDestination *C_CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, - const std::map * params) -{ - return i2p::api::CreateLocalDestination(keys, isPublic, params).get(); -} - -i2p::client::ClientDestination *C_CreateTransientLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, - const std::map * params) -{ - return i2p::api::CreateLocalDestination(isPublic, sigType, params).get(); -} - -void C_DestroyLocalDestination (i2p::client::ClientDestination *dest) -{ - std::shared_ptr cppDest(dest); - return i2p::api::DestroyLocalDestination(cppDest); -} - -void C_RequestLeaseSet (i2p::client::ClientDestination *dest, const i2p::data::IdentHash& remote) -{ - std::shared_ptr cppDest(dest); - return i2p::api::RequestLeaseSet(cppDest, remote); -} - -i2p::stream::Stream *C_CreateStream (i2p::client::ClientDestination *dest, const i2p::data::IdentHash& remote) -{ - std::shared_ptr cppDest(dest); - return i2p::api::CreateStream(cppDest, remote).get(); -} - -void C_AcceptStream (i2p::client::ClientDestination *dest, const i2p::stream::StreamingDestination::Acceptor& acceptor) -{ - std::shared_ptr cppDest(dest); - return i2p::api::AcceptStream(cppDest, acceptor); -} - -void C_DestroyStream (i2p::stream::Stream *stream) -{ - std::shared_ptr cppStream(stream); - return i2p::api::DestroyStream(cppStream); -} - - #ifdef __cplusplus } #endif diff --git a/libi2pd/capi.h b/libi2pd/capi.h index 8395cfca..3e33a0ee 100644 --- a/libi2pd/capi.h +++ b/libi2pd/capi.h @@ -9,10 +9,6 @@ #ifndef CAPI_H__ #define CAPI_H__ -#include "Identity.h" -#include "Destination.h" -#include "Streaming.h" - #ifdef __cplusplus extern "C" { #endif @@ -26,19 +22,6 @@ void C_StartI2P (); //std::ostream *logStream = nullptr); void C_StopI2P (); void C_RunPeerTest (); // should be called after UPnP -// destinations -i2p::client::ClientDestination *C_CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, - const std::map * params = nullptr); -i2p::client::ClientDestination *C_CreateTransientLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, - const std::map * params = nullptr); // transient destinations usually not published -void C_DestroyLocalDestination (i2p::client::ClientDestination *dest); - -// streams -void C_RequestLeaseSet (i2p::client::ClientDestination *dest, const i2p::data::IdentHash& remote); -i2p::stream::Stream *C_CreateStream (i2p::client::ClientDestination *dest, const i2p::data::IdentHash& remote); -void C_AcceptStream (i2p::client::ClientDestination *dest, const i2p::stream::StreamingDestination::Acceptor& acceptor); -void C_DestroyStream (i2p::stream::Stream *stream); - #ifdef __cplusplus } #endif From f9d9aa0306eae1c19849e7ea40d0cfb2a15eb76d Mon Sep 17 00:00:00 2001 From: idk Date: Thu, 24 Jun 2021 09:35:42 -0400 Subject: [PATCH 4298/6300] move wrapper code to own directory --- Makefile | 14 ++++++++++++++ filelist.mk | 2 ++ {libi2pd => libi2pd_wrapper}/api.go | 0 {libi2pd => libi2pd_wrapper}/api.swigcxx | 0 {libi2pd => libi2pd_wrapper}/capi.cpp | 0 {libi2pd => libi2pd_wrapper}/capi.h | 0 6 files changed, 16 insertions(+) rename {libi2pd => libi2pd_wrapper}/api.go (100%) rename {libi2pd => libi2pd_wrapper}/api.swigcxx (100%) rename {libi2pd => libi2pd_wrapper}/capi.cpp (100%) rename {libi2pd => libi2pd_wrapper}/capi.h (100%) diff --git a/Makefile b/Makefile index 72e68118..59faa94b 100644 --- a/Makefile +++ b/Makefile @@ -5,10 +5,13 @@ SHLIB_LANG := libi2pdlang.so ARLIB_LANG := libi2pdlang.a SHLIB_CLIENT := libi2pdclient.so ARLIB_CLIENT := libi2pdclient.a +SHLIB_WRAP := libi2pdwrapper.so +ARLIB_WRAP := libi2pdwrapper.a I2PD := i2pd LIB_SRC_DIR := libi2pd LIB_CLIENT_SRC_DIR := libi2pd_client +WRAP_SRC_DIR := libi2pd_wrapper LANG_SRC_DIR := i18n DAEMON_SRC_DIR := daemon @@ -56,6 +59,7 @@ NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -I$(LANG_SR LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) LIB_CLIENT_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) +WRAP_LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(WRAP_LIB_SRC)) LANG_OBJS += $(patsubst %.cpp,obj/%.o,$(LANG_SRC)) DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(LANG_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) @@ -68,11 +72,13 @@ mk_obj_dir: @mkdir -p obj/$(LIB_SRC_DIR) @mkdir -p obj/$(LIB_CLIENT_SRC_DIR) @mkdir -p obj/$(LANG_SRC_DIR) + @mkdir -p obj/$(WRAP_SRC_DIR) @mkdir -p obj/$(DAEMON_SRC_DIR) api: mk_obj_dir $(SHLIB) $(ARLIB) client: mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) +wrapper: api_client $(SHLIB_WRAP) $(ARLIB_WRAP) langs: mk_obj_dir $(LANG_OBJS) $(SHLIB_LANG) $(ARLIB_LANG) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time @@ -101,6 +107,11 @@ ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) endif +$(SHLIB_WRAP): $(WRAP_LIB_OBJS) +ifneq ($(USE_STATIC),yes) + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) +endif + $(SHLIB_LANG): $(LANG_OBJS) ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) @@ -112,6 +123,9 @@ $(ARLIB): $(LIB_OBJS) $(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) $(AR) -r $@ $^ +$(ARLIB_WRAP): $(LIB_OBJS) + $(AR) -r $@ $^ + $(ARLIB_LANG): $(LANG_OBJS) $(AR) -r $@ $^ diff --git a/filelist.mk b/filelist.mk index e2a5d40e..d8f503e6 100644 --- a/filelist.mk +++ b/filelist.mk @@ -21,4 +21,6 @@ LIB_CLIENT_SRC = $(wildcard $(LIB_CLIENT_SRC_DIR)/*.cpp) LANG_SRC = $(wildcard $(LANG_SRC_DIR)/*.cpp) +WRAP_LIB_SRC = $(wildcard $(WRAP_SRC_DIR)/*.cpp) + DAEMON_SRC = $(wildcard $(DAEMON_SRC_DIR)/*.cpp) diff --git a/libi2pd/api.go b/libi2pd_wrapper/api.go similarity index 100% rename from libi2pd/api.go rename to libi2pd_wrapper/api.go diff --git a/libi2pd/api.swigcxx b/libi2pd_wrapper/api.swigcxx similarity index 100% rename from libi2pd/api.swigcxx rename to libi2pd_wrapper/api.swigcxx diff --git a/libi2pd/capi.cpp b/libi2pd_wrapper/capi.cpp similarity index 100% rename from libi2pd/capi.cpp rename to libi2pd_wrapper/capi.cpp diff --git a/libi2pd/capi.h b/libi2pd_wrapper/capi.h similarity index 100% rename from libi2pd/capi.h rename to libi2pd_wrapper/capi.h From d0c5732e16d28f0be86c4026ef23e0b70518f8ea Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 26 Jun 2021 07:18:42 -0400 Subject: [PATCH 4299/6300] eliminate extra lookups for sequential fragments --- libi2pd/TunnelEndpoint.cpp | 143 +++++++++++++++++++++++++++---------- libi2pd/TunnelEndpoint.h | 12 ++-- 2 files changed, 113 insertions(+), 42 deletions(-) diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp index eb70bdca..a07230cb 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -52,11 +52,13 @@ namespace tunnel bool isFollowOnFragment = flag & 0x80, isLastFragment = true; uint32_t msgID = 0; int fragmentNum = 0; - TunnelMessageBlockEx m; + TunnelMessageBlockEx& m = m_CurrentMessage; if (!isFollowOnFragment) { // first fragment - + if (m_CurrentMsgID) + AddIncompleteCurrentMessage (); // we have got a new message while previous is not complete + m.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); switch (m.deliveryType) { @@ -81,6 +83,7 @@ namespace tunnel // Message ID msgID = bufbe32toh (fragment); fragment += 4; + m_CurrentMsgID = msgID; isLastFragment = false; } } @@ -96,11 +99,20 @@ namespace tunnel uint16_t size = bufbe16toh (fragment); fragment += 2; + if (isFollowOnFragment && m_CurrentMsgID && m_CurrentMsgID == msgID && + m_CurrentMessage.nextFragmentNum == fragmentNum) + { + HandleCurrenMessageFollowOnFragment (fragment, size, isLastFragment); + fragment += size; + continue; + } + msg->offset = fragment - msg->buf; msg->len = msg->offset + size; if (msg->len > msg->maxLen) { LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size); + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; return; } if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) @@ -114,31 +126,31 @@ namespace tunnel else m.data = msg; - if (!isFollowOnFragment && isLastFragment) - HandleNextMessage (m); + if (!isFollowOnFragment) + { + if (isLastFragment) + { + HandleNextMessage (m); + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + } + else if (msgID) + { + m_CurrentMessage.nextFragmentNum = 1; + m_CurrentMessage.receiveTime = i2p::util::GetMillisecondsSinceEpoch (); + HandleOutOfSequenceFragments (msgID, m_CurrentMessage); + } + else + { + LogPrint (eLogError, "TunnelMessage: Message is fragmented, but msgID is not presented"); + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + } + } else { - if (msgID) // msgID is presented, assume message is fragmented - { - if (!isFollowOnFragment) // create new incomlete message - { - m.nextFragmentNum = 1; - m.receiveTime = i2p::util::GetMillisecondsSinceEpoch (); - auto ret = m_IncompleteMessages.insert (std::pair(msgID, m)); - if (ret.second) - HandleOutOfSequenceFragments (msgID, ret.first->second); - else - LogPrint (eLogError, "TunnelMessage: Incomplete message ", msgID, " already exists"); - } - else - { - m.nextFragmentNum = fragmentNum; - HandleFollowOnFragment (msgID, isLastFragment, m); - } - } - else - LogPrint (eLogError, "TunnelMessage: Message is fragmented, but msgID is not presented"); - } + m.nextFragmentNum = fragmentNum; + HandleFollowOnFragment (msgID, isLastFragment, m); + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + } fragment += size; } @@ -157,17 +169,8 @@ namespace tunnel auto& msg = it->second; if (m.nextFragmentNum == msg.nextFragmentNum) { - if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long + if (ConcatFollowOnFragment (msg, fragment, size)) { - if (msg.data->len + size > msg.data->maxLen) - { - // LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough"); - auto newMsg = NewI2NPMessage (); - *newMsg = *(msg.data); - msg.data = newMsg; - } - if (msg.data->Concat (fragment, size) < size) // concatenate fragment - LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen); if (isLastFragment) { // message complete @@ -199,9 +202,67 @@ namespace tunnel } } + bool TunnelEndpoint::ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const + { + if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long + { + if (msg.data->len + size > msg.data->maxLen) + { + // LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough"); + auto newMsg = NewI2NPMessage (); + *newMsg = *(msg.data); + msg.data = newMsg; + } + if (msg.data->Concat (fragment, size) < size) // concatenate fragment + { + LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen); + return false; + } + } + else + return false; + return true; + } + + void TunnelEndpoint::HandleCurrenMessageFollowOnFragment (const uint8_t * fragment, size_t size, bool isLastFragment) + { + if (ConcatFollowOnFragment (m_CurrentMessage, fragment, size)) + { + if (isLastFragment) + { + // message complete + HandleNextMessage (m_CurrentMessage); + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + } + else + { + m_CurrentMessage.nextFragmentNum++; + HandleOutOfSequenceFragments (m_CurrentMsgID, m_CurrentMessage); + } + } + else + { + LogPrint (eLogError, "TunnelMessage: Fragment ", m_CurrentMessage.nextFragmentNum, " of message ", m_CurrentMsgID, " exceeds max I2NP message size, message dropped"); + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + } + } + + void TunnelEndpoint::AddIncompleteCurrentMessage () + { + if (m_CurrentMsgID) + { + auto ret = m_IncompleteMessages.emplace (m_CurrentMsgID, m_CurrentMessage); + if (!ret.second) + LogPrint (eLogError, "TunnelMessage: Incomplete message ", m_CurrentMsgID, " already exists"); + m_CurrentMessage.data = nullptr; + m_CurrentMsgID = 0; + } + } + void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data) { - if (!m_OutOfSequenceFragments.insert ({{msgID, fragmentNum}, {isLastFragment, data, i2p::util::GetMillisecondsSinceEpoch () }}).second) + if (!m_OutOfSequenceFragments.insert ({(uint64_t)msgID << 32 | fragmentNum, + {isLastFragment, data, i2p::util::GetMillisecondsSinceEpoch () }}).second) LogPrint (eLogInfo, "TunnelMessage: duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); } @@ -212,7 +273,13 @@ namespace tunnel if (!msg.nextFragmentNum) // message complete { HandleNextMessage (msg); - m_IncompleteMessages.erase (msgID); + if (&msg == &m_CurrentMessage) + { + m_CurrentMsgID = 0; + m_CurrentMessage.data = nullptr; + } + else + m_IncompleteMessages.erase (msgID); break; } } @@ -220,7 +287,7 @@ namespace tunnel bool TunnelEndpoint::ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg) { - auto it = m_OutOfSequenceFragments.find ({msgID, msg.nextFragmentNum}); + auto it = m_OutOfSequenceFragments.find ((uint64_t)msgID << 32 | msg.nextFragmentNum); if (it != m_OutOfSequenceFragments.end ()) { LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found"); diff --git a/libi2pd/TunnelEndpoint.h b/libi2pd/TunnelEndpoint.h index 6466df3a..70514e35 100644 --- a/libi2pd/TunnelEndpoint.h +++ b/libi2pd/TunnelEndpoint.h @@ -10,7 +10,6 @@ #define TUNNEL_ENDPOINT_H__ #include -#include #include #include #include "I2NPProtocol.h" @@ -37,7 +36,7 @@ namespace tunnel public: - TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0) {}; + TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0), m_CurrentMsgID (0) {}; ~TunnelEndpoint (); size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; void Cleanup (); @@ -47,18 +46,23 @@ namespace tunnel private: void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m); + bool ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const; // true if success + void HandleCurrenMessageFollowOnFragment (const uint8_t * frgament, size_t size, bool isLastFragment); void HandleNextMessage (const TunnelMessageBlock& msg); void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data); bool ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); // true if something added void HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg); - + void AddIncompleteCurrentMessage (); + private: std::unordered_map m_IncompleteMessages; - std::map, Fragment> m_OutOfSequenceFragments; // (msgID, fragment#)->fragment + std::unordered_map m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment bool m_IsInbound; size_t m_NumReceivedBytes; + TunnelMessageBlockEx m_CurrentMessage; + uint32_t m_CurrentMsgID; }; } } From da20cae25ca13f66603d9095fff208fc066829a8 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 26 Jun 2021 18:59:48 +0300 Subject: [PATCH 4300/6300] [webconsole] urldecode domain for registration string generator Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 7eb296c4..0b9e8f0f 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1281,7 +1281,7 @@ namespace http { else if (cmd == HTTP_COMMAND_GET_REG_STRING) { std::string b32 = params["b32"]; - std::string name = params["name"]; + std::string name = i2p::http::UrlDecode(params["name"]); i2p::data::IdentHash ident; ident.FromBase32 (b32); From 377a50fa1301063e2c3398a87ec0e84790e8659e Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 26 Jun 2021 23:45:55 +0300 Subject: [PATCH 4301/6300] [make] build translations as library Signed-off-by: R4SAS --- Makefile | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 40e72918..4117234e 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ SHLIB := libi2pd.so ARLIB := libi2pd.a SHLIB_CLIENT := libi2pdclient.so ARLIB_CLIENT := libi2pdclient.a +SHLIB_LANG := libi2pdlang.so +ARLIB_LANG := libi2pdlang.a I2PD := i2pd LIB_SRC_DIR := libi2pd @@ -71,6 +73,7 @@ mk_obj_dir: api: mk_obj_dir $(SHLIB) $(ARLIB) client: mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) +lang: mk_obj_dir $(SHLIB_LANG) $(ARLIB_LANG) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. @@ -85,7 +88,7 @@ obj/%.o: %.cpp # '-' is 'ignore if missing' on first run -include $(DEPS) -$(I2PD): $(LANG_OBJS) $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) +$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) $(SHLIB): $(LIB_OBJS) @@ -98,18 +101,26 @@ ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) endif +$(SHLIB_LANG): $(LANG_OBJS) +ifneq ($(USE_STATIC),yes) + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) +endif + $(ARLIB): $(LIB_OBJS) $(AR) -r $@ $^ $(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) $(AR) -r $@ $^ +$(ARLIB_LANG): $(LANG_OBJS) + $(AR) -r $@ $^ + clean: $(RM) -r obj $(RM) -r docs/generated - $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) + $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) $(SHLIB_LANG) $(ARLIB_LANG) -strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) +strip: $(I2PD) $(SHLIB) $(SHLIB_CLIENT) $(SHLIB_LANG) strip $^ LATEST_TAG=$(shell git describe --tags --abbrev=0 openssl) @@ -133,6 +144,7 @@ doxygen: .PHONY: api .PHONY: api_client .PHONY: client +.PHONY: lang .PHONY: mk_obj_dir .PHONY: install .PHONY: strip From 9fb8e8a582e8f23e39da0c635ee8c0efe4fcf0c5 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 26 Jun 2021 23:59:34 +0300 Subject: [PATCH 4302/6300] [cmake] build translations as library Signed-off-by: R4SAS --- .gitignore | 2 ++ build/CMakeLists.txt | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2506fd93..1fc6cefe 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,10 @@ netDb /i2pd /libi2pd.a /libi2pdclient.a +/libi2pdlang.a /libi2pd.so /libi2pdclient.so +/libi2pdlang.so *.exe diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index d1b51f23..d1502deb 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -69,6 +69,16 @@ if(WITH_LIBRARY) endif() FILE(GLOB LANG_SRC ${LANG_SRC_DIR}/*.cpp) +add_library(libi2pdlang ${LANG_SRC}) +set_target_properties(libi2pdlang PROPERTIES PREFIX "") + +if(WITH_LIBRARY) + install(TARGETS libi2pdlang + EXPORT libi2pdlang + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + COMPONENT Libraries) +endif() set(DAEMON_SRC "${DAEMON_SRC_DIR}/Daemon.cpp" @@ -198,10 +208,11 @@ if(WITH_PCH) ) target_compile_options(libi2pd PRIVATE -include libi2pd/stdafx.h) target_compile_options(libi2pdclient PRIVATE -include libi2pd/stdafx.h) + target_compile_options(libi2pdlang PRIVATE -include libi2pd/stdafx.h) target_link_libraries(libi2pd stdafx) endif() -target_link_libraries(libi2pdclient libi2pd) +target_link_libraries(libi2pdclient libi2pd libi2pdlang) find_package(Boost COMPONENTS system filesystem program_options date_time REQUIRED) if(NOT DEFINED Boost_INCLUDE_DIRS) @@ -265,7 +276,7 @@ message(STATUS "---------------------------------------") include(GNUInstallDirs) if(WITH_BINARY) - add_executable("${PROJECT_NAME}" ${LANG_SRC} ${DAEMON_SRC}) + add_executable("${PROJECT_NAME}" ${DAEMON_SRC}) if(WITH_STATIC) set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static") @@ -295,7 +306,7 @@ if(WITH_BINARY) endif() target_link_libraries(libi2pd ${Boost_LIBRARIES} ${ZLIB_LIBRARY}) - target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${UPNP_LIB} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) + target_link_libraries("${PROJECT_NAME}" libi2pd libi2pdclient libi2pdlang ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${UPNP_LIB} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB} ${CMAKE_REQUIRED_LIBRARIES}) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") From b9476791f4b6b2a46f5f2fc59d491c5d34b85db1 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 26 Jun 2021 17:40:25 -0400 Subject: [PATCH 4303/6300] eliminated extra I2NP messages for fragments --- libi2pd/TunnelEndpoint.cpp | 115 +++++++++++++++++++------------------ libi2pd/TunnelEndpoint.h | 10 ++-- 2 files changed, 65 insertions(+), 60 deletions(-) diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp index a07230cb..70004ede 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -52,26 +52,25 @@ namespace tunnel bool isFollowOnFragment = flag & 0x80, isLastFragment = true; uint32_t msgID = 0; int fragmentNum = 0; - TunnelMessageBlockEx& m = m_CurrentMessage; if (!isFollowOnFragment) { // first fragment if (m_CurrentMsgID) AddIncompleteCurrentMessage (); // we have got a new message while previous is not complete - m.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); - switch (m.deliveryType) + m_CurrentMessage.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); + switch (m_CurrentMessage.deliveryType) { case eDeliveryTypeLocal: // 0 break; case eDeliveryTypeTunnel: // 1 - m.tunnelID = bufbe32toh (fragment); + m_CurrentMessage.tunnelID = bufbe32toh (fragment); fragment += 4; // tunnelID - m.hash = i2p::data::IdentHash (fragment); + m_CurrentMessage.hash = i2p::data::IdentHash (fragment); fragment += 32; // hash break; case eDeliveryTypeRouter: // 2 - m.hash = i2p::data::IdentHash (fragment); + m_CurrentMessage.hash = i2p::data::IdentHash (fragment); fragment += 32; // to hash break; default: ; @@ -99,42 +98,51 @@ namespace tunnel uint16_t size = bufbe16toh (fragment); fragment += 2; - if (isFollowOnFragment && m_CurrentMsgID && m_CurrentMsgID == msgID && - m_CurrentMessage.nextFragmentNum == fragmentNum) - { - HandleCurrenMessageFollowOnFragment (fragment, size, isLastFragment); - fragment += size; - continue; + // handle fragment + if (isFollowOnFragment) + { + // existing message + if (m_CurrentMsgID && m_CurrentMsgID == msgID && m_CurrentMessage.nextFragmentNum == fragmentNum) + HandleCurrenMessageFollowOnFragment (fragment, size, isLastFragment); // previous + else + { + HandleFollowOnFragment (msgID, isLastFragment, fragmentNum, fragment, size); // another + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + } } - - msg->offset = fragment - msg->buf; - msg->len = msg->offset + size; - if (msg->len > msg->maxLen) - { - LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size); - m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; - return; - } - if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) - { - // this is not last message. we have to copy it - m.data = NewI2NPTunnelMessage (); - m.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header - m.data->len += TUNNEL_GATEWAY_HEADER_SIZE; - *(m.data) = *msg; - } else - m.data = msg; - - if (!isFollowOnFragment) - { + { + // new message + msg->offset = fragment - msg->buf; + msg->len = msg->offset + size; + // check message size + if (msg->len > msg->maxLen) + { + LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size); + m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; + return; + } + // create new or assign I2NP message + if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) + { + // this is not last message. we have to copy it + m_CurrentMessage.data = NewI2NPTunnelMessage (); + m_CurrentMessage.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header + m_CurrentMessage.data->len += TUNNEL_GATEWAY_HEADER_SIZE; + *(m_CurrentMessage.data) = *msg; + } + else + m_CurrentMessage.data = msg; + if (isLastFragment) { - HandleNextMessage (m); + // single message + HandleNextMessage (m_CurrentMessage); m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; } else if (msgID) { + // first fragment of a new message m_CurrentMessage.nextFragmentNum = 1; m_CurrentMessage.receiveTime = i2p::util::GetMillisecondsSinceEpoch (); HandleOutOfSequenceFragments (msgID, m_CurrentMessage); @@ -145,13 +153,7 @@ namespace tunnel m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; } } - else - { - m.nextFragmentNum = fragmentNum; - HandleFollowOnFragment (msgID, isLastFragment, m); - m_CurrentMsgID = 0; m_CurrentMessage.data = nullptr; - } - + fragment += size; } } @@ -159,15 +161,14 @@ namespace tunnel LogPrint (eLogError, "TunnelMessage: zero not found"); } - void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m) + void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, + uint8_t fragmentNum, const uint8_t * fragment, size_t size) { - auto fragment = m.data->GetBuffer (); - auto size = m.data->GetLength (); auto it = m_IncompleteMessages.find (msgID); if (it != m_IncompleteMessages.end()) { auto& msg = it->second; - if (m.nextFragmentNum == msg.nextFragmentNum) + if (fragmentNum == msg.nextFragmentNum) { if (ConcatFollowOnFragment (msg, fragment, size)) { @@ -185,20 +186,20 @@ namespace tunnel } else { - LogPrint (eLogError, "TunnelMessage: Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped"); + LogPrint (eLogError, "TunnelMessage: Fragment ", fragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped"); m_IncompleteMessages.erase (it); } } else { - LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); - AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); + LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)fragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); + AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); } } else { LogPrint (eLogWarning, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); - AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); + AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); } } @@ -259,10 +260,12 @@ namespace tunnel } } - void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data) + void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, + bool isLastFragment, const uint8_t * fragment, size_t size) { - if (!m_OutOfSequenceFragments.insert ({(uint64_t)msgID << 32 | fragmentNum, - {isLastFragment, data, i2p::util::GetMillisecondsSinceEpoch () }}).second) + std::unique_ptr f(new Fragment (isLastFragment, i2p::util::GetMillisecondsSinceEpoch (), size)); + memcpy (f->data.data (), fragment, size); + if (!m_OutOfSequenceFragments.emplace ((uint64_t)msgID << 32 | fragmentNum, std::move (f)).second) LogPrint (eLogInfo, "TunnelMessage: duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); } @@ -291,7 +294,7 @@ namespace tunnel if (it != m_OutOfSequenceFragments.end ()) { LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found"); - size_t size = it->second.data->GetLength (); + size_t size = it->second->data.size (); if (msg.data->len + size > msg.data->maxLen) { LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); @@ -299,9 +302,9 @@ namespace tunnel *newMsg = *(msg.data); msg.data = newMsg; } - if (msg.data->Concat (it->second.data->GetBuffer (), size) < size) // concatenate out-of-sync fragment + if (msg.data->Concat (it->second->data.data (), size) < size) // concatenate out-of-sync fragment LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); - if (it->second.isLastFragment) + if (it->second->isLastFragment) // message complete msg.nextFragmentNum = 0; else @@ -354,7 +357,7 @@ namespace tunnel // out-of-sequence fragments for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();) { - if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) + if (ts > it->second->receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) it = m_OutOfSequenceFragments.erase (it); else ++it; diff --git a/libi2pd/TunnelEndpoint.h b/libi2pd/TunnelEndpoint.h index 70514e35..f9878165 100644 --- a/libi2pd/TunnelEndpoint.h +++ b/libi2pd/TunnelEndpoint.h @@ -11,6 +11,7 @@ #include #include +#include #include #include "I2NPProtocol.h" #include "TunnelBase.h" @@ -29,9 +30,10 @@ namespace tunnel struct Fragment { + Fragment (bool last, uint64_t t, size_t size): isLastFragment (last), receiveTime (t), data (size) {}; bool isLastFragment; - std::shared_ptr data; uint64_t receiveTime; // milliseconds since epoch + std::vector data; }; public: @@ -45,12 +47,12 @@ namespace tunnel private: - void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m); + void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, uint8_t fragmentNum, const uint8_t * fragment, size_t size); bool ConcatFollowOnFragment (TunnelMessageBlockEx& msg, const uint8_t * fragment, size_t size) const; // true if success void HandleCurrenMessageFollowOnFragment (const uint8_t * frgament, size_t size, bool isLastFragment); void HandleNextMessage (const TunnelMessageBlock& msg); - void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data); + void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, const uint8_t * fragment, size_t size); bool ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); // true if something added void HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg); void AddIncompleteCurrentMessage (); @@ -58,7 +60,7 @@ namespace tunnel private: std::unordered_map m_IncompleteMessages; - std::unordered_map m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment + std::unordered_map > m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment bool m_IsInbound; size_t m_NumReceivedBytes; TunnelMessageBlockEx m_CurrentMessage; From 66422d6d837ce25f4cf7eb2b90fa6f9e5f9a9d22 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 26 Jun 2021 21:44:51 -0400 Subject: [PATCH 4304/6300] double size tunnel message --- libi2pd/I2NPProtocol.cpp | 3 ++- libi2pd/TunnelEndpoint.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 7c4d84c3..f570a2a1 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -38,7 +38,8 @@ namespace i2p std::shared_ptr NewI2NPTunnelMessage () { - auto msg = new I2NPMessageBuffer(); // reserved for alignment and NTCP 16 + 6 + 12 + // should fit two tunnel message, enough for one garlic encrypted streaming packet + auto msg = new I2NPMessageBuffer<2*i2p::tunnel::TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + 34>(); // reserved for alignment and NTCP 16 + 6 + 12 msg->Align (12); return std::shared_ptr(msg); } diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp index 70004ede..109f3120 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -198,7 +198,7 @@ namespace tunnel } else { - LogPrint (eLogWarning, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); + LogPrint (eLogDebug, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); AddOutOfSequenceFragment (msgID, fragmentNum, isLastFragment, fragment, size); } } @@ -283,6 +283,7 @@ namespace tunnel } else m_IncompleteMessages.erase (msgID); + LogPrint (eLogDebug, "TunnelMessage: All fragments of message ", msgID, " found"); break; } } From 6d2c9e367bb35e0d3b4d19db734a243744a92fc9 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 27 Jun 2021 12:24:41 +0300 Subject: [PATCH 4305/6300] remove unused CI and docker files Signed-off-by: R4SAS --- .travis.yml | 54 ---------------------- appveyor.yml | 57 ------------------------ build/docker/README.md | 34 -------------- build/docker/old-ubuntu-based/Dockerfile | 11 ----- build/fig.yml | 2 - contrib/docker/Dockerfile | 2 +- 6 files changed, 1 insertion(+), 159 deletions(-) delete mode 100644 .travis.yml delete mode 100644 appveyor.yml delete mode 100644 build/docker/README.md delete mode 100644 build/docker/old-ubuntu-based/Dockerfile delete mode 100644 build/fig.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index af47f458..00000000 --- a/.travis.yml +++ /dev/null @@ -1,54 +0,0 @@ -language: cpp -cache: - apt: true -os: -- linux -#- osx -dist: xenial -sudo: required -compiler: -- g++ -- clang++ -env: - global: - - MAKEFLAGS="-j 2" - matrix: - - BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes - - BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no - - BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes - - BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no -matrix: - exclude: - - os: osx - env: BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes - - os: osx - env: BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no - - os: linux - compiler: clang++ - env: BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes - - os: linux - compiler: clang++ - env: BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no -addons: - apt: - packages: - - build-essential - - cmake - - g++ - - clang - - libboost-chrono-dev - - libboost-date-time-dev - - libboost-filesystem-dev - - libboost-program-options-dev - - libboost-system-dev - - libboost-thread-dev - - libminiupnpc-dev - - libssl-dev -before_install: -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install libressl miniupnpc ; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated boost || brew upgrade boost ; fi -script: -- if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "cmake" ]]; then cd build && cmake -DCMAKE_BUILD_TYPE=Release -DWITH_UPNP=${UPNP} && make ; fi -- if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "make" ]]; then make USE_UPNP=${MAKE_UPNP} ; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then make HOMEBREW=1 USE_UPNP=${MAKE_UPNP} ; fi diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 89d85494..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,57 +0,0 @@ -version: 2.38.0.{build} -pull_requests: - do_not_increment_build_number: true -branches: - only: - - openssl -skip_tags: true -os: Visual Studio 2015 -shallow_clone: true -clone_depth: 1 - -# avoid building 32-bit if 64-bit failed already -matrix: - fast_finish: true - -environment: - APPVEYOR_SAVE_CACHE_ON_ERROR: true - MSYS2_PATH_TYPE: inherit - CHERE_INVOKING: enabled_from_arguments - matrix: - - MSYSTEM: MINGW64 - - MSYSTEM: MINGW32 - -cache: - - c:\msys64\var\cache\pacman\pkg\ - -install: -# install new signing keyring -- c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" -- c:\msys64\usr\bin\bash -lc "curl -O https://mirror.selfnet.de/msys2/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" -- c:\msys64\usr\bin\bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig" -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -U msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz" -# remove packages which can break build -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc mingw-w64-{i686,x86_64}-gcc-ada mingw-w64-{i686,x86_64}-gcc-objc" -# update runtime -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" -# Kill bash before next try -- taskkill /T /F /IM bash.exe /IM gpg.exe /IM gpg-agent.exe | exit /B 0 -# update packages and install required -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu $MINGW_PACKAGE_PREFIX-boost $MINGW_PACKAGE_PREFIX-miniupnpc" - -build_script: -- c:\msys64\usr\bin\bash -lc "make USE_UPNP=yes DEBUG=no -j3" -# prepare archive for uploading -- set "FILELIST=i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates contrib/tunnels.d" -- echo This is development build, use it carefully! For running in portable mode, move all files from contrib directory here. > README.txt -- 7z a -tzip -mx9 -mmt i2pd-%APPVEYOR_BUILD_VERSION%-%APPVEYOR_REPO_COMMIT:~0,7%-mingw-win%MSYSTEM:~-2%.zip %FILELIST% - -after_build: -- c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Sc" - -test: off - -deploy: off - -artifacts: -- path: i2pd-*.zip diff --git a/build/docker/README.md b/build/docker/README.md deleted file mode 100644 index 877a9ce5..00000000 --- a/build/docker/README.md +++ /dev/null @@ -1,34 +0,0 @@ -Howto build & run -================== - -**Build** - -Assuming you're in the root directory of the anoncoin source code. - -$ `cd build/docker` -$ `docker -t meeh/i2pd:latest .` - -**Run** - -To run either the local build, or if not found - fetched prebuild from hub.docker.io, run the following command. - -$ `docker run --name anonnode -v /path/to/i2pd/datadir/on/host:/var/lib/i2pd -p 7070:7070 -p 4444:4444 -p 4447:4447 -p 7656:7656 -p 2827:2827 -p 7654:7654 -p 7650:7650 -d meeh/i2pd` - -All the ports ( -p HOSTPORT:DOCKERPORT ) is optional. However the command above enable all features (Webconsole, HTTP Proxy, BOB, SAM, i2cp, etc) - -The volume ( -v HOSTDIR:DOCKERDIR ) is also optional, but if you don't use it, your config, routerid and private keys will die along with the container. - -**Options** - -Options are set via docker environment variables. This can be set at run with -e parameters. - -* **ENABLE_IPV6** - Enable IPv6 support. Any value can be used - it triggers as long as it's not empty. -* **LOGLEVEL** - Set the loglevel. -* **ENABLE_AUTH** - Enable auth for the webconsole. Username and password needs to be set manually in i2pd.conf cause security reasons. - -**Logging** - -Logging happens to STDOUT as the best practise with docker containers, since infrastructure systems like kubernetes with ELK integration can automatically forward the log to say, kibana or greylog without manual setup. :) - - - diff --git a/build/docker/old-ubuntu-based/Dockerfile b/build/docker/old-ubuntu-based/Dockerfile deleted file mode 100644 index 3faddf2e..00000000 --- a/build/docker/old-ubuntu-based/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM ubuntu - -RUN apt-get update && apt-get install -y libboost-dev libboost-filesystem-dev \ - libboost-program-options-dev libboost-date-time-dev \ - libssl-dev git build-essential - -RUN git clone https://github.com/PurpleI2P/i2pd.git -WORKDIR /i2pd -RUN make - -CMD ./i2pd diff --git a/build/fig.yml b/build/fig.yml deleted file mode 100644 index 372ef350..00000000 --- a/build/fig.yml +++ /dev/null @@ -1,2 +0,0 @@ -i2pd: - build: . diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index 6470e148..71af141e 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.13 +FROM alpine:latest LABEL authors "Mikal Villa , Darknet Villain " # Expose git branch, tag and URL variables as arguments From 12d6f03dc9d1adef53abeba13b0499d52242220d Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 27 Jun 2021 17:14:45 +0300 Subject: [PATCH 4306/6300] [i18n] add language changing at runtime in webconsole Signed-off-by: R4SAS --- contrib/i18n/English.po | 296 +++++++++++++++++++------------------- daemon/HTTPServer.cpp | 75 ++++++---- i18n/Afrikaans.cpp | 7 +- i18n/English.cpp | 7 +- i18n/I18N.h | 13 +- i18n/I18N_langs.h | 28 +++- i18n/Russian.cpp | 310 ++++++++++++++++++---------------------- i18n/Turkmen.cpp | 7 +- i18n/Ukrainian.cpp | 7 +- 9 files changed, 396 insertions(+), 354 deletions(-) diff --git a/contrib/i18n/English.po b/contrib/i18n/English.po index 1de2ddaa..011d0fee 100644 --- a/contrib/i18n/English.po +++ b/contrib/i18n/English.po @@ -26,548 +26,554 @@ msgstr "" msgid "Enabled" msgstr "" -#: daemon/HTTPServer.cpp:141 +#: daemon/HTTPServer.cpp:147 msgid "day" msgid_plural "days" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:145 + +#: daemon/HTTPServer.cpp:151 msgid "hour" msgid_plural "hours" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:149 + +#: daemon/HTTPServer.cpp:155 msgid "minute" msgid_plural "minutes" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:152 +#: daemon/HTTPServer.cpp:158 msgid "second" msgid_plural "seconds" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:160 daemon/HTTPServer.cpp:188 +#: daemon/HTTPServer.cpp:166 daemon/HTTPServer.cpp:194 msgid "KiB" msgstr "" -#: daemon/HTTPServer.cpp:162 +#: daemon/HTTPServer.cpp:168 msgid "MiB" msgstr "" -#: daemon/HTTPServer.cpp:164 +#: daemon/HTTPServer.cpp:170 msgid "GiB" msgstr "" -#: daemon/HTTPServer.cpp:181 +#: daemon/HTTPServer.cpp:187 msgid "building" msgstr "" -#: daemon/HTTPServer.cpp:182 +#: daemon/HTTPServer.cpp:188 msgid "failed" msgstr "" -#: daemon/HTTPServer.cpp:183 +#: daemon/HTTPServer.cpp:189 msgid "expiring" msgstr "" -#: daemon/HTTPServer.cpp:184 +#: daemon/HTTPServer.cpp:190 msgid "established" msgstr "" -#: daemon/HTTPServer.cpp:185 +#: daemon/HTTPServer.cpp:191 msgid "unknown" msgstr "" -#: daemon/HTTPServer.cpp:187 +#: daemon/HTTPServer.cpp:193 msgid "exploratory" msgstr "" -#: daemon/HTTPServer.cpp:223 +#: daemon/HTTPServer.cpp:229 msgid "i2pd webconsole" msgstr "" -#: daemon/HTTPServer.cpp:226 +#: daemon/HTTPServer.cpp:232 msgid "Main page" msgstr "" -#: daemon/HTTPServer.cpp:227 daemon/HTTPServer.cpp:683 +#: daemon/HTTPServer.cpp:233 daemon/HTTPServer.cpp:689 msgid "Router commands" msgstr "" -#: daemon/HTTPServer.cpp:228 +#: daemon/HTTPServer.cpp:234 msgid "Local destinations" msgstr "" -#: daemon/HTTPServer.cpp:230 daemon/HTTPServer.cpp:382 -#: daemon/HTTPServer.cpp:463 daemon/HTTPServer.cpp:469 -#: daemon/HTTPServer.cpp:599 daemon/HTTPServer.cpp:642 -#: daemon/HTTPServer.cpp:646 +#: daemon/HTTPServer.cpp:236 daemon/HTTPServer.cpp:388 +#: daemon/HTTPServer.cpp:469 daemon/HTTPServer.cpp:475 +#: daemon/HTTPServer.cpp:605 daemon/HTTPServer.cpp:648 +#: daemon/HTTPServer.cpp:652 msgid "LeaseSets" msgstr "" -#: daemon/HTTPServer.cpp:232 daemon/HTTPServer.cpp:652 +#: daemon/HTTPServer.cpp:238 daemon/HTTPServer.cpp:658 msgid "Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:233 daemon/HTTPServer.cpp:727 -#: daemon/HTTPServer.cpp:743 +#: daemon/HTTPServer.cpp:239 daemon/HTTPServer.cpp:746 +#: daemon/HTTPServer.cpp:762 msgid "Transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:234 daemon/HTTPServer.cpp:792 +#: daemon/HTTPServer.cpp:240 daemon/HTTPServer.cpp:811 msgid "Transports" msgstr "" -#: daemon/HTTPServer.cpp:235 +#: daemon/HTTPServer.cpp:241 msgid "I2P tunnels" msgstr "" -#: daemon/HTTPServer.cpp:237 daemon/HTTPServer.cpp:854 -#: daemon/HTTPServer.cpp:864 +#: daemon/HTTPServer.cpp:243 daemon/HTTPServer.cpp:873 +#: daemon/HTTPServer.cpp:883 msgid "SAM sessions" msgstr "" -#: daemon/HTTPServer.cpp:253 daemon/HTTPServer.cpp:1254 -#: daemon/HTTPServer.cpp:1257 daemon/HTTPServer.cpp:1260 -#: daemon/HTTPServer.cpp:1274 daemon/HTTPServer.cpp:1319 -#: daemon/HTTPServer.cpp:1322 daemon/HTTPServer.cpp:1325 +#: daemon/HTTPServer.cpp:259 daemon/HTTPServer.cpp:1273 +#: daemon/HTTPServer.cpp:1276 daemon/HTTPServer.cpp:1279 +#: daemon/HTTPServer.cpp:1293 daemon/HTTPServer.cpp:1338 +#: daemon/HTTPServer.cpp:1341 daemon/HTTPServer.cpp:1344 msgid "ERROR" msgstr "" -#: daemon/HTTPServer.cpp:260 +#: daemon/HTTPServer.cpp:266 msgid "OK" msgstr "" -#: daemon/HTTPServer.cpp:261 +#: daemon/HTTPServer.cpp:267 msgid "Testing" msgstr "" -#: daemon/HTTPServer.cpp:262 +#: daemon/HTTPServer.cpp:268 msgid "Firewalled" msgstr "" -#: daemon/HTTPServer.cpp:263 daemon/HTTPServer.cpp:284 -#: daemon/HTTPServer.cpp:370 +#: daemon/HTTPServer.cpp:269 daemon/HTTPServer.cpp:290 +#: daemon/HTTPServer.cpp:376 msgid "Unknown" msgstr "" -#: daemon/HTTPServer.cpp:264 daemon/HTTPServer.cpp:394 -#: daemon/HTTPServer.cpp:395 daemon/HTTPServer.cpp:922 -#: daemon/HTTPServer.cpp:931 +#: daemon/HTTPServer.cpp:270 daemon/HTTPServer.cpp:400 +#: daemon/HTTPServer.cpp:401 daemon/HTTPServer.cpp:941 +#: daemon/HTTPServer.cpp:950 msgid "Proxy" msgstr "" -#: daemon/HTTPServer.cpp:265 +#: daemon/HTTPServer.cpp:271 msgid "Mesh" msgstr "" -#: daemon/HTTPServer.cpp:268 +#: daemon/HTTPServer.cpp:274 msgid "Error" msgstr "" -#: daemon/HTTPServer.cpp:272 +#: daemon/HTTPServer.cpp:278 msgid "Clock skew" msgstr "" -#: daemon/HTTPServer.cpp:275 +#: daemon/HTTPServer.cpp:281 msgid "Offline" msgstr "" -#: daemon/HTTPServer.cpp:278 +#: daemon/HTTPServer.cpp:284 msgid "Symmetric NAT" msgstr "" -#: daemon/HTTPServer.cpp:290 +#: daemon/HTTPServer.cpp:296 msgid "Uptime" msgstr "" -#: daemon/HTTPServer.cpp:293 +#: daemon/HTTPServer.cpp:299 msgid "Network status" msgstr "" -#: daemon/HTTPServer.cpp:298 +#: daemon/HTTPServer.cpp:304 msgid "Network status v6" msgstr "" -#: daemon/HTTPServer.cpp:304 daemon/HTTPServer.cpp:311 +#: daemon/HTTPServer.cpp:310 daemon/HTTPServer.cpp:317 msgid "Stopping in" msgstr "" -#: daemon/HTTPServer.cpp:318 +#: daemon/HTTPServer.cpp:324 msgid "Family" msgstr "" -#: daemon/HTTPServer.cpp:319 +#: daemon/HTTPServer.cpp:325 msgid "Tunnel creation success rate" msgstr "" -#: daemon/HTTPServer.cpp:320 +#: daemon/HTTPServer.cpp:326 msgid "Received" msgstr "" -#: daemon/HTTPServer.cpp:322 daemon/HTTPServer.cpp:325 -#: daemon/HTTPServer.cpp:328 +#: daemon/HTTPServer.cpp:328 daemon/HTTPServer.cpp:331 +#: daemon/HTTPServer.cpp:334 msgid "KiB/s" msgstr "" -#: daemon/HTTPServer.cpp:323 +#: daemon/HTTPServer.cpp:329 msgid "Sent" msgstr "" -#: daemon/HTTPServer.cpp:326 +#: daemon/HTTPServer.cpp:332 msgid "Transit" msgstr "" -#: daemon/HTTPServer.cpp:329 +#: daemon/HTTPServer.cpp:335 msgid "Data path" msgstr "" -#: daemon/HTTPServer.cpp:332 +#: daemon/HTTPServer.cpp:338 msgid "Hidden content. Press on text to see." msgstr "" -#: daemon/HTTPServer.cpp:335 +#: daemon/HTTPServer.cpp:341 msgid "Router Ident" msgstr "" -#: daemon/HTTPServer.cpp:337 +#: daemon/HTTPServer.cpp:343 msgid "Router Family" msgstr "" -#: daemon/HTTPServer.cpp:338 +#: daemon/HTTPServer.cpp:344 msgid "Router Caps" msgstr "" -#: daemon/HTTPServer.cpp:339 +#: daemon/HTTPServer.cpp:345 msgid "Version" msgstr "" -#: daemon/HTTPServer.cpp:340 +#: daemon/HTTPServer.cpp:346 msgid "Our external address" msgstr "" -#: daemon/HTTPServer.cpp:348 +#: daemon/HTTPServer.cpp:354 msgid "supported" msgstr "" -#: daemon/HTTPServer.cpp:380 +#: daemon/HTTPServer.cpp:386 msgid "Routers" msgstr "" -#: daemon/HTTPServer.cpp:381 +#: daemon/HTTPServer.cpp:387 msgid "Floodfills" msgstr "" -#: daemon/HTTPServer.cpp:388 daemon/HTTPServer.cpp:908 +#: daemon/HTTPServer.cpp:394 daemon/HTTPServer.cpp:927 msgid "Client Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:389 +#: daemon/HTTPServer.cpp:395 msgid "Transit Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:393 +#: daemon/HTTPServer.cpp:399 msgid "Services" msgstr "" -#: daemon/HTTPServer.cpp:407 daemon/HTTPServer.cpp:419 +#: daemon/HTTPServer.cpp:413 daemon/HTTPServer.cpp:425 msgid "Local Destinations" msgstr "" -#: daemon/HTTPServer.cpp:442 +#: daemon/HTTPServer.cpp:448 msgid "Encrypted B33 address" msgstr "" -#: daemon/HTTPServer.cpp:451 +#: daemon/HTTPServer.cpp:457 msgid "Address registration line" msgstr "" -#: daemon/HTTPServer.cpp:456 +#: daemon/HTTPServer.cpp:462 msgid "Domain" msgstr "" -#: daemon/HTTPServer.cpp:457 +#: daemon/HTTPServer.cpp:463 msgid "Generate" msgstr "" -#: daemon/HTTPServer.cpp:458 +#: daemon/HTTPServer.cpp:464 msgid "" "Note: result string can be used only for registering 2LD domains " "(example.i2p). For registering subdomains please use i2pd-tools." msgstr "" -#: daemon/HTTPServer.cpp:464 +#: daemon/HTTPServer.cpp:470 msgid "Address" msgstr "" -#: daemon/HTTPServer.cpp:464 +#: daemon/HTTPServer.cpp:470 msgid "Type" msgstr "" -#: daemon/HTTPServer.cpp:464 +#: daemon/HTTPServer.cpp:470 msgid "EncType" msgstr "" -#: daemon/HTTPServer.cpp:474 daemon/HTTPServer.cpp:657 +#: daemon/HTTPServer.cpp:480 daemon/HTTPServer.cpp:663 msgid "Inbound tunnels" msgstr "" -#: daemon/HTTPServer.cpp:479 daemon/HTTPServer.cpp:489 -#: daemon/HTTPServer.cpp:662 daemon/HTTPServer.cpp:672 -#: Means milliseconds +#. Milliseconds +#: daemon/HTTPServer.cpp:485 daemon/HTTPServer.cpp:495 +#: daemon/HTTPServer.cpp:668 daemon/HTTPServer.cpp:678 msgid "ms" msgstr "" -#: daemon/HTTPServer.cpp:484 daemon/HTTPServer.cpp:667 +#: daemon/HTTPServer.cpp:490 daemon/HTTPServer.cpp:673 msgid "Outbound tunnels" msgstr "" -#: daemon/HTTPServer.cpp:496 +#: daemon/HTTPServer.cpp:502 msgid "Tags" msgstr "" -#: daemon/HTTPServer.cpp:496 +#: daemon/HTTPServer.cpp:502 msgid "Incoming" msgstr "" -#: daemon/HTTPServer.cpp:503 daemon/HTTPServer.cpp:506 +#: daemon/HTTPServer.cpp:509 daemon/HTTPServer.cpp:512 msgid "Outgoing" msgstr "" -#: daemon/HTTPServer.cpp:504 daemon/HTTPServer.cpp:520 +#: daemon/HTTPServer.cpp:510 daemon/HTTPServer.cpp:526 msgid "Destination" msgstr "" -#: daemon/HTTPServer.cpp:504 +#: daemon/HTTPServer.cpp:510 msgid "Amount" msgstr "" -#: daemon/HTTPServer.cpp:511 +#: daemon/HTTPServer.cpp:517 msgid "Incoming Tags" msgstr "" -#: daemon/HTTPServer.cpp:519 daemon/HTTPServer.cpp:522 +#: daemon/HTTPServer.cpp:525 daemon/HTTPServer.cpp:528 msgid "Tags sessions" msgstr "" -#: daemon/HTTPServer.cpp:520 +#: daemon/HTTPServer.cpp:526 msgid "Status" msgstr "" -#: daemon/HTTPServer.cpp:529 daemon/HTTPServer.cpp:584 +#: daemon/HTTPServer.cpp:535 daemon/HTTPServer.cpp:590 msgid "Local Destination" msgstr "" -#: daemon/HTTPServer.cpp:538 daemon/HTTPServer.cpp:887 +#: daemon/HTTPServer.cpp:544 daemon/HTTPServer.cpp:906 msgid "Streams" msgstr "" -#: daemon/HTTPServer.cpp:560 +#: daemon/HTTPServer.cpp:566 msgid "Close stream" msgstr "" -#: daemon/HTTPServer.cpp:589 +#: daemon/HTTPServer.cpp:595 msgid "I2CP session not found" msgstr "" -#: daemon/HTTPServer.cpp:592 +#: daemon/HTTPServer.cpp:598 msgid "I2CP is not enabled" msgstr "" -#: daemon/HTTPServer.cpp:618 +#: daemon/HTTPServer.cpp:624 msgid "Invalid" msgstr "" -#: daemon/HTTPServer.cpp:621 +#: daemon/HTTPServer.cpp:627 msgid "Store type" msgstr "" -#: daemon/HTTPServer.cpp:622 +#: daemon/HTTPServer.cpp:628 msgid "Expires" msgstr "" -#: daemon/HTTPServer.cpp:627 +#: daemon/HTTPServer.cpp:633 msgid "Non Expired Leases" msgstr "" -#: daemon/HTTPServer.cpp:630 +#: daemon/HTTPServer.cpp:636 msgid "Gateway" msgstr "" -#: daemon/HTTPServer.cpp:631 +#: daemon/HTTPServer.cpp:637 msgid "TunnelID" msgstr "" -#: daemon/HTTPServer.cpp:632 +#: daemon/HTTPServer.cpp:638 msgid "EndDate" msgstr "" -#: daemon/HTTPServer.cpp:642 +#: daemon/HTTPServer.cpp:648 msgid "not floodfill" msgstr "" -#: daemon/HTTPServer.cpp:653 +#: daemon/HTTPServer.cpp:659 msgid "Queue size" msgstr "" -#: daemon/HTTPServer.cpp:684 +#: daemon/HTTPServer.cpp:690 msgid "Run peer test" msgstr "" -#: daemon/HTTPServer.cpp:687 +#: daemon/HTTPServer.cpp:693 msgid "Decline transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:689 +#: daemon/HTTPServer.cpp:695 msgid "Accept transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:692 daemon/HTTPServer.cpp:697 +#: daemon/HTTPServer.cpp:698 daemon/HTTPServer.cpp:703 msgid "Cancel graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:694 daemon/HTTPServer.cpp:699 +#: daemon/HTTPServer.cpp:700 daemon/HTTPServer.cpp:705 msgid "Start graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:701 +#: daemon/HTTPServer.cpp:707 msgid "Force shutdown" msgstr "" -#: daemon/HTTPServer.cpp:704 +#: daemon/HTTPServer.cpp:710 msgid "" "Note: any action done here are not persistent and not changes your " "config files." msgstr "" -#: daemon/HTTPServer.cpp:706 +#: daemon/HTTPServer.cpp:712 msgid "Logging level" msgstr "" -#: daemon/HTTPServer.cpp:714 +#: daemon/HTTPServer.cpp:720 msgid "Transit tunnels limit" msgstr "" -#: daemon/HTTPServer.cpp:719 +#: daemon/HTTPServer.cpp:725 daemon/HTTPServer.cpp:737 msgid "Change" msgstr "" -#: daemon/HTTPServer.cpp:743 +#: daemon/HTTPServer.cpp:729 +msgid "Change language" +msgstr "" + +#: daemon/HTTPServer.cpp:762 msgid "no transit tunnels currently built" msgstr "" -#: daemon/HTTPServer.cpp:848 daemon/HTTPServer.cpp:871 +#: daemon/HTTPServer.cpp:867 daemon/HTTPServer.cpp:890 msgid "SAM disabled" msgstr "" -#: daemon/HTTPServer.cpp:864 +#: daemon/HTTPServer.cpp:883 msgid "no sessions currently running" msgstr "" -#: daemon/HTTPServer.cpp:877 +#: daemon/HTTPServer.cpp:896 msgid "SAM session not found" msgstr "" -#: daemon/HTTPServer.cpp:882 +#: daemon/HTTPServer.cpp:901 msgid "SAM Session" msgstr "" -#: daemon/HTTPServer.cpp:939 +#: daemon/HTTPServer.cpp:958 msgid "Server Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:955 +#: daemon/HTTPServer.cpp:974 msgid "Client Forwards" msgstr "" -#: daemon/HTTPServer.cpp:969 +#: daemon/HTTPServer.cpp:988 msgid "Server Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1175 +#: daemon/HTTPServer.cpp:1194 msgid "Unknown page" msgstr "" -#: daemon/HTTPServer.cpp:1194 +#: daemon/HTTPServer.cpp:1213 msgid "Invalid token" msgstr "" -#: daemon/HTTPServer.cpp:1252 daemon/HTTPServer.cpp:1309 -#: daemon/HTTPServer.cpp:1337 +#: daemon/HTTPServer.cpp:1271 daemon/HTTPServer.cpp:1328 +#: daemon/HTTPServer.cpp:1364 msgid "SUCCESS" msgstr "" -#: daemon/HTTPServer.cpp:1252 +#: daemon/HTTPServer.cpp:1271 msgid "Stream closed" msgstr "" -#: daemon/HTTPServer.cpp:1254 +#: daemon/HTTPServer.cpp:1273 msgid "Stream not found or already was closed" msgstr "" -#: daemon/HTTPServer.cpp:1257 +#: daemon/HTTPServer.cpp:1276 msgid "Destination not found" msgstr "" -#: daemon/HTTPServer.cpp:1260 +#: daemon/HTTPServer.cpp:1279 msgid "StreamID can't be null" msgstr "" -#: daemon/HTTPServer.cpp:1262 daemon/HTTPServer.cpp:1327 +#: daemon/HTTPServer.cpp:1281 daemon/HTTPServer.cpp:1346 msgid "Return to destination page" msgstr "" -#: daemon/HTTPServer.cpp:1263 daemon/HTTPServer.cpp:1276 +#: daemon/HTTPServer.cpp:1282 daemon/HTTPServer.cpp:1295 msgid "You will be redirected back in 5 seconds" msgstr "" -#: daemon/HTTPServer.cpp:1274 +#: daemon/HTTPServer.cpp:1293 msgid "Transit tunnels count must not exceed 65535" msgstr "" -#: daemon/HTTPServer.cpp:1275 daemon/HTTPServer.cpp:1338 +#: daemon/HTTPServer.cpp:1294 daemon/HTTPServer.cpp:1365 msgid "Back to commands list" msgstr "" -#: daemon/HTTPServer.cpp:1311 +#: daemon/HTTPServer.cpp:1330 msgid "Register at reg.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1312 +#: daemon/HTTPServer.cpp:1331 msgid "Description" msgstr "" -#: daemon/HTTPServer.cpp:1312 +#: daemon/HTTPServer.cpp:1331 msgid "A bit information about service on domain" msgstr "" -#: daemon/HTTPServer.cpp:1313 +#: daemon/HTTPServer.cpp:1332 msgid "Submit" msgstr "" -#: daemon/HTTPServer.cpp:1319 +#: daemon/HTTPServer.cpp:1338 msgid "Domain can't end with .b32.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1322 +#: daemon/HTTPServer.cpp:1341 msgid "Domain must end with .i2p" msgstr "" -#: daemon/HTTPServer.cpp:1325 +#: daemon/HTTPServer.cpp:1344 msgid "Such destination is not found" msgstr "" -#: daemon/HTTPServer.cpp:1333 +#: daemon/HTTPServer.cpp:1360 msgid "Unknown command" msgstr "" -#: daemon/HTTPServer.cpp:1337 +#: daemon/HTTPServer.cpp:1364 msgid "Command accepted" msgstr "" -#: daemon/HTTPServer.cpp:1339 +#: daemon/HTTPServer.cpp:1366 msgid "You will be redirected in 5 seconds" msgstr "" diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 0b9e8f0f..8ed4511f 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -68,11 +68,11 @@ namespace http { << " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n" << " color: initial; padding: 0 5px; border: 1px solid #894C84; }\r\n" << " .header { font-size: 2.5em; text-align: center; margin: 1em 0; color: #894C84; }\r\n" - << " .wrapper { margin: 0 auto; padding: 1em; max-width: 58em; }\r\n" - << " .menu { float: left; } .menu a, .commands a { display: block; }\r\n" + << " .wrapper { margin: 0 auto; padding: 1em; max-width: 64em; }\r\n" + << " .menu { display: block; float: left; overflow: hidden; max-width: 12em; white-space: nowrap; text-overflow: ellipsis; }\r\n" << " .listitem { display: block; font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n" << " .tableitem { font-family: monospace; font-size: 1.2em; white-space: nowrap; }\r\n" - << " .content { float: left; font-size: 1em; margin-left: 4em; max-width: 45em; overflow: auto; }\r\n" + << " .content { float: left; font-size: 1em; margin-left: 4em; max-width: 48em; overflow: auto; }\r\n" << " .tunnel.established { color: #56B734; } .tunnel.expiring { color: #D3AE3F; }\r\n" << " .tunnel.failed { color: #D33F3F; } .tunnel.building { color: #434343; }\r\n" << " caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n" @@ -84,19 +84,24 @@ namespace http { << " .slide [type=\"checkbox\"]:checked ~ div.slidecontent { display: block; margin-top: 0; padding: 0; }\r\n" << " .disabled:after { color: #D33F3F; content: \"" << tr("Disabled") << "\" }\r\n" << " .enabled:after { color: #56B734; content: \"" << tr("Enabled") << "\" }\r\n" - << " @media screen and (max-width: 980px) {\r\n" /* adaptive style */ + << " @media screen and (max-width: 1150px) {\r\n" /* adaptive style */ + << " .wrapper { max-width: 58em; } .menu { max-width: 10em; }\r\n" + << " .content { margin-left: 2em; max-width: 42em; }\r\n" + << " }\r\n" + << " @media screen and (max-width: 980px) {\r\n" << " body { padding: 1.5em 0 0 0; }\r\n" - << " .menu { width: 100%; display: block; float: none; position: unset; font-size: 16px;\r\n" + << " .menu { width: 100%; max-width: unset; display: block; float: none; position: unset; font-size: 16px;\r\n" << " text-align: center; }\r\n" - << " .menu a, .commands a { padding: 2px; }\r\n" + << " .menu a, .commands a { display: inline-block; padding: 4px; }\r\n" << " .content { float: none; margin-left: unset; margin-top: 16px; max-width: 100%; width: 100%;\r\n" << " text-align: center; }\r\n" << " a, .slide label { /* margin-right: 10px; */ display: block; /* font-size: 18px; */ }\r\n" << " .header { margin: unset; font-size: 1.5em; } small {display: block}\r\n" << " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n" << " color: initial; margin-top: 10px; padding: 6px; border: 1px solid #894c84; width: -webkit-fill-available; }\r\n" - << " input { width: 35%; text-align: center; padding: 5px;\r\n" + << " input, select { width: 35%; text-align: center; padding: 5px;\r\n" << " border: 2px solid #ccc; -webkit-border-radius: 5px; border-radius: 5px; font-size: 18px; }\r\n" + << " table.extaddr { margin: auto; text-align: unset; }\r\n" << " textarea { width: -webkit-fill-available; height: auto; padding:5px; border:2px solid #ccc;\r\n" << " -webkit-border-radius: 5px; border-radius: 5px; font-size: 12px; }\r\n" << " button[type=submit] { padding: 5px 15px; background: #ccc; border: 0 none; cursor: pointer;\r\n" @@ -127,6 +132,7 @@ namespace http { const char HTTP_COMMAND_KILLSTREAM[] = "closestream"; const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit"; const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string"; + const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; @@ -223,18 +229,18 @@ namespace http { "
    " << tr("i2pd webconsole") << "
    \r\n" "
    \r\n" "\r\n" "
    "; @@ -476,7 +482,7 @@ namespace http { s << "
    "; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; + s << " ( " << it->GetMeanLatency() << tr(/* Milliseconds */ "ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); s << "
    \r\n"; } @@ -681,22 +687,22 @@ namespace http { std::string webroot; i2p::config::GetOption("http.webroot", webroot); /* commands */ s << "" << tr("Router commands") << "
    \r\n
    \r\n
    \r\n"; - s << " " << tr("Run peer test") << "\r\n"; + s << " " << tr("Run peer test") << "
    \r\n"; //s << " Reload config
    \r\n"; if (i2p::context.AcceptsTunnels ()) - s << " " << tr("Decline transit tunnels") << "\r\n"; + s << " " << tr("Decline transit tunnels") << "
    \r\n"; else - s << " " << tr("Accept transit tunnels") << "\r\n"; + s << " " << tr("Accept transit tunnels") << "
    \r\n"; #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) if (Daemon.gracefulShutdownInterval) - s << " " << tr("Cancel graceful shutdown") << "\r\n"; + s << " " << tr("Cancel graceful shutdown") << "
    \r\n"; else - s << " " << tr("Start graceful shutdown") << "\r\n"; + s << " " << tr("Start graceful shutdown") << "
    \r\n"; #elif defined(WIN32_APP) if (i2p::util::DaemonWin32::Instance().isGraceful) - s << " " << tr("Cancel graceful shutdown") << "\r\n"; + s << " " << tr("Cancel graceful shutdown") << "
    \r\n"; else - s << " " << tr("Start graceful shutdown") << "\r\n"; + s << " " << tr("Start graceful shutdown") << "
    \r\n"; #endif s << " " << tr("Force shutdown") << "\r\n"; s << "
    "; @@ -718,6 +724,19 @@ namespace http { s << " \r\n"; s << " \r\n"; s << "\r\n
    \r\n"; + + std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); // get current used language + s << "" << tr("Change language") << "
    \r\n"; + s << "
    \r\n"; + s << " \r\n"; + s << " \r\n"; + s << " \r\n"; + s << " \r\n"; + s << "
    \r\n
    \r\n"; + } void ShowTransitTunnels (std::stringstream& s) @@ -1327,6 +1346,14 @@ namespace http { s << "" << tr("Return to destination page") << "\r\n"; return; } + else if (cmd == HTTP_COMMAND_SETLANGUAGE) + { + std::string lang = params["lang"]; + std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); + + if (currLang.compare(lang) != 0) + i2p::i18n::SetLanguage(lang); + } else { res.code = 400; diff --git a/i18n/Afrikaans.cpp b/i18n/Afrikaans.cpp index d2b652b4..96ee52ee 100644 --- a/i18n/Afrikaans.cpp +++ b/i18n/Afrikaans.cpp @@ -18,8 +18,11 @@ namespace i2p { namespace i18n { -namespace afrikaans // language +namespace afrikaans // language namespace { + // language name in lowercase + static std::string language = "afrikaans"; + // See for language plural forms here: // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html static int plural (int n) { @@ -65,7 +68,7 @@ namespace afrikaans // language std::shared_ptr GetLocale() { - return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/English.cpp b/i18n/English.cpp index 6015f8e1..2670e984 100644 --- a/i18n/English.cpp +++ b/i18n/English.cpp @@ -19,8 +19,11 @@ namespace i2p { namespace i18n { -namespace english // language +namespace english // language namespace { + // language name in lowercase + static std::string language = "english"; + // See for language plural forms here: // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html static int plural (int n) { @@ -39,7 +42,7 @@ namespace english // language std::shared_ptr GetLocale() { - return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/I18N.h b/i18n/I18N.h index 272f65e8..03add48d 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -17,16 +17,11 @@ namespace i18n { inline void SetLanguage(const std::string &lang) { - if (!lang.compare("afrikaans")) - i2p::context.SetLanguage (i2p::i18n::afrikaans::GetLocale()); - else if (!lang.compare("russian")) - i2p::context.SetLanguage (i2p::i18n::russian::GetLocale()); - else if (!lang.compare("turkmen")) - i2p::context.SetLanguage (i2p::i18n::turkmen::GetLocale()); - else if (!lang.compare("ukrainian")) - i2p::context.SetLanguage (i2p::i18n::ukrainian::GetLocale()); - else // fallback + const auto it = i2p::i18n::languages.find(lang); + if (it == i2p::i18n::languages.end()) // fallback i2p::context.SetLanguage (i2p::i18n::english::GetLocale()); + else + i2p::context.SetLanguage (it->second.LocaleFunc()); } inline std::string translate (const std::string& arg) diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index 181a4793..949e5844 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -17,10 +17,17 @@ namespace i18n { public: Locale ( + const std::string& language, const std::map& strings, const std::map>& plurals, std::function formula - ): m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; + ): m_Language (language), m_Strings (strings), m_Plurals (plurals), m_Formula (formula) { }; + + // Get activated language name for webconsole + std::string GetLanguage() const + { + return m_Language; + } std::string GetString (const std::string& arg) const { @@ -50,11 +57,18 @@ namespace i18n } private: + const std::string m_Language; const std::map m_Strings; const std::map> m_Plurals; std::function m_Formula; }; + struct langData + { + std::string LocaleName; //localized name + std::function (void)> LocaleFunc; + }; + // Add localization here with language name as namespace namespace afrikaans { std::shared_ptr GetLocale (); } namespace english { std::shared_ptr GetLocale (); } @@ -62,6 +76,18 @@ namespace i18n namespace turkmen { std::shared_ptr GetLocale (); } namespace ukrainian { std::shared_ptr GetLocale (); } + /** + * That map contains international language name lower-case and name in it's language + */ + static std::map languages + { + { "afrikaans", {"Afrikaans", i2p::i18n::afrikaans::GetLocale} }, + { "english", {"English", i2p::i18n::english::GetLocale} }, + { "russian", {"руÑÑкий Ñзык", i2p::i18n::russian::GetLocale} }, + { "turkmen", {"türkmen dili", i2p::i18n::turkmen::GetLocale} }, + { "ukrainian", {"україÌнÑька моÌва", i2p::i18n::ukrainian::GetLocale} }, + }; + } // i18n } // i2p diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index 7df82d54..5e7f9c6b 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -18,8 +18,11 @@ namespace i2p { namespace i18n { -namespace russian // language +namespace russian // language namespace { + // language name in lowercase + static std::string language = "russian"; + // See for language plural forms here: // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html static int plural (int n) { @@ -28,23 +31,152 @@ namespace russian // language static std::map strings { - // HTTP Proxy + {"Disabled", "Выключено"}, + {"Enabled", "Включено"}, + {"KiB", "КиБ"}, + {"MiB", "МиБ"}, + {"GiB", "ГиБ"}, + {"building", "ÑтроитÑÑ"}, + {"failed", "неудачный"}, + {"expiring", "иÑтекает"}, + {"established", "работает"}, + {"unknown", "неизвеÑтно"}, + {"exploratory", "иÑÑледовательÑкий"}, + {"i2pd webconsole", "Веб-конÑоль i2pd"}, + {"Main page", "ГлавнаÑ"}, + {"Router commands", "Команды роутера"}, + {"Local destinations", "Локальные назначениÑ"}, + {"LeaseSets", "ЛизÑеты"}, + {"Tunnels", "Туннели"}, + {"Transit tunnels", "Транзитные туннели"}, + {"Transports", "ТранÑпорты"}, + {"I2P tunnels", "I2P туннели"}, + {"SAM sessions", "SAM ÑеÑÑии"}, + {"ERROR", "ОШИБКÐ"}, + {"OK", "OK"}, + {"Testing", "ТеÑтирование"}, + {"Firewalled", "Заблокировано извне"}, + {"Unknown", "ÐеизвеÑтно"}, + {"Proxy", "ПрокÑи"}, + {"Mesh", "MESH-Ñеть"}, + {"Error", "Ошибка"}, + {"Clock skew", "Ðе точное времÑ"}, + {"Offline", "Оффлайн"}, + {"Symmetric NAT", "Симметричный NAT"}, + {"Uptime", "Ð’ Ñети"}, + {"Network status", "Сетевой ÑтатуÑ"}, + {"Network status v6", "Сетевой ÑÑ‚Ð°Ñ‚ÑƒÑ v6"}, + {"Stopping in", "ОÑтановка через"}, + {"Family", "СемейÑтво"}, + {"Tunnel creation success rate", "УÑпешно поÑтроенных туннелей"}, + {"Received", "Получено"}, + {"KiB/s", "КиБ/Ñ"}, + {"Sent", "Отправлено"}, + {"Transit", "Транзит"}, + {"Data path", "Путь к данным"}, + {"Hidden content. Press on text to see.", "Скрытый контент. Ðажмите на текÑÑ‚ чтобы отобразить."}, + {"Router Ident", "Идентификатор роутера"}, + {"Router Family", "СемейÑтво роутера"}, + {"Router Caps", "Флаги роутера"}, + {"Version", "ВерÑиÑ"}, + {"Our external address", "Ðаш внешний адреÑ"}, + {"supported", "поддерживаетÑÑ"}, + {"Routers", "Роутеры"}, + {"Floodfills", "Флудфилы"}, + {"Client Tunnels", "КлиентÑкие туннели"}, + {"Transit Tunnels", "Транзитные туннели"}, + {"Services", "СервиÑÑ‹"}, + {"Local Destinations", "Локальные назначениÑ"}, + {"Encrypted B33 address", "Шифрованные B33 адреÑа"}, + {"Address registration line", "Строка региÑтрации адреÑа"}, + {"Domain", "Домен"}, + {"Generate", "Сгенерировать"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Примечание: Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð½Ð°Ñ Ñтрока может быть иÑпользована только Ð´Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации доменов второго ÑƒÑ€Ð¾Ð²Ð½Ñ (example.i2p). Ð”Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации поддоменов иÑпользуйте i2pd-tools."}, + {"Address", "ÐдреÑ"}, + {"Type", "Тип"}, + {"EncType", "ТипШифр"}, + {"Inbound tunnels", "ВходÑщие туннели"}, + {"ms", "мÑ"}, + {"Outbound tunnels", "ИÑходÑщие туннели"}, + {"Tags", "Теги"}, + {"Incoming", "ВходÑщие"}, + {"Outgoing", "ИÑходÑщие"}, + {"Destination", "Ðазначение"}, + {"Amount", "КоличеÑтво"}, + {"Incoming Tags", "ВходÑщие Теги"}, + {"Tags sessions", "СеÑÑии Тегов"}, + {"Status", "СтатуÑ"}, + {"Local Destination", "Локальное назначение"}, + {"Streams", "Стримы"}, + {"Close stream", "Закрыть Ñтрим"}, + {"I2CP session not found", "I2CP ÑеÑÑÐ¸Ñ Ð½Ðµ найдена"}, + {"I2CP is not enabled", "I2CP не включен"}, + {"Invalid", "Ðекорректный"}, + {"Store type", "Тип хранилища"}, + {"Expires", "ИÑтекает"}, + {"Non Expired Leases", "Ðе иÑтекшие Lease-Ñ‹"}, + {"Gateway", "Шлюз"}, + {"TunnelID", "ID туннелÑ"}, + {"EndDate", "ЗаканчиваетÑÑ"}, + {"not floodfill", "не флудфил"}, + {"Queue size", "Размер очереди"}, + {"Run peer test", "ЗапуÑтить теÑтирование"}, + {"Decline transit tunnels", "ОтклонÑть транзитные туннели"}, + {"Accept transit tunnels", "Принимать транзитные туннели"}, + {"Cancel graceful shutdown", "Отменить плавную оÑтановку"}, + {"Start graceful shutdown", "ЗапуÑтить плавную оÑтановку"}, + {"Force shutdown", "ÐŸÑ€Ð¸Ð½ÑƒÐ´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¾Ñтановка"}, + {"Note: any action done here are not persistent and not changes your config files.", "Примечание: любое дейÑтвие произведенное здеÑÑŒ не ÑвлÑетÑÑ Ð¿Ð¾ÑтоÑнным и не изменÑет ваши конфигурационные файлы."}, + {"Logging level", "Уровень логированиÑ"}, + {"Transit tunnels limit", "Лимит транзитных туннелей"}, + {"Change", "Изменить"}, + {"Change language", "Изменение Ñзыка"}, + {"no transit tunnels currently built", "нет поÑтроенных транзитных туннелей"}, + {"SAM disabled", "SAM выключен"}, + {"no sessions currently running", "нет запущенных ÑеÑÑий"}, + {"SAM session not found", "SAM ÑеÑÑÐ¸Ñ Ð½Ðµ найдена"}, + {"SAM Session", "SAM ÑеÑÑиÑ"}, + {"Server Tunnels", "Серверные туннели"}, + {"Client Forwards", "КлиентÑкие перенаправлениÑ"}, + {"Server Forwards", "Серверные перенаправлениÑ"}, + {"Unknown page", "ÐеизвеÑÑ‚Ð½Ð°Ñ Ñтраница"}, + {"Invalid token", "Ðеверный токен"}, + {"SUCCESS", "УСПЕШÐО"}, + {"Stream closed", "Стрим закрыт"}, + {"Stream not found or already was closed", "Стрим не найден или уже закрыт"}, + {"Destination not found", "Точка Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð½Ðµ найдена"}, + {"StreamID can't be null", "StreamID не может быть пуÑтым"}, + {"Return to destination page", "ВернутьÑÑ Ð½Ð° Ñтраницу точки назначениÑ"}, + {"You will be redirected back in 5 seconds", "Ð’Ñ‹ будете переадреÑованы назад через 5 Ñекунд"}, + {"Transit tunnels count must not exceed 65535", "ЧиÑло транзитных туннелей не должно превышать 65535"}, + {"Back to commands list", "ВернутьÑÑ Ðº ÑпиÑку команд"}, + {"Register at reg.i2p", "ЗарегиÑтрировать на reg.i2p"}, + {"Description", "ОпиÑание"}, + {"A bit information about service on domain", "Ðемного информации о ÑервиÑе на домене"}, + {"Submit", "Отправить"}, + {"Domain can't end with .b32.i2p", "Домен не может заканчиватьÑÑ Ð½Ð° .b32.i2p"}, + {"Domain must end with .i2p", "Домен должен заканчиватьÑÑ Ð½Ð° .i2p"}, + {"Such destination is not found", "Ð¢Ð°ÐºÐ°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð½Ðµ найдена"}, + {"Unknown command", "ÐеизвеÑÑ‚Ð½Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°"}, + {"Command accepted", "Команда принÑта"}, + {"You will be redirected in 5 seconds", "Ð’Ñ‹ будете переадреÑованы через 5 Ñекунд"}, {"Proxy error", "Ошибка прокÑи"}, {"Proxy info", "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¿Ñ€Ð¾ÐºÑи"}, - {"Proxy error: Host not found", "Ошибка прокÑи: ÐÐ´Ñ€ÐµÑ Ð½Ðµ найден"}, - {"Remote host not found in router's addressbook", "Запрошенный Ð°Ð´Ñ€ÐµÑ Ð½Ðµ найден в адреÑной книге роутера"}, - {"You may try to find this host on jump services below", "Ð’Ñ‹ можете попробовать найти Ð°Ð´Ñ€ÐµÑ Ð½Ð° джамп ÑервиÑах ниже"}, + {"Proxy error: Host not found", "Ошибка прокÑи: Узел не найден"}, + {"Remote host not found in router's addressbook", "Запрошенный узел не найден в адреÑной книге роутера"}, + {"You may try to find this host on jump services below", "Ð’Ñ‹ можете попробовать найти узел через джамп ÑервиÑÑ‹ ниже"}, {"Invalid request", "Ðекорректный запроÑ"}, {"Proxy unable to parse your request", "ПрокÑи не может разобрать ваш запроÑ"}, {"addresshelper is not supported", "addresshelper не поддерживаетÑÑ"}, - {"Host", "ÐдреÑ"}, + {"Host", "Узел"}, {"added to router's addressbook from helper", "добавлен в адреÑную книгу роутера через хелпер"}, - {"already in router's addressbook", "уже в адреÑной книге роутера"}, {"Click", "Ðажмите"}, {"here", "здеÑÑŒ"}, {"to proceed", "чтобы продолжить"}, - {"to update record", "чтобы обновить запиÑÑŒ"}, {"Addresshelper found", "Ðайден addresshelper"}, + {"already in router's addressbook", "уже в адреÑной книге роутера"}, + {"to update record", "чтобы обновить запиÑÑŒ"}, + {"Invalid Request", "неверный запроÑ"}, {"invalid request uri", "некорректный URI запроÑа"}, {"Can't detect destination host from request", "Ðе удалоÑÑŒ определить Ð°Ð´Ñ€ÐµÑ Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¸Ð· запроÑа"}, {"Outproxy failure", "Ошибка внешнего прокÑи"}, @@ -63,169 +195,13 @@ namespace russian // language {"cannot connect", "не удалоÑÑŒ подключитьÑÑ"}, {"http out proxy not implemented", "поддержка внешнего HTTP прокÑи Ñервера не реализована"}, {"cannot connect to upstream http proxy", "не удалоÑÑŒ подключитьÑÑ Ðº вышеÑтоÑщему HTTP прокÑи Ñерверу"}, - {"Host is down", "ÐÐ´Ñ€ÐµÑ Ð½ÐµÐ´Ð¾Ñтупен"}, - {"Can't create connection to requested host, it may be down. Please try again later.", - "Ðе удалоÑÑŒ уÑтановить Ñоединение к запрошенному адреÑу, возможно он не в Ñети. Попробуйте повторить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð¿Ð¾Ð·Ð¶Ðµ."}, - - // Webconsole // - // cssStyles - {"Disabled", "Выключено"}, - {"Enabled", "Включено"}, - // ShowTraffic - {"KiB", "КиБ"}, - {"MiB", "МиБ"}, - {"GiB", "ГиБ"}, - // ShowTunnelDetails - {"building", "ÑтроитÑÑ"}, - {"failed", "неудачный"}, - {"expiring", "иÑтекает"}, - {"established", "работает"}, - {"exploratory", "иÑÑледовательÑкий"}, - {"unknown", "неизвеÑтно"}, - {"i2pd webconsole", "Веб-конÑоль i2pd"}, - // ShowPageHead - {"Main page", "ГлавнаÑ"}, - {"Router commands", "Команды роутера"}, - {"Local destinations", "Локальные назнач."}, - {"LeaseSets", "ЛизÑеты"}, - {"Tunnels", "Туннели"}, - {"Transit tunnels", "Транзит. туннели"}, - {"Transports", "ТранÑпорты"}, - {"I2P tunnels", "I2P туннели"}, - {"SAM sessions", "SAM ÑеÑÑии"}, - // Network Status - {"OK", "OK"}, - {"Testing", "ТеÑтирование"}, - {"Firewalled", "Заблокировано извне"}, - {"Unknown", "ÐеизвеÑтно"}, - {"Proxy", "ПрокÑи"}, - {"Mesh", "MESH-Ñеть"}, - {"Error", "Ошибка"}, - {"Clock skew", "Ðе точное времÑ"}, - {"Offline", "Оффлайн"}, - {"Symmetric NAT", "Симметричный NAT"}, - // Status - {"Uptime", "Ð’ Ñети"}, - {"Network status", "Сетевой ÑтатуÑ"}, - {"Network status v6", "Сетевой ÑÑ‚Ð°Ñ‚ÑƒÑ v6"}, - {"Stopping in", "ОÑтановка через"}, - {"Family", "СемейÑтво"}, - {"Tunnel creation success rate", "УÑпешно поÑтроенных туннелей"}, - {"Received", "Получено"}, - {"Sent", "Отправлено"}, - {"Transit", "Транзит"}, - {"KiB/s", "КиБ/Ñ"}, - {"Data path", "Путь к данным"}, - {"Hidden content. Press on text to see.", "Скрытый контент. Ðажмите на текÑÑ‚ чтобы отобразить."}, - {"Router Ident", "Идентификатор роутера"}, - {"Router Family", "СемейÑтво роутера"}, - {"Router Caps", "Флаги роутера"}, - {"Version", "ВерÑиÑ"}, - {"Our external address", "Ðаш внешний адреÑ"}, - {"supported", "поддерживаетÑÑ"}, - {"Routers", "Роутеры"}, - {"Floodfills", "Флудфилы"}, - {"LeaseSets", "ЛизÑеты"}, - {"Client Tunnels", "КлиентÑкие туннели"}, - {"Transit Tunnels", "Транзитные туннели"}, - {"Services", "СервиÑÑ‹"}, - // ShowLocalDestinations - {"Local Destinations", "Локальные назначениÑ"}, - // ShowLeaseSetDestination - {"Encrypted B33 address", "Шифрованные B33 адреÑа"}, - {"Address registration line", "Строка региÑтрации адреÑа"}, - {"Domain", "Домен"}, - {"Generate", "Сгенерировать"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", - "Примечание: Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð½Ð°Ñ Ñтрока может быть иÑпользована только Ð´Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации доменов второго уровнÑ. Ð”Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации поддоменов иÑпользуйте i2pd-tools."}, - {"Address", "ÐдреÑ"}, - {"Type", "Тип"}, - {"EncType", "ТипШифр"}, - {"Inbound tunnels", "ВходÑщие туннели"}, - {"Outbound tunnels", "ИÑходÑщие туннели"}, - {"ms", "мÑ"}, // milliseconds - {"Tags", "Теги"}, - {"Incoming", "ВходÑщие"}, - {"Outgoing", "ИÑходÑщие"}, - {"Destination", "Ðазначение"}, - {"Amount", "КоличеÑтво"}, - {"Incoming Tags", "ВходÑщие Теги"}, - {"Tags sessions", "СеÑÑии Тегов"}, - {"Status", "СтатуÑ"}, - // ShowLocalDestination - {"Local Destination", "Локальное назначение"}, - {"Streams", "Стримы"}, - {"Close stream", "Закрыть Ñтрим"}, - // ShowI2CPLocalDestination - {"I2CP session not found", "I2CP ÑеÑÑÐ¸Ñ Ð½Ðµ найдена"}, - {"I2CP is not enabled", "I2CP не включен"}, - // ShowLeasesSets - {"Invalid", "Ðекорректный"}, - {"Store type", "Тип хранилища"}, - {"Expires", "ИÑтекает"}, - {"Non Expired Leases", "Ðе иÑтекшие Lease-Ñ‹"}, - {"Gateway", "Шлюз"}, - {"TunnelID", "ID туннелÑ"}, - {"EndDate", "ЗаканчиваетÑÑ"}, - {"not floodfill", "не флудфил"}, - // ShowTunnels - {"Queue size", "Размер очереди"}, - // ShowCommands - {"Run peer test", "ЗапуÑтить теÑтирование"}, - {"Decline transit tunnels", "ОтклонÑть транзитные туннели"}, - {"Accept transit tunnels", "Принимать транзитные туннели"}, - {"Cancel graceful shutdown", "Отменить плавную оÑтановку"}, - {"Start graceful shutdown", "ЗапуÑтить плавную оÑтановку"}, - {"Force shutdown", "ÐŸÑ€Ð¸Ð½ÑƒÐ´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¾Ñтановка"}, - {"Note: any action done here are not persistent and not changes your config files.", - "Примечание: любое дейÑтвие произведенное здеÑÑŒ не ÑвлÑетÑÑ Ð¿Ð¾ÑтоÑнным и не изменÑет ваши конфигурационные файлы."}, - {"Logging level", "Уровень логированиÑ"}, - {"Transit tunnels limit", "Лимит транзитных туннелей"}, - {"Change", "Изменить"}, - // ShowTransitTunnels - {"no transit tunnels currently built", "нет поÑтроенных транзитных туннелей"}, - // ShowSAMSessions/ShowSAMSession - {"SAM disabled", "SAM выключен"}, - {"SAM session not found", "SAM ÑеÑÑÐ¸Ñ Ð½Ðµ найдена"}, - {"no sessions currently running", "нет запущенных ÑеÑÑий"}, - {"SAM Session", "SAM ÑеÑÑиÑ"}, - // ShowI2PTunnels - {"Server Tunnels", "Серверные туннели"}, - {"Client Forwards", "КлиентÑкие перенаправлениÑ"}, - {"Server Forwards", "Серверные перенаправлениÑ"}, - // HandlePage - {"Unknown page", "ÐеизвеÑÑ‚Ð½Ð°Ñ Ñтраница"}, - // HandleCommand, ShowError - {"Invalid token", "Ðеверный токен"}, - {"SUCCESS", "УСПЕШÐО"}, - {"ERROR", "ОШИБКÐ"}, - {"Unknown command", "ÐеизвеÑÑ‚Ð½Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°"}, - {"Command accepted", "Команда принÑта"}, - {"Back to commands list", "ВернутьÑÑ Ðº ÑпиÑку команд"}, - {"You will be redirected in 5 seconds", "Ð’Ñ‹ будете переадреÑованы через 5 Ñекунд"}, - // HTTP_COMMAND_KILLSTREAM - {"Stream closed", "Стрим закрыт"}, - {"Stream not found or already was closed", "Стрим не найден или уже закрыт"}, - {"Destination not found", "Точка Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð½Ðµ найдена"}, - {"StreamID can't be null", "StreamID не может быть пуÑтым"}, - {"Return to destination page", "ВернутьÑÑ Ð½Ð° Ñтраницу точки назначениÑ"}, - {"You will be redirected back in 5 seconds", "Ð’Ñ‹ будете переадреÑованы назад через 5 Ñекунд"}, - // HTTP_COMMAND_LIMITTRANSIT - {"Transit tunnels count must not exceed 65535", "ЧиÑло транзитных туннелей не должно превышать 65535"}, - // HTTP_COMMAND_GET_REG_STRING - {"Register at reg.i2p", "ЗарегиÑтрировать на reg.i2p"}, - {"Description", "ОпиÑание"}, - {"A bit information about service on domain", "Ðемного информации о ÑервиÑе на домене"}, - {"Submit", "Отправить"}, - {"Domain can't end with .b32.i2p", "Домен не может заканчиватьÑÑ Ð½Ð° .b32.i2p"}, - {"Domain must end with .i2p", "Домен должен заканчиватьÑÑ Ð½Ð° .i2p"}, - {"Such destination is not found", "Ð¢Ð°ÐºÐ°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð½Ðµ найдена"}, + {"Host is down", "Узел недоÑтупен"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Ðе удалоÑÑŒ уÑтановить Ñоединение к запрошенному узлу, возможно он не в Ñети. Попробуйте повторить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð¿Ð¾Ð·Ð¶Ðµ."}, {"", ""}, }; static std::map> plurals { - // ShowUptime {"days", {"день", "днÑ", "дней"}}, {"hours", {"чаÑ", "чаÑа", "чаÑов"}}, {"minutes", {"минуту", "минуты", "минут"}}, @@ -235,7 +211,7 @@ namespace russian // language std::shared_ptr GetLocale() { - return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp index 0edc9a6e..8af89f6f 100644 --- a/i18n/Turkmen.cpp +++ b/i18n/Turkmen.cpp @@ -18,8 +18,11 @@ namespace i2p { namespace i18n { -namespace turkmen // language +namespace turkmen // language namespace { + // language name in lowercase + static std::string language = "turkmen"; + // See for language plural forms here: // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html static int plural (int n) { @@ -234,7 +237,7 @@ namespace turkmen // language std::shared_ptr GetLocale() { - return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp index 5e856c52..8da132d7 100644 --- a/i18n/Ukrainian.cpp +++ b/i18n/Ukrainian.cpp @@ -18,8 +18,11 @@ namespace i2p { namespace i18n { -namespace ukrainian // language +namespace ukrainian // language namespace { + // language name in lowercase + static std::string language = "ukrainian"; + // See for language plural forms here: // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html static int plural (int n) { @@ -207,7 +210,7 @@ namespace ukrainian // language std::shared_ptr GetLocale() { - return std::make_shared(strings, plurals, [] (int n)->int { return plural(n); }); + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); } } // language From 25f63ac22a8604f05cabee447c28337190be404d Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 27 Jun 2021 15:49:57 -0400 Subject: [PATCH 4307/6300] create different I2NP tunnel messages for endpoint and non-endpoint --- libi2pd/I2NPProtocol.cpp | 26 ++++++++++++++++++-------- libi2pd/I2NPProtocol.h | 4 ++-- libi2pd/TransitTunnel.cpp | 4 ++-- libi2pd/Tunnel.cpp | 2 +- libi2pd/TunnelEndpoint.cpp | 4 +--- libi2pd/TunnelGateway.cpp | 2 +- 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index f570a2a1..a040e25b 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -36,11 +36,21 @@ namespace i2p return std::make_shared >(); } - std::shared_ptr NewI2NPTunnelMessage () + std::shared_ptr NewI2NPTunnelMessage (bool endpoint) { - // should fit two tunnel message, enough for one garlic encrypted streaming packet - auto msg = new I2NPMessageBuffer<2*i2p::tunnel::TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + 34>(); // reserved for alignment and NTCP 16 + 6 + 12 - msg->Align (12); + I2NPMessage * msg = nullptr; + if (endpoint) + { + // should fit two tunnel message + tunnel gateway header, enough for one garlic encrypted streaming packet + msg = new I2NPMessageBuffer<2*i2p::tunnel::TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE + 28>(); // reserved for alignment and NTCP 16 + 6 + 6 + msg->Align (6); + msg->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header + } + else + { + msg = new I2NPMessageBuffer(); // reserved for alignment and NTCP 16 + 6 + 12 + msg->Align (12); + } return std::shared_ptr(msg); } @@ -692,7 +702,7 @@ namespace i2p std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf) { - auto msg = NewI2NPTunnelMessage (); + auto msg = NewI2NPTunnelMessage (false); msg->Concat (buf, i2p::tunnel::TUNNEL_DATA_MSG_SIZE); msg->FillI2NPMessageHeader (eI2NPTunnelData); return msg; @@ -700,7 +710,7 @@ namespace i2p std::shared_ptr CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload) { - auto msg = NewI2NPTunnelMessage (); + auto msg = NewI2NPTunnelMessage (false); htobe32buf (msg->GetPayload (), tunnelID); msg->len += 4; // tunnelID msg->Concat (payload, i2p::tunnel::TUNNEL_DATA_MSG_SIZE - 4); @@ -708,9 +718,9 @@ namespace i2p return msg; } - std::shared_ptr CreateEmptyTunnelDataMsg () + std::shared_ptr CreateEmptyTunnelDataMsg (bool endpoint) { - auto msg = NewI2NPTunnelMessage (); + auto msg = NewI2NPTunnelMessage (endpoint); msg->len += i2p::tunnel::TUNNEL_DATA_MSG_SIZE; return msg; } diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index cc7c94af..69d6bb04 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -278,7 +278,7 @@ namespace tunnel std::shared_ptr NewI2NPMessage (); std::shared_ptr NewI2NPShortMessage (); - std::shared_ptr NewI2NPTunnelMessage (); + std::shared_ptr NewI2NPTunnelMessage (bool endpoint); std::shared_ptr NewI2NPMessage (size_t len); std::shared_ptr CreateI2NPMessage (I2NPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0); @@ -307,7 +307,7 @@ namespace tunnel std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf); std::shared_ptr CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload); - std::shared_ptr CreateEmptyTunnelDataMsg (); + std::shared_ptr CreateEmptyTunnelDataMsg (bool endpoint); std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len); std::shared_ptr CreateTunnelGatewayMsg (uint32_t tunnelID, I2NPMessageType msgType, diff --git a/libi2pd/TransitTunnel.cpp b/libi2pd/TransitTunnel.cpp index 73ca977c..dc7655d1 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.cpp @@ -39,7 +39,7 @@ namespace tunnel void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { - auto newMsg = CreateEmptyTunnelDataMsg (); + auto newMsg = CreateEmptyTunnelDataMsg (false); EncryptTunnelMsg (tunnelMsg, newMsg); m_NumTransmittedBytes += tunnelMsg->GetLength (); @@ -87,7 +87,7 @@ namespace tunnel void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { - auto newMsg = CreateEmptyTunnelDataMsg (); + auto newMsg = CreateEmptyTunnelDataMsg (true); EncryptTunnelMsg (tunnelMsg, newMsg); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index e016e9e4..ba01ae20 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -234,7 +234,7 @@ namespace tunnel void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) { if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive - auto newMsg = CreateEmptyTunnelDataMsg (); + auto newMsg = CreateEmptyTunnelDataMsg (true); EncryptTunnelMsg (msg, newMsg); newMsg->from = shared_from_this (); m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp index 109f3120..4885c090 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -126,9 +126,7 @@ namespace tunnel if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) { // this is not last message. we have to copy it - m_CurrentMessage.data = NewI2NPTunnelMessage (); - m_CurrentMessage.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header - m_CurrentMessage.data->len += TUNNEL_GATEWAY_HEADER_SIZE; + m_CurrentMessage.data = NewI2NPTunnelMessage (true); *(m_CurrentMessage.data) = *msg; } else diff --git a/libi2pd/TunnelGateway.cpp b/libi2pd/TunnelGateway.cpp index 317926ae..08df569c 100644 --- a/libi2pd/TunnelGateway.cpp +++ b/libi2pd/TunnelGateway.cpp @@ -215,7 +215,7 @@ namespace tunnel const auto& tunnelDataMsgs = m_Buffer.GetTunnelDataMsgs (); for (auto& tunnelMsg : tunnelDataMsgs) { - auto newMsg = CreateEmptyTunnelDataMsg (); + auto newMsg = CreateEmptyTunnelDataMsg (false); m_Tunnel->EncryptTunnelMsg (tunnelMsg, newMsg); htobe32buf (newMsg->GetPayload (), m_Tunnel->GetNextTunnelID ()); newMsg->FillI2NPMessageHeader (eI2NPTunnelData); From f036b8df2dc74977b700bd543fec9d9754dbb4ce Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 28 Jun 2021 12:45:28 +0300 Subject: [PATCH 4308/6300] [i18n] update translatable strings (remove douplicates) Signed-off-by: R4SAS --- contrib/i18n/English.po | 180 +++++++++++++++++------------------ daemon/HTTPServer.cpp | 39 ++++---- libi2pd_client/HTTPProxy.cpp | 2 +- 3 files changed, 108 insertions(+), 113 deletions(-) diff --git a/contrib/i18n/English.po b/contrib/i18n/English.po index 011d0fee..76d58390 100644 --- a/contrib/i18n/English.po +++ b/contrib/i18n/English.po @@ -50,14 +50,17 @@ msgid_plural "seconds" msgstr[0] "" msgstr[1] "" +#. tr: Kibibit #: daemon/HTTPServer.cpp:166 daemon/HTTPServer.cpp:194 msgid "KiB" msgstr "" +#. tr: Mebibit #: daemon/HTTPServer.cpp:168 msgid "MiB" msgstr "" +#. tr: Gibibit #: daemon/HTTPServer.cpp:170 msgid "GiB" msgstr "" @@ -94,31 +97,32 @@ msgstr "" msgid "Main page" msgstr "" -#: daemon/HTTPServer.cpp:233 daemon/HTTPServer.cpp:689 +#: daemon/HTTPServer.cpp:233 daemon/HTTPServer.cpp:690 msgid "Router commands" msgstr "" -#: daemon/HTTPServer.cpp:234 -msgid "Local destinations" +#: daemon/HTTPServer.cpp:234 daemon/HTTPServer.cpp:413 +#: daemon/HTTPServer.cpp:425 +msgid "Local Destinations" msgstr "" #: daemon/HTTPServer.cpp:236 daemon/HTTPServer.cpp:388 #: daemon/HTTPServer.cpp:469 daemon/HTTPServer.cpp:475 -#: daemon/HTTPServer.cpp:605 daemon/HTTPServer.cpp:648 -#: daemon/HTTPServer.cpp:652 +#: daemon/HTTPServer.cpp:606 daemon/HTTPServer.cpp:649 +#: daemon/HTTPServer.cpp:653 msgid "LeaseSets" msgstr "" -#: daemon/HTTPServer.cpp:238 daemon/HTTPServer.cpp:658 +#: daemon/HTTPServer.cpp:238 daemon/HTTPServer.cpp:659 msgid "Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:239 daemon/HTTPServer.cpp:746 -#: daemon/HTTPServer.cpp:762 -msgid "Transit tunnels" +#: daemon/HTTPServer.cpp:239 daemon/HTTPServer.cpp:395 +#: daemon/HTTPServer.cpp:753 daemon/HTTPServer.cpp:769 +msgid "Transit Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:240 daemon/HTTPServer.cpp:811 +#: daemon/HTTPServer.cpp:240 daemon/HTTPServer.cpp:818 msgid "Transports" msgstr "" @@ -126,15 +130,15 @@ msgstr "" msgid "I2P tunnels" msgstr "" -#: daemon/HTTPServer.cpp:243 daemon/HTTPServer.cpp:873 -#: daemon/HTTPServer.cpp:883 +#: daemon/HTTPServer.cpp:243 daemon/HTTPServer.cpp:880 +#: daemon/HTTPServer.cpp:890 msgid "SAM sessions" msgstr "" -#: daemon/HTTPServer.cpp:259 daemon/HTTPServer.cpp:1273 -#: daemon/HTTPServer.cpp:1276 daemon/HTTPServer.cpp:1279 -#: daemon/HTTPServer.cpp:1293 daemon/HTTPServer.cpp:1338 -#: daemon/HTTPServer.cpp:1341 daemon/HTTPServer.cpp:1344 +#: daemon/HTTPServer.cpp:259 daemon/HTTPServer.cpp:1280 +#: daemon/HTTPServer.cpp:1283 daemon/HTTPServer.cpp:1286 +#: daemon/HTTPServer.cpp:1300 daemon/HTTPServer.cpp:1345 +#: daemon/HTTPServer.cpp:1348 daemon/HTTPServer.cpp:1351 msgid "ERROR" msgstr "" @@ -156,8 +160,8 @@ msgid "Unknown" msgstr "" #: daemon/HTTPServer.cpp:270 daemon/HTTPServer.cpp:400 -#: daemon/HTTPServer.cpp:401 daemon/HTTPServer.cpp:941 -#: daemon/HTTPServer.cpp:950 +#: daemon/HTTPServer.cpp:401 daemon/HTTPServer.cpp:948 +#: daemon/HTTPServer.cpp:957 msgid "Proxy" msgstr "" @@ -209,6 +213,7 @@ msgstr "" msgid "Received" msgstr "" +#. tr: Kibibit/s #: daemon/HTTPServer.cpp:328 daemon/HTTPServer.cpp:331 #: daemon/HTTPServer.cpp:334 msgid "KiB/s" @@ -262,22 +267,14 @@ msgstr "" msgid "Floodfills" msgstr "" -#: daemon/HTTPServer.cpp:394 daemon/HTTPServer.cpp:927 +#: daemon/HTTPServer.cpp:394 daemon/HTTPServer.cpp:934 msgid "Client Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:395 -msgid "Transit Tunnels" -msgstr "" - #: daemon/HTTPServer.cpp:399 msgid "Services" msgstr "" -#: daemon/HTTPServer.cpp:413 daemon/HTTPServer.cpp:425 -msgid "Local Destinations" -msgstr "" - #: daemon/HTTPServer.cpp:448 msgid "Encrypted B33 address" msgstr "" @@ -312,17 +309,17 @@ msgstr "" msgid "EncType" msgstr "" -#: daemon/HTTPServer.cpp:480 daemon/HTTPServer.cpp:663 +#: daemon/HTTPServer.cpp:480 daemon/HTTPServer.cpp:664 msgid "Inbound tunnels" msgstr "" -#. Milliseconds +#. tr: Milliseconds #: daemon/HTTPServer.cpp:485 daemon/HTTPServer.cpp:495 -#: daemon/HTTPServer.cpp:668 daemon/HTTPServer.cpp:678 +#: daemon/HTTPServer.cpp:669 daemon/HTTPServer.cpp:679 msgid "ms" msgstr "" -#: daemon/HTTPServer.cpp:490 daemon/HTTPServer.cpp:673 +#: daemon/HTTPServer.cpp:490 daemon/HTTPServer.cpp:674 msgid "Outbound tunnels" msgstr "" @@ -358,225 +355,222 @@ msgstr "" msgid "Status" msgstr "" -#: daemon/HTTPServer.cpp:535 daemon/HTTPServer.cpp:590 +#: daemon/HTTPServer.cpp:535 daemon/HTTPServer.cpp:591 msgid "Local Destination" msgstr "" -#: daemon/HTTPServer.cpp:544 daemon/HTTPServer.cpp:906 +#: daemon/HTTPServer.cpp:545 daemon/HTTPServer.cpp:913 msgid "Streams" msgstr "" -#: daemon/HTTPServer.cpp:566 +#: daemon/HTTPServer.cpp:567 msgid "Close stream" msgstr "" -#: daemon/HTTPServer.cpp:595 +#: daemon/HTTPServer.cpp:596 msgid "I2CP session not found" msgstr "" -#: daemon/HTTPServer.cpp:598 +#: daemon/HTTPServer.cpp:599 msgid "I2CP is not enabled" msgstr "" -#: daemon/HTTPServer.cpp:624 +#: daemon/HTTPServer.cpp:625 msgid "Invalid" msgstr "" -#: daemon/HTTPServer.cpp:627 +#: daemon/HTTPServer.cpp:628 msgid "Store type" msgstr "" -#: daemon/HTTPServer.cpp:628 +#: daemon/HTTPServer.cpp:629 msgid "Expires" msgstr "" -#: daemon/HTTPServer.cpp:633 +#: daemon/HTTPServer.cpp:634 msgid "Non Expired Leases" msgstr "" -#: daemon/HTTPServer.cpp:636 +#: daemon/HTTPServer.cpp:637 msgid "Gateway" msgstr "" -#: daemon/HTTPServer.cpp:637 +#: daemon/HTTPServer.cpp:638 msgid "TunnelID" msgstr "" -#: daemon/HTTPServer.cpp:638 +#: daemon/HTTPServer.cpp:639 msgid "EndDate" msgstr "" -#: daemon/HTTPServer.cpp:648 +#: daemon/HTTPServer.cpp:649 msgid "not floodfill" msgstr "" -#: daemon/HTTPServer.cpp:659 +#: daemon/HTTPServer.cpp:660 msgid "Queue size" msgstr "" -#: daemon/HTTPServer.cpp:690 +#: daemon/HTTPServer.cpp:691 msgid "Run peer test" msgstr "" -#: daemon/HTTPServer.cpp:693 +#: daemon/HTTPServer.cpp:698 msgid "Decline transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:695 +#: daemon/HTTPServer.cpp:700 msgid "Accept transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:698 daemon/HTTPServer.cpp:703 +#: daemon/HTTPServer.cpp:704 daemon/HTTPServer.cpp:709 msgid "Cancel graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:700 daemon/HTTPServer.cpp:705 +#: daemon/HTTPServer.cpp:706 daemon/HTTPServer.cpp:711 msgid "Start graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:707 +#: daemon/HTTPServer.cpp:714 msgid "Force shutdown" msgstr "" -#: daemon/HTTPServer.cpp:710 +#: daemon/HTTPServer.cpp:717 msgid "" "Note: any action done here are not persistent and not changes your " "config files." msgstr "" -#: daemon/HTTPServer.cpp:712 +#: daemon/HTTPServer.cpp:719 msgid "Logging level" msgstr "" -#: daemon/HTTPServer.cpp:720 +#: daemon/HTTPServer.cpp:727 msgid "Transit tunnels limit" msgstr "" -#: daemon/HTTPServer.cpp:725 daemon/HTTPServer.cpp:737 +#: daemon/HTTPServer.cpp:732 daemon/HTTPServer.cpp:744 msgid "Change" msgstr "" -#: daemon/HTTPServer.cpp:729 +#: daemon/HTTPServer.cpp:736 msgid "Change language" msgstr "" -#: daemon/HTTPServer.cpp:762 +#: daemon/HTTPServer.cpp:769 msgid "no transit tunnels currently built" msgstr "" -#: daemon/HTTPServer.cpp:867 daemon/HTTPServer.cpp:890 +#: daemon/HTTPServer.cpp:874 daemon/HTTPServer.cpp:897 msgid "SAM disabled" msgstr "" -#: daemon/HTTPServer.cpp:883 +#: daemon/HTTPServer.cpp:890 msgid "no sessions currently running" msgstr "" -#: daemon/HTTPServer.cpp:896 +#: daemon/HTTPServer.cpp:903 msgid "SAM session not found" msgstr "" -#: daemon/HTTPServer.cpp:901 +#: daemon/HTTPServer.cpp:908 msgid "SAM Session" msgstr "" -#: daemon/HTTPServer.cpp:958 +#: daemon/HTTPServer.cpp:965 msgid "Server Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:974 +#: daemon/HTTPServer.cpp:981 msgid "Client Forwards" msgstr "" -#: daemon/HTTPServer.cpp:988 +#: daemon/HTTPServer.cpp:995 msgid "Server Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1194 +#: daemon/HTTPServer.cpp:1201 msgid "Unknown page" msgstr "" -#: daemon/HTTPServer.cpp:1213 +#: daemon/HTTPServer.cpp:1220 msgid "Invalid token" msgstr "" -#: daemon/HTTPServer.cpp:1271 daemon/HTTPServer.cpp:1328 -#: daemon/HTTPServer.cpp:1364 +#: daemon/HTTPServer.cpp:1278 daemon/HTTPServer.cpp:1335 +#: daemon/HTTPServer.cpp:1371 msgid "SUCCESS" msgstr "" -#: daemon/HTTPServer.cpp:1271 +#: daemon/HTTPServer.cpp:1278 msgid "Stream closed" msgstr "" -#: daemon/HTTPServer.cpp:1273 +#: daemon/HTTPServer.cpp:1280 msgid "Stream not found or already was closed" msgstr "" -#: daemon/HTTPServer.cpp:1276 +#: daemon/HTTPServer.cpp:1283 msgid "Destination not found" msgstr "" -#: daemon/HTTPServer.cpp:1279 +#: daemon/HTTPServer.cpp:1286 msgid "StreamID can't be null" msgstr "" -#: daemon/HTTPServer.cpp:1281 daemon/HTTPServer.cpp:1346 +#: daemon/HTTPServer.cpp:1288 daemon/HTTPServer.cpp:1353 msgid "Return to destination page" msgstr "" -#: daemon/HTTPServer.cpp:1282 daemon/HTTPServer.cpp:1295 -msgid "You will be redirected back in 5 seconds" +#: daemon/HTTPServer.cpp:1289 daemon/HTTPServer.cpp:1302 +#: daemon/HTTPServer.cpp:1373 +msgid "You will be redirected in 5 seconds" msgstr "" -#: daemon/HTTPServer.cpp:1293 +#: daemon/HTTPServer.cpp:1300 msgid "Transit tunnels count must not exceed 65535" msgstr "" -#: daemon/HTTPServer.cpp:1294 daemon/HTTPServer.cpp:1365 +#: daemon/HTTPServer.cpp:1301 daemon/HTTPServer.cpp:1372 msgid "Back to commands list" msgstr "" -#: daemon/HTTPServer.cpp:1330 +#: daemon/HTTPServer.cpp:1337 msgid "Register at reg.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1331 +#: daemon/HTTPServer.cpp:1338 msgid "Description" msgstr "" -#: daemon/HTTPServer.cpp:1331 +#: daemon/HTTPServer.cpp:1338 msgid "A bit information about service on domain" msgstr "" -#: daemon/HTTPServer.cpp:1332 +#: daemon/HTTPServer.cpp:1339 msgid "Submit" msgstr "" -#: daemon/HTTPServer.cpp:1338 +#: daemon/HTTPServer.cpp:1345 msgid "Domain can't end with .b32.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1341 +#: daemon/HTTPServer.cpp:1348 msgid "Domain must end with .i2p" msgstr "" -#: daemon/HTTPServer.cpp:1344 +#: daemon/HTTPServer.cpp:1351 msgid "Such destination is not found" msgstr "" -#: daemon/HTTPServer.cpp:1360 +#: daemon/HTTPServer.cpp:1367 msgid "Unknown command" msgstr "" -#: daemon/HTTPServer.cpp:1364 +#: daemon/HTTPServer.cpp:1371 msgid "Command accepted" msgstr "" -#: daemon/HTTPServer.cpp:1366 -msgid "You will be redirected in 5 seconds" -msgstr "" - #: libi2pd_client/HTTPProxy.cpp:157 msgid "Proxy error" msgstr "" @@ -598,7 +592,7 @@ msgid "You may try to find this host on jump services below" msgstr "" #: libi2pd_client/HTTPProxy.cpp:273 libi2pd_client/HTTPProxy.cpp:288 -#: libi2pd_client/HTTPProxy.cpp:365 +#: libi2pd_client/HTTPProxy.cpp:322 libi2pd_client/HTTPProxy.cpp:365 msgid "Invalid request" msgstr "" @@ -643,10 +637,6 @@ msgstr "" msgid "to update record" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:322 -msgid "Invalid Request" -msgstr "" - #: libi2pd_client/HTTPProxy.cpp:322 msgid "invalid request uri" msgstr "" diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 8ed4511f..fd26d8f1 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -163,11 +163,11 @@ namespace http { s << std::fixed << std::setprecision(2); auto numKBytes = (double) bytes / 1024; if (numKBytes < 1024) - s << numKBytes << " " << tr("KiB"); + s << numKBytes << " " << tr(/* tr: Kibibit */ "KiB"); else if (numKBytes < 1024 * 1024) - s << numKBytes / 1024 << " " << tr("MiB"); + s << numKBytes / 1024 << " " << tr(/* tr: Mebibit */ "MiB"); else - s << numKBytes / 1024 / 1024 << " " << tr("GiB"); + s << numKBytes / 1024 / 1024 << " " << tr(/* tr: Gibibit */ "GiB"); } static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) @@ -191,7 +191,7 @@ namespace http { else stateText = tr("unknown"); s << " " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << ", "; - s << " " << (int) (bytes / 1024) << " " << tr("KiB") << "\r\n"; + s << " " << (int) (bytes / 1024) << " " << tr(/* tr: Kibibit */ "KiB") << "\r\n"; } static void SetLogLevel (const std::string& level) @@ -231,12 +231,12 @@ namespace http { "
    \r\n" " " << tr("Main page") << "

    \r\n" " " << tr("Router commands") << "
    \r\n" - " " << tr("Local destinations") << "
    \r\n"; + " " << tr("Local Destinations") << "
    \r\n"; if (i2p::context.IsFloodfill ()) s << " " << tr("LeaseSets") << "
    \r\n"; s << " " << tr("Tunnels") << "
    \r\n" - " " << tr("Transit tunnels") << "
    \r\n" + " " << tr("Transit Tunnels") << "
    \r\n" " " << tr ("Transports") << "
    \r\n" " " << tr("I2P tunnels") << "
    \r\n"; if (i2p::client::context.GetSAMBridge ()) @@ -325,13 +325,13 @@ namespace http { s << "" << tr("Tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
    \r\n"; s << "" << tr("Received") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); - s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " " << tr("KiB/s") << ")
    \r\n"; + s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
    \r\n"; s << "" << tr("Sent") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); - s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " " << tr("KiB/s") << ")
    \r\n"; + s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
    \r\n"; s << "" << tr("Transit") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); - s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " " << tr("KiB/s") << ")
    \r\n"; + s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "KiB/s") << ")
    \r\n"; s << "" << tr("Data path") << ": " << i2p::fs::GetUTF8DataDir() << "
    \r\n"; s << "
    "; if((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { @@ -482,7 +482,7 @@ namespace http { s << "
    "; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << tr(/* Milliseconds */ "ms") << " )"; + s << " ( " << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); s << "
    \r\n"; } @@ -540,7 +540,8 @@ namespace http { if (dest) { ShowLeaseSetDestination (s, dest, token); - // show streams + + // Print table with streams information s << "\r\n\r\n\r\n"; s << ""; s << "
    " << tr("Streams") << "
    StreamID"; // Stream closing button column @@ -685,14 +686,17 @@ namespace http { static void ShowCommands (std::stringstream& s, uint32_t token) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - /* commands */ + s << "" << tr("Router commands") << "
    \r\n
    \r\n
    \r\n"; s << " " << tr("Run peer test") << "
    \r\n"; - //s << " Reload config
    \r\n"; + + // s << " Reload config
    \r\n"; + if (i2p::context.AcceptsTunnels ()) s << " " << tr("Decline transit tunnels") << "
    \r\n"; else s << " " << tr("Accept transit tunnels") << "
    \r\n"; + #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) if (Daemon.gracefulShutdownInterval) s << " " << tr("Cancel graceful shutdown") << "
    \r\n"; @@ -704,6 +708,7 @@ namespace http { else s << " " << tr("Start graceful shutdown") << "
    \r\n"; #endif + s << " " << tr("Force shutdown") << "\r\n"; s << "
    "; @@ -743,7 +748,7 @@ namespace http { { if(i2p::tunnel::tunnels.CountTransitTunnels()) { - s << "" << tr("Transit tunnels") << ":
    \r\n
    \r\n"; + s << "" << tr("Transit Tunnels") << ":
    \r\n
    \r\n"; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { s << "
    \r\n"; @@ -759,7 +764,7 @@ namespace http { } else { - s << "" << tr("Transit tunnels") << ": " << tr("no transit tunnels currently built") << ".
    \r\n"; + s << "" << tr("Transit Tunnels") << ": " << tr("no transit tunnels currently built") << ".
    \r\n"; } } @@ -1279,7 +1284,7 @@ namespace http { s << "" << tr("ERROR") << ": " << tr("StreamID can't be null") << "
    \r\n
    \r\n"; s << "" << tr("Return to destination page") << "
    \r\n"; - s << "

    " << tr("You will be redirected back in 5 seconds") << ""; + s << "

    " << tr("You will be redirected in 5 seconds") << ""; redirect = "5; url=" + webroot + "?page=local_destination&b32=" + b32; res.add_header("Refresh", redirect.c_str()); return; @@ -1292,7 +1297,7 @@ namespace http { else { s << "" << tr("ERROR") << ": " << tr("Transit tunnels count must not exceed 65535") << "\r\n
    \r\n
    \r\n"; s << "" << tr("Back to commands list") << "\r\n
    \r\n"; - s << "

    " << tr("You will be redirected back in 5 seconds") << ""; + s << "

    " << tr("You will be redirected in 5 seconds") << ""; res.add_header("Refresh", redirect.c_str()); return; } diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index d8b84e82..70cf78a8 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -319,7 +319,7 @@ namespace proxy { auto pos = uri.find(":"); if(pos == std::string::npos || pos == uri.size() - 1) { - GenericProxyError(tr("Invalid Request"), tr("invalid request uri")); + GenericProxyError(tr("Invalid request"), tr("invalid request uri")); return true; } else From 5781335814b1c3766ccaadb66418c715daa88e55 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 29 Jun 2021 19:08:11 -0400 Subject: [PATCH 4309/6300] save and check last stream --- libi2pd/Streaming.cpp | 21 +++++++++++++++------ libi2pd/Streaming.h | 11 ++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 612b1058..3269e040 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -1050,6 +1050,7 @@ namespace stream it.second->Terminate (false); // we delete here m_Streams.clear (); m_IncomingStreams.clear (); + m_LastStream = nullptr; } } @@ -1058,9 +1059,16 @@ namespace stream uint32_t sendStreamID = packet->GetSendStreamID (); if (sendStreamID) { - auto it = m_Streams.find (sendStreamID); - if (it != m_Streams.end ()) - it->second->HandleNextPacket (packet); + if (!m_LastStream || sendStreamID != m_LastStream->GetRecvStreamID ()) + { + auto it = m_Streams.find (sendStreamID); + if (it != m_Streams.end ()) + m_LastStream = it->second; + else + m_LastStream = nullptr; + } + if (m_LastStream) + m_LastStream->HandleNextPacket (packet); else if (packet->IsEcho () && m_Owner->IsStreamingAnswerPings ()) { // ping @@ -1166,7 +1174,7 @@ namespace stream { auto s = std::make_shared (m_Owner->GetService (), *this, remote, port); std::unique_lock l(m_StreamsMutex); - m_Streams[s->GetRecvStreamID ()] = s; + m_Streams.emplace (s->GetRecvStreamID (), s); return s; } @@ -1174,8 +1182,8 @@ namespace stream { auto s = std::make_shared (m_Owner->GetService (), *this); std::unique_lock l(m_StreamsMutex); - m_Streams[s->GetRecvStreamID ()] = s; - m_IncomingStreams[receiveStreamID] = s; + m_Streams.emplace (s->GetRecvStreamID (), s); + m_IncomingStreams.emplace (receiveStreamID, s); return s; } @@ -1186,6 +1194,7 @@ namespace stream std::unique_lock l(m_StreamsMutex); m_Streams.erase (stream->GetRecvStreamID ()); m_IncomingStreams.erase (stream->GetSendStreamID ()); + if (m_LastStream == stream) m_LastStream = nullptr; } } diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index fe035136..c40c49f5 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include #include @@ -297,12 +297,13 @@ namespace stream uint16_t m_LocalPort; bool m_Gzip; // gzip compression of data messages std::mutex m_StreamsMutex; - std::map > m_Streams; // sendStreamID->stream - std::map > m_IncomingStreams; // receiveStreamID->stream + std::unordered_map > m_Streams; // sendStreamID->stream + std::unordered_map > m_IncomingStreams; // receiveStreamID->stream + std::shared_ptr m_LastStream; Acceptor m_Acceptor; std::list > m_PendingIncomingStreams; boost::asio::deadline_timer m_PendingIncomingTimer; - std::map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN + std::unordered_map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN i2p::util::MemoryPool m_PacketsPool; i2p::util::MemoryPool > m_I2NPMsgsPool; From abee29719d57c6569ad0bc462cd13cdcd4b466b3 Mon Sep 17 00:00:00 2001 From: idk Date: Fri, 2 Jul 2021 10:47:55 -0400 Subject: [PATCH 4310/6300] fix go linking --- Makefile | 2 +- libi2pd_wrapper/api.go | 14 ++++++++++++-- libi2pd_wrapper/capi.cpp | 3 +-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 59faa94b..b1db36a8 100644 --- a/Makefile +++ b/Makefile @@ -123,7 +123,7 @@ $(ARLIB): $(LIB_OBJS) $(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) $(AR) -r $@ $^ -$(ARLIB_WRAP): $(LIB_OBJS) +$(ARLIB_WRAP): $(WRAP_LIB_OBJS) $(AR) -r $@ $^ $(ARLIB_LANG): $(LANG_OBJS) diff --git a/libi2pd_wrapper/api.go b/libi2pd_wrapper/api.go index 48a41a4f..4674f16f 100644 --- a/libi2pd_wrapper/api.go +++ b/libi2pd_wrapper/api.go @@ -1,7 +1,17 @@ package api /* -#cgo CXXFLAGS: -I${SRCDIR}/../i18n -I${SRCDIR}/../libi2pd_client -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi -fPIC -D__AES__ -maes -#cgo LDFLAGS: -latomic -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lstdc++ +* Copyright (c) 2013-2020, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +/* +#cgo CXXFLAGS: -I${SRCDIR}/../i18n -I${SRCDIR}/../libi2pd_client -I${SRCDIR}/../libi2pd -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi -fPIC -D__AES__ -maes +#cgo LDFLAGS: -L${SRCDIR}/../ -l:libi2pd.a -latomic -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lstdc++ */ import "C" + +// -D, -U, -I, and \ No newline at end of file diff --git a/libi2pd_wrapper/capi.cpp b/libi2pd_wrapper/capi.cpp index 55b1b051..fc4df917 100644 --- a/libi2pd_wrapper/capi.cpp +++ b/libi2pd_wrapper/capi.cpp @@ -85,10 +85,9 @@ void C_TerminateI2P () return i2p::api::TerminateI2P(); } -void C_StartI2P ()//std::ostream *logStream) +void C_StartI2P () { std::shared_ptr logStream; - //cppLogStream(&out); return i2p::api::StartI2P(logStream); } From ff0e23d2c40fd52ba1fd1f7fba67c09444c57b9f Mon Sep 17 00:00:00 2001 From: r4sas Date: Fri, 2 Jul 2021 16:43:41 +0000 Subject: [PATCH 4311/6300] [cmake] use GNUInstallDirs for libraries destination path (#1672) Signed-off-by: r4sas --- build/CMakeLists.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index d1502deb..2cd598f4 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -27,6 +27,9 @@ option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules") set(CMAKE_SOURCE_DIR "..") +#Handle paths nicely +include(GNUInstallDirs) + # architecture include(TargetArch) target_architecture(ARCHITECTURE) @@ -48,8 +51,8 @@ set_target_properties(libi2pd PROPERTIES PREFIX "") if(WITH_LIBRARY) install(TARGETS libi2pd EXPORT libi2pd - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries) # TODO Make libi2pd available to 3rd party projects via CMake as imported target # FIXME This pulls stdafx @@ -63,8 +66,8 @@ set_target_properties(libi2pdclient PROPERTIES PREFIX "") if(WITH_LIBRARY) install(TARGETS libi2pdclient EXPORT libi2pdclient - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries) endif() @@ -75,8 +78,8 @@ set_target_properties(libi2pdlang PROPERTIES PREFIX "") if(WITH_LIBRARY) install(TARGETS libi2pdlang EXPORT libi2pdlang - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries) endif() @@ -272,9 +275,6 @@ message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") message(STATUS " THREADSANITIZER : ${WITH_THREADSANITIZER}") message(STATUS "---------------------------------------") -#Handle paths nicely -include(GNUInstallDirs) - if(WITH_BINARY) add_executable("${PROJECT_NAME}" ${DAEMON_SRC}) From 8b35ce33202591c092fc57be6f8e27eb91ead2b1 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 2 Jul 2021 13:20:24 -0400 Subject: [PATCH 4312/6300] separate decryption between own record and other records --- libi2pd/Tunnel.cpp | 36 ++++++++++++++++++------------------ libi2pd/TunnelConfig.cpp | 23 +++++++++++++++++++++++ libi2pd/TunnelConfig.h | 1 + 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index ba01ae20..7e154cbf 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -111,31 +111,31 @@ namespace tunnel TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) { + // decrypt current hop + auto idx = hop->recordIndex; + if (idx >= 0 && idx < msg[0]) + { + uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE; + if (!hop->DecryptBuildResponseRecord (record, record)) + return false; + } + else + { + LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); + return false; + } + + // decrypt records before current hop decryption.SetKey (hop->replyKey); - // decrypt records before and current hop - TunnelHopConfig * hop1 = hop; + TunnelHopConfig * hop1 = hop->prev; while (hop1) { auto idx = hop1->recordIndex; if (idx >= 0 && idx < msg[0]) { uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE; - if (hop1 == hop && hop1->IsECIES ()) - { - uint8_t nonce[12]; - memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (record, TUNNEL_BUILD_RECORD_SIZE - 16, - hop->m_H, 32, hop->m_CK, nonce, record, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt - { - LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); - return false; - } - } - else - { - decryption.SetIV (hop->replyIV); - decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); - } + decryption.SetIV (hop->replyIV); + decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); } else LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 8f515c5d..9079e134 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -147,5 +147,28 @@ namespace tunnel } MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) } + + bool TunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) + { + if (IsECIES ()) + { + uint8_t nonce[12]; + memset (nonce, 0, 12); + if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, TUNNEL_BUILD_RECORD_SIZE - 16, + m_H, 32, m_CK, nonce, clearText, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt + { + LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); + return false; + } + } + else + { + i2p::crypto::CBCDecryption decryption; + decryption.SetKey (replyKey); + decryption.SetIV (replyIV); + decryption.Decrypt (encrypted, TUNNEL_BUILD_RECORD_SIZE, clearText); + } + return true; + } } } \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 45693970..548ef031 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -43,6 +43,7 @@ namespace tunnel void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); void EncryptECIES (std::shared_ptr& encryptor, const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx); + bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText); }; class TunnelConfig From 5d01ee95810aa72ccb6d0aca5c39a9b8eca37ef5 Mon Sep 17 00:00:00 2001 From: idk Date: Fri, 2 Jul 2021 13:20:28 -0400 Subject: [PATCH 4313/6300] Also add the languages to the linker flags in the api.go file --- libi2pd_wrapper/api.go | 4 +--- libi2pd_wrapper/capi.h | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/libi2pd_wrapper/api.go b/libi2pd_wrapper/api.go index 4674f16f..64403aae 100644 --- a/libi2pd_wrapper/api.go +++ b/libi2pd_wrapper/api.go @@ -10,8 +10,6 @@ package api /* #cgo CXXFLAGS: -I${SRCDIR}/../i18n -I${SRCDIR}/../libi2pd_client -I${SRCDIR}/../libi2pd -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-psabi -fPIC -D__AES__ -maes -#cgo LDFLAGS: -L${SRCDIR}/../ -l:libi2pd.a -latomic -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lstdc++ +#cgo LDFLAGS: -L${SRCDIR}/../ -l:libi2pd.a -l:libi2pdlang.a -latomic -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lstdc++ */ import "C" - -// -D, -U, -I, and \ No newline at end of file diff --git a/libi2pd_wrapper/capi.h b/libi2pd_wrapper/capi.h index 3e33a0ee..bfc4f88b 100644 --- a/libi2pd_wrapper/capi.h +++ b/libi2pd_wrapper/capi.h @@ -17,7 +17,7 @@ extern "C" { void C_InitI2P (int argc, char argv[], const char * appName); //void C_InitI2P (int argc, char** argv, const char * appName); void C_TerminateI2P (); -void C_StartI2P (); //std::ostream *logStream = nullptr); +void C_StartI2P (); // write system log to logStream, if not specified to .log in application's folder void C_StopI2P (); void C_RunPeerTest (); // should be called after UPnP From 0ae170531e942f9488889d2f4367f90109f35928 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 2 Jul 2021 15:41:33 -0400 Subject: [PATCH 4314/6300] different ElGamal and ECIES hops configs --- libi2pd/TunnelConfig.cpp | 122 ++++++++++++++++++++------------------- libi2pd/TunnelConfig.h | 42 +++++++++++--- 2 files changed, 99 insertions(+), 65 deletions(-) diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 9079e134..6a314ec3 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -78,53 +78,42 @@ namespace tunnel } } - void TunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) + void ElGamalTunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) { + // fill clear text uint8_t flag = 0; if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); + memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); + htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); + memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); + memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); + clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; + htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); + htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); + RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); + // encrypt auto encryptor = ident->CreateEncryptor (nullptr); - if (IsECIES ()) - { - uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); - memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; - memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, 600); // +10 minutes - htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); - memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); - if (encryptor) - EncryptECIES (encryptor, clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx); - } - else - { - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); - htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; - htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); - htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); - RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); - if (encryptor) - encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); - } + if (encryptor) + encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); - } + } - void TunnelHopConfig::EncryptECIES (std::shared_ptr& encryptor, + bool ElGamalTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) + { + i2p::crypto::CBCDecryption decryption; + decryption.SetKey (replyKey); + decryption.SetIV (replyIV); + decryption.Decrypt (encrypted, TUNNEL_BUILD_RECORD_SIZE, clearText); + return true; + } + + void ECIESTunnelHopConfig::EncryptECIES (std::shared_ptr& encryptor, const uint8_t * plainText, uint8_t * encrypted, BN_CTX * ctx) { uint8_t hepk[32]; @@ -147,26 +136,43 @@ namespace tunnel } MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) } - - bool TunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) + + void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) { - if (IsECIES ()) + // fill clear text + uint8_t flag = 0; + if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; + if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); + memcpy (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; + memset (clearText + ECIES_BUILD_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 3); // set to 0 for compatibility + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET, 600); // +10 minutes + htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); + memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); + // encrypt + auto encryptor = ident->CreateEncryptor (nullptr); + if (encryptor) + EncryptECIES (encryptor, clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx); + memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); + } + + bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) + { + uint8_t nonce[12]; + memset (nonce, 0, 12); + if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, TUNNEL_BUILD_RECORD_SIZE - 16, + m_H, 32, m_CK, nonce, clearText, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt { - uint8_t nonce[12]; - memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, TUNNEL_BUILD_RECORD_SIZE - 16, - m_H, 32, m_CK, nonce, clearText, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt - { - LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); - return false; - } - } - else - { - i2p::crypto::CBCDecryption decryption; - decryption.SetKey (replyKey); - decryption.SetIV (replyIV); - decryption.Decrypt (encrypted, TUNNEL_BUILD_RECORD_SIZE, clearText); + LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); + return false; } return true; } diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 548ef031..f64db0b0 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -18,7 +18,7 @@ namespace i2p { namespace tunnel { - struct TunnelHopConfig: public i2p::crypto::NoiseSymmetricState + struct TunnelHopConfig { std::shared_ptr ident; i2p::data::IdentHash nextIdent; @@ -33,18 +33,42 @@ namespace tunnel int recordIndex; // record # in tunnel build message TunnelHopConfig (std::shared_ptr r); + virtual ~TunnelHopConfig () {}; void SetNextIdent (const i2p::data::IdentHash& ident); void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent); void SetNext (TunnelHopConfig * n); void SetPrev (TunnelHopConfig * p); - bool IsECIES () const { return ident->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; - void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); - void EncryptECIES (std::shared_ptr& encryptor, - const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx); - bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText); + virtual bool IsECIES () const { return false; }; + virtual void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) = 0; + virtual bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) = 0; }; + + struct ElGamalTunnelHopConfig: public TunnelHopConfig + { + ElGamalTunnelHopConfig (std::shared_ptr r): + TunnelHopConfig (r) {}; + void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); + bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText); + }; + + struct ECIESTunnelHopConfig: public TunnelHopConfig, public i2p::crypto::NoiseSymmetricState + { + ECIESTunnelHopConfig (std::shared_ptr r): + TunnelHopConfig (r) {}; + bool IsECIES () const { return true; }; + void EncryptECIES (std::shared_ptr& encryptor, + const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx); + }; + + struct LongECIESTunnelHopConfig: public ECIESTunnelHopConfig + { + LongECIESTunnelHopConfig (std::shared_ptr r): + ECIESTunnelHopConfig (r) {}; + void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); + bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText); + }; class TunnelConfig { @@ -154,7 +178,11 @@ namespace tunnel TunnelHopConfig * prev = nullptr; for (const auto& it: peers) { - auto hop = new TunnelHopConfig (it); + TunnelHopConfig * hop; + if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + hop = new LongECIESTunnelHopConfig (it); + else + hop = new ElGamalTunnelHopConfig (it); if (prev) prev->SetNext (hop); else From aace644815dbf170b3e9e8e720b2116922de0f8a Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 2 Jul 2021 22:06:24 -0400 Subject: [PATCH 4315/6300] added ShortECIESTunnelHopConfig --- libi2pd/I2NPProtocol.h | 1 + libi2pd/TunnelConfig.cpp | 59 ++++++++++++++++++++++++++++++++-------- libi2pd/TunnelConfig.h | 12 ++++++-- 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 69d6bb04..8cb68856 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -113,6 +113,7 @@ namespace i2p const size_t SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET = SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE + 1; const size_t SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET = SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; const size_t SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET = SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET + 4; + const size_t SHORT_REQUEST_RECORD_PADDING_OFFSET = SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; const size_t SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE = 154; enum I2NPMessageType diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 6a314ec3..505f66cc 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -113,9 +113,10 @@ namespace tunnel return true; } - void ECIESTunnelHopConfig::EncryptECIES (std::shared_ptr& encryptor, - const uint8_t * plainText, uint8_t * encrypted, BN_CTX * ctx) + void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted) { + auto encryptor = ident->CreateEncryptor (nullptr); + if (!encryptor) return; uint8_t hepk[32]; encryptor->Encrypt (nullptr, hepk, nullptr, false); i2p::crypto::InitNoiseNState (*this, hepk); @@ -128,13 +129,19 @@ namespace tunnel MixKey (sharedSecret); uint8_t nonce[12]; memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, m_H, 32, - m_CK + 32, nonce, encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16, true)) // encrypt + if (!i2p::crypto::AEADChaCha20Poly1305 (plainText, len, m_H, 32, m_CK + 32, nonce, encrypted, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Tunnel: Plaintext AEAD encryption failed"); return; } - MixHash (encrypted, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 16); // h = SHA256(h || ciphertext) + MixHash (encrypted, len + 16); // h = SHA256(h || ciphertext) + } + + bool ECIESTunnelHopConfig::DecryptECIES (const uint8_t * encrypted, size_t len, uint8_t * clearText) + { + uint8_t nonce[12]; + memset (nonce, 0, 12); + return i2p::crypto::AEADChaCha20Poly1305 (encrypted, len - 16, m_H, 32, m_CK, nonce, clearText, len - 16, false); // decrypt } void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) @@ -158,18 +165,46 @@ namespace tunnel htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); // encrypt - auto encryptor = ident->CreateEncryptor (nullptr); - if (encryptor) - EncryptECIES (encryptor, clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx); + EncryptECIES (clearText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET); memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) { - uint8_t nonce[12]; - memset (nonce, 0, 12); - if (!i2p::crypto::AEADChaCha20Poly1305 (encrypted, TUNNEL_BUILD_RECORD_SIZE - 16, - m_H, 32, m_CK, nonce, clearText, TUNNEL_BUILD_RECORD_SIZE - 16, false)) // decrypt + if (!DecryptECIES (encrypted, TUNNEL_BUILD_RECORD_SIZE, clearText)) + { + LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); + return false; + } + return true; + } + + void ShortECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) + { + // fill clear text + uint8_t flag = 0; + if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; + if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE ]; + htobe32buf (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); + htobe32buf (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); + memcpy (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] = flag; + memset (clearText + SHORT_REQUEST_RECORD_MORE_FLAGS_OFFSET, 0, 2); + clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE] = 0; // AES + htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetMinutesSinceEpoch ()); + htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET , 600); // +10 minutes + htobe32buf (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); + memset (clearText + SHORT_REQUEST_RECORD_PADDING_OFFSET, 0, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE - SHORT_REQUEST_RECORD_PADDING_OFFSET); + // TODO: derive layer and reply keys + // encrypt + EncryptECIES (clearText, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET); + memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); + } + + bool ShortECIESTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) + { + if (!DecryptECIES (encrypted, SHORT_TUNNEL_BUILD_RECORD_SIZE, clearText)) { LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); return false; diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index f64db0b0..7d6456fd 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -58,8 +58,8 @@ namespace tunnel ECIESTunnelHopConfig (std::shared_ptr r): TunnelHopConfig (r) {}; bool IsECIES () const { return true; }; - void EncryptECIES (std::shared_ptr& encryptor, - const uint8_t * clearText, uint8_t * encrypted, BN_CTX * ctx); + void EncryptECIES (const uint8_t * clearText, size_t len, uint8_t * encrypted); + bool DecryptECIES (const uint8_t * encrypted, size_t len, uint8_t * clearText); }; struct LongECIESTunnelHopConfig: public ECIESTunnelHopConfig @@ -69,6 +69,14 @@ namespace tunnel void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText); }; + + struct ShortECIESTunnelHopConfig: public ECIESTunnelHopConfig + { + ShortECIESTunnelHopConfig (std::shared_ptr r): + ECIESTunnelHopConfig (r) {}; + void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); + bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText); + }; class TunnelConfig { From a71754273319bd12f6f92007d943f62714a49e67 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 4 Jul 2021 07:33:28 -0400 Subject: [PATCH 4316/6300] update yggdrasil reseed to 0.4 --- libi2pd/Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index e5640ad0..de294357 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -213,7 +213,7 @@ namespace config { "https://i2p.novg.net/" ), "Reseed URLs, separated by comma") ("reseed.yggurls", value()->default_value( - "http://[324:9de3:fea4:f6ac::ace]:7070/" + "http://[324:71e:281a:9ed3::ace]:7070/" ), "Reseed URLs through the Yggdrasil, separated by comma") ; From 9000b3df4edcbe7f2c8afd0e1e30609746311ace Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 5 Jul 2021 14:31:07 -0400 Subject: [PATCH 4317/6300] KDF for short tunnel build messages --- libi2pd/I2NPProtocol.cpp | 28 +++++++++++++++++++--------- libi2pd/TunnelConfig.cpp | 22 +++++++++++++++++----- libi2pd/TunnelConfig.h | 2 +- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index a040e25b..6034e5f4 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -622,17 +622,28 @@ namespace i2p return; } auto& noiseState = i2p::context.GetCurrentNoiseState (); - uint8_t layerKeys[64]; // (layer key, iv key) - i2p::crypto::HKDF (noiseState.m_CK + 32, nullptr, 0, "LayerAndIVKeys", layerKeys); // TODO: correct domain + uint8_t replyKey[32], layerKey[32], ivKey[32]; + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK); + memcpy (replyKey, noiseState.m_CK + 32, 32); + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelLayerKey", noiseState.m_CK); + memcpy (layerKey, noiseState.m_CK + 32, 32); + bool isEndpoint = clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; + if (isEndpoint) + { + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK); + memcpy (ivKey, noiseState.m_CK + 32, 32); + } + else + memcpy (ivKey, noiseState.m_CK , 32); auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - layerKeys, layerKeys + 32, + layerKey, ivKey, clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); - if (clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) + if (isEndpoint) { // we are endpoint, create OutboundTunnelBuildReply auto otbrm = NewI2NPShortMessage (); @@ -658,14 +669,13 @@ namespace i2p } otbrm->len += (payload - otbrm->GetPayload ()); otbrm->FillI2NPMessageHeader (eI2NPOutboundTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); - uint8_t replyKeys[64]; // (reply key, tag) - i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "ReplyKeyAndTag", replyKeys); // TODO: correct domain + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); uint64_t tag; - memcpy (&tag, replyKeys + 32, 8); + memcpy (&tag, noiseState.m_CK, 8); // send garlic to reply tunnel transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - i2p::garlic::WrapECIESX25519AEADRatchetMessage (otbrm, replyKeys, tag))); + i2p::garlic::WrapECIESX25519AEADRatchetMessage (otbrm, noiseState.m_CK + 32, tag))); } else { @@ -680,7 +690,7 @@ namespace i2p { // TODO: fill reply if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState.m_H, 32, noiseState.m_CK, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt { LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); return; diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 505f66cc..cc612c00 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -137,11 +137,11 @@ namespace tunnel MixHash (encrypted, len + 16); // h = SHA256(h || ciphertext) } - bool ECIESTunnelHopConfig::DecryptECIES (const uint8_t * encrypted, size_t len, uint8_t * clearText) + bool ECIESTunnelHopConfig::DecryptECIES (const uint8_t * key, const uint8_t * encrypted, size_t len, uint8_t * clearText) { uint8_t nonce[12]; memset (nonce, 0, 12); - return i2p::crypto::AEADChaCha20Poly1305 (encrypted, len - 16, m_H, 32, m_CK, nonce, clearText, len - 16, false); // decrypt + return i2p::crypto::AEADChaCha20Poly1305 (encrypted, len - 16, m_H, 32, key, nonce, clearText, len - 16, false); // decrypt } void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) @@ -171,7 +171,7 @@ namespace tunnel bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) { - if (!DecryptECIES (encrypted, TUNNEL_BUILD_RECORD_SIZE, clearText)) + if (!DecryptECIES (m_CK, encrypted, TUNNEL_BUILD_RECORD_SIZE, clearText)) { LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); return false; @@ -196,15 +196,27 @@ namespace tunnel htobe32buf (clearText + SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET , 600); // +10 minutes htobe32buf (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); memset (clearText + SHORT_REQUEST_RECORD_PADDING_OFFSET, 0, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE - SHORT_REQUEST_RECORD_PADDING_OFFSET); - // TODO: derive layer and reply keys // encrypt EncryptECIES (clearText, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET); + // derive reply and layer key + i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelReplyKey", m_CK); + memcpy (replyKey, m_CK + 32, 32); + i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelLayerKey", m_CK); + memcpy (layerKey, m_CK + 32, 32); + if (isEndpoint) + { + i2p::crypto::HKDF (m_CK, nullptr, 0, "TunnelLayerIVKey", m_CK); + memcpy (ivKey, m_CK + 32, 32); + i2p::crypto::HKDF (m_CK, nullptr, 0, "RGarlicKeyAndTag", m_CK); // OTBRM garlic key m_CK + 32, tag first 8 bytes of m_CK + } + else + memcpy (ivKey, m_CK, 32); // last HKDF memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } bool ShortECIESTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) { - if (!DecryptECIES (encrypted, SHORT_TUNNEL_BUILD_RECORD_SIZE, clearText)) + if (!DecryptECIES (replyKey, encrypted, SHORT_TUNNEL_BUILD_RECORD_SIZE, clearText)) { LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); return false; diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 7d6456fd..d4c85558 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -59,7 +59,7 @@ namespace tunnel TunnelHopConfig (r) {}; bool IsECIES () const { return true; }; void EncryptECIES (const uint8_t * clearText, size_t len, uint8_t * encrypted); - bool DecryptECIES (const uint8_t * encrypted, size_t len, uint8_t * clearText); + bool DecryptECIES (const uint8_t * key, const uint8_t * encrypted, size_t len, uint8_t * clearText); }; struct LongECIESTunnelHopConfig: public ECIESTunnelHopConfig From 4255c4901de8199ffae805ff040cb0bf54d2ad82 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 6 Jul 2021 17:44:39 -0400 Subject: [PATCH 4318/6300] orignal's reseed ceritifcate --- contrib/certificates/{router => reseed}/orignal_at_mail.i2p.crt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contrib/certificates/{router => reseed}/orignal_at_mail.i2p.crt (100%) diff --git a/contrib/certificates/router/orignal_at_mail.i2p.crt b/contrib/certificates/reseed/orignal_at_mail.i2p.crt similarity index 100% rename from contrib/certificates/router/orignal_at_mail.i2p.crt rename to contrib/certificates/reseed/orignal_at_mail.i2p.crt From 431265a86af2979b37ef1a1cb679b7a462a5bf15 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 6 Jul 2021 18:22:08 -0400 Subject: [PATCH 4319/6300] update orignal's certificate --- .../reseed/orignal_at_mail.i2p.crt | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/contrib/certificates/reseed/orignal_at_mail.i2p.crt b/contrib/certificates/reseed/orignal_at_mail.i2p.crt index c1229f3b..799b601b 100644 --- a/contrib/certificates/reseed/orignal_at_mail.i2p.crt +++ b/contrib/certificates/reseed/orignal_at_mail.i2p.crt @@ -1,31 +1,32 @@ -----BEGIN CERTIFICATE----- -MIIFVDCCAzwCCQC2r1XWYtqtAzANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJY -WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMRMwEQYDVQQKDApQdXJwbGUgSTJQ -MQ0wCwYDVQQLDARJMlBEMR8wHQYJKoZIhvcNAQkBFhBvcmlnbmFsQG1haWwuaTJw -MB4XDTE1MDIyMjEzNTgxOFoXDTI1MDIxOTEzNTgxOFowbDELMAkGA1UEBhMCWFgx -CzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEGA1UECgwKUHVycGxlIEkyUDEN -MAsGA1UECwwESTJQRDEfMB0GCSqGSIb3DQEJARYQb3JpZ25hbEBtYWlsLmkycDCC -AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALp3D/gdvFjrMm+IE8tHZCWE -hQ6Pp0CCgCGDBC3WQFLqR98bqVPl4UwRG/MKY/LY7Woai06JNmGcpfw0LMoNnHxT -bvKtDRe/8kQdhdLHhgIkWKSbMvTAl7uUdV6FzsPgDR0x7scoFVWEhkF0wfmzGF2V -yr/WCBQejFPu69z03m5tRQ8Xjp2txWV45RawUmFu50bgbZvLCSLfTkIvxmfJzgPN -pJ3sPa/g7TBZl2uEiAu4uaEKvTuuzStOWCGgFaHYFVlTfFXTvmhFMqHfaidtzrlu -H35WGrmIWTDl6uGPC5QkSppvkj73rDj5aEyPzWMz5DN3YeECoVSchN+OJJCM6m7+ -rLFYXghVEp2h+T9O1GBRfcHlQ2E3CrWWvxhmK8dfteJmd501dyNX2paeuIg/aPFO -54/8m2r11uyF29hgY8VWLdXtqvwhKuK36PCzofEwDp9QQX8GRsEV4pZTrn4bDhGo -kb9BF7TZTqtL3uyiRmIyBXrNNiYlA1Xm4fyKRtxl0mrPaUXdgdnCt3KxOAJ8WM2B -7L/kk9U8C/nexHbMxIZfTap49XcUg5dxSO9kOBosIOcCUms8sAzBPDV2tWAByhYF -jI/Tutbd3F0+fvcmTcIFOlGbOxKgO2SfwXjv/44g/3LMK6IAMFB9UOc8KhnnJP0f -uAHvMXn1ahRs4pM1VizLAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAIOxdaXT+wfu -nv/+1hy5T4TlRMNNsuj79ROcy6Mp+JwMG50HjTc0qTlXh8C7nHybDJn4v7DA+Nyn -RxT0J5I+Gqn+Na9TaC9mLeX/lwe8/KomyhBWxjrsyWj1V6v/cLO924S2rtcfzMDm -l3SFh9YHM1KF/R9N1XYBwtMzr3bupWDnE1yycYp1F4sMLr5SMzMQ0svQpQEM2/y5 -kly8+eUzryhm+ag9x1686uEG5gxhQ1eHQoZEaClHUOsV+28+d5If7cqcYx9Hf5Tt -CiVjJQzdxBF+6GeiJtKxnLtevqlkbyIJt6Cm9/7YIy/ovRGF2AKSYN6oCwmZQ6i1 -8nRnFq5zE7O94m+GXconWZxy0wVqA6472HThMi7S+Tk/eLYen2ilGY+KCb9a0FH5 -5MOuWSoJZ8/HfW2VeQmL8EjhWm5F2ybg28wgXK4BOGR3jQi03Fsc+AFidnWxSKo0 -aiJoPgOsfyu8/fnCcAi07kSmjzUKIWskApgcpGQLNXHFK9mtg7+VA8esRnfLlKtP -tJf+nNAPY1sqHfGBzh7WWGWal5RGHF5nEm3ta3oiFF5sMKCJ6C87zVwFkEcRytGC -xOGmiG1O1RPrO5NG7rZUaQ4y1OKl2Y1H+nGONzZ3mvoAOvxEq6JtUnU2kZscpPlk -fpeOSDoGBYJGbIpzDreBDhxaZrwGq36k +MIIFfzCCA2egAwIBAgIEbNbRPjANBgkqhkiG9w0BAQ0FADBwMQswCQYDVQQGEwJY +WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMR4wHAYDVQQKDBVJMlAgQW5vbnlt +b3VzIE5ldHdvcmsxDDAKBgNVBAsMA0kyUDEZMBcGA1UEAwwQb3JpZ25hbEBtYWls +LmkycDAeFw0yMTA3MDYyMjExMDFaFw0zMTA3MDQyMjExMDFaMHAxCzAJBgNVBAYT +AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u +eW1vdXMgTmV0d29yazEMMAoGA1UECwwDSTJQMRkwFwYDVQQDDBBvcmlnbmFsQG1h +aWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvNJz2KGuAkHP +tGFobfLvpybtxB50fkcEsTc9opmiy7wBKK9rSI01VS616IhABkWKZVfK2A9NqpGv +v/CyhTKoaeSNeXY7+zORUWgWK/zA9fA4GRZFqlW8j4tbompDwcLYNqRBCsn1C0OY +YA5JhXPBixMcnXl8N8x4sXhQ4l9R3+QrydhUHRvgDc8dOxRyIX7zuQAyf8tmA2Xo +xZLdvDcCJdLBIbFwxhIceIhgcOwaOx7oRkZDZdYcLJd3zjyPbu8JtOM2ZkwH7r+0 +ro5PktuDp2LAS6SII5yYNcwcrvPZGPqhLdifIw1BrdTIb/rIkQZ5iXOOdyPmT7e8 +IwAJcPFlfvrS4Vbi9oDqyx3aDUBoubgmFnO1TirL56ck83R/ubcKtdnyzAn5dp+f +ZNYW6/foSBpDDOCViylbFAR5H0HJEbBns7PZx6mGEEI4tUAJdNYl7Ly7Df60a9Rz +cD/gz08U9UwFXYKoT6roEjToADGAzb5MI4cVlAb2AmQaMNXNe04HcDL1bU50mkNU +amqPv8nxf72fBQCEmZz2G57T6QiYTtcCwiWS1QdWsuaOtCo9zO0MKcjzSdUxuxEc +dXhjQdNegsgg/Xk7bJ8lKOsACqMpFftdPmuyeZU2t+3RPuBpV/0j2qUfg/y6kb0z +CxAOYmlcL4kqw4VT+5V/EeZLIG0h9I0CAwEAAaMhMB8wHQYDVR0OBBYEFD/wJObg +CCDuhMJCVWTSTj+B3rsUMA0GCSqGSIb3DQEBDQUAA4ICAQC0PjsTSPWlGbLNeeI8 +F0B5xAwXYJzZ7/LRxh8u42HDUqVIDjqkuls1l3v9D7htty2Gr3Ws2dcvcOr2KcOy +mEWg+jdP/N3vt9IkZeVS4YQoPgq6orn7lVkk00bcKb24f7ZnoQnnVV0/m42Y5P4j +LLh+8MBxsez9azXyZbDVEkgsMUAkdVO6KNz6scqz7wb8egV2GAMAp7cwChC6lanK +gv9ZyJhG/HdTv6VyuMZhJy6rX4geM97tm1iHu1VLsQcIzBKAdEvWJv8ofMeiyINe +hqAP9NYaeowKi975NOrmf+XZwxd0niApIohV684RCVUfL8H7HSPbdXhBJ/WslyDP +cTGhA2BLqEXZBn/nLQknlnl0SZTQxG2n4fEgD1E5YS/aoBrig/uXtWm2Zdf8U3mM ++bNXhbi9s7LneN2ye8LlNJBSRklNn/bNo8OmzLII1RQwf1+vaHT96lASbTVepMZ/ +Y9VcC8fAmho/zfQEKueLEB03K+gr2dGD+1crmMtUBjWJ9vPjtooZArtkDbh+kVYA +cx4N4NXULRwxVWZe5wTQOqcZ3qSS1ClMwaziwychGaj8xRAirHMZnlPOZO1UK4+5 +8F4RMJktyZjNgSLP76XPS4rJK5fobuPqFeA4OpDFn/5+/XeQFF6i6wntx1tzztzH +zc+BrVZOdcYPqu9iLXyRQ9JwwA== -----END CERTIFICATE----- From a6294df9e8bbfae39a71bd39fd3248699f8b41e4 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 6 Jul 2021 20:15:55 -0400 Subject: [PATCH 4320/6300] decrypt one-time message encrypted with tag on router --- libi2pd/Garlic.cpp | 37 +++++++++++++++++++++---------------- libi2pd/Garlic.h | 1 + libi2pd/RouterContext.cpp | 12 ++++++++---- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 2bf89556..38eda05a 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -494,22 +494,9 @@ namespace garlic buf += 4; // length bool found = false; - uint64_t tag; if (SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) - { // try ECIESx25519 tag - memcpy (&tag, buf, 8); - auto it1 = m_ECIESx25519Tags.find (tag); - if (it1 != m_ECIESx25519Tags.end ()) - { - found = true; - if (it1->second.tagset->HandleNextMessage (buf, length, it1->second.index)) - m_LastTagset = it1->second.tagset; - else - LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); - m_ECIESx25519Tags.erase (it1); - } - } + found = HandleECIESx25519TagMessage (buf, length); if (!found) { auto it = !mod ? m_Tags.find (SessionTag(buf)) : m_Tags.end (); // AES block is multiple of 16 @@ -555,6 +542,7 @@ namespace garlic // try to gererate more tags for last tagset if (m_LastTagset && (m_LastTagset->GetNextIndex () - m_LastTagset->GetTrimBehind () < 3*ECIESX25519_MAX_NUM_GENERATED_TAGS)) { + uint64_t missingTag; memcpy (&missingTag, buf, 8); auto maxTags = std::max (m_NumRatchetInboundTags, ECIESX25519_MAX_NUM_GENERATED_TAGS); LogPrint (eLogWarning, "Garlic: trying to generate more ECIES-X25519-AEAD-Ratchet tags"); for (int i = 0; i < maxTags; i++) @@ -565,10 +553,10 @@ namespace garlic LogPrint (eLogError, "Garlic: can't create new ECIES-X25519-AEAD-Ratchet tag for last tagset"); break; } - if (nextTag == tag) + if (nextTag == missingTag) { LogPrint (eLogDebug, "Garlic: Missing ECIES-X25519-AEAD-Ratchet tag was generated"); - if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[tag].index)) + if (m_LastTagset->HandleNextMessage (buf, length, m_ECIESx25519Tags[nextTag].index)) found = true; break; } @@ -585,6 +573,23 @@ namespace garlic } } + bool GarlicDestination::HandleECIESx25519TagMessage (uint8_t * buf, size_t len) + { + uint64_t tag; + memcpy (&tag, buf, 8); + auto it = m_ECIESx25519Tags.find (tag); + if (it != m_ECIESx25519Tags.end ()) + { + if (it->second.tagset->HandleNextMessage (buf, len, it->second.index)) + m_LastTagset = it->second.tagset; + else + LogPrint (eLogError, "Garlic: can't handle ECIES-X25519-AEAD-Ratchet message"); + m_ECIESx25519Tags.erase (it); + return true; + } + return false; + } + void GarlicDestination::HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr decryption, std::shared_ptr from) { diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 4288e74b..a898e6a1 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -260,6 +260,7 @@ namespace garlic protected: + bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) = 0; void HandleGarlicMessage (std::shared_ptr msg); diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index b2a19890..c25f5064 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -843,10 +843,14 @@ namespace i2p return; } buf += 4; - if (m_ECIESSession) - m_ECIESSession->HandleNextMessage (buf, len); - else - LogPrint (eLogError, "Router: Session is not set for ECIES router"); + if (!HandleECIESx25519TagMessage (buf, len)) // try tag first + { + // then Noise_N one-time decryption + if (m_ECIESSession) + m_ECIESSession->HandleNextMessage (buf, len); + else + LogPrint (eLogError, "Router: Session is not set for ECIES router"); + } } else i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); From 847225c6bfa0d8e83ff27606104805b2870b93ad Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 7 Jul 2021 08:24:01 -0400 Subject: [PATCH 4321/6300] more yggdrasil reseeds added --- libi2pd/Config.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index de294357..a8516e00 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -213,7 +213,9 @@ namespace config { "https://i2p.novg.net/" ), "Reseed URLs, separated by comma") ("reseed.yggurls", value()->default_value( - "http://[324:71e:281a:9ed3::ace]:7070/" + "http://[324:71e:281a:9ed3::ace]:7070/," + "http://[301:65b9:c7cd:9a36::1]:18801/," + "http://[320:8936:ec1a:31f1::216]/" ), "Reseed URLs through the Yggdrasil, separated by comma") ; From ed0c2e68a5e45f06caf5d394ff240129967c68ae Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 7 Jul 2021 21:16:30 -0400 Subject: [PATCH 4322/6300] DecryptRecord per tunnel hop --- libi2pd/Tunnel.cpp | 12 ++---------- libi2pd/TunnelConfig.cpp | 37 +++++++++++++++++++++++++++++-------- libi2pd/TunnelConfig.h | 19 ++++++++++--------- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 7e154cbf..84df9b56 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -78,18 +78,14 @@ namespace tunnel } // decrypt real records - i2p::crypto::CBCDecryption decryption; hop = m_Config->GetLastHop ()->prev; while (hop) { - decryption.SetKey (hop->replyKey); // decrypt records after current hop TunnelHopConfig * hop1 = hop->next; while (hop1) { - decryption.SetIV (hop->replyIV); - uint8_t * record = records + hop1->recordIndex*TUNNEL_BUILD_RECORD_SIZE; - decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); + hop->DecryptRecord (records, hop1->recordIndex); hop1 = hop1->next; } hop = hop->prev; @@ -132,11 +128,7 @@ namespace tunnel { auto idx = hop1->recordIndex; if (idx >= 0 && idx < msg[0]) - { - uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE; - decryption.SetIV (hop->replyIV); - decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); - } + hop->DecryptRecord (msg + 1, idx); else LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); hop1 = hop1->prev; diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index cc612c00..1680a0b1 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -77,6 +77,15 @@ namespace tunnel isGateway = false; } } + + void TunnelHopConfig::DecryptRecord (uint8_t * records, int index) const + { + uint8_t * record = records + index*TUNNEL_BUILD_RECORD_SIZE; + i2p::crypto::CBCDecryption decryption; + decryption.SetKey (replyKey); + decryption.SetIV (replyIV); + decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); + } void ElGamalTunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) { @@ -104,7 +113,7 @@ namespace tunnel memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } - bool ElGamalTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) + bool ElGamalTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const { i2p::crypto::CBCDecryption decryption; decryption.SetKey (replyKey); @@ -137,10 +146,8 @@ namespace tunnel MixHash (encrypted, len + 16); // h = SHA256(h || ciphertext) } - bool ECIESTunnelHopConfig::DecryptECIES (const uint8_t * key, const uint8_t * encrypted, size_t len, uint8_t * clearText) + bool ECIESTunnelHopConfig::DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const { - uint8_t nonce[12]; - memset (nonce, 0, 12); return i2p::crypto::AEADChaCha20Poly1305 (encrypted, len - 16, m_H, 32, key, nonce, clearText, len - 16, false); // decrypt } @@ -169,9 +176,11 @@ namespace tunnel memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } - bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) + bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const { - if (!DecryptECIES (m_CK, encrypted, TUNNEL_BUILD_RECORD_SIZE, clearText)) + uint8_t nonce[12]; + memset (nonce, 0, 12); + if (!DecryptECIES (m_CK, nonce, encrypted, TUNNEL_BUILD_RECORD_SIZE, clearText)) { LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); return false; @@ -214,14 +223,26 @@ namespace tunnel memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } - bool ShortECIESTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) + bool ShortECIESTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const { - if (!DecryptECIES (replyKey, encrypted, SHORT_TUNNEL_BUILD_RECORD_SIZE, clearText)) + uint8_t nonce[12]; + memset (nonce, 0, 12); + nonce[4] = recordIndex; // nonce is record index + if (!DecryptECIES (replyKey, nonce, encrypted, SHORT_TUNNEL_BUILD_RECORD_SIZE, clearText)) { LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); return false; } return true; } + + void ShortECIESTunnelHopConfig::DecryptRecord (uint8_t * records, int index) const + { + uint8_t * record = records + index*SHORT_TUNNEL_BUILD_RECORD_SIZE; + uint8_t nonce[12]; + memset (nonce, 0, 12); + nonce[4] = index; // nonce is index + i2p::crypto::ChaCha20 (record, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, record); + } } } \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index d4c85558..e356a9f0 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -42,7 +42,8 @@ namespace tunnel virtual bool IsECIES () const { return false; }; virtual void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) = 0; - virtual bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) = 0; + virtual bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const = 0; + virtual void DecryptRecord (uint8_t * records, int index) const; // AES }; struct ElGamalTunnelHopConfig: public TunnelHopConfig @@ -50,7 +51,7 @@ namespace tunnel ElGamalTunnelHopConfig (std::shared_ptr r): TunnelHopConfig (r) {}; void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); - bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText); + bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const; }; struct ECIESTunnelHopConfig: public TunnelHopConfig, public i2p::crypto::NoiseSymmetricState @@ -59,7 +60,7 @@ namespace tunnel TunnelHopConfig (r) {}; bool IsECIES () const { return true; }; void EncryptECIES (const uint8_t * clearText, size_t len, uint8_t * encrypted); - bool DecryptECIES (const uint8_t * key, const uint8_t * encrypted, size_t len, uint8_t * clearText); + bool DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const; }; struct LongECIESTunnelHopConfig: public ECIESTunnelHopConfig @@ -67,7 +68,7 @@ namespace tunnel LongECIESTunnelHopConfig (std::shared_ptr r): ECIESTunnelHopConfig (r) {}; void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); - bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText); + bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const; }; struct ShortECIESTunnelHopConfig: public ECIESTunnelHopConfig @@ -75,20 +76,21 @@ namespace tunnel ShortECIESTunnelHopConfig (std::shared_ptr r): ECIESTunnelHopConfig (r) {}; void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); - bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText); + bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const; + void DecryptRecord (uint8_t * records, int index) const override; // Chacha20 }; class TunnelConfig { public: - TunnelConfig (std::vector > peers) // inbound + TunnelConfig (const std::vector >& peers) // inbound { CreatePeers (peers); m_LastHop->SetNextIdent (i2p::context.GetIdentHash ()); } - TunnelConfig (std::vector > peers, + TunnelConfig (const std::vector >& peers, uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) // outbound { CreatePeers (peers); @@ -180,8 +182,7 @@ namespace tunnel private: - template - void CreatePeers (const Peers& peers) + void CreatePeers (const std::vector >& peers) { TunnelHopConfig * prev = nullptr; for (const auto& it: peers) From d73b42b7266233ae399cd54a949c382f755803e0 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Jul 2021 16:39:38 -0400 Subject: [PATCH 4323/6300] extract ret code per hop --- libi2pd/Tunnel.cpp | 13 ++++--------- libi2pd/TunnelConfig.cpp | 15 +++++++++------ libi2pd/TunnelConfig.h | 17 +++++++++++------ 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 84df9b56..fe219214 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -103,26 +103,22 @@ namespace tunnel { LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); - i2p::crypto::CBCDecryption decryption; TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) { // decrypt current hop - auto idx = hop->recordIndex; - if (idx >= 0 && idx < msg[0]) + if (hop->recordIndex >= 0 && hop->recordIndex < msg[0]) { - uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE; - if (!hop->DecryptBuildResponseRecord (record, record)) + if (!hop->DecryptBuildResponseRecord (msg + 1)) return false; } else { - LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); + LogPrint (eLogWarning, "Tunnel: hop index ", hop->recordIndex, " is out of range"); return false; } // decrypt records before current hop - decryption.SetKey (hop->replyKey); TunnelHopConfig * hop1 = hop->prev; while (hop1) { @@ -140,8 +136,7 @@ namespace tunnel hop = m_Config->GetFirstHop (); while (hop) { - const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE; - uint8_t ret = record[hop->IsECIES () ? ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET : BUILD_RESPONSE_RECORD_RET_OFFSET]; + uint8_t ret = hop->GetRetCode (msg + 1); LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); if (profile) diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 1680a0b1..b461a3d5 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -113,12 +113,13 @@ namespace tunnel memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } - bool ElGamalTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const + bool ElGamalTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const { + uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; i2p::crypto::CBCDecryption decryption; decryption.SetKey (replyKey); decryption.SetIV (replyIV); - decryption.Decrypt (encrypted, TUNNEL_BUILD_RECORD_SIZE, clearText); + decryption.Decrypt (record, TUNNEL_BUILD_RECORD_SIZE, record); return true; } @@ -176,11 +177,12 @@ namespace tunnel memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } - bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const + bool LongECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const { + uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; uint8_t nonce[12]; memset (nonce, 0, 12); - if (!DecryptECIES (m_CK, nonce, encrypted, TUNNEL_BUILD_RECORD_SIZE, clearText)) + if (!DecryptECIES (m_CK, nonce, record, TUNNEL_BUILD_RECORD_SIZE, record)) { LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); return false; @@ -223,12 +225,13 @@ namespace tunnel memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } - bool ShortECIESTunnelHopConfig::DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const + bool ShortECIESTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const { + uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; uint8_t nonce[12]; memset (nonce, 0, 12); nonce[4] = recordIndex; // nonce is record index - if (!DecryptECIES (replyKey, nonce, encrypted, SHORT_TUNNEL_BUILD_RECORD_SIZE, clearText)) + if (!DecryptECIES (replyKey, nonce, record, SHORT_TUNNEL_BUILD_RECORD_SIZE, record)) { LogPrint (eLogWarning, "Tunnel: Response AEAD decryption failed"); return false; diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index e356a9f0..b2bf26b7 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -40,9 +40,9 @@ namespace tunnel void SetNext (TunnelHopConfig * n); void SetPrev (TunnelHopConfig * p); - virtual bool IsECIES () const { return false; }; + virtual uint8_t GetRetCode (const uint8_t * records) const = 0; virtual void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) = 0; - virtual bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const = 0; + virtual bool DecryptBuildResponseRecord (uint8_t * records) const = 0; virtual void DecryptRecord (uint8_t * records, int index) const; // AES }; @@ -50,15 +50,16 @@ namespace tunnel { ElGamalTunnelHopConfig (std::shared_ptr r): TunnelHopConfig (r) {}; + uint8_t GetRetCode (const uint8_t * records) const + { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[BUILD_RESPONSE_RECORD_RET_OFFSET]; }; void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); - bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const; + bool DecryptBuildResponseRecord (uint8_t * records) const; }; struct ECIESTunnelHopConfig: public TunnelHopConfig, public i2p::crypto::NoiseSymmetricState { ECIESTunnelHopConfig (std::shared_ptr r): TunnelHopConfig (r) {}; - bool IsECIES () const { return true; }; void EncryptECIES (const uint8_t * clearText, size_t len, uint8_t * encrypted); bool DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const; }; @@ -67,16 +68,20 @@ namespace tunnel { LongECIESTunnelHopConfig (std::shared_ptr r): ECIESTunnelHopConfig (r) {}; + uint8_t GetRetCode (const uint8_t * records) const + { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; }; void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); - bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const; + bool DecryptBuildResponseRecord (uint8_t * records) const; }; struct ShortECIESTunnelHopConfig: public ECIESTunnelHopConfig { ShortECIESTunnelHopConfig (std::shared_ptr r): ECIESTunnelHopConfig (r) {}; + uint8_t GetRetCode (const uint8_t * records) const + { return (records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; }; // TODO void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); - bool DecryptBuildResponseRecord (const uint8_t * encrypted, uint8_t * clearText) const; + bool DecryptBuildResponseRecord (uint8_t * records) const; void DecryptRecord (uint8_t * records, int index) const override; // Chacha20 }; From 84f6024cc92182793ea974fccf791cae1dcde799 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Jul 2021 19:00:25 -0400 Subject: [PATCH 4324/6300] locate record to build inside CreateBuildRequestRecord --- libi2pd/Tunnel.cpp | 8 ++------ libi2pd/TunnelConfig.cpp | 13 ++++++++++--- libi2pd/TunnelConfig.h | 8 ++++---- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index fe219214..1eb30db2 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -55,7 +55,6 @@ namespace tunnel uint8_t * records = msg->GetPayload () + 1; TunnelHopConfig * hop = m_Config->GetFirstHop (); int i = 0; - BN_CTX * ctx = BN_CTX_new (); while (hop) { uint32_t msgID; @@ -63,13 +62,10 @@ namespace tunnel RAND_bytes ((uint8_t *)&msgID, 4); else msgID = replyMsgID; - int idx = recordIndicies[i]; - hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, msgID, ctx); - hop->recordIndex = idx; - i++; + hop->recordIndex = recordIndicies[i]; i++; + hop->CreateBuildRequestRecord (records, msgID); hop = hop->next; } - BN_CTX_free (ctx); // fill up fake records with random data for (int i = numHops; i < numRecords; i++) { diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index b461a3d5..948ffc01 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -87,7 +87,7 @@ namespace tunnel decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); } - void ElGamalTunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) + void ElGamalTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) { // fill clear text uint8_t flag = 0; @@ -107,9 +107,14 @@ namespace tunnel htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); // encrypt + uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; auto encryptor = ident->CreateEncryptor (nullptr); if (encryptor) + { + BN_CTX * ctx = BN_CTX_new (); encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); + BN_CTX_free (ctx); + } memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } @@ -152,7 +157,7 @@ namespace tunnel return i2p::crypto::AEADChaCha20Poly1305 (encrypted, len - 16, m_H, 32, key, nonce, clearText, len - 16, false); // decrypt } - void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) + void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) { // fill clear text uint8_t flag = 0; @@ -173,6 +178,7 @@ namespace tunnel htobe32buf (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); memset (clearText + ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET, 0, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - ECIES_BUILD_REQUEST_RECORD_PADDING_OFFSET); // encrypt + uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; EncryptECIES (clearText, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET); memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } @@ -190,7 +196,7 @@ namespace tunnel return true; } - void ShortECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) + void ShortECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) { // fill clear text uint8_t flag = 0; @@ -208,6 +214,7 @@ namespace tunnel htobe32buf (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); memset (clearText + SHORT_REQUEST_RECORD_PADDING_OFFSET, 0, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE - SHORT_REQUEST_RECORD_PADDING_OFFSET); // encrypt + uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; EncryptECIES (clearText, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET); // derive reply and layer key i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelReplyKey", m_CK); diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index b2bf26b7..591fed52 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -41,7 +41,7 @@ namespace tunnel void SetPrev (TunnelHopConfig * p); virtual uint8_t GetRetCode (const uint8_t * records) const = 0; - virtual void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) = 0; + virtual void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) = 0; virtual bool DecryptBuildResponseRecord (uint8_t * records) const = 0; virtual void DecryptRecord (uint8_t * records, int index) const; // AES }; @@ -52,7 +52,7 @@ namespace tunnel TunnelHopConfig (r) {}; uint8_t GetRetCode (const uint8_t * records) const { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[BUILD_RESPONSE_RECORD_RET_OFFSET]; }; - void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); + void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); bool DecryptBuildResponseRecord (uint8_t * records) const; }; @@ -70,7 +70,7 @@ namespace tunnel ECIESTunnelHopConfig (r) {}; uint8_t GetRetCode (const uint8_t * records) const { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; }; - void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); + void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); bool DecryptBuildResponseRecord (uint8_t * records) const; }; @@ -80,7 +80,7 @@ namespace tunnel ECIESTunnelHopConfig (r) {}; uint8_t GetRetCode (const uint8_t * records) const { return (records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; }; // TODO - void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx); + void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); bool DecryptBuildResponseRecord (uint8_t * records) const; void DecryptRecord (uint8_t * records, int index) const override; // Chacha20 }; From c02a0c4da9f417c9958812ebd682f1abdf443f59 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 8 Jul 2021 22:22:00 -0400 Subject: [PATCH 4325/6300] process DELAY_REQUESTED option --- libi2pd/Streaming.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 3269e040..bbb649df 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -276,7 +276,20 @@ namespace stream const uint8_t * optionData = packet->GetOptionData (); size_t optionSize = packet->GetOptionSize (); if (flags & PACKET_FLAG_DELAY_REQUESTED) + { + if (!m_IsAckSendScheduled) + { + uint16_t delayRequested = bufbe16toh (optionData); + if (delayRequested > 0 && delayRequested < m_RTT) + { + m_IsAckSendScheduled = true; + m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(delayRequested)); + m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, + shared_from_this (), std::placeholders::_1)); + } + } optionData += 2; + } if (flags & PACKET_FLAG_FROM_INCLUDED) { @@ -793,7 +806,7 @@ namespace stream if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) { std::vector msgs; - for (auto it: packets) + for (const auto& it: packets) { auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage ( it->GetBuffer (), it->GetLength (), m_Port, !m_RoutingSession->IsRatchets ())); From 59dd60f5cbc458830d4f7eec30f3e4251f95347b Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 9 Jul 2021 19:24:28 -0400 Subject: [PATCH 4326/6300] genarate keys in CreateBuildRequestRecord --- libi2pd/TunnelConfig.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 948ffc01..930c565b 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -23,10 +23,6 @@ namespace tunnel { TunnelHopConfig::TunnelHopConfig (std::shared_ptr r) { - RAND_bytes (layerKey, 32); - RAND_bytes (ivKey, 32); - RAND_bytes (replyKey, 32); - RAND_bytes (replyIV, 16); RAND_bytes ((uint8_t *)&tunnelID, 4); if (!tunnelID) tunnelID = 1; // tunnelID can't be zero isGateway = true; @@ -89,6 +85,11 @@ namespace tunnel void ElGamalTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) { + // generate keys + RAND_bytes (layerKey, 32); + RAND_bytes (ivKey, 32); + RAND_bytes (replyKey, 32); + RAND_bytes (replyIV, 16); // fill clear text uint8_t flag = 0; if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; @@ -159,6 +160,11 @@ namespace tunnel void LongECIESTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) { + // generate keys + RAND_bytes (layerKey, 32); + RAND_bytes (ivKey, 32); + RAND_bytes (replyKey, 32); + RAND_bytes (replyIV, 16); // fill clear text uint8_t flag = 0; if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; @@ -216,7 +222,7 @@ namespace tunnel // encrypt uint8_t * record = records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE; EncryptECIES (clearText, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE, record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET); - // derive reply and layer key + // derive keys i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelReplyKey", m_CK); memcpy (replyKey, m_CK + 32, 32); i2p::crypto::HKDF (m_CK, nullptr, 0, "SMTunnelLayerKey", m_CK); From d47bf1bada8621e0ac084bef8639a609f99a83b3 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 9 Jul 2021 19:26:14 -0400 Subject: [PATCH 4327/6300] different tunnel build record size --- libi2pd/Tunnel.cpp | 7 ++++--- libi2pd/TunnelConfig.h | 14 +++++++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 1eb30db2..34105962 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -42,10 +42,11 @@ namespace tunnel void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { auto numHops = m_Config->GetNumHops (); - int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; + const int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : MAX_NUM_RECORDS; auto msg = numRecords <= STANDARD_NUM_RECORDS ? NewI2NPShortMessage () : NewI2NPMessage (); *msg->GetPayload () = numRecords; - msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1; + const size_t recordSize = m_Config->IsShort () ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; + msg->len += numRecords*recordSize + 1; // shuffle records std::vector recordIndicies; for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i); @@ -70,7 +71,7 @@ namespace tunnel for (int i = numHops; i < numRecords; i++) { int idx = recordIndicies[i]; - RAND_bytes (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE); + RAND_bytes (records + idx*recordSize, recordSize); } // decrypt real records diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 591fed52..b33f7668 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -115,6 +115,8 @@ namespace tunnel } } + bool IsShort () const { return m_IsShort; } + TunnelHopConfig * GetFirstHop () const { return m_FirstHop; @@ -193,10 +195,15 @@ namespace tunnel for (const auto& it: peers) { TunnelHopConfig * hop; - if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - hop = new LongECIESTunnelHopConfig (it); + if (m_IsShort) + hop = new ShortECIESTunnelHopConfig (it); else - hop = new ElGamalTunnelHopConfig (it); + { + if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + hop = new LongECIESTunnelHopConfig (it); + else + hop = new ElGamalTunnelHopConfig (it); + } if (prev) prev->SetNext (hop); else @@ -209,6 +216,7 @@ namespace tunnel private: TunnelHopConfig * m_FirstHop, * m_LastHop; + bool m_IsShort = false; }; class ZeroHopsTunnelConfig: public TunnelConfig From 1e9eb30aa32b73e7a15929ecaf72e873926830ce Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 10 Jul 2021 14:33:23 -0400 Subject: [PATCH 4328/6300] garlic encryption of inbound tunnel build message --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 9 ++++++++- libi2pd/ECIESX25519AEADRatchetSession.h | 3 ++- libi2pd/I2NPProtocol.cpp | 2 +- libi2pd/NetDb.cpp | 2 +- libi2pd/Tunnel.cpp | 6 ++++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 55e962d8..9a76ea3c 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1133,7 +1133,7 @@ namespace garlic return true; } - std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag) + std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag) { auto m = NewI2NPMessage (); m->Align (12); // in order to get buf aligned to 16 (12 + 4) @@ -1167,5 +1167,12 @@ namespace garlic return m; } + std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey) + { + // TODO: implement without session + auto session = std::make_shared(nullptr, false); + session->SetRemoteStaticKey (routerPublicKey); + return session->WrapOneTimeMessage (msg, true); + } } } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index cd7b9b40..71e0a803 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -256,7 +256,8 @@ namespace garlic i2p::crypto::NoiseSymmetricState m_CurrentNoiseState; }; - std::shared_ptr WrapECIESX25519AEADRatchetMessage (std::shared_ptr msg, const uint8_t * key, uint64_t tag); + std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag); + std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey); } } diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 6034e5f4..b9b15c8d 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -675,7 +675,7 @@ namespace i2p // send garlic to reply tunnel transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - i2p::garlic::WrapECIESX25519AEADRatchetMessage (otbrm, noiseState.m_CK + 32, tag))); + i2p::garlic::WrapECIESX25519Message (otbrm, noiseState.m_CK + 32, tag))); } else { diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 5e93fabb..e3a55832 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -988,7 +988,7 @@ namespace data { uint64_t tag; memcpy (&tag, excluded + 33, 8); - replyMsg = i2p::garlic::WrapECIESX25519AEADRatchetMessage (replyMsg, sessionKey, tag); + replyMsg = i2p::garlic::WrapECIESX25519Message (replyMsg, sessionKey, tag); } else { diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 34105962..63f90077 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -23,6 +23,7 @@ #include "Tunnel.h" #include "TunnelPool.h" #include "util.h" +#include "ECIESX25519AEADRatchetSession.h" namespace i2p { @@ -91,7 +92,12 @@ namespace tunnel // send message if (outboundTunnel) + { + auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr; + if (ident && ident->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + msg = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); + } else i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); } From ba1b8c7c2b57f84ccc7fe9011688b64a5437406b Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 10 Jul 2021 16:15:15 -0400 Subject: [PATCH 4329/6300] WrapECIESX25519MessageForRouter wihout session --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 108 +++++++++++----------- libi2pd/ECIESX25519AEADRatchetSession.h | 8 +- 2 files changed, 56 insertions(+), 60 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 9a76ea3c..09bbfe53 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -518,36 +518,7 @@ namespace garlic } return true; } - - bool ECIESX25519AEADRatchetSession::NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) - { - // we are Alice, router's bpk is m_RemoteStaticKey - i2p::crypto::InitNoiseNState (GetNoiseState (), m_RemoteStaticKey); - size_t offset = 0; - m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); - memcpy (out + offset, m_EphemeralKeys->GetPublicKey (), 32); - MixHash (out + offset, 32); // h = SHA256(h || aepk) - offset += 32; - uint8_t sharedSecret[32]; - if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk) - { - LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); - return false; - } - MixKey (sharedSecret); - uint8_t nonce[12]; - CreateNonce (0, nonce); - // encrypt payload - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt - { - LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); - return false; - } - - m_State = eSessionStateNewSessionSent; - return true; - } - + bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob @@ -858,11 +829,6 @@ namespace garlic return nullptr; len += 96; break; - case eSessionStateForRouter: - if (!NewOutgoingMessageForRouter (payload.data (), payload.size (), buf, m->maxLen)) - return nullptr; - len += 48; - break; default: return nullptr; } @@ -873,9 +839,9 @@ namespace garlic return m; } - std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter) + std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg) { - m_State = isForRouter ? eSessionStateForRouter : eSessionStateOneTime; + m_State = eSessionStateOneTime; return WrapSingleMessage (msg); } @@ -1132,19 +1098,10 @@ namespace garlic HandlePayload (payload.data (), len - 16, nullptr, 0); return true; } - - std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag) + + static size_t CreateGarlicCloveBlock (std::shared_ptr msg, uint8_t * payload) { - auto m = NewI2NPMessage (); - m->Align (12); // in order to get buf aligned to 16 (12 + 4) - uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length - uint8_t nonce[12]; - memset (nonce, 0, 12); // n = 0 - size_t offset = 0; - memcpy (buf + offset, &tag, 8); offset += 8; - auto payload = buf + offset; uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; - size_t len = cloveSize + 3; payload[0] = eECIESx25519BlkGalicClove; // clove type htobe16buf (payload + 1, cloveSize); // size payload += 3; @@ -1153,14 +1110,26 @@ namespace garlic htobe32buf (payload + 1, msg->GetMsgID ()); // msgID htobe32buf (payload + 5, msg->GetExpiration () / 1000); // expiration in seconds memcpy (payload + 9, msg->GetPayload (), msg->GetPayloadLength ()); - - if (!i2p::crypto::AEADChaCha20Poly1305 (buf + offset, len, buf, 8, key, nonce, buf + offset, len + 16, true)) // encrypt + return cloveSize + 3; + } + + std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag) + { + auto m = NewI2NPMessage (); + m->Align (12); // in order to get buf aligned to 16 (12 + 4) + uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length + size_t offset = 0; + memcpy (buf + offset, &tag, 8); offset += 8; + auto payload = buf + offset; + size_t len = CreateGarlicCloveBlock (msg, payload); + uint8_t nonce[12]; + memset (nonce, 0, 12); // n = 0 + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, buf, 8, key, nonce, payload, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return nullptr; } offset += len + 16; - htobe32buf (m->GetPayload (), offset); m->len += offset + 4; m->FillI2NPMessageHeader (eI2NPGarlic); @@ -1169,10 +1138,39 @@ namespace garlic std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey) { - // TODO: implement without session - auto session = std::make_shared(nullptr, false); - session->SetRemoteStaticKey (routerPublicKey); - return session->WrapOneTimeMessage (msg, true); + // Noise_N, we are Alice, routerPublicKey is Bob's + i2p::crypto::NoiseSymmetricState noiseState; + i2p::crypto::InitNoiseNState (noiseState, routerPublicKey); + auto m = NewI2NPMessage (); + m->Align (12); // in order to get buf aligned to 16 (12 + 4) + uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length + size_t offset = 0; + auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + memcpy (buf + offset, ephemeralKeys->GetPublicKey (), 32); + noiseState.MixHash (buf + offset, 32); // h = SHA256(h || aepk) + offset += 32; + uint8_t sharedSecret[32]; + if (!ephemeralKeys->Agree (routerPublicKey, sharedSecret)) // x25519(aesk, bpk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); + return nullptr; + } + noiseState.MixKey (sharedSecret); + auto payload = buf + offset; + size_t len = CreateGarlicCloveBlock (msg, payload); + uint8_t nonce[12]; + memset (nonce, 0, 12); + // encrypt payload + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, noiseState.m_H, 32, noiseState.m_CK + 32, nonce, payload, len + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); + return nullptr; + } + offset += len + 16; + htobe32buf (m->GetPayload (), offset); + m->len += offset + 4; + m->FillI2NPMessageHeader (eI2NPGarlic); + return m; } } } diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 71e0a803..762e00ef 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -147,8 +147,7 @@ namespace garlic eSessionStateNewSessionSent, eSessionStateNewSessionReplySent, eSessionStateEstablished, - eSessionStateOneTime, - eSessionStateForRouter + eSessionStateOneTime }; struct DHRatchet @@ -166,7 +165,7 @@ namespace garlic bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter = false); + std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg); const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } @@ -207,8 +206,7 @@ namespace garlic bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - bool NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - + std::vector CreatePayload (std::shared_ptr msg, bool first); size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len); size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); From 6a467a09bd6be0800478fa68ca6fcb3c001c505e Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 10 Jul 2021 16:47:28 -0400 Subject: [PATCH 4330/6300] fixed build error --- libi2pd/Garlic.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 38eda05a..d91c75f5 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -754,11 +754,7 @@ namespace garlic std::shared_ptr msg) { if (router->GetEncryptionType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - { - auto session = std::make_shared(this, false); - session->SetRemoteStaticKey (router->GetIdentity ()->GetEncryptionPublicKey ()); - return session->WrapOneTimeMessage (msg, true); - } + return WrapECIESX25519MessageForRouter (msg, router->GetIdentity ()->GetEncryptionPublicKey ()); else { auto session = GetRoutingSession (router, false); From 15c3d46492cc9b04e51a210bce554b9fe40b12d0 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 10 Jul 2021 17:28:18 -0400 Subject: [PATCH 4331/6300] encrypt inbound tunnel build message for short tunnel build only --- libi2pd/Tunnel.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 63f90077..6c9d59e9 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -93,9 +93,15 @@ namespace tunnel // send message if (outboundTunnel) { - auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr; - if (ident && ident->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - msg = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); + if (m_Config->IsShort ()) + { + auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr; + if (ident) + { + auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); + if (msg1) msg = msg; + } + } outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); } else From 3e281d47907234110569083c4b23b24e53e1fa47 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 11 Jul 2021 23:10:53 +0300 Subject: [PATCH 4332/6300] Update README.md --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 37c4553a..6f2d23ec 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![License](https://img.shields.io/github/license/PurpleI2P/i2pd.svg)](https://github.com/PurpleI2P/i2pd/blob/openssl/LICENSE) [![Packaging status](https://repology.org/badge/tiny-repos/i2pd.svg)](https://repology.org/project/i2pd/versions) [![Docker Pulls](https://img.shields.io/docker/pulls/purplei2p/i2pd)](https://hub.docker.com/r/purplei2p/i2pd) +[![Crowdin](https://badges.crowdin.net/i2pd/localized.svg)](https://crowdin.com/project/i2pd) *note: i2pd for Android can be found in [i2pd-android](https://github.com/PurpleI2P/i2pd-android) repository and with Qt GUI in [i2pd-qt](https://github.com/PurpleI2P/i2pd-qt) repository* @@ -85,6 +86,16 @@ Using i2pd See [documentation](https://i2pd.readthedocs.io/en/latest/user-guide/run/) and [example config file](https://github.com/PurpleI2P/i2pd/blob/openssl/contrib/i2pd.conf). +Localization +------------ + +You can help us with translation i2pd to your language using Crowdin platform! +Translation project can be found [here](https://crowdin.com/project/i2pd). + +New languages can be requested on project's [discussion page](https://crowdin.com/project/i2pd/discussions). + +Current status: [![Crowdin](https://badges.crowdin.net/i2pd/localized.svg)](https://crowdin.com/project/i2pd) + Donations --------- From dbe427d5eb43c4a5abb5d7c43be10048c39788ca Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 11 Jul 2021 19:29:16 -0400 Subject: [PATCH 4333/6300] set reply code for short tunnel build messages --- libi2pd/I2NPProtocol.cpp | 22 ++++++++++++++++++++-- libi2pd/I2NPProtocol.h | 4 ++++ libi2pd/TunnelConfig.h | 2 +- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index b9b15c8d..6ffa1551 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -608,7 +608,24 @@ namespace i2p LogPrint (eLogError, "I2NP: ShortTunnelBuild message of ", num, " records is too short ", len); return; } - // TODO: check replyMsgID + auto tunnel = i2p::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID); + if (tunnel) + { + // endpoint of inbound tunnel + LogPrint (eLogDebug, "I2NP: ShortTunnelBuild reply for tunnel ", tunnel->GetTunnelID ()); + if (tunnel->HandleTunnelBuildResponse (buf, len)) + { + LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created"); + tunnel->SetState (i2p::tunnel::eTunnelStateEstablished); + i2p::tunnel::tunnels.AddInboundTunnel (tunnel); + } + else + { + LogPrint (eLogInfo, "I2NP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined"); + tunnel->SetState (i2p::tunnel::eTunnelStateBuildFailed); + } + return; + } const uint8_t * record = buf + 1; for (int i = 0; i < num; i++) { @@ -688,7 +705,8 @@ namespace i2p nonce[4] = j; // nonce is record # if (j == i) { - // TODO: fill reply + memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = 0; // TODO: correct ret code if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt { diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 8cb68856..e20a0c0d 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -115,6 +115,10 @@ namespace i2p const size_t SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET = SHORT_REQUEST_RECORD_REQUEST_EXPIRATION_OFFSET + 4; const size_t SHORT_REQUEST_RECORD_PADDING_OFFSET = SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; const size_t SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE = 154; + + // ShortResponseRecord + const size_t SHORT_RESPONSE_RECORD_OPTIONS_OFFSET = 0; + const size_t SHORT_RESPONSE_RECORD_RET_OFFSET = 201; enum I2NPMessageType { diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index b33f7668..306d8a72 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -79,7 +79,7 @@ namespace tunnel ShortECIESTunnelHopConfig (std::shared_ptr r): ECIESTunnelHopConfig (r) {}; uint8_t GetRetCode (const uint8_t * records) const - { return (records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; }; // TODO + { return (records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE)[SHORT_RESPONSE_RECORD_RET_OFFSET]; }; void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); bool DecryptBuildResponseRecord (uint8_t * records) const; void DecryptRecord (uint8_t * records, int index) const override; // Chacha20 From 2c129b6d39607f84a8372f3d731d232f342f1b22 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 12 Jul 2021 19:40:40 -0400 Subject: [PATCH 4334/6300] create and handle short tunnel build reply --- libi2pd/Garlic.cpp | 7 +++- libi2pd/Garlic.h | 3 +- libi2pd/I2NPProtocol.cpp | 87 ++++++++++++++++------------------------ libi2pd/I2NPProtocol.h | 2 +- libi2pd/Tunnel.cpp | 13 +++++- libi2pd/TunnelConfig.cpp | 8 ++++ libi2pd/TunnelConfig.h | 2 + 7 files changed, 65 insertions(+), 57 deletions(-) diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index d91c75f5..94dadab2 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -471,8 +471,13 @@ namespace garlic { uint64_t t; memcpy (&t, tag, 8); + AddECIESx25519Key (key, t); + } + + void GarlicDestination::AddECIESx25519Key (const uint8_t * key, uint64_t tag) + { auto tagset = std::make_shared(this, key); - m_ECIESx25519Tags.emplace (t, ECIESX25519AEADRatchetIndexTagset{0, tagset}); + m_ECIESx25519Tags.emplace (tag, ECIESX25519AEADRatchetIndexTagset{0, tagset}); } bool GarlicDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index a898e6a1..d3d11641 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -243,7 +243,7 @@ namespace garlic std::shared_ptr msg); void AddSessionKey (const uint8_t * key, const uint8_t * tag); // one tag - void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag + void AddECIESx25519Key (const uint8_t * key, uint64_t tag); // one tag virtual bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag); // from different thread void DeliveryStatusSent (GarlicRoutingSessionPtr session, uint32_t msgID); uint64_t AddECIESx25519SessionNextTag (ReceiveRatchetTagSetPtr tagset); @@ -260,6 +260,7 @@ namespace garlic protected: + void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) = 0; diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 6ffa1551..07ce2518 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -660,68 +660,47 @@ namespace i2p clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); + + // encrypt reply + uint8_t nonce[12]; + memset (nonce, 0, 12); + uint8_t * reply = buf + 1; + for (int j = 0; j < num; j++) + { + nonce[4] = j; // nonce is record # + if (j == i) + { + memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = 0; // TODO: correct ret code + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt + { + LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); + return; + } + } + else + i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, noiseState.m_CK, nonce, reply); + reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; + } + // send reply if (isEndpoint) { - // we are endpoint, create OutboundTunnelBuildReply - auto otbrm = NewI2NPShortMessage (); - auto payload = otbrm->GetPayload (); - payload[0] = num; // num - payload[1] = i; // slot - payload +=2; - // reply - htobe16buf (payload, 3); payload += 2; // length, TODO - memset (payload, 0, 3); payload += 3; // ClearText: no options, and zero ret code. TODO - // ShortBuildReplyRecords. Exclude ours - uint8_t * records = buf + 1; - if (i > 0) - { - memcpy (payload, records, i*SHORT_TUNNEL_BUILD_RECORD_SIZE); - payload += i*SHORT_TUNNEL_BUILD_RECORD_SIZE; - records += i*SHORT_TUNNEL_BUILD_RECORD_SIZE; - } - if (i < num-1) - { - memcpy (payload, records, (num-1-i)*SHORT_TUNNEL_BUILD_RECORD_SIZE); - payload += (num-1-i)*SHORT_TUNNEL_BUILD_RECORD_SIZE; - } - otbrm->len += (payload - otbrm->GetPayload ()); - otbrm->FillI2NPMessageHeader (eI2NPOutboundTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); + auto replyMsg = NewI2NPShortMessage (); + replyMsg->Concat (buf, len); + replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); uint64_t tag; memcpy (&tag, noiseState.m_CK, 8); - // send garlic to reply tunnel + // we send it to reply tunnel transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - i2p::garlic::WrapECIESX25519Message (otbrm, noiseState.m_CK + 32, tag))); - } - else - { - // we are participant, encrypt reply - uint8_t nonce[12]; - memset (nonce, 0, 12); - uint8_t * reply = buf + 1; - for (int j = 0; j < num; j++) - { - nonce[4] = j; // nonce is record # - if (j == i) - { - memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options - reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = 0; // TODO: correct ret code - if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt - { - LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); - return; - } - } - else - i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, noiseState.m_CK, nonce, reply); - reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; - } + CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); + } + else transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } return; } record += SHORT_TUNNEL_BUILD_RECORD_SIZE; @@ -843,6 +822,7 @@ namespace i2p HandleVariableTunnelBuildMsg (msgID, buf, size); break; case eI2NPVariableTunnelBuildReply: + case eI2NPShortTunnelBuildReply: HandleVariableTunnelBuildReplyMsg (msgID, buf, size); break; case eI2NPShortTunnelBuild: @@ -905,6 +885,7 @@ namespace i2p case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: case eI2NPShortTunnelBuild: + case eI2NPShortTunnelBuildReply: // forward to tunnel thread i2p::tunnel::tunnels.PostTunnelData (msg); break; diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index e20a0c0d..a0b5802c 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -136,7 +136,7 @@ namespace i2p eI2NPVariableTunnelBuild = 23, eI2NPVariableTunnelBuildReply = 24, eI2NPShortTunnelBuild = 25, - eI2NPOutboundTunnelBuildReply = 26 + eI2NPShortTunnelBuildReply = 26 }; const uint8_t TUNNEL_BUILD_RECORD_GATEWAY_FLAG = 0x80; diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 6c9d59e9..f9f5ac38 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -105,7 +105,16 @@ namespace tunnel outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); } else + { + if (m_Config->IsShort () && m_Config->GetLastHop ()) + { + // add garlic key/tag for reply + uint8_t key[32]; + uint64_t tag = m_Config->GetLastHop ()->GetGarlicKey (key); + i2p::context.AddECIESx25519Key (key, tag); + } i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); + } } bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) @@ -513,8 +522,10 @@ namespace tunnel } case eI2NPVariableTunnelBuild: case eI2NPVariableTunnelBuildReply: + case eI2NPShortTunnelBuild: + case eI2NPShortTunnelBuildReply: case eI2NPTunnelBuild: - case eI2NPTunnelBuildReply: + case eI2NPTunnelBuildReply: HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); break; default: diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 930c565b..45e8a12a 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -260,5 +260,13 @@ namespace tunnel nonce[4] = index; // nonce is index i2p::crypto::ChaCha20 (record, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, record); } + + uint64_t ShortECIESTunnelHopConfig::GetGarlicKey (uint8_t * key) const + { + uint64_t tag; + memcpy (&tag, m_CK, 8); + memcpy (key, m_CK + 32, 32); + return tag; + } } } \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 306d8a72..d6441b03 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -44,6 +44,7 @@ namespace tunnel virtual void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) = 0; virtual bool DecryptBuildResponseRecord (uint8_t * records) const = 0; virtual void DecryptRecord (uint8_t * records, int index) const; // AES + virtual uint64_t GetGarlicKey (uint8_t * key) const { return 0; }; // return tag }; struct ElGamalTunnelHopConfig: public TunnelHopConfig @@ -83,6 +84,7 @@ namespace tunnel void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); bool DecryptBuildResponseRecord (uint8_t * records) const; void DecryptRecord (uint8_t * records, int index) const override; // Chacha20 + uint64_t GetGarlicKey (uint8_t * key) const override; }; class TunnelConfig From 41bfc7899dbf00332a74083cb86ffb251eecd11e Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 14 Jul 2021 14:46:56 -0400 Subject: [PATCH 4335/6300] keep own RouterInfo in netdb --- libi2pd/I2NPProtocol.cpp | 6 ++++++ libi2pd/NetDb.cpp | 24 +++++++++++++++++++++++- libi2pd/RouterContext.h | 8 ++++---- libi2pd/TunnelPool.cpp | 2 +- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 07ce2518..15b51e7d 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -262,6 +262,12 @@ namespace i2p if (!router) // we send own RouterInfo router = context.GetSharedRouterInfo (); + if (!router->GetBuffer ()) + { + LogPrint (eLogError, "I2NP: Invalid RouterInfo buffer for DatabaseStore"); + return nullptr; + } + auto m = NewI2NPShortMessage (); uint8_t * payload = m->GetPayload (); diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index e3a55832..b81289f3 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -59,6 +59,18 @@ namespace data Reseed (); else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false)) Reseed (); // we don't have a router we can connect to. Trying to reseed + + auto it = m_RouterInfos.find (i2p::context.GetIdentHash ()); + if (it != m_RouterInfos.end ()) + { + // remove own router + m_RouterInfos.erase (it); + m_Floodfills.remove (it->second); + } + // insert own router + m_RouterInfos.emplace (i2p::context.GetIdentHash (), i2p::context.GetSharedRouterInfo ()); + if (i2p::context.IsFloodfill ()) + m_Floodfills.push_back (i2p::context.GetSharedRouterInfo ()); i2p::config::GetOption("persist.profiles", m_PersistProfiles); @@ -162,10 +174,18 @@ namespace data bool publish = false; if (m_PublishReplyToken) { + // next publishing attempt if (ts - lastPublish >= NETDB_PUBLISH_CONFIRMATION_TIMEOUT) publish = true; } else if (i2p::context.GetLastUpdateTime () > lastPublish || - ts - lastPublish >= NETDB_PUBLISH_INTERVAL) publish = true; + ts - lastPublish >= NETDB_PUBLISH_INTERVAL) + { + // new publish + m_PublishExcluded.clear (); + if (i2p::context.IsFloodfill ()) + m_PublishExcluded.insert (i2p::context.GetIdentHash ()); // do publish to ourselves + publish = true; + } if (publish) // update timestamp and publish { i2p::context.UpdateTimestamp (ts); @@ -567,8 +587,10 @@ namespace data expirationTimeout = i2p::context.IsFloodfill () ? NETDB_FLOODFILL_EXPIRATION_TIMEOUT*1000LL : NETDB_MIN_EXPIRATION_TIMEOUT*1000LL + (NETDB_MAX_EXPIRATION_TIMEOUT - NETDB_MIN_EXPIRATION_TIMEOUT)*1000LL*NETDB_MIN_ROUTERS/total; + auto own = i2p::context.GetSharedRouterInfo (); for (auto& it: m_RouterInfos) { + if (it.second == own) continue; // skip own std::string ident = it.second->GetIdentHashBase64(); std::string path = m_Storage.Path(ident); if (it.second->IsUpdated ()) diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index acb33795..978490ae 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -68,11 +68,11 @@ namespace garlic void Init (); const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; - i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; }; - std::shared_ptr GetSharedRouterInfo () const + i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; }; + std::shared_ptr GetSharedRouterInfo () { - return std::shared_ptr (&m_RouterInfo, - [](const i2p::data::RouterInfo *) {}); + return std::shared_ptr (&m_RouterInfo, + [](i2p::data::RouterInfo *) {}); } std::shared_ptr GetSharedDestination () { diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index e45c6090..faa28e69 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -423,7 +423,7 @@ namespace tunnel bool StandardSelectPeers(Path & peers, int numHops, bool inbound, SelectHopFunc nextHop) { int start = 0; - auto prevHop = i2p::context.GetSharedRouterInfo (); + std::shared_ptr prevHop = i2p::context.GetSharedRouterInfo (); if(i2p::transport::transports.RoutesRestricted()) { /** if routes are restricted prepend trusted first hop */ From 197f13f9c0e241c75f30ce75c50d0883c2089ecb Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 15 Jul 2021 14:02:20 -0400 Subject: [PATCH 4336/6300] rollback --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 113 +++++++++++----------- libi2pd/ECIESX25519AEADRatchetSession.h | 10 +- 2 files changed, 65 insertions(+), 58 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 09bbfe53..7b3947f1 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -518,7 +518,36 @@ namespace garlic } return true; } - + + bool ECIESX25519AEADRatchetSession::NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) + { + // we are Alice, router's bpk is m_RemoteStaticKey + i2p::crypto::InitNoiseNState (GetNoiseState (), m_RemoteStaticKey); + size_t offset = 0; + m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + memcpy (out + offset, m_EphemeralKeys->GetPublicKey (), 32); + MixHash (out + offset, 32); // h = SHA256(h || aepk) + offset += 32; + uint8_t sharedSecret[32]; + if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); + return false; + } + MixKey (sharedSecret); + uint8_t nonce[12]; + CreateNonce (0, nonce); + // encrypt payload + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); + return false; + } + + m_State = eSessionStateNewSessionSent; + return true; + } + bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob @@ -829,6 +858,11 @@ namespace garlic return nullptr; len += 96; break; + case eSessionStateForRouter: + if (!NewOutgoingMessageForRouter (payload.data (), payload.size (), buf, m->maxLen)) + return nullptr; + len += 48; + break; default: return nullptr; } @@ -839,9 +873,9 @@ namespace garlic return m; } - std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg) + std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter) { - m_State = eSessionStateOneTime; + m_State = isForRouter ? eSessionStateForRouter : eSessionStateOneTime; return WrapSingleMessage (msg); } @@ -1098,10 +1132,19 @@ namespace garlic HandlePayload (payload.data (), len - 16, nullptr, 0); return true; } - - static size_t CreateGarlicCloveBlock (std::shared_ptr msg, uint8_t * payload) + + std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag) { + auto m = NewI2NPMessage (); + m->Align (12); // in order to get buf aligned to 16 (12 + 4) + uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length + uint8_t nonce[12]; + memset (nonce, 0, 12); // n = 0 + size_t offset = 0; + memcpy (buf + offset, &tag, 8); offset += 8; + auto payload = buf + offset; uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; + size_t len = cloveSize + 3; payload[0] = eECIESx25519BlkGalicClove; // clove type htobe16buf (payload + 1, cloveSize); // size payload += 3; @@ -1110,26 +1153,14 @@ namespace garlic htobe32buf (payload + 1, msg->GetMsgID ()); // msgID htobe32buf (payload + 5, msg->GetExpiration () / 1000); // expiration in seconds memcpy (payload + 9, msg->GetPayload (), msg->GetPayloadLength ()); - return cloveSize + 3; - } - - std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag) - { - auto m = NewI2NPMessage (); - m->Align (12); // in order to get buf aligned to 16 (12 + 4) - uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length - size_t offset = 0; - memcpy (buf + offset, &tag, 8); offset += 8; - auto payload = buf + offset; - size_t len = CreateGarlicCloveBlock (msg, payload); - uint8_t nonce[12]; - memset (nonce, 0, 12); // n = 0 - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, buf, 8, key, nonce, payload, len + 16, true)) // encrypt + + if (!i2p::crypto::AEADChaCha20Poly1305 (buf + offset, len, buf, 8, key, nonce, buf + offset, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return nullptr; } offset += len + 16; + htobe32buf (m->GetPayload (), offset); m->len += offset + 4; m->FillI2NPMessageHeader (eI2NPGarlic); @@ -1138,39 +1169,13 @@ namespace garlic std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey) { - // Noise_N, we are Alice, routerPublicKey is Bob's - i2p::crypto::NoiseSymmetricState noiseState; - i2p::crypto::InitNoiseNState (noiseState, routerPublicKey); - auto m = NewI2NPMessage (); - m->Align (12); // in order to get buf aligned to 16 (12 + 4) - uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length - size_t offset = 0; - auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); - memcpy (buf + offset, ephemeralKeys->GetPublicKey (), 32); - noiseState.MixHash (buf + offset, 32); // h = SHA256(h || aepk) - offset += 32; - uint8_t sharedSecret[32]; - if (!ephemeralKeys->Agree (routerPublicKey, sharedSecret)) // x25519(aesk, bpk) - { - LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); - return nullptr; - } - noiseState.MixKey (sharedSecret); - auto payload = buf + offset; - size_t len = CreateGarlicCloveBlock (msg, payload); - uint8_t nonce[12]; - memset (nonce, 0, 12); - // encrypt payload - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, noiseState.m_H, 32, noiseState.m_CK + 32, nonce, payload, len + 16, true)) // encrypt - { - LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); - return nullptr; - } - offset += len + 16; - htobe32buf (m->GetPayload (), offset); - m->len += offset + 4; - m->FillI2NPMessageHeader (eI2NPGarlic); - return m; + // TODO: implement without session + auto session = std::make_shared(nullptr, false); + session->SetRemoteStaticKey (routerPublicKey); + return session->WrapOneTimeMessage (msg, true); } } } + + + \ No newline at end of file diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 762e00ef..68599c3e 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -147,7 +147,8 @@ namespace garlic eSessionStateNewSessionSent, eSessionStateNewSessionReplySent, eSessionStateEstablished, - eSessionStateOneTime + eSessionStateOneTime, + eSessionStateForRouter }; struct DHRatchet @@ -165,7 +166,7 @@ namespace garlic bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg); + std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter = false); const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } @@ -206,7 +207,8 @@ namespace garlic bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - + bool NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); + std::vector CreatePayload (std::shared_ptr msg, bool first); size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len); size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); From a1d1a5df74536e2c5ae194beeca98c97c76b0721 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 15 Jul 2021 18:18:55 -0400 Subject: [PATCH 4337/6300] datetime block for message for router --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 126 ++++++++++++---------- libi2pd/ECIESX25519AEADRatchetSession.h | 10 +- 2 files changed, 73 insertions(+), 63 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 7b3947f1..a29f0ded 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -518,36 +518,7 @@ namespace garlic } return true; } - - bool ECIESX25519AEADRatchetSession::NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) - { - // we are Alice, router's bpk is m_RemoteStaticKey - i2p::crypto::InitNoiseNState (GetNoiseState (), m_RemoteStaticKey); - size_t offset = 0; - m_EphemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); - memcpy (out + offset, m_EphemeralKeys->GetPublicKey (), 32); - MixHash (out + offset, 32); // h = SHA256(h || aepk) - offset += 32; - uint8_t sharedSecret[32]; - if (!m_EphemeralKeys->Agree (m_RemoteStaticKey, sharedSecret)) // x25519(aesk, bpk) - { - LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); - return false; - } - MixKey (sharedSecret); - uint8_t nonce[12]; - CreateNonce (0, nonce); - // encrypt payload - if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, m_H, 32, m_CK + 32, nonce, out + offset, len + 16, true)) // encrypt - { - LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); - return false; - } - - m_State = eSessionStateNewSessionSent; - return true; - } - + bool ECIESX25519AEADRatchetSession::NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen) { // we are Bob @@ -858,11 +829,6 @@ namespace garlic return nullptr; len += 96; break; - case eSessionStateForRouter: - if (!NewOutgoingMessageForRouter (payload.data (), payload.size (), buf, m->maxLen)) - return nullptr; - len += 48; - break; default: return nullptr; } @@ -873,9 +839,9 @@ namespace garlic return m; } - std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter) + std::shared_ptr ECIESX25519AEADRatchetSession::WrapOneTimeMessage (std::shared_ptr msg) { - m_State = isForRouter ? eSessionStateForRouter : eSessionStateOneTime; + m_State = eSessionStateOneTime; return WrapSingleMessage (msg); } @@ -1132,19 +1098,17 @@ namespace garlic HandlePayload (payload.data (), len - 16, nullptr, 0); return true; } - - std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag) + + static size_t CreateGarlicPayload (std::shared_ptr msg, uint8_t * payload) { - auto m = NewI2NPMessage (); - m->Align (12); // in order to get buf aligned to 16 (12 + 4) - uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length - uint8_t nonce[12]; - memset (nonce, 0, 12); // n = 0 - size_t offset = 0; - memcpy (buf + offset, &tag, 8); offset += 8; - auto payload = buf + offset; + size_t len = 7; + // DateTime + payload[0] = eECIESx25519BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + // I2NP + payload += len; uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; - size_t len = cloveSize + 3; payload[0] = eECIESx25519BlkGalicClove; // clove type htobe16buf (payload + 1, cloveSize); // size payload += 3; @@ -1153,14 +1117,34 @@ namespace garlic htobe32buf (payload + 1, msg->GetMsgID ()); // msgID htobe32buf (payload + 5, msg->GetExpiration () / 1000); // expiration in seconds memcpy (payload + 9, msg->GetPayload (), msg->GetPayloadLength ()); - - if (!i2p::crypto::AEADChaCha20Poly1305 (buf + offset, len, buf, 8, key, nonce, buf + offset, len + 16, true)) // encrypt + len += cloveSize + 3; + /* payload += cloveSize + 3; + // padding + uint8_t paddingSize = (rand () & 0x0F) + 1; // 1 - 16 + payload[0] = eECIESx25519BlkPadding; + htobe16buf (payload + 1, paddingSize); + memset (payload + 3, 0, paddingSize); + len += paddingSize + 3;*/ + return len; + } + + std::shared_ptr WrapECIESX25519Message (std::shared_ptr msg, const uint8_t * key, uint64_t tag) + { + auto m = NewI2NPMessage (); + m->Align (12); // in order to get buf aligned to 16 (12 + 4) + uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length + size_t offset = 0; + memcpy (buf + offset, &tag, 8); offset += 8; + auto payload = buf + offset; + size_t len = CreateGarlicPayload (msg, payload); + uint8_t nonce[12]; + memset (nonce, 0, 12); // n = 0 + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, buf, 8, key, nonce, payload, len + 16, true)) // encrypt { LogPrint (eLogWarning, "Garlic: Payload section AEAD encryption failed"); return nullptr; } offset += len + 16; - htobe32buf (m->GetPayload (), offset); m->len += offset + 4; m->FillI2NPMessageHeader (eI2NPGarlic); @@ -1169,13 +1153,39 @@ namespace garlic std::shared_ptr WrapECIESX25519MessageForRouter (std::shared_ptr msg, const uint8_t * routerPublicKey) { - // TODO: implement without session - auto session = std::make_shared(nullptr, false); - session->SetRemoteStaticKey (routerPublicKey); - return session->WrapOneTimeMessage (msg, true); + // Noise_N, we are Alice, routerPublicKey is Bob's + i2p::crypto::NoiseSymmetricState noiseState; + i2p::crypto::InitNoiseNState (noiseState, routerPublicKey); + auto m = NewI2NPMessage (); + m->Align (12); // in order to get buf aligned to 16 (12 + 4) + uint8_t * buf = m->GetPayload () + 4; // 4 bytes for length + size_t offset = 0; + auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); + memcpy (buf + offset, ephemeralKeys->GetPublicKey (), 32); + noiseState.MixHash (buf + offset, 32); // h = SHA256(h || aepk) + offset += 32; + uint8_t sharedSecret[32]; + if (!ephemeralKeys->Agree (routerPublicKey, sharedSecret)) // x25519(aesk, bpk) + { + LogPrint (eLogWarning, "Garlic: Incorrect Bob static key"); + return nullptr; + } + noiseState.MixKey (sharedSecret); + auto payload = buf + offset; + size_t len = CreateGarlicPayload (msg, payload); + uint8_t nonce[12]; + memset (nonce, 0, 12); + // encrypt payload + if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, noiseState.m_H, 32, noiseState.m_CK + 32, nonce, payload, len + 16, true)) // encrypt + { + LogPrint (eLogWarning, "Garlic: Payload for router AEAD encryption failed"); + return nullptr; + } + offset += len + 16; + htobe32buf (m->GetPayload (), offset); + m->len += offset + 4; + m->FillI2NPMessageHeader (eI2NPGarlic); + return m; } } } - - - \ No newline at end of file diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 68599c3e..3e627fff 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -1,3 +1,4 @@ + /* * Copyright (c) 2013-2021, The PurpleI2P Project * @@ -147,8 +148,7 @@ namespace garlic eSessionStateNewSessionSent, eSessionStateNewSessionReplySent, eSessionStateEstablished, - eSessionStateOneTime, - eSessionStateForRouter + eSessionStateOneTime }; struct DHRatchet @@ -166,7 +166,7 @@ namespace garlic bool HandleNextMessage (uint8_t * buf, size_t len, std::shared_ptr receiveTagset, int index = 0); std::shared_ptr WrapSingleMessage (std::shared_ptr msg); - std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg, bool isForRouter = false); + std::shared_ptr WrapOneTimeMessage (std::shared_ptr msg); const uint8_t * GetRemoteStaticKey () const { return m_RemoteStaticKey; } void SetRemoteStaticKey (const uint8_t * key) { memcpy (m_RemoteStaticKey, key, 32); } @@ -207,8 +207,7 @@ namespace garlic bool NewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - bool NewOutgoingMessageForRouter (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - + std::vector CreatePayload (std::shared_ptr msg, bool first); size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len); size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); @@ -262,3 +261,4 @@ namespace garlic } #endif + From cd0751d3f172dace381483d403f208c5934afffc Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 15 Jul 2021 18:30:32 -0400 Subject: [PATCH 4338/6300] padding block for message for router --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index a29f0ded..8a525d19 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1108,23 +1108,23 @@ namespace garlic htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); // I2NP payload += len; - uint16_t cloveSize = msg->GetPayloadLength () + 9 + 1; + uint16_t cloveSize = msg->GetPayloadLength () + 10; payload[0] = eECIESx25519BlkGalicClove; // clove type htobe16buf (payload + 1, cloveSize); // size payload += 3; - *payload = 0; payload++; // flag and delivery instructions - *payload = msg->GetTypeID (); // I2NP msg type - htobe32buf (payload + 1, msg->GetMsgID ()); // msgID - htobe32buf (payload + 5, msg->GetExpiration () / 1000); // expiration in seconds - memcpy (payload + 9, msg->GetPayload (), msg->GetPayloadLength ()); + payload[0] = 0; // flag and delivery instructions + payload[1] = msg->GetTypeID (); // I2NP msg type + htobe32buf (payload + 2, msg->GetMsgID ()); // msgID + htobe32buf (payload + 6, msg->GetExpiration () / 1000); // expiration in seconds + memcpy (payload + 10, msg->GetPayload (), msg->GetPayloadLength ()); len += cloveSize + 3; - /* payload += cloveSize + 3; + payload += cloveSize; // padding uint8_t paddingSize = (rand () & 0x0F) + 1; // 1 - 16 payload[0] = eECIESx25519BlkPadding; htobe16buf (payload + 1, paddingSize); memset (payload + 3, 0, paddingSize); - len += paddingSize + 3;*/ + len += paddingSize + 3; return len; } From 0cd9f1b0028bd0f5ae9b971b9b2d380d21848393 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 15 Jul 2021 19:01:43 -0400 Subject: [PATCH 4339/6300] precalculate padding sizes --- libi2pd/NTCP2.cpp | 13 +++++++++++-- libi2pd/NTCP2.h | 3 +++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index cb200b42..ed84b8e7 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -332,7 +332,8 @@ namespace transport m_SendMDCtx(nullptr), m_ReceiveMDCtx (nullptr), #endif m_NextReceivedLen (0), m_NextReceivedBuffer (nullptr), m_NextSendBuffer (nullptr), - m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), m_IsSending (false) + m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), m_IsSending (false), + m_NextPaddingSize (16) { if (in_RemoteRouter) // Alice { @@ -1058,7 +1059,15 @@ namespace transport size_t paddingSize = (msgLen*NTCP2_MAX_PADDING_RATIO)/100; if (msgLen + paddingSize + 3 > NTCP2_UNENCRYPTED_FRAME_MAX_SIZE) paddingSize = NTCP2_UNENCRYPTED_FRAME_MAX_SIZE - msgLen -3; if (paddingSize > len) paddingSize = len; - if (paddingSize) paddingSize = rand () % paddingSize; + if (paddingSize) + { + if (m_NextPaddingSize >= 16) + { + RAND_bytes ((uint8_t *)m_PaddingSizes, sizeof (m_PaddingSizes)); + m_NextPaddingSize = 0; + } + paddingSize = m_PaddingSizes[m_NextPaddingSize++] % paddingSize; + } buf[0] = eNTCP2BlkPadding; // blk htobe16buf (buf + 1, paddingSize); // size memset (buf + 3, 0, paddingSize); diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index 01ee3e34..bcee1b09 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -218,6 +218,9 @@ namespace transport bool m_IsSending; std::list > m_SendQueue; uint64_t m_NextRouterInfoResendTime; // seconds since epoch + + uint16_t m_PaddingSizes[16]; + int m_NextPaddingSize; }; class NTCP2Server: private i2p::util::RunnableServiceWithWork From 5d022c25ba8924ec09e89c8c261b0b00fdaf9e53 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 16 Jul 2021 09:44:22 -0400 Subject: [PATCH 4340/6300] don't send datetime for one time key message --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 24 +++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 8a525d19..03c5b00e 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1099,13 +1099,17 @@ namespace garlic return true; } - static size_t CreateGarlicPayload (std::shared_ptr msg, uint8_t * payload) + static size_t CreateGarlicPayload (std::shared_ptr msg, uint8_t * payload, bool datetime) { - size_t len = 7; - // DateTime - payload[0] = eECIESx25519BlkDateTime; - htobe16buf (payload + 1, 4); - htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + size_t len = 0; + if (datetime) + { + // DateTime + payload[0] = eECIESx25519BlkDateTime; + htobe16buf (payload + 1, 4); + htobe32buf (payload + 3, i2p::util::GetSecondsSinceEpoch ()); + len = 7; + } // I2NP payload += len; uint16_t cloveSize = msg->GetPayloadLength () + 10; @@ -1120,10 +1124,10 @@ namespace garlic len += cloveSize + 3; payload += cloveSize; // padding - uint8_t paddingSize = (rand () & 0x0F) + 1; // 1 - 16 + uint8_t paddingSize = rand () & 0x0F; // 0 - 15 payload[0] = eECIESx25519BlkPadding; htobe16buf (payload + 1, paddingSize); - memset (payload + 3, 0, paddingSize); + if (paddingSize) memset (payload + 3, 0, paddingSize); len += paddingSize + 3; return len; } @@ -1136,7 +1140,7 @@ namespace garlic size_t offset = 0; memcpy (buf + offset, &tag, 8); offset += 8; auto payload = buf + offset; - size_t len = CreateGarlicPayload (msg, payload); + size_t len = CreateGarlicPayload (msg, payload, false); uint8_t nonce[12]; memset (nonce, 0, 12); // n = 0 if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, buf, 8, key, nonce, payload, len + 16, true)) // encrypt @@ -1172,7 +1176,7 @@ namespace garlic } noiseState.MixKey (sharedSecret); auto payload = buf + offset; - size_t len = CreateGarlicPayload (msg, payload); + size_t len = CreateGarlicPayload (msg, payload, true); uint8_t nonce[12]; memset (nonce, 0, 12); // encrypt payload From f4902e66422c829d519d163997f7a185bb7ba624 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 16 Jul 2021 13:53:12 -0400 Subject: [PATCH 4341/6300] eligble floodfill must be reachable by ipv4 --- libi2pd/RouterInfo.cpp | 5 ++--- libi2pd/RouterInfo.h | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index a426d9b7..65470a88 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1163,9 +1163,8 @@ namespace data bool RouterInfo::IsEligibleFloodfill () const { - // floodfill must be reachable somehow, >= 0.9.28 and not DSA - return (IsReachable () || (m_SupportedTransports & eSSUV4)) && - m_Version >= NETDB_MIN_FLOODFILL_VERSION && + // floodfill must be reachable by ipv4, >= 0.9.28 and not DSA + return IsReachableBy (eNTCP2V4 | eSSUV4) && m_Version >= NETDB_MIN_FLOODFILL_VERSION && GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index f27a5d68..b2b6df61 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -206,7 +206,7 @@ namespace data void DisableMesh (); bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; bool IsReachableFrom (const RouterInfo& other) const { return m_ReachableTransports & other.m_SupportedTransports; }; - bool IsReachableBy (SupportedTransports transport) const { return m_ReachableTransports & transport; }; + bool IsReachableBy (uint8_t transports) const { return m_ReachableTransports & transports; }; bool HasValidAddresses () const { return m_SupportedTransports; }; bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; From a37cf058cd7834c207d5f8b5daca901afdc9239c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 16 Jul 2021 20:12:41 -0400 Subject: [PATCH 4342/6300] router with expired introducer is still valid --- libi2pd/NetDb.cpp | 13 +++++++------ libi2pd/RouterInfo.cpp | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index b81289f3..1f63c054 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -473,14 +473,15 @@ namespace data bool NetDb::LoadRouterInfo (const std::string & path) { auto r = std::make_shared(path); - if (r->GetRouterIdentity () && !r->IsUnreachable () && - (r->IsReachable () || !r->IsSSU (false) || m_LastLoad < r->GetTimestamp () + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT*1000LL)) // 1 hour - { + if (r->GetRouterIdentity () && !r->IsUnreachable () && r->HasValidAddresses ()) + { r->DeleteBuffer (); r->ClearProperties (); // properties are not used for regular routers - m_RouterInfos[r->GetIdentHash ()] = r; - if (r->IsFloodfill () && r->IsReachable ()) // floodfill must be reachable - m_Floodfills.push_back (r); + if (m_RouterInfos.emplace (r->GetIdentHash (), r).second) + { + if (r->IsFloodfill () && r->IsEligibleFloodfill ()) + m_Floodfills.push_back (r); + } } else { diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 65470a88..3feaa504 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -343,7 +343,8 @@ namespace data int numValid = 0; for (auto& it: address->ssu->introducers) { - if ((!it.iExp || ts <= it.iExp) && it.iPort > 0 && + if (!it.iExp) it.iExp = m_Timestamp/1000 + NETDB_INTRODUCEE_EXPIRATION_TIMEOUT; + if (ts <= it.iExp && it.iPort > 0 && ((it.iHost.is_v4 () && address->IsV4 ()) || (it.iHost.is_v6 () && address->IsV6 ()))) numValid++; else From 6ecfe0789f4a07e9737cc53e7287802beb5e40c6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 18 Jul 2021 18:45:08 -0400 Subject: [PATCH 4343/6300] don't allocate payload buffer for every single ECIESx25519 message --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 81 ++++++++++++----------- libi2pd/ECIESX25519AEADRatchetSession.h | 2 +- libi2pd/Garlic.cpp | 11 ++- libi2pd/Garlic.h | 4 +- 4 files changed, 57 insertions(+), 41 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 03c5b00e..9c76320b 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -795,8 +795,9 @@ namespace garlic std::shared_ptr ECIESX25519AEADRatchetSession::WrapSingleMessage (std::shared_ptr msg) { - auto payload = CreatePayload (msg, m_State != eSessionStateEstablished); - size_t len = payload.size (); + uint8_t * payload = GetOwner ()->GetPayloadBuffer (); + if (!payload) return nullptr; + size_t len = CreatePayload (msg, m_State != eSessionStateEstablished, payload); if (!len) return nullptr; auto m = NewI2NPMessage (len + 100); // 96 + 4 m->Align (12); // in order to get buf aligned to 16 (12 + 4) @@ -805,27 +806,27 @@ namespace garlic switch (m_State) { case eSessionStateEstablished: - if (!NewExistingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) + if (!NewExistingSessionMessage (payload, len, buf, m->maxLen)) return nullptr; len += 24; break; case eSessionStateNew: - if (!NewOutgoingSessionMessage (payload.data (), payload.size (), buf, m->maxLen)) + if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen)) return nullptr; len += 96; break; case eSessionStateNewSessionReceived: - if (!NewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) + if (!NewSessionReplyMessage (payload, len, buf, m->maxLen)) return nullptr; len += 72; break; case eSessionStateNewSessionReplySent: - if (!NextNewSessionReplyMessage (payload.data (), payload.size (), buf, m->maxLen)) + if (!NextNewSessionReplyMessage (payload, len, buf, m->maxLen)) return nullptr; len += 72; break; case eSessionStateOneTime: - if (!NewOutgoingSessionMessage (payload.data (), payload.size (), buf, m->maxLen, false)) + if (!NewOutgoingSessionMessage (payload, len, buf, m->maxLen, false)) return nullptr; len += 96; break; @@ -845,7 +846,7 @@ namespace garlic return WrapSingleMessage (msg); } - std::vector ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr msg, bool first) + size_t ECIESX25519AEADRatchetSession::CreatePayload (std::shared_ptr msg, bool first, uint8_t * payload) { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); size_t payloadLen = 0; @@ -907,89 +908,93 @@ namespace garlic payloadLen += paddingSize + 3; } } - std::vector v(payloadLen); if (payloadLen) - { + { + if (payloadLen > I2NP_MAX_MESSAGE_SIZE) + { + LogPrint (eLogError, "Garlic: payload length ", payloadLen, " is too long"); + return 0; + } m_LastSentTimestamp = ts; size_t offset = 0; // DateTime if (first) { - v[offset] = eECIESx25519BlkDateTime; offset++; - htobe16buf (v.data () + offset, 4); offset += 2; - htobe32buf (v.data () + offset, ts/1000); offset += 4; // in seconds + payload[offset] = eECIESx25519BlkDateTime; offset++; + htobe16buf (payload + offset, 4); offset += 2; + htobe32buf (payload + offset, ts/1000); offset += 4; // in seconds } // LeaseSet if (leaseSet) { - offset += CreateLeaseSetClove (leaseSet, ts, v.data () + offset, payloadLen - offset); + offset += CreateLeaseSetClove (leaseSet, ts, payload + offset, payloadLen - offset); if (!first) { // ack request - v[offset] = eECIESx25519BlkAckRequest; offset++; - htobe16buf (v.data () + offset, 1); offset += 2; - v[offset] = 0; offset++; // flags + payload[offset] = eECIESx25519BlkAckRequest; offset++; + htobe16buf (payload + offset, 1); offset += 2; + payload[offset] = 0; offset++; // flags } } // msg if (msg) - offset += CreateGarlicClove (msg, v.data () + offset, payloadLen - offset); + offset += CreateGarlicClove (msg, payload + offset, payloadLen - offset); // ack if (m_AckRequests.size () > 0) { - v[offset] = eECIESx25519BlkAck; offset++; - htobe16buf (v.data () + offset, m_AckRequests.size () * 4); offset += 2; + payload[offset] = eECIESx25519BlkAck; offset++; + htobe16buf (payload + offset, m_AckRequests.size () * 4); offset += 2; for (auto& it: m_AckRequests) { - htobe16buf (v.data () + offset, it.first); offset += 2; - htobe16buf (v.data () + offset, it.second); offset += 2; + htobe16buf (payload + offset, it.first); offset += 2; + htobe16buf (payload + offset, it.second); offset += 2; } m_AckRequests.clear (); } // next keys if (m_SendReverseKey) { - v[offset] = eECIESx25519BlkNextKey; offset++; - htobe16buf (v.data () + offset, m_NextReceiveRatchet->newKey ? 35 : 3); offset += 2; - v[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; + payload[offset] = eECIESx25519BlkNextKey; offset++; + htobe16buf (payload + offset, m_NextReceiveRatchet->newKey ? 35 : 3); offset += 2; + payload[offset] = ECIESX25519_NEXT_KEY_REVERSE_KEY_FLAG; int keyID = m_NextReceiveRatchet->keyID - 1; if (m_NextReceiveRatchet->newKey) { - v[offset] |= ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG; + payload[offset] |= ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG; keyID++; } offset++; // flag - htobe16buf (v.data () + offset, keyID); offset += 2; // keyid + htobe16buf (payload + offset, keyID); offset += 2; // keyid if (m_NextReceiveRatchet->newKey) { - memcpy (v.data () + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); + memcpy (payload + offset, m_NextReceiveRatchet->key->GetPublicKey (), 32); offset += 32; // public key } m_SendReverseKey = false; } if (m_SendForwardKey) { - v[offset] = eECIESx25519BlkNextKey; offset++; - htobe16buf (v.data () + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; - v[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; - if (!m_NextSendRatchet->keyID) v[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only + payload[offset] = eECIESx25519BlkNextKey; offset++; + htobe16buf (payload + offset, m_NextSendRatchet->newKey ? 35 : 3); offset += 2; + payload[offset] = m_NextSendRatchet->newKey ? ECIESX25519_NEXT_KEY_KEY_PRESENT_FLAG : ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; + if (!m_NextSendRatchet->keyID) payload[offset] |= ECIESX25519_NEXT_KEY_REQUEST_REVERSE_KEY_FLAG; // for first key only offset++; // flag - htobe16buf (v.data () + offset, m_NextSendRatchet->keyID); offset += 2; // keyid + htobe16buf (payload + offset, m_NextSendRatchet->keyID); offset += 2; // keyid if (m_NextSendRatchet->newKey) { - memcpy (v.data () + offset, m_NextSendRatchet->key->GetPublicKey (), 32); + memcpy (payload + offset, m_NextSendRatchet->key->GetPublicKey (), 32); offset += 32; // public key } } // padding if (paddingSize) { - v[offset] = eECIESx25519BlkPadding; offset++; - htobe16buf (v.data () + offset, paddingSize); offset += 2; - memset (v.data () + offset, 0, paddingSize); offset += paddingSize; + payload[offset] = eECIESx25519BlkPadding; offset++; + htobe16buf (payload + offset, paddingSize); offset += 2; + memset (payload + offset, 0, paddingSize); offset += paddingSize; } } - return v; + return payloadLen; } size_t ECIESX25519AEADRatchetSession::CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index 3e627fff..aa72e4b0 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -208,7 +208,7 @@ namespace garlic bool NextNewSessionReplyMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); bool NewExistingSessionMessage (const uint8_t * payload, size_t len, uint8_t * out, size_t outLen); - std::vector CreatePayload (std::shared_ptr msg, bool first); + size_t CreatePayload (std::shared_ptr msg, bool first, uint8_t * payload); size_t CreateGarlicClove (std::shared_ptr msg, uint8_t * buf, size_t len); size_t CreateLeaseSetClove (std::shared_ptr ls, uint64_t ts, uint8_t * buf, size_t len); diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 94dadab2..62e9e423 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -433,7 +433,7 @@ namespace garlic } GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default - m_NumRatchetInboundTags (0) // 0 means standard + m_PayloadBuffer (nullptr), m_NumRatchetInboundTags (0) // 0 means standard { m_Ctx = BN_CTX_new (); } @@ -441,6 +441,8 @@ namespace garlic GarlicDestination::~GarlicDestination () { BN_CTX_free (m_Ctx); + if (m_PayloadBuffer) + delete[] m_PayloadBuffer; } void GarlicDestination::CleanUp () @@ -1121,5 +1123,12 @@ namespace garlic m_ECIESx25519Sessions.erase (it); } } + + uint8_t * GarlicDestination::GetPayloadBuffer () + { + if (!m_PayloadBuffer) + m_PayloadBuffer = new uint8_t[I2NP_MAX_MESSAGE_SIZE]; + return m_PayloadBuffer; + } } } diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index d3d11641..561f4ff7 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -250,7 +250,8 @@ namespace garlic void AddECIESx25519Session (const uint8_t * staticKey, ECIESX25519AEADRatchetSessionPtr session); void RemoveECIESx25519Session (const uint8_t * staticKey); void HandleECIESx25519GarlicClove (const uint8_t * buf, size_t len); - + uint8_t * GetPayloadBuffer (); + virtual void ProcessGarlicMessage (std::shared_ptr msg); virtual void ProcessDeliveryStatusMessage (std::shared_ptr msg); virtual void SetLeaseSetUpdated (); @@ -284,6 +285,7 @@ namespace garlic std::mutex m_SessionsMutex; std::unordered_map m_Sessions; std::unordered_map, ECIESX25519AEADRatchetSessionPtr> m_ECIESx25519Sessions; // static key -> session + uint8_t * m_PayloadBuffer; // for ECIESX25519AEADRatchet // incoming int m_NumRatchetInboundTags; std::unordered_map, std::hash > > m_Tags; From db9223b0d57f5dc94f3fd94403348ab958eedd62 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 19 Jul 2021 17:50:55 -0400 Subject: [PATCH 4344/6300] set minimal version for floodfill to 0.9.38 --- libi2pd/NetDb.hpp | 2 +- libi2pd/RouterInfo.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index 364cae4b..ccab664b 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -44,7 +44,7 @@ namespace data const int NETDB_PUBLISH_CONFIRMATION_TIMEOUT = 5; // in seconds const int NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 36); // 0.9.36 - const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 28); // 0.9.28 + const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 38); // 0.9.38 /** function for visiting a leaseset stored in a floodfill */ typedef std::function)> LeaseSetVisitor; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 3feaa504..0d439dfc 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1164,7 +1164,7 @@ namespace data bool RouterInfo::IsEligibleFloodfill () const { - // floodfill must be reachable by ipv4, >= 0.9.28 and not DSA + // floodfill must be reachable by ipv4, >= 0.9.38 and not DSA return IsReachableBy (eNTCP2V4 | eSSUV4) && m_Version >= NETDB_MIN_FLOODFILL_VERSION && GetIdentity ()->GetSigningKeyType () != SIGNING_KEY_TYPE_DSA_SHA1; } From bdc1107c9608da51fe69492b054c7291842af136 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 20 Jul 2021 14:35:02 -0400 Subject: [PATCH 4345/6300] correct message type for ShortTunnelBuild --- libi2pd/Tunnel.cpp | 4 ++-- libi2pd/TunnelConfig.h | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index f9f5ac38..9f096282 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -88,7 +88,7 @@ namespace tunnel } hop = hop->prev; } - msg->FillI2NPMessageHeader (eI2NPVariableTunnelBuild); + msg->FillI2NPMessageHeader (m_Config->IsShort () ? eI2NPShortTunnelBuild : eI2NPVariableTunnelBuild); // send message if (outboundTunnel) @@ -99,7 +99,7 @@ namespace tunnel if (ident) { auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); - if (msg1) msg = msg; + if (msg1) msg = msg1; } } outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index d6441b03..76aa0b34 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -91,14 +91,17 @@ namespace tunnel { public: - TunnelConfig (const std::vector >& peers) // inbound + TunnelConfig (const std::vector >& peers, + bool isShort = false): // inbound + m_IsShort (isShort) { CreatePeers (peers); m_LastHop->SetNextIdent (i2p::context.GetIdentHash ()); } TunnelConfig (const std::vector >& peers, - uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) // outbound + uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort = false): // outbound + m_IsShort (isShort) { CreatePeers (peers); m_FirstHop->isGateway = false; @@ -185,7 +188,7 @@ namespace tunnel protected: // this constructor can't be called from outside - TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr) + TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr), m_IsShort (false) { } @@ -218,7 +221,7 @@ namespace tunnel private: TunnelHopConfig * m_FirstHop, * m_LastHop; - bool m_IsShort = false; + bool m_IsShort; }; class ZeroHopsTunnelConfig: public TunnelConfig From 4807092df63499ceaa2e1f205ecc83a8ad812c6e Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 20 Jul 2021 15:17:58 -0400 Subject: [PATCH 4346/6300] fixed typo --- libi2pd/I2NPProtocol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 15b51e7d..48b4a34c 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -704,7 +704,7 @@ namespace i2p i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); } else - transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, + transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); return; From c2334db8f8f51494678be698c8557f1992865790 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 20 Jul 2021 18:02:48 -0400 Subject: [PATCH 4347/6300] correct reply key for short tunnel build record --- libi2pd/I2NPProtocol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 48b4a34c..9c71632a 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -686,7 +686,7 @@ namespace i2p } } else - i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, noiseState.m_CK, nonce, reply); + i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply); reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; } // send reply From 0b14c810fbd9453259acc68a567a822c1a517166 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 20 Jul 2021 19:38:36 -0400 Subject: [PATCH 4348/6300] handle ShortTunnelBuildReply --- libi2pd/I2NPProtocol.cpp | 27 +++++++++++++++------------ libi2pd/I2NPProtocol.h | 6 ------ 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 9c71632a..1123407d 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -381,7 +381,7 @@ namespace i2p return g_MaxNumTransitTunnels; } - bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) + static bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) { for (int i = 0; i < num; i++) { @@ -470,7 +470,7 @@ namespace i2p return false; } - void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + static void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) { int num = buf[0]; LogPrint (eLogDebug, "I2NP: VariableTunnelBuild ", num, " records"); @@ -540,7 +540,7 @@ namespace i2p } } - void HandleTunnelBuildMsg (uint8_t * buf, size_t len) + static void HandleTunnelBuildMsg (uint8_t * buf, size_t len) { if (i2p::context.IsECIES ()) { @@ -570,13 +570,14 @@ namespace i2p } } - void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + static void HandleTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len, bool isShort) { int num = buf[0]; - LogPrint (eLogDebug, "I2NP: VariableTunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); - if (len < num*BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE + 1) + LogPrint (eLogDebug, "I2NP: TunnelBuildReplyMsg of ", num, " records replyMsgID=", replyMsgID); + size_t recordSize = isShort ? SHORT_TUNNEL_BUILD_RECORD_SIZE : TUNNEL_BUILD_RECORD_SIZE; + if (len < num*recordSize + 1) { - LogPrint (eLogError, "I2NP: VaribleTunnelBuildReply message of ", num, " records is too short ", len); + LogPrint (eLogError, "I2NP: TunnelBuildReply message of ", num, " records is too short ", len); return; } @@ -600,7 +601,7 @@ namespace i2p LogPrint (eLogWarning, "I2NP: Pending tunnel for message ", replyMsgID, " not found"); } - void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) + static void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) { if (!i2p::context.IsECIES ()) { @@ -827,12 +828,14 @@ namespace i2p case eI2NPVariableTunnelBuild: HandleVariableTunnelBuildMsg (msgID, buf, size); break; - case eI2NPVariableTunnelBuildReply: - case eI2NPShortTunnelBuildReply: - HandleVariableTunnelBuildReplyMsg (msgID, buf, size); - break; case eI2NPShortTunnelBuild: HandleShortTunnelBuildMsg (msgID, buf, size); + break; + case eI2NPVariableTunnelBuildReply: + HandleTunnelBuildReplyMsg (msgID, buf, size, false); + break; + case eI2NPShortTunnelBuildReply: + HandleTunnelBuildReplyMsg (msgID, buf, size, true); break; case eI2NPTunnelBuild: HandleTunnelBuildMsg (buf, size); diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index a0b5802c..32aebbb2 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -304,12 +304,6 @@ namespace tunnel std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0, std::shared_ptr replyTunnel = nullptr); bool IsRouterInfoMsg (std::shared_ptr msg); - bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); - void HandleVariableTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); - void HandleVariableTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); - void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len); - void HandleTunnelBuildMsg (uint8_t * buf, size_t len); - std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf); std::shared_ptr CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload); std::shared_ptr CreateEmptyTunnelDataMsg (bool endpoint); From 5cb1f5986dc3e5a29e2a8e4ccf3ba2f287ae848b Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 20 Jul 2021 22:00:06 -0400 Subject: [PATCH 4349/6300] use msgID from ECIESx25519 block --- libi2pd/Destination.cpp | 5 +++-- libi2pd/Destination.h | 2 +- libi2pd/Garlic.cpp | 10 ++++++---- libi2pd/Garlic.h | 2 +- libi2pd/RouterContext.cpp | 4 ++-- libi2pd/RouterContext.h | 2 +- 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 7bcfe111..62f8ed26 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -345,10 +345,11 @@ namespace client void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len) { I2NPMessageType typeID = (I2NPMessageType)(buf[I2NP_HEADER_TYPEID_OFFSET]); - LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE); + uint32_t msgID = bufbe32toh (buf + I2NP_HEADER_MSGID_OFFSET); + LeaseSetDestination::HandleCloveI2NPMessage (typeID, buf + I2NP_HEADER_SIZE, GetI2NPMessageLength(buf, len) - I2NP_HEADER_SIZE, msgID); } - bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) + bool LeaseSetDestination::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) { switch (typeID) { diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 6695796d..2effff27 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -144,7 +144,7 @@ namespace client // implements GarlicDestination void HandleI2NPMessage (const uint8_t * buf, size_t len); - bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len); + bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID); void SetLeaseSet (std::shared_ptr newLeaseSet); int GetLeaseSetType () const { return m_LeaseSetType; }; diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index 62e9e423..e5b74624 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -1044,10 +1044,11 @@ namespace garlic { LogPrint (eLogDebug, "Garlic: type local"); I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid - buf += (4 + 4); // msgID + expiration + int32_t msgID = bufbe32toh (buf); buf += 4; // msgID + buf += 4; // expiration ptrdiff_t offset = buf - buf1; if (offset <= (int)len) - HandleCloveI2NPMessage (typeID, buf, len - offset); + HandleCloveI2NPMessage (typeID, buf, len - offset, msgID); else LogPrint (eLogError, "Garlic: clove is too long"); break; @@ -1066,13 +1067,14 @@ namespace garlic } uint32_t gwTunnel = bufbe32toh (buf); buf += 4; I2NPMessageType typeID = (I2NPMessageType)(buf[0]); buf++; // typeid - buf += (4 + 4); // msgID + expiration + uint32_t msgID = bufbe32toh (buf); buf += 4; // msgID + buf += 4; // expiration offset += 13; if (GetTunnelPool ()) { auto tunnel = GetTunnelPool ()->GetNextOutboundTunnel (); if (tunnel) - tunnel->SendTunnelDataMsg (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset)); + tunnel->SendTunnelDataMsg (gwHash, gwTunnel, CreateI2NPMessage (typeID, buf, len - offset, msgID)); else LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove"); } diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index 561f4ff7..b150e78d 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -264,7 +264,7 @@ namespace garlic void AddECIESx25519Key (const uint8_t * key, const uint8_t * tag); // one tag bool HandleECIESx25519TagMessage (uint8_t * buf, size_t len); // return true if found virtual void HandleI2NPMessage (const uint8_t * buf, size_t len) = 0; // called from clove only - virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) = 0; + virtual bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) = 0; void HandleGarlicMessage (std::shared_ptr msg); void HandleDeliveryStatusMessage (uint32_t msgID); diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index c25f5064..edabcdfa 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -821,9 +821,9 @@ namespace i2p i2p::HandleI2NPMessage (CreateI2NPMessage (buf, GetI2NPMessageLength (buf, len))); } - bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len) + bool RouterContext::HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID) { - auto msg = CreateI2NPMessage (typeID, payload, len); + auto msg = CreateI2NPMessage (typeID, payload, len, msgID); if (!msg) return false; i2p::HandleI2NPMessage (msg); return true; diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 978490ae..b52e20d9 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -154,7 +154,7 @@ namespace garlic // implements GarlicDestination void HandleI2NPMessage (const uint8_t * buf, size_t len); - bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len); + bool HandleCloveI2NPMessage (I2NPMessageType typeID, const uint8_t * payload, size_t len, uint32_t msgID); private: From cfbf5862f9643b6b74e5d90b6487eaa402dc7073 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Jul 2021 13:08:12 -0400 Subject: [PATCH 4350/6300] set pool for tunnel before build --- libi2pd/Tunnel.cpp | 24 +++++++++++++++--------- libi2pd/Tunnel.h | 7 ++++--- libi2pd/TunnelPool.cpp | 17 ++++++----------- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 9f096282..d0aaa482 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -111,7 +111,10 @@ namespace tunnel // add garlic key/tag for reply uint8_t key[32]; uint64_t tag = m_Config->GetLastHop ()->GetGarlicKey (key); - i2p::context.AddECIESx25519Key (key, tag); + if (m_Pool && m_Pool->GetLocalDestination ()) + m_Pool->GetLocalDestination ()->AddECIESx25519Key (key, tag); + else + i2p::context.AddECIESx25519Key (key, tag); } i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); } @@ -710,7 +713,7 @@ namespace tunnel LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }, - inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) + inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()), nullptr ); } } @@ -786,7 +789,7 @@ namespace tunnel } LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel"); CreateTunnel ( - std::make_shared (std::vector > { router->GetRouterIdentity () }) + std::make_shared (std::vector > { router->GetRouterIdentity () }), nullptr ); } } @@ -832,9 +835,11 @@ namespace tunnel } template - std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) + std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, + std::shared_ptr pool, std::shared_ptr outboundTunnel) { auto newTunnel = std::make_shared (config); + newTunnel->SetTunnelPool (pool); uint32_t replyMsgID; RAND_bytes ((uint8_t *)&replyMsgID, 4); AddPendingTunnel (replyMsgID, newTunnel); @@ -842,18 +847,19 @@ namespace tunnel return newTunnel; } - std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) + std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, + std::shared_ptr pool, std::shared_ptr outboundTunnel) { if (config) - return CreateTunnel(config, outboundTunnel); + return CreateTunnel(config, pool, outboundTunnel); else return CreateZeroHopsInboundTunnel (); } - std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config) + std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool) { if (config) - return CreateTunnel(config); + return CreateTunnel(config, pool); else return CreateZeroHopsOutboundTunnel (); } @@ -889,7 +895,7 @@ namespace tunnel { // build symmetric outbound tunnel CreateTunnel (std::make_shared(newTunnel->GetInvertedPeers (), - newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), + newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), nullptr, GetNextOutboundTunnel ()); } else diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index f30eab14..c85b734b 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -205,8 +205,8 @@ namespace tunnel void AddTransitTunnel (std::shared_ptr tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); - std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel); - std::shared_ptr CreateOutboundTunnel (std::shared_ptr config); + std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr pool, std::shared_ptr outboundTunnel); + std::shared_ptr CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool); void PostTunnelData (std::shared_ptr msg); void PostTunnelData (const std::vector >& msgs); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); @@ -219,7 +219,8 @@ namespace tunnel private: template - std::shared_ptr CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel = nullptr); + std::shared_ptr CreateTunnel (std::shared_ptr config, + std::shared_ptr pool, std::shared_ptr outboundTunnel = nullptr); template std::shared_ptr GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels); diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index faa28e69..8abab3d6 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -526,8 +526,7 @@ namespace tunnel std::reverse (peers.begin (), peers.end ()); config = std::make_shared (peers); } - auto tunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); - tunnel->SetTunnelPool (shared_from_this ()); + auto tunnel = tunnels.CreateInboundTunnel (config, shared_from_this (), outboundTunnel); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } @@ -551,10 +550,9 @@ namespace tunnel { config = std::make_shared(tunnel->GetPeers ()); } - if (m_NumInboundHops == 0 || config) + if (!m_NumInboundHops || config) { - auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); - newTunnel->SetTunnelPool (shared_from_this()); + auto newTunnel = tunnels.CreateInboundTunnel (config, shared_from_this(), outboundTunnel); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); } @@ -574,8 +572,7 @@ namespace tunnel std::shared_ptr config; if (m_NumOutboundHops > 0) config = std::make_shared(peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); - auto tunnel = tunnels.CreateOutboundTunnel (config); - tunnel->SetTunnelPool (shared_from_this ()); + auto tunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } @@ -606,8 +603,7 @@ namespace tunnel } if (!m_NumOutboundHops || config) { - auto newTunnel = tunnels.CreateOutboundTunnel (config); - newTunnel->SetTunnelPool (shared_from_this ()); + auto newTunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); } @@ -621,8 +617,7 @@ namespace tunnel LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); auto tunnel = tunnels.CreateInboundTunnel ( m_NumOutboundHops > 0 ? std::make_shared(outboundTunnel->GetInvertedPeers ()) : nullptr, - outboundTunnel); - tunnel->SetTunnelPool (shared_from_this ()); + shared_from_this (), outboundTunnel); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } From 911ab9813e76098b39435b6d2b3a9f6bf5767a0f Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Jul 2021 14:55:38 -0400 Subject: [PATCH 4351/6300] handle encrypteed I2NPShortTunnelBuildReply in destination --- libi2pd/Destination.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 62f8ed26..bc6ca31e 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -366,6 +366,9 @@ namespace client case eI2NPDatabaseSearchReply: HandleDatabaseSearchReplyMessage (payload, len); break; + case eI2NPShortTunnelBuildReply: // might come as garlic encrypted + i2p::HandleI2NPMessage (CreateI2NPMessage (typeID, payload, len, msgID)); + break; default: LogPrint (eLogWarning, "Destination: Unexpected I2NP message type ", typeID); return false; From f28024cfe849a1eb54f716df8671982d551392e6 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 21 Jul 2021 18:12:37 -0400 Subject: [PATCH 4352/6300] decline transit tunnels from short tunnel build message --- libi2pd/I2NPProtocol.cpp | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 1123407d..5c6e6e02 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -645,6 +645,11 @@ namespace i2p LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i); return; } + if (clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE]) // not AES + { + LogPrint (eLogWarning, "I2NP: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record"); + return; + } auto& noiseState = i2p::context.GetCurrentNoiseState (); uint8_t replyKey[32], layerKey[32], ivKey[32]; i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK); @@ -659,15 +664,27 @@ namespace i2p } else memcpy (ivKey, noiseState.m_CK , 32); - auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( - bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), - clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, - bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - layerKey, ivKey, - clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, - clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); - i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); - + + // check if we accept this tunnel + uint8_t retCode = 0; + if (!i2p::context.AcceptsTunnels () || + i2p::tunnel::tunnels.GetTransitTunnels ().size () > g_MaxNumTransitTunnels || + i2p::transport::transports.IsBandwidthExceeded () || + i2p::transport::transports.IsTransitBandwidthExceeded ()) + retCode = 30; + if (!retCode) + { + // create new transit tunnel + auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( + bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), + clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + layerKey, ivKey, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, + clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); + i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); + } + // encrypt reply uint8_t nonce[12]; memset (nonce, 0, 12); @@ -678,7 +695,7 @@ namespace i2p if (j == i) { memset (reply + SHORT_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options - reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = 0; // TODO: correct ret code + reply[SHORT_RESPONSE_RECORD_RET_OFFSET] = retCode; if (!i2p::crypto::AEADChaCha20Poly1305 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE - 16, noiseState.m_H, 32, replyKey, nonce, reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt { From 7078ca53c32f3619d5b61bb3aeaf62778d478dbd Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 22 Jul 2021 13:23:05 +0000 Subject: [PATCH 4353/6300] [debian] update patch for upnp --- debian/patches/02-upnp.patch | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/debian/patches/02-upnp.patch b/debian/patches/02-upnp.patch index 7b9bb317..5098b098 100644 --- a/debian/patches/02-upnp.patch +++ b/debian/patches/02-upnp.patch @@ -6,12 +6,12 @@ Last-Update: 2021-01-16 --- i2pd.orig/Makefile +++ i2pd/Makefile -@@ -15,7 +15,7 @@ include filelist.mk - USE_AESNI := yes - USE_STATIC := no - USE_MESHNET := no --USE_UPNP := no -+USE_UPNP := yes - DEBUG := yes - +@@ -21,7 +21,7 @@ include filelist.mk + USE_AESNI := $(or $(USE_AESNI),yes) + USE_STATIC := $(or $(USE_STATIC),no) + USE_MESHNET := $(or $(USE_MESHNET),no) +-USE_UPNP := $(or $(USE_UPNP),no) ++USE_UPNP := $(or $(USE_UPNP),yes) + DEBUG := $(or $(DEBUG),yes) + ifeq ($(DEBUG),yes) From 445c5f47ae58abe33dbc4be11c9cb6f09c8610cc Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 22 Jul 2021 13:24:42 +0000 Subject: [PATCH 4354/6300] [debian] update patch for upnp --- debian/patches/02-upnp.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/patches/02-upnp.patch b/debian/patches/02-upnp.patch index 5098b098..379b2f60 100644 --- a/debian/patches/02-upnp.patch +++ b/debian/patches/02-upnp.patch @@ -13,5 +13,5 @@ Last-Update: 2021-01-16 -USE_UPNP := $(or $(USE_UPNP),no) +USE_UPNP := $(or $(USE_UPNP),yes) DEBUG := $(or $(DEBUG),yes) - + ifeq ($(DEBUG),yes) From 28369faa0079eaf610557e5fd9ffc41a7e8dc60f Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 22 Jul 2021 13:35:58 +0000 Subject: [PATCH 4355/6300] [debian] fix tabulation in patch --- debian/patches/02-upnp.patch | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/debian/patches/02-upnp.patch b/debian/patches/02-upnp.patch index 379b2f60..75ea2bfa 100644 --- a/debian/patches/02-upnp.patch +++ b/debian/patches/02-upnp.patch @@ -7,11 +7,11 @@ Last-Update: 2021-01-16 --- i2pd.orig/Makefile +++ i2pd/Makefile @@ -21,7 +21,7 @@ include filelist.mk - USE_AESNI := $(or $(USE_AESNI),yes) - USE_STATIC := $(or $(USE_STATIC),no) - USE_MESHNET := $(or $(USE_MESHNET),no) --USE_UPNP := $(or $(USE_UPNP),no) -+USE_UPNP := $(or $(USE_UPNP),yes) - DEBUG := $(or $(DEBUG),yes) + USE_AESNI := $(or $(USE_AESNI),yes) + USE_STATIC := $(or $(USE_STATIC),no) + USE_MESHNET := $(or $(USE_MESHNET),no) +-USE_UPNP := $(or $(USE_UPNP),no) ++USE_UPNP := $(or $(USE_UPNP),yes) + DEBUG := $(or $(DEBUG),yes) ifeq ($(DEBUG),yes) From c153471c4980e5d0b29808d9c2a63a5600c9d663 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 22 Jul 2021 20:58:35 -0400 Subject: [PATCH 4356/6300] use short tunnel build if possible --- libi2pd/NetDb.hpp | 1 + libi2pd/TunnelPool.cpp | 63 +++++++++++++++++++-------- libi2pd/TunnelPool.h | 18 +++++--- libi2pd_client/MatchedDestination.cpp | 10 ++--- 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index ccab664b..c52be02b 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -45,6 +45,7 @@ namespace data const int NETDB_MAX_PUBLISH_EXCLUDED_FLOODFILLS = 15; const int NETDB_MIN_HIGHBANDWIDTH_VERSION = MAKE_VERSION_NUMBER(0, 9, 36); // 0.9.36 const int NETDB_MIN_FLOODFILL_VERSION = MAKE_VERSION_NUMBER(0, 9, 38); // 0.9.38 + const int NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION = MAKE_VERSION_NUMBER(0, 9, 51); // 0.9.51 /** function for visiting a leaseset stored in a floodfill */ typedef std::function)> LeaseSetVisitor; diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 8abab3d6..614a04f0 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -24,6 +24,22 @@ namespace i2p { namespace tunnel { + void Path::Add (std::shared_ptr r) + { + if (r) + { + peers.push_back (r->GetRouterIdentity ()); + if (r->GetVersion () < i2p::data::NETDB_MIN_SHORT_TUNNEL_BUILD_VERSION || + r->GetRouterIdentity ()->GetCryptoKeyType () != i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + isShort = false; + } + } + + void Path::Reverse () + { + std::reverse (peers.begin (), peers.end ()); + } + TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), @@ -420,7 +436,7 @@ namespace tunnel return hop; } - bool StandardSelectPeers(Path & peers, int numHops, bool inbound, SelectHopFunc nextHop) + bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop) { int start = 0; std::shared_ptr prevHop = i2p::context.GetSharedRouterInfo (); @@ -429,7 +445,7 @@ namespace tunnel /** if routes are restricted prepend trusted first hop */ auto hop = i2p::transport::transports.GetRestrictedPeer(); if(!hop) return false; - peers.push_back(hop->GetRouterIdentity()); + path.Add (hop); prevHop = hop; start++; } @@ -441,7 +457,7 @@ namespace tunnel (numHops > 1 || (r->IsV4 () && (!inbound || r->IsReachable ())))) // first inbound must be reachable { prevHop = r; - peers.push_back (r->GetRouterIdentity ()); + path.Add (r); start++; } } @@ -466,12 +482,12 @@ namespace tunnel if (hop1) hop = hop1; } prevHop = hop; - peers.push_back (hop->GetRouterIdentity ()); + path.Add (hop); } return true; } - bool TunnelPool::SelectPeers (std::vector >& peers, bool isInbound) + bool TunnelPool::SelectPeers (Path& path, bool isInbound) { int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; // peers is empty @@ -480,14 +496,14 @@ namespace tunnel { std::lock_guard lock(m_CustomPeerSelectorMutex); if (m_CustomPeerSelector) - return m_CustomPeerSelector->SelectPeers(peers, numHops, isInbound); + return m_CustomPeerSelector->SelectPeers(path, numHops, isInbound); } // explicit peers in use - if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); - return StandardSelectPeers(peers, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1, std::placeholders::_2)); + if (m_ExplicitPeers) return SelectExplicitPeers (path, isInbound); + return StandardSelectPeers(path, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1, std::placeholders::_2)); } - bool TunnelPool::SelectExplicitPeers (std::vector >& peers, bool isInbound) + bool TunnelPool::SelectExplicitPeers (Path& path, bool isInbound) { int size = m_ExplicitPeers->size (); std::vector peerIndicies; @@ -500,7 +516,7 @@ namespace tunnel auto& ident = (*m_ExplicitPeers)[peerIndicies[i]]; auto r = i2p::data::netdb.FindRouter (ident); if (r) - peers.push_back (r->GetRouterIdentity ()); + path.Add (r); else { LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ()); @@ -517,14 +533,14 @@ namespace tunnel if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Creating destination inbound tunnel..."); - std::vector > peers; - if (SelectPeers (peers, true)) + Path path; + if (SelectPeers (path, true)) { std::shared_ptr config; if (m_NumInboundHops > 0) { - std::reverse (peers.begin (), peers.end ()); - config = std::make_shared (peers); + path.Reverse (); + config = std::make_shared (path.peers, path.isShort); } auto tunnel = tunnels.CreateInboundTunnel (config, shared_from_this (), outboundTunnel); if (tunnel->IsEstablished ()) // zero hops @@ -566,14 +582,23 @@ namespace tunnel if (inboundTunnel) { LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); - std::vector > peers; - if (SelectPeers (peers, false)) + Path path; + if (SelectPeers (path, false)) { std::shared_ptr config; if (m_NumOutboundHops > 0) - config = std::make_shared(peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); - auto tunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); - if (tunnel->IsEstablished ()) // zero hops + config = std::make_shared(path.peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash (), path.isShort); + + std::shared_ptr tunnel; + if (path.isShort) + { + // TODO: implement it better + tunnel = tunnels.CreateOutboundTunnel (config, inboundTunnel->GetTunnelPool ()); + tunnel->SetTunnelPool (shared_from_this ()); + } + else + tunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); + if (tunnel && tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } else diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index 164ca7a1..97ca0419 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -36,7 +36,14 @@ namespace tunnel class OutboundTunnel; typedef std::shared_ptr Peer; - typedef std::vector Path; + struct Path + { + std::vector peers; + bool isShort = true; + + void Add (std::shared_ptr r); + void Reverse (); + }; /** interface for custom tunnel peer selection algorithm */ struct ITunnelPeerSelector @@ -47,9 +54,8 @@ namespace tunnel typedef std::function(std::shared_ptr, bool)> SelectHopFunc; - // standard peer selection algorithm - bool StandardSelectPeers(Path & path, int hops, bool inbound, SelectHopFunc nextHop); - + bool StandardSelectPeers(Path & path, int numHops, bool inbound, SelectHopFunc nextHop); + class TunnelPool: public std::enable_shared_from_this // per local destination { public: @@ -114,8 +120,8 @@ namespace tunnel void CreatePairedInboundTunnel (std::shared_ptr outboundTunnel); template typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; - bool SelectPeers (std::vector >& hops, bool isInbound); - bool SelectExplicitPeers (std::vector >& hops, bool isInbound); + bool SelectPeers (Path& path, bool isInbound); + bool SelectExplicitPeers (Path& path, bool isInbound); private: diff --git a/libi2pd_client/MatchedDestination.cpp b/libi2pd_client/MatchedDestination.cpp index c0766096..8d17632b 100644 --- a/libi2pd_client/MatchedDestination.cpp +++ b/libi2pd_client/MatchedDestination.cpp @@ -79,23 +79,23 @@ namespace client if(!inbound && m_RemoteLeaseSet) { if(m_RemoteLeaseSet->IsExpired()) - { ResolveCurrentLeaseSet(); - } if(m_RemoteLeaseSet && !m_RemoteLeaseSet->IsExpired()) { // remote lease set is good auto leases = m_RemoteLeaseSet->GetNonExpiredLeases(); // pick lease std::shared_ptr obep; - while(!obep && leases.size() > 0) { + while(!obep && leases.size() > 0) + { auto idx = rand() % leases.size(); auto lease = leases[idx]; obep = i2p::data::netdb.FindRouter(lease->tunnelGateway); leases.erase(leases.begin()+idx); } - if(obep) { - path.push_back(obep->GetRouterIdentity()); + if(obep) + { + path.Add (obep); LogPrint(eLogDebug, "Destination: found OBEP matching IBGW"); } else LogPrint(eLogWarning, "Destination: could not find proper IBGW for matched outbound tunnel"); From c7234f705a5a74dc5169ae5d0847c2376d368b5e Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 23 Jul 2021 18:34:51 -0400 Subject: [PATCH 4357/6300] let NTCP sync through ipv6 --- libi2pd/Timestamp.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index 3cd336ed..4664061f 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -60,14 +60,14 @@ namespace util { LogPrint (eLogInfo, "Timestamp: NTP request to ", address); boost::asio::io_service service; - boost::asio::ip::udp::resolver::query query (boost::asio::ip::udp::v4 (), address, "ntp"); boost::system::error_code ec; - auto it = boost::asio::ip::udp::resolver (service).resolve (query, ec); + auto it = boost::asio::ip::udp::resolver (service).resolve ( + boost::asio::ip::udp::resolver::query (address, "ntp"), ec); if (!ec && it != boost::asio::ip::udp::resolver::iterator()) { auto ep = (*it).endpoint (); // take first one boost::asio::ip::udp::socket socket (service); - socket.open (boost::asio::ip::udp::v4 (), ec); + socket.open (ep.protocol (), ec); if (!ec) { uint8_t buf[48];// 48 bytes NTP request/response @@ -103,7 +103,7 @@ namespace util LogPrint (eLogError, "Timestamp: Couldn't open UDP socket"); } else - LogPrint (eLogError, "Timestamp: Couldn't resove address ", address); + LogPrint (eLogError, "Timestamp: Couldn't resolve address ", address); } NTPTimeSync::NTPTimeSync (): m_IsRunning (false), m_Timer (m_Service) From 26d5ced2efce37eb80662bcc7984eca36dd361dc Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 23 Jul 2021 20:28:55 -0400 Subject: [PATCH 4358/6300] optimal padding for one-time messages --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 26 ++++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 9c76320b..8faf1cd9 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -1104,7 +1104,8 @@ namespace garlic return true; } - static size_t CreateGarlicPayload (std::shared_ptr msg, uint8_t * payload, bool datetime) + static size_t CreateGarlicPayload (std::shared_ptr msg, uint8_t * payload, + bool datetime, size_t optimalSize) { size_t len = 0; if (datetime) @@ -1129,11 +1130,20 @@ namespace garlic len += cloveSize + 3; payload += cloveSize; // padding - uint8_t paddingSize = rand () & 0x0F; // 0 - 15 - payload[0] = eECIESx25519BlkPadding; - htobe16buf (payload + 1, paddingSize); - if (paddingSize) memset (payload + 3, 0, paddingSize); - len += paddingSize + 3; + int delta = (int)optimalSize - (int)len; + if (delta < 0 || delta > 3) // don't create padding if we are close to optimal size + { + uint8_t paddingSize = rand () & 0x0F; // 0 - 15 + if (delta > 3) + { + delta -= 3; + if (paddingSize > delta) paddingSize %= delta; + } + payload[0] = eECIESx25519BlkPadding; + htobe16buf (payload + 1, paddingSize); + if (paddingSize) memset (payload + 3, 0, paddingSize); + len += paddingSize + 3; + } return len; } @@ -1145,7 +1155,7 @@ namespace garlic size_t offset = 0; memcpy (buf + offset, &tag, 8); offset += 8; auto payload = buf + offset; - size_t len = CreateGarlicPayload (msg, payload, false); + size_t len = CreateGarlicPayload (msg, payload, false, 956); // 1003 - 8 tag - 16 Poly1305 hash - 16 I2NP header - 4 garlic length - 3 local tunnel delivery uint8_t nonce[12]; memset (nonce, 0, 12); // n = 0 if (!i2p::crypto::AEADChaCha20Poly1305 (payload, len, buf, 8, key, nonce, payload, len + 16, true)) // encrypt @@ -1181,7 +1191,7 @@ namespace garlic } noiseState.MixKey (sharedSecret); auto payload = buf + offset; - size_t len = CreateGarlicPayload (msg, payload, true); + size_t len = CreateGarlicPayload (msg, payload, true, 900); // 1003 - 32 eph key - 16 Poly1305 hash - 16 I2NP header - 4 garlic length - 35 router tunnel delivery uint8_t nonce[12]; memset (nonce, 0, 12); // encrypt payload From cd8e8970deb28a87d2d3b554d62139c351dfe032 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 24 Jul 2021 16:01:11 -0400 Subject: [PATCH 4359/6300] NTP request through compatible address --- libi2pd/Timestamp.cpp | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/libi2pd/Timestamp.cpp b/libi2pd/Timestamp.cpp index 4664061f..ec0c25f2 100644 --- a/libi2pd/Timestamp.cpp +++ b/libi2pd/Timestamp.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,6 +16,7 @@ #include #include "Config.h" #include "Log.h" +#include "RouterContext.h" #include "I2PEndian.h" #include "Timestamp.h" #include "util.h" @@ -63,9 +64,38 @@ namespace util boost::system::error_code ec; auto it = boost::asio::ip::udp::resolver (service).resolve ( boost::asio::ip::udp::resolver::query (address, "ntp"), ec); - if (!ec && it != boost::asio::ip::udp::resolver::iterator()) + if (!ec) { - auto ep = (*it).endpoint (); // take first one + bool found = false; + boost::asio::ip::udp::resolver::iterator end; + boost::asio::ip::udp::endpoint ep; + while (it != end) + { + ep = *it; + if (!ep.address ().is_unspecified ()) + { + if (ep.address ().is_v4 ()) + { + if (i2p::context.SupportsV4 ()) found = true; + } + else if (ep.address ().is_v6 ()) + { + if (i2p::util::net::IsYggdrasilAddress (ep.address ())) + { + if (i2p::context.SupportsMesh ()) found = true; + } + else if (i2p::context.SupportsV6 ()) found = true; + } + } + if (found) break; + it++; + } + if (!found) + { + LogPrint (eLogError, "Timestamp: can't find compatible address for ", address); + return; + } + boost::asio::ip::udp::socket socket (service); socket.open (ep.protocol (), ec); if (!ec) From 99c7d5c23a08526357d3cdf97b1af8a656297139 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 25 Jul 2021 22:30:54 -0400 Subject: [PATCH 4360/6300] don't create enryptor for ECIES record encryption --- libi2pd/TunnelConfig.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 45e8a12a..543ba0fa 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -131,17 +131,14 @@ namespace tunnel void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted) { - auto encryptor = ident->CreateEncryptor (nullptr); - if (!encryptor) return; - uint8_t hepk[32]; - encryptor->Encrypt (nullptr, hepk, nullptr, false); - i2p::crypto::InitNoiseNState (*this, hepk); + if (!ident) return; + i2p::crypto::InitNoiseNState (*this, ident->GetEncryptionPublicKey ()); auto ephemeralKeys = i2p::transport::transports.GetNextX25519KeysPair (); memcpy (encrypted, ephemeralKeys->GetPublicKey (), 32); MixHash (encrypted, 32); // h = SHA256(h || sepk) encrypted += 32; uint8_t sharedSecret[32]; - ephemeralKeys->Agree (hepk, sharedSecret); // x25519(sesk, hepk) + ephemeralKeys->Agree (ident->GetEncryptionPublicKey (), sharedSecret); // x25519(sesk, hepk) MixKey (sharedSecret); uint8_t nonce[12]; memset (nonce, 0, 12); From a6937c792fba9c3736fdd5c6b39c70498e74136f Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 26 Jul 2021 17:51:32 -0400 Subject: [PATCH 4361/6300] more precise router selection --- libi2pd/NetDb.cpp | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 1f63c054..c8d8dc38 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1223,24 +1223,34 @@ namespace data { if (m_RouterInfos.empty()) return 0; - uint32_t ind = rand () % m_RouterInfos.size (); - for (int j = 0; j < 2; j++) + std::unique_lock l(m_RouterInfosMutex); + auto ind = rand () % m_RouterInfos.size (); + auto it = m_RouterInfos.begin (); + std::advance (it, ind); + // try random router + if (it != m_RouterInfos.end () && !it->second->IsUnreachable () && filter (it->second)) + return it->second; + // try closest routers + auto it1 = it; it1++; + // forward + while (it1 != m_RouterInfos.end ()) { - uint32_t i = 0; - std::unique_lock l(m_RouterInfosMutex); - for (const auto& it: m_RouterInfos) + if (!it1->second->IsUnreachable () && filter (it1->second)) + return it1->second; + it1++; + } + // still not found, try from the beginning + if (ind) + { + it1 = m_RouterInfos.begin (); + while (it1 != it || it1 != m_RouterInfos.end ()) { - if (i >= ind) - { - if (!it.second->IsUnreachable () && filter (it.second)) - return it.second; - } - else - i++; - } - // we couldn't find anything, try second pass - ind = 0; - } + if (!it1->second->IsUnreachable () && filter (it1->second)) + return it1->second; + it1++; + } + } + return nullptr; // seems we have too few routers } From 513493fa78973bb81540bd1d39145f1b48963335 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 26 Jul 2021 18:46:29 -0400 Subject: [PATCH 4362/6300] fixed typo --- libi2pd/NetDb.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index c8d8dc38..429d06b2 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1243,10 +1243,10 @@ namespace data if (ind) { it1 = m_RouterInfos.begin (); - while (it1 != it || it1 != m_RouterInfos.end ()) + while (it1 != it && it1 != m_RouterInfos.end ()) { if (!it1->second->IsUnreachable () && filter (it1->second)) - return it1->second; + return it1->second; it1++; } } From e68cff8bba374a2d06478a6732886cf76b452afe Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 27 Jul 2021 18:35:30 -0400 Subject: [PATCH 4363/6300] try routers before random router --- libi2pd/NetDb.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 429d06b2..edbd7800 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1230,25 +1230,38 @@ namespace data // try random router if (it != m_RouterInfos.end () && !it->second->IsUnreachable () && filter (it->second)) return it->second; - // try closest routers + // try routers after auto it1 = it; it1++; - // forward while (it1 != m_RouterInfos.end ()) { if (!it1->second->IsUnreachable () && filter (it1->second)) return it1->second; it1++; } - // still not found, try from the beginning + // still not found, try some routers before if (ind) { + ind = rand () % ind; it1 = m_RouterInfos.begin (); - while (it1 != it && it1 != m_RouterInfos.end ()) + std::advance (it1, ind); + auto it2 = it1; + while (it2 != it && it2 != m_RouterInfos.end ()) { - if (!it1->second->IsUnreachable () && filter (it1->second)) - return it1->second; - it1++; - } + if (!it2->second->IsUnreachable () && filter (it2->second)) + return it2->second; + it2++; + } + if (ind) + { + // still not found, try from the begining + it2 = m_RouterInfos.begin (); + while (it2 != it1 && it2 != m_RouterInfos.end ()) + { + if (!it2->second->IsUnreachable () && filter (it2->second)) + return it2->second; + it2++; + } + } } return nullptr; // seems we have too few routers From 9a3c22f47d42b88a6ff42ee7d4cb70b856d6decf Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 28 Jul 2021 15:06:24 -0400 Subject: [PATCH 4364/6300] don't encrypt ShortTunnelBuild and ShortTunnelBuildReply if on the same router --- libi2pd/I2NPProtocol.cpp | 30 ++++++++++++++++++++++-------- libi2pd/Tunnel.cpp | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 5c6e6e02..54924451 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -709,17 +709,31 @@ namespace i2p } // send reply if (isEndpoint) - { + { auto replyMsg = NewI2NPShortMessage (); replyMsg->Concat (buf, len); replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); - i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); - uint64_t tag; - memcpy (&tag, noiseState.m_CK, 8); - // we send it to reply tunnel - transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); + if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (), + clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local? + { + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); + uint64_t tag; + memcpy (&tag, noiseState.m_CK, 8); + // we send it to reply tunnel + transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); + } + else + { + // IBGW is local + uint32_t tunnelID = bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET); + auto tunnel = i2p::tunnel::tunnels.GetTunnel (tunnelID); + if (tunnel) + tunnel->SendTunnelDataMsg (replyMsg); + else + LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply"); + } } else transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index d0aaa482..54b24d37 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -96,7 +96,7 @@ namespace tunnel if (m_Config->IsShort ()) { auto ident = m_Config->GetFirstHop () ? m_Config->GetFirstHop ()->ident : nullptr; - if (ident) + if (ident && ident->GetIdentHash () != outboundTunnel->GetNextIdentHash ()) // don't encrypt if IBGW = OBEP { auto msg1 = i2p::garlic::WrapECIESX25519MessageForRouter (msg, ident->GetEncryptionPublicKey ()); if (msg1) msg = msg1; From f8623b612148c490a9148d91d916226e10646da6 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 28 Jul 2021 19:08:55 -0400 Subject: [PATCH 4365/6300] consistent path for explicit peers --- libi2pd/TunnelPool.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 614a04f0..1745ebb8 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -505,15 +505,12 @@ namespace tunnel bool TunnelPool::SelectExplicitPeers (Path& path, bool isInbound) { - int size = m_ExplicitPeers->size (); - std::vector peerIndicies; - for (int i = 0; i < size; i++) peerIndicies.push_back(i); - std::shuffle (peerIndicies.begin(), peerIndicies.end(), std::mt19937(std::random_device()())); - int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; + if (numHops > (int)m_ExplicitPeers->size ()) numHops = m_ExplicitPeers->size (); + if (!numHops) return false; for (int i = 0; i < numHops; i++) { - auto& ident = (*m_ExplicitPeers)[peerIndicies[i]]; + auto& ident = (*m_ExplicitPeers)[i]; auto r = i2p::data::netdb.FindRouter (ident); if (r) path.Add (r); From 7a55d1fc38451c40ec4c215106a85ee0b8954e95 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 28 Jul 2021 21:14:03 -0400 Subject: [PATCH 4366/6300] don't insert garlic tag for short tunnel build reply if the same router --- libi2pd/Tunnel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 54b24d37..416519a4 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -106,7 +106,8 @@ namespace tunnel } else { - if (m_Config->IsShort () && m_Config->GetLastHop ()) + if (m_Config->IsShort () && m_Config->GetLastHop () && + m_Config->GetLastHop ()->ident->GetIdentHash () != m_Config->GetLastHop ()->nextIdent) { // add garlic key/tag for reply uint8_t key[32]; From b16b753ed2e1d47702072fd6d8f5d935ab8a8fbb Mon Sep 17 00:00:00 2001 From: TomasGl Date: Fri, 30 Jul 2021 17:49:19 +0300 Subject: [PATCH 4367/6300] Change default irc server to IRC ILITA (#1677) --- contrib/tunnels.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/tunnels.conf b/contrib/tunnels.conf index 3358ffc4..55723c43 100644 --- a/contrib/tunnels.conf +++ b/contrib/tunnels.conf @@ -1,16 +1,16 @@ -[IRC-IRC2P] +[IRC-ILITA] type = client address = 127.0.0.1 port = 6668 -destination = irc.postman.i2p +destination = irc.ilita.i2p destinationport = 6667 keys = irc-keys.dat -#[IRC-ILITA] +#[IRC-IRC2P] #type = client #address = 127.0.0.1 #port = 6669 -#destination = irc.ilita.i2p +#destination = irc.postman.i2p #destinationport = 6667 #keys = irc-keys.dat From 1e01c30e63b45c3ccf9bb1b1189d485509426c12 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 30 Jul 2021 14:12:50 -0400 Subject: [PATCH 4368/6300] set pool for zero-hops tunnels --- libi2pd/Tunnel.cpp | 14 ++++++++------ libi2pd/Tunnel.h | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 416519a4..f1f0d087 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -763,8 +763,8 @@ namespace tunnel if (m_InboundTunnels.empty ()) { LogPrint (eLogDebug, "Tunnel: Creating zero hops inbound tunnel"); - CreateZeroHopsInboundTunnel (); - CreateZeroHopsOutboundTunnel (); + CreateZeroHopsInboundTunnel (nullptr); + CreateZeroHopsOutboundTunnel (nullptr); if (!m_ExploratoryPool) { int ibLen; i2p::config::GetOption("exploratory.inbound.length", ibLen); @@ -854,7 +854,7 @@ namespace tunnel if (config) return CreateTunnel(config, pool, outboundTunnel); else - return CreateZeroHopsInboundTunnel (); + return CreateZeroHopsInboundTunnel (pool); } std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config, std::shared_ptr pool) @@ -862,7 +862,7 @@ namespace tunnel if (config) return CreateTunnel(config, pool); else - return CreateZeroHopsOutboundTunnel (); + return CreateZeroHopsOutboundTunnel (pool); } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) @@ -912,18 +912,20 @@ namespace tunnel } - std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel () + std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel (std::shared_ptr pool) { auto inboundTunnel = std::make_shared (); + inboundTunnel->SetTunnelPool (pool); inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; return inboundTunnel; } - std::shared_ptr Tunnels::CreateZeroHopsOutboundTunnel () + std::shared_ptr Tunnels::CreateZeroHopsOutboundTunnel (std::shared_ptr pool) { auto outboundTunnel = std::make_shared (); + outboundTunnel->SetTunnelPool (pool); outboundTunnel->SetState (eTunnelStateEstablished); m_OutboundTunnels.push_back (outboundTunnel); // we don't insert into m_Tunnels diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index c85b734b..acfa21e8 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -237,8 +237,8 @@ namespace tunnel void ManagePendingTunnels (PendingTunnels& pendingTunnels); void ManageTunnelPools (uint64_t ts); - std::shared_ptr CreateZeroHopsInboundTunnel (); - std::shared_ptr CreateZeroHopsOutboundTunnel (); + std::shared_ptr CreateZeroHopsInboundTunnel (std::shared_ptr pool); + std::shared_ptr CreateZeroHopsOutboundTunnel (std::shared_ptr pool); private: From d88fe203e1296bea928726f9f6d257b9d661e3a2 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 1 Aug 2021 09:25:02 +0300 Subject: [PATCH 4369/6300] [tunnels] count outbound traffic for zero-hop tunnels Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 2 +- libi2pd/Tunnel.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index fd26d8f1..283313fb 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -492,7 +492,7 @@ namespace http { s << "

    "; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); s << "
    \r\n"; } diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index f1f0d087..e7b38686 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -328,10 +328,11 @@ namespace tunnel for (auto& msg : msgs) { if (!msg.data) continue; + m_NumSentBytes += msg.data->GetLength (); switch (msg.deliveryType) { case eDeliveryTypeLocal: - i2p::HandleI2NPMessage (msg.data); + HandleI2NPMessage (msg.data); break; case eDeliveryTypeTunnel: i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); From da7e41c188ced4921c2963a71da0396dc26bbb71 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 1 Aug 2021 18:42:13 -0400 Subject: [PATCH 4370/6300] use Tag<64> for ratechet tags --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 12 ++++++------ libi2pd/ECIESX25519AEADRatchetSession.h | 11 +---------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 8faf1cd9..5ff2ae5c 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -31,16 +31,16 @@ namespace garlic uint8_t keydata[64]; i2p::crypto::HKDF (rootKey, k, 32, "KDFDHRatchetStep", keydata); // keydata = HKDF(rootKey, k, "KDFDHRatchetStep", 64) memcpy (m_NextRootKey, keydata, 32); // nextRootKey = keydata[0:31] - i2p::crypto::HKDF (keydata + 32, nullptr, 0, "TagAndKeyGenKeys", m_KeyData.buf); + i2p::crypto::HKDF (keydata + 32, nullptr, 0, "TagAndKeyGenKeys", m_SessionTagKeyData); // [sessTag_ck, symmKey_ck] = HKDF(keydata[32:63], ZEROLEN, "TagAndKeyGenKeys", 64) - memcpy (m_SymmKeyCK, m_KeyData.buf + 32, 32); + memcpy (m_SymmKeyCK, (const uint8_t *)m_SessionTagKeyData + 32, 32); m_NextSymmKeyIndex = 0; } void RatchetTagSet::NextSessionTagRatchet () { - i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), nullptr, 0, "STInitialization", m_KeyData.buf); // [sessTag_ck, sesstag_constant] = HKDF(sessTag_ck, ZEROLEN, "STInitialization", 64) - memcpy (m_SessTagConstant, m_KeyData.GetSessTagConstant (), 32); + i2p::crypto::HKDF (m_SessionTagKeyData, nullptr, 0, "STInitialization", m_SessionTagKeyData); // [sessTag_ck, sesstag_constant] = HKDF(sessTag_ck, ZEROLEN, "STInitialization", 64) + memcpy (m_SessTagConstant, (const uint8_t *)m_SessionTagKeyData + 32, 32); // SESSTAG_CONSTANT = keydata[32:63] m_NextIndex = 0; } @@ -52,8 +52,8 @@ namespace garlic LogPrint (eLogError, "Garlic: Tagset ", GetTagSetID (), " is empty"); return 0; } - i2p::crypto::HKDF (m_KeyData.GetSessTagCK (), m_SessTagConstant, 32, "SessionTagKeyGen", m_KeyData.buf); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) - return m_KeyData.GetTag (); + i2p::crypto::HKDF (m_SessionTagKeyData, m_SessTagConstant, 32, "SessionTagKeyGen", m_SessionTagKeyData); // [sessTag_ck, tag] = HKDF(sessTag_chainkey, SESSTAG_CONSTANT, "SessionTagKeyGen", 64) + return m_SessionTagKeyData.GetLL ()[4]; // tag = keydata[32:39] } void RatchetTagSet::GetSymmKey (int index, uint8_t * key) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h index aa72e4b0..5fd2c929 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.h +++ b/libi2pd/ECIESX25519AEADRatchetSession.h @@ -60,16 +60,7 @@ namespace garlic private: - union - { - uint64_t ll[8]; - uint8_t buf[64]; - - const uint8_t * GetSessTagCK () const { return buf; }; // sessTag_chainKey = keydata[0:31] - const uint8_t * GetSessTagConstant () const { return buf + 32; }; // SESSTAG_CONSTANT = keydata[32:63] - uint64_t GetTag () const { return ll[4]; }; // tag = keydata[32:39] - - } m_KeyData; + i2p::data::Tag<64> m_SessionTagKeyData; uint8_t m_SessTagConstant[32], m_SymmKeyCK[32], m_CurrentSymmKeyCK[64], m_NextRootKey[32]; int m_NextIndex, m_NextSymmKeyIndex; std::unordered_map > m_ItermediateSymmKeys; From 367df4d0dbb90b1b272a9575343304f0dc5acc8c Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Aug 2021 15:43:58 -0400 Subject: [PATCH 4371/6300] RAND_bytes from random router selection --- libi2pd/NetDb.cpp | 75 ++++++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index edbd7800..825242b5 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1223,47 +1223,54 @@ namespace data { if (m_RouterInfos.empty()) return 0; + uint16_t inds[3]; + RAND_bytes ((uint8_t *)inds, sizeof (inds)); std::unique_lock l(m_RouterInfosMutex); - auto ind = rand () % m_RouterInfos.size (); + inds[0] %= m_RouterInfos.size (); auto it = m_RouterInfos.begin (); - std::advance (it, ind); + std::advance (it, inds[0]); // try random router if (it != m_RouterInfos.end () && !it->second->IsUnreachable () && filter (it->second)) return it->second; - // try routers after - auto it1 = it; it1++; - while (it1 != m_RouterInfos.end ()) + // try some routers around + auto it1 = m_RouterInfos.begin (); + if (inds[0]) { - if (!it1->second->IsUnreachable () && filter (it1->second)) - return it1->second; - it1++; + // before + inds[1] %= inds[0]; + std::advance (it1, inds[1]); } - // still not found, try some routers before - if (ind) - { - ind = rand () % ind; - it1 = m_RouterInfos.begin (); - std::advance (it1, ind); - auto it2 = it1; - while (it2 != it && it2 != m_RouterInfos.end ()) - { - if (!it2->second->IsUnreachable () && filter (it2->second)) - return it2->second; - it2++; - } - if (ind) - { - // still not found, try from the begining - it2 = m_RouterInfos.begin (); - while (it2 != it1 && it2 != m_RouterInfos.end ()) - { - if (!it2->second->IsUnreachable () && filter (it2->second)) - return it2->second; - it2++; - } - } - } - + auto it2 = it; + if (inds[0] < m_RouterInfos.size () - 1) + { + // after + inds[2] %= (m_RouterInfos.size () - 1 - inds[0]); + std::advance (it2, inds[2]); + } + // it1 - from, it2 - to + it = it1; + while (it != it2 && it != m_RouterInfos.end ()) + { + if (!it->second->IsUnreachable () && filter (it->second)) + return it->second; + it++; + } + // still not found, try from the begining + it = m_RouterInfos.begin (); + while (it != it1 && it != m_RouterInfos.end ()) + { + if (!it->second->IsUnreachable () && filter (it->second)) + return it->second; + it++; + } + // still not found, try to the begining + it = it2; + while (it != m_RouterInfos.end ()) + { + if (!it->second->IsUnreachable () && filter (it->second)) + return it->second; + it++; + } return nullptr; // seems we have too few routers } From 64ec7dd559e9fe11dd28c1ea0fbeb664b50f4127 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 3 Aug 2021 19:26:09 -0400 Subject: [PATCH 4372/6300] narrow down random range --- libi2pd/NetDb.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 825242b5..4bc144e4 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1237,14 +1237,16 @@ namespace data if (inds[0]) { // before - inds[1] %= inds[0]; - std::advance (it1, inds[1]); + inds[1] %= inds[0]; + std::advance (it1, (inds[1] + inds[0])/2); } + else + it1 = it; auto it2 = it; if (inds[0] < m_RouterInfos.size () - 1) { // after - inds[2] %= (m_RouterInfos.size () - 1 - inds[0]); + inds[2] %= (m_RouterInfos.size () - 1 - inds[0]); inds[2] /= 2; std::advance (it2, inds[2]); } // it1 - from, it2 - to From 37f1a551479eab1627c842b1484869a9b2f98092 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 6 Aug 2021 12:32:21 -0400 Subject: [PATCH 4373/6300] encryption type 0,4 by default for server tunnel --- libi2pd_client/ClientContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 94311297..f8bc5667 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -457,7 +457,7 @@ namespace client options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); options[I2CP_PARAM_STREAMING_ANSWER_PINGS] = GetI2CPOption(section, I2CP_PARAM_STREAMING_ANSWER_PINGS, isServer ? DEFAULT_ANSWER_PINGS : false); options[I2CP_PARAM_LEASESET_TYPE] = GetI2CPOption(section, I2CP_PARAM_LEASESET_TYPE, DEFAULT_LEASESET_TYPE); - std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, isServer ? "" : "0,4"); + std::string encType = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_ENCRYPTION_TYPE, "0,4"); if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType; std::string privKey = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_PRIV_KEY, ""); if (privKey.length () > 0) options[I2CP_PARAM_LEASESET_PRIV_KEY] = privKey; From 28a055bd78d6d3c413a74ca9dc1fa6ac3019b354 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 6 Aug 2021 17:42:08 +0000 Subject: [PATCH 4374/6300] [webconsole] add external CSS support (#1682) Signed-off-by: R4SAS --- contrib/i18n/English.po | 314 ++++++++++++++++++----------------- contrib/i18n/README.md | 29 ++++ contrib/i18n/regex.txt | 10 -- contrib/webconsole/style.css | 245 +++++++++++++++++++++++++++ daemon/HTTPServer.cpp | 172 +++++++++++-------- i18n/I18N_langs.h | 13 +- i18n/Russian.cpp | 19 +-- i18n/Turkmen.cpp | 288 +++++++++++++++----------------- i18n/Ukrainian.cpp | 16 +- 9 files changed, 689 insertions(+), 417 deletions(-) create mode 100644 contrib/i18n/README.md delete mode 100644 contrib/i18n/regex.txt create mode 100644 contrib/webconsole/style.css diff --git a/contrib/i18n/English.po b/contrib/i18n/English.po index 76d58390..5a7cc09c 100644 --- a/contrib/i18n/English.po +++ b/contrib/i18n/English.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: i2pd\n" "Report-Msgid-Bugs-To: https://github.com/PurpleI2P/i2pd/issues\n" -"POT-Creation-Date: 2021-06-15 17:40\n" +"POT-Creation-Date: 2021-08-06 17:12\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -18,556 +18,564 @@ msgstr "" "X-Poedit-SearchPath-0: daemon/HTTPServer.cpp\n" "X-Poedit-SearchPath-1: libi2pd_client/HTTPProxy.cpp\n" -#: daemon/HTTPServer.cpp:85 -msgid "Disabled" -msgstr "" - -#: daemon/HTTPServer.cpp:86 -msgid "Enabled" -msgstr "" - -#: daemon/HTTPServer.cpp:147 +#: daemon/HTTPServer.cpp:175 msgid "day" msgid_plural "days" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:151 +#: daemon/HTTPServer.cpp:179 msgid "hour" msgid_plural "hours" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:155 +#: daemon/HTTPServer.cpp:183 msgid "minute" msgid_plural "minutes" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:158 +#: daemon/HTTPServer.cpp:186 msgid "second" msgid_plural "seconds" msgstr[0] "" msgstr[1] "" #. tr: Kibibit -#: daemon/HTTPServer.cpp:166 daemon/HTTPServer.cpp:194 +#: daemon/HTTPServer.cpp:194 daemon/HTTPServer.cpp:222 msgid "KiB" msgstr "" #. tr: Mebibit -#: daemon/HTTPServer.cpp:168 +#: daemon/HTTPServer.cpp:196 msgid "MiB" msgstr "" #. tr: Gibibit -#: daemon/HTTPServer.cpp:170 +#: daemon/HTTPServer.cpp:198 msgid "GiB" msgstr "" -#: daemon/HTTPServer.cpp:187 +#: daemon/HTTPServer.cpp:215 msgid "building" msgstr "" -#: daemon/HTTPServer.cpp:188 +#: daemon/HTTPServer.cpp:216 msgid "failed" msgstr "" -#: daemon/HTTPServer.cpp:189 +#: daemon/HTTPServer.cpp:217 msgid "expiring" msgstr "" -#: daemon/HTTPServer.cpp:190 +#: daemon/HTTPServer.cpp:218 msgid "established" msgstr "" -#: daemon/HTTPServer.cpp:191 +#: daemon/HTTPServer.cpp:219 msgid "unknown" msgstr "" -#: daemon/HTTPServer.cpp:193 +#: daemon/HTTPServer.cpp:221 msgid "exploratory" msgstr "" -#: daemon/HTTPServer.cpp:229 +#: daemon/HTTPServer.cpp:257 msgid "i2pd webconsole" msgstr "" -#: daemon/HTTPServer.cpp:232 +#: daemon/HTTPServer.cpp:260 msgid "Main page" msgstr "" -#: daemon/HTTPServer.cpp:233 daemon/HTTPServer.cpp:690 +#: daemon/HTTPServer.cpp:261 daemon/HTTPServer.cpp:723 msgid "Router commands" msgstr "" -#: daemon/HTTPServer.cpp:234 daemon/HTTPServer.cpp:413 -#: daemon/HTTPServer.cpp:425 +#: daemon/HTTPServer.cpp:262 daemon/HTTPServer.cpp:446 +#: daemon/HTTPServer.cpp:458 msgid "Local Destinations" msgstr "" -#: daemon/HTTPServer.cpp:236 daemon/HTTPServer.cpp:388 -#: daemon/HTTPServer.cpp:469 daemon/HTTPServer.cpp:475 -#: daemon/HTTPServer.cpp:606 daemon/HTTPServer.cpp:649 -#: daemon/HTTPServer.cpp:653 +#: daemon/HTTPServer.cpp:264 daemon/HTTPServer.cpp:416 +#: daemon/HTTPServer.cpp:502 daemon/HTTPServer.cpp:508 +#: daemon/HTTPServer.cpp:639 daemon/HTTPServer.cpp:682 +#: daemon/HTTPServer.cpp:686 msgid "LeaseSets" msgstr "" -#: daemon/HTTPServer.cpp:238 daemon/HTTPServer.cpp:659 +#: daemon/HTTPServer.cpp:266 daemon/HTTPServer.cpp:692 msgid "Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:239 daemon/HTTPServer.cpp:395 -#: daemon/HTTPServer.cpp:753 daemon/HTTPServer.cpp:769 +#: daemon/HTTPServer.cpp:267 daemon/HTTPServer.cpp:423 +#: daemon/HTTPServer.cpp:785 daemon/HTTPServer.cpp:801 msgid "Transit Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:240 daemon/HTTPServer.cpp:818 +#: daemon/HTTPServer.cpp:268 daemon/HTTPServer.cpp:850 msgid "Transports" msgstr "" -#: daemon/HTTPServer.cpp:241 +#: daemon/HTTPServer.cpp:269 msgid "I2P tunnels" msgstr "" -#: daemon/HTTPServer.cpp:243 daemon/HTTPServer.cpp:880 -#: daemon/HTTPServer.cpp:890 +#: daemon/HTTPServer.cpp:271 daemon/HTTPServer.cpp:912 +#: daemon/HTTPServer.cpp:922 msgid "SAM sessions" msgstr "" -#: daemon/HTTPServer.cpp:259 daemon/HTTPServer.cpp:1280 -#: daemon/HTTPServer.cpp:1283 daemon/HTTPServer.cpp:1286 -#: daemon/HTTPServer.cpp:1300 daemon/HTTPServer.cpp:1345 -#: daemon/HTTPServer.cpp:1348 daemon/HTTPServer.cpp:1351 +#: daemon/HTTPServer.cpp:287 daemon/HTTPServer.cpp:1304 +#: daemon/HTTPServer.cpp:1307 daemon/HTTPServer.cpp:1310 +#: daemon/HTTPServer.cpp:1324 daemon/HTTPServer.cpp:1369 +#: daemon/HTTPServer.cpp:1372 daemon/HTTPServer.cpp:1375 msgid "ERROR" msgstr "" -#: daemon/HTTPServer.cpp:266 +#: daemon/HTTPServer.cpp:294 msgid "OK" msgstr "" -#: daemon/HTTPServer.cpp:267 +#: daemon/HTTPServer.cpp:295 msgid "Testing" msgstr "" -#: daemon/HTTPServer.cpp:268 +#: daemon/HTTPServer.cpp:296 msgid "Firewalled" msgstr "" -#: daemon/HTTPServer.cpp:269 daemon/HTTPServer.cpp:290 -#: daemon/HTTPServer.cpp:376 +#: daemon/HTTPServer.cpp:297 daemon/HTTPServer.cpp:318 +#: daemon/HTTPServer.cpp:404 msgid "Unknown" msgstr "" -#: daemon/HTTPServer.cpp:270 daemon/HTTPServer.cpp:400 -#: daemon/HTTPServer.cpp:401 daemon/HTTPServer.cpp:948 -#: daemon/HTTPServer.cpp:957 +#: daemon/HTTPServer.cpp:298 daemon/HTTPServer.cpp:433 +#: daemon/HTTPServer.cpp:434 daemon/HTTPServer.cpp:980 +#: daemon/HTTPServer.cpp:989 msgid "Proxy" msgstr "" -#: daemon/HTTPServer.cpp:271 +#: daemon/HTTPServer.cpp:299 msgid "Mesh" msgstr "" -#: daemon/HTTPServer.cpp:274 +#: daemon/HTTPServer.cpp:302 msgid "Error" msgstr "" -#: daemon/HTTPServer.cpp:278 +#: daemon/HTTPServer.cpp:306 msgid "Clock skew" msgstr "" -#: daemon/HTTPServer.cpp:281 +#: daemon/HTTPServer.cpp:309 msgid "Offline" msgstr "" -#: daemon/HTTPServer.cpp:284 +#: daemon/HTTPServer.cpp:312 msgid "Symmetric NAT" msgstr "" -#: daemon/HTTPServer.cpp:296 +#: daemon/HTTPServer.cpp:324 msgid "Uptime" msgstr "" -#: daemon/HTTPServer.cpp:299 +#: daemon/HTTPServer.cpp:327 msgid "Network status" msgstr "" -#: daemon/HTTPServer.cpp:304 +#: daemon/HTTPServer.cpp:332 msgid "Network status v6" msgstr "" -#: daemon/HTTPServer.cpp:310 daemon/HTTPServer.cpp:317 +#: daemon/HTTPServer.cpp:338 daemon/HTTPServer.cpp:345 msgid "Stopping in" msgstr "" -#: daemon/HTTPServer.cpp:324 +#: daemon/HTTPServer.cpp:352 msgid "Family" msgstr "" -#: daemon/HTTPServer.cpp:325 +#: daemon/HTTPServer.cpp:353 msgid "Tunnel creation success rate" msgstr "" -#: daemon/HTTPServer.cpp:326 +#: daemon/HTTPServer.cpp:354 msgid "Received" msgstr "" #. tr: Kibibit/s -#: daemon/HTTPServer.cpp:328 daemon/HTTPServer.cpp:331 -#: daemon/HTTPServer.cpp:334 +#: daemon/HTTPServer.cpp:356 daemon/HTTPServer.cpp:359 +#: daemon/HTTPServer.cpp:362 msgid "KiB/s" msgstr "" -#: daemon/HTTPServer.cpp:329 +#: daemon/HTTPServer.cpp:357 msgid "Sent" msgstr "" -#: daemon/HTTPServer.cpp:332 +#: daemon/HTTPServer.cpp:360 msgid "Transit" msgstr "" -#: daemon/HTTPServer.cpp:335 +#: daemon/HTTPServer.cpp:363 msgid "Data path" msgstr "" -#: daemon/HTTPServer.cpp:338 +#: daemon/HTTPServer.cpp:366 msgid "Hidden content. Press on text to see." msgstr "" -#: daemon/HTTPServer.cpp:341 +#: daemon/HTTPServer.cpp:369 msgid "Router Ident" msgstr "" -#: daemon/HTTPServer.cpp:343 +#: daemon/HTTPServer.cpp:371 msgid "Router Family" msgstr "" -#: daemon/HTTPServer.cpp:344 +#: daemon/HTTPServer.cpp:372 msgid "Router Caps" msgstr "" -#: daemon/HTTPServer.cpp:345 +#: daemon/HTTPServer.cpp:373 msgid "Version" msgstr "" -#: daemon/HTTPServer.cpp:346 +#: daemon/HTTPServer.cpp:374 msgid "Our external address" msgstr "" -#: daemon/HTTPServer.cpp:354 +#: daemon/HTTPServer.cpp:382 msgid "supported" msgstr "" -#: daemon/HTTPServer.cpp:386 +#: daemon/HTTPServer.cpp:414 msgid "Routers" msgstr "" -#: daemon/HTTPServer.cpp:387 +#: daemon/HTTPServer.cpp:415 msgid "Floodfills" msgstr "" -#: daemon/HTTPServer.cpp:394 daemon/HTTPServer.cpp:934 +#: daemon/HTTPServer.cpp:422 daemon/HTTPServer.cpp:966 msgid "Client Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:399 +#: daemon/HTTPServer.cpp:432 msgid "Services" msgstr "" -#: daemon/HTTPServer.cpp:448 +#: daemon/HTTPServer.cpp:433 daemon/HTTPServer.cpp:434 +#: daemon/HTTPServer.cpp:435 daemon/HTTPServer.cpp:436 +#: daemon/HTTPServer.cpp:437 daemon/HTTPServer.cpp:438 +msgid "Enabled" +msgstr "" + +#: daemon/HTTPServer.cpp:433 daemon/HTTPServer.cpp:434 +#: daemon/HTTPServer.cpp:435 daemon/HTTPServer.cpp:436 +#: daemon/HTTPServer.cpp:437 daemon/HTTPServer.cpp:438 +msgid "Disabled" +msgstr "" + +#: daemon/HTTPServer.cpp:481 msgid "Encrypted B33 address" msgstr "" -#: daemon/HTTPServer.cpp:457 +#: daemon/HTTPServer.cpp:490 msgid "Address registration line" msgstr "" -#: daemon/HTTPServer.cpp:462 +#: daemon/HTTPServer.cpp:495 msgid "Domain" msgstr "" -#: daemon/HTTPServer.cpp:463 +#: daemon/HTTPServer.cpp:496 msgid "Generate" msgstr "" -#: daemon/HTTPServer.cpp:464 +#: daemon/HTTPServer.cpp:497 msgid "" "Note: result string can be used only for registering 2LD domains " "(example.i2p). For registering subdomains please use i2pd-tools." msgstr "" -#: daemon/HTTPServer.cpp:470 +#: daemon/HTTPServer.cpp:503 msgid "Address" msgstr "" -#: daemon/HTTPServer.cpp:470 +#: daemon/HTTPServer.cpp:503 msgid "Type" msgstr "" -#: daemon/HTTPServer.cpp:470 +#: daemon/HTTPServer.cpp:503 msgid "EncType" msgstr "" -#: daemon/HTTPServer.cpp:480 daemon/HTTPServer.cpp:664 +#: daemon/HTTPServer.cpp:513 daemon/HTTPServer.cpp:697 msgid "Inbound tunnels" msgstr "" #. tr: Milliseconds -#: daemon/HTTPServer.cpp:485 daemon/HTTPServer.cpp:495 -#: daemon/HTTPServer.cpp:669 daemon/HTTPServer.cpp:679 +#: daemon/HTTPServer.cpp:518 daemon/HTTPServer.cpp:528 +#: daemon/HTTPServer.cpp:702 daemon/HTTPServer.cpp:712 msgid "ms" msgstr "" -#: daemon/HTTPServer.cpp:490 daemon/HTTPServer.cpp:674 +#: daemon/HTTPServer.cpp:523 daemon/HTTPServer.cpp:707 msgid "Outbound tunnels" msgstr "" -#: daemon/HTTPServer.cpp:502 +#: daemon/HTTPServer.cpp:535 msgid "Tags" msgstr "" -#: daemon/HTTPServer.cpp:502 +#: daemon/HTTPServer.cpp:535 msgid "Incoming" msgstr "" -#: daemon/HTTPServer.cpp:509 daemon/HTTPServer.cpp:512 +#: daemon/HTTPServer.cpp:542 daemon/HTTPServer.cpp:545 msgid "Outgoing" msgstr "" -#: daemon/HTTPServer.cpp:510 daemon/HTTPServer.cpp:526 +#: daemon/HTTPServer.cpp:543 daemon/HTTPServer.cpp:559 msgid "Destination" msgstr "" -#: daemon/HTTPServer.cpp:510 +#: daemon/HTTPServer.cpp:543 msgid "Amount" msgstr "" -#: daemon/HTTPServer.cpp:517 +#: daemon/HTTPServer.cpp:550 msgid "Incoming Tags" msgstr "" -#: daemon/HTTPServer.cpp:525 daemon/HTTPServer.cpp:528 +#: daemon/HTTPServer.cpp:558 daemon/HTTPServer.cpp:561 msgid "Tags sessions" msgstr "" -#: daemon/HTTPServer.cpp:526 +#: daemon/HTTPServer.cpp:559 msgid "Status" msgstr "" -#: daemon/HTTPServer.cpp:535 daemon/HTTPServer.cpp:591 +#: daemon/HTTPServer.cpp:568 daemon/HTTPServer.cpp:624 msgid "Local Destination" msgstr "" -#: daemon/HTTPServer.cpp:545 daemon/HTTPServer.cpp:913 +#: daemon/HTTPServer.cpp:578 daemon/HTTPServer.cpp:945 msgid "Streams" msgstr "" -#: daemon/HTTPServer.cpp:567 +#: daemon/HTTPServer.cpp:600 msgid "Close stream" msgstr "" -#: daemon/HTTPServer.cpp:596 +#: daemon/HTTPServer.cpp:629 msgid "I2CP session not found" msgstr "" -#: daemon/HTTPServer.cpp:599 +#: daemon/HTTPServer.cpp:632 msgid "I2CP is not enabled" msgstr "" -#: daemon/HTTPServer.cpp:625 +#: daemon/HTTPServer.cpp:658 msgid "Invalid" msgstr "" -#: daemon/HTTPServer.cpp:628 +#: daemon/HTTPServer.cpp:661 msgid "Store type" msgstr "" -#: daemon/HTTPServer.cpp:629 +#: daemon/HTTPServer.cpp:662 msgid "Expires" msgstr "" -#: daemon/HTTPServer.cpp:634 +#: daemon/HTTPServer.cpp:667 msgid "Non Expired Leases" msgstr "" -#: daemon/HTTPServer.cpp:637 +#: daemon/HTTPServer.cpp:670 msgid "Gateway" msgstr "" -#: daemon/HTTPServer.cpp:638 +#: daemon/HTTPServer.cpp:671 msgid "TunnelID" msgstr "" -#: daemon/HTTPServer.cpp:639 +#: daemon/HTTPServer.cpp:672 msgid "EndDate" msgstr "" -#: daemon/HTTPServer.cpp:649 +#: daemon/HTTPServer.cpp:682 msgid "not floodfill" msgstr "" -#: daemon/HTTPServer.cpp:660 +#: daemon/HTTPServer.cpp:693 msgid "Queue size" msgstr "" -#: daemon/HTTPServer.cpp:691 +#: daemon/HTTPServer.cpp:724 msgid "Run peer test" msgstr "" -#: daemon/HTTPServer.cpp:698 +#: daemon/HTTPServer.cpp:729 msgid "Decline transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:700 +#: daemon/HTTPServer.cpp:731 msgid "Accept transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:704 daemon/HTTPServer.cpp:709 +#: daemon/HTTPServer.cpp:735 daemon/HTTPServer.cpp:740 msgid "Cancel graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:706 daemon/HTTPServer.cpp:711 +#: daemon/HTTPServer.cpp:737 daemon/HTTPServer.cpp:742 msgid "Start graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:714 +#: daemon/HTTPServer.cpp:745 msgid "Force shutdown" msgstr "" -#: daemon/HTTPServer.cpp:717 +#: daemon/HTTPServer.cpp:746 +msgid "Reload external CSS styles" +msgstr "" + +#: daemon/HTTPServer.cpp:749 msgid "" "Note: any action done here are not persistent and not changes your " "config files." msgstr "" -#: daemon/HTTPServer.cpp:719 +#: daemon/HTTPServer.cpp:751 msgid "Logging level" msgstr "" -#: daemon/HTTPServer.cpp:727 +#: daemon/HTTPServer.cpp:759 msgid "Transit tunnels limit" msgstr "" -#: daemon/HTTPServer.cpp:732 daemon/HTTPServer.cpp:744 +#: daemon/HTTPServer.cpp:764 daemon/HTTPServer.cpp:776 msgid "Change" msgstr "" -#: daemon/HTTPServer.cpp:736 +#: daemon/HTTPServer.cpp:768 msgid "Change language" msgstr "" -#: daemon/HTTPServer.cpp:769 +#: daemon/HTTPServer.cpp:801 msgid "no transit tunnels currently built" msgstr "" -#: daemon/HTTPServer.cpp:874 daemon/HTTPServer.cpp:897 +#: daemon/HTTPServer.cpp:906 daemon/HTTPServer.cpp:929 msgid "SAM disabled" msgstr "" -#: daemon/HTTPServer.cpp:890 +#: daemon/HTTPServer.cpp:922 msgid "no sessions currently running" msgstr "" -#: daemon/HTTPServer.cpp:903 +#: daemon/HTTPServer.cpp:935 msgid "SAM session not found" msgstr "" -#: daemon/HTTPServer.cpp:908 +#: daemon/HTTPServer.cpp:940 msgid "SAM Session" msgstr "" -#: daemon/HTTPServer.cpp:965 +#: daemon/HTTPServer.cpp:997 msgid "Server Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:981 +#: daemon/HTTPServer.cpp:1013 msgid "Client Forwards" msgstr "" -#: daemon/HTTPServer.cpp:995 +#: daemon/HTTPServer.cpp:1027 msgid "Server Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1201 +#: daemon/HTTPServer.cpp:1225 msgid "Unknown page" msgstr "" -#: daemon/HTTPServer.cpp:1220 +#: daemon/HTTPServer.cpp:1244 msgid "Invalid token" msgstr "" -#: daemon/HTTPServer.cpp:1278 daemon/HTTPServer.cpp:1335 -#: daemon/HTTPServer.cpp:1371 +#: daemon/HTTPServer.cpp:1302 daemon/HTTPServer.cpp:1359 +#: daemon/HTTPServer.cpp:1399 msgid "SUCCESS" msgstr "" -#: daemon/HTTPServer.cpp:1278 +#: daemon/HTTPServer.cpp:1302 msgid "Stream closed" msgstr "" -#: daemon/HTTPServer.cpp:1280 +#: daemon/HTTPServer.cpp:1304 msgid "Stream not found or already was closed" msgstr "" -#: daemon/HTTPServer.cpp:1283 +#: daemon/HTTPServer.cpp:1307 msgid "Destination not found" msgstr "" -#: daemon/HTTPServer.cpp:1286 +#: daemon/HTTPServer.cpp:1310 msgid "StreamID can't be null" msgstr "" -#: daemon/HTTPServer.cpp:1288 daemon/HTTPServer.cpp:1353 +#: daemon/HTTPServer.cpp:1312 daemon/HTTPServer.cpp:1377 msgid "Return to destination page" msgstr "" -#: daemon/HTTPServer.cpp:1289 daemon/HTTPServer.cpp:1302 -#: daemon/HTTPServer.cpp:1373 +#: daemon/HTTPServer.cpp:1313 daemon/HTTPServer.cpp:1326 +#: daemon/HTTPServer.cpp:1401 msgid "You will be redirected in 5 seconds" msgstr "" -#: daemon/HTTPServer.cpp:1300 +#: daemon/HTTPServer.cpp:1324 msgid "Transit tunnels count must not exceed 65535" msgstr "" -#: daemon/HTTPServer.cpp:1301 daemon/HTTPServer.cpp:1372 +#: daemon/HTTPServer.cpp:1325 daemon/HTTPServer.cpp:1400 msgid "Back to commands list" msgstr "" -#: daemon/HTTPServer.cpp:1337 +#: daemon/HTTPServer.cpp:1361 msgid "Register at reg.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1338 +#: daemon/HTTPServer.cpp:1362 msgid "Description" msgstr "" -#: daemon/HTTPServer.cpp:1338 +#: daemon/HTTPServer.cpp:1362 msgid "A bit information about service on domain" msgstr "" -#: daemon/HTTPServer.cpp:1339 +#: daemon/HTTPServer.cpp:1363 msgid "Submit" msgstr "" -#: daemon/HTTPServer.cpp:1345 +#: daemon/HTTPServer.cpp:1369 msgid "Domain can't end with .b32.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1348 +#: daemon/HTTPServer.cpp:1372 msgid "Domain must end with .i2p" msgstr "" -#: daemon/HTTPServer.cpp:1351 +#: daemon/HTTPServer.cpp:1375 msgid "Such destination is not found" msgstr "" -#: daemon/HTTPServer.cpp:1367 +#: daemon/HTTPServer.cpp:1395 msgid "Unknown command" msgstr "" -#: daemon/HTTPServer.cpp:1371 +#: daemon/HTTPServer.cpp:1399 msgid "Command accepted" msgstr "" diff --git a/contrib/i18n/README.md b/contrib/i18n/README.md new file mode 100644 index 00000000..be44e87f --- /dev/null +++ b/contrib/i18n/README.md @@ -0,0 +1,29 @@ +`xgettext` command for extracting translation +=== + +``` +xgettext --omit-header -ctr: -ktr -ktr:1,2 daemon/HTTPServer.cpp libi2pd_client/HTTPProxy.cpp +``` + +Regex for transforming gettext translations to our format: +=== + +``` +in: msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\nmsgstr\[1\]\ \"(.*)\"\n(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? +out: #{"$2", {"$3", "$4", "$6", "$8", "$10"}},\n +``` + +``` +in: msgid\ \"(.*)\"\nmsgstr\ \"(.*)\"\n +out: {"$1", "$2"},\n +``` + +``` +in: ^#[:.](.*)$\n +out: +``` + +``` +in: \n\n +out: \n +``` diff --git a/contrib/i18n/regex.txt b/contrib/i18n/regex.txt deleted file mode 100644 index e74f9d2d..00000000 --- a/contrib/i18n/regex.txt +++ /dev/null @@ -1,10 +0,0 @@ -Regex for transforming gettext translations to our format - -msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\nmsgstr\[1\]\ \"(.*)\"\n(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? -#{"$2", {"$3", "$4", "$6", "$8", "$10"}},\n - -msgid\ \"(.*)\"\nmsgstr\ \"(.*)\"\n -{"$1", "$2"},\n - -^#:(.*)$\n - diff --git a/contrib/webconsole/style.css b/contrib/webconsole/style.css new file mode 100644 index 00000000..b6e56477 --- /dev/null +++ b/contrib/webconsole/style.css @@ -0,0 +1,245 @@ +body { + font: 100%/1.5em sans-serif; + margin: 0; + padding: 1.5em; + background: #FAFAFA; + color: #103456; +} + +a, .slide label { + text-decoration: none; + color: #894C84; +} + +a:hover, .slide label:hover { + color: #FAFAFA; + background: #894C84; +} + +a.button { + -webkit-appearance: button; + -moz-appearance: button; + appearance: button; + text-decoration: none; + color: initial; + padding: 0 5px; + border: 1px solid #894C84; +} + +.header { + font-size: 2.5em; + text-align: center; + margin: 1em 0; + color: #894C84; +} + +.wrapper { + margin: 0 auto; + padding: 1em; + max-width: 64em; +} + +.menu { + display: block; + float: left; + overflow: hidden; + max-width: 12em; + white-space: nowrap; + text-overflow: ellipsis; +} + +.listitem { + display: block; + font-family: monospace; + font-size: 1.2em; + white-space: nowrap; +} + +.tableitem { + font-family: monospace; + font-size: 1.2em; + white-space: nowrap; +} + +.content { + float: left; + font-size: 1em; + margin-left: 4em; + max-width: 48em; + overflow: auto; +} + +.tunnel.established { + color: #56B734; +} + +.tunnel.expiring { + color: #D3AE3F; +} + +.tunnel.failed { + color: #D33F3F; +} + +.tunnel.building { + color: #434343; +} + +caption { + font-size: 1.5em; + text-align: center; + color: #894C84; +} + +table { + display: table; + border-collapse: collapse; + text-align: center; +} + +table.extaddr { + text-align: left; +} + +table.services { + width: 100%; +} + +textarea { + word-break: break-all; +} + +.streamdest { + width: 120px; + max-width: 240px; + overflow: hidden; + text-overflow: ellipsis; +} + +.slide div.slidecontent, .slide [type="checkbox"] { + display: none; +} + +.slide [type="checkbox"]:checked ~ div.slidecontent { + display: block; + margin-top: 0; + padding: 0; +} + +.disabled { + color: #D33F3F; +} + +.enabled { + color: #56B734; +} + +@media screen and (max-width: 1150px) { /* adaptive style */ + .wrapper { + max-width: 58em; + } + + .menu { + max-width: 10em; + } + + .content { + margin-left: 2em; + max-width: 42em; + } +} + +@media screen and (max-width: 980px) { + body { + padding: 1.5em 0 0 0; + } + + .menu { + width: 100%; + max-width: unset; + display: block; + float: none; + position: unset; + font-size: 16px; + text-align: center; + } + + .menu a, .commands a { + display: inline-block; + padding: 4px; + } + + .content { + float: none; + margin-left: unset; + margin-top: 16px; + max-width: 100%; + width: 100%; + text-align: center; + } + + a, .slide label { + /* margin-right: 10px; */ + display: block; + /* font-size: 18px; */ + } + + .header { + margin: unset; + font-size: 1.5em; + } + + small { + display: block + } + + a.button { + -webkit-appearance: button; + -moz-appearance: button; + appearance: button; + text-decoration: none; + color: initial; + margin-top: 10px; + padding: 6px; + border: 1px solid #894c84; + width: -webkit-fill-available; + } + + input, select { + width: 35%; + text-align: center; + padding: 5px; + border: 2px solid #ccc; + -webkit-border-radius: 5px; + border-radius: 5px; + font-size: 18px; + } + + table.extaddr { + margin: auto; + text-align: unset; + } + + textarea { + width: -webkit-fill-available; + height: auto; + padding:5px; + border:2px solid #ccc; + -webkit-border-radius: 5px; + border-radius: 5px; + font-size: 12px; + } + + button[type=submit] { + padding: 5px 15px; + background: #ccc; + border: 0 none; + cursor: pointer; + -webkit-border-radius: 5px; + border-radius: 5px; + position: relative; + height: 36px; + display: -webkit-inline-box; + margin-top: 10px; + } +} \ No newline at end of file diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 283313fb..5c6a3cd8 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -59,55 +59,75 @@ namespace http { "JHYnlIsfzJjIp9xZKswL5YKBHL+coKJoRDaUSzoozxHVrygQU4JykQADAwAT5b1NHtwZugAAAABJ" "RU5ErkJggg=="; + // Bundled style + const std::string internalCSS = + "\r\n"; + + // for external style sheet + std::string externalCSS; + + static void LoadExtCSS () + { + std::stringstream s; + std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.css"); + if (i2p::fs::Exists(styleFile)) { + std::ifstream f(styleFile, std::ifstream::binary); + s << f.rdbuf(); + externalCSS = s.str(); + } + } + static void GetStyles (std::stringstream& s) { - s << "\r\n"; + if (externalCSS.length() != 0) + s << "\r\n"; + else + s << internalCSS; } const char HTTP_PAGE_TUNNELS[] = "tunnels"; @@ -133,11 +153,19 @@ namespace http { const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit"; const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string"; const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage"; + const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; - static std::string ConvertTime (uint64_t time); - std::map HTTPConnection::m_Tokens; + static std::string ConvertTime (uint64_t time) + { + lldiv_t divTime = lldiv(time, 1000); + time_t t = divTime.quot; + struct tm *tm = localtime(&t); + char date[128]; + snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem); + return date; + } static void ShowUptime (std::stringstream& s, int seconds) { @@ -210,9 +238,9 @@ namespace http { std::string webroot; i2p::config::GetOption("http.webroot", webroot); // Page language - std::string lang, langCode; i2p::config::GetOption("http.lang", lang); - if (lang == "russian") langCode = "ru"; - else langCode = "en"; + std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); // get current used language + auto it = i2p::i18n::languages.find(currLang); + std::string langCode = it->second.ShortCode; s << "\r\n" @@ -395,14 +423,19 @@ namespace http { s << "" << tr("Transit Tunnels") << ": " << std::to_string(transitTunnelCount) << "
    \r\n
    \r\n"; if(outputFormat==OutputFormatEnum::forWebConsole) { - bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); + bool httpproxy = i2p::client::context.GetHttpProxy () ? true : false; + bool socksproxy = i2p::client::context.GetSocksProxy () ? true : false; + bool bob = i2p::client::context.GetBOBCommandChannel () ? true : false; + bool sam = i2p::client::context.GetSAMBridge () ? true : false; + bool i2cp = i2p::client::context.GetI2CPServer () ? true : false; + bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; s << "
    " << tr("Services") << "
    " << "HTTP " << tr("Proxy") << "
    " << "SOCKS " << tr("Proxy") << "
    " << "BOB" << "
    " << "SAM" << "
    " << "I2CP" << "
    " << "I2PControl" << "
    " << "HTTP " << tr("Proxy") << "" << (httpproxy ? tr("Enabled") : tr("Disabled")) << "
    " << "SOCKS " << tr("Proxy") << "" << (socksproxy ? tr("Enabled") : tr("Disabled")) << "
    " << "BOB" << "" << (bob ? tr("Enabled") : tr("Disabled")) << "
    " << "SAM" << "" << (sam ? tr("Enabled") : tr("Disabled")) << "
    " << "I2CP" << "" << (i2cp ? tr("Enabled") : tr("Disabled")) << "
    " << "I2PControl" << "" << (i2pcontrol ? tr("Enabled") : tr("Disabled")) << "
    \r\n"; } } @@ -709,7 +742,8 @@ namespace http { s << " " << tr("Start graceful shutdown") << "
    \r\n"; #endif - s << " " << tr("Force shutdown") << "\r\n"; + s << " " << tr("Force shutdown") << "

    \r\n"; + s << " " << tr("Reload external CSS styles") << "\r\n"; s << "
    "; s << "
    \r\n" << tr("Note: any action done here are not persistent and not changes your config files.") << "\r\n
    \r\n"; @@ -1003,16 +1037,6 @@ namespace http { } } - std::string ConvertTime (uint64_t time) - { - lldiv_t divTime = lldiv(time, 1000); - time_t t = divTime.quot; - struct tm *tm = localtime(&t); - char date[128]; - snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem); - return date; - } - HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr socket): m_Socket (socket), m_BufferLen (0), expected_host(hostname) { @@ -1139,6 +1163,8 @@ namespace http { SendReply (res, content); } + std::map HTTPConnection::m_Tokens; + uint32_t HTTPConnection::CreateToken () { uint32_t token; @@ -1359,6 +1385,10 @@ namespace http { if (currLang.compare(lang) != 0) i2p::i18n::SetLanguage(lang); } + else if (cmd == HTTP_COMMAND_RELOAD_CSS) + { + LoadExtCSS(); + } else { res.code = 400; @@ -1421,6 +1451,8 @@ namespace http { m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this))); m_Acceptor.listen (); Accept (); + + LoadExtCSS(); } void HTTPServer::Stop () diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index 949e5844..a5029782 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -65,7 +65,8 @@ namespace i18n struct langData { - std::string LocaleName; //localized name + std::string LocaleName; // localized name + std::string ShortCode; // short language code, like "en" std::function (void)> LocaleFunc; }; @@ -81,11 +82,11 @@ namespace i18n */ static std::map languages { - { "afrikaans", {"Afrikaans", i2p::i18n::afrikaans::GetLocale} }, - { "english", {"English", i2p::i18n::english::GetLocale} }, - { "russian", {"руÑÑкий Ñзык", i2p::i18n::russian::GetLocale} }, - { "turkmen", {"türkmen dili", i2p::i18n::turkmen::GetLocale} }, - { "ukrainian", {"україÌнÑька моÌва", i2p::i18n::ukrainian::GetLocale} }, + { "afrikaans", {"Afrikaans", "af", i2p::i18n::afrikaans::GetLocale} }, + { "english", {"English", "en", i2p::i18n::english::GetLocale} }, + { "russian", {"руÑÑкий Ñзык", "ru", i2p::i18n::russian::GetLocale} }, + { "turkmen", {"türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} }, + { "ukrainian", {"україÌнÑька моÌва", "uk", i2p::i18n::ukrainian::GetLocale} }, }; } // i18n diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index 5e7f9c6b..a826cdda 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -31,8 +31,6 @@ namespace russian // language namespace static std::map strings { - {"Disabled", "Выключено"}, - {"Enabled", "Включено"}, {"KiB", "КиБ"}, {"MiB", "МиБ"}, {"GiB", "ГиБ"}, @@ -45,10 +43,10 @@ namespace russian // language namespace {"i2pd webconsole", "Веб-конÑоль i2pd"}, {"Main page", "ГлавнаÑ"}, {"Router commands", "Команды роутера"}, - {"Local destinations", "Локальные назначениÑ"}, + {"Local Destinations", "Локальные назначениÑ"}, {"LeaseSets", "ЛизÑеты"}, {"Tunnels", "Туннели"}, - {"Transit tunnels", "Транзитные туннели"}, + {"Transit Tunnels", "Транзитные туннели"}, {"Transports", "ТранÑпорты"}, {"I2P tunnels", "I2P туннели"}, {"SAM sessions", "SAM ÑеÑÑии"}, @@ -84,9 +82,9 @@ namespace russian // language namespace {"Routers", "Роутеры"}, {"Floodfills", "Флудфилы"}, {"Client Tunnels", "КлиентÑкие туннели"}, - {"Transit Tunnels", "Транзитные туннели"}, {"Services", "СервиÑÑ‹"}, - {"Local Destinations", "Локальные назначениÑ"}, + {"Enabled", "Включено"}, + {"Disabled", "Выключено"}, {"Encrypted B33 address", "Шифрованные B33 адреÑа"}, {"Address registration line", "Строка региÑтрации адреÑа"}, {"Domain", "Домен"}, @@ -103,8 +101,8 @@ namespace russian // language namespace {"Outgoing", "ИÑходÑщие"}, {"Destination", "Ðазначение"}, {"Amount", "КоличеÑтво"}, - {"Incoming Tags", "ВходÑщие Теги"}, - {"Tags sessions", "СеÑÑии Тегов"}, + {"Incoming Tags", "ВходÑщие теги"}, + {"Tags sessions", "СеÑÑии тегов"}, {"Status", "СтатуÑ"}, {"Local Destination", "Локальное назначение"}, {"Streams", "Стримы"}, @@ -126,6 +124,7 @@ namespace russian // language namespace {"Cancel graceful shutdown", "Отменить плавную оÑтановку"}, {"Start graceful shutdown", "ЗапуÑтить плавную оÑтановку"}, {"Force shutdown", "ÐŸÑ€Ð¸Ð½ÑƒÐ´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¾Ñтановка"}, + {"Reload external CSS styles", "Перезагрузить внешние CSS Ñтили"}, {"Note: any action done here are not persistent and not changes your config files.", "Примечание: любое дейÑтвие произведенное здеÑÑŒ не ÑвлÑетÑÑ Ð¿Ð¾ÑтоÑнным и не изменÑет ваши конфигурационные файлы."}, {"Logging level", "Уровень логированиÑ"}, {"Transit tunnels limit", "Лимит транзитных туннелей"}, @@ -147,7 +146,7 @@ namespace russian // language namespace {"Destination not found", "Точка Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð½Ðµ найдена"}, {"StreamID can't be null", "StreamID не может быть пуÑтым"}, {"Return to destination page", "ВернутьÑÑ Ð½Ð° Ñтраницу точки назначениÑ"}, - {"You will be redirected back in 5 seconds", "Ð’Ñ‹ будете переадреÑованы назад через 5 Ñекунд"}, + {"You will be redirected in 5 seconds", "Ð’Ñ‹ будете переадреÑованы через 5 Ñекунд"}, {"Transit tunnels count must not exceed 65535", "ЧиÑло транзитных туннелей не должно превышать 65535"}, {"Back to commands list", "ВернутьÑÑ Ðº ÑпиÑку команд"}, {"Register at reg.i2p", "ЗарегиÑтрировать на reg.i2p"}, @@ -159,7 +158,6 @@ namespace russian // language namespace {"Such destination is not found", "Ð¢Ð°ÐºÐ°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð½Ðµ найдена"}, {"Unknown command", "ÐеизвеÑÑ‚Ð½Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°"}, {"Command accepted", "Команда принÑта"}, - {"You will be redirected in 5 seconds", "Ð’Ñ‹ будете переадреÑованы через 5 Ñекунд"}, {"Proxy error", "Ошибка прокÑи"}, {"Proxy info", "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¿Ñ€Ð¾ÐºÑи"}, {"Proxy error: Host not found", "Ошибка прокÑи: Узел не найден"}, @@ -176,7 +174,6 @@ namespace russian // language namespace {"Addresshelper found", "Ðайден addresshelper"}, {"already in router's addressbook", "уже в адреÑной книге роутера"}, {"to update record", "чтобы обновить запиÑÑŒ"}, - {"Invalid Request", "неверный запроÑ"}, {"invalid request uri", "некорректный URI запроÑа"}, {"Can't detect destination host from request", "Ðе удалоÑÑŒ определить Ð°Ð´Ñ€ÐµÑ Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¸Ð· запроÑа"}, {"Outproxy failure", "Ошибка внешнего прокÑи"}, diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp index 8af89f6f..2bcd77cd 100644 --- a/i18n/Turkmen.cpp +++ b/i18n/Turkmen.cpp @@ -31,7 +31,133 @@ namespace turkmen // language namespace static std::map strings { - // HTTP Proxy + {"KiB", "KiB"}, + {"MiB", "MiB"}, + {"GiB", "GiB"}, + {"building", "bina"}, + {"failed", "ÅŸowsuz"}, + {"expiring", "möhleti gutarýar"}, + {"established", "iÅŸleýär"}, + {"unknown", "näbelli"}, + {"exploratory", "gözleg"}, + {"i2pd webconsole", "Web konsoly i2pd"}, + {"Main page", "Esasy sahypa"}, + {"Router commands", "MarÅŸrutizator buýruklary"}, + {"Local Destinations", "Ãerli ýerler"}, + {"LeaseSets", "Lizset"}, + {"Tunnels", "Tuneller"}, + {"Transit Tunnels", "Tranzit Tunelleri"}, + {"Transports", "DaÅŸamak"}, + {"I2P tunnels", "I2P tuneller"}, + {"SAM sessions", "SAM Sessiýasy"}, + {"ERROR", "ÃalňyÅŸlyk"}, + {"OK", "OK"}, + {"Testing", "Synag etmek"}, + {"Firewalled", "DaÅŸynda petiklendi"}, + {"Unknown", "Näbelli"}, + {"Proxy", "Proksi"}, + {"Mesh", "MESH-tor"}, + {"Error", "ÃalňyÅŸlyk"}, + {"Clock skew", "Takyk wagt däl"}, + {"Offline", "Awtonom"}, + {"Symmetric NAT", "Simmetriklik NAT"}, + {"Uptime", "Onlaýn onlaýn sözlügi"}, + {"Network status", "Tor ýagdaýy"}, + {"Network status v6", "Tor ýagdaýy v6"}, + {"Stopping in", "Soň duruň"}, + {"Family", "MaÅŸgala"}, + {"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"}, + {"Received", "Alnan"}, + {"KiB/s", "KiB/s"}, + {"Sent", "ÃerleÅŸdirildi"}, + {"Transit", "Tranzit"}, + {"Data path", "Maglumat ýoly"}, + {"Hidden content. Press on text to see.", "Gizlin mazmun. Görkezmek üçin tekste basyň."}, + {"Router Ident", "MarÅŸrutly kesgitleýji"}, + {"Router Family", "MarÅŸrutler maÅŸgalasy"}, + {"Router Caps", "Baýdaklar marÅŸruteri"}, + {"Version", "Wersiýasy"}, + {"Our external address", "DaÅŸarky salgymyz"}, + {"supported", "goldanýar"}, + {"Routers", "MarÅŸrutizatorlar"}, + {"Floodfills", "Fludfillar"}, + {"Client Tunnels", "Müşderi tunelleri"}, + {"Services", "Hyzmatlar"}, + {"Enabled", "GoÅŸuldy"}, + {"Disabled", "Öçürildi"}, + {"Encrypted B33 address", "Åžifrlenen B33 salgylar"}, + {"Address registration line", "Hasaba alyÅŸ salgysy"}, + {"Domain", "Domen"}, + {"Generate", "Öndürmek"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Bellik: Alnan setir diňe ikinji derejeli domenleri bellige almak üçin ulanylyp bilner (example.i2p). Subýutmalary hasaba almak üçin i2pd ulanyň-tools."}, + {"Address", "Salgysy"}, + {"Type", "Görnüş"}, + {"EncType", "Åžifrlemek görnüşi"}, + {"Inbound tunnels", "Gelýän tuneller"}, + {"ms", "ms"}, + {"Outbound tunnels", "Çykýan tuneller"}, + {"Tags", "Bellikler"}, + {"Incoming", "Gelýän"}, + {"Outgoing", "Çykýan"}, + {"Destination", "Maksat"}, + {"Amount", "Sany"}, + {"Incoming Tags", "Gelýän bellikler"}, + {"Tags sessions", "Sapaklar bellikler"}, + {"Status", "Ãagdaýy"}, + {"Local Destination", "Ãerli maksat"}, + {"Streams", "Strimlary"}, + {"Close stream", "Yap strim"}, + {"I2CP session not found", "I2CP Sessiýa tapylmady"}, + {"I2CP is not enabled", "I2CP goÅŸulmaýar"}, + {"Invalid", "Nädogry"}, + {"Store type", "Ammar görnüşi"}, + {"Expires", "Möhleti gutarýar"}, + {"Non Expired Leases", "Möhleti gutarmady Lizsetlary"}, + {"Gateway", "Derweze"}, + {"TunnelID", "Tuneliň ID"}, + {"EndDate", "Gutarýar"}, + {"not floodfill", "fludfil däl"}, + {"Queue size", "Nobatyň ululygy"}, + {"Run peer test", "Synag baÅŸlaň"}, + {"Decline transit tunnels", "Tranzit tunellerini ret ediň"}, + {"Accept transit tunnels", "Tranzit tunellerini alyň"}, + {"Cancel graceful shutdown", "Tekiz durmagy ýatyryň"}, + {"Start graceful shutdown", "Tekiz durmak"}, + {"Force shutdown", "Mejbury duralga"}, + {"Reload external CSS styles", "DaÅŸarky CSS stillerini täzeden ýükläň"}, + {"Note: any action done here are not persistent and not changes your config files.", "Bellik: Bu ýerde öndürilen islendik çäre hemiÅŸelik däl we konfigurasiýa faýllaryňyzy üýtgetmeýär."}, + {"Logging level", "GiriÅŸ derejesi"}, + {"Transit tunnels limit", "Tranzit tunelleriniň çägi"}, + {"Change", "Üýtgetmek"}, + {"Change language", "Dil üýtgetmek"}, + {"no transit tunnels currently built", "gurlan tranzit tunelleri ýok"}, + {"SAM disabled", "SAM öçürilen"}, + {"no sessions currently running", "baÅŸlamagyň sessiýalary ýok"}, + {"SAM session not found", "SAM Sessiýa tapylmady"}, + {"SAM Session", "SAM Sessiýa"}, + {"Server Tunnels", "Serwer tunelleri"}, + {"Client Forwards", "Müşderi gönükdirýär"}, + {"Server Forwards", "Serweriň täzeden düzlüleri"}, + {"Unknown page", "Näbelli sahypa"}, + {"Invalid token", "Nädogry token"}, + {"SUCCESS", "Üstünlikli"}, + {"Stream closed", "Strim ýapyk"}, + {"Stream not found or already was closed", "Strim tapylmady ýa-da eýýäm ýapyldy"}, + {"Destination not found", "Niýetlenen ýeri tapylmady"}, + {"StreamID can't be null", "StreamID boÅŸ bolup bilmez"}, + {"Return to destination page", "Barmaly nokadynyň nokadyna gaýdyp geliň"}, + {"You will be redirected in 5 seconds", "5 sekuntdan soň täzeden ugrukdyrylarsyňyz"}, + {"Transit tunnels count must not exceed 65535", "Tranzit tagtalaryň sany 65535-den geçmeli däldir"}, + {"Back to commands list", "Topar sanawyna dolan"}, + {"Register at reg.i2p", "Reg.i2P-de hasaba duruň"}, + {"Description", "Beýany"}, + {"A bit information about service on domain", "Domendäki hyzmat barada käbir maglumatlar"}, + {"Submit", "Iber"}, + {"Domain can't end with .b32.i2p", "Domain .b32.i2p bilen gutaryp bilmez"}, + {"Domain must end with .i2p", "Domeni .i2p bilen gutarmaly"}, + {"Such destination is not found", "Bu barmaly ýer tapylmady"}, + {"Unknown command", "Näbelli topar"}, + {"Command accepted", "Topar kabul edilýär"}, {"Proxy error", "Proksi ýalňyÅŸlygy"}, {"Proxy info", "Proksi maglumat"}, {"Proxy error: Host not found", "Proksi ýalňyÅŸlygy: Host tapylmady"}, @@ -42,12 +168,12 @@ namespace turkmen // language namespace {"addresshelper is not supported", "Salgylandyryjy goldanok"}, {"Host", "Adres"}, {"added to router's addressbook from helper", "marÅŸruteriň adresini kömekçiden goÅŸdy"}, - {"already in router's addressbook", "marÅŸruteriň adres kitaby"}, {"Click", "Basyň"}, {"here", "bu ýerde"}, {"to proceed", "dowam etmek"}, - {"to update record", "recordazgyny täzelemek üçin"}, {"Addresshelper found", "Forgelper tapyldy"}, + {"already in router's addressbook", "marÅŸruteriň adres kitaby"}, + {"to update record", "recordazgyny täzelemek üçin"}, {"invalid request uri", "nädogry haýyÅŸ URI"}, {"Can't detect destination host from request", "HaýyÅŸdan barmaly ýerini tapyp bilemok"}, {"Outproxy failure", "DaÅŸarky proksi ýalňyÅŸlyk"}, @@ -67,161 +193,7 @@ namespace turkmen // language namespace {"http out proxy not implemented", "daÅŸarky HTTP proksi serwerini goldamak amala aÅŸyrylmaýar"}, {"cannot connect to upstream http proxy", "ýokary akym HTTP proksi serwerine birigip bilmedi"}, {"Host is down", "Salgy elýeterli däl"}, - {"Can't create connection to requested host, it may be down. Please try again later.", - "Talap edilýän salgyda birikmäni gurup bilmedim, onlaýn bolup bilmez. Soňra haýyÅŸy soň gaýtalamaga synanyÅŸyň."}, - - // Webconsole // - // cssStyles - {"Disabled", "Öçürildi"}, - {"Enabled", "GoÅŸuldy"}, - // ShowTraffic - {"KiB", "KiB"}, - {"MiB", "MiB"}, - {"GiB", "GiB"}, - // ShowTunnelDetails - {"building", "bina"}, - {"failed", "ÅŸowsuz"}, - {"expiring", "möhleti gutarýar"}, - {"established", "iÅŸleýär"}, - {"exploratory", "gözleg"}, - {"unknown", "näbelli"}, - {"i2pd webconsole", "Web konsoly i2pd"}, - // ShowPageHead - {"Main page", "Esasy sahypa"}, - {"Router commands", "MarÅŸrutizator buýruklary"}, - {"Local destinations", "Ãerli ýerler"}, - {"LeaseSets", "Lizset"}, - {"Tunnels", "Tuneller"}, - {"Transit tunnels", "Tranzit tunels"}, - {"Transports", "DaÅŸamak"}, - {"I2P tunnels", "I2P tuneller"}, - {"SAM sessions", "SAM Sessiýasy"}, - // Network Status - {"OK", "OK"}, - {"Testing", "Synag etmek"}, - {"Firewalled", "DaÅŸynda petiklendi"}, - {"Unknown", "Näbelli"}, - {"Proxy", "Proksi"}, - {"Mesh", "MESH-tor"}, - {"Error", "ÃalňyÅŸlyk"}, - {"Clock skew", "Takyk wagt däl"}, - {"Offline", "Awtonom"}, - {"Symmetric NAT", "Simmetriklik NAT"}, - // Status - {"Uptime", "Onlaýn onlaýn sözlügi"}, - {"Network status", "Tor ýagdaýy"}, - {"Network status v6", "Tor ýagdaýy v6"}, - {"Stopping in", "Soň duruň"}, - {"Family", "MaÅŸgala"}, - {"Tunnel creation success rate", "Gurlan teneller üstünlikli gurlan teneller"}, - {"Received", "Alnan"}, - {"Sent", "ÃerleÅŸdirildi"}, - {"Transit", "Tranzit"}, - {"KiB/s", "KiB/s"}, - {"Data path", "Maglumat ýoly"}, - {"Hidden content. Press on text to see.", "Gizlin mazmun. Görkezmek üçin tekste basyň."}, - {"Router Ident", "MarÅŸrutly kesgitleýji"}, - {"Router Family", "MarÅŸrutler maÅŸgalasy"}, - {"Router Caps", "Baýdaklar marÅŸruteri"}, - {"Version", "Wersiýasy"}, - {"Our external address", "DaÅŸarky salgymyz"}, - {"supported", "goldanýar"}, - {"Routers", "MarÅŸrutizatorlar"}, - {"Floodfills", "Fludfillar"}, - {"Client Tunnels", "Müşderi tunelleri"}, - {"Transit Tunnels", "Tranzit Tunelleri"}, - {"Services", "Hyzmatlar"}, - // ShowLocalDestinations - {"Local Destinations", "Ãerli ýerler"}, - // ShowLeaseSetDestination - {"Encrypted B33 address", "Åžifrlenen B33 salgylar"}, - {"Address registration line", "Hasaba alyÅŸ salgysy"}, - {"Domain", "Domen"}, - {"Generate", "Öndürmek"}, - {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", - "Bellik: Alnan setir diňe ikinji derejeli domenleri bellige almak üçin ulanylyp bilner. Subýutmalary hasaba almak üçin i2pd ulanyň-tools."}, - {"Address", "Salgysy"}, - {"Type", "Görnüş"}, - {"EncType", "Åžifrlemek görnüşi"}, - {"Inbound tunnels", "Gelýän tuneller"}, - {"Outbound tunnels", "Çykýan tuneller"}, - {"ms", "ms"}, // milliseconds - {"Tags", "Bellikler"}, - {"Incoming", "Gelýän"}, - {"Outgoing", "Çykýan"}, - {"Destination", "Maksat"}, - {"Amount", "Sany"}, - {"Incoming Tags", "Gelýän bellikler"}, - {"Tags sessions", "Sapaklar bellikler"}, - {"Status", "Ãagdaýy"}, - // ShowLocalDestination - {"Local Destination", "Ãerli maksat"}, - {"Streams", "Strimlary"}, - {"Close stream", "Yap strim"}, - // ShowI2CPLocalDestination - {"I2CP session not found", "I2CP Sessiýa tapylmady"}, - {"I2CP is not enabled", "I2CP goÅŸulmaýar"}, - // ShowLeasesSets - {"Invalid", "Nädogry"}, - {"Store type", "Ammar görnüşi"}, - {"Expires", "Möhleti gutarýar"}, - {"Non Expired Leases", "Möhleti gutarmady Lizsetlary"}, - {"Gateway", "Derweze"}, - {"TunnelID", "Tuneliň ID"}, - {"EndDate", "Gutarýar"}, - {"not floodfill", "fludfil däl"}, - // ShowTunnels - {"Queue size", "Nobatyň ululygy"}, - // ShowCommands - {"Run peer test", "Synag baÅŸlaň"}, - {"Decline transit tunnels", "Tranzit tunellerini ret ediň"}, - {"Accept transit tunnels", "Tranzit tunellerini alyň"}, - {"Cancel graceful shutdown", "Tekiz durmagy ýatyryň"}, - {"Start graceful shutdown", "Tekiz durmak"}, - {"Force shutdown", "Mejbury duralga"}, - {"Note: any action done here are not persistent and not changes your config files.", - "Bellik: Bu ýerde öndürilen islendik çäre hemiÅŸelik däl we konfigurasiýa faýllaryňyzy üýtgetmeýär."}, - {"Logging level", "GiriÅŸ derejesi"}, - {"Transit tunnels limit", "Tranzit tunelleriniň çägi"}, - {"Change", "Üýtgetmek"}, - // ShowTransitTunnels - {"no transit tunnels currently built", "gurlan tranzit tunelleri ýok"}, - // ShowSAMSessions/ShowSAMSession - {"SAM disabled", "SAM öçürilen"}, - {"SAM session not found", "SAM Sessiýa tapylmady"}, - {"no sessions currently running", "baÅŸlamagyň sessiýalary ýok"}, - {"SAM Session", "SAM Sessiýa"}, - // ShowI2PTunnels - {"Server Tunnels", "Serwer tunelleri"}, - {"Client Forwards", "Müşderi gönükdirýär"}, - {"Server Forwards", "Serweriň täzeden düzlüleri"}, - // HandlePage - {"Unknown page", "Näbelli sahypa"}, - // HandleCommand, ShowError - {"Invalid token", "Nädogry token"}, - {"SUCCESS", "Üstünlikli"}, - {"ERROR", "ÃalňyÅŸlyk"}, - {"Unknown command", "Näbelli topar"}, - {"Command accepted", "Topar kabul edilýär"}, - {"Back to commands list", "Topar sanawyna dolan"}, - {"You will be redirected in 5 seconds", "5 sekuntdan soň täzeden ugrukdyrylarsyňyz"}, - // HTTP_COMMAND_KILLSTREAM - {"Stream closed", "Strim ýapyk"}, - {"Stream not found or already was closed", "Strim tapylmady ýa-da eýýäm ýapyldy"}, - {"Destination not found", "Niýetlenen ýeri tapylmady"}, - {"StreamID can't be null", "StreamID boÅŸ bolup bilmez"}, - {"Return to destination page", "Barmaly nokadynyň nokadyna gaýdyp geliň"}, - {"You will be redirected back in 5 seconds", "5 sekuntda yzyna iberiler"}, - // HTTP_COMMAND_LIMITTRANSIT - {"Transit tunnels count must not exceed 65535", "Tranzit tagtalaryň sany 65535-den geçmeli däldir"}, - // HTTP_COMMAND_GET_REG_STRING - {"Register at reg.i2p", "Reg.i2P-de hasaba duruň"}, - {"Description", "Beýany"}, - {"A bit information about service on domain", "Domendäki hyzmat barada käbir maglumatlar"}, - {"Submit", "Iber"}, - {"Domain can't end with .b32.i2p", "Domain .b32.i2p bilen gutaryp bilmez"}, - {"Domain must end with .i2p", "Domeni .i2p bilen gutarmaly"}, - {"Such destination is not found", "Bu barmaly ýer tapylmady"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Talap edilýän salgyda birikmäni gurup bilmedim, onlaýn bolup bilmez. Soňra haýyÅŸy soň gaýtalamaga synanyÅŸyň."}, {"", ""}, }; diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp index 8da132d7..413375ba 100644 --- a/i18n/Ukrainian.cpp +++ b/i18n/Ukrainian.cpp @@ -31,8 +31,6 @@ namespace ukrainian // language namespace static std::map strings { - {"Disabled", "Вимкнуто"}, - {"Enabled", "Увімкнуто"}, {"KiB", "КіБ"}, {"MiB", "МіБ"}, {"GiB", "ГіБ"}, @@ -45,10 +43,10 @@ namespace ukrainian // language namespace {"i2pd webconsole", "Веб-конÑоль i2pd"}, {"Main page", "Головна"}, {"Router commands", "Команди маршрутизатора"}, - {"Local destinations", "Локальні призначеннÑ"}, + {"Local Destinations", "Локальні ПризначеннÑ"}, {"LeaseSets", "ЛізÑети"}, {"Tunnels", "Тунелі"}, - {"Transit tunnels", "Транзитні тунелі"}, + {"Transit Tunnels", "Транзитні Тунелі"}, {"Transports", "ТранÑпорти"}, {"I2P tunnels", "I2P тунелі"}, {"SAM sessions", "SAM ÑеÑÑ–Ñ—"}, @@ -84,9 +82,9 @@ namespace ukrainian // language namespace {"Routers", "Маршрутизатори"}, {"Floodfills", "Флудфіли"}, {"Client Tunnels", "КлієнтÑькі Тунелі"}, - {"Transit Tunnels", "Транзитні Тунелі"}, {"Services", "СервіÑи"}, - {"Local Destinations", "Локальні ПризначеннÑ"}, + {"Enabled", "Увімкнуто"}, + {"Disabled", "Вимкнуто"}, {"Encrypted B33 address", "Шифровані B33 адреÑи"}, {"Address registration line", "РÑдок реєÑтрації адреÑи"}, {"Domain", "Домен"}, @@ -126,10 +124,12 @@ namespace ukrainian // language namespace {"Cancel graceful shutdown", "СкаÑувати плавну зупинку"}, {"Start graceful shutdown", "ЗапуÑтити плавну зупинку"}, {"Force shutdown", "ПримуÑова зупинка"}, + {"Reload external CSS styles", "Перезавантажити зовнішні Ñтилі CSS"}, {"Note: any action done here are not persistent and not changes your config files.", "Примітка: будь-Ñка зроблена тут Ð´Ñ–Ñ Ð½Ðµ Ñ” поÑтійною та не змінює ваші конфігураційні файли."}, {"Logging level", "Рівень логуваннÑ"}, {"Transit tunnels limit", "ÐžÐ±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‚Ñ€Ð°Ð½Ð·Ð¸Ñ‚Ð½Ð¸Ñ… тунелів"}, {"Change", "Змінити"}, + {"Change language", "Змінити мову"}, {"no transit tunnels currently built", "немає побудованих транзитних тунелів"}, {"SAM disabled", "SAM вимкнуто"}, {"no sessions currently running", "немає запущених ÑеÑій"}, @@ -146,7 +146,7 @@ namespace ukrainian // language namespace {"Destination not found", "Точка Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ знайдена"}, {"StreamID can't be null", "Ідентифікатор потоку не може бути порожнім"}, {"Return to destination page", "ПовернутиÑÑ Ð½Ð° Ñторінку точки призначеннÑ"}, - {"You will be redirected back in 5 seconds", "Ви будете переадреÑовані назад через 5 Ñекунд"}, + {"You will be redirected in 5 seconds", "Ви будете переадреÑовані через 5 Ñекунд"}, {"Transit tunnels count must not exceed 65535", "КількіÑть транзитних тунелів не повинна перевищувати 65535"}, {"Back to commands list", "ПовернутиÑÑ Ð´Ð¾ ÑпиÑку команд"}, {"Register at reg.i2p", "ЗареєÑтрувати на reg.i2p"}, @@ -158,7 +158,6 @@ namespace ukrainian // language namespace {"Such destination is not found", "Така точка Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ знайдена"}, {"Unknown command", "Ðевідома команда"}, {"Command accepted", "Команда прийнÑта"}, - {"You will be redirected in 5 seconds", "Ви будете переадреÑовані через 5 Ñекунд"}, {"Proxy error", "Помилка прокÑÑ–"}, {"Proxy info", "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ÐºÑÑ–"}, {"Proxy error: Host not found", "Помилка прокÑÑ–: ÐдреÑа не знайдена"}, @@ -175,7 +174,6 @@ namespace ukrainian // language namespace {"Addresshelper found", "Знайдено addresshelper"}, {"already in router's addressbook", "вже в адреÑній книзі маршрутизатора"}, {"to update record", "щоб оновити запиÑ"}, - {"Invalid Request", "Ðекоректний Запит"}, {"invalid request uri", "некоректний URI запиту"}, {"Can't detect destination host from request", "Ðе вдалоÑÑŒ визначити адреÑу Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð· запиту"}, {"Outproxy failure", "Помилка зовнішнього прокÑÑ–"}, From 939682737950f53cd5b6dc68d029b3218c2efe6a Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 4 Aug 2021 07:03:54 +0300 Subject: [PATCH 4375/6300] [makefile] build libraries on default target Signed-off-by: R4SAS --- .gitignore | 3 +++ Makefile | 49 +++++++++++++++++++++++++++---------------------- Makefile.mingw | 4 ++-- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 1fc6cefe..bafe2e18 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ netDb /libi2pd.so /libi2pdclient.so /libi2pdlang.so +/libi2pd.dll +/libi2pdclient.dll +/libi2pdlang.dll *.exe diff --git a/Makefile b/Makefile index abea5fd7..1db11c21 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,20 @@ SYS := $(shell $(CXX) -dumpmachine) -SHLIB := libi2pd.so + +ifneq (, $(findstring darwin, $(SYS))) + SHARED_PREFIX = dylib +else ifneq (, $(findstring mingw, $(SYS))$(findstring cygwin, $(SYS))) + SHARED_PREFIX = dll +else + SHARED_PREFIX = so +endif + +SHLIB := libi2pd.$(SHARED_PREFIX) ARLIB := libi2pd.a -SHLIB_LANG := libi2pdlang.so +SHLIB_LANG := libi2pdlang.$(SHARED_PREFIX) ARLIB_LANG := libi2pdlang.a -SHLIB_CLIENT := libi2pdclient.so +SHLIB_CLIENT := libi2pdclient.$(SHARED_PREFIX) ARLIB_CLIENT := libi2pdclient.a -SHLIB_WRAP := libi2pdwrapper.so +SHLIB_WRAP := libi2pdwrapper.$(SHARED_PREFIX) ARLIB_WRAP := libi2pdwrapper.a I2PD := i2pd @@ -64,22 +73,18 @@ LANG_OBJS += $(patsubst %.cpp,obj/%.o,$(LANG_SRC)) DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(LANG_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) -all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) +## Build all code (libi2pd, libi2pdclient, libi2pdlang), link code to .a and .so (.dll on windows) and build binary +## Windows binary is not depending on output dlls +all: | api_client $(I2PD) mk_obj_dir: - @mkdir -p obj - @mkdir -p obj/Win32 - @mkdir -p obj/$(LIB_SRC_DIR) - @mkdir -p obj/$(LIB_CLIENT_SRC_DIR) - @mkdir -p obj/$(LANG_SRC_DIR) - @mkdir -p obj/$(WRAP_SRC_DIR) - @mkdir -p obj/$(DAEMON_SRC_DIR) + @mkdir -p obj/{Win32,$(LIB_SRC_DIR),$(LIB_CLIENT_SRC_DIR),$(LANG_SRC_DIR),$(WRAP_SRC_DIR),$(DAEMON_SRC_DIR)} -api: mk_obj_dir $(SHLIB) $(ARLIB) -client: mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) -api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) -wrapper: api_client $(SHLIB_WRAP) $(ARLIB_WRAP) -lang: mk_obj_dir $(SHLIB_LANG) $(ARLIB_LANG) +api: | mk_obj_dir $(SHLIB) $(ARLIB) +client: | mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) +lang: | mk_obj_dir $(SHLIB_LANG) $(ARLIB_LANG) +api_client: | api client lang +wrapper: | mk_obj_dir api_client $(SHLIB_WRAP) $(ARLIB_WRAP) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time @@ -98,14 +103,14 @@ obj/%.o: %.cpp $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) -$(SHLIB): $(LIB_OBJS) +$(SHLIB): $(LIB_OBJS) $(SHLIB_LANG) ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB_LANG) endif -$(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) +$(SHLIB_CLIENT): $(LIB_CLIENT_OBJS) $(SHLIB) $(SHLIB_LANG) ifneq ($(USE_STATIC),yes) - $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) + $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) $(SHLIB) $(SHLIB_LANG) endif $(SHLIB_WRAP): $(WRAP_LIB_OBJS) @@ -118,7 +123,7 @@ ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) endif -$(ARLIB): $(LIB_OBJS) +$(ARLIB): $(LIB_OBJS) $(AR) -r $@ $^ $(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) diff --git a/Makefile.mingw b/Makefile.mingw index ce1966a1..b57860b4 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -3,9 +3,9 @@ USE_WIN32_APP := yes WINDRES = windres -CXXFLAGS := $(CXX_DEBUG) -D_MT -DWIN32_LEAN_AND_MEAN -fPIC -msse +CXXFLAGS := $(CXX_DEBUG) -DWIN32_LEAN_AND_MEAN -fPIC -msse INCFLAGS = -I$(DAEMON_SRC_DIR) -IWin32 -LDFLAGS := ${LD_DEBUG} -Wl,-Bstatic -static-libgcc +LDFLAGS := ${LD_DEBUG} -static # detect proper flag for c++11 support by compilers CXXVER := $(shell $(CXX) -dumpversion) From dc9e5dc2f1479c519db5bab16a9258d025e8f99c Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 6 Aug 2021 20:51:25 +0300 Subject: [PATCH 4376/6300] [makefile] suffix, not prefix Signed-off-by: R4SAS --- Makefile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 1db11c21..328214cd 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,20 @@ SYS := $(shell $(CXX) -dumpmachine) ifneq (, $(findstring darwin, $(SYS))) - SHARED_PREFIX = dylib + SHARED_SUFFIX = dylib else ifneq (, $(findstring mingw, $(SYS))$(findstring cygwin, $(SYS))) - SHARED_PREFIX = dll + SHARED_SUFFIX = dll else - SHARED_PREFIX = so + SHARED_SUFFIX = so endif -SHLIB := libi2pd.$(SHARED_PREFIX) +SHLIB := libi2pd.$(SHARED_SUFFIX) ARLIB := libi2pd.a -SHLIB_LANG := libi2pdlang.$(SHARED_PREFIX) +SHLIB_LANG := libi2pdlang.$(SHARED_SUFFIX) ARLIB_LANG := libi2pdlang.a -SHLIB_CLIENT := libi2pdclient.$(SHARED_PREFIX) +SHLIB_CLIENT := libi2pdclient.$(SHARED_SUFFIX) ARLIB_CLIENT := libi2pdclient.a -SHLIB_WRAP := libi2pdwrapper.$(SHARED_PREFIX) +SHLIB_WRAP := libi2pdwrapper.$(SHARED_SUFFIX) ARLIB_WRAP := libi2pdwrapper.a I2PD := i2pd From 2f945a4fce3fafa3dc34abccf9e08c059026331d Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 6 Aug 2021 21:27:37 +0300 Subject: [PATCH 4377/6300] [makefile] dont build .so and .dll on default target Signed-off-by: R4SAS --- Makefile | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 328214cd..a4b3c3fa 100644 --- a/Makefile +++ b/Makefile @@ -73,19 +73,17 @@ LANG_OBJS += $(patsubst %.cpp,obj/%.o,$(LANG_SRC)) DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(LANG_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) -## Build all code (libi2pd, libi2pdclient, libi2pdlang), link code to .a and .so (.dll on windows) and build binary -## Windows binary is not depending on output dlls -all: | api_client $(I2PD) +## Build all code (libi2pd, libi2pdclient, libi2pdlang), link it to .a and build binary +all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(I2PD) mk_obj_dir: @mkdir -p obj/{Win32,$(LIB_SRC_DIR),$(LIB_CLIENT_SRC_DIR),$(LANG_SRC_DIR),$(WRAP_SRC_DIR),$(DAEMON_SRC_DIR)} -api: | mk_obj_dir $(SHLIB) $(ARLIB) -client: | mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) -lang: | mk_obj_dir $(SHLIB_LANG) $(ARLIB_LANG) -api_client: | api client lang -wrapper: | mk_obj_dir api_client $(SHLIB_WRAP) $(ARLIB_WRAP) - +api: mk_obj_dir $(SHLIB) $(ARLIB) +client: mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) +lang: mk_obj_dir $(SHLIB_LANG) $(ARLIB_LANG) +api_client: api client lang +wrapper: mk_obj_dir api_client $(SHLIB_WRAP) $(ARLIB_WRAP) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. @@ -138,7 +136,7 @@ $(ARLIB_LANG): $(LANG_OBJS) clean: $(RM) -r obj $(RM) -r docs/generated - $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) $(SHLIB_LANG) $(ARLIB_LANG) + $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) $(SHLIB_LANG) $(ARLIB_LANG) $(SHLIB_WRAP) $(ARLIB_WRAP) strip: $(I2PD) $(SHLIB) $(SHLIB_CLIENT) $(SHLIB_LANG) strip $^ From a3b172bbcbdd279514e37c78ad6d8a67e315eda2 Mon Sep 17 00:00:00 2001 From: r4sas Date: Fri, 6 Aug 2021 22:18:02 +0200 Subject: [PATCH 4378/6300] [makefile] change back directories creation, create them before compiling object files --- Makefile | 25 +++++++++++++++---------- build/.gitignore | 3 ++- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index a4b3c3fa..d7765af7 100644 --- a/Makefile +++ b/Makefile @@ -68,22 +68,27 @@ NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -I$(LANG_SR LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) LIB_CLIENT_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) -WRAP_LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(WRAP_LIB_SRC)) LANG_OBJS += $(patsubst %.cpp,obj/%.o,$(LANG_SRC)) DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) -DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(LANG_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) +WRAP_LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(WRAP_LIB_SRC)) +DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(LANG_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) $(WRAP_LIB_OBJS:.o=.d) ## Build all code (libi2pd, libi2pdclient, libi2pdlang), link it to .a and build binary -all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(I2PD) +all: $(ARLIB) $(ARLIB_CLIENT) $(ARLIB_LANG) $(I2PD) mk_obj_dir: - @mkdir -p obj/{Win32,$(LIB_SRC_DIR),$(LIB_CLIENT_SRC_DIR),$(LANG_SRC_DIR),$(WRAP_SRC_DIR),$(DAEMON_SRC_DIR)} + @mkdir -p obj/$(LIB_SRC_DIR) + @mkdir -p obj/$(LIB_CLIENT_SRC_DIR) + @mkdir -p obj/$(LANG_SRC_DIR) + @mkdir -p obj/$(DAEMON_SRC_DIR) + @mkdir -p obj/$(WRAP_SRC_DIR) + @mkdir -p obj/Win32 -api: mk_obj_dir $(SHLIB) $(ARLIB) -client: mk_obj_dir $(SHLIB_CLIENT) $(ARLIB_CLIENT) -lang: mk_obj_dir $(SHLIB_LANG) $(ARLIB_LANG) +api: $(SHLIB) $(ARLIB) +client: $(SHLIB_CLIENT) $(ARLIB_CLIENT) +lang: $(SHLIB_LANG) $(ARLIB_LANG) api_client: api client lang -wrapper: mk_obj_dir api_client $(SHLIB_WRAP) $(ARLIB_WRAP) +wrapper: api_client $(SHLIB_WRAP) $(ARLIB_WRAP) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. @@ -92,7 +97,7 @@ wrapper: mk_obj_dir api_client $(SHLIB_WRAP) $(ARLIB_WRAP) ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. -obj/%.o: %.cpp +obj/%.o: %.cpp | mk_obj_dir $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -c -o $@ $< # '-' is 'ignore if missing' on first run @@ -121,7 +126,7 @@ ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS) endif -$(ARLIB): $(LIB_OBJS) +$(ARLIB): $(LIB_OBJS) $(AR) -r $@ $^ $(ARLIB_CLIENT): $(LIB_CLIENT_OBJS) diff --git a/build/.gitignore b/build/.gitignore index b595141b..872332c5 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -3,6 +3,7 @@ /i2pd /libi2pd.a /libi2pdclient.a +/libi2pdlang.a /cmake_install.cmake /CMakeCache.txt /CPackConfig.cmake @@ -11,4 +12,4 @@ /arch.c # windows build script i2pd*.zip -build*.log \ No newline at end of file +build*.log From fcbc16f2fd8e34734cfda6c8fd3ba635863fcce9 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 7 Aug 2021 01:37:45 +0300 Subject: [PATCH 4379/6300] [webconsole] fix style issues, clean external style in file was not found on reload Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 5c6a3cd8..ac83f87c 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -66,7 +66,7 @@ namespace http { " a, .slide label { text-decoration: none; color: #894C84; }\r\n" " a:hover, .slide label:hover { color: #FAFAFA; background: #894C84; }\r\n" " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n" - " color: initial; padding: 0 5px; border: 1px solid #894C84; }\r\n" + " padding: 0 5px; border: 1px solid #894C84; }\r\n" " .header { font-size: 2.5em; text-align: center; margin: 1em 0; color: #894C84; }\r\n" " .wrapper { margin: 0 auto; padding: 1em; max-width: 64em; }\r\n" " .menu { display: block; float: left; overflow: hidden; max-width: 12em; white-space: nowrap; text-overflow: ellipsis; }\r\n" @@ -97,7 +97,7 @@ namespace http { " a, .slide label { /* margin-right: 10px; */ display: block; /* font-size: 18px; */ }\r\n" " .header { margin: unset; font-size: 1.5em; } small {display: block}\r\n" " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none;\r\n" - " color: initial; margin-top: 10px; padding: 6px; border: 1px solid #894c84; width: -webkit-fill-available; }\r\n" + " margin-top: 10px; padding: 6px; border: 1px solid #894c84; width: -webkit-fill-available; }\r\n" " input, select { width: 35%; text-align: center; padding: 5px;\r\n" " border: 2px solid #ccc; -webkit-border-radius: 5px; border-radius: 5px; font-size: 18px; }\r\n" " table.extaddr { margin: auto; text-align: unset; }\r\n" @@ -119,6 +119,8 @@ namespace http { std::ifstream f(styleFile, std::ifstream::binary); s << f.rdbuf(); externalCSS = s.str(); + } else if (externalCSS.length() != 0) { // clean up external style if file was removed + externalCSS = ""; } } From bef8587d8fa6684575939d8a2ed5f042aff1a935 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 7 Aug 2021 01:38:35 +0300 Subject: [PATCH 4380/6300] [makefile] create object dirs on windres (race condition) Signed-off-by: R4SAS --- Makefile.mingw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.mingw b/Makefile.mingw index b57860b4..e31d895e 100644 --- a/Makefile.mingw +++ b/Makefile.mingw @@ -61,5 +61,5 @@ ifeq ($(USE_ASLR),yes) LDFLAGS += -Wl,--nxcompat -Wl,--high-entropy-va -Wl,--dynamicbase,--export-all-symbols endif -obj/%.o : %.rc +obj/%.o : %.rc | mk_obj_dir $(WINDRES) -i $< -o $@ From ba369d9b3064eaf01a4b9bf523fa680928e7cafa Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 7 Aug 2021 17:31:26 +0300 Subject: [PATCH 4381/6300] [webconsole] fix style in css Signed-off-by: R4SAS --- contrib/webconsole/style.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/contrib/webconsole/style.css b/contrib/webconsole/style.css index b6e56477..047839a6 100644 --- a/contrib/webconsole/style.css +++ b/contrib/webconsole/style.css @@ -21,7 +21,6 @@ a.button { -moz-appearance: button; appearance: button; text-decoration: none; - color: initial; padding: 0 5px; border: 1px solid #894C84; } @@ -198,7 +197,6 @@ textarea { -moz-appearance: button; appearance: button; text-decoration: none; - color: initial; margin-top: 10px; padding: 6px; border: 1px solid #894c84; From d124d4caceb17a0fb212b79ab258d90184b893c3 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 10 Aug 2021 11:36:12 -0400 Subject: [PATCH 4382/6300] allow ipv6 adresses for UDP server tunnels --- libi2pd_client/ClientContext.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index f8bc5667..6e9ac391 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -720,9 +720,15 @@ namespace client { // udp server tunnel // TODO: hostnames - if (address.empty ()) address = "127.0.0.1"; - auto localAddress = boost::asio::ip::address::from_string(address); boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); + if (address.empty ()) + { + if (!endpoint.address ().is_unspecified () && endpoint.address ().is_v6 ()) + address = "::1"; + else + address = "127.0.0.1"; + } + auto localAddress = boost::asio::ip::address::from_string(address); auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, port, gzip); if(!isUniqueLocal) { From 49b3ac7f77431723f88bb79cf2390778ddfadac8 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Aug 2021 12:23:43 -0400 Subject: [PATCH 4383/6300] don't reschedule resend timer for terminated streams --- libi2pd/Streaming.cpp | 16 ++++++++++------ libi2pd/Streaming.h | 3 ++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index bbb649df..95f6a150 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -104,6 +104,7 @@ namespace stream void Stream::Terminate (bool deleteFromDestination) // shoudl be called from StreamingDestination::Stop only { + m_Status = eStreamStatusTerminated; m_AckSendTimer.cancel (); m_ReceiveTimer.cancel (); m_ResendTimer.cancel (); @@ -857,12 +858,15 @@ namespace stream void Stream::ScheduleResend () { - m_ResendTimer.cancel (); - // check for invalid value - if (m_RTO <= 0) m_RTO = INITIAL_RTO; - m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); - m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, - shared_from_this (), std::placeholders::_1)); + if (m_Status != eStreamStatusTerminated) + { + m_ResendTimer.cancel (); + // check for invalid value + if (m_RTO <= 0) m_RTO = INITIAL_RTO; + m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); + m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, + shared_from_this (), std::placeholders::_1)); + } } void Stream::HandleResendTimer (const boost::system::error_code& ecode) diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index c40c49f5..9d206098 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -152,7 +152,8 @@ namespace stream eStreamStatusOpen, eStreamStatusReset, eStreamStatusClosing, - eStreamStatusClosed + eStreamStatusClosed, + eStreamStatusTerminated }; class StreamingDestination; From 38a2d45a3c24a3be48aee1b16ffec63500e374f5 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 11 Aug 2021 12:31:46 -0400 Subject: [PATCH 4384/6300] close all existing streams when command SAM socket got closed --- libi2pd_client/SAM.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp index af9ba6a8..c2983f43 100644 --- a/libi2pd_client/SAM.cpp +++ b/libi2pd_client/SAM.cpp @@ -1207,7 +1207,11 @@ namespace client void SAMSingleSession::StopLocalDestination () { localDestination->Release (); + // stop accepting new streams localDestination->StopAcceptingStreams (); + // terminate existing streams + auto s = localDestination->GetStreamingDestination (); // TODO: take care about datagrams + if (s) s->Stop (); } void SAMMasterSession::Close () From b3e7b1b5ac79e2283835a07e15efab3f9083756e Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 13 Aug 2021 09:11:56 +0300 Subject: [PATCH 4385/6300] Squashed commit of the following: commit 40ec4e8b59e91efe2ef7654c8c0938facfddef1b Author: Simon Vetter Date: Fri Jul 30 21:23:27 2021 +0200 libi2pd: mark additional ipv6 addresses/nets as reserved This adds :: (undefined address), ::1 (loopback address) as well as ff00::/8 (multicast prefix) to reservedIPv6Ranges. A bunch of nodes seem to be publishing bogus addresses (mostly ::1) in the netDB, resulting in unnecessary tunnel build failures. Signed-off-by: R4SAS --- libi2pd/util.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 69fc366a..2d5617b6 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -555,7 +555,10 @@ namespace net static const std::vector< std::pair > reservedIPv6Ranges { address_pair_v6("2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"), address_pair_v6("fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), - address_pair_v6("fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + address_pair_v6("fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), + address_pair_v6("ff00::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), + address_pair_v6("::", "::"), + address_pair_v6("::1", "::1") }; boost::asio::ip::address_v6::bytes_type ipv6_address = host.to_v6 ().to_bytes (); From 1e17ef2f21b83a9b49fb803354b377aff6998ca1 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 13 Aug 2021 09:17:27 +0300 Subject: [PATCH 4386/6300] [webconsole] show v4 status only ipv4 is enabled Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index ac83f87c..d13aeb32 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -326,9 +326,12 @@ namespace http { s << "" << tr("Uptime") << ": "; ShowUptime(s, i2p::context.GetUptime ()); s << "
    \r\n"; - s << "" << tr("Network status") << ": "; - ShowNetworkStatus (s, i2p::context.GetStatus ()); - s << "
    \r\n"; + if (i2p::context.SupportsV4 ()) + { + s << "" << tr("Network status") << ": "; + ShowNetworkStatus (s, i2p::context.GetStatus ()); + s << "
    \r\n"; + } if (i2p::context.SupportsV6 ()) { s << "" << tr("Network status v6") << ": "; From fc29911ffda418cfa94d39f16ebdc425e920032b Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 13 Aug 2021 11:36:04 -0400 Subject: [PATCH 4387/6300] rollback --- daemon/HTTPServer.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index d13aeb32..ac83f87c 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -326,12 +326,9 @@ namespace http { s << "" << tr("Uptime") << ": "; ShowUptime(s, i2p::context.GetUptime ()); s << "
    \r\n"; - if (i2p::context.SupportsV4 ()) - { - s << "" << tr("Network status") << ": "; - ShowNetworkStatus (s, i2p::context.GetStatus ()); - s << "
    \r\n"; - } + s << "" << tr("Network status") << ": "; + ShowNetworkStatus (s, i2p::context.GetStatus ()); + s << "
    \r\n"; if (i2p::context.SupportsV6 ()) { s << "" << tr("Network status v6") << ": "; From 797f5eb714a9c4accf7dce10fc865c9e3a816f2c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 13 Aug 2021 13:31:04 -0400 Subject: [PATCH 4388/6300] select compatible resolved address for server tunnel --- libi2pd_client/I2PTunnel.cpp | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 5476bfe2..378dc705 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -605,7 +605,40 @@ namespace client { if (!ecode) { - auto addr = (*it).endpoint ().address (); + bool found = false; + boost::asio::ip::tcp::resolver::iterator end; + boost::asio::ip::tcp::endpoint ep; + while (it != end) + { + ep = *it; + if (!ep.address ().is_unspecified ()) + { + if (ep.address ().is_v4 ()) + { + if (!m_LocalAddress || m_LocalAddress->is_v4 ()) // look for ipv4 if not specified + found = true; + } + else if (ep.address ().is_v6 ()) + { + if (i2p::util::net::IsYggdrasilAddress (ep.address ())) + { + if (m_LocalAddress && i2p::util::net::IsYggdrasilAddress (*m_LocalAddress)) + found = true; + } + else if (m_LocalAddress && m_LocalAddress->is_v6 ()) + found = true; + } + } + if (found) break; + it++; + } + if (!found) + { + LogPrint (eLogError, "I2PTunnel: Unable to reslove to compatible address"); + return; + } + + auto addr = ep.address (); LogPrint (eLogInfo, "I2PTunnel: server tunnel ", (*it).host_name (), " has been resolved to ", addr); m_Endpoint.address (addr); Accept (); From b0874410f1a48a158461c4971a74604b6e7a9542 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 13 Aug 2021 13:54:23 -0400 Subject: [PATCH 4389/6300] take first avalable resolved address if local address is not specified --- libi2pd_client/I2PTunnel.cpp | 51 ++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index 378dc705..e8a66228 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -606,35 +606,42 @@ namespace client if (!ecode) { bool found = false; - boost::asio::ip::tcp::resolver::iterator end; boost::asio::ip::tcp::endpoint ep; - while (it != end) + if (m_LocalAddress) { - ep = *it; - if (!ep.address ().is_unspecified ()) - { - if (ep.address ().is_v4 ()) - { - if (!m_LocalAddress || m_LocalAddress->is_v4 ()) // look for ipv4 if not specified - found = true; - } - else if (ep.address ().is_v6 ()) + boost::asio::ip::tcp::resolver::iterator end; + while (it != end) + { + ep = *it; + if (!ep.address ().is_unspecified ()) { - if (i2p::util::net::IsYggdrasilAddress (ep.address ())) + if (ep.address ().is_v4 ()) + { + if (m_LocalAddress->is_v4 ()) found = true; + } + else if (ep.address ().is_v6 ()) { - if (m_LocalAddress && i2p::util::net::IsYggdrasilAddress (*m_LocalAddress)) + if (i2p::util::net::IsYggdrasilAddress (ep.address ())) + { + if (i2p::util::net::IsYggdrasilAddress (*m_LocalAddress)) + found = true; + } + else if (m_LocalAddress->is_v6 ()) found = true; - } - else if (m_LocalAddress && m_LocalAddress->is_v6 ()) - found = true; - } - } - if (found) break; - it++; - } + } + } + if (found) break; + it++; + } + } + else + { + found = true; + ep = *it; // first available + } if (!found) { - LogPrint (eLogError, "I2PTunnel: Unable to reslove to compatible address"); + LogPrint (eLogError, "I2PTunnel: Unable to resolve to compatible address"); return; } From 8c3823fc920578eb09efd330f3b33c1408ab30b7 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 15 Aug 2021 16:53:10 +0300 Subject: [PATCH 4390/6300] [gha] build docker containers for arm/arm64 Signed-off-by: R4SAS --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index aced7f39..1c01d373 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -38,7 +38,7 @@ jobs: with: context: ./contrib/docker file: ./contrib/docker/Dockerfile - platforms: linux/amd64,linux/386 + platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 push: true tags: | purplei2p/i2pd:latest @@ -54,7 +54,7 @@ jobs: with: context: ./contrib/docker file: ./contrib/docker/Dockerfile - platforms: linux/amd64,linux/386 + platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7 push: true tags: | purplei2p/i2pd:latest From 86e118f2b78d6f8ce6c1e50fe79220a546c1f27f Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 18 Aug 2021 22:23:39 +0300 Subject: [PATCH 4391/6300] [i18n] change string in HTTPProxy Signed-off-by: R4SAS --- contrib/i18n/English.po | 308 +++++++++++++++++------------------ contrib/i18n/README.md | 4 +- libi2pd_client/HTTPProxy.cpp | 6 +- 3 files changed, 157 insertions(+), 161 deletions(-) diff --git a/contrib/i18n/English.po b/contrib/i18n/English.po index 5a7cc09c..25378f82 100644 --- a/contrib/i18n/English.po +++ b/contrib/i18n/English.po @@ -18,564 +18,564 @@ msgstr "" "X-Poedit-SearchPath-0: daemon/HTTPServer.cpp\n" "X-Poedit-SearchPath-1: libi2pd_client/HTTPProxy.cpp\n" -#: daemon/HTTPServer.cpp:175 +#: daemon/HTTPServer.cpp:177 msgid "day" msgid_plural "days" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:179 +#: daemon/HTTPServer.cpp:181 msgid "hour" msgid_plural "hours" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:183 +#: daemon/HTTPServer.cpp:185 msgid "minute" msgid_plural "minutes" msgstr[0] "" msgstr[1] "" -#: daemon/HTTPServer.cpp:186 +#: daemon/HTTPServer.cpp:188 msgid "second" msgid_plural "seconds" msgstr[0] "" msgstr[1] "" #. tr: Kibibit -#: daemon/HTTPServer.cpp:194 daemon/HTTPServer.cpp:222 +#: daemon/HTTPServer.cpp:196 daemon/HTTPServer.cpp:224 msgid "KiB" msgstr "" #. tr: Mebibit -#: daemon/HTTPServer.cpp:196 +#: daemon/HTTPServer.cpp:198 msgid "MiB" msgstr "" #. tr: Gibibit -#: daemon/HTTPServer.cpp:198 +#: daemon/HTTPServer.cpp:200 msgid "GiB" msgstr "" -#: daemon/HTTPServer.cpp:215 +#: daemon/HTTPServer.cpp:217 msgid "building" msgstr "" -#: daemon/HTTPServer.cpp:216 +#: daemon/HTTPServer.cpp:218 msgid "failed" msgstr "" -#: daemon/HTTPServer.cpp:217 +#: daemon/HTTPServer.cpp:219 msgid "expiring" msgstr "" -#: daemon/HTTPServer.cpp:218 +#: daemon/HTTPServer.cpp:220 msgid "established" msgstr "" -#: daemon/HTTPServer.cpp:219 +#: daemon/HTTPServer.cpp:221 msgid "unknown" msgstr "" -#: daemon/HTTPServer.cpp:221 +#: daemon/HTTPServer.cpp:223 msgid "exploratory" msgstr "" -#: daemon/HTTPServer.cpp:257 +#: daemon/HTTPServer.cpp:259 msgid "i2pd webconsole" msgstr "" -#: daemon/HTTPServer.cpp:260 +#: daemon/HTTPServer.cpp:262 msgid "Main page" msgstr "" -#: daemon/HTTPServer.cpp:261 daemon/HTTPServer.cpp:723 +#: daemon/HTTPServer.cpp:263 daemon/HTTPServer.cpp:725 msgid "Router commands" msgstr "" -#: daemon/HTTPServer.cpp:262 daemon/HTTPServer.cpp:446 -#: daemon/HTTPServer.cpp:458 +#: daemon/HTTPServer.cpp:264 daemon/HTTPServer.cpp:448 +#: daemon/HTTPServer.cpp:460 msgid "Local Destinations" msgstr "" -#: daemon/HTTPServer.cpp:264 daemon/HTTPServer.cpp:416 -#: daemon/HTTPServer.cpp:502 daemon/HTTPServer.cpp:508 -#: daemon/HTTPServer.cpp:639 daemon/HTTPServer.cpp:682 -#: daemon/HTTPServer.cpp:686 +#: daemon/HTTPServer.cpp:266 daemon/HTTPServer.cpp:418 +#: daemon/HTTPServer.cpp:504 daemon/HTTPServer.cpp:510 +#: daemon/HTTPServer.cpp:641 daemon/HTTPServer.cpp:684 +#: daemon/HTTPServer.cpp:688 msgid "LeaseSets" msgstr "" -#: daemon/HTTPServer.cpp:266 daemon/HTTPServer.cpp:692 +#: daemon/HTTPServer.cpp:268 daemon/HTTPServer.cpp:694 msgid "Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:267 daemon/HTTPServer.cpp:423 -#: daemon/HTTPServer.cpp:785 daemon/HTTPServer.cpp:801 +#: daemon/HTTPServer.cpp:269 daemon/HTTPServer.cpp:425 +#: daemon/HTTPServer.cpp:787 daemon/HTTPServer.cpp:803 msgid "Transit Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:268 daemon/HTTPServer.cpp:850 +#: daemon/HTTPServer.cpp:270 daemon/HTTPServer.cpp:852 msgid "Transports" msgstr "" -#: daemon/HTTPServer.cpp:269 +#: daemon/HTTPServer.cpp:271 msgid "I2P tunnels" msgstr "" -#: daemon/HTTPServer.cpp:271 daemon/HTTPServer.cpp:912 -#: daemon/HTTPServer.cpp:922 +#: daemon/HTTPServer.cpp:273 daemon/HTTPServer.cpp:914 +#: daemon/HTTPServer.cpp:924 msgid "SAM sessions" msgstr "" -#: daemon/HTTPServer.cpp:287 daemon/HTTPServer.cpp:1304 -#: daemon/HTTPServer.cpp:1307 daemon/HTTPServer.cpp:1310 -#: daemon/HTTPServer.cpp:1324 daemon/HTTPServer.cpp:1369 -#: daemon/HTTPServer.cpp:1372 daemon/HTTPServer.cpp:1375 +#: daemon/HTTPServer.cpp:289 daemon/HTTPServer.cpp:1306 +#: daemon/HTTPServer.cpp:1309 daemon/HTTPServer.cpp:1312 +#: daemon/HTTPServer.cpp:1326 daemon/HTTPServer.cpp:1371 +#: daemon/HTTPServer.cpp:1374 daemon/HTTPServer.cpp:1377 msgid "ERROR" msgstr "" -#: daemon/HTTPServer.cpp:294 +#: daemon/HTTPServer.cpp:296 msgid "OK" msgstr "" -#: daemon/HTTPServer.cpp:295 +#: daemon/HTTPServer.cpp:297 msgid "Testing" msgstr "" -#: daemon/HTTPServer.cpp:296 +#: daemon/HTTPServer.cpp:298 msgid "Firewalled" msgstr "" -#: daemon/HTTPServer.cpp:297 daemon/HTTPServer.cpp:318 -#: daemon/HTTPServer.cpp:404 +#: daemon/HTTPServer.cpp:299 daemon/HTTPServer.cpp:320 +#: daemon/HTTPServer.cpp:406 msgid "Unknown" msgstr "" -#: daemon/HTTPServer.cpp:298 daemon/HTTPServer.cpp:433 -#: daemon/HTTPServer.cpp:434 daemon/HTTPServer.cpp:980 -#: daemon/HTTPServer.cpp:989 +#: daemon/HTTPServer.cpp:300 daemon/HTTPServer.cpp:435 +#: daemon/HTTPServer.cpp:436 daemon/HTTPServer.cpp:982 +#: daemon/HTTPServer.cpp:991 msgid "Proxy" msgstr "" -#: daemon/HTTPServer.cpp:299 +#: daemon/HTTPServer.cpp:301 msgid "Mesh" msgstr "" -#: daemon/HTTPServer.cpp:302 +#: daemon/HTTPServer.cpp:304 msgid "Error" msgstr "" -#: daemon/HTTPServer.cpp:306 +#: daemon/HTTPServer.cpp:308 msgid "Clock skew" msgstr "" -#: daemon/HTTPServer.cpp:309 +#: daemon/HTTPServer.cpp:311 msgid "Offline" msgstr "" -#: daemon/HTTPServer.cpp:312 +#: daemon/HTTPServer.cpp:314 msgid "Symmetric NAT" msgstr "" -#: daemon/HTTPServer.cpp:324 +#: daemon/HTTPServer.cpp:326 msgid "Uptime" msgstr "" -#: daemon/HTTPServer.cpp:327 +#: daemon/HTTPServer.cpp:329 msgid "Network status" msgstr "" -#: daemon/HTTPServer.cpp:332 +#: daemon/HTTPServer.cpp:334 msgid "Network status v6" msgstr "" -#: daemon/HTTPServer.cpp:338 daemon/HTTPServer.cpp:345 +#: daemon/HTTPServer.cpp:340 daemon/HTTPServer.cpp:347 msgid "Stopping in" msgstr "" -#: daemon/HTTPServer.cpp:352 +#: daemon/HTTPServer.cpp:354 msgid "Family" msgstr "" -#: daemon/HTTPServer.cpp:353 +#: daemon/HTTPServer.cpp:355 msgid "Tunnel creation success rate" msgstr "" -#: daemon/HTTPServer.cpp:354 +#: daemon/HTTPServer.cpp:356 msgid "Received" msgstr "" #. tr: Kibibit/s -#: daemon/HTTPServer.cpp:356 daemon/HTTPServer.cpp:359 -#: daemon/HTTPServer.cpp:362 +#: daemon/HTTPServer.cpp:358 daemon/HTTPServer.cpp:361 +#: daemon/HTTPServer.cpp:364 msgid "KiB/s" msgstr "" -#: daemon/HTTPServer.cpp:357 +#: daemon/HTTPServer.cpp:359 msgid "Sent" msgstr "" -#: daemon/HTTPServer.cpp:360 +#: daemon/HTTPServer.cpp:362 msgid "Transit" msgstr "" -#: daemon/HTTPServer.cpp:363 +#: daemon/HTTPServer.cpp:365 msgid "Data path" msgstr "" -#: daemon/HTTPServer.cpp:366 +#: daemon/HTTPServer.cpp:368 msgid "Hidden content. Press on text to see." msgstr "" -#: daemon/HTTPServer.cpp:369 +#: daemon/HTTPServer.cpp:371 msgid "Router Ident" msgstr "" -#: daemon/HTTPServer.cpp:371 +#: daemon/HTTPServer.cpp:373 msgid "Router Family" msgstr "" -#: daemon/HTTPServer.cpp:372 +#: daemon/HTTPServer.cpp:374 msgid "Router Caps" msgstr "" -#: daemon/HTTPServer.cpp:373 +#: daemon/HTTPServer.cpp:375 msgid "Version" msgstr "" -#: daemon/HTTPServer.cpp:374 +#: daemon/HTTPServer.cpp:376 msgid "Our external address" msgstr "" -#: daemon/HTTPServer.cpp:382 +#: daemon/HTTPServer.cpp:384 msgid "supported" msgstr "" -#: daemon/HTTPServer.cpp:414 +#: daemon/HTTPServer.cpp:416 msgid "Routers" msgstr "" -#: daemon/HTTPServer.cpp:415 +#: daemon/HTTPServer.cpp:417 msgid "Floodfills" msgstr "" -#: daemon/HTTPServer.cpp:422 daemon/HTTPServer.cpp:966 +#: daemon/HTTPServer.cpp:424 daemon/HTTPServer.cpp:968 msgid "Client Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:432 +#: daemon/HTTPServer.cpp:434 msgid "Services" msgstr "" -#: daemon/HTTPServer.cpp:433 daemon/HTTPServer.cpp:434 #: daemon/HTTPServer.cpp:435 daemon/HTTPServer.cpp:436 #: daemon/HTTPServer.cpp:437 daemon/HTTPServer.cpp:438 +#: daemon/HTTPServer.cpp:439 daemon/HTTPServer.cpp:440 msgid "Enabled" msgstr "" -#: daemon/HTTPServer.cpp:433 daemon/HTTPServer.cpp:434 #: daemon/HTTPServer.cpp:435 daemon/HTTPServer.cpp:436 #: daemon/HTTPServer.cpp:437 daemon/HTTPServer.cpp:438 +#: daemon/HTTPServer.cpp:439 daemon/HTTPServer.cpp:440 msgid "Disabled" msgstr "" -#: daemon/HTTPServer.cpp:481 +#: daemon/HTTPServer.cpp:483 msgid "Encrypted B33 address" msgstr "" -#: daemon/HTTPServer.cpp:490 +#: daemon/HTTPServer.cpp:492 msgid "Address registration line" msgstr "" -#: daemon/HTTPServer.cpp:495 +#: daemon/HTTPServer.cpp:497 msgid "Domain" msgstr "" -#: daemon/HTTPServer.cpp:496 +#: daemon/HTTPServer.cpp:498 msgid "Generate" msgstr "" -#: daemon/HTTPServer.cpp:497 +#: daemon/HTTPServer.cpp:499 msgid "" "Note: result string can be used only for registering 2LD domains " "(example.i2p). For registering subdomains please use i2pd-tools." msgstr "" -#: daemon/HTTPServer.cpp:503 +#: daemon/HTTPServer.cpp:505 msgid "Address" msgstr "" -#: daemon/HTTPServer.cpp:503 +#: daemon/HTTPServer.cpp:505 msgid "Type" msgstr "" -#: daemon/HTTPServer.cpp:503 +#: daemon/HTTPServer.cpp:505 msgid "EncType" msgstr "" -#: daemon/HTTPServer.cpp:513 daemon/HTTPServer.cpp:697 +#: daemon/HTTPServer.cpp:515 daemon/HTTPServer.cpp:699 msgid "Inbound tunnels" msgstr "" #. tr: Milliseconds -#: daemon/HTTPServer.cpp:518 daemon/HTTPServer.cpp:528 -#: daemon/HTTPServer.cpp:702 daemon/HTTPServer.cpp:712 +#: daemon/HTTPServer.cpp:520 daemon/HTTPServer.cpp:530 +#: daemon/HTTPServer.cpp:704 daemon/HTTPServer.cpp:714 msgid "ms" msgstr "" -#: daemon/HTTPServer.cpp:523 daemon/HTTPServer.cpp:707 +#: daemon/HTTPServer.cpp:525 daemon/HTTPServer.cpp:709 msgid "Outbound tunnels" msgstr "" -#: daemon/HTTPServer.cpp:535 +#: daemon/HTTPServer.cpp:537 msgid "Tags" msgstr "" -#: daemon/HTTPServer.cpp:535 +#: daemon/HTTPServer.cpp:537 msgid "Incoming" msgstr "" -#: daemon/HTTPServer.cpp:542 daemon/HTTPServer.cpp:545 +#: daemon/HTTPServer.cpp:544 daemon/HTTPServer.cpp:547 msgid "Outgoing" msgstr "" -#: daemon/HTTPServer.cpp:543 daemon/HTTPServer.cpp:559 +#: daemon/HTTPServer.cpp:545 daemon/HTTPServer.cpp:561 msgid "Destination" msgstr "" -#: daemon/HTTPServer.cpp:543 +#: daemon/HTTPServer.cpp:545 msgid "Amount" msgstr "" -#: daemon/HTTPServer.cpp:550 +#: daemon/HTTPServer.cpp:552 msgid "Incoming Tags" msgstr "" -#: daemon/HTTPServer.cpp:558 daemon/HTTPServer.cpp:561 +#: daemon/HTTPServer.cpp:560 daemon/HTTPServer.cpp:563 msgid "Tags sessions" msgstr "" -#: daemon/HTTPServer.cpp:559 +#: daemon/HTTPServer.cpp:561 msgid "Status" msgstr "" -#: daemon/HTTPServer.cpp:568 daemon/HTTPServer.cpp:624 +#: daemon/HTTPServer.cpp:570 daemon/HTTPServer.cpp:626 msgid "Local Destination" msgstr "" -#: daemon/HTTPServer.cpp:578 daemon/HTTPServer.cpp:945 +#: daemon/HTTPServer.cpp:580 daemon/HTTPServer.cpp:947 msgid "Streams" msgstr "" -#: daemon/HTTPServer.cpp:600 +#: daemon/HTTPServer.cpp:602 msgid "Close stream" msgstr "" -#: daemon/HTTPServer.cpp:629 +#: daemon/HTTPServer.cpp:631 msgid "I2CP session not found" msgstr "" -#: daemon/HTTPServer.cpp:632 +#: daemon/HTTPServer.cpp:634 msgid "I2CP is not enabled" msgstr "" -#: daemon/HTTPServer.cpp:658 +#: daemon/HTTPServer.cpp:660 msgid "Invalid" msgstr "" -#: daemon/HTTPServer.cpp:661 +#: daemon/HTTPServer.cpp:663 msgid "Store type" msgstr "" -#: daemon/HTTPServer.cpp:662 +#: daemon/HTTPServer.cpp:664 msgid "Expires" msgstr "" -#: daemon/HTTPServer.cpp:667 +#: daemon/HTTPServer.cpp:669 msgid "Non Expired Leases" msgstr "" -#: daemon/HTTPServer.cpp:670 +#: daemon/HTTPServer.cpp:672 msgid "Gateway" msgstr "" -#: daemon/HTTPServer.cpp:671 +#: daemon/HTTPServer.cpp:673 msgid "TunnelID" msgstr "" -#: daemon/HTTPServer.cpp:672 +#: daemon/HTTPServer.cpp:674 msgid "EndDate" msgstr "" -#: daemon/HTTPServer.cpp:682 +#: daemon/HTTPServer.cpp:684 msgid "not floodfill" msgstr "" -#: daemon/HTTPServer.cpp:693 +#: daemon/HTTPServer.cpp:695 msgid "Queue size" msgstr "" -#: daemon/HTTPServer.cpp:724 +#: daemon/HTTPServer.cpp:726 msgid "Run peer test" msgstr "" -#: daemon/HTTPServer.cpp:729 +#: daemon/HTTPServer.cpp:731 msgid "Decline transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:731 +#: daemon/HTTPServer.cpp:733 msgid "Accept transit tunnels" msgstr "" -#: daemon/HTTPServer.cpp:735 daemon/HTTPServer.cpp:740 +#: daemon/HTTPServer.cpp:737 daemon/HTTPServer.cpp:742 msgid "Cancel graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:737 daemon/HTTPServer.cpp:742 +#: daemon/HTTPServer.cpp:739 daemon/HTTPServer.cpp:744 msgid "Start graceful shutdown" msgstr "" -#: daemon/HTTPServer.cpp:745 +#: daemon/HTTPServer.cpp:747 msgid "Force shutdown" msgstr "" -#: daemon/HTTPServer.cpp:746 +#: daemon/HTTPServer.cpp:748 msgid "Reload external CSS styles" msgstr "" -#: daemon/HTTPServer.cpp:749 +#: daemon/HTTPServer.cpp:751 msgid "" "Note: any action done here are not persistent and not changes your " "config files." msgstr "" -#: daemon/HTTPServer.cpp:751 +#: daemon/HTTPServer.cpp:753 msgid "Logging level" msgstr "" -#: daemon/HTTPServer.cpp:759 +#: daemon/HTTPServer.cpp:761 msgid "Transit tunnels limit" msgstr "" -#: daemon/HTTPServer.cpp:764 daemon/HTTPServer.cpp:776 +#: daemon/HTTPServer.cpp:766 daemon/HTTPServer.cpp:778 msgid "Change" msgstr "" -#: daemon/HTTPServer.cpp:768 +#: daemon/HTTPServer.cpp:770 msgid "Change language" msgstr "" -#: daemon/HTTPServer.cpp:801 +#: daemon/HTTPServer.cpp:803 msgid "no transit tunnels currently built" msgstr "" -#: daemon/HTTPServer.cpp:906 daemon/HTTPServer.cpp:929 +#: daemon/HTTPServer.cpp:908 daemon/HTTPServer.cpp:931 msgid "SAM disabled" msgstr "" -#: daemon/HTTPServer.cpp:922 +#: daemon/HTTPServer.cpp:924 msgid "no sessions currently running" msgstr "" -#: daemon/HTTPServer.cpp:935 +#: daemon/HTTPServer.cpp:937 msgid "SAM session not found" msgstr "" -#: daemon/HTTPServer.cpp:940 +#: daemon/HTTPServer.cpp:942 msgid "SAM Session" msgstr "" -#: daemon/HTTPServer.cpp:997 +#: daemon/HTTPServer.cpp:999 msgid "Server Tunnels" msgstr "" -#: daemon/HTTPServer.cpp:1013 +#: daemon/HTTPServer.cpp:1015 msgid "Client Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1027 +#: daemon/HTTPServer.cpp:1029 msgid "Server Forwards" msgstr "" -#: daemon/HTTPServer.cpp:1225 +#: daemon/HTTPServer.cpp:1227 msgid "Unknown page" msgstr "" -#: daemon/HTTPServer.cpp:1244 +#: daemon/HTTPServer.cpp:1246 msgid "Invalid token" msgstr "" -#: daemon/HTTPServer.cpp:1302 daemon/HTTPServer.cpp:1359 -#: daemon/HTTPServer.cpp:1399 +#: daemon/HTTPServer.cpp:1304 daemon/HTTPServer.cpp:1361 +#: daemon/HTTPServer.cpp:1401 msgid "SUCCESS" msgstr "" -#: daemon/HTTPServer.cpp:1302 +#: daemon/HTTPServer.cpp:1304 msgid "Stream closed" msgstr "" -#: daemon/HTTPServer.cpp:1304 +#: daemon/HTTPServer.cpp:1306 msgid "Stream not found or already was closed" msgstr "" -#: daemon/HTTPServer.cpp:1307 +#: daemon/HTTPServer.cpp:1309 msgid "Destination not found" msgstr "" -#: daemon/HTTPServer.cpp:1310 +#: daemon/HTTPServer.cpp:1312 msgid "StreamID can't be null" msgstr "" -#: daemon/HTTPServer.cpp:1312 daemon/HTTPServer.cpp:1377 +#: daemon/HTTPServer.cpp:1314 daemon/HTTPServer.cpp:1379 msgid "Return to destination page" msgstr "" -#: daemon/HTTPServer.cpp:1313 daemon/HTTPServer.cpp:1326 -#: daemon/HTTPServer.cpp:1401 +#: daemon/HTTPServer.cpp:1315 daemon/HTTPServer.cpp:1328 +#: daemon/HTTPServer.cpp:1403 msgid "You will be redirected in 5 seconds" msgstr "" -#: daemon/HTTPServer.cpp:1324 +#: daemon/HTTPServer.cpp:1326 msgid "Transit tunnels count must not exceed 65535" msgstr "" -#: daemon/HTTPServer.cpp:1325 daemon/HTTPServer.cpp:1400 +#: daemon/HTTPServer.cpp:1327 daemon/HTTPServer.cpp:1402 msgid "Back to commands list" msgstr "" -#: daemon/HTTPServer.cpp:1361 +#: daemon/HTTPServer.cpp:1363 msgid "Register at reg.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1362 +#: daemon/HTTPServer.cpp:1364 msgid "Description" msgstr "" -#: daemon/HTTPServer.cpp:1362 +#: daemon/HTTPServer.cpp:1364 msgid "A bit information about service on domain" msgstr "" -#: daemon/HTTPServer.cpp:1363 +#: daemon/HTTPServer.cpp:1365 msgid "Submit" msgstr "" -#: daemon/HTTPServer.cpp:1369 +#: daemon/HTTPServer.cpp:1371 msgid "Domain can't end with .b32.i2p" msgstr "" -#: daemon/HTTPServer.cpp:1372 +#: daemon/HTTPServer.cpp:1374 msgid "Domain must end with .i2p" msgstr "" -#: daemon/HTTPServer.cpp:1375 +#: daemon/HTTPServer.cpp:1377 msgid "Such destination is not found" msgstr "" -#: daemon/HTTPServer.cpp:1395 +#: daemon/HTTPServer.cpp:1397 msgid "Unknown command" msgstr "" -#: daemon/HTTPServer.cpp:1399 +#: daemon/HTTPServer.cpp:1401 msgid "Command accepted" msgstr "" @@ -621,16 +621,12 @@ msgstr "" msgid "added to router's addressbook from helper" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:298 libi2pd_client/HTTPProxy.cpp:307 -msgid "Click" +#: libi2pd_client/HTTPProxy.cpp:298 +msgid "Click here to proceed:" msgstr "" #: libi2pd_client/HTTPProxy.cpp:298 libi2pd_client/HTTPProxy.cpp:308 -msgid "here" -msgstr "" - -#: libi2pd_client/HTTPProxy.cpp:298 -msgid "to proceed" +msgid "Continue" msgstr "" #: libi2pd_client/HTTPProxy.cpp:299 libi2pd_client/HTTPProxy.cpp:309 @@ -641,8 +637,8 @@ msgstr "" msgid "already in router's addressbook" msgstr "" -#: libi2pd_client/HTTPProxy.cpp:308 -msgid "to update record" +#: libi2pd_client/HTTPProxy.cpp:307 +msgid "Click here to update record:" msgstr "" #: libi2pd_client/HTTPProxy.cpp:322 diff --git a/contrib/i18n/README.md b/contrib/i18n/README.md index be44e87f..04779473 100644 --- a/contrib/i18n/README.md +++ b/contrib/i18n/README.md @@ -1,12 +1,12 @@ `xgettext` command for extracting translation -=== +--- ``` xgettext --omit-header -ctr: -ktr -ktr:1,2 daemon/HTTPServer.cpp libi2pd_client/HTTPProxy.cpp ``` Regex for transforming gettext translations to our format: -=== +--- ``` in: msgid\ \"(.*)\"\nmsgid_plural\ \"(.*)\"\nmsgstr\[0\]\ \"(.*)\"\nmsgstr\[1\]\ \"(.*)\"\n(msgstr\[2\]\ \"(.*)\"\n)?(msgstr\[3\]\ \"(.*)\"\n)?(msgstr\[4\]\ \"(.*)\"\n)?(msgstr\[5\]\ \"(.*)\"\n)? diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index 70cf78a8..7acdc333 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -295,7 +295,7 @@ namespace proxy { std::string full_url = m_RequestURL.to_string(); std::stringstream ss; ss << tr("Host") <<" " << m_RequestURL.host << " " << tr("added to router's addressbook from helper") << ". "; - ss << tr("Click") << " " << tr("here") << " " << tr("to proceed") << "."; + ss << tr("Click here to proceed:") << " " << tr("Continue") << "."; GenericProxyInfo(tr("Addresshelper found"), ss.str()); return true; /* request processed */ } @@ -304,8 +304,8 @@ namespace proxy { std::string full_url = m_RequestURL.to_string(); std::stringstream ss; ss << tr("Host") << " " << m_RequestURL.host << " " << tr("already in router's addressbook") << ". "; - ss << tr("Click") << " " << tr("here") << " " << tr("to update record") << "."; + ss << tr("Click here to update record:") << " " << tr("Continue") << "."; GenericProxyInfo(tr("Addresshelper found"), ss.str()); return true; /* request processed */ } From 8943d212ee16f297f2bb09789f6fd22e9f299d67 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 18 Aug 2021 22:55:14 +0300 Subject: [PATCH 4392/6300] [i18n] add Uzbek translation (partial) Signed-off-by: R4SAS --- i18n/Afrikaans.cpp | 11 ++- i18n/I18N_langs.h | 1 + i18n/Russian.cpp | 7 +- i18n/Turkmen.cpp | 7 +- i18n/Ukrainian.cpp | 7 +- i18n/Uzbek.cpp | 206 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 224 insertions(+), 15 deletions(-) create mode 100644 i18n/Uzbek.cpp diff --git a/i18n/Afrikaans.cpp b/i18n/Afrikaans.cpp index 96ee52ee..5860facf 100644 --- a/i18n/Afrikaans.cpp +++ b/i18n/Afrikaans.cpp @@ -31,12 +31,9 @@ namespace afrikaans // language namespace static std::map strings { - {"Disabled", "Gedeaktiveer"}, - {"Enabled", "Geaktiveer"}, {"failed", "Het misluk"}, {"unknown", "onbekend"}, {"Tunnels", "Tonnels"}, - {"Transit tunnels", "Deurgang tonnels"}, {"I2P tunnels", "I2P tonnels"}, {"SAM sessions", "SAM sessies"}, {"OK", "LEKKER"}, @@ -54,6 +51,14 @@ namespace afrikaans // language namespace {"Hidden content. Press on text to see.", "Hidden content. Druk om te sien."}, {"Router Ident", "Router Ident"}, {"Router Family", "Router Familie"}, + {"Enabled", "Geaktiveer"}, + {"Disabled", "Gedeaktiveer"}, + {"Change", "Verander"}, + {"Change language", "Verander taal"}, + {"Description", "Beskrywing"}, + {"Submit", "Stuur"}, + {"Proxy error", "Proxy-fout"}, + {"Host", "Gasheer"}, {"", ""}, }; diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index a5029782..957d550d 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -87,6 +87,7 @@ namespace i18n { "russian", {"руÑÑкий Ñзык", "ru", i2p::i18n::russian::GetLocale} }, { "turkmen", {"türkmen dili", "tk", i2p::i18n::turkmen::GetLocale} }, { "ukrainian", {"україÌнÑька моÌва", "uk", i2p::i18n::ukrainian::GetLocale} }, + { "uzbek", {"OÊ»zbek", "uz", i2p::i18n::uzbek::GetLocale} }, }; } // i18n diff --git a/i18n/Russian.cpp b/i18n/Russian.cpp index a826cdda..f6a19c7c 100644 --- a/i18n/Russian.cpp +++ b/i18n/Russian.cpp @@ -168,12 +168,11 @@ namespace russian // language namespace {"addresshelper is not supported", "addresshelper не поддерживаетÑÑ"}, {"Host", "Узел"}, {"added to router's addressbook from helper", "добавлен в адреÑную книгу роутера через хелпер"}, - {"Click", "Ðажмите"}, - {"here", "здеÑÑŒ"}, - {"to proceed", "чтобы продолжить"}, + {"Click here to proceed:", "Ðажмите здеÑÑŒ, чтобы продолжить:"}, + {"Continue", "Продолжить"}, {"Addresshelper found", "Ðайден addresshelper"}, {"already in router's addressbook", "уже в адреÑной книге роутера"}, - {"to update record", "чтобы обновить запиÑÑŒ"}, + {"Click here to update record:", "Ðажмите здеÑÑŒ, чтобы обновить запиÑÑŒ:"}, {"invalid request uri", "некорректный URI запроÑа"}, {"Can't detect destination host from request", "Ðе удалоÑÑŒ определить Ð°Ð´Ñ€ÐµÑ Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¸Ð· запроÑа"}, {"Outproxy failure", "Ошибка внешнего прокÑи"}, diff --git a/i18n/Turkmen.cpp b/i18n/Turkmen.cpp index 2bcd77cd..1d4f5ee1 100644 --- a/i18n/Turkmen.cpp +++ b/i18n/Turkmen.cpp @@ -168,12 +168,11 @@ namespace turkmen // language namespace {"addresshelper is not supported", "Salgylandyryjy goldanok"}, {"Host", "Adres"}, {"added to router's addressbook from helper", "marÅŸruteriň adresini kömekçiden goÅŸdy"}, - {"Click", "Basyň"}, - {"here", "bu ýerde"}, - {"to proceed", "dowam etmek"}, + {"Click here to proceed:", "Dowam etmek bu ýerde basyň:"}, + {"Continue", "Dowam et"}, {"Addresshelper found", "Forgelper tapyldy"}, {"already in router's addressbook", "marÅŸruteriň adres kitaby"}, - {"to update record", "recordazgyny täzelemek üçin"}, + {"Click here to update record:", "Recordazgyny täzelemek üçin bu ýerde basyň:"}, {"invalid request uri", "nädogry haýyÅŸ URI"}, {"Can't detect destination host from request", "HaýyÅŸdan barmaly ýerini tapyp bilemok"}, {"Outproxy failure", "DaÅŸarky proksi ýalňyÅŸlyk"}, diff --git a/i18n/Ukrainian.cpp b/i18n/Ukrainian.cpp index 413375ba..abbe8f81 100644 --- a/i18n/Ukrainian.cpp +++ b/i18n/Ukrainian.cpp @@ -168,12 +168,11 @@ namespace ukrainian // language namespace {"addresshelper is not supported", "addresshelper не підтримуєтьÑÑ"}, {"Host", "ÐдреÑа"}, {"added to router's addressbook from helper", "доданий в адреÑну книгу маршрутизатора через хелпер"}, - {"Click", "ÐатиÑніть"}, - {"here", "тут"}, - {"to proceed", "щоб продовжити"}, + {"Click here to proceed:", "ÐатиÑніть тут щоб продовжити:"}, + {"Continue", "Продовжити"}, {"Addresshelper found", "Знайдено addresshelper"}, {"already in router's addressbook", "вже в адреÑній книзі маршрутизатора"}, - {"to update record", "щоб оновити запиÑ"}, + {"Click here to update record:", "ÐатиÑніть тут щоб оновити запиÑ:"}, {"invalid request uri", "некоректний URI запиту"}, {"Can't detect destination host from request", "Ðе вдалоÑÑŒ визначити адреÑу Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð· запиту"}, {"Outproxy failure", "Помилка зовнішнього прокÑÑ–"}, diff --git a/i18n/Uzbek.cpp b/i18n/Uzbek.cpp new file mode 100644 index 00000000..72734b58 --- /dev/null +++ b/i18n/Uzbek.cpp @@ -0,0 +1,206 @@ +/* +* Copyright (c) 2021, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "I18N.h" + +// Ukrainian localization file + +namespace i2p +{ +namespace i18n +{ +namespace uzbek // language namespace +{ + // language name in lowercase + static std::string language = "uzbek"; + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + static int plural (int n) { + return n > 1 ? 1 : 0; + } + + static std::map strings + { + {"KiB", "KiB"}, + {"MiB", "MiB"}, + {"GiB", "GiB"}, + {"building", "qurilish"}, + {"failed", "muvaffaqiyatsiz"}, + {"expiring", "muddati tugaydi"}, + {"established", "aloqa o'rnatildi"}, + {"unknown", "noma'lum"}, + {"exploratory", "tadqiqiy"}, + {"i2pd webconsole", "i2pd veb -konsoli"}, + {"Main page", "Asosiy sahifa"}, + {"Router commands", "Router buyruqlari"}, + {"LeaseSets", "LeaseSets"}, + {"Tunnels", "Tunnellar"}, + {"Transit Tunnels", "Tranzit Tunellar"}, + {"Transports", "Transportlar"}, + {"I2P tunnels", "I2P tunnellar"}, + {"SAM sessions", "SAM sessiyalari"}, + {"ERROR", "XATO"}, + {"OK", "OK"}, + {"Testing", "Testlash"}, + {"Firewalled", "Xavfsizlik devori bilan himoyalangan"}, + {"Unknown", "Notanish"}, + {"Proxy", "Proksi"}, + {"Mesh", "Mesh To'r"}, + {"Error", "Xato"}, + {"Clock skew", "Aniq vaqt emas"}, + {"Offline", "Oflayn"}, + {"Symmetric NAT", "Simmetrik NAT"}, + {"Uptime", "Ish vaqti"}, + {"Network status", "Tarmoq holati"}, + {"Network status v6", "Tarmoq holati v6"}, + {"Stopping in", "Ichida to'xtatish"}, + {"Family", "Oila"}, + {"Tunnel creation success rate", "Tunnel yaratish muvaffaqiyat darajasi"}, + {"Received", "Qabul qilindi"}, + {"KiB/s", "KiB/s"}, + {"Sent", "Yuborilgan"}, + {"Transit", "Tranzit"}, + {"Data path", "Ma'lumotlar yo'li"}, + {"Hidden content. Press on text to see.", "Yashirin tarkib. Ko'rish uchun matn ustida bosing."}, + {"Router Ident", "Router identifikatori"}, + {"Router Family", "Router Oila"}, + {"Router Caps", "Router bayroqlari"}, + {"Version", "Versiya"}, + {"Our external address", "Bizning tashqi manzilimiz"}, + {"supported", "qo'llab -quvvatlanadi"}, + {"Routers", "Routerlar"}, + {"Floodfills", "Floodfills"}, + {"Client Tunnels", "Mijoz tunellari"}, + {"Services", "Xizmatlar"}, + {"Enabled", "Yoqilgan"}, + {"Disabled", "O'chirilgan"}, + {"Encrypted B33 address", "Shifrlangan B33 manzil"}, + {"Address registration line", "Manzilni ro'yxatga olish liniyasi"}, + {"Domain", "Domen"}, + {"Generate", "Varatish"}, + {"Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.", "Eslatma: natija satridan faqat 2LD domenlarini ro'yxatdan o'tkazish uchun foydalanish mumkin (example.i2p). Subdomenlarni ro'yxatdan o'tkazish uchun i2pd-tools dan foydalaning."}, + {"Address", "Manzil"}, + {"Type", "Turi"}, + {"EncType", "ShifrlashTuri"}, + {"Inbound tunnels", "Kirish tunnellari"}, + {"ms", "ms"}, + {"Outbound tunnels", "Chiquvchi tunnellar"}, + {"Tags", "Teglar"}, + {"Incoming", "Kiruvchi"}, + {"Outgoing", "Chiquvchi"}, + {"Destination", "Manzilgoh"}, + {"Amount", "Yig'indi"}, + {"Incoming Tags", "Kiruvchi teglar"}, + {"Tags sessions", "Teglar sessiyalari"}, + {"Status", "Holat"}, + {"Streams", "Strim"}, + {"Close stream", "Strimni o'chirish"}, + {"I2CP session not found", "I2CP sessiyasi topilmadi"}, + {"I2CP is not enabled", "I2CP yoqilmagan"}, + {"Invalid", "Noto'g'ri"}, + {"Store type", "Saqlash turi"}, + {"Expires", "Muddati tugaydi"}, + {"Non Expired Leases", "Muddati O'tmagan Leases"}, + {"Gateway", "Kirish yo'li"}, + {"TunnelID", "TunnelID"}, + {"EndDate", "Tugash Sanasi"}, + {"not floodfill", "floodfill emas"}, + {"Queue size", "Navbat hajmi"}, + {"Run peer test", "Sinovni boshlang"}, + {"Decline transit tunnels", "Tranzit tunnellarni rad etish"}, + {"Accept transit tunnels", "Tranzit tunnellarni qabul qilish"}, + {"Cancel graceful shutdown", "Yumshoq to'xtashni bekor qiling"}, + {"Start graceful shutdown", "Yumshoq to'xtashni boshlang"}, + {"Force shutdown", "Bizning tashqi manzilimiz"}, + {"Reload external CSS styles", "Tashqi CSS uslublarini qayta yuklang"}, + {"Note: any action done here are not persistent and not changes your config files.", "Eslatma: bu erda qilingan har qanday harakat doimiy emas va konfiguratsiya fayllarini o'zgartirmaydi."}, + {"Transit tunnels limit", "Tranzit tunellar chegarasi"}, + {"Change", "O'zgartirish"}, + {"Change language", "Tilni o'zgartirish"}, + {"no transit tunnels currently built", "qurilgan tranzit tunnellari yo'q"}, + {"SAM disabled", "SAM o'chirilgan"}, + {"no sessions currently running", "hech qanday ishlaydigan sessiyalar yo'q"}, + {"SAM session not found", "SAM sessiyasi topilmadi"}, + {"SAM Session", "SAM sessiyasi"}, + {"Server Tunnels", "Server Tunellari"}, + {"Client Forwards", "Mijozlarni Yo'naltirish"}, + {"Server Forwards", "Serverni Yo'naltirish"}, + {"Unknown page", "Noma'lum sahifa"}, + {"Invalid token", "Noto‘g‘ri belgi"}, + {"SUCCESS", "Muvaffaqiyat"}, + {"Stream closed", "Strim yopiq"}, + {"Stream not found or already was closed", "Strim topilmadi yoki allaqachon yopilgan"}, + {"Destination not found", "Yo'nalish topilmadi"}, + {"StreamID can't be null", "StreamID bo'sh bo'lishi mumkin emas"}, + {"Return to destination page", "Belgilangan sahifaga qaytish"}, + {"You will be redirected in 5 seconds", "Siz 5 soniyada qayta yo'naltirilasiz"}, + {"Transit tunnels count must not exceed 65535", "Tranzit tunnellar soni 65535 dan oshmasligi kerak"}, + {"Back to commands list", "Buyruqlar ro'yxatiga qaytish"}, + {"Register at reg.i2p", "Reg.i2p-da ro'yxatdan o'ting"}, + {"Description", "Tavsif"}, + {"A bit information about service on domain", "Domen xizmatlari haqida bir oz ma'lumot"}, + {"Submit", "Yuborish"}, + {"Domain can't end with .b32.i2p", "Domen .b32.i2p bilan tugashi mumkin emas"}, + {"Domain must end with .i2p", "Domen .i2p bilan tugashi kerak"}, + {"Such destination is not found", "Bunday yo'nalish topilmadi"}, + {"Unknown command", "Noma'lum buyruq"}, + {"Command accepted", "Buyruq qabul qilindi"}, + {"Proxy error", "Proksi xatosi"}, + {"Proxy info", "Proksi ma'lumotlari"}, + {"Proxy error: Host not found", "Proksi xatosi: Xost topilmadi"}, + {"Remote host not found in router's addressbook", "Masofaviy xost yo'riqnoma manzillar kitobida topilmadi"}, + {"Invalid request", "Noto‘g‘ri so‘rov"}, + {"Proxy unable to parse your request", "Proksi sizning so'rovingizni tahlil qila olmaydi"}, + {"addresshelper is not supported", "addresshelper qo'llab -quvvatlanmaydi"}, + {"Host", "Xost"}, + {"Addresshelper found", "Addresshelper topildi"}, + {"invalid request uri", "noto'g'ri URI so'rovi"}, + {"Can't detect destination host from request", "So‘rov orqali manzil xostini aniqlab bo'lmayapti"}, + {"Outproxy failure", "Tashqi proksi muvaffaqiyatsizligi"}, + {"bad outproxy settings", "noto'g'ri tashqi proksi -server sozlamalari"}, + {"not inside I2P network, but outproxy is not enabled", "I2P tarmog'ida emas, lekin tashqi proksi yoqilmagan"}, + {"unknown outproxy url", "noma'lum outproxy url"}, + {"cannot resolve upstream proxy", "yuqoridagi proksi -serverni aniqlab olib bolmaydi"}, + {"hostname too long", "xost nomi juda uzun"}, + {"cannot connect to upstream socks proxy", "yuqori soks proksi -serveriga ulanib bo'lmaydi"}, + {"Cannot negotiate with socks proxy", "Soks proksi bilan muzokara olib bo'lmaydi"}, + {"CONNECT error", "CONNECT xatosi"}, + {"Failed to Connect", "Ulanmadi"}, + {"socks proxy error", "soks proksi xatosi"}, + {"failed to send request to upstream", "yuqori http proksi-serveriga ulanib bo'lmadi"}, + {"No Reply From socks proxy", "Soks-proksidan javob yo'q"}, + {"cannot connect", "ulab bo'lmaydi"}, + {"http out proxy not implemented", "tashqi HTTP proksi -serverni qo'llab -quvvatlash amalga oshirilmagan"}, + {"cannot connect to upstream http proxy", "yuqori http proksi-serveriga ulanib bo'lmadi"}, + {"Host is down", "Xost ishlamayapti"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Talab qilingan xost bilan aloqa o'rnatilmadi, u ishlamay qolishi mumkin. Iltimos keyinroq qayta urinib ko'ring."}, + {"", ""}, + }; + + static std::map> plurals + { + {"days", {"kun", "kunlar"}}, + {"hours", {"soat", "soat"}}, + {"minutes", {"daqiqa", "daqiqalar"}}, + {"seconds", {"soniya", "soniyalar"}}, + {"", {"", ""}}, + }; + + std::shared_ptr GetLocale() + { + return std::make_shared(language, strings, plurals, [] (int n)->int { return plural(n); }); + } + +} // language +} // i18n +} // i2p From 97765ef895c7fe4187bfea60cf0103ba8a2efcbb Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 18 Aug 2021 23:04:52 +0300 Subject: [PATCH 4393/6300] [i18n] add namespace Signed-off-by: R4SAS --- i18n/I18N_langs.h | 1 + 1 file changed, 1 insertion(+) diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h index 957d550d..559b17be 100644 --- a/i18n/I18N_langs.h +++ b/i18n/I18N_langs.h @@ -76,6 +76,7 @@ namespace i18n namespace russian { std::shared_ptr GetLocale (); } namespace turkmen { std::shared_ptr GetLocale (); } namespace ukrainian { std::shared_ptr GetLocale (); } + namespace uzbek { std::shared_ptr GetLocale (); } /** * That map contains international language name lower-case and name in it's language From b830babcf41e6bb8b3be3f60f3ed9cdf830012af Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 22 Aug 2021 22:32:03 +0300 Subject: [PATCH 4394/6300] [rpm] try fix build on fedora rawhide Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 1061fc8f..46ed8a51 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -56,33 +56,38 @@ cd build %endif %endif -%if 0%{?fedora} >= 33 -pushd %{_target_platform} + +%if 0%{?fedora} >= 36 + pushd redhat-linux-build +%else + %if 0%{?fedora} >= 33 + pushd %{_target_platform} + %endif %endif %if 0%{?mageia} > 7 -pushd build + pushd build %endif make %{?_smp_mflags} %if 0%{?fedora} >= 33 -popd + popd %endif %if 0%{?mageia} > 7 -popd + popd %endif %install pushd build %if 0%{?fedora} >= 33 -pushd %{_target_platform} + pushd %{_target_platform} %endif %if 0%{?mageia} -pushd build + pushd build %endif chrpath -d i2pd From 33355c0abe752095742190019ba8b4c6255bfe19 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 22 Aug 2021 22:44:04 +0300 Subject: [PATCH 4395/6300] [rpm] try fix build on fedora rawhide Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 12 ++++++++---- contrib/rpm/i2pd.spec | 28 ++++++++++++++++++---------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 46ed8a51..c99c2c8b 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -11,9 +11,9 @@ URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/openssl/i2pd-openssl.tar.gz %if 0%{?rhel} == 7 -BuildRequires: cmake3 + BuildRequires: cmake3 %else -BuildRequires: cmake + BuildRequires: cmake %endif BuildRequires: chrpath @@ -82,8 +82,12 @@ make %{?_smp_mflags} %install pushd build -%if 0%{?fedora} >= 33 - pushd %{_target_platform} +%if 0%{?fedora} >= 36 + pushd redhat-linux-build +%else + %if 0%{?fedora} >= 33 + pushd %{_target_platform} + %endif %endif %if 0%{?mageia} diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index a2994f28..1339fe90 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -9,9 +9,9 @@ URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz %if 0%{?rhel} == 7 -BuildRequires: cmake3 + BuildRequires: cmake3 %else -BuildRequires: cmake + BuildRequires: cmake %endif BuildRequires: chrpath @@ -54,33 +54,41 @@ cd build %endif %endif -%if 0%{?fedora} >= 33 -pushd %{_target_platform} +%if 0%{?fedora} >= 36 + pushd redhat-linux-build +%else + %if 0%{?fedora} >= 33 + pushd %{_target_platform} + %endif %endif %if 0%{?mageia} > 7 -pushd build + pushd build %endif make %{?_smp_mflags} %if 0%{?fedora} >= 33 -popd + popd %endif %if 0%{?mageia} > 7 -popd + popd %endif %install pushd build -%if 0%{?fedora} >= 33 -pushd %{_target_platform} +%if 0%{?fedora} >= 36 + pushd redhat-linux-build +%else + %if 0%{?fedora} >= 33 + pushd %{_target_platform} + %endif %endif %if 0%{?mageia} -pushd build + pushd build %endif chrpath -d i2pd From 8abd08bd1b3bc845d4c7b33a564bee79060ae360 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 22 Aug 2021 15:58:46 -0400 Subject: [PATCH 4396/6300] change log for 2.39.0 --- ChangeLog | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 9a79440e..d12bdd22 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,45 @@ # for this file format description, # see https://github.com/olivierlacan/keep-a-changelog -## [2.38.0] - 2021-03-17 +## [2.39.0] - 2021-08-23 +### Added +- Short tunnel build messages +- Localization. To: Russian, Ukrainian, Turkmen, Uzbek and Afrikaans +- Custom CSS styles for webconsole +- Avoid slow tunnels with more than 250 ms per hop +- Process DELAY_REQUESTED streaming option +- "certsdir" options for certificates location +- Keep own RouterInfo in NetBb +- Pick ECIES routers only for tunneln on non-x64 +- NTP sync through ipv6 +- Allow ipv6 adresses for UDP server tunnels +### Changed +- Rekey of all routers to ECIES +- Better distribution for random tunnel's peer selection +- Yggdrasil reseed for v0.4, added two more +- Encyption type 0,4 by default for server tunnels +- Handle i2cp.dontPublishLeaseSet param for all destinations +- reg.i2p for subscriptions +- LeaseSet type 3 by default +- Don't allocate payload buffer for every single ECIESx25519 message +- Prefer public ipv6 instead rfc4941 +- Optimal padding for one-time ECIESx25519 message +- Don't send datetime block for one-time ECIESx25519 message with one-time key +- Router with expired introducer is still valid +- Don't disable floodfill if still reachable by ipv6 +- Set minimal version for floodfill to 0.9.38 +- Eliminate extra lookups for sequential fragments on tunnel endpoint +- Consistent path for explicit peers +### Fixed +- Zero-hop tunnels +- Crash upon SAM session termination +- Build with boost < 1.55.0 +- Address type for NTCP2 acceptors +- Check of ipv4/ipv6 address +- Requst router to send to if not in NetDb +- Count outbound traffic for zero-hop tunnels + +## [2.38.0] - 2021-05-17 ### Added - Publish ipv6 introducers - Bind ipv6 or yggdrasil NTCP2 acceptor to specified address From c93ab8f829a1f2afb6d6b7d2855a0927df0201fe Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 22 Aug 2021 23:36:08 +0300 Subject: [PATCH 4397/6300] update changelog, i2pd.conf Signed-off-by: R4SAS --- ChangeLog | 16 ++++++++++------ contrib/i2pd.conf | 3 +-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index d12bdd22..2128f5d2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,14 +10,14 @@ - Process DELAY_REQUESTED streaming option - "certsdir" options for certificates location - Keep own RouterInfo in NetBb -- Pick ECIES routers only for tunneln on non-x64 +- Pick ECIES routers only for tunnels on non-x64 - NTP sync through ipv6 -- Allow ipv6 adresses for UDP server tunnels +- Allow ipv6 addresses for UDP server tunnels ### Changed - Rekey of all routers to ECIES - Better distribution for random tunnel's peer selection - Yggdrasil reseed for v0.4, added two more -- Encyption type 0,4 by default for server tunnels +- Encryption type 0,4 by default for server tunnels - Handle i2cp.dontPublishLeaseSet param for all destinations - reg.i2p for subscriptions - LeaseSet type 3 by default @@ -30,14 +30,18 @@ - Set minimal version for floodfill to 0.9.38 - Eliminate extra lookups for sequential fragments on tunnel endpoint - Consistent path for explicit peers +- Always create new tunnel from exploratory pool +- Don't try to connect to a router not reachable from us +- Mark additional ipv6 addresses/nets as reserved (#1679) ### Fixed - Zero-hop tunnels - Crash upon SAM session termination -- Build with boost < 1.55.0 +- Build with boost < 1.55.0 - Address type for NTCP2 acceptors - Check of ipv4/ipv6 address -- Requst router to send to if not in NetDb -- Count outbound traffic for zero-hop tunnels +- Request router to send to if not in NetDb +- Count outbound traffic for zero-hop tunnels +- URLdecode domain for registration string generator in webconsole ## [2.38.0] - 2021-05-17 ### Added diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 599a2081..25402629 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -108,8 +108,7 @@ port = 7070 # user = i2pd # pass = changeme ## Select webconsole language -## Currently supported english (default), afrikaans, russian, turkmen and ukrainian languages - +## Currently supported english (default), afrikaans, russian, turkmen ukrainian and uzbek languages # lang = english [httpproxy] From 2bdfcedd0e7acf9616b7204896fa8497043b8b07 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 22 Aug 2021 23:37:56 +0300 Subject: [PATCH 4398/6300] [docs] add comma to description Signed-off-by: R4SAS --- contrib/i2pd.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index 25402629..dbf74002 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -108,7 +108,7 @@ port = 7070 # user = i2pd # pass = changeme ## Select webconsole language -## Currently supported english (default), afrikaans, russian, turkmen ukrainian and uzbek languages +## Currently supported english (default), afrikaans, russian, turkmen, ukrainian and uzbek languages # lang = english [httpproxy] From 6ba992dabd22520dd2a9b99674dbd9c0fe796a9f Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 22 Aug 2021 23:41:36 +0300 Subject: [PATCH 4399/6300] [rpm] try fix build on fedora rawhide [try 3] Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 28 ++++++++++++++-------------- contrib/rpm/i2pd.spec | 28 ++++++++++++++-------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index c99c2c8b..c9a848ee 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -11,9 +11,9 @@ URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/openssl/i2pd-openssl.tar.gz %if 0%{?rhel} == 7 - BuildRequires: cmake3 +BuildRequires: cmake3 %else - BuildRequires: cmake +BuildRequires: cmake %endif BuildRequires: chrpath @@ -58,40 +58,40 @@ cd build %if 0%{?fedora} >= 36 - pushd redhat-linux-build +pushd redhat-linux-build %else - %if 0%{?fedora} >= 33 - pushd %{_target_platform} - %endif +%if 0%{?fedora} >= 33 +pushd %{_target_platform} +%endif %endif %if 0%{?mageia} > 7 - pushd build +pushd build %endif make %{?_smp_mflags} %if 0%{?fedora} >= 33 - popd +popd %endif %if 0%{?mageia} > 7 - popd +popd %endif %install pushd build %if 0%{?fedora} >= 36 - pushd redhat-linux-build +pushd redhat-linux-build %else - %if 0%{?fedora} >= 33 - pushd %{_target_platform} - %endif +%if 0%{?fedora} >= 33 +pushd %{_target_platform} +%endif %endif %if 0%{?mageia} - pushd build +pushd build %endif chrpath -d i2pd diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 1339fe90..218a6bf7 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -9,9 +9,9 @@ URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz %if 0%{?rhel} == 7 - BuildRequires: cmake3 +BuildRequires: cmake3 %else - BuildRequires: cmake +BuildRequires: cmake %endif BuildRequires: chrpath @@ -55,40 +55,40 @@ cd build %endif %if 0%{?fedora} >= 36 - pushd redhat-linux-build +pushd redhat-linux-build %else - %if 0%{?fedora} >= 33 - pushd %{_target_platform} - %endif +%if 0%{?fedora} >= 33 +pushd %{_target_platform} +%endif %endif %if 0%{?mageia} > 7 - pushd build +pushd build %endif make %{?_smp_mflags} %if 0%{?fedora} >= 33 - popd +popd %endif %if 0%{?mageia} > 7 - popd +popd %endif %install pushd build %if 0%{?fedora} >= 36 - pushd redhat-linux-build +pushd redhat-linux-build %else - %if 0%{?fedora} >= 33 - pushd %{_target_platform} - %endif +%if 0%{?fedora} >= 33 +pushd %{_target_platform} +%endif %endif %if 0%{?mageia} - pushd build +pushd build %endif chrpath -d i2pd From 96850da31e2ddde169b53b81c98c70d2849ceffd Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 23 Aug 2021 06:58:36 -0400 Subject: [PATCH 4400/6300] 2.39.0 --- contrib/rpm/i2pd-git.spec | 5 ++++- contrib/rpm/i2pd.spec | 5 ++++- debian/changelog | 6 ++++++ libi2pd/version.h | 6 +++--- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index c9a848ee..880d937e 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -1,7 +1,7 @@ %define git_hash %(git rev-parse HEAD | cut -c -7) Name: i2pd-git -Version: 2.38.0 +Version: 2.39.0 Release: git%{git_hash}%{?dist} Summary: I2P router written in C++ Conflicts: i2pd @@ -146,6 +146,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Aug 23 2021 orignal - 2.39.0 +- update to 2.39.0 + * Mon May 17 2021 orignal - 2.38.0 - update to 2.38.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 218a6bf7..5bdc5231 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,5 +1,5 @@ Name: i2pd -Version: 2.38.0 +Version: 2.39.0 Release: 1%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -143,6 +143,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Aug 23 2021 orignal - 2.39.0 +- update to 2.39.0 + * Mon May 17 2021 orignal - 2.38.0 - update to 2.38.0 diff --git a/debian/changelog b/debian/changelog index 777b6216..c675f263 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i2pd (2.39.0-1) unstable; urgency=medium + + * updated to version 2.39.0/0.9.51 + + -- orignal Mon, 23 Aug 2021 16:00:00 +0000 + i2pd (2.38.0-1) unstable; urgency=medium * updated to version 2.38.0/0.9.50 diff --git a/libi2pd/version.h b/libi2pd/version.h index 028d1692..97351c40 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -16,7 +16,7 @@ #define MAKE_VERSION_NUMBER(a,b,c) ((a*100+b)*100+c) #define I2PD_VERSION_MAJOR 2 -#define I2PD_VERSION_MINOR 38 +#define I2PD_VERSION_MINOR 39 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) @@ -30,7 +30,7 @@ #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 -#define I2P_VERSION_MICRO 50 +#define I2P_VERSION_MICRO 51 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #define I2P_VERSION_NUMBER MAKE_VERSION_NUMBER(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) From 455c71ff2557dde7104744cb0e8822ba8177440e Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 23 Aug 2021 17:00:46 +0300 Subject: [PATCH 4401/6300] fix warning about ifr_name size Signed-off-by: R4SAS --- libi2pd/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index 2d5617b6..f7b376f6 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -344,7 +344,7 @@ namespace net if(fd > 0) { ifreq ifr; - strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query + strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ-1); // set interface for query if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) mtu = ifr.ifr_mtu; // MTU else From 24eeadea765931d46697b3e44746f24eb65d1c53 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 23 Aug 2021 17:03:26 +0300 Subject: [PATCH 4402/6300] [rpm] add changelog note Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 1 + contrib/rpm/i2pd.spec | 1 + 2 files changed, 2 insertions(+) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 880d937e..0643ea8b 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -148,6 +148,7 @@ getent passwd i2pd >/dev/null || \ %changelog * Mon Aug 23 2021 orignal - 2.39.0 - update to 2.39.0 +- fixed build on fedora 36 * Mon May 17 2021 orignal - 2.38.0 - update to 2.38.0 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 5bdc5231..844901c9 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -145,6 +145,7 @@ getent passwd i2pd >/dev/null || \ %changelog * Mon Aug 23 2021 orignal - 2.39.0 - update to 2.39.0 +- fixed build on fedora 36 * Mon May 17 2021 orignal - 2.38.0 - update to 2.38.0 From f0c49b58fbffd2afcb56a39e2294105298866275 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 23 Aug 2021 19:29:55 +0300 Subject: [PATCH 4403/6300] suppress inconsistent-missing-override warning message Signed-off-by: R4SAS --- libi2pd/TunnelConfig.h | 54 +++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 76aa0b34..875c20bb 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,14 +31,14 @@ namespace tunnel TunnelHopConfig * next, * prev; int recordIndex; // record # in tunnel build message - + TunnelHopConfig (std::shared_ptr r); virtual ~TunnelHopConfig () {}; - + void SetNextIdent (const i2p::data::IdentHash& ident); void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent); void SetNext (TunnelHopConfig * n); - void SetPrev (TunnelHopConfig * p); + void SetPrev (TunnelHopConfig * p); virtual uint8_t GetRetCode (const uint8_t * records) const = 0; virtual void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) = 0; @@ -51,47 +51,47 @@ namespace tunnel { ElGamalTunnelHopConfig (std::shared_ptr r): TunnelHopConfig (r) {}; - uint8_t GetRetCode (const uint8_t * records) const - { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[BUILD_RESPONSE_RECORD_RET_OFFSET]; }; - void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); - bool DecryptBuildResponseRecord (uint8_t * records) const; - }; + uint8_t GetRetCode (const uint8_t * records) const + { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[BUILD_RESPONSE_RECORD_RET_OFFSET]; }; + void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); + bool DecryptBuildResponseRecord (uint8_t * records) const; + }; struct ECIESTunnelHopConfig: public TunnelHopConfig, public i2p::crypto::NoiseSymmetricState { ECIESTunnelHopConfig (std::shared_ptr r): TunnelHopConfig (r) {}; - void EncryptECIES (const uint8_t * clearText, size_t len, uint8_t * encrypted); + void EncryptECIES (const uint8_t * clearText, size_t len, uint8_t * encrypted); bool DecryptECIES (const uint8_t * key, const uint8_t * nonce, const uint8_t * encrypted, size_t len, uint8_t * clearText) const; }; - + struct LongECIESTunnelHopConfig: public ECIESTunnelHopConfig { LongECIESTunnelHopConfig (std::shared_ptr r): ECIESTunnelHopConfig (r) {}; - uint8_t GetRetCode (const uint8_t * records) const - { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; }; - void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); - bool DecryptBuildResponseRecord (uint8_t * records) const; - }; + uint8_t GetRetCode (const uint8_t * records) const override + { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET]; }; + void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) override; + bool DecryptBuildResponseRecord (uint8_t * records) const override; + }; struct ShortECIESTunnelHopConfig: public ECIESTunnelHopConfig { ShortECIESTunnelHopConfig (std::shared_ptr r): ECIESTunnelHopConfig (r) {}; - uint8_t GetRetCode (const uint8_t * records) const - { return (records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE)[SHORT_RESPONSE_RECORD_RET_OFFSET]; }; - void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); - bool DecryptBuildResponseRecord (uint8_t * records) const; + uint8_t GetRetCode (const uint8_t * records) const override + { return (records + recordIndex*SHORT_TUNNEL_BUILD_RECORD_SIZE)[SHORT_RESPONSE_RECORD_RET_OFFSET]; }; + void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) override; + bool DecryptBuildResponseRecord (uint8_t * records) const override; void DecryptRecord (uint8_t * records, int index) const override; // Chacha20 - uint64_t GetGarlicKey (uint8_t * key) const override; - }; - + uint64_t GetGarlicKey (uint8_t * key) const override; + }; + class TunnelConfig { public: - TunnelConfig (const std::vector >& peers, + TunnelConfig (const std::vector >& peers, bool isShort = false): // inbound m_IsShort (isShort) { @@ -121,7 +121,7 @@ namespace tunnel } bool IsShort () const { return m_IsShort; } - + TunnelHopConfig * GetFirstHop () const { return m_FirstHop; @@ -203,12 +203,12 @@ namespace tunnel if (m_IsShort) hop = new ShortECIESTunnelHopConfig (it); else - { + { if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) hop = new LongECIESTunnelHopConfig (it); else hop = new ElGamalTunnelHopConfig (it); - } + } if (prev) prev->SetNext (hop); else From 7d220fb2ebc1213030defa79cf265d56c7a9621a Mon Sep 17 00:00:00 2001 From: Daniel Bermond Date: Mon, 23 Aug 2021 17:22:28 -0300 Subject: [PATCH 4404/6300] [tests] fix compilation of test-blinding test-blinding currently fails to build with the following error: In file included from ../libi2pd/Timestamp.cpp:19: ../libi2pd/RouterContext.h:21:10: fatal error: I18N_langs.h: No such file or directory 21 | #include "I18N_langs.h" | ^~~~~~~~~~~~~~ compilation terminated. --- tests/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Makefile b/tests/Makefile index 4c80c37c..2cb5348f 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,4 +1,5 @@ CXXFLAGS += -Wall -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -I../libi2pd/ -pthread -Wl,--unresolved-symbols=ignore-in-object-files +INCFLAGS += -I ../i18n TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding test-elligator From af2c6c5575d28fa320f3b048b9d791d98fe77761 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 24 Aug 2021 03:16:28 +0300 Subject: [PATCH 4405/6300] [rpm] change if statement to cover fedora 35 Signed-off-by: R4SAS --- contrib/rpm/i2pd-git.spec | 7 +++++-- contrib/rpm/i2pd.spec | 9 ++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec index 0643ea8b..b1354173 100644 --- a/contrib/rpm/i2pd-git.spec +++ b/contrib/rpm/i2pd-git.spec @@ -57,7 +57,7 @@ cd build %endif -%if 0%{?fedora} >= 36 +%if 0%{?fedora} >= 35 pushd redhat-linux-build %else %if 0%{?fedora} >= 33 @@ -82,7 +82,7 @@ popd %install pushd build -%if 0%{?fedora} >= 36 +%if 0%{?fedora} >= 35 pushd redhat-linux-build %else %if 0%{?fedora} >= 33 @@ -146,6 +146,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Aug 24 2021 r4sas - 2.39.0-2 +- changed if statements to cover fedora 35 + * Mon Aug 23 2021 orignal - 2.39.0 - update to 2.39.0 - fixed build on fedora 36 diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec index 844901c9..ef8e32f2 100644 --- a/contrib/rpm/i2pd.spec +++ b/contrib/rpm/i2pd.spec @@ -1,6 +1,6 @@ Name: i2pd Version: 2.39.0 -Release: 1%{?dist} +Release: 2%{?dist} Summary: I2P router written in C++ Conflicts: i2pd-git @@ -54,7 +54,7 @@ cd build %endif %endif -%if 0%{?fedora} >= 36 +%if 0%{?fedora} >= 35 pushd redhat-linux-build %else %if 0%{?fedora} >= 33 @@ -79,7 +79,7 @@ popd %install pushd build -%if 0%{?fedora} >= 36 +%if 0%{?fedora} >= 35 pushd redhat-linux-build %else %if 0%{?fedora} >= 33 @@ -143,6 +143,9 @@ getent passwd i2pd >/dev/null || \ %changelog +* Mon Aug 24 2021 r4sas - 2.39.0-2 +- changed if statements to cover fedora 35 + * Mon Aug 23 2021 orignal - 2.39.0 - update to 2.39.0 - fixed build on fedora 36 From ec98ff297c2d8050e3a525abe1b4203a3c97a0db Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 24 Aug 2021 13:23:10 +0300 Subject: [PATCH 4406/6300] Make blinding test runnable --- tests/Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/Makefile b/tests/Makefile index 2cb5348f..89fceeec 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,5 +1,7 @@ -CXXFLAGS += -Wall -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -I../libi2pd/ -pthread -Wl,--unresolved-symbols=ignore-in-object-files -INCFLAGS += -I ../i18n +CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -pthread -Wl,--unresolved-symbols=ignore-in-object-files +INCFLAGS += -I../libi2pd -I../i18n + +LOCALESRC = ../i18n/Afrikaans.cpp ../i18n/English.cpp ../i18n/Russian.cpp ../i18n/Turkmen.cpp ../i18n/Ukrainian.cpp ../i18n/Uzbek.cpp TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding test-elligator @@ -23,7 +25,7 @@ test-x25519: ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp test-aeadchacha20poly1305: ../libi2pd/Crypto.cpp ../libi2pd/ChaCha20.cpp ../libi2pd/Poly1305.cpp test-aeadchacha20poly1305.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system -test-blinding: ../libi2pd/Crypto.cpp ../libi2pd/Blinding.cpp ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/util.cpp ../libi2pd/Identity.cpp ../libi2pd/Signature.cpp ../libi2pd/Timestamp.cpp test-blinding.cpp +test-blinding: ../libi2pd/Crypto.cpp ../libi2pd/Blinding.cpp ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/util.cpp ../libi2pd/Identity.cpp ../libi2pd/Signature.cpp ../libi2pd/Timestamp.cpp $(LOCALESRC) test-blinding.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system test-elligator: ../libi2pd/Elligator.cpp ../libi2pd/Crypto.cpp test-elligator.cpp From 541464b7057095a220db0d094fdaef16d8191fd5 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 26 Aug 2021 15:13:58 -0400 Subject: [PATCH 4407/6300] don't delete floodfill if number of remaining floodfills is less than minimal --- libi2pd/NetDb.cpp | 9 ++++++--- libi2pd/NetDb.hpp | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 4bc144e4..904782cf 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -577,8 +577,9 @@ namespace data void NetDb::SaveUpdated () { - int updatedCount = 0, deletedCount = 0; + int updatedCount = 0, deletedCount = 0, deletedFloodfillsCount = 0; auto total = m_RouterInfos.size (); + auto totalFloodfills = m_Floodfills.size (); uint64_t expirationTimeout = NETDB_MAX_EXPIRATION_TIMEOUT*1000LL; uint64_t ts = i2p::util::GetMillisecondsSinceEpoch(); auto uptime = i2p::context.GetUptime (); @@ -603,8 +604,9 @@ namespace data updatedCount++; continue; } - // make router reachable back if too few routers - if (it.second->IsUnreachable () && total - deletedCount < NETDB_MIN_ROUTERS) + // make router reachable back if too few routers or floodfills + if (it.second->IsUnreachable () && (total - deletedCount < NETDB_MIN_ROUTERS || + (it.second->IsFloodfill () && totalFloodfills - deletedFloodfillsCount < NETDB_MIN_FLOODFILLS))) it.second->SetUnreachable (false); // find & mark expired routers if (!it.second->IsReachable () && it.second->IsSSU (false)) @@ -618,6 +620,7 @@ namespace data if (it.second->IsUnreachable ()) { + if (it.second->IsFloodfill ()) deletedFloodfillsCount++; // delete RI file m_Storage.Remove(ident); deletedCount++; diff --git a/libi2pd/NetDb.hpp b/libi2pd/NetDb.hpp index c52be02b..097f92e7 100644 --- a/libi2pd/NetDb.hpp +++ b/libi2pd/NetDb.hpp @@ -36,6 +36,7 @@ namespace i2p namespace data { const int NETDB_MIN_ROUTERS = 90; + const int NETDB_MIN_FLOODFILLS = 5; const int NETDB_FLOODFILL_EXPIRATION_TIMEOUT = 60 * 60; // 1 hour, in seconds const int NETDB_INTRODUCEE_EXPIRATION_TIMEOUT = 65 * 60; const int NETDB_MIN_EXPIRATION_TIMEOUT = 90 * 60; // 1.5 hours From c45e202fabbb7a0763428d81a06a6d681a22b020 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 29 Aug 2021 14:22:01 -0400 Subject: [PATCH 4408/6300] removed ElGamal encryption support for own router --- libi2pd/Crypto.cpp | 29 +++++++++-------------------- libi2pd/Crypto.h | 8 ++++---- libi2pd/CryptoKey.cpp | 16 ++++++++-------- libi2pd/CryptoKey.h | 30 +++++++++++++++--------------- libi2pd/Destination.cpp | 4 ++-- libi2pd/RouterContext.cpp | 13 +++++-------- libi2pd_client/I2CP.cpp | 6 +++--- 7 files changed, 46 insertions(+), 60 deletions(-) diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 14ef83ae..136a2072 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -451,15 +451,15 @@ namespace crypto BN_CTX_end (ctx); } - bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, - uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, + uint8_t * data, BN_CTX * ctx) { BN_CTX_start (ctx); BIGNUM * x = BN_CTX_get (ctx), * a = BN_CTX_get (ctx), * b = BN_CTX_get (ctx); BN_bin2bn (key, 256, x); BN_sub (x, elgp, x); BN_sub_word (x, 1); // x = elgp - x- 1 - BN_bin2bn (zeroPadding ? encrypted + 1 : encrypted, 256, a); - BN_bin2bn (zeroPadding ? encrypted + 258 : encrypted + 256, 256, b); + BN_bin2bn (encrypted + 1, 256, a); + BN_bin2bn (encrypted + 258, 256, b); // m = b*(a^x mod p) mod p BN_mod_exp (x, a, x, elgp, ctx); BN_mod_mul (b, b, x, elgp, ctx); @@ -552,7 +552,7 @@ namespace crypto BN_CTX_end (ctx); } - bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) { bool ret = true; BN_CTX_start (ctx); @@ -561,16 +561,8 @@ namespace crypto int len = BN_num_bytes (q); // point for shared secret BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx); - if (zeroPadding) - { - BN_bin2bn (encrypted + 1, len, x); - BN_bin2bn (encrypted + 1 + len, len, y); - } - else - { - BN_bin2bn (encrypted, len, x); - BN_bin2bn (encrypted + len, len, y); - } + BN_bin2bn (encrypted + 1, len, x); + BN_bin2bn (encrypted + 1 + len, len, y); auto p = EC_POINT_new (curve); if (EC_POINT_set_affine_coordinates_GFp (curve, p, x, y, nullptr)) { @@ -587,10 +579,7 @@ namespace crypto CBCDecryption decryption; decryption.SetKey (shared); decryption.SetIV (iv); - if (zeroPadding) - decryption.Decrypt (encrypted + 258, 256, m); - else - decryption.Decrypt (encrypted + 256, 256, m); + decryption.Decrypt (encrypted + 258, 256, m); // verify and copy uint8_t hash[32]; SHA256 (m + 33, 222, hash); diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index a6c64c70..56791ceb 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -108,13 +108,13 @@ namespace crypto }; // ElGamal - void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); - bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding = false); + void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx); // 514 bytes encrypted, 222 data void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub); // ECIES void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without - bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding = false); + bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx); // 514 bytes encrypted, 222 data void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub); // HMAC diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp index ad93d386..3b2105a4 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -31,10 +31,10 @@ namespace crypto memcpy (m_PrivateKey, priv, 256); } - bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) { if (!ctx) return false; - return ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx, zeroPadding); + return ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx); } ECIESP256Encryptor::ECIESP256Encryptor (const uint8_t * pub) @@ -72,10 +72,10 @@ namespace crypto if (m_PrivateKey) BN_free (m_PrivateKey); } - bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) { if (m_Curve && m_PrivateKey) - return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data, ctx, zeroPadding); + return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data, ctx); return false; } @@ -130,10 +130,10 @@ namespace crypto if (m_PrivateKey) BN_free (m_PrivateKey); } - bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) + bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) { if (m_PrivateKey) - return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data, ctx, zeroPadding); + return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data, ctx); return false; } @@ -171,7 +171,7 @@ namespace crypto m_StaticKeys.SetPrivateKey (priv, calculatePublic); } - bool ECIESX25519AEADRatchetDecryptor::Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding) + bool ECIESX25519AEADRatchetDecryptor::Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx) { return m_StaticKeys.Agree (epub, sharedSecret); } diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h index fb69558f..a5b96c47 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,7 +21,7 @@ namespace crypto public: virtual ~CryptoKeyEncryptor () {}; - virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) = 0; // 222 bytes data, 512/514 bytes encrypted + virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) = 0; }; class CryptoKeyDecryptor @@ -29,7 +29,7 @@ namespace crypto public: virtual ~CryptoKeyDecryptor () {}; - virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) = 0; // 512/514 bytes encrypted, 222 bytes data + virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) = 0; virtual size_t GetPublicKeyLen () const = 0; // we need it to set key in LS2 }; @@ -39,7 +39,7 @@ namespace crypto public: ElGamalEncryptor (const uint8_t * pub); - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding); + void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) override; // 222 bytes data, 512/514 bytes encrypted private: @@ -51,8 +51,8 @@ namespace crypto public: ElGamalDecryptor (const uint8_t * priv); - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); - size_t GetPublicKeyLen () const { return 256; }; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) override; // 514 bytes encrypted, 222 bytes data + size_t GetPublicKeyLen () const override { return 256; }; private: @@ -67,7 +67,7 @@ namespace crypto ECIESP256Encryptor (const uint8_t * pub); ~ECIESP256Encryptor (); - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding); + void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) override; private: @@ -82,8 +82,8 @@ namespace crypto ECIESP256Decryptor (const uint8_t * priv); ~ECIESP256Decryptor (); - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); - size_t GetPublicKeyLen () const { return 64; }; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) override; + size_t GetPublicKeyLen () const override { return 64; }; private: @@ -101,7 +101,7 @@ namespace crypto ECIESGOSTR3410Encryptor (const uint8_t * pub); ~ECIESGOSTR3410Encryptor (); - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding); + void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) override; private: @@ -115,8 +115,8 @@ namespace crypto ECIESGOSTR3410Decryptor (const uint8_t * priv); ~ECIESGOSTR3410Decryptor (); - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding); - size_t GetPublicKeyLen () const { return 64; }; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) override; + size_t GetPublicKeyLen () const override { return 64; }; private: @@ -133,7 +133,7 @@ namespace crypto ECIESX25519AEADRatchetEncryptor (const uint8_t * pub); ~ECIESX25519AEADRatchetEncryptor () {}; - void Encrypt (const uint8_t *, uint8_t * pub, BN_CTX *, bool); + void Encrypt (const uint8_t *, uint8_t * pub, BN_CTX *, bool) override; // copies m_PublicKey to pub private: @@ -147,9 +147,9 @@ namespace crypto ECIESX25519AEADRatchetDecryptor (const uint8_t * priv, bool calculatePublic = false); ~ECIESX25519AEADRatchetDecryptor () {}; - bool Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx, bool zeroPadding); + bool Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx) override; // agree with static and return in sharedSecret (32 bytes) - size_t GetPublicKeyLen () const { return 32; }; + size_t GetPublicKeyLen () const override { return 32; }; const uint8_t * GetPubicKey () const { return m_StaticKeys.GetPublicKey (); }; private: diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index bc6ca31e..f269c092 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1249,9 +1249,9 @@ namespace client { if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) if (m_ECIESx25519EncryptionKey && m_ECIESx25519EncryptionKey->decryptor) - return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data, ctx, true); + return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data, ctx); if (m_StandardEncryptionKey && m_StandardEncryptionKey->decryptor) - return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data, ctx, true); + return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data, ctx); else LogPrint (eLogError, "Destinations: decryptor is not set"); return false; diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index edabcdfa..92dc5aea 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -880,7 +880,7 @@ namespace i2p bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const { - return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, true) : false; + return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx) : false; } bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data) @@ -889,11 +889,8 @@ namespace i2p return DecryptECIESTunnelBuildRecord (encrypted, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE); else { - if (!m_TunnelDecryptor) return false; - BN_CTX * ctx = BN_CTX_new (); - bool success = m_TunnelDecryptor->Decrypt (encrypted, data, ctx, false); - BN_CTX_free (ctx); - return success; + LogPrint (eLogError, "Router: Non-ECIES router is not longer supported"); + return false; } } @@ -903,7 +900,7 @@ namespace i2p m_CurrentNoiseState = m_InitialNoiseState; m_CurrentNoiseState.MixHash (encrypted, 32); // h = SHA256(h || sepk) uint8_t sharedSecret[32]; - if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret, nullptr, false)) + if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret, nullptr)) { LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); return false; @@ -928,7 +925,7 @@ namespace i2p return DecryptECIESTunnelBuildRecord (encrypted, data, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE); else { - LogPrint (eLogWarning, "Router: Can't decrypt short request record on non-ECIES router"); + LogPrint (eLogError, "Router: Can't decrypt short request record on non-ECIES router"); return false; } } diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index 25a5504a..b6618ff9 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -55,9 +55,9 @@ namespace client bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const { if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && m_ECIESx25519Decryptor) - return m_ECIESx25519Decryptor->Decrypt (encrypted, data, ctx, true); + return m_ECIESx25519Decryptor->Decrypt (encrypted, data, ctx); if (m_Decryptor) - return m_Decryptor->Decrypt (encrypted, data, ctx, true); + return m_Decryptor->Decrypt (encrypted, data, ctx); else LogPrint (eLogError, "I2CP: decryptor is not set"); return false; From bb518d3d51bda9d965952d786558825fd877b27a Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 31 Aug 2021 18:51:40 -0400 Subject: [PATCH 4409/6300] don't pass BN_CTX to encrypt/decrypt functions --- libi2pd/Crypto.cpp | 17 +++++++++---- libi2pd/Crypto.h | 8 +++--- libi2pd/CryptoKey.cpp | 30 +++++++++++------------ libi2pd/CryptoKey.h | 20 +++++++-------- libi2pd/Destination.cpp | 8 +++--- libi2pd/Destination.h | 4 +-- libi2pd/ECIESX25519AEADRatchetSession.cpp | 10 ++++---- libi2pd/Garlic.cpp | 12 +++------ libi2pd/Garlic.h | 3 +-- libi2pd/Identity.h | 6 ++--- libi2pd/LeaseSet.cpp | 10 ++++---- libi2pd/LeaseSet.h | 6 ++--- libi2pd/RouterContext.cpp | 8 +++--- libi2pd/RouterContext.h | 4 +-- libi2pd/RouterInfo.cpp | 4 +-- libi2pd/RouterInfo.h | 2 +- libi2pd/TunnelConfig.cpp | 6 +---- libi2pd_client/I2CP.cpp | 6 ++--- libi2pd_client/I2CP.h | 2 +- 19 files changed, 81 insertions(+), 85 deletions(-) diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 136a2072..9c9d5252 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -398,8 +398,9 @@ namespace crypto } // ElGamal - void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, bool zeroPadding) { + BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); // everything, but a, because a might come from table BIGNUM * k = BN_CTX_get (ctx); @@ -449,11 +450,12 @@ namespace crypto } BN_free (a); BN_CTX_end (ctx); + BN_CTX_free (ctx); } - bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, - uint8_t * data, BN_CTX * ctx) + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data) { + BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * x = BN_CTX_get (ctx), * a = BN_CTX_get (ctx), * b = BN_CTX_get (ctx); BN_bin2bn (key, 256, x); @@ -466,6 +468,7 @@ namespace crypto uint8_t m[255]; bn2buf (b, m, 255); BN_CTX_end (ctx); + BN_CTX_free (ctx); uint8_t hash[32]; SHA256 (m + 33, 222, hash); if (memcmp (m + 1, hash, 32)) @@ -499,8 +502,9 @@ namespace crypto } // ECIES - void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, bool zeroPadding) { + BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * q = BN_CTX_get (ctx); EC_GROUP_get_order(curve, q, ctx); @@ -550,11 +554,13 @@ namespace crypto encryption.Encrypt (m, 256, encrypted + 256); EC_POINT_free (p); BN_CTX_end (ctx); + BN_CTX_free (ctx); } - bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) + bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data) { bool ret = true; + BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); BIGNUM * q = BN_CTX_get (ctx); EC_GROUP_get_order(curve, q, ctx); @@ -599,6 +605,7 @@ namespace crypto EC_POINT_free (p); BN_CTX_end (ctx); + BN_CTX_free (ctx); return ret; } diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 56791ceb..f165d59d 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -108,13 +108,13 @@ namespace crypto }; // ElGamal - void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without - bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx); // 514 bytes encrypted, 222 data + void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without + bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub); // ECIES - void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without - bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx); // 514 bytes encrypted, 222 data + void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without + bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub); // HMAC diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp index 3b2105a4..8e49792a 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -20,10 +20,9 @@ namespace crypto memcpy (m_PublicKey, pub, 256); } - void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) { - if (!ctx) return; - ElGamalEncrypt (m_PublicKey, data, encrypted, ctx, zeroPadding); + ElGamalEncrypt (m_PublicKey, data, encrypted, zeroPadding); } ElGamalDecryptor::ElGamalDecryptor (const uint8_t * priv) @@ -31,10 +30,9 @@ namespace crypto memcpy (m_PrivateKey, priv, 256); } - bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) + bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data) { - if (!ctx) return false; - return ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx); + return ElGamalDecrypt (m_PrivateKey, encrypted, data); } ECIESP256Encryptor::ECIESP256Encryptor (const uint8_t * pub) @@ -54,10 +52,10 @@ namespace crypto if (m_PublicKey) EC_POINT_free (m_PublicKey); } - void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) { if (m_Curve && m_PublicKey) - ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, ctx, zeroPadding); + ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, zeroPadding); } ECIESP256Decryptor::ECIESP256Decryptor (const uint8_t * priv) @@ -72,10 +70,10 @@ namespace crypto if (m_PrivateKey) BN_free (m_PrivateKey); } - bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) + bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data) { if (m_Curve && m_PrivateKey) - return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data, ctx); + return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data); return false; } @@ -114,10 +112,10 @@ namespace crypto if (m_PublicKey) EC_POINT_free (m_PublicKey); } - void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) + void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) { if (m_PublicKey) - ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted, ctx, zeroPadding); + ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted, zeroPadding); } ECIESGOSTR3410Decryptor::ECIESGOSTR3410Decryptor (const uint8_t * priv) @@ -130,10 +128,10 @@ namespace crypto if (m_PrivateKey) BN_free (m_PrivateKey); } - bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) + bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data) { if (m_PrivateKey) - return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data, ctx); + return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data); return false; } @@ -161,7 +159,7 @@ namespace crypto memcpy (m_PublicKey, pub, 32); } - void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub, BN_CTX *, bool) + void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub, bool) { memcpy (pub, m_PublicKey, 32); } @@ -171,7 +169,7 @@ namespace crypto m_StaticKeys.SetPrivateKey (priv, calculatePublic); } - bool ECIESX25519AEADRatchetDecryptor::Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx) + bool ECIESX25519AEADRatchetDecryptor::Decrypt (const uint8_t * epub, uint8_t * sharedSecret) { return m_StaticKeys.Agree (epub, sharedSecret); } diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h index a5b96c47..705de49e 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -21,7 +21,7 @@ namespace crypto public: virtual ~CryptoKeyEncryptor () {}; - virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) = 0; + virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) = 0; }; class CryptoKeyDecryptor @@ -29,7 +29,7 @@ namespace crypto public: virtual ~CryptoKeyDecryptor () {}; - virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) = 0; + virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data) = 0; virtual size_t GetPublicKeyLen () const = 0; // we need it to set key in LS2 }; @@ -39,7 +39,7 @@ namespace crypto public: ElGamalEncryptor (const uint8_t * pub); - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) override; // 222 bytes data, 512/514 bytes encrypted + void Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) override; // 222 bytes data, 512/514 bytes encrypted private: @@ -51,7 +51,7 @@ namespace crypto public: ElGamalDecryptor (const uint8_t * priv); - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) override; // 514 bytes encrypted, 222 bytes data + bool Decrypt (const uint8_t * encrypted, uint8_t * data) override; // 514 bytes encrypted, 222 bytes data size_t GetPublicKeyLen () const override { return 256; }; private: @@ -67,7 +67,7 @@ namespace crypto ECIESP256Encryptor (const uint8_t * pub); ~ECIESP256Encryptor (); - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) override; + void Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) override; private: @@ -82,7 +82,7 @@ namespace crypto ECIESP256Decryptor (const uint8_t * priv); ~ECIESP256Decryptor (); - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) override; + bool Decrypt (const uint8_t * encrypted, uint8_t * data) override; size_t GetPublicKeyLen () const override { return 64; }; private: @@ -101,7 +101,7 @@ namespace crypto ECIESGOSTR3410Encryptor (const uint8_t * pub); ~ECIESGOSTR3410Encryptor (); - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) override; + void Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) override; private: @@ -115,7 +115,7 @@ namespace crypto ECIESGOSTR3410Decryptor (const uint8_t * priv); ~ECIESGOSTR3410Decryptor (); - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) override; + bool Decrypt (const uint8_t * encrypted, uint8_t * data) override; size_t GetPublicKeyLen () const override { return 64; }; private: @@ -133,7 +133,7 @@ namespace crypto ECIESX25519AEADRatchetEncryptor (const uint8_t * pub); ~ECIESX25519AEADRatchetEncryptor () {}; - void Encrypt (const uint8_t *, uint8_t * pub, BN_CTX *, bool) override; + void Encrypt (const uint8_t *, uint8_t * pub, bool) override; // copies m_PublicKey to pub private: @@ -147,7 +147,7 @@ namespace crypto ECIESX25519AEADRatchetDecryptor (const uint8_t * priv, bool calculatePublic = false); ~ECIESX25519AEADRatchetDecryptor () {}; - bool Decrypt (const uint8_t * epub, uint8_t * sharedSecret, BN_CTX * ctx) override; + bool Decrypt (const uint8_t * epub, uint8_t * sharedSecret) override; // agree with static and return in sharedSecret (32 bytes) size_t GetPublicKeyLen () const override { return 32; }; const uint8_t * GetPubicKey () const { return m_StaticKeys.GetPublicKey (); }; diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index f269c092..438b3f19 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -1245,13 +1245,13 @@ namespace client if (m_DatagramDestination) m_DatagramDestination->CleanUp (); } - bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const + bool ClientDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const { if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) if (m_ECIESx25519EncryptionKey && m_ECIESx25519EncryptionKey->decryptor) - return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data, ctx); + return m_ECIESx25519EncryptionKey->decryptor->Decrypt (encrypted, data); if (m_StandardEncryptionKey && m_StandardEncryptionKey->decryptor) - return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data, ctx); + return m_StandardEncryptionKey->decryptor->Decrypt (encrypted, data); else LogPrint (eLogError, "Destinations: decryptor is not set"); return false; diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 2effff27..9e63d9bb 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -254,7 +254,7 @@ namespace client i2p::datagram::DatagramDestination * CreateDatagramDestination (bool gzip = true); // implements LocalDestination - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const; std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index 5ff2ae5c..c9671a7e 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -239,7 +239,7 @@ namespace garlic MixHash (m_Aepk, 32); // h = SHA256(h || aepk) uint8_t sharedSecret[32]; - if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) + if (!GetOwner ()->Decrypt (m_Aepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) { LogPrint (eLogWarning, "Garlic: Incorrect Alice ephemeral key"); return false; @@ -263,7 +263,7 @@ namespace garlic { // static key, fs is apk memcpy (m_RemoteStaticKey, fs, 32); - if (!GetOwner ()->Decrypt (fs, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, apk) + if (!GetOwner ()->Decrypt (fs, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, apk) { LogPrint (eLogWarning, "Garlic: Incorrect Alice static key"); return false; @@ -492,7 +492,7 @@ namespace garlic // KDF2 if (isStatic) { - GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bpk) + GetOwner ()->Decrypt (m_RemoteStaticKey, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bpk) MixKey (sharedSecret); } else @@ -639,7 +639,7 @@ namespace garlic return false; } MixKey (sharedSecret); - GetOwner ()->Decrypt (bepk, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) + GetOwner ()->Decrypt (bepk, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD); // x25519 (ask, bepk) MixKey (sharedSecret); uint8_t nonce[12]; @@ -1084,7 +1084,7 @@ namespace garlic // we are Bob m_CurrentNoiseState.MixHash (buf, 32); uint8_t sharedSecret[32]; - if (!GetOwner ()->Decrypt (buf, sharedSecret, nullptr, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) + if (!GetOwner ()->Decrypt (buf, sharedSecret, i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) // x25519(bsk, aepk) { LogPrint (eLogWarning, "Garlic: Incorrect N ephemeral public key"); return false; diff --git a/libi2pd/Garlic.cpp b/libi2pd/Garlic.cpp index e5b74624..ea30a249 100644 --- a/libi2pd/Garlic.cpp +++ b/libi2pd/Garlic.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -164,9 +164,7 @@ namespace garlic RAND_bytes (elGamal.preIV, 32); // Pre-IV uint8_t iv[32]; // IV is first 16 bytes SHA256(elGamal.preIV, 32, iv); - BN_CTX * ctx = BN_CTX_new (); - m_Destination->Encrypt ((uint8_t *)&elGamal, buf, ctx); - BN_CTX_free (ctx); + m_Destination->Encrypt ((uint8_t *)&elGamal, buf); m_Encryption.SetIV (iv); buf += 514; len += 514; @@ -435,12 +433,10 @@ namespace garlic GarlicDestination::GarlicDestination (): m_NumTags (32), // 32 tags by default m_PayloadBuffer (nullptr), m_NumRatchetInboundTags (0) // 0 means standard { - m_Ctx = BN_CTX_new (); } GarlicDestination::~GarlicDestination () { - BN_CTX_free (m_Ctx); if (m_PayloadBuffer) delete[] m_PayloadBuffer; } @@ -531,7 +527,7 @@ namespace garlic // try ElGamal/AES first if leading block is 514 ElGamalBlock elGamal; if (mod == 2 && length >= 514 && SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ELGAMAL) && - Decrypt (buf, (uint8_t *)&elGamal, m_Ctx, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)) + Decrypt (buf, (uint8_t *)&elGamal, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL)) { auto decryption = std::make_shared(elGamal.sessionKey); uint8_t iv[32]; // IV is first 16 bytes @@ -777,7 +773,7 @@ namespace garlic { ECIESX25519AEADRatchetSessionPtr session; uint8_t staticKey[32]; - destination->Encrypt (nullptr, staticKey, nullptr); // we are supposed to get static key + destination->Encrypt (nullptr, staticKey); // we are supposed to get static key auto it = m_ECIESx25519Sessions.find (staticKey); if (it != m_ECIESx25519Sessions.end ()) { diff --git a/libi2pd/Garlic.h b/libi2pd/Garlic.h index b150e78d..0d7b8461 100644 --- a/libi2pd/Garlic.h +++ b/libi2pd/Garlic.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -279,7 +279,6 @@ namespace garlic private: - BN_CTX * m_Ctx; // incoming // outgoing sessions int m_NumTags; std::mutex m_SessionsMutex; diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index e9cf63ed..b52f36cf 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -222,7 +222,7 @@ namespace data virtual ~RoutingDestination () {}; virtual std::shared_ptr GetIdentity () const = 0; - virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const = 0; // encrypt data for + virtual void Encrypt (const uint8_t * data, uint8_t * encrypted) const = 0; // encrypt data for virtual bool IsDestination () const = 0; // for garlic const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; @@ -234,7 +234,7 @@ namespace data public: virtual ~LocalDestination() {}; - virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL) const = 0; + virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL) const = 0; virtual std::shared_ptr GetIdentity () const = 0; const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index a0b14385..4d5e58d4 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -254,12 +254,12 @@ namespace data return ts > m_ExpirationTime; } - void LeaseSet::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const + void LeaseSet::Encrypt (const uint8_t * data, uint8_t * encrypted) const { if (!m_EncryptionKey) return; auto encryptor = m_Identity->CreateEncryptor (m_EncryptionKey); if (encryptor) - encryptor->Encrypt (data, encrypted, ctx, true); + encryptor->Encrypt (data, encrypted, true); } void LeaseSet::SetBuffer (const uint8_t * buf, size_t len) @@ -658,11 +658,11 @@ namespace data return offset - 1; } - void LeaseSet2::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const + void LeaseSet2::Encrypt (const uint8_t * data, uint8_t * encrypted) const { auto encryptor = m_Encryptor; // TODO: atomic if (encryptor) - encryptor->Encrypt (data, encrypted, ctx, true); + encryptor->Encrypt (data, encrypted, true); } uint64_t LeaseSet2::ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h index cd6535df..8d501cb1 100644 --- a/libi2pd/LeaseSet.h +++ b/libi2pd/LeaseSet.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -93,7 +93,7 @@ namespace data // implements RoutingDestination std::shared_ptr GetIdentity () const { return m_Identity; }; - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; + void Encrypt (const uint8_t * data, uint8_t * encrypted) const; bool IsDestination () const { return true; }; protected: @@ -156,7 +156,7 @@ namespace data bool IsNewer (const uint8_t * buf, size_t len) const; // implements RoutingDestination - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; + void Encrypt (const uint8_t * data, uint8_t * encrypted) const; CryptoKeyType GetEncryptionType () const { return m_EncryptionType; }; private: diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 92dc5aea..1a2dd335 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -878,9 +878,9 @@ namespace i2p return std::chrono::duration_cast (std::chrono::steady_clock::now() - m_StartupTime).count (); } - bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const + bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const { - return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx) : false; + return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data) : false; } bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data) @@ -900,7 +900,7 @@ namespace i2p m_CurrentNoiseState = m_InitialNoiseState; m_CurrentNoiseState.MixHash (encrypted, 32); // h = SHA256(h || sepk) uint8_t sharedSecret[32]; - if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret, nullptr)) + if (!m_TunnelDecryptor->Decrypt (encrypted, sharedSecret)) { LogPrint (eLogWarning, "Router: Incorrect ephemeral public key"); return false; diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index b52e20d9..98ee6a72 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -134,7 +134,7 @@ namespace garlic // implements LocalDestination std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const; void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; void SetLeaseSetUpdated () {}; diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 0d439dfc..78abbb51 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1155,11 +1155,11 @@ namespace data return m_Profile; } - void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const + void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted) const { auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr); if (encryptor) - encryptor->Encrypt (data, encrypted, ctx, true); + encryptor->Encrypt (data, encrypted, true); } bool RouterInfo::IsEligibleFloodfill () const diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index b2b6df61..6ba52c7f 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -243,7 +243,7 @@ namespace data // implements RoutingDestination std::shared_ptr GetIdentity () const { return m_RouterIdentity; }; - void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; + void Encrypt (const uint8_t * data, uint8_t * encrypted) const; bool IsDestination () const { return false; }; diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 543ba0fa..3eee2067 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -111,11 +111,7 @@ namespace tunnel uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; auto encryptor = ident->CreateEncryptor (nullptr); if (encryptor) - { - BN_CTX * ctx = BN_CTX_new (); - encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false); - BN_CTX_free (ctx); - } + encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, false); memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp index b6618ff9..7c76d359 100644 --- a/libi2pd_client/I2CP.cpp +++ b/libi2pd_client/I2CP.cpp @@ -52,12 +52,12 @@ namespace client } } - bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const + bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const { if (preferredCrypto == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && m_ECIESx25519Decryptor) - return m_ECIESx25519Decryptor->Decrypt (encrypted, data, ctx); + return m_ECIESx25519Decryptor->Decrypt (encrypted, data); if (m_Decryptor) - return m_Decryptor->Decrypt (encrypted, data, ctx); + return m_Decryptor->Decrypt (encrypted, data); else LogPrint (eLogError, "I2CP: decryptor is not set"); return false; diff --git a/libi2pd_client/I2CP.h b/libi2pd_client/I2CP.h index 9a8bda4e..08a96af6 100644 --- a/libi2pd_client/I2CP.h +++ b/libi2pd_client/I2CP.h @@ -83,7 +83,7 @@ namespace client void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession // implements LocalDestination - bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, i2p::data::CryptoKeyType preferredCrypto) const; + bool Decrypt (const uint8_t * encrypted, uint8_t * data, i2p::data::CryptoKeyType preferredCrypto) const; bool SupportsEncryptionType (i2p::data::CryptoKeyType keyType) const; const uint8_t * GetEncryptionPublicKey (i2p::data::CryptoKeyType keyType) const; // for 4 only std::shared_ptr GetIdentity () const { return m_Identity; }; From 349022ae42791e2ce39da84ae81fbd40742cf85e Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 3 Sep 2021 13:30:01 -0400 Subject: [PATCH 4410/6300] don't select ElGamal routers for tunnels --- libi2pd/Crypto.cpp | 47 ++++++++------------------- libi2pd/Crypto.h | 4 +-- libi2pd/CryptoKey.cpp | 14 ++++---- libi2pd/CryptoKey.h | 10 +++--- libi2pd/LeaseSet.cpp | 4 +-- libi2pd/NetDb.cpp | 12 +++---- libi2pd/RouterInfo.cpp | 2 +- libi2pd/RouterInfo.h | 1 + libi2pd/TunnelConfig.cpp | 69 ++++++++++++++++------------------------ libi2pd/TunnelConfig.h | 34 +------------------- libi2pd/TunnelPool.cpp | 13 ++++++-- 11 files changed, 75 insertions(+), 135 deletions(-) diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 9c9d5252..427bbbd6 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -398,7 +398,7 @@ namespace crypto } // ElGamal - void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, bool zeroPadding) + void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted) { BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); @@ -436,18 +436,11 @@ namespace crypto BN_bin2bn (m, 255, b); BN_mod_mul (b, b1, b, elgp, ctx); // copy a and b - if (zeroPadding) - { - encrypted[0] = 0; - bn2buf (a, encrypted + 1, 256); - encrypted[257] = 0; - bn2buf (b, encrypted + 258, 256); - } - else - { - bn2buf (a, encrypted, 256); - bn2buf (b, encrypted + 256, 256); - } + encrypted[0] = 0; + bn2buf (a, encrypted + 1, 256); + encrypted[257] = 0; + bn2buf (b, encrypted + 258, 256); + BN_free (a); BN_CTX_end (ctx); BN_CTX_free (ctx); @@ -502,7 +495,7 @@ namespace crypto } // ECIES - void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, bool zeroPadding) + void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted) { BN_CTX * ctx = BN_CTX_new (); BN_CTX_start (ctx); @@ -516,19 +509,10 @@ namespace crypto EC_POINT_mul (curve, p, k, nullptr, nullptr, ctx); BIGNUM * x = BN_CTX_get (ctx), * y = BN_CTX_get (ctx); EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); - if (zeroPadding) - { - encrypted[0] = 0; - bn2buf (x, encrypted + 1, len); - bn2buf (y, encrypted + 1 + len, len); - RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len); - } - else - { - bn2buf (x, encrypted, len); - bn2buf (y, encrypted + len, len); - RAND_bytes (encrypted + 2*len, 256 - 2*len); - } + encrypted[0] = 0; + bn2buf (x, encrypted + 1, len); + bn2buf (y, encrypted + 1 + len, len); + RAND_bytes (encrypted + 1 + 2*len, 256 - 2*len); // encryption key and iv EC_POINT_mul (curve, p, nullptr, key, k, ctx); EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, nullptr); @@ -545,13 +529,8 @@ namespace crypto CBCEncryption encryption; encryption.SetKey (shared); encryption.SetIV (iv); - if (zeroPadding) - { - encrypted[257] = 0; - encryption.Encrypt (m, 256, encrypted + 258); - } - else - encryption.Encrypt (m, 256, encrypted + 256); + encrypted[257] = 0; + encryption.Encrypt (m, 256, encrypted + 258); EC_POINT_free (p); BN_CTX_end (ctx); BN_CTX_free (ctx); diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index f165d59d..5f42b527 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -108,12 +108,12 @@ namespace crypto }; // ElGamal - void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without + void ElGamalEncrypt (const uint8_t * key, const uint8_t * data, uint8_t * encrypted); // 222 bytes data, 514 bytes encrypted bool ElGamalDecrypt (const uint8_t * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data void GenerateElGamalKeyPair (uint8_t * priv, uint8_t * pub); // ECIES - void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without + void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted); // 222 bytes data, 514 bytes encrypted bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data); // 514 bytes encrypted, 222 data void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub); diff --git a/libi2pd/CryptoKey.cpp b/libi2pd/CryptoKey.cpp index 8e49792a..ad986129 100644 --- a/libi2pd/CryptoKey.cpp +++ b/libi2pd/CryptoKey.cpp @@ -20,9 +20,9 @@ namespace crypto memcpy (m_PublicKey, pub, 256); } - void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) + void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted) { - ElGamalEncrypt (m_PublicKey, data, encrypted, zeroPadding); + ElGamalEncrypt (m_PublicKey, data, encrypted); } ElGamalDecryptor::ElGamalDecryptor (const uint8_t * priv) @@ -52,10 +52,10 @@ namespace crypto if (m_PublicKey) EC_POINT_free (m_PublicKey); } - void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) + void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted) { if (m_Curve && m_PublicKey) - ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, zeroPadding); + ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted); } ECIESP256Decryptor::ECIESP256Decryptor (const uint8_t * priv) @@ -112,10 +112,10 @@ namespace crypto if (m_PublicKey) EC_POINT_free (m_PublicKey); } - void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) + void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted) { if (m_PublicKey) - ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted, zeroPadding); + ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted); } ECIESGOSTR3410Decryptor::ECIESGOSTR3410Decryptor (const uint8_t * priv) @@ -159,7 +159,7 @@ namespace crypto memcpy (m_PublicKey, pub, 32); } - void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub, bool) + void ECIESX25519AEADRatchetEncryptor::Encrypt (const uint8_t *, uint8_t * pub) { memcpy (pub, m_PublicKey, 32); } diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h index 705de49e..0ac0bdd5 100644 --- a/libi2pd/CryptoKey.h +++ b/libi2pd/CryptoKey.h @@ -21,7 +21,7 @@ namespace crypto public: virtual ~CryptoKeyEncryptor () {}; - virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) = 0; + virtual void Encrypt (const uint8_t * data, uint8_t * encrypted) = 0; }; class CryptoKeyDecryptor @@ -39,7 +39,7 @@ namespace crypto public: ElGamalEncryptor (const uint8_t * pub); - void Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) override; // 222 bytes data, 512/514 bytes encrypted + void Encrypt (const uint8_t * data, uint8_t * encrypted) override; // 222 bytes data, 514 bytes encrypted private: @@ -67,7 +67,7 @@ namespace crypto ECIESP256Encryptor (const uint8_t * pub); ~ECIESP256Encryptor (); - void Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) override; + void Encrypt (const uint8_t * data, uint8_t * encrypted) override; private: @@ -101,7 +101,7 @@ namespace crypto ECIESGOSTR3410Encryptor (const uint8_t * pub); ~ECIESGOSTR3410Encryptor (); - void Encrypt (const uint8_t * data, uint8_t * encrypted, bool zeroPadding) override; + void Encrypt (const uint8_t * data, uint8_t * encrypted) override; private: @@ -133,7 +133,7 @@ namespace crypto ECIESX25519AEADRatchetEncryptor (const uint8_t * pub); ~ECIESX25519AEADRatchetEncryptor () {}; - void Encrypt (const uint8_t *, uint8_t * pub, bool) override; + void Encrypt (const uint8_t *, uint8_t * pub) override; // copies m_PublicKey to pub private: diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index 4d5e58d4..75187cfe 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -259,7 +259,7 @@ namespace data if (!m_EncryptionKey) return; auto encryptor = m_Identity->CreateEncryptor (m_EncryptionKey); if (encryptor) - encryptor->Encrypt (data, encrypted, true); + encryptor->Encrypt (data, encrypted); } void LeaseSet::SetBuffer (const uint8_t * buf, size_t len) @@ -662,7 +662,7 @@ namespace data { auto encryptor = m_Encryptor; // TODO: atomic if (encryptor) - encryptor->Encrypt (data, encrypted, true); + encryptor->Encrypt (data, encrypted); } uint64_t LeaseSet2::ExtractExpirationTimestamp (const uint8_t * buf, size_t len) const diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 904782cf..7c473a09 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1171,7 +1171,8 @@ namespace data { return !router->IsHidden () && router != compatibleWith && (reverse ? compatibleWith->IsReachableFrom (*router) : - router->IsReachableFrom (*compatibleWith)); + router->IsReachableFrom (*compatibleWith)) && + router->IsECIES (); }); } @@ -1212,12 +1213,9 @@ namespace data return !router->IsHidden () && router != compatibleWith && (reverse ? compatibleWith->IsReachableFrom (*router) : router->IsReachableFrom (*compatibleWith)) && - (router->GetCaps () & RouterInfo::eHighBandwidth) && -#if defined(__x86_64__) - router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION; -#else - router->GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; -#endif + (router->GetCaps () & RouterInfo::eHighBandwidth) && + router->GetVersion () >= NETDB_MIN_HIGHBANDWIDTH_VERSION && + router->IsECIES (); }); } diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 78abbb51..5d66e0e4 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -1159,7 +1159,7 @@ namespace data { auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr); if (encryptor) - encryptor->Encrypt (data, encrypted, true); + encryptor->Encrypt (data, encrypted); } bool RouterInfo::IsEligibleFloodfill () const diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 6ba52c7f..8ffd81cd 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -191,6 +191,7 @@ namespace data void UpdateSupportedTransports (); bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; bool IsReachable () const { return m_Caps & Caps::eReachable; }; + bool IsECIES () const { return m_RouterIdentity->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; bool IsSSU (bool v4only = true) const; bool IsSSUV6 () const; bool IsNTCP2 (bool v4only = true) const; diff --git a/libi2pd/TunnelConfig.cpp b/libi2pd/TunnelConfig.cpp index 3eee2067..4592c663 100644 --- a/libi2pd/TunnelConfig.cpp +++ b/libi2pd/TunnelConfig.cpp @@ -82,48 +82,6 @@ namespace tunnel decryption.SetIV (replyIV); decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); } - - void ElGamalTunnelHopConfig::CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID) - { - // generate keys - RAND_bytes (layerKey, 32); - RAND_bytes (ivKey, 32); - RAND_bytes (replyKey, 32); - RAND_bytes (replyIV, 16); - // fill clear text - uint8_t flag = 0; - if (isGateway) flag |= TUNNEL_BUILD_RECORD_GATEWAY_FLAG; - if (isEndpoint) flag |= TUNNEL_BUILD_RECORD_ENDPOINT_FLAG; - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); - htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); - memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); - memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; - htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); - htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); - RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); - // encrypt - uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; - auto encryptor = ident->CreateEncryptor (nullptr); - if (encryptor) - encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, false); - memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); - } - - bool ElGamalTunnelHopConfig::DecryptBuildResponseRecord (uint8_t * records) const - { - uint8_t * record = records + recordIndex*TUNNEL_BUILD_RECORD_SIZE; - i2p::crypto::CBCDecryption decryption; - decryption.SetKey (replyKey); - decryption.SetIV (replyIV); - decryption.Decrypt (record, TUNNEL_BUILD_RECORD_SIZE, record); - return true; - } void ECIESTunnelHopConfig::EncryptECIES (const uint8_t * plainText, size_t len, uint8_t * encrypted) { @@ -261,5 +219,32 @@ namespace tunnel memcpy (key, m_CK + 32, 32); return tag; } + + void TunnelConfig::CreatePeers (const std::vector >& peers) + { + TunnelHopConfig * prev = nullptr; + for (const auto& it: peers) + { + TunnelHopConfig * hop = nullptr; + if (m_IsShort) + hop = new ShortECIESTunnelHopConfig (it); + else + { + if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + hop = new LongECIESTunnelHopConfig (it); + else + LogPrint (eLogError, "Tunnel: ElGamal router is not supported"); + } + if (hop) + { + if (prev) + prev->SetNext (hop); + else + m_FirstHop = hop; + prev = hop; + } + } + m_LastHop = prev; + } } } \ No newline at end of file diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 875c20bb..8cb46d09 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -47,16 +47,6 @@ namespace tunnel virtual uint64_t GetGarlicKey (uint8_t * key) const { return 0; }; // return tag }; - struct ElGamalTunnelHopConfig: public TunnelHopConfig - { - ElGamalTunnelHopConfig (std::shared_ptr r): - TunnelHopConfig (r) {}; - uint8_t GetRetCode (const uint8_t * records) const - { return (records + recordIndex*TUNNEL_BUILD_RECORD_SIZE)[BUILD_RESPONSE_RECORD_RET_OFFSET]; }; - void CreateBuildRequestRecord (uint8_t * records, uint32_t replyMsgID); - bool DecryptBuildResponseRecord (uint8_t * records) const; - }; - struct ECIESTunnelHopConfig: public TunnelHopConfig, public i2p::crypto::NoiseSymmetricState { ECIESTunnelHopConfig (std::shared_ptr r): @@ -194,29 +184,7 @@ namespace tunnel private: - void CreatePeers (const std::vector >& peers) - { - TunnelHopConfig * prev = nullptr; - for (const auto& it: peers) - { - TunnelHopConfig * hop; - if (m_IsShort) - hop = new ShortECIESTunnelHopConfig (it); - else - { - if (it->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) - hop = new LongECIESTunnelHopConfig (it); - else - hop = new ElGamalTunnelHopConfig (it); - } - if (prev) - prev->SetNext (hop); - else - m_FirstHop = hop; - prev = hop; - } - m_LastHop = prev; - } + void CreatePeers (const std::vector >& peers); private: diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 1745ebb8..b885f69f 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -453,7 +453,7 @@ namespace tunnel (inbound && i2p::transport::transports.GetNumPeers () > 25)) { auto r = i2p::transport::transports.GetRandomPeer (); - if (r && !r->GetProfile ()->IsBad () && + if (r && r->IsECIES () && !r->GetProfile ()->IsBad () && (numHops > 1 || (r->IsV4 () && (!inbound || r->IsReachable ())))) // first inbound must be reachable { prevHop = r; @@ -469,6 +469,7 @@ namespace tunnel { LogPrint (eLogInfo, "Tunnels: Can't select first hop for a tunnel. Trying already connected"); hop = i2p::transport::transports.GetRandomPeer (); + if (!hop->IsECIES ()) hop = nullptr; } if (!hop) { @@ -513,7 +514,15 @@ namespace tunnel auto& ident = (*m_ExplicitPeers)[i]; auto r = i2p::data::netdb.FindRouter (ident); if (r) - path.Add (r); + { + if (r->IsECIES ()) + path.Add (r); + else + { + LogPrint (eLogError, "Tunnels: ElGamal router ", ident.ToBase64 (), " is not supported"); + return false; + } + } else { LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ()); From 6b1ef6e1b97081617816a95e8bea3be86dd1ea79 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 3 Sep 2021 23:25:47 +0300 Subject: [PATCH 4411/6300] tunnels reload changes: fix tcp tunnels reload Signed-off-by: R4SAS --- libi2pd_client/ClientContext.cpp | 132 ++++++++++++++++++++----------- libi2pd_client/ClientContext.h | 3 +- libi2pd_client/I2PTunnel.cpp | 10 ++- libi2pd_client/I2PTunnel.h | 10 ++- 4 files changed, 100 insertions(+), 55 deletions(-) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 6e9ac391..41efa611 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -185,7 +185,7 @@ namespace client LogPrint(eLogInfo, "Clients: stopping AddressBook"); m_AddressBook.Stop (); - { + { std::lock_guard lock(m_ForwardsMutex); m_ServerForwards.clear(); m_ClientForwards.clear(); @@ -200,6 +200,8 @@ namespace client for (auto& it: m_Destinations) it.second->Stop (); m_Destinations.clear (); + + m_SharedLocalDestination->Release (); m_SharedLocalDestination = nullptr; } @@ -209,14 +211,6 @@ namespace client /*std::string config; i2p::config::GetOption("conf", config); i2p::config::ParseConfig(config);*/ - // handle tunnels - // reset isUpdated for each tunnel - VisitTunnels ([](I2PService * s)->bool { s->isUpdated = false; return true; }); - // reload tunnels - ReadTunnels(); - // delete not updated tunnels (not in config anymore) - VisitTunnels ([](I2PService * s)->bool { return s->isUpdated; }); - // change shared local destination m_SharedLocalDestination->Release (); CreateNewSharedLocalDestination (); @@ -225,6 +219,7 @@ namespace client if (m_HttpProxy) { m_HttpProxy->Stop (); + delete m_HttpProxy; m_HttpProxy = nullptr; } ReadHttpProxy (); @@ -233,10 +228,19 @@ namespace client if (m_SocksProxy) { m_SocksProxy->Stop (); + delete m_SocksProxy; m_SocksProxy = nullptr; } ReadSocksProxy (); + // handle tunnels + // reset isUpdated for each tunnel + VisitTunnels (false); + // reload tunnels + ReadTunnels(); + // delete not updated tunnels (not in config anymore) + VisitTunnels (true); + // delete unused destinations std::unique_lock l(m_DestinationsMutex); for (auto it = m_Destinations.begin (); it != m_Destinations.end ();) @@ -504,20 +508,15 @@ namespace client int numClientTunnels = 0, numServerTunnels = 0; std::string tunConf; i2p::config::GetOption("tunconf", tunConf); if (tunConf.empty ()) - { - // TODO: cleanup this in 2.8.0 - tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); - if (i2p::fs::Exists(tunConf)) - LogPrint(eLogWarning, "Clients: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); - else - tunConf = i2p::fs::DataDirPath ("tunnels.conf"); - } + tunConf = i2p::fs::DataDirPath ("tunnels.conf"); + LogPrint(eLogDebug, "Clients: tunnels config file: ", tunConf); ReadTunnels (tunConf, numClientTunnels, numServerTunnels); std::string tunDir; i2p::config::GetOption("tunnelsdir", tunDir); if (tunDir.empty ()) tunDir = i2p::fs::DataDirPath ("tunnels.d"); + if (i2p::fs::Exists (tunDir)) { std::vector files; @@ -582,7 +581,7 @@ namespace client if (it != destinations.end ()) localDestination = it->second; else - { + { i2p::data::PrivateKeys k; if(LoadPrivateKeys (k, keys, sigType, cryptoType)) { @@ -597,7 +596,7 @@ namespace client destinations[keys] = localDestination; } } - } + } } if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { @@ -609,10 +608,18 @@ namespace client bool gzip = section.second.get (I2P_CLIENT_TUNNEL_GZIP, true); auto clientTunnel = std::make_shared(name, dest, end, localDestination, destinationPort, gzip); - if(m_ClientForwards.insert(std::make_pair(end, clientTunnel)).second) + + auto ins = m_ClientForwards.insert(std::make_pair(end, clientTunnel)); + if (ins.second) + { clientTunnel->Start(); + numClientTunnels++; + } else + { + ins.first->second->isUpdated = true; LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); + } } else { boost::asio::ip::tcp::endpoint clientEndpoint; @@ -666,13 +673,16 @@ namespace client if (ins.first->second->GetLocalDestination () != clientTunnel->GetLocalDestination ()) { LogPrint (eLogInfo, "Clients: I2P client tunnel destination updated"); + ins.first->second->Stop (); ins.first->second->SetLocalDestination (clientTunnel->GetLocalDestination ()); + ins.first->second->Start (); } ins.first->second->isUpdated = true; LogPrint (eLogInfo, "Clients: I2P client tunnel for endpoint ", clientEndpoint, " already exists"); } } } + else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC @@ -705,17 +715,17 @@ namespace client if (it != destinations.end ()) localDestination = it->second; else - { + { i2p::data::PrivateKeys k; if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) continue; localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); if (!localDestination) - { + { localDestination = CreateNewLocalDestination (k, true, &options); destinations[keys] = localDestination; - } - } + } + } if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // udp server tunnel @@ -727,8 +737,8 @@ namespace client address = "::1"; else address = "127.0.0.1"; - } - auto localAddress = boost::asio::ip::address::from_string(address); + } + auto localAddress = boost::asio::ip::address::from_string(address); auto serverTunnel = std::make_shared(name, localDestination, localAddress, endpoint, port, gzip); if(!isUniqueLocal) { @@ -736,16 +746,16 @@ namespace client serverTunnel->SetUniqueLocal(isUniqueLocal); } std::lock_guard lock(m_ForwardsMutex); - if(m_ServerForwards.insert( - std::make_pair( - std::make_pair( - localDestination->GetIdentHash(), port), - serverTunnel)).second) + auto ins = m_ServerForwards.insert(std::make_pair( + std::make_pair(localDestination->GetIdentHash(), port), + serverTunnel)); + if (ins.second) { serverTunnel->Start(); LogPrint(eLogInfo, "Clients: I2P Server Forward created for UDP Endpoint ", host, ":", port, " bound on ", address, " for ",localDestination->GetIdentHash().ToBase32()); } else + ins.first->second->isUpdated = true; LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); continue; @@ -795,7 +805,9 @@ namespace client if (ins.first->second->GetLocalDestination () != serverTunnel->GetLocalDestination ()) { LogPrint (eLogInfo, "Clients: I2P server tunnel destination updated"); + ins.first->second->Stop (); ins.first->second->SetLocalDestination (serverTunnel->GetLocalDestination ()); + ins.first->second->Start (); } ins.first->second->isUpdated = true; LogPrint (eLogInfo, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists"); @@ -872,7 +884,7 @@ namespace client { localDestination = m_HttpProxy->GetLocalDestination (); localDestination->Acquire (); - } + } else if (socksProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; @@ -920,27 +932,51 @@ namespace client } } - template - void VisitTunnelsContainer (Container& c, Visitor v) + void ClientContext::VisitTunnels (bool clean) { - for (auto it = c.begin (); it != c.end ();) + for (auto it = m_ClientTunnels.begin (); it != m_ClientTunnels.end ();) { - if (!v (it->second.get ())) - { + if(clean && !it->second->isUpdated) { it->second->Stop (); - it = c.erase (it); - } - else + it = m_ClientTunnels.erase(it); + } else { + it->second->isUpdated = false; it++; + } + } + + for (auto it = m_ServerTunnels.begin (); it != m_ServerTunnels.end ();) + { + if(clean && !it->second->isUpdated) { + it->second->Stop (); + it = m_ServerTunnels.erase(it); + } else { + it->second->isUpdated = false; + it++; + } + } + + for (auto it = m_ClientForwards.begin (); it != m_ClientForwards.end ();) + { + if(clean && !it->second->isUpdated) { + it->second = nullptr; + it = m_ClientForwards.erase(it); + } else { + it->second->isUpdated = false; + it++; + } + } + + for (auto it = m_ServerForwards.begin (); it != m_ServerForwards.end ();) + { + if(clean && !it->second->isUpdated) { + it->second = nullptr; + it = m_ServerForwards.erase(it); + } else { + it->second->isUpdated = false; + it++; + } } } - - template - void ClientContext::VisitTunnels (Visitor v) - { - VisitTunnelsContainer (m_ClientTunnels, v); - VisitTunnelsContainer (m_ServerTunnels, v); - // TODO: implement UDP forwards - } } } diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 076aaa5f..90a21a60 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -121,8 +121,7 @@ namespace client void CleanupUDP(const boost::system::error_code & ecode); void ScheduleCleanupUDP(); - template - void VisitTunnels (Visitor v); // Visitor: (I2PService *) -> bool, true means retain + void VisitTunnels (bool clean); void CreateNewSharedLocalDestination (); void AddLocalDestination (std::shared_ptr localDestination); diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index e8a66228..c01f0fe3 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -303,7 +303,7 @@ namespace client m_ProxyConnectionSent = true; } else - m_OutHeader << line << "\n"; + m_OutHeader << line << "\n"; } } else @@ -888,7 +888,8 @@ namespace client LogPrint(eLogInfo, "UDPServer: done"); } - void I2PUDPServerTunnel::Start() { + void I2PUDPServerTunnel::Start() + { m_LocalDest->Start(); } @@ -1064,8 +1065,9 @@ namespace client else LogPrint(eLogWarning, "UDP Client: not tracking udp session using port ", (int) toPort); } - - I2PUDPClientTunnel::~I2PUDPClientTunnel() { + + I2PUDPClientTunnel::~I2PUDPClientTunnel() + { auto dgram = m_LocalDest->GetDatagramDestination(); if (dgram) dgram->ResetReceiver(); diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index 3b52ea1a..1db34b6c 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -254,6 +254,10 @@ namespace client std::vector m_Sessions; std::shared_ptr m_LocalDest; UDPSessionPtr m_LastSession; + + public: + + bool isUpdated; // transient, used during reload only }; class I2PUDPClientTunnel @@ -283,7 +287,7 @@ namespace client void TryResolving(); private: - + const std::string m_Name; std::mutex m_SessionsMutex; std::unordered_map > m_Sessions; // maps i2p port -> local udp convo @@ -298,6 +302,10 @@ namespace client uint16_t RemotePort, m_LastPort; bool m_cancel_resolve; std::shared_ptr m_LastSession; + + public: + + bool isUpdated; // transient, used during reload only }; class I2PServerTunnel: public I2PService From 3f46ca41cad15f5bf5c66b53581d7ff48fa6811d Mon Sep 17 00:00:00 2001 From: yangfl Date: Sat, 4 Sep 2021 15:07:09 +0800 Subject: [PATCH 4412/6300] disable pthread_setname_np on GNU/Hurd which does not exist on GNU/Hurd --- libi2pd/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/util.cpp b/libi2pd/util.cpp index f7b376f6..2b5c79f2 100644 --- a/libi2pd/util.cpp +++ b/libi2pd/util.cpp @@ -129,7 +129,7 @@ namespace util pthread_set_name_np(pthread_self(), name); #elif defined(__NetBSD__) pthread_setname_np(pthread_self(), "%s", (void *)name); -#else +#elif !defined(__gnu_hurd__) pthread_setname_np(pthread_self(), name); #endif } From bce8469e59ca714c06ea35b6898517f89c35b114 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 4 Sep 2021 08:53:39 -0400 Subject: [PATCH 4413/6300] eliminate extra error message --- libi2pd_client/ClientContext.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 41efa611..a0ebb315 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -755,8 +755,10 @@ namespace client LogPrint(eLogInfo, "Clients: I2P Server Forward created for UDP Endpoint ", host, ":", port, " bound on ", address, " for ",localDestination->GetIdentHash().ToBase32()); } else + { ins.first->second->isUpdated = true; - LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); + LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, " already exists"); + } continue; } From e8f4c42bfb297ec8d5ee64d9889b47685acfbdd6 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 4 Sep 2021 14:01:57 -0400 Subject: [PATCH 4414/6300] moved current language from RouterContext to ClientContext --- daemon/HTTPServer.cpp | 6 +++--- i18n/I18N.h | 10 +++++----- libi2pd/RouterContext.h | 8 -------- libi2pd_client/ClientContext.h | 8 ++++++++ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index ac83f87c..c2aee205 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -240,7 +240,7 @@ namespace http { std::string webroot; i2p::config::GetOption("http.webroot", webroot); // Page language - std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); // get current used language + std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language auto it = i2p::i18n::languages.find(currLang); std::string langCode = it->second.ShortCode; @@ -766,7 +766,7 @@ namespace http { s << " \r\n"; s << "\r\n
    \r\n"; - std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); // get current used language + std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language s << "" << tr("Change language") << "
    \r\n"; s << "
    \r\n"; s << " \r\n"; @@ -1382,7 +1382,7 @@ namespace http { else if (cmd == HTTP_COMMAND_SETLANGUAGE) { std::string lang = params["lang"]; - std::string currLang = i2p::context.GetLanguage ()->GetLanguage(); + std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); if (currLang.compare(lang) != 0) i2p::i18n::SetLanguage(lang); diff --git a/i18n/I18N.h b/i18n/I18N.h index 03add48d..5024fb56 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -9,7 +9,7 @@ #ifndef __I18N_H__ #define __I18N_H__ -#include "RouterContext.h" +#include "ClientContext.h" namespace i2p { @@ -19,19 +19,19 @@ namespace i18n { const auto it = i2p::i18n::languages.find(lang); if (it == i2p::i18n::languages.end()) // fallback - i2p::context.SetLanguage (i2p::i18n::english::GetLocale()); + i2p::client::context.SetLanguage (i2p::i18n::english::GetLocale()); else - i2p::context.SetLanguage (it->second.LocaleFunc()); + i2p::client::context.SetLanguage (it->second.LocaleFunc()); } inline std::string translate (const std::string& arg) { - return i2p::context.GetLanguage ()->GetString (arg); + return i2p::client::context.GetLanguage ()->GetString (arg); } inline std::string translate (const std::string& arg, const std::string& arg2, const int& n) { - return i2p::context.GetLanguage ()->GetPlural (arg, arg2, n); + return i2p::client::context.GetLanguage ()->GetPlural (arg, arg2, n); } } // i18n } // i2p diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 98ee6a72..3535f1ac 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -18,7 +18,6 @@ #include "Identity.h" #include "RouterInfo.h" #include "Garlic.h" -#include "I18N_langs.h" namespace i2p { @@ -146,10 +145,6 @@ namespace garlic void ProcessGarlicMessage (std::shared_ptr msg); void ProcessDeliveryStatusMessage (std::shared_ptr msg); - // i18n - std::shared_ptr GetLanguage () { return m_Language; }; - void SetLanguage (const std::shared_ptr language) { m_Language = language; }; - protected: // implements GarlicDestination @@ -186,9 +181,6 @@ namespace garlic std::unique_ptr m_StaticKeys; // for ECIESx25519 i2p::crypto::NoiseSymmetricState m_InitialNoiseState, m_CurrentNoiseState; - - // i18n - std::shared_ptr m_Language; }; extern RouterContext context; diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 90a21a60..801dc0cd 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -22,6 +22,7 @@ #include "BOB.h" #include "I2CP.h" #include "AddressBook.h" +#include "I18N_langs.h" namespace i2p { @@ -102,6 +103,10 @@ namespace client std::vector > GetForwardInfosFor(const i2p::data::IdentHash & destination); + // i18n + std::shared_ptr GetLanguage () { return m_Language; }; + void SetLanguage (const std::shared_ptr language) { m_Language = language; }; + private: void ReadTunnels (); @@ -149,6 +154,9 @@ namespace client std::unique_ptr m_CleanupUDPTimer; + // i18n + std::shared_ptr m_Language; + public: // for HTTP From 41d6c117ee2063c4328d9e35d77eaaf2016de4e9 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 4 Sep 2021 18:45:32 -0400 Subject: [PATCH 4415/6300] make sure server tunnel is published --- libi2pd/Destination.h | 1 + libi2pd_client/ClientContext.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 9e63d9bb..0dc5450b 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -139,6 +139,7 @@ namespace client void SetLeaseSetUpdated (); bool IsPublic () const { return m_IsPublic; }; + void SetPublic (bool pub) { m_IsPublic = pub; }; protected: diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index a0ebb315..081fa67d 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -713,7 +713,10 @@ namespace client std::shared_ptr localDestination = nullptr; auto it = destinations.find (keys); if (it != destinations.end ()) + { localDestination = it->second; + localDestination->SetPublic (true); + } else { i2p::data::PrivateKeys k; @@ -725,6 +728,8 @@ namespace client localDestination = CreateNewLocalDestination (k, true, &options); destinations[keys] = localDestination; } + else + localDestination->SetPublic (true); } if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { From 3a77e7ba2dc892d75c4a823901aab31324bab86c Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 4 Sep 2021 18:55:51 -0400 Subject: [PATCH 4416/6300] remove dependancy from localization --- tests/Makefile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/Makefile b/tests/Makefile index 89fceeec..8eb52fde 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,7 +1,5 @@ CXXFLAGS += -Wall -Wno-unused-parameter -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -pthread -Wl,--unresolved-symbols=ignore-in-object-files -INCFLAGS += -I../libi2pd -I../i18n - -LOCALESRC = ../i18n/Afrikaans.cpp ../i18n/English.cpp ../i18n/Russian.cpp ../i18n/Turkmen.cpp ../i18n/Ukrainian.cpp ../i18n/Uzbek.cpp +INCFLAGS += -I../libi2pd TESTS = test-gost test-gost-sig test-base-64 test-x25519 test-aeadchacha20poly1305 test-blinding test-elligator @@ -25,7 +23,7 @@ test-x25519: ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp test-aeadchacha20poly1305: ../libi2pd/Crypto.cpp ../libi2pd/ChaCha20.cpp ../libi2pd/Poly1305.cpp test-aeadchacha20poly1305.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system -test-blinding: ../libi2pd/Crypto.cpp ../libi2pd/Blinding.cpp ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/util.cpp ../libi2pd/Identity.cpp ../libi2pd/Signature.cpp ../libi2pd/Timestamp.cpp $(LOCALESRC) test-blinding.cpp +test-blinding: ../libi2pd/Crypto.cpp ../libi2pd/Blinding.cpp ../libi2pd/Ed25519.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Log.cpp ../libi2pd/util.cpp ../libi2pd/Identity.cpp ../libi2pd/Signature.cpp ../libi2pd/Timestamp.cpp test-blinding.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system test-elligator: ../libi2pd/Elligator.cpp ../libi2pd/Crypto.cpp test-elligator.cpp From c76347291429164f977e6e30de5c21804e2e7697 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 5 Sep 2021 08:41:32 -0400 Subject: [PATCH 4417/6300] select ECIES routers only for peer tests and introducers --- libi2pd/NetDb.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 7c473a09..2ac5408c 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -1181,8 +1181,8 @@ namespace data return GetRandomRouter ( [v4, &excluded](std::shared_ptr router)->bool { - return !router->IsHidden () && router->IsPeerTesting (v4) && - !excluded.count (router->GetIdentHash ()); + return !router->IsHidden () && router->IsECIES () && + router->IsPeerTesting (v4) && !excluded.count (router->GetIdentHash ()); }); } @@ -1191,7 +1191,7 @@ namespace data return GetRandomRouter ( [](std::shared_ptr router)->bool { - return !router->IsHidden () && router->IsSSUV6 (); + return !router->IsHidden () && router->IsECIES () && router->IsSSUV6 (); }); } @@ -1200,8 +1200,8 @@ namespace data return GetRandomRouter ( [v4, &excluded](std::shared_ptr router)->bool { - return router->IsIntroducer (v4) && !excluded.count (router->GetIdentHash ()) && - !router->IsHidden () && !router->IsFloodfill (); // floodfills don't send relay tag + return !router->IsHidden () && router->IsECIES () && !router->IsFloodfill () && // floodfills don't send relay tag + router->IsIntroducer (v4) && !excluded.count (router->GetIdentHash ()); }); } From a54b5c18c63d97a2e1f93b5e66b63cdf13cfe84b Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 5 Sep 2021 09:08:29 -0400 Subject: [PATCH 4418/6300] fixed crash --- libi2pd/TunnelPool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index b885f69f..05eed7e5 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -469,7 +469,7 @@ namespace tunnel { LogPrint (eLogInfo, "Tunnels: Can't select first hop for a tunnel. Trying already connected"); hop = i2p::transport::transports.GetRandomPeer (); - if (!hop->IsECIES ()) hop = nullptr; + if (hop && !hop->IsECIES ()) hop = nullptr; } if (!hop) { From 76dca1b46b2b19b2937dc65c2358682891dc483c Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 5 Sep 2021 09:10:13 -0400 Subject: [PATCH 4419/6300] don't handle ElGamal build record --- libi2pd/I2NPProtocol.cpp | 133 +++++++++------------------------------ libi2pd/I2NPProtocol.h | 21 ------- 2 files changed, 29 insertions(+), 125 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 54924451..07ecb19e 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -391,76 +391,48 @@ namespace i2p LogPrint (eLogDebug, "I2NP: Build request record ", i, " is ours"); if (!i2p::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) return false; uint8_t retCode = 0; - bool isECIES = i2p::context.IsECIES (); // replace record to reply if (i2p::context.AcceptsTunnels () && i2p::tunnel::tunnels.GetTransitTunnels ().size () <= g_MaxNumTransitTunnels && !i2p::transport::transports.IsBandwidthExceeded () && !i2p::transport::transports.IsTransitBandwidthExceeded ()) { - auto transitTunnel = isECIES ? - i2p::tunnel::CreateTransitTunnel ( + auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), clearText + ECIES_BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, clearText + ECIES_BUILD_REQUEST_RECORD_IV_KEY_OFFSET, clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, - clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) : - i2p::tunnel::CreateTransitTunnel ( - bufbe32toh (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), - clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, - clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_GATEWAY_FLAG, - clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); + clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); } else retCode = 30; // always reject with bandwidth reason (30) - if (isECIES) - { - memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options - record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; - } - else - { - record[BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; - SHA256 (record + BUILD_RESPONSE_RECORD_PADDING_OFFSET, BUILD_RESPONSE_RECORD_PADDING_SIZE + 1, // + 1 byte of ret - record + BUILD_RESPONSE_RECORD_HASH_OFFSET); - } + memset (record + ECIES_BUILD_RESPONSE_RECORD_OPTIONS_OFFSET, 0, 2); // no options + record[ECIES_BUILD_RESPONSE_RECORD_RET_OFFSET] = retCode; // encrypt reply i2p::crypto::CBCEncryption encryption; for (int j = 0; j < num; j++) { uint8_t * reply = records + j*TUNNEL_BUILD_RECORD_SIZE; - if (isECIES) - { - if (j == i) + if (j == i) + { + uint8_t nonce[12]; + memset (nonce, 0, 12); + auto& noiseState = i2p::context.GetCurrentNoiseState (); + if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, + noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt { - uint8_t nonce[12]; - memset (nonce, 0, 12); - auto& noiseState = i2p::context.GetCurrentNoiseState (); - if (!i2p::crypto::AEADChaCha20Poly1305 (reply, TUNNEL_BUILD_RECORD_SIZE - 16, - noiseState.m_H, 32, noiseState.m_CK, nonce, reply, TUNNEL_BUILD_RECORD_SIZE, true)) // encrypt - { - LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); - return false; - } - } - else - { - encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); - encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); - encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); + LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); + return false; } } else - { - encryption.SetKey (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); - encryption.SetIV (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); + { + encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); + encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); } } @@ -499,75 +471,28 @@ namespace i2p } else { - if (i2p::context.IsECIES ()) + uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + if (HandleBuildRequestRecords (num, buf + 1, clearText)) { - uint8_t clearText[ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - if (HandleBuildRequestRecords (num, buf + 1, clearText)) + if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel { - if (clearText[ECIES_BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel - { - // so we send it to reply tunnel - transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - eI2NPVariableTunnelBuildReply, buf, len, - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - else - transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, - bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); + // so we send it to reply tunnel + transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateTunnelGatewayMsg (bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), + eI2NPVariableTunnelBuildReply, buf, len, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } + else + transports.SendMessage (clearText + ECIES_BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, + CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, + bufbe32toh (clearText + ECIES_BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); } - else - { - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - if (HandleBuildRequestRecords (num, buf + 1, clearText)) - { - if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outboud tunnel - { - // so we send it to reply tunnel - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - eI2NPVariableTunnelBuildReply, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - else - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPVariableTunnelBuild, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - } } } static void HandleTunnelBuildMsg (uint8_t * buf, size_t len) { - if (i2p::context.IsECIES ()) - { - LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router"); - return; - } - if (len < NUM_TUNNEL_BUILD_RECORDS*TUNNEL_BUILD_RECORD_SIZE) - { - LogPrint (eLogError, "I2NP: TunnelBuild message is too short ", len); - return; - } - uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; - if (HandleBuildRequestRecords (NUM_TUNNEL_BUILD_RECORDS, buf, clearText)) - { - if (clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG) // we are endpoint of outbound tunnel - { - // so we send it to reply tunnel - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateTunnelGatewayMsg (bufbe32toh (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), - eI2NPTunnelBuildReply, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } - else - transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, - CreateI2NPMessage (eI2NPTunnelBuild, buf, len, - bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); - } + LogPrint (eLogWarning, "I2NP: TunnelBuild is too old for ECIES router"); } static void HandleTunnelBuildReplyMsg (uint32_t replyMsgID, uint8_t * buf, size_t len, bool isShort) diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index 32aebbb2..f0777ac2 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -57,31 +57,10 @@ namespace i2p const size_t TUNNEL_BUILD_RECORD_SIZE = 528; const size_t SHORT_TUNNEL_BUILD_RECORD_SIZE = 218; - //BuildRequestRecordClearText - const size_t BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; - const size_t BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET = BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; - const size_t BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET = BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET + 4; - const size_t BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET = BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_IV_KEY_OFFSET = BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET = BUILD_REQUEST_RECORD_IV_KEY_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_REPLY_IV_OFFSET = BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET + 32; - const size_t BUILD_REQUEST_RECORD_FLAG_OFFSET = BUILD_REQUEST_RECORD_REPLY_IV_OFFSET + 16; - const size_t BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET = BUILD_REQUEST_RECORD_FLAG_OFFSET + 1; - const size_t BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET = BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET + 4; - const size_t BUILD_REQUEST_RECORD_PADDING_OFFSET = BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET + 4; - const size_t BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE = 222; - // BuildRequestRecordEncrypted const size_t BUILD_REQUEST_RECORD_TO_PEER_OFFSET = 0; const size_t BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET = BUILD_REQUEST_RECORD_TO_PEER_OFFSET + 16; - // BuildResponseRecord - const size_t BUILD_RESPONSE_RECORD_HASH_OFFSET = 0; - const size_t BUILD_RESPONSE_RECORD_PADDING_OFFSET = 32; - const size_t BUILD_RESPONSE_RECORD_PADDING_SIZE = 495; - const size_t BUILD_RESPONSE_RECORD_RET_OFFSET = BUILD_RESPONSE_RECORD_PADDING_OFFSET + BUILD_RESPONSE_RECORD_PADDING_SIZE; - // ECIES BuildRequestRecordClearText const size_t ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET = 0; const size_t ECIES_BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET = ECIES_BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET + 4; From 292fe94352d4b55fed148edb6a37be87717b5b7b Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 5 Sep 2021 11:16:41 -0400 Subject: [PATCH 4420/6300] RouterContext is always ECIES --- libi2pd/I2NPProtocol.cpp | 5 ---- libi2pd/RouterContext.cpp | 56 +++++++++++++-------------------------- libi2pd/RouterContext.h | 1 - 3 files changed, 18 insertions(+), 44 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 07ecb19e..5a2fa9c1 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -528,11 +528,6 @@ namespace i2p static void HandleShortTunnelBuildMsg (uint32_t replyMsgID, uint8_t * buf, size_t len) { - if (!i2p::context.IsECIES ()) - { - LogPrint (eLogWarning, "I2NP: ShortTunnelBuild can be handled by ECIES router only"); - return; - } int num = buf[0]; LogPrint (eLogDebug, "I2NP: ShortTunnelBuild ", num, " records"); if (len < num*SHORT_TUNNEL_BUILD_RECORD_SIZE + 1) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index 1a2dd335..3710092b 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -43,11 +43,8 @@ namespace i2p m_Decryptor = m_Keys.CreateDecryptor (nullptr); m_TunnelDecryptor = m_Keys.CreateDecryptor (nullptr); UpdateRouterInfo (); - if (IsECIES ()) - { - i2p::crypto::InitNoiseNState (m_InitialNoiseState, GetIdentity ()->GetEncryptionPublicKey ()); - m_ECIESSession = std::make_shared(m_InitialNoiseState); - } + i2p::crypto::InitNoiseNState (m_InitialNoiseState, GetIdentity ()->GetEncryptionPublicKey ()); + m_ECIESSession = std::make_shared(m_InitialNoiseState); } void RouterContext::CreateNewRouter () @@ -833,27 +830,22 @@ namespace i2p void RouterContext::ProcessGarlicMessage (std::shared_ptr msg) { std::unique_lock l(m_GarlicMutex); - if (IsECIES ()) + uint8_t * buf = msg->GetPayload (); + uint32_t len = bufbe32toh (buf); + if (len > msg->GetLength ()) { - uint8_t * buf = msg->GetPayload (); - uint32_t len = bufbe32toh (buf); - if (len > msg->GetLength ()) - { - LogPrint (eLogWarning, "Router: garlic message length ", len, " exceeds I2NP message length ", msg->GetLength ()); - return; - } - buf += 4; - if (!HandleECIESx25519TagMessage (buf, len)) // try tag first - { - // then Noise_N one-time decryption - if (m_ECIESSession) - m_ECIESSession->HandleNextMessage (buf, len); - else - LogPrint (eLogError, "Router: Session is not set for ECIES router"); - } + LogPrint (eLogWarning, "Router: garlic message length ", len, " exceeds I2NP message length ", msg->GetLength ()); + return; } - else - i2p::garlic::GarlicDestination::ProcessGarlicMessage (msg); + buf += 4; + if (!HandleECIESx25519TagMessage (buf, len)) // try tag first + { + // then Noise_N one-time decryption + if (m_ECIESSession) + m_ECIESSession->HandleNextMessage (buf, len); + else + LogPrint (eLogError, "Router: Session is not set for ECIES router"); + } } void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr msg) @@ -885,13 +877,7 @@ namespace i2p bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data) { - if (IsECIES ()) - return DecryptECIESTunnelBuildRecord (encrypted, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE); - else - { - LogPrint (eLogError, "Router: Non-ECIES router is not longer supported"); - return false; - } + return DecryptECIESTunnelBuildRecord (encrypted, data, ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE); } bool RouterContext::DecryptECIESTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, size_t clearTextSize) @@ -921,13 +907,7 @@ namespace i2p bool RouterContext::DecryptTunnelShortRequestRecord (const uint8_t * encrypted, uint8_t * data) { - if (IsECIES ()) - return DecryptECIESTunnelBuildRecord (encrypted, data, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE); - else - { - LogPrint (eLogError, "Router: Can't decrypt short request record on non-ECIES router"); - return false; - } + return DecryptECIESTunnelBuildRecord (encrypted, data, SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE); } i2p::crypto::X25519Keys& RouterContext::GetStaticKeys () diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 3535f1ac..647c1a7f 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -123,7 +123,6 @@ namespace garlic void SetSupportsV6 (bool supportsV6); void SetSupportsV4 (bool supportsV4); void SetSupportsMesh (bool supportsmesh, const boost::asio::ip::address_v6& host); - bool IsECIES () const { return GetIdentity ()->GetCryptoKeyType () == i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD; }; i2p::crypto::NoiseSymmetricState& GetCurrentNoiseState () { return m_CurrentNoiseState; }; void UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from Daemon. TODO: remove From 20652f799569039470416e8997d6a25892cb4734 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 9 Sep 2021 15:12:53 -0400 Subject: [PATCH 4421/6300] resseed if too few floodfills --- libi2pd/NetDb.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 2ac5408c..9c079d57 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -55,8 +55,10 @@ namespace data Load (); uint16_t threshold; i2p::config::GetOption("reseed.threshold", threshold); - if (m_RouterInfos.size () < threshold) // reseed if # of router less than threshold + if (m_RouterInfos.size () < threshold || m_Floodfills.size () < NETDB_MIN_FLOODFILLS) // reseed if # of router less than threshold or too few floodfiils + { Reseed (); + } else if (!GetRandomRouter (i2p::context.GetSharedRouterInfo (), false)) Reseed (); // we don't have a router we can connect to. Trying to reseed From ad036de69d3009bf55efce21aa083c3c72529a10 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 9 Sep 2021 21:19:52 -0400 Subject: [PATCH 4422/6300] eliminate allocation of m_ExtendedBuffer --- libi2pd/Identity.cpp | 33 ++++++++++++--------------------- libi2pd/Identity.h | 5 +++-- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index 9dfaa1fc..aa03bfb7 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -42,7 +42,7 @@ namespace data } IdentityEx::IdentityEx (): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0) { } @@ -119,11 +119,15 @@ namespace data m_StandardIdentity.certificate[0] = CERTIFICATE_TYPE_KEY; htobe16buf (m_StandardIdentity.certificate + 1, m_ExtendedLen); // fill extended buffer - m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; htobe16buf (m_ExtendedBuffer, type); htobe16buf (m_ExtendedBuffer + 2, cryptoType); if (excessLen && excessBuf) { + if (excessLen > MAX_EXTENDED_BUFFER_SIZE - 4) + { + LogPrint (eLogError, "Identity: Unexpected excessive signing key len ", excessLen); + excessLen = MAX_EXTENDED_BUFFER_SIZE - 4; + } memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen); delete[] excessBuf; } @@ -136,7 +140,6 @@ namespace data memset (m_StandardIdentity.certificate, 0, sizeof (m_StandardIdentity.certificate)); m_IdentHash = m_StandardIdentity.Hash (); m_ExtendedLen = 0; - m_ExtendedBuffer = nullptr; } CreateVerifier (); } @@ -154,26 +157,25 @@ namespace data } IdentityEx::IdentityEx (const uint8_t * buf, size_t len): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0) { FromBuffer (buf, len); } IdentityEx::IdentityEx (const IdentityEx& other): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0) { *this = other; } IdentityEx::IdentityEx (const Identity& standard): - m_ExtendedLen (0), m_ExtendedBuffer (nullptr) + m_ExtendedLen (0) { *this = standard; } IdentityEx::~IdentityEx () { - delete[] m_ExtendedBuffer; delete m_Verifier; } @@ -182,15 +184,12 @@ namespace data memcpy (&m_StandardIdentity, &other.m_StandardIdentity, DEFAULT_IDENTITY_SIZE); m_IdentHash = other.m_IdentHash; - delete[] m_ExtendedBuffer; m_ExtendedLen = other.m_ExtendedLen; if (m_ExtendedLen > 0) { - m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE; memcpy (m_ExtendedBuffer, other.m_ExtendedBuffer, m_ExtendedLen); } - else - m_ExtendedBuffer = nullptr; delete m_Verifier; m_Verifier = nullptr; @@ -203,8 +202,6 @@ namespace data m_StandardIdentity = standard; m_IdentHash = m_StandardIdentity.Hash (); - delete[] m_ExtendedBuffer; - m_ExtendedBuffer = nullptr; m_ExtendedLen = 0; delete m_Verifier; @@ -222,15 +219,12 @@ namespace data } memcpy (&m_StandardIdentity, buf, DEFAULT_IDENTITY_SIZE); - if(m_ExtendedBuffer) delete[] m_ExtendedBuffer; - m_ExtendedBuffer = nullptr; - m_ExtendedLen = bufbe16toh (m_StandardIdentity.certificate + 1); if (m_ExtendedLen) { if (m_ExtendedLen + DEFAULT_IDENTITY_SIZE <= len) { - m_ExtendedBuffer = new uint8_t[m_ExtendedLen]; + if (m_ExtendedLen > MAX_EXTENDED_BUFFER_SIZE) m_ExtendedLen = MAX_EXTENDED_BUFFER_SIZE; memcpy (m_ExtendedBuffer, buf + DEFAULT_IDENTITY_SIZE, m_ExtendedLen); } else @@ -241,10 +235,7 @@ namespace data } } else - { m_ExtendedLen = 0; - m_ExtendedBuffer = nullptr; - } SHA256(buf, GetFullLen (), m_IdentHash); delete m_Verifier; @@ -258,7 +249,7 @@ namespace data const size_t fullLen = GetFullLen(); if (fullLen > len) return 0; // buffer is too small and may overflow somewhere else memcpy (buf, &m_StandardIdentity, DEFAULT_IDENTITY_SIZE); - if (m_ExtendedLen > 0 && m_ExtendedBuffer) + if (m_ExtendedLen > 0) memcpy (buf + DEFAULT_IDENTITY_SIZE, m_ExtendedBuffer, m_ExtendedLen); return fullLen; } diff --git a/libi2pd/Identity.h b/libi2pd/Identity.h index b52f36cf..f96d5f41 100644 --- a/libi2pd/Identity.h +++ b/libi2pd/Identity.h @@ -84,8 +84,9 @@ namespace data typedef uint16_t SigningKeyType; typedef uint16_t CryptoKeyType; + const size_t MAX_EXTENDED_BUFFER_SIZE = 8; // cryptoKeyType + signingKeyType + 4 extra bytes of P521 class IdentityEx - { + { public: IdentityEx (); @@ -137,7 +138,7 @@ namespace data mutable i2p::crypto::Verifier * m_Verifier = nullptr; mutable std::mutex m_VerifierMutex; size_t m_ExtendedLen; - uint8_t * m_ExtendedBuffer; + uint8_t m_ExtendedBuffer[MAX_EXTENDED_BUFFER_SIZE]; }; class PrivateKeys // for eepsites From 5e2e1a1e3d08e563b9d4db656c17325507df1de9 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 10 Sep 2021 19:57:38 -0400 Subject: [PATCH 4423/6300] don't include old tunnel to LeaseSet if recreated --- libi2pd/Tunnel.cpp | 6 +++--- libi2pd/Tunnel.h | 6 +++--- libi2pd/TunnelPool.cpp | 13 +++++++++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index e7b38686..76504e1f 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -692,7 +692,7 @@ namespace tunnel // let it die if the tunnel pool has been reconfigured and this is old if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops()) { - tunnel->SetIsRecreated (); + tunnel->SetRecreated (true); pool->RecreateOutboundTunnel (tunnel); } } @@ -746,7 +746,7 @@ namespace tunnel // let it die if the tunnel pool was reconfigured and has different number of hops if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops()) { - tunnel->SetIsRecreated (); + tunnel->SetRecreated (true); pool->RecreateInboundTunnel (tunnel); } } diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index acfa21e8..a1fd247a 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -76,7 +76,7 @@ namespace tunnel bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; bool IsFailed () const { return m_State == eTunnelStateFailed; }; bool IsRecreated () const { return m_IsRecreated; }; - void SetIsRecreated () { m_IsRecreated = true; }; + void SetRecreated (bool recreated) { m_IsRecreated = recreated; }; int GetNumHops () const { return m_Hops.size (); }; virtual bool IsInbound() const = 0; @@ -111,7 +111,7 @@ namespace tunnel std::vector > m_Hops; std::shared_ptr m_Pool; // pool, tunnel belongs to, or null TunnelState m_State; - bool m_IsRecreated; + bool m_IsRecreated; // if tunnel is replaced by new, or new tunnel requested to replace uint64_t m_Latency; // in milliseconds }; diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 05eed7e5..7a5721fb 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -113,6 +113,17 @@ namespace tunnel if (!m_IsActive) return; { std::unique_lock l(m_InboundTunnelsMutex); + if (createdTunnel->IsRecreated ()) + { + // find and mark old tunnel as expired + createdTunnel->SetRecreated (false); + for (auto& it: m_InboundTunnels) + if (it->IsRecreated () && it->GetNextIdentHash () == createdTunnel->GetNextIdentHash ()) + { + it->SetState (eTunnelStateExpiring); + break; + } + } m_InboundTunnels.insert (createdTunnel); } if (m_LocalDestination) @@ -577,6 +588,8 @@ namespace tunnel auto newTunnel = tunnels.CreateInboundTunnel (config, shared_from_this(), outboundTunnel); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); + else + newTunnel->SetRecreated (true); } } From e054c6e82caa489285f8abb9a3165930272d7478 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 11 Sep 2021 18:58:27 -0400 Subject: [PATCH 4424/6300] memory pool for SSU messages and fragments --- libi2pd/SSU.cpp | 5 +++++ libi2pd/SSU.h | 11 ++++++++++- libi2pd/SSUData.cpp | 20 ++++++++++---------- libi2pd/SSUData.h | 12 ++++++------ libi2pd/SSUSession.h | 3 ++- libi2pd/util.h | 11 ++++++++--- 6 files changed, 41 insertions(+), 21 deletions(-) diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index f3da611c..a4fec82e 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -919,6 +919,11 @@ namespace transport } if (numDeleted > 0) LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired"); + // some cleaups. TODO: use separate timer + m_FragmentsPool.CleanUp (); + m_IncompleteMessagesPool.CleanUp (); + m_SentMessagesPool.CleanUp (); + SchedulePeerTestsCleanupTimer (); } } diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h index aad3a384..586d7089 100644 --- a/libi2pd/SSU.h +++ b/libi2pd/SSU.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -18,6 +18,7 @@ #include #include #include "Crypto.h" +#include "util.h" #include "I2PEndian.h" #include "Identity.h" #include "RouterInfo.h" @@ -63,6 +64,10 @@ namespace transport void DeleteAllSessions (); boost::asio::io_service& GetService () { return m_Service; }; + i2p::util::MemoryPool& GetFragmentsPool () { return m_FragmentsPool; }; + i2p::util::MemoryPool& GetIncompleteMessagesPool () { return m_IncompleteMessagesPool; }; + i2p::util::MemoryPool& GetSentMessagesPool () { return m_SentMessagesPool; }; + uint16_t GetPort () const { return m_Endpoint.port (); }; void SetLocalAddress (const boost::asio::ip::address& localAddress); @@ -136,6 +141,10 @@ namespace transport std::map > m_Relays; // we are introducer std::map m_PeerTests; // nonce -> creation time in milliseconds + i2p::util::MemoryPool m_FragmentsPool; + i2p::util::MemoryPool m_IncompleteMessagesPool; + i2p::util::MemoryPool m_SentMessagesPool; + public: // for HTTP only const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 5458cc97..2c6f72e1 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -140,7 +140,7 @@ namespace transport if (bitfield & mask) { if (fragment < numSentFragments) - it->second->fragments[fragment].reset (nullptr); + it->second->fragments[fragment] = nullptr; } fragment++; mask <<= 1; @@ -182,9 +182,9 @@ namespace transport auto msg = NewI2NPShortMessage (); msg->len -= I2NP_SHORT_HEADER_SIZE; it = m_IncompleteMessages.insert (std::make_pair (msgID, - std::unique_ptr(new IncompleteMessage (msg)))).first; + m_Session.GetServer ().GetIncompleteMessagesPool ().AcquireShared (msg))).first; } - std::unique_ptr& incompleteMessage = it->second; + auto& incompleteMessage = it->second; // mark fragment as received if (fragmentNum < 64) incompleteMessage->receivedFragmentsBits |= (0x01 << fragmentNum); @@ -224,8 +224,8 @@ namespace transport { // missing fragment LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); - auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast); - if (incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) + auto savedFragment = m_Session.GetServer ().GetFragmentsPool ().AcquireShared (fragmentNum, buf, fragmentSize, isLast); + if (incompleteMessage->savedFragments.insert (savedFragment).second) incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); else LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); @@ -313,8 +313,8 @@ namespace transport if (m_SentMessages.empty ()) // schedule resend at first message only ScheduleResend (); - auto ret = m_SentMessages.insert (std::make_pair (msgID, std::unique_ptr(new SentMessage))); - std::unique_ptr& sentMessage = ret.first->second; + auto ret = m_SentMessages.insert (std::make_pair (msgID, m_Session.GetServer ().GetSentMessagesPool ().AcquireShared ())); + auto& sentMessage = ret.first->second; if (ret.second) { sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL; @@ -328,7 +328,7 @@ namespace transport uint32_t fragmentNum = 0; while (len > 0 && fragmentNum <= 127) { - Fragment * fragment = new Fragment; + auto fragment = m_Session.GetServer ().GetFragmentsPool ().AcquireShared (); fragment->fragmentNum = fragmentNum; uint8_t * payload = fragment->buf + sizeof (SSUHeader); *payload = DATA_FLAG_WANT_REPLY; // for compatibility @@ -358,7 +358,7 @@ namespace transport size += padding; } fragment->len = size; - fragments.push_back (std::unique_ptr (fragment)); + fragments.push_back (fragment); // encrypt message with session key uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index 902c009a..1eb98b1d 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -64,7 +64,7 @@ namespace transport struct FragmentCmp { - bool operator() (const std::unique_ptr& f1, const std::unique_ptr& f2) const + bool operator() (const std::shared_ptr& f1, const std::shared_ptr& f2) const { return f1->fragmentNum < f2->fragmentNum; }; @@ -76,7 +76,7 @@ namespace transport int nextFragmentNum; uint32_t lastFragmentInsertTime; // in seconds uint64_t receivedFragmentsBits; - std::set, FragmentCmp> savedFragments; + std::set, FragmentCmp> savedFragments; IncompleteMessage (std::shared_ptr m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0), receivedFragmentsBits (0) {}; @@ -85,7 +85,7 @@ namespace transport struct SentMessage { - std::vector > fragments; + std::vector > fragments; uint32_t nextResendTime; // in seconds int numResends; }; @@ -126,8 +126,8 @@ namespace transport private: SSUSession& m_Session; - std::unordered_map > m_IncompleteMessages; - std::unordered_map > m_SentMessages; + std::unordered_map > m_IncompleteMessages; + std::unordered_map > m_SentMessages; std::unordered_set m_ReceivedMessages; boost::asio::deadline_timer m_ResendTimer, m_IncompleteMessagesCleanupTimer; int m_MaxPacketSize, m_PacketSize; diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h index 43d0d595..462afc35 100644 --- a/libi2pd/SSUSession.h +++ b/libi2pd/SSUSession.h @@ -89,7 +89,8 @@ namespace transport void Done (); void Failed (); const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; - + SSUServer& GetServer () { return m_Server; }; + bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; void SendI2NPMessages (const std::vector >& msgs); void SendPeerTest (); // Alice diff --git a/libi2pd/util.h b/libi2pd/util.h index 000cb74e..282ce7aa 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -50,6 +50,11 @@ namespace util MemoryPool (): m_Head (nullptr) {} ~MemoryPool () + { + CleanUp (); + } + + void CleanUp () { while (m_Head) { @@ -57,8 +62,8 @@ namespace util m_Head = static_cast(*(void * *)m_Head); // next ::operator delete ((void *)tmp); } - } - + } + template T * Acquire (TArgs&&... args) { From f7f36568efb3f9b9feffc3d6a6caee9816dd237e Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 12 Sep 2021 14:29:43 -0400 Subject: [PATCH 4425/6300] set gzip compression to false by default --- libi2pd/Streaming.h | 2 +- libi2pd_client/ClientContext.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index 9d206098..51fb03ab 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -261,7 +261,7 @@ namespace stream typedef std::function)> Acceptor; - StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = true); + StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = false); ~StreamingDestination (); void Start (); diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 081fa67d..09516bb9 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -699,7 +699,7 @@ namespace client accessList=section.second.get (I2P_SERVER_TUNNEL_WHITE_LIST, ""); std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); - bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); + bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, false); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); From 247b6a0ed236202eb0d5778ef02d71044c8d3a92 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 13 Sep 2021 13:13:27 -0400 Subject: [PATCH 4426/6300] memory pool for SSU packets --- libi2pd/SSU.cpp | 18 +++++++++--------- libi2pd/SSU.h | 1 + 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index a4fec82e..86e76b8e 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -255,14 +255,14 @@ namespace transport void SSUServer::Receive () { - SSUPacket * packet = new SSUPacket (); + SSUPacket * packet = m_PacketsPool.AcquireMt (); m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet)); } void SSUServer::ReceiveV6 () { - SSUPacket * packet = new SSUPacket (); + SSUPacket * packet = m_PacketsPool.AcquireMt (); m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet)); } @@ -293,7 +293,7 @@ namespace transport { while (moreBytes && packets.size () < 25) { - packet = new SSUPacket (); + packet = m_PacketsPool.AcquireMt (); packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, 0, ec); if (!ec) { @@ -304,7 +304,7 @@ namespace transport else { LogPrint (eLogError, "SSU: receive_from error: code ", ec.value(), ": ", ec.message ()); - delete packet; + m_PacketsPool.ReleaseMt (packet); break; } } @@ -315,7 +315,7 @@ namespace transport } else { - delete packet; + m_PacketsPool.ReleaseMt (packet); if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "SSU: receive error: code ", ecode.value(), ": ", ecode.message ()); @@ -352,7 +352,7 @@ namespace transport { while (moreBytes && packets.size () < 25) { - packet = new SSUPacket (); + packet = m_PacketsPool.AcquireMt (); packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, 0, ec); if (!ec) { @@ -363,7 +363,7 @@ namespace transport else { LogPrint (eLogError, "SSU: v6 receive_from error: code ", ec.value(), ": ", ec.message ()); - delete packet; + m_PacketsPool.ReleaseMt (packet);; break; } } @@ -374,7 +374,7 @@ namespace transport } else { - delete packet; + m_PacketsPool.ReleaseMt (packet); if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "SSU: v6 receive error: code ", ecode.value(), ": ", ecode.message ()); @@ -421,8 +421,8 @@ namespace transport if (session) session->FlushData (); session = nullptr; } - delete packet; } + m_PacketsPool.ReleaseMt (packets); if (session) session->FlushData (); } diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h index 586d7089..b97cacb4 100644 --- a/libi2pd/SSU.h +++ b/libi2pd/SSU.h @@ -144,6 +144,7 @@ namespace transport i2p::util::MemoryPool m_FragmentsPool; i2p::util::MemoryPool m_IncompleteMessagesPool; i2p::util::MemoryPool m_SentMessagesPool; + i2p::util::MemoryPoolMt m_PacketsPool; public: // for HTTP only From ec86c4611d527cf9c9b3e4d9b9db53f7bde8e393 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 10 Sep 2021 05:19:55 +0300 Subject: [PATCH 4427/6300] disable reload checks for UDP tunnels (TODO) Signed-off-by: R4SAS --- libi2pd_client/ClientContext.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 09516bb9..4eab189f 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -963,6 +963,7 @@ namespace client } } + /* // TODO: Write correct UDP tunnels stop for (auto it = m_ClientForwards.begin (); it != m_ClientForwards.end ();) { if(clean && !it->second->isUpdated) { @@ -983,7 +984,7 @@ namespace client it->second->isUpdated = false; it++; } - } + } */ } } } From e5c773a3eb22e95c33a293a8ccdd6bdf4899a607 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 13 Sep 2021 13:27:29 +0300 Subject: [PATCH 4428/6300] [webconsole] move resources to separate header file Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 73 +---------------------------- daemon/HTTPServerResources.h | 89 ++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 71 deletions(-) create mode 100644 daemon/HTTPServerResources.h diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index c2aee205..1e002b1b 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -36,81 +36,12 @@ #include "Win32App.h" #endif -// For image and info +// For image, style and info #include "version.h" +#include "HTTPServerResources.h" namespace i2p { namespace http { - const std::string itoopieFavicon = - "data:image/png;base64," - "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACx" - "jwv8YQUAAAAJcEhZcwAALiIAAC4iAari3ZIAAAAHdElNRQfgCQsUNSZrkhi1AAAAGXRFWHRTb2Z0" - "d2FyZQBwYWludC5uZXQgNC4wLjEyQwRr7AAAAoJJREFUOE9jwAUqi4Q1oEwwcDTV1+5sETaBclGB" - "vb09C5QJB6kWpvFQJoOCeLC5kmjEHCgXE2SlyETLi3h6QrkM4VL+ssWSCZUgtopITLKqaOotRTEn" - "cbAkLqAkGtOqLBLVAWLXyWSVFkkmRiqLxuaqiWb/VBYJMAYrwgckJY25VEUzniqKhjU2y+RtCRSP" - "6lUXy/1jIBV5tlYxZUaFVMq2NInwIi9hO8fSfOEAqDZUoCwal6MulvOvyS7gi69K4j9zxZT/m0ps" - "/28ptvvvquXXryIa7QYMMdTwqi0WNtVi0GIDseXl7TnUxFKfnGlxAGp0+D8j2eH/8Ub7/9e7nf7X" - "+Af/B7rwt6pI0h0l0WhQADOC9DBkhSirpImHNVZKp24ukkyoshGLnN8d5fA/y13t/44Kq/8hlnL/" - "z7fZ/58f6vcxSNpbVUVFhV1RLNBVTsQzVYZPSwhsCAhkiIfpNMrkbO6TLf071Sfk/5ZSi/+7q6z/" - "P5ns+v9mj/P/CpuI/20y+aeNGYxZoVoYGmsF3aFMBAAZlCwftnF9ke3//bU2//fXWP8/UGv731Am" - "+V+DdNblSqnUYqhSTKAiYSOqJBrVqiaa+S3UNPr/gmyH/xuKXf63hnn/B8bIP0UxHfEyyeSNQKVM" - "EB1AEB2twhcTLp+gIBJUoyKasEpVJHmqskh8qryovUG/ffCHHRU2q/Tk/YuB6eGPsbExa7ZkpLu1" - "oLEcVDtuUCgV1w60rQzElpRUE1EVSX0BYidHiInXF4nagNhYQW60EF+ApH1ktni0A1SIITSUgVlZ" - "JHYnlIsfzJjIp9xZKswL5YKBHL+coKJoRDaUSzoozxHVrygQU4JykQADAwAT5b1NHtwZugAAAABJ" - "RU5ErkJggg=="; - - // Bundled style - const std::string internalCSS = - "\r\n"; - - // for external style sheet - std::string externalCSS; - static void LoadExtCSS () { std::stringstream s; diff --git a/daemon/HTTPServerResources.h b/daemon/HTTPServerResources.h new file mode 100644 index 00000000..876948e8 --- /dev/null +++ b/daemon/HTTPServerResources.h @@ -0,0 +1,89 @@ +/* +* Copyright (c) 2013-2021, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef HTTP_SERVER_RESOURCES_H__ +#define HTTP_SERVER_RESOURCES_H__ + +namespace i2p +{ +namespace http +{ + const std::string itoopieFavicon = + "data:image/png;base64," + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACx" + "jwv8YQUAAAAJcEhZcwAALiIAAC4iAari3ZIAAAAHdElNRQfgCQsUNSZrkhi1AAAAGXRFWHRTb2Z0" + "d2FyZQBwYWludC5uZXQgNC4wLjEyQwRr7AAAAoJJREFUOE9jwAUqi4Q1oEwwcDTV1+5sETaBclGB" + "vb09C5QJB6kWpvFQJoOCeLC5kmjEHCgXE2SlyETLi3h6QrkM4VL+ssWSCZUgtopITLKqaOotRTEn" + "cbAkLqAkGtOqLBLVAWLXyWSVFkkmRiqLxuaqiWb/VBYJMAYrwgckJY25VEUzniqKhjU2y+RtCRSP" + "6lUXy/1jIBV5tlYxZUaFVMq2NInwIi9hO8fSfOEAqDZUoCwal6MulvOvyS7gi69K4j9zxZT/m0ps" + "/28ptvvvquXXryIa7QYMMdTwqi0WNtVi0GIDseXl7TnUxFKfnGlxAGp0+D8j2eH/8Ub7/9e7nf7X" + "+Af/B7rwt6pI0h0l0WhQADOC9DBkhSirpImHNVZKp24ukkyoshGLnN8d5fA/y13t/44Kq/8hlnL/" + "z7fZ/58f6vcxSNpbVUVFhV1RLNBVTsQzVYZPSwhsCAhkiIfpNMrkbO6TLf071Sfk/5ZSi/+7q6z/" + "P5ns+v9mj/P/CpuI/20y+aeNGYxZoVoYGmsF3aFMBAAZlCwftnF9ke3//bU2//fXWP8/UGv731Am" + "+V+DdNblSqnUYqhSTKAiYSOqJBrVqiaa+S3UNPr/gmyH/xuKXf63hnn/B8bIP0UxHfEyyeSNQKVM" + "EB1AEB2twhcTLp+gIBJUoyKasEpVJHmqskh8qryovUG/ffCHHRU2q/Tk/YuB6eGPsbExa7ZkpLu1" + "oLEcVDtuUCgV1w60rQzElpRUE1EVSX0BYidHiInXF4nagNhYQW60EF+ApH1ktni0A1SIITSUgVlZ" + "JHYnlIsfzJjIp9xZKswL5YKBHL+coKJoRDaUSzoozxHVrygQU4JykQADAwAT5b1NHtwZugAAAABJ" + "RU5ErkJggg=="; + + // bundled style sheet + const std::string internalCSS = + "\r\n"; + + // for external style sheet + std::string externalCSS; + +} // http +} // i2p + +#endif /* HTTP_SERVER_RESOURCES_H__ */ From d2faec70be9cfac055c0296e58a8299d47d6d01b Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 14 Sep 2021 14:48:21 +0300 Subject: [PATCH 4429/6300] [gzip] do not initialize deflator if gzip is not enabled for tunnel Signed-off-by: R4SAS --- libi2pd/Datagram.cpp | 14 ++++++-- libi2pd/Datagram.h | 4 +-- libi2pd/I2NPProtocol.cpp | 72 ++++++++++++++++++++-------------------- libi2pd/Streaming.cpp | 14 +++++--- libi2pd/Streaming.h | 2 +- 5 files changed, 60 insertions(+), 46 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index d000a9e0..627c0481 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -21,6 +21,9 @@ namespace datagram DatagramDestination::DatagramDestination (std::shared_ptr owner, bool gzip): m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr), m_Gzip (gzip) { + if (m_Gzip) + m_Deflator.reset (new i2p::data::GzipDeflator); + auto identityLen = m_Owner->GetIdentity ()->GetFullLen (); m_From.resize (identityLen); m_Owner->GetIdentity ()->ToBuffer (m_From.data (), identityLen); @@ -152,11 +155,16 @@ namespace datagram const std::vector >& payloads, uint16_t fromPort, uint16_t toPort, bool isRaw, bool checksum) { + size_t size; auto msg = m_I2NPMsgsPool.AcquireShared (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for length - size_t size = m_Gzip ? m_Deflator.Deflate (payloads, buf, msg->maxLen - msg->len) : - i2p::data::GzipNoCompression (payloads, buf, msg->maxLen - msg->len); + + if (m_Gzip && m_Deflator) + size = m_Deflator->Deflate (payloads, buf, msg->maxLen - msg->len); + else + size = i2p::data::GzipNoCompression (payloads, buf, msg->maxLen - msg->len); + if (size) { htobe32buf (msg->GetPayload (), size); // length diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index 5dd6c8b6..564eb10b 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -164,7 +164,7 @@ namespace datagram std::map m_ReceiversByPorts; i2p::data::GzipInflator m_Inflator; - i2p::data::GzipDeflator m_Deflator; + std::unique_ptr m_Deflator; std::vector m_From, m_Signature; i2p::util::MemoryPool > m_I2NPMsgsPool; }; diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 5a2fa9c1..29d68b3c 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -40,7 +40,7 @@ namespace i2p { I2NPMessage * msg = nullptr; if (endpoint) - { + { // should fit two tunnel message + tunnel gateway header, enough for one garlic encrypted streaming packet msg = new I2NPMessageBuffer<2*i2p::tunnel::TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE + 28>(); // reserved for alignment and NTCP 16 + 6 + 6 msg->Align (6); @@ -50,7 +50,7 @@ namespace i2p { msg = new I2NPMessageBuffer(); // reserved for alignment and NTCP 16 + 6 + 12 msg->Align (12); - } + } return std::shared_ptr(msg); } @@ -222,12 +222,12 @@ namespace i2p { memcpy (buf + 33, replyTag, 8); // 8 bytes tag buf += 41; - } - else - { + } + else + { memcpy (buf + 33, replyTag, 32); // 32 bytes tag buf += 65; - } + } m->len += (buf - m->GetPayload ()); m->FillI2NPMessageHeader (eI2NPDatabaseLookup); @@ -267,7 +267,7 @@ namespace i2p LogPrint (eLogError, "I2NP: Invalid RouterInfo buffer for DatabaseStore"); return nullptr; } - + auto m = NewI2NPShortMessage (); uint8_t * payload = m->GetPayload (); @@ -285,12 +285,12 @@ namespace i2p buf += 32; // reply tunnel gateway } else - { + { memset (buf, 0, 4); // zero tunnelID means direct reply buf += 4; memcpy (buf, context.GetIdentHash (), 32); buf += 32; - } + } } uint8_t * sizePtr = buf; @@ -303,7 +303,7 @@ namespace i2p { i2p::data::GzipDeflator deflator; size = deflator.Deflate (router->GetBuffer (), router->GetBufferLen (), buf, m->maxLen -m->len); - } + } if (size) { htobe16buf (sizePtr, size); // size @@ -427,14 +427,14 @@ namespace i2p { LogPrint (eLogWarning, "I2NP: Reply AEAD encryption failed"); return false; - } + } } else - { + { encryption.SetKey (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET); - encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); + encryption.SetIV (clearText + ECIES_BUILD_REQUEST_RECORD_REPLY_IV_OFFSET); encryption.Encrypt(reply, TUNNEL_BUILD_RECORD_SIZE, reply); - } + } } return true; } @@ -561,7 +561,7 @@ namespace i2p LogPrint (eLogDebug, "I2NP: Short request record ", i, " is ours"); uint8_t clearText[SHORT_REQUEST_RECORD_CLEAR_TEXT_SIZE]; if (!i2p::context.DecryptTunnelShortRequestRecord (record + SHORT_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText)) - { + { LogPrint (eLogWarning, "I2NP: Can't decrypt short request record ", i); return; } @@ -569,7 +569,7 @@ namespace i2p { LogPrint (eLogWarning, "I2NP: Unknown layer encryption type ", clearText[SHORT_REQUEST_RECORD_LAYER_ENCRYPTION_TYPE], " in short request record"); return; - } + } auto& noiseState = i2p::context.GetCurrentNoiseState (); uint8_t replyKey[32], layerKey[32], ivKey[32]; i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "SMTunnelReplyKey", noiseState.m_CK); @@ -581,7 +581,7 @@ namespace i2p { i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "TunnelLayerIVKey", noiseState.m_CK); memcpy (ivKey, noiseState.m_CK + 32, 32); - } + } else memcpy (ivKey, noiseState.m_CK , 32); @@ -593,7 +593,7 @@ namespace i2p i2p::transport::transports.IsTransitBandwidthExceeded ()) retCode = 30; if (!retCode) - { + { // create new transit tunnel auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( bufbe32toh (clearText + SHORT_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET), @@ -604,13 +604,13 @@ namespace i2p clearText[SHORT_REQUEST_RECORD_FLAG_OFFSET] & TUNNEL_BUILD_RECORD_ENDPOINT_FLAG); i2p::tunnel::tunnels.AddTransitTunnel (transitTunnel); } - + // encrypt reply uint8_t nonce[12]; memset (nonce, 0, 12); uint8_t * reply = buf + 1; for (int j = 0; j < num; j++) - { + { nonce[4] = j; // nonce is record # if (j == i) { @@ -621,29 +621,29 @@ namespace i2p { LogPrint (eLogWarning, "I2NP: Short reply AEAD encryption failed"); return; - } + } } else i2p::crypto::ChaCha20 (reply, SHORT_TUNNEL_BUILD_RECORD_SIZE, replyKey, nonce, reply); - reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; + reply += SHORT_TUNNEL_BUILD_RECORD_SIZE; } // send reply if (isEndpoint) - { + { auto replyMsg = NewI2NPShortMessage (); replyMsg->Concat (buf, len); replyMsg->FillI2NPMessageHeader (eI2NPShortTunnelBuildReply, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET)); if (memcmp ((const uint8_t *)i2p::context.GetIdentHash (), clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, 32)) // reply IBGW is not local? { - i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); + i2p::crypto::HKDF (noiseState.m_CK, nullptr, 0, "RGarlicKeyAndTag", noiseState.m_CK); uint64_t tag; memcpy (&tag, noiseState.m_CK, 8); // we send it to reply tunnel transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateTunnelGatewayMsg (bufbe32toh (clearText + SHORT_REQUEST_RECORD_NEXT_TUNNEL_OFFSET), i2p::garlic::WrapECIESX25519Message (replyMsg, noiseState.m_CK + 32, tag))); - } + } else { // IBGW is local @@ -653,18 +653,18 @@ namespace i2p tunnel->SendTunnelDataMsg (replyMsg); else LogPrint (eLogWarning, "I2NP: Tunnel ", tunnelID, " not found for short tunnel build reply"); - } - } - else + } + } + else transports.SendMessage (clearText + SHORT_REQUEST_RECORD_NEXT_IDENT_OFFSET, CreateI2NPMessage (eI2NPShortTunnelBuild, buf, len, bufbe32toh (clearText + SHORT_REQUEST_RECORD_SEND_MSG_ID_OFFSET))); return; - } + } record += SHORT_TUNNEL_BUILD_RECORD_SIZE; - } - } - + } + } + std::shared_ptr CreateTunnelDataMsg (const uint8_t * buf) { auto msg = NewI2NPTunnelMessage (false); @@ -781,13 +781,13 @@ namespace i2p break; case eI2NPShortTunnelBuild: HandleShortTunnelBuildMsg (msgID, buf, size); - break; + break; case eI2NPVariableTunnelBuildReply: HandleTunnelBuildReplyMsg (msgID, buf, size, false); break; case eI2NPShortTunnelBuildReply: HandleTunnelBuildReplyMsg (msgID, buf, size, true); - break; + break; case eI2NPTunnelBuild: HandleTunnelBuildMsg (buf, size); break; @@ -844,8 +844,8 @@ namespace i2p case eI2NPVariableTunnelBuildReply: case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: - case eI2NPShortTunnelBuild: - case eI2NPShortTunnelBuildReply: + case eI2NPShortTunnelBuild: + case eI2NPShortTunnelBuildReply: // forward to tunnel thread i2p::tunnel::tunnels.PostTunnelData (msg); break; diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index 95f6a150..c6447b5f 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -1040,6 +1040,8 @@ namespace stream m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), m_PendingIncomingTimer (m_Owner->GetService ()) { + if (m_Gzip) + m_Deflator.reset (new i2p::data::GzipDeflator); } StreamingDestination::~StreamingDestination () @@ -1296,13 +1298,17 @@ namespace stream std::shared_ptr StreamingDestination::CreateDataMessage ( const uint8_t * payload, size_t len, uint16_t toPort, bool checksum) { + size_t size; auto msg = m_I2NPMsgsPool.AcquireShared (); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for lengthlength msg->len += 4; - size_t size = (!m_Gzip || len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE)? - i2p::data::GzipNoCompression (payload, len, buf, msg->maxLen - msg->len): - m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); + + if (m_Gzip && m_Deflator) + size = m_Deflator->Deflate (payload, len, buf, msg->maxLen - msg->len); + else + size = i2p::data::GzipNoCompression (payload, len, buf, msg->maxLen - msg->len); + if (size) { htobe32buf (msg->GetPayload (), size); // length diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index 51fb03ab..189b1a64 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -312,7 +312,7 @@ namespace stream public: i2p::data::GzipInflator m_Inflator; - i2p::data::GzipDeflator m_Deflator; + std::unique_ptr m_Deflator; // for HTTP only const decltype(m_Streams)& GetStreams () const { return m_Streams; }; From 3dd9e812968f7f75a4730b1287d42483f8eecd99 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 17 Sep 2021 02:53:30 +0300 Subject: [PATCH 4430/6300] [addressbook] check domain ending when processing subscriptions Signed-off-by: R4SAS --- libi2pd_client/AddressBook.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp index ba2d8276..3d5c83c0 100644 --- a/libi2pd_client/AddressBook.cpp +++ b/libi2pd_client/AddressBook.cpp @@ -470,6 +470,20 @@ namespace client if (pos != std::string::npos) addr = addr.substr(0, pos); // remove comments + pos = name.find(".b32.i2p"); + if (pos != std::string::npos) + { + LogPrint (eLogError, "Addressbook: skipped adding of b32 address: ", name); + continue; + } + + pos = name.find(".i2p"); + if (pos == std::string::npos) + { + LogPrint (eLogError, "Addressbook: malformed domain: ", name); + continue; + } + auto ident = std::make_shared (); if (!ident->FromBase64(addr)) { LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name); From 5b2b9e00a2d5ddb673ea443c73d381251e54e4ea Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 17 Sep 2021 21:52:39 -0400 Subject: [PATCH 4431/6300] reuse receive buffer --- libi2pd/NTCP2.cpp | 35 ++++++++++++++++++++++++++++++----- libi2pd/NTCP2.h | 8 ++++++-- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index ed84b8e7..5b542187 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -332,8 +332,8 @@ namespace transport m_SendMDCtx(nullptr), m_ReceiveMDCtx (nullptr), #endif m_NextReceivedLen (0), m_NextReceivedBuffer (nullptr), m_NextSendBuffer (nullptr), - m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), m_IsSending (false), - m_NextPaddingSize (16) + m_NextReceivedBufferSize (0), m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), + m_IsSending (false), m_IsReceiving (false), m_NextPaddingSize (16) { if (in_RemoteRouter) // Alice { @@ -405,7 +405,30 @@ namespace transport htole64buf (nonce + 4, seqn); } + void NTCP2Session::CreateNextReceivedBuffer (size_t size) + { + if (m_NextReceivedBuffer) + { + if (size <= m_NextReceivedBufferSize) + return; // buffer is good, do nothing + else + delete[] m_NextReceivedBuffer; + } + m_NextReceivedBuffer = new uint8_t[size]; + m_NextReceivedBufferSize = size; + } + void NTCP2Session::DeleteNextReceiveBuffer (uint64_t ts) + { + if (m_NextReceivedBuffer && !m_IsReceiving && + ts > m_LastActivityTimestamp + NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT) + { + delete[] m_NextReceivedBuffer; + m_NextReceivedBuffer = nullptr; + m_NextReceivedBufferSize = 0; + } + } + void NTCP2Session::KeyDerivationFunctionDataPhase () { uint8_t k[64]; @@ -759,8 +782,7 @@ namespace transport LogPrint (eLogDebug, "NTCP2: received length ", m_NextReceivedLen); if (m_NextReceivedLen >= 16) { - if (m_NextReceivedBuffer) delete[] m_NextReceivedBuffer; - m_NextReceivedBuffer = new uint8_t[m_NextReceivedLen]; + CreateNextReceivedBuffer (m_NextReceivedLen); boost::system::error_code ec; size_t moreBytes = m_Socket.available(ec); if (!ec && moreBytes >= m_NextReceivedLen) @@ -787,6 +809,7 @@ namespace transport const int one = 1; setsockopt(m_Socket.native_handle(), IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one)); #endif + m_IsReceiving = true; boost::asio::async_read (m_Socket, boost::asio::buffer(m_NextReceivedBuffer, m_NextReceivedLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } @@ -810,7 +833,7 @@ namespace transport { LogPrint (eLogDebug, "NTCP2: received message decrypted"); ProcessNextFrame (m_NextReceivedBuffer, m_NextReceivedLen-16); - delete[] m_NextReceivedBuffer; m_NextReceivedBuffer = nullptr; // we don't need received buffer anymore + m_IsReceiving = false; ReceiveLength (); } else @@ -1448,6 +1471,8 @@ namespace transport LogPrint (eLogDebug, "NTCP2: No activity for ", session->GetTerminationTimeout (), " seconds"); session->TerminateByTimeout (); // it doesn't change m_NTCP2Session right a way } + else + it.second->DeleteNextReceiveBuffer (ts); // pending for (auto it = m_PendingIncomingSessions.begin (); it != m_PendingIncomingSessions.end ();) { diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index bcee1b09..b54c1634 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -34,6 +34,7 @@ namespace transport const int NTCP2_ESTABLISH_TIMEOUT = 10; // 10 seconds const int NTCP2_TERMINATION_TIMEOUT = 120; // 2 minutes const int NTCP2_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds + const int NTCP2_RECEIVE_BUFFER_DELETION_TIMEOUT = 3; // 3 seconds const int NTCP2_ROUTERINFO_RESEND_INTERVAL = 25*60; // 25 minuntes in seconds const int NTCP2_ROUTERINFO_RESEND_INTERVAL_THRESHOLD = 25*60; // 25 minuntes @@ -132,7 +133,8 @@ namespace transport void TerminateByTimeout (); void Done (); void Close () { m_Socket.close (); }; // for accept - + void DeleteNextReceiveBuffer (uint64_t ts); + boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; const boost::asio::ip::tcp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; void SetRemoteEndpoint (const boost::asio::ip::tcp::endpoint& ep) { m_RemoteEndpoint = ep; }; @@ -151,6 +153,7 @@ namespace transport void Established (); void CreateNonce (uint64_t seqn, uint8_t * nonce); + void CreateNextReceivedBuffer (size_t size); void KeyDerivationFunctionDataPhase (); void SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey); @@ -206,6 +209,7 @@ namespace transport #endif uint16_t m_NextReceivedLen; uint8_t * m_NextReceivedBuffer, * m_NextSendBuffer; + size_t m_NextReceivedBufferSize; union { uint8_t buf[8]; @@ -215,7 +219,7 @@ namespace transport i2p::I2NPMessagesHandler m_Handler; - bool m_IsSending; + bool m_IsSending, m_IsReceiving; std::list > m_SendQueue; uint64_t m_NextRouterInfoResendTime; // seconds since epoch From 317d8cdc48c2e7934fe14406190d23fdb8e3030d Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 18 Sep 2021 15:44:43 -0400 Subject: [PATCH 4432/6300] don't allocate separate buffers for SessionRequest and SessionCreated --- libi2pd/NTCP2.cpp | 18 ++++++------------ libi2pd/NTCP2.h | 9 ++++++--- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 5b542187..05d68203 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -32,14 +32,12 @@ namespace i2p namespace transport { NTCP2Establisher::NTCP2Establisher (): - m_SessionRequestBuffer (nullptr), m_SessionCreatedBuffer (nullptr), m_SessionConfirmedBuffer (nullptr) + m_SessionConfirmedBuffer (nullptr) { } NTCP2Establisher::~NTCP2Establisher () { - delete[] m_SessionRequestBuffer; - delete[] m_SessionCreatedBuffer; delete[] m_SessionConfirmedBuffer; } @@ -112,9 +110,8 @@ namespace transport void NTCP2Establisher::CreateSessionRequestMessage () { // create buffer and fill padding - auto paddingLength = rand () % (287 - 64); // message length doesn't exceed 287 bytes + auto paddingLength = rand () % (NTCP2_SESSION_REQUEST_MAX_SIZE - 64); // message length doesn't exceed 287 bytes m_SessionRequestBufferLen = paddingLength + 64; - m_SessionRequestBuffer = new uint8_t[m_SessionRequestBufferLen]; RAND_bytes (m_SessionRequestBuffer + 64, paddingLength); // encrypt X i2p::crypto::CBCEncryption encryption; @@ -152,9 +149,8 @@ namespace transport void NTCP2Establisher::CreateSessionCreatedMessage () { - auto paddingLen = rand () % (287 - 64); + auto paddingLen = rand () % (NTCP2_SESSION_CREATED_MAX_SIZE - 64); m_SessionCreatedBufferLen = paddingLen + 64; - m_SessionCreatedBuffer = new uint8_t[m_SessionCreatedBufferLen]; RAND_bytes (m_SessionCreatedBuffer + 64, paddingLen); // encrypt Y i2p::crypto::CBCEncryption encryption; @@ -463,7 +459,6 @@ namespace transport } else { - m_Establisher->m_SessionCreatedBuffer = new uint8_t[287]; // TODO: determine actual max size // we receive first 64 bytes (32 Y, and 32 ChaCha/Poly frame) first boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer, 64), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionCreatedReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -486,7 +481,7 @@ namespace transport { if (paddingLen > 0) { - if (paddingLen <= 287 - 64) // session request is 287 bytes max + if (paddingLen <= NTCP2_SESSION_REQUEST_MAX_SIZE - 64) // session request is 287 bytes max { boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer + 64, paddingLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionRequestPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -539,7 +534,7 @@ namespace transport { if (paddingLen > 0) { - if (paddingLen <= 287 - 64) // session created is 287 bytes max + if (paddingLen <= NTCP2_SESSION_CREATED_MAX_SIZE - 64) // session created is 287 bytes max { boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionCreatedBuffer + 64, paddingLen), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionCreatedPaddingReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); @@ -742,7 +737,6 @@ namespace transport void NTCP2Session::ServerLogin () { m_Establisher->CreateEphemeralKey (); - m_Establisher->m_SessionRequestBuffer = new uint8_t[287]; // 287 bytes max for now boost::asio::async_read (m_Socket, boost::asio::buffer(m_Establisher->m_SessionRequestBuffer, 64), boost::asio::transfer_all (), std::bind(&NTCP2Session::HandleSessionRequestReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index b54c1634..f9f3e751 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -28,6 +28,8 @@ namespace transport { const size_t NTCP2_UNENCRYPTED_FRAME_MAX_SIZE = 65519; + const size_t NTCP2_SESSION_REQUEST_MAX_SIZE = 287; + const size_t NTCP2_SESSION_CREATED_MAX_SIZE = 287; const int NTCP2_MAX_PADDING_RATIO = 6; // in % const int NTCP2_CONNECT_TIMEOUT = 5; // 5 seconds @@ -40,7 +42,7 @@ namespace transport const int NTCP2_CLOCK_SKEW = 60; // in seconds const int NTCP2_MAX_OUTGOING_QUEUE_SIZE = 500; // how many messages we can queue up - + enum NTCP2BlockType { eNTCP2BlkDateTime = 0, @@ -116,7 +118,8 @@ namespace transport i2p::data::IdentHash m_RemoteIdentHash; uint16_t m3p2Len; - uint8_t * m_SessionRequestBuffer, * m_SessionCreatedBuffer, * m_SessionConfirmedBuffer; + uint8_t m_SessionRequestBuffer[NTCP2_SESSION_REQUEST_MAX_SIZE], + m_SessionCreatedBuffer[NTCP2_SESSION_CREATED_MAX_SIZE], * m_SessionConfirmedBuffer; size_t m_SessionRequestBufferLen, m_SessionCreatedBufferLen; }; From 31bdce1f1f37d6fafb83cb202b0b6f759b00e470 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 21 Sep 2021 19:01:22 -0400 Subject: [PATCH 4433/6300] cleanup received messages list by timestamp --- libi2pd/SSUData.cpp | 19 +++++++++++++++---- libi2pd/SSUData.h | 3 ++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 2c6f72e1..7d048799 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -246,8 +246,8 @@ namespace transport { if (!m_ReceivedMessages.count (msgID)) { - m_ReceivedMessages.insert (msgID); m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch (); + m_ReceivedMessages.emplace (msgID, m_LastMessageReceivedTime); if (!msg->IsExpired ()) { m_Handler.PutNextMessage (msg); @@ -511,10 +511,21 @@ namespace transport else ++it; } - // decay - if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || - i2p::util::GetSecondsSinceEpoch () > m_LastMessageReceivedTime + DECAY_INTERVAL) + + if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || ts > m_LastMessageReceivedTime + DECAY_INTERVAL) + // decay m_ReceivedMessages.clear (); + else + { + // delete old received messages + for (auto it = m_ReceivedMessages.begin (); it != m_ReceivedMessages.end ();) + { + if (ts > it->second + RECEIVED_MESSAGES_CLEANUP_TIMEOUT) + it = m_ReceivedMessages.erase (it); + else + ++it; + } + } ScheduleIncompleteMessagesCleanup (); } diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index 1eb98b1d..3dcff1d4 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -40,6 +40,7 @@ namespace transport const int MAX_NUM_RESENDS = 5; const int DECAY_INTERVAL = 20; // in seconds const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds + const int RECEIVED_MESSAGES_CLEANUP_TIMEOUT = 40; // in seconds const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check const int MAX_OUTGOING_WINDOW_SIZE = 200; // how many unacked message we can store // data flags @@ -128,7 +129,7 @@ namespace transport SSUSession& m_Session; std::unordered_map > m_IncompleteMessages; std::unordered_map > m_SentMessages; - std::unordered_set m_ReceivedMessages; + std::unordered_map m_ReceivedMessages; // msgID -> timestamp in seconds boost::asio::deadline_timer m_ResendTimer, m_IncompleteMessagesCleanupTimer; int m_MaxPacketSize, m_PacketSize; i2p::I2NPMessagesHandler m_Handler; From 8debdc264c863ed81380b3b2e010ffe1612ad0d2 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 21 Sep 2021 22:13:34 -0400 Subject: [PATCH 4434/6300] use common cleanup timer for all SSU sessions --- libi2pd/SSU.cpp | 4 +++ libi2pd/SSUData.cpp | 63 +++++++++++++++--------------------------- libi2pd/SSUData.h | 10 ++----- libi2pd/SSUSession.cpp | 6 ++++ libi2pd/SSUSession.h | 3 +- 5 files changed, 38 insertions(+), 48 deletions(-) diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index 86e76b8e..7b129811 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -952,6 +952,8 @@ namespace transport session->Failed (); }); } + else + it.second->CleanUp (); ScheduleTermination (); } } @@ -980,6 +982,8 @@ namespace transport session->Failed (); }); } + else + it.second->CleanUp (); ScheduleTerminationV6 (); } } diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 7d048799..0468956b 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -33,7 +33,6 @@ namespace transport SSUData::SSUData (SSUSession& session): m_Session (session), m_ResendTimer (session.GetService ()), - m_IncompleteMessagesCleanupTimer (session.GetService ()), m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE), m_PacketSize (m_MaxPacketSize), m_LastMessageReceivedTime (0) { @@ -45,13 +44,11 @@ namespace transport void SSUData::Start () { - ScheduleIncompleteMessagesCleanup (); } void SSUData::Stop () { m_ResendTimer.cancel (); - m_IncompleteMessagesCleanupTimer.cancel (); m_IncompleteMessages.clear (); m_SentMessages.clear (); m_ReceivedMessages.clear (); @@ -487,48 +484,34 @@ namespace transport } } - void SSUData::ScheduleIncompleteMessagesCleanup () + void SSUData::CleanUp () { - m_IncompleteMessagesCleanupTimer.cancel (); - m_IncompleteMessagesCleanupTimer.expires_from_now (boost::posix_time::seconds(INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT)); - auto s = m_Session.shared_from_this(); - m_IncompleteMessagesCleanupTimer.async_wait ([s](const boost::system::error_code& ecode) - { s->m_Data.HandleIncompleteMessagesCleanupTimer (ecode); }); - } - - void SSUData::HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode) - { - if (ecode != boost::asio::error::operation_aborted) + uint32_t ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) + if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) { - if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) - { - LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); - it = m_IncompleteMessages.erase (it); - } + LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); + it = m_IncompleteMessages.erase (it); + } + else + ++it; + } + + if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || ts > m_LastMessageReceivedTime + DECAY_INTERVAL) + // decay + m_ReceivedMessages.clear (); + else + { + // delete old received messages + for (auto it = m_ReceivedMessages.begin (); it != m_ReceivedMessages.end ();) + { + if (ts > it->second + RECEIVED_MESSAGES_CLEANUP_TIMEOUT) + it = m_ReceivedMessages.erase (it); else ++it; - } - - if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || ts > m_LastMessageReceivedTime + DECAY_INTERVAL) - // decay - m_ReceivedMessages.clear (); - else - { - // delete old received messages - for (auto it = m_ReceivedMessages.begin (); it != m_ReceivedMessages.end ();) - { - if (ts > it->second + RECEIVED_MESSAGES_CLEANUP_TIMEOUT) - it = m_ReceivedMessages.erase (it); - else - ++it; - } } - - ScheduleIncompleteMessagesCleanup (); - } - } + } + } } } diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index 3dcff1d4..590a538c 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include "I2NPProtocol.h" @@ -101,7 +100,8 @@ namespace transport void Start (); void Stop (); - + void CleanUp (); + void ProcessMessage (uint8_t * buf, size_t len); void FlushReceivedMessage (); void Send (std::shared_ptr msg); @@ -120,17 +120,13 @@ namespace transport void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); - void ScheduleIncompleteMessagesCleanup (); - void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode); - - private: SSUSession& m_Session; std::unordered_map > m_IncompleteMessages; std::unordered_map > m_SentMessages; std::unordered_map m_ReceivedMessages; // msgID -> timestamp in seconds - boost::asio::deadline_timer m_ResendTimer, m_IncompleteMessagesCleanupTimer; + boost::asio::deadline_timer m_ResendTimer; int m_MaxPacketSize, m_PacketSize; i2p::I2NPMessagesHandler m_Handler; uint32_t m_LastMessageReceivedTime; // in second diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index 59ee578b..e01b0c0e 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -1004,6 +1004,12 @@ namespace transport } } + void SSUSession::CleanUp () + { + m_Data.CleanUp (); + // TODO: clean up m_RelayRequests + } + void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { uint32_t nonce = bufbe32toh (buf); // 4 bytes diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h index 462afc35..acf232b9 100644 --- a/libi2pd/SSUSession.h +++ b/libi2pd/SSUSession.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -106,6 +106,7 @@ namespace transport void SetCreationTime (uint32_t ts) { m_CreationTime = ts; }; // for introducers void FlushData (); + void CleanUp (); private: From 18b6ba80f2cfaa52c2c5609ac54a1a96360db422 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 22 Sep 2021 19:09:56 -0400 Subject: [PATCH 4435/6300] cleanup RelayRequests --- libi2pd/SSU.cpp | 10 ++++++---- libi2pd/SSUData.cpp | 3 +-- libi2pd/SSUData.h | 2 +- libi2pd/SSUSession.cpp | 17 ++++++++++++----- libi2pd/SSUSession.h | 4 ++-- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index 7b129811..9e814303 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -930,7 +930,8 @@ namespace transport void SSUServer::ScheduleTermination () { - m_TerminationTimer.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT)); + uint64_t timeout = SSU_TERMINATION_CHECK_TIMEOUT + (rand () % SSU_TERMINATION_CHECK_TIMEOUT)/5; + m_TerminationTimer.expires_from_now (boost::posix_time::seconds(timeout)); m_TerminationTimer.async_wait (std::bind (&SSUServer::HandleTerminationTimer, this, std::placeholders::_1)); } @@ -953,14 +954,15 @@ namespace transport }); } else - it.second->CleanUp (); + it.second->CleanUp (ts); ScheduleTermination (); } } void SSUServer::ScheduleTerminationV6 () { - m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT)); + uint64_t timeout = SSU_TERMINATION_CHECK_TIMEOUT + (rand () % SSU_TERMINATION_CHECK_TIMEOUT)/5; + m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(timeout)); m_TerminationTimerV6.async_wait (std::bind (&SSUServer::HandleTerminationTimerV6, this, std::placeholders::_1)); } @@ -983,7 +985,7 @@ namespace transport }); } else - it.second->CleanUp (); + it.second->CleanUp (ts); ScheduleTerminationV6 (); } } diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 0468956b..18621fe0 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -484,9 +484,8 @@ namespace transport } } - void SSUData::CleanUp () + void SSUData::CleanUp (uint64_t ts) { - uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) { if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index 590a538c..35311bb6 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -100,7 +100,7 @@ namespace transport void Start (); void Stop (); - void CleanUp (); + void CleanUp (uint64_t ts); void ProcessMessage (uint8_t * buf, size_t len); void FlushReceivedMessage (); diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index e01b0c0e..3e4eecb8 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -730,7 +730,7 @@ namespace transport (remoteIP.is_v6 () && i2p::context.GetStatusV6 () == eRouterStatusFirewalled)) m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch // we assume that HolePunch has been sent by this time and our SessionRequest will go through - m_Server.CreateDirectSession (it->second, remoteEndpoint, false); + m_Server.CreateDirectSession (it->second.first, remoteEndpoint, false); } // delete request m_RelayRequests.erase (it); @@ -905,7 +905,8 @@ namespace transport } uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); - m_RelayRequests[nonce] = to; + auto ts = i2p::util::GetSecondsSinceEpoch (); + m_RelayRequests.emplace (nonce, std::make_pair (to, ts)); SendRelayRequest (introducer, nonce); } @@ -1004,10 +1005,16 @@ namespace transport } } - void SSUSession::CleanUp () + void SSUSession::CleanUp (uint64_t ts) { - m_Data.CleanUp (); - // TODO: clean up m_RelayRequests + m_Data.CleanUp (ts); + for (auto it = m_RelayRequests.begin (); it != m_RelayRequests.end ();) + { + if (ts > it->second.second + SSU_CONNECT_TIMEOUT) + it = m_RelayRequests.erase (it); + else + ++it; + } } void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h index acf232b9..95697a91 100644 --- a/libi2pd/SSUSession.h +++ b/libi2pd/SSUSession.h @@ -106,7 +106,7 @@ namespace transport void SetCreationTime (uint32_t ts) { m_CreationTime = ts; }; // for introducers void FlushData (); - void CleanUp (); + void CleanUp (uint64_t ts); private: @@ -170,7 +170,7 @@ namespace transport SSUData m_Data; bool m_IsDataReceived; std::unique_ptr m_SignedData; // we need it for SessionConfirmed only - std::map > m_RelayRequests; // nonce->Charlie + std::unordered_map, uint64_t > > m_RelayRequests; // nonce->(Charlie, timestamp) std::shared_ptr m_DHKeysPair; // X - for client and Y - for server }; } From 518e53a61c2491a1f97fcc317aee871f343ece12 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 24 Sep 2021 14:23:39 -0400 Subject: [PATCH 4436/6300] use flat_map for smaller tables --- libi2pd/SSU.cpp | 2 +- libi2pd/SSU.h | 2 +- libi2pd/SSUData.cpp | 2 +- libi2pd/SSUData.h | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libi2pd/SSU.cpp b/libi2pd/SSU.cpp index 9e814303..be263370 100644 --- a/libi2pd/SSU.cpp +++ b/libi2pd/SSU.cpp @@ -218,7 +218,7 @@ namespace transport void SSUServer::AddRelay (uint32_t tag, std::shared_ptr relay) { - m_Relays[tag] = relay; + m_Relays.emplace (tag, relay); } void SSUServer::RemoveRelay (uint32_t tag) diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h index b97cacb4..4b561be0 100644 --- a/libi2pd/SSU.h +++ b/libi2pd/SSU.h @@ -139,7 +139,7 @@ namespace transport std::list m_Introducers, m_IntroducersV6; // introducers we are connected to std::map > m_Sessions, m_SessionsV6; std::map > m_Relays; // we are introducer - std::map m_PeerTests; // nonce -> creation time in milliseconds + boost::container::flat_map m_PeerTests; // nonce -> creation time in milliseconds i2p::util::MemoryPool m_FragmentsPool; i2p::util::MemoryPool m_IncompleteMessagesPool; diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 18621fe0..ccc89b41 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -310,7 +310,7 @@ namespace transport if (m_SentMessages.empty ()) // schedule resend at first message only ScheduleResend (); - auto ret = m_SentMessages.insert (std::make_pair (msgID, m_Session.GetServer ().GetSentMessagesPool ().AcquireShared ())); + auto ret = m_SentMessages.emplace (msgID, m_Session.GetServer ().GetSentMessagesPool ().AcquireShared ()); auto& sentMessage = ret.first->second; if (ret.second) { diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index 35311bb6..fa6aa92c 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -11,10 +11,10 @@ #include #include -#include #include #include #include +#include #include "I2NPProtocol.h" #include "Identity.h" #include "RouterInfo.h" @@ -123,9 +123,9 @@ namespace transport private: SSUSession& m_Session; - std::unordered_map > m_IncompleteMessages; - std::unordered_map > m_SentMessages; - std::unordered_map m_ReceivedMessages; // msgID -> timestamp in seconds + boost::container::flat_map > m_IncompleteMessages; + boost::container::flat_map > m_SentMessages; + boost::container::flat_map m_ReceivedMessages; // msgID -> timestamp in seconds boost::asio::deadline_timer m_ResendTimer; int m_MaxPacketSize, m_PacketSize; i2p::I2NPMessagesHandler m_Handler; From b9dd4aee8d58de3051a0883a2484555227541b74 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 24 Sep 2021 19:12:12 -0400 Subject: [PATCH 4437/6300] use flat_map for incompete messages --- libi2pd/TunnelEndpoint.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libi2pd/TunnelEndpoint.h b/libi2pd/TunnelEndpoint.h index f9878165..aa87537a 100644 --- a/libi2pd/TunnelEndpoint.h +++ b/libi2pd/TunnelEndpoint.h @@ -10,9 +10,9 @@ #define TUNNEL_ENDPOINT_H__ #include -#include #include #include +#include #include "I2NPProtocol.h" #include "TunnelBase.h" @@ -59,8 +59,8 @@ namespace tunnel private: - std::unordered_map m_IncompleteMessages; - std::unordered_map > m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment + boost::container::flat_map m_IncompleteMessages; + boost::container::flat_map > m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment bool m_IsInbound; size_t m_NumReceivedBytes; TunnelMessageBlockEx m_CurrentMessage; From 1bb1d89fab14577be5fe1d33eaab630d05ede136 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 25 Sep 2021 18:30:17 -0400 Subject: [PATCH 4438/6300] change back to map and unodered_map --- libi2pd/SSU.h | 2 +- libi2pd/SSUData.h | 9 +++++---- libi2pd/SSUSession.h | 2 +- libi2pd/TunnelEndpoint.h | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/libi2pd/SSU.h b/libi2pd/SSU.h index 4b561be0..b97cacb4 100644 --- a/libi2pd/SSU.h +++ b/libi2pd/SSU.h @@ -139,7 +139,7 @@ namespace transport std::list m_Introducers, m_IntroducersV6; // introducers we are connected to std::map > m_Sessions, m_SessionsV6; std::map > m_Relays; // we are introducer - boost::container::flat_map m_PeerTests; // nonce -> creation time in milliseconds + std::map m_PeerTests; // nonce -> creation time in milliseconds i2p::util::MemoryPool m_FragmentsPool; i2p::util::MemoryPool m_IncompleteMessagesPool; diff --git a/libi2pd/SSUData.h b/libi2pd/SSUData.h index fa6aa92c..8fe33a33 100644 --- a/libi2pd/SSUData.h +++ b/libi2pd/SSUData.h @@ -12,9 +12,10 @@ #include #include #include +#include +#include #include #include -#include #include "I2NPProtocol.h" #include "Identity.h" #include "RouterInfo.h" @@ -123,9 +124,9 @@ namespace transport private: SSUSession& m_Session; - boost::container::flat_map > m_IncompleteMessages; - boost::container::flat_map > m_SentMessages; - boost::container::flat_map m_ReceivedMessages; // msgID -> timestamp in seconds + std::map > m_IncompleteMessages; + std::map > m_SentMessages; + std::unordered_map m_ReceivedMessages; // msgID -> timestamp in seconds boost::asio::deadline_timer m_ResendTimer; int m_MaxPacketSize, m_PacketSize; i2p::I2NPMessagesHandler m_Handler; diff --git a/libi2pd/SSUSession.h b/libi2pd/SSUSession.h index 95697a91..bd4b4b93 100644 --- a/libi2pd/SSUSession.h +++ b/libi2pd/SSUSession.h @@ -170,7 +170,7 @@ namespace transport SSUData m_Data; bool m_IsDataReceived; std::unique_ptr m_SignedData; // we need it for SessionConfirmed only - std::unordered_map, uint64_t > > m_RelayRequests; // nonce->(Charlie, timestamp) + std::map, uint64_t > > m_RelayRequests; // nonce->(Charlie, timestamp) std::shared_ptr m_DHKeysPair; // X - for client and Y - for server }; } diff --git a/libi2pd/TunnelEndpoint.h b/libi2pd/TunnelEndpoint.h index aa87537a..e5a6bbc8 100644 --- a/libi2pd/TunnelEndpoint.h +++ b/libi2pd/TunnelEndpoint.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include "I2NPProtocol.h" #include "TunnelBase.h" @@ -59,8 +59,8 @@ namespace tunnel private: - boost::container::flat_map m_IncompleteMessages; - boost::container::flat_map > m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment + std::unordered_map m_IncompleteMessages; + std::unordered_map > m_OutOfSequenceFragments; // ((msgID << 8) + fragment#)->fragment bool m_IsInbound; size_t m_NumReceivedBytes; TunnelMessageBlockEx m_CurrentMessage; From b10e5ce35819397d105b122396ce4c6f8cfc7716 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 26 Sep 2021 11:20:20 -0400 Subject: [PATCH 4439/6300] send ping --- libi2pd/Streaming.cpp | 51 ++++++++++++++++++++++++++++++++++++++++++- libi2pd/Streaming.h | 4 +++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index c6447b5f..be3d09d8 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -490,7 +490,7 @@ namespace stream handler(boost::system::error_code ()); m_Service.post (std::bind (&Stream::SendBuffer, shared_from_this ())); } - + void Stream::SendBuffer () { int numMsgs = m_WindowSize - m_SentPackets.size (); @@ -665,6 +665,42 @@ namespace stream LogPrint (eLogDebug, "Streaming: Quick Ack sent. ", (int)numNacks, " NACKs"); } + void Stream::SendPing () + { + Packet p; + uint8_t * packet = p.GetBuffer (); + size_t size = 0; + htobe32buf (packet, m_RecvStreamID); + size += 4; // sendStreamID + memset (packet + size, 0, 14); + size += 14; // all zeroes + uint16_t flags = PACKET_FLAG_ECHO | PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_FROM_INCLUDED; + bool isOfflineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().IsOfflineSignature (); + if (isOfflineSignature) flags |= PACKET_FLAG_OFFLINE_SIGNATURE; + htobe16buf (packet + size, flags); + size += 2; // flags + size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen (); + size_t signatureLen = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetSignatureLen (); + uint8_t * optionsSize = packet + size; // set options size later + size += 2; // options size + m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen); + size += identityLen; // from + if (isOfflineSignature) + { + const auto& offlineSignature = m_LocalDestination.GetOwner ()->GetPrivateKeys ().GetOfflineSignature (); + memcpy (packet + size, offlineSignature.data (), offlineSignature.size ()); + size += offlineSignature.size (); // offline signature + } + uint8_t * signature = packet + size; // set it later + memset (signature, 0, signatureLen); // zeroes for now + size += signatureLen; // signature + htobe16buf (optionsSize, packet + size - 2 - optionsSize); // actual options size + m_LocalDestination.GetOwner ()->Sign (packet, size, signature); + p.len = size; + SendPackets (std::vector { &p }); + LogPrint (eLogDebug, "Streaming: Ping of ", p.len, " bytes sent"); + } + void Stream::Close () { LogPrint(eLogDebug, "Streaming: closing stream with sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ", status=", m_Status); @@ -1103,6 +1139,13 @@ namespace stream } else { + if (packet->IsEcho ()) + { + // pong + LogPrint (eLogInfo, "Streaming: Pong received rSID=", packet->GetReceiveStreamID ()); + DeletePacket (packet); + return; + } if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream { uint32_t receiveStreamID = packet->GetReceiveStreamID (); @@ -1197,6 +1240,12 @@ namespace stream return s; } + void StreamingDestination::SendPing (std::shared_ptr remote) + { + auto s = std::make_shared (m_Owner->GetService (), *this, remote, 0); + s->SendPing (); + } + std::shared_ptr StreamingDestination::CreateNewIncomingStream (uint32_t receiveStreamID) { auto s = std::make_shared (m_Owner->GetService (), *this); diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h index 189b1a64..c13dcab1 100644 --- a/libi2pd/Streaming.h +++ b/libi2pd/Streaming.h @@ -179,7 +179,8 @@ namespace stream void HandlePing (Packet * packet); size_t Send (const uint8_t * buf, size_t len); void AsyncSend (const uint8_t * buf, size_t len, SendHandler handler); - + void SendPing (); + template void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0); size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); }; @@ -268,6 +269,7 @@ namespace stream void Stop (); std::shared_ptr CreateNewOutgoingStream (std::shared_ptr remote, int port = 0); + void SendPing (std::shared_ptr remote); void DeleteStream (std::shared_ptr stream); bool DeleteStream (uint32_t recvStreamID); void SetAcceptor (const Acceptor& acceptor); From 2eded7cdd7cec2ce964e2695daf3ef56072064db Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 26 Sep 2021 16:25:12 -0400 Subject: [PATCH 4440/6300] send ping every keealive interval for client tunnels --- libi2pd/Destination.cpp | 25 ++++++++++++++++++++ libi2pd/Destination.h | 2 ++ libi2pd_client/ClientContext.cpp | 9 +++++++- libi2pd_client/ClientContext.h | 3 ++- libi2pd_client/I2PTunnel.cpp | 39 ++++++++++++++++++++++++++++++-- libi2pd_client/I2PTunnel.h | 10 ++++++-- 6 files changed, 82 insertions(+), 6 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 438b3f19..dd811593 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1096,6 +1096,31 @@ namespace client return nullptr; } + void ClientDestination::SendPing (const i2p::data::IdentHash& to) + { + if (m_StreamingDestination) + { + auto leaseSet = FindLeaseSet (to); + if (leaseSet) + m_StreamingDestination->SendPing (leaseSet); + else + RequestDestination (to, + [s = m_StreamingDestination](std::shared_ptr ls) + { + if (ls) s->SendPing (ls); + }); + } + } + + void ClientDestination::SendPing (std::shared_ptr to) + { + RequestDestinationWithEncryptedLeaseSet (to, + [s = m_StreamingDestination](std::shared_ptr ls) + { + if (ls) s->SendPing (ls); + }); + } + std::shared_ptr ClientDestination::GetStreamingDestination (int port) const { if (port) diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h index 0dc5450b..54df8706 100644 --- a/libi2pd/Destination.h +++ b/libi2pd/Destination.h @@ -243,6 +243,8 @@ namespace client void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); void CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr dest, int port = 0); std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); + void SendPing (const i2p::data::IdentHash& to); + void SendPing (std::shared_ptr to); void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); void StopAcceptingStreams (); bool IsAcceptingStreams () const; diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index 4eab189f..d6871fbe 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -652,6 +652,13 @@ namespace client auto tun = std::make_shared (name, dest, address, port, localDestination, destinationPort); clientTunnel = tun; clientEndpoint = tun->GetLocalEndpoint (); + + uint32_t keepAlive = section.second.get(I2P_CLIENT_TUNNEL_KEEP_ALIVE_INTERVAL, 0); + if (keepAlive) + { + tun->SetKeepAliveInterval (keepAlive); + LogPrint(eLogInfo, "Clients: I2P Client tunnel keep alive interval set to ", keepAlive); + } } uint32_t timeout = section.second.get(I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT, 0); diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 801dc0cd..cb28e40e 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -48,6 +48,7 @@ namespace client const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport"; const char I2P_CLIENT_TUNNEL_MATCH_TUNNELS[] = "matchtunnels"; const char I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT[] = "connecttimeout"; + const char I2P_CLIENT_TUNNEL_KEEP_ALIVE_INTERVAL[] = "keepaliveinterval"; const char I2P_SERVER_TUNNEL_HOST[] = "host"; const char I2P_SERVER_TUNNEL_HOST_OVERRIDE[] = "hostoverride"; const char I2P_SERVER_TUNNEL_PORT[] = "port"; diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index c01f0fe3..f70ebb8f 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -532,7 +532,7 @@ namespace client I2PClientTunnel::I2PClientTunnel (const std::string& name, const std::string& destination, const std::string& address, int port, std::shared_ptr localDestination, int destinationPort): TCPIPAcceptor (address, port, localDestination), m_Name (name), m_Destination (destination), - m_DestinationPort (destinationPort) + m_DestinationPort (destinationPort), m_KeepAliveInterval (0) { } @@ -540,14 +540,24 @@ namespace client { TCPIPAcceptor::Start (); GetAddress (); + if (m_KeepAliveInterval) + ScheduleKeepAliveTimer (); } void I2PClientTunnel::Stop () { TCPIPAcceptor::Stop(); m_Address = nullptr; + if (m_KeepAliveTimer) m_KeepAliveTimer->cancel (); } + void I2PClientTunnel::SetKeepAliveInterval (uint32_t keepAliveInterval) + { + m_KeepAliveInterval = keepAliveInterval; + if (m_KeepAliveInterval) + m_KeepAliveTimer.reset (new boost::asio::deadline_timer (GetLocalDestination ()->GetService ())); + } + /* HACK: maybe we should create a caching IdentHash provider in AddressBook */ std::shared_ptr I2PClientTunnel::GetAddress () { @@ -569,6 +579,31 @@ namespace client return nullptr; } + void I2PClientTunnel::ScheduleKeepAliveTimer () + { + if (m_KeepAliveTimer) + { + m_KeepAliveTimer->expires_from_now (boost::posix_time::seconds(m_KeepAliveInterval)); + m_KeepAliveTimer->async_wait (std::bind (&I2PClientTunnel::HandleKeepAliveTimer, + this, std::placeholders::_1)); + } + } + + void I2PClientTunnel::HandleKeepAliveTimer (const boost::system::error_code& ecode) + { + if (ecode != boost::asio::error::operation_aborted) + { + if (m_Address && m_Address->IsValid ()) + { + if (m_Address->IsIdentHash ()) + GetLocalDestination ()->SendPing (m_Address->identHash); + else + GetLocalDestination ()->SendPing (m_Address->blindedPublicKey); + } + ScheduleKeepAliveTimer (); + } + } + I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, int inport, bool gzip): I2PService (localDestination), m_IsUniqueLocal(true), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false) diff --git a/libi2pd_client/I2PTunnel.h b/libi2pd_client/I2PTunnel.h index 1db34b6c..d21b2e11 100644 --- a/libi2pd_client/I2PTunnel.h +++ b/libi2pd_client/I2PTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -153,16 +153,22 @@ namespace client void Stop (); const char* GetName() { return m_Name.c_str (); } - + void SetKeepAliveInterval (uint32_t keepAliveInterval); + private: std::shared_ptr GetAddress (); + + void ScheduleKeepAliveTimer (); + void HandleKeepAliveTimer (const boost::system::error_code& ecode); private: std::string m_Name, m_Destination; std::shared_ptr m_Address; int m_DestinationPort; + uint32_t m_KeepAliveInterval; + std::unique_ptr m_KeepAliveTimer; }; From cc75efcbca2673a33ca6dfcc76b03e09a54278a8 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 27 Sep 2021 18:25:15 -0400 Subject: [PATCH 4441/6300] fixed build for C++11 --- libi2pd/Destination.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index dd811593..54137664 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -1104,18 +1104,22 @@ namespace client if (leaseSet) m_StreamingDestination->SendPing (leaseSet); else + { + auto s = m_StreamingDestination; RequestDestination (to, - [s = m_StreamingDestination](std::shared_ptr ls) + [s](std::shared_ptr ls) { if (ls) s->SendPing (ls); }); + } } } void ClientDestination::SendPing (std::shared_ptr to) { + auto s = m_StreamingDestination; RequestDestinationWithEncryptedLeaseSet (to, - [s = m_StreamingDestination](std::shared_ptr ls) + [s](std::shared_ptr ls) { if (ls) s->SendPing (ls); }); From d723faaaa3a3539bc1b09ca52351c73297a38680 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 28 Sep 2021 14:27:22 +0300 Subject: [PATCH 4442/6300] [UDPTunnel] restart local listener on error Signed-off-by: R4SAS --- libi2pd_client/I2PTunnel.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libi2pd_client/I2PTunnel.cpp b/libi2pd_client/I2PTunnel.cpp index f70ebb8f..b97270e4 100644 --- a/libi2pd_client/I2PTunnel.cpp +++ b/libi2pd_client/I2PTunnel.cpp @@ -964,7 +964,8 @@ namespace client RemotePort(remotePort), m_LastPort (0), m_cancel_resolve(false) { - m_LocalSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU )); + m_LocalSocket.set_option (boost::asio::socket_base::receive_buffer_size (I2P_UDP_MAX_MTU)); + m_LocalSocket.set_option (boost::asio::socket_base::reuse_address (true)); auto dgram = m_LocalDest->CreateDatagramDestination(gzip); dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, @@ -991,7 +992,8 @@ namespace client void I2PUDPClientTunnel::HandleRecvFromLocal(const boost::system::error_code & ec, std::size_t transferred) { if(ec) { - LogPrint(eLogError, "UDP Client: ", ec.message()); + LogPrint(eLogError, "UDP Client: Reading from socket error: ", ec.message(), ". Restarting listener..."); + RecvFromLocal(); // Restart listener and continue work return; } if(!m_RemoteIdent) { @@ -1016,7 +1018,7 @@ namespace client auto ts = i2p::util::GetMillisecondsSinceEpoch(); LogPrint(eLogDebug, "UDP Client: send ", transferred, " to ", m_RemoteIdent->ToBase32(), ":", RemotePort); auto session = m_LocalDest->GetDatagramDestination()->GetSession (*m_RemoteIdent); - if (ts > m_LastSession->second + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) + if (ts > m_LastSession->second + I2P_UDP_REPLIABLE_DATAGRAM_INTERVAL) m_LocalDest->GetDatagramDestination()->SendDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); else m_LocalDest->GetDatagramDestination()->SendRawDatagram (session, m_RecvBuff, transferred, remotePort, RemotePort); From af133f4968946d3074e8a70799ba8f9a776b1896 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 29 Sep 2021 12:38:38 -0400 Subject: [PATCH 4443/6300] fixed crash if incorrect blinded signature type --- libi2pd/LeaseSet.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp index 75187cfe..f6734b10 100644 --- a/libi2pd/LeaseSet.cpp +++ b/libi2pd/LeaseSet.cpp @@ -912,6 +912,11 @@ namespace data uint8_t blindedPriv[64], blindedPub[128]; // 64 and 128 max size_t publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub); std::unique_ptr blindedSigner (i2p::data::PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv)); + if (!blindedSigner) + { + LogPrint (eLogError, "LeaseSet2: Can't create blinded signer for signature type ", blindedKey.GetSigType ()); + return; + } auto offset = 1; htobe16buf (m_Buffer + offset, blindedKey.GetBlindedSigType ()); offset += 2; // Blinded Public Key Sig Type memcpy (m_Buffer + offset, blindedPub, publicKeyLen); offset += publicKeyLen; // Blinded Public Key From e6bcd04a36a705cd24956534a6df28024f664b39 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 5 Oct 2021 19:38:33 -0400 Subject: [PATCH 4444/6300] short build message for re-created tunnels and far end transports --- libi2pd/RouterInfo.h | 13 ++++++++----- libi2pd/Tunnel.cpp | 13 ++++++++----- libi2pd/Tunnel.h | 3 +++ libi2pd/TunnelConfig.h | 18 +++++++++++++----- libi2pd/TunnelPool.cpp | 19 ++++++++++++++----- libi2pd/TunnelPool.h | 1 + 6 files changed, 47 insertions(+), 20 deletions(-) diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 8ffd81cd..c16f8e5f 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -67,7 +67,8 @@ namespace data eSSUV6 = 0x08, eNTCP2V6Mesh = 0x10 }; - + typedef uint8_t CompatibleTransports; + enum Caps { eFloodfill = 0x01, @@ -206,9 +207,10 @@ namespace data void EnableMesh (); void DisableMesh (); bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; - bool IsReachableFrom (const RouterInfo& other) const { return m_ReachableTransports & other.m_SupportedTransports; }; - bool IsReachableBy (uint8_t transports) const { return m_ReachableTransports & transports; }; - bool HasValidAddresses () const { return m_SupportedTransports; }; + bool IsReachableFrom (const RouterInfo& other) const { return m_ReachableTransports & other.m_SupportedTransports; }; + bool IsReachableBy (CompatibleTransports transports) const { return m_ReachableTransports & transports; }; + CompatibleTransports GetCompatibleTransports (bool incoming) const { return incoming ? m_ReachableTransports : m_SupportedTransports; }; + bool HasValidAddresses () const { return m_SupportedTransports; }; bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; @@ -273,7 +275,8 @@ namespace data boost::shared_ptr m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9 std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; - uint8_t m_SupportedTransports, m_ReachableTransports, m_Caps; + CompatibleTransports m_SupportedTransports, m_ReachableTransports; + uint8_t m_Caps; int m_Version; mutable std::shared_ptr m_Profile; }; diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 76504e1f..1f69470c 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -31,8 +31,9 @@ namespace tunnel { Tunnel::Tunnel (std::shared_ptr config): TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()), - m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false), - m_Latency (0) + m_Config (config), m_IsShortBuildMessage (false), m_Pool (nullptr), + m_State (eTunnelStatePending), m_FarEndTransports (0), + m_IsRecreated (false), m_Latency (0) { } @@ -180,6 +181,8 @@ namespace tunnel m_Hops.push_back (std::unique_ptr(tunnelHop)); hop = hop->prev; } + m_IsShortBuildMessage = m_Config->IsShort (); + m_FarEndTransports = m_Config->GetFarEndTransports (); m_Config = nullptr; } if (established) m_State = eTunnelStateEstablished; @@ -715,7 +718,7 @@ namespace tunnel LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }, - inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()), nullptr + inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash (), false), nullptr ); } } @@ -791,7 +794,7 @@ namespace tunnel } LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel"); CreateTunnel ( - std::make_shared (std::vector > { router->GetRouterIdentity () }), nullptr + std::make_shared (std::vector > { router->GetRouterIdentity () }, false), nullptr ); } } @@ -897,7 +900,7 @@ namespace tunnel { // build symmetric outbound tunnel CreateTunnel (std::make_shared(newTunnel->GetInvertedPeers (), - newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), nullptr, + newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash (), false), nullptr, GetNextOutboundTunnel ()); } else diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index a1fd247a..2e1c9534 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -71,6 +71,7 @@ namespace tunnel std::shared_ptr GetTunnelConfig () const { return m_Config; } std::vector > GetPeers () const; std::vector > GetInvertedPeers () const; + bool IsShortBuildMessage () const { return m_IsShortBuildMessage; }; TunnelState GetState () const { return m_State; }; void SetState (TunnelState state); bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; @@ -109,8 +110,10 @@ namespace tunnel std::shared_ptr m_Config; std::vector > m_Hops; + bool m_IsShortBuildMessage; std::shared_ptr m_Pool; // pool, tunnel belongs to, or null TunnelState m_State; + i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports; bool m_IsRecreated; // if tunnel is replaced by new, or new tunnel requested to replace uint64_t m_Latency; // in milliseconds }; diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index 8cb46d09..f198dffd 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -82,16 +82,17 @@ namespace tunnel public: TunnelConfig (const std::vector >& peers, - bool isShort = false): // inbound - m_IsShort (isShort) + bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports = 0): // inbound + m_IsShort (isShort), m_FarEndTransports (farEndTransports) { CreatePeers (peers); m_LastHop->SetNextIdent (i2p::context.GetIdentHash ()); } TunnelConfig (const std::vector >& peers, - uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort = false): // outbound - m_IsShort (isShort) + uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort, + i2p::data::RouterInfo::CompatibleTransports farEndTransports = 0): // outbound + m_IsShort (isShort), m_FarEndTransports (farEndTransports) { CreatePeers (peers); m_FirstHop->isGateway = false; @@ -112,6 +113,11 @@ namespace tunnel bool IsShort () const { return m_IsShort; } + i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const + { + return m_FarEndTransports; + } + TunnelHopConfig * GetFirstHop () const { return m_FirstHop; @@ -178,7 +184,8 @@ namespace tunnel protected: // this constructor can't be called from outside - TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr), m_IsShort (false) + TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr), m_IsShort (false), + m_FarEndTransports (0) { } @@ -190,6 +197,7 @@ namespace tunnel TunnelHopConfig * m_FirstHop, * m_LastHop; bool m_IsShort; + i2p::data::RouterInfo::CompatibleTransports m_FarEndTransports; }; class ZeroHopsTunnelConfig: public TunnelConfig diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 7a5721fb..b349ada6 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -495,6 +495,8 @@ namespace tunnel } prevHop = hop; path.Add (hop); + if (i == numHops - 1) + path.farEndTransports = hop->GetCompatibleTransports (inbound); } return true; } @@ -527,7 +529,11 @@ namespace tunnel if (r) { if (r->IsECIES ()) + { path.Add (r); + if (i == numHops - 1) + path.farEndTransports = r->GetCompatibleTransports (isInbound); + } else { LogPrint (eLogError, "Tunnels: ElGamal router ", ident.ToBase64 (), " is not supported"); @@ -557,7 +563,7 @@ namespace tunnel if (m_NumInboundHops > 0) { path.Reverse (); - config = std::make_shared (path.peers, path.isShort); + config = std::make_shared (path.peers, path.isShort, path.farEndTransports); } auto tunnel = tunnels.CreateInboundTunnel (config, shared_from_this (), outboundTunnel); if (tunnel->IsEstablished ()) // zero hops @@ -581,7 +587,7 @@ namespace tunnel std::shared_ptr config; if (m_NumInboundHops > 0 && tunnel->GetPeers().size()) { - config = std::make_shared(tunnel->GetPeers ()); + config = std::make_shared(tunnel->GetPeers (), tunnel->IsShortBuildMessage ()); } if (!m_NumInboundHops || config) { @@ -606,7 +612,8 @@ namespace tunnel { std::shared_ptr config; if (m_NumOutboundHops > 0) - config = std::make_shared(path.peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash (), path.isShort); + config = std::make_shared(path.peers, inboundTunnel->GetNextTunnelID (), + inboundTunnel->GetNextIdentHash (), path.isShort, path.farEndTransports); std::shared_ptr tunnel; if (path.isShort) @@ -643,7 +650,8 @@ namespace tunnel std::shared_ptr config; if (m_NumOutboundHops > 0 && tunnel->GetPeers().size()) { - config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); + config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), + inboundTunnel->GetNextIdentHash (), inboundTunnel->IsShortBuildMessage ()); } if (!m_NumOutboundHops || config) { @@ -660,7 +668,8 @@ namespace tunnel { LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); auto tunnel = tunnels.CreateInboundTunnel ( - m_NumOutboundHops > 0 ? std::make_shared(outboundTunnel->GetInvertedPeers ()) : nullptr, + m_NumOutboundHops > 0 ? std::make_shared(outboundTunnel->GetInvertedPeers (), + outboundTunnel->IsShortBuildMessage ()) : nullptr, shared_from_this (), outboundTunnel); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index 97ca0419..875a9955 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -40,6 +40,7 @@ namespace tunnel { std::vector peers; bool isShort = true; + i2p::data::RouterInfo::CompatibleTransports farEndTransports = 0; void Add (std::shared_ptr r); void Reverse (); From 49e8cf89d83339ae44ec8f155d8e40dfef1da2aa Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 6 Oct 2021 12:42:32 -0400 Subject: [PATCH 4445/6300] don't send short tunnel build messages for ElGamal only destinations --- libi2pd/TunnelPool.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index b349ada6..5ed330c8 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -610,6 +610,9 @@ namespace tunnel Path path; if (SelectPeers (path, false)) { + if (m_LocalDestination && !m_LocalDestination->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) + path.isShort = false; // because can't handle ECIES encrypted reply + std::shared_ptr config; if (m_NumOutboundHops > 0) config = std::make_shared(path.peers, inboundTunnel->GetNextTunnelID (), From 48131f4597dcb9a62e539d9a713e9dadaf493d7d Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 7 Oct 2021 15:08:33 -0400 Subject: [PATCH 4446/6300] don't store full path with RouterInfo --- libi2pd/NetDb.cpp | 3 ++- libi2pd/RouterInfo.cpp | 26 +++++++++++++------------- libi2pd/RouterInfo.h | 8 ++++---- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 9c079d57..8d0025ec 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -963,7 +963,8 @@ namespace data if (router) { LogPrint (eLogDebug, "NetDb: requested RouterInfo ", key, " found"); - router->LoadBuffer (); + if (!router->GetBuffer ()) + router->LoadBuffer (m_Storage.Path (router->GetIdentHashBase64 ())); if (router->GetBuffer ()) replyMsg = CreateDatabaseStoreMsg (router); } diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 5d66e0e4..f2eefcd6 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -35,12 +35,12 @@ namespace data } RouterInfo::RouterInfo (const std::string& fullPath): - m_FullPath (fullPath), m_IsUpdated (false), m_IsUnreachable (false), - m_SupportedTransports (0), m_ReachableTransports (0), m_Caps (0), m_Version (0) + m_IsUpdated (false), m_IsUnreachable (false), m_SupportedTransports (0), + m_ReachableTransports (0), m_Caps (0), m_Version (0) { m_Addresses = boost::make_shared(); // create empty list m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; - ReadFromFile (); + ReadFromFile (fullPath); } RouterInfo::RouterInfo (const uint8_t * buf, int len): @@ -113,16 +113,16 @@ namespace data m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); } - bool RouterInfo::LoadFile () + bool RouterInfo::LoadFile (const std::string& fullPath) { - std::ifstream s(m_FullPath, std::ifstream::binary); + std::ifstream s(fullPath, std::ifstream::binary); if (s.is_open ()) { s.seekg (0,std::ios::end); m_BufferLen = s.tellg (); if (m_BufferLen < 40 || m_BufferLen > MAX_RI_BUFFER_SIZE) { - LogPrint(eLogError, "RouterInfo: File", m_FullPath, " is malformed"); + LogPrint(eLogError, "RouterInfo: File", fullPath, " is malformed"); return false; } s.seekg(0, std::ios::beg); @@ -131,15 +131,15 @@ namespace data } else { - LogPrint (eLogError, "RouterInfo: Can't open file ", m_FullPath); + LogPrint (eLogError, "RouterInfo: Can't open file ", fullPath); return false; } return true; } - void RouterInfo::ReadFromFile () + void RouterInfo::ReadFromFile (const std::string& fullPath) { - if (LoadFile ()) + if (LoadFile (fullPath)) ReadFromBuffer (false); else m_IsUnreachable = true; @@ -748,11 +748,11 @@ namespace data return bufbe64toh (buf + size) > m_Timestamp; } - const uint8_t * RouterInfo::LoadBuffer () + const uint8_t * RouterInfo::LoadBuffer (const std::string& fullPath) { if (!m_Buffer) { - if (LoadFile ()) + if (LoadFile (fullPath)) LogPrint (eLogDebug, "RouterInfo: Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file"); } return m_Buffer; @@ -783,8 +783,8 @@ namespace data bool RouterInfo::SaveToFile (const std::string& fullPath) { - m_FullPath = fullPath; - if (!m_Buffer) { + if (!m_Buffer) + { LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL"); return false; } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index c16f8e5f..38acbb16 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -226,7 +226,7 @@ namespace data bool IsUnreachable () const { return m_IsUnreachable; }; const uint8_t * GetBuffer () const { return m_Buffer; }; - const uint8_t * LoadBuffer (); // load if necessary + const uint8_t * LoadBuffer (const std::string& fullPath); // load if necessary int GetBufferLen () const { return m_BufferLen; }; void CreateBuffer (const PrivateKeys& privateKeys); @@ -252,8 +252,8 @@ namespace data private: - bool LoadFile (); - void ReadFromFile (); + bool LoadFile (const std::string& fullPath); + void ReadFromFile (const std::string& fullPath); void ReadFromStream (std::istream& s); void ReadFromBuffer (bool verifySignature); void WriteToStream (std::ostream& s) const; @@ -267,7 +267,7 @@ namespace data private: - std::string m_FullPath, m_Family; + std::string m_Family; std::shared_ptr m_RouterIdentity; uint8_t * m_Buffer; size_t m_BufferLen; From 7def2fa6a34d85dde1bdb436862a9b24a163b2e1 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 10 Oct 2021 09:53:21 -0400 Subject: [PATCH 4447/6300] use std::vector for address list --- libi2pd/RouterInfo.cpp | 1 + libi2pd/RouterInfo.h | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index f2eefcd6..7747736c 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -194,6 +194,7 @@ namespace data auto addresses = boost::make_shared(); uint8_t numAddresses; s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return; + addresses->reserve (numAddresses); for (int i = 0; i < numAddresses; i++) { uint8_t supportedTransports = 0; diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index 38acbb16..b2b883dc 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -157,7 +156,7 @@ namespace data bool IsV4 () const { return (caps & AddressCaps::eV4) || (host.is_v4 () && !host.is_unspecified ()); }; bool IsV6 () const { return (caps & AddressCaps::eV6) || (host.is_v6 () && !host.is_unspecified ()); }; }; - typedef std::list > Addresses; + typedef std::vector > Addresses; RouterInfo (); RouterInfo (const std::string& fullPath); From 44e01b41f8cf166d26fd3555f79690260792ba51 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 12 Oct 2021 13:28:16 -0400 Subject: [PATCH 4448/6300] reserve address for 3 introducers --- libi2pd/RouterInfo.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index 7747736c..cb5a3db7 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -187,13 +187,14 @@ namespace data void RouterInfo::ReadFromStream (std::istream& s) { + if (!s) return; m_Caps = 0; s.read ((char *)&m_Timestamp, sizeof (m_Timestamp)); m_Timestamp = be64toh (m_Timestamp); // read addresses auto addresses = boost::make_shared(); uint8_t numAddresses; - s.read ((char *)&numAddresses, sizeof (numAddresses)); if (!s) return; + s.read ((char *)&numAddresses, sizeof (numAddresses)); addresses->reserve (numAddresses); for (int i = 0; i < numAddresses; i++) { @@ -282,7 +283,11 @@ namespace data if (s) continue; else return; } if (index >= address->ssu->introducers.size ()) + { + if (address->ssu->introducers.empty ()) // first time + address->ssu->introducers.reserve (3); address->ssu->introducers.resize (index + 1); + } Introducer& introducer = address->ssu->introducers.at (index); if (!strcmp (key, "ihost")) { From 1af9117b80ec0461d32eb7229d81ba57f2ed8775 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 15 Oct 2021 14:01:41 -0400 Subject: [PATCH 4449/6300] don't create new tunnel message for encryption/decryption --- libi2pd/TransitTunnel.cpp | 17 ++++++++--------- libi2pd/TransitTunnel.h | 8 ++++---- libi2pd/Tunnel.cpp | 13 ++++++------- libi2pd/Tunnel.h | 4 ++-- libi2pd/TunnelBase.h | 2 +- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/libi2pd/TransitTunnel.cpp b/libi2pd/TransitTunnel.cpp index dc7655d1..c4f3fa19 100644 --- a/libi2pd/TransitTunnel.cpp +++ b/libi2pd/TransitTunnel.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -37,15 +37,14 @@ namespace tunnel { } - void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { - auto newMsg = CreateEmptyTunnelDataMsg (false); - EncryptTunnelMsg (tunnelMsg, newMsg); + EncryptTunnelMsg (tunnelMsg, tunnelMsg); m_NumTransmittedBytes += tunnelMsg->GetLength (); - htobe32buf (newMsg->GetPayload (), GetNextTunnelID ()); - newMsg->FillI2NPMessageHeader (eI2NPTunnelData); - m_TunnelDataMsgs.push_back (newMsg); + htobe32buf (tunnelMsg->GetPayload (), GetNextTunnelID ()); + tunnelMsg->FillI2NPMessageHeader (eI2NPTunnelData); + m_TunnelDataMsgs.push_back (tunnelMsg); } void TransitTunnelParticipant::FlushTunnelDataMsgs () @@ -65,7 +64,7 @@ namespace tunnel LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); } - void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); } @@ -85,7 +84,7 @@ namespace tunnel m_Gateway.SendBuffer (); } - void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { auto newMsg = CreateEmptyTunnelDataMsg (true); EncryptTunnelMsg (tunnelMsg, newMsg); diff --git a/libi2pd/TransitTunnel.h b/libi2pd/TransitTunnel.h index e71ec750..bce90958 100644 --- a/libi2pd/TransitTunnel.h +++ b/libi2pd/TransitTunnel.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -35,7 +35,7 @@ namespace tunnel // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); private: @@ -54,7 +54,7 @@ namespace tunnel ~TransitTunnelParticipant (); size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; }; - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); void FlushTunnelDataMsgs (); private: @@ -95,7 +95,7 @@ namespace tunnel void Cleanup () { m_Endpoint.Cleanup (); } - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); } private: diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 1f69470c..c8a1cc99 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -243,13 +243,12 @@ namespace tunnel } } - void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) + void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr&& msg) { if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive - auto newMsg = CreateEmptyTunnelDataMsg (true); - EncryptTunnelMsg (msg, newMsg); - newMsg->from = shared_from_this (); - m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); + EncryptTunnelMsg (msg, msg); + msg->from = shared_from_this (); + m_Endpoint.HandleDecryptedTunnelDataMsg (msg); } void InboundTunnel::Print (std::stringstream& s) const @@ -308,7 +307,7 @@ namespace tunnel m_Gateway.SendBuffer (); } - void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) + void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) { LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ()); } @@ -519,7 +518,7 @@ namespace tunnel if (tunnel) { if (typeID == eI2NPTunnelData) - tunnel->HandleTunnelDataMsg (msg); + tunnel->HandleTunnelDataMsg (std::move (msg)); else // tunnel gateway assumed HandleTunnelGatewayMsg (tunnel, msg); } diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 2e1c9534..930d0c58 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -132,7 +132,7 @@ namespace tunnel void Print (std::stringstream& s) const; // implements TunnelBase - void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); + void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg); bool IsInbound() const { return false; } @@ -148,7 +148,7 @@ namespace tunnel public: InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; - void HandleTunnelDataMsg (std::shared_ptr msg); + void HandleTunnelDataMsg (std::shared_ptr&& msg); virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; void Print (std::stringstream& s) const; bool IsInbound() const { return true; } diff --git a/libi2pd/TunnelBase.h b/libi2pd/TunnelBase.h index f98066d3..8d0edff1 100644 --- a/libi2pd/TunnelBase.h +++ b/libi2pd/TunnelBase.h @@ -47,7 +47,7 @@ namespace tunnel virtual ~TunnelBase () {}; virtual void Cleanup () {}; - virtual void HandleTunnelDataMsg (std::shared_ptr tunnelMsg) = 0; + virtual void HandleTunnelDataMsg (std::shared_ptr&& tunnelMsg) = 0; virtual void SendTunnelDataMsg (std::shared_ptr msg) = 0; virtual void FlushTunnelDataMsgs () {}; virtual void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) = 0; From d310efcb5cbe5f81cbd1b5705109b8f9caf14294 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 17 Oct 2021 11:31:37 -0400 Subject: [PATCH 4450/6300] pass I2NPMessage by move --- libi2pd/I2NPProtocol.cpp | 2 +- libi2pd/I2NPProtocol.h | 2 +- libi2pd/NTCP2.cpp | 5 +++-- libi2pd/SSUData.cpp | 2 +- libi2pd/Transports.cpp | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 29d68b3c..0bc721ec 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -860,7 +860,7 @@ namespace i2p Flush (); } - void I2NPMessagesHandler::PutNextMessage (std::shared_ptr msg) + void I2NPMessagesHandler::PutNextMessage (std::shared_ptr&& msg) { if (msg) { diff --git a/libi2pd/I2NPProtocol.h b/libi2pd/I2NPProtocol.h index f0777ac2..c8bf1697 100644 --- a/libi2pd/I2NPProtocol.h +++ b/libi2pd/I2NPProtocol.h @@ -301,7 +301,7 @@ namespace tunnel public: ~I2NPMessagesHandler (); - void PutNextMessage (std::shared_ptr msg); + void PutNextMessage (std::shared_ptr&& msg); void Flush (); private: diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 05d68203..296139a3 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -876,11 +876,12 @@ namespace transport break; } auto nextMsg = NewI2NPMessage (size); - nextMsg->Align (12); // for possible tunnel msg + nextMsg->Align (6); // for possible tunnel msg + nextMsg->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header nextMsg->len = nextMsg->offset + size + 7; // 7 more bytes for full I2NP header memcpy (nextMsg->GetNTCP2Header (), frame + offset, size); nextMsg->FromNTCP2 (); - m_Handler.PutNextMessage (nextMsg); + m_Handler.PutNextMessage (std::move (nextMsg)); break; } case eNTCP2BlkTermination: diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index ccc89b41..18b471e2 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -247,7 +247,7 @@ namespace transport m_ReceivedMessages.emplace (msgID, m_LastMessageReceivedTime); if (!msg->IsExpired ()) { - m_Handler.PutNextMessage (msg); + m_Handler.PutNextMessage (std::move (msg)); } else LogPrint (eLogDebug, "SSU: message expired"); diff --git a/libi2pd/Transports.cpp b/libi2pd/Transports.cpp index 181c3663..6fd5fad9 100644 --- a/libi2pd/Transports.cpp +++ b/libi2pd/Transports.cpp @@ -389,7 +389,7 @@ namespace transport { // we send it to ourself for (auto& it: msgs) - m_LoopbackHandler.PutNextMessage (it); + m_LoopbackHandler.PutNextMessage (std::move (it)); m_LoopbackHandler.Flush (); return; } From 197882a4c9e15ab6b3a40f4c658c3e6720019417 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 17 Oct 2021 15:30:24 -0400 Subject: [PATCH 4451/6300] create I2NP depending on type in I2NP block --- libi2pd/NTCP2.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 296139a3..9e18c81e 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -875,13 +875,16 @@ namespace transport LogPrint (eLogError, "NTCP2: I2NP block is too long ", size); break; } - auto nextMsg = NewI2NPMessage (size); - nextMsg->Align (6); // for possible tunnel msg - nextMsg->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header + auto nextMsg = (frame[offset] == eI2NPTunnelData) ? NewI2NPTunnelMessage (true) : NewI2NPMessage (size); nextMsg->len = nextMsg->offset + size + 7; // 7 more bytes for full I2NP header - memcpy (nextMsg->GetNTCP2Header (), frame + offset, size); - nextMsg->FromNTCP2 (); - m_Handler.PutNextMessage (std::move (nextMsg)); + if (nextMsg->len <= nextMsg->maxLen) + { + memcpy (nextMsg->GetNTCP2Header (), frame + offset, size); + nextMsg->FromNTCP2 (); + m_Handler.PutNextMessage (std::move (nextMsg)); + } + else + LogPrint (eLogError, "NTCP2: I2NP block is too long for I2NP message"); break; } case eNTCP2BlkTermination: From 278fd2d8d58210a126abfbbe4eb6a3b7d4b2063b Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 18 Oct 2021 19:03:08 -0400 Subject: [PATCH 4452/6300] create tunnel I2NP message for tunnel data --- libi2pd/SSUData.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 18b471e2..6bf01778 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -176,7 +176,8 @@ namespace transport if (it == m_IncompleteMessages.end ()) { // create new message - auto msg = NewI2NPShortMessage (); + auto msg = (!fragmentNum && fragmentSize > 0 && buf[I2NP_SHORT_HEADER_TYPEID_OFFSET] == eI2NPTunnelData) ? + NewI2NPTunnelMessage (true) : NewI2NPShortMessage (); msg->len -= I2NP_SHORT_HEADER_SIZE; it = m_IncompleteMessages.insert (std::make_pair (msgID, m_Session.GetServer ().GetIncompleteMessagesPool ().AcquireShared (msg))).first; From efd84a240483de954c688cc12ebdf78e03968b24 Mon Sep 17 00:00:00 2001 From: xanoni <77220130+xanoni@users.noreply.github.com> Date: Mon, 18 Oct 2021 22:10:17 -0400 Subject: [PATCH 4453/6300] Makefile: set default target ("all") Fixes compilation on Darwin, see: https://github.com/PurpleI2P/i2pd/pull/1698#issuecomment-946304938 --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index d7765af7..59b1df1a 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +.DEFAULT_GOAL := all + SYS := $(shell $(CXX) -dumpmachine) ifneq (, $(findstring darwin, $(SYS))) From ff3d2db85e4c49600adf38c6de9087881b57247e Mon Sep 17 00:00:00 2001 From: xanoni <77220130+xanoni@users.noreply.github.com> Date: Mon, 18 Oct 2021 22:29:30 -0400 Subject: [PATCH 4454/6300] Darwin: allow calling make install more than once This commit ensures that `gzip` does not overwrite any of the repo files, because that prevents `make install` from executing more than once. --- .gitignore | 1 + Makefile.homebrew | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bafe2e18..75bd6abb 100644 --- a/.gitignore +++ b/.gitignore @@ -260,6 +260,7 @@ docs/generated build/Makefile # debian stuff +debian/i2pd.1.gz .pc/ # qt diff --git a/Makefile.homebrew b/Makefile.homebrew index c1992296..8d6dd7a3 100644 --- a/Makefile.homebrew +++ b/Makefile.homebrew @@ -44,7 +44,7 @@ install: all install -m 644 contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/etc/i2pd @cp -R contrib/certificates ${PREFIX}/share/i2pd/ install -m 644 ChangeLog LICENSE README.md contrib/i2pd.conf contrib/subscriptions.txt contrib/tunnels.conf ${PREFIX}/share/doc/i2pd - @gzip debian/i2pd.1 && install debian/i2pd.1.gz ${PREFIX}/share/man/man1 + @gzip -kf debian/i2pd.1 && install debian/i2pd.1.gz ${PREFIX}/share/man/man1 @ln -sf ${PREFIX}/share/i2pd/certificates ${PREFIX}/var/lib/i2pd/ @ln -sf ${PREFIX}/etc/i2pd/i2pd.conf ${PREFIX}/var/lib/i2pd/i2pd.conf @ln -sf ${PREFIX}/etc/i2pd/subscriptions.txt ${PREFIX}/var/lib/i2pd/subscriptions.txt From 0c25e8f1eb0f90089c8f95158cfa147672d28af8 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 6 Oct 2021 21:35:23 +0300 Subject: [PATCH 4455/6300] [build] changes in windows build script Signed-off-by: R4SAS --- build/build_mingw.cmd | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/build/build_mingw.cmd b/build/build_mingw.cmd index e8f1378b..a4e6d8de 100644 --- a/build/build_mingw.cmd +++ b/build/build_mingw.cmd @@ -34,13 +34,19 @@ del /S build_*.log >> nul 2>&1 echo Receiving latest commit and cleaning up... %xSH% "git checkout contrib/* && git pull && make clean" > build\build.log 2>&1 -echo. REM set to variable current commit hash -FOR /F "usebackq" %%a IN (`%xSH% 'git describe --tags'`) DO ( +FOR /F "usebackq" %%a IN (`%xSH% "git describe --tags"`) DO ( set tag=%%a ) +REM set to variable latest released tag +FOR /F "usebackq" %%b IN (`%xSH% "git describe --abbrev=0"`) DO ( + set reltag=%%b +) + +echo Preparing configuration files and README for packaging... + %xSH% "echo To use configs and certificates, move all files and certificates folder from contrib directory here. > README.txt" >> nul REM converting configuration files to DOS format (usable in default notepad) @@ -64,8 +70,9 @@ call :BUILDING_XP echo. REM compile installer -C:\PROGRA~2\INNOSE~1\ISCC.exe /dI2Pd_TextVer="%tag%" /dI2Pd_Ver="%tag%.0" build\win_installer.iss >> build\build.log 2>&1 +C:\PROGRA~2\INNOSE~1\ISCC.exe /dI2Pd_TextVer="%tag%" /dI2Pd_Ver="%reltag%.0" build\win_installer.iss >> build\build.log 2>&1 +%xSH% "git checkout contrib/*" >> build\build.log 2>&1 del README.txt i2pd_x32.exe i2pd_x64.exe i2pd_xp.exe >> nul echo Build complete... @@ -74,13 +81,13 @@ exit /b 0 :BUILDING %xSH% "make clean" >> nul -echo Building i2pd %tag% for win%bitness% +echo Building i2pd %tag% for win%bitness%... %xSH% "make DEBUG=no USE_UPNP=yes -j%threads% && cp i2pd.exe i2pd_x%bitness%.exe && zip -r9 build/i2pd_%tag%_win%bitness%_mingw.zip %FILELIST% && make clean" > build\build_win%bitness%_%tag%.log 2>&1 goto EOF :BUILDING_XP %xSH% "make clean" >> nul -echo Building i2pd %tag% for winxp +echo Building i2pd %tag% for winxp... %xSH% "make DEBUG=no USE_UPNP=yes USE_WINXP_FLAGS=yes -j%threads% && cp i2pd.exe i2pd_xp.exe && zip -r9 build/i2pd_%tag%_winxp_mingw.zip %FILELIST% && make clean" > build\build_winxp_%tag%.log 2>&1 :EOF \ No newline at end of file From 8a58572b342449b1e6c90cb9bfb201b9c6614be3 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Tue, 19 Oct 2021 18:24:49 +0300 Subject: [PATCH 4456/6300] [webconsole] upload example dark style Signed-off-by: R4SAS --- contrib/webconsole/style-dark.css | 272 ++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 contrib/webconsole/style-dark.css diff --git a/contrib/webconsole/style-dark.css b/contrib/webconsole/style-dark.css new file mode 100644 index 00000000..6ec19023 --- /dev/null +++ b/contrib/webconsole/style-dark.css @@ -0,0 +1,272 @@ +:root { + --main-bg-color: #121212; + --main-text-color: #156A3D; + --main-link-color: #894C84; +} + +body { + font: 100%/1.5em sans-serif; + margin: 0; + padding: 1.5em; + background: var(--main-bg-color); + color: var(--main-text-color); +} + +a, .slide label { + text-decoration: none; + color: var(--main-link-color); +} + +a:hover, .slide label:hover, button[type=submit]:hover { + color: #FAFAFA; + background: var(--main-link-color); +} + +a.button { + appearance: button; + text-decoration: none; + padding: 0 5px; + border: 1px solid var(--main-link-color); +} + +.header { + font-size: 2.5em; + text-align: center; + margin: 1em 0; + color: var(--main-link-color); +} + +.wrapper { + margin: 0 auto; + padding: 1em; + max-width: 64em; +} + +.menu { + display: block; + float: left; + overflow: hidden; + max-width: 12em; + white-space: nowrap; + text-overflow: ellipsis; +} + +.listitem { + display: block; + font-family: monospace; + font-size: 1.2em; + white-space: nowrap; +} + +.tableitem { + font-family: monospace; + font-size: 1.2em; + white-space: nowrap; +} + +.content { + float: left; + font-size: 1em; + margin-left: 4em; + max-width: 48em; + overflow: auto; +} + +.tunnel.established { + color: #56B734; +} + +.tunnel.expiring { + color: #D3AE3F; +} + +.tunnel.failed { + color: #D33F3F; +} + +.tunnel.building { + color: #434343; +} + +caption { + font-size: 1.5em; + text-align: center; + color: var(--main-link-color); +} + +table { + display: table; + border-collapse: collapse; + text-align: center; +} + +table.extaddr { + text-align: left; +} + +table.services { + width: 100%; +} + +textarea { + background-color: var(--main-bg-color); + color: var(--main-text-color); + word-break: break-all; +} + +.streamdest { + width: 120px; + max-width: 240px; + overflow: hidden; + text-overflow: ellipsis; +} + +.slide div.slidecontent, .slide [type="checkbox"] { + display: none; +} + +.slide [type="checkbox"]:checked ~ div.slidecontent { + display: block; + margin-top: 0; + padding: 0; +} + +.disabled { + color: #D33F3F; +} + +.enabled { + color: #56B734; +} + +button[type=submit] { + background-color: transparent; + color: var(--main-link-color); + text-decoration: none; + padding: 5px; + border: 1px solid var(--main-link-color); + font-size: 14px; +} + +input, select, select option { + background-color: var(--main-bg-color); + color: var(--main-link-color); + padding: 5px; + border: 1px solid var(--main-link-color); + font-size: 14px; +} + +input:focus, select:focus, select option:focus { + outline: none; +} + +input[type=number]::-webkit-inner-spin-button { + -webkit-appearance: none; +} + +@media screen and (max-width: 1150px) { /* adaptive style */ + .wrapper { + max-width: 58em; + } + + .menu { + max-width: 10em; + } + + .content { + margin-left: 2em; + max-width: 42em; + } +} + +@media screen and (max-width: 980px) { + body { + font: 100%/1.2em sans-serif; + padding: 1.2em 0 0 0; + } + + .menu { + width: 100%; + max-width: unset; + display: block; + float: none; + position: unset; + font-size: 16px; + text-align: center; + } + + .menu a, .commands a { + display: inline-block; + padding: 4px; + } + + .content { + float: none; + margin-left: unset; + margin-top: 16px; + max-width: 100%; + width: 100%; + text-align: center; + } + + a, .slide label { + /* margin-right: 10px; */ + display: block; + /* font-size: 18px; */ + } + + .header { + margin: unset; + font-size: 1.5em; + } + + small { + display: block + } + + a.button { + appearance: button; + text-decoration: none; + margin-top: 10px; + padding: 6px; + border: 2px solid var(--main-link-color); + border-radius: 5px; + width: -webkit-fill-available; + } + + input, select { + width: 35%; + text-align: center; + padding: 5px; + border: 2px solid var(--main-link-color); + border-radius: 5px; + font-size: 18px; + } + + table.extaddr { + margin: auto; + text-align: unset; + } + + textarea { + width: -webkit-fill-available; + height: auto; + padding: 5px; + border: 2px solid var(--main-link-color); + border-radius: 5px; + font-size: 12px; + } + + button[type=submit] { + padding: 5px 15px; + background: transparent; + border: 2px solid var(--main-link-color); + cursor: pointer; + -webkit-border-radius: 5px; + border-radius: 5px; + position: relative; + height: 36px; + display: -webkit-inline-box; + margin-top: 10px; + } +} \ No newline at end of file From af794f901f5b4474de41168873d1cadc674b2988 Mon Sep 17 00:00:00 2001 From: Simon Vetter Date: Fri, 8 Oct 2021 11:31:47 +0200 Subject: [PATCH 4457/6300] libi2pd: minor logging fixes --- libi2pd/SSUSession.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp index 3e4eecb8..b28f324b 100644 --- a/libi2pd/SSUSession.cpp +++ b/libi2pd/SSUSession.cpp @@ -303,7 +303,7 @@ namespace transport } else { - LogPrint (eLogError, "SSU: Wrong external address ", ourIP.to_string ()); + LogPrint (eLogError, "SSU: External address ", ourIP.to_string (), " is in reserved range"); Failed (); } } @@ -609,7 +609,7 @@ namespace transport { *payload = 16; payload++; // size - memcpy (payload, to.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 + memcpy (payload, to.address ().to_v6 ().to_bytes ().data (), 16); // Charlie's IP V6 payload += 16; // address } htobe16buf (payload, to.port ()); // Charlie's port @@ -703,7 +703,7 @@ namespace transport if (!i2p::util::net::IsInReservedRange (ourIP)) i2p::context.UpdateAddress (ourIP); else - LogPrint (eLogWarning, "SSU: Wrong external address ", ourIP.to_string ()); + LogPrint (eLogError, "SSU: External address ", ourIP.to_string (), " is in reserved range"); if (ourIP.is_v4 ()) { if (ourPort != m_Server.GetPort ()) @@ -1301,7 +1301,7 @@ namespace transport ip = boost::asio::ip::address_v6 (bytes); } else - LogPrint (eLogWarning, "SSU: Address size ", size, " is not supported"); + LogPrint (eLogWarning, "SSU: Address size ", int(size), " is not supported"); buf += size; port = bufbe16toh (buf); return s; From a348e106206a1871d33e01cb381eaff50b0ad29c Mon Sep 17 00:00:00 2001 From: Simon Vetter Date: Mon, 18 Oct 2021 12:09:56 +0200 Subject: [PATCH 4458/6300] libi2pd: fix undefined behaviour and memory overruns This fixes the following issues (flagged by cppcheck): [libi2pd/ECIESX25519AEADRatchetSession.cpp:537]: (error) Buffer is accessed out of bounds: m_NSREncodedKey [libi2pd/Identity.cpp:22]: (error) Buffer is accessed out of bounds: keys.publicKey [libi2pd/Identity.cpp:22]: (error) Buffer is accessed out of bounds: publicKey [libi2pd/NetDb.cpp:70] -> [libi2pd/NetDb.cpp:69]: (error) Iterator 'it' used after element has been erased [libi2pd/SSUData.cpp:186] -> [libi2pd/SSUData.cpp:187]: (warning) Shifting 32-bit value by 63 bits is undefined behaviour. --- libi2pd/ECIESX25519AEADRatchetSession.cpp | 2 +- libi2pd/Identity.cpp | 3 ++- libi2pd/NetDb.cpp | 2 +- libi2pd/SSUData.cpp | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp index c9671a7e..88d59c33 100644 --- a/libi2pd/ECIESX25519AEADRatchetSession.cpp +++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp @@ -534,7 +534,7 @@ namespace garlic LogPrint (eLogError, "Garlic: Can't encode elligator"); return false; } - memcpy (m_NSREncodedKey, out + offset, 56); // for possible next NSR + memcpy (m_NSREncodedKey, out + offset, 32); // for possible next NSR memcpy (m_NSRH, m_H, 32); offset += 32; // KDF for Reply Key Section diff --git a/libi2pd/Identity.cpp b/libi2pd/Identity.cpp index aa03bfb7..fe876997 100644 --- a/libi2pd/Identity.cpp +++ b/libi2pd/Identity.cpp @@ -19,7 +19,8 @@ namespace data Identity& Identity::operator=(const Keys& keys) { // copy public and signing keys together - memcpy (publicKey, keys.publicKey, sizeof (publicKey) + sizeof (signingKey)); + memcpy (publicKey, keys.publicKey, sizeof (publicKey)); + memcpy (signingKey, keys.signingKey, sizeof (signingKey)); memset (certificate, 0, sizeof (certificate)); return *this; } diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 8d0025ec..b4ffe2bd 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -66,8 +66,8 @@ namespace data if (it != m_RouterInfos.end ()) { // remove own router - m_RouterInfos.erase (it); m_Floodfills.remove (it->second); + m_RouterInfos.erase (it); } // insert own router m_RouterInfos.emplace (i2p::context.GetIdentHash (), i2p::context.GetSharedRouterInfo ()); diff --git a/libi2pd/SSUData.cpp b/libi2pd/SSUData.cpp index 6bf01778..2f93218e 100644 --- a/libi2pd/SSUData.cpp +++ b/libi2pd/SSUData.cpp @@ -185,7 +185,7 @@ namespace transport auto& incompleteMessage = it->second; // mark fragment as received if (fragmentNum < 64) - incompleteMessage->receivedFragmentsBits |= (0x01 << fragmentNum); + incompleteMessage->receivedFragmentsBits |= (uint64_t(0x01) << fragmentNum); else LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); From ae0cf2e8311cd2b8cdaa5a0c9f33907a6b82bfeb Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 20 Oct 2021 21:05:22 -0400 Subject: [PATCH 4459/6300] use memory pool for tunnel messages --- libi2pd/I2NPProtocol.cpp | 12 +++--------- libi2pd/Tunnel.cpp | 16 +++++++++++++++- libi2pd/Tunnel.h | 6 ++++++ libi2pd/util.h | 38 ++++++++++++++++++++++++++++++++------ 4 files changed, 56 insertions(+), 16 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index 0bc721ec..fa4fa7a6 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -38,20 +38,14 @@ namespace i2p std::shared_ptr NewI2NPTunnelMessage (bool endpoint) { - I2NPMessage * msg = nullptr; if (endpoint) - { - // should fit two tunnel message + tunnel gateway header, enough for one garlic encrypted streaming packet - msg = new I2NPMessageBuffer<2*i2p::tunnel::TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE + 28>(); // reserved for alignment and NTCP 16 + 6 + 6 - msg->Align (6); - msg->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header - } + return i2p::tunnel::tunnels.NewI2NPTunnelMessage (); else { - msg = new I2NPMessageBuffer(); // reserved for alignment and NTCP 16 + 6 + 12 + auto msg = new I2NPMessageBuffer(); // reserved for alignment and NTCP 16 + 6 + 12 msg->Align (12); + return std::shared_ptr(msg); } - return std::shared_ptr(msg); } std::shared_ptr NewI2NPMessage (size_t len) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index c8a1cc99..3de91940 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -488,7 +488,7 @@ namespace tunnel i2p::util::SetThreadName("Tunnels"); std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready - uint64_t lastTs = 0, lastPoolsTs = 0; + uint64_t lastTs = 0, lastPoolsTs = 0, lastMemoryPoolTs = 0; while (m_IsRunning) { try @@ -564,6 +564,11 @@ namespace tunnel ManageTunnelPools (ts); lastPoolsTs = ts; } + if (ts - lastMemoryPoolTs >= 120) // manage memory pool every 2 minutes + { + m_I2NPTunnelMessagesMemoryPool.CleanUpMt (); + lastMemoryPoolTs = ts; + } } } catch (std::exception& ex) @@ -935,6 +940,15 @@ namespace tunnel return outboundTunnel; } + std::shared_ptr Tunnels::NewI2NPTunnelMessage () + { + // should fit two tunnel message + tunnel gateway header, enough for one garlic encrypted streaming packet + auto msg = m_I2NPTunnelMessagesMemoryPool.AcquireSharedMt (); + msg->Align (6); + msg->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header + return msg; + } + int Tunnels::GetTransitTunnelsExpirationTimeout () { int timeout = 0; diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 930d0c58..30d9d565 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -18,6 +18,7 @@ #include #include #include +#include "util.h" #include "Queue.h" #include "Crypto.h" #include "TunnelConfig.h" @@ -40,6 +41,8 @@ namespace tunnel const int MAX_NUM_RECORDS = 8; const int HIGH_LATENCY_PER_HOP = 250; // in milliseconds + const size_t I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE = 2*TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE + 28; // reserved for alignment and NTCP 16 + 6 + 6 + enum TunnelState { eTunnelStatePending, @@ -219,6 +222,8 @@ namespace tunnel void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); + std::shared_ptr NewI2NPTunnelMessage (); + private: template @@ -257,6 +262,7 @@ namespace tunnel std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; + i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; // some stats int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; diff --git a/libi2pd/util.h b/libi2pd/util.h index 282ce7aa..d681829c 100644 --- a/libi2pd/util.h +++ b/libi2pd/util.h @@ -56,12 +56,8 @@ namespace util void CleanUp () { - while (m_Head) - { - auto tmp = m_Head; - m_Head = static_cast(*(void * *)m_Head); // next - ::operator delete ((void *)tmp); - } + CleanUp (m_Head); + m_Head = nullptr; } template @@ -98,6 +94,18 @@ namespace util std::bind (&MemoryPool::Release, this, std::placeholders::_1)); } + protected: + + void CleanUp (T * head) + { + while (head) + { + auto tmp = head; + head = static_cast(*(void * *)head); // next + ::operator delete ((void *)tmp); + } + } + protected: T * m_Head; @@ -131,6 +139,24 @@ namespace util this->Release (it); } + template + std::shared_ptr AcquireSharedMt (TArgs&&... args) + { + return std::shared_ptr(AcquireMt (std::forward(args)...), + std::bind::*)(T *)> (&MemoryPoolMt::ReleaseMt, this, std::placeholders::_1)); + } + + void CleanUpMt () + { + T * head; + { + std::lock_guard l(m_Mutex); + head = this->m_Head; + this->m_Head = nullptr; + } + if (head) this->CleanUp (head); + } + private: std::mutex m_Mutex; From b0f043ec863cfe48332afc5edffc37a949e4ae5c Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 22 Oct 2021 05:28:59 +0300 Subject: [PATCH 4460/6300] [make] USE_GIT_VERSION option to use commit info in version (closes #1702) Signed-off-by: R4SAS --- Makefile | 21 ++++++++++++++++----- daemon/Daemon.cpp | 2 +- libi2pd/version.h | 7 ++++++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 59b1df1a..f1258874 100644 --- a/Makefile +++ b/Makefile @@ -29,11 +29,17 @@ DAEMON_SRC_DIR := daemon # import source files lists include filelist.mk -USE_AESNI := $(or $(USE_AESNI),yes) -USE_STATIC := $(or $(USE_STATIC),no) -USE_MESHNET := $(or $(USE_MESHNET),no) -USE_UPNP := $(or $(USE_UPNP),no) -DEBUG := $(or $(DEBUG),yes) +USE_AESNI := $(or $(USE_AESNI),yes) +USE_STATIC := $(or $(USE_STATIC),no) +USE_MESHNET := $(or $(USE_MESHNET),no) +USE_UPNP := $(or $(USE_UPNP),no) +DEBUG := $(or $(DEBUG),yes) + +# for debugging purposes only, when commit hash needed in trunk builds in i2pd version string +USE_GIT_VERSION := $(or $(USE_GIT_VERSION),no) + +# for MacOS only, waiting for "1", not "yes" +HOMEBREW := $(or $(HOMEBREW),0) ifeq ($(DEBUG),yes) CXX_DEBUG = -g @@ -66,6 +72,11 @@ ifeq ($(USE_MESHNET),yes) NEEDED_CXXFLAGS += -DMESHNET endif +ifeq ($(USE_GIT_VERSION),yes) + GIT_VERSION := $(shell git describe --tags) + NEEDED_CXXFLAGS += -DGITVER=\"$(GIT_VERSION)\" +endif + NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -I$(LANG_SRC_DIR) LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 445c4dfd..8cee7852 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -134,7 +134,7 @@ namespace util // use stdout -- default } - LogPrint(eLogNone, "i2pd v", VERSION, " starting"); + LogPrint(eLogNone, "i2pd v", VERSION, " (", I2P_VERSION, ") starting"); LogPrint(eLogDebug, "FS: main config file: ", config); LogPrint(eLogDebug, "FS: data directory: ", datadir); LogPrint(eLogDebug, "FS: certificates directory: ", certsdir); diff --git a/libi2pd/version.h b/libi2pd/version.h index 97351c40..5aba36bd 100644 --- a/libi2pd/version.h +++ b/libi2pd/version.h @@ -19,7 +19,12 @@ #define I2PD_VERSION_MINOR 39 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 -#define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) +#ifdef GITVER + #define I2PD_VERSION GITVER +#else + #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) +#endif + #define VERSION I2PD_VERSION #ifdef MESHNET From 0a62a962d7d702d66acab122902521ae41d734b7 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Fri, 22 Oct 2021 05:57:04 +0300 Subject: [PATCH 4461/6300] [debian] update upnp patch Signed-off-by: R4SAS --- debian/patches/02-upnp.patch | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/debian/patches/02-upnp.patch b/debian/patches/02-upnp.patch index 75ea2bfa..99698737 100644 --- a/debian/patches/02-upnp.patch +++ b/debian/patches/02-upnp.patch @@ -2,16 +2,16 @@ Description: Enable UPnP usage in package Author: r4sas Reviewed-By: r4sas -Last-Update: 2021-01-16 +Last-Update: 2021-10-22 --- i2pd.orig/Makefile +++ i2pd/Makefile -@@ -21,7 +21,7 @@ include filelist.mk - USE_AESNI := $(or $(USE_AESNI),yes) - USE_STATIC := $(or $(USE_STATIC),no) - USE_MESHNET := $(or $(USE_MESHNET),no) --USE_UPNP := $(or $(USE_UPNP),no) -+USE_UPNP := $(or $(USE_UPNP),yes) - DEBUG := $(or $(DEBUG),yes) +@@ -32,7 +32,7 @@ include filelist.mk + USE_AESNI := $(or $(USE_AESNI),yes) + USE_STATIC := $(or $(USE_STATIC),no) + USE_MESHNET := $(or $(USE_MESHNET),no) +-USE_UPNP := $(or $(USE_UPNP),no) ++USE_UPNP := $(or $(USE_UPNP),yes) + DEBUG := $(or $(DEBUG),yes) - ifeq ($(DEBUG),yes) + # for debugging purposes only, when commit hash needed in trunk builds in i2pd version string From cdc8e463b7e393458921d2d3788b0dd99feabc6d Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 22 Oct 2021 19:18:45 -0400 Subject: [PATCH 4462/6300] use memory pool for outgoing tunnel gateway messages --- libi2pd/I2NPProtocol.cpp | 9 +-------- libi2pd/Tunnel.cpp | 22 ++++++++++++++++------ libi2pd/Tunnel.h | 6 ++++-- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/libi2pd/I2NPProtocol.cpp b/libi2pd/I2NPProtocol.cpp index fa4fa7a6..bdd65d30 100644 --- a/libi2pd/I2NPProtocol.cpp +++ b/libi2pd/I2NPProtocol.cpp @@ -38,14 +38,7 @@ namespace i2p std::shared_ptr NewI2NPTunnelMessage (bool endpoint) { - if (endpoint) - return i2p::tunnel::tunnels.NewI2NPTunnelMessage (); - else - { - auto msg = new I2NPMessageBuffer(); // reserved for alignment and NTCP 16 + 6 + 12 - msg->Align (12); - return std::shared_ptr(msg); - } + return i2p::tunnel::tunnels.NewI2NPTunnelMessage (endpoint); } std::shared_ptr NewI2NPMessage (size_t len) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index 3de91940..a315ff49 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -566,6 +566,7 @@ namespace tunnel } if (ts - lastMemoryPoolTs >= 120) // manage memory pool every 2 minutes { + m_I2NPTunnelEndpointMessagesMemoryPool.CleanUpMt (); m_I2NPTunnelMessagesMemoryPool.CleanUpMt (); lastMemoryPoolTs = ts; } @@ -940,13 +941,22 @@ namespace tunnel return outboundTunnel; } - std::shared_ptr Tunnels::NewI2NPTunnelMessage () + std::shared_ptr Tunnels::NewI2NPTunnelMessage (bool endpoint) { - // should fit two tunnel message + tunnel gateway header, enough for one garlic encrypted streaming packet - auto msg = m_I2NPTunnelMessagesMemoryPool.AcquireSharedMt (); - msg->Align (6); - msg->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header - return msg; + if (endpoint) + { + // should fit two tunnel message + tunnel gateway header, enough for one garlic encrypted streaming packet + auto msg = m_I2NPTunnelEndpointMessagesMemoryPool.AcquireSharedMt (); + msg->Align (6); + msg->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header + return msg; + } + else + { + auto msg = m_I2NPTunnelMessagesMemoryPool.AcquireSharedMt (); + msg->Align (12); + return msg; + } } int Tunnels::GetTransitTunnelsExpirationTimeout () diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 30d9d565..56166e0b 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -41,6 +41,7 @@ namespace tunnel const int MAX_NUM_RECORDS = 8; const int HIGH_LATENCY_PER_HOP = 250; // in milliseconds + const size_t I2NP_TUNNEL_MESSAGE_SIZE = TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + 34; // reserved for alignment and NTCP 16 + 6 + 12 const size_t I2NP_TUNNEL_ENPOINT_MESSAGE_SIZE = 2*TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE + 28; // reserved for alignment and NTCP 16 + 6 + 6 enum TunnelState @@ -222,7 +223,7 @@ namespace tunnel void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); - std::shared_ptr NewI2NPTunnelMessage (); + std::shared_ptr NewI2NPTunnelMessage (bool endpoint); private: @@ -262,7 +263,8 @@ namespace tunnel std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; - i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; + i2p::util::MemoryPoolMt > m_I2NPTunnelEndpointMessagesMemoryPool; + i2p::util::MemoryPoolMt > m_I2NPTunnelMessagesMemoryPool; // some stats int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; From f1990bc2ab1c6b9cc32c5f7a07b7a9d08ae0c8f1 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 22 Oct 2021 21:08:20 -0400 Subject: [PATCH 4463/6300] use tunnel endpoint memroy pool to split to tunnel messages at gateway --- libi2pd/TunnelGateway.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libi2pd/TunnelGateway.cpp b/libi2pd/TunnelGateway.cpp index 08df569c..c9ad0357 100644 --- a/libi2pd/TunnelGateway.cpp +++ b/libi2pd/TunnelGateway.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -158,8 +158,7 @@ namespace tunnel void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage () { m_CurrentTunnelDataMsg = nullptr; - m_CurrentTunnelDataMsg = NewI2NPShortMessage (); - m_CurrentTunnelDataMsg->Align (12); + m_CurrentTunnelDataMsg = NewI2NPTunnelMessage (true); // tunnel endpoint is at least of two tunnel messages size // we reserve space for padding m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE; m_CurrentTunnelDataMsg->len = m_CurrentTunnelDataMsg->offset; From 921ec9ec12ead861994c98d96087b93a31051ef5 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 23 Oct 2021 18:10:02 -0400 Subject: [PATCH 4464/6300] fix build with openssl 3.0.0 --- libi2pd/Crypto.cpp | 2 +- libi2pd/Reseed.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp index 427bbbd6..d38d489a 100644 --- a/libi2pd/Crypto.cpp +++ b/libi2pd/Crypto.cpp @@ -1277,7 +1277,7 @@ namespace crypto EVP_PKEY_CTX_set1_hkdf_key (pctx, tempKey, len); } if (info.length () > 0) - EVP_PKEY_CTX_add1_hkdf_info (pctx, info.c_str (), info.length ()); + EVP_PKEY_CTX_add1_hkdf_info (pctx, (const uint8_t *)info.c_str (), info.length ()); EVP_PKEY_derive (pctx, out, &outLen); EVP_PKEY_CTX_free (pctx); #else diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index aec683d4..2e8eccf7 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -478,7 +478,7 @@ namespace data if (terminator) terminator[0] = 0; } // extract RSA key (we need n only, e = 65537) - RSA * key = EVP_PKEY_get0_RSA (X509_get_pubkey (cert)); + const RSA * key = EVP_PKEY_get0_RSA (X509_get_pubkey (cert)); const BIGNUM * n, * e, * d; RSA_get0_key(key, &n, &e, &d); PublicKey value; From 9965d72990b704a8c724fb6f5d3a951fce8ca4aa Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 26 Oct 2021 21:36:34 -0400 Subject: [PATCH 4465/6300] don't store EVP_PKEY with EdDSA signer and verifier --- libi2pd/Signature.cpp | 27 +++++++++++---------------- libi2pd/Signature.h | 5 ++--- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/libi2pd/Signature.cpp b/libi2pd/Signature.cpp index 88ee4060..28ae5fa6 100644 --- a/libi2pd/Signature.cpp +++ b/libi2pd/Signature.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,8 +15,7 @@ namespace i2p namespace crypto { #if OPENSSL_EDDSA - EDDSA25519Verifier::EDDSA25519Verifier (): - m_Pkey (nullptr) + EDDSA25519Verifier::EDDSA25519Verifier () { m_MDCtx = EVP_MD_CTX_create (); } @@ -24,13 +23,13 @@ namespace crypto EDDSA25519Verifier::~EDDSA25519Verifier () { EVP_MD_CTX_destroy (m_MDCtx); - if (m_Pkey) EVP_PKEY_free (m_Pkey); } void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey) { - m_Pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); - EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, m_Pkey); + EVP_PKEY * pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32); + EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, pkey); + EVP_PKEY_free (pkey); } bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const @@ -100,33 +99,29 @@ namespace crypto #if OPENSSL_EDDSA EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey): - m_Fallback (nullptr) + m_MDCtx (nullptr), m_Fallback (nullptr) { - m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); + EVP_PKEY * pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32); uint8_t publicKey[EDDSA25519_PUBLIC_KEY_LENGTH]; size_t len = EDDSA25519_PUBLIC_KEY_LENGTH; - EVP_PKEY_get_raw_public_key (m_Pkey, publicKey, &len); + EVP_PKEY_get_raw_public_key (pkey, publicKey, &len); if (signingPublicKey && memcmp (publicKey, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH)) { LogPrint (eLogWarning, "EdDSA public key mismatch. Fallback"); - EVP_PKEY_free (m_Pkey); m_Fallback = new EDDSA25519SignerCompat (signingPrivateKey, signingPublicKey); } else { m_MDCtx = EVP_MD_CTX_create (); - EVP_DigestSignInit (m_MDCtx, NULL, NULL, NULL, m_Pkey); + EVP_DigestSignInit (m_MDCtx, NULL, NULL, NULL, pkey); } + EVP_PKEY_free (pkey); } EDDSA25519Signer::~EDDSA25519Signer () { if (m_Fallback) delete m_Fallback; - else - { - EVP_MD_CTX_destroy (m_MDCtx); - EVP_PKEY_free (m_Pkey); - } + EVP_MD_CTX_destroy (m_MDCtx); } void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const diff --git a/libi2pd/Signature.h b/libi2pd/Signature.h index 18084603..caf9b274 100644 --- a/libi2pd/Signature.h +++ b/libi2pd/Signature.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2021, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -304,7 +304,6 @@ namespace crypto private: #if OPENSSL_EDDSA - EVP_PKEY * m_Pkey; EVP_MD_CTX * m_MDCtx; #else EDDSAPoint m_PublicKey; @@ -341,7 +340,7 @@ namespace crypto void Sign (const uint8_t * buf, int len, uint8_t * signature) const; private: - EVP_PKEY * m_Pkey; + EVP_MD_CTX * m_MDCtx; EDDSA25519SignerCompat * m_Fallback; }; From bb8dc679421ae8e116ebc4a1bc4648e6887d4c62 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 27 Oct 2021 19:05:16 -0400 Subject: [PATCH 4466/6300] don't use openssl's SipHash from 3.0.0 due regression --- libi2pd/Crypto.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h index 5f42b527..75942905 100644 --- a/libi2pd/Crypto.h +++ b/libi2pd/Crypto.h @@ -39,7 +39,9 @@ # define OPENSSL_HKDF 1 # define OPENSSL_EDDSA 1 # define OPENSSL_X25519 1 -# define OPENSSL_SIPHASH 1 +# if (OPENSSL_VERSION_NUMBER < 0x030000000) // 3.0.0, regression in SipHash +# define OPENSSL_SIPHASH 1 +# endif # endif # if !defined OPENSSL_NO_CHACHA && !defined OPENSSL_NO_POLY1305 // some builds might not include them # define OPENSSL_AEAD_CHACHA20_POLY1305 1 From 8566f6c127eb54825cdbabefa32c25a611c5f241 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 27 Oct 2021 21:18:21 -0400 Subject: [PATCH 4467/6300] don't store EVP_PKEY sip keys --- libi2pd/NTCP2.cpp | 19 ++++++++++--------- libi2pd/NTCP2.h | 1 - 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 9e18c81e..b4f290dd 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -323,9 +323,10 @@ namespace transport m_Server (server), m_Socket (m_Server.GetService ()), m_IsEstablished (false), m_IsTerminated (false), m_Establisher (new NTCP2Establisher), - m_SendSipKey (nullptr), m_ReceiveSipKey (nullptr), #if OPENSSL_SIPHASH m_SendMDCtx(nullptr), m_ReceiveMDCtx (nullptr), +#else + m_SendSipKey (nullptr), m_ReceiveSipKey (nullptr), #endif m_NextReceivedLen (0), m_NextReceivedBuffer (nullptr), m_NextSendBuffer (nullptr), m_NextReceivedBufferSize (0), m_ReceiveSequenceNumber (0), m_SendSequenceNumber (0), @@ -352,8 +353,6 @@ namespace transport delete[] m_NextReceivedBuffer; delete[] m_NextSendBuffer; #if OPENSSL_SIPHASH - if (m_SendSipKey) EVP_PKEY_free (m_SendSipKey); - if (m_ReceiveSipKey) EVP_PKEY_free (m_ReceiveSipKey); if (m_SendMDCtx) EVP_MD_CTX_destroy (m_SendMDCtx); if (m_ReceiveMDCtx) EVP_MD_CTX_destroy (m_ReceiveMDCtx); #endif @@ -711,17 +710,19 @@ namespace transport void NTCP2Session::SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey) { #if OPENSSL_SIPHASH - m_SendSipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, sendSipKey, 16); + EVP_PKEY * sipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, sendSipKey, 16); m_SendMDCtx = EVP_MD_CTX_create (); EVP_PKEY_CTX *ctx = nullptr; - EVP_DigestSignInit (m_SendMDCtx, &ctx, nullptr, nullptr, m_SendSipKey); + EVP_DigestSignInit (m_SendMDCtx, &ctx, nullptr, nullptr, sipKey); EVP_PKEY_CTX_ctrl (ctx, -1, EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_DIGEST_SIZE, 8, nullptr); - - m_ReceiveSipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, receiveSipKey, 16); + EVP_PKEY_free (sipKey); + + sipKey = EVP_PKEY_new_raw_private_key (EVP_PKEY_SIPHASH, nullptr, receiveSipKey, 16); m_ReceiveMDCtx = EVP_MD_CTX_create (); ctx = nullptr; - EVP_DigestSignInit (m_ReceiveMDCtx, &ctx, NULL, NULL, m_ReceiveSipKey); + EVP_DigestSignInit (m_ReceiveMDCtx, &ctx, NULL, NULL, sipKey); EVP_PKEY_CTX_ctrl (ctx, -1, EVP_PKEY_OP_SIGNCTX, EVP_PKEY_CTRL_SET_DIGEST_SIZE, 8, nullptr); + EVP_PKEY_free (sipKey); #else m_SendSipKey = sendSipKey; m_ReceiveSipKey = receiveSipKey; @@ -1114,7 +1115,7 @@ namespace transport void NTCP2Session::SendTermination (NTCP2TerminationReason reason) { - if (!m_SendKey || !m_SendSipKey) return; + if (!m_SendKey) return; m_NextSendBuffer = new uint8_t[49]; // 49 = 12 bytes message + 16 bytes MAC + 2 bytes size + up to 19 padding block // termination block m_NextSendBuffer[2] = eNTCP2BlkTermination; diff --git a/libi2pd/NTCP2.h b/libi2pd/NTCP2.h index f9f3e751..3516ce6a 100644 --- a/libi2pd/NTCP2.h +++ b/libi2pd/NTCP2.h @@ -205,7 +205,6 @@ namespace transport uint8_t m_Kab[32], m_Kba[32], m_Sipkeysab[32], m_Sipkeysba[32]; const uint8_t * m_SendKey, * m_ReceiveKey; #if OPENSSL_SIPHASH - EVP_PKEY * m_SendSipKey, * m_ReceiveSipKey; EVP_MD_CTX * m_SendMDCtx, * m_ReceiveMDCtx; #else const uint8_t * m_SendSipKey, * m_ReceiveSipKey; From 876e98d91e2cf75f1c856e1d8ec2e3efac401ba9 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 27 Oct 2021 22:23:32 -0400 Subject: [PATCH 4468/6300] check if sip key is available --- libi2pd/NTCP2.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index b4f290dd..03aac04d 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -910,12 +910,14 @@ namespace transport void NTCP2Session::SetNextSentFrameLength (size_t frameLen, uint8_t * lengthBuf) { - #if OPENSSL_SIPHASH +#if OPENSSL_SIPHASH + if (!m_SendMDCtx) return; EVP_DigestSignInit (m_SendMDCtx, nullptr, nullptr, nullptr, nullptr); EVP_DigestSignUpdate (m_SendMDCtx, m_SendIV.buf, 8); size_t l = 8; - EVP_DigestSignFinal (m_SendMDCtx, m_SendIV.buf, &l); + EVP_DigestSignFinal (m_SendMDCtx, m_SendIV.buf, &l); #else + if (!m_SendSipKey) return; i2p::crypto::Siphash<8> (m_SendIV.buf, m_SendIV.buf, 8, m_SendSipKey); #endif // length must be in BigEndian From 26db88d89be2cd9cba842593c8d597e768b5fa05 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 27 Oct 2021 22:33:37 -0400 Subject: [PATCH 4469/6300] check if sip key is available --- libi2pd/NTCP2.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index 03aac04d..e592a7f2 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -911,13 +911,11 @@ namespace transport void NTCP2Session::SetNextSentFrameLength (size_t frameLen, uint8_t * lengthBuf) { #if OPENSSL_SIPHASH - if (!m_SendMDCtx) return; EVP_DigestSignInit (m_SendMDCtx, nullptr, nullptr, nullptr, nullptr); EVP_DigestSignUpdate (m_SendMDCtx, m_SendIV.buf, 8); size_t l = 8; EVP_DigestSignFinal (m_SendMDCtx, m_SendIV.buf, &l); #else - if (!m_SendSipKey) return; i2p::crypto::Siphash<8> (m_SendIV.buf, m_SendIV.buf, 8, m_SendSipKey); #endif // length must be in BigEndian @@ -1117,7 +1115,13 @@ namespace transport void NTCP2Session::SendTermination (NTCP2TerminationReason reason) { - if (!m_SendKey) return; + if (!m_SendKey || +#if OPENSSL_SIPHASH + !m_SendMDCtx +#else + !m_SendSipKey +#endif + ) return; m_NextSendBuffer = new uint8_t[49]; // 49 = 12 bytes message + 16 bytes MAC + 2 bytes size + up to 19 padding block // termination block m_NextSendBuffer[2] = eNTCP2BlkTermination; From 7073a6bf384e763b46dc891d0091f69d295bca58 Mon Sep 17 00:00:00 2001 From: Simon Vetter Date: Thu, 28 Oct 2021 21:59:27 +0200 Subject: [PATCH 4470/6300] libi2pd: make Tunnel and TunnelConfig destructors virtual --- libi2pd/Tunnel.h | 2 +- libi2pd/TunnelConfig.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 56166e0b..80d29a15 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -68,7 +68,7 @@ namespace tunnel public: Tunnel (std::shared_ptr config); - ~Tunnel (); + virtual ~Tunnel (); void Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel = nullptr); diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index f198dffd..ae924ba6 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -99,7 +99,7 @@ namespace tunnel m_LastHop->SetReplyHop (replyTunnelID, replyIdent); } - ~TunnelConfig () + virtual ~TunnelConfig () { TunnelHopConfig * hop = m_FirstHop; From 1de1c79d4f72a1c7aa3ad51e6a582df5c8302c39 Mon Sep 17 00:00:00 2001 From: Simon Vetter Date: Sun, 31 Oct 2021 14:51:41 +0100 Subject: [PATCH 4471/6300] libi2pd: add missing locks to i2p::tunnel::Tunnels m_InboundTunnelsMutex, m_OutboundTunnelsMutex and m_PoolsMutex have been changed to recursive_mutexes since they can be acquired multiple times by the same thread. --- libi2pd/Tunnel.cpp | 33 ++++++++++++++++++++++++++++----- libi2pd/Tunnel.h | 8 +++++++- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index a315ff49..f3cdd432 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -366,6 +366,7 @@ namespace tunnel std::shared_ptr Tunnels::GetTunnel (uint32_t tunnelID) { + std::unique_lock l(m_TunnelsMutex); auto it = m_Tunnels.find(tunnelID); if (it != m_Tunnels.end ()) return it->second; @@ -374,11 +375,13 @@ namespace tunnel std::shared_ptr Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID) { + std::unique_lock l(m_PendingInboundTunnelsMutex); return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels); } std::shared_ptr Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID) { + std::unique_lock l(m_PendingOutboundTunnelsMutex); return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels); } @@ -459,9 +462,11 @@ namespace tunnel void Tunnels::AddTransitTunnel (std::shared_ptr tunnel) { - if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) + std::unique_lock l(m_TunnelsMutex); + if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) { + std::unique_lock l(m_TransitTunnelsMutex); m_TransitTunnels.push_back (tunnel); - else + } else LogPrint (eLogError, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " already exists"); } @@ -616,7 +621,10 @@ namespace tunnel void Tunnels::ManagePendingTunnels () { + std::unique_lock li(m_PendingInboundTunnelsMutex); ManagePendingTunnels (m_PendingInboundTunnels); + li.unlock(); + std::unique_lock lo(m_PendingOutboundTunnelsMutex); ManagePendingTunnels (m_PendingOutboundTunnels); } @@ -676,6 +684,7 @@ namespace tunnel void Tunnels::ManageOutboundTunnels () { + std::unique_lock l(m_OutboundTunnelsMutex); uint64_t ts = i2p::util::GetSecondsSinceEpoch (); { for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) @@ -730,6 +739,8 @@ namespace tunnel void Tunnels::ManageInboundTunnels () { + std::unique_lock lt(m_TunnelsMutex); + std::unique_lock li(m_InboundTunnelsMutex); uint64_t ts = i2p::util::GetSecondsSinceEpoch (); { for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) @@ -786,6 +797,7 @@ namespace tunnel return; } + std::unique_lock lo(m_OutboundTunnelsMutex); if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 3) { // trying to create one more inbound tunnel @@ -806,6 +818,8 @@ namespace tunnel void Tunnels::ManageTransitTunnels () { + std::unique_lock lt(m_TunnelsMutex); + std::unique_lock l(m_TransitTunnelsMutex); uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();) { @@ -876,17 +890,20 @@ namespace tunnel void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { + std::unique_lock l(m_PendingInboundTunnelsMutex); m_PendingInboundTunnels[replyMsgID] = tunnel; } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { + std::unique_lock l(m_PendingOutboundTunnelsMutex); m_PendingOutboundTunnels[replyMsgID] = tunnel; } void Tunnels::AddOutboundTunnel (std::shared_ptr newTunnel) { // we don't need to insert it to m_Tunnels + std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (pool && pool->IsActive ()) @@ -897,8 +914,10 @@ namespace tunnel void Tunnels::AddInboundTunnel (std::shared_ptr newTunnel) { + std::unique_lock l(m_TunnelsMutex); if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second) { + std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (!pool) @@ -926,6 +945,8 @@ namespace tunnel auto inboundTunnel = std::make_shared (); inboundTunnel->SetTunnelPool (pool); inboundTunnel->SetState (eTunnelStateEstablished); + std::unique_lock lt(m_TunnelsMutex); + std::unique_lock li(m_InboundTunnelsMutex); m_InboundTunnels.push_back (inboundTunnel); m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; return inboundTunnel; @@ -936,6 +957,7 @@ namespace tunnel auto outboundTunnel = std::make_shared (); outboundTunnel->SetTunnelPool (pool); outboundTunnel->SetState (eTunnelStateEstablished); + std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.push_back (outboundTunnel); // we don't insert into m_Tunnels return outboundTunnel; @@ -964,6 +986,7 @@ namespace tunnel int timeout = 0; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); // TODO: possible race condition with I2PControl + std::unique_lock l(m_TransitTunnelsMutex); for (const auto& it : m_TransitTunnels) { int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; @@ -974,19 +997,19 @@ namespace tunnel size_t Tunnels::CountTransitTunnels() const { - // TODO: locking + std::unique_lock l(m_TransitTunnelsMutex); return m_TransitTunnels.size(); } size_t Tunnels::CountInboundTunnels() const { - // TODO: locking + std::unique_lock l(m_InboundTunnelsMutex); return m_InboundTunnels.size(); } size_t Tunnels::CountOutboundTunnels() const { - // TODO: locking + std::unique_lock l(m_OutboundTunnelsMutex); return m_OutboundTunnels.size(); } } diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 80d29a15..640a9ca7 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -254,12 +254,18 @@ namespace tunnel bool m_IsRunning; std::thread * m_Thread; std::map > m_PendingInboundTunnels; // by replyMsgID + mutable std::mutex m_PendingInboundTunnelsMutex; std::map > m_PendingOutboundTunnels; // by replyMsgID + mutable std::mutex m_PendingOutboundTunnelsMutex; std::list > m_InboundTunnels; + mutable std::recursive_mutex m_InboundTunnelsMutex; std::list > m_OutboundTunnels; + mutable std::recursive_mutex m_OutboundTunnelsMutex; std::list > m_TransitTunnels; + mutable std::mutex m_TransitTunnelsMutex; std::unordered_map > m_Tunnels; // tunnelID->tunnel known by this id - std::mutex m_PoolsMutex; + mutable std::recursive_mutex m_TunnelsMutex; + mutable std::mutex m_PoolsMutex; std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; From 58b7b7d7317df28a40a6bc620761eb218d551a02 Mon Sep 17 00:00:00 2001 From: Simon Vetter Date: Sat, 30 Oct 2021 23:14:48 +0200 Subject: [PATCH 4472/6300] libi2pd: add missing locks to i2p::tunnel::TunnelPool --- libi2pd/TunnelPool.cpp | 46 +++++++++++++++++++++++++++++++++--------- libi2pd/TunnelPool.h | 6 ++++-- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 5ed330c8..4d00ad6f 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -59,6 +59,7 @@ namespace tunnel void TunnelPool::SetExplicitPeers (std::shared_ptr > explicitPeers) { + std::unique_lock l(m_ExplicitPeersMutex); m_ExplicitPeers = explicitPeers; if (m_ExplicitPeers) { @@ -92,6 +93,7 @@ namespace tunnel it->SetTunnelPool (nullptr); m_OutboundTunnels.clear (); } + std::unique_lock l(m_TestsMutex); m_Tests.clear (); } @@ -126,6 +128,7 @@ namespace tunnel } m_InboundTunnels.insert (createdTunnel); } + std::unique_lock l(m_LocalDestinationMutex); if (m_LocalDestination) m_LocalDestination->SetLeaseSetUpdated (); } @@ -135,10 +138,11 @@ namespace tunnel if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); + std::unique_lock lt(m_TestsMutex); for (auto& it: m_Tests) if (it.second.second == expiredTunnel) it.second.second = nullptr; - std::unique_lock l(m_InboundTunnelsMutex); + std::unique_lock li(m_InboundTunnelsMutex); m_InboundTunnels.erase (expiredTunnel); } } @@ -157,10 +161,11 @@ namespace tunnel if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); + std::unique_lock lt(m_TestsMutex); for (auto& it: m_Tests) if (it.second.first == expiredTunnel) it.second.first = nullptr; - std::unique_lock l(m_OutboundTunnelsMutex); + std::unique_lock lo(m_OutboundTunnelsMutex); m_OutboundTunnels.erase (expiredTunnel); } } @@ -261,11 +266,21 @@ namespace tunnel return tunnel; } + std::shared_ptr TunnelPool::GetLocalDestination () const { + std::unique_lock l(m_LocalDestinationMutex); + return m_LocalDestination; + } + + void TunnelPool::SetLocalDestination (std::shared_ptr destination) { + std::unique_lock l(m_LocalDestinationMutex); + m_LocalDestination = destination; + } + void TunnelPool::CreateTunnels () { int num = 0; { - std::unique_lock l(m_OutboundTunnelsMutex); + std::unique_lock lo(m_OutboundTunnelsMutex); for (const auto& it : m_OutboundTunnels) if (it->IsEstablished ()) num++; } @@ -274,10 +289,11 @@ namespace tunnel num = 0; { - std::unique_lock l(m_InboundTunnelsMutex); + std::unique_lock li(m_InboundTunnelsMutex); for (const auto& it : m_InboundTunnels) if (it->IsEstablished ()) num++; } + std::unique_lock lo(m_OutboundTunnelsMutex); if (!num && !m_OutboundTunnels.empty () && m_NumOutboundHops > 0) { for (auto it: m_OutboundTunnels) @@ -287,9 +303,11 @@ namespace tunnel if (num >= m_NumInboundTunnels) break; } } + lo.unlock(); for (int i = num; i < m_NumInboundTunnels; i++) CreateInboundTunnel (); + std::unique_lock l(m_LocalDestinationMutex); if (num < m_NumInboundTunnels && m_NumInboundHops <= 0 && m_LocalDestination) // zero hops IB m_LocalDestination->SetLeaseSetUpdated (); // update LeaseSet immediately } @@ -311,7 +329,7 @@ namespace tunnel if (it.second.first->GetState () == eTunnelStateTestFailed) { it.second.first->SetState (eTunnelStateFailed); - std::unique_lock l(m_OutboundTunnelsMutex); + std::unique_lock lo(m_OutboundTunnelsMutex); m_OutboundTunnels.erase (it.second.first); } else @@ -323,9 +341,10 @@ namespace tunnel { it.second.second->SetState (eTunnelStateFailed); { - std::unique_lock l(m_InboundTunnelsMutex); + std::unique_lock li(m_InboundTunnelsMutex); m_InboundTunnels.erase (it.second.second); } + std::unique_lock ld(m_LocalDestinationMutex); if (m_LocalDestination) m_LocalDestination->SetLeaseSetUpdated (); } @@ -335,7 +354,10 @@ namespace tunnel } // new tests + std::unique_lock lt(m_TestsMutex); + std::unique_lock lo(m_OutboundTunnelsMutex); auto it1 = m_OutboundTunnels.begin (); + std::unique_lock li(m_InboundTunnelsMutex); auto it2 = m_InboundTunnels.begin (); while (it1 != m_OutboundTunnels.end () && it2 != m_InboundTunnels.end ()) { @@ -355,7 +377,6 @@ namespace tunnel uint32_t msgID; RAND_bytes ((uint8_t *)&msgID, 4); { - std::unique_lock l(m_TestsMutex); m_Tests[msgID] = std::make_pair (*it1, *it2); } (*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), @@ -377,10 +398,11 @@ namespace tunnel void TunnelPool::ProcessGarlicMessage (std::shared_ptr msg) { + std::unique_lock l(m_LocalDestinationMutex); if (m_LocalDestination) m_LocalDestination->ProcessGarlicMessage (msg); else - LogPrint (eLogWarning, "Tunnels: local destination doesn't exist, dropped"); + LogPrint (eLogWarning, "Tunnels: local destination doesn't exist, garlic message dropped"); } void TunnelPool::ProcessDeliveryStatus (std::shared_ptr msg) @@ -425,10 +447,11 @@ namespace tunnel } else { + std::unique_lock l(m_LocalDestinationMutex); if (m_LocalDestination) m_LocalDestination->ProcessDeliveryStatusMessage (msg); else - LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped"); + LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, delivery status message dropped"); } } @@ -512,13 +535,16 @@ namespace tunnel if (m_CustomPeerSelector) return m_CustomPeerSelector->SelectPeers(path, numHops, isInbound); } + // explicit peers in use + std::lock_guard lock(m_ExplicitPeersMutex); if (m_ExplicitPeers) return SelectExplicitPeers (path, isInbound); return StandardSelectPeers(path, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1, std::placeholders::_2)); } bool TunnelPool::SelectExplicitPeers (Path& path, bool isInbound) { + std::unique_lock l(m_ExplicitPeersMutex); int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; if (numHops > (int)m_ExplicitPeers->size ()) numHops = m_ExplicitPeers->size (); if (!numHops) return false; @@ -610,8 +636,10 @@ namespace tunnel Path path; if (SelectPeers (path, false)) { + std::unique_lock ld(m_LocalDestinationMutex); if (m_LocalDestination && !m_LocalDestination->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) path.isShort = false; // because can't handle ECIES encrypted reply + ld.unlock(); std::shared_ptr config; if (m_NumOutboundHops > 0) diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index 875a9955..1069e1b9 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -64,8 +64,8 @@ namespace tunnel TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels); ~TunnelPool (); - std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; - void SetLocalDestination (std::shared_ptr destination) { m_LocalDestination = destination; }; + std::shared_ptr GetLocalDestination () const; + void SetLocalDestination (std::shared_ptr destination); void SetExplicitPeers (std::shared_ptr > explicitPeers); void CreateTunnels (); @@ -126,8 +126,10 @@ namespace tunnel private: + mutable std::mutex m_LocalDestinationMutex; std::shared_ptr m_LocalDestination; int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels; + mutable std::mutex m_ExplicitPeersMutex; std::shared_ptr > m_ExplicitPeers; mutable std::mutex m_InboundTunnelsMutex; std::set, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first From 4c5ec68ff14ea9f144b0cad792956315d4969233 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 1 Nov 2021 02:40:54 +0300 Subject: [PATCH 4473/6300] [win] add menu item for opening datadir Signed-off-by: R4SAS --- Win32/Win32App.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 7104ec7f..377e1076 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -31,6 +31,7 @@ #define ID_RELOAD 2006 #define ID_ACCEPT_TRANSIT 2007 #define ID_DECLINE_TRANSIT 2008 +#define ID_DATADIR 2009 #define ID_TRAY_ICON 2050 #define WM_TRAYICON (WM_USER + 1) @@ -49,7 +50,8 @@ namespace win32 { HMENU hPopup = CreatePopupMenu(); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_CONSOLE, "Open &console"); - InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "Show app"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DATADIR, "Open &datadir"); + InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "&Show app"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); if(!i2p::context.AcceptsTunnels()) @@ -303,6 +305,12 @@ namespace win32 SetTimer(hWnd, FRAME_UPDATE_TIMER, 3000, NULL); return 0; } + case ID_DATADIR: + { + std::string datadir(i2p::fs::GetUTF8DataDir()); + ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL); + return 0; + } } break; } From 56ec8fe95b5490e4773ac6b90d1bdacc89884ad7 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 31 Oct 2021 21:20:16 -0400 Subject: [PATCH 4474/6300] eliminate local destination mutex --- libi2pd/TunnelPool.cpp | 17 ----------------- libi2pd/TunnelPool.h | 5 ++--- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 4d00ad6f..051ecdc6 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -128,7 +128,6 @@ namespace tunnel } m_InboundTunnels.insert (createdTunnel); } - std::unique_lock l(m_LocalDestinationMutex); if (m_LocalDestination) m_LocalDestination->SetLeaseSetUpdated (); } @@ -266,16 +265,6 @@ namespace tunnel return tunnel; } - std::shared_ptr TunnelPool::GetLocalDestination () const { - std::unique_lock l(m_LocalDestinationMutex); - return m_LocalDestination; - } - - void TunnelPool::SetLocalDestination (std::shared_ptr destination) { - std::unique_lock l(m_LocalDestinationMutex); - m_LocalDestination = destination; - } - void TunnelPool::CreateTunnels () { int num = 0; @@ -307,7 +296,6 @@ namespace tunnel for (int i = num; i < m_NumInboundTunnels; i++) CreateInboundTunnel (); - std::unique_lock l(m_LocalDestinationMutex); if (num < m_NumInboundTunnels && m_NumInboundHops <= 0 && m_LocalDestination) // zero hops IB m_LocalDestination->SetLeaseSetUpdated (); // update LeaseSet immediately } @@ -344,7 +332,6 @@ namespace tunnel std::unique_lock li(m_InboundTunnelsMutex); m_InboundTunnels.erase (it.second.second); } - std::unique_lock ld(m_LocalDestinationMutex); if (m_LocalDestination) m_LocalDestination->SetLeaseSetUpdated (); } @@ -398,7 +385,6 @@ namespace tunnel void TunnelPool::ProcessGarlicMessage (std::shared_ptr msg) { - std::unique_lock l(m_LocalDestinationMutex); if (m_LocalDestination) m_LocalDestination->ProcessGarlicMessage (msg); else @@ -447,7 +433,6 @@ namespace tunnel } else { - std::unique_lock l(m_LocalDestinationMutex); if (m_LocalDestination) m_LocalDestination->ProcessDeliveryStatusMessage (msg); else @@ -636,10 +621,8 @@ namespace tunnel Path path; if (SelectPeers (path, false)) { - std::unique_lock ld(m_LocalDestinationMutex); if (m_LocalDestination && !m_LocalDestination->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) path.isShort = false; // because can't handle ECIES encrypted reply - ld.unlock(); std::shared_ptr config; if (m_NumOutboundHops > 0) diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index 1069e1b9..b32bafa3 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -64,8 +64,8 @@ namespace tunnel TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels); ~TunnelPool (); - std::shared_ptr GetLocalDestination () const; - void SetLocalDestination (std::shared_ptr destination); + std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; + void SetLocalDestination (std::shared_ptr destination) { m_LocalDestination = destination; }; void SetExplicitPeers (std::shared_ptr > explicitPeers); void CreateTunnels (); @@ -126,7 +126,6 @@ namespace tunnel private: - mutable std::mutex m_LocalDestinationMutex; std::shared_ptr m_LocalDestination; int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels; mutable std::mutex m_ExplicitPeersMutex; From c6e47581877e6f61ed3293b0f0b9c24eae161bb6 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 1 Nov 2021 05:03:34 +0300 Subject: [PATCH 4475/6300] Revert "Merge pull request #1703 from simonvetter/simon/memory-and-multithreading-fixes" This reverts commit 67863cfcf9784dd638f505b8052abb8070ed8a90, reversing changes made to 4c5ec68ff14ea9f144b0cad792956315d4969233. That change completly bloking transports thread on windows. Signed-off-by: R4SAS --- libi2pd/Tunnel.cpp | 33 +++++---------------------------- libi2pd/Tunnel.h | 10 ++-------- libi2pd/TunnelConfig.h | 2 +- libi2pd/TunnelPool.cpp | 29 +++++++++-------------------- libi2pd/TunnelPool.h | 1 - 5 files changed, 17 insertions(+), 58 deletions(-) diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index f3cdd432..a315ff49 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -366,7 +366,6 @@ namespace tunnel std::shared_ptr Tunnels::GetTunnel (uint32_t tunnelID) { - std::unique_lock l(m_TunnelsMutex); auto it = m_Tunnels.find(tunnelID); if (it != m_Tunnels.end ()) return it->second; @@ -375,13 +374,11 @@ namespace tunnel std::shared_ptr Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID) { - std::unique_lock l(m_PendingInboundTunnelsMutex); return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels); } std::shared_ptr Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID) { - std::unique_lock l(m_PendingOutboundTunnelsMutex); return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels); } @@ -462,11 +459,9 @@ namespace tunnel void Tunnels::AddTransitTunnel (std::shared_ptr tunnel) { - std::unique_lock l(m_TunnelsMutex); - if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) { - std::unique_lock l(m_TransitTunnelsMutex); + if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) m_TransitTunnels.push_back (tunnel); - } else + else LogPrint (eLogError, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " already exists"); } @@ -621,10 +616,7 @@ namespace tunnel void Tunnels::ManagePendingTunnels () { - std::unique_lock li(m_PendingInboundTunnelsMutex); ManagePendingTunnels (m_PendingInboundTunnels); - li.unlock(); - std::unique_lock lo(m_PendingOutboundTunnelsMutex); ManagePendingTunnels (m_PendingOutboundTunnels); } @@ -684,7 +676,6 @@ namespace tunnel void Tunnels::ManageOutboundTunnels () { - std::unique_lock l(m_OutboundTunnelsMutex); uint64_t ts = i2p::util::GetSecondsSinceEpoch (); { for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) @@ -739,8 +730,6 @@ namespace tunnel void Tunnels::ManageInboundTunnels () { - std::unique_lock lt(m_TunnelsMutex); - std::unique_lock li(m_InboundTunnelsMutex); uint64_t ts = i2p::util::GetSecondsSinceEpoch (); { for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) @@ -797,7 +786,6 @@ namespace tunnel return; } - std::unique_lock lo(m_OutboundTunnelsMutex); if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 3) { // trying to create one more inbound tunnel @@ -818,8 +806,6 @@ namespace tunnel void Tunnels::ManageTransitTunnels () { - std::unique_lock lt(m_TunnelsMutex); - std::unique_lock l(m_TransitTunnelsMutex); uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();) { @@ -890,20 +876,17 @@ namespace tunnel void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { - std::unique_lock l(m_PendingInboundTunnelsMutex); m_PendingInboundTunnels[replyMsgID] = tunnel; } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { - std::unique_lock l(m_PendingOutboundTunnelsMutex); m_PendingOutboundTunnels[replyMsgID] = tunnel; } void Tunnels::AddOutboundTunnel (std::shared_ptr newTunnel) { // we don't need to insert it to m_Tunnels - std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (pool && pool->IsActive ()) @@ -914,10 +897,8 @@ namespace tunnel void Tunnels::AddInboundTunnel (std::shared_ptr newTunnel) { - std::unique_lock l(m_TunnelsMutex); if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second) { - std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (!pool) @@ -945,8 +926,6 @@ namespace tunnel auto inboundTunnel = std::make_shared (); inboundTunnel->SetTunnelPool (pool); inboundTunnel->SetState (eTunnelStateEstablished); - std::unique_lock lt(m_TunnelsMutex); - std::unique_lock li(m_InboundTunnelsMutex); m_InboundTunnels.push_back (inboundTunnel); m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; return inboundTunnel; @@ -957,7 +936,6 @@ namespace tunnel auto outboundTunnel = std::make_shared (); outboundTunnel->SetTunnelPool (pool); outboundTunnel->SetState (eTunnelStateEstablished); - std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.push_back (outboundTunnel); // we don't insert into m_Tunnels return outboundTunnel; @@ -986,7 +964,6 @@ namespace tunnel int timeout = 0; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); // TODO: possible race condition with I2PControl - std::unique_lock l(m_TransitTunnelsMutex); for (const auto& it : m_TransitTunnels) { int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; @@ -997,19 +974,19 @@ namespace tunnel size_t Tunnels::CountTransitTunnels() const { - std::unique_lock l(m_TransitTunnelsMutex); + // TODO: locking return m_TransitTunnels.size(); } size_t Tunnels::CountInboundTunnels() const { - std::unique_lock l(m_InboundTunnelsMutex); + // TODO: locking return m_InboundTunnels.size(); } size_t Tunnels::CountOutboundTunnels() const { - std::unique_lock l(m_OutboundTunnelsMutex); + // TODO: locking return m_OutboundTunnels.size(); } } diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 640a9ca7..56166e0b 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -68,7 +68,7 @@ namespace tunnel public: Tunnel (std::shared_ptr config); - virtual ~Tunnel (); + ~Tunnel (); void Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel = nullptr); @@ -254,18 +254,12 @@ namespace tunnel bool m_IsRunning; std::thread * m_Thread; std::map > m_PendingInboundTunnels; // by replyMsgID - mutable std::mutex m_PendingInboundTunnelsMutex; std::map > m_PendingOutboundTunnels; // by replyMsgID - mutable std::mutex m_PendingOutboundTunnelsMutex; std::list > m_InboundTunnels; - mutable std::recursive_mutex m_InboundTunnelsMutex; std::list > m_OutboundTunnels; - mutable std::recursive_mutex m_OutboundTunnelsMutex; std::list > m_TransitTunnels; - mutable std::mutex m_TransitTunnelsMutex; std::unordered_map > m_Tunnels; // tunnelID->tunnel known by this id - mutable std::recursive_mutex m_TunnelsMutex; - mutable std::mutex m_PoolsMutex; + std::mutex m_PoolsMutex; std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index ae924ba6..f198dffd 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -99,7 +99,7 @@ namespace tunnel m_LastHop->SetReplyHop (replyTunnelID, replyIdent); } - virtual ~TunnelConfig () + ~TunnelConfig () { TunnelHopConfig * hop = m_FirstHop; diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 051ecdc6..5ed330c8 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -59,7 +59,6 @@ namespace tunnel void TunnelPool::SetExplicitPeers (std::shared_ptr > explicitPeers) { - std::unique_lock l(m_ExplicitPeersMutex); m_ExplicitPeers = explicitPeers; if (m_ExplicitPeers) { @@ -93,7 +92,6 @@ namespace tunnel it->SetTunnelPool (nullptr); m_OutboundTunnels.clear (); } - std::unique_lock l(m_TestsMutex); m_Tests.clear (); } @@ -137,11 +135,10 @@ namespace tunnel if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); - std::unique_lock lt(m_TestsMutex); for (auto& it: m_Tests) if (it.second.second == expiredTunnel) it.second.second = nullptr; - std::unique_lock li(m_InboundTunnelsMutex); + std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.erase (expiredTunnel); } } @@ -160,11 +157,10 @@ namespace tunnel if (expiredTunnel) { expiredTunnel->SetTunnelPool (nullptr); - std::unique_lock lt(m_TestsMutex); for (auto& it: m_Tests) if (it.second.first == expiredTunnel) it.second.first = nullptr; - std::unique_lock lo(m_OutboundTunnelsMutex); + std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.erase (expiredTunnel); } } @@ -269,7 +265,7 @@ namespace tunnel { int num = 0; { - std::unique_lock lo(m_OutboundTunnelsMutex); + std::unique_lock l(m_OutboundTunnelsMutex); for (const auto& it : m_OutboundTunnels) if (it->IsEstablished ()) num++; } @@ -278,11 +274,10 @@ namespace tunnel num = 0; { - std::unique_lock li(m_InboundTunnelsMutex); + std::unique_lock l(m_InboundTunnelsMutex); for (const auto& it : m_InboundTunnels) if (it->IsEstablished ()) num++; } - std::unique_lock lo(m_OutboundTunnelsMutex); if (!num && !m_OutboundTunnels.empty () && m_NumOutboundHops > 0) { for (auto it: m_OutboundTunnels) @@ -292,7 +287,6 @@ namespace tunnel if (num >= m_NumInboundTunnels) break; } } - lo.unlock(); for (int i = num; i < m_NumInboundTunnels; i++) CreateInboundTunnel (); @@ -317,7 +311,7 @@ namespace tunnel if (it.second.first->GetState () == eTunnelStateTestFailed) { it.second.first->SetState (eTunnelStateFailed); - std::unique_lock lo(m_OutboundTunnelsMutex); + std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.erase (it.second.first); } else @@ -329,7 +323,7 @@ namespace tunnel { it.second.second->SetState (eTunnelStateFailed); { - std::unique_lock li(m_InboundTunnelsMutex); + std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.erase (it.second.second); } if (m_LocalDestination) @@ -341,10 +335,7 @@ namespace tunnel } // new tests - std::unique_lock lt(m_TestsMutex); - std::unique_lock lo(m_OutboundTunnelsMutex); auto it1 = m_OutboundTunnels.begin (); - std::unique_lock li(m_InboundTunnelsMutex); auto it2 = m_InboundTunnels.begin (); while (it1 != m_OutboundTunnels.end () && it2 != m_InboundTunnels.end ()) { @@ -364,6 +355,7 @@ namespace tunnel uint32_t msgID; RAND_bytes ((uint8_t *)&msgID, 4); { + std::unique_lock l(m_TestsMutex); m_Tests[msgID] = std::make_pair (*it1, *it2); } (*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), @@ -388,7 +380,7 @@ namespace tunnel if (m_LocalDestination) m_LocalDestination->ProcessGarlicMessage (msg); else - LogPrint (eLogWarning, "Tunnels: local destination doesn't exist, garlic message dropped"); + LogPrint (eLogWarning, "Tunnels: local destination doesn't exist, dropped"); } void TunnelPool::ProcessDeliveryStatus (std::shared_ptr msg) @@ -436,7 +428,7 @@ namespace tunnel if (m_LocalDestination) m_LocalDestination->ProcessDeliveryStatusMessage (msg); else - LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, delivery status message dropped"); + LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped"); } } @@ -520,16 +512,13 @@ namespace tunnel if (m_CustomPeerSelector) return m_CustomPeerSelector->SelectPeers(path, numHops, isInbound); } - // explicit peers in use - std::lock_guard lock(m_ExplicitPeersMutex); if (m_ExplicitPeers) return SelectExplicitPeers (path, isInbound); return StandardSelectPeers(path, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1, std::placeholders::_2)); } bool TunnelPool::SelectExplicitPeers (Path& path, bool isInbound) { - std::unique_lock l(m_ExplicitPeersMutex); int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; if (numHops > (int)m_ExplicitPeers->size ()) numHops = m_ExplicitPeers->size (); if (!numHops) return false; diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index b32bafa3..875a9955 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -128,7 +128,6 @@ namespace tunnel std::shared_ptr m_LocalDestination; int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels; - mutable std::mutex m_ExplicitPeersMutex; std::shared_ptr > m_ExplicitPeers; mutable std::mutex m_InboundTunnelsMutex; std::set, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first From c0400bfd0789940b118e444cca7d4d8f3d87b9de Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 31 Oct 2021 22:14:59 -0400 Subject: [PATCH 4476/6300] virtual destructor for TunnelConfig --- libi2pd/TunnelConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index f198dffd..ae924ba6 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -99,7 +99,7 @@ namespace tunnel m_LastHop->SetReplyHop (replyTunnelID, replyIdent); } - ~TunnelConfig () + virtual ~TunnelConfig () { TunnelHopConfig * hop = m_FirstHop; From 1a8a32a773deffa80ccd03102cfa687eb1622a9e Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 5 Nov 2021 14:51:24 -0400 Subject: [PATCH 4477/6300] select next tunnel with compatible transports --- libi2pd/Destination.cpp | 28 ++++++++++++++-------------- libi2pd/RouterInfo.h | 3 ++- libi2pd/Tunnel.cpp | 2 +- libi2pd/Tunnel.h | 1 + libi2pd/TunnelConfig.h | 4 ++-- libi2pd/TunnelPool.cpp | 15 +++++++++------ libi2pd/TunnelPool.h | 9 ++++++--- 7 files changed, 35 insertions(+), 27 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index 54137664..f523243a 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -555,18 +555,6 @@ namespace client shared_from_this (), std::placeholders::_1)); return; } - auto outbound = m_Pool->GetNextOutboundTunnel (); - if (!outbound) - { - LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); - return; - } - auto inbound = m_Pool->GetNextInboundTunnel (); - if (!inbound) - { - LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); - return; - } auto floodfill = i2p::data::netdb.GetClosestFloodfill (leaseSet->GetIdentHash (), m_ExcludedFloodfills); if (!floodfill) { @@ -575,6 +563,18 @@ namespace client return; } m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); + auto outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)); + if (!outbound) + { + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); + return; + } + auto inbound = m_Pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)); + if (!inbound) + { + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); + return; + } LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); auto msg = WrapMessageForRouter (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound)); @@ -751,10 +751,10 @@ namespace client std::shared_ptr nextFloodfill, std::shared_ptr request) { if (!request->replyTunnel || !request->replyTunnel->IsEstablished ()) - request->replyTunnel = m_Pool->GetNextInboundTunnel (); + request->replyTunnel = m_Pool->GetNextInboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (true)); if (!request->replyTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no inbound tunnels found"); if (!request->outboundTunnel || !request->outboundTunnel->IsEstablished ()) - request->outboundTunnel = m_Pool->GetNextOutboundTunnel (); + request->outboundTunnel = m_Pool->GetNextOutboundTunnel (nullptr, nextFloodfill->GetCompatibleTransports (false)); if (!request->outboundTunnel) LogPrint (eLogError, "Destination: Can't send LeaseSet request, no outbound tunnels found"); if (request->replyTunnel && request->outboundTunnel) diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index b2b883dc..de1fe235 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -64,7 +64,8 @@ namespace data eNTCP2V6 = 0x02, eSSUV4 = 0x04, eSSUV6 = 0x08, - eNTCP2V6Mesh = 0x10 + eNTCP2V6Mesh = 0x10, + eAllTransports = 0xFF }; typedef uint8_t CompatibleTransports; diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp index a315ff49..ad048bb8 100644 --- a/libi2pd/Tunnel.cpp +++ b/libi2pd/Tunnel.cpp @@ -32,7 +32,7 @@ namespace tunnel Tunnel::Tunnel (std::shared_ptr config): TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()), m_Config (config), m_IsShortBuildMessage (false), m_Pool (nullptr), - m_State (eTunnelStatePending), m_FarEndTransports (0), + m_State (eTunnelStatePending), m_FarEndTransports (i2p::data::RouterInfo::eAllTransports), m_IsRecreated (false), m_Latency (0) { } diff --git a/libi2pd/Tunnel.h b/libi2pd/Tunnel.h index 56166e0b..3ab366c8 100644 --- a/libi2pd/Tunnel.h +++ b/libi2pd/Tunnel.h @@ -76,6 +76,7 @@ namespace tunnel std::vector > GetPeers () const; std::vector > GetInvertedPeers () const; bool IsShortBuildMessage () const { return m_IsShortBuildMessage; }; + i2p::data::RouterInfo::CompatibleTransports GetFarEndTransports () const { return m_FarEndTransports; }; TunnelState GetState () const { return m_State; }; void SetState (TunnelState state); bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index ae924ba6..c514a404 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -82,7 +82,7 @@ namespace tunnel public: TunnelConfig (const std::vector >& peers, - bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports = 0): // inbound + bool isShort, i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports): // inbound m_IsShort (isShort), m_FarEndTransports (farEndTransports) { CreatePeers (peers); @@ -91,7 +91,7 @@ namespace tunnel TunnelConfig (const std::vector >& peers, uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent, bool isShort, - i2p::data::RouterInfo::CompatibleTransports farEndTransports = 0): // outbound + i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports): // outbound m_IsShort (isShort), m_FarEndTransports (farEndTransports) { CreatePeers (peers); diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index 5ed330c8..c9897b29 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -190,20 +190,23 @@ namespace tunnel return v; } - std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded) const + std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded, + i2p::data::RouterInfo::CompatibleTransports compatible) const { std::unique_lock l(m_OutboundTunnelsMutex); - return GetNextTunnel (m_OutboundTunnels, excluded); + return GetNextTunnel (m_OutboundTunnels, excluded, compatible); } - std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded) const + std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded, + i2p::data::RouterInfo::CompatibleTransports compatible) const { std::unique_lock l(m_InboundTunnelsMutex); - return GetNextTunnel (m_InboundTunnels, excluded); + return GetNextTunnel (m_InboundTunnels, excluded, compatible); } template - typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const + typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, + typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const { if (tunnels.empty ()) return nullptr; uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0; @@ -211,7 +214,7 @@ namespace tunnel typename TTunnels::value_type tunnel = nullptr; for (const auto& it: tunnels) { - if (it->IsEstablished () && it != excluded) + if (it->IsEstablished () && it != excluded && (compatible & it->GetFarEndTransports ())) { if (it->IsSlow () || (HasLatencyRequirement() && it->LatencyIsKnown() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency))) diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index 875a9955..2f4018ce 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -76,8 +76,10 @@ namespace tunnel void RecreateInboundTunnel (std::shared_ptr tunnel); void RecreateOutboundTunnel (std::shared_ptr tunnel); std::vector > GetInboundTunnels (int num) const; - std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr) const; - std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr) const; + std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr, + i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const; + std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr, + i2p::data::RouterInfo::CompatibleTransports compatible = i2p::data::RouterInfo::eAllTransports) const; std::shared_ptr GetNewOutboundTunnel (std::shared_ptr old) const; void TestTunnels (); void ManageTunnels (uint64_t ts); @@ -120,7 +122,8 @@ namespace tunnel void CreateOutboundTunnel (); void CreatePairedInboundTunnel (std::shared_ptr outboundTunnel); template - typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; + typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, + typename TTunnels::value_type excluded, i2p::data::RouterInfo::CompatibleTransports compatible) const; bool SelectPeers (Path& path, bool isInbound); bool SelectExplicitPeers (Path& path, bool isInbound); From 8f0978cfd6439559803b5e6ba6c22dcd20f59dff Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 6 Nov 2021 10:49:18 -0400 Subject: [PATCH 4478/6300] all transports by default --- libi2pd/TunnelConfig.h | 2 +- libi2pd/TunnelPool.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/TunnelConfig.h b/libi2pd/TunnelConfig.h index c514a404..7934bc6b 100644 --- a/libi2pd/TunnelConfig.h +++ b/libi2pd/TunnelConfig.h @@ -185,7 +185,7 @@ namespace tunnel // this constructor can't be called from outside TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr), m_IsShort (false), - m_FarEndTransports (0) + m_FarEndTransports (i2p::data::RouterInfo::eAllTransports) { } diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h index 2f4018ce..41cc400f 100644 --- a/libi2pd/TunnelPool.h +++ b/libi2pd/TunnelPool.h @@ -40,7 +40,7 @@ namespace tunnel { std::vector peers; bool isShort = true; - i2p::data::RouterInfo::CompatibleTransports farEndTransports = 0; + i2p::data::RouterInfo::CompatibleTransports farEndTransports = i2p::data::RouterInfo::eAllTransports; void Add (std::shared_ptr r); void Reverse (); From f8c390cdd3a4b9f375f97b9ed1c7c15b8f8cc11a Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 6 Nov 2021 15:44:56 -0400 Subject: [PATCH 4479/6300] pick compatible tunnels --- libi2pd/Destination.cpp | 2 +- libi2pd/NetDb.cpp | 8 ++--- libi2pd/TunnelPool.cpp | 72 ++++++++++++++++++++--------------------- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp index f523243a..9341baa4 100644 --- a/libi2pd/Destination.cpp +++ b/libi2pd/Destination.cpp @@ -562,7 +562,6 @@ namespace client m_ExcludedFloodfills.clear (); return; } - m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); auto outbound = m_Pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)); if (!outbound) { @@ -575,6 +574,7 @@ namespace client LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); return; } + m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); auto msg = WrapMessageForRouter (floodfill, i2p::CreateDatabaseStoreMsg (leaseSet, m_PublishReplyToken, inbound)); diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index b4ffe2bd..7123741a 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -681,8 +681,8 @@ namespace data else { auto pool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = pool ? pool->GetNextOutboundTunnel () : nullptr; - auto inbound = pool ? pool->GetNextInboundTunnel () : nullptr; + auto outbound = pool ? pool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)) : nullptr; + auto inbound = pool ? pool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)) : nullptr; if (outbound && inbound) outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, dest->CreateRequestMessage (floodfill, inbound)); else @@ -1129,8 +1129,8 @@ namespace data { // otherwise through exploratory auto exploratoryPool = i2p::tunnel::tunnels.GetExploratoryPool (); - auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel () : nullptr; - auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel () : nullptr; + auto outbound = exploratoryPool ? exploratoryPool->GetNextOutboundTunnel (nullptr, floodfill->GetCompatibleTransports (false)) : nullptr; + auto inbound = exploratoryPool ? exploratoryPool->GetNextInboundTunnel (nullptr, floodfill->GetCompatibleTransports (true)) : nullptr; if (inbound && outbound) outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, CreateDatabaseStoreMsg (i2p::context.GetSharedRouterInfo (), replyToken, inbound)); diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index c9897b29..a67b6618 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -498,9 +498,8 @@ namespace tunnel } prevHop = hop; path.Add (hop); - if (i == numHops - 1) - path.farEndTransports = hop->GetCompatibleTransports (inbound); } + path.farEndTransports = prevHop->GetCompatibleTransports (inbound); // last hop return true; } @@ -555,13 +554,13 @@ namespace tunnel void TunnelPool::CreateInboundTunnel () { - auto outboundTunnel = GetNextOutboundTunnel (); - if (!outboundTunnel) - outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Creating destination inbound tunnel..."); Path path; if (SelectPeers (path, true)) { + auto outboundTunnel = GetNextOutboundTunnel (nullptr, path.farEndTransports); + if (!outboundTunnel) + outboundTunnel = tunnels.GetNextOutboundTunnel (); std::shared_ptr config; if (m_NumInboundHops > 0) { @@ -583,7 +582,7 @@ namespace tunnel CreateInboundTunnel (); return; } - auto outboundTunnel = GetNextOutboundTunnel (); + auto outboundTunnel = GetNextOutboundTunnel (nullptr, tunnel->GetFarEndTransports ()); if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel..."); @@ -604,40 +603,41 @@ namespace tunnel void TunnelPool::CreateOutboundTunnel () { - auto inboundTunnel = GetNextInboundTunnel (); - if (!inboundTunnel) - inboundTunnel = tunnels.GetNextInboundTunnel (); - if (inboundTunnel) + LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); + Path path; + if (SelectPeers (path, false)) { - LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); - Path path; - if (SelectPeers (path, false)) + auto inboundTunnel = GetNextInboundTunnel (nullptr, path.farEndTransports); + if (!inboundTunnel) + inboundTunnel = tunnels.GetNextInboundTunnel (); + if (!inboundTunnel) { - if (m_LocalDestination && !m_LocalDestination->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) - path.isShort = false; // because can't handle ECIES encrypted reply - - std::shared_ptr config; - if (m_NumOutboundHops > 0) - config = std::make_shared(path.peers, inboundTunnel->GetNextTunnelID (), - inboundTunnel->GetNextIdentHash (), path.isShort, path.farEndTransports); + LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no inbound tunnels found"); + return; + } + + if (m_LocalDestination && !m_LocalDestination->SupportsEncryptionType (i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD)) + path.isShort = false; // because can't handle ECIES encrypted reply + + std::shared_ptr config; + if (m_NumOutboundHops > 0) + config = std::make_shared(path.peers, inboundTunnel->GetNextTunnelID (), + inboundTunnel->GetNextIdentHash (), path.isShort, path.farEndTransports); - std::shared_ptr tunnel; - if (path.isShort) - { - // TODO: implement it better - tunnel = tunnels.CreateOutboundTunnel (config, inboundTunnel->GetTunnelPool ()); - tunnel->SetTunnelPool (shared_from_this ()); - } - else - tunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); - if (tunnel && tunnel->IsEstablished ()) // zero hops - TunnelCreated (tunnel); - } - else - LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); + std::shared_ptr tunnel; + if (path.isShort) + { + // TODO: implement it better + tunnel = tunnels.CreateOutboundTunnel (config, inboundTunnel->GetTunnelPool ()); + tunnel->SetTunnelPool (shared_from_this ()); + } + else + tunnel = tunnels.CreateOutboundTunnel (config, shared_from_this ()); + if (tunnel && tunnel->IsEstablished ()) // zero hops + TunnelCreated (tunnel); } else - LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no inbound tunnels found"); + LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); } void TunnelPool::RecreateOutboundTunnel (std::shared_ptr tunnel) @@ -647,7 +647,7 @@ namespace tunnel CreateOutboundTunnel (); return; } - auto inboundTunnel = GetNextInboundTunnel (); + auto inboundTunnel = GetNextInboundTunnel (nullptr, tunnel->GetFarEndTransports ()); if (!inboundTunnel) inboundTunnel = tunnels.GetNextInboundTunnel (); if (inboundTunnel) From 3f63f15b16e8c512e5dda777f1266d6a50d11409 Mon Sep 17 00:00:00 2001 From: orignal Date: Sat, 6 Nov 2021 19:16:45 -0400 Subject: [PATCH 4480/6300] copy compatible transports to new tunnel --- libi2pd/TunnelPool.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp index a67b6618..7bce0519 100644 --- a/libi2pd/TunnelPool.cpp +++ b/libi2pd/TunnelPool.cpp @@ -588,9 +588,7 @@ namespace tunnel LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel..."); std::shared_ptr config; if (m_NumInboundHops > 0 && tunnel->GetPeers().size()) - { - config = std::make_shared(tunnel->GetPeers (), tunnel->IsShortBuildMessage ()); - } + config = std::make_shared(tunnel->GetPeers (), tunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); if (!m_NumInboundHops || config) { auto newTunnel = tunnels.CreateInboundTunnel (config, shared_from_this(), outboundTunnel); @@ -657,7 +655,7 @@ namespace tunnel if (m_NumOutboundHops > 0 && tunnel->GetPeers().size()) { config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), - inboundTunnel->GetNextIdentHash (), inboundTunnel->IsShortBuildMessage ()); + inboundTunnel->GetNextIdentHash (), inboundTunnel->IsShortBuildMessage (), tunnel->GetFarEndTransports ()); } if (!m_NumOutboundHops || config) { From d798faa1cad7a80dc7cc98d7ea9cc95e211c2a44 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 7 Nov 2021 17:18:31 -0500 Subject: [PATCH 4481/6300] pick compatible ooutbound tunnel --- libi2pd/Streaming.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index be3d09d8..8be6811b 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -828,13 +828,6 @@ namespace stream m_RTO = m_RTT*1.5; // TODO: implement it better } } - if (!m_CurrentOutboundTunnel || !m_CurrentOutboundTunnel->IsEstablished ()) - m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); - if (!m_CurrentOutboundTunnel) - { - LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); - return; - } auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate || // excluded from LeaseSet @@ -842,6 +835,21 @@ namespace stream UpdateCurrentRemoteLease (true); if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) { + if (!m_CurrentOutboundTunnel) + { + auto leaseRouter = i2p::data::netdb.FindRouter (m_CurrentRemoteLease->tunnelGateway); + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (nullptr, + leaseRouter ? leaseRouter->GetCompatibleTransports (false) : (i2p::data::RouterInfo::CompatibleTransports)i2p::data::RouterInfo::eAllTransports); + } + else if (!m_CurrentOutboundTunnel->IsEstablished ()) + m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); + if (!m_CurrentOutboundTunnel) + { + LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); + m_CurrentRemoteLease = nullptr; + return; + } + std::vector msgs; for (const auto& it: packets) { From 49883dc3ac43054ed1090107ed761d95074ef2eb Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 8 Nov 2021 07:02:11 +0300 Subject: [PATCH 4482/6300] [webconsole] update stylesheet (closes #1699) Signed-off-by: R4SAS --- contrib/webconsole/style-dark.css | 272 ------------------------------ contrib/webconsole/style.css | 107 +++++++++--- daemon/HTTPServerResources.h | 97 ++++++----- 3 files changed, 133 insertions(+), 343 deletions(-) delete mode 100644 contrib/webconsole/style-dark.css diff --git a/contrib/webconsole/style-dark.css b/contrib/webconsole/style-dark.css deleted file mode 100644 index 6ec19023..00000000 --- a/contrib/webconsole/style-dark.css +++ /dev/null @@ -1,272 +0,0 @@ -:root { - --main-bg-color: #121212; - --main-text-color: #156A3D; - --main-link-color: #894C84; -} - -body { - font: 100%/1.5em sans-serif; - margin: 0; - padding: 1.5em; - background: var(--main-bg-color); - color: var(--main-text-color); -} - -a, .slide label { - text-decoration: none; - color: var(--main-link-color); -} - -a:hover, .slide label:hover, button[type=submit]:hover { - color: #FAFAFA; - background: var(--main-link-color); -} - -a.button { - appearance: button; - text-decoration: none; - padding: 0 5px; - border: 1px solid var(--main-link-color); -} - -.header { - font-size: 2.5em; - text-align: center; - margin: 1em 0; - color: var(--main-link-color); -} - -.wrapper { - margin: 0 auto; - padding: 1em; - max-width: 64em; -} - -.menu { - display: block; - float: left; - overflow: hidden; - max-width: 12em; - white-space: nowrap; - text-overflow: ellipsis; -} - -.listitem { - display: block; - font-family: monospace; - font-size: 1.2em; - white-space: nowrap; -} - -.tableitem { - font-family: monospace; - font-size: 1.2em; - white-space: nowrap; -} - -.content { - float: left; - font-size: 1em; - margin-left: 4em; - max-width: 48em; - overflow: auto; -} - -.tunnel.established { - color: #56B734; -} - -.tunnel.expiring { - color: #D3AE3F; -} - -.tunnel.failed { - color: #D33F3F; -} - -.tunnel.building { - color: #434343; -} - -caption { - font-size: 1.5em; - text-align: center; - color: var(--main-link-color); -} - -table { - display: table; - border-collapse: collapse; - text-align: center; -} - -table.extaddr { - text-align: left; -} - -table.services { - width: 100%; -} - -textarea { - background-color: var(--main-bg-color); - color: var(--main-text-color); - word-break: break-all; -} - -.streamdest { - width: 120px; - max-width: 240px; - overflow: hidden; - text-overflow: ellipsis; -} - -.slide div.slidecontent, .slide [type="checkbox"] { - display: none; -} - -.slide [type="checkbox"]:checked ~ div.slidecontent { - display: block; - margin-top: 0; - padding: 0; -} - -.disabled { - color: #D33F3F; -} - -.enabled { - color: #56B734; -} - -button[type=submit] { - background-color: transparent; - color: var(--main-link-color); - text-decoration: none; - padding: 5px; - border: 1px solid var(--main-link-color); - font-size: 14px; -} - -input, select, select option { - background-color: var(--main-bg-color); - color: var(--main-link-color); - padding: 5px; - border: 1px solid var(--main-link-color); - font-size: 14px; -} - -input:focus, select:focus, select option:focus { - outline: none; -} - -input[type=number]::-webkit-inner-spin-button { - -webkit-appearance: none; -} - -@media screen and (max-width: 1150px) { /* adaptive style */ - .wrapper { - max-width: 58em; - } - - .menu { - max-width: 10em; - } - - .content { - margin-left: 2em; - max-width: 42em; - } -} - -@media screen and (max-width: 980px) { - body { - font: 100%/1.2em sans-serif; - padding: 1.2em 0 0 0; - } - - .menu { - width: 100%; - max-width: unset; - display: block; - float: none; - position: unset; - font-size: 16px; - text-align: center; - } - - .menu a, .commands a { - display: inline-block; - padding: 4px; - } - - .content { - float: none; - margin-left: unset; - margin-top: 16px; - max-width: 100%; - width: 100%; - text-align: center; - } - - a, .slide label { - /* margin-right: 10px; */ - display: block; - /* font-size: 18px; */ - } - - .header { - margin: unset; - font-size: 1.5em; - } - - small { - display: block - } - - a.button { - appearance: button; - text-decoration: none; - margin-top: 10px; - padding: 6px; - border: 2px solid var(--main-link-color); - border-radius: 5px; - width: -webkit-fill-available; - } - - input, select { - width: 35%; - text-align: center; - padding: 5px; - border: 2px solid var(--main-link-color); - border-radius: 5px; - font-size: 18px; - } - - table.extaddr { - margin: auto; - text-align: unset; - } - - textarea { - width: -webkit-fill-available; - height: auto; - padding: 5px; - border: 2px solid var(--main-link-color); - border-radius: 5px; - font-size: 12px; - } - - button[type=submit] { - padding: 5px 15px; - background: transparent; - border: 2px solid var(--main-link-color); - cursor: pointer; - -webkit-border-radius: 5px; - border-radius: 5px; - position: relative; - height: 36px; - display: -webkit-inline-box; - margin-top: 10px; - } -} \ No newline at end of file diff --git a/contrib/webconsole/style.css b/contrib/webconsole/style.css index 047839a6..3a6ab059 100644 --- a/contrib/webconsole/style.css +++ b/contrib/webconsole/style.css @@ -1,35 +1,63 @@ +/* + * Copyright (c) 2013-2021, The PurpleI2P Project + * + * This file is part of Purple i2pd project and licensed under BSD3 + * + * See full license text in LICENSE file at top of project tree + * + ****************************************************************** + * + * This is styles heet for webconsole, with @media selectors for adaptive + * view on desktop and mobile devices, respecting preferred user's color + * scheme used in system/browser. + * + * Minified copy of that style sheet is bundled inside i2pd sources. +*/ + +:root { + --main-bg-color: #FAFAFA; + --main-text-color: #103456; + --main-link-color: #894C84; +} + +@media (prefers-color-scheme: dark) { + :root { + --main-bg-color: #121212; + --main-text-color: #156A3D; + --main-link-color: #894C84; + } +} + body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; - background: #FAFAFA; - color: #103456; + background: var(--main-bg-color); + color: var(--main-text-color); } a, .slide label { text-decoration: none; - color: #894C84; + color: var(--main-link-color); } -a:hover, .slide label:hover { +a:hover, .slide label:hover, button[type=submit]:hover { color: #FAFAFA; - background: #894C84; + background: var(--main-link-color); } a.button { - -webkit-appearance: button; - -moz-appearance: button; appearance: button; text-decoration: none; padding: 0 5px; - border: 1px solid #894C84; + border: 1px solid var(--main-link-color); } .header { font-size: 2.5em; text-align: center; margin: 1em 0; - color: #894C84; + color: var(--main-link-color); } .wrapper { @@ -42,6 +70,7 @@ a.button { display: block; float: left; overflow: hidden; + padding: 4px; max-width: 12em; white-space: nowrap; text-overflow: ellipsis; @@ -63,8 +92,9 @@ a.button { .content { float: left; font-size: 1em; - margin-left: 4em; - max-width: 48em; + margin-left: 2em; + padding: 4px; + max-width: 50em; overflow: auto; } @@ -87,7 +117,7 @@ a.button { caption { font-size: 1.5em; text-align: center; - color: #894C84; + color: var(--main-link-color); } table { @@ -105,6 +135,8 @@ table.services { } textarea { + background-color: var(--main-bg-color); + color: var(--main-text-color); word-break: break-all; } @@ -133,9 +165,34 @@ textarea { color: #56B734; } +button[type=submit] { + background-color: transparent; + color: var(--main-link-color); + text-decoration: none; + padding: 5px; + border: 1px solid var(--main-link-color); + font-size: 14px; +} + +input, select, select option { + background-color: var(--main-bg-color); + color: var(--main-link-color); + padding: 5px; + border: 1px solid var(--main-link-color); + font-size: 14px; +} + +input:focus, select:focus, select option:focus { + outline: none; +} + +input[type=number]::-webkit-inner-spin-button { + -webkit-appearance: none; +} + @media screen and (max-width: 1150px) { /* adaptive style */ .wrapper { - max-width: 58em; + max-width: 60em; } .menu { @@ -144,13 +201,14 @@ textarea { .content { margin-left: 2em; - max-width: 42em; + max-width: 46em; } } @media screen and (max-width: 980px) { body { - padding: 1.5em 0 0 0; + font: 100%/1.2em sans-serif; + padding: 1.2em 0 0 0; } .menu { @@ -178,9 +236,7 @@ textarea { } a, .slide label { - /* margin-right: 10px; */ display: block; - /* font-size: 18px; */ } .header { @@ -193,13 +249,12 @@ textarea { } a.button { - -webkit-appearance: button; - -moz-appearance: button; appearance: button; text-decoration: none; margin-top: 10px; padding: 6px; - border: 1px solid #894c84; + border: 2px solid var(--main-link-color); + border-radius: 5px; width: -webkit-fill-available; } @@ -207,8 +262,7 @@ textarea { width: 35%; text-align: center; padding: 5px; - border: 2px solid #ccc; - -webkit-border-radius: 5px; + border: 2px solid var(--main-link-color); border-radius: 5px; font-size: 18px; } @@ -221,17 +275,16 @@ textarea { textarea { width: -webkit-fill-available; height: auto; - padding:5px; - border:2px solid #ccc; - -webkit-border-radius: 5px; + padding: 5px; + border: 2px solid var(--main-link-color); border-radius: 5px; font-size: 12px; } button[type=submit] { padding: 5px 15px; - background: #ccc; - border: 0 none; + background: transparent; + border: 2px solid var(--main-link-color); cursor: pointer; -webkit-border-radius: 5px; border-radius: 5px; diff --git a/daemon/HTTPServerResources.h b/daemon/HTTPServerResources.h index 876948e8..75cf2d6e 100644 --- a/daemon/HTTPServerResources.h +++ b/daemon/HTTPServerResources.h @@ -34,50 +34,59 @@ namespace http // bundled style sheet const std::string internalCSS = "\r\n"; // for external style sheet From fdde197c5800c723eba76eb891b160a8dfc695de Mon Sep 17 00:00:00 2001 From: R4SAS Date: Mon, 8 Nov 2021 07:40:39 +0300 Subject: [PATCH 4483/6300] [webconsole] update stylesheet Signed-off-by: R4SAS --- contrib/webconsole/style.css | 12 ++++++------ daemon/HTTPServerResources.h | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contrib/webconsole/style.css b/contrib/webconsole/style.css index 3a6ab059..861e747a 100644 --- a/contrib/webconsole/style.css +++ b/contrib/webconsole/style.css @@ -7,7 +7,7 @@ * ****************************************************************** * - * This is styles heet for webconsole, with @media selectors for adaptive + * This is style sheet for webconsole, with @media selectors for adaptive * view on desktop and mobile devices, respecting preferred user's color * scheme used in system/browser. * @@ -22,7 +22,7 @@ @media (prefers-color-scheme: dark) { :root { - --main-bg-color: #121212; + --main-bg-color: #181818; --main-text-color: #156A3D; --main-link-color: #894C84; } @@ -63,7 +63,7 @@ a.button { .wrapper { margin: 0 auto; padding: 1em; - max-width: 64em; + max-width: 60em; } .menu { @@ -92,7 +92,7 @@ a.button { .content { float: left; font-size: 1em; - margin-left: 2em; + margin-left: 4em; padding: 4px; max-width: 50em; overflow: auto; @@ -192,7 +192,7 @@ input[type=number]::-webkit-inner-spin-button { @media screen and (max-width: 1150px) { /* adaptive style */ .wrapper { - max-width: 60em; + max-width: 56em; } .menu { @@ -201,7 +201,7 @@ input[type=number]::-webkit-inner-spin-button { .content { margin-left: 2em; - max-width: 46em; + max-width: 44em; } } diff --git a/daemon/HTTPServerResources.h b/daemon/HTTPServerResources.h index 75cf2d6e..89c742d3 100644 --- a/daemon/HTTPServerResources.h +++ b/daemon/HTTPServerResources.h @@ -35,17 +35,17 @@ namespace http const std::string internalCSS = "